/-
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 a47cda9662ff3925c6df271090b5808adbca5b46
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Algebra.GroupPower.Basic
import Mathlib.Algebra.GroupWithZero.Divisibility
import Mathlib.Data.Nat.Order.Lemmas

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

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

Note that the global `IsCoprime` is not a straightforward generalization of `Nat.coprime`, see
`Nat.isCoprime_iff_coprime` for the connection between the two.

-/

namespace Nat

/-! ### `gcd` -/

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

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

@[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

@[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

@[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

@[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

@[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

@[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

@[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

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

@[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

@[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

@[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` -/

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

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

/-!
### `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) := inferInstanceAs (Decidable (gcd m n = 1))

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

theorem coprime.symmetric : Symmetric coprime := fun _ _ => coprime.symm
#align nat.coprime.symmetric Nat.coprime.symmetric

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

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

@[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

@[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

@[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

@[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

@[simp]

@[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

@[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

@[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

@[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

@[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

@[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

@[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

@[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.right, fun hab => ⟨hab.pow_left _, hab⟩⟩
#align nat.coprime_pow_left_iff Nat.coprime_pow_left_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

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

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

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

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

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 =>
    let eq := hn ▸ h.symm
    ⟨m.coprime_zero_left.mp <| eq, hn⟩
#align nat.coprime.eq_of_mul_eq_zero Nat.coprime.eq_of_mul_eq_zero

/-- 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
`GCDMonoid`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⟩⟩, (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 Nat.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

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

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 := Nat.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

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

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 (Nat.dvd_mul_left n b)).mpr
        ((congr_arg _ h).mpr (Nat.dvd_mul_left n m)))
  obtain ⟨y, rfl⟩ : m ∣ b :=
    cop.dvd_of_dvd_mul_right
      ((Nat.dvd_add_iff_right (Nat.dvd_mul_left m (n * x))).mpr
        ((congr_arg _ h).mpr (Nat.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


/-
Copyright (c) 2021 Alain Verberkmoes. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Alain Verberkmoes
-/

import data.int.basic

/-!
# IMO 2011 Q5

Let `f` be a function from the set of integers to the set
of positive integers.  Suppose that, for any two integers
`m` and `n`, the difference `f(m) − f(n)` is divisible by
`f(m − n)`.  Prove that, for all integers `m` and `n` with
`f(m) ≤ f(n)`, the number `f(n)` is divisible by `f(m)`.
-/

open int

theorem imo2011_q5 (f : ℤ → ℤ) (hpos : ∀ n : ℤ, 0 < f n)
  (hdvd : ∀ m n : ℤ, f (m - n) ∣ f m - f n) :
∀ m n : ℤ, f m ≤ f n → f m ∣ f n :=
begin
  intros m n h_fm_le_fn,
  cases lt_or_eq_of_le h_fm_le_fn with h_fm_lt_fn h_fm_eq_fn,
  { -- m < n
    let d := f m - f (m - n),
    have h_fn_dvd_d : f n ∣ d,
    { rw ←sub_sub_self m n,
      exact hdvd m (m - n) },
    have h_d_lt_fn : d < f n,
    { calc d < f m : sub_lt_self _ (hpos (m - n))
        ... < f n : h_fm_lt_fn },
    have h_neg_d_lt_fn : -d < f n,
    { calc -d = f (m - n) - f m : neg_sub _ _
        ... < f (m - n) : sub_lt_self _ (hpos m)
        ... ≤ f n - f m : le_of_dvd (sub_pos.mpr h_fm_lt_fn) _
        ... < f n : sub_lt_self _ (hpos m),
      -- ⊢ f (m - n) ∣ f n - f m
      rw [←dvd_neg, neg_sub], exact hdvd m n },
    have h_d_eq_zero : d = 0,
    { obtain (hd | hd | hd) : d > 0 ∨ d = 0 ∨ d < 0 := trichotomous d 0,
      { -- d > 0
        have h₁ : f n ≤ d, from le_of_dvd hd h_fn_dvd_d,
        have h₂ : ¬ f n ≤ d, from not_le.mpr h_d_lt_fn,
        contradiction },
      { -- d = 0
        exact hd },
      { -- d < 0
        have h₁ : f n ≤ -d, from le_of_dvd (neg_pos.mpr hd) ((dvd_neg _ _).mpr h_fn_dvd_d),
        have h₂ : ¬ f n ≤ -d, from not_le.mpr h_neg_d_lt_fn,
        contradiction } },
    have h₁ : f m = f (m - n), from sub_eq_zero.mp h_d_eq_zero,
    have h₂ : f (m - n) ∣ f m - f n, from hdvd m n,
    rw ←h₁ at h₂,
    exact (dvd_iff_dvd_of_dvd_sub h₂).mp dvd_rfl },
  { -- m = n
    rw h_fm_eq_fn }
end


import data.finset
import algebra.big_operators
import tactic.ring
open nat

set_option pp.structure_projections false

variable (n : ℕ)

def impar (n : ℕ) := 2 * n + 1

-- (finset.range n) es el conjunto finito {0,1,2,...,n-1}. Por ejemplo,
--    #eval (finset.range 3)                  -- => {0, 1, 2}
--    #eval finset.sum (finset.range 3) impar -- => 9

-- 1ª demostración
example :
  finset.sum (finset.range n) impar = n ^ 2 :=
begin
  induction n with d hd,
    refl,
  rw finset.sum_range_succ,
  rw hd,
  change d ^ 2 + (2 * d + 1) = (d + 1) ^ 2,
  ring,
end

-- 2ª demostración
example :
  finset.sum (finset.range n) impar = n ^ 2 :=
begin
  induction n with m HI,
  { calc finset.sum (finset.range 0) impar
          = 0
            : by simp
     ...  = 0 ^ 2
            : rfl, },
  { calc finset.sum (finset.range (succ m)) impar
         = finset.sum (finset.range m) impar + impar m
           : finset.sum_range_succ impar m
     ... = m ^ 2 + impar m
           : congr_arg2 (+) HI rfl
     ... = m ^ 2 + 2 * m + 1
           : rfl
     ... = (m + 1) ^ 2
           : by ring_nf
     ... = succ m ^ 2
           : rfl },
end


/-
Copyright (c) 2019 Sébastien Gouëzel. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Sébastien Gouëzel
-/
import topology.uniform_space.completion
import topology.metric_space.isometry

/-!
# The completion of a metric space

Completion of uniform spaces are already defined in `topology.uniform_space.completion`. We show
here that the uniform space completion of a metric space inherits a metric space structure,
by extending the distance to the completion and checking that it is indeed a distance, and that
it defines the same uniformity as the already defined uniform structure on the completion
-/

open set filter uniform_space uniform_space.completion
open_locale filter
noncomputable theory

universes u
variables {α : Type u} [pseudo_metric_space α]

namespace metric

/-- The distance on the completion is obtained by extending the distance on the original space,
by uniform continuity. -/
instance : has_dist (completion α) :=
⟨completion.extension₂ dist⟩

/-- The new distance is uniformly continuous. -/
protected lemma completion.uniform_continuous_dist :
  uniform_continuous (λp:completion α × completion α, dist p.1 p.2) :=
uniform_continuous_extension₂ dist

/-- The new distance is an extension of the original distance. -/
protected lemma completion.dist_eq (x y : α) : dist (x : completion α) y = dist x y :=
completion.extension₂_coe_coe uniform_continuous_dist _ _

/- Let us check that the new distance satisfies the axioms of a distance, by starting from the
properties on α and extending them to `completion α` by continuity. -/
protected lemma completion.dist_self (x : completion α) : dist x x = 0 :=
begin
  apply induction_on x,
  { refine is_closed_eq _ continuous_const,
    exact (completion.uniform_continuous_dist.continuous.comp
             (continuous.prod_mk continuous_id continuous_id : _) : _) },
  { assume a,
    rw [completion.dist_eq, dist_self] }
end

protected lemma completion.dist_comm (x y : completion α) : dist x y = dist y x :=
begin
  apply induction_on₂ x y,
  { refine is_closed_eq completion.uniform_continuous_dist.continuous _,
    exact completion.uniform_continuous_dist.continuous.comp
      (@continuous_swap (completion α) (completion α) _ _) },
  { assume a b,
    rw [completion.dist_eq, completion.dist_eq, dist_comm] }
end

protected lemma completion.dist_triangle (x y z : completion α) : dist x z ≤ dist x y + dist y z :=
begin
  apply induction_on₃ x y z,
  { refine is_closed_le _ (continuous.add _ _),
    { have : continuous (λp : completion α × completion α × completion α, (p.1, p.2.2)) :=
        continuous.prod_mk continuous_fst (continuous.comp continuous_snd continuous_snd),
      exact (completion.uniform_continuous_dist.continuous.comp this : _) },
    { have : continuous (λp : completion α × completion α × completion α, (p.1, p.2.1)) :=
        continuous.prod_mk continuous_fst (continuous_fst.comp continuous_snd),
      exact (completion.uniform_continuous_dist.continuous.comp this : _) },
    { have : continuous (λp : completion α × completion α × completion α, (p.2.1, p.2.2)) :=
        continuous.prod_mk (continuous_fst.comp continuous_snd)
                           (continuous.comp continuous_snd continuous_snd),
      exact (continuous.comp completion.uniform_continuous_dist.continuous this : _) } },
  { assume a b c,
    rw [completion.dist_eq, completion.dist_eq, completion.dist_eq],
    exact dist_triangle a b c }
end

/-- Elements of the uniformity (defined generally for completions) can be characterized in terms
of the distance. -/
protected lemma completion.mem_uniformity_dist (s : set (completion α × completion α)) :
  s ∈ uniformity (completion α) ↔ (∃ε>0, ∀{a b}, dist a b < ε → (a, b) ∈ s) :=
begin
  split,
  { /- Start from an entourage `s`. It contains a closed entourage `t`. Its pullback in α is an
    entourage, so it contains an ε-neighborhood of the diagonal by definition of the entourages
    in metric spaces. Then `t` contains an ε-neighborhood of the diagonal in `completion α`, as
    closed properties pass to the completion. -/
    assume hs,
    rcases mem_uniformity_is_closed hs with ⟨t, ht, ⟨tclosed, ts⟩⟩,
    have A : {x : α × α | (coe (x.1), coe (x.2)) ∈ t} ∈ uniformity α :=
      uniform_continuous_def.1 (uniform_continuous_coe α) t ht,
    rcases mem_uniformity_dist.1 A with ⟨ε, εpos, hε⟩,
    refine ⟨ε, εpos, λx y hxy, _⟩,
    have : ε ≤ dist x y ∨ (x, y) ∈ t,
    { apply induction_on₂ x y,
      { have : {x : completion α × completion α | ε ≤ dist (x.fst) (x.snd) ∨ (x.fst, x.snd) ∈ t}
               = {p : completion α × completion α | ε ≤ dist p.1 p.2} ∪ t, by ext; simp,
        rw this,
        apply is_closed_union _ tclosed,
        exact is_closed_le continuous_const completion.uniform_continuous_dist.continuous },
      { assume x y,
        rw completion.dist_eq,
        by_cases h : ε ≤ dist x y,
        { exact or.inl h },
        { have Z := hε (not_le.1 h),
          simp only [set.mem_set_of_eq] at Z,
          exact or.inr Z }}},
    simp only [not_le.mpr hxy, false_or, not_le] at this,
    exact ts this },
  { /- Start from a set `s` containing an ε-neighborhood of the diagonal in `completion α`. To show
    that it is an entourage, we use the fact that `dist` is uniformly continuous on
    `completion α × completion α` (this is a general property of the extension of uniformly
    continuous functions). Therefore, the preimage of the ε-neighborhood of the diagonal in ℝ
    is an entourage in `completion α × completion α`. Massaging this property, it follows that
    the ε-neighborhood of the diagonal is an entourage in `completion α`, and therefore this is
    also the case of `s`. -/
    rintros ⟨ε, εpos, hε⟩,
    let r : set (ℝ × ℝ) := {p | dist p.1 p.2 < ε},
    have : r ∈ uniformity ℝ := metric.dist_mem_uniformity εpos,
    have T := uniform_continuous_def.1 (@completion.uniform_continuous_dist α _) r this,
    simp only [uniformity_prod_eq_prod, mem_prod_iff, exists_prop,
               filter.mem_map, set.mem_set_of_eq] at T,
    rcases T with ⟨t1, ht1, t2, ht2, ht⟩,
    refine mem_sets_of_superset ht1 _,
    have A : ∀a b : completion α, (a, b) ∈ t1 → dist a b < ε,
    { assume a b hab,
      have : ((a, b), (a, a)) ∈ set.prod t1 t2 := ⟨hab, refl_mem_uniformity ht2⟩,
      have I := ht this,
      simp [completion.dist_self, real.dist_eq, completion.dist_comm] at I,
      exact lt_of_le_of_lt (le_abs_self _) I },
    show t1 ⊆ s,
    { rintros ⟨a, b⟩ hp,
      have : dist a b < ε := A a b hp,
      exact hε this }}
end

/-- If two points are at distance 0, then they coincide. -/
protected lemma completion.eq_of_dist_eq_zero (x y : completion α) (h : dist x y = 0) : x = y :=
begin
  /- This follows from the separation of `completion α` and from the description of
  entourages in terms of the distance. -/
  have : separated_space (completion α) := by apply_instance,
  refine separated_def.1 this x y (λs hs, _),
  rcases (completion.mem_uniformity_dist s).1 hs with ⟨ε, εpos, hε⟩,
  rw ← h at εpos,
  exact hε εpos
end

/-- Reformulate `completion.mem_uniformity_dist` in terms that are suitable for the definition
of the metric space structure. -/
protected lemma completion.uniformity_dist' :
  uniformity (completion α) = (⨅ε:{ε : ℝ // 0 < ε}, 𝓟 {p | dist p.1 p.2 < ε.val}) :=
begin
  ext s, rw mem_infi,
  { simp [completion.mem_uniformity_dist, subset_def] },
  { rintro ⟨r, hr⟩ ⟨p, hp⟩, use ⟨min r p, lt_min hr hp⟩,
    simp [lt_min_iff, (≥)] {contextual := tt} }
end

protected lemma completion.uniformity_dist :
  uniformity (completion α) = (⨅ ε>0, 𝓟 {p | dist p.1 p.2 < ε}) :=
by simpa [infi_subtype] using @completion.uniformity_dist' α _

/-- Metric space structure on the completion of a pseudo_metric space. -/
instance completion.metric_space : metric_space (completion α) :=
{ dist_self          := completion.dist_self,
  eq_of_dist_eq_zero := completion.eq_of_dist_eq_zero,
  dist_comm          := completion.dist_comm,
  dist_triangle      := completion.dist_triangle,
  to_uniform_space   := by apply_instance,
  uniformity_dist    := completion.uniformity_dist }

/-- The embedding of a metric space in its completion is an isometry. -/
lemma completion.coe_isometry : isometry (coe : α → completion α) :=
isometry_emetric_iff_metric.2 completion.dist_eq

end metric


-- ---------------------------------------------------------------------
-- Ejercicio. Realizar las siguientes acciones
-- 1. Importar la teoría de los números reales.
-- 2. Importar la librería con la táctica de sugerencias.
-- 3. Declarar la teoría como no cumputable.
-- 4. Usar la lógica clásica.
-- ----------------------------------------------------------------------

import data.real.basic -- 1
import tactic.suggest  -- 2
noncomputable theory   -- 3
open_locale classical  -- 4

-- ---------------------------------------------------------------------
-- Ejercicio. Definir la función
--    up_bounds : set ℝ → set ℝ
-- tal que (up_bounds A) es el conjunto de las cotas superiores de A.
-- ----------------------------------------------------------------------

def up_bounds (A : set ℝ) : set ℝ :=
  { x : ℝ | ∀ a ∈ A, a ≤ x}

-- ---------------------------------------------------------------------
-- Ejercicio. Definir la función
--    is_max : ℝ → set ℝ → Prop
-- tal que (is_max a A) afirma que a es un máximo de A.
-- ----------------------------------------------------------------------

def is_max (a : ℝ) (A : set ℝ) : Prop :=
  a ∈ A ∧ a ∈ up_bounds A

-- ---------------------------------------------------------------------
-- Ejercicio. Definir la notación infija de is_max de forma que
-- se pueda escribir `a is_a_max_of A` en lugar de `is_max a A`.
-- ----------------------------------------------------------------------

infix ` is_a_max_of `:55 := is_max

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar la unicidad del máximo; es decir, un conjunto
-- no puede poseer más de un máximo.
-- ----------------------------------------------------------------------

-- 1ª demostración
-- ===============

lemma unique_max
  (A : set ℝ)
  (x y : ℝ)
  (hx : x is_a_max_of A)
  (hy : y is_a_max_of A)
  : x = y :=
begin
  cases hx with x_in x_up,
  cases hy with y_in y_up,
  specialize x_up y y_in,
  specialize y_up x x_in,
  linarith,
end

-- Comentarios:
-- 1. La táctica (cases h with h1 h2) cuando h es una conjunción aplica
--    la eliminación de la conjunción; es decir si (h : A ∧ B) la cambia
--    por (h1 : A) y (h2 : B).
-- 2. La táctica (specialize h y) cuando h es del tipo (∀ x : α, P x)
--    aplica la eliminación del cuantificador universal; es decir
--    sustituye h por (y : α) y (h : P y).
-- 3. La táctica (specialize h y h1) cuando h es del tipo
--    (∀ x : α, P x → Q x) y h1 es del tipo (P y) aplica la eliminación
--    del cuantificador universal y de la implicación; es decir
--    sustituye h por (y : α) y (h : Q y).
-- 4. La táctica linarith intenta demostrar la conclusión mediante la
--    aritmética lineal.

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

example
  (A : set ℝ)
  (x y : ℝ)
  (hx : x is_a_max_of A)
  (hy : y is_a_max_of A)
  : x = y :=
begin
  have : x ≤ y, from hy.2 x hx.1,
  have : y ≤ x, from hx.2 y hy.1,
  linarith,
end

-- Comentarios: 1. Si h es una conjunción, entonces h.1 y h.2 son sus
-- componentes; es decir, si h es (A ∧ B) entonces h.1 es A y h.2 es B.

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

example
  (A : set ℝ)
  (x y : ℝ)
  (hx : x is_a_max_of A)
  (hy : y is_a_max_of A)
  : x = y :=
begin
  apply le_antisymm,
  { have h1 : x ∈ A := hx.1,
    have h2 : ∀ x ∈ A, x ≤ y := hy.2,
    show x ≤ y, from
      h2 x h1 },
  { have h3 : y ∈ A := hy.1,
    have h4 : ∀ y ∈ A, y ≤ x := hx.2,
    show y ≤ x, from
      h4 y h3 },
end

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

example
  (A : set ℝ)
  (x y : ℝ)
  (hx : x is_a_max_of A)
  (hy : y is_a_max_of A)
  : x = y :=
le_antisymm
  ( have h1 : x ∈ A := hx.1,
    have h2 : ∀ x ∈ A, x ≤ y := hy.2,
    show x ≤ y, from
      h2 x h1 )
  ( have h3 : y ∈ A := hy.1,
    have h4 : ∀ y ∈ A, y ≤ x := hx.2,
    show y ≤ x, from
      h4 y h3 )

-- 5ª demostración
-- ===============

example
  (A : set ℝ)
  (x y : ℝ)
  (hx : x is_a_max_of A)
  (hy : y is_a_max_of A)
  : x = y :=
le_antisymm (hy.2 x hx.1) (hx.2 y hy.1)

-- Comentario: Se ha usado el lema
-- + le_antisymm : a ≤ b → b ≤ a → a = b

-- ---------------------------------------------------------------------
-- Ejercicio. Definir la función
--    low_bounds : set ℝ → set ℝ
-- tal que (low_bounds A) es el conjunto de las cotas inferiores de A.
-- ----------------------------------------------------------------------

def low_bounds (A : set ℝ) :=
  { x : ℝ | ∀ a ∈ A, x ≤ a}

-- ---------------------------------------------------------------------
-- Ejercicio. Definir la función
--    is_inf : ℝ → set ℝ → Prop
-- tal que (is_inf x A) afirma que x es el ínfimo de A.
-- ----------------------------------------------------------------------

def is_inf (x : ℝ) (A : set ℝ) : Prop :=
  x is_a_max_of (low_bounds A)

-- ---------------------------------------------------------------------
-- Ejercicio. Definir la notación infija de is_inf de forma que
-- se pueda escribir `a is_an_inf A` en lugar de `is_inf a A`.
-- ----------------------------------------------------------------------

infix ` is_an_inf_of `:55 := is_inf

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que si y es mayor que el ínfimo de A, entonces
-- existe algún elemento a en A tal que a < y.
-- ----------------------------------------------------------------------

-- 1ª demostración
-- ===============

lemma inf_lt
  {A : set ℝ}
  {x : ℝ}
  (hx : x is_an_inf_of A)
  : ∀ y, x < y → ∃ a ∈ A, a < y :=
begin
  intro y,
  contrapose,
  push_neg,
  intro h,
  exact hx.2 y h
end

-- Comentarios:
-- 1. La táctica (intro y) cuando la conclusión es una forma universal
--    aplica la regla de introducción del cuantificador universal; es
--    decir, si la conclusión es (∀ x : α, P x) añade la hipótesis (y : α)
--    y cambia la conclusión por (P y).
-- 2. La táctica contrapose cuando la conclusión es una implicación, la
--    cambia por su contrapositiva; es decir, si la conclusión es (A → B)
--    la cambia por (¬B → ¬ A).
-- 3. La táctica push_neg interioriza las negaciones.
-- 4. La táctica (intro h) cuando la conclusión es una implicación
--    aplica la regla de introducción del condicional; es decir, si la
--    conclusión es (A → B) entonces añade la hipótesis (h : A) y cambia la
--    conclusión por B.

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

example
  {A : set ℝ}
  {x : ℝ}
  (hx : x is_an_inf_of A)
  : ∀ y, x < y → ∃ a ∈ A, a < y :=
begin
  intro y,
  contrapose!,
  intro h,
  exact hx.2 y h
end

-- Comentarios:
-- 1. la táctica contrapose aplica sucesivamente las tácticas contrapose
-- y push_neg.

------------------------------------------------------------------------
-- § Desigualdades entre límites de sucesiones                        --
------------------------------------------------------------------------

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que si (y ≤ x + ε) para todo número positivo ε,
-- entonces y ≤ x.
-- ----------------------------------------------------------------------

lemma le_of_le_add_eps
  {x y : ℝ}
  : (∀ ε > 0, y ≤ x + ε) →  y ≤ x :=
begin
  -- Let's prove the contrapositive, asking Lean to push negations right away.
  contrapose!,
  -- Assume `h : x < y`.
  intro h,
  -- We need to find `ε` such that `ε` is positive and `x + ε < y`.
  -- Let's use `(y-x)/2`
  use ((y-x)/2),
  -- we now have two properties to prove. Let's do both in turn, using `linarith`
  split,
  linarith,
  linarith,
end

/-
Note how `linarith` was used for both sub-goals at the end of the above proof.
We could have shortened that using the semi-colon combinator instead of comma,
writing `split ; linarith`.

Next we will study a compressed version of that proof:
-/

example {x y : ℝ} : (∀ ε > 0, y ≤ x + ε) →  y ≤ x :=
begin
  contrapose!,
  exact assume h, ⟨(y-x)/2, by linarith, by linarith⟩,
end

/-
The angle brackets `⟨` and `⟩` introduce compound data or proofs. A proof
of a `∃ z, P z` statemement is composed of a witness `z₀` and a proof `h` of
`P z₀`. The compound is denoted by `⟨z₀, h⟩`. In the example above, the predicate is
itself compound, it is a conjunction `P z ∧ Q z`. So the proof term should read
`⟨z₀, ⟨h₁, h₂⟩⟩` where `h₁` (resp. `h₂`) is a proof of `P z₀` (resp. `Q z₀`).
But these so-called "anonymous constructor" brackets are right-associative, so we can
get rid of the nested brackets.

The keyword `by` introduces tactic mode inside term mode, it is a shorter version
of the `begin`/`end` pair, which is more convenient for single tactic blocks.
In this example, `begin` enters tactic mode, `exact` leaves it, `by` re-enters it.

Going all the way to a proof term would make the proof much longer, because we
crucially use automation with `contrapose!` and `linarith`. We can still get a one-line
proof using curly braces to gather several tactic invocations, and the `by` abbreviation
instead of `begin`/`end`:
-/

example {x y : ℝ} : (∀ ε > 0, y ≤ x + ε) →  y ≤ x :=
by { contrapose!, exact assume h, ⟨(y-x)/2, by linarith, by linarith⟩ }

/-
One could argue that the above proof is a bit too terse, and we are relying too much
on linarith. Let's have more `linarith` calls for smaller steps. For the sake
of (tiny) variation, we will also assume the premise and argue by contradiction
instead of contraposing.
-/

example {x y : ℝ} : (∀ ε > 0, y ≤ x + ε) →  y ≤ x :=
begin
  intro h,
  -- Assume the conclusion is false, and call this assumption H.
  by_contradiction H,
  push_neg at H,
  -- Now let's compute.
  have key := calc
  -- Each line must end with a colon followed by a proof term
  -- We want to specialize our assumption `h` to `ε = (y-x)/2` but this is long to
  -- type, so let's put a hole `_` that Lean will fill in by comparing the
  -- statement we want to prove and our proof term with a hole. As usual,
  -- positivity of `(y-x)/2` is proved by `linarith`
    y   ≤ x + (y-x)/2 : h _ (by linarith)
    ... = x/2 + y/2   : by ring
    ... < y           : by linarith,
  -- our key now says `y < y` (notice how the sequence `≤`, `=`, `<` was correctly
  -- merged into a `<`). Let `linarith` find the desired contradiction now.
  linarith,
  -- alternatively, we could have provided the proof term
  -- `exact lt_irrefl y key`
end

/-
Now we are ready for some analysis. Let's set up notation for absolute value
-/

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

/-
And let's define convergence of sequences of real numbers (of course there is
a much more general definition in mathlib).
-/

/-- The sequence `u` tends to `l` -/
def limit (u : ℕ → ℝ) (l : ℝ) := ∀ ε > 0, ∃ N, ∀ n ≥ N, |u n - l| ≤ ε

/-
In the above definition, `u n` denotes the n-th term of the sequence. We can
add parentheses to get `u(n)` but we try to avoid parentheses because they pile up
very quickly
-/

-- If y ≤ u n for all n and u n goes to x then y ≤ x
lemma le_lim {x y : ℝ} {u : ℕ → ℝ} (hu : limit u x) (ineq : ∀ n, y ≤ u n) : y ≤ x :=
begin
  -- Let's apply our previous lemma
  apply le_of_le_add_eps,
  -- We need to prove y ≤ x + ε for all positive ε.
  -- Let ε be any positive real
  intros ε ε_pos,
  -- we now specialize our limit assumption to this `ε`, and immediately
  -- fix a `N` as promised by the definition.
  cases hu ε ε_pos with N HN,
  -- Now we only need to compute until reaching the conclusion
  calc
  y ≤ u N             : ineq N
  ... = x + (u N - x) : by linarith
    -- We'll need `add_le_add` which says `a ≤ b` and `c ≤ d` implies `a + c ≤ b + d`
    -- We need a lemma saying `z ≤ |z|`. Because we don't know the name of this lemma,
    -- let's use `library_search`. Because searching thourgh the library is slow,
    -- Lean will write what it found in the Lean message window when cursor is on
    -- that line, so that we can replace it by the lemma. We see `le_max_left` which
    -- says `a ≤ max a b`. Actually there is a more specific lemma `le_abs_self`
  ... ≤ x + |u N - x| : add_le_add (by linarith) (by exact le_abs_self (u N - x))
  ... ≤ x + ε         : add_le_add (by linarith) (HN N (by linarith)),
end

/-
The next lemma has been extracted from the main proof in order to discuss numbers.
In ordinary maths, we know that ℕ is *not* contained in `ℝ`, whatever the
construction of real numbers that we use. For instance a natural number is not
an equivalence class of Cauchy sequences. But it's very easy to
pretend otherwise. Formal maths requires slightly more care. In the statement below,
the "type ascription" `(n + 1 : ℝ)` forces Lean to convert the natural number
`n+1` into a real number.  The "inclusion" map will be displayed in tactic state
as `↑`. There are various lemmas asserting this map is compatible with addition and
monotone, but we don't want to bother writing their names. The `norm_cast`
tactic is designed to wisely apply those lemmas for us.
-/

lemma inv_succ_pos : ∀ n : ℕ, 1/(n+1 : ℝ) > 0 :=
begin
  -- Let `n` be any integer
  intro n,
  -- Since we don't know the name of the relevant lemma, asserting that the inverse of
  -- a positive number is positive, let's state that is suffices
  -- to prove that `n+1`, seen as a real number, is positive, and ask `library_search`
  suffices : (n + 1 : ℝ) > 0,
  { exact one_div_pos.mpr this },
  -- Now we want to reduce to a statement about natural numbers, not real numbers
  -- coming from natural numbers.
  norm_cast,
  -- and then get the usual help from `linarith`
  linarith,
end

/-
That was a pretty long proof for an obvious fact. And stating it as a lemma feels
stupid, so let's find a way to write it on one line in case we want to include it
in some other proof without stating a lemma. First the `library_search` call
above displays the name of the relevant lemma: `one_div_pos_of_pos`. We can also
replace the `linarith` call on the last line by `library_search` to learn the name
of the lemma `nat.succ_pos` asserting that the successor of a natural number is
positive. There is also a variant on `norm_cast` that combines it with `exact`.
The term mode analogue of `intro` is `λ`. We get down to:
-/

-- example : ∀ n : ℕ, 1/(n+1 : ℝ) > 0 :=
-- λ n, one_div_pos_of_pos (by exact_mod_cast nat.succ_pos n)

/-
The next proof uses mostly known things, so we will commment only new aspects.
-/

lemma limit_inv_succ : ∀ ε > 0, ∃ N : ℕ, ∀ n ≥ N, 1/(n + 1 : ℝ) ≤ ε :=
begin
  intros ε ε_pos,
  suffices : ∃ N : ℕ, 1/ε ≤ N,
  { -- Because we didn't provide a name for the above statement, Lean called it `this`.
    -- Let's fix an `N` that works.
    cases this with N HN,
    use N,
    intros n Hn,
    -- Now we want to rewrite the goal using lemmas
    -- `div_le_iff' : 0 < b →  (a / b ≤ c ↔ a ≤ b * c)`
    -- `div_le_iff : 0 < b →  (a / b ≤ c ↔ a ≤ c * b)`
    -- the second one will be rewritten from right to left, as indicated by `←`.
    -- Lean will create a side goal for the required positivity assumption that
    -- we don't provide for `div_le_iff'`.
    rw [div_le_iff', ← div_le_iff ε_pos],
    -- We want to replace assumption `Hn` by its real counter-part so that
    -- linarith can find what it needs.
    replace Hn : (N : ℝ) ≤ n, exact_mod_cast Hn,
    linarith,
    -- we are still left with the positivity assumption, but already discussed
    -- how to prove it in the preceding lemma
    exact_mod_cast nat.succ_pos n },
  -- Now we need to prove that sufficient statement.
  -- We want to use that `ℝ` is archimedean. So we start typing
  -- `exact archimedean_` and hit Ctrl-space to see what completion Lean proposes
  -- the lemma `archimedean_iff_nat_le` sounds promising. We select the left to
  -- right implication using `.1`. This a generic lemma for fields equiped with
  -- a linear (ie total) order. We need to provide a proof that `ℝ` is indeed
  -- archimedean. This is done using the `apply_instance` tactic that will be
  -- covered elsewhere.
  exact archimedean_iff_nat_le.1 (by apply_instance) (1/ε),
end

/-
We can now put all pieces together, with almost no new things to explain.
-/

lemma inf_seq (A : set ℝ) (x : ℝ) :
  (x is_an_inf_of A) ↔ (x ∈ low_bounds A ∧ ∃ u : ℕ → ℝ, limit u x ∧ ∀ n, u n ∈ A ) :=
begin
  split,
  { intro h,
    split,
    { exact h.1 },
    -- On the next line, we don't need to tell Lean to treat `n+1` as a real number because
    -- we add `x` to it, so Lean knows there is only one way to make sense of this expression.
    have key : ∀ n : ℕ, ∃ a ∈ A, a < x + 1/(n+1),
    { intro n,
      -- we can use the lemma we proved above
      apply inf_lt h,
      -- and another one we proved!
      have : 0 < 1/(n+1 : ℝ), from inv_succ_pos n,
      linarith },
    -- Now we need to use axiom of (countable) choice
    choose u hu using key,
    use u,
    split,
    { intros ε ε_pos,
      -- again we use a lemma we proved, specializing it to our fixed `ε`, and fixing a `N`
      cases limit_inv_succ ε ε_pos with N H,
      use N,
      intros n hn,
      have : x ≤ u n, from h.1 _ (hu n).1,
      have := calc
        u n < x + 1/(n + 1) : (hu n).2
        ... ≤ x + ε         : add_le_add (le_refl x) (H n hn),
      rw abs_of_nonneg ; linarith },
    { intro n,
      exact (hu n).1 } },
  { intro h,
    -- Assumption `h` is made of nested compound statements. We can use the
    -- recursive version of `cases` to unpack it in one go.
    rcases h with ⟨x_min, u, lim, huA⟩,
    split,
    exact x_min,
    intros y y_mino,
    apply le_lim lim,
    intro n,
    exact y_mino (u n) (huA n) },
end


import MyNat.Definition
import MyNat.Addition
import AdvancedAdditionWorld.Level1 -- succ_inj
namespace MyNat
open MyNat
/-!

# Advanced Addition World

## Level 2: `succ_succ_inj`

In the below theorem, we need to apply `succ_inj` twice. Once to prove
`succ (succ a) = succ (succ b) ⟹ succ a = succ b`, and then again to prove `succ a = succ b ⟹ a = b`.
However `succ a = succ b` is nowhere to be found, it's neither an assumption or a goal when we start
this level. You can make it with `have` or you can use `apply`.

## Theorem : succ_succ_inj

For all naturals `a` and `b`, if we assume `succ (succ a) = succ (succ b)`, then we can
deduce `a = b`.
-/
theorem succ_succ_inj {a b : MyNat} (h : succ (succ a) = succ (succ b)) :  a = b := by
  apply succ_inj
  apply succ_inj
  exact h

/-!
## Other solutions

Make sure you understand them all. And remember that `rw` should not be used
with `succ_inj` -- `rw` works only with equalities or `↔` statements,
not implications or functions.

-/

example {a b : MyNat} (h : succ (succ a) = succ (succ b)) :  a = b := by
  apply succ_inj
  exact succ_inj h

example {a b : MyNat} (h : succ (succ a) = succ (succ b)) :  a = b := by
  exact succ_inj (succ_inj h)

/-!
Next up [Level 3](./Level3.lean.md)
-/


import .lovelib

/-! # LoVe Homework 5: Inductive Predicates

Homework must be done individually. -/

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

/-! ## Question 1 (3 points): A Type of λ-Terms

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

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

/-! 1.1 (1 point). Define an inductive predicate `is_app` that returns `true` if
its argument is of the form `term.app …` and that returns false otherwise. -/

inductive is_app : term → Prop
| app (t u : term) : is_app (term.app t u)

/-! 1.2 (2 points). Define an inductive predicate `is_abs_free` that is true if
and only if its argument is a λ-term that contains no λ-expressions. -/

inductive is_abs_free : term → Prop
| var (s : string) : is_abs_free (term.var s)
| app (t u : term) (ht : is_abs_free t) (hu : is_abs_free u) :
  is_abs_free (term.app t u)

/-! ## Question 2 (6 points): Even and Odd

Consider the following inductive definition of even numbers: -/

inductive even : ℕ → Prop
| zero            : even 0
| add_two {n : ℕ} : even n → even (n + 2)

/-! 2.1 (1 point). Define a similar predicate for odd numbers, by completing the
Lean definition below. The definition should distinguish two cases, like `even`,
and should not rely on `even`. -/

inductive odd : ℕ → Prop
| one             : odd 1
| add_two {n : ℕ} : odd n → odd (n + 2)

/-! 2.2 (1 point). Give proof terms for the following propositions, based on
your answer to question 2.1. -/

lemma odd_3 :
  odd 3 :=
odd.add_two odd.one

lemma odd_5 :
  odd 5 :=
odd.add_two odd_3

/-! 2.3 (2 points). Prove the following lemma by rule induction, as a "paper"
proof. This is a good exercise to develop a deeper understanding of how rule
induction works (and is good practice for the final exam).

    lemma even_odd {n : ℕ} (heven : even n) :
      odd (n + 1) :=

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 `linarith`
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 rule induction on `heven`.

Case `even.zero`: The goal is `odd (0 + 1)`. Trivial using the introduction rule
`odd.one` and exploiting the arithmetic fact that `0 + 1 = 1`.

Case `even.add_two`: The goal is `odd ((m + 2) + 1)`. A hypothesis `even m` is
available. The corresponding induction hypothesis is `odd (m + 1)`. The goal can
easily be proved using the introduction rule `odd.add_two` on the induction
hypothesis. QED -/

/-! 2.4 (1 point). Prove the same lemma again, but this time in Lean: -/

lemma even_odd {n : ℕ} (heven : even n) :
  odd (n + 1) :=
begin
  induction' heven,
  case zero {
    exact odd.one },
  case add_two : m hevenm ih {
    exact odd.add_two ih }
end

/-! 2.5 (1 point). Prove the following lemma in Lean.

Hint: Recall that `¬ a` is defined as `a → false`. -/

lemma even_not_odd {n : ℕ} (heven : even n) :
  ¬ odd n :=
begin
  intro hodd,
  induction' heven,
  case zero {
    cases' hodd },
  case add_two : m hevenm ih {
    apply ih,
    cases' hodd,
    assumption }
end

end LoVe


import tactic
open classical

section

parameters {A : Type} {R : A → A → Prop}

/- Defininig Complete Relations -/
def complete (R : A → A → Prop) : Prop :=
∀ x y, R x y ∨ R y x

/- Defininig Incomplete Relations -/
def incomplete (R : A → A → Prop) : Prop :=
∃ x y, ¬ (R x y ∨ R y x)

/- Defininig S the Strict Preference Relation-/
def S (a b : A) : Prop := R a b ∧ ¬ R b a

/- Defininig the Indifference Relation-/
def I (a b : A) : Prop := R a b ∧ R b a

/-MWG Proposition 1.B.1:-/

/-≻ is irreflexive-/
theorem S_Irreflexive (compR : complete R) (trnsR : transitive R)(x : A): ¬ S x x :=
begin
rw S, tauto,
end

/-If x≻y≿z then x≻z -/
theorem StrictWeakTransitivity (compR : complete R) (trnsR : transitive R)(x : A)(y : A)(z : A): (S x y ∧ R y z) → S x z :=
begin
intro SxyandRyz, cases SxyandRyz, rename [SxyandRyz_left Sxy, SxyandRyz_right Ryz], cases Sxy, rw [S], split,
{rename[Sxy_left Rxy, Sxy_right nRyx], have Rxz : R x z, from trnsR Rxy Ryz, assumption,},
{rename[Sxy_left Rxy, Sxy_right nRyx], by_contra Rzx, have Ryx : R y x, from trnsR Ryz Rzx, tauto,}
end

/-≻ is transitive-/
theorem S_Transitive (compR : complete R) (trnsR : transitive R)(x : A)(y : A)(z : A): (S x y ∧ S y z) → S x z :=
begin
intro SxyandSyx, cases SxyandSyx, rename[SxyandSyx_left Sxy, SxyandSyx_right Syz], rw [S] at Syz, cases Syz,
rename[Syz_left Ryz, Syz_right nRzy], exact StrictWeakTransitivity compR trnsR x y z (and.intro Sxy Ryz),
end

/-∼ is reflexive.-/
theorem I_Reflexive (compR : complete R) (trnsR : transitive R)(x : A): I x x :=
begin
rw I,
have Rxx : R x x ∨ R x x, from compR x x,
simp at *,
assumption,
end

/-∼ is transitive-/
theorem I_transitive (compR : complete R) (trnsR : transitive R)(x : A)(y : A)(z : A):
(I x y ∧ I y z) → I x z :=
begin
intro IxyandIyz, cases IxyandIyz, rename [IxyandIyz_left Ixy, IxyandIyz_right Iyz], cases Ixy, cases Iyz, rw [I], tauto,
end

/-∼ is symmetric-/
theorem I_Symmetric (compR : complete R) (trnsR : transitive R)(x : A)(y : A): I x y → I y x :=
begin
intro Ixy, cases Ixy, rw [I], tauto,
end

end


/-
Copyright (c) 2021 Yourong Zang. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yourong Zang
-/
import analysis.calculus.conformal.normed_space
import analysis.inner_product_space.conformal_linear_map

/-!
# Conformal maps between inner product spaces

A function between inner product spaces is which has a derivative at `x`
is conformal at `x` iff the derivative preserves inner products up to a scalar multiple.
-/

noncomputable theory

variables {E F : Type*}
variables [normed_add_comm_group E] [normed_add_comm_group F]
variables [inner_product_space ℝ E] [inner_product_space ℝ F]

open_locale real_inner_product_space

/-- A real differentiable map `f` is conformal at point `x` if and only if its
    differential `fderiv ℝ f x` at that point scales every inner product by a positive scalar. -/
lemma conformal_at_iff' {f : E → F} {x : E} :
  conformal_at f x ↔
  ∃ (c : ℝ), 0 < c ∧ ∀ (u v : E), ⟪fderiv ℝ f x u, fderiv ℝ f x v⟫ = c * ⟪u, v⟫ :=
by rw [conformal_at_iff_is_conformal_map_fderiv, is_conformal_map_iff]

/-- A real differentiable map `f` is conformal at point `x` if and only if its
    differential `f'` at that point scales every inner product by a positive scalar. -/
lemma conformal_at_iff {f : E → F} {x : E} {f' : E →L[ℝ] F} (h : has_fderiv_at f f' x) :
  conformal_at f x ↔ ∃ (c : ℝ), 0 < c ∧ ∀ (u v : E), ⟪f' u, f' v⟫ = c * ⟪u, v⟫ :=
by simp only [conformal_at_iff', h.fderiv]

/-- The conformal factor of a conformal map at some point `x`. Some authors refer to this function
    as the characteristic function of the conformal map. -/
def conformal_factor_at {f : E → F} {x : E} (h : conformal_at f x) : ℝ :=
classical.some (conformal_at_iff'.mp h)

lemma conformal_factor_at_pos {f : E → F} {x : E} (h : conformal_at f x) :
  0 < conformal_factor_at h :=
(classical.some_spec $ conformal_at_iff'.mp h).1

lemma conformal_factor_at_inner_eq_mul_inner' {f : E → F} {x : E}
  (h : conformal_at f x) (u v : E) :
  ⟪(fderiv ℝ f x) u, (fderiv ℝ f x) v⟫ = (conformal_factor_at h : ℝ) * ⟪u, v⟫ :=
(classical.some_spec $ conformal_at_iff'.mp h).2 u v

lemma conformal_factor_at_inner_eq_mul_inner {f : E → F} {x : E} {f' : E →L[ℝ] F}
  (h : has_fderiv_at f f' x) (H : conformal_at f x) (u v : E) :
  ⟪f' u, f' v⟫ = (conformal_factor_at H : ℝ) * ⟪u, v⟫ :=
(H.differentiable_at.has_fderiv_at.unique h) ▸ conformal_factor_at_inner_eq_mul_inner' H u v


/-

This exam focuses on assessing your
ability to write and prove propositions
in predicate logic, with an emphasis on
predicates, disjunctions, and existentially
quantified propositions. There are three
parts:

A: Predicates [20 points in 4 parts]
B: Disjuctions [40 points in 2 parts]
C. Existentials [40 point in 2 parts]
-/

/- ******************************** -/
/- *** A. PREDICATES [20 points] ***-/
/- ******************************** -/

/-
1a. Define a function called isOdd that
takes an argument, n : ℕ, and returns a
proposition that asserts that n is odd.
The function will thus be a predicate on
values of type ℕ. Hint: a number is odd
if it's one more than an even number.
-/

/-
1b. To test your predicate, use "example"
to write and prove isOdd(15).
-/

/-
1c. Define isSmall : ℕ → Prop, to be a
predicate that is true exactly when the
argument, n, is such that n = 0 ∨ n = 1
∨ n = 2 ∨ n = 3 ∨ n = 4 ∨ n = 5. (Don't
try to rewrite this proposition as an
inequality; just use it as is.)
-/

/-
1d. Define a predicate, isBoth(n:ℕ) that
is true exacly when n satisfies both the
isOdd and isSmall predicates. Use isOdd
and isSmall in your answer.
-/

/- ******************* -/
/- *** DISJUNCTIONS ***-/
/- ******************* -/

/-
2. [20 Points]

Jane walks to school or she carries
her lunch. In either case, she uses
a bag. If she walks, she uses a bag for
her books. If she carries her lunch, she
uses a bag to carry her food. So if she
walks, she uses a bag, and if she carries
her lunch, she uses a bag. From the fact
that she walks or carries her lunch, and
from the added facts that in either case
she uses a bag, we can conclude that she
uses a bag.

Using Walks, Carries, and Bag as names
of propositions, write a Lean example
that asserts the following proposition;
then prove it.

If Walks, Carries, and Bag are
propositions, then if (Walks ∨ Carries)
is true, and then if (Walks implies Bag)
is true, and then if (Carries implies Bag)
is true, then Bag is true.

Here's a start.

example : ∀ (Walks Carries Bag : Prop), ...
-/

/-
3. [20 Points]

Prove the following proposition.
-/

example :
    ∀ (P Q R : Prop),
    (P ∧ Q) → (Q ∨ R) → (P ∨ R) :=
begin
_
end

/- *********************** -/
/- *** C. EXISTENTIALS  ***-/
/- *********************** -/

/-
4. [20 points]

Referring to the isBoth predicate you
defined in question #1, state and prove
the proposition that there *exists* a
number, n, that satisfies isBoth. Remember
that you can use the unfold tactic to
expand the name of a predicate in a goal.
Use "example" to state the proposition.
-/

/-
5. [20 points]

Suppose that Heavy and Round are
predicates on values of any type, T.
Prove the proposition that if every
t : T is Heavy (satisfies the Heavy
predicate) and if there exists some
t : T that is Round (satisfies the
Round predicate) then there exists
some t : T is both Heavy and Round
(satisfies the conjunction of the
two properties).
-/

example :
∀ T : Type, ∀ (Heavy Round : T → Prop),
(∀ t, Heavy t) → (∃ t, Round t) →
(∃ t, Heavy t ∧ Round t) :=
begin
_
end


-- Math 52: Week 4

import .utils.int_refl

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

-- Lakins Definition 2.1.1: Let a, b ∈ ℤ.
-- a divides b if there exists n ∈ ℤ such that b = an.
-- We write a ∣ b for "a divides b" and say that a is
-- a divisor of b.
definition divides (a b : ℤ) : Prop := ∃ (n : ℤ), a * n = b

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

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

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

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

-- Lakins Exercise 2.1.1: Let a,b, and c be integers.
-- For all integers m and n, if a ∣ b and a ∣ c, then
-- a ∣ (bm + cn).
theorem L211 : ∀ (a b c m n : ℤ), a ∣ b ∧ a ∣ c → a ∣ (b * m + c * n) :=
begin
sorry
end

-- Theorem: For every integer a, a is even if and only if 2 ∣ a.
theorem is_even_iff_two_divides : ∀ (a : ℤ), is_even a ↔ 2 ∣ a :=
begin
sorry
end

-- We will prove this fact later after we discuss induction.
-- For now we take it as an axiom, i.e., as statement that we
-- take as true without proof.
axiom even_or_odd (a : ℤ) : is_even a ∨ is_odd a

-- Lakins Theorem 2.1.9: For all integers a, a(a + 1) is even.
theorem even_product : ∀ (a : ℤ), is_even (a * (a + 1)) :=
begin
sorry
end
-- Proof: Let a ∈ ℤ. We show that a(a + 1) is even by
-- considering two cases.
--
-- Case I: a is even.
-- Then 2 ∣ a, by Definition 1.2.1. Since a ∣ a(a + 1)
-- by Definition 2.1.1, we have that 2 ∣ a(a + 1) since
-- the divisibility relation is transitive (Proposition
-- 2.1.3). Hence a(a + 1) is even.
--
-- Case II: a is not even.
-- Since a is not even, we know that a is odd. Then
-- a+1 is even by Exercise 1.2.2b. Then, using an
-- argument similar to that of Case I, we have that
-- 2 ∣ (a+1) and (a+1) ∣ a(a+1), and hence 2 ∣ a(a+1)
-- by Proposition 2.1.3. Thus a(a + 1) is even.
--
-- Hence, since we have considered all possible cases
-- for the integer a, we have proved that for all
-- integers a, a(a + 1) is even. 􏰟


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

import tactic -- imports all the Lean tactics
/-!

# Subgroups

[note: the questions start on line 125 or so! ]

Let's go back to Lean's definition of a group instead of our own.
The reason for this is that Lean's groups API has many many lemmas
about groups.

Let's define our own subgroups though. We start with all the basic
API needed to even get going.

What you need to know: `set G` is the type of subsets of `G`.
A subgroup of G is a subset of G which is closed under the group
structure (i.e. contains `1` and is closed under `*` and `⁻¹` ).
Here's how we say this in Lean
-/

/-- `mysubgroup G` is the type of subgroups of a group `G`. -/
structure mysubgroup (G : Type) [group G] :=
(carrier : set G) -- `carrier` is the sub*set* of `G` which underlies the subgroup.
-- now the axioms for a subgroup
(one_mem' : (1 : G) ∈ carrier)
(mul_mem' {x y} : x ∈ carrier → y ∈ carrier → x * y ∈ carrier)
(inv_mem' {x} : x ∈ carrier → x⁻¹ ∈ carrier)

/-

These axioms look a little ugly because they're constantly going on
about `carrier`: the subset corresponding to the subgroup. We'll fix
this in the 40 or so lines below. You don't have to worry about these;
this is all the set-up you need to make the definition usable and make
notation like `g ∈ H` work for `H : mysubgroup G`.

## Extensionality

Two subgroups are equal iff they have the same elements!
This lemma is proved in a "formulatic" way (it's true for
subgroups, subrings, subfields etc, with the same proof)
and the wonderful people at mathlib central wrote some code which
generates the proof automatically. You run it by tagging
`mysubgroup` with the `@[ext]` attribute:

-/

attribute [ext] mysubgroup

-- we now have theorems `my_subgroup.ext` and `my_subgroup.ext_iff`,
-- plus the `ext` tactic works on subgroups and reduces goals of
-- the form `H₁ = H₂` to `∀ g, g ∈ H₁ ↔ g ∈ H₂`

-- The next 30 lines are also boilerplate. You can skip them
namespace mysubgroup

-- let G be a group and let G be a subgroup
variables {G : Type} [group G] (H : mysubgroup G)

#check G -- G is a type. Type of G is `Type` which is a universe
#check H -- H is a term! Type of H is `subgroup G` which is a type
variables (a b c : G)

-- This line makes `g ∈ H` make sense; it says "`g ∈ H` is defined
-- to mean that `g` is in the underlying carrier set"
instance : has_mem G (mysubgroup G) :=
{ mem := λ a H, a ∈ H.carrier }

-- This line means that if `H : subgroup G` and the user suddenly starts
-- talking about `H : set G` without warning, then this just means
-- `H.carrier` again -- the underlying set.
instance : has_coe (mysubgroup G) (set G) :=
{ coe := λ H, H.carrier }

/-- `g` is in `H` considered as a subset of `G`, iff `g` is in `H` considered
as subgroup of `G`. -/
@[simp] lemma mem_coe {g : G} : g ∈ (H : set G) ↔ g ∈ H :=
begin
  -- These two concepts just mean the same thing
  refl
end

-- We now start reformulating the axioms without ever mentioning "carrier".
theorem one_mem : (1 : G) ∈ H :=
begin
  apply H.one_mem',
end

/-- A subgroup is closed under multiplication. -/
theorem mul_mem {x y : G} : x ∈ H → y ∈ H → x * y ∈ H :=
begin
  apply H.mul_mem',
end

/-- A subgroup is closed under inverse -/
theorem inv_mem {x : G} : x ∈ H → x⁻¹ ∈ H :=
begin
  apply H.inv_mem',
end

/-

Basic boilerplate code is now over.

So here are the three theorems which you need to remember about subgroups.
Say `H : mysubgroup G`, and `x` and `y` are terms of type `G`
(i.e. elements of `G`). Then

`H.one_mem : (1 : G) ∈ H`
`H.mul_mem : x ∈ H → y ∈ H → x * y ∈ H`
`H.inv_mem : x ∈ H → x⁻¹ ∈ H`

These now look like the way a mathematician would write things.

Now let's start to prove basic theorems about subgroups (or, as a the computer
scientists would say, make a basic _interface_ or _API_ for subgroups),
using this sensible notation.

Here's an example; let's prove `x ∈ H ↔ x⁻¹ ∈ H`. Let's put the more
complicated expression on the left hand side of the `↔` though, because then
we can make it a `simp` lemma.

-/

@[simp] theorem inv_mem_iff {x : G} : x⁻¹ ∈ H ↔ x ∈ H :=
begin
  split,
  { intro h,
    convert H.inv_mem h,
    rw inv_inv }, -- found using `squeeze_simp`
  { exact H.inv_mem }
end

-- We could prove a bunch more theorems here. Let's just do two more.

theorem mul_mem_cancel_left {x y : G} (hx : x ∈ H) :
  x * y ∈ H ↔ y ∈ H :=
begin
  split,
  { intro hxy,
    have hxi := H.inv_mem hx,
    have h := H.mul_mem hxi hxy,
    simpa using h },
  { exact H.mul_mem hx }
end

theorem mul_mem_cancel_right {x y : G} (hx : x ∈ H) :
  y * x ∈ H ↔ y ∈ H :=
begin
  split,
  { replace hx := H.inv_mem hx,
    intro h,
    convert H.mul_mem h hx,
    simp },
  { intro hy,
    exact H.mul_mem hy hx }
end

def is_abelian (G : Type) [group G] : Prop :=
∀ a b : G, a * b = b * a

def conjugate (H : mysubgroup G) (g : G) : mysubgroup G :=
{ carrier := { a : G | ∃ h ∈ H, a = g * h * g⁻¹ },
  one_mem' := ⟨1, by simp [H.one_mem]⟩,
  mul_mem' := begin
    rintro - - ⟨a, ha, rfl⟩ ⟨b, hb, rfl⟩,
    refine ⟨a * b, H.mul_mem ha hb, by group⟩, -- life much easier with the `group` tactic!
  end,
  inv_mem' := begin
    rintro - ⟨a, ha, rfl⟩,
    refine ⟨a⁻¹, H.inv_mem ha, by group⟩,
  end }

def is_normal {G : Type} [group G] (H : mysubgroup G) : Prop :=
∀ g : G, conjugate H g = H

@[ext] def ext' (H K : mysubgroup G) (h : ∀ g : G, g ∈ H ↔ g ∈ K) : H = K :=
begin
  ext x,
  exact h x,
end

example (h_ab : is_abelian G) (H : mysubgroup G) : is_normal H :=
begin
  intro g,
  ext x,
  split,
  { rintro ⟨a, ha, rfl⟩,
    rw h_ab g a,
    simpa using ha },
  { intro h,
    use g⁻¹ * x * g,
    split,
    { rw h_ab,
      simpa using h },
    { group } }
end

end mysubgroup


-- Suma_de_los_primeros_cubos.lean
-- Suma de los primeros cubos
-- José A. Alonso Jiménez
-- Sevilla, 22 de septiembre de 2021
-- ---------------------------------------------------------------------

-- ---------------------------------------------------------------------
-- Demostrar que la suma de los primeros cubos
--    0³ + 1³ + 2³ + 3³ + ··· + n³
-- es (n(n+1)/2)²
-- ---------------------------------------------------------------------

import data.nat.basic
import tactic
open nat

variable (n : ℕ)

set_option pp.structure_projections false

@[simp]
def sumaCubos : ℕ → ℕ
| 0     := 0
| (n+1) := sumaCubos n + (n+1)^3

-- 1ª demostración
example :
  4 * sumaCubos n = (n*(n+1))^2 :=
begin
  induction n with n HI,
  { simp, },
  { calc 4 * sumaCubos (succ n)
         = 4 * (sumaCubos n + (n+1)^3)
           : by simp
     ... = 4 * sumaCubos n + 4*(n+1)^3
           : by ring
     ... = (n*(n+1))^2 + 4*(n+1)^3
           : by {congr; rw HI}
     ... = (n+1)^2*(n^2+4*n+4)
           : by ring
     ... = (n+1)^2*(n+2)^2
           : by ring
     ... = ((n+1)*(n+2))^2
           : by ring
     ... = (succ n * (succ n + 1)) ^ 2
           : by simp, },
end

-- 2ª demostración
example :
  4 * sumaCubos n = (n*(n+1))^2 :=
begin
  induction n with n HI,
  { simp, },
  { calc 4 * sumaCubos (succ n)
         = 4 * sumaCubos n + 4*(n+1)^3
           : by {simp ; ring}
     ... = (n*(n+1))^2 + 4*(n+1)^3
           : by {congr; rw HI}
     ... = ((n+1)*(n+2))^2
           : by ring
     ... = (succ n * (succ n + 1)) ^ 2
           : by simp, },
end

-- Referencia:
-- ¿Qué es la Matemática? https://bit.ly/3lrPKAp de R. Courant y
-- H. Robbins p. 22


-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que
--    gcd m n = gcd n m
-- ----------------------------------------------------------------------

import data.nat.gcd

open nat

variables k m n : ℕ

-- 1ª demostración
-- ===============

example : gcd m n = gcd n m :=
begin
  have h1 : gcd m n ∣ gcd n m,
    { have h1a : gcd m n ∣ n,
        by exact gcd_dvd_right m n,
      have h1b : gcd m n ∣ m,
        by exact gcd_dvd_left m n,
      show gcd m n ∣ gcd n m,
        by exact dvd_gcd h1a h1b, },
  have h2 : gcd n m ∣ gcd m n,
    { have h2a : gcd n m ∣ m,
        by exact gcd_dvd_right n m,
      have h2b : gcd n m ∣ n,
        by exact gcd_dvd_left n m,
      show gcd n m ∣ gcd m n,
        by exact dvd_gcd h2a h2b, },
  show gcd m n = gcd n m,
    by exact dvd_antisymm h1 h2,
end

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

example : gcd m n = gcd n m :=
begin
  apply dvd_antisymm,
  { apply dvd_gcd,
    { exact gcd_dvd_right m n, },
    { exact gcd_dvd_left m n, }},
  { apply dvd_gcd,
    { exact gcd_dvd_right n m, },
    { exact gcd_dvd_left n m, }},
end

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

example : gcd m n = gcd n m :=
begin
  apply dvd_antisymm,
  { apply dvd_gcd (gcd_dvd_right m n) (gcd_dvd_left m n)},
  { apply dvd_gcd (gcd_dvd_right n m) (gcd_dvd_left n m)},
end

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

example : 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))

-- 5ª demostración
-- ===============

lemma aux : gcd m n ∣ gcd n m :=
begin
  have h1 : gcd m n ∣ n,
    by exact gcd_dvd_right m n,
  have h2 : gcd m n ∣ m,
    by exact gcd_dvd_left m n,
  show gcd m n ∣ gcd n m,
    by exact dvd_gcd h1 h2,
end

example : gcd m n = gcd n m :=
begin
  apply dvd_antisymm,
  { exact aux m n, },
  { exact aux n m, },
end

-- 6ª demostración
-- ===============

lemma aux2 : gcd m n ∣ gcd n m :=
dvd_gcd (gcd_dvd_right m n) (gcd_dvd_left m n)

example : gcd m n = gcd n m :=
dvd_antisymm (aux2 m n) (aux2 n m)

-- 7ª demostración
-- ===============

example : gcd m n = gcd n m :=
-- by library_search
gcd_comm m n

-- Lemas usados
-- ============

-- #check (dvd_antisymm : m ∣ n → n ∣ m → m = n)
-- #check (dvd_gcd : k ∣ m → k ∣ n → k ∣ gcd m n)
-- #check (gcd_dvd_left : ∀ (m n : ℕ), gcd m n ∣ m)
-- #check (gcd_dvd_right : ∀ (m n : ℕ), gcd m n ∣ n)


/-
Copyright (c) 2021 Alain Verberkmoes. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Alain Verberkmoes
-/

import data.int.basic

/-!
# IMO 2011 Q5

Let `f` be a function from the set of integers to the set
of positive integers.  Suppose that, for any two integers
`m` and `n`, the difference `f(m) - f(n)` is divisible by
`f(m - n)`.  Prove that, for all integers `m` and `n` with
`f(m) ≤ f(n)`, the number `f(n)` is divisible by `f(m)`.
-/

open int

theorem imo2011_q5 (f : ℤ → ℤ) (hpos : ∀ n : ℤ, 0 < f n)
  (hdvd : ∀ m n : ℤ, f (m - n) ∣ f m - f n) :
∀ m n : ℤ, f m ≤ f n → f m ∣ f n :=
begin
  intros m n h_fm_le_fn,
  cases lt_or_eq_of_le h_fm_le_fn with h_fm_lt_fn h_fm_eq_fn,
  { -- m < n
    let d := f m - f (m - n),
    have h_fn_dvd_d : f n ∣ d,
    { rw ←sub_sub_self m n,
      exact hdvd m (m - n) },
    have h_d_lt_fn : d < f n,
    { calc d < f m : sub_lt_self _ (hpos (m - n))
        ... < f n : h_fm_lt_fn },
    have h_neg_d_lt_fn : -d < f n,
    { calc -d = f (m - n) - f m : neg_sub _ _
        ... < f (m - n) : sub_lt_self _ (hpos m)
        ... ≤ f n - f m : le_of_dvd (sub_pos.mpr h_fm_lt_fn) _
        ... < f n : sub_lt_self _ (hpos m),
      -- ⊢ f (m - n) ∣ f n - f m
      rw [←dvd_neg, neg_sub], exact hdvd m n },
    have h_d_eq_zero : d = 0,
    { obtain (hd | hd | hd) : d > 0 ∨ d = 0 ∨ d < 0 := trichotomous d 0,
      { -- d > 0
        have h₁ : f n ≤ d, from le_of_dvd hd h_fn_dvd_d,
        have h₂ : ¬ f n ≤ d, from not_le.mpr h_d_lt_fn,
        contradiction },
      { -- d = 0
        exact hd },
      { -- d < 0
        have h₁ : f n ≤ -d, from le_of_dvd (neg_pos.mpr hd) ((dvd_neg _ _).mpr h_fn_dvd_d),
        have h₂ : ¬ f n ≤ -d, from not_le.mpr h_neg_d_lt_fn,
        contradiction } },
    have h₁ : f m = f (m - n), from sub_eq_zero.mp h_d_eq_zero,
    have h₂ : f (m - n) ∣ f m - f n, from hdvd m n,
    rw ←h₁ at h₂,
    exact (dvd_iff_dvd_of_dvd_sub h₂).mp dvd_rfl },
  { -- m = n
    rw h_fm_eq_fn }
end


-- ---------------------------------------------------------------------
-- Ejercicio 1. Realizar las siguientes acciones:
--    1. Importar la teoría de anillos.
--    2. Crear el espacio de nombres my_ring
--    3. Declarar R como una variable sobre anillos.
--    4. Declarar a como variable sobre R.
-- ----------------------------------------------------------------------

import algebra.ring            -- 1
namespace my_ring              -- 2
variables {R : Type*} [ring R] -- 3
variables (a : R)              -- 4

-- ---------------------------------------------------------------------
-- Ejercicio 2. Demostrar que
--    1 + 1 = 2
-- ----------------------------------------------------------------------

lemma one_add_one_eq_two : 1 + 1 = (2 : R) :=
by refl

-- ---------------------------------------------------------------------
-- Ejercicio 3. Demostrar que
--    2 * a = a + a
-- ----------------------------------------------------------------------

theorem two_mul : 2 * a = a + a :=
calc
  2 * a = (1 + 1) * a   : by rw one_add_one_eq_two
  ...   = 1 * a + 1 * a : by rw add_mul
  ...   = a + a         : by rw one_mul

end my_ring


section propositional

variables P Q R : Prop

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

theorem doubleneg_intro :
  P → ¬¬P  :=
begin
  intro p,
  intro np,
  apply np,
  exact p,
end

theorem doubleneg_elim :
  ¬¬P → P  :=
begin
  intro nnp,
  --by_cases p : P, -- other solution
  --exact p,        -- other solution
  --exfalso,        -- other solution
  --contradiction,  -- other solution
  by_contradiction nnpboom, -- RAA (Reductio Ad Absurdum)
  contradiction,
end

theorem doubleneg_law :
  ¬¬P ↔ P  :=
begin
  split,
    intro nnp,
    by_contradiction nnpboom, -- RAA (Reductio Ad Absurdum)
    contradiction,

    intro p,
    intro np,
    contradiction,
end

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

theorem disj_comm :
  (P ∨ Q) → (Q ∨ P)  :=
begin
  intro f1,
  cases f1 with p q,
  right,
  exact p,
  left,
  exact q,
end

theorem conj_comm :
  (P ∧ Q) → (Q ∧ P)  :=
begin
  intro f1,
  cases f1 with p q,
  split,
  exact q,
  exact p,
end

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

theorem impl_as_disj_converse :
  (¬P ∨ Q) → (P → Q)  :=
begin
  intro f1,
  cases f1 with np q,

  intro p,
  exfalso,
  apply np,
  exact p,

  intro p,
  exact q,
end

theorem disj_as_impl :
  (P ∨ Q) → (¬P → Q)  :=
begin
  intro f1,
  cases f1 with p q,

  intro np,
  exfalso,
  apply np,
  exact p,

  intro np,
  exact q,

end

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

theorem impl_as_contrapositive :
  (P → Q) → (¬Q → ¬P)  :=
begin
  by_cases p : P; by_cases q : Q,

  intro f1,
  intro nq,
  exfalso,
  apply nq,
  exact q,

  intro f2,
  intro nq,
  exfalso,
  apply nq,
  apply f2,
  exact p,

  intro f3,
  intro nq,
  exact p,

  intro f4,
  intro nq,
  exact p,

end

theorem impl_as_contrapositive_converse :
  (¬Q → ¬P) → (P → Q)  :=
begin
  by_cases p : P; by_cases q : Q,

  intro f1,
  intro p,
  exact q,

  intro f2,
  intro p,
  exfalso,
  apply f2,
  exact q,
  exact p,

  intro f3,
  intro p,
  exact q,

  intro f4,
  intro p2,
  exfalso,
  apply p,
  exact p2,

end

theorem contrapositive_law :
  (P → Q) ↔ (¬Q → ¬P)  :=
begin
  split,

    intro f1,
    intro nq,
    intro p,
    apply nq,
    apply f1,
    exact p,

    by_cases p : P; by_cases q : Q,

    intro f2,
    intro p,
    exact q,

    intro f3,
    intro p1,
    exfalso,
    apply f3,
    exact q,
    exact p,

    intro f4,
    intro p2,
    exact q,

    intro f5,
    intro p2,
    exfalso,
    apply p,
    exact p2,

end

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

theorem lem_irrefutable :
  ¬¬(P∨¬P)  :=
begin
  intro f1,
  apply f1,
  right,
  intro p,
  apply f1,
  left,
  exact p,
end

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

theorem peirce_law_weak :
  ((P → Q) → P) → ¬¬P  :=
begin
  by_cases p : P; by_cases q : Q,

  intro f1,
  intro np,
  apply np,
  exact p,

  intro f2,
  intro np,
  apply np,
  exact p,

  intro f3,
  intro np,
  apply p,
  apply f3,
  intro p2,
  exact q,

  intro f4,
  intro np,
  apply p,
  apply f4,
  intro p2,
  exfalso,
  apply np,
  exact p2,

end

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

theorem disj_as_negconj :
  P∨Q → ¬(¬P∧¬Q)  :=
begin
  intro f1,
  cases f1 with p q,
  intro f2,
  apply f2.1,
  exact p,

  intro f3,
  apply f3.2,
  exact q,
end

theorem conj_as_negdisj :
  P∧Q → ¬(¬P∨¬Q)  :=
begin
  intro f1,
  cases f1 with f2 f3,
  intro f4,
  cases f4 with f5 f6,
  apply f5,
  exact f2,

  apply f6,
  exact f3,
end

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

theorem demorgan_disj :
  ¬(P∨Q) → (¬P ∧ ¬Q)  :=
begin
  intro f1,
  split,
  intro p,
  apply f1,
  left,
  exact p,

  intro q,
  apply f1,
  right,
  exact q,
end

theorem demorgan_disj_converse :
  (¬P ∧ ¬Q) → ¬(P∨Q)  :=
begin
  intro f1,
  intro f2,
  apply f1.1,
  cases f2 with p q,

  exact p,

  exfalso,
  apply f1.2,
  exact q,
end

theorem demorgan_conj :
  ¬(P∧Q) → (¬Q ∨ ¬P)  :=
begin
  by_cases p : P; by_cases np : ¬P,

  intro f1,
  right,
  exact np,

  intro f2,
  left,
  intro q,
  apply f2,
  split,
    exact p,
    exact q,

  intro f3,
  right,
  exact np,

  intro f4,
  right,
  exact p,
end

theorem demorgan_conj_converse :
  (¬Q ∨ ¬P) → ¬(P∧Q)  :=
begin
  intro f1,
  cases f1 with nq np,
  intro f2,
  cases f2 with p q,
  apply nq,
  exact q,

  intro f3,
  cases f3 with p q,
  apply np,
  exact p,
end

theorem demorgan_conj_law :
  ¬(P∧Q) ↔ (¬Q ∨ ¬P)  :=
begin
  by_cases p : P; by_cases np : ¬P,

  split,
    intro f1,
    right,
    exact np,

  intro f2,
  intro f3,
  apply np,
  exact p,

  split,
    intro f4,
    left,
    intro q,
    apply f4,
    split,
      exact p,
      exact q,

    intro f5,
    cases f5 with nq np,
    intro f6,
    apply nq,
    exact f6.2,

    intro f7,
    apply np,
    exact p,

    split,
      intro f8,
      right,
      exact np,

      intro f9,
      intro f10,
      apply np,
      exact f10.1,

    split,
      intro f11,
      right,
      exact p,

      intro f12,
      intro f13,
      apply np,
      exact p,

end

theorem demorgan_disj_law :
  ¬(P∨Q) ↔ (¬P ∧ ¬Q)  :=
begin
  split,
    intro f1,
    split,
      intro p,
      apply f1,
      left,
      exact p,

      intro q,
      apply f1,
      right,
      exact q,

    intro f2,
    cases f2 with np nq,
    intro f3,
    cases f3 with p q,
    apply np,
    exact p,
    apply nq,
    exact q,

end

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

theorem distr_conj_disj :
  P∧(Q∨R) → (P∧Q)∨(P∧R)  :=
begin
  intro f1,
  cases f1 with p f2,
  cases f2 with q r,
  left,
  split,
    exact p,
    exact q,

  right,
  split,
    exact p,
    exact r,
end

theorem distr_conj_disj_converse :
  (P∧Q)∨(P∧R) → P∧(Q∨R)  :=
begin
  intro f1,
  cases f1 with f2 f3,
  cases f2 with p q,

  split,
    exact p,

    left,
    exact q,

  cases f3 with p r,
  split,
    exact p,

    right,
    exact r,
end

theorem distr_disj_conj :
  P∨(Q∧R) → (P∨Q)∧(P∨R)  :=
begin
  intro f1,
  cases f1 with p f2,

  split,
    left,
    exact p,

    left,
    exact p,

  cases f2 with q r,
  split,
    right,
    exact q,

    right,
    exact r,
end

theorem distr_disj_conj_converse :
  (P∨Q)∧(P∨R) → P∨(Q∧R)  :=
begin
  by_cases p : P; by_cases q : Q, by_cases r : R,

  intro f1,
  left,
  exact p,

  intro f2,
  left,
  exact p,

  intro f3,
  left,
  exact p,

  intro f4,
  right,
  split,
    exact q,

    cases f4 with f5 f6,
    cases f6 with p6 r,
    exfalso,
    apply p,
    exact p6,

  exact r,

  intro f7,
  cases f7 with f8 f9,
  left,
  cases f8 with p10 q10,
  exact p10,

  exfalso,
  apply q,
  exact q10,

end

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

theorem curry_prop :
  ((P∧Q)→R) → (P→(Q→R))  :=
begin
  intro f1,
  intro p,
  intro q,

  have p_and_q : P∧Q,

  split,
    exact p,

    exact q,

  exact f1 p_and_q, -- Usando dados on the fly

end

theorem uncurry_prop :
  (P→(Q→R)) → ((P∧Q)→R)  :=
begin
  intro f1,
  intro f2,
  cases f2 with p q,

  exact f1 p q, -- Usando dados on the fly

end

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

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

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

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

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

theorem weaken_conj_right :
  (P∧Q) → P  :=
begin
  intro f1,
  cases f1 with p q,
  exact p,
end

theorem weaken_conj_left :
  (P∧Q) → Q  :=
begin
  intro f1,
  cases f1 with p q,
  exact q,
end

theorem conj_idempot :
  (P∧P) ↔ P :=
begin
  split,
    intro f1,
    cases f1 with p_left p_right,
    exact p_left,

    intro p,
    split,
      exact p,

      exact p,
end

theorem disj_idempot :
  (P∨P) ↔ P  :=
begin
  split,
    intro f1,
    cases f1 with p_left p_right,
    exact p_left,
    exact p_right,

    intro p,
    left,
    exact p,
end

end propositional

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

section predicate

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

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

theorem demorgan_exists :
  ¬(∃x, P x) → (∀x, ¬P x)  :=
begin
  intro f1,
  intro f2,
  intro f3,
  apply f1,
  existsi f2,
  exact f3,
end

theorem demorgan_exists_converse :
  (∀x, ¬P x) → ¬(∃x, P x)  :=
begin
  intro f1,
  intro f2,
  cases f2 with u f3,
  apply f1,
  exact f3,
end

theorem demorgan_forall :
  ¬(∀x, P x) → (∃x, ¬P x)  :=
begin
  --intro f1,
  --split,
    --intro f2,
    --apply f1,
    --intro f3,
    sorry,
end

theorem demorgan_forall_converse :
  (∃x, ¬P x) → ¬(∀x, P x)  :=
begin
  intro f1,
  intro f2,
  cases f1 with u f3,
  apply f3,
  exact f2 u,
end

theorem demorgan_forall_law :
  ¬(∀x, P x) ↔ (∃x, ¬P x)  :=
begin
  --split,
   -- intro f1,
  --  split,
  --    intro f2,
   --   apply f1,
   --   intro f3,
      sorry,

end

theorem demorgan_exists_law :
  ¬(∃x, P x) ↔ (∀x, ¬P x)  :=
begin
  split,
    intro f1,
    intro f2,
    intro f3,
    apply f1,
    existsi f2,
    exact f3,

    intro f4, -- demorgan_exists
    intro f5,
    cases f5 with u f6,
    apply f4,
    exact f6,

end

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

theorem exists_as_neg_forall :
  (∃x, P x) → ¬(∀x, ¬P x)  :=
begin
  intro f1,
  intro f2,
  cases f1 with f3 f4,
  apply f2,
  exact f4,
end

theorem forall_as_neg_exists :
  (∀x, P x) → ¬(∃x, ¬P x)  :=
begin
  intro f1,
  intro f2,
  cases f2 with f3 f4,
  apply f4,
  apply f1,
end

theorem forall_as_neg_exists_converse :
  ¬(∃x, ¬P x) → (∀x, P x)  :=
begin
  --intro f1,
  --intro f2,
  --by_cases f3 : P f2,
  --exact f3,
  sorry,
end

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

  sorry,

end

theorem forall_as_neg_exists_law :
  (∀x, P x) ↔ ¬(∃x, ¬P x)  :=
begin
  split,
    intro f1,
    intro f2,
    cases f2 with f3 f4,
    apply f4,
    exact f1 f3,

    intro f5,
    intro f6,
    sorry,

end

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

sorry,

end

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

theorem exists_conj_as_conj_exists :
  (∃x, P x ∧ Q x) → (∃x, P x) ∧ (∃x, Q x)  :=
begin
  intro f1,
  cases f1 with f2 f3,
  cases f3 with f4 f5,
  split,
    existsi f2,
    exact f4,

    existsi f2,
    exact f5,
end

theorem exists_disj_as_disj_exists :
  (∃x, P x ∨ Q x) → (∃x, P x) ∨ (∃x, Q x)  :=
begin
  --intro f1,
  --cases f1 with f2 f3,
  --left,
  --split,
    --cases f3 with f4 f5,
    sorry,
end

theorem exists_disj_as_disj_exists_converse :
  (∃x, P x) ∨ (∃x, Q x) → (∃x, P x ∨ Q x)  :=
begin
  intro f1,
  cases f1 with f2 f3,
  cases f2 with f4 f5,
  split,
    left,
    exact f5,

    cases f3 with f6 f7,
    split,
      right,
      exact f7,
end

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

  --intro f1,
  --split,
    --intro f2,
    sorry,

end

theorem forall_conj_as_conj_forall_converse :
  (∀x, P x) ∧ (∀x, Q x) → (∀x, P x ∧ Q x)  :=
begin
  intro f1,
  cases f1 with f2 f3,
  intro f4,
  split,
    exact f2 f4,

    exact f3 f4,
end

theorem forall_disj_as_disj_forall_converse :
  (∀x, P x) ∨ (∀x, Q x) → (∀x, P x ∨ Q x)  :=
begin
  intro f1,
  cases f1 with f2 f3,
  intro f4,
  left,
  exact f2 f4,

  intro f5,
  right,
  exact f3 f5,
end

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

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

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

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

end predicate


/-
  Exercise 1.
    Define the function Do_Twice, as described in Section 2.4.
  Exercise 2.
    Define the functions curry and uncurry, as described in Section 2.4.
-/

def do_twice (f : ℕ → ℕ) (x : ℕ) : ℕ := f (f x)
def double (x : ℕ) : ℕ := x * 2
def quadruple (x : ℕ) : ℕ := do_twice double x
def octuple (x : ℕ) : ℕ := double (do_twice double x)
def Do_Twice (g : (ℕ → ℕ) → ℕ → ℕ) (f : ℕ → ℕ) : ℕ → ℕ := g (g f)

#reduce double 2
#reduce do_twice double 2
#reduce quadruple 2
#reduce octuple 2
#reduce Do_Twice do_twice double 2

#check do_twice
#check Do_Twice
#check Do_Twice do_twice double

-- curry : (α × β) → γ → α → β → γ
-- uncurry : α → β → γ → (α × β) → γ
def curry (α β γ : Type) (f : α × β → γ) : α → β → γ := λ (a : α) (b : β), f (a, b)
def uncurry (α β γ : Type) (f : α → β → γ) : α × β → γ := λ (ab : α × β), f ab.1 ab.2

#check curry
#check uncurry

/-
  ...try to understand why the definition of foo below type checks, but
  the definition of bar does not.
-/

def foo := let a := nat  in λ x : a, x + 2
/-
  def bar := (λ a, (λ x : a, x + 2)) nat
    this does not type check because at check time,
    Lean does not know that the operation '+' is valid
    for type 'a', even though 'a' is later resolved to 'nat'.
-/
def bar2 := (λ a, (λ x : a, x)) nat -- to illustrate, this is ok.
#check foo
#check bar2
-- this means that let bindings are evaluated before type checks.

/-
  Exercise 3.
    Above, we used the example vec α n for vectors of elements of type α of length n.
    Declare a constant vec_add that could represent a function that adds two vectors
    of natural numbers of the same length, and a constant vec_reverse that can represent
    a function that reverses its argument. Use implicit arguments for parameters
    that can be inferred. Declare some variables and check some expressions involving
    the constants that you have declared.
-/

universe u
variable α : Type u
variables m n o : ℕ

constant vec : Type u → ℕ → Type u
constant vec_add : Π {n}, vec ℕ n → vec ℕ n → vec ℕ n
constant vec_reverse : Π {n}, vec ℕ n → vec ℕ n

variable v2 : vec ℕ 2
variable v3 : vec ℕ 3

#check vec_add v2 v2
#check vec_add v3 v3
-- #check vec_add v2 v3 -- error

#check vec_reverse v2
#check vec_reverse v3

/-
  Exercise 4.
    Similarly, declare a constant matrix so that matrix α m n could represent
    the type of m by n matrices. Declare some constants to represent functions on
    this type, such as matrix addition and multiplication, and (using vec) multiplication
    of a matrix by a vector. Once again, declare some variables and check some expressions
    involving the constants that you have declared.
-/

constant matrix : Type u → ℕ → ℕ → Type u
constant mat_add : Π {α m n}, matrix α m n → matrix α m n → matrix α m n
constant mat_mul : Π {α m n o}, matrix α m n → matrix α n o → matrix α m o
constant mat_vec_mul : Π {α m n}, matrix α m n → vec α n → vec α m

variable m35 : matrix ℕ 3 5
variable m57 : matrix ℕ 5 7
variable v5 : vec ℕ 5
variable v7 : vec ℕ 7

#check mat_add m35 m35
-- #check mat_add m35 m57 -- error
#check mat_mul m35 m57
-- #check mat_mul m35 m35 -- error
#check mat_vec_mul m35 v5
-- #check mat_vec_mul m35 v7 -- error


import MyNat.Definition
namespace MyNat
open MyNat

/-!
# Proposition world.

## Level 8 : `(P → Q) → (¬ Q → ¬ P)`

There is a false proposition `false`, with no proof. It is easy to check that `¬ Q` is equivalent to
`Q ⟹ false`, and in this tutorial we call this

`not_iff_imp_false (P : Prop) : ¬ P ↔ (P → false)`

which we can prove here using the `simp` tactic:
-/
theorem not_iff_imp_false (P : Prop) : ¬ P ↔ (P → false) :=
  by simp
/-!
So you can start the proof of the contrapositive below with

`repeat rw [not_iff_imp_false]`

to get rid of the two occurrences of `¬`, and I'm sure you can take it from there. At some point
your goal might be to prove `false`. At that point I guess you must be proving something by
contradiction. Or are you?

## Lemma : contrapositive
If `P` and `Q` are propositions, and `P⟹ Q`, then
`¬ Q⟹ ¬ P`.
-/
lemma contrapositive (P Q : Prop) : (P → Q) → (¬ Q → ¬ P) := by
  repeat rw [not_iff_imp_false]
  intro f
  intro h
  intro p
  apply h
  apply f
  exact p

/-!
## Technical note

All of that rewriting you did with `rw` in addition world was rewriting hypothesis of the form
`h : X = Y`, but you can also `rw [h]` if `h : P ↔ Q` (because propositional extensionality says
that if `P ⟺ Q` then `P = Q`, and mathematicians use this whether or not they notice.)
-/

/-!
Next up [Level 9](./Level9.lean.md)
-/


/-
Copyright (c) 2022 Floris van Doorn. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Floris van Doorn, Patrick Massot
-/
import topology.basic
/-!
# Neighborhoods of a set

In this file we define the filter `𝓝ˢ s` or `nhds_set s` consisting of all neighborhoods of a set
`s`.

## Main Properties

There are a couple different notions equivalent to `s ∈ 𝓝ˢ t`:
* `s ⊆ interior t` using `subset_interior_iff_mem_nhds_set`
* `∀ (x : α), x ∈ t → s ∈ 𝓝 x` using `mem_nhds_set_iff_forall`
* `∃ U : set α, is_open U ∧ t ⊆ U ∧ U ⊆ s` using `mem_nhds_set_iff_exists`

Furthermore, we have the following results:
* `monotone_nhds_set`: `𝓝ˢ` is monotone
* In T₁-spaces, `𝓝ˢ`is strictly monotone and hence injective:
  `strict_mono_nhds_set`/`injective_nhds_set`. These results are in `topology.separation`.
-/

open set filter
open_locale topological_space

variables {α : Type*} [topological_space α] {s t s₁ s₂ t₁ t₂ : set α} {x : α}

/-- The filter of neighborhoods of a set in a topological space. -/
def nhds_set (s : set α) : filter α :=
Sup (nhds '' s)

localized "notation `𝓝ˢ` := nhds_set" in topological_space

lemma mem_nhds_set_iff_forall : s ∈ 𝓝ˢ t ↔ ∀ (x : α), x ∈ t → s ∈ 𝓝 x :=
by simp_rw [nhds_set, filter.mem_Sup, ball_image_iff]

lemma subset_interior_iff_mem_nhds_set : s ⊆ interior t ↔ t ∈ 𝓝ˢ s :=
by simp_rw [mem_nhds_set_iff_forall, subset_interior_iff_nhds]

lemma mem_nhds_set_iff_exists : s ∈ 𝓝ˢ t ↔ ∃ U : set α, is_open U ∧ t ⊆ U ∧ U ⊆ s :=
by { rw [← subset_interior_iff_mem_nhds_set, subset_interior_iff] }

lemma is_open.mem_nhds_set (hU : is_open s) : s ∈ 𝓝ˢ t ↔ t ⊆ s :=
by rw [← subset_interior_iff_mem_nhds_set, interior_eq_iff_open.mpr hU]

@[simp] lemma nhds_set_singleton : 𝓝ˢ {x} = 𝓝 x :=
by { ext,
     rw [← subset_interior_iff_mem_nhds_set, ← mem_interior_iff_mem_nhds, singleton_subset_iff] }

lemma mem_nhds_set_interior : s ∈ 𝓝ˢ (interior s) :=
subset_interior_iff_mem_nhds_set.mp subset.rfl

lemma mem_nhds_set_empty : s ∈ 𝓝ˢ (∅ : set α) :=
subset_interior_iff_mem_nhds_set.mp $ empty_subset _

@[simp] lemma nhds_set_empty : 𝓝ˢ (∅ : set α) = ⊥ :=
by { ext, simp [mem_nhds_set_empty] }

@[simp] lemma nhds_set_univ : 𝓝ˢ (univ : set α) = ⊤ :=
by { ext, rw [← subset_interior_iff_mem_nhds_set, univ_subset_iff, interior_eq_univ, mem_top] }

lemma monotone_nhds_set : monotone (𝓝ˢ : set α → filter α) :=
by { intros s t hst O, simp_rw [← subset_interior_iff_mem_nhds_set], exact subset.trans hst }

lemma union_mem_nhds_set (h₁ : s₁ ∈ 𝓝ˢ t₁) (h₂ : s₂ ∈ 𝓝ˢ t₂) : s₁ ∪ s₂ ∈ 𝓝ˢ (t₁ ∪ t₂) :=
begin
  rw [← subset_interior_iff_mem_nhds_set] at *,
  exact union_subset
    (h₁.trans $ interior_mono $ subset_union_left _ _)
    (h₂.trans $ interior_mono $ subset_union_right _ _)
end


-- ---------------------------------------------------------------------
-- Ejercicio 1. Realizar las siguientes acciones:
-- 1. Importar la librería de los números naturales.
-- 2. Declarar x e y como variables sobre los reales.
-- ----------------------------------------------------------------------

import data.real.basic   -- 1
variables {x y : ℝ}      -- 2

-- ---------------------------------------------------------------------
-- Ejercicio 2. Demostrar que si
--    y > x^2)
-- entonces
--    y > 0 ∨ y < -1
-- ----------------------------------------------------------------------

-- 1ª demostración
-- ===============

example
  (h : y > x^2)
  : y > 0 ∨ y < -1 :=
begin
  left,
  linarith [pow_two_nonneg x],
end

-- Prueba
-- ======

/-
x y : ℝ,
h : y > x ^ 2
⊢ y > 0 ∨ y < -1
  >> left,
⊢ y > 0
  >> linarith [pow_two_nonneg x],
no goals
-/

-- Comentarios:
-- 1. La táctica left, si el objetivo es una disjunción (P ∨ Q), aplica
--    la regla de introducción de la disyunción; es decir, cambia el
--    objetivo por P. Ver https://bit.ly/3enkT3d
-- 2. Se usa el lema
--       pow_two_nonneg x : 0 ≤ x ^ 2

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

example
  (h : y > x^2)
  : y > 0 ∨ y < -1 :=
by { left, linarith [pow_two_nonneg x] }

-- ---------------------------------------------------------------------
-- Ejercicio 3. Demostrar que si
--    -y > x^2 + 1
-- entonces
--    y > 0 ∨ y < -1
-- ----------------------------------------------------------------------

-- 1ª demostración
-- ===============

example
  (h : -y > x^2 + 1)
  : y > 0 ∨ y < -1 :=
begin
  right,
  linarith [pow_two_nonneg x],
end

-- Prueba
-- ======

/-
x y : ℝ,
h : -y > x ^ 2 + 1
⊢ y > 0 ∨ y < -1
  >> right,
⊢ y < -1
  >> linarith [pow_two_nonneg x],
no goals
-/

-- Comentarios:
-- 1. La táctica right, si el objetivo es una disjunción (P ∨ Q), aplica
--    la regla de introducción de la disyunción; es decir, cambia el
--    objetivo por Q. Ver https://bit.ly/3enkT3d
-- 2. Se usa el lema
--       pow_two_nonneg x : 0 ≤ x ^ 2

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

example
  (h : -y > x^2 + 1)
  : y > 0 ∨ y < -1 :=
by { right, linarith [pow_two_nonneg x] }

-- ---------------------------------------------------------------------
-- Ejercicio 4. Demostrar que si
--      y > 0
-- entonces
--      y > 0 ∨ y < -1
-- ----------------------------------------------------------------------

example
  (h : y > 0)
  : y > 0 ∨ y < -1 :=
or.inl h

-- Comentario: Se usa el lema
--    or.inl : a → a ∨ b

-- ---------------------------------------------------------------------
-- Ejercicio 5. Demostrar que si
--    y < -1
-- entonces
--    y > 0 ∨ y < -1
-- ----------------------------------------------------------------------

example
  (h : y < -1)
  : y > 0 ∨ y < -1 :=
or.inr h

-- Comentario: Se usa el lema
--    or.inr : b → a ∨ b


import tactic
import data.real.basic
import data.pnat.basic
import measure_theory.interval_integral

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

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

def is_convergent (a : ℕ → ℝ) : Prop :=
∃ l : ℝ, is_limit a l

open real

noncomputable def question1a : ℕ → ℝ
| 0 := 1
| (n+1) := sqrt(2*(question1a n))

-- guess : converges to 2
theorem question1 : is_convergent question1a :=
begin
  sorry
end

-- Q2 -- need to know what ratio test is

-- Q3 -- should be possible

-- for each part, precisely one of 1 and 2 is true. Prove the true one
-- and delete the other one

theorem q4a1 (an : ℕ → ℝ) :
  (∃ a : ℝ, ∀ ε > 0, ∀ N : ℕ, ∃ n ≥ N, | an n - a | < ε) →
  (∃ a : ℝ, ∀ ε > 0, ∃ N : ℕ, ∀ n ≥ N, | an n - a | < ε)
:= sorry

theorem q4a2 (an : ℕ → ℝ) :
  (∃ a : ℝ, ∀ ε > 0, ∃ N : ℕ, ∀ n ≥ N, | an n - a | < ε) →
  (∃ a : ℝ, ∀ ε > 0, ∀ N : ℕ, ∃ n ≥ N, | an n - a | < ε)
:= sorry

theorem q4b1 (an : ℕ → ℝ) :
  (∃ a : ℝ, ∀ ε > 0, ∀ N : ℕ, ∃ n ≥ N, | an n - a | < ε) →
  (∃ a : ℝ, ∃ ε > 0, ∀ N : ℕ, ∀ n ≥ N, | an n - a | < ε)
:= sorry

theorem q4b2 (an : ℕ → ℝ) :
  (∃ a : ℝ, ∃ ε > 0, ∀ N : ℕ, ∀ n ≥ N, | an n - a | < ε) →
  (∃ a : ℝ, ∀ ε > 0, ∀ N : ℕ, ∃ n ≥ N, | an n - a | < ε)
:= sorry

theorem q4c1 (an : ℕ → ℝ) :
  (∃ a : ℝ, ∀ ε > 0, ∀ N : ℕ, ∃ n ≥ N, | an n - a | < ε) →
  (∀ a : ℝ, ∃ ε > 0, ∀ N : ℕ, ∀ n ≥ N, | an n - a | < ε)
:= sorry

theorem q4c2 (an : ℕ → ℝ) :
  (∀ a : ℝ, ∃ ε > 0, ∀ N : ℕ, ∀ n ≥ N, | an n - a | < ε) →
  (∃ a : ℝ, ∀ ε > 0, ∀ N : ℕ, ∃ n ≥ N, | an n - a | < ε)
:= sorry

theorem q4d1 (an : ℕ → ℝ) :
  (∃ a : ℝ, ∀ ε > 0, ∀ N : ℕ, ∃ n ≥ N, | an n - a | < ε) →
  (∃ a : ℝ, ∃ N : ℕ, ∀ ε > 0, ∀ n ≥ N, | an n - a | < ε)
:= sorry

theorem q4d2 (an : ℕ → ℝ) :
  (∃ a : ℝ, ∃ N : ℕ, ∀ ε > 0, ∀ n ≥ N, | an n - a | < ε) →
  (∃ a : ℝ, ∀ ε > 0, ∀ N : ℕ, ∃ n ≥ N, | an n - a | < ε)
:= sorry

open_locale big_operators

open finset

def is_sum (a : ℕ → ℝ) (l : ℝ) : Prop :=
∀ ε > 0, ∃ N, ∀ n ≥ N, | (∑ i in range n, a i) - l | < ε

def sum_is_convergent (a : ℕ → ℝ) : Prop :=
∃ l : ℝ, is_sum a l

theorem Q7 (a : ℕ → ℝ) (c : ℝ) (l : ℝ) : is_sum a l → is_sum (λ n, c * a n) (c * l) :=
begin
  sorry
end


/-
Copyright (c) 2021 Johan Commelin. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johan Commelin

! This file was ported from Lean 3 source module group_theory.subgroup.saturated
! leanprover-community/mathlib commit f7fc89d5d5ff1db2d1242c7bb0e9062ce47ef47c
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.GroupTheory.Subgroup.Basic

/-!
# Saturated subgroups

## Tags
subgroup, subgroups

-/

namespace Subgroup

variable {G : Type _} [Group G]

/-- A subgroup `H` of `G` is *saturated* if for all `n : ℕ` and `g : G` with `g^n ∈ H`
we have `n = 0` or `g ∈ H`. -/
@[to_additive
      "An additive subgroup `H` of `G` is *saturated* if for all `n : ℕ` and `g : G` with `n•g ∈ H`
      we have `n = 0` or `g ∈ H`."]
def Saturated (H : Subgroup G) : Prop :=
  ∀ ⦃n g⦄, g ^ n ∈ H → n = 0 ∨ g ∈ H
#align subgroup.saturated Subgroup.Saturated
#align add_subgroup.saturated AddSubgroup.Saturated

@[to_additive]
theorem saturated_iff_npow {H : Subgroup G} :
    Saturated H ↔ ∀ (n : ℕ) (g : G), g ^ n ∈ H → n = 0 ∨ g ∈ H :=
  Iff.rfl
#align subgroup.saturated_iff_npow Subgroup.saturated_iff_npow
#align add_subgroup.saturated_iff_nsmul AddSubgroup.saturated_iff_nsmul

@[to_additive]
theorem saturated_iff_zpow {H : Subgroup G} :
    Saturated H ↔ ∀ (n : ℤ) (g : G), g ^ n ∈ H → n = 0 ∨ g ∈ H := by
  constructor
  · intros hH n g hgn
    induction' n with n n
    · simp only [Int.coe_nat_eq_zero, Int.ofNat_eq_coe, zpow_ofNat] at hgn⊢
      exact hH hgn
    · suffices g ^ (n + 1) ∈ H by
        refine' (hH this).imp _ id
        simp only [IsEmpty.forall_iff, Nat.succ_ne_zero]
      simpa only [inv_mem_iff, zpow_negSucc] using hgn
  · intro h n g hgn
    specialize h n g
    simp only [Int.coe_nat_eq_zero, zpow_ofNat] at h
    apply h hgn
#align subgroup.saturated_iff_zpow Subgroup.saturated_iff_zpow
#align add_subgroup.saturated_iff_zsmul AddSubgroup.saturated_iff_zsmul

end Subgroup

namespace AddSubgroup

theorem ker_saturated {A₁ A₂ : Type _} [AddCommGroup A₁] [AddCommGroup A₂] [NoZeroSMulDivisors ℕ A₂]
    (f : A₁ →+ A₂) : f.ker.Saturated := by
  intro n g hg
  simpa only [f.mem_ker, nsmul_eq_smul, f.map_nsmul, smul_eq_zero] using hg
#align add_subgroup.ker_saturated AddSubgroup.ker_saturated

end AddSubgroup


import .love05_inductive_predicates_demo

/- # LoVe Exercise 5: Inductive Predicates -/

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

/- ## Question 1: Even and Odd

The `even` predicate is true for even numbers and false for odd numbers. -/

#check even

/- We define `odd` as the negation of `even`: -/

def odd (n : ℕ) : Prop :=
  ¬ even n

/- 1.1. Prove that 1 is odd and register this fact as a simp rule.

Hint: `cases'` is useful to reason about hypotheses of the form `even …`. -/

@[simp] lemma odd_1 :
  odd 1 :=
sorry

/- 1.2. Prove that 3, 5, and 7 are odd. -/

-- enter your answer here

/- 1.3. Complete the following proof by structural induction. -/

lemma even_two_times :
  ∀m : ℕ, even (2 * m)
| 0       := even.zero
| (m + 1) :=
  sorry

/- 1.4. Complete the following proof by rule induction.

Hint: You can use the `cases'` tactic (or `match … with`) to destruct an
existential quantifier and extract the witness. -/

lemma even_imp_exists_two_times :
  ∀n : ℕ, even n → ∃m, n = 2 * m :=
begin
  intros n hen,
  induction' hen,
  case zero {
    apply exists.intro 0,
    refl },
  case add_two : k hek ih {
    sorry }
end

/- 1.6. Using `even_two_times` and `even_imp_exists_two_times`, prove the
following equivalence. -/

lemma even_iff_exists_two_times (n : ℕ) :
  even n ↔ ∃m, n = 2 * m :=
sorry

/- ## Question 2: Binary Trees

2.1. Prove the converse of `is_full_mirror`. You may exploit already proved
lemmas (e.g., `is_full_mirror`, `mirror_mirror`). -/

#check is_full_mirror
#check mirror_mirror

lemma mirror_is_full {α : Type} :
  ∀t : btree α, is_full (mirror t) → is_full t :=
sorry

/- 2.2. Define a `map` function on binary trees, similar to `list.map`. -/

def btree.map {α β : Type} (f : α → β) : btree α → btree β
| _ := sorry   -- remove this dummy case and enter the missing cases

/- 2.3. Prove the following lemma by case distinction. -/

lemma btree.map_eq_empty_iff {α β : Type} (f : α → β) :
  ∀t : btree α, btree.map f t = btree.empty ↔ t = btree.empty :=
sorry

/- 2.4. Prove the following lemma by rule induction. -/

lemma btree.map_mirror {α β : Type} (f : α → β) :
  ∀t : btree α, is_full t → is_full (btree.map f t) :=
sorry

end LoVe