/-
 -----------------------------------------------------------
  Correctness and semantic security of ElGamal public-key
  encryption scheme
 -----------------------------------------------------------
-/

import ddh
import pke
import tactics
import to_mathlib
import uniform

section elgamal

noncomputable theory

parameters (G : Type) [fintype G] [comm_group G] [decidable_eq G]
           (g : G) (g_gen_G : ∀ (x : G), x ∈ subgroup.gpowers g)
           (q : ℕ) [fact (0 < q)] (G_card_q : fintype.card G = q)
           (A_state : Type)

include g_gen_G G_card_q

parameters (A1 : G → pmf (G × G × A_state))
           (A2 : G → G → A_state → pmf (zmod 2))

def A2' : G × G → A_state → pmf (zmod 2) := λ (gg : G × G), A2 gg.1 gg.2

-- generates a public key `g^x.val`, and private key `x`
def keygen : pmf (G × (zmod q)) :=
do
  x ← uniform_zmod q,
  pure (g^x.val, x)

-- encrypt takes a pair (public key, message)
def encrypt (pk m : G) : pmf (G × G) :=
do
  y ← uniform_zmod q,
  pure (g^y.val, pk^y.val * m)

-- `x` is the secret key, `c.1` is g^y, the first part of the
-- cipher text returned from encrypt, and `c.2` is the
-- second value returned from encrypt
def decrypt (x : zmod q)(c : G × G) : G :=
	(c.2 / (c.1^x.val))

/-
  -----------------------------------------------------------
  Proof of correctness of ElGamal
  -----------------------------------------------------------
-/

lemma decrypt_eq_m (m : G) (x y: zmod q) : decrypt x ((g^y.val), ((g^x.val)^y.val * m)) = m :=
begin
  simp [decrypt],
  rw (pow_mul g x.val y.val).symm,
  rw (pow_mul g y.val x.val).symm,
  rw mul_comm y.val x.val,
  repeat {rw group.div_eq_mul_inv},
  conv_lhs {rw [mul_assoc, mul_comm m _, <- mul_assoc]},
  rw mul_inv_self _,
  exact one_mul m,
end

theorem elgamal_correctness : pke_correctness keygen encrypt decrypt :=
begin
  simp [pke_correctness],
  intro m,
  simp [enc_dec, keygen, encrypt, bind],
  bind_skip_const with x,
  simp [pure],
  bind_skip_const with y,
  simp_rw decrypt_eq_m,
  simp,
end

/-
  -----------------------------------------------------------
  Proof of semantic security of ElGamal
  -----------------------------------------------------------
-/

def D (gx gy gz : G) : pmf (zmod 2) :=
do
  m ← A1 gx,
  b ← uniform_2,
  mb ← pure (if b = 0 then m.1 else m.2.1),
  b' ← A2 gy (gz * mb) m.2.2,
  pure (1 + b + b')

/-
  The probability of the attacker (i.e. the composition of A1 and A2)
  winning the semantic security game (i.e. guessing the correct bit),
  w.r.t. ElGamal is equal to the probability of D winning the game DDH0.
-/
theorem SSG_DDH0 : SSG keygen encrypt A1 A2' =  DDH0 G g g_gen_G q G_card_q D :=
begin
  simp only [SSG, DDH0, bind, keygen, encrypt, D],
  simp_rw pmf.bind_bind (uniform_zmod q),
  bind_skip with x,
  simp [pure],
  simp_rw pmf.bind_comm (uniform_zmod q),
  bind_skip with m,
  bind_skip with b,
  bind_skip with y,
  simp only [A2'],
  rw pow_mul g x.val y.val,
end

def Game1 : pmf (zmod 2) :=
do
  x ← uniform_zmod q,
  y ← uniform_zmod q,
  m ← A1 (g^x.val),
  b ← uniform_2,
  ζ ← (do z ← uniform_zmod q, mb ← pure (if b = 0 then m.1 else m.2.1), pure (g^z.val * mb)),
  b' ← A2 (g^y.val) ζ m.2.2,
  pure (1 + b + b')

def Game2 : pmf (zmod 2) :=
do
  x ← uniform_zmod q,
  y ← uniform_zmod q,
  m ← A1 (g^x.val),
  b ← uniform_2,
  ζ ← (do z ← uniform_zmod q, pure (g^z.val)),
  b' ← A2 (g^y.val) ζ m.2.2,
  pure (1 + b + b')

/-
  The probability of the attacker (i.e. the composition of A1 and A2)
  winning Game1 (i.e. guessing the correct bit) is equal to the
  probability of D winning the game DDH1.
-/
theorem Game1_DDH1 : Game1 = DDH1 G g g_gen_G q G_card_q D :=
begin
  simp only [DDH1, Game1, bind, D],
  bind_skip with x,
  bind_skip with y,
  simp_rw pmf.bind_bind (A1 _),
  conv_rhs {rw pmf.bind_comm (uniform_zmod q)},
  bind_skip with m,
  simp_rw pmf.bind_bind (uniform_zmod q),
  conv_lhs {rw pmf.bind_comm (uniform_2)},
  bind_skip with z,
  conv_rhs {rw pmf.bind_bind (uniform_2)},
  bind_skip with b,
  simp_rw pmf.bind_bind,
  bind_skip with mb,
  simp [pure],
end

lemma exp_bij :
  function.bijective (λ (z : zmod q), g ^ z.val) :=
begin
  apply (fintype.bijective_iff_surjective_and_card _).mpr,
  split,

  { -- (λ (z : zmod q), g ^ z.val) surjective
    intro gz,
    have hz := subgroup.mem_gpowers_iff.mp (g_gen_G gz),
    cases hz with z hz,
    cases z,

    { -- Case : z = z' for (z : ℕ)
      let zq := z % q,
      use zq,
      have h1 : (λ (z : zmod q), g ^ z.val) ↑zq = g ^ (zq : zmod q).val := rfl,
      rw h1,
      rw zmod.val_cast_of_lt,
      {
        have goal : gz = g ^ zq :=
        calc
           gz = g ^ int.of_nat z : hz.symm
          ... = g ^ z  : by simp
          ... = g ^ (z % q + q * (z / q)) : by rw nat.mod_add_div z q
          ... = g ^ (z % q) * g ^ (q * (z / q)) : by rw pow_add
          ... = g ^ (z % q) * (g ^ q) ^ (z / q) : by rw pow_mul
          ... = g ^ (z % q) * (g ^ fintype.card G) ^ (z / q) : by rw <- G_card_q
          ... = g ^ (z % q) : by simp [pow_card_eq_one]
          ... = g ^ zq : rfl,
        exact goal.symm,
      },
      exact nat.mod_lt z _inst_4.out,
    },

    { -- Case : z = - (1 + z') for (z' : ℕ)
      let zq := (q - (z + 1) % q ) % q,
      use zq,
      have h1 : (λ (z : zmod q), g ^ z.val) ↑zq = g ^ (zq : zmod q).val := rfl,
      rw h1,
      rw zmod.val_cast_of_lt,
      {
        have h1 : (z + 1) % q ≤ fintype.card G :=
        begin
          rw G_card_q,
          apply le_of_lt,
          exact nat.mod_lt _ _inst_4.out,
        end,

        have goal : gz = g ^ zq :=
        calc
           gz = g ^ int.neg_succ_of_nat z : hz.symm
          ... = (g ^ z.succ)⁻¹  : by rw gpow_neg_succ_of_nat
          ... = (g ^ (z + 1))⁻¹ : rfl
          ... = (g ^ ((z + 1) % fintype.card G))⁻¹ : by rw pow_eq_mod_card G g (z + 1)
          ... = (g ^ ((z + 1) % q))⁻¹ : by rw G_card_q
          ... = g ^ (fintype.card G - (z + 1) % q) : inv_pow_eq_card_sub_pow G g _ h1
          ... = g ^ (q - ((z + 1) % q)) : by rw G_card_q
          ... = g ^ ((q - (z + 1) % q) % q
                  + q * ((q - (z + 1) % q) / q)) : by rw nat.mod_add_div
          ... = g ^ ((q - (z + 1) % q) % q)
                  * g ^ (q * ((q - (z + 1) % q) / q)) : by rw pow_add
          ... = g ^ ((q - (z + 1) % q) % q)
                  * (g ^ q) ^ ((q - (z + 1) % q) / q) : by rw pow_mul
          ... = g ^ ((q - (z + 1) % q) % q)
                  * (g ^ fintype.card G) ^ ((q - (z + 1) % q) / q) : by rw <- G_card_q
          ... = g ^ ((q - (z + 1) % q) % q) : by simp [pow_card_eq_one]
          ... = g ^ zq : rfl,
        exact goal.symm,
      },

      exact nat.mod_lt _ _inst_4.out,
    },
  },

  { -- fintype.card (zmod q) = fintype.card G
    rw G_card_q,
    exact zmod.card q,
  },
end

lemma exp_mb_bij (mb : G) : function.bijective (λ (z : zmod q), g ^ z.val * mb) :=
begin
  have h : (λ (z : zmod q), g ^ z.val * mb) =
    ((λ (m : G), (m * mb)) ∘ (λ (z : zmod q), g ^ z.val)) := by simp,
  rw h,
  apply function.bijective.comp,

  { -- (λ (m : G), (m * mb)) bijective
    refine fintype.injective_iff_bijective.mp _,
    intros x a hxa,
    simp at hxa,
    exact hxa,
  },

  { -- (λ (z : zmod q), g ^ z.val) bijective
    exact exp_bij,
  }
end

lemma G1_G2_lemma1 (x : G) (exp : zmod q → G) (exp_bij : function.bijective exp) :
  ∑' (z : zmod q), ite (x = exp z) (1 : nnreal) 0 = 1 :=
begin
  have inv :=  function.bijective_iff_has_inverse.mp exp_bij,
  cases inv with exp_inv,
  have bij_eq : ∀ (z : zmod q), (x = exp z) = (z = exp_inv x) :=
  begin
    intro z,
    simp,
    split,

    { -- (x = exp z) → (z = exp_inv x)
      intro h,
      have h1 : exp_inv x = exp_inv (exp z) := congr_arg exp_inv h,
      rw inv_h.left z at h1,
      exact h1.symm,
    },

    { -- (z = exp_inv x) → (x = exp z)
      intro h,
      have h2 : exp z = exp (exp_inv x) := congr_arg exp h,
      rw inv_h.right x at h2,
      exact h2.symm,
    },
  end,
  simp_rw bij_eq,
  simp,
end

lemma G1_G2_lemma2 (mb : G) :
  (uniform_zmod q).bind (λ (z : zmod q), pure (g^z.val * mb)) =
  (uniform_zmod q).bind (λ (z : zmod q), pure (g^z.val))  :=
begin
  simp [pmf.bind],
  simp_rw uniform_zmod_prob,
  ext,
  simp only [pure, pmf.pure, coe_fn, has_coe_to_fun.coe, nnreal.tsum_mul_left],
  norm_cast,
  simp,
  apply or.intro_left,
  repeat {rw G1_G2_lemma1 x},
  exact exp_bij,
  exact exp_mb_bij mb,
end

lemma G1_G2_lemma3 (m : pmf G) :
  m.bind (λ (mb : G), (uniform_zmod q).bind (λ (z : zmod q), pure (g^z.val * mb))) =
  (uniform_zmod q).bind (λ (z : zmod q), pure (g^z.val)) :=
begin
  simp_rw G1_G2_lemma2 _,
  bind_skip_const with m,
  congr,
end

/-
  The probability of the attacker (i.e. the composition of A1 and A2)
  winning Game1 (i.e. guessing the correct bit) is equal to the
  probability of the attacker winning Game2.
-/
theorem Game1_Game2 : Game1 = Game2 :=
begin
  simp only [Game1, Game2],
  bind_skip with x,
  bind_skip with y,
  bind_skip with m,
  bind_skip with b,
  simp [bind, -pmf.bind_pure, -pmf.bind_bind],
  simp_rw pmf.bind_comm (uniform_zmod q),
  rw G1_G2_lemma3,
end

lemma G2_uniform_lemma (b' : zmod 2) :
uniform_2.bind (λ (b : zmod 2), pure (1 + b + b')) = uniform_2 :=
begin
  fin_cases b' with [0,1],

  { -- b = 0
    ring_nf,
    ext,
    simp [uniform_2],
    rw uniform_zmod_prob a,
    simp_rw uniform_zmod_prob,
    simp [nnreal.tsum_mul_left],
    have zmod_eq_comm : ∀ (x : zmod 2), (a = 1 + x) = (x = 1 + a) :=
    begin
      intro x,
      fin_cases a with [0,1],

      { -- a = 0
        fin_cases x with [0,1],
        simp,
        ring_nf,
      },

      { -- a = 1
        fin_cases x with [0,1],
        simp [if_pos],
        ring_nf,
      },
    end,
    have h : ∑' (x : zmod 2), (pure (1 + x) : pmf (zmod 2)) a = 1 :=
    begin
      simp [pure, pmf.pure, coe_fn, has_coe_to_fun.coe],
      simp_rw zmod_eq_comm,
      simp,
    end,
    rw h,
    simp,
  },

  { -- b = 1
    ring_nf,
    have h :
      uniform_2.bind (λ (b : zmod 2), pure (b + 0)) = uniform_2 := by simp [pure],
    exact h,
  },
end

/-
  The probability of the attacker (i.e. the composition of A1 and A2)
  winning Game2 (i.e. guessing the correct bit) is equal to a coin flip.
-/
theorem Game2_uniform : Game2 = uniform_2 :=
begin
  simp [Game2, bind],
  bind_skip_const with x,
  bind_skip_const with m,
  bind_skip_const with y,
  rw pmf.bind_comm uniform_2,
  simp_rw pmf.bind_comm uniform_2,
  bind_skip_const with z,
  bind_skip_const with ζ,
  bind_skip_const with b',
  exact G2_uniform_lemma b',
end

parameters (ε : nnreal)

/-
  The advantage of the attacker (i.e. the composition of A1 and A2) in
  the semantic security game `ε` is exactly equal to the advantage of D in
  the Diffie-Hellman game. Therefore, assumining the decisional Diffie-Hellman
  assumption holds for the group `G`, we conclude `ε` is negligble, and
  therefore ElGamal is, by definition, semantically secure.
-/
theorem elgamal_semantic_security (DDH_G : DDH G g g_gen_G q G_card_q D ε) :
  pke_semantic_security keygen encrypt A1 A2' ε :=
begin
  simp only [pke_semantic_security],
  rw SSG_DDH0,
  have h : (((uniform_2) 1) : ℝ) = 1/2 :=
  begin
    simp only [uniform_2],
    rw uniform_zmod_prob 1,
    norm_cast,
  end,
  rw <- h,
  rw <- Game2_uniform,
  rw <- Game1_Game2,
  rw Game1_DDH1,
  exact DDH_G,
end

end elgamal


/-
This file is intended for Lean beginners. The goal is to demonstrate what it feels like to prove
things using Lean and mathlib. Complicated definitions and theory building are not covered.
Everything is covered again more slowly and with exercises in the next files.
-/

-- We want real numbers and their basic properties
import data.real.basic

-- We want to be able to use Lean's built-in "help" functionality
import tactic.suggest

-- We want to be able to define functions using the law of excluded middle
noncomputable theory
open_locale classical

/-
Our first goal is to define the set of upper bounds of a set of real numbers.
This is already defined in mathlib (in a more general context), but we repeat
it for the sake of exposition. Right-click "upper_bounds" below to get offered
to jump to mathlib's version
-/
#check upper_bounds

/-- The set of upper bounds of a set of real numbers ℝ -/
def up_bounds (A : set ℝ) := { x : ℝ | ∀ a ∈ A, a ≤ x}

/-- Predicate `is_maximum a A` means `a` is a maximum of `A` -/
def is_maximum (a : ℝ) (A : set ℝ) := a ∈ A ∧ a ∈ up_bounds A

/-
In the above definition, the symbol `∧` means "and". We also see the most
visible difference between set theoretic foundations and type theoretic ones
(used by almost all proof assistants). In set theory, everything is a set, and the
only relation you get from foundations are `=` and `∈`. In type theory, there is
a meta-theoretic relation of "typing": `a : ℝ` reads "`a` is a real number" or,
more precisely, "the type of `a` is `ℝ`". Here "meta-theoretic" means this is not a
statement you can prove or disprove inside the theory, it's a fact that is true or
not. Here we impose this fact, in other circumstances, it would be checked by the
Lean kernel.
By contrast, `a ∈ A` is a statement inside the theory. Here it's part of the
definition, in other circumstances it could be something proven inside Lean.
-/

/- For illustrative purposes, we now define an infix version of the above predicate.
It will allow us to write `a is_a_max_of A`, which is closer to a sentence.
-/
infix ` is_a_max_of `:55 := is_maximum

/-
Let's prove something now! A set of real numbers has at most one maximum. Here
everything left of the final `:` is introducing the objects and assumption. The equality
`x = y` right of the colon is the conclusion.
-/
lemma unique_max (A : set ℝ) (x y : ℝ) (hx : x is_a_max_of A) (hy : y is_a_max_of A) : x = y :=
begin
  -- We first break our assumptions in their two constituent pieces.
  -- We are free to choose the name following `with`
  cases hx with x_in x_up,
  cases hy with y_in y_up,
  -- Assumption `x_up` means x isn't less than elements of A, let's apply this to y
  specialize x_up y,
  -- Assumption `x_up` now needs the information that `y` is indeed in `A`.
  specialize x_up y_in,
  -- Let's do this quicker with roles swapped
  specialize y_up x x_in,
  -- We explained to Lean the idea of this proof.
  -- Now we know `x ≤ y` and `y ≤ x`, and Lean shouldn't need more help.
  -- `linarith` proves equalities and inequalities that follow linearly from
  -- the assumption we have.
  linarith,
end

/-
The above proof is too long, even if you remove comments. We don't really need the
unpacking steps at the beginning; we can access both parts of the assumption
`hx : x is_a_max_of A` using shortcuts `hx.1` and `hx.2`. We can also improve
readability without assistance from the tactic state display, clearly announcing
intermediate goals using `have`. This way we get to the following version of the
same proof.
-/

example (A : set ℝ) (x y : ℝ) (hx : x is_a_max_of A) (hy : y is_a_max_of A) : x = y :=
begin
  have : x ≤ y, from hy.2 x hx.1,
  have : y ≤ x, from hx.2 y hy.1,
  linarith,
end

/-
Notice how mathematics based on type theory treats the assumption
`∀ a ∈ A, a ≤ y` as a function turning an element `a` of `A` into the statement
`a ≤ y`. More precisely, this assumption is the abbreviation of
`∀ a : ℝ, a ∈ A → a ≤ y`. The expression `hy.2 x` appearing in the above proof
is then the statement `x ∈ A → x ≤ y`, which itself is a function turning a
statement `x ∈ A` into `x ≤ y` so that the full expression `hy.2 x hx.1` is
indeed a proof of `x ≤ y`.

One could argue a three-line-long proof of this lemma is still two lines too long.
This is debatable, but mathlib's style is to write very short proofs for trivial
lemmas. Those proofs are not easy to read but they are meant to indicate that the
proof is probably not worth reading.

In order to reach this stage, we need to know what `linarith` did for us. It invoked
the lemma `le_antisymm` which says: `x ≤ y → y ≤ x → x = y`. This arrow, which
is used both for function and implication, is right associative. So the statement is
`x ≤ y → (y ≤ x → x = y)` which reads: I will send a proof `p` of `x ≤ y` to a function
sending a proof `q'` of `y ≤ x` to a proof of `x = y`. Hence `le_antisymm p q'` is a
proof of `x = y`.

Using this we can get our one-line proof:
-/

example (A : set ℝ) (x y : ℝ) (hx : x is_a_max_of A) (hy : y is_a_max_of A) : x = y :=
le_antisymm (hy.2 x hx.1) (hx.2 y hy.1)

/-
Such a proof is called a proof term (or a "term mode" proof). Notice it has no `begin`
and `end`. It is directly the kind of low level proof that the Lean kernel is
consuming. Commands like `cases`, `specialize` or `linarith` are called tactics, they
help users constructing proof terms that could be very tedious to write directly.
The most efficient proof style combines tactics with proof terms like our previous
`have : x ≤ y, from hy.2 x hx.1` where `hy.2 x hx.1` is a proof term embeded inside
a tactic mode proof.

In the remaining of this file, we'll be characterizing infima of sets of real numbers
in term of sequences.
-/

/-- The set of lower bounds of a set of real numbers ℝ -/
def low_bounds (A : set ℝ) := { x : ℝ | ∀ a ∈ A, x ≤ a}

/-
We now define `a` is an infimum of `A`. Again there is already a more general version
in mathlib.
-/
def is_inf (x : ℝ) (A : set ℝ) := x is_a_max_of (low_bounds A)
infix ` is_an_inf_of `:55 := is_inf

/-
We need to prove that any number which is greater than the infimum of A is greater
than some element of A.
-/

lemma inf_lt {A : set ℝ} {x : ℝ} (hx : x is_an_inf_of A) :
  ∀ y, x < y → ∃ a ∈ A, a < y :=
begin
  -- Let `y` be any real number.
  intro y,
  -- Let's prove the contrapositive
  contrapose,
  -- The symbol `¬` means negation. Let's ask Lean to rewrite the goal without negation,
  -- pushing negation through quantifiers and inequalities
  push_neg,
  -- Let's assume the premise, calling the assumption `h`
  intro h,
  -- `h` is exactly saying `y` is a lower bound of `A` so the second part of
  -- the infimum assumption `hx` applied to `y` and `h` is exactly what we want.
  exact hx.2 y h
end

/-
In the above proof, the sequence `contrapose, push_neg` is so common that it can be
abbreviated to `contrapose!`. With these commands, we enter the gray zone between
proof checking and proof finding. Practical computer proof checking crucially needs
the computer to handle tedious proof steps. In the next proof, we'll start using
`linarith` a bit more seriously, going one step further into automation.

Our next real goal is to prove inequalities for limits of sequences. We extract the
following lemma: if `y ≤ x + ε` for all positive `ε` then `y ≤ x`.
-/

lemma le_of_le_add_eps {x y : ℝ} : (∀ ε > 0, y ≤ x + ε) →  y ≤ x :=
begin
  -- Let's prove the contrapositive, asking Lean to push negations right away.
  contrapose!,
  -- Assume `h : x < y`.
  intro h,
  -- We need to find `ε` such that `ε` is positive and `x + ε < y`.
  -- Let's use `(y-x)/2`
  use ((y-x)/2),
  -- we now have two properties to prove. Let's do both in turn, using `linarith`
  split,
  linarith,
  linarith,
end

/-
Note how `linarith` was used for both sub-goals at the end of the above proof.
We could have shortened that using the semi-colon combinator instead of comma,
writing `split ; linarith`.

Next we will study a compressed version of that proof:
-/

example {x y : ℝ} : (∀ ε > 0, y ≤ x + ε) →  y ≤ x :=
begin
  contrapose!,
  exact assume h, ⟨(y-x)/2, by linarith, by linarith⟩,
end

/-
The angle brackets `⟨` and `⟩` introduce compound data or proofs. A proof
of a `∃ z, P z` statemement is composed of a witness `z₀` and a proof `h` of
`P z₀`. The compound is denoted by `⟨z₀, h⟩`. In the example above, the predicate is
itself compound, it is a conjunction `P z ∧ Q z`. So the proof term should read
`⟨z₀, ⟨h₁, h₂⟩⟩` where `h₁` (resp. `h₂`) is a proof of `P z₀` (resp. `Q z₀`).
But these so-called "anonymous constructor" brackets are right-associative, so we can
get rid of the nested brackets.

The keyword `by` introduces tactic mode inside term mode, it is a shorter version
of the `begin`/`end` pair, which is more convenient for single tactic blocks.
In this example, `begin` enters tactic mode, `exact` leaves it, `by` re-enters it.

Going all the way to a proof term would make the proof much longer, because we
crucially use automation with `contrapose!` and `linarith`. We can still get a one-line
proof using curly braces to gather several tactic invocations, and the `by` abbreviation
instead of `begin`/`end`:
-/

example {x y : ℝ} : (∀ ε > 0, y ≤ x + ε) →  y ≤ x :=
by { contrapose!, exact assume h, ⟨(y-x)/2, by linarith, by linarith⟩ }

/-
One could argue that the above proof is a bit too terse, and we are relying too much
on linarith. Let's have more `linarith` calls for smaller steps. For the sake
of (tiny) variation, we will also assume the premise and argue by contradiction
instead of contraposing.
-/

example {x y : ℝ} : (∀ ε > 0, y ≤ x + ε) →  y ≤ x :=
begin
  intro h,
  -- Assume the conclusion is false, and call this assumption H.
  by_contradiction H,
  push_neg at H,
  -- Now let's compute.
  have key := calc
  -- Each line must end with a colon followed by a proof term
  -- We want to specialize our assumption `h` to `ε = (y-x)/2` but this is long to
  -- type, so let's put a hole `_` that Lean will fill in by comparing the
  -- statement we want to prove and our proof term with a hole. As usual,
  -- positivity of `(y-x)/2` is proved by `linarith`
    y   ≤ x + (y-x)/2 : h _ (by linarith)
    ... = x/2 + y/2   : by ring
    ... < y           : by linarith,
  -- our key now says `y < y` (notice how the sequence `≤`, `=`, `<` was correctly
  -- merged into a `<`). Let `linarith` find the desired contradiction now.
  linarith,
  -- alternatively, we could have provided the proof term
  -- `exact lt_irrefl y key`
end

/-
Now we are ready for some analysis. Let's define convergence of sequences of real numbers
(of course there is a much more general definition in mathlib).
-/

/-- The sequence `u` tends to `l` -/
def limit (u : ℕ → ℝ) (l : ℝ) := ∀ ε > 0, ∃ N, ∀ n ≥ N, |u n - l| ≤ ε

/-
In the above definition, `u n` denotes the n-th term of the sequence. We can
add parentheses to get `u(n)` but we try to avoid parentheses because they pile up
very quickly
-/

-- If y ≤ u n for all n and u n goes to x then y ≤ x
lemma le_lim {x y : ℝ} {u : ℕ → ℝ} (hu : limit u x) (ineq : ∀ n, y ≤ u n) : y ≤ x :=
begin
  -- Let's apply our previous lemma
  apply le_of_le_add_eps,
  -- We need to prove y ≤ x + ε for all positive ε.
  -- Let ε be any positive real
  intros ε ε_pos,
  -- we now specialize our limit assumption to this `ε`, and immediately
  -- fix a `N` as promised by the definition.
  cases hu ε ε_pos with N HN,
  -- Now we only need to compute until reaching the conclusion
  calc
  y ≤ u N             : ineq N
  ... = x + (u N - x) : by linarith
    -- We'll need `add_le_add` which says `a ≤ b` and `c ≤ d` implies `a + c ≤ b + d`
    -- We need a lemma saying `z ≤ |z|`. Because we don't know the name of this lemma,
    -- let's use `library_search`. Because searching through the library is slow,
    -- Lean will write what it found in the Lean message window when cursor is on
    -- that line, so that we can replace it by the lemma. We see `le_abs_self`, which
    -- says `a ≤ |a|`, exactly what we're looking for.
  ... ≤ x + |u N - x| : add_le_add (by linarith) (by library_search)
  ... ≤ x + ε         : add_le_add (by linarith) (HN N (by linarith)),
end

/-
The next lemma has been extracted from the main proof in order to discuss numbers.
In ordinary maths, we know that ℕ is *not* contained in `ℝ`, whatever the
construction of real numbers that we use. For instance a natural number is not
an equivalence class of Cauchy sequences. But it's very easy to
pretend otherwise. Formal maths requires slightly more care. In the statement below,
the "type ascription" `(n + 1 : ℝ)` forces Lean to convert the natural number
`n+1` into a real number.  The "inclusion" map will be displayed in tactic state
as `↑`. There are various lemmas asserting this map is compatible with addition and
monotone, but we don't want to bother writing their names. The `norm_cast`
tactic is designed to wisely apply those lemmas for us.
-/

lemma inv_succ_pos : ∀ n : ℕ, 1/(n+1 : ℝ) > 0 :=
begin
  -- Let `n` be any integer
  intro n,
  -- Since we don't know the name of the relevant lemma, asserting that the inverse of
  -- a positive number is positive, let's state that is suffices
  -- to prove that `n+1`, seen as a real number, is positive, and ask `library_search`
  suffices : (n + 1 : ℝ) > 0,
  { library_search },
  -- Now we want to reduce to a statement about natural numbers, not real numbers
  -- coming from natural numbers.
  norm_cast,
  -- and then get the usual help from `linarith`
  linarith,
end

/-
That was a pretty long proof for an obvious fact. And stating it as a lemma feels
stupid, so let's find a way to write it on one line in case we want to include it
in some other proof without stating a lemma. First the `library_search` call
above displays the name of the relevant lemma: `one_div_pos`. We can also
replace the `linarith` call on the last line by `library_search` to learn the name
of the lemma `nat.succ_pos` asserting that the successor of a natural number is
positive. There is also a variant on `norm_cast` that combines it with `exact`.
The term mode analogue of `intro` is `λ`. We get down to:
-/

example : ∀ n : ℕ, 1/(n+1 : ℝ) > 0 :=
λ n, one_div_pos.mpr (by exact_mod_cast nat.succ_pos n)

/-
The next proof uses mostly known things, so we will commment only new aspects.
-/

lemma limit_inv_succ : ∀ ε > 0, ∃ N : ℕ, ∀ n ≥ N, 1/(n + 1 : ℝ) ≤ ε :=
begin
  intros ε ε_pos,
  suffices : ∃ N : ℕ, 1/ε ≤ N,
  { -- Because we didn't provide a name for the above statement, Lean called it `this`.
    -- Let's fix an `N` that works.
    cases this with N HN,
    use N,
    intros n Hn,
    -- Now we want to rewrite the goal using lemmas
    -- `div_le_iff' : 0 < b →  (a / b ≤ c ↔ a ≤ b * c)`
    -- `div_le_iff : 0 < b →  (a / b ≤ c ↔ a ≤ c * b)`
    -- the second one will be rewritten from right to left, as indicated by `←`.
    -- Lean will create a side goal for the required positivity assumption that
    -- we don't provide for `div_le_iff'`.
    rw [div_le_iff', ← div_le_iff ε_pos],
    -- We want to replace assumption `Hn` by its real counter-part so that
    -- linarith can find what it needs.
    replace Hn : (N : ℝ) ≤ n, exact_mod_cast Hn,
    linarith,
    -- we are still left with the positivity assumption, but already discussed
    -- how to prove it in the preceding lemma
    exact_mod_cast nat.succ_pos n },
  -- Now we need to prove that sufficient statement.
  -- We want to use that `ℝ` is archimedean. So we start typing
  -- `exact archimedean_` and hit Ctrl-space to see what completion Lean proposes
  -- the lemma `archimedean_iff_nat_le` sounds promising. We select the left to
  -- right implication using `.1`. This a generic lemma for fields equiped with
  -- a linear (ie total) order. We need to provide a proof that `ℝ` is indeed
  -- archimedean. This is done using the `apply_instance` tactic that will be
  -- covered elsewhere.
  exact archimedean_iff_nat_le.1 (by apply_instance) (1/ε),
end

/-
We can now put all pieces together, with almost no new things to explain.
-/

lemma inf_seq (A : set ℝ) (x : ℝ) :
  (x is_an_inf_of A) ↔ (x ∈ low_bounds A ∧ ∃ u : ℕ → ℝ, limit u x ∧ ∀ n, u n ∈ A ) :=
begin
  split,
  { intro h,
    split,
    { exact h.1 },
    -- On the next line, we don't need to tell Lean to treat `n+1` as a real number because
    -- we add `x` to it, so Lean knows there is only one way to make sense of this expression.
    have key : ∀ n : ℕ, ∃ a ∈ A, a < x + 1/(n+1),
    { intro n,
      -- we can use the lemma we proved above
      apply inf_lt h,
      -- and another one we proved!
      have : 0 < 1/(n+1 : ℝ), from inv_succ_pos n,
      linarith },
    -- Now we need to use axiom of (countable) choice
    choose u hu using key,
    use u,
    split,
    { intros ε ε_pos,
      -- again we use a lemma we proved, specializing it to our fixed `ε`, and fixing a `N`
      cases limit_inv_succ ε ε_pos with N H,
      use N,
      intros n hn,
      have : x ≤ u n, from h.1 _ (hu n).1,
      have := calc
        u n < x + 1/(n + 1) : (hu n).2
        ... ≤ x + ε         : add_le_add (le_refl x) (H n hn),
      rw abs_of_nonneg ; linarith },
    { intro n,
      exact (hu n).1 } },
  { intro h,
    -- Assumption `h` is made of nested compound statements. We can use the
    -- recursive version of `cases` to unpack it in one go.
    rcases h with ⟨x_min, u, lim, huA⟩,
    split,
    exact x_min,
    intros y y_mino,
    apply le_lim lim,
    intro n,
    exact y_mino (u n) (huA n) },
end


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

! This file was ported from Lean 3 source module linear_algebra.eigenspace
! leanprover-community/mathlib commit 2705404e701abc6b3127da906f40bae062a169c9
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Algebra.Algebra.Spectrum
import Mathbin.Order.Hom.Basic
import Mathbin.LinearAlgebra.FreeModule.Finite.Basic
import Mathbin.LinearAlgebra.GeneralLinearGroup

/-!
# Eigenvectors and eigenvalues

This file defines eigenspaces, eigenvalues, and eigenvalues, as well as their generalized
counterparts. We follow Axler's approach [axler2015] because it allows us to derive many properties
without choosing a basis and without using matrices.

An eigenspace of a linear map `f` for a scalar `μ` is the kernel of the map `(f - μ • id)`. The
nonzero elements of an eigenspace are eigenvectors `x`. They have the property `f x = μ • x`. If
there are eigenvectors for a scalar `μ`, the scalar `μ` is called an eigenvalue.

There is no consensus in the literature whether `0` is an eigenvector. Our definition of
`has_eigenvector` permits only nonzero vectors. For an eigenvector `x` that may also be `0`, we
write `x ∈ f.eigenspace μ`.

A generalized eigenspace of a linear map `f` for a natural number `k` and a scalar `μ` is the kernel
of the map `(f - μ • id) ^ k`. The nonzero elements of a generalized eigenspace are generalized
eigenvectors `x`. If there are generalized eigenvectors for a natural number `k` and a scalar `μ`,
the scalar `μ` is called a generalized eigenvalue.

## References

* [Sheldon Axler, *Linear Algebra Done Right*][axler2015]
* https://en.wikipedia.org/wiki/Eigenvalues_and_eigenvectors

## Tags

eigenspace, eigenvector, eigenvalue, eigen
-/

universe u v w

namespace Module

namespace End

open Module PrincipalIdealRing Polynomial FiniteDimensional

open Polynomial

variable {K R : Type v} {V M : Type w} [CommRing R] [AddCommGroup M] [Module R M] [Field K]
  [AddCommGroup V] [Module K V]

/-- The submodule `eigenspace f μ` for a linear map `f` and a scalar `μ` consists of all vectors `x`
    such that `f x = μ • x`. (Def 5.36 of [axler2015])-/
def eigenspace (f : End R M) (μ : R) : Submodule R M :=
  (f - algebraMap R (End R M) μ).ker
#align module.End.eigenspace Module.End.eigenspace

@[simp]
theorem eigenspace_zero (f : End R M) : f.eigenspace 0 = f.ker := by simp [eigenspace]
#align module.End.eigenspace_zero Module.End.eigenspace_zero

/-- A nonzero element of an eigenspace is an eigenvector. (Def 5.7 of [axler2015]) -/
def HasEigenvector (f : End R M) (μ : R) (x : M) : Prop :=
  x ∈ eigenspace f μ ∧ x ≠ 0
#align module.End.has_eigenvector Module.End.HasEigenvector

/-- A scalar `μ` is an eigenvalue for a linear map `f` if there are nonzero vectors `x`
    such that `f x = μ • x`. (Def 5.5 of [axler2015]) -/
def HasEigenvalue (f : End R M) (a : R) : Prop :=
  eigenspace f a ≠ ⊥
#align module.End.has_eigenvalue Module.End.HasEigenvalue

/-- The eigenvalues of the endomorphism `f`, as a subtype of `R`. -/
def Eigenvalues (f : End R M) : Type _ :=
  { μ : R // f.HasEigenvalue μ }
#align module.End.eigenvalues Module.End.Eigenvalues

instance (f : End R M) : Coe f.Eigenvalues R :=
  coeSubtype

theorem hasEigenvalue_of_hasEigenvector {f : End R M} {μ : R} {x : M} (h : HasEigenvector f μ x) :
    HasEigenvalue f μ := by
  rw [has_eigenvalue, Submodule.ne_bot_iff]
  use x; exact h
#align module.End.has_eigenvalue_of_has_eigenvector Module.End.hasEigenvalue_of_hasEigenvector

theorem mem_eigenspace_iff {f : End R M} {μ : R} {x : M} : x ∈ eigenspace f μ ↔ f x = μ • x := by
  rw [eigenspace, LinearMap.mem_ker, LinearMap.sub_apply, algebra_map_End_apply, sub_eq_zero]
#align module.End.mem_eigenspace_iff Module.End.mem_eigenspace_iff

theorem HasEigenvector.apply_eq_smul {f : End R M} {μ : R} {x : M} (hx : f.HasEigenvector μ x) :
    f x = μ • x :=
  mem_eigenspace_iff.mp hx.1
#align module.End.has_eigenvector.apply_eq_smul Module.End.HasEigenvector.apply_eq_smul

theorem HasEigenvalue.exists_hasEigenvector {f : End R M} {μ : R} (hμ : f.HasEigenvalue μ) :
    ∃ v, f.HasEigenvector μ v :=
  Submodule.exists_mem_ne_zero_of_ne_bot hμ
#align module.End.has_eigenvalue.exists_has_eigenvector Module.End.HasEigenvalue.exists_hasEigenvector

theorem mem_spectrum_of_hasEigenvalue {f : End R M} {μ : R} (hμ : HasEigenvalue f μ) :
    μ ∈ spectrum R f := by
  refine' spectrum.mem_iff.mpr fun h_unit => _
  set f' := LinearMap.GeneralLinearGroup.toLinearEquiv h_unit.unit
  rcases hμ.exists_has_eigenvector with ⟨v, hv⟩
  refine' hv.2 ((linear_map.ker_eq_bot'.mp f'.ker) v (_ : μ • v - f v = 0))
  rw [hv.apply_eq_smul, sub_self]
#align module.End.mem_spectrum_of_has_eigenvalue Module.End.mem_spectrum_of_hasEigenvalue

theorem hasEigenvalue_iff_mem_spectrum [FiniteDimensional K V] {f : End K V} {μ : K} :
    f.HasEigenvalue μ ↔ μ ∈ spectrum K f :=
  Iff.intro mem_spectrum_of_hasEigenvalue fun h => by
    rwa [spectrum.mem_iff, IsUnit.sub_iff, LinearMap.isUnit_iff_ker_eq_bot] at h
#align module.End.has_eigenvalue_iff_mem_spectrum Module.End.hasEigenvalue_iff_mem_spectrum

theorem eigenspace_div (f : End K V) (a b : K) (hb : b ≠ 0) :
    eigenspace f (a / b) = (b • f - algebraMap K (End K V) a).ker :=
  calc
    eigenspace f (a / b) = eigenspace f (b⁻¹ * a) := by rw [div_eq_mul_inv, mul_comm]
    _ = (f - (b⁻¹ * a) • LinearMap.id).ker := rfl
    _ = (f - b⁻¹ • a • LinearMap.id).ker := by rw [smul_smul]
    _ = (f - b⁻¹ • algebraMap K (End K V) a).ker := rfl
    _ = (b • (f - b⁻¹ • algebraMap K (End K V) a)).ker := by rw [LinearMap.ker_smul _ b hb]
    _ = (b • f - algebraMap K (End K V) a).ker := by rw [smul_sub, smul_inv_smul₀ hb]

#align module.End.eigenspace_div Module.End.eigenspace_div

theorem eigenspace_aeval_polynomial_degree_1 (f : End K V) (q : K[X]) (hq : degree q = 1) :
    eigenspace f (-q.coeff 0 / q.leadingCoeff) = (aeval f q).ker :=
  calc
    eigenspace f (-q.coeff 0 / q.leadingCoeff) =
        (q.leadingCoeff • f - algebraMap K (End K V) (-q.coeff 0)).ker :=
      by
      rw [eigenspace_div]
      intro h
      rw [leading_coeff_eq_zero_iff_deg_eq_bot.1 h] at hq
      cases hq
    _ = (aeval f (C q.leadingCoeff * X + C (q.coeff 0))).ker :=
      by
      rw [C_mul', aeval_def]
      simp [algebraMap, Algebra.toRingHom]
    _ = (aeval f q).ker := by rwa [← eq_X_add_C_of_degree_eq_one]

#align module.End.eigenspace_aeval_polynomial_degree_1 Module.End.eigenspace_aeval_polynomial_degree_1

theorem ker_aeval_ring_hom'_unit_polynomial (f : End K V) (c : K[X]ˣ) :
    (aeval f (c : K[X])).ker = ⊥ :=
  by
  rw [Polynomial.eq_C_of_degree_eq_zero (degree_coe_units c)]
  simp only [aeval_def, eval₂_C]
  apply ker_algebra_map_End
  apply coeff_coe_units_zero_ne_zero c
#align module.End.ker_aeval_ring_hom'_unit_polynomial Module.End.ker_aeval_ring_hom'_unit_polynomial

theorem aeval_apply_of_hasEigenvector {f : End K V} {p : K[X]} {μ : K} {x : V}
    (h : f.HasEigenvector μ x) : aeval f p x = p.eval μ • x :=
  by
  apply p.induction_on
  · intro a
    simp [Module.algebraMap_end_apply]
  · intro p q hp hq
    simp [hp, hq, add_smul]
  · intro n a hna
    rw [mul_comm, pow_succ, mul_assoc, AlgHom.map_mul, LinearMap.mul_apply, mul_comm, hna]
    simp only [mem_eigenspace_iff.1 h.1, smul_smul, aeval_X, eval_mul, eval_C, eval_pow, eval_X,
      LinearMap.map_smulₛₗ, RingHom.id_apply, mul_comm]
#align module.End.aeval_apply_of_has_eigenvector Module.End.aeval_apply_of_hasEigenvector

section minpoly

theorem isRoot_of_hasEigenvalue {f : End K V} {μ : K} (h : f.HasEigenvalue μ) :
    (minpoly K f).IsRoot μ :=
  by
  rcases(Submodule.ne_bot_iff _).1 h with ⟨w, ⟨H, ne0⟩⟩
  refine' Or.resolve_right (smul_eq_zero.1 _) ne0
  simp [← aeval_apply_of_has_eigenvector ⟨H, ne0⟩, minpoly.aeval K f]
#align module.End.is_root_of_has_eigenvalue Module.End.isRoot_of_hasEigenvalue

variable [FiniteDimensional K V] (f : End K V)

variable {f} {μ : K}

theorem hasEigenvalue_of_isRoot (h : (minpoly K f).IsRoot μ) : f.HasEigenvalue μ :=
  by
  cases' dvd_iff_is_root.2 h with p hp
  rw [has_eigenvalue, eigenspace]
  intro con
  cases' (LinearMap.isUnit_iff_ker_eq_bot _).2 Con with u hu
  have p_ne_0 : p ≠ 0 := by
    intro con
    apply minpoly.ne_zero f.is_integral
    rw [hp, Con, MulZeroClass.mul_zero]
  have h_deg := minpoly.degree_le_of_ne_zero K f p_ne_0 _
  · rw [hp, degree_mul, degree_X_sub_C, Polynomial.degree_eq_natDegree p_ne_0] at h_deg
    norm_cast  at h_deg
    linarith
  · have h_aeval := minpoly.aeval K f
    revert h_aeval
    simp [hp, ← hu]
#align module.End.has_eigenvalue_of_is_root Module.End.hasEigenvalue_of_isRoot

theorem hasEigenvalue_iff_isRoot : f.HasEigenvalue μ ↔ (minpoly K f).IsRoot μ :=
  ⟨isRoot_of_hasEigenvalue, hasEigenvalue_of_isRoot⟩
#align module.End.has_eigenvalue_iff_is_root Module.End.hasEigenvalue_iff_isRoot

/-- An endomorphism of a finite-dimensional vector space has finitely many eigenvalues. -/
noncomputable instance (f : End K V) : Fintype f.Eigenvalues :=
  Set.Finite.fintype
    (by
      have h : minpoly K f ≠ 0 := minpoly.ne_zero f.is_integral
      convert(minpoly K f).rootSet_finite K
      ext μ
      have : μ ∈ { μ : K | f.eigenspace μ = ⊥ → False } ↔ ¬f.eigenspace μ = ⊥ := by tauto
      convert rfl.mpr this
      simp [Polynomial.rootSet_def, Polynomial.mem_roots h, ← has_eigenvalue_iff_is_root,
        has_eigenvalue])

end minpoly

-- This is Lemma 5.21 of [axler2015], although we are no longer following that proof.
/-- Every linear operator on a vector space over an algebraically closed field has
    an eigenvalue. -/
theorem exists_eigenvalue [IsAlgClosed K] [FiniteDimensional K V] [Nontrivial V] (f : End K V) :
    ∃ c : K, f.HasEigenvalue c :=
  by
  simp_rw [has_eigenvalue_iff_mem_spectrum]
  exact spectrum.nonempty_of_isAlgClosed_of_finiteDimensional K f
#align module.End.exists_eigenvalue Module.End.exists_eigenvalue

noncomputable instance [IsAlgClosed K] [FiniteDimensional K V] [Nontrivial V] (f : End K V) :
    Inhabited f.Eigenvalues :=
  ⟨⟨f.exists_eigenvalue.some, f.exists_eigenvalue.choose_spec⟩⟩

/-- The eigenspaces of a linear operator form an independent family of subspaces of `V`.  That is,
any eigenspace has trivial intersection with the span of all the other eigenspaces. -/
theorem eigenspaces_independent (f : End K V) : CompleteLattice.Independent f.eigenspace := by
  classical
    -- Define an operation from `Π₀ μ : K, f.eigenspace μ`, the vector space of of finitely-supported
    -- choices of an eigenvector from each eigenspace, to `V`, by sending a collection to its sum.
    let S :
      @LinearMap K K _ _ (RingHom.id K) (Π₀ μ : K, f.eigenspace μ) V
        (@Dfinsupp.addCommMonoid K (fun μ => f.eigenspace μ) _) _
        (@Dfinsupp.module K _ (fun μ => f.eigenspace μ) _ _ _) _ :=
      @Dfinsupp.lsum K K ℕ _ V _ _ _ _ _ _ _ _ _ fun μ => (f.eigenspace μ).Subtype
    -- We need to show that if a finitely-supported collection `l` of representatives of the
    -- eigenspaces has sum `0`, then it itself is zero.
    suffices ∀ l : Π₀ μ, f.eigenspace μ, S l = 0 → l = 0
      by
      rw [CompleteLattice.independent_iff_dfinsupp_lsum_injective]
      change Function.Injective S
      rw [←
        @LinearMap.ker_eq_bot K K (Π₀ μ, f.eigenspace μ) V _ _
          (@Dfinsupp.addCommGroup K (fun μ => f.eigenspace μ) _)]
      rw [eq_bot_iff]
      exact this
    intro l hl
    -- We apply induction on the finite set of eigenvalues from which `l` selects a nonzero
    -- eigenvector, i.e. on the support of `l`.
    induction' h_l_support : l.support using Finset.induction with μ₀ l_support' hμ₀ ih generalizing
      l
    -- If the support is empty, all coefficients are zero and we are done.
    · exact Dfinsupp.support_eq_empty.1 h_l_support
    -- Now assume that the support of `l` contains at least one eigenvalue `μ₀`. We define a new
    -- collection of representatives `l'` to apply the induction hypothesis on later. The collection
    -- of representatives `l'` is derived from `l` by multiplying the coefficient of the eigenvector
    -- with eigenvalue `μ` by `μ - μ₀`.
    · let l' :=
        Dfinsupp.mapRange.linearMap (fun μ => (μ - μ₀) • @LinearMap.id K (f.eigenspace μ) _ _ _) l
      -- The support of `l'` is the support of `l` without `μ₀`.
      have h_l_support' : l'.support = l_support' :=
        by
        rw [← Finset.erase_insert hμ₀, ← h_l_support]
        ext a
        have : ¬(a = μ₀ ∨ l a = 0) ↔ ¬a = μ₀ ∧ ¬l a = 0 := not_or
        simp only [l', Dfinsupp.mapRange.linearMap_apply, Dfinsupp.mapRange_apply,
          Dfinsupp.mem_support_iff, Finset.mem_erase, id.def, LinearMap.id_coe,
          LinearMap.smul_apply, Ne.def, smul_eq_zero, sub_eq_zero, this]
      -- The entries of `l'` add up to `0`.
      have total_l' : S l' = 0 :=
        by
        let g := f - algebraMap K (End K V) μ₀
        let a : Π₀ μ : K, V := Dfinsupp.mapRange.linearMap (fun μ => (f.eigenspace μ).Subtype) l
        calc
          S l' =
              Dfinsupp.lsum ℕ (fun μ => (f.eigenspace μ).Subtype.comp ((μ - μ₀) • LinearMap.id))
                l :=
            _
          _ = Dfinsupp.lsum ℕ (fun μ => g.comp (f.eigenspace μ).Subtype) l := _
          _ = Dfinsupp.lsum ℕ (fun μ => g) a := _
          _ = g (Dfinsupp.lsum ℕ (fun μ => (LinearMap.id : V →ₗ[K] V)) a) := _
          _ = g (S l) := _
          _ = 0 := by rw [hl, g.map_zero]

        · exact Dfinsupp.sum_mapRange_index.linearMap
        · congr
          ext (μ v)
          simp only [g, eq_self_iff_true, Function.comp_apply, id.def, LinearMap.coe_comp,
            LinearMap.id_coe, LinearMap.smul_apply, LinearMap.sub_apply,
            Module.algebraMap_end_apply, sub_left_inj, sub_smul, Submodule.coe_smul_of_tower,
            Submodule.coe_sub, Submodule.subtype_apply, mem_eigenspace_iff.1 v.prop]
        · rw [Dfinsupp.sum_mapRange_index.linearMap]
        ·
          simp only [Dfinsupp.sumAddHom_apply, LinearMap.id_coe, LinearMap.map_dfinsupp_sum, id.def,
            LinearMap.toAddMonoidHom_coe, Dfinsupp.lsum_apply_apply]
        · congr
          simp only [S, a, Dfinsupp.sum_mapRange_index.linearMap, LinearMap.id_comp]
      -- Therefore, by the induction hypothesis, all entries of `l'` are zero.
      have l'_eq_0 := ih l' total_l' h_l_support'
      -- By the definition of `l'`, this means that `(μ - μ₀) • l μ = 0` for all `μ`.
      have h_smul_eq_0 : ∀ μ, (μ - μ₀) • l μ = 0 :=
        by
        intro μ
        calc
          (μ - μ₀) • l μ = l' μ := by
            simp only [l', LinearMap.id_coe, id.def, LinearMap.smul_apply, Dfinsupp.mapRange_apply,
              Dfinsupp.mapRange.linearMap_apply]
          _ = 0 := by
            rw [l'_eq_0]
            rfl

      -- Thus, the eigenspace-representatives in `l` for all `μ ≠ μ₀` are `0`.
      have h_lμ_eq_0 : ∀ μ : K, μ ≠ μ₀ → l μ = 0 :=
        by
        intro μ hμ
        apply or_iff_not_imp_left.1 (smul_eq_zero.1 (h_smul_eq_0 μ))
        rwa [sub_eq_zero]
      -- So if we sum over all these representatives, we obtain `0`.
      have h_sum_l_support'_eq_0 : (Finset.sum l_support' fun μ => (l μ : V)) = 0 :=
        by
        rw [← Finset.sum_const_zero]
        apply Finset.sum_congr rfl
        intro μ hμ
        rw [Submodule.coe_eq_zero, h_lμ_eq_0]
        rintro rfl
        exact hμ₀ hμ
      -- The only potentially nonzero eigenspace-representative in `l` is the one corresponding to
      -- `μ₀`. But since the overall sum is `0` by assumption, this representative must also be `0`.
      have : l μ₀ = 0 :=
        by
        simp only [S, Dfinsupp.lsum_apply_apply, Dfinsupp.sumAddHom_apply,
          LinearMap.toAddMonoidHom_coe, Dfinsupp.sum, h_l_support, Submodule.subtype_apply,
          Submodule.coe_eq_zero, Finset.sum_insert hμ₀, h_sum_l_support'_eq_0, add_zero] at hl
        exact hl
      -- Thus, all coefficients in `l` are `0`.
      show l = 0
      · ext μ
        by_cases h_cases : μ = μ₀
        · rwa [h_cases, SetLike.coe_eq_coe, Dfinsupp.coe_zero, Pi.zero_apply]
        exact congr_arg (coe : _ → V) (h_lμ_eq_0 μ h_cases)
#align module.End.eigenspaces_independent Module.End.eigenspaces_independent

/-- Eigenvectors corresponding to distinct eigenvalues of a linear operator are linearly
    independent. (Lemma 5.10 of [axler2015])

    We use the eigenvalues as indexing set to ensure that there is only one eigenvector for each
    eigenvalue in the image of `xs`. -/
theorem eigenvectors_linearIndependent (f : End K V) (μs : Set K) (xs : μs → V)
    (h_eigenvec : ∀ μ : μs, f.HasEigenvector μ (xs μ)) : LinearIndependent K xs :=
  CompleteLattice.Independent.linearIndependent _
    (f.eigenspaces_independent.comp Subtype.coe_injective) (fun μ => (h_eigenvec μ).1) fun μ =>
    (h_eigenvec μ).2
#align module.End.eigenvectors_linear_independent Module.End.eigenvectors_linearIndependent

/-- The generalized eigenspace for a linear map `f`, a scalar `μ`, and an exponent `k ∈ ℕ` is the
kernel of `(f - μ • id) ^ k`. (Def 8.10 of [axler2015]). Furthermore, a generalized eigenspace for
some exponent `k` is contained in the generalized eigenspace for exponents larger than `k`. -/
def generalizedEigenspace (f : End R M) (μ : R) : ℕ →o Submodule R M
    where
  toFun k := ((f - algebraMap R (End R M) μ) ^ k).ker
  monotone' k m hm := by
    simp only [← pow_sub_mul_pow _ hm]
    exact
      LinearMap.ker_le_ker_comp ((f - algebraMap R (End R M) μ) ^ k)
        ((f - algebraMap R (End R M) μ) ^ (m - k))
#align module.End.generalized_eigenspace Module.End.generalizedEigenspace

@[simp]
theorem mem_generalizedEigenspace (f : End R M) (μ : R) (k : ℕ) (m : M) :
    m ∈ f.generalizedEigenspace μ k ↔ ((f - μ • 1) ^ k) m = 0 :=
  Iff.rfl
#align module.End.mem_generalized_eigenspace Module.End.mem_generalizedEigenspace

@[simp]
theorem generalizedEigenspace_zero (f : End R M) (k : ℕ) :
    f.generalizedEigenspace 0 k = (f ^ k).ker := by simp [Module.End.generalizedEigenspace]
#align module.End.generalized_eigenspace_zero Module.End.generalizedEigenspace_zero

/-- A nonzero element of a generalized eigenspace is a generalized eigenvector.
    (Def 8.9 of [axler2015])-/
def HasGeneralizedEigenvector (f : End R M) (μ : R) (k : ℕ) (x : M) : Prop :=
  x ≠ 0 ∧ x ∈ generalizedEigenspace f μ k
#align module.End.has_generalized_eigenvector Module.End.HasGeneralizedEigenvector

/-- A scalar `μ` is a generalized eigenvalue for a linear map `f` and an exponent `k ∈ ℕ` if there
    are generalized eigenvectors for `f`, `k`, and `μ`. -/
def HasGeneralizedEigenvalue (f : End R M) (μ : R) (k : ℕ) : Prop :=
  generalizedEigenspace f μ k ≠ ⊥
#align module.End.has_generalized_eigenvalue Module.End.HasGeneralizedEigenvalue

/-- The generalized eigenrange for a linear map `f`, a scalar `μ`, and an exponent `k ∈ ℕ` is the
    range of `(f - μ • id) ^ k`. -/
def generalizedEigenrange (f : End R M) (μ : R) (k : ℕ) : Submodule R M :=
  ((f - algebraMap R (End R M) μ) ^ k).range
#align module.End.generalized_eigenrange Module.End.generalizedEigenrange

/-- The exponent of a generalized eigenvalue is never 0. -/
theorem exp_ne_zero_of_hasGeneralizedEigenvalue {f : End R M} {μ : R} {k : ℕ}
    (h : f.HasGeneralizedEigenvalue μ k) : k ≠ 0 :=
  by
  rintro rfl
  exact h LinearMap.ker_id
#align module.End.exp_ne_zero_of_has_generalized_eigenvalue Module.End.exp_ne_zero_of_hasGeneralizedEigenvalue

/-- The union of the kernels of `(f - μ • id) ^ k` over all `k`. -/
def maximalGeneralizedEigenspace (f : End R M) (μ : R) : Submodule R M :=
  ⨆ k, f.generalizedEigenspace μ k
#align module.End.maximal_generalized_eigenspace Module.End.maximalGeneralizedEigenspace

theorem generalizedEigenspace_le_maximal (f : End R M) (μ : R) (k : ℕ) :
    f.generalizedEigenspace μ k ≤ f.maximalGeneralizedEigenspace μ :=
  le_supᵢ _ _
#align module.End.generalized_eigenspace_le_maximal Module.End.generalizedEigenspace_le_maximal

@[simp]
theorem mem_maximalGeneralizedEigenspace (f : End R M) (μ : R) (m : M) :
    m ∈ f.maximalGeneralizedEigenspace μ ↔ ∃ k : ℕ, ((f - μ • 1) ^ k) m = 0 := by
  simp only [maximal_generalized_eigenspace, ← mem_generalized_eigenspace,
    Submodule.mem_supᵢ_of_chain]
#align module.End.mem_maximal_generalized_eigenspace Module.End.mem_maximalGeneralizedEigenspace

/-- If there exists a natural number `k` such that the kernel of `(f - μ • id) ^ k` is the
maximal generalized eigenspace, then this value is the least such `k`. If not, this value is not
meaningful. -/
noncomputable def maximalGeneralizedEigenspaceIndex (f : End R M) (μ : R) :=
  monotonicSequenceLimitIndex (f.generalizedEigenspace μ)
#align module.End.maximal_generalized_eigenspace_index Module.End.maximalGeneralizedEigenspaceIndex

/-- For an endomorphism of a Noetherian module, the maximal eigenspace is always of the form kernel
`(f - μ • id) ^ k` for some `k`. -/
theorem maximalGeneralizedEigenspace_eq [h : IsNoetherian R M] (f : End R M) (μ : R) :
    maximalGeneralizedEigenspace f μ =
      f.generalizedEigenspace μ (maximalGeneralizedEigenspaceIndex f μ) :=
  by
  rw [isNoetherian_iff_wellFounded] at h
  exact (WellFounded.supᵢ_eq_monotonicSequenceLimit h (f.generalized_eigenspace μ) : _)
#align module.End.maximal_generalized_eigenspace_eq Module.End.maximalGeneralizedEigenspace_eq

/-- A generalized eigenvalue for some exponent `k` is also
    a generalized eigenvalue for exponents larger than `k`. -/
theorem hasGeneralizedEigenvalue_of_hasGeneralizedEigenvalue_of_le {f : End R M} {μ : R} {k : ℕ}
    {m : ℕ} (hm : k ≤ m) (hk : f.HasGeneralizedEigenvalue μ k) : f.HasGeneralizedEigenvalue μ m :=
  by
  unfold has_generalized_eigenvalue at *
  contrapose! hk
  rw [← le_bot_iff, ← hk]
  exact (f.generalized_eigenspace μ).Monotone hm
#align module.End.has_generalized_eigenvalue_of_has_generalized_eigenvalue_of_le Module.End.hasGeneralizedEigenvalue_of_hasGeneralizedEigenvalue_of_le

/-- The eigenspace is a subspace of the generalized eigenspace. -/
theorem eigenspace_le_generalizedEigenspace {f : End R M} {μ : R} {k : ℕ} (hk : 0 < k) :
    f.eigenspace μ ≤ f.generalizedEigenspace μ k :=
  (f.generalizedEigenspace μ).Monotone (Nat.succ_le_of_lt hk)
#align module.End.eigenspace_le_generalized_eigenspace Module.End.eigenspace_le_generalizedEigenspace

/-- All eigenvalues are generalized eigenvalues. -/
theorem hasGeneralizedEigenvalue_of_hasEigenvalue {f : End R M} {μ : R} {k : ℕ} (hk : 0 < k)
    (hμ : f.HasEigenvalue μ) : f.HasGeneralizedEigenvalue μ k :=
  by
  apply has_generalized_eigenvalue_of_has_generalized_eigenvalue_of_le hk
  rw [has_generalized_eigenvalue, generalized_eigenspace, OrderHom.coe_fun_mk, pow_one]
  exact hμ
#align module.End.has_generalized_eigenvalue_of_has_eigenvalue Module.End.hasGeneralizedEigenvalue_of_hasEigenvalue

/-- All generalized eigenvalues are eigenvalues. -/
theorem hasEigenvalue_of_hasGeneralizedEigenvalue {f : End R M} {μ : R} {k : ℕ}
    (hμ : f.HasGeneralizedEigenvalue μ k) : f.HasEigenvalue μ :=
  by
  intro contra; apply hμ
  erw [LinearMap.ker_eq_bot] at contra⊢; rw [LinearMap.coe_pow]
  exact Function.Injective.iterate contra k
#align module.End.has_eigenvalue_of_has_generalized_eigenvalue Module.End.hasEigenvalue_of_hasGeneralizedEigenvalue

/-- Generalized eigenvalues are actually just eigenvalues. -/
@[simp]
theorem hasGeneralizedEigenvalue_iff_hasEigenvalue {f : End R M} {μ : R} {k : ℕ} (hk : 0 < k) :
    f.HasGeneralizedEigenvalue μ k ↔ f.HasEigenvalue μ :=
  ⟨hasEigenvalue_of_hasGeneralizedEigenvalue, hasGeneralizedEigenvalue_of_hasEigenvalue hk⟩
#align module.End.has_generalized_eigenvalue_iff_has_eigenvalue Module.End.hasGeneralizedEigenvalue_iff_hasEigenvalue

/-- Every generalized eigenvector is a generalized eigenvector for exponent `finrank K V`.
    (Lemma 8.11 of [axler2015]) -/
theorem generalizedEigenspace_le_generalizedEigenspace_finrank [FiniteDimensional K V] (f : End K V)
    (μ : K) (k : ℕ) : f.generalizedEigenspace μ k ≤ f.generalizedEigenspace μ (finrank K V) :=
  ker_pow_le_ker_pow_finrank _ _
#align module.End.generalized_eigenspace_le_generalized_eigenspace_finrank Module.End.generalizedEigenspace_le_generalizedEigenspace_finrank

/-- Generalized eigenspaces for exponents at least `finrank K V` are equal to each other. -/
theorem generalizedEigenspace_eq_generalizedEigenspace_finrank_of_le [FiniteDimensional K V]
    (f : End K V) (μ : K) {k : ℕ} (hk : finrank K V ≤ k) :
    f.generalizedEigenspace μ k = f.generalizedEigenspace μ (finrank K V) :=
  ker_pow_eq_ker_pow_finrank_of_le hk
#align module.End.generalized_eigenspace_eq_generalized_eigenspace_finrank_of_le Module.End.generalizedEigenspace_eq_generalizedEigenspace_finrank_of_le

/-- If `f` maps a subspace `p` into itself, then the generalized eigenspace of the restriction
    of `f` to `p` is the part of the generalized eigenspace of `f` that lies in `p`. -/
theorem generalizedEigenspace_restrict (f : End R M) (p : Submodule R M) (k : ℕ) (μ : R)
    (hfp : ∀ x : M, x ∈ p → f x ∈ p) :
    generalizedEigenspace (LinearMap.restrict f hfp) μ k =
      Submodule.comap p.Subtype (f.generalizedEigenspace μ k) :=
  by
  simp only [generalized_eigenspace, OrderHom.coe_fun_mk, ← LinearMap.ker_comp]
  induction' k with k ih
  · rw [pow_zero, pow_zero, LinearMap.one_eq_id]
    apply (Submodule.ker_subtype _).symm
  ·
    erw [pow_succ', pow_succ', LinearMap.ker_comp, LinearMap.ker_comp, ih, ← LinearMap.ker_comp,
      LinearMap.comp_assoc]
#align module.End.generalized_eigenspace_restrict Module.End.generalizedEigenspace_restrict

/-- If `p` is an invariant submodule of an endomorphism `f`, then the `μ`-eigenspace of the
restriction of `f` to `p` is a submodule of the `μ`-eigenspace of `f`. -/
theorem eigenspace_restrict_le_eigenspace (f : End R M) {p : Submodule R M} (hfp : ∀ x ∈ p, f x ∈ p)
    (μ : R) : (eigenspace (f.restrict hfp) μ).map p.Subtype ≤ f.eigenspace μ :=
  by
  rintro a ⟨x, hx, rfl⟩
  simp only [SetLike.mem_coe, mem_eigenspace_iff, LinearMap.restrict_apply] at hx⊢
  exact congr_arg coe hx
#align module.End.eigenspace_restrict_le_eigenspace Module.End.eigenspace_restrict_le_eigenspace

/-- Generalized eigenrange and generalized eigenspace for exponent `finrank K V` are disjoint. -/
theorem generalized_eigenvec_disjoint_range_ker [FiniteDimensional K V] (f : End K V) (μ : K) :
    Disjoint (f.generalizedEigenrange μ (finrank K V)) (f.generalizedEigenspace μ (finrank K V)) :=
  by
  have h :=
    calc
      Submodule.comap ((f - algebraMap _ _ μ) ^ finrank K V)
            (f.generalized_eigenspace μ (finrank K V)) =
          ((f - algebraMap _ _ μ) ^ finrank K V *
              (f - algebraMap K (End K V) μ) ^ finrank K V).ker :=
        by simpa only [generalized_eigenspace, OrderHom.coe_fun_mk, ← LinearMap.ker_comp]
      _ = f.generalized_eigenspace μ (finrank K V + finrank K V) :=
        by
        rw [← pow_add]
        rfl
      _ = f.generalized_eigenspace μ (finrank K V) :=
        by
        rw [generalized_eigenspace_eq_generalized_eigenspace_finrank_of_le]
        linarith

  rw [disjoint_iff_inf_le, generalized_eigenrange, LinearMap.range_eq_map,
    Submodule.map_inf_eq_map_inf_comap, top_inf_eq, h]
  apply Submodule.map_comap_le
#align module.End.generalized_eigenvec_disjoint_range_ker Module.End.generalized_eigenvec_disjoint_range_ker

/-- If an invariant subspace `p` of an endomorphism `f` is disjoint from the `μ`-eigenspace of `f`,
then the restriction of `f` to `p` has trivial `μ`-eigenspace. -/
theorem eigenspace_restrict_eq_bot {f : End R M} {p : Submodule R M} (hfp : ∀ x ∈ p, f x ∈ p)
    {μ : R} (hμp : Disjoint (f.eigenspace μ) p) : eigenspace (f.restrict hfp) μ = ⊥ :=
  by
  rw [eq_bot_iff]
  intro x hx
  simpa using hμp.le_bot ⟨eigenspace_restrict_le_eigenspace f hfp μ ⟨x, hx, rfl⟩, x.prop⟩
#align module.End.eigenspace_restrict_eq_bot Module.End.eigenspace_restrict_eq_bot

/-- The generalized eigenspace of an eigenvalue has positive dimension for positive exponents. -/
theorem pos_finrank_generalizedEigenspace_of_hasEigenvalue [FiniteDimensional K V] {f : End K V}
    {k : ℕ} {μ : K} (hx : f.HasEigenvalue μ) (hk : 0 < k) :
    0 < finrank K (f.generalizedEigenspace μ k) :=
  calc
    0 = finrank K (⊥ : Submodule K V) := by rw [finrank_bot]
    _ < finrank K (f.eigenspace μ) := (Submodule.finrank_lt_finrank_of_lt (bot_lt_iff_ne_bot.2 hx))
    _ ≤ finrank K (f.generalizedEigenspace μ k) :=
      Submodule.finrank_mono ((f.generalizedEigenspace μ).Monotone (Nat.succ_le_of_lt hk))

#align module.End.pos_finrank_generalized_eigenspace_of_has_eigenvalue Module.End.pos_finrank_generalizedEigenspace_of_hasEigenvalue

/-- A linear map maps a generalized eigenrange into itself. -/
theorem map_generalizedEigenrange_le {f : End K V} {μ : K} {n : ℕ} :
    Submodule.map f (f.generalizedEigenrange μ n) ≤ f.generalizedEigenrange μ n :=
  calc
    Submodule.map f (f.generalizedEigenrange μ n) = (f * (f - algebraMap _ _ μ) ^ n).range :=
      (LinearMap.range_comp _ _).symm
    _ = ((f - algebraMap _ _ μ) ^ n * f).range := by rw [Algebra.mul_sub_algebraMap_pow_commutes]
    _ = Submodule.map ((f - algebraMap _ _ μ) ^ n) f.range := (LinearMap.range_comp _ _)
    _ ≤ f.generalizedEigenrange μ n := LinearMap.map_le_range

#align module.End.map_generalized_eigenrange_le Module.End.map_generalizedEigenrange_le

/-- The generalized eigenvectors span the entire vector space (Lemma 8.21 of [axler2015]). -/
theorem supᵢ_generalizedEigenspace_eq_top [IsAlgClosed K] [FiniteDimensional K V] (f : End K V) :
    (⨆ (μ : K) (k : ℕ), f.generalizedEigenspace μ k) = ⊤ :=
  by
  -- We prove the claim by strong induction on the dimension of the vector space.
  induction' h_dim : finrank K V using Nat.strong_induction_on with n ih generalizing V
  cases n
  -- If the vector space is 0-dimensional, the result is trivial.
  · rw [← top_le_iff]
    simp only [finrank_eq_zero.1 (Eq.trans finrank_top h_dim), bot_le]
  -- Otherwise the vector space is nontrivial.
  · haveI : Nontrivial V :=
      finrank_pos_iff.1
        (by
          rw [h_dim]
          apply Nat.zero_lt_succ)
    -- Hence, `f` has an eigenvalue `μ₀`.
    obtain ⟨μ₀, hμ₀⟩ : ∃ μ₀, f.has_eigenvalue μ₀ := exists_eigenvalue f
    -- We define `ES` to be the generalized eigenspace
    let ES := f.generalized_eigenspace μ₀ (finrank K V)
    -- and `ER` to be the generalized eigenrange.
    let ER := f.generalized_eigenrange μ₀ (finrank K V)
    -- `f` maps `ER` into itself.
    have h_f_ER : ∀ x : V, x ∈ ER → f x ∈ ER := fun x hx =>
      map_generalized_eigenrange_le (Submodule.mem_map_of_mem hx)
    -- Therefore, we can define the restriction `f'` of `f` to `ER`.
    let f' : End K ER := f.restrict h_f_ER
    -- The dimension of `ES` is positive
    have h_dim_ES_pos : 0 < finrank K ES := by
      dsimp only [ES]
      rw [h_dim]
      apply pos_finrank_generalized_eigenspace_of_has_eigenvalue hμ₀ (Nat.zero_lt_succ n)
    -- and the dimensions of `ES` and `ER` add up to `finrank K V`.
    have h_dim_add : finrank K ER + finrank K ES = finrank K V := by
      apply LinearMap.finrank_range_add_finrank_ker
    -- Therefore the dimension `ER` mus be smaller than `finrank K V`.
    have h_dim_ER : finrank K ER < n.succ := by linarith
    -- This allows us to apply the induction hypothesis on `ER`:
    have ih_ER : (⨆ (μ : K) (k : ℕ), f'.generalized_eigenspace μ k) = ⊤ :=
      ih (finrank K ER) h_dim_ER f' rfl
    -- The induction hypothesis gives us a statement about subspaces of `ER`. We can transfer this
    -- to a statement about subspaces of `V` via `submodule.subtype`:
    have ih_ER' : (⨆ (μ : K) (k : ℕ), (f'.generalized_eigenspace μ k).map ER.subtype) = ER := by
      simp only [(Submodule.map_supᵢ _ _).symm, ih_ER, Submodule.map_subtype_top ER]
    -- Moreover, every generalized eigenspace of `f'` is contained in the corresponding generalized
    -- eigenspace of `f`.
    have hff' :
      ∀ μ k, (f'.generalized_eigenspace μ k).map ER.subtype ≤ f.generalized_eigenspace μ k :=
      by
      intros
      rw [generalized_eigenspace_restrict]
      apply Submodule.map_comap_le
    -- It follows that `ER` is contained in the span of all generalized eigenvectors.
    have hER : ER ≤ ⨆ (μ : K) (k : ℕ), f.generalized_eigenspace μ k :=
      by
      rw [← ih_ER']
      exact supᵢ₂_mono hff'
    -- `ES` is contained in this span by definition.
    have hES : ES ≤ ⨆ (μ : K) (k : ℕ), f.generalized_eigenspace μ k :=
      le_trans (le_supᵢ (fun k => f.generalized_eigenspace μ₀ k) (finrank K V))
        (le_supᵢ (fun μ : K => ⨆ k : ℕ, f.generalized_eigenspace μ k) μ₀)
    -- Moreover, we know that `ER` and `ES` are disjoint.
    have h_disjoint : Disjoint ER ES := generalized_eigenvec_disjoint_range_ker f μ₀
    -- Since the dimensions of `ER` and `ES` add up to the dimension of `V`, it follows that the
    -- span of all generalized eigenvectors is all of `V`.
    show (⨆ (μ : K) (k : ℕ), f.generalized_eigenspace μ k) = ⊤
    · rw [← top_le_iff, ← Submodule.eq_top_of_disjoint ER ES h_dim_add h_disjoint]
      apply sup_le hER hES
#align module.End.supr_generalized_eigenspace_eq_top Module.End.supᵢ_generalizedEigenspace_eq_top

end End

end Module


import data.real.basic

def fn_ub (f : ℝ → ℝ) (a : ℝ) : Prop := ∀ x, f x ≤ a
def fn_has_ub (f : ℝ → ℝ) := ∃ a, fn_ub f a

variables (f g : ℝ → ℝ)

-- BEGIN
example {c : ℝ} (mf : monotone f) (nnc : 0 ≤ c) :
  monotone (λ x, c * f x) :=
begin
  intros a b aleb,
  dsimp,
  have : f a ≤ f b,
    from mf aleb,
  exact mul_le_mul_of_nonneg_left this nnc,
end

example {c : ℝ} (ubf : fn_has_ub f) (h : c ≥ 0):
  fn_has_ub (λ x, c * f x) :=
begin
  cases ubf with a ubfa,
  use c * a,
  intro x,
  dsimp,
  exact mul_le_mul_of_nonneg_left (ubfa x) h,
end
-- END