set_option pp.width 60
section
/-
variables p q : Prop
variable hp : p
example (hq : q) : p ∧ q := and.intro hp hq

variable h : p ∨ q
example : q ∨ p :=
or.elim h
(assume hp : p, or.inr hp)
(assume hq : q, or.intro_left p hq)
check @or.intro_right q p hp
check @or.elim
check @or.intro_left
check @or.intro_right
-/

variables p q : Prop
theorem contra (hpq : p → q) : ¬q → ¬p :=
λ hnq : ¬q, λ hp : p, false.elim (hnq (hpq hp))

theorem aswap : p ∧ q ↔ q ∧ p :=
iff.intro
(assume h : p ∧ q, show q ∧ p, from and.intro (and.right h) (and.left h))
(assume h : q ∧ p, show p ∧ q, from and.intro (and.right h)(and.left h))

theorem aaswap : ∀ {p q : Prop}, p ∧ q ↔ q ∧ p :=
λ p q : Prop,
iff.intro
(λ h : p ∧ q, and.intro (and.right h) (and.left h))
(λ h : q ∧ p, and.intro (and.right h) (and.left h))

variables x y : Prop
variable z : y ∧ x
lemma a : x ∧ y := iff.mp (aaswap) z

lemma b : x ∧ y := and.intro
(and.right z)
(and.left z)

lemma c : x ∧ y :=
(λ (hx : x),
(λ (hy : y),
and.intro hx hy) (and.left z)) (and.right z)

lemma havelist : x ∧ y :=
have hx : x, from and.right z,
have hy : y, from and.left z,
show x ∧ y, from and.intro hx hy

lemma suff : x ∧ y :=
have hx : x, from and.right z,
suffices hy : y, from and.intro hx hy,
show y, from and.left z

open classical
theorem dner {p : Prop} : p → ¬¬p :=
assume hp : p,
    not.intro (assume np : ¬p, np hp)

theorem dne {p : Prop} : ¬¬p → p :=
--λ h : ¬¬p,
assume h : ¬¬p,
show p, from or.elim (em p)
(λ hp : p, hp)
(λ hnp : ¬p, absurd hnp h)

example (h : ¬¬p) : p :=
by_contradiction (assume h1: ¬p, show false, from h h1)

lemma demorg (h : ¬(p ∧ q)) : ¬p ∨ ¬q :=
--or.elim (em p)
by_cases
(assume hp : p, or.inr (show ¬q, from assume hq : q, h ⟨hp, hq⟩))
(assume hp : ¬p, or.inl hp)

end

section propositional_validities --Proofs of lots of propositions

variables p q r s : Prop

--commutativity of ∧ and ∨
example : p ∧ q ↔ q ∧ p :=
iff.intro
(assume x : p ∧ q,
show q ∧ p, from and.intro (and.right x) (and.left x))
(assume y : q ∧ p,
show p ∧ q, from and.intro (and.right y) (and.left y))
--cleaner(?) version
example : p ∧ q ↔ q ∧ p :=
have for : p ∧ q → q ∧ p, from (assume x : p ∧ q,
    have z : q, from and.right x,
    have c : p, from and.left x,
    and.intro z c),
have bac : q ∧ p → p ∧ q, from (assume y : q ∧ p, and.intro (and.right y) (and.left y)),
iff.intro for bac

example : p ∨ q ↔ q ∨ p :=
iff.intro
(assume x : p ∨ q,
show q ∨ p, from or.elim x
(assume hp : p, or.inr hp) (assume hq : q, or.inl hq))
(assume y : q ∨ p,
show p ∨ q, from or.elim y
(assume hq : q, or.inr hq) (assume hp : p, or.inl hp))
--associativity of ∧ and ∨
example : (p ∧ q) ∧ r ↔ p ∧ (q ∧ r) :=
iff.intro
(assume x : (p ∧ q) ∧ r,
show p ∧ (q ∧ r), from and.intro (and.left (and.left x))
(and.intro (and.right (and.left x))
(and.right x)))
(assume y : p ∧ (q ∧ r),
show (p ∧ q) ∧ r, from and.intro (and.intro
(and.left y) (and.left (and.right y)))
(and.right (and.right y)))

example : (p ∨ q) ∨ r ↔ p ∨ (q ∨ r) :=
have forward : (p ∨ q) ∨ r → p ∨ (q ∨ r), from (
assume x : (p ∨ q) ∨ r, show p ∨ (q ∨ r), from
or.elim x (assume hpq : p ∨ q,
or.elim hpq (assume hp : p, or.inl hp)
(assume hq : q, or.inr (or.inl hq)))
(assume hr : r, or.inr (or.inr hr))),
have backward : p ∨ (q ∨ r) → (p ∨ q) ∨ r, from (
assume x : p ∨ (q ∨ r), show (p ∨ q) ∨ r, from
or.elim x (assume hp : p, or.inl (or.inl hp))
(assume hqr : q ∨ r, or.elim hqr
(assume hq : q, or.inl (or.inr hq))
(assume hr : r, or.inr hr))),
iff.intro forward backward
--distributivity of ∧ and ∨
example : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) :=
iff.intro
    (assume h : p ∧ (q ∨ r),
        have hp : p, from h^.left,
        have hqr : q ∨ r, from h^.right,
        or.elim hqr
            (assume hq : q, or.inl ⟨hp,hq⟩)
            (assume hr : r, or.inr ⟨hp,hr⟩))
    (assume h : (p ∧ q) ∨ (p ∧ r),
        have hp : p, from
            or.elim h
                (assume hpq : p ∧ q, hpq^.left)
                (assume hpr : p ∧ r, hpr^.left),
        have hqr : q ∨ r, from
            or.elim h
                (assume hpq : p ∧ q, or.inl hpq^.right)
                (assume hpr : p ∧ r, or.inr hpr^.right),
        ⟨hp,hqr⟩)

example : p ∨ (q ∧ r) ↔ (p ∨ q) ∧ (p ∨ r) :=
iff.intro
    (assume h : p ∨ (q ∧ r),
        or.elim h
            (assume hp : p, ⟨or.inl hp,or.inl hp⟩)
            (assume hqr : q ∧ r, ⟨or.inr hqr^.left, or.inr hqr^.right⟩))
    (assume h : (p ∨ q) ∧ (p ∨ r),
        have hpq : p ∨ q, from h^.left,
        have hpr : p ∨ r, from h^.right,
         or.elim hpq
            (assume hp : p, or.inl hp)
            (assume hq : q,
                or.elim hpr
                    (assume hp : p, or.inl hp)
                    (assume hr : r, or.inr ⟨hq, hr⟩)
            )
    )
--other propositional proofs (non-classical)
example : (p → (q → r)) ↔ (p ∧ q → r) :=
iff.intro
    (assume h : p → (q → r),
        assume hpq : p ∧ q,
            h hpq^.left hpq^.right)
    (assume h : p ∧ q → r,
        assume hp : p,
            assume hq : q,
                h ⟨hp, hq⟩)

example : ((p ∨ q) → r) ↔ (p → r) ∧ (q → r) :=
iff.intro
    (assume h : (p ∨ q) → r,
        have hpr : p → r, from
            assume hp : p,
                h (or.inl hp),
        have hqr : q → r, from
            assume hq : q,
                h (or.inr hq),
        ⟨hpr, hqr⟩)
    (assume h : (p → r) ∧ (q → r),
        have hpr : p → r, from h^.left,
        have hqr : q → r, from h^.right,
        assume hpq : p ∨ q,
            show r, from or.elim hpq
                (assume hp : p, hpr hp)
                (assume hq : q, hqr hq))

lemma lem₁ {p q : Prop} : ¬(p ∨ q) ↔ ¬p ∧ ¬q :=
iff.intro
    (assume h : ¬(p ∨ q),
        have np : ¬p, from
            not.intro (show p → false, from (assume hp : p, h (or.inl hp))),
        have nq : ¬q, from
            not.intro (assume hq : q, h (or.inr hq)),
        ⟨np, nq⟩)
    (assume h : ¬p ∧ ¬q,
        show ¬(p ∨ q), from
            not.intro (assume hpq : p ∨ q, or.elim hpq
            (assume hp : p, h^.left hp)
            (assume hq : q, h^.right hq)))

example : ¬p ∨ ¬q → ¬(p ∧ q) :=
assume h : ¬p ∨ ¬q,
    or.elim h
        (assume np : ¬p, not.intro (assume hpq : p ∧ q, np hpq^.left))
        (assume nq : ¬q, not.intro (assume hpq : p ∧ q, nq hpq^.right))

example : ¬(p ∧ ¬p) :=
not.intro (assume h : p ∧ ¬p, h^.right h^.left)

example : p ∧ ¬q → ¬(p → q) :=
assume h : p ∧ ¬q,
    not.intro (assume hpq : p → q, h^.right (hpq h^.left))

example : ¬p → (p → q) :=
assume np : ¬p,
    assume hp : p,
        false.elim (np hp)

lemma lem₄ {p q : Prop} : (¬p ∨ q) → (p → q) :=
assume h : ¬p ∨ q,
    assume hp : p,
        or.elim h
            (assume np : ¬p, false.elim (np hp))
            (assume hq : q, hq)

example : p ∨ false ↔ p :=
iff.intro
    (assume h : p ∨ false, or.elim h
        (assume hp : p, hp)
        (assume fal, false.elim fal))
    (assume h : p, or.inl h)

example : p ∧ false ↔ false :=
iff.intro
    (assume h : p ∧ false, h^.right)
    (assume h : false, false.elim h)

example : ¬(p ↔ ¬p) :=
not.intro
    (assume h : p ↔ ¬p,
        have np : ¬p, from not.intro
            (assume hp : p,
                ((iff.elim_left h hp) hp)),
        (np (iff.elim_right h np)))

example : ¬(p ↔ ¬p) := --fully lambda???
not.intro (λ h : p ↔ ¬p, ((not.intro (λ hp : p, ((iff.elim_left h hp) hp))) (iff.elim_right h (not.intro (λ hp : p, ((iff.elim_left h hp) hp))))))

example : (p → q) → (¬q → ¬p) :=
assume h : p → q,
    assume nq : ¬q,
        not.intro
            (assume hp : p, nq (h hp))
--propositions that rely on classical logic
open classical

lemma lem₃ {p q : Prop} : ¬(p ∧ q) → ¬p ∨ ¬q :=
assume h : ¬(p ∧ q),
    or.elim (em (¬p ∨ ¬q))
        (assume h₁ : ¬p ∨ ¬q, h₁)
        (assume h₂ : ¬(¬p ∨ ¬q),
            have nnpq : (¬¬p ∧ ¬¬q),
                from iff.elim_left (@lem₁ (¬p) (¬q)) h₂,
            have hp : p, from dne nnpq^.left,
            have hq : q, from dne nnpq^.right,
            absurd (and.intro hp hq) h)

lemma lem₂ {p q : Prop} : ¬(p → q) → p ∧ ¬q :=
assume h : ¬(p → q),
    by_contradiction (assume h₂ : ¬(p ∧ ¬q),
        have nnpq : ¬p ∨ ¬¬q, from lem₃ h₂,
        have npq : ¬p ∨ q, from or.elim nnpq (assume np : ¬p, or.inl np) (assume nnq : ¬¬q, or.inr (dne nnq)),
        h (lem₄ npq))

example : (p → r ∨ s) → (p → r) ∨ (p → s) :=
assume h : p → r ∨ s,
    by_contradiction (assume h₂ : ¬((p → r) ∨ (p → s)),
        have h₃ : ¬(p → r) ∧ ¬(p → s), from iff.elim_left lem₁ h₂,
        have npr : ¬(p → r), from h₃^.left,
        have nps : ¬(p → s), from h₃^.right,
        have pnr : p ∧ ¬r, from lem₂ npr,
        have pns : p ∧ ¬s, from lem₂ nps,
        have hp : p, from pnr^.left,
        have nr : ¬r, from pnr^.right,
        have ns : ¬s, from pns^.right,
        or.elim (h hp)
            (assume hr : r, nr hr)
            (assume hs : s, ns hs)
    )

example : ¬(p ∧ q) → ¬p ∨ ¬q := --same as lem₃
assume h : ¬(p ∧ q),
    by_contradiction (assume h₂ : ¬(¬p ∨ ¬q),
        have nnpq : (¬¬p ∧ ¬¬q), from iff.elim_left (@lem₁ (¬p) (¬q)) h₂,
        h ⟨dne nnpq^.left, dne nnpq^.right⟩)

example : (p → q) → (¬p ∨ q) :=
assume h : p → q,
    or.elim (em (¬p ∨ q))
        (assume npq : ¬p ∨ q, npq)
        (assume nnpq : ¬(¬p ∨ q),
            have nh : ¬¬p ∧ ¬q, from (iff.elim_left lem₁) nnpq,
            have hp : p, from dne nh^.left,
            have hq : q, from h hp,
            or.inr hq)

example : (¬q → ¬p) → (p → q) :=
assume h : ¬q → ¬p,
    assume hp : p,
        or.elim (em q)
            (assume hq : q, hq)
            (assume nq : ¬q,
                have np : ¬p, from h nq,
                absurd hp np)

example : p ∨ ¬p := em p

lemma lem₆ : (((p → q) → p) → p) :=
assume h : (p → q) → p,
    show p, from or.elim (em (p → q))
        (assume h₂ : p → q, h h₂)
        (assume h₃ : ¬(p → q),
            have h₄ : p ∧ ¬q, from lem₂ h₃,
            h₄^.left)

end propositional_validities

section
open classical
lemma iffcontradiction {p : Prop} (h : p ↔ ¬p) : false :=
have h₁ : p → ¬p, from iff.elim_left h,
have h₂ : ¬p → p, from iff.elim_right h,
by_cases
    (assume h₃ : p, (h₁ h₃) h₃)
    (assume h₄ : ¬p, h₄ (h₂ h₄))
end

section

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

example : (∀ x : α, p x ∧ q x) → ∀ z : α, p z :=
assume h : ∀ x : α, p x ∧ q x,
    take y : α,
        show p y, from (h y)^.left

    section
    variable (r : α → α → Prop)
    variable transr : ∀ {x y z}, r x y → r y z → r x z
    variable reflr : ∀ x, r x x
    variable symmr : ∀ {x y}, r x y → r y x

    lemma a1short (a b c d : α) (hab : r a b) (hcb : r c b) (hcd : r c d) : r a d :=
    transr (transr hab (symmr hcb)) hcd

    lemma a1full
    : Π α : Type, Π r : α → α → Prop, Π transr : Π {x y z}, r x y → r y z → r x z, Π symmr : Π x y, r x y → r y x, Π a b c d : α, r a b → r c b → r c d → r a d :=
    λ α : Type, λ r : α → α → Prop, λ transr : Π {x y z}, r x y → r y z → r x z, λ symmr : Π x y, r x y → r y x, λ a b c d : α, λ hab : r a b, λ hcb : r c b, λ (hcd : r c d), (transr (transr hab (symmr c b hcb)) hcd)
    end
--variables (α : Type) (p q : α → Prop) is above

example : (∀ x, p x ∧ q x) ↔ (∀ x, p x) ∧ (∀ x, q x) :=
iff.intro
    (assume h : ∀ x, p x ∧ q x,
        and.intro
            (take z : α, show p z, from (h z)^.left)
            (take z : α, show q z, from (h z)^.right))
    (assume h : (∀ x, p x) ∧ (∀ x, q x),
        take z : α,
            show p z ∧ q z, from ⟨h^.left z, h^.right z⟩)

example : (∀ x, p x → q x) → (∀ x, p x) → (∀ x, q x) :=
assume h : ∀ x, p x → q x,
    assume h₂ : ∀ x, p x,
        take z,
            show q z, from (h z) (h₂ z)

example : (∀ x, p x) ∨ (∀ x, q x) → ∀ x, p x ∨ q x :=
assume h : (∀ x, p x) ∨ (∀ x, q x),
    take x : α,
        or.elim h
            (assume h₂ : ∀ x, p x,
                or.inl (h₂ x))
            (assume h₃ : ∀ x, q x,
                or.inr (h₃ x))

    section
    variable r : Prop
    open classical
    example : α → ((∀ x : α, r) ↔ r) :=
    assume h : α,
        iff.intro
            (assume h₂ : ∀ x : α, r,
                h₂ h)
            (assume hr : r,
                take x : α, hr)

    example : (∀ x, p x ∨ r) ↔ (∀ x, p x) ∨ r :=
    iff.intro
        (assume h : ∀ x, p x ∨ r, by_contradiction
        (assume h₃ : ¬((∀ x, p x) ∨ r),
            have h₄ : ¬(∀ x, p x) ∧ ¬r, from (iff.elim_left lem₁) h₃,
            have hapx : ∀ x, p x, from
                take x : α, or.elim (h x)
                    (assume hpx : p x, hpx)
                    (assume hr : r, absurd hr h₄^.right),
            h₄^.left hapx))
        (assume h : (∀ x, p x) ∨ r,
            or.elim h
                (assume hpx : ∀ x, p x,
                    take x : α,
                        or.inl (hpx x))
                (assume hr : r,
                    take x : α,
                        or.inr hr))

    example : (∀ x, r → p x) ↔ (r → ∀ x, p x) :=
    iff.intro
        (assume h : ∀ x, r → p x,
            assume hr : r,
                take x : α,
                    h x hr)
        (assume h : r → ∀ x, p x,
            take x : α,
                assume hr : r,
                    h hr x)
    end

    section
    open classical
    variables (men : Type) (barber : men) (shaves : men → men → Prop)

    example (h : ∀ x : men, shaves barber x ↔ ¬ shaves x x) : false :=
    iffcontradiction (h barber)
    end

end

section

example : 2 + 3 = 5 := rfl

example (x : ℕ) (h : x > 0) : ∃ y, y < x :=
@exists.intro ℕ (λ y, y < x) 0 (h)

example (x : ℕ) (h : 0 < x) : @Exists (ℕ) (λ y, y < x) :=
exists.intro 0 h
section
variables (α : Type) (p q : α → Prop)

example (h : ∃ x, p x ∧ q x) : ∃ x, q x ∧ p x :=
@exists.elim α (λ x : α, p x ∧ q x) (∃ x, q x ∧ p x) h
(take w : α,
assume hw : (λ x : α, p x ∧ q x) w,
show ∃ x, q x ∧ p x, from @exists.intro α (λ x : α, q x ∧ p x) w ⟨hw^.right, hw^.left⟩)--⟨w, hw^.right, hw^.left⟩)
end
section
def is_even (a : ℕ) := ∃ b, a = 2 * b
/-
is_even : ℕ → Prop
exists.elim : ∀ (α : Type) (p : α → Prop) (b : Prop), (∃ (x : α), p x)
exists.elim : ∀ (α : ℕ) (p : ℕ → Prop) (b : Prop), (∃ (x : ℕ), p x)
exists.elim : ∀ (α : ℕ) (p : ℕ → Prop) (b : Prop), (∃ (x : ℕ), (p x) : Prop)
a : nat
h1 : is_even a = Prop
is_even a = ∃ b, eq a (2 * b)
is_even = ∃ b, a = 2*b
-/
theorem even_plus_even {a b : ℕ} (h1 : is_even a) (h2 : is_even b) : is_even (a + b) :=
@exists.elim ℕ (λ b : ℕ, a = 2 * b) (is_even (a + b)) h1 (take w1, assume hw1 : a = 2 * w1,
exists.elim h2 (take w2, assume hw2 : b = 2 * w2,
    @exists.intro ℕ (λ (b_1 : ℕ), a + b = 2 * b_1) (w1 + w2)
        (calc
            a + b = 2 * w1 + 2 * w2 : by rw [hw1, hw2]
              ... = 2 * (w1 + w2)   : by rw mul_add)))
end

section

section
open classical

theorem contrapositive {p q : Prop} : p → q ↔ ¬q → ¬p :=
iff.intro
    (assume h : p → q,
        assume nq : ¬q,
            by_contradiction (assume nnp : ¬¬p,
                nq $ h $ dne nnp))
    (assume h : ¬q → ¬p,
        assume hp : p,
            by_contradiction (assume nq : ¬q,
                h nq hp))

end

open classical
variables (α : Type) (p q : α → Prop)
variable a : α
variable r : Prop

example : (∃ x : α, r) → r :=
assume h : (∃ x : α, r),
    @exists.elim α (λ x : α, r) r h
        (take a : α,
            assume r,
                r)

example : (∃ x : α, r) → r :=
assume h : (∃ x : α, r),
    match h with ⟨(a : α), (x : (λ y, r) a)⟩ :=
        x
    end

lemma existstests₂ : r → (∃ x : α, r) :=
assume hr : r,
    ⟨a, hr⟩
set_option pp.implicit true
--check @exists.intro
--print existstests₂

example : r → (∃ x : α, r) :=
λ (hr : r), @Exists.intro α (λ (x : α), r) a hr

example : (∃ x, p x ∧ r) ↔ (∃ x, p x) ∧ r :=
iff.intro
    (assume h : ∃ x, p x ∧ r,
        match h with ⟨(x : α), (hp : p x ∧ r)⟩ :=
            ⟨⟨x, hp^.left⟩, hp^.right⟩
        end)
    (assume h : (∃ x, p x) ∧ r,
        have hr : r, from h^.right,
        have hp : ∃ x, p x, from h^.left,
        match hp with ⟨(x : α), (hhp : p x)⟩ :=
            ⟨x, hhp, hr⟩
        end)

example : (∃ x, p x ∨ q x) ↔ (∃ x, p x) ∨ (∃ x, q x) :=
iff.intro
    (assume h : ∃ x, p x ∨ q x,
        match h with ⟨(x : α), (hpq : p x ∨ q x)⟩ :=
            or.elim hpq
                (assume hpx : p x,
                    or.inl ⟨x, hpx⟩)
                (assume hqx : q x,
                    or.inr ⟨x, hqx⟩)
        end)
    (assume h : (∃ x, p x) ∨ (∃ x, q x),
        or.elim h
            (assume epx : ∃ x, p x,
                match epx with ⟨x, (hp : p x)⟩ :=
                    ⟨x, or.inl hp⟩
                end)
            (assume eqx : ∃ x, q x,
                match eqx with ⟨x, (hq : q x)⟩ :=
                    ⟨x, or.inr hq⟩
                end))

lemma leme₁ {α : Type} {p : α → Prop} : (∀ x, p x) ↔ ¬(∃ x, ¬p x) :=
iff.intro
    (assume h : ∀ x, p x,
        not.intro (assume h₂ : ∃ x, ¬p x,
            match h₂ with ⟨(t : α), (np : ¬p t)⟩ :=
                np (h t)
            end))
    (assume h : ¬(∃ x, ¬p x),
        take x,
            by_contradiction
                (assume np : ¬p x,
                    h ⟨x, np⟩))

lemma leme₂ {α : Type} {p : α → Prop} : (¬∃ x, p x) → (∀ x, ¬p x) :=
assume h : ¬∃ x, p x,
    take x,
        (assume hpx : p x,
            false.elim $ h ⟨x, hpx⟩)

lemma leme₃ {α : Type} {p : α → Prop} : (∃ x, p x) ↔ ¬(∀ x, ¬p x) :=
iff.intro
    (assume h : ∃ x, p x,
        not.intro (assume n : ∀ x, ¬p x,
            match h with ⟨x, hp⟩ :=
                n x hp
            end))
    (assume h : ¬(∀ x, ¬p x),
        by_contradiction
            (assume h₂ : ¬∃ x, p x,
                false.elim $ h $ leme₂ h₂))

lemma leme₄ : (∀ x, ¬p x) → (¬∃ x, p x) :=
assume h : ∀ x, ¬p x,
    by_contradiction
        (assume h₂ : ¬¬∃ x, p x,
            false.elim $ leme₃^.elim_left (dne h₂) h)

example : (¬∀ x, p x) ↔ (∃ x, ¬p x) :=
iff.intro
    (assume h : ¬∀ x, p x,
        by_contradiction (assume hn : ¬∃ x, ¬p x,
            h $ leme₁^.elim_right hn))
    (assume h : ∃ x, ¬p x,
        by_contradiction (assume hn : ¬¬∀ x, p x,
            leme₁^.elim_left (dne hn) h))

example : (∀ x, p x → r) ↔ (∃ x, p x) → r :=
iff.intro
    (assume h : ∀ x, p x → r,
        assume h₂ : ∃ x, p x,
            match h₂ with ⟨x, hpx⟩ :=
                h x hpx
            end)
    (assume h : (∃ x, p x) → r,
        take x,
            assume h₂ : p x,
                h (show ∃ x, p x, from
                    ⟨x, h₂⟩))

example : (∃ x, p x → r) ↔ (∀ x, p x) → r :=
iff.intro
    (assume h : ∃ x, p x → r,
        assume h₂ : ∀ x, p x,
            match h with ⟨a, h₃⟩ :=
                h₃ $ h₂ a
            end)
    (assume h : (∀ x, p x) → r,
        by_cases
            (assume hap : ∀ x, p x,
                ⟨a, take hpx : p a, h hap⟩)
            (assume hnap : ¬∀ x, p x,
                by_contradiction (assume h₂ : ¬∃ x, p x → r,
                    have hap : ∀ x, p x, from
                        take x, by_contradiction (assume hnp : ¬p x,
                            have hex : ∃ x, p x → r, from
                                ⟨x, (assume hp, absurd hp hnp)⟩,
                            h₂ hex),
                    hnap hap)))

example : (∃ x, r → p x) → (r → ∃ x, p x) := --TODO converse
--iff.intro
    (assume h : ∃ x, r → p x,
        assume hr : r,
            match h with ⟨y, hy⟩ :=
                ⟨y, hy hr⟩
            end)
    /-
    (assume h : r → ∃ x, p x,
        by_cases
            (assume h₂ : p a,
                ⟨a, (assume hr : r, h₂)⟩)
            (assume h₃ : ¬p a,
                by_contradiction (assume h₄ : ¬(∃ x, r → p x),
                    by_contradiction (assume hnnr : ¬¬r,
                        have hr : r, from dne hnnr,
                        have hexp : ∃ x, p x, from h hr,
                        _
                    )
                    have h₅ : ∃ x, r → p x, from
                        ⟨a, (assume hr : r, absurd hr nr)⟩,
                    h₄ h₅))
    -/

end

--check and.intro
theorem test (p q : Prop) (hp : p) (hq : q) : p ∧ q ∧ p :=
/-begin
    apply and.intro,
    exact hp,
    apply and.intro,
    exact hq,
    exact hp
end
-/
by apply and.intro hp (and.intro hq hp)

variables x y z w : ℕ
example (h₁ : x = y) (h₂ : y = z) (h₃ : z = w) : x = w :=
begin
    apply eq.trans,
    assumption,
    apply eq.trans,
    assumption,
    assumption
end

example (h₁ : x = y) (h₂ : y = z) (h₃ : z = w) : x = w :=
begin
    apply eq.trans _ h₃,
    apply eq.trans _ h₂,
    assumption
end

example : ∀ a b c : ℕ, a = b → a = c → c = b :=
begin
    intros,
    apply eq.trans,
    apply eq.symm,
    assumption,
    assumption
end

example : x + y + z = x + y + z :=
begin
    generalize (x + y + z) w, -- goal is x y z : N ⊢ ∀ (w : N), w = w
    clear x y z,
    intro u, -- goal is x y z u : N ⊢ u = u
    reflexivity
end

example (x : ℕ) : x = x :=
begin
    revert x, -- goal is ⊢ ∀ (x : N), x = x
    intro y, -- goal is y : N ⊢ y = y
    reflexivity
end

example (x y : ℕ) (h : x = y) : y = x :=
    begin
    revert h, -- goal is x y : N ⊢ x = y → y = x
    intro h₁, -- goal is x y : N, h 1 : x = y ⊢ y = x
    symmetry,
    assumption
end

example (p q : Prop) : p ∨ q → q ∨ p :=
begin
    intro h,
    cases h with hp hq,
    -- case hp : p
    right, exact hp,
    -- case hq : q
    left, exact hq
end

example (p q : ℕ → Prop) : (∃ x, p x) → ∃ x, p x ∨ q x :=
begin
    revert p q,
    intros p q h,
    cases h with x px,
    existsi x, left, exact px
end

section
open nat
example (P : ℕ → Prop) (h₀ : P 0) (h₁ : ∀ n, P (succ n)) (m : ℕ) : P m :=
begin
    cases m with m', exact h₀, exact h₁ m'
end
end

example : ∃ x : ℕ, x + 3 = 8 :=
begin
    pose x := 5,
    existsi 5,
    clear x,
    reflexivity
end

example (a b : ℕ) (h : a = b) : a + 0 = b + 0 :=
begin
    change a = b, --since a + 0 is defined to be a
    assumption
end

section
variables (f : ℕ → ℕ) (k : ℕ)
example (h₁ : f 0 = 0) (h₂ : k = 0) : f k = 0 :=
begin
    rw h₂, -- replace k with 0
    rw h₁  -- replace f 0 with 0
end
end

example (a b c : ℕ) : a + b + c = a + c + b :=
begin
    rw [add_assoc, add_comm b, -add_assoc]
end

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

example (a b c : ℕ) : a + b + c = a + c + b :=
begin
    rw [add_assoc, add_assoc, add_comm _ b]
end

section
variables (f : ℕ → ℕ) (a : ℕ)
example (h : a + 0 = 0) : f a = f 0 :=
begin
    rw add_zero at h, rw h
end
end

end

namespace nat
def addy (m n : nat) : nat :=
nat.rec_on n m (λ n addy_m_n, succ addy_m_n)

--eval addy (succ zero) (succ (succ zero))
--set_option pp.implicit true

end nat

namespace testin
inductive es
| zero : es
| succ : es → es
| succi : es → (es → es)
| succ₂ : es → es → es

end testin

namespace inductivetypes

inductive weekday : Type
| sunday : ℕ → weekday
| monday : weekday
| tuesday : weekday
| wednesday : weekday
| thursday : weekday
| friday : weekday
| saturday : weekday

def daynumber (d : weekday) : ℕ :=
weekday.rec_on d (λ (x : ℕ), 4 * x) 10 15 20 25 30 35

--check weekday.rec_on
--check daynumber
--check weekday.induction_on
--eval daynumber (weekday.sunday 3)

inductive bol : Type
| tt : bol
| ff : bol

open bol

def bnot (b : bol) : bol :=
bol.rec_on b ff tt

def bor (b₁ b₂ : bol) : bol :=
bol.rec_on b₁ tt (bol.rec_on b₂ tt ff)

def band (b₁ b₂ : bol) : bol :=
bol.rec_on b₁ (bol.rec_on b₂ tt ff) ff

example : bnot tt = ff := rfl
example : bnot ff = tt := rfl
example : bnot (bnot tt) = tt := rfl
example (b : bol) : bnot (bnot b) = b :=
bol.rec_on b
    (show bnot (bnot tt) = tt, from rfl)
    (show bnot (bnot ff) = ff, from rfl)
example (b₁ b₂ : bol) : bor b₁ b₂ = bor b₂ b₁ :=
bol.rec_on b₁
    (bol.rec_on b₂ rfl rfl)
    (bol.rec_on b₂ rfl rfl)
example (b₁ b₂ : bol) : bor b₁ b₂ = bnot (band (bnot b₁) (bnot b₂)) :=
bol.rec_on b₁
    (bol.rec_on b₂ rfl rfl)
    (bol.rec_on b₂ rfl rfl)
section universevariables

universe variables u v
inductive pro (α : Type u) (β : Type v)
| mk : α → β → pro
inductive su (α : Type u) (β : Type v)
| il {} : α → su
| ir {} : β → su
open pro su

def fst {α : Type u} {β : Type v} (p : pro α β) : α :=
pro.rec_on p (λ a b, a)
def snd {α : Type u} {β : Type v} (p : pro α β) : β :=
pro.rec_on p (λ a b, b)

def funky₁ (s : su ℕ ℕ) : ℕ :=
su.rec_on s (λ x, x + 12) (λ x, x * 7)

--eval funky₁ $ su.ir 3

inductive maybe (α : Type u)
| nothing {} : maybe
| just       : α → maybe

--check maybe.rec_on

def bolmaybefun (m : maybe bol) : bol :=
maybe.rec_on m (bol.ff) (λ b, b)

--eval bolmaybefun $ maybe.just ff

def maybeffun (f : ℕ) : maybe ℕ :=
cond (f < 5) (maybe.nothing) (maybe.just (f*f))
--check maybeffun

inductive inhab (α : Type u)
| mk : α → inhab

def partialsub (a : ℕ) (b : ℕ) : maybe ℕ :=
cond (a < b) (maybe.nothing) (maybe.just (a-b))

--def partialcomp {α β γ : Type u} (f : maybe (α → β)) (g : maybe (β → γ)) : maybe (α → γ) :=
lemma inhabnat : inhab ℕ :=
inhab.mk 0

lemma inhabbol : inhab bol :=
inhab.mk bol.tt

lemma inhabprod (α : Type u) (β : Type v) (a : inhab α) (b : inhab β) : inhab (α × β) :=
inhab.mk (prod.mk (inhab.rec_on a (λ a₂, a₂)) (inhab.rec_on b (λ b₂, b₂)))

inductive pfalse : Prop

inductive ptrue : Prop
| intro : ptrue

inductive pand (a b : Prop) : Prop
| intro : a → b → pand

inductive por (a b : Prop) : Prop
| inl {} : a → por
| inr {} : b → por

inductive pexists {α : Type u} (p : α → Prop) : Prop
| intro : ∀ (a : α), p a → pexists

/-
check @pand.rec_on
check @por.rec_on
check @pexists.rec_on
check @nat.rec_on
-/

end universevariables

end inductivetypes

namespace inductivetactics

open nat
variable p : ℕ → Prop

example (hz : p 0) (hs : ∀ n, p (succ n)) : ∀ n, p n :=
begin
    intro n,
    cases n,
    exact hz,
    apply hs
end

def f (n : ℕ) : ℕ :=
begin
    cases n, exact 3, exact 7
end

/-
check list.rec

variable α : Type
def appendy (s t : list α) : list α :=
list.rec t (λ x l u, x::u) s
print appendy
-/

def is_not_zero (a : nat) : bool :=
match a with
| 0 := ff
| (n+1) := tt
end

universe variable u
class has_add (α : Type u) :=
(add : α → α → α)

def add {α : Type u} [has_add α] : α → α → α := has_add.add

instance nat_has_add : has_add nat :=
--⟨nat.add⟩
has_add.mk nat.add --should be equivalent

end inductivetactics

/-
definition compA : Π p q r : Prop, (q → r) → (p → q) → p → r :=
λ p, λ q, λ r, λ h1, λ h2, λ h3, h1 (h2 h3)
variables p q r s : Prop
theorem comp (h1 : q → r) (h2 : p → q) (h3 : p): r := h1 (h2 h3)
theorem com3 (h1 : q → r) (h2 : p → q) : p → r :=
λ h3 : p, h1 (h2 h3)
theorem com2 : (q → r) → (p → q) → p → r :=
λ h1, λ h2, λ h3, h1 (h2 h3)
variable h1 : q → r
variable h2 : p → q
variable h3 : p
theorem compy : r := h1 (h2 h3)

check compA
check comp
check compy
-/

/-
universe variable u
def ident (α : Type u) (x : α) := x
def ident' := λ α : Type u, λ x : α, x
def identy {α : Type u} (x : α) := x

check ident
check ident'

variable β : Type
variable b : β
check identy b
check @identy β b

section
variables p q : Prop
theorem t1 : p → q → p := λ hp : p, λ hq : q, hp
end
--theorem t1 := λ p : Prop, λ q : Prop, λ hp : p, λ hq : q, hp
theorem t1' (p q : Prop) (hp : p) (hq : q) : p := hp
theorem t₁ (p q : Prop) : p → q → p := λ hp, λ hq, hp
theorem t₁' (p q : Prop) (hp : p) : q → p := λ hq, hp
theorem t₂ : Π p : Prop, Π q : Prop, p → q → p := λ p : Prop, λ q : Prop, λ hp, λ hq, hp
theorem t₃ : Π p q : Prop, p → q → p := λ p : Prop, λ q : Prop, λ hp, λ hq, hp
--theorem t1₁ (p q : Prop) (hp : p) (hq : q) : p := hp
check t1
check t1'
check t₁'
check t₁
check t₂
-/


import Leanhello

def main : IO Unit := do
  IO.println s!"Hello, {hello}!"
  IO.println s!"Hello, {hello}!"

inductive Palindrome: List α -> Prop where
  | nil : Palindrome []
  | single : (a : α) -> Palindrome [a]
  | sandwich : (a : α) -> Palindrome as -> Palindrome ([a] ++ as ++ [a])

theorem palindrome_reverse (h: Palindrome as) : Palindrome as.reverse := by
  induction h with
  | nil => exact Palindrome.nil
  | single a => exact Palindrome.single a
  | sandwich a h ih => simp; exact Palindrome.sandwich _ ih

theorem reverse_eq_of_palindrome (hh: Palindrome as) : as.reverse = as := by
  induction hh with
  | nil => rfl
  | single a => rfl
  | sandwich a _ ih => simp [ih]

example (h: Palindrome as) : as.reverse = as := by
  simp [reverse_eq_of_palindrome h, h]

def List.last : (as : List α) → as ≠ [] → α
  | [a],        _ => a
  | _::a₂::as, _ => (a₂::as).last (by simp)

@[simp] theorem List.dropLast_append_last (h : as ≠ []) : as.dropLast ++ [as.last h] = as := by
  match as with
  | [] => contradiction
  | [a] => simp_all [last, dropLast]
  | a₁ :: a₂ :: as =>
    simp [last ,dropLast]
    exact dropLast_append_last (as := a₂ :: as) (by simp)

theorem List.palindrome_ind (motive : List α → Prop)
    (h₁ : motive [])
    (h₂ : (a : α) → motive [a])
    (h₃ : (a b : α) → (as : List α) → motive as → motive ([a] ++ as ++ [b]))
    (as : List α)
    : motive as :=
  match as with
  | []  => h₁
  | [a] => h₂ a
  | a₁::a₂::as' =>
    have ih := palindrome_ind motive h₁ h₂ h₃ (a₂::as').dropLast
    have : [a₁] ++ (a₂::as').dropLast ++ [(a₂::as').last (by simp)] = a₁::a₂::as' := by simp
    this ▸ h₃ _ _ _ ih
termination_by _ as => as.length

theorem List.palindrome_of_eq_reverse (h : as.reverse = as) : Palindrome as := by
  induction as using palindrome_ind
  next => exact Palindrome.nil
  next a => exact Palindrome.single a
  next a b as ih =>
    have : a = b := by simp_all
    subst this
    have : as.reverse = as := by simp_all
    exact Palindrome.sandwich a (ih this)

def List.isPalindrome [DecidableEq α] (as : List α) : Bool :=
    as.reverse = as

theorem List.isPalindrome_correct [DecidableEq α] (as : List α) : as.isPalindrome ↔ Palindrome as := by
  simp [isPalindrome]
  exact Iff.intro (fun h => palindrome_of_eq_reverse h) (fun h => reverse_eq_of_palindrome h)

#eval [1, 2, 1].isPalindrome
#eval [1, 2, 3, 1].isPalindrome

example : [1, 2, 1].isPalindrome := rfl


/-
Copyright (c) 2018 Chris Hughes. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Chris Hughes, Abhimanyu Pallavi Sudhir, Jean Lo, Calle Sönne, Benjamin Davidson
-/
import analysis.special_functions.trigonometric.basic
import topology.algebra.ordered.proj_Icc

/-!
# Inverse trigonometric functions.

See also `analysis.special_functions.trigonometric.arctan` for the inverse tan function.
(This is delayed as it is easier to set up after developing complex trigonometric functions.)

Basic inequalities on trigonometric functions.
-/

noncomputable theory
open_locale classical topological_space filter
open set filter

open_locale real

namespace real

/-- Inverse of the `sin` function, returns values in the range `-π / 2 ≤ arcsin x ≤ π / 2`.
It defaults to `-π / 2` on `(-∞, -1)` and to `π / 2` to `(1, ∞)`. -/
@[pp_nodot] noncomputable def arcsin : ℝ → ℝ :=
coe ∘ Icc_extend (neg_le_self zero_le_one) sin_order_iso.symm

lemma arcsin_mem_Icc (x : ℝ) : arcsin x ∈ Icc (-(π / 2)) (π / 2) := subtype.coe_prop _

@[simp] lemma range_arcsin : range arcsin = Icc (-(π / 2)) (π / 2) :=
by { rw [arcsin, range_comp coe], simp [Icc] }

lemma arcsin_le_pi_div_two (x : ℝ) : arcsin x ≤ π / 2 := (arcsin_mem_Icc x).2

lemma neg_pi_div_two_le_arcsin (x : ℝ) : -(π / 2) ≤ arcsin x := (arcsin_mem_Icc x).1

lemma arcsin_proj_Icc (x : ℝ) :
  arcsin (proj_Icc (-1) 1 (neg_le_self $ @zero_le_one ℝ _) x) = arcsin x :=
by rw [arcsin, function.comp_app, Icc_extend_coe, function.comp_app, Icc_extend]

lemma sin_arcsin' {x : ℝ} (hx : x ∈ Icc (-1 : ℝ) 1) : sin (arcsin x) = x :=
by simpa [arcsin, Icc_extend_of_mem _ _ hx, -order_iso.apply_symm_apply]
  using subtype.ext_iff.1 (sin_order_iso.apply_symm_apply ⟨x, hx⟩)

lemma sin_arcsin {x : ℝ} (hx₁ : -1 ≤ x) (hx₂ : x ≤ 1) : sin (arcsin x) = x :=
sin_arcsin' ⟨hx₁, hx₂⟩

lemma arcsin_sin' {x : ℝ} (hx : x ∈ Icc (-(π / 2)) (π / 2)) : arcsin (sin x) = x :=
inj_on_sin (arcsin_mem_Icc _) hx $ by rw [sin_arcsin (neg_one_le_sin _) (sin_le_one _)]

lemma arcsin_sin {x : ℝ} (hx₁ : -(π / 2) ≤ x) (hx₂ : x ≤ π / 2) : arcsin (sin x) = x :=
arcsin_sin' ⟨hx₁, hx₂⟩

lemma strict_mono_on_arcsin : strict_mono_on arcsin (Icc (-1) 1) :=
(subtype.strict_mono_coe _).comp_strict_mono_on $
  sin_order_iso.symm.strict_mono.strict_mono_on_Icc_extend _

lemma monotone_arcsin : monotone arcsin :=
(subtype.mono_coe _).comp $ sin_order_iso.symm.monotone.Icc_extend _

lemma inj_on_arcsin : inj_on arcsin (Icc (-1) 1) := strict_mono_on_arcsin.inj_on

lemma arcsin_inj {x y : ℝ} (hx₁ : -1 ≤ x) (hx₂ : x ≤ 1) (hy₁ : -1 ≤ y) (hy₂ : y ≤ 1) :
  arcsin x = arcsin y ↔ x = y :=
inj_on_arcsin.eq_iff ⟨hx₁, hx₂⟩ ⟨hy₁, hy₂⟩

@[continuity]
lemma continuous_arcsin : continuous arcsin :=
continuous_subtype_coe.comp sin_order_iso.symm.continuous.Icc_extend'

lemma continuous_at_arcsin {x : ℝ} : continuous_at arcsin x :=
continuous_arcsin.continuous_at

lemma arcsin_eq_of_sin_eq {x y : ℝ} (h₁ : sin x = y) (h₂ : x ∈ Icc (-(π / 2)) (π / 2)) :
  arcsin y = x :=
begin
  subst y,
  exact inj_on_sin (arcsin_mem_Icc _) h₂ (sin_arcsin' (sin_mem_Icc x))
end

@[simp] lemma arcsin_zero : arcsin 0 = 0 :=
arcsin_eq_of_sin_eq sin_zero ⟨neg_nonpos.2 pi_div_two_pos.le, pi_div_two_pos.le⟩

@[simp] lemma arcsin_one : arcsin 1 = π / 2 :=
arcsin_eq_of_sin_eq sin_pi_div_two $ right_mem_Icc.2 (neg_le_self pi_div_two_pos.le)

lemma arcsin_of_one_le {x : ℝ} (hx : 1 ≤ x) : arcsin x = π / 2 :=
by rw [← arcsin_proj_Icc, proj_Icc_of_right_le _ hx, subtype.coe_mk, arcsin_one]

lemma arcsin_neg_one : arcsin (-1) = -(π / 2) :=
arcsin_eq_of_sin_eq (by rw [sin_neg, sin_pi_div_two]) $
  left_mem_Icc.2 (neg_le_self pi_div_two_pos.le)

lemma arcsin_of_le_neg_one {x : ℝ} (hx : x ≤ -1) : arcsin x = -(π / 2) :=
by rw [← arcsin_proj_Icc, proj_Icc_of_le_left _ hx, subtype.coe_mk, arcsin_neg_one]

@[simp] lemma arcsin_neg (x : ℝ) : arcsin (-x) = -arcsin x :=
begin
  cases le_total x (-1) with hx₁ hx₁,
  { rw [arcsin_of_le_neg_one hx₁, neg_neg, arcsin_of_one_le (le_neg.2 hx₁)] },
  cases le_total 1 x with hx₂ hx₂,
  { rw [arcsin_of_one_le hx₂, arcsin_of_le_neg_one (neg_le_neg hx₂)] },
  refine arcsin_eq_of_sin_eq _ _,
  { rw [sin_neg, sin_arcsin hx₁ hx₂] },
  { exact ⟨neg_le_neg (arcsin_le_pi_div_two _), neg_le.2 (neg_pi_div_two_le_arcsin _)⟩ }
end

lemma arcsin_le_iff_le_sin {x y : ℝ} (hx : x ∈ Icc (-1 : ℝ) 1) (hy : y ∈ Icc (-(π / 2)) (π / 2)) :
  arcsin x ≤ y ↔ x ≤ sin y :=
by rw [← arcsin_sin' hy, strict_mono_on_arcsin.le_iff_le hx (sin_mem_Icc _), arcsin_sin' hy]

lemma arcsin_le_iff_le_sin' {x y : ℝ} (hy : y ∈ Ico (-(π / 2)) (π / 2)) :
  arcsin x ≤ y ↔ x ≤ sin y :=
begin
  cases le_total x (-1) with hx₁ hx₁,
  { simp [arcsin_of_le_neg_one hx₁, hy.1, hx₁.trans (neg_one_le_sin _)] },
  cases lt_or_le 1 x with hx₂ hx₂,
  { simp [arcsin_of_one_le hx₂.le, hy.2.not_le, (sin_le_one y).trans_lt hx₂] },
  exact arcsin_le_iff_le_sin ⟨hx₁, hx₂⟩ (mem_Icc_of_Ico hy)
end

lemma le_arcsin_iff_sin_le {x y : ℝ} (hx : x ∈ Icc (-(π / 2)) (π / 2)) (hy : y ∈ Icc (-1 : ℝ) 1) :
  x ≤ arcsin y ↔ sin x ≤ y :=
by rw [← neg_le_neg_iff, ← arcsin_neg,
  arcsin_le_iff_le_sin ⟨neg_le_neg hy.2, neg_le.2 hy.1⟩ ⟨neg_le_neg hx.2, neg_le.2 hx.1⟩,
  sin_neg, neg_le_neg_iff]

lemma le_arcsin_iff_sin_le' {x y : ℝ} (hx : x ∈ Ioc (-(π / 2)) (π / 2)) :
  x ≤ arcsin y ↔ sin x ≤ y :=
by rw [← neg_le_neg_iff, ← arcsin_neg, arcsin_le_iff_le_sin' ⟨neg_le_neg hx.2, neg_lt.2 hx.1⟩,
  sin_neg, neg_le_neg_iff]

lemma arcsin_lt_iff_lt_sin {x y : ℝ} (hx : x ∈ Icc (-1 : ℝ) 1) (hy : y ∈ Icc (-(π / 2)) (π / 2)) :
  arcsin x < y ↔ x < sin y :=
not_le.symm.trans $ (not_congr $ le_arcsin_iff_sin_le hy hx).trans not_le

lemma arcsin_lt_iff_lt_sin' {x y : ℝ} (hy : y ∈ Ioc (-(π / 2)) (π / 2)) :
  arcsin x < y ↔ x < sin y :=
not_le.symm.trans $ (not_congr $ le_arcsin_iff_sin_le' hy).trans not_le

lemma lt_arcsin_iff_sin_lt {x y : ℝ} (hx : x ∈ Icc (-(π / 2)) (π / 2)) (hy : y ∈ Icc (-1 : ℝ) 1) :
  x < arcsin y ↔ sin x < y :=
not_le.symm.trans $ (not_congr $ arcsin_le_iff_le_sin hy hx).trans not_le

lemma lt_arcsin_iff_sin_lt' {x y : ℝ} (hx : x ∈ Ico (-(π / 2)) (π / 2)) :
  x < arcsin y ↔ sin x < y :=
not_le.symm.trans $ (not_congr $ arcsin_le_iff_le_sin' hx).trans not_le

lemma arcsin_eq_iff_eq_sin {x y : ℝ} (hy : y ∈ Ioo (-(π / 2)) (π / 2)) :
  arcsin x = y ↔ x = sin y :=
by simp only [le_antisymm_iff, arcsin_le_iff_le_sin' (mem_Ico_of_Ioo hy),
  le_arcsin_iff_sin_le' (mem_Ioc_of_Ioo hy)]

@[simp] lemma arcsin_nonneg {x : ℝ} : 0 ≤ arcsin x ↔ 0 ≤ x :=
(le_arcsin_iff_sin_le' ⟨neg_lt_zero.2 pi_div_two_pos, pi_div_two_pos.le⟩).trans $ by rw [sin_zero]

@[simp] lemma arcsin_nonpos {x : ℝ} : arcsin x ≤ 0 ↔ x ≤ 0 :=
neg_nonneg.symm.trans $ arcsin_neg x ▸ arcsin_nonneg.trans neg_nonneg

@[simp] lemma arcsin_eq_zero_iff {x : ℝ} : arcsin x = 0 ↔ x = 0 :=
by simp [le_antisymm_iff]

@[simp] lemma zero_eq_arcsin_iff {x} : 0 = arcsin x ↔ x = 0 :=
eq_comm.trans arcsin_eq_zero_iff

@[simp] lemma arcsin_pos {x : ℝ} : 0 < arcsin x ↔ 0 < x :=
lt_iff_lt_of_le_iff_le arcsin_nonpos

@[simp] lemma arcsin_lt_zero {x : ℝ} : arcsin x < 0 ↔ x < 0 :=
lt_iff_lt_of_le_iff_le arcsin_nonneg

@[simp] lemma arcsin_lt_pi_div_two {x : ℝ} : arcsin x < π / 2 ↔ x < 1 :=
(arcsin_lt_iff_lt_sin' (right_mem_Ioc.2 $ neg_lt_self pi_div_two_pos)).trans $
  by rw sin_pi_div_two

@[simp] lemma neg_pi_div_two_lt_arcsin {x : ℝ} : -(π / 2) < arcsin x ↔ -1 < x :=
(lt_arcsin_iff_sin_lt' $ left_mem_Ico.2 $ neg_lt_self pi_div_two_pos).trans $
  by rw [sin_neg, sin_pi_div_two]

@[simp] lemma arcsin_eq_pi_div_two {x : ℝ} : arcsin x = π / 2 ↔ 1 ≤ x :=
⟨λ h, not_lt.1 $ λ h', (arcsin_lt_pi_div_two.2 h').ne h, arcsin_of_one_le⟩

@[simp] lemma pi_div_two_eq_arcsin {x} : π / 2 = arcsin x ↔ 1 ≤ x :=
eq_comm.trans arcsin_eq_pi_div_two

@[simp] lemma pi_div_two_le_arcsin {x} : π / 2 ≤ arcsin x ↔ 1 ≤ x :=
(arcsin_le_pi_div_two x).le_iff_eq.trans pi_div_two_eq_arcsin

@[simp] lemma arcsin_eq_neg_pi_div_two {x : ℝ} : arcsin x = -(π / 2) ↔ x ≤ -1 :=
⟨λ h, not_lt.1 $ λ h', (neg_pi_div_two_lt_arcsin.2 h').ne' h, arcsin_of_le_neg_one⟩

@[simp] lemma neg_pi_div_two_eq_arcsin {x} : -(π / 2) = arcsin x ↔ x ≤ -1 :=
eq_comm.trans arcsin_eq_neg_pi_div_two

@[simp] lemma arcsin_le_neg_pi_div_two {x} : arcsin x ≤ -(π / 2) ↔ x ≤ -1 :=
(neg_pi_div_two_le_arcsin x).le_iff_eq.trans arcsin_eq_neg_pi_div_two

lemma maps_to_sin_Ioo : maps_to sin (Ioo (-(π / 2)) (π / 2)) (Ioo (-1) 1) :=
λ x h, by rwa [mem_Ioo, ← arcsin_lt_pi_div_two, ← neg_pi_div_two_lt_arcsin,
  arcsin_sin h.1.le h.2.le]

/-- `real.sin` as a `local_homeomorph` between `(-π / 2, π / 2)` and `(-1, 1)`. -/
@[simp] def sin_local_homeomorph : local_homeomorph ℝ ℝ :=
{ to_fun := sin,
  inv_fun := arcsin,
  source := Ioo (-(π / 2)) (π / 2),
  target := Ioo (-1) 1,
  map_source' := maps_to_sin_Ioo,
  map_target' := λ y hy, ⟨neg_pi_div_two_lt_arcsin.2 hy.1, arcsin_lt_pi_div_two.2 hy.2⟩,
  left_inv' := λ x hx, arcsin_sin hx.1.le hx.2.le,
  right_inv' := λ y hy, sin_arcsin hy.1.le hy.2.le,
  open_source := is_open_Ioo,
  open_target := is_open_Ioo,
  continuous_to_fun := continuous_sin.continuous_on,
  continuous_inv_fun := continuous_arcsin.continuous_on }

lemma cos_arcsin_nonneg (x : ℝ) : 0 ≤ cos (arcsin x) :=
cos_nonneg_of_mem_Icc ⟨neg_pi_div_two_le_arcsin _, arcsin_le_pi_div_two _⟩

lemma cos_arcsin {x : ℝ} (hx₁ : -1 ≤ x) (hx₂ : x ≤ 1) : cos (arcsin x) = sqrt (1 - x ^ 2) :=
have sin (arcsin x) ^ 2 + cos (arcsin x) ^ 2 = 1 := sin_sq_add_cos_sq (arcsin x),
begin
  rw [← eq_sub_iff_add_eq', ← sqrt_inj (sq_nonneg _) (sub_nonneg.2 (sin_sq_le_one (arcsin x))),
    sq, sqrt_mul_self (cos_arcsin_nonneg _)] at this,
  rw [this, sin_arcsin hx₁ hx₂],
end

/-- Inverse of the `cos` function, returns values in the range `0 ≤ arccos x` and `arccos x ≤ π`.
  If the argument is not between `-1` and `1` it defaults to `π / 2` -/
@[pp_nodot] noncomputable def arccos (x : ℝ) : ℝ :=
π / 2 - arcsin x

lemma arccos_eq_pi_div_two_sub_arcsin (x : ℝ) : arccos x = π / 2 - arcsin x := rfl

lemma arcsin_eq_pi_div_two_sub_arccos (x : ℝ) : arcsin x = π / 2 - arccos x :=
by simp [arccos]

lemma arccos_le_pi (x : ℝ) : arccos x ≤ π :=
by unfold arccos; linarith [neg_pi_div_two_le_arcsin x]

lemma arccos_nonneg (x : ℝ) : 0 ≤ arccos x :=
by unfold arccos; linarith [arcsin_le_pi_div_two x]

lemma cos_arccos {x : ℝ} (hx₁ : -1 ≤ x) (hx₂ : x ≤ 1) : cos (arccos x) = x :=
by rw [arccos, cos_pi_div_two_sub, sin_arcsin hx₁ hx₂]

lemma arccos_cos {x : ℝ} (hx₁ : 0 ≤ x) (hx₂ : x ≤ π) : arccos (cos x) = x :=
by rw [arccos, ← sin_pi_div_two_sub, arcsin_sin]; simp [sub_eq_add_neg]; linarith

lemma strict_anti_on_arccos : strict_anti_on arccos (Icc (-1) 1) :=
λ x hx y hy h, sub_lt_sub_left (strict_mono_on_arcsin hx hy h) _

lemma arccos_inj_on : inj_on arccos (Icc (-1) 1) := strict_anti_on_arccos.inj_on

lemma arccos_inj {x y : ℝ} (hx₁ : -1 ≤ x) (hx₂ : x ≤ 1) (hy₁ : -1 ≤ y) (hy₂ : y ≤ 1) :
  arccos x = arccos y ↔ x = y :=
arccos_inj_on.eq_iff ⟨hx₁, hx₂⟩ ⟨hy₁, hy₂⟩

@[simp] lemma arccos_zero : arccos 0 = π / 2 := by simp [arccos]

@[simp] lemma arccos_one : arccos 1 = 0 := by simp [arccos]

@[simp] lemma arccos_neg_one : arccos (-1) = π := by simp [arccos, add_halves]

@[simp] lemma arccos_eq_zero {x} : arccos x = 0 ↔ 1 ≤ x :=
by simp [arccos, sub_eq_zero]

@[simp] lemma arccos_eq_pi_div_two {x} : arccos x = π / 2 ↔ x = 0 :=
by simp [arccos, sub_eq_iff_eq_add]

@[simp] lemma arccos_eq_pi {x} : arccos x = π ↔ x ≤ -1 :=
by rw [arccos, sub_eq_iff_eq_add, ← sub_eq_iff_eq_add', div_two_sub_self, neg_pi_div_two_eq_arcsin]

lemma arccos_neg (x : ℝ) : arccos (-x) = π - arccos x :=
by rw [← add_halves π, arccos, arcsin_neg, arccos, add_sub_assoc, sub_sub_self, sub_neg_eq_add]

lemma sin_arccos {x : ℝ} (hx₁ : -1 ≤ x) (hx₂ : x ≤ 1) : sin (arccos x) = sqrt (1 - x ^ 2) :=
by rw [arccos_eq_pi_div_two_sub_arcsin, sin_pi_div_two_sub, cos_arcsin hx₁ hx₂]

@[continuity]
lemma continuous_arccos : continuous arccos := continuous_const.sub continuous_arcsin

end real


/-
2. Prove the following identities, replacing the “sorry” placeholders with actual proofs. These require classical reasoning.
-/

open classical

variables p q r s : Prop

example : (p → r ∨ s) → ((p → r) ∨ (p → s)) :=
    assume h : p → r ∨ s,
    (em p).elim
    (
        assume hp : p,
        have hrs : r ∨ s, from h hp,
        hrs.elim
        (
            assume hr : r,
            show (p → r) ∨ (p → s),
            from or.inl (assume hp : p, hr)
        )
        (
            assume hs : s,
            show (p → r) ∨ (p → s),
            from or.inr (assume hp : p, hs)
        )
    )
    (
        assume hnp : ¬p,
        suffices hpr : p → r, from or.inl hpr,
        show p → r, from
            assume hp : p,
            show r, from false.elim (hnp hp)
    )
-- short version
example : (p → r ∨ s) → ((p → r) ∨ (p → s)) :=
    λ h,
    (em p).elim
    (
        λ hp,
        (h hp).elim
        (λ hr, or.inl (λ hp : p, hr))
        (λ hs, or.inr (λ hp : p, hs))
    )
    (λ hnp, or.inl (λ hp, absurd hp hnp))

example : ¬(p ∧ q) → ¬p ∨ ¬q :=
    assume h : ¬(p ∧ q),
    or.elim (em p)
    (
        assume hp : p,
        have hnq : ¬q, from
            assume hq : q,
            h ⟨hp, hq⟩,
        or.inr hnq
    )
    (
        assume hnp : ¬p,
        or.inl hnp
    )
-- short version
example : ¬(p ∧ q) → ¬p ∨ ¬q :=
    λ h,
    or.elim (em p)
    (λ hp, or.inr (λ hq, h ⟨hp, hq⟩))
    (λ hnp, or.inl hnp)

example : ¬(p → q) → p ∧ ¬q :=
    assume h : ¬(p → q),
    (em p).elim
    (
        assume hp : p,
        (em q).elim
        (
            assume hq : q,
            have hpimplq : p → q, from
                assume hp' : p,
                hq,
            show p ∧ ¬q, from false.elim (h hpimplq)
        )
        (
            assume hnq : ¬q,
            show p ∧ ¬q, from ⟨hp, hnq⟩
        )
    )
    (
        assume hnp : ¬p,
        have hpimplq : p → q, from
            assume hp : p,
            false.elim (hnp hp),
        false.elim (h hpimplq)
    )
-- short version
example : ¬(p → q) → p ∧ ¬q :=
    λ h,
    (em p).elim
    (
        λ hp : p,
        (em q).elim
        (λ hq, absurd (λ hp', hq) h)
        (λ hnq, ⟨hp, hnq⟩)
    )
    (
        λ hnp,
        false.elim (h (λ hp, absurd hp hnp))
    )

example : (p → q) → (¬p ∨ q) :=
    assume h : p → q,
    (em p).elim
    (
        assume hp : p,
        or.inr (h hp)
    )
    (
        assume hnp : ¬p,
        or.inl hnp
    )
-- short version
example : (p → q) → (¬p ∨ q) :=
    λ h,
    (em p).elim
    (λ hp, or.inr (h hp))
    (λ hnp, or.inl hnp)

example : (¬q → ¬p) → (p → q) :=
    assume h : ¬q → ¬p,
    assume hp : p,
    (em q).elim
    (
        assume hq : q,
        hq
    )
    (
        assume hnq : ¬q,
        absurd hp (h hnq)
    )
-- short version
example : (¬q → ¬p) → (p → q) :=
    λ h,
    λ hp,
    (em q).elim
    id
    (λ hnq, absurd hp (h hnq))

example : p ∨ ¬p :=
    em p

example : (((p → q) → p) → p) :=
    assume h : (p → q) → p,
    (em p).elim
    (
        assume hp : p,
        hp
    )
    (
        assume hnp : ¬p,
        have hpimplq : p → q, from
            assume hp : p,
            absurd hp hnp,
        absurd (h hpimplq) hnp
    )
-- short version
example : (((p → q) → p) → p) :=
    λ h,
    (em p).elim
    id
    (λ hnp, absurd (h (λ hp : p, absurd hp hnp)) hnp)


/-
Copyright (c) 2023 Eric Wieser. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Eric Wieser
-/
import analysis.special_functions.exponential
/-!
# Trigonometric functions as sums of infinite series

In this file we express trigonometric functions in terms of their series expansion.

## Main results

* `complex.has_sum_cos`, `complex.tsum_cos`: `complex.cos` as the sum of an infinite series.
* `real.has_sum_cos`, `real.tsum_cos`: `real.cos` as the sum of an infinite series.
* `complex.has_sum_sin`, `complex.tsum_sin`: `complex.sin` as the sum of an infinite series.
* `real.has_sum_sin`, `real.tsum_sin`: `real.sin` as the sum of an infinite series.
-/

open_locale nat

/-! ### `cos` and `sin` for `ℝ` and `ℂ` -/

section sin_cos

lemma complex.has_sum_cos' (z : ℂ) :
  has_sum (λ n : ℕ, (z * complex.I) ^ (2 * n) / ↑(2 * n)!) (complex.cos z) :=
begin
  rw [complex.cos, complex.exp_eq_exp_ℂ],
  have := ((exp_series_div_has_sum_exp ℂ (z * complex.I)).add
          (exp_series_div_has_sum_exp ℂ (-z * complex.I))).div_const 2,
  replace := ((nat.div_mod_equiv 2)).symm.has_sum_iff.mpr this,
  dsimp [function.comp] at this,
  simp_rw [←mul_comm 2 _] at this,
  refine this.prod_fiberwise (λ k, _),
  dsimp only,
  convert has_sum_fintype (_ : fin 2 → ℂ) using 1,
  rw fin.sum_univ_two,
  simp_rw [fin.coe_zero, fin.coe_one, add_zero, pow_succ', pow_mul,
    mul_pow, neg_sq, ←two_mul, neg_mul, mul_neg, neg_div, add_right_neg, zero_div, add_zero,
    mul_div_cancel_left _ (two_ne_zero : (2 : ℂ) ≠ 0)],
end

lemma complex.has_sum_sin' (z : ℂ) :
  has_sum (λ n : ℕ, (z * complex.I) ^ (2 * n + 1) / ↑(2 * n + 1)! / complex.I) (complex.sin z) :=
begin
  rw [complex.sin, complex.exp_eq_exp_ℂ],
  have := (((exp_series_div_has_sum_exp ℂ (-z * complex.I)).sub
          (exp_series_div_has_sum_exp ℂ (z * complex.I))).mul_right complex.I).div_const 2,
  replace := ((nat.div_mod_equiv 2)).symm.has_sum_iff.mpr this,
  dsimp [function.comp] at this,
  simp_rw [←mul_comm 2 _] at this,
  refine this.prod_fiberwise (λ k, _),
  dsimp only,
  convert has_sum_fintype (_ : fin 2 → ℂ) using 1,
  rw fin.sum_univ_two,
  simp_rw [fin.coe_zero, fin.coe_one, add_zero, pow_succ', pow_mul,
    mul_pow, neg_sq, sub_self, zero_mul, zero_div, zero_add,
    neg_mul, mul_neg, neg_div, ← neg_add', ←two_mul, neg_mul, neg_div, mul_assoc,
    mul_div_cancel_left _ (two_ne_zero : (2 : ℂ) ≠ 0), complex.div_I],
end

/-- The power series expansion of `complex.cos`. -/
lemma complex.has_sum_cos (z : ℂ) :
  has_sum (λ n : ℕ, ((-1) ^ n) * z ^ (2 * n) / ↑(2 * n)!) (complex.cos z) :=
begin
  convert complex.has_sum_cos' z using 1,
  simp_rw [mul_pow, pow_mul, complex.I_sq, mul_comm]
end

/-- The power series expansion of `complex.sin`. -/
lemma complex.has_sum_sin (z : ℂ) :
  has_sum (λ n : ℕ, ((-1) ^ n) * z ^ (2 * n + 1) / ↑(2 * n + 1)!) (complex.sin z) :=
begin
  convert complex.has_sum_sin' z using 1,
  simp_rw [mul_pow, pow_succ', pow_mul, complex.I_sq, ←mul_assoc,
    mul_div_assoc, div_right_comm, div_self complex.I_ne_zero, mul_comm _ ((-1 : ℂ)^_), mul_one_div,
    mul_div_assoc, mul_assoc]
end

lemma complex.cos_eq_tsum' (z : ℂ) :
  complex.cos z = ∑' n : ℕ, (z * complex.I) ^ (2 * n) / ↑(2 * n)! :=
(complex.has_sum_cos' z).tsum_eq.symm

lemma complex.sin_eq_tsum' (z : ℂ) :
  complex.sin z = ∑' n : ℕ, (z * complex.I) ^ (2 * n + 1) / ↑(2 * n + 1)! / complex.I :=
(complex.has_sum_sin' z).tsum_eq.symm

lemma complex.cos_eq_tsum (z : ℂ) :
  complex.cos z = ∑' n : ℕ, ((-1) ^ n) * z ^ (2 * n) / ↑(2 * n)! :=
(complex.has_sum_cos z).tsum_eq.symm

lemma complex.sin_eq_tsum (z : ℂ) :
  complex.sin z = ∑' n : ℕ, ((-1) ^ n) * z ^ (2 * n + 1) / ↑(2 * n + 1)! :=
(complex.has_sum_sin z).tsum_eq.symm

/-- The power series expansion of `real.cos`. -/
lemma real.has_sum_cos (r : ℝ) :
  has_sum (λ n : ℕ, ((-1) ^ n) * r ^ (2 * n) / ↑(2 * n)!) (real.cos r) :=
by exact_mod_cast complex.has_sum_cos r

/-- The power series expansion of `real.sin`. -/
lemma real.has_sum_sin (r : ℝ) :
  has_sum (λ n : ℕ, ((-1) ^ n) * r ^ (2 * n + 1) / ↑(2 * n + 1)!) (real.sin r) :=
by exact_mod_cast complex.has_sum_sin r

lemma real.cos_eq_tsum (r : ℝ) :
  real.cos r = ∑' n : ℕ, ((-1) ^ n) * r ^ (2 * n) / ↑(2 * n)! :=
(real.has_sum_cos r).tsum_eq.symm

lemma real.sin_eq_tsum (r : ℝ) :
  real.sin r = ∑' n : ℕ, ((-1) ^ n) * r ^ (2 * n + 1) / ↑(2 * n + 1)! :=
(real.has_sum_sin r).tsum_eq.symm

end sin_cos


/-
Copyright (c) 2019 Patrick Massot All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Patrick Massot, Simon Hudon

A tactic pushing negations into an expression
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.logic.basic
import Mathlib.algebra.order
import Mathlib.PostPort

universes u

namespace Mathlib

namespace push_neg

theorem not_not_eq (p : Prop) : (¬¬p) = p :=
  propext not_not

theorem not_and_eq (p : Prop) (q : Prop) : (¬(p ∧ q)) = (p → ¬q) :=
  propext not_and

theorem not_or_eq (p : Prop) (q : Prop) : (¬(p ∨ q)) = (¬p ∧ ¬q) :=
  propext not_or_distrib

theorem not_forall_eq {α : Sort u} (s : α → Prop) : (¬∀ (x : α), s x) = ∃ (x : α), ¬s x :=
  propext not_forall

theorem not_exists_eq {α : Sort u} (s : α → Prop) : (¬∃ (x : α), s x) = ∀ (x : α), ¬s x :=
  propext not_exists

theorem not_implies_eq (p : Prop) (q : Prop) : (¬(p → q)) = (p ∧ ¬q) :=
  propext not_imp

theorem classical.implies_iff_not_or (p : Prop) (q : Prop) : p → q ↔ ¬p ∨ q :=
  imp_iff_not_or

theorem not_eq {α : Sort u} (a : α) (b : α) : ¬a = b ↔ a ≠ b :=
  iff.rfl

theorem not_le_eq {β : Type u} [linear_order β] (a : β) (b : β) : (¬a ≤ b) = (b < a) :=
  propext not_le

theorem not_lt_eq {β : Type u} [linear_order β] (a : β) (b : β) : (¬a < b) = (b ≤ a) :=
  propext not_lt

end push_neg

/--
Push negations in the goal of some assumption.

For instance, a hypothesis `h : ¬ ∀ x, ∃ y, x ≤ y` will be transformed by `push_neg at h` into
`h : ∃ x, ∀ y, y < x`. Variables names are conserved.

This tactic pushes negations inside expressions. For instance, given an assumption
```lean
h : ¬ ∀ ε > 0, ∃ δ > 0, ∀ x, |x - x₀| ≤ δ → |f x - y₀| ≤ ε)
```
writing `push_neg at h` will turn `h` into
```lean
h : ∃ ε, ε > 0 ∧ ∀ δ, δ > 0 → (∃ x, |x - x₀| ≤ δ ∧ ε < |f x - y₀|),
```

(the pretty printer does *not* use the abreviations `∀ δ > 0` and `∃ ε > 0` but this issue
has nothing to do with `push_neg`).
Note that names are conserved by this tactic, contrary to what would happen with `simp`
using the relevant lemmas. One can also use this tactic at the goal using `push_neg`,
at every assumption and the goal using `push_neg at *` or at selected assumptions and the goal
using say `push_neg at h h' ⊢` as usual.
-/
theorem imp_of_not_imp_not (P : Prop) (Q : Prop) : (¬Q → ¬P) → P → Q :=
  fun (h : ¬Q → ¬P) (hP : P) => classical.by_contradiction fun (h' : ¬Q) => h h' hP

/-- Matches either an identifier "h" or a pair of identifiers "h with k" -/
/--
Transforms the goal into its contrapositive.

* `contrapose`     turns a goal `P → Q` into `¬ Q → ¬ P`
* `contrapose!`    turns a goal `P → Q` into `¬ Q → ¬ P` and pushes negations inside `P` and `Q`
  using `push_neg`
* `contrapose h`   first reverts the local assumption `h`, and then uses `contrapose` and `intro h`
* `contrapose! h`  first reverts the local assumption `h`, and then uses `contrapose!` and `intro h`
* `contrapose h with new_h` uses the name `new_h` for the introduced hypothesis
-/


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

! This file was ported from Lean 3 source module data.multiset.nat_antidiagonal
! leanprover-community/mathlib commit f2f413b9d4be3a02840d0663dace76e8fe3da053
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Data.Multiset.Nodup
import Mathbin.Data.List.NatAntidiagonal

/-!
# Antidiagonals in ℕ × ℕ as multisets

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

This file defines the antidiagonals of ℕ × ℕ as multisets: the `n`-th antidiagonal is the multiset
of pairs `(i, j)` such that `i + j = n`. This is useful for polynomial multiplication and more
generally for sums going from `0` to `n`.

## Notes

This refines file `data.list.nat_antidiagonal` and is further refined by file
`data.finset.nat_antidiagonal`.
-/

namespace Multiset

namespace Nat

#print Multiset.Nat.antidiagonal /-
/-- The antidiagonal of a natural number `n` is
    the multiset of pairs `(i, j)` such that `i + j = n`. -/
def antidiagonal (n : ℕ) : Multiset (ℕ × ℕ) :=
  List.Nat.antidiagonal n
#align multiset.nat.antidiagonal Multiset.Nat.antidiagonal
-/

#print Multiset.Nat.mem_antidiagonal /-
/-- A pair (i, j) is contained in the antidiagonal of `n` if and only if `i + j = n`. -/
@[simp]
theorem mem_antidiagonal {n : ℕ} {x : ℕ × ℕ} : x ∈ antidiagonal n ↔ x.1 + x.2 = n := by
  rw [antidiagonal, mem_coe, List.Nat.mem_antidiagonal]
#align multiset.nat.mem_antidiagonal Multiset.Nat.mem_antidiagonal
-/

/- warning: multiset.nat.card_antidiagonal -> Multiset.Nat.card_antidiagonal is a dubious translation:
lean 3 declaration is
  forall (n : Nat), Eq.{1} Nat (coeFn.{1, 1} (AddMonoidHom.{0, 0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) Nat (AddMonoid.toAddZeroClass.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (AddRightCancelMonoid.toAddMonoid.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (AddCancelMonoid.toAddRightCancelMonoid.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (AddCancelCommMonoid.toAddCancelMonoid.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (OrderedCancelAddCommMonoid.toCancelAddCommMonoid.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (Multiset.orderedCancelAddCommMonoid.{0} (Prod.{0, 0} Nat Nat))))))) (AddMonoid.toAddZeroClass.{0} Nat Nat.addMonoid)) (fun (_x : AddMonoidHom.{0, 0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) Nat (AddMonoid.toAddZeroClass.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (AddRightCancelMonoid.toAddMonoid.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (AddCancelMonoid.toAddRightCancelMonoid.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (AddCancelCommMonoid.toAddCancelMonoid.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (OrderedCancelAddCommMonoid.toCancelAddCommMonoid.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (Multiset.orderedCancelAddCommMonoid.{0} (Prod.{0, 0} Nat Nat))))))) (AddMonoid.toAddZeroClass.{0} Nat Nat.addMonoid)) => (Multiset.{0} (Prod.{0, 0} Nat Nat)) -> Nat) (AddMonoidHom.hasCoeToFun.{0, 0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) Nat (AddMonoid.toAddZeroClass.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (AddRightCancelMonoid.toAddMonoid.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (AddCancelMonoid.toAddRightCancelMonoid.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (AddCancelCommMonoid.toAddCancelMonoid.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (OrderedCancelAddCommMonoid.toCancelAddCommMonoid.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (Multiset.orderedCancelAddCommMonoid.{0} (Prod.{0, 0} Nat Nat))))))) (AddMonoid.toAddZeroClass.{0} Nat Nat.addMonoid)) (Multiset.card.{0} (Prod.{0, 0} Nat Nat)) (Multiset.Nat.antidiagonal n)) (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))
but is expected to have type
  forall (n : Nat), Eq.{1} ((fun (x._@.Mathlib.Algebra.Hom.Group._hyg.403 : Multiset.{0} (Prod.{0, 0} Nat Nat)) => Nat) (Multiset.Nat.antidiagonal n)) (FunLike.coe.{1, 1, 1} (AddMonoidHom.{0, 0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) Nat (AddMonoid.toAddZeroClass.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (AddRightCancelMonoid.toAddMonoid.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (AddCancelMonoid.toAddRightCancelMonoid.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (AddCancelCommMonoid.toAddCancelMonoid.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (OrderedCancelAddCommMonoid.toCancelAddCommMonoid.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (Multiset.instOrderedCancelAddCommMonoidMultiset.{0} (Prod.{0, 0} Nat Nat))))))) (AddMonoid.toAddZeroClass.{0} Nat Nat.addMonoid)) (Multiset.{0} (Prod.{0, 0} Nat Nat)) (fun (_x : Multiset.{0} (Prod.{0, 0} Nat Nat)) => (fun (x._@.Mathlib.Algebra.Hom.Group._hyg.403 : Multiset.{0} (Prod.{0, 0} Nat Nat)) => Nat) _x) (AddHomClass.toFunLike.{0, 0, 0} (AddMonoidHom.{0, 0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) Nat (AddMonoid.toAddZeroClass.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (AddRightCancelMonoid.toAddMonoid.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (AddCancelMonoid.toAddRightCancelMonoid.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (AddCancelCommMonoid.toAddCancelMonoid.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (OrderedCancelAddCommMonoid.toCancelAddCommMonoid.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (Multiset.instOrderedCancelAddCommMonoidMultiset.{0} (Prod.{0, 0} Nat Nat))))))) (AddMonoid.toAddZeroClass.{0} Nat Nat.addMonoid)) (Multiset.{0} (Prod.{0, 0} Nat Nat)) Nat (AddZeroClass.toAdd.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (AddMonoid.toAddZeroClass.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (AddRightCancelMonoid.toAddMonoid.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (AddCancelMonoid.toAddRightCancelMonoid.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (AddCancelCommMonoid.toAddCancelMonoid.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (OrderedCancelAddCommMonoid.toCancelAddCommMonoid.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (Multiset.instOrderedCancelAddCommMonoidMultiset.{0} (Prod.{0, 0} Nat Nat)))))))) (AddZeroClass.toAdd.{0} Nat (AddMonoid.toAddZeroClass.{0} Nat Nat.addMonoid)) (AddMonoidHomClass.toAddHomClass.{0, 0, 0} (AddMonoidHom.{0, 0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) Nat (AddMonoid.toAddZeroClass.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (AddRightCancelMonoid.toAddMonoid.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (AddCancelMonoid.toAddRightCancelMonoid.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (AddCancelCommMonoid.toAddCancelMonoid.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (OrderedCancelAddCommMonoid.toCancelAddCommMonoid.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (Multiset.instOrderedCancelAddCommMonoidMultiset.{0} (Prod.{0, 0} Nat Nat))))))) (AddMonoid.toAddZeroClass.{0} Nat Nat.addMonoid)) (Multiset.{0} (Prod.{0, 0} Nat Nat)) Nat (AddMonoid.toAddZeroClass.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (AddRightCancelMonoid.toAddMonoid.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (AddCancelMonoid.toAddRightCancelMonoid.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (AddCancelCommMonoid.toAddCancelMonoid.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (OrderedCancelAddCommMonoid.toCancelAddCommMonoid.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (Multiset.instOrderedCancelAddCommMonoidMultiset.{0} (Prod.{0, 0} Nat Nat))))))) (AddMonoid.toAddZeroClass.{0} Nat Nat.addMonoid) (AddMonoidHom.addMonoidHomClass.{0, 0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) Nat (AddMonoid.toAddZeroClass.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (AddRightCancelMonoid.toAddMonoid.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (AddCancelMonoid.toAddRightCancelMonoid.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (AddCancelCommMonoid.toAddCancelMonoid.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (OrderedCancelAddCommMonoid.toCancelAddCommMonoid.{0} (Multiset.{0} (Prod.{0, 0} Nat Nat)) (Multiset.instOrderedCancelAddCommMonoidMultiset.{0} (Prod.{0, 0} Nat Nat))))))) (AddMonoid.toAddZeroClass.{0} Nat Nat.addMonoid)))) (Multiset.card.{0} (Prod.{0, 0} Nat Nat)) (Multiset.Nat.antidiagonal n)) (HAdd.hAdd.{0, 0, 0} Nat ((fun (x._@.Mathlib.Algebra.Hom.Group._hyg.403 : Multiset.{0} (Prod.{0, 0} Nat Nat)) => Nat) (Multiset.Nat.antidiagonal n)) Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} ((fun (x._@.Mathlib.Algebra.Hom.Group._hyg.403 : Multiset.{0} (Prod.{0, 0} Nat Nat)) => Nat) (Multiset.Nat.antidiagonal n)) 1 (instOfNatNat 1)))
Case conversion may be inaccurate. Consider using '#align multiset.nat.card_antidiagonal Multiset.Nat.card_antidiagonalₓ'. -/
/-- The cardinality of the antidiagonal of `n` is `n+1`. -/
@[simp]
theorem card_antidiagonal (n : ℕ) : (antidiagonal n).card = n + 1 := by
  rw [antidiagonal, coe_card, List.Nat.length_antidiagonal]
#align multiset.nat.card_antidiagonal Multiset.Nat.card_antidiagonal

#print Multiset.Nat.antidiagonal_zero /-
/-- The antidiagonal of `0` is the list `[(0, 0)]` -/
@[simp]
theorem antidiagonal_zero : antidiagonal 0 = {(0, 0)} :=
  rfl
#align multiset.nat.antidiagonal_zero Multiset.Nat.antidiagonal_zero
-/

#print Multiset.Nat.nodup_antidiagonal /-
/-- The antidiagonal of `n` does not contain duplicate entries. -/
@[simp]
theorem nodup_antidiagonal (n : ℕ) : Nodup (antidiagonal n) :=
  coe_nodup.2 <| List.Nat.nodup_antidiagonal n
#align multiset.nat.nodup_antidiagonal Multiset.Nat.nodup_antidiagonal
-/

#print Multiset.Nat.antidiagonal_succ /-
@[simp]
theorem antidiagonal_succ {n : ℕ} :
    antidiagonal (n + 1) = (0, n + 1) ::ₘ (antidiagonal n).map (Prod.map Nat.succ id) := by
  simp only [antidiagonal, List.Nat.antidiagonal_succ, coe_map, cons_coe]
#align multiset.nat.antidiagonal_succ Multiset.Nat.antidiagonal_succ
-/

#print Multiset.Nat.antidiagonal_succ' /-
theorem antidiagonal_succ' {n : ℕ} :
    antidiagonal (n + 1) = (n + 1, 0) ::ₘ (antidiagonal n).map (Prod.map id Nat.succ) := by
  rw [antidiagonal, List.Nat.antidiagonal_succ', ← coe_add, add_comm, antidiagonal, coe_map,
    coe_add, List.singleton_append, cons_coe]
#align multiset.nat.antidiagonal_succ' Multiset.Nat.antidiagonal_succ'
-/

#print Multiset.Nat.antidiagonal_succ_succ' /-
theorem antidiagonal_succ_succ' {n : ℕ} :
    antidiagonal (n + 2) =
      (0, n + 2) ::ₘ (n + 2, 0) ::ₘ (antidiagonal n).map (Prod.map Nat.succ Nat.succ) :=
  by
  rw [antidiagonal_succ, antidiagonal_succ', map_cons, map_map, Prod_map]
  rfl
#align multiset.nat.antidiagonal_succ_succ' Multiset.Nat.antidiagonal_succ_succ'
-/

#print Multiset.Nat.map_swap_antidiagonal /-
theorem map_swap_antidiagonal {n : ℕ} : (antidiagonal n).map Prod.swap = antidiagonal n := by
  rw [antidiagonal, coe_map, List.Nat.map_swap_antidiagonal, coe_reverse]
#align multiset.nat.map_swap_antidiagonal Multiset.Nat.map_swap_antidiagonal
-/

end Nat

end Multiset


import ..library.src_field
import tactic

namespace mth1001

namespace myreal

section grouplaws

variables {R : Type} [comm_group R]

lemma add_comm (x y : R) : x + y = y + x := comm_group.add_comm x y
lemma add_assoc (x y z : R) : (x + y) + z = x + (y + z) := comm_group.add_assoc x y z
lemma add_zero (x : R) : x + 0 = x := comm_group.add_zero x
lemma zero_add (x : R) : 0 + x = x := by {rw [add_comm, add_zero]}
lemma add_neg (x : R) : x + (-x) = 0 := comm_group.add_inv x
lemma neg_add (x : R) : (-x) + x = 0 := by {rw [add_comm, add_neg]}

def add_identity (u : R) := ∀ x : R, (x + u = x) ∧ (u + x = x)

example : add_identity (0 : R) :=
begin
  intro x,
  exact and.intro (add_zero x) (zero_add x),
end

lemma has_add_inverse : ∀ a : R, ∃ b : R, (a + b = 0) ∧ (b + a = 0) :=
begin
  intro a,
  use (-a),
  exact and.intro (add_neg a) (neg_add a)
end

theorem add_identity_unique {a b : R} (h₁ : add_identity a) (h₂ : add_identity b)
  : a = b :=
begin
  specialize h₁ b, -- `h₁ : (b + a = b) ∧ (a + b = b)`
  specialize h₂ a, -- `h₂ : (a + b = a) ∧ (b + a = a)`
  calc  a = a + b : h₂.left.symm -- If `h : x = y`, then `h.symm : y = x`.
      ... = b     : h₁.right
end

def add_inverse (y x : R) := (x + y = 0) ∧ (y + x = 0)

theorem add_inverse_unique {a b x : R} (h₁ : add_inverse a x) (h₂ : add_inverse b x)
  : a = b :=
calc  a = a + 0        : (add_zero a).symm
    ... = a + (x + b)  : by rw h₂.left
    ... = (a + x) + b  : by rw add_assoc
    ... = 0 + b        : by rw h₁.right
    ... = b            : zero_add b

-- Exercise 146:
lemma add_left_eq_self_mp {x a : R} : x + a = a → x = 0 :=
begin
  sorry
end

-- Exercise 147:
lemma add_left_eq_self_mpr {x a : R} : x = 0 → x + a = a :=
begin
  sorry
end

-- Exercise 148:
-- You can prove the following in one line using the above two results.
theorem add_left_eq_self (x a : R) : x + a = a ↔ x = 0:=
sorry

-- Exercise 149:
theorem add_left_inj (x a b : R) : a + x = b + x ↔ a = b :=
begin
  sorry
end

lemma sub_eq_add_neg (x y : R) : x - y = x + (-y) := rfl

-- Exercise 150:
-- You can do the following with two rewrites. Use `add_zero` and `neg_add`
lemma neg_zero : -(0 : R) = 0 :=
sorry

-- Exercise 151:
lemma sub_zero (a : R) : a - 0 = a :=
sorry

-- Exercise 152:
-- The following can be proved in one line as a sequences of rewrites.
theorem sub_eq_zero_iff_eq (x y : R) : x - y = 0 ↔ x = y :=
sorry

-- Exercise 153:
-- The following can be proved in one line as a sequences of rewrites.
theorem neg_neg (x : R) : - - x = x :=
sorry

-- Exercise 154:
-- Do the following using proof by calculation.
lemma neg_add_eq_neg_add_neg (x y : R) : -(x + y) = -y + - x :=
sorry

end grouplaws

section fieldlaws

variables {R : Type} [myfield R]

lemma mul_comm (x y : R) : x * y = y * x := myfield.mul_comm x y
lemma mul_assoc (x y z : R) : (x * y) * z = x * (y * z) := myfield.mul_assoc x y z
lemma mul_one (x : R) : x * 1 = x := myfield.mul_one x
lemma one_mul (x : R) : 1 * x = x := by { rw [mul_comm, mul_one] }
lemma mul_inv (x : R) : x ≠ 0 → x * x⁻¹  = 1 := myfield.mul_inv x
lemma inv_mul (x : R) : x ≠ 0 → x⁻¹ * x = 1 := by {intro h, rw [mul_comm, mul_inv x h], }
lemma mul_add (x y z : R) : x * (y + z) = x * y + x * z := myfield.mul_add x y z
lemma zero_ne_one : (0 : R) ≠ (1 : R) := myfield.zero_ne_one

def mul_identity (u : R) := ∀ x : R, (x * u = x) ∧ (u * x = x)

-- Exercise 155:
-- Use `add_mul` and `add_comm` to prove the following.
theorem add_mul (x y z : R) : (x + y) * z = x * z + y * z :=
begin
  sorry
end

-- Exercise 156:
theorem mul_identity_unique {a b : R} (h₁ : mul_identity a) (h₂ : mul_identity b)
  : a = b :=
begin
  sorry
end

def mul_inverse (y x : R) := (x * y = 1) ∧ (y * x = 1)

-- Exercise 157:
theorem mul_inverse_unique {a b x : R} (h₁ : mul_inverse a x) (h₂ : mul_inverse b x)
  : a = b :=
sorry

-- Exercise 158:
-- This exercise is somewhat challenging.
theorem mul_zero {x : R} : x * (0 : R) = (0 : R) :=
begin
  have h₁ : x * 0 = x * 0 + x * 0,
    calc x * 0
          = x * (0 + 0)   : by rw add_zero (0 : R)
      ... = x * 0 + x * 0 : by rw mul_add,
  sorry
end

-- Exercise 159:
-- Whereas the next result is a simple consequence of the previous result.
theorem zero_mul {x : R} : (0 : R) * x = (0 : R) :=
begin
  sorry
end

-- Exercise 160:
theorem neg_one_mul (x : R) : (-1) * x = -x :=
begin
  sorry
end

-- Exercise 161:
-- Use the result above to prove this lemma.
lemma neg_one_mul_neg_one : (-(1 : R)) * (-1) = 1 :=
sorry

-- Exercise 162:
lemma neg_mul_eq_mul_neg (x y : R) : -(x * y) = x * (-y) :=
sorry

lemma neg_mul_neg_self (x : R) : (-x)*(-x) = x * x :=
by rw [←neg_one_mul, mul_assoc, mul_comm x _, ←mul_assoc, ←mul_assoc, neg_one_mul_neg_one, one_mul]

-- Exercise 163:
lemma neg_mul_neg (x y : R) : (-x) * (-y) = x * y :=
begin
  sorry
end

-- Exercise 164:
lemma one_inv : (1 : R)⁻¹ = (1 : R) :=
sorry
-- Exercise 165:
theorem mul_sub (x y z : R) : x * (y - z) = x * y - x * z :=
begin
  sorry
end

-- Exercise 166:
theorem mul_left_inj' (x a b : R) (h₁ : x ≠ 0): a * x = b * x ↔ a = b :=
begin
  sorry
end

open_locale classical

-- Exercise 167:
-- This is a challenging exercise.
-- Hint 1. Do a `by_cases` proof, depending on whether `x = 0` or not.
-- Hint 2. Use the fact that a non-zero real number has a multiplicative inverse.
lemma eq_zero_of_not_eq_zero_of_mul_not_eq_zero (x b : R) (h₁ : b ≠ 0) (h₂ : x * b = 0) : x = 0 :=
begin
  sorry
end

-- Exercise 168:
-- The following is a corollary of the result above.
lemma eq_zero_or_eq_zero_of_mul_eq_zero (x y : R) (h : x * y = 0) : x = 0 ∨ y = 0 :=
begin
  sorry
end

-- Exercise 169:
lemma mul_inv' (x y : R) (h₁ : x ≠ 0) (h₂ : y ≠ 0) : (x * y)⁻¹ = y⁻¹ * x⁻¹ :=
begin
  have h : x * y ≠ 0,
  { sorry, },
  rw ←(mul_left_inj' _ _ _ h₁),
  sorry
end

-- Exercise 170:
theorem inv_ne_zero {a : R} : a ≠ 0 → a⁻¹ ≠ 0 :=
begin
  sorry
end

-- Exercise 171:
theorem inv_inv' (a : R) (h : a ≠ 0 ) : (a⁻¹)⁻¹ = a :=
begin
  sorry  end

end fieldlaws

section powers

variables {R : Type} [myfield R]

def pow1 : R → ℕ → R
| x 0            := (1 : R)
| x (nat.succ n) := (pow1 x n) * x  -- `nat.succ` is the Lean version of our function `S`

-- Exercise 172:
example (x : R) : pow1 x 2 = x * x :=
begin
  sorry  end

-- Exercise 173:
-- Prove the following by induction on `n`.
theorem pow_ne_zero (x : R) (n : ℕ) (h : x ≠ 0) : pow1 x n ≠ 0 :=
begin
  sorry
end

-- Exercise 174:
-- Use induction. Make use of `nat.add_zero`.
theorem pow1_add (x : R) (m n : ℕ) : pow1 x (m + n) = (pow1 x m) * (pow1 x n) :=
begin
  sorry
end

-- Exercise 175:
-- Use induction. Make use of `nat.mul_zero`, `nat.succ_eq_add_one`, and `nat.mul_one`.begin
-- You'll need `left_distrib`, a synonym of `mul_add` for `ℕ`.
theorem pow1_mul (x : R) (m n : ℕ) : pow1 x (m*n) = pow1 (pow1 x m) n :=
begin
  sorry
end

lemma sub_succ_eq_pred_sub_of_le (b k : ℕ) : nat.succ k ≤ b → (b-(nat.succ k)) = (b - 1) - k :=
begin
  induction k with m hm,
  { rw nat.sub_zero, intro h, refl, },
  { intro h,
    exact (nat.pred_sub b (nat.succ m)).symm, },
end

-- Exercise 176:
-- The exercise below is very hard. Use the result above together with any other appropriate
-- results concerning the natural numbers from mathlib.
theorem pow1_sub (x : R) (h₁ : x ≠ 0) (n : ℕ) :
  ∀ m : ℕ, n ≤ m → pow1 x (m - n) = (pow1 x m) * (pow1 x n)⁻¹ :=
begin
  sorry
end

def pow2 (x : R) (m : ℤ) := ite (m ≥ 0) (pow1 x (int.to_nat m)) (pow1 x (int.to_nat (-m)))

end powers

end myreal

end mth1001


def divisor (n m : Nat) : Prop := ∃ c, n = m*c
def even (n : Nat) : Prop := ∃ d, n=2*d

infixl:40 " | " => divisor

example : (a | b*c) → a | b ∧ a | c := by
intro h
apply Exists.elim h
intro x hx
apply And.intro <;> rw [Nat.mul_assoc] at hx
exact Exists.intro (c*x) hx
rw [Nat.mul_left_comm] at hx
exact Exists.intro (b*x) hx

def rel_prime (n m : Nat) : Prop := (¬ n = 1 ∧ ¬ m = 1) ∧ ∀ c, (n | c) ∧ (m | c) → c = 1

example (a b : Nat) : even a → rel_prime a b → ¬ even b := by
intro h1 h2 h3
apply Exists.elim h1
intro x hx
apply Exists.elim h3
intro y hy
have ab_even : a | 2 ∧ b | 2 := And.intro (Exists.intro x hx) (Exists.intro y hy)
have : 2 = 1 := (h2.right 2) ab_even
contradiction

theorem rel_prime_not_eq (n m : Nat) : rel_prime n m → ¬ n = m := by
intro h1 h2
have h4 : n | m ∧ m | m → m = 1 := h1.right m
apply h1.left.right
apply h4
apply And.intro
rw [h2]
apply Exists.intro 1
exact Eq.symm (Nat.mul_one m)
apply Exists.intro 1
exact Eq.symm (Nat.mul_one m)


/-
Copyright (c) 2020 Anatole Dedecker. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Anatole Dedecker
-/
import analysis.asymptotics.asymptotic_equivalent
import analysis.asymptotics.specific_asymptotics
import data.polynomial.erase_lead

/-!
# Limits related to polynomial and rational functions

This file proves basic facts about limits of polynomial and rationals functions.
The main result is `eval_is_equivalent_at_top_eval_lead`, which states that for
any polynomial `P` of degree `n` with leading coefficient `a`, the corresponding
polynomial function is equivalent to `a * x^n` as `x` goes to +∞.

We can then use this result to prove various limits for polynomial and rational
functions, depending on the degrees and leading coefficients of the considered
polynomials.
-/

open filter finset asymptotics
open_locale asymptotics topological_space

namespace polynomial

variables {𝕜 : Type*} [normed_linear_ordered_field 𝕜] (P Q : polynomial 𝕜)

lemma eventually_no_roots (hP : P ≠ 0) : ∀ᶠ x in filter.at_top, ¬ P.is_root x :=
begin
  obtain ⟨x₀, hx₀⟩ := polynomial.exists_max_root P hP,
  refine filter.eventually_at_top.mpr (⟨x₀ + 1, λ x hx h, _⟩),
  exact absurd (hx₀ x h) (not_le.mpr (lt_of_lt_of_le (lt_add_one x₀) hx)),
end

variables [order_topology 𝕜]

lemma is_equivalent_at_top_lead :
  (λ x, eval x P) ~[at_top] (λ x, P.leading_coeff * x ^ P.nat_degree) :=
begin
  by_cases h : P = 0,
  { simp [h] },
  { conv_lhs
    { funext,
      rw [polynomial.eval_eq_finset_sum, sum_range_succ] },
    exact is_equivalent.refl.add_is_o (is_o.sum $ λ i hi, is_o.const_mul_left
      (is_o.const_mul_right (λ hz, h $ leading_coeff_eq_zero.mp hz) $
        is_o_pow_pow_at_top_of_lt (mem_range.mp hi)) _) }
end

lemma tendsto_at_top_of_leading_coeff_nonneg (hdeg : 1 ≤ P.degree) (hnng : 0 ≤ P.leading_coeff) :
  tendsto (λ x, eval x P) at_top at_top :=
P.is_equivalent_at_top_lead.symm.tendsto_at_top
  (tendsto_const_mul_pow_at_top (le_nat_degree_of_coe_le_degree hdeg)
    (lt_of_le_of_ne hnng $ ne.symm $ mt leading_coeff_eq_zero.mp $ ne_zero_of_coe_le_degree hdeg))

lemma tendsto_at_bot_of_leading_coeff_nonpos (hdeg : 1 ≤ P.degree) (hnps : P.leading_coeff ≤ 0) :
  tendsto (λ x, eval x P) at_top at_bot :=
P.is_equivalent_at_top_lead.symm.tendsto_at_bot
  (tendsto_neg_const_mul_pow_at_top (le_nat_degree_of_coe_le_degree hdeg)
    (lt_of_le_of_ne hnps $ mt leading_coeff_eq_zero.mp $ ne_zero_of_coe_le_degree hdeg))

lemma abs_tendsto_at_top (hdeg : 1 ≤ P.degree) :
  tendsto (λ x, abs $ eval x P) at_top at_top :=
begin
  by_cases hP : 0 ≤ P.leading_coeff,
  { exact tendsto_abs_at_top_at_top.comp (P.tendsto_at_top_of_leading_coeff_nonneg hdeg hP)},
  { push_neg at hP,
    exact tendsto_abs_at_bot_at_top.comp (P.tendsto_at_bot_of_leading_coeff_nonpos hdeg hP.le)}
end

lemma is_equivalent_at_top_div :
  (λ x, (eval x P)/(eval x Q)) ~[at_top]
    λ x, P.leading_coeff/Q.leading_coeff * x^(P.nat_degree - Q.nat_degree : ℤ) :=
begin
  by_cases hP : P = 0,
  { simp [hP] },
  by_cases hQ : Q = 0,
  { simp [hQ] },
  refine (P.is_equivalent_at_top_lead.symm.div
          Q.is_equivalent_at_top_lead.symm).symm.trans
         (eventually_eq.is_equivalent ((eventually_gt_at_top 0).mono $ λ x hx, _)),
  simp [← div_mul_div, hP, hQ, fpow_sub hx.ne.symm]
end

lemma div_tendsto_zero_of_degree_lt (hdeg : P.degree < Q.degree) :
  tendsto (λ x, (eval x P)/(eval x Q)) at_top (𝓝 0) :=
begin
  by_cases hP : P = 0,
  { simp [hP, tendsto_const_nhds] },
  rw ←  nat_degree_lt_nat_degree_iff hP at hdeg,
  refine (is_equivalent_at_top_div P Q).symm.tendsto_nhds _,
  rw ← mul_zero,
  refine (tendsto_fpow_at_top_zero _).const_mul _,
  linarith
end

lemma div_tendsto_leading_coeff_div_of_degree_eq (hdeg : P.degree = Q.degree) :
  tendsto (λ x, (eval x P)/(eval x Q)) at_top (𝓝 $ P.leading_coeff / Q.leading_coeff) :=
begin
  refine (is_equivalent_at_top_div P Q).symm.tendsto_nhds _,
  rw show (P.nat_degree : ℤ) = Q.nat_degree, by simp [hdeg, nat_degree],
  simp [tendsto_const_nhds]
end

lemma div_tendsto_at_top_of_degree_gt' (hdeg : Q.degree < P.degree)
  (hpos : 0 < P.leading_coeff/Q.leading_coeff) :
  tendsto (λ x, (eval x P)/(eval x Q)) at_top at_top :=
begin
  have hQ : Q ≠ 0 := λ h, by {simp only [h, div_zero, leading_coeff_zero] at hpos, linarith},
  rw ← nat_degree_lt_nat_degree_iff hQ at hdeg,
  refine (is_equivalent_at_top_div P Q).symm.tendsto_at_top _,
  apply tendsto.const_mul_at_top hpos,
  apply tendsto_fpow_at_top_at_top,
  linarith
end

lemma div_tendsto_at_top_of_degree_gt (hdeg : Q.degree < P.degree)
  (hQ : Q ≠ 0) (hnng : 0 ≤ P.leading_coeff/Q.leading_coeff) :
  tendsto (λ x, (eval x P)/(eval x Q)) at_top at_top :=
have ratio_pos : 0 < P.leading_coeff/Q.leading_coeff,
  from lt_of_le_of_ne hnng
    (div_ne_zero (λ h, ne_zero_of_degree_gt hdeg $ leading_coeff_eq_zero.mp h)
      (λ h, hQ $ leading_coeff_eq_zero.mp h)).symm,
div_tendsto_at_top_of_degree_gt' P Q hdeg ratio_pos

lemma div_tendsto_at_bot_of_degree_gt' (hdeg : Q.degree < P.degree)
  (hneg : P.leading_coeff/Q.leading_coeff < 0) :
  tendsto (λ x, (eval x P)/(eval x Q)) at_top at_bot :=
begin
  have hQ : Q ≠ 0 := λ h, by {simp only [h, div_zero, leading_coeff_zero] at hneg, linarith},
  rw ← nat_degree_lt_nat_degree_iff hQ at hdeg,
  refine (is_equivalent_at_top_div P Q).symm.tendsto_at_bot _,
  apply tendsto.neg_const_mul_at_top hneg,
  apply tendsto_fpow_at_top_at_top,
  linarith
end

lemma div_tendsto_at_bot_of_degree_gt (hdeg : Q.degree < P.degree)
  (hQ : Q ≠ 0) (hnps : P.leading_coeff/Q.leading_coeff ≤ 0) :
  tendsto (λ x, (eval x P)/(eval x Q)) at_top at_bot :=
have ratio_neg : P.leading_coeff/Q.leading_coeff < 0,
  from lt_of_le_of_ne hnps
    (div_ne_zero (λ h, ne_zero_of_degree_gt hdeg $ leading_coeff_eq_zero.mp h)
      (λ h, hQ $ leading_coeff_eq_zero.mp h)),
div_tendsto_at_bot_of_degree_gt' P Q hdeg ratio_neg

lemma eval_div_tendsto_at_top_of_degree_gt (hdeg : Q.degree < P.degree)
  (hQ : Q ≠ 0) :
  tendsto (λ x, abs ((eval x P)/(eval x Q))) at_top at_top :=
begin
  by_cases h : 0 ≤ P.leading_coeff/Q.leading_coeff,
  { exact tendsto_abs_at_top_at_top.comp (P.div_tendsto_at_top_of_degree_gt Q hdeg hQ h) },
  { push_neg at h,
    exact tendsto_abs_at_bot_at_top.comp (P.div_tendsto_at_bot_of_degree_gt Q hdeg hQ h.le) }
end

theorem is_O_of_degree_le (h : P.degree ≤ Q.degree) :
  is_O (λ x, eval x P) (λ x, eval x Q) filter.at_top :=
begin
  by_cases hp : P = 0,
  { simpa [hp] using is_O_zero (λ x, eval x Q) filter.at_top },
  { have hq : Q ≠ 0 := ne_zero_of_degree_ge_degree h hp,
    have hPQ : ∀ᶠ (x : 𝕜) in at_top, eval x Q = 0 → eval x P = 0 :=
      filter.mem_sets_of_superset (polynomial.eventually_no_roots Q hq) (λ x h h', absurd h' h),
    cases le_iff_lt_or_eq.mp h with h h,
    { exact is_O_of_div_tendsto_nhds hPQ 0 (div_tendsto_zero_of_degree_lt P Q h) },
    { exact is_O_of_div_tendsto_nhds hPQ _ (div_tendsto_leading_coeff_div_of_degree_eq P Q h) } }
end

end polynomial


-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que si c es no negativo y f es monótona,
-- entonces c * f es monótona.
-- ----------------------------------------------------------------------

import data.real.basic

variables (f : ℝ → ℝ)
variable  {c : ℝ}

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

example
  (mf : monotone f)
  (nnc : 0 ≤ c)
  : monotone (λ x, c * f x) :=
begin
  have h1 : ∀ a b, a ≤ b → (λ x, c * f x) a ≤ (λ x, c * f x) b,
    { intros a b hab,
      have h2 : f a ≤ f b := mf hab,
      have h3 : c * f a ≤ c * f b := mul_le_mul_of_nonneg_left h2 nnc,
      show (λ x, c * f x) a ≤ (λ x, c * f x) b,
        by exact h3, },
  show monotone (λ x, c * f x),
    by exact h1,
end

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

example
  (mf : monotone f)
  (nnc : 0 ≤ c)
  : monotone (λ x, c * f x) :=
begin
  intros a b hab,
  apply mul_le_mul_of_nonneg_left,
  apply mf hab,
  apply nnc
end

-- Su desarrollo es
--
-- f : ℝ → ℝ,
-- c : ℝ,
-- mf : monotone f,
-- nnc : 0 ≤ c
-- ⊢ monotone (λ (x : ℝ), c * f x)
--    >> intros a b hab,
-- a b : ℝ,
-- hab : a ≤ b
-- ⊢ (λ (x : ℝ), c * f x) a ≤ (λ (x : ℝ), c * f x) b
--    >> apply mul_le_mul_of_nonneg_left,
-- | ⊢ f a ≤ f b
-- |    >> apply mf hab,
-- | ⊢ 0 ≤ c
-- |    >> apply nnc
-- no goals

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

example (mf : monotone f) (nnc : 0 ≤ c) :
  monotone (λ x, c * f x) :=
λ a b hab, mul_le_mul_of_nonneg_left (mf hab) nnc


namespace Ex

-- 7.2 Define some operations on lists like `length` and `reverse`.

inductive List (α : Type u) where
  | nil : List α
  | cons (head : α) (tail : List α) : List α

open Ex.List

def list123 := cons 1 (cons 2 (cons 3 nil))
def list456 := cons 3 (cons 5 (cons 6 nil))
def list654 := cons 6 (cons 5 (cons 4 nil))
def list123456 := cons 1 (cons 2 (cons 3 (cons 4 (cons 5 (cons 6 nil)))))

def length (s : List α) : Nat :=
  match s with
  | nil       => 0
  | cons _ s' => 1 + length s'

example : length (nil : List α) = 0 := rfl

example : length list123 = 3 := rfl

def append (s t : List α) : List α :=
  match s with
  | nil       => t
  | cons x s' => cons x (append s' t)

--example : append list123 list456 = list123456 := by
--  simp [cons_append]

def reverse (s : List α) : List α :=
  match s with
  | nil       => nil
  | cons x s' => append (reverse s') (cons x nil)

-- 7.2.a. `length (s ++ t) = length s + length t`

theorem length_nil : length (nil : List α) = 0 := rfl

theorem length_cons (s : List α) : length (cons x s) = 1 + length s := rfl

theorem cons_append (s t : List α) : append (cons x s) t = cons x (append s t) := rfl

theorem nil_append (s : List α) : append nil s = s := rfl

theorem append_nil (s : List α) : append s nil = s := by
  induction s with
  | nil          => rfl
  | cons x s' ih => rw [cons_append, ih]

theorem length_append (s t : List α) : length (append s t) = length s + length t := by
  induction s with
  | nil          => rw [nil_append, length_nil, Nat.zero_add]
  | cons x s' ih => rw [cons_append, length_cons, length_cons, Nat.add_assoc, ih]

-- 7.2.b. `length (reverse t) = length t`

theorem reverse_cons (x : α) (s : List α) : reverse (cons x s) = append (reverse s) (cons x nil) := by
  induction s with
  | nil           => rfl
  | cons x' s' ih => rfl

theorem length_reverse (s : List α) : length (reverse s) = length s := by
  induction s with
  | nil          => rfl
  | cons x s' ih => rw [length_cons, reverse_cons, length_append, ih, length_cons, length_nil, Nat.add_zero, Nat.add_comm]

-- 7.2.c. `reverse (reverse t) = t`

theorem reverse_nil : reverse (nil : List α) = (nil : List α) := rfl

theorem append_assoc (s t u : List α) : append s (append t u) = append (append s t) u := by
  induction s with
  | nil          => rw [nil_append, nil_append]
  | cons x s' ih => rw [cons_append, ih, cons_append, cons_append]

theorem reverse_append (s t : List α) : reverse (append s t) = append (reverse t) (reverse s) := by
  induction s with
  | nil          => rw [nil_append, reverse_nil, append_nil]
  | cons x s' ih => rw [cons_append, reverse_cons, ih, reverse_cons, append_assoc]

theorem reverse_reverse (s : List α) : reverse (reverse s) = s := by
  induction s with
  | nil          => rfl
  | cons x s' ih => rw [reverse_cons, reverse_append, ih, reverse_cons, reverse_nil, nil_append, cons_append, nil_append]


/-
Copyright (c) 2022 Rishikesh Vaishnav. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Rishikesh Vaishnav
-/
import probability.independence

/-!
# Conditional Probability

This file defines conditional probability and includes basic results relating to it.

Given some measure `μ` defined on a measure space on some type `α` and some `s : set α`,
we define the measure of `μ` conditioned on `s` as the restricted measure scaled by
the inverse of the measure of `s`: `cond μ s = (μ s)⁻¹ • μ.restrict s`. The scaling
ensures that this is a probability measure (when `μ` is a finite measure).

From this definition, we derive the "axiomatic" definition of conditional probability
based on application: for any `s t : set α`, we have `μ[t|s] = (μ s)⁻¹ * μ (s ∩ t)`.

## Main Statements

* `cond_cond_eq_cond_inter`: conditioning on one set and then another is equivalent
  to conditioning on their intersection.
* `cond_eq_inv_mul_cond_mul`: Bayes' Theorem, `μ[t|s] = (μ s)⁻¹ * μ[s|t] * (μ t)`.

## Notations

This file uses the notation `μ[|s]` the measure of `μ` conditioned on `s`,
and `μ[t|s]` for the probability of `t` given `s` under `μ` (equivalent to the
application `μ[|s] t`).

These notations are contained in the locale `probability_theory`.

## Implementation notes

Because we have the alternative measure restriction application principles
`measure.restrict_apply` and `measure.restrict_apply'`, which require
measurability of the restricted and restricting sets, respectively,
many of the theorems here will have corresponding alternatives as well.
For the sake of brevity, we've chosen to only go with `measure.restrict_apply'`
for now, but the alternative theorems can be added if needed.

Use of `@[simp]` generally follows the rule of removing conditions on a measure
when possible.

Hypotheses that are used to "define" a conditional distribution by requiring that
the conditioning set has non-zero measure should be named using the abbreviation
"c" (which stands for "conditionable") rather than "nz". For example `(hci : μ (s ∩ t) ≠ 0)`
(rather than `hnzi`) should be used for a hypothesis ensuring that `μ[|s ∩ t]` is defined.

## Tags
conditional, conditioned, bayes
-/

noncomputable theory

open_locale ennreal

open measure_theory measurable_space

variables {α : Type*} {m : measurable_space α} (μ : measure α) {s t : set α}

namespace probability_theory

section definitions

/-- The conditional probability measure of measure `μ` on set `s` is `μ` restricted to `s`
and scaled by the inverse of `μ s` (to make it a probability measure):
`(μ s)⁻¹ • μ.restrict s`. -/
def cond (s : set α) : measure α :=
  (μ s)⁻¹ • μ.restrict s

end definitions

localized "notation  μ `[` s `|` t `]` := probability_theory.cond μ t s" in probability_theory
localized "notation  μ `[|`:60 t`]` := probability_theory.cond μ t" in probability_theory

/-- The conditional probability measure of any finite measure on any set of positive measure
is a probability measure. -/
lemma cond_is_probability_measure [is_finite_measure μ] (hcs : μ s ≠ 0) :
  is_probability_measure $ μ[|s] :=
⟨by { rw [cond, measure.smul_apply, measure.restrict_apply measurable_set.univ,
  set.univ_inter], exact ennreal.inv_mul_cancel hcs (measure_ne_top _ s) }⟩

section bayes

@[simp] lemma cond_empty : μ[|∅] = 0 :=
by simp [cond]

@[simp] lemma cond_univ [is_probability_measure μ] :
  μ[|set.univ] = μ :=
by simp [cond, measure_univ, measure.restrict_univ]

/-- The axiomatic definition of conditional probability derived from a measure-theoretic one. -/
lemma cond_apply (hms : measurable_set s) (t : set α) :
  μ[t|s] = (μ s)⁻¹ * μ (s ∩ t) :=
by { rw [cond, measure.smul_apply, measure.restrict_apply' hms, set.inter_comm], refl }

lemma cond_inter_self (hms : measurable_set s) (t : set α) :
  μ[s ∩ t|s] = μ[t|s] :=
by rw [cond_apply _ hms, ← set.inter_assoc, set.inter_self, ← cond_apply _ hms]

lemma inter_pos_of_cond_ne_zero (hms : measurable_set s) (hcst : μ[t|s] ≠ 0) :
  0 < μ (s ∩ t) :=
begin
  refine pos_iff_ne_zero.mpr (right_ne_zero_of_mul _),
  { exact (μ s)⁻¹ },
  convert hcst,
  simp [hms, set.inter_comm]
end

lemma cond_pos_of_inter_ne_zero [is_finite_measure μ]
  (hms : measurable_set s) (hci : μ (s ∩ t) ≠ 0) :
  0 < μ[|s] t :=
begin
  rw cond_apply _ hms,
  refine ennreal.mul_pos _ hci,
  exact ennreal.inv_ne_zero.mpr (measure_ne_top _ _),
end

lemma cond_cond_eq_cond_inter'
  (hms : measurable_set s) (hmt : measurable_set t) (hcs : μ s ≠ ∞) (hci : μ (s ∩ t) ≠ 0) :
  μ[|s][|t] = μ[|s ∩ t] :=
begin
  have hcs : μ s ≠ 0 := (μ.to_outer_measure.pos_of_subset_ne_zero
    (set.inter_subset_left _ _) hci).ne',
  ext u,
  simp [*, hms.inter hmt, cond_apply, ← mul_assoc, ← set.inter_assoc,
    ennreal.mul_inv, mul_comm, ← mul_assoc, ennreal.inv_mul_cancel],
end

/-- Conditioning first on `s` and then on `t` results in the same measure as conditioning
on `s ∩ t`. -/
lemma cond_cond_eq_cond_inter [is_finite_measure μ]
  (hms : measurable_set s) (hmt : measurable_set t) (hci : μ (s ∩ t) ≠ 0) :
  μ[|s][|t] = μ[|s ∩ t] :=
cond_cond_eq_cond_inter' μ hms hmt (measure_ne_top μ s) hci

lemma cond_mul_eq_inter'
  (hms : measurable_set s) (hcs : μ s ≠ 0) (hcs' : μ s ≠ ∞) (t : set α) :
  μ[t|s] * μ s = μ (s ∩ t) :=
by rw [cond_apply μ hms t, mul_comm, ←mul_assoc,
  ennreal.mul_inv_cancel hcs hcs', one_mul]

lemma cond_mul_eq_inter [is_finite_measure μ]
  (hms : measurable_set s) (hcs : μ s ≠ 0) (t : set α) :
  μ[t|s] * μ s = μ (s ∩ t) :=
cond_mul_eq_inter' μ hms hcs (measure_ne_top _ s) t

/-- A version of the law of total probability. -/
lemma cond_add_cond_compl_eq [is_finite_measure μ]
  (hms : measurable_set s) (hcs : μ s ≠ 0) (hcs' : μ sᶜ ≠ 0) :
  μ[t|s] * μ s + μ[t|sᶜ] * μ sᶜ = μ t :=
begin
  rw [cond_mul_eq_inter μ hms hcs, cond_mul_eq_inter μ hms.compl hcs', set.inter_comm _ t,
    set.inter_comm _ t],
  exact measure_inter_add_diff t hms,
end

/-- **Bayes' Theorem** -/
theorem cond_eq_inv_mul_cond_mul [is_finite_measure μ]
  (hms : measurable_set s) (hmt : measurable_set t) :
  μ[t|s] = (μ s)⁻¹ * μ[s|t] * (μ t) :=
begin
  by_cases ht : μ t = 0,
  { simp [cond, ht, measure.restrict_apply hmt, or.inr (measure_inter_null_of_null_left s ht)] },
  { rw [mul_assoc, cond_mul_eq_inter μ hmt ht s, set.inter_comm, cond_apply _ hms] }
end

end bayes

end probability_theory


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

import tactic -- imports all the Lean tactics

/-!

# Logic in Lean, example sheet 6 : "or" (∨`)

We learn about how to manipulate `P ∨ Q` in Lean.

## Tactics

You'll need to know about the tactics from the previous sheets,
and also the following tactics

* `left` and `right`
* `cases` (new functionality)

-/

-- Throughout this sheet, `P`, `Q`, `R` and `S` will denote propositions.

variables (P Q R S : Prop)

example : P → P ∨ Q :=
begin
  intro hP,
  left,
  exact hP,
end

example : Q → P ∨ Q :=
begin
  intro hQ,
  right,
  exact hQ,
end

example : P ∨ Q → (P → R) → (Q → R) → R :=
begin
  intros hPQ hPR hQR,
  cases hPQ with hP hQ,
  exact hPR hP,
  exact hQR hQ,
end

-- symmetry of `or`
example : P ∨ Q → Q ∨ P :=
begin
  sorry
end

-- associativity of `or`
example : (P ∨ Q) ∨ R ↔ P ∨ (Q ∨ R) :=
begin
  sorry,
end

example : (P → R) → (Q → S) → P ∨ Q → R ∨ S :=
begin
  sorry,
end

example : (P → Q) → P ∨ R → Q ∨ R :=
begin
  sorry,
end

example : (P ↔ R) → (Q ↔ S) → (P ∨ Q ↔ R ∨ S) :=
begin
  sorry,
end

-- de Morgan's laws
example : ¬ (P ∨ Q) ↔ ¬ P ∧ ¬ Q :=
begin
  sorry
end

example : ¬ (P ∧ Q) ↔ ¬ P ∨ ¬ Q :=
begin
  sorry
end