import .vector_space

open vector_space

universes u v
variables {V : Type u} {F : Type v} [field F] [vector_space V F]

class subspace (U : set V) : Prop :=
(contains_zero : (0 : V) ∈ U)
(add_closed  : ∀ {u v : V}, u ∈ U → v ∈ U → u + v ∈ U)
(mul_closed  : ∀ (a : F) {u : V}, u ∈ U → a ⋅ u ∈ U)

open subspace

instance subspace_is_vector_space {U : set V} [@subspace _ F _ _ U] : vector_space (subtype U) F :=
{ add        := λ⟨u, pu⟩ ⟨v, pv⟩, ⟨u + v, add_closed _ pu pv⟩,
  neg        := λ⟨u, pu⟩, ⟨-u, by {rw ←neg_one_scalar_mul_neg u, exact mul_closed (-1) pu}⟩,
  scalar_mul := λ a ⟨u, pu⟩, ⟨a ⋅ u, mul_closed a pu⟩,
  zero       := ⟨0, contains_zero _ _⟩,

  add_comm     := λ⟨_,_⟩ ⟨_,_⟩,       subtype.eq (add_comm _ _),
  add_assoc    := λ⟨_,_⟩ ⟨_,_⟩ ⟨_,_⟩, subtype.eq (add_assoc _ _ _),
  zero_add     := λ⟨_,_⟩,             subtype.eq (zero_add _),
  add_zero     := λ⟨_,_⟩,             subtype.eq (add_zero _),
  add_left_neg := λ⟨_,_⟩,             subtype.eq (add_left_neg _),

  scalar_mul_assoc         := λ _ _ ⟨_,_⟩,       subtype.eq (scalar_mul_assoc         _ _ _),
  one_scalar_mul           := λ     ⟨_,_⟩,       subtype.eq (one_scalar_mul           _ _),
  scalar_mul_left_distrib  := λ _   ⟨_,_⟩ ⟨_,_⟩, subtype.eq (scalar_mul_left_distrib  _ _ _),
  scalar_mul_right_distrib := λ _ _ ⟨_,_⟩,       subtype.eq (scalar_mul_right_distrib _ _ _) }

-- Subset of V containing the sum of any two elements from U₁ and U₂
inductive sum_of_subsets {V F} [field F] [vector_space V F] (U₁ U₂ : set V) : set V
| intro {u v : V} : u ∈ U₁ → v ∈ U₂ → sum_of_subsets (u + v)

instance : has_add (set V) :=
⟨@sum_of_subsets _ F _ _⟩

-- Sum of subspaces is a subspace
instance sum_of_subsets_is_subspace {U₁ U₂ : set V} [@subspace _ F _ _ U₁] [@subspace _ F _ _ U₂] :
subspace (U₁ + U₂) :=
{ contains_zero := begin
    -- WTS 0 + 0 ∈ U₁ + U₂
    rw ←add_zero (0 : V),
    -- This is trivial, because 0 ∈ U₁ and 0 ∈ U₂
    split, repeat { apply contains_zero }
  end,
  add_closed := begin
    introv h1 h2,
    cases h1 with u1 u2,
    cases h2 with v1 v2,
    show (u1 + u2) + (v1 + v2) ∈ U₁ + U₂,
    -- WTS (u1 + v1) + (u2 + v2) ∈ U1 + U2
    rw [add_assoc u1 u2 (v1 + v2),
       ←add_assoc u2 v1 v2,
        add_comm  u2 v1,
        add_assoc v1 u2 v2,
       ←add_assoc u1 v1 (u2 + v2)],
    -- This is trivial since u1 + v1 ∈ U₁ and u2 + v2 ∈ U₂
    split, all_goals {apply add_closed, assumption, assumption}
  end,
  mul_closed := begin
    intros a _ h1,
    cases h1 with u v,
    show a ⋅ (u + v) ∈ U₁ + U₂, simp,
    -- WTS a ⋅ u + a ⋅ v ∈ U₁ + U₂
    -- This is trivial since a ⋅ u ∈ U₁ and a ⋅ v ∈ U₂
    split, all_goals {apply mul_closed, assumption}
  end }

-- A better-behaved singleton set
inductive just {α} (a : α) : set α
| intro : just a

instance zero_subspace : @subspace V F _ _ (just 0) :=
{ contains_zero := by split,
  add_closed := begin
    introv h1 h2,
    cases h1, cases h2,
    show (0 : V) + 0 ∈ just (0 : V),
    simp, split
  end,
  mul_closed := begin
    intros a _ h,
    cases h,
    show a ⋅ (0 : V) ∈ just (0 : V),
    simp, split
  end }

-- A vector space is the largest subspace of itself, of course
instance total_space : @subspace V F _ _ set.univ :=
{ contains_zero :=            trivial,
  add_closed    := λ _ _ _ _, trivial,
  mul_closed    := λ _ _ _,   trivial }

lemma set.empty {V} (a : V) : ¬a ∈ (∅ : set V) :=
id

lemma empty_not_subspace : ¬(@subspace V F _ _ ∅) :=
assume h : subspace ∅,
absurd h.contains_zero (set.empty 0)


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

/-
Non-constant terms of linear constraints are represented
by storing their coefficients in integer lists.
-/

import data.list.func
import tactic.ring
import tactic.omega.misc

namespace omega
namespace coeffs

open list.func

variable {v : nat → int}

/-- `val_between v as l o` is the value (under valuation `v`) of the term
    obtained taking the term represented by `(0, as)` and dropping all
    subterms that include variables outside the range `[l,l+o)` -/
@[simp] def val_between (v : nat → int) (as : list int) (l : nat) : nat → int
| 0     := 0
| (o+1) := (val_between o) + (get (l+o) as * v (l+o))

@[simp] lemma val_between_nil {l : nat} :
  ∀ m, val_between v [] l m = 0
| 0     := by simp only [val_between]
| (m+1) :=
  by simp only [val_between_nil m, omega.coeffs.val_between,
     get_nil, zero_add, zero_mul, int.default_eq_zero]

/-- Evaluation of the nonconstant component of a normalized linear arithmetic term. -/
def val (v : nat → int) (as : list int) : int :=
val_between v as 0 as.length

@[simp] lemma val_nil : val v [] = 0 := rfl

lemma val_between_eq_of_le {as : list int} {l : nat} :
  ∀ m, as.length ≤ l + m →
  val_between v as l m = val_between v as l (as.length - l)
| 0 h1 := by { rw add_zero at h1, rw tsub_eq_zero_iff_le.mpr h1 }
| (m+1) h1 :=
  begin
    rw le_iff_eq_or_lt at h1, cases h1,
    { rw [h1, add_comm l, add_tsub_cancel_right] },
    have h2 :  list.length as ≤ l + m,
    { rw ← nat.lt_succ_iff, apply h1 },
    simpa [ get_eq_default_of_le _ h2, zero_mul, add_zero,
      val_between ] using val_between_eq_of_le _ h2
  end

lemma val_eq_of_le {as : list int} {k : nat} :
  as.length ≤ k → val v as = val_between v as 0 k :=
begin
  intro h1, unfold val,
  rw [val_between_eq_of_le k _], refl,
  rw zero_add, exact h1
end

lemma val_between_eq_val_between
  {v w : nat → int} {as bs : list int} {l : nat} :
  ∀ {m}, (∀ x, l ≤ x → x < l + m → v x = w x) →
    (∀ x, l ≤ x → x < l + m → get x as = get x bs) →
    val_between v as l m = val_between w bs l m
| 0 h1 h2 := rfl
| (m+1) h1 h2 :=
  begin
    unfold val_between,
    have h3 : l + m < l + (m + 1),
    { rw ← add_assoc, apply lt_add_one },
    apply fun_mono_2,
    apply val_between_eq_val_between; intros x h4 h5,
    { apply h1 _ h4 (lt_trans h5 h3) },
    { apply h2 _ h4 (lt_trans h5 h3) },
    rw [h1 _ _ h3, h2 _ _ h3];
    apply nat.le_add_right
  end

open_locale list.func

lemma val_between_set {a : int} {l n : nat} :
  ∀ {m}, l ≤ n → n < l + m → val_between v ([] {n ↦ a}) l m = a * v n
| 0 h1 h2 :=
  begin exfalso, apply lt_irrefl l (lt_of_le_of_lt h1 h2) end
| (m+1) h1 h2 :=
  begin
    rw [← add_assoc, nat.lt_succ_iff, le_iff_eq_or_lt] at h2,
    cases h2; unfold val_between,
    { have h3 : val_between v ([] {l + m ↦ a}) l m = 0,
      { apply @eq.trans _ _ (val_between v [] l m),
        { apply val_between_eq_val_between,
          { intros, refl },
          { intros x h4 h5, rw [get_nil,
            get_set_eq_of_ne, get_nil],
            apply ne_of_lt h5 } },
        apply val_between_nil },
      rw h2,
      simp only [h3, zero_add, list.func.get_set] },
    { have h3 : l + m ≠ n,
      { apply ne_of_gt h2 },
      rw [@val_between_set m h1 h2, get_set_eq_of_ne _ _ h3],
      simp only [h3, get_nil, add_zero, zero_mul, int.default_eq_zero] }
  end

@[simp] lemma val_set {m : nat} {a : int} :
  val v ([] {m ↦ a}) = a * v m :=
begin
  apply val_between_set (zero_le _),
  rw [length_set, zero_add],
  exact lt_max_of_lt_right (lt_add_one _),
end

lemma val_between_neg {as : list int} {l : nat} :
  ∀ {o}, val_between v (neg as) l o = -(val_between v as l o)
| 0 := rfl
| (o+1) :=
  begin
    unfold val_between,
    rw [neg_add, neg_mul_eq_neg_mul],
    apply fun_mono_2,
    apply val_between_neg,
    apply fun_mono_2 _ rfl,
    apply get_neg
  end

@[simp] lemma val_neg {as : list int} :
   val v (neg as) = -(val v as) :=
by simpa only [val, length_neg] using val_between_neg

lemma val_between_add {is js : list int} {l : nat} :
  ∀ m, val_between v (add is js) l m =
  (val_between v is l m) + (val_between v js l m)
| 0     := rfl
| (m+1) :=
  by { simp only [val_between, val_between_add m,
      list.func.get, get_add], ring }

@[simp] lemma val_add {is js : list int} :
  val v (add is js) = (val v is) + (val v js) :=
begin
  unfold val,
  rw val_between_add, apply fun_mono_2;
  apply val_between_eq_of_le;
  rw [zero_add, length_add],
  apply le_max_left, apply le_max_right
end

lemma val_between_sub {is js : list int} {l : nat} :
  ∀ m, val_between v (sub is js) l m =
  (val_between v is l m) - (val_between v js l m)
| 0 := rfl
| (m+1) :=
  by { simp only [val_between, val_between_sub m,
      list.func.get, get_sub], ring }

@[simp] lemma val_sub {is js : list int} :
  val v (sub is js) = (val v is) - (val v js) :=
begin
  unfold val,
  rw val_between_sub,
  apply fun_mono_2;
  apply val_between_eq_of_le;
  rw [zero_add, length_sub],
  apply le_max_left,
  apply le_max_right
end

/-- `val_except k v as` is the value (under valuation `v`) of the term
    obtained taking the term represented by `(0, as)` and dropping the
    subterm that includes the `k`th variable. -/
def val_except (k : nat) (v : nat → int) (as) :=
val_between v as 0 k + val_between v as (k+1) (as.length - (k+1))

lemma val_except_eq_val_except
  {k : nat} {is js : list int} {v w : nat → int} :
  (∀ x ≠ k, v x = w x) → (∀ x ≠ k, get x is = get x js) →
  val_except k v is = val_except k w js :=
begin
  intros h1 h2, unfold val_except,
  apply fun_mono_2,
  { apply val_between_eq_val_between;
    intros x h3 h4;
    [ {apply h1}, {apply h2} ];
    apply ne_of_lt;
    rw zero_add at h4;
    apply h4 },
  { repeat { rw ← val_between_eq_of_le
      ((max is.length js.length) - (k+1)) },
    { apply val_between_eq_val_between;
      intros x h3 h4;
      [ {apply h1}, {apply h2} ];
      apply ne.symm;
      apply ne_of_lt;
      rw nat.lt_iff_add_one_le;
      exact h3 },
    { refine le_trans (le_max_right _ _) le_add_tsub },
    { refine le_trans (le_max_left _ _) le_add_tsub } }
end

open_locale omega

lemma val_except_update_set
  {n : nat} {as : list int} {i j : int} :
  val_except n (v⟨n ↦ i⟩) (as {n ↦ j}) = val_except n v as :=
by apply val_except_eq_val_except update_eq_of_ne (get_set_eq_of_ne _)

lemma val_between_add_val_between {as : list int} {l m : nat} :
  ∀ {n}, val_between v as l m + val_between v as (l+m) n =
  val_between v as l (m+n)
| 0 := by simp only [val_between, add_zero]
| (n+1) :=
  begin
    rw ← add_assoc,
    unfold val_between,
    rw add_assoc,
    rw ← @val_between_add_val_between n,
    ring,
  end

lemma val_except_add_eq (n : nat) {as : list int} :
  (val_except n v as) + ((get n as) * (v n)) = val v as :=
begin
  unfold val_except, unfold val,
  cases le_total (n + 1) as.length with h1 h1,
  { have h4 := @val_between_add_val_between v as 0 (n+1) (as.length - (n+1)),
    have h5 : n + 1 + (as.length - (n + 1)) = as.length,
    { rw [add_comm, tsub_add_cancel_of_le h1] },
    rw h5 at h4, apply eq.trans _ h4,
     simp only [val_between, zero_add], ring },
  have h2 : list.length as - (n + 1) = 0,
  { exact tsub_eq_zero_iff_le.mpr h1 },
  have h3 : val_between v as 0 (list.length as) =
            val_between v as 0 (n + 1),
  { simpa only [val] using @val_eq_of_le v as (n+1) h1 },
  simp only [add_zero, val_between, zero_add, h2, h3]
end

@[simp] lemma val_between_map_mul {i : int} {as: list int} {l : nat} :
  ∀ {m}, val_between v (list.map ((*) i) as) l m = i * val_between v as l m
| 0     := by simp only [val_between, mul_zero, list.map]
| (m+1) :=
  begin
    unfold val_between,
    rw [@val_between_map_mul m, mul_add],
    apply fun_mono_2 rfl,
    by_cases h1 : l + m < as.length,
    { rw [get_map h1, mul_assoc] },
    rw not_lt at h1,
    rw [get_eq_default_of_le, get_eq_default_of_le];
    try {simp}; apply h1
  end

lemma forall_val_dvd_of_forall_mem_dvd {i : int} {as : list int} :
  (∀ x ∈ as, i ∣ x) → (∀ n, i ∣ get n as) | h1 n :=
by { apply forall_val_of_forall_mem _ h1,
     apply dvd_zero }

lemma dvd_val_between {i} {as: list int} {l : nat} :
  ∀ {m}, (∀ x ∈ as, i ∣ x) → (i ∣ val_between v as l m)
| 0 h1 := dvd_zero _
| (m+1) h1 :=
  begin
    unfold val_between,
    apply dvd_add,
    apply dvd_val_between h1,
    apply dvd_mul_of_dvd_left,
    by_cases h2 : get (l+m) as = 0,
    { rw h2, apply dvd_zero },
    apply h1, apply mem_get_of_ne_zero h2
  end

lemma dvd_val {as : list int} {i : int} :
  (∀ x ∈ as, i ∣ x) → (i ∣ val v as) := by apply dvd_val_between

@[simp] lemma val_between_map_div
  {as: list int} {i : int} {l : nat} (h1 : ∀ x ∈ as, i ∣ x) :
  ∀ {m}, val_between v (list.map (λ x, x / i) as) l m = (val_between v as l m) / i
| 0     := by simp only [int.zero_div, val_between, list.map]
| (m+1) :=
  begin
    unfold val_between,
    rw [@val_between_map_div m, int.add_div_of_dvd_right],
    apply fun_mono_2 rfl,
    { apply calc get (l + m) (list.map (λ (x : ℤ), x / i) as) * v (l + m)
          = ((get (l + m) as) / i) * v (l + m) :
            begin
              apply fun_mono_2 _ rfl,
              rw get_map',
              apply int.zero_div
            end
      ... =  get (l + m) as * v (l + m) / i :
            begin
              repeat {rw mul_comm _ (v (l+m))},
              rw int.mul_div_assoc,
              apply forall_val_dvd_of_forall_mem_dvd h1
            end },
    apply dvd_mul_of_dvd_left,
    apply forall_val_dvd_of_forall_mem_dvd h1,
  end

@[simp] lemma val_map_div {as : list int} {i : int} :
  (∀ x ∈ as, i ∣ x) → val v (list.map (λ x, x / i) as) = (val v as) / i :=
by {intro h1, simpa only [val, list.length_map] using val_between_map_div h1}

lemma val_between_eq_zero {is: list int} {l : nat} :
  ∀ {m}, (∀ x : int, x ∈ is → x = 0) → val_between v is l m = 0
| 0 h1 := rfl
| (m+1) h1 :=
  begin
    have h2 := @forall_val_of_forall_mem _ _ is (λ x, x = 0) rfl h1,
    simpa only [val_between, h2 (l+m), zero_mul, add_zero]
      using @val_between_eq_zero m h1,
  end

lemma val_eq_zero {is : list int} :
  (∀ x : int, x ∈ is → x = 0) → val v is = 0 :=
by apply val_between_eq_zero

end coeffs

end omega


/-
Copyright (c) 2019 Kevin Buzzard. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kevin Buzzard
-/
import data.real.basic

/-!
# The extended reals [-∞, ∞].

This file defines `ereal`, the real numbers together with a top and bottom element,
referred to as ⊤ and ⊥. It is implemented as `with_top (with_bot ℝ)`

Addition and multiplication are problematic in the presence of ±∞, but
negation has a natural definition and satisfies the usual properties.
An addition is derived, but `ereal` is not even a monoid (there is no identity).

`ereal` is a `complete_lattice`; this is now deduced by type class inference from
the fact that `with_top (with_bot L)` is a complete lattice if `L` is
a conditionally complete lattice.

## Tags

real, ereal, complete lattice

## TODO

abs : ereal → ℝ≥0∞

In Isabelle they define + - * and / (making junk choices for things like -∞ + ∞)
and then prove whatever bits of the ordered ring/field axioms still hold. They
also do some limits stuff (liminf/limsup etc).
See https://isabelle.in.tum.de/dist/library/HOL/HOL-Library/Extended_Real.html
-/

/-- ereal : The type `[-∞, ∞]` -/
@[derive [linear_order, order_bot, order_top,
  has_Sup, has_Inf, complete_lattice, has_add]]
def ereal := with_top (with_bot ℝ)

namespace ereal
instance : has_coe ℝ ereal := ⟨some ∘ some⟩
@[simp, norm_cast] protected lemma coe_real_le {x y : ℝ} : (x : ereal) ≤ (y : ereal) ↔ x ≤ y :=
by { unfold_coes, norm_num }
@[simp, norm_cast] protected lemma coe_real_lt {x y : ℝ} : (x : ereal) < (y : ereal) ↔ x < y :=
by { unfold_coes, norm_num }
@[simp, norm_cast] protected lemma coe_real_inj' {x y : ℝ} : (x : ereal) = (y : ereal) ↔ x = y :=
by { unfold_coes, simp [option.some_inj] }

instance : has_zero ereal := ⟨(0 : ℝ)⟩
instance : inhabited ereal := ⟨0⟩

/-! ### Negation -/

/-- negation on ereal -/
protected def neg : ereal → ereal
| ⊥       := ⊤
| ⊤       := ⊥
| (x : ℝ) := (-x : ℝ)

instance : has_neg ereal := ⟨ereal.neg⟩

@[norm_cast] protected lemma neg_def (x : ℝ) : ((-x : ℝ) : ereal) = -x := rfl

/-- - -a = a on ereal -/
protected theorem neg_neg : ∀ (a : ereal), - (- a) = a
| ⊥ := rfl
| ⊤ := rfl
| (a : ℝ) := by { norm_cast, simp [neg_neg a] }

theorem neg_inj (a b : ereal) (h : -a = -b) : a = b := by rw [←ereal.neg_neg a, h, ereal.neg_neg b]

/-- Even though ereal is not an additive group, -a = b ↔ -b = a still holds -/
theorem neg_eq_iff_neg_eq {a b : ereal} : -a = b ↔ -b = a :=
⟨by {intro h, rw ←h, exact ereal.neg_neg a},
 by {intro h, rw ←h, exact ereal.neg_neg b}⟩

/-- if -a ≤ b then -b ≤ a on ereal -/
protected theorem neg_le_of_neg_le : ∀ {a b : ereal} (h : -a ≤ b), -b ≤ a
| ⊥ ⊥ h := h
| ⊥ (some b) h := by cases (top_le_iff.1 h)
| ⊤ l h := le_top
| (a : ℝ) ⊥ h := by cases (le_bot_iff.1 h)
| l ⊤ h := bot_le
| (a : ℝ) (b : ℝ) h := by { norm_cast at h ⊢, exact _root_.neg_le_of_neg_le h }

/-- -a ≤ b ↔ -b ≤ a on ereal-/
protected theorem neg_le {a b : ereal} : -a ≤ b ↔ -b ≤ a :=
⟨ereal.neg_le_of_neg_le, ereal.neg_le_of_neg_le⟩

/-- a ≤ -b → b ≤ -a on ereal -/
theorem le_neg_of_le_neg {a b : ereal} (h : a ≤ -b) : b ≤ -a :=
by rwa [←ereal.neg_neg b, ereal.neg_le, ereal.neg_neg]

end ereal


/-
Copyright (c) 2017 Johannes Hölzl. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johannes Hölzl
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.algebra.big_operators.basic
import Mathlib.data.finset.intervals
import Mathlib.PostPort

universes u_1 v

namespace Mathlib

/-!
# Results about big operators over intervals

We prove results about big operators over intervals (mostly the `ℕ`-valued `Ico m n`).
-/

namespace finset

theorem sum_Ico_add {δ : Type u_1} [add_comm_monoid δ] (f : ℕ → δ) (m : ℕ) (n : ℕ) (k : ℕ) : (finset.sum (Ico m n) fun (l : ℕ) => f (k + l)) = finset.sum (Ico (m + k) (n + k)) fun (l : ℕ) => f l :=
  Eq.subst (Ico.image_add m n k) Eq.symm
    (sum_image fun (x : ℕ) (hx : x ∈ Ico m n) (y : ℕ) (hy : y ∈ Ico m n) (h : k + x = k + y) => nat.add_left_cancel h)

theorem prod_Ico_add {β : Type v} [comm_monoid β] (f : ℕ → β) (m : ℕ) (n : ℕ) (k : ℕ) : (finset.prod (Ico m n) fun (l : ℕ) => f (k + l)) = finset.prod (Ico (m + k) (n + k)) fun (l : ℕ) => f l :=
  sum_Ico_add f m n k

theorem sum_Ico_succ_top {δ : Type u_1} [add_comm_monoid δ] {a : ℕ} {b : ℕ} (hab : a ≤ b) (f : ℕ → δ) : (finset.sum (Ico a (b + 1)) fun (k : ℕ) => f k) = (finset.sum (Ico a b) fun (k : ℕ) => f k) + f b := sorry

theorem prod_Ico_succ_top {β : Type v} [comm_monoid β] {a : ℕ} {b : ℕ} (hab : a ≤ b) (f : ℕ → β) : (finset.prod (Ico a (b + 1)) fun (k : ℕ) => f k) = (finset.prod (Ico a b) fun (k : ℕ) => f k) * f b :=
  sum_Ico_succ_top hab fun (k : ℕ) => f k

theorem sum_eq_sum_Ico_succ_bot {δ : Type u_1} [add_comm_monoid δ] {a : ℕ} {b : ℕ} (hab : a < b) (f : ℕ → δ) : (finset.sum (Ico a b) fun (k : ℕ) => f k) = f a + finset.sum (Ico (a + 1) b) fun (k : ℕ) => f k := sorry

theorem prod_eq_prod_Ico_succ_bot {β : Type v} [comm_monoid β] {a : ℕ} {b : ℕ} (hab : a < b) (f : ℕ → β) : (finset.prod (Ico a b) fun (k : ℕ) => f k) = f a * finset.prod (Ico (a + 1) b) fun (k : ℕ) => f k :=
  sum_eq_sum_Ico_succ_bot hab fun (k : ℕ) => f k

theorem prod_Ico_consecutive {β : Type v} [comm_monoid β] (f : ℕ → β) {m : ℕ} {n : ℕ} {k : ℕ} (hmn : m ≤ n) (hnk : n ≤ k) : ((finset.prod (Ico m n) fun (i : ℕ) => f i) * finset.prod (Ico n k) fun (i : ℕ) => f i) =
  finset.prod (Ico m k) fun (i : ℕ) => f i :=
  Eq.subst (Ico.union_consecutive hmn hnk) Eq.symm (prod_union (Ico.disjoint_consecutive m n k))

theorem sum_range_add_sum_Ico {β : Type v} [add_comm_monoid β] (f : ℕ → β) {m : ℕ} {n : ℕ} (h : m ≤ n) : ((finset.sum (range m) fun (k : ℕ) => f k) + finset.sum (Ico m n) fun (k : ℕ) => f k) =
  finset.sum (range n) fun (k : ℕ) => f k :=
  Ico.zero_bot m ▸ Ico.zero_bot n ▸ sum_Ico_consecutive f (nat.zero_le m) h

theorem sum_Ico_eq_add_neg {δ : Type u_1} [add_comm_group δ] (f : ℕ → δ) {m : ℕ} {n : ℕ} (h : m ≤ n) : (finset.sum (Ico m n) fun (k : ℕ) => f k) =
  (finset.sum (range n) fun (k : ℕ) => f k) + -finset.sum (range m) fun (k : ℕ) => f k := sorry

theorem sum_Ico_eq_sub {δ : Type u_1} [add_comm_group δ] (f : ℕ → δ) {m : ℕ} {n : ℕ} (h : m ≤ n) : (finset.sum (Ico m n) fun (k : ℕ) => f k) =
  (finset.sum (range n) fun (k : ℕ) => f k) - finset.sum (range m) fun (k : ℕ) => f k := sorry

theorem sum_Ico_eq_sum_range {β : Type v} [add_comm_monoid β] (f : ℕ → β) (m : ℕ) (n : ℕ) : (finset.sum (Ico m n) fun (k : ℕ) => f k) = finset.sum (range (n - m)) fun (k : ℕ) => f (m + k) := sorry

theorem prod_Ico_reflect {β : Type v} [comm_monoid β] (f : ℕ → β) (k : ℕ) {m : ℕ} {n : ℕ} (h : m ≤ n + 1) : (finset.prod (Ico k m) fun (j : ℕ) => f (n - j)) = finset.prod (Ico (n + 1 - m) (n + 1 - k)) fun (j : ℕ) => f j := sorry

theorem sum_Ico_reflect {δ : Type u_1} [add_comm_monoid δ] (f : ℕ → δ) (k : ℕ) {m : ℕ} {n : ℕ} (h : m ≤ n + 1) : (finset.sum (Ico k m) fun (j : ℕ) => f (n - j)) = finset.sum (Ico (n + 1 - m) (n + 1 - k)) fun (j : ℕ) => f j :=
  prod_Ico_reflect f k h

theorem prod_range_reflect {β : Type v} [comm_monoid β] (f : ℕ → β) (n : ℕ) : (finset.prod (range n) fun (j : ℕ) => f (n - 1 - j)) = finset.prod (range n) fun (j : ℕ) => f j := sorry

theorem sum_range_reflect {δ : Type u_1} [add_comm_monoid δ] (f : ℕ → δ) (n : ℕ) : (finset.sum (range n) fun (j : ℕ) => f (n - 1 - j)) = finset.sum (range n) fun (j : ℕ) => f j :=
  prod_range_reflect f n

@[simp] theorem prod_Ico_id_eq_factorial (n : ℕ) : (finset.prod (Ico 1 (n + 1)) fun (x : ℕ) => x) = nat.factorial n := sorry

@[simp] theorem prod_range_add_one_eq_factorial (n : ℕ) : (finset.prod (range n) fun (x : ℕ) => x + 1) = nat.factorial n := sorry

/-- Gauss' summation formula -/
theorem sum_range_id_mul_two (n : ℕ) : (finset.sum (range n) fun (i : ℕ) => i) * bit0 1 = n * (n - 1) := sorry

/-- Gauss' summation formula -/
theorem sum_range_id (n : ℕ) : (finset.sum (range n) fun (i : ℕ) => i) = n * (n - 1) / bit0 1 := sorry


/-
Copyright (c) 2019 Chris Hughes. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Chris Hughes
-/
import set_theory.cardinal

/-!
# Denumerability of ℚ

This file proves that ℚ is infinite, denumerable, and deduces that it has cardinality `omega`.
-/

namespace rat
open denumerable

instance : infinite ℚ :=
infinite.of_injective (coe : ℕ → ℚ) nat.cast_injective

private def denumerable_aux : ℚ ≃ { x : ℤ × ℕ // 0 < x.2 ∧ x.1.nat_abs.coprime x.2 } :=
{ to_fun := λ x, ⟨⟨x.1, x.2⟩, x.3, x.4⟩,
  inv_fun := λ x, ⟨x.1.1, x.1.2, x.2.1, x.2.2⟩,
  left_inv := λ ⟨_, _, _, _⟩, rfl,
  right_inv := λ ⟨⟨_, _⟩, _, _⟩, rfl }

/-- **Denumerability of the Rational Numbers** -/
instance : denumerable ℚ :=
begin
  let T := { x : ℤ × ℕ // 0 < x.2 ∧ x.1.nat_abs.coprime x.2 },
  letI : infinite T := infinite.of_injective _ denumerable_aux.injective,
  letI : encodable T := encodable.subtype,
  letI : denumerable T := of_encodable_of_infinite T,
  exact denumerable.of_equiv T denumerable_aux
end

end rat

namespace cardinal

open_locale cardinal

lemma mk_rat : #ℚ = ω := mk_denumerable ℚ

end cardinal


/-
Copyright (c) 2018 Chris Hughes. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Chris Hughes, Johannes Hölzl, Scott Morrison, Jens Wagemaker
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.data.polynomial.monomial
import Mathlib.data.finset.nat_antidiagonal
import Mathlib.PostPort

universes u v u_1

namespace Mathlib

/-!
# Theory of univariate polynomials

The theorems include formulas for computing coefficients, such as
`coeff_add`, `coeff_sum`, `coeff_mul`

-/

namespace polynomial

theorem coeff_one {R : Type u} [semiring R] (n : ℕ) : coeff 1 n = ite (0 = n) 1 0 :=
  coeff_monomial

@[simp] theorem coeff_add {R : Type u} [semiring R] (p : polynomial R) (q : polynomial R) (n : ℕ) : coeff (p + q) n = coeff p n + coeff q n :=
  rfl

theorem coeff_sum {R : Type u} {S : Type v} [semiring R] {p : polynomial R} [semiring S] (n : ℕ) (f : ℕ → R → polynomial S) : coeff (finsupp.sum p f) n = finsupp.sum p fun (a : ℕ) (b : R) => coeff (f a b) n :=
  finsupp.sum_apply

@[simp] theorem coeff_smul {R : Type u} [semiring R] (p : polynomial R) (r : R) (n : ℕ) : coeff (r • p) n = r * coeff p n :=
  finsupp.smul_apply

theorem mem_support_iff_coeff_ne_zero {R : Type u} {n : ℕ} [semiring R] {p : polynomial R} : n ∈ finsupp.support p ↔ coeff p n ≠ 0 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (n ∈ finsupp.support p ↔ coeff p n ≠ 0)) (propext (finsupp.mem_support_to_fun p n))))
    (iff.refl (finsupp.to_fun p n ≠ 0))

theorem not_mem_support_iff_coeff_zero {R : Type u} {n : ℕ} [semiring R] {p : polynomial R} : ¬n ∈ finsupp.support p ↔ coeff p n = 0 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (¬n ∈ finsupp.support p ↔ coeff p n = 0)) (propext (finsupp.mem_support_to_fun p n))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (¬finsupp.to_fun p n ≠ 0 ↔ coeff p n = 0)) (propext not_not)))
      (iff.refl (finsupp.to_fun p n = 0)))

/-- The nth coefficient, as a linear map. -/
def lcoeff (R : Type u) [semiring R] (n : ℕ) : linear_map R (polynomial R) R :=
  finsupp.lapply n

@[simp] theorem lcoeff_apply {R : Type u} [semiring R] (n : ℕ) (f : polynomial R) : coe_fn (lcoeff R n) f = coeff f n :=
  rfl

@[simp] theorem finset_sum_coeff {R : Type u} [semiring R] {ι : Type u_1} (s : finset ι) (f : ι → polynomial R) (n : ℕ) : coeff (finset.sum s fun (b : ι) => f b) n = finset.sum s fun (b : ι) => coeff (f b) n :=
  Eq.symm (finset.sum_hom s fun (q : polynomial R) => coe_fn (lcoeff R n) q)

theorem coeff_mul {R : Type u} [semiring R] (p : polynomial R) (q : polynomial R) (n : ℕ) : coeff (p * q) n = finset.sum (finset.nat.antidiagonal n) fun (x : ℕ × ℕ) => coeff p (prod.fst x) * coeff q (prod.snd x) :=
  add_monoid_algebra.mul_apply_antidiagonal p q n (finset.nat.antidiagonal n)
    fun (x : ℕ × ℕ) => finset.nat.mem_antidiagonal

@[simp] theorem mul_coeff_zero {R : Type u} [semiring R] (p : polynomial R) (q : polynomial R) : coeff (p * q) 0 = coeff p 0 * coeff q 0 := sorry

theorem coeff_mul_X_zero {R : Type u} [semiring R] (p : polynomial R) : coeff (p * X) 0 = 0 := sorry

theorem coeff_X_mul_zero {R : Type u} [semiring R] (p : polynomial R) : coeff (X * p) 0 = 0 := sorry

theorem coeff_C_mul_X {R : Type u} [semiring R] (x : R) (k : ℕ) (n : ℕ) : coeff (coe_fn C x * X ^ k) n = ite (n = k) x 0 := sorry

@[simp] theorem coeff_C_mul {R : Type u} {a : R} {n : ℕ} [semiring R] (p : polynomial R) : coeff (coe_fn C a * p) n = a * coeff p n :=
  add_monoid_algebra.single_zero_mul_apply p a n

theorem C_mul' {R : Type u} [semiring R] (a : R) (f : polynomial R) : coe_fn C a * f = a • f :=
  ext fun (n : ℕ) => coeff_C_mul f

@[simp] theorem coeff_mul_C {R : Type u} [semiring R] (p : polynomial R) (n : ℕ) (a : R) : coeff (p * coe_fn C a) n = coeff p n * a :=
  add_monoid_algebra.mul_single_zero_apply p a n

theorem coeff_X_pow {R : Type u} [semiring R] (k : ℕ) (n : ℕ) : coeff (X ^ k) n = ite (n = k) 1 0 := sorry

@[simp] theorem coeff_X_pow_self {R : Type u} [semiring R] (n : ℕ) : coeff (X ^ n) n = 1 := sorry

theorem coeff_mul_X_pow {R : Type u} [semiring R] (p : polynomial R) (n : ℕ) (d : ℕ) : coeff (p * X ^ n) (d + n) = coeff p d := sorry

@[simp] theorem coeff_mul_X {R : Type u} [semiring R] (p : polynomial R) (n : ℕ) : coeff (p * X) (n + 1) = coeff p n := sorry

theorem mul_X_pow_eq_zero {R : Type u} [semiring R] {p : polynomial R} {n : ℕ} (H : p * X ^ n = 0) : p = 0 :=
  ext fun (k : ℕ) => Eq.trans (Eq.symm (coeff_mul_X_pow p n k)) (iff.mp ext_iff H (k + n))

theorem C_mul_X_pow_eq_monomial {R : Type u} [semiring R] (c : R) (n : ℕ) : coe_fn C c * X ^ n = coe_fn (monomial n) c := sorry

theorem support_mul_X_pow {R : Type u} [semiring R] (c : R) (n : ℕ) (H : c ≠ 0) : finsupp.support (coe_fn C c * X ^ n) = singleton n :=
  eq.mpr (id (Eq._oldrec (Eq.refl (finsupp.support (coe_fn C c * X ^ n) = singleton n)) (C_mul_X_pow_eq_monomial c n)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (finsupp.support (coe_fn (monomial n) c) = singleton n)) (support_monomial n c H)))
      (Eq.refl (singleton n)))

theorem support_C_mul_X_pow' {R : Type u} [semiring R] {c : R} {n : ℕ} : finsupp.support (coe_fn C c * X ^ n) ⊆ singleton n :=
  eq.mpr (id (Eq._oldrec (Eq.refl (finsupp.support (coe_fn C c * X ^ n) ⊆ singleton n)) (C_mul_X_pow_eq_monomial c n)))
    (support_monomial' n c)

theorem C_dvd_iff_dvd_coeff {R : Type u} [semiring R] (r : R) (φ : polynomial R) : coe_fn C r ∣ φ ↔ ∀ (i : ℕ), r ∣ coeff φ i := sorry

/--  If the coefficients of a polynomial belong to n ideal contains the submodule span of the
coefficients of a polynomial. -/
theorem span_le_of_coeff_mem_C_inverse {R : Type u} [semiring R] {f : polynomial R} {I : submodule (polynomial R) (polynomial R)} (cf : ∀ (i : ℕ), coeff f i ∈ ⇑C ⁻¹' submodule.carrier I) : submodule.span (polynomial R) (set_of fun (g : polynomial R) => ∃ (i : ℕ), g = coe_fn C (coeff f i)) ≤ I := sorry

theorem mem_span_C_coeff {R : Type u} [semiring R] {f : polynomial R} : f ∈ submodule.span (polynomial R) (set_of fun (g : polynomial R) => ∃ (i : ℕ), g = coe_fn C (coeff f i)) := sorry

theorem exists_coeff_not_mem_C_inverse {R : Type u} [semiring R] {f : polynomial R} {I : submodule (polynomial R) (polynomial R)} : ¬f ∈ I → ∃ (i : ℕ), ¬coeff f i ∈ ⇑C ⁻¹' submodule.carrier I :=
  imp_of_not_imp_not (¬f ∈ I) (∃ (i : ℕ), ¬coeff f i ∈ ⇑C ⁻¹' submodule.carrier I)
    fun (cf : ¬∃ (i : ℕ), ¬coeff f i ∈ ⇑C ⁻¹' submodule.carrier I) =>
      iff.mpr not_not (span_le_of_coeff_mem_C_inverse (iff.mp not_exists_not cf) mem_span_C_coeff)


/-
Copyright (c) 2020 The Xena project. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kevin Buzzard.
Thanks: Imperial College London, leanprover-community
-/

-- Import levels 1 to 3
import complex.Level_03_conj

/-!

# Level 4: Norms

Define `norm_sq : ℂ → ℝ` by defining `norm_sq(z)` to be
`re(z)*re(z)+im(z)*im(z)` and see what you can prove about it.

-/

namespace complex

/-- The real number which is the squared norm of z -/
def norm_sq (z : ℂ) : ℝ := sorry

/-! ## Behaviour with respect to 0, 1 and I -/

@[simp] lemma norm_sq_zero : norm_sq 0 = 0 := sorry
@[simp] lemma norm_sq_one : norm_sq 1 = 1 := sorry
@[simp] lemma norm_sq_I : norm_sq I = 1 := sorry

/-! ## Behaviour with respect to *, + and - -/

@[simp] lemma norm_sq_mul (z w : ℂ) : norm_sq (z * w) = norm_sq z * norm_sq w :=
sorry

lemma norm_sq_add (z w : ℂ) : norm_sq (z + w) =
  norm_sq z + norm_sq w + 2 * (z * conj w).re :=
sorry

@[simp] lemma norm_sq_neg (z : ℂ) : norm_sq (-z) = norm_sq z :=
sorry

/-! ## Behaviour with respect to `conj` -/

@[simp] lemma norm_sq_conj (z : ℂ) : norm_sq (conj z) = norm_sq z :=
sorry

/-! ## Behaviour with respect to real numbers` -/

@[simp] lemma norm_sq_of_real (r : ℝ) : norm_sq r = r * r :=
sorry

theorem mul_conj (z : ℂ) : z * conj z = norm_sq z :=
sorry

/-! ## Behaviour with respect to real 0, ≤, < and so on -/

-- Warning: you will have to know something about Lean's API for
-- real numbers to solve these ones. If you turn the statements about
-- complex numbers into statements about real numbers, you'll find
-- they're of the form "prove $$x^2+y^2\geq0$$" with `x` and `y` real.

lemma norm_sq_nonneg (z : ℂ) : 0 ≤ norm_sq z := sorry

@[simp] lemma norm_sq_eq_zero {z : ℂ} : norm_sq z = 0 ↔ z = 0 :=
sorry

@[simp] lemma norm_sq_pos {z : ℂ} : 0 < norm_sq z ↔ z ≠ 0 :=
sorry

lemma re_sq_le_norm_sq (z : ℂ) : z.re * z.re ≤ norm_sq z :=
sorry

lemma im_sq_le_norm_sq (z : ℂ) : z.im * z.im ≤ norm_sq z :=
sorry

end complex