/-
Copyright (c) 2018 Chris Hughes. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Chris Hughes, Johannes Hölzl, Scott Morrison, Jens Wagemaker
-/

import data.polynomial.basic
import data.finset.nat_antidiagonal
import data.nat.choose.sum

/-!
# Theory of univariate polynomials

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

The theorems include formulas for computing coefficients, such as
`coeff_add`, `coeff_sum`, `coeff_mul`

-/

noncomputable theory

open finsupp finset add_monoid_algebra
open_locale big_operators polynomial

namespace polynomial
universes u v
variables {R : Type u} {S : Type v} {a b : R} {n m : ℕ}

variables [semiring R] {p q r : R[X]}

section coeff

lemma coeff_one (n : ℕ) : coeff (1 : R[X]) n = if 0 = n then 1 else 0 :=
coeff_monomial

@[simp]
lemma coeff_add (p q : R[X]) (n : ℕ) : coeff (p + q) n = coeff p n + coeff q n :=
by { rcases p, rcases q, simp_rw [←of_finsupp_add, coeff], exact finsupp.add_apply _ _ _ }

@[simp] lemma coeff_bit0 (p : R[X]) (n : ℕ) : coeff (bit0 p) n = bit0 (coeff p n) := by simp [bit0]

@[simp] lemma coeff_smul [monoid S] [distrib_mul_action S R] (r : S) (p : R[X]) (n : ℕ) :
  coeff (r • p) n = r • coeff p n :=
by { rcases p, simp_rw [←of_finsupp_smul, coeff], exact finsupp.smul_apply _ _ _ }

lemma support_smul [monoid S] [distrib_mul_action S R] (r : S) (p : R[X]) :
  support (r • p) ⊆ support p :=
begin
  assume i hi,
  simp [mem_support_iff] at hi ⊢,
  contrapose! hi,
  simp [hi]
end

/-- `polynomial.sum` as a linear map. -/
@[simps] def lsum {R A M : Type*} [semiring R] [semiring A] [add_comm_monoid M]
  [module R A] [module R M] (f : ℕ → A →ₗ[R] M) :
  A[X] →ₗ[R] M :=
{ to_fun := λ p, p.sum (λ n r, f n r),
  map_add' := λ p q, sum_add_index p q _ (λ n, (f n).map_zero) (λ n _ _, (f n).map_add _ _),
  map_smul' := λ c p,
  begin
    rw [sum_eq_of_subset _ (λ n r, f n r) (λ n, (f n).map_zero) _ (support_smul c p)],
    simp only [sum_def, finset.smul_sum, coeff_smul, linear_map.map_smul, ring_hom.id_apply]
  end }

variable (R)
/-- The nth coefficient, as a linear map. -/
def lcoeff (n : ℕ) : R[X] →ₗ[R] R :=
{ to_fun := λ p, coeff p n,
  map_add' := λ p q, coeff_add p q n,
  map_smul' := λ r p, coeff_smul r p n }

variable {R}

@[simp] lemma lcoeff_apply (n : ℕ) (f : R[X]) : lcoeff R n f = coeff f n := rfl

@[simp] lemma finset_sum_coeff {ι : Type*} (s : finset ι) (f : ι → R[X]) (n : ℕ) :
  coeff (∑ b in s, f b) n = ∑ b in s, coeff (f b) n :=
(lcoeff R n).map_sum

lemma coeff_sum [semiring S] (n : ℕ) (f : ℕ → R → S[X]) :
  coeff (p.sum f) n = p.sum (λ a b, coeff (f a b) n) :=
by { rcases p, simp [polynomial.sum, support, coeff] }

/-- Decomposes the coefficient of the product `p * q` as a sum
over `nat.antidiagonal`. A version which sums over `range (n + 1)` can be obtained
by using `finset.nat.sum_antidiagonal_eq_sum_range_succ`. -/
lemma coeff_mul (p q : R[X]) (n : ℕ) :
  coeff (p * q) n = ∑ x in nat.antidiagonal n, coeff p x.1 * coeff q x.2 :=
begin
  rcases p, rcases q,
  simp_rw [←of_finsupp_mul, coeff],
  exact add_monoid_algebra.mul_apply_antidiagonal p q n _ (λ x, nat.mem_antidiagonal)
end

@[simp] lemma mul_coeff_zero (p q : R[X]) : coeff (p * q) 0 = coeff p 0 * coeff q 0 :=
by simp [coeff_mul]

/-- `constant_coeff p` returns the constant term of the polynomial `p`,
  defined as `coeff p 0`. This is a ring homomorphism. -/
@[simps] def constant_coeff : R[X] →+* R :=
{ to_fun := λ p, coeff p 0,
  map_one' := coeff_one_zero,
  map_mul' := mul_coeff_zero,
  map_zero' := coeff_zero 0,
  map_add' :=  λ p q, coeff_add p q 0 }

lemma is_unit_C {x : R} : is_unit (C x) ↔ is_unit x :=
⟨λ h, (congr_arg is_unit coeff_C_zero).mp (h.map $ @constant_coeff R _), λ h, h.map C⟩

lemma coeff_mul_X_zero (p : R[X]) : coeff (p * X) 0 = 0 := by simp

lemma coeff_X_mul_zero (p : R[X]) : coeff (X * p) 0 = 0 := by simp

lemma coeff_C_mul_X_pow (x : R) (k n : ℕ) : coeff (C x * X ^ k : R[X]) n = if n = k then x else 0 :=
by { rw [C_mul_X_pow_eq_monomial, coeff_monomial], congr' 1, simp [eq_comm] }

lemma coeff_C_mul_X (x : R) (n : ℕ) : coeff (C x * X : R[X]) n = if n = 1 then x else 0 :=
by rw [← pow_one X, coeff_C_mul_X_pow]

@[simp] lemma coeff_C_mul (p : R[X]) : coeff (C a * p) n = a * coeff p n :=
begin
  rcases p,
  simp_rw [←monomial_zero_left, ←of_finsupp_single, ←of_finsupp_mul, coeff],
  exact add_monoid_algebra.single_zero_mul_apply p a n
end

lemma C_mul' (a : R) (f : R[X]) : C a * f = a • f :=
by { ext, rw [coeff_C_mul, coeff_smul, smul_eq_mul] }

@[simp] lemma coeff_mul_C (p : R[X]) (n : ℕ) (a : R) :
  coeff (p * C a) n = coeff p n * a :=
begin
  rcases p,
  simp_rw [←monomial_zero_left, ←of_finsupp_single, ←of_finsupp_mul, coeff],
  exact add_monoid_algebra.mul_single_zero_apply p a n
end

lemma coeff_X_pow (k n : ℕ) :
  coeff (X^k : R[X]) n = if n = k then 1 else 0 :=
by simp only [one_mul, ring_hom.map_one, ← coeff_C_mul_X_pow]

@[simp]
lemma coeff_X_pow_self (n : ℕ) :
  coeff (X^n : R[X]) n = 1 :=
by simp [coeff_X_pow]

section fewnomials

open finset

lemma support_binomial {k m : ℕ} (hkm : k ≠ m) {x y : R} (hx : x ≠ 0) (hy : y ≠ 0) :
  (C x * X ^ k + C y * X ^ m).support = {k, m} :=
begin
  apply subset_antisymm (support_binomial' k m x y),
  simp_rw [insert_subset, singleton_subset_iff, mem_support_iff, coeff_add, coeff_C_mul,
    coeff_X_pow_self, mul_one, coeff_X_pow, if_neg hkm, if_neg hkm.symm,
    mul_zero, zero_add, add_zero, ne.def, hx, hy, and_self, not_false_iff],
end

lemma support_trinomial {k m n : ℕ} (hkm : k < m) (hmn : m < n) {x y z : R} (hx : x ≠ 0)
  (hy : y ≠ 0) (hz : z ≠ 0) : (C x * X ^ k + C y * X ^ m + C z * X ^ n).support = {k, m, n} :=
begin
  apply subset_antisymm (support_trinomial' k m n x y z),
  simp_rw [insert_subset, singleton_subset_iff, mem_support_iff, coeff_add, coeff_C_mul,
    coeff_X_pow_self, mul_one, coeff_X_pow, if_neg hkm.ne, if_neg hkm.ne', if_neg hmn.ne,
    if_neg hmn.ne', if_neg (hkm.trans hmn).ne, if_neg (hkm.trans hmn).ne',
    mul_zero, add_zero, zero_add, ne.def, hx, hy, hz, and_self, not_false_iff],
end

lemma card_support_binomial {k m : ℕ} (h : k ≠ m) {x y : R} (hx : x ≠ 0) (hy : y ≠ 0) :
  (C x * X ^ k + C y * X ^ m).support.card = 2 :=
by rw [support_binomial h hx hy, card_insert_of_not_mem (mt mem_singleton.mp h), card_singleton]

lemma card_support_trinomial {k m n : ℕ} (hkm : k < m) (hmn : m < n) {x y z : R} (hx : x ≠ 0)
  (hy : y ≠ 0) (hz : z ≠ 0) : (C x * X ^ k + C y * X ^ m + C z * X ^ n).support.card = 3 :=
by rw [support_trinomial hkm hmn hx hy hz, card_insert_of_not_mem
  (mt mem_insert.mp (not_or hkm.ne (mt mem_singleton.mp (hkm.trans hmn).ne))),
  card_insert_of_not_mem (mt mem_singleton.mp hmn.ne), card_singleton]

end fewnomials

@[simp]
theorem coeff_mul_X_pow (p : R[X]) (n d : ℕ) :
  coeff (p * polynomial.X ^ n) (d + n) = coeff p d :=
begin
  rw [coeff_mul, sum_eq_single (d,n), coeff_X_pow, if_pos rfl, mul_one],
  { rintros ⟨i,j⟩ h1 h2, rw [coeff_X_pow, if_neg, mul_zero], rintro rfl, apply h2,
    rw [nat.mem_antidiagonal, add_right_cancel_iff] at h1, subst h1 },
  { exact λ h1, (h1 (nat.mem_antidiagonal.2 rfl)).elim }
end

@[simp]
theorem coeff_X_pow_mul (p : R[X]) (n d : ℕ) :
  coeff (polynomial.X ^ n * p) (d + n) = coeff p d :=
by rw [(commute_X_pow p n).eq, coeff_mul_X_pow]

lemma coeff_mul_X_pow' (p : R[X]) (n d : ℕ) :
  (p * X ^ n).coeff d = ite (n ≤ d) (p.coeff (d - n)) 0 :=
begin
  split_ifs,
  { rw [← tsub_add_cancel_of_le h, coeff_mul_X_pow, add_tsub_cancel_right] },
  { refine (coeff_mul _ _ _).trans (finset.sum_eq_zero (λ x hx, _)),
    rw [coeff_X_pow, if_neg, mul_zero],
    exact ((le_of_add_le_right (finset.nat.mem_antidiagonal.mp hx).le).trans_lt $ not_le.mp h).ne }
end

lemma coeff_X_pow_mul' (p : R[X]) (n d : ℕ) :
  (X ^ n * p).coeff d = ite (n ≤ d) (p.coeff (d - n)) 0 :=
by rw [(commute_X_pow p n).eq, coeff_mul_X_pow']

@[simp] theorem coeff_mul_X (p : R[X]) (n : ℕ) :
  coeff (p * X) (n + 1) = coeff p n :=
by simpa only [pow_one] using coeff_mul_X_pow p 1 n

@[simp] theorem coeff_X_mul (p : R[X]) (n : ℕ) :
  coeff (X * p) (n + 1) = coeff p n := by rw [(commute_X p).eq, coeff_mul_X]

theorem coeff_mul_monomial (p : R[X]) (n d : ℕ) (r : R) :
  coeff (p * monomial n r) (d + n) = coeff p d * r :=
by rw [← C_mul_X_pow_eq_monomial, ←X_pow_mul, ←mul_assoc, coeff_mul_C, coeff_mul_X_pow]

theorem coeff_monomial_mul (p : R[X]) (n d : ℕ) (r : R) :
  coeff (monomial n r * p) (d + n) = r * coeff p d :=
by rw [← C_mul_X_pow_eq_monomial, mul_assoc, coeff_C_mul, X_pow_mul, coeff_mul_X_pow]

-- This can already be proved by `simp`.
theorem coeff_mul_monomial_zero (p : R[X]) (d : ℕ) (r : R) :
  coeff (p * monomial 0 r) d = coeff p d * r :=
coeff_mul_monomial p 0 d r

-- This can already be proved by `simp`.
theorem coeff_monomial_zero_mul (p : R[X]) (d : ℕ) (r : R) :
  coeff (monomial 0 r * p) d = r * coeff p d :=
coeff_monomial_mul p 0 d r

theorem mul_X_pow_eq_zero {p : R[X]} {n : ℕ}
  (H : p * X ^ n = 0) : p = 0 :=
ext $ λ k, (coeff_mul_X_pow p n k).symm.trans $ ext_iff.1 H (k+n)

lemma mul_X_pow_injective (n : ℕ) : function.injective (λ P : R[X], X ^ n * P) :=
begin
  intros P Q hPQ,
  simp only at hPQ,
  ext i,
  rw [← coeff_X_pow_mul P n i, hPQ, coeff_X_pow_mul Q n i]
end

lemma mul_X_injective : function.injective (λ P : R[X], X * P) :=
pow_one (X : R[X]) ▸ mul_X_pow_injective 1

lemma coeff_X_add_C_pow (r : R) (n k : ℕ) :
  ((X + C r) ^ n).coeff k = r ^ (n - k) * (n.choose k : R) :=
begin
  rw [(commute_X (C r : R[X])).add_pow, ← lcoeff_apply, linear_map.map_sum],
  simp only [one_pow, mul_one, lcoeff_apply, ← C_eq_nat_cast, ←C_pow, coeff_mul_C, nat.cast_id],
  rw [finset.sum_eq_single k, coeff_X_pow_self, one_mul],
  { intros _ _ h,
    simp [coeff_X_pow, h.symm] },
  { simp only [coeff_X_pow_self, one_mul, not_lt, finset.mem_range],
    intro h, rw [nat.choose_eq_zero_of_lt h, nat.cast_zero, mul_zero] }
end

lemma coeff_X_add_one_pow (R : Type*) [semiring R] (n k : ℕ) :
  ((X + 1) ^ n).coeff k = (n.choose k : R) :=
by rw [←C_1, coeff_X_add_C_pow, one_pow, one_mul]

lemma coeff_one_add_X_pow (R : Type*) [semiring R] (n k : ℕ) :
  ((1 + X) ^ n).coeff k = (n.choose k : R) :=
by rw [add_comm _ X, coeff_X_add_one_pow]

lemma C_dvd_iff_dvd_coeff (r : R) (φ : R[X]) :
  C r ∣ φ ↔ ∀ i, r ∣ φ.coeff i :=
begin
  split,
  { rintros ⟨φ, rfl⟩ c, rw coeff_C_mul, apply dvd_mul_right },
  { intro h,
    choose c hc using h,
    classical,
    let c' : ℕ → R := λ i, if i ∈ φ.support then c i else 0,
    let ψ : R[X] := ∑ i in φ.support, monomial i (c' i),
    use ψ,
    ext i,
    simp only [ψ, c', coeff_C_mul, mem_support_iff, coeff_monomial,
               finset_sum_coeff, finset.sum_ite_eq'],
    split_ifs with hi hi,
    { rw hc },
    { rw [not_not] at hi, rwa mul_zero } },
end

lemma coeff_bit0_mul (P Q : R[X]) (n : ℕ) :
  coeff (bit0 P * Q) n = 2 * coeff (P * Q) n :=
by simp [bit0, add_mul]

lemma smul_eq_C_mul (a : R) : a • p = C a * p := by simp [ext_iff]

lemma update_eq_add_sub_coeff {R : Type*} [ring R] (p : R[X]) (n : ℕ) (a : R) :
  p.update n a = p + (polynomial.C (a - p.coeff n) * polynomial.X ^ n) :=
begin
  ext,
  rw [coeff_update_apply, coeff_add, coeff_C_mul_X_pow],
  split_ifs with h;
  simp [h]
end

end coeff

section cast

@[simp] lemma nat_cast_coeff_zero {n : ℕ} {R : Type*} [semiring R] :
  (n : R[X]).coeff 0 = n :=
begin
  induction n with n ih,
  { simp, },
  { simp [ih], },
end

@[simp, norm_cast] theorem nat_cast_inj
  {m n : ℕ} {R : Type*} [semiring R] [char_zero R] : (↑m : R[X]) = ↑n ↔ m = n :=
begin
  fsplit,
  { intro h,
    apply_fun (λ p, p.coeff 0) at h,
    simpa using h, },
  { rintro rfl, refl, },
end

@[simp] lemma int_cast_coeff_zero {i : ℤ} {R : Type*} [ring R] :
  (i : R[X]).coeff 0 = i :=
by cases i; simp

@[simp, norm_cast] theorem int_cast_inj
  {m n : ℤ} {R : Type*} [ring R] [char_zero R] : (↑m : R[X]) = ↑n ↔ m = n :=
begin
  fsplit,
  { intro h,
    apply_fun (λ p, p.coeff 0) at h,
    simpa using h, },
  { rintro rfl, refl, },
end

end cast

instance [char_zero R] : char_zero R[X] :=
{ cast_injective := λ x y, nat_cast_inj.mp }

end polynomial


import ..exercises.love02_backward_proofs_exercise_sheet

/-! # LoVe Homework 3: Forward Proofs

Homework must be done individually. -/

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

/-! ## Question 1 (6 points): Connectives and Quantifiers

1.1 (2 points). We have proved or stated three of the six possible implications
between `excluded_middle`, `peirce`, and `double_negation`. Prove the three
missing implications using structured proofs, exploiting the three theorems we
already have. -/

namespace backward_proofs

#check peirce_of_em
#check dn_of_peirce
#check sorry_lemmas.em_of_dn

lemma peirce_of_dn :
  double_negation → peirce :=
sorry

lemma em_of_peirce :
  peirce → excluded_middle :=
sorry

lemma dn_of_em :
  excluded_middle → double_negation :=
sorry

end backward_proofs

/-! 1.2 (4 points). Supply a structured proof of the commutativity of `∧` under
an `∃` quantifier, using no other lemmas than the introduction and elimination
rules for `∃`, `∧`, and `↔`. -/

lemma exists_and_commute {α : Type} (p q : α → Prop) :
  (∃x, p x ∧ q x) ↔ (∃x, q x ∧ p x) :=
sorry

/-! ## Question 2 (4 points): Logic Puzzles

Recall the following tactical proof: -/

lemma weak_peirce :
  ∀a b : Prop, ((((a → b) → a) → a) → b) → b :=
begin
  intros a b habaab,
  apply habaab,
  intro habaa,
  apply habaa,
  intro ha,
  apply habaab,
  intro haba,
  apply ha
end

/-! 2.1 (1 point). Prove the same lemma again, this time by providing a proof
term.

Hint: There is an easy way. Anything goes! -/

lemma weak_peirce₂ :
  ∀a b : Prop, ((((a → b) → a) → a) → b) → b :=
sorry

/-! 2.2 (2 points). Prove the same lemma again, this time by providing a
structured proof, with `assume`s and `show`s. -/

lemma weak_peirce₃ :
  ∀a b : Prop, ((((a → b) → a) → a) → b) → b :=
sorry

/-! 2.3 (1 point). In a certain faraway village, there is only one barber for
the whole town. He shaves all those who do not shave themselves. Does the barber
shave himself?

Show that this premise implies `false`.
-/
axiom not_iff_not_self (P : Prop) : ¬ (P ↔ ¬ P)

example (Q : Prop) : ¬ (Q ↔ ¬ Q) :=
not_iff_not_self Q

section
  variable Person : Type
  variable shaves : Person → Person → Prop
  variable barber : Person
  variable h : ∀ x, shaves barber x ↔ ¬ shaves x x
  include h
  -- Show the following:
  example : false :=
    sorry
end

/-!
Optional extra challenge: we don't need `not_iff_not_self` to be an axiom.
State it as a lemma and prove it!
Extra extra challenge: prove it without using `classical.em` or `classical.by_contradiction`.
-/

/-! ## Question 3 (2 points): Calc Mode
Use `calc` mode to prove that the difference of squares formula holds on the
integers. (In this particular problem, working on the integers is necessary, but in
practice not much different from working on ℕ.)
You might find some or all of the following subtraction lemmas useful!
-/
#check mul_sub
#check sub_add_eq_sub_sub
#check sub_self
#check add_sub_assoc
#check mul_comm
#check add_mul

lemma difference_of_squares (a b : ℤ) :
  (a + b) * (a - b) = a * a - b * b :=
  sorry

end LoVe


import Mathlib.Tactic.Linarith

def hello := "world"

def a : ℕ → ℤ
| 0 => 2
| 1 => 5
| n + 2 => 5 * a (n + 1) - 6 * a n

#check Nat.two_step_induction

lemma helper (n : ℕ) : a (n + 2) = 5 * a (n + 1) - 6 * a n := rfl

lemma foo (n : ℕ) : a n = 2 ^ n + 3 ^ n := by
  induction' n using Nat.two_step_induction with n h1 h2
  . rfl
  . rfl
  simp [Nat.succ_eq_add_one] at *
  calc
    a (n + 2)
      = (5 : ℤ) * (2 ^ (n + 1) + 3 ^ (n + 1)) -
          6 * (2 ^ n + 3 ^ n) := by linarith [helper n]
    _ = (2 : ℤ) ^ (n + 2) + 3 ^ (n + 2) := by ring

def odd (k : ℤ) : Prop := ∃ a, k = 2 * a + 1

lemma bar2 (n : ℕ) : odd (a (n + 1)) := by
  induction' n with n h1 h2
  . use 2
    rfl
  cases' h1 with k hk
  use 5*k - 3*a n + 2
  rw [helper]
  linarith

lemma bar (n : ℕ) : (n ≥ 1) → odd (a n) := by
  induction' n using Nat.two_step_induction with n h1 h2
  . norm_num
  . intro h
    use 2
    rfl
  simp only [Nat.succ_eq_add_one] at *

/-
    calc
      a (n + 2)
      -- = 5 * a (n + 1) - 6 * a n := helper n
       = (5 : ℤ) * (2 ^ (n + 1) + 3 ^ (n + 1)) -
        6 * (2 ^ n + 3 ^ n) := by linarith [helper n]
      -- _ = (5 : ℤ) * (2 * 2^n + 3 * 3^n) - 6 * (2 ^ n + 3 ^ n) := by ring
      -- _ = ((5*2 - 6) : ℤ) * 2^n + (5*3 - 6)*3^n := by ring
      -- _ = (4 : ℤ) * 2^n + 9 * 3^n := by ring
      _ = (2 : ℤ) ^ (n + 2) + 3 ^ (n + 2) := by ring

-/


import tactic

lemma sub_diff
  (m m' : ℕ)
  (h : m < m')
  : ∃ s : ℕ, 0 < s ∧ m' = m + s :=
begin
  use m' - m,
  split,
  { omega, },
  { exact (nat.add_sub_of_le (le_of_lt h)).symm }
end

-- 2ª demostración
lemma nat.exists_eq_add_of_lt'
  (m m' : ℕ)
  (h : m < m')
  : ∃ s : ℕ, 0 < s ∧ m' = m + s :=
let ⟨k, hk⟩ := nat.exists_eq_add_of_lt h
in ⟨k.succ, k.zero_lt_succ, hk⟩


/-
Copyright (c) 2022 Yakov Pechersky. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yakov Pechersky, Floris van Doorn

! This file was ported from Lean 3 source module data.pnat.find
! leanprover-community/mathlib commit c3291da49cfa65f0d43b094750541c0731edc932
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Data.Pnat.Basic

/-!
# Explicit least witnesses to existentials on positive natural numbers

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

Implemented via calling out to `nat.find`.

-/

namespace PNat

variable {p q : ℕ+ → Prop} [DecidablePred p] [DecidablePred q] (h : ∃ n, p n)

#print PNat.decidablePredExistsNat /-
instance decidablePredExistsNat : DecidablePred fun n' : ℕ => ∃ (n : ℕ+)(hn : n' = n), p n :=
  fun n' =>
  decidable_of_iff' (∃ h : 0 < n', p ⟨n', h⟩) <|
    Subtype.exists.trans <| by
      simp_rw [Subtype.coe_mk, @exists_comm (_ < _) (_ = _), exists_prop, exists_eq_left']
#align pnat.decidable_pred_exists_nat PNat.decidablePredExistsNat
-/

include h

#print PNat.findX /-
/-- The `pnat` version of `nat.find_x` -/
protected def findX : { n // p n ∧ ∀ m : ℕ+, m < n → ¬p m } :=
  by
  have : ∃ (n' : ℕ)(n : ℕ+)(hn' : n' = n), p n := Exists.elim h fun n hn => ⟨n, n, rfl, hn⟩
  have n := Nat.findX this
  refine' ⟨⟨n, _⟩, _, fun m hm pm => _⟩
  · obtain ⟨n', hn', -⟩ := n.prop.1
    rw [hn']
    exact n'.prop
  · obtain ⟨n', hn', pn'⟩ := n.prop.1
    simpa [hn', Subtype.coe_eta] using pn'
  · exact n.prop.2 m hm ⟨m, rfl, pm⟩
#align pnat.find_x PNat.findX
-/

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

The API for `pnat.find` is:

* `pnat.find_spec` is the proof that `pnat.find hp` satisfies `p`.
* `pnat.find_min` is the proof that if `m < pnat.find hp` then `m` does not satisfy `p`.
* `pnat.find_min'` is the proof that if `m` does satisfy `p` then `pnat.find hp ≤ m`.
-/
protected def find : ℕ+ :=
  PNat.findX h
#align pnat.find PNat.find
-/

#print PNat.find_spec /-
protected theorem find_spec : p (PNat.find h) :=
  (PNat.findX h).Prop.left
#align pnat.find_spec PNat.find_spec
-/

#print PNat.find_min /-
protected theorem find_min : ∀ {m : ℕ+}, m < PNat.find h → ¬p m :=
  (PNat.findX h).Prop.right
#align pnat.find_min PNat.find_min
-/

#print PNat.find_min' /-
protected theorem find_min' {m : ℕ+} (hm : p m) : PNat.find h ≤ m :=
  le_of_not_lt fun l => PNat.find_min h l hm
#align pnat.find_min' PNat.find_min'
-/

variable {n m : ℕ+}

#print PNat.find_eq_iff /-
theorem find_eq_iff : PNat.find h = m ↔ p m ∧ ∀ n < m, ¬p n :=
  by
  constructor
  · rintro rfl
    exact ⟨PNat.find_spec h, fun _ => PNat.find_min h⟩
  · rintro ⟨hm, hlt⟩
    exact le_antisymm (PNat.find_min' h hm) (not_lt.1 <| imp_not_comm.1 (hlt _) <| PNat.find_spec h)
#align pnat.find_eq_iff PNat.find_eq_iff
-/

/- warning: pnat.find_lt_iff -> PNat.find_lt_iff is a dubious translation:
lean 3 declaration is
  forall {p : PNat -> Prop} [_inst_1 : DecidablePred.{1} PNat p] (h : Exists.{1} PNat (fun (n : PNat) => p n)) (n : PNat), Iff (LT.lt.{0} PNat (Preorder.toLT.{0} PNat (PartialOrder.toPreorder.{0} PNat (OrderedCancelCommMonoid.toPartialOrder.{0} PNat (LinearOrderedCancelCommMonoid.toOrderedCancelCommMonoid.{0} PNat PNat.linearOrderedCancelCommMonoid)))) (PNat.find (fun (n : PNat) => p n) (fun (a : PNat) => _inst_1 a) h) n) (Exists.{1} PNat (fun (m : PNat) => Exists.{0} (LT.lt.{0} PNat (Preorder.toLT.{0} PNat (PartialOrder.toPreorder.{0} PNat (OrderedCancelCommMonoid.toPartialOrder.{0} PNat (LinearOrderedCancelCommMonoid.toOrderedCancelCommMonoid.{0} PNat PNat.linearOrderedCancelCommMonoid)))) m n) (fun (H : LT.lt.{0} PNat (Preorder.toLT.{0} PNat (PartialOrder.toPreorder.{0} PNat (OrderedCancelCommMonoid.toPartialOrder.{0} PNat (LinearOrderedCancelCommMonoid.toOrderedCancelCommMonoid.{0} PNat PNat.linearOrderedCancelCommMonoid)))) m n) => p m)))
but is expected to have type
  forall {p : PNat -> Prop} [_inst_1 : DecidablePred.{1} PNat p] (h : Exists.{1} PNat (fun (n : PNat) => p n)) (n : PNat), Iff (LT.lt.{0} PNat (Preorder.toLT.{0} PNat (PartialOrder.toPreorder.{0} PNat (OrderedCancelCommMonoid.toPartialOrder.{0} PNat (LinearOrderedCancelCommMonoid.toOrderedCancelCommMonoid.{0} PNat instPNatLinearOrderedCancelCommMonoid)))) (PNat.find (fun (n : PNat) => p n) (fun (a : PNat) => _inst_1 a) h) n) (Exists.{1} PNat (fun (m : PNat) => And (LT.lt.{0} PNat (Preorder.toLT.{0} PNat (PartialOrder.toPreorder.{0} PNat (OrderedCancelCommMonoid.toPartialOrder.{0} PNat (LinearOrderedCancelCommMonoid.toOrderedCancelCommMonoid.{0} PNat instPNatLinearOrderedCancelCommMonoid)))) m n) (p m)))
Case conversion may be inaccurate. Consider using '#align pnat.find_lt_iff PNat.find_lt_iffₓ'. -/
@[simp]
theorem find_lt_iff (n : ℕ+) : PNat.find h < n ↔ ∃ m < n, p m :=
  ⟨fun h2 => ⟨PNat.find h, h2, PNat.find_spec h⟩, fun ⟨m, hmn, hm⟩ =>
    (PNat.find_min' h hm).trans_lt hmn⟩
#align pnat.find_lt_iff PNat.find_lt_iff

/- warning: pnat.find_le_iff -> PNat.find_le_iff is a dubious translation:
lean 3 declaration is
  forall {p : PNat -> Prop} [_inst_1 : DecidablePred.{1} PNat p] (h : Exists.{1} PNat (fun (n : PNat) => p n)) (n : PNat), Iff (LE.le.{0} PNat (Preorder.toLE.{0} PNat (PartialOrder.toPreorder.{0} PNat (OrderedCancelCommMonoid.toPartialOrder.{0} PNat (LinearOrderedCancelCommMonoid.toOrderedCancelCommMonoid.{0} PNat PNat.linearOrderedCancelCommMonoid)))) (PNat.find (fun (n : PNat) => p n) (fun (a : PNat) => _inst_1 a) h) n) (Exists.{1} PNat (fun (m : PNat) => Exists.{0} (LE.le.{0} PNat (Preorder.toLE.{0} PNat (PartialOrder.toPreorder.{0} PNat (OrderedCancelCommMonoid.toPartialOrder.{0} PNat (LinearOrderedCancelCommMonoid.toOrderedCancelCommMonoid.{0} PNat PNat.linearOrderedCancelCommMonoid)))) m n) (fun (H : LE.le.{0} PNat (Preorder.toLE.{0} PNat (PartialOrder.toPreorder.{0} PNat (OrderedCancelCommMonoid.toPartialOrder.{0} PNat (LinearOrderedCancelCommMonoid.toOrderedCancelCommMonoid.{0} PNat PNat.linearOrderedCancelCommMonoid)))) m n) => p m)))
but is expected to have type
  forall {p : PNat -> Prop} [_inst_1 : DecidablePred.{1} PNat p] (h : Exists.{1} PNat (fun (n : PNat) => p n)) (n : PNat), Iff (LE.le.{0} PNat (Preorder.toLE.{0} PNat (PartialOrder.toPreorder.{0} PNat (OrderedCancelCommMonoid.toPartialOrder.{0} PNat (LinearOrderedCancelCommMonoid.toOrderedCancelCommMonoid.{0} PNat instPNatLinearOrderedCancelCommMonoid)))) (PNat.find (fun (n : PNat) => p n) (fun (a : PNat) => _inst_1 a) h) n) (Exists.{1} PNat (fun (m : PNat) => And (LE.le.{0} PNat (Preorder.toLE.{0} PNat (PartialOrder.toPreorder.{0} PNat (OrderedCancelCommMonoid.toPartialOrder.{0} PNat (LinearOrderedCancelCommMonoid.toOrderedCancelCommMonoid.{0} PNat instPNatLinearOrderedCancelCommMonoid)))) m n) (p m)))
Case conversion may be inaccurate. Consider using '#align pnat.find_le_iff PNat.find_le_iffₓ'. -/
@[simp]
theorem find_le_iff (n : ℕ+) : PNat.find h ≤ n ↔ ∃ m ≤ n, p m := by
  simp only [exists_prop, ← lt_add_one_iff, find_lt_iff]
#align pnat.find_le_iff PNat.find_le_iff

#print PNat.le_find_iff /-
@[simp]
theorem le_find_iff (n : ℕ+) : n ≤ PNat.find h ↔ ∀ m < n, ¬p m := by
  simp_rw [← not_lt, find_lt_iff, not_exists]
#align pnat.le_find_iff PNat.le_find_iff
-/

#print PNat.lt_find_iff /-
@[simp]
theorem lt_find_iff (n : ℕ+) : n < PNat.find h ↔ ∀ m ≤ n, ¬p m := by
  simp only [← add_one_le_iff, le_find_iff, add_le_add_iff_right]
#align pnat.lt_find_iff PNat.lt_find_iff
-/

#print PNat.find_eq_one /-
@[simp]
theorem find_eq_one : PNat.find h = 1 ↔ p 1 := by simp [find_eq_iff]
#align pnat.find_eq_one PNat.find_eq_one
-/

#print PNat.one_le_find /-
@[simp]
theorem one_le_find : 1 < PNat.find h ↔ ¬p 1 :=
  not_iff_not.mp <| by simp
#align pnat.one_le_find PNat.one_le_find
-/

#print PNat.find_mono /-
theorem find_mono (h : ∀ n, q n → p n) {hp : ∃ n, p n} {hq : ∃ n, q n} :
    PNat.find hp ≤ PNat.find hq :=
  PNat.find_min' _ (h _ (PNat.find_spec hq))
#align pnat.find_mono PNat.find_mono
-/

#print PNat.find_le /-
theorem find_le {h : ∃ n, p n} (hn : p n) : PNat.find h ≤ n :=
  (PNat.find_le_iff _ _).2 ⟨n, le_rfl, hn⟩
#align pnat.find_le PNat.find_le
-/

/- warning: pnat.find_comp_succ -> PNat.find_comp_succ is a dubious translation:
lean 3 declaration is
  forall {p : PNat -> Prop} [_inst_1 : DecidablePred.{1} PNat p] (h : Exists.{1} PNat (fun (n : PNat) => p n)) (h₂ : Exists.{1} PNat (fun (n : PNat) => p (HAdd.hAdd.{0, 0, 0} PNat PNat PNat (instHAdd.{0} PNat PNat.hasAdd) n (OfNat.ofNat.{0} PNat 1 (OfNat.mk.{0} PNat 1 (One.one.{0} PNat PNat.hasOne)))))), (Not (p (OfNat.ofNat.{0} PNat 1 (OfNat.mk.{0} PNat 1 (One.one.{0} PNat PNat.hasOne))))) -> (Eq.{1} PNat (PNat.find (fun (n : PNat) => p n) (fun (a : PNat) => _inst_1 a) h) (HAdd.hAdd.{0, 0, 0} PNat PNat PNat (instHAdd.{0} PNat PNat.hasAdd) (PNat.find (fun (n : PNat) => p (HAdd.hAdd.{0, 0, 0} PNat PNat PNat (instHAdd.{0} PNat PNat.hasAdd) n (OfNat.ofNat.{0} PNat 1 (OfNat.mk.{0} PNat 1 (One.one.{0} PNat PNat.hasOne))))) (fun (a : PNat) => _inst_1 (HAdd.hAdd.{0, 0, 0} PNat PNat PNat (instHAdd.{0} PNat PNat.hasAdd) a (OfNat.ofNat.{0} PNat 1 (OfNat.mk.{0} PNat 1 (One.one.{0} PNat PNat.hasOne))))) h₂) (OfNat.ofNat.{0} PNat 1 (OfNat.mk.{0} PNat 1 (One.one.{0} PNat PNat.hasOne)))))
but is expected to have type
  forall {p : PNat -> Prop} [_inst_1 : DecidablePred.{1} PNat p] (h : Exists.{1} PNat (fun (n : PNat) => p n)) (h₂ : Exists.{1} PNat (fun (n : PNat) => p (HAdd.hAdd.{0, 0, 0} PNat PNat PNat (instHAdd.{0} PNat instPNatAdd) n (OfNat.ofNat.{0} PNat 1 (instOfNatPNatHAddNatInstHAddInstAddNatOfNat (OfNat.ofNat.{0} Nat 0 (instOfNatNat 0))))))), (Not (p (OfNat.ofNat.{0} PNat 1 (instOfNatPNatHAddNatInstHAddInstAddNatOfNat (OfNat.ofNat.{0} Nat 0 (instOfNatNat 0)))))) -> (Eq.{1} PNat (PNat.find (fun (n : PNat) => p n) (fun (a : PNat) => _inst_1 a) h) (HAdd.hAdd.{0, 0, 0} PNat PNat PNat (instHAdd.{0} PNat instPNatAdd) (PNat.find (fun (n : PNat) => p (HAdd.hAdd.{0, 0, 0} PNat PNat PNat (instHAdd.{0} PNat instPNatAdd) n (OfNat.ofNat.{0} PNat 1 (instOfNatPNatHAddNatInstHAddInstAddNatOfNat (OfNat.ofNat.{0} Nat 0 (instOfNatNat 0)))))) (fun (a : PNat) => _inst_1 (HAdd.hAdd.{0, 0, 0} PNat PNat PNat (instHAdd.{0} PNat instPNatAdd) a (OfNat.ofNat.{0} PNat 1 (instOfNatPNatHAddNatInstHAddInstAddNatOfNat (OfNat.ofNat.{0} Nat 0 (instOfNatNat 0)))))) h₂) (OfNat.ofNat.{0} PNat 1 (instOfNatPNatHAddNatInstHAddInstAddNatOfNat (OfNat.ofNat.{0} Nat 0 (instOfNatNat 0))))))
Case conversion may be inaccurate. Consider using '#align pnat.find_comp_succ PNat.find_comp_succₓ'. -/
theorem find_comp_succ (h : ∃ n, p n) (h₂ : ∃ n, p (n + 1)) (h1 : ¬p 1) :
    PNat.find h = PNat.find h₂ + 1 :=
  by
  refine' (find_eq_iff _).2 ⟨PNat.find_spec h₂, fun n => PNat.recOn n _ _⟩
  · simp [h1]
  intro m IH hm
  simp only [add_lt_add_iff_right, lt_find_iff] at hm
  exact hm _ le_rfl
#align pnat.find_comp_succ PNat.find_comp_succ

end PNat


import .love01_definitions_and_statements_demo

/-! # LoVe Demo 2: Backward Proofs

A __tactic__ operates on a proof goal and either proves it or creates new
subgoals. Tactics are a __backward__ proof mechanism: They start from the goal
and work towards the available hypotheses and lemmas. -/

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

namespace backward_proofs

/-! ## Tactic Mode

Syntax of tactical proofs:

    begin
      _tactic₁_,
      …,
      _tacticN_
    end -/

lemma fst_of_two_props :
  ∀a b : Prop, a → b → a :=
begin
  intros a b,
  intros ha hb,
  apply ha
end

/-! ## Basic Tactics

`intro`(`s`) moves `∀`-quantified variables, or the assumptions of
implications `→`, from the goal's conclusion (after `⊢`) into the goal's
hypotheses (before `⊢`).

`apply` matches the goal's conclusion with the conclusion of the specified lemma
and adds the lemma's hypotheses as new goals. -/

lemma fst_of_two_props₂ (a b : Prop) (ha : a) (hb : b) :
  a :=
begin
  apply ha
end

/-! Terminal tactic syntax:

    by _tactic_

abbreviates

    begin
      _tactic_
    end -/

lemma fst_of_two_props₃ (a b : Prop) (ha : a) (hb : b) :
  a :=
by apply ha

lemma prop_comp (a b c : Prop) (hab : a → b) (hbc : b → c) :
  a → c :=
begin
  intro ha,
  apply hbc,
  apply hab,
  apply ha
end

/-! `exact` matches the goal's conclusion with the specified lemma, closing the
goal. We can often use `apply` in such situations, but `exact` communicates our
intentions better. -/

lemma fst_of_two_props₄ (a b : Prop) (ha : a) (hb : b) :
  a :=
by exact ha

/-! `assumption` finds a hypothesis from the local context that matches the
goal's conclusion and applies it to prove the goal. -/

lemma fst_of_two_props₅ (a b : Prop) (ha : a) (hb : b) :
  a :=
by assumption

/-! ## Reasoning about Logical Connectives and Quantifiers

Introduction rules:

The relevant symbol appears in the *conclusion* of the statement.
(On the right side of the ->)

We apply these rules when the symbol appears in our *goal*.
-/

#check true.intro
#check not.intro
#check and.intro
#check or.intro_left
#check or.intro_right
#check iff.intro
#check exists.intro

/-! Elimination rules:

The relevant symbol appears in a *hypothesis* of the statement.
(On the left side of the ->)

We apply these rules when the symbol appears in our *context*.
-/

#check false.elim
#check and.elim_left
#check and.elim_right
#check or.elim
#check iff.elim_left
#check iff.elim_right
#check exists.elim

/-! Definition of `¬` and related lemmas: -/

#print not
#check not_def
#check classical.em
#check classical.by_contradiction

lemma and_swap (a b : Prop) :
  a ∧ b → b ∧ a :=
begin
  intro hab,
  apply and.intro,
  apply and.elim_right,
  exact hab,
  apply and.elim_left,
  exact hab
end

/-! The `{ … }` combinator focuses on the first subgoal. The tactic inside must
fully prove it. -/

lemma and_swap₂ :
  ∀a b : Prop, a ∧ b → b ∧ a :=
begin
  intros a b hab,
  apply and.intro,
  { exact and.elim_right hab },
  { exact and.elim_left hab }
end

/-! Notice above how we pass the hypothesis `hab` directly to the lemmas
`and.elim_right` and `and.elim_left`, instead of waiting for the lemmas's
assumptions to appear as new subgoals. This is a small forward step in an
otherwise backward proof. -/

lemma or_swap (a b : Prop) :
  a ∨ b → b ∨ a :=
begin
  intros hab,
  apply or.elim hab,
  { intro ha,
    exact or.intro_right _ ha },
  { intro hb,
    exact or.intro_left _ hb }
end

lemma modus_ponens (a b : Prop) :
  (a → b) → a → b :=
begin
  intros hab ha,
  apply hab,
  exact ha
end

lemma not_not_intro (a : Prop) :
  a → ¬¬ a :=
begin
  intro ha,
  apply not.intro,
  intro hna,
  apply hna,
  exact ha
end

lemma not_not_intro₂ (a : Prop) :
  a → ¬¬ a :=
begin
  intros ha hna,
  apply hna,
  exact ha
end

def double (n : ℕ) : ℕ :=
n + n

lemma nat_exists_double_iden :
  ∃n : ℕ, double n = n :=
begin
  apply exists.intro 0,
  refl
end

/-! ## Reasoning about Equality

*Syntactic* equality:
  x = x
  [2, 1, 3] = [2, 1, 3]

*Definitional* equality (*intensional*, *up to computation*):
  2 + 2 = 4
  quicksort [2, 1, 3] = mergesort [2, 1, 3]
  all of the `by refl` examples below

*Propositional* equality (*provable*):
  x + y = y + x
  quicksort = mergesort
 -/

/-! `refl` proves `l = r`, where the two sides are equal up to
computation. Computation means unfolding of definitions, β-reduction
(application of λ to an argument), `let`, and more. -/

lemma α_example {α β : Type} (f : α → β) :
  (λx, f x) = (λy, f y) :=
begin
  refl
end

lemma α_example₂ {α β : Type} (f : α → β) :
  (λx, f x) = (λy, f y) :=
by refl

lemma β_example {α β : Type} (f : α → β) (a : α) :
  (λx, f x) a = f a :=
by refl

lemma δ_example :
  double 5 = 5 + 5 :=
by refl

lemma ζ_example :
  (let n : ℕ := 2 in n + n) = 2 + 2 :=
by refl

lemma η_example {α β : Type} (f : α → β) :
  (λx, f x) = f :=
by refl

inductive my_prod (α β : Type) : Type
| mk : α → β → my_prod

def my_prod.first {α β : Type} : my_prod α β → α
| (my_prod.mk a b) := a

lemma ι_example {α β : Type} (a : α) (b : β) :
  my_prod.first (my_prod.mk a b) = a :=
by refl

/-!

Which ones of these are *reduction rules*?

-/

#check eq.refl
#check eq.symm
#check eq.trans
#check eq.subst

/-! The above rules can be used directly: -/

lemma cong_fst_arg {α : Type} (a a' b : α)
    (f : α → α → α) (ha : a = a') :
  f a b = f a' b :=
begin
  apply eq.subst ha,
  apply eq.refl
end

lemma cong_two_args {α : Type} (a a' b b' : α)
    (f : α → α → α) (ha : a = a') (hb : b = b') :
  f a b = f a' b' :=
begin
  apply eq.subst ha,
  apply eq.subst hb,
  apply eq.refl
end

/-! `rw` applies a single equation as a left-to-right rewrite rule, once. To
apply an equation right-to-left, prefix its name with `←`. -/

lemma cong_two_args₂ {α : Type} (a a' b b' : α)
    (f : α → α → α) (ha : a = a') (hb : b = b') :
  f a b = f a' b' :=
begin
  rw ha,
  rw hb
end

#check add_comm
#check add_assoc

lemma nat_comm_example (a b c : ℕ) :
  a + b + c = c + b + a :=
begin
  rw add_comm,
  rw add_comm a,
  rw add_assoc
end

lemma nat_comm_example₂ (a b c : ℕ) :
  a + b + c = c + b + a :=
begin
  rw [add_comm, add_comm a, add_assoc]
end

lemma double_example (n : ℕ) :
  double n = n + n + 0 :=
begin
  rw double,
  refl
end

lemma a_proof_of_negation₃ (a : Prop) :
  a → ¬¬ a :=
begin
  rw not_def,
  rw not_def,
  intro ha,
  intro hna,
  apply hna,
  exact ha
end

/-! `simp` applies a standard set of rewrite rules (the __simp set__)
exhaustively. The set can be extended using the `@[simp]` attribute. Lemmas can
be temporarily added to the simp set with the syntax
`simp [_lemma₁_, …, _lemmaN_]`. -/

lemma cong_two_args_etc {α : Type} (a a' b b' : α)
    (g : α → α → ℕ → α) (ha : a = a') (hb : b = b') :
  g a b (1 + 1) = g a' b' 2 :=
by simp [ha, hb]

/-! `cc` applies __congruence closure__ to derive new equalities. -/

lemma cong_two_args₃ {α : Type} (a a' b b' : α)
    (f : α → α → α) (ha : a = a') (hb : b = b') :
  f a b = f a' b' :=
by cc

/-! `cc` can also reason up to associativity and commutativity of `+`, `*`,
and other binary operators. -/

lemma cong_assoc_comm (a a' b c : ℝ) (f : ℝ → ℝ)
    (ha : a = a') :
  f (a + b + c) = f (c + b + a') :=
by cc

/-! ## Proofs by Mathematical Induction

`induction'` performs induction on the specified variable. It gives rise to one
subgoal per constructor. -/

lemma add_zero (n : ℕ) :
  add 0 n = n :=
begin
  induction' n,
  { refl },
  { simp [add, ih] }
end

/-! We use `induction'`, a variant of Lean's built-in `induction` tactic. The
two tactics are similar, but `induction'` is more user-friendly. -/

lemma add_succ (i j : ℕ) :
  add (nat.succ i) j = nat.succ (add i j) :=
begin
  induction' j,
  { refl },
  { simp [add, ih] }
end

lemma add_comm (i j : ℕ) :
  add i j = add j i :=
begin
  induction' j,
  { simp [add, add_zero] },
  { simp [add, add_succ, ih] }
end

lemma add_assoc (i j k : ℕ) :
  add (add i j) k = add i (add j k) :=
begin
  induction' k,
  { refl },
  { simp [add, ih] }
end

/-! `cc` is extensible. We can register `add` as a commutative and associative
operator using the type class instance mechanism (explained in lecture 4). This
is useful for the `cc` invocation below. -/

@[instance] def add.is_commutative : is_commutative ℕ add :=
{ comm := add_comm }

@[instance] def add.is_associative : is_associative ℕ add :=
{ assoc := add_assoc }

lemma mul_add (i j k : ℕ) :
  mul i (add j k) = add (mul i j) (mul i k) :=
begin
  induction' k,
  { refl },
  { simp [add, mul, ih],
    cc }
end

/-! ## Cleanup Tactics

`rename` changes the name of a variable or hypothesis.

`clear` removes unused variables or hypotheses. -/

lemma cleanup_example (a b c : Prop) (ha : a) (hb : b)
    (hab : a → b) (hbc : b → c) :
  c :=
begin
  clear ha hab a,
  apply hbc,
  clear hbc c,
  rename hb h,
  exact h
end

end backward_proofs

end LoVe


import tactic

variables (x y : ℕ)

open nat

theorem Q1a : x + y = y + x :=
begin
  induction y with d hd,
  { rw [add_zero, zero_add] },
  { rw [add_succ, succ_add, hd] }
end

theorem Q1b : x + y = x → y = 0 :=
begin
  intro h,
  induction x with d hd,
  { convert h, rw zero_add },
  { apply hd,
    rw succ_add at h,
    rw ← succ_inj',
    assumption,
  }
end

theorem Q1c : x + y = 0 → x = 0 ∧ y = 0 :=
begin
  intro h,
  induction y with d hd,
  { split,
    { exact h },
    { refl },
  },
  { rw add_succ at h,
    exfalso,
    apply succ_ne_zero (x + d),
    assumption },
end

theorem Q1d : x * y = y * x :=
begin
  induction y with d hd,
  { rw [mul_zero, zero_mul]},
  { rw [mul_succ, succ_mul, hd]},
end


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

! This file was ported from Lean 3 source module analysis.complex.re_im_topology
! leanprover-community/mathlib commit 468b141b14016d54b479eb7a0fff1e360b7e3cf6
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Analysis.Complex.Basic
import Mathbin.Topology.FiberBundle.IsHomeomorphicTrivialBundle

/-!
# Closure, interior, and frontier of preimages under `re` and `im`

In this fact we use the fact that `ℂ` is naturally homeomorphic to `ℝ × ℝ` to deduce some
topological properties of `complex.re` and `complex.im`.

## Main statements

Each statement about `complex.re` listed below has a counterpart about `complex.im`.

* `complex.is_homeomorphic_trivial_fiber_bundle_re`: `complex.re` turns `ℂ` into a trivial
  topological fiber bundle over `ℝ`;
* `complex.is_open_map_re`, `complex.quotient_map_re`: in particular, `complex.re` is an open map
  and is a quotient map;
* `complex.interior_preimage_re`, `complex.closure_preimage_re`, `complex.frontier_preimage_re`:
  formulas for `interior (complex.re ⁻¹' s)` etc;
* `complex.interior_set_of_re_le` etc: particular cases of the above formulas in the cases when `s`
  is one of the infinite intervals `set.Ioi a`, `set.Ici a`, `set.Iio a`, and `set.Iic a`,
  formulated as `interior {z : ℂ | z.re ≤ a} = {z | z.re < a}` etc.

## Tags

complex, real part, imaginary part, closure, interior, frontier
-/

open Set

noncomputable section

namespace Complex

/-- `complex.re` turns `ℂ` into a trivial topological fiber bundle over `ℝ`. -/
theorem isHomeomorphicTrivialFiberBundle_re : IsHomeomorphicTrivialFiberBundle ℝ re :=
  ⟨equivRealProdClm.toHomeomorph, fun z => rfl⟩
#align complex.is_homeomorphic_trivial_fiber_bundle_re Complex.isHomeomorphicTrivialFiberBundle_re

/-- `complex.im` turns `ℂ` into a trivial topological fiber bundle over `ℝ`. -/
theorem isHomeomorphicTrivialFiberBundle_im : IsHomeomorphicTrivialFiberBundle ℝ im :=
  ⟨equivRealProdClm.toHomeomorph.trans (Homeomorph.prodComm ℝ ℝ), fun z => rfl⟩
#align complex.is_homeomorphic_trivial_fiber_bundle_im Complex.isHomeomorphicTrivialFiberBundle_im

theorem isOpenMap_re : IsOpenMap re :=
  isHomeomorphicTrivialFiberBundle_re.isOpenMap_proj
#align complex.is_open_map_re Complex.isOpenMap_re

theorem isOpenMap_im : IsOpenMap im :=
  isHomeomorphicTrivialFiberBundle_im.isOpenMap_proj
#align complex.is_open_map_im Complex.isOpenMap_im

theorem quotientMap_re : QuotientMap re :=
  isHomeomorphicTrivialFiberBundle_re.quotientMap_proj
#align complex.quotient_map_re Complex.quotientMap_re

theorem quotientMap_im : QuotientMap im :=
  isHomeomorphicTrivialFiberBundle_im.quotientMap_proj
#align complex.quotient_map_im Complex.quotientMap_im

theorem interior_preimage_re (s : Set ℝ) : interior (re ⁻¹' s) = re ⁻¹' interior s :=
  (isOpenMap_re.preimage_interior_eq_interior_preimage continuous_re _).symm
#align complex.interior_preimage_re Complex.interior_preimage_re

theorem interior_preimage_im (s : Set ℝ) : interior (im ⁻¹' s) = im ⁻¹' interior s :=
  (isOpenMap_im.preimage_interior_eq_interior_preimage continuous_im _).symm
#align complex.interior_preimage_im Complex.interior_preimage_im

theorem closure_preimage_re (s : Set ℝ) : closure (re ⁻¹' s) = re ⁻¹' closure s :=
  (isOpenMap_re.preimage_closure_eq_closure_preimage continuous_re _).symm
#align complex.closure_preimage_re Complex.closure_preimage_re

theorem closure_preimage_im (s : Set ℝ) : closure (im ⁻¹' s) = im ⁻¹' closure s :=
  (isOpenMap_im.preimage_closure_eq_closure_preimage continuous_im _).symm
#align complex.closure_preimage_im Complex.closure_preimage_im

theorem frontier_preimage_re (s : Set ℝ) : frontier (re ⁻¹' s) = re ⁻¹' frontier s :=
  (isOpenMap_re.preimage_frontier_eq_frontier_preimage continuous_re _).symm
#align complex.frontier_preimage_re Complex.frontier_preimage_re

theorem frontier_preimage_im (s : Set ℝ) : frontier (im ⁻¹' s) = im ⁻¹' frontier s :=
  (isOpenMap_im.preimage_frontier_eq_frontier_preimage continuous_im _).symm
#align complex.frontier_preimage_im Complex.frontier_preimage_im

@[simp]
theorem interior_setOf_re_le (a : ℝ) : interior { z : ℂ | z.re ≤ a } = { z | z.re < a } := by
  simpa only [interior_Iic] using interior_preimage_re (Iic a)
#align complex.interior_set_of_re_le Complex.interior_setOf_re_le

@[simp]
theorem interior_setOf_im_le (a : ℝ) : interior { z : ℂ | z.im ≤ a } = { z | z.im < a } := by
  simpa only [interior_Iic] using interior_preimage_im (Iic a)
#align complex.interior_set_of_im_le Complex.interior_setOf_im_le

@[simp]
theorem interior_setOf_le_re (a : ℝ) : interior { z : ℂ | a ≤ z.re } = { z | a < z.re } := by
  simpa only [interior_Ici] using interior_preimage_re (Ici a)
#align complex.interior_set_of_le_re Complex.interior_setOf_le_re

@[simp]
theorem interior_setOf_le_im (a : ℝ) : interior { z : ℂ | a ≤ z.im } = { z | a < z.im } := by
  simpa only [interior_Ici] using interior_preimage_im (Ici a)
#align complex.interior_set_of_le_im Complex.interior_setOf_le_im

@[simp]
theorem closure_setOf_re_lt (a : ℝ) : closure { z : ℂ | z.re < a } = { z | z.re ≤ a } := by
  simpa only [closure_Iio] using closure_preimage_re (Iio a)
#align complex.closure_set_of_re_lt Complex.closure_setOf_re_lt

@[simp]
theorem closure_setOf_im_lt (a : ℝ) : closure { z : ℂ | z.im < a } = { z | z.im ≤ a } := by
  simpa only [closure_Iio] using closure_preimage_im (Iio a)
#align complex.closure_set_of_im_lt Complex.closure_setOf_im_lt

@[simp]
theorem closure_setOf_lt_re (a : ℝ) : closure { z : ℂ | a < z.re } = { z | a ≤ z.re } := by
  simpa only [closure_Ioi] using closure_preimage_re (Ioi a)
#align complex.closure_set_of_lt_re Complex.closure_setOf_lt_re

@[simp]
theorem closure_setOf_lt_im (a : ℝ) : closure { z : ℂ | a < z.im } = { z | a ≤ z.im } := by
  simpa only [closure_Ioi] using closure_preimage_im (Ioi a)
#align complex.closure_set_of_lt_im Complex.closure_setOf_lt_im

@[simp]
theorem frontier_setOf_re_le (a : ℝ) : frontier { z : ℂ | z.re ≤ a } = { z | z.re = a } := by
  simpa only [frontier_Iic] using frontier_preimage_re (Iic a)
#align complex.frontier_set_of_re_le Complex.frontier_setOf_re_le

@[simp]
theorem frontier_setOf_im_le (a : ℝ) : frontier { z : ℂ | z.im ≤ a } = { z | z.im = a } := by
  simpa only [frontier_Iic] using frontier_preimage_im (Iic a)
#align complex.frontier_set_of_im_le Complex.frontier_setOf_im_le

@[simp]
theorem frontier_setOf_le_re (a : ℝ) : frontier { z : ℂ | a ≤ z.re } = { z | z.re = a } := by
  simpa only [frontier_Ici] using frontier_preimage_re (Ici a)
#align complex.frontier_set_of_le_re Complex.frontier_setOf_le_re

@[simp]
theorem frontier_setOf_le_im (a : ℝ) : frontier { z : ℂ | a ≤ z.im } = { z | z.im = a } := by
  simpa only [frontier_Ici] using frontier_preimage_im (Ici a)
#align complex.frontier_set_of_le_im Complex.frontier_setOf_le_im

@[simp]
theorem frontier_setOf_re_lt (a : ℝ) : frontier { z : ℂ | z.re < a } = { z | z.re = a } := by
  simpa only [frontier_Iio] using frontier_preimage_re (Iio a)
#align complex.frontier_set_of_re_lt Complex.frontier_setOf_re_lt

@[simp]
theorem frontier_setOf_im_lt (a : ℝ) : frontier { z : ℂ | z.im < a } = { z | z.im = a } := by
  simpa only [frontier_Iio] using frontier_preimage_im (Iio a)
#align complex.frontier_set_of_im_lt Complex.frontier_setOf_im_lt

@[simp]
theorem frontier_setOf_lt_re (a : ℝ) : frontier { z : ℂ | a < z.re } = { z | z.re = a } := by
  simpa only [frontier_Ioi] using frontier_preimage_re (Ioi a)
#align complex.frontier_set_of_lt_re Complex.frontier_setOf_lt_re

@[simp]
theorem frontier_setOf_lt_im (a : ℝ) : frontier { z : ℂ | a < z.im } = { z | z.im = a } := by
  simpa only [frontier_Ioi] using frontier_preimage_im (Ioi a)
#align complex.frontier_set_of_lt_im Complex.frontier_setOf_lt_im

theorem closure_reProdIm (s t : Set ℝ) : closure (s ×ℂ t) = closure s ×ℂ closure t := by
  simpa only [← preimage_eq_preimage equiv_real_prod_clm.symm.to_homeomorph.surjective,
    equiv_real_prod_clm.symm.to_homeomorph.preimage_closure] using @closure_prod_eq _ _ _ _ s t
#align complex.closure_re_prod_im Complex.closure_reProdIm

theorem interior_reProdIm (s t : Set ℝ) : interior (s ×ℂ t) = interior s ×ℂ interior t := by
  rw [re_prod_im, re_prod_im, interior_inter, interior_preimage_re, interior_preimage_im]
#align complex.interior_re_prod_im Complex.interior_reProdIm

theorem frontier_reProdIm (s t : Set ℝ) :
    frontier (s ×ℂ t) = closure s ×ℂ frontier t ∪ frontier s ×ℂ closure t := by
  simpa only [← preimage_eq_preimage equiv_real_prod_clm.symm.to_homeomorph.surjective,
    equiv_real_prod_clm.symm.to_homeomorph.preimage_frontier] using frontier_prod_eq s t
#align complex.frontier_re_prod_im Complex.frontier_reProdIm

theorem frontier_setOf_le_re_and_le_im (a b : ℝ) :
    frontier { z | a ≤ re z ∧ b ≤ im z } = { z | a ≤ re z ∧ im z = b ∨ re z = a ∧ b ≤ im z } := by
  simpa only [closure_Ici, frontier_Ici] using frontier_re_prod_im (Ici a) (Ici b)
#align complex.frontier_set_of_le_re_and_le_im Complex.frontier_setOf_le_re_and_le_im

theorem frontier_setOf_le_re_and_im_le (a b : ℝ) :
    frontier { z | a ≤ re z ∧ im z ≤ b } = { z | a ≤ re z ∧ im z = b ∨ re z = a ∧ im z ≤ b } := by
  simpa only [closure_Ici, closure_Iic, frontier_Ici, frontier_Iic] using
    frontier_re_prod_im (Ici a) (Iic b)
#align complex.frontier_set_of_le_re_and_im_le Complex.frontier_setOf_le_re_and_im_le

end Complex

open Complex Metric

variable {s t : Set ℝ}

theorem IsOpen.reProdIm (hs : IsOpen s) (ht : IsOpen t) : IsOpen (s ×ℂ t) :=
  (hs.Preimage continuous_re).inter (ht.Preimage continuous_im)
#align is_open.re_prod_im IsOpen.reProdIm

theorem IsClosed.reProdIm (hs : IsClosed s) (ht : IsClosed t) : IsClosed (s ×ℂ t) :=
  (hs.Preimage continuous_re).inter (ht.Preimage continuous_im)
#align is_closed.re_prod_im IsClosed.reProdIm

theorem Metric.Bounded.reProdIm (hs : Bounded s) (ht : Bounded t) : Bounded (s ×ℂ t) :=
  antilipschitz_equivRealProd.bounded_preimage (hs.Prod ht)
#align metric.bounded.re_prod_im Metric.Bounded.reProdIm


/-
Copyright (c) 2018 Jan-David Salchow. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jan-David Salchow, Patrick Massot
-/
import topology.subset_properties
import topology.metric_space.basic

/-!
# Sequences in topological spaces

In this file we define sequences in topological spaces and show how they are related to
filters and the topology. In particular, we
* define the sequential closure of a set and prove that it's contained in the closure,
* define a type class "sequential_space" in which closure and sequential closure agree,
* define sequential continuity and show that it coincides with continuity in sequential spaces,
* provide an instance that shows that every first-countable (and in particular metric) space is
  a sequential space.
* define sequential compactness, prove that compactness implies sequential compactness in first
  countable spaces, and prove they are equivalent for uniform spaces having a countable uniformity
  basis (in particular metric spaces).
-/

open set function filter
open_locale topological_space

variables {X Y : Type*}

local notation x ` ⟶ ` a := tendsto x at_top (𝓝 a)

/-! ### Sequential closures, sequential continuity, and sequential spaces. -/
section topological_space
variables [topological_space X] [topological_space Y]

/-- The sequential closure of a set `s : set X` in a topological space `X` is
the set of all `a : X` which arise as limit of sequences in `s`. -/
def seq_closure (s : set X) : set X :=
{a | ∃ x : ℕ → X, (∀ n : ℕ, x n ∈ s) ∧ (x ⟶ a)}

lemma subset_seq_closure (s : set X) : s ⊆ seq_closure s :=
λ a ha, ⟨const ℕ a, λ n, ha, tendsto_const_nhds⟩

/-- A set `s` is sequentially closed if for any converging sequence `x n` of elements of `s`,
the limit belongs to `s` as well. -/
def is_seq_closed (s : set X) : Prop := s = seq_closure s

/-- A convenience lemma for showing that a set is sequentially closed. -/
lemma is_seq_closed_of_def {s : set X}
  (h : ∀ (x : ℕ → X) (a : X), (∀ n : ℕ, x n ∈ s) → (x ⟶ a) → a ∈ s) : is_seq_closed s :=
show s = seq_closure s, from subset.antisymm
  (subset_seq_closure s)
  (show ∀ a, a ∈ seq_closure s → a ∈ s, from
    (assume a ⟨x, _, _⟩, show a ∈ s, from h x a ‹∀ n : ℕ, ((x n) ∈ s)› ‹(x ⟶ a)›))

/-- The sequential closure of a set is contained in the closure of that set.
The converse is not true. -/
lemma seq_closure_subset_closure (s : set X) : seq_closure s ⊆ closure s :=
assume a ⟨x, xM, xa⟩,
mem_closure_of_tendsto xa (eventually_of_forall xM)

/-- A set is sequentially closed if it is closed. -/
lemma is_closed.is_seq_closed {s : set X} (hs : is_closed s) : is_seq_closed s :=
suffices seq_closure s ⊆ s, from (subset_seq_closure s).antisymm this,
calc seq_closure s ⊆ closure s : seq_closure_subset_closure s
               ... = s         : hs.closure_eq

/-- The limit of a convergent sequence in a sequentially closed set is in that set.-/
lemma is_seq_closed.mem_of_tendsto {s : set X} (hs : is_seq_closed s) {x : ℕ → X}
  (hmem : ∀ n, x n ∈ s) {a : X} (ha : (x ⟶ a)) : a ∈ s :=
have a ∈ seq_closure s, from
  show ∃ x : ℕ → X, (∀ n : ℕ, x n ∈ s) ∧ (x ⟶ a), from ⟨x, ‹∀ n, x n ∈ s›, ‹(x ⟶ a)›⟩,
eq.subst (eq.symm ‹is_seq_closed s›) ‹a ∈ seq_closure s›

/-- A sequential space is a space in which 'sequences are enough to probe the topology'. This can be
 formalised by demanding that the sequential closure and the closure coincide. The following
 statements show that other topological properties can be deduced from sequences in sequential
 spaces. -/
class sequential_space (X : Type*) [topological_space X] : Prop :=
(seq_closure_eq_closure : ∀ s : set X, seq_closure s = closure s)

/-- In a sequential space, a set is closed iff it's sequentially closed. -/
lemma is_seq_closed_iff_is_closed [sequential_space X] {s : set X} :
  is_seq_closed s ↔ is_closed s :=
iff.intro
  (assume _, closure_eq_iff_is_closed.mp (eq.symm
    (calc s = seq_closure s : by assumption
        ... = closure s     : sequential_space.seq_closure_eq_closure s)))
  is_closed.is_seq_closed

alias is_seq_closed_iff_is_closed ↔ is_seq_closed.is_closed _

/-- In a sequential space, a point belongs to the closure of a set iff it is a limit of a sequence
taking values in this set. -/
lemma mem_closure_iff_seq_limit [sequential_space X] {s : set X} {a : X} :
  a ∈ closure s ↔ ∃ x : ℕ → X, (∀ n : ℕ, x n ∈ s) ∧ (x ⟶ a) :=
by { rw ← sequential_space.seq_closure_eq_closure, exact iff.rfl }

/-- A function between topological spaces is sequentially continuous if it commutes with limit of
 convergent sequences. -/
def seq_continuous (f : X → Y) : Prop :=
∀ (x : ℕ → X), ∀ {a : X}, (x ⟶ a) → (f ∘ x ⟶ f a)

/- A continuous function is sequentially continuous. -/
protected lemma continuous.seq_continuous {f : X → Y} (hf : continuous f) : seq_continuous f :=
assume x a (_ : x ⟶ a),
have tendsto f (𝓝 a) (𝓝 (f a)), from continuous.tendsto ‹continuous f› a,
show (f ∘ x) ⟶ (f a), from tendsto.comp this ‹(x ⟶ a)›

/-- In a sequential space, continuity and sequential continuity coincide. -/
lemma continuous_iff_seq_continuous {f : X → Y} [sequential_space X] :
  continuous f ↔ seq_continuous f :=
iff.intro
  continuous.seq_continuous
  (assume : seq_continuous f, show continuous f, from
    suffices h : ∀ {s : set Y}, is_closed s → is_seq_closed (f ⁻¹' s), from
      continuous_iff_is_closed.mpr (assume s _, is_seq_closed_iff_is_closed.mp $ h ‹is_closed s›),
    assume s (_ : is_closed s),
      is_seq_closed_of_def $
        assume (x : ℕ → X) a (_ : ∀ n, f (x n) ∈ s) (_ : x ⟶ a),
        have (f ∘ x) ⟶ (f a), from ‹seq_continuous f› x ‹(x ⟶ a)›,
        show f a ∈ s,
          from ‹is_closed s›.is_seq_closed.mem_of_tendsto ‹∀ n, f (x n) ∈ s› ‹(f∘x ⟶ f a)›)

alias continuous_iff_seq_continuous ↔ _ seq_continuous.continuous

end topological_space

namespace topological_space

namespace first_countable_topology

variables [topological_space X] [first_countable_topology X]

/-- Every first-countable space is sequential. -/
@[priority 100] -- see Note [lower instance priority]
instance : sequential_space X :=
⟨show ∀ s, seq_closure s = closure s, from assume s,
  suffices closure s ⊆ seq_closure s,
    from set.subset.antisymm (seq_closure_subset_closure s) this,
  -- For every a ∈ closure s, we need to construct a sequence `x` in `s` that converges to `a`:
  assume (a : X) (ha : a ∈ closure s),
  -- Since we are in a first-countable space, the neighborhood filter around `a` has a decreasing
  -- basis `U` indexed by `ℕ`.
  let ⟨U, hU⟩ := (𝓝 a).exists_antitone_basis in
  -- Since `p ∈ closure M`, there is an element in each `M ∩ U i`
  have ha : ∀ (i : ℕ), ∃ (y : X), y ∈ s ∧ y ∈ U i,
    by simpa using (mem_closure_iff_nhds_basis hU.1).mp ha,
  begin
    -- The axiom of (countable) choice builds our sequence from the later fact
    choose u hu using ha,
    rw forall_and_distrib at hu,
    -- It clearly takes values in `M`
    use [u, hu.1],
    -- and converges to `p` because the basis is decreasing.
    apply hU.tendsto hu.2,
  end⟩

end first_countable_topology

end topological_space

section seq_compact
open topological_space topological_space.first_countable_topology
variables [topological_space X]

/-- A set `s` is sequentially compact if every sequence taking values in `s` has a
converging subsequence. -/
def is_seq_compact (s : set X) :=
∀ ⦃x : ℕ → X⦄, (∀ n, x n ∈ s) → ∃ (a ∈ s) (φ : ℕ → ℕ), strict_mono φ ∧ (x ∘ φ ⟶ a)

/-- A space `X` is sequentially compact if every sequence in `X` has a
converging subsequence. -/
class seq_compact_space (X : Type*) [topological_space X] : Prop :=
(seq_compact_univ : is_seq_compact (univ : set X))

lemma is_seq_compact.subseq_of_frequently_in {s : set X} (hs : is_seq_compact s) {x : ℕ → X}
  (hx : ∃ᶠ n in at_top, x n ∈ s) :
  ∃ (a ∈ s) (φ : ℕ → ℕ), strict_mono φ ∧ (x ∘ φ ⟶ a) :=
let ⟨ψ, hψ, huψ⟩ := extraction_of_frequently_at_top hx, ⟨a, a_in, φ, hφ, h⟩ := hs huψ in
⟨a, a_in, ψ ∘ φ, hψ.comp hφ, h⟩

lemma seq_compact_space.tendsto_subseq [seq_compact_space X] (x : ℕ → X) :
  ∃ a (φ : ℕ → ℕ), strict_mono φ ∧ (x ∘ φ ⟶ a) :=
let ⟨a, _, φ, mono, h⟩ := seq_compact_space.seq_compact_univ (λ n, mem_univ (x n)) in
⟨a, φ, mono, h⟩

section first_countable_topology
variables [first_countable_topology X]
open topological_space.first_countable_topology

lemma is_compact.is_seq_compact {s : set X} (hs : is_compact s) : is_seq_compact s :=
λ x x_in,
let ⟨a, a_in, ha⟩ := @hs (map x at_top) _
  (le_principal_iff.mpr (univ_mem' x_in : _)) in ⟨a, a_in, tendsto_subseq ha⟩

lemma is_compact.tendsto_subseq' {s : set X} {x : ℕ → X} (hs : is_compact s)
  (hx : ∃ᶠ n in at_top, x n ∈ s) :
  ∃ (a ∈ s) (φ : ℕ → ℕ), strict_mono φ ∧ (x ∘ φ ⟶ a) :=
hs.is_seq_compact.subseq_of_frequently_in hx

lemma is_compact.tendsto_subseq {s : set X} {x : ℕ → X} (hs : is_compact s) (hx : ∀ n, x n ∈ s) :
  ∃ (a ∈ s) (φ : ℕ → ℕ), strict_mono φ ∧ (x ∘ φ ⟶ a) :=
hs.is_seq_compact hx

@[priority 100] -- see Note [lower instance priority]
instance first_countable_topology.seq_compact_of_compact [compact_space X] : seq_compact_space X :=
⟨compact_univ.is_seq_compact⟩

lemma compact_space.tendsto_subseq [compact_space X] (x : ℕ → X) :
  ∃ a (φ : ℕ → ℕ), strict_mono φ ∧ (x ∘ φ ⟶ a) :=
seq_compact_space.tendsto_subseq x

end first_countable_topology
end seq_compact

section uniform_space_seq_compact

open_locale uniformity
open uniform_space prod

variables [uniform_space X] {s : set X}

lemma lebesgue_number_lemma_seq {ι : Type*} [is_countably_generated (𝓤 X)] {c : ι → set X}
  (hs : is_seq_compact s) (hc₁ : ∀ i, is_open (c i)) (hc₂ : s ⊆ ⋃ i, c i) :
  ∃ V ∈ 𝓤 X, symmetric_rel V ∧ ∀ x ∈ s, ∃ i, ball x V ⊆ c i :=
begin
  classical,
  obtain ⟨V, hV, Vsymm⟩ :
    ∃ V : ℕ → set (X × X), (𝓤 X).has_antitone_basis V ∧ ∀ n, swap ⁻¹' V n = V n,
      from uniform_space.has_seq_basis X,
  suffices : ∃ n, ∀ x ∈ s, ∃ i, ball x (V n) ⊆ c i,
  { cases this with n hn,
    exact ⟨V n, hV.to_has_basis.mem_of_mem trivial, Vsymm n, hn⟩ },
  by_contradiction H,
  obtain ⟨x, x_in, hx⟩ : ∃ x : ℕ → X, (∀ n, x n ∈ s) ∧ ∀ n i, ¬ ball (x n) (V n) ⊆ c i,
  { push_neg at H,
    choose x hx using H,
    exact ⟨x, forall_and_distrib.mp hx⟩ }, clear H,
  obtain ⟨x₀, x₀_in, φ, φ_mono, hlim⟩ : ∃ (x₀ ∈ s) (φ : ℕ → ℕ), strict_mono φ ∧ (x ∘ φ ⟶ x₀),
    from hs x_in, clear hs,
  obtain ⟨i₀, x₀_in⟩ : ∃ i₀, x₀ ∈ c i₀,
  { rcases hc₂ x₀_in with ⟨_, ⟨i₀, rfl⟩, x₀_in_c⟩,
    exact ⟨i₀, x₀_in_c⟩ }, clear hc₂,
  obtain ⟨n₀, hn₀⟩ : ∃ n₀, ball x₀ (V n₀) ⊆ c i₀,
  { rcases (nhds_basis_uniformity hV.to_has_basis).mem_iff.mp
      (is_open_iff_mem_nhds.mp (hc₁ i₀) _ x₀_in) with ⟨n₀, _, h⟩,
    use n₀,
    rwa ← ball_eq_of_symmetry (Vsymm n₀) at h }, clear hc₁,
  obtain ⟨W, W_in, hWW⟩ : ∃ W ∈ 𝓤 X, W ○ W ⊆ V n₀,
    from comp_mem_uniformity_sets (hV.to_has_basis.mem_of_mem trivial),
  obtain ⟨N, x_φ_N_in, hVNW⟩ : ∃ N, x (φ N) ∈ ball x₀ W ∧ V (φ N) ⊆ W,
  { obtain ⟨N₁, h₁⟩ : ∃ N₁, ∀ n ≥ N₁, x (φ n) ∈ ball x₀ W,
      from tendsto_at_top'.mp hlim _ (mem_nhds_left x₀ W_in),
    obtain ⟨N₂, h₂⟩ : ∃ N₂, V (φ N₂) ⊆ W,
    { rcases hV.to_has_basis.mem_iff.mp W_in with ⟨N, _, hN⟩,
      use N,
      exact subset.trans (hV.antitone $ φ_mono.id_le _) hN },
    have : φ N₂ ≤ φ (max N₁ N₂),
      from φ_mono.le_iff_le.mpr (le_max_right _ _),
    exact ⟨max N₁ N₂, h₁ _ (le_max_left _ _), trans (hV.antitone this) h₂⟩ },
  suffices : ball (x (φ N)) (V (φ N)) ⊆ c i₀,
    from hx (φ N) i₀ this,
  calc
    ball (x $ φ N) (V $ φ N) ⊆ ball (x $ φ N) W : preimage_mono hVNW
                         ... ⊆ ball x₀ (V n₀)   : ball_subset_of_comp_subset x_φ_N_in hWW
                         ... ⊆ c i₀             : hn₀,
end

lemma is_seq_compact.totally_bounded (h : is_seq_compact s) : totally_bounded s :=
begin
  classical,
  apply totally_bounded_of_forall_symm,
  unfold is_seq_compact at h,
  contrapose! h,
  rcases h with ⟨V, V_in, V_symm, h⟩,
  simp_rw [not_subset] at h,
  have : ∀ (t : set X), t.finite → ∃ a, a ∈ s ∧ a ∉ ⋃ y ∈ t, ball y V,
  { intros t ht,
    obtain ⟨a, a_in, H⟩ : ∃ a ∈ s, ∀ x ∈ t, (x, a) ∉ V,
      by simpa [ht] using h t,
    use [a, a_in],
    intro H',
    obtain ⟨x, x_in, hx⟩ := mem_Union₂.mp H',
    exact H x x_in hx },
  cases seq_of_forall_finite_exists this with u hu, clear h this,
  simp [forall_and_distrib] at hu,
  cases hu with u_in hu,
  use [u, u_in], clear u_in,
  intros x x_in φ,
  intros hφ huφ,
  obtain ⟨N, hN⟩ : ∃ N, ∀ p q, p ≥ N → q ≥ N → (u (φ p), u (φ q)) ∈ V,
    from huφ.cauchy_seq.mem_entourage V_in,
  specialize hN N (N+1) (le_refl N) (nat.le_succ N),
  specialize hu (φ $ N+1) (φ N) (hφ $ lt_add_one N),
  exact hu hN,
end

protected lemma is_seq_compact.is_compact [is_countably_generated $ 𝓤 X] (hs : is_seq_compact s) :
  is_compact s :=
begin
  classical,
  rw is_compact_iff_finite_subcover,
  intros ι U Uop s_sub,
  rcases lebesgue_number_lemma_seq hs Uop s_sub with ⟨V, V_in, Vsymm, H⟩,
  rcases totally_bounded_iff_subset.mp hs.totally_bounded V V_in with ⟨t,t_sub, tfin,  ht⟩,
  have : ∀ x : t, ∃ (i : ι), ball x.val V ⊆ U i,
  { rintros ⟨x, x_in⟩,
    exact H x (t_sub x_in) },
  choose i hi using this,
  haveI : fintype t := tfin.fintype,
  use finset.image i finset.univ,
  transitivity ⋃ y ∈ t, ball y V,
  { intros x x_in,
    specialize ht x_in,
    rw mem_Union₂ at *,
    simp_rw ball_eq_of_symmetry Vsymm,
    exact ht },
  { refine Union₂_mono' (λ x x_in, _),
    exact ⟨i ⟨x, x_in⟩, finset.mem_image_of_mem _ (finset.mem_univ _), hi ⟨x, x_in⟩⟩ },
end

/-- A version of Bolzano-Weistrass: in a uniform space with countably generated uniformity filter
(e.g., in a metric space), a set is compact if and only if it is sequentially compact. -/
protected lemma uniform_space.compact_iff_seq_compact [is_countably_generated $ 𝓤 X] :
 is_compact s ↔ is_seq_compact s :=
⟨λ H, H.is_seq_compact, λ H, H.is_compact⟩

lemma uniform_space.compact_space_iff_seq_compact_space [is_countably_generated $ 𝓤 X] :
  compact_space X ↔ seq_compact_space X :=
have key : is_compact (univ : set X) ↔ is_seq_compact univ := uniform_space.compact_iff_seq_compact,
⟨λ ⟨h⟩, ⟨key.mp h⟩, λ ⟨h⟩, ⟨key.mpr h⟩⟩

end uniform_space_seq_compact

section metric_seq_compact

variables [pseudo_metric_space X]
open metric

lemma seq_compact.lebesgue_number_lemma_of_metric {ι : Sort*} {c : ι → set X}
  {s : set X} (hs : is_seq_compact s) (hc₁ : ∀ i, is_open (c i)) (hc₂ : s ⊆ ⋃ i, c i) :
  ∃ δ > 0, ∀ a ∈ s, ∃ i, ball a δ ⊆ c i :=
lebesgue_number_lemma_of_metric hs.is_compact hc₁ hc₂

variables [proper_space X] {s : set X}

/-- A version of **Bolzano-Weistrass**: in a proper metric space (eg. $ℝ^n$),
every bounded sequence has a converging subsequence. This version assumes only
that the sequence is frequently in some bounded set. -/
lemma tendsto_subseq_of_frequently_bounded (hs : bounded s)
  {x : ℕ → X} (hx : ∃ᶠ n in at_top, x n ∈ s) :
  ∃ a ∈ closure s, ∃ φ : ℕ → ℕ, strict_mono φ ∧ (x ∘ φ ⟶ a) :=
have hcs : is_seq_compact (closure s), from hs.is_compact_closure.is_seq_compact,
have hu' : ∃ᶠ n in at_top, x n ∈ closure s, from hx.mono (λ n hn, subset_closure hn),
hcs.subseq_of_frequently_in hu'

/-- A version of Bolzano-Weistrass: in a proper metric space (eg. $ℝ^n$),
every bounded sequence has a converging subsequence. -/
lemma tendsto_subseq_of_bounded (hs : bounded s) {x : ℕ → X} (hx : ∀ n, x n ∈ s) :
  ∃ a ∈ closure s, ∃ φ : ℕ → ℕ, strict_mono φ ∧ (x ∘ φ ⟶ a) :=
tendsto_subseq_of_frequently_bounded hs $ frequently_of_forall hx

end metric_seq_compact


/-
Copyright (c) 2020 Kenny Lau. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kenny Lau, Eric Wieser
-/

import algebra.char_p.basic
import ring_theory.ideal.quotient

/-!
# Characteristic of quotients rings
-/

universes u v

namespace char_p

theorem quotient (R : Type u) [comm_ring R] (p : ℕ) [hp1 : fact p.prime] (hp2 : ↑p ∈ nonunits R) :
  char_p (R ⧸ (ideal.span {p} : ideal R)) p :=
have hp0 : (p : R ⧸ (ideal.span {p} : ideal R)) = 0,
  from (ideal.quotient.mk (ideal.span {p} : ideal R)).map_nat_cast p ▸
    ideal.quotient.eq_zero_iff_mem.2 (ideal.subset_span $ set.mem_singleton _),
ring_char.of_eq $ or.resolve_left ((nat.dvd_prime hp1.1).1 $ ring_char.dvd hp0) $ λ h1,
hp2 $ is_unit_iff_dvd_one.2 $ ideal.mem_span_singleton.1 $ ideal.quotient.eq_zero_iff_mem.1 $
@@subsingleton.elim (@@char_p.subsingleton _ $ ring_char.of_eq h1) _ _

/-- If an ideal does not contain any coercions of natural numbers other than zero, then its quotient
inherits the characteristic of the underlying ring. -/
lemma quotient' {R : Type*} [comm_ring R] (p : ℕ) [char_p R p] (I : ideal R)
  (h : ∀ x : ℕ, (x : R) ∈ I → (x : R) = 0) :
  char_p (R ⧸ I) p :=
⟨λ x, begin
  rw [←cast_eq_zero_iff R p x, ←(ideal.quotient.mk I).map_nat_cast],
  refine quotient.eq'.trans (_ : ↑x - 0 ∈ I ↔ _),
  rw sub_zero,
  exact ⟨h x, λ h', h'.symm ▸ I.zero_mem⟩,
end⟩

end char_p


import algebra.pi_instances
set_option old_structure_cmd true

universe variables u

/-
  A non-unital ring or rng is a ring without unit. According to ncatlab
  https://ncatlab.org/nlab/show/nonunital+ring

  Definition 2.1. A nonunital ring or rng is a set R with operations of addition and multiplication, such that:

  * R is a semigroup under multiplication;
  * R is an abelian group under addition;
  * multiplication distributes over addition.
-/

/- Define rng in Lean. Use a definition similar to `ring`. Replace the following definition with the correct definition. -/
class rng (α : Type u) extends ring α

/- Formulate and show that every ring is a rng. -/

variables {α : Type u} [rng α] {a b c x y z w : α} {n m : ℤ}

/- Prove the following lemma. -/
lemma distrib2 : (x + y) * (z + w) = x * z + x * w + y * z + y * w :=
sorry

/- Construct the following instance.
For each field, first write a lemma that proves that field.
There are very similar lemmas proven for rings, try to copy those and modify where needed -/

instance rng.mul_zero_class : mul_zero_class α :=
sorry

/- Formulate and prove lemmas that state -(a * b) = a * -b = -a * b (see mathlib for similar lemmas) -/

/- Prove that a * (b - c) = a * b - a * c (see mathlib for similar lemmas) -/

/- Prove the following lemmas by induction on n.
  Try to prove one with the `induction` tactic, and one with the equation compiler. -/
lemma rng.nat_smul_mul_left : n • a * b = n • (a * b) := sorry

lemma rng.nat_smul_mul_right : a * n • b = n • (a * b) := sorry

/-
  The following lemmas are tricky, so they are provided for you.
  You can uncomment them in VSCode by selecting them and pressing `ctrl+/`
  The proof assumes that `rng.neg_mul_eq_neg_mul` is the name of the lemma that `-(a * b) = -a * b`.
  Make sure you understand the proofs.
-/
-- lemma rng.smul_mul_left : ∀{n : ℤ}, n • a * b = n • (a * b)
-- | (n : ℕ) := rng.nat_smul_mul_left
-- | -[1+n]  := show (-↑(n+1) : ℤ) • a * b  = (-↑(n+1) : ℤ) • (a * b),
--   by { rw [neg_smul, neg_smul, ←rng.neg_mul_eq_neg_mul], congr' 1,
--        apply rng.nat_smul_mul_left }

-- lemma rng.smul_mul_right : ∀{n : ℤ}, a * n • b = n • (a * b)
-- | (n : ℕ) := rng.nat_smul_mul_right
-- | -[1+n]  := show a * (-↑(n+1) : ℤ) • b  = (-↑(n+1) : ℤ) • (a * b),
--   by { rw [neg_smul, neg_smul, ←rng.neg_mul_eq_mul_neg], congr' 1,
--        apply rng.nat_smul_mul_right }

/- Prove that n • (m • a) = m • (n • a) -/

/- We will now construct the unitisation of a non-unital ring. See ncatlab:

Definition 3.1. Given a non-unital ring A, then its unitisation is the ring F(A) obtained by freely adjoining an identity element, hence the ring whose underlying abelian group is the direct sum Z⊕A of A with the integers, and whose product operation is defined by

(n₁,a₁)(n₂,a₂) = (n₁n₂, n₁a₂ + n₂a₁ + a₁a₂),
where for n∈Z and a∈A we set na = a + a + ⋯ + a with n summands.
-/
def unitisation (α : Type u) : Type u := ℤ × α

namespace unitisation

/- Define multiplication so that (n₁,a₁)(n₂,a₂) = (n₁n₂, n₁a₂ + n₂a₁ + a₁a₂).
  Use `•` to multiply a integer with a element of α. -/
instance : has_mul (unitisation α) :=
sorry

/- Fill in the blanks, Prove the following rewrite rules. Also add them as simplification lemmas. -/
lemma mk_mul_mk : (⟨n, a⟩ * ⟨m, b⟩ : unitisation α) = sorry :=
sorry

lemma mul_fst {x y : unitisation α} : (x * y).fst = sorry :=
sorry

lemma mul_snd {x y : unitisation α} :
  (x * y).snd = sorry :=
sorry

/- Now prove that this is a ring. Lean already knows it is a commutative group under addition using instance `prod.add_comm_group`. -/
instance : ring (unitisation α) := sorry

end unitisation


-- TODO ask Mario why instance : distrib nat            := by apply_instance
-- is in lemmas.lean in core nat

import data.nat.dist -- distance function
import data.nat.gcd -- gcd
import data.nat.modeq -- modular arithmetic
import data.nat.prime -- prime number stuff
import data.nat.sqrt  -- square roots

open nat

example : fact 4 = 24 := rfl -- factorial

example (a : ℕ) : fact a > 0 := fact_pos a

example : dist 6 4 = 2 := rfl -- distance function

example (a b : ℕ) : a ≠ b → dist a b > 0 := dist_pos_of_ne

example (a b : ℕ) : gcd a b ∣ a ∧ gcd a b ∣ b := gcd_dvd a b

example : lcm 6 4 = 12 := rfl

example (a b : ℕ) : lcm a b = lcm b a := lcm_comm a b
example (a b : ℕ) : gcd a b * lcm a b = a * b := gcd_mul_lcm a b

example (a b : ℕ) : (∀ k : ℕ, k > 1 → k ∣ a → ¬ (k ∣ b) ) → coprime a b := coprime_of_dvd

-- type the congruence symbol with \==

example : 5 ≡ 8 [MOD 3] := rfl

example (a b c d m : ℕ) : a ≡ b [MOD m] → c ≡ d [MOD m] → a * c ≡ b * d [MOD m] := modeq.modeq_mul

-- nat.sqrt is integer square root (it rounds down).

#eval sqrt 1000047
-- returns 1000

example (a : ℕ) : sqrt (a * a) = a := sqrt_eq a

example (a b : ℕ) : sqrt a < b ↔ a < b * b := sqrt_lt

-- nat.prime n returns whether n is prime or not.
-- We can prove 59 is prime if we first tell Lean that primality
-- is decidable. But it's slow because the algorithms are
-- not optimised for the kernel.

instance : decidable (prime 59) := decidable_prime_1 59
example : prime 59 := dec_trivial

example (p : ℕ) : prime p → p ≥ 2 := prime.ge_two

example (p : ℕ) : prime p ↔ p ≥ 2 ∧ ∀ m, 2 ≤ m → m ≤ sqrt p → ¬ (m ∣ p) := prime_def_le_sqrt

example (p : ℕ) : prime p → (∀ m, coprime p m ∨ p ∣ m) := coprime_or_dvd_of_prime

example : ∀ n, ∃ p, p ≥ n ∧ prime p := exists_infinite_primes

-- min_fac returns the smallest prime factor of n (or junk if it doesn't have one)

example : min_fac 12 = 2 := rfl

-- `factors n` is the prime factorization of `n`, listed in increasing order.
-- As far as I can see this isn't decidable, and doesn't seem to reduce either.
-- But we can evaluate it in the virtual machine using #eval .

#eval factors (2^32+1)
-- [641, 6700417]


/-
Copyright (c) 2022 Bhavik Mehta. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Bhavik Mehta
-/

import algebra.is_prime_pow
import number_theory.arithmetic_function
import analysis.special_functions.log

/-!
# The von Mangoldt Function

In this file we define the von Mangoldt function: the function on natural numbers that returns
`log p` if the input can be expressed as `p^k` for a prime `p`.

## Main Results

The main definition for this file is

- `nat.arithmetic_function.von_mangoldt`: The von Mangoldt function `Λ`.

We then prove the classical summation property of the von Mangoldt function in
`nat.arithmetic_function.von_mangoldt_sum`, that `∑ i in n.divisors, Λ i = real.log n`, and use this
to deduce alternative expressions for the von Mangoldt function via Möbius inversion, see
`nat.arithmetic_function.sum_moebius_mul_log_eq`.

## Notation

We use the standard notation `Λ` to represent the von Mangoldt function.

-/

namespace nat
namespace arithmetic_function

open finset

open_locale arithmetic_function

/-- `log` as an arithmetic function `ℕ → ℝ`. Note this is in the `nat.arithmetic_function`
namespace to indicate that it is bundled as an `arithmetic_function` rather than being the usual
real logarithm. -/
noncomputable def log : arithmetic_function ℝ :=
⟨λ n, real.log n, by simp⟩

@[simp] lemma log_apply {n : ℕ} : log n = real.log n := rfl

/--
The `von_mangoldt` function is the function on natural numbers that returns `log p` if the input can
be expressed as `p^k` for a prime `p`.
In the case when `n` is a prime power, `min_fac` will give the appropriate prime, as it is the
smallest prime factor.

In the `arithmetic_function` locale, we have the notation `Λ` for this function.
-/
noncomputable def von_mangoldt : arithmetic_function ℝ :=
⟨λ n, if is_prime_pow n then real.log (min_fac n) else 0, if_neg not_is_prime_pow_zero⟩

localized "notation `Λ` := nat.arithmetic_function.von_mangoldt" in arithmetic_function

lemma von_mangoldt_apply {n : ℕ} :
  Λ n = if is_prime_pow n then real.log (min_fac n) else 0 := rfl

@[simp] lemma von_mangoldt_apply_one : Λ 1 = 0 := by simp [von_mangoldt_apply]

@[simp] lemma von_mangoldt_nonneg {n : ℕ} : 0 ≤ Λ n :=
begin
  rw [von_mangoldt_apply],
  split_ifs,
  { exact real.log_nonneg (one_le_cast.2 (nat.min_fac_pos n)) },
  refl
end

lemma von_mangoldt_apply_pow {n k : ℕ} (hk : k ≠ 0) : Λ (n ^ k) = Λ n :=
by simp only [von_mangoldt_apply, is_prime_pow_pow_iff hk, pow_min_fac hk]

lemma von_mangoldt_apply_prime {p : ℕ} (hp : p.prime) : Λ p = real.log p :=
by rw [von_mangoldt_apply, prime.min_fac_eq hp, if_pos (nat.prime_iff.1 hp).is_prime_pow]

open_locale big_operators

lemma von_mangoldt_sum {n : ℕ} :
  ∑ i in n.divisors, Λ i = real.log n :=
begin
  refine rec_on_prime_coprime _ _ _ n,
  { simp },
  { intros p k hp,
    rw [sum_divisors_prime_pow hp, cast_pow, real.log_pow, finset.sum_range_succ', pow_zero,
      von_mangoldt_apply_one],
    simp [von_mangoldt_apply_pow (nat.succ_ne_zero _), von_mangoldt_apply_prime hp] },
  intros a b ha' hb' hab ha hb,
  simp only [von_mangoldt_apply, ←sum_filter] at ha hb ⊢,
  rw [mul_divisors_filter_prime_pow hab, filter_union,
    sum_union (disjoint_divisors_filter_prime_pow hab), ha, hb, nat.cast_mul,
    real.log_mul (nat.cast_ne_zero.2 ha'.ne') (nat.cast_ne_zero.2 hb'.ne')],
end

@[simp] lemma von_mangoldt_mul_zeta : Λ * ζ = log :=
by { ext n, rw [coe_mul_zeta_apply, von_mangoldt_sum], refl }

@[simp] lemma zeta_mul_von_mangoldt : (ζ : arithmetic_function ℝ) * Λ = log :=
by { rw [mul_comm], simp }

@[simp]

@[simp] lemma moebius_mul_log_eq_von_mangoldt : (μ : arithmetic_function ℝ) * log = Λ :=
by { rw [mul_comm], simp }

lemma sum_moebius_mul_log_eq {n : ℕ} :
  ∑ d in n.divisors, (μ d : ℝ) * log d = - Λ n :=
begin
  simp only [←log_mul_moebius_eq_von_mangoldt, mul_comm log, mul_apply, log_apply, int_coe_apply,
    ←finset.sum_neg_distrib, neg_mul_eq_mul_neg],
  rw sum_divisors_antidiagonal (λ i j, (μ i : ℝ) * -real.log j),
  have : ∑ (i : ℕ) in n.divisors, (μ i : ℝ) * -real.log (n / i : ℕ) =
         ∑ (i : ℕ) in n.divisors, ((μ i : ℝ) * real.log i - μ i * real.log n),
  { apply sum_congr rfl,
    simp only [and_imp, int.cast_eq_zero, mul_eq_mul_left_iff, ne.def, neg_inj, mem_divisors],
    intros m mn hn,
    have : (m : ℝ) ≠ 0,
    { rw [cast_ne_zero],
      rintro rfl,
      exact hn (by simpa using mn) },
    rw [nat.cast_dvd mn this, real.log_div (cast_ne_zero.2 hn) this, neg_sub, mul_sub] },
  rw [this, sum_sub_distrib, ←sum_mul, ←int.cast_sum, ←coe_mul_zeta_apply, eq_comm, sub_eq_self,
    moebius_mul_coe_zeta, mul_eq_zero, int.cast_eq_zero],
  rcases eq_or_ne n 1 with hn | hn;
  simp [hn],
end

end arithmetic_function
end nat


import algebra.group.basic

lemma right_inverse_eq_left_inverse {T : Type} [monoid T]
  {a b c : T} (inv_right : a * b = 1) (inv_left : c * a = 1) :
  b = c :=
calc b = 1 * b : (one_mul b).symm
... = (c * a) * b : by rw inv_left
... = c * (a * b) : mul_assoc c a b
... = c * 1 : by rw inv_right
... = c : mul_one c

lemma right_inverse_unique {T : Type} [group T]
  {a b c : T} (inv_ab : a * b = 1) (inv_ac : a * c = 1) :
  b = c :=
calc b = 1 * b : (one_mul b).symm
... = (a⁻¹ * a) * b : by rw mul_left_inv
... = a⁻¹ * (a * b) : mul_assoc a⁻¹ a b
... = a⁻¹ * 1 : by rw inv_ab
... = a⁻¹ * (a * c) : by rw inv_ac
... = (a⁻¹ * a) * c : (mul_assoc a⁻¹ a c).symm
... = 1 * c : by rw mul_left_inv
... = c : one_mul c

lemma right_inverse_unique_aux {T : Type} [group T]
  {a b : T} (inv_ab : a * b = 1) :
  b = a⁻¹ :=
calc b = 1 * b : (one_mul b).symm
... = (a⁻¹ * a) * b : by rw mul_left_inv
... = a⁻¹ * (a * b) : mul_assoc a⁻¹ a b
... = a⁻¹ * 1 : by rw inv_ab
... = a⁻¹ : mul_one a⁻¹

lemma right_inverse_unique' {T : Type} [group T]
  {a b c : T} (inv_ab : a * b = 1) (inv_ac : a * c = 1) :
  b = c :=
begin
  rw right_inverse_unique_aux inv_ab,
  rw right_inverse_unique_aux inv_ac,
end


import tactic
import tactic.suggest
import tactic.nth_rewrite
import data.fintype.basic
import data.setoid.partition

variables {α : Type} {β : Type} {γ : Type}

open_locale classical
open_locale big_operators

lemma lift_disjoint_to_finset (s1 s2 : set α) [fintype α] (h : disjoint s1 s2) : disjoint s1.to_finset s2.to_finset :=
begin
    intros a hinter,
    have hset : a ∈ ∅,
    {
        rw ←set.bot_eq_empty,
        rw ←le_bot_iff.mp h,
        apply (set.mem_inter_iff a s1 s2).mpr ,
        split,
        exact set.mem_to_finset.mp (finset.mem_of_mem_inter_left hinter),
        exact set.mem_to_finset.mp (finset.mem_of_mem_inter_right hinter),
    },
    exfalso,
    apply set.not_mem_empty a hset,
end

lemma to_finset_bind_eq_univ_of_partition {c : set (set α)} [fintype α] (h : setoid.is_partition c) :
    (set.to_finset c).bind(λ (x : set α), x.to_finset) = finset.univ :=
begin
    ext,
    split,
    {
        simp,
    },
    {
        intro ha,
        rw finset.mem_bind,
        cases h.2 a with s hs,
        simp at hs,
        use s,
        rcases hs with ⟨⟨hmemsc, hmemas⟩, _⟩,
        split,
        {
            exact set.mem_to_finset.mpr hmemsc,
        },
        {
            exact set.mem_to_finset.mpr hmemas,
        }
    }
end

lemma sum_card_partition {c : set (set α)} [fintype α] (h : setoid.is_partition c):
    fintype.card α = ∑ x in (set.to_finset c), (set.to_finset x).card := begin
    /- proof idea: |α| = ∑ x in α, 1 = ∑ x in (⋃ s in c, s), 1
                       = ∑ s in c, (∑ x in s, 1)
                       = ∑ s in c, |s|  -/
    conv
    begin
        to_rhs,
        congr,
        skip,
        funext,
        rw finset.card_eq_sum_ones,
    end,
    have hdisjoint : ∀ (x : set α), x ∈ c.to_finset → ∀ (y : set α), y ∈ c.to_finset → x ≠ y → disjoint x.to_finset y.to_finset,
    {
        intros x hx y hy hne,
        apply lift_disjoint_to_finset,
        exact setoid.is_partition.pairwise_disjoint h x (set.mem_to_finset.mp hx) y (set.mem_to_finset.mp hy) hne,
    },
    rw ← finset.sum_bind hdisjoint,
    rw ←finset.card_eq_sum_ones,
    rw to_finset_bind_eq_univ_of_partition h,
    exact finset.card_univ.symm,
end


import data.nat.digits

open nat
open int

/-
(a) Prove the "rule of 9": an integer is divisible by 9 if and only if the sum of its digits is divisible by 9.
(b) Prove the "rule of 11" stated in Example 13.6. Use this rule to decide in your head whether the number 82918073579 is divisible by 11.
-/

lemma part_a (n : ℤ) : 9 ∣ n ↔ 9 ∣ (digits 10 n.nat_abs).sum :=
begin
  sorry
end

lemma part_b (n : ℤ) : 11 ∣ n ↔ (11 : ℤ) ∣ ((digits 10 n.nat_abs).map (λ (n : ℕ), ↑n)).alternating_sum :=
begin
  sorry
end


import MyNat.Addition
import MyNat.Multiplication
import AdditionWorld.Level1 -- zero_add
import AdditionWorld.Level2 -- add_assoc
namespace MyNat
open MyNat

/-!
# Multiplication World

## Level 4: `mul_add`

Where are we going? Well we want to prove `mul_comm`
and `mul_assoc`, i.e. that `a * b = b * a` and
`(a * b) * c = a * (b * c)`. But we *also* want to
establish the way multiplication interacts with addition,
i.e. we want to prove that we can "expand out the brackets"
and show `a * (b + c) = (a * b) + (a * c)`.
The technical term for this is "left distributivity of
multiplication over addition" (there is also right distributivity,
which we'll get to later).

Note the name of this proof -- `mul_add`. And note the left
hand side -- `a * (b + c)`, a multiplication and then an addition.
I think `mul_add` is much easier to remember than "left_distrib",
an alternative name for the proof of this lemma.

## Lemma

Multiplication is distributive over addition.
In other words, for all natural numbers `a`, `b` and `t`, we have
` t(a + b) = ta + tb. `
-/

lemma mul_add (t a b : MyNat) : t * (a + b) = t * a + t * b := by
  induction b with
  | zero =>
     rw [zero_is_0, add_zero, mul_zero, add_zero]
  | succ b ih =>
    rw [add_succ]
    rw [mul_succ]
    rw [ih]
    rw [mul_succ]
    rw [add_assoc]

def left_distrib := mul_add -- the "proper" name for this lemma

/-!
Next up is [Multiplication Level 5](./Level5.lean.md).
-/


import algebra.group group_theory.subgroup

variables {G: Type*}

def normaliser (a: G) [group G]: set G :=
  { x: G | a * x = x * a }

theorem Q_13 (a: G) [group G]:
  is_subgroup (normaliser a) := {
  one_mem := calc
    a * 1 = 1 * a: by rw [mul_one, one_mul],

  mul_mem := λ g h hg hh, calc
    a * (g * h) = (a * g) * h  : (mul_assoc _ _ _).symm
    ...         = (g * a) * h  : hg ▸ rfl
    ...         =  g * (a * h) : mul_assoc _ _ _
    ...         =  g * (h * a) : hh ▸ rfl
    ...         = (g * h) * a  : (mul_assoc _ _ _).symm,

  inv_mem := λ g hg, calc
    a * g⁻¹ = (g⁻¹ * g) * (a * g⁻¹)  : (mul_left_inv g).symm ▸ (one_mul _).symm
    ...     =  g⁻¹ * (g * a) * g⁻¹   : by rw [ ←mul_assoc, mul_assoc g⁻¹ ]
    ...     =  g⁻¹ * (a * g) * g⁻¹   : hg ▸ rfl
    ...     = (g⁻¹ * a) * g  * g⁻¹   : mul_assoc g⁻¹ a g ▸ rfl
    ...     =  g⁻¹ * a               : mul_inv_cancel_right (g⁻¹ * a) g
}


/-
Copyright (c) 2022 Eric Wieser. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Eric Wieser
-/
import data.nat.log
import algebra.order.floor
import algebra.field_power

/-!
# Integer logarithms in a field with respect to a natural base

This file defines two `ℤ`-valued analogs of the logarithm of `r : R` with base `b : ℕ`:

* `int.log b r`: Lower logarithm, or floor **log**. Greatest `k` such that `↑b^k ≤ r`.
* `int.clog b r`: Upper logarithm, or **c**eil **log**. Least `k` such that `r ≤ ↑b^k`.

Note that `int.log` gives the position of the left-most non-zero digit:
```lean
#eval (int.log 10 (0.09 : ℚ), int.log 10 (0.10 : ℚ), int.log 10 (0.11 : ℚ))
--    (-2,                    -1,                    -1)
#eval (int.log 10 (9 : ℚ),    int.log 10 (10 : ℚ),   int.log 10 (11 : ℚ))
--    (0,                     1,                     1)
```
which means it can be used for computing digit expansions
```lean
import data.fin.vec_notation

def digits (b : ℕ) (q : ℚ) (n : ℕ) : ℕ :=
⌊q*b^(↑n - int.log b q)⌋₊ % b

#eval digits 10 (1/7) ∘ (coe : fin 8 → ℕ)
-- ![1, 4, 2, 8, 5, 7, 1, 4]
```

## Main results

* For `int.log`:
  * `int.zpow_log_le_self`, `int.lt_zpow_succ_log_self`: the bounds formed by `int.log`,
    `(b : R) ^ log b r ≤ r < (b : R) ^ (log b r + 1)`.
  * `int.zpow_log_gi`: the galois coinsertion between `zpow` and `int.log`.
* For `int.clog`:
  * `int.zpow_pred_clog_lt_self`, `int.self_le_zpow_clog`: the bounds formed by `int.clog`,
    `(b : R) ^ (clog b r - 1) < r ≤ (b : R) ^ clog b r`.
  * `int.clog_zpow_gi`:  the galois insertion between `int.clog` and `zpow`.
* `int.neg_log_inv_eq_clog`, `int.neg_clog_inv_eq_log`: the link between the two definitions.
-/

variables {R : Type*} [linear_ordered_semifield R] [floor_semiring R]

namespace int

/-- The greatest power of `b` such that `b ^ log b r ≤ r`. -/
def log (b : ℕ) (r : R) : ℤ :=
if 1 ≤ r then
  nat.log b ⌊r⌋₊
else
  -nat.clog b ⌈r⁻¹⌉₊

lemma log_of_one_le_right (b : ℕ) {r : R} (hr : 1 ≤ r) : log b r = nat.log b ⌊r⌋₊ :=
if_pos hr

lemma log_of_right_le_one (b : ℕ) {r : R} (hr : r ≤ 1) : log b r = -nat.clog b ⌈r⁻¹⌉₊ :=
begin
  obtain rfl | hr := hr.eq_or_lt,
  { rw [log, if_pos hr, inv_one, nat.ceil_one, nat.floor_one, nat.log_one_right, nat.clog_one_right,
        int.coe_nat_zero, neg_zero], },
  { exact if_neg hr.not_le }
end

@[simp, norm_cast] lemma log_nat_cast (b : ℕ) (n : ℕ) : log b (n : R) = nat.log b n :=
begin
  cases n,
  { simp [log_of_right_le_one _ _, nat.log_zero_right] },
  { have : 1 ≤ (n.succ : R) := by simp,
    simp [log_of_one_le_right _ this, ←nat.cast_succ] }
end

lemma log_of_left_le_one {b : ℕ} (hb : b ≤ 1) (r : R) : log b r = 0 :=
begin
  cases le_total 1 r,
  { rw [log_of_one_le_right _ h, nat.log_of_left_le_one hb, int.coe_nat_zero] },
  { rw [log_of_right_le_one _ h, nat.clog_of_left_le_one hb, int.coe_nat_zero, neg_zero] },
end

lemma log_of_right_le_zero (b : ℕ) {r : R} (hr : r ≤ 0) : log b r = 0 :=
by rw [log_of_right_le_one _ (hr.trans zero_le_one),
    nat.clog_of_right_le_one ((nat.ceil_eq_zero.mpr $ inv_nonpos.2 hr).trans_le zero_le_one),
    int.coe_nat_zero, neg_zero]

lemma zpow_log_le_self {b : ℕ} {r : R} (hb : 1 < b) (hr : 0 < r) :
  (b : R) ^ log b r ≤ r :=
begin
  cases le_total 1 r with hr1 hr1,
  { rw log_of_one_le_right _ hr1,
    refine le_trans _ (nat.floor_le hr.le),
    rw [zpow_coe_nat, ←nat.cast_pow, nat.cast_le],
    exact nat.pow_log_le_self hb (nat.floor_pos.mpr hr1) },
  { rw [log_of_right_le_one _ hr1, zpow_neg, zpow_coe_nat, ← nat.cast_pow],
    apply inv_le_of_inv_le hr,
    refine (nat.le_ceil _).trans (nat.cast_le.2 _),
    exact nat.le_pow_clog hb _ },
end

lemma lt_zpow_succ_log_self {b : ℕ} (hb : 1 < b) (r : R) :
  r < (b : R) ^ (log b r + 1) :=
begin
  cases le_or_lt r 0 with hr hr,
  { rw [log_of_right_le_zero _ hr, zero_add, zpow_one],
    exact hr.trans_lt (zero_lt_one.trans_le $ by exact_mod_cast hb.le) },
  cases le_or_lt 1 r with hr1 hr1,
  { rw log_of_one_le_right _ hr1,
    rw [int.coe_nat_add_one_out, zpow_coe_nat, ←nat.cast_pow],
    apply nat.lt_of_floor_lt,
    exact nat.lt_pow_succ_log_self hb _, },
  { rw log_of_right_le_one _ hr1.le,
    have hcri : 1 < r⁻¹ := one_lt_inv hr hr1,
    have : 1 ≤ nat.clog b ⌈r⁻¹⌉₊ :=
      nat.succ_le_of_lt (nat.clog_pos hb $ nat.one_lt_cast.1 $ hcri.trans_le (nat.le_ceil _)),
    rw [neg_add_eq_sub, ←neg_sub, ←int.coe_nat_one, ← int.coe_nat_sub this,
      zpow_neg, zpow_coe_nat, lt_inv hr (pow_pos (nat.cast_pos.mpr $ zero_lt_one.trans hb) _),
      ←nat.cast_pow],
    refine nat.lt_ceil.1 _,
    exact (nat.pow_pred_clog_lt_self hb $ nat.one_lt_cast.1 $ hcri.trans_le $ nat.le_ceil _), }
end

@[simp] lemma log_zero_right (b : ℕ) : log b (0 : R) = 0 :=
log_of_right_le_zero b le_rfl

@[simp] lemma log_one_right (b : ℕ) : log b (1 : R) = 0 :=
by rw [log_of_one_le_right _ le_rfl, nat.floor_one, nat.log_one_right, int.coe_nat_zero]

lemma log_zpow {b : ℕ} (hb : 1 < b) (z : ℤ) : log b (b ^ z : R) = z :=
begin
  obtain ⟨n, rfl | rfl⟩ := z.eq_coe_or_neg,
  { rw [log_of_one_le_right _ (one_le_zpow_of_nonneg _ $ int.coe_nat_nonneg _),
      zpow_coe_nat, ←nat.cast_pow, nat.floor_coe, nat.log_pow hb],
    exact_mod_cast hb.le, },
  { rw [log_of_right_le_one _ (zpow_le_one_of_nonpos _ $ neg_nonpos.mpr (int.coe_nat_nonneg _)),
      zpow_neg, inv_inv, zpow_coe_nat, ←nat.cast_pow, nat.ceil_coe, nat.clog_pow _ _ hb],
    exact_mod_cast hb.le, },
end

@[mono] lemma log_mono_right {b : ℕ} {r₁ r₂ : R} (h₀ : 0 < r₁) (h : r₁ ≤ r₂) :
  log b r₁ ≤ log b r₂ :=
begin
  cases le_or_lt b 1 with hb hb,
  { rw [log_of_left_le_one hb, log_of_left_le_one hb], },
  cases le_total r₁ 1 with h₁ h₁; cases le_total r₂ 1 with h₂ h₂,
  { rw [log_of_right_le_one _ h₁, log_of_right_le_one _ h₂, neg_le_neg_iff, int.coe_nat_le],
    exact nat.clog_mono_right _ (nat.ceil_mono $ inv_le_inv_of_le h₀ h), },
  { rw [log_of_right_le_one _ h₁, log_of_one_le_right _ h₂],
    exact (neg_nonpos.mpr (int.coe_nat_nonneg _)).trans (int.coe_nat_nonneg _) },
  { obtain rfl := le_antisymm h (h₂.trans h₁), refl, },
  { rw [log_of_one_le_right _ h₁, log_of_one_le_right _ h₂, int.coe_nat_le],
    exact nat.log_mono_right (nat.floor_mono h), },
end

variables (R)

/-- Over suitable subtypes, `zpow` and `int.log` form a galois coinsertion -/
def zpow_log_gi {b : ℕ} (hb : 1 < b) :
  galois_coinsertion
    (λ z : ℤ, subtype.mk ((b : R) ^ z) $ zpow_pos_of_pos (by exact_mod_cast zero_lt_one.trans hb) z)
    (λ r : set.Ioi (0 : R), int.log b (r : R)) :=
galois_coinsertion.monotone_intro
  (λ r₁ r₂, log_mono_right r₁.prop)
  (λ z₁ z₂ hz, subtype.coe_le_coe.mp $ (zpow_strict_mono $ by exact_mod_cast hb).monotone hz)
  (λ r, subtype.coe_le_coe.mp $ zpow_log_le_self hb r.prop)
  (λ _, log_zpow hb _)

variables {R}

/-- `zpow b` and `int.log b` (almost) form a Galois connection. -/
lemma lt_zpow_iff_log_lt {b : ℕ} (hb : 1 < b) {x : ℤ} {r : R} (hr : 0 < r) :
  r < (b : R) ^ x ↔ log b r < x :=
@galois_connection.lt_iff_lt _ _ _ _ _ _ (zpow_log_gi R hb).gc x ⟨r, hr⟩

/-- `zpow b` and `int.log b` (almost) form a Galois connection. -/
lemma zpow_le_iff_le_log {b : ℕ} (hb : 1 < b) {x : ℤ} {r : R} (hr : 0 < r) :
  (b : R) ^ x ≤ r ↔ x ≤ log b r :=
@galois_connection.le_iff_le _ _ _ _ _ _ (zpow_log_gi R hb).gc x ⟨r, hr⟩

/-- The least power of `b` such that `r ≤ b ^ log b r`. -/
def clog (b : ℕ) (r : R) : ℤ :=
if 1 ≤ r then
  nat.clog b ⌈r⌉₊
else
  -nat.log b ⌊r⁻¹⌋₊

lemma clog_of_one_le_right (b : ℕ) {r : R} (hr : 1 ≤ r) : clog b r = nat.clog b ⌈r⌉₊ :=
if_pos hr

lemma clog_of_right_le_one (b : ℕ) {r : R} (hr : r ≤ 1) : clog b r = -nat.log b ⌊r⁻¹⌋₊ :=
begin
  obtain rfl | hr := hr.eq_or_lt,
  { rw [clog, if_pos hr, inv_one, nat.ceil_one, nat.floor_one, nat.log_one_right,
        nat.clog_one_right, int.coe_nat_zero, neg_zero], },
  { exact if_neg hr.not_le }
end

lemma clog_of_right_le_zero (b : ℕ) {r : R} (hr : r ≤ 0) : clog b r = 0 :=
begin
  rw [clog, if_neg (hr.trans_lt zero_lt_one).not_le, neg_eq_zero, int.coe_nat_eq_zero,
    nat.log_eq_zero_iff],
  cases le_or_lt b 1 with hb hb,
  { exact or.inr hb },
  { refine or.inl (lt_of_le_of_lt _ hb),
    exact nat.floor_le_one_of_le_one ((inv_nonpos.2 hr).trans zero_le_one) },
end

@[simp] lemma clog_inv (b : ℕ) (r : R) : clog b r⁻¹ = -log b r :=
begin
  cases lt_or_le 0 r with hrp hrp,
  { obtain hr | hr := le_total 1 r,
    { rw [clog_of_right_le_one _ (inv_le_one hr), log_of_one_le_right _ hr, inv_inv] },
    { rw [clog_of_one_le_right _ (one_le_inv hrp hr),  log_of_right_le_one _ hr, neg_neg] }, },
  { rw [clog_of_right_le_zero _ (inv_nonpos.mpr hrp), log_of_right_le_zero _ hrp, neg_zero], },
end

@[simp] lemma log_inv (b : ℕ) (r : R) : log b r⁻¹ = -clog b r :=
by rw [←inv_inv r, clog_inv, neg_neg, inv_inv]

-- note this is useful for writing in reverse
lemma neg_log_inv_eq_clog (b : ℕ) (r : R) : -log b r⁻¹ = clog b r :=
by rw [log_inv, neg_neg]

lemma neg_clog_inv_eq_log (b : ℕ) (r : R) : -clog b r⁻¹ = log b r :=
by rw [clog_inv, neg_neg]

@[simp, norm_cast] lemma clog_nat_cast (b : ℕ) (n : ℕ) : clog b (n : R) = nat.clog b n :=
begin
  cases n,
  { simp [clog_of_right_le_one _ _, nat.clog_zero_right] },
  { have : 1 ≤ (n.succ : R) := by simp,
    simp [clog_of_one_le_right _ this, ←nat.cast_succ] }
end

lemma clog_of_left_le_one {b : ℕ} (hb : b ≤ 1) (r : R) : clog b r = 0 :=
by rw [←neg_log_inv_eq_clog, log_of_left_le_one hb, neg_zero]

lemma self_le_zpow_clog {b : ℕ} (hb : 1 < b) (r : R) : r ≤ (b : R) ^ clog b r :=
begin
  cases le_or_lt r 0 with hr hr,
  { rw [clog_of_right_le_zero _ hr, zpow_zero],
    exact hr.trans zero_le_one },
  rw [←neg_log_inv_eq_clog, zpow_neg, le_inv hr (zpow_pos_of_pos _ _)],
  { exact zpow_log_le_self hb (inv_pos.mpr hr), },
  { exact nat.cast_pos.mpr (zero_le_one.trans_lt hb), },
end

lemma zpow_pred_clog_lt_self {b : ℕ} {r : R} (hb : 1 < b) (hr : 0 < r) :
  (b : R) ^ (clog b r - 1) < r :=
begin
  rw [←neg_log_inv_eq_clog, ←neg_add', zpow_neg, inv_lt _ hr],
  { exact lt_zpow_succ_log_self hb _, },
  { exact zpow_pos_of_pos (nat.cast_pos.mpr $ zero_le_one.trans_lt hb) _ }
end

@[simp] lemma clog_zero_right (b : ℕ) : clog b (0 : R) = 0 :=
clog_of_right_le_zero _ le_rfl

@[simp] lemma clog_one_right (b : ℕ) : clog b (1 : R) = 0 :=
by rw [clog_of_one_le_right _ le_rfl, nat.ceil_one, nat.clog_one_right, int.coe_nat_zero]

lemma clog_zpow {b : ℕ} (hb : 1 < b) (z : ℤ) : clog b (b ^ z : R) = z :=
by rw [←neg_log_inv_eq_clog, ←zpow_neg, log_zpow hb, neg_neg]

@[mono] lemma clog_mono_right {b : ℕ} {r₁ r₂ : R} (h₀ : 0 < r₁) (h : r₁ ≤ r₂) :
  clog b r₁ ≤ clog b r₂ :=
begin
  rw [←neg_log_inv_eq_clog, ←neg_log_inv_eq_clog, neg_le_neg_iff],
  exact log_mono_right (inv_pos.mpr $ h₀.trans_le h) (inv_le_inv_of_le h₀ h),
end

variables (R)
/-- Over suitable subtypes, `int.clog` and `zpow` form a galois insertion -/
def clog_zpow_gi {b : ℕ} (hb : 1 < b) :
  galois_insertion
    (λ r : set.Ioi (0 : R), int.clog b (r : R))
    (λ z : ℤ, ⟨(b : R) ^ z, zpow_pos_of_pos (by exact_mod_cast zero_lt_one.trans hb) z⟩) :=
galois_insertion.monotone_intro
  (λ z₁ z₂ hz, subtype.coe_le_coe.mp $ (zpow_strict_mono $ by exact_mod_cast hb).monotone hz)
  (λ r₁ r₂, clog_mono_right r₁.prop)
  (λ r, subtype.coe_le_coe.mp $ self_le_zpow_clog hb _)
  (λ _, clog_zpow hb _)
variables {R}

/-- `int.clog b` and `zpow b` (almost) form a Galois connection. -/
lemma zpow_lt_iff_lt_clog {b : ℕ} (hb : 1 < b) {x : ℤ} {r : R} (hr : 0 < r) :
  (b : R) ^ x < r ↔ x < clog b r :=
(@galois_connection.lt_iff_lt _ _ _ _ _ _ (clog_zpow_gi R hb).gc ⟨r, hr⟩ x).symm

/-- `int.clog b` and `zpow b` (almost) form a Galois connection. -/
lemma le_zpow_iff_clog_le {b : ℕ} (hb : 1 < b) {x : ℤ} {r : R} (hr : 0 < r) :
  r ≤ (b : R) ^ x ↔ clog b r ≤ x :=
(@galois_connection.le_iff_le _ _ _ _ _ _ (clog_zpow_gi R hb).gc ⟨r, hr⟩ x).symm

end int


import tactic
import data.nat.basic
import data.nat.parity

noncomputable theory
open_locale classical

open nat

--BEGIN--
/-
nat.prime.dvd_of_dvd_pow : ∀ {p m n : ℕ}, p.prime → p ∣ m ^ n → p ∣ m

Challenge mode: start with nat.even_or_odd instead
-/
lemma two_dvd_of_two_dvd_sq {k : ℕ} (hk : 2 ∣ k^2) :
  2 ∣ k :=
begin
  sorry,
end

lemma division_lemma_n {m n : ℕ}
  (hmn : 2 * m ^ 2 = n ^ 2)
: 2 ∣ n :=
begin
  sorry,
end

lemma div_2 {m n : ℕ} (hnm : 2 * m = 2 * n) : (m = n) :=
begin
  linarith,
end

lemma division_lemma_m {m n : ℕ}
  (hmn : 2 * m ^ 2 = n ^ 2)
: 2 ∣ m :=
begin
  sorry,
end
--END--


-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar el teorema de Cantor: No existe singuna
-- aplicación suprayectiva de un conjunto en su conjunto potencia.
-- ----------------------------------------------------------------------

import data.set.basic

open function

variable {α : Type*}

theorem Cantor : ∀ f : α → set α, ¬ surjective f :=
begin
  intros f surjf,
  let S := {i | i ∉ f i},
  cases surjf S with j hj,
  have h₁ : j ∉ f j,
  { intro h',
    have : j ∉ f j,
      { by rwa hj at h' },
    contradiction },
  have h₂ : j ∈ S,
    from h₁,
  have h₃ : j ∉ S,
    by rwa hj at h₁,
  contradiction,
end

-- Prueba
-- ======

/-
α : Type u_1
⊢ ∀ (f : α → set α), ¬surjective f
  >> intros f surjf,
f : α → set α,
surjf : surjective f
⊢ false
  >> let S := { i | i ∉ f i},
S : set α := {i : α | i ∉ f i}
⊢ false
  >> rcases surjf S with j,
j : α,
h : f j = S
⊢ false
  >> have h₁ : j ∉ f j,
| ⊢ j ∉ f j
|   >> { intro h',
| h' : j ∈ f j
| ⊢ false
|   >>   have : j ∉ f j,
| | ⊢ j ∉ f j
| |   >>     { by rwa h at h' },
| this : j ∉ f j
| ⊢ false
|   >>   contradiction },
h₁ : j ∉ f j
⊢ false
  >> have h₂ : j ∈ S,
| ⊢ j ∈ S
|   >>   from h₁,
h₂ : j ∈ S
⊢ false
  >> have h₃ : j ∉ S,
  >>   by rwa h at h₁,
h₃ : j ∉ S
⊢ false
  >> contradiction,
no goals
-/