/-
Copyright (c) 2022 María Inés de Frutos-Fernández, Filippo A. E. Nuccio. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: María Inés de Frutos-Fernández, Filippo A. E. Nuccio
-/
import number_theory.padics.padic_integers

noncomputable theory

open function
open_locale big_operators

namespace padic_int

variables {p : ℕ} [fact(nat.prime p)]

-- *[FAE]*: `Already declared`, probably already pushed?
-- lemma coe_eq_zero (x : ℤ_[p]) : (x : ℚ_[p]) = 0  ↔ x = 0 :=
-- ⟨λ h, by {rw ← padic_int.coe_zero at h, exact subtype.coe_inj.mp h},
--     λ h, by {rw h, exact padic_int.coe_zero}⟩

/-- `ℤ_[p]` with its usual ring structure is not a field. -/
lemma not_is_field (p : ℕ) [hp : fact(nat.prime p)] : ¬ is_field ℤ_[p] :=
begin
  rw ring.not_is_field_iff_exists_ideal_bot_lt_and_lt_top,
  use ideal.span {(p : ℤ_[p])},
  split,
  { rw [bot_lt_iff_ne_bot, ne.def, ideal.span_singleton_eq_bot, nat.cast_eq_zero],
    exact hp.1.ne_zero },
  { rw [lt_top_iff_ne_top, ne.def, ideal.eq_top_iff_one, ideal.mem_span_singleton,
      ← padic_int.norm_lt_one_iff_dvd, norm_one, not_lt], }
end

end padic_int

-- For instances/lemmas about ℚₚ and ℤₚ
namespace padic

variables {p : ℕ} [fact(nat.prime p)]

instance algebra : algebra ℤ_[p] ℚ_[p] := ring_hom.to_algebra (padic_int.coe.ring_hom)

lemma algebra_map_def : algebra_map ℤ_[p] ℚ_[p] =  padic_int.coe.ring_hom := rfl
lemma algebra_map_apply (x : ℤ_[p]) : algebra_map ℤ_[p] ℚ_[p] x = x := rfl

-- *[FAE]* `Already declared`: already pushed?
-- lemma norm_le_one_iff_val_nonneg (x : ℚ_[p]) : ‖x‖ ≤ 1 ↔ 0 ≤ x.valuation :=
-- begin
--   by_cases hx : x = 0,
--   { simp only [hx, norm_zero, padic.valuation_zero, zero_le_one, le_refl], },
--   { rw [padic.norm_eq_pow_val hx, ← zpow_zero (p : ℝ), zpow_le_iff_le
--       (nat.one_lt_cast.mpr (nat.prime.one_lt' p).1), right.neg_nonpos_iff],
--     apply_instance, }
-- end

instance is_fraction_ring : is_fraction_ring ℤ_[p] ℚ_[p] :=
{ map_units :=
  begin
    rintros ⟨x, hx⟩,
    rw [set_like.coe_mk, algebra_map_apply, is_unit_iff_ne_zero, ne.def,
      padic_int.coe_eq_zero],
    exact mem_non_zero_divisors_iff_ne_zero.mp hx,
  end,
  surj      := λ x,
  begin
    by_cases hx : ‖x‖ ≤ 1,
    { use (⟨x, hx⟩, 1),
      rw [submonoid.coe_one, map_one, mul_one],
      refl, },
    { set n := int.to_nat(- x.valuation) with hn,
      have hn_coe : (n : ℤ) = -x.valuation,
      { rw [hn, int.to_nat_of_nonneg],
        rw right.nonneg_neg_iff,
        rw norm_le_one_iff_val_nonneg at hx,
        exact le_of_lt (not_le.mp hx), },
      set a := x * p^n with ha,
      have ha_norm : ‖a‖ = 1,
      { have hx : x ≠ 0,
        { intro h0,
          rw [h0, norm_zero] at hx,
          exact hx (zero_le_one) },
        rw [padic_norm_e.mul, padic_norm_e.norm_p_pow, zpow_neg,
          norm_eq_pow_val hx, mul_inv_eq_one₀, hn_coe],
        apply zpow_ne_zero_of_ne_zero,
        exact nat.cast_ne_zero.mpr (ne_zero.ne p) },
      set b := (p^n : ℤ_[p]) with hb,
      have hb_mem : b ∈ non_zero_divisors ℤ_[p],
      { exact mem_non_zero_divisors_iff_ne_zero.mpr (ne_zero.ne _) },
      use (⟨a, le_of_eq ha_norm⟩, ⟨b, hb_mem⟩),
      simp only [set_like.coe_mk, map_pow, map_nat_cast, algebra_map_apply,
        padic_int.coe_pow, padic_int.coe_nat_cast, subtype.coe_mk] }
  end,
  eq_iff_exists := λ x y,
  begin
    rw [algebra_map_apply, algebra_map_apply, subtype.coe_inj],
    refine ⟨λ h, _, _⟩,
    { use 1,
      simp only [submonoid.coe_one, one_mul],
      exact h },
    { rintro ⟨⟨c, hc⟩, h⟩,
      exact (mul_eq_mul_left_iff.mp h).resolve_right (mem_non_zero_divisors_iff_ne_zero.mp hc) }
  end }

end padic


variable (α : Type) (p q : α → Prop)

example : (∀ x, p x ∧ q x) ↔ (∀ x, p x) ∧ (∀ x, q x) :=
  Iff.intro
    (
      λ h : ∀ x, p x ∧ q x =>
          ⟨
            λ y : α =>
              (h y).left
          , λ y : α =>
              (h y).right
          ⟩
    )
    (
      λ h : (∀ x, p x) ∧ (∀ x, q x) =>
        λ y : α =>
          ⟨
            h.left y
          , h.right y
          ⟩
    )

example : (∀ x, p x → q x) → (∀ x, p x) → (∀ x, q x) :=
  λ h : ∀ x, p x → q x =>
    λ h' : ∀ y, p y =>
      λ z : α =>
        have h'' : p z → q z := h z
        have z' : p z := h' z
        h'' z'

example : (∀ x, p x) ∨ (∀ x, q x) → ∀ x, p x ∨ q x :=
  λ h : (∀ x, p x) ∨ (∀ x, q x) =>
    λ y : α =>
      Or.elim h
        (
          λ hl : ∀ x, p x =>
            Or.inl (hl y)
        )
        (
          λ hr : ∀ x, q x =>
            Or.inr (hr y)
        )


import data.real.basic
import algebra.big_operators

---- DISCRETE RANDOM VARIABLE

open finset

open_locale big_operators

universe x

variables
{ι : Type x}
[fintype ι]
[decidable_eq ι]

class rnd_var (X : ι → ℝ) :=
(probs_nonneg :  ∀ i, 0 ≤ X i)
(sum_probs_one : ∑ i, X i = 1)

variables {X : ι → ℝ} [rnd_var X]

lemma probs_le_one : ∀ i, X i ≤ 1 :=
begin
  have h1 : ∑ i, X i ≤ 1, by exact le_of_eq rnd_var.sum_probs_one,
  have h2 : ∀ i, X i ≤ ∑ i, X i, {
    intro i,
    apply single_le_sum,
    intros i hi,
    apply rnd_var.probs_nonneg,
    norm_num,
  },
  intro i,
  apply le_trans, exact h2 i, exact h1,
end

variable (X) -- Tell Lean to explicitly ask for X in what follows.

noncomputable theory

section
open_locale classical

/--
Kronecker delta:
-/
def delta (i : ι) (j : ι) : ℝ := if (i = j) then 1 else 0

/--
Definition (deterministic random variable):
-/
def is_deterministic :=  ∃ j, ∀ i, if (i = j) then (X i = 1) else (X i = 0)

end

lemma delta_if_det {X : ι → ℝ} [rnd_var X]
  (h : ∀ i, X i = 0 ∨ X i = 1) :
  (∃ j, ∀ i, if (i = j) then (X i = 1) else (X i = 0)) :=
begin
  have h_ite : X = λ i, if (X i = 1) then 1 else 0,
  { ext i,
    cases h i with hxi0 hxi1,
    { rw [hxi0, if_neg (@zero_ne_one ℝ _ _)] },
    { rw [if_pos hxi1, hxi1] } },
  have h_norm : ∑ i, X i = 1 := rnd_var.sum_probs_one,
  rw h_ite at h_norm,
  dsimp only at h_norm,
  -- split sum into two by value of X i
  rw sum_ite at h_norm,
  have h_aux : (∑ x in univ.filter (λ x, ¬X x = 1), 0 : ℝ) = 0 := sum_const_zero,
  rw h_aux at h_norm,
  rw [add_zero, sum_const, nsmul_one, ← nat.cast_one, nat.cast_inj,
      card_eq_one] at h_norm,
  -- now h_norm is essentially the goal but needs some massaging
  cases h_norm with j hj,
  simp_rw [ext_iff, mem_filter, mem_univ, true_and, mem_singleton, nat.cast_one] at hj,
  use j,
  intro i,
  rw if_congr_prop (hj i).symm iff.rfl iff.rfl,
  cases h i with hxi0 hxi1,
  { rw [hxi0, if_neg (@zero_ne_one ℝ _ _)] },
  { rw [if_pos hxi1, hxi1] }
end

/--
Definition (uniform random variable): a random variable
whose  probabilities are equal to 1/n, where n
is the number of symbols that the random variable
can assume.
-/
def is_uniform := ∀ i, X i = 1 / fintype.card ι


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

import topology.algebra.filter_basis
import topology.algebra.uniform_group

/-!
# Uniform properties of neighborhood bases in topological algebra

This files contains properties of filter bases on algebraic structures that also require the theory
of uniform spaces.

The only result so far is a characterization of Cauchy filters in topological groups.

-/

open_locale uniformity filter
open filter

namespace add_group_filter_basis

variables {G : Type*} [add_comm_group G] (B : add_group_filter_basis G)

/-- The uniform space structure associated to an abelian group filter basis via the associated
topological abelian group structure. -/
protected def uniform_space : uniform_space G :=
@topological_add_group.to_uniform_space G _ B.topology B.is_topological_add_group

/-- The uniform space structure associated to an abelian group filter basis via the associated
topological abelian group structure is compatible with its group structure. -/
protected lemma uniform_add_group : @uniform_add_group G B.uniform_space _:=
@topological_add_group_is_uniform G _ B.topology B.is_topological_add_group

lemma cauchy_iff {F : filter G} :
  @cauchy G B.uniform_space F ↔ F.ne_bot ∧ ∀ U ∈ B, ∃ M ∈ F, ∀ x y ∈ M, y - x ∈ U :=
begin
  letI := B.uniform_space,
  haveI := B.uniform_add_group,
  suffices : F ×ᶠ F ≤ 𝓤 G ↔ ∀ U ∈ B, ∃ M ∈ F, ∀ x y ∈ M, y - x ∈ U,
    by split ; rintros ⟨h', h⟩ ; refine ⟨h', _⟩ ; [rwa ← this, rwa this],
  rw [uniformity_eq_comap_nhds_zero G, ← map_le_iff_le_comap],
  change tendsto _ _ _ ↔ _,
  simp [(basis_sets F).prod_self.tendsto_iff B.nhds_zero_has_basis]
end

end add_group_filter_basis


/-
Copyright (c) 2021 François Sunatori. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: François Sunatori
-/
import analysis.complex.circle
import linear_algebra.determinant
import linear_algebra.general_linear_group

/-!
# Isometries of the Complex Plane

The lemma `linear_isometry_complex` states the classification of isometries in the complex plane.
Specifically, isometries with rotations but without translation.
The proof involves:
1. creating a linear isometry `g` with two fixed points, `g(0) = 0`, `g(1) = 1`
2. applying `linear_isometry_complex_aux` to `g`
The proof of `linear_isometry_complex_aux` is separated in the following parts:
1. show that the real parts match up: `linear_isometry.re_apply_eq_re`
2. show that I maps to either I or -I
3. every z is a linear combination of a + b * I

## References

* [Isometries of the Complex Plane](http://helmut.knaust.info/mediawiki/images/b/b5/Iso.pdf)
-/
noncomputable theory

open complex
open_locale complex_conjugate

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

/-- An element of the unit circle defines a `linear_isometry_equiv` from `ℂ` to itself, by
rotation. -/
def rotation : circle →* (ℂ ≃ₗᵢ[ℝ] ℂ) :=
{ to_fun := λ a,
  { norm_map' := λ x, show |a * x| = |x|, by rw [complex.abs_mul, abs_coe_circle, one_mul],
    ..distrib_mul_action.to_linear_equiv ℝ ℂ a },
  map_one' := linear_isometry_equiv.ext $ one_smul _,
  map_mul' := λ _ _, linear_isometry_equiv.ext $ mul_smul _ _ }

@[simp] lemma rotation_apply (a : circle) (z : ℂ) : rotation a z = a * z := rfl

lemma rotation_ne_conj_lie (a : circle) : rotation a ≠ conj_lie :=
begin
  intro h,
  have h1 : rotation a 1 = conj 1 := linear_isometry_equiv.congr_fun h 1,
  have hI : rotation a I = conj I := linear_isometry_equiv.congr_fun h I,
  rw [rotation_apply, ring_hom.map_one, mul_one] at h1,
  rw [rotation_apply, conj_I, ← neg_one_mul, mul_left_inj' I_ne_zero, h1, eq_neg_self_iff] at hI,
  exact one_ne_zero hI,
end

/-- Takes an element of `ℂ ≃ₗᵢ[ℝ] ℂ` and checks if it is a rotation, returns an element of the
unit circle. -/
@[simps]
def rotation_of (e : ℂ ≃ₗᵢ[ℝ] ℂ) : circle :=
⟨(e 1) / complex.abs (e 1), by simp⟩

@[simp]
lemma rotation_of_rotation (a : circle) : rotation_of (rotation a) = a :=
subtype.ext $ by simp

lemma rotation_injective : function.injective rotation :=
function.left_inverse.injective rotation_of_rotation

lemma linear_isometry.re_apply_eq_re_of_add_conj_eq (f : ℂ →ₗᵢ[ℝ] ℂ)
  (h₃ : ∀ z, z + conj z = f z + conj (f z)) (z : ℂ) : (f z).re = z.re :=
by simpa [ext_iff, add_re, add_im, conj_re, conj_im, ←two_mul,
         (show (2 : ℝ) ≠ 0, by simp [two_ne_zero'])] using (h₃ z).symm

lemma linear_isometry.im_apply_eq_im_or_neg_of_re_apply_eq_re {f : ℂ →ₗᵢ[ℝ] ℂ}
  (h₂ : ∀ z, (f z).re = z.re) (z : ℂ) :
  (f z).im = z.im ∨ (f z).im = -z.im :=
begin
  have h₁ := f.norm_map z,
  simp only [complex.abs, norm_eq_abs] at h₁,
  rwa [real.sqrt_inj (norm_sq_nonneg _) (norm_sq_nonneg _), norm_sq_apply (f z), norm_sq_apply z,
    h₂, add_left_cancel_iff, mul_self_eq_mul_self_iff] at h₁,
end

lemma linear_isometry.im_apply_eq_im {f : ℂ →ₗᵢ[ℝ] ℂ} (h : f 1 = 1) (z : ℂ) :
  z + conj z = f z + conj (f z) :=
begin
  have : ∥f z - 1∥ = ∥z - 1∥ := by rw [← f.norm_map (z - 1), f.map_sub, h],
  apply_fun λ x, x ^ 2 at this,
  simp only [norm_eq_abs, ←norm_sq_eq_abs] at this,
  rw [←of_real_inj, ←mul_conj, ←mul_conj] at this,
  rw [ring_hom.map_sub, ring_hom.map_sub] at this,
  simp only [sub_mul, mul_sub, one_mul, mul_one] at this,
  rw [mul_conj, norm_sq_eq_abs, ←norm_eq_abs, linear_isometry.norm_map] at this,
  rw [mul_conj, norm_sq_eq_abs, ←norm_eq_abs] at this,
  simp only [sub_sub, sub_right_inj, mul_one, of_real_pow, ring_hom.map_one, norm_eq_abs] at this,
  simp only [add_sub, sub_left_inj] at this,
  rw [add_comm, ←this, add_comm],
end

lemma linear_isometry.re_apply_eq_re {f : ℂ →ₗᵢ[ℝ] ℂ} (h : f 1 = 1) (z : ℂ) : (f z).re = z.re :=
begin
  apply linear_isometry.re_apply_eq_re_of_add_conj_eq,
  intro z,
  apply linear_isometry.im_apply_eq_im h,
end

lemma linear_isometry_complex_aux {f : ℂ ≃ₗᵢ[ℝ] ℂ} (h : f 1 = 1) :
  f = linear_isometry_equiv.refl ℝ ℂ ∨ f = conj_lie :=
begin
  have h0 : f I = I ∨ f I = -I,
  { have : |f I| = 1 := by simpa using f.norm_map complex.I,
    simp only [ext_iff, ←and_or_distrib_left, neg_re, I_re, neg_im, neg_zero],
    split,
    { rw ←I_re,
      exact @linear_isometry.re_apply_eq_re f.to_linear_isometry h I, },
    { apply @linear_isometry.im_apply_eq_im_or_neg_of_re_apply_eq_re f.to_linear_isometry,
      intro z, rw @linear_isometry.re_apply_eq_re f.to_linear_isometry h } },
  refine h0.imp (λ h' : f I = I, _) (λ h' : f I = -I, _);
  { apply linear_isometry_equiv.to_linear_equiv_injective,
    apply complex.basis_one_I.ext',
    intros i,
    fin_cases i; simp [h, h'] }
end

lemma linear_isometry_complex (f : ℂ ≃ₗᵢ[ℝ] ℂ) :
  ∃ a : circle, f = rotation a ∨ f = conj_lie.trans (rotation a) :=
begin
  let a : circle := ⟨f 1, by simpa using f.norm_map 1⟩,
  use a,
  have : (f.trans (rotation a).symm) 1 = 1,
  { simpa using rotation_apply a⁻¹ (f 1) },
  refine (linear_isometry_complex_aux this).imp (λ h₁, _) (λ h₂, _),
  { simpa using eq_mul_of_inv_mul_eq h₁ },
  { exact eq_mul_of_inv_mul_eq h₂ }
end

/-- The matrix representation of `rotation a` is equal to the conformal matrix
`![![re a, -im a], ![im a, re a]]`. -/
lemma to_matrix_rotation (a : circle) :
  linear_map.to_matrix basis_one_I basis_one_I (rotation a).to_linear_equiv =
    matrix.plane_conformal_matrix (re a) (im a) (by simp [pow_two, ←norm_sq_apply]) :=
begin
  ext i j,
  simp [linear_map.to_matrix_apply],
  fin_cases i; fin_cases j; simp
end

/-- The determinant of `rotation` (as a linear map) is equal to `1`. -/
@[simp] lemma det_rotation (a : circle) : ((rotation a).to_linear_equiv : ℂ →ₗ[ℝ] ℂ).det = 1 :=
begin
  rw [←linear_map.det_to_matrix basis_one_I, to_matrix_rotation, matrix.det_fin_two],
  simp [←norm_sq_apply]
end

/-- The determinant of `rotation` (as a linear equiv) is equal to `1`. -/
@[simp] lemma linear_equiv_det_rotation (a : circle) : (rotation a).to_linear_equiv.det = 1 :=
by rw [←units.eq_iff, linear_equiv.coe_det, det_rotation, units.coe_one]


import tactic
/-
The theory of even and odd numbers
-/
def even (n : ℕ) : Prop := ∃ d, n = 2 * d
def odd (n : ℕ) : Prop := ∃ d, n = 2 * d + 1

/-
see https://www.codewars.com/kata/5e998b42dcf07b0001581def/lean
for a completely different approach (inductive definitions)
-/

/- ## interaction with 0 -/

lemma even_zero : even 0 :=
begin
  unfold even,

end

/- ## interaction with succ -/

lemma even_add_one {n : ℕ} : even n → odd (n + 1) := sorry

lemma odd_add_one {n : ℕ} : odd n → even (n + 1) := sorry

/- ## interaction with add -/

theorem odd_add_odd {n m : ℕ} (hn : odd n) (hm : odd m) :
  even (n + m) := sorry

theorem odd_add_even {n m : ℕ} (hn : odd n) (hm : even m) :
  odd (n + m) := sorry

theorem even_add_odd {n m : ℕ} (hn : even n) (hm : odd m) :
  odd (n + m) := sorry

theorem even_add_even {n m : ℕ} (hn : even n) (hm : even m) :
  even (n + m) := sorry

/- ## interaction with one -/

theorem odd_one : odd 1 := sorry

/- ## interaction with mul -/

theorem even_mul_even {n m : ℕ} (hn : even n) (hm : even m) :
  even (n * m) := sorry

theorem even_mul_odd {n m : ℕ} (hn : even n) (hm : odd m) :
  even (n * m) := sorry

theorem odd_mul_even {n m : ℕ} (hn : odd n) (hm : even m) :
  even (n * m) := sorry

theorem odd_mul_odd {n m : ℕ} (hn : odd n) (hm : odd m) :
  odd (n * m) := sorry

/- ## interaction with each other -/

lemma odd_or_even (n : ℕ) : odd n ∨ even n := sorry

-- hard?
lemma not_odd_and_even {n : ℕ} : ¬ (odd n ∧ even n) := sorry


def main :

namespace hides

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

def nat_to_string : nat → string
| nat.zero := "0"
| (nat.succ n) := "succ(" ++ nat_to_string n ++ ")"

instance : has_repr nat := ⟨nat_to_string⟩

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

def zero : nat := nat.zero
def one : nat := nat.succ nat.zero
def two : nat := nat.succ one
def three : nat := nat.succ two

#eval three

end hides

def even (n : nat) : Prop := ∃ m, n = 2 * m
def odd (n : nat) : Prop := ∃ m, n = 2 * m + 1

#check ∀ n, even n ∨ even (n+1)

lemma even_10: even 10 :=
begin
  use 5,
  refl,
end

theorem even_plus_even {n m : nat} (h1 : even n) (h2 : even m) : even (n + m) :=
begin
  cases h1 with k hk,
  cases h2 with l hl,
  use k + l,
  rw [hk, hl],
  ring,
end

inductive mynat : Type
| zero : mynat
| succ : mynat → mynat

variables (p q : Prop)

theorem and_comm : p ∧ q → q ∧ p :=
begin
  assume h,
  cases h with hp hq,
  split,
  exact hq,
  exact hp,
end

theorem and_left : p ∧ q → p :=
begin
  assume h,
  cases h with hp hq,
  exact hp,
end

theorem or_left : p ∨ q → p :=
begin
  assume h,
  cases h with hp hq,
 exact hp,
end


-- Conmutatividad_del_maximo.lean
-- Si a, b ∈ ℝ, entonces max(a,b) = max(b,a)
-- José A. Alonso Jiménez <https://jaalonso.github.io>
-- Sevilla, 30-septiembre-2022
-- ---------------------------------------------------------------------

-- ---------------------------------------------------------------------
-- Demostrar que si a, b ∈ ℝ, entonces max(a,b) = max(b,a)
-- ---------------------------------------------------------------------

import data.real.basic

variables a b : ℝ

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

example : max a b = max b a :=
begin
  apply le_antisymm,
  { show max a b ≤ max b a,
    apply max_le,
    { apply le_max_right },
    { apply le_max_left }},
  { show max b a ≤ max a b,
    apply max_le,
    { apply le_max_right },
    { apply le_max_left }},
end

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

example : max a b = max b a :=
begin
  have h : ∀ x y : ℝ, max x y ≤ max y x,
  { intros x y,
    apply max_le,
    { apply le_max_right },
    { apply le_max_left }},
  apply le_antisymm,
  apply h,
  apply h,
end

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

example : max a b = max b a :=
begin
  have h : ∀ {x y : ℝ}, max x y ≤ max y x,
  { intros x y,
    exact max_le (le_max_right y x) (le_max_left y x),},
  exact le_antisymm h h,
end

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

example : max a b = max b a :=
begin
  apply le_antisymm,
  repeat {
    apply max_le,
    apply le_max_right,
    apply le_max_left },
end


/-
Styles of function writing
    1) "C" style
    2) tactic script
    3) lambda abstraction
-/
def square : ℕ → ℕ
    | n := n*n

def square' : ℕ → ℕ :=
    λ n, n^2

#eval (λ (n : ℕ ), n^2) 3

def x := λ (n:ℕ ), n^2

#check x

/-
Must really know how to write lambda expressions
-/
def x' := λ (n : bool), tt

/-
Be able to produce recursive function definitions (in Lean) for simple
arithmetic functions. Know how to reason about and write appropriate base
and recursive cases for functions involving natural numbers. A base case
generally involves an argument value of nat.zero. A recursive case generally
occurs when an argument value is of the form (nat.succ n') for some n'. These
two cases correspond to the two constructors for the nat data type. You might
be tempted to name the argument in a recursive case n, and to recurse on the
expression "n minus 1" but this will not work in Lean, because Lean cannot then
prove to itself that the recursion terminates. The definition of the factorial
function (in Lean) is a good example to study.
-/

--recursive find fibonaci
open nat
def fib : ℕ → ℕ
    |nat.zero := nat.zero
    | (succ(nat.zero)):= (succ(nat.zero))
    | (succ(succ n')) := fib(n') + fib(n' + 1)

def fact : ℕ → ℕ
    | nat.zero := succ nat.zero
    | (succ(nat.zero)):= (succ(nat.zero))
    | (succ n') := fact (n') * (succ n')

#eval fact 4

/-
Understand key tradeoffs when defining functions using mathematical logic, and
using imperative programming languages, respectively
Answer: mathematical logic can be easier to understand and explain but imperative
programming allows for more efficient use of computational resources as
well as fewer lines of code
-/

/-
Understand key shortcomings of natural languages for expressing mathematical
(including specification and implementation) concepts.
Answer: Natural Language: The language that we all speak.
Not the language that machines operate using. There is too much
ambiguity in natural language. It is not checkable by machines.

Formal Language: ex Python, boolean algebra. Well defined
language with no ambiguity. Some formal languages are based
off of the structure of natural language(ex. cobol)
-/

/-
Understand precisely what it means for a program to be correct with respect to
(or "equivalent" to) a specification written in math logic.
-/

/-
Know Boolean function truth tables
-/

/-
Know how lists ("sequences") are inductively defined and be able to define
recursive functions that operate on lists

inductive nat : Type
| zero : nat
| succ : nat → nat
-/
open nat
inductive list_nat : Type
| nil
| cons : nat → list_nat → list_nat
open list_nat
--Recursive list function
def length_nat : list_nat → ℕ
| nil := 0
| (cons h t) := 1 + (length_nat t)

def append_mnat : list_nat → list_nat → list_nat
| nil h := h
| (cons h t) o := cons h (append_mnat t o)

/-
Know how to define polymorphic functions that work with values of type
(list alpha), where alpha is a type parameter. Understand implicit
arguments and how to use them in Lean function definitions.

def unbox' (α : Type) : boxed α → α
| (box v) := v

def unbox {α : Type} : boxed α → α
-- Use curly braces to indicate implicit arg
| (box v) := v
-/

/-
Know how to define simple enumerated types in Lean and functions
that operate on values of such types. The "day" type that we covered
is an example of such a type.
-/
inductive day : Type
 | sun : day
 | mon : day
 | tue : day
 | wed : day
 | thu : day
 | fri : day
 | sat : day

inductive mybool : Type
    | ttt
    | fff

open day
def nextDay : day → day
 | sun := mon
 | mon := tue
 | tue := wed
 | wed := thu
 | thu := fri
 | fri := sat
 | sat := sun

/-
Understand the higher-order "map" and "reduce" functions
that we've studied. The key characteristic of higher-order
functions is that they take function arguments, or return
functions as results, or both. Be able to implement and use
map and reduce functions.
-/
open list
def map_pred : (ℕ → bool) → list nat → (list bool)
| _ [] := []
| f (cons h t) :=
    if (f h)
    then (cons tt (map_pred f t))
    else (cons ff (map_pred f t))

-- Answer here
def b_list := [0,1,2,5,1,0]

def reduce_and : list bool → bool
| [] := tt
| (cons ff t) := ff
| (cons tt t) := reduce_and t

/-
Understand how to specify and work with "product" types.
The "prod" type we defined is an example of a type whose
values are ordered *pairs* of values of other specified types.

Ans: A polymorphic "product type", prod. A product
type is a type whose values are ordered pairs.
Here the types of the first and second elements
of such a pair are given by the values of two
type arguments, α and β respectively. The pair
constructor takes a value of type α and one of
type β and yeilds the term (pair a b), whic we
will interpret as representing the ordered pair,
(a, b), a concept that should be familiar from
basic high school algebra.
-/

inductive prod2 (α β : Type) : Type
| pair (a : α) (b : β) : prod2

def fst {α β : Type} : prod2 α β → α
| (prod2.pair a b) := a

def snd {α β : Type} : prod2 α β → β
| (prod2.pair a b) := b

/-
Understand the combinatorics of binary values. E.g.,
there are 2^n possible values of a sequence of n
binary variables.

(2^(n))^(2^m) = number of posible functions with n
input bits and m output bits
-/


/-
Copyright (c) 2020 Shing Tak Lam. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Shing Tak Lam
-/
import data.fintype.card
import data.zmod.basic
import group_theory.order_of_element

/-!
# Dihedral Groups

We define the dihedral groups `dihedral_group n`, with elements `r i` and `sr i` for `i : zmod n`.

For `n ≠ 0`, `dihedral_group n` represents the symmetry group of the regular `n`-gon. `r i`
represents the rotations of the `n`-gon by `2πi/n`, and `sr i` represents the reflections of the
`n`-gon. `dihedral_group 0` corresponds to the infinite dihedral group.
-/

/--
For `n ≠ 0`, `dihedral_group n` represents the symmetry group of the regular `n`-gon.
`r i` represents the rotations of the `n`-gon by `2πi/n`, and `sr i` represents the reflections of
the `n`-gon. `dihedral_group 0` corresponds to the infinite dihedral group.
-/
@[derive decidable_eq]
inductive dihedral_group (n : ℕ) : Type
| r : zmod n → dihedral_group
| sr : zmod n → dihedral_group

namespace dihedral_group

variables {n : ℕ}

/--
Multiplication of the dihedral group.
-/
private def mul : dihedral_group n → dihedral_group n → dihedral_group n
| (r i) (r j) := r (i + j)
| (r i) (sr j) := sr (j - i)
| (sr i) (r j) := sr (i + j)
| (sr i) (sr j) := r (j - i)

/--
The identity `1` is the rotation by `0`.
-/
private def one : dihedral_group n := r 0

instance : inhabited (dihedral_group n) := ⟨one⟩

/--
The inverse of a an element of the dihedral group.
-/
private def inv : dihedral_group n → dihedral_group n
| (r i) := r (-i)
| (sr i) := sr i

/--
The group structure on `dihedral_group n`.
-/
instance : group (dihedral_group n) :=
{ mul := mul,
  mul_assoc :=
  begin
    rintros (a | a) (b | b) (c | c);
    simp only [mul];
    ring,
  end,
  one := one,
  one_mul :=
  begin
    rintros (a | a),
    exact congr_arg r (zero_add a),
    exact congr_arg sr (sub_zero a),
  end,
  mul_one := begin
    rintros (a | a),
    exact congr_arg r (add_zero a),
    exact congr_arg sr (add_zero a),
  end,
  inv := inv,
  mul_left_inv := begin
    rintros (a | a),
    exact congr_arg r (neg_add_self a),
    exact congr_arg r (sub_self a),
  end }

@[simp] lemma r_mul_r (i j : zmod n) : r i * r j = r (i + j) := rfl
@[simp] lemma r_mul_sr (i j : zmod n) : r i * sr j = sr (j - i) := rfl
@[simp] lemma sr_mul_r (i j : zmod n) : sr i * r j = sr (i + j) := rfl
@[simp] lemma sr_mul_sr (i j : zmod n) : sr i * sr j = r (j - i) := rfl

lemma one_def : (1 : dihedral_group n) = r 0 := rfl

private def fintype_helper : (zmod n ⊕ zmod n) ≃ dihedral_group n :=
{ inv_fun := λ i, match i with
                 | (r j) := sum.inl j
                 | (sr j) := sum.inr j
                 end,
  to_fun := λ i, match i with
                 | (sum.inl j) := r j
                 | (sum.inr j) := sr j
                 end,
  left_inv := by rintro (x | x); refl,
  right_inv := by rintro (x | x); refl }

/--
If `0 < n`, then `dihedral_group n` is a finite group.
-/
instance [fact (0 < n)] : fintype (dihedral_group n) := fintype.of_equiv _ fintype_helper

instance : nontrivial (dihedral_group n) := ⟨⟨r 0, sr 0, dec_trivial⟩⟩

/--
If `0 < n`, then `dihedral_group n` has `2n` elements.
-/
lemma card [fact (0 < n)] : fintype.card (dihedral_group n) = 2 * n :=
by rw [← fintype.card_eq.mpr ⟨fintype_helper⟩, fintype.card_sum, zmod.card, two_mul]

@[simp] lemma r_one_pow (k : ℕ) : (r 1 : dihedral_group n) ^ k = r k :=
begin
  induction k with k IH,
  { refl },
  { rw [pow_succ, IH, r_mul_r],
    congr' 1,
    norm_cast,
    rw nat.one_add }
end

@[simp] lemma r_one_pow_n : (r (1 : zmod n))^n = 1 :=
begin
  cases n,
  { rw pow_zero },
  { rw [r_one_pow, one_def],
    congr' 1,
    exact zmod.nat_cast_self _, }
end

@[simp]

/--
If `0 < n`, then `sr i` has order 2.
-/
@[simp] lemma order_of_sr (i : zmod n) : order_of (sr i) = 2 :=
begin
  rw order_of_eq_prime _ _,
  { exact ⟨nat.prime_two⟩ },
  rw [sq, sr_mul_self],
  dec_trivial,
end

/--
If `0 < n`, then `r 1` has order `n`.
-/
@[simp] lemma order_of_r_one : order_of (r 1 : dihedral_group n) = n :=
begin
  by_cases hnpos : 0 < n,
  { haveI : fact (0 < n) := ⟨hnpos⟩,
    cases lt_or_eq_of_le (nat.le_of_dvd hnpos (order_of_dvd_of_pow_eq_one (@r_one_pow_n n)))
      with h h,
    { have h1 : (r 1 : dihedral_group n)^(order_of (r 1)) = 1,
      { exact pow_order_of_eq_one _ },
      rw r_one_pow at h1,
      injection h1 with h2,
      rw [← zmod.val_eq_zero, zmod.val_nat_cast, nat.mod_eq_of_lt h] at h2,
      apply absurd h2.symm,
      apply ne_of_lt,
      exact absurd h2.symm (ne_of_lt (order_of_pos _)) },
    { exact h } },
  { simp only [not_lt, nonpos_iff_eq_zero] at hnpos,
    rw hnpos,
    apply order_of_eq_zero,
    rw is_of_fin_order_iff_pow_eq_one,
    push_neg,
    intros m hm,
    rw [r_one_pow, one_def],
    by_contradiction h,
    have h' : (m : zmod 0) = 0,
    { exact r.inj h, },
    have h'' : m = 0,
    { simp only [int.coe_nat_eq_zero, int.nat_cast_eq_coe_nat] at h',
      exact h', },
    rw h'' at hm,
    apply nat.lt_irrefl,
    exact hm },
end

/--
If `0 < n`, then `i : zmod n` has order `n / gcd n i`.
-/
lemma order_of_r [fact (0 < n)] (i : zmod n) : order_of (r i) = n / nat.gcd n i.val :=
begin
  conv_lhs { rw ←zmod.nat_cast_zmod_val i },
  rw [←r_one_pow, order_of_pow, order_of_r_one]
end

end dihedral_group


/-
# References

1. Avigad, Jeremy. ‘Theorem Proving in Lean’, n.d.
-/

-- Exercise 1
--
-- Prove these equivalences. You should also try to understand why the reverse
-- implication is not derivable in the last example.
namespace ex1

variable (α : Type _)
variable (p q : α → Prop)

example : (∀ x, p x ∧ q x) ↔ (∀ x, p x) ∧ (∀ x, q x) :=
  Iff.intro
    (fun h => ⟨fun x => And.left (h x), fun x => And.right (h x)⟩)
    (fun ⟨h₁, h₂⟩ x => ⟨h₁ x, h₂ x⟩)

example : (∀ x, p x → q x) → (∀ x, p x) → (∀ x, q x) :=
  fun h₁ h₂ x =>
    have px : p x := h₂ x
    h₁ x px

example : (∀ x, p x) ∨ (∀ x, q x) → ∀ x, p x ∨ q x :=
  fun h₁ x => h₁.elim
    (fun h₂ => Or.inl (h₂ x))
    (fun h₂ => Or.inr (h₂ x))

-- The implication in the above example cannot be proven in the other direction
-- because it may be the case predicate `p x` holds for certain values of `x`
-- but not others that `q x` may hold for (and vice versa).

end ex1

-- Exercise 2
--
-- It is often possible to bring a component of a formula outside a universal
-- quantifier, when it does not depend on the quantified variable. Try proving
-- these (one direction of the second of these requires classical logic).
namespace ex2

variable (α : Type _)
variable (p q : α → Prop)
variable (r : Prop)

example : α → ((∀ _ : α, r) ↔ r) :=
  fun a => Iff.intro (fun h => h a) (fun hr _ => hr)

section

open Classical

example : (∀ x, p x ∨ r) ↔ (∀ x, p x) ∨ r :=
  Iff.intro
    (fun h₁ => (em r).elim
      Or.inr
      (fun nr => Or.inl (fun x => (h₁ x).elim id (absurd · nr))))
    (fun h₁ => h₁.elim
      (fun h₂ x => Or.inl (h₂ x))
      (fun hr _ => Or.inr hr))

end

example : (∀ x, r → p x) ↔ (r → ∀ x, p x) :=
  Iff.intro
    (fun h hr hx => h hx hr)
    (fun h hx hr => h hr hx)

end ex2

-- Exercise 3
--
-- Consider the "barber paradox," that is, the claim that in a certain town
-- there is a (male) barber that shaves all and only the men who do not shave
-- themselves. Prove that this is a contradiction.
namespace ex3

open Classical

variable (men : Type _)
variable (barber : men)
variable (shaves : men → men → Prop)

example (h : ∀ x : men, shaves barber x ↔ ¬shaves x x) : False :=
  have b : shaves barber barber ↔ ¬shaves barber barber := h barber
  (em (shaves barber barber)).elim
    (fun b' => absurd b' (Iff.mp b b'))
    (fun b' => absurd (Iff.mpr b b') b')

end ex3

-- Exercise 4
--
-- Remember that, without any parameters, an expression of type `Prop` is just
-- an assertion. Fill in the definitions of `prime` and `Fermat_prime` below,
-- and construct each of the given assertions. For example, you can say that
-- there are infinitely many primes by asserting that for every natural number
-- `n`, there is a prime number greater than `n.` Goldbach’s weak conjecture
-- states that every odd number greater than `5` is the sum of three primes.
-- Look up the definition of a Fermat prime or any of the other statements, if
-- necessary.
namespace ex4

def even (a : Nat) := ∃ b, a = 2 * b

def odd (a : Nat) := ¬even a

def prime (n : Nat) : Prop :=
  n > 1 ∧ ∀ (m : Nat), (1 < m ∧ m < n) → n % m ≠ 0

def infinitelyManyPrimes : Prop :=
  ∀ (n : Nat), (∃ (m : Nat), m > n ∧ prime m)

def FermatPrime (n : Nat) : Prop :=
  ∃ (m : Nat), n = 2^(2^m) + 1

def infinitelyManyFermatPrimes : Prop :=
  ∀ (n : Nat), (∃ (m : Nat), m > n ∧ FermatPrime m)

def GoldbachConjecture : Prop :=
  ∀ (n : Nat), even n ∧ n > 2 →
    ∃ (x y : Nat), prime x ∧ prime y ∧ x + y = n

def Goldbach'sWeakConjecture : Prop :=
  ∀ (n : Nat), odd n ∧ n > 5 →
    ∃ (x y z : Nat), prime x ∧ prime y ∧ prime z ∧ x + y + z = n

def Fermat'sLastTheorem : Prop :=
  ∀ (n : Nat), n > 2 → (∀ (a b c : Nat), a^n + b^n ≠ c^n)

end ex4

-- Exercise 5
--
-- Prove as many of the identities listed in Section 4.4 as you can.
namespace ex5

open Classical

variable (α : Type _)
variable (p q : α → Prop)
variable (r s : Prop)

example : (∃ _ : α, r) → r :=
  fun ⟨_, hr⟩ => hr

example (a : α) : r → (∃ _ : α, r) :=
  fun hr => ⟨a, hr⟩

example : (∃ x, p x ∧ r) ↔ (∃ x, p x) ∧ r :=
  Iff.intro
    (fun ⟨hx, ⟨hp, hr⟩⟩ => ⟨⟨hx, hp⟩, hr⟩)
    (fun ⟨⟨hx, hp⟩, hr⟩ => ⟨hx, ⟨hp, hr⟩⟩)

example : (∃ x, p x ∨ q x) ↔ (∃ x, p x) ∨ (∃ x, q x) :=
  Iff.intro
    (fun ⟨hx, hpq⟩ => hpq.elim
      (fun hp => Or.inl ⟨hx, hp⟩)
      (fun hq => Or.inr ⟨hx, hq⟩))
    (fun h => h.elim
      (fun ⟨hx, hp⟩ => ⟨hx, Or.inl hp⟩)
      (fun ⟨hx, hq⟩ => ⟨hx, Or.inr hq⟩))

example : (∀ x, p x) ↔ ¬(∃ x, ¬p x) :=
  Iff.intro
    (fun h ⟨hx, np⟩ => np (h hx))
    (fun h hx => byContradiction
      fun np => h ⟨hx, np⟩)

example : (∃ x, p x) ↔ ¬(∀ x, ¬p x) :=
  Iff.intro
    (fun ⟨hx, hp⟩ h => absurd hp (h hx))
    (fun h => byContradiction
      fun h' => h (fun (x : α) hp => h' ⟨x, hp⟩))

example : (¬∃ x, p x) ↔ (∀ x, ¬p x) :=
  Iff.intro
    (fun h hx hp => h ⟨hx, hp⟩)
    (fun h ⟨hx, hp⟩ => absurd hp (h hx))

theorem forall_negation : (¬∀ x, p x) ↔ (∃ x, ¬p x) :=
  Iff.intro
    (fun h => byContradiction
      fun h' => h (fun (x : α) => byContradiction
        fun np => h' ⟨x, np⟩))
    (fun ⟨hx, np⟩ h => absurd (h hx) np)

example : (¬∀ x, p x) ↔ (∃ x, ¬p x) :=
  forall_negation α p

example : (∀ x, p x → r) ↔ (∃ x, p x) → r :=
  Iff.intro
    (fun h ⟨hx, hp⟩ => h hx hp)
    (fun h hx hp => h ⟨hx, hp⟩)

example (a : α) : (∃ x, p x → r) ↔ (∀ x, p x) → r :=
  Iff.intro
    (fun ⟨hx, hp⟩ h => hp (h hx))
    (fun h₁ => (em (∀ x, p x)).elim
      (fun h₂ => ⟨a, fun _ => h₁ h₂⟩)
      (fun h₂ =>
        have h₃ : (∃ x, ¬p x) := Iff.mp (forall_negation α p) h₂
        match h₃ with
        | ⟨hx, hp⟩ => ⟨hx, fun hp' => absurd hp' hp⟩))

example (a : α) : (∃ x, r → p x) ↔ (r → ∃ x, p x) :=
  Iff.intro
    (fun ⟨hx, hrp⟩ hr => ⟨hx, hrp hr⟩)
    (fun h => (em r).elim
      (fun hr => match h hr with
                 | ⟨hx, hp⟩ => ⟨hx, fun _ => hp⟩)
      (fun nr => ⟨a, fun hr => absurd hr nr⟩))

end ex5

-- Exercise 6
--
-- Give a calculational proof of the theorem `log_mul` below.
namespace ex6

variable (log exp : Float → Float)
variable (log_exp_eq : ∀ x, log (exp x) = x)
variable (exp_log_eq : ∀ {x}, x > 0 → exp (log x) = x)
variable (exp_pos : ∀ x, exp x > 0)
variable (exp_add : ∀ x y, exp (x + y) = exp x * exp y)

example (x y z : Float) : exp (x + y + z) = exp x * exp y * exp z :=
  by rw [exp_add, exp_add]

example (y : Float) (h : y > 0) : exp (log y) = y := exp_log_eq h

theorem log_mul {x y : Float} (hx : x > 0) (hy : y > 0) :
  log (x * y) = log x + log y :=
calc log (x * y) = log (x * exp (log y)) := by rw [exp_log_eq hy]
               _ = log (exp (log x) * exp (log y)) := by rw [exp_log_eq hx]
               _ = log (exp (log x + log y)) := by rw [exp_add]
               _ = log x + log y := by rw [log_exp_eq]

end ex6


-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que
--    [a,b] - (a,b) = {a,b}
-- ----------------------------------------------------------------------

import data.set.intervals.basic
import tactic

open set

variables {α : Type*}  [linear_order α]
variables {a b : α}

open_locale classical

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

lemma aux
  (h : a < b)
  (x : α)
  : (x ∈ Icc a b \ Ioo a b) ↔ x ∈ ({a, b} : set α) :=
begin
  by_cases h' : a < x,
  { simp [h', ne_of_gt h'],
    split;
    simp [le_antisymm_iff, le_of_lt h']
      {contextual := tt} },
  { push_neg at h',
    simp [h', ne_of_lt (lt_of_le_of_lt h' h), le_trans h' (le_of_lt h)],
    simp [le_antisymm_iff, h'],
    intros h1 h2,
    have h3 : a = x := le_antisymm h1 h',
    rw h3 at h2,
    have h4 : ¬(x < x) := lt_irrefl x,
    exfalso,
    apply h4,
    exact h2, },
end

example
  (h : a < b)
  : Icc a b \ Ioo a b = {a, b} :=
ext (aux h)

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

example
  (h : a < b)
  : Icc a b \ Ioo a b = {a, b} :=
Icc_diff_Ioo_same $ le_of_lt h


-- Si |x| < ε, para todo ε > 0, entonces x = 0
-- ===========================================

import data.real.basic

variable (x : ℝ)

-- ----------------------------------------------------
-- Ejercicio 1. Definir la notación |x| para el valor
-- absoluto de x.
-- ----------------------------------------------------

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

-- ----------------------------------------------------
-- Ejercicio 2. Demostrar que si |x| < ε, para todo
-- ε > 0, entonces x = 0
-- ----------------------------------------------------

-- 1ª demostración
example
  (h : ∀ ε > 0, |x| < ε)
  : x = 0 :=
begin
  rw ← abs_eq_zero,
  apply eq_of_le_of_forall_le_of_dense,
  { exact abs_nonneg x, },
  { intros ε hε,
    apply le_of_lt,
    exact h ε hε, },
end

-- 2ª demostración
example
  (h : ∀ ε > 0, |x| < ε)
  : x = 0 :=
begin
  rw ← abs_eq_zero,
  apply eq_of_le_of_forall_le_of_dense,
  { exact abs_nonneg x, },
  { intros ε hε,
    exact le_of_lt (h ε hε), },
end

-- 3ª demostración
example
  (h : ∀ ε > 0, |x| < ε)
  : x = 0 :=
begin
  rw ← abs_eq_zero,
  apply eq_of_le_of_forall_le_of_dense,
  { exact abs_nonneg x, },
  { exact λ ε hε, le_of_lt (h ε hε), },
end

-- 4ª demostración
example
  (h : ∀ ε > 0, |x| < ε)
  : x = 0 :=
begin
  rw ← abs_eq_zero,
  apply eq_of_le_of_forall_le_of_dense
        (abs_nonneg x)
        (λ ε hε, le_of_lt (h ε hε)),
end

-- 5ª demostración
example
  (h : ∀ ε > 0, |x| < ε)
  : x = 0 :=
abs_eq_zero.mp
  (eq_of_le_of_forall_le_of_dense
    (abs_nonneg x)
    (λ ε hε, le_of_lt (h ε hε)))

-- 6ª demostración
example
  (h : ∀ ε > 0, |x| < ε)
  : x = 0 :=
have h1 : 0 ≤ |x|,
  from abs_nonneg x,
have h2 : ∀ ε, ε > 0 → |x| ≤ ε,
  { assume ε,
    assume hε : ε > 0,
    have h2a : |x| < ε,
      from h ε hε,
    show |x| ≤ ε,
      from le_of_lt h2a },
have h3 : |x| = 0,
  from eq_of_le_of_forall_le_of_dense h1 h2,
show x = 0,
  from abs_eq_zero.mp h3

-- 7ª demostración
example
  (h : ∀ ε > 0, |x| < ε)
  : x = 0 :=
have h1 : 0 ≤ |x|,
  from abs_nonneg x,
have h2 : ∀ ε, ε > 0 → |x| ≤ ε,
  { assume ε,
    assume hε : ε > 0,
    have h2a : |x| < ε,
      from h ε hε,
    show |x| ≤ ε,
      from le_of_lt h2a },
have h3 : |x| = 0,
  from eq_of_le_of_forall_le_of_dense h1 h2,
abs_eq_zero.mp h3

-- 8ª demostración
example
  (h : ∀ ε > 0, |x| < ε)
  : x = 0 :=
have h1 : 0 ≤ |x|,
  from abs_nonneg x,
have h2 : ∀ ε, ε > 0 → |x| ≤ ε,
  { assume ε,
    assume hε : ε > 0,
    have h2a : |x| < ε,
      from h ε hε,
    show |x| ≤ ε,
      from le_of_lt h2a },
abs_eq_zero.mp (eq_of_le_of_forall_le_of_dense h1 h2)

-- 9ª demostración
example
  (h : ∀ ε > 0, |x| < ε)
  : x = 0 :=
have h1 : 0 ≤ |x|,
  from abs_nonneg x,
have h2 : ∀ ε, ε > 0 → |x| ≤ ε,
  { assume ε,
    assume hε : ε > 0,
    show |x| ≤ ε,
      from le_of_lt (h ε hε) },
abs_eq_zero.mp (eq_of_le_of_forall_le_of_dense h1 h2)

-- 10ª demostración
lemma cero_de_abs_mn_todos
  (h : ∀ ε > 0, |x| < ε)
  : x = 0 :=
abs_eq_zero.mp
  (eq_of_le_of_forall_le_of_dense
    (abs_nonneg x)
    (λ ε hε, le_of_lt (h ε hε)))


/-
If P and Q are propositions, then so in P ∨ Q.
We want to judge P ∨ Q to be true if at least
one of them is true. In other words, to judge
P ∨ Q to be true, we need either to be able to
judge P to be true or Q to be true (and if both
are true, that's fine, too).
-/

#check or

/-
inductive or (a b : Prop) : Prop
| inl (h : a) : or
| inr (h : b) : or
-/

/-
It's a polymorphic "either" type (in Prop)!
-/

axioms (P Q : Prop) (p : P)

lemma porq' : P ∨ Q := or.inl p

lemma porq'' : P ∨ Q :=
begin
apply or.inl _,
exact p,
end

axiom q : Q

lemma porq''' : P ∨ Q := or.inr q

/-
def or.intro_left {a : Prop} (b : Prop) (ha : a) : or a b :=
or.inl ha

def or.intro_right (a : Prop) {b : Prop} (hb : b) : or a b :=
or.inr hb
-/

/-
Suppose it's raining or the sprinkler is running.
Futhermore, suppose that if it's raining the grass
is wet, and if the sprinkler is running then the
grass is wet? What can you conclude?
-/

/-
You *used* the fact that at least one of the cases
held, combined with the fact that in *either* case,
the grass is wet, to deduce that the grass is wet.
-/

axioms (R : Prop) (porq : P ∨ Q) (pr : P → R) (qr : Q → R)

theorem RisTrue : R := or.elim porq pr qr

theorem RisTrue' : R :=
begin
apply or.elim porq,
exact pr,
exact qr,
end

/- Or -/

/-
inductive or (a b : Prop) : Prop
| inl (h : a) : or
| inr (h : b) : or
-/

-- commutes forward, proof term
example : P ∨ Q → Q ∨ P :=
λ h,
  (match h with
  | or.inl p := or.inr p
  | or.inr q := or.inl q
  end)

-- commutes forward, tactic script
example : P ∨ Q → Q ∨ P :=
begin
  assume porq,
  cases porq with p q,
  exact or.inr p,
  exact or.inl q,
end

/-
or is associative, full proof
-/
example : P ∨ (Q ∨ R) ↔ (P ∨ Q) ∨ R :=
begin
  apply iff.intro _ _,

  -- Forwards ->
  assume pqr,
  cases pqr with p qr,
  apply or.inl _,
  exact or.inl p,
  cases qr with q r,
  apply or.inl,
  exact or.inr q,
  exact or.inr r,

  -- Backwards <-
  assume pqr,
  cases pqr,
  _
end


import data.set
import .src_17_even_odd_further

open set function

namespace mth1001

section images_of_functions

/-
Suppose `U` and `V` are types, that `f : U → V` is a function with domain `U` and codomain `V`.

Given a set `A : set U`, the *image* of `A` under `f` is the set
`{v : V | ∃ u : U, u ∈ A ∧ f u = v}`.

In less formal language, we write this as `{f u | u ∈ A}`.

Lean denotes this image as `image f A` or `f '' A`. Mathematicians typically write the image of `A`
under `f` as `f (A)`. Note `A` is a set and so is `f (A)`.
-/

section image_example

/-
For example, we'll define a function `f : ℤ → ℤ` by `f n = 2 * n + 6`. We'll take `A` to be the
set of integers and we'll show that `image f A` is the set of even integers.
-/

def f (n : ℤ) : ℤ := 2 * n + 6
def A : set ℤ := {x | true} -- This is the set of all integers

example : f '' A = {y | even y} :=
begin
  ext,  -- Assume `x : ℤ`. It suffices to show `x ∈ f '' A ↔ x ∈ {y : ℤ | even y}`.
  split, -- Decompose the `↔` into two implication proofs.
  { rintro ⟨n, ⟨_, h₂⟩⟩, -- `rintro` is intro followed by recursive application of `cases`.
    -- Used here, we have `h₂ : f n = x` with a goal `x ∈ {y : ℤ | even y}`.
    use (n + 3), -- It suffices to show `x = 2 * (n + 3)`.
    rw ←h₂, -- Equally, to show `f n = 2 * (n + 3)`.
    unfold f, -- In other words, to show `2 * n + 6 = 2 * (n + 3)`.
    linarith, }, -- Which follows by linear arithmetic.
  { rintro ⟨m, h₂⟩, -- Assume `m : ℤ` and `h₂ : x = 2 *m`. It suffices to show `x ∈ f '' A`.
    use (m - 3), -- It suffices to show `m - 3 ∈ A ∧ f (m - 3) = x`.
    split,
    { unfold A, }, -- It's trivially true that `m - 3 ∈ A` as `A` is the set of all integers.
    { unfold f, -- It suffices to show `2 * (m - 3) + 6 = x`.
      rw h₂, -- That is, we must show `2 * (m - 3) + 6 = 2 * m`.
      linarith, }, }, -- Which holds by linear arithmetic.
end

end image_example

section image_theorems

variable {α : Type*}
variable {β : Type*}
variables S T : set α

-- The image of a union of the union of images.
theorem image_union (f : α → β) : f '' (S ∪ T) = f '' S ∪ f '' T :=
begin
  ext,
  split,
  { rintro ⟨a, hs | ht, rfl⟩,
    { rw mem_union_eq,
      left,
      use a,
      cc, },
    { right,
      use a,
      cc, }, },
  { rintro (⟨a, hs, rfl⟩ | ⟨a, ht, rfl⟩),
    { use a,
      split,
      { left, exact hs, },
      { refl, }, },
    { use a,
      split,
      { right, exact ht, },
      { refl, }, }, },
end

-- Exercise 143:
-- The image of an intersection is a subset of the intersection of the images.
theorem image_inter_subset (f : α → β) : f '' (S ∩ T) ⊆ f '' S ∩ f '' T :=
begin
  rw subset_def,
  sorry
end

/-
The reverse inclusion of the above theorem is false. That is, there exist types `α`, `β`,
there exists a function `f : α → β`, there exist sets `S : set α` and `T : set β` such that
`¬(f '' S ∩ f '' T ⊆ f '' (S ∩ T))`.

Can you find a counterexample?
-/

end image_theorems

end images_of_functions

end mth1001


import .prop_logic
import .bool_sat

open prop_logic
open prop_logic.var
open prop_logic.pExp

/-
An example: The 0th, 1st, and
2nd bits from the right in 100,
the binary numeral for decimal
4, are 0, 0, and 1, respectively.
-/
#eval mth_bit_from_right_in_n 2 4

/-
#1. Write and evaluate expressions
(using eval) to determine what is
the bit in the 9th position from
the right in the binary expansion
of the decimal number 8485672345?

Hint: don't use reduce here. Eval
uses hardware (machine) int values
to represent nats, while reduce
uses the unary nat representation.

Self-test: How much memory might
it take to represent the decimal
number, 8485672345, as a  ℕ value
in unary?
-/

#eval _

/-
The next section presents examples
to set up test cases for definitions
to follow.
-/

/-
We define a few variables to use
in the rest of this assignment.
-/
def P : pExp:= varExp (mkVar 0)
def Q: pExp := varExp (mkVar 1)
def R : pExp := varExp (mkVar 2)

/-
We set parameter values used in
some function definitions to follow.
-/
def max_var_index := 2
def num_vars := max_var_index + 1

/-
An example of a propositional logic
expression.
-/
def theExpr : pExp := (P ⇒ (P ∧ R))

/-
An example using the truth_table_results
function to compute and return a list of
the truth values of theExpr under each of
its possible interpretations.
-/
#eval truth_table_results theExpr num_vars

/-
#2. Define interp5 to be the interpretation
in the six row (m=5) of the truth table that
our interps_for_n_vars functions returns for
our three variables (P, Q, and R).

Hint: use the mth_interp_n_vars function.
Definitely check out the definition of the
function, and any specification text, even
if informal, given with the formal definition.
-/

def interp5 := _

/-
#3. What Boolean values are assigned to
P, Q, and R by this interpretation (interp5)?
Use three #eval commands to compute answers by
evaluating each variable expressions under the
interp5 interpretation.
-/

#eval _
#eval _
#eval _

/-
#4. Write a truth table within this
comment block showing the values for
P, Q, and R, in each row in the truth
table, represented by a corresponding
valule in the list of interpretations
returned by interps_for_n_vars. Label
your columns as R, Q, and P, in that
order. (Try to understand why.)
Hint: Don't just write what you think
the answers are:, evaluate each of
the three variable expression under
each interpretation. You can use the
mth_interp_n_vars function if you want
to obtain interpretation functions for
each of the rows individually if you
want.

Answer:

-/

/-
#5. Write an expression here to
compute the "results" column of
the truth table for "theExpr" as
defined above.
-/

#eval _

/-
#6. Copy and paste the truth table
from question #4 here and extend it
with the results you just obtained.
Check the results for correctness.

Answer here:

-/

/-
#7.

Write a "predicate" function, isModel,
that takes a propositional logic
expression, e, and an interpretation,
i, as its arguments and that returns
the Boolean true (tt) value if and only
if i is a model of e (otherwise false).
-/

def isModel :pExp → (var → bool) → bool

/-
#7. Write a one-line implementation
of a function, is_valid, that takes as
its arguments a propositional expression,
e, and the number of variables, n, in its
truth table, and that returns true if and
only if it is valid, construed to mean
tha every result in the list returned by
the truth_table_results function is true.

To do so, define and use a fold function
to reduce returned lists of Boolean truth
values to single truth values. Define and
use a bool-specific function, fold_bool :
    (bool → bool → bool) →
    bool →
    (list bool) →
    bool,
where the arguments are, respectively,
a binary operator, an identity element
for that operator, and a list of bools
to be reduced to a single bool result.
-/

def fold_bool :
    (bool → bool → bool) → bool → (list bool) → bool

def is_valid : pExp → ℕ → bool

/-
Write similar one-line implementations of the
functions, is_satisfiable and is_unsatisfiable,
respectively. Do not use fold (directly) in your
implementation of is_unsatisfiable.
-/

def is_satisfiable : pExp → ℕ → bool
| _ := _

def is_unsatisfiable : pExp → ℕ → bool
| _ := _

/-
8. Use your is_valid function to determine which
of the following putative valid laws of reasoning
really are valid, and which ones are not. For each
one that is not, give a real-world scenario that
shows that the rule doesn't always lead to a valid
deduction. Use #eval to evaluate the validity of
each proposition. Use -- to put a comment after
each of the following definitions indicating
either "-- valid" or "-- NOT valid".
-/

def true_intro := pTrue
def false_elim := pFalse ⇒ P
def and_intro := P ⇒ Q ⇒ (P ∧ Q)
def and_elim_left := (P ∧ Q) ⇒ P
def and_elim_right := (P ∧ Q) ⇒ Q
def or_intro_left := P ⇒ (P ∨ Q)
def or_intro_right := Q ⇒ (P ∨ Q)
def or_elim := (P ∨ Q) ⇒ (P ⇒ R) ⇒ (Q ⇒ R) ⇒ R
def iff_intro := (P ⇒ Q) ⇒ (Q ⇒ P) ⇒ (P ↔ Q)
def iff_elim_left := (P ↔ Q) ⇒ (P ⇒ Q)
def iff_elim_right := (P ↔ Q) ⇒ (Q ⇒ P)
def arrow_elim := (P ⇒ Q) ⇒ P ⇒ Q
def affirm_consequence := (P ⇒ Q) ⇒ Q ⇒ P
def resolution := (P ∨ Q) ⇒ (¬ Q ∨ R) ⇒ (P ∨ R)
def unit_resolution := (P ∨ Q) ⇒ (¬ Q) ⇒ P
def syllogism := (P ⇒ Q) ⇒ (Q ⇒ R) ⇒ (P ⇒ R)
def modus_tollens := (P ⇒ Q) ⇒ ¬ Q ⇒ ¬ P
def neg_elim := ¬ ¬ P ⇒ P
def excluded_middle := P ∨ ¬ P
def neg_intro := (P ⇒ pFalse) ⇒ ¬ P
def affirm_disjunct := (P ∨ Q) ⇒ P ⇒ ¬ Q
def deny_antecedent := (P ⇒ Q) ⇒ (¬ P ⇒ ¬ Q)

-- Answer below

example : ∃ (n : ℕ), n = 6 :=
exists.intro 6 rfl

example : (∃ (n : ℕ), n = 6) → true :=
begin
assume h,
cases h with w pf,
constructor,
end

axiom Person : Type
axiom Nice : Person → Prop
axiom Tall : Person → Prop

example : (∃ (p : Person), Nice p ∧ Tall p) →
           ∃ (q : Person), Tall q :=
λ h,
    match h with
    | exists.intro w pf := exists.intro w pf.right
    end

  example : false → 0 = 1 :=
  λ f,
  match f with
  end


import tactic.linarith

namespace mth1001

section inequalities_and_upper_bounds

/-
At the start of this module, we looked at simple inequalities. We'll extend our study to
consider bounds and greatest bounds.

These considerations will later form the basis of our study of suprema of sets of real numbers.
-/

-- Given, `x ≤ 2`, we can prove `3 * x + 8 ≥ 5 * x`.
-- Below, `∀ x ≤ 2, 3 * x + 8 ≥ 5 * x` is an abbreviation of `∀ x, x ≤ 2 → 3 * x + 8 ≥ 5 * x`.
example : ∀ x ≤ 2, 3 * x + 8 ≥ 5 * x :=
begin
  intros x hx,
  linarith,
end

-- Exercise 100:
example : ∀ x ≤ 0, 3 * x  + 8 ≥ 5 * x :=
begin
  sorry
end

/-
We can generalise the above, writing `x ≤ y` instead of `x ≤ 2` or `x ≤ 0`. However, it simply
isn't true *for every `y`* that if `x ≤ y`, then `3 * x + 8 ≥ 5 * x`. We ask you to prove this
below.
-/

-- Exercise 101:
example : ¬(∀ y, ∀ x, x ≤ y → 3 * x + 8 ≥ 5 * x) :=
begin
  sorry
end

/-
However, if we impose constraints on `y`, for instance, given `y ≤ 3`, given `x ≤ y`, you can
prove `3 * x + 8 ≥ 5 * x`.
-/

-- Exercise 102:
example : ∀ y ≤ 1, ∀ x ≤ y, 3 * x + 8 ≥ 5 * x :=
begin
  sorry
end

/-
Now `1` isn't the only bound for `y`.
-/

-- Exercise 103:
example : ∀ y ≤ 0, ∀ x ≤ y, 3 * x + 8 ≥ 5 * x :=
begin
  sorry
end

-- Exercise 104:
/-
We can generalise further, writing `y ≤ z` instead of `1` or `0` above. Once more, however, it
isn't true that *for every* `z`, given `y ≤ z`, given `x ≤ y`, one has `3 * x + 8 ≥ 5 * x`.
-/
example : ¬(∀ z, ∀ y ≤ z, ∀ x ≤ y, 3 * x + 8 ≥ 5 * x) :=
begin
  sorry
end

/-
We'll write a new definition `is_bnd_ineq` so that `is_bnd_ineq z` holds if, given `y ≤ z`,
given `x ≤ y`, one has `3 * x + 8 ≥ 5 * x`.
-/

def is_bnd_ineq (z : ℤ) := ∀ y ≤ z, ∀ x ≤ y, 3 * x + 8 ≥ 5 * x

/-
With this definition, we can rephrase some of the results above. The proofs are no different.
-/

-- Exercise 105:
example : is_bnd_ineq 1 :=
begin
  unfold is_bnd_ineq, -- This line isn't necessary for Lean, but helps the human proof writer!
  sorry
end

-- Exercise 106:
example : is_bnd_ineq 0 :=
begin
  sorry
end

/-
We come now to the main definition of this section. What does it mean to be a *greatest* bound?
`z` is a greatest bound if:
* `z` is a bound (i.e. we have `is_bnd_ineq z`) and
* For every `w`, if `w` is a bound, then `w ≤ z`.
-/

def greatest_bnd_ineq (z : ℤ) :=
is_bnd_ineq z ∧ (∀ w, is_bnd_ineq w → w ≤ z)

/-
The main task of this section is to prove `greatest_bnd_ineq 4`, i.e that `4` is a greatest bound.
-/

-- Exercise 107:
example : greatest_bnd_ineq 4 :=
begin
  sorry
end

/-
We'd like to conclude that `4` is *the* greatest bound, but we haven't proved that there is only
one greatest bound. This, we proceed to do.
-/

-- Exercise 108:
example (z₁ z₂ : ℤ) : greatest_bnd_ineq z₁ ∧ greatest_bnd_ineq z₂ → z₁ = z₂ :=
begin
  intro h,
  rcases h with ⟨⟨bnd₁, gt₁⟩, bnd₂, gt₂⟩,
  have : z₁ ≤ z₂,
  { sorry, },
  have : z₂ ≤ z₁,
  { sorry, },
  linarith,
end

end inequalities_and_upper_bounds

end mth1001


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

! This file was ported from Lean 3 source module topology.nhds_set
! leanprover-community/mathlib commit f2ce6086713c78a7f880485f7917ea547a215982
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Topology.Basic

/-!
# Neighborhoods of a set

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

## Main Properties

There are a couple different notions equivalent to `s ∈ 𝓝ˢ t`:
* `s ⊆ interior t` using `subset_interior_iff_mem_nhdsSet`
* `∀ x : α, x ∈ t → s ∈ 𝓝 x` using `mem_nhdsSet_iff_forall`
* `∃ U : Set α, IsOpen U ∧ t ⊆ U ∧ U ⊆ s` using `mem_nhdsSet_iff_exists`

Furthermore, we have the following results:
* `monotone_nhdsSet`: `𝓝ˢ` is monotone
* In T₁-spaces, `𝓝ˢ`is strictly monotone and hence injective:
  `strict_mono_nhdsSet`/`injective_nhdsSet`. These results are in `Mathlib.Topology.Separation`.

-/

open Set Filter Topology

variable {α β : Type _} [TopologicalSpace α] [TopologicalSpace β] {s t s₁ s₂ t₁ t₂ : Set α} {x : α}

/-- The filter of neighborhoods of a set in a topological space. -/
def nhdsSet (s : Set α) : Filter α :=
  supₛ (nhds '' s)
#align nhds_set nhdsSet

@[inherit_doc] scoped[Topology] notation "𝓝ˢ" => nhdsSet

theorem nhdsSet_diagonal (α) [TopologicalSpace (α × α)] :
    𝓝ˢ (diagonal α) = ⨆ (x : α), 𝓝 (x, x) := by
  rw [nhdsSet, ← range_diag, ← range_comp]
  rfl
#align nhds_set_diagonal nhdsSet_diagonal

theorem mem_nhdsSet_iff_forall : s ∈ 𝓝ˢ t ↔ ∀ x : α, x ∈ t → s ∈ 𝓝 x := by
  simp_rw [nhdsSet, Filter.mem_supₛ, ball_image_iff]
#align mem_nhds_set_iff_forall mem_nhdsSet_iff_forall

theorem bUnion_mem_nhdsSet {t : α → Set α} (h : ∀ x ∈ s, t x ∈ 𝓝 x) : (⋃ x ∈ s, t x) ∈ 𝓝ˢ s :=
  mem_nhdsSet_iff_forall.2 fun x hx => mem_of_superset (h x hx) <|
    subset_unionᵢ₂ (s := fun x _ => t x) x hx -- porting note: fails to find `s`
#align bUnion_mem_nhds_set bUnion_mem_nhdsSet

theorem subset_interior_iff_mem_nhdsSet : s ⊆ interior t ↔ t ∈ 𝓝ˢ s := by
  simp_rw [mem_nhdsSet_iff_forall, subset_interior_iff_nhds]
#align subset_interior_iff_mem_nhds_set subset_interior_iff_mem_nhdsSet

theorem disjoint_principal_nhdsSet : Disjoint (𝓟 s) (𝓝ˢ t) ↔ Disjoint (closure s) t := by
  rw [disjoint_principal_left, ← subset_interior_iff_mem_nhdsSet, interior_compl,
    subset_compl_iff_disjoint_left]

theorem disjoint_nhdsSet_principal : Disjoint (𝓝ˢ s) (𝓟 t) ↔ Disjoint s (closure t) := by
  rw [disjoint_comm, disjoint_principal_nhdsSet, disjoint_comm]

theorem mem_nhdsSet_iff_exists : s ∈ 𝓝ˢ t ↔ ∃ U : Set α, IsOpen U ∧ t ⊆ U ∧ U ⊆ s := by
  rw [← subset_interior_iff_mem_nhdsSet, subset_interior_iff]
#align mem_nhds_set_iff_exists mem_nhdsSet_iff_exists

theorem hasBasis_nhdsSet (s : Set α) : (𝓝ˢ s).HasBasis (fun U => IsOpen U ∧ s ⊆ U) fun U => U :=
  ⟨fun t => by simp [mem_nhdsSet_iff_exists, and_assoc]⟩
#align has_basis_nhds_set hasBasis_nhdsSet

theorem IsOpen.mem_nhdsSet (hU : IsOpen s) : s ∈ 𝓝ˢ t ↔ t ⊆ s := by
  rw [← subset_interior_iff_mem_nhdsSet, hU.interior_eq]
#align is_open.mem_nhds_set IsOpen.mem_nhdsSet

theorem principal_le_nhdsSet : 𝓟 s ≤ 𝓝ˢ s := fun _s hs =>
  (subset_interior_iff_mem_nhdsSet.mpr hs).trans interior_subset
#align principal_le_nhds_set principal_le_nhdsSet

@[simp]
theorem nhdsSet_eq_principal_iff : 𝓝ˢ s = 𝓟 s ↔ IsOpen s := by
  rw [← principal_le_nhdsSet.le_iff_eq, le_principal_iff, mem_nhdsSet_iff_forall,
    isOpen_iff_mem_nhds]
#align nhds_set_eq_principal_iff nhdsSet_eq_principal_iff

alias nhdsSet_eq_principal_iff ↔ _ IsOpen.nhdsSet_eq
#align is_open.nhds_set_eq IsOpen.nhdsSet_eq

@[simp]
theorem nhdsSet_interior : 𝓝ˢ (interior s) = 𝓟 (interior s) :=
  isOpen_interior.nhdsSet_eq
#align nhds_set_interior nhdsSet_interior

@[simp]
theorem nhdsSet_singleton : 𝓝ˢ {x} = 𝓝 x := by
  ext
  rw [← subset_interior_iff_mem_nhdsSet, ← mem_interior_iff_mem_nhds, singleton_subset_iff]
#align nhds_set_singleton nhdsSet_singleton

theorem mem_nhdsSet_interior : s ∈ 𝓝ˢ (interior s) :=
  subset_interior_iff_mem_nhdsSet.mp Subset.rfl
#align mem_nhds_set_interior mem_nhdsSet_interior

@[simp]
theorem nhdsSet_empty : 𝓝ˢ (∅ : Set α) = ⊥ := by rw [isOpen_empty.nhdsSet_eq, principal_empty]
#align nhds_set_empty nhdsSet_empty

theorem mem_nhdsSet_empty : s ∈ 𝓝ˢ (∅ : Set α) := by simp
#align mem_nhds_set_empty mem_nhdsSet_empty

@[simp]
theorem nhdsSet_univ : 𝓝ˢ (univ : Set α) = ⊤ := by rw [isOpen_univ.nhdsSet_eq, principal_univ]
#align nhds_set_univ nhdsSet_univ

@[mono]
theorem nhdsSet_mono (h : s ⊆ t) : 𝓝ˢ s ≤ 𝓝ˢ t :=
  supₛ_le_supₛ <| image_subset _ h
#align nhds_set_mono nhdsSet_mono

theorem monotone_nhdsSet : Monotone (𝓝ˢ : Set α → Filter α) := fun _ _ => nhdsSet_mono
#align monotone_nhds_set monotone_nhdsSet

theorem nhds_le_nhdsSet (h : x ∈ s) : 𝓝 x ≤ 𝓝ˢ s :=
  le_supₛ <| mem_image_of_mem _ h
#align nhds_le_nhds_set nhds_le_nhdsSet

@[simp]
theorem nhdsSet_union (s t : Set α) : 𝓝ˢ (s ∪ t) = 𝓝ˢ s ⊔ 𝓝ˢ t := by
  simp only [nhdsSet, image_union, supₛ_union]
#align nhds_set_union nhdsSet_union

theorem union_mem_nhdsSet (h₁ : s₁ ∈ 𝓝ˢ t₁) (h₂ : s₂ ∈ 𝓝ˢ t₂) : s₁ ∪ s₂ ∈ 𝓝ˢ (t₁ ∪ t₂) := by
  rw [nhdsSet_union]
  exact union_mem_sup h₁ h₂
#align union_mem_nhds_set union_mem_nhdsSet

/-- Preimage of a set neighborhood of `t` under a continuous map `f` is a set neighborhood of `s`
provided that `f` maps `s` to `t`.  -/
theorem Continuous.tendsto_nhdsSet {f : α → β} {t : Set β} (hf : Continuous f)
    (hst : MapsTo f s t) : Tendsto f (𝓝ˢ s) (𝓝ˢ t) :=
  ((hasBasis_nhdsSet s).tendsto_iff (hasBasis_nhdsSet t)).mpr fun U hU =>
    ⟨f ⁻¹' U, ⟨hU.1.preimage hf, hst.mono Subset.rfl hU.2⟩, fun _ => id⟩
#align continuous.tendsto_nhds_set Continuous.tendsto_nhdsSet


-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que si f es una función de ℝ en ℝ tal que
-- para cada a, existe un x tal que f x > a, entonces f no tiene cota
-- superior.
-- ----------------------------------------------------------------------

import data.real.basic

def fn_ub (f : ℝ → ℝ) (a : ℝ) : Prop := ∀ x, f x ≤ a

def fn_has_ub (f : ℝ → ℝ) := ∃ a, fn_ub f a

variable f : ℝ → ℝ

lemma no_has_ub
  (h : ∀ a, ∃ x, f x > a)
  : ¬ fn_has_ub f :=
begin
  intros fnub,
  cases fnub with a fnuba,
  cases h a with x hx,
  have : f x ≤ a,
    from fnuba x,
  linarith,
end

-- Prueba
-- ------

-- f : ℝ → ℝ,
-- h : ∀ (a : ℝ), ∃ (x : ℝ), f x > a
-- ⊢ ¬fn_has_ub f
--    >> intros fnub,
-- fnub : fn_has_ub f
-- ⊢ false
--    >> cases fnub with a fnuba,
-- a : ℝ,
-- fnuba : fn_ub f a
-- ⊢ false
--    >> cases h a with x hx,
-- x : ℝ,
-- hx : f x > a
-- ⊢ false
--    >> have : f x ≤ a,
--    >>   from fnuba x,
-- this : f x ≤ a
-- ⊢ false
--    >> linarith,
-- no goals


/-
Copyright (c) 2015 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura, Jeremy Avigad, Mario Carneiro
-/
import data.nat.prime
import data.list.prime
import data.list.sort
import tactic.nth_rewrite

/-!
# Prime numbers

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

This file deals with the factors of natural numbers.

## Important declarations

- `nat.factors n`: the prime factorization of `n`
- `nat.factors_unique`: uniqueness of the prime factorisation

-/

open bool subtype
open_locale nat

namespace nat

/-- `factors n` is the prime factorization of `n`, listed in increasing order. -/
def factors : ℕ → list ℕ
| 0 := []
| 1 := []
| n@(k+2) :=
  let m := min_fac n in have n / m < n := factors_lemma,
  m :: factors (n / m)

@[simp] lemma factors_zero : factors 0 = [] := by rw factors
@[simp] lemma factors_one : factors 1 = [] := by rw factors

lemma prime_of_mem_factors : ∀ {n p}, p ∈ factors n → prime p
| 0       := by simp
| 1       := by simp
| n@(k+2) := λ p h,
  let m := min_fac n in have n / m < n := factors_lemma,
  have h₁ : p = m ∨ p ∈ (factors (n / m)) :=
    (list.mem_cons_iff _ _ _).1 (by rwa [factors] at h),
  or.cases_on h₁ (λ h₂, h₂.symm ▸ min_fac_prime dec_trivial)
    prime_of_mem_factors

lemma pos_of_mem_factors {n p : ℕ} (h : p ∈ factors n) : 0 < p :=
prime.pos (prime_of_mem_factors h)

lemma prod_factors : ∀ {n}, n ≠ 0 → list.prod (factors n) = n
| 0       := by simp
| 1       := by simp
| n@(k+2) := λ h,
  let m := min_fac n in have n / m < n := factors_lemma,
  show (factors n).prod = n, from
  have h₁ : n / m ≠ 0 := λ h,
    have n = 0 * m := (nat.div_eq_iff_eq_mul_left (min_fac_pos _) (min_fac_dvd _)).1 h,
    by rw zero_mul at this; exact (show k + 2 ≠ 0, from dec_trivial) this,
  by rw [factors, list.prod_cons, prod_factors h₁, nat.mul_div_cancel' (min_fac_dvd _)]

lemma factors_prime {p : ℕ} (hp : nat.prime p) : p.factors = [p] :=
begin
  have : p = (p - 2) + 2 := (tsub_eq_iff_eq_add_of_le hp.two_le).mp rfl,
  rw [this, nat.factors],
  simp only [eq.symm this],
  have : nat.min_fac p = p := (nat.prime_def_min_fac.mp hp).2,
  split,
  { exact this, },
  { simp only [this, nat.factors, nat.div_self (nat.prime.pos hp)], },
end

lemma factors_chain : ∀ {n a}, (∀ p, prime p → p ∣ n → a ≤ p) → list.chain (≤) a (factors n)
| 0       := λ a h, by simp
| 1       := λ a h, by simp
| n@(k+2) := λ a h,
  let m := min_fac n in have n / m < n := factors_lemma,
  begin
    rw factors,
    refine list.chain.cons ((le_min_fac.2 h).resolve_left dec_trivial) (factors_chain _),
    exact λ p pp d, min_fac_le_of_dvd pp.two_le (d.trans $ div_dvd_of_dvd $ min_fac_dvd _),
  end

lemma factors_chain_2 (n) : list.chain (≤) 2 (factors n) := factors_chain $ λ p pp _, pp.two_le

lemma factors_chain' (n) : list.chain' (≤) (factors n) :=
@list.chain'.tail _ _ (_::_) (factors_chain_2 _)

lemma factors_sorted (n : ℕ) : list.sorted (≤) (factors n) :=
list.chain'_iff_pairwise.1 (factors_chain' _)

/-- `factors` can be constructed inductively by extracting `min_fac`, for sufficiently large `n`. -/
lemma factors_add_two (n : ℕ) :
  factors (n+2) = min_fac (n+2) :: factors ((n+2) / min_fac (n+2)) :=
by rw factors

@[simp]
lemma factors_eq_nil (n : ℕ) : n.factors = [] ↔ n = 0 ∨ n = 1 :=
begin
  split; intro h,
  { rcases n with (_ | _ | n),
    { exact or.inl rfl },
    { exact or.inr rfl },
    { rw factors at h, injection h }, },
  { rcases h with (rfl | rfl),
    { exact factors_zero },
    { exact factors_one }, }
end

lemma eq_of_perm_factors {a b : ℕ} (ha : a ≠ 0) (hb : b ≠ 0) (h : a.factors ~ b.factors) : a = b :=
by simpa [prod_factors ha, prod_factors hb] using list.perm.prod_eq h

section
open list

lemma mem_factors_iff_dvd {n p : ℕ} (hn : n ≠ 0) (hp : prime p) : p ∈ factors n ↔ p ∣ n :=
⟨λ h, prod_factors hn ▸ list.dvd_prod h,
  λ h, mem_list_primes_of_dvd_prod
    (prime_iff.mp hp)
    (λ p h, prime_iff.mp (prime_of_mem_factors h))
    ((prod_factors hn).symm ▸ h)⟩

lemma dvd_of_mem_factors {n p : ℕ} (h : p ∈ n.factors) : p ∣ n :=
begin
  rcases n.eq_zero_or_pos with rfl | hn,
  { exact dvd_zero p },
  { rwa ←mem_factors_iff_dvd hn.ne' (prime_of_mem_factors h) }
end

lemma mem_factors {n p} (hn : n ≠ 0) : p ∈ factors n ↔ prime p ∧ p ∣ n :=
⟨λ h, ⟨prime_of_mem_factors h, dvd_of_mem_factors h⟩,
 λ ⟨hprime, hdvd⟩, (mem_factors_iff_dvd hn hprime).mpr hdvd⟩

lemma le_of_mem_factors {n p : ℕ} (h : p ∈ n.factors) : p ≤ n :=
begin
  rcases n.eq_zero_or_pos with rfl | hn,
  { rw factors_zero at h, cases h },
  { exact le_of_dvd hn (dvd_of_mem_factors h) },
end

/-- **Fundamental theorem of arithmetic**-/
lemma factors_unique {n : ℕ} {l : list ℕ} (h₁ : prod l = n) (h₂ : ∀ p ∈ l, prime p) :
  l ~ factors n :=
begin
  refine perm_of_prod_eq_prod _ _ _,
  { rw h₁,
    refine (prod_factors _).symm,
    rintro rfl,
    rw prod_eq_zero_iff at h₁,
    exact prime.ne_zero (h₂ 0 h₁) rfl },
  { simp_rw ←prime_iff, exact h₂ },
  { simp_rw ←prime_iff, exact (λ p, prime_of_mem_factors) },
end

lemma prime.factors_pow {p : ℕ} (hp : p.prime) (n : ℕ) :
  (p ^ n).factors = list.replicate n p :=
begin
  symmetry,
  rw ← list.replicate_perm,
  apply nat.factors_unique (list.prod_replicate n p),
  intros q hq,
  rwa eq_of_mem_replicate hq,
end

lemma eq_prime_pow_of_unique_prime_dvd {n p : ℕ} (hpos : n ≠ 0)
  (h : ∀ {d}, nat.prime d → d ∣ n → d = p) :
  n = p ^ n.factors.length :=
begin
  set k := n.factors.length,
  rw [← prod_factors hpos, ← prod_replicate k p,
    eq_replicate_of_mem (λ d hd, h (prime_of_mem_factors hd) (dvd_of_mem_factors hd))],
end

/-- For positive `a` and `b`, the prime factors of `a * b` are the union of those of `a` and `b` -/
lemma perm_factors_mul {a b : ℕ} (ha : a ≠ 0) (hb : b ≠ 0) :
  (a * b).factors ~ a.factors ++ b.factors :=
begin
  refine (factors_unique _ _).symm,
  { rw [list.prod_append, prod_factors ha, prod_factors hb] },
  { intros p hp,
    rw list.mem_append at hp,
    cases hp;
    exact prime_of_mem_factors hp },
end

/-- For coprime `a` and `b`, the prime factors of `a * b` are the union of those of `a` and `b` -/
lemma perm_factors_mul_of_coprime {a b : ℕ} (hab : coprime a b) :
  (a * b).factors ~ a.factors ++ b.factors :=
begin
  rcases a.eq_zero_or_pos with rfl | ha,
  { simp [(coprime_zero_left _).mp hab] },
  rcases b.eq_zero_or_pos with rfl | hb,
  { simp [(coprime_zero_right _).mp hab] },
  exact perm_factors_mul ha.ne' hb.ne',
end

lemma factors_sublist_right {n k : ℕ} (h : k ≠ 0) : n.factors <+ (n * k).factors :=
begin
  cases n,
  { rw zero_mul },
  apply sublist_of_subperm_of_sorted _ (factors_sorted _) (factors_sorted _),
  rw (perm_factors_mul n.succ_ne_zero h).subperm_left,
  exact (sublist_append_left _ _).subperm,
end

lemma factors_sublist_of_dvd {n k : ℕ} (h : n ∣ k) (h' : k ≠ 0) : n.factors <+ k.factors :=
begin
  obtain ⟨a, rfl⟩ := h,
  exact factors_sublist_right (right_ne_zero_of_mul h'),
end

lemma factors_subset_right {n k : ℕ} (h : k ≠ 0) : n.factors ⊆ (n * k).factors :=
(factors_sublist_right h).subset

lemma factors_subset_of_dvd {n k : ℕ} (h : n ∣ k) (h' : k ≠ 0) : n.factors ⊆ k.factors :=
(factors_sublist_of_dvd h h').subset

lemma dvd_of_factors_subperm {a b : ℕ} (ha : a ≠ 0) (h : a.factors <+~ b.factors) : a ∣ b :=
begin
  rcases b.eq_zero_or_pos with rfl | hb,
  { exact dvd_zero _ },
  rcases a with (_|_|a),
  { exact (ha rfl).elim },
  { exact one_dvd _ },
  use (b.factors.diff a.succ.succ.factors).prod,
  nth_rewrite 0 ←nat.prod_factors ha,
  rw [←list.prod_append,
      list.perm.prod_eq $ list.subperm_append_diff_self_of_count_le $ list.subperm_ext_iff.mp h,
      nat.prod_factors hb.ne']
end

end

lemma mem_factors_mul {a b : ℕ} (ha : a ≠ 0) (hb : b ≠ 0) {p : ℕ} :
  p ∈ (a * b).factors ↔ p ∈ a.factors ∨ p ∈ b.factors :=
begin
  rw [mem_factors (mul_ne_zero ha hb), mem_factors ha, mem_factors hb, ←and_or_distrib_left],
  simpa only [and.congr_right_iff] using prime.dvd_mul
end

/-- The sets of factors of coprime `a` and `b` are disjoint -/
lemma coprime_factors_disjoint {a b : ℕ} (hab : a.coprime b) : list.disjoint a.factors b.factors :=
begin
  intros q hqa hqb,
  apply not_prime_one,
  rw ←(eq_one_of_dvd_coprimes hab (dvd_of_mem_factors hqa) (dvd_of_mem_factors hqb)),
  exact prime_of_mem_factors hqa
end

lemma mem_factors_mul_of_coprime {a b : ℕ} (hab : coprime a b) (p : ℕ):
  p ∈ (a * b).factors ↔ p ∈ a.factors ∪ b.factors :=
begin
  rcases a.eq_zero_or_pos with rfl | ha,
  { simp [(coprime_zero_left _).mp hab] },
  rcases b.eq_zero_or_pos with rfl | hb,
  { simp [(coprime_zero_right _).mp hab] },
  rw [mem_factors_mul ha.ne' hb.ne', list.mem_union]
end

open list

/-- If `p` is a prime factor of `a` then `p` is also a prime factor of `a * b` for any `b > 0` -/
lemma mem_factors_mul_left {p a b : ℕ} (hpa : p ∈ a.factors) (hb : b ≠ 0) : p ∈ (a*b).factors :=
begin
  rcases eq_or_ne a 0 with rfl | ha,
  { simpa using hpa },
  apply (mem_factors_mul ha hb).2 (or.inl hpa),
end

/-- If `p` is a prime factor of `b` then `p` is also a prime factor of `a * b` for any `a > 0` -/
lemma mem_factors_mul_right {p a b : ℕ} (hpb : p ∈ b.factors) (ha : a ≠ 0) : p ∈ (a*b).factors :=
by { rw mul_comm, exact mem_factors_mul_left hpb ha }

lemma eq_two_pow_or_exists_odd_prime_and_dvd (n : ℕ) :
  (∃ k : ℕ, n = 2 ^ k) ∨ ∃ p, nat.prime p ∧ p ∣ n ∧ odd p :=
(eq_or_ne n 0).elim
  (λ hn, (or.inr ⟨3, prime_three, hn.symm ▸ dvd_zero 3, ⟨1, rfl⟩⟩))
  (λ hn, or_iff_not_imp_right.mpr
    (λ H, ⟨n.factors.length, eq_prime_pow_of_unique_prime_dvd hn
      (λ p hprime hdvd, hprime.eq_two_or_odd'.resolve_right
        (λ hodd, H ⟨p, hprime, hdvd, hodd⟩))⟩))

end nat

assert_not_exists multiset


import combinatorics.simple_graph.basic
import combinatorics.simple_graph.degree_sum
import data.finset.basic
import data.nat.basic
import tactic.core
import algebra.big_operators

--local
import misc_finset

open finset nat
open_locale big_operators
namespace simple_graph

-- When counting edges in graphs we often want to consider subgraphs induced by a set of vertices
-- or subgraphs between two (possibly disjoint) sets of vertices
-- For this purpose we introduce the restricted neighbourhood a vertex to a finset.
-- this is G.nbhd_res v A = A ∩ G.neighbor_finset v

-- the restricted nbhd of a set of vertices
section nbhd_res
variables {t n : ℕ}
variables {α : Type*} (G : simple_graph α) [fintype α][nonempty α]{s : finset α}[decidable_eq α][decidable_rel G.adj]

-- restricted nbhd is the part of nbhd in A
include G
@[ext]
def nbhd_res (v : α) (A : finset α) : finset α := A ∩ G.neighbor_finset v

-- restriction of degree to A
def deg_res (v : α) (A : finset α) : ℕ:= (G.nbhd_res v A).card

-- restricting to univ is no restriction at all
lemma deg_res_univ (v : α) : G.deg_res v univ = G.degree v:=
begin
  rw [deg_res,degree], congr, rw [nbhd_res,univ_inter],
end

-- we only define this over A restricted to A (could be broader)
-- max deg res is zero if A is empty
-- could replace this with (G.ind A).max_degree
def max_deg_res (A :finset α) : ℕ :=option.get_or_else (A.image (λ v, G.deg_res v A)).max 0

-- if A.nonempty then there is a vertex of max_deg_res A
lemma exists_max_res_deg_vertex  {A :finset α} (hA: A.nonempty) :
  ∃ v∈A, G.max_deg_res A  = G.deg_res v A :=
begin
  have neim: (A.image (λ v, G.deg_res v A)).nonempty:=nonempty.image hA _,
  obtain ⟨t, ht⟩ := max_of_nonempty neim,
  have ht₂ := mem_of_max ht,
  simp only [pi.coe_nat, nat.cast_id, exists_prop, nonempty.image_iff, mem_image] at *,
  rcases ht₂ with ⟨a,ha1, ha2⟩,
  refine ⟨a, _⟩,
  rw [max_deg_res, ht,option.get_or_else_coe],
  exact ⟨ha1,ha2.symm⟩,
end

-- The max_deg_res over A is at least the deg_res of any particular vertex in A.
lemma deg_res_le_max_deg_res  {v : α} {A : finset α} (hvA: v ∈ A) : G.deg_res v A ≤ G.max_deg_res A :=
begin
  have hA: A.nonempty:=⟨v,hvA⟩,
  obtain ⟨t, ht : _ = _⟩ := finset.max_of_mem (mem_image_of_mem (λ v, G.deg_res v A) hvA),
  have := finset.le_max_of_mem (mem_image_of_mem _ hvA) ht,
  rwa [max_deg_res,ht],
end

-- bound on sum of deg_res given max deg_res (also a bound on e(C) for C ⊆ A)
-- or equiv if C ⊆ A then 2*e(G[C])+e(G[C,A\C])≤ (G.ind A).max_degree * |C|
lemma max_deg_res_sum_le {A C : finset α} (hC: C ⊆ A) : ∑ v in C, G.deg_res v A ≤ (G.max_deg_res A)*(C.card):=
begin
  rw [card_eq_sum_ones, mul_sum, mul_one],
  apply sum_le_sum _, intros i hi, exact G.deg_res_le_max_deg_res (hC hi),
end

-- restricted degree to A is sum of ones over each neighbour of v in A
lemma deg_res_ones (v : α) (A : finset α) : G.deg_res v A = ∑ x in G.nbhd_res v A, 1:=card_eq_sum_ones _

--- if the restricted nbhd is non-empty then v has a neighbor in A
lemma exists_mem_nempty {v :α} {A : finset α} (hA:  ¬(G.nbhd_res v A) = ∅ ): ∃ w∈A, G.adj v w :=
begin
  rw nbhd_res at hA, contrapose! hA,
  rw eq_empty_iff_forall_not_mem,
  intros x hx, rw [mem_inter, mem_neighbor_finset] at hx,
  exact hA x hx.1 hx.2,
end

-- member of the restricted nhd iff in nbhd and in A
lemma mem_res_nbhd (v w : α) (A : finset α) : w ∈ G.nbhd_res v A ↔ w ∈ A ∧ w ∈ G.neighbor_finset v
:=by rwa [nbhd_res,mem_inter]

-- v is not a neighbor of itself
lemma not_mem_nbhd (v : α)  : v ∉ G.neighbor_finset v :=
begin
 rw mem_neighbor_finset, exact G.loopless v,
end

-- nor is v a restricted neighbor of itself
lemma not_mem_res_nbhd (v : α) (A :finset α) : v ∉ G.nbhd_res v A :=
begin
  rw mem_res_nbhd,push_neg,intro h, exact G.not_mem_nbhd v,
end

-- restricted nbhd is contained in A
lemma sub_res_nbhd_A (v : α) (A : finset α) : G.nbhd_res v A ⊆ A:=
begin
  intro x, rw mem_res_nbhd,intro h, exact h.1,
end

-- restricted nbhd of member is stictly contained in A
lemma ssub_res_nbhd_of_mem {v : α} {A : finset α} (h: v ∈ A) : G.nbhd_res v A ⊂ A
:=(ssubset_iff_of_subset (G.sub_res_nbhd_A v A)).mpr ⟨v,h,G.not_mem_res_nbhd v A⟩

-- restricted nbhd contained in nbhd
lemma sub_res_nbhd_N (v : α)(A : finset α) : G.nbhd_res v A ⊆ G.neighbor_finset v:=
begin
  intro _, rw mem_res_nbhd, intro h, exact h.2,
end

-- restricted degree additive over partition of A into B ∪ A\B
-- this is daft, it would work for any function defined on A
lemma sum_sdf {A B: finset α} (hB: B ⊆ A) (C: finset α):
 ∑ v in A, G.deg_res v C = ∑v in B, G.deg_res v C + ∑ v in A\B, G.deg_res v C:=
begin
  nth_rewrite 0 ← union_sdiff_of_subset hB, exact sum_union (disjoint_sdiff),
end

-- restricted deg over A = restricted deg over B + restricted deg over A\B
lemma deg_res_add  {v : α} {A B : finset α} (hB: B ⊆ A): G.deg_res v A=  G.deg_res v B +  G.deg_res v (A\B):=
begin
  simp [deg_res,nbhd_res], nth_rewrite 0 ← union_sdiff_of_subset hB,
  rw inter_distrib_right B (A\B) _,
  exact card_disjoint_union (sdiff_inter_disj A B _),
end

-- sum version of previous lemma
lemma deg_res_add_sum {A B C : finset α} (hB: B ⊆ A) : ∑ v in C, G.deg_res v A=  ∑ v in C, G.deg_res v B+  ∑ v in C,G.deg_res v (A\B):=
begin
  rw ← sum_add_distrib, exact sum_congr rfl (λ _ _ , G.deg_res_add hB),
end

-- if A and B are disjoint then for any vertex v the deg_res add
lemma deg_res_add'  {v : α} {A B : finset α} (h: disjoint A B): G.deg_res v (A∪B)=  G.deg_res v A +  G.deg_res v B:=
begin
  simp [deg_res,nbhd_res],  rw inter_distrib_right,
  exact card_disjoint_union (disj_of_inter_disj _ _ h),
end

-- sum version of previous lemma
lemma deg_res_add_sum' {A B C: finset α} (h: disjoint A B) : ∑ v in C, G.deg_res v (A ∪ B) = ∑ v in C, G.deg_res v A +∑ v in C, G.deg_res v B:=
begin
  rw ← sum_add_distrib, exact sum_congr rfl (λ _ _ , G.deg_res_add' h),
end

-- counting edges exiting B via ite helper, really just counting edges in e(B,A\B)
lemma bip_count_help {A B : finset α} (hB: B ⊆ A) : ∑ v in B, G.deg_res v (A\B) = ∑ v in B, ∑ w in A\B, ite (G.adj v w) 1 0:=
begin
  simp only [deg_res_ones], congr,ext x, simp only [sum_const, algebra.id.smul_eq_mul, mul_one, sum_boole, cast_id],
  congr, ext, rwa [mem_res_nbhd,mem_filter,mem_neighbor_finset],
end

-- edges from B to A\B equals edges from A\B to B
lemma bip_count {A B : finset α} (hB: B ⊆ A) : ∑ v in B, G.deg_res v (A\B) = ∑ v in A\B, G.deg_res v B:=
begin
  rw G.bip_count_help hB,
  have:=sdiff_sdiff_eq_self hB,
  conv { to_rhs,congr, skip,rw ← this,},
  rw [G.bip_count_help (sdiff_subset A B),this,sum_comm],
  congr, ext y, congr,ext x,
  split_ifs,{refl},{exfalso, rw adj_comm at h, exact h_1 h},
  {exfalso, rw adj_comm at h, exact h h_1},{refl},
end

-- same but between any pair of disjoint sets rather tha B⊆A and A\B
lemma bip_count_help' {A B : finset α}  (hB: disjoint A B ) : ∑ v in B, G.deg_res v A = ∑ v in B, ∑ w in A, ite (G.adj v w) 1 0:=
begin
  simp only [deg_res_ones], congr,ext x, simp only [sum_const, algebra.id.smul_eq_mul, mul_one, sum_boole, cast_id],
  congr, ext, rwa [mem_res_nbhd,mem_filter,mem_neighbor_finset],
end

-- edges from A to B (disjoint) equals edges from B to A
lemma bip_count' {A B : finset α} (hB: disjoint A B ) : ∑ v in B, G.deg_res v A = ∑ v in A, G.deg_res v B:=
begin
  rw G.bip_count_help' hB, rw G.bip_count_help' hB.symm,rw sum_comm, congr,
  ext y, congr,ext x,
  split_ifs,{refl},{exfalso, rw adj_comm at h, exact h_1 h},
  {exfalso, rw adj_comm at h, exact h h_1},{refl},
end

-- sum of res_res_deg ≤ sum of res_deg
lemma sum_res_le {A B C: finset α} (hB: B ⊆ A) (hC: C ⊆ A): ∑ v in C, G.deg_res v B ≤ ∑ v in C, G.deg_res v A :=
begin
  apply sum_le_sum _,
  intros i hi,
  rw [deg_res,deg_res], apply card_le_of_subset _,
  intros x hx, rw [mem_res_nbhd] at *,
  exact ⟨hB hx.1, hx.2⟩,
end

end nbhd_res
#lint
end simple_graph


/-
Copyright (c) 2022 Violeta Hernández Palacios. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Violeta Hernández Palacios
-/
import order.min_max
import order.rel_classes
import data.set.intervals.basic

/-!
# Bounded and unbounded sets

We prove miscellaneous lemmas about bounded and unbounded sets. Many of these are just variations on
the same ideas, or similar results with a few minor differences. The file is divided into these
different general ideas.
-/

namespace set
variables {α : Type*} {r : α → α → Prop} {s t : set α}

/-! ### Subsets of bounded and unbounded sets -/

theorem bounded.mono (hst : s ⊆ t) (hs : bounded r t) : bounded r s :=
hs.imp $ λ a ha b hb, ha b (hst hb)

theorem unbounded.mono (hst : s ⊆ t) (hs : unbounded r s) : unbounded r t :=
λ a, let ⟨b, hb, hb'⟩ := hs a in ⟨b, hst hb, hb'⟩

/-! ### Alternate characterizations of unboundedness on orders -/

lemma unbounded_le_of_forall_exists_lt [preorder α] (h : ∀ a, ∃ b ∈ s, a < b) : unbounded (≤) s :=
λ a, let ⟨b, hb, hb'⟩ := h a in ⟨b, hb, λ hba, hba.not_lt hb'⟩

lemma unbounded_le_iff [linear_order α] : unbounded (≤) s ↔ ∀ a, ∃ b ∈ s, a < b :=
by simp only [unbounded, not_le]

lemma unbounded_lt_of_forall_exists_le [preorder α] (h : ∀ a, ∃ b ∈ s, a ≤ b) : unbounded (<) s :=
λ a, let ⟨b, hb, hb'⟩ := h a in ⟨b, hb, λ hba, hba.not_le hb'⟩

lemma unbounded_lt_iff [linear_order α] : unbounded (<) s ↔ ∀ a, ∃ b ∈ s, a ≤ b :=
by simp only [unbounded, not_lt]

lemma unbounded_ge_of_forall_exists_gt [preorder α] (h : ∀ a, ∃ b ∈ s, b < a) : unbounded (≥) s :=
@unbounded_le_of_forall_exists_lt (order_dual α) _ _ h

lemma unbounded_ge_iff [linear_order α] : unbounded (≥) s ↔ ∀ a, ∃ b ∈ s, b < a :=
⟨λ h a, let ⟨b, hb, hba⟩ := h a in ⟨b, hb, lt_of_not_ge hba⟩, unbounded_ge_of_forall_exists_gt⟩

lemma unbounded_gt_of_forall_exists_ge [preorder α] (h : ∀ a, ∃ b ∈ s, b ≤ a) : unbounded (>) s :=
λ a, let ⟨b, hb, hb'⟩ := h a in ⟨b, hb, λ hba, not_le_of_gt hba hb'⟩

lemma unbounded_gt_iff [linear_order α] : unbounded (>) s ↔ ∀ a, ∃ b ∈ s, b ≤ a :=
⟨λ h a, let ⟨b, hb, hba⟩ := h a in ⟨b, hb, le_of_not_gt hba⟩, unbounded_gt_of_forall_exists_ge⟩

/-! ### Relation between boundedness by strict and nonstrict orders. -/

/-! #### Less and less or equal -/

lemma bounded.rel_mono {r' : α → α → Prop} (h : bounded r s) (hrr' : r ≤ r') : bounded r' s :=
let ⟨a, ha⟩ := h in ⟨a, λ b hb, hrr' b a (ha b hb)⟩

lemma bounded_le_of_bounded_lt [preorder α] (h : bounded (<) s) : bounded (≤) s :=
h.rel_mono $ λ _ _, le_of_lt

lemma unbounded.rel_mono {r' : α → α → Prop} (hr : r' ≤ r) (h : unbounded r s) : unbounded r' s :=
λ a, let ⟨b, hb, hba⟩ := h a in ⟨b, hb, λ hba', hba (hr b a hba')⟩

lemma unbounded_lt_of_unbounded_le [preorder α] (h : unbounded (≤) s) :
  unbounded (<) s :=
h.rel_mono $ λ _ _, le_of_lt

lemma bounded_le_iff_bounded_lt [preorder α] [no_max_order α] : bounded (≤) s ↔ bounded (<) s :=
begin
  refine ⟨λ h, _, bounded_le_of_bounded_lt⟩,
  cases h with a ha,
  cases exists_gt a with b hb,
  exact ⟨b, λ c hc, lt_of_le_of_lt (ha c hc) hb⟩
end

lemma unbounded_lt_iff_unbounded_le [preorder α] [no_max_order α] :
  unbounded (<) s ↔ unbounded (≤) s :=
by simp_rw [← not_bounded_iff, bounded_le_iff_bounded_lt]

/-! #### Greater and greater or equal -/

lemma bounded_ge_of_bounded_gt [preorder α] (h : bounded (>) s) : bounded (≥) s :=
let ⟨a, ha⟩ := h in ⟨a, λ b hb, le_of_lt (ha b hb)⟩

lemma unbounded_gt_of_unbounded_ge [preorder α] (h : unbounded (≥) s) : unbounded (>) s :=
λ a, let ⟨b, hb, hba⟩ := h a in ⟨b, hb, λ hba', hba (le_of_lt hba')⟩

lemma bounded_ge_iff_bounded_gt [preorder α] [no_min_order α] : bounded (≥) s ↔ bounded (>) s :=
@bounded_le_iff_bounded_lt (order_dual α) _ _ _

lemma unbounded_gt_iff_unbounded_ge [preorder α] [no_min_order α] :
  unbounded (>) s ↔ unbounded (≥) s :=
@unbounded_lt_iff_unbounded_le (order_dual α) _ _ _

/-! ### Bounded and unbounded intervals -/

theorem bounded_self (a : α) : bounded r {b | r b a} :=
⟨a, λ x, id⟩

/-! #### Half-open bounded intervals -/

theorem bounded_le_Iio [preorder α] (a : α) : bounded (≤) (set.Iio a) :=
bounded_le_of_bounded_lt (bounded_lt_Iio a)

theorem bounded_le_Iic [preorder α] (a : α) : bounded (≤) (set.Iic a) :=
bounded_self a

theorem bounded_lt_Iic [preorder α] [no_max_order α] (a : α) : bounded (<) (set.Iic a) :=
by simp only [← bounded_le_iff_bounded_lt, bounded_le_Iic]

theorem bounded_gt_Ioi [preorder α] (a : α) : bounded (>) (set.Ioi a) :=
bounded_self a

theorem bounded_ge_Ioi [preorder α] (a : α) : bounded (≥) (set.Ioi a) :=
bounded_ge_of_bounded_gt (bounded_gt_Ioi a)

theorem bounded_ge_Ici [preorder α] (a : α) : bounded (≥) (set.Ici a) :=
bounded_self a

theorem bounded_gt_Ici [preorder α] [no_min_order α] (a : α) : bounded (>) (set.Ici a) :=
by simp only [← bounded_ge_iff_bounded_gt, bounded_ge_Ici]

/-! #### Other bounded intervals -/

theorem bounded_lt_Ioo [preorder α] (a b : α) : bounded (<) (set.Ioo a b) :=
(bounded_lt_Iio b).mono set.Ioo_subset_Iio_self

theorem bounded_lt_Ico [preorder α] (a b : α) : bounded (<) (set.Ico a b) :=
(bounded_lt_Iio b).mono set.Ico_subset_Iio_self

theorem bounded_lt_Ioc [preorder α] [no_max_order α] (a b : α) : bounded (<) (set.Ioc a b) :=
(bounded_lt_Iic b).mono set.Ioc_subset_Iic_self

theorem bounded_lt_Icc [preorder α] [no_max_order α] (a b : α) : bounded (<) (set.Icc a b) :=
(bounded_lt_Iic b).mono set.Icc_subset_Iic_self

theorem bounded_le_Ioo [preorder α] (a b : α) : bounded (≤) (set.Ioo a b) :=
(bounded_le_Iio b).mono set.Ioo_subset_Iio_self

theorem bounded_le_Ico [preorder α] (a b : α) : bounded (≤) (set.Ico a b) :=
(bounded_le_Iio b).mono set.Ico_subset_Iio_self

theorem bounded_le_Ioc [preorder α] (a b : α) : bounded (≤) (set.Ioc a b) :=
(bounded_le_Iic b).mono set.Ioc_subset_Iic_self

theorem bounded_le_Icc [preorder α] (a b : α) : bounded (≤) (set.Icc a b) :=
(bounded_le_Iic b).mono set.Icc_subset_Iic_self

theorem bounded_gt_Ioo [preorder α] (a b : α) : bounded (>) (set.Ioo a b) :=
(bounded_gt_Ioi a).mono set.Ioo_subset_Ioi_self

theorem bounded_gt_Ioc [preorder α] (a b : α) : bounded (>) (set.Ioc a b) :=
(bounded_gt_Ioi a).mono set.Ioc_subset_Ioi_self

theorem bounded_gt_Ico [preorder α] [no_min_order α] (a b : α) : bounded (>) (set.Ico a b) :=
(bounded_gt_Ici a).mono set.Ico_subset_Ici_self

theorem bounded_gt_Icc [preorder α] [no_min_order α] (a b : α) : bounded (>) (set.Icc a b) :=
(bounded_gt_Ici a).mono set.Icc_subset_Ici_self

theorem bounded_ge_Ioo [preorder α] (a b : α) : bounded (≥) (set.Ioo a b) :=
(bounded_ge_Ioi a).mono set.Ioo_subset_Ioi_self

theorem bounded_ge_Ioc [preorder α] (a b : α) : bounded (≥) (set.Ioc a b) :=
(bounded_ge_Ioi a).mono set.Ioc_subset_Ioi_self

theorem bounded_ge_Ico [preorder α] (a b : α) : bounded (≥) (set.Ico a b) :=
(bounded_ge_Ici a).mono set.Ico_subset_Ici_self

theorem bounded_ge_Icc [preorder α] (a b : α) : bounded (≥) (set.Icc a b) :=
(bounded_ge_Ici a).mono set.Icc_subset_Ici_self

/-! #### Unbounded intervals -/

theorem unbounded_le_Ioi [semilattice_sup α] [no_max_order α] (a : α) : unbounded (≤) (set.Ioi a) :=
λ b, let ⟨c, hc⟩ := exists_gt (a ⊔ b) in
  ⟨c, le_sup_left.trans_lt hc, (le_sup_right.trans_lt hc).not_le⟩

theorem unbounded_le_Ici [semilattice_sup α] [no_max_order α] (a : α) : unbounded (≤) (set.Ici a) :=
(unbounded_le_Ioi a).mono set.Ioi_subset_Ici_self

theorem unbounded_lt_Ioi [semilattice_sup α] [no_max_order α] (a : α) : unbounded (<) (set.Ioi a) :=
unbounded_lt_of_unbounded_le (unbounded_le_Ioi a)

theorem unbounded_lt_Ici [semilattice_sup α] (a : α) : unbounded (<) (set.Ici a) :=
λ b, ⟨a ⊔ b, le_sup_left, le_sup_right.not_lt⟩

/-! ### Bounded initial segments -/

theorem bounded_inter_not (H : ∀ a b, ∃ m, ∀ c, r c a ∨ r c b → r c m) (a : α) :
  bounded r (s ∩ {b | ¬ r b a}) ↔ bounded r s :=
begin
  refine ⟨_, bounded.mono (set.inter_subset_left s _)⟩,
  rintro ⟨b, hb⟩,
  cases H a b with m hm,
  exact ⟨m, λ c hc, hm c (or_iff_not_imp_left.2 (λ hca, (hb c ⟨hc, hca⟩)))⟩
end

theorem unbounded_inter_not (H : ∀ a b, ∃ m, ∀ c, r c a ∨ r c b → r c m) (a : α) :
  unbounded r (s ∩ {b | ¬ r b a}) ↔ unbounded r s :=
by simp_rw [← not_bounded_iff, bounded_inter_not H]

/-! #### Less or equal -/

theorem bounded_le_inter_not_le [semilattice_sup α] (a : α) :
  bounded (≤) (s ∩ {b | ¬ b ≤ a}) ↔ bounded (≤) s :=
bounded_inter_not (λ x y, ⟨x ⊔ y, λ z h, h.elim le_sup_of_le_left le_sup_of_le_right⟩) a

theorem unbounded_le_inter_not_le [semilattice_sup α] (a : α) :
  unbounded (≤) (s ∩ {b | ¬ b ≤ a}) ↔ unbounded (≤) s :=
begin
  rw [←not_bounded_iff, ←not_bounded_iff, not_iff_not],
  exact bounded_le_inter_not_le a
end

theorem bounded_le_inter_lt [linear_order α] (a : α) :
  bounded (≤) (s ∩ {b | a < b}) ↔ bounded (≤) s :=
by simp_rw [← not_le, bounded_le_inter_not_le]

theorem unbounded_le_inter_lt [linear_order α] (a : α) :
  unbounded (≤) (s ∩ {b | a < b}) ↔ unbounded (≤) s :=
by { convert unbounded_le_inter_not_le a, ext, exact lt_iff_not_ge' }

theorem bounded_le_inter_le [linear_order α] (a : α) :
  bounded (≤) (s ∩ {b | a ≤ b}) ↔ bounded (≤) s :=
begin
  refine ⟨_, bounded.mono (set.inter_subset_left s _)⟩,
  rw ←@bounded_le_inter_lt _ s _ a,
  exact bounded.mono (λ x ⟨hx, hx'⟩, ⟨hx, le_of_lt hx'⟩)
end

theorem unbounded_le_inter_le [linear_order α] (a : α) :
  unbounded (≤) (s ∩ {b | a ≤ b}) ↔ unbounded (≤) s :=
begin
  rw [←not_bounded_iff, ←not_bounded_iff, not_iff_not],
  exact bounded_le_inter_le a
end

/-! #### Less than -/

theorem bounded_lt_inter_not_lt [semilattice_sup α] (a : α) :
  bounded (<) (s ∩ {b | ¬ b < a}) ↔ bounded (<) s :=
bounded_inter_not (λ x y, ⟨x ⊔ y, λ z h, h.elim lt_sup_of_lt_left lt_sup_of_lt_right⟩) a

theorem unbounded_lt_inter_not_lt [semilattice_sup α] (a : α) :
  unbounded (<) (s ∩ {b | ¬ b < a}) ↔ unbounded (<) s :=
begin
  rw [←not_bounded_iff, ←not_bounded_iff, not_iff_not],
  exact bounded_lt_inter_not_lt a
end

theorem bounded_lt_inter_le [linear_order α] (a : α) :
  bounded (<) (s ∩ {b | a ≤ b}) ↔ bounded (<) s :=
by { convert bounded_lt_inter_not_lt a, ext, exact not_lt.symm }

theorem unbounded_lt_inter_le [linear_order α] (a : α) :
  unbounded (<) (s ∩ {b | a ≤ b}) ↔ unbounded (<) s :=
by { convert unbounded_lt_inter_not_lt a, ext, exact not_lt.symm }

theorem bounded_lt_inter_lt [linear_order α] [no_max_order α] (a : α) :
  bounded (<) (s ∩ {b | a < b}) ↔ bounded (<) s :=
begin
  rw [←bounded_le_iff_bounded_lt, ←bounded_le_iff_bounded_lt],
  exact bounded_le_inter_lt a
end

theorem unbounded_lt_inter_lt [linear_order α] [no_max_order α] (a : α) :
  unbounded (<) (s ∩ {b | a < b}) ↔ unbounded (<) s :=
begin
  rw [←not_bounded_iff, ←not_bounded_iff, not_iff_not],
  exact bounded_lt_inter_lt a
end

/-! #### Greater or equal -/

theorem bounded_ge_inter_not_ge [semilattice_inf α] (a : α) :
  bounded (≥) (s ∩ {b | ¬ a ≤ b}) ↔ bounded (≥) s :=
@bounded_le_inter_not_le (order_dual α) s _ a

theorem unbounded_ge_inter_not_ge [semilattice_inf α] (a : α) :
  unbounded (≥) (s ∩ {b | ¬ a ≤ b}) ↔ unbounded (≥) s :=
@unbounded_le_inter_not_le (order_dual α) s _ a

theorem bounded_ge_inter_gt [linear_order α] (a : α) :
  bounded (≥) (s ∩ {b | b < a}) ↔ bounded (≥) s :=
@bounded_le_inter_lt (order_dual α) s _ a

theorem unbounded_ge_inter_gt [linear_order α] (a : α) :
  unbounded (≥) (s ∩ {b | b < a}) ↔ unbounded (≥) s :=
@unbounded_le_inter_lt (order_dual α) s _ a

theorem bounded_ge_inter_ge [linear_order α] (a : α) :
  bounded (≥) (s ∩ {b | b ≤ a}) ↔ bounded (≥) s :=
@bounded_le_inter_le (order_dual α) s _ a

theorem unbounded_ge_iff_unbounded_inter_ge [linear_order α] (a : α) :
  unbounded (≥) (s ∩ {b | b ≤ a}) ↔ unbounded (≥) s :=
@unbounded_le_inter_le (order_dual α) s _ a

/-! #### Greater than -/

theorem bounded_gt_inter_not_gt [semilattice_inf α] (a : α) :
  bounded (>) (s ∩ {b | ¬ a < b}) ↔ bounded (>) s :=
@bounded_lt_inter_not_lt (order_dual α) s _ a

theorem unbounded_gt_inter_not_gt [semilattice_inf α] (a : α) :
  unbounded (>) (s ∩ {b | ¬ a < b}) ↔ unbounded (>) s :=
@unbounded_lt_inter_not_lt (order_dual α) s _ a

theorem bounded_gt_inter_ge [linear_order α] (a : α) :
  bounded (>) (s ∩ {b | b ≤ a}) ↔ bounded (>) s :=
@bounded_lt_inter_le (order_dual α) s _ a

theorem unbounded_inter_ge [linear_order α] (a : α) :
  unbounded (>) (s ∩ {b | b ≤ a}) ↔ unbounded (>) s :=
@unbounded_lt_inter_le (order_dual α) s _ a

theorem bounded_gt_inter_gt [linear_order α] [no_min_order α] (a : α) :
  bounded (>) (s ∩ {b | b < a}) ↔ bounded (>) s :=
@bounded_lt_inter_lt (order_dual α) s _ _ a

theorem unbounded_gt_inter_gt [linear_order α] [no_min_order α] (a : α) :
  unbounded (>) (s ∩ {b | b < a}) ↔ unbounded (>) s :=
@unbounded_lt_inter_lt (order_dual α) s _ _ a

end set