/-
Copyright (c) 2021 Yury Kudryashov. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yury Kudryashov
-/
import number_theory.liouville.basic
import topology.metric_space.baire
import topology.instances.irrational

/-!
# Density of Liouville numbers

In this file we prove that the set of Liouville numbers form a dense `Gδ` set. We also prove a
similar statement about irrational numbers.
-/

open_locale filter
open filter set metric

lemma set_of_liouville_eq_Inter_Union :
  {x | liouville x} =
    ⋂ n : ℕ, ⋃ (a b : ℤ) (hb : 1 < b), ball (a / b) (1 / b ^ n) \ {a / b} :=
begin
  ext x,
  simp only [mem_Inter, mem_Union, liouville, mem_set_of_eq, exists_prop, mem_diff,
    mem_singleton_iff, mem_ball, real.dist_eq, and_comm]
end

lemma is_Gδ_set_of_liouville : is_Gδ {x | liouville x} :=
begin
  rw set_of_liouville_eq_Inter_Union,
  refine is_Gδ_Inter (λ n, is_open.is_Gδ _),
  refine is_open_Union (λ a, is_open_Union $ λ b, is_open_Union $ λ hb, _),
  exact is_open_ball.inter is_closed_singleton.is_open_compl
end

lemma set_of_liouville_eq_irrational_inter_Inter_Union :
  {x | liouville x} =
    {x | irrational x} ∩ ⋂ n : ℕ, ⋃ (a b : ℤ) (hb : 1 < b), ball (a / b) (1 / b ^ n) :=
begin
  refine subset.antisymm _ _,
  { refine subset_inter (λ x hx, hx.irrational) _,
    rw set_of_liouville_eq_Inter_Union,
    exact Inter_subset_Inter (λ n, Union_subset_Union $ λ a, Union_subset_Union $
      λ b, Union_subset_Union $ λ hb, diff_subset _ _) },
  { simp only [inter_Inter, inter_Union, set_of_liouville_eq_Inter_Union],
    refine Inter_subset_Inter (λ n, Union_subset_Union $ λ a, Union_subset_Union $
      λ b, Union_subset_Union $ λ hb, _),
    rw [inter_comm],
    refine diff_subset_diff subset.rfl (singleton_subset_iff.2 ⟨a / b, _⟩),
    norm_cast }
end

/-- The set of Liouville numbers is a residual set. -/
lemma eventually_residual_liouville : ∀ᶠ x in residual ℝ, liouville x :=
begin
  rw [filter.eventually, set_of_liouville_eq_irrational_inter_Inter_Union],
  refine eventually_residual_irrational.and _,
  refine eventually_residual.2 ⟨_, _, rat.dense_embedding_coe_real.dense.mono _, subset.rfl⟩,
  { exact is_Gδ_Inter (λ n, is_open.is_Gδ $ is_open_Union $ λ a, is_open_Union $
      λ b, is_open_Union $ λ hb, is_open_ball) },
  { rintro _ ⟨r, rfl⟩,
    simp only [mem_Inter, mem_Union],
    refine λ n, ⟨r.num * 2, r.denom * 2, _, _⟩,
    { have := int.coe_nat_le.2 r.pos, rw int.coe_nat_one at this, linarith },
    { convert mem_ball_self _ using 2,
      { norm_cast, field_simp },
      { refine one_div_pos.2 (pow_pos (int.cast_pos.2 _) _),
        exact mul_pos (int.coe_nat_pos.2 r.pos) zero_lt_two } } }
end

/-- The set of Liouville numbers in dense. -/
lemma dense_liouville : dense {x | liouville x} :=
dense_of_mem_residual eventually_residual_liouville


-- Setup for doing the topology problem sheet 2 exercise 4
import analysis.topology.continuity
import analysis.topology.topological_space
import analysis.topology.infinite_sum
import analysis.topology.topological_structures
import analysis.topology.uniform_space

universe u

open set filter lattice classical

-- A function that checks if a collection is sets satisfies the axioms of a topology
definition is_open_sets {α : Type u} (is_open : set α → Prop) :=
  is_open univ ∧ (∀s t, is_open s → is_open t → is_open (s ∩ t)) ∧ (∀s, (∀t∈s, is_open t) → is_open (⋃₀ s))

-- A function that converts a proof that a collection of sets satisfies the axioms of a topology into an actual topology (see the definition of a topology in mathlib)
definition is_to_top {α : Type u} (is_open : set α → Prop) (H : is_open_sets (is_open)) : topological_space α :=
{ is_open := is_open,
  is_open_univ := H.left,
  is_open_inter := H.right.left,
  is_open_sUnion := H.right.right
}

-- A function that converts a topology into a proof that the given collection of sets satisfies the axioms of a topology.
definition top_to_is {α : Type u} (T : topological_space α) : is_open_sets (T.is_open) :=
⟨T.is_open_univ,T.is_open_inter,T.is_open_sUnion⟩

-- A proof that given two collections of sets that satisfy the axioms of a topology, their intersection satisfies the axioms of a topology.
theorem exercisefoura {α : Type u} {T1_sets T2_sets : set α → Prop} : is_open_sets T1_sets → is_open_sets T2_sets → is_open_sets (λ (a : set α), and (T1_sets a) (T2_sets a) :=
begin
intros H1 H2,
unfold is_open_sets,
split,
exact ⟨H1.left, H2.left⟩,
split,
intros s t H3 H4,
exact ⟨H1.right.left s t H3.left H4.left, H2.right.left s t H3.right H4.right⟩,
intros I H3,
split,
have H4 : ∀ (t : set α), t ∈ I → T1_sets t,
intros A H5,
exact (H3 A H5).left,
exact H1.right.right I H4,
have H4 :  ∀ (t : set α), t ∈ I → T2_sets t,
intros A H5,
exact (H3 A H5).right,
exact H2.right.right I H4,
end


import Mathlib.Data.Real.Basic
variable (P Q R S : Prop)
variable (A B C D : Type)
variable (a b c d k m n x: ℕ)
open Function Set
/-
# Logic

In Lean a `Prop` is any true/false statement.

For example: `3 = 2`  or `x = 7 → x is prime` or
`∀ (n:ℕ), 2 ∣ n` or `∃ (k:ℕ), even k ∧ ¬ prime k` -/

--#check 3 = 2
--#check x = 7 → prime x
--#check ∀ (n:ℕ), 2 ∣ n
--#check ∃ (k:ℕ), even k ∧ ¬ prime k

/-
If we have `(P : Prop)` then `P` is a proposition, so it is
a statement that is either true or false.

So what is `(hp : P)`? It is simply a proof that P is true.
Or equivalently `hp` is the hypothesis that P is true.

# Tactics

We will use the three basic tactics from last time: `exact`, `intro`, `apply`

# exact
If our goal is `⊢ P` and we have a term `hp : P` then
we can close it with `exact hp`

# intro(s)
If our goal is `⊢ P → Q` then `intro hp` introduces a term `(hp : P)`
into our local context and our goal changes to `⊢ Q`.

# apply
If our goal is `⊢ Q` and we have a term `(hpq : P → Q)` then
`apply hpq` changes our goal to `⊢ P`

(We think of this as : we know `P implies Q`, so if our goal is `Q` it
suffices to prove `P` and then `Q` follows.)

# New tactic: refl

# refl
If our goal can be proved by `reflexivity` then `refl` will close it.
For example  `P = P` or `P ↔ P` -/

-- 01 `if and only if` `↔` is reflexive so refl works here
example : P ↔ P :=
by
  rfl

-- 02 Equality is also reflexive so again `refl` will work
example : P = P :=
by
  rfl

/- Unless told otherwise, Lean assumes numerals refer to natural
numbers i.e. ℕ = {0, 1, 2,...} also known as `nats`.

If we do operations such as ` + - / * ^ % ` with nats then the result is
also a nat. -/

-- 03
example : 1 + 1 = 2 :=
by
  rfl

-- 04
example : 12 * 25 = 300 :=
by
  rfl

-- 05
example : 7 - 3 = 2 ^ 2 :=
by
  rfl

-- 06
example : 2 - 3 = 0 := -- yes 2 - 3 = 0 in Lean
by
  rfl

-- 07  `a % b` is the remainder of `a` after division by `b`.
example : 18 % 7 = 4:=
by
  rfl

-- 08  Remember everything here is a natural number
example (n : ℕ): (12 / 5)^4  = 4^(5 / 2) * n^ (3 - 4) :=
by
  rfl

-- 09 `refl` can also see through definitions
example  (f : A → B) : Injective f ↔ ∀ (x y : A), f x = f y → x = y :=
by
  rfl

-- 10
example (f : A → B) : Surjective f ↔ ∀ (b:B), ∃ (a:A), f a = b:=
by
  rfl

/- We give the next example a name because it captures one of the fundmental
   properties of `Prop` in Lean:
           `Any two proofs of the same Prop are equal` -/
-- 11
lemma proof_irrelevance (P : Prop) (h1 : P) (h2 : P) : h1 = h2:=
by
  rfl

/- The same isn't true for most Types.
   [Qu: what can you say about types for which this is true?] -/
-- 12
example (A : Type) (a1 : A) (a2 : A) : a1 = a2:=
by
  sorry -- don't try to prove this it isn't true without further assumptions about A!

-- 13 Any proposition implies itself
example  : P → P :=
by
  intro hp -- introduce the hypothesis `hp : P` in the local context
            -- our goal is now `⊢ P` so we can accomplish this with
  exact hp

-- 14 If P is true and P → Q is true then Q is true
example (hp : P) (hpq : P → Q) : Q :=
by
  exact hpq hp

/-  # There is no special `implies` symbol in Lean

As we have just seen we don't have a different symbol for `implies` we just reuse `→`
that was introduced earlier for functions.

So if `(A B : Type)` and `(P Q : Prop)` what is the difference between
 `f : A → B` and `hpq : P → Q` ?

`f` is a function mapping terms of type `A` to terms of type `B`, while `hpq` is a
function mapping terms of type `P` to terms of type `Q`.

The crucial difference is that `A` and `B` have type `Type` while `P` and `Q` have
type `Prop`

Interpreting a term of type `P` as a proof of `P` then we can think of `hpq : P → Q` as
a function mapping proofs of P to proofs of Q. -/

-- 15
example : P → Q → P :=
by
  intro hp _
  exact hp

-- 16
example : P → (P → Q) → (Q → R) → R :=
by
  intro p pq qr
  apply qr
  apply pq
  exact p

-- 17
example  : (P → Q → R) → (Q → R → P) → (R → Q → P) :=
by
  intro _ qrp r q
  apply qrp
  exact q
  exact r

/-
# New tactic: rw

# rw
rw is short for rewrite, it allows us to rewrite using equations and equivalences.

For example if we have `h : a = b` and our goal is `⊢ a + c = b + c` then `rw h` will replace
`a` by `b` in the goal so it becomes `⊢ b + c = b + c`  (which we can then close with `refl`)

We can tell `rw` to do the rewrite on a term in our local context.
For example if we have `h1 : a = b` and `h2 : a = c` we can use `rw h1 at h2` to convert h2
into `h2 : b = c`

rw works from left to right, so given `h : a = b`, `rw h` will replace each `a` in the goal by `b`.
If we want to instead replace each `b` by `a` we can use `rw ← h` to rewrite from right to left.  -/

-- 18 We can do this be rewriting
example (h1 : a = b) (h2 : b = c) (h3: c = d) : a = d:=
by
  rwa [h1,h2]

-- 19 We start with a rewrite in the local context
example (h1 : a = b) (h2 : b = c) (h3: c = d) : a = d :=
by
  rw [h1]
  rwa [h3] at h2

-- 20 We can start by rewriting from right to left using `hqr : Q ↔ R`
example  (hpq : P ↔ Q) (hqr : Q ↔ R) : P ↔ R:=
by
  rwa [hpq]

-- 21
example  (f g : A → B) (hfg : Injective f ↔ Injective g)
(hg : ∀ (x y: A), g x = g y → x = y)  : Injective f:=
by
   rw [hfg]
   intro a b h
   apply hg
   assumption

-- 22 -- symmetry may be useful here..
example (f: A → ℕ) (x y : A) (hf : f x = f y → x = y)
(hfx: f x = n) (hfy: f y = m) (heq: m = n) : x = y:=
by
  apply hf
  rw [hfx,hfy]
  exact heq.symm


import data.set.basic -- hide
import data.set.finite -- hide
open set -- hide
/-
We will use the following lemma when we start proving facts about topological spaces.

It seems clear that we want to use induction, so we can try to apply the `finite.induction_on`
lemma. But be careful on how you apply it, or you will be left with an impossible goal.

For the inductive step, the lemmas `sInter_insert`, `mem_insert_iff` and `forall_eq_or_imp`
may be useful, as well as the `simp` tactic.
-/

/- Hint : Click here for a hint, in case you get stuck.
The `finite.induction_on` lemma allows to prove something of the form `P S` for all finite sets `S`.
So apply `finite.induction_on hfin` looks like it's making progress. However, the induction
hypothesis you will be left with is too weak. Try instead starting with `revert hS`, and
see why this helps.
-/

variables {X Y : Type} -- hide

/- Lemma : no-side-bar
If P is a property of sets which is closed under pairwise intersection then it is also closed under
arbitrary finite interesctions.
-/
lemma sInter_of_inter (P : set X → Prop) (huniv : P univ) (hinter : ∀ A B : set X, P A → P B → P (A ∩ B))
(S : set (set X)) (hfin : finite S) (hS : ∀ s ∈ S, P s) : P ( sInter S ) :=
begin
  revert hS,
  apply finite.induction_on hfin,
  {
    simp,
    exact huniv,
  },
  {
    intros U S hUS hSfin hind h,
    have h : ⋂₀ insert U S = (⋂₀ S) ∩ U, by finish,
    rw h,
    apply hinter;
    finish,
  }
end


/-
Copyright (c) 2020 Hanting Zhang. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Hanting Zhang, Johan Commelin
-/
import data.mv_polynomial.rename
import data.mv_polynomial.comm_ring
import algebra.algebra.subalgebra.basic

/-!
# Symmetric Polynomials and Elementary Symmetric Polynomials

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

This file defines symmetric `mv_polynomial`s and elementary symmetric `mv_polynomial`s.
We also prove some basic facts about them.

## Main declarations

* `mv_polynomial.is_symmetric`

* `mv_polynomial.symmetric_subalgebra`

* `mv_polynomial.esymm`

## Notation

+ `esymm σ R n`, is the `n`th elementary symmetric polynomial in `mv_polynomial σ R`.

As in other polynomial files, we typically use the notation:

+ `σ τ : Type*` (indexing the variables)

+ `R S : Type*` `[comm_semiring R]` `[comm_semiring S]` (the coefficients)

+ `r : R` elements of the coefficient ring

+ `i : σ`, with corresponding monomial `X i`, often denoted `X_i` by mathematicians

+ `φ ψ : mv_polynomial σ R`

-/

open equiv (perm)
open_locale big_operators
noncomputable theory

namespace multiset

variables {R : Type*} [comm_semiring R]

/-- The `n`th elementary symmetric function evaluated at the elements of `s` -/
def esymm (s : multiset R) (n : ℕ) : R := ((s.powerset_len n).map multiset.prod).sum

lemma _root_.finset.esymm_map_val {σ} (f : σ → R) (s : finset σ) (n : ℕ) :
  (s.val.map f).esymm n = (s.powerset_len n).sum (λ t, t.prod f) :=
by simpa only [esymm, powerset_len_map, ← finset.map_val_val_powerset_len, map_map]

end multiset

namespace mv_polynomial

variables {σ : Type*} {R : Type*}
variables {τ : Type*} {S : Type*}

/-- A `mv_polynomial φ` is symmetric if it is invariant under
permutations of its variables by the  `rename` operation -/
def is_symmetric [comm_semiring R] (φ : mv_polynomial σ R) : Prop :=
∀ e : perm σ, rename e φ = φ

variables (σ R)

/-- The subalgebra of symmetric `mv_polynomial`s. -/
def symmetric_subalgebra [comm_semiring R] : subalgebra R (mv_polynomial σ R) :=
{ carrier := set_of is_symmetric,
  algebra_map_mem' := λ r e, rename_C e r,
  mul_mem' := λ a b ha hb e, by rw [alg_hom.map_mul, ha, hb],
  add_mem' := λ a b ha hb e, by rw [alg_hom.map_add, ha, hb] }

variables {σ R}

@[simp] lemma mem_symmetric_subalgebra [comm_semiring R] (p : mv_polynomial σ R) :
  p ∈ symmetric_subalgebra σ R ↔ p.is_symmetric := iff.rfl

namespace is_symmetric

section comm_semiring
variables [comm_semiring R] [comm_semiring S] {φ ψ : mv_polynomial σ R}

@[simp]
lemma C (r : R) : is_symmetric (C r : mv_polynomial σ R) :=
(symmetric_subalgebra σ R).algebra_map_mem r

@[simp]
lemma zero : is_symmetric (0 : mv_polynomial σ R) :=
(symmetric_subalgebra σ R).zero_mem

@[simp]
lemma one : is_symmetric (1 : mv_polynomial σ R) :=
(symmetric_subalgebra σ R).one_mem

lemma add (hφ : is_symmetric φ) (hψ : is_symmetric ψ) : is_symmetric (φ + ψ) :=
(symmetric_subalgebra σ R).add_mem hφ hψ

lemma mul (hφ : is_symmetric φ) (hψ : is_symmetric ψ) : is_symmetric (φ * ψ) :=
(symmetric_subalgebra σ R).mul_mem hφ hψ

@[simp]
lemma map (hφ : is_symmetric φ) (f : R →+* S) : is_symmetric (map f φ) :=
λ e, by rw [← map_rename, hφ]

end comm_semiring

section comm_ring
variables [comm_ring R] {φ ψ : mv_polynomial σ R}

lemma neg (hφ : is_symmetric φ) : is_symmetric (-φ) :=
(symmetric_subalgebra σ R).neg_mem hφ

lemma sub (hφ : is_symmetric φ) (hψ : is_symmetric ψ) : is_symmetric (φ - ψ) :=
(symmetric_subalgebra σ R).sub_mem hφ hψ

end comm_ring

end is_symmetric

section elementary_symmetric
open finset
variables (σ R) [comm_semiring R] [comm_semiring S] [fintype σ] [fintype τ]

/-- The `n`th elementary symmetric `mv_polynomial σ R`. -/
def esymm (n : ℕ) : mv_polynomial σ R :=
∑ t in powerset_len n univ, ∏ i in t, X i

/-- The `n`th elementary symmetric `mv_polynomial σ R` is obtained by evaluating the
`n`th elementary symmetric at the `multiset` of the monomials -/
lemma esymm_eq_multiset_esymm : esymm σ R = (finset.univ.val.map X).esymm :=
funext $ λ n, (finset.univ.esymm_map_val X n).symm

lemma aeval_esymm_eq_multiset_esymm [algebra R S] (f : σ → S) (n : ℕ) :
  aeval f (esymm σ R n) = (finset.univ.val.map f).esymm n :=
by simp_rw [esymm, aeval_sum, aeval_prod, aeval_X, esymm_map_val]

/-- We can define `esymm σ R n` by summing over a subtype instead of over `powerset_len`. -/
lemma esymm_eq_sum_subtype (n : ℕ) : esymm σ R n =
  ∑ t : {s : finset σ // s.card = n}, ∏ i in (t : finset σ), X i :=
sum_subtype _ (λ _, mem_powerset_len_univ_iff) _

/-- We can define `esymm σ R n` as a sum over explicit monomials -/
lemma esymm_eq_sum_monomial (n : ℕ) : esymm σ R n =
  ∑ t in powerset_len n univ, monomial (∑ i in t, finsupp.single i 1) 1 :=
begin
  simp_rw monomial_sum_one,
  refl,
end

@[simp] lemma esymm_zero : esymm σ R 0 = 1 :=
by simp only [esymm, powerset_len_zero, sum_singleton, prod_empty]

lemma map_esymm (n : ℕ) (f : R →+* S) : map f (esymm σ R n) = esymm σ S n :=
by simp_rw [esymm, map_sum, map_prod, map_X]

lemma rename_esymm (n : ℕ) (e : σ ≃ τ) : rename e (esymm σ R n) = esymm τ R n :=
calc rename e (esymm σ R n)
     = ∑ x in powerset_len n univ, ∏ i in x, X (e i)
       : by simp_rw [esymm, map_sum, map_prod, rename_X]
 ... = ∑ t in powerset_len n (univ.map e.to_embedding), ∏ i in t, X i
       : by simp [finset.powerset_len_map, -finset.map_univ_equiv]
 ... = ∑ t in powerset_len n univ, ∏ i in t, X i : by rw finset.map_univ_equiv

lemma esymm_is_symmetric (n : ℕ) : is_symmetric (esymm σ R n) :=
by { intro, rw rename_esymm }

lemma support_esymm'' (n : ℕ) [decidable_eq σ] [nontrivial R] :
  (esymm σ R n).support = (powerset_len n (univ : finset σ)).bUnion
    (λ t, (finsupp.single (∑ (i : σ) in t, finsupp.single i 1) (1:R)).support) :=
begin
  rw esymm_eq_sum_monomial,
  simp only [← single_eq_monomial],
  convert finsupp.support_sum_eq_bUnion (powerset_len n (univ : finset σ)) _,
  intros s t hst,
  rw finset.disjoint_left,
  simp only [finsupp.support_single_ne_zero _ one_ne_zero, mem_singleton],
  rintro a h rfl,
  have := congr_arg finsupp.support h,
  rw [finsupp.support_sum_eq_bUnion, finsupp.support_sum_eq_bUnion] at this,
  { simp only [finsupp.support_single_ne_zero _ one_ne_zero, bUnion_singleton_eq_self] at this,
    exact absurd this hst.symm },
  all_goals { intros x y, simp [finsupp.support_single_disjoint] }
end

lemma support_esymm' (n : ℕ) [decidable_eq σ] [nontrivial R] :
  (esymm σ R n).support =
  (powerset_len n (univ : finset σ)).bUnion (λ t, {∑ (i : σ) in t, finsupp.single i 1}) :=
begin
  rw support_esymm'',
  congr,
  funext,
  exact finsupp.support_single_ne_zero _ one_ne_zero
end

lemma support_esymm (n : ℕ) [decidable_eq σ] [nontrivial R] :
  (esymm σ R n).support =
  (powerset_len n (univ : finset σ)).image (λ t, ∑ (i : σ) in t, finsupp.single i 1) :=
by { rw support_esymm', exact bUnion_singleton }

lemma degrees_esymm [nontrivial R]
  (n : ℕ) (hpos : 0 < n) (hn : n ≤ fintype.card σ) :
  (esymm σ R n).degrees = (univ : finset σ).val :=
begin
  classical,
  have : (finsupp.to_multiset ∘ λ (t : finset σ), ∑ (i : σ) in t, finsupp.single i 1) = finset.val,
  { funext, simp [finsupp.to_multiset_sum_single] },
  rw [degrees, support_esymm, sup_finset_image, this, ←comp_sup_eq_sup_comp],
  { obtain ⟨k, rfl⟩ := nat.exists_eq_succ_of_ne_zero hpos.ne',
    simpa using powerset_len_sup _ _ (nat.lt_of_succ_le hn) },
  { intros,
    simp only [union_val, sup_eq_union],
    congr },
  { refl }
end

end elementary_symmetric

end mv_polynomial


inductive bool_var : Type
| mk (n : nat)

open bool_var

/-
Abstract syntax
-/
inductive bool_expr : Type
| lit_expr : bool → bool_expr
| var_expr : bool_var → bool_expr
| and_expr : bool_expr → bool_expr → bool_expr
| or_expr : bool_expr → bool_expr → bool_expr
| impl_expr : bool_expr → bool_expr → bool_expr   -- New
| not_expr : bool_expr → bool_expr

open bool_expr

/-
Concrete syntax
-/
notation e1 ∧ e2 := and_expr e1 e2
notation e1 ∨ e2 := or_expr e1 e2
notation ¬e := not_expr e
notation `[` b `]` := lit_expr b
notation `[` v `]` := var_expr v
reserve infixr `=>`:67                          -- New
notation e1 => e2 := impl_expr e1 e2            -- New

/-
Semantics
-/

-- Boolean implies function, not provided by Lean
def bimp : bool → bool → bool
| tt ff := ff
| _ _ := tt

def bool_eval : bool_expr → (bool_var → bool) → bool
| (lit_expr b) _ := b
| (var_expr v) st := st v
| (and_expr e1 e2) st := band (bool_eval e1 st) (bool_eval e2 st)
| (or_expr e1 e2) st := bor (bool_eval e1 st) (bool_eval e2 st)
| (impl_expr e1 e2) st := bimp (bool_eval e1 st) (bool_eval e2 st) -- New
| (not_expr e) st := bnot (bool_eval e st)

/-
Testing -- traditional, run program, compare to expected output
-/

def true_expr : bool_expr := [tt]
def false_expr : bool_expr := [ff]
def e1 := [tt] ∧ [ff]
def e2 := e1 ∨  [tt]

-- Let's name some variables

def Pvar := (bool_var.mk 0)
def Qvar := (bool_var.mk 1)
def Rvar := (bool_var.mk 2)

-- One possible state: giving values to variables
def bool_state_4 : bool_var → bool :=
λ v,
match v with
  | (bool_var.mk 0) := tt
  | (bool_var.mk 1) := ff
  | (bool_var.mk 2) := ff
  | _ := ff
  end

/-
Informal testing: run code, compare actual to expected output
-/
#eval bool_eval e2 bool_state_4  -- expect tt

/-
Testing as proving theorems about specific inputs (not ∀ propositions)
-/
example : bool_eval e2 bool_state_4 = tt := rfl
example : bool_eval (¬e2 ∨ e1) bool_state_4 = ff := rfl

-- An expression with variable expressions as subexpressions
def e5' := [Pvar] ∧ [Qvar]

/-
Properties
-/

-- Oops, this proof broke when we added state as an argument

theorem and_connective_commutes : ∀ (e1 e2 : bool_expr),
  bool_eval (e1 ∧ e2) = bool_eval (e2 ∧ e1)
  :=
begin
  assume e1 e2,
  simp [bool_eval],
  cases (bool_eval e1),
  cases (bool_eval e2),
  apply rfl,
  apply rfl,
  cases (bool_eval e2),
  repeat {apply rfl},
end


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

import algebra.ring.basic
import algebra.algebra.basic
import algebra.group_power.basic
import algebra.field_power
import algebra.opposites

/-!  # Squares, even and odd elements

This file proves some general facts about squares, even and odd elements of semirings.

In the implementation, we define `is_square` and we let `even` be the notion transported by
`to_additive`.  The definition are therefore as follows:
```lean
is_square a ↔ ∃ r, a = r * r
even a ↔ ∃ r, a = r + r
```

Odd elements are not unified with a multiplicative notion.

## Future work

* TODO: Try to generalize further the typeclass assumptions on `is_square/even`.
  For instance, in some cases, there are `semiring` assumptions that I (DT) am not convinced are
  necessary.
* TODO: Consider moving the definition and lemmas about `odd` to a separate file.
* TODO: The "old" definition of `even a` asked for the existence of an element `c` such that
  `a = 2 * c`.  For this reason, several fixes introduce an extra `two_mul` or `← two_mul`.
  It might be the case that by making a careful choice of `simp` lemma, this can be avoided.
 -/

open mul_opposite
variables {α β : Type*}

/--  An element `a` of a type `α` with multiplication satisfies `square a` if `a = r * r`,
for some `r : α`. -/
@[to_additive
"An element `a` of a type `α` with addition satisfies `even a` if `a = r + r`,
for some `r : α`."]
def is_square [has_mul α] (a : α) : Prop := ∃ r, a = r * r

@[simp, to_additive]
lemma is_square_mul_self [has_mul α] (m : α) : is_square (m * m) := ⟨m, rfl⟩

@[to_additive even_iff_exists_two_nsmul]
lemma is_square_iff_exists_sq [monoid α] (m : α) : is_square m ↔ ∃ c, m = c ^ 2 :=
by simp [is_square, pow_two]

alias is_square_iff_exists_sq ↔ is_square.exists_sq is_square_of_exists_sq

attribute [to_additive even.exists_two_nsmul] is_square.exists_sq
/-- Alias of the forwards direction of `even_iff_exists_two_nsmul`. -/
add_decl_doc even.exists_two_nsmul

attribute [to_additive even_of_exists_two_nsmul] is_square_of_exists_sq
/-- Alias of the backwards direction of `even_iff_exists_two_nsmul`. -/
add_decl_doc even_of_exists_two_nsmul

@[simp, to_additive even_two_nsmul]
lemma is_square_sq [monoid α] (a : α) : is_square (a ^ 2) := ⟨a, pow_two _⟩

@[simp, to_additive]
lemma is_square_one [mul_one_class α] : is_square (1 : α) := ⟨1, (mul_one _).symm⟩

@[to_additive]
lemma is_square.map {F : Type*} [mul_one_class α] [mul_one_class β] [monoid_hom_class F α β]
  {m : α} (f : F) (hm : is_square m) :
  is_square (f m) :=
begin
  rcases hm with ⟨m, rfl⟩,
  exact ⟨f m, by simp⟩
end

@[to_additive]
lemma is_square.mul_is_square [comm_monoid α] {m n : α} (hm : is_square m) (hn : is_square n) :
  is_square (m * n) :=
begin
  rcases hm with ⟨m, rfl⟩,
  rcases hn with ⟨n, rfl⟩,
  refine ⟨m * n, mul_mul_mul_comm m m n n⟩,
end

section group
variable [group α]

@[to_additive]
lemma is_square_op_iff (a : α) : is_square (op a) ↔ is_square a :=
⟨λ ⟨c, hc⟩, ⟨unop c, by rw [← unop_mul, ← hc, unop_op]⟩, λ ⟨c, hc⟩, by simp [hc]⟩

@[simp, to_additive] lemma is_square_inv (a : α) : is_square a⁻¹ ↔ is_square a :=
begin
  refine ⟨λ h, _, λ h, _⟩,
  { rw [← is_square_op_iff, ← inv_inv a],
    exact h.map (mul_equiv.inv' α) },
  { exact ((is_square_op_iff a).mpr h).map (mul_equiv.inv' α).symm }
end

end group

section comm_group
variable [comm_group α]

@[to_additive]
lemma is_square.div_is_square {m n : α} (hm : is_square m) (hn : is_square n) : is_square (m / n) :=
by { rw div_eq_mul_inv,  exact hm.mul_is_square ((is_square_inv n).mpr hn) }

end comm_group

section semiring
variables [semiring α] [semiring β] {m n : α}

lemma even_iff_exists_two_mul (m : α) : even m ↔ ∃ c, m = 2 * c :=
by simp [even_iff_exists_two_nsmul]

lemma even_iff_two_dvd {a : α} : even a ↔ 2 ∣ a := by simp [even, has_dvd.dvd, two_mul]

@[simp] lemma range_two_mul (α : Type*) [semiring α] :
  set.range (λ x : α, 2 * x) = {a | even a} :=
by { ext x, simp [eq_comm, two_mul, even] }

@[simp] lemma even_bit0 (a : α) : even (bit0 a) :=
⟨a, rfl⟩

@[simp] lemma even_two : even (2 : α) := ⟨1, rfl⟩

@[simp] lemma even.mul_left (hm : even m) (n) : even (n * m) :=
hm.map (add_monoid_hom.mul_left n)

@[simp] lemma even.mul_right (hm : even m) (n) : even (m * n) :=
hm.map (add_monoid_hom.mul_right n)

lemma even_two_mul (m : α) : even (2 * m) := ⟨m, two_mul _⟩

lemma even.pow_of_ne_zero (hm : even m) : ∀ {a : ℕ}, a ≠ 0 → even (m ^ a)
| 0       a0 := (a0 rfl).elim
| (a + 1) _  := by { rw pow_succ, exact hm.mul_right _ }

section with_odd

/-- An element `a` of a semiring is odd if there exists `k` such `a = 2*k + 1`. -/
def odd (a : α) : Prop := ∃ k, a = 2*k + 1

@[simp] lemma odd_bit1 (a : α) : odd (bit1 a) :=
⟨a, by rw [bit1, bit0, two_mul]⟩

@[simp] lemma range_two_mul_add_one (α : Type*) [semiring α] :
  set.range (λ x : α, 2 * x + 1) = {a | odd a} :=
by { ext x, simp [odd, eq_comm] }

lemma even.add_odd (hm : even m) (hn : odd n) : odd (m + n) :=
begin
  rcases hm with ⟨m, rfl⟩,
  rcases hn with ⟨n, rfl⟩,
  exact ⟨m + n, by rw [mul_add, ← two_mul, add_assoc]⟩
end

lemma odd.add_even (hm : odd m) (hn : even n) : odd (m + n) :=
by { rw add_comm, exact hn.add_odd hm }

lemma odd.add_odd (hm : odd m) (hn : odd n) : even (m + n) :=
begin
  rcases hm with ⟨m, rfl⟩,
  rcases hn with ⟨n, rfl⟩,
  refine ⟨n + m + 1, _⟩,
  rw [← two_mul, ←add_assoc, add_comm _ (2 * n), ←add_assoc, ←mul_add, add_assoc, mul_add _ (n + m),
    mul_one],
  refl
end

@[simp] lemma odd_one : odd (1 : α) :=
⟨0, (zero_add _).symm.trans (congr_arg (+ (1 : α)) (mul_zero _).symm)⟩

@[simp] lemma odd_two_mul_add_one (m : α) : odd (2 * m + 1) := ⟨m, rfl⟩

lemma ring_hom.odd (f : α →+* β) (hm : odd m) : odd (f m) :=
begin
  rcases hm with ⟨m, rfl⟩,
  exact ⟨f m, by simp [two_mul]⟩
end

@[simp] lemma odd.mul_odd (hm : odd m) (hn : odd n) : odd (m * n) :=
begin
  rcases hm with ⟨m, rfl⟩,
  rcases hn with ⟨n, rfl⟩,
  refine ⟨2 * m * n + n + m, _⟩,
  rw [mul_add, add_mul, mul_one, ← add_assoc, one_mul, mul_assoc, ← mul_add, ← mul_add, ← mul_assoc,
    ← nat.cast_two, ← nat.cast_comm],
end

lemma odd.pow (hm : odd m) : ∀ {a : ℕ}, odd (m ^ a)
| 0       := by { rw pow_zero, exact odd_one }
| (a + 1) := by { rw pow_succ, exact hm.mul_odd odd.pow }

end with_odd

end semiring

section ring
variables [ring α] {m n : α}

@[simp] lemma even_neg_two : even (- 2 : α) := by simp only [even_neg, even_two]

lemma even_abs [linear_order α] {a : α} : even (|a|) ↔ even a :=
begin
  rcases abs_choice a with h | h; rw h,
  exact even_neg a,
end

lemma odd.neg {a : α} (hp : odd a) : odd (-a) :=
begin
  obtain ⟨k, hk⟩ := hp,
  use -(k + 1),
  rw [mul_neg, mul_add, neg_add, add_assoc, two_mul (1 : α), neg_add,
    neg_add_cancel_right, ←neg_add, hk],
end

@[simp] lemma odd_neg (a : α) : odd (-a) ↔ odd a :=
⟨λ h, neg_neg a ▸ h.neg, odd.neg⟩

@[simp] lemma odd_neg_one : odd (- 1 : α) := by simp

theorem odd.sub_even (hm : odd m) (hn : even n) : odd (m - n) :=
by { rw sub_eq_add_neg, exact hm.add_even ((even_neg n).mpr hn) }

theorem even.sub_odd (hm : even m) (hn : odd n) : odd (m - n) :=
by { rw sub_eq_add_neg, exact hm.add_odd ((odd_neg n).mpr hn) }

lemma odd.sub_odd (hm : odd m) (hn : odd n) : even (m - n) :=
by { rw sub_eq_add_neg, exact hm.add_odd ((odd_neg n).mpr hn) }

lemma odd_abs [linear_order α] {a : α} : odd (abs a) ↔ odd a :=
by { cases abs_choice a with h h; simp only [h, odd_neg] }

end ring

section powers
variables {R : Type*}
  {a : R} {n : ℕ} [linear_ordered_ring R]

lemma even.pow_nonneg (hn : even n) (a : R) : 0 ≤ a ^ n :=
by cases hn with k hk; simpa only [hk, two_mul] using pow_bit0_nonneg a k

lemma even.pow_pos (hn : even n) (ha : a ≠ 0) : 0 < a ^ n :=
by cases hn with k hk; simpa only [hk, two_mul] using pow_bit0_pos ha k

lemma odd.pow_nonpos (hn : odd n) (ha : a ≤ 0) : a ^ n ≤ 0:=
by cases hn with k hk; simpa only [hk, two_mul] using pow_bit1_nonpos_iff.mpr ha

lemma odd.pow_neg (hn : odd n) (ha : a < 0) : a ^ n < 0:=
by cases hn with k hk; simpa only [hk, two_mul] using pow_bit1_neg_iff.mpr ha

lemma odd.pow_nonneg_iff (hn : odd n) : 0 ≤ a ^ n ↔ 0 ≤ a :=
⟨λ h, le_of_not_lt (λ ha, h.not_lt $ hn.pow_neg ha), λ ha, pow_nonneg ha n⟩

lemma odd.pow_nonpos_iff (hn : odd n) : a ^ n ≤ 0 ↔ a ≤ 0 :=
⟨λ h, le_of_not_lt (λ ha, h.not_lt $ pow_pos ha _), hn.pow_nonpos⟩

lemma odd.pow_pos_iff (hn : odd n) : 0 < a ^ n ↔ 0 < a :=
⟨λ h, lt_of_not_ge' (λ ha, h.not_le $ hn.pow_nonpos ha), λ ha, pow_pos ha n⟩

lemma odd.pow_neg_iff (hn : odd n) : a ^ n < 0 ↔ a < 0 :=
⟨λ h, lt_of_not_ge' (λ ha, h.not_le $ pow_nonneg ha _), hn.pow_neg⟩

lemma even.pow_pos_iff (hn : even n) (h₀ : 0 < n) : 0 < a ^ n ↔ a ≠ 0 :=
⟨λ h ha, by { rw [ha, zero_pow h₀] at h, exact lt_irrefl 0 h }, hn.pow_pos⟩

lemma even.pow_abs {p : ℕ} (hp : even p) (a : R) : |a| ^ p = a ^ p :=
begin
  rw [←abs_pow, abs_eq_self],
  exact hp.pow_nonneg _
end

@[simp] lemma pow_bit0_abs (a : R) (p : ℕ) : |a| ^ bit0 p = a ^ bit0 p := (even_bit0 _).pow_abs _

lemma odd.strict_mono_pow (hn : odd n) : strict_mono (λ a : R, a ^ n) :=
by cases hn with k hk; simpa only [hk, two_mul] using strict_mono_pow_bit1 _

end powers

/-- The cardinality of `fin (bit0 k)` is even, `fact` version.
This `fact` is needed as an instance by `matrix.special_linear_group.has_neg`. -/
lemma fintype.card_fin_even {k : ℕ} : fact (even (fintype.card (fin (bit0 k)))) :=
⟨by { rw [fintype.card_fin], exact even_bit0 k }⟩

section field_power
variable {K : Type*}

lemma even.zpow_neg [division_ring K] {n : ℤ} (h : even n) (a : K) :
  (-a) ^ n = a ^ n :=
begin
  obtain ⟨k, rfl⟩ := h,
  rw [← two_mul, ←bit0_eq_two_mul, zpow_bit0_neg],
end

variables [linear_ordered_field K] {n : ℤ} {a : K}

lemma even.zpow_nonneg (hn : even n) (a : K) :
  0 ≤ a ^ n :=
begin
  cases le_or_lt 0 a with h h,
  { exact zpow_nonneg h _ },
  { exact (hn.zpow_neg a).subst (zpow_nonneg (neg_nonneg_of_nonpos h.le) _) }
end

theorem even.zpow_pos (hn : even n) (ha : a ≠ 0) : 0 < a ^ n :=
by cases hn with k hk; simpa only [hk, two_mul] using zpow_bit0_pos ha k

theorem odd.zpow_nonneg (hn : odd n) (ha : 0 ≤ a) : 0 ≤ a ^ n :=
by cases hn with k hk; simpa only [hk, two_mul] using zpow_bit1_nonneg_iff.mpr ha

theorem odd.zpow_pos (hn : odd n) (ha : 0 < a) : 0 < a ^ n :=
by cases hn with k hk; simpa only [hk, two_mul] using zpow_bit1_pos_iff.mpr ha

theorem odd.zpow_nonpos (hn : odd n) (ha : a ≤ 0) : a ^ n ≤ 0:=
by cases hn with k hk; simpa only [hk, two_mul] using zpow_bit1_nonpos_iff.mpr ha

theorem odd.zpow_neg (hn : odd n) (ha : a < 0) : a ^ n < 0:=
by cases hn with k hk; simpa only [hk, two_mul] using zpow_bit1_neg_iff.mpr ha

lemma even.zpow_abs {p : ℤ} (hp : even p) (a : K) : |a| ^ p = a ^ p :=
begin
  cases abs_choice a with h h;
  simp only [h, hp.zpow_neg _],
end

@[simp] lemma zpow_bit0_abs (a : K) (p : ℤ) : |a| ^ bit0 p = a ^ bit0 p :=
(even_bit0 _).zpow_abs _

lemma even.abs_zpow {p : ℤ} (hp : even p) (a : K) : |a ^ p| = a ^ p :=
begin
  rw [abs_eq_self],
  exact hp.zpow_nonneg _
end

@[simp] lemma abs_zpow_bit0 (a : K) (p : ℤ) :
  |a ^ bit0 p| = a ^ bit0 p :=
(even_bit0 _).abs_zpow _

end field_power


-- Pruebas de intersección sobre unión general
-- ===========================================

import data.set

open set

variables {I U : Type}
variables {A : I → set U}
variable  {C : set U}

-- ----------------------------------------------------
-- Ej. 1. Demostrar
--    C ∩ (⋃i, A i) ⊆ (⋃ i, C ∩ A i)
-- ----------------------------------------------------

-- 1ª demostración
example :
  C ∩ (⋃i, A i) ⊆ (⋃ i, C ∩ A i)  :=
begin
  rintros x ⟨hC, hU⟩,
  rw mem_Union at hU,
  cases hU with i hA,
  apply mem_Union.mpr,
  use i,
  split,
  assumption',
end

-- 2ª demostración
example :
  C ∩ (⋃i, A i) ⊆ (⋃ i, C ∩ A i)  :=
begin
  intros x h,
  simp * at *,
end

-- 3ª demostración
lemma inter_Uni_l1 :
  C ∩ (⋃i, A i) ⊆ (⋃ i, C ∩ A i)  :=
by {intros x h, simp * at *}

-- ----------------------------------------------------
-- Ej. 2. Demostrar
--    (⋃ i, C ∩ A i) ⊆ C ∩ (⋃i, A i)
-- ----------------------------------------------------

-- 1ª demostración
example :
  (⋃ i, C ∩ A i) ⊆ C ∩ (⋃i, A i) :=
begin
  intros x h,
  rw mem_Union at h,
  cases h with i hi,
  cases hi with hC hA,
  split,
  { exact hC, },
  { apply mem_Union.mpr,
    use i,
    exact hA, },
end

-- 2ª demostración
example : (⋃ i, C ∩ A i) ⊆ C ∩ (⋃i, A i) :=
begin
  intros x h,
  rw mem_Union at h,
  rcases h with ⟨i, hC, hA⟩,
  split,
  { exact hC, },
  { apply mem_Union.mpr,
    use i,
    exact hA, },
end

-- 3ª demostración
example :
  (⋃ i, C ∩ A i) ⊆ C ∩ (⋃i, A i) :=
begin
  intros x h,
  simp * at *,
end

-- 4ª demostración
lemma inter_Uni_l2 :
  (⋃ i, C ∩ A i) ⊆ C ∩ (⋃i, A i) :=
by {intros x h, simp * at *}

-- ----------------------------------------------------
-- Ej. 3. Demostrar
--    C ∩ (⋃i, A i) = (⋃ i, C ∩ A i)
-- ----------------------------------------------------

-- 1ª demostración
example :
  C ∩ (⋃i, A i) = (⋃ i, C ∩ A i) :=
eq_of_subset_of_subset inter_Uni_l1 inter_Uni_l2

-- 2ª demostración
example :
  C ∩ (⋃i, A i) = (⋃ i, C ∩ A i) :=
-- by library_search
inter_Union C A

-- 3ª demostración
example :
  C ∩ (⋃i, A i) = (⋃ i, C ∩ A i) :=
ext $ by simp

-- 4ª demostración
example :
  C ∩ (⋃i, A i) = (⋃ i, C ∩ A i) :=
by {ext, simp}


import BrownCs22.Library.Tactics
import BrownCs22.Library.Defs

-- don't change these lines
namespace HW3
open Set BrownCs22.Set
set_option linter.unusedVariables false
variable (U : Type)
variable (A B : Set U)

/-

# Welcome to the Lean section of HW3!

In these problems, we're going to get a little practice formally proving
set equalities. We've seen two techniques for doing this on paper:

* With the set-element method, we argue that `A = B` by showing that
  `A ⊆ B` and `B ⊆ A`.
* With the algebraic method, we can prove that sets are equal by rewriting
  one or both sides with algebraic identities like `(Aᶜ)ᶜ = A`, until we
  have identical expressions.

We can do both of these in Lean. In this assignment you'll prove two
set equalities, one with each method.

**Notes**:

* Remember that in Lean, we write "the complement of A" as `Aᶜ` instead
  of using a bar over the letter. (Diacritics are hard in a text editor!)

* You can type `⊆` using `\sub`.

* You can type `ᶜ` using `\compl` or `\^c`.

-/

/-

## Problem 1: the set-element method

We saw two important tactics in lecture for set-element proofs in Lean:

* `ext x`: given a goal `A = B` where `A` and `B` are sets, changes the goal
  to showing `x ∈ A ↔ x ∈ B` for an arbitrary element `x`. The name comes
  from "extensionality," the property that two sets are equal if they have
  the same elements.

* `set_simplify`: unfolds the "logic" of a set membership proposition.
  For instance, `x ∈ A ∩ B` simplifies to `x ∈ A ∧ x ∈ B`.
  `x ∈ A \ C` simplifies to `x ∈ A ∧ ¬(x ∈ C)`.
  Calling `set_simplify` will simplify the goal and all hypotheses.

Use these techniques to prove the following.
Starting with `ext x` is probably a good move!
Then think about the last few homeworks; how do you prove an `↔` goal?

-/

/- 4 points -/
theorem problem_1 : (A ∩ B) ∪ A = A := by
  ext x
  split_goal
  { intro hx
    set_simplify
    eliminate hx with hxab hxa
    { eliminate hxab with hxa hxb
      assumption }
    { assumption } }
  { intro hx
    set_simplify
    right
    assumption }

/-

## Problem 2: the algebraic method

What you just proved is sometimes called an "absorption law," since the
intersection `A ∩ B` gets "absorbed" into the bigger set `A`.
This is an example of a useful rewrite rule: if we ever see the pattern
`X ∪ (X ∩ Y)` in a proposition, we can replace it with `X`, since we know
that these sets are the same.

As we saw in Lecture 8, the `rewrite` tactic lets us do this in Lean.
Here's an example of using the above rule, and another useful rewrite rule:

-/

example : A ∩ ((Aᶜ ∩ B) ∪ Aᶜ) = ∅ := by
  rewrite [inter_union_cancel_left]
  rewrite [inter_compl_self]
  reflexivity

/-

The name Lean gives to our identity from problem 1 is
`inter_union_cancel_left`. We also used `inter_compl_self`, which says
`A ∩ Aᶜ = ∅`. Hover over the name in the proof above to see this statement!

The final tactic, `reflexivity`, tells Lean that we are done: we can close
any goal of the form `P ↔ P`.

The `rewrite` tactic will use the identity "left to right":
it will look for the pattern on the left hand side of the identity,
and replace it with the right hand side. For example, `inter_compl_self`
says that `s ∩ sᶜ = ∅`, so it replaces `s ∩ sᶜ` with `∅`.
Sometimes you can use a rule in reverse direction, right to left,
using the symbol `←` (typed `\l` or `\<-`). For example:

-/

example : Aᶜ ∩ Aᶜ = Aᶜ := by
  rewrite [← compl_union] -- we have used de morgan's law "backward",
                          -- changing `Aᶜ ∩ Aᶜ` to `(A ∪ A)ᶜ`.
  rewrite [union_self]
  reflexivity

/-
But this won't work for every identity! Think about `inter_compl_self`.
Backward, this would say that if you see the pattern `∅`, you can replace
it with `s ∩ sᶜ`. But how would Lean know what set `s` you wanted to use?
It could be anything! If you see some funny symbols like `?m1000`,
this is probably what's going on.

You may or may not need to use rules backwards in the following problem.

In recitation, you saw (or will see) a list of set identities:
<https://brown-cs22.github.io/resources/math-resources/sets.pdf>
All the identities on this list are available as rewrite rules in Lean,
listed in the file `BrownCs22/Demos/SetIdentities.lean`.

Use these rewrite rules to complete the following proof. Your proof should
have the same structure as the example above:
a sequence of rewrites, followed by `reflexivity`.

It might help to plan out your steps on paper!

-/

/- 4 points -/
theorem problem_2 : (Aᶜ \ B)ᶜ = A ∪ B := by
  rewrite [diff_eq]
  rewrite [compl_inter]
  rewrite [compl_compl]
  rewrite [compl_compl]
  reflexivity

end HW3


import analysis.special_functions.trigonometric data.real.basic analysis.special_functions.pow
import tactic

variables (x : ℝ)
open real
noncomputable theory

attribute [pp_nodot] real.log

lemma cosh_eq (x : ℝ) : cosh x = (exp x + exp (-x)) / 2 :=
eq_div_of_mul_eq two_ne_zero $ by rw [cosh, exp, exp, complex.of_real_neg, complex.cosh, mul_two,
    ← complex.add_re, ← mul_two, div_mul_cancel _ (two_ne_zero' : (2 : ℂ) ≠ 0), complex.add_re]

lemma sinh_eq (x : ℝ) : sinh x = (exp x - exp (-x)) / 2 :=
eq_div_of_mul_eq two_ne_zero $ by rw [sinh, exp, exp, complex.of_real_neg, complex.sinh, mul_two,
    ← complex.add_re, ← mul_two, div_mul_cancel _ (two_ne_zero' : (2 : ℂ) ≠ 0), complex.sub_re]

/-- A real version of `complex.cosh_sq_sub_sinh_sq`-/
lemma real.cosh_sq_sub_sinh_sq (x : ℝ) : cosh x ^ 2 - sinh x ^ 2 = 1 :=
begin
  rw [sinh, cosh],
  have := complex.cosh_sq_sub_sinh_sq x,
  apply_fun complex.re at this,
  rw [pow_two, pow_two] at this,
  change (⟨_, _⟩ : ℂ).re - (⟨_, _⟩ : ℂ).re = 1 at this,
  rw [complex.cosh_of_real_im x, complex.sinh_of_real_im x] at this,
  norm_num at this,
  rwa [pow_two, pow_two],
end

/-- `real.cosh` is positive-/
lemma cosh_pos (x : ℝ) : 0 < real.cosh x :=
(cosh_eq x).symm ▸ half_pos (add_pos (exp_pos x) (exp_pos (-x)))

lemma cosh_nonneg (x : ℝ) : 0 ≤ real.cosh x := le_of_lt (cosh_pos x)

lemma sinh_strict_mono : strict_mono sinh :=
strict_mono_of_deriv_pos differentiable_sinh (by rw [real.deriv_sinh]; exact cosh_pos)

@[simp] lemma cosh_zero : cosh 0 = 1 := by simp [cosh]

@[simp] lemma cosh_neg : cosh (-x) = cosh x :=
by simp [add_comm, cosh, exp_neg]

lemma sinh_nonneg (h : 0 ≤ x) : 0 ≤ sinh x :=
begin
  rw sinh_eq,
  apply div_nonneg,
  { rw [le_sub_iff_add_le, zero_add, exp_le_exp],
    linarith},
  { norm_num},
end


def binom: ℕ → ℕ → ℕ
| _ 0 := 1
| 0 _ := 0
| (m + 1) (n + 1) := binom m (n + 1) + binom m n

def binom_sum: ℕ → ℕ → ℕ
| n 0 := binom n 0
| n (k + 1) := binom_sum n k + binom n (k + 1)

lemma is_zero: ∀ k, binom 0 (k + 1) = 0 :=
begin
    intros,
    induction k,
        simp [binom],
    assumption
end

lemma binom_sum_zero: ∀ (n : ℕ), binom_sum 0 n = 1 :=
begin
    intros,
    induction n,
        simp [binom_sum, binom],
    simp [binom_sum, binom],
    rw [n_ih, is_zero]
end

lemma binom_exceeds: ∀ (n : ℕ), ∀ (k : ℕ), binom n (n + 1 + k) = 0 :=
begin
    intro n,
    induction n,
        simp [is_zero],
    intros,
    rw [←nat.add_one],
    calc  binom (n_n + 1) (n_n + 1 + 1 + k)
        = binom (n_n + 1) ((n_n + 1 + k) + 1)
            : by simp [nat.add_assoc, nat.add_comm]
    ... = binom n_n (n_n + 1 + k + 1) + binom n_n (n_n + 1 + k)
            : by rw [binom]
    ... = binom n_n (n_n + 1 + k + 1) + 0 : by rw [n_ih]
    ... = binom n_n (n_n + 1 + (k + 1))
            : by simp [nat.add_zero, nat.add_assoc]
    ... = 0 : by rw [n_ih]
end

lemma binom_sum_continue: ∀ n, binom_sum n (n + 1) = binom_sum n n :=
begin
    intros,
    simp [binom_sum, binom_exceeds n 0]
end

lemma binom_sum_split: ∀ (n: ℕ), ∀ (k: ℕ), binom_sum (n + 1) (k + 1)
    = binom_sum n (k + 1) + binom_sum n k :=
begin
    intro n, induction n,
        intro k, induction k,
            simp [binom, binom_sum],
        rw [binom_sum_zero] at *,
        simp * at *,
        assumption,
    intro k, induction k,
        simp [binom_sum, binom],
    calc  binom_sum (nat.succ n_n + 1) (nat.succ k_n + 1)
        = binom_sum (nat.succ n_n + 1) (nat.succ k_n)
          + binom (nat.succ n_n + 1) (nat.succ k_n + 1)
            : by rw binom_sum
    ... = binom_sum (nat.succ n_n) (k_n + 1) + binom_sum (nat.succ n_n) k_n
          + binom (nat.succ n_n + 1) (nat.succ k_n + 1)
            : by rw k_ih
    ... = (binom_sum (nat.succ n_n) (k_n + 1) + binom_sum (nat.succ n_n) k_n)
          + binom (nat.succ n_n) (nat.succ k_n + 1) + binom (nat.succ n_n) (nat.succ k_n)
            : by simp [binom]
    ... = (binom_sum (nat.succ n_n) (k_n + 1) + binom (nat.succ n_n) (nat.succ k_n + 1))
          + (binom_sum (nat.succ n_n) k_n) + binom (nat.succ n_n) (nat.succ k_n)
            : by simp [nat.add_assoc, nat.add_comm]
    ... = binom_sum (nat.succ n_n) (nat.succ k_n + 1)
           + binom_sum (nat.succ n_n) (nat.succ k_n)
            : by simp [binom_sum]
end

example: ∀ (n: ℕ), binom_sum n n = 2^n :=
begin
    intros,
    induction n,
        simp [binom_sum, binom],
    simp [binom_sum_split],
    simp [binom_sum_continue],
    simp *,
    calc 2 ^ n_n + 2 ^ n_n
        = 1 * 2 ^ n_n + 2 ^ n_n : by rw nat.one_mul
    ... = 2 * 2 ^ n_n : by rw ←nat.succ_mul
    ... = 2 ^ n_n * 2 : by rw ←mul_comm
    ... = 2 ^ nat.succ n_n : by rw nat.pow_succ
end


import float.basic

-- Following https://isabelle.in.tum.de/website-Isabelle2013/dist/library/HOL/HOL-Library/Float.html

variable (prec : ℕ)

def round_up (x : ℚ) : 𝔽 :=
float.mk (⌈x * 2 ^ prec⌉) (-prec)

def round_down (x : ℚ) : 𝔽 :=
float.mk (⌊x * 2 ^ prec⌋) (-prec)

lemma round_up_zero : round_up prec 0 = 0 :=
by { apply quotient.sound, show to_rat _ = _, simp [to_rat], }

lemma round_down_zero : round_down prec 0 = 0 :=
by { apply quotient.sound, show to_rat _ = _, simp [to_rat], }

lemma round_up_diff_round_down (x : ℚ)
: round_up prec x - round_down prec x ≤ float.mk 1 (-prec) :=
begin
  show float.eval _ ≤ _,
  simp [float.eval_sub, round_up, round_down, float.eval_mk],
  suffices hsuff : ↑(⌈x * 2 ^ prec⌉ - ⌊x * 2 ^ prec⌋) * ((2 : ℚ) ^ prec)⁻¹ ≤ 1 * ((2 : ℚ) ^ prec)⁻¹,
  { push_cast at hsuff, rw [sub_mul, one_mul] at hsuff, exact hsuff, },
  have h : 0 < ((2 : ℚ) ^ prec)⁻¹,
  { norm_num, },
  rw [mul_le_mul_right h], show _ ≤ ↑(1 : ℤ), simp [coe],
  rw [sub_le_iff_le_add, add_comm],
  exact ceil_le_floor_add_one _,
end


import tuto_lib

section
/-
The first part of this file makes sure you can negate quantified statements
in your head without the help of `push_neg`.

You need to complete the statement and then use the `check_me` tactic
to check your answer. This tactic exists only for those exercises,
it mostly calls `push_neg` and then cleans up a bit.

def seq_limit (u : ℕ → ℝ) (l : ℝ) : Prop :=
∀ ε > 0, ∃ N, ∀ n ≥ N, |u n - l| ≤ ε
-/

-- In this section, u denotes a sequence of real numbers
-- f is a function from ℝ to ℝ
-- x₀ and l are real numbers
variables (u : ℕ → ℝ) (f : ℝ → ℝ) (x₀ l : ℝ)

/- Negation of "u tends to l" -/
-- 0062
example : ¬ (∀ ε > 0, ∃ N, ∀ n ≥ N, |u n - l| ≤ ε) ↔
∃ ε > 0, ∀ N, ∃ n ≥ N, |u n - l| > ε
:=
begin
  check_me,
end

/- Negation of "f is continuous at x₀" -/
-- 0063
example : ¬ (∀ ε > 0, ∃ δ > 0, ∀ x, |x - x₀| ≤ δ →  |f x - f x₀| ≤ ε) ↔
∃ ε > 0, ∀ δ > 0, ∃ x, |x - x₀| ≤ δ ∧ |f x - f x₀| > ε
:=
begin
  check_me,
end

/-
In the next exercise, we need to keep in mind that
`∀ x x', ...` is the abbreviation of
`∀ x, ∀ x', ... `.

Also, `∃ x x', ...` is the abbreviation of `∃ x, ∃ x', ...`.
-/

/- Negation of "f is uniformly continuous on ℝ" -/
-- 0064
example : ¬ (∀ ε > 0, ∃ δ > 0, ∀ x x', |x' - x| ≤ δ →  |f x' - f x| ≤ ε) ↔
∃ ε > 0, ∀ δ > 0, ∃ x x', |x' - x| ≤ δ ∧ |f x' - f x| > ε
:=
begin
  check_me,
end

/- Negation of "f is sequentially continuous at x₀" -/
-- 0065
example : ¬ (∀ u : ℕ → ℝ, (∀ ε > 0, ∃ N, ∀ n ≥ N, |u n - x₀| ≤ ε) → (∀ ε > 0, ∃ N, ∀ n ≥ N, |(f ∘ u) n - f x₀| ≤ ε))  ↔
∃ u : ℕ → ℝ, (∀ ε > 0, ∃ N, ∀ n ≥ N, |u n - x₀| ≤ ε) ∧ (∃ ε > 0, ∀ N, ∃ n ≥ N, |(f ∘ u) n - f x₀| > ε)
:=
begin
  check_me,
end
end

/-
We now turn to elementary applications of negations to limits of sequences.
Remember that `linarith` can find easy numerical contradictions.

Also recall the following lemmas:

abs_le {x y : ℝ} : |x| ≤ y ↔ -y ≤ x ∧ x ≤ y

ge_max_iff (p q r) : r ≥ max p q  ↔ r ≥ p ∧ r ≥ q

le_max_left p q : p ≤ max p q

le_max_right p q : q ≤ max p q

/-- The sequence `u` tends to `+∞`. -/
def tendsto_infinity (u : ℕ → ℝ) := ∀ A, ∃ N, ∀ n ≥ N, u n ≥ A
-/

-- 0066
example {u : ℕ → ℝ} : tendsto_infinity u → ∀ l, ¬ seq_limit u l :=
begin
  intros h l, unfold seq_limit, push_neg, use 1, split, linarith, intro N,
  rcases h (l+2) with ⟨N', hN⟩, specialize hN (max N N'), specialize hN (le_max_right _ _),
  use max N N', split, apply le_max_left, by_contradiction hyp, push_neg at hyp,
  rw abs_le at hyp, cases hyp with h1 h2, linarith,
end

def nondecreasing_seq (u : ℕ → ℝ) := ∀ n m, n ≤ m → u n ≤ u m

-- 0067
example (u : ℕ → ℝ) (l : ℝ) (h : seq_limit u l) (h' : nondecreasing_seq u) :
  ∀ n, u n ≤ l :=
begin
  intro k, by_contradiction hyp, push_neg at hyp, specialize h ((u k - l)/2) (by linarith),
  cases h with N h, specialize h (max N k) (le_max_left _ _),
  specialize h' k (max N k) (le_max_right _ _), rw abs_le at h, cases h with h1 h2, linarith,
end

/-
In the following exercises, `A : set ℝ` means that A is a set of real numbers.
We can use the usual notation x ∈ A.

The notation `∀ x ∈ A, ...` is the abbreviation of `∀ x, x ∈ A → ... `

The notation `∃ x ∈ A, ...` is the abbreviation of `∃ x, x ∈ A ∧ ... `.
More precisely it is the abbreviation of `∃ x (H : x ∈ A), ...`
which is Lean's strange way of saying `∃ x, x ∈ A ∧ ... `.
You can convert between these forms using the lemma
  exists_prop {p q : Prop} : (∃ (h : p), q) ↔ p ∧ q

We'll work with upper bounds and supremums.
Again we'll introduce specialized definitions for the sake of exercises, but mathlib
has more general versions.

def upper_bound (A : set ℝ) (x : ℝ) := ∀ a ∈ A, a ≤ x

def is_sup (A : set ℝ) (x : ℝ) := upper_bound A x ∧ ∀ y, upper_bound A y → x ≤ y

Remark: one can easily show that a set of real numbers has at most one sup,
but we won't need this.
-/

-- 0068
example {A : set ℝ} {x : ℝ} (hx : is_sup A x) :
∀ y, y < x → ∃ a ∈ A, y < a :=
begin
  intros y hy, by_contradiction hyp, push_neg at hyp, change upper_bound A y at hyp,
  cases hx with h1 h2, specialize h2 y hyp, linarith,
end

/-
Let's do a variation on an example from file 07 that will be useful in the last
exercise below.
-/

-- 0069
lemma le_of_le_add_all' {x y : ℝ} :
  (∀ ε > 0, y ≤ x + ε) →  y ≤ x :=
begin
  intro h, by_contradiction hyp, push_neg at hyp, specialize h ((y-x)/2) (by linarith),
  linarith,
end

-- 0070
example {x y : ℝ} {u : ℕ → ℝ} (hu : seq_limit u x)
  (ineg : ∀ n, u n ≤ y) : x ≤ y :=
begin
  apply le_of_le_add_all', intros ε ε_pos, rcases hu ε ε_pos with ⟨N, hN⟩,
  specialize hN N (by linarith), specialize ineg N, rw abs_le at hN, linarith,
end