import .love11_logical_foundations_of_mathematics_demo

/-! # LoVe Exercise 11: Logical Foundations of Mathematics -/

set_option pp.beta true

namespace LoVe

/-! ## Question 1: Vectors as Subtypes

Recall the definition of vectors from the demo: -/

#check vector

/-! The following function adds two lists of integers elementwise. If one
function is longer than the other, the tail of the longer function is
truncated. -/

def list.add : list ℤ → list ℤ → list ℤ
| []        []        := []
| (x :: xs) (y :: ys) := (x + y) :: list.add xs ys
| []        (y :: ys) := []
| (x :: xs) []        := []

/-! 1.1. Show that if the lists have the same length, the resulting list also
has that length. -/

lemma list.length_add :
  ∀(xs : list ℤ) (ys : list ℤ) (h : list.length xs = list.length ys),
    list.length (list.add xs ys) = list.length xs
| []        []        :=
  sorry
| (x :: xs) (y :: ys) :=
  sorry
| []        (y :: ys) :=
  sorry
| (x :: xs) []        :=
  sorry

/-! 1.2. Define componentwise addition on vectors using `list.add` and
`length.length_add`. -/

def vector.add {n : ℕ} : vector ℤ n → vector ℤ n → vector ℤ n :=
sorry

/-! 1.3. Show that `list.add` and `vector.add` are commutative. -/

lemma list.add.comm :
  ∀(xs : list ℤ) (ys : list ℤ), list.add xs ys = list.add ys xs :=
sorry

lemma vector.add.comm {n : ℕ} (x y : vector ℤ n) :
  vector.add x y = vector.add y x :=
sorry

/-! ## Question 2: Integers as Quotients

Recall the construction of integers from the lecture, not to be confused with
Lean's predefined type `int` (= `ℤ`): -/

#check int.rel
#check int.rel_iff
#check int

/-! 2.1. Define negation on these integers. -/

def int.neg : int → int :=
sorry

/-! 2.2. Prove the following lemmas about negation. -/

lemma int.neg_eq (p n : ℕ) :
  int.neg ⟦(p, n)⟧ = ⟦(n, p)⟧ :=
sorry

lemma int.neg_neg (a : int) :
  int.neg (int.neg a) = a :=
sorry

end LoVe


import tactic          -- Imports all the Lean tactics.
import data.real.basic -- Imports the real numbers.

namespace my_analysis

  /-- Sequence of real numbers indexed by natural numbers. -/
  def seq := ℕ → ℝ

  /-- Sequence formed by taking the absolute value of each term. -/
  noncomputable def seq.abs (s : seq) : seq := λ k, |s k|
  @[simp] theorem seq_abs_eq (s : seq) (n : ℕ) : s.abs n = |s n| := rfl

  /-- Set containing all the values taken by terms of the sequence `s`. -/
  def seq.to_set (s : seq) : set ℝ := {x | ∃ n, s n = x}
  /-- Proof that the image set of sequence indexed by ℕ is nonempty. -/
  def seq.nonempty (s : seq) : s.to_set.nonempty :=
    by { use s 0, use 0 }

  /-- Proposition that a sequence converges to a specified limit. -/
  def tendsto (s : seq) (x : ℝ) : Prop := ∀ ⦃ε⦄, 0 < ε → ∃ B : ℕ, ∀ ⦃n⦄, B ≤ n → |s n - x| < ε
  notation s ` ⟶ ` limit := tendsto s limit

  /-- Proposition that there is a limit to which a sequence converges. -/
  def seq.convergent (s : seq) : Prop := ∃ x, s ⟶ x

  section neg

    /-- Sequence formed by taking the additive inverse of each real term of the original sequence `s`. -/
    def seq.neg (s : seq) : seq := λ n, - s n

    instance seq_neg : has_neg seq := ⟨seq.neg⟩
    @[simp] theorem seq.neg_eq (s : seq) (n : ℕ) : (-s) n = -(s n) := rfl

    /-- Sequence negation is an involution, i.e. it is its own inverse. -/
    @[simp] theorem seq.neg_neg (s : seq) : -(-s) = s :=
    begin
      apply funext, intro n,
      rw [seq.neg_eq, seq.neg_eq, neg_neg]
    end

    /-- tendsto is preserved by negating both the sequence and its limit. -/
    theorem neg_tendsto_neg {s : seq} {x : ℝ} : (-s ⟶ -x) ↔ (s ⟶ x) :=
    begin
      split,
      { intros h ε hε, cases h hε with B hB, use B, intros n hn,
        rw [← abs_neg, neg_sub, ← neg_sub_neg, ← s.neg_eq],
        exact hB hn },
      { intros h ε hε, cases h hε with B hB, use B, intros n hn,
        rw [s.neg_eq, ← neg_sub, abs_neg, neg_sub_neg],
        exact hB hn }
    end

    /-- tendsto is preserved by swapping the negation between the sequence and limit. -/
    theorem neg_tendsto {s : seq} {x : ℝ} : (-s ⟶ x) ↔ (s ⟶ -x) :=
    begin
      have : x = - -x, from eq_neg_of_eq_neg rfl,
      conv_lhs { rw [this] },
      exact neg_tendsto_neg
    end

    /-- Negation preserves convergence. -/
    theorem neg_convergent {s : seq} : (-s).convergent ↔ s.convergent :=
    begin
      split,
      { intro h, cases h with x hx,
        exact ⟨-x, neg_tendsto.mp hx⟩ },
      { intro h, cases h with x hx,
        exact ⟨-x, neg_tendsto_neg.mpr hx⟩ }
    end

  end neg

  section bounded

    /-- Proposition that a sequence is bounded above, i.e. all terms lie within `(-∞, M]`. -/
    def seq.bounded_above (s : seq) : Prop := ∃ M, ∀ n, s n ≤ M

    /-- Proposition that a sequence is bounded above, i.e. all terms lie within `[m, ∞)`. -/
    def seq.bounded_below (s : seq) : Prop := ∃ m, ∀ n, m ≤ s n

    /-- Proposition that a sequence is bounded above and below, i.e. all terms lie within `[-M, M]`. -/
    def seq.bounded (s : seq) : Prop := s.abs.bounded_above

    /-- Proof that a sequence is bounded in absolute value if and only if it is bounded both above and below. -/
    theorem bounded_iff_above_below {s : seq} : s.bounded ↔ s.bounded_above ∧ s.bounded_below :=
    begin
      split,
      { intro hb,
        cases hb with M hM,
        split,
        { use M, intro n,
          exact le_of_abs_le (hM n) },
        { use -M, intro n,
          exact neg_le_of_abs_le (hM n) } },
      { rintro ⟨ha, hb⟩,
        cases ha with M hM,
        cases hb with m hm,
        use max M (-m), intro n,
        by_cases h : s n ≤ 0,
        { rw [seq_abs_eq, abs_of_nonpos h],
          exact le_max_of_le_right (neg_le_neg (hm n)) },
        { rw [seq_abs_eq, abs_of_nonneg (le_of_not_ge h)],
          exact le_max_of_le_left (hM n) }}
    end

    /-- Cast from myanalysis.bounded_above to mathlib's bdd_above Prop. -/
    theorem seq.bounded_above.to_bdd_above {s : seq} (hb : s.bounded_above) : bdd_above s.to_set :=
    begin
      cases hb with M hM,
      use M, intros x hx,
      cases hx with n hn,
      rw [← hn], exact hM n
    end

    /-- Negation turn an upper bound into a lower bound. -/
    theorem bounded_above_iff_neg_below {s : seq} : s.bounded_above ↔ (-s).bounded_below :=
    begin
      split,
      { intro h, cases h with M hM,
        use -M, intro n, rw [seq.neg_eq],
        exact neg_le_neg_iff.mpr (hM n) },
      { intro h, cases h with m hm,
        use -m, intro n, rw [←s.neg_neg, (-s).neg_eq],
        exact neg_le_neg_iff.mpr (hm n) }
    end

    /-- Negation turn an lower bound into a upper bound. -/
    theorem bounded_below_iff_neg_above {s : seq} : s.bounded_below ↔ (-s).bounded_above :=
    begin
      conv_lhs { rw [←seq.neg_neg s] },
      exact bounded_above_iff_neg_below.symm
    end

  end bounded

  section order

    /-- Proposition that every term in a sequence is greater than or equal to any term before it. -/
    def seq.increasing (s : seq) : Prop := ∀ ⦃m n⦄, m ≤ n → s m ≤ s n
    /-- Proposition that every term in a sequence is strictly greater than any term before it. -/
    def seq.strictly_increasing (s : seq) : Prop := ∀ ⦃m n⦄, m < n → s m < s n

    /-- Proposition that every term in a sequence is less than or equal to any term before it. -/
    def seq.decreasing (s : seq) : Prop := ∀ ⦃m n⦄, m ≤ n → s n ≤ s m
    /-- Proposition that every term in a sequence is strictly less than any term before it. -/
    def seq.strictly_decreasing (s : seq) : Prop := ∀ ⦃m n⦄, m < n → s n < s m

    /-- Proposition that in a sequence, either every term is greater than or equal to any term before it,
      or every term is less than or equal to any term before it. -/
    def seq.monotone (s : seq) : Prop := s.increasing ∨ s.decreasing
    /-- Proposition that in a sequence, either every term is strictly greater than any term before it,
      or every term is strictly less than any term before it. -/
    def seq.strictly_monotone (s : seq) : Prop := s.strictly_increasing ∨ s.strictly_decreasing

    /-- Weakening of strictly increasing to just increasing. -/
    theorem seq.strictly_increasing.to_increasing {s : seq} (si : s.strictly_increasing) : s.increasing :=
    begin
      intros m n h,
      by_cases h' : m = n,
      { rw [h'] },
      { exact le_of_lt (si (lt_of_le_of_ne h h')) }
    end
    /-- Weakening of strictly increasing to just increasing. -/
    theorem seq.strictly_decreasing.to_decreasing {s : seq} (si : s.strictly_decreasing) : s.decreasing :=
    begin
      intros m n h,
      by_cases h' : m = n,
      { rw [h'] },
      { exact le_of_lt (si (lt_of_le_of_ne h h')) }
    end
    /-- Weakening of strictly increasing to just increasing. -/
    theorem seq.strictly_monotone.to_monotone {s : seq} (si : s.strictly_monotone) : s.monotone :=
    begin
      cases si with h h,
      { exact or.inl h.to_increasing },
      { exact or.inr h.to_decreasing }
    end

    /-- Negation turns an increasing sequence into a decreasing sequence. -/
    theorem increasing_iff_neg_decreasing {s : seq} : s.increasing ↔ (-s).decreasing :=
    begin
      split,
      { intros h a b hab,
        rw [seq.neg_eq, seq.neg_eq],
        exact neg_le_neg (h hab) },
      { intros h a b hab,
        rw [←s.neg_neg, (-s).neg_eq],
        exact neg_le_neg_iff.mpr (h hab) }
    end

    /-- Negation turns an decreasing sequence into a increasing sequence. -/
    theorem decreasing_iff_neg_increasing {s : seq} : s.decreasing ↔ (-s).increasing :=
    begin
      conv_lhs { rw [←seq.neg_neg s] },
      exact increasing_iff_neg_decreasing.symm
    end

    /-- Any increasing bounded-above sequence is convergent. -/
    theorem convergent_of_bounded_above_increasing {s : seq} (hba : s.bounded_above) (hi : s.increasing) :
      s.convergent :=
    begin
      use Sup s.to_set,
      intros ε hε,
      have hlub := real.is_lub_Sup s.to_set s.nonempty hba.to_bdd_above,
      have : ∃ B, s B > Sup s.to_set - ε,
        by_contradiction h,
        simp only [not_exists, not_lt] at h,
        have : Sup s.to_set - ε ∈ upper_bounds s.to_set,
          rintros x ⟨n, rfl⟩,
          exact h n,
        exact absurd hε (not_lt.mpr ((le_sub_self_iff _).mp (hlub.right this))),
      cases this with B hB,
      use B, intros n hn,
      refine abs_lt.mpr ⟨_, _⟩,
      exact lt_tsub_iff_left.mpr (lt_of_lt_of_le hB (hi hn)),
      exact lt_of_le_of_lt (sub_nonpos.mpr (hlub.left (by use n))) hε
    end

    /-- Any decreasing bounded-below sequence is convergent. -/
    theorem convergent_of_bounded_below_decreasing {s : seq} (hbb : s.bounded_below) (hd : s.decreasing) :
      s.convergent :=
        neg_convergent.mp $ convergent_of_bounded_above_increasing
          (bounded_below_iff_neg_above.mp hbb)
          (decreasing_iff_neg_increasing.mp hd)

  end order

  section shift

    /-- Sequence formed by ignoring the first `n` elements, such that `s.shift 0 = s n`. -/
    def seq.shift (s : seq) (n : ℕ) : seq := λ m, s (m + n)

    /-- Shifting by `0` leaves the sequence unchanged. -/
    @[simp] theorem shift_zero (s : seq) : s.shift 0 = s :=
      by simp only [seq.shift, nat.add_zero]

    /-- For any `m` greater than the shift number `n`, `s m` has been moved to `s.shift n (m - n)`. -/
    theorem shift_ge (s : seq) {n m : ℕ} (h : n ≤ m) : s.shift n (m - n) = s m :=
      by simp only [seq.shift, nat.sub_add_cancel h]

    /-- tendsto is preserved by arbitrary finite shifts (including preserving the limit). -/
    theorem tendsto_shift (s : seq) (x : ℝ) : ∀ n, (s ⟶ x) ↔ (s.shift n ⟶ x) :=
    begin
      intro n, split,
      { intros h ε hε,
        cases h hε with B hB,
        use B, intros m hm,
        exact hB (le_add_right hm : B ≤ m + n) },
      { intros h ε hε,
        cases h hε with B hB,
        use B + n, intros m hm,
        rw [← shift_ge s (nat.le_of_add_le_right hm)],
        exact hB (le_tsub_of_add_le_right hm) }
    end

  end shift

  section finite_prefix

    /-- A finite set made up of the values of the first `n` terms of the sequence `s` (from `s 0` to `s (n-1)`). -/
    noncomputable def finite_prefix (s : seq) (n : ℕ) : finset ℝ :=
      (multiset.map s (multiset.range n)).to_finset

    /-- A real number belongs to the finite prefix set if and only if it is the value taken by
      the sequence `s` for some term before the `n`-th term. -/
    @[simp] theorem mem_finite_prefix {s : seq} {n : ℕ} {x : ℝ} : x ∈ finite_prefix s n ↔ ∃ k, k < n ∧ s k = x :=
      by simp only [finite_prefix, multiset.mem_to_finset, multiset.mem_map, multiset.mem_range]

    /-- If the natural number `n` isn't zero, then the finite prefix with `n` elements is nonempty. -/
    theorem finite_prefix_nonempty (s : seq) {n : ℕ} (hn : n ≠ 0) :
      (finite_prefix s n).nonempty :=
        ⟨s 0, mem_finite_prefix.mpr ⟨0, pos_iff_ne_zero.mpr hn, rfl⟩⟩

    /-- For any `n`, the real numbers `|s m|` for `m < n` have a maximal element. -/
    theorem finite_prefix_max (s : seq) {n : ℕ} (hn : n ≠ 0) :
      ∃ m, m < n ∧ ∀ k < n, s k ≤ s m :=
    begin
      let fs := (finite_prefix s n),
      have hM := mem_finite_prefix.mp (fs.max'_mem (finite_prefix_nonempty s hn)),
      cases hM with m hm,
      use m, split, exact hm.left,
      intros k hk, rw [hm.right],
      exact fs.le_max' (s k)(mem_finite_prefix.mpr ⟨k, hk, rfl⟩)
    end

    /-- For any `n`, the real numbers `|s m|` for `m < n` have a minimal element. -/
    theorem finite_prefix_min (s : seq) {n : ℕ} (hn : n ≠ 0) :
      ∃ m, m < n ∧ ∀ k < n, s m ≤ s k :=
    begin
      cases finite_prefix_max (-s) hn with M hM,
      use M, split, exact hM.left,
      intros k hk,
      exact neg_le_neg_iff.mp (hM.right k hk)
    end

    /-- For any `n`, the elements `|s m|` for `m < n` are bounded. -/
    theorem finite_prefix_bounded (s : seq) (n : ℕ) : ∃ M, ∀ m < n, |s m| ≤ M :=
    begin
      by_cases hn : n = 0,
      { use 0,
        intros m hm,
        rw [hn] at hm,
        exact absurd hm (nat.not_lt_zero m) },
      { cases finite_prefix_max s.abs hn with M hM,
        exact ⟨|s M|, hM.right⟩ }
    end

  end finite_prefix

  /-- bounded is preserved by arbitrary finite shifts (although the bound `M` itself might change). -/
  theorem bounded_shift (s : seq) : ∀ n, s.bounded ↔ (s.shift n).bounded :=
  begin
    intro n, split,
    { rintro ⟨M, h⟩,
      use M, intro k,
      exact h (k + n) },
    { rintro ⟨M₁, h₁⟩,
      cases finite_prefix_bounded s n with M₂ h₂,
      use max M₁ M₂,
      intro k,
      by_cases hk : k < n,
      { exact le_max_of_le_right (h₂ k hk) },
      { rw [seq_abs_eq, ← shift_ge s (not_lt.mp hk)],
        exact le_max_of_le_left (h₁ (k - n)) } }
  end

end my_analysis


/-
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

/-!

# Behind the scenes at the natural number game

I explain a bit about how inductive types and inductive definitions work.

## How to make the natural numbers?

I present the natural numbers in the natural number game
(TODO add URL) as "something with zero and succ which
satisfies induction" and I present addition as "something
which by definition satisfies `x+0=x` and `x+succ(y)=succ(x+y)`.
Here's how it actually happens.

-/

inductive mynat : Type -- note no `:=` next; this is a different kind of definition
| zero : mynat
| succ (n : mynat) : mynat

/-

This "inductive definition" creates four things automatically:
(1) a new type called `mynat : Type`
(2) a new term `mynat.zero : mynat`
(3) a new function `mynat.succ : mynat → mynat`
(4) a "recursor" -- something I'll tell you about in a minute.

Right now, the `0` in `mynat` is called `mynat.zero`. However if
we `open mynat` or move into the `mynat` namespace with `namespace mynat`,
it will just be called `zero`.
-/

-- #check zero -- doesn't work

namespace mynat

-- now zero works:

-- #check zero -- zero : mynat

-- but `0` still doesn't work:

-- #check (0 : mynat) -- failed to find `⊢ has_zero mynat`

/-

To get `0` working, let's make an instance of `has_zero mynat`.

-/

instance : has_zero mynat :=
{ zero := zero }

-- #check (0 : mynat) -- works

-- We could even make a `simp` lemma which turns `mynat.zero` into `0`.

@[simp] lemma mynat.zero_eq_zero : mynat.zero = 0 :=
begin
  refl
end

-- Now if we ever see a `mynat.zero` we can `simp` it into `0`.

/-

## The recursor

The fourth thing which magically appears after our definition of `mynat`
is a new (quite complicated) function `mynat.rec`, which is at once
the principles of induction and recursion.

The reason `mynat.rec` can be both is that it mentions a term
`motive : mynat → Sort u` and `Sort u` is Lean's way of saying
"any universe -- either `Prop` or `Type` (or even the higher universes)".
Let's see what `mynat.rec` looks like in the special cases of `Prop`
and `Type`:

-/

-- induction: how to prove P(n) for all n, where P(n) is a proposition
-- using mynat.rec directly
example
  -- let P be a family of true-false statements
  (P : mynat → Prop)
  -- Assume P(0) is true
  (h0 : P mynat.zero)
  -- assume P(n) implies P(succ n)
  (hsucc : ∀ n : mynat, P n → P (mynat.succ n))
  -- Then
  :
  -- P(n) is true for all n
  ∀ n, P n :=
begin
  -- apply mynat.rec
  exact mynat.rec h0 hsucc
end

-- recursion: how to define f(n) for all n, given f(0) and
-- a formula for f(n+1) which can depend on f(n) (and on n)

example
  -- let X be a type
  (X : Type)
  -- Say we have x₀ in X (the value of f(0))
  (x₀ : X)
  -- and say we have a function mynat → X → X, for example
  -- if X is the reals and f(n+1)= 1/n + f(n) then this function would
  -- be λ n x, 1/n + x
  (I : mynat → X → X)
  -- then
  :
  -- we can build a function mynat → X
  mynat → X :=
@mynat.rec (λ _, X) x₀ I

/-

This is not now we use the recursor though.
In term mode, we use recursive definitions; in tactic
mode we use the induction tactic.

Let's see these in action, with our first inductive definition,
namely addition.

-/

def add : mynat → mynat → mynat -- again no `:=`
| n 0 := n -- n + 0 defined to be n
| n (succ m) := succ (add n m)

/-

Again we see these weird `|` symbols, but this time what's happening
is not that we are defining a new inductive type, but we are defining
a new function which eats inductive types and is defined on a case
by case basis; it's "defined by recursion". Internally the definition
of `add` uses `mynat.rec` but we don't have to worry about that any more.
This way of working with the recursor is far less painful.

Now `add_zero` and `add_succ` are definitional. Let's see this.
First let's set up notation `+` for addition with the notational
typeclass `has_add`

-/

instance : has_add mynat :=
{ add := add }

lemma add_zero (n : mynat) : n + 0 = n :=
begin
  refl
end

-- dot notation for succ is quite confusing; let's switch it off
attribute [pp_nodot] mynat.succ

lemma add_succ (n m : mynat) : n + succ m = succ (n + m) :=
begin
  refl
end

/-
If you've played the natural number game, you might now be
able to find shorter proofs of things. For example `n + 0 = n`
can be proved by `refl` even though I never explain this.

The `induction` tactic automatically works for `mynat` now
because it knows about `mynat.rec`.
-/

lemma zero_add (n : mynat) : 0 + n = n :=
begin
  induction n with d hd, -- works!
  {
    -- see how we have a mixture of notation now? :-(
    -- see if you can sort out the mess. In the natural number game
    -- I do this behind the scenes.
    sorry }, --
  { sorry }
end

/-

Later on in the natural number game I randomly introduce two new
theorems: `zero_ne_succ` (saying `0 ≠ succ n`) and `succ_inj` (saying
that `succ m = succ n → m = n`). Here's how we prove them. Let's
make a new inductive type with two terms.

-/

/-- The type `yesno` has two terms, `yesno.yes` and `yesno.no`. -/
inductive yesno : Type
| yes : yesno
| no : yesno

open yesno

-- now they're just called `yes` and `no`.

example : yes ≠ no :=
begin
  intro h, -- h : yes = no
  cases h, -- no cases where this is true. Proved using yesno.rec ;-)
end

example : yes ≠ no. -- a complete proof! Proof is "there are no cases"

-- now let's define a function `is_zero`

@[pp_nodot]
def is_zero : mynat → yesno
| 0 := yes
| (succ n) := no

lemma yes_zero_is_zero : is_zero 0 = yes :=
begin
  refl
end

lemma no_succ_isnt_zero (n : mynat) : is_zero (succ n) = no :=
begin
  refl
end

lemma succ_ne_zero (n : mynat) : succ n ≠ 0 :=
begin
  -- see if you can prove it.
  -- Recall the `apply_fun` tactic which can turn a hypothesis
  -- `x = y` into `f x = f y`
  sorry
end

-- Now here's how to prove that `succ` is injective

@[pp_nodot]
def pred : mynat → mynat
| 0 := succ (succ (succ (succ (succ (succ (succ 0)))))) -- junk value
| (succ n) := n -- what we care about

lemma pred_succ (n : mynat) : pred (succ n) = n :=
begin
  sorry
end

lemma succ_inj (a b : mynat) (hab : succ a = succ b) : a = b :=
begin
  sorry,
end

end mynat


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

import data.nat.interval
import data.polynomial.derivative
import tactic.linarith

/-!
# Theory of iterated derivative
We define and prove some lemmas about iterated (formal) derivative for polynomials over a semiring.
-/

noncomputable theory

open finset nat polynomial
open_locale big_operators polynomial

namespace polynomial
universes u
variable {R : Type u}

section semiring

variables [semiring R] (r : R) (f p q : R[X]) (n k : ℕ)

/-- `iterated_deriv f n` is the `n`-th formal derivative of the polynomial `f` -/
def iterated_deriv : R[X] := derivative ^[n] f

@[simp] lemma iterated_deriv_zero_right : iterated_deriv f 0 = f := rfl

lemma iterated_deriv_succ : iterated_deriv f (n + 1) = (iterated_deriv f n).derivative :=
by rw [iterated_deriv, iterated_deriv, function.iterate_succ']

@[simp] lemma iterated_deriv_zero_left : iterated_deriv (0 : R[X]) n = 0 :=
begin
  induction n with n hn,
  { exact iterated_deriv_zero_right _ },
  { rw [iterated_deriv_succ, hn, derivative_zero] },
end

@[simp] lemma iterated_deriv_add :
  iterated_deriv (p + q) n = iterated_deriv p n + iterated_deriv q n :=
begin
  induction n with n ih,
  { simp only [iterated_deriv_zero_right], },
  { simp only [iterated_deriv_succ, ih, derivative_add] }
end

@[simp] lemma iterated_deriv_smul {S : Type*} [monoid S]
  [distrib_mul_action S R] [is_scalar_tower S R R]
  (s : S) : iterated_deriv (s • p) n = s • iterated_deriv p n :=
begin
  induction n with n ih,
  { simp only [iterated_deriv_zero_right] },
  { simp only [iterated_deriv_succ, ih, derivative_smul] }
end

@[simp] lemma iterated_deriv_X_zero : iterated_deriv (X : R[X]) 0 = X :=
by simp only [iterated_deriv_zero_right]

@[simp] lemma iterated_deriv_X_one : iterated_deriv (X : R[X]) 1 = 1 :=
by simp only [iterated_deriv, derivative_X, function.iterate_one]

@[simp] lemma iterated_deriv_X (h : 1 < n) : iterated_deriv (X : R[X]) n = 0 :=
begin
  induction n with n ih,
  { exfalso, exact nat.not_lt_zero 1 h },
  { simp only [iterated_deriv_succ],
    by_cases H : n = 1,
    { rw H, simp only [iterated_deriv_X_one, derivative_one] },
    { replace h : 1 < n := array.push_back_idx h (ne.symm H),
      rw ih h, simp only [derivative_zero] } }
end

@[simp] lemma iterated_deriv_C_zero : iterated_deriv (C r) 0 = C r :=
by simp only [iterated_deriv_zero_right]

@[simp] lemma iterated_deriv_C (h : 0 < n) : iterated_deriv (C r) n = 0 :=
begin
  induction n with n ih,
  { exfalso, exact nat.lt_asymm h h },
  { by_cases H : n = 0,
    { rw [iterated_deriv_succ, H], simp only [iterated_deriv_C_zero, derivative_C] },
    { replace h : 0 < n := nat.pos_of_ne_zero H,
      rw [iterated_deriv_succ, ih h], simp only [derivative_zero] } }
end

@[simp] lemma iterated_deriv_one_zero : iterated_deriv (1 : R[X]) 0 = 1 :=
by simp only [iterated_deriv_zero_right]

@[simp] lemma iterated_deriv_one : 0 < n → iterated_deriv (1 : R[X]) n = 0 := λ h,
begin
  have eq1 : (1 : R[X]) = C 1 := by simp only [ring_hom.map_one],
  rw eq1, exact iterated_deriv_C _ _ h,
end

lemma coeff_iterated_deriv_as_prod_Ico :
  ∀ m : ℕ, (iterated_deriv f k).coeff m = (∏ i in Ico m.succ (m + k.succ), i) • (f.coeff (m+k)) :=
begin
  induction k with k ih,
  { simp only [add_zero, forall_const, one_smul, Ico_self, eq_self_iff_true,
      iterated_deriv_zero_right, prod_empty] },
  { intro m, rw [iterated_deriv_succ, coeff_derivative, ih (m+1), ← nat.cast_add_one,
      ← nsmul_eq_mul', smul_smul, mul_comm],
    apply congr_arg2,
    { have set_eq : (Ico m.succ (m + k.succ.succ)) = (Ico (m + 1).succ (m + 1 + k.succ)) ∪ {m+1},
      { simp_rw [← Ico_succ_singleton, union_comm, succ_eq_add_one, add_comm (k + 1), add_assoc],
        rw [Ico_union_Ico_eq_Ico]; simp_rw [add_le_add_iff_left, le_add_self], },
      rw [set_eq, prod_union, prod_singleton],
      { rw [disjoint_singleton_right, mem_Ico],
        exact λ h, (nat.lt_succ_self _).not_le h.1 } },
    { exact congr_arg _ (succ_add m k) } },
end

lemma coeff_iterated_deriv_as_prod_range :
  ∀ m : ℕ, (iterated_deriv f k).coeff m = (∏ i in range k, (m + k - i)) • f.coeff (m + k) :=
begin
  induction k with k ih,
  { simp },
  intro m,
  calc (f.iterated_deriv k.succ).coeff m
      = (∏ i in range k, (m + k.succ - i)) • f.coeff (m + k.succ) * (m + 1) :
    by rw [iterated_deriv_succ, coeff_derivative, ih m.succ, succ_add, add_succ]
  ... = ((∏ i in range k, (m + k.succ - i)) * (m + 1)) • f.coeff (m + k.succ) :
    by rw [← nat.cast_add_one, ← nsmul_eq_mul', smul_smul, mul_comm]
  ... = (∏ i in range k.succ, (m + k.succ - i)) • f.coeff (m + k.succ) :
    by rw [prod_range_succ, add_tsub_assoc_of_le k.le_succ, succ_sub le_rfl, tsub_self]
end

lemma iterated_deriv_eq_zero_of_nat_degree_lt (h : f.nat_degree < n) : iterated_deriv f n = 0 :=
begin
  ext m,
  rw [coeff_iterated_deriv_as_prod_range, coeff_zero, coeff_eq_zero_of_nat_degree_lt, smul_zero],
  linarith
end

lemma iterated_deriv_mul :
  iterated_deriv (p * q) n =
  ∑ k in range n.succ,
    n.choose k • (iterated_deriv p (n - k) * iterated_deriv q k) :=
begin
  induction n with n IH,
  { simp },

  calc (p * q).iterated_deriv n.succ
      = (∑ (k : ℕ) in range n.succ,
          (n.choose k) • (p.iterated_deriv (n - k) * q.iterated_deriv k)).derivative :
    by rw [iterated_deriv_succ, IH]
  ... = ∑ (k : ℕ) in range n.succ,
          (n.choose k) • (p.iterated_deriv (n - k + 1) * q.iterated_deriv k) +
        ∑ (k : ℕ) in range n.succ,
          (n.choose k) • (p.iterated_deriv (n - k) * q.iterated_deriv (k + 1)) :
    by simp_rw [derivative_sum, derivative_smul, derivative_mul, iterated_deriv_succ, smul_add,
      sum_add_distrib]
  ... = (∑ (k : ℕ) in range n.succ,
            (n.choose k.succ) • (p.iterated_deriv (n - k) * q.iterated_deriv (k + 1)) +
          1 • (p.iterated_deriv n.succ * q.iterated_deriv 0)) +
        ∑ (k : ℕ) in range n.succ,
          (n.choose k) • (p.iterated_deriv (n - k) * q.iterated_deriv (k + 1)) : _
  ... = ∑ (k : ℕ) in range n.succ,
          (n.choose k) • (p.iterated_deriv (n - k) * q.iterated_deriv (k + 1)) +
        ∑ (k : ℕ) in range n.succ,
            (n.choose k.succ) • (p.iterated_deriv (n - k) * q.iterated_deriv (k + 1)) +
        1 • (p.iterated_deriv n.succ * q.iterated_deriv 0) :
    by rw [add_comm, add_assoc]
  ... = ∑ (i : ℕ) in range n.succ,
          ((n+1).choose (i+1)) • (p.iterated_deriv (n + 1 - (i+1)) * q.iterated_deriv (i+1)) +
        1 • (p.iterated_deriv n.succ * q.iterated_deriv 0) :
    by simp_rw [choose_succ_succ, succ_sub_succ, add_smul, sum_add_distrib]
  ... = ∑ (k : ℕ) in range n.succ.succ,
          (n.succ.choose k) • (p.iterated_deriv (n.succ - k) * q.iterated_deriv k) :
    by rw [sum_range_succ' _ n.succ, choose_zero_right, tsub_zero],

  congr,
  refine (sum_range_succ' _ _).trans (congr_arg2 (+) _ _),
  { rw [sum_range_succ, nat.choose_succ_self, zero_smul, add_zero],
    refine sum_congr rfl (λ k hk, _),
    rw mem_range at hk,
    congr,
    rw [tsub_add_eq_add_tsub (nat.succ_le_of_lt hk), nat.succ_sub_succ] },
  { rw [choose_zero_right, tsub_zero] },
end

end semiring

section ring
variables [ring R] (p q : R[X]) (n : ℕ)

@[simp] lemma iterated_deriv_neg : iterated_deriv (-p) n = - iterated_deriv p n :=
begin
  induction n with n ih,
  { simp only [iterated_deriv_zero_right] },
  { simp only [iterated_deriv_succ, ih, derivative_neg] }
end

@[simp] lemma iterated_deriv_sub :
  iterated_deriv (p - q) n = iterated_deriv p n - iterated_deriv q n :=
by rw [sub_eq_add_neg, iterated_deriv_add, iterated_deriv_neg, ←sub_eq_add_neg]

end ring

end polynomial


/-
Copyright (c) 2023 María Inés de Frutos-Fernández. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author : María Inés de Frutos-Fernández
-/

import topology.continuous_function.compact -- funciones continuas, conjuntos compactos

/-!

# Ejercicio
En este fichero el objetivo es demostrar que si `f : X → Y`
es una función continua y `S : set X` es un conjunto compacto,
entonces su imagen `f '' S : set Y` también es compacta.

Un lema útil para esta demostración es

* `continuous.is_open_preimage` : la preimagen de un abierto bajo
una función continua es un abierto.
-/

-- Fijamos espacios topológicos X e Y
variables (X Y : Type) [topological_space X] [topological_space Y]

-- S es un subconjunto de X
variable (S : set X)

-- `f : X → Y` es una función
variables (f : X → Y)

-- Si f es continua y S es compacto,  la imagen f(S) también es un conjunto compacto.
example (hf : continuous f) (hS : is_compact S) : is_compact (f '' S) :=
begin
  rw is_compact_iff_finite_subcover at hS ⊢,
  intros ι U hU hUS,
  rw [set.image_subset_iff, set.preimage_Union] at hUS,
  obtain ⟨t, ht⟩ := hS (λ i, f⁻¹' (U i)) (λ i, hf.is_open_preimage (U i) (hU i)) hUS,
  use t,
  simp only [set.image_subset_iff, set.preimage_Union],
  exact ht,
end


/-
Proof Strategies:
Intro rules for or -
or.inl takes a proof of P and returns a proof of P ∨ Q
or.inr takes a proof of Q  and returns a proof of P ∨ Q
or ⇒ disjuncture
-/

axiom Q : Prop
axiom P : Prop
axiom q: Q

example : P ∨ Q :=
or.inr q -- because no proof of P exists

example : 0 = 1 ∨ 2 = 2 :=
or.inr (eq.refl 2)
--or.intro_right (0 = 1) rfl

-- CONSTRUCTIVE LOGIC
example : P ∨ ¬ P :=
begin
-- yikes...
-- apparently we are stuck
-- no proof either way!
_
end

-- CLASSICAL LOGIC
-- law of the excluded middle
axiom em: ∀ (P : Prop), P ∨ ¬ P
 #check em Q

example : ∀ (P : Prop), ¬ (¬ P) → P :=
begin
assume P,
assume nnp, -- (P → false) → false
cases (em P) with p np ,
exact p,
--exact false.elim (nnp np),  -- constructively this doesn't have a proof.
contradiction,
end

axiom : ∀ (P : Prop), ¬ (¬ P) → P

/-
proof by contradiction
look up and add here the difference between proof by negation and proof by contradiction

- show P, prove ¬ ¬ P (¬ P → false)

proof by negation is used to prove a prop of the form ¬ P. this world whenever you try and use
- show ¬ P, prove P → false

(PBC) if you want to show that P is true, assume it is false and show how that leads to a contradiction.
-/

-- paper pencil proof of square root 2 is irrational

/-
NEGATION                                        CONTRA
want ¬ P                                            want P,
show p → ff                                         show ¬ ¬ P
assume (p: P)                                       then infer P
show contradiction, proof of false                  → ¬ P → false
                                                [¬ ¬ P → P, classically valid]
-/

/-
40% of the exam is actually about the functional programming we have learned.
60% is propositions and proofs. (intro rules and elimination rules)
-/

axioms
    (R : Prop)
    (p : P)
    (pr: P → R)
    (qr: Q → R)

example : (P ∨ Q) → R :=
λ (pq : P ∨ Q),
match pq with
| or.inl pfP := pr p
| or.inr pfQ := qr q
end


/- Natural Numbers -/

namespace hidden

  inductive Nat where
  | zero : Nat
  | succ : Nat → Nat
  deriving Repr

  namespace Nat

    /- Addition -/
    def add (m n : Nat) : Nat :=
      match n with
      | zero => m
      | succ n' => Nat.succ (add m n')

    notation:65 lhs:65 "+" rhs:66 => add lhs rhs
    notation:max "one" => succ zero

    theorem zero_not_succ (n : Nat) : succ n ≠ zero := by simp [add]

    theorem add_zero (n : Nat) : n + zero = n := by rfl

    theorem add_succ (m n : Nat) : m + succ n = succ (m + n) := by rfl

    theorem succ_eq_add_one (n : Nat) : n + one = succ n := by
      calc
        n + one = succ (n + zero) := by simp only [add_succ]
        _ = succ n := by simp only [add_zero]

    theorem zero_add (n: Nat) : zero + n = n :=
      Nat.recOn (motive := fun x => zero + x = x)
        n
        (show zero + zero = zero from rfl)
        (fun (n : Nat) (ih : zero + n = n) =>
          show zero + succ n = succ n from
            calc
              zero + succ n = succ (zero + n) := rfl
              _ = succ n := by rw [ih])

    theorem add_assoc (m n k : Nat) : (m + n) + k = m + (n + k) :=
      Nat.recOn (motive := fun x => (m + n) + x = m + (n + x)) k
        (show (m + n) + zero = m + (n + zero) by rfl)
        (fun k ih => by
          calc
            (m + n) + succ k = succ ((m + n) + k) := by rfl
            _ = succ (m + (n + k)) := by rw [ih])

    theorem succ_add (m n : Nat) : succ n + m = succ (n + m) :=
      Nat.recOn (motive := fun x => succ n + x = succ (n + x)) m
        (by rfl)
        (fun m ih => by simp only [add_succ, *])

    theorem add_comm (m n : Nat) : m + n = n + m :=
      Nat.recOn (motive := fun x => m + x = x + m) n
        (show m + zero = zero + m by simp [zero_add, add])
        (fun n ih => by calc
          m + succ n = succ (m + n) := by rfl
          _ = succ (n + m) := by rw [ih]
          _ = succ n + m := by simp only [succ_add])

    theorem succ_eq (m n : Nat) : succ m = succ n → m = n := by
      intro h
      match h with
      | rfl => rfl

    theorem left_add_cancel (m n k : Nat) : m + k = n + k → m = n := by
      intro h
      induction k with
      | zero => simp only [add_zero]; assumption
      | succ k ih =>
        have : succ (m + k) = succ (n + k) := by simp only [add]; assumption
        have : m + k = n + k := by apply succ_eq; assumption
        show m = n; simp only [ih, *]

    theorem not_and_iff_or_not (p q : Prop) : ¬ (p ∧ q) ↔ ¬ p ∨ ¬ q := by
      apply Iff.intro
      . intro
        cases (Classical.em p) with
        | inl => cases (Classical.em q) with
          | inl =>
            have : p ∧ q := by constructor <;> assumption
            contradiction
          | inr => apply Or.inr; assumption
        | inr => apply Or.inl; assumption
      . intro h
        intro
        | And.intro _ _ =>
          cases h <;> contradiction

    theorem zero_sum_elim (m n : Nat) : m + n = zero → m = zero ∧ n = zero := by
      intro h
      apply Classical.byContradiction
      intro hn
      have : ¬ (m = zero) ∨ ¬ (n = zero) := Iff.mp (not_and_iff_or_not ..) hn
      have : m ≠ zero ∨ n ≠ zero := by simp [*]
      cases this with
      | inl => match m with
        | zero => contradiction
        | succ m' =>
          have : succ (m' + n) = zero := by simp [succ_add, *] at h
          match (succ (m' + n)) with
          | zero => contradiction
          | succ a => contradiction
      | inr => match n with
        | zero => contradiction
        | succ n' =>
          have : succ (m + n') = zero := by simp [add, *] at h
          cases (succ (m + n')) <;> contradiction

    /- Predecesor -/
    def pred (n : Nat) : Nat :=
      match n with
      | zero => zero
      | succ n' => n'

    theorem succ_pred_self (n : Nat) (h : n ≠ zero) : succ (pred n) = n :=
      match n with
      | zero => by contradiction
      | succ n' => by rfl

    /- Multiplication -/
    def mul (m n : Nat) : Nat :=
      match n with
      | zero => zero
      | succ n' => mul m n' + m

    notation:75 lhs:75 "*" rhs:76 => mul lhs rhs

    theorem mul_zero (n : Nat) : n * zero = zero := by rfl

    theorem mul_succ (m n : Nat) : m * succ n = m * n + m := by rfl

    theorem zero_mul (n : Nat) : zero * n = zero := by
      induction n with
      | zero => rfl
      | succ n ih => calc
        zero * succ n = zero * n + zero := by rw [mul_succ]
        _ = zero + zero := by simp only [ih]
        _ = zero := by simp only [add]

    theorem mul_one (n : Nat) : n * one = n := by
      calc
        n * one = n * zero + n := by simp only [mul]
        _ = n := by simp only [mul_zero, zero_add]

    theorem mul_add (m n k : Nat) : m * (n + k) = m * n + m * k := by
      induction k with
      | zero => rfl
      | succ k ih => calc
        m * (n + succ k) = m * succ (n + k) := by simp only [add]
        _ = m * (n + k) + m := by simp only [mul]
        _ = m * n + m * k + m := by rw [ih]
        _ = m * n + m * succ k := by simp only [mul_succ, add_assoc]

    theorem add_mul (m n k : Nat) : (m + n) * k = m * k + n * k := by
      induction k with
      | zero => rfl
      | succ k ih => calc
        (m + n) * succ k = (m + n) * k + (m + n) := by simp only [mul_succ]
        _ = m * k + n * k + (m + n) := by rw [ih]
        _ = (m * k + m) + (n * k + n) := by simp only [←add_assoc, add_comm]
        _ = m * succ k + n * succ k := by simp only [mul_succ]

    theorem one_mul (n : Nat) : one * n = n := by
      induction n with
      | zero => rfl
      | succ n ih => calc
        one * succ n = one * n + one := by simp only [mul]
        _ = n + one := by rw [ih]
        _ = succ n := by rw [succ_eq_add_one]

    theorem mul_comm (m n : Nat) : m * n = n * m := by
      induction n with
      | zero => simp only [mul_zero, zero_mul]
      | succ n ih => calc
        m * succ n = m * n + m := by simp only [mul]
        _ = n * m + one * m := by simp only [ih, one_mul]
        _ = (n + one) * m := by simp only [add_mul]
        _ = succ n * m := by simp only [succ_eq_add_one]

    theorem mul_assoc (m n k : Nat) : m * (n * k) = (m * n) * k := by
      induction k with
      | zero => simp only [mul]
      | succ k ih => calc
        m * (n * succ k) = m * (n * k + n) := by simp only [mul]
        _ = m * (n * k) + m * n := by simp only [mul_add]
        _ = (m * n) * k + (m * n) * one := by simp only [ih, mul_one]
        _ = (m * n) * (k + one) := by simp only [mul_add]
        _ = (m * n) * succ k := by simp only [succ_eq_add_one]

    theorem not_or_iff_and_not (p q : Prop) : ¬ (p ∨ q) ↔ ¬ p ∧ ¬ q := by
      apply Iff.intro
      . intro
        cases (Classical.em p) with
        | inl =>
          have : p ∨ q := by apply Or.inl; assumption
          contradiction
        | inr => cases (Classical.em q) with
          | inl =>
            have : p ∨ q := by apply Or.inr; assumption
            contradiction
          | inr => constructor <;> assumption
      . intro h
        intro ho
        cases ho with
        | inl => apply h.left; assumption
        | inr => apply h.right; assumption

    theorem zero_mul_elim (m n : Nat) : zero = m * n → m = zero ∨ n = zero := by
      intro h
      apply Classical.byContradiction
      intro
      have : m ≠ zero ∧ n ≠ zero := by
        apply Iff.mp (not_or_iff_and_not ..); assumption
      match this with
      | And.intro _ _ =>
        match n with
        | zero => contradiction
        | succ n => match m with
          | zero => contradiction
          | succ m =>
            have : zero = succ (succ m * n + m) := by calc
              zero = succ m * succ n := by simp only [*]
              _ = succ m * n + succ m := by simp only [mul]
              _ = succ (succ m * n + m) := by simp only [add]
            contradiction

    /-
    Exponentiation of Natural Numbers
    -/

    def pow : Nat → Nat → Nat
      | zero, zero => zero
      | _, zero => one
      | m, succ n => (pow m n) * m

    notation:85 lhs:85 "^" rhs:86 => pow lhs rhs

    theorem pow_zero (n : Nat) : n ≠ zero → n ^ zero = one := by
      intro; cases n; contradiction; rfl

    theorem pow_one (n : Nat) : n ^ one = n := by
      match n with
      | zero => rfl
      | succ n => calc
        (succ n) ^ one = (succ n) ^ zero * (succ n) := by simp only [pow]
        _ = one * (succ n) := by simp only [pow]
        _ = succ n := by simp only [one_mul]

    theorem one_pow (n : Nat) : one ^ n = one := by
      induction n with
      | zero => rfl
      | succ n ih => simp [pow, ih, one_mul]

    theorem zero_pow (n : Nat) : zero ^ n = zero := by
      cases n <;> rfl

    theorem pow_mul_sum (n a b : Nat) : n ^ a * n ^ b = n ^ (a + b) := by
      induction a with
      | zero => match n with
        | zero => calc
          zero ^ zero * zero ^ b = zero * zero ^ b := by simp [pow]
          _ = zero ^ (zero + b) := by simp [zero_mul, zero_pow, zero_add]
        | succ n =>
          have : succ n ≠ zero := by simp [add]
          calc
            succ n ^ zero * succ n ^ b = one * succ n ^ b := by simp [pow_zero (succ n) this]
            _ = succ n ^ b := by simp [one_mul]
            _ = succ n ^ (zero + b) := by simp [zero_add]
      | succ a ha => calc
        n ^ (succ a) * n ^ b = (n ^ a * n) * n ^ b := by simp [pow]
        _ = (n ^ a * n ^ b) * n := by simp [mul_assoc, mul_comm]
        _ = n ^ (a + b) * n := by rw [ha]
        _ = n ^ (succ (a + b)) := by simp [pow]
        _ = n ^ ((a + one) + b) := by simp [succ_eq_add_one (a + b), add_assoc, add_comm]; rfl
        _ = n ^ (succ a + b) := by simp [succ_eq_add_one]

    theorem pow_dist (m n a : Nat) : (m * n) ^ a = m ^ a * n ^ a := by
      induction a with
      | zero => match m, n with
        | zero, _ => simp [zero_mul, pow]
        | _, zero => simp [mul_zero, pow]
        | succ m, succ n => simp [pow]; rfl
      | succ a ih => calc
         (m * n) ^ (succ a) = (m * n) ^ a * (m * n) := by simp [pow]
         _ = (m ^ a * n ^ a) * (m * n) := by rw [ih]
         _ = (m ^ a * m) * (n ^ a * n) := by simp [mul_comm, mul_assoc]
         _ = m ^ succ a * n ^ succ a := by simp [pow]

    theorem pow_of_pow (n a b : Nat) : (n ^ a) ^ b = n ^ (a * b) := by
      induction a with
      | zero => match n with
        | zero => simp [pow, zero_pow]
        | succ n => calc
          (succ n ^ zero) ^ b = one ^ b := by simp [pow_zero]
          _ = one ^ (zero * b) := by simp [one_pow]
          _ = (succ n) ^ (zero * b) := by simp [pow_zero, zero_mul]
      | succ a ih => calc
        (n ^ (succ a)) ^ b = (n ^ a * n) ^ b := by simp [pow]
        _ = (n ^ a) ^ b * n ^ b := by simp [pow_dist]
        _ = n ^ (a * b) * n ^ b := by simp [ih]
        _ = n ^ (a * b + b) := by simp [pow_mul_sum]
        _ = n ^ ((a + one) * b) := by simp [add_mul, one_mul]

    /- Nth root of b eq a -/
    def root (n b a : Nat) : Prop := a ^ n = b

    theorem root_cancel : root n (b ^ n) b := by rfl
  end Nat
end hidden

/- Operation over Vectors -/

namespace hidden2

  inductive Vector (α : Type u) : Nat → Type u where
  | nil : Vector α 0
  | cons : α → {n : Nat} → Vector α n → Vector α (n + 1)
  deriving Repr

  namespace Vector

  def head : {n : Nat} → Vector α (n + 1) → α
    | _, cons a as => a

  def tail : {n : Nat} → Vector α (n + 1) → Vector α n
    | _, cons a as => as

  def appendAux (h : m = n) (vs : Vector α m) : Vector α n := by
    simp [h] at vs; assumption

  def append {m n : Nat} (as : Vector α m) (bs : Vector α n) : Vector α (m + n) := by
    match m, as with
    | 0, nil =>
      have : n = 0 + n := by simp_arith
      exact appendAux this bs
    | Nat.succ m, cons a as =>
      have : (m + (n + 1) = (m + 1) + n) := by simp_arith [←Nat.add_assoc m 1 n]
      exact appendAux this (cons a (append as bs))

  #eval append (cons 1 (cons 2 nil)) (cons 4 (cons 5 nil)) -- 1 2 4 5

  end Vector

end hidden2

/- Arithmetic Operations -/

inductive Expr where
| const : Nat → Expr
| var : Nat → Expr
| plus : Expr → Expr → Expr
| times : Expr → Expr → Expr
deriving Repr

namespace Expr

  def sampleExpr : Expr :=
    plus (times (var 0) (const 7)) (times (const 2) (var 1))

  def eval (v : Nat → Nat) : Expr → Nat
    | const n => n
    | var n => v n
    | plus e f => (eval v e) + (eval v f)
    | times e f => (eval v e) * (eval v f)

  def sampleVal : Nat → Nat
    | 0 => 5
    | 1 => 6
    | _ => 0

  #eval eval sampleVal sampleExpr -- 47

  def simpConst : Expr → Expr
    | plus (const a) (const b) => const (a + b)
    | times (const a) (const b) => const (a * b)
    | e => e

  theorem simpConst_eq (v : Nat → Nat)
      : ∀ e : Expr, eval v (simpConst e) = eval v e := by
    intro e
    match e with
    | const n => rfl
    | var n => rfl
    | plus f g => admit
    | times f g => match f, g with
      | const m, const n => rfl
      | f, g => admit

end Expr


/-
COMP2009-ACE

Exercise 01 (Propositional logic) (10 points)

Prove all the following propositions in Lean. 1 point per exercise.
That is replace "sorry" with your proof.

You are only allowed to use the tactics introduced in the lecture
(i.e. assume, exact, apply, constructor, cases, left, right, have)

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

-/

variables P Q R : Prop

theorem e01 : P → Q → P :=
begin
  assume p q,
  exact p,
end

theorem e02 : (P → Q → R) → (P → Q) → P → R :=
begin
  assume pqr pq p,
  apply pqr,
  exact p,
  apply pq,
  exact p,
end

theorem e03 : (P → Q) → P ∧ R → Q ∧ R :=
begin
  assume pq pr,
  cases pr with p r,
  constructor,
  apply pq,
  exact p,
  exact r,
end

theorem e04 : (P → Q) → P ∨ R → Q ∨ R :=
begin
  assume pq pr,
  cases pr with p r,
  left,
  apply pq,
  exact p,
  right,
  exact r,
end

theorem e05 : P ∨ Q → R ↔ (P → R) ∧ (Q → R) :=
begin
  constructor,
  assume pqr,
  constructor,
  assume p,
  apply pqr,
  left,
  exact p,

  assume q,
  apply pqr,
  right,
  exact q,

  assume first second,
  cases first with first_left first_right,
  cases second with c d,
  apply first_left,
  exact c,
  apply first_right,
  exact d,
end

theorem e06 : P → ¬ ¬ P :=
begin
  assume p,
  assume np,
  apply np,
  exact p,
end

theorem e07 : P ∧ true ↔ P :=
begin
  constructor,
  assume pt,
  cases pt with p t,
  exact p,

  assume p2,
  constructor,
  exact p2,
  trivial,
end

theorem e08 : P ∨ false ↔ P :=
begin
  constructor,
  assume pf,
  cases pf with p f,
  exact p,
  cases f,

  assume p2,
  left,
  exact p2,
end

theorem e09 : P ∧ false ↔ false :=
begin
  constructor,
  assume pf,
  cases pf with p f,
  cases f,

  assume f2,
  cases f2,
  --constructor and then exact also work
end

theorem e10 : P ∨ true ↔ true :=
begin
  constructor,
  assume pt,
  trivial, --cases pt and then exact also work
  assume t2,
  right,
  trivial,
end


import data.nat.basic
import tactic.ring
noncomputable theory
open_locale classical
open nat

def power : ℕ → ℕ → ℕ
| n zero := 1
| n (succ x) := power n x * n

theorem power_add (a b c : ℕ) : (power a b) * (power a c) = (power a (b + c)) :=
begin
  induction c with c hyp,
  calc power a b * power a 0
        = power a b * 1   : rfl
    ... = power a b       : by rw mul_one
    ... = power a (b + 0) : rfl,

  calc (power a b)*(power a(c+1))
        = (power a b) * ((power a c) * a) : rfl
    ... = (power a b * power a c) * a     : by ring
    ... = power a (b + c) * a             : by rw hyp
    ... = power a (b + c + 1)             : rfl,
end

theorem power_down (a b c : ℕ) : power (power a b) c = power a (b * c) :=
begin
  induction c with c hyp,

  -- base case
  calc power (power a b) 0
        = 1               : rfl
    ... = power a 0       : rfl
    ... = power a (b * 0) : rfl,

  -- induction step
  calc power (power a b) (c + 1)
        = power (power a b) c * (power a b) : rfl
    ... = power a (b * c) * (power a b)     : by rw hyp
    ... = power a (b * c + b)               : by rw power_add a (b * c) b
    ... = power a (b * (c + 1))             : rfl,
end


section propositional

variables P Q R : Prop

------------------------------------------------
-- Proposições de dupla negaço:
------------------------------------------------

theorem doubleneg_intro :
  P → ¬¬P  :=
begin
  intro P,
  intro NoP,
  have boom : false := NoP P,
  exact boom,
end

theorem doubleneg_elim :
  ¬¬P → P  :=
begin
  intro h,
  by_contradiction g,
  contradiction,

end

theorem doubleneg_law :
  ¬¬P ↔ P  :=
begin
  split,
  intro h,
  by_contradiction p,
  have boom: false := h p,
  contradiction,
  intro g,
  intro t,
  contradiction,
end

------------------------------------------------
-- Comutatividade dos ∨,∧:
------------------------------------------------

theorem disj_comm :
  (P ∨ Q) → (Q ∨ P)  :=
begin
  intro pouq,
  cases pouq with p q,
  right,
  exact p,
  left,
  exact q,
end

theorem conj_comm :
  (P ∧ Q) → (Q ∧ P)  :=
begin
  intro h,
  cases h with u v,
  split,
  exact v,
  exact u,
end

------------------------------------------------
-- Proposições de interdefinabilidade dos →,∨:
------------------------------------------------

theorem impl_as_disj_converse :
  (¬P ∨ Q) → (P → Q)  :=
begin
  intro Nopq,
  intro p,
  cases Nopq with np q,
  have boom: false := np p,
  contradiction,
  exact q,
end

theorem disj_as_impl :
  (P ∨ Q) → (¬P → Q)  :=
begin
  intro pouq,
  intro Nop,
  cases pouq with p q,
  have boom: false := Nop p,
  contradiction,
  exact q,
end

------------------------------------------------
-- Proposições de contraposição:
------------------------------------------------

theorem impl_as_contrapositive :
  (P → Q) → (¬Q → ¬P)  :=
begin
  intro PimpQ,
  intro NoQ,
  intro P,
  have Q: Q := PimpQ P,
  have boom: false := NoQ Q,
  exact boom,
end

theorem impl_as_contrapositive_converse :
  (¬Q → ¬P) → (P → Q)  :=
begin
  intro u,
  intro p,
  by_contra,
  have jj: ¬P := u h,
  contradiction,
end

theorem contrapositive_law :
  (P → Q) ↔ (¬Q → ¬P)  :=
begin
split,
apply impl_as_contrapositive,
apply impl_as_contrapositive_converse,

end

------------------------------------------------
-- A irrefutabilidade do LEM:
------------------------------------------------

theorem lem_irrefutable :
  ¬¬(P∨¬P)  :=
begin
  intro h,
  have g: (P∨¬P),
  right,
  intro j,
  have ll: (P∨¬P),
  left,
  exact j,
  contradiction,
  contradiction,

end

------------------------------------------------
-- A lei de Peirce
------------------------------------------------

theorem peirce_law_weak :
  ((P → Q) → P) → ¬¬P  :=
begin
  intro h,
  intro g,
  have hh: (P → Q),
  intro b,
  contradiction,
  have kj: P := h hh,
  contradiction,
end

------------------------------------------------
-- Proposições de interdefinabilidade dos ∨,∧:
------------------------------------------------

theorem disj_as_negconj :
  P∨Q → ¬(¬P∧¬Q)  :=
begin
  intro h,
  intro g,
  cases h with p q,
  cases g with nop noq,
  have boom: false := nop p,
  contradiction,
  cases g with nop noq,
  have boom: false := noq q,
  exact boom,
end

theorem conj_as_negdisj :
  P∧Q → ¬(¬P∨¬Q)  :=
begin
  intro h,
  intro g,
  cases h with p q,
  cases g with nop noq,
  contradiction,
  contradiction,
end

------------------------------------------------
-- As leis de De Morgan para ∨,∧:
------------------------------------------------

theorem demorgan_disj :
  ¬(P∨Q) → (¬P ∧ ¬Q)  :=
begin
  intro h,
  split,
  intro g,
  have pq: (P∨Q),
  left,
  exact g,
  have boom: false:= h pq,
  contradiction,
  intro pp,
  have bom: (P∨Q),
  right,
  exact pp,
  contradiction,

end

theorem demorgan_disj_converse :
  (¬P ∧ ¬Q) → ¬(P∨Q)  :=
begin
  intro h,
  intro g,
  cases h with nop noq,
  cases g with p q,
  contradiction,
  contradiction,
end

theorem demorgan_conj :
  ¬(P∧Q) → (¬Q ∨ ¬P)  :=
begin
  intro h,
  by_cases df: Q,
  right,
  intro gg,
  have nb: P ∧ Q,
  split,
  exact gg,
  exact df,
  have boom: false := h nb,
  contradiction,
  left,
  exact df,
end

theorem demorgan_conj_converse :
  (¬Q ∨ ¬P) → ¬(P∧Q)  :=
begin
  intro h,
  intro g,
  cases g with u v,
  cases h with t o,
  contradiction,
  contradiction,
end

theorem demorgan_conj_law :
  ¬(P∧Q) ↔ (¬Q ∨ ¬P)  :=
begin
  split,
  apply demorgan_conj,
  apply demorgan_conj_converse,
end

theorem demorgan_disj_law :
  ¬(P∨Q) ↔ (¬P ∧ ¬Q)  :=
begin
  split,
  apply demorgan_disj,
  apply demorgan_disj_converse,

end

------------------------------------------------
-- Proposições de distributividade dos ∨,∧:
------------------------------------------------

theorem distr_conj_disj :
  P∧(Q∨R) → (P∧Q)∨(P∧R)  :=
begin
  intro p,
  cases p with u v,
  cases v with pp mm,
  left,
  split,
  exact u,
  exact pp,
  right,
  split,
  exact u,
  exact mm,

end

theorem distr_conj_disj_converse :
  (P∧Q)∨(P∧R) → P∧(Q∨R)  :=
begin
  intro h,
  split,
  cases h with p q,
  cases p with pp f,
  exact pp,
  cases q with qq rr,
  exact qq,
  cases h with u v,
  cases u with t y,
  left,
  exact y,
  cases v with k l,
  right,
  exact l,
end

theorem distr_disj_conj :
  P∨(Q∧R) → (P∨Q)∧(P∨R)  :=
begin
  intro h,
  cases h with u v,
  split,
  left,
  exact u,
  left,
  exact u,
  split,
  cases v with w e,
  right,
  exact w,
  right,
  cases v with bb nn,
  exact nn,
end

theorem distr_disj_conj_converse :
  (P∨Q)∧(P∨R) → P∨(Q∧R)  :=
begin
  intro f,
  cases f with d e,
  cases d with yy pp,
  cases e with gg kk,
  left,
  exact yy,
  left,
  exact yy,
  cases e with tt uu,
  left,
  exact tt,
  right,
  split,
  exact pp,
  exact uu,

end

------------------------------------------------
-- Currificação
------------------------------------------------

theorem curry_prop :
  ((P∧Q)→R) → (P→(Q→R))  :=
begin
  intro h,
  intro g,
  intro f,
  have k: P ∧ Q,
  split,
  exact g,
  exact f,
  have pp: R := h k,
  exact pp,

end

theorem uncurry_prop :
  (P→(Q→R)) → ((P∧Q)→R)  :=
begin
  intro h,
  intro g,
  cases g with p q,
  have qr: Q → R := h p,
  have r: R := qr q,
  exact r,
end

------------------------------------------------
-- Reflexividade da →:
------------------------------------------------

theorem impl_refl :
  P → P  :=
begin
  intro h,
  exact h,
end

------------------------------------------------
-- Weakening and contraction:
------------------------------------------------

theorem weaken_disj_right :
  P → (P∨Q)  :=
begin
  intro h,
  left,
  exact h,
end

theorem weaken_disj_left :
  Q → (P∨Q)  :=
begin
  intro h,
  right,
  exact h,
end

theorem weaken_conj_right :
  (P∧Q) → P  :=
begin
  intro h,
  cases h with u v,
  exact u,
end

theorem weaken_conj_left :
  (P∧Q) → Q  :=
begin
  intro h,
  cases h with p q,
  exact q,
end

theorem conj_idempot :
  (P∧P) ↔ P :=
begin
  split,
  intro h,
  cases h with p hp,
  exact p,
  intro d,
  split,
  exact d,
  exact d,
end

theorem disj_idempot :
  (P∨P) ↔ P  :=
begin
  split,
  intro h,
  cases h with p hp,
  exact p,
  exact hp,
  intro g,
  left,
  exact g,
end

end propositional

----------------------------------------------------------------

section predicate

variable U : Type
variables P Q : U -> Prop

------------------------------------------------
-- As leis de De Morgan para ∃,∀:
------------------------------------------------

theorem demorgan_exists :
  ¬(∃x, P x) → (∀x, ¬P x)  :=
begin
  intro h,
  intro x,
  intro p,
  apply h,
  existsi x,
  exact p,
end

theorem demorgan_exists_converse :
  (∀x, ¬P x) → ¬(∃x, P x)  :=
begin
  intro h,
  intro g,
  cases g with x e,
  have boom: ¬P x := h x,
  contradiction,
end

theorem demorgan_forall :
  ¬(∀x, P x) → (∃x, ¬P x)  :=
begin
  rw contrapositive_law,
  rw doubleneg_law,
  intro h,
  intro x,
  by_cases j: P x,
  exact j,
  have: ∃ (x : U), ¬P x,
  existsi x,
  exact j,
  contradiction,

end

theorem demorgan_forall_converse :
  (∃x, ¬P x) → ¬(∀x, P x)  :=
begin
  intro h,
  intro g,
  cases h with x e,
have boom: P x := g x,
contradiction,
end

theorem demorgan_forall_law :
  ¬(∀x, P x) ↔ (∃x, ¬P x)  :=
begin
  split,
  apply demorgan_forall,
  apply demorgan_forall_converse,
end

theorem demorgan_exists_law :
  ¬(∃x, P x) ↔ (∀x, ¬P x)  :=
begin
split,
apply demorgan_exists,
apply demorgan_exists_converse,
end

------------------------------------------------
-- Proposições de interdefinabilidade dos ∃,∀:
------------------------------------------------

theorem exists_as_neg_forall :
  (∃x, P x) → ¬(∀x, ¬P x)  :=
begin
  intro h,
  intro g,
  cases h with x e,
  have boom: ¬P x := g x,
  contradiction,
end

theorem forall_as_neg_exists :
  (∀x, P x) → ¬(∃x, ¬P x)  :=
begin
  intro h,
  intro g,
  cases g with x e,
  have tt: P x := h x,
  contradiction,
end

theorem forall_as_neg_exists_converse :
  ¬(∃x, ¬P x) → (∀x, P x)  :=
begin
  intro h,
  intro x,
  have bt: ¬P x,
  intro pp,
end

theorem exists_as_neg_forall_converse :
  ¬(∀x, ¬P x) → (∃x, P x)  :=
begin
  sorry,
end

theorem forall_as_neg_exists_law :
  (∀x, P x) ↔ ¬(∃x, ¬P x)  :=
begin
  sorry,
end

theorem exists_as_neg_forall_law :
  (∃x, P x) ↔ ¬(∀x, ¬P x)  :=
begin
  split,
  apply exists_as_neg_forall,

end

------------------------------------------------
--  Proposições de distributividade de quantificadores:
------------------------------------------------

theorem exists_conj_as_conj_exists :
  (∃x, P x ∧ Q x) → (∃x, P x) ∧ (∃x, Q x)  :=
begin
  intro h,
  cases h with x l,
  cases l with j m,
  split,
  existsi x,
  exact j,
  existsi x,
  exact m,
end

theorem exists_disj_as_disj_exists :
  (∃x, P x ∨ Q x) → (∃x, P x) ∨ (∃x, Q x)  :=
begin
  intro h,
  cases h with x l,
  cases l with u v,
  left,
  existsi x,
  exact u,
  right,
  existsi x,
  exact v,

end

theorem exists_disj_as_disj_exists_converse :
  (∃x, P x) ∨ (∃x, Q x) → (∃x, P x ∨ Q x)  :=
begin
  intro h,
  cases h with e k,
  cases e with x m,
  existsi x,
  left,
  exact m,
  cases k with d f,
  existsi d,
  right,
  exact f,
end

theorem forall_conj_as_conj_forall :
  (∀x, P x ∧ Q x) → (∀x, P x) ∧ (∀x, Q x)  :=
begin
  intro h,
  split,
  intro x,
  have m: P x ∧ Q x := h x,
  cases m with t c,
  exact t,
  intro xx,
  have mm: P xx ∧ Q xx := h xx,
  cases mm with ee rr,
  exact rr,

end

theorem forall_conj_as_conj_forall_converse :
  (∀x, P x) ∧ (∀x, Q x) → (∀x, P x ∧ Q x)  :=
begin
  sorry,
end

theorem forall_disj_as_disj_forall_converse :
  (∀x, P x) ∨ (∀x, Q x) → (∀x, P x ∨ Q x)  :=
begin
  sorry,
end

/- NOT THEOREMS --------------------------------

theorem forall_disj_as_disj_forall :
  (∀x, P x ∨ Q x) → (∀x, P x) ∨ (∀x, Q x)  :=
begin
end

theorem exists_conj_as_conj_exists_converse :
  (∃x, P x) ∧ (∃x, Q x) → (∃x, P x ∧ Q x)  :=
begin
end

---------------------------------------------- -/

end predicate


-- Uniqueness of limits in a metric space
-- https://www.codewars.com/kata/5ea1f341014f0c0001ec7c5e/train/lean

/-
This kata was designed by @JasonKY

Using the Lean maths library's definition of a metric space, we can define the notion of a sequence converging to a limit. Note: the arrow in the notation s ⟶ x can be typeset in VS Code with \hom or \h.

The challenge is to prove that a sequence of elements in a metric space has at most one limit.

Here is the contents of Preloaded.lean.
-/

import topology.metric_space.basic
import tactic

def converges_to {X : Type*} [metric_space X] (s : ℕ → X) (x : X) :=
∀ (ε : ℝ) (hε : 0 < ε), ∃ N : ℕ, ∀ (n : ℕ) (hn : N ≤ n), dist x (s n) < ε

notation s ` ⟶ ` x := converges_to s x

#check archimedean

lemma archimedean' (ε : ℝ) : (0 ≤ ε ∧ ∀ n : ℕ, (ε * n) < 1) → ε = 0 := sorry

lemma lem1 {ε N : ℝ}
  (pos_ε : 0 ≤ ε)
  (pos_N : 0 < N)
  (hε : ε < 2 * (1 / (2 * N) ))
: ε * N < 1 :=
calc
  ε * N <  2 * (1 / (2 * N) ) * N : by sorry
    ... = (2 / (2 * N)) * N : sorry
    ... = (1/N) * N : sorry
    ... = 1 : by sorry

theorem limit_unique {X : Type*} [metric_space X] {s : ℕ → X}
  (x₀ x₁ : X) (h₀ : s ⟶ x₀) (h₁ : s ⟶ x₁) :
x₀ = x₁ :=
begin
unfold converges_to at h₀,
unfold converges_to at h₁,
suffices hx: (dist x₀ x₁) = 0,
  by exact zero_eq_dist.mp (eq.symm hx),
apply archimedean',
split,
  by simp only [dist_nonneg],
intros N,
set ε := 1/(2 * (N : ℝ)) with he,
have hε : ε > 0, by sorry,
specialize h₀ ε hε,
specialize h₁ ε hε,
cases h₀ with N₀ h₀,
cases h₁ with N₁ h₁,

let n:= max N₀ N₁,
specialize h₀ n,
specialize h₁ n,

have hN₀ : N₀ ≤ n, exact le_max_left N₀ N₁,
have hN₁ : N₁ ≤ n, exact le_max_right N₀ N₁,

have hd₀ := h₀ hN₀,
have hd₁ := h₁ hN₁,

have hd₀₁:= dist_triangle_right x₀ x₁ (s n),
have : dist x₀ x₁ < 2 * ε, by linarith,
clear h₀ h₁ hd₀ hd₁ hd₀₁ hN₀ hN₁ n,
rw he at this,

-- simp only [dist_nonneg, this],

-- specialize h₀ N₀,
-- have : N₀ ≤ N₀, by exact rfl.ge,
-- have h₀' := h₀ this,
-- clear h₀ this,

repeat {sorry,}
end

#check @dist_nonneg

github-repos/lean/apurvanakade-lean-playground/lean-playground-2fe58797031ff8a6c29e1a442cbcc7a0ebc9c768/src/codewars/incomplete/Uniqueness of limits in a metric space.lean