import data.real.basic algebra.group_power tactic.ring

variable {x : ℝ}
def f : ℝ → ℝ := λ x, x^2 + 3
def g : ℝ → ℝ := λ x, 2*x

def h1 : ℝ → ℝ := g ∘ f
theorem Q1005i : h1 x = 2*x^2 + 6 := sorry

def h2 : ℝ → ℝ := f ∘ g
theorem Q1005ii : h2 x = 4*x^2 + 3 := sorry

def h3 : ℝ → ℝ := λ x, f x * g x
theorem Q1005iii : h3 x = 2*x^3 + 6*x := sorry

def h4 : ℝ → ℝ := λ x, f x + g x
theorem Q1005iv : h4 x = x^2 + 2*x + 3 := sorry

def h5 : ℝ → ℝ := λ x, f (g x)
theorem Q1005v : h5 x = h2 x := sorry


/- First-class functions -/

def twice (f : Nat → Nat) (a : Nat) :=
  f (f a)

#check twice
-- (Nat → Nat) → Nat → Nat

#eval twice (fun x => x + 2) 10

theorem twice_add_2 (a : Nat) : twice (fun x => x + 2) a = a + 4 := rfl

-- `(· + 2)` is syntax sugar for `(fun x => x + 2)`.
#eval twice (· + 2) 10


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

@[ext]
structure frog :=
  -- A frog hangs out on the natural number line of lily pads
  (location : ℕ → ℕ)
  -- At time 0, it sits on location 0
  (location_zero : location 0 = 0)
  -- For some fixed step size,
  (step_size : ℕ)
  -- the frog jumps `step_size` units to the right each second.
  (step : ∀ n, location (n+1) = location n + step_size)

lemma frog_explicit_formula (f : frog) :
-- Show that the position of the frog at time n is n * step_size.
∀ n, f.location n = n * f.step_size :=
begin
  intros, induction n with d hd,
  rw f.location_zero, norm_num,
  rw [f.step, hd, ← nat.add_one],
  ring,
end

-- We can define a frog just by giving its step size
local attribute [simp]
def frog_of_step_size (step_size : ℕ) : frog :=
{ location := λ n, n * step_size,
  location_zero := by simp,
  step_size := step_size,
  step := by {intro, ring} }

-- and every frog can be defined in this way
lemma frog_eq_frog_of_step_size (f : frog) :
f = frog_of_step_size f.step_size :=
begin
  ext,
  { rw frog_explicit_formula, simp },
  simp,
end

lemma catch_the_frog :
-- Show that there is a way to check one lily pad each second
  ∃ (strategy : ℕ → ℕ),
-- so that no matter how fast the frog travels,
  ∀ step_size,
-- you'll eventually catch it.
  ∃ catch_time > 0,
  strategy catch_time = (frog_of_step_size step_size).location catch_time :=
begin
  use λ n, n * (n - 1),
  intro k, use k + 1,
  split, { linarith, },
  simp,
end


-- One way in which Lean’s dependent type theory extends simple type theory is that types themselves
-- entities like nat and bool, are first-class citizens
-- which is to say that they themselves are objects of study which means that they
-- also have types

#check nat
#check bool
#check nat → bool
#check nat × bool
#check nat → nat
#check nat × nat → nat
#check nat → nat → nat
#check nat → (nat → nat)
#check nat → nat → bool
#check (nat → nat) → nat

-- We can also declare new constants and constructors for types:
constants α β : Type
constant F : Type → Type
constant G : Type → Type → Type

#check α
#check F α
#check F nat
#check G α
#check G α β
#check G α nat

-- Indeed, we have already seen an example of a function of type Type → Type → Type, namely,
-- the Cartesian product.

constants χ μ : Type

#check prod χ μ
#check prod nat nat

-- Here is another example: given any type α,
-- the type list α denotes the type of lists of elements of type α.

constant γ : Type

#check list γ
#check list nat

-- Given that every expression in Lean has a type,
-- it is natural to ask: what type does Type itself have?
#check Type

-- We have actually come up against one of the most subtle aspects of Lean’s typing system.
-- Lean’s underlying foundation has an infinite hierarchy of types:

#check Type     -- Type 1
#check Type 1   -- Type 2
#check Type 2   -- Type 3
#check Type 3   -- Type 4
#check Type 4   -- Type 5

#check Prop

-- We want some operations, however, to be polymorphic over type universes.
-- For example, list α should make sense for any type α, no matter which type universe α lives in.
#check list

-- The function prod is similarly polymorphic:
#check prod

-- To define polymorphic constants and variables,
-- Lean allows us to declare universe variables explicitly:
universe u
constant ζ : Type u
#check ζ

-- Equivalently, we can write Type _ or Type* to avoid giving the arbitrary universe a name:
constant σ : Type _
#check σ

constant κ : Type*
#check κ


-- Lista 7: Capítulo 14

-- Lucas Moschen

-- Exercício 1

section
    parameters {A : Type} {R : A → A → Prop}
    parameter (irreflR : irreflexive R)
    parameter (transR : transitive R)

    variables a b c : A

    local infix < := R

    def R' (a b : A) : Prop := R a b ∨ a = b
    local infix ≤ := R'

    theorem reflR' (a : A) : a ≤ a :=
    begin
        have h1: a = a, from eq.refl a,
        apply or.inr h1
    end

    theorem transR' {a b c : A} (h1 : a ≤ b) (h2 : b ≤ c):
    a ≤ c :=
    or.elim h1
        (assume h3: a < b, or.elim h2
                (assume h4: b < c, or.inl (transR h3 h4))
                (assume h4: b = c, eq.subst h4 h1))
        (assume h3: a = b, eq.subst (eq.symm h3) h2)

    theorem antisymmR' {a b : A} (h1 : a ≤ b) (h2 : b ≤ a) :
    a = b :=
    or.elim h1
        (assume h: a < b, or.elim h2
            (assume h3: b < a, false.elim ((irreflR b) (transR h3 h)))
            (assume h3: b = a, eq.symm h3))
        (assume h: a = b, h)
end

-- Exercício 2

section
    parameters {A : Type} {R : A → A → Prop}
    parameter (reflR : reflexive R)
    parameter (transR : transitive R)

    def S (a b : A) : Prop := R a b ∧ R b a

    example : transitive S :=
        assume x y z,
        assume h1 h2,
        and.intro (transR h1.left h2.left) (transR h2.right h1.right)
end

-- Exercício 3

section
  parameters {A : Type} {a b c : A} {R : A → A → Prop}
  parameter (Rab : R a b)
  parameter (Rbc : R b c)
  parameter (nRac : ¬ R a c)

  -- Prove one of the following two theorems:

  theorem R_is_not_strict_partial_order :
    ¬(irreflexive R ∧ transitive R) :=
  assume h: irreflexive R ∧ transitive R,
  show false, from nRac (h.right Rab Rbc)
end

-- Exercício 4

open nat

example : 1 ≤ 4 :=
have h1: 1 <  1 + 1, from lt_succ_self 1,
have h2: (1 + 1) < (1 + 1) + 1, from lt_succ_self (1 + 1),
have h3: (1 + 1 + 1) < (1 + 1 + 1) + 1, from lt_succ_self (1 + 1 + 1),
have h4: 4 = 1 + 1 + 1 + 1, by simp,
have h5: 1 ≤ (1 + 1 + 1) + 1, from le_of_lt (lt_trans h1 (lt_trans h2 h3)),
eq.subst h4 h5


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

! This file was ported from Lean 3 source module data.list.rdrop
! leanprover-community/mathlib commit 26f081a2fb920140ed5bc5cc5344e84bcc7cb2b2
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Data.List.Basic
import Mathlib.Data.List.Infix
/-!

# Dropping or taking from lists on the right

Taking or removing element from the tail end of a list

## Main defintions

- `rdrop n`: drop `n : ℕ` elements from the tail
- `rtake n`: take `n : ℕ` elements from the tail
- `rdropWhile p`: remove all the elements from the tail of a list until it finds the first element
  for which `p : α → Bool` returns false. This element and everything before is returned.
- `rtakeWhile p`:  Returns the longest terminal segment of a list for which `p : α → Bool` returns
  true.

## Implementation detail

The two predicate-based methods operate by performing the regular "from-left" operation on
`List.reverse`, followed by another `List.reverse`, so they are not the most performant.
The other two rely on `List.length l` so they still traverse the list twice. One could construct
another function that takes a `L : ℕ` and use `L - n`. Under a proof condition that
`L = l.length`, the function would do the right thing.

-/

variable {α : Type _} (p : α → Bool) (l : List α) (n : ℕ)

namespace List

/-- Drop `n` elements from the tail end of a list. -/
def rdrop : List α :=
  l.take (l.length - n)
#align list.rdrop List.rdrop

@[simp]
theorem rdrop_nil : rdrop ([] : List α) n = [] := by simp [rdrop]
#align list.rdrop_nil List.rdrop_nil

@[simp]
theorem rdrop_zero : rdrop l 0 = l := by simp [rdrop]
#align list.rdrop_zero List.rdrop_zero

theorem rdrop_eq_reverse_drop_reverse : l.rdrop n = reverse (l.reverse.drop n) := by
  rw [rdrop]
  induction' l using List.reverseRecOn with xs x IH generalizing n
  · simp
  · cases n
    · simp [take_append]
    · simp [take_append_eq_append_take, IH]
#align list.rdrop_eq_reverse_drop_reverse List.rdrop_eq_reverse_drop_reverse

@[simp]
theorem rdrop_concat_succ (x : α) : rdrop (l ++ [x]) (n + 1) = rdrop l n := by
  simp [rdrop_eq_reverse_drop_reverse]
#align list.rdrop_concat_succ List.rdrop_concat_succ

/-- Take `n` elements from the tail end of a list. -/
def rtake : List α :=
  l.drop (l.length - n)
#align list.rtake List.rtake

@[simp]
theorem rtake_nil : rtake ([] : List α) n = [] := by simp [rtake]
#align list.rtake_nil List.rtake_nil

@[simp]
theorem rtake_zero : rtake l 0 = [] := by simp [rtake]
#align list.rtake_zero List.rtake_zero

theorem rtake_eq_reverse_take_reverse : l.rtake n = reverse (l.reverse.take n) := by
  rw [rtake]
  induction' l using List.reverseRecOn with xs x IH generalizing n
  · simp
  · cases n
    · exact drop_length _
    · simp [drop_append_eq_append_drop, IH]
#align list.rtake_eq_reverse_take_reverse List.rtake_eq_reverse_take_reverse

@[simp]
theorem rtake_concat_succ (x : α) : rtake (l ++ [x]) (n + 1) = rtake l n ++ [x] := by
  simp [rtake_eq_reverse_take_reverse]
#align list.rtake_concat_succ List.rtake_concat_succ

/-- Drop elements from the tail end of a list that satisfy `p : α → Bool`.
Implemented naively via `List.reverse` -/
def rdropWhile : List α :=
  reverse (l.reverse.dropWhile p)
#align list.rdrop_while List.rdropWhile

@[simp]
theorem rdropWhile_nil : rdropWhile p ([] : List α) = [] := by simp [rdropWhile, dropWhile]
#align list.rdrop_while_nil List.rdropWhile_nil

theorem rdropWhile_concat (x : α) :
    rdropWhile p (l ++ [x]) = if p x then rdropWhile p l else l ++ [x] := by
  simp only [rdropWhile, dropWhile, reverse_append, reverse_singleton, singleton_append]
  split_ifs with h <;> simp [h]
#align list.rdrop_while_concat List.rdropWhile_concat

@[simp]
theorem rdropWhile_concat_pos (x : α) (h : p x) : rdropWhile p (l ++ [x]) = rdropWhile p l := by
  rw [rdropWhile_concat, if_pos h]
#align list.rdrop_while_concat_pos List.rdropWhile_concat_pos

@[simp]
theorem rdropWhile_concat_neg (x : α) (h : ¬p x) : rdropWhile p (l ++ [x]) = l ++ [x] := by
  rw [rdropWhile_concat, if_neg h]
#align list.rdrop_while_concat_neg List.rdropWhile_concat_neg

theorem rdropWhile_singleton (x : α) : rdropWhile p [x] = if p x then [] else [x] := by
  rw [← nil_append [x], rdropWhile_concat, rdropWhile_nil]
#align list.rdrop_while_singleton List.rdropWhile_singleton

theorem rdropWhile_last_not (hl : l.rdropWhile p ≠ []) : ¬p ((rdropWhile p l).getLast hl) := by
  simp_rw [rdropWhile]
  rw [getLast_reverse]
  exact dropWhile_nthLe_zero_not _ _ _
#align list.rdrop_while_last_not List.rdropWhile_last_not

theorem rdropWhile_prefix : l.rdropWhile p <+: l := by
  rw [← reverse_suffix, rdropWhile, reverse_reverse]
  exact dropWhile_suffix _
#align list.rdrop_while_prefix List.rdropWhile_prefix

variable {p} {l}

@[simp]
theorem rdropWhile_eq_nil_iff : rdropWhile p l = [] ↔ ∀ x ∈ l, p x := by simp [rdropWhile]
#align list.rdrop_while_eq_nil_iff List.rdropWhile_eq_nil_iff

-- it is in this file because it requires `List.Infix`
@[simp]
theorem dropWhile_eq_self_iff : dropWhile p l = l ↔ ∀ hl : 0 < l.length, ¬p (l.nthLe 0 hl) := by
  cases' l with hd tl
  · simp only [dropWhile, true_iff]
    intro h
    by_contra
    rwa [length_nil, lt_self_iff_false] at h
  · rw [dropWhile]
    refine' ⟨fun h => _, fun h => _⟩
    · intro _ H
      rw [nthLe, get] at H
      refine' (cons_ne_self hd tl) (Sublist.antisymm _ (sublist_cons _ _))
      rw [← h]
      simp only [H]
      exact List.isSuffix.sublist (dropWhile_suffix p)
    · have := h (by simp only [length, Nat.succ_pos])
      rw [nthLe, get] at this
      simp_rw [this]
#align list.drop_while_eq_self_iff List.dropWhile_eq_self_iff

/- porting note: This proof is longer than it used to be because `simp` refuses to rewrite
 the `l ≠ []` condition if `hl` is not `intro`'d yet -/
@[simp]
theorem rdropWhile_eq_self_iff : rdropWhile p l = l ↔ ∀ hl : l ≠ [], ¬p (l.getLast hl) := by
  simp only [rdropWhile, reverse_eq_iff, dropWhile_eq_self_iff, getLast_eq_get]
  refine' ⟨fun h hl => _, fun h hl => _⟩
  · rw [← length_pos, ← length_reverse] at hl
    have := h hl
    rwa [nthLe, get_reverse'] at this
  · rw [length_reverse, length_pos] at hl
    have := h hl
    rwa [nthLe, get_reverse']
#align list.rdrop_while_eq_self_iff List.rdropWhile_eq_self_iff

variable (p) (l)

theorem dropWhile_idempotent : dropWhile p (dropWhile p l) = dropWhile p l := by
  simp only [dropWhile_eq_self_iff]
  exact fun h => dropWhile_nthLe_zero_not p l h

#align list.drop_while_idempotent List.dropWhile_idempotent

theorem rdropWhile_idempotent : rdropWhile p (rdropWhile p l) = rdropWhile p l :=
  rdropWhile_eq_self_iff.mpr (rdropWhile_last_not _ _)
#align list.rdrop_while_idempotent List.rdropWhile_idempotent

/-- Take elements from the tail end of a list that satisfy `p : α → Bool`.
Implemented naively via `List.reverse` -/
def rtakeWhile : List α :=
  reverse (l.reverse.takeWhile p)
#align list.rtake_while List.rtakeWhile

@[simp]
theorem rtakeWhile_nil : rtakeWhile p ([] : List α) = [] := by simp [rtakeWhile, takeWhile]
#align list.rtake_while_nil List.rtakeWhile_nil

theorem rtakeWhile_concat (x : α) :
    rtakeWhile p (l ++ [x]) = if p x then rtakeWhile p l ++ [x] else [] := by
  simp only [rtakeWhile, takeWhile, reverse_append, reverse_singleton, singleton_append]
  split_ifs with h <;> simp [h]
#align list.rtake_while_concat List.rtakeWhile_concat

@[simp]
theorem rtakeWhile_concat_pos (x : α) (h : p x) :
    rtakeWhile p (l ++ [x]) = rtakeWhile p l ++ [x] := by rw [rtakeWhile_concat, if_pos h]
#align list.rtake_while_concat_pos List.rtakeWhile_concat_pos

@[simp]
theorem rtakeWhile_concat_neg (x : α) (h : ¬p x) : rtakeWhile p (l ++ [x]) = [] := by
  rw [rtakeWhile_concat, if_neg h]
#align list.rtake_while_concat_neg List.rtakeWhile_concat_neg

theorem rtakeWhile_suffix : l.rtakeWhile p <:+ l := by
  rw [← reverse_prefix, rtakeWhile, reverse_reverse]
  exact takeWhile_prefix _
#align list.rtake_while_suffix List.rtakeWhile_suffix

variable {p} {l}

@[simp]
theorem rtakeWhile_eq_self_iff : rtakeWhile p l = l ↔ ∀ x ∈ l, p x := by
  simp [rtakeWhile, reverse_eq_iff]
#align list.rtake_while_eq_self_iff List.rtakeWhile_eq_self_iff

-- Porting note: This needed a lot of rewriting.
@[simp]

theorem mem_rtakeWhile_imp {x : α} (hx : x ∈ rtakeWhile p l) : p x := by
  rw [rtakeWhile, mem_reverse] at hx
  exact mem_takeWhile_imp hx

#align list.mem_rtake_while_imp List.mem_rtakeWhile_imp

variable (p) (l)

theorem rtakeWhile_idempotent : rtakeWhile p (rtakeWhile p l) = rtakeWhile p l :=
  rtakeWhile_eq_self_iff.mpr fun _ => mem_rtakeWhile_imp
#align list.rtake_while_idempotent List.rtakeWhile_idempotent

end List


/-
Copyright (c) 2021 Anatole Dedecker. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Anatole Dedecker
-/
import analysis.normed_space.ordered
import analysis.asymptotics.asymptotics

/-!
# A collection of specific asymptotic results

This file contains specific lemmas about asymptotics which don't have their place in the general
theory developped in `analysis.asymptotics.asymptotics`.
-/

open filter asymptotics
open_locale topological_space

section linear_ordered_field

variables {𝕜 : Type*} [linear_ordered_field 𝕜]

lemma pow_div_pow_eventually_eq_at_top {p q : ℕ} :
  (λ x : 𝕜, x^p / x^q) =ᶠ[at_top] (λ x, x^((p : ℤ) -q)) :=
begin
  apply ((eventually_gt_at_top (0 : 𝕜)).mono (λ x hx, _)),
  simp [fpow_sub hx.ne'],
end

lemma pow_div_pow_eventually_eq_at_bot {p q : ℕ} :
  (λ x : 𝕜, x^p / x^q) =ᶠ[at_bot] (λ x, x^((p : ℤ) -q)) :=
begin
  apply ((eventually_lt_at_bot (0 : 𝕜)).mono (λ x hx, _)),
  simp [fpow_sub hx.ne'.symm],
end

lemma tendsto_fpow_at_top_at_top {n : ℤ}
  (hn : 0 < n) : tendsto (λ x : 𝕜, x^n) at_top at_top :=
begin
  lift n to ℕ using hn.le,
  simp only [gpow_coe_nat],
  exact tendsto_pow_at_top (nat.succ_le_iff.mpr $int.coe_nat_pos.mp hn)
end

lemma tendsto_pow_div_pow_at_top_at_top {p q : ℕ}
  (hpq : q < p) : tendsto (λ x : 𝕜, x^p / x^q) at_top at_top :=
begin
  rw tendsto_congr' pow_div_pow_eventually_eq_at_top,
  apply tendsto_fpow_at_top_at_top,
  linarith
end

lemma tendsto_pow_div_pow_at_top_zero [topological_space 𝕜] [order_topology 𝕜] {p q : ℕ}
  (hpq : p < q) : tendsto (λ x : 𝕜, x^p / x^q) at_top (𝓝 0) :=
begin
  rw tendsto_congr' pow_div_pow_eventually_eq_at_top,
  apply tendsto_fpow_at_top_zero,
  linarith
end

end linear_ordered_field

section normed_linear_ordered_field

variables {𝕜 : Type*} [normed_linear_ordered_field 𝕜]

lemma asymptotics.is_o_pow_pow_at_top_of_lt
  [order_topology 𝕜] {p q : ℕ} (hpq : p < q) :
  is_o (λ x : 𝕜, x^p) (λ x, x^q) at_top :=
begin
  refine (is_o_iff_tendsto' _).mpr (tendsto_pow_div_pow_at_top_zero hpq),
  exact (eventually_gt_at_top 0).mono (λ x hx hxq, (pow_ne_zero q hx.ne' hxq).elim),
end

lemma asymptotics.is_O.trans_tendsto_norm_at_top {α : Type*} {u v : α → 𝕜} {l : filter α}
  (huv : is_O u v l) (hu : tendsto (λ x, ∥u x∥) l at_top) : tendsto (λ x, ∥v x∥) l at_top :=
begin
  rcases huv.exists_pos with ⟨c, hc, hcuv⟩,
  rw is_O_with at hcuv,
  convert tendsto.at_top_div_const hc (tendsto_at_top_mono' l hcuv hu),
  ext x,
  rw mul_div_cancel_left _ hc.ne.symm,
end

end normed_linear_ordered_field


-- Pruebas de y = x → y = z → x = z
-- ================================

-- ----------------------------------------------------
-- Ej. 1. Demostrar
--    y = x → y = z → x = z
-- ----------------------------------------------------

import tactic

variables (U : Type)
variables (x y z : U)

-- 1ª demostración
example : y = x → y = z → x = z :=
assume h1 : y = x,
assume h2 : y = z,
have h3 : x = y, from eq.symm h1,
show x = z,      from eq.trans h3 h2

-- 2ª demostración
example : y = x → y = z → x = z :=
assume h1 : y = x,
assume h2 : y = z,
have h3 : x = y, from eq.symm h1,
eq.trans h3 h2

-- 3ª demostración
example : y = x → y = z → x = z :=
assume h1 : y = x,
assume h2 : y = z,
eq.trans (eq.symm h1) h2

-- 4ª demostración
example : y = x → y = z → x = z :=
λ h1 h2, eq.trans (eq.symm h1) h2

-- 5ª demostración
example : y = x → y = z → x = z :=
λ h1 h2, eq.trans h1.symm h2

-- 6ª demostración
example : y = x → y = z → x = z :=
-- by library_search
λ h, h.congr_left.mp

-- 7ª demostración
example : y = x → y = z → x = z :=
begin
  intros h1 h2,
  rwa ←h1,
end

-- 8ª demostración
example : y = x → y = z → x = z :=
begin
  intros h1 h2,
  rw h1 at h2,
  assumption,
end

-- 9ª demostración
example : y = x → y = z → x = z :=
begin
  intros h1 h2,
  rwa h1 at h2,
end

-- 10ª demostración
example : y = x → y = z → x = z :=
begin
  intros h1 h2,
  calc x = y : h1.symm
     ... = z : h2,
end

-- 11ª demostración
example : y = x → y = z → x = z :=
-- by hint
by finish

-- 12ª demostración
example : y = x → y = z → x = z :=
assume h1 : y = x,
assume h2 : y = z,
show x = z,
  begin
    rw ←h1,
    rw h2
  end

-- 13ª demostración
example : y = x → y = z → x = z :=
assume h1 : y = x,
assume h2 : y = z,
show x = z,
  begin
    rw [←h1, h2]
  end

-- 14ª demostración
example : y = x → y = z → x = z :=
assume h1 : y = x,
assume h2 : y = z,
show x = z, by rw [←h1, h2]


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

import hard_mode.sheet09
import tactic.ring

/-

# Induced functions on quotients

The quotient comes with what one would call a universal property in advanced courses: Given a type `X`, an
equivalence relation `≈` on `X`, then for every other type `Y` and every function `h : X → Y` such that
`x ≈ x' → f x = f x'` there is a unique function `h : X/≈  → Y` such that `f = h ∘ q`. In standard maths courses
the existence of such `h` in examples is often justified as follows: For each equivalence class `⟦x⟧` of
the quotient, choose a representative `x : X` and define `h(⟦x⟧) := f(x)`. One has to then prove that this
is well-defined by invoking the assumption `x ≈ x' → f x = f x'`. In Lean, the function `h` can be produced
automatically using `quotient.lift` applied to the assumption `x ≈ x' → f x = f x'`, one doesn't have to go
through the steps.

On this sheet we will see how this works, first starting with the example of negation, which is easier than
addition as it only has one input, not two.
-/

def neg : myint → myint := _

instance : has_neg myint :=
{ neg := neg }

/-

There is an alternative way, which is in fact a special case of the first. Suppose `Y = Z/≈₂` and we are given
a function `g : X → Z`. Consider the function `f = q₂ ∘ g` where `q₂ : Z → Z/≈₂` is the surjection.
Then `x ≈ x' → f x = f x'` can be checked on `Z` instead, i.e. it can be checked that `x ≈ x' → g x ≈₂ g x'`
in order to reach the conclusion for `f`. Since this is a common situation, there is a special name for it:
`quotient.map`, which takes the assumption `x ≈ x' → g x ≈₂ g x'` and defines a function `X/≈ → Z/≈₂`.

-/

def neg2 : myint → myint := _
begin
  sorry
end

/- Why do the two definitions just defined agree? -/
lemma defn_neg_agree : neg = neg2 :=
begin
  sorry
end

/-

Our next step will be to provide an addition and a multiplication on `myint` and prove that `myint`
with these operations forms a commutative ring. To recall, a commutative ring is a set `R`
together with operations `+: R×R → R` and `*: R×R → R` such that the following axioms hold:
(R1) `(x+y)+z=x+(y+z)` for all `x y z : R`
(R2) `x+y=y+x` for all `x y : R`
(R3) `∃ 0 : x+0=x=0+x` for all `x : R`
(R4) `∀ x ∃ -x : x + (-x) = (-x) + x = 0`
(R5) `(x * y) * z = x * (y * z)`
(R6) `∃ 1 : 1 * x = x`
(R7) `x * y = y * x` for all `x y : R`
(R8) `x * (y + z) = x * y + x * z` for all `x y z : R`

In fact, for lean addition is not a function `R×R → R`, but rather a function `R → (R → R)`, or
`R → R → R` as in contrast to addition and multiplication, `→` is right associative in Lean.
The function `R → (R → R)` takes a ring element `x : R` and sends it to the function `R → R`
which sends `y : R` to `x + y`, in λ-notation `λ y, x+y`. In computer science, the correspondence
between functions `R×R → R` and `R → (R → R)` is called currying after Haskell Curry. For
mathematicians (using category theory), it is just a special case of the concept of an adjunction,
i.e. the functors of taking cartesian product  `- × R` and taking Hom in the category of sets
`Hom(R,-)` are adjoint to each other.

We want to define addition `myint × myint → myint` or equivalently `myint → myint → myint`.
If we think about the first incarnation, we could do that as follows: First we prove that
given an equivalence relation on `X` and an equivalence relation on `Y` we prove that there
is an equivalence relation on `X × Y` given by `(x,y) ≈ (x',y')` iff `x ≈ x'` and `y ≈ y'`.
Then we do the same steps as before. Applying currying, in the other way of thinking,
we would have to do a two-step process of constructing a function from the quotient `myint` to
the set of functions from the quotient `myint` to `myint`. Luckily, we don't have to think in
detail about what this entails - binary operations are a common enough thing that Lean has a
special construction that does all in one step, namely `quotient.map₂`.

-/

def add : myint → myint → myint := _
begin
  sorry
end

instance : has_add myint :=
{ add := add }

def mul : myint → myint → myint := _
begin
  sorry
end

instance : has_mul myint :=
{ mul := mul }

/- Now we have to prove the ring axioms. For this, we have to prove something for all equivalence
classes `r s t : myint`. We would rather like to check this on representatives. Then it becomes
easy. To do this (depending on the number of inputs) you can apply the lemmas
`quotient.induction_on r`, `quotient.induction_on₂ r s`, `quotient.induction_on₃ r s t`. To
simplify the goal view, after that you can put `clear r s t` but that is not strictly necessary. -/

instance : comm_ring myint :=
_


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

! This file was ported from Lean 3 source module data.real.pi.wallis
! leanprover-community/mathlib commit 980755c33b9168bc82f774f665eaa27878140fac
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Analysis.SpecialFunctions.Integrals

/-! # The Wallis formula for Pi

This file establishes the Wallis product for `π` (`real.tendsto_prod_pi_div_two`). Our proof is
largely about analyzing the behaviour of the sequence `∫ x in 0..π, sin x ^ n` as `n → ∞`.
See: https://en.wikipedia.org/wiki/Wallis_product

The proof can be broken down into two pieces. The first step (carried out in
`analysis.special_functions.integrals`) is to use repeated integration by parts to obtain an
explicit formula for this integral, which is rational if `n` is odd and a rational multiple of `π`
if `n` is even.

The second step, carried out here, is to estimate the ratio
`∫ (x : ℝ) in 0..π, sin x ^ (2 * k + 1) / ∫ (x : ℝ) in 0..π, sin x ^ (2 * k)` and prove that
it converges to one using the squeeze theorem. The final product for `π` is obtained after some
algebraic manipulation.

## Main statements

* `real.wallis.W`: the product of the first `k` terms in Wallis' formula for `π`.
* `real.wallis.W_eq_integral_sin_pow_div_integral_sin_pow`: express `W n` as a ratio of integrals.
* `real.wallis.W_le` and `real.wallis.le_W`: upper and lower bounds for `W n`.
* `real.tendsto_prod_pi_div_two`: the Wallis product formula.
 -/

open Real Topology BigOperators Nat

open Filter Finset intervalIntegral

namespace Real

namespace Wallis

/-- The product of the first `k` terms in Wallis' formula for `π`. -/
noncomputable def w (k : ℕ) : ℝ :=
  ∏ i in range k, (2 * i + 2) / (2 * i + 1) * ((2 * i + 2) / (2 * i + 3))
#align real.wallis.W Real.Wallis.w

theorem w_succ (k : ℕ) :
    w (k + 1) = w k * ((2 * k + 2) / (2 * k + 1) * ((2 * k + 2) / (2 * k + 3))) :=
  prod_range_succ _ _
#align real.wallis.W_succ Real.Wallis.w_succ

theorem w_pos (k : ℕ) : 0 < w k := by
  induction' k with k hk
  · unfold W
    simp
  · rw [W_succ]
    refine' mul_pos hk (mul_pos (div_pos _ _) (div_pos _ _)) <;> positivity
#align real.wallis.W_pos Real.Wallis.w_pos

theorem w_eq_factorial_ratio (n : ℕ) : w n = 2 ^ (4 * n) * n ! ^ 4 / ((2 * n)! ^ 2 * (2 * n + 1)) :=
  by
  induction' n with n IH
  ·
    simp only [W, prod_range_zero, Nat.factorial_zero, MulZeroClass.mul_zero, pow_zero,
      algebraMap.coe_one, one_pow, mul_one, algebraMap.coe_zero, zero_add, div_self, Ne.def,
      one_ne_zero, not_false_iff]
  · unfold W at IH⊢
    rw [prod_range_succ, IH, _root_.div_mul_div_comm, _root_.div_mul_div_comm]
    refine' (div_eq_div_iff _ _).mpr _
    any_goals exact ne_of_gt (by positivity)
    simp_rw [Nat.mul_succ, Nat.factorial_succ, pow_succ]
    push_cast
    ring_nf
#align real.wallis.W_eq_factorial_ratio Real.Wallis.w_eq_factorial_ratio

theorem w_eq_integral_sin_pow_div_integral_sin_pow (k : ℕ) :
    (π / 2)⁻¹ * w k = (∫ x : ℝ in 0 ..π, sin x ^ (2 * k + 1)) / ∫ x : ℝ in 0 ..π, sin x ^ (2 * k) :=
  by
  rw [integral_sin_pow_even, integral_sin_pow_odd, mul_div_mul_comm, ← prod_div_distrib, inv_div]
  simp_rw [div_div_div_comm, div_div_eq_mul_div, mul_div_assoc]
  rfl
#align real.wallis.W_eq_integral_sin_pow_div_integral_sin_pow Real.Wallis.w_eq_integral_sin_pow_div_integral_sin_pow

theorem w_le (k : ℕ) : w k ≤ π / 2 :=
  by
  rw [← div_le_one pi_div_two_pos, div_eq_inv_mul]
  rw [W_eq_integral_sin_pow_div_integral_sin_pow, div_le_one (integral_sin_pow_pos _)]
  apply integral_sin_pow_succ_le
#align real.wallis.W_le Real.Wallis.w_le

theorem le_w (k : ℕ) : ((2 : ℝ) * k + 1) / (2 * k + 2) * (π / 2) ≤ w k :=
  by
  rw [← le_div_iff pi_div_two_pos, div_eq_inv_mul (W k) _]
  rw [W_eq_integral_sin_pow_div_integral_sin_pow, le_div_iff (integral_sin_pow_pos _)]
  convert integral_sin_pow_succ_le (2 * k + 1)
  rw [integral_sin_pow (2 * k)]
  simp only [sin_zero, zero_pow', Ne.def, Nat.succ_ne_zero, not_false_iff, MulZeroClass.zero_mul,
    sin_pi, tsub_zero, Nat.cast_mul, Nat.cast_bit0, algebraMap.coe_one, zero_div, zero_add]
#align real.wallis.le_W Real.Wallis.le_w

theorem tendsto_w_nhds_pi_div_two : Tendsto w atTop (𝓝 <| π / 2) :=
  by
  refine' tendsto_of_tendsto_of_tendsto_of_le_of_le _ tendsto_const_nhds le_W W_le
  have : 𝓝 (π / 2) = 𝓝 ((1 - 0) * (π / 2)) := by rw [sub_zero, one_mul]
  rw [this]
  refine' tendsto.mul _ tendsto_const_nhds
  have h : ∀ n : ℕ, ((2 : ℝ) * n + 1) / (2 * n + 2) = 1 - 1 / (2 * n + 2) :=
    by
    intro n
    rw [sub_div' _ _ _
        (ne_of_gt
          (add_pos_of_nonneg_of_pos (mul_nonneg (two_pos : 0 < (2 : ℝ)).le (Nat.cast_nonneg _))
            two_pos)),
      one_mul]
    congr 1
    ring
  simp_rw [h]
  refine' (tendsto_const_nhds.div_at_top _).const_sub _
  refine' tendsto.at_top_add _ tendsto_const_nhds
  exact tendsto_coe_nat_at_top_at_top.const_mul_at_top two_pos
#align real.wallis.tendsto_W_nhds_pi_div_two Real.Wallis.tendsto_w_nhds_pi_div_two

end Wallis

end Real

/-- Wallis' product formula for `π / 2`. -/
theorem Real.tendsto_prod_pi_div_two :
    Tendsto (fun k => ∏ i in range k, ((2 : ℝ) * i + 2) / (2 * i + 1) * ((2 * i + 2) / (2 * i + 3)))
      atTop (𝓝 (π / 2)) :=
  Real.Wallis.tendsto_w_nhds_pi_div_two
#align real.tendsto_prod_pi_div_two Real.tendsto_prod_pi_div_two


/-
Copyright (c) 2021 OpenAI. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kunhao Zheng, Stanislas Polu, David Renshaw, OpenAI GPT-f
-/
import mathzoo.imports.miniF2F

open_locale nat rat real big_operators topological_space

theorem mathd_numbertheory_229 :
  (5^30) % 7 = 1 :=
begin
  have five_to_thirty_is_one :
    (5^30 : zmod 7) = 1 :=
  begin
    have five_to_the_six_is_one : (5^6 : zmod 7) = 1, by dec_trivial,
    have break_power : (5^30 : zmod 7) = (5^6)^5, by norm_num,
    rw break_power,
    rw five_to_the_six_is_one,
    norm_num,
  end,
  change 5^30 ≡ 1 [MOD 7],
  rw ←zmod.eq_iff_modeq_nat,
  exact_mod_cast five_to_thirty_is_one,
end


import ..lectures.love02_backward_proofs_demo

/-! # LoVe Exercise 2: Backward Proofs -/

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

namespace backward_proofs

/-! ## Question 1: Connectives and Quantifiers

1.1. Carry out the following proofs using basic tactics.

Hint: Some strategies for carrying out such proofs are described at the end of
Section 2.3 in the Hitchhiker's Guide. -/

lemma I (a : Prop) :
  a → a :=
sorry

lemma K (a b : Prop) :
  a → b → b :=
sorry

lemma C (a b c : Prop) :
  (a → b → c) → b → a → c :=
sorry

lemma proj_1st (a : Prop) :
  a → a → a :=
sorry

/-! Please give a different answer than for `proj_1st`: -/

lemma proj_2nd (a : Prop) :
  a → a → a :=
sorry

lemma some_nonsense (a b c : Prop) :
  (a → b → c) → a → (a → c) → b → c :=
sorry

/-! 1.2. Prove the contraposition rule using basic tactics. -/

lemma contrapositive (a b : Prop) :
  (a → b) → ¬ b → ¬ a :=
sorry

/-! 1.3. Prove the distributivity of `∀` over `∧` using basic tactics.

Hint: This exercise is tricky, especially the right-to-left direction. Some
forward reasoning, like in the proof of `and_swap₂` in the lecture, might be
necessary. -/

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

end backward_proofs

namespace existentials

/-! ## Question 2: Existentials

In this section, you'll practice using `exists.intro` and `exists.elim`.
-/

#check @exists.intro
#check @exists.elim

/-! 2.1. Prove that we can "flip" the existential and universal quantifiers.
(Note that this only works in one direction!)
-/

lemma website_icon {α β : Sort _} {p : α → β → Prop} :
  (∃ a, ∀ b, p a b) → (∀ b, ∃ a, p a b) :=
sorry

/-! 2.2 Prove, using the definition of `isEven` below, that adding two to an
even number gives another even number.
-/

-- Here is a definition and helper lemma for exercise 2.2
def isEven (n : ℕ) := ∃ k, n = 2 * k
lemma add_two_eq (m n : ℕ) : m = n → m + 2 = n + 2 :=
congr_arg (λ x, x + 2)

-- You may also find the following lemmas helpful:
#check mul_add
#check mul_one

lemma even_add_two : ∀ n : ℕ, isEven n → isEven (n + 2) :=
sorry

/- 2.3. Prove that two consecutive natural numbers cannot be even.

Hints:
* You can use `and.elim` to extract the two sides of a conjunction.
* `eq.symm` reverses the sides of an equality.
* If you have a hypothesis `h : a = b` and want to replace `b` with `a` in your
  goal (instead of `a` with `b`), use `rw ←h` (note the `←`).
-/
axiom no_odd_doubles : ∀ (n : ℕ), ¬ ∃ (m : ℕ), 2 * n = 2 * m + 1

lemma no_consecutive_evens (n : ℕ) : ¬(isEven n ∧ isEven (n + 1)) :=
sorry

end existentials

namespace backward_proofs

/-! ## Question 3: Natural Numbers

3.1. Prove the following recursive equations on the first argument of the
`mul` operator defined in lecture 1. -/

#check mul

lemma mul_zero (n : ℕ) :
  mul 0 n = 0 :=
sorry

lemma mul_succ (m n : ℕ) :
  mul (nat.succ m) n = add (mul m n) n :=
sorry

/-! 3.2. Prove commutativity and associativity of multiplication using the
`induction'` tactic. Choose the induction variable carefully. -/

lemma mul_comm (m n : ℕ) :
  mul m n = mul n m :=
sorry

lemma mul_assoc (l m n : ℕ) :
  mul (mul l m) n = mul l (mul m n) :=
sorry

/-! 3.3. Prove the symmetric variant of `mul_add` using `rw`. To apply
commutativity at a specific position, instantiate the rule by passing some
arguments (e.g., `mul_comm _ l`). -/

lemma add_mul (l m n : ℕ) :
  mul (add l m) n = add (mul n l) (mul n m) :=
sorry

/-! ## Question 4 (**optional**): Intuitionistic Logic

Intuitionistic logic is extended to classical logic by assuming a classical
axiom. There are several possibilities for the choice of axiom. In this
question, we are concerned with the logical equivalence of three different
axioms: -/

def excluded_middle : Prop :=
∀a : Prop, a ∨ ¬ a

def peirce : Prop :=
∀a b : Prop, ((a → b) → a) → a

def double_negation : Prop :=
∀a : Prop, (¬¬ a) → a

/-! For the proofs below, please avoid using lemmas from Lean's `classical`
namespace, as this would defeat the purpose of the exercise.

4.1 (**optional**). Prove the following implication using tactics.

Hint: You will need `or.elim` and `false.elim`. You can use
`rw excluded_middle` to unfold the definition of `excluded_middle`,
and similarly for `peirce`. -/

lemma peirce_of_em :
  excluded_middle → peirce :=
sorry

/-! 4.2 (**optional**). Prove the following implication using tactics. -/

lemma dn_of_peirce :
  peirce → double_negation :=
sorry

/-! We leave the remaining implication for the homework: -/

namespace sorry_lemmas

lemma em_of_dn :
  double_negation → excluded_middle :=
sorry

end sorry_lemmas

end backward_proofs

end LoVe


/-
COMP2009-ACE

Exercise 04 (Bool)

    This exercise has 2+1 parts.

    The first part is "logic chess" which has slightly different rules
    than logic poker but see below. The 2nd part asks you to define
    implication on booleans and prove it correct. You are not supposed
    to use classical logic for this exercise.

    Part 1+2 count for 80% of the mark (40% each). If you think you should get
    100% and are prepared to put a lot of extra effort in then try
    part 3. But you have been warned. They will be no help with this
    one.

    You are only allowed to use the tactics introduced in the lecture
    (i.e. assume, exact, apply, constructor, cases, left, right, have,
    trivial, existsi, reflexivity, rewrite, dsimp, contradiction, calc)

    Please only use the tactics in the way indicated in the script,
    otherwise you may lose upto 2 style points.

-/

namespace ex04

def bnot : bool → bool
| tt := ff
| ff := tt

def band : bool → bool → bool
| tt b := b
| ff b := ff

def bor : bool → bool → bool
| tt b := tt
| ff b := b

local notation x && y := band x y
local notation x || y := bor x y

/-
PART I (40%)
============
Logic chess

Unlike logic poker in logic chess there is no guessing. You either
prove the proposition or you prove its negation, for example if the
proposition is true, e.g.

chx0) ∀ x : bool, x=x

then you just go ahead and prove it -/

theorem chx0 : ∀ x : bool, x=x :=
begin
  assume x,
  reflexivity,
end

/- However, if the proposition is false, e.g.

chx1) ∀ x : bool, x ≠ x

then  you prove its negation -/

theorem chx1 : ¬ (∀ x : bool, x ≠ x) :=
begin
  assume h,
  have g : tt ≠ tt,
  apply h,
  apply g,
  reflexivity,
end

/-
For each of the following proposition either prove them or their negation.

ch01) ∀ x : bool, bnot (bnot x) = x
ch02) ∀ x:bool,∃ y:bool, x ≠ y
ch03) ∃ x:bool,∀ y:bool, x ≠ y
ch04) ∀ x y : bool, x=y ∨ x ≠ y
ch05) ∃ x:bool, x=bnot x
ch06) ∀ x y z : bool, x=y ∨ x=z ∨ y=z
ch07) ∀ y:bool, ∃ x:bool, y = bnot x
ch08) ∀ x y : bool, bnot x = bnot y → x=y
ch09) ∃ b : bool, ∀ y:bool, b && y = y
ch10) ∃ b : bool, ∀ y:bool, b && y = b
-/

-- insert answer here
theorem ch01 : ∀ x : bool, bnot (bnot x) = x  :=
begin
  assume x,
  cases x,
  refl,
  refl,
end

theorem ch02 : ∀ x:bool, ∃ y:bool, x ≠ y  :=
begin
  assume x,
  cases x,
  existsi tt,
  assume h,
  contradiction,
  existsi ff,
  assume h2,
  contradiction,
end

theorem ch03 : ¬ (∃ x:bool,∀ y:bool, x ≠ y)  :=
--to prove its negation
begin
  assume x,
  cases x with x h,
  apply h,
  reflexivity,
end

theorem ch04: ∀ x y : bool, x=y ∨ x ≠ y :=
begin
  assume x y,
  cases x,
  cases y,
  left,
  reflexivity,
  right,
  assume fftt,
  contradiction,

  cases y,
  right,
  assume ttff,
  contradiction,
  left,
  reflexivity,
end

theorem ch05: ¬ (∃ x:bool, x = bnot x) :=
--to prove negation
begin
  assume h,
  cases h with x h,
  cases x,
  dsimp[bnot] at h,
  contradiction,
  dsimp[bnot] at h,
  contradiction,
end

theorem ch06: ∀ x y z : bool, x=y ∨ x=z ∨ y=z :=
begin
  assume x y z,
  cases x,
  cases y,
  left,
  reflexivity,

  right,
  cases z,
  left,
  reflexivity,

  right,
  reflexivity,

  cases y,
  cases z,
  right,
  right,
  reflexivity,

  right,
  left,
  reflexivity,

  left,
  reflexivity,
end

theorem ch07: ∀ y:bool, ∃ x:bool, y = bnot x :=
begin
  assume y,
  cases y,
  existsi tt,
  reflexivity,
  existsi ff,
  reflexivity,
end

theorem ch08: ∀ x y : bool, bnot x = bnot y → x=y :=
begin
  assume x y,
  assume h,
  cases x,
  cases y,
  reflexivity,
  dsimp[bnot] at h,
  rewrite h,

  cases y,
  dsimp[bnot] at h,
  rewrite h,
  reflexivity,
end

theorem ch09: ∃ b : bool, ∀ y:bool, b && y = y :=
begin
  existsi tt,
  assume y,
  dsimp [(&&),band],
  refl,
end

theorem ch10: ∃ b : bool, ∀ y:bool, b && y = b :=
begin
  existsi ff,
  assume y,
  dsimp [(&&),band],
  refl,
end

/-
PART II (40%)
=============

Define the operation

implb :   bool → bool → bool

by pattern matching on bool and show that it corresponds to
implication on Prop, i.e. prove

theorem implb_ok : ∀ x y : bool , implb x y = tt ↔ (x = tt) → (y = tt)

-/

-- insert answer here
def implb : bool → bool → bool
| tt y := y
| ff y := tt

theorem implb_ok : ∀ x y : bool , implb x y = tt ↔ (x = tt) → (y = tt) :=
begin
  assume x y,
  constructor,
  assume h1,
  assume xtt,
  cases x,
  contradiction,

  dsimp[implb] at h1,
  exact h1,

  assume xttytt,
  cases x,
  dsimp[implb],
  reflexivity,

  dsimp[implb],
  apply xttytt,
  reflexivity,
end

/-
PART III (20%)
==============

(only for the criminally insane)

Prove the following theorem about functions  bool → bool.

Hint: Use "cases e:b" on a boolean expression b. This also adds
equations "e:b = tt" and "e:b = ff" to the cases.

-/

theorem weird : ∀ f : bool → bool, ∀ x:bool, f (f (f x)) = f x :=
begin
  assume f,
  assume x,
  cases x,
  cases g : f ff,
  rewrite g,
  exact g,

  cases ftt: f tt,
  exact g,

  exact ftt,

  cases o : f (f (f tt)),
  cases r : f tt,
  reflexivity,
  rewrite ← o,
  rewrite r,
  rewrite r,
  exact r,

  cases t : f tt,
  rewrite ← o,
  rewrite t,
  cases w : f ff,
  exact w,

  rewrite t,
  reflexivity,
end

end ex04


/-
Copyright (c) 2020 Zhouhang Zhou. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Zhouhang Zhou
-/
import order.bounds
import data.set.intervals.image_preimage

/-!
# Intervals without endpoints ordering

In any decidable linear order `α`, we define the set of elements lying between two elements `a` and
`b` as `Icc (min a b) (max a b)`.

`Icc a b` requires the assumption `a ≤ b` to be meaningful, which is sometimes inconvenient. The
interval as defined in this file is always the set of things lying between `a` and `b`, regardless
of the relative order of `a` and `b`.

For real numbers, `Icc (min a b) (max a b)` is the same as `segment ℝ a b`.

## Notation

We use the localized notation `[a, b]` for `interval a b`. One can open the locale `interval` to
make the notation available.

-/

universe u
open_locale pointwise

namespace set

section linear_order

variables {α : Type u} [linear_order α] {a a₁ a₂ b b₁ b₂ c x : α}

/-- `interval a b` is the set of elements lying between `a` and `b`, with `a` and `b` included. -/
def interval (a b : α) := Icc (min a b) (max a b)

localized "notation `[`a `, ` b `]` := set.interval a b" in interval

@[simp] lemma interval_of_le (h : a ≤ b) : [a, b] = Icc a b :=
by rw [interval, min_eq_left h, max_eq_right h]

@[simp] lemma interval_of_ge (h : b ≤ a) : [a, b] = Icc b a :=
by { rw [interval, min_eq_right h, max_eq_left h] }

lemma interval_swap (a b : α) : [a, b] = [b, a] :=
or.elim (le_total a b) (by simp {contextual := tt}) (by simp {contextual := tt})

lemma interval_of_lt (h : a < b) : [a, b] = Icc a b :=
interval_of_le (le_of_lt h)

lemma interval_of_gt (h : b < a) : [a, b] = Icc b a :=
interval_of_ge (le_of_lt h)

lemma interval_of_not_le (h : ¬ a ≤ b) : [a, b] = Icc b a :=
interval_of_gt (lt_of_not_ge h)

lemma interval_of_not_ge (h : ¬ b ≤ a) : [a, b] = Icc a b :=
interval_of_lt (lt_of_not_ge h)

@[simp] lemma interval_self : [a, a] = {a} :=
set.ext $ by simp [le_antisymm_iff, and_comm]

@[simp] lemma nonempty_interval : set.nonempty [a, b] :=
by { simp only [interval, min_le_iff, le_max_iff, nonempty_Icc], left, left, refl }

@[simp] lemma left_mem_interval : a ∈ [a, b] :=
by { rw [interval, mem_Icc], exact ⟨min_le_left _ _, le_max_left _ _⟩ }

@[simp] lemma right_mem_interval : b ∈ [a, b] :=
by { rw interval_swap, exact left_mem_interval }

lemma Icc_subset_interval : Icc a b ⊆ [a, b] :=
by { assume x h, rwa interval_of_le, exact le_trans h.1 h.2 }

lemma Icc_subset_interval' : Icc b a ⊆ [a, b] :=
by { rw interval_swap, apply Icc_subset_interval }

lemma mem_interval_of_le (ha : a ≤ x) (hb : x ≤ b) : x ∈ [a, b] :=
Icc_subset_interval ⟨ha, hb⟩

lemma mem_interval_of_ge (hb : b ≤ x) (ha : x ≤ a) : x ∈ [a, b] :=
Icc_subset_interval' ⟨hb, ha⟩

lemma not_mem_interval_of_lt (ha : c < a) (hb : c < b) : c ∉ interval a b :=
not_mem_Icc_of_lt $ lt_min_iff.mpr ⟨ha, hb⟩

lemma not_mem_interval_of_gt (ha : a < c) (hb : b < c) : c ∉ interval a b :=
not_mem_Icc_of_gt $ max_lt_iff.mpr ⟨ha, hb⟩

lemma interval_subset_interval (h₁ : a₁ ∈ [a₂, b₂]) (h₂ : b₁ ∈ [a₂, b₂]) : [a₁, b₁] ⊆ [a₂, b₂] :=
Icc_subset_Icc (le_min h₁.1 h₂.1) (max_le h₁.2 h₂.2)

lemma interval_subset_Icc (ha : a₁ ∈ Icc a₂ b₂) (hb : b₁ ∈ Icc a₂ b₂) : [a₁, b₁] ⊆ Icc a₂ b₂ :=
Icc_subset_Icc (le_min ha.1 hb.1) (max_le ha.2 hb.2)

lemma interval_subset_interval_iff_mem : [a₁, b₁] ⊆ [a₂, b₂] ↔ a₁ ∈ [a₂, b₂] ∧ b₁ ∈ [a₂, b₂] :=
iff.intro (λh, ⟨h left_mem_interval, h right_mem_interval⟩) (λ h, interval_subset_interval h.1 h.2)

lemma interval_subset_interval_iff_le :
  [a₁, b₁] ⊆ [a₂, b₂] ↔ min a₂ b₂ ≤ min a₁ b₁ ∧ max a₁ b₁ ≤ max a₂ b₂ :=
by { rw [interval, interval, Icc_subset_Icc_iff], exact min_le_max }

lemma interval_subset_interval_right (h : x ∈ [a, b]) : [x, b] ⊆ [a, b] :=
interval_subset_interval h right_mem_interval

lemma interval_subset_interval_left (h : x ∈ [a, b]) : [a, x] ⊆ [a, b] :=
interval_subset_interval left_mem_interval h

/-- A sort of triangle inequality. -/
lemma interval_subset_interval_union_interval : [a, c] ⊆ [a, b] ∪ [b, c] :=
begin
  rintro x hx,
  obtain hac | hac := le_total a c,
  { rw interval_of_le hac at hx,
    obtain hb | hb := le_total x b,
    { exact or.inl (mem_interval_of_le hx.1 hb) },
    { exact or.inr (mem_interval_of_le hb hx.2) } },
  { rw interval_of_ge hac at hx,
    obtain hb | hb := le_total x b,
    { exact or.inr (mem_interval_of_ge hx.1 hb) },
    { exact or.inl (mem_interval_of_ge hb hx.2) } }
end

lemma bdd_below_bdd_above_iff_subset_interval (s : set α) :
  bdd_below s ∧ bdd_above s ↔ ∃ a b, s ⊆ [a, b] :=
begin
  rw [bdd_below_bdd_above_iff_subset_Icc],
  split,
  { rintro ⟨a, b, h⟩, exact ⟨a, b, λ x hx, Icc_subset_interval (h hx)⟩ },
  { rintro ⟨a, b, h⟩, exact ⟨min a b, max a b, h⟩ }
end

/-- The open-closed interval with unordered bounds. -/
def interval_oc : α → α → set α := λ a b, Ioc (min a b) (max a b)

-- Below is a capital iota
localized "notation `Ι` := set.interval_oc" in interval

lemma interval_oc_of_le (h : a ≤ b) : Ι a b = Ioc a b :=
by simp [interval_oc, h]

lemma interval_oc_of_lt (h : b < a) : Ι a b = Ioc b a :=
by simp [interval_oc, le_of_lt h]

lemma forall_interval_oc_iff  {P : α → Prop} :
  (∀ x ∈ Ι a b, P x) ↔ (∀ x ∈ Ioc a b, P x) ∧ (∀ x ∈ Ioc b a, P x) :=
by { dsimp [interval_oc], cases le_total a b with hab hab ; simp [hab] }

end linear_order

open_locale interval

section ordered_add_comm_group

variables {α : Type u} [linear_ordered_add_comm_group α] (a b c x y : α)

@[simp] lemma preimage_const_add_interval : (λ x, a + x) ⁻¹' [b, c] = [b - a, c - a] :=
by simp only [interval, preimage_const_add_Icc, min_sub_sub_right, max_sub_sub_right]

@[simp] lemma preimage_add_const_interval : (λ x, x + a) ⁻¹' [b, c] = [b - a, c - a] :=
by simpa only [add_comm] using preimage_const_add_interval a b c

@[simp] lemma preimage_neg_interval : - [a, b] = [-a, -b] :=
by simp only [interval, preimage_neg_Icc, min_neg_neg, max_neg_neg]

@[simp] lemma preimage_sub_const_interval : (λ x, x - a) ⁻¹' [b, c] = [b + a, c + a] :=
by simp [sub_eq_add_neg]

@[simp] lemma preimage_const_sub_interval : (λ x, a - x) ⁻¹' [b, c] = [a - b, a - c] :=
by { rw [interval, interval, preimage_const_sub_Icc],
  simp only [sub_eq_add_neg, min_add_add_left, max_add_add_left, min_neg_neg, max_neg_neg], }

@[simp] lemma image_const_add_interval : (λ x, a + x) '' [b, c] = [a + b, a + c] :=
by simp [add_comm]

@[simp] lemma image_add_const_interval : (λ x, x + a) '' [b, c] = [b + a, c + a] :=
by simp

@[simp] lemma image_const_sub_interval : (λ x, a - x) '' [b, c] = [a - b, a - c] :=
by simp [sub_eq_add_neg, image_comp (λ x, a + x) (λ x, -x)]

@[simp] lemma image_sub_const_interval : (λ x, x - a) '' [b, c] = [b - a, c - a] :=
by simp [sub_eq_add_neg, add_comm]

lemma image_neg_interval : has_neg.neg '' [a, b] = [-a, -b] := by simp

variables {a b c x y}

/-- If `[x, y]` is a subinterval of `[a, b]`, then the distance between `x` and `y`
is less than or equal to that of `a` and `b` -/
lemma abs_sub_le_of_subinterval (h : [x, y] ⊆ [a, b]) : |y - x| ≤ |b - a| :=
begin
  rw [← max_sub_min_eq_abs, ← max_sub_min_eq_abs],
  rw [interval_subset_interval_iff_le] at h,
  exact sub_le_sub h.2 h.1,
end

/-- If `x ∈ [a, b]`, then the distance between `a` and `x` is less than or equal to
that of `a` and `b`  -/
lemma abs_sub_left_of_mem_interval (h : x ∈ [a, b]) : |x - a| ≤ |b - a| :=
abs_sub_le_of_subinterval (interval_subset_interval_left h)

/-- If `x ∈ [a, b]`, then the distance between `x` and `b` is less than or equal to
that of `a` and `b`  -/
lemma abs_sub_right_of_mem_interval (h : x ∈ [a, b]) : |b - x| ≤ |b - a| :=
abs_sub_le_of_subinterval (interval_subset_interval_right h)

end ordered_add_comm_group

section linear_ordered_field

variables {k : Type u} [linear_ordered_field k] {a : k}

@[simp] lemma preimage_mul_const_interval (ha : a ≠ 0) (b c : k) :
  (λ x, x * a) ⁻¹' [b, c] = [b / a, c / a] :=
(lt_or_gt_of_ne ha).elim
  (λ ha, by simp [interval, ha, ha.le, min_div_div_right_of_nonpos, max_div_div_right_of_nonpos])
  (λ (ha : 0 < a), by simp [interval, ha, ha.le, min_div_div_right, max_div_div_right])

@[simp] lemma preimage_const_mul_interval (ha : a ≠ 0) (b c : k) :
  (λ x, a * x) ⁻¹' [b, c] = [b / a, c / a] :=
by simp only [← preimage_mul_const_interval ha, mul_comm]

@[simp] lemma preimage_div_const_interval (ha : a ≠ 0) (b c : k) :
  (λ x, x / a) ⁻¹' [b, c] = [b * a, c * a] :=
by simp only [div_eq_mul_inv, preimage_mul_const_interval (inv_ne_zero ha), inv_inv₀]

@[simp] lemma image_mul_const_interval (a b c : k) : (λ x, x * a) '' [b, c] = [b * a, c * a] :=
if ha : a = 0 then by simp [ha] else
calc (λ x, x * a) '' [b, c] = (λ x, x * a⁻¹) ⁻¹' [b, c] :
  (units.mk0 a ha).mul_right.image_eq_preimage _
... = (λ x, x / a) ⁻¹' [b, c] : by simp only [div_eq_mul_inv]
... = [b * a, c * a] : preimage_div_const_interval ha _ _

@[simp] lemma image_const_mul_interval (a b c : k) : (λ x, a * x) '' [b, c] = [a * b, a * c] :=
by simpa only [mul_comm] using image_mul_const_interval a b c

@[simp] lemma image_div_const_interval (a b c : k) : (λ x, x / a) '' [b, c] = [b / a, c / a] :=
by simp only [div_eq_mul_inv, image_mul_const_interval]

end linear_ordered_field

end set


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

import linear_algebra.free_module.finite.rank

/-!
# Rank of matrices

The rank of a matrix `A` is defined to be the rank of range of the linear map corresponding to `A`.
This definition does not depend on the choice of basis, see `matrix.rank_eq_finrank_range_to_lin`.

## Main declarations

* `matrix.rank`: the rank of a matrix

## TODO

* Show that `matrix.rank` is equal to the row-rank and column-rank
* Generalize away from fields

-/

open_locale matrix

namespace matrix

open finite_dimensional

variables {m n o K : Type*} [m_fin : fintype m] [fintype n] [fintype o]
variables [decidable_eq n] [decidable_eq o] [field K]
variables (A : matrix m n K)

/-- The rank of a matrix is the rank of its image. -/
noncomputable def rank : ℕ := finrank K A.to_lin'.range

@[simp] lemma rank_one : rank (1 : matrix n n K) = fintype.card n :=
by rw [rank, to_lin'_one, linear_map.range_id, finrank_top, module.free.finrank_pi]

@[simp] lemma rank_zero : rank (0 : matrix n n K) = 0 :=
by rw [rank, linear_equiv.map_zero, linear_map.range_zero, finrank_bot]

lemma rank_le_card_width : A.rank ≤ fintype.card n :=
begin
  convert nat.le_of_add_le_left (A.to_lin'.finrank_range_add_finrank_ker).le,
  exact (module.free.finrank_pi K).symm,
end

lemma rank_le_width {m n : ℕ} (A : matrix (fin m) (fin n) K) : A.rank ≤ n :=
A.rank_le_card_width.trans $ (fintype.card_fin n).le

lemma rank_mul_le (B : matrix n o K) : (A ⬝ B).rank ≤ A.rank :=
begin
  refine linear_map.finrank_le_finrank_of_injective (submodule.of_le_injective _),
  rw [to_lin'_mul],
  exact linear_map.range_comp_le_range _ _,
end

lemma rank_unit (A : (matrix n n K)ˣ) :
  (A : matrix n n K).rank = fintype.card n :=
begin
  refine le_antisymm (rank_le_card_width A) _,
  have := rank_mul_le (A : matrix n n K) (↑A⁻¹ : matrix n n K),
  rwa [← mul_eq_mul, ← units.coe_mul, mul_inv_self, units.coe_one, rank_one] at this,
end

lemma rank_of_is_unit (A : matrix n n K) (h : is_unit A) :
  A.rank = fintype.card n :=
by { obtain ⟨A, rfl⟩ := h, exact rank_unit A }

include m_fin

lemma rank_eq_finrank_range_to_lin
  {M₁ M₂ : Type*} [add_comm_group M₁] [add_comm_group M₂]
  [module K M₁] [module K M₂] (v₁ : basis m K M₁) (v₂ : basis n K M₂) :
  A.rank = finrank K (to_lin v₂ v₁ A).range :=
begin
  let e₁ := (pi.basis_fun K m).equiv v₁ (equiv.refl _),
  let e₂ := (pi.basis_fun K n).equiv v₂ (equiv.refl _),
  have range_e₂ : (e₂ : (n → K) →ₗ[K] M₂).range = ⊤,
  { rw linear_map.range_eq_top, exact e₂.surjective },
  refine linear_equiv.finrank_eq (e₁.of_submodules _ _ _),
  rw [← linear_map.range_comp, ← linear_map.range_comp_of_range_eq_top (to_lin v₂ v₁ A) range_e₂],
  congr' 1,
  apply linear_map.pi_ext', rintro i, apply linear_map.ext_ring,
  have aux₁ := to_lin_self (pi.basis_fun K n) (pi.basis_fun K m) A i,
  have aux₂ := basis.equiv_apply (pi.basis_fun K n) i v₂,
  rw [to_lin_eq_to_lin'] at aux₁,
  rw [pi.basis_fun_apply, linear_map.coe_std_basis] at aux₁ aux₂,
  simp only [linear_map.comp_apply, e₁, e₂, linear_equiv.coe_coe, equiv.refl_apply, aux₁, aux₂,
    linear_map.coe_single, to_lin_self, linear_equiv.map_sum, linear_equiv.map_smul,
    basis.equiv_apply],
end

lemma rank_le_card_height : A.rank ≤ fintype.card m :=
(submodule.finrank_le _).trans (module.free.finrank_pi K).le

omit m_fin

lemma rank_le_height {m n : ℕ} (A : matrix (fin m) (fin n) K) : A.rank ≤ m :=
A.rank_le_card_height.trans $ (fintype.card_fin m).le

end matrix


/-
Copyright (c) 2020 Scott Morrison. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Scott Morrison
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.tactic.apply_fun
import Mathlib.ring_theory.matrix_algebra
import Mathlib.ring_theory.polynomial_algebra
import Mathlib.linear_algebra.nonsingular_inverse
import Mathlib.tactic.squeeze
import Mathlib.PostPort

universes u w

namespace Mathlib

/-!
# Characteristic polynomials and the Cayley-Hamilton theorem

We define characteristic polynomials of matrices and
prove the Cayley–Hamilton theorem over arbitrary commutative rings.

## Main definitions

* `char_poly` is the characteristic polynomial of a matrix.

## Implementation details

We follow a nice proof from http://drorbn.net/AcademicPensieve/2015-12/CayleyHamilton.pdf
-/

/--
The "characteristic matrix" of `M : matrix n n R` is the matrix of polynomials $t I - M$.
The determinant of this matrix is the characteristic polynomial.
-/
def char_matrix {R : Type u} [comm_ring R] {n : Type w} [DecidableEq n] [fintype n] (M : matrix n n R) : matrix n n (polynomial R) :=
  coe_fn (matrix.scalar n) polynomial.X - coe_fn (ring_hom.map_matrix polynomial.C) M

@[simp] theorem char_matrix_apply_eq {R : Type u} [comm_ring R] {n : Type w} [DecidableEq n] [fintype n] (M : matrix n n R) (i : n) : char_matrix M i i = polynomial.X - coe_fn polynomial.C (M i i) := sorry

@[simp] theorem char_matrix_apply_ne {R : Type u} [comm_ring R] {n : Type w} [DecidableEq n] [fintype n] (M : matrix n n R) (i : n) (j : n) (h : i ≠ j) : char_matrix M i j = -coe_fn polynomial.C (M i j) := sorry

theorem mat_poly_equiv_char_matrix {R : Type u} [comm_ring R] {n : Type w} [DecidableEq n] [fintype n] (M : matrix n n R) : coe_fn mat_poly_equiv (char_matrix M) = polynomial.X - coe_fn polynomial.C M := sorry

/--
The characteristic polynomial of a matrix `M` is given by $\det (t I - M)$.
-/
def char_poly {R : Type u} [comm_ring R] {n : Type w} [DecidableEq n] [fintype n] (M : matrix n n R) : polynomial R :=
  matrix.det (char_matrix M)

/--
The Cayley-Hamilton theorem, that the characteristic polynomial of a matrix,
applied to the matrix itself, is zero.

This holds over any commutative ring.
-/
-- This proof follows http://drorbn.net/AcademicPensieve/2015-12/CayleyHamilton.pdf

theorem aeval_self_char_poly {R : Type u} [comm_ring R] {n : Type w} [DecidableEq n] [fintype n] (M : matrix n n R) : coe_fn (polynomial.aeval M) (char_poly M) = 0 := sorry


import tactic
import data.nat.prime
import topology.continuous_function.compact

/-!

∀
∀ x : ℕ, x = 37
-/

#check ∀ x : ℕ, x = 37

lemma easy : 2 + 2 = 4 :=
begin
  norm_num,
end

#check 2 + 2 = 4
#check easy

/-!
# Infinte unions

Want to do ⋃ A₁, A₂, ... are all sets
and i is a natural
-/

variable (X : Type)
variables (U V : set X)

#check U
#check U ∪ V
#print notation ∪
#print notation ⋃

variable (A : ℕ → set X)

#check A(0)
 -- want to take union of all the A(i)
#check ⋃ (i : ℕ), A i

variable (B : ℕ → ℕ → set X)

-- set_option pp.notation false (to disable unicode notation)
#check ⋃ (i : ℕ), ⋃ (j : ℕ), B i j
#check ⋃ (i : ℕ) (j : ℕ), B i j
#check ⋃ (i j : ℕ), B i j

#check nat.prime
#check nat.prime 6

def primes : Type := {n : ℕ // nat.prime n} -- // creates a dependent pair
def primes_set : set ℕ  := {n : ℕ | nat.prime n}

variables (x : primes)
#check x.1
#check x.2

def two : primes := ⟨2, begin
  exact nat.prime_two,
end⟩

example : 2 ∈ primes_set :=
begin
  unfold primes_set,
  dsimp, -- definition simplification
  exact nat.prime_two,
end

#check ⋃ (p: primes), A (p.1)
#check ⋃ (p ∈ primes_set), A p --- exciting new development
-- ⋃ (p ∈ S), ... works!

#check set.mem_bUnion

example (a : X) : a ∈ (⋃(i : ℕ), A i) ↔ ∃ j, a ∈ A j :=
begin
  exact set.mem_Union, -- key fact for unions
end

example (a : X) : a ∈ (⋃ (p ∈ primes_set), A p) ↔ ∃ p : ℕ, nat.prime p ∧ a ∈ A p :=
begin
  rw set.mem_Union,
  simp_rw set.mem_Union,
  split,
    --intro h,
    --cases h with i hi,
    --cases hi with h1 h2,
    --use i,
    --split,
    --  exact h1, exact h2,
    {
      rintro ⟨i, h1, h2⟩,
      exact ⟨i, h1, h2⟩
    },
--  unfold primes_set,
  {sorry}
end

/-!
## Second hour
Things to know

Well I'm now rep for the class

OpenAI have been in touch with Buzzard, and want to give money
for people who formalise olympid questions on the Xena dicord

Check on Twitter of OpenAI for link to blog

And Meta (Facebook) want our emai;l address to have a meeting where they show off
their extension for VSCode to guess the next line of a Lean

PDF where fine for the project

Can Buzzard share the projects for:
- next year students
- to the world

And NOW
## Topology
check the sheets of the formalising course (section 7)

 -/

-- Let X be a topologycal space
variables (Z : Type) [topological_space X]


import data.set
open set
/-
NOTE: The final exam is comprehensive, but this practice
exam is not. In addition to working through this practice
exam, you should revisit the exam1 and exam2 practice
exams.
-/

/- SETS -/
namespace sets

/-
1.

Prove that 1 is in the set {1, 2, 3, 4, 5}.
-/

example: 1 ∈ ({1, 2, 3, 4, 5}: set ℕ) :=
begin
  right, right, right, right, left,
  exact rfl,
end

/-
2.

Prove that 1 is not in the set {2, 3, 4}.
-/

example: 1 ∉ ({2, 3, 4}: set ℕ) :=
begin
  assume pf_in,
  cases pf_in with one_is_4 pf_in,
    -- case with hypothesis 1 = 4
    have one_isnt_4: 1 ≠ 4 := dec_trivial,
    exact one_isnt_4 one_is_4,
    -- case with hypothesis 1 ∈ {2, 3}
    cases pf_in with one_is_3 pf_in,
      -- case with hypothesis 1 = 3
      have one_isnt_3: 1 ≠ 3 := dec_trivial,
      exact one_isnt_3 one_is_3,
      -- case with hypothesis 1 ∈ {2}
      cases pf_in with one_is_2 pf_in,
        -- case with hypothesis 1 = 2
        have one_isnt_2: 1 ≠ 2 := dec_trivial,
        exact one_isnt_2 one_is_2,
        -- case with hypothesis 1 ∈ ∅
        assumption
end

/-
3.

Prove that there does not exist any natural number that is
a member of the empty set of natural numbers.
-/

example: ¬(∃ n:ℕ, n ∈ (∅: set ℕ)) :=
begin
  assume pf_exists_n,
  apply exists.elim pf_exists_n,
  assume w pf_w_in_empty,
  assumption
end

/-
4.

Prove that the intersection of the sets of natural numbers
{1, 2} and {3, 4} is empty by showing that for all natural
numbers, they are not in the intersection of {1, 2} and
{3, 4}.
-/

#reduce 2 ∈ ({3, 4}: set ℕ)

example: ∀n: ℕ, n ∉ ({1, 2}: set ℕ) ∩ ({3, 4}: set ℕ) :=
begin
  intros n,
  assume pf_n_in_intersection,
  cases pf_n_in_intersection with pf_n_in_12 pf_n_in_34,
    cases pf_n_in_12 with pf_n_is_2 pf_n_in_1,
      rw pf_n_is_2 at pf_n_in_34,
      cases pf_n_in_34 with pf_2_is_4 pf_2_in_3,
        have pf_2_isnt_4: 2 ≠ 4 := dec_trivial,
        exact pf_2_isnt_4 pf_2_is_4,

        cases pf_2_in_3 with pf_2_is_3 pf_2_in_empty,
          have pf_2_isnt_3: 2 ≠ 3 := dec_trivial,
          exact pf_2_isnt_3 pf_2_is_3,

          assumption,

      cases pf_n_in_1 with pf_n_is_1 pf_n_in_empty,
        rw pf_n_is_1 at pf_n_in_34,
        cases pf_n_in_34 with pf_1_is_4 pf_1_in_3,
          have pf_1_isnt_4: 1 ≠ 4 := dec_trivial,
          exact pf_1_isnt_4 pf_1_is_4,

          cases pf_1_in_3 with pf_1_is_3 pf_1_in_empty,
            have pf_1_isnt_3: 1 ≠ 3 := dec_trivial,
            exact pf_1_isnt_3 pf_1_is_3,

            assumption,

        assumption,
end

/-
5.

Prove that 1 is in the difference between {1, 2, 3} and {3, 4}
-/

example: 1 ∈ ({1, 2, 3}: set ℕ) \ ({3, 4}: set ℕ) :=
begin
  split,
    right, right, left,
    exact rfl,

    assume pf_1_in_34,
    cases pf_1_in_34 with pf_1_is_4 pf_1_in_3,
      have pf_1_isnt_4: 1 ≠ 4 := dec_trivial,
      exact pf_1_isnt_4 pf_1_is_4,

      cases pf_1_in_3 with pf_1_is_3 pf_1_in_empty,
        have pf_1_isnt_3: 1 ≠ 3 := dec_trivial,
        exact pf_1_isnt_3 pf_1_is_3,

        assumption,
end

/-
6.

Prove that {2, 3} is a subset of the union of {1, 2} and {3, 4}
Note: use ⊆ not ⊂
-/

example: ({2, 3}: set ℕ) ⊆ ({1, 2}: set ℕ) ∪ ({3, 4}: set ℕ) :=
begin
  intros a,
  assume pf_a_in_23,
  cases pf_a_in_23 with pf_a_is_3 pf_a_in_2,
    apply or.inr,
    right, left, assumption,

    cases pf_a_in_2 with pf_a_is_2 pf_false,
      apply or.inl,
      left, assumption,

      exact false.elim pf_false,
end

/-
7.

Prove that {2, 3} is an element of the powerset of {1, 2, 3, 4}
-/

#reduce {2, 3} ∈ 𝒫({1, 2, 3, 4}: set ℕ)
-- ∀ ⦃a : ℕ⦄, a = 3 ∨ a = 2 ∨ false → a = 4 ∨ a = 3 ∨ a = 2 ∨ a = 1 ∨ false

example: {2, 3} ∈ 𝒫({1, 2, 3, 4}: set ℕ) :=
begin
  change ∀(a : ℕ), a = 3 ∨ a = 2 ∨ false → a = 4 ∨ a = 3 ∨ a = 2 ∨ a = 1 ∨ false,
  intros a,
  assume pf_a_in_23,
  cases pf_a_in_23 with pf_a_is_3 pf_a_in_2,
    right, left, assumption,

    cases pf_a_in_2 with pf_a_is_2 pf_false,
      right, right, left, assumption,
      exact false.elim pf_false,
end

end sets

/- RELATIONS -/
namespace relations

/-
1.

Prove that disjunction has the symmetric property
-/

example: symmetric or :=
begin
  unfold symmetric,
  intros x y,
  assume pf_x_or_y,
  cases pf_x_or_y with pf_x pf_y,
    exact or.inr pf_x,
    exact or.inl pf_y
end

/-
2.

Prove that conjunction is a subrelation of disjunction
-/

example: subrelation and or :=
begin
  unfold subrelation,
  intros x y,
  assume pf_x_and_y,
  have pf_x := and.elim_left pf_x_and_y,
  exact or.inl pf_x,
end

end relations

/-
===================================================
Inductive types, functions, properties, and proofs.
====================================================
-/

/-
1.

Create sub_mynat that subtracts two mynats n and m with
the condition that if m > n that it returns 0, otherwise
it returns n - m
Hint: 3 - 2 = 2 - 1 = 1 - 0
-/

namespace mynat

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

def sub_mynat: mynat → mynat → mynat
| n mynat.zero := n
| mynat.zero m := mynat.zero
| (mynat.succ n') (mynat.succ m') :=
    sub_mynat n' m'

/-
2.

Prove that ∀ n, sub_mynat n n = 0.
-/

example: ∀(n: mynat), sub_mynat n n = mynat.zero :=
begin
  intros n,
  induction n with n' h,
    -- base case
    simp [sub_mynat],

    simp [sub_mynat],
    assumption
end

/-
3.

We give you a copy of the add_mynat function
below. Show that our definition of add_mynat
is associative.
-/

def add_mynat: mynat → mynat → mynat
| mynat.zero m := m
| (mynat.succ n') m :=
    mynat.succ (add_mynat n' m)

example: ∀(a b c: mynat),
  add_mynat a (add_mynat b c) = add_mynat (add_mynat a b) c :=
begin
  intros a b c,
  induction a with a' h,
    -- base case
    simp [add_mynat],

    have h': add_mynat (mynat.succ a') (add_mynat b c) =
      mynat.succ (add_mynat a' (add_mynat b c)) :=
        begin
          simp [add_mynat],
        end,

    have h'': add_mynat (mynat.succ a') b =
      mynat.succ (add_mynat a' b) :=
        begin
          simp [add_mynat],
        end,

    rw h',
    rw h'',

    have h''': add_mynat (mynat.succ (add_mynat a' b)) c =
      mynat.succ (add_mynat (add_mynat a' b) c):=
        begin
          simp [add_mynat],
        end,

    rw h''',
    rw h,
end

end mynat

/-
Simple inductive types.
-----------------------
-/

/-
3.

Define a new type called day having
these seven constant constructors:
sunday, monday, tuesday, wednesday,
thursday, friday, saturday. We will
interpret these constructors as
representing the actual days of the
week. Write your code below here now.
-/

inductive day : Type
| sunday
| monday
| tuesday
| wednesday
| thursday
| friday
| saturday

-- We suggest you open the day namespace

open day

/-
4.

Define a function, nextday : day → day,
that, when given a value, d : day, returns
the day value that represents the next day
of the week. Hint: either write a function
using a match d with ... end, or, if you
can use a tactic script (to write code!).
Use cases d and for each possible case of
d, specify the exact day to be returned in
that case. For extra credit do it both ways
and write a single sentence to describe how
these two representations correspond.
-/

def nextday (d : day) : day :=
begin
  cases d,
    exact monday,
    exact tuesday,
    exact wednesday,
    exact thursday,
    exact friday,
    exact saturday,
    exact sunday,
end

def nextday' (d : day) :=
match d with
| sunday := monday
| monday := tuesday
| tuesday := wednesday
| wednesday := thursday
| thursday := friday
| friday := saturday
| saturday := sunday
end

/-
5.

Formalize and prove the proposition that
for any day, d, if you apply nextday to d
then nextday to the result, seven times,
the result is that very same d.
-/

example : ∀ d : day,
nextday ( nextday ( nextday (nextday (nextday (nextday (nextday d)))))) = d :=
begin
  assume d,
  cases d,
  repeat { apply rfl }, -- don't write it 7 times!
end

/-
Recursive (nested) inductive types.
-----------------------------------
-/

/-
6.

Give an inductive type definition for nat_list,
so that values of this type represent lists of
values of type ℕ.

While there's an infinity of such lists, we can
cleverly define the set inductively with just two
rules.

First, there is an empty list of natural numbers. We will represent it with the constant constructor,
nil_nat. Make it one of the nat_list constructors.

Second, if we are given a nat_list value, l, and
a ℕ value, e : ℕ, we can always construct a list
that is one longer than by prepending e to l. To
do this, define a constructor called nat_cons. It take arguments, e : ℕ and l : ₤st_nat, and yield a
nat_list.
-/

inductive nat_list : Type
| nat_nil : nat_list
| nat_cons : ℕ → nat_list → nat_list

-- we suggest you open the nat_list namespace

open nat_list

/-
7.

Define X to be the nat_list value that we
interpret as representing the empty list of
natural numbers, []; Y to be the nat_list
value that represents the list, [1]; and
finally Z to represent the list, [2, 1].
-/

def X := nat_nil
def Y := (nat_cons 1 nat_nil)
def Z := (nat_cons 2 Y)

/-
8.

Define a function (it will be recursive)
that takes any nat_list value and that
returns its length (a natural number that
indicates how many values are in the list).
Call the function, length.

The challenge is to see the recursion in
the definition of the length of a list.
Hint questions: What is the length of the
empty list? What is the length of a list
that is not empty, and that thus must be
of the form (nat_cons h t), where h is the
value at the head of the list and t is the
rest of the list.
-/

def length : nat_list → ℕ
| nat_nil := 0
| (nat_cons h t) := 1 + length t

/-
The stuff that follows is challenging
Get solid on the basic stuff before
going here.
-/

/-
9.

Define a function, app (short for "append"),
that takes two nat_list values, let's call them
l1 and l2, and that returns the nat_list with
the elements of the first list followed by the
elements of the second list. For example,
(app Y Z) should return the list [1, 2, 1].

Hint: Consider the possible forms of l1. It
can only be either nat_nil or (nat_cons h t),
where, once again, h is is first element in
l1, and t is the rest of the list. Write the
function recursively accordingly.
-/

def app : nat_list → nat_list → nat_list
| nat_nil l2 := l2
| (nat_cons h t) l2 := nat_cons h (app t l2)

/-
10.

Prove the following

∀ l1 l2 : nat_list,
(length l1 + length l2) = length (append l1 l2)

Hint: use proof by induction on l1. There will be
two cases. In the first, l1 will be nat_nil, and
its length will reduce directly to 0. In the second
case, you will show that the property is true for a
next bigger list: one in the form of (nat_cons h t).
-/

example : ∀ l1 l2 : nat_list,
(length l1 + length l2) =
length (app l1 l2) :=
begin
  intros l1 l2,
  induction l1 with l1' n h,
    -- base case
    -- simplify using rules in app and length defs
    simp [app],
    simp [length],

    -- inductive case
    -- simplify using app and length rules in one line
    simp [app,length],
    -- now use induction hypothesis to rewrite goal
    rw <-h,
    -- and the rest is simple arithmetic manipulation
    simp,
    --rw
end

/- Formal Languages -/

namespace formal
/-
1.

Extend the following formal language to incorporate "or"
-/

inductive pVar : Type
| mk : ℕ → pVar

inductive pExp : Type
| mk_lit_pexp : bool → pExp
| mk_var_pexp : pVar → pExp
| mk_not_pexp : pExp → pExp
| mk_and_pexp : pExp → pExp → pExp
-- ADD THIS
| mk_or_pexp :  pExp → pExp → pExp

open pExp

def pInterp := pVar → bool

def pEval : pExp → pInterp → bool
-- how to evaluate literal expression
| (mk_lit_pexp b) i := b
-- how to evaluate variable expression
| (mk_var_pexp v) i := i v
-- how to evaluate a "not" expression
| (mk_not_pexp e) i := bnot (pEval e i)
-- how to evaluate an "and" expression
| (mk_and_pexp e1 e2) i :=
    band (pEval e1 i) (pEval e2 i)
-- AND ADD THIS
| (mk_or_pexp e1 e2) i :=
    bor (pEval e1 i) (pEval e2 i)

end formal


import game.order.level02

namespace xena -- hide

/-
# Chapter 2 : Order

## Level 3

Another property of the absolute value.
-/

notation `|` x `|` := abs x --hide

/- Lemma
For any two real numbers $a$ and $b$, we have that
$$|a| ≤ c ↔ -c ≤ a ≤ c$$.
-/
theorem abs_le (a c : ℝ) (h : 0 ≤ c): |a| ≤ c ↔ (-c) ≤ a ∧ a ≤ c :=
begin
    split,
    intro j,
    rcases lt_trichotomy a 0 with han | haz | hap,
    swap,
    rw haz,
    split,
    linarith,
    exact h,
    split,
    have h1 : | a | = - a, exact abs_of_neg han,
    rw h1 at j,
    linarith,
    linarith,
    split,
    have h2 : | a | = a, exact abs_of_pos hap,
    rw h2 at j,
    linarith,
    have h2 : | a | = a, exact abs_of_pos hap,
    rw h2 at j,
    exact j,
    intro k,
    cases k with d hd,
    rcases lt_trichotomy a 0 with han | haz | hap,
    have h1 : | a | = - a, exact abs_of_neg han,
    rw h1,
    linarith,
    rw haz,
    norm_num,
    exact h,
    have h1 : | a | = a, exact abs_of_pos hap,
    rw h1,
    linarith,
end

end xena --hide

/-
split,
    rcases lt_trichotomy a 0 with haNeg | haZero | haPos,
    { -- case a < 0
        intro H,
        have h1 : | a | = - a, exact abs_of_neg haNeg,
        rw h1 at H, split, linarith, linarith,
    },
    { -- case a = 0
        intro H, rw haZero, split, linarith, exact h,
    },
    { -- case 0 < a
        intro H,
        have h1 : |a| = a, exact abs_of_pos haPos,
        rw h1 at H, split, linarith, exact H,
    },

    rcases lt_trichotomy a 0 with haNeg | haZero | haPos,
    { -- case a < 0
        intro H,
        have h1 : | a | = - a, exact abs_of_neg haNeg,
        rw abs_le,
        exact H,
    },
    { -- case a = 0
        intro H,
        rw abs_le, exact H,
    },
    { -- case 0 < a
        intro H,
        rw abs_le, exact H,
    },

    done
-/


import order.filter.basic

/-

# tendsto

Here's an overview of the main definition we're learning today.

If `X` and `Y` are types, `φ : X → Y` is a function,
and `F : filter X` and `G : filter Y` are filters, then

`filter.tendsto φ F G`

is a true-false statement, which is pronounced something like
"`F` tends to `G` along `φ`". Of course we will `open filter`
in this file, so you can just write `tendsto φ F G`, or if
you like the dot notation you can even write `F.tendsto φ G`.

## Geometric meaning of `tendsto`.

Let's start by thinking about the easy case where `F` and `G`
are actually subsets of `X` and `Y` (that is, principal filters,
associated to sets which we will also call `F` and `G`). In this case,
`tendsto φ F G` simply means "`φ` restricts to a function
from `F` to `G`", or in other words `∀ x ∈ F, φ(x) ∈ G`.

There are two other ways of writing this predicate. The first
involves pushing a set forward along a map. If `F` is a subset of `X`
then let `φ(F)` denote the image of `F` under `φ`, that
is, the subset `{y : Y | ∃ x : X, φ x = y}` of `Y`.
Then `tendsto φ F G` simply means `φ(F) ⊆ G`.

The second involves pulling a set back along a map. If `G` is a subset
of `Y` then let `φ⁻¹(G)` denote the preimage of `G` under `φ`,
that is, the subset `{x : X | φ x ∈ G}` of `Y`. Then `tendsto φ F G`
simply means `F ⊆ φ⁻¹(G)`.

This is how it all works in the case of sets. What we need to
do today is to figure out how to push forward and pull back
filters along a map `φ`. Once we have done this, then we can
prove `φ(F) ≤ G ↔ F ≤ φ⁻¹(G)` and use either one of these
as our definition of `tendsto φ F G` -- it doesn't matter which.

## Digression : adjoint functors.

The discussion below is not needed to be able to do this week's
problems, but it might provide some helpful background for some.
Also note that anyone who still doens't like the word "type" can
literally just change it for the word "set" (and change "term of
type" to "element of set"), which is how arguments
of the below kind would appear in the traditional mathematical
literature.

Partially ordered types, such as the type of subsets of a fixed
type `X` or the type of filters on `X`, are actually very simple
examples of categories. In general if `P` is a partially ordered type
and `x,y` are terms of type `P` then the idea is that we can
define `Hom(x,y)` to have exactly one element if `x ≤ y` is true,
and no elements at all if `x ≤ y` is false. The structure/axioms for
a category are that `Hom(x,x)` is supposed to have an identity
element, which follows from reflexivity of `≤`, and that one can
compose morphisms, which follows from transitivity of `≤`.
Antisymmetry states that if two objects are isomorphic (i.e.,
in this case, if `Hom(x,y)` and `Hom(y,x)` are both nonempty),
then they are equal. If `φ : X → Y` is a map of types, then
pushing forward subsets and pulling back subsets are both
functors from `set X` to `set Y`, because `S ⊆ T → φ(S) ⊆ φ(T)`
and `U ⊆ V → φ⁻¹(U) ⊆ φ⁻¹(V)`. The statement that
`φ(S) ≤ U ↔ S ≤ φ⁻¹(U)` is simply the statement that these functors
are adjoint to each other. Today we will define pushforward and
pullback of filters, and show that they are also a pair of
adjoint functors, but we will not use this language. In fact there
is a special language for adjoint functors in this simple situation:
we will say that pushforward and pullback form a Galois connection.

-/

/-

## Warm-up: pushing forward and pulling back subsets.

Say `X` and `Y` are types, and `f : X → Y`.

-/

variables (X Y : Type) (f : X → Y)

/-

### images

In Lean, the image `f(S)` of a subset `S : set X` cannot
be denoted `f S`, because `f` expects an _element_ of `X` as
an input, not a subset of `X`, so we need new notation.

Notation : `f '' S` is the image of `S` under `f`. Let's
check this.

-/

example (S : set X) : f '' S = {y : Y | ∃ x : X, x ∈ S ∧ f x = y} :=
begin
  -- true by definition
  refl
end

/-

### preimages

In Lean, the preimage `f⁻¹(T)` of a subset `T : set Y` cannot
be denoted `f⁻¹ T` because `⁻¹` is the inverse notation in group
theory, so if anything would be a function from `Y` to `X`,
not a function on subsets of `Y`.

Notation : `f ⁻¹' T` is the preimage of `T` under `f`. Let's
check this.

Pro shortcut: `\-'` for `⁻¹'`

-/

example (T : set Y) : f ⁻¹' T = {x : X | f x ∈ T} :=
begin
  -- true by definition
  refl
end

/-

I claim that the following conditions on `S : set X` and `T : set Y`
are equivalent:

1) `f '' S ⊆ T`
2) `S ⊆ f⁻¹' T`

Indeed, they both say that `f` restricts to a function from `S` to `T`.
Let's check this. You might find

`mem_preimage : a ∈ f ⁻¹' s ↔ f a ∈ s`

and

-/

open set

example (S : set X) (T : set Y) : f '' S ⊆ T ↔ S ⊆ f⁻¹' T :=
begin
  sorry,
end

/-

## Pushing forward filters.

Pushing forward is easy, so let's do that first.
It's called `filter.map` in Lean.

We define the pushforward filter `map f F` on `Y` to be the
obvious thing: a subset of `Y` is in the filter iff `f⁻¹(Y)`
is in `F`. Let's check this is a filter.

Reminder of some helpful lemmas:

In `set`:
`mem_set_of_eq : a ∈ {x : α | p x} = p a` -- definitional

In `filter`:
`univ_mem_sets : univ ∈ F`
`mem_sets_of_superset : S ∈ F → S ⊆ T → T ∈ F`
`inter_mem_sets : S ∈ F → T ∈ F → S ∩ T ∈ F`

-/

open filter

-- this is called `F.map f` or `filter.map f F`
-- or just `map f F` if `filter` is open.
example (F : filter X) : filter Y :=
{ sets := {T : set Y | f ⁻¹' T ∈ F },
  univ_sets := begin
    sorry
  end,
  sets_of_superset := begin
    sorry,
  end,
  inter_sets := begin
    sorry
  end, }

-- this is `filter.mem_map` and it's true by definition.
-- It's useful in the form `rw mem_map` if you want to figure out
-- what's going on in a proof, but often you'll find you can
-- delete it at the end.
example (F : filter X) (T : set Y) : T ∈ F.map f ↔ f ⁻¹' T ∈ F :=
begin
  -- true by definition
  refl
end

-- Let's check that `map` satisfies some basic functorialities.
-- Recall that if your goal is to check two filters are
-- equal then you can use the `ext` tactic, e.g. with `ext S`.

-- pushing along the identity map id : X → X doesn't change the filter.
-- this is `filter.map_id` but see if you can prove it yourself.
example (F : filter X) : F.map id = F :=
begin
  sorry
end

-- pushing along g ∘ f is the same as pushing along f and then g
-- for some reason this isn't in mathlib, instead they have `map_map` which
-- has the equality the other way.
variables (Z : Type) (g : Y → Z)

-- this isn't in mathlib, but `filter.map_map` is the equality the other
-- way around. See if you can prove it yourself.
example (F : filter X) : F.map (g ∘ f) = (F.map f).map g :=
begin
  sorry,
end

open_locale filter -- for 𝓟 notation

-- pushing the principal filter `𝓟 S` along `f` gives `𝓟 (f '' S)`
-- this is `filter.map_principal` but see if you can prove it yourself.
example (S : set X) : (𝓟 S).map f = 𝓟 (f '' S) :=
begin
  sorry,
end

/-

## tendsto

The definition: if `f : X → Y` and `F : filter X` and `G : filter Y`
then `tendsto f F G : Prop := map f F ≤ G`. It's pronounced something
like "`F` tends to `G` along `f`". This is a *definition* (it
has type `Prop`), not the proof of a theorem. It is a true-false statement
attached to `f`, `F` and `G`, it's a bit like saying "f is continuous at x"
or something like that, it might be true and it might be false.

The mental model you might want to have of the definition is that
`tendsto f F G` means that the function `f` restricts to a function
from the generalized set `F` to the generalized set `G`.

-/

-- this is `filter.tendsto_def`
example (F : filter X) (G : filter Y) :
  tendsto f F G ↔ ∀ T : set Y, T ∈ G → f ⁻¹' T ∈ F :=
begin
  -- true by definition
  refl
end

-- Let's make a basic API for `tendsto`

-- this is `tendsto_id` but see if you can prove it yourself.
example (F : filter X) : tendsto id F F :=
begin
  sorry,
end

-- this is `tendsto.comp` but see if you can prove it yourself
example (F : filter X) (G : filter Y) (H : filter Z)
  (f : X → Y) (g : Y → Z)
  (hf : tendsto f F G) (hg : tendsto g G H) : tendsto (g ∘ f) F H :=
begin
  sorry,
end

-- I would recommend looking at the model answer to this one if
-- you get stuck.
lemma tendsto_comp_map (g : Y → Z) (F : filter X) (G : filter Z) :
  tendsto (g ∘ f) F G ↔ tendsto g (F.map f) G :=
begin
  sorry,
end

/-

## Pulling back filters

We don't use this in the next part.

Say `f : X → Y` and `G : filter Y`, and we want a filter on `X`. Let's make a
naive definition. We want a collection of subsets of `X` corresponding to the
filter obtained by pulling back `G` along `f`. When should `S : set X` be
in this filter? Perhaps it is when `f '' S ∈ G`. However, there is no reason
that the collection of `S` satisfying this property should be a filter
on `X`. For example, there is no reason to espect that `f '' univ ∈ G`
if `f` is not surjective. Our naive guess doesn't work.

Here's a way of fixing this, by coming up with a less naive guess which
is informed by our mental model. Remember that our model of a filter `G` is some
kind of generalised notion of a set. If `T : set Y` then `T ∈ G` is supposed to
mean that the "set" `G` is a subset of `T`. So this should imply
that `f⁻¹(G) ⊆ f⁻¹(T)`. In particular, if `T ∈ G` and `f⁻¹(T) ⊆ S` then this
should mean `f⁻¹(G) ⊆ S` and hence `S ∈ f⁻¹(G)`. Let's try this condition
(defining `S ∈ f⁻¹(G)` to mean `∃ T ∈ G, f⁻¹(T) ⊆ S`) and see if it works.

Random useful lemmas (you might be getting to the point where you can
guess the names of the lemmas):

`subset_univ S : S ⊆ univ`
`subset.trans : A ⊆ B → B ⊆ C → A ⊆ C`
-/

-- this is called filter.comap
example (G : filter Y) : filter X :=
{ sets := {S : set X | ∃ T ∈ G, f ⁻¹' T ⊆ S},
  univ_sets := begin
    sorry
  end,
  sets_of_superset := begin
    sorry
  end,
  inter_sets := begin
    sorry
  end }

-- Let's call this mem_comap
lemma mem_comap (f : X → Y) (G : filter Y) (S : set X) :
  S ∈ comap f G ↔ ∃ T ∈ G, f ⁻¹' T ⊆ S :=
begin
  -- true by definition
  refl
end

-- If you want to, you can check some preliminary properties of `comap`.

-- this is comap_id
example (G : filter Y) : comap id G = G :=
begin
  sorry
end

-- this is comap_comap but the other way around
lemma comap_comp (H : filter Z) : comap (g ∘ f) H = comap f (comap g H) :=
begin
  sorry
end

-- this is comap_principal. Remember `mem_principal_sets`! It's true by definition...
example (T : set Y) : comap f (𝓟 T) = 𝓟 (f ⁻¹' T) :=
begin
  sorry
end

-- This is the proof that `map f` and `comap f` are adjoint functors,
-- or in other words form a Galois connection. It is the "generalised set"
-- analogue of the assertion that if S is a subset of X and T is a subset of Y
-- then f(S) ⊆ T ↔ S ⊆ f⁻¹(T), these both being ways to say that `f` restricts
-- to a function from `S` to `T`.
lemma filter.galois_connection (F : filter X) (G : filter Y) :
  map f F ≤ G ↔ F ≤ comap f G :=
begin
  sorry,
end

-- indeed, `map f` and `comap f` form a Galois connection.
example : galois_connection (map f) (comap f) :=
filter.galois_connection X Y f


/-
# Goal:

Prove the equivalence of a pair of definitions for **flat modules**.

## Definition 1 (ideal_def):

The definition of a *flat module* implemented in Lean is:

Let `R` be a commutative ring, and `M` and `R`-module. The module is *flat* if for all finitely
generated ideals `I` of `R`, the canonical map `I ⊗ M →ₗ M` is injective.

## Definition 2 (ses_def):

The goal definition is that given in the Stacks project <https://stacks.math.columbia.edu/tag/00HB>:

An `R`-module `M` is called *flat* if when `N₁ →ₗ N₂ →ₗ N₃` is an exact sequence of R-modules, the
induced sequence obtained by tensoring `N₁ ⊗ M →ₗ N₂ ⊗ M →ₗ N₃ ⊗ M` is exact.

## Main Theorem:

The main theorem of this Lean document is that **ideal_def** and **ses_def** are equivalent.
This has as a consequence the more general [theorem](https://stacks.math.columbia.edu/tag/00HD):

Let `M` be an `R`-module. TFAE:

1. `M` is flat over `R` (according to **ses_def**)
2. For every injection `N →ₗ N'` of `R`-modules, the map `N ⊗ M →ₗ N' ⊗ M` is injective.
3. For every idea `I` of `R`, the induced map `I ⊗ M →ₗ M` is injective.
4. `M` is flat over `R` in the Lean sense (**ideal_def**)

## Proof Sketch:

The proof sketch in the Stacks project goes as follows:

1. **ses_def** implies **ideal_def**.

    This argument is straightforward, because `I` and `R` fit into the exact sequence
    `I →ₗ R →ₗ R / I`, and exactness in the middle of `I ⊗ M →ₗ R ⊗ M ≃ₗ M →ₗ (R / I) ⊗ M`
    implies that the left map is injective.

    New goal (1): Show **ideal_def** implies **ses_def**

2. Show that in order to prove **ses_def** it is sufficient to prove `_ ⊗ M` preserves injectives.

    Given any exact sequence of the form  `N₁ →ₗ N₂ →ₗ N₃`, let `K = ker N₂ →ₗ N₃`
    and `Q = im N₂ →ₗ N₃`. Then after tensoring with `M` everything fits into a sequence
    `N₁ ⊗ M →ₗ K ⊗ M →ₗ N₂ ⊗ M →ₗ  Q ⊗ M →ₗ N₃ ⊗ M`. Then show that

    * The first and third maps are surjective always.
    * If we show the second and forth maps are injective, then **ses_def** follows. (footnote 1)

    New goal (2): If `K →ₗ N` is injective, then `K ⊗ M →ₗ N ⊗ M` is injective.

3. Show that in order to prove (2) it is enough to show `_ ⊗ M` preserves injective maps `K →ₗ N`
when `K` is a finite `R`-module.

    Given an a general `K`, it can be written as a limit of its finite submodules. So in order to
    show that `x : ker (K ⊗ M →ₗ N ⊗ M)` is zero, we can restrict to a finite submodule `K'`
    of `K` that contains `x`.

    New goal (3): If `K →ₗ N` is injective with `K` finite, show `K ⊗ M →ₗ N ⊗ M` is injective.

4. In order to show (3) it suffces to show that `_ ⊗ M` preserves injective maps `K →ₗ N` when both
`K` and `N` are finite `R`-modules.

    Again, `N` can be written as a limit of its finite submodules, which is a directed system
    becuase any two finite submodules are contained in the finite submodule

    The result then follows by the following [lemma](https://stacks.math.columbia.edu/tag/00D7):
    If `(M i, μ i j)` is a directed system of modules with `M = lim M i` (write `μ i : M i →ₗ M` for
    the associated map) then `x i : M i` maps to zero in `M` via `μ i` if and only if there exists
    a `j ≥ i` such that `μ i j (x i) = 0`. (this lemma also may follow from
    [something else](https://stacks.math.columbia.edu/tag/00D6))
    [note this is already done!!!!]

    New goal (4): If `K →ₗ N` is injective with with `K` and `N` finite, show `K ⊗ M →ₗ N ⊗ M`
    is injective.

5. In order to show (4) it suffices to show that `_ ⊗ M` preserves injective maps of the form
`L →ₗ ⊕ⁿ R`.

    Because `N` is finite, we can write it as `⊗ⁿ R / L`, and `K = L'/L` for some submodules `L`
    and `L'` of `⊕ⁿ R`. In order to show `K ⊗ M →ₗ N ⊗ M` is injective it suffices to show both
    `L ⊗ M →ₗ ⊕ⁿ M` and `L' ⊗ M →ₗ ⊕ⁿ M` are injective.

    New goal (5): Given `L →ₗ ⊕ⁿ R` injective, show that `L ⊗ M →ₗ ⊕ⁿ M` is injective.

6. We now prove (5) by induction on `n`.

    The `n = 1` case, we are considering submodules `L →ₗ R`, which we can repeat the same argument
    as in (3) to reduce to the case when `L` is a finite submodule (finitely generated ideal),
    which is precisely **ideal_def**

    For the induction step if we consider the meet of the submodules `L` and `R ⊕ 0ⁿ⁻¹` written
    as `L'`, then `L'' = L'/L` is a submodule of `⊕ⁿ⁻¹ R` and we obtain the diagram

            `L' ⊗ M` -----> `L ⊗ M` -----> `L'' ⊗ M` -----> `0`
              |                 |                |
              |                 |                |
              |                 |                |
              v                 v                v
    `0` ---> `M` ----------> `⊕ⁿ M` -----> `⊕ⁿ⁻¹ M` -------> `0`

    where the bottom row is obviously exact, the top row is exact as well, and our induction
    hypothesis implies the left and right vertical arrows are injective. Hence the middle arrow
    is injective as well.

## Todo:

* Show that any module is a direct limit of its finitely generated submodules.

## Progress:

* Proved that `0 ⊗ M = 0`
* Defined and proved that finite submodules are a directed system
* Showed that if the components of a map out of a direct limit are injective, then the map itself is

## Where to go from here?:

* Maybe defining `Tor`?
-/

/-
## Subgoal 1: Prove that any module equivalent to the direct limit of its finite submodules.

This is a lemma that is used repeatedly in the argument above, and can generally play an important
rule in strengthening theorems in commutative algebra about finitely generated/finite things.

### Pieces

* Have `linear_map.ker_eq_bot_of_injective`, but it appears that the converse isn't implemented
in mathlib yet?
Ok that's not true, we have `mono_iff_ker_eq_bot` and `mono_iff_injective` which we can use to
translate back and forth, so the lemma should be super easy.
* Want to prove some basic stuff about `0 ⊗ m = 0`
-/

-- theorem main_result : injective f → injective (tensor_product.map (@linear_map.id R P _ _ _) f) :=
-- begin
-- intro h,
-- sorry
-- end


constants (p q : Prop) (H : p ∧ q)
theorem t_proof_left : p := and.elim_left H
theorem t_proof_right : q := and.elim_right H
check t_proof_left    -- t_proof_left : p
check t_proof_right   -- t_proof_right : q


/-
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 data.polynomial.cardinal
import ring_theory.algebraic

/-!
### Cardinality of algebraic numbers

In this file, we prove variants of the following result: the cardinality of algebraic numbers under
an R-algebra is at most `# R[X] * ℵ₀`.

Although this can be used to prove that real or complex transcendental numbers exist, a more direct
proof is given by `liouville.is_transcendental`.
-/

universes u v

open cardinal polynomial set
open_locale cardinal polynomial

namespace algebraic

lemma infinite_of_char_zero (R A : Type*) [comm_ring R] [is_domain R]
  [ring A] [algebra R A] [char_zero A] : {x : A | is_algebraic R x}.infinite :=
infinite_of_injective_forall_mem nat.cast_injective is_algebraic_nat

theorem aleph_0_le_cardinal_mk_of_char_zero (R A : Type*) [comm_ring R] [is_domain R]
  [ring A] [algebra R A] [char_zero A] : ℵ₀ ≤ #{x : A // is_algebraic R x} :=
infinite_iff.1 (set.infinite_coe_iff.2 $ infinite_of_char_zero R A)

section lift

variables (R : Type u) (A : Type v) [comm_ring R] [comm_ring A] [is_domain A] [algebra R A]
  [no_zero_smul_divisors R A]

theorem cardinal_mk_lift_le_mul :
  cardinal.lift.{u} (#{x : A // is_algebraic R x}) ≤ cardinal.lift.{v} #(R[X]) * ℵ₀ :=
begin
  rw [←mk_ulift, ←mk_ulift],
  choose g hg₁ hg₂ using λ x : {x : A | is_algebraic R x}, x.coe_prop,
  refine lift_mk_le_lift_mk_mul_of_lift_mk_preimage_le g (λ f, _),
  rw [lift_le_aleph_0, le_aleph_0_iff_set_countable],
  suffices : maps_to coe (g ⁻¹' {f}) (f.root_set A),
    from this.countable_of_inj_on (subtype.coe_injective.inj_on _) (f.root_set_finite A).countable,
  rintro x (rfl : g x = f),
  exact mem_root_set.2 ⟨hg₁ x, hg₂ x⟩
end

theorem cardinal_mk_lift_le_max :
  cardinal.lift.{u} (#{x : A // is_algebraic R x}) ≤ max (cardinal.lift.{v} (#R)) ℵ₀ :=
(cardinal_mk_lift_le_mul R A).trans $
  (mul_le_mul_right' (lift_le.2 cardinal_mk_le_max) _).trans $ by simp

@[simp] lemma cardinal_mk_lift_of_infinite [infinite R] :
  cardinal.lift.{u} (#{x : A // is_algebraic R x}) = cardinal.lift.{v} (#R) :=
((cardinal_mk_lift_le_max R A).trans_eq (max_eq_left $ aleph_0_le_mk _)).antisymm $
  lift_mk_le'.2 ⟨⟨λ x, ⟨algebra_map R A x, is_algebraic_algebra_map _⟩,
    λ x y h, no_zero_smul_divisors.algebra_map_injective R A (subtype.ext_iff.1 h)⟩⟩

variable [countable R]

@[simp] protected theorem countable : set.countable {x : A | is_algebraic R x} :=
begin
  rw [←le_aleph_0_iff_set_countable, ←lift_le],
  apply (cardinal_mk_lift_le_max R A).trans,
  simp
end

@[simp] theorem cardinal_mk_of_countble_of_char_zero [char_zero A] [is_domain R] :
  #{x : A // is_algebraic R x} = ℵ₀ :=
(algebraic.countable R A).le_aleph_0.antisymm (aleph_0_le_cardinal_mk_of_char_zero R A)

end lift

section non_lift

variables (R A : Type u) [comm_ring R] [comm_ring A] [is_domain A] [algebra R A]
  [no_zero_smul_divisors R A]

theorem cardinal_mk_le_mul : #{x : A // is_algebraic R x} ≤ #R[X] * ℵ₀ :=
by { rw [←lift_id (#_), ←lift_id #R[X]], exact cardinal_mk_lift_le_mul R A }

theorem cardinal_mk_le_max : #{x : A // is_algebraic R x} ≤ max (#R) ℵ₀ :=
by { rw [←lift_id (#_), ←lift_id (#R)], exact cardinal_mk_lift_le_max R A }

@[simp] theorem cardinal_mk_of_infinite [infinite R] : #{x : A // is_algebraic R x} = #R :=
lift_inj.1 $ cardinal_mk_lift_of_infinite R A

end non_lift

end algebraic


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

/-!
# Conformal maps between inner product spaces

In an inner product space, a map is conformal iff it preserves inner products up to a scalar factor.
-/

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

open linear_isometry continuous_linear_map
open_locale real_inner_product_space

/-- A map between two inner product spaces is a conformal map if and only if it preserves inner
products up to a scalar factor, i.e., there exists a positive `c : ℝ` such that `⟪f u, f v⟫ = c *
⟪u, v⟫` for all `u`, `v`. -/
lemma is_conformal_map_iff (f : E →L[ℝ] F) :
  is_conformal_map f ↔ ∃ (c : ℝ), 0 < c ∧ ∀ (u v : E), ⟪f u, f v⟫ = c * ⟪u, v⟫ :=
begin
  split,
  { rintros ⟨c₁, hc₁, li, rfl⟩,
    refine ⟨c₁ * c₁, mul_self_pos.2 hc₁, λ u v, _⟩,
    simp only [real_inner_smul_left, real_inner_smul_right, mul_assoc, coe_smul',
      coe_to_continuous_linear_map, pi.smul_apply, inner_map_map] },
  { rintros ⟨c₁, hc₁, huv⟩,
    obtain ⟨c, hc, rfl⟩ : ∃ c : ℝ, 0 < c ∧ c₁ = c * c,
      from ⟨real.sqrt c₁, real.sqrt_pos.2 hc₁, (real.mul_self_sqrt hc₁.le).symm⟩,
    refine ⟨c, hc.ne', (c⁻¹ • f : E →ₗ[ℝ] F).isometry_of_inner (λ u v, _), _⟩,
    { simp only [real_inner_smul_left, real_inner_smul_right, huv, mul_assoc, coe_smul,
        inv_mul_cancel_left₀ hc.ne', linear_map.smul_apply, continuous_linear_map.coe_coe] },
    { ext1 x,
      exact (smul_inv_smul₀ hc.ne' (f x)).symm } }
end


import .love10_denotational_semantics_demo

/-! # LoVe Exercise 10: Denotational Semantics -/

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

/-! ## Question 1: Monotonicity

1.1. Prove the following lemma from the lecture. -/

lemma monotone_comp {α β : Type} [partial_order α] (f g : α → set (β × β))
    (hf : monotone f) (hg : monotone g) :
  monotone (λa, f a ◯ g a) :=
begin
  intros a₁ a₂ ha b hb,
  cases' hb with m hm,
  cases' hm,
  apply exists.intro m,
  apply and.intro,
  { exact hf _ _ ha left },
  { exact hg _ _ ha right }
end

/-! 1.2. Prove its cousin. -/

lemma monotone_restrict {α β : Type} [partial_order α] (f : α → set (β × β))
    (p : β → Prop) (hf : monotone f) :
  monotone (λa, f a ⇃ p) :=
begin
  intros a₁ a₂ ha b hb,
  cases' hb,
  apply and.intro,
  { apply hf _ _ ha,
    exact left },
  { exact right }
end

/-! ## Question 2: Regular Expressions

__Regular expressions__, or __regexes__, are a highly popular tool for software
development, to analyze textual inputs. Regexes are generated by the following
grammar:

    R ::= ∅
        | ε
        | a
        | R ⬝ R
        | R + R
        | R*

Informally, the semantics of regular expressions is as follows:

* `∅` accepts nothing;
* `ε` accepts the empty string;
* `a` accepts the atom `a`;
* `R ⬝ R` accepts the concatenation of two regexes;
* `R + R` accepts either of two regexes;
* `R*` accepts arbitrary many repetitions of a regex.

Notice the rough correspondence with a WHILE language:

    `∅` ~ diverging statement (e.g., `while true do skip`)
    `ε` ~ `skip`
    `a` ~ `:=`
    `⬝` ~ `;`
    `+` ~ `if then else`
    `*` ~ `while` loop -/

inductive regex (α : Type) : Type
| nothing {} : regex
| empty {}   : regex
| atom       : α → regex
| concat     : regex → regex → regex
| alt        : regex → regex → regex
| star       : regex → regex

/-! In this exercise, we explore an alternative semantics of regular
expressions. Namely, we can imagine that the atoms represent binary relations,
instead of letters or symbols. Concatenation corresponds to composition of
relations, and alternation is union. Mathematically, regexes and binary
relations are both instances of Kleene algebras.

2.1. Complete the following translation of regular expressions to relations.

Hint: Exploit the correspondence with the WHILE language. -/

def rel_of_regex {α : Type} : regex (set (α × α)) → set (α × α)
| regex.nothing        := ∅
| regex.empty          := Id
| (regex.atom s)       := s
| (regex.concat r₁ r₂) := rel_of_regex r₁ ◯ rel_of_regex r₂
| (regex.alt r₁ r₂)    := rel_of_regex r₁ ∪ rel_of_regex r₂
| (regex.star r)       := lfp (λX, (rel_of_regex r ◯ X) ∪ Id)

/-! 2.2. Prove the following recursive equation about your definition. -/

lemma rel_of_regex_star {α : Type} (r : regex (set (α × α))) :
  rel_of_regex (regex.star r) =
  rel_of_regex (regex.alt (regex.concat r (regex.star r)) regex.empty) :=
begin
  apply lfp_eq,
  apply monotone_union,
  { apply monotone_comp,
    { exact monotone_const _ },
    { exact monotone_id } },
  { exact monotone_const _ }
end

end LoVe