/-
Copyright (c) 2014 Jeremy Avigad. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jeremy Avigad, Leonardo de Moura
-/
import algebra.group_power.order
import algebra.big_operators.basic

/-!
# Definitions and properties of `gcd`, `lcm`, and `coprime`

-/

namespace nat

/-! ### `gcd` -/

theorem gcd_dvd (m n : ℕ) : (gcd m n ∣ m) ∧ (gcd m n ∣ n) :=
gcd.induction m n
  (λn, by rw gcd_zero_left; exact ⟨dvd_zero n, dvd_refl n⟩)
  (λm n npos, by rw ←gcd_rec; exact λ ⟨IH₁, IH₂⟩, ⟨IH₂, (dvd_mod_iff IH₂).1 IH₁⟩)

theorem gcd_dvd_left (m n : ℕ) : gcd m n ∣ m := (gcd_dvd m n).left

theorem gcd_dvd_right (m n : ℕ) : gcd m n ∣ n := (gcd_dvd m n).right

theorem gcd_le_left {m} (n) (h : 0 < m) : gcd m n ≤ m := le_of_dvd h $ gcd_dvd_left m n

theorem gcd_le_right (m) {n} (h : 0 < n) : gcd m n ≤ n := le_of_dvd h $ gcd_dvd_right m n

theorem dvd_gcd {m n k : ℕ} : k ∣ m → k ∣ n → k ∣ gcd m n :=
gcd.induction m n (λn _ kn, by rw gcd_zero_left; exact kn)
  (λn m mpos IH H1 H2, by rw gcd_rec; exact IH ((dvd_mod_iff H1).2 H2) H1)

theorem dvd_gcd_iff {m n k : ℕ} : k ∣ gcd m n ↔ k ∣ m ∧ k ∣ n :=
iff.intro (λ h, ⟨h.trans (gcd_dvd m n).left, h.trans (gcd_dvd m n).right⟩)
          (λ h, dvd_gcd h.left h.right)

theorem gcd_comm (m n : ℕ) : gcd m n = gcd n m :=
dvd_antisymm
  (dvd_gcd (gcd_dvd_right m n) (gcd_dvd_left m n))
  (dvd_gcd (gcd_dvd_right n m) (gcd_dvd_left n m))

theorem gcd_eq_left_iff_dvd {m n : ℕ} : m ∣ n ↔ gcd m n = m :=
⟨λ h, by rw [gcd_rec, mod_eq_zero_of_dvd h, gcd_zero_left],
 λ h, h ▸ gcd_dvd_right m n⟩

theorem gcd_eq_right_iff_dvd {m n : ℕ} : m ∣ n ↔ gcd n m = m :=
by rw gcd_comm; apply gcd_eq_left_iff_dvd

theorem gcd_assoc (m n k : ℕ) : gcd (gcd m n) k = gcd m (gcd n k) :=
dvd_antisymm
  (dvd_gcd
    ((gcd_dvd_left (gcd m n) k).trans (gcd_dvd_left m n))
    (dvd_gcd ((gcd_dvd_left (gcd m n) k).trans (gcd_dvd_right m n))
      (gcd_dvd_right (gcd m n) k)))
  (dvd_gcd
    (dvd_gcd (gcd_dvd_left m (gcd n k)) ((gcd_dvd_right m (gcd n k)).trans (gcd_dvd_left n k)))
    ((gcd_dvd_right m (gcd n k)).trans (gcd_dvd_right n k)))

@[simp] theorem gcd_one_right (n : ℕ) : gcd n 1 = 1 :=
eq.trans (gcd_comm n 1) $ gcd_one_left n

theorem gcd_mul_left (m n k : ℕ) : gcd (m * n) (m * k) = m * gcd n k :=
gcd.induction n k
  (λk, by repeat {rw mul_zero <|> rw gcd_zero_left})
  (λk n H IH, by rwa [←mul_mod_mul_left, ←gcd_rec, ←gcd_rec] at IH)

theorem gcd_mul_right (m n k : ℕ) : gcd (m * n) (k * n) = gcd m k * n :=
by rw [mul_comm m n, mul_comm k n, mul_comm (gcd m k) n, gcd_mul_left]

theorem gcd_pos_of_pos_left {m : ℕ} (n : ℕ) (mpos : 0 < m) : 0 < gcd m n :=
pos_of_dvd_of_pos (gcd_dvd_left m n) mpos

theorem gcd_pos_of_pos_right (m : ℕ) {n : ℕ} (npos : 0 < n) : 0 < gcd m n :=
pos_of_dvd_of_pos (gcd_dvd_right m n) npos

theorem eq_zero_of_gcd_eq_zero_left {m n : ℕ} (H : gcd m n = 0) : m = 0 :=
or.elim (nat.eq_zero_or_pos m) id
  (assume H1 : 0 < m, absurd (eq.symm H) (ne_of_lt (gcd_pos_of_pos_left _ H1)))

theorem eq_zero_of_gcd_eq_zero_right {m n : ℕ} (H : gcd m n = 0) : n = 0 :=
by rw gcd_comm at H; exact eq_zero_of_gcd_eq_zero_left H

@[simp] theorem gcd_eq_zero_iff {i j : ℕ} : gcd i j = 0 ↔ i = 0 ∧ j = 0 :=
begin
  split,
  { intro h,
    exact ⟨eq_zero_of_gcd_eq_zero_left h, eq_zero_of_gcd_eq_zero_right h⟩, },
  { rintro ⟨rfl, rfl⟩,
    exact nat.gcd_zero_right 0 }
end

theorem gcd_div {m n k : ℕ} (H1 : k ∣ m) (H2 : k ∣ n) :
  gcd (m / k) (n / k) = gcd m n / k :=
or.elim (nat.eq_zero_or_pos k)
  (λk0, by rw [k0, nat.div_zero, nat.div_zero, nat.div_zero, gcd_zero_right])
  (λH3, nat.eq_of_mul_eq_mul_right H3 $ by rw [
    nat.div_mul_cancel (dvd_gcd H1 H2), ←gcd_mul_right,
    nat.div_mul_cancel H1, nat.div_mul_cancel H2])

theorem gcd_greatest {a b d : ℕ} (hda : d ∣ a) (hdb : d ∣ b)
  (hd : ∀ e : ℕ, e ∣ a → e ∣ b → e ∣ d) : d = a.gcd b :=
(dvd_antisymm (hd _ (gcd_dvd_left a b) (gcd_dvd_right a b)) (dvd_gcd hda hdb)).symm

theorem gcd_dvd_gcd_of_dvd_left {m k : ℕ} (n : ℕ) (H : m ∣ k) : gcd m n ∣ gcd k n :=
dvd_gcd ((gcd_dvd_left m n).trans H) (gcd_dvd_right m n)

theorem gcd_dvd_gcd_of_dvd_right {m k : ℕ} (n : ℕ) (H : m ∣ k) : gcd n m ∣ gcd n k :=
dvd_gcd (gcd_dvd_left n m) ((gcd_dvd_right n m).trans H)

theorem gcd_dvd_gcd_mul_left (m n k : ℕ) : gcd m n ∣ gcd (k * m) n :=
gcd_dvd_gcd_of_dvd_left _ (dvd_mul_left _ _)

theorem gcd_dvd_gcd_mul_right (m n k : ℕ) : gcd m n ∣ gcd (m * k) n :=
gcd_dvd_gcd_of_dvd_left _ (dvd_mul_right _ _)

theorem gcd_dvd_gcd_mul_left_right (m n k : ℕ) : gcd m n ∣ gcd m (k * n) :=
gcd_dvd_gcd_of_dvd_right _ (dvd_mul_left _ _)

theorem gcd_dvd_gcd_mul_right_right (m n k : ℕ) : gcd m n ∣ gcd m (n * k) :=
gcd_dvd_gcd_of_dvd_right _ (dvd_mul_right _ _)

theorem gcd_eq_left {m n : ℕ} (H : m ∣ n) : gcd m n = m :=
dvd_antisymm (gcd_dvd_left _ _) (dvd_gcd dvd_rfl H)

theorem gcd_eq_right {m n : ℕ} (H : n ∣ m) : gcd m n = n :=
by rw [gcd_comm, gcd_eq_left H]

-- Lemmas where one argument is a multiple of the other

@[simp] lemma gcd_mul_left_left (m n : ℕ) : gcd (m * n) n = n :=
dvd_antisymm (gcd_dvd_right _ _) (dvd_gcd (dvd_mul_left _ _) dvd_rfl)

@[simp] lemma gcd_mul_left_right (m n : ℕ) : gcd n (m * n) = n :=
by rw [gcd_comm, gcd_mul_left_left]

@[simp] lemma gcd_mul_right_left (m n : ℕ) : gcd (n * m) n = n :=
by rw [mul_comm, gcd_mul_left_left]

@[simp] lemma gcd_mul_right_right (m n : ℕ) : gcd n (n * m) = n :=
by rw [gcd_comm, gcd_mul_right_left]

-- Lemmas for repeated application of `gcd`

@[simp] lemma gcd_gcd_self_right_left (m n : ℕ) : gcd m (gcd m n) = gcd m n :=
dvd_antisymm (gcd_dvd_right _ _) (dvd_gcd (gcd_dvd_left _ _) dvd_rfl)

@[simp] lemma gcd_gcd_self_right_right (m n : ℕ) : gcd m (gcd n m) = gcd n m :=
by rw [gcd_comm n m, gcd_gcd_self_right_left]

@[simp] lemma gcd_gcd_self_left_right (m n : ℕ) : gcd (gcd n m) m = gcd n m :=
by rw [gcd_comm, gcd_gcd_self_right_right]

@[simp] lemma gcd_gcd_self_left_left (m n : ℕ) : gcd (gcd m n) m = gcd m n :=
by rw [gcd_comm m n, gcd_gcd_self_left_right]

-- Lemmas where one argument consists of addition of a multiple of the other

@[simp] lemma gcd_add_mul_right_right (m n k : ℕ) : gcd m (n + k * m) = gcd m n :=
by simp [gcd_rec m (n + k * m), gcd_rec m n]

@[simp] lemma gcd_add_mul_left_right (m n k : ℕ) : gcd m (n + m * k) = gcd m n :=
by simp [gcd_rec m (n + m * k), gcd_rec m n]

@[simp] lemma gcd_mul_right_add_right (m n k : ℕ) : gcd m (k * m + n) = gcd m n :=
by simp [add_comm _ n]

@[simp] lemma gcd_mul_left_add_right (m n k : ℕ) : gcd m (m * k + n) = gcd m n :=
by simp [add_comm _ n]

@[simp] lemma gcd_add_mul_right_left (m n k : ℕ) : gcd (m + k * n) n = gcd m n :=
by rw [gcd_comm, gcd_add_mul_right_right, gcd_comm]

@[simp] lemma gcd_add_mul_left_left (m n k : ℕ) : gcd (m + n * k) n = gcd m n :=
by rw [gcd_comm, gcd_add_mul_left_right, gcd_comm]

@[simp] lemma gcd_mul_right_add_left (m n k : ℕ) : gcd (k * n + m) n = gcd m n :=
by rw [gcd_comm, gcd_mul_right_add_right, gcd_comm]

@[simp] lemma gcd_mul_left_add_left (m n k : ℕ) : gcd (n * k + m) n = gcd m n :=
by rw [gcd_comm, gcd_mul_left_add_right, gcd_comm]

-- Lemmas where one argument consists of an addition of the other

@[simp] lemma gcd_add_self_right (m n : ℕ) : gcd m (n + m) = gcd m n :=
eq.trans (by rw one_mul) (gcd_add_mul_right_right m n 1)

@[simp] lemma gcd_add_self_left (m n : ℕ) : gcd (m + n) n = gcd m n :=
by rw [gcd_comm, gcd_add_self_right, gcd_comm]

@[simp] lemma gcd_self_add_left (m n : ℕ) : gcd (m + n) m = gcd n m :=
by rw [add_comm, gcd_add_self_left]

@[simp] lemma gcd_self_add_right (m n : ℕ) : gcd m (m + n) = gcd m n :=
by rw [add_comm, gcd_add_self_right]

/-! ### `lcm` -/

theorem lcm_comm (m n : ℕ) : lcm m n = lcm n m :=
by delta lcm; rw [mul_comm, gcd_comm]

@[simp]
theorem lcm_zero_left (m : ℕ) : lcm 0 m = 0 :=
by delta lcm; rw [zero_mul, nat.zero_div]

@[simp]
theorem lcm_zero_right (m : ℕ) : lcm m 0 = 0 := lcm_comm 0 m ▸ lcm_zero_left m

@[simp]
theorem lcm_one_left (m : ℕ) : lcm 1 m = m :=
by delta lcm; rw [one_mul, gcd_one_left, nat.div_one]

@[simp]
theorem lcm_one_right (m : ℕ) : lcm m 1 = m := lcm_comm 1 m ▸ lcm_one_left m

@[simp]
theorem lcm_self (m : ℕ) : lcm m m = m :=
or.elim (nat.eq_zero_or_pos m)
  (λh, by rw [h, lcm_zero_left])
  (λh, by delta lcm; rw [gcd_self, nat.mul_div_cancel _ h])

theorem dvd_lcm_left (m n : ℕ) : m ∣ lcm m n :=
dvd.intro (n / gcd m n) (nat.mul_div_assoc _ $ gcd_dvd_right m n).symm

theorem dvd_lcm_right (m n : ℕ) : n ∣ lcm m n :=
lcm_comm n m ▸ dvd_lcm_left n m

theorem gcd_mul_lcm (m n : ℕ) : gcd m n * lcm m n = m * n :=
by delta lcm; rw [nat.mul_div_cancel' ((gcd_dvd_left m n).trans (dvd_mul_right m n))]

theorem lcm_dvd {m n k : ℕ} (H1 : m ∣ k) (H2 : n ∣ k) : lcm m n ∣ k :=
or.elim (nat.eq_zero_or_pos k)
  (λh, by rw h; exact dvd_zero _)
  (λkpos, dvd_of_mul_dvd_mul_left (gcd_pos_of_pos_left n (pos_of_dvd_of_pos H1 kpos)) $
    by rw [gcd_mul_lcm, ←gcd_mul_right, mul_comm n k];
       exact dvd_gcd (mul_dvd_mul_left _ H2) (mul_dvd_mul_right H1 _))

theorem lcm_dvd_mul (m n : ℕ) : lcm m n ∣ m * n :=
lcm_dvd (dvd_mul_right _ _) (dvd_mul_left _ _)

lemma lcm_dvd_iff {m n k : ℕ} : lcm m n ∣ k ↔ m ∣ k ∧ n ∣ k :=
⟨λ h, ⟨(dvd_lcm_left _ _).trans h, (dvd_lcm_right _ _).trans h⟩,
  and_imp.2 lcm_dvd⟩

theorem lcm_assoc (m n k : ℕ) : lcm (lcm m n) k = lcm m (lcm n k) :=
dvd_antisymm
  (lcm_dvd
    (lcm_dvd (dvd_lcm_left m (lcm n k)) ((dvd_lcm_left n k).trans (dvd_lcm_right m (lcm n k))))
    ((dvd_lcm_right n k).trans (dvd_lcm_right m (lcm n k))))
  (lcm_dvd
    ((dvd_lcm_left m n).trans (dvd_lcm_left (lcm m n) k))
    (lcm_dvd ((dvd_lcm_right m n).trans (dvd_lcm_left (lcm m n) k))
      (dvd_lcm_right (lcm m n) k)))

theorem lcm_ne_zero {m n : ℕ} (hm : m ≠ 0) (hn : n ≠ 0) : lcm m n ≠ 0 :=
by { intro h, simpa [h, hm, hn] using gcd_mul_lcm m n, }

/-!
### `coprime`

See also `nat.coprime_of_dvd` and `nat.coprime_of_dvd'` to prove `nat.coprime m n`.
-/

instance (m n : ℕ) : decidable (coprime m n) := by unfold coprime; apply_instance

theorem coprime_iff_gcd_eq_one {m n : ℕ} : coprime m n ↔ gcd m n = 1 := iff.rfl

theorem coprime.gcd_eq_one {m n : ℕ} (h : coprime m n) : gcd m n = 1 := h

theorem coprime.lcm_eq_mul {m n : ℕ} (h : coprime m n) : lcm m n = m * n :=
by rw [←one_mul (lcm m n), ←h.gcd_eq_one, gcd_mul_lcm]

theorem coprime.symm {m n : ℕ} : coprime n m → coprime m n := (gcd_comm m n).trans

theorem coprime_comm {m n : ℕ} : coprime n m ↔ coprime m n := ⟨coprime.symm, coprime.symm⟩

theorem coprime.dvd_of_dvd_mul_right {m n k : ℕ} (H1 : coprime k n) (H2 : k ∣ m * n) : k ∣ m :=
let t := dvd_gcd (dvd_mul_left k m) H2 in
by rwa [gcd_mul_left, H1.gcd_eq_one, mul_one] at t

theorem coprime.dvd_of_dvd_mul_left {m n k : ℕ} (H1 : coprime k m) (H2 : k ∣ m * n) : k ∣ n :=
by rw mul_comm at H2; exact H1.dvd_of_dvd_mul_right H2

theorem coprime.dvd_mul_right {m n k : ℕ} (H : coprime k n) : k ∣ m * n ↔ k ∣ m :=
⟨H.dvd_of_dvd_mul_right, λ h, dvd_mul_of_dvd_left h n⟩

theorem coprime.dvd_mul_left {m n k : ℕ} (H : coprime k m) : k ∣ m * n ↔ k ∣ n :=
⟨H.dvd_of_dvd_mul_left, λ h, dvd_mul_of_dvd_right h m⟩

theorem coprime.gcd_mul_left_cancel {k : ℕ} (m : ℕ) {n : ℕ} (H : coprime k n) :
   gcd (k * m) n = gcd m n :=
have H1 : coprime (gcd (k * m) n) k,
by rw [coprime, gcd_assoc, H.symm.gcd_eq_one, gcd_one_right],
dvd_antisymm
  (dvd_gcd (H1.dvd_of_dvd_mul_left (gcd_dvd_left _ _)) (gcd_dvd_right _ _))
  (gcd_dvd_gcd_mul_left _ _ _)

theorem coprime.gcd_mul_right_cancel (m : ℕ) {k n : ℕ} (H : coprime k n) :
   gcd (m * k) n = gcd m n :=
by rw [mul_comm m k, H.gcd_mul_left_cancel m]

theorem coprime.gcd_mul_left_cancel_right {k m : ℕ} (n : ℕ) (H : coprime k m) :
   gcd m (k * n) = gcd m n :=
by rw [gcd_comm m n, gcd_comm m (k * n), H.gcd_mul_left_cancel n]

theorem coprime.gcd_mul_right_cancel_right {k m : ℕ} (n : ℕ) (H : coprime k m) :
   gcd m (n * k) = gcd m n :=
by rw [mul_comm n k, H.gcd_mul_left_cancel_right n]

theorem coprime_div_gcd_div_gcd {m n : ℕ} (H : 0 < gcd m n) :
  coprime (m / gcd m n) (n / gcd m n) :=
by rw [coprime_iff_gcd_eq_one, gcd_div (gcd_dvd_left m n) (gcd_dvd_right m n), nat.div_self H]

theorem not_coprime_of_dvd_of_dvd {m n d : ℕ} (dgt1 : 1 < d) (Hm : d ∣ m) (Hn : d ∣ n) :
  ¬ coprime m n :=
λ co, not_lt_of_ge (le_of_dvd zero_lt_one $ by rw [←co.gcd_eq_one]; exact dvd_gcd Hm Hn) dgt1

theorem exists_coprime {m n : ℕ} (H : 0 < gcd m n) :
  ∃ m' n', coprime m' n' ∧ m = m' * gcd m n ∧ n = n' * gcd m n :=
⟨_, _, coprime_div_gcd_div_gcd H,
  (nat.div_mul_cancel (gcd_dvd_left m n)).symm,
  (nat.div_mul_cancel (gcd_dvd_right m n)).symm⟩

theorem exists_coprime' {m n : ℕ} (H : 0 < gcd m n) :
  ∃ g m' n', 0 < g ∧ coprime m' n' ∧ m = m' * g ∧ n = n' * g :=
let ⟨m', n', h⟩ := exists_coprime H in ⟨_, m', n', H, h⟩

@[simp] theorem coprime_add_self_right {m n : ℕ} : coprime m (n + m) ↔ coprime m n :=
by rw [coprime, coprime, gcd_add_self_right]

@[simp] theorem coprime_self_add_right {m n : ℕ} : coprime m (m + n) ↔ coprime m n :=
by rw [add_comm, coprime_add_self_right]

@[simp] theorem coprime_add_self_left {m n : ℕ} : coprime (m + n) n ↔ coprime m n :=
by rw [coprime, coprime, gcd_add_self_left]

@[simp] theorem coprime_self_add_left {m n : ℕ} : coprime (m + n) m ↔ coprime n m :=
by rw [coprime, coprime, gcd_self_add_left]

@[simp] lemma coprime_add_mul_right_right (m n k : ℕ) : coprime m (n + k * m) ↔ coprime m n :=
by rw [coprime, coprime, gcd_add_mul_right_right]

@[simp] lemma coprime_add_mul_left_right (m n k : ℕ) : coprime m (n + m * k) ↔ coprime m n :=
by rw [coprime, coprime, gcd_add_mul_left_right]

@[simp] lemma coprime_mul_right_add_right (m n k : ℕ) : coprime m (k * m + n) ↔ coprime m n :=
by rw [coprime, coprime, gcd_mul_right_add_right]

@[simp] lemma coprime_mul_left_add_right (m n k : ℕ) : coprime m (m * k + n) ↔ coprime m n :=
by rw [coprime, coprime, gcd_mul_left_add_right]

@[simp] lemma coprime_add_mul_right_left (m n k : ℕ) : coprime (m + k * n) n ↔ coprime m n :=
by rw [coprime, coprime, gcd_add_mul_right_left]

@[simp] lemma coprime_add_mul_left_left (m n k : ℕ) : coprime (m + n * k) n ↔ coprime m n :=
by rw [coprime, coprime, gcd_add_mul_left_left]

@[simp] lemma coprime_mul_right_add_left (m n k : ℕ) : coprime (k * n + m) n ↔ coprime m n :=
by rw [coprime, coprime, gcd_mul_right_add_left]

@[simp] lemma coprime_mul_left_add_left (m n k : ℕ) : coprime (n * k + m) n ↔ coprime m n :=
by rw [coprime, coprime, gcd_mul_left_add_left]

theorem coprime.mul {m n k : ℕ} (H1 : coprime m k) (H2 : coprime n k) : coprime (m * n) k :=
(H1.gcd_mul_left_cancel n).trans H2

theorem coprime.mul_right {k m n : ℕ} (H1 : coprime k m) (H2 : coprime k n) : coprime k (m * n) :=
(H1.symm.mul H2.symm).symm

theorem coprime.coprime_dvd_left {m k n : ℕ} (H1 : m ∣ k) (H2 : coprime k n) : coprime m n :=
eq_one_of_dvd_one (by delta coprime at H2; rw ← H2; exact gcd_dvd_gcd_of_dvd_left _ H1)

theorem coprime.coprime_dvd_right {m k n : ℕ} (H1 : n ∣ m) (H2 : coprime k m) : coprime k n :=
(H2.symm.coprime_dvd_left H1).symm

theorem coprime.coprime_mul_left {k m n : ℕ} (H : coprime (k * m) n) : coprime m n :=
H.coprime_dvd_left (dvd_mul_left _ _)

theorem coprime.coprime_mul_right {k m n : ℕ} (H : coprime (m * k) n) : coprime m n :=
H.coprime_dvd_left (dvd_mul_right _ _)

theorem coprime.coprime_mul_left_right {k m n : ℕ} (H : coprime m (k * n)) : coprime m n :=
H.coprime_dvd_right (dvd_mul_left _ _)

theorem coprime.coprime_mul_right_right {k m n : ℕ} (H : coprime m (n * k)) : coprime m n :=
H.coprime_dvd_right (dvd_mul_right _ _)

theorem coprime.coprime_div_left {m n a : ℕ} (cmn : coprime m n) (dvd : a ∣ m) :
  coprime (m / a) n :=
begin
  by_cases a_split : (a = 0),
  { subst a_split,
    rw zero_dvd_iff at dvd,
    simpa [dvd] using cmn, },
  { rcases dvd with ⟨k, rfl⟩,
    rw nat.mul_div_cancel_left _ (nat.pos_of_ne_zero a_split),
    exact coprime.coprime_mul_left cmn, },
end

theorem coprime.coprime_div_right {m n a : ℕ} (cmn : coprime m n) (dvd : a ∣ n) :
  coprime m (n / a) :=
(coprime.coprime_div_left cmn.symm dvd).symm

lemma coprime_mul_iff_left {k m n : ℕ} : coprime (m * n) k ↔ coprime m k ∧ coprime n k :=
⟨λ h, ⟨coprime.coprime_mul_right h, coprime.coprime_mul_left h⟩,
  λ ⟨h, _⟩, by rwa [coprime_iff_gcd_eq_one, coprime.gcd_mul_left_cancel n h]⟩

lemma coprime_mul_iff_right {k m n : ℕ} : coprime k (m * n) ↔ coprime k m ∧ coprime k n :=
by simpa only [coprime_comm] using coprime_mul_iff_left

lemma coprime.gcd_left (k : ℕ) {m n : ℕ} (hmn : coprime m n) : coprime (gcd k m) n :=
hmn.coprime_dvd_left $ gcd_dvd_right k m

lemma coprime.gcd_right (k : ℕ) {m n : ℕ} (hmn : coprime m n) : coprime m (gcd k n) :=
hmn.coprime_dvd_right $ gcd_dvd_right k n

lemma coprime.gcd_both (k l : ℕ) {m n : ℕ} (hmn : coprime m n) : coprime (gcd k m) (gcd l n) :=
(hmn.gcd_left k).gcd_right l

lemma coprime.mul_dvd_of_dvd_of_dvd {a n m : ℕ} (hmn : coprime m n)
  (hm : m ∣ a) (hn : n ∣ a) : m * n ∣ a :=
let ⟨k, hk⟩ := hm in hk.symm ▸ mul_dvd_mul_left _ (hmn.symm.dvd_of_dvd_mul_left (hk ▸ hn))

theorem coprime_one_left : ∀ n, coprime 1 n := gcd_one_left

theorem coprime_one_right : ∀ n, coprime n 1 := gcd_one_right

theorem coprime.pow_left {m k : ℕ} (n : ℕ) (H1 : coprime m k) : coprime (m ^ n) k :=
nat.rec_on n (coprime_one_left _) (λn IH, H1.mul IH)

theorem coprime.pow_right {m k : ℕ} (n : ℕ) (H1 : coprime k m) : coprime k (m ^ n) :=
(H1.symm.pow_left n).symm

theorem coprime.pow {k l : ℕ} (m n : ℕ) (H1 : coprime k l) : coprime (k ^ m) (l ^ n) :=
(H1.pow_left _).pow_right _

@[simp] lemma coprime_pow_left_iff {n : ℕ} (hn : 0 < n) (a b : ℕ)  :
  nat.coprime (a ^ n) b ↔ nat.coprime a b :=
begin
  obtain ⟨n, rfl⟩ := exists_eq_succ_of_ne_zero hn.ne',
  rw [pow_succ, nat.coprime_mul_iff_left],
  exact ⟨and.left, λ hab, ⟨hab, hab.pow_left _⟩⟩
end

@[simp] lemma coprime_pow_right_iff {n : ℕ} (hn : 0 < n) (a b : ℕ)  :
  nat.coprime a (b ^ n) ↔ nat.coprime a b :=
by rw [nat.coprime_comm, coprime_pow_left_iff hn, nat.coprime_comm]

theorem coprime.eq_one_of_dvd {k m : ℕ} (H : coprime k m) (d : k ∣ m) : k = 1 :=
by rw [← H.gcd_eq_one, gcd_eq_left d]

@[simp] theorem coprime_zero_left (n : ℕ) : coprime 0 n ↔ n = 1 :=
by simp [coprime]

@[simp] theorem coprime_zero_right (n : ℕ) : coprime n 0 ↔ n = 1 :=
by simp [coprime]

theorem not_coprime_zero_zero : ¬ coprime 0 0 := by simp

@[simp] theorem coprime_one_left_iff (n : ℕ) : coprime 1 n ↔ true :=
by simp [coprime]

@[simp] theorem coprime_one_right_iff (n : ℕ) : coprime n 1 ↔ true :=
by simp [coprime]

@[simp] theorem coprime_self (n : ℕ) : coprime n n ↔ n = 1 :=
by simp [coprime]

lemma gcd_mul_of_coprime_of_dvd {a b c : ℕ} (hac : coprime a c) (b_dvd_c : b ∣ c) :
  gcd (a * b) c = b :=
begin
  rcases exists_eq_mul_left_of_dvd b_dvd_c with ⟨d, rfl⟩,
  rw [gcd_mul_right],
  convert one_mul b,
  exact coprime.coprime_mul_right_right hac,
end

section big_operators

open_locale big_operators

/-- See `is_coprime.prod_left` for the corresponding lemma about `is_coprime` -/
lemma coprime_prod_left
  {ι : Type*} {x : ℕ} {s : ι → ℕ} {t : finset ι} :
  (∀ (i : ι), i ∈ t → coprime (s i) x) → coprime (∏ (i : ι) in t, s i) x :=
finset.prod_induction s (λ y, y.coprime x) (λ a b, coprime.mul) (by simp)

/-- See `is_coprime.prod_right` for the corresponding lemma about `is_coprime` -/
lemma coprime_prod_right
  {ι : Type*} {x : ℕ} {s : ι → ℕ} {t : finset ι} :
  (∀ (i : ι), i ∈ t → coprime x (s i)) → coprime x (∏ (i : ι) in t, s i) :=
finset.prod_induction s (λ y, x.coprime y) (λ a b, coprime.mul_right) (by simp)

end big_operators

lemma coprime.eq_of_mul_eq_zero {m n : ℕ} (h : m.coprime n) (hmn : m * n = 0) :
  m = 0 ∧ n = 1 ∨ m = 1 ∧ n = 0 :=
(nat.eq_zero_of_mul_eq_zero hmn).imp
  (λ hm, ⟨hm, n.coprime_zero_left.mp $ hm ▸ h⟩)
  (λ hn, ⟨m.coprime_zero_left.mp $ hn ▸ h.symm, hn⟩)

/-- Represent a divisor of `m * n` as a product of a divisor of `m` and a divisor of `n`. -/
def prod_dvd_and_dvd_of_dvd_prod {m n k : ℕ} (H : k ∣ m * n) :
  { d : {m' // m' ∣ m} × {n' // n' ∣ n} // k = d.1 * d.2 } :=
begin
cases h0 : (gcd k m),
case nat.zero
{ have : k = 0 := eq_zero_of_gcd_eq_zero_left h0, subst this,
  have : m = 0 := eq_zero_of_gcd_eq_zero_right h0, subst this,
  exact ⟨⟨⟨0, dvd_refl 0⟩, ⟨n, dvd_refl n⟩⟩, (zero_mul n).symm⟩ },
case nat.succ : tmp
{ have hpos : 0 < gcd k m := h0.symm ▸ nat.zero_lt_succ _; clear h0 tmp,
  have hd : gcd k m * (k / gcd k m) = k := (nat.mul_div_cancel' (gcd_dvd_left k m)),
  refine ⟨⟨⟨gcd k m,  gcd_dvd_right k m⟩, ⟨k / gcd k m, _⟩⟩, hd.symm⟩,
  apply dvd_of_mul_dvd_mul_left hpos,
  rw [hd, ← gcd_mul_right],
  exact dvd_gcd (dvd_mul_right _ _) H }
end

theorem gcd_mul_dvd_mul_gcd (k m n : ℕ) : gcd k (m * n) ∣ gcd k m * gcd k n :=
begin
rcases (prod_dvd_and_dvd_of_dvd_prod $ gcd_dvd_right k (m * n)) with ⟨⟨⟨m', hm'⟩, ⟨n', hn'⟩⟩, h⟩,
replace h : gcd k (m * n) = m' * n' := h,
rw h,
have hm'n' : m' * n' ∣ k := h ▸ gcd_dvd_left _ _,
apply mul_dvd_mul,
  { have hm'k : m' ∣ k := (dvd_mul_right m' n').trans hm'n',
    exact dvd_gcd hm'k hm' },
  { have hn'k : n' ∣ k := (dvd_mul_left n' m').trans hm'n',
    exact dvd_gcd hn'k hn' }
end

theorem coprime.gcd_mul (k : ℕ) {m n : ℕ} (h : coprime m n) : gcd k (m * n) = gcd k m * gcd k n :=
dvd_antisymm
  (gcd_mul_dvd_mul_gcd k m n)
  ((h.gcd_both k k).mul_dvd_of_dvd_of_dvd
    (gcd_dvd_gcd_mul_right_right _ _ _)
    (gcd_dvd_gcd_mul_left_right _ _ _))

theorem pow_dvd_pow_iff {a b n : ℕ} (n0 : 0 < n) : a ^ n ∣ b ^ n ↔ a ∣ b :=
begin
  refine ⟨λ h, _, λ h, pow_dvd_pow_of_dvd h _⟩,
  cases nat.eq_zero_or_pos (gcd a b) with g0 g0,
  { simp [eq_zero_of_gcd_eq_zero_right g0] },
  rcases exists_coprime' g0 with ⟨g, a', b', g0', co, rfl, rfl⟩,
  rw [mul_pow, mul_pow] at h,
  replace h := dvd_of_mul_dvd_mul_right (pow_pos g0' _) h,
  have := pow_dvd_pow a' n0,
  rw [pow_one, (co.pow n n).eq_one_of_dvd h] at this,
  simp [eq_one_of_dvd_one this]
end

lemma gcd_mul_gcd_of_coprime_of_mul_eq_mul {a b c d : ℕ} (cop : c.coprime d) (h : a * b = c * d) :
  a.gcd c * b.gcd c = c :=
begin
  apply dvd_antisymm,
  { apply nat.coprime.dvd_of_dvd_mul_right (nat.coprime.mul (cop.gcd_left _) (cop.gcd_left _)),
    rw ← h,
    apply mul_dvd_mul (gcd_dvd _ _).1 (gcd_dvd _ _).1 },
  { rw [gcd_comm a _, gcd_comm b _],
    transitivity c.gcd (a * b),
    rw [h, gcd_mul_right_right d c],
    apply gcd_mul_dvd_mul_gcd }
end

/-- If `k:ℕ` divides coprime `a` and `b` then `k = 1` -/
lemma eq_one_of_dvd_coprimes {a b k : ℕ} (h_ab_coprime : coprime a b)
  (hka : k ∣ a) (hkb : k ∣ b) : k = 1  :=
begin
  rw coprime_iff_gcd_eq_one at h_ab_coprime,
  have h1 := dvd_gcd hka hkb,
  rw h_ab_coprime at h1,
  exact nat.dvd_one.mp h1,
end

lemma coprime.mul_add_mul_ne_mul {m n a b : ℕ} (cop : coprime m n) (ha : a ≠ 0) (hb : b ≠ 0) :
  a * m + b * n ≠ m * n :=
begin
  intro h,
  obtain ⟨x, rfl⟩ : n ∣ a := cop.symm.dvd_of_dvd_mul_right
    ((nat.dvd_add_iff_left (dvd_mul_left n b)).mpr ((congr_arg _ h).mpr (dvd_mul_left n m))),
  obtain ⟨y, rfl⟩ : m ∣ b := cop.dvd_of_dvd_mul_right
    ((nat.dvd_add_iff_right (dvd_mul_left m (n*x))).mpr ((congr_arg _ h).mpr (dvd_mul_right m n))),
  rw [mul_comm, mul_ne_zero_iff, ←one_le_iff_ne_zero] at ha hb,
  refine mul_ne_zero hb.2 ha.2 (eq_zero_of_mul_eq_self_left (ne_of_gt (add_le_add ha.1 hb.1)) _),
  rw [← mul_assoc, ← h, add_mul, add_mul, mul_comm _ n, ←mul_assoc, mul_comm y]
end

end nat


import BrownCs22.Library.Tactics
import BrownCs22.Library.TruthTables

/-

In this file, we'll keep a running lecture-by-lecture list
of the Lean syntax and tactics that we've learned.

-/

/-

## Lecture 2

`#check <expression>` tells us what kind of thing `<expression>` is.

-/

#check 0
#check 1 + 1 = 3

/-

## Lecture 3

`variable (p q r : Prop)` introduces the atomic propositions `p`, `q`, `r`.
(If we enclose them in `section` ... `end`, their scope is limited.)

-/

section

variable (p q r : Prop)

#check p ∧ q → r

end

/-

`#truth_table p ∧ q ∨ r` prints a truth table for the given proposition.

-/

#truth_table p ∧ q

/-

## Lecture 4

We begin proofs by writing

`example : p → p := by` or
`theorem my_theorem_name : p → p := by`.

Following lines should be indented two spaces.

-/

example : p → p := by
  intro hp
  assumption

/-

The new tactics (proof rules) we saw:

* `sorry`: proves anything! (Cheating!)

* `split_goal`: turn a goal of `p ∧ q` into two goals `p` and `q`,
  and a goal of `p ↔ q` into two goals `p → q` and `q → p`.

* `left` and `right`: turn a goal of `p ∨ q` into a goal of `p`
  or a goal of `q`, respectively.

* `intro h`: turn a goal of `p → q` into a goal of `q`,
  with an extra hypothesis `h : p`.

* `assumption`: if a hypothesis `h` matches the goal, will solve the goal.

* `eliminate h with h1 h2`: when a hypothesis `h : p ∧ q`,
  replaces `h` with two hypotheses `h1 : p` and `h2 : q`.
  When a hypothesis `h : p ∨ q`, creates two goals, one with
  a hypothesis `h1 : p` and one with a hypothesis `h2 : q`.

* `apply h`: if `h : p → q` and the goal is `q`, changes the goal to `p`.
  Read this as "to show `q`, it suffices to show `p`".
  We'll use this later in a slightly different context.

* `have h : q := hpq hp`: if `hpq : p → q` and `hp : p` (the lhs of the → matches),
  creates a new hypothesis `h : q`.
  Read this as "I know `p → q` and I know `p`, so I know `q`."

* `contradiction`: If you have both a proposition and its negation in your context,
  will prove any goal.

-/

/-

## Lecture 6

Some more new tactics, or new behavior for old tactics:

* `intro t`: turn a goal of `∀ x : T, P x` into a goal of `P t`,
  with a new term `t : T` in the context.

* `have h : P t := hP t`: if `hP : ∀ x : T, P x` and `t : T`,
  creates a new hypothesis `h : P t`.
  Read this as "I know every `T` has property `P`, and `t` is a `T`,
  so `t` must have property `P`.

* `existsi t`: turns a goal of `∃ x : T, G x` into a goal of `G t`.
  This is the tactic we use to provide a witness.

* `eliminate h with t ht`: when a hypothesis `h :  ∃ x : T, H x`,
  replaces `h` with a new witness `t` and the hypothesis `ht : H t`.
  You should always use a fresh name for `t`.

* `reflexivity`: proves equality goals where the LHS and RHS are the same,
  like `x = x` and `f y = f y`.

-/

/-

## Homework 2

* `positivity`: if your goal is to show something is positive or nonnegative
  (like `0 < x`, `x ≥ 0`, `0 ≤ a^2`, ...) and this "obviously" follows from
  your hypotheses, write `positivity` to finish that goal. This tactic knows
  basic facts like "natural numbers are nonnegative" and "the square of a
  positive number is positive." It does not know complicated arithmetic.

* `numbers`: If your goal is to show an arithmetic statement about numerals,
  like `5 + 5 = 10` or `1000 < 50000000`, `numbers` will close the goal.
  It's basically a calculator!

* `linarith`: stands for "linear arithmetic." (If you don't know this term,
  don't worry.) `linarith` does similar things to `positivity` and `numbers`,
  but it can do some simple arithmetic, and use hypotheses.
  For instance, if you know `h1 : x < 10` and `h2 : x + y < 20`, `linarith`
  can prove the goal `3*x + 2*y < 50`.

-/


/-
From LF Require Export Basics.
-/

import data.nat.basic
import tactic.basic
import .ch01_basics

open basics (evenb)
open nat (succ)

namespace induction

/-
Theorem plus_n_O_firsttry : ∀n:nat,
  n = n + 0.
Proof.
  intros n.
  simpl. (* Does nothing! *)
Abort.
-/

/- swapping order as this is refl in lean -/

/- zero_add is a theorem used by simp in lean -/
theorem zero_add_firsttry (n : ℕ) : 0 + n = n := by squeeze_simp

/-
Theorem plus_n_O_secondtry : ∀n:nat,
  n = n + 0.
Proof.
  intros n. destruct n as [| n'] eqn:E.
  - (* n = 0 *)
    reflexivity. (* so far so good... *)
  - (* n = S n' *)
    simpl. (* ...but here we are stuck again *)
Abort.
-/

/- zero_add is still a theorem in lean -/
theorem zero_add_secondtry (n : ℕ) : 0 + n = n :=
begin
  cases n,
    refl,
  squeeze_simp,
end

/-
Theorem plus_n_O : ∀n:nat, n = n + 0.
Proof.
  intros n. induction n as [| n' IHn'].
  - (* n = 0 *) reflexivity.
  - (* n = S n' *) simpl. rewrite <- IHn'. reflexivity. Qed.
-/

theorem zero_add (n : ℕ) : 0 + n = n :=
begin
  induction n with n ih,
    refl,
  rw nat.add_succ,
  rw ih,
end

/-
Theorem minus_diag : ∀n,
  minus n n = 0.
Proof.
  (* WORKED IN CLASS *)
  intros n. induction n as [| n' IHn'].
  - (* n = 0 *)
    simpl. reflexivity.
  - (* n = S n' *)
    simpl. rewrite → IHn'. reflexivity. Qed.
-/

theorem sub_self (n : ℕ) : n - n = 0 :=
begin
  induction n with n ih,
    refl,
  /- don't use version from library -/
  simp only [nat.succ_sub_succ_eq_sub],
  rw ih,
end

/-
Theorem mult_0_r : ∀n:nat,
  n * 0 = 0.
Proof.

  (* FILL IN HERE *) Admitted.
Theorem plus_n_Sm : ∀n m : nat,
  S (n + m) = n + (S m).
Proof.
  (* FILL IN HERE *) Admitted.

Theorem plus_comm : ∀n m : nat,
  n + m = m + n.
Proof.
  (* FILL IN HERE *) Admitted.

Theorem plus_assoc : ∀n m p : nat,
  n + (m + p) = (n + m) + p.
Proof.
  (* FILL IN HERE *) Admitted.
-/
/-
  n.b. lean defines mul with recursion on the right,
  which makes this too trivial
-/
theorem mul_zero (n : ℕ) : n * 0 = 0 := rfl

/-
  defining other way to get similar challenge as expected
-/
theorem mul_succ (n m : ℕ) : n * succ m = (n * m) + n := rfl

theorem zero_mul (n : ℕ) : 0 * n = 0 :=
begin
  induction n with n ih,
    refl,
  rw mul_succ,
  rw ih,
end

theorem add_succ (n m : ℕ) : n + succ m = succ (n + m) := rfl

theorem succ_add (n m : ℕ) : succ n + m = succ (n + m) :=
begin
  induction m with m ih,
    refl,
  rw add_succ,
  rw ih,
end

theorem add_comm (n m : ℕ) : n + m = m + n :=
begin
  induction n with n ih,
    rw add_zero,
    rw zero_add,
  rw add_succ,
  rw succ_add,
  rw ih,
end

theorem add_assoc (n m p : ℕ) : n + (m + p) = (n + m) + p :=
begin
  induction n with n ih,
    rw [zero_add, zero_add],
  rw [succ_add, succ_add, succ_add],
  rw ih,
end

/-
Fixpoint double (n:nat) :=
  match n with
  | O ⇒ O
  | S n' ⇒ S (S (double n'))
  end.
-/

def double : ℕ → ℕ
| 0 := 0
| (n + 1) := double n + 2

/-
Lemma double_plus : ∀n, double n = n + n .
Proof.
  (* FILL IN HERE *) Admitted.
-/

lemma double_add (n : ℕ) : double n = n + n :=
begin
  induction n with n ih,
    refl,
  rw double,
  rw ih,
  rw succ_add,
  rw [add_succ, add_succ, add_succ],
  rw add_zero,
end

/-
Theorem evenb_S : ∀n : nat,
  evenb (S n) = negb (evenb n).
Proof.
  (* FILL IN HERE *) Admitted.
-/

theorem evenb_succ (n : ℕ) : evenb (succ n) = bnot (evenb n) :=
begin
  induction n with n ih,
    refl,
  rw evenb,
  rw ih,
  rw bnot_bnot,
end

/-
Theorem mult_0_plus' : ∀n m : nat,
  (0 + n) * m = n * m.
Proof.
  intros n m.
  assert (H: 0 + n = n). { reflexivity. }
  rewrite → H.
  reflexivity. Qed.
-/

theorem zero_add_mul' (n m : ℕ) : (0 + n) * m = n * m :=
begin
  have h : 0 + n = n, rw zero_add,
  rewrite h,
end

/-
Theorem plus_rearrange_firsttry : ∀n m p q : nat,
  (n + m) + (p + q) = (m + n) + (p + q).
Proof.
  intros n m p q.
  (* We just need to swap (n + m) for (m + n)... seems
     like plus_comm should do the trick! *)
  rewrite → plus_comm.
  (* Doesn't work...Coq rewrites the wrong plus! *)
Abort.
-/

theorem add_rearrange_firsttry (n m p q : ℕ)
  : (n + m) + (p + q) = (m + n) + (p + q) :=
begin
  rw add_comm,
  /- simp is a bit smarter though -/
  /- ac_refl also works -/
  simp [add_comm],
end

/-
Theorem plus_rearrange : ∀n m p q : nat,
  (n + m) + (p + q) = (m + n) + (p + q).
Proof.
  intros n m p q.
  assert (H: n + m = m + n).
  { rewrite → plus_comm. reflexivity. }
  rewrite → H. reflexivity. Qed.
-/

theorem add_rearrange (n m p q : ℕ)
  : (n + m) + (p + q) = (m + n) + (p + q) :=
begin
  have h : n + m = m + n,
    rw add_comm,
  rw h,
end

/- or tell it where to apply -/
theorem add_rearrange' (n m p q : ℕ)
  : (n + m) + (p + q) = (m + n) + (p + q) := by rw add_comm n m

/-
Theorem plus_assoc' : ∀n m p : nat,
  n + (m + p) = (n + m) + p.
Proof. intros n m p. induction n as [| n' IHn']. reflexivity.
  simpl. rewrite → IHn'. reflexivity. Qed.
-/

theorem add_assoc' (n m p : ℕ) : n + (m + p) = (n + m) + p :=
begin
  induction n with n ih,
    rw [zero_add, zero_add],
  rw [succ_add, succ_add, succ_add],
  rw ih,
end

/-
Theorem plus_assoc'' : ∀n m p : nat,
  n + (m + p) = (n + m) + p.
Proof.
  intros n m p. induction n as [| n' IHn'].
  - (* n = 0 *)
    reflexivity.
  - (* n = S n' *)
    simpl. rewrite → IHn'. reflexivity. Qed.
-/

theorem add_assoc'' (n m p : ℕ) : n + (m + p) = (n + m) + p :=
begin
  induction n,
  case zero {
    show 0 + (m + p) = (0 + m) + p,
    rw [zero_add, zero_add],
  },
  case succ : n ih {
    show (succ n) + (m + p) = ((succ n) + m) + p,
    rw [succ_add, succ_add, succ_add],
    show succ (n + (m + p)) = succ ((n + m) + p),
    rw ih,
  }
end

end induction


import ..lectures.love03_forward_proofs_demo

/-! # LoVe Exercise 4: Functional Programming -/

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

/-! ## Question 1: Reverse of a List

We define a new accumulator-based version of `reverse`. The first argument,
`as`, serves as the accumulator. This definition is __tail-recursive__, meaning
that compilers and interpreters can easily optimize the recursion away,
resulting in more efficient code. -/

def accurev {α : Type} : list α → list α → list α
| as []        := as
| as (x :: xs) := accurev (x :: as) xs

/-! 1.1. Our intention is that `accurev [] xs` should be equal to `reverse xs`.
But if we start an induction, we quickly see that the induction hypothesis is
not strong enough. Start by proving the following generalization (using the
`induction'` tactic or pattern matching): -/

lemma accurev_eq_reverse_append {α : Type} :
  ∀as xs : list α, accurev as xs = reverse xs ++ as
| as []        := by refl
| as (x :: xs) := by simp [reverse, accurev, accurev_eq_reverse_append _ xs]

/-! 1.2. Derive the desired equation. -/

lemma accurev_eq_reverse {α : Type} (xs : list α) :
  accurev [] xs = reverse xs :=
by simp [accurev_eq_reverse_append]

/-! 1.3. Prove the following property.

Hint: A one-line inductionless proof is possible. -/

lemma accurev_accurev {α : Type} (xs : list α) :
  accurev [] (accurev [] xs) = xs :=
by simp [accurev_eq_reverse, reverse_reverse]

/-! 1.4. Prove the following lemma by structural induction, as a "paper" proof.
This is a good exercise to develop a deeper understanding of how structural
induction works.

    lemma accurev_eq_reverse_append {α : Type} :
      ∀as xs : list α, accurev as xs = reverse xs ++ as

Guidelines for paper proofs:

We expect detailed, rigorous, mathematical proofs. You are welcome to use
standard mathematical notation or Lean structured commands (e.g., `assume`,
`have`, `show`, `calc`). You can also use tactical proofs (e.g., `intro`,
`apply`), but then please indicate some of the intermediate goals, so that we
can follow the chain of reasoning.

Major proof steps, including applications of induction and invocation of the
induction hypothesis, must be stated explicitly. For each case of a proof by
induction, you must list the inductive hypotheses assumed (if any) and the goal
to be proved. Minor proof steps corresponding to `refl`, `simp`, or `cc` need
not be justified if you think they are obvious (to humans), but you should say
which key lemmas they depend on. You should be explicit whenever you use a
function definition or an introduction rule for an inductive predicate. -/

/-! We perform the proof by structural induction on `xs` (generalizing `as`).

Case `[]`: The goal is `accurev as [] = reverse [] ++ as`. The left-hand side
is `as` by definition of `accurev`. The right-hand side is `as` by definition
of `reverse` and `++`.

Case `x :: xs`: The goal is `accurev as (x :: xs) = reverse (x :: xs) ++ as`.
The induction hypothesis is `∀as, accurev as xs = reverse xs ++ as`.

Let us simplify the goal's left-hand side:

      accurev as (x :: xs)
    = accurev (x :: as) xs        -- by definition of `accurev`
    = reverse xs ++ (x :: as)     -- by the induction hypothesis

Now let us massage the right-hand side so that it matches the simplified
left-hand side:

      reverse (x :: xs) ++ as
    = (reverse xs ++ [x]) ++ as   -- by definition of `reverse`
    = reverse xs ++ ([x] ++ as)   -- by associativity of `++`
    = reverse xs ++ (x :: as)     -- by definition of `++`

The two sides are equal. QED -/

/-! ## Question 2: Drop and Take

The `drop` function removes the first `n` elements from the front of a list. -/

def drop {α : Type} : ℕ → list α → list α
| 0       xs        := xs
| (_ + 1) []        := []
| (m + 1) (x :: xs) := drop m xs

/-! 2.1. Define the `take` function, which returns a list consisting of the the
first `n` elements at the front of a list.

To avoid unpleasant surprises in the proofs, we recommend that you follow the
same recursion pattern as for `drop` above. -/

def take {α : Type} : ℕ → list α → list α
| 0       _         := []
| (_ + 1) []        := []
| (m + 1) (x :: xs) := x :: take m xs

#eval take 0 [3, 7, 11]   -- expected: []
#eval take 1 [3, 7, 11]   -- expected: [3]
#eval take 2 [3, 7, 11]   -- expected: [3, 7]
#eval take 3 [3, 7, 11]   -- expected: [3, 7, 11]
#eval take 4 [3, 7, 11]   -- expected: [3, 7, 11]

#eval take 2 ["a", "b", "c"]   -- expected: ["a", "b"]

/-! 2.2. Prove the following lemmas, using `induction'` or pattern matching.
Notice that they are registered as simplification rules thanks to the `@[simp]`
attribute. -/

@[simp] lemma drop_nil {α : Type} :
  ∀n : ℕ, drop n ([] : list α) = []
| 0       := by refl
| (_ + 1) := by refl

@[simp] lemma take_nil {α : Type} :
  ∀n : ℕ, take n ([] : list α) = []
| 0       := by refl
| (_ + 1) := by refl

/-! 2.3. Follow the recursion pattern of `drop` and `take` to prove the
following lemmas. In other words, for each lemma, there should be three cases,
and the third case will need to invoke the induction hypothesis.

Hint: Note that there are three variables in the `drop_drop` lemma (but only two
arguments to `drop`). For the third case, `←add_assoc` might be useful. -/

lemma drop_drop {α : Type} :
  ∀(m n : ℕ) (xs : list α), drop n (drop m xs) = drop (n + m) xs
| 0       n xs        := by refl
| (_ + 1) _ []        := by simp [drop]
| (m + 1) n (x :: xs) :=
  by simp [drop, drop_drop m n xs, ←add_assoc]

lemma take_take {α : Type} :
  ∀(m : ℕ) (xs : list α), take m (take m xs) = take m xs
| 0       _         := by refl
| (_ + 1) []        := by refl
| (m + 1) (x :: xs) := by simp [take, take_take m xs]

lemma take_drop {α : Type} :
  ∀(n : ℕ) (xs : list α), take n xs ++ drop n xs = xs
| 0       _         := by refl
| (_ + 1) []        := by refl
| (m + 1) (x :: xs) := by simp [take, drop, take_drop m]

/-! ## Question 3: A Type of λ-Terms

3.1. Define an inductive type corresponding to the untyped λ-terms, as given
by the following context-free grammar:

    term ::= 'var' string        -- variable (e.g., `x`)
           | 'lam' string term   -- λ-expression (e.g., `λx, t`)
           | 'app' term term     -- application (e.g., `t u`) -/

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

/-! 3.2. Register a textual representation of the type `term` as an instance of
the `has_repr` type class. Make sure to supply enough parentheses to guarantee
that the output is unambiguous. -/

def term.repr : term → string
| (term.var s)   := s
| (term.lam s t) := "(λ" ++ s ++ ", " ++ term.repr t ++ ")"
| (term.app t u) := "(" ++ term.repr t ++ " " ++ term.repr u ++ ")"

@[instance] def term.has_repr : has_repr term :=
{ repr := term.repr }

/-! 3.3. Test your textual representation. The following command should print
something like `(λx, ((y x) x))`. -/

#eval (term.lam "x" (term.app (term.app (term.var "y") (term.var "x"))
    (term.var "x")))

/-! ## Question 4: Vectors and Matrices

(Note: this question is long, but good preparation for the last HW problem!)

Recall the type constructor `vec : Type → nat → Type` that represents a sequence
of values of a particular length. -/

inductive vec (α : Type) : ℕ → Type
| nil {}                           : vec 0
| cons (a : α) {n : ℕ} (v : vec n) : vec (n + 1)

-- We define convenience notation for writing vectors. Note that this notation
-- cannot be used when pattern-matching.
local notation `V[]` := vec.nil
local notation `V[` l:(foldr `,` (h t, vec.cons h t) vec.nil) `]` := l

def sample_vector_1 : vec string 0 := V[]
def sample_vector_2 : vec ℕ 4 := V[1, 9, 5, 1]

namespace vec

/-! We can define functions on vectors just as we do on lists and trees. For
instance, here's an `append` function on vectors analogous to its counterpart
for lists: -/

-- NOTE: the length arguments `m` and `n` must go after the colon (i.e., they
-- can't be parameters of `vec_map`). We can then simply ignore those arguments
-- when pattern matching by using `_`, as shown.
def append {α : Type} : ∀ {m n : ℕ}, vec α m → vec α n → vec α (n + m)
| _ _ vec.nil ys := ys
| _ _ (vec.cons x xs) ys := vec.cons x (append xs ys)

/-! 4.1. Implement the function `dot_product` that computes the dot
product of two equal-dimensional (i.e., equal-length in the programmatic sense)
vectors with integer coordinates (i.e., elements). The dot product of two
vectors `V[v₁, v₂, ..., vₙ]` and `V[w₁, w₂, ..., wₙ]` is the number
`v₁ * w₁ + v₂ * w₂ + ... + vₙ * wₙ`, or, more formally, `∑_{i=1}^n (vᵢ * wᵢ)`. -/

def dot_product : ∀ {n : ℕ}, vec ℤ n → vec ℤ n → ℤ
| _ vec.nil vec.nil := 0
| _ (vec.cons m ms) (vec.cons n ns) := m * n + dot_product ms ns

/-! 4.2. We define the *melding* (a coined term) of two equal-length
vectors to be the vector formed by applying a combining function to each
corresponding pair of elements in the two vectors. For instance, the melding of
`V[1, 2, 3]` and `V[4, 5, 6]` using the combining function `(+)` would be the
vector `V[5, 7, 9]`.

Implement a function `meld` that performs this operation. (You'll need to fill
in both the type and body of the function.)
-/

def meld {α β γ : Type} : ∀ {n : ℕ}, (α → β → γ) → vec α n → vec β n → vec γ n
| _ f vec.nil vec.nil := vec.nil
| _ f (vec.cons x xs) (vec.cons y ys) := vec.cons (f x y) (meld f xs ys)

/-! The *product* of two types `α` and `β`, denoted `α × β`, is the type of
pairs `(a, b)` such that `a : α` and `b : β`. (`(a, b)` is syntactic sugar for
`prod.mk a b`, where `prod.mk : ∀ {α β : Type}, α → β → α × β`.) -/

#check (1, 2)
#check prod.mk 1951 "X"

/-! 4.3. The *zipping* of two vectors is the vector formed by pairing
corresponding elements. For instance, zipping `V[1, 2, 3] : vec ℕ 3` and
`["a", "b", "c"] : vec string 3` yields the vector
`[(1, "a"), (2, "b"), (3, "c")] : vec (ℕ × string) 3`.

Use `meld` to implement `zip`. The only modification you may make to the line
below is to replace `sorry` with a *non-recursive* function. -/

def zip {α β : Type} {n : ℕ} : vec α n → vec β n → vec (α × β) n :=
meld prod.mk

/-! 4.4. Prove the lemma below that says that appending and melding
can be done in either order -- that is, that melding the results of appending
two pairs of vectors is the same as appending the result of melding two pairs of
vectors.

You may prove your base case however you wish. However, for the inductive
case, you must use `calc` mode, and you may only use the following terms in your
proof (i.e., as "justifications" after the `:` in `calc` mode):
* `rfl`
* `eq.symm rfl` (this is used for "stepping backward" through functions)
* `by rw [ih]`

You should, of course, fill in the `ih` declaration with the appropriate
variables. -/

lemma meld_append {α β γ : Type} : ∀ {m n : ℕ}
  (f : α → β → γ) (vs : vec α n) (ws : vec β n) (xs : vec α m) (ys : vec β m),
    meld f (append vs xs) (append ws ys) =
    append (meld f vs ws) (meld f xs ys)
| _ _ f vec.nil vec.nil xs ys :=
   by simp [append, meld]
| _ _ f (vec.cons v vs) (vec.cons w ws) xs ys :=
   have ih : _, from meld_append f vs ws xs ys,
calc
  meld f (append (vec.cons v vs) xs) (append (vec.cons w ws) ys)
      = meld f (vec.cons v (append vs xs)) (vec.cons w (append ws ys)) : rfl
  ... = vec.cons (f v w) (meld f (append vs xs) (append ws ys)) : rfl
  ... = vec.cons (f v w) (append (meld f vs ws) (meld f xs ys)) : by rw [ih]
  ... = append (vec.cons (f v w) (meld f vs ws)) (meld f xs ys) : eq.symm rfl

end vec

/-! 4.5. Having defined vectors, we now turn our attention to matrices. An
`r`-by-`c` matrix is a "grid" of numerical entries (we'll use the naturals to
make things simple) with `r` rows and `c` columns. We can think of them as
vectors of vectors: either a length-`c` vector of `r`-element column vectors, or
a length-`r` vector of `c`-element row vectors.

For this problem, we'll use the *row-wise* representation: that is, we'll think
of matrices as a vector containing row vectors.

Fill in the type-level function that defines this type below. That is, `mat r c`
should evaluate to the type whose values are row-wise representations of
matrices that store natural numbers as their entries. -/

def mat (r : ℕ) (c : ℕ) : Type :=
vec (vec ℕ c) r

-- The errors below should disappear if you've defined the type correctly!
#reduce (V[] : mat 0 0)
#reduce (V[V[11, 12], V[21, 22], V[31, 32]] : mat 3 2)
#reduce (V[V[1, 3, 4], V[2, 2, 5]] : mat 2 3)

/-! 4.6. The first type of matrix operation we'll implement is extracting
subcomponents. Below, implement the functions `get_row` and `get_col` that,
respectively, extract a row or column at a specified index from a matrix.

Notice an interesting feature of these functions: they each take as an argument
a *proof* that the requested row or column index is legal for the given matrix.
This allows us to enforce the legality of arguments at the type level.

We provide two helper functions below that you will likely find helpful. (Hint:
notice the second explicit argument to `vec.nth`!) -/

def vec.map {α β : Type} : ∀ {n : ℕ}, (α → β) → vec α n → vec β n
| _ _ vec.nil         := vec.nil
| _ f (vec.cons x xs) := vec.cons (f x) (vec.map f xs)

def vec.nth {α : Type} : ∀ {n : ℕ} (i : ℕ), i < n → vec α n → α
| _ 0       _ (vec.cons x _)  := x
| _ (n + 1) h (vec.cons x xs) := vec.nth n (nat.succ_lt_succ_iff.mp h) xs
| 0 _       h vec.nil         := absurd h dec_trivial

namespace mat

def get_row {r c : ℕ} (i : ℕ) (h : i < r) (m : mat r c) : vec ℕ c :=
vec.nth i h m

def get_col {r c : ℕ} (i : ℕ) (h : i < c) (m : mat r c) : vec ℕ r :=
vec.map (vec.nth i h) m

/-! 4.7. Our second matrix operation is addition. Matrix addition proceeds
component-wise: e.g.,
┌     ┐    ┌     ┐     ┌         ┐
| a b |  + | e f |  =  | a+e b+f |
| c d |    | g h |     | c+g d+h |
└     ┘    └     ┘     └         ┘

Below, implement a function `add` for adding two matrices.

Hint: you might find your `vec.zip` function from earlier useful. You also
remain free to use any vector helper functions you might need. -/

def add : ∀ {r c}, mat r c → mat r c → mat r c
:= λ _ _, vec.meld (vec.meld (+))

end mat

end LoVe


/-
Complete these proofs.

variable  U : Type
variables A B C : U → Prop

example : (¬ ∃ x, A x) → ∀ x, ¬ A x :=
sorry

example : (∀ x, ¬ A x) → ¬ ∃ x, A x :=
sorry
-/

variable  U : Type
variables A B C : U → Prop

example : (¬ ∃ x, A x) → ∀ x, ¬ A x :=
assume hNotExistsAx: (¬ ∃ x, A x),
assume x: U,
assume hAx: A(x),
show false, from (
  have hExistsAx: ∃ x, A x,
  from exists.intro(x)(hAx),
  hNotExistsAx(hExistsAx)
)

example : (∀ x, ¬ A x) → ¬ ∃ x, A x :=
assume hNeverA: (∀ x, ¬ A x),
assume hExistAx: ∃ x, A x,
show false, from (
  exists.elim(hExistAx)(
    assume (x: U) (hAx: A(x)),
    have hnAx: ¬ A(x), from hNeverA(x),
    hnAx(hAx)
  )
)


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

import tactic -- imports all the Lean tactics

/-!

# Logic in Lean, example sheet 4 : "and" (`∧`)

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

## Tactics

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

* `cases`
* `split`

### The `cases` tactic

If `h : P ∧ Q` is a hypothesis, then `cases h with hP hQ,`
decomposes it into two hypotheses `hP : P` and `hQ : Q`.

### The `split` tactic

If `⊢ P ∧ Q` is in the goal, The `split` tactic will turn it into
two goals, `⊢ P` and `⊢ Q`. NB tactics operate on the first goal only.

-/

-- Throughout this sheet, `P`, `Q` and `R` will denote propositions.

variables (P Q R : Prop)

example : P ∧ Q → P :=
begin
  intro h,
  cases h with hP hQ,
  apply hP,
  /-intro h, apply h.1-/
end

example : P ∧ Q → Q :=
begin
  intro h,
  apply h.2,
end

example : (P → Q → R) → (P ∧ Q → R) :=
begin
  intro h,
  intro hPQ,
  apply h hPQ.1 hPQ.2,
end

example : P → Q → P ∧ Q :=
begin
  intros hP hQ,
  split,
  apply hP,
  apply hQ,
end

/-- `∧` is symmetric -/
example : P ∧ Q → Q ∧ P :=
begin
  intro h,
  split,
  apply h.2,
  apply h.1,
end

example : P → P ∧ true :=
begin
  intro h,
  split,
  exact h,
  trivial,
end

example : false → P ∧ false :=
begin
  trivial,
end

/-- `∧` is transitive -/
example : (P ∧ Q) → (Q ∧ R) → (P ∧ R) :=
begin
  intros hPQ hQR,
  split,
  apply hPQ.1,
  apply hQR.2,
end

example : ((P ∧ Q) → R) → (P → Q → R) :=
begin
  intro hPQR,
  intros hP hQ,
  apply hPQR,
  split,
  apply hP,
  apply hQ
end

/-!

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

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

## Tactics

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

* `refl`
* `rw`

### The `refl` tactic

If your goal is `P ↔ P` then `refl,` will solve it.

### The `rw` tactic

If `h : P ↔ Q` is a hypothesis, you can decompose it
using `cases h with hPQ hQP,`. However, if you keep
it around then you can do `rw h,` which changes all `P`s in the goal to `Q`s.
Variant: `rw h at h2,` will change all `P`s to `Q`s in hypothesis `h2`.

-/

variables (S : Prop)

example : P ↔ P :=
by refl

example : (P ↔ Q) → (Q ↔ P) :=
begin
  intro hPQ,
  split,
  apply hPQ.2,
  apply hPQ.1,
end

example : (P ↔ Q) ↔ (Q ↔ P) :=
begin
  split,
  {intro hPQ,
  rw hPQ,},
  {intro hQP,
  rw hQP,},
end

example : (P ↔ Q) → (Q ↔ R) → (P ↔ R) :=
begin
  intros hPQ hQR,
  split,
  {rw[hPQ, hQR], intro h, exact h,},
  {rw[hPQ, hQR], intro h, exact h,},
end

example : P ∧ Q ↔ Q ∧ P :=
begin
  split,                                    /-very much the same-/
  {intro h, split, apply h.2, apply h.1,},
  {intro h, split, apply h.2, apply h.1,}
end

example : ((P ∧ Q) ∧ R) ↔ (P ∧ (Q ∧ R)) :=
begin
  split,
  {intro hPQR,
  split,
  {apply hPQR.1.1,},
  {split,
  apply hPQR.1.2,
  apply hPQR.2}},
  {intro hPQR,
  split,
  {split,
  apply hPQR.1,
  apply hPQR.2.1},
  apply hPQR.2.2,},
end

example : P ↔ (P ∧ true) :=
begin
  split,
  {intro hP,
  split,
  apply hP,
  trivial,},
  {intro hP',
  apply hP'.1},
end

example : false ↔ (P ∧ false) :=
begin
  split,
  {trivial,},
  {intro h,
  apply h.2}
end

example : (P ↔ Q) → (R ↔ S) → (P ∧ R ↔ Q ∧ S) :=
begin
  intros hPQ hRS,
  split,
  {intro hPR,
  split,
  rw ←hPQ, apply hPR.1,
  rw ←hRS, apply hPR.2,},
  {intro hQS,
  split,
  rw hPQ, apply hQS.1,
  rw hRS, apply hQS.2,},
end

example : ¬ (P ↔ ¬ P) :=
begin
  by_contra h1,
  by_cases P,
  {have h2: ¬ P, rw h1 at h,
  change P → false at h,
  trivial,
  trivial,},
  {have h2: P, rw ←h1 at h,
  apply h,
  trivial,},
end

/-at: rw h₁ at h₂-/


/-
Copyright (c) 2019 Simon Hudon. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Simon Hudon
-/
import tactic.monotonicity.basic
import algebra.ordered_ring
import data.set.lattice
import order.bounds

variables {α : Type*}

@[mono]
lemma mul_mono_nonneg {x y z : α} [ordered_semiring α]
  (h' : 0 ≤ z)
  (h : x ≤ y)
: x * z ≤ y * z :=
by apply mul_le_mul_of_nonneg_right; assumption

lemma lt_of_mul_lt_mul_neg_right {a b c : α}  [linear_ordered_ring α]
  (h : a * c < b * c) (hc : c ≤ 0) : b < a :=
have nhc : -c ≥ 0, from neg_nonneg_of_nonpos hc,
have h2 : -(b * c) < -(a * c), from neg_lt_neg h,
have h3 : b * (-c) < a * (-c), from calc
     b * (-c) = - (b * c)    : by rewrite neg_mul_eq_mul_neg
          ... < - (a * c)    : h2
          ... = a * (-c)     : by rewrite neg_mul_eq_mul_neg,
lt_of_mul_lt_mul_right h3 nhc

@[mono]
lemma mul_mono_nonpos {x y z : α} [linear_ordered_ring α]
  (h' : z ≤ 0) (h : y ≤ x) : x * z ≤ y * z :=
begin
  classical,
  by_contradiction h'',
  revert h,
  apply not_le_of_lt,
  apply lt_of_mul_lt_mul_neg_right _ h',
  apply lt_of_not_ge h''
end

@[mono]
lemma nat.sub_mono_left_strict {x y z : ℕ}
  (h' : z ≤ x)
  (h : x < y)
: x - z < y - z :=
begin
  have : z ≤ y,
  { transitivity, assumption, apply le_of_lt h, },
  apply @nat.lt_of_add_lt_add_left z,
  rw [nat.add_sub_of_le,nat.add_sub_of_le];
    solve_by_elim
end

@[mono]
lemma nat.sub_mono_right_strict {x y z : ℕ}
  (h' : x ≤ z)
  (h : y < x)
: z - x < z - y :=
begin
  have h'' : y ≤ z,
  { transitivity, apply le_of_lt h, assumption },
  apply @nat.lt_of_add_lt_add_right _ x,
  rw [nat.sub_add_cancel h'],
  apply @lt_of_le_of_lt _ _ _ (z - y + y),
  rw [nat.sub_add_cancel h''],
  apply nat.add_lt_add_left h
end

open set

attribute [mono] inter_subset_inter union_subset_union
                 sUnion_mono bUnion_mono sInter_subset_sInter bInter_mono
                 image_subset preimage_mono prod_mono monotone_prod seq_mono
                 image2_subset order_embedding.monotone
attribute [mono] upper_bounds_mono_set lower_bounds_mono_set
                 upper_bounds_mono_mem  lower_bounds_mono_mem
                 upper_bounds_mono  lower_bounds_mono
                 bdd_above.mono bdd_below.mono

attribute [mono] add_le_add mul_le_mul neg_le_neg
         mul_lt_mul_of_pos_left mul_lt_mul_of_pos_right
         imp_imp_imp le_implies_le_of_le_of_le
         sub_le_sub abs_le_abs sup_le_sup
         inf_le_inf
attribute [mono left] add_lt_add_of_le_of_lt mul_lt_mul'
attribute [mono right] add_lt_add_of_lt_of_le mul_lt_mul


import data.set
import .src_13_applications_to_even_and_odd
import .src_22_set_equality

open set

namespace mth1001

section subsets

/-
Type `⊆` as `\sub`
-/

/-
Working with particular finite sets, Lean can prove results using `dec_trivial`.
-/
example : ({4, 6, 8} : finset ℤ) ⊆ ({8, 4, 8, 10, 6} : finset ℤ) := dec_trivial

/-
Recall our theorem that for every `a : ℤ`, if `a` is even, then `a * a` is even.
-/
theorem even_square_of_even : ∀ a : ℤ , even a → even (a*a) :=
begin
  intros a h,
  cases h with k hk,
  use (2*k*k),
  rw hk,
  ring,
end

/-
We'll show that the set of even numbers is a subset of the set of numbers whose square is even.
Lean automatically uses the definition of subset to turn the goal into one of proving
a univerally quantified statement.
-/
example : {x : ℤ | even x} ⊆ {x : ℤ | even (x*x)} := by apply even_square_of_even

-- Here's a proof of the same result that doesn't use `even_square_of_even`.

example : {x : ℤ | even x} ⊆ {x : ℤ | even (x*x)} :=
begin
  intros a h, -- Assume `a : ℤ`, assume `h : a ∈ {x : ℤ | even x}`.
  rw mem_set_of_eq at *, -- By def. of set membership, `h : even a`. Goal `⊢ even (a*a)`.
  unfold even at h, -- By definition of even, `h : ∃ (m : ℤ), a = 2 * m`.
  cases h with b hb, -- Assume `b : ℤ`, assume `hb : a = 2*b` (exists elim. on `h`)
  -- It suffices to prove `even (a*a)`, i.e. `∃ (m : ℤ), (a*a) = 2*m`
  use (2*b*b), -- By exists introduction on `2*b*b`, it suffices to prove `(a*a)=2*(2*b*b)`.
  rw hb, -- Rewriting with `hb`, the goal is to prove `(2*b)*(2*b)=2*(2*b*b)`.
  ring,  -- This follows by basic arithmetic.
end

open_locale classical

-- Exercise 128:
-- In the following example, we show the converse of the above result. You may wish to refer to
-- the file `src_13_applications_to_even_and_odd.lean` for inspiration.
-- Helpful results and tactics: `even_iff_not_odd`, `not_not`, `contrapose`.
example : {x : ℤ | even (x*x)} ⊆ {x : ℤ | even x} :=
begin
  rw subset_def, -- We must show `∀ (x : ℤ), x ∈ {x : ℤ | even (x*x)} → {x : ℤ | even x}`
  intro x, -- Assume `x : ℤ`. We must show `x ∈ {x : ℤ | even (x*x)} → {x : ℤ | even x}`
  sorry
end

-- Exercise 129:
/-
We'll show 'anti-symmetry' of the subset relation. That is, for sets `S` and `T`, we show
`S = T ↔ (S ⊆ T) ∧ (T ⊆ S)`.
-/
theorem subset_antisymm (A : Type*) (S T : set A) : S = T ↔ (S ⊆ T) ∧ (T ⊆ S) :=
begin
  rw ext_iff, -- Rewrite using the definition of set.
  repeat { rw subset_def, }, -- Rewrite (repeatedly) using the definition of subset.
  split,
  { intro h ,
    sorry, },
  { intros h x,
    sorry, },
end

-- Exercise 130:
-- Transitivity of the subset relation. We prove this from the definition.
theorem subset_trans (A : Type*) (S T U : set A) : (S ⊆ T) ∧ (T ⊆ U) → (S ⊆ U) :=
begin
  repeat { rw subset_def, }, -- Rewrite (repeatedly) using the definition of subset.
  intros h x h₂, -- Assume `h : (S ⊆ T) ∧ (T ⊆ U)`, `x : A`, and `h₂ : x ∈ S`.
  cases h with h₃ h₄, -- Left & right ∧ elim. `h₃ : ∀ x, x ∈ S → x ∈ T`, `h₄ : ∀ x, x ∈ T → x ∈ U`.
  sorry
end

/-
We'll prove that for all sets `S` and `T` on a type `A`, `¬(S ⊆ T) ↔ ∃ x, x ∈ S ∧ x ∉ T`.
-/
theorem not_subset {A : Type*} (S T : set A): ¬(S ⊆ T) ↔ ∃ x, x ∈ S ∧ x ∉ T :=
begin
  rw subset_def, -- Rewrite using the definition of subset.
  push_neg, -- Negate the universal quantifier and the implication,
  refl, -- The result follows by reflexivity.
end

/-
In the following, we show `{4, 2, 6}` is a subset of the set of even integers.
In Lean, we write the above set as `({4, 2, 6} : set ℤ)`.
Note that the `rcases` tactic decomposes set membership recursively.
-/
example : ({4, 2, -6} : set ℤ) ⊆ {x : ℤ | even x} :=
begin
  intros x h, -- Assume `x : ℤ` and `h : x ∈ {4, 2, 6}`.
  rcases h with hm₆ | h₂ | h₄ | k,
  { use (-3), rw hm₆, norm_num, }, -- The case `x = -6`.
  { use 1, rw h₂, norm_num, }, -- The case `x = 2`.
  { use 2, rw h₄, norm_num, }, -- The case `x = 4`.
  { exfalso, apply k }, -- The case `x ∈ ∅`.
end

open classical
local attribute [instance] prop_decidable

/-
Let `U = {x : ℤ | x ≤ 8 ∧ x > 4}` and let `S` be the set of even integers. We'll show `¬(U ⊆ S)`.

-/
example : ¬( {x : ℤ | x ≤ 8 ∧ x > 4} ⊆ {x : ℤ | even x} ) :=
begin
  rw not_subset, -- By the `not_subset` result, it suffices to show
  -- `∃ x : ℤ, x ∈ U ∧ x ∉ S`.
  use 5, -- By `∃` intro on `5 : ℤ`, it suffices to prove `5 ∈ U ∧ 5 ∉ S`.
  split, -- By and introduction, it suffices to prove 1. `5 ∈ U` and 2. `5 ∉ S`.
  { split; -- By and introduction, it suffices to prove `5 ≤ 8` and `5 > 4` }
    -- The semi-colon (rather than comma) here means, 'apply the following tactic to *both* goals'.
      norm_num, -- We close both goals `5 ≤ 8` and `5 > 4` with norm_num.
  },
  { rw nmem_set_of_eq, -- Rewriting by definition of `∉`, the goal is to show `¬even 5`.
    rw even_iff_not_odd, -- Equally, to show `¬¬odd 5`.
    rw not_not, -- By double negation, we must show `odd 5`, i.e. `∃ m : Z, 5 = 2 * m + 1`.
    use 2, -- By `∃` intro with `2`, it suffices to show `5 = 2 * 2 + 1`.
    norm_num, -- We close the goal with `norm_num`.
  }
end

/-
We show the subset relation is not symmetric via a counterexample.
We aim to show `¬ (∀ S T, S ⊆ T → T ⊆ S)`.
-/
example : ¬(∀ A : Type*, ∀ S T : set A, S ⊆ T → T ⊆ S) :=
begin
  push_neg, -- Push the negation inside the goal to tranform it to
  -- `∃ (A : Type*), ∃ S T : set A, S ⊆ T ∧ ¬(T ⊆ S)`.
  use [ℤ, {1, 2}, {1, 2, 3}], -- Take `A = ℤ`, `S = {1, 2}`, and `T = {1, 2, 3}`.
  split,
  { exact subset_insert 3 {1, 2} }, -- `{1, 2, 3}` is the set construct by inserting `3`
  -- into the set `{1, 2}`, so `{1, 2}` is a subset of `{1, 2, 3}`.
  { rw subset_def, -- Rewrite the goal using the definition of subset.
    intro h, -- Assume `h : ∀ x, x ∈ {1, 2, 3} → x ∈ {1, 2}`.
    specialize h 3, -- By `∀` elim. on `h` with `3`, we have `h : 3 ∈ {1, 2, 3} → 3 ∈ {1, 2}`.
    contrapose! h, -- Negating, the goal is equivalent to `3 ∈ {1, 2, 3} ∧ 3 ∉ {1, 2}`.
    split; -- By and introduction, it suffices to prove 1. `3 ∈ {1, 2, 3}` and 2. `3 ∉ {1, 2}`.
      norm_num, }, -- Close both goals by `norm_num`.
end

end subsets

end mth1001


import Proofs.Naturals

inductive le : ℕ → ℕ → Type where
  | zLEN : {n : ℕ} → le 0 n
  | sLEN : le m n  → le (ℕ.s m) (ℕ.s n)

macro_rules | `($x ≤ $y)  => `(le $x $y)

example : (2 ≤ 4) := le.sLEN (le.sLEN (le.zLEN))

theorem invLE : ℕ.s m ≤ ℕ.s n → m ≤ n | le.sLEN n => n
theorem invZE : ∀{m : ℕ}, m ≤ 0 → m = 0 | _, le.zLEN => rfl

-- Relation types
-- Reflexive: a ≤ n holds
-- Transitive: if m ≤ n and n ≤ j then m ≤ j
-- Antisimetric: if m ≤ n and n ≤ m then m = n
-- Total: forall a b either a ≤ b or b ≤ a holds

-- Pre order: reflexive + transative
-- Partial order: also antisimetric
-- Total oroder: also total

inductive Total : ℕ → ℕ → Type where
  | forward : m ≤ n → Total m n
  | flipped : n ≤ m → Total m n

def totalSuc : Total m n → Total (ℕ.s m) (ℕ.s n)
  | Total.forward m => Total.forward (le.sLEN m)
  | Total.flipped m => Total.flipped (le.sLEN m)

theorem Le.refl : ∀ {m : ℕ}, m ≤ m
  | 0 => le.zLEN
  | ℕ.s m => le.sLEN (@refl m)

theorem Le.trans : m ≤ n → n ≤ p → m ≤ p
  | le.zLEN, n => le.zLEN
  | le.sLEN m, le.sLEN n => le.sLEN (trans m n)

theorem Le.antiSym : m ≤ n → n ≤ m → m = n
  | le.zLEN, le.zLEN => rfl
  | le.sLEN m, le.sLEN n => congrArg ℕ.s (antiSym m n)

theorem Le.total : ∀ {m n : ℕ}, Total m n
  | 0, n => Total.forward le.zLEN
  | n, 0 => Total.flipped le.zLEN
  | ℕ.s m, ℕ.s n => totalSuc (@total m n)

theorem Le.Add.monoLeft: ∀ {m n p : ℕ}, m ≤ n → p + m ≤ p + n
  | m, n, 0, hip   => hip
  | m, n, ℕ.s p, hip => le.sLEN (@monoLeft m n p hip)

theorem Le.Add.monoRight {m n p : ℕ} : m ≤ n → m + p ≤ n + p := by
  simp [Nt.Add.comm m p, Nt.Add.comm n p]
  apply Le.Add.monoLeft

theorem Le.Add.mono : ∀ {m n p q : ℕ}, (m ≤ n) → (p ≤ q) → (p + m ≤ q + n)
  | m, n, p, q, h1, h2 => Le.trans (@Le.Add.monoLeft m n p h1) (@Le.Add.monoRight p q n h2)

theorem Le.Mul.monoLeft : ∀ {m n p : ℕ}, (m ≤ n) → (p * m ≤ p * n)
  | m, n, 0, h1     => by simp [Nt.Mul.leftNeutral]; exact le.zLEN
  | m, n, ℕ.s p, h1 => by
    simp [Nt.Mul.sucLeft];
    apply (Le.Add.mono (@monoLeft m n p h1) h1)

theorem Le.Mul.mono : ∀ {m n p q : ℕ}, (m ≤ n) -> (p ≤ q) → (p * m ≤ q * n)
  | m, n, 0    , q, h1, h2 => by simp [Nt.Mul.leftNeutral]; exact le.zLEN
  | m, n, ℕ.s p, ℕ.s q, h1, h2 => by
    simp [Nt.Mul.sucLeft];
    exact (@Le.Add.mono (p*m) (q*n) m n (@mono m n p q h1 (invLE h2)) h1)

-- Inequality

inductive ls : ℕ → ℕ → Type where
  | zLEN : {n : ℕ} → ls 0 (ℕ.s n)
  | sLEN : ls m n  → ls (ℕ.s m) (ℕ.s n)

macro_rules | `($x < $y)  => `(ls $x $y)

inductive Tricotomy : ℕ → ℕ → Type where
  | lessT  : {n m : ℕ} → ls n m → Tricotomy n m
  | greatT : {n m : ℕ} → ls m n → Tricotomy n m
  | eqT    : {n : ℕ}           → Tricotomy n n

def succTricotomy : Tricotomy m n → Tricotomy (ℕ.s m) (ℕ.s n)
  | Tricotomy.eqT      => Tricotomy.eqT
  | Tricotomy.lessT p  => Tricotomy.lessT (ls.sLEN p)
  | Tricotomy.greatT p => Tricotomy.greatT (ls.sLEN p)

theorem tricotomy : ∀ {m n}, Tricotomy m n
  | 0    , 0     => Tricotomy.eqT
  | ℕ.s m, 0     => Tricotomy.greatT ls.zLEN
  | 0    , ℕ.s n => Tricotomy.lessT ls.zLEN
  | ℕ.s m, ℕ.s n => succTricotomy (@tricotomy m n)

theorem Ls.trans : m < n → n < p → m < p
  | ls.zLEN  , ls.sLEN m => ls.zLEN
  | ls.sLEN m, ls.sLEN n => ls.sLEN (trans m n)

theorem Ls.Add.monoLeft : ∀ {m n p: ℕ}, m < n → (p + m < p + n)
  | m, n, 0, h1 => h1
  | m, n, ℕ.s p, h1 => ls.sLEN (@monoLeft m n p h1)

theorem Ls.Add.monoRight : ∀ {m n p: ℕ}, m < n → (m + p < n + p)
  | m, n, 0, h1 => by simp [Nt.Add.rightId]; exact h1
  | m, n, ℕ.s p, h1 => by rw [Nt.Add.comm m (ℕ.s p), Nt.Add.comm n (ℕ.s p)]; exact (@Ls.Add.monoLeft m n (ℕ.s p) h1)

theorem Ls.Add.mono {m n p q : ℕ} (h1: m < n) (h2: p < q) : (p + m < q + n) :=
  Ls.trans (@Ls.Add.monoLeft m n p h1) (@Ls.Add.monoRight p q n h2)

theorem Ls.Eq.iff : ∀ {m n : ℕ}, ℕ.s m ≤ n → m < n
  | ℕ.z, ℕ.s n, le.sLEN _ => ls.zLEN
  | ℕ.s m, ℕ.s n, le.sLEN o => ls.sLEN (iff o)

theorem Ls.Eq.iffInv : ∀ {m n : ℕ}, m < n → ℕ.s m ≤ n
  | 0,     ℕ.s n, ls.zLEN   => le.sLEN le.zLEN
  | ℕ.s m, ℕ.s n, ls.sLEN o => le.sLEN (iffInv o)

theorem Ls.toLs : ∀ {m n : ℕ}, m < n → m ≤ n
  | 0,     ℕ.s n, ls.zLEN   => le.zLEN
  | ℕ.s m, ℕ.s n, ls.sLEN o => le.sLEN (toLs o)

theorem leTransRevisited (h1: m < n) (h2: n < p): m < p := Ls.Eq.iff (Le.trans (Ls.Eq.iffInv h1) (Ls.toLs h2))


/-
Copyright (c) 2020 Zhouhang Zhou. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Zhouhang Zhou, Yury Kudryashov

! This file was ported from Lean 3 source module analysis.normed_space.indicator_function
! leanprover-community/mathlib commit 17ef379e997badd73e5eabb4d38f11919ab3c4b3
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Analysis.Normed.Group.Basic
import Mathlib.Algebra.IndicatorFunction

/-!
# Indicator function and norm

This file contains a few simple lemmas about `Set.indicator` and `norm`.

## Tags
indicator, norm
-/

variable {α E : Type _} [SeminormedAddCommGroup E] {s t : Set α} (f : α → E) (a : α)

open Set

theorem norm_indicator_eq_indicator_norm : ‖indicator s f a‖ = indicator s (fun a => ‖f a‖) a :=
  flip congr_fun a (indicator_comp_of_zero norm_zero).symm
#align norm_indicator_eq_indicator_norm norm_indicator_eq_indicator_norm

theorem nnnorm_indicator_eq_indicator_nnnorm :
    ‖indicator s f a‖₊ = indicator s (fun a => ‖f a‖₊) a :=
  flip congr_fun a (indicator_comp_of_zero nnnorm_zero).symm
#align nnnorm_indicator_eq_indicator_nnnorm nnnorm_indicator_eq_indicator_nnnorm

theorem norm_indicator_le_of_subset (h : s ⊆ t) (f : α → E) (a : α) :
    ‖indicator s f a‖ ≤ ‖indicator t f a‖ := by
  simp only [norm_indicator_eq_indicator_norm]
  exact indicator_le_indicator_of_subset ‹_› (fun _ => norm_nonneg _) _
#align norm_indicator_le_of_subset norm_indicator_le_of_subset

theorem indicator_norm_le_norm_self : indicator s (fun a => ‖f a‖) a ≤ ‖f a‖ :=
  indicator_le_self' (fun _ _ => norm_nonneg _) a
#align indicator_norm_le_norm_self indicator_norm_le_norm_self

theorem norm_indicator_le_norm_self : ‖indicator s f a‖ ≤ ‖f a‖ := by
  rw [norm_indicator_eq_indicator_norm]
  apply indicator_norm_le_norm_self
#align norm_indicator_le_norm_self norm_indicator_le_norm_self


import algebra.pi_instances
import data.real.basic

local attribute [instance] classical.prop_decidable

class metric_space (α : Type*) :=
(dist : α → α → ℝ)
(dist_self : ∀ x, dist x x = 0)
(eq_of_dist_eq_zero : ∀ {x y : α}, dist x y = 0 → x = y)
(dist_comm : ∀ x y : α, dist x y = dist y x)
(dist_triangle : ∀ x y z : α, dist x z ≤ dist x y + dist y z)

open metric_space

section

variables {α : Type*} [metric_space α]
variables {u v : ℕ → α} {l l₁ l₂ : α}

def limit (u : ℕ → α) (l : α) : Prop :=
  ∀ ε > 0, ∃ N : ℕ, ∀ n ≥ N, dist (u n) l ≤ ε

theorem dist_nonneg {x y : α}: 0 ≤ dist x y :=
begin
  have : dist x x ≤ dist x y + dist y x, from dist_triangle _ _ _,
  rw dist_comm y x at this,
  rw dist_self at this,
  linarith
end

theorem dist_pos {x y : α} : 0 < dist x y ↔ x ≠ y :=
begin
  split; contrapose!,
  { rintro rfl,
    have : dist x x = 0, from dist_self _,
    linarith },
  { intro h,
    apply eq_of_dist_eq_zero,
    have : 0 ≤ dist x y, from dist_nonneg,
    linarith }
end

lemma limit_unique (h₁ : limit u l₁) (h₂ : limit u l₂) : l₁ = l₂ :=
begin
  by_contra h,
  let ε := dist l₁ l₂,
  have : ε > 0, from dist_pos.mpr h,
  cases h₁ (ε/3) (by linarith) with N₁ hN₁,
  cases h₂ (ε/3) (by linarith) with N₂ hN₂,
  let n := max N₁ N₂,
  specialize hN₁ n (le_max_left _ _),
  specialize hN₂ n (le_max_right _ _),
  have : dist (u n) l₁ + dist (u n) l₂ < ε, by linarith,
  have : ε ≤ dist l₁ (u n) + dist (u n) l₂, from dist_triangle _ _ _,
  linarith [dist_comm l₁ (u n)]
end

end

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

noncomputable instance : metric_space ℝ :=
{ dist := λ x y, |x - y|,
  -- This proof term is hideous and beautiful simultaneously.
  dist_self := λ x, eq.symm (sub_self x) ▸ abs_zero,
  eq_of_dist_eq_zero := λ x y, eq_of_abs_sub_eq_zero,
  dist_comm := abs_sub,
  dist_triangle := abs_sub_le }

variables {u v : ℕ → ℝ} {c l l₁ l₂ : ℝ}

@[simp] def const (l : ℝ) : ℕ → ℝ := function.const _ l

lemma neg_const : -const l = const (-l) := by funext; simp

theorem const_limit : limit (const c) c :=
begin
  intros ε ε_pos,
  use 0,
  intros n hn,
  unfold dist,
  simp,
  linarith
end

theorem limit_add_limit (h₁ : limit u l₁) (h₂ : limit v l₂) :
  limit (u + v) (l₁ + l₂) :=
begin
  intros ε ε_pos,
  cases h₁ (ε/2) (by linarith) with N₁ hN₁,
  cases h₂ (ε/2) (by linarith) with N₂ hN₂,
  use max N₁ N₂,
  intros n hn,
  specialize hN₁ n (le_of_max_le_left hn),
  specialize hN₂ n (le_of_max_le_right hn),
  unfold dist at *,
  calc
  |(u + v) n - (l₁ + l₂)| = |(u n + v n) - (l₁ + l₂)| : rfl
  ...                     = |(u n - l₁) + (v n - l₂)| : by congr; ring
  ...                     ≤ |u n - l₁| + |v n - l₂|   : by apply abs_add
  ...                     ≤ ε                         : by linarith
end

lemma limit_add_const (h : limit u l) : limit (u + const c) (l + c) :=
limit_add_limit h const_limit

lemma limit_sub_self (h : limit u l) : limit (u - const l) 0 :=
begin
  rw [←sub_self l, sub_eq_add_neg, sub_eq_add_neg, neg_const],
  exact limit_add_const h
end

example (α : Type*) [linear_ordered_field α] (a b c : α) (h : a ≤ b / c) (H : c > 0) : a*c ≤ b := by refine (le_div_iff H).mp h

lemma limit_mul_const (h : limit u l) : limit (u * const c) (l * c) :=
begin
  intros ε ε_pos,
  by_cases hc : c = 0,
  { use 0,
    intros n hn,
    rw hc,
    simp,
    rw dist_self,
    exact le_of_lt ε_pos },
  { change c ≠ 0 at hc,
    have : ε / |c| > 0, from div_pos_of_pos_of_pos ε_pos (by rwa abs_pos_iff),
    cases h _ this with N hN,
    use N,
    intros n hn,
    specialize hN n hn,
    unfold dist at *,
    suffices : |(u n - l) * c| ≤ ε,
    { calc
      |(u * const c) n - l * c| = |(u n * c) - l * c| : rfl
      ...                       = |(u n - l) * c|     : by congr; ring
      ...                       ≤ ε                   : this },
    rwa [abs_mul, ←le_div_iff],
    rwa abs_pos_iff }
end

theorem limit_zero_mul_limit_zero (h₁ : limit u 0) (h₂ : limit v 0) :
  limit (u * v) 0 :=
begin
  intros ε ε_pos,
  have : ε.sqrt > 0, from real.sqrt_pos.mpr ε_pos,
  cases h₁ (ε.sqrt) this with N₁ hN₁,
  cases h₂ (ε.sqrt) this with N₂ hN₂,
  use max N₁ N₂,
  intros n hn,
  specialize hN₁ n (le_of_max_le_left hn),
  specialize hN₂ n (le_of_max_le_right hn),
  unfold dist at *,
  calc
  |(u * v) n - 0| = |(u n * v n) - 0|       : rfl
  ...             = |(u n - 0) * (v n - 0)| : by ring
  ...             = |u n - 0| * |v n - 0|   : by apply abs_mul
  ...             ≤ ε.sqrt * ε.sqrt         : mul_le_mul hN₁ hN₂ (abs_nonneg _) (le_of_lt this)
  ...             = ε.sqrt ^ 2              : by ring
  ...             = ε                       : real.sqr_sqrt (le_of_lt ε_pos)
end

lemma limit_neg_iff : limit (-u) (-l) ↔ limit u l :=
begin
  split;
  { intros h ε ε_pos,
    cases h ε ε_pos with N hN,
    use N,
    intros n hn,
    specialize hN n hn,
    unfold dist at *,
    simp at *,
    rwa abs_sub at hN }
end

notation u `+` v := limit_add_limit u v

theorem limit_mul_limit (h₁ : limit u l₁) (h₂ : limit v l₂) :
  limit (u * v) (l₁ * l₂) :=
begin
  let c₁ := const l₁,
  let c₂ := const l₂,
  have w₁ : limit ((u - c₁) * (v - c₂)) 0,
  from limit_zero_mul_limit_zero (limit_sub_self h₁) (limit_sub_self h₂),
  rw (show (u - c₁) * (v - c₂) = u * v - u * c₂ - v * c₁ + c₁ * c₂, by ring) at w₁,
  have w₂ : limit (-c₁ * c₂) (-l₁ * l₂),
  { simp,
    rw limit_neg_iff,
    exact limit_mul_const const_limit },
  have w₃ : limit (u * c₂) (l₁ * l₂), from limit_mul_const h₁,
  have w₄ : limit (v * c₁) (l₁ * l₂), by { rw mul_comm l₁, exact limit_mul_const h₂ },
  have w := w₁ + w₂ + w₃ + w₄,
  -- The most satisfying two lines.
  simp at w,
  ring at w,
  rwa [mul_comm u, mul_comm l₁]
end


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

import topology.basic

/-!

# API para el `interior` de un conjunto.

Fijamos un espacio topológico `X` y un subconjunto `S : set X`.

En Lean, `interior S` denota el interior de `S`, es decir, el mayor
subconjunto abierto de `S`. Sin embargo, para practicar parte
de lo introducido en el fichero `conjuntos_2.lean`, en este
fichero definiremos de nuevo esta noción (que denotaremos
`interior' S`) y crearemos su API básica.

-/

--Fijamos un espacio topológico `X` y un subconjunto `S : set X`.
variables {X : Type} [topological_space X] (S : set X)

/-

## API para espacios topológicos

`is_open S : Prop` es el predicado que dice que `S : set X` es abierto.

Los siguientes lemas de `mathlib` serán útiles en estos ejercicios:
* is_open_univ
* is_open_Union, is_open_bUnion
* is_open.inter

Los nombres son descriptivos, pero si queréis aseguraros de que un
lema dice lo que creéis, podéis utilizar #check o buscarlo en la
documentación de mathlib (https://leanprover-community.github.io/mathlib_docs/).
-/

open set

/-- El interior de `S` es la unión de todos los subconjuntos abiertos de `X`
  contenidos en `S`. -/
def interior' (S : set X) : set X := ⋃ (U ∈ {V : set X | is_open V ∧ V ⊆ S}), U

-- Lema de reescritura
lemma mem_interior' (x : X) :
  x ∈ interior' S ↔ ∃ (U : set X) (hU : is_open U) (hUS : U ⊆ S), x ∈ U :=
begin
  unfold interior',
  simp only [set.mem_set_of_eq, set.mem_Union, exists_prop],
  tauto,
end

/-- Como hemos definido `interior'` como una `bUnion`, podemos empezar
  la demostración con `apply is_open_bUnion`. -/
lemma interior'_open : is_open (interior' S) :=
begin
  apply is_open_bUnion,
  intros U hU,
  exact hU.left,
end

lemma interior'_subset : interior' S ⊆ S :=
begin
  intros x hx,
  rw mem_interior' at hx,
  obtain ⟨U, hU, hUS, hUX⟩ := hx,
  exact hUS hUX,
end

variable {S}

lemma subset_interior' {U : set X} (hU : is_open U) (hUS : U ⊆ S) : U ⊆ interior' S :=
begin
  intros x hx,
  rw mem_interior',
  exact ⟨U, hU, hUS, hx⟩,
end

lemma interior'_mono {S T : set X} (hST : S ⊆ T) : interior' S ⊆ interior' T :=
begin
  rintros x hx,
  rw mem_interior' at hx ⊢,
  obtain ⟨U, hU, hUS, hUx⟩ := hx,
  exact ⟨U, hU, subset_trans hUS hST, hUx⟩,
end

/- En lugar de empezar con `ext x`, podéis usar `apply set.subset.antisymm`,
  que dice que si `S ⊆ T` y `T ⊆ S` entonces `S = T`. -/
lemma interior'_interior' : interior' (interior' S) = interior' S :=
begin
  apply set.subset.antisymm,
  { exact interior'_subset _ },
  { exact subset_interior' (interior'_open _) rfl.subset },
end

--Ejemplos de interiores:

lemma interior'_empty : interior' (∅ : set X) = ∅ :=
begin
  exact set.eq_empty_of_subset_empty (interior'_subset _)
end

lemma interior'_univ : interior' (set.univ : set X) = set.univ :=
begin
  exact set.eq_univ_of_univ_subset (subset_interior' is_open_univ rfl.subset)
end

lemma interior'_inter (S T : set X) : interior' (S ∩ T) = interior' S ∩ interior' T :=
begin
  apply set.subset.antisymm,
  { rw [set.subset_inter_iff],
    exact ⟨interior'_mono (inter_subset_left _ _), interior'_mono (inter_subset_right _ _)⟩, },
  { rintros x ⟨hxS, hxT⟩,
    rw mem_interior' at hxS hxT ⊢,
    obtain ⟨U, hU, hUS, hUx⟩ := hxS,
    obtain ⟨V, hV, hVT, hVx⟩ := hxT,
    refine ⟨U ∩ V, hU.inter hV, inter_subset_inter hUS hVT, mem_inter hUx hVx⟩ },
end


import topology.metric_space.basic

noncomputable theory

open topological_space filter

universe u            -- the universe we work in
variable {X : Type u} -- the underlying set for our space

/-- input a pseudo_metric_space and a set. Returns a collection of open sets centered on that set. -/
def base_of_metric_and_set [M : pseudo_metric_space X] (D : set X) :
set (set X) := { Y | ∃(n:ℕ) (d:X), d ∈ D ∧ metric.ball d (1/(↑n+1)) = Y }

/-This is a lemma to make it easier to access the inner sets.-/
lemma sets_in_base [M : pseudo_metric_space X] (D : set X) : ∀(n:ℕ) (d ∈ D), metric.ball d (1/(↑n+1)) ∈ base_of_metric_and_set D :=
begin
  intros n d hd,
  refine set.mem_def.mpr _,
  split,
  use d,
  split,
  exact hd,
  simp,
end

/- This is a lemma to more easily say dense set intsersect all balls.-/
lemma dense_set_int_balls [M : pseudo_metric_space X] (D : set X) (hD : dense D) (x:X) (r:ℝ) ( hr_pos : r>0): set.nonempty ((metric.ball x r) ∩ D) :=
begin
  have ball_open := @metric.is_open_ball X M x r,
  have ball_nonempty := @metric.nonempty_ball X M x r,
  rcases ball_nonempty with ⟨-,ball_nonempty⟩,
  specialize ball_nonempty hr_pos,
  have D_int_ball := dense.inter_open_nonempty hD (metric.ball x r) ball_open ball_nonempty,
  exact D_int_ball,
end

lemma sets_in_nbhd [M : pseudo_metric_space X] (D : set X) (hD : dense D) (U : set X) ( hU : is_open U) (x : X) (hx : x ∈ U):
∃(n:ℕ) (d ∈ D), metric.ball d (1/(↑n+1)) ⊆ U ∧ x ∈ metric.ball d (1/(n+1 : ℝ)) :=
begin
  have hdist := (@metric.is_open_iff X M U),
  rcases hdist with ⟨hdist , -⟩,
  replace hdist := hdist hU x hx,
  rcases hdist with ⟨δ, ⟨δ_pos, hball⟩ ⟩,
  have δ2_pos : δ/2 > 0 := by linarith,
  have hyp := exists_nat_one_div_lt (δ2_pos),
  cases hyp with n n_less_δ2,
  have int_nonempty := dense_set_int_balls D hD x (1 / (↑n + 1)) (nat.one_div_pos_of_nat),
  have d_in_int := @set.nonempty.some_mem X (metric.ball x (1 / (↑n + 1)) ∩ D) int_nonempty,
  cases d_in_int with d_in_ball_xn d_in_D,
  use [n, int_nonempty.some, d_in_D],
  set d := int_nonempty.some,
  split,
  have ball_dn_subset_ball_xδ : metric.ball d (1/(↑n + 1)) ⊆ metric.ball x δ,
  { have h_dist := @metric.mem_ball X M x d (1/(↑n + 1)),
    rcases h_dist with ⟨h_dist, -⟩,
    specialize h_dist d_in_ball_xn,
    refine metric.ball_subset _,
    linarith  },
  exact set.subset.trans ball_dn_subset_ball_xδ hball,
  have final := @metric.mem_ball_comm X M x d (1 / (↑n+1)),
  rw final,
  exact d_in_ball_xn,
end

/--The construction results in a topological basis when the set is dense. -/
lemma metric_and_dense_is_basis [M : pseudo_metric_space X] (D : set X) (hD : dense D): topological_space.is_topological_basis (base_of_metric_and_set D) :=
begin
  set BASIS := base_of_metric_and_set D,
  have hopen : ∀B ∈ BASIS, is_open B,
  { rintro B ⟨n, ⟨d, ⟨hd, b2⟩⟩⟩,
    rw ← b2,
    apply metric.is_open_ball },
  have hbase : ∀ x:X, ∀ U:set X, (x ∈ U)  → is_open U → (∃ (B : set X) (H : B ∈ BASIS), x ∈ B ∧ B ⊆ U),
  { intros x U U_is_nbhd U_is_open,
    have openiff := @metric.is_open_iff X M U,
    rcases openiff with ⟨case1,-⟩,
    replace case1 := case1 U_is_open,
    specialize case1 x U_is_nbhd,
    rcases case1 with ⟨ε,  ⟨ε_geq, Hball_subset⟩⟩,
    have HBnbhd := sets_in_nbhd D hD U U_is_open x U_is_nbhd,
    rcases HBnbhd with ⟨Jn, ⟨Jd, ⟨JD, ⟨Jhu, Jhx⟩⟩⟩⟩,
    use metric.ball Jd (1 / (↑Jn+1)),
    split,
    apply sets_in_base D Jn Jd JD,
    split,
    apply Jhx,
    apply Jhu },
  exact is_topological_basis_of_open_of_nhds (hopen) (hbase),
end


-- import data.real.basic  -- Real numbers and their basic properties
import tactic
import tactic.suggest  -- Lean's built-in "help" functionality

/-
Lean exercise:

Define the relation "x is a subsequence of y", and prove ...

"subsequence" is a preorder, since it is:
* reflexive
* transitive

"subsequence" is not a partial order, since it is:
* not antisymmetric

-/

/- DEFINITIONS -/

-- An infinite sequence is equivalent to a map from ℕ to sequence elements.
def seq (t : Type) :=
  ℕ → t

-- To define "subsequence", we need to keep track of a sequence of increasing indices.
-- An increasing indices sequence is an increasing sequence of natural numbers.
def is_inc_indices (s : seq ℕ) :=
  ∀ i j : ℕ , (i < j) → (s i < s j)
def inc_indices :=
  {s : seq ℕ // is_inc_indices s}

-- A subsequence is created by taking some elements from a sequence, keeping them in order.
-- Given a sequence and some increasing indices, we can construct a subsequence.
def subseq_of {t : Type} (p : seq t) (s : inc_indices) : seq t :=
  λ i, p (s.1 i)

-- We define subsequence in terms of inc_indices and subseq_of.
def subseq {t : Type} (x y : seq t) :=
  ∃ s : inc_indices, x = subseq_of y s

/- REFLEXIVITY -/

-- subseq is a reflexive relation.
lemma subseq_refl {t : Type} (a : seq t) : subseq a a :=
begin
  unfold subseq,
  use id,  -- use the identity function as the indexing sequence
  {
    -- prove ⊢ is_inc_indices id
    -- unfold is_inc_indices,
    intros i j h,
    -- unfold id,
    exact h,
  },
  {
    -- prove ⊢ a = subseq_of a ⟨id ℕ, _⟩
    unfold subseq_of,
    simp,
  },
end

/- TRANSITIVITY -/
-- First, define/prove that increasing indices can be composed, and will still be increasing.
def inc_comp (s1 s2 : inc_indices) : inc_indices :=
⟨
  s1.1 ∘ s2.1,
  begin
    unfold is_inc_indices,
    intros i j h,
    unfold function.comp,
    apply s1.2,
    apply s2.2,
    exact h,
  end
⟩

-- subseq is a transitive relation.
lemma Subseq_trans {t : Type} (a b c : seq t) :
  subseq a b → subseq b c → subseq a c :=
begin
  unfold subseq,
  -- intros h1 h2,
  -- cases h1 with s1 h1,
  -- cases h2 with s2 h2,
  rintros ⟨s1, h1⟩ ⟨s2, h2⟩,
  cases h1 with h1l h1r,
  cases h2 with h2l h2r,
  use inc_comp s2 s1,
  exact h1,
end

/- NON-ANTISYMMETRIC -/
-- 'subseq', as defined above, is not a partial order, since it is not antisymmetric.
-- For antisymmetry, we would have to have:
--   ∀ a b,  (a ≤ b) ∧ (b ≤ a)  →  a = b
-- We will prove that subseq is not antisymmetric, by providing a counterexample.

-- The sequence (0, 1, 0, 1, ...).
def seq0101 := λ n : ℕ, n % 2
-- The sequence (1, 0, 1, 0, ...).
def seq1010 := λ n : ℕ, (n + 1) % 2

-- The indexing sequence (1, 2, 3, 4, ...).
def seq1234 : inc_indices :=
⟨
  λ n : ℕ, n + 1,
  begin
    unfold is_inc_indices,
    intros i j h,
    simp,
    exact h,
  end
⟩

-- subseq is not an antisymmetric relation.
lemma subseq_not_antisymm :
  ¬ ∀ t : Type, ∀ a b : seq t, subseq a b → subseq b a → a = b :=
begin
  push_neg,
  use ℕ,
  use seq0101,
  use seq1010,
  split,
  {
    -- prove ⊢ subseq seq0101 seq1010
    use seq1234,
    unfold subseq_of,
    rw seq1234,
    rw seq0101,
    rw seq1010,
    simp,
    apply funext,
    intro n,
    rw add_assoc,
    simp,
  },
  split,
  {
    -- prove ⊢ subseq seq1010 seq0101
    use seq1234,
    unfold subseq_of,
    rw seq1234,
    rw seq0101,
    rw seq1010,
  },
  {
    -- prove ⊢ seq0101 ≠ seq1010
    intro h,
    have hh : (seq0101 0 = seq0101 0),
    {refl},
    nth_rewrite 0 h at hh,
    rw seq0101 at hh,
    rw seq1010 at hh,
    simp at hh,
    contradiction,
  },
end


/-
You need to understand the following
elements of automated predicate logic
for Exam 2

Suppose P and Q are arbitrary propositions
(i.e., P Q : Prop) and T and V are arbitrary
types (i.e., T V : Type).

Know the following forms, how to prove them,
how to use proofs of them, and how to do these
things in Lean.

To know how to prove them and how to use proofs
of them, you need an intuitive understanding of
the introduction and elimination rules for each
form, and how to use them in Lean.

* true : Prop
* false : Prop
* =                    -- equality
* P ∧ Q : Prop
* (∀ p : P, Q) : Prop  -- Q can involve p
* T → V : Type         -- function type)
* P → Q : Prop         -- implication)
* ¬ P : Prop
* P ↔ Q : Prop
* P ∨ Q : Prop
* (∃ p : P, Q) : Prop  -- Q can involve p
* T → Prop             -- a property of Ts
* T → V → Prop         -- a T-V binary relation

Knowing this material, you are also expected to be
able to combine these reasoning rules to prove more
interesting propositions. In general, such a proof
first applies elimination rules to obtain additional
useful elements from given assumptions, and then uses
introduction  rules to combine the elements obtained
into proofs of desired conclusions.

You should have an intuitive understanding of the
meaning of each form and how to use each form in
logical reasoning. In particular, understand (1)
the introduction rules for each -- how to construct
proofs in these forms -- and (2) the elimination
rules for each form -- how to use proofs in these
forms to obtain additional facts to be used in
constructing proofs of other propositions.

For extra credit, be able to work in Lean with
(1) propositions and proofs involving combinations
of quantifiers, such as (∀ x : X, ∃ y : Y, Z) and
(∃ x : X, ∀ y : Y, Z), and with (2) negations of
quantified propositions, such as ¬ (∃ p : P, Q)
and ¬ (∀ p : P, Q).

The test will be structured to help you to know and
to show how far you've gotten and where you still
have some work to do.

Here are exercises for each form.
-/

/- ***************************************** -/
/- ***************** true ****************** -/
/- ***************************************** -/

/-
(1)

Use "example" in Lean to prove that there
is a proof of true.  Be sure that after the :=
you can provide a proof using both an expression
and a tactic script.
-/

/- ***************************************** -/
/- ***************** false ***************** -/
/- ***************************************** -/

/-
(2)
Use "def" in Lean to define a function, fq,
that proves that if P and Q are propositions
and if Q is true then false → ¬ Q.
-/

/-
(3)
Use "example" in Lean to prove that if 0 = 1
then 0 ≠ 1.
-/

/-
(4) Use "example" to prove that for any two
natural numbers, a and b if a = b then if
b ≠ a then a ≠ b.
-/

/- ***************************************** -/
/- ***************** and ******************* -/
/- ***************************************** -/

/-
(5)

Use "example" to prove that if P, Q, and R
are propositions, P → Q → R → (P ∧ Q) ∧ R
-/

/-
(6)

Use "example" to prove that if P, Q, and R
are propositions, (P ∧ Q) ∧ R → P ∧ R.
-/

/- ***************************************** -/
/- *************** functions *************** -/
/- ***************************************** -/

/-
(7) Use example to prove that if S and T
are arbitrary types and if there is a value,
t : T, then S → T. Remember that a proof of
S → T has to be a function that if given any
value of type S returns some value of type T.

Present this proof as a Python-style function
definition, isFun, then using a tactic script.
The Π you can read and treast as ∀, for now.
-/

/-
(8) use def to define comp to be a function
that takes as its argments, the types S, T,
and R, along with a function of type S → T
and a function of type T → R and that returns
a function of type S → R. It should take S,
T, and R implicitly and st and tr explicitly.
-/

/-
(9) Define square to be a function that
takes a natural number and returns its
square, and inc to be a function that
takes a nat, n, and returns n + 1. Now
use def and comp to define incSquare to
be a function that takes a nat, n, as an
argument and that returns one more than
n^2. Use #reduce to check that the value
of (incSquare 5) is 26.
-/

/-
(10)

Consider the function, sum4, below. What
is the type of (sum4 3 7)? What function is
(sum4 3 7)? Answer the second question
with a lambda abstraction.
-/

/- ***************************************** -/
/- ************** implication ************** -/
/- ***************************************** -/

/-
(11)

Use several "examples" to prove (1) false → false,
(2) false → true, (3) ¬ (true → false).
-/

/-
(12)

Use example to prove that for any two
propositions, P, Q, if P ∧ Q is true
then if ¬ (P ∨ Q) is true, then you
can derive a proof of false.
-/

/- ***************************************** -/
/- *************** forall (∀) ************** -/
/- ***************************************** -/

/-
(13)

Use example to prove that for all proposition,
P, Q, and R, if P → Q → R then P → R.
-/

/-
(14)

Prove that for any type, T, for any a : T,
and for any property, (P : T → Prop), if
(∀ t : T, P t), then P a.
-/

/- ***************************************** -/
/- *************** negation **************** -/
/- ***************************************** -/

/-
(15)

Show that for any propositions, P and Q,
¬ ((P ∧ Q) ∧ ((P ∧ Q) → (¬ Q ∧ P)).
-/

/-
(16)

Prove that for any propositions, P and R,
if P → R  and ¬ P → R, then R. You might
need to use the law of the excluded middle.
-/

/- ***************************************** -/
/- ************* bi-implication ************ -/
/- ***************************************** -/

/-
(17)

Prove that for any propositions P and Q,
(P -> Q) ∧ (Q → P) → (P ↔ Q).
-/

/-
(18)

Prove that for propositions P and Q,
((P ↔ Q) ∧ P) → Q.
-/

/- ***************************************** -/
/- ************** disjunction ************** -/
/- ***************************************** -/

/-
(19)

Prove that if you eat donuts or if you eat
candy you will get cavities. The proposition
you prove should build in the assumptions
that if you eat donuts you will get cavities
and if you eat candy you will get cavities.
We start the proof for you. You complete it.
Start your proof like this:

example : ∀ donut candy cavity : Prop, ...
-/

/-
(20)

Prove that if P and Q are any propositions, and
if you have a proof of ¬(P ∨ Q) then you can
construct a proof of ¬ P.
-/

/-
(21)

Show, without using em explicitly, that if
for any proposition, P, P ∨ ¬ P, then for any
proposition, Q, ¬ ¬ Q → Q. The proposition to
prove is (∀ P : Prop, P ∨ ¬ P) → (∀ Q, ¬¬ Q → Q).
Remember that a proof of (∀ P, S) can be applied
to a value of type P to get a value of type S.
-/

/- ***************************************** -/
/- **************** predicates ************* -/
/- ***************************************** -/

/-
(22)

Define notZero(n) to be a predicate on
natural numbers that is true when 0 ≠ n
(and false otherwise). Then prove two facts
using "example." First, ¬ (notZero 0). When
doing this proof, remember what (notZero 0)
means, and remember what negation means.
Second, prove (notZero 1).
-/

/-
(23)

Define eqString(s, t) to be a predicate on
values of type string, that is true when
s = t (and not true otherwise). Then prove:
eqString "Hello Lean" ("Hello " ++ "Lean")
-/

/- ***************************************** -/
/- **************** exists ***************** -/
/- ***************************************** -/

/-
(24)

Prove that ∃ n : ℕ, n = 13.
-/

/-
(25)

Prove ∀ s : string, ∃ n, n = string.length s.
-/

/-
(26)

Prove exists m : ℕ, exists n: ℕ, m * n = 100.
Remember that you can apply exists.intro to a
witness, leaving the proof to be constructed
interactively.
-/

/-
(27)

Prove that if P and S are properties of
natural numbers, and if (∃ n : ℕ, P n ∧ S n),
then (∃ n : ℕ, P n ∨ S n).
-/