/-
Copyright (c) 2016 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura, Jeremy Avigad
-/
import Mathlib.Init.Data.Nat.Basic
import Mathlib.Init.Algebra.Functions

universe u

namespace Nat

lemma succ_ne_self : ∀ n : ℕ, succ n ≠ n
| 0,   h => absurd h (Nat.succ_ne_zero 0)
| n+1, h => succ_ne_self n (Nat.noConfusion h id)

protected lemma eq_zero_of_add_eq_zero_right : ∀ {n m : ℕ}, n + m = 0 → n = 0
| 0,   m => by simp [Nat.zero_add]
| n+1, m => fun h => by
  exfalso
  rw [add_one, succ_add] at h
  apply succ_ne_zero _ h

protected lemma eq_zero_of_add_eq_zero_left {n m : ℕ} (h : n + m = 0) : m = 0 :=
@Nat.eq_zero_of_add_eq_zero_right m n (Nat.add_comm n m ▸ h)

attribute [simp] Nat.pred_zero Nat.pred_succ

/-

private meta def sort_add :=
`[simp [nat.add_assoc, nat.add_comm, nat.add_left_comm]]
-/

/- properties of inequality -/

protected lemma pos_of_ne_zero {n : ℕ} : n ≠ 0 → 0 < n :=
Or.resolve_left (eq_zero_or_pos n)

protected lemma lt_iff_le_not_le {m n : ℕ} : m < n ↔ m ≤ n ∧ ¬ n ≤ m :=
⟨fun h => ⟨Nat.le_of_lt h, Nat.not_le_of_gt h⟩, fun h => Nat.gt_of_not_le h.2⟩

instance : LinearOrder ℕ :=
{ le := Nat.le,
  le_refl := @Nat.le_refl,
  le_trans := @Nat.le_trans,
  le_antisymm := @Nat.le_antisymm,
  le_total := @Nat.le_total,
  lt := Nat.lt,
  lt_iff_le_not_le := @Nat.lt_iff_le_not_le,
  decidable_lt               := inferInstance,
  decidable_le               := inferInstance,
  decidable_eq               := inferInstance }

lemma pred_lt_pred : ∀ {n m : ℕ}, n ≠ 0 → n < m → pred n < pred m
| 0,   _,   h, _ => (h rfl).elim
| n+1, m+1, _, h => lt_of_succ_lt_succ h

-- Port note: this lemma was not present in Lean 3
protected lemma add_left_cancel_iff {n m k : ℕ} : n + m = n + k ↔ m = k :=
⟨Nat.add_left_cancel, fun | rfl => rfl⟩

protected lemma add_le_add_iff_le_right (k n m : ℕ) : n + k ≤ m + k ↔ n ≤ m :=
⟨Nat.le_of_add_le_add_right, fun h => Nat.add_le_add_right h _⟩

protected lemma lt_of_add_lt_add_left {k n m : ℕ} (h : k + n < k + m) : n < m :=
Nat.lt_of_le_of_ne (Nat.le_of_add_le_add_left (Nat.le_of_lt h)) fun heq =>
  Nat.lt_irrefl (k + m) $ by rwa [heq] at h

protected lemma lt_of_add_lt_add_right {a b c : ℕ} (h : a + b < c + b) : a < c :=
  Nat.lt_of_add_lt_add_left ((by rwa [Nat.add_comm b a, Nat.add_comm b c]): b + a < b + c)

protected lemma lt_add_right (a b c : Nat) : a < b -> a < b + c :=
  fun h => lt_of_lt_of_le h (Nat.le_add_right _ _)

protected lemma lt_add_of_pos_right {n k : ℕ} (h : 0 < k) : n < n + k :=
Nat.add_lt_add_left h n

protected lemma lt_add_of_pos_left {n k : ℕ} (h : 0 < k) : n < k + n :=
by rw [Nat.add_comm]; exact Nat.lt_add_of_pos_right h

/- sub properties -/

attribute [simp] Nat.zero_sub

lemma sub_lt_succ (a b : ℕ) : a - b < succ a :=
lt_succ_of_le (sub_le a b)

protected lemma sub_le_sub_right {n m : ℕ} (h : n ≤ m) : ∀ k, n - k ≤ m - k
| 0   => h
| z+1 => pred_le_pred (Nat.sub_le_sub_right h z)

-- port note: bit0/bit1 items have been omitted.

protected lemma add_self_ne_one : ∀ (n : ℕ), n + n ≠ 1
| n+1, h =>
  have h1 : succ (succ (n + n)) = 1 := succ_add n n ▸ h
  Nat.noConfusion h1 fun.

/- subtraction -/

protected lemma le_of_le_of_sub_le_sub_right {n m k : ℕ} (h₀ : k ≤ m) (h₁ : n - k ≤ m - k) : n ≤ m := by
  revert k m
  induction n with intros m k h₀ h₁
  | zero => exact m.zero_le
  | succ pn hpn => cases k with
       | zero => apply h₁
       | succ k => cases m with
          | zero => cases not_succ_le_zero k h₀
          | succ m => simp [succ_sub_succ] at h₁
                      apply succ_le_succ
                      apply hpn _ h₁
                      apply le_of_succ_le_succ h₀

protected lemma sub_le_sub_right_iff {n m k : ℕ} (h : k ≤ m) : n - k ≤ m - k ↔ n ≤ m :=
⟨Nat.le_of_le_of_sub_le_sub_right h, fun h => Nat.sub_le_sub_right h k⟩

protected theorem add_le_to_le_sub (x : ℕ) {y k : ℕ}
  (h : k ≤ y)
: x + k ≤ y ↔ x ≤ y - k :=
by rw [← Nat.add_sub_cancel x k, Nat.sub_le_sub_right_iff h, Nat.add_sub_cancel]

protected lemma sub_lt_of_pos_le (a b : ℕ) (h₀ : 0 < a) (h₁ : a ≤ b)
: b - a < b :=
by apply Nat.sub_lt _ h₀
   apply lt_of_lt_of_le h₀ h₁

protected theorem sub_one (n : ℕ) : n - 1 = pred n :=
rfl

theorem succ_sub_one (n : ℕ) : succ n - 1 = n :=
rfl

theorem succ_pred_eq_of_pos : ∀ {n : ℕ}, 0 < n → succ (pred n) = n
| succ k, h => rfl

protected theorem le_of_sub_eq_zero : ∀{n m : ℕ}, n - m = 0 → n ≤ m
| n, 0, H => by rw [Nat.sub_zero] at H; simp [H]
| 0, m+1, H => Nat.zero_le (m + 1)
| n+1, m+1, H => Nat.add_le_add_right
  (Nat.le_of_sub_eq_zero (by simp [Nat.add_sub_add_right] at H; exact H)) _

protected theorem eq_zero_of_nonpos : ∀ (n : Nat), ¬0 < n -> n = 0
| 0 => fun _ => rfl
| n+1 => fun h => absurd (Nat.zero_lt_succ n) h

protected lemma sub_eq_zero_iff_le : n - m = 0 ↔ n ≤ m :=
⟨Nat.le_of_sub_eq_zero, Nat.sub_eq_zero_of_le⟩

protected lemma sub_eq_iff_eq_add {a b c : ℕ} (ab : b ≤ a) : a - b = c ↔ a = c + b :=
⟨fun c_eq => by rw [c_eq.symm, Nat.sub_add_cancel ab],
 fun a_eq => by rw [a_eq, Nat.add_sub_cancel]⟩

protected lemma lt_of_sub_eq_succ (H : m - n = succ l) : n < m :=
not_le.1 fun H' => by simp [Nat.sub_eq_zero_of_le H'] at H

@[simp] protected lemma min_eq_min (a : ℕ) : Nat.min a b = min a b := rfl

protected lemma zero_min (a : ℕ) : min 0 a = 0 :=
min_eq_left (zero_le a)

protected lemma min_zero (a : ℕ) : min a 0 = 0 :=
min_eq_right (zero_le a)

-- Distribute succ over min
theorem min_succ_succ (x y : ℕ) : min (succ x) (succ y) = succ (min x y) :=
have f : x ≤ y → min (succ x) (succ y) = succ (min x y) := by
  intro p
  have h1 : min (succ x) (succ y) = succ x := if_pos (succ_le_succ p)
  have h2 : succ x = succ (min x y) := congr_arg succ (Eq.symm (if_pos p))
  rwa [←h2]
have g : ¬ (x ≤ y) → min (succ x) (succ y) = succ (min x y) := by
  intro p
  have h1 : min (succ x) (succ y) = succ y :=
    if_neg (λeq => let hp : pred (succ x) ≤ pred (succ y) := (pred_le_pred eq)
                   let hp2 : x ≤ y := hp
                   p hp)
  have h2 : succ y = succ (min x y) := congr_arg succ (Eq.symm (if_neg p))
  rwa [←h2]
Decidable.by_cases f g

theorem sub_eq_sub_min (n m : ℕ) : n - m = n - min n m :=
if h : n ≥ m then by rw [min_eq_right h]
else by rw [Nat.sub_eq_zero_of_le (le_of_not_ge h), min_eq_left (le_of_not_ge h), Nat.sub_self]

@[simp] protected theorem sub_add_min_cancel (n m : ℕ) : n - m + min n m = n :=
by rw [sub_eq_sub_min, Nat.sub_add_cancel (min_le_left n m)]

/- TODO(Leo): sub + inequalities -/

protected def strong_rec_on {p : ℕ → Sort u}
  (n : ℕ) (H : ∀ n, (∀ m, m < n → p m) → p n) : p n :=
Nat.lt_wfRel.wf.fix' H n

protected lemma strong_induction_on {p : Nat → Prop} (n : Nat) (h : ∀ n, (∀ m, m < n → p m) → p n) : p n :=
Nat.strong_rec_on n h

protected lemma case_strong_induction_on {p : Nat → Prop} (a : Nat)
  (hz : p 0)
  (hi : ∀ n, (∀ m, m ≤ n → p m) → p (succ n)) : p a :=
Nat.strong_induction_on a $ λ n =>
  match n with
  | 0     => λ _ => hz
  | (n+1) => λ h₁ => hi n (λ m h₂ => h₁ _ (lt_succ_of_le h₂))

/- mod -/

-- TODO mod_core_congr, mod_def

lemma mod_two_eq_zero_or_one (n : ℕ) : n % 2 = 0 ∨ n % 2 = 1 :=
match n % 2, @Nat.mod_lt n 2 (by simp) with
| 0,   _ => Or.inl rfl
| 1,   _ => Or.inr rfl
| k+2, h => absurd h (λ h => not_lt_zero k (lt_of_succ_lt_succ (lt_of_succ_lt_succ h)))

/- div & mod -/

-- TODO div_core_congr, div_def

lemma mod_add_div (m k : ℕ) : m % k + k * (m / k) = m := by
  induction m, k using mod.inductionOn with rw [div_eq, mod_eq]
  | base x y h => simp [h]
  | ind x y h IH => simp [h]; rw [Nat.mul_succ, ← Nat.add_assoc, IH, Nat.sub_add_cancel h.2]

/- div -/

@[simp] protected lemma div_one (n : ℕ) : n / 1 = n :=
have this : n % 1 + 1 * (n / 1) = n := mod_add_div _ _
by rwa [mod_one, Nat.zero_add, Nat.one_mul] at this

@[simp] protected lemma div_zero (n : ℕ) : n / 0 = 0 :=
by rw [div_eq]; simp [Nat.lt_irrefl]

@[simp] protected lemma zero_div (b : ℕ) : 0 / b = 0 :=
(div_eq 0 b).trans $ if_neg $ And.rec Nat.not_le_of_gt

lemma le_div_iff_mul_le (k0 : 0 < k) : x ≤ y / k ↔ x * k ≤ y := by
  induction y, k using mod.inductionOn generalizing x with
    (rw [div_eq]; simp [h]; cases x with simp [zero_le] | succ x => ?_)
  | base y k h =>
    simp [eq_false (not_succ_le_zero x), succ_mul, Nat.add_comm]
    refine Nat.lt_of_lt_of_le ?_ (Nat.le_add_right _ _)
    exact not_le.1 fun h' => h ⟨k0, h'⟩
  | ind y k h IH =>
    rw [← add_one, Nat.add_le_add_iff_le_right, IH k0, succ_mul,
        ← Nat.add_sub_cancel (x*k) k, Nat.sub_le_sub_right_iff h.2, Nat.add_sub_cancel]

protected lemma div_le_of_le_mul {m n : ℕ} : ∀ {k}, m ≤ k * n → m / k ≤ n
| 0,      h => by simp [Nat.div_zero, n.zero_le]
| succ k, h =>
  suffices succ k * (m / succ k) ≤ succ k * n from Nat.le_of_mul_le_mul_left this (zero_lt_succ _)
  by have h1 : succ k * (m / succ k) ≤ m % succ k + succ k * (m / succ k) := Nat.le_add_left _ _
     have h2 : m % succ k + succ k * (m / succ k) = m := by rw [mod_add_div]
     have h3 : m ≤ succ k * n := h
     rw [←h2] at h3
     exact le_trans h1 h3

protected lemma div_le_self : ∀ (m n : ℕ), m / n ≤ m
| m, 0   => by simp [Nat.div_zero]
| m, n+1 => Nat.div_le_of_le_mul $ by
  have := Nat.mul_le_mul_right m (succ_pos n)
  rwa [Nat.one_mul] at this

lemma div_eq_sub_div (h₁ : 0 < b) (h₂ : b ≤ a) : a / b = (a - b) / b + 1 := by
 rw [div_eq a, if_pos]; constructor <;> assumption

lemma div_eq_of_lt (h₀ : a < b) : a / b = 0 := by
  rw [div_eq a, if_neg]
  intro h₁
  apply Nat.not_le_of_gt h₀ h₁.right

lemma div_lt_iff_lt_mul (Hk : 0 < k) : x / k < y ↔ x < y * k := by
  rw [← not_le, ← not_le]; apply not_congr; apply le_div_iff_mul_le Hk

def iterate {α : Sort u} (op : α → α) : ℕ → α → α
 | 0,      a => a
 | succ k, a => iterate op k (op a)

notation f "^["n"]" => iterate f n

/- successor and predecessor -/

theorem add_one_ne_zero (n : ℕ) : n + 1 ≠ 0 := succ_ne_zero _

lemma eq_zero_or_eq_succ_pred (n : ℕ) : n = 0 ∨ n = succ (pred n) :=
by cases n <;> simp

theorem exists_eq_succ_of_ne_zero {n : ℕ} (H : n ≠ 0) : ∃k : ℕ, n = succ k :=
⟨_, (eq_zero_or_eq_succ_pred _).resolve_left H⟩

def discriminate (H1: n = 0 → α) (H2 : ∀m, n = succ m → α) : α :=
  match n with
  | 0 => H1 rfl
  | succ m => H2 m rfl

lemma one_eq_succ_zero : 1 = succ 0 := rfl

def two_step_induction {P : ℕ → Sort u} (H1 : P 0) (H2 : P 1)
    (H3 : ∀ (n : ℕ) (IH1 : P n) (IH2 : P (succ n)), P (succ (succ n))) : (a : ℕ) → P a
| 0   => H1
| 1   => H2
| n+2 => H3 _ (two_step_induction H1 H2 H3 _) (two_step_induction H1 H2 H3 _)

def sub_induction {P : ℕ → ℕ → Sort u} (H1 : ∀m, P 0 m)
   (H2 : ∀n, P (succ n) 0) (H3 : ∀n m, P n m → P (succ n) (succ m)) : (n m : ℕ) → P n m
| 0,   m   => H1 _
| n+1, 0   => H2 _
| n+1, m+1 => H3 _ _ (sub_induction H1 H2 H3 n m)

/- addition -/

theorem succ_add_eq_succ_add (n m : ℕ) : succ n + m = n + succ m :=
by simp [succ_add, add_succ]

theorem one_add (n : ℕ) : 1 + n = succ n := by simp [Nat.add_comm]

lemma eq_zero_of_add_eq_zero {n m : ℕ} (H : n + m = 0) : n = 0 ∧ m = 0 :=
⟨Nat.eq_zero_of_add_eq_zero_right H, Nat.eq_zero_of_add_eq_zero_left H⟩

lemma eq_zero_of_mul_eq_zero : ∀ {n m : ℕ}, n * m = 0 → n = 0 ∨ m = 0
| 0,   m, _ => Or.inl rfl
| n+1, m, h => by rw [succ_mul] at h; exact Or.inr (Nat.eq_zero_of_add_eq_zero_left h)

/- properties of inequality -/

lemma le_succ_of_pred_le {n m : ℕ} : pred n ≤ m → n ≤ succ m :=
match n with | 0 => fun _ => zero_le _ | a+1 => succ_le_succ

lemma le_lt_antisymm {n m : ℕ} (h₁ : n ≤ m) (h₂ : m < n) : False :=
Nat.lt_irrefl n (Nat.lt_of_le_of_lt h₁ h₂)

lemma lt_le_antisymm {n m : ℕ} (h₁ : n < m) (h₂ : m ≤ n) : False :=
le_lt_antisymm h₂ h₁

protected lemma lt_asymm {n m : ℕ} (h₁ : n < m) : ¬ m < n :=
le_lt_antisymm (Nat.le_of_lt h₁)

protected def lt_ge_by_cases {a b : ℕ} {C : Sort u} (h₁ : a < b → C) (h₂ : b ≤ a → C) : C :=
if h : a < b then h₁ h else h₂ (not_lt.1 h)

protected def lt_by_cases {a b : ℕ} {C : Sort u} (h₁ : a < b → C) (h₂ : a = b → C)
  (h₃ : b < a → C) : C :=
Nat.lt_ge_by_cases h₁ fun h₁ =>
  Nat.lt_ge_by_cases h₃ fun h => h₂ (Nat.le_antisymm h h₁)

protected lemma lt_trichotomy (a b : ℕ) : a < b ∨ a = b ∨ b < a :=
Nat.lt_by_cases Or.inl (Or.inr ∘ Or.inl) (Or.inr ∘ Or.inr)

protected lemma eq_or_lt_of_not_lt {a b : ℕ} (hnlt : ¬ a < b) : a = b ∨ b < a :=
(Nat.lt_trichotomy a b).resolve_left hnlt

lemma lt_succ_of_lt (h : a < b) : a < succ b := le_succ_of_le h

lemma one_pos : 0 < 1 := Nat.zero_lt_one

/- subtraction -/

protected lemma sub_add_eq_max {a b : Nat} : a - b + b = max a b := by
cases (@le_total Nat Nat.instLinearOrderNat a b) with
| inl hl => rw [max_eq_right hl, Nat.sub_eq_zero_iff_le.mpr hl, Nat.zero_add]
| inr hr => rw [max_eq_left hr, Nat.sub_add_cancel hr]

protected lemma sub_le_sub_left (k : ℕ) (h : n ≤ m) : k - m ≤ k - n :=
  match m, le.dest h with
  | _, ⟨a, rfl⟩ => by rw [← Nat.sub_sub]; apply sub_le

theorem succ_sub_sub_succ (n m k : ℕ) : succ n - m - succ k = n - m - k :=
by rw [Nat.sub_sub, Nat.sub_sub, add_succ, succ_sub_succ]

protected lemma sub.right_comm (m n k : ℕ) : m - n - k = m - k - n :=
by rw [Nat.sub_sub, Nat.sub_sub, Nat.add_comm]

protected lemma mul_self_sub_mul_self_eq (a b : Nat) : a * a - b * b = (a + b) * (a - b) :=
by rw [Nat.mul_sub_left_distrib, Nat.right_distrib, Nat.right_distrib, Nat.mul_comm b a, Nat.add_comm (a*a) (a*b),
       Nat.add_sub_add_left]

lemma succ_mul_succ_eq (a b : Nat) : succ a * succ b = a*b + a + b + 1 := by
  rw [mul_succ, succ_mul, Nat.add_right_comm _ a]; rfl

theorem succ_sub {m n : ℕ} (h : n ≤ m) : succ m - n = succ (m - n) :=
Exists.elim (Nat.le.dest h)
  (λ k => λ hk : n + k = m =>
    by rw [← hk, Nat.add_sub_cancel_left, ← add_succ, Nat.add_sub_cancel_left])

protected lemma sub_pos_of_lt (h : m < n) : 0 < n - m := by
  apply Nat.lt_of_add_lt_add_right (b := m)
  rw [Nat.zero_add, Nat.sub_add_cancel (Nat.le_of_lt h)]; exact h

protected lemma sub_sub_self {n m : ℕ} (h : m ≤ n) : n - (n - m) = m :=
(Nat.sub_eq_iff_eq_add (Nat.sub_le _ _)).2 (Nat.add_sub_of_le h).symm

protected lemma sub_add_comm {n m k : ℕ} (h : k ≤ n) : n + m - k = n - k + m :=
(Nat.sub_eq_iff_eq_add (Nat.le_trans h (Nat.le_add_right _ _))).2
  (by rwa [Nat.add_right_comm, Nat.sub_add_cancel])

lemma sub_one_sub_lt (h : i < n) : n - 1 - i < n := by
  rw [Nat.sub_sub]
  apply Nat.sub_lt
  apply Nat.lt_of_lt_of_le (Nat.zero_lt_succ _) h
  rw [Nat.add_comm]
  apply Nat.zero_lt_succ

lemma pred_inj : ∀ {a b : ℕ}, 0 < a → 0 < b → Nat.pred a = Nat.pred b → a = b
| a+1, b+1, ha, hb, h => by rw [show a = b from h]
| a+1, 0,   ha, hb, h => absurd hb (Nat.lt_irrefl _)
| 0,   b+1, ha, hb, h => absurd ha (Nat.lt_irrefl _)
| 0,   0,   ha, hb, h => rfl

/- find -/

section find
variable (p : ℕ → Prop)

private def lbp (m n : ℕ) : Prop := m = n + 1 ∧ ∀ k, k ≤ n → ¬p k

variable [DecidablePred p] (H : ∃ n, p n)

private def wf_lbp : WellFounded (lbp p) := by
  refine ⟨let ⟨n, pn⟩ := H; ?_⟩
  suffices ∀ m k, n ≤ k + m → Acc (lbp p) k from fun a => this _ _ (Nat.le_add_left _ _)
  intro m
  induction m with refine fun k kn => ⟨_, fun | _, ⟨rfl, a⟩ => ?_⟩
  | zero => exact absurd pn (a _ kn)
  | succ m IH => exact IH _ (by rw [Nat.add_right_comm]; exact kn)

protected def find_x : {n // p n ∧ ∀ m, m < n → ¬p m} :=
(wf_lbp p H).fix' (C := fun k => (∀n, n < k → ¬p n) → {n // p n ∧ ∀ m, m < n → ¬p m})
  (fun m IH al => if pm : p m then ⟨m, pm, al⟩ else
      have this : ∀ n, n ≤ m → ¬p n := fun n h =>
        (lt_or_eq_of_le h).elim (al n) fun e => by rw [e]; exact pm
      IH _ ⟨rfl, this⟩ fun n h => this n $ Nat.le_of_succ_le_succ h)
  0 fun n h => absurd h (Nat.not_lt_zero _)

/--
If `p` is a (decidable) predicate on `ℕ` and `hp : ∃ (n : ℕ), p n` is a proof that
there exists some natural number satisfying `p`, then `nat.find hp` is the
smallest natural number satisfying `p`. Note that `nat.find` is protected,
meaning that you can't just write `find`, even if the `nat` namespace is open.

The API for `nat.find` is:

* `nat.find_spec` is the proof that `nat.find hp` satisfies `p`.
* `nat.find_min` is the proof that if `m < nat.find hp` then `m` does not satisfy `p`.
* `nat.find_min'` is the proof that if `m` does satisfy `p` then `nat.find hp ≤ m`.
-/

protected def find : ℕ := (Nat.find_x p H).1

protected lemma find_spec : p (Nat.find p H) := (Nat.find_x p H).2.1

protected lemma find_min : ∀ {m : ℕ}, m < Nat.find p H → ¬p m := @(Nat.find_x p H).2.2

protected lemma find_min' {m : ℕ} (h : p m) : Nat.find p H ≤ m :=
not_lt.1 fun l => Nat.find_min p H l h

end find

/- mod -/

lemma le_of_mod_lt {a b : Nat} (h : a % b < a) : b <= a :=
  Decidable.byContradiction $ fun hf =>
  have h0 : a % b = a := Nat.mod_eq_of_lt ((Nat.lt_or_ge a b).resolve_right hf)
  have h1 : a % b ≠ a := ne_of_lt h
  False.elim (h1 h0)

@[simp] theorem add_mod_right (x z : ℕ) : (x + z) % z = x % z :=
by rw [mod_eq_sub_mod (Nat.le_add_left _ _), Nat.add_sub_cancel]

@[simp] lemma add_mod_left (x z : ℕ) : (x + z) % x = z % x :=
by rw [Nat.add_comm, add_mod_right]

@[simp] lemma add_mul_mod_self_left (x y z : ℕ) : (x + y * z) % y = x % y := by
  induction z with
  | zero => rw [Nat.mul_zero, Nat.add_zero]
  | succ z ih => rw [mul_succ, ← Nat.add_assoc, add_mod_right, ih]

@[simp] lemma add_mul_mod_self_right (x y z : ℕ) : (x + y * z) % z = x % z :=
by rw [Nat.mul_comm, add_mul_mod_self_left]

@[simp] lemma mul_mod_right (m n : ℕ) : (m * n) % m = 0 :=
by rw [← Nat.zero_add (m*n), add_mul_mod_self_left, zero_mod]

@[simp] theorem mul_mod_left (m n : ℕ) : (m * n) % n = 0 :=
by rw [Nat.mul_comm, mul_mod_right]

lemma mul_mod_mul_left (z x y : ℕ) : (z * x) % (z * y) = z * (x % y) :=
if y0 : y = 0 then
  by rw [y0, Nat.mul_zero, mod_zero, mod_zero]
else if z0 : z = 0 then
  by rw [z0, Nat.zero_mul, Nat.zero_mul, Nat.zero_mul, mod_zero]
else by
  induction x using Nat.strong_rec_on with
  | _ n IH =>
    have y0 : y > 0 := Nat.pos_of_ne_zero y0
    have z0 : z > 0 := Nat.pos_of_ne_zero z0
    cases Nat.lt_or_ge n y with
    | inl yn => rw [mod_eq_of_lt yn, mod_eq_of_lt (Nat.mul_lt_mul_of_pos_left yn z0)]
    | inr yn =>
      rw [mod_eq_sub_mod yn, mod_eq_sub_mod (Nat.mul_le_mul_left z yn),
        ← Nat.mul_sub_left_distrib]
      exact IH _ (sub_lt (Nat.lt_of_lt_of_le y0 yn) y0)

lemma mul_mod_mul_right (z x y : ℕ) : (x * z) % (y * z) = (x % y) * z :=
by rw [Nat.mul_comm x z, Nat.mul_comm y z, Nat.mul_comm (x % y) z]; apply mul_mod_mul_left

-- TODO cont_to_bool_mod_two

lemma sub_mul_mod (x k n : ℕ) (h₁ : n*k ≤ x) : (x - n*k) % n = x % n := by
  induction k with
  | zero => rw [Nat.mul_zero, Nat.sub_zero]
  | succ k IH =>
    have h₂ : n * k ≤ x := by
      rw [mul_succ] at h₁
      apply Nat.le_trans _ h₁
      apply le_add_right _ n
    have h₄ : x - n * k ≥ n := by
      apply Nat.le_of_add_le_add_right (b := n*k)
      rw [Nat.sub_add_cancel h₂]
      simp [mul_succ, Nat.add_comm] at h₁; simp [h₁]
    rw [mul_succ, ← Nat.sub_sub, ← mod_eq_sub_mod h₄, IH h₂]

/- div -/

lemma sub_mul_div (x n p : ℕ) (h₁ : n*p ≤ x) : (x - n*p) / n = x / n - p := by
  cases eq_zero_or_pos n with
  | inl h₀ => rw [h₀, Nat.div_zero, Nat.div_zero, Nat.zero_sub]
  | inr h₀ => induction p with
    | zero => rw [Nat.mul_zero, Nat.sub_zero, Nat.sub_zero]
    | succ p IH =>
      have h₂ : n*p ≤ x := by
        transitivity
        · apply Nat.mul_le_mul_left; apply le_succ
        · apply h₁
      have h₃ : x - n * p ≥ n := by
        apply Nat.le_of_add_le_add_right
        rw [Nat.sub_add_cancel h₂, Nat.add_comm]
        rw [mul_succ] at h₁
        apply h₁
      rw [sub_succ, ← IH h₂]
      rw [div_eq_sub_div h₀ h₃]
      simp [add_one, Nat.pred_succ, mul_succ, Nat.sub_sub]

lemma div_mul_le_self : ∀ (m n : ℕ), m / n * n ≤ m
| m, 0   => by simp
| m, n+1 => (le_div_iff_mul_le (Nat.succ_pos _)).1 (Nat.le_refl _)

@[simp] lemma add_div_right (x : ℕ) {z : ℕ} (H : 0 < z) : (x + z) / z = succ (x / z) :=
by rw [div_eq_sub_div H (Nat.le_add_left _ _), Nat.add_sub_cancel]

@[simp] lemma add_div_left (x : ℕ) {z : ℕ} (H : 0 < z) : (z + x) / z = succ (x / z) :=
by rw [Nat.add_comm, add_div_right x H]

@[simp] lemma mul_div_right (n : ℕ) {m : ℕ} (H : 0 < m) : m * n / m = n :=
by induction n <;> simp_all [mul_succ]

@[simp] lemma mul_div_left (m : ℕ) {n : ℕ} (H : 0 < n) : m * n / n = m :=
by rw [Nat.mul_comm, mul_div_right _ H]

protected lemma div_self (H : 0 < n) : n / n = 1 :=
let t := add_div_right 0 H; by rwa [Nat.zero_add, Nat.zero_div] at t

lemma add_mul_div_left (x z : ℕ) {y : ℕ} (H : 0 < y) : (x + y * z) / y = x / y + z := by
  induction z with
  | zero => rw [Nat.mul_zero, Nat.add_zero, Nat.add_zero]
  | succ z ih => rw [mul_succ, ← Nat.add_assoc, add_div_right _ H, ih]; rfl

lemma add_mul_div_right (x y : ℕ) {z : ℕ} (H : 0 < z) : (x + y * z) / z = x / z + y :=
by rw [Nat.mul_comm, add_mul_div_left _ _ H]

protected lemma mul_div_cancel (m : ℕ) {n : ℕ} (H : 0 < n) : m * n / n = m :=
let t := add_mul_div_right 0 m H; by rwa [Nat.zero_add, Nat.zero_div, Nat.zero_add] at t

protected lemma mul_div_cancel_left (m : ℕ) {n : ℕ} (H : 0 < n) : n * m / n = m :=
by rw [Nat.mul_comm, Nat.mul_div_cancel _ H]

protected lemma div_eq_of_eq_mul_left (H1 : 0 < n) (H2 : m = k * n) : m / n = k :=
by rw [H2, Nat.mul_div_cancel _ H1]

protected lemma div_eq_of_eq_mul_right (H1 : 0 < n) (H2 : m = n * k) : m / n = k :=
by rw [H2, Nat.mul_div_cancel_left _ H1]

protected lemma div_eq_of_lt_le
  (lo : k * n ≤ m) (hi : m < succ k * n) : m / n = k :=
have npos : 0 < n := (eq_zero_or_pos _).resolve_left fun hn => by
  rw [hn, Nat.mul_zero] at hi lo; exact absurd lo (Nat.not_le_of_gt hi)
Nat.le_antisymm
  (le_of_lt_succ ((Nat.div_lt_iff_lt_mul npos).2 hi))
  ((Nat.le_div_iff_mul_le npos).2 lo)

lemma mul_sub_div (x n p : ℕ) (h₁ : x < n*p) : (n * p - succ x) / n = p - succ (x / n) := by
  have npos : 0 < n := (eq_zero_or_pos _).resolve_left fun n0 => by
    rw [n0, Nat.zero_mul] at h₁; exact not_lt_zero _ h₁
  apply Nat.div_eq_of_lt_le
  · rw [Nat.mul_sub_right_distrib, Nat.mul_comm]
    apply Nat.sub_le_sub_left
    exact (div_lt_iff_lt_mul npos).1 (lt_succ_self _)
  · change succ (pred (n * p - x)) ≤ (succ (pred (p - x / n))) * n
    rw [succ_pred_eq_of_pos (Nat.sub_pos_of_lt h₁),
      fun h => succ_pred_eq_of_pos (Nat.sub_pos_of_lt h)] -- TODO: why is the function needed?
    · rw [Nat.mul_sub_right_distrib, Nat.mul_comm]
      apply Nat.sub_le_sub_left; apply div_mul_le_self
    · apply (div_lt_iff_lt_mul npos).2; rwa [Nat.mul_comm]

protected lemma div_div_eq_div_mul (m n k : ℕ) : m / n / k = m / (n * k) := by
  cases eq_zero_or_pos k with
  | inl k0 => rw [k0, Nat.mul_zero, Nat.div_zero, Nat.div_zero] | inr kpos => ?_
  cases eq_zero_or_pos n with
  | inl n0 => rw [n0, Nat.zero_mul, Nat.div_zero, Nat.zero_div] | inr npos => ?_
  apply Nat.le_antisymm
  · apply (le_div_iff_mul_le (Nat.mul_pos npos kpos)).2
    rw [Nat.mul_comm n k, ← Nat.mul_assoc]
    apply (le_div_iff_mul_le npos).1
    apply (le_div_iff_mul_le kpos).1
    (apply Nat.le_refl)
  · apply (le_div_iff_mul_le kpos).2
    apply (le_div_iff_mul_le npos).2
    rw [Nat.mul_assoc, Nat.mul_comm n k]
    apply (le_div_iff_mul_le (Nat.mul_pos kpos npos)).1
    apply Nat.le_refl

protected lemma mul_div_mul {m : ℕ} (n k : ℕ) (H : 0 < m) : m * n / (m * k) = n / k :=
by rw [← Nat.div_div_eq_div_mul, Nat.mul_div_cancel_left _ H]

/- dvd -/

protected theorem dvd_mul_right (a b : ℕ) : a ∣ a * b := ⟨b, rfl⟩

protected theorem dvd_trans {a b c : ℕ} (h₁ : a ∣ b) (h₂ : b ∣ c) : a ∣ c :=
match h₁, h₂ with
| ⟨d, (h₃ : b = a * d)⟩, ⟨e, (h₄ : c = b * e)⟩ =>
  ⟨d * e, show c = a * (d * e) by simp[h₃,h₄, Nat.mul_assoc]⟩

protected theorem eq_zero_of_zero_dvd {a : ℕ} (h : 0 ∣ a) : a = 0 :=
Exists.elim h (λ c (H' : a = 0 * c) => Eq.trans H' (Nat.zero_mul c))

protected theorem dvd_add {a b c : ℕ} (h₁ : a ∣ b) (h₂ : a ∣ c) : a ∣ b + c :=
  Exists.elim h₁ (λ d hd => Exists.elim h₂ (λ e he => ⟨d + e, by simp[Nat.left_distrib, hd, he]⟩))

protected theorem dvd_add_iff_right {k m n : ℕ} (h : k ∣ m) : k ∣ n ↔ k ∣ m + n :=
  ⟨Nat.dvd_add h,
   Exists.elim h $ λd hd =>
     match m, hd with
     | _, rfl => λh₂ => Exists.elim h₂ $ λe he =>
          ⟨e - d, by rw [Nat.mul_sub_left_distrib, ←he, Nat.add_sub_cancel_left]⟩⟩

protected theorem dvd_add_iff_left {k m n : ℕ} (h : k ∣ n) : k ∣ m ↔ k ∣ m + n :=
  by rw [Nat.add_comm]
     exact Nat.dvd_add_iff_right h

theorem dvd_sub {k m n : ℕ} (H : n ≤ m) (h₁ : k ∣ m) (h₂ : k ∣ n) : k ∣ m - n :=
  (Nat.dvd_add_iff_left h₂).mpr $ by rw [Nat.sub_add_cancel H]
                                        exact h₁

theorem dvd_mod_iff {k m n : ℕ} (h: k ∣ n) : k ∣ m % n ↔ k ∣ m :=
  let t := @Nat.dvd_add_iff_left _ (m % n) _ (Nat.dvd_trans h (Nat.dvd_mul_right n (m / n)))
  by rwa [mod_add_div] at t

theorem le_of_dvd {m n : ℕ} (h : 0 < n) : m ∣ n → m ≤ n :=
 λ⟨k,e⟩ => by
   revert h
   rw [e]
   match k with
   | 0 => intro hn
          simp at hn
   | pk+1 => intro _
             let t := Nat.mul_le_mul_left m (succ_pos pk)
             rwa [Nat.mul_one] at t

theorem dvd_antisymm : ∀ {m n : ℕ}, m ∣ n → n ∣ m → m = n
| m,     0, h₁, h₂ => Nat.eq_zero_of_zero_dvd h₂
| 0,     n, h₁, h₂ => (Nat.eq_zero_of_zero_dvd h₁).symm
| m+1, n+1, h₁, h₂ => Nat.le_antisymm (le_of_dvd (succ_pos _) h₁) (le_of_dvd (succ_pos _) h₂)

theorem pos_of_dvd_of_pos {m n : ℕ} (H1 : m ∣ n) (H2 : 0 < n) : 0 < m :=
Nat.pos_of_ne_zero $ λ m0 =>
  by rw [m0] at H1
     rw [Nat.eq_zero_of_zero_dvd H1] at H2
     exact Nat.lt_irrefl _ H2

theorem eq_one_of_dvd_one {n : ℕ} (H : n ∣ 1) : n = 1 :=
  Nat.le_antisymm
   (le_of_dvd (of_decide_eq_true (by trivial)) H)
   (pos_of_dvd_of_pos H (of_decide_eq_true (by trivial)))

theorem dvd_of_mod_eq_zero {m n : ℕ} (H : n % m = 0) : m ∣ n :=
Exists.intro
  (n / m)
  (by have t := (mod_add_div n m).symm
      rwa [H, Nat.zero_add] at t)

theorem mod_eq_zero_of_dvd {m n : ℕ} (H : m ∣ n) : n % m = 0 :=
Exists.elim H (λ z H1 => by rw [H1, mul_mod_right])

theorem dvd_iff_mod_eq_zero (m n : ℕ) : m ∣ n ↔ n % m = 0 :=
Iff.intro mod_eq_zero_of_dvd dvd_of_mod_eq_zero

instance decidable_dvd : @DecidableRel ℕ (·∣·) :=
λm n => decidable_of_decidable_of_iff (dvd_iff_mod_eq_zero _ _).symm

protected theorem mul_div_cancel' {m n : ℕ} (H : n ∣ m) : n * (m / n) = m :=
 let t := mod_add_div m n
 by rwa [mod_eq_zero_of_dvd H, Nat.zero_add] at t

protected theorem div_mul_cancel {m n : ℕ} (H: n ∣ m) : m / n * n = m :=
  by rw [Nat.mul_comm, Nat.mul_div_cancel' H]

protected theorem mul_div_assoc (m : ℕ) {n k : ℕ} (H : k ∣ n) : m * n / k = m * (n / k) :=
match Nat.eq_zero_or_pos k with
| Or.inl h0 => by rw [h0, Nat.div_zero, Nat.div_zero, Nat.mul_zero]
| Or.inr hpos => by have h1 : m * n / k = m *(n / k * k) / k := by rw [Nat.div_mul_cancel H]
                    rw [h1, ← Nat.mul_assoc, Nat.mul_div_cancel _ hpos]

protected theorem dvd_of_mul_dvd_mul_left {m n k : ℕ} (kpos : 0 < k) (H : k * m ∣ k * n) : m ∣ n :=
Exists.elim H (λ l H1 => by rw [Nat.mul_assoc] at H1
                            exact ⟨_, Nat.eq_of_mul_eq_mul_left kpos H1⟩)

protected theorem dvd_of_mul_dvd_mul_right {m n k : ℕ} (kpos : 0 < k) (H : m * k ∣ n * k) : m ∣ n :=
by rw [Nat.mul_comm m k, Nat.mul_comm n k] at H; exact Nat.dvd_of_mul_dvd_mul_left kpos H

/- --- -/

protected lemma mul_le_mul_of_nonneg_left {a b c : ℕ} (h₁ : a ≤ b) : c * a ≤ c * b := by
  by_cases hba: b ≤ a; { simp [Nat.le_antisymm hba h₁] }
  by_cases hc0 : c ≤ 0; { simp [Nat.le_antisymm hc0 (zero_le c), Nat.zero_mul] }
  exact Nat.le_of_lt (Nat.mul_lt_mul_of_pos_left (not_le.1 hba) (not_le.1 hc0))

protected lemma mul_le_mul_of_nonneg_right {a b c : ℕ} (h₁ : a ≤ b) : a * c ≤ b * c := by
  by_cases hba : b ≤ a; { simp [Nat.le_antisymm hba h₁] }
  by_cases hc0 : c ≤ 0; { simp [Nat.le_antisymm hc0 (zero_le c), Nat.mul_zero] }
  exact Nat.le_of_lt (Nat.mul_lt_mul_of_pos_right (not_le.1 hba) (not_le.1 hc0))

protected lemma mul_lt_mul (hac : a < c) (hbd : b ≤ d) (pos_b : 0 < b) : a * b < c * d :=
Nat.lt_of_lt_of_le (Nat.mul_lt_mul_of_pos_right hac pos_b) (Nat.mul_le_mul_of_nonneg_left hbd)

protected lemma mul_lt_mul' (h1 : a ≤ c) (h2 : b < d) (h3 : 0 < c) : a * b < c * d :=
Nat.lt_of_le_of_lt (Nat.mul_le_mul_of_nonneg_right h1) (Nat.mul_lt_mul_of_pos_left h2 h3)

lemma div_lt_self (h₁ : 0 < n) (h₂ : 1 < m) : n / m < n := by
  have m_pos : 0 < m := Nat.lt_trans Nat.zero_lt_one h₂
  suffices 1 * n < m * n by
    rw [Nat.one_mul, Nat.mul_comm] at this
    exact (Nat.div_lt_iff_lt_mul m_pos).2 this
  exact Nat.mul_lt_mul h₂ (Nat.le_refl _) h₁

@[simp] theorem mod_mod (a n : ℕ) : (a % n) % n = a % n :=
(eq_zero_or_pos n).elim
  (λ n0 => by simp [n0, mod_zero])
  (λ npos => Nat.mod_eq_of_lt (mod_lt _ npos))

lemma mul_mod (a b n : ℕ) : (a * b) % n = ((a % n) * (b % n)) % n := by
    let hy := (a * b) % n
    have hx : (a * b) % n = hy := rfl
    rw [←mod_add_div a n, ←mod_add_div b n, Nat.right_distrib, Nat.left_distrib, Nat.left_distrib,
        Nat.mul_assoc, Nat.mul_assoc, ←Nat.left_distrib n _ _, add_mul_mod_self_left,
        Nat.mul_comm _ (n * (b / n)), Nat.mul_assoc, add_mul_mod_self_left] at hx
    rw [hx]
    done

@[simp] theorem mod_add_mod (m n k : ℕ) : (m % n + k) % n = (m + k) % n := by
   have := (add_mul_mod_self_left (m % n + k) n (m / n)).symm
   rwa [Nat.add_right_comm, mod_add_div] at this

@[simp] theorem add_mod_mod (m n k : ℕ) : (m + n % k) % k = (m + n) % k :=
by rw [Nat.add_comm, mod_add_mod, Nat.add_comm]

lemma add_mod (a b n : ℕ) : (a + b) % n = ((a % n) + (b % n)) % n :=
by rw [add_mod_mod, mod_add_mod]

lemma to_digits_core_lens_eq_aux (b f : Nat)
  : ∀ (n : Nat) (l1 l2 : List Char) (h0 : l1.length = l2.length), (Nat.toDigitsCore b f n l1).length = (Nat.toDigitsCore b f n l2).length := by
induction f with
  simp only [Nat.toDigitsCore, List.length] <;> intro n l1 l2 hlen
| zero => assumption
| succ f ih =>
  by_cases hx : n / b = 0
  case pos => simp only [hx, if_true, List.length, congrArg (fun l => l + 1) hlen]
  case neg =>
    simp only [hx, if_false]
    specialize ih (n / b) (Nat.digitChar (n % b) :: l1) (Nat.digitChar (n % b) :: l2)
    simp only [List.length, congrArg (fun l => l + 1) hlen] at ih
    exact ih trivial

lemma to_digits_core_lens_eq (b f : Nat) : ∀ (n : Nat) (c : Char) (tl : List Char), (Nat.toDigitsCore b f n (c :: tl)).length = (Nat.toDigitsCore b f n tl).length + 1 := by
induction f with
  intro n c tl <;> simp only [Nat.toDigitsCore, List.length]
| succ f ih =>
  by_cases hnb : (n / b) = 0
  case pos => simp only [hnb, if_true, List.length]
  case neg =>
    generalize hx: Nat.digitChar (n % b) = x
    simp only [hx, hnb, if_false] at ih
    simp only [hnb, if_false]
    specialize ih (n / b) c (x :: tl)
    rw [<- ih]
    have lens_eq : (x :: (c :: tl)).length = (c :: x :: tl).length := by simp
    apply to_digits_core_lens_eq_aux
    exact lens_eq

lemma nat_repr_len_aux (n b e : Nat) (h_b_pos : 0 < b) :  n < b ^ e.succ -> n / b < b ^ e := by
simp only [Nat.pow_succ]
exact (@Nat.div_lt_iff_lt_mul b n (b ^ e) h_b_pos).mpr

/-- The String representation produced by toDigitsCore has the proper length relative to
the number of digits in `n < e` for some base `b`. Since this works with any base greater
than one, it can be used for binary, decimal, and hex. -/
lemma to_digits_core_length (b : Nat) (h : 2 <= b) (f n e : Nat) (hlt : n < b ^ e) (h_e_pos: 0 < e) : (Nat.toDigitsCore b f n []).length <= e := by
induction f generalizing n e hlt h_e_pos with
  simp only [Nat.toDigitsCore, List.length, Nat.zero_le]
| succ f ih =>
  cases e with
  | zero => exact False.elim (Nat.lt_irrefl 0 h_e_pos)
  | succ e =>
    by_cases h_pred_pos : 0 < e
    case pos =>
      have _ : 0 < b := Nat.lt_trans (by decide) h
      specialize ih (n / b) e (nat_repr_len_aux n b e ‹0 < b› hlt) h_pred_pos
      by_cases hdiv_ten : n / b = 0
      case pos => simp only [hdiv_ten]; exact Nat.le.step h_pred_pos
      case neg =>
        simp only [hdiv_ten, to_digits_core_lens_eq b f (n / b) (Nat.digitChar $ n % b), if_false]
        exact Nat.succ_le_succ ih
    case neg =>
      have _ : e = 0 := Nat.eq_zero_of_nonpos e h_pred_pos
      rw [‹e = 0›]
      have _ : b ^ 1 = b := by simp only [pow_succ, pow_zero, Nat.one_mul]
      have _ : n < b := ‹b ^ 1 = b› ▸ (‹e = 0› ▸ hlt : n < b ^ Nat.succ 0)
      simp only [(@Nat.div_eq_of_lt n b ‹n < b› : n / b = 0), if_true, List.length]

/-- The core implementation of `Nat.repr` returns a String with length less than or equal to the
number of digits in the decimal number (represented by `e`). For example, the decimal string
representation of any number less than 1000 (10 ^ 3) has a length less than or equal to 3. -/
lemma repr_length (n e : Nat) : 0 < e -> n < 10 ^ e -> (Nat.repr n).length <= e := by
cases n with
  intro _ _ <;> simp only [Nat.repr, Nat.toDigits, String.length, List.asString]
| zero => assumption
| succ n =>
  by_cases hterm : n.succ / 10 = 0
  case pos => simp only [hterm, Nat.toDigitsCore]; assumption
  case neg =>
    simp only [hterm]
    exact to_digits_core_length 10 (by decide) (Nat.succ n + 1) (Nat.succ n) e ‹n.succ < 10 ^ e› ‹0 < e›

lemma Nat.pow_succ' {m n : Nat} : m ^ n.succ = m * m ^ n := by
  rw [Nat.pow_succ, Nat.mul_comm]

@[simp] lemma Nat.pow_eq {m n : Nat} : m.pow n = m ^ n := rfl

end Nat


import analysis.normed.group.basic
import analysis.normed_space.basic
variables {n: ℕ }
local notation `ℝ^n ` := fin (n) → ℝ

/-!
# Translation invariance of Lennard-Jones potential
This section defines the translation invariance of Lennard-Jonnes potential function that describes the intermolecular
pair potentials in molecular simulations. The commonly used expression is: <br>
$$
E = 4ε  [(\frac{σ}{r})^{12} - (\frac{σ}{r})^6]
$$
where:
- `E` is the intermolecular potential between the two atoms or molecules
- `ε` is the well depth and a measure of how strongly the two particles attract each other
- `σ` is the distance at which the intermolecular potential between the two particles is zero
- `r` is the distance of separation between both particles

Here we show that if we translate all the coordinates equally - the molecule is not changing conformations and hence the energy
does not change. Using pairwise disntace removes the choice of origin and makes the model translational invariant.

### Assumption
The pairwise distance is given by Euclidean distance or `L^2` norm where `x` and `y`
are the coordinates of the two vectors representing molecular positions.

### To-Do
- Proof rotational invariance of the Lennard-Jones function
- Generalize proof from properties of system using fundamental theorems of invariant theory
-/

lemma LJ_invariance (x y t : ℝ^n) (r rt a b E Et: ℝ)
( a1: r = dist x y) -- Distance
( a2: rt = dist (x+t) (y+t)  ) -- Distance after translation
( a3: E = a/r^12 - b/r^6 ) -- Engery
( a4: Et = a/rt^12 - b/rt^6 ) -- Energy after translation
:
E = Et -- Conjecture
:=
begin
  simp at a2,
  rw a2 at a4,
  rw a1 at a3,
  exact (rfl.congr (eq.symm a4)).mp a3,
end


import data.set
open set

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

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

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

-- Type Constructors: set

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

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

#check set

-- Membership Predicates

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

#check set ℕ

#reduce set ℕ

-- Example: the empty set of ℕ

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

#check (∅ : set ℕ )

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

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

#reduce (∅ : set ℕ)

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

-- Display Notation

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

def e: set ℕ := { }

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

def e': set ℕ := ∅

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

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

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

-- Set Builder Notation

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

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

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

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

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

-- Singleton Sets

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

def x: set nat := { 1 }

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

#reduce x

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

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

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

-- SET MEMBERSHIP

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

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

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

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

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

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

-- MORE EXAMPLE

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

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

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

#reduce y

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

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

#reduce y

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

-- SET UNION

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

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

def u := y ∪ z

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

#reduce u

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

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

#reduce u

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

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

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

#reduce 3 ∈ y ∪ z

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

-- SET INTERSECTION

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

def w := y ∩ z

#reduce w

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

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

-- SET DIFFERENCE

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

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

#reduce y \ z

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

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

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

          have f : false := pf,
          contradiction,
end

-- SUMMARY SO FAR

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

#print x
#print y
#print z

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

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

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

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

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

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

#reduce (1 ∈ y ∩ z)

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

#reduce 1 ∈ y \ z

-- PART II

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

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

-- SUBSET

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

#check x ⊆ y
#reduce x ⊆ y

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

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

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

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

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

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

#check x ⊆ y
#reduce x ⊆ y

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

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

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

Let's translate this to logicky English.

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

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

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

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

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

#reduce x ⊆ y

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

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

  -- case false
    contradiction,
end

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

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

#reduce A ⊆ B

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

-- SET EQUALITY (and extensionality)

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

#check ext

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

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

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

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

#reduce 1 ∈ p

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

  -- forward direction

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

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

-- POWERSET

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

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

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

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

#check A
#check 𝒫 A
#reduce 𝒫 A

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

#reduce ∅ ∈ 𝒫 A

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

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

#reduce A ∈ 𝒫 A

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    exact false.elim pf_s_in_emptyset,

end

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

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

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

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

#check ("Hello Lean", 1)

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

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

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

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

-- PRODUCT SET

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

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

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

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

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

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

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

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

-- COMPLEMENT

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

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

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

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

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

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

    assumption
end

-- INSERTION

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

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

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

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

-- MORE EXAMPLES

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

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

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

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

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

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

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

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

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

end sets


set_option pp.structure_projections false

namespace l10
open nat

#check nat
#check ℕ
#check zero
#check succ
#check succ zero
#check succ (succ(succ zero))

/--
inductive nat: Type
| zero : nat
| succ : nat → nat
--/

--zero and succ are different
--succ is injective : if succ m = succ n then m = n
--all the natural numbers are generaed from zero and succ

example : ∀ n : ℕ, 0 ≠ succ n :=
begin
  assume n h,
  contradiction,
end

def pred : ℕ → ℕ
| zero := zero
| (succ n) := n

#reduce (pred 7)

theorem inj_succ : ∀ m n : ℕ, succ m = succ n → m = n :=
begin
  assume m n h,
  change pred (succ m) = pred (succ n),
  rewrite h,
end
--utomatically for all inductive types which avoids the need to define pred:
example :  ∀ m n : nat, succ m = succ n → m = n :=
begin
  assume m n h,
  injection h, --given h : succ m = succ n, we can prove m = n
end

--structural recursion (primitive recursion)
def double : ℕ → ℕ
| zero := 0
| (succ n) := succ (succ (double n))
--double 7 = double (succ 6) = succ (succ (double 6)) = succ (succ 12) = 14

#reduce (double 7)

def half : ℕ → ℕ
| zero := zero
| (succ zero) := zero
| (succ (succ n)) := succ (half n)

#reduce (half 7)
#reduce (half (double 7))

/--
def foo3 : ℕ → ℕ
| 0 := 0
| (succ n) := foo3 (succ n)
--/
--non terminating recursion

--proof by induction
theorem half_double : ∀ n : ℕ , half (double n) = n :=
begin
  assume n,
  induction n with n' ih, --induction hypothesis
  refl,
  dsimp [double],
  dsimp [half],  --similar to apply
  apply congr_arg succ, --eliminate same term
  --exact ih,
  rewrite ih,
end
end l10

namespace l11

set_option pp.structure_projections false
open nat

--primirative/structural recursion
--induction

def foo : ℕ → ℕ
| 0 := 0
| (succ n) := succ (foo n)

#reduce (foo 7)

example : ∀ n : ℕ , foo n = n :=
begin
  assume n,
  induction n with n' ih,
  --dsimp[foo],
  reflexivity,

  dsimp[foo],
  --apply congr_arg succ,
  --exact ih,
  rewrite ih,
end

def add : ℕ → ℕ → ℕ
| m  zero     := m
| m  (succ n) := succ (add m n)

#reduce add 7 3

local notation m + n := add m n

theorem add_rneutr : ∀ n : ℕ, n + 0 = n := --right neutral
begin
  assume n,
  dsimp[(+),add],
  refl,
end

theorem add_lneutr : ∀ n : ℕ, 0 + n  = n :=
begin
  assume n,
  induction n with n' ih,
  refl,

  apply congr_arg succ, --local notation
  exact ih,
end

--associativity

theorem add_assoc : ∀ l m n : ℕ , (l + m) + n = l + (m + n) :=
begin
  assume l m n,
  induction n with n' ih,
  --dsimp[(+),add],
  refl,

  dsimp [(+), add],
  apply congr_arg succ,
  exact ih,
end
--shown that + with 0 is a monoid
--another monoid 1 * n

-- commutativity
lemma add_succ_lem : ∀ m n : ℕ, succ m + n = succ (m + n) :=
begin
  assume m n,
  induction n with n' ih,
  --dsimp[(+),add],
  refl,

  apply congr_arg succ,
  exact ih,
end

theorem add_comm : ∀ m n : ℕ , m + n = n + m :=
begin
  assume m n,
  induction m with m' ih,
  dsimp[(+),add],
  apply add_lneutr,--left neutral --rewrite lneutr,

  dsimp[(+),add],
  --transitivity,
  rewrite add_succ_lem,
  apply congr_arg succ,
  exact ih,
end

theorem add_comm2 : ∀ m n : ℕ , m + n = n + m :=
begin
  assume m n,
  induction m with m' ih,
  dsimp[(+),add],
  rewrite add_lneutr,
  calc
    succ m' + n = succ (m' + n) : by apply add_succ_lem
    ... = succ (n + m') : by apply congr_arg succ ih
    ... = n + succ m' : by refl
end

end l11


section propositional

variables P Q R : Prop

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

theorem doubleneg_intro :
  P → ¬¬P  :=
begin
  intro hp,
  intro hdn,
  contradiction,
end

theorem doubleneg_elim :
  ¬¬P → P  :=
begin
  intro hp,
  by_cases h : P,
  exact h,
  contradiction,
end

theorem doubleneg_law :
  ¬¬P ↔ P  :=
begin
  apply iff.intro,
  apply doubleneg_elim,
  apply doubleneg_intro,
end

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

theorem disj_comm :
  (P ∨ Q) → (Q ∨ P)  :=
begin
  intro h,
  cases h with hp hq,
  right, exact hp,
  left, exact hq,
end

theorem conj_comm :
  (P ∧ Q) → (Q ∧ P)  :=
begin
  intro hl,
  cases hl with hp hq,
  constructor,
  exact hq,
  exact hp,
end

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

theorem impl_as_disj_converse :
  (¬P ∨ Q) → (P → Q)  :=
begin
  intro h,
  intro hp,
  cases h with hnp hnq,
  contradiction,
  assumption,
end

theorem disj_as_impl :
  (P ∨ Q) → (¬P → Q)  :=
begin
  intro h,
  intro hpq,
  cases h with hnp hnq,
  contradiction,
  assumption,
end

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

theorem impl_as_contrapositive :
  (P → Q) → (¬Q → ¬P)  :=
begin
  intro hp,
  intro hnpq,
  intro hnp,
  have hq : Q := hp hnp,
  contradiction,
end

theorem impl_as_contrapositive_converse :
  (¬Q → ¬P) → (P → Q)  :=
begin
  intro hp,
  intro hpq,
  by_contra,
  apply hp,
  contradiction,
  assumption,
end

theorem contrapositive_law :
  (P → Q) ↔ (¬Q → ¬P)  :=
begin
  apply iff.intro,
  apply impl_as_contrapositive,
  apply impl_as_contrapositive_converse,
end

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

theorem lem_irrefutable :
  ¬¬(P∨¬P)  :=
begin
  intro h,
  apply h,
  right,
  intro hp,
  apply h,
  left,
  assumption,
end

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

theorem peirce_law_weak :
  ((P → Q) → P) → ¬¬P  :=
begin
  intro hp,
  intro hnnp,
  apply hnnp,
  apply hp,
  intro hnp,
  by_cases h: Q,
  exact h,
  contradiction,
end

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

theorem disj_as_negconj :
  P∨Q → ¬(¬P∧¬Q)  :=
begin
  intro hi,
  intro hc,
  cases hc with hnp hnq,
  cases hi with hip hiq,
  contradiction,
  contradiction,
end

theorem conj_as_negdisj :
  P∧Q → ¬(¬P∨¬Q)  :=
begin
  intro hc,
  intro hn,
  cases hn with hnp hnq,
  cases hc with hcp hcq,
  contradiction,
  cases hc with hcp hcq,
  contradiction,
end

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

theorem demorgan_disj :
  ¬(P∨Q) → (¬P ∧ ¬Q)  :=
begin
  intro hni,
  split,
  intro hp,
  apply hni,
  left, exact hp,
  intro hq,
  apply hni,
  right, exact hq,
end

theorem demorgan_disj_converse :
  (¬P ∧ ¬Q) → ¬(P∨Q)  :=
begin
  intro hc,
  intro hnor,
  cases hnor with hp hq,
  cases hc with hnp hnq,
  contradiction,
  cases hc with hnp hnq,
  contradiction,
end

theorem demorgan_conj :
  ¬(P∧Q) → (¬Q ∨ ¬P)  :=
begin
  intro hi,
  by_contra hn,
  apply hi,
  split,
  by_contra hnp,
  apply hn,
  right,
  exact hnp,
  by_contra hnq,
  apply hn,
  left,
  exact hnq,
end

theorem demorgan_conj_converse :
  (¬Q ∨ ¬P) → ¬(P∧Q)  :=
begin
  intro hnd,
  intro hc,
  cases hc with hcp hcq,
  cases hnd with hndp hndq,
  contradiction,
  contradiction,
end

theorem demorgan_conj_law :
  ¬(P∧Q) ↔ (¬Q ∨ ¬P)  :=
begin
  apply iff.intro,
  apply demorgan_conj,
  apply demorgan_conj_converse,
end

theorem demorgan_disj_law :
  ¬(P∨Q) ↔ (¬P ∧ ¬Q)  :=
begin
  apply iff.intro,
  apply demorgan_disj,
  apply demorgan_disj_converse,
end

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

theorem distr_conj_disj :
  P∧(Q∨R) → (P∧Q)∨(P∧R)  :=
begin
  intro hi,
  cases hi with hip hiq,
  cases hiq with hiq hir,
  left,
  split,
  exact hip,
  exact hiq,
  right,
  split,
  exact hip,
  exact hir,
end

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

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

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

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

theorem curry_prop :
  ((P∧Q)→R) → (P→(Q→R))  :=
begin
  intro hi,
  intro hp,
  intro hq,
  apply hi,
  split,
  exact hp,
  exact hq,
end

theorem uncurry_prop :
  (P→(Q→R)) → ((P∧Q)→R)  :=
begin
  intro hi,
  intro hpcq,
  apply hi,
  cases hpcq,
  exact hpcq_left,
  cases hpcq,
  exact hpcq_right,
end

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

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

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

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

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

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

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

theorem conj_idempot :
  (P∧P) ↔ P :=
begin
  apply iff.intro,
  apply weaken_conj_right,
  intro hpp,
  split,
  by_contra,
  contradiction,
  by_contra,
  contradiction,
end

theorem disj_idempot :
  (P∨P) ↔ P  :=
begin
  apply iff.intro,
  intro hd,
  cases hd,
  exact hd,
  exact hd,
  apply weaken_disj_right,
end

end propositional

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

section predicate

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

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

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

theorem demorgan_exists_converse :
  (∀x, ¬P x) → ¬(∃x, P x)  :=
begin
  intro hepx,
  intro nx,
  cases nx with x hpx,
  have hnpx : ¬P x := hepx x,
  contradiction,
end

theorem demorgan_forall :
  ¬(∀x, P x) → (∃x, ¬P x)  :=
begin
  sorry,
end

theorem demorgan_forall_converse :
  (∃x, ¬P x) → ¬(∀x, P x)  :=
begin
  sorry,
end

theorem demorgan_forall_law :
  ¬(∀x, P x) ↔ (∃x, ¬P x)  :=
begin
  sorry,
end

theorem demorgan_exists_law :
  ¬(∃x, P x) ↔ (∀x, ¬P x)  :=
begin
  sorry,
end

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

theorem exists_as_neg_forall :
  (∃x, P x) → ¬(∀x, ¬P x)  :=
begin
  sorry,
end

theorem forall_as_neg_exists :
  (∀x, P x) → ¬(∃x, ¬P x)  :=
begin
  sorry,
end

theorem forall_as_neg_exists_converse :
  ¬(∃x, ¬P x) → (∀x, P x)  :=
begin
  sorry,
end

theorem exists_as_neg_forall_converse :
  ¬(∀x, ¬P x) → (∃x, P x)  :=
begin
  sorry,
end

theorem forall_as_neg_exists_law :
  (∀x, P x) ↔ ¬(∃x, ¬P x)  :=
begin
  sorry,
end

theorem exists_as_neg_forall_law :
  (∃x, P x) ↔ ¬(∀x, ¬P x)  :=
begin
  sorry,
end

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

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

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

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

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

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

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

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

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

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

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

end predicate


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

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

/-

# Figuring out how to use the reals

## The `library_search` tactic

We saw in the previous sheet that we couldn't even prove something
as simple as "if `aₙ → L` then `-aₙ → -L`" because when you write down
the proof carefully, it relies on the fact that `|x - y| = |y - x|`
or, equivalently, that `|(-x)| = |x|`. I say "equivalently" because
`ring` will prove that `-(x - y) = y - x`.

You don't want to be proving stuff like `|x - y| = |y - x|` from first
principles. Someone else has already done all the hard work for you.
All you need to do is to learn how to find out the names of the lemmas.
The `library_search` tactic tells you the names of all these lemmas.
See where it says "try this" -- click there and Lean will replace
`library_search` with the actual name of the lemma. Once you've done
that, hover over the lemma name to see in what generality it holds.

## The `linarith` tactic

Some of the results below are bare inequalities which are too complex
to be in the library. The library contains "natural" or "standard"
results, but it doesn't contain a random inequality fact just because
it happens to be true -- the library just contains "beautiful" facts.
However `linarith` doesn't know about anything other than `=`, `≠`,
`<` and `≤`, so don't expect it to prove any results about `|x|` or
`max A B`.

Experiment with the `library_search` and `linarith` tactics below.
Try and learn something about the naming convention which Lean uses;
see if you can start beginning to guess what various lemmas should be called.

-/

example (x : ℝ) : |(-x)| = |x| :=
begin
  library_search, -- or norm_num
end

example (x y : ℝ) : |x - y| = |y - x| :=
begin
  library_search, -- click where it says "try this" to replace
                  -- library_search with an "exact" proof
                  -- Why do this? Because it's much quicker!
end

example (A B C : ℕ) : max A B ≤ C ↔ A ≤ C ∧ B ≤ C :=
begin
  library_search, -- Hmm. What would a theorem saying "the max is
                  -- less-or-equal to something iff something else
                  -- be called, according to Lean's naming conventions?"
end

example (x y : ℝ) : |x| < y ↔ -y < x ∧ x < y :=
begin
  library_search, -- abs of something less than something...
end

example (ε : ℝ) (hε : 0 < ε) : 0 < ε / 2 :=
begin
  linarith,
end

example (a b x y : ℝ) (h1 : a < x) (h2 : b < y) : a + b < x + y :=
begin
  library_search, -- or linarith, or guess the name...
end

example (ε : ℝ) (hε : 0 < ε) : 0 < ε / 3 :=
begin
  linarith, -- apparently deemed too obscure for the library
end

example (a b c d x y : ℝ) (h1 : a + c < x) (h2 : b + d < y) :
  a + b + c + d < x + y :=
begin
  linarith, -- note that add_lt_add doesn't work because
            -- ((a+b)+c)+d and (a+c)+(b+d) are not definitionally equal
end


/-
Copyright (c) 2020 Kevin Lacker. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kevin Lacker
-/

import tactic.ring
import data.nat.prime

/-!
# IMO 1959 Q1

Prove that the fraction `(21n+4)/(14n+3)` is irreducible for every natural number `n`.

Since Lean doesn't have a concept of "irreducible fractions" per se, we just formalize this
as saying the numerator and denominator are relatively prime.
-/

open nat

lemma calculation (n k : ℕ) (h1 : k ∣ 21 * n + 4) (h2 : k ∣ 14 * n + 3) : k ∣ 1 :=
have h3 : k ∣ 2 * (21 * n + 4), from dvd_mul_of_dvd_right h1 2,
have h4 : k ∣ 3 * (14 * n + 3), from dvd_mul_of_dvd_right h2 3,
have h5 : 3 * (14 * n + 3) = 2 * (21 * n + 4) + 1, by ring,
(nat.dvd_add_right h3).mp (h5 ▸ h4)

theorem imo1959_q1 : ∀ n : ℕ, coprime (21 * n + 4) (14 * n + 3) :=
assume n, coprime_of_dvd' $ λ k hp h1 h2, calculation n k h1 h2


import data.real.basic

/-
In the previous file, we saw how to rewrite using equalities.
The analogue operation with mathematical statements is rewriting using
equivalences. This is also done using the `rw` tactic.
Lean uses ↔ to denote equivalence instead of ⇔.

In the following exercises we will use the lemma:

  sub_nonneg {x y : ℝ} : 0 ≤ y - x ↔ x ≤ y

The curly braces around x and y instead of parentheses mean Lean will always try to figure out what
x and y are from context, unless we really insist on telling it (we'll see how to insist much later).
Let's not worry about that for now.

In order to announce an intermediate statement we use:

  have my_name : my statement,

This triggers the apparition of a new goal: proving the statement. After this is done,
the statement becomes available under the name `my_name`.
We can focus on the current goal by typing tactics between curly braces.
-/

example {a b c : ℝ} (hab : a ≤ b) : c + a ≤ c + b :=
begin
  rw ← sub_nonneg,
  have key : (c + b) - (c + a) = b - a, -- Here we introduce an intermediate statement named key
  { ring, },  -- and prove it between curly braces
  rw key, -- we can now use the key statement
  rw sub_nonneg,
  exact hab,
end

/-
Of course the previous lemma is already in the core library, named `add_le_add_left`, so we can use it below.

Let's prove a variation (without invoking commutativity of addition since this would spoil our fun).
-/

-- 0009
example {a b : ℝ} (hab : a ≤ b) (c : ℝ) : a + c ≤ b + c :=
begin
  rw <- sub_nonneg,
  have key : b+c - (a+c) = b-a,
  { ring, },
  rw key,
  rw sub_nonneg,
  exact hab,
end

/-
Let's see how we could use this lemma. It is already in the core library, under the name `add_le_add_right`:

  add_le_add_right {a b : ℝ} (hab : a ≤ b) (c : ℝ) : a + c ≤ b + c

This can be read as: "add_le_add_right is a function that will take as input real numbers a and b, an
assumption `hab` claiming a ≤ b and a real number c, and will output a proof of a + c ≤ b + c".

In addition, recall that curly braces around a b mean Lean will figure out those arguments unless we
insist to help. This is because they can be deduced from the next argument `hab`.
So it will be sufficient to feed `hab` and c to this function.
-/

example {a b : ℝ}  (ha : 0 ≤ a) : b ≤ a + b :=
begin
  calc b = 0 + b : by ring
     ... ≤ a + b : by exact add_le_add_right ha b,
end

/-
In the second line of the above proof, we need to prove 0 + b ≤ a + b.
The proof after the colon says: this is exactly lemma `add_le_add_right` applied to ha and b.
Actually the `calc` block expects proof terms, and the `by` keyword is used to tell Lean we will use tactics
to build such a proof term. But since the only tactic used in this block is `exact`, we can skip
tactics entirely, and write:
-/

example (a b : ℝ)  (ha : 0 ≤ a) : b ≤ a + b :=
begin
  calc b = 0 + b : by ring
     ... ≤ a + b : add_le_add_right ha b,
end

/- Let's do a variant. -/

-- 0010
example (a b : ℝ) (hb : 0 ≤ b) : a ≤ a + b :=
begin
  calc a = a + 0 : by ring
     ... ≤ a + b : add_le_add_left hb a,
end

/-
The two preceding examples are in the core library :

  le_add_of_nonneg_left  {a b : ℝ} (ha : 0 ≤ a) : b ≤ a + b
  le_add_of_nonneg_right {a b : ℝ} (hb : 0 ≤ b) : a ≤ a + b

Again, there won't be any need to memorize those names, we will
soon see how to get rid of such goals automatically.
But we can already try to understand how their names are built:
"le_add" describe the conclusion "less or equal than some addition"
It comes first because we are focussed on proving stuff, and
auto-completion works by looking at the beginning of words.
"of" introduces assumptions. "nonneg" is Lean's abbreviation for non-negative.
"left" or "right" disambiguates between the two variations.

Let's use those lemmas by hand for now.

Note that you can have several inequalities steps in a `calc` block,
transitivity of inequalities will be used automatically to assemble
the pieces.
-/

-- 0011
example (a b : ℝ) (ha : 0 ≤ a) (hb : 0 ≤ b) : 0 ≤ a + b :=
begin
  calc 0 ≤ a : ha
     ... ≤ a + b : le_add_of_nonneg_right hb,
end

/- And let's combine with our earlier lemmas. -/

-- 0012
example (a b c d : ℝ) (hab : a ≤ b) (hcd : c ≤ d) : a + c ≤ b + d :=
begin
 have key : 0 ≤ d - c, rw sub_nonneg, exact hcd,
 have key' : 0 ≤ b - a, rw sub_nonneg, exact hab,
 calc a + c ≤ a + c + (d-c) : le_add_of_nonneg_right key
        ... = a + d         : by ring
        ... ≤ a + d + (b-a) : le_add_of_nonneg_right key'
        ... = b + d         : by ring,
end

/-
In the above examples, we prepared proofs of assumptions of our lemmas beforehand, so
that we could feed them to the lemmas. This is called forward reasoning.
The `calc` proofs also belong to this category.

We can also announce the use of a lemma, and provide proofs after the fact, using
the `apply` tactic. This is called backward reasoning because we get the conclusion
first, and provide proofs later. Using `rw` on the goal (rather than on an assumption
from the local context) is also backward reasoning.

Let's do that using the lemma

  mul_nonneg {x y : ℝ} (hx : 0 ≤ x) (hy : 0 ≤ y) : 0 ≤ x*y
-/

example (a b c  : ℝ) (hc : 0 ≤ c) (hab : a ≤ b) : a*c ≤ b*c :=
begin
  rw ← sub_nonneg,
  have key : b*c - a*c = (b - a)*c,
  { ring },
  rw key,
  apply mul_nonneg, -- Here we don't provide proofs for the lemma's assumptions
  -- Now we need to provide the proofs.
  { rw sub_nonneg,
    exact hab },
  { exact hc },
end

/-
Let's prove the same statement using only forward reasoning: announcing stuff,
proving it by working with known facts, moving forward.
-/

example (a b c  : ℝ) (hc : 0 ≤ c) (hab : a ≤ b) : a*c ≤ b*c :=
begin
  have hab' : 0 ≤ b - a,
  { rw ← sub_nonneg at hab,
    exact hab, },
  have h₁ : 0 ≤ (b - a)*c,
  { exact mul_nonneg hab' hc },
  have h₂ : (b - a)*c = b*c - a*c,
  { ring, },
  have h₃ : 0 ≤ b*c - a*c,
  { rw h₂ at h₁,
    exact h₁, },
  rw sub_nonneg at h₃,
  exact h₃,
end

/-
One reason why the backward reasoning proof is shorter is because Lean can
infer of lot of things by comparing the goal and the lemma statement. Indeed
in the `apply mul_nonneg` line, we didn't need to tell Lean that x = b - a
and y = c in the lemma. It was infered by "unification" between the lemma
statement and the goal.

To be fair to the forward reasoning version, we should introduce a convenient
variation on `rw`. The `rwa` tactic performs rewrite and then looks for an
assumption matching the goal. We can use it to rewrite our latest proof as:
-/

example (a b c  : ℝ) (hc : 0 ≤ c) (hab : a ≤ b) : a*c ≤ b*c :=
begin
  have hab' : 0 ≤ b - a,
  { rwa ← sub_nonneg at hab, },
  have h₁ : 0 ≤ (b - a)*c,
  { exact mul_nonneg hab' hc },
  have h₂ : (b - a)*c = b*c - a*c,
  { ring, },
  have h₃ : 0 ≤ b*c - a*c,
  { rwa h₂ at h₁, },
  rwa sub_nonneg at h₃,
end

/-
Let's now combine forward and backward reasoning, to get our most
efficient proof of this statement. Note in particular how unification is used
to know what to prove inside the parentheses in the `mul_nonneg` arguments.
-/
example (a b c : ℝ) (hc : 0 ≤ c) (hab : a ≤ b) : a*c ≤ b*c :=
begin
  rw ← sub_nonneg,
  calc 0 ≤ (b - a)*c  : mul_nonneg (by rwa sub_nonneg) hc
     ... =  b*c - a*c : by ring,
end

/-
Let's now practice all three styles using:

  mul_nonneg_of_nonpos_of_nonpos {a b : α} (ha : a ≤ 0) (hb : b ≤ 0) : 0 ≤ a * b

  sub_nonpos {a b : α} : a - b ≤ 0 ↔ a ≤ b
-/

/- First using mostly backward reasoning -/
-- 0013
example (a b c : ℝ) (hc : c ≤ 0) (hab :  a ≤ b) : b*c ≤ a*c :=
begin
  rw ← sub_nonneg,
  rw ← sub_mul,
  apply mul_nonneg_of_nonpos_of_nonpos,
  rw sub_nonpos, exact hab,
  exact hc,
end

/- Using forward reasoning -/
-- 0014
example (a b c : ℝ) (hc : c ≤ 0) (hab :  a ≤ b) : b*c ≤ a*c :=
begin
  have key    : a - b ≤ 0, exact sub_nonpos.2 hab,
  have key'   : 0 ≤ (a - b)*c, exact mul_nonneg_of_nonpos_of_nonpos key hc,
  have key''  : 0 ≤ a*c - b*c, rwa sub_mul at key',
  have key''' : b*c ≤ a*c, rwa sub_nonneg at key'',
  exact key''',
end

/-- Using a combination of both, with a `calc` block -/
-- 0015
example (a b c : ℝ) (hc : c ≤ 0) (hab :  a ≤ b) : b*c ≤ a*c :=
begin
    rw ← sub_nonneg,
    have key : a-b ≤ 0, rw ← sub_nonpos at hab, exact hab,
    calc a*c - b*c = (a-b)*c : by ring
               ... ≥ 0 : by exact mul_nonneg_of_nonpos_of_nonpos key hc,
end

/-
Let's now move to proving implications. Lean denotes implications using
a simple arrow →, the same it uses for functions (say denoting the type of functions
from ℕ to ℕ by ℕ → ℕ). This is because it sees a proof of P ⇒ Q as a function turning
a proof of P into a proof Q.

Many of the examples that we already met are implications under the hood. For instance we proved

  le_add_of_nonneg_left (a b : ℝ) (ha : 0 ≤ a) : b ≤ a + b

But this can be rephrased as

  le_add_of_nonneg_left (a b : ℝ) : 0 ≤ a → b ≤ a + b

In order to prove P → Q, we use the tactic `intros`, followed by an assumption name.
This creates an assumption with that name asserting that P holds, and turns the goal into Q.

Let's check we can go from our old version of `le_add_of_nonneg_left` to the new one.

-/
example (a b : ℝ): 0 ≤ a → b ≤ a + b :=
begin
  intros ha,
  exact le_add_of_nonneg_left ha,
end

/-
Actually Lean doesn't make any difference between those two versions. It is also happy with
-/
example (a b : ℝ): 0 ≤ a → b ≤ a + b :=
le_add_of_nonneg_left

/- No tactic state is shown in the above line because we don't even need to enter
tactic mode using `begin` or `by`.

Let's practise using `intros`. -/

-- 0016
example (a b : ℝ): 0 ≤ b → a ≤ a + b :=
begin
  intros hb,
  exact le_add_of_nonneg_right hb,
end

/-
What about lemmas having more than one assumption? For instance:

  add_nonneg {a b : ℝ} (ha : 0 ≤ a) (hb : 0 ≤ b) : 0 ≤ a + b

A natural idea is to use the conjunction operator (logical AND), which Lean denotes
by ∧. Assumptions built using this operator can be decomposed using the `cases` tactic,
which is a very general assumption-decomposing tactic.
-/
example {a b : ℝ} : (0 ≤ a ∧ 0 ≤ b) → 0 ≤ a + b :=
begin
  intros hyp,
  cases hyp with ha hb,
  exact add_nonneg ha hb,
end

/-
Needing that intermediate line invoking `cases` shows this formulation is not what is used by
Lean. It rather sees `add_nonneg` as two nested implications:
if a is non-negative then if b is non-negative then a+b is non-negative.
It reads funny, but it is much more convenient to use in practice.
-/
example {a b : ℝ} : 0 ≤ a → (0 ≤ b → 0 ≤ a + b) :=
add_nonneg

/-
The above pattern is so common that implications are defined as right-associative operators,
hence parentheses are not needed above.

Let's prove that the naive conjunction version implies the funny Lean version. For this we need
to know how to prove a conjunction. The `split` tactic creates two goals from a conjunction goal.
It can also be used to create two implication goals from an equivalence goal.
-/
example {a b : ℝ} (H : (0 ≤ a ∧ 0 ≤ b) → 0 ≤ a + b) : 0 ≤ a → (0 ≤ b → 0 ≤ a + b) :=
begin
  intros ha,
  intros hb,
  apply H,
  split,
  exact ha,
  exact hb,
end

/-
Let's practice `cases` and `split`. In the next exercise, P, Q and R denote
unspecified mathematical statements.
-/

-- 0017
example (P Q R : Prop) : P ∧ Q → Q ∧ P :=
begin
  intros h,
  cases h with hp hq,
  split,
  exact hq,
  exact hp,
end

/-
Of course using `split` only to be able to use `exact` twice in a row feels silly. One can
also use the anonymous constructor syntax: ⟨ ⟩
Beware those are not parentheses but angle brackets. This is a generic way of providing
compound objects to Lean when Lean already has a very clear idea of what it is waiting for.

So we could have replaced the last three lines by:
  exact ⟨hQ, hP⟩

We can also combine the `intros` steps. We can now compress our earlier proof to:
-/

example {a b : ℝ} (H : (0 ≤ a ∧ 0 ≤ b) → 0 ≤ a + b) : 0 ≤ a → (0 ≤ b → 0 ≤ a + b) :=
begin
  intros ha hb,
  exact H ⟨ha, hb⟩,
end

/-
The anonymous contructor trick actually also works in `intros` provided we use
its recursive version `rintros`. So we can replace
  intro h,
  cases h with h₁ h₂
by
  rintros ⟨h₁, h₂⟩,
Now redo the previous exercise using all those compressing techniques, in exactly two lines. -/

-- 0018
example (P Q R : Prop): P ∧ Q → Q ∧ P :=
begin
  rintros ⟨hp,hq⟩,
  exact ⟨hq,hp⟩,
end

/-
We are ready to come back to the equivalence between the different formulations of
lemmas having two assumptions. Remember the `split` tactic can be used to split
an equivalence into two implications.
-/

-- 0019
example (P Q R : Prop) : (P ∧ Q → R) ↔ (P → (Q → R)) :=
begin
  split,
  intros h hp hq,
  exact h ⟨hp,hq⟩,
  rintros h ⟨hp, hq⟩,
  exact h hp hq,
end

/-
If you used more than five lines in the above exercise then try to compress things
(without simply removing line ends).

One last compression technique: given a proof h of a conjunction P ∧ Q, one can get
a proof of P using h.left and a proof of Q using h.right, without using cases.
One can also use the more generic (but less legible) names h.1 and h.2.

Similarly, given a proof h of P ↔ Q, one can get a proof of P → Q using h.mp
and a proof of Q → P using h.mpr (or the generic h.1 and h.2 that are even less legible
in this case).

Before the final exercise in this file, let's make sure we'll be able to leave
without learning 10 lemma names. The `linarith` tactic will prove any equality or
inequality or contradiction that follows by linear combinations of assumptions from the
context (with constant coefficients).
-/

example (a b : ℝ) (hb : 0 ≤ b) : a ≤ a + b :=
begin
  linarith,
end

/-
Now let's enjoy this for a while.
-/

-- 0020
example (a b : ℝ) (ha : 0 ≤ a) (hb : 0 ≤ b) : 0 ≤ a + b :=
begin
  linarith,
end

/- And let's combine with our earlier lemmas. -/

-- 0021
example (a b c d : ℝ) (hab : a ≤ b) (hcd : c ≤ d) : a + c ≤ b + d :=
begin
  linarith,
end

/-
Final exercise

In the last exercise of this file, we will use the divisibility relation on ℕ,
denoted by ∣ (beware this is a unicode divisibility bar, not the ASCII pipe character),
and the gcd function.

The definitions are the usual ones, but our goal is to avoid using these definitions and
only use the following three lemmas:

  dvd_refl (a : ℕ) : a ∣ a

  dvd_antisymm {a b : ℕ} : a ∣ b → b ∣ a → a = b :=

  dvd_gcd_iff {a b c : ℕ} : c ∣ gcd a b ↔ c ∣ a ∧ c ∣ b
-/

-- All functions and lemmas below are about natural numbers.
open nat

-- 0022
example (a b : ℕ) : a ∣ b ↔ gcd a b = a :=
begin
  have m := dvd_gcd_iff.1 (dvd_refl (gcd a b)),
  split,
  intro h,
  have j := dvd_refl a,
  have k : a ∣ gcd a b, exact dvd_gcd_iff.2 ⟨j,h⟩,
  have l : gcd a b ∣ a,  exact m.1,
  exact dvd_antisymm l k,
  intro h, rw h at m, exact m.2,
end