/-
Copyright (c) 2020 Damiano Testa. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Damiano Testa
-/
import data.polynomial.degree.definitions

/-!
# Erase the leading term of a univariate polynomial

## Definition

* `erase_lead f`: the polynomial `f - leading term of f`

`erase_lead` serves as reduction step in an induction, shaving off one monomial from a polynomial.
The definition is set up so that it does not mention subtraction in the definition,
and thus works for polynomials over semirings as well as rings.
-/

noncomputable theory
open_locale classical

open polynomial finset

namespace polynomial

variables {R : Type*} [semiring R] {f : polynomial R}

/-- `erase_lead f` for a polynomial `f` is the polynomial obtained by
subtracting from `f` the leading term of `f`. -/
def erase_lead (f : polynomial R) : polynomial R :=
polynomial.erase f.nat_degree f

section erase_lead

lemma erase_lead_support (f : polynomial R) :
  f.erase_lead.support = f.support.erase f.nat_degree :=
by simp only [erase_lead, support_erase]

lemma erase_lead_coeff (i : ℕ) :
  f.erase_lead.coeff i = if i = f.nat_degree then 0 else f.coeff i :=
by simp only [erase_lead, coeff_erase]

@[simp] lemma erase_lead_coeff_nat_degree : f.erase_lead.coeff f.nat_degree = 0 :=
by simp [erase_lead_coeff]

lemma erase_lead_coeff_of_ne (i : ℕ) (hi : i ≠ f.nat_degree) :
  f.erase_lead.coeff i = f.coeff i :=
by simp [erase_lead_coeff, hi]

@[simp] lemma erase_lead_zero : erase_lead (0 : polynomial R) = 0 :=
by simp only [erase_lead, erase_zero]

@[simp] lemma erase_lead_add_monomial_nat_degree_leading_coeff (f : polynomial R) :
  f.erase_lead + monomial f.nat_degree f.leading_coeff = f :=
begin
  ext i,
  simp only [erase_lead_coeff, coeff_monomial, coeff_add, @eq_comm _ _ i],
  split_ifs with h,
  { subst i, simp only [leading_coeff, zero_add] },
  { exact add_zero _ }
end

@[simp] lemma erase_lead_add_C_mul_X_pow (f : polynomial R) :
  f.erase_lead + (C f.leading_coeff) * X ^ f.nat_degree = f :=
by rw [C_mul_X_pow_eq_monomial, erase_lead_add_monomial_nat_degree_leading_coeff]

@[simp] lemma self_sub_monomial_nat_degree_leading_coeff {R : Type*} [ring R] (f : polynomial R) :
  f - monomial f.nat_degree f.leading_coeff = f.erase_lead :=
(eq_sub_iff_add_eq.mpr (erase_lead_add_monomial_nat_degree_leading_coeff f)).symm

@[simp] lemma self_sub_C_mul_X_pow {R : Type*} [ring R] (f : polynomial R) :
  f - (C f.leading_coeff) * X ^ f.nat_degree = f.erase_lead :=
by rw [C_mul_X_pow_eq_monomial, self_sub_monomial_nat_degree_leading_coeff]

lemma erase_lead_ne_zero (f0 : 2 ≤ f.support.card) : erase_lead f ≠ 0 :=
begin
  rw [ne.def, ← card_support_eq_zero, erase_lead_support],
  exact (zero_lt_one.trans_le $ (tsub_le_tsub_right f0 1).trans
    finset.pred_card_le_card_erase).ne.symm
end

@[simp] lemma nat_degree_not_mem_erase_lead_support : f.nat_degree ∉ (erase_lead f).support :=
by simp [not_mem_support_iff]

lemma ne_nat_degree_of_mem_erase_lead_support {a : ℕ} (h : a ∈ (erase_lead f).support) :
  a ≠ f.nat_degree :=
by { rintro rfl, exact nat_degree_not_mem_erase_lead_support h }

lemma erase_lead_support_card_lt (h : f ≠ 0) : (erase_lead f).support.card < f.support.card :=
begin
  rw erase_lead_support,
  exact card_lt_card (erase_ssubset $ nat_degree_mem_support_of_nonzero h)
end

lemma erase_lead_card_support' {c : ℕ} (fc : f.support.card = c + 1) :
  f.erase_lead.support.card = c :=
erase_lead_card_support fc

@[simp] lemma erase_lead_monomial (i : ℕ) (r : R) :
  erase_lead (monomial i r) = 0 :=
begin
  by_cases hr : r = 0,
  { subst r, simp only [monomial_zero_right, erase_lead_zero] },
  { rw [erase_lead, nat_degree_monomial _ _ hr, erase_monomial] }
end

@[simp] lemma erase_lead_C (r : R) : erase_lead (C r) = 0 :=
erase_lead_monomial _ _

@[simp] lemma erase_lead_X : erase_lead (X : polynomial R) = 0 :=
erase_lead_monomial _ _

@[simp] lemma erase_lead_X_pow (n : ℕ) : erase_lead (X ^ n : polynomial R) = 0 :=
by rw [X_pow_eq_monomial, erase_lead_monomial]

@[simp] lemma erase_lead_C_mul_X_pow (r : R) (n : ℕ) : erase_lead (C r * X ^ n) = 0 :=
by rw [C_mul_X_pow_eq_monomial, erase_lead_monomial]

lemma erase_lead_degree_le : (erase_lead f).degree ≤ f.degree :=
begin
  rw degree_le_iff_coeff_zero,
  intros i hi,
  rw erase_lead_coeff,
  split_ifs with h, { refl },
  apply coeff_eq_zero_of_degree_lt hi
end

lemma erase_lead_nat_degree_le : (erase_lead f).nat_degree ≤ f.nat_degree :=
nat_degree_le_nat_degree erase_lead_degree_le

lemma erase_lead_nat_degree_lt (f0 : 2 ≤ f.support.card) :
  (erase_lead f).nat_degree < f.nat_degree :=
lt_of_le_of_ne erase_lead_nat_degree_le $ ne_nat_degree_of_mem_erase_lead_support $
  nat_degree_mem_support_of_nonzero $ erase_lead_ne_zero f0

lemma erase_lead_nat_degree_lt_or_erase_lead_eq_zero (f : polynomial R) :
  (erase_lead f).nat_degree < f.nat_degree ∨ f.erase_lead = 0 :=
begin
  by_cases h : f.support.card ≤ 1,
  { right,
    rw ← C_mul_X_pow_eq_self h,
    simp },
  { left,
    apply erase_lead_nat_degree_lt (lt_of_not_ge h) }
end

end erase_lead

/-- An induction lemma for polynomials. It takes a natural number `N` as a parameter, that is
required to be at least as big as the `nat_degree` of the polynomial.  This is useful to prove
results where you want to change each term in a polynomial to something else depending on the
`nat_degree` of the polynomial itself and not on the specific `nat_degree` of each term. -/
lemma induction_with_nat_degree_le {R : Type*} [semiring R] {P : polynomial R → Prop} (N : ℕ)
  (P_0 : P 0)
  (P_C_mul_pow : ∀ n : ℕ, ∀ r : R, r ≠ 0 → n ≤ N → P (C r * X ^ n))
  (P_C_add : ∀ f g : polynomial R, f.nat_degree ≤ N → g.nat_degree ≤ N → P f → P g → P (f + g)) :
  ∀ f : polynomial R, f.nat_degree ≤ N → P f :=
begin
  intros f df,
  generalize' hd : card f.support = c,
  revert f,
  induction c with c hc,
  { assume f df f0,
    convert P_0,
    simpa only [support_eq_empty, card_eq_zero] using f0},

 --   exact λ f df f0, by rwa (finsupp.support_eq_empty.mp (card_eq_zero.mp f0)) },
  { intros f df f0,
    rw ← erase_lead_add_C_mul_X_pow f,
    refine P_C_add f.erase_lead _ (erase_lead_nat_degree_le.trans df) _ _ _,
    { exact (nat_degree_C_mul_X_pow_le f.leading_coeff f.nat_degree).trans df },
    { exact hc _ (erase_lead_nat_degree_le.trans df) (erase_lead_card_support f0) },
    { refine P_C_mul_pow _ _ _ df,
      rw [ne.def, leading_coeff_eq_zero],
      rintro rfl,
      exact not_le.mpr c.succ_pos f0.ge } }
end

end polynomial


import game.limits.seq_convCauchy

namespace xena -- hide

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

/-
Another useful result for working with sequences -- a convergent sequence is bounded.
-/

/-
We define the concepts of a bounded sequence, in much the same way
that we defined the concepts of a limit and a convergent sequence.
-/

/-
NOTE: limits/seq_cauchyBdd.lean introduces the definition

is_bdd (a : ℕ → ℝ) := ∃ B > 0, ∀ n, |a n| ≤ B

Here I omit the B > 0 condition (which follows from properties of abs?)
But this level is perhaps superfluous anyway.
-/

definition is_bound (a : ℕ → ℝ) (M : ℝ) := ∀ n, | a n | ≤ M
definition is_bounded (a : ℕ → ℝ) := ∃ M, is_bound a M

/- Lemma
A convergent sequence is bounded.
-/
open finset
lemma bounded_if_convergent (a : ℕ → ℝ)
    (ha : is_convergent a ) :
    is_bounded a :=
begin

cases ha with α islim,
specialize islim 1 (by linarith),  -- specialise to the case ε = 1, linarith knows this is OK
cases islim with N_k HN_k,         -- introduce the natural N_k fulfilling ε = 1 case

   -- We use these to show that there is a bound for all terms with index ≥ N_k
   -- Then take the max of this bound and the earlier terms.

have bounds_geq_N_k : ∀ n ≥ N_k, |a n| ≤ | α | + 1,

{
intros bigN Nisbig,                 -- introduce some bigN ≥ N_k
specialize HN_k bigN,               -- specialize to bigN
have fact1:= HN_k(Nisbig),          -- just applying our conditional
rw abs_lt at fact1,

rw abs_le,     -- note our abs_le will require us to show c ≥ 0
simp,
split,

    {have smallfact: - α ≤ | α | := neg_le_abs_self α,
        linarith},

    {have smallfact2: α ≤ | α | := le_abs_self(α),
        linarith},

-- condition for abs_le
have fact: 0 ≤ |α|, from abs_nonneg α,
linarith,

},

   --  Having shown that we have a bound for the elements above N_k, we consider the earlier terms
   --  This section is mostly cribbed from Kevin's M1P1 limits file

   let rangeset := finset.image (abs ∘ a) (range (N_k + 1)),    -- range (N) is set of nats < N

   have fact4 : |a 0| ∈ rangeset := finset.mem_image_of_mem _ (mem_range.2 (nat.zero_lt_succ _)),
   have fact5 : rangeset ≠ ∅ := ne_empty_of_mem fact4,
   have fact6 := nonempty_iff_ne_empty.mpr fact5,

   let Bx := rangeset.max' fact6,
   let B := max Bx ( |α|  + 1),

use B,
intro n,
cases lt_or_ge n N_k with Hlt Hge,

    simp,
    left,
    have HBx : ∀ n < N_k, |a n| ≤ Bx := λ n Hn, le_max' rangeset fact6 _
    (mem_image_of_mem _ (mem_range.2 (nat.lt_succ_of_lt Hn))),
    apply HBx,
    exact Hlt,

    have h2 : |a n| ≤ |α| + 1 := bounds_geq_N_k n Hge,
    have h3 : |α| + 1 ≤ B := le_max_right _ _,
    linarith,
end

end xena -- hide


import data.real.basic

/-
# Chapter 1 : Sets

## Level 9
-/

/-
This is a little more complicated example asking you to work with intervals of reals.
The result will be of help in the sup-inf world.
-/

notation `[` a `,` b `]`  := set.Icc a b

def mem_prod_sets (A : set ℝ) (B : set ℝ) := { x : ℝ | ∃ y ∈ A, ∃ z ∈ B, x = y * z}

/- Lemma
Prove that if $x = 0$, then `x ∈ mem_prod_sets [(-2:ℝ),-1] [(0:ℝ), 3]`
-/
lemma zero_in_prod : (0:ℝ) ∈ mem_prod_sets [(-2:ℝ), -1] [(0:ℝ), 3] :=
begin
  set a0 := (0:ℝ) with ha,
  have h1 : a0 ∈ (set.Icc (0:ℝ) 3), simp, linarith,
  set b := (-2:ℝ) with hb,
  have h2 : b ∈ set.Icc (-2:ℝ) (-1:ℝ), simp, linarith,
  use b, split, exact h2,
  use a0, split, exact h1, rw ha, norm_num, done
end


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

import representation_theory.fdRep -- finite-dimensional representations

/-

Here is a fourth way of doing undergraduate representation theory in Lean ;-)

# Finite dimensional representations via category theory

As well as the category `Rep k G` there's the full subcategory of finite-dimensional
representations `fdRep k G`. Let's see it in action!

-/

variables {k : Type} [field k] {G : Type} [group G]

/-

The category of representations of G on finite-dimensional k-vector spaces is called `fdRep k G`

-/

example : Type 1 := fdRep k G

/-

Given `V : fdRep k G` here's how to recover the `representation`:

-/

example (V : fdRep k G) : representation k G V := V.ρ -- note: the type is `representation k G ↥V`

/-

Conversely, given a finite-dimensional `representation` we can make an object
of the category.

-/

example (V : Type) [add_comm_group V] [module k V] [finite_dimensional k V]
  (ρ : representation k G V) : fdRep k G :=
fdRep.of ρ

-- Here's how to say that a finite-dimensional representation is irreducible:

open category_theory

example (V : fdRep k G) : Prop := simple V -- this is `category_theory.simple`

-- Here's Schur's lemma, stated and proved in the `fdRep` language:

open finite_dimensional

open_locale classical

-- if k is alg closed and V,W are irreducible then Hom(V,W) has dimension 1
-- if V ≅ W, and 0 otherwise.
example [is_alg_closed k] (V W : fdRep k G) [simple V] [simple W] :
  finrank k (V ⟶ W) = if nonempty (V ≅ W) then 1 else 0 :=
fdRep.finrank_hom_simple_simple V W


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

import tactic -- imports all the Lean tactics

/-!

# Logic in Lean, example sheet 1 : "implies" (`→`)

We learn about propositions, and implications `P → Q` between them. You can get
this arrow by typing `\to` or `\r`. Mathematicians usually write the
implication arrow as `P ⇒ Q` but Lean prefers a single arrow.

## The absolute basics

`P : Prop` means that `P` is a true-false statement. `h : P` means
that `h` is a proof that `P` is true, or you can regard `h` as an
assumption that `P` is true; logically these are the same. Stuff above
the `⊢` symbol is your assumptions. The statement to the right of it is
the goal. Your job is to prove the goal from the assumptions.

## Tactics you will need

To solve the levels on this sheet you will need to know how to use the
following tactics:

* `intro`
* `exact`
* `apply`

### The `intro` tactic

Mathematical background: `intro h,` says "To prove `P → Q`, you can assume
that `P` is true (call this assumption `h`) and then it
suffices to prove `Q`."

Lean: If your goal is `⊢ P → Q` then `intro h,` will introduce a
hypothesis `h : P` (i.e. a hypothesis that `P` is true)
and change the goal to `⊢ Q`.

### The `exact` tactic

Mathematics: If you have an assumption `h` that `P` is true,
and your goal is to prove that `P` is true, then `exact h,`
will solve this goal.

Lean: If your goal is `⊢ P` and you have a hypothesis `h : P`
then `exact h,` will solve it.

### The `apply` tactic

Mathematics: `apply` is *arguing backwards*. It is like "it suffices to...".
If you're trying to prove `Q`, and you know `h : P → Q` is true, then it
suffices to prove `P`. So `apply h,` *changes the goal* from `Q` to `P`. The key
point to remember is that `apply h` will only work when `h` is an implication,
and it will only work when the *conclusion* of `h` *matches the goal*.

Lean: If your goal is `⊢ Q` and you have `h : P → Q` then `apply h,` will
change the goal to `⊢ P`.

-/

/-

## Worked examples

Click around in the proofs to see the tactic state (on the right) change.
The tactic is implemented and the state changes just before the comma.
I will use the following conventions: variables with capital
letters like `P`, `Q`, `R` denote propositions
(i.e. true/false statements) and variables whose names begin
with `h` like `h1` or `hP` are proofs or hypotheses.

-/

-- Throughout this sheet, `P`, `Q` and `R` will denote propositions.
variables (P Q R : Prop)

-- Here are some examples of `intro`, `exact` and `apply` being used.

-- Assume that `P` and `Q` and `R` are all true. Deduce that `P` is true.
example (hP : P) (hQ : Q) (hR : R) : P :=
begin
  -- note that `exact P` does *not* work. `P` is the proposition, `hP` is the proof.
  exact hP,
end

-- Assume `Q` is true. Prove that `P → Q`.
example (hQ : Q) : P → Q :=
begin
  -- The goal is of the form `X → Y` so we can use `intro`
  intro h, -- now `h` is the hypothesis that `P` is true.
  -- Our goal is now the same as a hypothesis so we can use `exact`
  exact hQ,
end

-- Assume `P → Q` and `P` is true. Deduce `Q`.
example (h : P → Q) (hP : P) : Q :=
begin
  -- our goal is `⊢ Q` which matches with the conclusion of `h` so `apply` works
  apply h,
  -- now our goal has changed to `P` which is an assumption
  exact hP,
end

/-

## Examples for you to try

Delete the `sorry`s and replace them with comma-separated tactic proofs
using `intro`, `exact` and `apply`.

-/

/-- Every proposition implies itself. -/
example : P → P :=
begin
  sorry
end

/-

Note that `→` is not associative: in general `P → (Q → R)` and `(P → Q) → R`
might not be equivalent. This is like subtraction on numbers -- in general
`a - (b - c)` and `(a - b) - c` might not be equal.

So if we write `P → Q → R` then we'd better know what this means.
The convention in Lean is that it means `P → (Q → R)`. If you think
about it, this means that to deduce `R` you will need to prove both `P`
and `Q`. In general to prove `P1 → P2 → P3 → ... Pn` you can assume
`P1`, `P2`,...,`P(n-1)` and then you have to prove `Pn`.

So the next level is asking you prove that `P → (Q → P)`.

-/
example : P → Q → P :=
begin
  sorry
end

/-- If we know `P`, and we also know `P → Q`, we can deduce `Q`.
This is called "Modus Ponens" by logicians. -/
example : P → (P → Q) → Q :=
begin
  sorry
end

/-- `→` is transitive. That is, if `P → Q` and `Q → R` are true, then
  so is `P → R`. -/
example : (P → Q) → (Q → R) → (P → R) :=
begin
  sorry,
end

-- If `h : P → Q → R` with goal `⊢ R` and you `apply h`, you'll get
-- two goals! Note that tactics operate on only the first goal.
example : (P → Q → R) → (P → Q) → (P → R) :=
begin
  sorry
end

-- Now they get a little harder. You can skip these if
-- you feel like you know what you're doing.

variables (S T : Prop)

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

example : (P → Q) → ((P → Q) → P) → Q :=
begin
  sorry
end

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

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

example : (((P → Q) → Q) → Q) → (P → Q) :=
begin
  sorry
end

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


-- Church numerals

-- Another way of doing nat.

-- The church nat, chℕ (happy to change the name) is a pi type
-- and not a structure. So proofs are not done by induction!
--import data.equiv

def chℕ := Π X : Type, (X → X) → X → X
namespace chnat

open nat
-- map from normal nats
def of_nat : ℕ → chℕ
| 0 := λ X f x, x
| (succ n) := λ X f x, f (of_nat n X f x)
-- can I close nat now?

-- examples of chnats
def c0 := of_nat 0
def c1 := of_nat 1
def c2 := of_nat 2
def c3 := of_nat 3

-- now we have some constants.

-- what is zero?
example (X f x) : c0 X f x = x := rfl
-- what is one?
example (X f x) : c1 X f x = f x := rfl
-- what is two?
example (X f x) : c2 X f x = f (f x) := rfl
-- what is three?
example (X f x) : c3 X f x = f (f (f x)) := rfl
-- and so on

-- we can go back from chℕ to ℕ
definition to_nat : chℕ → ℕ := λ m, m ℕ nat.succ 0 -- there is a beauty here
-- it is almost as if the structure ℕ were built to be fed into chℕ
-- Why does this happen? KB doesn't understand

-- that definition needs to be moved if we can't prove functoriality wrt succ

example : to_nat c3 = 3 := rfl

-- exercise: define succ
def succ :chℕ → chℕ := sorry -- KB can do this one
-- no notation

--unit tests -- KB can pass these
example : succ c0 = c1 := sorry
example : succ c2 = c3 := sorry

example (n : ℕ) : of_nat (nat.succ n) = succ (of_nat n) := sorry

--KB can't do this one. Is it unprovable? If so, move definition of to_nat much further down.
example (m : chℕ) : to_nat (succ m) = nat.succ (to_nat m) := sorry

-- exercise : define add
def add : chℕ → chℕ → chℕ := sorry -- KB can do this
instance : has_add chℕ := ⟨add⟩ -- now we have + notation

example : c2 + c1 = c3 := sorry
-- KB didn't do this one yet but feels it should be true.
example (m n : ℕ) : of_nat (m + n) = of_nat m + of_nat n := sorry

-- exercise : define mul
def mul : chℕ → chℕ → chℕ := sorry -- KB can do this
instance : has_mul chℕ := ⟨mul⟩ -- incantation to give us *

-- KB can do this one
example : c1 + c2 + c3 = c2 * c3 := sorry
-- KB didn't try this one
example (m n : ℕ) : of_nat (m * n) = of_nat m * of_nat n := sorry

-- exercise : define pow
def pow : chℕ → chℕ → chℕ := sorry -- KB can do this one
-- instance : has_pow chℕ := ⟨pow⟩ -- doesn't seem to work

-- KB can do this
example : pow c2 c3 + c1 = pow c3 c2 := sorry
-- KB didn't try this
example (m n : ℕ) : nat.pow m n = pow (of_nat m) (of_nat n) := sorry

-- exercise : define Ackermann
def ack : chℕ → chℕ → chℕ := sorry -- KB didn't try this one
-- Is it possible?

-- if it's possible, prove it agrees with usual ackermnann
-- example : ack m n = ack (of_nat m) (of_nat n) := sorry

-- question : Is this provable? KB couldn't do this one
theorem add_comm (m n : chℕ) : m + n = n + m := sorry

-- KB thinks this might be chℕ's free theorem
-- KB can't prove it
theorem free_chnat : ∀ (A B : Type), ∀ f : A → B,
∀ r : chℕ, ∀ a : A, r (A → B) (λ g,f) f a  = r (A → B) (λ g,g) f a
 := sorry

structure equiv' (α : Sort*) (β : Sort*) :=
(to_fun    : α → β)
(inv_fun   : β → α)
(left_inv  : ∀ (x : α), inv_fun (to_fun x) = x)
(right_inv : ∀ (y : β), to_fun (inv_fun y) = y)

-- is ℕ equiv to chℕ ?

theorem ij : ∀ n : ℕ, to_nat (of_nat n) = n := begin
intro n,
induction n with d Hd,refl,
unfold of_nat,
unfold to_nat,
unfold to_nat at Hd,
rw Hd,
end

-- KB can't do this one
theorem ji : ∀ c : chℕ, of_nat (to_nat c) = c := sorry
-- Can someone write down an uncomputable counterexample?

-- so KB can't do this either
definition ℕ_is_chℕ : equiv' ℕ chℕ := sorry

-- idle question
theorem is_it_true (X : Type) (f : X → X) (x : X) : f x = x := sorry

end chnat