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

! This file was ported from Lean 3 source module data.nat.gcd.basic
! leanprover-community/mathlib commit 448144f7ae193a8990cb7473c9e9a01990f64ac7
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Algebra.GroupPower.Basic
import Mathbin.Algebra.GroupWithZero.Divisibility
import Mathbin.Data.Nat.Order.Lemmas

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

> THIS FILE IS SYNCHRONIZED WITH MATHLIB4.
> Any changes to this file require a corresponding PR to mathlib4.

Generalizations of these are provided in a later file as `gcd_monoid.gcd` and
`gcd_monoid.lcm`.

Note that the global `is_coprime` is not a straightforward generalization of `nat.coprime`, see
`nat.is_coprime_iff_coprime` for the connection between the two.

-/

namespace Nat

/-! ### `gcd` -/

#print Nat.gcd_dvd /-
theorem gcd_dvd (m n : ℕ) : gcd m n ∣ m ∧ gcd m n ∣ n :=
  gcd.induction m n (fun n => by rw [gcd_zero_left] <;> exact ⟨dvd_zero n, dvd_refl n⟩)
    fun m n npos => by rw [← gcd_rec] <;> exact fun ⟨IH₁, IH₂⟩ => ⟨IH₂, (dvd_mod_iff IH₂).1 IH₁⟩
#align nat.gcd_dvd Nat.gcd_dvd
-/

#print Nat.gcd_dvd_left /-
theorem gcd_dvd_left (m n : ℕ) : gcd m n ∣ m :=
  (gcd_dvd m n).left
#align nat.gcd_dvd_left Nat.gcd_dvd_left
-/

#print Nat.gcd_dvd_right /-
theorem gcd_dvd_right (m n : ℕ) : gcd m n ∣ n :=
  (gcd_dvd m n).right
#align nat.gcd_dvd_right Nat.gcd_dvd_right
-/

#print Nat.gcd_le_left /-
theorem gcd_le_left {m} (n) (h : 0 < m) : gcd m n ≤ m :=
  le_of_dvd h <| gcd_dvd_left m n
#align nat.gcd_le_left Nat.gcd_le_left
-/

#print Nat.gcd_le_right /-
theorem gcd_le_right (m) {n} (h : 0 < n) : gcd m n ≤ n :=
  le_of_dvd h <| gcd_dvd_right m n
#align nat.gcd_le_right Nat.gcd_le_right
-/

/- warning: nat.dvd_gcd -> Nat.dvd_gcd is a dubious translation:
lean 3 declaration is
  forall {m : Nat} {n : Nat} {k : Nat}, (Dvd.Dvd.{0} Nat Nat.hasDvd k m) -> (Dvd.Dvd.{0} Nat Nat.hasDvd k n) -> (Dvd.Dvd.{0} Nat Nat.hasDvd k (Nat.gcd m n))
but is expected to have type
  forall {m : Nat} {n : Nat} {k : Nat}, (Dvd.dvd.{0} Nat Nat.instDvdNat m n) -> (Dvd.dvd.{0} Nat Nat.instDvdNat m k) -> (Dvd.dvd.{0} Nat Nat.instDvdNat m (Nat.gcd n k))
Case conversion may be inaccurate. Consider using '#align nat.dvd_gcd Nat.dvd_gcdₓ'. -/
theorem dvd_gcd {m n k : ℕ} : k ∣ m → k ∣ n → k ∣ gcd m n :=
  gcd.induction m n (fun n _ kn => by rw [gcd_zero_left] <;> exact kn) fun n m mpos IH H1 H2 => by
    rw [gcd_rec] <;> exact IH ((dvd_mod_iff H1).2 H2) H1
#align nat.dvd_gcd Nat.dvd_gcd

/- warning: nat.dvd_gcd_iff -> Nat.dvd_gcd_iff is a dubious translation:
lean 3 declaration is
  forall {m : Nat} {n : Nat} {k : Nat}, Iff (Dvd.Dvd.{0} Nat Nat.hasDvd k (Nat.gcd m n)) (And (Dvd.Dvd.{0} Nat Nat.hasDvd k m) (Dvd.Dvd.{0} Nat Nat.hasDvd k n))
but is expected to have type
  forall {m : Nat} {n : [mdata borrowed:1 Nat]} {k : [mdata borrowed:1 Nat]}, Iff (Dvd.dvd.{0} Nat Nat.instDvdNat m (Nat.gcd n k)) (And (Dvd.dvd.{0} Nat Nat.instDvdNat m n) (Dvd.dvd.{0} Nat Nat.instDvdNat m k))
Case conversion may be inaccurate. Consider using '#align nat.dvd_gcd_iff Nat.dvd_gcd_iffₓ'. -/
theorem dvd_gcd_iff {m n k : ℕ} : k ∣ gcd m n ↔ k ∣ m ∧ k ∣ n :=
  Iff.intro (fun h => ⟨h.trans (gcd_dvd m n).left, h.trans (gcd_dvd m n).right⟩) fun h =>
    dvd_gcd h.left h.right
#align nat.dvd_gcd_iff Nat.dvd_gcd_iff

#print Nat.gcd_comm /-
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))
#align nat.gcd_comm Nat.gcd_comm
-/

#print Nat.gcd_eq_left_iff_dvd /-
theorem gcd_eq_left_iff_dvd {m n : ℕ} : m ∣ n ↔ gcd m n = m :=
  ⟨fun h => by rw [gcd_rec, mod_eq_zero_of_dvd h, gcd_zero_left], fun h => h ▸ gcd_dvd_right m n⟩
#align nat.gcd_eq_left_iff_dvd Nat.gcd_eq_left_iff_dvd
-/

#print Nat.gcd_eq_right_iff_dvd /-
theorem gcd_eq_right_iff_dvd {m n : ℕ} : m ∣ n ↔ gcd n m = m := by
  rw [gcd_comm] <;> apply gcd_eq_left_iff_dvd
#align nat.gcd_eq_right_iff_dvd Nat.gcd_eq_right_iff_dvd
-/

#print Nat.gcd_assoc /-
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)))
#align nat.gcd_assoc Nat.gcd_assoc
-/

#print Nat.gcd_one_right /-
@[simp]
theorem gcd_one_right (n : ℕ) : gcd n 1 = 1 :=
  Eq.trans (gcd_comm n 1) <| gcd_one_left n
#align nat.gcd_one_right Nat.gcd_one_right
-/

#print Nat.gcd_mul_left /-
theorem gcd_mul_left (m n k : ℕ) : gcd (m * n) (m * k) = m * gcd n k :=
  gcd.induction n k (fun k => by repeat' first |rw [MulZeroClass.mul_zero]|rw [gcd_zero_left])
    fun k n H IH => by rwa [← mul_mod_mul_left, ← gcd_rec, ← gcd_rec] at IH
#align nat.gcd_mul_left Nat.gcd_mul_left
-/

#print Nat.gcd_mul_right /-
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]
#align nat.gcd_mul_right Nat.gcd_mul_right
-/

#print Nat.gcd_pos_of_pos_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
#align nat.gcd_pos_of_pos_left Nat.gcd_pos_of_pos_left
-/

#print Nat.gcd_pos_of_pos_right /-
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
#align nat.gcd_pos_of_pos_right Nat.gcd_pos_of_pos_right
-/

#print Nat.eq_zero_of_gcd_eq_zero_left /-
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 fun H1 : 0 < m =>
    absurd (Eq.symm H) (ne_of_lt (gcd_pos_of_pos_left _ H1))
#align nat.eq_zero_of_gcd_eq_zero_left Nat.eq_zero_of_gcd_eq_zero_left
-/

#print Nat.eq_zero_of_gcd_eq_zero_right /-
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
#align nat.eq_zero_of_gcd_eq_zero_right Nat.eq_zero_of_gcd_eq_zero_right
-/

#print Nat.gcd_eq_zero_iff /-
@[simp]
theorem gcd_eq_zero_iff {i j : ℕ} : gcd i j = 0 ↔ i = 0 ∧ j = 0 :=
  by
  constructor
  · 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
#align nat.gcd_eq_zero_iff Nat.gcd_eq_zero_iff
-/

#print Nat.gcd_div /-
theorem gcd_div {m n k : ℕ} (H1 : k ∣ m) (H2 : k ∣ n) : gcd (m / k) (n / k) = gcd m n / k :=
  (Decidable.eq_or_ne k 0).elim
    (fun k0 => by rw [k0, Nat.div_zero, Nat.div_zero, Nat.div_zero, gcd_zero_right]) fun H3 =>
    mul_right_cancel₀ H3 <| by
      rw [Nat.div_mul_cancel (dvd_gcd H1 H2), ← gcd_mul_right, Nat.div_mul_cancel H1,
        Nat.div_mul_cancel H2]
#align nat.gcd_div Nat.gcd_div
-/

#print Nat.gcd_greatest /-
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
#align nat.gcd_greatest Nat.gcd_greatest
-/

#print Nat.gcd_dvd_gcd_of_dvd_left /-
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)
#align nat.gcd_dvd_gcd_of_dvd_left Nat.gcd_dvd_gcd_of_dvd_left
-/

#print Nat.gcd_dvd_gcd_of_dvd_right /-
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)
#align nat.gcd_dvd_gcd_of_dvd_right Nat.gcd_dvd_gcd_of_dvd_right
-/

#print Nat.gcd_dvd_gcd_mul_left /-
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 _ _)
#align nat.gcd_dvd_gcd_mul_left Nat.gcd_dvd_gcd_mul_left
-/

#print Nat.gcd_dvd_gcd_mul_right /-
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 _ _)
#align nat.gcd_dvd_gcd_mul_right Nat.gcd_dvd_gcd_mul_right
-/

#print Nat.gcd_dvd_gcd_mul_left_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 _ _)
#align nat.gcd_dvd_gcd_mul_left_right Nat.gcd_dvd_gcd_mul_left_right
-/

#print Nat.gcd_dvd_gcd_mul_right_right /-
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 _ _)
#align nat.gcd_dvd_gcd_mul_right_right Nat.gcd_dvd_gcd_mul_right_right
-/

#print Nat.gcd_eq_left /-
theorem gcd_eq_left {m n : ℕ} (H : m ∣ n) : gcd m n = m :=
  dvd_antisymm (gcd_dvd_left _ _) (dvd_gcd dvd_rfl H)
#align nat.gcd_eq_left Nat.gcd_eq_left
-/

#print Nat.gcd_eq_right /-
theorem gcd_eq_right {m n : ℕ} (H : n ∣ m) : gcd m n = n := by rw [gcd_comm, gcd_eq_left H]
#align nat.gcd_eq_right Nat.gcd_eq_right
-/

#print Nat.gcd_mul_left_left /-
-- Lemmas where one argument is a multiple of the other
@[simp]
theorem gcd_mul_left_left (m n : ℕ) : gcd (m * n) n = n :=
  dvd_antisymm (gcd_dvd_right _ _) (dvd_gcd (dvd_mul_left _ _) dvd_rfl)
#align nat.gcd_mul_left_left Nat.gcd_mul_left_left
-/

#print Nat.gcd_mul_left_right /-
@[simp]
theorem gcd_mul_left_right (m n : ℕ) : gcd n (m * n) = n := by rw [gcd_comm, gcd_mul_left_left]
#align nat.gcd_mul_left_right Nat.gcd_mul_left_right
-/

#print Nat.gcd_mul_right_left /-
@[simp]
theorem gcd_mul_right_left (m n : ℕ) : gcd (n * m) n = n := by rw [mul_comm, gcd_mul_left_left]
#align nat.gcd_mul_right_left Nat.gcd_mul_right_left
-/

#print Nat.gcd_mul_right_right /-
@[simp]
theorem gcd_mul_right_right (m n : ℕ) : gcd n (n * m) = n := by rw [gcd_comm, gcd_mul_right_left]
#align nat.gcd_mul_right_right Nat.gcd_mul_right_right
-/

#print Nat.gcd_gcd_self_right_left /-
-- Lemmas for repeated application of `gcd`
@[simp]
theorem 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)
#align nat.gcd_gcd_self_right_left Nat.gcd_gcd_self_right_left
-/

#print Nat.gcd_gcd_self_right_right /-
@[simp]
theorem 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]
#align nat.gcd_gcd_self_right_right Nat.gcd_gcd_self_right_right
-/

#print Nat.gcd_gcd_self_left_right /-
@[simp]
theorem gcd_gcd_self_left_right (m n : ℕ) : gcd (gcd n m) m = gcd n m := by
  rw [gcd_comm, gcd_gcd_self_right_right]
#align nat.gcd_gcd_self_left_right Nat.gcd_gcd_self_left_right
-/

#print Nat.gcd_gcd_self_left_left /-
@[simp]
theorem 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]
#align nat.gcd_gcd_self_left_left Nat.gcd_gcd_self_left_left
-/

#print Nat.gcd_add_mul_right_right /-
-- Lemmas where one argument consists of addition of a multiple of the other
@[simp]
theorem 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]
#align nat.gcd_add_mul_right_right Nat.gcd_add_mul_right_right
-/

#print Nat.gcd_add_mul_left_right /-
@[simp]
theorem 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]
#align nat.gcd_add_mul_left_right Nat.gcd_add_mul_left_right
-/

#print Nat.gcd_mul_right_add_right /-
@[simp]
theorem gcd_mul_right_add_right (m n k : ℕ) : gcd m (k * m + n) = gcd m n := by simp [add_comm _ n]
#align nat.gcd_mul_right_add_right Nat.gcd_mul_right_add_right
-/

#print Nat.gcd_mul_left_add_right /-
@[simp]
theorem gcd_mul_left_add_right (m n k : ℕ) : gcd m (m * k + n) = gcd m n := by simp [add_comm _ n]
#align nat.gcd_mul_left_add_right Nat.gcd_mul_left_add_right
-/

#print Nat.gcd_add_mul_right_left /-
@[simp]
theorem 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]
#align nat.gcd_add_mul_right_left Nat.gcd_add_mul_right_left
-/

#print Nat.gcd_add_mul_left_left /-
@[simp]
theorem 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]
#align nat.gcd_add_mul_left_left Nat.gcd_add_mul_left_left
-/

#print Nat.gcd_mul_right_add_left /-
@[simp]
theorem 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]
#align nat.gcd_mul_right_add_left Nat.gcd_mul_right_add_left
-/

#print Nat.gcd_mul_left_add_left /-
@[simp]
theorem 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]
#align nat.gcd_mul_left_add_left Nat.gcd_mul_left_add_left
-/

#print Nat.gcd_add_self_right /-
-- Lemmas where one argument consists of an addition of the other
@[simp]
theorem 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)
#align nat.gcd_add_self_right Nat.gcd_add_self_right
-/

#print Nat.gcd_add_self_left /-
@[simp]
theorem gcd_add_self_left (m n : ℕ) : gcd (m + n) n = gcd m n := by
  rw [gcd_comm, gcd_add_self_right, gcd_comm]
#align nat.gcd_add_self_left Nat.gcd_add_self_left
-/

#print Nat.gcd_self_add_left /-
@[simp]
theorem gcd_self_add_left (m n : ℕ) : gcd (m + n) m = gcd n m := by rw [add_comm, gcd_add_self_left]
#align nat.gcd_self_add_left Nat.gcd_self_add_left
-/

#print Nat.gcd_self_add_right /-
@[simp]
theorem gcd_self_add_right (m n : ℕ) : gcd m (m + n) = gcd m n := by
  rw [add_comm, gcd_add_self_right]
#align nat.gcd_self_add_right Nat.gcd_self_add_right
-/

/-! ### `lcm` -/

#print Nat.lcm_comm /-
theorem lcm_comm (m n : ℕ) : lcm m n = lcm n m := by delta lcm <;> rw [mul_comm, gcd_comm]
#align nat.lcm_comm Nat.lcm_comm
-/

#print Nat.lcm_zero_left /-
@[simp]
theorem lcm_zero_left (m : ℕ) : lcm 0 m = 0 := by
  delta lcm <;> rw [MulZeroClass.zero_mul, Nat.zero_div]
#align nat.lcm_zero_left Nat.lcm_zero_left
-/

#print Nat.lcm_zero_right /-
@[simp]
theorem lcm_zero_right (m : ℕ) : lcm m 0 = 0 :=
  lcm_comm 0 m ▸ lcm_zero_left m
#align nat.lcm_zero_right Nat.lcm_zero_right
-/

#print Nat.lcm_one_left /-
@[simp]
theorem lcm_one_left (m : ℕ) : lcm 1 m = m := by
  delta lcm <;> rw [one_mul, gcd_one_left, Nat.div_one]
#align nat.lcm_one_left Nat.lcm_one_left
-/

#print Nat.lcm_one_right /-
@[simp]
theorem lcm_one_right (m : ℕ) : lcm m 1 = m :=
  lcm_comm 1 m ▸ lcm_one_left m
#align nat.lcm_one_right Nat.lcm_one_right
-/

#print Nat.lcm_self /-
@[simp]
theorem lcm_self (m : ℕ) : lcm m m = m :=
  Or.elim (Nat.eq_zero_or_pos m) (fun h => by rw [h, lcm_zero_left]) fun h => by
    delta lcm <;> rw [gcd_self, Nat.mul_div_cancel _ h]
#align nat.lcm_self Nat.lcm_self
-/

#print Nat.dvd_lcm_left /-
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
#align nat.dvd_lcm_left Nat.dvd_lcm_left
-/

#print Nat.dvd_lcm_right /-
theorem dvd_lcm_right (m n : ℕ) : n ∣ lcm m n :=
  lcm_comm n m ▸ dvd_lcm_left n m
#align nat.dvd_lcm_right Nat.dvd_lcm_right
-/

#print Nat.gcd_mul_lcm /-
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))]
#align nat.gcd_mul_lcm Nat.gcd_mul_lcm
-/

#print Nat.lcm_dvd /-
theorem lcm_dvd {m n k : ℕ} (H1 : m ∣ k) (H2 : n ∣ k) : lcm m n ∣ k :=
  Or.elim (Nat.eq_zero_or_pos k) (fun h => by rw [h] <;> exact dvd_zero _) fun 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 _)
#align nat.lcm_dvd Nat.lcm_dvd
-/

#print Nat.lcm_dvd_mul /-
theorem lcm_dvd_mul (m n : ℕ) : lcm m n ∣ m * n :=
  lcm_dvd (dvd_mul_right _ _) (dvd_mul_left _ _)
#align nat.lcm_dvd_mul Nat.lcm_dvd_mul
-/

#print Nat.lcm_dvd_iff /-
theorem lcm_dvd_iff {m n k : ℕ} : lcm m n ∣ k ↔ m ∣ k ∧ n ∣ k :=
  ⟨fun h => ⟨(dvd_lcm_left _ _).trans h, (dvd_lcm_right _ _).trans h⟩, and_imp.2 lcm_dvd⟩
#align nat.lcm_dvd_iff Nat.lcm_dvd_iff
-/

#print Nat.lcm_assoc /-
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)))
#align nat.lcm_assoc Nat.lcm_assoc
-/

#print Nat.lcm_ne_zero /-
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
#align nat.lcm_ne_zero Nat.lcm_ne_zero
-/

/-!
### `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 <;> infer_instance

#print Nat.coprime_iff_gcd_eq_one /-
theorem coprime_iff_gcd_eq_one {m n : ℕ} : coprime m n ↔ gcd m n = 1 :=
  Iff.rfl
#align nat.coprime_iff_gcd_eq_one Nat.coprime_iff_gcd_eq_one
-/

#print Nat.coprime.gcd_eq_one /-
theorem coprime.gcd_eq_one {m n : ℕ} (h : coprime m n) : gcd m n = 1 :=
  h
#align nat.coprime.gcd_eq_one Nat.coprime.gcd_eq_one
-/

#print Nat.coprime.lcm_eq_mul /-
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]
#align nat.coprime.lcm_eq_mul Nat.coprime.lcm_eq_mul
-/

/- warning: nat.coprime.symm -> Nat.coprime.symm is a dubious translation:
lean 3 declaration is
  forall {m : Nat} {n : Nat}, (Nat.coprime n m) -> (Nat.coprime m n)
but is expected to have type
  forall {m : Nat} {n : Nat}, (Nat.coprime m n) -> (Nat.coprime n m)
Case conversion may be inaccurate. Consider using '#align nat.coprime.symm Nat.coprime.symmₓ'. -/
theorem coprime.symm {m n : ℕ} : coprime n m → coprime m n :=
  (gcd_comm m n).trans
#align nat.coprime.symm Nat.coprime.symm

/- warning: nat.coprime_comm -> Nat.coprime_comm is a dubious translation:
lean 3 declaration is
  forall {m : Nat} {n : Nat}, Iff (Nat.coprime n m) (Nat.coprime m n)
but is expected to have type
  forall {m : Nat} {n : Nat}, Iff (Nat.coprime m n) (Nat.coprime n m)
Case conversion may be inaccurate. Consider using '#align nat.coprime_comm Nat.coprime_commₓ'. -/
theorem coprime_comm {m n : ℕ} : coprime n m ↔ coprime m n :=
  ⟨coprime.symm, coprime.symm⟩
#align nat.coprime_comm Nat.coprime_comm

#print Nat.coprime.symmetric /-
theorem coprime.symmetric : Symmetric coprime := fun m n => coprime.symm
#align nat.coprime.symmetric Nat.coprime.symmetric
-/

/- warning: nat.coprime.dvd_of_dvd_mul_right -> Nat.coprime.dvd_of_dvd_mul_right is a dubious translation:
lean 3 declaration is
  forall {m : Nat} {n : Nat} {k : Nat}, (Nat.coprime k n) -> (Dvd.Dvd.{0} Nat Nat.hasDvd k (HMul.hMul.{0, 0, 0} Nat Nat Nat (instHMul.{0} Nat Nat.hasMul) m n)) -> (Dvd.Dvd.{0} Nat Nat.hasDvd k m)
but is expected to have type
  forall {m : Nat} {n : Nat} {k : Nat}, (Nat.coprime m n) -> (Dvd.dvd.{0} Nat Nat.instDvdNat m (HMul.hMul.{0, 0, 0} Nat Nat Nat (instHMul.{0} Nat instMulNat) k n)) -> (Dvd.dvd.{0} Nat Nat.instDvdNat m k)
Case conversion may be inaccurate. Consider using '#align nat.coprime.dvd_of_dvd_mul_right Nat.coprime.dvd_of_dvd_mul_rightₓ'. -/
theorem coprime.dvd_of_dvd_mul_right {m n k : ℕ} (H1 : coprime k n) (H2 : k ∣ m * n) : k ∣ m :=
  by
  let t := dvd_gcd (dvd_mul_left k m) H2
  rwa [gcd_mul_left, H1.gcd_eq_one, mul_one] at t
#align nat.coprime.dvd_of_dvd_mul_right Nat.coprime.dvd_of_dvd_mul_right

/- warning: nat.coprime.dvd_of_dvd_mul_left -> Nat.coprime.dvd_of_dvd_mul_left is a dubious translation:
lean 3 declaration is
  forall {m : Nat} {n : Nat} {k : Nat}, (Nat.coprime k m) -> (Dvd.Dvd.{0} Nat Nat.hasDvd k (HMul.hMul.{0, 0, 0} Nat Nat Nat (instHMul.{0} Nat Nat.hasMul) m n)) -> (Dvd.Dvd.{0} Nat Nat.hasDvd k n)
but is expected to have type
  forall {m : Nat} {n : Nat} {k : Nat}, (Nat.coprime m n) -> (Dvd.dvd.{0} Nat Nat.instDvdNat m (HMul.hMul.{0, 0, 0} Nat Nat Nat (instHMul.{0} Nat instMulNat) n k)) -> (Dvd.dvd.{0} Nat Nat.instDvdNat m k)
Case conversion may be inaccurate. Consider using '#align nat.coprime.dvd_of_dvd_mul_left Nat.coprime.dvd_of_dvd_mul_leftₓ'. -/
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
#align nat.coprime.dvd_of_dvd_mul_left Nat.coprime.dvd_of_dvd_mul_left

#print Nat.coprime.dvd_mul_right /-
theorem coprime.dvd_mul_right {m n k : ℕ} (H : coprime k n) : k ∣ m * n ↔ k ∣ m :=
  ⟨H.dvd_of_dvd_mul_right, fun h => dvd_mul_of_dvd_left h n⟩
#align nat.coprime.dvd_mul_right Nat.coprime.dvd_mul_right
-/

#print Nat.coprime.dvd_mul_left /-
theorem coprime.dvd_mul_left {m n k : ℕ} (H : coprime k m) : k ∣ m * n ↔ k ∣ n :=
  ⟨H.dvd_of_dvd_mul_left, fun h => dvd_mul_of_dvd_right h m⟩
#align nat.coprime.dvd_mul_left Nat.coprime.dvd_mul_left
-/

/- warning: nat.coprime.gcd_mul_left_cancel -> Nat.coprime.gcd_mul_left_cancel is a dubious translation:
lean 3 declaration is
  forall {k : Nat} (m : Nat) {n : Nat}, (Nat.coprime k n) -> (Eq.{1} Nat (Nat.gcd (HMul.hMul.{0, 0, 0} Nat Nat Nat (instHMul.{0} Nat Nat.hasMul) k m) n) (Nat.gcd m n))
but is expected to have type
  forall {k : Nat} {m : Nat} (n : Nat), (Nat.coprime k m) -> (Eq.{1} Nat (Nat.gcd (HMul.hMul.{0, 0, 0} Nat Nat Nat (instHMul.{0} Nat instMulNat) k n) m) (Nat.gcd n m))
Case conversion may be inaccurate. Consider using '#align nat.coprime.gcd_mul_left_cancel Nat.coprime.gcd_mul_left_cancelₓ'. -/
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 _ _ _)
#align nat.coprime.gcd_mul_left_cancel Nat.coprime.gcd_mul_left_cancel

/- warning: nat.coprime.gcd_mul_right_cancel -> Nat.coprime.gcd_mul_right_cancel is a dubious translation:
lean 3 declaration is
  forall (m : Nat) {k : Nat} {n : Nat}, (Nat.coprime k n) -> (Eq.{1} Nat (Nat.gcd (HMul.hMul.{0, 0, 0} Nat Nat Nat (instHMul.{0} Nat Nat.hasMul) m k) n) (Nat.gcd m n))
but is expected to have type
  forall {m : Nat} {k : Nat} (n : Nat), (Nat.coprime m k) -> (Eq.{1} Nat (Nat.gcd (HMul.hMul.{0, 0, 0} Nat Nat Nat (instHMul.{0} Nat instMulNat) n m) k) (Nat.gcd n k))
Case conversion may be inaccurate. Consider using '#align nat.coprime.gcd_mul_right_cancel Nat.coprime.gcd_mul_right_cancelₓ'. -/
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]
#align nat.coprime.gcd_mul_right_cancel Nat.coprime.gcd_mul_right_cancel

#print Nat.coprime.gcd_mul_left_cancel_right /-
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]
#align nat.coprime.gcd_mul_left_cancel_right Nat.coprime.gcd_mul_left_cancel_right
-/

#print Nat.coprime.gcd_mul_right_cancel_right /-
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]
#align nat.coprime.gcd_mul_right_cancel_right Nat.coprime.gcd_mul_right_cancel_right
-/

#print Nat.coprime_div_gcd_div_gcd /-
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]
#align nat.coprime_div_gcd_div_gcd Nat.coprime_div_gcd_div_gcd
-/

/- warning: nat.not_coprime_of_dvd_of_dvd -> Nat.not_coprime_of_dvd_of_dvd is a dubious translation:
lean 3 declaration is
  forall {m : Nat} {n : Nat} {d : Nat}, (LT.lt.{0} Nat Nat.hasLt (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))) d) -> (Dvd.Dvd.{0} Nat Nat.hasDvd d m) -> (Dvd.Dvd.{0} Nat Nat.hasDvd d n) -> (Not (Nat.coprime m n))
but is expected to have type
  forall {m : Nat} {n : Nat} {d : Nat}, (LT.lt.{0} Nat instLTNat (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)) m) -> (Dvd.dvd.{0} Nat Nat.instDvdNat m n) -> (Dvd.dvd.{0} Nat Nat.instDvdNat m d) -> (Not (Nat.coprime n d))
Case conversion may be inaccurate. Consider using '#align nat.not_coprime_of_dvd_of_dvd Nat.not_coprime_of_dvd_of_dvdₓ'. -/
theorem not_coprime_of_dvd_of_dvd {m n d : ℕ} (dgt1 : 1 < d) (Hm : d ∣ m) (Hn : d ∣ n) :
    ¬coprime m n := fun co =>
  not_lt_of_ge (le_of_dvd zero_lt_one <| by rw [← co.gcd_eq_one] <;> exact dvd_gcd Hm Hn) dgt1
#align nat.not_coprime_of_dvd_of_dvd Nat.not_coprime_of_dvd_of_dvd

#print Nat.exists_coprime /-
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⟩
#align nat.exists_coprime Nat.exists_coprime
-/

#print Nat.exists_coprime' /-
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
  ⟨_, m', n', H, h⟩
#align nat.exists_coprime' Nat.exists_coprime'
-/

#print Nat.coprime_add_self_right /-
@[simp]
theorem coprime_add_self_right {m n : ℕ} : coprime m (n + m) ↔ coprime m n := by
  rw [coprime, coprime, gcd_add_self_right]
#align nat.coprime_add_self_right Nat.coprime_add_self_right
-/

#print Nat.coprime_self_add_right /-
@[simp]
theorem coprime_self_add_right {m n : ℕ} : coprime m (m + n) ↔ coprime m n := by
  rw [add_comm, coprime_add_self_right]
#align nat.coprime_self_add_right Nat.coprime_self_add_right
-/

#print Nat.coprime_add_self_left /-
@[simp]
theorem coprime_add_self_left {m n : ℕ} : coprime (m + n) n ↔ coprime m n := by
  rw [coprime, coprime, gcd_add_self_left]
#align nat.coprime_add_self_left Nat.coprime_add_self_left
-/

#print Nat.coprime_self_add_left /-
@[simp]
theorem coprime_self_add_left {m n : ℕ} : coprime (m + n) m ↔ coprime n m := by
  rw [coprime, coprime, gcd_self_add_left]
#align nat.coprime_self_add_left Nat.coprime_self_add_left
-/

#print Nat.coprime_add_mul_right_right /-
@[simp]
theorem 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]
#align nat.coprime_add_mul_right_right Nat.coprime_add_mul_right_right
-/

#print Nat.coprime_add_mul_left_right /-
@[simp]
theorem 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]
#align nat.coprime_add_mul_left_right Nat.coprime_add_mul_left_right
-/

#print Nat.coprime_mul_right_add_right /-
@[simp]
theorem 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]
#align nat.coprime_mul_right_add_right Nat.coprime_mul_right_add_right
-/

#print Nat.coprime_mul_left_add_right /-
@[simp]
theorem 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]
#align nat.coprime_mul_left_add_right Nat.coprime_mul_left_add_right
-/

#print Nat.coprime_add_mul_right_left /-
@[simp]
theorem 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]
#align nat.coprime_add_mul_right_left Nat.coprime_add_mul_right_left
-/

#print Nat.coprime_add_mul_left_left /-
@[simp]
theorem 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]
#align nat.coprime_add_mul_left_left Nat.coprime_add_mul_left_left
-/

#print Nat.coprime_mul_right_add_left /-
@[simp]
theorem 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]
#align nat.coprime_mul_right_add_left Nat.coprime_mul_right_add_left
-/

#print Nat.coprime_mul_left_add_left /-
@[simp]
theorem 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]
#align nat.coprime_mul_left_add_left Nat.coprime_mul_left_add_left
-/

/- warning: nat.coprime.mul -> Nat.coprime.mul is a dubious translation:
lean 3 declaration is
  forall {m : Nat} {n : Nat} {k : Nat}, (Nat.coprime m k) -> (Nat.coprime n k) -> (Nat.coprime (HMul.hMul.{0, 0, 0} Nat Nat Nat (instHMul.{0} Nat Nat.hasMul) m n) k)
but is expected to have type
  forall {m : Nat} {n : Nat} {k : Nat}, (Nat.coprime m n) -> (Nat.coprime k n) -> (Nat.coprime (HMul.hMul.{0, 0, 0} Nat Nat Nat (instHMul.{0} Nat instMulNat) m k) n)
Case conversion may be inaccurate. Consider using '#align nat.coprime.mul Nat.coprime.mulₓ'. -/
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
#align nat.coprime.mul Nat.coprime.mul

#print Nat.coprime.mul_right /-
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
#align nat.coprime.mul_right Nat.coprime.mul_right
-/

#print Nat.coprime.coprime_dvd_left /-
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)
#align nat.coprime.coprime_dvd_left Nat.coprime.coprime_dvd_left
-/

/- warning: nat.coprime.coprime_dvd_right -> Nat.coprime.coprime_dvd_right is a dubious translation:
lean 3 declaration is
  forall {m : Nat} {k : Nat} {n : Nat}, (Dvd.Dvd.{0} Nat Nat.hasDvd n m) -> (Nat.coprime k m) -> (Nat.coprime k n)
but is expected to have type
  forall {m : Nat} {k : Nat} {n : Nat}, (Dvd.dvd.{0} Nat Nat.instDvdNat m k) -> (Nat.coprime n k) -> (Nat.coprime n m)
Case conversion may be inaccurate. Consider using '#align nat.coprime.coprime_dvd_right Nat.coprime.coprime_dvd_rightₓ'. -/
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
#align nat.coprime.coprime_dvd_right Nat.coprime.coprime_dvd_right

#print Nat.coprime.coprime_mul_left /-
theorem coprime.coprime_mul_left {k m n : ℕ} (H : coprime (k * m) n) : coprime m n :=
  H.coprime_dvd_left (dvd_mul_left _ _)
#align nat.coprime.coprime_mul_left Nat.coprime.coprime_mul_left
-/

/- warning: nat.coprime.coprime_mul_right -> Nat.coprime.coprime_mul_right is a dubious translation:
lean 3 declaration is
  forall {k : Nat} {m : Nat} {n : Nat}, (Nat.coprime (HMul.hMul.{0, 0, 0} Nat Nat Nat (instHMul.{0} Nat Nat.hasMul) m k) n) -> (Nat.coprime m n)
but is expected to have type
  forall {k : Nat} {m : Nat} {n : Nat}, (Nat.coprime (HMul.hMul.{0, 0, 0} Nat Nat Nat (instHMul.{0} Nat instMulNat) k m) n) -> (Nat.coprime k n)
Case conversion may be inaccurate. Consider using '#align nat.coprime.coprime_mul_right Nat.coprime.coprime_mul_rightₓ'. -/
theorem coprime.coprime_mul_right {k m n : ℕ} (H : coprime (m * k) n) : coprime m n :=
  H.coprime_dvd_left (dvd_mul_right _ _)
#align nat.coprime.coprime_mul_right Nat.coprime.coprime_mul_right

/- warning: nat.coprime.coprime_mul_left_right -> Nat.coprime.coprime_mul_left_right is a dubious translation:
lean 3 declaration is
  forall {k : Nat} {m : Nat} {n : Nat}, (Nat.coprime m (HMul.hMul.{0, 0, 0} Nat Nat Nat (instHMul.{0} Nat Nat.hasMul) k n)) -> (Nat.coprime m n)
but is expected to have type
  forall {k : Nat} {m : Nat} {n : Nat}, (Nat.coprime k (HMul.hMul.{0, 0, 0} Nat Nat Nat (instHMul.{0} Nat instMulNat) m n)) -> (Nat.coprime k n)
Case conversion may be inaccurate. Consider using '#align nat.coprime.coprime_mul_left_right Nat.coprime.coprime_mul_left_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 _ _)
#align nat.coprime.coprime_mul_left_right Nat.coprime.coprime_mul_left_right

/- warning: nat.coprime.coprime_mul_right_right -> Nat.coprime.coprime_mul_right_right is a dubious translation:
lean 3 declaration is
  forall {k : Nat} {m : Nat} {n : Nat}, (Nat.coprime m (HMul.hMul.{0, 0, 0} Nat Nat Nat (instHMul.{0} Nat Nat.hasMul) n k)) -> (Nat.coprime m n)
but is expected to have type
  forall {k : Nat} {m : Nat} {n : Nat}, (Nat.coprime k (HMul.hMul.{0, 0, 0} Nat Nat Nat (instHMul.{0} Nat instMulNat) m n)) -> (Nat.coprime k m)
Case conversion may be inaccurate. Consider using '#align nat.coprime.coprime_mul_right_right Nat.coprime.coprime_mul_right_rightₓ'. -/
theorem coprime.coprime_mul_right_right {k m n : ℕ} (H : coprime m (n * k)) : coprime m n :=
  H.coprime_dvd_right (dvd_mul_right _ _)
#align nat.coprime.coprime_mul_right_right Nat.coprime.coprime_mul_right_right

#print Nat.coprime.coprime_div_left /-
theorem coprime.coprime_div_left {m n a : ℕ} (cmn : coprime m n) (dvd : a ∣ m) :
    coprime (m / a) n := by
  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
#align nat.coprime.coprime_div_left Nat.coprime.coprime_div_left
-/

#print Nat.coprime.coprime_div_right /-
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
#align nat.coprime.coprime_div_right Nat.coprime.coprime_div_right
-/

/- warning: nat.coprime_mul_iff_left -> Nat.coprime_mul_iff_left is a dubious translation:
lean 3 declaration is
  forall {k : Nat} {m : Nat} {n : Nat}, Iff (Nat.coprime (HMul.hMul.{0, 0, 0} Nat Nat Nat (instHMul.{0} Nat Nat.hasMul) m n) k) (And (Nat.coprime m k) (Nat.coprime n k))
but is expected to have type
  forall {k : Nat} {m : Nat} {n : Nat}, Iff (Nat.coprime (HMul.hMul.{0, 0, 0} Nat Nat Nat (instHMul.{0} Nat instMulNat) k m) n) (And (Nat.coprime k n) (Nat.coprime m n))
Case conversion may be inaccurate. Consider using '#align nat.coprime_mul_iff_left Nat.coprime_mul_iff_leftₓ'. -/
theorem coprime_mul_iff_left {k m n : ℕ} : coprime (m * n) k ↔ coprime m k ∧ coprime n k :=
  ⟨fun h => ⟨coprime.coprime_mul_right h, coprime.coprime_mul_left h⟩, fun ⟨h, _⟩ => by
    rwa [coprime_iff_gcd_eq_one, coprime.gcd_mul_left_cancel n h]⟩
#align nat.coprime_mul_iff_left Nat.coprime_mul_iff_left

#print Nat.coprime_mul_iff_right /-
theorem 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
#align nat.coprime_mul_iff_right Nat.coprime_mul_iff_right
-/

/- warning: nat.coprime.gcd_left -> Nat.coprime.gcd_left is a dubious translation:
lean 3 declaration is
  forall (k : Nat) {m : Nat} {n : Nat}, (Nat.coprime m n) -> (Nat.coprime (Nat.gcd k m) n)
but is expected to have type
  forall {k : Nat} {m : Nat} (n : Nat), (Nat.coprime k m) -> (Nat.coprime (Nat.gcd n k) m)
Case conversion may be inaccurate. Consider using '#align nat.coprime.gcd_left Nat.coprime.gcd_leftₓ'. -/
theorem coprime.gcd_left (k : ℕ) {m n : ℕ} (hmn : coprime m n) : coprime (gcd k m) n :=
  hmn.coprime_dvd_left <| gcd_dvd_right k m
#align nat.coprime.gcd_left Nat.coprime.gcd_left

/- warning: nat.coprime.gcd_right -> Nat.coprime.gcd_right is a dubious translation:
lean 3 declaration is
  forall (k : Nat) {m : Nat} {n : Nat}, (Nat.coprime m n) -> (Nat.coprime m (Nat.gcd k n))
but is expected to have type
  forall {k : Nat} {m : Nat} (n : Nat), (Nat.coprime k m) -> (Nat.coprime k (Nat.gcd n m))
Case conversion may be inaccurate. Consider using '#align nat.coprime.gcd_right Nat.coprime.gcd_rightₓ'. -/
theorem coprime.gcd_right (k : ℕ) {m n : ℕ} (hmn : coprime m n) : coprime m (gcd k n) :=
  hmn.coprime_dvd_right <| gcd_dvd_right k n
#align nat.coprime.gcd_right Nat.coprime.gcd_right

/- warning: nat.coprime.gcd_both -> Nat.coprime.gcd_both is a dubious translation:
lean 3 declaration is
  forall (k : Nat) (l : Nat) {m : Nat} {n : Nat}, (Nat.coprime m n) -> (Nat.coprime (Nat.gcd k m) (Nat.gcd l n))
but is expected to have type
  forall {k : Nat} {l : Nat} (m : Nat) (n : Nat), (Nat.coprime k l) -> (Nat.coprime (Nat.gcd m k) (Nat.gcd n l))
Case conversion may be inaccurate. Consider using '#align nat.coprime.gcd_both Nat.coprime.gcd_bothₓ'. -/
theorem 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
#align nat.coprime.gcd_both Nat.coprime.gcd_both

/- warning: nat.coprime.mul_dvd_of_dvd_of_dvd -> Nat.coprime.mul_dvd_of_dvd_of_dvd is a dubious translation:
lean 3 declaration is
  forall {a : Nat} {n : Nat} {m : Nat}, (Nat.coprime m n) -> (Dvd.Dvd.{0} Nat Nat.hasDvd m a) -> (Dvd.Dvd.{0} Nat Nat.hasDvd n a) -> (Dvd.Dvd.{0} Nat Nat.hasDvd (HMul.hMul.{0, 0, 0} Nat Nat Nat (instHMul.{0} Nat Nat.hasMul) m n) a)
but is expected to have type
  forall {a : Nat} {n : Nat} {m : Nat}, (Nat.coprime a n) -> (Dvd.dvd.{0} Nat Nat.instDvdNat a m) -> (Dvd.dvd.{0} Nat Nat.instDvdNat n m) -> (Dvd.dvd.{0} Nat Nat.instDvdNat (HMul.hMul.{0, 0, 0} Nat Nat Nat (instHMul.{0} Nat instMulNat) a n) m)
Case conversion may be inaccurate. Consider using '#align nat.coprime.mul_dvd_of_dvd_of_dvd Nat.coprime.mul_dvd_of_dvd_of_dvdₓ'. -/
theorem 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
  hk.symm ▸ mul_dvd_mul_left _ (hmn.symm.dvd_of_dvd_mul_left (hk ▸ hn))
#align nat.coprime.mul_dvd_of_dvd_of_dvd Nat.coprime.mul_dvd_of_dvd_of_dvd

#print Nat.coprime_one_left /-
theorem coprime_one_left : ∀ n, coprime 1 n :=
  gcd_one_left
#align nat.coprime_one_left Nat.coprime_one_left
-/

#print Nat.coprime_one_right /-
theorem coprime_one_right : ∀ n, coprime n 1 :=
  gcd_one_right
#align nat.coprime_one_right Nat.coprime_one_right
-/

#print Nat.coprime.pow_left /-
theorem coprime.pow_left {m k : ℕ} (n : ℕ) (H1 : coprime m k) : coprime (m ^ n) k :=
  Nat.recOn n (coprime_one_left _) fun n IH => H1.mul IH
#align nat.coprime.pow_left Nat.coprime.pow_left
-/

/- warning: nat.coprime.pow_right -> Nat.coprime.pow_right is a dubious translation:
lean 3 declaration is
  forall {m : Nat} {k : Nat} (n : Nat), (Nat.coprime k m) -> (Nat.coprime k (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat (Monoid.Pow.{0} Nat Nat.monoid)) m n))
but is expected to have type
  forall {m : Nat} {k : Nat} (n : Nat), (Nat.coprime m k) -> (Nat.coprime m (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat instPowNat) k n))
Case conversion may be inaccurate. Consider using '#align nat.coprime.pow_right Nat.coprime.pow_rightₓ'. -/
theorem coprime.pow_right {m k : ℕ} (n : ℕ) (H1 : coprime k m) : coprime k (m ^ n) :=
  (H1.symm.pow_leftₓ n).symm
#align nat.coprime.pow_right Nat.coprime.pow_right

#print Nat.coprime.pow /-
theorem coprime.pow {k l : ℕ} (m n : ℕ) (H1 : coprime k l) : coprime (k ^ m) (l ^ n) :=
  (H1.pow_leftₓ _).pow_right _
#align nat.coprime.pow Nat.coprime.pow
-/

#print Nat.coprime_pow_left_iff /-
@[simp]
theorem coprime_pow_left_iff {n : ℕ} (hn : 0 < n) (a b : ℕ) :
    Nat.coprime (a ^ n) b ↔ Nat.coprime a b :=
  by
  obtain ⟨n, rfl⟩ := exists_eq_succ_of_ne_zero hn.ne'
  rw [pow_succ, Nat.coprime_mul_iff_left]
  exact ⟨And.left, fun hab => ⟨hab, hab.pow_leftₓ _⟩⟩
#align nat.coprime_pow_left_iff Nat.coprime_pow_left_iff
-/

#print Nat.coprime_pow_right_iff /-
@[simp]
theorem 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]
#align nat.coprime_pow_right_iff Nat.coprime_pow_right_iff
-/

#print Nat.coprime.eq_one_of_dvd /-
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]
#align nat.coprime.eq_one_of_dvd Nat.coprime.eq_one_of_dvd
-/

#print Nat.coprime_zero_left /-
@[simp]
theorem coprime_zero_left (n : ℕ) : coprime 0 n ↔ n = 1 := by simp [coprime]
#align nat.coprime_zero_left Nat.coprime_zero_left
-/

#print Nat.coprime_zero_right /-
@[simp]
theorem coprime_zero_right (n : ℕ) : coprime n 0 ↔ n = 1 := by simp [coprime]
#align nat.coprime_zero_right Nat.coprime_zero_right
-/

#print Nat.not_coprime_zero_zero /-
theorem not_coprime_zero_zero : ¬coprime 0 0 := by simp
#align nat.not_coprime_zero_zero Nat.not_coprime_zero_zero
-/

#print Nat.coprime_one_left_iff /-
@[simp]
theorem coprime_one_left_iff (n : ℕ) : coprime 1 n ↔ True := by simp [coprime]
#align nat.coprime_one_left_iff Nat.coprime_one_left_iff
-/

#print Nat.coprime_one_right_iff /-
@[simp]
theorem coprime_one_right_iff (n : ℕ) : coprime n 1 ↔ True := by simp [coprime]
#align nat.coprime_one_right_iff Nat.coprime_one_right_iff
-/

#print Nat.coprime_self /-
@[simp]
theorem coprime_self (n : ℕ) : coprime n n ↔ n = 1 := by simp [coprime]
#align nat.coprime_self Nat.coprime_self
-/

#print Nat.gcd_mul_of_coprime_of_dvd /-
theorem gcd_mul_of_coprime_of_dvd {a b c : ℕ} (hac : coprime a c) (b_dvd_c : b ∣ c) :
    gcd (a * b) c = b :=
  by
  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
#align nat.gcd_mul_of_coprime_of_dvd Nat.gcd_mul_of_coprime_of_dvd
-/

#print Nat.coprime.eq_of_mul_eq_zero /-
theorem 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 (fun hm => ⟨hm, n.coprime_zero_left.mp <| hm ▸ h⟩) fun hn =>
    ⟨m.coprime_zero_left.mp <| hn ▸ h.symm, hn⟩
#align nat.coprime.eq_of_mul_eq_zero Nat.coprime.eq_of_mul_eq_zero
-/

#print Nat.prodDvdAndDvdOfDvdProd /-
/-- Represent a divisor of `m * n` as a product of a divisor of `m` and a divisor of `n`.

See `exists_dvd_and_dvd_of_dvd_mul` for the more general but less constructive version for other
`gcd_monoid`s. -/
def prodDvdAndDvdOfDvdProd {m n k : ℕ} (H : k ∣ m * n) :
    { d : { m' // m' ∣ m } × { n' // n' ∣ n } // k = d.1 * d.2 } :=
  by
  cases h0 : gcd k m
  case zero =>
    obtain rfl : k = 0 := eq_zero_of_gcd_eq_zero_left h0
    obtain rfl : m = 0 := eq_zero_of_gcd_eq_zero_right h0
    exact ⟨⟨⟨0, dvd_refl 0⟩, ⟨n, dvd_refl n⟩⟩, (MulZeroClass.zero_mul n).symm⟩
  case
    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
#align nat.prod_dvd_and_dvd_of_dvd_prod Nat.prodDvdAndDvdOfDvdProd
-/

#print Nat.dvd_mul /-
theorem dvd_mul {x m n : ℕ} : x ∣ m * n ↔ ∃ y z, y ∣ m ∧ z ∣ n ∧ y * z = x :=
  by
  constructor
  · intro h
    obtain ⟨⟨⟨y, hy⟩, ⟨z, hz⟩⟩, rfl⟩ := prod_dvd_and_dvd_of_dvd_prod h
    exact ⟨y, z, hy, hz, rfl⟩
  · rintro ⟨y, z, hy, hz, rfl⟩
    exact mul_dvd_mul hy hz
#align nat.dvd_mul Nat.dvd_mul
-/

#print Nat.gcd_mul_dvd_mul_gcd /-
theorem gcd_mul_dvd_mul_gcd (k m n : ℕ) : gcd k (m * n) ∣ gcd k m * gcd k n :=
  by
  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'
#align nat.gcd_mul_dvd_mul_gcd Nat.gcd_mul_dvd_mul_gcd
-/

/- warning: nat.coprime.gcd_mul -> Nat.coprime.gcd_mul is a dubious translation:
lean 3 declaration is
  forall (k : Nat) {m : Nat} {n : Nat}, (Nat.coprime m n) -> (Eq.{1} Nat (Nat.gcd k (HMul.hMul.{0, 0, 0} Nat Nat Nat (instHMul.{0} Nat Nat.hasMul) m n)) (HMul.hMul.{0, 0, 0} Nat Nat Nat (instHMul.{0} Nat Nat.hasMul) (Nat.gcd k m) (Nat.gcd k n)))
but is expected to have type
  forall {k : Nat} {m : Nat} (n : Nat), (Nat.coprime k m) -> (Eq.{1} Nat (Nat.gcd n (HMul.hMul.{0, 0, 0} Nat Nat Nat (instHMul.{0} Nat instMulNat) k m)) (HMul.hMul.{0, 0, 0} Nat Nat Nat (instHMul.{0} Nat instMulNat) (Nat.gcd n k) (Nat.gcd n m)))
Case conversion may be inaccurate. Consider using '#align nat.coprime.gcd_mul Nat.coprime.gcd_mulₓ'. -/
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 _ _ _))
#align nat.coprime.gcd_mul Nat.coprime.gcd_mul

#print Nat.pow_dvd_pow_iff /-
theorem pow_dvd_pow_iff {a b n : ℕ} (n0 : 0 < n) : a ^ n ∣ b ^ n ↔ a ∣ b :=
  by
  refine' ⟨fun h => _, fun 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]
#align nat.pow_dvd_pow_iff Nat.pow_dvd_pow_iff
-/

/- warning: nat.gcd_mul_gcd_of_coprime_of_mul_eq_mul -> Nat.gcd_mul_gcd_of_coprime_of_mul_eq_mul is a dubious translation:
lean 3 declaration is
  forall {a : Nat} {b : Nat} {c : Nat} {d : Nat}, (Nat.coprime c d) -> (Eq.{1} Nat (HMul.hMul.{0, 0, 0} Nat Nat Nat (instHMul.{0} Nat Nat.hasMul) a b) (HMul.hMul.{0, 0, 0} Nat Nat Nat (instHMul.{0} Nat Nat.hasMul) c d)) -> (Eq.{1} Nat (HMul.hMul.{0, 0, 0} Nat Nat Nat (instHMul.{0} Nat Nat.hasMul) (Nat.gcd a c) (Nat.gcd b c)) c)
but is expected to have type
  forall {a : Nat} {b : Nat} {c : Nat} {d : Nat}, (Nat.coprime a b) -> (Eq.{1} Nat (HMul.hMul.{0, 0, 0} Nat Nat Nat (instHMul.{0} Nat instMulNat) c d) (HMul.hMul.{0, 0, 0} Nat Nat Nat (instHMul.{0} Nat instMulNat) a b)) -> (Eq.{1} Nat (HMul.hMul.{0, 0, 0} Nat Nat Nat (instHMul.{0} Nat instMulNat) (Nat.gcd c a) (Nat.gcd d a)) a)
Case conversion may be inaccurate. Consider using '#align nat.gcd_mul_gcd_of_coprime_of_mul_eq_mul Nat.gcd_mul_gcd_of_coprime_of_mul_eq_mulₓ'. -/
theorem 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 := by
  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 _]
    trans c.gcd (a * b)
    rw [h, gcd_mul_right_right d c]
    apply gcd_mul_dvd_mul_gcd
#align nat.gcd_mul_gcd_of_coprime_of_mul_eq_mul Nat.gcd_mul_gcd_of_coprime_of_mul_eq_mul

#print Nat.eq_one_of_dvd_coprimes /-
/-- If `k:ℕ` divides coprime `a` and `b` then `k = 1` -/
theorem eq_one_of_dvd_coprimes {a b k : ℕ} (h_ab_coprime : coprime a b) (hka : k ∣ a)
    (hkb : k ∣ b) : k = 1 :=
  by
  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
#align nat.eq_one_of_dvd_coprimes Nat.eq_one_of_dvd_coprimes
-/

#print Nat.coprime.mul_add_mul_ne_mul /-
theorem 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 := by
  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]
#align nat.coprime.mul_add_mul_ne_mul Nat.coprime.mul_add_mul_ne_mul
-/

end Nat


-- Límites e sucesiones
-- ====================

-- Desarrollamos una teoría de límites de sucesiones a₀, a₁, a₂, ... de
-- reales, siguiendo la forma en que tradicionalmente se hace en un
-- primer año del grado de matemáticas.
--
-- Este fichero contiene la definición básica del límite de una
-- sucesión, y demuestra propiedades básicas sobre el mismo.
--
-- La importación `data.real.basic` importa la definición y las
-- propiedades de los números reales, incluyendo, por ejemplo, la
-- función de valor absoluto `abs : ℝ → ℝ`, y la prueba de que `ℝ` es un
-- cuerpo arquimediano completo totalmente ordenado.
--
-- Para obtener `ℝ` en Lean, escribimos `\R`.
--
-- Definimos el predicado `is_limit (a : ℕ → ℝ) (l : ℝ)` para significar
-- que `aₙ → l` de la forma habitual:
--    `∀ ε > 0, ∃ N, ∀ n ≥ N, | a n - l | < ε`.
--
-- A continuación desarrollamos la teoría básica de los límites.
--
-- Usaremos las siguientes variables
--    variables (a b c : ℕ → ℝ) (c l m : ℝ)
-- y los principales teoremas que demostraremos son
-- + is_limit_const :
--     is_limit (λ n, c) c
-- + is_limit_subsingleton
--     (hl : is_limit a l)
--     (hm : is_limit a m)
--     : l = m
-- + is_limit_add
--     (h1 : is_limit a l)
--     (h2 : is_limit b m)
--     : is_limit (a + b) (l + m)
-- + is_limit_mul
--     (h1 : is_limit a l)
--     (h2 : is_limit b m)
--     : is_limit (a * b) (l * m)
-- + is_limit_le_of_le
--     (hl : is_limit a l)
--     (hm : is_limit b m)
--     (hle : ∀ n, a n ≤ b n)
--     : l ≤ m
-- + sandwich
--     (ha : is_limit a l)
--     (hc : is_limit c l)
--     (hab : ∀ n, a n ≤ b n)
--     (hbc : ∀ n, b n ≤ c n)
--     : is_limit b l

-- ---------------------------------------------------------------------
-- Ejercicio. Importar la librería de los números reales y de las
-- tácticas.
-- ---------------------------------------------------------------------

import data.real.basic
import tactic

-- ---------------------------------------------------------------------
-- Ejercicio. Crear una sesión con espacio de nombre `oculto`.
-- ---------------------------------------------------------------------

namespace oculto

-- ---------------------------------------------------------------------
-- Ejercicio. Declarar c como variable sobre los reales.
-- ---------------------------------------------------------------------

variable (c : ℝ)

-- ---------------------------------------------------------------------
-- Ejercicio. Definir la notación |x| para `abs x`
-- ---------------------------------------------------------------------

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

-- Nota. Las sucesiones de números reales a₀, a₁, a₂,... se representan
-- mediante funciones a de ℕ a ℝ tal que a(n) = aₙ.

-- ---------------------------------------------------------------------
-- Ejercicio. Definir la función
--    is_limit : (ℕ → ℝ) → ℝ → Prop
-- tal que (is_limit a l) expresa que l es el límite de a.
-- ---------------------------------------------------------------------

definition is_limit : (ℕ → ℝ) → ℝ → Prop :=
λ a l, ∀ ε > 0, ∃ N, ∀ n ≥ N, | a n - l | < ε

-- Nota: Hay que tener en cuenta que `is_limit` no es una función
-- (ℕ → ℝ) → ℝ. Es una relación binaria sobre (ℕ → ℝ) y ℝ; es decir, es
-- una función que toma como entrada una secuencia y un límite
-- candidato, y devuelve la declaración verdadero-falso diciendo que la
-- secuencia converge al límite.
--
-- La razón por la que no podemos utilizar una función "límite", que
-- toma una secuencia y devuelve su límite, es doble:
-- (1) algunas secuencias (como 0, 1, 0, 1, 0, 1,...) no tienen límite, y
-- (2) en este punto del desarrollo, algunas secuencias podrían tener en
--     teoría más de un límite.

-- Nota. La notación `λ n, f n` en Lean sólo significa lo que los
-- matemáticos llamaríamos `f` o `f(n)`. Literalmente, significa "la
-- función que envía `n` a `f n`, con la particularidad de que no es
-- necesario escribir los paréntesis (aunque `λ n, f(n)` también
-- funcionaría bien). Otra forma de reescribirlo de manera más familiar:
-- `λ n, f n` es la función `n ↦ f n`. Así, por ejemplo, `λ n, 2 * n` es
-- simplemente la función `f(x)=2*x`. A veces se llama "notación de
-- función anónima" porque no necesitamos introducir un nombre para la
-- función si utilizamos notación lambda.
--
-- Hay que conocer un truco al respecto. ¿Qué ocurre si tenemos una
-- función definida por la notación lambda, y luego tratamos de
-- ¡evaluarla! Tienes que saber cómo cambiar `(λ n, f n) (37)` en
-- `f(37)` (o, como lo llamaría Lean, `f 37`). Los informáticos llaman a
-- esta transformación "reducción beta". En Lean, la reducción beta es
-- verdadera por definición, por lo que si se utiliza `apply` o `intro`
-- u otras tácticas que funcionan hasta la igualdad por definición,
-- entonces puede que no ni siquiera tiene que cambiarlo en
-- absoluto. Pero si tu objetivo contiene una "λ evaluada" como `⊢ (λ n,
-- f n) 37` y tienes una hipótesis `h1 : f 37 = 100` entonces `rw h1`
-- fallará, porque `rw` es más exigente y sólo funciona hasta la
-- igualdad sintáctica. Así que necesitas conocer el truco para cambiar
-- este objetivo a `f 37`, que es la táctica `dsimp only`. También
-- funciona con las hipótesis -- `dsimp only at h` eliminará una `λ`
-- evaluada de la hipótesis `h`.
--
-- Ahora demostraremos que el límite de una secuencia constante `aₙ = c`
-- es `c`. La definición de la sucesión constante es `λ n, c`, la
-- función que envía cada `n` a `c`.

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que el límite de la función constante c es c.
-- ---------------------------------------------------------------------

-- 1ª demostración
example :
  is_limit (λ n, c) c :=
begin
  unfold is_limit,
  intros ε hε,
  use 0,
  intros n hn,
  dsimp,
  simp,
  exact hε,
end

-- 2ª demostración
example :
  is_limit (λ n, c) c :=
begin
  intros ε hε,
  use 0,
  rintro n -,
  norm_num,
  linarith,
end

-- 3ª demostración
example :
  is_limit (λ n, c) c :=
begin
  intros ε hε,
  use 0,
  rintro n -,
  calc |(λ n, c) n - c|
       = |c - c|  : rfl
   ... = 0        : by norm_num
   ... < ε        : hε
end

-- 4ª demostración
lemma is_limit_const :
  is_limit (λ n, c) c :=
assume ε,
assume hε : ε > 0,
exists.intro 0
  ( assume n,
    assume hn : n ≥ 0,
    show |(λ n, c) n - c| < ε, from
      calc |(λ n, c) n - c|
           = |c - c|  : rfl
       ... = 0        : by norm_num
       ... < ε        : hε)

-- Nota. Voy a guiarte también por la siguiente prueba. Es una prueba
-- de que si `aₙ → l` y `aₙ → m` entonces `l = m`. Así es como se
-- expresa en Lean:
--    theorem is_limit_subsingleton
--      {a : ℕ → ℝ}
--      {l m : ℝ}
--      (hl : is_limit a l)
--      (hm : is_limit a m)
--      : l = m := ...
--
-- Antes de seguir con esta demostración, creo que es hora de que
-- explique estos corchetes correctamente. ¿Cómo es que he escrito `{a :
-- ℕ → ℝ}` y no `(a : ℕ → ℝ)`?
--
--
-- `is_limit_subsingleton` es una prueba de que una secuencia puede
-- tener como máximo un límite. También es una función. Aprender a
-- pensar en las pruebas como funciones es una habilidad importante para
-- demostrar los teoremas de este seminario. Así que vamos a hablar un
-- poco sobre cómo una prueba puede ser una función.
--
-- En la teoría de tipos dependientes de Lean, hay tipos y términos, y
-- cada término tiene un tipo único. Los tipos y los términos se usan
-- para unificar dos ideas que los matemáticos suelen considerar
-- completamente diferentes: la de conjuntos y elementos, y la de los
-- teoremas y las pruebas. Veamos de cerca qué es exactamente esta
-- función `is_limit_subsingleton`.
--
-- Veamos con más detalle `is_limit_subsingleton` (el teorema se enuncia
-- más arriba). Es una función con cinco entradas. La primera entrada es
-- una secuencia `a : ℕ → ℝ`. La segunda y tercera son números reales
-- `l` y `m`. La cuarta entrada es una prueba de que `a(n)` tiende a
-- `l`, y la quinta es una prueba de que `a(n)` tiende a `m`. La salida
-- de la función (después de los dos puntos) es una prueba que "l =
-- m". Así es como Lean piensa en las pruebas internamente, y es
-- importante que interiorice este punto de vista porque tratará las
-- pruebas como como funciones y las evaluará en las entradas para
-- obtener otras pruebas. Bastante por hoy. Si todavía piensas que es un
-- poco raro tener pruebas como entradas y salidas de funciones, piensa
-- en una declaración verdadero-falso (por ejemplo, un teorema como un
-- conjunto, y los elementos de ese conjunto son las pruebas del
-- teorema. Por ejemplo, `2 + 2 = 5` es un conjunto sin elementos,
-- porque no hay pruebas de este teorema (suponiendo que las matemáticas
-- sean consistentes).
--
-- Ahora bien, si piensas en estas entradas cuidadosamente, y piensas en
-- tu modelo mental de una función, puede que te des cuenta de que hay
-- algo más un poco sospechoso que está ocurriendo aquí. Normalmente se
-- piensa en una función con cinco entradas como una función de "A × B ×
-- C × D × E" a "X", donde "A", "B", "C", "D" y "E" son todos
-- conjuntos. Las tres primeras entradas `a` (del tipo `ℕ → ℝ`) y `l` y
-- `m` (del tipo `ℝ`) no son conflictivas: basta con poner `A = ℕ → ℝ` y
-- `B = C = ℝ`. Pero la cuarta entrada a `is_limit_singleton` es un elemento
-- del conjunto de pruebas de `is_limit a l`, la afirmación de que
-- `a(n)` tiende a `l`, y en particular este conjunto en sí mismo
-- *depende de las dos primeras entradas*. El conjunto `D` es una
-- función de `a` y `l` -- las propias entradas, en lugar de los
-- conjuntos "A" y "B" a los que pertenecen. Lo mismo ocurre para el
-- conjunto "E", que es una función de "a" y "m". Esta configuración un
-- poco extraña tiene la consecuencia aún más extraña de que, en
-- realidad, si Lean conoce las cuarto y quinto datos (en este caso, las
-- pruebas de " is_limit) y " is_limit") entonces *ni siquiera necesita
-- saber cuáles son las tres primeras entradas*, porque Lean puede
-- calcularlas a partir del *tipo* de las entradas cuarta y quinta.
--
-- En resumen, las cinco entradas de esta función son
-- + `a` de tipo `ℕ → ℝ`,
-- + `l` y "m" de tipo `ℝ`,
-- + `h1` de tipo `is_limit a l`
-- + `h2` de tipo `is_limit a m`.
--
-- En particular, si conocemos las entradas cuarta y quinta `h1` y `h2`,
-- entonces mirando a los tipos de estos términos, podemos realmente
-- trabajar lo que tienen que ser las tres primeras entradas para que
-- todo tenga sentido.
--
-- Por eso ponemos las tres primeras entradas entre paréntesis
-- `{}`. Esto significa que "estas entradas son parte de la función,
-- pero el *sistema de unificación* de Lean (la parte del sistema que
-- comprueba que todo tiene el tipo correcto) los resolverá
-- automáticamente, por lo que no molestaremos al usuario preguntando
-- por ellos". En resumen, si alguna vez ejecutamos esta función de
-- cinco entradas, podemos simplemente dar `h1` y `h2` y dejar que Lean
-- resuelva las tres primeras entradas por sí mismo. En general, si la
-- entrada de una función tiene corchetes `{}` entonces el usuario no
-- tiene que proporcionar esas entradas, el usuario puede confiar en el
-- sistema los rellene automáticamente.

-- Demostración comentada
theorem is_limit_subsingleton
  {a : ℕ → ℝ}
  {l m : ℝ}
  (hl : is_limit a l)
  (hm : is_limit a m)
  : l = m :=
begin
  -- Por contradicción. Supongamos `h : l ≠ m` y demostremos `false`.
  by_contra h,
  -- La idea es que si `ε = |l - m|` entonces la sucesión `a` estará
  -- eventualmente dentro de `ε/2` de `l` y `ε/2` de `m`, lo que será
  -- una contradicción. Para hacer la vida más fácil, rompamos la
  -- simetría y supongamos que `l < m`, porque entonces podemos dejar
  -- que `ε` sea `m - l`.
  wlog hlm : l < m,
  -- Lean comprueba que todo es simétrico en `l` y `m` así que esta
  -- táctica tiene éxito, pero nos pide que demostremos que o bien `l <
  -- m` o bien `m < l`. Ahora tenemos dos objetivos así que pongamos un
  -- par `{` `}` para obtener de nuevo un objetivo.
  { -- Ahora sólo tenemos la única meta fácil `l < m ∨ m < l`. Primero
    -- observamos que los reales están totalmente ordenados, así que
    -- podemos añadir `l < m ∨ l = m ∨ m < l` a la lista de hipótesis con
    -- la táctica `have`:
    have : l < m ∨ l = m ∨ m < l := lt_trichotomy l m,
    -- Ahora el resultado se desprende por pura lógica.
    tauto },
  -- Ahora definamos ε como m - l.
  set ε := m - l with hε,
  -- Matemáticamente, el plan es encontrar ahora números naturales
  -- grandes `L` y `M` tales que `n ≥ L → |a n - l| < ε/2`, y `n ≥ M →
  -- |a n - m| < ε/2`, y luego establecer `n = max L M` para obtener una
  -- contradicción. ¿Cómo hacemos esto en Lean?
  --
  -- Bien, pensemos en `hl` como una función. Su tipo es `is_limit a l`
  -- que es, por definición, `∀ ε, ε > 0 → ...`. Así que `hl` es una
  -- función que quiere como entrada un número real y una prueba de que
  -- es positivo. Demos primero a `hl` el número real `ε/2` de una vez
  -- por todas (es la única vez que usaremos `hl` en la prueba, así que
  -- podemos cambiar su definición).
  specialize hl (ε/2),
  -- Ahora `hl` es una función que quiere una prueba por `ε/2>0` como su
  -- entrada. Matemáticamente, esto es obvio: `ε/2=(m-l)/2` y `l <
  -- m`. La táctica de Lean `linarith` (aritmética lineal) puede
  -- resolver este tipo de objetivo:
  have hε2 : ε/2 > 0 := by linarith,
  -- Ahora podemos especializar más a `hl`:
  specialize hl hε2,
  -- Ahora `hl` ya no es una función. En la jerga, es un tipo en lugar
  -- de un tipo pi. `hl : ∃ (N : ℕ), ∀ (n : ℕ), n ≥ N → |a n - l| < ε /
  -- 2` `hl` es ahora un hecho de un par de piezas de información:
  -- primero un número natural `N`, y segundo una prueba de algún hecho
  -- sobre `N`. Podemos separar `hl` en estas dos piezas con la táctica
  -- `cases`:
  cases hl with L hL,
  -- Ahora `L` es el natural y `hL` es una demostración de un teorema
  -- sobre él: `hL : ∀ (n : ℕ), n ≥ L → |a n - l| < ε / 2`
  --
  -- A continuación tenemos que hacer lo mismo con `hm`. Vamos a hacerlo
  -- todo de una vez. Comprueba que entiendes por qué esta única línea
  -- hace el mismo tipo de cosas que las cuatro líneas anteriores.
  cases hm (ε/2) (by linarith) with M hM,
  -- Ahora volvamos a la prueba matemática. Sea N el máximo de L y M.
  set N := max L M with hN,
  -- Anotemos aquí el hecho de que `L ≤ N` y `M ≤ N`. Encontré estas
  -- pruebas usando `library_search` y luego haciendo clic en "Try
  -- this!". Por ejemplo, `have hLN : L ≤ N := por library_search`,
  have hLN : L ≤ N := le_max_left L M,
  have hMN : M ≤ N := le_max_right L M,
  -- Vamos a poner `n = N` en `hL` y `hM`. De nuevo estoy pensando en
  -- estas cosas como funciones.
  specialize hL N hLN,
  specialize hM N hMN,
  -- Parece que ya deberíamos haber terminado: todo debería seguirse ahora
  -- de las desigualdades obtenidas. Pero tenemos que darle a Lean una
  -- pista más, porque `linarith` no sabe nada de la función `abs`;
  -- necesitamos saber que |x|<ε/2 es lo mismo que `-ε/2 < x ∧ x <
  -- ε/2`. Este teorema se llama `abs_lt` ("el valor absoluto es menor
  -- que").
  rw abs_lt at hL hM,
  -- Como reto, ¿puedes ahora mirar el estado de la táctica y terminar
  -- la prueba en papel? La táctica `linarith` de Lean puede ver su
  -- camino a través del laberinto de desigualdades. Terminemos esta
  -- prueba y hablemos de `linarith` y de otra táctica de gran potencia,
  -- `ring`.
  linarith,
end

-- Nota. Dos comentarios rápidos sobre algunas otras cosas nuevas en la
-- prueba anterior:
--
-- 1) En este seminario utilizaremos mucho el término "máximo". El
-- término "max A B" es el máximo de "A" y "B". La definición de `max`
-- es una definición, no un teorema, lo que significa que habrá una API
-- asociada a ella, es decir, una lista de pequeños teoremas que hacen
-- posible el uso de `max`. Acabamos de ver los dos teoremas importantes
-- que utilizaremos: `le_max_left A B : A ≤ max A B` y `le_max-right A B
-- : B ≤ max A B`.
--
-- Hay otras funciones interesantes en la API `max`, por ejemplo `max_le
-- : A ≤ C → B ≤ C → max A B ≤ C`. La forma más fácil de orientarse en
-- la API `max` es *adivinar* los nombres de los teoremas. Por ejemplo,
-- ¿cómo crees que se llama `max A B < C ↔ A < C ∧ B < C`? Si no puedes
-- adivinarlo, haz trampa ejecutando
--    example (A B C : ℝ) : max A B < C ↔ A < C ∧ B < C :=
--    begin
--      library_search
--    end
--
-- 2) `specialize` es una táctica que modifica una función fijando una
-- vez para todas las primeras entradas. Por ejemplo, digamos que `f : A
-- → B → C → D` es una función. Como `→` es asociativa por la derecha en
-- Lean, `f` es una función que quiere una entrada de `A`, y luego da
-- una función que quiere una entrada de `B`, y da una función que
-- quiere una entrada de `C` y da un elemento de `D`. Así que en
-- realidad es sólo una función que toma tres entradas, una de `A`, una
-- de `B` y una de `C`, y da como resultado algo en `D`. Esto es lo que
-- los informáticos llaman "currying".
--
-- Ahora digamos que tengo `a : A` y quiero que esta sea mi primera
-- entrada para `f`, y no quiero volver a ejecutar `f` con ninguna otra
-- entrada de `A`. Entonces
--    `especializar f a`
-- introducirá `a` en `f` y luego renombrará `f` para que sea la nueva
-- función resultante `B → C → D`.

-- Nota sobre la táctica `ring`. La táctica `ring` probará cualquier
-- objetivo que pueda deducirse de los axiomas de un anillo conmutativo
-- (o incluso de un semirretículo conmutativo como `ℕ`). Por ejemplo, si
-- `(x y : ℝ)` y el objetivo es `(x+y)^3=x^3+3*x^2*y+3*x*y^2+y^ entonces
-- `ring` resolverá este objetivo. En la prueba de `is_limit_add` que
-- aparece a continuación en mi archivo de soluciones, utilizo `ring`
-- para demostrar que `a n + b n - (l + m) = (a n - l) + (b n - m)` y
-- para demostrar que `ε/2 + ε/2 = ε`. Nótese que `ring` se confundirá
-- si ve términos `λ` ya que trabaja hasta la igualdad sintáctica. La
-- táctica `ring` quiere ver una afirmación limpia sobre los elementos
-- de un anillo que implique sólo `+`, `-` y `*`. Tenga en cuenta
-- también que `ring` no mira las hipótesis, sólo trabaja con el
-- objetivo. Así que, por ejemplo, `ring` no resolverá este objetivo
-- directamente:
--    a b c : ℝ
--    ha : a = b + c
--    ⊢ 2 * a = b + b + c + c
-- Para resolver este objetivo hay que hacer `rw ha` y luego `ring`.

-- La táctica `linarith`
-- ---------------------

-- La táctica `linarith` resuelve desigualdades lineales. Por ejemplo,
-- resolverá este objetivo:
--    a b c : ℝ
--    hab : a ≤ b
--    hbc : b < c
--    ⊢ a ≤ c + 1
-- Sin embargo, hay que tener en cuenta que no hará la lógica por el
-- usuario. Por ejemplo no resolverá este objetivo:
--    a b c : ℝ
--    hab : a ≤ b
--    hbc : a ≤ b → b < c
--    ⊢ a ≤ c + 1
-- aunque `b < c` es " evidentemente cierto por `hab`", `linarith` no
-- puede verlo. La única cosa que puede ver es `∧` en las hipótesis:
-- resolverá este objetivo.
--    a b c : ℝ
--    h : a ≤ b ∧ b < c
--    ⊢ a ≤ c + 1
-- Sin embargo no verá a través de `∧` en las conclusiones; no resolverá
-- esto.
--    a b c : ℝ
--    h : a ≤ b ∧ b < c
--    ⊢ a ≤ c + 1 ∧ a ≤ c + 1
-- Para resolver este objetivo, utiliza `split; linarith`. El punto y
-- coma significa "aplicar la siguiente táctica a todos los objetivos
-- creados por la táctica anterior".
--
-- Si no estás seguro de si `linarith` puede ver una desigualdad,
-- simplemente aíslala como una hipótesis o conclusión por sí
-- misma. Entonces `linarith` podrá verla sin duda.

-- La táctica convert
-- ------------------

-- Ya que estamos aquí, he aquí una explicación de otra táctica de gran
-- potencia.
--
-- Si tu objetivo es `⊢ P` y tienes una hipótesis `h : P'` en la que `P`
-- y `P'` sólo difieren ligeramente, entonces `convert h'` reemplazará
-- el objetivo con nuevos objetivos pidiendo que se justifique que todos
-- los lugares en los que `P` y `P'` difieren son iguales.
--
-- He aquí un ejemplo:

example
  (a b : ℝ)
  (h : a * 2 = b + 1)
  : a + a = b - (-1) :=
begin
  -- rw `h` no puede funcionar porque no tenemos una coincidencia
  -- completa en ambos lados de la igualdad.
  convert h,
  -- genera dos objetivos: `a + a = a * 2` and `b - -1 = b + 1`
  { ring },
  { ring }
end

-- Nota. Un ejemplo en el que las cosas pueden ir un poco mal es el
-- siguiente.

example
  (a b : ℝ)
  (h : a * 2 = b + 1)
  : a + a = 1 + b :=
begin
  -- convert h,
  convert h using 1,
  { ring },
  { ring }
end

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que si el límite de la sucesión a es l y c ∈ ℝ,
-- entonces el límite de a(i)+c es l+c.
-- ---------------------------------------------------------------------

-- 1ª demostración
example
  {a : ℕ → ℝ}
  {l : ℝ}
  (c : ℝ)
  (ha : is_limit a l)
  : is_limit (λ i, a i + c) (l + c) :=
begin
  intros ε hε,
  dsimp,
  cases ha ε hε with k hk,
  use k,
  intros n hn,
  calc |a n + c - (l + c)|
       = |a n - l|         : by norm_num
   ... < ε                 : hk n hn,
end

-- 2ª demostración
-- ===============

example
  {a : ℕ → ℝ}
  {l : ℝ}
  (c : ℝ)
  (ha : is_limit a l)
  : is_limit (λ i, a i + c) (l + c) :=
begin
  intros ε hε,
  dsimp,
  cases ha ε hε with k hk,
  use k,
  intros n hn,
  convert hk n hn using 2,
  ring,
end

-- 3ª demostración
-- ===============

example
  {a : ℕ → ℝ}
  {l : ℝ}
  (c : ℝ)
  (ha : is_limit a l)
  : is_limit (λ i, a i + c) (l + c) :=
begin
  intros ε hε,
  convert ha ε hε,
  by norm_num,
end

-- 4ª demostración
-- ===============

lemma is_limit_add_const
  {a : ℕ → ℝ}
  {l : ℝ}
  (c : ℝ)
  (ha : is_limit a l)
  : is_limit (λ i, a i + c) (l + c) :=
λ ε hε, (by convert ha ε hε; norm_num)

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que son equivalentes
-- + el límite de a(i) es l
-- + el límite de a(i)+c es l+c
-- ---------------------------------------------------------------------

-- 1ª demostración
example
  {a : ℕ → ℝ}
  {l : ℝ}
  (c : ℝ)
  : is_limit a l ↔ is_limit (λ i, a i + c) (l + c) :=
begin
  split,
  { apply is_limit_add_const },
  { intro h,
    have h' := is_limit_add_const (-c) h,
    simp at h',
    exact h', }
end

-- 2ª demostración
lemma is_limit_add_const_iff
  {a : ℕ → ℝ}
  {l : ℝ}
  (c : ℝ)
  : is_limit a l ↔ is_limit (λ i, a i + c) (l + c) :=
begin
  split,
  { apply is_limit_add_const },
  { intro h,
    convert is_limit_add_const (-c) h,
    { ext, ring, },
    { ring, }},
end

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que son equivalentes
-- + el límite de a(i) es l
-- + el límite de a(i)-l es 0
-- ---------------------------------------------------------------------

-- 1ª demostración
example
  (a : ℕ → ℝ)
  (l : ℝ)
  : is_limit a l ↔ is_limit (λ i, a i - l) 0 :=
begin
  have h := @is_limit_add_const_iff a l (-l),
  simp [tactic.ring.add_neg_eq_sub] at h,
  exact h,
end

-- 2ª demostración
lemma is_limit_iff_is_limit_sub_eq_zero
  (a : ℕ → ℝ)
  (l : ℝ)
  : is_limit a l ↔ is_limit (λ i, a i - l) 0 :=
begin
  convert is_limit_add_const_iff (-l),
  { ext, ring },
  { ring }
end

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que el límite de la suma de dos sucesiones
-- convergentes es la suma de los límites de dichas sucesiones.
-- ---------------------------------------------------------------------

theorem is_limit_add
  {a b : ℕ → ℝ}
  {l m : ℝ}
  (h1 : is_limit a l)
  (h2 : is_limit b m)
  : is_limit (a + b) (l + m) :=
begin
  intros ε Hε,
  cases h1 (ε/2) (by linarith) with L HL,
  cases h2 (ε/2) (by linarith) with M HM,
  set N := max L M with hN,
  use N,
  have HLN : N ≥ L := le_max_left _ _,
  have HMN : N ≥ M := le_max_right _ _,
  intros n Hn,
  have H3 : |a n - l| < ε/2 := HL n (by linarith),
  have H4 : |b n - m| < ε/2 := HM n (by linarith),
  calc |(a + b) n - (l + m)|
       = |(a n + b n) - (l + m)|   : rfl
  ...  = |(a n - l) + (b n - m)|   : by ring
  ...  ≤ |(a n - l)| + |(b n - m)| : abs_add _ _
  ...  < ε/2 + ε/2                 : by linarith
  ...  = ε                         : by ring
end

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que si el límite de a(i) es l, entonces el de
-- c*a(i) es c*l.
-- ---------------------------------------------------------------------

lemma is_limit_mul_const_left
  {a : ℕ → ℝ}
  {l c : ℝ}
  (h : is_limit a l)
  : is_limit (λ n, c * (a n)) (c * l) :=
begin
  by_cases hc : c = 0,
  { subst hc,
    convert is_limit_const 0,
    { ext, simp, },
    { simp, }},
  { have hc' : 0 < |c| := by rwa abs_pos,
    intros ε hε,
    have hεc : 0 < ε / |c| := div_pos hε hc',
    specialize h (ε/|c|) hεc,
    cases h with N hN,
    use N,
    intros n hn,
    specialize hN n hn,
    dsimp only,
    rw [← mul_sub, abs_mul],
    rw ← lt_div_iff' hc',
    exact hN, }
end

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que si el límite de a(n) es α y el de b(n) es β,
-- entonces el de c·a(n)+d·b(n) es c·α+d·β
-- ---------------------------------------------------------------------

lemma is_limit_linear
  (a : ℕ → ℝ)
  (b : ℕ → ℝ) (α β c d : ℝ)
  (ha : is_limit a α)
  (hb : is_limit b β)
  : is_limit (λ n, c * (a n) + d * (b n) ) (c * α + d * β) :=
begin
  apply is_limit_add,
  { apply is_limit_mul_const_left ha },
  { apply is_limit_mul_const_left hb },
end

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que si las sucesiones a(n) y b(n) convergen a
-- cero, entonces a(n)·b(n) también converge a cero.
-- ---------------------------------------------------------------------

lemma is_limit_mul_eq_zero_of_is_limit_eq_zero
  {a : ℕ → ℝ}
  {b : ℕ → ℝ}
  (ha : is_limit a 0)
  (hb : is_limit b 0)
  : is_limit (a * b) 0 :=
begin
  intros ε hε,
  cases ha ε hε with A hA,
  cases hb 1 (by linarith) with B hB,
  set N := max A B with hN,
  use N,
  intros n hn,
  have hAN : A ≤ N := le_max_left A B,
  have hBN : B ≤ N := le_max_right A B,
  specialize hA n (by linarith),
  specialize hB n (by linarith),
  rw sub_zero at ⊢ hA hB,
  rw pi.mul_apply,
  rw abs_mul,
  convert mul_lt_mul'' hA hB _ _, simp,
  all_goals {apply abs_nonneg},
end

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que el límite del producto de dos sucesiones
-- convergentes es el producto de sus límites.
-- ---------------------------------------------------------------------

-- 1ª demostración
example
  (a : ℕ → ℝ)
  (b : ℕ → ℝ)
  (l m : ℝ)
  (h1 : is_limit a l)
  (h2 : is_limit b m)
  : is_limit (a * b) (l * m) :=
begin
  suffices : is_limit (λ i, (a i - l) * (b i - m) + (l * (b i - m)) + m * (a i - l)) 0,
  { rw is_limit_iff_is_limit_sub_eq_zero,
      convert this,
    ext,
    rw pi.mul_apply,
    ring },
  have h1 : is_limit (λ i, a i - l) 0,
  { rwa is_limit_iff_is_limit_sub_eq_zero at h1 },
  have h2 : is_limit (λ i, b i - m) 0,
  { rwa is_limit_iff_is_limit_sub_eq_zero at h2 },
  have h3 : is_limit (λ i, (a i - l) * (b i - m)) 0,
  { apply is_limit_mul_eq_zero_of_is_limit_eq_zero h1 h2 },
  have h4 : is_limit (λ i, l * (b i - m)) 0,
  { convert is_limit_mul_const_left h2,
    ring },
  have h5 : is_limit (λ i, m * (a i - l)) 0,
  { convert is_limit_mul_const_left h1,
    ring },
  convert is_limit_add (_ : is_limit _ 0) h5, norm_num,
  convert is_limit_add h3 h4,
  norm_num,
end

-- 2ª demostración
theorem is_limit_mul
  (a : ℕ → ℝ)
  (b : ℕ → ℝ)
  (l m : ℝ)
  (h1 : is_limit a l)
  (h2 : is_limit b m)
  : is_limit (a * b) (l * m) :=
begin
  suffices : is_limit (λ i, (a i - l) * (b i - m) + (l * (b i - m)) + m * (a i - l)) 0,
  { rw is_limit_iff_is_limit_sub_eq_zero,
      convert this,
    ext,
    rw pi.mul_apply,
    ring },
  have h1 : is_limit (λ i, a i - l) 0,
  { rwa is_limit_iff_is_limit_sub_eq_zero at h1 },
  have h2 : is_limit (λ i, b i - m) 0,
  { rwa is_limit_iff_is_limit_sub_eq_zero at h2 },
  have h3 : is_limit (λ i, (a i - l) * (b i - m)) 0,
  { apply is_limit_mul_eq_zero_of_is_limit_eq_zero h1 h2 },
  have h4 : is_limit (λ i, l * (b i - m)) 0,
  { convert is_limit_mul_const_left h2,
    ring },
  have h5 : is_limit (λ i, m * (a i - l)) 0,
  { convert is_limit_mul_const_left h1,
    ring },
  convert is_limit_add (_ : is_limit _ 0) h5, norm_num,
  convert is_limit_add h3 h4,
  norm_num,
end

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que si aₙ → l, bₙ → m y aₙ ≤ bₙ para todo n,
-- entonces l ≤ m.
-- ---------------------------------------------------------------------

-- 1ª demostración
example
  (a : ℕ → ℝ)
  (b : ℕ → ℝ)
  (l : ℝ)
  (m : ℝ)
  (hl : is_limit a l)
  (hm : is_limit b m)
  (hle : ∀ n, a n ≤ b n)
  : l ≤ m :=
begin
  apply le_of_not_lt,
  intro hlt,
  set ε := (l - m) /2 with hεlm,
  have hε : 0 < ε := by linarith,
  cases hl ε hε with Na HNa,
  cases hm ε hε with Nb HNb,
  let N := max Na Nb,
  have HNa' : Na ≤ N := le_max_left Na Nb,
  have HNb' : Nb ≤ N := le_max_right Na Nb,
  have Hl' : |a N - l| < ε := HNa N HNa',
  have Hm' : |b N - m| < ε := HNb N HNb',
  have HN : a N ≤ b N := hle N,
  rw abs_lt at Hl' Hm',
  linarith,
end

-- 2ª demostración
theorem is_limit_le_of_le
  (a b : ℕ → ℝ)
  (l m : ℝ)
  (hl : is_limit a l)
  (hm : is_limit b m)
  (hle : ∀ n, a n ≤ b n)
  : l ≤ m :=
begin
  apply le_of_not_lt,
  intro hlt,
  set ε := (l - m) /2 with hε,
  cases hl ε (by linarith) with Na HNa,
  cases hm ε (by linarith) with Nb HNb,
  let N := max Na Nb,
  have Hl : |a N - l| < ε :=
    HNa N (le_max_left Na Nb),
  have Hm : |b N - m| < ε :=
    HNb N (le_max_right Na Nb),
  have HN : a N ≤ b N := hle N,
  rw abs_lt at Hl Hm,
  linarith,
end

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar el teorema del emparedado; es decir, sean a(n),
-- b(n) y c(n) tres sucesiones tales que (∀ n, a(n) ≤ b(n) ≤ c(n)) y
-- a(n) tiene el mismo límite que c(n), entonces b(n) también tiene el
-- mismo límite.
-- ---------------------------------------------------------------------

theorem sandwich
  (a b c : ℕ → ℝ)
  (l : ℝ)
  (ha : is_limit a l)
  (hc : is_limit c l)
  (hab : ∀ n, a n ≤ b n)
  (hbc : ∀ n, b n ≤ c n)
  : is_limit b l :=
begin
  intros ε hε,
  cases ha ε hε with A hA,
  cases hc ε hε with C hC,
  set N := max A C with hN,
  use N,
  intros n hn,
  rw hN at hn,
  replace hn := max_le_iff.1 hn,
  specialize hA n (by linarith),
  specialize hC n (by linarith),
  specialize hab n,
  specialize hbc n,
  rw abs_lt at *,
  split;
  linarith,
end

-- ---------------------------------------------------------------------
-- Ejercicio. Definir la función
--    is_bounded : (ℕ → ℝ) → Prop
-- tal que (is_bounded a) expresa que la sucesión a está acotada.
-- ---------------------------------------------------------------------

definition is_bounded (a : ℕ → ℝ) :=
∃ B, ∀ n, |a n| ≤ B

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que si a(n) está acotada por 0, entonces es la
-- sucesión nula.
-- ---------------------------------------------------------------------

-- 1ª demostración
example
  {a : ℕ → ℝ}
  (h : ∀ n, |a n| ≤ 0)
  : ∀ n, a n = 0 :=
begin
  intro n,
  rw ← abs_eq_zero,
  specialize h n,
  apply le_antisymm,
  { exact h, },
  { exact abs_nonneg (a n), },
end

-- 2ª demostración
example
  {a : ℕ → ℝ}
  (h : ∀ n, |a n| ≤ 0)
  : ∀ n, a n = 0 :=
begin
  intro n,
  rw ← abs_eq_zero,
  specialize h n,
  exact le_antisymm h (abs_nonneg (a n)),
end

-- 3ª demostración
example
  {a : ℕ → ℝ}
  (h : ∀ n, |a n| ≤ 0)
  : ∀ n, a n = 0 :=
begin
  intro n,
  rw ← abs_eq_zero,
  exact le_antisymm (h n) (abs_nonneg (a n)),
end

-- 4ª demostración
example
  {a : ℕ → ℝ}
  (h : ∀ n, |a n| ≤ 0)
  : ∀ n, a n = 0 :=
begin
  intro n,
  exact abs_eq_zero.mp (le_antisymm (h n) (abs_nonneg (a n))),
end

-- 5ª demostración
lemma acotada_por_cero
  {a : ℕ → ℝ}
  (h : ∀ n, |a n| ≤ 0)
  : ∀ n, a n = 0 :=
λ n, abs_eq_zero.mp (le_antisymm (h n) (abs_nonneg (a n)))

-- ---------------------------------------------------------------------
-- Ejercicio. Sean a(n) y b(n) dos sucesiones tales que a(n) está
-- acotada y b(n) converge a 0. Demostrar que a*b converge a 0.
-- ---------------------------------------------------------------------

-- 1ª demostración
example
  {a : ℕ → ℝ}
  {b : ℕ → ℝ}
  (hA : is_bounded a)
  (hB : is_limit b 0)
  : is_limit (a*b) 0 :=
begin
  cases hA with A hA,
  have hAnonneg : 0 ≤ A,
    calc 0 ≤ |a 0| : abs_nonneg (a 0)
       ... ≤ A     : hA 0,
  by_cases hA0 : A = 0,
  { subst hA0,
    intros ε hε,
    use 0,
    intros n hn,
    simp_rw [sub_zero] at *,
    calc |(a * b) n|
         = |a n * b n|   : congr_arg abs (pi.mul_apply a b n)
     ... = |a n| * |b n| : abs_mul (a n) (b n)
     ... ≤ 0 * |b n|     : mul_le_mul_of_nonneg_right (hA n) (abs_nonneg (b n))
     ... = 0             : zero_mul (|b n|)
     ... < ε             : hε, },
  { change A ≠ 0 at hA0,
    have hApos : 0 < A := (ne.le_iff_lt hA0.symm).mp hAnonneg,
    intros ε hε,
    cases hB (ε/A) (div_pos hε hApos) with N hN,
    use N,
    intros n hn,
    simp_rw [sub_zero] at *,
    calc |(a * b) n|
         = |a n * b n|    : congr_arg abs (pi.mul_apply a b n)
     ... = |a n| * |b n|  : abs_mul (a n) (b n)
     ... ≤ A * |b n|      : mul_le_mul_of_nonneg_right (hA n) (abs_nonneg _)
     ... < A * (ε/A)      : mul_lt_mul_of_pos_left (hN n hn) hApos
     ... = ε              : mul_div_cancel' ε hA0 },
end

-- 2ª demostración
lemma tendsto_bounded_mul_zero
  {a : ℕ → ℝ}
  {b : ℕ → ℝ}
  (hA : is_bounded a)
  (hB : is_limit b 0)
  : is_limit (a*b) 0 :=
begin
  cases hA with A hA,
  have hAnonneg : 0 ≤ A,
    { refine le_trans _ (hA 0),
      apply abs_nonneg, },
  by_cases hA0 : A = 0,
  { subst hA0,
    have hA2 : ∀ n, a n = 0,
      { intro n,
        specialize hA n,
        have h := abs_nonneg (a n),
        rw ← abs_eq_zero,
        linarith },
    convert is_limit_const 0,
    ext n,
    rw pi.mul_apply,
    rw hA2,
    simp },
  { change A ≠ 0 at hA0,
    have hApos : 0 < A,
      rwa ← (hA0.symm).le_iff_lt,
    intros ε εpos,
    cases hB (ε/A) (div_pos εpos hApos) with N hN,
    simp_rw [sub_zero] at hN,
    use N,
    intros n hn,
    calc |(a * b) n - 0|
         = |a n * b n|    : by simp
     ... = |a n| * |b n|  : abs_mul _ _
     ... ≤ A * |b n|      : mul_le_mul_of_nonneg_right (hA n) (abs_nonneg _)
     ... < A * (ε/A)      : mul_lt_mul_of_pos_left (hN n hn) hApos
     ... = ε              : mul_div_cancel' ε hA0 },
end

-- ---------------------------------------------------------------------
-- Ejercicio. Definir la función
--    is_cauchy : (ℕ → ℝ) → Prop
-- tal que (is_cauchy a) expresa que a es una sucesión de Cauchy.
-- ---------------------------------------------------------------------

def is_cauchy (a : ℕ → ℝ) : Prop :=
∀ ε > 0, ∃ N, ∀ m ≥ N, ∀ n ≥ N, |a m - a n| < ε

end oculto


def Set (α : Type) := α → Prop

def Set.union (s₁ s₂ : Set α) : Set α :=
  fun a => s₁ a ∨ s₂ a

def FinSet (n : Nat) := Fin n → Prop

namespace FinSet
  export Set (union)
end FinSet

example (x y : FinSet 10) : FinSet 10 :=
  FinSet.union x y

example (x y : FinSet 10) : FinSet 10 :=
  x.union y


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

The Special Linear group $SL(n, R)$
-/
import linear_algebra.matrix
import linear_algebra.nonsingular_inverse

/-!
# The Special Linear group $SL(n, R)$

This file defines the elements of the Special Linear group `special_linear_group n R`,
also written `SL(n, R)` or `SLₙ(R)`, consisting of all `n` by `n` `R`-matrices with
determinant `1`.  In addition, we define the group structure on `special_linear_group n R`
and the embedding into the general linear group `general_linear_group R (n → R)`
(i.e. `GL(n, R)` or `GLₙ(R)`).

## Main definitions

 * `matrix.special_linear_group` is the type of matrices with determinant 1
 * `matrix.special_linear_group.group` gives the group structure (under multiplication)
 * `matrix.special_linear_group.embedding_GL` is the embedding `SLₙ(R) → GLₙ(R)`

## Implementation notes
The inverse operation in the `special_linear_group` is defined to be the adjugate
matrix, so that `special_linear_group n R` has a group structure for all `comm_ring R`.

We define the elements of `special_linear_group` to be matrices, since we need to
compute their determinant. This is in contrast with `general_linear_group R M`,
which consists of invertible `R`-linear maps on `M`.

## References

 * https://en.wikipedia.org/wiki/Special_linear_group

## Tags

matrix group, group, matrix inverse
-/

namespace matrix
universes u v
open_locale matrix
open linear_map

section

variables (n : Type u) [decidable_eq n] [fintype n] (R : Type v) [comm_ring R]

/-- `special_linear_group n R` is the group of `n` by `n` `R`-matrices with determinant equal to 1.
-/
def special_linear_group := { A : matrix n n R // A.det = 1 }

end

namespace special_linear_group

variables {n : Type u} [decidable_eq n] [fintype n] {R : Type v} [comm_ring R]

instance coe_matrix : has_coe (special_linear_group n R) (matrix n n R) :=
⟨λ A, A.val⟩

instance coe_fun : has_coe_to_fun (special_linear_group n R) :=
{ F   := λ _, n → n → R,
  coe := λ A, A.val }

/--
  `to_lin' A` is matrix multiplication of vectors by `A`, as a linear map.

  After the group structure on `special_linear_group n R` is defined,
  we show in `to_linear_equiv` that this gives a linear equivalence.
-/
def to_lin' (A : special_linear_group n R) := matrix.to_lin' A

lemma ext_iff (A B : special_linear_group n R) : A = B ↔ (∀ i j, A i j = B i j) :=
iff.trans subtype.ext_iff_val ⟨(λ h i j, congr_fun (congr_fun h i) j), matrix.ext⟩

@[ext] lemma ext (A B : special_linear_group n R) : (∀ i j, A i j = B i j) → A = B :=
(special_linear_group.ext_iff A B).mpr

instance has_inv : has_inv (special_linear_group n R) :=
⟨λ A, ⟨adjugate A, det_adjugate_eq_one A.2⟩⟩

instance has_mul : has_mul (special_linear_group n R) :=
⟨λ A B, ⟨A.1 ⬝ B.1, by erw [det_mul, A.2, B.2, one_mul]⟩⟩

instance has_one : has_one (special_linear_group n R) :=
⟨⟨1, det_one⟩⟩

instance : inhabited (special_linear_group n R) := ⟨1⟩

section coe_lemmas

variables (A B : special_linear_group n R)

@[simp] lemma inv_val : ↑(A⁻¹) = adjugate A := rfl

@[simp] lemma inv_apply : ⇑(A⁻¹) = adjugate A := rfl

@[simp] lemma mul_val : ↑(A * B) = A ⬝ B := rfl

@[simp] lemma mul_apply : ⇑(A * B) = (A ⬝ B) := rfl

@[simp] lemma one_val : ↑(1 : special_linear_group n R) = (1 : matrix n n R) := rfl

@[simp] lemma one_apply : ⇑(1 : special_linear_group n R) = (1 : matrix n n R) := rfl

@[simp] lemma det_coe_matrix : det A = 1 := A.2

lemma det_coe_fun : det ⇑A = 1 := A.2

@[simp] lemma to_lin'_mul :
  to_lin' (A * B) = (to_lin' A).comp (to_lin' B) :=
matrix.to_lin'_mul A B

@[simp] lemma to_lin'_one :
  to_lin' (1 : special_linear_group n R) = linear_map.id :=
matrix.to_lin'_one

end coe_lemmas

instance group : group (special_linear_group n R) :=
{ mul_assoc := λ A B C, by { ext, simp [matrix.mul_assoc] },
  one_mul := λ A, by { ext, simp },
  mul_one := λ A, by { ext, simp },
  mul_left_inv := λ A, by { ext, simp [adjugate_mul] },
  ..special_linear_group.has_mul,
  ..special_linear_group.has_one,
  ..special_linear_group.has_inv }

/-- `to_linear_equiv A` is matrix multiplication of vectors by `A`, as a linear equivalence. -/
def to_linear_equiv (A : special_linear_group n R) : (n → R) ≃ₗ[R] (n → R) :=
{ inv_fun := A⁻¹.to_lin',
  left_inv := λ x, calc
    A⁻¹.to_lin'.comp A.to_lin' x
        = (A⁻¹ * A).to_lin' x : by rw [←to_lin'_mul]
    ... = x : by rw [mul_left_inv, to_lin'_one, id_apply],
  right_inv := λ x, calc
    A.to_lin'.comp A⁻¹.to_lin' x
        = (A * A⁻¹).to_lin' x : by rw [←to_lin'_mul]
    ... = x : by rw [mul_right_inv, to_lin'_one, id_apply],
  ..matrix.to_lin' A }

/-- `to_GL` is the map from the special linear group to the general linear group -/
def to_GL (A : special_linear_group n R) : general_linear_group R (n → R) :=
general_linear_group.of_linear_equiv (to_linear_equiv A)

lemma coe_to_GL (A : special_linear_group n R) :
  (@coe (units _) _ _ (to_GL A)) = A.to_lin' :=
rfl

@[simp]
lemma to_GL_one : to_GL (1 : special_linear_group n R) = 1 :=
by { ext v i, rw [coe_to_GL, to_lin'_one], refl }

@[simp]
lemma to_GL_mul (A B : special_linear_group n R) :
  to_GL (A * B) = to_GL A * to_GL B :=
by { ext v i, rw [coe_to_GL, to_lin'_mul], refl }

/-- `special_linear_group.embedding_GL` is the embedding from `special_linear_group n R`
  to `general_linear_group n R`. -/
def embedding_GL : (special_linear_group n R) →* (general_linear_group R (n → R)) :=
⟨λ A, to_GL A, by simp, by simp⟩

end special_linear_group

end matrix