diff --git a/LeanPool.lean b/LeanPool.lean index 7f0a0359e3..a19b850fad 100644 --- a/LeanPool.lean +++ b/LeanPool.lean @@ -2554,6 +2554,59 @@ public import LeanPool.ErdosTuzaValtr.Main.Lemmas.JoinN2N2 public import LeanPool.ErdosTuzaValtr.Main.Lemmas.JoinN2N3JoinN3N2 public import LeanPool.ErdosTuzaValtr.Main.Lemmas.JoinN2N3N2 public import LeanPool.ErdosTuzaValtr.Main.Main +public import LeanPool.EuclideanJordan +public import LeanPool.EuclideanJordan.EuclideanJordan +public import LeanPool.EuclideanJordan.EuclideanJordan.Block +public import LeanPool.EuclideanJordan.EuclideanJordan.Bridge +public import LeanPool.EuclideanJordan.EuclideanJordan.Class +public import LeanPool.EuclideanJordan.EuclideanJordan.Connection +public import LeanPool.EuclideanJordan.EuclideanJordan.FormallyReal +public import LeanPool.EuclideanJordan.EuclideanJordan.Frame +public import LeanPool.EuclideanJordan.EuclideanJordan.FrameExists +public import LeanPool.EuclideanJordan.EuclideanJordan.FramePeirce +public import LeanPool.EuclideanJordan.EuclideanJordan.FramePeirceMul +public import LeanPool.EuclideanJordan.EuclideanJordan.HermitianBilin +public import LeanPool.EuclideanJordan.EuclideanJordan.HermitianCarrier +public import LeanPool.EuclideanJordan.EuclideanJordan.Order +public import LeanPool.EuclideanJordan.EuclideanJordan.OrderAuto +public import LeanPool.EuclideanJordan.EuclideanJordan.OrderUnitSpace +public import LeanPool.EuclideanJordan.EuclideanJordan.Orthogonal +public import LeanPool.EuclideanJordan.EuclideanJordan.Pattern +public import LeanPool.EuclideanJordan.EuclideanJordan.Peirce +public import LeanPool.EuclideanJordan.EuclideanJordan.PeirceMul +public import LeanPool.EuclideanJordan.EuclideanJordan.PeirceSubalgebra +public import LeanPool.EuclideanJordan.EuclideanJordan.Power +public import LeanPool.EuclideanJordan.EuclideanJordan.PowerAssoc +public import LeanPool.EuclideanJordan.EuclideanJordan.Rank +public import LeanPool.EuclideanJordan.EuclideanJordan.Spectral +public import LeanPool.EuclideanJordan.EuclideanJordan.Subalgebra +public import LeanPool.EuclideanJordan.EuclideanJordan.TraceForm +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.ContinuousLinearMap +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.Basic +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.CFC +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.Inner +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.Jordan +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.NonSingular +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.Order +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.Proj +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.Reindex +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.Trace +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.IsMaximalSelfAdjoint +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.Isometry +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.LinearEquiv +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.Matrix +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.Misc +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.Tactic +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.Tactic.Commutes +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.Tactic.Commutes.Attribute +public import LeanPool.EuclideanJordan.EuclideanJordan.Witness +public import LeanPool.EuclideanJordan.FramePeirceSolution +public import LeanPool.EuclideanJordan.KoecherSolution +public import LeanPool.EuclideanJordan.SpectralSolution +public import LeanPool.EuclideanJordan.StructureSolution +public import LeanPool.EuclideanJordan.TraceFormSolution public import LeanPool.EvenGraphCycles public import LeanPool.EventStructures public import LeanPool.EventStructures.Basic diff --git a/LeanPool/EuclideanJordan.lean b/LeanPool/EuclideanJordan.lean new file mode 100644 index 0000000000..316f449a89 --- /dev/null +++ b/LeanPool/EuclideanJordan.lean @@ -0,0 +1,74 @@ +/- +Copyright (c) 2026 Bryan Ehrlich. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Bryan Ehrlich +-/ + +module + +public import LeanPool.EuclideanJordan.EuclideanJordan +public import LeanPool.EuclideanJordan.EuclideanJordan.Block +public import LeanPool.EuclideanJordan.EuclideanJordan.Bridge +public import LeanPool.EuclideanJordan.EuclideanJordan.Class +public import LeanPool.EuclideanJordan.EuclideanJordan.Connection +public import LeanPool.EuclideanJordan.EuclideanJordan.FormallyReal +public import LeanPool.EuclideanJordan.EuclideanJordan.Frame +public import LeanPool.EuclideanJordan.EuclideanJordan.FrameExists +public import LeanPool.EuclideanJordan.EuclideanJordan.FramePeirce +public import LeanPool.EuclideanJordan.EuclideanJordan.FramePeirceMul +public import LeanPool.EuclideanJordan.EuclideanJordan.HermitianBilin +public import LeanPool.EuclideanJordan.EuclideanJordan.HermitianCarrier +public import LeanPool.EuclideanJordan.EuclideanJordan.Order +public import LeanPool.EuclideanJordan.EuclideanJordan.OrderAuto +public import LeanPool.EuclideanJordan.EuclideanJordan.OrderUnitSpace +public import LeanPool.EuclideanJordan.EuclideanJordan.Orthogonal +public import LeanPool.EuclideanJordan.EuclideanJordan.Pattern +public import LeanPool.EuclideanJordan.EuclideanJordan.Peirce +public import LeanPool.EuclideanJordan.EuclideanJordan.PeirceMul +public import LeanPool.EuclideanJordan.EuclideanJordan.PeirceSubalgebra +public import LeanPool.EuclideanJordan.EuclideanJordan.Power +public import LeanPool.EuclideanJordan.EuclideanJordan.PowerAssoc +public import LeanPool.EuclideanJordan.EuclideanJordan.Rank +public import LeanPool.EuclideanJordan.EuclideanJordan.Spectral +public import LeanPool.EuclideanJordan.EuclideanJordan.Subalgebra +public import LeanPool.EuclideanJordan.EuclideanJordan.TraceForm +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.ContinuousLinearMap +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.Basic +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.CFC +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.Inner +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.Jordan +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.NonSingular +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.Order +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.Proj +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.Reindex +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.Trace +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.IsMaximalSelfAdjoint +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.Isometry +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.LinearEquiv +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.Matrix +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.Misc +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.Tactic +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.Tactic.Commutes +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.Tactic.Commutes.Attribute +public import LeanPool.EuclideanJordan.EuclideanJordan.Witness +public import LeanPool.EuclideanJordan.FramePeirceSolution +public import LeanPool.EuclideanJordan.KoecherSolution +public import LeanPool.EuclideanJordan.SpectralSolution +public import LeanPool.EuclideanJordan.StructureSolution +public import LeanPool.EuclideanJordan.TraceFormSolution + + +/-! +# Euclidean Jordan algebras and the frame Peirce decomposition + +Source: url:https://github.com/ehrlich-b/euclidean-jordan +Authors: Bryan Ehrlich +Status: verified +Main declarations: `EuclideanJordan.frameBlock_isInternal` +Tags: nonassociative-algebra +MSC: 17C20, 17C27, 17C37, 17C65, 17A15, 46L70 +-/ + +@[expose] public section diff --git a/LeanPool/EuclideanJordan/EuclideanJordan.lean b/LeanPool/EuclideanJordan/EuclideanJordan.lean new file mode 100644 index 0000000000..29643674b3 --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan.lean @@ -0,0 +1,59 @@ +/- +Copyright (c) 2026 Bryan Ehrlich. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Bryan Ehrlich +-/ +module + +public import LeanPool.EuclideanJordan.EuclideanJordan.Block +public import LeanPool.EuclideanJordan.EuclideanJordan.Bridge +public import LeanPool.EuclideanJordan.EuclideanJordan.Class +public import LeanPool.EuclideanJordan.EuclideanJordan.Connection +public import LeanPool.EuclideanJordan.EuclideanJordan.FormallyReal +public import LeanPool.EuclideanJordan.EuclideanJordan.Frame +public import LeanPool.EuclideanJordan.EuclideanJordan.FrameExists +public import LeanPool.EuclideanJordan.EuclideanJordan.FramePeirce +public import LeanPool.EuclideanJordan.EuclideanJordan.FramePeirceMul +public import LeanPool.EuclideanJordan.EuclideanJordan.HermitianBilin +public import LeanPool.EuclideanJordan.EuclideanJordan.HermitianCarrier +public import LeanPool.EuclideanJordan.EuclideanJordan.Order +public import LeanPool.EuclideanJordan.EuclideanJordan.OrderAuto +public import LeanPool.EuclideanJordan.EuclideanJordan.OrderUnitSpace +public import LeanPool.EuclideanJordan.EuclideanJordan.Orthogonal +public import LeanPool.EuclideanJordan.EuclideanJordan.Pattern +public import LeanPool.EuclideanJordan.EuclideanJordan.Peirce +public import LeanPool.EuclideanJordan.EuclideanJordan.PeirceMul +public import LeanPool.EuclideanJordan.EuclideanJordan.PeirceSubalgebra +public import LeanPool.EuclideanJordan.EuclideanJordan.Power +public import LeanPool.EuclideanJordan.EuclideanJordan.PowerAssoc +public import LeanPool.EuclideanJordan.EuclideanJordan.Rank +public import LeanPool.EuclideanJordan.EuclideanJordan.Spectral +public import LeanPool.EuclideanJordan.EuclideanJordan.Subalgebra +public import LeanPool.EuclideanJordan.EuclideanJordan.TraceForm +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.ContinuousLinearMap +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.Basic +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.CFC +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.Inner +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.Jordan +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.NonSingular +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.Order +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.Proj +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.Reindex +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.Trace +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.IsMaximalSelfAdjoint +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.Isometry +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.LinearEquiv +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.Matrix +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.Misc +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.Tactic.Commutes +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.Tactic.Commutes.Attribute +public import LeanPool.EuclideanJordan.EuclideanJordan.Witness + + +/-! +# Euclidean Jordan algebras in Lean 4 + +Root import for the library. See `README.md` for the headline results. +-/ + +@[expose] public section diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/Block.lean b/LeanPool/EuclideanJordan/EuclideanJordan/Block.lean new file mode 100644 index 0000000000..8909478ba0 --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/Block.lean @@ -0,0 +1,203 @@ +/- +Copyright (c) 2026 Bryan Ehrlich. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Bryan Ehrlich +-/ +module + +public import LeanPool.EuclideanJordan.EuclideanJordan.Frame + + + +/-! +# The rank-two block, split into its three Peirce pieces + +For orthogonal idempotents `p, q`, the Peirce-1 space of the rank-two block `q' = p + q` +decomposes: + + `J₁(p + q) = J₁(p) ⊕ (J_{1/2}(p) ∩ J_{1/2}(q)) ⊕ J₁(q)`. + +`exists_block_split` is the forward half and `block_mul_eq_self` the converse, so together +they characterise `J₁(p+q)` exactly. This is the Faraut–Korányi coherence-block structure at +the smallest interesting size. + +## The argument + +`L_p` commutes with `L_q` (`opCommute_of_orthogonal`), so `L_p` commutes with `L_{p+q}`, so +**`L_{p+q}` commutes with every polynomial in `L_p`** — and the three Peirce projections of +`p` are such polynomials, hence they all preserve `J₁(p+q)`. +★ The subject of that chain shifts, and an earlier draft let it shift silently ("…hence with +every polynomial in `L_p`", with `L_p` still the subject, which is trivially true and not what +is meant). Caught on the arc's second audit pass; a true sentence whose grammar names the +wrong subject is the same defect kind as a true conclusion with a false reason. Apply +them to `x ∈ J₁(p+q)`: the pieces have `p`-eigenvalues `1`, `1/2`, `0`, and since +`q ∘ z = (p+q) ∘ z − p ∘ z = z − p ∘ z` on `J₁(p+q)`, their `q`-eigenvalues are the +complements `0`, `1/2`, `1`. **The three admissible patterns are exactly the three ways +`μ + ν = 1` can happen with `μ, ν ∈ {0, 1/2, 1}`** — which is why the block has three pieces +and not nine. + +★ **The eigenvalue trichotomy is what makes this finite**, but note it is not *invoked*: the +projections do the work directly. The trichotomy explains the shape of the answer rather than +appearing in the proof. + +## `q` is not assumed idempotent, and that is not an oversight + +`exists_block_split` needs only **`p ∘ p = p` and `p ∘ q = 0`**. Lean's unused-variable linter +caught the `hq` hypothesis being dead and it was removed rather than silenced. The reason is +visible in the argument above: every claim about `q` is derived from +`q ∘ z = (p+q) ∘ z − p ∘ z`, which never asks what `q ∘ q` is. + +★ What `hq` buys is the *interpretation*: without it `p + q` need not be idempotent, so +"`J₁(p+q)`" is not a Peirce space and "`J₁(q)`" is not either — the conclusions are still true, +they just stop being a Peirce decomposition. Callers wanting the FK reading should have `hq` +in hand from `IsOrthIdemFamily`; the theorem simply does not need to be told. +-/ + +@[expose] public section + +namespace EuclideanJordan + +section Block + +variable {J : Type*} [NonUnitalNonAssocCommRing J] [IsCommJordan J] [Module ℝ J] + [IsScalarTower ℝ J J] + +omit [IsCommJordan J] in +/-- If `L_c` commutes with `L_p` then it commutes with the Peirce-1 projection of `p`. -/ +theorem mul_peirceOne_comm {c p : J} (h : ∀ w : J, c * (p * w) = p * (c * w)) (x : J) : + c * peirceOne p x = peirceOne p (c * x) := by + simp only [peirceOne_apply, mul_sub, mul_smul_comm', h] + +omit [IsCommJordan J] in +/-- The same for the Peirce-`1/2` projection. -/ +theorem mul_peirceHalf_comm {c p : J} (h : ∀ w : J, c * (p * w) = p * (c * w)) (x : J) : + c * peirceHalf p x = peirceHalf p (c * x) := by + simp only [peirceHalf_apply, mul_sub, mul_smul_comm', h] + +omit [IsCommJordan J] in +/-- The same for the Peirce-`0` projection. -/ +theorem mul_peirceZero_comm {c p : J} (h : ∀ w : J, c * (p * w) = p * (c * w)) (x : J) : + c * peirceZero p x = peirceZero p (c * x) := by + simp only [peirceZero_apply, mul_add, mul_sub, mul_smul_comm', h] + +variable {p q : J} + +omit [IsScalarTower ℝ J J] in +/-- `L_{p+q}` commutes with `L_p`, for `p` idempotent and orthogonal to `q`. -/ +theorem add_mul_comm_left (hp : p * p = p) (hpq : p * q = 0) (w : J) : + (p + q) * (p * w) = p * ((p + q) * w) := by + rw [add_mul, add_mul, mul_add, opCommute_of_orthogonal hp hpq w] + +/-- **The rank-two block splits.** Every element of `J₁(p+q)` is the sum of an element of +`J₁(p)`, an element of the coherence space `J_{1/2}(p) ∩ J_{1/2}(q)`, and an element of +`J₁(q)` — and the `q`-eigenvalues come out as the complements of the `p`-eigenvalues. + +★ Needs only `p ∘ p = p` and `p ∘ q = 0`; see the module docstring on why `q ∘ q = q` is not +required. -/ +theorem exists_block_split (hp : p * p = p) (hpq : p * q = 0) {x : J} (hx : (p + q) * x = x) : + ∃ a b c : J, (p * a = a ∧ q * a = 0) ∧ (p * b = (2 : ℝ)⁻¹ • b ∧ q * b = (2 : ℝ)⁻¹ • b) + ∧ (p * c = 0 ∧ q * c = c) ∧ x = a + b + c := by + have hcomm := add_mul_comm_left hp hpq + refine ⟨peirceOne p x, peirceHalf p x, peirceZero p x, ⟨mul_peirceOne hp x, ?_⟩, + ⟨mul_peirceHalf hp x, ?_⟩, ⟨mul_peirceZero hp x, ?_⟩, (peirce_add_add p x).symm⟩ + · have h1 : p * peirceOne p x + q * peirceOne p x = peirceOne p x := by + rw [← add_mul, mul_peirceOne_comm hcomm x, hx] + rw [mul_peirceOne hp x] at h1 + refine add_left_cancel (a := peirceOne p x) ?_ + rw [add_zero] + exact h1 + · have h2 : p * peirceHalf p x + q * peirceHalf p x = peirceHalf p x := by + rw [← add_mul, mul_peirceHalf_comm hcomm x, hx] + rw [mul_peirceHalf hp x] at h2 + have hstep : q * peirceHalf p x = peirceHalf p x - (2 : ℝ)⁻¹ • peirceHalf p x := + eq_sub_of_add_eq (by rw [add_comm]; exact h2) + rw [hstep] + module + · have h3 : p * peirceZero p x + q * peirceZero p x = peirceZero p x := by + rw [← add_mul, mul_peirceZero_comm hcomm x, hx] + rw [mul_peirceZero hp x, zero_add] at h3 + exact h3 + +omit [IsCommJordan J] [IsScalarTower ℝ J J] in +/-- **The converse.** Each of the three admissible eigenvalue patterns lands in `J₁(p+q)`, so +together with `exists_block_split` this characterises the block exactly. + +The three cases are `1 + 0`, `1/2 + 1/2` and `0 + 1` — the only ways two elements of +`{0, 1/2, 1}` sum to `1`. -/ +theorem block_mul_eq_self {z : J} (h : (p * z = z ∧ q * z = 0) + ∨ (p * z = (2 : ℝ)⁻¹ • z ∧ q * z = (2 : ℝ)⁻¹ • z) ∨ (p * z = 0 ∧ q * z = z)) : + (p + q) * z = z := by + rw [add_mul] + rcases h with ⟨h1, h2⟩ | ⟨h1, h2⟩ | ⟨h1, h2⟩ + · rw [h1, h2, add_zero] + · rw [h1, h2]; module + · rw [h1, h2, zero_add] + +/-! ### The Peirce projections of orthogonal idempotents commute + +`EuclideanJordan/PeirceMul.lean` named this as the ingredient missing for the frame-level joint +decomposition: *"closing the field needs the joint Peirce decomposition over a frame, which +needs these rules plus the commutation of the projections of distinct frame idempotents."* +Here it is. + +The generic lemmas below take **any** linear `F` commuting with `L_q` and conclude that `F` +commutes with each Peirce projection of `q`. Instantiating `F` at a Peirce projection of `p` +gives all nine commutations; the three diagonal ones are named, and the mixed six are one +line each from the same lemmas. -/ + +section ProjComm + +omit [IsCommJordan J] in +/-- A linear map commuting with `L_q` commutes with `q`'s Peirce-1 projection. -/ +theorem peirceOne_comm_of_mul_comm {q : J} {F : J →ₗ[ℝ] J} (h : ∀ w, q * F w = F (q * w)) + (x : J) : peirceOne q (F x) = F (peirceOne q x) := by + simp only [peirceOne_apply, h, map_sub, map_smul] + +omit [IsCommJordan J] in +/-- The same for the Peirce-`1/2` projection. -/ +theorem peirceHalf_comm_of_mul_comm {q : J} {F : J →ₗ[ℝ] J} (h : ∀ w, q * F w = F (q * w)) + (x : J) : peirceHalf q (F x) = F (peirceHalf q x) := by + simp only [peirceHalf_apply, h, map_sub, map_smul] + +omit [IsCommJordan J] in +/-- The same for the Peirce-`0` projection. -/ +theorem peirceZero_comm_of_mul_comm {q : J} {F : J →ₗ[ℝ] J} (h : ∀ w, q * F w = F (q * w)) + (x : J) : peirceZero q (F x) = F (peirceZero q x) := by + simp only [peirceZero_apply, h, map_add, map_sub, map_smul] + +/-- For orthogonal idempotents, `L_q` commutes with `p`'s Peirce-1 projection. -/ +theorem mul_peirceOne_comm_orth (hq : q * q = q) (hpq : p * q = 0) (x : J) : + q * peirceOne p x = peirceOne p (q * x) := + mul_peirceOne_comm (fun w => opCommute_of_orthogonal hq (by rw [mul_comm]; exact hpq) w) x + +/-- For orthogonal idempotents, `L_q` commutes with `p`'s Peirce-`1/2` projection. -/ +theorem mul_peirceHalf_comm_orth (hq : q * q = q) (hpq : p * q = 0) (x : J) : + q * peirceHalf p x = peirceHalf p (q * x) := + mul_peirceHalf_comm (fun w => opCommute_of_orthogonal hq (by rw [mul_comm]; exact hpq) w) x + +/-- For orthogonal idempotents, `L_q` commutes with `p`'s Peirce-`0` projection. -/ +theorem mul_peirceZero_comm_orth (hq : q * q = q) (hpq : p * q = 0) (x : J) : + q * peirceZero p x = peirceZero p (q * x) := + mul_peirceZero_comm (fun w => opCommute_of_orthogonal hq (by rw [mul_comm]; exact hpq) w) x + +/-- **The Peirce-1 projections of orthogonal idempotents commute.** -/ +theorem peirceOne_comm_peirceOne (hq : q * q = q) (hpq : p * q = 0) (x : J) : + peirceOne q (peirceOne p x) = peirceOne p (peirceOne q x) := + peirceOne_comm_of_mul_comm (F := peirceOne p) (mul_peirceOne_comm_orth hq hpq) x + +/-- **The Peirce-`1/2` projections of orthogonal idempotents commute** — the projection onto +the coherence space is well defined independently of which idempotent is applied first. -/ +theorem peirceHalf_comm_peirceHalf (hq : q * q = q) (hpq : p * q = 0) (x : J) : + peirceHalf q (peirceHalf p x) = peirceHalf p (peirceHalf q x) := + peirceHalf_comm_of_mul_comm (F := peirceHalf p) (mul_peirceHalf_comm_orth hq hpq) x + +/-- **The Peirce-`0` projections of orthogonal idempotents commute.** -/ +theorem peirceZero_comm_peirceZero (hq : q * q = q) (hpq : p * q = 0) (x : J) : + peirceZero q (peirceZero p x) = peirceZero p (peirceZero q x) := + peirceZero_comm_of_mul_comm (F := peirceZero p) (mul_peirceZero_comm_orth hq hpq) x + +end ProjComm + +end Block + +end EuclideanJordan diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/Bridge.lean b/LeanPool/EuclideanJordan/EuclideanJordan/Bridge.lean new file mode 100644 index 0000000000..cd1deb405e --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/Bridge.lean @@ -0,0 +1,98 @@ +/- +Copyright (c) 2026 Bryan Ehrlich. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Bryan Ehrlich +-/ +module + +public import LeanPool.EuclideanJordan.EuclideanJordan.Pattern + + + +/-! +# Unbundled Jordan algebras: the multiplication as a bilinear map + +The rest of this library runs on the **typeclasses** `[NonUnitalNonAssocCommRing J] +[IsCommJordan J] [Module ℝ J]`. A caller who instead holds the Jordan product as a **bundled +bilinear map** `m : J →ₗ[ℝ] J →ₗ[ℝ] J` over `[NormedAddCommGroup J] [Module ℝ J]` cannot simply +assume both: that gives two different `AddCommGroup J` instances, `Module ℝ J` then fails to +synthesise at the use site, and the result is a textbook Mathlib diamond. + +**This file dodges it.** `ringOfBilinear` builds the multiplicative structure *on the ambient +additive group* — `{ (inferInstance : AddCommGroup J) with mul := fun x y => m x y, … }` — so +only one `AddCommGroup` is ever in play. Nothing is assumed twice. + +## What that buys + +Two sample transfers are given, both stated with `m` alone: + +* `peirce_poly_bilinear` — the Peirce polynomial identity `2 c(c(c y)) + c y = 3 c(c y)` for an + idempotent `c`; +* `opCommute_scalarOn_bilinear` — the Jordan multiplication operators `L_a` and `L_b` commute, + for `a` scalar on `range c` and `b` in the `1`-eigenspace of `L_c`. This is the load-bearing + Faraut–Korányi operator-commutation hypothesis. + +`EuclideanJordan/Spectral.lean` and `EuclideanJordan/Order.lean` use the same device at scale: +`spectral_resolution_bilinear` and `orderUnitSpaceOfBilinear` are stated over `m` and proved by +installing `ringOfBilinear` locally. + +★ **The device has a hard limit.** Only results whose *statements* are expressible with `m` +alone cross over; anything whose statement needs the ring instance — `jpow`, and so Albert's +power-associativity theorem — cannot be bridged this way, because the instance would have to +exist before the statement elaborates. +-/ + +@[expose] public section +namespace EuclideanJordan + +section Bridge + +variable {J : Type*} [NormedAddCommGroup J] [Module ℝ J] + +/-- Build the multiplicative structure ON the ambient additive group, from a bilinear map. -/ +@[instance_reducible] +def ringOfBilinear (m : J →ₗ[ℝ] J →ₗ[ℝ] J) (hcomm : ∀ x y, m x y = m y x) : + NonUnitalNonAssocCommRing J := + { (inferInstance : AddCommGroup J) with + mul := fun x y => m x y + left_distrib := fun a b c => (m a).map_add b c + right_distrib := fun a b c => by + change m (a + b) c = m a c + m b c + rw [map_add]; rfl + zero_mul := fun a => by + change m 0 a = 0 + rw [map_zero]; rfl + mul_zero := fun a => (m a).map_zero + mul_comm := hcomm } + + +variable (m : J →ₗ[ℝ] J →ₗ[ℝ] J) + +/-- `m` is linear in its first argument — the scalar-tower law for the constructed ring. -/ +theorem smul_bilinear (r : ℝ) (a b : J) : m (r • a) b = r • m a b := by + rw [map_smul]; rfl + +/-- **The Peirce polynomial identity, in bilinear-map vocabulary.** -/ +theorem peirce_poly_bilinear (hcomm : ∀ x y : J, m x y = m y x) + (hjordan : ∀ a b : J, m (m a b) (m a a) = m a (m b (m a a))) + {c : J} (hc : m c c = c) (y : J) : + (2 : ℕ) • m c (m c (m c y)) + m c y = (3 : ℕ) • m c (m c y) := by + let : NonUnitalNonAssocCommRing J := ringOfBilinear m hcomm + let : IsCommJordan J := ⟨hjordan⟩ + exact peirce_poly hc y + +/-- **Operator commutation in bilinear-map vocabulary**: `L_a` and `L_b` commute at `w`, for +`a` scalar on `range c` and `b` in the `1`-eigenspace of `L_c`. -/ +theorem opCommute_scalarOn_bilinear (hcomm : ∀ x y : J, m x y = m y x) + (hjordan : ∀ a b : J, m (m a b) (m a a) = m a (m b (m a a))) + {c a a₀ b : J} {mu : ℝ} (hc : m c c = c) (ha : a = mu • c + a₀) + (ha₀ : m c a₀ = 0) (hb : m c b = b) (w : J) : + m a (m b w) = m b (m a w) := by + let : NonUnitalNonAssocCommRing J := ringOfBilinear m hcomm + let : IsCommJordan J := ⟨hjordan⟩ + let : IsScalarTower ℝ J J := ⟨fun r x y => smul_bilinear m r x y⟩ + exact opCommute_scalarOn hc ha ha₀ hb w + +end Bridge + +end EuclideanJordan diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/Class.lean b/LeanPool/EuclideanJordan/EuclideanJordan/Class.lean new file mode 100644 index 0000000000..9d32b23702 --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/Class.lean @@ -0,0 +1,266 @@ +/- +Copyright (c) 2026 Bryan Ehrlich. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Bryan Ehrlich +-/ +module + +public import LeanPool.EuclideanJordan.EuclideanJordan.Order +public import LeanPool.EuclideanJordan.EuclideanJordan.Pattern + + + +/-! +# The Euclidean Jordan algebra class + +The abstract modules of this library built before this one — `Peirce`, `PeirceMul`, +`Orthogonal`, `Frame`, `Power`, `PowerAssoc`, `FormallyReal`, `Subalgebra`, `Block`, `Pattern` +and `Spectral` — state their hypotheses as a *tuple* drawn from +`[NonUnitalNonAssocCommRing J] [IsCommJordan J] [Module ℝ J] [IsScalarTower ℝ J J] +[IsFormallyReal J] [Module.Finite ℝ J]`, each module taking the sub-tuple it needs; or — on the +Euclidean side of `EuclideanJordan/Order.lean` and in `Spectral`'s interface section — as a + bilinear map +`m : J →ₗ[ℝ] J →ₗ[ℝ] J` carrying `hcomm`/`hjordan`/`hassoc` as ordinary hypotheses. (`Witness` +and `Spectral`'s concrete section state theirs over `HermitianMat` instead.) Both abstract +vocabularies are correct and neither is a *class*, so a theorem about a Euclidean Jordan +algebra cannot be stated by naming one. + +This file names one. `EuclideanJordanAlgebra J` is a real inner-product space with a +commutative bilinear product with unit, satisfying the Jordan identity and the associativity of +the inner product — Faraut–Korányi's definition (FK III.1). + +★ **Hypothesis direction.** The class is *weaker* than the textbook definition in two +respects, which is the correct direction for an import — a theorem proved over this class +applies to the textbook setting, not the other way round. First, finite-dimensionality is +folded into the textbook definition and is carried here as a separate `[FiniteDimensional ℝ J]` +argument. Second, a common presentation fixes the inner product to be the trace form +`⟪x, y⟫ = tr(x ∘ y)` for the Jordan trace, whereas the class asks only that *some* +positive-definite associative inner product exist. `inner_mul_one` below shows the gap is +smaller than it looks: any associative inner product satisfies `⟪x ∘ y, 1⟫ = ⟪x, y⟫`, so it *is* +the trace form of the linear functional `z ↦ ⟪z, 1⟫`. It need not be the form of the *Jordan* +trace — rescaling an associative inner product by a positive constant keeps it associative — +and nothing here claims otherwise. + +## The shape, and the diamond it dodges + +★ The product is placed **on top of** the additive group of the inner-product space, never +alongside a second one. Assuming `[NormedAddCommGroup J]` and `[NonUnitalNonAssocCommRing J]` +simultaneously produces two `AddCommGroup J` instances and `Module ℝ J` then fails to +synthesise; `EuclideanJordan/Bridge.lean` records that diamond and `ringOfBilinear` dodges it by + building +the multiplicative structure on the *ambient* additive group. This class is that dodge +promoted from a `def` to a `class`: it `extends Mul J, One J` over +`[NormedAddCommGroup J] [InnerProductSpace ℝ J]`, so only one `AddCommGroup J` is ever in play +and `instNonUnitalNonAssocCommRing` below is built from `inferInstance` on the nose. + +Consequently `ringOfBilinear (jmulₗ J) mul_comm = instNonUnitalNonAssocCommRing` holds by `rfl` +(`ringOfBilinear_jmulₗ`), which is the statement that the class and the bilinear vocabulary of +`EuclideanJordan/Order.lean` are the same structure and not merely isomorphic ones. + +## What finite-dimensionality is, and is not, needed for + +`FiniteDimensional ℝ J` is deliberately **not** a field of the class. It is genuinely required +downstream: `EuclideanJordan/Spectral.lean` records that its `spectral_resolution_bilinear` — + which is +`spectral_resolution_complete` in bilinear vocabulary, and carries the same hypotheses minus the +inner product — is false without it, `ℝ[X]` satisfying every other hypothesis with no nonconstant +resolution. So the dimension is carried as a separate instance argument at exactly the theorems +that need it, and `spectral_resolution_complete'` below is one of them. + +★ It is *not* needed for formal reality. `instIsFormallyReal` below is unconditional: pairing a +vanishing sum of squares against the unit turns `∑ᵢ ⟪xᵢ ∘ xᵢ, 1⟫` into `∑ᵢ ⟪xᵢ, xᵢ⟫` by one +application of `inner_assoc`, and a vanishing sum of nonnegative reals has vanishing terms. + +This corrects the build plan on two points. The plan derived the instance from +`EuclideanJordan/Spectral.lean`'s `isFormallyReal_of_fin` under `[FiniteDimensional ℝ J]`. That + lemma +cannot supply it: `isFormallyReal_of_fin` *takes formal reality as a hypothesis*, in `Fin k` +form, and does nothing but reindex it to the `Finset` form the class `IsFormallyReal` carries. +The derivation had to come from the inner product instead — and once it does, the dimension +hypothesis turns out to be unused. + +## Scope + +Almost all of this file is repackaging: the two restatements at the end + (`spectral_resolution_complete'`, `peirce_add_add'`) discharge the claim +that the existing layer is reachable from the class, and are not new results. + +★ Two declarations are *not* repackaging, and the file should not be described as if they were. +`inner_mul_one`, and `instIsFormallyReal` resting on it, derive formal reality from the +associative inner product, and the existing layer does not contain that derivation anywhere: it +takes formal reality as a hypothesis at every abstract site (`EuclideanJordan/Spectral.lean`'s +`isFormallyReal_of_fin` *receives* it and does nothing but reindex; `EuclideanJordan/Order.lean`'s +`orderUnitSpaceOfBilinear` receives it as `[IsFormallyReal J]`), and derives it only on the +concrete carrier, in `EuclideanJordan/Witness.lean`'s `instIsFormallyReal` for `HermitianMat d + 𝕜`. Both new +declarations are short; the point is only that "this file contains no new mathematics" would be +false. + +★ One hazard to record for later modules. `instNonUnitalNonAssocCommRing` fires on any type +carrying `EuclideanJordanAlgebra`, and `HermitianMat d 𝕜` already carries a `Mul` — from +`EuclideanJordan/Vendor/HermitianMat/Jordan.lean`'s `scoped instance : CommMagma +(HermitianMat d 𝕜)`, whose product is `HermitianMat.symmMul`. Nothing declares +`EuclideanJordanAlgebra (HermitianMat d 𝕜)` today, and until something does the two never meet; +if one is ever declared, that scoped instance and this class's `toMul` will both be in scope +inside `open HermMul` sections and one of them has to give way. +-/ + +@[expose] public section + +noncomputable section + +namespace EuclideanJordan + +/-- A **Euclidean Jordan algebra**: a real inner-product space carrying a commutative bilinear +product with unit, satisfying the Jordan identity and the associativity of the inner product. + +This is Faraut–Korányi's definition (FK III.1), weakened in the two ways the module docstring +records: finite-dimensionality is *not* a field, and the inner product is an arbitrary +associative one rather than the Jordan trace form. Both weakenings run in the import-safe +direction. -/ +class EuclideanJordanAlgebra (J : Type*) [NormedAddCommGroup J] [InnerProductSpace ℝ J] + extends Mul J, One J where + /-- The Jordan product is commutative. -/ + mul_comm : ∀ x y : J, x * y = y * x + /-- The Jordan product is additive in its left argument. -/ + add_mul : ∀ x y z : J, (x + y) * z = x * z + y * z + /-- The Jordan product is homogeneous in its left argument. -/ + smul_mul : ∀ (r : ℝ) (x y : J), (r • x) * y = r • (x * y) + /-- `1` is a unit for the Jordan product. -/ + one_mul : ∀ x : J, (1 : J) * x = x + /-- The Jordan identity, `x ∘ (x² ∘ y) = x² ∘ (x ∘ y)`. -/ + jordan : ∀ x y : J, x * ((x * x) * y) = (x * x) * (x * y) + /-- The inner product is associative: `⟪x ∘ y, z⟫ = ⟪y, x ∘ z⟫`. This is what "Euclidean" + adds to "formally real"; `EuclideanJordan/Order.lean` carries the same condition as the hypothesis + `hassoc`. -/ + inner_assoc : ∀ x y z : J, inner ℝ (x * y) z = inner ℝ y (x * z) + +namespace EuclideanJordanAlgebra + +variable {J : Type*} [NormedAddCommGroup J] [InnerProductSpace ℝ J] [EuclideanJordanAlgebra J] + +/-- Left multiplication by `0` is `0` — the one ring axiom the class does not state, obtained +from additivity at `(0, 0, a)`. -/ +theorem zero_mul' (a : J) : (0 : J) * a = 0 := by + have h : (0 : J) * a + (0 : J) * a = (0 : J) * a + 0 := by + rw [add_zero, ← add_mul, add_zero] + exact add_left_cancel h + +/-- The ring structure, built on the **ambient** additive group. -/ +instance instNonUnitalNonAssocCommRing : NonUnitalNonAssocCommRing J := + { (inferInstance : AddCommGroup J), (inferInstance : Mul J) with + left_distrib := fun a b c => by + rw [mul_comm a (b + c), add_mul, mul_comm b a, mul_comm c a] + right_distrib := add_mul + zero_mul := zero_mul' + mul_zero := fun a => by rw [mul_comm, zero_mul'] + mul_comm := mul_comm } + +/-- Mathlib's Jordan class. Its field `lmul_comm_rmul_rmul` is oriented +`a ∘ b ∘ (a ∘ a) = a ∘ (b ∘ (a ∘ a))`, which is the class's `jordan` field read through +commutativity twice. -/ +instance instIsCommJordan : IsCommJordan J := + ⟨fun a b => by rw [mul_comm (a * b) (a * a), ← jordan a b, mul_comm (a * a) b]⟩ + +instance instIsScalarTower : IsScalarTower ℝ J J := ⟨smul_mul⟩ + +/-- Required by `Submodule`-valued and `NonUnitalSubalgebra`-valued subobject constructions +downstream; do not remove because nothing in this file uses it. -/ +instance instSMulCommClass : SMulCommClass ℝ J J := + ⟨fun r x y => by + change r • (x * y) = x * (r • y) + rw [mul_comm x (r • y), smul_mul, mul_comm y x]⟩ + +theorem mul_one' (x : J) : x * (1 : J) = x := by rw [mul_comm, one_mul] + +/-- **The inner product is a trace form.** `⟪x ∘ y, 1⟫ = ⟪x, y⟫`: one application of +`inner_assoc` against the unit. So the linear functional `z ↦ ⟪z, 1⟫` plays the role the +article's `tr` plays, and the inner product's positive-definiteness is available as +positive-definiteness of that form on products. It is *not* claimed that this functional is the +Jordan trace — see the module docstring. -/ +theorem inner_mul_one (x y : J) : (inner ℝ (x * y) (1 : J) : ℝ) = inner ℝ x y := by + rw [inner_assoc x y 1, mul_one' x, real_inner_comm] + +/-- **Formal reality, from the inner product.** Unconditional on the dimension — see the +module docstring. -/ +instance instIsFormallyReal : IsFormallyReal J := by + refine ⟨fun {ι} s f hsum i hi => ?_⟩ + have hz : (∑ j ∈ s, (inner ℝ (f j) (f j) : ℝ)) = 0 := by + have h0 : (inner ℝ (∑ j ∈ s, f j * f j) (1 : J) : ℝ) = 0 := by rw [hsum, inner_zero_left] + rw [sum_inner] at h0 + simpa only [fun x : J => inner_mul_one x x] using h0 + have hnn : ∀ j ∈ s, (0 : ℝ) ≤ inner ℝ (f j) (f j) := fun _ _ => real_inner_self_nonneg + exact inner_self_eq_zero.mp ((Finset.sum_eq_zero_iff_of_nonneg hnn).mp hz i hi) + +/-- The associativity of the inner product in its other orientation, `⟪x ∘ y, z⟫ = ⟪x, y ∘ z⟫`, +obtained from the field by commuting the product. -/ +theorem inner_assoc' (x y z : J) : (inner ℝ (x * y) z : ℝ) = inner ℝ x (y * z) := by + rw [mul_comm x y, inner_assoc y x z] + +end EuclideanJordanAlgebra + +/-! ## The bridge to the bilinear-map vocabulary + +`EuclideanJordan/Order.lean`'s Euclidean section and `EuclideanJordan/Spectral.lean`'s interface + section state +everything over a bundled `m : J →ₗ[ℝ] J →ₗ[ℝ] J` carrying `hcomm`, `hjordan`, `hassoc` and a +`Fin k`-indexed formal-reality hypothesis. `jmulₗ` is the class's product in that vocabulary +and the five lemmas after it are exactly that hypothesis tuple, so a consumer of +`orderUnitSpaceOfBilinear`, `inner_left_coeff`, `isArchimedean_ofBilinear`, +`isSoS_iff_exists_sq` or `spectral_resolution_bilinear` supplies them by name rather than +rebuilding them. -/ + +/-- The Jordan product of a `EuclideanJordanAlgebra` as a bundled bilinear map. -/ +def jmulₗ (J : Type*) [NormedAddCommGroup J] [InnerProductSpace ℝ J] + [EuclideanJordanAlgebra J] : J →ₗ[ℝ] J →ₗ[ℝ] J := + LinearMap.mk₂ ℝ (· * ·) EuclideanJordanAlgebra.add_mul EuclideanJordanAlgebra.smul_mul + (fun x y z => mul_add x y z) (fun r x y => mul_smul_comm r x y) + +variable {J : Type*} [NormedAddCommGroup J] [InnerProductSpace ℝ J] [EuclideanJordanAlgebra J] + +@[simp] theorem jmulₗ_apply (x y : J) : jmulₗ J x y = x * y := rfl + +/-- ★ The class and `EuclideanJordan/Bridge.lean`'s `ringOfBilinear` produce the **same** ring +structure, +definitionally. This is the precise sense in which the class does not introduce a second +multiplicative structure alongside the one the existing layer runs on. -/ +theorem ringOfBilinear_jmulₗ : + ringOfBilinear (jmulₗ J) EuclideanJordanAlgebra.mul_comm = + EuclideanJordanAlgebra.instNonUnitalNonAssocCommRing (J := J) := rfl + +theorem jmulₗ_comm (x y : J) : jmulₗ J x y = jmulₗ J y x := EuclideanJordanAlgebra.mul_comm x y + +theorem jmulₗ_jordan (a b : J) : + jmulₗ J (jmulₗ J a b) (jmulₗ J a a) = jmulₗ J a (jmulₗ J b (jmulₗ J a a)) := + IsCommJordan.lmul_comm_rmul_rmul a b + +theorem jmulₗ_inner_assoc (x y z : J) : + (inner ℝ (jmulₗ J x y) z : ℝ) = inner ℝ y (jmulₗ J x z) := + EuclideanJordanAlgebra.inner_assoc x y z + +theorem jmulₗ_one_mul (y : J) : jmulₗ J 1 y = y := EuclideanJordanAlgebra.one_mul y + +/-- Formal reality in the `Fin k` form `spectral_resolution_bilinear` and +`isFormallyReal_of_fin` take. -/ +theorem jmulₗ_formallyReal (k : ℕ) (f : Fin k → J) (h : (∑ i, jmulₗ J (f i) (f i)) = 0) + (i : Fin k) : f i = 0 := + IsFormallyReal.eq_zero_of_sum_mul_self Finset.univ f h i (Finset.mem_univ i) + +/-! ## The existing layer, restated over the class -/ + +/-- **The spectral theorem with completeness, over the class.** `EuclideanJordan/Spectral.lean`'s +`spectral_resolution_complete` carries the unit as an explicit hypothesis `he : ∀ y, e ∘ y = y` +because it has no `One`; the class supplies it. -/ +theorem spectral_resolution_complete' [FiniteDimensional ℝ J] (x : J) : + ∃ (n : ℕ) (c : Fin n → J) (lam : Fin n → ℝ), + IsOrthIdemFamily c ∧ (∑ i, c i) = 1 ∧ x = ∑ i, lam i • c i := + spectral_resolution_complete 1 EuclideanJordanAlgebra.one_mul x + +/-- **The Peirce decomposition at a single idempotent, over the class.** +`EuclideanJordan/Peirce.lean`'s +`peirce_add_add` needs no idempotency hypothesis: the three projections sum to the identity for +every `c`. -/ +theorem peirce_add_add' (c y : J) : peirceOne c y + peirceHalf c y + peirceZero c y = y := + peirce_add_add c y + +end EuclideanJordan diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/Connection.lean b/LeanPool/EuclideanJordan/EuclideanJordan/Connection.lean new file mode 100644 index 0000000000..3525a02420 --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/Connection.lean @@ -0,0 +1,543 @@ +/- +Copyright (c) 2026 Bryan Ehrlich. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Bryan Ehrlich +-/ +module + +public import LeanPool.EuclideanJordan.EuclideanJordan.FramePeirceMul + + + +/-! +# Connections between frame blocks + +`EuclideanJordan/FramePeirceMul.lean` gives the multiplication table of a Jordan frame's blocks. + This file +proves the two identities that turn that table into an *algebra*, and they are the engine of +Jacobson coordinatization. + +## The two identities + +**(1) An off-diagonal block element squares to a multiple of `pᵢ + pⱼ`.** +`frameBlockRaw_mul_self_eq` already gives `x ∘ x = a • pᵢ + b • pⱼ` for `x ∈ V_{ij}`. Here +`a = b` (`frameBlockRaw_sq_coeff_eq`), so the square is a multiple of the *idempotent* +`pᵢ + pⱼ` and its coefficient is a quadratic form on the block (`exists_sq_smul`, +`sq_eq_inner_smul`). + +★ **The proof is power associativity, not the trace form.** Computing `x⁴` as `x² ∘ x²` and as +`x ∘ (x ∘ x²)` — the same element, by `EuclideanJordan/PowerAssoc.lean`'s `jpow_mul_jpow` — gives +`a² = a(a+b)/2` and `b² = b(a+b)/2` after pairing against `pᵢ` and `pⱼ`. Adding those, +`a² + b² = (a+b)²/2 = 2ab`, so `(a - b)² = 0`. The inner product enters only to *read off* +those two scalar equations from an equation between elements; the identity `a = b` itself is +algebraic. + +**(2) `x ∘ (x ∘ y) = (a/4) • y` for `x ∈ V_{ij}` and `y ∈ V_{jk}`** (`block_sq_act`). So `2 L_x` +restricted to `V_{jk}` squares to the scalar `a`. + +★ **The plain Jordan identity gives nothing here.** Both sides of `x ∘ (x² ∘ y) = x² ∘ (x ∘ y)` +reduce to `½ a • (x ∘ y)` using only the eigenvalue rules, so it is satisfied for *any* value of +`x ∘ (x ∘ y)`. What works is the fully **linearised** Jordan identity — Mathlib's +`two_nsmul_lie_lmul_lmul_add_add_eq_zero`, restated pointwise here as `lin_jordan` — at +`(x, x, y)`, evaluated at `pⱼ`. Six terms; `x ∘ y ∈ V_{ik}` is annihilated by `pⱼ`, `pⱼ` halves +`x` and `y`, and what survives is `-(x ∘ (x ∘ y)) + (a/4) • y = 0`. + +## Connectors + +A **connector** on `(i, j)` is a `c ∈ V_{ij}` with `c ∘ c = pᵢ + pⱼ`. A nonzero block has one +(`exists_isConnector`: normalise by `a^{-1/2}`), and identity (2) at `a = 1` makes +`y ↦ 2 (c ∘ y)` an involution, hence a linear equivalence `V_{jk} ≃ V_{ik}` (`connEquiv`). So +connected blocks have the same dimension, connectivity is transitive, and a block is nonzero +exactly when it carries a connector. + +The **composition law** `block_mul_sq` says the coefficients multiply: if `x ∘ x = a • (pᵢ + pⱼ)` +and `y ∘ y = b • (pⱼ + p_k)` then `(2 (x ∘ y)) ∘ (2 (x ∘ y)) = (ab) • (pᵢ + p_k)`. It follows +from (2) by pairing, once `⟪pᵢ, pᵢ⟫ = ⟪pⱼ, pⱼ⟫`. ★ That identification is **not** unconditional: +`inner_p_eq_of_sq` gets it from (1) — the two pairings of `x ∘ x` both return `½‖x‖²`, so +`a τᵢ = a τⱼ` — and then needs `a ≠ 0` to cancel. `V_{ij} = 0` really does leave `τᵢ` and `τⱼ` +unrelated, and `block_mul_sq` disposes of that case separately rather than through the +identification. + +## Scope + +★ `rank J = n` is **not** available and nothing here is a step towards it; a `JordanFrame J n` is +carried as data and its cardinality bounds the rank from below, nothing more. + +★ Nothing here says a frame *is* connected. Connectors are hypotheses everywhere they appear. +The statement "a simple `J` has every block nonzero" is **not proved anywhere in this +library**, which has no notion of a Jordan ideal to state simplicity with. +-/ + +@[expose] public section + +noncomputable section + +namespace EuclideanJordan + +open EuclideanJordanAlgebra + +variable {J : Type*} [NormedAddCommGroup J] [InnerProductSpace ℝ J] [EuclideanJordanAlgebra J] +variable {n : ℕ} + +/-! ## The linearised Jordan identity -/ + +/-- **The fully linearised Jordan identity, evaluated at a point.** + +Mathlib's `two_nsmul_lie_lmul_lmul_add_add_eq_zero` states +`2 • (⁅L a, L (b∘c)⁆ + ⁅L b, L (c∘a)⁆ + ⁅L c, L (a∘b)⁆) = 0` in `AddMonoid.End J`; this is that +equation applied to `w`, with the `2 •` divided out (legitimate because `J` is a real vector +space). -/ +theorem lin_jordan (a b c w : J) : + (a * ((b * c) * w) - (b * c) * (a * w)) + + (b * ((c * a) * w) - (c * a) * (b * w)) + + (c * ((a * b) * w) - (a * b) * (c * w)) = 0 := by + have H := two_nsmul_lie_lmul_lmul_add_add_eq_zero (A := J) a b c + have H' := DFunLike.congr_fun H w + simp only [Ring.lie_def, AddMonoid.End.mulLeft] at H' + have h2 : (2 : ℕ) • ((a * (b * c * w) - b * c * (a * w)) + (b * (c * a * w) - c * a * (b * w)) + + (c * (a * b * w) - a * b * (c * w))) = 0 := H' + have h3 : ((2 : ℕ) : ℝ) • ((a * (b * c * w) - b * c * (a * w)) + + (b * (c * a * w) - c * a * (b * w)) + (c * (a * b * w) - a * b * (c * w))) = 0 := by + rw [Nat.cast_smul_eq_nsmul]; exact h2 + rcases smul_eq_zero.mp h3 with h | h + · norm_num at h + · exact h + +/-! ## The frame in the inner product -/ + +/-- Distinct frame members are orthogonal in the inner product, not merely in the algebra: +`⟪pᵢ, pⱼ⟫ = ⟪pᵢ ∘ pⱼ, 1⟫ = 0` by `EuclideanJordan/Class.lean`'s `inner_mul_one`. -/ +theorem inner_p_p_of_ne (F : JordanFrame J n) {i j : Fin n} (hij : i ≠ j) : + (inner ℝ (F.p i) (F.p j) : ℝ) = 0 := by + rw [← inner_mul_one (F.p i) (F.p j), F.orthIdem.orth i j hij, inner_zero_left] + +/-- `⟪pᵢ, pᵢ⟫ > 0` — the frame members are nonzero and the inner product is definite. -/ +theorem inner_p_self_pos (F : JordanFrame J n) (i : Fin n) : + 0 < (inner ℝ (F.p i) (F.p i) : ℝ) := + real_inner_self_pos.mpr (F.p_ne_zero i) + +/-- Pairing an element of `V_{ij}` against `pᵢ`: `⟪x ∘ x, pᵢ⟫ = ½‖x‖²`. -/ +theorem inner_sq_p_of_mem (F : JordanFrame J n) {i j : Fin n} (hij : i ≠ j) {x : J} + (hx : x ∈ frameBlockRaw F i j) : + (inner ℝ (x * x) (F.p i) : ℝ) = (2 : ℝ)⁻¹ * (inner ℝ x x : ℝ) := by + rw [inner_assoc x x (F.p i), _root_.mul_comm x (F.p i), + frameBlockRaw_mul_left_half F hij hx, real_inner_smul_right] + +/-! ## The square of an off-diagonal element + +★ `EuclideanJordan/FramePeirceMul.lean`'s `frameBlockRaw_mul_self_eq` gives `x ∘ x = a • pᵢ + b + • pⱼ`. +This section proves `a = b`, so that the square is a multiple of the *idempotent* `pᵢ + pⱼ` +and the coefficient is a quadratic form. The argument is power associativity, not the trace +form: `x⁴` computed as `x² ∘ x²` and as `x ∘ (x ∘ x²)` gives `a² = a(a+b)/2` and +`b² = b(a+b)/2`, whence `(a - b)² = 0`. -/ + +section Square + +variable [FiniteDimensional ℝ J] + +omit [FiniteDimensional ℝ J] in +/-- `x⁴ = x² ∘ x²` and `x⁴ = x ∘ (x ∘ x²)` agree — `EuclideanJordan/PowerAssoc.lean`'s +`jpow_mul_jpow` at +`(1, 1)`, unfolded. -/ +theorem sq_mul_sq_eq (x : J) : (x * x) * (x * x) = x * (x * (x * x)) := by + have h := jpow_mul_jpow x 1 1 + simpa using h + +omit [FiniteDimensional ℝ J] in +/-- **The two coefficients of `x ∘ x` on `V_{ij}` are equal.** -/ +theorem frameBlockRaw_sq_coeff_eq (F : JordanFrame J n) {i j : Fin n} (hij : i ≠ j) {x : J} + (hx : x ∈ frameBlockRaw F i j) {a b : ℝ} (hab : x * x = a • F.p i + b • F.p j) : + a = b := by + have hxi : F.p i * x = (2 : ℝ)⁻¹ • x := frameBlockRaw_mul_left_half F hij hx + have hxj : F.p j * x = (2 : ℝ)⁻¹ • x := frameBlockRaw_mul_right_half F hij hx + -- `x ∘ (x ∘ x) = ((a + b)/2) • x` + have hcube : x * (x * x) = ((a + b) / 2) • x := by + rw [hab] + simp only [mul_add, mul_smul_comm, _root_.mul_comm x (F.p i), _root_.mul_comm x (F.p j), + hxi, hxj] + module + -- `x ∘ (x ∘ (x ∘ x)) = ((a + b)/2) • (a • pᵢ + b • pⱼ)` + have hquart : x * (x * (x * x)) = ((a + b) / 2 * a) • F.p i + ((a + b) / 2 * b) • F.p j := by + rw [hcube, mul_smul_comm, hab] + module + -- `(x ∘ x) ∘ (x ∘ x) = a² • pᵢ + b² • pⱼ` + have hsqsq : (x * x) * (x * x) = (a * a) • F.p i + (b * b) • F.p j := by + rw [hab] + simp only [_root_.add_mul, mul_add, smul_mul, mul_smul_comm, F.orthIdem.idem i, + F.orthIdem.idem j, F.orthIdem.orth i j hij, F.orthIdem.orth j i (Ne.symm hij)] + module + have hkey : (a * a) • F.p i + (b * b) • F.p j + = ((a + b) / 2 * a) • F.p i + ((a + b) / 2 * b) • F.p j := by + rw [← hsqsq, ← hquart]; exact sq_mul_sq_eq x + have hτi : (0 : ℝ) < inner ℝ (F.p i) (F.p i) := inner_p_self_pos F i + have hτj : (0 : ℝ) < inner ℝ (F.p j) (F.p j) := inner_p_self_pos F j + have hij' : (inner ℝ (F.p i) (F.p j) : ℝ) = 0 := inner_p_p_of_ne F hij + have hji' : (inner ℝ (F.p j) (F.p i) : ℝ) = 0 := inner_p_p_of_ne F (Ne.symm hij) + have hA : a * a = (a + b) / 2 * a := by + have := congrArg (fun z : J => (inner ℝ z (F.p i) : ℝ)) hkey + simp only [inner_add_left, real_inner_smul_left, hji'] at this + field_simp [hij', hji'] at this + nlinarith [this, hτi] + have hB : b * b = (a + b) / 2 * b := by + have := congrArg (fun z : J => (inner ℝ z (F.p j) : ℝ)) hkey + simp only [inner_add_left, real_inner_smul_left, hij'] at this + field_simp [hij', hji'] at this + nlinarith [this, hτj] + have hsq : (a - b) ^ 2 = 0 := by nlinarith [hA, hB] + have := pow_eq_zero_iff (n := 2) (by norm_num) |>.mp hsq + linarith [this] + +/-- **`x ∘ x = a • (pᵢ + pⱼ)` for `x ∈ V_{ij}`**, with `a ≥ 0` and `a = 0` only at `x = 0`. + +The idempotent `pᵢ + pⱼ` is the unit of the Peirce subalgebra the block lives in, so this says +the square of a block element is a nonnegative multiple of that unit. -/ +theorem exists_sq_smul (F : JordanFrame J n) {i j : Fin n} (hij : i ≠ j) {x : J} + (hx : x ∈ frameBlockRaw F i j) : + ∃ a : ℝ, x * x = a • (F.p i + F.p j) + ∧ a * (inner ℝ (F.p i) (F.p i) : ℝ) = (2 : ℝ)⁻¹ * (inner ℝ x x : ℝ) := by + obtain ⟨a, b, hab⟩ := frameBlockRaw_mul_self_eq F hij hx hx + have hba : a = b := frameBlockRaw_sq_coeff_eq F hij hx hab + subst hba + refine ⟨a, by rw [hab]; module, ?_⟩ + have h := inner_sq_p_of_mem F hij hx + rw [hab] at h + simp only [inner_add_left, real_inner_smul_left, inner_p_p_of_ne F (Ne.symm hij)] at h + linarith [h] + +end Square + +/-! ## The key operator identity -/ + +/-- **`x ∘ (x ∘ y) = (a/4) • y`** for `x ∈ V_{ij}`, `y ∈ V_{jk}` with `x ∘ x = a • (pᵢ + pⱼ)`. + +This is the whole engine of coordinatization: it says `2 L_x` restricted to `V_{jk}` squares to +the scalar `a`, so a *normalised* `x` gives an involution `V_{jk} ≃ V_{ik}`, and it is what makes +the coordinate product multiplicative on norms. + +The proof is `lin_jordan` at `(x, x, y)` evaluated at `pⱼ`. Every term collapses: +`x ∘ y ∈ V_{ik}` is annihilated by `pⱼ`, `pⱼ` halves both `x` and `y`, and `x ∘ x` acts on `pⱼ` +as `a` and on `y` as `a/2`. What survives is `-(x ∘ (x ∘ y)) + (a/4) • y = 0`. + +★ The plain (unlinearised) Jordan identity gives **nothing** here: both of its sides reduce to +`½ a • (x ∘ y)` by the eigenvalue rules alone. The linearisation is essential. -/ +theorem block_sq_act (F : JordanFrame J n) {i j k : Fin n} (hij : i ≠ j) (hjk : j ≠ k) + (hik : i ≠ k) {x y : J} (hx : x ∈ frameBlockRaw F i j) (hy : y ∈ frameBlockRaw F j k) + {a : ℝ} (hxx : x * x = a • (F.p i + F.p j)) : + x * (x * y) = (a / 4) • y := by + have hxy : x * y ∈ frameBlockRaw F i k := frameBlockRaw_mul_middle F hij hjk hik hx hy + have hpjxy : (x * y) * F.p j = 0 := by + rw [_root_.mul_comm] + exact frameBlockRaw_mul_eq_zero F (Ne.symm hij) hjk hxy + have hpjx : x * F.p j = (2 : ℝ)⁻¹ • x := by + rw [_root_.mul_comm]; exact frameBlockRaw_mul_right_half F hij hx + have hpjy : y * F.p j = (2 : ℝ)⁻¹ • y := by + rw [_root_.mul_comm]; exact frameBlockRaw_mul_left_half F hjk hy + have hpiy : F.p i * y = 0 := frameBlockRaw_mul_eq_zero F hij hik hy + have hxxpj : (x * x) * F.p j = a • F.p j := by + rw [hxx] + simp only [smul_mul, _root_.add_mul, F.orthIdem.idem j, F.orthIdem.orth i j hij] + module + have hxxy : (x * x) * y = (a / 2) • y := by + rw [hxx] + simp only [smul_mul, _root_.add_mul, hpiy, frameBlockRaw_mul_left_half F hjk hy] + module + have H := lin_jordan x x y (F.p j) + rw [_root_.mul_comm y x, hpjxy, hpjx, hxxpj] at H + simp only [mul_zero, mul_smul_comm, hpjy, hxxy, _root_.mul_comm (x * y) x] at H + linear_combination (norm := module) -H + +/-! ## Consequences of the square rule -/ + +section SquareConsequences + +/-- Pairing `x ∘ x = a • (pᵢ + pⱼ)` against `pᵢ`. -/ +theorem sq_coeff_left (F : JordanFrame J n) {i j : Fin n} (hij : i ≠ j) {x : J} + (hx : x ∈ frameBlockRaw F i j) {a : ℝ} (hxx : x * x = a • (F.p i + F.p j)) : + a * (inner ℝ (F.p i) (F.p i) : ℝ) = (2 : ℝ)⁻¹ * (inner ℝ x x : ℝ) := by + have h := inner_sq_p_of_mem F hij hx + rw [hxx] at h + simp only [real_inner_smul_left, inner_add_left, inner_p_p_of_ne F (Ne.symm hij)] at h + linarith [h] + +/-- Pairing `x ∘ x = a • (pᵢ + pⱼ)` against `pⱼ`. -/ +theorem sq_coeff_right (F : JordanFrame J n) {i j : Fin n} (hij : i ≠ j) {x : J} + (hx : x ∈ frameBlockRaw F i j) {a : ℝ} (hxx : x * x = a • (F.p i + F.p j)) : + a * (inner ℝ (F.p j) (F.p j) : ℝ) = (2 : ℝ)⁻¹ * (inner ℝ x x : ℝ) := by + have hx' : x ∈ frameBlockRaw F j i := (frameBlockRaw_comm F i j) ▸ hx + have h := inner_sq_p_of_mem F (Ne.symm hij) hx' + rw [hxx] at h + simp only [real_inner_smul_left, inner_add_left, inner_p_p_of_ne F hij] at h + linarith [h] + +/-- A block element whose square vanishes is itself zero — positive-definiteness, not +formal reality. -/ +theorem eq_zero_of_sq_coeff_zero (F : JordanFrame J n) {i j : Fin n} (hij : i ≠ j) {x : J} + (hx : x ∈ frameBlockRaw F i j) (hxx : x * x = (0 : ℝ) • (F.p i + F.p j)) : x = 0 := by + have h := sq_coeff_left F hij hx hxx + rw [zero_mul] at h + have h0 : (inner ℝ x x : ℝ) = 0 := by linarith [h] + exact inner_self_eq_zero (𝕜 := ℝ) |>.mp h0 + +/-- **`⟪pᵢ, pᵢ⟫ = ⟪pⱼ, pⱼ⟫` as soon as `V_{ij}` contains a nonzero element.** + +The two pairings of `x ∘ x = a • (pᵢ + pⱼ)` both return `½‖x‖²`, so `a τᵢ = a τⱼ`, and `a ≠ 0` +because `a τᵢ = ½‖x‖² > 0`. This is what makes the coordinate norm form below unambiguous: +the normalisation constant is the same at every index a connection reaches. -/ +theorem inner_p_eq_of_sq (F : JordanFrame J n) {i j : Fin n} (hij : i ≠ j) {x : J} + (hx : x ∈ frameBlockRaw F i j) {a : ℝ} (hxx : x * x = a • (F.p i + F.p j)) (ha : a ≠ 0) : + (inner ℝ (F.p i) (F.p i) : ℝ) = (inner ℝ (F.p j) (F.p j) : ℝ) := + mul_left_cancel₀ ha ((sq_coeff_left F hij hx hxx).trans (sq_coeff_right F hij hx hxx).symm) + +end SquareConsequences + +/-! ## The composition law on the blocks + +★ This is the result the coordinate algebra is built out of: the blocks compose, so a +normalised element of `V_{ij}` acts on `V_{jk}` as an isometry up to the scalar `a`. -/ + +section Composition + +variable [FiniteDimensional ℝ J] + +omit [FiniteDimensional ℝ J] in +/-- **`‖x ∘ y‖² = (a/4)‖y‖²`** for `x ∈ V_{ij}`, `y ∈ V_{jk}`: `block_sq_act` paired against +`y` through the associativity of the inner product. -/ +theorem inner_mul_self_of_block (F : JordanFrame J n) {i j k : Fin n} (hij : i ≠ j) (hjk : j ≠ k) + (hik : i ≠ k) {x y : J} (hx : x ∈ frameBlockRaw F i j) (hy : y ∈ frameBlockRaw F j k) + {a : ℝ} (hxx : x * x = a • (F.p i + F.p j)) : + (inner ℝ (x * y) (x * y) : ℝ) = a / 4 * (inner ℝ y y : ℝ) := by + rw [inner_assoc x y (x * y), block_sq_act F hij hjk hik hx hy hxx, real_inner_smul_right] + +/-- **The composition law.** For `x ∈ V_{ij}` and `y ∈ V_{jk}` with `x ∘ x = a • (pᵢ + pⱼ)` and +`y ∘ y = b • (pⱼ + p_k)`, the product `2 (x ∘ y) ∈ V_{ik}` satisfies + + `(2 (x ∘ y)) ∘ (2 (x ∘ y)) = (a b) • (pᵢ + p_k)`. + +So the coefficient of the square — the norm form of the coordinate algebra — is multiplicative +under the doubled product `x ⊙ y = 2 (x ∘ y)`. Everything is bookkeeping around +`block_sq_act` except the identification `τᵢ = τⱼ`, which `inner_p_eq_of_sq` supplies. -/ +theorem block_mul_sq (F : JordanFrame J n) {i j k : Fin n} (hij : i ≠ j) (hjk : j ≠ k) + (hik : i ≠ k) {x y : J} (hx : x ∈ frameBlockRaw F i j) (hy : y ∈ frameBlockRaw F j k) + {a b : ℝ} (hxx : x * x = a • (F.p i + F.p j)) (hyy : y * y = b • (F.p j + F.p k)) : + ((2 : ℝ) • (x * y)) * ((2 : ℝ) • (x * y)) = (a * b) • (F.p i + F.p k) := by + rcases eq_or_ne a 0 with ha | ha + · subst ha + have hx0 : x = 0 := eq_zero_of_sq_coeff_zero F hij hx hxx + rw [hx0, zero_mul'] + simp + have hz : (2 : ℝ) • (x * y) ∈ frameBlockRaw F i k := + Submodule.smul_mem _ _ (frameBlockRaw_mul_middle F hij hjk hik hx hy) + obtain ⟨c, hc, hcspec⟩ := exists_sq_smul F hik hz + have hτ : (inner ℝ (F.p i) (F.p i) : ℝ) = (inner ℝ (F.p j) (F.p j) : ℝ) := + inner_p_eq_of_sq F hij hx hxx ha + have hyn : b * (inner ℝ (F.p j) (F.p j) : ℝ) = (2 : ℝ)⁻¹ * (inner ℝ y y : ℝ) := + sq_coeff_left F hjk hy hyy + have hzn : (inner ℝ ((2 : ℝ) • (x * y)) ((2 : ℝ) • (x * y)) : ℝ) + = a * (inner ℝ y y : ℝ) := by + rw [real_inner_smul_left, real_inner_smul_right, + inner_mul_self_of_block F hij hjk hik hx hy hxx] + ring + rw [hzn] at hcspec + have hτi : (0 : ℝ) < inner ℝ (F.p i) (F.p i) := inner_p_self_pos F i + have hcab : c = a * b := by + have h1 : c * (inner ℝ (F.p i) (F.p i) : ℝ) = a * b * (inner ℝ (F.p i) (F.p i) : ℝ) := by + linear_combination hcspec - a * hyn - a * b * hτ + exact mul_right_cancel₀ (ne_of_gt hτi) h1 + rw [hc, hcab] + +end Composition + +/-! ## Connectors + +A **connector** for the pair `(i, j)` is an element `c ∈ V_{ij}` with `c ∘ c = pᵢ + pⱼ`: a +square root of the rank-two idempotent inside the block. It exists exactly when the block is +nonzero, and `y ↦ 2 (c ∘ y)` is then an involutive linear isomorphism `V_{jk} ≃ V_{ik}`. -/ + +section Connector + +/-- A square root of `pᵢ + pⱼ` inside `V_{ij}`. -/ +def IsConnector (F : JordanFrame J n) (i j : Fin n) (c : J) : Prop := + c ∈ frameBlockRaw F i j ∧ c * c = F.p i + F.p j + +theorem IsConnector.mem {F : JordanFrame J n} {i j : Fin n} {c : J} (h : IsConnector F i j c) : + c ∈ frameBlockRaw F i j := h.1 + +theorem IsConnector.sq {F : JordanFrame J n} {i j : Fin n} {c : J} (h : IsConnector F i j c) : + c * c = F.p i + F.p j := h.2 + +theorem IsConnector.sq' {F : JordanFrame J n} {i j : Fin n} {c : J} (h : IsConnector F i j c) : + c * c = (1 : ℝ) • (F.p i + F.p j) := by rw [h.sq, one_smul] + +/-- A connector is symmetric in its two indices. -/ +theorem IsConnector.symm {F : JordanFrame J n} {i j : Fin n} {c : J} (h : IsConnector F i j c) : + IsConnector F j i c := + ⟨(frameBlockRaw_comm F i j) ▸ h.mem, by rw [h.sq, add_comm]⟩ + +/-- **`c ∘ (c ∘ y) = ¼ • y` for a connector `c` on `(i, j)` and `y ∈ V_{jk}`** — `block_sq_act` +at `a = 1`. -/ +theorem IsConnector.act {F : JordanFrame J n} {i j k : Fin n} (hij : i ≠ j) (hjk : j ≠ k) + (hik : i ≠ k) {c : J} (hc : IsConnector F i j c) {y : J} (hy : y ∈ frameBlockRaw F j k) : + c * (c * y) = (4 : ℝ)⁻¹ • y := by + have h := block_sq_act F hij hjk hik hc.mem hy hc.sq' + rw [h]; norm_num + +/-- The transfer map `y ↦ 2 (c ∘ y)` attached to a connector. -/ +def connMap (c : J) : J →ₗ[ℝ] J := (2 : ℝ) • jmulₗ J c + +@[simp] theorem connMap_apply (c y : J) : connMap c y = (2 : ℝ) • (c * y) := rfl + +/-- The transfer map carries `V_{jk}` into `V_{ik}`. -/ +theorem connMap_mem {F : JordanFrame J n} {i j k : Fin n} (hij : i ≠ j) (hjk : j ≠ k) + (hik : i ≠ k) {c : J} (hc : IsConnector F i j c) {y : J} (hy : y ∈ frameBlockRaw F j k) : + connMap c y ∈ frameBlockRaw F i k := + Submodule.smul_mem _ _ (frameBlockRaw_mul_middle F hij hjk hik hc.mem hy) + +/-- **The transfer map is an involution on `V_{jk}`.** Applying it twice multiplies by +`4 c ∘ (c ∘ ·) = 1`. Note the two applications run in opposite directions, `V_{jk} → V_{ik}` +and then `V_{ik} → V_{jk}`, so the hypothesis is used at both index orders. -/ +theorem connMap_connMap {F : JordanFrame J n} {i j k : Fin n} (hij : i ≠ j) (hjk : j ≠ k) + (hik : i ≠ k) {c : J} (hc : IsConnector F i j c) {y : J} (hy : y ∈ frameBlockRaw F j k) : + connMap c (connMap c y) = y := by + rw [connMap_apply, connMap_apply, mul_smul_comm, smul_smul, + IsConnector.act hij hjk hik hc hy, smul_smul] + norm_num + +variable [FiniteDimensional ℝ J] + +/-- **A nonzero block has a connector.** Normalise: `x ∘ x = a • (pᵢ + pⱼ)` with `a > 0`, and +`c := a^{-1/2} • x` has `c ∘ c = pᵢ + pⱼ`. -/ +theorem exists_isConnector (F : JordanFrame J n) {i j : Fin n} (hij : i ≠ j) {x : J} + (hx : x ∈ frameBlockRaw F i j) (hx0 : x ≠ 0) : ∃ c : J, IsConnector F i j c := by + obtain ⟨a, ha, haspec⟩ := exists_sq_smul F hij hx + have hxx : (0 : ℝ) < inner ℝ x x := real_inner_self_pos.mpr hx0 + have hτi : (0 : ℝ) < inner ℝ (F.p i) (F.p i) := inner_p_self_pos F i + have hapos : 0 < a := by nlinarith [haspec, hxx, hτi] + refine ⟨(Real.sqrt a)⁻¹ • x, Submodule.smul_mem _ _ hx, ?_⟩ + rw [smul_mul, mul_smul_comm, ha, smul_smul, smul_smul] + have hcoef : (Real.sqrt a)⁻¹ * (Real.sqrt a)⁻¹ * a = 1 := by + have hs : Real.sqrt a * Real.sqrt a = a := Real.mul_self_sqrt hapos.le + have hs0 : Real.sqrt a ≠ 0 := ne_of_gt (Real.sqrt_pos.mpr hapos) + field_simp + linarith [hs] + rw [hcoef, one_smul] + +/-- **The transfer map preserves the square coefficient.** If `y ∘ y = b • (pⱼ + p_k)` then +`(2 (c ∘ y)) ∘ (2 (c ∘ y)) = b • (pᵢ + p_k)` — `block_mul_sq` at `a = 1`. -/ +theorem connMap_sq {F : JordanFrame J n} {i j k : Fin n} (hij : i ≠ j) (hjk : j ≠ k) + (hik : i ≠ k) {c : J} (hc : IsConnector F i j c) {y : J} (hy : y ∈ frameBlockRaw F j k) + {b : ℝ} (hyy : y * y = b • (F.p j + F.p k)) : + connMap c y * connMap c y = b • (F.p i + F.p k) := by + have h := block_mul_sq F hij hjk hik hc.mem hy hc.sq' hyy + rw [connMap_apply, h, _root_.one_mul] + +/-- The transfer map carries connectors to connectors: `V_{jk} ∋ d ↦ 2 (c ∘ d)` is a connector +for `(i, k)`. -/ +theorem IsConnector.transfer {F : JordanFrame J n} {i j k : Fin n} (hij : i ≠ j) (hjk : j ≠ k) + (hik : i ≠ k) {c d : J} (hc : IsConnector F i j c) (hd : IsConnector F j k d) : + IsConnector F i k (connMap c d) := + ⟨connMap_mem hij hjk hik hc hd.mem, by + rw [connMap_sq hij hjk hik hc hd.mem hd.sq', one_smul]⟩ + +end Connector + +/-! ## Transfer as an equivalence, and connectivity + +★ The transfer map is not merely a map: it is an involution, hence a linear **isomorphism** +`V_{jk} ≃ V_{ik}`. So a frame all of whose blocks are connected has all its off-diagonal blocks +of the same dimension — the "coordinate algebra" does not depend on which block is read. -/ + +section Transfer + +/-- `pᵢ + pⱼ ≠ 0` for `i ≠ j`: pair it against `pᵢ`. -/ +theorem pair_ne_zero (F : JordanFrame J n) {i j : Fin n} (hij : i ≠ j) : + F.p i + F.p j ≠ 0 := by + intro h + have := congrArg (fun z : J => (inner ℝ z (F.p i) : ℝ)) h + simp only [inner_add_left, inner_p_p_of_ne F (Ne.symm hij), add_zero, inner_zero_left] at this + exact absurd this (ne_of_gt (inner_p_self_pos F i)) + +/-- A connector is nonzero. -/ +theorem IsConnector.ne_zero {F : JordanFrame J n} {i j : Fin n} (hij : i ≠ j) {c : J} + (hc : IsConnector F i j c) : c ≠ 0 := by + intro h + have hsq := hc.sq + rw [h, zero_mul'] at hsq + exact pair_ne_zero F hij hsq.symm + +/-- **The transfer map as a linear equivalence `V_{jk} ≃ V_{ik}`.** Both directions are the +same map, read at the two index orders; `connMap_connMap` is both inverse laws. -/ +def connEquiv {F : JordanFrame J n} {i j k : Fin n} (hij : i ≠ j) (hjk : j ≠ k) (hik : i ≠ k) + {c : J} (hc : IsConnector F i j c) : + ↥(frameBlockRaw F j k) ≃ₗ[ℝ] ↥(frameBlockRaw F i k) where + toFun y := ⟨connMap c y, connMap_mem hij hjk hik hc y.2⟩ + map_add' y z := Subtype.ext (by simp only [Submodule.coe_add, map_add]) + map_smul' r y := Subtype.ext (by simp only [SetLike.val_smul, map_smul, RingHom.id_apply]) + invFun z := ⟨connMap c z, connMap_mem (Ne.symm hij) hik hjk hc.symm z.2⟩ + left_inv y := Subtype.ext (connMap_connMap hij hjk hik hc y.2) + right_inv z := Subtype.ext (connMap_connMap (Ne.symm hij) hik hjk hc.symm z.2) + +/-- **Connected blocks have the same dimension.** -/ +theorem finrank_frameBlockRaw_eq {F : JordanFrame J n} {i j k : Fin n} + (hij : i ≠ j) (hjk : j ≠ k) (hik : i ≠ k) {c : J} (hc : IsConnector F i j c) : + Module.finrank ℝ ↥(frameBlockRaw F j k) = Module.finrank ℝ ↥(frameBlockRaw F i k) := + (connEquiv hij hjk hik hc).finrank_eq + +/-- **Connectivity is transitive.** A connector on `(i,j)` and one on `(j,k)` produce one on +`(i,k)`; this is `IsConnector.transfer` stated as the graph-theoretic fact the simplicity +argument consumes. ★ What is *not* proved anywhere in this tree is the other half of that +argument — that a **simple** `J` has every block nonzero. -/ +theorem exists_isConnector_trans [FiniteDimensional ℝ J] {F : JordanFrame J n} {i j k : Fin n} + (hij : i ≠ j) (hjk : j ≠ k) (hik : i ≠ k) + (h₁ : ∃ c : J, IsConnector F i j c) (h₂ : ∃ d : J, IsConnector F j k d) : + ∃ e : J, IsConnector F i k e := by + obtain ⟨c, hc⟩ := h₁ + obtain ⟨d, hd⟩ := h₂ + exact ⟨connMap c d, IsConnector.transfer hij hjk hik hc hd⟩ + +/-- A block is nonzero exactly when it has a connector. -/ +theorem exists_isConnector_iff [FiniteDimensional ℝ J] (F : JordanFrame J n) {i j : Fin n} + (hij : i ≠ j) : + (∃ c : J, IsConnector F i j c) ↔ frameBlockRaw F i j ≠ ⊥ := by + constructor + · rintro ⟨c, hc⟩ hbot + exact hc.ne_zero hij ((Submodule.eq_bot_iff _).mp hbot c hc.mem) + · intro hbot + obtain ⟨x, hx, hx0⟩ := (Submodule.ne_bot_iff _).mp hbot + exact exists_isConnector F hij hx hx0 + +end Transfer + +/-! ## The square coefficient as a quadratic form + +The coefficient of `x ∘ x` on `V_{ij}` is pinned by the inner product, so it is a genuine +quadratic form on the block rather than a choice. This is the form the coordinate algebra +carries. -/ + +section QuadForm + +/-- The coefficient of a multiple of `pᵢ + pⱼ` is unique — pair against `pᵢ`. -/ +theorem smul_pair_inj (F : JordanFrame J n) {i j : Fin n} (hij : i ≠ j) {a b : ℝ} + (h : a • (F.p i + F.p j) = b • (F.p i + F.p j)) : a = b := by + have := congrArg (fun z : J => (inner ℝ z (F.p i) : ℝ)) h + simp only [real_inner_smul_left, inner_add_left, inner_p_p_of_ne F (Ne.symm hij), + add_zero] at this + exact mul_right_cancel₀ (ne_of_gt (inner_p_self_pos F i)) this + +variable [FiniteDimensional ℝ J] + +/-- **`x ∘ x = (‖x‖² / 2τᵢ) • (pᵢ + pⱼ)`** for `x ∈ V_{ij}`, where `τᵢ = ⟪pᵢ, pᵢ⟫`. The +coefficient supplied by `exists_sq_smul` is exactly this ratio. -/ +theorem sq_eq_inner_smul (F : JordanFrame J n) {i j : Fin n} (hij : i ≠ j) {x : J} + (hx : x ∈ frameBlockRaw F i j) : + x * x = ((inner ℝ x x : ℝ) / (2 * (inner ℝ (F.p i) (F.p i) : ℝ))) • (F.p i + F.p j) := by + obtain ⟨a, ha, haspec⟩ := exists_sq_smul F hij hx + have hτ : (0 : ℝ) < inner ℝ (F.p i) (F.p i) := inner_p_self_pos F i + have : a = (inner ℝ x x : ℝ) / (2 * (inner ℝ (F.p i) (F.p i) : ℝ)) := by + field_simp + linarith [haspec] + rw [ha, this] + +end QuadForm + +end EuclideanJordan diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/FormallyReal.lean b/LeanPool/EuclideanJordan/EuclideanJordan/FormallyReal.lean new file mode 100644 index 0000000000..83bb6c0647 --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/FormallyReal.lean @@ -0,0 +1,120 @@ +/- +Copyright (c) 2026 Bryan Ehrlich. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Bryan Ehrlich +-/ +module + +public import LeanPool.EuclideanJordan.EuclideanJordan.PowerAssoc + + + +/-! +# Formal reality, and the absence of nilpotents + +A Euclidean Jordan algebra is a finite-dimensional real Jordan algebra that is **formally +real**: a sum of squares vanishes only if every summand does. This file adds that hypothesis +as a mixin and draws the consequence the spectral theorem needs first — +**`nilpotent ⟹ zero`**. + +The nilpotence result is the first place in this development where **Albert's theorem does +real work**: `x^{k+1} ∘ x^{k+1} = x^{2k+2}` is a *product* identity, so without +`jpow_mul_jpow` there is no way to turn "some power vanishes" into "a square vanishes", +which is the only thing formal reality can see. + +## What is here + +* `IsFormallyReal` — the mixin, stated over a `Finset` sum so that it is the usual + many-summand condition rather than the two-summand special case. +* `eq_zero_of_mul_self_eq_zero` — the one-summand case: `x ∘ x = 0 ⟹ x = 0`. +* `eq_zero_of_jpow_eq_zero` — **no nilpotents**: if any power of `x` vanishes, `x = 0`. + +## What is not + +No trace form, no inner product, no finite-dimensionality, and **no spectral theorem**. The +spectral theorem additionally needs `ℝ[x]` to be finite-dimensional and reduced, and then the +classification of finite-dimensional reduced commutative `ℝ`-algebras. Formal reality is what +supplies "reduced"; the rest is not built. + +★ **The hypothesis has a carrier.** `EuclideanJordan/Witness.lean` carries +`instIsFormallyReal : IsFormallyReal (HermitianMat d 𝕜)`, so both theorems below are live on +`H_d(𝕜)` (`hermitian_eq_zero_of_jpow_eq_zero`); neither is conditional on a premise nothing +satisfies. + +★ The proof needed nothing new — `inner_self_nonneg` and `InnerProductCore.definite` are +vendored and `symmMul_self` says the Jordan square *is* the matrix square. What was missing was +the application, which is the third time in this arc that a residue named a hypothesis and a +conclusion whose connecting lemma was already in the tree (after row 35 and the FK fields). +**When a file's own docstring declares an exposure, try to close it before writing the sentence +that documents it.** +-/ + +@[expose] public section + +namespace EuclideanJordan + +open Finset + +/-- **Formal reality.** A sum of squares vanishes only if every summand does. Stated over an +arbitrary `Finset` index rather than for two elements: the two-summand version does not +obviously imply the general one in a non-associative setting, because `x ∘ x + y ∘ y` need +not itself be a square. -/ +class IsFormallyReal (J : Type*) [Mul J] [AddCommMonoid J] : Prop where + /-- A vanishing sum of squares has vanishing summands. -/ + eq_zero_of_sum_mul_self : ∀ {ι : Type} (s : Finset ι) (f : ι → J), + ∑ i ∈ s, f i * f i = 0 → ∀ i ∈ s, f i = 0 + +section Basic + +variable {J : Type*} [NonUnitalNonAssocCommRing J] [IsFormallyReal J] + +/-- The one-summand case: an element whose square vanishes is zero. -/ +theorem eq_zero_of_mul_self_eq_zero {x : J} (h : x * x = 0) : x = 0 := by + have := IsFormallyReal.eq_zero_of_sum_mul_self ({0} : Finset ℕ) (fun _ => x) (by simpa using h) + exact this 0 (Finset.mem_singleton_self 0) + +end Basic + +section Nilpotent + +variable {J : Type*} [NonUnitalNonAssocCommRing J] [IsCommJordan J] [Module ℝ J] + [IsFormallyReal J] + +omit [IsFormallyReal J] in +/-- If one power vanishes, every higher power does. Uses Albert's theorem to split +`x^{m+1} = x^{n+1} ∘ x^{m−n}`. -/ +theorem jpow_eq_zero_of_le {x : J} {n : ℕ} (h : jpow x n = 0) {m : ℕ} (hm : n ≤ m) : + jpow x m = 0 := by + rcases Nat.eq_or_lt_of_le hm with rfl | hlt + · exact h + · have hsplit : jpow x n * jpow x (m - n - 1) = jpow x m := by + rw [jpow_mul_jpow] + congr 1 + omega + rw [← hsplit, h, zero_mul] + +/-- **A formally real Jordan algebra has no nilpotents.** If any power of `x` vanishes then +`x = 0`. + +★ This is where Albert's theorem earns its place. Formal reality can only see *squares*, and +turning "some power vanishes" into "a square vanishes" is exactly the product identity +`jpow_mul_jpow` supplies. In this file's indexing (`jpow x n = x^{n+1}`) the descent from +`jpow x (k+1) = 0` runs: `jpow x (k+k+1) = 0` by `jpow_eq_zero_of_le`, that element **is** +`jpow x k * jpow x k`, so formal reality gives `jpow x k = 0`, and the index has strictly +dropped. -/ +theorem eq_zero_of_jpow_eq_zero {x : J} : ∀ (n : ℕ), jpow x n = 0 → x = 0 := by + intro n + induction n using Nat.strong_induction_on with + | _ n ih => + intro h + match n with + | 0 => simpa using h + | (k + 1) => + have hsq : jpow x k * jpow x k = 0 := by + rw [jpow_mul_jpow] + exact jpow_eq_zero_of_le h (by omega) + exact ih k (by omega) (eq_zero_of_mul_self_eq_zero hsq) + +end Nilpotent + +end EuclideanJordan diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/Frame.lean b/LeanPool/EuclideanJordan/EuclideanJordan/Frame.lean new file mode 100644 index 0000000000..59afbf4f9d --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/Frame.lean @@ -0,0 +1,181 @@ +/- +Copyright (c) 2026 Bryan Ehrlich. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Bryan Ehrlich +-/ +module + +public import LeanPool.EuclideanJordan.EuclideanJordan.Orthogonal +public import Mathlib.Algebra.BigOperators.Ring.Finset + + + +/-! +# Orthogonal idempotent families, and three Faraut–Korányi facts + +Three Faraut–Korányi facts about a Jordan frame, each of which a coalescence argument would +otherwise have to carry as a hypothesis: + +| content | derived here as | +| --- | --- | +| scalar-on-`range q` and `J₂(q)` operator-commute | `opCommute_scalarOn_frame` | +| `V_{ij} ⊆ J₂(pᵢ + pⱼ)` | `mem_J2_of_half_half` | +| `rᵢ = rⱼ ⟹ a(r)` is scalar on `range(pᵢ + pⱼ)` | `diagFamily_scalarOn` | + +**All three are theorems rather than hypotheses**, given `EuclideanJordan/PeirceMul.lean`. + +★ **But not all three are theorems of the same depth, and the first draft of this paragraph +claimed they were** ("all three are theorems of the Jordan identity"). Only +`opCommute_scalarOn_frame` uses the Jordan identity — through `EuclideanJordan/PeirceMul.lean`. The +other two do not use it at all: `mem_J2_of_half_half` is `1/2 + 1/2 = 1`, and +`diagFamily_scalarOn` is a `Finset` split. Their `omit` lines say so, and a reader should +take the FK content of this file to be **one** theorem plus two pieces of bookkeeping that +become available once the frame equations exist. The bookkeeping still has to be done; it +is just not where the difficulty is. + +## The hypotheses, stated exactly + +Nothing below is stated over a structure that bundles a frame. Each theorem takes as explicit +hypotheses exactly the frame equations it uses: + +* `p i * p i = p i` and `p i * p j = 0` for `i ≠ j` — bundled as `IsOrthIdemFamily`; +* where a diagonal element is involved, that it is presented as `∑ k, f k • p k`. + +★★ **A consumer that carries its Jordan product as a bundled bilinear map cannot apply these +lemmas without a bridge, and the reason is a typeclass diamond rather than a missing +theorem.** This layer uses the *typeclass* `Mul J` from `NonUnitalNonAssocCommRing` together +with Mathlib's `IsCommJordan`. A structure carrying the product as +`jordan : J →ₗ[ℝ] J →ₗ[ℝ] J` over `[NormedAddCommGroup J] [InnerProductSpace ℝ J]`, with +operator-commutation defined through that map, supplies a *different* `AddCommGroup J` from +the one under `NonUnitalNonAssocCommRing`. With both in scope `Module ℝ J` fails to synthesise +at `peirceOne`'s use site: it is an `AddCommGroup` diamond, not a gap in the mathematics. +`EuclideanJordan/Bridge.lean` resolves it by building the ring on the *ambient* additive group, +so only one `AddCommGroup` is ever in play. Concrete carriers are unaffected — +`EuclideanJordan/Witness.lean` uses both worlds on `HermitianMat`, where the two are the same + instance. + +★ **Completeness (`∑ p i = e`) is not assumed anywhere in this file.** None of the three +facts needs it; it is what the *spectral* theorem produces and what the rank argument +consumes. Keeping it out makes visible which results are independent of the spectral theorem. +-/ + +@[expose] public section + +namespace EuclideanJordan + +open Finset + +section Family + +variable {J : Type*} [NonUnitalNonAssocCommRing J] [IsCommJordan J] [Module ℝ J] +variable {ι : Type*} [Fintype ι] [DecidableEq ι] + +/-- A family of pairwise-orthogonal idempotents. Completeness is deliberately *not* part of +this definition — see the module docstring. -/ +structure IsOrthIdemFamily (p : ι → J) : Prop where + /-- Each member is idempotent. -/ + idem : ∀ i, p i * p i = p i + /-- Distinct members are orthogonal. -/ + orth : ∀ i j, i ≠ j → p i * p j = 0 + +namespace IsOrthIdemFamily + +variable {p : ι → J} + +omit [IsCommJordan J] [Module ℝ J] [Fintype ι] [DecidableEq ι] in +/-- **A sum over any subset of an orthogonal idempotent family is an idempotent.** In +particular `p i + p j` is, which is the rank-two block the results below run on. -/ +theorem sum_idem (hp : IsOrthIdemFamily p) (s : Finset ι) : + (∑ i ∈ s, p i) * (∑ i ∈ s, p i) = ∑ i ∈ s, p i := by + rw [Finset.sum_mul_sum] + refine Finset.sum_congr rfl fun i hi => ?_ + rw [Finset.sum_eq_single i (fun j hj hne => hp.orth i j (Ne.symm hne)) (fun h => absurd hi h)] + exact hp.idem i + +omit [IsCommJordan J] [Module ℝ J] [Fintype ι] [DecidableEq ι] in +/-- A member outside a subset is orthogonal to that subset's sum. -/ +theorem sum_mul_of_notMem (hp : IsOrthIdemFamily p) {s : Finset ι} {k : ι} (hk : k ∉ s) : + (∑ i ∈ s, p i) * p k = 0 := by + rw [Finset.sum_mul] + refine Finset.sum_eq_zero fun i hi => hp.orth i k ?_ + rintro rfl + exact hk hi + +end IsOrthIdemFamily + +end Family + +section Fields + +variable {J : Type*} [NonUnitalNonAssocCommRing J] [IsCommJordan J] [Module ℝ J] + [IsScalarTower ℝ J J] +variable {ι : Type*} [Fintype ι] [DecidableEq ι] {p : ι → J} + +omit [IsCommJordan J] [IsScalarTower ℝ J J] [Fintype ι] [DecidableEq ι] in +/-- **The block containment, derived.** An element halved by `p i` and by `p j` — +that is, an element of the coherence block `V_{ij}` — lies in `J₂(p i + p j)`. + +The proof is that `1/2 + 1/2 = 1`; the content is entirely in the *definition* of `V_{ij}` +as a joint half-eigenspace, which is what the Peirce theory of `EuclideanJordan/Peirce.lean` + licenses. -/ +theorem mem_J2_of_half_half {i j : ι} {x : J} (hi : p i * x = (2 : ℝ)⁻¹ • x) + (hj : p j * x = (2 : ℝ)⁻¹ • x) : (p i + p j) * x = x := by + rw [add_mul, hi, hj] + module + +omit [IsCommJordan J] [Module ℝ J] [IsScalarTower ℝ J J] [Fintype ι] [DecidableEq ι] in +/-- The complement of a rank-two block annihilates it: `(p i + p j) ∘ p k = 0` for +`k ∉ {i, j}`. -/ +theorem pair_mul_of_ne (hp : IsOrthIdemFamily p) {i j k : ι} (hki : k ≠ i) (hkj : k ≠ j) : + (p i + p j) * p k = 0 := by + rw [add_mul, hp.orth i k (Ne.symm hki), hp.orth j k (Ne.symm hkj), add_zero] + +omit [IsCommJordan J] in +/-- The off-block part of a diagonal family is annihilated by the block. -/ +theorem pair_mul_offblock (hp : IsOrthIdemFamily p) (f : ι → ℝ) (i j : ι) : + (p i + p j) * (∑ k ∈ univ \ {i, j}, f k • p k) = 0 := by + rw [Finset.mul_sum] + refine Finset.sum_eq_zero fun k hk => ?_ + have hk' := Finset.mem_sdiff.mp hk + have hki : k ≠ i := fun h => hk'.2 (by simp [h]) + have hkj : k ≠ j := fun h => hk'.2 (by simp [h]) + rw [mul_smul_comm', pair_mul_of_ne hp hki hkj, smul_zero] + +omit [IsCommJordan J] [IsScalarTower ℝ J J] in +/-- **The scalar-on-a-block decomposition, derived.** When two coordinates of `f` agree, the +diagonal family `∑ f k • p k` is *scalar on the range of* `p i + p j`: it splits as +`f i • (p i + p j)` plus a part the block annihilates. + +The decomposition is exhibited rather than assumed. -/ +theorem diagFamily_scalarOn (f : ι → ℝ) {i j : ι} (hij : i ≠ j) (h : f i = f j) : + ∑ k, f k • p k = f i • (p i + p j) + ∑ k ∈ univ \ {i, j}, f k • p k := by + have hsd := Finset.sum_sdiff (f := fun k => f k • p k) (Finset.subset_univ ({i, j} : Finset ι)) + rw [← hsd, Finset.sum_pair hij, ← h, ← smul_add] + abel + +omit [IsCommJordan J] [IsScalarTower ℝ J J] in +/-- The `i = j` case of `diagFamily_scalarOn`, for a consumer whose scalar-on-a-block +hypothesis carries no `i ≠ j` side condition. -/ +theorem diagFamily_scalarOn_self (f : ι → ℝ) (i : ι) : + ∑ k, f k • p k = (f i / 2) • (p i + p i) + ∑ k ∈ univ \ {i}, f k • p k := by + have hsd := Finset.sum_sdiff (f := fun k => f k • p k) (Finset.subset_univ ({i} : Finset ι)) + rw [← hsd, Finset.sum_singleton] + have : (f i / 2) • (p i + p i) = f i • p i := by module + rw [this] + abel + +omit [DecidableEq ι] in +/-- **The operator-commutation fact, derived.** For a +rank-two block `q = p i + p j` of an orthogonal idempotent family, a diagonal family with +`f i = f j` operator-commutes with every element of `J₂(q)`. -/ +theorem opCommute_scalarOn_frame (hp : IsOrthIdemFamily p) (f : ι → ℝ) {i j : ι} (hij : i ≠ j) + (h : f i = f j) {b : J} (hb : (p i + p j) * b = b) (w : J) : + (∑ k, f k • p k) * (b * w) = b * ((∑ k, f k • p k) * w) := by + classical + exact + opCommute_scalarOn (add_idem_of_orthogonal (hp.idem i) (hp.idem j) (hp.orth i j hij)) + (diagFamily_scalarOn f hij h) (pair_mul_offblock hp f i j) hb w + +end Fields + +end EuclideanJordan diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/FrameExists.lean b/LeanPool/EuclideanJordan/EuclideanJordan/FrameExists.lean new file mode 100644 index 0000000000..9793be3c06 --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/FrameExists.lean @@ -0,0 +1,278 @@ +/- +Copyright (c) 2026 Bryan Ehrlich. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Bryan Ehrlich +-/ +module + +public import LeanPool.EuclideanJordan.EuclideanJordan.Rank + + + +/-! +# Every nontrivial finite-dimensional Euclidean Jordan algebra carries a Jordan frame + +`EuclideanJordan/Rank.lean` defines `JordanFrame J n` — a complete family of `n` pairwise-orthogonal +primitive idempotents — and carries it as *data*, because a rank hypothesis supplies one. This + file proves such data always exists: `exists_jordanFrame`, by strong +induction on `Module.finrank ℝ J` down the Peirce decomposition of a nontrivial idempotent. + +The induction itself is the expected one. If `1` is primitive the one-element family +`fun _ : Fin 1 => (1 : J)` is a frame. Otherwise primitivity fails at its third clause, which + hands over an idempotent `c` with +`c ≠ 0` and `c ≠ 1`; `EuclideanJordan/PeirceSubalgebra.lean` makes `J₂(c)` and `J₀(c)` Euclidean + Jordan +algebras with units `c` and `1 - c` and drops the dimension at both; and the two frames obtained +from the induction hypothesis concatenate along `Fin.append`. + +## ★ The step the build plan missed + +The plan priced this module as "mostly `Fin n ⊕ Fin m ≃ Fin (n + m)` re-indexing, not +mathematics". The re-indexing is indeed routine — it is `exists_frame_of_split` below, five +`Fin.addCases` splits and a `Fin.sum_univ_add`, and it compiled on the first attempt. The +prediction is wrong about where the content is. Concatenating the two frames requires each +member to be primitive **in `J`**, and what the induction hypothesis supplies is primitivity +*inside* the subalgebra. Those differ: the ambient statement quantifies over every idempotent +`y` of `J` with `d ∘ y = y`, the subalgebra statement only over those that additionally lie in +`J₂(c)`. Closing the gap is exactly + +> `J₂(d) ⊆ J₂(c)` for `d` an idempotent of `J₂(c)`, + +which is `eigen_one_of_eigen_one` below, and which is in neither +`EuclideanJordan/PeirceSubalgebra.lean` nor `EuclideanJordan/Rank.lean`. + +★ **What makes it work is changing which idempotent one decomposes at.** Attacking it at `c` — +rewriting `c ∘ x` as `c ∘ (d ∘ x)` and trying to move `c` inwards — is circular, because moving +`c` past `d` is what needs the conclusion; `EuclideanJordan/PeirceMul.lean`'s + `mul_comm_of_eigen_one` only +relocates that difficulty: `mul_comm_of_eigen_one` at `(c, d, x)` yields +`c ∘ x = d ∘ (c ∘ x)`, which puts `c ∘ x` back inside `J₂(d)` rather than identifying it with +`x`. Decomposing at **`d`** instead makes it immediate: `d` annihilates `c - d` +(`mul_sub_eq_zero_of_eigen_one`, one line), so `c - d` lies in `J₀(d)` while `x` lies in +`J₂(d)`, and `EuclideanJordan/PeirceMul.lean`'s `eigen_one_mul_zero` — the rule that the two + *extreme* +Peirce components of a single idempotent annihilate each other — kills `(c - d) ∘ x` outright. +Then `c ∘ x = d ∘ x + (c - d) ∘ x = x`. (`c - d` is also idempotent, but that is neither +proved nor used here.) + +★ `eigen_one_of_eigen_one` does **not** need `c` to be idempotent. The hypothesis was written +into the first draft, the unused-variable linter flagged it, and it was deleted rather than +underscored. Nothing is claimed about the remaining three: `hd`, `hcd` and `hx` are each used +by the proof, but whether any of them could be dropped was not tested. `peirceOneSub_le` +restates the lemma as the submodule inequality, and *there* `hc` reappears — not because the +mathematics needs it but because `peirceOneSub` is indexed by an idempotency proof, so the +ambient carrier cannot be named without one. + +## Scope + + +★ The frame produced here carries **no claim about its cardinality**. `exists_jordanFrame` +existentially quantifies `n`, and `EuclideanJordan/Rank.lean`'s module docstring records why + `rank J = n` +is not available: `rank J` is a supremum over *all* orthogonal families of nonzero idempotents, +and bounding such a family by a frame's cardinality needs the frame Peirce decomposition or +frame conjugacy. Nothing here is a step towards it. Do not read `exists_jordanFrame` as +"`J` has rank `n`". +-/ + +@[expose] public section + +noncomputable section + +namespace EuclideanJordan + +universe u + +variable {J : Type*} [NormedAddCommGroup J] [InnerProductSpace ℝ J] [EuclideanJordanAlgebra J] + +/-! ## `J₂(d) ⊆ J₂(c)`, the step the induction turns on -/ + +/-- For an idempotent `d` of `J₂(c)`, the complement `c - d` lies in `J₀(d)`: two rewrites and +`d ∘ d = d`. Idempotency of `c` is not used. -/ +theorem mul_sub_eq_zero_of_eigen_one {c d : J} (hd : d * d = d) (hcd : c * d = d) : + d * (c - d) = 0 := by + rw [mul_sub, _root_.mul_comm d c, hcd, hd, sub_self] + +/-- ★ **`J₂(d) ⊆ J₂(c)` for `d` an idempotent of `J₂(c)`.** An element fixed by `d` is fixed by +`c`. + +The proof decomposes at `d`, not at `c` — see the module docstring. `c - d` sits in `J₀(d)` by +`mul_sub_eq_zero_of_eigen_one` and `x` sits in `J₂(d)` by hypothesis, so `eigen_one_mul_zero` +at `d` gives +`x ∘ (c - d) = 0`, and `c ∘ x = d ∘ x + (c - d) ∘ x = x + 0`. + +Idempotency of `c` is not required. -/ +theorem eigen_one_of_eigen_one {c d x : J} (hd : d * d = d) (hcd : c * d = d) + (hx : d * x = x) : c * x = x := by + have h0 : (c - d) * x = 0 := by + rw [_root_.mul_comm, eigen_one_mul_zero hd hx (mul_sub_eq_zero_of_eigen_one hd hcd)] + have h : c * x = d * x + (c - d) * x := by rw [← add_mul, add_sub_cancel] + rw [h, h0, hx, add_zero] + +/-- The submodule form of `eigen_one_of_eigen_one`. `hc` is present only because +`peirceOneSub` is indexed by an idempotency proof. -/ +theorem peirceOneSub_le {c d : J} (hc : c * c = c) (hd : d * d = d) (hcd : c * d = d) : + peirceOneSub hd ≤ peirceOneSub hc := + fun _x hx => eigen_one_of_eigen_one hd hcd hx + +/-! ## Primitivity transfers out of a Peirce subalgebra + +Primitivity inside `J₂(c)` (resp. `J₀(c)`) implies primitivity in `J`. Apart from the +restatement `peirceOneSub_le`, which nothing downstream consumes, this is where +`eigen_one_of_eigen_one` is used, and it is the reason the induction closes. -/ + +/-- Primitivity from a weakened third clause: an idempotent `d` fixed by `u` is primitive as +soon as its splitting condition is checked on those idempotents that `u` also fixes, because +`eigen_one_of_eigen_one` supplies that side condition for free. -/ +theorem isPrimitive_of_unit {u d : J} (hd : d * d = d) (hd0 : d ≠ 0) (hud : u * d = d) + (h : ∀ y : J, y * y = y → d * y = y → u * y = y → y = 0 ∨ y = d) : IsPrimitive d := + ⟨hd, hd0, fun y hy hdy => h y hy hdy (eigen_one_of_eigen_one hd hud hdy)⟩ + +/-- A primitive idempotent of `J₂(c)` is primitive in `J`. -/ +theorem isPrimitive_coe_of_peirceOne {c : J} (hc : c * c = c) {d : ↥(peirceOneSub hc)} + (hd : IsPrimitive d) : IsPrimitive (d : J) := by + refine isPrimitive_of_unit (congrArg Subtype.val hd.idem) + (fun h => hd.ne_zero (Subtype.ext (h.trans (ZeroMemClass.coe_zero _).symm))) d.2 ?_ + intro y hy hdy hcy + rcases hd.eq_zero_or_eq (d := ⟨y, hcy⟩) (Subtype.ext hy) (Subtype.ext hdy) with h | h + · exact Or.inl (congrArg Subtype.val h) + · exact Or.inr (congrArg Subtype.val h) + +/-- A primitive idempotent of `J₀(c)` is primitive in `J`. The unit fed to +`isPrimitive_of_unit` is `1 - c`, and the side condition `(1 - c) ∘ y = y` is membership in +`J₀(c)` read backwards. -/ +theorem isPrimitive_coe_of_peirceZero {c : J} (hc : c * c = c) {d : ↥(peirceZeroSub hc)} + (hd : IsPrimitive d) : IsPrimitive (d : J) := by + have hd2 : c * (d : J) = 0 := d.2 + have hud : ((1 : J) - c) * (d : J) = (d : J) := by + rw [sub_mul, EuclideanJordanAlgebra.one_mul, hd2, sub_zero] + refine isPrimitive_of_unit (congrArg Subtype.val hd.idem) + (fun h => hd.ne_zero (Subtype.ext (h.trans (ZeroMemClass.coe_zero _).symm))) hud ?_ + intro y hy hdy hcy + have hmem : y ∈ peirceZeroSub hc := by + rw [sub_mul, EuclideanJordanAlgebra.one_mul] at hcy + exact sub_eq_self.mp hcy + rcases hd.eq_zero_or_eq (d := ⟨y, hmem⟩) (Subtype.ext hy) (Subtype.ext hdy) with h | h + · exact Or.inl (congrArg Subtype.val h) + · exact Or.inr (congrArg Subtype.val h) + +/-! ## Concatenating the two frames -/ + +/-- **The frames of `J₂(c)` and `J₀(c)` concatenate to a frame of `J`.** Along `Fin.append`; +the four orthogonality cases are the two blocks' own orthogonality and, across the blocks, +`eigen_one_mul_zero` at `c`. Completeness adds the two units `c` and `1 - c`. -/ +theorem exists_frame_of_split {c : J} (hc : c * c = c) {n₁ n₀ : ℕ} + (F₁ : JordanFrame ↥(peirceOneSub hc) n₁) (F₀ : JordanFrame ↥(peirceZeroSub hc) n₀) : + Nonempty (JordanFrame J (n₁ + n₀)) := by + classical + set a : Fin n₁ → J := fun i => (F₁.p i : J) with ha + set b : Fin n₀ → J := fun i => (F₀.p i : J) with hb + have haidem : ∀ i, a i * a i = a i := fun i => congrArg Subtype.val (F₁.orthIdem.idem i) + have hbidem : ∀ i, b i * b i = b i := fun i => congrArg Subtype.val (F₀.orthIdem.idem i) + have haorth : ∀ i j, i ≠ j → a i * a j = 0 := fun i j hij => + congrArg Subtype.val (F₁.orthIdem.orth i j hij) + have hborth : ∀ i j, i ≠ j → b i * b j = 0 := fun i j hij => + congrArg Subtype.val (F₀.orthIdem.orth i j hij) + have hcross : ∀ i j, a i * b j = 0 := fun i j => eigen_one_mul_zero hc (F₁.p i).2 (F₀.p j).2 + refine ⟨{ p := Fin.append a b, orthIdem := ⟨?_, ?_⟩, primitive := ?_, complete := ?_ }⟩ + · refine Fin.addCases ?_ ?_ + · intro i; simp only [Fin.append_left]; exact haidem i + · intro i; simp only [Fin.append_right]; exact hbidem i + · refine Fin.addCases ?_ ?_ + · intro i + refine Fin.addCases ?_ ?_ + · intro j hij + simp only [Fin.append_left] + exact haorth i j (fun h => hij (by rw [h])) + · intro j _ + simp only [Fin.append_left, Fin.append_right] + exact hcross i j + · intro i + refine Fin.addCases ?_ ?_ + · intro j _ + simp only [Fin.append_left, Fin.append_right] + rw [_root_.mul_comm]; exact hcross j i + · intro j hij + simp only [Fin.append_right] + exact hborth i j (fun h => hij (by rw [h])) + · refine Fin.addCases ?_ ?_ + · intro i; simp only [Fin.append_left] + exact isPrimitive_coe_of_peirceOne hc (F₁.primitive i) + · intro i; simp only [Fin.append_right] + exact isPrimitive_coe_of_peirceZero hc (F₀.primitive i) + · rw [Fin.sum_univ_add] + simp only [Fin.append_left, Fin.append_right] + have h1 : ∑ i, a i = c := by + rw [ha, ← AddSubmonoidClass.coe_finsetSum, F₁.complete] + exact coe_one_peirceOneSub hc + have h0 : ∑ i, b i = 1 - c := by + rw [hb, ← AddSubmonoidClass.coe_finsetSum, F₀.complete] + exact coe_one_peirceZeroSub hc + rw [h1, h0]; abel + +/-! ## The induction -/ + +/-- The induction carrier: quantified over the *type*, because the recursive calls land on the +two Peirce subalgebras rather than on `J`. The dimension bound is a `≤`, so the outer +induction is the ordinary one on `ℕ` rather than a well-founded recursion. -/ +theorem exists_jordanFrame_of_finrank_le (N : ℕ) : ∀ (J : Type u) [NormedAddCommGroup J] + [InnerProductSpace ℝ J] [EuclideanJordanAlgebra J] [FiniteDimensional ℝ J], + Module.finrank ℝ J ≤ N → (1 : J) ≠ 0 → ∃ n, Nonempty (JordanFrame J n) := by + induction N with + | zero => + intro J _ _ _ _ hle h1 + exact absurd (finrank_zero_iff_forall_zero.mp (Nat.le_zero.mp hle) 1) h1 + | succ N ih => + intro J _ _ _ _ hle h1 + by_cases hprim : IsPrimitive (1 : J) + · have horth : IsOrthIdemFamily (fun _ : Fin 1 => (1 : J)) := by + refine ⟨fun _ => EuclideanJordanAlgebra.one_mul 1, fun i j hij => ?_⟩ + exact absurd (Subsingleton.elim i j) hij + exact ⟨1, ⟨⟨fun _ => 1, horth, fun _ => hprim, by simp⟩⟩⟩ + · obtain ⟨c, hcidem, hc0, hc1⟩ : ∃ c : J, c * c = c ∧ c ≠ 0 ∧ c ≠ 1 := by + by_contra hno + refine hprim ⟨EuclideanJordanAlgebra.one_mul 1, h1, fun d hd _ => ?_⟩ + by_cases h : d = 0 + · exact Or.inl h + · by_cases h' : d = 1 + · exact Or.inr h' + · exact absurd ⟨d, hd, h, h'⟩ hno + have hlt1 : Module.finrank ℝ ↥(peirceOneSub hcidem) ≤ N := + Nat.lt_succ_iff.mp (lt_of_lt_of_le (finrank_peirceOneSub_lt hcidem hc1) hle) + have hlt0 : Module.finrank ℝ ↥(peirceZeroSub hcidem) ≤ N := + Nat.lt_succ_iff.mp (lt_of_lt_of_le (finrank_peirceZeroSub_lt hcidem hc0) hle) + have hu1 : (1 : ↥(peirceOneSub hcidem)) ≠ 0 := fun h => hc0 (by + have hco := congrArg Subtype.val h + rwa [coe_one_peirceOneSub, ZeroMemClass.coe_zero] at hco) + have hu0 : (1 : ↥(peirceZeroSub hcidem)) ≠ 0 := fun h => hc1 (by + have hco := congrArg Subtype.val h + rw [coe_one_peirceZeroSub, ZeroMemClass.coe_zero] at hco + linear_combination (norm := module) -hco) + obtain ⟨n₁, ⟨F₁⟩⟩ := ih _ hlt1 hu1 + obtain ⟨n₀, ⟨F₀⟩⟩ := ih _ hlt0 hu0 + exact ⟨n₁ + n₀, exists_frame_of_split hcidem F₁ F₀⟩ + +/-- **M3.** Every finite-dimensional Euclidean Jordan algebra with `1 ≠ 0` carries a Jordan +frame. The cardinality is existentially quantified and is *not* claimed to be the rank — see +the module docstring. -/ +theorem exists_jordanFrame (J : Type u) [NormedAddCommGroup J] [InnerProductSpace ℝ J] + [EuclideanJordanAlgebra J] [FiniteDimensional ℝ J] (h1 : (1 : J) ≠ 0) : + ∃ n, Nonempty (JordanFrame J n) := + exists_jordanFrame_of_finrank_le (Module.finrank ℝ J) J le_rfl h1 + +/-- `1 ≠ 0` is exactly nontriviality: if `1 = 0` then `x = 1 ∘ x = 0` for every `x`. -/ +theorem one_ne_zero_of_nontrivial [Nontrivial J] : (1 : J) ≠ 0 := by + intro h + obtain ⟨x, y, hxy⟩ := exists_pair_ne J + refine hxy ?_ + have hz : ∀ z : J, z = 0 := fun z => by + rw [← EuclideanJordanAlgebra.one_mul z, h, EuclideanJordanAlgebra.zero_mul'] + rw [hz x, hz y] + +/-- **M3, stated over `Nontrivial`.** -/ +theorem exists_jordanFrame' (J : Type u) [NormedAddCommGroup J] [InnerProductSpace ℝ J] + [EuclideanJordanAlgebra J] [FiniteDimensional ℝ J] [Nontrivial J] : + ∃ n, Nonempty (JordanFrame J n) := + exists_jordanFrame J one_ne_zero_of_nontrivial + +end EuclideanJordan diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/FramePeirce.lean b/LeanPool/EuclideanJordan/EuclideanJordan/FramePeirce.lean new file mode 100644 index 0000000000..a2ffbadd92 --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/FramePeirce.lean @@ -0,0 +1,470 @@ +/- +Copyright (c) 2026 Bryan Ehrlich. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Bryan Ehrlich +-/ +module + +public import LeanPool.EuclideanJordan.EuclideanJordan.FrameExists + + + +/-! +# The frame Peirce decomposition: `J = ⨁_{i ≤ j} V_{ij}` + +For a Jordan frame `F = (p₁, …, pₙ)` this file builds the blocks + +* `V_{ii} := J₂(p i) = {x | p i ∘ x = x}`, +* `V_{ij} := {x | p i ∘ x = ½ • x ∧ p j ∘ x = ½ • x}` for `i ≠ j`, + +as a family `frameBlock F : Sym2 (Fin n) → Submodule ℝ J` (`Sym2` because `V_{ij} = V_{ji}`), +and proves `frameBlock_isInternal : DirectSum.IsInternal (frameBlock F)` — independence +(`frameBlock_iSupIndep`) plus spanning (`frameBlock_iSup_eq_top`). + +`frameBlockRaw_self` records that the diagonal block really is + `EuclideanJordan/PeirceSubalgebra.lean`'s +`peirceOneSub`, so the two definitions of `J₂(p i)` in the tree do not drift apart. + +## ★ The reprice held in direction and was wrong about the mechanism + +The build plan and the task brief both priced this module as risk #2, with the diagnosis: the +eigenvalue combinatorics is *already done* in `EuclideanJordan/Pattern.lean`, what is missing is (a) +simultaneous diagonalisation / spanning and (b) the `DirectSum.IsInternal` packaging, and the +mechanism for (a) is to expand + + id = ∏ᵢ (P₁(pᵢ) + P½(pᵢ) + P₀(pᵢ)) = ∑_{μ : Fin n → Fin 3} ∏ᵢ P_{μ i}(pᵢ) + +and let the `Pattern` lemmas annihilate every `μ` outside the allowed set — the cost driver +being that "expand a `Finset.prod` of sums of commuting idempotent projections over a `Fintype` +and package it as `DirectSum.IsInternal`" is the archetypal Lean chore. + +(a) and (b) were indeed the missing parts. **The mechanism was not used, and neither was +`EuclideanJordan/Pattern.lean`.** `Pattern` is in scope here — it arrives through +`EuclideanJordan/Class.lean`'s import — and no declaration in this file mentions `sum_eigen_eq_one`, +`eigen_pattern_mem` or `eigen_pattern_card_le_two`. There is no `Finset.prod` of projections +below, and no sum over `Fin n → Fin 3`; neither string occurs in this file outside this +docstring. + +★ What replaced it is a *residual* argument, `frame_peirce_span`. Set + + z := x − ∑ᵢ P₁(pᵢ) x − ½ • ∑ᵢ P½(pᵢ) x. + +The eight composition rules of the `Compose` section below, together with +`sum_peirceHalf_erase` in the one place where `P½(p k) P½(p k) x` has to be matched against the +rest of the half-component, kill `P₁(p k) z` and `P½(p k) z` for every `k`; so `peirce_add_add` +at `p k` collapses to `z = P₀(p k) z`, whence `p k ∘ z = 0` for every `k`, whence +`z = 1 ∘ z = (∑ₖ p k) ∘ z = 0`. Nothing is expanded, because the *identity* is never expanded — +only its residual is tested, one idempotent at a time. + +★ The `½` in that identity is not a normalisation choice. `P½(pᵢ) x` is the whole +`i`-th half-eigencomponent, which is the sum of the `V_{ij}` parts over all `j ≠ i`; so an +off-diagonal block is counted once by `P½(pᵢ)` and again by `P½(pⱼ)`, and the sum over `i` +double-counts exactly. Breaking `P½(pᵢ) x` into its blocks is `sum_peirceHalf_erase`, which is +one of exactly two places `F.complete` is used; the other is the final `z = 1 ∘ z`. + +## What is *not* needed, and what is still missing + +★ **No proof below uses primitivity.** The string `primitive` does not occur in this file +outside this docstring: every proof runs on `F.orthIdem` and `F.complete` alone, so primitivity +enters the *statements* only through the `JordanFrame` hypothesis they carry, never through an +argument. (That is an observation about these proofs, not a claim that the results have been +restated at the weaker hypothesis — they have not.) Primitivity is what will collapse `V_{ii}` +to `ℝ ∙ pᵢ`: that is the next module's `dim V_{ii} = 1`, and it is not proved here. + +★ **`rank J = n` is still not available** and nothing here is a step towards it; the frame is +carried as data. See `EuclideanJordan/Rank.lean`'s module docstring. Do not read +`frameBlock_isInternal` as a statement about the rank. + +★ That guard got easier to trip once `EuclideanJordan/HermitianCarrier.lean` named a frame. +`diagJordanFrame : JordanFrame (HermitianMat n ℂ) (Fintype.card n)` puts a frame of cardinality +`Fintype.card n` on `H_n(ℂ)`, which reads like "`rank (H_n(ℂ)) = Fintype.card n`". It is not: +`EuclideanJordan/Rank.lean` supplies `JordanFrame.card_le_rank`, an inequality in one direction + only, and +`rank` is a supremum over *every* orthogonal family of nonzero idempotents. A named frame of +cardinality `k` is a lower bound on the rank and nothing more. + +★ **The carrier arrived after this file — this paragraph used to say the opposite.** As +written on 2026-08-23 it recorded that the only `EuclideanJordanAlgebra` instances in the tree +were `EuclideanJordan/PeirceSubalgebra.lean`'s two, both *conditional* on an ambient +`[EuclideanJordanAlgebra J]`, so that nothing exhibited a base model and the results below +applied to no concrete algebra. That was true when written and is **false now**: +`EuclideanJordan/HermitianCarrier.lean`'s `instEuclideanJordanAlgebraHermitianMat` makes +`HermitianMat n 𝕜` an instance for every `RCLike 𝕜`, and its `hermitian_exists_jordanFrame` +and `hermitian_frameBlock_isInternal` (both under `[Nonempty n]`, which is what `1 ≠ 0` needs) +are `exists_jordanFrame` and `frameBlock_isInternal` read on that carrier. So the theorems +below are statements about a live object, not only about an unwitnessed class. Since +`1ad66d2` the carrier module goes past existence and *names* a frame — `diagJordanFrame`, the +diagonal matrix units, over ℂ — so `frameBlock_isInternal` has an instance with nothing left +existentially quantified. Note what that does and does not settle: see the rank guard above. + +## Scope + +-/ + +@[expose] public section + +noncomputable section + +namespace EuclideanJordan + +open EuclideanJordanAlgebra + +variable {J : Type*} [NormedAddCommGroup J] [InnerProductSpace ℝ J] [EuclideanJordanAlgebra J] + +/-! ## Eigenspaces of left multiplication -/ + +/-- The `r`-eigenspace of `L_a`. -/ +def eigSub (a : J) (r : ℝ) : Submodule ℝ J where + carrier := {x : J | a * x = r • x} + add_mem' := fun {u v} hu hv => by + change a * (u + v) = r • (u + v) + rw [mul_add, hu, hv, smul_add] + zero_mem' := by change a * 0 = r • (0 : J); rw [mul_zero, smul_zero] + smul_mem' := fun t x hx => by + change a * (t • x) = r • (t • x) + rw [mul_smul_comm, hx, smul_comm] + +@[simp] theorem mem_eigSub {a : J} {r : ℝ} {x : J} : x ∈ eigSub a r ↔ a * x = r • x := Iff.rfl + +/-! ## The blocks -/ + +variable {n : ℕ} + +/-- The eigenvalue attached to the pair `(i, j)`: `1` on the diagonal, `1/2` off it. -/ +def blockCoef (i j : Fin n) : ℝ := if i = j then 1 else (2 : ℝ)⁻¹ + +theorem blockCoef_comm (i j : Fin n) : blockCoef i j = blockCoef j i := by + unfold blockCoef + by_cases h : i = j + · simp [h] + · simp [h, Ne.symm h] + +/-- `V_{ij}` before it is pushed through `Sym2`. -/ +def frameBlockRaw (F : JordanFrame J n) (i j : Fin n) : Submodule ℝ J := + eigSub (F.p i) (blockCoef i j) ⊓ eigSub (F.p j) (blockCoef i j) + +theorem frameBlockRaw_comm (F : JordanFrame J n) (i j : Fin n) : + frameBlockRaw F i j = frameBlockRaw F j i := by + unfold frameBlockRaw + rw [blockCoef_comm i j, inf_comm] + +/-- **`V_{ij}`.** For `i ≠ j` the joint `1/2`-eigenspace of `L_{p i}` and `L_{p j}`; on the +diagonal, `J₂(p i)`. -/ +def frameBlock (F : JordanFrame J n) : Sym2 (Fin n) → Submodule ℝ J := + Sym2.lift ⟨frameBlockRaw F, frameBlockRaw_comm F⟩ + +@[simp] theorem frameBlock_mk (F : JordanFrame J n) (i j : Fin n) : + frameBlock F s(i, j) = frameBlockRaw F i j := rfl + +theorem mem_frameBlockRaw_diag {F : JordanFrame J n} {i : Fin n} {x : J} : + x ∈ frameBlockRaw F i i ↔ F.p i * x = x := by + simp [frameBlockRaw, blockCoef] + +theorem mem_frameBlockRaw_off {F : JordanFrame J n} {i j : Fin n} (hij : i ≠ j) {x : J} : + x ∈ frameBlockRaw F i j ↔ F.p i * x = (2 : ℝ)⁻¹ • x ∧ F.p j * x = (2 : ℝ)⁻¹ • x := by + simp [frameBlockRaw, blockCoef, hij] + +/-- The diagonal block is `EuclideanJordan/PeirceSubalgebra.lean`'s `J₂(p i)` on the nose. -/ +theorem frameBlockRaw_self (F : JordanFrame J n) (i : Fin n) : + frameBlockRaw F i i = peirceOneSub (F.orthIdem.idem i) := + Submodule.ext fun _ => mem_frameBlockRaw_diag + +/-! ## What the frame does to a block element -/ + +/-- An element of `J₂(p i)` is annihilated by every other member of the frame. -/ +theorem frame_mul_eq_zero_of_eigen_one (F : JordanFrame J n) {i k : Fin n} (hik : k ≠ i) + {x : J} (hx : F.p i * x = x) : F.p k * x = 0 := by + have h := eigen_one_mul_zero (F.orthIdem.idem i) hx (F.orthIdem.orth i k (Ne.symm hik)) + rw [_root_.mul_comm]; exact h + +/-- An element halved by `p i` and by `p j` is annihilated by every other member of the frame: +it lies in `J₂(p i + p j)`, and every other `p k` lies in `J₀(p i + p j)`. -/ +theorem frame_mul_eq_zero_of_eigen_half (F : JordanFrame J n) {i j k : Fin n} (hij : i ≠ j) + (hki : k ≠ i) (hkj : k ≠ j) {x : J} (hi : F.p i * x = (2 : ℝ)⁻¹ • x) + (hj : F.p j * x = (2 : ℝ)⁻¹ • x) : F.p k * x = 0 := by + have hq : (F.p i + F.p j) * (F.p i + F.p j) = F.p i + F.p j := + add_idem_of_orthogonal (F.orthIdem.idem i) (F.orthIdem.idem j) (F.orthIdem.orth i j hij) + have hqx : (F.p i + F.p j) * x = x := mem_J2_of_half_half hi hj + have hqk : (F.p i + F.p j) * F.p k = 0 := pair_mul_of_ne F.orthIdem hki hkj + have h := eigen_one_mul_zero hq hqx hqk + rw [_root_.mul_comm]; exact h + +/-- **The eigenvalue of `L_{p k}` on `V_{ij}` is `0` for every `k` outside `{i, j}`.** -/ +theorem frameBlockRaw_mul_eq_zero (F : JordanFrame J n) {i j k : Fin n} (hki : k ≠ i) + (hkj : k ≠ j) {x : J} (hx : x ∈ frameBlockRaw F i j) : F.p k * x = 0 := by + by_cases hij : i = j + · subst hij + exact frame_mul_eq_zero_of_eigen_one F hki (mem_frameBlockRaw_diag.mp hx) + · obtain ⟨hi, hj⟩ := (mem_frameBlockRaw_off hij).mp hx + exact frame_mul_eq_zero_of_eigen_half F hij hki hkj hi hj + +/-! ## How the frame's Peirce projections compose + +The eight ways two of the projections `peirceOne (p i)`, `peirceHalf (p i)` can be applied in +succession. Every one is `EuclideanJordan/Block.lean`'s commutation plus a single-idempotent + rule from +`EuclideanJordan/Peirce.lean`. -/ + +section Compose + +variable (F : JordanFrame J n) (x : J) {i k : Fin n} + +theorem peirceOne_peirceOne_self : peirceOne (F.p k) (peirceOne (F.p k) x) = + peirceOne (F.p k) x := peirceOne_of_eigen (mul_peirceOne (F.orthIdem.idem k) x) + +theorem peirceOne_peirceOne_of_ne (h : i ≠ k) : + peirceOne (F.p k) (peirceOne (F.p i) x) = 0 := + peirceOne_of_eigen_zero + (frame_mul_eq_zero_of_eigen_one F (Ne.symm h) (mul_peirceOne (F.orthIdem.idem i) x)) + +theorem peirceOne_peirceHalf_self : peirceOne (F.p k) (peirceHalf (F.p k) x) = 0 := + peirceOne_of_eigen_half (mul_peirceHalf (F.orthIdem.idem k) x) + +theorem peirceOne_peirceHalf_of_ne (h : i ≠ k) : + peirceOne (F.p k) (peirceHalf (F.p i) x) = 0 := by + have hcomm : peirceOne (F.p k) (peirceHalf (F.p i) x) + = peirceHalf (F.p i) (peirceOne (F.p k) x) := + peirceOne_comm_of_mul_comm (F := peirceHalf (F.p i)) + (mul_peirceHalf_comm_orth (F.orthIdem.idem k) (F.orthIdem.orth i k h)) x + rw [hcomm] + exact peirceHalf_of_eigen_zero + (frame_mul_eq_zero_of_eigen_one F h (mul_peirceOne (F.orthIdem.idem k) x)) + +theorem peirceHalf_peirceOne_self : peirceHalf (F.p k) (peirceOne (F.p k) x) = 0 := + peirceHalf_of_eigen (mul_peirceOne (F.orthIdem.idem k) x) + +theorem peirceHalf_peirceOne_of_ne (h : i ≠ k) : + peirceHalf (F.p k) (peirceOne (F.p i) x) = 0 := + peirceHalf_of_eigen_zero + (frame_mul_eq_zero_of_eigen_one F (Ne.symm h) (mul_peirceOne (F.orthIdem.idem i) x)) + +theorem peirceHalf_peirceHalf_self : peirceHalf (F.p k) (peirceHalf (F.p k) x) = + peirceHalf (F.p k) x := peirceHalf_of_eigen_half (mul_peirceHalf (F.orthIdem.idem k) x) + +theorem peirceHalf_peirceHalf_comm (h : i ≠ k) : + peirceHalf (F.p k) (peirceHalf (F.p i) x) = peirceHalf (F.p i) (peirceHalf (F.p k) x) := + peirceHalf_comm_peirceHalf (F.orthIdem.idem k) (F.orthIdem.orth i k h) x + +end Compose + +/-! ## The half-eigenspace of one frame member splits over the others -/ + +/-- **`J½(p k) = ∑_{m ≠ k} V_{km}`, in the form the spanning argument needs.** + +For `y` halved by `p k` and `m ≠ k`, `peirceOne (p m) y` lies in `J₂(p m)` — hence in `J₀(p k)` +— and simultaneously in `J½(p k)`, because `peirceOne (p m)` commutes with `L_{p k}`; so it +vanishes. Then `p m ∘ y = ½ • peirceHalf (p m) y`, and completeness turns +`∑ₘ p m ∘ y = y` into the claim. -/ +theorem sum_peirceHalf_erase (F : JordanFrame J n) {k : Fin n} {y : J} + (hy : F.p k * y = (2 : ℝ)⁻¹ • y) : + ∑ m ∈ Finset.univ.erase k, peirceHalf (F.p m) y = y := by + classical + have hone : ∀ m, m ≠ k → peirceOne (F.p m) y = 0 := by + intro m hm + have hcomm : F.p k * peirceOne (F.p m) y = peirceOne (F.p m) (F.p k * y) := + mul_peirceOne_comm_orth (F.orthIdem.idem k) (F.orthIdem.orth m k hm) y + have h1 : F.p k * peirceOne (F.p m) y = (2 : ℝ)⁻¹ • peirceOne (F.p m) y := by + rw [hcomm, hy, map_smul] + have h0 : F.p k * peirceOne (F.p m) y = 0 := + frame_mul_eq_zero_of_eigen_one F (Ne.symm hm) (mul_peirceOne (F.orthIdem.idem m) y) + have : (2 : ℝ)⁻¹ • peirceOne (F.p m) y = 0 := by rw [← h1, h0] + simpa using this + have hmul : ∀ m, m ≠ k → F.p m * y = (2 : ℝ)⁻¹ • peirceHalf (F.p m) y := by + intro m hm + calc F.p m * y = F.p m * (peirceOne (F.p m) y + peirceHalf (F.p m) y + peirceZero (F.p m) y) + := by + rw [peirce_add_add] + _ = (2 : ℝ)⁻¹ • peirceHalf (F.p m) y := by + rw [mul_add, mul_add, mul_peirceOne (F.orthIdem.idem m), + mul_peirceHalf (F.orthIdem.idem m), mul_peirceZero (F.orthIdem.idem m), hone m hm] + module + have hsum : ∑ m, F.p m * y = y := by + rw [← Finset.sum_mul, F.complete, EuclideanJordanAlgebra.one_mul] + rw [← Finset.add_sum_erase _ _ (Finset.mem_univ k), hy] at hsum + have hstep : ∑ m ∈ Finset.univ.erase k, F.p m * y + = (2 : ℝ)⁻¹ • ∑ m ∈ Finset.univ.erase k, peirceHalf (F.p m) y := by + rw [Finset.smul_sum] + exact Finset.sum_congr rfl fun m hm => hmul m (Finset.ne_of_mem_erase hm) + rw [hstep] at hsum + have : (2 : ℝ)⁻¹ • ∑ m ∈ Finset.univ.erase k, peirceHalf (F.p m) y = (2 : ℝ)⁻¹ • y := by + linear_combination (norm := module) hsum + exact smul_right_injective J (by norm_num : (2 : ℝ)⁻¹ ≠ 0) this + +/-! ## Spanning -/ + +/-- **The frame Peirce decomposition, spanning half.** + +The proof is not an expansion of `∏ᵢ (P₁(pᵢ) + P½(pᵢ) + P₀(pᵢ))`. Set +`z := x - ∑ᵢ P₁(pᵢ) x - ½ ∑ᵢ P½(pᵢ) x`; the composition lemmas above kill `P₁(p k) z` and +`P½(p k) z` for every `k`, so `z = P₀(p k) z` and hence `p k ∘ z = 0` for every `k`, and +completeness gives `z = 1 ∘ z = 0`. -/ +theorem frame_peirce_span (F : JordanFrame J n) (x : J) : + x = ∑ i, peirceOne (F.p i) x + (2 : ℝ)⁻¹ • ∑ i, peirceHalf (F.p i) x := by + classical + have e1 : ∀ k, ∑ i, peirceOne (F.p k) (peirceOne (F.p i) x) = peirceOne (F.p k) x := by + intro k + rw [Finset.sum_eq_single k (fun b _ hb => peirceOne_peirceOne_of_ne F x hb) + (fun h => absurd (Finset.mem_univ k) h)] + exact peirceOne_peirceOne_self F x + have e2 : ∀ k, ∑ i, peirceOne (F.p k) (peirceHalf (F.p i) x) = 0 := fun k => + Finset.sum_eq_zero fun i _ => by + by_cases h : i = k + · subst h; exact peirceOne_peirceHalf_self F x + · exact peirceOne_peirceHalf_of_ne F x h + have e3 : ∀ k, ∑ i, peirceHalf (F.p k) (peirceOne (F.p i) x) = 0 := fun k => + Finset.sum_eq_zero fun i _ => by + by_cases h : i = k + · subst h; exact peirceHalf_peirceOne_self F x + · exact peirceHalf_peirceOne_of_ne F x h + have e4 : ∀ k, ∑ i, peirceHalf (F.p k) (peirceHalf (F.p i) x) + = peirceHalf (F.p k) x + peirceHalf (F.p k) x := by + intro k + rw [← Finset.add_sum_erase _ _ (Finset.mem_univ k)] + congr 1 + · exact peirceHalf_peirceHalf_self F x + · rw [Finset.sum_congr rfl fun i hi => peirceHalf_peirceHalf_comm F x + (Finset.ne_of_mem_erase hi)] + exact sum_peirceHalf_erase F (mul_peirceHalf (F.orthIdem.idem k) x) + obtain ⟨z, hzdef⟩ : ∃ z : J, + z = x - ∑ i, peirceOne (F.p i) x - (2 : ℝ)⁻¹ • ∑ i, peirceHalf (F.p i) x := ⟨_, rfl⟩ + have hA : ∀ k, peirceOne (F.p k) z = 0 := by + intro k + rw [hzdef] + simp only [map_sub, map_smul, map_sum] + rw [e1 k, e2 k] + module + have hB : ∀ k, peirceHalf (F.p k) z = 0 := by + intro k + rw [hzdef] + simp only [map_sub, map_smul, map_sum] + rw [e3 k, e4 k] + module + have hzero : ∀ k, F.p k * z = 0 := by + intro k + have hsplit := peirce_add_add (F.p k) z + rw [hA k, hB k, zero_add, zero_add] at hsplit + rw [← hsplit] + exact mul_peirceZero (F.orthIdem.idem k) z + have hz0 : z = 0 := by + have : (∑ i, F.p i) * z = 0 := by + rw [Finset.sum_mul] + exact Finset.sum_eq_zero fun i _ => hzero i + rwa [F.complete, EuclideanJordanAlgebra.one_mul] at this + rw [hzdef, sub_sub, sub_eq_zero] at hz0 + exact hz0 + +/-! ## The block projections -/ + +open scoped Classical in +/-- The projection onto `V_{ij}`: `P₁(p i)` on the diagonal, `P½(p i) ∘ P½(p j)` off it. -/ +def blockProj (F : JordanFrame J n) (i j : Fin n) : J →ₗ[ℝ] J := + if i = j then peirceOne (F.p i) else (peirceHalf (F.p i)).comp (peirceHalf (F.p j)) + +theorem blockProj_diag (F : JordanFrame J n) (i : Fin n) : + blockProj F i i = peirceOne (F.p i) := by + unfold blockProj; simp + +theorem blockProj_off (F : JordanFrame J n) {i j : Fin n} (h : i ≠ j) : + blockProj F i j = (peirceHalf (F.p i)).comp (peirceHalf (F.p j)) := by + unfold blockProj; simp [h] + +/-- `blockProj F i j` is the identity on `V_{ij}`. -/ +theorem blockProj_apply_eq_self (F : JordanFrame J n) {i j : Fin n} {x : J} + (hx : x ∈ frameBlockRaw F i j) : blockProj F i j x = x := by + by_cases hij : i = j + · subst hij + rw [blockProj_diag] + exact peirceOne_of_eigen (mem_frameBlockRaw_diag.mp hx) + · obtain ⟨hi, hj⟩ := (mem_frameBlockRaw_off hij).mp hx + rw [blockProj_off F hij, LinearMap.comp_apply, peirceHalf_of_eigen_half hj, + peirceHalf_of_eigen_half hi] + +/-- `blockProj F a b` annihilates every *other* block. -/ +theorem blockProj_apply_eq_zero (F : JordanFrame J n) {a b c d : Fin n} {x : J} + (hne : s(c, d) ≠ s(a, b)) (hx : x ∈ frameBlockRaw F c d) : blockProj F a b x = 0 := by + have hne' : ¬((c = a ∧ d = b) ∨ (c = b ∧ d = a)) := fun h => hne (Sym2.eq_iff.mpr h) + have hL : ¬(a = c ∧ b = d) := fun h => hne' (Or.inl ⟨h.1.symm, h.2.symm⟩) + have hR : ¬(a = d ∧ b = c) := fun h => hne' (Or.inr ⟨h.2.symm, h.1.symm⟩) + by_cases hab : a = b + · subst hab + rw [blockProj_diag] + by_cases hac : a = c + · have had : a ≠ d := fun h => hL ⟨hac, h⟩ + have hcd : c ≠ d := hac ▸ had + have := ((mem_frameBlockRaw_off hcd).mp hx).1 + rw [← hac] at this + exact peirceOne_of_eigen_half this + · by_cases had : a = d + · have hbc : a ≠ c := hac + have hcd : c ≠ d := fun h => hac (had.trans h.symm) + have := ((mem_frameBlockRaw_off hcd).mp hx).2 + rw [← had] at this + exact peirceOne_of_eigen_half this + · exact peirceOne_of_eigen_zero (frameBlockRaw_mul_eq_zero F hac had hx) + · rw [blockProj_off F hab, LinearMap.comp_apply] + by_cases hac : a = c + · have hbd : b ≠ d := fun h => hL ⟨hac, h⟩ + have hbc : b ≠ c := fun h => hab (hac.trans h.symm) + rw [peirceHalf_of_eigen_zero (frameBlockRaw_mul_eq_zero F hbc hbd hx), map_zero] + · by_cases had : a = d + · have hbc : b ≠ c := fun h => hR ⟨had, h⟩ + have hbd : b ≠ d := fun h => hab (had.trans h.symm) + rw [peirceHalf_of_eigen_zero (frameBlockRaw_mul_eq_zero F hbc hbd hx), map_zero] + · have hax : F.p a * x = 0 := frameBlockRaw_mul_eq_zero F hac had hx + refine peirceHalf_of_eigen_zero ?_ + rw [mul_peirceHalf_comm_orth (F.orthIdem.idem a) (F.orthIdem.orth b a (Ne.symm hab)), + hax, map_zero] + +/-! ## `DirectSum.IsInternal` -/ + +/-- For `i ≠ m`, `P½(p m) P½(p i) x` lies in `V_{im}`. -/ +theorem peirceHalf_peirceHalf_mem (F : JordanFrame J n) {i m : Fin n} (him : i ≠ m) (x : J) : + peirceHalf (F.p m) (peirceHalf (F.p i) x) ∈ frameBlockRaw F i m := by + refine (mem_frameBlockRaw_off him).mpr ⟨?_, mul_peirceHalf (F.orthIdem.idem m) _⟩ + rw [mul_peirceHalf_comm_orth (F.orthIdem.idem i) (F.orthIdem.orth m i (Ne.symm him)), + mul_peirceHalf (F.orthIdem.idem i), map_smul] + +theorem peirceOne_mem_frameBlock (F : JordanFrame J n) (i : Fin n) (x : J) : + peirceOne (F.p i) x ∈ frameBlock F s(i, i) := + mem_frameBlockRaw_diag.mpr (mul_peirceOne (F.orthIdem.idem i) x) + +/-- **The blocks span.** -/ +theorem frameBlock_iSup_eq_top (F : JordanFrame J n) : ⨆ s, frameBlock F s = ⊤ := by + classical + refine eq_top_iff.mpr fun x _ => ?_ + have hhalf : ∀ i, peirceHalf (F.p i) x ∈ ⨆ s, frameBlock F s := by + intro i + rw [← sum_peirceHalf_erase F (mul_peirceHalf (F.orthIdem.idem i) x)] + refine Submodule.sum_mem _ fun m hm => Submodule.mem_iSup_of_mem s(i, m) ?_ + exact peirceHalf_peirceHalf_mem F (Ne.symm (Finset.ne_of_mem_erase hm)) x + rw [frame_peirce_span F x] + refine Submodule.add_mem _ (Submodule.sum_mem _ fun i _ => ?_) + (Submodule.smul_mem _ _ (Submodule.sum_mem _ fun i _ => hhalf i)) + exact Submodule.mem_iSup_of_mem s(i, i) (peirceOne_mem_frameBlock F i x) + +/-- **The blocks are independent.** -/ +theorem frameBlock_iSupIndep (F : JordanFrame J n) : iSupIndep (frameBlock F) := by + classical + intro s + induction s using Sym2.ind with + | _ a b => + have hker : (⨆ t, ⨆ (_ : t ≠ s(a, b)), frameBlock F t) + ≤ LinearMap.ker (blockProj F a b) := by + refine iSup_le fun t => ?_ + induction t using Sym2.ind with + | _ c d => + refine iSup_le fun ht => fun y hy => ?_ + simp only [LinearMap.mem_ker] + exact blockProj_apply_eq_zero F ht hy + rw [Submodule.disjoint_def] + intro x hx hx' + have h1 : blockProj F a b x = x := blockProj_apply_eq_self F hx + have h0 : blockProj F a b x = 0 := hker hx' + rw [← h1, h0] + +/-- **The frame Peirce decomposition.** `J = ⨁_{i ≤ j} V_{ij}`. -/ +theorem frameBlock_isInternal (F : JordanFrame J n) : DirectSum.IsInternal (frameBlock F) := + (DirectSum.isInternal_submodule_iff_iSupIndep_and_iSup_eq_top _).mpr + ⟨frameBlock_iSupIndep F, frameBlock_iSup_eq_top F⟩ + +end EuclideanJordan diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/FramePeirceMul.lean b/LeanPool/EuclideanJordan/EuclideanJordan/FramePeirceMul.lean new file mode 100644 index 0000000000..3e676a1067 --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/FramePeirceMul.lean @@ -0,0 +1,378 @@ +/- +Copyright (c) 2026 Bryan Ehrlich. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Bryan Ehrlich +-/ +module + +public import LeanPool.EuclideanJordan.EuclideanJordan.FramePeirce + + + +/-! +# The Faraut–Korányi multiplication table relative to a Jordan frame + +`EuclideanJordan/FramePeirce.lean` builds the blocks `V_{ij}` of a Jordan frame `F = (p₁, …, + pₙ)` and proves +`J = ⨁_{i ≤ j} V_{ij}`. This file multiplies them. The table: + +* `V_{ii} ∘ V_{ii} ⊆ V_{ii}` (`frameBlockRaw_diag_mul_diag`), +* `V_{ii} ∘ V_{ij} ⊆ V_{ij}` (`frameBlockRaw_diag_mul_off`), +* `V_{ij} ∘ V_{jk} ⊆ V_{ik}` for `i, j, k` distinct (`frameBlockRaw_mul_middle`), +* `V_{ij} ∘ V_{kl} = 0` when `{i,j} ∩ {k,l} = ∅` (`frameBlockRaw_mul_disjoint`), +* `V_{ij} ∘ V_{ij} ⊆ ℝ∙pᵢ + ℝ∙pⱼ` (`frameBlockRaw_mul_self_eq`), + +together with `dim V_{ii} = 1` (`finrank_frameBlockRaw_self`) and the eigenvalue rule +`pᵢ ∘ x = ½ • x` on `V_{ij}` (`frameBlockRaw_mul_left_half`). The last three lines of the table +are also stated as literal submodule inclusions through `EuclideanJordan/Class.lean`'s bundled + `jmulₗ` and +`Submodule.map₂`, in the `Map₂` section; there is no `Mul` on `Submodule ℝ J` to state them with, +because Mathlib's `Submodule.mul` instance is declared for `[Semiring A] [Module R A] +[IsScalarTower R A A]` — an associative unital ring — which a Jordan algebra is not. + +## ★ Where the coefficients come from — the plan's one unpriced step + +The build plan priced every rule here as a short consequence of `EuclideanJordan/PeirceMul.lean`'s +single-idempotent rules **except** `V_{ij} ∘ V_{ij} ⊆ ℝ∙pᵢ + ℝ∙pⱼ`, which it left explicitly +unpriced: `eigen_half_mul_half` gives the projection identity, but *pinning the two coefficients* +was expected to need the trace form, and no in-tree lemma had been matched to that step. + +**The trace form is not used.** The step is the frame's completeness. For `x, y ∈ V_{ij}` put +`z := x ∘ y`. Every `p_k` with `k ∉ {i,j}` kills both factors, so `eigen_zero_mul_zero` kills +`z`; hence + + z = 1 ∘ z = (∑ₖ p_k) ∘ z = p_i ∘ z + p_j ∘ z, + +which is `frameBlockRaw_mul_self_split`. And `eigen_half_mul_half` at `p_i` says exactly +`p_i ∘ (p_i ∘ z) = p_i ∘ z`, i.e. `p_i ∘ z ∈ J₂(p_i) = V_{ii}` — so the two summands are already +in the two diagonal blocks (`frameBlockRaw_mul_self_left_mem`). That much is the *decomposition*, +and it needs neither primitivity nor finite-dimensionality. + +The **coefficients** then come from `V_{ii}` being a line, not from an inner-product computation: +`p_i ∘ z = a • p_i` because `dim V_{ii} = 1`. So the sub-item the plan could not price is really +two independent facts, and the trace form is in neither. What *is* load-bearing is the +completeness of the frame, used once, in `frameBlockRaw_mul_self_split`. + +## ★ Primitivity is spent here + +`EuclideanJordan/FramePeirce.lean` records that no proof in it uses primitivity at all. This + file is not, +however, the first in the tree to *touch* the `IsPrimitive` clauses, and an earlier draft of this +docstring said so wrongly: `EuclideanJordan/Rank.lean`'s `JordanFrame.p_ne_zero` consumes the + `ne_zero` clause, +and `EuclideanJordan/FrameExists.lean`'s `isPrimitive_coe_of_peirceOne` / + `isPrimitive_coe_of_peirceZero` +consume the splitting clause `∀ d, d ∘ d = d → c ∘ d = d → d = 0 ∨ d = c` in full. What those two +do with it is *transport* it across the coercion `↥(J₂(c)) → J`; they extract no structural +consequence from it. + +This file is the first to spend it. Within this file the splitting clause is used at exactly one +theorem, `peirceOneSub_eq_span_of_isPrimitive`, which runs `EuclideanJordan/Class.lean`'s +`spectral_resolution_complete'` **inside** `J₂(c)` — legitimate because +`EuclideanJordan/PeirceSubalgebra.lean` gives `J₂(c)` its own `EuclideanJordanAlgebra` instance + with unit `c` +— and reads off that every idempotent appearing in the resolution is `0` or `1`, by +`EuclideanJordan/Rank.lean`'s `isPrimitive_iff_of_idem`. A resolution all of whose idempotents + are `0` or `1` +has every term a real multiple of `1 = c`. + +Orthogonality of the resolution is *not* used in that argument, only the two-valuedness; that is +why the proof does not have to rule out two indices both landing on `1`. + +The rest of the `Primitive` section is downstream of that one theorem, except that +`finrank_frameBlockRaw_self` also uses the `ne_zero` clause, through `JordanFrame.p_ne_zero`. +Everything *before* the `Primitive` section runs on `F.orthIdem` and `F.complete` alone. + +## Scope + + +★ `rank J = n` is **not** available and nothing here is a step towards it — `dim V_{ii} = 1` is a +statement about one block of a frame carried as data, not about the rank of `J`. + +★ **There is a carrier and a named frame**, so the theorems below are not statements about an +empty class: `EuclideanJordan/HermitianCarrier.lean` supplies + `instEuclideanJordanAlgebraHermitianMat` and +`diagJordanFrame : JordanFrame (HermitianMat n ℂ) (Fintype.card n)`. `frameBlockRaw_mul_self_eq` is +consumed at `F = diagJordanFrame` through `EuclideanJordan/Connection.lean`'s `exists_sq_smul`. + +★ Do not quote this paragraph for the carrier's state; read +`EuclideanJordan/HermitianCarrier.lean`. +The durable part is the one below. + +★ `rank J = n` is not proved for **any** frame, `diagJordanFrame` included: + `EuclideanJordan/Rank.lean` +bounds a frame's cardinality by the rank and by the dimension, and nothing anywhere converts +`dim V_{ii} = 1` into a statement about `rank J`. +-/ + +@[expose] public section + +noncomputable section + +namespace EuclideanJordan + +open EuclideanJordanAlgebra + +variable {J : Type*} [NormedAddCommGroup J] [InnerProductSpace ℝ J] [EuclideanJordanAlgebra J] +variable {n : ℕ} + +/-! ## The eigenvalue rule on a block -/ + +/-- **`pᵢ ∘ x = ½ • x` for `x ∈ V_{ij}`, `i ≠ j`.** Definitional at +`EuclideanJordan/FramePeirce.lean`'s +`frameBlockRaw`; named because the multiplication rules below take it as an argument constantly. -/ +theorem frameBlockRaw_mul_left_half (F : JordanFrame J n) {i j : Fin n} (hij : i ≠ j) {x : J} + (hx : x ∈ frameBlockRaw F i j) : F.p i * x = (2 : ℝ)⁻¹ • x := + ((mem_frameBlockRaw_off hij).mp hx).1 + +/-- The same at the second index. -/ +theorem frameBlockRaw_mul_right_half (F : JordanFrame J n) {i j : Fin n} (hij : i ≠ j) {x : J} + (hx : x ∈ frameBlockRaw F i j) : F.p j * x = (2 : ℝ)⁻¹ • x := + ((mem_frameBlockRaw_off hij).mp hx).2 + +/-! ## The table + +Each rule is read off the single-idempotent Faraut–Korányi rules of + `EuclideanJordan/PeirceMul.lean`, applied +at one frame member at a time. `frameBlockRaw_mul_eq_zero` (in + `EuclideanJordan/FramePeirce.lean`) supplies +the `0`-eigenvalue of every frame member outside a block's index pair. -/ + +/-- **`V_{ii} ∘ V_{ii} ⊆ V_{ii}`** — the diagonal block is a subalgebra. This is +`eigen_one_mul_one` verbatim; `EuclideanJordan/PeirceSubalgebra.lean` already uses it as the + `Mul` field of +`J₂(pᵢ)`. -/ +theorem frameBlockRaw_diag_mul_diag (F : JordanFrame J n) {i : Fin n} {x y : J} + (hx : x ∈ frameBlockRaw F i i) (hy : y ∈ frameBlockRaw F i i) : + x * y ∈ frameBlockRaw F i i := + mem_frameBlockRaw_diag.mpr + (eigen_one_mul_one (F.orthIdem.idem i) (mem_frameBlockRaw_diag.mp hx) + (mem_frameBlockRaw_diag.mp hy)) + +/-- **`V_{ii} ∘ V_{ij} ⊆ V_{ij}`.** At `pᵢ` this is `eigen_one_mul_half`; at `pⱼ` it is +`eigen_zero_mul_half`, because `pⱼ` annihilates `J₂(pᵢ)`. -/ +theorem frameBlockRaw_diag_mul_off (F : JordanFrame J n) {i j : Fin n} (hij : i ≠ j) {x y : J} + (hx : x ∈ frameBlockRaw F i i) (hy : y ∈ frameBlockRaw F i j) : + x * y ∈ frameBlockRaw F i j := by + have hxi : F.p i * x = x := mem_frameBlockRaw_diag.mp hx + have hxj : F.p j * x = 0 := + frameBlockRaw_mul_eq_zero F (Ne.symm hij) (Ne.symm hij) hx + refine (mem_frameBlockRaw_off hij).mpr ⟨?_, ?_⟩ + · exact eigen_one_mul_half (F.orthIdem.idem i) hxi (frameBlockRaw_mul_left_half F hij hy) + · exact eigen_zero_mul_half (F.orthIdem.idem j) hxj (frameBlockRaw_mul_right_half F hij hy) + +/-- **`V_{ij} ∘ V_{jk} ⊆ V_{ik}` for `i, j, k` distinct.** + +`pᵢ` halves `x` and kills `y`, `p_k` kills `x` and halves `y`; `eigen_zero_mul_half` at each of +them is the whole proof. The middle index enters only through the eigenvalue hypotheses on `x` +and `y`: nothing has to be shown about `pⱼ ∘ (x ∘ y)`, because membership in `V_{ik}` is a +condition at `pᵢ` and `p_k` only. -/ +theorem frameBlockRaw_mul_middle (F : JordanFrame J n) {i j k : Fin n} (hij : i ≠ j) + (hjk : j ≠ k) (hik : i ≠ k) {x y : J} (hx : x ∈ frameBlockRaw F i j) + (hy : y ∈ frameBlockRaw F j k) : x * y ∈ frameBlockRaw F i k := by + have hyi : F.p i * y = 0 := frameBlockRaw_mul_eq_zero F hij hik hy + have hxk : F.p k * x = 0 := frameBlockRaw_mul_eq_zero F (Ne.symm hik) (Ne.symm hjk) hx + refine (mem_frameBlockRaw_off hik).mpr ⟨?_, ?_⟩ + · have h := eigen_zero_mul_half (F.orthIdem.idem i) hyi (frameBlockRaw_mul_left_half F hij hx) + rwa [_root_.mul_comm y x] at h + · exact eigen_zero_mul_half (F.orthIdem.idem k) hxk (frameBlockRaw_mul_right_half F hjk hy) + +/-- **`V_{ij} ∘ V_{kl} = 0` when `{i,j}` and `{k,l}` are disjoint** — the two blocks annihilate +each other, not merely land in a common block. + +Both index pairs are allowed to be diagonal. If `i = j` then `x ∈ J₂(pᵢ)` and `y ∈ J₀(pᵢ)`, and +`eigen_one_mul_zero` finishes; otherwise the same argument runs at the rank-two idempotent +`q = pᵢ + pⱼ`, with `x ∈ J₂(q)` by `mem_J2_of_half_half` and `y ∈ J₀(q)` because `pᵢ` and `pⱼ` +each kill `y`. -/ +theorem frameBlockRaw_mul_disjoint (F : JordanFrame J n) {i j k l : Fin n} (hik : i ≠ k) + (hil : i ≠ l) (hjk : j ≠ k) (hjl : j ≠ l) {x y : J} (hx : x ∈ frameBlockRaw F i j) + (hy : y ∈ frameBlockRaw F k l) : x * y = 0 := by + have hyi : F.p i * y = 0 := frameBlockRaw_mul_eq_zero F hik hil hy + have hyj : F.p j * y = 0 := frameBlockRaw_mul_eq_zero F hjk hjl hy + by_cases hij : i = j + · subst hij + exact eigen_one_mul_zero (F.orthIdem.idem i) (mem_frameBlockRaw_diag.mp hx) hyi + · have hq : (F.p i + F.p j) * (F.p i + F.p j) = F.p i + F.p j := + add_idem_of_orthogonal (F.orthIdem.idem i) (F.orthIdem.idem j) (F.orthIdem.orth i j hij) + have hqx : (F.p i + F.p j) * x = x := + mem_J2_of_half_half (frameBlockRaw_mul_left_half F hij hx) + (frameBlockRaw_mul_right_half F hij hx) + have hqy : (F.p i + F.p j) * y = 0 := by rw [_root_.add_mul, hyi, hyj, add_zero] + exact eigen_one_mul_zero hq hqx hqy + +/-! ## `V_{ij} ∘ V_{ij}`: the decomposition, before any coefficients + +★ These three are the plan's unpriced step, and they use no trace form, no primitivity and no +finite-dimensionality — only `F.complete` and `EuclideanJordan/PeirceMul.lean`. -/ + +/-- **`x ∘ y = pᵢ ∘ (x ∘ y) + pⱼ ∘ (x ∘ y)` for `x, y ∈ V_{ij}`.** + +The one place completeness is used: every frame member outside `{i,j}` kills both factors, hence +their product by `eigen_zero_mul_zero`, so `∑ₖ p_k ∘ z` collapses to two terms and `∑ₖ p_k = 1` +says that sum is `z`. -/ +theorem frameBlockRaw_mul_self_split (F : JordanFrame J n) {i j : Fin n} (hij : i ≠ j) {x y : J} + (hx : x ∈ frameBlockRaw F i j) (hy : y ∈ frameBlockRaw F i j) : + x * y = F.p i * (x * y) + F.p j * (x * y) := by + classical + have hsum : ∑ k, F.p k * (x * y) = x * y := by + rw [← Finset.sum_mul, F.complete, EuclideanJordanAlgebra.one_mul] + have hpair : ∑ k ∈ ({i, j} : Finset (Fin n)), F.p k * (x * y) = ∑ k, F.p k * (x * y) := by + refine Finset.sum_subset (Finset.subset_univ _) ?_ + intro k _ hk + have hki : k ≠ i := fun h => hk (by simp [h]) + have hkj : k ≠ j := fun h => hk (by simp [h]) + exact eigen_zero_mul_zero (F.orthIdem.idem k) + (frameBlockRaw_mul_eq_zero F hki hkj hx) (frameBlockRaw_mul_eq_zero F hki hkj hy) + rw [Finset.sum_pair hij] at hpair + exact (hpair.trans hsum).symm + +/-- The first summand of `frameBlockRaw_mul_self_split` lies in the diagonal block `V_{ii}`. +This is `eigen_half_mul_half` read as a membership: `L_{pᵢ}² = L_{pᵢ}` on `x ∘ y` says precisely +that `pᵢ ∘ (x ∘ y)` is fixed by `L_{pᵢ}`. -/ +theorem frameBlockRaw_mul_self_left_mem (F : JordanFrame J n) {i j : Fin n} (hij : i ≠ j) + {x y : J} (hx : x ∈ frameBlockRaw F i j) (hy : y ∈ frameBlockRaw F i j) : + F.p i * (x * y) ∈ frameBlockRaw F i i := + mem_frameBlockRaw_diag.mpr + (eigen_half_mul_half (F.orthIdem.idem i) (frameBlockRaw_mul_left_half F hij hx) + (frameBlockRaw_mul_left_half F hij hy)) + +/-- The second summand lies in `V_{jj}`. -/ +theorem frameBlockRaw_mul_self_right_mem (F : JordanFrame J n) {i j : Fin n} (hij : i ≠ j) + {x y : J} (hx : x ∈ frameBlockRaw F i j) (hy : y ∈ frameBlockRaw F i j) : + F.p j * (x * y) ∈ frameBlockRaw F j j := + mem_frameBlockRaw_diag.mpr + (eigen_half_mul_half (F.orthIdem.idem j) (frameBlockRaw_mul_right_half F hij hx) + (frameBlockRaw_mul_right_half F hij hy)) + +/-- **`V_{ij} ∘ V_{ij} ⊆ V_{ii} ⊔ V_{jj}`**, the coefficient-free form. -/ +theorem frameBlockRaw_mul_self_mem_sup (F : JordanFrame J n) {i j : Fin n} (hij : i ≠ j) + {x y : J} (hx : x ∈ frameBlockRaw F i j) (hy : y ∈ frameBlockRaw F i j) : + x * y ∈ frameBlockRaw F i i ⊔ frameBlockRaw F j j := by + rw [frameBlockRaw_mul_self_split F hij hx hy] + exact Submodule.add_mem _ + (Submodule.mem_sup_left (frameBlockRaw_mul_self_left_mem F hij hx hy)) + (Submodule.mem_sup_right (frameBlockRaw_mul_self_right_mem F hij hx hy)) + +/-! ## Primitivity: the diagonal block is a line + +★ The `JordanFrame.primitive` field is consumed here and nowhere else in the frame Peirce +layer. -/ + +section Primitive + +variable [FiniteDimensional ℝ J] + +/-- **`J₂(c) = ℝ ∙ c` for a primitive idempotent `c`.** + +`EuclideanJordan/PeirceSubalgebra.lean` makes `J₂(c)` a Euclidean Jordan algebra with unit `c`, so +`EuclideanJordan/Class.lean`'s `spectral_resolution_complete'` applies *inside* it: every element is +`∑ₖ λ_k • e_k` with each `e_k` idempotent in `J₂(c)`. `EuclideanJordan/Rank.lean`'s +`isPrimitive_iff_of_idem` says each `e_k` is `0` or `1`, and `1 = c`; so every term is a real +multiple of `c`. + +Orthogonality of the `e_k` is not used — only that each is `0` or `1`. -/ +theorem peirceOneSub_eq_span_of_isPrimitive {c : J} (hp : IsPrimitive c) : + peirceOneSub hp.idem = Submodule.span ℝ {c} := by + classical + refine le_antisymm ?_ ?_ + · intro x hx + obtain ⟨m, e, lam, hfam, -, hrep⟩ := + spectral_resolution_complete' (J := ↥(peirceOneSub hp.idem)) ⟨x, hx⟩ + have hcoe : x = ∑ k, lam k • ((e k : ↥(peirceOneSub hp.idem)) : J) := by + have := congrArg (fun z : ↥(peirceOneSub hp.idem) => (z : J)) hrep + simpa using this + rw [hcoe] + refine Submodule.sum_mem _ fun k _ => Submodule.smul_mem _ _ ?_ + rcases (isPrimitive_iff_of_idem hp.idem hp.ne_zero).mp hp (e k) (hfam.idem k) with h | h + · rw [h]; simp + · rw [h]; exact Submodule.mem_span_singleton_self c + · rw [Submodule.span_le, Set.singleton_subset_iff] + exact hp.idem + +/-- `dim J₂(c) = 1` for a primitive idempotent — the definition of primitivity, cashed out as a +dimension. -/ +theorem finrank_peirceOneSub_of_isPrimitive {c : J} (hp : IsPrimitive c) : + Module.finrank ℝ ↥(peirceOneSub hp.idem) = 1 := by + rw [peirceOneSub_eq_span_of_isPrimitive hp] + exact finrank_span_singleton hp.ne_zero + +/-- **`V_{ii} = ℝ ∙ pᵢ`.** -/ +theorem frameBlockRaw_self_eq_span (F : JordanFrame J n) (i : Fin n) : + frameBlockRaw F i i = Submodule.span ℝ {F.p i} := by + rw [frameBlockRaw_self F i] + exact peirceOneSub_eq_span_of_isPrimitive (F.primitive i) + +/-- **`dim V_{ii} = 1`.** The statement the coordinatization rests on. -/ +theorem finrank_frameBlockRaw_self (F : JordanFrame J n) (i : Fin n) : + Module.finrank ℝ ↥(frameBlockRaw F i i) = 1 := by + rw [frameBlockRaw_self_eq_span F i] + exact finrank_span_singleton (F.p_ne_zero i) + +/-- `dim V_{ii} = 1`, in the `Sym2`-indexed vocabulary `frameBlock` uses. -/ +theorem finrank_frameBlock_diag (F : JordanFrame J n) (i : Fin n) : + Module.finrank ℝ ↥(frameBlock F s(i, i)) = 1 := + finrank_frameBlockRaw_self F i + +/-- An element of `V_{ii}` is a real multiple of `pᵢ` — `frameBlockRaw_self_eq_span` read +pointwise. -/ +theorem exists_smul_of_mem_frameBlockRaw_self (F : JordanFrame J n) {i : Fin n} {x : J} + (hx : x ∈ frameBlockRaw F i i) : ∃ a : ℝ, x = a • F.p i := by + rw [frameBlockRaw_self_eq_span F i, Submodule.mem_span_singleton] at hx + obtain ⟨a, ha⟩ := hx + exact ⟨a, ha.symm⟩ + +/-- **`V_{ij} ∘ V_{ij} ⊆ ℝ∙pᵢ + ℝ∙pⱼ`**, with the coefficients exhibited. + +`frameBlockRaw_mul_self_split` puts the product in `V_{ii} ⊕ V_{jj}`; `dim V_{ii} = 1` turns each +summand into a scalar multiple of a frame member. -/ +theorem frameBlockRaw_mul_self_eq (F : JordanFrame J n) {i j : Fin n} (hij : i ≠ j) {x y : J} + (hx : x ∈ frameBlockRaw F i j) (hy : y ∈ frameBlockRaw F i j) : + ∃ a b : ℝ, x * y = a • F.p i + b • F.p j := by + obtain ⟨a, ha⟩ := + exists_smul_of_mem_frameBlockRaw_self F (frameBlockRaw_mul_self_left_mem F hij hx hy) + obtain ⟨b, hb⟩ := + exists_smul_of_mem_frameBlockRaw_self F (frameBlockRaw_mul_self_right_mem F hij hx hy) + exact ⟨a, b, by rw [frameBlockRaw_mul_self_split F hij hx hy, ha, hb]⟩ + +end Primitive + +/-! ## The table as submodule inclusions + +`Submodule.map₂ (jmulₗ J) P Q` is the submodule generated by the products, so these are the +literal `⊆` statements of the module docstring. `EuclideanJordan/Class.lean`'s `jmulₗ` is the + bundled form of +the same product (`jmulₗ_apply` is `rfl`). -/ + +section Map₂ + +/-- `V_{ij} ∘ V_{jk} ⊆ V_{ik}`. -/ +theorem map₂_frameBlockRaw_middle_le (F : JordanFrame J n) {i j k : Fin n} (hij : i ≠ j) + (hjk : j ≠ k) (hik : i ≠ k) : + Submodule.map₂ (jmulₗ J) (frameBlockRaw F i j) (frameBlockRaw F j k) + ≤ frameBlockRaw F i k := + Submodule.map₂_le.mpr fun _ hx _ hy => frameBlockRaw_mul_middle F hij hjk hik hx hy + +/-- `V_{ij} ∘ V_{kl} = 0` for disjoint index pairs. -/ +theorem map₂_frameBlockRaw_disjoint (F : JordanFrame J n) {i j k l : Fin n} (hik : i ≠ k) + (hil : i ≠ l) (hjk : j ≠ k) (hjl : j ≠ l) : + Submodule.map₂ (jmulₗ J) (frameBlockRaw F i j) (frameBlockRaw F k l) = ⊥ := + le_bot_iff.mp <| Submodule.map₂_le.mpr fun _ hx _ hy => + (Submodule.mem_bot ℝ).mpr (frameBlockRaw_mul_disjoint F hik hil hjk hjl hx hy) + +/-- `V_{ij} ∘ V_{ij} ⊆ V_{ii} ⊔ V_{jj}`. -/ +theorem map₂_frameBlockRaw_self_le (F : JordanFrame J n) {i j : Fin n} (hij : i ≠ j) : + Submodule.map₂ (jmulₗ J) (frameBlockRaw F i j) (frameBlockRaw F i j) + ≤ frameBlockRaw F i i ⊔ frameBlockRaw F j j := + Submodule.map₂_le.mpr fun _ hx _ hy => frameBlockRaw_mul_self_mem_sup F hij hx hy + +/-- `V_{ij} ∘ V_{ij} ⊆ ℝ∙pᵢ + ℝ∙pⱼ`. -/ +theorem map₂_frameBlockRaw_self_le_span [FiniteDimensional ℝ J] (F : JordanFrame J n) + {i j : Fin n} (hij : i ≠ j) : + Submodule.map₂ (jmulₗ J) (frameBlockRaw F i j) (frameBlockRaw F i j) + ≤ Submodule.span ℝ {F.p i} ⊔ Submodule.span ℝ {F.p j} := by + rw [← frameBlockRaw_self_eq_span F i, ← frameBlockRaw_self_eq_span F j] + exact map₂_frameBlockRaw_self_le F hij + +end Map₂ + +end EuclideanJordan diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/HermitianBilin.lean b/LeanPool/EuclideanJordan/EuclideanJordan/HermitianBilin.lean new file mode 100644 index 0000000000..27ec96bcfe --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/HermitianBilin.lean @@ -0,0 +1,74 @@ +/- +Copyright (c) 2026 Bryan Ehrlich. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Bryan Ehrlich +-/ +module + +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.Jordan +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.Inner + + + +/-! +# The Jordan product on `H_n(𝕜)` as a bundled bilinear map + +`EuclideanJordan/Vendor/HermitianMat/Jordan.lean` supplies `HermitianMat.symmMul`, the +Jordan product `A ∘ B = ½(AB + BA)`, together with the scoped `HermMul` instances that make +it the `*` of a `NonUnitalNonAssocCommRing`. Several results in this library are stated over +the *unbundled* form of the product instead — an ℝ-bilinear map `m : J →ₗ[ℝ] J →ₗ[ℝ] J`, see +`EuclideanJordan/Bridge.lean` for why. This file supplies that form for the concrete carrier, +so those results can be instantiated on `H_n(𝕜)`. + +The bilinearity proofs go through `symmMul_toMat` and `symmMul_comm`; the ℝ-linearity (rather +than 𝕜-linearity) is the right statement because `HermitianMat n 𝕜` is only an ℝ-module — +a 𝕜-multiple of a Hermitian matrix need not be Hermitian. +-/ + +@[expose] public section + +noncomputable section + +open scoped Matrix + +namespace EuclideanJordan + +variable {n : Type*} [Fintype n] [DecidableEq n] +variable {𝕜 : Type*} [RCLike 𝕜] + +theorem symmMul_add_rightG (a b c : HermitianMat n 𝕜) : + a.symmMul (b + c) = a.symmMul b + a.symmMul c := by + ext1 + simp only [HermitianMat.symmMul_toMat, HermitianMat.mat_add] + rw [Matrix.mul_add, Matrix.add_mul, ← smul_add] + congr 1 + abel + +omit [DecidableEq n] in +theorem symmMul_smul_rightG (t : ℝ) (a b : HermitianMat n 𝕜) : + a.symmMul (t • b) = t • a.symmMul b := by + ext1 + simp only [HermitianMat.symmMul_toMat, HermitianMat.mat_smul] + rw [Matrix.mul_smul, Matrix.smul_mul, ← smul_add, smul_comm] + +/-- The Euclidean Jordan product `x ∘ y = ½(xy + yx)` on `H_n(𝕜)` as an ℝ-bilinear map. -/ +def jordanBilinG (𝕜 : Type*) [RCLike 𝕜] : + HermitianMat n 𝕜 →ₗ[ℝ] HermitianMat n 𝕜 →ₗ[ℝ] HermitianMat n 𝕜 := + LinearMap.mk₂ ℝ (fun a b => a.symmMul b) + (fun a a' b => by + change (a + a').symmMul b = a.symmMul b + a'.symmMul b + rw [HermitianMat.symmMul_comm, symmMul_add_rightG] + rw [HermitianMat.symmMul_comm (A := b) (B := a), + HermitianMat.symmMul_comm (A := b) (B := a')]) + (fun t a b => by + change (t • a).symmMul b = t • a.symmMul b + rw [HermitianMat.symmMul_comm, symmMul_smul_rightG, + HermitianMat.symmMul_comm (A := b) (B := a)]) + (fun a b b' => symmMul_add_rightG a b b') + (fun t a b => symmMul_smul_rightG t a b) + +@[simp] +theorem jordanBilin_applyG (a b : HermitianMat n 𝕜) : + jordanBilinG 𝕜 a b = a.symmMul b := rfl + +end EuclideanJordan diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/HermitianCarrier.lean b/LeanPool/EuclideanJordan/EuclideanJordan/HermitianCarrier.lean new file mode 100644 index 0000000000..ad7716b139 --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/HermitianCarrier.lean @@ -0,0 +1,369 @@ +/- +Copyright (c) 2026 Bryan Ehrlich. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Bryan Ehrlich +-/ +module + +public import LeanPool.EuclideanJordan.EuclideanJordan.FramePeirce +public import LeanPool.EuclideanJordan.EuclideanJordan.Witness +public import LeanPool.EuclideanJordan.EuclideanJordan.HermitianBilin + + + +/-! +# A carrier for `EuclideanJordanAlgebra` + +`EuclideanJordan/Class.lean` names the Euclidean Jordan algebra hypothesis as a class, and + `EuclideanJordan/Rank.lean`, +`EuclideanJordan/FrameExists.lean` and `EuclideanJordan/FramePeirce.lean` state their results + over it. Until this file +the **only** instances of that class in the tree were `EuclideanJordan/PeirceSubalgebra.lean`'s + two, and both +are conditional on an ambient `[EuclideanJordanAlgebra J]`, so nothing exhibited a base model: +`exists_jordanFrame` and `frameBlock_isInternal` were universally quantified over a class no +object was known to inhabit. + +This file supplies the base model. `H_n(𝕜)` — Hermitian matrices over an `RCLike` field, under +the symmetrized product `A ∘ B = ½(AB + BA)` and the real trace form — is a +`EuclideanJordanAlgebra`. + +★ **This is the same exposure the layer has had to close for every other hypothesis it carries.** +An abstract theorem whose hypothesis no carrier is known to satisfy is worth nothing; +`EuclideanJordan/Witness.lean`'s `instIsFormallyReal` and `diagFrame_orthIdem` close it for +`IsFormallyReal` and `IsOrthIdemFamily`, and this file closes it for `EuclideanJordanAlgebra`. + +★ The class was **not** the only unwitnessed hypothesis left in the layer, and an earlier draft +of this docstring said it was. `EuclideanJordan/Rank.lean`'s `IsPrimitive` and `JordanFrame` + had no witness +either — `EuclideanJordan/Witness.lean` builds the diagonal matrix units but never proves one + primitive, so +no `JordanFrame` had ever been constructed. Both are witnessed at the end of this file, but by a +separate argument, and the class instance alone would not have supplied them. + +## No field needed new mathematics + +Every one of the class's six field obligations was already proved in the tree, in +`jordanBilinG` or `symmMul` vocabulary: + +| class field | discharged by | +| --- | --- | +| `mul_comm` | `HermitianMat.symmMul_comm` | +| `add_mul` | bilinearity of `EuclideanJordan/HermitianBilin.lean`'s `jordanBilinG` | +| `smul_mul` | bilinearity of `EuclideanJordan/HermitianBilin.lean`'s `jordanBilinG` | +| `one_mul` | `HermitianMat.one_symmMul` | +| `jordan` | `EuclideanJordan/Order.lean`'s `hermitian_jordan_id` (the vendored `IsCommJordan`) | +| `inner_assoc` | `EuclideanJordan/Order.lean`'s `hermitian_jordan_assoc` | + +The three lemmas before the instance are re-orientations, not new results: two unbundle +`jordanBilinG`'s `LinearMap` structure, and `hermitian_symmMul_jordan` turns Mathlib's +`lmul_comm_rmul_rmul` orientation into the class's by commuting the product twice. + +## The diamond `EuclideanJordan/Class.lean` warned about, measured + +`EuclideanJordan/Class.lean` records that `instNonUnitalNonAssocCommRing` fires on any type + carrying the +class, that `HermitianMat d 𝕜` already carries a `Mul` from +`EuclideanJordan/Vendor/HermitianMat/Jordan.lean`'s `scoped instance : CommMagma`, and that +"if one is ever declared, that scoped instance and this class's `toMul` will both be in scope +inside `open HermMul` sections and one of them has to give way." + +That is now declared, and the collision is **definitional**: `hermMul_toMul_eq` below is `rfl`. +Both `Mul` structures have `HermitianMat.symmMul` as their multiplication, so which one instance +search returns does not change any statement. Concretely, inside an `open HermMul` section with +this instance in scope, `#synth Mul (HermitianMat (Fin 2) ℂ)` returns *this* instance's `toMul` +(the global instance outranks the opened scoped one), `HermMul.mul_eq_symmMul` still typechecks, +and `EuclideanJordan/Order.lean`'s two `open HermMul` proofs — `hermitian_jordan_id` and +`hermitian_formallyReal` — still go through unaltered. + +★ Two things this does *not* claim. It is not claimed that the collision is invisible: instance +search does return a different term inside `open HermMul` once this module is imported, and a +proof that pins the instance by name rather than by its product would notice. And it is not +claimed that anything currently pays that cost. ★ Every `open HermMul` site in this library — +`EuclideanJordan/Order.lean`, `EuclideanJordan/Spectral.lean` and `EuclideanJordan/Witness.lean` — +is strictly *upstream* of this file: this file's import closure contains all three, and imports +are directional, so none of them can see this instance. ★ A **downstream** module that both +imports this file and opens `HermMul` is a different matter: it would see the collision, and would +have to navigate it rather than avoid it, bridging with `hermitian_mul_eq_symmMul`. That does not +change what the three sites above see. + +★ One elaboration trap, recorded because it cost a probe round. Inside the structure-instance +notation, `simpa only [...] using e` **fails** on fields whose statement is in `*` form while `e` +is in `symmMul` form, reporting a type mismatch between `(A + B) * C` and `(A + B).symmMul C` +even though the two are `rfl`-equal (`hermitian_mul_eq_symmMul` below). The named-lemma route +(`add_mul A B C := hermitian_symmMul_add_left A B C`) succeeds on the same goal. So the +mismatch is `simpa`'s reducibility setting, not a real failure of the defeq — do not conclude +from such a message that the product is wrong. + +## Scope + +This file is substrate: it proves no new theorem about Jordan algebras. What changes is that the +M3/M4 results are now known to be about a nonempty class: `hermitian_exists_jordanFrame` and +`hermitian_frameBlock_isInternal` are `exists_jordanFrame` and `frameBlock_isInternal` read on +`H_n(𝕜)`. + +★ The last section goes past existence and **names a frame** on `H_n(ℂ)`: `diagJordanFrame`, the +diagonal matrix units of `EuclideanJordan/Witness.lean` reindexed along `Fintype.equivFin`. + Unlike the class +instance, that is not repackaging: `diagFrame_isPrimitive` is the one obligation of `JordanFrame` +that no lemma in the tree discharges — `diagFrame_orthIdem` gives `orthIdem` and `diagFrame_sum` +gives `complete`, and `p` is data — and its proof and the four entrywise lemmas under it are +written here. (`hermitian_one_ne_zero` is new too, but it is three lines and reads off one +matrix entry.) ★ Note what primitivity is **not**: it is not +`dim V_ii = 1`, and this file does not prove that. Two scope limits on the frame: it is over +`ℂ` only, because `EuclideanJordan/Witness.lean`'s `diagFrame` is, and its cardinality is + `Fintype.card n`, +which is **not** proved to be the rank — `EuclideanJordan/Rank.lean` proves only `card ≤ rank` and +`card ≤ finrank`. +-/ + +@[expose] public section + +noncomputable section + +namespace EuclideanJordan + +variable {n : Type*} [Fintype n] [DecidableEq n] {𝕜 : Type*} [RCLike 𝕜] + +/-! ## The three re-orientations the class fields need -/ + +/-- Additivity of `∘` in its left argument, from `jordanBilinG`'s `LinearMap` structure. -/ +theorem hermitian_symmMul_add_left (A B C : HermitianMat n 𝕜) : + (A + B).symmMul C = A.symmMul C + B.symmMul C := by + simpa only [jordanBilin_applyG] using (jordanBilinG (n := n) 𝕜).map_add₂ A B C + +omit [DecidableEq n] in +/-- Real homogeneity of `∘` in its left argument, from `jordanBilinG`'s `LinearMap` structure. -/ +theorem hermitian_symmMul_smul_left (r : ℝ) (A B : HermitianMat n 𝕜) : + (r • A).symmMul B = r • (A.symmMul B) := by + classical + simpa only [jordanBilin_applyG] using (jordanBilinG (n := n) 𝕜).map_smul₂ r A B + +omit [DecidableEq n] in +/-- The Jordan identity in the class's orientation, `x ∘ (x² ∘ y) = x² ∘ (x ∘ y)`. +`EuclideanJordan/Order.lean`'s `hermitian_jordan_id` carries Mathlib's orientation +`(x ∘ y) ∘ x² = x ∘ (y ∘ x²)`; the two differ by commuting the product twice. -/ +theorem hermitian_symmMul_jordan (A B : HermitianMat n 𝕜) : + A.symmMul ((A.symmMul A).symmMul B) = (A.symmMul A).symmMul (A.symmMul B) := by + classical + have h := hermitian_jordan_id (n := n) (𝕜 := 𝕜) A B + simp only [jordanBilin_applyG] at h + rw [HermitianMat.symmMul_comm (A.symmMul A) B, ← h] + exact HermitianMat.symmMul_comm _ _ + +/-! ## The instance -/ + +/-- ★★★ **`H_n(𝕜)` is a Euclidean Jordan algebra.** The class of `EuclideanJordan/Class.lean`, +on the +Hermitian-matrix carrier, at the generality `EuclideanJordan/Order.lean` uses: any `RCLike` scalar +field and any finite decidable index type. -/ +instance instEuclideanJordanAlgebraHermitianMat : + EuclideanJordanAlgebra (HermitianMat n 𝕜) where + mul := HermitianMat.symmMul + one := 1 + mul_comm A B := HermitianMat.symmMul_comm A B + add_mul A B C := hermitian_symmMul_add_left A B C + smul_mul r A B := hermitian_symmMul_smul_left r A B + one_mul A := HermitianMat.one_symmMul A + jordan A B := hermitian_symmMul_jordan A B + inner_assoc A B C := hermitian_jordan_assoc A B C + +/-! ### Fidelity: the class's product is the intended one + +Inhabitedness alone would be satisfied by any product making the fields true. These three +`rfl`s pin the instance's `*` to the three names the rest of the tree uses for `A ∘ B`. -/ + +/-- The class's product on `H_n(𝕜)` is `HermitianMat.symmMul`. -/ +theorem hermitian_mul_eq_symmMul (A B : HermitianMat n 𝕜) : A * B = A.symmMul B := rfl + +/-- The class's product on `H_n(𝕜)` is `jordanBilinG`, the bundled bilinear form of the same +product. -/ +theorem hermitian_mul_eq_jordanBilinG (A B : HermitianMat n 𝕜) : + A * B = jordanBilinG 𝕜 A B := rfl + +/-- ★ **The `HermMul` collision is definitional.** The `Mul` this instance supplies and the one +`EuclideanJordan/Vendor/HermitianMat/Jordan.lean`'s scoped `CommMagma` supplies are the same +structure, so which one instance search returns inside an `open HermMul` section cannot change a +statement. See the module docstring for what this does and does not claim. -/ +theorem hermMul_toMul_eq : + (HermMul.instCommMagmaHermitianMat (d := n) (𝕜 := 𝕜)).toMul + = (instEuclideanJordanAlgebraHermitianMat (n := n) (𝕜 := 𝕜)).toMul := rfl + +/-! ## Nontriviality -/ + +/-- `1 ≠ 0` in `H_n(𝕜)` whenever the index type is nonempty: read off the `(i, i)` entry. -/ +theorem hermitian_one_ne_zero [Nonempty n] : (1 : HermitianMat n 𝕜) ≠ 0 := by + intro h + have h2 := congrArg + (fun A : HermitianMat n 𝕜 => A.mat (Classical.arbitrary n) (Classical.arbitrary n)) h + simp only [HermitianMat.mat_one, HermitianMat.mat_zero, Matrix.one_apply_eq, + Matrix.zero_apply] at h2 + exact one_ne_zero h2 + +omit [Fintype n] [DecidableEq n] in +instance instNontrivialHermitianMat [Finite n] [Nonempty n] : + Nontrivial (HermitianMat n 𝕜) := by + let : DecidableEq n := Classical.decEq n + let := Fintype.ofFinite n + exact ⟨⟨1, 0, hermitian_one_ne_zero⟩⟩ + +/-! ## M3 and M4 on the carrier + +The two results now read on a live object rather than on a class with no known inhabitant. +Neither proof is new: each is the abstract theorem with `J := H_n(𝕜)`. -/ + +/-- **(M3) on `H_n(𝕜)`.** `EuclideanJordan/FrameExists.lean`'s `exists_jordanFrame`, +instantiated. The +finite-dimensionality it needs is `HermitianMat.FiniteDimensional`; the nontriviality is +`hermitian_one_ne_zero`. -/ +theorem hermitian_exists_jordanFrame [Nonempty n] : + ∃ k, Nonempty (JordanFrame (HermitianMat n 𝕜) k) := + exists_jordanFrame (HermitianMat n 𝕜) hermitian_one_ne_zero + +/-- **(M4) on `H_n(𝕜)`.** `EuclideanJordan/FramePeirce.lean`'s `frameBlock_isInternal`, +instantiated: a +Jordan frame of `H_n(𝕜)` splits it as the internal direct sum of its diagonal and coherence +blocks. -/ +theorem hermitian_frameBlock_isInternal {k : ℕ} (F : JordanFrame (HermitianMat n 𝕜) k) : + DirectSum.IsInternal (frameBlock F) := + frameBlock_isInternal F + +/-- **(M3) and (M4) together on `H_n(𝕜)`**: the algebra has a Jordan frame, and that frame +decomposes it. This is the statement that was universally quantified over an unwitnessed class +before this file. -/ +theorem hermitian_exists_frame_isInternal [Nonempty n] : + ∃ (k : ℕ) (F : JordanFrame (HermitianMat n 𝕜) k), DirectSum.IsInternal (frameBlock F) := by + obtain ⟨k, ⟨F⟩⟩ := hermitian_exists_jordanFrame (n := n) (𝕜 := 𝕜) + exact ⟨k, F, frameBlock_isInternal F⟩ + +/-! ## A named frame on `H_n(ℂ)` + +`EuclideanJordan/Witness.lean` builds the diagonal matrix units `E_ii` and proves them orthogonal +idempotents summing to the unit. That is three of `JordanFrame`'s four fields. The missing one +is **primitivity**, which is proved here. + +The argument is entrywise and short. If `d` is fixed by `E_ii ∘ -` then reading the `(a, b)` +entry of `½(E_ii D + D E_ii) = D` gives `D_ab = 0` off `(i, i)` — the three off-diagonal cases +reduce to `½ D_ab = D_ab` or to `0 = D_ab` — so `D = D_ii · E_ii`. Idempotency then forces +`D_ii² = D_ii` at the surviving entry, so `D_ii ∈ {0, 1}` and `d` is `0` or `E_ii`. -/ + +section Frame + +open Witness + +variable {n : Type*} [Fintype n] [DecidableEq n] + +omit [Fintype n] in +/-- The diagonal matrix unit entrywise: `1` at `(i, i)` and `0` elsewhere. -/ +theorem diagFrame_mat_apply (i a b : n) : + (diagFrame i).mat a b = if a = b then (if a = i then (1 : ℂ) else 0) else 0 := by + rw [diagFrame_mat, Matrix.diagonal_apply] + split_ifs <;> simp_all + +/-- The entries of `E_ii ∘ D`. Left multiplication by `E_ii` keeps row `i`, right +multiplication keeps column `i`, so the symmetrized product halves everything meeting the cross +at `i` exactly once and kills everything off it. -/ +theorem diagFrame_symmMul_mat_apply (i : n) (D : HermitianMat n ℂ) (a b : n) : + ((diagFrame i).symmMul D).mat a b + = (2 : ℂ)⁻¹ * ((if a = i then D.mat a b else 0) + (if b = i then D.mat a b else 0)) := by + rw [HermitianMat.symmMul_toMat] + simp only [Matrix.smul_apply, Matrix.add_apply, Matrix.mul_apply, smul_eq_mul] + congr 1 + congr 1 + · rw [Finset.sum_eq_single a] + · rw [diagFrame_mat_apply]; simp + · intro k _ hk; rw [diagFrame_mat_apply]; simp [Ne.symm hk] + · intro h; exact absurd (Finset.mem_univ a) h + · rw [Finset.sum_eq_single b] + · rw [diagFrame_mat_apply]; simp [eq_comm] + · intro k _ hk; rw [diagFrame_mat_apply]; simp [hk] + · intro h; exact absurd (Finset.mem_univ b) h + +/-- **An element fixed by `E_ii ∘ -` lives at the single entry `(i, i)`.** Note this uses only +the fixed-point hypothesis, not idempotency of `D`. -/ +theorem mat_eq_zero_of_diagFrame_fixed {i : n} {D : HermitianMat n ℂ} + (h : (diagFrame i).symmMul D = D) {a b : n} (hab : ¬(a = i ∧ b = i)) : + D.mat a b = 0 := by + have he := congrArg (fun M : HermitianMat n ℂ => M.mat a b) h + simp only [diagFrame_symmMul_mat_apply] at he + by_cases ha : a = i <;> by_cases hb : b = i + · exact absurd ⟨ha, hb⟩ hab + · rw [ite_eq_left ha, ite_eq_right hb, add_zero] at he + linear_combination -2 * he + · rw [ite_eq_right ha, ite_eq_left hb, zero_add] at he + linear_combination -2 * he + · rw [ite_eq_right ha, ite_eq_right hb, add_zero, mul_zero] at he + exact he.symm + +/-- So such an element is a scalar multiple of `E_ii`. -/ +theorem mat_eq_smul_of_diagFrame_fixed {i : n} {D : HermitianMat n ℂ} + (h : (diagFrame i).symmMul D = D) : + D.mat = D.mat i i • (diagFrame i).mat := by + ext a b + rw [Matrix.smul_apply, diagFrame_mat_apply, smul_eq_mul] + by_cases hab : a = b + · subst hab + by_cases ha : a = i + · subst ha; simp + · rw [mat_eq_zero_of_diagFrame_fixed h (fun hc => ha hc.1)]; simp [ha] + · rw [mat_eq_zero_of_diagFrame_fixed h (fun hc => hab (hc.1.trans hc.2.symm))]; simp [hab] + +/-- ★★ **The diagonal matrix units are primitive.** The one obligation of `JordanFrame` that +`EuclideanJordan/Witness.lean` leaves open, and the only new mathematics in this file. -/ +theorem diagFrame_isPrimitive (i : n) : IsPrimitive (diagFrame (d := n) i) := by + refine ⟨(diagFrame_orthIdem (d := n)).idem i, ?_, ?_⟩ + · intro h + have hii := congrArg (fun M : HermitianMat n ℂ => M.mat i i) h + rw [diagFrame_mat_apply] at hii + simp at hii + · intro D hD hcD + have hsupp : D.mat = D.mat i i • (diagFrame i).mat := mat_eq_smul_of_diagFrame_fixed hcD + -- the Jordan square of `D` is its matrix square, since `D` commutes with itself + have hD' : D.symmMul D = D := hD + have hsq : D.mat * D.mat = D.mat := by + have hm := congrArg HermitianMat.mat hD' + rwa [HermitianMat.symmMul_self] at hm + have hii : D.mat i i * D.mat i i = D.mat i i := by + have hentry := congrFun (congrFun hsq i) i + rw [Matrix.mul_apply, Finset.sum_eq_single i] at hentry + · exact hentry + · intro k _ hk + rw [mat_eq_zero_of_diagFrame_fixed hcD (fun hc => hk hc.2), zero_mul] + · intro h; exact absurd (Finset.mem_univ i) h + have hfac : D.mat i i * (D.mat i i - 1) = 0 := by linear_combination hii + rcases mul_eq_zero.mp hfac with h0 | h1 + · left + apply HermitianMat.ext + rw [hsupp, h0, zero_smul, HermitianMat.mat_zero] + · right + apply HermitianMat.ext + rw [hsupp, sub_eq_zero.mp h1, one_smul] + +/-- ★★★ **A Jordan frame on `H_n(ℂ)`, named.** The diagonal matrix units, reindexed along +`Fintype.equivFin` because `JordanFrame` is `Fin`-indexed. ★ Its cardinality `Fintype.card n` +is **not** claimed to be the rank of the algebra — `EuclideanJordan/Rank.lean` proves only that + a frame's +cardinality is bounded by the rank and by the dimension. -/ +noncomputable def diagJordanFrame : JordanFrame (HermitianMat n ℂ) (Fintype.card n) where + p k := diagFrame ((Fintype.equivFin n).symm k) + orthIdem := + ⟨fun _ => (diagFrame_orthIdem (d := n)).idem _, + fun _ _ hkl => (diagFrame_orthIdem (d := n)).orth _ _ + ((Fintype.equivFin n).symm.injective.ne hkl)⟩ + primitive _ := diagFrame_isPrimitive _ + complete := by + rw [Equiv.sum_comp (Fintype.equivFin n).symm diagFrame] + exact diagFrame_sum + +@[simp] theorem diagJordanFrame_p (k : Fin (Fintype.card n)) : + (diagJordanFrame (n := n)).p k = diagFrame ((Fintype.equivFin n).symm k) := rfl + +/-- **(M4) on a named frame.** `H_n(ℂ)` is the internal direct sum of the Peirce blocks of the +diagonal frame — the frame Peirce decomposition with nothing left quantified. -/ +theorem diagJordanFrame_isInternal : + DirectSum.IsInternal (frameBlock (diagJordanFrame (n := n))) := + frameBlock_isInternal _ + +end Frame + +end EuclideanJordan diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/Order.lean b/LeanPool/EuclideanJordan/EuclideanJordan/Order.lean new file mode 100644 index 0000000000..c5ed0ebf7e --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/Order.lean @@ -0,0 +1,676 @@ +/- +Copyright (c) 2026 Bryan Ehrlich. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Bryan Ehrlich +-/ +module + +public import LeanPool.EuclideanJordan.EuclideanJordan.OrderUnitSpace +public import LeanPool.EuclideanJordan.EuclideanJordan.Spectral +public import LeanPool.EuclideanJordan.EuclideanJordan.HermitianBilin + + + +/-! +# The order structure on a Euclidean Jordan algebra + +The rest of this library runs on `[NonUnitalNonAssocCommRing J] [IsCommJordan J] [Module ℝ J]`, +which carries no order at all. This file supplies one: `orderUnitSpaceOfBilinear` produces an +`OrderUnitSpace J` for `J` a finite-dimensional formally real Jordan algebra, with the **cone +of sums of squares** as the positive cone and the Jordan unit as the order unit. + +## The cone, and why it is sums of squares rather than squares + +`0 ≤ x` is defined here as "`x` is a finite sum of Jordan squares" (`IsSoS`). The alternative — +`x` is a *single* square — is the right reading, but it is not usable as a *definition*: +closure of the single-square set under addition is not available before the spectral theorem, +whereas closure of the sums-of-squares set under addition is a concatenation of index sets. The +two readings are then **proved equal** in `isSoS_iff_exists_sq` — under the Euclidean +hypothesis of the third section, and not before. + +Each field of `OrderUnitSpace` is paid for by exactly one thing: + +| field | what pays for it | +| --- | --- | +| `le_refl`, `le_trans` | the cone contains `0` and is closed under `+` | +| `le_antisymm` | **formal reality** (`eq_zero_of_isSoS_of_isSoS_neg`) | +| `add_le_add_left` | the order is a difference condition | +| `smul_nonneg_mono` | `r • (y ∘ y) = (√r • y) ∘ (√r • y)` for `r ≥ 0` | +| `ousUnit_nonneg` | the unit is idempotent, hence a square | +| `archimedean` (order-unit boundedness) | **the spectral theorem** | + +★ **The spectral theorem is what buys order-unit boundedness.** +`spectral_resolution_bilinear` writes `x = ∑ lam i • q i` over an orthogonal idempotent family +summing to `e`, so `r • e - x = ∑ (r - lam i) • q i` is a sum of nonnegative multiples of +idempotents — a sum of squares — for any `r` dominating every `lam i`. The bound taken here is +`∑ i, |lam i|` rather than `max lam`: it dominates every coefficient, is manifestly +nonnegative, and needs no nonemptiness side condition when the resolution is empty. + +## Shape: a hypothesis-carrying `def` in bilinear-map vocabulary, not an instance + +Two deliberate choices, both forced by diamonds. + +1. **A `def`, never an `instance`.** A global `OrderUnitSpace` instance keyed on the Jordan + typeclasses would fire on `HermitianMat d 𝕜`, which already carries a `PartialOrder` and a + `Norm` from the vendored Loewner structure, putting two of each on the concrete carrier. + Consumers write `letI := orderUnitSpaceOfBilinear …`, exactly as + `EuclideanJordan/Bridge.lean`'s `ringOfBilinear` is used. +2. **Bilinear-map vocabulary.** Every statement takes the Jordan product as + `m : J →ₗ[ℝ] J →ₗ[ℝ] J` over `[NormedAddCommGroup J] [InnerProductSpace ℝ J]`, and reaches + the ring vocabulary only *inside* proofs, via `ringOfBilinear`. Assuming + `[NonUnitalNonAssocCommRing J]` and `[NormedAddCommGroup J]` together would give two + `AddCommGroup J` instances, which is the diamond `EuclideanJordan/Bridge.lean` was written + to dodge. Only one `AddCommGroup` is ever in play here, and the produced structure's + `toNormedAddCommGroup` is the ambient instance on the nose + (`normedAddCommGroup_ofBilinear`, proved by `rfl`). + +## Scope — what is and is not proved here + +* **The Euclidean hypothesis `hassoc` is carried, not derived.** Six declarations in the third + section — `inner_mul_self_nonneg_of_idem`, `inner_left_coeff`, `nonneg_coeff_of_inner_nonneg`, + `nonneg_coeff_of_isSoS`, `isArchimedean_ofBilinear`, `isSoS_iff_exists_sq` — assume an + associative inner product, `⟪x ∘ y, z⟫ = ⟪y, x ∘ z⟫`. That is Faraut–Korányi's definition of + *Euclidean* Jordan algebra (FK III.1), and over ℝ in finite dimension it is equivalent to + formal reality — but that equivalence needs the trace form and **is not formalized here**. + Both directions of the dependency are therefore hypotheses, and `hermitian_jordan_assoc` + supplies a live carrier for the new one so that no theorem is conditional on an uninhabited + premise. +* **The constructed order is the Loewner order on `H_n(𝕜)`.** Both containments are proved — + `hermitian_isSoS_iff_nonneg`, `hermitian_le_ofEJA_iff`. No square root on the carrier is + needed for this: the spectral idempotents are themselves positive semidefinite, so + `HermitianMat.inner_ge_zero` makes `⟪q i, A⟫ ≥ 0` and `inner_left_coeff` reads the + coefficient off. +-/ + +@[expose] public section + +noncomputable section + +namespace EuclideanJordan + +open Finset + +/-! ## The cone of sums of squares + +Nothing in this section mentions a norm or an inner product; the ambient structure is the +additive group and the `ℝ`-module, which is all the cone algebra needs. -/ + +section Cone + +variable {J : Type*} [AddCommGroup J] [Module ℝ J] + +/-- **The positive cone**: `z` is a finite sum of squares of the bilinear product `m`. + +The empty sum is allowed, so `0` is in the cone by `k = 0`. -/ +def IsSoS (m : J →ₗ[ℝ] J →ₗ[ℝ] J) (z : J) : Prop := + ∃ (k : ℕ) (f : Fin k → J), z = ∑ i, m (f i) (f i) + +variable {m : J →ₗ[ℝ] J →ₗ[ℝ] J} + +theorem isSoS_zero : IsSoS m 0 := ⟨0, fun i => i.elim0, by simp⟩ + +/-- The cone is closed under addition — the whole reason it is stated with sums of squares +rather than squares: the witness is a concatenation of index sets. -/ +theorem IsSoS.add {a b : J} (ha : IsSoS m a) (hb : IsSoS m b) : IsSoS m (a + b) := by + obtain ⟨k, f, hf⟩ := ha + obtain ⟨l, g, hg⟩ := hb + refine ⟨k + l, Fin.append f g, ?_⟩ + rw [Fin.sum_univ_add] + simp only [Fin.append_left, Fin.append_right] + rw [← hf, ← hg] + +theorem isSoS_sum {ι : Type*} (s : Finset ι) (g : ι → J) (h : ∀ i ∈ s, IsSoS m (g i)) : + IsSoS m (∑ i ∈ s, g i) := by + classical + induction s using Finset.induction with + | empty => simpa using isSoS_zero + | insert a s ha ih => + rw [Finset.sum_insert ha] + exact (h a (Finset.mem_insert_self a s)).add + (ih fun i hi => h i (Finset.mem_insert_of_mem hi)) + +/-- The cone is closed under nonnegative scalars: `r • (y ∘ y) = (√r • y) ∘ (√r • y)`. -/ +theorem IsSoS.smul {r : ℝ} (hr : 0 ≤ r) {a : J} (ha : IsSoS m a) : IsSoS m (r • a) := by + obtain ⟨k, f, hf⟩ := ha + refine ⟨k, fun i => Real.sqrt r • f i, ?_⟩ + rw [hf, Finset.smul_sum] + refine Finset.sum_congr rfl fun i _ => ?_ + simp only [map_smul, LinearMap.smul_apply, smul_smul] + rw [Real.mul_self_sqrt hr] + +theorem isSoS_of_idem {c : J} (hc : m c c = c) : IsSoS m c := + ⟨1, fun _ => c, by simp [hc]⟩ + +theorem isSoS_smul_idem {r : ℝ} (hr : 0 ≤ r) {c : J} (hc : m c c = c) : IsSoS m (r • c) := + (isSoS_of_idem hc).smul hr + +/-- **Antisymmetry of the cone, and the only place formal reality is used in this file.** + +If `a` and `-a` are both sums of squares then the concatenated family has vanishing sum of +squares, so formal reality kills every member of it — including every member of `a`'s own +family. -/ +theorem eq_zero_of_isSoS_of_isSoS_neg + (hfr : ∀ (k : ℕ) (f : Fin k → J), (∑ i, m (f i) (f i)) = 0 → ∀ i, f i = 0) + {a : J} (ha : IsSoS m a) (hna : IsSoS m (-a)) : a = 0 := by + obtain ⟨k, f, hf⟩ := ha + obtain ⟨l, g, hg⟩ := hna + have hsum : (∑ i, m (Fin.append f g i) (Fin.append f g i)) = 0 := by + rw [Fin.sum_univ_add] + simp only [Fin.append_left, Fin.append_right] + rw [← hf, ← hg, add_neg_cancel] + have hz := hfr (k + l) (Fin.append f g) hsum + rw [hf, Finset.sum_eq_zero] + intro i _ + have hi := hz (Fin.castAdd l i) + rw [Fin.append_left] at hi + simp [hi] + +/-- The partial order induced by the cone: `x ≤ y` iff `y - x` is a sum of squares. -/ +@[instance_reducible] +def partialOrderOfSoS (m : J →ₗ[ℝ] J →ₗ[ℝ] J) + (hfr : ∀ (k : ℕ) (f : Fin k → J), (∑ i, m (f i) (f i)) = 0 → ∀ i, f i = 0) : + PartialOrder J where + le x y := IsSoS m (y - x) + le_refl x := by simpa using isSoS_zero + le_trans x y z hxy hyz := by + show IsSoS m (z - x) + rw [← sub_add_sub_cancel z y x] + exact hyz.add hxy + le_antisymm x y hxy hyx := by + have h : y - x = 0 := + eq_zero_of_isSoS_of_isSoS_neg hfr hxy (by rw [neg_sub]; exact hyx) + exact (sub_eq_zero.mp h).symm + +/-- The rearrangement both the order-unit bound and the Archimedean squeeze run on: against a +complete orthogonal idempotent family, `r • e - x` is again diagonal, with coefficients +`r - lam i`. -/ +theorem smul_unit_sub_eq {n : ℕ} {q : Fin n → J} {lam : Fin n → ℝ} {e x : J} + (hsum : (∑ i, q i) = e) (hx : x = ∑ i, lam i • q i) (r : ℝ) : + r • e - x = ∑ i, (r - lam i) • q i := by + rw [← hsum, hx, Finset.smul_sum, ← Finset.sum_sub_distrib] + exact Finset.sum_congr rfl fun i _ => (sub_smul _ _ _).symm + +end Cone + +/-! ## The order unit space + +From here the ambient structure is a finite-dimensional real inner product space carrying the +Jordan product as a bundled bilinear map. -/ + +section OrderUnit + +variable {J : Type*} [NormedAddCommGroup J] [InnerProductSpace ℝ J] [FiniteDimensional ℝ J] +variable {m : J →ₗ[ℝ] J →ₗ[ℝ] J} + +/-- **Order-unit boundedness, read off the spectral resolution.** This is the field the EJA +layer had no way to supply before `EuclideanJordan/Spectral.lean`. -/ +theorem exists_isSoS_smul_unit_sub + (hcomm : ∀ x y : J, m x y = m y x) + (hjordan : ∀ a b : J, m (m a b) (m a a) = m a (m b (m a a))) + (hfr : ∀ (k : ℕ) (f : Fin k → J), (∑ i, m (f i) (f i)) = 0 → ∀ i, f i = 0) + (e : J) (he : ∀ y : J, m e y = y) (x : J) : + ∃ r : ℝ, 0 ≤ r ∧ IsSoS m (r • e - x) := by + obtain ⟨n, q, lam, hidem, _horth, hsum, hx⟩ := + spectral_resolution_bilinear m hcomm hjordan hfr e he x + refine ⟨∑ i, |lam i|, Finset.sum_nonneg fun i _ => abs_nonneg _, ?_⟩ + have hle : ∀ i, lam i ≤ ∑ j, |lam j| := fun i => + le_trans (le_abs_self _) + (Finset.single_le_sum (f := fun j => |lam j|) (fun j _ => abs_nonneg _) (Finset.mem_univ i)) + rw [smul_unit_sub_eq hsum hx] + exact isSoS_sum _ _ fun i _ => isSoS_smul_idem (by linarith [hle i]) (hidem i) + +/-- **A Euclidean Jordan algebra is an order unit space**, with the cone of sums of squares +as the positive cone and the Jordan unit as the order unit. + +A `def`, not an `instance` — see the module docstring. The `NormedAddCommGroup` and +`NormedSpace` parents are filled from the ambient instances, so no second normed structure is +created. -/ +@[instance_reducible] +def orderUnitSpaceOfBilinear (m : J →ₗ[ℝ] J →ₗ[ℝ] J) + (hcomm : ∀ x y : J, m x y = m y x) + (hjordan : ∀ a b : J, m (m a b) (m a a) = m a (m b (m a a))) + (hfr : ∀ (k : ℕ) (f : Fin k → J), (∑ i, m (f i) (f i)) = 0 → ∀ i, f i = 0) + (e : J) (he : ∀ y : J, m e y = y) : + OrderUnitSpace J := + { (inferInstance : NormedAddCommGroup J), (inferInstance : NormedSpace ℝ J), + partialOrderOfSoS m hfr with + add_le_add_left := fun a b h c => by + change IsSoS m (c + b - (c + a)) + rw [show c + b - (c + a) = b - a by abel] + exact h + ousUnit := e + smul_nonneg_mono := fun r hr {a b} h => by + change IsSoS m (r • b - r • a) + rw [← smul_sub] + exact IsSoS.smul hr h + ousUnit_nonneg := by + change IsSoS m (e - 0) + rw [sub_zero] + exact isSoS_of_idem (he e) + archimedean := fun a => exists_isSoS_smul_unit_sub hcomm hjordan hfr e he a } + +section Characterization + +variable (m) +variable (hcomm : ∀ x y : J, m x y = m y x) + (hjordan : ∀ a b : J, m (m a b) (m a a) = m a (m b (m a a))) + (hfr : ∀ (k : ℕ) (f : Fin k → J), (∑ i, m (f i) (f i)) = 0 → ∀ i, f i = 0) + (e : J) (he : ∀ y : J, m e y = y) + +/-- **No second normed structure.** The produced order unit space's normed group is the +ambient one on the nose — the check that the `ringOfBilinear` diamond stays shut. -/ +theorem normedAddCommGroup_ofBilinear : + (orderUnitSpaceOfBilinear m hcomm hjordan hfr e he).toNormedAddCommGroup + = (inferInstance : NormedAddCommGroup J) := rfl + +theorem le_ofBilinear (x y : J) : + letI := orderUnitSpaceOfBilinear m hcomm hjordan hfr e he + x ≤ y ↔ IsSoS m (y - x) := Iff.rfl + +theorem ousUnit_ofBilinear : + @OrderUnitSpace.ousUnit J (orderUnitSpaceOfBilinear m hcomm hjordan hfr e he) = e := rfl + +/-- The effect space at EJA generality: the interval `[0, e]`, unfolded to the two cone +conditions that define it. -/ +theorem isEffect_ofBilinear (a : J) : + @OrderUnitSpace.IsEffect J (orderUnitSpaceOfBilinear m hcomm hjordan hfr e he) a + ↔ IsSoS m a ∧ IsSoS m (e - a) := by + constructor + · rintro ⟨h0, h1⟩ + refine ⟨?_, h1⟩ + have h0' : IsSoS m (a - 0) := h0 + rwa [sub_zero] at h0' + · rintro ⟨h0, h1⟩ + refine ⟨?_, h1⟩ + change IsSoS m (a - 0) + rwa [sub_zero] + +/-- `EuclideanJordan/OrderUnitSpace.lean`'s spanning theorem, live at EJA generality. It is +here as evidence that the abstract effect API genuinely applies to the constructed structure, +not merely that the structure typechecks. -/ +theorem span_isEffect_eq_top_ofBilinear : + letI := orderUnitSpaceOfBilinear m hcomm hjordan hfr e he + Submodule.span ℝ {a : J | OrderUnitSpace.IsEffect a} = ⊤ := by + let := orderUnitSpaceOfBilinear m hcomm hjordan hfr e he + exact OrderUnitSpace.span_isEffect_eq_top + +end Characterization + +end OrderUnit + +/-! ## The Euclidean hypothesis: the Archimedean squeeze and the cone of squares + +The two results below need more than the order: they need the coefficients of a spectral +resolution of a *positive* element to be nonnegative, which no amount of cone algebra +supplies. What supplies it is the associative inner product — the "Euclidean" in Euclidean +Jordan algebra. -/ + +section Euclidean + +variable {J : Type*} [NormedAddCommGroup J] [InnerProductSpace ℝ J] +variable {m : J →ₗ[ℝ] J →ₗ[ℝ] J} + +/-- **`L_c` is a positive operator for an idempotent `c`.** + +`L_c = P₁(c) + ½ P_{1/2}(c)` on the nose, and both Peirce projections are idempotent +(`EuclideanJordan/Peirce.lean`'s `mul_peirceOne` feeding `peirceOne_of_eigen`) and self-adjoint + (from +self-adjointness of `L_c`, which is `hassoc` at `x := c`). A self-adjoint idempotent `P` +satisfies `⟪P y, y⟫ = ⟪P y, P y⟫ ≥ 0`, so the sum is nonnegative. + +★ The eigenvalue trichotomy is never invoked, and no functional calculus is needed: the two +projections are polynomials in `L_c` that the tree already carries as linear maps. -/ +theorem inner_mul_self_nonneg_of_idem + (hcomm : ∀ x y : J, m x y = m y x) + (hjordan : ∀ a b : J, m (m a b) (m a a) = m a (m b (m a a))) + (hassoc : ∀ x y z : J, inner ℝ (m x y) z = inner ℝ y (m x z)) + {c : J} (hc : m c c = c) (y : J) : + 0 ≤ inner ℝ (m c y) y := by + let : NonUnitalNonAssocCommRing J := ringOfBilinear m hcomm + let : IsCommJordan J := ⟨hjordan⟩ + let : IsScalarTower ℝ J J := ⟨fun r x y => smul_bilinear m r x y⟩ + have hc' : c * c = c := hc + have hsa : ∀ u v : J, inner ℝ (c * u) v = inner ℝ u (c * v) := fun u v => hassoc c u v + have hsa1 : ∀ u v : J, inner ℝ (peirceOne c u) v = inner ℝ u (peirceOne c v) := by + intro u v + simp only [peirceOne_apply, inner_sub_left, inner_sub_right, real_inner_smul_left, + real_inner_smul_right] + rw [hsa (c * u) v, hsa u (c * v), hsa u v] + have hsah : ∀ u v : J, inner ℝ (peirceHalf c u) v = inner ℝ u (peirceHalf c v) := by + intro u v + simp only [peirceHalf_apply, inner_sub_left, inner_sub_right, real_inner_smul_left, + real_inner_smul_right] + rw [hsa (c * u) v, hsa u (c * v), hsa u v] + have hid1 : peirceOne c (peirceOne c y) = peirceOne c y := + peirceOne_of_eigen (mul_peirceOne hc' y) + have hidh : peirceHalf c (peirceHalf c y) = peirceHalf c y := + peirceHalf_of_eigen_half (mul_peirceHalf hc' y) + have hsplit : (m c y : J) = peirceOne c y + (2 : ℝ)⁻¹ • peirceHalf c y := by + change c * y = _ + simp only [peirceOne_apply, peirceHalf_apply] + module + have key1 : inner ℝ (peirceOne c y) (peirceOne c y) = inner ℝ (peirceOne c y) y := by + rw [hsa1 y (peirceOne c y), hid1, real_inner_comm] + have keyh : inner ℝ (peirceHalf c y) (peirceHalf c y) = inner ℝ (peirceHalf c y) y := by + rw [hsah y (peirceHalf c y), hidh, real_inner_comm] + have h1 : (0 : ℝ) ≤ inner ℝ (peirceOne c y) y := key1 ▸ real_inner_self_nonneg + have hh : (0 : ℝ) ≤ inner ℝ (peirceHalf c y) y := keyh ▸ real_inner_self_nonneg + rw [hsplit, inner_add_left, real_inner_smul_left] + linarith + +/-- **A sum of squares has nonnegative spectral coefficients.** + +Pairing against `q k` reads the coefficient off — the idempotents are pairwise orthogonal for +the inner product because `hassoc` turns `⟪q k, q i⟫` into `⟪q k, q k ∘ q i⟫` — while pairing +against the sum-of-squares presentation is nonnegative term by term, each term being +`⟪L_{q k} f j, f j⟫`. + +This is the fact that both `isArchimedean_ofBilinear` and `isSoS_iff_exists_sq` reduce to, and +it is the only content in this file that the order axioms themselves do not supply. -/ +theorem inner_left_coeff + (hassoc : ∀ x y z : J, inner ℝ (m x y) z = inner ℝ y (m x z)) + {n : ℕ} {q : Fin n → J} {lam : Fin n → ℝ} + (hidem : ∀ i, m (q i) (q i) = q i) + (horth : ∀ i j, i ≠ j → m (q i) (q j) = 0) + {x : J} (hx : x = ∑ i, lam i • q i) (k : Fin n) : + inner ℝ (q k) x = lam k * inner ℝ (q k) (q k) := by + have horthinner : ∀ i, i ≠ k → inner ℝ (q k) (q i) = (0 : ℝ) := by + intro i hi + calc inner ℝ (q k) (q i) = inner ℝ (m (q k) (q k)) (q i) := by rw [hidem k] + _ = inner ℝ (q k) (m (q k) (q i)) := hassoc (q k) (q k) (q i) + _ = 0 := by rw [horth k i (Ne.symm hi), inner_zero_right] + rw [hx, inner_sum, Finset.sum_eq_single k] + · rw [real_inner_smul_right] + · intro i _ hi + rw [real_inner_smul_right, horthinner i hi, mul_zero] + · intro h + exact absurd (Finset.mem_univ k) h + +/-- A coefficient is nonnegative as soon as its idempotent pairs nonnegatively with the +element — the shape shared by `nonneg_coeff_of_isSoS` (where the pairing is nonnegative +because `x` is a sum of squares) and by `hermitian_nonneg_le_isSoS` (where it is nonnegative +because `x` is positive semidefinite). -/ +theorem nonneg_coeff_of_inner_nonneg + (hassoc : ∀ x y z : J, inner ℝ (m x y) z = inner ℝ y (m x z)) + {n : ℕ} {q : Fin n → J} {lam : Fin n → ℝ} + (hidem : ∀ i, m (q i) (q i) = q i) + (horth : ∀ i j, i ≠ j → m (q i) (q j) = 0) + {x : J} (hx : x = ∑ i, lam i • q i) + {k : Fin n} (hk : q k ≠ 0) (hnn : (0 : ℝ) ≤ inner ℝ (q k) x) : 0 ≤ lam k := by + have hxk := inner_left_coeff hassoc hidem horth hx k + have hpos : (0 : ℝ) < inner ℝ (q k) (q k) := real_inner_self_pos.mpr hk + nlinarith [hxk, hnn, hpos] + +theorem nonneg_coeff_of_isSoS + (hcomm : ∀ x y : J, m x y = m y x) + (hjordan : ∀ a b : J, m (m a b) (m a a) = m a (m b (m a a))) + (hassoc : ∀ x y z : J, inner ℝ (m x y) z = inner ℝ y (m x z)) + {n : ℕ} {q : Fin n → J} {lam : Fin n → ℝ} + (hidem : ∀ i, m (q i) (q i) = q i) + (horth : ∀ i j, i ≠ j → m (q i) (q j) = 0) + {x : J} (hx : x = ∑ i, lam i • q i) (hsos : IsSoS m x) + {k : Fin n} (hk : q k ≠ 0) : 0 ≤ lam k := by + obtain ⟨j, f, hf⟩ := hsos + have hnn : (0 : ℝ) ≤ inner ℝ (q k) x := by + rw [hf, inner_sum] + refine Finset.sum_nonneg fun i _ => ?_ + have hstep : inner ℝ (q k) (m (f i) (f i)) = inner ℝ (m (q k) (f i)) (f i) := by + calc inner ℝ (q k) (m (f i) (f i)) + = inner ℝ (m (f i) (f i)) (q k) := real_inner_comm _ _ + _ = inner ℝ (f i) (m (f i) (q k)) := hassoc (f i) (f i) (q k) + _ = inner ℝ (f i) (m (q k) (f i)) := by rw [hcomm (f i) (q k)] + _ = inner ℝ (m (q k) (f i)) (f i) := real_inner_comm _ _ + rw [hstep] + exact inner_mul_self_nonneg_of_idem hcomm hjordan hassoc (hidem k) (f i) + exact nonneg_coeff_of_inner_nonneg hassoc hidem horth hx hk hnn + +variable [FiniteDimensional ℝ J] + +/-- **The genuine Archimedean property**, in the sense +`EuclideanJordan/OrderUnitSpace.lean`'s `IsArchimedean` carries: an element under *every* +positive multiple of the unit is nonpositive. + +This is strictly stronger than the class's `archimedean` field, which is order-unit +boundedness only. ★ It is proved here at EJA generality; before this, `H_n(𝕜)` was the only +carrier known to satisfy it, so results assuming `IsArchimedean` had a single model. -/ +theorem isArchimedean_ofBilinear + (hcomm : ∀ x y : J, m x y = m y x) + (hjordan : ∀ a b : J, m (m a b) (m a a) = m a (m b (m a a))) + (hfr : ∀ (k : ℕ) (f : Fin k → J), (∑ i, m (f i) (f i)) = 0 → ∀ i, f i = 0) + (hassoc : ∀ x y z : J, inner ℝ (m x y) z = inner ℝ y (m x z)) + (e : J) (he : ∀ y : J, m e y = y) : + @OrderUnitSpace.IsArchimedean J (orderUnitSpaceOfBilinear m hcomm hjordan hfr e he) := by + intro x hx + change IsSoS m (0 - x) + obtain ⟨n, q, lam, hidem, horth, hsum, hxe⟩ := + spectral_resolution_bilinear m hcomm hjordan hfr e he x + have hlam : ∀ i, q i ≠ 0 → lam i ≤ 0 := by + intro i hi + by_contra hcon + have hpos : 0 < lam i := not_le.mp hcon + have hsos : IsSoS m ((lam i / 2) • e - x) := hx (lam i / 2) (by linarith) + have hrw := smul_unit_sub_eq hsum hxe (lam i / 2) + have := nonneg_coeff_of_isSoS hcomm hjordan hassoc hidem horth hrw (hrw ▸ hsos) hi + linarith + have h0 := smul_unit_sub_eq hsum hxe 0 + rw [zero_smul] at h0 + rw [h0] + refine isSoS_sum _ _ fun i _ => ?_ + by_cases hi : q i = 0 + · rw [hi, smul_zero] + exact isSoS_zero + · exact isSoS_smul_idem (by linarith [hlam i hi]) (hidem i) + +/-- **The cone of the order is the cone of squares.** The two readings of `0 ≤ x` — a sum of +squares, and a single square — coincide, so a development that *defines* positivity as "is a +square" agrees with the one built here; that agreement is a theorem rather than a stipulation. + +The forward direction is the whole content: a sum of squares has nonnegative coefficients, so +`∑ √(lam i) • q i` squares back to it by orthogonality. -/ +theorem isSoS_iff_exists_sq + (hcomm : ∀ x y : J, m x y = m y x) + (hjordan : ∀ a b : J, m (m a b) (m a a) = m a (m b (m a a))) + (hfr : ∀ (k : ℕ) (f : Fin k → J), (∑ i, m (f i) (f i)) = 0 → ∀ i, f i = 0) + (hassoc : ∀ x y z : J, inner ℝ (m x y) z = inner ℝ y (m x z)) + (e : J) (he : ∀ y : J, m e y = y) (x : J) : + IsSoS m x ↔ ∃ y : J, x = m y y := by + constructor + · intro hsos + obtain ⟨n, q, lam, hidem, horth, hsum, hxe⟩ := + spectral_resolution_bilinear m hcomm hjordan hfr e he x + set s : Fin n → ℝ := fun i => Real.sqrt (max (lam i) 0) with hs + refine ⟨∑ i, s i • q i, ?_⟩ + have step : ∀ v : J, m (∑ i, s i • q i) v = ∑ i, s i • m (q i) v := by + intro v + rw [map_sum, LinearMap.sum_apply] + exact Finset.sum_congr rfl fun i _ => by rw [map_smul, LinearMap.smul_apply] + have step2 : ∀ i, m (q i) (∑ j, s j • q j) = s i • q i := by + intro i + rw [map_sum, Finset.sum_eq_single i] + · rw [map_smul, hidem i] + · intro j _ hj + rw [map_smul, horth i j (Ne.symm hj), smul_zero] + · intro h + exact absurd (Finset.mem_univ i) h + have hexp : m (∑ i, s i • q i) (∑ i, s i • q i) = ∑ i, (s i * s i) • q i := by + rw [step] + simp only [step2, smul_smul] + rw [hexp, hxe] + refine Finset.sum_congr rfl fun i _ => ?_ + by_cases hi : q i = 0 + · rw [hi, smul_zero, smul_zero] + · have hnn : 0 ≤ lam i := + nonneg_coeff_of_isSoS hcomm hjordan hassoc hidem horth hxe hsos hi + rw [hs] + simp only + rw [max_eq_left hnn, Real.mul_self_sqrt hnn] + · rintro ⟨y, rfl⟩ + exact ⟨1, fun _ => y, by simp⟩ + +end Euclidean + +/-! ## A live carrier for the hypothesis bundle + +`H_n(𝕜)` satisfies every hypothesis above, including the associative-inner-product hypothesis +this file introduces, so nothing in the two previous sections is conditional on a premise with +no carrier. The construction is applied to it at the end; the resulting order unit space is a +*second* one on `H_n(𝕜)`, kept as a `def` and never an instance, and it is **not** proved +equal to the vendored Loewner structure (see the module docstring). -/ + +section Carrier + +open ComplexOrder + +variable {n : Type*} [Fintype n] [DecidableEq n] {𝕜 : Type*} [RCLike 𝕜] + +/-- **The Euclidean hypothesis, live on `H_n(𝕜)`.** Both sides are +`½(Tr[ABC] + Tr[BAC])` after `Matrix.trace_mul_cycle`. -/ +theorem hermitian_jordan_assoc (A B C : HermitianMat n 𝕜) : + inner ℝ (jordanBilinG 𝕜 A B) C = inner ℝ B (jordanBilinG 𝕜 A C) := by + have htr : (((2 : 𝕜)⁻¹ • (A.mat * B.mat + B.mat * A.mat)) * C.mat).trace + = (B.mat * ((2 : 𝕜)⁻¹ • (A.mat * C.mat + C.mat * A.mat))).trace := by + rw [Matrix.smul_mul, Matrix.mul_smul, Matrix.trace_smul, Matrix.trace_smul] + congr 1 + rw [Matrix.add_mul, Matrix.mul_add, Matrix.trace_add, Matrix.trace_add, add_comm] + congr 1 + · rw [mul_assoc] + · rw [Matrix.trace_mul_cycle, Matrix.trace_mul_cycle, mul_assoc] + rw [HermitianMat.inner_def, HermitianMat.inner_def, jordanBilin_applyG, jordanBilin_applyG, + HermitianMat.symmMul_toMat, HermitianMat.symmMul_toMat, htr] + +theorem hermitian_jordan_comm (A B : HermitianMat n 𝕜) : + jordanBilinG 𝕜 A B = jordanBilinG 𝕜 B A := + HermitianMat.symmMul_comm A B + +/-! The two facts below are the only ones that need `HermMul`'s *scoped* multiplicative +instances (`CommMagma`, `MulZeroClass`, `IsCommJordan`), so the `open` is confined to this +block rather than covering the whole section. + +★ **That confinement is hygiene, not a fix, and the record should say so.** It was made while +chasing an elaboration blow-up in `hermitian_isArchimedean_ofEJA` and +`hermitian_isSoS_iff_exists_sq` on the theory that a second multiplicative structure in scope +was making unification search a diamond. **It changed nothing** — the timeout survived it +unaltered, as did a second theory that the mismatch between `HermitianMat.instAddCommGroup` +and `NormedAddCommGroup.toAddCommGroup` was being paid for (that defeq costs about a second in +isolation). The real cause is recorded at the two call sites below. -/ + +section ScopedMul +open HermMul + +theorem hermitian_jordan_id (A B : HermitianMat n 𝕜) : + jordanBilinG 𝕜 (jordanBilinG 𝕜 A B) (jordanBilinG 𝕜 A A) + = jordanBilinG 𝕜 A (jordanBilinG 𝕜 B (jordanBilinG 𝕜 A A)) := by + simpa only [jordanBilin_applyG, ← HermMul.mul_eq_symmMul] using + IsCommJordan.lmul_comm_rmul_rmul A B + +theorem hermitian_formallyReal (k : ℕ) (f : Fin k → HermitianMat n 𝕜) + (h : (∑ i, jordanBilinG 𝕜 (f i) (f i)) = 0) (i : Fin k) : f i = 0 := + IsFormallyReal.eq_zero_of_sum_mul_self Finset.univ f + (by simpa only [jordanBilin_applyG, ← HermMul.mul_eq_symmMul] using h) + i (Finset.mem_univ i) + +end ScopedMul + +theorem hermitian_jordan_unit (A : HermitianMat n 𝕜) : jordanBilinG 𝕜 1 A = A := + HermitianMat.one_symmMul A + +/-! ★★ **Why every application below pins `(J := HermitianMat n 𝕜)` explicitly.** + +Without it these three declarations exhausted 200 000 heartbeats, and raising the budget was +the wrong move: with `maxHeartbeats 0` the elaboration ran for two minutes and then *failed*, +having defaulted `J := ℕ` off the bare numeral `1` supplied for the explicit `e : J`. The +profiler names the cost exactly. With `J` unsolved, the `m` argument stays a metavariable +through the argument list, so checking `hermitian_formallyReal` becomes the higher-order +problem + + `∑ i, (?m (f i)) (f i) =?= ∑ i, ((jordanBilinG ?n) (f i)) (f i)`, + +and `isDefEq` unfolds `Finset.sum` through `Multiset.foldr`, `Quot.liftOn` and `List.map` +hunting for a match — thirteen seconds, and it fails. Pinning `J` makes the same unification +first-order and the whole file elaborates in about six seconds. + +The transferable rule: **an `isDefEq` timeout under a `Finset.sum` usually means a +metavariable in the function position, not a budget that is too small.** Ascribing the +numeral (`(1 : HermitianMat n 𝕜)`) is necessary too but not sufficient — it removes the wrong +`ℕ` answer without removing the search. -/ + +/-- **The construction, applied to `H_n(𝕜)`.** Its only job is to witness that the +hypothesis bundle of `orderUnitSpaceOfBilinear` is inhabited. -/ +@[instance_reducible] +def hermitianOrderUnitOfEJA : OrderUnitSpace (HermitianMat n 𝕜) := + orderUnitSpaceOfBilinear (J := HermitianMat n 𝕜) (jordanBilinG 𝕜) hermitian_jordan_comm + hermitian_jordan_id hermitian_formallyReal (1 : HermitianMat n 𝕜) hermitian_jordan_unit + +/-- The Archimedean squeeze holds for the constructed structure on `H_n(𝕜)`, so +`isArchimedean_ofBilinear` is not vacuous either. -/ +theorem hermitian_isArchimedean_ofEJA : + @OrderUnitSpace.IsArchimedean (HermitianMat n 𝕜) hermitianOrderUnitOfEJA := + isArchimedean_ofBilinear (J := HermitianMat n 𝕜) (m := jordanBilinG 𝕜) hermitian_jordan_comm + hermitian_jordan_id hermitian_formallyReal hermitian_jordan_assoc (1 : HermitianMat n 𝕜) + hermitian_jordan_unit + +/-- And the cone of the constructed order is the cone of squares on `H_n(𝕜)`. -/ +theorem hermitian_isSoS_iff_exists_sq (A : HermitianMat n 𝕜) : + IsSoS (jordanBilinG 𝕜) A ↔ ∃ B : HermitianMat n 𝕜, A = jordanBilinG 𝕜 B B := + isSoS_iff_exists_sq (J := HermitianMat n 𝕜) (m := jordanBilinG 𝕜) hermitian_jordan_comm + hermitian_jordan_id hermitian_formallyReal hermitian_jordan_assoc (1 : HermitianMat n 𝕜) + hermitian_jordan_unit A + +/-! ### The constructed order *is* the Loewner order on `H_n(𝕜)` + +Fidelity, not inhabitedness: the abstract cone could have been inhabited and still been the +wrong cone. Both containments are below. -/ + +/-- A Jordan square in `H_n(𝕜)` is positive semidefinite: the Jordan square is the matrix +square, and `M M = Mᴴ M` for `M` Hermitian. -/ +theorem hermitian_sq_nonneg (B : HermitianMat n 𝕜) : 0 ≤ jordanBilinG 𝕜 B B := by + rw [HermitianMat.zero_le_iff, jordanBilin_applyG, HermitianMat.symmMul_self] + simpa only [B.H.eq] using Matrix.posSemidef_conjTranspose_mul_self B.mat + +/-- A Jordan idempotent is therefore positive semidefinite: it *is* its own square. -/ +theorem hermitian_idem_nonneg {C : HermitianMat n 𝕜} (hC : jordanBilinG 𝕜 C C = C) : 0 ≤ C := + hC ▸ hermitian_sq_nonneg C + +/-- Sums of squares are positive semidefinite. -/ +theorem hermitian_isSoS_le_nonneg {A : HermitianMat n 𝕜} (h : IsSoS (jordanBilinG 𝕜) A) : + 0 ≤ A := by + obtain ⟨k, f, hf⟩ := h + rw [hf] + exact Finset.sum_nonneg fun i _ => hermitian_sq_nonneg (f i) + +/-- **The containment that needed the Euclidean hypothesis**: a positive semidefinite matrix is +a sum of squares. Its spectral idempotents are themselves positive semidefinite, so +`⟪q i, A⟫ ≥ 0` by `HermitianMat.inner_ge_zero`, and `inner_left_coeff` reads that off as +`lam i ≥ 0`. -/ +theorem hermitian_nonneg_le_isSoS {A : HermitianMat n 𝕜} (hA : 0 ≤ A) : + IsSoS (jordanBilinG 𝕜) A := by + obtain ⟨N, q, lam, hidem, horth, hsum, hxe⟩ := + spectral_resolution_bilinear (J := HermitianMat n 𝕜) (jordanBilinG 𝕜) hermitian_jordan_comm + hermitian_jordan_id hermitian_formallyReal (1 : HermitianMat n 𝕜) hermitian_jordan_unit A + rw [hxe] + refine isSoS_sum _ _ fun i _ => ?_ + by_cases hi : q i = 0 + · rw [hi, smul_zero] + exact isSoS_zero + · refine isSoS_smul_idem ?_ (hidem i) + refine nonneg_coeff_of_inner_nonneg (J := HermitianMat n 𝕜) (m := jordanBilinG 𝕜) + hermitian_jordan_assoc hidem horth hxe hi ?_ + exact HermitianMat.inner_ge_zero (hermitian_idem_nonneg (hidem i)) hA + +/-- **The abstract cone is the Loewner cone on `H_n(𝕜)`.** -/ +theorem hermitian_isSoS_iff_nonneg (A : HermitianMat n 𝕜) : + IsSoS (jordanBilinG 𝕜) A ↔ 0 ≤ A := + ⟨hermitian_isSoS_le_nonneg, hermitian_nonneg_le_isSoS⟩ + +/-- **The constructed order relation is the Loewner order relation.** -/ +theorem hermitian_le_ofEJA_iff (A B : HermitianMat n 𝕜) : + @LE.le (HermitianMat n 𝕜) (@Preorder.toLE _ (@PartialOrder.toPreorder _ + (@OrderUnitSpace.toPartialOrder _ hermitianOrderUnitOfEJA))) A B ↔ A ≤ B := by + rw [show (@LE.le (HermitianMat n 𝕜) (@Preorder.toLE _ (@PartialOrder.toPreorder _ + (@OrderUnitSpace.toPartialOrder _ hermitianOrderUnitOfEJA))) A B) + = IsSoS (jordanBilinG 𝕜) (B - A) from rfl, + hermitian_isSoS_iff_nonneg, sub_nonneg] + +end Carrier + +end EuclideanJordan diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/OrderAuto.lean b/LeanPool/EuclideanJordan/EuclideanJordan/OrderAuto.lean new file mode 100644 index 0000000000..e8468a076a --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/OrderAuto.lean @@ -0,0 +1,499 @@ +/- +Copyright (c) 2026 Bryan Ehrlich. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Bryan Ehrlich +-/ +module + +public import LeanPool.EuclideanJordan.EuclideanJordan.TraceForm +public import LeanPool.EuclideanJordan.EuclideanJordan.Order + + + +/-! +# Koecher / Alfsen–Shultz: a unital linear order isomorphism is a Jordan automorphism + +On a finite-dimensional formally real Jordan algebra, a linear bijection `Φ` that fixes the unit +and preserves the cone of sums of squares **in both directions** preserves the Jordan product. + +This is the classical theorem — Koecher; Alfsen–Shultz, *Geometry of State Spaces*, Thm 2.80. It is +**not** van Imhoff–Roelands' JB-generality version (arXiv:1904.09278), which concludes linearity +rather than assuming it. Here `Φ : J ≃ₗ[ℝ] J` is linear **by type**, and that is the whole +difference. + +## The route + +Jordan-multiplicativity reduces to preservation of squares, and squares to preservation of +idempotents, provided idempotents can be recognised *order-theoretically*. They can: + +* `c` is idempotent **iff** `c` is **sharp** — `0 ≤ c ≤ e` and no nonzero element of the cone lies + below both `c` and `e − c` (`isSharp_iff_idem`). + +Sharpness is visibly transported by `Φ`, since every clause is a statement about the cone and the +unit; so `Φ` maps idempotents to idempotents. A complete orthogonal family `∑ qᵢ = e` goes to a +family with `∑ Φqᵢ = e`, and orthogonality is then *recovered* rather than transported +(`orth_of_sum_eq_unit`). Finally `x = ∑ λᵢ qᵢ ⟹ x ∘ x = ∑ λᵢ² qᵢ` gives `Φ(x ∘ x) = Φx ∘ Φx`, and +polarisation finishes it. + +★ **The reflecting half of the order hypothesis is load-bearing.** `horder` is a *biconditional*. +A one-directional `IsSoS x → IsSoS (Φ x)` would not do: the sharpness clause is a `∀` over the cone, +and transporting it along `Φ.symm` needs the converse. This is checked, not assumed — see +`map_idem_of_orderIso`, where `(horder _).mpr` appears three times. + +## What the argument runs on + +Two facts about the cone, both of which need an **associative positive definite form**: + +* a sum of squares has nonnegative spectral coefficients (`nonneg_coeff`); +* the **face lemma**: `0 ≤ x ≤ c` with `c` idempotent forces `c ∘ x = x` (`face_lemma`). + +`EuclideanJordan/Order.lean` proves the first from the *ambient* inner product, under the + hypothesis that the +inner product is associative for the Jordan product. A consumer that carries an inner product +and a Jordan product as unrelated data has no such hypothesis available, which is why this file +pairs against `EuclideanJordan/TraceForm.lean`'s `traceForm` instead: a form built from the + algebra, so nothing has to +be assumed about an ambient one. Both facts are proved here from scratch in that vocabulary; the +`inner`-shaped originals in `EuclideanJordan/Order.lean` are untouched and are not used. + +## Extreme points are not needed + +The natural-looking characterisation — `c` is idempotent iff it is an extreme point of `[0, e]` — +is **avoided deliberately**. ★ The reason is a price judgment and is recorded as one, not as a +theorem: on the classical proof both of its directions run through the same face lemma that +sharpness needs, and it additionally drags in Mathlib's `Set.extremePoints` and convexity API and +forces `[0, e]` to be stated as a `Set`. Nothing below establishes that, because the +extreme-points route was never built. + +★ `EuclideanJordan/OrderUnitSpace.lean` carries an `IsSharp` of its own, and this file does +**not** use it or bridge to it. That one is stated over an `OrderUnitSpace` instance, and getting +one here would mean instantiating `EuclideanJordan/Order.lean`'s `orderUnitSpaceOfBilinear` — a + `def`, whose +own docstring warns that instantiating it puts a second `PartialOrder J` in scope. +★★ **The two are not literally the same formula, and the difference is worth stating rather than +glossing.** `OrderUnitSpace.IsSharp` quantifies its witness over the *effects* (`IsEffect a`, i.e. +`0 ≤ a ≤ 𝟙`); `IsSharp` below quantifies over the whole cone (`IsSoS mulLₗ x`, with no upper bound +on `x`). The extra clause is redundant — `x ≤ c` and `c ≤ e` give `x ≤ e` by transitivity of the +cone order — so the two conditions are equivalent; but **that equivalence is proved nowhere below +and nothing needs it**, and the version used here is the one whose witness hypothesis is weaker, +hence the harder condition to satisfy and the safer one to transport. + +## What this closes, and what it does not + +★★ **This is one clause of a larger statement, and which clause it is matters.** The route that +builds an order automorphism `Θ_a` out of a sequential product and then reads it as a Jordan +automorphism needs three separate things: the construction of `Θ_a` (van de Wetering, +arXiv:1803.11139, Prop 5.3), the upgrade of a unital linear order isomorphism to a Jordan +automorphism, and the fixing and cocycle properties (vdW Props 5.5, 5.7). **Only the upgrade is +proved here.** The other two are cited, not formalized, and nothing below establishes them. +-/ + +@[expose] public section + +noncomputable section + +namespace EuclideanJordan + +open Finset + +/-! ## Pairing against the trace form -/ + +section Abstract + +variable {J : Type*} [NonUnitalNonAssocCommRing J] [IsCommJordan J] [Module ℝ J] + [IsScalarTower ℝ J J] [IsFormallyReal J] [Module.Finite ℝ J] + +omit [IsFormallyReal J] [Module.Finite ℝ J] in +/-- **`L_c` is a positive operator for the trace form**, in the sharp form that also reads off when +the pairing vanishes: `τ(c ∘ y, y)` is the sum of two squares of the form, one for each nonzero +Peirce eigenvalue of `c`. + +`L_c = P₁(c) + ½ P_{1/2}(c)`; both Peirce projections are self-adjoint for `τ` (associativity, three +times) and idempotent, so `τ(P y, y) = τ(P y, P y)`. + +★ Stated as an equation rather than as an inequality because both consumers need it: nonnegativity +falls out, and so does `c ∘ y = 0` from a vanishing pairing — which an inequality would have +lost. -/ +theorem traceForm_mulL_split {c : J} (hc : c * c = c) (y : J) : + traceForm (c * y) y + = traceForm (peirceOne c y) (peirceOne c y) + + (2 : ℝ)⁻¹ * traceForm (peirceHalf c y) (peirceHalf c y) := by + have hsa1 : ∀ u v : J, traceForm (peirceOne c u) v = traceForm u (peirceOne c v) := by + intro u v + simp only [peirceOne_apply, map_sub, map_smul, LinearMap.sub_apply, LinearMap.smul_apply, + smul_eq_mul] + rw [traceForm_assoc c (c * u) v, traceForm_assoc c u (c * v), traceForm_assoc c u v] + have hsah : ∀ u v : J, traceForm (peirceHalf c u) v = traceForm u (peirceHalf c v) := by + intro u v + simp only [peirceHalf_apply, map_sub, map_smul, LinearMap.sub_apply, LinearMap.smul_apply, + smul_eq_mul] + rw [traceForm_assoc c (c * u) v, traceForm_assoc c u (c * v), traceForm_assoc c u v] + have hid1 : peirceOne c (peirceOne c y) = peirceOne c y := + peirceOne_of_eigen (mul_peirceOne hc y) + have hidh : peirceHalf c (peirceHalf c y) = peirceHalf c y := + peirceHalf_of_eigen_half (mul_peirceHalf hc y) + have key1 : traceForm (peirceOne c y) (peirceOne c y) = traceForm (peirceOne c y) y := by + rw [hsa1 y (peirceOne c y), hid1, traceForm_comm] + have keyh : traceForm (peirceHalf c y) (peirceHalf c y) = traceForm (peirceHalf c y) y := by + rw [hsah y (peirceHalf c y), hidh, traceForm_comm] + have hsplit : (c * y : J) = peirceOne c y + (2 : ℝ)⁻¹ • peirceHalf c y := by + simp only [peirceOne_apply, peirceHalf_apply] + module + rw [key1, keyh, hsplit, map_add, map_smul, LinearMap.add_apply, LinearMap.smul_apply, + smul_eq_mul] + +theorem traceForm_mulL_nonneg_of_idem {c : J} (hc : c * c = c) (y : J) : + 0 ≤ traceForm (c * y) y := by + rw [traceForm_mulL_split hc y] + have h1 := traceForm_self_nonneg (peirceOne c y) + have h2 := traceForm_self_nonneg (peirceHalf c y) + linarith + +/-- A vanishing pairing kills the product outright: both Peirce components of `y` at `c` vanish, so +`c ∘ y = 0`. This is where definiteness of `traceForm` is spent. -/ +theorem mul_eq_zero_of_traceForm_mulL_eq_zero {c : J} (hc : c * c = c) {y : J} + (h : traceForm (c * y) y = 0) : c * y = 0 := by + rw [traceForm_mulL_split hc y] at h + have h1 := traceForm_self_nonneg (peirceOne c y) + have h2 := traceForm_self_nonneg (peirceHalf c y) + have e1 : peirceOne c y = 0 := eq_zero_of_traceForm_self_eq_zero (by linarith) + have e2 : peirceHalf c y = 0 := eq_zero_of_traceForm_self_eq_zero (by linarith) + have hsplit : (c * y : J) = peirceOne c y + (2 : ℝ)⁻¹ • peirceHalf c y := by + simp only [peirceOne_apply, peirceHalf_apply] + module + rw [hsplit, e1, e2, smul_zero, add_zero] + +/-- **An idempotent pairs nonnegatively with the cone.** Associativity moves `c` onto one factor of +each square, and the previous lemma does the rest. -/ +theorem traceForm_nonneg_of_idem_of_isSoS {c x : J} (hc : c * c = c) + (hx : IsSoS mulLₗ x) : 0 ≤ traceForm c x := by + obtain ⟨k, f, hf⟩ := hx + simp only [mulLₗ_apply, mulL_apply] at hf + rw [hf, map_sum] + refine Finset.sum_nonneg fun i _ => ?_ + have hmove : traceForm c (f i * f i) = traceForm (c * f i) (f i) := by + simp only [traceForm_apply] + exact (jtr_assoc c (f i) (f i)).symm + rw [hmove] + exact traceForm_mulL_nonneg_of_idem hc (f i) + +/-- **A vanishing pairing against the cone annihilates.** Every square in the presentation is +killed factor by factor, and `J₀(c)` is a subalgebra, so the whole element is killed. -/ +theorem mul_eq_zero_of_traceForm_eq_zero {c x : J} (hc : c * c = c) + (hx : IsSoS mulLₗ x) (h : traceForm c x = 0) : c * x = 0 := by + obtain ⟨k, f, hf⟩ := hx + simp only [mulLₗ_apply, mulL_apply] at hf + have hterm : ∀ i : Fin k, 0 ≤ traceForm c (f i * f i) := by + intro i + have hmove : traceForm c (f i * f i) = traceForm (c * f i) (f i) := by + simp only [traceForm_apply] + exact (jtr_assoc c (f i) (f i)).symm + rw [hmove] + exact traceForm_mulL_nonneg_of_idem hc (f i) + have hsum : (∑ i, traceForm c (f i * f i)) = 0 := by rw [← map_sum, ← hf]; exact h + have hzero : ∀ i : Fin k, traceForm c (f i * f i) = 0 := fun i => + (Finset.sum_eq_zero_iff_of_nonneg fun j _ => hterm j).mp hsum i (Finset.mem_univ i) + have hkill : ∀ i : Fin k, c * f i = 0 := by + intro i + refine mul_eq_zero_of_traceForm_mulL_eq_zero hc ?_ + have hmove : traceForm c (f i * f i) = traceForm (c * f i) (f i) := by + simp only [traceForm_apply] + exact (jtr_assoc c (f i) (f i)).symm + rw [← hmove] + exact hzero i + rw [hf, Finset.mul_sum] + exact Finset.sum_eq_zero fun i _ => eigen_zero_mul_zero hc (hkill i) (hkill i) + +/-! ## The face lemma -/ + +omit [IsCommJordan J] [Module ℝ J] [IsScalarTower ℝ J J] [IsFormallyReal J] [Module.Finite ℝ J] in +/-- The orthocomplement of an idempotent is an idempotent. -/ +theorem sub_idem {e c : J} (he : ∀ y : J, e * y = y) (hc : c * c = c) : + (e - c) * (e - c) = e - c := by + have hce : c * e = c := by rw [mul_comm]; exact he c + rw [sub_mul, mul_sub, mul_sub, he, he, hc, hce, sub_self, sub_zero] + +/-- **The face lemma.** If `0 ≤ x ≤ c` with `c` idempotent, then `c ∘ x = x`. + +Pair against the complementary idempotent `e − c`: the pairing with `x` is nonnegative, and bounded +above by the pairing with `c`, which is `τ((e − c) ∘ c) = τ(0) = 0`. So it vanishes, and the +previous lemma turns that into `(e − c) ∘ x = 0`. -/ +theorem face_lemma {e c x : J} (he : ∀ y : J, e * y = y) (hc : c * c = c) + (hx : IsSoS mulLₗ x) (hcx : IsSoS mulLₗ (c - x)) : c * x = x := by + have hd : (e - c) * (e - c) = e - c := sub_idem he hc + have h1 : 0 ≤ traceForm (e - c) x := traceForm_nonneg_of_idem_of_isSoS hd hx + have h2 : 0 ≤ traceForm (e - c) (c - x) := traceForm_nonneg_of_idem_of_isSoS hd hcx + have hdc : traceForm (e - c) c = 0 := by + have hz : (e - c) * c = 0 := by rw [sub_mul, he, hc, sub_self] + simp only [traceForm_apply, hz, map_zero] + rw [map_sub, hdc] at h2 + have hzero : traceForm (e - c) x = 0 := le_antisymm (by linarith) h1 + have hdx : (e - c) * x = 0 := mul_eq_zero_of_traceForm_eq_zero hd hx hzero + rw [sub_mul, he, sub_eq_zero] at hdx + exact hdx.symm + +/-- **Idempotents summing to the unit are pairwise orthogonal** — orthogonality is *recovered*, not +transported. `qⱼ` lies under the idempotent `e − qᵢ`, because the difference is the sum of the +remaining members of the family; the face lemma then gives `(e − qᵢ) ∘ qⱼ = qⱼ`. -/ +theorem orth_of_sum_eq_unit {n : ℕ} {q : Fin n → J} {e : J} (he : ∀ y : J, e * y = y) + (hidem : ∀ i, q i * q i = q i) (hsum : (∑ i, q i) = e) (i j : Fin n) (hij : i ≠ j) : + q i * q j = 0 := by + classical + have hjm : j ∈ Finset.univ.erase i := Finset.mem_erase.mpr ⟨Ne.symm hij, Finset.mem_univ j⟩ + have h1 : (∑ k, q k) = q i + ∑ k ∈ Finset.univ.erase i, q k := + (Finset.add_sum_erase _ q (Finset.mem_univ i)).symm + have h2 : (∑ k ∈ Finset.univ.erase i, q k) + = q j + ∑ k ∈ (Finset.univ.erase i).erase j, q k := + (Finset.add_sum_erase _ q hjm).symm + have hrest : e - q i - q j = ∑ k ∈ (Finset.univ.erase i).erase j, q k := by + rw [← hsum, h1, h2]; abel + have hsos : IsSoS mulLₗ (e - q i - q j) := by + rw [hrest] + exact isSoS_sum _ _ fun k _ => isSoS_of_idem (hidem k) + have hfl : (e - q i) * q j = q j := + face_lemma he (sub_idem he (hidem i)) (isSoS_of_idem (hidem j)) hsos + rw [sub_mul, he, sub_eq_self] at hfl + exact hfl + +/-! ## Reading the spectral coefficients -/ + +omit [IsCommJordan J] [IsFormallyReal J] [Module.Finite ℝ J] in +/-- A combination of orthogonal idempotents with nonnegative coefficients is in the cone. The +coefficient condition is only imposed where the idempotent is nonzero, matching what +`nonneg_coeff` can supply: `EuclideanJordan/Spectral.lean`'s resolution pads with a + possibly-zero idempotent, +whose coefficient is unconstrained. -/ +theorem isSoS_sum_smul_idem {n : ℕ} {q : Fin n → J} (hidem : ∀ i, q i * q i = q i) + {g : Fin n → ℝ} (hg : ∀ i, q i ≠ 0 → 0 ≤ g i) : + IsSoS mulLₗ (∑ i, g i • q i) := by + refine isSoS_sum _ _ fun i _ => ?_ + by_cases h : q i = 0 + · rw [h, smul_zero]; exact isSoS_zero + · exact isSoS_smul_idem (hg i h) (hidem i) + +/-- **A sum of squares has nonnegative spectral coefficients.** Pairing against `qₖ` reads the +coefficient off directly — `τ(qₖ, qᵢ) = tr(L_{qₖ ∘ qᵢ})` is `0` off the diagonal by orthogonality — +and the pairing is nonnegative because `x` is in the cone; the diagonal value `tr(L_{qₖ})` is at +least `1`. -/ +theorem nonneg_coeff {n : ℕ} {q : Fin n → J} {lam : Fin n → ℝ} + (hidem : ∀ i, q i * q i = q i) (horth : ∀ i j, i ≠ j → q i * q j = 0) + {x : J} (hx : x = ∑ i, lam i • q i) (hsos : IsSoS mulLₗ x) + {k : Fin n} (hk : q k ≠ 0) : 0 ≤ lam k := by + have hpair : traceForm (q k) x = lam k * jtr (q k) := by + rw [hx, map_sum, Finset.sum_eq_single k] + · rw [map_smul, smul_eq_mul, traceForm_apply, hidem k] + · intro i _ hik + rw [map_smul, smul_eq_mul, traceForm_apply, horth k i (Ne.symm hik), map_zero, mul_zero] + · intro h; exact absurd (Finset.mem_univ k) h + have hnn : 0 ≤ traceForm (q k) x := traceForm_nonneg_of_idem_of_isSoS (hidem k) hsos + have hpos : (0 : ℝ) < jtr (q k) := + lt_of_lt_of_le zero_lt_one (one_le_jtr_of_idem (hidem k) hk) + nlinarith [hpair, hnn, hpos] + +omit [IsCommJordan J] [IsScalarTower ℝ J J] [IsFormallyReal J] [Module.Finite ℝ J] in +/-- Subtracting a multiple of one member of a family shifts exactly that coefficient. The +bookkeeping step behind both witnesses in `idem_of_isSharp`. -/ +theorem sum_smul_sub_smul {n : ℕ} (q : Fin n → J) (g : Fin n → ℝ) (i : Fin n) (mu : ℝ) : + (∑ j, g j • q j) - mu • q i = ∑ j, (if j = i then g i - mu else g j) • q j := by + classical + have hpt : ∀ j : Fin n, (if j = i then g i - mu else g j) • q j + = g j • q j - (if j = i then mu • q i else 0) := by + intro j + by_cases hj : j = i + · subst hj; simp [sub_smul] + · simp [hj] + calc (∑ j, g j • q j) - mu • q i + = (∑ j, g j • q j) - ∑ j, (if j = i then mu • q i else 0) := by + simp [Finset.sum_ite_eq'] + _ = ∑ j, (g j • q j - (if j = i then mu • q i else 0)) := by + rw [Finset.sum_sub_distrib] + _ = ∑ j, (if j = i then g i - mu else g j) • q j := + (Finset.sum_congr rfl fun j _ => hpt j).symm + +/-! ## Sharpness -/ + +/-- **A sharp element**: in the cone, below the unit, and with no nonzero element of the cone below +both it and its complement. + +This is `EuclideanJordan/OrderUnitSpace.lean`'s `IsSharp` written in the sums-of-squares +vocabulary, with `0 ≤ z` spelled `IsSoS mulLₗ z` and `a ≤ b` spelled `IsSoS mulLₗ (b - a)`. No +lemma below relates the two; see the module docstring for why not. -/ +def IsSharp (e c : J) : Prop := + IsSoS mulLₗ c ∧ IsSoS mulLₗ (e - c) ∧ + ∀ x : J, IsSoS mulLₗ x → IsSoS mulLₗ (c - x) → IsSoS mulLₗ (e - c - x) → x = 0 + +/-- An idempotent is sharp. The face lemma applies at `c` and at `e − c`, giving `x = c ∘ x` and +`x = (e − c) ∘ x = x − c ∘ x`, so `x = 0`. -/ +theorem isSharp_of_idem {e c : J} (he : ∀ y : J, e * y = y) (hc : c * c = c) : IsSharp e c := by + refine ⟨isSoS_of_idem hc, isSoS_of_idem (sub_idem he hc), fun x hx hcx hecx => ?_⟩ + have h1 : c * x = x := face_lemma he hc hx hcx + have h2 : (e - c) * x = x := face_lemma he (sub_idem he hc) hx hecx + rw [sub_mul, he, h1, sub_self] at h2 + exact h2.symm + +/-- **A sharp element is an idempotent** — the half that spends the spectral theorem. + +Resolve `c = ∑ λᵢ qᵢ`. Being in the cone forces `λᵢ ≥ 0` and being below the unit forces +`λᵢ ≤ 1`, both by `nonneg_coeff`. A coefficient strictly inside `(0, 1)` would make +`min(λₖ, 1 − λₖ) • qₖ` a nonzero witness against sharpness, so every coefficient at a nonzero +idempotent is `0` or `1` — and then `λᵢ² = λᵢ` termwise. -/ +theorem idem_of_isSharp {e c : J} (he : ∀ y : J, e * y = y) (h : IsSharp e c) : c * c = c := by + obtain ⟨hc, hec, hsharp⟩ := h + obtain ⟨n, q, lam, hfam, hsum, hcq⟩ := spectral_resolution_complete e he c + have hec' : e - c = ∑ i, (1 - lam i) • q i := by + have hr := smul_unit_sub_eq hsum hcq 1 + rwa [one_smul] at hr + have hlo : ∀ i, q i ≠ 0 → 0 ≤ lam i := fun i hi => + nonneg_coeff hfam.idem hfam.orth hcq hc hi + have hhi : ∀ i, q i ≠ 0 → lam i ≤ 1 := by + intro i hi + have := nonneg_coeff hfam.idem hfam.orth hec' hec hi + linarith + have hbin : ∀ i, q i ≠ 0 → lam i = 0 ∨ lam i = 1 := by + intro i hi + rcases eq_or_lt_of_le (hlo i hi) with h0 | hlt0 + · exact Or.inl h0.symm + rcases eq_or_lt_of_le (hhi i hi) with h1 | hlt1 + · exact Or.inr h1 + exfalso + have hmupos : 0 < min (lam i) (1 - lam i) := lt_min hlt0 (by linarith) + set mu := min (lam i) (1 - lam i) with hmudef + have hmu1 : mu ≤ lam i := min_le_left _ _ + have hmu2 : mu ≤ 1 - lam i := min_le_right _ _ + have hwx : IsSoS mulLₗ (mu • q i) := isSoS_smul_idem (le_of_lt hmupos) (hfam.idem i) + have hwc : IsSoS mulLₗ (c - mu • q i) := by + rw [hcq, sum_smul_sub_smul q lam i mu] + refine isSoS_sum_smul_idem hfam.idem fun j hj => ?_ + by_cases hji : j = i + · rw [ite_eq_left hji]; linarith + · rw [ite_eq_right hji]; exact hlo j hj + have hwe : IsSoS mulLₗ (e - c - mu • q i) := by + rw [hec', sum_smul_sub_smul q (fun j => 1 - lam j) i mu] + refine isSoS_sum_smul_idem hfam.idem fun j hj => ?_ + by_cases hji : j = i + · rw [ite_eq_left hji] + show (0 : ℝ) ≤ 1 - lam i - mu + linarith + · rw [ite_eq_right hji] + show (0 : ℝ) ≤ 1 - lam j + linarith [hhi j hj] + have hzero := hsharp (mu • q i) hwx hwc hwe + rcases smul_eq_zero.mp hzero with hz | hz + · exact absurd hz (ne_of_gt hmupos) + · exact hi hz + have hsq : c * c = ∑ i, (lam i * lam i) • q i := sq_of_orthIdem hfam hcq + rw [hsq, hcq] + refine Finset.sum_congr rfl fun i _ => ?_ + by_cases hi : q i = 0 + · rw [hi, smul_zero, smul_zero] + · rcases hbin i hi with hb | hb <;> rw [hb] <;> norm_num + +/-- **Idempotents are exactly the sharp elements.** -/ +theorem isSharp_iff_idem {e c : J} (he : ∀ y : J, e * y = y) : IsSharp e c ↔ c * c = c := + ⟨idem_of_isSharp he, isSharp_of_idem he⟩ + +/-! ## The automorphism -/ + +variable {e : J} + +/-- **A unital linear order isomorphism preserves idempotents.** + +Sharpness is a statement about the cone and the unit, both of which `Φ` preserves; the `∀` clause is +transported through `Φ.symm`, which is where the *reflecting* half of `horder` is spent. -/ +theorem map_idem_of_orderIso (he : ∀ y : J, e * y = y) (Φ : J ≃ₗ[ℝ] J) (hunital : Φ e = e) + (horder : ∀ x : J, IsSoS mulLₗ x ↔ IsSoS mulLₗ (Φ x)) {c : J} (hc : c * c = c) : + Φ c * Φ c = Φ c := by + refine idem_of_isSharp he ?_ + obtain ⟨h1, h2, h3⟩ := isSharp_of_idem he hc + refine ⟨(horder c).mp h1, ?_, ?_⟩ + · have hstep := (horder (e - c)).mp h2 + rwa [map_sub, hunital] at hstep + · intro x hx hcx hecx + have hw : Φ.symm x = 0 := by + refine h3 (Φ.symm x) ((horder _).mpr ?_) ((horder _).mpr ?_) ((horder _).mpr ?_) + · rwa [Φ.apply_symm_apply] + · rwa [map_sub, Φ.apply_symm_apply] + · rwa [map_sub, map_sub, hunital, Φ.apply_symm_apply] + rw [← Φ.apply_symm_apply x, hw, map_zero] + +/-- **A unital linear order isomorphism preserves squares.** + +The image of a complete orthogonal family is a family of idempotents summing to the unit, hence +pairwise orthogonal by `orth_of_sum_eq_unit`; squaring is then coefficientwise on both sides. -/ +theorem map_sq_of_orderIso (he : ∀ y : J, e * y = y) (Φ : J ≃ₗ[ℝ] J) (hunital : Φ e = e) + (horder : ∀ x : J, IsSoS mulLₗ x ↔ IsSoS mulLₗ (Φ x)) (x : J) : + Φ (x * x) = Φ x * Φ x := by + obtain ⟨n, q, lam, hfam, hsum, hx⟩ := spectral_resolution_complete e he x + have hidem' : ∀ i, Φ (q i) * Φ (q i) = Φ (q i) := fun i => + map_idem_of_orderIso he Φ hunital horder (hfam.idem i) + have hsum' : (∑ i, Φ (q i)) = e := by rw [← map_sum, hsum, hunital] + have hfam' : IsOrthIdemFamily (fun i => Φ (q i)) := + ⟨hidem', orth_of_sum_eq_unit he hidem' hsum'⟩ + have hΦx : Φ x = ∑ i, lam i • Φ (q i) := by + rw [hx, map_sum]; simp only [map_smul] + rw [sq_of_orthIdem hfam' hΦx, sq_of_orthIdem hfam hx, map_sum] + simp only [map_smul] + +/-- **Koecher / Alfsen–Shultz.** A unital linear order isomorphism of a finite-dimensional formally +real Jordan algebra is a Jordan automorphism. + +Polarisation: `2 (x ∘ y) = (x + y)² − x² − y²`, and `Φ` is linear, so preservation of squares is +preservation of the product once the `2` is cancelled. -/ +theorem map_jordan_of_orderIso (he : ∀ y : J, e * y = y) (Φ : J ≃ₗ[ℝ] J) (hunital : Φ e = e) + (horder : ∀ z : J, IsSoS mulLₗ z ↔ IsSoS mulLₗ (Φ z)) (x y : J) : + Φ (x * y) = Φ x * Φ y := by + have hxy := map_sq_of_orderIso he Φ hunital horder (x + y) + have hxx := map_sq_of_orderIso he Φ hunital horder x + have hyy := map_sq_of_orderIso he Φ hunital horder y + have hl : (x + y) * (x + y) = x * x + (x * y + x * y) + y * y := by + rw [add_mul, mul_add, mul_add, mul_comm y x]; abel + have hr : (Φ x + Φ y) * (Φ x + Φ y) + = Φ x * Φ x + (Φ x * Φ y + Φ x * Φ y) + Φ y * Φ y := by + rw [add_mul, mul_add, mul_add, mul_comm (Φ y) (Φ x)]; abel + rw [hl, map_add Φ x y, hr, map_add, map_add, map_add, hxx, hyy] at hxy + have h4 : Φ (x * y) + Φ (x * y) = Φ x * Φ y + Φ x * Φ y := + add_left_cancel (add_right_cancel hxy) + have h5 : (2 : ℕ) • (Φ (x * y) - Φ x * Φ y) = 0 := by + rw [two_nsmul] + linear_combination (norm := abel) h4 + exact sub_eq_zero.mp (nsmul_eq_zero_iff' (by norm_num) h5) + +end Abstract + +/-! ## The theorem in bilinear-map vocabulary + +The crossing `EuclideanJordan/Bridge.lean` was built for: the *statement* mentions only the + bundled bilinear +map, so no ring instance has to exist before it elaborates; only the proof needs one. -/ + +section Interface + +variable {J : Type*} [NormedAddCommGroup J] [Module ℝ J] [Module.Finite ℝ J] + +/-- **Koecher / Alfsen–Shultz, in bilinear-map vocabulary.** The Jordan product as a bundled +bilinear map, the Jordan identity and formal reality as hypotheses in that vocabulary, and the cone +as `EuclideanJordan/Order.lean`'s `IsSoS`. + +★ No inner product appears. The ambient structure is a normed additive group carrying an +`ℝ`-module structure — `NormedAddCommGroup` only because `EuclideanJordan/Bridge.lean`'s + `ringOfBilinear` is +stated over one — and the norm is never used. -/ +theorem orderIso_preservesJordan (m : J →ₗ[ℝ] J →ₗ[ℝ] J) + (hcomm : ∀ x y : J, m x y = m y x) + (hjordan : ∀ a b : J, m (m a b) (m a a) = m a (m b (m a a))) + (hfr : ∀ (k : ℕ) (f : Fin k → J), (∑ i, m (f i) (f i)) = 0 → ∀ i, f i = 0) + (e : J) (he : ∀ y : J, m e y = y) + (Φ : J ≃ₗ[ℝ] J) (hunital : Φ e = e) + (horder : ∀ x : J, IsSoS m x ↔ IsSoS m (Φ x)) (x y : J) : + Φ (m x y) = m (Φ x) (Φ y) := by + let : NonUnitalNonAssocCommRing J := ringOfBilinear m hcomm + let : IsCommJordan J := ⟨hjordan⟩ + let : IsScalarTower ℝ J J := ⟨fun r a b => smul_bilinear m r a b⟩ + let : IsFormallyReal J := isFormallyReal_of_fin m hcomm hfr + have hm : (mulLₗ : J →ₗ[ℝ] J →ₗ[ℝ] J) = m := by ext a b; rfl + rw [hm] at * + exact map_jordan_of_orderIso he Φ hunital horder x y + +end Interface + +end EuclideanJordan diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/OrderUnitSpace.lean b/LeanPool/EuclideanJordan/EuclideanJordan/OrderUnitSpace.lean new file mode 100644 index 0000000000..fa3525f5e9 --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/OrderUnitSpace.lean @@ -0,0 +1,310 @@ +/- +Copyright (c) 2026 Bryan Ehrlich. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Bryan Ehrlich +-/ +module + +public import Mathlib.Basic.Real.Basic +public import Mathlib.Analysis.Normed.Module.Basic +public import Mathlib.Algebra.Order.Group.Defs +public import Mathlib.Tactic.Linarith + + + +/-! +# Order Unit Spaces + +An **order unit space** (V, V⁺, 1) is a real ordered vector space with a distinguished +order unit. Two honesty notes on this encoding: the domination field below is the +*order-unit* boundedness property (`a ≤ r • 1`), not the full Archimedean condition; +and the carried `NormedAddCommGroup`/`NormedSpace` structure is an independent normed +structure, not derived here as the order-unit norm. + +## Main definitions + +* `OrderUnitSpace` — typeclass for ordered real vector spaces with an order unit + (order-unit boundedness) +* `OrderUnitSpace.IsEffect` — predicate for the effect space [0, 1]_V + +## References + +* Alfsen-Shultz, Geometry of State Spaces of Operator Algebras +* van de Wetering, arXiv:1803.11139 +-/ + +@[expose] public section + +noncomputable section + +/-- An order unit space is a real vector space with a partial order compatible + with addition and a distinguished order unit (order-unit boundedness; see the + `archimedean` field note). -/ +class OrderUnitSpace (V : Type*) extends + NormedAddCommGroup V, + NormedSpace ℝ V, + PartialOrder V where + /-- Addition respects order on the left. -/ + add_le_add_left : ∀ (a b : V), a ≤ b → ∀ c, c + a ≤ c + b + /-- The order unit. -/ + ousUnit : V + /-- Nonneg scalar mult is monotone. -/ + smul_nonneg_mono : ∀ (r : ℝ), 0 ≤ r → ∀ {a b : V}, a ≤ b → r • a ≤ r • b + /-- The order unit is positive. -/ + ousUnit_nonneg : (0 : V) ≤ ousUnit + /-- **Order-unit boundedness** (the order-unit axiom): every element is dominated + by some nonnegative scalar multiple of the unit, `a ≤ r • 1`. This is the + order-unit property, weaker than the full Archimedean condition; the field name + is retained for continuity. -/ + archimedean : ∀ a : V, ∃ r : ℝ, 0 ≤ r ∧ a ≤ r • ousUnit + +namespace OrderUnitSpace + +variable {V : Type*} [OrderUnitSpace V] + +/-- The distinguished order unit of an order unit space. -/ +scoped notation "𝟙" => OrderUnitSpace.ousUnit (V := _) + +-- Ordered group lemmas derived from add_le_add_left + +theorem add_le_add_right' {a b : V} (h : a ≤ b) (c : V) : a + c ≤ b + c := by + rw [add_comm a c, add_comm b c] + exact add_le_add_left a b h c + +theorem neg_le_neg {a b : V} (h : a ≤ b) : -b ≤ -a := by + have h1 := add_le_add_right' h (-b) + rw [add_neg_cancel] at h1 + have h2 := add_le_add_right' h1 (-a) + simp? [add_assoc, add_neg_cancel, zero_add, add_zero] at h2 + exact h2 + +theorem neg_nonneg_of_nonpos {a : V} (h : a ≤ 0) : (0 : V) ≤ -a := by + have := neg_le_neg h + simp? at this + exact this + +theorem neg_nonpos_of_nonneg {a : V} (h : (0 : V) ≤ a) : -a ≤ 0 := by + have := neg_le_neg h + simp? at this + exact this + +theorem sub_nonneg_of_le {a b : V} (h : a ≤ b) : (0 : V) ≤ b - a := by + have h1 := add_le_add_right' h (-a) + rw [add_neg_cancel] at h1 + rwa [sub_eq_add_neg] + +theorem sub_le_self_of_nonneg {b : V} {a : V} (h : (0 : V) ≤ a) : b - a ≤ b := by + rw [sub_eq_add_neg] + have h1 : -a ≤ 0 := neg_nonpos_of_nonneg h + have h2 := add_le_add_left (-a) (0 : V) h1 b + rwa [add_zero] at h2 + +theorem le_add_of_nonneg_right {a b : V} (h : (0 : V) ≤ b) : a ≤ a + b := by + have h1 := add_le_add_left (0 : V) b h a + rw [add_zero] at h1 + exact h1 + +-- Effect space + +/-- An effect is an element `a` with `0 ≤ a ≤ 𝟙`. -/ +def IsEffect (a : V) : Prop := (0 : V) ≤ a ∧ a ≤ 𝟙 + +/-- The converse of `sub_nonneg_of_le`: a nonnegative difference gives an inequality. -/ +theorem le_of_sub_nonneg {a b : V} (h : (0 : V) ≤ b - a) : a ≤ b := by + have h1 := add_le_add_left 0 (b - a) h a + rw [add_zero, add_sub_cancel] at h1 + exact h1 + +/-- Subtracting a fixed element preserves order. -/ +theorem sub_le_sub_right' {a b : V} (h : a ≤ b) (c : V) : a - c ≤ b - c := by + refine le_of_sub_nonneg ?_ + have hrw : (b - c) - (a - c) = b - a := by abel + rw [hrw] + exact sub_nonneg_of_le h + +/-- Subtracting from a fixed element reverses order. -/ +theorem sub_le_sub_left' {a b : V} (h : a ≤ b) (c : V) : c - b ≤ c - a := by + refine le_of_sub_nonneg ?_ + have hrw : (c - a) - (c - b) = b - a := by abel + rw [hrw] + exact sub_nonneg_of_le h + +/-- A nonpositive difference gives an inequality. -/ +theorem le_of_sub_nonpos {a b : V} (h : a - b ≤ 0) : a ≤ b := by + refine le_of_sub_nonneg ?_ + have h1 : (0 : V) ≤ -(a - b) := neg_nonneg_of_nonpos h + have hrw : -(a - b) = b - a := by abel + rwa [hrw] at h1 + +/-- **Monotonicity in the SCALAR**: on a nonnegative element, a larger scalar gives a +larger multiple. (`smul_nonneg_mono` is monotonicity in the *element*; this is the +companion the class was missing, and the direct-sum carrier's order-unit bound needs it +to compare against `max r₁ r₂`.) -/ +theorem smul_le_smul_of_le_of_nonneg {r s : ℝ} (hrs : r ≤ s) {a : V} + (ha : (0 : V) ≤ a) : r • a ≤ s • a := by + apply le_of_sub_nonneg + rw [← sub_smul] + have h := smul_nonneg_mono (s - r) (by linarith) ha + rwa [smul_zero] at h + +theorem isEffect_zero : IsEffect (0 : V) := + ⟨le_refl 0, ousUnit_nonneg⟩ + +theorem isEffect_unit : IsEffect (𝟙 : V) := + ⟨ousUnit_nonneg, le_refl 𝟙⟩ + +/-- The orthocomplement `𝟙 - a` of an effect is an effect. -/ +theorem IsEffect.ortho {a : V} (h : IsEffect a) : + IsEffect (𝟙 - a) := + ⟨sub_nonneg_of_le h.2, sub_le_self_of_nonneg h.1⟩ + +/-- Two effects are orthogonal if `a + b ≤ 𝟙`. -/ +def AreOrthogonal (a b : V) : Prop := a + b ≤ 𝟙 + +/-- A sharp effect (projective unit) is an effect `p` with no nonzero + effect below both `p` and `𝟙 - p`. -/ +def IsSharp (p : V) : Prop := + IsEffect p ∧ ∀ (a : V), IsEffect a → a ≤ p → a ≤ 𝟙 - p → a = 0 + +/-- Sum of nonneg elements is nonneg. -/ +theorem add_nonneg {a b : V} (ha : (0 : V) ≤ a) (hb : (0 : V) ≤ b) : + (0 : V) ≤ a + b := + le_trans ha (le_add_of_nonneg_right hb) + +/-- Addition of effects whose sum is bounded. -/ +theorem IsEffect.add_of_le_unit {a b : V} (ha : IsEffect a) (hb : IsEffect b) + (hab : a + b ≤ 𝟙) : IsEffect (a + b) := + ⟨add_nonneg ha.1 hb.1, hab⟩ + +/-! ## The effects span, and linear maps are determined on them + +The textbook route proves the spanning property through the order-unit *norm* — if `‖v‖ ≤ ½` +then `0 ≤ ½𝟙 + v ≤ 𝟙`, so the effects contain a ball about `½𝟙`. That route needs the +carried norm to *be* the order-unit norm, which this class deliberately does not +assert (see the class docstring), and needs the Archimedean property proper rather than +the order-unit boundedness the `archimedean` field carries. + +The two conclusions — that the effects span and that linear maps agreeing on effects are +equal — do not need either. They follow from order-unit boundedness alone, which is what is +proved here: strictly more general than the norm route, and available at exactly this class's +own strength. The ball clause itself is *not* formalized; it is the textbook route, not its +content. -/ + +/-- Nonnegative scalars preserve nonnegativity. -/ +theorem smul_nonneg' {r : ℝ} (hr : 0 ≤ r) {a : V} (ha : (0 : V) ≤ a) : + (0 : V) ≤ r • a := by + have h := smul_nonneg_mono r hr ha + rwa [smul_zero] at h + +/-- A scalar in `[0,1]` times an effect is an effect. -/ +theorem isEffect_smul {r : ℝ} (hr0 : 0 ≤ r) (hr1 : r ≤ 1) {a : V} (ha : IsEffect a) : + IsEffect (r • a) := by + refine ⟨smul_nonneg' hr0 ha.1, ?_⟩ + calc r • a ≤ (1 : ℝ) • a := smul_le_smul_of_le_of_nonneg hr1 ha.1 + _ = a := one_smul ℝ a + _ ≤ 𝟙 := ha.2 + +/-- A scalar in `[0,1]` times the unit is an effect. -/ +theorem isEffect_smul_unit {r : ℝ} (hr0 : 0 ≤ r) (hr1 : r ≤ 1) : + IsEffect (r • (𝟙 : V)) := + isEffect_smul hr0 hr1 isEffect_unit + +/-- **The spanning clause.** The effects span the whole space. Proof from +order-unit boundedness only: bound `x` above by `r • 𝟙` and `-x` above by `s • 𝟙`, so +that `x + s • 𝟙` is nonnegative and below `(r+s) • 𝟙`; rescaling by +`c = r + s + 1 > 0` lands it in the effects, and `x = c • (that) - s • 𝟙` with `𝟙` +itself an effect. -/ +theorem span_isEffect_eq_top : + Submodule.span ℝ {a : V | IsEffect a} = ⊤ := by + rw [eq_top_iff] + intro x _ + obtain ⟨r, hr0, hr⟩ := archimedean x + obtain ⟨s, hs0, hs⟩ := archimedean (-x) + set c : ℝ := r + s + 1 with hc + have hcpos : (0 : ℝ) < c := by positivity + -- `x + s • 𝟙` is nonnegative + have hxs_nonneg : (0 : V) ≤ x + s • 𝟙 := by + have h := add_le_add_left (-x) (s • 𝟙) hs x + rwa [add_neg_cancel] at h + -- and bounded by `c • 𝟙` + have hxs_le : x + s • 𝟙 ≤ c • (𝟙 : V) := by + calc x + s • 𝟙 ≤ r • 𝟙 + s • 𝟙 := add_le_add_right' hr _ + _ = (r + s) • (𝟙 : V) := (add_smul r s 𝟙).symm + _ ≤ c • (𝟙 : V) := + smul_le_smul_of_le_of_nonneg (by rw [hc]; linarith) ousUnit_nonneg + -- so its `c`-rescaling is an effect + have hy : IsEffect (c⁻¹ • (x + s • 𝟙)) := by + refine ⟨smul_nonneg' (le_of_lt (inv_pos.mpr hcpos)) hxs_nonneg, ?_⟩ + have h := smul_nonneg_mono c⁻¹ (le_of_lt (inv_pos.mpr hcpos)) hxs_le + rwa [smul_smul, inv_mul_cancel₀ (ne_of_gt hcpos), one_smul] at h + -- and `x` is a combination of it and the unit + have hx : x = c • (c⁻¹ • (x + s • 𝟙)) - s • 𝟙 := by + rw [smul_smul, mul_inv_cancel₀ (ne_of_gt hcpos), one_smul] + abel + rw [hx] + exact Submodule.sub_mem _ + (Submodule.smul_mem _ _ (Submodule.subset_span hy)) + (Submodule.smul_mem _ _ (Submodule.subset_span isEffect_unit)) + +/-- **The extensionality clause.** Two linear maps agreeing on the effects are +equal. This is what the spanning property is normally put to use for. -/ +theorem linearMap_eq_of_eq_on_effects {W : Type*} [AddCommGroup W] [Module ℝ W] + (f g : V →ₗ[ℝ] W) (h : ∀ a : V, IsEffect a → f a = g a) : f = g := + LinearMap.ext_on span_isEffect_eq_top (fun a ha => h a ha) + +/-- **The Archimedean property**, in the sense the standard definition of an order unit space +carries and this class does not. + +The class's `archimedean` field is order-unit *boundedness* only — every element is under +*some* multiple of the unit. This is the genuine Archimedean squeeze: an element under +*every* positive multiple of the unit is nonpositive. The two are different, and the +difference is what a homogeneity argument consumes at its last step. + +★ It is supplied as an explicit `Prop` rather than added to the class, so that any theorem +needing it says so in its own statement. Nothing is given up by that: the Archimedean property +*is* part of the standard definition of an order unit space — not a stand-in for a cited +result — so a theorem proved under it is proved at the standard generality. -/ +def IsArchimedean (V : Type*) [OrderUnitSpace V] : Prop := + ∀ x : V, (∀ ε : ℝ, 0 < ε → x ≤ ε • ousUnit) → x ≤ 0 + +/-- The textbook Archimedean order-unit condition in its `ℕ` form (Alfsen–Shultz, +Paulsen–Tomforde): `n • x ≤ 𝟙` for every positive `n` forces `x ≤ 0`. -/ +def IsArchNat (V : Type*) [OrderUnitSpace V] : Prop := + ∀ x : V, (∀ n : ℕ, 0 < n → (n : ℝ) • x ≤ (𝟙 : V)) → x ≤ 0 + +theorem archNat_of_arch {V : Type*} [OrderUnitSpace V] (h : IsArchimedean V) : + IsArchNat V := by + intro x hx + refine h x ?_ + intro ε hε + obtain ⟨n, hn⟩ := exists_nat_gt (1 / ε) + have hnpos : (0 : ℝ) < (n : ℝ) := lt_trans (by positivity) hn + have hn0 : 0 < n := by exact_mod_cast hnpos + have h2 := smul_nonneg_mono ((n : ℝ))⁻¹ (by positivity) (hx n hn0) + rw [smul_smul, inv_mul_cancel₀ (ne_of_gt hnpos), one_smul] at h2 + have h3 : ((n : ℝ))⁻¹ ≤ ε := by + rw [div_lt_iff₀ hε] at hn + rw [inv_eq_one_div, div_le_iff₀ hnpos] + nlinarith + exact le_trans h2 (smul_le_smul_of_le_of_nonneg h3 ousUnit_nonneg) + +theorem arch_of_archNat {V : Type*} [OrderUnitSpace V] (h : IsArchNat V) : + IsArchimedean V := by + intro x hx + refine h x ?_ + intro n hn0 + have hnpos : (0 : ℝ) < (n : ℝ) := by exact_mod_cast hn0 + have h2 := smul_nonneg_mono ((n : ℝ)) (le_of_lt hnpos) (hx ((n : ℝ))⁻¹ (by positivity)) + rwa [smul_smul, mul_inv_cancel₀ (ne_of_gt hnpos), one_smul] at h2 + +/-- **`IsArchimedean` is exactly the textbook notion — neither stronger nor weaker.** + +This matters for honesty, not for any proof. A theorem proved under `IsArchimedean` is claimed +to hold at the standard generality of an order unit space, on the grounds that the Archimedean +property is *part of the standard definition* rather than a stand-in for a cited result. That +defence is only as good as the claim that this `Prop` is the standard condition — so here it +is, machine-checked in both directions against the `ℕ` form. -/ +theorem arch_iff {V : Type*} [OrderUnitSpace V] : IsArchimedean V ↔ IsArchNat V := + ⟨archNat_of_arch, arch_of_archNat⟩ + +end OrderUnitSpace diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/Orthogonal.lean b/LeanPool/EuclideanJordan/EuclideanJordan/Orthogonal.lean new file mode 100644 index 0000000000..5c6bcfd42b --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/Orthogonal.lean @@ -0,0 +1,95 @@ +/- +Copyright (c) 2026 Bryan Ehrlich. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Bryan Ehrlich +-/ +module + +public import LeanPool.EuclideanJordan.EuclideanJordan.PeirceMul + + + +/-! +# Orthogonal idempotents, and the simultaneous-diagonalisation field + +This file derives, at the generality of an arbitrary idempotent, the Faraut–Korányi +simultaneous-diagonalisation fact: + +> an element **scalar on `range q`** and an element of **`J₂(q)`** operator-commute. + +Here `q` is any idempotent `c`, "scalar on `range c`" is `a = μ • c + a₀` with `a₀ ∈ J₀(c)`, +and `J₂(c)` is `J₁(c)` in the eigenvalue naming of `EuclideanJordan/Peirce.lean`. The proof is four +lines, because `EuclideanJordan/PeirceMul.lean` already did the work: `L_c` commutes with `L_b` for +`b ∈ J₁(c)`, and `L_{a₀}` commutes with `L_b` for `a₀ ∈ J₀(c)`, so `L_a = μ L_c + L_{a₀}` +commutes with `L_b` by linearity. + +The interface's `q` is a *rank-two* idempotent `pᵢ + pⱼ` built from a Jordan frame, so +`add_idem_of_orthogonal` below supplies the shape: a sum of two orthogonal idempotents is an +idempotent, and then the general result applies. + +## Scope + +The frame-level version of the statement quantifies over a rank-two `q = pᵢ + pⱼ` built from a +Jordan frame; `add_idem_of_orthogonal` below supplies the shape that reduces it to the general +result — a sum of two orthogonal idempotents is an idempotent — +and `EuclideanJordan/Frame.lean` assembles it (`opCommute_scalarOn_frame`). + +## References + +* Faraut and Korányi, *Analysis on Symmetric Cones*, Ch. IV. +-/ + +@[expose] public section + +namespace EuclideanJordan + +section Orthogonal + +variable {J : Type*} [NonUnitalNonAssocCommRing J] [IsCommJordan J] [Module ℝ J] + +omit [IsCommJordan J] [Module ℝ J] in +/-- **A sum of two orthogonal idempotents is an idempotent.** Pure expansion — this needs +only commutativity and distributivity, not the Jordan identity. -/ +theorem add_idem_of_orthogonal {p q : J} (hp : p * p = p) (hq : q * q = q) (hpq : p * q = 0) : + (p + q) * (p + q) = p + q := by + have hqp : q * p = 0 := by rw [mul_comm]; exact hpq + rw [add_mul, mul_add, mul_add, hp, hq, hpq, hqp] + abel + +/-- **Orthogonal idempotents operator-commute.** Not merely `p ∘ q = 0`: the multiplication +operators themselves commute, which is what every simultaneous-diagonalisation argument +needs. Immediate from `opCommute_eigen_one_zero`, since `p ∈ J₁(p)` and `q ∈ J₀(p)`. -/ +theorem opCommute_of_orthogonal {p q : J} (hp : p * p = p) (hpq : p * q = 0) (w : J) : + p * (q * w) = q * (p * w) := + opCommute_eigen_one_zero hp hp hpq w + +section ScalarTower + +variable [IsScalarTower ℝ J J] + +/-- **Faraut–Korányi simultaneous diagonalisation, at single-idempotent generality.** + +If `a` is scalar on `range c` — that is, `a = μ • c + a₀` with `a₀` in the `0`-Peirce +component — and `b` lies in the `1`-Peirce component `J₂(c)`, then `L_a` and `L_b` commute. + +It is a consequence of the Jordan identity alone. -/ +theorem opCommute_scalarOn {c a a₀ b : J} {μ : ℝ} (hc : c * c = c) + (ha : a = μ • c + a₀) (ha₀ : c * a₀ = 0) (hb : c * b = b) (w : J) : + a * (b * w) = b * (a * w) := by + have hcb : c * (b * w) = b * (c * w) := mul_comm_of_eigen_one hc hb w + have h0 : a₀ * (b * w) = b * (a₀ * w) := (opCommute_eigen_one_zero hc hb ha₀ w).symm + subst ha + rw [add_mul, add_mul, mul_add, smul_mul_assoc, smul_mul_assoc, hcb, h0, mul_smul_comm'] + +/-- The interface's actual shape: `c` is the rank-two idempotent `p + q` built from two +orthogonal idempotents of a Jordan frame. -/ +theorem opCommute_scalarOn_pair {p q a a₀ b : J} {μ : ℝ} (hp : p * p = p) (hq : q * q = q) + (hpq : p * q = 0) (ha : a = μ • (p + q) + a₀) (ha₀ : (p + q) * a₀ = 0) + (hb : (p + q) * b = b) (w : J) : a * (b * w) = b * (a * w) := + opCommute_scalarOn (add_idem_of_orthogonal hp hq hpq) ha ha₀ hb w + +end ScalarTower + +end Orthogonal + +end EuclideanJordan diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/Pattern.lean b/LeanPool/EuclideanJordan/EuclideanJordan/Pattern.lean new file mode 100644 index 0000000000..1f35eceda7 --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/Pattern.lean @@ -0,0 +1,104 @@ +/- +Copyright (c) 2026 Bryan Ehrlich. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Bryan Ehrlich +-/ +module + +public import LeanPool.EuclideanJordan.EuclideanJordan.Block + + + +/-! +# The Faraut–Korányi block pattern, forced by three constraints + +`EuclideanJordan/Block.lean` characterises the rank-two block `J₁(p+q)`. This file does the + frame-level +analogue, but as a **constraint on eigenvalue patterns** rather than as an assembled direct +sum — which is both what is provable here and what the FK block structure actually *is*. + +Let `p` be an orthogonal idempotent family and `x ≠ 0` a joint eigenvector, +`pᵢ ∘ x = μᵢ • x`, lying in `J₁(∑ pᵢ)`. Three facts: + +* `sum_eigen_eq_one` — **`∑ μᵢ = 1`**; +* `eigen_pattern_mem` — **each `μᵢ ∈ {0, 1/2, 1}`** (the trichotomy, applied at each `pᵢ`); +* `eigen_pattern_card_le_two` — **at most two `μᵢ` are nonzero**, since every nonzero one is + at least `1/2` and they sum to `1`. + +Together these force the Faraut–Korányi pattern: **either exactly one `μᵢ` is `1` — the +diagonal block `J_ii` — or exactly two are `1/2` — the coherence block `J_ij`.** The +arithmetic is immediate from the three: zero nonzero entries give sum `0 ≠ 1`; one gives a +single entry equal to `1`, so it is `1` and not `1/2`; two give `a + b = 1` with +`a, b ∈ {1/2, 1}`, whose only solution is `1/2 + 1/2`. + +★ **That packaged statement is deliberately NOT a theorem here.** Formalising it means +extracting the elements of a `Finset` of cardinality `≤ 2` and case-splitting, which is +bookkeeping with no mathematical content, and stating it without proving it is exactly the +defect this arc spent four audit rounds removing. The three constraints are proved; the +one-line consequence is written out above so a reader can check it rather than trust it. + +★ **What is still not built:** the assembled direct sum `J = ⊕_{i ≤ j} J_{ij}`. These +constraints say what the summands can be, not that every element decomposes into them. +-/ + +@[expose] public section + +namespace EuclideanJordan + +open Finset + +section Pattern + +variable {J : Type*} [NonUnitalNonAssocCommRing J] [IsCommJordan J] [Module ℝ J] + [IsScalarTower ℝ J J] +variable {ι : Type*} [Fintype ι] [DecidableEq ι] {p : ι → J} {x : J} {mu : ι → ℝ} + +omit [IsCommJordan J] [IsScalarTower ℝ J J] [DecidableEq ι] in +/-- **The eigenvalue pattern of a joint eigenvector in the block sums to 1.** -/ +theorem sum_eigen_eq_one (hx : x ≠ 0) (hs : (∑ i, p i) * x = x) + (hmu : ∀ i, p i * x = mu i • x) : ∑ i, mu i = 1 := by + have hexp : (∑ i, p i) * x = (∑ i, mu i) • x := by + rw [Finset.sum_mul, Finset.sum_smul] + exact Finset.sum_congr rfl fun i _ => hmu i + have hcalc : (∑ i, mu i) • x = x := by rw [← hexp, hs] + have hz : ((∑ i, mu i) - 1) • x = 0 := by rw [sub_smul, hcalc, one_smul, sub_self] + rcases smul_eq_zero.mp hz with h | h + · linarith [sub_eq_zero.mp h] + · exact absurd h hx + +omit [Fintype ι] [DecidableEq ι] in +/-- **Each entry of the pattern is `0`, `1/2` or `1`.** -/ +theorem eigen_pattern_mem (hp : IsOrthIdemFamily p) (hx : x ≠ 0) + (hmu : ∀ i, p i * x = mu i • x) (i : ι) : mu i = 0 ∨ mu i = (2 : ℝ)⁻¹ ∨ mu i = 1 := + eigenvalue_trichotomy (hp.idem i) hx (hmu i) + +omit [DecidableEq ι] in +/-- **At most two frame idempotents see a given joint eigenvector.** -/ +theorem eigen_pattern_card_le_two (hp : IsOrthIdemFamily p) (hx : x ≠ 0) + (hs : (∑ i, p i) * x = x) (hmu : ∀ i, p i * x = mu i • x) : + ({i | mu i ≠ 0} : Finset ι).card ≤ 2 := by + classical + set S : Finset ι := {i | mu i ≠ 0} with hSdef + have hlb : ∀ i ∈ S, (2 : ℝ)⁻¹ ≤ mu i := by + intro i hi + have hne : mu i ≠ 0 := by simpa [hSdef] using hi + rcases eigen_pattern_mem hp hx hmu i with h | h | h + · exact absurd h hne + · rw [h] + · rw [h]; norm_num + have hsumS : ∑ i ∈ S, mu i = 1 := by + have hsub : ∑ i ∈ S, mu i = ∑ i, mu i := by + refine Finset.sum_subset (Finset.subset_univ S) ?_ + intro i _ hi + simpa [hSdef] using hi + rw [hsub, sum_eigen_eq_one hx hs hmu] + have hcard : (S.card : ℝ) * (2 : ℝ)⁻¹ ≤ ∑ i ∈ S, mu i := by + have h1 : ∑ _i ∈ S, (2 : ℝ)⁻¹ ≤ ∑ i ∈ S, mu i := Finset.sum_le_sum hlb + simpa [Finset.sum_const, nsmul_eq_mul] using h1 + rw [hsumS] at hcard + have : (S.card : ℝ) ≤ 2 := by linarith + exact_mod_cast this + +end Pattern + +end EuclideanJordan diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/Peirce.lean b/LeanPool/EuclideanJordan/EuclideanJordan/Peirce.lean new file mode 100644 index 0000000000..bce4c9192b --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/Peirce.lean @@ -0,0 +1,346 @@ +/- +Copyright (c) 2026 Bryan Ehrlich. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Bryan Ehrlich +-/ +module + +public import Mathlib.Algebra.Jordan.Basic +public import Mathlib.Analysis.Normed.Module.Basic +public import Mathlib.Basic.Real.Basic +public import Mathlib.Tactic.LinearCombination + + + +/-! +# The Peirce decomposition at a single idempotent + +It is natural to expect the Peirce decomposition to depend on the spectral theorem. **It does +not, in the direction that matters, and this file is the evidence.** +The Peirce decomposition at a *given* idempotent needs the Jordan identity and the +invertibility of `2`, and nothing else: no spectral theorem, no formal reality, no finite +dimension, no inner product, not even a unit. +★ The first draft of this sentence said "the Jordan identity and nothing else", which is +wrong — `peirce_poly` divides by `2` (`two_smul_eq_zero'`), which is why every statement below +it carries `Module ℝ J`. Only the *linearised identities* `two_lin1_raw`/`two_lin1_apply` are +genuinely torsion-free, and they are, deliberately: their factor of `2` is carried in the +statement rather than cancelled. Caught 2026-08-12 by reading the `omit` lines against this +paragraph. What the spectral theorem is needed for is *producing* idempotents — a Jordan frame — + not for +decomposing at one that is already in hand. `EuclideanJordan/FrameExists.lean` does the +producing; this file does the decomposing. + +## The mathematics + +For an idempotent `c`, the multiplication operator `L_c : y ↦ c ∘ y` satisfies + + `2·L_c³ − 3·L_c² + L_c = 0`, i.e. `L_c (L_c − 1) (2L_c − 1) = 0`, + +so its only possible eigenvalues are `0`, `1/2`, `1`, and the three Lagrange interpolants +at those roots are projections summing to the identity. That is the Peirce decomposition +`J = J₁(c) ⊕ J_{1/2}(c) ⊕ J₀(c)`. + +The polynomial identity comes from **one** substitution into the linearised Jordan identity. +Writing `⁅·,·⁆` for the commutator of multiplication operators, polarising the Jordan +identity `⁅L_x, L_{x²}⁆ = 0` at `x = a ± b` and subtracting gives + + `⁅L_{a²}, L_b⁆ + 2⁅L_{ab}, L_a⁆ = 0` (`two_lin1_raw`, up to a factor of 2), + +and evaluating that at `a := c`, `b := y`, argument `:= c` collapses immediately to the +Peirce polynomial. Mathlib proves only the `a ↔ b` *symmetrised* consequence +(`two_nsmul_lie_lmul_lmul_add_eq_lie_lmul_lmul_add`), which is strictly weaker; the `a − b` +substitution is what separates the two halves, and it costs one extra line. + +## What is here, and what is not + +Here: the polynomial identity, the three projections, the resolution of the identity, the +three eigenvalue equations, existence and uniqueness of the decomposition, and the +trichotomy (`L_c` has no eigenvalue outside `{0, 1/2, 1}`). + +**Not here:** the Faraut–Korányi *multiplication rules* between Peirce components +(`J_i ∘ J_j ⊆ …`), which are `EuclideanJordan/PeirceMul.lean`, and the decomposition relative +to a whole Jordan frame, which is `EuclideanJordan/FramePeirce.lean`. Nothing in this file +should be read as covering them. + +## References + +Mathlib's Jordan support (`Mathlib/Algebra/Jordan/Basic.lean`, 237 lines) is the classes +`IsJordan` / `IsCommJordan`, five operator-commutation lemmas and two linearised +identities. There is no idempotent theory, no Peirce decomposition and no spectral theory +in Mathlib; we are aware of none in any other proof assistant either, though we have not +searched them systematically. + +* Faraut and Korányi, *Analysis on Symmetric Cones*, Prop. IV.1.1. +* McCrimmon, *A Taste of Jordan Algebras*, §II.8. +-/ + +@[expose] public section + +namespace EuclideanJordan + +local notation "L" => AddMonoid.End.mulLeft + +/-! Applying an `AddMonoid.End` expression to an element is definitional in every constructor +we use, but Mathlib's corresponding lemmas are phrased for the `AddMonoidHom` coercion and do not +match the `AddMonoid.End` one. Lean 4.28's simp set bridged this on its own; 4.30's does not, so +the four `rfl`s are stated here and passed to `simpa` explicitly. -/ + +private theorem L_apply {J : Type*} [NonUnitalNonAssocSemiring J] (a w : J) : + (AddMonoid.End.mulLeft a) w = a * w := rfl + +private theorem End_add_apply {J : Type*} [NonUnitalNonAssocCommRing J] + (f g : AddMonoid.End J) (w : J) : (f + g) w = f w + g w := rfl + +private theorem End_mul_apply {J : Type*} [NonUnitalNonAssocCommRing J] + (f g : AddMonoid.End J) (w : J) : (f * g) w = f (g w) := rfl + +private theorem End_neg_apply {J : Type*} [NonUnitalNonAssocCommRing J] + (f : AddMonoid.End J) (w : J) : (-f) w = -(f w) := rfl + +section Linearisation + +variable {J : Type*} [NonUnitalNonAssocCommRing J] [IsCommJordan J] + +/-- **Twice the linearised Jordan identity.** Polarising `⁅L_x, L_{x²}⁆ = 0` at `x = a + b` +and at `x = a − b` and subtracting isolates the half that the symmetrised Mathlib version +(`two_nsmul_lie_lmul_lmul_add_eq_lie_lmul_lmul_add`) leaves fused. + +Stated with the factor `2` carried rather than cancelled, so that this lemma needs no +torsion hypothesis and holds over any `NonUnitalNonAssocCommRing`. -/ +theorem two_lin1_raw (a b : J) : + (2 : ℕ) • ⁅L b, L (a * a)⁆ + (4 : ℕ) • ⁅L a, L (a * b)⁆ = 0 := by + -- Mathlib's `LieRing.ofAssociativeRing` is only a `local instance` of its own file, so a ring's + -- commutator carries `Ring.instBracket` but no `LieRing`, and `lie_add`/`add_lie`/`lie_sub`/ + -- `sub_lie` cannot fire on `AddMonoid.End J`. Reinstating it here is what makes the `simp only` + -- below distribute the bracket over the polarised sums. + let _ : LieRing (AddMonoid.End J) := LieRing.ofAssociativeRing + have hs := (commute_lmul_lmul_sq (a + b)).lie_eq + have hd := (commute_lmul_lmul_sq (a - b)).lie_eq + have ha := (commute_lmul_lmul_sq a).lie_eq + have hb := (commute_lmul_lmul_sq b).lie_eq + simp only [add_mul, mul_add, sub_mul, mul_sub, map_add, map_sub, lie_add, add_lie, + lie_sub, sub_lie, mul_comm b a] at hs hd + rw [ha, hb] at hs hd + have h := sub_eq_zero_of_eq (hs.trans hd.symm) + rw [← h] + abel + +/-- `two_lin1_raw` evaluated at an element. -/ +theorem two_lin1_apply (a b w : J) : + (2 : ℕ) • (b * (a * a * w) - a * a * (b * w)) + + (4 : ℕ) • (a * (a * b * w) - a * b * (a * w)) = 0 := by + have h := congrArg (fun f : AddMonoid.End J => f w) (two_lin1_raw a b) + simpa [Ring.lie_def, sub_eq_add_neg, L_apply, End_add_apply, End_mul_apply, + End_neg_apply] using h + +end Linearisation + +section Projections + +variable {J : Type*} [NonUnitalNonAssocCommRing J] [Module ℝ J] [IsScalarTower ℝ J J] + +/-- In a *commutative* algebra the scalar-tower rule already gives the `SMulCommClass` +rule, so only `IsScalarTower` has to be assumed — which is what the concrete carrier +`HermitianMat` supplies (`EuclideanJordan/Vendor/HermitianMat/Jordan.lean`). -/ +theorem mul_smul_comm' (r : ℝ) (a b : J) : a * (r • b) = r • (a * b) := by + rw [mul_comm, smul_mul_assoc, mul_comm] + +/-- The Jordan multiplication operator `L_c : y ↦ c ∘ y`, as an `ℝ`-linear map. -/ +def mulL (c : J) : J →ₗ[ℝ] J where + toFun y := c * y + map_add' := mul_add c + map_smul' r y := mul_smul_comm' r c y + +@[simp] theorem mulL_apply (c y : J) : mulL c y = c * y := rfl + +/-- The Peirce projection onto the `1`-eigenspace of `L_c`: the Lagrange interpolant +`2L² − L`, which is `1` at `1` and `0` at `0` and `1/2`. -/ +def peirceOne (c : J) : J →ₗ[ℝ] J := (2 : ℝ) • (mulL c ∘ₗ mulL c) - mulL c + +/-- The Peirce projection onto the `1/2`-eigenspace of `L_c`: the Lagrange interpolant +`4L − 4L²`. -/ +def peirceHalf (c : J) : J →ₗ[ℝ] J := (4 : ℝ) • mulL c - (4 : ℝ) • (mulL c ∘ₗ mulL c) + +/-- The Peirce projection onto the `0`-eigenspace of `L_c`: the Lagrange interpolant +`1 − 3L + 2L²`. -/ +def peirceZero (c : J) : J →ₗ[ℝ] J := + LinearMap.id - (3 : ℝ) • mulL c + (2 : ℝ) • (mulL c ∘ₗ mulL c) + +@[simp] theorem peirceOne_apply (c y : J) : + peirceOne c y = (2 : ℝ) • (c * (c * y)) - c * y := rfl + +@[simp] theorem peirceHalf_apply (c y : J) : + peirceHalf c y = (4 : ℝ) • (c * y) - (4 : ℝ) • (c * (c * y)) := rfl + +@[simp] theorem peirceZero_apply (c y : J) : + peirceZero c y = y - (3 : ℝ) • (c * y) + (2 : ℝ) • (c * (c * y)) := rfl + +/-- **The resolution of the identity.** The three Lagrange interpolants sum to `1`. + +★ This is pure polynomial arithmetic and holds for **every** `c`, idempotent or not — it is +the Jordan identity that makes the three summands land in the eigenspaces, not the +resolution itself. Keeping the two facts separate is what makes the failure mode visible: +a decomposition into three pieces is worthless without knowing what the pieces are. -/ +theorem peirce_add_add (c y : J) : peirceOne c y + peirceHalf c y + peirceZero c y = y := by + simp only [peirceOne_apply, peirceHalf_apply, peirceZero_apply] + module + +/-! ### How the projections act on the eigenspaces + +These six lemmas are the "already an eigenvector" direction, and — like `peirce_add_add` — +they are polynomial arithmetic that needs no Jordan identity: they say what the Lagrange +interpolants do to something already known to satisfy `c ∘ y = μ • y`. -/ + +/-- On the `1`-eigenspace, `peirceOne` is the identity. -/ +theorem peirceOne_of_eigen {c y : J} (h : c * y = y) : peirceOne c y = y := by + simp only [peirceOne_apply, h] + module + +/-- `peirceOne` kills the `1/2`-eigenspace. -/ +theorem peirceOne_of_eigen_half {c y : J} (h : c * y = (2 : ℝ)⁻¹ • y) : + peirceOne c y = 0 := by + simp only [peirceOne_apply, h, mul_smul_comm'] + module + +/-- `peirceOne` kills the `0`-eigenspace. -/ +theorem peirceOne_of_eigen_zero {c y : J} (h : c * y = 0) : peirceOne c y = 0 := by + simp only [peirceOne_apply, h, mul_zero] + module + +/-- `peirceHalf` kills the `1`-eigenspace. -/ +theorem peirceHalf_of_eigen {c y : J} (h : c * y = y) : peirceHalf c y = 0 := by + simp only [peirceHalf_apply, h] + module + +/-- On the `1/2`-eigenspace, `peirceHalf` is the identity. -/ +theorem peirceHalf_of_eigen_half {c y : J} (h : c * y = (2 : ℝ)⁻¹ • y) : + peirceHalf c y = y := by + simp only [peirceHalf_apply, h, mul_smul_comm'] + module + +/-- `peirceHalf` kills the `0`-eigenspace. -/ +theorem peirceHalf_of_eigen_zero {c y : J} (h : c * y = 0) : peirceHalf c y = 0 := by + simp only [peirceHalf_apply, h, mul_zero] + module + +end Projections + +section Peirce + +variable {J : Type*} [NonUnitalNonAssocCommRing J] [IsCommJordan J] [Module ℝ J] + [IsScalarTower ℝ J J] + +omit [IsCommJordan J] [IsScalarTower ℝ J J] in +private theorem two_smul_eq_zero' {x : J} (h : (2 : ℕ) • x = 0) : x = 0 := by + have h2 : (2 : ℝ) • x = 0 := by + rw [show ((2 : ℝ)) = ((2 : ℕ) : ℝ) by norm_num, Nat.cast_smul_eq_nsmul] + exact h + simpa using h2 + +omit [IsScalarTower ℝ J J] in +/-- **The Peirce polynomial identity.** For an idempotent `c`, + + `2·L_c³ − 3·L_c² + L_c = 0`, i.e. `L_c (L_c − 1) (2L_c − 1) = 0`. + +Everything else in this file is a consequence. The proof is `two_lin1_apply` at +`a := c`, `b := y`, argument `:= c`, and nothing more: the hypotheses are the Jordan +identity and `c ∘ c = c`. -/ +theorem peirce_poly {c : J} (hc : c * c = c) (y : J) : + (2 : ℕ) • (c * (c * (c * y))) + c * y = (3 : ℕ) • (c * (c * y)) := by + have h := two_lin1_apply c y c + simp only [hc, mul_comm y c, mul_comm (c * y) c] at h + refine sub_eq_zero.mp (two_smul_eq_zero' ?_) + rw [← h] + abel + +omit [IsScalarTower ℝ J J] in +/-- `peirce_poly` solved for the cube, over `ℝ` — the form every consumer below uses. -/ +theorem peirce_cube {c : J} (hc : c * c = c) (y : J) : + c * (c * (c * y)) = (3 / 2 : ℝ) • (c * (c * y)) - (2 : ℝ)⁻¹ • (c * y) := by + have h := peirce_poly hc y + have h2 : (2 : ℝ) • (c * (c * (c * y))) + c * y = (3 : ℝ) • (c * (c * y)) := by + rw [show (2 : ℝ) = ((2 : ℕ) : ℝ) by norm_num, show (3 : ℝ) = ((3 : ℕ) : ℝ) by norm_num, + Nat.cast_smul_eq_nsmul, Nat.cast_smul_eq_nsmul] + exact h + have h3 : (2 : ℝ) • (c * (c * (c * y))) = (3 : ℝ) • (c * (c * y)) - c * y := + eq_sub_of_add_eq h2 + calc c * (c * (c * y)) = (2 : ℝ)⁻¹ • ((2 : ℝ) • (c * (c * (c * y)))) := by module + _ = (2 : ℝ)⁻¹ • ((3 : ℝ) • (c * (c * y)) - c * y) := by rw [h3] + _ = (3 / 2 : ℝ) • (c * (c * y)) - (2 : ℝ)⁻¹ • (c * y) := by module + +/-- The image of `peirceOne c` lies in the `1`-eigenspace of `L_c`. -/ +theorem mul_peirceOne {c : J} (hc : c * c = c) (y : J) : + c * peirceOne c y = peirceOne c y := by + simp only [peirceOne_apply, mul_sub, mul_smul_comm', peirce_cube hc y] + module + +/-- The image of `peirceHalf c` lies in the `1/2`-eigenspace of `L_c`. -/ +theorem mul_peirceHalf {c : J} (hc : c * c = c) (y : J) : + c * peirceHalf c y = (2 : ℝ)⁻¹ • peirceHalf c y := by + simp only [peirceHalf_apply, mul_sub, mul_smul_comm', peirce_cube hc y] + module + +/-- The image of `peirceZero c` lies in the `0`-eigenspace of `L_c`. -/ +theorem mul_peirceZero {c : J} (hc : c * c = c) (y : J) : c * peirceZero c y = 0 := by + simp only [peirceZero_apply, mul_add, mul_sub, mul_smul_comm', peirce_cube hc y] + module + +/-- **The Peirce decomposition, existence half.** Every element of a real commutative +Jordan algebra splits, relative to any idempotent `c`, into a part fixed by `L_c`, a part +halved by it, and a part killed by it. -/ +theorem exists_peirce_decomposition {c : J} (hc : c * c = c) (y : J) : + ∃ y₁ yₕ y₀ : J, c * y₁ = y₁ ∧ c * yₕ = (2 : ℝ)⁻¹ • yₕ ∧ c * y₀ = 0 + ∧ y = y₁ + yₕ + y₀ := + ⟨peirceOne c y, peirceHalf c y, peirceZero c y, mul_peirceOne hc y, mul_peirceHalf hc y, + mul_peirceZero hc y, (peirce_add_add c y).symm⟩ + +omit [IsCommJordan J] in +/-- **The Peirce decomposition, uniqueness half.** A vanishing sum of Peirce components is +componentwise zero — so the decomposition of `exists_peirce_decomposition` is unique, and +`J = J₁(c) ⊕ J_{1/2}(c) ⊕ J₀(c)` is a genuine direct sum. + +The proof needs no independence-of-eigenspaces import: applying the two projections +`peirceOne` and `peirceHalf` to the relation reads off two of the three components, and +the third follows by subtraction. -/ +theorem peirce_eq_zero_of_add_eq_zero {c y₁ yₕ y₀ : J} (h₁ : c * y₁ = y₁) + (hₕ : c * yₕ = (2 : ℝ)⁻¹ • yₕ) (h₀ : c * y₀ = 0) (h : y₁ + yₕ + y₀ = 0) : + y₁ = 0 ∧ yₕ = 0 ∧ y₀ = 0 := by + have e1 : y₁ = 0 := by + have := congrArg (peirceOne c) h + rwa [map_add, map_add, peirceOne_of_eigen h₁, peirceOne_of_eigen_half hₕ, + peirceOne_of_eigen_zero h₀, add_zero, add_zero, map_zero] at this + have eh : yₕ = 0 := by + have := congrArg (peirceHalf c) h + rwa [map_add, map_add, peirceHalf_of_eigen h₁, peirceHalf_of_eigen_half hₕ, + peirceHalf_of_eigen_zero h₀, add_zero, zero_add, map_zero] at this + refine ⟨e1, eh, ?_⟩ + rw [e1, eh, zero_add, zero_add] at h + exact h + +/-- **The eigenvalue trichotomy.** `L_c` has no eigenvalue outside `{0, 1/2, 1}`: the +Peirce polynomial annihilates it, and its roots are exactly those three. + +★ Stated for a *nonzero* eigenvector, which is the whole content — the equation +`c ∘ y = μ • y` is satisfied by `y = 0` for every `μ`. -/ +theorem eigenvalue_trichotomy {c : J} (hc : c * c = c) {y : J} (hy : y ≠ 0) {μ : ℝ} + (h : c * y = μ • y) : μ = 0 ∨ μ = (2 : ℝ)⁻¹ ∨ μ = 1 := by + have hp : peirceOne c y = (2 * μ * μ - μ) • y := by + simp only [peirceOne_apply, h, mul_smul_comm', smul_smul] + module + have key := mul_peirceOne hc y + rw [hp, mul_smul_comm', h, smul_smul] at key + have hs : ((2 * μ * μ - μ) * μ - (2 * μ * μ - μ)) • y = 0 := by + rw [sub_smul, key] + exact sub_self _ + have hz : (2 * μ * μ - μ) * μ - (2 * μ * μ - μ) = 0 := + (smul_eq_zero.mp hs).resolve_right hy + have hfac : μ * (2 * μ - 1) * (μ - 1) = 0 := by linear_combination hz + rcases mul_eq_zero.mp hfac with h' | h' + · rcases mul_eq_zero.mp h' with h'' | h'' + · exact Or.inl h'' + · exact Or.inr (Or.inl (by linarith)) + · exact Or.inr (Or.inr (by linarith)) + +end Peirce + +end EuclideanJordan diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/PeirceMul.lean b/LeanPool/EuclideanJordan/EuclideanJordan/PeirceMul.lean new file mode 100644 index 0000000000..bfda7784a0 --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/PeirceMul.lean @@ -0,0 +1,244 @@ +/- +Copyright (c) 2026 Bryan Ehrlich. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Bryan Ehrlich +-/ +module + +public import LeanPool.EuclideanJordan.EuclideanJordan.Peirce + + + +/-! +# The Peirce multiplication rules + +`EuclideanJordan/Peirce.lean` decomposes `J = J₁(c) ⊕ J_{1/2}(c) ⊕ J₀(c)` for an idempotent `c`. + This +file proves how the three components multiply — the Faraut–Korányi relations: + +| | `J₁` | `J_{1/2}` | `J₀` | +| --- | --- | --- | --- | +| **`J₁`** | `⊆ J₁` | `⊆ J_{1/2}` | `= 0` | +| **`J_{1/2}`** | `⊆ J_{1/2}` | `⊆ J₁ ⊕ J₀` | `⊆ J_{1/2}` | +| **`J₀`** | `= 0` | `⊆ J_{1/2}` | `⊆ J₀` | + +As in `EuclideanJordan/Peirce.lean`, the hypotheses are the Jordan identity and the + invertibility of the +integers used (`2` for the commuting rules, `4` for the half-half rule): no spectral theorem, +no formal reality, no finite dimension, no unit. +## The two ingredients + +`EuclideanJordan/Peirce.lean` needed only the *once*-linearised Jordan identity `two_lin1_raw`. + Five of +the six rules follow from a single consequence of it — **`L_x` commutes with `L_c` whenever +`x` lies in `J₁(c)` or `J₀(c)`** (`mul_comm_of_eigen_one`, `mul_comm_of_eigen_zero`) — after +which each rule is one rewrite. + +The sixth, `J_{1/2} ∘ J_{1/2} ⊆ J₁ ⊕ J₀`, is genuinely deeper and needs the *fully* +linearised identity `four_lin2_raw`, obtained here by polarising `two_lin1_raw` a second +time. Evaluated at the right point it collapses to `L_c² = L_c` on the product, which is +exactly "the `1/2`-component vanishes". + +## Why `opCommute_eigen_one_zero` is the one to look at + +The Faraut–Korányi simultaneous-diagonalisation fact — *an element scalar on `range q` and an +element of `J₂(q)` operator-commute* — is the load-bearing hypothesis in the coalescence +arguments that run over a Jordan frame. Its single-idempotent case is +`opCommute_eigen_one_zero` below, and it is three lines from `four_lin2_raw`. + +★ **That is a case, not the frame-level statement.** The frame-level version quantifies over a +rank-two `q = pᵢ + pⱼ` drawn from a Jordan frame, and this file has no frame. +`EuclideanJordan/Frame.lean` puts it in frame shape (`opCommute_scalarOn_frame`) once +orthogonal idempotent families are available, and `EuclideanJordan/Block.lean` supplies the +projection commutation (`peirceOne_comm_peirceOne` and siblings) and the exact +characterisation of the rank-two block. + +## References + +* Faraut and Korányi, *Analysis on Symmetric Cones*, Prop. IV.1.1 and Lemma IV.1.3. +* McCrimmon, *A Taste of Jordan Algebras*, §II.8. +-/ + +@[expose] public section + +namespace EuclideanJordan + +local notation "L" => AddMonoid.End.mulLeft + +/-! Applying an `AddMonoid.End` expression to an element is definitional in every constructor +we use, but Mathlib's corresponding lemmas are phrased for the `AddMonoidHom` coercion and do not +match the `AddMonoid.End` one. Lean 4.28's simp set bridged this on its own; 4.30's does not, so +the four `rfl`s are stated here and passed to `simpa` explicitly. -/ + +private theorem L_apply {J : Type*} [NonUnitalNonAssocSemiring J] (a w : J) : + (AddMonoid.End.mulLeft a) w = a * w := rfl + +private theorem End_add_apply {J : Type*} [NonUnitalNonAssocCommRing J] + (f g : AddMonoid.End J) (w : J) : (f + g) w = f w + g w := rfl + +private theorem End_mul_apply {J : Type*} [NonUnitalNonAssocCommRing J] + (f g : AddMonoid.End J) (w : J) : (f * g) w = f (g w) := rfl + +private theorem End_neg_apply {J : Type*} [NonUnitalNonAssocCommRing J] + (f : AddMonoid.End J) (w : J) : (-f) w = -(f w) := rfl + +section Lin2 + +variable {J : Type*} [NonUnitalNonAssocCommRing J] [IsCommJordan J] + +/-- **Four times the fully linearised Jordan identity.** Polarising `two_lin1_raw` a second +time, in `a := p + q`, and subtracting the two pure terms leaves the cyclic sum + + `⁅L_b, L_{pq}⁆ + ⁅L_p, L_{qb}⁆ + ⁅L_q, L_{pb}⁆ = 0`, + +which is the identity every Peirce multiplication rule beyond the commuting ones needs. +The factor `4` is carried rather than cancelled so that this holds with no torsion +hypothesis. -/ +theorem four_lin2_raw (p q b : J) : + (4 : ℕ) • (⁅L b, L (p * q)⁆ + ⁅L p, L (q * b)⁆ + ⁅L q, L (p * b)⁆) = 0 := by + -- Same instance gap as in `two_lin1_raw`: `LieRing.ofAssociativeRing` is a `local instance` + -- of its own Mathlib file, so a ring's commutator carries `Ring.instBracket` but no `LieRing`, + -- and `lie_add`/`add_lie` cannot fire on `AddMonoid.End J`. Reinstating it inside the proof — + -- never at section scope, which would elaborate this theorem's own `⁅·,·⁆` against a different + -- instance — is what makes the `simp only` below distribute the bracket. + let _ : LieRing (AddMonoid.End J) := LieRing.ofAssociativeRing + have h1 := two_lin1_raw (p + q) b + have hp := two_lin1_raw p b + have hq := two_lin1_raw q b + simp only [add_mul, mul_add, map_add, lie_add, add_lie, mul_comm q p] at h1 + have hpq : (2 : ℕ) • ⁅L b, L (p * p)⁆ + (4 : ℕ) • ⁅L p, L (p * b)⁆ + + ((2 : ℕ) • ⁅L b, L (q * q)⁆ + (4 : ℕ) • ⁅L q, L (q * b)⁆) = 0 := by + rw [hp, hq, add_zero] + have h := sub_eq_zero_of_eq (h1.trans hpq.symm) + rw [← h] + abel + +/-- `four_lin2_raw` evaluated at an element. -/ +theorem four_lin2_apply (p q b w : J) : + (4 : ℕ) • (b * (p * q * w) - p * q * (b * w) + + (p * (q * b * w) - q * b * (p * w)) + + (q * (p * b * w) - p * b * (q * w))) = 0 := by + have h := congrArg (fun f : AddMonoid.End J => f w) (four_lin2_raw p q b) + simpa [Ring.lie_def, sub_eq_add_neg, L_apply, End_add_apply, End_mul_apply, + End_neg_apply] using h + +end Lin2 + +section Commuting + +variable {J : Type*} [NonUnitalNonAssocCommRing J] [IsCommJordan J] [Module ℝ J] + +omit [IsCommJordan J] in +/-- Cancel a nonzero natural multiple in a real vector space. -/ +theorem nsmul_eq_zero_iff' {n : ℕ} (hn : n ≠ 0) {x : J} (h : n • x = 0) : x = 0 := by + have h2 : (n : ℝ) • x = 0 := by rw [Nat.cast_smul_eq_nsmul]; exact h + rcases smul_eq_zero.mp h2 with h' | h' + · exact absurd (Nat.cast_eq_zero.mp h') hn + · exact h' + +/-- **`L_x` commutes with `L_c` when `c ∘ x = x`.** The `1`-eigenvectors of `L_c` are +operator-compatible with `c`. Five of the six multiplication rules come from this and its +`0`-eigenvalue twin. -/ +theorem mul_comm_of_eigen_one {c x : J} (hc : c * c = c) (hx : c * x = x) (w : J) : + c * (x * w) = x * (c * w) := by + have h := two_lin1_apply c x w + simp only [hc, hx] at h + refine sub_eq_zero.mp (nsmul_eq_zero_iff' (n := 2) (by norm_num) ?_) + rw [← h] + abel + +/-- **`L_x` commutes with `L_c` when `c ∘ x = 0`.** -/ +theorem mul_comm_of_eigen_zero {c x : J} (hc : c * c = c) (hx : c * x = 0) (w : J) : + c * (x * w) = x * (c * w) := by + have h := two_lin1_apply c x w + simp only [hc, hx, zero_mul, mul_zero, sub_self] at h + refine (sub_eq_zero.mp (nsmul_eq_zero_iff' (n := 2) (by norm_num) ?_)).symm + rw [← h] + abel + +/-- **`J₁(c) ∘ J₁(c) ⊆ J₁(c)`.** -/ +theorem eigen_one_mul_one {c x y : J} (hc : c * c = c) (hx : c * x = x) (hy : c * y = y) : + c * (x * y) = x * y := by + rw [mul_comm_of_eigen_one hc hx y, hy] + +/-- **`J₀(c) ∘ J₀(c) ⊆ J₀(c)`.** -/ +theorem eigen_zero_mul_zero {c x y : J} (hc : c * c = c) (hx : c * x = 0) (hy : c * y = 0) : + c * (x * y) = 0 := by + rw [mul_comm_of_eigen_zero hc hx y, hy, mul_zero] + +/-- **`J₁(c) ∘ J₀(c) = 0`** — the two extreme Peirce components annihilate each other, and +not merely land in a common component. + +The proof reads the same product twice: `L_c` fixes it because `x ∈ J₁`, and `L_c` kills it +because `y ∈ J₀`. -/ +theorem eigen_one_mul_zero {c x y : J} (hc : c * c = c) (hx : c * x = x) (hy : c * y = 0) : + x * y = 0 := by + have h1 : c * (x * y) = 0 := by rw [mul_comm_of_eigen_one hc hx y, hy, mul_zero] + have h2 : c * (x * y) = x * y := by + rw [mul_comm x y, mul_comm_of_eigen_zero hc hy x, hx, mul_comm] + rw [← h2, h1] + +/-- **The single-idempotent case of Faraut–Korányi simultaneous diagonalisation.** + +An element of `J₁(c)` and an element of `J₀(c)` **operator**-commute: `L_x L_y = L_y L_x`, +which is strictly more than the product `x ∘ y` vanishing. It falls straight out of the +cyclic identity, because two of its three brackets vanish. + +★ This is the *case* `q = c`, not the field: the field ranges over a rank-two `q = pᵢ + pⱼ` +inside a Jordan frame, and there is no frame in this file. -/ +theorem opCommute_eigen_one_zero {c x y : J} (hc : c * c = c) (hx : c * x = x) + (hy : c * y = 0) (w : J) : x * (y * w) = y * (x * w) := by + have hxy : x * y = 0 := eigen_one_mul_zero hc hx hy + have h := four_lin2_apply x y c w + rw [hxy] at h + simp only [mul_comm y c, mul_comm x c, hx, hy, zero_mul, mul_zero, + sub_self, zero_add, add_zero] at h + refine (sub_eq_zero.mp (nsmul_eq_zero_iff' (n := 4) (by norm_num) ?_)).symm + rw [← h] + +end Commuting + +section HalfRules + +variable {J : Type*} [NonUnitalNonAssocCommRing J] [IsCommJordan J] [Module ℝ J] + [IsScalarTower ℝ J J] + +/-- **`J₁(c) ∘ J_{1/2}(c) ⊆ J_{1/2}(c)`.** -/ +theorem eigen_one_mul_half {c x y : J} (hc : c * c = c) (hx : c * x = x) + (hy : c * y = (2 : ℝ)⁻¹ • y) : c * (x * y) = (2 : ℝ)⁻¹ • (x * y) := by + rw [mul_comm_of_eigen_one hc hx y, hy, mul_smul_comm'] + +/-- **`J₀(c) ∘ J_{1/2}(c) ⊆ J_{1/2}(c)`.** -/ +theorem eigen_zero_mul_half {c x y : J} (hc : c * c = c) (hx : c * x = 0) + (hy : c * y = (2 : ℝ)⁻¹ • y) : c * (x * y) = (2 : ℝ)⁻¹ • (x * y) := by + rw [mul_comm_of_eigen_zero hc hx y, hy, mul_smul_comm'] + +/-- **`J_{1/2}(c) ∘ J_{1/2}(c) ⊆ J₁(c) ⊕ J₀(c)`**, stated as the polynomial relation +`L_c² = L_c` on the product — which is exactly "no `1/2`-component", since +`peirceHalf c z = 4•(c ∘ z) − 4•(c ∘ (c ∘ z))` collapses to `0` under it + (`peirceHalf_mul_half_eq_zero`). +★ An earlier draft attributed this to the eigenvalue trichotomy. It does not use the +trichotomy — it is the projection formula directly. + +This is the one rule that needs the fully linearised identity: evaluating `four_lin2_raw` +at `(c, y, x)` and argument `c`, the four `1/4`-terms cancel in pairs and what survives is +`L_c²(xy) − L_c(xy) = 0`. -/ +theorem eigen_half_mul_half {c x y : J} (hc : c * c = c) (hx : c * x = (2 : ℝ)⁻¹ • x) + (hy : c * y = (2 : ℝ)⁻¹ • y) : c * (c * (x * y)) = c * (x * y) := by + have h := four_lin2_apply c y x c + simp only [hc, hx, hy, smul_mul_assoc, mul_smul_comm', smul_smul, + mul_comm y c, mul_comm x c, mul_comm (x * y) c, mul_comm y x] at h + refine sub_eq_zero.mp (nsmul_eq_zero_iff' (n := 4) (by norm_num) ?_) + rw [← h] + abel + +/-- The `1/2`-component of a product of two `1/2`-elements vanishes — `eigen_half_mul_half` +read through the projection of `EuclideanJordan/Peirce.lean`. -/ +theorem peirceHalf_mul_half_eq_zero {c x y : J} (hc : c * c = c) + (hx : c * x = (2 : ℝ)⁻¹ • x) (hy : c * y = (2 : ℝ)⁻¹ • y) : + peirceHalf c (x * y) = 0 := by + rw [peirceHalf_apply, eigen_half_mul_half hc hx hy, sub_self] + +end HalfRules + +end EuclideanJordan diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/PeirceSubalgebra.lean b/LeanPool/EuclideanJordan/EuclideanJordan/PeirceSubalgebra.lean new file mode 100644 index 0000000000..b6d6fe2217 --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/PeirceSubalgebra.lean @@ -0,0 +1,248 @@ +/- +Copyright (c) 2026 Bryan Ehrlich. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Bryan Ehrlich +-/ +module + +public import LeanPool.EuclideanJordan.EuclideanJordan.Class + + + +/-! +# Peirce subalgebras: `J₂(c)` and `J₀(c)` as Euclidean Jordan algebras in their own right + +`EuclideanJordan/Peirce.lean` carries the Peirce decomposition at a single idempotent as three + *linear +maps* `peirceOne c`, `peirceHalf c`, `peirceZero c`, and `EuclideanJordan/PeirceMul.lean` + carries the six +Faraut–Korányi rules governing how their images multiply. Two of those rules — +`eigen_one_mul_one` and `eigen_zero_mul_zero` — say that the eigenvalue-`1` and eigenvalue-`0` +eigenspaces are closed under the product. This file turns that closure into structure: each +of the two eigenspaces is itself a Euclidean Jordan algebra, with its own unit. + +Everything that recurses into a Peirce subalgebra sits on this file. + +## The subobject vehicle: `Submodule`, not `NonUnitalSubalgebra` + +★ **This is a deviation from the build plan, and it is what made the module cheap.** The plan +routed the two eigenspaces through `NonUnitalSubalgebra ℝ J` and priced the module at its +risk #3, on a probe showing that `IsCommJordan ↥S` does not synthesise for a +`NonUnitalSubalgebra` subtype: Mathlib has no such transfer instance, so one would have to be +written, and `IsFormallyReal` with it. + +That probe reproduces here, re-run against this tree on 2026-08-22: with +`[EuclideanJordanAlgebra J]` in context, +`example (S : NonUnitalSubalgebra ℝ J) : IsCommJordan ↥S := inferInstance` fails to synthesise, +while the same `example` for `NonUnitalNonAssocCommRing ↥S` succeeds. The conclusion drawn from +it still does not apply, because under `EuclideanJordan/Class.lean`'s design **no transfer is + needed**. +The class puts `Mul` and `One` on top of an inner-product space rather than alongside a ring +structure, so the natural subobject is the same shape one level down: the ambient +`Submodule ℝ J`, whose subtype already carries +`NormedAddCommGroup`, `InnerProductSpace ℝ` and `Module ℝ` from Mathlib, with `Mul` and `One` +added on top. Then `EuclideanJordanAlgebra ↥(peirceOneSub hc)` is *constructed* from six +field proofs, every one of which is the ambient identity read through `Subtype.ext`, and +`IsCommJordan`, `IsScalarTower`, `SMulCommClass` and the ring structure then arrive on the +subtype the same way they arrive on `J`: as `EuclideanJordan/Class.lean`'s derived instances. + Nothing is +transferred because nothing has to be. + +★ **One rough edge, measured rather than predicted.** `IsFormallyReal ↥(peirceOneSub hc)` does +*not* come back from a bare `inferInstance`, though `IsCommJordan ↥(peirceOneSub hc)`, +`IsScalarTower`, `SMulCommClass` and `NonUnitalNonAssocCommRing` all do. The cause is +`IsFormallyReal`'s keying: it is indexed on `[Mul J]` **and** `[AddCommMonoid J]` as two +independent arguments, and on a `Submodule` subtype a bare goal picks +`(peirceOneSub hc).addCommMonoid` while `EuclideanJordan/Class.lean`'s instance carries the +ring-derived `AddCommMonoid`. The two are **definitionally equal** — both `rfl`-checked, as are +the two `Mul` paths — so this is an elaboration-order artifact and not a diamond, and it costs +nothing where it matters: every consumer in the tree demands `IsFormallyReal` downstream of a +`NonUnitalNonAssocCommRing` argument, so the ring-derived path is the one in the goal and the +instance matches. `spectral_resolution_complete'` therefore runs inside `J₂(c)` unaided. +Should a bare goal ever be wanted, `EuclideanJordanAlgebra.instIsFormallyReal +(J := ↥(peirceOneSub hc))` supplies it — pinning the type is what fixes it, since without it the +instance arguments are synthesised against a metavariable. No such instance is declared here, +because nothing needs one. + +Three facts make this work. None is asserted here on the strength of a past probe: each is +re-checked every time the file compiles, at the declaration named after it. + +* `NormedAddCommGroup ↥W` and `InnerProductSpace ℝ ↥W` synthesise for any `W : Submodule ℝ J` — + without which `instEJAPeirceOneSub` could not even be stated, since the class is indexed by + them; +* the inner product on the subtype is the ambient one **by `rfl`**, which is why + `instEJAPeirceOneSub`'s `inner_assoc` field is the ambient `inner_assoc` applied to the + coercions with no rewriting at all; +* membership in the two carriers is *definitionally* the eigenvalue equation, which is why + `instMulPeirceOneSub` passes `eigen_one_mul_one hc x.2 y.2` straight in as the closure proof + and `instEJAPeirceOneSub`'s `one_mul` field is `Subtype.ext x.2`. + +The plan's *specific* warning — that the class carries `One`, so the instance on `J₂(q)` is +constructed with `1 = q` rather than derived, and `simp`-normal-form care is owed around +`(1 : ↥S)` — is exactly right and is paid here by `coe_one_peirceOneSub` and +`coe_one_peirceZeroSub`, two `@[simp]` lemmas pinning `((1 : ↥(peirceOneSub hc)) : J) = c` and +`((1 : ↥(peirceZeroSub hc)) : J) = 1 - c`. + +The `NonUnitalSubalgebra` versions are **not** built. Nothing downstream needs the subalgebra +lattice, and `Submodule.finrank_lt` — which the dimension-drop lemmas below consume — wants a +`Submodule` anyway. + +## Naming: eigenvalues, not Jacobson indices + +The literature writes the eigenvalue-`1` and eigenvalue-`0` Peirce spaces as `J₂(c)` and +`J₀(c)`, indexing by twice the eigenvalue. The tree's existing projections are named by the +eigenvalue itself (`peirceOne`, `peirceHalf`, `peirceZero`), and consistency inside the tree +wins: `peirceOneSub hc` **is** `J₂(c)` and `peirceZeroSub hc` **is** `J₀(c)`. Reading +`peirceOneSub` as `J₁(c)` — the half-eigenspace — would be a mistake. The half-eigenspace gets +no subalgebra here, and cannot get one on the same terms: `EuclideanJordan/PeirceMul.lean`'s +`peirceHalf_mul_half_eq_zero` puts the product of two half-eigenvectors in `J₂(c) ⊕ J₀(c)`, so +the half-eigenspace is closed under the product only in the degenerate case where it squares to +zero. + +## Scope + +-/ + +@[expose] public section + +noncomputable section + +namespace EuclideanJordan + +open EuclideanJordanAlgebra (smul_mul jordan inner_assoc mul_one') + +variable {J : Type*} [NormedAddCommGroup J] [InnerProductSpace ℝ J] [EuclideanJordanAlgebra J] + +/-! ## The two carriers -/ + +/-- **`J₂(c)`**, the eigenvalue-`1` Peirce space `{x | c ∘ x = x}`, as a submodule. + +The idempotency hypothesis is carried but unused: the eigenspace of any element is a submodule. +It is present so that the *type* `↥(peirceOneSub hc)` records it, which is what lets the +`Mul`, `One` and `EuclideanJordanAlgebra` instances below be found by synthesis. -/ +def peirceOneSub {c : J} (_hc : c * c = c) : Submodule ℝ J where + carrier := {x : J | c * x = x} + add_mem' := fun {a b} ha hb => by + change c * (a + b) = a + b + rw [mul_add, ha, hb] + zero_mem' := mul_zero c + smul_mem' := fun r x hx => by + change c * (r • x) = r • x + rw [mul_smul_comm, hx] + +@[simp] theorem mem_peirceOneSub {c : J} (hc : c * c = c) {x : J} : + x ∈ peirceOneSub hc ↔ c * x = x := Iff.rfl + +/-- **`J₀(c)`**, the eigenvalue-`0` Peirce space `{x | c ∘ x = 0}`, as a submodule. -/ +def peirceZeroSub {c : J} (_hc : c * c = c) : Submodule ℝ J where + carrier := {x : J | c * x = 0} + add_mem' := fun {a b} ha hb => by + change c * (a + b) = 0 + rw [mul_add, ha, hb, add_zero] + zero_mem' := mul_zero c + smul_mem' := fun r x hx => by + change c * (r • x) = 0 + rw [mul_smul_comm, hx, smul_zero] + +@[simp] theorem mem_peirceZeroSub {c : J} (hc : c * c = c) {x : J} : + x ∈ peirceZeroSub hc ↔ c * x = 0 := Iff.rfl + +/-! ## `J₂(c)` is a Euclidean Jordan algebra with unit `c` -/ + +section Two + +variable {c : J} (hc : c * c = c) + +/-- Closure under the product is `EuclideanJordan/PeirceMul.lean`'s `eigen_one_mul_one`, applied +to the +membership proofs directly: membership unfolds to the eigenvalue equation. -/ +instance instMulPeirceOneSub : Mul ↥(peirceOneSub hc) := + ⟨fun x y => ⟨(x : J) * (y : J), eigen_one_mul_one hc x.2 y.2⟩⟩ + +/-- ★ **The unit of `J₂(c)` is `c`, not `1`.** -/ +instance instOnePeirceOneSub : One ↥(peirceOneSub hc) := ⟨⟨c, hc⟩⟩ + +@[simp] theorem coe_mul_peirceOneSub (x y : ↥(peirceOneSub hc)) : + ((x * y : ↥(peirceOneSub hc)) : J) = (x : J) * (y : J) := rfl + +@[simp] theorem coe_one_peirceOneSub : ((1 : ↥(peirceOneSub hc)) : J) = c := rfl + +/-- **`J₂(c)` is a Euclidean Jordan algebra.** Every field is the ambient identity at the +coercions; `one_mul` is the membership proof itself. -/ +instance instEJAPeirceOneSub : EuclideanJordanAlgebra ↥(peirceOneSub hc) := + { (inferInstance : Mul ↥(peirceOneSub hc)), (inferInstance : One ↥(peirceOneSub hc)) with + mul_comm := fun x y => Subtype.ext (_root_.mul_comm (x : J) (y : J)) + add_mul := fun x y z => Subtype.ext (_root_.add_mul (x : J) (y : J) (z : J)) + smul_mul := fun r x y => Subtype.ext (smul_mul r (x : J) (y : J)) + one_mul := fun x => Subtype.ext x.2 + jordan := fun x y => Subtype.ext (jordan (x : J) (y : J)) + inner_assoc := fun x y z => inner_assoc (x : J) (y : J) (z : J) } + +end Two + +/-! ## `J₀(c)` is a Euclidean Jordan algebra with unit `1 - c` -/ + +section Zero + +variable {c : J} (hc : c * c = c) + +theorem one_sub_mem_peirceZeroSub : (1 : J) - c ∈ peirceZeroSub hc := by + change c * (1 - c) = 0 + rw [mul_sub, mul_one', hc, sub_self] + +instance instMulPeirceZeroSub : Mul ↥(peirceZeroSub hc) := + ⟨fun x y => ⟨(x : J) * (y : J), eigen_zero_mul_zero hc x.2 y.2⟩⟩ + +/-- ★ **The unit of `J₀(c)` is `1 - c`.** -/ +instance instOnePeirceZeroSub : One ↥(peirceZeroSub hc) := + ⟨⟨(1 : J) - c, one_sub_mem_peirceZeroSub hc⟩⟩ + +@[simp] theorem coe_mul_peirceZeroSub (x y : ↥(peirceZeroSub hc)) : + ((x * y : ↥(peirceZeroSub hc)) : J) = (x : J) * (y : J) := rfl + +@[simp] theorem coe_one_peirceZeroSub : ((1 : ↥(peirceZeroSub hc)) : J) = 1 - c := rfl + +/-- **`J₀(c)` is a Euclidean Jordan algebra.** -/ +instance instEJAPeirceZeroSub : EuclideanJordanAlgebra ↥(peirceZeroSub hc) := + { (inferInstance : Mul ↥(peirceZeroSub hc)), (inferInstance : One ↥(peirceZeroSub hc)) with + mul_comm := fun x y => Subtype.ext (_root_.mul_comm (x : J) (y : J)) + add_mul := fun x y z => Subtype.ext (_root_.add_mul (x : J) (y : J) (z : J)) + smul_mul := fun r x y => Subtype.ext (smul_mul r (x : J) (y : J)) + one_mul := fun x => Subtype.ext (by + change ((1 : J) - c) * (x : J) = (x : J) + rw [sub_mul, EuclideanJordanAlgebra.one_mul, x.2, sub_zero]) + jordan := fun x y => Subtype.ext (jordan (x : J) (y : J)) + inner_assoc := fun x y z => inner_assoc (x : J) (y : J) (z : J) } + +end Zero + +/-! ## The dimension drop + +The two lemmas that make an induction on `finrank ℝ J` down the Peirce decomposition +terminate. Each is a one-element argument: if the subalgebra were everything it would contain +the ambient unit (resp. `c` itself), and its defining equation would then force `c = 1` +(resp. `c = 0`). -/ + +section Finrank + +variable [FiniteDimensional ℝ J] {c : J} (hc : c * c = c) + +theorem finrank_peirceOneSub_lt (hne : c ≠ 1) : + Module.finrank ℝ ↥(peirceOneSub hc) < Module.finrank ℝ J := by + refine Submodule.finrank_lt ?_ + intro htop + have h1 : (1 : J) ∈ peirceOneSub hc := by rw [htop]; exact Submodule.mem_top + have h2 : c * (1 : J) = 1 := h1 + exact hne (by rwa [mul_one'] at h2) + +theorem finrank_peirceZeroSub_lt (hne : c ≠ 0) : + Module.finrank ℝ ↥(peirceZeroSub hc) < Module.finrank ℝ J := by + refine Submodule.finrank_lt ?_ + intro htop + have h1 : c ∈ peirceZeroSub hc := by rw [htop]; exact Submodule.mem_top + have h2 : c * c = 0 := h1 + exact hne (by rwa [hc] at h2) + +end Finrank + +end EuclideanJordan diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/Power.lean b/LeanPool/EuclideanJordan/EuclideanJordan/Power.lean new file mode 100644 index 0000000000..83dc9344d7 --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/Power.lean @@ -0,0 +1,154 @@ +/- +Copyright (c) 2026 Bryan Ehrlich. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Bryan Ehrlich +-/ +module + +public import LeanPool.EuclideanJordan.EuclideanJordan.Frame + + + +/-! +# Powers of a single element, and how far power associativity gets + +Power associativity — Albert's theorem, that the subalgebra generated by one element of a +Jordan algebra is associative — is the gateway to the Jordan spectral theorem: the spectral +resolution of `x` lives inside `ℝ[x]`, and that argument needs `ℝ[x]` associative before it can +start. + +★ **`EuclideanJordan/PowerAssoc.lean` proves the theorem itself** (`jpow_mul_jpow`, +unconditional). This file is the *reduction* that made it reachable: it cuts power +associativity down to a single family of commutator statements and discharges the first three +members. It is kept because the reduction, not the induction, is where the content is. + +## The reduction + +Write `CommuteAt x m` for *`L_{x^{m+1}}` commutes with `L_x`*: + + `∀ w, x ∘ (x^{m+1} ∘ w) = x^{m+1} ∘ (x ∘ w)`. + +Then `jpow_mul_jpow_of_commuteAt` says: **`CommuteAt x m` alone gives +`x^{m+1} ∘ x^{n+1} = x^{m+n+2}` for every `n`.** So full power associativity is exactly +`∀ m, CommuteAt x m`, and nothing else is missing. + +★ The reduction itself does **not** use the Jordan identity — its `omit` line says so. Every +bit of Jordan content in power associativity is concentrated in `CommuteAt`. **This is the +part that held up**: it is what let `EuclideanJordan/PowerAssoc.lean` attack a single commutator + family +instead of the product law, and the proof there is exactly that attack. + +## What is discharged here + +| `m` | statement | source | +| --- | --- | --- | +| `0` | `L_x` commutes with `L_x` | trivial | +| `1` | `L_x` commutes with `L_{x²}` | **the Jordan identity itself** | +| `2` | `L_x` commutes with `L_{x³}` | one instance of `two_lin1_apply` | + +★ All three are **subsumed** by `commuteAt_all` in `EuclideanJordan/PowerAssoc.lean`. They are +kept because they are the elementary route, because `commuteAt_one` *is* the Jordan identity in +this vocabulary, and because `commuteAt_two` is what made the general pattern visible. + +★ **Do not read `sq_mul_jpow` as power associativity.** It is the `m = 1` row of the table +above and nothing more. +-/ + +@[expose] public section + +namespace EuclideanJordan + +section Pow + +variable {J : Type*} [NonUnitalNonAssocCommRing J] [IsCommJordan J] + +/-- `jpow x n = x^{n+1}`. Indexed from `1` rather than `0` so that no unit is assumed — +the Peirce layer of this development is unit-free and this stays so. -/ +def jpow (x : J) : ℕ → J + | 0 => x + | (n + 1) => x * jpow x n + +omit [IsCommJordan J] in +@[simp] theorem jpow_zero (x : J) : jpow x 0 = x := rfl + +omit [IsCommJordan J] in +@[simp] theorem jpow_succ (x : J) (n : ℕ) : jpow x (n + 1) = x * jpow x n := rfl + +omit [IsCommJordan J] in +theorem jpow_succ' (x : J) (n : ℕ) : jpow x (n + 1) = jpow x n * x := by + rw [jpow_succ, mul_comm] + +/-- `L_{x^{m+1}}` commutes with `L_x`. Power associativity is exactly `∀ m, CommuteAt x m` +(see `jpow_mul_jpow_of_commuteAt`). -/ +def CommuteAt (x : J) (m : ℕ) : Prop := ∀ w : J, x * (jpow x m * w) = jpow x m * (x * w) + +omit [IsCommJordan J] in +/-- **The reduction.** A single commutation hypothesis at `m` gives the whole `m`-th row of +the power table. + +★ Note the `omit`: this holds in *any* commutative non-associative ring. The Jordan identity +is not used, which localises all of Albert's difficulty in `CommuteAt`. -/ +theorem jpow_mul_jpow_of_commuteAt {x : J} {m : ℕ} (h : CommuteAt x m) (n : ℕ) : + jpow x m * jpow x n = jpow x (m + n + 1) := by + induction n with + | zero => rw [jpow_zero, ← jpow_succ', Nat.add_zero] + | succ n ih => + rw [jpow_succ, ← h (jpow x n), ih, + show m + (n + 1) + 1 = (m + n + 1) + 1 from by omega] + exact (jpow_succ x (m + n + 1)).symm + +omit [IsCommJordan J] in +/-- `m = 0`: `L_x` commutes with itself. -/ +theorem commuteAt_zero (x : J) : CommuteAt x 0 := fun _ => rfl + +/-- `m = 1`: `L_x` commutes with `L_{x²}`. **This is the Jordan identity**, in the operator +form Mathlib states it (`commute_lmul_lmul_sq`). -/ +theorem commuteAt_one (x : J) : CommuteAt x 1 := by + intro w + have h := IsCommJordan.lmul_comm_rmul_rmul x w + -- `(x * w) * (x * x) = x * (w * (x * x))` + calc x * (jpow x 1 * w) = x * ((x * x) * w) := rfl + _ = x * (w * (x * x)) := by rw [mul_comm (x * x) w] + _ = (x * w) * (x * x) := h.symm + _ = (x * x) * (x * w) := mul_comm _ _ + _ = jpow x 1 * (x * w) := rfl + +/-- **`x² ∘ x^{n+1} = x^{n+3}`.** The `m = 1` row. -/ +theorem sq_mul_jpow (x : J) (n : ℕ) : (x * x) * jpow x n = jpow x (n + 2) := by + have := jpow_mul_jpow_of_commuteAt (commuteAt_one x) n + rw [show (1 : ℕ) + n + 1 = n + 2 from by omega] at this + exact this + +end Pow + +section PowTwo + +variable {J : Type*} [NonUnitalNonAssocCommRing J] [IsCommJordan J] [Module ℝ J] + +/-- `m = 2`: **`L_x` commutes with `L_{x³}`.** One instance of the linearised Jordan +identity: at `a := x`, `b := x²` the first bracket of `two_lin1_apply` cancels against +itself, leaving exactly this. -/ +theorem commuteAt_two (x : J) : CommuteAt x 2 := by + intro w + have h := two_lin1_apply x (jpow x 1) w + simp only [jpow_succ, jpow_zero, sub_self, smul_zero, zero_add] at h + refine sub_eq_zero.mp (nsmul_eq_zero_iff' (n := 4) (by norm_num) ?_) + rw [← h] + abel + +/-- **`x³ ∘ x^{n+1} = x^{n+4}`.** The `m = 2` row. -/ +theorem cube_mul_jpow (x : J) (n : ℕ) : jpow x 2 * jpow x n = jpow x (n + 3) := by + have := jpow_mul_jpow_of_commuteAt (commuteAt_two x) n + rw [show (2 : ℕ) + n + 1 = n + 3 from by omega] at this + exact this + +omit [IsCommJordan J] [Module ℝ J] in +/-- **Power associativity, as the single statement that remains.** Everything else is +`jpow_mul_jpow_of_commuteAt`. -/ +theorem jpow_mul_jpow_of_forall_commuteAt {x : J} (h : ∀ m, CommuteAt x m) (m n : ℕ) : + jpow x m * jpow x n = jpow x (m + n + 1) := + jpow_mul_jpow_of_commuteAt (h m) n + +end PowTwo + +end EuclideanJordan diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/PowerAssoc.lean b/LeanPool/EuclideanJordan/EuclideanJordan/PowerAssoc.lean new file mode 100644 index 0000000000..5f4ce471f3 --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/PowerAssoc.lean @@ -0,0 +1,156 @@ +/- +Copyright (c) 2026 Bryan Ehrlich. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Bryan Ehrlich +-/ +module + +public import LeanPool.EuclideanJordan.EuclideanJordan.Power + + + +/-! +# Albert's theorem: power associativity + +**`jpow_mul_jpow`: `x^{m+1} ∘ x^{n+1} = x^{m+n+2}` in any real commutative Jordan algebra.** +Unconditional; closure is Lean's three core axioms. + +This is the gateway to the Jordan spectral theorem: the spectral resolution of `x` lives in +the subalgebra `ℝ[x]`, and that argument cannot begin until `ℝ[x]` is associative. We are aware +of no formalization of Albert's theorem in any proof assistant, though we have not searched +them systematically, and Mathlib has nothing between `IsCommJordan` and this. + +## The proof + +Write `cm x i j w` for the commutator `[L_{x^{i+1}}, L_{x^{j+1}}]` applied to `w`. The whole +theorem is `cm x i j w = 0`, since `EuclideanJordan/Power.lean`'s `jpow_mul_jpow_of_commuteAt` + already +turns commutation into the product law. + +The engine is `cm_cyclic`: for `i + j = N` (and given the product law at level `N`), the +fully linearised Jordan identity `four_lin2_apply` collapses to + + `cm x j (i+1) w + cm x 0 (N+1) w + cm x i (j+1) w = 0`. + +Now fix `N` and `w` and look at the "antidiagonal" `d k := cm x k (N+1-k) w`. The identity +says exactly `d j + d 0 + d i = 0` whenever `i + j = N`, and antisymmetry says +`d k = − d (N+1−k)`. Together these force + + `d (k+1) = d k + d 0`, hence `d k = (k+1) · d 0`, + +and the wrap-around `d (N+1) = − d 0` then gives `(N+3) · d 0 = 0`. In a real vector space +that means `d 0 = 0`, so the whole antidiagonal vanishes and the induction advances. + +★ **`(N+3)` is where the characteristic hypothesis bites *hardest*.** Albert's theorem needs +**every** positive integer invertible — it would fail in characteristic `p` for any `p ≤ N+3` +— whereas the Peirce layer needs only `2`. So `Module ℝ J` is load-bearing everywhere in this +development, and unboundedly so here. +## Relation to Mathlib + +The Mathlib-facing shape is `instance : PNatPowAssoc J` — the class +(`Mathlib/Algebra/Group/PNatPowAssoc.lean`, `ppow_add : x ^ (k + n) = x ^ k * x ^ n`) already +exists with two users. Supplying the `Pow J ℕ+` instance and transporting `jpow_mul_jpow` +across it is the upstream contribution. **Not done here** — that translation is unwritten, and +this file should not be read as providing it. +-/ + +@[expose] public section + +namespace EuclideanJordan + +section Albert + +variable {J : Type*} [NonUnitalNonAssocCommRing J] [IsCommJordan J] [Module ℝ J] + +/-- The commutator `[L_{x^{i+1}}, L_{x^{j+1}}]` applied to `w`. Albert's theorem is that this +vanishes identically. -/ +def cm (x : J) (i j : ℕ) (w : J) : J := + jpow x i * (jpow x j * w) - jpow x j * (jpow x i * w) + +omit [IsCommJordan J] [Module ℝ J] in +theorem cm_antisymm (x : J) (i j : ℕ) (w : J) : cm x i j w = - cm x j i w := by simp [cm] + +omit [IsCommJordan J] [Module ℝ J] in +theorem cm_self (x : J) (i : ℕ) (w : J) : cm x i i w = 0 := by simp [cm] + +omit [IsCommJordan J] [Module ℝ J] in +/-- `cm x 0 i ≡ 0` is precisely `CommuteAt x i`. -/ +theorem commuteAt_of_cm {x : J} {i : ℕ} (h : ∀ w, cm x 0 i w = 0) : CommuteAt x i := by + intro w + have hw := h w + rw [cm, jpow_zero] at hw + exact sub_eq_zero.mp hw + +/-- **The engine.** The fully linearised Jordan identity, instantiated at `(x, x^{i+1}, +x^{j+1})` and evaluated at `w`, with the middle product already reduced by the level-`N` +product law. This is the relation that ties the antidiagonal together. -/ +theorem cm_cyclic (x : J) (i j : ℕ) (hij : jpow x i * jpow x j = jpow x (i + j + 1)) (w : J) : + cm x j (i + 1) w + cm x 0 (i + j + 1) w + cm x i (j + 1) w = 0 := by + have h := four_lin2_apply (jpow x 0) (jpow x i) (jpow x j) w + simp only [jpow_zero] at h + rw [show x * jpow x i = jpow x (i + 1) from rfl] at h + rw [show x * jpow x j = jpow x (j + 1) from rfl] at h + rw [hij] at h + refine nsmul_eq_zero_iff' (n := 4) (by norm_num) ?_ + rw [← h] + simp only [cm, jpow_zero] + +/-- **Albert's theorem, commutator form.** All multiplication operators of powers of a single +element commute. The induction is on the total degree `N`; at each level the antidiagonal +argument of the module docstring forces the new commutators to vanish. -/ +theorem cm_eq_zero (x : J) (N : ℕ) : ∀ i j : ℕ, i + j ≤ N → ∀ w : J, cm x i j w = 0 := by + induction N with + | zero => + intro i j hij w + obtain ⟨rfl, rfl⟩ : i = 0 ∧ j = 0 := ⟨by omega, by omega⟩ + exact cm_self x 0 w + | succ N ih => + have hA : ∀ i j : ℕ, i + j ≤ N → jpow x i * jpow x j = jpow x (i + j + 1) := fun i j hij => + jpow_mul_jpow_of_commuteAt (commuteAt_of_cm (fun w => ih 0 i (by omega) w)) j + intro i j hij w + rcases Nat.lt_or_ge (i + j) (N + 1) with hlt | hge + · exact ih i j (by omega) w + · have hsum : i + j = N + 1 := by omega + set c : J := cm x 0 (N + 1) w with hc + have key : ∀ k : ℕ, k ≤ N + 1 → ∀ l : ℕ, k + l = N + 1 → cm x k l w = (k + 1) • c := by + intro k + induction k with + | zero => + intro _ l hl + obtain rfl : l = N + 1 := by omega + simp [hc] + | succ k ihk => + intro hk l hl + have hcyc := cm_cyclic x l k (hA l k (by omega)) w + rw [show l + k + 1 = N + 1 from by omega] at hcyc + rw [ihk (by omega) (l + 1) (by omega), cm_antisymm x l (k + 1) w, ← hc] at hcyc + rw [(add_neg_eq_zero.mp hcyc).symm, ← succ_nsmul] + have hlast : cm x (N + 1) 0 w = (N + 2) • c := key (N + 1) le_rfl 0 (by omega) + have hneg : cm x (N + 1) 0 w = - c := by rw [cm_antisymm x (N + 1) 0 w, ← hc] + have hzero : (N + 3) • c = 0 := by + have h2 : (N + 2) • c = -c := by rw [← hlast, hneg] + rw [show N + 3 = (N + 2) + 1 from rfl, succ_nsmul, h2] + exact neg_add_cancel c + rw [key i (by omega) j (by omega), nsmul_eq_zero_iff' (n := N + 3) (by omega) hzero, + smul_zero] + +/-- `L_{x^{m+1}}` commutes with `L_x`, for every `m` — the gap this arc's wall certificate +priced as open. -/ +theorem commuteAt_all (x : J) (m : ℕ) : CommuteAt x m := + commuteAt_of_cm (fun w => cm_eq_zero x m 0 m (by omega) w) + +/-- **POWER ASSOCIATIVITY (Albert's theorem).** `x^{m+1} ∘ x^{n+1} = x^{m+n+2}` in any real +commutative Jordan algebra. No formal reality, no finite dimension, no unit, no inner +product — only the Jordan identity and the real scalars. -/ +theorem jpow_mul_jpow (x : J) (m n : ℕ) : jpow x m * jpow x n = jpow x (m + n + 1) := + jpow_mul_jpow_of_commuteAt (commuteAt_all x m) n + +/-- The operator form: the multiplication operators of powers of one element mutually +commute. This, not the product law, is what a spectral argument consumes. -/ +theorem opCommute_jpow (x : J) (i j : ℕ) (w : J) : + jpow x i * (jpow x j * w) = jpow x j * (jpow x i * w) := + sub_eq_zero.mp (cm_eq_zero x (i + j) i j le_rfl w) + +end Albert + +end EuclideanJordan diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/Rank.lean b/LeanPool/EuclideanJordan/EuclideanJordan/Rank.lean new file mode 100644 index 0000000000..d1084fc882 --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/Rank.lean @@ -0,0 +1,196 @@ +/- +Copyright (c) 2026 Bryan Ehrlich. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Bryan Ehrlich +-/ +module + +public import LeanPool.EuclideanJordan.EuclideanJordan.PeirceSubalgebra + + + +/-! +# Primitive idempotents, Jordan frames, and rank + +`EuclideanJordan/Frame.lean` carries `IsOrthIdemFamily`: a family of pairwise-orthogonal + idempotents, with +completeness deliberately left out. This file adds the two conditions that turn such a family +into a *Jordan frame* — every member primitive, and the family complete — and defines the rank. + +## What the rank is here, and the chapter that is not needed + +★ **Frame conjugacy is not proved and is not needed.** "All Jordan frames have the same +cardinality" looks like a prerequisite — "rank `n ≥ 3`" seems meaningless before it — and its +classical proof (Faraut–Korányi Thm IV.2.5, conjugacy of frames under `Aut(J)`) is a chapter of +its own. The direction saves it. The rank is *defined* as the size of a Jordan frame — a complete +system of orthogonal primitive idempotents `F = {p₁, …, pₙ}` with `∑ᵢ pᵢ = e` (FK IV.2) — so a +hypothesis "rank `= n`" **implies** "there exists a Jordan frame of cardinality `n`". Taking +the latter as the Lean hypothesis makes the Lean theorem weaker or equal, which is the correct +direction for an import; and well-definedness falls out downstream, since once `J ≅ H_n(K)` the +dimension pins `n`. + +★ So this file's `rank` is deliberately **not** the load-bearing object, and downstream modules +should carry a `JordanFrame J n` as *data* rather than reason about the number `rank J`. What +is proved about `rank J` is exactly two inequalities: `JordanFrame.card_le_rank` (a frame's +cardinality is at most the rank) and `rank_le_finrank`. The reverse of the first — +`rank J = n` for a frame of cardinality `n` — is **not** proved here, and nothing in this file +is a step towards it: `rank J` is a supremum over *all* orthogonal families of nonzero +idempotents, primitive or not, and bounding such a family by `n` needs either the frame Peirce +decomposition or frame conjugacy, neither of which is available yet. Do not quote `rank J = n` +off this file. + +## The linear-independence argument is already in the tree + +Orthogonal nonzero idempotents are linearly independent, and the argument is +`EuclideanJordan/Order.lean`'s `inner_left_coeff` verbatim: idempotency and then associativity + of the inner +product give `⟪pₖ, pᵢ⟫ = ⟪pₖ ∘ pₖ, pᵢ⟫ = ⟪pₖ, pₖ ∘ pᵢ⟫ = 0` for `i ≠ k`, so pairing a vanishing +combination against `pₖ` reads off `gₖ ‖pₖ‖² = 0`. That lemma is stated in + `EuclideanJordan/Order.lean`'s bilinear-map vocabulary; +`EuclideanJordan/Class.lean`'s `jmulₗ` and `jmulₗ_inner_assoc` are what let it be applied at the + class +without restating it. + +## Scope + +-/ + +@[expose] public section + +noncomputable section + +namespace EuclideanJordan + +variable {J : Type*} [NormedAddCommGroup J] [InnerProductSpace ℝ J] [EuclideanJordanAlgebra J] + +/-! ## Primitivity -/ + +/-- A **primitive idempotent**: a nonzero idempotent that cannot be split, i.e. the only +idempotents of the Peirce subalgebra `J₂(c)` are `0` and `c` itself. + +The third clause is stated in the ambient algebra — `d` idempotent with `c ∘ d = d`, which is +membership in `peirceOneSub` — rather than over the subtype, so that it can be checked without +first producing the subalgebra. `isPrimitive_iff_of_idem` below is the two readings' +equivalence. -/ +def IsPrimitive (c : J) : Prop := + c * c = c ∧ c ≠ 0 ∧ ∀ d : J, d * d = d → c * d = d → d = 0 ∨ d = c + +theorem IsPrimitive.idem {c : J} (h : IsPrimitive c) : c * c = c := h.1 + +theorem IsPrimitive.ne_zero {c : J} (h : IsPrimitive c) : c ≠ 0 := h.2.1 + +theorem IsPrimitive.eq_zero_or_eq {c : J} (h : IsPrimitive c) {d : J} (hd : d * d = d) + (hcd : c * d = d) : d = 0 ∨ d = c := h.2.2 d hd hcd + +/-- Primitivity of `c`, read *inside* `J₂(c)`: `c` is primitive exactly when the only +idempotents of the Peirce subalgebra are its zero and its unit. The intended consumer is the +`dim V_ii = 1` step of the frame Peirce decomposition, which runs the spectral theorem inside +`J₂(pᵢ)`; that step is `EuclideanJordan/FramePeirceMul.lean`'s + `peirceOneSub_eq_span_of_isPrimitive`, and it +consumes this lemma in the `→` direction. -/ +theorem isPrimitive_iff_of_idem {c : J} (hc : c * c = c) (hc0 : c ≠ 0) : + IsPrimitive c ↔ ∀ d : ↥(peirceOneSub hc), d * d = d → d = 0 ∨ d = 1 := by + constructor + · rintro ⟨-, -, h⟩ d hd + rcases h (d : J) (congrArg Subtype.val hd) d.2 with h0 | h1 + · exact Or.inl (Subtype.ext h0) + · exact Or.inr (Subtype.ext h1) + · intro h + refine ⟨hc, hc0, fun d hd hcd => ?_⟩ + rcases h ⟨d, hcd⟩ (Subtype.ext hd) with h0 | h1 + · exact Or.inl (congrArg Subtype.val h0) + · exact Or.inr (congrArg Subtype.val h1) + +/-! ## Jordan frames -/ + +/-- A **Jordan frame**: a complete family of pairwise-orthogonal primitive idempotents. +Carried as data, indexed by `Fin n`, so that its cardinality is available without any +well-definedness theorem — see the module docstring. -/ +structure JordanFrame (J : Type*) [NormedAddCommGroup J] [InnerProductSpace ℝ J] + [EuclideanJordanAlgebra J] (n : ℕ) where + /-- The idempotents. -/ + p : Fin n → J + /-- They are idempotent and pairwise orthogonal. -/ + orthIdem : IsOrthIdemFamily p + /-- Each is primitive. -/ + primitive : ∀ i, IsPrimitive (p i) + /-- They sum to the unit. -/ + complete : ∑ i, p i = 1 + +namespace JordanFrame + +variable {n : ℕ} (F : JordanFrame J n) + +theorem p_ne_zero (i : Fin n) : F.p i ≠ 0 := (F.primitive i).ne_zero + +/-- **A Jordan frame cannot be extended.** Anything orthogonal to every member of a complete +family is annihilated by the unit, hence zero — so there is no nonzero idempotent to adjoin. +Idempotency of `q` is not used. -/ +theorem eq_zero_of_orth {q : J} (hq : ∀ i, F.p i * q = 0) : q = 0 := by + have h : (1 : J) * q = 0 := by + rw [← F.complete, Finset.sum_mul, Finset.sum_eq_zero (fun i _ => hq i)] + rwa [EuclideanJordanAlgebra.one_mul] at h + +include F in +/-- A frame of a nontrivial algebra is nonempty. -/ +theorem card_pos (h1 : (1 : J) ≠ 0) : 0 < n := by + rcases Nat.eq_zero_or_pos n with rfl | h + · exact absurd (by simpa using F.complete.symm) h1 + · exact h + +end JordanFrame + +/-! ## Linear independence and the dimension bound -/ + +/-- **Orthogonal nonzero idempotents are linearly independent.** `EuclideanJordan/Order.lean`'s +`inner_left_coeff`, applied through `EuclideanJordan/Class.lean`'s `jmulₗ`. -/ +theorem linearIndependent_of_orthIdem {n : ℕ} {p : Fin n → J} (hp : IsOrthIdemFamily p) + (hne : ∀ i, p i ≠ 0) : LinearIndependent ℝ p := by + rw [Fintype.linearIndependent_iff] + intro g hg k + have key := inner_left_coeff (m := jmulₗ J) (q := p) (lam := g) jmulₗ_inner_assoc + (fun i => hp.idem i) (fun i j hij => hp.orth i j hij) (x := 0) hg.symm k + rw [inner_zero_right] at key + have hpos : (0 : ℝ) < inner ℝ (p k) (p k) := real_inner_self_pos.mpr (hne k) + nlinarith [key, hpos] + +theorem card_le_finrank_of_orthIdem [FiniteDimensional ℝ J] {n : ℕ} {p : Fin n → J} + (hp : IsOrthIdemFamily p) (hne : ∀ i, p i ≠ 0) : n ≤ Module.finrank ℝ J := by + simpa using (linearIndependent_of_orthIdem hp hne).fintype_card_le_finrank + +/-! ## Rank -/ + +/-- The cardinalities realised by orthogonal families of **nonzero** idempotents. Primitivity +is not required — see the module docstring on what that costs. -/ +def orthIdemCards (J : Type*) [NormedAddCommGroup J] [InnerProductSpace ℝ J] + [EuclideanJordanAlgebra J] : Set ℕ := + {n | ∃ p : Fin n → J, IsOrthIdemFamily p ∧ ∀ i, p i ≠ 0} + +theorem zero_mem_orthIdemCards : 0 ∈ orthIdemCards J := + ⟨Fin.elim0, ⟨fun i => i.elim0, fun i => i.elim0⟩, fun i => i.elim0⟩ + +theorem bddAbove_orthIdemCards [FiniteDimensional ℝ J] : BddAbove (orthIdemCards J) := + ⟨Module.finrank ℝ J, fun _ ⟨_p, hp, hne⟩ => card_le_finrank_of_orthIdem hp hne⟩ + +/-- The **rank** of a Euclidean Jordan algebra: the greatest cardinality of a family of +pairwise-orthogonal nonzero idempotents. -/ +def rank (J : Type*) [NormedAddCommGroup J] [InnerProductSpace ℝ J] + [EuclideanJordanAlgebra J] : ℕ := sSup (orthIdemCards J) + +theorem le_rank [FiniteDimensional ℝ J] {n : ℕ} (hn : n ∈ orthIdemCards J) : n ≤ rank J := + le_csSup bddAbove_orthIdemCards hn + +theorem rank_le_finrank [FiniteDimensional ℝ J] : rank J ≤ Module.finrank ℝ J := + csSup_le ⟨0, zero_mem_orthIdemCards⟩ (fun _ ⟨_p, hp, hne⟩ => card_le_finrank_of_orthIdem hp hne) + +theorem JordanFrame.mem_orthIdemCards {n : ℕ} (F : JordanFrame J n) : n ∈ orthIdemCards J := + ⟨F.p, F.orthIdem, F.p_ne_zero⟩ + +theorem JordanFrame.card_le_rank [FiniteDimensional ℝ J] {n : ℕ} (F : JordanFrame J n) : + n ≤ rank J := le_rank F.mem_orthIdemCards + +theorem JordanFrame.card_le_finrank [FiniteDimensional ℝ J] {n : ℕ} (F : JordanFrame J n) : + n ≤ Module.finrank ℝ J := + card_le_finrank_of_orthIdem F.orthIdem F.p_ne_zero + +end EuclideanJordan diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/Spectral.lean b/LeanPool/EuclideanJordan/EuclideanJordan/Spectral.lean new file mode 100644 index 0000000000..db1f230649 --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/Spectral.lean @@ -0,0 +1,607 @@ +/- +Copyright (c) 2026 Bryan Ehrlich. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Bryan Ehrlich +-/ +module + +public import LeanPool.EuclideanJordan.EuclideanJordan.Subalgebra +public import LeanPool.EuclideanJordan.EuclideanJordan.FormallyReal +public import LeanPool.EuclideanJordan.EuclideanJordan.Witness +public import LeanPool.EuclideanJordan.EuclideanJordan.Bridge +public import Mathlib.LinearAlgebra.Lagrange +public import Mathlib.Analysis.Complex.Polynomial.Basic +public import Mathlib.RingTheory.PrincipalIdealDomain + + + +/-! +# The single-element spectral theorem for a formally real Jordan algebra + +Every element of a finite-dimensional formally real Jordan algebra is a real combination of +pairwise-orthogonal idempotents drawn from the subalgebra it generates +(`spectral_resolution`), and, when a unit is available, of a family summing to it +(`spectral_resolution_complete`). Both are instantiated on `HermitianMat d 𝕜` at the end of +the file, so the abstract statements have a live carrier. + +## The route, and why it carries no ring structure on `jspan x` + +The classical treatment works inside the unital algebra `ℝ[x] = span{1, x, x², …}` and needs +that algebra to be a *ring* — which forces either a unit on `J` or a `Unitization` of a +subtype, and with it a `NonUnitalCommRing ↥(jspan x)` instance and its scalar towers. **None +of that appears here.** The polynomial bookkeeping is carried by a linear map + + `jeval x : ℝ[X] →ₗ[ℝ] J`, `jeval x p = ∑ₙ p.coeff n • x^{n+1}`, + +which is "`x·p(x)`" — the shape available with no unit at all, since every monomial carries +at least one factor of `x`. Its whole content is one identity, + + `jeval_mul : jeval x p * jeval x q = jeval x (X * p * q)`, + +which is `EuclideanJordan/PowerAssoc.lean`'s `jpow_mul_jpow` transported along bilinearity. + Everything +downstream is ideal theory in `ℝ[X]`, where Mathlib already has what is needed, and the +values live in the ambient `J`, where `EuclideanJordan/FormallyReal.lean` already applies. + +The steps, each a named declaration below: + +| step | statement | declaration | +| --- | --- | --- | +| 1 | the annihilator `{p | x·p(x) = 0}` is an ideal of `ℝ[X]`, nonzero in finite dimension | + `jann`, `exists_annihilator_generator` | +| 2 | its generator `m` is radical, hence squarefree | `isRadical_of_annihilator` | +| 3 | `m` has a nonzero constant term | `coeff_zero_ne_zero_of_annihilator` | +| 4 | `m` has no non-real complex root | `annihilator_aeval_ne_zero_of_im_ne_zero` | +| 5 | so some product of distinct linear factors, none of them `X`, annihilates `x` | + `exists_split_annihilator` | +| 6 | Lagrange interpolation at those roots together with `0` gives the idempotents | + `exists_orthIdem_finset` | + +★ **Step 2 is where formal reality enters the polynomial algebra**, through the identity +`jpow (jeval x f) n = jeval x (Xⁿ f^{n+1})` (`jpow_jeval`): a Jordan power of a value of +`jeval` is again a value of `jeval`, so `EuclideanJordan/FormallyReal.lean`'s no-nilpotents + theorem — +which is stated about *ambient* elements of `J` — applies with no repackaging. This is why +no `IsReduced` instance on a ring structure over `jspan x` is needed anywhere. + +★ **Step 4 uses no idempotent.** The textbook argument builds `e ≡ e² (mod m)` from a Bézout +splitting and applies formal reality to `((x−Re z)e(x))² + (Im z · e(x))²`. Here the +idempotence is never used: with `q ∣ m` the real quadratic through a non-real root `z`, and +`e := βu` from `α q + β u = 1`, the *only* facts consumed are `q·e ≡ 0 (mod m)` and, for the +contradiction, `e ≢ 0 (mod m)`. What formal reality kills is the value `jeval x g` directly, +where `X * g` is `e` corrected to have zero constant term — a correction available exactly +because of step 3. Multiplicities never enter, and neither does `Mathlib`'s `radical` API. + +## Scope — what these theorems are NOT + +* They are stated at the **typeclass** generality of the rest of the EJA layer + (`NonUnitalNonAssocCommRing` + `IsCommJordan` + `Module ℝ` + `IsScalarTower` + finite + dimension + `IsFormallyReal`). A consumer carrying its Jordan product as a *bundled bilinear + map* `J →ₗ[ℝ] J →ₗ[ℝ] J` reaches them only through `EuclideanJordan/Bridge.lean`'s + `ringOfBilinear`, for the reason recorded in `EuclideanJordan/Frame.lean`. The interface + section at the end of this file does exactly that crossing, for this theorem and no other. +* `spectral_resolution` is unit-free and its idempotents therefore sum to the *support* of + `x`, not to a unit. `spectral_resolution_complete` adds completeness by appending `e − s` + with coefficient `0`; that member is not in `jspan x`, so the `jspan` clause is the price + of completeness and is dropped there rather than weakened. +* Nothing here is a functional calculus: the resolution is produced, but no continuous or + polynomial calculus is built on it. +-/ + +@[expose] public section + +namespace EuclideanJordan + +open Polynomial + +section Eval + +variable {J : Type*} [NonUnitalNonAssocCommRing J] [IsCommJordan J] [Module ℝ J] + [IsScalarTower ℝ J J] + +/-- `jeval x p = ∑ n, p.coeff n • x^{n+1}`, morally `x * p(x)`. -/ +noncomputable def jeval (x : J) : Polynomial ℝ →ₗ[ℝ] J where + toFun p := p.sum fun n a => a • jpow x n + map_add' p q := + Polynomial.sum_add_index p q _ (fun _ => zero_smul _ _) (fun _ b₁ b₂ => add_smul b₁ b₂ _) + map_smul' r p := by + simp only [RingHom.id_apply] + rw [Polynomial.sum_smul_index p r (fun n a => a • jpow x n) (fun _ => zero_smul ℝ _), + Polynomial.smul_sum] + exact Finset.sum_congr rfl fun n _ => (mul_smul r _ _) + +omit [IsCommJordan J] [IsScalarTower ℝ J J] in +@[simp] theorem jeval_monomial (x : J) (n : ℕ) (a : ℝ) : + jeval x (monomial n a) = a • jpow x n := + Polynomial.sum_monomial_index a (fun n a => a • jpow x n) (zero_smul _ _) + +omit [IsCommJordan J] [IsScalarTower ℝ J J] in +@[simp] theorem jeval_one (x : J) : jeval x 1 = x := by + rw [← C_1, ← monomial_zero_left, jeval_monomial, one_smul, jpow_zero] + +omit [IsCommJordan J] in +/-- Multiplying the argument by `X` multiplies the value by `x`. -/ +theorem jeval_X_mul (x : J) (p : Polynomial ℝ) : jeval x (X * p) = x * jeval x p := by + induction p using Polynomial.induction_on' with + | add p q hp hq => rw [mul_add, map_add, map_add, hp, hq, mul_add] + | monomial n a => + rw [X_mul_monomial, jeval_monomial, jeval_monomial, jpow_succ, + mul_smul_comm'] + +/-- **The multiplication rule.** `jeval` is "`x·p(x)`", so the product of two values is +`x·p(x)·x·q(x) = x·(X p q)(x)`. -/ +theorem jeval_mul (x : J) (p q : Polynomial ℝ) : + jeval x p * jeval x q = jeval x (X * p * q) := by + induction p using Polynomial.induction_on' with + | add p₁ p₂ hp₁ hp₂ => rw [map_add, add_mul, hp₁, hp₂, mul_add, add_mul, map_add] + | monomial m a => + induction q using Polynomial.induction_on' with + | add q₁ q₂ hq₁ hq₂ => rw [map_add, mul_add, hq₁, hq₂, mul_add, map_add] + | monomial n b => + rw [jeval_monomial, jeval_monomial, smul_mul_assoc, mul_smul_comm', jpow_mul_jpow, + smul_smul, X_mul_monomial, monomial_mul_monomial, jeval_monomial] + congr 2 + omega + +end Eval + +section Annihilator + +variable {J : Type*} [NonUnitalNonAssocCommRing J] [IsCommJordan J] [Module ℝ J] + [IsScalarTower ℝ J J] + +omit [IsCommJordan J] in +theorem jeval_X_pow_mul {x : J} {p : Polynomial ℝ} (hp : jeval x p = 0) (k : ℕ) : + jeval x (X ^ k * p) = 0 := by + induction k with + | zero => simpa using hp + | succ k ih => rw [pow_succ', mul_assoc, jeval_X_mul, ih, mul_zero] + +omit [IsCommJordan J] in +theorem jeval_mul_eq_zero {x : J} {p : Polynomial ℝ} (hp : jeval x p = 0) (q : Polynomial ℝ) : + jeval x (q * p) = 0 := by + induction q using Polynomial.induction_on' with + | add q₁ q₂ h₁ h₂ => rw [add_mul, map_add, h₁, h₂, add_zero] + | monomial n a => + have hmul : (monomial n a : Polynomial ℝ) * p = a • (X ^ n * p) := by + rw [← C_mul_X_pow_eq_monomial, smul_eq_C_mul, mul_assoc] + rw [hmul, map_smul, jeval_X_pow_mul hp n, smul_zero] + +/-- The annihilator of `x`: the polynomials `p` with `x·p(x) = 0`. -/ +noncomputable def jann (x : J) : Ideal (Polynomial ℝ) where + carrier := {p | jeval x p = 0} + add_mem' := fun {p q} hp hq => by + simp only [Set.mem_ofPred_eq] at * + rw [map_add, hp, hq, add_zero] + zero_mem' := by simp only [Set.mem_ofPred_eq, map_zero] + smul_mem' := fun q p hp => by + simp only [Set.mem_ofPred_eq, smul_eq_mul] at * + exact jeval_mul_eq_zero hp q + +omit [IsCommJordan J] in +theorem mem_jann {x : J} {p : Polynomial ℝ} : p ∈ jann x ↔ jeval x p = 0 := Iff.rfl + +omit [IsCommJordan J] in +/-- **The annihilator has a nonzero generator.** Finite dimension makes the powers of `x` +dependent, so the annihilator is a nonzero ideal of the principal ideal ring `ℝ[X]`. -/ +theorem exists_annihilator_generator [Module.Finite ℝ J] (x : J) : + ∃ m : Polynomial ℝ, m ≠ 0 ∧ ∀ f, jeval x f = 0 ↔ m ∣ f := by + obtain ⟨n, c, ⟨i₀, hi₀⟩, hsum⟩ := exists_jpow_relation x + have hp0 : jeval x (∑ i : Fin n, monomial (i : ℕ) (c i)) = 0 := by + rw [map_sum] + simpa using hsum + have hpne : (∑ i : Fin n, monomial (i : ℕ) (c i)) ≠ 0 := by + intro h + have hco : (∑ i : Fin n, monomial (i : ℕ) (c i)).coeff (i₀ : ℕ) = c i₀ := by + rw [Polynomial.finsetSum_coeff, Finset.sum_eq_single i₀] + · simp + · intro b _ hb + simp [Polynomial.coeff_monomial, Fin.val_ne_of_ne hb] + · simp + rw [h, Polynomial.coeff_zero] at hco + exact hi₀ hco.symm + refine ⟨Submodule.IsPrincipal.generator (jann x), ?_, fun f => ?_⟩ + · intro hgen + have := (Submodule.IsPrincipal.mem_iff_generator_dvd (jann x)).mp (mem_jann.mpr hp0) + rw [hgen, zero_dvd_iff] at this + exact hpne this + · exact (mem_jann (x := x) (p := f)).symm.trans + (Submodule.IsPrincipal.mem_iff_generator_dvd (jann x)) + +/-- `x·f(x)` raised to the `n`-th Jordan power is again a value of `jeval`. -/ +theorem jpow_jeval (x : J) (f : Polynomial ℝ) (n : ℕ) : + jpow (jeval x f) n = jeval x (X ^ n * f ^ (n + 1)) := by + induction n with + | zero => simp + | succ n ih => + rw [jpow_succ, ih, jeval_mul] + congr 1 + ring + +variable [IsFormallyReal J] + +/-- **The generator is radical.** A power of `f` annihilating `x` forces `f` to, because the +Jordan powers of `x·f(x)` are exactly the values of `jeval` on `Xⁿ f^{n+1}` and a formally +real Jordan algebra has no nilpotents. -/ +theorem isRadical_of_annihilator {x : J} {m : Polynomial ℝ} + (hm : ∀ f, jeval x f = 0 ↔ m ∣ f) : IsRadical m := by + intro n f hdvd + match n with + | 0 => exact (isUnit_of_dvd_one (by simpa using hdvd)).dvd + | (k + 1) => + refine (hm f).mp ?_ + have h1 : jeval x (X ^ k * f ^ (k + 1)) = 0 := + jeval_X_pow_mul ((hm _).mpr hdvd) k + rw [← jpow_jeval] at h1 + exact eq_zero_of_jpow_eq_zero k h1 + +/-- **The generator has a nonzero constant term.** If `X ∣ m` then the cofactor's value +squares to zero, so it too annihilates `x`, and it has smaller degree than the generator. -/ +theorem coeff_zero_ne_zero_of_annihilator {x : J} {m : Polynomial ℝ} + (hm : ∀ f, jeval x f = 0 ↔ m ∣ f) (hm0 : m ≠ 0) : m.coeff 0 ≠ 0 := by + intro hc + obtain ⟨m₁, hm₁⟩ := Polynomial.X_dvd_iff.mpr hc + have hm₁0 : m₁ ≠ 0 := by + rintro rfl + rw [mul_zero] at hm₁ + exact hm0 hm₁ + have hsq : jeval x m₁ * jeval x m₁ = 0 := by + rw [jeval_mul, ← hm₁] + exact (hm _).mpr ⟨m₁, rfl⟩ + have hdvd : m ∣ m₁ := (hm m₁).mp (eq_zero_of_mul_self_eq_zero hsq) + have hle := Polynomial.natDegree_le_of_dvd hdvd hm₁0 + rw [hm₁, Polynomial.natDegree_mul Polynomial.X_ne_zero hm₁0, Polynomial.natDegree_X] at hle + omega + +end Annihilator + +section Kill + +variable {J : Type*} [NonUnitalNonAssocCommRing J] [IsCommJordan J] [Module ℝ J] + [IsScalarTower ℝ J J] [IsFormallyReal J] + +open Complex in +/-- **The kill.** The generator of the annihilator has no non-real complex root. -/ +theorem annihilator_aeval_ne_zero_of_im_ne_zero {x : J} {m : Polynomial ℝ} + (hm : ∀ f, jeval x f = 0 ↔ m ∣ f) (hsq : Squarefree m) (h0 : m.coeff 0 ≠ 0) + {z : ℂ} (hz : z.im ≠ 0) : aeval z m ≠ 0 := by + intro hroot + obtain ⟨u, hu⟩ := m.quadratic_dvd_of_aeval_eq_zero_im_ne_zero hroot hz + set q : Polynomial ℝ := X ^ 2 - C (2 * z.re) * X + C (‖z‖ ^ 2) with hqdef + have hqnu : ¬ IsUnit q := by + intro h + have h2 : q.natDegree = 2 := by rw [hqdef]; compute_degree! + rw [Polynomial.natDegree_eq_zero_of_isUnit h] at h2 + exact absurd h2 (by norm_num) + have hcop : IsCoprime q u := by + rw [hu] at hsq + exact (IsRelPrime.of_squarefree_mul hsq).isCoprime + obtain ⟨α, β, hbez⟩ := hcop + set c : ℝ := (β * u).coeff 0 / m.coeff 0 with hcdef + set g : Polynomial ℝ := (β * u - C c * m).divX with hgdef + have hXg : X * g = β * u - C c * m := by + have hc0 : (β * u - C c * m).coeff 0 = 0 := by + simp [hcdef, div_mul_cancel₀ _ h0] + rw [hgdef] + conv_rhs => rw [← Polynomial.X_mul_divX_add (β * u - C c * m)] + rw [hc0, map_zero, add_zero] + -- `q` annihilates the class of `X * g` + have h1 : m ∣ q * (X * g) := by + rw [hXg] + exact ⟨β - C c * q, by rw [hu]; ring⟩ + -- `q` is a sum of two squares + have hqsq : q = (X - C z.re) ^ 2 + C (z.im ^ 2) := by + have hnorm : ‖z‖ ^ 2 = z.re ^ 2 + z.im ^ 2 := by + rw [← Complex.normSq_eq_norm_sq, Complex.normSq_apply]; ring + rw [hqdef, hnorm] + simp only [Polynomial.C_add, Polynomial.C_mul, Polynomial.C_pow, map_ofNat] + ring + -- the sum of squares vanishes + set a : J := jeval x ((X - C z.re) * g) with hadef + set b : J := z.im • jeval x g with hbdef + have hsum : a * a + b * b = 0 := by + have hb2 : b * b = jeval x (C (z.im ^ 2) * (X * g * g)) := by + rw [hbdef, smul_mul_assoc, mul_smul_comm', smul_smul, jeval_mul, ← Polynomial.smul_eq_C_mul, + map_smul] + ring_nf + rw [hadef, jeval_mul, hb2, ← map_add] + refine (hm _).mpr (dvd_trans (h1.mul_right g) ?_) + exact ⟨1, by rw [hqsq]; ring⟩ + have hb0 : b = 0 := by + have := IsFormallyReal.eq_zero_of_sum_mul_self (Finset.univ : Finset (Fin 2)) ![a, b] + (by simpa [Fin.sum_univ_two] using hsum) + simpa using this 1 (Finset.mem_univ 1) + have hg0 : jeval x g = 0 := by + rcases smul_eq_zero.mp (hbdef ▸ hb0) with h | h + · exact absurd h hz + · exact h + -- and that forces `q` to be a unit + have hmg : m ∣ β * u := by + have h4 : m ∣ β * u - C c * m := by + rw [← hXg]; exact ((hm g).mp hg0).mul_left X + simpa using dvd_add h4 (⟨C c, by ring⟩ : m ∣ C c * m) + have hqm : q ∣ m := ⟨u, hu⟩ + exact hqnu (isUnit_of_dvd_one (hbez ▸ dvd_add (dvd_mul_left q α) (hqm.trans hmg))) + +end Kill + + +section Split + +variable {J : Type*} [NonUnitalNonAssocCommRing J] [IsCommJordan J] [Module ℝ J] + [IsScalarTower ℝ J J] [IsFormallyReal J] [Module.Finite ℝ J] + +/-- **The split annihilator.** Some product of *distinct* linear factors, none of them `X`, +annihilates `x`. -/ +theorem exists_split_annihilator (x : J) : + ∃ S : Finset ℝ, (0 : ℝ) ∉ S ∧ jeval x (∏ a ∈ S, (X - C a)) = 0 := by + obtain ⟨m, hm0, hm⟩ := exists_annihilator_generator x + have hsq : Squarefree m := (isRadical_of_annihilator hm).squarefree hm0 + have hc0 : m.coeff 0 ≠ 0 := coeff_zero_ne_zero_of_annihilator hm hm0 + refine ⟨m.roots.toFinset, ?_, ?_⟩ + · rw [Polynomial.coeff_zero_eq_eval_zero] at hc0 + simpa [Polynomial.mem_roots', Polynomial.IsRoot.def] using fun _ => hc0 + · have hPm : (∏ a ∈ m.roots.toFinset, (X - C a)) ∣ m := + Finset.prod_dvd_of_coprime + ((Polynomial.pairwise_coprime_X_sub_C (Function.injective_id (α := ℝ))).set_pairwise _) + (fun a ha => Polynomial.dvd_iff_isRoot.mpr + (Polynomial.isRoot_of_mem_roots (Multiset.mem_toFinset.mp ha))) + obtain ⟨W, hW⟩ := hPm + have hWnr : ∀ r : ℝ, W.eval r ≠ 0 := by + intro r hr + have hmr : m.IsRoot r := by rw [hW]; simp [Polynomial.IsRoot.def, hr] + have hrS : r ∈ m.roots.toFinset := + Multiset.mem_toFinset.mpr (Polynomial.mem_roots'.mpr ⟨hm0, hmr⟩) + have hdd : (X - C r) * (X - C r) ∣ m := by + rw [hW] + exact mul_dvd_mul (Finset.dvd_prod_of_mem _ hrS) (Polynomial.dvd_iff_isRoot.mpr hr) + have hu := hsq _ hdd + rw [Polynomial.isUnit_iff_degree_eq_zero, Polynomial.degree_X_sub_C] at hu + exact one_ne_zero hu + have hWu : IsUnit W := by + by_contra hWnu + have hW0 : W ≠ 0 := by + rintro rfl + rw [mul_zero] at hW + exact hm0 hW + have hdeg : W.degree ≠ 0 := fun h => hWnu (Polynomial.isUnit_iff_degree_eq_zero.mpr h) + obtain ⟨z, hz⟩ := IsAlgClosed.exists_aeval_eq_zero ℂ W hdeg + have him : z.im ≠ 0 := by + intro h + refine hWnr z.re ?_ + have hzr : z = algebraMap ℝ ℂ z.re := by + apply Complex.ext <;> simp [h] + rw [hzr, Polynomial.aeval_algebraMap_apply_eq_algebraMap_eval] at hz + simpa using hz + exact annihilator_aeval_ne_zero_of_im_ne_zero hm hsq hc0 him (by rw [hW, map_mul, hz, + mul_zero]) + obtain ⟨v, hv⟩ := hWu.exists_right_inv + refine (hm _).mpr ⟨v, ?_⟩ + calc (∏ a ∈ m.roots.toFinset, (X - C a)) + = (∏ a ∈ m.roots.toFinset, (X - C a)) * (W * v) := by rw [hv, mul_one] + _ = m * v := by rw [← mul_assoc, ← hW] + +omit [IsCommJordan J] [IsScalarTower ℝ J J] [IsFormallyReal J] [Module.Finite ℝ J] in +/-- Every value of `jeval x` lies in the subalgebra generated by `x`. -/ +theorem jeval_mem_jspan (x : J) (p : Polynomial ℝ) : jeval x p ∈ jspan x := by + change p.sum (fun n a => a • jpow x n) ∈ jspan x + rw [Polynomial.sum_def] + exact Submodule.sum_mem _ fun n _ => Submodule.smul_mem _ _ (jpow_mem_jspan x n) + +omit [IsCommJordan J] [IsFormallyReal J] [Module.Finite ℝ J] in +/-- **The vehicle.** A polynomial vanishing at every node of `S ∪ {0}` is divisible by the +nodal polynomial, and the annihilator absorbs the quotient. -/ +theorem jeval_eq_zero_of_eval_eq_zero {x : J} {S : Finset ℝ} (h0S : (0 : ℝ) ∉ S) + (hA : jeval x (∏ a ∈ S, (X - C a)) = 0) {p f : Polynomial ℝ} (hpf : X * p = f) + (hf : ∀ ν ∈ insert (0 : ℝ) S, f.eval ν = 0) : jeval x p = 0 := by + have hnodal : (∏ ν ∈ insert (0 : ℝ) S, (X - C ν)) = X * ∏ a ∈ S, (X - C a) := by + rw [Finset.prod_insert h0S, map_zero, sub_zero] + have hdvd : (∏ ν ∈ insert (0 : ℝ) S, (X - C ν)) ∣ f := + Finset.prod_dvd_of_coprime + ((Polynomial.pairwise_coprime_X_sub_C (Function.injective_id (α := ℝ))).set_pairwise _) + (fun ν hν => Polynomial.dvd_iff_isRoot.mpr (hf ν hν)) + rw [hnodal] at hdvd + obtain ⟨h, hh⟩ := hdvd + have hp : p = (∏ a ∈ S, (X - C a)) * h := by + apply mul_left_cancel₀ (Polynomial.X_ne_zero (R := ℝ)) + rw [hpf, hh, mul_assoc] + rw [hp, mul_comm] + exact jeval_mul_eq_zero hA h + +/-- **The spectral resolution, indexed by the eigenvalues.** -/ +theorem exists_orthIdem_finset (x : J) : + ∃ (S : Finset ℝ) (c : ℝ → J), (∀ a ∈ S, c a * c a = c a) ∧ + (∀ a ∈ S, ∀ a' ∈ S, a ≠ a' → c a * c a' = 0) ∧ + (∀ a, c a ∈ jspan x) ∧ x = ∑ a ∈ S, a • c a := by + classical + obtain ⟨S, h0S, hA⟩ := exists_split_annihilator x + set b : ℝ → Polynomial ℝ := fun a => Lagrange.basis (insert (0 : ℝ) S) id a with hbdef + set g : ℝ → Polynomial ℝ := fun a => (b a).divX with hgdef + have h0T : (0 : ℝ) ∈ insert (0 : ℝ) S := Finset.mem_insert_self 0 S + have hne : ∀ a ∈ S, a ≠ 0 := fun a ha h => h0S (h ▸ ha) + have hinj : Set.InjOn (id : ℝ → ℝ) (↑(insert (0 : ℝ) S) : Set ℝ) := fun _ _ _ _ h => h + have heval : ∀ a ∈ S, ∀ ν ∈ insert (0 : ℝ) S, (b a).eval ν = if ν = a then 1 else 0 := by + intro a ha ν hν + by_cases h : ν = a + · subst h + simpa [hbdef] using Lagrange.eval_basis_self hinj (Finset.mem_insert_of_mem ha) + · simpa [hbdef, h] using Lagrange.eval_basis_of_ne (v := (id : ℝ → ℝ)) (Ne.symm h) hν + have hXg : ∀ a ∈ S, b a = X * g a := by + intro a ha + have h0 : (b a).coeff 0 = 0 := by + rw [Polynomial.coeff_zero_eq_eval_zero, heval a ha 0 h0T, ite_eq_right (Ne.symm (hne a ha))] + conv_lhs => rw [← Polynomial.X_mul_divX_add (b a)] + rw [h0, map_zero, add_zero, hgdef] + refine ⟨S, fun a => jeval x (g a), ?_, ?_, fun a => jeval_mem_jspan x (g a), ?_⟩ + · intro a ha + have key : jeval x (X * g a * g a - g a) = 0 := by + refine jeval_eq_zero_of_eval_eq_zero h0S hA (f := b a ^ 2 - b a) ?_ ?_ + · rw [hXg a ha]; ring + · intro ν hν + simp only [Polynomial.eval_sub, Polynomial.eval_pow, heval a ha ν hν] + by_cases h : ν = a <;> simp [h] + rw [map_sub, sub_eq_zero, ← jeval_mul] at key + change jeval x (g a) * jeval x (g a) = jeval x (g a) + exact key + · intro a ha a' ha' haa' + have key : jeval x (X * g a * g a') = 0 := by + refine jeval_eq_zero_of_eval_eq_zero h0S hA (f := b a * b a') ?_ ?_ + · rw [hXg a ha, hXg a' ha']; ring + · intro ν hν + simp only [Polynomial.eval_mul, heval a ha ν hν, heval a' ha' ν hν] + by_cases h : ν = a + · rw [ite_eq_left h, ite_eq_right (by rw [h]; exact haa'), mul_zero] + · rw [ite_eq_right h, zero_mul] + rw [← jeval_mul] at key + change jeval x (g a) * jeval x (g a') = 0 + exact key + · have key : jeval x (1 - ∑ a ∈ S, a • g a) = 0 := by + refine jeval_eq_zero_of_eval_eq_zero h0S hA (f := X - ∑ a ∈ S, a • b a) ?_ ?_ + · rw [mul_sub, mul_one, Finset.mul_sum] + congr 1 + exact Finset.sum_congr rfl fun a ha => by rw [mul_smul_comm, ← hXg a ha] + · intro ν hν + simp only [Polynomial.eval_sub, Polynomial.eval_X, Polynomial.eval_finsetSum, + Polynomial.eval_smul, smul_eq_mul] + rcases Finset.mem_insert.mp hν with rfl | hνS + · rw [Finset.sum_eq_zero fun a ha => by + rw [heval a ha 0 hν, ite_eq_right (Ne.symm (hne a ha)), mul_zero], sub_zero] + · rw [Finset.sum_eq_single ν + (fun a ha hane => by rw [heval a ha ν hν, ite_eq_right (Ne.symm hane), mul_zero]) + (fun h => absurd hνS h), + heval ν hνS ν hν, ite_eq_left rfl, mul_one, sub_self] + rw [map_sub, map_sum, jeval_one, sub_eq_zero] at key + simpa using key + +/-- **The single-element spectral theorem.** -/ +theorem spectral_resolution (x : J) : + ∃ (n : ℕ) (c : Fin n → J) (lam : Fin n → ℝ), + IsOrthIdemFamily c ∧ (∀ i, c i ∈ jspan x) ∧ x = ∑ i, lam i • c i := by + classical + obtain ⟨S, c, hidem, horth, hmem, hx⟩ := exists_orthIdem_finset x + refine ⟨S.card, fun i => c (S.equivFin.symm i), fun i => ((S.equivFin.symm i : ℝ)), + ⟨fun i => hidem _ (S.equivFin.symm i).2, fun i j hij => ?_⟩, fun i => hmem _, ?_⟩ + · refine horth _ (S.equivFin.symm i).2 _ (S.equivFin.symm j).2 fun h => hij ?_ + exact S.equivFin.symm.injective (Subtype.ext h) + · rw [hx, ← Finset.sum_coe_sort S (fun a => a • c a)] + exact (Equiv.sum_comp S.equivFin.symm (fun a : {y // y ∈ S} => (a : ℝ) • c a)).symm + +/-- **The spectral theorem with completeness**, the unit carried as an ordinary hypothesis. -/ +theorem spectral_resolution_complete (e : J) (he : ∀ y : J, e * y = y) (x : J) : + ∃ (n : ℕ) (c : Fin n → J) (lam : Fin n → ℝ), + IsOrthIdemFamily c ∧ (∑ i, c i) = e ∧ x = ∑ i, lam i • c i := by + obtain ⟨n, c, lam, hfam, _, hx⟩ := spectral_resolution x + have hss : (∑ i, c i) * (∑ i, c i) = ∑ i, c i := hfam.sum_idem Finset.univ + have hsc : ∀ k, (∑ i, c i) * c k = c k := by + intro k + rw [Finset.sum_mul, Finset.sum_eq_single k (fun j _ hjk => hfam.orth j k hjk) + (fun h => absurd (Finset.mem_univ k) h)] + exact hfam.idem k + have hes : (∑ i, c i) * e = ∑ i, c i := by rw [mul_comm, he] + have hlast : (e - ∑ i, c i) * (e - ∑ i, c i) = e - ∑ i, c i := by + rw [sub_mul, mul_sub, mul_sub, he, he, hss, hes, sub_self, sub_zero] + have horthlast : ∀ k, (e - ∑ i, c i) * c k = 0 := by + intro k + rw [sub_mul, he, hsc, sub_self] + refine ⟨n + 1, Fin.snoc c (e - ∑ i, c i), Fin.snoc lam 0, ⟨?_, ?_⟩, ?_, ?_⟩ + · intro i + induction i using Fin.lastCases with + | last => simpa using hlast + | cast i => simpa using hfam.idem i + · intro i j hij + induction i using Fin.lastCases with + | last => + induction j using Fin.lastCases with + | last => exact absurd rfl hij + | cast j => simpa using horthlast j + | cast i => + induction j using Fin.lastCases with + | last => simpa [mul_comm] using horthlast i + | cast j => + simpa using hfam.orth i j fun h => hij (by rw [h]) + · rw [Fin.sum_univ_castSucc] + simp + · rw [Fin.sum_univ_castSucc] + simpa using hx + +end Split + + +section Concrete + +open HermMul + +variable {d : Type*} [Fintype d] [DecidableEq d] {𝕜 : Type*} [RCLike 𝕜] + +/-- **The spectral theorem live on `H_d(𝕜)`.** -/ +theorem hermitian_spectral_resolution (A : HermitianMat d 𝕜) : + ∃ (n : ℕ) (c : Fin n → HermitianMat d 𝕜) (lam : Fin n → ℝ), + IsOrthIdemFamily c ∧ (∀ i, c i ∈ jspan A) ∧ A = ∑ i, lam i • c i := + spectral_resolution A + +/-- **The spectral theorem with completeness, live on `H_d(𝕜)`**: the unit is `1`. -/ +theorem hermitian_spectral_resolution_complete (A : HermitianMat d 𝕜) : + ∃ (n : ℕ) (c : Fin n → HermitianMat d 𝕜) (lam : Fin n → ℝ), + IsOrthIdemFamily c ∧ (∑ i, c i) = 1 ∧ A = ∑ i, lam i • c i := + spectral_resolution_complete 1 (fun y => by rw [mul_eq_symmMul, HermitianMat.symmMul_comm, + HermitianMat.symmMul_one]) A + +end Concrete + + +section Interface + +variable {J : Type*} [NormedAddCommGroup J] [InnerProductSpace ℝ J] + +omit [InnerProductSpace ℝ J] in +/-- Formal reality over an arbitrary `Finset`, from the `Fin k` form. The two differ only by +reindexing. -/ +theorem isFormallyReal_of_fin [Module ℝ J] (m : J →ₗ[ℝ] J →ₗ[ℝ] J) (hcomm : ∀ x y : J, m x y = m + y x) + (hfr : ∀ (k : ℕ) (f : Fin k → J), (∑ i, m (f i) (f i)) = 0 → ∀ i, f i = 0) : + letI : NonUnitalNonAssocCommRing J := ringOfBilinear m hcomm + IsFormallyReal J := by + let : NonUnitalNonAssocCommRing J := ringOfBilinear m hcomm + refine ⟨fun {ι} s f hsum i hi => ?_⟩ + classical + have key : (∑ k : Fin s.card, m (f (s.equivFin.symm k)) (f (s.equivFin.symm k))) = 0 := by + rw [show (∑ k : Fin s.card, m (f (s.equivFin.symm k)) (f (s.equivFin.symm k))) + = ∑ a : {y // y ∈ s}, m (f a) (f a) from + Equiv.sum_comp s.equivFin.symm (fun a : {y // y ∈ s} => m (f a) (f a)), + Finset.sum_coe_sort s (fun a => m (f a) (f a))] + exact hsum + simpa using hfr s.card (fun k => f (s.equivFin.symm k)) key (s.equivFin ⟨i, hi⟩) + +/-- **The spectral theorem in bilinear-map vocabulary**: the Jordan product as a bundled +bilinear map, the Jordan identity and formal reality as hypotheses in that vocabulary, and the +conclusion stated without mentioning any ring instance. + +★ This is the crossing `EuclideanJordan/Bridge.lean` was built for, and it works here for the + reason that file +gives: the *statement* is expressible with `m` alone, so no ring instance has to exist before it +elaborates. Only the proof needs one, and `ringOfBilinear` supplies it on the ambient additive +group. + +★ Finite-dimensionality is not decoration. Without it the statement is false: `ℝ[X]` with +polynomial multiplication satisfies every hypothesis below and has no nonconstant spectral +resolution, its only idempotents being `0` and `1`. -/ +theorem spectral_resolution_bilinear [FiniteDimensional ℝ J] (m : J →ₗ[ℝ] J →ₗ[ℝ] J) + (hcomm : ∀ x y : J, m x y = m y x) + (hjordan : ∀ a b : J, m (m a b) (m a a) = m a (m b (m a a))) + (hfr : ∀ (k : ℕ) (f : Fin k → J), (∑ i, m (f i) (f i)) = 0 → ∀ i, f i = 0) + (e : J) (he : ∀ y : J, m e y = y) (x : J) : + ∃ (n : ℕ) (q : Fin n → J) (lam : Fin n → ℝ), + (∀ i, m (q i) (q i) = q i) ∧ + (∀ i j, i ≠ j → m (q i) (q j) = 0) ∧ + (∑ i, q i) = e ∧ + x = ∑ i, lam i • q i := by + let : NonUnitalNonAssocCommRing J := ringOfBilinear m hcomm + let : IsCommJordan J := ⟨hjordan⟩ + let : IsScalarTower ℝ J J := ⟨fun r x y => smul_bilinear m r x y⟩ + let : IsFormallyReal J := isFormallyReal_of_fin m hcomm hfr + obtain ⟨n, q, lam, hfam, hsum, hx⟩ := spectral_resolution_complete e he x + exact ⟨n, q, lam, hfam.idem, hfam.orth, hsum, hx⟩ + +end Interface + +end EuclideanJordan diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/Subalgebra.lean b/LeanPool/EuclideanJordan/EuclideanJordan/Subalgebra.lean new file mode 100644 index 0000000000..c5ace796eb --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/Subalgebra.lean @@ -0,0 +1,160 @@ +/- +Copyright (c) 2026 Bryan Ehrlich. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Bryan Ehrlich +-/ +module + +public import LeanPool.EuclideanJordan.EuclideanJordan.PowerAssoc +public import Mathlib.LinearAlgebra.Dimension.Finite + + + +/-! +# The subalgebra generated by one element is associative + +This is Albert's theorem in the form it is usually *stated* — `EuclideanJordan/PowerAssoc.lean` + proves the +form it is usually *proved* in, the power law `x^{m+1} ∘ x^{n+1} = x^{m+n+2}`. The two are not +the same statement: the power law is about products of powers, and this is about products of +arbitrary `ℝ`-combinations of powers. + +`jspan x` is the `ℝ`-span of `{x, x², x³, …}`. The results: + +* `mul_mem_jspan` — it is closed under the Jordan product, so it is a subalgebra; +* `jspan_assoc` — **the product is associative on it**. + +Both come from `jpow_mul_jpow` by span induction and nothing else. `jspan_assoc` needs three +nested inductions because associativity is trilinear, which is the only reason this file is +longer than its content. + +## Why it is worth stating separately + +The textbook route to the Jordan spectral theorem runs through `ℝ[x]`: one shows it is a +finite-dimensional associative commutative real algebra, that formal reality makes it reduced +(`EuclideanJordan/FormallyReal.lean`), and hence that it is `ℝ^k` — which produces the orthogonal +idempotents that `EuclideanJordan/Frame.lean` then consumes. **`jspan_assoc` is the first of + those four +steps.** The remaining three — finite-dimensionality of `jspan x`, reducedness in the form the +classification wants, and the classification itself — are not built. + +★★★ **The spectral theorem is proved in `EuclideanJordan/Spectral.lean`, and the route above is +not the one it took.** The paragraph is left as written because the mis-shaping is the record. +`jspan x` is never made a ring, never reduced, never classified; the proof evaluates polynomials +into the *ambient* `J` by a linear map and does its ideal theory in `ℝ[X]`. What this file +actually supplies to that proof is `exists_jpow_relation` (the nonzero annihilator) and +`mul_mem_jspan` / `jspan_assoc` — the latter two only to place the resulting idempotents in +`jspan x`, not to build any algebra structure on it. + +★ `jspan x` is a `Submodule`, not a `NonUnitalSubalgebra`: bundling it would need the ambient +`NonUnitalNonAssocCommRing` to carry an algebra structure over `ℝ`, which this unit-free +development deliberately does not assume. The two theorems below say exactly what a bundled +version would, without the bundling. +-/ + +@[expose] public section + +namespace EuclideanJordan + +section Subalgebra + +variable {J : Type*} [NonUnitalNonAssocCommRing J] [IsCommJordan J] [Module ℝ J] + [IsScalarTower ℝ J J] + +/-- The `ℝ`-span of the powers of `x`: the subalgebra generated by `x`. -/ +def jspan (x : J) : Submodule ℝ J := Submodule.span ℝ (Set.range (jpow x)) + +omit [IsCommJordan J] [IsScalarTower ℝ J J] in +theorem jpow_mem_jspan (x : J) (n : ℕ) : jpow x n ∈ jspan x := + Submodule.subset_span ⟨n, rfl⟩ + +omit [IsCommJordan J] [IsScalarTower ℝ J J] in +theorem self_mem_jspan (x : J) : x ∈ jspan x := by + simpa using jpow_mem_jspan x 0 + +/-- `jspan x` is closed under the Jordan product. -/ +theorem mul_mem_jspan {x a b : J} (ha : a ∈ jspan x) (hb : b ∈ jspan x) : a * b ∈ jspan x := by + have key : ∀ n : ℕ, ∀ c ∈ jspan x, jpow x n * c ∈ jspan x := by + intro n c hc + induction hc using Submodule.span_induction with + | mem y hy => + obtain ⟨m, rfl⟩ := hy + rw [jpow_mul_jpow] + exact jpow_mem_jspan x _ + | zero => rw [mul_zero]; exact Submodule.zero_mem _ + | add u v _ _ hu hv => rw [mul_add]; exact Submodule.add_mem _ hu hv + | smul r u _ hu => rw [mul_smul_comm']; exact Submodule.smul_mem _ r hu + induction ha using Submodule.span_induction with + | mem y hy => obtain ⟨n, rfl⟩ := hy; exact key n b hb + | zero => rw [zero_mul]; exact Submodule.zero_mem _ + | add u v _ _ hu hv => rw [add_mul]; exact Submodule.add_mem _ hu hv + | smul r u _ hu => rw [smul_mul_assoc]; exact Submodule.smul_mem _ r hu + +/-- **Albert's theorem, classical form: the subalgebra generated by one element is +associative.** -/ +theorem jspan_assoc {x a b c : J} (ha : a ∈ jspan x) (hb : b ∈ jspan x) (hc : c ∈ jspan x) : + (a * b) * c = a * (b * c) := by + have h3 : ∀ i j : ℕ, ∀ z ∈ jspan x, (jpow x i * jpow x j) * z = jpow x i * (jpow x j * z) := by + intro i j z hz + induction hz using Submodule.span_induction with + | mem y hy => + obtain ⟨k, rfl⟩ := hy + rw [jpow_mul_jpow, jpow_mul_jpow, jpow_mul_jpow, jpow_mul_jpow] + congr 1 + omega + | zero => rw [mul_zero, mul_zero, mul_zero] + | add u v _ _ hu hv => rw [mul_add, mul_add, mul_add, hu, hv] + | smul r u _ hu => rw [mul_smul_comm', mul_smul_comm', mul_smul_comm', hu] + have h2 : ∀ i : ℕ, ∀ y ∈ jspan x, ∀ z ∈ jspan x, + (jpow x i * y) * z = jpow x i * (y * z) := by + intro i y hy z hz + induction hy using Submodule.span_induction with + | mem w hw => obtain ⟨j, rfl⟩ := hw; exact h3 i j z hz + | zero => rw [mul_zero, zero_mul, mul_zero] + | add u v _ _ hu hv => rw [mul_add, add_mul, hu, hv, ← mul_add, ← add_mul] + | smul r u _ hu => simp only [mul_smul_comm', smul_mul_assoc]; rw [hu] + induction ha using Submodule.span_induction with + | mem w hw => obtain ⟨i, rfl⟩ := hw; exact h2 i b hb c hc + | zero => rw [zero_mul, zero_mul, zero_mul] + | add u v _ _ hu hv => rw [add_mul, add_mul, add_mul, hu, hv] + | smul r u _ hu => rw [smul_mul_assoc, smul_mul_assoc, smul_mul_assoc, hu] + +/-! ### The finite-dimensional entry point + +★ **Both results below are linear algebra, not Jordan theory**, and their `omit` lines prove +it: neither uses `IsCommJordan`. They are recorded here because they are step 1 of the route +to the spectral theorem and because it is worth being explicit that step 1 is free — all the +difficulty is downstream, in turning an annihilating relation into idempotents. -/ + +section Finite + +variable [Module.Finite ℝ J] + +omit [IsCommJordan J] [IsScalarTower ℝ J J] in +/-- **A nontrivial annihilating relation among the powers.** In finite dimension the powers of +`x` cannot be independent, so some nonzero real combination of `x, x², …, x^{N+1}` vanishes — +the existence half of a minimal polynomial *with zero constant term*, which is the right shape +for a unit-free setting. + +★ No Jordan input: this is "an over-long family in a finite-dimensional space is dependent". -/ +theorem exists_jpow_relation (x : J) : + ∃ (n : ℕ) (c : Fin n → ℝ), (∃ i, c i ≠ 0) ∧ ∑ i, c i • jpow x i = 0 := by + have hnot : ¬ LinearIndependent ℝ (fun i : Fin (Module.finrank ℝ J + 1) => jpow x i) := by + intro h + have hle := h.fintype_card_le_finrank + simp only [Fintype.card_fin] at hle + omega + obtain ⟨c, hsum, i, hi⟩ := Fintype.not_linearIndependent_iff.mp hnot + exact ⟨_, c, ⟨i, hi⟩, hsum⟩ + +omit [IsCommJordan J] [IsScalarTower ℝ J J] in +/-- `jspan x` is finite-dimensional. Immediate — it is a submodule of a finite-dimensional +space — and recorded only so that the spectral-theorem route can cite it. -/ +instance jspan_finite (x : J) : Module.Finite ℝ (jspan x) := + Module.Finite.of_injective (jspan x).subtype (jspan x).injective_subtype + +end Finite + +end Subalgebra + +end EuclideanJordan diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/TraceForm.lean b/LeanPool/EuclideanJordan/EuclideanJordan/TraceForm.lean new file mode 100644 index 0000000000..ab9fc55b08 --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/TraceForm.lean @@ -0,0 +1,340 @@ +/- +Copyright (c) 2026 Bryan Ehrlich. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Bryan Ehrlich +-/ +module + +public import LeanPool.EuclideanJordan.EuclideanJordan.Spectral +public import Mathlib.LinearAlgebra.Trace +public import Mathlib.LinearAlgebra.Projection + + + +/-! +# The Jordan trace form + +`τ(x, y) := tr(L_{x ∘ y})`, where `L_c` is the Jordan multiplication operator + `EuclideanJordan/Peirce.lean` +carries as `mulL`. On a finite-dimensional formally real Jordan algebra this form is symmetric, +**associative** (`τ(x ∘ y, z) = τ(y, x ∘ z)`) and **positive definite** — which is to say, it makes +such an algebra Euclidean without any inner product having been supplied. + +## Why this file exists + +`EuclideanJordan/Order.lean`'s Euclidean section — `inner_mul_self_nonneg_of_idem`, + `inner_left_coeff`, +`nonneg_coeff_of_isSoS`, `isArchimedean_ofBilinear`, `isSoS_iff_exists_sq` — takes the associativity +of the **ambient** inner product as a hypothesis, `hassoc : ∀ x y z, ⟪m x y, z⟫ = ⟪y, m x z⟫`. The +concrete carrier `H_n(𝕜)` discharges it by hand, through `hermitian_jordan_assoc` +(`EuclideanJordan/HermitianCarrier.lean`). ★ An *abstract* consumer generally cannot. A +structure that carries a `NormedAddCommGroup`/`InnerProductSpace` pair on `J` and a Jordan +product as an unrelated bilinear field has **no** compatibility between the two, and adding the +Jordan identity, formal reality and a cone-of-squares reading of positivity does not supply one: +associativity of the inner product is an independent assumption. So a proof that has to run +inside those hypotheses has no associative form to pair against, and every one of the six results +above is inapplicable as stated. + +This file builds the missing form *from the algebra*. Nothing here mentions a norm or an inner +product; the ambient structure is `EuclideanJordan/Peirce.lean`'s (a commutative Jordan ring + that is an +`ℝ`-module), plus finite-dimensionality and formal reality where the spectral theorem is used. +A consumer working in bilinear-map vocabulary installs `EuclideanJordan/Bridge.lean`'s +`ringOfBilinear` and gets `traceForm` on the nose. + +## The associativity argument + +`tr(L_·)` is associative because the **quadrilinear** form of the Jordan identity, + +`L_{(a∘c)∘y} + L_a L_y L_c + L_c L_y L_a = L_a L_{c∘y} + L_c L_{a∘y} + L_y L_{a∘c}`, + +has a right-hand side that is symmetric under permuting `(a, c, y)` — the three pairs +`{(a, c∘y), (c, a∘y), (y, a∘c)}` are permuted among themselves and `tr(fg) = tr(gf)` — while +`tr(L_a L_y L_c) + tr(L_c L_y L_a)` is symmetric by cyclicity of the trace. Hence +`tr(L_{(a∘c)∘y})` is symmetric in `(a, c, y)`, and that *is* associativity. + +★ The quadrilinear identity itself is cheap and this was not expected. It is the difference of two +instances of Mathlib's own linearized Jordan identity +(`two_nsmul_lie_lmul_lmul_add_add_eq_zero`) with the last two arguments swapped; `jordan_linearized` +and `jordan_quadrilinear` below are the whole of it. + +## Positive definiteness + +`τ(x, x) = tr(L_{x∘x})`, and the spectral resolution `x = ∑ᵢ λᵢ qᵢ` into orthogonal idempotents +gives `x ∘ x = ∑ᵢ λᵢ² qᵢ`, so `τ(x, x) = ∑ᵢ λᵢ² tr(L_{qᵢ})`. For an idempotent `c` the split +`L_c = P₁(c) + ½ P_{1/2}(c)` writes `tr(L_c)` as a nonnegative combination of traces of *idempotent +endomorphisms*, which are ranks of their ranges; and `P₁(c) c = c`, so a nonzero `c` forces +`tr(L_c) ≥ 1`. That inequality is the whole of definiteness: a vanishing `τ(x, x)` kills every +coefficient whose idempotent is nonzero. + +★ **What this file does NOT claim.** `τ` is not shown to be *the* Jordan trace form in the sense of +Faraut–Korányi (the trace of the quadratic representation, or the sum of the eigenvalues of `x`); +for a unit `e` one has `L_e = id` and so `tr(L_e) = finrank ℝ J` rather than the rank of `J` — **a +remark, not a lemma; it is not proved below and nothing uses it.** Nothing downstream needs the +normalisation, and +`EuclideanJordan/Class.lean`'s `EuclideanJordanAlgebra` deliberately takes an *arbitrary* + associative positive +definite form rather than a normalised one, so `traceForm` is admissible there as it stands. +-/ + +@[expose] public section + +noncomputable section + +namespace EuclideanJordan + +open Finset LinearMap + +/-! ## The linearized and quadrilinear Jordan identities -/ + +section Linearised + +variable {J : Type*} [NonUnitalNonAssocCommRing J] [IsCommJordan J] [Module ℝ J] + +/-- **The multilinearized Jordan identity, elementwise.** + +Mathlib states it as `2 • ([L_{ab}, L_c] + [L_{bc}, L_a] + [L_{ca}, L_b]) = 0` in +`AddMonoid.End J`; applying that at `v`, unfolding the commutators and cancelling the `2` gives the +form below. The `have h3 : … := h2` step works because the `AddMonoid.End` ring operations are +definitionally pointwise, which saves fighting for application `simp` lemmas that do not exist +under the names one would guess. -/ +theorem jordan_linearized (a b c v : J) : + (a * b) * (c * v) + (b * c) * (a * v) + (c * a) * (b * v) + = c * ((a * b) * v) + a * ((b * c) * v) + b * ((c * a) * v) := by + have h := congrArg (fun f : AddMonoid.End J => f v) + (two_nsmul_lie_lmul_lmul_add_add_eq_zero (A := J) a b c) + simp only [Ring.lie_def] at h + have h2 := nsmul_eq_zero_iff' (J := J) (n := 2) (by norm_num) h + have h3 : (a * ((b * c) * v) - (b * c) * (a * v)) + + (b * ((c * a) * v) - (c * a) * (b * v)) + + (c * ((a * b) * v) - (a * b) * (c * v)) = 0 := h2 + linear_combination (norm := abel) -h3 + +/-- **The quadrilinear Jordan identity**, the operator identity of this file read elementwise. + +It is `jordan_linearized a c y v - jordan_linearized a c v y`: the *same* identity with its last two +arguments swapped. The `rw`s only put the two instances into a common shape by commuting +products. -/ +theorem jordan_quadrilinear (a c y v : J) : + ((a * c) * y) * v + a * (y * (c * v)) + c * (y * (a * v)) + = a * ((c * y) * v) + c * ((a * y) * v) + y * ((a * c) * v) := by + have h1 := jordan_linearized a c y v + have h2 := jordan_linearized a c v y + rw [show ((a * c) * y) * v = v * ((a * c) * y) from mul_comm _ _, + show a * (y * (c * v)) = a * ((c * v) * y) by rw [mul_comm y (c * v)], + show c * (y * (a * v)) = c * ((v * a) * y) by rw [mul_comm y (a * v), mul_comm a v], + show c * ((a * y) * v) = c * ((y * a) * v) by rw [mul_comm a y]] + rw [show (a * c) * (y * v) = (a * c) * (v * y) by rw [mul_comm y v]] at h1 + rw [show (c * y) * (a * v) = (v * a) * (c * y) by rw [mul_comm a v, mul_comm]] at h1 + rw [show (y * a) * (c * v) = (c * v) * (a * y) by rw [mul_comm y a, mul_comm]] at h1 + linear_combination (norm := abel) h1 - h2 + +end Linearised + +/-! ## `tr ∘ L` and its associativity -/ + +section Trace + +variable {J : Type*} [NonUnitalNonAssocCommRing J] [IsCommJordan J] [Module ℝ J] + [IsScalarTower ℝ J J] + +/-- `EuclideanJordan/Peirce.lean`'s `mulL`, bundled as a linear map in the multiplier — which is +what makes +`jtr` linear. -/ +def mulLₗ : J →ₗ[ℝ] J →ₗ[ℝ] J where + toFun := mulL + map_add' a b := by ext y; simp only [mulL_apply, LinearMap.add_apply, add_mul] + map_smul' r a := by + ext y + simp only [mulL_apply, LinearMap.smul_apply, RingHom.id_apply, smul_mul_assoc] + +omit [IsCommJordan J] in +@[simp] theorem mulLₗ_apply (a : J) : mulLₗ a = mulL a := rfl + +/-- **The operator form of the quadrilinear identity**, in `Module.End ℝ J`. + +`L_{(a∘c)∘y} + L_a L_y L_c + L_c L_y L_a = L_a L_{c∘y} + L_c L_{a∘y} + L_y L_{a∘c}`. -/ +theorem mulL_quad_op (a c y : J) : + mulL ((a * c) * y) + mulL a * mulL y * mulL c + mulL c * mulL y * mulL a + = mulL a * mulL (c * y) + mulL c * mulL (a * y) + mulL y * mulL (a * c) := by + ext v + simp only [LinearMap.add_apply, Module.End.mul_apply, mulL_apply] + exact jordan_quadrilinear a c y v + +/-- **The Jordan trace functional** `x ↦ tr(L_x)`, as an `ℝ`-linear form. + +It is not normalised — see the module docstring. ★ The carrier here has no `1` +(`NonUnitalNonAssocCommRing`), so the normalisation cannot even be *stated* at this generality; +where a unit `e` is available it is `L_e = id`, so `jtr e` would be `finrank ℝ J` rather than the +rank. That is a remark, not a lemma: nothing below proves it and nothing needs it. -/ +def jtr : J →ₗ[ℝ] ℝ := (LinearMap.trace ℝ J).comp mulLₗ + +omit [IsCommJordan J] in +@[simp] theorem jtr_apply (x : J) : jtr x = LinearMap.trace ℝ J (mulL x) := rfl + +/-- **`tr(L_·)` is associative.** Trace the operator identity twice, at `(a, c, y)` and at +`(c, y, a)`; the two right-hand sides agree term by term after commuting products, and the two +triple-trace sums agree by cyclicity, so the two remaining terms agree. + +★ Finite-dimensionality is not needed: `LinearMap.trace` is total (it is `0` on a module with no +finite basis) and `trace_mul_cycle` is unconditional. It enters two theorems below, where the +trace of an idempotent endomorphism has to be its rank. -/ +theorem jtr_assoc (a c y : J) : jtr ((a * c) * y) = jtr (a * (c * y)) := by + have h1 := congrArg (LinearMap.trace ℝ J) (mulL_quad_op a c y) + have h2 := congrArg (LinearMap.trace ℝ J) (mulL_quad_op c y a) + simp only [map_add] at h1 h2 + rw [show a * (c * y) = (c * y) * a from mul_comm _ _] + rw [show y * a = a * y from mul_comm _ _, show c * a = a * c from mul_comm _ _] at h2 + have e1 : trace ℝ J (mulL c * mulL a * mulL y) = trace ℝ J (mulL a * mulL y * mulL c) := + (trace_mul_cycle ℝ (mulL a) (mulL y) (mulL c)).symm + have e2 : trace ℝ J (mulL y * mulL a * mulL c) = trace ℝ J (mulL c * mulL y * mulL a) := + trace_mul_cycle ℝ (mulL y) (mulL a) (mulL c) + simp only [jtr_apply] at * + linarith [h1, h2, e1, e2] + +variable [Module.Finite ℝ J] + +/-- **The trace of `L_c` is nonnegative for an idempotent `c`.** + +`L_c = P₁(c) + ½ P_{1/2}(c)`, both Peirce projections are idempotent *endomorphisms*, and the trace +of an idempotent endomorphism is the rank of its range. -/ +theorem jtr_nonneg_of_idem {c : J} (hc : c * c = c) : 0 ≤ jtr c := by + have hid1 : IsIdempotentElem (peirceOne c) := by + ext y; exact peirceOne_of_eigen (mul_peirceOne hc y) + have hidh : IsIdempotentElem (peirceHalf c) := by + ext y; exact peirceHalf_of_eigen_half (mul_peirceHalf hc y) + have hsplit : mulL c = peirceOne c + (2 : ℝ)⁻¹ • peirceHalf c := by + ext y + simp only [mulL_apply, LinearMap.add_apply, LinearMap.smul_apply, peirceOne_apply, + peirceHalf_apply] + module + rw [jtr_apply, hsplit, map_add, LinearMap.map_smul, hid1.isProj_range.trace, + hidh.isProj_range.trace] + have h1 : (0 : ℝ) ≤ (Module.finrank ℝ (LinearMap.range (peirceOne c)) : ℝ) := Nat.cast_nonneg _ + have h2 : (0 : ℝ) ≤ (Module.finrank ℝ (LinearMap.range (peirceHalf c)) : ℝ) := Nat.cast_nonneg _ + simp only [smul_eq_mul] + linarith + +/-- **The trace of `L_c` is at least one for a nonzero idempotent `c`** — the whole of positive +definiteness. `P₁(c) c = c`, so `c` lies in the range of `P₁(c)`; a nonzero `c` makes that range +nonzero, hence of rank at least one. -/ +theorem one_le_jtr_of_idem {c : J} (hc : c * c = c) (hc0 : c ≠ 0) : 1 ≤ jtr c := by + have hid1 : IsIdempotentElem (peirceOne c) := by + ext y; exact peirceOne_of_eigen (mul_peirceOne hc y) + have hidh : IsIdempotentElem (peirceHalf c) := by + ext y; exact peirceHalf_of_eigen_half (mul_peirceHalf hc y) + have hsplit : mulL c = peirceOne c + (2 : ℝ)⁻¹ • peirceHalf c := by + ext y + simp only [mulL_apply, LinearMap.add_apply, LinearMap.smul_apply, peirceOne_apply, + peirceHalf_apply] + module + have hmem : c ∈ LinearMap.range (peirceOne c) := ⟨c, peirceOne_of_eigen (by rw [hc])⟩ + have hne : LinearMap.range (peirceOne c) ≠ ⊥ := fun h => hc0 (by simpa [h] using hmem) + have hpos : 0 < Module.finrank ℝ (LinearMap.range (peirceOne c)) := + Module.finrank_pos_iff.mpr (by rw [Submodule.nontrivial_iff_ne_bot]; exact hne) + rw [jtr_apply, hsplit, map_add, LinearMap.map_smul, hid1.isProj_range.trace, + hidh.isProj_range.trace] + have h1 : (1 : ℝ) ≤ (Module.finrank ℝ (LinearMap.range (peirceOne c)) : ℝ) := by + exact_mod_cast hpos + have h2 : (0 : ℝ) ≤ (Module.finrank ℝ (LinearMap.range (peirceHalf c)) : ℝ) := Nat.cast_nonneg _ + simp only [smul_eq_mul] + linarith + +end Trace + +/-! ## The form itself -/ + +section Form + +variable {J : Type*} [NonUnitalNonAssocCommRing J] [IsCommJordan J] [Module ℝ J] + [IsScalarTower ℝ J J] + +/-- **The Jordan trace form** `τ(x, y) = tr(L_{x ∘ y})`. -/ +def traceForm : J →ₗ[ℝ] J →ₗ[ℝ] ℝ := + LinearMap.mk₂ ℝ (fun x y => jtr (x * y)) + (fun x x' y => by rw [add_mul, map_add]) + (fun r x y => by rw [smul_mul_assoc, map_smul, smul_eq_mul]) + (fun x y y' => by rw [mul_add, map_add]) + (fun r x y => by rw [mul_smul_comm' r x y, map_smul, smul_eq_mul]) + +omit [IsCommJordan J] in +@[simp] theorem traceForm_apply (x y : J) : traceForm x y = jtr (x * y) := rfl + +omit [IsCommJordan J] in +theorem traceForm_comm (x y : J) : traceForm x y = traceForm y x := by + simp only [traceForm_apply, mul_comm] + +/-- **The trace form is associative**: `τ(x ∘ y, z) = τ(y, x ∘ z)`. This is the hypothesis +`hassoc` that `EuclideanJordan/Order.lean`'s Euclidean section and + `EuclideanJordan/Class.lean`'s class both take, now a +theorem about a form built from the algebra alone. -/ +theorem traceForm_assoc (x y z : J) : traceForm (x * y) z = traceForm y (x * z) := by + simp only [traceForm_apply] + rw [show x * y = y * x from mul_comm _ _] + exact jtr_assoc y x z + +end Form + +/-! ## Positive definiteness -/ + +section PosDef + +variable {J : Type*} [NonUnitalNonAssocCommRing J] [IsCommJordan J] [Module ℝ J] + [IsScalarTower ℝ J J] [IsFormallyReal J] [Module.Finite ℝ J] + +omit [IsCommJordan J] [IsFormallyReal J] [Module.Finite ℝ J] in +/-- The square of a diagonal element is diagonal with squared coefficients. Orthogonality kills +every cross term. -/ +theorem sq_of_orthIdem {n : ℕ} {q : Fin n → J} (hq : IsOrthIdemFamily q) {lam : Fin n → ℝ} + {x : J} (hx : x = ∑ i, lam i • q i) : + x * x = ∑ i, (lam i * lam i) • q i := by + classical + rw [hx, Finset.sum_mul] + refine Finset.sum_congr rfl fun i _ => ?_ + rw [Finset.mul_sum, Finset.sum_eq_single i] + · rw [smul_mul_assoc, mul_smul_comm', smul_smul, hq.idem i] + · intro j _ hji + rw [smul_mul_assoc, mul_smul_comm', smul_smul, hq.orth i j (Ne.symm hji), smul_zero] + · intro h + exact absurd (Finset.mem_univ i) h + +/-- **`τ(x, x) ≥ 0`.** The spectral resolution turns `x ∘ x` into a nonnegative combination of +idempotents, and `jtr` is nonnegative on each of them. -/ +theorem traceForm_self_nonneg (x : J) : 0 ≤ traceForm x x := by + obtain ⟨n, q, lam, hfam, _, hx⟩ := spectral_resolution x + rw [traceForm_apply, sq_of_orthIdem hfam hx, map_sum] + refine Finset.sum_nonneg fun i _ => ?_ + rw [map_smul, smul_eq_mul] + exact mul_nonneg (mul_self_nonneg _) (jtr_nonneg_of_idem (hfam.idem i)) + +/-- **`τ` is definite.** A vanishing `τ(x, x) = ∑ᵢ λᵢ² tr(L_{qᵢ})` forces `λᵢ = 0` for every +`qᵢ ≠ 0`, because `tr(L_{qᵢ}) ≥ 1` there; and the terms with `qᵢ = 0` vanish anyway. -/ +theorem eq_zero_of_traceForm_self_eq_zero {x : J} (h : traceForm x x = 0) : x = 0 := by + classical + obtain ⟨n, q, lam, hfam, _, hx⟩ := spectral_resolution x + have hterm : ∀ i : Fin n, 0 ≤ (lam i * lam i) * jtr (q i) := fun i => + mul_nonneg (mul_self_nonneg _) (jtr_nonneg_of_idem (hfam.idem i)) + have hsum : (∑ i, (lam i * lam i) * jtr (q i)) = 0 := by + rw [traceForm_apply, sq_of_orthIdem hfam hx, map_sum] at h + simpa only [map_smul, smul_eq_mul] using h + have hzero : ∀ i : Fin n, (lam i * lam i) * jtr (q i) = 0 := fun i => + (Finset.sum_eq_zero_iff_of_nonneg fun j _ => hterm j).mp hsum i (Finset.mem_univ i) + rw [hx] + refine Finset.sum_eq_zero fun i _ => ?_ + by_cases hq0 : q i = 0 + · rw [hq0, smul_zero] + · have hpos : (0 : ℝ) < jtr (q i) := lt_of_lt_of_le zero_lt_one (one_le_jtr_of_idem (hfam.idem + i) hq0) + have : lam i * lam i = 0 := by + rcases mul_eq_zero.mp (hzero i) with h' | h' + · exact h' + · exact absurd h' (ne_of_gt hpos) + rw [mul_self_eq_zero.mp this, zero_smul] + +/-- `τ(x, x) = 0 ↔ x = 0`. -/ +theorem traceForm_self_eq_zero_iff (x : J) : traceForm x x = 0 ↔ x = 0 := + ⟨eq_zero_of_traceForm_self_eq_zero, fun h => by rw [h]; simp⟩ + +end PosDef + +end EuclideanJordan diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/Vendor.lean b/LeanPool/EuclideanJordan/EuclideanJordan/Vendor.lean new file mode 100644 index 0000000000..ca3417123d --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/Vendor.lean @@ -0,0 +1,26 @@ +/- +Copyright (c) 2026 Bryan Ehrlich. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Bryan Ehrlich +-/ + +module + +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.ContinuousLinearMap +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.IsMaximalSelfAdjoint +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.Isometry +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.LinearEquiv +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.Matrix +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.Misc +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.Tactic + + +/-! +# Vendor + +Supporting modules for Euclidean Jordan algebras: power associativity, the spectral theorem, the + trace form, Koecher/Alfsen-Shultz, and the frame Peirce decomposition. +-/ + +@[expose] public section diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/ContinuousLinearMap.lean b/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/ContinuousLinearMap.lean new file mode 100644 index 0000000000..e1e96b3d02 --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/ContinuousLinearMap.lean @@ -0,0 +1,73 @@ +/- +Copyright (c) 2025 Alex Meiburg. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Alex Meiburg +-/ +module + +public import Mathlib.Analysis.InnerProductSpace.Spectrum +public import Mathlib.Order.CompletePartialOrder + +/-! +# Continuous linear maps + +This file collects auxiliary results about `ContinuousLinearMap`s that are not (yet) available in +Mathlib. + +## Main results + +* `ContinuousLinearMap.ker_mk`: the kernel of the continuous linear map built from a continuous + semilinear map `f` agrees with the kernel of `f`. +* `ContinuousLinearMap.support_eq_sup_eigenspace_nonzero`: the range of a symmetric continuous + linear map on a finite-dimensional Euclidean space is the supremum of its eigenspaces for + nonzero eigenvalues. +-/ + +@[expose] public section + +namespace ContinuousLinearMap + +variable {R S : Type*} [Semiring R] [Semiring S] (σ : R →+* S) (M M₂ : Type*) +variable [TopologicalSpace M] [AddCommMonoid M] [TopologicalSpace M₂] [AddCommMonoid M₂] +variable [Module R M] [Module S M₂] + +theorem ker_mk (f : M →ₛₗ[σ] M₂) (hf : Continuous f.toFun) : + (ContinuousLinearMap.mk f hf).ker = LinearMap.ker f := by + rfl + +end ContinuousLinearMap + +namespace ContinuousLinearMap + +variable {n 𝕜 : Type*} [Fintype n] [RCLike 𝕜] + +/-- The support of a Hermitian matrix is the sum of its nonzero eigenspaces. -/ +theorem support_eq_sup_eigenspace_nonzero (A : EuclideanSpace 𝕜 n →L[𝕜] EuclideanSpace 𝕜 n) + (hA : A.IsSymmetric) : A.range = ⨆ μ ≠ 0, Module.End.eigenspace A μ := by + apply le_antisymm + · rintro x ⟨y, hy⟩ + have h_decomp : y ∈ ⨆ (μ : 𝕜), Module.End.eigenspace A.toLinearMap μ := by + have h_orth := hA.orthogonalComplement_iSup_eigenspaces_eq_bot + rw [Submodule.orthogonal_eq_bot_iff] at h_orth + rw [h_orth] + exact Submodule.mem_top; + rw [Submodule.mem_iSup_iff_exists_finsupp] at h_decomp + rcases h_decomp with ⟨f, hf₁, hf₂⟩ + have h_apply_A : A y = ∑ i ∈ f.support, A (f i) := by + rw [← hf₂, map_finsuppSum] + exact rfl + have h_eigen (i) : A (f i) = (i : 𝕜) • f i := + Module.End.mem_eigenspace_iff.mp (hf₁ i) + rw [← hy, coe_coe, h_apply_A, Finset.sum_congr rfl (fun i _ ↦ h_eigen i)] + refine Submodule.sum_mem _ fun i _ ↦ ?_ + by_cases hi0 : i = 0 + · simp [hi0] + · apply Submodule.smul_mem + apply Submodule.mem_iSup_of_mem i + exact Submodule.mem_iSup_of_mem hi0 (hf₁ i) + · simp only [iSup_le_iff] + intro μ hμ x hx + use μ⁻¹ • x + simp_all + +end ContinuousLinearMap diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/HermitianMat.lean b/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/HermitianMat.lean new file mode 100644 index 0000000000..85d602e36b --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/HermitianMat.lean @@ -0,0 +1,27 @@ +/- +Copyright (c) 2026 Bryan Ehrlich. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Bryan Ehrlich +-/ + +module + +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.Basic +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.CFC +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.Inner +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.Jordan +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.NonSingular +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.Order +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.Proj +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.Reindex +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.Trace + + +/-! +# HermitianMat + +Supporting modules for Euclidean Jordan algebras: power associativity, the spectral theorem, the + trace form, Koecher/Alfsen-Shultz, and the frame Peirce decomposition. +-/ + +@[expose] public section diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/HermitianMat/Basic.lean b/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/HermitianMat/Basic.lean new file mode 100644 index 0000000000..99d3315381 --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/HermitianMat/Basic.lean @@ -0,0 +1,820 @@ +/- +Copyright (c) 2025 Alex Meiburg. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Alex Meiburg +-/ +module + +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.Matrix +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.IsMaximalSelfAdjoint +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.ContinuousLinearMap +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.Tactic.Commutes + + +/-! Hermitian matrices, their algebraic structure, eigenspaces, and tensor products. -/ + +@[expose] public section + +/-- The type of Hermitian matrices, as a `Subtype`. Equivalent to a `Matrix n n α` bundled +with the fact that `Matrix.IsHermitian`. -/ +def HermitianMat (n : Type*) (α : Type*) [AddGroup α] [StarAddMonoid α] := + (selfAdjoint (Matrix n n α) : Type (max u_1 u_2)) + +namespace HermitianMat + +variable {α R 𝕜 : Type*} {m n : Type*} +variable [Star R] [TrivialStar R] +variable [RCLike 𝕜] + +section addgroup + +variable [AddGroup α] [StarAddMonoid α] + +theorem eq_IsHermitian : HermitianMat n α = { m : Matrix n n α // m.IsHermitian} := by + rfl + +/-- The underlying matrix of a Hermitian matrix. -/ +@[coe] def mat : HermitianMat n α → Matrix n n α := + Subtype.val + +instance : Coe (HermitianMat n α) (Matrix n n α) := ⟨mat⟩ + +@[simp] +theorem val_eq_coe (A : HermitianMat n α) : A.val = A := by + rfl + +@[simp] +theorem mat_mk (x : Matrix n n α) (h) : mat ⟨x, h⟩ = x := by + rfl + +@[simp] +theorem mk_mat {A : HermitianMat n α} (h : A.mat.IsHermitian) : ⟨A.mat, h⟩ = A := by + rfl + +/-- Alias for HermitianMat.property or HermitianMat.2, this gets the fact that the value + is actually `IsHermitian`. -/ +theorem H (A : HermitianMat n α) : A.mat.IsHermitian := + A.2 + +@[ext] protected theorem ext {A B : HermitianMat n α} : A.mat = B.mat → A = B := + Subtype.ext + +instance instFun : FunLike (HermitianMat n α) n (n → α) where + coe M := (M : Matrix n n α) + coe_injective _ _ h := HermitianMat.ext h + +@[simp] +theorem mat_apply {A : HermitianMat n α} {i j : n} : A.mat i j = A i j := by + rfl + +@[simp] +theorem conjTranspose_mat (A : HermitianMat n α) : + A.mat.conjTranspose = A.mat := + A.H + +instance : AddGroup (HermitianMat n α) := + AddSubgroup.toAddGroup _ + +instance [IsEmpty n] : Unique (HermitianMat n α) where + default := 0 + uniq a := by ext; exact (IsEmpty.false ‹_›).elim + +@[simp, norm_cast] +theorem mat_zero : (0 : HermitianMat n α).mat = 0 := by + rfl + +@[simp] +theorem mk_zero (h : (0 : Matrix n n α).IsHermitian) : ⟨0, h⟩ = (0 : HermitianMat n α) := by + rfl + +@[simp] +theorem zero_apply (i j : n) : (0 : HermitianMat n 𝕜) i j = 0 := by + rfl + +@[simp, norm_cast] +theorem mat_add (A B : HermitianMat n α) : + (A + B).mat = A.mat + B.mat := by + rfl + +@[simp, norm_cast] +theorem mat_sub (A B : HermitianMat n α) : + (A - B).mat = A.mat - B.mat := by + rfl + +@[simp, norm_cast] +theorem mat_neg (A : HermitianMat n α) : + (-A).mat = -A.mat := by + rfl + +section smul +variable [SMul R α] [StarModule R α] + +instance : SMul R (HermitianMat n α) := + ⟨fun c A ↦ ⟨c • A.mat, (IsSelfAdjoint.all _).smul A.H⟩⟩ + +@[simp, norm_cast] +theorem mat_smul (c : R) (A : HermitianMat n α) : + (c • A).mat = c • A.mat := by + rfl + +@[simp] +theorem smul_apply (c : R) (A : HermitianMat n α) (i j : n) : + (c • A) i j = c • A i j := by + rfl +end smul +section topology + +variable [TopologicalSpace α] + +instance : TopologicalSpace (HermitianMat n α) := + inferInstanceAs (TopologicalSpace (selfAdjoint _)) + +/- Amusingly, if we don't tag this fun_prop, then fun_prop fails to prove other things! Because +it will look through and see that `HermitianMat.mat` is `Subtype.val` *here*, but not in downstream +applications of the tactic. -/ +@[fun_prop] +theorem continuous_mat : Continuous (HermitianMat.mat : HermitianMat n α → Matrix n n α) := by + fun_prop + +lemma continuousOn_iff_coe {X : Type*} [TopologicalSpace X] {s : Set X} + (f : X → HermitianMat n α) : + ContinuousOn f s ↔ ContinuousOn (fun x => (f x).mat) s := by + constructor + · intro; fun_prop + · intro h + rw [continuousOn_iff_continuous_domRestrict] at * + apply Continuous.subtype_mk h + +variable [IsTopologicalAddGroup α] + +--In principle, ContinuousAdd and ContinuousNeg just need corresponding instances for α, +-- not all of IsTopologicalAddGroup. + +instance : ContinuousAdd (HermitianMat n α) := + inferInstanceAs (ContinuousAdd (selfAdjoint _)) + +instance : ContinuousNeg (HermitianMat n α) := + inferInstanceAs (ContinuousNeg (selfAdjoint _)) + +instance : IsTopologicalAddGroup (HermitianMat n α) where + +variable [TopologicalSpace R] [SMul R α] [ContinuousSMul R α] [StarModule R α] + +instance : ContinuousSMul R (HermitianMat n α) where + continuous_smul := by + apply Continuous.subtype_mk + exact continuous_fst.smul (continuous_mat.comp continuous_snd) + +--Shorcut instances: +instance : IsTopologicalAddGroup (HermitianMat n 𝕜) := inferInstance + +instance : ContinuousSMul ℝ (HermitianMat n ℂ) := inferInstance + +--TODO: Would be good to figure out the general (not just RCLike) version of this. +instance : T3Space (HermitianMat n 𝕜) := + inferInstanceAs (T3Space (selfAdjoint _)) + +end topology + +section mulAction +variable [Monoid R] [MulAction R α] [StarModule R α] + +instance : MulAction R (HermitianMat n α) := + Function.Injective.mulAction Subtype.val Subtype.coe_injective mat_smul + +end mulAction +end addgroup +section addcommgroup + +variable [AddCommGroup α] [StarAddMonoid α] + +instance : AddCommGroup (HermitianMat n α) := + AddSubgroup.toAddCommGroup _ + +@[simp, norm_cast] +theorem mat_finset_sum (f : ι → HermitianMat n α) (s : Finset ι) : + (∑ i ∈ s, f i).mat = ∑ i ∈ s, (f i).mat := by + apply AddSubgroup.val_finsetSum + +section module + +variable [Semiring R] [Module R α] [StarModule R α] + +instance : Module R (HermitianMat n α) := + inferInstanceAs (Module R (selfAdjoint (Matrix n n α))) + +variable [TopologicalSpace α] + +/-- The projection from HermitianMat to Matrix, as a continuous linear map. -/ +@[simps] +def matₗ : HermitianMat n α →L[R] Matrix n n α where + toFun := mat + cont := by fun_prop + map_add' := by simp + map_smul' := by intros; rfl + +end module +end addcommgroup +section ring + +variable [NonAssocRing α] [StarRing α] [DecidableEq n] + +instance : One (HermitianMat n α) := + ⟨1, by + simp [selfAdjoint.mem_iff, ← Matrix.ext_iff, + Matrix.one_apply, apply_ite (β := α), eq_comm]⟩ + +@[simp, norm_cast] +theorem mat_one : (1 : HermitianMat n α).mat = 1 := by + rfl + +@[simp] +theorem mk_one (h : (1 : Matrix n n α).IsHermitian) : ⟨1, h⟩ = (1 : HermitianMat n α) := by + rfl + +@[simp] +theorem one_apply (i j : n) : (1 : HermitianMat n α) i j = (1 : Matrix n n α) i j := by + rfl + +noncomputable instance : AddCommMonoidWithOne (HermitianMat n 𝕜) where + +instance [i : Nonempty n] : NeZero (1 : HermitianMat n 𝕜) := by + constructor + intro h + simpa using congr($h i.some i.some) + +end ring +section commring + +variable [CommRing α] [StarRing α] [DecidableEq m] [Fintype m] +variable (A : HermitianMat m α) (n : ℕ) (z : ℤ) + +noncomputable instance instInv : Inv (HermitianMat m α) := + ⟨fun x ↦ ⟨x⁻¹, x.H.inv⟩⟩ + +@[simp, norm_cast] +theorem mat_inv : (A⁻¹).mat = A.mat⁻¹ := by + rfl + +@[simp] +theorem zero_inv : ((0 : HermitianMat m α)⁻¹) = 0 := by + ext1; simp + +@[simp] +theorem one_inv : ((1 : HermitianMat m α)⁻¹) = 1 := by + ext1; simp + +noncomputable instance instPow : Pow (HermitianMat m α) ℕ := + ⟨fun x n ↦ ⟨x ^ n, x.H.pow n⟩⟩ + +@[simp, norm_cast] +theorem mat_pow (n : ℕ) : (A ^ n).mat = A.mat ^ n := by + rfl + +@[simp] +theorem pow_zero : A ^ 0 = 1 := by + ext1; simp + +@[simp] +theorem zero_pow (hn : n ≠ 0) : (0 : HermitianMat m α) ^ n = 0 := by + ext1; simp [hn] + +@[simp] +theorem one_pow : ((1 : HermitianMat m α) ^ n) = 1 := by + ext1; simp + +noncomputable instance instZPow : Pow (HermitianMat m α) ℤ := + ⟨fun x z ↦ ⟨x ^ z, x.H.zpow z⟩⟩ + +@[simp] +theorem mat_zpow (z : ℤ) : (A ^ z).mat = A.mat ^ z := by + rfl + +@[simp, norm_cast] +theorem zpow_natCast : A ^ (n : ℤ) = A ^ n := by + rfl + +@[simp] +theorem zpow_zero : A ^ (0 : ℤ) = 1 := by + ext1; simp + +@[simp] +theorem zpow_one : A ^ (1 : ℤ) = A := by + ext1; simp + +@[simp] +theorem one_zpow : ((1 : HermitianMat m α) ^ z) = 1 := by + ext1; simp + +@[simp] +theorem zpow_neg_one : A ^ (-1 : ℤ) = A⁻¹ := by + ext1; exact A.mat.zpow_neg_one + +@[simp] +theorem inv_zpow : A⁻¹ ^ z = (A ^ z)⁻¹ := by + ext1; exact A.mat.inv_zpow z + +add_aesop_rules safe norm (rule_sets := [Commutes]) + [mat_zero, mat_one, mat_smul, mat_add, mat_sub, mat_neg, mat_pow, mat_zpow, mat_inv] + +@[aesop safe apply (rule_sets := [Commutes])] +theorem _root_.Matrix.inv_commute {α : Type*} {A : Matrix m m α} [CommRing α] : Commute A⁻¹ A := by + rcases A.nonsing_inv_cancel_or_zero with h | h + · simp [Commute, SemiconjBy, h] + · simp [h] + +@[aesop safe apply (rule_sets := [Commutes])] +theorem commute_inv_self : Commute A⁻¹.mat A.mat := by + commutes + +@[aesop safe apply (rule_sets := [Commutes])] +theorem commute_self_inv : Commute A.mat A⁻¹.mat := by + commutes + +end commring +section rclike + +variable [Finite n] in +instance FiniteDimensional : FiniteDimensional ℝ (HermitianMat n 𝕜) := + FiniteDimensional.finiteDimensional_submodule (selfAdjoint.submodule ℝ (Matrix n n 𝕜)) + +@[simp] +theorem im_diag_eq_zero (A : HermitianMat n 𝕜) (x : n) : + RCLike.im (A x x) = 0 := by + simpa [CharZero.eq_neg_self_iff] using congrArg (RCLike.im <| · x x) A.H.symm + +--Repeat it explicitly for Complex.im so that simp can find it +@[simp] +theorem complex_im_eq_zero (A : HermitianMat n ℂ) (x : n) : + (A x x).im = 0 := + A.im_diag_eq_zero x + +end rclike + +section conj + +variable [CommRing α] [StarRing α] [Fintype n] +variable (A : HermitianMat n α) + +/-- The Hermitian matrix given by conjugating by a (possibly rectangular) Matrix. If we required +`B` to be +square, this would apply to any `Semigroup`+`StarMul` (as proved by `IsSelfAdjoint.conjugate`). + But this lets +us conjugate to other sizes too, as is done in e.g. Kraus operators. That is, it's a + _heterogeneous_ conjguation. +-/ +def conj {m} (B : Matrix m n α) : HermitianMat n α →+ HermitianMat m α where + toFun A := + ⟨B * A.mat * B.conjTranspose, by + ext + simp only [Matrix.star_apply, Matrix.mul_apply, Matrix.conjTranspose_apply, Finset.sum_mul, + star_sum, star_mul', star_star, show ∀ (a b : n), star (A.mat b a) = A.mat a b from + congrFun₂ A.property] + rw [Finset.sum_comm] + congr! 2 + ring⟩ + map_add' X Y := by + apply HermitianMat.ext + change B * (X.mat + Y.mat) * B.conjTranspose = + B * X.mat * B.conjTranspose + B * Y.mat * B.conjTranspose + rw [Matrix.mul_add, Matrix.add_mul] + map_zero' := by + apply HermitianMat.ext + change B * 0 * B.conjTranspose = 0 + rw [Matrix.mul_zero, Matrix.zero_mul] + +theorem conj_apply (B : Matrix m n α) (A : HermitianMat n α) : + conj B A = ⟨B * A.mat * B.conjTranspose, (conj B A).2⟩ := by + rfl + +@[simp] +theorem conj_apply_mat (B : Matrix m n α) (A : HermitianMat n α) : + (A.conj B).mat = B * A.mat * B.conjTranspose := by + rfl + +theorem conj_conj {m l} [Fintype m] (B : Matrix m n α) (C : Matrix l m α) : + (A.conj B).conj C = A.conj (C * B) := by + ext1 + simp [Matrix.conjTranspose_mul, Matrix.mul_assoc] + +variable (B : HermitianMat n α) + +@[simp] +theorem conj_zero : A.conj (0 : Matrix m n α) = 0 := by + apply HermitianMat.ext + change 0 * A.mat * (0 : Matrix m n α).conjTranspose = 0 + rw [Matrix.zero_mul, Matrix.zero_mul] + +@[simp] +theorem conj_one [DecidableEq n] : A.conj 1 = A := by + apply HermitianMat.ext + simp only [conj_apply_mat, Matrix.one_mul, Matrix.conjTranspose_one, Matrix.mul_one] + +@[simp] +lemma conj_one_unitary [DecidableEq n] (U : Matrix.unitaryGroup n α) : + conj U.val 1 = 1 := by + ext1 + have h : U * U.val.conjTranspose = 1 := U.prop.2 + simp [h] + +variable (R : Type*) [Star R] [TrivialStar R] [CommSemiring R] [Algebra R α] [StarModule R α] + +/-- `HermitianMat.conj` as an `R`-linear map, where `R` is the ring of relevant reals. -/ +def conjLinear {m} (B : Matrix m n α) : HermitianMat n α →ₗ[R] HermitianMat m α where + toAddHom := conj B + map_smul' r A := by + apply HermitianMat.ext + change B * (r • A.mat) * B.conjTranspose = r • (B * A.mat * B.conjTranspose) + rw [Matrix.mul_smul, Matrix.smul_mul] + +@[simp] +theorem conjLinear_apply (B : Matrix m n α) : conjLinear R B A = conj B A := by + rfl + +@[fun_prop] +lemma continuous_conj (ρ : HermitianMat n 𝕜) : Continuous (ρ.conj (m := m) ·) := by + apply Continuous.subtype_mk + change Continuous (fun B : Matrix m n 𝕜 => B * ρ.mat * B.conjTranspose) + fun_prop + +end conj + +section eigenspace + +variable [Fintype n] [DecidableEq n] (A : HermitianMat n 𝕜) + +omit [DecidableEq n] in +instance [i : Nonempty n] : FaithfulSMul ℝ (HermitianMat n 𝕜) where + eq_of_smul_eq_smul h := by + let : DecidableEq n := Classical.decEq n + simpa [RCLike.smul_re, -mat_apply] using congr(RCLike.re ($(h 1).val i.some i.some)) + +/-- The continuous linear map associated with a Hermitian matrix. -/ +noncomputable def lin : EuclideanSpace 𝕜 n →L[𝕜] EuclideanSpace 𝕜 n where + toLinearMap := A.mat.toEuclideanLin + cont := LinearMap.continuous_of_finiteDimensional _ + +@[simp] +theorem isSymmetric : A.lin.IsSymmetric := + Matrix.isSymmetric_toEuclideanLin_iff.symm.mp A.H + +@[simp] +theorem lin_zero : (0 : HermitianMat n 𝕜).lin = 0 := by + simp [lin]; rfl + +@[simp] +theorem lin_one : (1 : HermitianMat n 𝕜).lin = 1 := by + simp [lin]; rfl + +/-- The eigenspace of the linear endomorphism induced by a Hermitian matrix. -/ +noncomputable def eigenspace (μ : 𝕜) : Submodule 𝕜 (EuclideanSpace 𝕜 n) := + Module.End.eigenspace A.lin μ + +/-- The kernel of a Hermitian matrix `A` as a submodule of Euclidean space, defined by +`LinearMap.ker A.toMat.toEuclideanLin`. Equivalently, the zero-eigenspace. -/ +noncomputable def ker : Submodule 𝕜 (EuclideanSpace 𝕜 n) := + LinearMap.ker A.lin.toLinearMap + +theorem mem_ker_iff_mulVec_zero (x : EuclideanSpace 𝕜 n) : x ∈ A.ker ↔ A.mat.mulVec x = 0 := by + simp [ker, LinearMap.mem_ker, lin, Matrix.toLpLin_apply] + +/-- The kernel of a Hermitian matrix is its zero eigenspace. -/ +theorem ker_eq_eigenspace_zero : A.ker = A.eigenspace 0 := by + ext + simp [ker, eigenspace] + +@[simp] +theorem ker_zero : (0 : HermitianMat n 𝕜).ker = ⊤ := by + simp [ker] + +theorem ker_one : (1 : HermitianMat n 𝕜).ker = ⊥ := by + simp [ker]; rfl + +theorem ker_pos_smul {c : ℝ} (hc : c ≠ 0) : (c • A).ker = A.ker := by + ext x + simp [mem_ker_iff_mulVec_zero, Matrix.smul_mulVec, hc] + +/-- The support of a Hermitian matrix `A` as a submodule of Euclidean space, defined by +`LinearMap.range A.toMat.toEuclideanLin`. Equivalently, the sum of all nonzero eigenspaces. -/ +noncomputable def support : Submodule 𝕜 (EuclideanSpace 𝕜 n) := + LinearMap.range A.lin.toLinearMap + +/-- The support of a Hermitian matrix is the sum of its nonzero eigenspaces. -/ +theorem support_eq_sup_eigenspace_nonzero : A.support = ⨆ μ ≠ 0, A.eigenspace μ := by + exact A.lin.support_eq_sup_eigenspace_nonzero A.isSymmetric + +@[simp] +theorem support_zero : (0 : HermitianMat n 𝕜).support = ⊥ := by + simp [support] + +theorem support_one : (1 : HermitianMat n 𝕜).support = ⊤ := by + simpa [support] using LinearMap.ker_eq_bot_iff_range_eq_top.mp rfl + +@[simp] +theorem ker_orthogonal_eq_support : A.kerᗮ = A.support := by + rw [ker, support] + convert ContinuousLinearMap.orthogonal_ker A.lin + simp + +@[simp] +theorem support_orthogonal_eq_range : A.supportᗮ = A.ker := by + rw [ker, support] + convert! ContinuousLinearMap.orthogonal_range A.lin + simp + +end eigenspace + +section diagonal + +variable {𝕜 : Type*} [RCLike 𝕜] [DecidableEq n] + +variable (𝕜) in +/-- The Hermitian diagonal matrix whose diagonal is the given real-valued function. -/ +def diagonal (f : n → ℝ) : HermitianMat n 𝕜 := + ⟨Matrix.diagonal (f ·), + by simp [selfAdjoint.mem_iff, Matrix.star_eq_conjTranspose, Matrix.diagonal_conjTranspose]⟩ + +variable (f g : n → ℝ) + +@[simp] +theorem diagonal_mat : (diagonal 𝕜 f).mat = Matrix.diagonal (f · : n → 𝕜) := by + rfl + +@[simp] +theorem diagonal_zero : (diagonal 𝕜 0) = (0 : HermitianMat n 𝕜) := by + ext1; simp + +@[simp] +theorem diagonal_one : (diagonal 𝕜 1) = (1 : HermitianMat n 𝕜) := by + ext; rw [diagonal_mat]; simp + +lemma diagonal_add : diagonal 𝕜 (f + g) = diagonal 𝕜 f + diagonal 𝕜 g := by + ext1; simp + +lemma diagonal_add_apply : diagonal 𝕜 (fun x ↦ f x + g x) = diagonal 𝕜 f + diagonal 𝕜 g := by + ext1; simp + +lemma diagonal_sub : diagonal 𝕜 (f - g) = diagonal 𝕜 f - diagonal 𝕜 g := by + ext1; simp + +theorem diagonal_mul (c : ℝ) : diagonal 𝕜 (fun x ↦ c * f x) = c • diagonal 𝕜 f := by + ext1; simp [← Matrix.diagonal_smul] + +theorem diagonal_conj_diagonal [Fintype n] : + (diagonal 𝕜 f).conj (diagonal 𝕜 g) = diagonal 𝕜 (fun i ↦ f i * (g i)^2) := by + apply HermitianMat.ext + change Matrix.diagonal (fun i => (g i : 𝕜)) * Matrix.diagonal (fun i => (f i : 𝕜)) * + (Matrix.diagonal (fun i => (g i : 𝕜))).conjTranspose = _ + simp only [Matrix.diagonal_conjTranspose, Pi.star_def, RCLike.star_def, + RCLike.conj_ofReal, Matrix.diagonal_mul_diagonal] + congr 1 + funext i + simp only [RCLike.ofReal_mul, RCLike.ofReal_pow] + ring + +/-- +A Hermitian matrix is equal to its diagonalization conjugated by its eigenvector unitary matrix. +-/ +lemma eq_conj_diagonal [Fintype n] (A : HermitianMat n 𝕜) : + A = (diagonal 𝕜 A.H.eigenvalues).conj A.H.eigenvectorUnitary := by + ext1 + exact Matrix.IsHermitian.spectral_theorem A.2 + +end diagonal + +section kronecker +open Kronecker + +variable {p q : Type*} +variable [CommRing α] [StarRing α] + +/-- The kronecker product of two HermitianMats, see `Matrix.kroneckerMap`. -/ +def kronecker (A : HermitianMat m α) (B : HermitianMat n α) : HermitianMat (m × n) α where + val := A.mat ⊗ₖ B.mat + property := Matrix.kroneckerMap_IsHermitian A.H B.H + +@[inherit_doc HermitianMat.kronecker] +scoped[HermitianMat] infixl:100 " ⊗ₖ " => HermitianMat.kronecker + +@[simp, norm_cast] +theorem kronecker_mat (A : HermitianMat m α) (B : HermitianMat n α) : + (A ⊗ₖ B).mat = A.mat ⊗ₖ B.mat := by + rfl + +@[simp] +theorem zero_kronecker (A : HermitianMat m α) : (0 : HermitianMat n α) ⊗ₖ A = 0 := by + ext1; simp + +@[simp] +theorem kronecker_zero (A : HermitianMat m α) : A ⊗ₖ (0 : HermitianMat n α) = 0 := by + ext1; simp + +variable [DecidableEq m] [DecidableEq n] in +@[simp] +theorem kronecker_one_one : (1 : HermitianMat m α) ⊗ₖ (1 : HermitianMat n α) = 1 := by + ext1; simp + +variable (A B : HermitianMat m α) (C : HermitianMat n α) in +theorem add_kronecker : (A + B) ⊗ₖ C = A ⊗ₖ C + B ⊗ₖ C := by + ext1; simp [Matrix.add_kronecker] + +variable (A : HermitianMat m α) (B C : HermitianMat n α) in +theorem kronecker_add : A ⊗ₖ (B + C) = A ⊗ₖ B + A ⊗ₖ C := by + ext1; simp [Matrix.kronecker_add] + +lemma kronecker_diagonal [DecidableEq m] [DecidableEq n] (d₁ : m → ℝ) (d₂ : n → ℝ) : + (diagonal 𝕜 d₁ ⊗ₖ diagonal 𝕜 d₂) = diagonal 𝕜 (fun (i : m × n) => d₁ i.1 * d₂ i.2) := by + ext1 + simp [Matrix.diagonal_kronecker_diagonal] + +/-- +A ⊗ₖ B always commutes with C ⊗ₖ D if the pairs commute. +-/ +--Apply safely. It will almost always work, but there are cases where it's not sound, +-- such as `A = 0`. But these can all get easily simp'ed away anyway. +@[aesop safe apply (rule_sets := [Commutes])] +theorem kron_commute [Fintype m] [Fintype n] + {A C : HermitianMat m α} {B D : HermitianMat n α} + (hAC : Commute A.mat C.mat) (hBD : Commute B.mat D.mat) : + Commute (A ⊗ₖ B).mat (C ⊗ₖ D).mat := by + rw [commute_iff_eq] at hAC hBD ⊢ + simp only [kronecker_mat, ← Matrix.mul_kronecker_mul, hAC, hBD] + +/-- +A ⊗ₖ 1 always commutes with 1 ⊗ₖ B +-/ +@[aesop safe apply (rule_sets := [Commutes])] --redundant but important shortcut +theorem kron_id_commute_id_kro [Fintype m] [Fintype n] [DecidableEq m] [DecidableEq n] + (A : HermitianMat m α) (B : HermitianMat n α) : + Commute (A ⊗ₖ (1 : HermitianMat n α)).mat ((1 : HermitianMat m α) ⊗ₖ B).mat := by + commutes + +/- +The conjugate of a Kronecker product by a Kronecker product is the Kronecker product of the + conjugates. +-/ +lemma kronecker_conj [Fintype m] [Fintype n] + (A : HermitianMat m α) (B : HermitianMat n α) (C : Matrix p m α) (D : Matrix q n α) : + (A ⊗ₖ B).conj (C ⊗ₖ D) = (A.conj C) ⊗ₖ (B.conj D) := by + ext1 + exact Matrix.kronecker_conj_eq A.mat B.mat C D + +end kronecker + +section more_range_stuff + +variable {d d₂ : Type*} [Fintype d] [DecidableEq d] [Fintype d₂] [DecidableEq d₂] + +/- +If the range of a Hermitian matrix is contained in its kernel, the matrix is zero. +-/ +theorem range_le_ker_imp_zero {A : HermitianMat d 𝕜} + (h : LinearMap.range A.mat.toEuclideanLin ≤ LinearMap.ker A.mat.toEuclideanLin) : A = 0 := by + rw [HermitianMat.ext_iff, mat_zero] + ext i j + have hA_sq : (A.mat * A.mat) = 0 := by + simp_all only [SetLike.le_def, LinearMap.mem_range, LinearMap.mem_ker, forall_exists_index, + forall_apply_eq_imp_iff] + simp_all only [← Matrix.ext_iff, Matrix.mul_apply, mat_apply, Matrix.zero_apply] + intro i j + specialize h ( EuclideanSpace.single j 1 ) + simpa [ Matrix.mulVec, dotProduct ] using congr(WithLp.ofLp $(h) i) + simp_all only [mat_apply, Matrix.zero_apply] + replace hA_sq := congr_fun ( congr_fun hA_sq i ) i + simp_all only [Matrix.mul_apply, mat_apply, Matrix.zero_apply]; + -- Since $A$ is Hermitian, we have $A i x * A x i = |A i x|^2$. + have h_abs : ∀ x, (A i x) * (A x i) = ‖A i x‖ ^ 2 := by + intro x; have := A.2 + simp_all only [val_eq_coe, sq]; + have := congr_fun ( congr_fun this i ) x + simp_all only [Matrix.star_apply, mat_apply, RCLike.star_def]; + simp only [← this, mul_comm, RCLike.norm_conj]; + simp [ ← sq, RCLike.mul_conj ]; + simp_rw [h_abs] at hA_sq + norm_cast at hA_sq + simp_all [Finset.sum_eq_zero_iff_of_nonneg] + +/-- +If ker M ⊆ ker A, then range (A Mᴴ) = range A. +-/ +theorem _root_.Matrix.range_mul_conjTranspose_of_ker_le_ker {A : Matrix d d 𝕜} {M : Matrix d₂ d 𝕜} + (h : LinearMap.ker M.toEuclideanLin ≤ LinearMap.ker A.toEuclideanLin) : + LinearMap.range (A * M.conjTranspose).toEuclideanLin = LinearMap.range A.toEuclideanLin := by + apply le_antisymm + · rintro x ⟨y, rfl⟩ + use (M.conjTranspose.toEuclideanLin) y; + simp [Matrix.toEuclideanLin] + · intro x hx; + -- Since $x \in \text{range}(A)$, there exists $y \in \text{range}(Mᴴ)$ such that $A y = x$. + obtain ⟨y, hy⟩ : ∃ y ∈ LinearMap.range (Matrix.toEuclideanLin (M.conjTranspose)), + A.toEuclideanLin y = x := by + have h_range_MH : LinearMap.range (Matrix.toEuclideanLin (M.conjTranspose)) = + (LinearMap.ker (Matrix.toEuclideanLin M))ᗮ := by + have h_orthogonal : (LinearMap.range (Matrix.toEuclideanLin (M.conjTranspose)))ᗮ = + LinearMap.ker (Matrix.toEuclideanLin M) := by + ext x + rw [Matrix.toEuclideanLin_conjTranspose_eq_adjoint] + simp only [Submodule.mem_orthogonal, LinearMap.mem_ker, LinearMap.mem_range] + constructor + · intro h + rw [← inner_self_eq_zero (𝕜 := 𝕜)] + have : ∀ y, @inner 𝕜 _ _ y (Matrix.toEuclideanLin M x) = 0 := by + intro y + rw [← LinearMap.adjoint_inner_left] + exact h _ ⟨y, rfl⟩ + exact this _ + · intro h y ⟨z, hz⟩ + rw [← hz, LinearMap.adjoint_inner_left, h, inner_zero_right] + rw [← h_orthogonal, Submodule.orthogonal_orthogonal] + obtain ⟨ y, rfl ⟩ := hx; + -- Since $y$ is in the range of $Mᴴ$, we can write $y$ as $y = y_1 + y_2$ where $y_1 + -- \in \text{range}(Mᴴ)$ and $y_2 \in \text{ker}(M)$. + obtain ⟨y1, y2, hy1, hy2, hy⟩ : ∃ y1 y2 : EuclideanSpace 𝕜 d, y1 ∈ LinearMap.range + (Matrix.toEuclideanLin (M.conjTranspose)) ∧ y2 ∈ LinearMap.ker (Matrix.toEuclideanLin + M) ∧ y = y1 + y2 := by + have h_decomp : ∀ y : EuclideanSpace 𝕜 d, ∃ y1 ∈ LinearMap.range (Matrix.toEuclideanLin + (M.conjTranspose)), ∃ y2 ∈ LinearMap.ker (Matrix.toEuclideanLin M), y = y1 + y2 := by + intro y + have h_decomp : y ∈ (LinearMap.range (Matrix.toEuclideanLin (M.conjTranspose))) ⊔ + (LinearMap.ker (Matrix.toEuclideanLin M)) := by + rw [ h_range_MH ]; + rw [ sup_comm, Submodule.sup_orthogonal_of_hasOrthogonalProjection ]; + exact Submodule.mem_top; + rw [ Submodule.mem_sup ] at h_decomp; tauto; + exact ⟨ _, _, h_decomp y |> Classical.choose_spec |> And.left, h_decomp y |> + Classical.choose_spec |> And.right |> Classical.choose_spec |> And.left, h_decomp y + |> Classical.choose_spec |> And.right |> Classical.choose_spec |> And.right ⟩; + exact ⟨ y1, hy1, by rw [ hy, map_add, LinearMap.mem_ker.mp ( h hy2 ) ]; simp ⟩; + obtain ⟨ z, rfl ⟩ := hy.1; + exact ⟨ z, by simpa [ Matrix.toEuclideanLin ] using hy.2 ⟩ + +omit [DecidableEq d₂] [Fintype d₂] in +theorem conj_ne_zero [Finite d₂] {A : HermitianMat d 𝕜} {M : Matrix d₂ d 𝕜} (hA : A ≠ 0) + (h : LinearMap.ker M.toEuclideanLin ≤ A.ker) : A.conj M ≠ 0 := by + classical + let := Fintype.ofFinite d₂ + by_contra h_contra + have h_range : LinearMap.range A.mat.toEuclideanLin ≤ LinearMap.ker A.mat.toEuclideanLin := by + have h_range : LinearMap.range (A.mat * M.conjTranspose).toEuclideanLin ≤ LinearMap.ker + M.toEuclideanLin := by + rintro x ⟨y, rfl⟩ + replace h_contra := congr($(h_contra).mat) + simp_all [Matrix.toLpLin_apply, Matrix.mul_assoc] + rw [← Matrix.range_mul_conjTranspose_of_ker_le_ker h] + exact h_range.trans h + exact hA (range_le_ker_imp_zero h_range) + +omit [DecidableEq d₂] [Fintype d₂] in +theorem conj_ne_zero_iff [Finite d₂] {A : HermitianMat d 𝕜} {M : Matrix d₂ d 𝕜} + (h : LinearMap.ker M.toEuclideanLin ≤ A.ker) : A.conj M ≠ 0 ↔ A ≠ 0 := by + classical + let := Fintype.ofFinite d₂ + refine ⟨?_, (conj_ne_zero · h)⟩ + intro h rfl; grind + +section spectrum + +variable [Fintype n] [DecidableEq n] [Fintype m] [DecidableEq m] + +theorem _root_.Matrix.IsHermitian.spectrum_rcLike {A : Matrix n n 𝕜} (hA : A.IsHermitian) : + RCLike.ofReal '' spectrum ℝ A = spectrum 𝕜 A := by + rw [hA.spectrum_eq_image_range, hA.spectrum_real_eq_range_eigenvalues] + +/-- We fix a simp-normal form that, for HermitianMat, we always work in terms +of the real spectrum. -/ +@[simp] +theorem spectrum_rcLike (A : HermitianMat n 𝕜) : + spectrum 𝕜 A.mat = RCLike.ofReal '' spectrum ℝ A.mat := by + exact A.H.spectrum_rcLike.symm + +theorem ne_zero_iff_ne_zero_spectrum (A : HermitianMat n 𝕜) : + A ≠ 0 ↔ ∃ x ∈ spectrum ℝ A.mat, x ≠ 0 := by + constructor; + · intro h_nonzero + contrapose! h_nonzero + simp only [HermitianMat.ext_iff, mat_zero] + rw [A.H.spectral_theorem] + ext i j + simp? [Matrix.mul_apply, Matrix.diagonal] + refine Finset.sum_eq_zero fun x _ ↦ ?_ + simp [h_nonzero _ <| A.H.spectrum_real_eq_range_eigenvalues.symm ▸ Set.mem_range_self _] + · rintro ⟨x, hx, hx'⟩ h + simp [h, spectrum, resolventSet, Algebra.algebraMap_eq_smul_one, + hx', Matrix.isUnit_iff_isUnit_det] at hx + +open scoped Pointwise in +theorem spectrum_prod + {A : HermitianMat m 𝕜} {B : HermitianMat n 𝕜} : + spectrum ℝ (A ⊗ₖ B).mat = spectrum ℝ A.mat * spectrum ℝ B.mat := + Matrix.spectrum_prod A.H B.H + +end spectrum + +--Shortcut instance +noncomputable instance : AddCommMonoid (HermitianMat d ℂ) := + inferInstance + + +end more_range_stuff + +end HermitianMat diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/HermitianMat/CFC.lean b/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/HermitianMat/CFC.lean new file mode 100644 index 0000000000..353c76a9c5 --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/HermitianMat/CFC.lean @@ -0,0 +1,1666 @@ +/- +Copyright (c) 2025 Alex Meiburg. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Alex Meiburg +-/ +module + +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.Inner +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.NonSingular +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.Isometry + +public import Mathlib.Analysis.CStarAlgebra.ContinuousFunctionalCalculus.Continuity +public import Mathlib.Analysis.CStarAlgebra.ContinuousFunctionalCalculus.Commute +public import Mathlib.Analysis.CStarAlgebra.CStarMatrix +public import Mathlib.Topology.TietzeExtension +public import Mathlib.MeasureTheory.Integral.IntervalIntegral.Basic + +/-! Matrix operations on HermitianMats with the CFC -/ + +@[expose] public section +namespace HermitianMat + +noncomputable section CFC + +variable {d d₂ 𝕜 : Type*} [Fintype d] [DecidableEq d] [Fintype d₂] [DecidableEq d₂] [RCLike 𝕜] +variable {X : Type*} [TopologicalSpace X] +variable (A : HermitianMat d 𝕜) (f : ℝ → ℝ) (g : ℝ → ℝ) (q r : ℝ) + +/- Adding this to the `CStarAlgebra` aesop set allows `cfc_tac` to use it. -/ +omit [Fintype d] [DecidableEq d] in +@[aesop safe apply (rule_sets := [CStarAlgebra])] +theorem isSelfAdjoint : IsSelfAdjoint A.mat := by + exact A.H + +/- Adding this to `fun_prop` allows `cfc_cont_tac` to use it. -/ +@[fun_prop] +theorem continuousOn_finite {α β : Type*} (f : α → β) (S : Set α) + [TopologicalSpace α] [TopologicalSpace β] [T1Space α] [Finite S] : ContinuousOn f S := by + rw [continuousOn_iff_continuous_domRestrict] + exact continuous_of_discreteTopology + +@[simp] +theorem conjTranspose_cfc : (cfc f A.mat).conjTranspose = cfc f A.mat := by + exact cfc_predicate f A.mat + +/-- Continuous functional calculus for a real-valued function of a Hermitian matrix. -/ +protected def cfc : HermitianMat d 𝕜 := + ⟨cfc f A.mat, cfc_predicate _ _⟩ + +theorem cfc_eq : A.cfc f = ⟨cfc f A.mat, cfc_predicate f A.mat⟩ := by + rfl + +@[simp] +theorem mat_cfc : (A.cfc f).mat = _root_.cfc f A.mat := by + rfl + +section congr + +variable {f g A} + +theorem cfc_eq_cfc_iff_eqOn (f g : ℝ → ℝ) : + A.cfc f = A.cfc g ↔ Set.EqOn f g (spectrum ℝ A.mat) := by + rw [HermitianMat.ext_iff, mat_cfc, mat_cfc] + exact _root_.cfc_eq_cfc_iff_eqOn A.H + +nonrec theorem cfc_congr (hfg : Set.EqOn f g (spectrum ℝ A.mat)) : + A.cfc f = A.cfc g := by + ext1 + exact cfc_congr hfg + +/-- Version of `cfc_congr` specialized to PSD matrices. -/ +nonrec theorem cfc_congr_of_nonneg (hA : 0 ≤ A) (hfg : Set.EqOn f g (Set.Ici 0)) : + A.cfc f = A.cfc g := by + refine cfc_congr (hfg.mono ?_) + open MatrixOrder in + exact spectrum_nonneg_of_nonneg (a := A.mat) hA + +open ComplexOrder in +/-- Version of `cfc_congr` specialized to positive definite matrices. -/ +nonrec theorem cfc_congr_of_posDef (hA : A.mat.PosDef) (hfg : Set.EqOn f g (Set.Ioi 0)) : + A.cfc f = A.cfc g := by + refine cfc_congr (hfg.mono ?_) + rw [A.H.spectrum_real_eq_range_eigenvalues] + rintro _ ⟨i, rfl⟩ + exact hA.eigenvalues_pos i + +end congr +section commute +variable {A B : HermitianMat d 𝕜} + +@[aesop apply 50% (rule_sets := [Commutes])] +theorem _root_.Commute.cfc_left (hAB : Commute A.mat B.mat) : + Commute (A.cfc f).mat B.mat := by + exact hAB.cfc_real f + +@[aesop apply 50% (rule_sets := [Commutes])] +theorem _root_.Commute.cfc_right (hAB : Commute A.mat B.mat) : + Commute A.mat (B.cfc f).mat := + (hAB.symm.cfc_left f).symm + +theorem cfc_commute (f g : ℝ → ℝ) (hAB : Commute A.mat B.mat) : + Commute (A.cfc f).mat (B.cfc g).mat := by + exact (hAB.cfc_right g).cfc_left f + +@[aesop safe apply (rule_sets := [Commutes])] +theorem cfc_self_commute (A : HermitianMat d 𝕜) (f g : ℝ → ℝ) : + Commute (A.cfc f).mat (A.cfc g).mat := by + commutes + +end commute + +/-- Reindexing a matrix commutes with applying the CFC. -/ +@[simp] +theorem cfc_reindex (e : d ≃ d₂) : (A.reindex e).cfc f = (A.cfc f).reindex e := by + rw [HermitianMat.ext_iff] + simp only [mat_cfc, mat_reindex] + exact Matrix.cfc_reindex f e + +theorem spectrum_cfc_eq_image (A : HermitianMat d 𝕜) (f : ℝ → ℝ) : + spectrum ℝ (A.cfc f).mat = f '' (spectrum ℝ A.mat) := by + exact cfc_map_spectrum f A.mat + +/-- +Spectral decomposition of `A.cfc f` as a sum of scaled projections (matrix version). +-/ +theorem cfc_toMat_eq_sum_smul_proj : (A.cfc f).mat = + ∑ i, f (A.H.eigenvalues i) • (A.H.eigenvectorUnitary.val * (Matrix.single i i 1) * + A.H.eigenvectorUnitary.val.conjTranspose) := by + rw [A.mat_cfc, A.H.cfc_eq, Matrix.IsHermitian.cfc] + have h : ( Matrix.diagonal ( RCLike.ofReal ∘ f ∘ Matrix.IsHermitian.eigenvalues A.H ) : Matrix + d d 𝕜 ) = ∑ i, f ( A.H.eigenvalues i ) • Matrix.single i i 1 := by + ext i j; by_cases hij : i = j <;> simp? [ hij ]; + · simp [ Matrix.sum_apply, Matrix.single ]; + simp [ Algebra.smul_def ]; + · rw [Matrix.sum_apply] + simp_all? + rw [h] + simp? [Matrix.single, Matrix.mul_assoc] + congr! 1 + ext j k + simp [Matrix.mul_apply,Finset.mul_sum, Finset.smul_sum, smul_ite, smul_zero] + +--Ensure we get this instance: + +theorem cfc_eigenvalues (A : HermitianMat d 𝕜) : + ∃ (e : d ≃ d), (A.cfc f).H.eigenvalues = f ∘ A.H.eigenvalues ∘ e := + A.H.cfc_eigenvalues f + +/-! Here we give HermitianMat versions of many cfc theorems, like `cfc_id`, `cfc_sub`, `cfc_comp`, +etc. We need these because (as above) `HermitianMat.cfc` is different from `_root_.cfc`. -/ + +@[simp] +nonrec theorem cfc_id : A.cfc id = A := by + simpa [HermitianMat.ext_iff] using cfc_id ℝ A.mat + +@[simp] +nonrec theorem cfc_id' : A.cfc (·) = A := + cfc_id A + +nonrec theorem cfc_add : A.cfc (f + g) = A.cfc f + A.cfc g := by + ext1; exact cfc_add .. + +theorem cfc_add_apply : A.cfc (fun x ↦ f x + g x) = A.cfc f + A.cfc g := + cfc_add A f g + +nonrec theorem cfc_sub : A.cfc (f - g) = A.cfc f - A.cfc g := by + ext1; exact cfc_sub .. + +theorem cfc_sub_apply : A.cfc (fun x ↦ f x - g x) = A.cfc f - A.cfc g := + cfc_sub A f g + +nonrec theorem cfc_neg : A.cfc (-f) = -A.cfc f := by + ext1; exact cfc_neg .. + +theorem cfc_neg_apply : A.cfc (fun x ↦ -f x) = -A.cfc f := + cfc_neg A f + +/-- We don't have a direct analog of `cfc_mul`, since we can't generally multiply +to HermitianMat's to get another one, so the theorem statement wouldn't be well-typed. +But, we can say that the matrices are always equal. See `cfc_conj` for the coe-free +analog to multiplication. -/ +theorem mat_cfc_mul : (A.cfc (f * g)).mat = A.cfc f * A.cfc g := by + simp only [mat_cfc] + exact cfc_mul .. + +theorem mat_cfc_mul_apply : (A.cfc (fun x ↦ f x * g x)).mat = A.cfc f * A.cfc g := by + exact mat_cfc_mul .. + +nonrec theorem cfc_comp : A.cfc (g ∘ f) = (A.cfc f).cfc g := by + ext1; exact cfc_comp .. + +theorem cfc_comp_apply : A.cfc (fun x ↦ g (f x)) = (A.cfc f).cfc g := + cfc_comp A f g + +nonrec theorem cfc_conj : (A.cfc f).conj (A.cfc g) = A.cfc (f * g^2) := by + ext1 + simp only [conj_apply, mat_cfc, mat_mk, conjTranspose_cfc] + rw [← cfc_mul, ← cfc_mul, Pi.mul_def, Pi.pow_def] + grind only + +@[simp] +theorem cfc_diagonal (g : d → ℝ) : (diagonal 𝕜 g).cfc f = diagonal 𝕜 (f ∘ g) := by + ext1 + exact Matrix.cfc_diagonal g f + +theorem cfc_conj_unitary (U : Matrix.unitaryGroup d 𝕜) : + (A.conj U.val).cfc f = (A.cfc f).conj U := by + ext1 + exact Matrix.cfc_conj_unitary f U + +@[simp] +nonrec theorem cfc_const : (A.cfc (fun _ ↦ r)) = r • 1 := by + ext1 + simp only [mat_cfc, mat_smul, mat_one] + rw [cfc_const r A.mat] + exact Algebra.algebraMap_eq_smul_one r + +@[simp] +nonrec theorem cfc_const_mul_id : A.cfc (fun x ↦ r * x) = r • A := by + ext1 + rw [mat_cfc, mat_smul, cfc_const_mul_id r A.mat] + +@[simp] +nonrec theorem cfc_const_mul : A.cfc (fun x ↦ r * f x) = r • A.cfc f := by + rw [← cfc_const_mul_id, ← cfc_comp] + rfl + +@[simp] +nonrec theorem cfc_apply_zero : (0 : HermitianMat d 𝕜).cfc f = f 0 • 1 := by + simp [HermitianMat.ext_iff, Algebra.algebraMap_eq_smul_one] + +@[simp] +nonrec theorem cfc_apply_one : (1 : HermitianMat d 𝕜).cfc f = f 1 • 1 := by + simp [HermitianMat.ext_iff, Algebra.algebraMap_eq_smul_one] + +theorem cfc_pow {n : ℕ} : A.cfc (· ^ n) = A ^ n := by + ext1 + induction n + · simp + · simp_rw [pow_succ, mat_pow, mat_cfc_mul_apply, pow_succ, cfc_id'] + congr + +theorem cfc_nonneg_iff : 0 ≤ A.cfc f ↔ ∀ i, 0 ≤ f (A.H.eigenvalues i) := by + open MatrixOrder in + rw [zero_le_iff, mat_cfc, ← Matrix.nonneg_iff_posSemidef] + rw [_root_.cfc_nonneg_iff f A.mat, A.H.spectrum_real_eq_range_eigenvalues] + grind + +open ComplexOrder in +theorem cfc_posDef : (A.cfc f).mat.PosDef ↔ ∀ i, 0 < f (A.H.eigenvalues i) := by + rw [(A.cfc f).H.posDef_iff_eigenvalues_pos] + obtain ⟨e, he⟩ := A.cfc_eigenvalues f + rw [he] + refine ⟨fun h i ↦ ?_, fun h i ↦ h (e i)⟩ + simpa using h (e.symm i) + +variable {A f} in +/-- If a rael function preserves nonnegativity, the CFC preserves PSDness. -/ +theorem cfc_nonneg_of_nonneg (hA : 0 ≤ A) (hf : ∀ i ≥ 0, 0 ≤ f i) : + 0 ≤ A.cfc f := by + rw [cfc_nonneg_iff] + rw [zero_le_iff, A.H.posSemidef_iff_eigenvalues_nonneg] at hA + exact fun i ↦ hf _ (hA i) + +theorem cfc_nonSingular (hf : ∀ i, f (A.H.eigenvalues i) ≠ 0) : NonSingular (A.cfc f) := by + rw [nonSingular_iff_eigenvalue_ne_zero] + obtain ⟨e, he⟩ := cfc_eigenvalues f A + intro i + rw [congrFun he i] + exact hf (e i) + +theorem trace_mul_cfc (A : HermitianMat d 𝕜) (f : ℝ → ℝ) : + (A.mat * (A.cfc f).mat).trace = ∑ i, A.H.eigenvalues i * f (A.H.eigenvalues i) := by + conv_lhs => rw [A.eq_conj_diagonal] + rw [cfc_conj_unitary] + simp [conj_apply_mat, Matrix.mul_assoc, A.H.eigenvectorUnitary.val.trace_mul_comm] + simp [← Matrix.mul_assoc, Matrix.IsHermitian.eigenvectorUnitary ] + +theorem norm_eq_sum_eigenvalues_sq (A : HermitianMat d 𝕜) : + ‖A‖ ^ 2 = ∑ i, (A.H.eigenvalues i)^2 := by + rw [← RCLike.ofReal_inj (K := 𝕜), RCLike.ofReal_pow, norm_eq_trace_sq] + conv_lhs => change (A ^ 2).mat.trace; rw [(A ^ 2).H.trace_eq_sum_eigenvalues] + simp only [map_sum, map_pow] + rw [← cfc_pow] + obtain ⟨e, he⟩ := cfc_eigenvalues (· ^ 2) A + simp only [he, Function.comp_apply, map_pow] + exact e.sum_comp (fun x ↦ (algebraMap ℝ 𝕜) (A.H.eigenvalues x) ^ 2) + +variable {A} in +theorem lt_smul_of_norm_lt {r : ℝ} (h : ‖A‖ ≤ r) : A ≤ r • 1 := by + rcases lt_or_ge r 0 with _ | hr + · have := norm_nonneg A + order + rcases isEmpty_or_nonempty d + · exact le_of_subsingleton + have h' := (sq_le_sq₀ (by positivity) (by positivity)).mpr h + rw [norm_eq_sum_eigenvalues_sq] at h' + nth_rw 1 [← cfc_const A, ← cfc_id A] + rw [le_iff, ← cfc_sub] + rw [(HermitianMat.H _).posSemidef_iff_eigenvalues_nonneg] + intro i; rw [Pi.zero_apply] + obtain ⟨e, he⟩ := cfc_eigenvalues ((fun x ↦ r) - id) A + rw [he]; clear he + dsimp only [Function.comp_apply, Pi.sub_apply, id_eq] + rw [sub_nonneg] + apply le_of_sq_le_sq _ hr + refine le_trans ?_ h' + exact Finset.single_le_sum (f := fun x ↦ (A.H.eigenvalues x)^2) (by intros; positivity) + (Finset.mem_univ _) + +theorem ball_subset_Icc : Metric.ball A r ⊆ Set.Icc (A - r • 1) (A + r • 1) := by + intro x + simp only [Metric.mem_ball, dist_eq_norm, Set.mem_Icc, tsub_le_iff_right] + intro h + constructor + · rw [← norm_neg] at h + grw [← lt_smul_of_norm_lt h.le] + simp + · grw [← lt_smul_of_norm_lt h.le] + simp + +theorem spectrum_subset_of_mem_Icc (A B : HermitianMat d 𝕜) : + ∃ a b, ∀ x, A ≤ x ∧ x ≤ B → spectrum ℝ x.mat ⊆ Set.Icc a b := by + use ⨅ i, A.H.eigenvalues i, ⨆ i, B.H.eigenvalues i + rintro x ⟨hl, hr⟩ + exact A.H.spectrum_subset_of_mem_Icc B.H hl hr + +--TODO: Generalize this to real matrices (really, RCLike) too. The theorem below +-- gives it for complex matrices only. +-- @[fun_prop] +-- protected theorem cfc_continuous {f : ℝ → ℝ} (hf : Continuous f) : +-- Continuous (cfc · f : HermitianMat d 𝕜 → HermitianMat d 𝕜) := by +-- rcases isEmpty_or_nonempty d +-- · sorry +-- rw [Metric.continuous_iff] at hf ⊢ +-- intro x ε hε +-- have _ : Nonempty (spectrum ℝ x.toMat) := by +-- sorry +-- replace hf b := hf b ε hε +-- choose fc hfc₀ hfc using hf +-- let δ : ℝ := ⨆ e : spectrum ℝ x.toMat, fc e +-- refine ⟨δ, ?_, ?_⟩ +-- · --This whole block should just be `positivity`. TODO fix. +-- dsimp [δ] +-- --Why doesn't just `classical` make this happen automatically? +-- replace h_fin := Fintype.ofFinite (spectrum ℝ x.toMat) +-- rw [← Finset.sup'_univ_eq_ciSup, gt_iff_lt, Finset.lt_sup'_iff] +-- simp [hfc₀] +-- intro a ha +-- simp only [dist, AddSubgroupClass.subtype_apply, val_eq_coe, cfc_toMat] at ha ⊢ +-- sorry + +@[fun_prop] +protected theorem cfc_continuous {f : ℝ → ℝ} (hf : Continuous f) : + Continuous (HermitianMat.cfc · f : HermitianMat d ℂ → HermitianMat d ℂ) := by + unfold HermitianMat.cfc + suffices Continuous (fun A : HermitianMat d ℂ ↦ _root_.cfc f A.mat) by + fun_prop + apply continuous_of_continuousOn_iUnion_of_isOpen (ι := HermitianMat d ℂ × {x : ℝ // 0 < x}) + (s := fun ab ↦ Metric.ball ab.1 ab.2) + · rintro ⟨A, r, hr⟩ + apply ContinuousOn.mono ?_ (ball_subset_Icc A r) + obtain ⟨a, b, hab⟩ := spectrum_subset_of_mem_Icc (A - r • 1) (A + r • 1) + open ComplexOrder in + refine ContinuousOn.cfc (s := fun _ ↦ Set.Icc a b) (t := Set.Icc (A - r • 1) (A + r • 1)) (A + := CStarMatrix d d ℂ) f ?_ (by fun_prop) ?_ (fun x _ ↦ x.H) + · intro _ _ + exact isCompact_Icc + · simp only [Set.mem_Icc] + exact fun _ _ ↦ eventually_nhdsWithin_of_forall hab + · rintro ⟨a, r, hr⟩ + exact Metric.isOpen_ball + · ext x + simp only [Set.mem_iUnion, Set.mem_univ, iff_true] + use ⟨x, 1⟩ + simp + +open ComplexOrder in +theorem Matrix.PosDef.spectrum_subset_Ioi {d 𝕜 : Type*} [Fintype d] [DecidableEq d] [RCLike 𝕜] + {A : Matrix d d 𝕜} (hA : A.PosDef) : spectrum ℝ A ⊆ Set.Ioi 0 := by + intro x hx; + -- Since $A$ is positive definite, all its eigenvalues are positive. + have h_eigenvalues_pos : ∀ i : d, 0 < hA.1.eigenvalues i := by + exact hA.eigenvalues_pos; + have h_spectrum_eq_range : spectrum ℝ A = Set.range (hA.1.eigenvalues) := by + exact Matrix.IsHermitian.spectrum_real_eq_range_eigenvalues hA.left; + aesop + +/-- +If f is a continuous family of functions parameterized by x, then (fun x => A.cfc (f x)) is also + continuous. +-/ +@[fun_prop] +theorem continuous_cfc_fun {f : X → ℝ → ℝ} (hf : ∀ i, Continuous (f · i)) : + Continuous (fun x ↦ A.cfc (f x)) := by + apply Continuous.subtype_mk + conv => enter [1, x]; apply A.cfc_toMat_eq_sum_smul_proj (f x) + fun_prop + +variable {f : X → ℝ → ℝ} {S : Set X} +/-- +ContinuousOn variant for when all the matrices (A x) have a spectrum in a set T, and f is + continuous on a set S. +-/ +@[fun_prop] +theorem continuousOn_cfc_fun {T : Set ℝ} + (hf : ∀ i ∈ T, ContinuousOn (f · i) S) (hA : spectrum ℝ A.mat ⊆ T) : + ContinuousOn (fun x ↦ A.cfc (f x)) S := by + simp_rw [continuousOn_iff_continuous_domRestrict] at hf ⊢ + apply Continuous.subtype_mk + conv => enter [1, x]; apply A.cfc_toMat_eq_sum_smul_proj (f x) + unfold Set.domRestrict at hf + apply continuous_finsetSum _ + rw [A.H.spectrum_real_eq_range_eigenvalues] at hA + refine fun i _ ↦ Continuous.smul (hf _ (by grind)) (by fun_prop) + +section joint_continuity + +--TODO Cleanup + +/-- +Bound the Frobenius norm of a functional calculus application. +-/ +lemma norm_cfc_le_sqrt_card_mul_bound {A : HermitianMat d ℂ} {f : ℝ → ℝ} {C : ℝ} + (hC : 0 ≤ C) (hf : ∀ x ∈ spectrum ℝ A.mat, ‖f x‖ ≤ C) : + ‖A.cfc f‖ ≤ Real.sqrt (Fintype.card d) * C := by + rw [ ← Real.sqrt_sq ( norm_nonneg _ ) ]; + -- Recall that the Frobenius norm of a Hermitian matrix is the square root of the sum of the + -- squares of its eigenvalues. + have h_frobenius_eigenvalues : ∀ (M : HermitianMat d ℂ), ‖M‖ ^ 2 = ∑ i ∈ Finset.univ, + (M.H.eigenvalues i) ^ 2 := by + exact fun M => norm_eq_sum_eigenvalues_sq M; + -- Applying the bound on the eigenvalues to the Frobenius norm. + have h_bound : ∑ i ∈ Finset.univ, ((A.cfc f).H.eigenvalues i) ^ 2 ≤ (Fintype.card d) * C ^ 2 := by + have h_bound : ∀ i, ((A.cfc f).H.eigenvalues i) ^ 2 ≤ C ^ 2 := by + intro i + have h_eigenvalue_bound : |(A.cfc f).H.eigenvalues i| ≤ C := by + obtain ⟨ x, hx, hx' ⟩ : (A.cfc f).H.eigenvalues i ∈ f '' spectrum ℝ A.mat := by + have h_bound := (A.cfc f).H.eigenvalues_mem_spectrum_real i + rwa [spectrum_cfc_eq_image A f] at h_bound + specialize hf x hx + aesop; + nlinarith only [ abs_le.mp h_eigenvalue_bound ]; + exact le_trans ( Finset.sum_le_sum fun _ _ => h_bound _ ) ( by simp ); + rw [ h_frobenius_eigenvalues, Real.sqrt_le_left ] <;> nlinarith [ Real.sqrt_nonneg ( + Fintype.card d : ℝ ), Real.mul_self_sqrt ( Nat.cast_nonneg ( Fintype.card d ) ) ] + +/- +The norm of the difference of two functional calculus applications is bounded by `sqrt(d)` times + the sup norm of the difference of the functions. +-/ +lemma norm_cfc_sub_cfc_le_sqrt_card {A : HermitianMat d ℂ} {f g : ℝ → ℝ} : + ‖A.cfc f - A.cfc g‖ ≤ Real.sqrt (Fintype.card d) * ⨆ x ∈ spectrum ℝ A.mat, ‖f x - g x‖ := by + rw [ ← HermitianMat.cfc_sub ]; + refine norm_cfc_le_sqrt_card_mul_bound (C := ⨆ x ∈ spectrum ℝ A.mat, ‖f x - g x‖) ?_ ?_ + · exact Real.iSup_nonneg fun _ => Real.iSup_nonneg fun _ => norm_nonneg _; + · intro x hx + apply le_csSup; + · -- The supremum of a finite set of real numbers is finite. + have h_finite : Set.Finite (spectrum ℝ A.mat) := by + exact Set.toFinite _; + obtain ⟨ M, hM ⟩ := h_finite.exists_finset_coe; + refine ⟨ ∑ x ∈ M, ‖f x - g x‖, Set.forall_mem_range.2 fun x => ?_ ⟩; + rw [ ← hM ]; + rw [ @ciSup_eq_ite ]; + split_ifs <;> [ exact Finset.single_le_sum ( fun x _ => norm_nonneg ( f x - g x ) ) ( by + assumption ); exact le_trans ( by norm_num ) ( Finset.sum_nonneg fun x _ => + norm_nonneg ( f x - g x ) ) ]; + · exact ⟨ x, by aesop ⟩; + +/- +If f and g are close on T, and the spectrum of A is in T, then A.cfc f and A.cfc g are close. +-/ +lemma norm_cfc_sub_le_of_sup_le {A : HermitianMat d ℂ} {f g : ℝ → ℝ} {T : Set ℝ} {ε : ℝ} + (hT : spectrum ℝ A.mat ⊆ T) (hε : 0 ≤ ε) (h_sup : ∀ x ∈ T, ‖f x - g x‖ ≤ ε) : + ‖A.cfc f - A.cfc g‖ ≤ Real.sqrt (Fintype.card d) * ε := by + refine le_trans ( norm_cfc_sub_cfc_le_sqrt_card ) ?_; + gcongr; + refine ciSup_le fun x => ?_; + exact Real.iSup_le (fun i => h_sup x (hT i)) hε + +/-- +If $f$ is jointly continuous on $S \times T$ and $T$ is compact, then $x \mapsto f(x, \cdot)$ is + continuous into the space of bounded functions on $T$ with the uniform norm. +-/ +lemma dist_lt_of_continuous' {X : Type*} [TopologicalSpace X] + {f : X → ℝ → ℝ} {S : Set X} {T : Set ℝ} + (hT : IsCompact T) + (hf : ContinuousOn (fun (p : X × ℝ) ↦ f p.1 p.2) (S ×ˢ T)) + {x₀ : X} (hx₀ : x₀ ∈ S) {ε : ℝ} (hε : 0 < ε) : + ∃ U ∈ nhds x₀, ∀ x ∈ U ∩ S, ∀ t ∈ T, ‖f x t - f x₀ t‖ < ε := by + by_contra h_contra; + -- For each $t \in T$, by continuity at $(x₀, t)$, there exist neighborhoods $U_t$ of $x₀$ + -- and $V_t$ of $t$ such that for all $x \in U_t \cap S$ and $t' \in V_t \cap T$, $|f(x, t') + -- - f(x₀, t)| < \epsilon/2$. + have h_cont : ∀ t ∈ T, ∃ U_t ∈ nhds x₀, ∃ V_t ∈ nhds t, ∀ x ∈ U_t ∩ S, ∀ t' ∈ V_t ∩ T, ‖f x t' + - f x₀ t‖ < ε / 2 := by + intro t ht + have h_cont_t : ∀ᶠ (p : X × ℝ) in nhds (x₀, t), p ∈ S ×ˢ T → ‖f p.1 p.2 - f x₀ t‖ < ε / 2 := by + have := hf ( x₀, t ) ⟨ hx₀, ht ⟩; + have := this.eventually ( Metric.ball_mem_nhds _ ( half_pos hε ) ); + rw [ eventually_nhdsWithin_iff ] at this; aesop; + rcases mem_nhds_prod_iff.mp h_cont_t with ⟨ U, hU, V, hV, hUV ⟩; + exact ⟨ U, hU, V, hV, fun x hx t' ht' => hUV ( Set.mk_mem_prod hx.1 ht'.1 ) ⟨ hx.2, ht'.2 ⟩ ⟩; + choose! U hU V hV hUV using h_cont; + -- Since $T$ is compact, cover it by finitely many $V_{t_i}$. Let $U = \bigcap U_{t_i}$. + obtain ⟨t_fin, ht_fin⟩ : ∃ t_fin : Finset ℝ, (∀ t ∈ t_fin, t ∈ T) ∧ T ⊆ ⋃ t ∈ t_fin, V t := by + have := hT.elim_nhds_subcover V fun t ht => hV t ht; + tauto; + refine h_contra ⟨⋂ t ∈ t_fin, U t, ?_, ?_⟩ + · exact Filter.biInter_mem ( Finset.finite_toSet t_fin ) |>.2 fun t ht => hU t ( ht_fin.1 t ht ); + · intro x hx t ht + obtain ⟨t', ht'_fin, ht'_t⟩ : ∃ t' ∈ t_fin, t ∈ V t' := by + simpa using ht_fin.2 ht; + have := hUV t' ( ht_fin.1 t' ht'_fin ) x ⟨ Set.mem_iInter₂.1 hx.1 t' ht'_fin, hx.2 ⟩ t ⟨ + ht'_t, ht ⟩; + have := hUV t' ( ht_fin.1 t' ht'_fin ) x₀ ⟨ mem_of_mem_nhds ( hU t' ( ht_fin.1 t' ht'_fin ) + ), hx₀ ⟩ t ⟨ ht'_t, ht ⟩; + exact abs_lt.mpr ⟨ by linarith [ abs_lt.mp ‹‖f x t - f x₀ t'‖ < ε / 2›, abs_lt.mp ‹‖f x₀ t - + f x₀ t'‖ < ε / 2› ], by linarith [ abs_lt.mp ‹‖f x t - f x₀ t'‖ < ε / 2›, abs_lt.mp ‹‖f + x₀ t - f x₀ t'‖ < ε / 2› ] ⟩ + +/-- +The functional calculus is continuous on matrices with spectrum in a compact set. +-/ +lemma continuousOn_cfc_of_compact {K : Set ℝ} {g : ℝ → ℝ} (hK : IsCompact K) (hg : ContinuousOn + g K) : + ContinuousOn (fun (A : HermitianMat d ℂ) ↦ A.cfc g) {A | spectrum ℝ A.mat ⊆ K} := by + by_contra! h_contra; + -- By Stone-Weierstrass, there exists a sequence of polynomials `p_n` converging uniformly + -- to `g` on `K`. + obtain ⟨p_n, hp_n⟩ : ∃ p_n : ℕ → Polynomial ℝ, (∀ n, ∀ x ∈ K, |(p_n n).eval x - g x| ≤ 1 / (n + + 1)) := by + have h_stone_weierstrass : ∀ ε > 0, ∃ p : Polynomial ℝ, ∀ x ∈ K, |p.eval x - g x| < ε := by + have := @exists_polynomial_near_of_continuousOn; + obtain ⟨a, b, hab⟩ : ∃ a b : ℝ, K ⊆ Set.Icc a b := by + exact ⟨ hK.bddBelow.some, hK.bddAbove.some, fun x hx => ⟨ hK.bddBelow.choose_spec hx, + hK.bddAbove.choose_spec hx ⟩ ⟩; + -- Extend $g$ to a continuous function on $[a, b]$. + obtain ⟨f, hf⟩ : ∃ f : ℝ → ℝ, ContinuousOn f (Set.Icc a b) ∧ ∀ x ∈ K, f x = g x := by + have := @ContinuousMap.exists_restrict_eq; + specialize this ( show IsClosed K from hK.isClosed ) ( ContinuousMap.mk ( fun x => g x ) + <| by exact continuousOn_iff_continuous_domRestrict.mp hg ); + refine ⟨_, this.choose.continuous.continuousOn, fun x hx => ?_⟩ + exact congr_arg (fun f => f ⟨x, hx⟩) this.choose_spec + exact fun ε εpos => by rcases this a b f hf.1 ε εpos with ⟨ p, hp ⟩; exact ⟨ p, fun x hx + => by simpa only [ hf.2 x hx ] using hp x ( hab hx ) ⟩; + exact ⟨ fun n => Classical.choose ( h_stone_weierstrass ( 1 / ( n + 1 ) ) ( by positivity ) + ), fun n x hx => le_of_lt ( Classical.choose_spec ( h_stone_weierstrass ( 1 / ( n + 1 ) + ) ( by positivity ) ) x hx ) ⟩; + -- The sequence `A ↦ A.cfc (p_n)` converges uniformly to `A ↦ A.cfc g` on `{A | spectrum A ⊆ K}`. + have h_uniform : ∀ ε > 0, ∃ N : ℕ, ∀ n ≥ N, ∀ A : HermitianMat d ℂ, spectrum ℝ A.mat ⊆ K → + ‖A.cfc (fun x => (p_n n).eval x) - A.cfc g‖ < ε := by + -- By the properties of the functional calculus, we have `‖A.cfc p_n - A.cfc g‖ ≤ + -- sqrt(d) * ‖p_n - g‖_{∞, K}`. + have h_uniform_bound : ∀ n, ∀ A : HermitianMat d ℂ, spectrum ℝ A.mat ⊆ K → ‖A.cfc (fun x => + (p_n n).eval x) - A.cfc g‖ ≤ Real.sqrt (Fintype.card d) * (1 / (n + 1)) := by + intro n A hA + have h_uniform_bound : ‖A.cfc (fun x => (p_n n).eval x) - A.cfc g‖ ≤ Real.sqrt + (Fintype.card d) * ⨆ x ∈ spectrum ℝ A.mat, |(p_n n).eval x - g x| := by + exact norm_cfc_sub_cfc_le_sqrt_card; + refine le_trans h_uniform_bound ( mul_le_mul_of_nonneg_left ?_ ( Real.sqrt_nonneg + (Fintype.card d : ℝ) ) ); + refine ciSup_le fun x => ?_; + field_simp; + by_cases hx : x ∈ spectrum ℝ A.mat <;> simp_all? + exact le_trans ( mul_le_mul_of_nonneg_right ( hp_n n x ( hA hx ) ) ( by positivity ) ) ( + by nlinarith [ mul_inv_cancel₀ ( by positivity : ( n : ℝ ) + 1 ≠ 0 ) ] ); + intro ε εpos + refine ⟨Nat.ceil (ε⁻¹ * Real.sqrt (Fintype.card d)), fun n hn A hA => ?_⟩ + apply lt_of_le_of_lt (h_uniform_bound n A hA) + rw [mul_one_div, div_lt_iff₀] <;> + nlinarith [Nat.ceil_le.mp hn, inv_pos.mpr εpos, mul_inv_cancel₀ εpos.ne', + Real.sqrt_nonneg (Fintype.card d), + Real.sq_sqrt (Nat.cast_nonneg (Fintype.card d))] + -- The uniform limit of continuous functions is continuous. + have h_cont : ContinuousOn (fun A : HermitianMat d ℂ => A.cfc g) {A : HermitianMat d ℂ | + spectrum ℝ A.mat ⊆ K} := by + have h_seq_cont : ∀ n, ContinuousOn (fun A : HermitianMat d ℂ => A.cfc (fun x => (p_n + n).eval x)) {A : HermitianMat d ℂ | spectrum ℝ A.mat ⊆ K} := by + fun_prop + refine Metric.continuousOn_iff.mpr ?_; + intro A hA ε εpos + obtain ⟨N, hN⟩ := h_uniform (ε / 3) (by linarith) + obtain ⟨δ, δpos, hδ⟩ : ∃ δ > 0, ∀ a ∈ {A : HermitianMat d ℂ | spectrum ℝ A.mat ⊆ K}, dist a + A < δ → ‖a.cfc (fun x => (p_n N).eval x) - A.cfc (fun x => (p_n N).eval x)‖ < ε / 3 := by + have := Metric.continuousOn_iff.mp ( h_seq_cont N ) A hA ( ε / 3 ) ( by linarith ); + refine ⟨this.choose, this.choose_spec.1, fun a ha ha' => ?_⟩ + simpa only [dist_eq_norm] using this.choose_spec.2 a ha ha' + refine ⟨ δ, δpos, fun a ha ha' => ?_ ⟩; + have := hN N le_rfl a ha; + have := hN N le_rfl A hA; + rw [ dist_eq_norm ]; + rw [ show a.cfc g - A.cfc g = ( a.cfc g - a.cfc ( fun x => Polynomial.eval x ( p_n N ) ) ) + + ( a.cfc ( fun x => Polynomial.eval x ( p_n N ) ) - A.cfc ( fun x => Polynomial.eval x ( + p_n N ) ) ) + ( A.cfc ( fun x => Polynomial.eval x ( p_n N ) ) - A.cfc g ) by abel1 ]; + apply lt_of_le_of_lt (norm_add₃_le ..) + have hnorma := norm_sub_rev (a.cfc g) (a.cfc fun x => (p_n N).eval x) + have hnormA := norm_sub_rev (A.cfc fun x => (p_n N).eval x) (A.cfc g) + have hδa := hδ a ha ha' + linarith + contradiction + +end joint_continuity + +theorem continuous_cfc_joint_compact {X d : Type*} [TopologicalSpace X] [Fintype d] [DecidableEq d] + {f : X → ℝ → ℝ} {A : X → HermitianMat d ℂ} {S : Set X} {T : Set ℝ} + (hT : IsCompact T) + (hf : ContinuousOn (fun (p : X × ℝ) ↦ f p.1 p.2) (S ×ˢ T)) + (hA₁ : ∀ x ∈ S, spectrum ℝ (A x).mat ⊆ T) + (hA₂ : ContinuousOn (fun x ↦ A x) S) : + ContinuousOn (fun x ↦ (A x).cfc (f x)) S := by + intro x x_in_S + have h_eps_delta : ContinuousWithinAt (fun y => (A y).cfc (f x)) S x := by + refine ContinuousOn.continuousWithinAt ?_ x_in_S + exact (continuousOn_cfc_of_compact hT (hf.uncurry_left x x_in_S)).comp hA₂ hA₁ + simp only [ContinuousWithinAt] at *; + have h_eps_delta := Metric.tendsto_nhds.mp h_eps_delta + apply Metric.tendsto_nhds.mpr + intro ε ε_pos + obtain ⟨U, hU₁, hU₂⟩ : ∃ U ∈ nhds x, ∀ y ∈ U ∩ S, ‖(A y).cfc (f y) - (A y).cfc (f x)‖ ≤ + Real.sqrt (Fintype.card d) * (ε / (2 * Real.sqrt (Fintype.card d) + 1)) := by + have h_eps_delta₁ : ∀ ε > 0, ∃ U ∈ nhds x, ∀ y ∈ U ∩ S, ‖(A y).cfc (f y) - (A y).cfc (f x)‖ + ≤ Real.sqrt (Fintype.card d) * ε := by + intro ε ε_pos + obtain ⟨U, hU₁, hU₂⟩ := dist_lt_of_continuous' (f := f) hT hf x_in_S ε_pos + use U, hU₁ + intro y hy + apply norm_cfc_sub_le_of_sup_le (hA₁ y hy.right) ε_pos.le + intro t ht + exact le_of_lt (hU₂ y hy t ht) + exact h_eps_delta₁ _ (by positivity) + filter_upwards [ h_eps_delta ( ε / 2 ) ( half_pos ε_pos ), self_mem_nhdsWithin, + mem_nhdsWithin_of_mem_nhds hU₁ ] with y hy₁ hy₂ hy₃ + apply lt_of_le_of_lt (dist_triangle _ ((A y).cfc (f x)) _) + nlinarith [hU₂ y ⟨ hy₃, hy₂ ⟩, + Real.sqrt_nonneg ( Fintype.card d : ℝ ), + mul_div_cancel₀ ε ( show ( 2 * Real.sqrt ( Fintype.card d : ℝ ) + 1 ) ≠ 0 by positivity ), + norm_nonneg ( ( A y ).cfc ( f y ) - ( A y ).cfc ( f x ) ), + norm_nonneg ( ( A y ).cfc ( f x ) - ( A x ).cfc ( f x ) ), + dist_eq_norm ( ( A y ).cfc ( f y ) ) ( ( A y ).cfc ( f x ) ), + dist_eq_norm ( ( A y ).cfc ( f x ) ) ( ( A x ).cfc ( f x ) )] + +open scoped Matrix.Norms.Frobenius + +/- +PROBLEM +Eigenvalues of a `HermitianMat` are bounded by its (Frobenius) norm. +PROVIDED SOLUTION +Use `norm_eq_sum_eigenvalues_sq` which gives `‖A‖² = Σᵢ (A.H.eigenvalues i)²`. Since + `(A.H.eigenvalues i)² ≤ Σⱼ (A.H.eigenvalues j)² = ‖A‖²`, we get `|A.H.eigenvalues i| ≤ ‖A‖`. +-/ +lemma eigenvalue_norm_le (A : HermitianMat d ℂ) (i : d) : + |A.H.eigenvalues i| ≤ ‖A‖ := by + have h_eigenvalue_bound : |A.H.eigenvalues i| ^ 2 ≤ ‖A‖ ^ 2 := by + rw [ norm_eq_sum_eigenvalues_sq A ]; + simp? [pow_two]; + exact Finset.single_le_sum ( fun i _ => mul_self_nonneg ( A.H.eigenvalues i ) ) ( + Finset.mem_univ i ); + nlinarith [ norm_nonneg A ] + +/-- +The spectrum of a `HermitianMat` is contained in the closed ball of radius `‖A‖` around 0. +-/ +lemma spectrum_subset_closedBall (A : HermitianMat d ℂ) : + spectrum ℝ A.mat ⊆ Metric.closedBall (0 : ℝ) ‖A‖ := by + rw [A.H.spectrum_real_eq_range_eigenvalues] + rintro _ ⟨i, rfl⟩ + simp [Metric.mem_closedBall, dist_zero_right, eigenvalue_norm_le] +/- +PROBLEM +Upper semicontinuity of the spectrum of a Hermitian matrix: if the spectrum of `A₀` is contained +in an open set `U`, then the spectrum of any Hermitian matrix sufficiently close to `A₀` is also +contained in `U`. This follows from the openness of the set of invertible matrices and compactness. +PROVIDED SOLUTION +The proof uses the resolvent approach and compactness. +1. Let M = ‖A₀‖ + 1. For B in a ball of radius 1 around A₀, ‖B‖ ≤ M, so spectrum ℝ B.mat ⊆ + Metric.closedBall 0 M (by spectrum_subset_closedBall and the triangle inequality for norms). +2. Let K = Metric.closedBall (0 : ℝ) M \ U. Then K is compact (closed and bounded minus open = + closed and bounded in ℝ). And K ∩ spectrum ℝ A₀.mat = ∅ (since spectrum ℝ A₀.mat ⊆ U). +3. For each t ∈ K: t ∉ spectrum ℝ A₀.mat. By the definition of spectrum, A₀.mat - algebraMap ℝ + (Matrix d d ℂ) t is a unit. The set of units is open (Units.isOpen, since Matrix d d ℂ has + HasSummableGeomSeries). The map B ↦ B.mat - algebraMap ℝ _ t is continuous. So there exist + δ_t > 0 and ε_t > 0 such that for ‖B - A₀‖ < δ_t and |s - t| < ε_t, B.mat - algebraMap ℝ _ s + is a unit, meaning s ∉ spectrum ℝ B.mat. +4. By compactness of K (it's compact since it's a closed subset of the compact ball): finitely + many ε-balls B(t_j, ε_{t_j}) cover K. Let δ = min(1, min_j δ_{t_j}). +5. For B with ‖B - A₀‖ < δ: spectrum ℝ B.mat ⊆ Metric.closedBall 0 M (by step 1) and spectrum ℝ + B.mat ∩ K = ∅ (by step 4). So spectrum ℝ B.mat ⊆ Metric.closedBall 0 M \ K ⊆ U. +Note: we need to connect spectrum ℝ B.mat (the real spectrum) to IsUnit in the complex matrix + ring. Use that for self-adjoint elements, t ∈ spectrum ℝ A.mat iff algebraMap ℝ (Matrix d d + ℂ) t ∈ spectrum ℂ A.mat, and the resolvent set is open. We can use + spectrum.isOpen_resolventSet or the characterization via IsUnit. +-/ + + +lemma spectrum_subset_of_isOpen (A₀ : HermitianMat d ℂ) (U : Set ℝ) + (hU : IsOpen U) (hAU : spectrum ℝ A₀.mat ⊆ U) : + ∀ᶠ B in nhds A₀, spectrum ℝ B.mat ⊆ U := by + -- Let `M = ‖A₀‖ + 1`. For `B` with `‖B - A₀‖ < 1` we get `‖B‖ ≤ M`, so `σ(B) ⊆ closedBall 0 M`. + obtain ⟨M, hM⟩ : ∃ M : ℝ, ∀ B : HermitianMat d ℂ, ‖B - A₀‖ < 1 → + spectrum ℝ B.mat ⊆ Metric.closedBall 0 M := by + use ‖A₀‖ + 1 + intro B hB + have h_norm : ‖B‖ ≤ ‖A₀‖ + 1 := by + have := norm_sub_norm_le B A₀; linarith + exact (spectrum_subset_closedBall B).trans (Metric.closedBall_subset_closedBall h_norm) + -- `K = closedBall 0 M \ U` is compact and disjoint from `σ(A₀)`. + set K : Set ℝ := Metric.closedBall 0 M \ U + have hK_compact : IsCompact K := IsCompact.diff (ProperSpace.isCompact_closedBall _ _) hU + have hK_disjoint : K ∩ spectrum ℝ A₀.mat = ∅ := + Set.eq_empty_of_forall_notMem fun x hx => hx.1.2 <| hAU hx.2 + -- For each `t ∈ K` there are `δ_t, ε_t > 0` such that `‖B - A₀‖ < δ_t` and `|s - t| < ε_t` + -- make `B.mat - s` a unit: units are open and `A₀.mat - t` is one. + have h_unitary : ∀ t ∈ K, ∃ δ_t > 0, ∃ ε_t > 0, ∀ B : HermitianMat d ℂ, ‖B - A₀‖ < δ_t → + ∀ s : ℝ, |s - t| < ε_t → IsUnit (B.mat - algebraMap ℝ (Matrix d d ℂ) s) := by + intro t ht + have h_unitary : IsUnit (A₀.mat - algebraMap ℝ (Matrix d d ℂ) t) := by + simp_all? [Set.ext_iff, spectrum.mem_iff] + simpa using hK_disjoint t ht |> IsUnit.neg |> IsUnit.mul <| isUnit_one + have h_unitary_open : IsOpen {B : Matrix d d ℂ | IsUnit B} := Units.isOpen + have h_unitary_cont : Continuous (fun p : HermitianMat d ℂ × ℝ => + p.1.mat - algebraMap ℝ (Matrix d d ℂ) p.2) := by + refine Continuous.sub ?_ ?_ <;> fun_prop (disch := solve_by_elim) + obtain ⟨ε, ε_pos, hε⟩ := + Metric.isOpen_iff.mp (h_unitary_open.preimage h_unitary_cont) (A₀, t) h_unitary + exact ⟨ε, ε_pos, ε, ε_pos, fun B hB s hs => hε (show (B, s) ∈ Metric.ball (A₀, t) ε from by + simp only [Metric.mem_ball, Prod.dist_eq] + refine max_lt ?_ ?_ + · simpa [dist_eq_norm] using hB + · simpa [dist_eq_norm] using hs)⟩ + -- Compactness gives a finite subcover of `ε`-balls; set `δ = min 1 (min_j δ_j)`. + obtain ⟨δ, hδ_pos, hδ⟩ : ∃ δ > 0, ∀ t ∈ K, ∃ ε_t > 0, ∀ B : HermitianMat d ℂ, ‖B - A₀‖ < δ → + ∀ s : ℝ, |s - t| < ε_t → IsUnit (B.mat - algebraMap ℝ (Matrix d d ℂ) s) := by + choose! δ hδ ε hε h using h_unitary + have := hK_compact.elim_nhds_subcover (fun t => Metric.ball t (ε t)) + fun t ht => Metric.ball_mem_nhds t (hε t ht) + simp_all? [Set.subset_def] + obtain ⟨t, ht₁, ht₂⟩ := this + obtain ⟨δ_min, hδ_min_pos, hδ_min⟩ : ∃ δ_min > 0, ∀ i ∈ t, δ_min ≤ δ i := by + by_cases ht : t.Nonempty <;> simp_all? [Finset.Nonempty] + · exact ⟨Finset.min' (t.image δ) ⟨_, Finset.mem_image_of_mem δ ht.choose_spec⟩, + by + obtain ⟨i, hi, he⟩ := Finset.mem_image.mp + (Finset.min'_mem (t.image δ) ⟨_, Finset.mem_image_of_mem δ ht.choose_spec⟩) + rw [← he] + exact hδ i (ht₁ i hi), + fun i hi => Finset.min'_le _ _ (Finset.mem_image_of_mem δ hi)⟩ + · exact ⟨1, zero_lt_one⟩ + refine ⟨Min.min δ_min 1, lt_min hδ_min_pos zero_lt_one, fun x hx => ?_⟩ + obtain ⟨i, hi, hi'⟩ := ht₂ x hx + exact ⟨ε i - |x - i|, sub_pos.mpr (by simp_all? [abs_sub_comm]; exact hi'), + fun B hB s hs => h i (ht₁ i hi) B (lt_of_lt_of_le hB (min_le_of_left_le (hδ_min i hi))) s + (by rw [abs_lt] at *; constructor <;> linarith [abs_le.mp (show |x - i| ≤ |x - i| by rfl)])⟩ + -- For `B` with `‖B - A₀‖ < δ`, any `t ∈ σ(B)` lies outside `K`. + have h_not_in_K : ∀ B : HermitianMat d ℂ, ‖B - A₀‖ < δ → ∀ t ∈ spectrum ℝ B.mat, t ∉ K := by + intro B hB t ht htK + obtain ⟨ε_t, hε_t_pos, hε_t⟩ := hδ t htK + have h_unit : IsUnit (B.mat - algebraMap ℝ (Matrix d d ℂ) t) := + hε_t B hB t (by simpa using hε_t_pos) + exact ht (by have h1 := h_unit.neg; simp_all?; exact h1) + filter_upwards [Metric.ball_mem_nhds A₀ (show 0 < Min.min δ 1 by positivity)] with B hB using + fun t ht => Classical.not_not.1 fun h => h_not_in_K B + (lt_of_lt_of_le (by simpa [dist_eq_norm] using hB) (min_le_left _ _)) t ht + ⟨hM B (lt_of_lt_of_le (by simpa [dist_eq_norm] using hB) (min_le_right _ _)) ht, h⟩ + +/- +PROBLEM +The CFC is continuous in the matrix argument when the function is continuous on a set containing +the spectra, even when that set is not compact. This generalizes `continuousOn_cfc_of_compact`. +The proof uses Tietze extension from the finite spectrum to a compact interval, applies the +compact version, and bounds the error using upper semicontinuity of the spectrum. +PROVIDED SOLUTION +The proof uses Tietze extension and the compact version `continuousOn_cfc_of_compact`. +Step 1: Extend g from the finite set spectrum(A₀) to all of ℝ via Tietze. +The spectrum `Λ := spectrum ℝ A₀.mat` is finite (it equals `Set.range A₀.H.eigenvalues`), hence + closed. +The restriction of g to Λ is continuous (any function on a finite set is continuous in a T1 + space, using `continuousOn_finite`). +By `ContinuousMap.exists_restrict_eq`, there exists a continuous function `h : C(ℝ, ℝ)` with `h + = g` on Λ. +Step 2: (A₀).cfc g = (A₀).cfc h, since g = h on spectrum(A₀) (by `cfc_congr`). +Step 3: Show `B ↦ B.cfc h` is continuous at A₀. +Let M = ‖A₀‖ + 1 and K = Set.Icc (-M) M. Since h is continuous on ℝ and hence on K, and K is + compact, by `continuousOn_cfc_of_compact`, the map `B ↦ B.cfc h` is continuous on `{B | + spectrum ℝ B.mat ⊆ K}`. Since `{B | spectrum ℝ B.mat ⊆ T} ∩ (Metric.ball A₀ 1) ⊆ {B | + spectrum ℝ B.mat ⊆ K}` (because for B near A₀, ‖B‖ ≤ M, so spectrum B ⊆ [-M, M] = K by + spectrum_subset_closedBall), the map is ContinuousWithinAt at A₀. +Step 4: Show `‖B.cfc g - B.cfc h‖ → 0` as B → A₀ within `{B | σ(B) ⊆ T}`. +The function `|g - h|` is 0 on Λ = spectrum(A₀). For each eigenvalue λᵢ ∈ Λ ⊆ T: +- g is continuous on T at λᵢ +- h is continuous everywhere +So `|g(t) - h(t)| = |g(t) - g(λᵢ) + h(λᵢ) - h(t)|` is small for t near λᵢ with t ∈ T. +Define U_ε = {t ∈ ℝ | ∀ λ ∈ Λ, if |t - λ| < some δ then |g(t) - h(t)| < ε for t ∈ T} ∪ + (complement of a ball around Λ). +Actually, more precisely: the set V_ε = {t : ℝ | t ∈ T → |g(t) - h(t)| < ε} is an open set + containing Λ (since |g - h| = 0 on Λ and both are continuous at each point of Λ along T). +By `spectrum_subset_of_isOpen`, for B near A₀, spectrum(B) ⊆ V_ε. +Since spectrum(B) ⊆ T, for t ∈ spectrum(B), |g(t) - h(t)| < ε. +So ‖B.cfc g - B.cfc h‖ ≤ sqrt(d) * ε by `norm_cfc_sub_le_of_sup_le`. +Step 5: Combine. By the triangle inequality: +‖B.cfc g - A₀.cfc g‖ ≤ ‖B.cfc g - B.cfc h‖ + ‖B.cfc h - A₀.cfc h‖ +Both terms → 0, so the map is ContinuousWithinAt. +Use `Metric.continuousWithinAt_iff` and an ε/2 argument. +-/ + + +lemma continuousWithinAt_cfc_of_continuousOn {T : Set ℝ} {g : ℝ → ℝ} + {A₀ : HermitianMat d ℂ} + (hg : ContinuousOn g T) (hA₀ : spectrum ℝ A₀.mat ⊆ T) : + ContinuousWithinAt (fun B ↦ B.cfc g) {B | spectrum ℝ B.mat ⊆ T} A₀ := by + have h_ext : ∃ h : ℝ → ℝ, Continuous h ∧ ∀ x ∈ spectrum ℝ A₀.mat, h x = g x := by + have h_finite : Set.Finite (spectrum ℝ A₀.mat) := by + exact Set.toFinite _ + have h_cont : ContinuousOn g (spectrum ℝ A₀.mat) := hg.mono hA₀ + obtain ⟨h, hh⟩ := ContinuousMap.exists_restrict_eq h_finite.isClosed + (ContinuousMap.mk (fun x => g x) + (continuousOn_iff_continuous_domRestrict.mp h_cont)) + refine ⟨h, h.continuous, fun x hx => ?_⟩ + exact congr_arg (fun f => f ⟨x, hx⟩) hh + obtain ⟨h, hh_cont, hh_eq⟩ := h_ext; + have h_cfc_cont : ContinuousWithinAt (fun B => B.cfc h) {B : HermitianMat d ℂ | spectrum ℝ + B.mat ⊆ T} A₀ := by + exact Continuous.continuousWithinAt (HermitianMat.cfc_continuous hh_cont) + have h_diff_small : ∀ ε > 0, ∃ U ∈ nhds A₀, ∀ B ∈ U ∩ {B : HermitianMat d ℂ | spectrum ℝ B.mat + ⊆ T}, ‖B.cfc g - B.cfc h‖ < ε := by + intro ε ε_pos + obtain ⟨δ, δ_pos, hδ⟩ : ∃ δ > 0, ∀ x ∈ T, ∀ y ∈ spectrum ℝ A₀.mat, |x - y| < δ → |g x - h x| + < ε / (Real.sqrt (Fintype.card d) + 1) := by + have h_diff_small : ∀ y ∈ spectrum ℝ A₀.mat, ∃ δ > 0, ∀ x ∈ T, |x - y| < δ → |g x - h x| < + ε / (Real.sqrt (Fintype.card d) + 1) := by + intro y hy + have h_diff_small : Filter.Tendsto (fun x => |g x - h x|) (nhdsWithin y T) (nhds 0) := by + have h_diff_small : Filter.Tendsto (fun x => g x - h x) (nhdsWithin y T) (nhds (g y - + h y)) := by + exact Filter.Tendsto.sub ( hg.continuousWithinAt ( hA₀ hy ) ) ( + hh_cont.continuousWithinAt ); + simpa [ hh_eq y hy ] using h_diff_small.abs; + have := Metric.tendsto_nhdsWithin_nhds.mp h_diff_small ( ε / ( Real.sqrt ( Fintype.card + d ) + 1 ) ) ( div_pos ε_pos ( add_pos_of_nonneg_of_pos ( Real.sqrt_nonneg _ ) + zero_lt_one ) ); aesop; + choose! δ hδ_pos hδ using h_diff_small; + have h_finite : Set.Finite (spectrum ℝ A₀.mat) := by + exact Set.toFinite _; + obtain ⟨δ_min, hδ_min_pos, hδ_min⟩ : ∃ δ_min > 0, ∀ y ∈ spectrum ℝ A₀.mat, δ_min ≤ δ y := by + by_cases h_empty : spectrum ℝ A₀.mat = ∅; + · exact ⟨ 1, zero_lt_one, by simp [ h_empty ] ⟩; + · have := h_finite.toFinset.exists_min_image δ; + exact Exists.elim ( this ( Finset.nonempty_of_ne_empty ( by simpa [ Set.ext_iff ] + using h_empty ) ) ) fun x hx => ⟨ δ x, hδ_pos x ( by simpa using hx.1 ), fun y hy + => hx.2 _ ( h_finite.mem_toFinset.mpr hy ) ⟩; + exact ⟨ δ_min, hδ_min_pos, fun x hx y hy hxy => hδ y hy x hx ( lt_of_lt_of_le hxy ( hδ_min + y hy ) ) ⟩; + -- By the spectrum_subset_of_isOpen lemma, there exists a neighborhood U of A₀ such that + -- the spectrum of B is within δ of the spectrum of A₀ for all B in U. + obtain ⟨U, hU⟩ : ∃ U ∈ nhds A₀, ∀ B ∈ U, spectrum ℝ B.mat ⊆ {x | ∃ y ∈ spectrum ℝ A₀.mat, |x + - y| < δ} := by + have h_spectrum_subset : ∀ᶠ B in nhds A₀, spectrum ℝ B.mat ⊆ Metric.thickening δ (spectrum + ℝ A₀.mat) := by + have h_open : IsOpen (Metric.thickening δ (spectrum ℝ A₀.mat)) := by + exact Metric.isOpen_thickening + have := spectrum_subset_of_isOpen A₀ ( Metric.thickening δ ( spectrum ℝ A₀.mat ) ) + h_open ( Metric.self_subset_thickening δ_pos _ ); aesop; + generalize_proofs at *; ( + refine ⟨_, h_spectrum_subset, fun B hB x hx => ?_⟩ + simpa [dist_eq_norm] using Metric.mem_thickening_iff.mp (hB hx)) + generalize_proofs at *; ( + refine ⟨ U, hU.1, fun B hB => ?_ ⟩ + have h_diff_small : ∀ x ∈ spectrum ℝ B.mat, |g x - h x| ≤ ε / (Real.sqrt (Fintype.card d) + + 1) := by + exact fun x hx => le_of_lt ( hδ x ( hB.2 hx ) _ ( hU.2 B hB.1 hx |> Classical.choose_spec + |> And.left ) ( hU.2 B hB.1 hx |> Classical.choose_spec |> And.right ) ) |> le_trans + <| by norm_num; + generalize_proofs at *; ( + have h_diff_small : ‖B.cfc g - B.cfc h‖ ≤ Real.sqrt (Fintype.card d) * (ε / (Real.sqrt + (Fintype.card d) + 1)) := by + apply_rules [ norm_cfc_sub_le_of_sup_le ]; + · positivity; + · exact fun x hx => hx + generalize_proofs at *; ( + apply h_diff_small.trans_lt + rw [mul_div, div_lt_iff₀] <;> + nlinarith [Real.sqrt_nonneg (Fintype.card d : ℝ), + Real.sq_sqrt (Nat.cast_nonneg (Fintype.card d))]))) + have h_cfc_cont := Metric.continuousWithinAt_iff.mp h_cfc_cont + apply Metric.continuousWithinAt_iff.mpr + intro ε hε + obtain ⟨δ, hδ_pos, hδ⟩ := h_cfc_cont (ε / 2) (half_pos hε) + obtain ⟨U, hU_nhds, hU⟩ := h_diff_small (ε / 2) (half_pos hε) + use Min.min δ (Metric.mem_nhds_iff.mp hU_nhds).choose + simp? [hδ_pos]; + refine ⟨ ?_, ?_ ⟩ + all_goals generalize_proofs at *; + · exact ‹∃ ε, 0 < ε ∧ Metric.ball A₀ ε ⊆ U›.choose_spec.1; + · intro x hx hx' hx'' + have hd1 := hδ hx hx' + have hd2 := hU x ⟨(Metric.mem_nhds_iff.mp hU_nhds).choose_spec.2 hx'', hx⟩ + have h_eq : A₀.cfc g = A₀.cfc h := by + exact cfc_congr (show Set.EqOn g h (spectrum ℝ A₀.mat) from fun x hx => hh_eq x hx ▸ rfl) + ▸ rfl + rw [dist_eq_norm, h_eq] + calc ‖x.cfc g - A₀.cfc h‖ + = ‖(x.cfc g - x.cfc h) + (x.cfc h - A₀.cfc h)‖ := by congr 1; abel + _ ≤ ‖x.cfc g - x.cfc h‖ + ‖x.cfc h - A₀.cfc h‖ := norm_add_le _ _ + _ < ε / 2 + ε / 2 := by + apply add_lt_add hd2 + rwa [dist_eq_norm] at hd1 + _ = ε := add_halves ε + +/- +PROBLEM +For `f` jointly continuous on `S ×ˢ T` and the spectrum of `A y` contained in `T`, the difference +`f(y,t) - f(x₀,t)` can be made uniformly small on `spectrum (ℝ) (A y).mat` for `y` near `x₀`. +This is the non-compact replacement for `dist_lt_of_continuous'`: instead of uniform convergence +on a fixed compact set, we get uniform convergence on the (moving, finite) spectrum. +PROVIDED SOLUTION +Constructive proof. The key steps: +Step 1: For each eigenvalue λᵢ := (A x₀).H.eigenvalues i (which is in T by hA₁), the function f + is continuous at (x₀, λᵢ) within S ×ˢ T. So there exist open neighborhoods U_i of x₀ and V_i + of λᵢ such that for all (y, t) ∈ (U_i ∩ S) × (V_i ∩ T), we have ‖f y t - f x₀ t‖ < ε. Here's + how to get this: +- hf (x₀, λᵢ) gives ContinuousWithinAt at (x₀, λᵢ) within S ×ˢ T +- Apply this to the ε-ball around f(x₀, λᵢ), use that f(x₀, λᵢ) - f(x₀, λᵢ) = 0 to get ‖f y t - + f x₀ λᵢ‖ < ε/2 +- Similarly, hf at (x₀, λᵢ) restricted to {x₀} × T gives ‖f x₀ t - f x₀ λᵢ‖ < ε/2 +- Triangle inequality: ‖f y t - f x₀ t‖ < ε +- Use `mem_nhds_prod_iff` to extract U_i and V_i from the product neighborhood +Step 2: The open set W := ⋃ᵢ V_i contains spectrum(A x₀) (since each λᵢ ∈ V_i and spectrum = + range of eigenvalues). W is open as a union of open sets. By `spectrum_subset_of_isOpen (A + x₀) W`, we get ∀ᶠ B in nhds (A x₀), spectrum ℝ B.mat ⊆ W. +Step 3: By ContinuousWithinAt of A at x₀ (from hA₂), and the filter from step 2, we get: ∀ᶠ y in + nhdsWithin x₀ S, spectrum ℝ (A y).mat ⊆ W. Convert this to ∃ U' ∈ nhds x₀, ∀ y ∈ U' ∩ S, + spectrum(A y) ⊆ W. +Step 4: Take U = U' ∩ ⋂ᵢ U_i (finite intersection since d is Fintype). For y ∈ U ∩ S and t ∈ + spectrum(A y): +- t ∈ T (by hA₁) +- spectrum(A y) ⊆ W (by step 3), so t ∈ V_i for some i +- y ∈ U_i ∩ S +- So ‖f y t - f x₀ t‖ < ε (by step 1) +Use `by_contra` and arrive at contradiction, or construct the neighborhood directly using + `Filter.inter_mem` and `Filter.iInter_mem` (since d is Fintype, the index set is finite). +IMPORTANT: To get the open sets V_i, use `ContinuousWithinAt` of f at (x₀, λᵢ) which gives an + eventually filter statement, then extract using `mem_nhdsWithin_iff_exists_mem_nhds_inter` + and `mem_nhds_prod_iff`. +For the continuity of A composed with spectrum_subset_of_isOpen: use + `ContinuousWithinAt.eventually` or compose the filter. Specifically: `(hA₂ x₀ + hx₀).eventually (spectrum_subset_of_isOpen (A x₀) W hW_open hW_contains)` gives `∀ᶠ y in + nhdsWithin x₀ S, spectrum(A y) ⊆ W`. Then use `Filter.Eventually.exists_mem` to get U'. +-/ + +lemma dist_lt_of_continuous_spectrum {X : Type*} [TopologicalSpace X] + {f : X → ℝ → ℝ} {A : X → HermitianMat d ℂ} {S : Set X} {T : Set ℝ} + (hf : ContinuousOn (fun (p : X × ℝ) ↦ f p.1 p.2) (S ×ˢ T)) + (hA₁ : ∀ x ∈ S, spectrum ℝ (A x).mat ⊆ T) + (hA₂ : ContinuousOn (fun x ↦ A x) S) + {x₀ : X} (hx₀ : x₀ ∈ S) {ε : ℝ} (hε : 0 < ε) : + ∃ U ∈ nhds x₀, ∀ y ∈ U ∩ S, ∀ t ∈ spectrum ℝ (A y).mat, ‖f y t - f x₀ t‖ < ε := by + by_contra h_contra; + -- For each eigenvalue λᵢ := (A x₀).H.eigenvalues i (which is in T by hA₁), the + -- function f is continuous at (x₀, λᵢ) within S ×ˢ T. So there exist open + -- neighborhoods U_i of x₀ and V_i of λᵢ such that for all (y, t) ∈ (U_i ∩ S) × (V_i + -- ∩ T), we have ‖f y t - f x₀ t‖ < ε. + obtain ⟨U_i, V_i, hU_i, hV_i, h_cont⟩ : ∃ (U_i : d → Set X) (V_i : d → Set ℝ), (∀ i, + IsOpen (U_i i)) ∧ (∀ i, IsOpen (V_i i)) ∧ (∀ i, x₀ ∈ U_i i) ∧ (∀ i, (A + x₀).H.eigenvalues i ∈ V_i i) ∧ (∀ i, ∀ y ∈ U_i i ∩ S, ∀ t ∈ V_i i ∩ T, ‖f y t - f x₀ + t‖ < ε) := by + have h_cont : ∀ i, ∃ (U_i : Set X) (V_i : Set ℝ), IsOpen U_i ∧ IsOpen V_i ∧ x₀ ∈ U_i ∧ + (A x₀).H.eigenvalues i ∈ V_i ∧ ∀ y ∈ U_i ∩ S, ∀ t ∈ V_i ∩ T, ‖f y t - f x₀ t‖ < ε := by + intro i + generalize_proofs at *; ( + have h_cont : ContinuousWithinAt (fun p : X × ℝ => f p.1 p.2 - f x₀ p.2) (S ×ˢ T) (x₀, + (A x₀).H.eigenvalues i) := by + have hmemT : (A x₀).H.eigenvalues i ∈ T := + hA₁ x₀ hx₀ ((A x₀).H.eigenvalues_mem_spectrum_real i) + have h1 : ContinuousWithinAt (fun p : X × ℝ => f p.1 p.2) (S ×ˢ T) + (x₀, (A x₀).H.eigenvalues i) := hf (x₀, (A x₀).H.eigenvalues i) ⟨hx₀, hmemT⟩ + have hg : ContinuousWithinAt (fun q : ℝ => f x₀ q) T ((A x₀).H.eigenvalues i) := + (hf (x₀, (A x₀).H.eigenvalues i) ⟨hx₀, hmemT⟩).comp + (continuousWithinAt_const.prodMk continuousWithinAt_id) (fun q hq => ⟨hx₀, hq⟩) + have h2 : ContinuousWithinAt (fun p : X × ℝ => f x₀ p.2) (S ×ˢ T) + (x₀, (A x₀).H.eigenvalues i) := + hg.comp (f := Prod.snd) (x := (x₀, (A x₀).H.eigenvalues i)) + continuousWithinAt_snd (fun p hp => hp.2) + exact h1.sub h2 ) + generalize_proofs at *; ( + have := h_cont.eventually ( Metric.ball_mem_nhds _ hε ) + simp_all? [ dist_eq_norm ] + ( + rw [ eventually_nhdsWithin_iff ] at this + generalize_proofs at *; ( + rcases mem_nhds_prod_iff.mp this with ⟨ U, V, hU, hV, h ⟩ + generalize_proofs at *; ( + exact ⟨ interior U, isOpen_interior, interior hU, isOpen_interior, + mem_interior_iff_mem_nhds.mpr V, mem_interior_iff_mem_nhds.mpr hV, fun y hy hyS t + ht htT => h ( Set.mk_mem_prod ( interior_subset hy ) ( interior_subset ht ) ) ⟨ + hyS, htT ⟩ ⟩)))) + generalize_proofs at *; ( + choose U_i V_i hU_i hV_i hx₀_i hV_i_i h_cont_i using h_cont; exact ⟨ U_i, V_i, hU_i, + hV_i, hx₀_i, hV_i_i, h_cont_i ⟩;); + -- The open set W := ⋃ᵢ V_i contains spectrum(A x₀) (since each λᵢ ∈ V_i and + -- spectrum = range of eigenvalues). W is open as a union of open sets. + set W := ⋃ i, V_i i with hW_def + have hW_open : IsOpen W := by + exact isOpen_iUnion hV_i + have hW_spectrum : spectrum ℝ (A x₀).mat ⊆ W := by + intro t ht + obtain ⟨i, hi⟩ : ∃ i, t = (A x₀).H.eigenvalues i := by + have h_eigenvalues : spectrum ℝ (A x₀).mat = Set.range (A x₀).H.eigenvalues := + (A x₀).H.spectrum_real_eq_range_eigenvalues + generalize_proofs at *; ( + exact h_eigenvalues.subset ht |> Exists.imp fun i hi => hi.symm) + aesop + have hW_subset : ∀ᶠ B in nhds (A x₀), spectrum ℝ B.mat ⊆ W := by + exact spectrum_subset_of_isOpen (A x₀) W hW_open hW_spectrum + have hW_subset_S : ∀ᶠ y in nhdsWithin x₀ S, spectrum ℝ (A y).mat ⊆ W := by + exact Filter.mem_of_superset ( hA₂.continuousWithinAt hx₀ |> fun h => h.eventually ( + hW_subset ) ) fun y hy => hy + obtain ⟨U', hU'⟩ : ∃ U' ∈ nhds x₀, ∀ y ∈ U' ∩ S, spectrum ℝ (A y).mat ⊆ W := by + obtain ⟨ U', hU' ⟩ := mem_nhdsWithin_iff_exists_mem_nhds_inter.mp hW_subset_S; use U'; + aesop; + obtain ⟨U'', hU''⟩ : ∃ U'' ∈ nhds x₀, ∀ i, U'' ⊆ U_i i := by + exact ⟨ ⋂ i, U_i i, Filter.mem_of_superset ( Filter.iInter_mem.mpr fun i => + IsOpen.mem_nhds ( hU_i i ) ( h_cont.1 i ) ) fun x hx => by aesop, fun i => + Set.iInter_subset _ i ⟩ + set U := U' ∩ U'' with hU_def + have hU_mem : U ∈ nhds x₀ := by + exact Filter.inter_mem hU'.1 hU''.1 + have hU_subset : ∀ y ∈ U ∩ S, spectrum ℝ (A y).mat ⊆ W := by + exact fun y hy => hU'.2 y ⟨ hy.1.1, hy.2 ⟩ |> Set.Subset.trans <| by simp [ hW_def ]; + have hU_cont : ∀ y ∈ U ∩ S, ∀ t ∈ spectrum ℝ (A y).mat, ‖f y t - f x₀ t‖ < ε := by + intro y hy t ht + obtain ⟨i, hi⟩ : ∃ i, t ∈ V_i i := by + exact Set.mem_iUnion.mp ( hU_subset y hy ht ) |> Exists.imp fun i => by tauto; + have h_cont_i : ‖f y t - f x₀ t‖ < ε := by + exact h_cont.2.2 i y ⟨ hU''.2 i ( by aesop ), hy.2 ⟩ t ⟨ hi, hA₁ y hy.2 ht ⟩ |> fun h + => by simpa using h; + exact h_cont_i + exact h_contra ⟨U, hU_mem, hU_cont⟩ + +/- +PROBLEM +Joint continuity of the functional calculus, without requiring compactness of `T`. +This generalizes `continuous_cfc_joint_compact` by removing the `IsCompact T` hypothesis. +The compactness is unnecessary because the spectrum of a `HermitianMat` (which is +finite-dimensional) is always finite and hence compact. The proof works by reducing to +the compact case locally: at each point `x₀ ∈ S`, the spectrum of `A x₀` is finite and +contained in a compact interval `K = [-M, M]`, and the compact version is applied with a +continuous extension of `f x₀` from the finite spectrum to `K`. +PROVIDED SOLUTION +The proof follows the same structure as `continuous_cfc_joint_compact` but uses the non-compact + helpers `dist_lt_of_continuous_spectrum` and `continuousWithinAt_cfc_of_continuousOn` + instead of `dist_lt_of_continuous'` and `continuousOn_cfc_of_compact`. +Fix x ∈ S. Show ContinuousWithinAt. +Step 1: Show `ContinuousWithinAt (fun y => (A y).cfc (f x)) S x`. +The function f x (i.e., f(x, ·)) is continuous on T: this follows from `hf.uncurry_left x + x_in_S` which gives `ContinuousOn (f x) T`. +By `continuousWithinAt_cfc_of_continuousOn` with g = f x and T = T: + `ContinuousWithinAt (fun B ↦ B.cfc (f x)) {B | spectrum ℝ B.mat ⊆ T} (A x)`. +Compose with `hA₂.continuousWithinAt x_in_S` and `hA₁`: + `ContinuousWithinAt (fun y => (A y).cfc (f x)) S x`. +Note: We need that `fun y => A y` maps `S` into `{B | spectrum ℝ B.mat ⊆ T}`, which follows from + `hA₁`. +Step 2: Use the triangle inequality, exactly as in `continuous_cfc_joint_compact`: +Decompose the goal using: + dist ((A y).cfc (f y)) ((A x).cfc (f x)) ≤ dist ((A y).cfc (f y)) ((A y).cfc (f x)) + dist ((A + y).cfc (f x)) ((A x).cfc (f x)) +For the first term (f varies, A = A(y)): +Use `dist_lt_of_continuous_spectrum` (our new helper) to get: for any ε > 0, there exists U ∈ + nhds x such that for y ∈ U ∩ S and t ∈ spectrum(A y), ‖f y t - f x t‖ < ε. +Then by `norm_cfc_sub_le_of_sup_le`, ‖(A y).cfc (f y) - (A y).cfc (f x)‖ ≤ sqrt(d) * ε. +For the second term (A varies, f = f(x)): +Use step 1 directly (ContinuousWithinAt of B ↦ B.cfc (f x)). +Combine with the same nlinarith/ε-δ argument as in `continuous_cfc_joint_compact`. +In code, the proof structure should mirror continuous_cfc_joint_compact closely, just replacing: +- `dist_lt_of_continuous' hT hf x_in_S ε_pos` with `dist_lt_of_continuous_spectrum hf hA₁ hA₂ + x_in_S ε_pos` +- `continuousOn_cfc_of_compact hT (hf.uncurry_left x x_in_S)` with + `continuousWithinAt_cfc_of_continuousOn (hf.uncurry_left x x_in_S) (hA₁ x x_in_S)` composed + with hA₂ and hA₁. +-/ + +@[fun_prop] +theorem continuous_cfc_joint {X d : Type*} [TopologicalSpace X] [Fintype d] [DecidableEq d] + {f : X → ℝ → ℝ} {A : X → HermitianMat d ℂ} {S : Set X} {T : Set ℝ} + (hf : ContinuousOn (fun (p : X × ℝ) ↦ f p.1 p.2) (S ×ˢ T)) + (hA₁ : ∀ x ∈ S, spectrum ℝ (A x).mat ⊆ T) + (hA₂ : ContinuousOn (fun x ↦ A x) S) : + ContinuousOn (fun x ↦ (A x).cfc (f x)) S := by + by_contra h_not_cont_at_x₀ + generalize_proofs at *; + have h_cont : ∀ x₀ ∈ S, ContinuousWithinAt (fun x => (A x).cfc (f x)) S x₀ := by + intro x₀ hx₀ + have h_cont : ContinuousWithinAt (fun x => (A x).cfc (f x₀)) S x₀ := by + have h_cont : ContinuousWithinAt (fun B => B.cfc (f x₀)) {B | spectrum ℝ B.mat ⊆ T} (A + x₀) := by + apply_rules [ continuousWithinAt_cfc_of_continuousOn, hf.uncurry_left x₀ hx₀ ] + generalize_proofs at *; ( + exact h_cont.comp ( hA₂.continuousWithinAt hx₀ ) ( by aesop ) |> + ContinuousWithinAt.mono <| by aesop;) + generalize_proofs at *; ( + -- By the triangle inequality, we can bound the distance between $(A x).cfc (f + -- x)$ and $(A x₀).cfc (f x₀)$. + have h_triangle : ∀ᶠ x in nhdsWithin x₀ S, ‖(A x).cfc (f x) - (A x).cfc (f x₀)‖ ≤ + Real.sqrt (Fintype.card d) * (⨆ t ∈ spectrum ℝ (A x).mat, ‖f x t - f x₀ t‖) := by + refine Filter.Eventually.of_forall fun x => ?_; + exact norm_cfc_sub_cfc_le_sqrt_card + generalize_proofs at *; ( + -- By the properties of the supremum, we can bound the distance between $(A + -- x).cfc (f x)$ and $(A x₀).cfc (f x₀)$. + have h_sup : Filter.Tendsto (fun x => ⨆ t ∈ spectrum ℝ (A x).mat, ‖f x t - f x₀ t‖) + (nhdsWithin x₀ S) (nhds 0) := by + have h_sup : ∀ ε > 0, ∃ U ∈ nhdsWithin x₀ S, ∀ x ∈ U, ∀ t ∈ spectrum ℝ (A x).mat, ‖f x + t - f x₀ t‖ < ε := by + intro ε ε_pos + generalize_proofs at *; ( + have := dist_lt_of_continuous_spectrum hf hA₁ hA₂ hx₀ ε_pos + generalize_proofs at *; ( + obtain ⟨ U, hU₁, hU₂ ⟩ := this; exact ⟨ U ∩ S, + mem_nhdsWithin_iff_exists_mem_nhds_inter.mpr ⟨ U, hU₁, by simp ⟩, fun x hx t ht + => hU₂ x ⟨ hx.1, hx.2 ⟩ t ht ⟩;)) + generalize_proofs at *; ( + refine Metric.tendsto_nhds.mpr ?_; + intro ε ε_pos; rcases h_sup ( ε / 2 ) ( half_pos ε_pos ) with ⟨ U, hU₁, hU₂ ⟩; + filter_upwards [ hU₁ ] with x hx; simp_all? [ dist_eq_norm ]; ( + rw [ abs_of_nonneg ( Real.iSup_nonneg fun _ => Real.iSup_nonneg fun _ => abs_nonneg _ + ) ]; refine lt_of_le_of_lt ( ciSup_le fun t => ?_ ) ( half_lt_self ε_pos ); + by_cases ht : t ∈ spectrum ℝ ( A x |> HermitianMat.mat ) <;> simp_all? [ abs_lt ]; + · exact abs_le.mpr ⟨ by linarith [ hU₂ x hx t ht ], by linarith [ hU₂ x hx t ht ] ⟩; + · linarith [ ε_pos ])) + generalize_proofs at *; ( + have h_final : Filter.Tendsto (fun x => ‖(A x).cfc (f x) - (A x).cfc (f x₀)‖) + (nhdsWithin x₀ S) (nhds 0) := by + exact squeeze_zero_norm' ( by filter_upwards [ h_triangle ] with x hx; simpa using hx + ) ( by simpa using h_sup.const_mul _ ) |> fun h => h.trans ( by simp ); + generalize_proofs at *; ( + convert h_cont.add ( show ContinuousWithinAt ( fun x => ( A x |> HermitianMat.cfc ) ( f + x ) - ( A x |> HermitianMat.cfc ) ( f x₀ ) ) S x₀ from ?_ ) using 1 + · aesop + generalize_proofs at *; ( + change Filter.Tendsto + (fun x => (A x).cfc (f x) - (A x).cfc (f x₀)) (nhdsWithin x₀ S) + (nhds ((A x₀).cfc (f x₀) - (A x₀).cfc (f x₀))) + rw [sub_self] + exact tendsto_zero_iff_norm_tendsto_zero.mpr h_final;))))) + generalize_proofs at *; ( + exact h_not_cont_at_x₀ <| fun x hx => h_cont x hx |> ContinuousWithinAt.mono <| by simp;) + +/-- Specialization of `continuousOn_cfc_fun` for nonsingular matrices. -/ +@[fun_prop] +theorem continuousOn_cfc_fun_nonsingular {f : X → ℝ → ℝ} {S : Set X} + (hf : ∀ i ≠ 0, ContinuousOn (f · i) S) [NonSingular A] : + ContinuousOn (fun x ↦ A.cfc (f x)) S := by + apply continuousOn_cfc_fun (T := {0}ᶜ) + · exact hf + · grind [nonSingular_zero_notMem_spectrum] + +/-- Specialization of `continuousOn_cfc_fun` for positive semidefinite matrices. -/ +@[fun_prop] +theorem continuousOn_cfc_fun_nonneg {f : X → ℝ → ℝ} {S : Set X} + (hf : ∀ i ≥ 0, ContinuousOn (f · i) S) (hA : 0 ≤ A) : + ContinuousOn (fun x ↦ A.cfc (f x)) S := by + apply continuousOn_cfc_fun (T := Set.Ici 0) + · exact hf + · rw [zero_le_iff] at hA + exact hA.pos_of_mem_spectrum + +open ComplexOrder in +/-- Specialization of `continuousOn_cfc_fun` for positive definite matrices. -/ +@[fun_prop] +theorem continuousOn_cfc_fun_posDef {f : X → ℝ → ℝ} {S : Set X} + (hf : ∀ i > 0, ContinuousOn (f · i) S) (hA : A.mat.PosDef) : + ContinuousOn (fun x ↦ A.cfc (f x)) S := by + apply continuousOn_cfc_fun (T := Set.Ioi 0) + · exact hf + · exact Matrix.PosDef.spectrum_subset_Ioi hA + +variable {A B : HermitianMat d 𝕜} (f : ℝ → ℝ) + +/-- +The inverse of the CFC is the CFC of the inverse function. +-/ +lemma inv_cfc_eq_cfc_inv (hf : ∀ i, f (A.H.eigenvalues i) ≠ 0) : + (A.cfc f)⁻¹ = A.cfc (fun u ↦ (f u)⁻¹) := by + suffices (A.cfc f).mat⁻¹ = (A.cfc (fun u ↦ 1 / f u)).mat by + ext1 + simpa using this + have h_def : (A.cfc f).mat = ∑ i, f (A.H.eigenvalues i) • (A.H.eigenvectorUnitary.val * + (Matrix.single i i 1) * A.H.eigenvectorUnitary.val.conjTranspose) := by + exact cfc_toMat_eq_sum_smul_proj A f; + have h_subst : (A.cfc (fun u ↦ 1 / f u)).mat = ∑ i, (1 / f (A.H.eigenvalues i)) • + (A.H.eigenvectorUnitary.val * (Matrix.single i i 1) * + A.H.eigenvectorUnitary.val.conjTranspose) := by + exact cfc_toMat_eq_sum_smul_proj A fun u ↦ 1 / f u; + have h_inv : (A.cfc f).mat * (A.cfc (fun u ↦ 1 / f u)).mat = 1 := by + -- Since the eigenvectorUnitary is unitary, we have that the product of the projections + -- is the identity matrix. + have h_unitary : A.H.eigenvectorUnitary.val * A.H.eigenvectorUnitary.val.conjTranspose = 1 := by + simp [ Matrix.IsHermitian.eigenvectorUnitary ]; + have h_inv : ∀ i j, (A.H.eigenvectorUnitary.val * (Matrix.single i i 1) * + A.H.eigenvectorUnitary.val.conjTranspose) * (A.H.eigenvectorUnitary.val * (Matrix.single + j j 1) * A.H.eigenvectorUnitary.val.conjTranspose) = if i = j then + A.H.eigenvectorUnitary.val * (Matrix.single i i 1) * + A.H.eigenvectorUnitary.val.conjTranspose else 0 := by + simp? [ ← Matrix.mul_assoc ]; + intro i j; split_ifs <;> simp_all? [ Matrix.mul_assoc, mul_eq_one_comm.mp h_unitary ]; + simp_all? [ Finset.sum_mul, Finset.mul_sum ]; + have h_sum : ∑ i, (A.H.eigenvectorUnitary.val * (Matrix.single i i 1) * + A.H.eigenvectorUnitary.val.conjTranspose) = A.H.eigenvectorUnitary.val * (∑ i, + Matrix.single i i 1) * A.H.eigenvectorUnitary.val.conjTranspose := by + simp [ Finset.mul_sum, Finset.sum_mul, Matrix.mul_assoc ]; + simp_all? [ Matrix.single ]; + convert h_unitary using 2; + ext i j; simp? [ Matrix.mul_apply] + simp? [ Matrix.sum_apply, Finset.filter_eq', Finset.filter_and ]; + rw [ Finset.sum_eq_single j ] <;> aesop; + rw [ Matrix.inv_eq_right_inv h_inv ]; + +theorem cfc_inv [NonSingular A] : A.cfc (fun u ↦ u⁻¹) = A⁻¹ := by + simpa using (inv_cfc_eq_cfc_inv id nonSingular_eigenvalue_ne_zero).symm + +section integral + +open MeasureTheory +open scoped Matrix.Norms.Frobenius + +/-- The extended norm structure induced by the norm on Hermitian matrices. -/ +local instance : ENormedAddCommMonoid (HermitianMat d 𝕜) := + NormedAddCommGroup.toENormedAddCommMonoid + +/-- The extended norm structure induced by the Frobenius norm on matrices. -/ +local instance : ENormedAddCommMonoid (Matrix d d 𝕜) := + NormedAddCommGroup.toENormedAddCommMonoid + +omit [Fintype d] [DecidableEq d] in +local instance [Finite d] : TopologicalSpace.PseudoMetrizableSpace (Matrix d d 𝕜) := by + let : DecidableEq d := Classical.decEq d + let := Fintype.ofFinite d + exact PseudoEMetricSpace.pseudoMetrizableSpace + +omit [Fintype d] in +local instance [Finite d] : TopologicalSpace.PseudoMetrizableSpace (HermitianMat d 𝕜) := by + let := Fintype.ofFinite d + exact PseudoEMetricSpace.pseudoMetrizableSpace + +omit [DecidableEq d] in +/-- +The integral of a Hermitian matrix function commutes with `toMat`. +-/ +lemma integral_toMat (A : ℝ → HermitianMat d 𝕜) (T₁ T₂ : ℝ) {μ : Measure ℝ} + (hA : IntervalIntegrable A μ T₁ T₂) : + (∫ t in T₁..T₂, A t ∂μ).mat = ∫ t in T₁..T₂, (A t).mat ∂μ := by + classical + exact ((matₗ (R := ℝ)).intervalIntegral_comp_comm hA).symm + +omit [DecidableEq d] in +/-- +A sum of scaled constant matrices is integrable if the scalar functions are integrable. +-/ +lemma intervalIntegrable_sum_smul_const (T₁ T₂ : ℝ) {μ : Measure ℝ} (g : ℝ → d → ℝ) + (P : d → Matrix d d 𝕜) (hg : ∀ i, IntervalIntegrable (fun t ↦ g t i) μ T₁ T₂) : + IntervalIntegrable (fun t ↦ ∑ i, g t i • P i) μ T₁ T₂ := by + constructor + · exact integrable_finsetSum _ fun i _ ↦ (hg i).1.smul_const (P i) + · exact integrable_finsetSum _ fun i _ ↦ (hg i).2.smul_const (P i) + +omit [DecidableEq d] in +/-- +A function to Hermitian matrices is integrable iff its matrix values are integrable. +-/ +lemma intervalIntegrable_toMat_iff (A : ℝ → HermitianMat d 𝕜) (T₁ T₂ : ℝ) {μ : Measure ℝ} : + IntervalIntegrable (fun t ↦ (A t).mat) μ T₁ T₂ ↔ IntervalIntegrable A μ T₁ T₂ := by + classical + --TODO Cleanup + apply (intervalIntegrable_iff.trans ?_).trans intervalIntegrable_iff.symm + constructor <;> intro h; + -- The linear isometry `toMat` reflects integrability. + · have h_toMat_integrable : IntegrableOn (fun t ↦ (A t).mat) (Set.uIoc T₁ T₂) μ → IntegrableOn + A (Set.uIoc T₁ T₂) μ := by + intro h_toMat_integrable + have h_toMat_linear : ∃ (L : HermitianMat d 𝕜 →ₗ[ℝ] Matrix d d 𝕜), ∀ x, L x = x.mat := by + exact ⟨(matₗ (R := ℝ)).toLinearMap, fun _ ↦ rfl⟩ + obtain ⟨L, hL⟩ := h_toMat_linear; + have h_toMat_linear : IntegrableOn (fun t ↦ L (A t)) (Set.uIoc T₁ T₂) μ → IntegrableOn A + (Set.uIoc T₁ T₂) μ := by + intro h_toMat_integrable + have h_toMat_linear : ∃ (L_inv : Matrix d d 𝕜 →ₗ[ℝ] HermitianMat d 𝕜), ∀ x, L_inv (L x) + = x := by + have h_toMat_linear : Function.Injective L := by + intro x y hxy; + simp_all? only [HermitianMat.ext_iff] + have h_toMat_linear : ∃ (L_inv : Matrix d d 𝕜 →ₗ[ℝ] HermitianMat d 𝕜), L_inv.comp L = + LinearMap.id := by + exact IsSemisimpleModule.extension_property L h_toMat_linear LinearMap.id; + refine ⟨h_toMat_linear.choose, fun x => ?_⟩ + simpa using LinearMap.congr_fun h_toMat_linear.choose_spec x + obtain ⟨ L_inv, hL_inv ⟩ := h_toMat_linear; + have h_toMat_linear : IntegrableOn (fun t ↦ L_inv (L (A t))) (Set.uIoc T₁ T₂) μ := by + exact ContinuousLinearMap.integrable_comp ( L_inv.toContinuousLinearMap ) + h_toMat_integrable; + aesop; + aesop; + exact h_toMat_integrable h; + · apply h.norm.mono' + · have := h.aestronglyMeasurable; + fun_prop + · filter_upwards with t using le_rfl + +/-- +The CFC of an integrable function family is integrable. +-/ +lemma integrable_cfc (T₁ T₂ : ℝ) (f : ℝ → ℝ → ℝ) {μ : Measure ℝ} + (hf : ∀ i, IntervalIntegrable (fun t ↦ f t (A.H.eigenvalues i)) μ T₁ T₂) : + IntervalIntegrable (fun t ↦ A.cfc (f t)) μ T₁ T₂ := by + have h_expand : ∀ t, (A.cfc (f t)).mat = ∑ i, f t (A.H.eigenvalues i) • + (A.H.eigenvectorUnitary.val * (Matrix.single i i 1) * + A.H.eigenvectorUnitary.val.conjTranspose) := by + exact fun t ↦ cfc_toMat_eq_sum_smul_proj A (f t); + rw [ ← intervalIntegrable_toMat_iff ]; + rw [ funext h_expand ]; + apply intervalIntegrable_sum_smul_const + exact hf + +/-- +The integral of the CFC is the CFC of the integral. +-/ +lemma integral_cfc_eq_cfc_integral (T₁ T₂ : ℝ) {μ : Measure ℝ} (f : ℝ → ℝ → ℝ) + (hf : ∀ i, IntervalIntegrable (fun t ↦ f t (A.H.eigenvalues i)) μ T₁ T₂) : + ∫ t in T₁..T₂, A.cfc (f t) ∂μ = A.cfc (fun u ↦ ∫ t in T₁..T₂, f t u ∂μ) := by + ext1 + rw [ integral_toMat ]; + · rw [ intervalIntegral.integral_congr fun t ht ↦ + HermitianMat.cfc_toMat_eq_sum_smul_proj A ( f t ), intervalIntegral.integral_finsetSum ]; + · rw [ Finset.sum_congr rfl fun i _ ↦ intervalIntegral.integral_smul_const _ _ ]; + exact Eq.symm (cfc_toMat_eq_sum_smul_proj A fun u ↦ ∫ (t : ℝ) in T₁..T₂, f t u ∂μ); + · simp_all? [ intervalIntegrable_iff ]; + exact fun i ↦ ( hf i ).smul_const _ + · exact integrable_cfc T₁ T₂ f hf + +end integral + +theorem cfc_pos_of_pos {A : HermitianMat d 𝕜} {f : ℝ → ℝ} (hA : 0 < A) + (hf : ∀ i > 0, 0 < f i) (hf₂ : 0 ≤ f 0) : 0 < A.cfc f := by + have h_pos := (posSemidef_iff_spectrum_nonneg A).mp hA.le + have h_f_pos : ∃ x ∈ spectrum ℝ (A.cfc f).mat, x ≠ 0 := by + obtain ⟨ x, hx₁, hx₂ ⟩ := ne_zero_iff_ne_zero_spectrum A |>.1 hA.ne' + exact ⟨ f x, by simpa using HermitianMat.spectrum_cfc_eq_image A f ▸ Set.mem_image_of_mem f hx₁, + by cases lt_or_gt_of_ne hx₂ <;> + linarith [ hf x ( lt_of_le_of_ne ( h_pos x hx₁ ) ( Ne.symm hx₂ ) ) ] ⟩; + have h_f_nonneg : 0 ≤ A.cfc f := by + rw [HermitianMat.posSemidef_iff_spectrum_nonneg]; + rw [ HermitianMat.spectrum_cfc_eq_image ]; + rintro _ ⟨ x, hx, rfl ⟩; exact if hx0 : x = 0 then by + simpa [ hx0 ] using hf₂ else hf x ( lt_of_le_of_ne ( h_pos x hx ) ( Ne.symm hx0 ) ) |> + le_of_lt; + have h_f_nonzero : A.cfc f ≠ 0 := by + contrapose! h_f_pos; + simp [h_f_pos, spectrum.mem_iff, Matrix.isUnit_iff_isUnit_det, Algebra.algebraMap_eq_smul_one] + exact lt_of_le_of_ne h_f_nonneg h_f_nonzero.symm + +/-- If two matrices A and B commute, then they is a common matrix with which they are both CFCs of. +This is a variant of the common theorem that "commuting matrices can be simultaneously +diagonalized." -/ +theorem _root_.Commute.exists_HermitianMat_cfc (hAB : Commute A.mat B.mat) : + ∃ C : HermitianMat d 𝕜, (∃ f : ℝ → ℝ, A = C.cfc f) ∧ (∃ g : ℝ → ℝ, B = C.cfc g) := by + obtain ⟨C, ⟨g₁, hg₁⟩, ⟨g₂, hg₂⟩⟩ := hAB.exists_cfc A.H B.H + by_cases hC : C.IsHermitian + · use ⟨C, hC⟩ + constructor + · exact ⟨g₁, HermitianMat.ext hg₁⟩ + · exact ⟨g₂, HermitianMat.ext hg₂⟩ + · change ¬(IsSelfAdjoint C) at hC + rw [cfc_apply_of_not_predicate C hC] at hg₁ hg₂ + use 0 + constructor + · exact ⟨0, by simp [HermitianMat.ext_iff, hg₁]⟩ + · exact ⟨0, by simp [HermitianMat.ext_iff, hg₂]⟩ + +open ComplexOrder in +theorem cfc_le_cfc_of_PosDef (hfg : ∀ i, 0 < i → f i ≤ g i) (hA : A.mat.PosDef) : + A.cfc f ≤ A.cfc g := by + rw [← sub_nonneg, ← HermitianMat.cfc_sub, cfc_nonneg_iff] + intro i + rw [Pi.sub_apply, sub_nonneg] + rw [A.H.posDef_iff_eigenvalues_pos] at hA + apply hfg + apply hA + +open ComplexOrder in +variable {f} in +/- TODO: Write a version of this that holds more broadly for some sets. Esp closed intervals of + reals, +which correspond nicely to closed intervals of matrices. Write the specialization to Set.univ + (Monotone +instead of MonotoneOn). Also a version that works for StrictMonoOn. -/ +theorem cfc_le_cfc_of_commute_monoOn (hf : MonotoneOn f (Set.Ioi 0)) + (hAB₁ : Commute A.mat B.mat) (hAB₂ : A ≤ B) (hA : A.mat.PosDef) (hB : B.mat.PosDef) : + A.cfc f ≤ B.cfc f := by + obtain ⟨C, ⟨g₁, rfl⟩, ⟨g₂, rfl⟩⟩ := hAB₁.exists_HermitianMat_cfc + -- Need to show that g₁ ≤ g₂ on spectrum ℝ C + rw [← C.cfc_comp, ← C.cfc_comp] + rw [← sub_nonneg, ← C.cfc_sub, cfc_nonneg_iff] at hAB₂ ⊢ + intro i + simp only [Pi.sub_apply, Function.comp_apply, sub_nonneg] + apply hf + · rw [cfc_posDef] at hA + exact hA i + · rw [cfc_posDef] at hB + exact hB i + · simpa using hAB₂ i + +/-- TODO: See above -/ +theorem cfc_le_cfc_of_commute (hf : Monotone f) (hAB₁ : Commute A.mat B.mat) (hAB₂ : A ≤ B) : + A.cfc f ≤ B.cfc f := by + obtain ⟨C, ⟨g₁, rfl⟩, ⟨g₂, rfl⟩⟩ := hAB₁.exists_HermitianMat_cfc + -- Need to show that g₁ ≤ g₂ on spectrum ℝ C + rw [← C.cfc_comp, ← C.cfc_comp] + rw [← sub_nonneg, ← C.cfc_sub, cfc_nonneg_iff] at hAB₂ ⊢ + intro i + simp only [Pi.sub_apply, Function.comp_apply, sub_nonneg] + apply hf + simpa using hAB₂ i + +-- This is the more general version that requires operator concave functions but doesn't require +-- the inputs +-- to commute. Requires the correct statement of operator convexity though, which we don't have +-- right now. +open ComplexOrder in +theorem cfc_monoOn_pos_of_monoOn_posDef {d : Type*} [Fintype d] [DecidableEq d] + {f : ℝ → ℝ} (hf_is_operator_convex : False) : + MonotoneOn (HermitianMat.cfc · f) { A : HermitianMat d ℂ | A.mat.PosDef } := by + exact False.elim hf_is_operator_convex + +section uncategorized_cleanup + +open ComplexOrder in +theorem inv_ge_one_of_le_one (hA : A.mat.PosDef) (h : A ≤ 1) : 1 ≤ A⁻¹ := by + -- Since $A$ is positive definite and $A \leq 1$, we have $A.cfc (fun x => x⁻¹ - 1) \geq 0$. + have h_cfc_nonneg : 0 ≤ A.cfc (fun x => x⁻¹ - 1) := by + have h_cfc_nonneg : ∀ i, 0 ≤ (A.H.eigenvalues i)⁻¹ - 1 := by + have h_pos : ∀ i, 0 < A.H.eigenvalues i ∧ A.H.eigenvalues i ≤ 1 := by + -- Since $A$ is positive definite, all its eigenvalues are positive. + have h_pos : ∀ i, 0 < A.H.eigenvalues i := by + exact fun i => Matrix.PosDef.eigenvalues_pos hA i; + -- Since $A \leq 1$, for any eigenvalue $\lambda_i$ of $A$, we have $\lambda_i \leq 1$. + have h_le_one : ∀ i, A.H.eigenvalues i ≤ 1 := by + have h_le_one : ∀ i, A.H.eigenvalues i ≤ 1 := by + intro i + have h_eigenvalue : A.mat.PosSemidef := by + exact hA.posSemidef + have h_eigenvalue_le_one : ∀ x : d → 𝕜, x ≠ 0 → (star x ⬝ᵥ A.mat.mulVec x) / (star x + ⬝ᵥ x) ≤ 1 := by + intro x hx_ne_zero + have h_eigenvalue_le_one : (star x ⬝ᵥ (1 - A.mat).mulVec x) ≥ 0 := by + exact Matrix.PosSemidef.dotProduct_mulVec_nonneg h x + generalize_proofs at *; ( + rw [ div_le_iff₀ ] <;> simp_all? [ Matrix.sub_mulVec, dotProduct_sub ]) + generalize_proofs at *; ( + have := h_eigenvalue_le_one ( A.H.eigenvectorBasis i ) ?_ <;> simp_all? [ + div_le_iff₀, ]; + · have := Matrix.IsHermitian.mulVec_eigenvectorBasis ( show Matrix.IsHermitian ( A : + Matrix d d _ ) from ‹_› ) i; simp_all? [ dotProduct_comm ]; + by_cases h : ( A.H.eigenvectorBasis i |> WithLp.ofLp ) ⬝ᵥ star ( + A.H.eigenvectorBasis i |> WithLp.ofLp ) = 0 <;> simp_all? [ div_le_iff₀ ]; ( + exact absurd h ( by exact ne_of_apply_ne ( fun x => ‖x‖ ) ( by simp ) )); + · exact fun h => by simpa [ h ] using ( A.H.eigenvectorBasis.orthonormal.ne_zero i );) + generalize_proofs at *; ( + exact h_le_one) + exact fun i => ⟨h_pos i, h_le_one i⟩; + exact fun i => sub_nonneg_of_le ( one_le_inv₀ ( h_pos i |>.1 ) |>.2 ( h_pos i |>.2 ) ); + exact (cfc_nonneg_iff A fun x => x⁻¹ - 1).mpr h_cfc_nonneg; + -- Since $A.cfc (fun x => x⁻¹ - 1) \geq 0$, we have $A.cfc (fun x => x⁻¹) \geq 1$. + have h_cfc_ge_one : A.cfc (fun x => x⁻¹) ≥ 1 := by + have h_cfc_sub : A.cfc (fun x => x⁻¹ - 1) = A.cfc (fun x => x⁻¹) - A.cfc (fun _ => 1) := by + exact cfc_sub_apply A Inv.inv fun x => 1; + aesop; + convert h_cfc_ge_one.le using 1; + convert cfc_inv.symm; + exact nonSingular_of_posDef hA + +/-- The trace of cfc(f, A) equals the sum of f applied to eigenvalues. -/ +lemma trace_cfc_eq (A : HermitianMat d ℂ) (f : ℝ → ℝ) : + (A.cfc f).trace = ∑ i, f (A.H.eigenvalues i) := by + have h1 := HermitianMat.trace_eq_trace (A.cfc f) + obtain ⟨e, he⟩ := HermitianMat.cfc_eigenvalues f A + have h2 := (A.cfc f).H.trace_eq_sum_eigenvalues + rw [he] at h2 + simp? [Function.comp] at h2 + rw [HermitianMat.mat_cfc] at h1 + rw [h2] at h1 + have h3 : (Complex.ofReal) (A.cfc f).trace = Complex.ofReal (∑ i, f (A.H.eigenvalues (e i))) := by + convert! h1 using 1 + simp + have h4 := Complex.ofReal_injective h3 + rw [h4] + exact Equiv.sum_comp e (fun x => f (A.H.eigenvalues x)) + +end uncategorized_cleanup + +lemma mulVec_eq_zero_iff_inner_eigenvector_zero + (A : HermitianMat d ℂ) (x : EuclideanSpace ℂ d) : + A.mat.mulVec x = 0 ↔ ∀ i, A.H.eigenvalues i ≠ 0 → inner ℂ (A.H.eigenvectorBasis i) x = 0 := by + -- Since the eigenvectors form an orthonormal basis, we can express x as a linear + -- combination of these eigenvectors. + obtain ⟨c, hc⟩ : ∃ c : d → ℂ, x = ∑ i, c i • A.H.eigenvectorBasis i := by + have := A.H.eigenvectorBasis.sum_repr x; + exact ⟨ _, this.symm ⟩; + -- By definition of $A$, we know that $A.mulVec (x.ofLp) = \sum_{i} c_i \lambda_i e_i$. + have h_mulVec : A.val.mulVec (x.ofLp) = ∑ i, c i • (A.H.eigenvalues i) • (A.H.eigenvectorBasis + i) := by + have h_mulVec : ∀ i, A.val.mulVec (A.H.eigenvectorBasis i) = (A.H.eigenvalues i) • + (A.H.eigenvectorBasis i) := by + intro i + have := A.H.mulVec_eigenvectorBasis i + aesop; + convert congr_arg ( fun y => ( ∑ i, c i • y i ) ) ( funext fun i => h_mulVec i ) using 1; + · simp? [ hc, ]; + ext i; rw [ Matrix.mulVec, dotProduct ] + simp? [ Finset.mul_sum _ _ _, mul_assoc, mul_comm ] + rw [ Finset.sum_comm ] + simp [ Matrix.mulVec, dotProduct, mul_comm, Finset.mul_sum _ _ _ ] + · ext i; simp [ Finset.sum_apply ]; + constructor; + · intro h i hi + have h_inner : inner ℂ (A.H.eigenvectorBasis i) (∑ j, c j • (A.H.eigenvalues j) • + (A.H.eigenvectorBasis j)) = 0 := by + convert congr_arg ( fun x => inner ℂ ( A.H.eigenvectorBasis i ) x ) ( show ( ∑ j, c j • + A.H.eigenvalues j • A.H.eigenvectorBasis j ) = 0 from ?_ ) using 1; + · simp [ inner_zero_right ]; + · ext j; replace h := congr_fun h j; aesop; + simp_all? + convert congr_arg ( fun x : ℂ => x / ( A.H.eigenvalues i ) ) h_inner using 1 <;> norm_num [ + Finset.sum_div _ _ _, hi ]; + refine Finset.sum_congr rfl fun j _ => ?_; by_cases hj : A.H.eigenvalues j = 0 <;> simp_all? + [ mul_div_assoc ] + · ring_nf + by_cases hij : i = j <;> simp_all? [ ]; + · by_cases hij : i = j <;> simp_all? [ inner_self_eq_norm_sq_to_K ]; + · intro h + have h_zero_coeffs : ∀ i, A.H.eigenvalues i ≠ 0 → c i = 0 := by + intro i hi; specialize h i hi; simp_all? + rw [ Finset.sum_eq_single i ] at h <;> simp_all? [ orthonormal_iff_ite.mp ( + A.H.eigenvectorBasis.orthonormal ) ]; + aesop; + simp_all? + exact Finset.sum_eq_zero fun i _ => by by_cases hi : A.H.eigenvalues i = 0 <;> simp [ hi, + h_zero_coeffs i ]; + +open InnerProductSpace in +lemma cfc_mulVec_expansion (A : HermitianMat d ℂ) (f : ℝ → ℝ) (x : EuclideanSpace ℂ d) : + (A.cfc f).mat.mulVec x = ∑ i, (f (A.H.eigenvalues i) : ℂ) • inner ℂ (A.H.eigenvectorBasis i) + x • A.H.eigenvectorBasis i := by + ext i; simp [ Matrix.mulVec, dotProduct ]; ring_nf + -- By definition of $cfc$, we know that $(A.cfc f).i j = \sum_k f(\lambda_k) \langle e_k, + -- e_i \rangle \langle e_j, e_k \rangle$. + have h_cfc_def : (A.cfc f).mat i = ∑ k, f (A.H.eigenvalues k) • (A.H.eigenvectorBasis k).ofLp + i • star (A.H.eigenvectorBasis k).ofLp := by + -- By definition of $cfc$, we know that $(A.cfc f).i j = \sum_k f(\lambda_k) \langle + -- e_k, e_i \rangle \langle e_j, e_k \rangle$ follows directly from the definition of + -- $cfc$. + have h_cfc_def : (A.cfc f).mat = ∑ k, f (A.H.eigenvalues k) • (A.H.eigenvectorUnitary.val * + (Matrix.single k k 1) * A.H.eigenvectorUnitary.val.conjTranspose) := by + convert cfc_toMat_eq_sum_smul_proj A f using 1; + convert congr_fun h_cfc_def i using 1; + simp? [ funext_iff, Matrix.single ]; + simp? [ Matrix.mul_apply, Matrix.conjTranspose_apply, Matrix.sum_apply, mul_assoc ]; + intro x; congr; ext y; simp [ Finset.sum_ite, Finset.filter_eq, Finset.filter_and ]; ring_nf + rw [ Finset.sum_eq_single y ] <;> aesop; + simp_all? [mul_comm, mul_left_comm ]; ring_nf + convert! congr_arg ( fun y => ∑ j, x.ofLp j * y j ) h_cfc_def using 1 + simp [ Finset.mul_sum _ _ _, mul_assoc, mul_left_comm ] + ring_nf! + rw [ Finset.sum_comm, Finset.sum_congr rfl ] + intros + simp [ mul_assoc, inner ] + ring_nf! + simp only [Finset.mul_sum _ _ _, mul_assoc] + +section ker_cfc + +variable {A : HermitianMat d ℂ} {f : ℝ → ℝ} {s : Set ℝ} + +lemma ker_cfc_le_ker_on_set + (hs : spectrum ℝ A.mat ⊆ s) + (h : ∀ i ∈ s, f i = 0 → i = 0) : + (A.cfc f).ker ≤ A.ker := by + intro x hx + have h_inner : ∀ i, A.H.eigenvalues i ≠ 0 → inner ℂ (A.H.eigenvectorBasis i) x = 0 := by + have h_inner_zero : (A.cfc f).mat.mulVec x = 0 := by + exact (mem_ker_iff_mulVec_zero (A.cfc f) x).mp hx + have h_inner_zero_expansion : ∑ i, (f (A.H.eigenvalues i) : ℂ) • inner ℂ + (A.H.eigenvectorBasis i) x • A.H.eigenvectorBasis i = 0 := by + convert h_inner_zero using 1; + rw [ cfc_mulVec_expansion ]; + exact Iff.symm (WithLp.ofLp_eq_zero 2) + have h_inner_zero_coeff : ∀ i, f (A.H.eigenvalues i) • inner ℂ (A.H.eigenvectorBasis i) x = + 0 := by + intro i + have h_inner_zero_coeff_i : f (A.H.eigenvalues i) • inner ℂ (A.H.eigenvectorBasis i) x = + inner ℂ (A.H.eigenvectorBasis i) (∑ j, (f (A.H.eigenvalues j) : ℂ) • inner ℂ + (A.H.eigenvectorBasis j) x • A.H.eigenvectorBasis j) := by + simp [ orthonormal_iff_ite.mp ( A.H.eigenvectorBasis.orthonormal ) ] + rw [h_inner_zero_coeff_i, h_inner_zero_expansion] + simp [inner_zero_right] + have h_inner_zero_final : ∀ i, A.H.eigenvalues i ≠ 0 → inner ℂ (A.H.eigenvectorBasis i) x = + 0 := by + -- Since $A.H.eigenvalues i \neq 0$, by hypothesis $h$, we have $f(A.H.eigenvalues i) \neq 0$. + have h_f_nonzero : ∀ i, A.H.eigenvalues i ≠ 0 → f (A.H.eigenvalues i) ≠ 0 := by + intro i hi; specialize h ( A.H.eigenvalues i ) ( hs <| by + exact Matrix.IsHermitian.eigenvalues_mem_spectrum_real (H A) i ); contrapose! hi; aesop; + generalize_proofs at *; ( + exact fun i hi => by simpa [ h_f_nonzero i hi ] using h_inner_zero_coeff i;) + exact h_inner_zero_final; + convert mulVec_eq_zero_iff_inner_eigenvector_zero A x |>.2 h_inner using 1; + exact mem_ker_iff_mulVec_zero A x + +lemma ker_cfc_le_ker (h : ∀ i, f i = 0 → i = 0) : + (A.cfc f).ker ≤ A.ker := by + exact ker_cfc_le_ker_on_set (Set.subset_univ _) (by simpa using h) + +lemma ker_cfc_le_ker_nonneg (hA : 0 ≤ A) (h : ∀ i ≥ 0, f i = 0 → i = 0) : + (A.cfc f).ker ≤ A.ker := by + rw [posSemidef_iff_spectrum_Ici] at hA + exact ker_cfc_le_ker_on_set hA h + +lemma ker_le_ker_cfc_on_set (hs : spectrum ℝ A.mat ⊆ s) (h : ∀ i ∈ s, i = 0 → f i = 0) : + A.ker ≤ (A.cfc f).ker := by + intro x hx + have h_inner_zero : ∀ i, A.H.eigenvalues i ≠ 0 → inner ℂ (A.H.eigenvectorBasis i) x = 0 := by + intro i hi + have h_inner_zero : A.mat.mulVec x = 0 := by + exact (mem_ker_iff_mulVec_zero A x).mp hx; + have := mulVec_eq_zero_iff_inner_eigenvector_zero A x; aesop; + have h_mulVec_zero : (A.cfc f).mat.mulVec x = ∑ i, (f (A.H.eigenvalues i) : ℂ) • inner ℂ + (A.H.eigenvectorBasis i) x • A.H.eigenvectorBasis i := by + convert cfc_mulVec_expansion A f x using 1; + convert h_mulVec_zero using 1 + simp_all? [ funext_iff]; + ext i; specialize h_mulVec_zero i; simp_all? [ lin, Matrix.mulVec ]; + refine Finset.sum_eq_zero fun j _ => ?_ + by_cases hj : A.H.eigenvalues j = 0 <;> simp_all? + exact Or.inl ( h _ ( hs (Matrix.IsHermitian.eigenvalues_mem_spectrum_real (H A) j ) ) hj ) + +lemma ker_le_ker_cfc (h : ∀ i, i = 0 → f i = 0) : + A.ker ≤ (A.cfc f).ker := by + exact ker_le_ker_cfc_on_set (Set.subset_univ _) (by simpa using h) + +lemma ker_le_ker_cfc_nonneg (hA : 0 ≤ A) (h : ∀ i ≥ 0, i = 0 → f i = 0) : + A.ker ≤ (A.cfc f).ker := by + rw [posSemidef_iff_spectrum_Ici] at hA + exact ker_le_ker_cfc_on_set hA h + +theorem ker_cfc_eq_ker (h : ∀ i, f i = 0 ↔ i = 0) : + (A.cfc f).ker = A.ker := by + refine le_antisymm (ker_cfc_le_ker ?_) (ker_le_ker_cfc ?_) + <;> grind only + +theorem ker_cfc_eq_ker_nonneg (hA : 0 ≤ A) (h : ∀ i ≥ 0, f i = 0 ↔ i = 0) : + (A.cfc f).ker = A.ker := by + refine le_antisymm (ker_cfc_le_ker_nonneg hA ?_) (ker_le_ker_cfc_nonneg hA ?_) + <;> grind only + +end ker_cfc +end CFC + + +end HermitianMat diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/HermitianMat/Inner.lean b/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/HermitianMat/Inner.lean new file mode 100644 index 0000000000..0ec648bd3a --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/HermitianMat/Inner.lean @@ -0,0 +1,554 @@ +/- +Copyright (c) 2025 Alex Meiburg. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Alex Meiburg +-/ +module + +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.Order +public import Mathlib.Analysis.Convex.Contractible +public import Mathlib.Topology.Instances.Real.Lemmas + +/-! # Inner product of Hermitian Matrices + +For general matrices there are multiple reasonable notions of "inner product" (Hilbert–Schmidt +inner product, +Frobenius inner product), and so Mathlib avoids giving a canonical `InnerProductSpace` instance. + But for the +particular case of Hermitian matrices, these all coincide, so we can put a canonical + `InnerProductSpace` +instance. + +This _does_ however induce a `Norm` on `HermitianMat` as well, the Frobenius norm, and this is + less obviously +a uniquely correct choice. It is something that one essentially has to live with, with the way + that Mathlib +currently structures the instances. (Thankfully, all norms induce the same _topology and + bornology_ on +finite-dimensional matrices.) + +Some care to be taken so that the topology induced by the InnerProductSpace is defeq with the + Subtype +topology that HermitianMat inherits from the topology on Matrix. This can be done via +`InnerProductSpace.ofCoreOfTopology`. + +-/ + +@[expose] public section + +namespace HermitianMat + +variable {R n α : Type*} [Star R] [TrivialStar R] [Fintype n] +open scoped InnerProductSpace RealInnerProductSpace +open IsMaximalSelfAdjoint + +section defs + +variable [Ring α] [StarAddMonoid α] [CommSemiring R] [Algebra R α] [IsMaximalSelfAdjoint R α] + +/-- The Hermitian inner product, `Tr[AB]`. This is equal to `Matrix.trace (A * B)`, but gives real + values when the matrices are complex, using `IsMaximalSelfAdjoint`. -/ +instance : Inner R (HermitianMat n α) where + inner A B := selfadjMap (A.mat * B.mat).trace + +theorem inner_def (A B : HermitianMat n α) : + ⟪A, B⟫_R = selfadjMap (A.mat * B.mat).trace := by + rfl + +end defs +section semiring + +--We necessarily re-state and re-prove many of the theorems from InnerProductSpace/Basic.lean, +--because our inner product happens outside of just an `InnerProductSpace` instance. + +variable [CommSemiring R] [Ring α] [StarAddMonoid α] [Algebra R α] [IsMaximalSelfAdjoint R α] +variable (A B C : HermitianMat n α) + +protected theorem inner_add_right : ⟪A, B + C⟫_R = ⟪A, B⟫_R + ⟪A, C⟫_R := by + simp [inner_def, left_distrib] + +protected theorem inner_add_left : ⟪A + B, C⟫_R = ⟪A, C⟫_R + ⟪B, C⟫_R := by + simp [inner_def, right_distrib] + +@[simp] +protected theorem inner_zero_right : ⟪A, 0⟫_R = 0 := by + simp [inner_def] + +@[simp] +protected theorem inner_zero_left : ⟪0, A⟫_R = 0 := by + simp [inner_def] + +end semiring +section ring + +variable [CommRing R] [Ring α] [StarAddMonoid α] [Algebra R α] [IsMaximalSelfAdjoint R α] +variable (A B C : HermitianMat n α) + +@[simp] +protected theorem inner_neg_left : ⟪-A, B⟫_R = -⟪A, B⟫_R := by + simp [inner_def] + +@[simp] +protected theorem inner_neg_right : ⟪A, -B⟫_R = -⟪A, B⟫_R := by + simp [inner_def] + +protected theorem inner_sub_left : ⟪A, B - C⟫_R = ⟪A, B⟫_R - ⟪A, C⟫_R := by + simp [inner_def, mul_sub] + +protected theorem inner_sub_right : ⟪A - B, C⟫_R = ⟪A, C⟫_R - ⟪B, C⟫_R := by + simp [inner_def, sub_mul] + +variable [StarModule R α] + +@[simp] +protected theorem inner_smul_left (r : R) : ⟪r • A, B⟫_R = r * ⟪A, B⟫_R := by + simp [inner_def, selfadj_smul] + +@[simp] +protected theorem inner_smul_right (r : R) : ⟪A, r • B⟫_R = r * ⟪A, B⟫_R := by + simp [inner_def, selfadj_smul] + +/-- The Hermitian inner product as bilinear form. Compare with `innerₗ` (in the root namespace) +which requires an `InnerProductSpace` instance. -/ +protected def innerₗ : LinearMap.BilinForm R (HermitianMat n α) where + toFun A := { + toFun := (⟪A, ·⟫_R) + map_add' := A.inner_add_right + map_smul' r B := by simp + } + map_add' A B := by ext1; apply A.inner_add_left B + map_smul' A B := by ext1; simp + +end ring +section starring + +variable [CommSemiring R] [Ring α] [StarRing α] [Algebra R α] [IsMaximalSelfAdjoint R α] + [DecidableEq n] +variable (A B : HermitianMat n α) + +@[simp] +theorem inner_one : ⟪A, 1⟫_R = A.trace := by + simp only [inner_def, mat_one, mul_one, trace] + +@[simp] +theorem one_inner : ⟪1, A⟫_R = A.trace := by + simp only [inner_def, one_mul, mat_one, trace] + +end starring +section commring + +variable [CommSemiring R] [CommRing α] [StarRing α] [Algebra R α] [IsMaximalSelfAdjoint R α] +variable (A B : HermitianMat n α) + +/-- The inner product for Hermtian matrices is equal to the trace of the product. -/ +theorem inner_eq_trace_mul : algebraMap R α ⟪A, B⟫_R = (A.mat * B.mat).trace := by + apply IsMaximalSelfAdjoint.selfadj_algebra + rw [IsSelfAdjoint, Matrix.trace] + simp_rw [star_sum, Matrix.diag_apply, Matrix.mul_apply, star_sum, star_mul, mul_comm] + rw [Finset.sum_comm] + congr! <;> apply congrFun₂ (H _) + +theorem inner_comm : ⟪A, B⟫_R = ⟪B, A⟫_R := by + rw [inner_def, inner_def, Matrix.trace_mul_comm] + +end commring + +section trivialstar +variable [CommRing α] [StarRing α] [TrivialStar α] +variable (A B : HermitianMat n α) + +/-- `HermitianMat.inner` reduces to `Matrix.trace (A * B)` when the elements are a `TrivialStar`. -/ +theorem inner_eq_trace_trivial : ⟪A, B⟫_α = (A.mat * B.mat).trace := by + rw [← inner_eq_trace_mul] + rfl + +end trivialstar + +section RCLike + +open ComplexOrder + +variable {n 𝕜 : Type*} [Fintype n] [RCLike 𝕜] (A B C : HermitianMat n 𝕜) + +theorem inner_eq_re_trace : ⟪A, B⟫ = RCLike.re (A.mat * B.mat).trace := by + rfl + +theorem inner_eq_trace_rc : ⟪A, B⟫ = (A.mat * B.mat).trace := by + rw [inner_eq_re_trace, ← RCLike.conj_eq_iff_re] + convert! (Matrix.trace_conjTranspose (A.mat * B.mat)).symm using 1 + rw [Matrix.conjTranspose_mul, A.H, B.H, Matrix.trace_mul_comm] + +theorem inner_self_nonneg : 0 ≤ ⟪A, A⟫ := by + simp_rw [inner_eq_re_trace, Matrix.trace, Matrix.diag, Matrix.mul_apply, map_sum] + refine Finset.sum_nonneg fun i _ ↦ Finset.sum_nonneg fun j _ ↦ ?_ + rw [← congrFun₂ A.H, Matrix.conjTranspose_apply] + refine And.left <| RCLike.nonneg_iff.mp ?_ + open ComplexOrder in + exact star_mul_self_nonneg (A.mat j i) + +variable {A B C} + +open MatrixOrder in +theorem inner_mul_nonneg (h : 0 ≤ A.mat * B.mat) : 0 ≤ ⟪A, B⟫ := by + rw [Matrix.nonneg_iff_posSemidef] at h + exact (RCLike.nonneg_iff.mp h.trace_nonneg).left + +/-- The inner product for PSD matrices is nonnegative. -/ +theorem inner_ge_zero (hA : 0 ≤ A) (hB : 0 ≤ B) : 0 ≤ ⟪A, B⟫ := by + rw [zero_le_iff] at hB + open MatrixOrder in + open Classical in + rw [inner_eq_re_trace, ← CFC.sqrt_mul_sqrt_self A.mat hA, Matrix.trace_mul_cycle, + Matrix.trace_mul_cycle] + nth_rewrite 1 [← (Matrix.nonneg_iff_posSemidef.mp (CFC.sqrt_nonneg A.mat)).left] + exact (RCLike.nonneg_iff.mp (hB.conjTranspose_mul_mul_same _).trace_nonneg).left + +theorem inner_mono (hA : 0 ≤ A) : B ≤ C → ⟪A, B⟫ ≤ ⟪A, C⟫ := by + intro hBC + classical have hTr : 0 ≤ ⟪A, C - B⟫ := inner_ge_zero hA (zero_le_iff.mpr hBC) + simpa [inner_def, mul_sub] using hTr + +theorem inner_mono' (hA : 0 ≤ A) : B ≤ C → ⟪B, A⟫ ≤ ⟪C, A⟫ := by + intro hBC + rw [inner_comm B A, inner_comm C A] + exact inner_mono hA hBC + +/-- The inner product for PSD matrices is at most the product of their traces. -/ +theorem inner_le_mul_trace (hA : 0 ≤ A) (hB : 0 ≤ B) : ⟪A, B⟫ ≤ A.trace * B.trace := by + classical convert inner_mono hA (le_trace_smul_one hB) + simp [mul_comm] + +--TODO cleanup +private theorem inner_zero_iff_aux_lemma [DecidableEq n] (hA₁ : A.mat.PosSemidef) (hB₁ : + B.mat.PosSemidef) : + RCLike.re (A.val * B.val).trace = 0 ↔ + LinearMap.range (Matrix.toEuclideanLin A.val) ≤ + LinearMap.ker (Matrix.toEuclideanLin B.val) := by + -- The kernel inclusion `range ≤ ker` says `(toEuclideanLin B) ∘ (toEuclideanLin A) = 0`, i.e. + -- `B * A = 0`, which by Hermitianness of `A` and `B` is `A * B = 0`. + rw [LinearMap.range_le_ker_iff, + show Matrix.toEuclideanLin B.val ∘ₗ Matrix.toEuclideanLin A.val + = Matrix.toEuclideanLin (B.val * A.val) from by + ext v; simp [Matrix.toLpLin_apply, Matrix.mulVec_mulVec, Matrix.toEuclideanLin], + LinearEquiv.map_eq_zero_iff, + show B.val * A.val = (A.val * B.val).conjTranspose from by simp [Matrix.conjTranspose_mul], + Matrix.conjTranspose_eq_zero] + -- Factoring `A = Cᴴ C` and `B = Dᴴ D`, a cyclic trace identity gives `tr (A * B) = tr (Eᴴ E)` + -- where `E = D Cᴴ`, so `re (tr (A * B)) = 0` forces `E = 0` and hence `A * B = 0`. + open MatrixOrder in + obtain ⟨C, hC⟩ : ∃ C : Matrix n n 𝕜, A.val = C.conjTranspose * C := + CStarAlgebra.nonneg_iff_eq_star_mul_self.mp (Matrix.nonneg_iff_posSemidef.mpr hA₁) + open MatrixOrder in + obtain ⟨D, hD⟩ : ∃ D : Matrix n n 𝕜, B.val = D.conjTranspose * D := + CStarAlgebra.nonneg_iff_eq_star_mul_self.mp (Matrix.nonneg_iff_posSemidef.mpr hB₁) + have htr : (A.val * B.val).trace + = ((D * C.conjTranspose).conjTranspose * (D * C.conjTranspose)).trace := by + rw [hC, hD, Matrix.conjTranspose_mul, Matrix.conjTranspose_conjTranspose, + show C.conjTranspose * C * (D.conjTranspose * D) + = C.conjTranspose * (C * D.conjTranspose * D) from by simp [Matrix.mul_assoc], + Matrix.trace_mul_comm, + show C * D.conjTranspose * D * C.conjTranspose + = C * D.conjTranspose * (D * C.conjTranspose) from by simp [Matrix.mul_assoc]] + refine ⟨fun h => ?_, fun h => by rw [h]; simp⟩ + have hE0 : D * C.conjTranspose = 0 := + Matrix.trace_conjTranspose_mul_self_eq_zero_iff.mp <| RCLike.ext + (by rw [← htr]; simpa using h) + (by simpa using (RCLike.nonneg_iff.mp + (Matrix.posSemidef_conjTranspose_mul_self (D * C.conjTranspose)).trace_nonneg).2) + have hE0' : C * D.conjTranspose = 0 := by + simpa [Matrix.conjTranspose_mul] using congrArg Matrix.conjTranspose hE0 + rw [hC, hD, show C.conjTranspose * C * (D.conjTranspose * D) + = C.conjTranspose * (C * D.conjTranspose) * D from by simp [Matrix.mul_assoc], hE0'] + simp + +/-- The inner product of two PSD matrices is zero iff they have disjoint support, i.e., each +lives entirely +in the other's kernel. -/ +theorem inner_zero_iff [DecidableEq n] (hA₁ : 0 ≤ A) (hB₁ : 0 ≤ B) + : ⟪A, B⟫ = 0 ↔ A.support ≤ B.ker := by + rw [zero_le_iff] at hA₁ hB₁ + rw [inner_eq_re_trace] + exact inner_zero_iff_aux_lemma hA₁ hB₁ + +variable {d d₂ : Type*} (A B : HermitianMat d 𝕜) [Fintype d₂] [Fintype d] + +@[simp] +theorem reindex_inner (e : d ≃ d₂) (B : HermitianMat d₂ 𝕜) : + ⟪A.reindex e, B⟫ = ⟪A, B.reindex e.symm⟫ := by + simp only [inner_def, mat_reindex, Matrix.reindex_apply, Equiv.symm_symm] + congr + rw (occs := [3,4]) [← e.symm_symm] + rw [← Matrix.submatrix_id_mul_right] + rw (occs := [2]) [Matrix.trace_mul_comm] + rw [Matrix.submatrix_id_mul_right, Matrix.trace_mul_comm, Equiv.symm_symm] + +end RCLike + +section topology +/-! +Theorems about `HermitianMat`s that have to do with the topological structure. Pretty much + everything here will +assume these are matrices over ℂ, but changes to upgrade this to other types are welcome. +-/ +open ComplexOrder + +variable {d : Type*} [Fintype d] {𝕜 : Type*} [RCLike 𝕜] + +--Check that it synthesizes ok + +@[fun_prop] +theorem inner_continuous : Continuous (Inner.inner ℝ (E := HermitianMat d 𝕜)) := by + rw [funext₂ inner_eq_re_trace] + fun_prop + +@[fun_prop] --fun_prop can actually prove this, should I leave this on or not? +theorem inner_bilinForm_Continuous (A : HermitianMat d 𝕜) : Continuous ⇑(HermitianMat.innerₗ A) := + LinearMap.continuous_of_finiteDimensional _ + +end topology + +section innerproductspace + +variable {d d₂ : Type*} [Fintype d] [Fintype d₂] {𝕜 : Type*} [RCLike 𝕜] + +/-- We define the Hermitian inner product as our "canonical" inner product, which does induce a +norm. +This disagrees slightly with Mathlib convention on the `Matrix` type, which avoids asserting one + norm +as there are several reasonable ones; for Hermitian matrices, though, this seem to be the right + choice. -/ +@[reducible] +noncomputable def InnerProductCore : InnerProductSpace.Core ℝ (HermitianMat d 𝕜) := + { + inner A B := ⟪A, B⟫ + conj_inner_symm := fun x y ↦ by + simpa using inner_comm y x + re_inner_nonneg := inner_self_nonneg + add_left := by simp [inner, add_mul] + smul_left x y r := by simp + definite x h := by + replace h : ∑ j, ∑ i, (RCLike.re (x i j) ^ 2 + RCLike.im (x i j) ^ 2) = 0 := by + convert h + simp only [inner_eq_re_trace, Matrix.trace, Matrix.diag_apply, Matrix.mul_apply, map_sum, + RCLike.mul_re, sub_eq_add_neg] + congr! 2 with i _ j + simp only [Matrix.conjTranspose_apply, ← congrFun₂ x.H i j] + simp [pow_two] + ext i j + rw [Fintype.sum_eq_zero_iff_of_nonneg (fun i ↦ by positivity)] at h + replace h := congrFun h j + rw [Pi.zero_apply, Fintype.sum_eq_zero_iff_of_nonneg (fun i ↦ by positivity)] at h + replace h := congrFun h i + rw [Pi.zero_apply] at h + rw [add_eq_zero_iff_of_nonneg (by positivity) (by positivity), sq_eq_zero_iff, + sq_eq_zero_iff] at h + apply RCLike.ext (h.left.trans RCLike.zero_re.symm) (h.right.trans (map_zero _).symm) + } + +open Matrix.Norms.Frobenius in +/-- The `HermitianMat` type inherits the Frobenius necessarily, since it's going to need the +Hermitian inner product, and in Mathlib an `InnerProductSpace` always carries the corresponding +norm. -/ +noncomputable instance instNormedGroup : NormedAddCommGroup (HermitianMat d 𝕜) := + AddSubgroupClass.normedAddCommGroup _ + +theorem norm_eq_frobenius (A : HermitianMat d 𝕜) : + ‖A‖ = (∑ i : d, ∑ j : d, ‖A i j‖ ^ 2) ^ (1 / 2 : ℝ) := by + convert! ← Matrix.frobenius_norm_def A.mat + exact Real.rpow_ofNat _ 2 + +theorem norm_eq_sqrt_inner_self (A : HermitianMat d 𝕜) : ‖A‖ = √(⟪A, A⟫) := by + rw [norm_eq_frobenius, ← Real.sqrt_eq_rpow] + congr + simp_rw [inner_eq_re_trace, Matrix.trace, Matrix.diag, Matrix.mul_apply] + simp only [map_sum] + congr! with i _ j _ + simp only [RCLike.norm_sq_eq_def, RCLike.mul_re, sub_eq_add_neg, + neg_mul_eq_mul_neg] + congr 2 <;> (rw [← A.H]; simp) + +noncomputable instance instNormedSpace : NormedSpace ℝ (HermitianMat d 𝕜) where + norm_smul_le r x := by + simp [norm_eq_sqrt_inner_self, ← mul_assoc, Real.sqrt_mul', + inner_self_nonneg, Real.sqrt_mul_self_eq_abs] + +noncomputable instance instInnerProductSpace : InnerProductSpace ℝ (HermitianMat d 𝕜) := + letI : Inner ℝ (HermitianMat d 𝕜) := InnerProductCore.toInner; + letI : NormedSpace ℝ (HermitianMat d 𝕜) := instNormedSpace; + { InnerProductCore with + norm_sq_eq_re_inner := fun x => by + rw [norm_eq_sqrt_inner_self, Real.sq_sqrt (inner_self_nonneg x), RCLike.re_to_real] + } + +instance : CompleteSpace (HermitianMat d 𝕜) := + inferInstance + +--Shortcut instances +noncomputable instance : NormedAddCommGroup (HermitianMat d ℝ) := + inferInstance + +noncomputable instance : NormedAddCommGroup (HermitianMat d ℂ) := + inferInstance + +--PR'ed in #35056 +open ComplexOrder in +lemma _root_.RCLike.instOrderClosed : OrderClosedTopology 𝕜 where + isClosed_le' := by + conv => enter [1, 1, p]; rw [RCLike.le_iff_re_im] + simp_rw [Set.ofPred_and] + refine IsClosed.inter (isClosed_le ?_ ?_) (isClosed_eq ?_ ?_) <;> continuity + +scoped[ComplexOrder] attribute [instance] RCLike.instOrderClosed + +variable (A B : HermitianMat d 𝕜) + +variable {A B} in +theorem dist_le_of_mem_Icc (x : HermitianMat d 𝕜) (hA : A ≤ x) (hB : x ≤ B) : + ‖x - A‖ ≤ ‖B - A‖ := by + classical + conv => enter [2, 1]; equals (B - x) + (x - A) => abel + rw [← sq_le_sq₀ (norm_nonneg _) (norm_nonneg _)] + rw [norm_add_pow_two_real, le_add_iff_nonneg_left] + suffices 0 ≤ ⟪B - x, x - A⟫ by positivity + apply inner_ge_zero <;> rwa [sub_nonneg] + +omit [Fintype n] in +theorem Matrix.IsHermitian_isClosed : IsClosed { A : Matrix n n 𝕜 | A.IsHermitian } := by + conv => + enter [1, 1, A] + rw [Matrix.IsHermitian, ← sub_eq_zero] + convert! isClosed_singleton.preimage (f := fun (x : Matrix n n 𝕜) ↦ (x.conjTranspose - x)) + (by fun_prop) using 1 + +open ComplexOrder + +omit [Fintype n] in +theorem Matrix.PosSemiDef_isClosed [Finite n] : IsClosed { A : Matrix n n 𝕜 | A.PosSemidef } := by + classical + let := Fintype.ofFinite n + rw [show { A : Matrix n n 𝕜 | A.PosSemidef } = { A | A.IsHermitian } ∩ { A | ∀ x : n → 𝕜, 0 ≤ + star x ⬝ᵥ A.mulVec x } from by + ext A; simp [Matrix.posSemidef_iff_dotProduct_mulVec]] + refine IsHermitian_isClosed.inter ?_ + suffices IsClosed (⋂ x : n → 𝕜, { A : Matrix n n 𝕜 | 0 ≤ star x ⬝ᵥ A.mulVec x }) by + rwa [← Set.ofPred_forall] at this + exact isClosed_iInter fun _ ↦ (isClosed_Ici (a := 0)).preimage (by fun_prop) + +omit [Fintype n] in +theorem isClosed_nonneg [Finite n] : IsClosed { A : HermitianMat n 𝕜 | 0 ≤ A } := by + classical + let := Fintype.ofFinite n + simp_rw [zero_le_iff] + exact Matrix.PosSemiDef_isClosed.preimage_val + +-- TODO: The PosDef matrices are open *within* the HermitianMat space (not in the ambient space +-- of matrices.) + +omit [Fintype d] in +instance [Finite d] : OrderClosedTopology (HermitianMat d 𝕜) where + isClosed_le' := by + classical + let := Fintype.ofFinite d + convert IsClosed.preimage (X := (HermitianMat d 𝕜 × HermitianMat d 𝕜)) + (f := fun xy ↦ (xy.2 - xy.1)) (by fun_prop) isClosed_nonneg + ext ⟨x, y⟩ + simp only [Set.mem_ofPred_eq, Set.mem_preimage, ← sub_nonneg (b := x)] + +omit [Fintype d] in +/-- The matrices `X` such that `X - A` and `B - X` are PSD form a compact set. -/ +instance [Finite d] : CompactIccSpace (HermitianMat d 𝕜) where + isCompact_Icc := by + let := Fintype.ofFinite d + intros A B + have hclosed : IsClosed (Set.Icc A B) := isClosed_Icc + apply Metric.isCompact_of_isClosed_isBounded hclosed + rw [Metric.isBounded_iff] + use 2 * ‖B - A‖ + rintro x ⟨hxA, hxB⟩ y ⟨hyA, hyB⟩ + grw [dist_triangle_right (z := A), dist_eq_norm, dist_eq_norm] + grw [dist_le_of_mem_Icc x hxA hxB, dist_le_of_mem_Icc y hyA hyB] + rw [two_mul] + +variable [DecidableEq d] + +omit [Fintype d] in +/-- The PSD matrices that are `≤ 1` are a compact set. More generally, this is true of any +closed interval, +but stating that is a bit different because of how numerals are treated. The `0` and `1` here + are already +directly matrices, putting in an `(a : ℝ) • 1 ≤ m ∧ m ≤ (b : ℝ) • 1` involves casts. But that + theorem should follow +easily from this. More generally `A ≤ m ∧ m ≤ B` is compact. +-/ +theorem unitInterval_IsCompact [Finite d] : IsCompact {m : HermitianMat d 𝕜 | 0 ≤ m ∧ m ≤ 1} := by + classical + let := Fintype.ofFinite d + exact + CompactIccSpace.isCompact_Icc + +@[simp] +theorem norm_one : ‖(1 : HermitianMat d 𝕜)‖ = √(Fintype.card d : ℝ) := by + rw [norm_eq_sqrt_real_inner (F := HermitianMat d 𝕜)] + congr 1 + simp [-inner_self_eq_norm_sq_to_K, inner_def] + +theorem norm_eq_trace_sq : ‖A‖ ^ 2 = (A.mat ^ 2).trace := by + rw [norm_eq_frobenius, ← RCLike.ofReal_pow, ← Real.rpow_two, ← Real.rpow_mul (by positivity)] + simp only [one_div, ne_eq, OfNat.ofNat_ne_zero, not_false_eq_true, inv_mul_cancel₀, Real.rpow_one] + simp only [sq A.mat, map_sum, map_pow, Matrix.trace, Matrix.diag_apply, Matrix.mul_apply, + mat_apply] + congr! with i _ j _ + rw [← star_star (A j i), ← A.mat_apply (i := j)] + rw [← A.mat.conjTranspose_apply j i, A.H, eq_comm] + exact RCLike.mul_conj (A.mat i j) + +end innerproductspace + +--TODO: Cleanup, ew what? +/-- +The inner product ⟪A, B⟫ equals ∑_{ij} a_i b_j w_{ij} where a_i, b_j are eigenvalues +and w_{ij} = ‖C_{ij}‖² for C = U_A^* U_B unitary. +-/ +lemma inner_eq_doubly_stochastic_sum {d : Type*} [Fintype d] [DecidableEq d] + (A B : HermitianMat d ℂ) : + let C := A.H.eigenvectorUnitary.val.conjTranspose * B.H.eigenvectorUnitary.val + ⟪A, B⟫_ℝ = ∑ i, ∑ j, + A.H.eigenvalues i * B.H.eigenvalues j * (‖C i j‖^2) := by + intro C + set U : Matrix d d ℂ := A.H.eigenvectorUnitary.val + set V : Matrix d d ℂ := B.H.eigenvectorUnitary.val + -- `U`, `V` are unitary and diagonalize `A`, `B`; `C = Uᴴ V`. + have hUU : star U * U = 1 := A.H.eigenvectorUnitary.2.1 + have hUU' : U * star U = 1 := A.H.eigenvectorUnitary.2.2 + have hAeq : A.mat = U * Matrix.diagonal (RCLike.ofReal ∘ A.H.eigenvalues) * star U := by + simpa [Unitary.conjStarAlgAut_apply] using A.H.spectral_theorem + have hBeq : B.mat = V * Matrix.diagonal (RCLike.ofReal ∘ B.H.eigenvalues) * star V := by + simpa [Unitary.conjStarAlgAut_apply] using B.H.spectral_theorem + have hC : C = star U * V := rfl + have hCH : C.conjTranspose = star V * U := by + simp [hC, Matrix.conjTranspose_mul, Matrix.star_eq_conjTranspose] + -- Conjugating `A * B` by `U` turns it into a product of two diagonals and `C`, `Cᴴ`. + have key : A.mat * B.mat = + U * (Matrix.diagonal (RCLike.ofReal ∘ A.H.eigenvalues) * C * + Matrix.diagonal (RCLike.ofReal ∘ B.H.eigenvalues) * C.conjTranspose) * star U := by + conv_lhs => rw [hAeq, hBeq] + rw [hCH, hC] + simp only [Matrix.mul_assoc, hUU', mul_one] + have hC_norm : ∀ i j, (‖C i j‖ ^ 2 : ℂ) = C i j * star (C i j) := fun i j => by + simp [Complex.mul_conj, Complex.normSq_eq_norm_sq] + -- The trace of the diagonal product expands entrywise to the doubly-stochastic sum. + have hC_trace : (Matrix.diagonal (RCLike.ofReal ∘ A.H.eigenvalues) * C * + Matrix.diagonal (RCLike.ofReal ∘ B.H.eigenvalues) * C.conjTranspose).trace = + ((∑ i, ∑ j, A.H.eigenvalues i * B.H.eigenvalues j * ‖C i j‖ ^ 2 : ℝ) : ℂ) := by + push_cast + simp only [Matrix.trace, Matrix.diag_apply, Matrix.mul_apply, hC_norm] + simp only [Matrix.diagonal_apply, ite_mul, zero_mul, mul_ite, mul_zero, + Finset.sum_ite_eq, Finset.sum_ite_eq', Finset.mem_univ, ite_true, Function.comp_apply, + Matrix.conjTranspose_apply] + exact Finset.sum_congr rfl fun _ _ => Finset.sum_congr rfl fun _ _ => by + simp only [RCLike.ofReal_eq_complex_ofReal]; ring + rw [inner_eq_re_trace, key, Matrix.trace_mul_cycle, hUU, one_mul, hC_trace] + exact Complex.ofReal_re _ + + +end HermitianMat diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/HermitianMat/Jordan.lean b/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/HermitianMat/Jordan.lean new file mode 100644 index 0000000000..4a41e63218 --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/HermitianMat/Jordan.lean @@ -0,0 +1,169 @@ +/- +Copyright (c) 2025 Alex Meiburg. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Alex Meiburg +-/ +module + +public import Mathlib.Algebra.Jordan.Basic + +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.CFC +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.Order + +/-! +Hermitian matrices have a Jordan algebra structure given by +`A * B := 2⁻¹ • (A.toMat * B.toMat + B.toMat * A.toMat)`. We call this operation +`HermitianMat.symmMul`, but it's available as `*` multiplication scoped under +`HermMul`. When `A` and `B` commute, this reduces to standard matrix multiplication. +-/ + +@[expose] public section + +noncomputable section + +section starRing + +variable {d 𝕜 : Type*} [Fintype d] [Field 𝕜] [StarRing 𝕜] +variable (A B : HermitianMat d 𝕜) + +namespace HermitianMat + +/-- The symmetrized matrix product `(A * B + B * A) / 2`, regarded as a Hermitian matrix. -/ +def symmMul : HermitianMat d 𝕜 := + ⟨(2 : 𝕜)⁻¹ • (A.mat * B.mat + B.mat * A.mat), + by simp [selfAdjoint, IsSelfAdjoint, add_comm, Matrix.star_eq_conjTranspose]⟩ + +theorem symmMul_comm : A.symmMul B = B.symmMul A := by + apply HermitianMat.ext + change (2 : 𝕜)⁻¹ • (A.mat * B.mat + B.mat * A.mat) = + (2 : 𝕜)⁻¹ • (B.mat * A.mat + A.mat * B.mat) + rw [add_comm] + +@[simp] +theorem symmMul_zero : A.symmMul 0 = 0:= by + apply HermitianMat.ext + change (2 : 𝕜)⁻¹ • (A.mat * 0 + 0 * A.mat) = 0 + simp + +@[simp] +theorem zero_symmMul : symmMul 0 A = 0 := by + apply HermitianMat.ext + change (2 : 𝕜)⁻¹ • (0 * A.mat + A.mat * 0) = 0 + simp + +theorem symmMul_toMat : (A.symmMul B).mat = + (2 : 𝕜)⁻¹ • (A.mat * B.mat + B.mat * A.mat) := by + rfl + +variable [Invertible (2 : 𝕜)] + +variable {A B} in +@[simp] +theorem symmMul_of_commute (hAB : Commute A.mat B.mat) : + (A.symmMul B).mat = A.mat * B.mat := by + rw [symmMul_toMat, hAB] + rw [smul_add, ← add_smul, inv_eq_one_div, ← add_div] + rw [add_self_div_two, one_smul] + +theorem symmMul_self : (symmMul A A).mat = A.mat * A.mat := by + simp + +variable [DecidableEq d] + +@[simp] +theorem symmMul_one : A.symmMul 1 = A := by + ext1; simp + +@[simp] +theorem one_symmMul : symmMul 1 A = A := by + ext1; simp + +@[simp] +theorem symmMul_neg_one : A.symmMul (-1) = -A := by + ext1; simp + +@[simp] +theorem neg_one_symmMul : symmMul (-1) A = -A := by + ext1; simp + +end HermitianMat +end starRing + +namespace HermMul + +section starRing + +variable {d 𝕜 : Type*} [Fintype d] [Field 𝕜] [StarRing 𝕜] +variable (A B : HermitianMat d 𝕜) + +/-- Hermitian matrices form a commutative magma under the symmetrized product. -/ +scoped instance : CommMagma (HermitianMat d 𝕜) where + mul := HermitianMat.symmMul + mul_comm := HermitianMat.symmMul_comm + +-- --Stupid shortcut that might actually help a lot +-- scoped instance : Mul (HermitianMat d 𝕜) := + -- CommMagma.toMul + +theorem mul_eq_symmMul : A * B = A.symmMul B := by + rfl + +scoped instance : IsCommJordan (HermitianMat d 𝕜) where + lmul_comm_rmul_rmul a b := by + ext1 + simp only [mul_eq_symmMul, HermitianMat.symmMul_toMat, smul_add, + mul_add, add_mul, Matrix.mul_smul, Matrix.smul_mul, Matrix.mul_assoc] + abel + +/-- Zero is absorbing for the symmetrized product of Hermitian matrices. -/ +scoped instance : MulZeroClass (HermitianMat d 𝕜) where + zero_mul := by simp [mul_eq_symmMul] + mul_zero := by simp [mul_eq_symmMul] + +variable [DecidableEq d] [Invertible (2 : 𝕜)] + +/-- The identity matrix is a unit for the symmetrized product when two is invertible. -/ +scoped instance : MulZeroOneClass (HermitianMat d 𝕜) where + one_mul := by simp [mul_eq_symmMul] + mul_one := by simp [mul_eq_symmMul] + +end starRing + +section field + +variable {d 𝕜 : Type*} [Fintype d] [Field 𝕜] [StarRing 𝕜] + +/-- The symmetrized product gives Hermitian matrices a nonunital, nonassociative ring structure. -/ +scoped instance : NonUnitalNonAssocRing (HermitianMat d 𝕜) where + left_distrib a b c := by + ext1 + simp [mul_eq_symmMul, HermitianMat.symmMul_toMat, mul_add, add_mul] + abel + right_distrib a b c := by + ext1 + simp [mul_eq_symmMul, HermitianMat.symmMul_toMat, mul_add, add_mul] + abel + +variable [Invertible (2 : 𝕜)] [DecidableEq d] + +/-- The symmetrized product gives Hermitian matrices a commutative, nonassociative ring +structure. -/ +scoped instance : NonAssocCommRing (HermitianMat d 𝕜) where + mul_comm := HermitianMat.symmMul_comm + +end field + +section rclike + +variable {d 𝕜 : Type*} [Fintype d] [RCLike 𝕜] + +scoped instance : IsScalarTower ℝ (HermitianMat d 𝕜) (HermitianMat d 𝕜) where + smul_assoc r x y := by + ext : 2 + simp only [smul_eq_mul, mul_eq_symmMul, HermitianMat.symmMul_toMat, + HermitianMat.mat_smul, smul_add] + simp + +end rclike + +end HermMul diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/HermitianMat/NonSingular.lean b/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/HermitianMat/NonSingular.lean new file mode 100644 index 0000000000..1fe4393936 --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/HermitianMat/NonSingular.lean @@ -0,0 +1,204 @@ +/- +Copyright (c) 2025 Alex Meiburg. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Alex Meiburg +-/ +module + +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.Order +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.Isometry + +/-! Nonsingular Hermitian matrices and their inverses. -/ + +@[expose] public section + +noncomputable section + +namespace Matrix + +variable {d R S F 𝕜 : Type*} [Fintype d] [DecidableEq d] +variable [CommSemiring R] [Semiring S] [Algebra R S] [Field F] [RCLike 𝕜] + +theorem isUnit_smul {c : R} (hA : IsUnit c) {M : Matrix d d S} (hM : IsUnit M) : + IsUnit (c • M : Matrix d d S) := by + obtain ⟨d, rfl⟩ := hA + obtain ⟨M', rfl⟩ := hM + use d • M' + rfl + +theorem isUnit_natCast {n : ℕ} (hn : n ≠ 0) [CharZero F] : IsUnit (n : Matrix d d F) := by + exact (IsUnit.mk0 (n : F) (mod_cast hn)).map (algebraMap F _) + +theorem isUnit_real_smul {r : ℝ} (hr : r ≠ 0) {M : Matrix d d 𝕜} (hM : IsUnit M) : + IsUnit (r • M : Matrix d d 𝕜) := + isUnit_smul hr.isUnit hM + +theorem isUnit_real_cast {r : ℝ} (hr : r ≠ 0) : IsUnit (r • 1 : Matrix d d 𝕜) := by + exact isUnit_real_smul hr isUnit_one + +end Matrix + +namespace HermitianMat + +variable {n m R 𝕜 : Type*} [Fintype n] [DecidableEq n] [Fintype m] [DecidableEq m] +variable [CommRing R] [StarRing R] [RCLike 𝕜] +variable (A : HermitianMat n R) (B : HermitianMat m R) + +/-- A Hermitian matrix is nonsingular when its underlying matrix is a unit. -/ +class NonSingular (A : HermitianMat n R) : Prop where + isUnit : IsUnit A.mat + +@[simp] +theorem isUnit_mat_of_nonSingular [NonSingular A] : IsUnit A.mat := + NonSingular.isUnit + +theorem nonsingular_iff_isUnit : NonSingular A ↔ IsUnit A.mat := by + exact Iff.intro (fun h ↦ h.isUnit) NonSingular.mk + +instance instHasInv_of_invertible [i : Invertible A.mat] : NonSingular A := + ⟨isUnit_of_invertible _⟩ + +instance instInvertibleOfHasInv [h : NonSingular A] : Invertible A.mat := + h.isUnit.invertible + +instance : NonSingular (1 : HermitianMat n R) := + instHasInv_of_invertible (i := invertibleOne) + +variable {A : HermitianMat n 𝕜} {B : HermitianMat m 𝕜} {C : Matrix n n 𝕜} +open ComplexOrder + +theorem nonSingular_of_posDef (hA : A.mat.PosDef) : NonSingular A := + ⟨hA.isUnit⟩ + +theorem nonSingular_iff_posDef_of_PSD (hA : 0 ≤ A) : + NonSingular A ↔ A.mat.PosDef := by + refine ⟨fun h ↦ ?_, fun hA₂ ↦ nonSingular_of_posDef hA₂⟩ + have : IsUnit A.mat := h.isUnit + grind [zero_le_iff] + +theorem nonSingular_smul [i : NonSingular A] {c : ℝ} (hc : IsUnit c) : NonSingular (c • A) := + ⟨Matrix.isUnit_smul hc i.isUnit⟩ + +theorem nonSingular_iff_zero_notMem_spectrum : NonSingular A ↔ 0 ∉ spectrum ℝ A.mat := by + simp [nonsingular_iff_isUnit, spectrum, resolventSet] + +theorem nonSingular_iff_eigenvalue_ne_zero : NonSingular A ↔ ∀ i, A.H.eigenvalues i ≠ 0 := by + simp [nonSingular_iff_zero_notMem_spectrum, A.H.spectrum_real_eq_range_eigenvalues] + +theorem nonSingular_iff_det_ne_zero : NonSingular A ↔ A.mat.det ≠ 0 := by + simp [Matrix.isUnit_iff_isUnit_det, nonsingular_iff_isUnit] + +theorem nonSingular_iff_ker_bot : NonSingular A ↔ A.ker = ⊥ := by + rw [nonSingular_iff_det_ne_zero]; + have h_det_nonzero_to_ker_trivial : A.mat.det ≠ 0 → A.ker = ⊥ := by + intro h_det_nonzero + rw [Submodule.eq_bot_iff] + have h_inv : Invertible A.mat := by + convert Matrix.invertibleOfDetInvertible A.mat + exact invertibleOfNonzero h_det_nonzero + have h_ker_trivial (x : n → 𝕜) (hx : A.mat.mulVec x = 0) : x = 0 := by + simpa using congr_arg (h_inv.1.mulVec) hx + intro x hx + rw [mem_ker_iff_mulVec_zero] at hx + exact PiLp.ext (fun i => congr_fun (h_ker_trivial x.ofLp hx) i) + refine ⟨h_det_nonzero_to_ker_trivial, fun h h' => ?_⟩ + obtain ⟨x, hx⟩ := Matrix.exists_mulVec_eq_zero_iff.mpr h' + have h_inj : Function.Injective (Matrix.mulVecLin A.mat) := by + rw [← LinearMap.ker_eq_bot, Submodule.eq_bot_iff] + intro y hy + rw [LinearMap.mem_ker, Matrix.mulVecLin_apply] at hy + have hm : (WithLp.toLp 2 y) ∈ A.ker := (mem_ker_iff_mulVec_zero A _).mpr hy + rw [h] at hm + have := (Submodule.mem_bot (R := 𝕜)).mp hm + simpa using this + specialize @h_inj x 0 + simp_all + +theorem nonSingular_iff_support_top : NonSingular A ↔ A.support = ⊤ := by + simp only [support, Submodule.eq_top_iff'] + refine ⟨fun hA ↦ ?_, fun hA ↦ ?_⟩ + · intro x + have hA_inv : IsUnit A.mat := hA.isUnit + rcases hA_inv.exists_right_inv with ⟨y, hy⟩ + exact ⟨WithLp.toLp 2 (y.mulVec x.ofLp), by + apply PiLp.ext; intro i + simp [lin, Matrix.toLpLin_apply, Matrix.mulVec_mulVec, hy]⟩ + · constructor + have : Function.Surjective A.mat.mulVec := by + intro y + obtain ⟨x, hx⟩ := hA (WithLp.toLp 2 y) + exact ⟨x.ofLp, by + have := congr_arg WithLp.ofLp hx + simp? [lin, Matrix.toLpLin_apply] at this + exact this⟩ + exact Matrix.mulVec_surjective_iff_isUnit.mp this + +@[simp] +theorem nonSingular_iff_neg : NonSingular (-A) ↔ NonSingular A := by + simp [nonSingular_iff_det_ne_zero, Matrix.det_neg] + +@[simp] +theorem nonSingular_iff_inv : NonSingular (A⁻¹) ↔ NonSingular A := by + rw [nonsingular_iff_isUnit, nonsingular_iff_isUnit] + exact Matrix.isUnit_nonsing_inv_iff + +@[simp] +theorem nonSingular_iff_kronecker [Nonempty n] [Nonempty m] : + NonSingular (A ⊗ₖ B) ↔ NonSingular A ∧ NonSingular B := by + simp [nonSingular_iff_det_ne_zero, Matrix.det_kronecker] + +theorem nonSingular_iff_conj (hC : IsUnit C) : NonSingular (A.conj C) ↔ NonSingular A := by + simp_all [Matrix.isUnit_iff_isUnit_det, nonsingular_iff_isUnit] + +@[simp] +theorem nonSingular_iff_reindex (e : n ≃ m) : NonSingular (A.reindex e) ↔ NonSingular A := by + rw [nonSingular_iff_det_ne_zero, nonSingular_iff_det_ne_zero] + rw [reindex_eq_conj, ← Matrix.det_reindex_self e, conj_apply_mat] + congr! 2 + ext : 2 + simp [Matrix.mul_apply, Matrix.one_apply] + +section fwd + +theorem nonSingular_empty [IsEmpty n] : NonSingular A := by + rw [Subsingleton.eq_one A] + infer_instance + +variable [NonSingular A] [NonSingular B] + +theorem nonSingular_det_ne_zero : A.mat.det ≠ 0 := by + rwa [← nonSingular_iff_det_ne_zero] + +@[simp] +theorem nonSingular_ker_bot : A.ker = ⊥ := by + rwa [← nonSingular_iff_ker_bot] + +@[simp] +theorem nonSingular_support_top : A.support = ⊤ := by + rwa [← nonSingular_iff_support_top] + +instance nonSingular_neg : NonSingular (-A) := by + rwa [nonSingular_iff_neg] + +instance nonSingular_inv : NonSingular (A⁻¹) := by + rwa [nonSingular_iff_inv] + +instance nonSingular_kron [Nonempty n] [Nonempty m] : NonSingular (A ⊗ₖ B) := + nonSingular_iff_kronecker.mpr ⟨inferInstance, inferInstance⟩ + +theorem nonSingular_conj (hC : IsUnit C) : NonSingular (A.conj C) := by + rwa [nonSingular_iff_conj hC] + +instance nonSingular_conj_isometry {B : HermitianMat n 𝕜} [NonSingular B] : + NonSingular (A.conj B.mat) := by + simpa [nonSingular_iff_conj] + +theorem nonSingular_zero_notMem_spectrum : 0 ∉ spectrum ℝ A.mat := by + rwa [← nonSingular_iff_zero_notMem_spectrum] + +theorem nonSingular_eigenvalue_ne_zero : ∀ i, A.H.eigenvalues i ≠ 0 := by + rwa [← nonSingular_iff_eigenvalue_ne_zero] + +end fwd + +end HermitianMat diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/HermitianMat/Order.lean b/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/HermitianMat/Order.lean new file mode 100644 index 0000000000..9d8d2970b1 --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/HermitianMat/Order.lean @@ -0,0 +1,765 @@ +/- +Copyright (c) 2025 Alex Meiburg. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Alex Meiburg +-/ +module + +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.Trace +public import Mathlib.Analysis.RCLike.Basic + +/-! The positive semidefinite order and associated norm bounds on Hermitian matrices. -/ + +@[expose] public section + +namespace HermitianMat + +open ComplexOrder +open scoped Matrix + +variable {𝕜 : Type*} [RCLike 𝕜] +variable {n m ι : Type*} [Fintype n] [Fintype m] [Fintype ι] +variable {A B C : HermitianMat n 𝕜} +variable {M : Matrix m n 𝕜} {N : Matrix n n 𝕜} + +open MatrixOrder in +/-- The `MatrixOrder` instance for Matrix (the Loewner order) we keep open for +HermitianMat, always. -/ +instance : PartialOrder (HermitianMat n 𝕜) := + inferInstanceAs (PartialOrder (selfAdjoint _)) + +open MatrixOrder in +instance : IsOrderedAddMonoid (HermitianMat n 𝕜) := + inferInstanceAs (IsOrderedAddMonoid (selfAdjoint _)) + +omit [Fintype n] in +theorem le_iff : A ≤ B ↔ (B - A).mat.PosSemidef := by + rfl + +omit [Fintype n] in +theorem zero_le_iff : 0 ≤ A ↔ A.mat.PosSemidef := by + rw [le_iff, sub_zero] + +theorem le_iff_mulVec_le : A ≤ B ↔ + ∀ x, star x ⬝ᵥ A.mat *ᵥ x ≤ star x ⬝ᵥ B.mat *ᵥ x := by + simp [le_iff, Matrix.posSemidef_iff_dotProduct_mulVec, B.H.sub A.H, Matrix.sub_mulVec] + +instance [DecidableEq n] : ZeroLEOneClass (HermitianMat n 𝕜) where + zero_le_one := by + rw [zero_le_iff] + exact Matrix.PosSemidef.one + +omit [Fintype n] in +theorem lt_iff_posdef : A < B ↔ (B - A).mat.PosSemidef ∧ A ≠ B := + lt_iff_le_and_ne + +instance : IsStrictOrderedModule ℝ (HermitianMat n 𝕜) where + smul_lt_smul_of_pos_left a ha b b₂ hb := by + rw [HermitianMat.lt_iff_posdef] at hb ⊢ + constructor + · change (a • b₂.mat - a • b.mat).PosSemidef + simpa only [mat_sub, smul_sub] using hb.1.smul ha.le + · intro h + apply hb.2 + apply HermitianMat.ext + have hm := congrArg HermitianMat.mat h + exact (smul_right_inj ha.ne').mp hm + smul_lt_smul_of_pos_right a ha b b₂ hb := by + rw [HermitianMat.lt_iff_posdef] at ha ⊢ + constructor + · change (b₂ • a.mat - b • a.mat).PosSemidef + simpa only [sub_zero, mat_sub, mat_zero, sub_smul] using ha.1.smul (sub_nonneg.mpr hb.le) + · intro h + have hm : (b₂ - b) • a.mat = 0 := by + rw [sub_smul] + exact sub_eq_zero.mpr (congrArg HermitianMat.mat h).symm + have hz : a.mat = 0 := (smul_eq_zero.mp hm).resolve_left (sub_ne_zero.mpr hb.ne') + exact ha.2 (HermitianMat.ext hz.symm) + +theorem posSemidef_iff_spectrum_Ici [DecidableEq n] (A : HermitianMat n 𝕜) : + 0 ≤ A ↔ spectrum ℝ A.mat ⊆ Set.Ici 0 := by + rw [zero_le_iff, Matrix.posSemidef_iff_isHermitian_and_spectrum_nonneg] + simp [A.H, Set.Ici.eq_1] + +theorem posSemidef_iff_spectrum_nonneg [DecidableEq n] (A : HermitianMat n 𝕜) : + 0 ≤ A ↔ ∀ x ∈ spectrum ℝ A.mat, 0 ≤ x := by + exact A.posSemidef_iff_spectrum_Ici + +theorem trace_nonneg (hA : 0 ≤ A) : 0 ≤ A.trace := by + exact (RCLike.nonneg_iff.mp (zero_le_iff.mp hA).trace_nonneg).1 + +theorem trace_pos (hA : 0 < A) : 0 < A.trace := by + open ComplexOrder in + have hA' := hA.le + rw [HermitianMat.zero_le_iff] at hA' + have h_pos := Matrix.PosSemidef.trace_pos hA' (by simpa [HermitianMat.ext_iff] using hA.ne') + rw [HermitianMat.trace_eq_re_trace] + rw [RCLike.pos_iff] at h_pos + exact h_pos.left + +open Lean Meta Mathlib.Meta.Positivity in +/-- Positivity extension for `HermitianMat.trace`: nonneg when the matrix is nonneg, +positive when the matrix is positive. -/ +@[positivity HermitianMat.trace _] +meta def evalHermitianMatTrace : PositivityExt where eval {_u _α} _zα _pα? e := + match _pα? with + | none => pure .none + | some _ => do + let .app _tr (A : Expr) ← whnfR e | throwError "not HermitianMat.trace" + let (isStrict, pfA) ← bestResult A + if isStrict then + pure (.positive (← mkAppM ``HermitianMat.trace_pos #[pfA])) + else + pure (.nonnegative (← mkAppM ``HermitianMat.trace_nonneg #[pfA])) + +--Without these shortcut instances, `gcongr` fails to close certain goals...? Why? TODO +instance : PosSMulMono ℝ (HermitianMat n 𝕜) where + smul_le_smul_of_nonneg_left a ha b c hbc := by + rw [le_iff] at hbc ⊢ + change (a • c.mat - a • b.mat).PosSemidef + simpa only [mat_sub, smul_sub] using hbc.smul ha + +instance : SMulPosMono ℝ (HermitianMat n 𝕜) where + smul_le_smul_of_nonneg_right a ha b c hbc := by + rw [zero_le_iff] at ha + rw [le_iff] + change (c • a.mat - b • a.mat).PosSemidef + simpa only [sub_smul] using ha.smul (sub_nonneg.mpr hbc) + +--Without explicitly giving this instance, Lean times out trying to find it sometimes. +instance : PosSMulReflectLE ℝ (HermitianMat n 𝕜) := + PosSMulMono.toPosSMulReflectLE + +theorem le_trace_smul_one [DecidableEq n] (hA : 0 ≤ A) : A ≤ A.trace • 1 := by + have hA' : A.mat.PosSemidef := zero_le_iff.mp hA + refine (Matrix.PosSemidef.le_smul_one_of_eigenvalues_iff hA'.1 A.trace).mp ?_ + rw [← A.sum_eigenvalues_eq_trace] + intro i + exact Finset.single_le_sum (fun j _ ↦ hA'.eigenvalues_nonneg j) (Finset.mem_univ i) + +omit [Fintype n] [Fintype m] in +/-- The Kronecker product of two nonnegative Hermitian matrices is nonnegative. -/ +theorem kronecker_nonneg [Finite n] [Finite m] {A : HermitianMat m 𝕜} + (hA : 0 ≤ A) (hB : 0 ≤ B) : 0 ≤ A ⊗ₖ B := by + classical + let := Fintype.ofFinite n + let := Fintype.ofFinite m + rw [zero_le_iff, kronecker_mat] + classical exact (zero_le_iff.mp hA).PosSemidef_kronecker (zero_le_iff.mp hB) + +omit [Fintype n] in +/-- The self-Kronecker map `A ↦ A ⊗ₖ A` is monotone on nonnegative Hermitian matrices. -/ +theorem kronecker_self_mono [Finite n] (hA : 0 ≤ A) (hB : 0 ≤ B) (hAB : A ≤ B) : + A ⊗ₖ A ≤ B ⊗ₖ B := by + classical + let := Fintype.ofFinite n + rw [← sub_nonneg] + have hAC : A ⊗ₖ B + -(A ⊗ₖ A) = A ⊗ₖ (B - A) := by + rw [show -(A ⊗ₖ A) = A ⊗ₖ (-A) by + symm + ext1 + simpa using (Matrix.kronecker_smul (-1 : 𝕜) A.mat A.mat)] + simpa [sub_eq_add_neg] using + (HermitianMat.kronecker_add (A := A) (B := B) (C := -A)).symm + have hEq : B ⊗ₖ B - A ⊗ₖ A = A ⊗ₖ (B - A) + (B - A) ⊗ₖ B := by + calc + B ⊗ₖ B - A ⊗ₖ A = (A + (B - A)) ⊗ₖ B - A ⊗ₖ A := by + rw [show A + (B - A) = B by abel] + _ = (A ⊗ₖ B + (B - A) ⊗ₖ B) - A ⊗ₖ A := by rw [HermitianMat.add_kronecker] + _ = (A ⊗ₖ B + -(A ⊗ₖ A)) + (B - A) ⊗ₖ B := by abel + _ = A ⊗ₖ (B - A) + (B - A) ⊗ₖ B := by rw [hAC] + simpa [hEq] using add_nonneg + (HermitianMat.kronecker_nonneg hA (sub_nonneg.mpr hAB)) + (HermitianMat.kronecker_nonneg (sub_nonneg.mpr hAB) hB) + +omit [Fintype n] [Fintype m] in +/-- The Kronecker product of two positive Hermitian matrices is positive. -/ +theorem kronecker_pos [Finite n] [Finite m] {A : HermitianMat m 𝕜} + (hA : 0 < A) (hB : 0 < B) : 0 < A ⊗ₖ B := by + classical + let := Fintype.ofFinite n + let := Fintype.ofFinite m + apply lt_of_le_of_ne (kronecker_nonneg hA.le hB.le) + intro h + replace h := congr(trace $h) + simp only [trace_zero, trace_kronecker, zero_eq_mul] at h + apply trace_pos at hA + apply trace_pos at hB + grind only [cases Or] + +omit [Fintype n] in +open MatrixOrder in +theorem posSemidef_to_nonneg {A : Matrix n n 𝕜} (hA : A.PosSemidef) : 0 ≤ A := by + exact hA.nonneg + +omit [Fintype n] in +open MatrixOrder in +theorem posDef_to_pos [Finite n] {A : Matrix n n 𝕜} (hA : A.PosDef) [Nonempty n] : 0 < A := by + classical + let := Fintype.ofFinite n + apply lt_of_le_of_ne hA.posSemidef.nonneg + rintro rfl + classical simpa [Matrix.det_zero] using hA.det_pos + +open Lean Meta in +/-- Given an expression `e` (a matrix) and a proof expression `p` whose type may be +`Matrix.PosSemidef A`, `Matrix.PosDef A`, or `And P Q` (syntactically), attempt to +find a proof of nonnegativity or positivity for `e`. Only syntactic matching on the +head constant is used; `isDefEq` is used only to compare the matrix argument. -/ +meta def findMatrixPSDInExpr (e : Expr) (p : Expr) (ty : Expr) : + MetaM (Option (Bool × Expr)) := do + let head := ty.getAppFn + if head.isConst then + let name := head.constName! + if name == ``Matrix.PosSemidef then + -- Last argument is the matrix + let args := ty.getAppArgs + let A := args.back! + if ← isDefEq A e then + let pf ← mkAppM ``HermitianMat.posSemidef_to_nonneg #[p] + return some (false, pf) + if name == ``Matrix.PosDef then + let args := ty.getAppArgs + let A := args.back! + if ← isDefEq A e then + -- Try strict (needs Nonempty n); extract the index type from PosDef args + -- PosDef args: [n, R, Fintype n, Ring R, PartialOrder R, StarRing R, A] + let nType := args[0]! + let nonemptyType ← mkAppM ``Nonempty #[nType] + match ← try? (synthInstance nonemptyType) with + | some nonemptyInst => + -- posDef_to_pos : {𝕜} → [RCLike 𝕜] → {n} → [Fintype n] → {A} → (hA : + -- A.PosDef) → [Nonempty n] → 0 < A + let pf ← mkAppOptM ``HermitianMat.posDef_to_pos #[none, none, none, none, none, p, + nonemptyInst] + return some (true, pf) + | none => + let pSemidef ← mkAppM ``Matrix.PosDef.posSemidef #[p] + let pf ← mkAppM ``HermitianMat.posSemidef_to_nonneg #[pSemidef] + return some (false, pf) + match ty with + | .app (.app (.const ``And _) left) right => + let pLeft ← mkAppM ``And.left #[p] + if let some result ← findMatrixPSDInExpr e pLeft left then + return some result + let pRight ← mkAppM ``And.right #[p] + if let some result ← findMatrixPSDInExpr e pRight right then + return some result + | _ => pure () + return none +termination_by structural ty + +open Lean Meta Mathlib.Meta.Positivity in +/-- Positivity extension for `Matrix`: looks for `A.PosSemidef` or `A.PosDef` in the +local context (including syntactic `And` conjunctions) to prove `0 ≤ A` or `0 < A`. -/ +@[positivity (_ : HermitianMat _ _)] +meta def evalMatrixPSD : PositivityExt where eval {_u _α} _zα _pα? e := + match _pα? with | none => pure .none | some _pα => do + let lctx ← getLCtx + let mut best : Strictness _zα e (some _pα) := .none + for ldecl in lctx do + if ldecl.isImplementationDetail then continue + let ty := ldecl.type + let p : Expr := .fvar ldecl.fvarId + if let some (isStrict, pf) ← findMatrixPSDInExpr e p ty then + if isStrict then + return .positive pf + else + best := .nonnegative pf + match best with + | .none => throwError "evalMatrixPSD: no PosSemidef or PosDef hypothesis found for {e}" + | other => return other + + +omit [Fintype n] in +theorem mat_posSemidef_to_nonneg (hA : A.mat.PosSemidef) : 0 ≤ A := + zero_le_iff.mpr hA + +omit [Fintype n] in +theorem mat_posDef_to_pos [Finite n] [Nonempty n] (hA : A.mat.PosDef) : 0 < A := by + classical + let := Fintype.ofFinite n + exact posDef_to_pos hA + +open Lean Meta in +/-- Given an expression `e` (a `HermitianMat`) and a proof expression `p` whose type may be +`Matrix.PosSemidef A.mat`, `Matrix.PosDef A.mat`, or `And P Q` (syntactically), attempt to +find a proof of nonnegativity or positivity for `e`. Only syntactic matching on the +head constant is used; `isDefEq` is used only to compare the `HermitianMat` argument. -/ +meta def findHermitianMatPSDInExpr (e : Expr) (p : Expr) (ty : Expr) : + MetaM (Option (Bool × Expr)) := do + let head := ty.getAppFn + if head.isConst then + let name := head.constName! + if name == ``Matrix.PosSemidef || name == ``Matrix.PosDef then + -- Last argument should be `A.mat` i.e. `HermitianMat.mat A` + let args := ty.getAppArgs + let matExpr := args.back! + -- Check if matExpr is `HermitianMat.mat A` (or equivalently `Subtype.val A`) + let matHead := matExpr.getAppFn + if matHead.isConst && (matHead.constName! == ``HermitianMat.mat || + matHead.constName! == ``Subtype.val) then + let matArgs := matExpr.getAppArgs + let A := matArgs.back! + if ← isDefEq A e then + if name == ``Matrix.PosSemidef then + let pf ← mkAppM ``HermitianMat.mat_posSemidef_to_nonneg #[p] + return some (false, pf) + else + -- PosDef: try strict (needs Nonempty n) + let psdArgs := ty.getAppArgs + let nType := psdArgs[0]! + let nonemptyType ← mkAppM ``Nonempty #[nType] + match ← try? (synthInstance nonemptyType) with + | some nonemptyInst => + -- mat_posDef_to_pos : {𝕜} → [RCLike 𝕜] → {n} → [Fintype n] → {A} → + -- [Nonempty n] → (hA : A.mat.PosDef) → 0 < A + let pf ← mkAppOptM ``HermitianMat.mat_posDef_to_pos #[none, none, none, none, + none, nonemptyInst, p] + return some (true, pf) + | none => + let pSemidef ← mkAppM ``Matrix.PosDef.posSemidef #[p] + let pf ← mkAppM ``HermitianMat.mat_posSemidef_to_nonneg #[pSemidef] + return some (false, pf) + match ty with + | .app (.app (.const ``And _) left) right => + let pLeft ← mkAppM ``And.left #[p] + if let some result ← findHermitianMatPSDInExpr e pLeft left then + return some result + let pRight ← mkAppM ``And.right #[p] + if let some result ← findHermitianMatPSDInExpr e pRight right then + return some result + | _ => pure () + return none +termination_by structural ty + +open Lean Meta Mathlib.Meta.Positivity in +/-- Positivity extension for `HermitianMat`: looks for `A.mat.PosSemidef` or `A.mat.PosDef` in +the local context (including syntactic `And` conjunctions) to prove `0 ≤ A` or `0 < A`. -/ +@[positivity (_ : HermitianMat _ _)] +meta def evalHermitianMatPSD : PositivityExt where eval {_u _α} _zα _pα? e := + match _pα? with | none => pure .none | some _pα => do + trace[Tactic.positivity] "evalHermitianMatPSD: {e}" + let lctx ← getLCtx + let mut best : Strictness _zα e (some _pα) := .none + for ldecl in lctx do + if ldecl.isImplementationDetail then continue + let ty := ldecl.type + let p : Expr := .fvar ldecl.fvarId + if let some (isStrict, pf) ← findHermitianMatPSDInExpr e p ty then + if isStrict then + return .positive pf + else + best := .nonnegative pf + match best with + | .none => + throwError "evalHermitianMatPSD: no A.mat.PosSemidef or A.mat.PosDef hypothesis found for {e}" + | other => return other + +open Lean Meta Mathlib.Meta.Positivity in +/-- Positivity extension for `HermitianMat.kronecker`: nonneg when both factors are. -/ +@[positivity HermitianMat.kronecker _ _] +meta def evalHermitianMatKronecker : PositivityExt where eval {_u _α} _zα _pα? e := + match _pα? with | none => pure .none | some _ => do + let .app (.app _kron A) B ← whnfR e | throwError "not HermitianMat.kronecker" + let (isStrictA, pfA) ← bestResult A + let (isStrictB, pfB) ← bestResult B + if isStrictA && isStrictB then + pure (.positive (← mkAppM ``HermitianMat.kronecker_pos #[pfA, pfB])) + else + let pfA' ← try mkAppM ``le_of_lt #[pfA] catch _ => pure pfA + let pfB' ← try mkAppM ``le_of_lt #[pfB] catch _ => pure pfB + let pfAB' ← mkAppM ``HermitianMat.kronecker_nonneg #[pfA', pfB'] + pure (.nonnegative pfAB') + +variable (M) in +omit [Fintype m] in +open Lean Meta Mathlib.Meta.Positivity in +/-- Positivity extension for `HermitianMat.conj`: nonneg when the inner matrix is. -/ +theorem conj_nonneg [Finite m] (hA : 0 ≤ A) : 0 ≤ A.conj M := by + classical + let := Fintype.ofFinite m + rw [zero_le_iff] at hA ⊢ + exact Matrix.PosSemidef.mul_mul_conjTranspose_same hA M + +omit [Fintype m] in +theorem conj_pos [Finite m] [DecidableEq n] {A : HermitianMat n 𝕜} {M : Matrix m n 𝕜} (hA : 0 < A) + (h : LinearMap.ker M.toEuclideanLin ≤ A.ker) : 0 < A.conj M := by + classical + let := Fintype.ofFinite m + classical exact (A.conj_nonneg M hA.le).lt_of_ne' (A.conj_ne_zero hA.ne' h) + +open Lean Meta Mathlib.Meta.Positivity in +/-- Positivity extension for `HermitianMat.conj`: nonneg when the inner matrix is. -/ +@[positivity HermitianMat.conj _ _] +meta def evalHermitianMatConj : PositivityExt where eval {_u _α} _zα _pα? e := + match _pα? with | none => pure .none | some _ => do + let .app (.app _coe conjM) (A : Expr) ← whnfR e | throwError "not conj application" + let M := conjM.appArg! + let (_, pfA) ← bestResult A + let pfNonneg ← try mkAppM ``le_of_lt #[pfA] catch _ => pure pfA + pure (.nonnegative (← mkAppM ``HermitianMat.conj_nonneg #[M, pfNonneg])) + +open MatrixOrder in +example {A : Matrix n n ℂ} (hA : A.PosSemidef) : 0 ≤ A := by + positivity + +open MatrixOrder in +example {A : Matrix n n ℂ} [Nonempty n] (hA : A.PosDef) : 0 < A := by + positivity + +example (hA : A.mat.PosSemidef) : 0 ≤ A := by + positivity + +example [Nonempty n] (hA : A.mat.PosDef) : 0 < A := by + positivity + +example [DecidableEq n] [DecidableEq m] [Nonempty n] [Nonempty m] + (A B : HermitianMat n ℂ) (hA : 0 ≤ A) (hB : 0 ≤ B) (M : Matrix m n ℂ) : + 0 < (2 : HermitianMat (n × m) ℂ) + (3 • A) ⊗ₖ (Real.pi • B).conj M := by + positivity + +example (A B : HermitianMat n ℂ) (hA : 0 < A) (hB : 0 < B) : + 0 < ((37 • A) ⊗ₖ ((38 : ℝ) • B)).trace := by + positivity + +omit [Fintype n] in +theorem convex_cone (hA : 0 ≤ A) (hB : 0 ≤ B) {c₁ c₂ : ℝ} (hc₁ : 0 ≤ c₁) (hc₂ : 0 ≤ c₂) : + 0 ≤ (c₁ • A + c₂ • B) := by + rw [zero_le_iff] at hA hB ⊢ + exact (hA.smul hc₁).add (hB.smul hc₂) + +theorem sq_nonneg [DecidableEq n] : 0 ≤ A ^ 2 := by + simp? [zero_le_iff, pow_two] + nth_rewrite 1 [←Matrix.IsHermitian.eq A.H] + exact Matrix.posSemidef_conjTranspose_mul_self A.mat + +theorem ker_antitone [DecidableEq n] (hA : 0 ≤ A) : A ≤ B → B.ker ≤ A.ker := by + intro h x hB + replace h := (le_iff_mulVec_le.mp h) x + rw [HermitianMat.mem_ker_iff_mulVec_zero] at hB ⊢ + rw [hB, dotProduct_zero] at h + rw [zero_le_iff] at hA + rw [← hA.dotProduct_mulVec_zero_iff] + rw [Matrix.posSemidef_iff_dotProduct_mulVec] at hA + exact le_antisymm h (hA.right x) + +omit [Fintype m] in +theorem conj_mono [Finite m] (h : A ≤ B) : A.conj M ≤ B.conj M := by + classical + let := Fintype.ofFinite m + have h_conj_pos : (M * (B - A).mat * Mᴴ).PosSemidef := + Matrix.PosSemidef.mul_mul_conjTranspose_same h M + constructor; + · simp [conj, Matrix.IsHermitian, Matrix.mul_assoc] + · simpa [Matrix.mul_sub, Matrix.sub_mul] using h_conj_pos.2 + +lemma conj_posDef [DecidableEq n] (hA : A.mat.PosDef) (hN : IsUnit N) : + (A.conj N).mat.PosDef := by + rw [Matrix.posDef_iff_dotProduct_mulVec] at hA ⊢ + use HermitianMat.H _ + intro x hx_ne_zero + open Matrix in + have h_pos : 0 < star (Nᴴ *ᵥ x) ⬝ᵥ A *ᵥ (Nᴴ *ᵥ x) := by + apply hA.2 + intro h + apply hx_ne_zero + simpa [ hN ] using Matrix.eq_zero_of_mulVec_eq_zero + (by simpa [Matrix.det_conjTranspose] using hN.map Matrix.detMonoidHom) h + convert h_pos using 1 + simp only [conj_apply_mat, mulVec_mulVec, Matrix.mul_assoc] + simp [dotProduct_mulVec, mulVec_conjTranspose] + +lemma inv_conj [DecidableEq n] {M : Matrix n n 𝕜} (hM : IsUnit M) : + (A.conj M)⁻¹ = A⁻¹.conj (M⁻¹)ᴴ := by + have h_inv : (M⁻¹)ᴴ * Mᴴ = 1 := by + simp only [Matrix.isUnit_iff_isUnit_det, isUnit_iff_ne_zero, ne_eq] at hM + simp [Matrix.conjTranspose_nonsing_inv, hM] + ext1 + simp only [mat_inv, conj_apply_mat, Matrix.conjTranspose_conjTranspose] + rw [Matrix.mul_inv_rev, Matrix.mul_inv_rev, Matrix.inv_eq_left_inv h_inv, mul_assoc] + +theorem le_iff_mulVec_le_mulVec (A B : HermitianMat n 𝕜) : + A ≤ B ↔ ∀ v : n → 𝕜, star v ⬝ᵥ A.mat *ᵥ v ≤ star v ⬝ᵥ B.mat *ᵥ v := by + rw [← sub_nonneg, HermitianMat.zero_le_iff] + conv_rhs => enter [v]; rw [← sub_nonneg] + have h := (B - A).H + simp only [HermitianMat.mat_sub] at h + simp [Matrix.posSemidef_iff_dotProduct_mulVec, Matrix.sub_mulVec, h] + +theorem inner_mulVec_nonneg (hA : 0 ≤ A) (v : n → 𝕜) : + 0 ≤ star v ⬝ᵥ A.mat *ᵥ v := by + rw [le_iff_mulVec_le_mulVec] at hA + simpa using hA v + +theorem mem_ker_of_inner_mulVec_zero [DecidableEq n] (hA : 0 ≤ A) (v : EuclideanSpace 𝕜 n) + (h : star v ⬝ᵥ A.mat *ᵥ v = 0) : v ∈ A.ker := by + have := ((zero_le_iff.mp hA).dotProduct_mulVec_zero_iff (x := v)).mp h + exact congr(WithLp.toLp 2 $this) + +theorem ker_add [DecidableEq n] (hA : 0 ≤ A) (hB : 0 ≤ B) : + (A + B).ker = A.ker ⊓ B.ker := by + have hA' := zero_le_iff.mp hA + have hB' := zero_le_iff.mp hB + ext v; simp only [Submodule.mem_inf, mem_ker_iff_mulVec_zero] + constructor + · intro hv + have h3 : star v ⬝ᵥ A.mat *ᵥ v + star v ⬝ᵥ B.mat *ᵥ v = 0 := by + rw [← dotProduct_add, ← Matrix.add_mulVec, ← mat_add, hv, dotProduct_zero] + rw [Matrix.posSemidef_iff_dotProduct_mulVec] at hA' hB' + obtain ⟨hzA, hzB⟩ := (add_eq_zero_iff_of_nonneg (hA'.2 v) (hB'.2 v)).mp h3 + rw [← Matrix.posSemidef_iff_dotProduct_mulVec] at hA' hB' + exact ⟨(hA'.dotProduct_mulVec_zero_iff (x := v)).mp hzA, + (hB'.dotProduct_mulVec_zero_iff (x := v)).mp hzB⟩ + · simp +contextual [Matrix.add_mulVec] + +theorem ker_sum [DecidableEq n] (f : ι → HermitianMat n 𝕜) (hf : ∀ i, 0 ≤ f i) : + (∑ i, f i).ker = ⨅ i, (f i).ker := by + ext v + simp only [Submodule.mem_iInf, mem_ker_iff_mulVec_zero] + constructor + · intro hv i + have hfi := zero_le_iff.mp (hf i) + rw [← hfi.dotProduct_mulVec_zero_iff] + have hge : ∀ j, 0 ≤ star v ⬝ᵥ (f j).mat *ᵥ v := by + intro j + have := zero_le_iff.mp (hf j) + rw [Matrix.posSemidef_iff_dotProduct_mulVec] at this + exact this.2 v + have hsum : ∑ j, star v ⬝ᵥ (f j).mat *ᵥ v = 0 := by + rw [← dotProduct_sum, ← Matrix.sum_mulVec, ← mat_finset_sum, hv, dotProduct_zero] + exact le_antisymm + (hsum ▸ Finset.single_le_sum (fun j _ => hge j) (Finset.mem_univ i)) + (hge i) + · intro h + simp [Matrix.sum_mulVec, h] + +theorem ker_conj [DecidableEq n] (hA : 0 ≤ A) (B : Matrix n n 𝕜) : + (A.conj B).ker = Submodule.comap (Matrix.toEuclideanLin B.conjTranspose) A.ker := by + ext v + simp only [Submodule.mem_comap] + change v ∈ (A.conj B).ker ↔ (Matrix.toEuclideanLin Bᴴ) v ∈ A.ker + constructor <;> intro h; + · have hz := (zero_le_iff.mp hA).dotProduct_mulVec_zero_iff (x := Bᴴ.mulVec v) + convert hz.1 _ using 1; + · rw [ mem_ker_iff_mulVec_zero ]; + congr! 2; + · convert congr_arg ( fun x : EuclideanSpace _ _ => star v.ofLp ⬝ᵥ x ) h using 1 + · simp [Matrix.mulVec_mulVec, Matrix.dotProduct_mulVec] + simp [conj_apply_mat, Matrix.mul_assoc, Matrix.dotProduct_mulVec, Matrix.star_mulVec, + Matrix.conjTranspose_conjTranspose, lin] + · simp [dotProduct] + · simp only [ker, LinearMap.mem_ker] + convert congr_arg B.toEuclideanLin h using 1 + · simp [HermitianMat.lin, conj_apply_mat, Matrix.toEuclideanLin] + · exact Eq.symm (LinearMap.map_zero (Matrix.toEuclideanLin B)) + +theorem ker_le_of_le_smul {α : ℝ} [DecidableEq n] (hα : α ≠ 0) (hA : 0 ≤ A) (hAB : A ≤ α • B) : + B.ker ≤ A.ker := by + rw [← ker_pos_smul B hα] + exact ker_antitone hA hAB + +/-- If a Hermitian matrix is bounded by `M * I`, then all its eigenvalues are at most `M`. -/ +theorem le_smul_one_imp_eigenvalues_le [DecidableEq n] (A : HermitianMat n ℂ) (M : ℝ) + (h : A ≤ M • (1 : HermitianMat n ℂ)) (i : n) : + A.H.eigenvalues i ≤ M := by + let v : n → ℂ := (A.H.eigenvectorBasis i).ofLp + have hv : star v ⬝ᵥ v = (1 : ℂ) := by + rw [show v = (A.H.eigenvectorBasis i).ofLp from rfl] + rw [dotProduct_comm, ← EuclideanSpace.inner_eq_star_dotProduct] + simp [A.H.eigenvectorBasis.orthonormal.1 i] + have hquad := (le_iff_mulVec_le_mulVec A (M • (1 : HermitianMat n ℂ))).mp h v + rw [show A.mat.mulVec v = (A.H.eigenvalues i : ℂ) • v from by + simpa [v] using A.H.mulVec_eigenvectorBasis i] at hquad + rw [dotProduct_smul, hv] at hquad + change (A.H.eigenvalues i : ℂ) • 1 ≤ + star v ⬝ᵥ ((M : ℂ) • (1 : Matrix n n ℂ)) *ᵥ v at hquad + have hquadC : (A.H.eigenvalues i : ℂ) ≤ (M : ℂ) := by + have hright : star v ⬝ᵥ ((M : ℂ) • (1 : Matrix n n ℂ)) *ᵥ v = (M : ℂ) := by + simp [Matrix.smul_mulVec, hv] + simpa [Matrix.smul_mulVec, hv] using hquad.trans_eq hright + exact_mod_cast hquadC + +open MatrixOrder in +/-- If all eigenvalues of a Hermitian matrix are at most `M`, then it is bounded by `M * I`. -/ +theorem eigenvalues_le_imp_le_smul_one [DecidableEq n] (A : HermitianMat n ℂ) (M : ℝ) + (h : ∀ i, A.H.eigenvalues i ≤ M) : + A ≤ M • (1 : HermitianMat n ℂ) := by + exact + (Matrix.PosSemidef.le_smul_one_of_eigenvalues_iff A.H M).mp h + +--TODO: Positivity extensions for traceLeft, traceRight, rpow, nat powers, inverse function, +-- the various `proj` function (in Proj.lean), and the inner product. + +/-! ## Positivity extensions connecting HermitianMat and Matrix -/ +section MatrixPositivity +open Lean Meta Mathlib.Meta.Positivity + +/-- If a HermitianMat is PSD, then its eigenvalues are nonneg. -/ +theorem eigenvalues_nonneg [DecidableEq n] (hA : 0 ≤ A) (i : n) : + 0 ≤ A.H.eigenvalues i := + (zero_le_iff.mp hA).eigenvalues_nonneg i + +omit [Fintype n] in +open MatrixOrder in +/-- If a HermitianMat is PSD, its underlying matrix is nonneg in the Loewner order. -/ +theorem mat_nonneg (hA : 0 ≤ A) : 0 ≤ A.mat := + Matrix.nonneg_iff_posSemidef.mpr (zero_le_iff.mp hA) + +omit [Fintype n] in +open MatrixOrder in +/-- If a HermitianMat is positive, its underlying matrix is positive in the Loewner order. -/ +theorem mat_pos (hA : 0 < A) : 0 < A.mat := + hA + +omit [Fintype n] in +open MatrixOrder in +/-- `Mᴴ * M` is nonneg in the Loewner order, for any matrix `M`. -/ +theorem _root_.Matrix.nonneg_conjTranspose_mul_self [Finite n] {m : Type*} [Fintype m] + (M : Matrix m n 𝕜) : 0 ≤ M.conjTranspose * M := by + classical + let := Fintype.ofFinite n + exact + Matrix.nonneg_iff_posSemidef.mpr (Matrix.posSemidef_conjTranspose_mul_self M) + +omit [Fintype n] in +open MatrixOrder in +/-- `M * Mᴴ` is nonneg in the Loewner order, for any matrix `M`. -/ +theorem _root_.Matrix.nonneg_self_mul_conjTranspose [Finite n] {m : Type*} [Fintype m] + (M : Matrix n m 𝕜) : 0 ≤ M * M.conjTranspose := by + classical + let := Fintype.ofFinite n + exact + Matrix.nonneg_iff_posSemidef.mpr (Matrix.posSemidef_self_mul_conjTranspose M) + +omit [Fintype m] in +open MatrixOrder in +theorem subtype_mk_nonneg {M : Matrix m m 𝕜} (h : 0 ≤ M) : + 0 ≤ (⟨M, (Matrix.LE.le.posSemidef h).isHermitian⟩ : HermitianMat m 𝕜) := + h + +omit [Fintype m] in +open MatrixOrder in +theorem subtype_mk_pos {M : Matrix m m 𝕜} (h : 0 < M) : + 0 < (⟨M, (Matrix.LE.le.posSemidef h.le).isHermitian⟩ : HermitianMat m 𝕜) := + h + +open MatrixOrder in +private theorem _root_.Matrix.eigenvalues_nonneg [DecidableEq n] {M : Matrix n n 𝕜} (h : 0 ≤ M) + (i : n) : + 0 ≤ (Matrix.LE.le.posSemidef h).isHermitian.eigenvalues i := + (Matrix.LE.le.posSemidef h).eigenvalues_nonneg i + +/-- Positivity extension for `A.mat` where `A : HermitianMat`: nonneg when `0 ≤ A`. -/ +@[positivity HermitianMat.mat _] +meta def evalHermitianMatMat : PositivityExt where eval {_u _α} _zα _pα? e := + match _pα? with | none => pure .none | some _ => do + let .app _matFn (A : Expr) ← whnfR e | throwError "not HermitianMat.mat" + match ← bestResult A with + | (true, pa) => + pure (.positive (← mkAppM ``HermitianMat.mat_pos #[pa])) + | (false, pa) => + pure (.nonnegative (← mkAppM ``HermitianMat.mat_nonneg #[pa])) + +/-- Positivity extension for `A.mat` where `A : HermitianMat`: nonneg when `0 ≤ A`. -/ +@[positivity Subtype.val (_ : HermitianMat _ _)] +meta def evalHermitianMatVal : PositivityExt where eval {_u _α} _zα _pα? e := + match _pα? with | none => pure .none | some _ => do + /- Note: we must not call `whnf` on `e` because `Subtype.val` is a structure + projection (reducible), so `whnf` would reduce it and destroy the pattern. -/ + let A := e.appArg! + match ← bestResult A with + | (true, pa) => + pure (.positive (← mkAppM ``HermitianMat.mat_pos #[pa])) + | (false, pa) => + pure (.nonnegative (← mkAppM ``HermitianMat.mat_nonneg #[pa])) + +/-- Positivity extension for `M * Mᴴ` as a Matrix: always nonneg. -/ +@[positivity HMul.hMul _ (Matrix.conjTranspose _)] +meta def evalMatrixSelfMulConjTranspose : PositivityExt where eval {_u _α} _zα _pα? e := + match _pα? with | none => pure .none | some _ => do + let .app (.app _hmul _M) Mstar ← whnfR e | throwError "not HMul application" + let .app _conjTranspose M' ← whnfR Mstar | throwError "not M * conjTranspose" + pure (.nonnegative (← mkAppM ``Matrix.nonneg_self_mul_conjTranspose #[M'])) + +/-- Positivity extension for `Mᴴ * M` as a Matrix: always nonneg. -/ +@[positivity HMul.hMul (Matrix.conjTranspose _) _] +meta def evalMatrixConjTransposeMulSelf : PositivityExt where eval {_u _α} _zα _pα? e := + match _pα? with | none => pure .none | some _ => do + let .app (.app _hmul Mstar) _M ← whnfR e | throwError "not HMul application" + let .app _conjTranspose M' ← whnfR Mstar | throwError "not conjTranspose * M" + pure (.nonnegative (← mkAppM ``Matrix.nonneg_conjTranspose_mul_self #[M'])) + +/-- Positivity extension for `⟨M, (pf : M.IsHermitian)⟩` as a HermitianMat: +equivalent to `0 ≤ M` in `MatrixOrder`. -/ +@[positivity (Subtype.mk _ _ : HermitianMat _ _)] +meta def evalHermitianMatMk : PositivityExt where eval {_u _α} _zα _pα? e := + match _pα? with | none => pure .none | some _ => do + let .app (.app _mkFn val) _proof ← whnfR e | throwError "not Subtype.mk" + match ← bestResult val with + | (true, pa) => + pure (.positive (← mkAppM ``HermitianMat.subtype_mk_pos #[pa])) + | (false, pa) => + pure (.nonnegative (← mkAppM ``HermitianMat.subtype_mk_nonneg #[pa])) + +/-- Positivity extension for eigenvalues of a Matrix: `0 ≤ (_ : M.IsHermitian).eigenvalues i`. +Will try to prove `0 ≤ M` in the `MatrixOrder`. If the proof is `A.H`, i.e. M comes from a +HermitianMat, this will give `0 ≤ A.mat` which becomes `0 ≤ A` later. -/ +@[positivity Matrix.IsHermitian.eigenvalues _ _] +meta def evalMatrixEigenvalues : PositivityExt where eval {_u _α} _zα _pα? e := + match _pα? with | none => pure .none | some _ => do + let .app (.app _eigenvaluesFn hProof) _i ← whnfR e | throwError "not eigenvalues application" + let pType ← inferType hProof + if pType.isAppOf ``Matrix.IsHermitian then + let M ← pure pType.appArg! + let (_, pa) ← bestResult M + let pa ← try mkAppM ``le_of_lt #[pa] catch _ => pure pa + pure (.nonnegative (← mkAppM ``Matrix.eigenvalues_nonneg #[pa, _i])) + else + throwError "not a Matrix.IsHermitian" + +-- Tests +section tests + +variable [DecidableEq n] [DecidableEq m] +open MatrixOrder + +-- Test: eigenvalues nonneg from PSD HermitianMat +example (A : HermitianMat n ℂ) (hA : 0 < A) (i : n) : 0 ≤ A.H.eigenvalues i := by + positivity + +-- Test: A.mat nonneg from A nonneg +example (A : HermitianMat n ℂ) (hA : 0 ≤ A) : 0 ≤ A.mat := by positivity +example (A : HermitianMat n ℂ) (hA : 0 < A) : 0 < A.mat := by positivity +example (A : HermitianMat n ℂ) (hA : 0 ≤ A) : 0 ≤ A.val := by positivity +example (A : HermitianMat n ℂ) (hA : 0 < A) : 0 < A.val := by positivity + +-- Test: Mᴴ * M nonneg as Matrix +example (M : Matrix m n ℂ) : 0 ≤ M.conjTranspose * M := by positivity + +-- Test: M * Mᴴ nonneg as Matrix +example (M : Matrix n m ℂ) : 0 ≤ M * M.conjTranspose := by positivity + +-- Test: ⟨Mᴴ * M, _⟩ nonneg as HermitianMat + +example (M : Matrix m n ℂ) : + (0 : HermitianMat n ℂ) ≤ ⟨M.conjTranspose * M, Matrix.isHermitian_conjTranspose_mul_self M⟩ + := by + exact zero_le_iff.mpr (Matrix.posSemidef_conjTranspose_mul_self M) + +-- Test: ⟨M * Mᴴ, _⟩ nonneg as HermitianMat + +example (M : Matrix n m ℝ) : + (0 : HermitianMat n ℝ) ≤ ⟨M * M.conjTranspose, Matrix.isHermitian_mul_conjTranspose_self M⟩ + := by + exact zero_le_iff.mpr (Matrix.posSemidef_self_mul_conjTranspose M) + +example (M : Matrix n n ℂ) (i : n) (A : HermitianMat n ℂ) (hA : 0 ≤ A) : + 0 ≤ (A + ⟨_, M.isHermitian_mul_conjTranspose_self⟩ + 0).H.eigenvalues i := by + positivity + +end tests +end MatrixPositivity + + +end HermitianMat diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/HermitianMat/Proj.lean b/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/HermitianMat/Proj.lean new file mode 100644 index 0000000000..6290006062 --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/HermitianMat/Proj.lean @@ -0,0 +1,572 @@ +/- +Copyright (c) 2025 Leonardo A Lessa. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Leonardo A Lessa, Alex Meiburg +-/ +module + +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.CFC + +public import Mathlib.Analysis.CStarAlgebra.Classes +public import Mathlib.Analysis.InnerProductSpace.Positive +public import Mathlib.Analysis.SpecialFunctions.ContinuousFunctionalCalculus.PosPart.Basic + +/-! + +# Projectors associated to Hermitian matrices + + * `projector`: The `HermitianMat` that projects onto a given submodule + * `supportProj`: The `HermitianMat` that projects onto the range (nonzero eigenvalues) + * `kerProj`: The `HermitianMat` that projects onto the kernel + * `projLE`: With notation `{A ≤ₚ B}`, `projLE A B` is the projector onto the nonnegative + eigenspace of `B - A`. + * `projLT`: With notation `{A <ₚ B}`, `projLT A B` is the projector onto the positive + eigenspace of `B - A`. + * Positive and negative part, written `A⁺` and `A⁻`, give the restriction of a HermitianMat + onto its positive (resp. negative) eigenvalues; equivalently, it's nonnegative (resp. + nonpositive) eigenvalues. +-/ + +@[expose] public section + +noncomputable section +namespace HermitianMat + +variable {n : Type*} [Fintype n] [DecidableEq n] +variable {𝕜 : Type*} [RCLike 𝕜] +variable {ι : Type*} [Fintype ι] (S : Submodule 𝕜 (EuclideanSpace 𝕜 n)) + +variable (A B : HermitianMat n 𝕜) + +open scoped InnerProductSpace + +/-- +Given a Submodule (EuclideanSpace ...) to HermitianMat, this gives the projector onto that subspace, +i.e. a matrix that squares to itself, preserves vectors in the submodule, and zeroes out anything +in the orthogonal complement of that submodule. +-/ +noncomputable def projector (S : Submodule 𝕜 (EuclideanSpace 𝕜 n)) : HermitianMat n 𝕜 := + let P := S.subtypeL.comp S.orthogonalProjectionOnto + ⟨P.toMatrix (EuclideanSpace.basisFun n 𝕜).toBasis (EuclideanSpace.basisFun n 𝕜).toBasis, by + ext i j + have h1 := S.inner_starProjection_left_eq_right (EuclideanSpace.single i 1) + (EuclideanSpace.single j 1) + simp_all? [EuclideanSpace.inner_single_right, EuclideanSpace.inner_single_left] + exact h1⟩ + +theorem projector_add_orthogonal : projector S + projector Sᗮ = 1 := by + unfold projector; + erw [ Subtype.mk_eq_mk ]; + ext i j; simp [ LinearMap.toMatrix_apply, Matrix.one_apply ]; + +theorem projector_nonneg : 0 ≤ projector S := by + rw [zero_le_iff] + unfold projector + let P := S.subtypeL.comp S.orthogonalProjectionOnto + have hP : P.toLinearMap.IsSymmetricProjection := by + simpa [Submodule.starProjection, P] using + (Submodule.isSymmetricProjection_starProjection (U := S)) + exact LinearMap.posSemidef_toMatrix_iff _ |>.2 + ((LinearMap.IsIdempotentElem.isPositive_iff_isSymmetric hP.1).2 hP.2) + +@[simp] +theorem projector_ker : (projector S).ker = Sᗮ := by + ext v + change (Matrix.toEuclideanLin + (LinearMap.toMatrix (PiLp.basisFun 2 𝕜 n) (PiLp.basisFun 2 𝕜 n) + (S.subtypeL.comp S.orthogonalProjectionOnto)) v = 0 ↔ v ∈ Sᗮ) + rw [show Matrix.toEuclideanLin = Matrix.toLpLin (2 : ENNReal) (2 : ENNReal) from rfl, + Matrix.toLpLin_eq_toLin, Matrix.toLin_toMatrix] + exact Submodule.starProjection_apply_eq_zero_iff (K := S) + +@[simp] +theorem trace_projector : (projector S).trace = (Module.finrank 𝕜 S : ℝ) := by + suffices h_trace : ((S.subtype ∘ₗ S.orthogonalProjectionOnto).toMatrix + (EuclideanSpace.basisFun n 𝕜).toBasis (EuclideanSpace.basisFun n 𝕜).toBasis).trace = + Module.finrank 𝕜 S by + rw [trace_eq_re_trace] + change RCLike.re ((S.subtype ∘ₗ S.orthogonalProjectionOnto).toMatrix + (EuclideanSpace.basisFun n 𝕜).toBasis + (EuclideanSpace.basisFun n 𝕜).toBasis).trace = _ + rw [h_trace] + simp + suffices h_trace : ((S.subtype ∘ₗ S.orthogonalProjectionOnto).toMatrix + (EuclideanSpace.basisFun n 𝕜).toBasis (EuclideanSpace.basisFun n 𝕜).toBasis).trace = + (LinearMap.id.toMatrix (Module.finBasis 𝕜 S) (Module.finBasis 𝕜 S)).trace by + simp [h_trace] + rw [LinearMap.toMatrix_comp _ (Module.finBasis 𝕜 ↥S), Matrix.trace_mul_comm, ← + LinearMap.toMatrix_comp] + congr 2 + ext1 + simp [Submodule.orthogonalProjectionOnto_mem_subspace_eq_self] + +/-- +The `HermitianMat.projector` for the `HermitianMat.support` submodule. +-/ +noncomputable def supportProj (A : HermitianMat n 𝕜) : HermitianMat n 𝕜 := projector A.support + +/-- +The `HermitianMat.projector` for the `HermitianMat.ker` submodule. +-/ +noncomputable def kerProj (A : HermitianMat n 𝕜) : HermitianMat n 𝕜 := projector A.ker + +@[simp] +theorem supportProj_ker : A.supportProj.ker = A.ker := by + rw [supportProj, projector_ker, support_orthogonal_eq_range] + +@[simp] +theorem kerProj_ker : A.kerProj.ker = A.support := by + rw [kerProj, projector_ker, ker_orthogonal_eq_support] + +@[simp] +theorem kerProj_add_supportProj : A.kerProj + A.supportProj = 1 := by + rw [← projector_add_orthogonal A.ker, ker_orthogonal_eq_support, kerProj, supportProj] + +@[simp] +theorem kerProj_of_nonSingular [NonSingular A] : A.kerProj = 0 := by + simp only [kerProj, nonSingular_ker_bot, HermitianMat.ext_iff] + simp [projector] + +@[simp] +theorem supportProj_of_nonSingular [NonSingular A] : A.supportProj = 1 := by + simpa using A.kerProj_add_supportProj + +/-- +The projector onto a submodule S is the sum of the outer products of the vectors in an + orthonormal basis of S. +-/ +theorem projector_eq_sum_rankOne (b : OrthonormalBasis ι 𝕜 S) : + (projector S).mat = ∑ i, Matrix.vecMulVec (S.subtype (b i)) (star (S.subtype (b i))) := by + unfold projector; + ext i j; + field_simp; + simp? [Matrix.vecMulVec] + -- By definition of orthogonal projection, we can write the projection of $e_j$ onto $S$ as + -- $\sum_{k} \langle e_j, b_k \rangle b_k$. + have h_proj : ∀ j : n, S.orthogonalProjectionOnto (EuclideanSpace.single j 1) = ∑ k, (star (b + k |>.1 j)) • (b k |>.1) := by + intro j + have h_proj : S.orthogonalProjectionOnto (EuclideanSpace.single j 1) = ∑ k, (inner 𝕜 (b k + |>.1) (EuclideanSpace.single j 1)) • (b k |>.1) := by + convert b.sum_repr ( S.orthogonalProjectionOnto ( EuclideanSpace.single j 1 ) ) using 1; + constructor <;> intro h <;> simp_all [ Subtype.ext_iff, b.repr_apply_apply ]; + convert! h_proj using 3 + simp [ inner]; + convert! congr_arg ( fun x : EuclideanSpace ( _ ) n => x i ) ( h_proj j ) using 1 + simp [ Matrix.sum_apply, mul_comm ] + +/-- +The projector onto the support of A is the sum of the projections onto the eigenvectors with + non-zero eigenvalues. +-/ +lemma projector_support_eq_sum : A.supportProj.mat = + ∑ i, (if A.H.eigenvalues i = 0 then 0 else 1) • + Matrix.vecMulVec (A.H.eigenvectorBasis i) (star (A.H.eigenvectorBasis i)) := by + have h_support : A.support = Submodule.span (𝕜) (Set.image (fun i => A.H.eigenvectorBasis i) { + i | A.H.eigenvalues i ≠ 0 }) := by + refine le_antisymm ?_ ?_; + · intro x hx; + -- By definition of $A.support$, we know that $x$ is in the orthogonal complement of + -- the kernel of $A$. + have h_orthogonal_complement : x ∈ (A.ker : Submodule (𝕜) (EuclideanSpace (𝕜) n))ᗮ := by + convert hx using 1; + exact ker_orthogonal_eq_support A; + -- By definition of $A.ker$, we know that $x$ is orthogonal to all eigenvectors with + -- zero eigenvalues. + have h_orthogonal_zero_eigenvalues : ∀ i, A.H.eigenvalues i = 0 → inner (𝕜) + (A.H.eigenvectorBasis i) x = 0 := by + intro i hi + have h_eigenvector_zero : A.mat.mulVec (A.H.eigenvectorBasis i) = 0 := by + have := A.H.mulVec_eigenvectorBasis i; aesop; + convert h_orthogonal_complement ( A.H.eigenvectorBasis i ) _ using 1; + exact (mem_ker_iff_mulVec_zero A ((H A).eigenvectorBasis i)).mpr h_eigenvector_zero; + -- By definition of $A.ker$, we know that $x$ can be written as a linear combination + -- of eigenvectors with non-zero eigenvalues. + have h_decomp : x = ∑ i, (inner (𝕜) (A.H.eigenvectorBasis i) x) • A.H.eigenvectorBasis i := by + exact Eq.symm (OrthonormalBasis.sum_repr' (H A).eigenvectorBasis x); + rw [ h_decomp ]; + exact Submodule.sum_mem _ fun i _ => if hi : A.H.eigenvalues i = 0 then by simp + [h_orthogonal_zero_eigenvalues i hi ] else Submodule.smul_mem _ _ ( + Submodule.subset_span ⟨ i, hi, rfl ⟩ ); + · rw [ Submodule.span_le, Set.image_subset_iff ]; + intro i hi; + simp_all? [ HermitianMat.support ]; + use (1 / A.H.eigenvalues i) • A.H.eigenvectorBasis i; + convert congr_arg ( fun x => ( 1 / A.H.eigenvalues i ) • x ) ( A.H.mulVec_eigenvectorBasis + i ) using 1 + simp? [hi] + simp? [ funext_iff, Matrix.mulVec, dotProduct ]; + exact PiLp.ext_iff; + have h_orthonormal_basis : ∃ b : OrthonormalBasis {i : n | A.H.eigenvalues i ≠ 0} (𝕜) + (Submodule.span (𝕜) (Set.image (fun i => A.H.eigenvectorBasis i) {i | A.H.eigenvalues i ≠ + 0})), ∀ i, b i = A.H.eigenvectorBasis i := by + refine ⟨OrthonormalBasis.mk (v := fun i => + ⟨A.H.eigenvectorBasis i, Submodule.subset_span (Set.mem_image_of_mem _ i.2)⟩) ?_ ?_, ?_⟩ + all_goals simp? [Orthonormal] + · intro i j hij; have := A.H.eigenvectorBasis.orthonormal; simp_all? [ orthonormal_iff_ite ]; + exact fun h => hij <| Subtype.ext h; + · rw [ Submodule.eq_top_iff' ]; + rintro ⟨ x, hx ⟩; + rw [ Submodule.mem_span ] at hx ⊢; + intro p hp; specialize hx ( Submodule.map ( Submodule.subtype _ ) p ); simp_all? [ + Set.range_subset_iff ]; + exact hx fun i hi => ⟨ _, hp i hi, rfl ⟩; + obtain ⟨ b, hb ⟩ := h_orthonormal_basis + have h_sum_rankOne : (projector A.support).mat = ∑ i, Matrix.vecMulVec (b i) (star (b i)) := by + convert! projector_eq_sum_rankOne _ b using 1 + simp [h_support] at * + simp_all? [ Finset.sum_ite ]; + convert h_sum_rankOne using 1; + · exact h_support ▸ rfl; + · refine Finset.sum_bij ( fun i hi => ⟨ i, by simpa using hi ⟩ ) ?_ ?_ ?_ ?_ <;> simp [ + Finset.mem_filter, hb ] + +/- +`HermitianMat.supportProj` as a cfc. +-/ +theorem supportProj_eq_cfc : A.supportProj = A.cfc (if · = 0 then 0 else 1) := by + apply HermitianMat.ext; + rw [HermitianMat.cfc_toMat_eq_sum_smul_proj]; + convert projector_support_eq_sum A using 1; + refine Finset.sum_congr rfl fun i _ => ?_; + ext x y + simp? [ Matrix.vecMulVec, Matrix.mul_apply ]; + simp? [ Matrix.single ]; + simp? [ Finset.sum_ite, Finset.filter_eq, Finset.filter_and ]; + rw [ Finset.sum_eq_single i ] <;> aesop + +/-- Projector onto the non-negative eigenspace of `B - A`. Accessible by the notation +`{A ≤ₚ B}`, which is scoped to `HermitianMat`. This is the unique maximum operator `P` +such that `P^2 = P` and `P * A * P ≤ P * B * P` in the Loewner order. -/ +def projLE (A B : HermitianMat n 𝕜) : HermitianMat n 𝕜 := + (B - A).cfc (fun x ↦ if 0 ≤ x then 1 else 0) + +/-- Projector onto the positive eigenspace of `B - A`. Accessible by the notation +`{A <ₚ B}`, which is scoped to `HermitianMat`. Compare with `proj_le`. -/ +noncomputable def projLT (A B : HermitianMat n 𝕜) : HermitianMat n 𝕜 := + (B - A).cfc (fun x ↦ if 0 < x then 1 else 0) + +-- Note this is in the opposite direction as in the Stein's Lemma paper, which uses `≥ₚ` +-- as the default ordering. We offer the `≥ₚ` notation which is the same with the arguments +-- flipped, similar to how `GT.gt` is defeq to `LT.lt` with arguments flipped. +-- We put the ≥ₚ first, since both can delaborate and we want to show the ≤ₚ one. +/-- The spectral projection onto the nonnegative spectral subspace of `A - B`. -/ +scoped notation "{" A " ≥ₚ " B "}" => projLE B A +/-- The spectral projection onto the nonnegative spectral subspace of `B - A`. -/ +scoped notation "{" A " ≤ₚ " B "}" => projLE A B + +/-- The spectral projection onto the positive spectral subspace of `A - B`. -/ +scoped notation "{" A " >ₚ " B "}" => projLT B A +/-- The spectral projection onto the positive spectral subspace of `B - A`. -/ +scoped notation "{" A " <ₚ " B "}" => projLT A B + +theorem projLE_def : {A ≤ₚ B} = (B - A).cfc (fun x ↦ if 0 ≤ x then 1 else 0) := by + rfl + +theorem projLT_def : {A <ₚ B} = (B - A).cfc (fun x ↦ if 0 < x then 1 else 0) := by + rfl + +theorem projLE_sq : {A ≤ₚ B}^2 = {A ≤ₚ B} := by + rw [projLE_def, ← cfc_pow, ← cfc_comp] + congr! 2 with x + simp + +theorem projLT_sq : {A <ₚ B}^2 = {A <ₚ B} := by + rw [projLT_def, ← cfc_pow, ← cfc_comp] + congr! 2 with x + simp + +theorem projLE_zero_cfc : {0 ≤ₚ A} = A.cfc (fun x ↦ if 0 ≤ x then 1 else 0) := by + simp only [projLE_def, sub_zero] + +theorem projLT_zero_cfc : {0 <ₚ A} = A.cfc (fun x ↦ if 0 < x then 1 else 0) := by + simp only [projLT_def, sub_zero] + +theorem projLE_zero_cfc' : {A ≤ₚ 0} = A.cfc (fun x ↦ if x ≤ 0 then 1 else 0) := by + simp only [projLE_def, zero_sub] + --TODO: Should do a `HermitianMat.cfc_comp_neg`? + nth_rw 1 [← cfc_id A] + rw [← cfc_neg, ← cfc_comp] + congr! 2 with x + simp + +theorem projLT_zero_cfc' : {A <ₚ 0} = A.cfc (fun x ↦ if x < 0 then 1 else 0) := by + simp only [projLT_def, zero_sub] + --TODO: Should do a `HermitianMat.cfc_comp_neg`? + nth_rw 1 [← cfc_id A] + rw [← cfc_neg, ← cfc_comp] + congr! 2 with x + simp + +theorem projLE_nonneg : 0 ≤ {A ≤ₚ B} := by + rw [projLE_def, cfc_nonneg_iff] + intro i + apply ite_nonneg <;> norm_num + +theorem projLT_nonneg : 0 ≤ {A <ₚ B} := by + rw [projLT_def, cfc_nonneg_iff] + intro i + apply ite_nonneg <;> norm_num + +theorem projLE_le_one : {A ≤ₚ B} ≤ 1 := by + --The whole `rw` line is a defeq, i.e. `change _root_.cfc _ (B - A).mat ≤ 1` works too. + --TODO better API. + open MatrixOrder in + change _root_.cfc (fun x ↦ if 0 ≤ x then 1 else 0) (B - A).mat ≤ 1 + apply cfc_le_one (f := fun x ↦ if 0 ≤ x then 1 else 0) + intros; split <;> norm_num + +open MatrixOrder in +theorem projLE_mul_nonneg : 0 ≤ {A ≤ₚ B}.mat * (B - A).mat := by + rw [projLE_def] + nth_rewrite 2 [← cfc_id (B - A)] + rw [← mat_cfc_mul] + apply cfc_nonneg + aesop + +open MatrixOrder in +theorem projLE_mul_le : {A ≤ₚ B}.mat * A.mat ≤ {A ≤ₚ B}.mat * B.mat := by + rw [← sub_nonneg, ← mul_sub_left_distrib] + exact projLE_mul_nonneg A B + +@[simp] +theorem proj_le_add_lt : {A <ₚ B} + {B ≤ₚ A} = 1 := by + rw [projLE_def, projLT_def] + rw [← neg_sub A B] + nth_rw 1 [← cfc_id (A - B)] + rw[← cfc_neg, ← cfc_comp, ← cfc_add] + convert cfc_const (A - B) 1 with x + · simp; grind + · simp + +theorem conj_lt_add_conj_le : A.conj {A <ₚ 0} + A.conj {0 ≤ₚ A} = A := by + rw (occs := [2, 4, 5]) [← cfc_id A] + rw [projLT_zero_cfc', projLE_zero_cfc, cfc_conj, cfc_conj, ← cfc_add] + congr; ext + simp; grind + +/- +The projection onto the support can be split into the projection onto positive +and negative eigenvalues. +-/ +theorem supportProj_eq_proj_lt_add_proj_lt (A : HermitianMat n 𝕜) : + A.supportProj = {A <ₚ 0} + {0 <ₚ A} := by + rw [supportProj_eq_cfc, projLT_zero_cfc, projLT_zero_cfc', ← cfc_add A] + congr 1 + grind only [Pi.add_apply] + +/-- The positive part of a Hermitian matrix: the projection onto its positive eigenvalues. -/ +instance : PosPart (HermitianMat n 𝕜) where + posPart A := A.cfc (fun x ↦ x ⊔ 0) + +/-- The negative part of a Hermitian matrix: the projection onto its negative eigenvalues. -/ +instance : NegPart (HermitianMat n 𝕜) where + negPart A := A.cfc (fun x ↦ -x ⊔ 0) + +theorem posPart_eq_cfc_max : A⁺ = A.cfc (fun x ↦ x ⊔ 0) := by + rfl + +theorem negPart_eq_cfc_min : A⁻ = A.cfc (fun x ↦ -x ⊔ 0) := by + rfl + +theorem posPart_eq_cfc_ite : A⁺ = A.cfc (fun x ↦ if 0 ≤ x then x else 0) := by + simp only [← max_def', posPart_eq_cfc_max] + +theorem negPart_eq_cfc_ite : A⁻ = A.cfc (fun x ↦ if x ≤ 0 then -x else 0) := by + simp only [negPart_eq_cfc_min, max_def] + congr; ext + split <;> split <;> grind + +/-- There is an existing (very slow) `PosPart` instance on `Matrix n n 𝕜`, this shows +that this is equal. -/ +theorem posPart_eq_posPart_toMat : A⁺ = A.mat⁺ := by + rw [CFC.posPart_def, cfcₙ_eq_cfc] + rfl + +/-- There is an existing (very slow) `PosPart` instance on `Matrix n n 𝕜`, this shows +that this is equal. -/ +theorem negPart_eq_negPart_toMat : A⁻ = A.mat⁻ := by + rw [CFC.negPart_def, cfcₙ_eq_cfc] + rfl + +/-- The positive part can be equivalently described as the nonnegative part. -/ +theorem posPart_eq_cfc_lt : A⁺ = A.cfc (fun x ↦ if 0 < x then x else 0) := by + rw [posPart_eq_cfc_ite] + congr with x + rcases lt_trichotomy x 0 <;> grind + +/-- The negative part can be equivalently described as the nonpositive part. -/ +theorem negPart_eq_cfc_lt : A⁻ = A.cfc (fun x ↦ if x < 0 then -x else 0) := by + rw [negPart_eq_cfc_ite] + congr with x + rcases lt_trichotomy x 0 <;> grind + +theorem posPart_add_negPart : A⁺ - A⁻ = A := by + rw [posPart_eq_cfc_ite, negPart_eq_cfc_lt, ← cfc_sub] + convert cfc_id A + ext; dsimp; grind + +theorem posPart_eq_self {A : HermitianMat n 𝕜} (hA : 0 ≤ A) : + A⁺ = A := by + nth_rw 2 [← cfc_id A] + apply cfc_congr_of_nonneg hA + grind [Set.EqOn] + +theorem posPart_nonneg : 0 ≤ A⁺ := by + rw [posPart_eq_cfc_ite, cfc_nonneg_iff] + intro; split <;> order + +theorem negPart_nonneg : 0 ≤ A⁻ := by + rw [negPart_eq_cfc_ite, cfc_nonneg_iff] + intro; split <;> grind + +theorem posPart_le : A ≤ A⁺ := by + nth_rw 1 [← cfc_id A] + rw [posPart_eq_cfc_ite, ← sub_nonneg, ← cfc_sub, cfc_nonneg_iff] + intro; simp?; split <;> order + +theorem posPart_mul_negPart : A⁺.mat * A⁻.mat = 0 := by + rw [posPart_eq_cfc_ite, negPart_eq_cfc_ite, ← mat_cfc_mul] + convert congrArg mat (cfc_const A 0) + · grind [Pi.mul_apply, mul_eq_zero] + · simp + +open RealInnerProductSpace + +theorem projLE_inner_nonneg : 0 ≤ ⟪{A ≤ₚ B}, (B - A)⟫ := + --This inner is equal to `(B - A)⁺.trace`, could be better way to describe it + inner_mul_nonneg (projLE_mul_nonneg A B) + +theorem projLE_inner_le : ⟪{A ≤ₚ B}, A⟫ ≤ ⟪{A ≤ₚ B}, B⟫ := by + rw [← sub_nonneg, ← inner_sub_right] + exact projLE_inner_nonneg A B + +open RealInnerProductSpace in +theorem inner_projLE_nonneg : 0 ≤ ⟪{A ≤ₚ B}, (B - A)⟫ := + projLE_inner_nonneg A B + +open RealInnerProductSpace in +theorem inner_projLE_le : ⟪{A ≤ₚ B}, A⟫ ≤ ⟪{A ≤ₚ B}, B⟫ := + projLE_inner_le A B + +--TODO: When we upgrade `cfc_continuous` from 𝕜 to ℂ, we upgrade these too. +@[fun_prop] +theorem posPart_Continuous : Continuous (·⁺ : HermitianMat n ℂ → _) := by + simp_rw [posPart_eq_cfc_max] + fun_prop + +@[fun_prop] +theorem negPart_Continuous : Continuous (·⁻ : HermitianMat n ℂ → _) := by + simp_rw [negPart_eq_cfc_min] + fun_prop + +--Many missing lemmas: see `Mathlib.Algebra.Order.Group.PosPart` for examples +-- (They don't apply here since it's not a Lattice, and there's no well-defined `max` in +-- the Loewner order.) +-- PosPart is Monotone (so `A ≤ B` implies `A⁺ ≤ B⁺`), as is NegPart +-- PosPart and NegPart commute with nonnegative scalar muliptlication +-- `A⁺ ≤ 0 ↔ A⁺ = 0 ↔ A = 0` +-- `0 ≤ A → A⁺ = A` +-- `0 < A → 0 < A⁺` (this is not the PosDef version, this is `≤ && ≠`) +-- `A.PosDef → A⁺.PosDef` +-- versions of those ^^ for negPart +-- simp: 0⁺ = 0, 0⁻ = 0, 1⁺ = 1, 1⁻ = 0 +-- (-A)⁺ = A⁻, (-A)⁻ = A⁺ +-- A⁺⁺ = A⁺, A⁺⁻ = 0 + +-- variable {d : Type*} [Fintype d] [DecidableEq d] (A B : HermitianMat d ℂ) + +theorem one_sub_projLT : 1 - {B ≤ₚ A} = {A <ₚ B} := by + rw [sub_eq_iff_eq_add, proj_le_add_lt] + +open MatrixOrder ComplexOrder in +theorem projLT_mul_nonneg : 0 ≤ {A <ₚ B}.mat * (B - A).mat := by + rw [projLT_def] + nth_rewrite 2 [← cfc_id (B - A)] + rw [← mat_cfc_mul] + apply cfc_nonneg + intros + simp only [Pi.mul_apply, id_eq, ite_mul, one_mul, zero_mul] + split <;> order + +open MatrixOrder ComplexOrder in +theorem proj_lt_mul_lt : {A <ₚ B}.mat * A.mat ≤ {A <ₚ B}.mat * B.mat := by + rw [← sub_nonneg, ← mul_sub_left_distrib] + exact A.projLT_mul_nonneg B + +theorem inner_negPart_nonpos : ⟪A, A⁻⟫ ≤ 0 := by + rw [← neg_le_neg_iff, neg_zero, ← inner_neg_right] + apply inner_mul_nonneg + nth_rw 1 [← A.cfc_id] + rw [negPart_eq_cfc_ite] + rw [← cfc_neg] + rw [← mat_cfc_mul] + change 0 ≤ A.cfc _ + rw [cfc_nonneg_iff] + intro i + dsimp + split_ifs with h + · rw [neg_neg] + exact mul_self_nonneg _ + · simp + +@[simp] +theorem posPart_inner_negPart_zero : ⟪A⁺, A⁻⟫ = 0 := by + have hi := inner_eq_trace_rc A⁺ A⁻ + rw [posPart_mul_negPart, Matrix.trace_zero] at hi + simpa only [map_eq_zero] using hi + +theorem inner_negPart_zero_iff : ⟪A, A⁻⟫ = 0 ↔ 0 ≤ A := by + constructor + · intro h + nth_rw 1 [← posPart_add_negPart A] at h + rw [inner_sub_left, sub_eq_zero, posPart_inner_negPart_zero, eq_comm, inner_self_eq_zero] at h + rw [← zero_smul ℝ 1, ← cfc_const A, negPart_eq_cfc_ite] at h --TODO cfc_zero + rw [cfc_eq_cfc_iff_eqOn, A.H.spectrum_real_eq_range_eigenvalues, Set.eqOn_range] at h + replace h (i) := congrFun h i + simp only [Function.comp_apply, ite_eq_right_iff, neg_eq_zero] at h + rw [zero_le_iff, A.H.posSemidef_iff_eigenvalues_nonneg] + intro i + contrapose! h + use i, h.le, h.ne + · intro h + apply le_antisymm + · exact inner_negPart_nonpos A + · exact inner_ge_zero h (negPart_nonneg A) + +theorem posPart_eq_zero_iff : A⁺ = 0 ↔ A ≤ 0 := by + refine ⟨fun h => by simpa [h] using posPart_le (A := A), fun hA => ?_⟩ + have hnegPart : (-A)⁻ = A⁺ := by + rw [negPart_eq_cfc_ite, posPart_eq_cfc_ite] + nth_rw 1 [← cfc_id A] + rw [← cfc_neg, ← cfc_comp] + congr! 2 with x; simp + have h0 : ⟪-A, A⁺⟫ = 0 := by + simpa [hnegPart] using (inner_negPart_zero_iff (A := -A)).2 (by simpa using hA) + have hA_eq : -A = A⁻ - A⁺ := by + conv_lhs => rw [show A = A⁺ - A⁻ from (posPart_add_negPart A).symm] + abel + have hself : ⟪A⁺, A⁺⟫ = 0 := by + rw [hA_eq, HermitianMat.inner_sub_right, HermitianMat.inner_comm A⁻ A⁺, + posPart_inner_negPart_zero, zero_sub, neg_eq_zero] at h0 + exact h0 + exact inner_self_eq_zero.mp hself + +theorem inner_negPart_neg_iff : ⟪A, A⁻⟫ < 0 ↔ ¬0 ≤ A := by + simp [← inner_negPart_zero_iff, lt_iff_le_and_ne, inner_negPart_nonpos A] + +omit [DecidableEq n] in +/-- The self-duality of the PSD cone: a matrix is PSD iff its inner product with all +nonnegative matrices is non-negative. -/ +theorem nonneg_iff_inner_nonneg (A : HermitianMat n 𝕜) : + 0 ≤ A ↔ ∀ B, 0 ≤ B → 0 ≤ ⟪A, B⟫ := by + classical + use fun h _ ↦ inner_ge_zero h + intro h + contrapose! h + classical + use A⁻, negPart_nonneg A + rwa [inner_negPart_neg_iff] + + +end HermitianMat diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/HermitianMat/Reindex.lean b/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/HermitianMat/Reindex.lean new file mode 100644 index 0000000000..32ac3034fd --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/HermitianMat/Reindex.lean @@ -0,0 +1,121 @@ +/- +Copyright (c) 2025 Alex Meiburg. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Alex Meiburg +-/ +module + +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.Basic +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.ContinuousLinearMap +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.LinearEquiv + +/-! +Much like `Matrix.reindex` and `Matrix.submatrix`, we can reindex a Hermitian matrix to get another +Hermitian matrix; however, this only makes sense when both permutations are the same, accordingly, +`HermitianMat.reindex` only takes one `Equiv` argument (as opposed to `Matrix.reindex`'s two). + +This file then gives relevant lemmas for simplifying this. +-/ + +@[expose] public section +namespace HermitianMat + +variable {d d₂ d₃ d₄ 𝕜 : Type*} [RCLike 𝕜] + +variable (A B : HermitianMat d 𝕜) (e : d ≃ d₂) + +/-- Reindex both rows and columns of a Hermitian matrix along an equivalence. -/ +def reindex (e : d ≃ d₂) : HermitianMat d₂ 𝕜 := + ⟨A.mat.reindex e e, A.H.submatrix e.symm⟩ + +@[simp] +theorem mat_reindex : (A.reindex e).mat = A.mat.reindex e e := by + rfl + +/-! Our simp-normal form for expressions involving `HermitianMat.reindex` is that we try to push +the reindexing as far out as possible, so that it can be absorbed by `HermitianMat.trace`, or +cancelled our in a `HermitianMat.inner`. In places where it commutes (like `HermitianMat.inner`) +we push it to the right side. One downside is that we're not as likely to hit `reindex_one`. -/ + +@[simp] +theorem reindex_refl (A : HermitianMat d 𝕜) : + A.reindex (.refl _) = A := by + rfl + +@[simp] +theorem reindex_reindex (A : HermitianMat d 𝕜) (e : d ≃ d₂) (f : d₂ ≃ d₃) : + (A.reindex e).reindex f = A.reindex (e.trans f) := by + ext1; simp + +@[simp] +theorem reindex_zero : (0 : HermitianMat d 𝕜).reindex e = 0 := by + ext1; simp + +@[simp] +theorem reindex_one [DecidableEq d] [DecidableEq d₂] : + (1 : HermitianMat d 𝕜).reindex e = 1 := by + ext1 + simp [reindex] + +@[simp] +theorem reindex_add : A.reindex e + B.reindex e = (A + B).reindex e := by + ext1; simp [Matrix.submatrix_add] + +@[simp] +theorem reindex_sub : A.reindex e - B.reindex e = (A - B).reindex e := by + ext1; simp [Matrix.submatrix_sub] + +@[simp] +theorem reindex_neg : (-A).reindex e = -(A.reindex e) := by + ext1; simp [Matrix.submatrix_neg] + +@[simp] +theorem reindex_smul (c : ℝ) : (c • A).reindex e = c • (A.reindex e) := by + ext1; simp [Matrix.submatrix_smul] + +@[simp] +theorem reindex_conj [Fintype d₂] [Fintype d] (B : Matrix d₃ d₂ 𝕜) : + (A.reindex e).conj B = A.conj (B.submatrix id e) := by + ext1 + simp only [conj_apply, mat_reindex, Matrix.reindex_apply, mat_mk] + rw [← Matrix.submatrix_id_mul_right, Matrix.mul_assoc] + rw [← Matrix.submatrix_id_mul_left, ← Matrix.mul_assoc] + simp + +variable [Fintype d] + +theorem conj_submatrix (B : Matrix d₂ d₄ 𝕜) (e : d₃ ≃ d₂) (f : d → d₄) : + A.conj (B.submatrix e f) = (A.conj (B.submatrix id f)).reindex e.symm := by + ext1 + change B.submatrix e f * A.mat * (B.submatrix e f).conjTranspose = + (B.submatrix id f * A.mat * (B.submatrix id f).conjTranspose).submatrix e e + simp [← Matrix.submatrix_mul_equiv (e₂ := .refl d)] + +theorem reindex_eq_conj [DecidableEq d] (e : d ≃ d₂) : + A.reindex e = A.conj (Matrix.reindex e (.refl d) 1) := by + apply HermitianMat.ext + change Matrix.reindex e e A.mat = + Matrix.reindex e (Equiv.refl d) 1 * A.mat * + (Matrix.reindex e (Equiv.refl d) 1).conjTranspose + ext i j + change A.mat (e.symm i) (e.symm j) = + ∑ k, (∑ l, (if e.symm i = l then 1 else 0) * A.mat l k) * + star (if e.symm j = k then (1 : 𝕜) else 0) + simp [ite_mul, apply_ite] + +variable [Fintype d₂] [DecidableEq d] [DecidableEq d₂] + +theorem ker_reindex : + (A.reindex e).ker = A.ker.comap (LinearEquiv.euclideanOfRelabel 𝕜 e).toLinearMap := by + change (Matrix.toEuclideanLin (Matrix.reindex e e A.mat)).ker = _ + rw [Matrix.reindex_toEuclideanLin, LinearEquiv.ker_comp, LinearMap.ker_comp] + rfl + +@[simp] +theorem ker_reindex_le_iff : + (A.reindex e).ker ≤ (B.reindex e).ker ↔ A.ker ≤ B.ker := by + rw [ker_reindex, ker_reindex] + apply Submodule.comap_le_comap_iff_of_surjective + exact LinearEquiv.surjective (LinearEquiv.euclideanOfRelabel 𝕜 e) + +end HermitianMat diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/HermitianMat/Trace.lean b/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/HermitianMat/Trace.lean new file mode 100644 index 0000000000..18274db177 --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/HermitianMat/Trace.lean @@ -0,0 +1,258 @@ +/- +Copyright (c) 2025 Alex Meiburg. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Alex Meiburg +-/ +module + +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.Reindex + +/-! # Trace of Hermitian Matrices + +While the trace of a Hermitian matrix is, in informal math, typically just "the same as" a trace of +a matrix that happens to be Hermitian - it is a real number, not a complex number. Or more + generally, +it is a self-adjoint element of the base `StarAddMonoid`. + +Working directly with `Matrix.trace` then means that there would be constant casts between rings, +chasing imaginary parts and inequalities and so on. By defining `HermitianMat.trace` as its own +operation, we encapsulate the mess and give a clean interface. + +The `IsMaximalSelfAdjoint` class is used so that (for example) for matrices over ℤ or ℝ, +`HermitianMat.trace` works as well and is in fact defeq to `Matrix.trace`. For ℂ or `RCLike`, +it uses the real part. +-/ + +@[expose] public section + +namespace HermitianMat + +variable {R n m α : Type*} [Star R] [TrivialStar R] [Fintype n] [Fintype m] + +section star +variable [Ring α] [StarAddMonoid α] [CommSemiring R] [Algebra R α] [IsMaximalSelfAdjoint R α] + +/-- The trace of the matrix. This requires a `IsMaximalSelfAdjoint R α` instance, and then maps from + `HermitianMat n α` to `R`. This means that the trace of (say) a `HermitianMat n ℤ` gives + values in ℤ, + but that the trace of a `HermitianMat n ℂ` gives values in ℝ. The fact that traces are + "automatically" + real reduces coercions down the line. -/ +def trace (A : HermitianMat n α) : R := + IsMaximalSelfAdjoint.selfadjMap (A.mat.trace) + +/-- `HermitianMat.trace` reduces to `Matrix.trace` in the algebra. -/ +theorem trace_eq_trace (A : HermitianMat n α) : algebraMap R α A.trace = Matrix.trace A.mat := by + rw [trace, Matrix.trace, map_sum, map_sum] + congr! 1 + exact IsMaximalSelfAdjoint.selfadj_algebra (Matrix.IsHermitian.apply A.H _ _) + +variable [StarModule R α] in +@[simp] +theorem trace_smul (A : HermitianMat n α) (r : R) : (r • A).trace = r * A.trace := by + simp [trace, IsMaximalSelfAdjoint.selfadj_smul] + +end star +section semiring +variable [CommSemiring R] [Ring α] [StarAddMonoid α] [Algebra R α] [IsMaximalSelfAdjoint R α] + +@[simp] +theorem trace_zero : (0 : HermitianMat n α).trace = 0 := by + simp [trace] + +@[simp] +theorem trace_add (A B : HermitianMat n α) : (A + B).trace = A.trace + B.trace := by + simp [trace] + +end semiring +section ring + +variable [CommRing R] [Ring α] [StarAddMonoid α] [Algebra R α] [IsMaximalSelfAdjoint R α] +@[simp] +theorem trace_neg (A : HermitianMat n α) : (-A).trace = -A.trace := by + simp [trace] + +@[simp] +theorem trace_sub (A B : HermitianMat n α) : (A - B).trace = A.trace - B.trace := by + simp [trace] + +end ring +section starring + +variable [CommRing R] [CommRing α] [StarRing α] [Algebra R α] [IsMaximalSelfAdjoint R α] + +--Move somewhere else? Needs to import `IsMaximalSelfAdjoint`, so maybe just here. +theorem _root_.Matrix.IsHermitian.isSelfAdjoint_trace {A : Matrix n n α} (hA : A.IsHermitian) : + IsSelfAdjoint A.trace := by + simp [Matrix.trace, IsSelfAdjoint, ← Matrix.star_apply, show star A = A from hA] + +variable (A : HermitianMat m α) (B : HermitianMat n α) + +@[simp] +theorem trace_kronecker [FaithfulSMul R α] : (A ⊗ₖ B).trace = A.trace * B.trace := by + apply FaithfulSMul.algebraMap_injective R α + simp only [trace, kronecker_mat] + rw [Matrix.trace_kronecker A.mat B.mat] + simp only [map_mul] + have hA := A.H.isSelfAdjoint_trace + have hB := B.H.isSelfAdjoint_trace + open IsMaximalSelfAdjoint in + rw [selfadj_algebra hA, selfadj_algebra hB, selfadj_algebra (hA.mul hB)] + +end starring + +section trivialstar + +variable [Star α] [TrivialStar α] [CommSemiring α] + +/-- `HermitianMat.trace` reduces to `Matrix.trace` when the elements are a `TrivialStar`. -/ +@[simp] +theorem trace_eq_trace_trivial (A : HermitianMat n ℝ) : A.trace = A.mat.trace := by + rw [← trace_eq_trace] + rfl + +end trivialstar + +section RCLike + +variable {n m 𝕜 : Type*} [Fintype n] [Fintype m] [RCLike 𝕜] + +theorem trace_eq_re_trace (A : HermitianMat n 𝕜) : A.trace = RCLike.re A.mat.trace := by + rfl + +@[simp] +theorem trace_one [DecidableEq n] : (1 : HermitianMat n 𝕜).trace = Fintype.card n := by + simp [trace_eq_re_trace] + +/-- `HermitianMat.trace` reduces to `Matrix.trace` when the elements are `RCLike`. -/ +@[simp] +theorem trace_eq_trace_rc (A : HermitianMat n 𝕜) : A.trace = A.mat.trace := by + rw [trace, Matrix.trace, map_sum, RCLike.ofReal_sum] + congr 1 + exact Matrix.IsHermitian.coe_re_diag A.H + +theorem trace_diagonal {T : Type*} [Fintype T] [DecidableEq T] (f : T → ℝ) : + (diagonal 𝕜 f).trace = ∑ i, f i := by + rw [trace_eq_re_trace] + simp [HermitianMat.diagonal, Matrix.trace] + +theorem sum_eigenvalues_eq_trace [DecidableEq n] (A : HermitianMat n 𝕜) : + ∑ i, A.H.eigenvalues i = A.trace := by + convert! congrArg RCLike.re A.H.sum_eigenvalues_eq_trace + rw [RCLike.ofReal_re] + +--Proving that traces are 0 or 1 is common enough that we have a convenience lemma here for turning +--statements about HermitianMat traces into Matrix traces. +theorem trace_eq_zero_iff (A : HermitianMat n 𝕜) : A.trace = 0 ↔ A.mat.trace = 0 := by + rw [← trace_eq_trace_rc] + exact ⟨mod_cast id, mod_cast id⟩ + +theorem trace_eq_one_iff (A : HermitianMat n 𝕜) : A.trace = 1 ↔ A.mat.trace = 1 := by + rw [← trace_eq_trace_rc] + exact ⟨mod_cast id, mod_cast id⟩ + +@[simp] +theorem trace_reindex (A : HermitianMat n ℂ) (e : n ≃ m) : + (A.reindex e).trace = A.trace := by + rw [trace_eq_re_trace, trace_eq_re_trace, mat_reindex] + simp [Matrix.reindex_apply] + +end RCLike +section partialTrace +section addCommGroup + +variable [AddCommGroup α] [StarAddMonoid α] +omit [Fintype n] + +/-- The partial trace over the left factor of a Hermitian matrix indexed by a product. -/ +def traceLeft (A : HermitianMat (m × n) α) : HermitianMat n α := + ⟨A.mat.traceLeft, A.H.traceLeft⟩ + +/-- The partial trace over the right factor of a Hermitian matrix indexed by a product. -/ +def traceRight (A : HermitianMat (m × n) α) : HermitianMat m α := + ⟨A.mat.traceRight, A.H.traceRight⟩ + +variable (A B : HermitianMat (m × n) α) + +@[simp] +theorem traceLeft_mat : A.traceLeft.mat = A.mat.traceLeft := by + rfl + +@[simp] +theorem traceLeft_add : (A + B).traceLeft = A.traceLeft + B.traceLeft := by + ext1; simp + +@[simp] +theorem traceLeft_neg : (-A).traceLeft = -A.traceLeft := by + ext1; simp + +@[simp] +theorem traceLeft_sub : (A - B).traceLeft = A.traceLeft - B.traceLeft := by + ext1; simp + +variable (A B : HermitianMat (n × m) α) + +@[simp] +theorem traceRight_mat : + (traceRight A).mat = A.mat.traceRight := by + rfl + +@[simp] +theorem traceRight_add : (A + B).traceRight = A.traceRight + B.traceRight := by + ext1; simp + +@[simp] +theorem traceRight_neg : (-A).traceRight = -A.traceRight := by + ext1; simp + +@[simp] +theorem traceRight_sub : (A - B).traceRight = A.traceRight - B.traceRight := by + ext1; simp + +end addCommGroup +section rcLike + +variable {𝕜} [RCLike 𝕜] +variable (A : HermitianMat (m × n) 𝕜) + +omit [Fintype n] in +@[simp] +theorem traceLeft_smul (r : ℝ) : (r • A).traceLeft = r • A.traceLeft := by + ext1; simp + +omit [Fintype m] in +@[simp] +theorem traceRight_smul (r : ℝ) : (r • A).traceRight = r • A.traceRight := by + ext1; simp + +@[simp] +theorem traceLeft_trace : A.traceLeft.trace = A.trace := by + simp [trace_eq_re_trace] + +@[simp] +theorem traceRight_trace : A.traceRight.trace = A.trace := by + simp [trace_eq_re_trace] + +end rcLike +section kron + +variable {m n 𝕜 : Type*} [RCLike 𝕜] +variable (A : HermitianMat m 𝕜) (B : HermitianMat n 𝕜) + +@[simp] +theorem traceLeft_kron [Fintype m] : (A ⊗ₖ B).traceLeft = A.trace • B := by + ext : 2 + simp only [HermitianMat.traceLeft, Matrix.traceLeft, kronecker_mat, mat_mk] + simp [Matrix.trace, RCLike.real_smul_eq_coe_mul, ← Finset.sum_mul] + +@[simp] +theorem traceRight_kron [Fintype n] : (A ⊗ₖ B).traceRight = B.trace • A := by + ext : 2 + simp only [HermitianMat.traceRight, Matrix.traceRight, kronecker_mat, mat_mk] + simp [Matrix.trace, RCLike.real_smul_eq_coe_mul, ← Finset.mul_sum, mul_comm] + +end kron +end partialTrace + + +end HermitianMat diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/IsMaximalSelfAdjoint.lean b/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/IsMaximalSelfAdjoint.lean new file mode 100644 index 0000000000..0d0f01cf28 --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/IsMaximalSelfAdjoint.lean @@ -0,0 +1,64 @@ +/- +Copyright (c) 2025 Alex Meiburg. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Alex Meiburg +-/ +module + +public import Mathlib.Analysis.Matrix.Normed + +/-! +# Maximal self-adjoint subrings + +This file introduces the `IsMaximalSelfAdjoint R α` typeclass, which records that the +`TrivialStar` ring `R` carries the self-adjoint part of a star ring `α`. It bundles an +additive, `R`-linear map `selfadjMap : α →+ R` that inverts `algebraMap R α` on self-adjoint +elements. The guiding example is `R = ℝ`, `α = ℂ`: it lets a quantity such as the trace of a +Hermitian matrix be valued in `ℝ` instead of `ℂ`, reflecting that physical observables are +self-adjoint and take real expectation values. +-/ + +@[expose] public section + +/-- `IsMaximalSelfAdjoint R α` witnesses that `R` is the maximal `TrivialStar` subring of the +star ring `α`, via an additive map `selfadjMap : α →+ R` collecting the self-adjoint part of +each element. This lets `HermitianMat.trace` return `𝕜` when `𝕜` already has a trivial star, +and the "clean" underlying type otherwise, e.g. `ℝ` when the input field is `ℂ`. -/ +class IsMaximalSelfAdjoint (R : outParam Type*) (α : Type*) [Star α] [Star R] [CommSemiring R] + [Semiring α] [TrivialStar R] [Algebra R α] where + /-- The additive map sending an element of `α` to its self-adjoint part in `R`. -/ + selfadjMap : α →+ R + /-- `selfadjMap` pulls scalar multiplication by `R` out of its argument. -/ + selfadj_smul : ∀ (r : R) (a : α), selfadjMap (r • a) = r * (selfadjMap a) + /-- On self-adjoint elements, `selfadjMap` is a section of `algebraMap R α`. -/ + selfadj_algebra : ∀ {a : α}, IsSelfAdjoint a → algebraMap _ _ (selfadjMap a) = a + +/-- Every `TrivialStar` `CommSemiring` is its own maximal self adjoints. -/ +instance instTrivialStarIsMaximalSelfAdjoint {R} [Star R] [TrivialStar R] [CommSemiring R] : + IsMaximalSelfAdjoint R R where + selfadjMap := AddMonoidHom.id R + selfadj_smul _ __ := rfl + selfadj_algebra {_} _ := rfl + +/-- ℝ is the maximal self adjoint elements over RCLike -/ +instance instRCLikeIsMaximalSelfAdjoint {α} [RCLike α] : IsMaximalSelfAdjoint ℝ α where + selfadjMap := RCLike.re + selfadj_smul := RCLike.smul_re + selfadj_algebra := RCLike.conj_eq_iff_re.mp + +namespace IsMaximalSelfAdjoint + +-- In particular instances we care about, simplify selfadjMap should it appear. +-- It _seems_ like `selfadjMap 1 = 1`, always, but I can't find a proof. But these lemmas +-- take care of proving that anyway. + +@[simp] +theorem trivial_selfadjMap {R} [Star R] [TrivialStar R] [CommSemiring R] : + (selfadjMap : R →+ R) = .id R := by + rfl + +@[simp] +theorem RCLike_selfadjMap {α} [RCLike α] : (selfadjMap : α →+ ℝ) = RCLike.re := by + rfl + +end IsMaximalSelfAdjoint diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/Isometry.lean b/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/Isometry.lean new file mode 100644 index 0000000000..610b00b312 --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/Isometry.lean @@ -0,0 +1,735 @@ +/- +Copyright (c) 2025 Alex Meiburg. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Alex Meiburg +-/ +module + +public import Mathlib.Analysis.InnerProductSpace.JointEigenspace +public import Mathlib.LinearAlgebra.Matrix.Permutation +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.Matrix + +/-! Simultaneous orthogonal diagonalization of commuting symmetric operators. -/ + +@[expose] public section + +open scoped Matrix + +variable {d d₂ d₃ R : Type*} +variable [Fintype d] [DecidableEq d] [Fintype d₂] [DecidableEq d₂] [Fintype d₃] [DecidableEq d₃] + +variable [CommRing R] [StarRing R] + +variable {𝕜 : Type*} [RCLike 𝕜] {A B : Matrix d d 𝕜} + +/-- An isometry is a matrix `A` such that `AAᴴ = 1`. Compare with a unitary, which +requires `AAᴴ = AᴴA = 1`. It is common to claim that, in a finite-dimensional vector +space, a two-sided isometry (`A.Isometry ∧ Aᴴ.Isometry`) must be square and therefore unitary; +this is does not work out so well here, since a `Matrix m n R` can be a two-sided isometry, +but cannot be a `unitary` since the rows and columns are index by different labels. -/ +def Matrix.Isometry (A : Matrix d d₂ R) : Prop := + Aᴴ * A = 1 + +omit [Fintype d₃] [DecidableEq d₂] in +omit [Fintype d] in +theorem Matrix.submatrix_one_isometry [Finite d] {e : d₂ → d} {f : d₃ → d} (he : e.Bijective) (hf : + f.Injective) : + (submatrix (α := R) 1 e f).Isometry := by + classical + let := Fintype.ofFinite d + -- Since $e$ is injective and $f$ is bijective, the submatrix of the identity matrix formed + -- by $e$ and $f$ is a permutation matrix. + have h_perm : ∀ i j, (Matrix.submatrix (1 : Matrix d d R) e f) i j = if e i = f j then 1 else + 0 := by + -- By definition of the identity matrix, the entry (i, j) in the submatrix is 1 if e i = + -- f j and 0 otherwise. + simp [Matrix.submatrix, Matrix.one_apply] + ext i j + -- Since $e$ is injective and $f$ is bijective, the product $A * Aᴴ$ will have 1s on the + -- diagonal and 0s elsewhere, which is the identity matrix. + change ∑ k, (Matrix.conjTranspose (Matrix.submatrix (1 : Matrix d d R) e f)) i k * + (Matrix.submatrix (1 : Matrix d d R) e f) k j = if i = j then 1 else 0 + simp_all only [Multiset.bijective_iff_map_univ_eq_univ, submatrix_apply, conjTranspose_apply, + one_apply] + symm; split <;> symm + next h => + subst h + simp_all only [implies_true, mul_ite, ↓reduceIte, star_one, mul_one, star_zero, mul_zero, + Finset.sum_boole] + have h_unique : ∀ i, ∃! x, e x = f i := by + intro i + obtain ⟨x, hx⟩ : ∃ x, e x = f i := by + replace he := congr_arg Multiset.toFinset he; rw [Finset.ext_iff] at he; specialize he ( + f i ); aesop; + use x + simp_all only [true_and] + intro y a + have := Fintype.bijective_iff_injective_and_card e + aesop + obtain ⟨ x, hx ⟩ := h_unique i; + rw [show ( Finset.univ.filter fun y => e y = f i ) = { x } from + Finset.eq_singleton_iff_unique_mem.2 ⟨ by aesop, fun y hy => hx.2 y <| Eq.symm <| + Finset.mem_filter.1 hy |>.2.symm ⟩]; simp; + next h => + -- Since $e$ is injective and $e i \neq e j$, there is no $x$ such that $e i = f x$ and $e j = + -- f x$. + have h_no_x : ∀ x : d₂, ¬(e x = f i ∧ e x = f j) := by + exact fun x hx => h ( hf ( hx.1.symm.trans hx.2 ) ); + exact Finset.sum_eq_zero fun x hx => by specialize h_no_x x; aesop + +omit [DecidableEq d₂] in +omit [Fintype d] in +theorem Matrix.submatrix_one_id_left_isometry [Finite d] {e : d₂ → d} (he : e.Bijective) : + (submatrix (1 : Matrix d d R) e id).Isometry := by + classical + let := Fintype.ofFinite d + exact + submatrix_one_isometry he Function.injective_id + +omit [Fintype d₂] in +theorem Matrix.submatrix_one_id_right_isometry {e : d₂ → d} (he : e.Injective) : + (submatrix (1 : Matrix d d R) id e).Isometry := + submatrix_one_isometry Function.bijective_id he + +theorem Matrix.mem_unitaryGroup_iff_isometry (A : Matrix d d R) : + A ∈ unitaryGroup d R ↔ A.Isometry ∧ Aᴴ.Isometry := by + rw [Isometry, Isometry, conjTranspose_conjTranspose] + rfl + +theorem Equiv.Perm.permMatrix_mem_unitaryGroup (e : Perm d) : + e.permMatrix R ∈ Matrix.unitaryGroup d R := by + -- Since $e$ is a permutation, its permutation matrix $P_e$ is orthogonal, meaning $P_e * + -- P_e^T = I$. + have h_perm_ortho : (Equiv.Perm.permMatrix R e) * (Equiv.Perm.permMatrix R e)ᵀ = 1 := by + ext i j; rw [Matrix.mul_apply]; aesop; + constructor + · simp_all only [Matrix.transpose_permMatrix] + -- Since the conjugate transpose of a permutation matrix is the permutation matrix of + -- the inverse permutation, we have: + have h_conj_transpose : star (Equiv.Perm.permMatrix R e) = (Equiv.Perm.permMatrix R e)ᵀ := by + ext i j; simp [Equiv.Perm.permMatrix]; aesop; + simp_all [mul_eq_one_comm] + · simp_all only [Matrix.transpose_permMatrix] + convert! h_perm_ortho using 2; + simp [Matrix.star_eq_conjTranspose, Equiv.Perm.permMatrix] + +omit [Fintype d₃] [DecidableEq d₂] in +omit [Fintype d] in +theorem Matrix.reindex_one_isometry [Finite d] (e : d ≃ d₂) (f : d ≃ d₃) : + (reindex (α := R) e f 1).Isometry := by + classical + let := Fintype.ofFinite d + -- Since $e$ and $f$ are bijections, the reindexing of the identity matrix by $e$ and $f$ is + -- a permutation matrix, which is unitary. + have h_perm : ∀ (e : d ≃ d₂) (f : d ≃ d₃), (Matrix.reindex e f (1 : Matrix d d R)).Isometry := by + intro e f + simp [Matrix.Isometry] + exact h_perm e f + +omit [Fintype d] in +theorem Matrix.reindex_one_mem_unitaryGroup (e : d ≃ d₂) : + reindex (α := R) e e 1 ∈ unitaryGroup d₂ R := by + -- The reindex of the identity matrix under an equivalence e is just the identity matrix on d₂. + have h_reindex_id : Matrix.reindex e e (1 : Matrix d d R) = 1 := by + -- By definition of reindex, the entry at (i, j) in the reindexed matrix is 1 if i = j + -- and 0 otherwise. + ext i j + simp [Matrix.reindex, Matrix.one_apply] + simp only [h_reindex_id, one_mem] + +omit [Fintype d₂] [DecidableEq d₂] [StarRing R] in +theorem Matrix.reindex_eq_conj (A : Matrix d d R) (e : d ≃ d₂) : reindex e e A = + (reindex (α := R) e (.refl d) 1) * A * (reindex (α := R) (.refl d) e 1) := by + ext i j + simp only [Matrix.mul_apply, Matrix.reindex] + simp [Matrix.one_apply] + +theorem Matrix.reindex_eq_conj_unitaryGroup' (A : Matrix d d R) (e : Equiv.Perm d) : reindex e e A = + (⟨_, e⁻¹.permMatrix_mem_unitaryGroup⟩ : unitaryGroup d R) * A * (⟨_, + e.permMatrix_mem_unitaryGroup⟩ : unitaryGroup d R) := by + ext i j; + simp? [Matrix.mul_apply] + rw [Finset.sum_eq_single ( e.symm j )] <;> aesop + +omit [DecidableEq d] in +theorem Matrix.IsHermitian.eigenvalue_ext (hA : A.IsHermitian) + (h : ∀ (v : d → 𝕜) (lam : 𝕜), A *ᵥ v = lam • v → B *ᵥ v = lam • v) : + A = B := by + classical + -- Since A is Hermitian, it is diagonalizable, and its eigenvectors form a complete basis. + -- Therefore, for any vector v, we have Av = Bv. + have h_diag : ∀ v : d → 𝕜, (A *ᵥ v) = (B *ᵥ v) := by + -- Since A is Hermitian, it is diagonalizable, and its eigenvectors form a complete + -- basis. Therefore, for any vector v, we can express it as a linear combination of + -- eigenvectors. + have h_diag : ∀ v : d → 𝕜, ∃ (c : d → 𝕜) (lam : d → 𝕜), v = ∑ i, c i • + (Matrix.IsHermitian.eigenvectorBasis hA i) ∧ ∀ i, A *ᵥ + (Matrix.IsHermitian.eigenvectorBasis hA i) = lam i • + (Matrix.IsHermitian.eigenvectorBasis hA i) := by + intro v + obtain ⟨c, hc⟩ : ∃ c : d → 𝕜, v = ∑ i, c i • (hA.eigenvectorBasis i) := by + have h_diag : ∀ v : EuclideanSpace 𝕜 d, ∃ c : d → 𝕜, v = ∑ i, c i • (hA.eigenvectorBasis + i) := by + intro v + set c := fun i => innerₛₗ 𝕜 (hA.eigenvectorBasis i) v + have hv : v = ∑ i, c i • (hA.eigenvectorBasis i) := by + exact Eq.symm (OrthonormalBasis.sum_repr' hA.eigenvectorBasis v) + use c; + obtain ⟨c, hc⟩ := h_diag (WithLp.toLp 2 v) + use c + exact congr(WithLp.ofLp $hc) + refine ⟨ c, fun i => ( hA.eigenvalues i ), hc, fun i => ?_ ⟩; + convert hA.mulVec_eigenvectorBasis i; + ext + simp only [smul_eq_mul, Pi.smul_apply] + symm + exact RCLike.real_smul_eq_coe_mul (hA.eigenvalues i) _ + -- By linearity of A and B, we can distribute them over the sum. + intros v + obtain ⟨c, lam, hv, hlam⟩ := h_diag v + have hA_v : A *ᵥ v = ∑ i, c i • lam i • (hA.eigenvectorBasis i) := by + -- By linearity of matrix multiplication, we can distribute A over the sum. + have hA_v : A *ᵥ (∑ i, c i • (hA.eigenvectorBasis i)) = ∑ i, c i • A *ᵥ + (hA.eigenvectorBasis i) := by + simp? [funext_iff] + simp? [Matrix.mulVec, dotProduct, Finset.mul_sum _ _ _] + exact fun _ => Finset.sum_comm.trans ( Finset.sum_congr rfl fun _ _ => Finset.sum_congr + rfl fun _ _ => by ring ); + aesop + have hB_v : B *ᵥ v = ∑ i, c i • lam i • (hA.eigenvectorBasis i) := by + have hBv : B *ᵥ v = ∑ i, c i • (B *ᵥ (hA.eigenvectorBasis i)) := by + -- By linearity of matrix multiplication, we can distribute $B$ over the sum. + have hBv : B *ᵥ v = B *ᵥ (∑ i, c i • (hA.eigenvectorBasis i)) := by + rw [hv] + simp only [WithLp.ofLp_sum, WithLp.ofLp_smul] + simp? [hBv, funext_iff] + simp? [Matrix.mulVec, dotProduct, Finset.mul_sum _ _ _] + exact fun _ => Finset.sum_comm.trans ( Finset.sum_congr rfl fun _ _ => Finset.sum_congr + rfl fun _ _ => by ring ); + refine hBv.trans ?_ + conv => enter [1, 2, i]; rw [h _ _ ( hlam i )] + simp only [WithLp.ofLp_sum, WithLp.ofLp_smul] + rw [hA_v, hB_v] + -- By the definition of matrix equality, if $A * v = B * v$ for all $v$, then $A = B$. + apply Matrix.ext; intro i j; exact (by + simpa using congr_fun ( h_diag ( Pi.single j 1 ) ) i) + +/-- Generalizes `Matrix.IsHermitian.cfc.eq_1`, which gives a definition for the matrix CFC in +terms of +`Matrix.IsHermitian.eigenvalues` and `Matrix.IsHermitian.eigenvectorUnitary`, to show that the + CFC works +similarly for _any_ diagonalization by a two-sided isometry. +-/ +theorem Matrix.IsHermitian.cfc_eq_any_isometry {n m 𝕜 : Type*} [RCLike 𝕜] + [Fintype n] [DecidableEq n] [Fintype m] [DecidableEq m] + {A : Matrix n n 𝕜} (hA : A.IsHermitian) {U : Matrix n m 𝕜} + (hU₁ : U * Uᴴ = 1) (hU₂ : Uᴴ * U = 1) {D : m → ℝ} + (hUD : A = (U * diagonal (RCLike.ofReal ∘ D) : Matrix _ _ _) * Uᴴ) (f : ℝ → ℝ) : + hA.cfc f = (U * diagonal (RCLike.ofReal ∘ f ∘ D) : Matrix _ _ _) * Uᴴ := by + --Thanks Aristotle + rw [Matrix.IsHermitian.cfc] + have hUV := hA.spectral_theorem + set V := hA.eigenvectorUnitary with hV; clear_value V + set D2 := hA.eigenvalues with hD; clear_value D2 + rcases V with ⟨V, hV₁, hV₂⟩ + simp only [Unitary.conjStarAlgAut_apply] at hUV ⊢ + clear hV hD + subst A; clear hA + have h_diag_eq : diagonal (RCLike.ofReal ∘ D) * (Uᴴ * V) = (Uᴴ * V) * diagonal (RCLike.ofReal + ∘ D2) := by + have h_mul : (Uᴴ * U * diagonal (RCLike.ofReal ∘ D) * Uᴴ : Matrix m n 𝕜) * V = Uᴴ * V * + (diagonal (RCLike.ofReal ∘ D2) * star V * V) := by + simp only [Matrix.mul_assoc, hUV] + simp_all [ Matrix.mul_assoc ]; + have h_diag_eq_f : diagonal (RCLike.ofReal ∘ f ∘ D) * (Uᴴ * V) = (Uᴴ * V) * diagonal + (RCLike.ofReal ∘ f ∘ D2) := by + ext i j + simp_all only [diagonal_mul, Function.comp_apply, mul_diagonal] + replace h_diag_eq := congr_fun ( congr_fun h_diag_eq i ) j + by_cases hi : D i = D2 j <;> simp_all [ mul_comm ]; + have h_final : U * diagonal (RCLike.ofReal ∘ f ∘ D) * Uᴴ * V = V * diagonal (RCLike.ofReal ∘ f + ∘ D2) := by + have h_final : U * diagonal (RCLike.ofReal ∘ f ∘ D) * (Uᴴ * V) = U * (Uᴴ * V) * diagonal + (RCLike.ofReal ∘ f ∘ D2) := by + rw [ Matrix.mul_assoc, h_diag_eq_f, Matrix.mul_assoc ]; + rw [ Matrix.mul_assoc, Matrix.mul_assoc ]; + simp_all +decide [ ← Matrix.mul_assoc ]; + rw [ ← h_final, Matrix.mul_assoc ]; + rw [hV₂, mul_one ] + +/-- Generalizes `Matrix.IsHermitian.cfc.eq_1`, which gives a definition for the matrix CFC in +terms of +`Matrix.IsHermitian.eigenvalues` and `Matrix.IsHermitian.eigenvectorUnitary`, to show that the + CFC works +similarly for _any_ diagonalization. +-/ +theorem Matrix.IsHermitian.cfc_eq_any_unitary {n 𝕜 : Type*} [RCLike 𝕜] [Fintype n] [DecidableEq n] + {A : Matrix n n 𝕜} (hA : A.IsHermitian) {U : unitaryGroup n 𝕜} {D : n → ℝ} + (hUD : A = U.val * diagonal (RCLike.ofReal ∘ D) * star U.val) (f : ℝ → ℝ) : + hA.cfc f = U.val * diagonal (RCLike.ofReal ∘ f ∘ D) * star U.val := + Matrix.IsHermitian.cfc_eq_any_isometry hA U.2.2 U.2.1 hUD f + +private theorem Matrix.cfc_conj_isometry' (hA : A.IsHermitian) (f : ℝ → ℝ) {u : Matrix d₂ d 𝕜} + (hu₁ : u.Isometry) (hu₂ : uᴴ.Isometry) : + cfc f (u * A * uᴴ) = u * (cfc f A) * uᴴ := by + let D := hA.eigenvalues + let U' := u * hA.eigenvectorUnitary.val + have := IsHermitian.cfc_eq_any_isometry + (A := u * A * uᴴ) (D := D) (n := d₂) (m := d) (U := U') ?_ ?_ ?_ ?_ f; rotate_left + · simpa using isHermitian_conjTranspose_mul_mul uᴴ hA + · dsimp [U'] + rw [conjTranspose_mul, Matrix.mul_assoc] + nth_rw 2 [← Matrix.mul_assoc] + rw [show _ * _ᴴ = 1 from hA.eigenvectorUnitary.2.2, Matrix.one_mul] + simpa [Isometry] using hu₂ + · dsimp [U'] + rw [conjTranspose_mul, Matrix.mul_assoc] + nth_rw 2 [← Matrix.mul_assoc] + rw [hu₁, Matrix.one_mul] + exact hA.eigenvectorUnitary.2.1 + · rw [hA.spectral_theorem] + simp [U', Matrix.mul_assoc] + rfl + rw [Matrix.IsHermitian.cfc_eq, this] + · rw [hA.cfc_eq, Matrix.IsHermitian.cfc.eq_1] + simp only [Unitary.conjStarAlgAut_apply] + simp only [Matrix.mul_assoc, conjTranspose_mul, star_eq_conjTranspose, U', D] + · exact isHermitian_mul_mul_conjTranspose _ hA + +theorem Matrix.cfc_conj_isometry (f : ℝ → ℝ) {u : Matrix d₂ d 𝕜} + (hu₁ : u.Isometry) (hu₂ : uᴴ.Isometry) : + cfc f (u * A * uᴴ) = u * (cfc f A) * uᴴ := by + by_cases hA : A.IsHermitian + · exact cfc_conj_isometry' hA f hu₁ hu₂ + rw [cfc_apply_of_not_predicate, cfc_apply_of_not_predicate] + · simp + · exact hA + · contrapose! hA + convert isHermitian_conjTranspose_mul_mul u hA + have hu₃ : uᴴ * u = 1 := by simpa [Isometry] using hu₁ + simp only [Matrix.mul_assoc, hu₃] + simp [← Matrix.mul_assoc, hu₃] + +theorem Matrix.cfc_conj_unitary (f : ℝ → ℝ) (u : unitaryGroup d 𝕜) : + cfc f (u * A * u⁻¹) = u * (cfc f A) * u⁻¹ := by + have hu := u.prop + rw [mem_unitaryGroup_iff_isometry] at hu + exact Matrix.cfc_conj_isometry f hu.left hu.right + +theorem Matrix.cfc_conj_unitary' (f : ℝ → ℝ) (u : unitaryGroup d 𝕜) : + cfc f (uᴴ * A * u.val) = uᴴ * (cfc f A) * u.val := by + have h1 := cfc_conj_unitary (A := A) f u⁻¹ + simp_all only [inv_inv] + exact h1 + +theorem Matrix.cfc_reindex (f : ℝ → ℝ) (e : d ≃ d₂) : + cfc f (reindex e e A) = reindex e e (cfc f A) := by + rw [reindex_eq_conj, reindex_eq_conj] + convert Matrix.cfc_conj_isometry f (u := (Matrix.reindex e (Equiv.refl d) : Matrix d d 𝕜 → + Matrix d₂ d 𝕜) 1) ?_ ?_ + · simp + · simp + · apply reindex_one_isometry + · rw [conjTranspose_reindex, conjTranspose_one] + apply reindex_one_isometry + +theorem Matrix.commute_euclideanLin (hAB : Commute A B) : + Commute A.toEuclideanLin B.toEuclideanLin := by + rw [commute_iff_eq] at hAB ⊢ + ext v i + convert congr(($hAB).mulVec (WithLp.ofLp v) i) using 0 + simp only [Module.End.mul_apply, ← Matrix.mulVec_mulVec]; + simp only [ofLp_toLpLin, toLin'_apply, mulVec_mulVec] + +section commute_module +open Module.End + +--TODO: All of these have Pi versions (instead of the "just two" operators versions below), +-- see the tail end of `JointEigenspace.lean` to see how it should generalize. This would +-- also give a Pi version for Matrix. That would be useful for e.g. we have a large number +-- of projectors that all pairwise commute, and we want to simultaneously diagonalize all +-- of them. + +/-- Similar to `LinearMap.IsSymmetric.orthogonalFamily_eigenspace_inf_eigenspace`, but here the +direct sum +is indexed by only the pairs of eigenvalues, as opposed to all pairs of `𝕜` values, giving a finite +decomposition. -/ +theorem LinearMap.IsSymmetric.orthogonalFamily_eigenspace_inf_eigenspace' {𝕜 E : Type*} [RCLike 𝕜] + [NormedAddCommGroup E] [InnerProductSpace 𝕜 E] {A B : E →ₗ[𝕜] E} + (hA : A.IsSymmetric) (hB : B.IsSymmetric) : + OrthogonalFamily 𝕜 (fun (μ₁₂ : Eigenvalues A × Eigenvalues B) ↦ + ↥(eigenspace A μ₁₂.1 ⊓ eigenspace B μ₁₂.2)) fun μ₁₂ ↦ + (eigenspace A μ₁₂.1 ⊓ eigenspace B μ₁₂.2).subtypeₗᵢ := by + have h := LinearMap.IsSymmetric.orthogonalFamily_eigenspace_inf_eigenspace hA hB + simp only [OrthogonalFamily, Submodule.coe_subtypeₗᵢ, Submodule.subtype_apply, + Subtype.forall, Submodule.mem_inf, mem_genEigenspace_one, and_imp] at h ⊢ + intro i j hij a ha hb a' ha' hb' + contrapose! h + simp only [Pairwise, ne_eq, Prod.forall, Prod.mk.injEq, not_and, not_forall] + refine ⟨_, _, _, _, ?_, a, ha, hb, a', ha', hb', h⟩ + intro h' h'' + exact hij (Prod.ext (Subtype.ext h'') (Subtype.ext h')) + +/-- Variant of `iSup_mono'` that allows for an easier handling of bottom elements. -/ +theorem iSup_mono_bot {α : Type*} {ι ι' : Sort*} [CompleteLattice α] + {f : ι → α} {g : ι' → α} (h : ∀ (i : ι), f i = ⊥ ∨ ∃ i', f i ≤ g i') : + iSup f ≤ iSup g := by + rcases isEmpty_or_nonempty ι' + · simp only [IsEmpty.exists_iff, or_false] at h + simp [h] + · refine iSup_mono' (fun i ↦ ?_) + rcases h i with h | h <;> simp [h] + +/-- The direct sum decomposition into joint eigenspaces of two commuting symmetric endomorphisms. -/ +@[reducible] +noncomputable def Commute.isSymmetricDirectSumDecomposition {𝕜 E : Type*} [RCLike 𝕜] + [NormedAddCommGroup E] [InnerProductSpace 𝕜 E] {A B : E →ₗ[𝕜] E} [FiniteDimensional 𝕜 E] + (hA : A.IsSymmetric) (hB : B.IsSymmetric) (hAB : Commute A B) : + DirectSum.Decomposition fun (μ₁₂ : Eigenvalues A × Eigenvalues B) ↦ + (eigenspace A μ₁₂.1 ⊓ eigenspace B μ₁₂.2) := by + apply (LinearMap.IsSymmetric.orthogonalFamily_eigenspace_inf_eigenspace' hA hB).decomposition + have h := LinearMap.IsSymmetric.iSup_iSup_eigenspace_inf_eigenspace_eq_top_of_commute + hA hB hAB + rw [iSup_prod'] at h + apply le_antisymm le_top + rw [← h, iSup_le_iff] + rintro ⟨fst, snd⟩ + by_cases h₁ : Module.End.HasEigenvalue A fst + · by_cases h₂ : Module.End.HasEigenvalue B snd + · exact le_iSup_of_le ⟨⟨fst, h₁⟩, ⟨snd, h₂⟩⟩ le_rfl + · replace h₂ : eigenspace B snd = ⊥ := by simpa [Module.End.HasUnifEigenvalue] using h₂ + simp [h₂] + · replace h₁ : eigenspace A fst = ⊥ := by simpa [Module.End.HasUnifEigenvalue] using h₁ + simp [h₁] + +/-- Similar to `LinearMap.IsSymmetric.directSum_isInternal_of_commute`, but here the direct sum +is indexed by only the pairs of eigenvalues, as opposed to all pairs of `𝕜` values, giving a finite +decomposition. -/ +theorem LinearMap.IsSymmetric.directSum_isInternal_of_commute' {𝕜 E : Type*} [RCLike 𝕜] + [NormedAddCommGroup E] [InnerProductSpace 𝕜 E] {A B : E →ₗ[𝕜] E} [FiniteDimensional 𝕜 E] + (hA : A.IsSymmetric) (hB : B.IsSymmetric) (hAB : Commute A B) : + DirectSum.IsInternal fun (μ₁₂ : Eigenvalues A × Eigenvalues B) ↦ + eigenspace A μ₁₂.1 ⊓ eigenspace B μ₁₂.2 := by + classical + have h := LinearMap.IsSymmetric.directSum_isInternal_of_commute hA hB hAB + constructor + · intro x y hxy + -- Since the subspaces are orthogonal, the only way their sum can be zero is if each + -- component is zero. Hence, x - y = 0, which implies x = y. + rw [← sub_eq_zero] + suffices h_diff_zero : ∀ (x : DirectSum (Eigenvalues A × Eigenvalues B) fun μ₁₂ ↦ + ↥(eigenspace A μ₁₂.1 ⊓ eigenspace B μ₁₂.2)), x.coeAddMonoidHom _ = 0 → x = 0 from + h_diff_zero (x - y) (by simp [hxy]) + clear x y hxy; intro x hx; + ext μ₁₂ + simp only [DirectSum.zero_apply, ZeroMemClass.coe_zero] + rw [← inner_self_eq_zero (𝕜 := 𝕜)] + have h_inner_zero : inner 𝕜 (x μ₁₂ : E) (x.coeAddMonoidHom _) = 0 := by + simp [hx] + rw [← h_inner_zero] + simp only [DirectSum.coeAddMonoidHom_eq_dfinsuppSum, ZeroMemClass.coe_zero, implies_true, + DFinsupp.sum_eq_sum_fintype, DFinsupp.equivFunOnFintype_apply] + -- Since the decomposition is orthogonal, the inner product of x μ₁₂ with any other + -- component is zero. Therefore, the sum simplifies to just the inner product of x μ₁₂ + -- with itself. + rw [inner_sum, Finset.sum_eq_add_sum_sdiff_singleton _ _ (by simp)] + rw [Finset.sdiff_singleton_eq_erase, left_eq_add] + apply Finset.sum_eq_zero + intro μ hμ + exact orthogonalFamily_eigenspace_inf_eigenspace' hA hB (Finset.ne_of_mem_erase hμ).symm _ _ + -- Orthogonality identifies the direct sum of the intersections with their sum. + · have h_sum : ⨆ (μ₁₂ : Eigenvalues A × Eigenvalues B), eigenspace A μ₁₂.1 ⊓ eigenspace B + μ₁₂.2 = ⊤ := by + rw [eq_top_iff] + intro x hx + obtain ⟨y, rfl⟩ := h.2 x + rw [DirectSum.coeAddMonoidHom_eq_dfinsuppSum] + refine Submodule.sum_mem _ fun i hi ↦ ?_ + have hyi := Submodule.coe_mem (y i) + simp only [Submodule.mem_inf, mem_genEigenspace_one] at hyi + have hiA : HasUnifEigenvalue A i.2 1 := by + simp only [HasUnifEigenvalue, ne_eq, Submodule.eq_bot_iff, mem_genEigenspace_one, + not_forall] + exact ⟨y i, hyi.1, by simpa using hi⟩ + have hiB : HasUnifEigenvalue B i.1 1 := by + simp only [HasUnifEigenvalue, ne_eq, Submodule.eq_bot_iff, mem_genEigenspace_one, + not_forall] + exact ⟨y i, hyi.2, by simpa using hi⟩ + apply Submodule.mem_iSup_of_mem (⟨i.2, hiA⟩, ⟨i.1, hiB⟩) + exact Submodule.coe_mem (y i) + intro x + rw [Submodule.eq_top_iff'] at h_sum + specialize h_sum x + rw [Submodule.mem_iSup_iff_exists_finsupp] at h_sum + rcases h_sum with ⟨f, hf₁, hf₂⟩ + exact ⟨∑ i ∈ f.support, .of _ i ⟨f i, hf₁ i⟩, by simp_all?; exact hf₂⟩ + +/-- An orthonormal basis of simultaneous eigenvectors for two commuting symmetric endomorphisms. -/ +noncomputable def LinearMap.sharedEigenbasis {A B : EuclideanSpace 𝕜 d →ₗ[𝕜] EuclideanSpace 𝕜 d} + (hA : A.IsSymmetric) (hB : B.IsSymmetric) (hAB : Commute A B) : + OrthonormalBasis d 𝕜 (EuclideanSpace 𝕜 d) := + ((hA.directSum_isInternal_of_commute' hB hAB).subordinateOrthonormalBasis rfl + (hA.orthogonalFamily_eigenspace_inf_eigenspace' hB)).reindex + (Fintype.equivOfCardEq (by simp)) + +/-- The eigenvalues of the first endomorphism in the chosen simultaneous orthonormal eigenbasis. -/ +noncomputable def LinearMap.sharedEigenvaluesA {A B : EuclideanSpace 𝕜 d →ₗ[𝕜] EuclideanSpace 𝕜 d} + (hA : A.IsSymmetric) (hB : B.IsSymmetric) (hAB : Commute A B) : d → ℝ := + fun i => RCLike.re (inner 𝕜 (LinearMap.sharedEigenbasis hA hB hAB i) (A + (LinearMap.sharedEigenbasis hA hB hAB i))) + +/-- The eigenvalues of the second endomorphism in the chosen simultaneous orthonormal eigenbasis. -/ +noncomputable def LinearMap.sharedEigenvaluesB {A B : EuclideanSpace 𝕜 d →ₗ[𝕜] EuclideanSpace 𝕜 d} + (hA : A.IsSymmetric) (hB : B.IsSymmetric) (hAB : Commute A B) : d → ℝ := + fun i => RCLike.re (inner 𝕜 (LinearMap.sharedEigenbasis hA hB hAB i) (B + (LinearMap.sharedEigenbasis hA hB hAB i))) + +omit [DecidableEq d] in +theorem LinearMap.mem_eigenspace_inf_of_sharedEigenbasis {A B : EuclideanSpace 𝕜 d →ₗ[𝕜] + EuclideanSpace 𝕜 d} + (hA : A.IsSymmetric) (hB : B.IsSymmetric) (hAB : Commute A B) (i : d) : + ∃ (μ : Module.End.Eigenvalues A) (ν : Module.End.Eigenvalues B), + LinearMap.sharedEigenbasis hA hB hAB i ∈ Module.End.eigenspace A μ ⊓ Module.End.eigenspace + B ν := by + rw [LinearMap.sharedEigenbasis] + rw [OrthonormalBasis.reindex_apply] + let hV := hA.directSum_isInternal_of_commute' hB hAB + let hV' := hA.orthogonalFamily_eigenspace_inf_eigenspace' hB + let hn : Module.finrank 𝕜 (EuclideanSpace 𝕜 d) = Module.finrank 𝕜 (EuclideanSpace 𝕜 d) := rfl + let e := Fintype.equivOfCardEq (show Fintype.card (Fin (Module.finrank 𝕜 (EuclideanSpace 𝕜 + d))) = Fintype.card d by simp) + let j := e.symm i + let idx := hV.subordinateOrthonormalBasisIndex hn j hV' + exists idx.1, idx.2 + exact hV.subordinateOrthonormalBasis_subordinate hn j hV' + +omit [DecidableEq d] in +theorem LinearMap.apply_A_sharedEigenbasis {A B : EuclideanSpace 𝕜 d →ₗ[𝕜] EuclideanSpace 𝕜 d} + (hA : A.IsSymmetric) (hB : B.IsSymmetric) (hAB : Commute A B) (i : d) : + A (sharedEigenbasis hA hB hAB i) = (sharedEigenvaluesA hA hB hAB i : 𝕜) • (sharedEigenbasis + hA hB hAB i) := by + obtain ⟨μ, ν, h⟩ := mem_eigenspace_inf_of_sharedEigenbasis hA hB hAB i + have h₂ := Module.End.mem_eigenspace_iff.mp h.1 + rw [h₂] + congr; symm + simp only [sharedEigenvaluesA, h₂, inner_smul_right, OrthonormalBasis.inner_eq_one, + mul_one, ← RCLike.conj_eq_iff_re, ← RCLike.star_def] + have h₃ : (sharedEigenbasis hA hB hAB) i ≠ 0 := by + have := (sharedEigenbasis hA hB hAB).orthonormal.1 i + exact fun h => by simp [h] at this + simpa [inner_smul_left, inner_smul_right, h₂, h₃] using + hA ((sharedEigenbasis hA hB hAB) i) ((sharedEigenbasis hA hB hAB) i) + +omit [DecidableEq d] in +theorem LinearMap.apply_B_sharedEigenbasis {A B : EuclideanSpace 𝕜 d →ₗ[𝕜] EuclideanSpace 𝕜 d} + (hA : A.IsSymmetric) (hB : B.IsSymmetric) (hAB : Commute A B) (i : d) : + B (sharedEigenbasis hA hB hAB i) = (sharedEigenvaluesB hA hB hAB i : 𝕜) • (sharedEigenbasis + hA hB hAB i) := by + obtain ⟨μ, ν, h⟩ := mem_eigenspace_inf_of_sharedEigenbasis hA hB hAB i + have h₂ := Module.End.mem_eigenspace_iff.mp h.2 + rw [h₂] + congr; symm + simp only [sharedEigenvaluesB, h₂, inner_smul_right, OrthonormalBasis.inner_eq_one, + mul_one, ← RCLike.conj_eq_iff_re, ← RCLike.star_def] + have h₃ : (sharedEigenbasis hA hB hAB) i ≠ 0 := by + have := (sharedEigenbasis hA hB hAB).orthonormal.1 i + exact fun h => by simp [h] at this + simpa [inner_smul_left, inner_smul_right, h₂, h₃] using + hB ((sharedEigenbasis hA hB hAB) i) ((sharedEigenbasis hA hB hAB) i) + +/-- An orthonormal basis of simultaneous eigenvectors for two commuting Hermitian matrices. -/ +noncomputable def Matrix.sharedEigenbasis + (hA : A.IsHermitian) (hB : B.IsHermitian) (hAB : Commute A B) : + OrthonormalBasis d 𝕜 (EuclideanSpace 𝕜 d) := + LinearMap.sharedEigenbasis (isSymmetric_toEuclideanLin_iff.symm.mp hA) + (isSymmetric_toEuclideanLin_iff.symm.mp hB) (commute_euclideanLin hAB) + +/-- The unitary matrix whose columns are the chosen simultaneous eigenvectors. -/ +noncomputable def Matrix.sharedEigenvectorUnitary (hA : A.IsHermitian) (hB : B.IsHermitian) + (hAB : Commute A B) : Matrix.unitaryGroup d 𝕜 := + ⟨(EuclideanSpace.basisFun d 𝕜).toBasis.toMatrix (sharedEigenbasis hA hB hAB).toBasis, + (EuclideanSpace.basisFun d 𝕜).toMatrix_orthonormalBasis_mem_unitary (sharedEigenbasis hA hB + hAB)⟩ + +namespace Matrix.SharedEigenbasis + +variable (hA : A.IsHermitian) (hB : B.IsHermitian) (hAB : Commute A B) + +/-- Analogous to `Matrix.IsHermitian.eigenvectorUnitary_mulVec` for the shared basis. -/ +theorem sharedEigenvectorUnitary_mulVec (j : d) : (sharedEigenvectorUnitary hA hB hAB) *ᵥ + Pi.single j 1 = WithLp.ofLp (sharedEigenbasis hA hB hAB j) := by + simp_all only [mulVec_single, MulOpposite.op_one, one_smul] + rfl + +/-- The eigenvalue of the first matrix at an index of the shared eigenbasis. -/ +noncomputable def sharedEigenvalueA (j : d) : ℝ := + LinearMap.sharedEigenvaluesA + (isSymmetric_toEuclideanLin_iff.symm.mp hA) + (isSymmetric_toEuclideanLin_iff.symm.mp hB) + (commute_euclideanLin hAB) j + +/-- The eigenvalue of the second matrix at an index of the shared eigenbasis. -/ +noncomputable def sharedEigenvalueB (j : d) : ℝ := + LinearMap.sharedEigenvaluesB + (isSymmetric_toEuclideanLin_iff.symm.mp hA) + (isSymmetric_toEuclideanLin_iff.symm.mp hB) + (commute_euclideanLin hAB) j + +/-- Analogous to `Matrix.IsHermitian.mulVec_eigenvectorBasis` for the shared basis. -/ +theorem mulVec_sharedEigenbasisA (j : d) : + A *ᵥ (sharedEigenbasis hA hB hAB j) = + (sharedEigenvalueA hA hB hAB) j • WithLp.ofLp (sharedEigenbasis hA hB hAB j) := by + rw [isSymmetric_toEuclideanLin_iff.symm] at hA hB + have h := LinearMap.apply_A_sharedEigenbasis hA hB (Matrix.commute_euclideanLin hAB) j + simp only [algebraMap_smul] at h + have := congr_arg WithLp.ofLp h + simp? [Matrix.toEuclideanLin, Matrix.toLpLin_apply] at this + exact this + +theorem mulVec_sharedEigenbasisB (j : d) : + B *ᵥ (sharedEigenbasis hA hB hAB j) = + (sharedEigenvalueB hA hB hAB) j • WithLp.ofLp (sharedEigenbasis hA hB hAB j) := by + rw [isSymmetric_toEuclideanLin_iff.symm] at hA hB + have h := LinearMap.apply_B_sharedEigenbasis hA hB (Matrix.commute_euclideanLin hAB) j + simp only [algebraMap_smul] at h + have := congr_arg WithLp.ofLp h + simp? [Matrix.toEuclideanLin, Matrix.toLpLin_apply] at this + exact this + +/- +PROVIDED SOLUTION +This is exactly analogous to star_shared_mul_B_mul_IsDiag (which is proved below in this file), + but for A instead of B. Use the same proof structure: rw isDiag_iff_diagonal_diag, apply + toEuclideanLin.injective, ext with basis, simp, then use mulVec_sharedEigenbasisA (instead + of mulVec_sharedEigenbasisB), sharedEigenvectorUnitary_mulVec, h_simp2 (orthogonality/unit + property), and by_cases on index equality, simplifying with simp +decide. Reference the B + version's proof approach for the exact tactic sequence. +-/ + + +theorem star_shared_mul_A_mul_IsDiag : IsDiag + ((star (sharedEigenvectorUnitary hA hB hAB : Matrix d d 𝕜)) * A * + (sharedEigenvectorUnitary hA hB hAB : Matrix d d 𝕜)) := by + intro i j hij; + have := @mulVec_sharedEigenbasisA d; + specialize this hA hB hAB j; + replace this := congr_arg ( fun x => star ( ( sharedEigenbasis hA hB hAB i ).ofLp ) ⬝ᵥ x ) this + simp only [dotProduct, Pi.star_apply, RCLike.star_def, mulVec, Finset.mul_sum, mul_comm, + mul_assoc, Pi.smul_apply, Algebra.smul_mul_assoc] at this + simp only [mul_assoc] + convert this using 1; + · simp? [ Matrix.mul_apply, mul_assoc, mul_comm, Finset.sum_mul ] + congr! 3; + · have := ( sharedEigenbasis hA hB hAB ).orthonormal; + rw [ orthonormal_iff_ite ] at this; + simp only [inner, ← starRingEnd_apply] at this + rw [ ← Finset.smul_sum, this i j, ite_eq_right hij, smul_zero ] + +/-- Analogous to `Matrix.IsHermitian.star_mul_self_mul_eq_diagonal` for the shared basis. -/ +theorem star_shared_mul_B_mul_IsDiag : IsDiag + ((star (sharedEigenvectorUnitary hA hB hAB : Matrix d d 𝕜)) * B * + (sharedEigenvectorUnitary hA hB hAB : Matrix d d 𝕜)) := by + rw [Matrix.isDiag_iff_diagonal_diag, eq_comm] + apply Matrix.toEuclideanLin.injective + apply (EuclideanSpace.basisFun d 𝕜).toBasis.ext + intro i + simp only [toLpLin_apply, OrthonormalBasis.coe_toBasis, EuclideanSpace.basisFun_apply, + PiLp.ofLp_single, ← mulVec_mulVec, sharedEigenvectorUnitary_mulVec, ← mulVec_mulVec, + Matrix.diagonal_mulVec_single, mul_one] + apply PiLp.ext + intro j + have h_simp : (Matrix.sharedEigenvectorUnitary hA hB hAB).val.conjTranspose.mulVec (B.mulVec + (WithLp.ofLp (Matrix.sharedEigenbasis hA hB hAB i))) = + (sharedEigenvalueB hA hB hAB i) • (Matrix.sharedEigenvectorUnitary hA hB + hAB).val.conjTranspose.mulVec (WithLp.ofLp (Matrix.sharedEigenbasis hA hB hAB i)) := by + convert congr_arg ( fun x => ( Matrix.sharedEigenvectorUnitary hA hB hAB : Matrix d d 𝕜 ) + ᴴ *ᵥ x ) ( mulVec_sharedEigenbasisB hA hB hAB i) using 1; + symm + exact (mulVec_smul ((sharedEigenvectorUnitary hA hB hAB).val)ᴴ (sharedEigenvalueB hA hB hAB i) + (WithLp.ofLp ((sharedEigenbasis hA hB hAB) i))) + have h_simp2 : (Matrix.sharedEigenvectorUnitary hA hB hAB).val.conjTranspose.mulVec + (WithLp.ofLp (Matrix.sharedEigenbasis hA hB hAB i)) = Pi.single i 1 := by + rw [ ← sharedEigenvectorUnitary_mulVec hA hB hAB i ]; + simp? + ext j + have := mul_eq_one_comm.mp ( show ( Matrix.sharedEigenvectorUnitary hA hB hAB : Matrix d d 𝕜 + ) * ( Matrix.sharedEigenvectorUnitary hA hB hAB : Matrix d d 𝕜 )ᴴ = 1 from ?_ ); + · convert! congr_fun ( congr_fun this j ) i using 1; + simp [ Pi.single_apply, Matrix.one_apply ]; + · exact Matrix.mem_unitaryGroup_iff.mp ( Matrix.sharedEigenvectorUnitary hA hB hAB ).2; + simp_all? [ Matrix.mulVec, funext_iff ]; + simp_all? [ Matrix.mul_apply, dotProduct ]; + by_cases hij : i = j + · simp? [ hij ]; + simp? [Matrix.mulVec, dotProduct ]; + simp only [Finset.mul_sum, mul_left_comm]; + rw [ Finset.sum_comm ] + simp? [ mul_comm, mul_left_comm, Finset.mul_sum] + congr! 3; + · simp? [ hij ]; + simp_all? [ mul_comm, Matrix.mulVec, dotProduct ]; + simp_all? [ mul_comm, Finset.mul_sum]; + rw [ Finset.sum_comm ] + simp_all [ mul_assoc, mul_left_comm]; + +end Matrix.SharedEigenbasis + +end commute_module + +theorem Commute.exists_unitary (hA : A.IsHermitian) (hB : B.IsHermitian) (hAB : Commute A B) : + ∃ U : Matrix.unitaryGroup d 𝕜, (U.val * A * Uᴴ).IsDiag ∧ (U.val * B * Uᴴ).IsDiag := by + use (Matrix.sharedEigenvectorUnitary hA hB hAB)⁻¹ + constructor + · convert! Matrix.SharedEigenbasis.star_shared_mul_A_mul_IsDiag hA hB hAB + simp [Matrix.star_eq_conjTranspose] + · convert! Matrix.SharedEigenbasis.star_shared_mul_B_mul_IsDiag hA hB hAB + simp [Matrix.star_eq_conjTranspose] + +variable (U : Matrix.unitaryGroup d 𝕜) + +instance instInvertibleUnitaryGroup (U : Matrix.unitaryGroup d 𝕜) : Invertible U := + invertibleOfGroup U + +/-- The inverse of a unitary matrix, regarded as an ambient matrix. -/ +instance EuclideanJordan.instInvertibleUnitaryMatrix + (U : Matrix.unitaryGroup d 𝕜) : Invertible U.val := + ⟨star U.val, U.2.1, U.2.2⟩ + +/-- If a matrix is diagonalized by a unitary matrix, then it can be written as a +CFC of a (particular, canonical) diagonal matrix. -/ +theorem Matrix.IsDiag.exists_cfc {U : Matrix.unitaryGroup d 𝕜} {M : Matrix d d 𝕜} + (hU : (U.val * M * Uᴴ).IsDiag) (hM : M.IsHermitian) (e : d ≃ Fin (Fintype.card d)) : + ∃ f : ℝ → ℝ, + M = cfc f (Uᴴ * (Matrix.diagonal fun x => ↑↑(e x)) * U.val) := by + use fun x ↦ if hn : ∃ n : Fin (Fintype.card d), n = x + then RCLike.re (Matrix.diag (U.val * M * Uᴴ) (e.symm hn.choose)) else 0 + rw [Matrix.cfc_conj_unitary'] + rw [Matrix.isDiag_iff_diagonal_diag] at hU + rw [← Matrix.mul_inv_eq_iff_eq_mul_of_invertible] at hU + rw [← Matrix.inv_mul_eq_iff_eq_mul_of_invertible] at hU + rw [← hU, ← Matrix.mul_assoc] + congr; rotate_right + · exact Matrix.inv_eq_right_inv U.2.1 + · exact Matrix.inv_eq_left_inv U.2.1 + conv in Nat.cast (e _) => + equals (RCLike.ofReal <| e x) => simp only [map_natCast] + rw [Matrix.cfc_diagonal] + congr + ext i + simp only [Matrix.diag_apply, Function.comp_apply, Nat.cast_inj, exists_apply_eq_apply, + ↓reduceDIte] + rw [← (Matrix.isHermitian_mul_mul_conjTranspose U.val hM).coe_re_apply_self i] + congr! + · rw [mul_assoc, hU] + all_goals + ( rw [e.eq_symm_apply] + symm; convert Classical.choose_eq _ + exact Fin.val_inj) + +--TODO: Make Iff version. +/-- If two Hermitian matrices commute, there exists a common matrix that they are both a CFC of. -/ +theorem Commute.exists_cfc (hA : A.IsHermitian) (hB : B.IsHermitian) (hAB : Commute A B) : + ∃ C : Matrix d d 𝕜, (∃ f : ℝ → ℝ, A = cfc f C) ∧ (∃ g : ℝ → ℝ, B = cfc g C) := by + obtain ⟨U, hU₁, hU₂⟩ := hAB.exists_unitary hA hB + let D : Matrix d d 𝕜 := Matrix.diagonal (Fintype.equivFin d ·) + exact ⟨Uᴴ * D * U.val, hU₁.exists_cfc hA _, hU₂.exists_cfc hB _⟩ diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/LinearEquiv.lean b/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/LinearEquiv.lean new file mode 100644 index 0000000000..52ad179036 --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/LinearEquiv.lean @@ -0,0 +1,127 @@ +/- +Copyright (c) 2025 Alex Meiburg. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Alex Meiburg +-/ +module + +public import Mathlib.Analysis.InnerProductSpace.PiL2 + +/-! +# Relabelling linear equivalences + +## i. Overview + +This module provides linear equivalences obtained by relabelling the index type of a finite +function space `d → R` or of `EuclideanSpace 𝕜 d` along an index equivalence `e : d ≃ d₂`, +together with lemmas relating them to `Matrix.reindex`. + +## ii. Key results + +- `LinearEquiv.ofRelabel` : the `R`-linear equivalence `(d₂ → R) ≃ₗ[R] (d → R)` induced by an + index equivalence `e : d ≃ d₂`. +- `LinearEquiv.euclideanOfRelabel` : the `EuclideanSpace` analogue of `ofRelabel`. +- `Matrix.reindex_toLin'` and `Matrix.reindex_toEuclideanLin` : reindexing a matrix conjugates + its associated linear map by these relabelling equivalences. + +## iii. Table of contents + +- A. Relabelling linear equivalences +- B. Reindexing matrices and their linear maps + +## iv. References + +-/ + +@[expose] public section + +variable {d d₁ d₂ d₃ R 𝕜 : Type*} [RCLike 𝕜] + +/-! + +## A. Relabelling linear equivalences + +-/ + +namespace LinearEquiv + +variable {R : Type*} [Semiring R] + +variable (R) in +/-- The `R`-linear equivalence `(d₂ → R) ≃ₗ[R] (d → R)` that relabels the coordinates of a +function along an index equivalence `e : d ≃ d₂`. This is the linear-equivalence packaging of +`Equiv.piCongrLeft`. -/ +@[simps] +def ofRelabel (e : d ≃ d₂) : (d₂ → R) ≃ₗ[R] (d → R) := by + refine { e.symm.piCongrLeft (fun _ ↦ R) with map_add' := ?_, map_smul' := ?_ } + <;> (intros; ext; simp [Equiv.piCongrLeft_apply]) + +variable (e : d ≃ d₂) + +variable (𝕜) in +/-- The `𝕜`-linear equivalence `EuclideanSpace 𝕜 d₂ ≃ₗ[𝕜] EuclideanSpace 𝕜 d` that relabels the +coordinates of a vector along an index equivalence `e : d ≃ d₂`. This is the `EuclideanSpace` +analogue of `LinearEquiv.ofRelabel`, obtained by transporting it across the `WithLp` +identifications. -/ +@[simps!] +def euclideanOfRelabel (e : d ≃ d₂) : EuclideanSpace 𝕜 d₂ ≃ₗ[𝕜] EuclideanSpace 𝕜 d := + (WithLp.linearEquiv 2 𝕜 _).trans ((ofRelabel _ e).trans (WithLp.linearEquiv 2 𝕜 _).symm) + +@[simp] +theorem ofRelabel_refl : ofRelabel R (.refl d) = LinearEquiv.refl R (d → R) := by + rfl + +@[simp] +theorem euclideanOfRelabel_refl : euclideanOfRelabel 𝕜 (.refl d) = + LinearEquiv.refl 𝕜 (EuclideanSpace 𝕜 d) := by + rfl + +end LinearEquiv + +/-! + +## B. Reindexing matrices and their linear maps + +-/ + +namespace Matrix + +variable {R : Type*} [CommSemiring R] +variable [Fintype d] [DecidableEq d] +variable [Fintype d₂] [DecidableEq d₂] + +theorem reindex_toLin' (e : d₁ ≃ d₃) (f : d₂ ≃ d) (M : Matrix d₁ d₂ R) : + (M.reindex e f).toLin' = (LinearEquiv.ofRelabel R e.symm) ∘ₗ + M.toLin' ∘ₗ (LinearEquiv.ofRelabel R f) := by + ext + simp [mulVec, dotProduct, Equiv.piCongrLeft_apply] + +theorem reindex_toEuclideanLin (e : d₁ ≃ d₃) (f : d₂ ≃ d) (M : Matrix d₁ d₂ 𝕜) : + (M.reindex e f).toEuclideanLin = (LinearEquiv.euclideanOfRelabel 𝕜 e.symm) ∘ₗ + M.toEuclideanLin ∘ₗ (LinearEquiv.euclideanOfRelabel 𝕜 f) := by + ext + simp [mulVec, dotProduct, Equiv.piCongrLeft_apply] + +theorem reindex_right_toLin' (e : d ≃ d₂) (M : Matrix d₃ d R) : + (M.reindex (.refl d₃) e).toLin' = M.toLin' ∘ₗ (LinearEquiv.ofRelabel R e) := by + rw [reindex_toLin'] + simp + +theorem reindex_right_toEuclideanLin (e : d ≃ d₂) (M : Matrix d₃ d 𝕜) : + (M.reindex (.refl d₃) e).toEuclideanLin = + M.toEuclideanLin ∘ₗ (LinearEquiv.euclideanOfRelabel 𝕜 e) := by + ext + simp [mulVec, dotProduct, Equiv.piCongrLeft_apply] + +theorem reindex_left_toLin' (e : d₁ ≃ d₃) (M : Matrix d₁ d₂ R) : + (M.reindex e (.refl d₂)).toLin' = (LinearEquiv.ofRelabel R e.symm) ∘ M.toLin' := by + rw [Matrix.reindex_toLin'] + simp + +theorem reindex_left_toEuclideanLin (e : d₁ ≃ d₃) (M : Matrix d₁ d₂ 𝕜) : + (M.reindex e (.refl d₂)).toEuclideanLin = + (LinearEquiv.euclideanOfRelabel 𝕜 e.symm) ∘ M.toEuclideanLin := by + rw [Matrix.reindex_toEuclideanLin] + simp + +end Matrix diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/Matrix.lean b/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/Matrix.lean new file mode 100644 index 0000000000..896f94e584 --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/Matrix.lean @@ -0,0 +1,1798 @@ +/- +Copyright (c) 2025 Alex Meiburg. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Alex Meiburg +-/ +module + +public import Mathlib.Algebra.Algebra.Spectrum.Quasispectrum +public import Mathlib.Algebra.Order.Group.Pointwise.CompleteLattice +public import Mathlib.Analysis.CStarAlgebra.Matrix +public import Mathlib.Analysis.Matrix.Order +public import Mathlib.Analysis.SpecialFunctions.Bernstein +public import Mathlib.Analysis.SpecialFunctions.Pow.NNReal +public import Mathlib.Data.Multiset.Functor --Can't believe I'm having to import this +public import Mathlib.LinearAlgebra.Matrix.Kronecker +public import Mathlib.LinearAlgebra.Matrix.PosDef +public import Mathlib.LinearAlgebra.Matrix.IsDiag +public import Mathlib.Tactic.Bound +public import Mathlib.Tactic.NormNum.GCD + +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.Misc + +-- Vendor edit 2: this repo's lakefile sets `relaxedAutoImplicit = false`; upstream +-- physlib relies on relaxed auto-bound implicits for `dA`/`dB` in this file. + + +/-! Matrix order, partial traces, spectra, and block constructions. -/ + +@[expose] public section + +noncomputable section + +open BigOperators + +variable {n 𝕜 dA dB : Type*} +variable [RCLike 𝕜] [DecidableEq n] + +namespace Matrix + +open scoped ComplexOrder MatrixOrder + +omit [DecidableEq n] in +/-- The block Gram matrix `[[YᴴY, YᴴX], [XᴴY, XᴴX]]` is positive semidefinite. -/ +theorem fromBlocks_gram_posSemidef {m n k : Type*} [Finite m] [Finite n] [Fintype k] + (X : Matrix k n ℂ) (Y : Matrix k m ℂ) : + (fromBlocks (Yᴴ * Y) (Yᴴ * X) (Xᴴ * Y) (Xᴴ * X)).PosSemidef := by + classical + let := Fintype.ofFinite m + let := Fintype.ofFinite n + convert posSemidef_conjTranspose_mul_self + (fromBlocks Y X (0 : Matrix k m ℂ) (0 : Matrix k n ℂ)) using 1 + rw [fromBlocks_conjTranspose, fromBlocks_multiply] + simp + +omit [DecidableEq n] in +theorem zero_rank_eq_zero {A : Matrix n n 𝕜} [Fintype n] (hA : A.rank = 0) : A = 0 := by + classical + have h : ∀ v, A.mulVecLin v = 0 := by + intro v + rw [rank, Module.finrank_zero_iff] at hA + have := hA.elim ⟨A.mulVecLin v, ⟨v, rfl⟩⟩ ⟨0, ⟨0, by rw [mulVecLin_apply, mulVec_zero]⟩⟩ + exact congrArg Subtype.val this + rw [← LinearEquiv.map_eq_zero_iff toLin'] + exact LinearMap.ext h + +namespace IsHermitian + +variable {A : Matrix n n 𝕜} {B : Matrix n n 𝕜} +variable (hA : A.IsHermitian) (hB : B.IsHermitian) + +include hA in +omit [DecidableEq n] in +theorem smul_selfAdjoint {c : 𝕜} (hc : _root_.IsSelfAdjoint c) : (c • A).IsHermitian := by + exact IsSelfAdjoint.smul hc hA + +include hA in +omit [DecidableEq n] in +theorem smul_im_zero {c : 𝕜} (h : RCLike.im c = 0) : (c • A).IsHermitian := + hA.smul_selfAdjoint (RCLike.conj_eq_iff_im.mpr h) + +include hA in +omit [DecidableEq n] in +theorem smul_real (c : ℝ) : (c • A).IsHermitian := by + convert hA.smul_im_zero (RCLike.ofReal_im c) using 1 + ext + simp only [smul_apply, smul_eq_mul, RCLike.real_smul_eq_coe_mul] + +/-- The real vector subspace of Hermitian matrices. -/ +def HermitianSubspace (n 𝕜 : Type*) [RCLike 𝕜] : Subspace ℝ (Matrix n n 𝕜) where + carrier := { A : Matrix n n 𝕜 | A.IsHermitian } + add_mem' _ _ := by simp_all only [Set.mem_ofPred_eq, IsHermitian.add] + zero_mem' := by simp only [Set.mem_ofPred_eq, isHermitian_zero] + smul_mem' c A := by + simp only [Set.mem_ofPred_eq] + intro hA + exact IsHermitian.smul_real hA c + +variable [Fintype n] + +include hA in +omit [DecidableEq n] in +@[simp] +theorem re_trace_eq_trace : RCLike.re (A.trace) = A.trace := by + rw [trace, map_sum, RCLike.ofReal_sum, IsHermitian.coe_re_diag hA] + +section eigenvalues + +/-- The sum of the eigenvalues of a Hermitian matrix is equal to its trace. -/ +theorem sum_eigenvalues_eq_trace : ∑ i, hA.eigenvalues i = A.trace := by + nth_rewrite 2 [hA.spectral_theorem] + simp only [Unitary.conjStarAlgAut_apply] + rw [trace_mul_comm, ← mul_assoc] + simp [trace_diagonal] + +/-- If all eigenvalues are equal to zero, then the matrix is zero. -/ +theorem eigenvalues_zero_eq_zero (h : ∀ i, hA.eigenvalues i = 0) : A = 0 := by + suffices A.rank = 0 from zero_rank_eq_zero this + simp only [hA.rank_eq_card_non_zero_eigs, h, ne_eq, not_true_eq_false, Fintype.card_eq_zero] + +end eigenvalues + +end IsHermitian + +section Kronecker + +open Kronecker + +variable [CommRing R] [StarRing R] +variable (A : Matrix m m R) (B : Matrix n n R) + +omit [DecidableEq n] in +theorem kroneckerMap_conjTranspose : (A ⊗ₖ B)ᴴ = (Aᴴ ⊗ₖ Bᴴ) := by + ext; simp + +variable {A : Matrix m m R} {B : Matrix n n R} +variable (hA : A.IsHermitian) (hB : B.IsHermitian) + +include hA hB in +omit [DecidableEq n] in +theorem kroneckerMap_IsHermitian : (A ⊗ₖ B).IsHermitian := by + exact (hA ▸ hB ▸ kroneckerMap_conjTranspose A B : _ = _) + +end Kronecker + +namespace PosSemidef + +open Kronecker +open scoped ComplexOrder + +variable {m n 𝕜 : Type*} +variable [Fintype m] [Fintype n] +variable [RCLike 𝕜] [dn : DecidableEq n] + +section +variable {A : Matrix m m 𝕜} {B : Matrix m m 𝕜} +variable (hA : A.PosSemidef) (hB : B.PosSemidef) + +include hA in +theorem trace_zero : A.trace = 0 → A = 0 := by + open Classical in + intro h + rw [← hA.isHermitian.sum_eigenvalues_eq_trace, RCLike.ofReal_eq_zero] at h + rw [Finset.sum_eq_zero_iff_of_nonneg (fun i _ ↦ hA.eigenvalues_nonneg i)] at h + simp only [Finset.mem_univ, forall_const] at h + exact hA.isHermitian.eigenvalues_zero_eq_zero h + +include hA in +@[simp] +theorem trace_zero_iff : A.trace = 0 ↔ A = 0 := + ⟨trace_zero hA, (by simp [·])⟩ + +--belongs somewhere else. compare with `Complex.normSq_eq_conj_mul_self`. +open ComplexConjugate in +theorem _root_.RCLike.normSq_eq_conj_mul_self {z : 𝕜} : RCLike.normSq z = conj z * z := by + rw [RCLike.ext_iff] + simp [RCLike.normSq] + ring_nf + +--PR +theorem Finsupp.sum_eq_ite + {α : Type u_1} {M : Type u_8} {N : Type u_10} [Zero M] [AddCommMonoid N] [Fintype α] + [DecidableEq M] (f : α →₀ M) (g : α → M → N) : + f.sum g = ∑ i, if f i ≠ 0 then g i (f i) else 0 := by + rw [Finsupp.sum, eq_comm] + classical convert Finset.sum_ite_mem Finset.univ f.support (fun i ↦ g i (f i)) + · simp + · simp + + +omit dn in +omit [Fintype n] in +open ComplexConjugate in +theorem outer_self_conj [Finite n] (v : n → 𝕜) : PosSemidef (vecMulVec v (conj v)) := by + classical + let := Fintype.ofFinite n + constructor + · ext + simp [vecMulVec_apply, mul_comm] + · intro x + rw [Finsupp.sum_fintype _ _ (by simp)] + conv => + enter [2, 2, i] + rw [Finsupp.sum_fintype _ _ (by simp)] + simp_rw [RCLike.star_def, + vecMulVec_apply, mul_assoc, ← Finset.mul_sum, ← mul_assoc, ← Finset.sum_mul] + change + 0 ≤ (∑ i : n, conj (x i) * v i) * ∑ i : n, conj (v i) * x i + have : (∑ i : n, conj (x i) * v i) = + (∑ i : n, conj (conj (v i) * x i)) := by + simp only [mul_comm (conj (x _)) (v _), map_mul, + RingHomCompTriple.comp_apply, RingHom.id_apply] + rw [this, ← map_sum, ← RCLike.normSq_eq_conj_mul_self, RCLike.ofReal_nonneg] + exact RCLike.normSq_nonneg _ + +omit [Fintype m] in +include hA hB in +theorem convex_cone {c₁ c₂ : 𝕜} (hc₁ : 0 ≤ c₁) (hc₂ : 0 ≤ c₂) : (c₁ • A + c₂ • B).PosSemidef := + (hA.smul hc₁).add (hB.smul hc₂) + +variable [dm : DecidableEq m] + +omit [Fintype m] in +/-- A standard basis matrix (with a positive entry) is positive semidefinite iff the entry is on +the diagonal. -/ +theorem stdBasisMatrix_iff_eq [Finite m] (i j : m) {c : 𝕜} (hc : 0 < c) : + (single i j c).PosSemidef ↔ i = j + := by + classical + let := Fintype.ofFinite m + constructor + · intro ⟨hherm, _⟩ + rw [IsHermitian, ← ext_iff] at hherm + replace hherm := hherm i j + simp only [single, conjTranspose_apply, of_apply, true_and, RCLike.star_def, ite_true] at hherm + apply_fun (starRingEnd 𝕜) at hherm + have hcstar := RCLike.conj_eq_iff_im.mpr (RCLike.pos_iff.mp hc).right + rw [starRingEnd_self_apply, hcstar, ite_eq_left_iff] at hherm + contrapose! hherm + have hcnezero : 0 ≠ c := by + by_contra hczero + subst hczero + exact (lt_self_iff_false 0).mp hc + exact ⟨fun _ => hherm.symm, hcnezero⟩ + · intro hij + subst hij + constructor + · ext x y + simp only [conjTranspose_apply, RCLike.star_def, single, of_apply] + split_ifs <;> try tauto + · exact RCLike.conj_eq_iff_im.mpr (RCLike.pos_iff.1 hc).2 + · exact RingHom.map_zero (starRingEnd 𝕜) + · intro x + rw [Finsupp.sum_fintype _ _ (by simp)] + conv => + enter [2, 2, i] + rw [Finsupp.sum_fintype _ _ (by simp)] + simp only [single, of_apply] + convert_to 0 ≤ (star (x i)) * c * (x i) + · rw [←Fintype.sum_prod_type'] + have h₀ : ∀ x_1 : m × m, x_1 ≠ ⟨i, i⟩ → star (x x_1.1) * ((if i = x_1.1 ∧ i = x_1.2 then + c else 0) * x x_1.2) = 0 := fun z hz => by + have h₁ : ¬(i = z.1 ∧ i = z.2) := by + rw [ne_eq, Prod.mk_inj] at hz + by_contra hz' + apply hz + exact ⟨hz'.left.symm, hz'.right.symm⟩ + rw [ite_eq_right_of_eq_false _ _ (eq_false h₁)] + ring + rw [Fintype.sum_eq_single ⟨i, i⟩] + · simp [mul_assoc] + · simpa [mul_assoc] using h₀ + · rw [mul_comm, ←mul_assoc] + have hpos : 0 ≤ (x i) * star (x i) := by simp only [RCLike.star_def, + RCLike.mul_conj, RCLike.ofReal_nonneg, norm_nonneg, pow_nonneg] + exact (mul_nonneg hpos (le_of_lt hc)) + +end + +variable {A : Matrix m m 𝕜} {B : Matrix n n 𝕜} +variable (hA : A.PosSemidef) (hB : B.PosSemidef) + +include hA hB in +omit [DecidableEq n] [Fintype m] [Fintype n] in +theorem PosSemidef_kronecker [Finite m] [Finite n] : (A ⊗ₖ B).PosSemidef := by + classical + let := Fintype.ofFinite m + let := Fintype.ofFinite n + open Classical in + rw [hA.left.spectral_theorem, hB.left.spectral_theorem] + simp only [Unitary.conjStarAlgAut_apply] + rw [mul_kronecker_mul, mul_kronecker_mul] + rw [star_eq_conjTranspose, star_eq_conjTranspose] + rw [← kroneckerMap_conjTranspose] + rw [diagonal_kronecker_diagonal] + apply mul_mul_conjTranspose_same + rw [posSemidef_diagonal_iff] + rintro ⟨i₁, i₂⟩ + convert mul_nonneg (hA.eigenvalues_nonneg i₁) (hB.eigenvalues_nonneg i₂) + rw [RCLike.nonneg_iff] + simp + +variable [dm : DecidableEq m] + +open MatrixOrder +open ComplexOrder + +omit [DecidableEq m] + +include hA in +theorem zero_dotProduct_zero_iff : (∀ x : m → 𝕜, 0 = star x ⬝ᵥ A.mulVec x) ↔ A = 0 := by + constructor + · intro h + ext i j + have h₂ := fun x ↦ (PosSemidef.dotProduct_mulVec_zero_iff hA).mp (h x).symm + classical have : DecidableEq m := inferInstance + convert! congrFun (h₂ (Pi.single j 1)) i using 1 + simp + · rintro rfl + simp + +omit [Fintype m] in +theorem pos_smul {c : 𝕜} (hA : (c • A).PosSemidef) (hc : 0 < c) : A.PosSemidef := by + have : 0 < 1/c := by + rw [RCLike.pos_iff] at hc ⊢ + aesop + convert hA.smul (a := 1/c) this.le + rw [smul_smul, one_div, inv_mul_cancel₀ hc.ne', one_smul] + +omit [Fintype m] in +theorem zero_posSemidef_neg_posSemidef_iff [Finite m] : A.PosSemidef ∧ (-A).PosSemidef ↔ A = 0 := by + classical + let := Fintype.ofFinite m + constructor + · intro ⟨hA, hNegA⟩ + have h0 : ∀ x : m → 𝕜, 0 = star x ⬝ᵥ A.mulVec x := fun x ↦ by + simp only [Matrix.posSemidef_iff_dotProduct_mulVec] at hA hNegA + have hNegA' := hNegA.right (Finsupp.ofSupportFinite x (Function.support x).toFinite) + rw [neg_mulVec, dotProduct_neg, le_neg, neg_zero] at hNegA' + exact le_antisymm (hA.right x) hNegA' + exact (zero_dotProduct_zero_iff hA).mp h0 + · rintro rfl + simp [PosSemidef.zero] + +end PosSemidef + + +namespace PosDef +open scoped ComplexOrder + +variable {n m 𝕜 : Type*} +variable [Fintype n] [RCLike 𝕜] [DecidableEq n] +variable {A : Matrix n n 𝕜} + +theorem toLin_ker_eq_bot (hA : A.PosDef) : LinearMap.ker A.toLin' = ⊥ := by + ext v + rw [Matrix.posDef_iff_dotProduct_mulVec] at hA + have := @hA.right v + grind [mulVec_zero, dotProduct_zero, LinearMap.mem_ker, toLin'_apply, Submodule.mem_bot] + +theorem of_toLin_ker_eq_bot (hA : LinearMap.ker A.toLin' = ⊥) (hA₂ : A.PosSemidef) : A.PosDef := by + rwa [hA₂.posDef_iff_isUnit, ← Matrix.isUnit_toLin'_iff, LinearMap.isUnit_iff_ker_eq_bot] + +theorem ker_range_antitone {d : Type*} [Fintype d] [DecidableEq d] {A B : Matrix d d ℂ} + (hA : A.IsHermitian) (hB : B.IsHermitian) : + LinearMap.ker A.toEuclideanLin ≤ LinearMap.ker B.toEuclideanLin ↔ + LinearMap.range B.toEuclideanLin ≤ LinearMap.range A.toEuclideanLin + := by + rw [isSymmetric_toEuclideanLin_iff.symm] at hA hB + exact ContinuousLinearMap.ker_le_ker_iff_range_le_range + (T := Matrix.toEuclideanCLM.toFun B) (U := Matrix.toEuclideanCLM.toFun A) hB hA + +end PosDef + +namespace PosSemidef +section partialOrder +open scoped ComplexOrder +open scoped MatrixOrder + +variable {n m 𝕜 : Type*} +variable [Fintype n] [Fintype m] [RCLike 𝕜] [DecidableEq m] +variable {A : Matrix n n 𝕜} {B : Matrix n n 𝕜} +variable (hA : A.IsHermitian) (hB : B.IsHermitian) + +omit [Fintype n] in +theorem le_of_nonneg_imp {R : Type*} [AddCommGroup R] [PartialOrder R] [IsOrderedAddMonoid R] + (f : Matrix n n 𝕜 →+ R) (h : ∀ A, A.PosSemidef → 0 ≤ f A) : + (A ≤ B → f A ≤ f B) := by + intro hAB + rw [←sub_nonneg, ←map_sub] + exact h (B - A) <| by rwa [← Matrix.le_iff] + +omit [Fintype n] in +theorem le_of_nonneg_imp' {R : Type*} [AddCommGroup R] [PartialOrder R] [IsOrderedAddMonoid R] + {x y : R} (f : R →+ Matrix n n 𝕜) (h : ∀ x, 0 ≤ x → (f x).PosSemidef) : + (x ≤ y → f x ≤ f y) := by + intro hxy + rw [← sub_nonneg, ← map_sub, Matrix.nonneg_iff_posSemidef] + rw [← sub_nonneg] at hxy + exact h (y - x) hxy + +omit [DecidableEq m] in +omit [Fintype m] in +theorem mul_mul_conjTranspose_mono [Finite m] (C : Matrix m n 𝕜) : + A ≤ B → C * A * C.conjTranspose ≤ C * B * C.conjTranspose := by + classical + let := Fintype.ofFinite m + exact + fun hAB ↦ by + rw [Matrix.le_iff] at hAB ⊢ + have hDistrib : C * B * Cᴴ - C * A * Cᴴ = C * (B - A) * Cᴴ := by + ext i j + simp only [sub_apply, mul_apply, conjTranspose_apply, RCLike.star_def, Finset.sum_mul, + ←Finset.sum_sub_distrib, mul_sub_left_distrib, mul_sub_right_distrib] + rw [hDistrib] + exact mul_mul_conjTranspose_same hAB C + +omit [DecidableEq m] in +omit [Fintype m] in +theorem conjTranspose_mul_mul_mono [Finite m] (C : Matrix n m 𝕜) : + A ≤ B → C.conjTranspose * A * C ≤ C.conjTranspose * B * C := by + classical + let := Fintype.ofFinite m + exact + fun hAB ↦ by + convert mul_mul_conjTranspose_mono Cᴴ hAB + <;> rw [conjTranspose_conjTranspose] + +theorem nonneg_iff_eigenvalue_nonneg [DecidableEq n] : 0 ≤ A ↔ ∀ x, 0 ≤ hA.eigenvalues x := + Iff.trans Matrix.nonneg_iff_posSemidef hA.posSemidef_iff_eigenvalues_nonneg + +omit [Fintype n] in +theorem diag_monotone : Monotone (diag : Matrix n n 𝕜 → (n → 𝕜)) := fun _ _ ↦ + le_of_nonneg_imp (diagAddMonoidHom n 𝕜) (fun _ ↦ diag_nonneg) + +omit [Fintype n] in +theorem diag_mono : A ≤ B → ∀ i, A.diag i ≤ B.diag i := diag_monotone.imp + +theorem trace_monotone : Monotone (@trace n 𝕜 _ _) := fun _ _ ↦ + le_of_nonneg_imp (traceAddMonoidHom n 𝕜) (fun _ ↦ trace_nonneg) + +theorem trace_mono : A ≤ B → A.trace ≤ B.trace := trace_monotone.imp + +variable [DecidableEq n] + +omit [Fintype n] in +theorem diagonal_monotone : Monotone (diagonal : (n → 𝕜) → _) := fun _ _ ↦ + le_of_nonneg_imp' (diagonalAddMonoidHom n 𝕜) (fun _ ↦ PosSemidef.diagonal) + +omit [Fintype n] in +theorem diagonal_mono {d₁ d₂ : n → 𝕜} : d₁ ≤ d₂ → diagonal d₁ ≤ diagonal d₂ := diagonal_monotone.imp + +omit [Fintype n] in +theorem diagonal_le_iff {d₁ d₂ : n → 𝕜} : d₁ ≤ d₂ ↔ diagonal d₁ ≤ diagonal d₂ := ⟨diagonal_mono, by + intro hd + rw [Matrix.le_iff, diagonal_sub, posSemidef_diagonal_iff] at hd + simp only [sub_nonneg] at hd + exact hd⟩ + +theorem le_smul_one_of_eigenvalues_iff (hA : A.IsHermitian) (c : ℝ) : + (∀ i, hA.eigenvalues i ≤ c) ↔ A ≤ c • (1 : Matrix n n 𝕜) := by + let U : Matrix n n 𝕜 := ↑hA.eigenvectorUnitary + have hU : U.conjTranspose = star U := by simp only [star] + have hU' : U * star U = 1 := by + simp only [SetLike.coe_mem, Unitary.mul_star_self_of_mem, U] + have hc : c • (1 : Matrix n n 𝕜) = U * (c • 1) * U.conjTranspose := by + simp only [Algebra.mul_smul_comm, mul_one, hU, Algebra.smul_mul_assoc, hU'] + have hc' : c • (1 : Matrix n n 𝕜) = diagonal (RCLike.ofReal ∘ fun _ : n ↦ c) := by + ext i j + simp only [smul_apply, one_apply, smul_ite, RCLike.real_smul_eq_coe_mul, mul_one, smul_zero, + diagonal, Function.comp_apply, of_apply] + have hAST : A = U * diagonal (RCLike.ofReal ∘ hA.eigenvalues) * U.conjTranspose := by + rw [hU] + exact IsHermitian.spectral_theorem hA + constructor + · intro h + rw [hc, hc', hAST] + apply mul_mul_conjTranspose_mono + apply diagonal_mono + intro i + simp only [Function.comp_apply, algebraMap_le_algebraMap, h i] + intro hAc i + replace hAc := conjTranspose_mul_mul_mono U hAc + have hU'CT : star U * U = 1 := by + simp only [SetLike.coe_mem, Unitary.star_mul_self_of_mem, U] + have hcCT : U.conjTranspose * (c • 1) * U = c • (1 : Matrix n n 𝕜) := by + simp only [Algebra.mul_smul_comm, mul_one, hU, Algebra.smul_mul_assoc, hU'CT] + have hASTCT : U.conjTranspose * A * U = diagonal (RCLike.ofReal ∘ hA.eigenvalues) := by + rw [hU] + convert IsHermitian.conjStarAlgAut_star_eigenvectorUnitary hA using 1 + simp +zetaDelta + rw [hcCT, hc', hASTCT, ←diagonal_le_iff] at hAc + specialize hAc i + simp only [Function.comp_apply, algebraMap_le_algebraMap] at hAc + exact hAc + +theorem smul_one_le_of_eigenvalues_iff (hA : A.IsHermitian) (c : ℝ) : + (∀ i, c ≤ hA.eigenvalues i) ↔ c • (1 : Matrix n n 𝕜) ≤ A := by + -- I did the lazy thing and just copied the previous proof + let U : Matrix n n 𝕜 := ↑hA.eigenvectorUnitary + have hU : U.conjTranspose = star U := by simp only [star] + have hU' : U * star U = 1 := by + simp only [SetLike.coe_mem, Unitary.mul_star_self_of_mem, U] + have hc : c • (1 : Matrix n n 𝕜) = U * (c • 1) * U.conjTranspose := by + simp only [Algebra.mul_smul_comm, mul_one, hU, Algebra.smul_mul_assoc, hU'] + have hc' : c • (1 : Matrix n n 𝕜) = diagonal (RCLike.ofReal ∘ fun _ : n ↦ c) := by + ext i j + simp only [smul_apply, one_apply, smul_ite, RCLike.real_smul_eq_coe_mul, mul_one, smul_zero, + diagonal, Function.comp_apply, of_apply] + have hAST : A = U * diagonal (RCLike.ofReal ∘ hA.eigenvalues) * U.conjTranspose := by + rw [hU] + exact IsHermitian.spectral_theorem hA + constructor + · intro h + rw [hc, hc', hAST] + apply mul_mul_conjTranspose_mono + apply diagonal_mono + intro i + simp only [Function.comp_apply, algebraMap_le_algebraMap, h i] + intro hAc i + replace hAc := conjTranspose_mul_mul_mono U hAc + have hU'CT : star U * U = 1 := by + simp only [SetLike.coe_mem, Unitary.star_mul_self_of_mem, U] + have hcCT : U.conjTranspose * (c • 1) * U = c • (1 : Matrix n n 𝕜) := by + simp only [Algebra.mul_smul_comm, mul_one, hU, Algebra.smul_mul_assoc, hU'CT] + have hASTCT : U.conjTranspose * A * U = diagonal (RCLike.ofReal ∘ hA.eigenvalues) := by + rw [hU] + convert IsHermitian.conjStarAlgAut_star_eigenvectorUnitary hA using 1 + simp +zetaDelta + rw [hcCT, hc', hASTCT, ←diagonal_le_iff] at hAc + specialize hAc i + simp only [Function.comp_apply, algebraMap_le_algebraMap] at hAc + exact hAc + +end partialOrder + +end PosSemidef + +-- noncomputable section frobenius_inner_product +-- open scoped ComplexOrder +-- variable {A : Matrix n n 𝕜} {B : Matrix n n 𝕜} {C : Matrix n n 𝕜} [Fintype n] + +-- /-- The InnerProductSpace on Matrix n n 𝕜 defined by the real part of the +-- Frobenius inner product. -/ +-- def InnerProductCore : InnerProductSpace.Core (𝕜 := ℝ) (F := Matrix n n 𝕜):= +-- { +-- inner A B := RCLike.re (Aᴴ * B).trace +-- conj_inner_symm := fun x y ↦ by +-- simpa [inner, starRingEnd_apply, ← trace_conjTranspose] using +-- RCLike.conj_re (xᴴ * y).trace +-- re_inner_nonneg := fun x ↦ +-- (RCLike.nonneg_iff.mp x.posSemidef_conjTranspose_mul_self.trace_nonneg).1 +-- add_left := by simp [inner, add_mul] +-- smul_left x y r := by +-- simpa using RCLike.smul_re _ (xᴴ * y).trace +-- definite x h := by +-- ext i j +-- replace h : ∑ j, ∑ i, (RCLike.re (x i j) ^ 2 + RCLike.im (x i j) ^ 2) = 0 := by +-- simpa [trace, mul_apply, ← pow_two] using h +-- rw [Fintype.sum_eq_zero_iff_of_nonneg (fun i ↦ by positivity)] at h +-- replace h := congrFun h j +-- rw [Pi.zero_apply, Fintype.sum_eq_zero_iff_of_nonneg (fun i ↦ by positivity)] at h +-- replace h := congrFun h i +-- dsimp at h +-- rw [add_eq_zero_iff_of_nonneg (sq_nonneg _) (sq_nonneg _), sq_eq_zero_iff, sq_eq_zero_iff] at +-- h +-- apply RCLike.ext (h.left.trans RCLike.zero_re.symm) (h.right.trans (map_zero _).symm) +-- } + +-- def instNormed : NormedAddCommGroup (Matrix n n 𝕜) := +-- InnerProductCore.toNormedAddCommGroup + +-- scoped[Frobenius] attribute [instance] Matrix.instNormed + +-- open scoped Frobenius in +-- def instInnerProductSpace : InnerProductSpace ℝ (Matrix n n 𝕜) := +-- InnerProductSpace.ofCore InnerProductCore + +-- scoped[Frobenius] attribute [instance] Matrix.instInnerProductSpace + +-- instance : Inner ℝ (Matrix n n 𝕜) := +-- instInnerProductSpace.toInner + +-- /-- The InnerProductSpace on Matrix n n 𝕜 defined by the Frobenius inner product. -/ +-- def CInnerProductCore : InnerProductSpace.Core (𝕜 := ℂ) (F := Matrix n n ℂ):= +-- { +-- inner A B := (Aᴴ * B).trace +-- conj_inner_symm := fun x y ↦ by +-- simp [inner, starRingEnd_apply, ← Matrix.trace_conjTranspose] +-- re_inner_nonneg := fun x ↦ +-- (RCLike.nonneg_iff.mp x.posSemidef_conjTranspose_mul_self.trace_nonneg).1 +-- add_left := by simp [inner, add_mul] +-- smul_left x y r := by simp +-- definite x h := by +-- ext i j +-- replace h : ∑ j, ∑ i, ((x i j).re ^ 2 + (x i j).im ^ 2) = (0 : ℂ) := by +-- convert h +-- simp only [Complex.ofReal_sum, Complex.ofReal_add, Complex.ofReal_pow, trace, diag_apply, +-- mul_apply, conjTranspose_apply, RCLike.star_def] +-- congr! 2 +-- norm_cast +-- rw [Complex.conj_mul', ← Complex.sq_norm_sub_sq_re] +-- norm_cast +-- abel +-- rw [Complex.ofReal_eq_zero, +-- Fintype.sum_eq_zero_iff_of_nonneg (fun i ↦ by positivity)] at h +-- replace h := congrFun h j +-- rw [Pi.zero_apply, Fintype.sum_eq_zero_iff_of_nonneg (fun i ↦ by positivity)] at h +-- replace h := congrFun h i +-- dsimp at h +-- rw [add_eq_zero_iff_of_nonneg (sq_nonneg _) (sq_nonneg _), sq_eq_zero_iff, sq_eq_zero_iff] at +-- h +-- apply RCLike.ext (h.left.trans RCLike.zero_re.symm) (h.right.trans (map_zero _).symm) +-- } + +-- open scoped Frobenius in +-- def instCInnerProductSpace : InnerProductSpace ℂ (Matrix n n ℂ) := +-- InnerProductSpace.ofCore CInnerProductCore + +-- scoped[Frobenius] attribute [instance] Matrix.instCInnerProductSpace + +-- instance : Inner ℂ (Matrix n n ℂ) := +-- instCInnerProductSpace.toInner + +--Makes the `Inner ℝ` instance is globally accessible, but the norm instances +--require `open scoped Frobenius`. e.g. + +-- open scoped Frobenius in +-- #synth InnerProductSpace ℝ (Matrix (Fin 5) (Fin 5) ℝ) + +-- (no `open` needed): +-- #synth Inner ℝ (Matrix (Fin 5) (Fin 5) ℝ) + +-- end frobenius_inner_product + +section partial_trace + +variable [AddCommMonoid R] [Fintype d] + +/-- The partial trace obtained by summing diagonal entries in the left index factor. -/ +def traceLeft (m : Matrix (d × d₁) (d × d₂) R) : Matrix d₁ d₂ R := + Matrix.of fun i₁ j₁ ↦ ∑ i₂, m (i₂, i₁) (i₂, j₁) + +/-- The partial trace obtained by summing diagonal entries in the right index factor. -/ +def traceRight (m : Matrix (d₁ × d) (d₂ × d) R) : Matrix d₁ d₂ R := + Matrix.of fun i₂ j₂ ↦ ∑ i₁, m (i₂, i₁) (j₂, i₁) + +variable [Fintype d₁] [Fintype d₂] in +@[simp] +theorem traceLeft_trace (A : Matrix (d₁ × d₂) (d₁ × d₂) R) : A.traceLeft.trace = A.trace := by + convert! (Fintype.sum_prod_type_right _).symm + rfl + +variable [Fintype d₁] [Fintype d₂] in +@[simp] +theorem traceRight_trace (A : Matrix (d₁ × d₂) (d₁ × d₂) R) : A.traceRight.trace = A.trace := by + convert! (Fintype.sum_prod_type _).symm + rfl + +variable [StarAddMonoid R] in +theorem IsHermitian.traceLeft {A : Matrix (d × d₁) (d × d₁) R} (hA : A.IsHermitian) : + A.traceLeft.IsHermitian := by + ext + simp only [Matrix.traceLeft, conjTranspose_apply, of_apply, star_sum] + congr! + exact congrFun₂ hA _ _ + +variable [StarAddMonoid R] in +theorem IsHermitian.traceRight {A : Matrix (d₁ × d) (d₁ × d) R} (hA : A.IsHermitian) : + A.traceRight.IsHermitian := by + ext + simp only [Matrix.traceRight, conjTranspose_apply, of_apply, star_sum] + congr! + exact congrFun₂ hA _ _ + +variable [DecidableEq dB] [Fintype dA] [Fintype dB] in +open scoped Kronecker in +/-- +`Tr(M (A ⊗ I)) = Tr(Tr_B(M) A)` +-/ +theorem trace_mul_kron_one_right {R : Type*} [Ring R] + (M : Matrix (dA × dB) (dA × dB) R) (A : Matrix dA dA R) : + (M * (A ⊗ₖ (1 : Matrix dB dB R))).trace = (M.traceRight * A).trace := by + simp? [trace, mul_apply, kroneckerMap_apply, traceRight, one_apply, + Fintype.sum_prod_type, Finset.sum_mul] + exact Finset.sum_congr rfl fun _ _ => Finset.sum_comm + +variable [DecidableEq dA] [Fintype dA] [Fintype dB] in +open scoped Kronecker in +/-- +`Tr(M (I ⊗ B)) = Tr(Tr_A(M) B)` +-/ +theorem trace_mul_one_kron_right {R : Type*} [Ring R] + (M : Matrix (dA × dB) (dA × dB) R) (B : Matrix dB dB R) : + (M * ((1 : Matrix dA dA R) ⊗ₖ B)).trace = (M.traceLeft * B).trace := by + simp? [trace, mul_apply, kroneckerMap_apply, traceLeft, one_apply, + Fintype.sum_prod_type, Finset.sum_mul] + rw [Finset.sum_comm] + exact Finset.sum_congr rfl fun _ _ => Finset.sum_comm + +open ComplexOrder + +variable {d₁ d₂ : Type*} {A : Matrix (d₁ × d₂) (d₁ × d₂) 𝕜} +variable [Fintype d₂] [Fintype d₁] + +omit [Fintype d₂] in +theorem PosSemidef.traceLeft [Finite d₂] (hA : A.PosSemidef) : A.traceLeft.PosSemidef := by + classical + let := Fintype.ofFinite d₂ + rw [Matrix.posSemidef_iff_dotProduct_mulVec] at hA ⊢ + constructor + · exact hA.1.traceLeft + · intro x + convert Finset.sum_nonneg (s := .univ) (fun (i : d₁) _ ↦ hA.2 (fun (j,k) ↦ if i = j then x k + else 0)) + simp_rw [Matrix.traceLeft, dotProduct_mulVec] + simpa [dotProduct, vecMul_eq_sum, ite_apply, Fintype.sum_prod_type, Finset.mul_sum, + Finset.sum_mul, + apply_ite] using Finset.sum_comm_cycle + +omit [Fintype d₁] in +theorem PosSemidef.traceRight [Finite d₁] (hA : A.PosSemidef) : A.traceRight.PosSemidef := by + classical + let := Fintype.ofFinite d₁ + rw [Matrix.posSemidef_iff_dotProduct_mulVec] at hA ⊢ + constructor + · exact hA.1.traceRight + · intro x + convert Finset.sum_nonneg (s := .univ) (fun (i : d₂) _ ↦ hA.2 (fun (j,k) ↦ if i = k then x j + else 0)) + simp_rw [Matrix.traceRight, dotProduct_mulVec] + simpa [dotProduct, vecMul_eq_sum, ite_apply, Fintype.sum_prod_type, Finset.mul_sum, + Finset.sum_mul, + apply_ite] using Finset.sum_comm_cycle + +end partial_trace + +section posdef + +open ComplexOrder +open Kronecker + +theorem PosDef.kron {d₁ d₂ 𝕜 : Type*} [Finite d₁] [Finite d₂] [RCLike 𝕜] + {A : Matrix d₁ d₁ 𝕜} {B : Matrix d₂ d₂ 𝕜} (hA : A.PosDef) (hB : B.PosDef) : (A ⊗ₖ B).PosDef + := by + classical + let := Fintype.ofFinite d₁ + let := Fintype.ofFinite d₂ + rw [hA.left.spectral_theorem, hB.left.spectral_theorem] + simp only [Unitary.conjStarAlgAut_apply] + rw [mul_kronecker_mul, mul_kronecker_mul] + rw [star_eq_conjTranspose, star_eq_conjTranspose] + rw [← kroneckerMap_conjTranspose] + rw [diagonal_kronecker_diagonal] + apply mul_mul_conjTranspose_same + · rw [posDef_diagonal_iff] + rintro ⟨i₁, i₂⟩ + convert mul_pos (hA.eigenvalues_pos i₁) (hB.eigenvalues_pos i₂) + rw [RCLike.pos_iff] + simp + · apply Matrix.vecMul_injective_of_isUnit + rw [isUnit_iff_exists] + use (star hA.left.eigenvectorUnitary.val) ⊗ₖ (star hB.left.eigenvectorUnitary.val) + simp [← Matrix.mul_kronecker_mul] + +theorem PosDef.reindex {d₁ d₂ 𝕜 : Type*} [RCLike 𝕜] + {M : Matrix d₁ d₁ 𝕜} (hM : M.PosDef) (e : d₁ ≃ d₂) : (M.reindex e e).PosDef := + hM.submatrix e.symm.injective + +theorem PosDef.reindex_iff {d₁ d₂ 𝕜 : Type*} [RCLike 𝕜] + {M : Matrix d₁ d₁ 𝕜} (e : d₁ ≃ d₂) : (M.reindex e e).PosDef ↔ M.PosDef := by + refine ⟨fun h ↦ ?_, fun h ↦ h.reindex e⟩ + convert h.reindex e.symm + simp + +theorem PosSemidef.rsmul {n : Type*} [Finite n] {M : Matrix n n ℂ} (hM : M.PosSemidef) {c : ℝ} + (hc : 0 ≤ c) : + (c • M).PosSemidef := by + classical + let := Fintype.ofFinite n + rw [Matrix.posSemidef_iff_dotProduct_mulVec] at hM ⊢ + constructor + · exact hM.1.smul_real c + · intro x + rw [smul_mulVec, dotProduct_smul] + exact smul_nonneg hc (hM.2 x) + +theorem PosDef.Convex {n 𝕜 : Type*} [RCLike 𝕜] : Convex ℝ (Matrix.PosDef (n := n) (R := 𝕜)) := by + intro A hA B hB a b ha hb hab + rcases ha.lt_or_eq with ha | rfl + · apply (hA.smul ha).add_posSemidef + exact hB.posSemidef.smul hb + · apply Matrix.PosDef.posSemidef_add + · simp [Matrix.PosSemidef.zero] + · exact hB.smul (by linarith) + +end posdef + +section eigenvalues + +open ComplexOrder + +variable {d 𝕜 : Type*} [Fintype d] [DecidableEq d] [RCLike 𝕜] + +theorem PosDef_iff_eigenvalues' (M : Matrix d d 𝕜) : + M.PosDef ↔ ∃ (h : M.IsHermitian), ∀ i, 0 < h.eigenvalues i := + ⟨fun h ↦ ⟨h.left, h.left.posDef_iff_eigenvalues_pos.mp h⟩, + fun ⟨w, h⟩ ↦ w.posDef_iff_eigenvalues_pos.mpr h⟩ + +-- These is disgusting atm. There's cleaner versions of them headed to Mathlib. See #29526 and +-- follow-ups +theorem IsHermitian.cfc_eigenvalues {M : Matrix d d 𝕜} (hM : M.IsHermitian) (f : ℝ → ℝ) : + ∃ (e : d ≃ d), Matrix.IsHermitian.eigenvalues (cfc_predicate f M) = f ∘ hM.eigenvalues ∘ e := by + have h_eigenvalues : Multiset.map hM.eigenvalues Finset.univ.val = Multiset.map (fun i => + hM.eigenvalues i) Finset.univ.val := by + rfl + generalize_proofs at *; + have h_eigenvalues_cfc : (IsHermitian.cfc hM f).charpoly.roots = Multiset.map (fun i => (f + (hM.eigenvalues i) : 𝕜)) Finset.univ.val := by + rw [ Matrix.IsHermitian.cfc, Matrix.charpoly ]; + -- Since $U$ is unitary, we have $U^* U = I$, and thus the characteristic polynomial of + -- $U D U^*$ is the same as the characteristic polynomial of $D$. + have h_charpoly : Matrix.det ((hM.eigenvectorUnitary : Matrix d d 𝕜) * Matrix.diagonal + (RCLike.ofReal ∘ f ∘ hM.eigenvalues) * Star.star (hM.eigenvectorUnitary : Matrix d d + 𝕜)).charmatrix = Matrix.det (Matrix.diagonal (RCLike.ofReal ∘ f ∘ + hM.eigenvalues)).charmatrix := by + -- Since $U$ is unitary, we have $U^* U = I$, and thus the characteristic polynomial + -- of $U D U^*$ is the same as the characteristic polynomial of $D$ by the + -- properties of determinants. + have h_char_poly : ∀ (t : 𝕜), Matrix.det (t • 1 - (hM.eigenvectorUnitary : Matrix d d 𝕜) * + Matrix.diagonal (RCLike.ofReal ∘ f ∘ hM.eigenvalues) * star (hM.eigenvectorUnitary : + Matrix d d 𝕜)) = Matrix.det (t • 1 - Matrix.diagonal (RCLike.ofReal ∘ f ∘ + hM.eigenvalues)) := by + intro t; + -- Since $U$ is unitary, we have $U^* U = I$, and thus the determinant of $tI - + -- UDU^*$ is the same as the determinant of $tI - D$. + have h_det : Matrix.det (t • 1 - (hM.eigenvectorUnitary : Matrix d d 𝕜) * + Matrix.diagonal (RCLike.ofReal ∘ f ∘ hM.eigenvalues) * star (hM.eigenvectorUnitary : + Matrix d d 𝕜)) = Matrix.det ((hM.eigenvectorUnitary : Matrix d d 𝕜) * (t • 1 - + Matrix.diagonal (RCLike.ofReal ∘ f ∘ hM.eigenvalues)) * star (hM.eigenvectorUnitary + : Matrix d d 𝕜)) := by + simp [ mul_sub, sub_mul, mul_assoc ]; + rw [ h_det, Matrix.det_mul, Matrix.det_mul ]; + rw [ mul_right_comm, ← Matrix.det_mul, mul_comm ]; + norm_num +zetaDelta at *; + refine Polynomial.funext fun t => ?_; + convert h_char_poly t using 1; + · simp? [ Matrix.det_apply', Polynomial.eval_finsetSum ]; + simp? [ Matrix.one_apply, Polynomial.eval_prod ]; + congr! 3; + aesop; + · simp? [ Matrix.det_apply', Polynomial.eval_finsetSum ]; + simp? [ Matrix.one_apply, Polynomial.eval_prod ]; + exact Finset.sum_congr rfl fun _ _ => by congr; ext; aesop; + simp_all? [ Matrix.charmatrix, Matrix.det_diagonal ]; + rw [ Polynomial.roots_prod ]; + · bound; + · exact Finset.prod_ne_zero_iff.mpr fun i _ => Polynomial.X_sub_C_ne_zero _; + have := Matrix.IsHermitian.roots_charpoly_eq_eigenvalues (cfc_predicate f M); + rw [← Matrix.IsHermitian.cfc_eq] at h_eigenvalues_cfc + rw [ h_eigenvalues_cfc ] at this; + simp? [ Function.comp ] at this; + rw [ Multiset.map_univ_eq_iff ] at this; + obtain ⟨ e, he ⟩ := this; + use e.symm + ext x + have := congr_fun he ( e.symm x ); + simp_all only [Function.comp_apply, Equiv.apply_symm_apply, algebraMap.coe_inj] + +--Should be combined the above...? TODO Cleanup +/-- +If a Hermitian matrix A is unitarily similar to a diagonal matrix with real entries f, then the + eigenvalues of A are a permutation of f. +-/ +lemma IsHermitian.eigenvalues_eq_of_unitary_similarity_diagonal {d 𝕜 : Type*} + [Fintype d] [DecidableEq d] [RCLike 𝕜] + {A : Matrix d d 𝕜} (hA : A.IsHermitian) + {U : Matrix d d 𝕜} (hU : U ∈ Matrix.unitaryGroup d 𝕜) + {f : d → ℝ} + (h : A = U * Matrix.diagonal (fun i => (RCLike.ofReal (f i) : 𝕜)) * Matrix.conjTranspose U) : + ∃ σ : d ≃ d, hA.eigenvalues ∘ σ = f := by + -- Since A is unitarily similar to D, they have the same characteristic polynomial. + have h_char_poly : Matrix.charpoly A = Matrix.charpoly (Matrix.diagonal fun i => (f i : 𝕜)) := by + have h_char_poly : Matrix.charpoly (U * Matrix.diagonal (fun i => (f i : 𝕜)) * Uᴴ) = + Matrix.charpoly (Matrix.diagonal (fun i => (f i : 𝕜))) := by + have h_det : ∀ (t : 𝕜), Matrix.det (t • 1 - U * Matrix.diagonal (fun i => (f i : 𝕜)) * Uᴴ) + = Matrix.det (t • 1 - Matrix.diagonal (fun i => (f i : 𝕜))) := by + intro t + have h_det : Matrix.det (t • 1 - U * Matrix.diagonal (fun i => (f i : 𝕜)) * Uᴴ) = + Matrix.det (U * (t • 1 - Matrix.diagonal (fun i => (f i : 𝕜))) * Uᴴ) := by + simp? [ mul_sub, sub_mul, Matrix.mul_assoc ]; + rw [ show U * Uᴴ = 1 from by exact hU.2 ]; + rw [h_det, Matrix.det_mul_comm, ← mul_assoc] + rw [← star_eq_conjTranspose, Matrix.UnitaryGroup.star_mul_self ⟨U, hU⟩] + simp + refine Polynomial.funext fun t => ?_; + convert h_det t using 1 <;> simp? [ Matrix.charpoly, Matrix.det_apply' ]; + · simp? [ Polynomial.eval_finsetSum, Polynomial.eval_mul, Polynomial.eval_prod, + Matrix.one_apply ]; + exact Finset.sum_congr rfl fun _ _ => by congr; ext; aesop; + · simp? [ Polynomial.eval_finsetSum, Polynomial.eval_mul, Polynomial.eval_prod, + Matrix.one_apply ]; + exact Finset.sum_congr rfl fun _ _ => by congr; ext; aesop; + rw [ h, h_char_poly ]; + -- The roots of the characteristic polynomial of A are its eigenvalues (by + -- `IsHermitian.charpoly_roots_eq_eigenvalues`). + have h_eigenvalues : (Matrix.charpoly A).roots = Multiset.map (RCLike.ofReal ∘ hA.eigenvalues) + Finset.univ.val := by + exact Matrix.IsHermitian.roots_charpoly_eq_eigenvalues hA; + -- The roots of the characteristic polynomial of D are the diagonal entries f. + have h_diag_roots : (Matrix.charpoly (Matrix.diagonal fun i => (f i : 𝕜))).roots = + Multiset.map (fun i => (f i : 𝕜)) Finset.univ.val := by + simp? [ Matrix.charpoly, Matrix.det_diagonal ]; + rw [ Polynomial.roots_prod ]; + · aesop; + · exact Finset.prod_ne_zero_iff.mpr fun i _ => Polynomial.X_sub_C_ne_zero _; + have := Multiset.map_univ_eq_iff ( RCLike.ofReal ∘ hA.eigenvalues ) f + subst h + simp_all only [Function.comp_apply, RCLike.ofReal_real_eq_id, id_eq, CompTriple.comp_eq] + refine this.mp ?_ |> fun ⟨ e, he ⟩ => ⟨ e.symm, ?_ ⟩ + · simpa [ Function.comp ] using congr_arg ( Multiset.map ( RCLike.re : 𝕜 → ℝ ) ) + h_eigenvalues.symm + · exact funext fun x => by simpa using congr_fun he ( e.symm x ); + +end eigenvalues + +section + +variable {α n : Type*} [RCLike α] [Fintype n] [DecidableEq n] + +theorem toEuclideanLin_one : Matrix.toEuclideanLin (1 : Matrix n n α) = .id := by + ext1 x + simp [Matrix.toEuclideanLin] + +end + +section more_cfc + +open ComplexOrder + +variable {d 𝕜 : Type*} [Fintype d] [DecidableEq d] [RCLike 𝕜] + +@[simp] +theorem cfc_diagonal (g : d → ℝ) (f : ℝ → ℝ) : + cfc f (Matrix.diagonal (fun x ↦ (g x : 𝕜))) = diagonal (RCLike.ofReal ∘ f ∘ g) := by + have h_self_adjoint : _root_.IsSelfAdjoint (diagonal (fun x => (g x : 𝕜))) := by + change Matrix.conjTranspose _ = _ + simp [Matrix.conjTranspose] + have hg (x : d) : g x ∈ spectrum ℝ (diagonal (fun y => (g y : 𝕜))) := by + intro hc + have h := congrArg (fun M : Matrix d d 𝕜 => M x x) hc.exists_left_inv.choose_spec + simp [algebraMap_eq_diagonal] at h + let φ : C(spectrum ℝ (diagonal (fun x => (g x : 𝕜))), ℝ) →⋆ₐ[ℝ] Matrix d d 𝕜 := { + toFun := fun h => diagonal fun x => RCLike.ofReal (h ⟨g x, hg x⟩) + map_zero' := by simp + map_one' := by simp + map_mul' := by + intro h k + simp only [ContinuousMap.coe_mul, Pi.mul_apply, RCLike.ofReal_mul, + diagonal_mul_diagonal] + map_add' := by + intro h k + simp only [ContinuousMap.coe_add, Pi.add_apply, RCLike.ofReal_add, diagonal_add] + commutes' := by + intro r + simp only [Algebra.algebraMap_eq_smul_one, ContinuousMap.smul_apply, + ContinuousMap.one_apply, smul_eq_mul, mul_one] + exact (diagonal_smul r (1 : d → 𝕜)).trans + (congrArg (fun M : Matrix d d 𝕜 => r • M) diagonal_one') + map_star' := by + intro h + simp only [star_trivial, star_eq_conjTranspose, diagonal_conjTranspose, Pi.star_def, + RCLike.star_def, RCLike.conj_ofReal] } + have hφ : Continuous φ := by + apply continuous_matrix + intro i j + change Continuous (fun h : C(spectrum ℝ (diagonal (fun x => (g x : 𝕜))), ℝ) => + diagonal (fun x => (h ⟨g x, hg x⟩ : 𝕜)) i j) + by_cases h : i = j + · subst j + simp only [diagonal_apply_eq] + exact RCLike.continuous_ofReal.comp (continuous_eval_const _) + · simp only [diagonal_apply_ne _ h] + exact continuous_const + rw [cfc, dite_eq_left ⟨h_self_adjoint, + continuousOn_iff_continuous_domRestrict.mpr (by fun_prop)⟩] + rw [cfcHom_eq_of_continuous_of_map_id h_self_adjoint φ hφ (by + ext i j + by_cases h : i = j <;> simp [φ, h])] + rfl + +theorem PosSemidef.pos_of_mem_spectrum {A : Matrix d d 𝕜} (hA : A.PosSemidef) (r : ℝ) : + r ∈ spectrum ℝ A → 0 ≤ r := by + intro hr + rw [hA.left.spectrum_real_eq_range_eigenvalues] at hr + rcases hr with ⟨i, rfl⟩ + exact hA.eigenvalues_nonneg i + +theorem PosSemidef.pow_add {A : Matrix d d 𝕜} (hA : A.PosSemidef) {x y : ℝ} (hxy : x + y ≠ 0) : + cfc (· ^ (x + y) : ℝ → ℝ) A = cfc (fun r ↦ r ^ x * r ^ y : ℝ → ℝ) A := by + refine cfc_congr fun r hr ↦ ?_ + exact Real.rpow_add' (hA.pos_of_mem_spectrum r hr) hxy + +theorem PosSemidef.pow_mul {A : Matrix d d 𝕜} {x y : ℝ} (hA : A.PosSemidef) : + cfc (· ^ (x * y) : ℝ → ℝ) A = cfc (fun r ↦ (r ^ x) ^ y : ℝ → ℝ) A := by + refine cfc_congr fun r hr ↦ ?_ + exact Real.rpow_mul (hA.pos_of_mem_spectrum r hr) x y + +end more_cfc + +section subm + +variable {α : Type*} [AddCommMonoid α] +variable {d₁ d₂ : Type*} [Fintype d₁] [Fintype d₂] + +@[simp] +theorem trace_submatrix + (A : Matrix d₁ d₁ α) (e : d₂ ≃ d₁) : + (A.submatrix e e).trace = A.trace := by + simpa [Matrix.trace] using e.sum_comp (fun x ↦ A x x) + +end subm + +section spectrum_kron + +--This is really really ugly, and already *after* trying to clean it up a bit. + + +open Kronecker +open scoped Pointwise + +private lemma spectrum_prod_complex {d d₂ : Type*} + [Fintype d] [DecidableEq d] [Fintype d₂] [DecidableEq d₂] + {A : Matrix d d 𝕜} {B : Matrix d₂ d₂ 𝕜} + (hA : A.IsHermitian) (hB : B.IsHermitian) : + ∀ x : 𝕜, x ∈ spectrum 𝕜 (A ⊗ₖ B) → ∃ a ∈ spectrum 𝕜 A, ∃ b ∈ spectrum 𝕜 B, x = a * b := by + intro x hx + have h_det : Matrix.det (A ⊗ₖ B - x • 1) = 0 := by + rw [ spectrum.mem_iff, Matrix.isUnit_iff_isUnit_det ] at hx; + rw [ ← neg_sub, Matrix.det_neg ] + simp_all only [isUnit_iff_ne_zero, ne_eq, Decidable.not_not, Fintype.card_prod, + mul_eq_zero, pow_eq_zero_iff', neg_eq_zero, one_ne_zero, not_or, false_and, false_or] + convert hx using 1; + congr! 1; + ext ⟨ i, j ⟩ ⟨ i', j' ⟩; + simp [ Algebra.smul_def ] + -- Since $A$ and $B$ are Hermitian, they are diagonalizable. Let $P$ and $Q$ be unitary + -- matrices such that $P^*AP$ and $Q^*BQ$ are diagonal. + obtain ⟨P, hP₁, ⟨D, hD⟩⟩ : ∃ P : Matrix d d 𝕜, P.det ≠ 0 ∧ ∃ D : Matrix d d 𝕜, D.IsDiag ∧ P⁻¹ + * A * P = D := by + refine ⟨ hA.eigenvectorUnitary, ?_, Matrix.diagonal ( RCLike.ofReal ∘ hA.eigenvalues ), ?_, + ?_ ⟩; + · intro h_det_zero; + exact absurd h_det_zero <| isUnit_iff_ne_zero.mp <| UnitaryGroup.det_isUnit + hA.eigenvectorUnitary + · exact isDiag_diagonal (RCLike.ofReal ∘ hA.eigenvalues); + · -- Since $U$ is unitary, $U⁻¹ = U*$, and thus $U⁻¹ * U = I$. + have h_unitary : (hA.eigenvectorUnitary : Matrix d d 𝕜)⁻¹ = star (hA.eigenvectorUnitary : + Matrix d d 𝕜) := by + rw [ Matrix.inv_eq_left_inv ]; + simp + -- Substitute h_unitary into the equation. + rw [h_unitary]; + convert Matrix.IsHermitian.conjStarAlgAut_star_eigenvectorUnitary hA using 1 + simp + obtain ⟨Q, hQ₁, ⟨E, hE⟩⟩ : ∃ Q : Matrix d₂ d₂ 𝕜, Q.det ≠ 0 ∧ ∃ E : Matrix d₂ d₂ 𝕜, E.IsDiag ∧ + Q⁻¹ * B * Q = E := by + have := Matrix.IsHermitian.spectral_theorem hB; + -- By the spectral theorem, since B is Hermitian, there exists a unitary matrix Q and a + -- diagonal matrix D such that B = Q * D * Q⁻¹. + obtain ⟨Q, hQ_unitary, D, hD_diag, hQ⟩ : ∃ Q : Matrix d₂ d₂ 𝕜, Q.det ≠ 0 ∧ ∃ D : Matrix d₂ + d₂ 𝕜, D.IsDiag ∧ B = Q * D * Q⁻¹ := by + refine ⟨ hB.eigenvectorUnitary, ?_, Matrix.diagonal ( RCLike.ofReal ∘ hB.eigenvalues ), + ?_, ?_ ⟩; + · intro h_det_zero; + -- Since the eigenvector unitary matrix is unitary, its determinant is non-zero. + have h_unitary_det : ∀ (U : Matrix d₂ d₂ 𝕜), U * star U = 1 → U.det ≠ 0 := + fun U hU => Matrix.det_ne_zero_of_right_inverse hU; + exact h_unitary_det _ ( by simp) h_det_zero; + · exact isDiag_diagonal (RCLike.ofReal ∘ hB.eigenvalues); + · convert this using 1; + rw [ Matrix.inv_eq_left_inv ]; + · simp + rfl + · simp only [SetLike.coe_mem, Unitary.star_mul_self_of_mem] + refine ⟨ Q, hQ_unitary, D, hD_diag, ?_ ⟩ + simp [ hQ, mul_assoc, hQ_unitary, isUnit_iff_ne_zero ]; + -- Then $(P \otimes Q)^{-1}(A \otimes B)(P \otimes Q) = D \otimes E$, where $D$ and $E$ are + -- diagonal matrices. + have h_diag : (P.kronecker Q)⁻¹ * (A ⊗ₖ B) * (P.kronecker Q) = D ⊗ₖ E := by + -- Using the properties of the Kronecker product and the fact that $P$ and $Q$ are + -- invertible, we can simplify the expression. + have h_kronecker : (P.kronecker Q)⁻¹ * (A.kronecker B) * (P.kronecker Q) = (P⁻¹ * A * + P).kronecker (Q⁻¹ * B * Q) := by + have h_kronecker : ∀ (X Y : Matrix d d 𝕜) (Z W : Matrix d₂ d₂ 𝕜), (X.kronecker Z) * + (Y.kronecker W) = (X * Y).kronecker (Z * W) := by + intro X Y Z W; ext i j; simp? [ Matrix.mul_apply ]; + simp only [mul_left_comm, mul_comm, Finset.mul_sum _ _ _]; + exact Fintype.sum_prod_type_right _ + rw [Matrix.inv_eq_right_inv, h_kronecker, h_kronecker]; + convert h_kronecker P P⁻¹ Q Q⁻¹ using 1; + simp [ hP₁, hQ₁, isUnit_iff_ne_zero ]; + aesop; + -- Since $D$ and $E$ are diagonal matrices, the determinant of $(D \otimes E - xI)$ is the + -- product of the determinants of $(D - xI)$ and $(E - xI)$. + have h_det_diag : Matrix.det (D ⊗ₖ E - x • 1) = 0 := by + have h_det_diag : Matrix.det ((P.kronecker Q)⁻¹ * (A ⊗ₖ B - x • 1) * (P.kronecker Q)) = + Matrix.det (D ⊗ₖ E - x • 1) := by + simp [ ← h_diag, mul_sub, sub_mul ]; + simp [ Matrix.det_kronecker, hP₁, hQ₁ ]; + simp_all [ Matrix.det_mul ]; + -- Since $D$ and $E$ are diagonal matrices, the determinant of $(D \otimes E - xI)$ is the + -- product of the determinants of $(D - xI)$ and $(E - xI)$. Therefore, there must be some + -- $i$ and $j$ such that $D_{ii} * E_{jj} = x$. + obtain ⟨i, j, hij⟩ : ∃ i : d, ∃ j : d₂, D i i * E j j = x := by + contrapose! h_det_diag; + have h_det_diag : Matrix.det (D ⊗ₖ E - x • 1) = ∏ i : d, ∏ j : d₂, (D i i * E j j - x) := by + have h_det_diag : Matrix.det (D ⊗ₖ E - x • 1) = Matrix.det (Matrix.diagonal (fun p : d × + d₂ => D p.1 p.1 * E p.2 p.2 - x)) := by + congr with p q + simp_all only [ne_eq, kronecker, sub_apply, + kroneckerMap_apply, smul_apply, smul_eq_mul] + obtain ⟨fst, snd⟩ := p + obtain ⟨fst_1, snd_1⟩ := q + obtain ⟨left, rfl⟩ := hD + obtain ⟨left_1, rfl⟩ := hE + simp_all only + by_cases h : fst = fst_1 <;> by_cases h' : snd = snd_1 <;> simp? [ h, h', + Matrix.one_apply ]; + · exact Or.inr ( left_1 ( by aesop ) ); + · exact Or.inl ( left h ); + · exact Or.inl ( left h ); + simp_all? [ Matrix.det_diagonal ]; + exact Fintype.prod_prod_type fun (x_2 : d × d₂) => D x_2.1 x_2.1 * E x_2.2 x_2.2 - x + exact h_det_diag.symm ▸ Finset.prod_ne_zero_iff.mpr fun i _ => Finset.prod_ne_zero_iff.mpr + fun j _ => sub_ne_zero_of_ne <| by solve_by_elim; + refine ⟨ D i i, ?_, E j j, ?_, ?_ ⟩ + · simp_all? [ spectrum.mem_iff ]; + simp_all? [ Matrix.isUnit_iff_isUnit_det ]; + have h_det_diag : Matrix.det (P⁻¹ * (D i i • 1 - A) * P) = 0 := by + simp_all? [ mul_sub, sub_mul, mul_assoc ]; + rw [ Matrix.det_eq_zero_of_row_eq_zero i ] + intro j_1 + subst hij + simp_all only [map_mul, sub_apply, smul_apply, smul_eq_mul] + obtain ⟨left, rfl⟩ := hD + obtain ⟨left_1, rfl⟩ := hE + by_cases hij : i = j_1 <;> simp_all? [ Matrix.one_apply ]; + exact left hij; + simp_all? [ Matrix.det_mul]; + convert h_det_diag using 1; + exact congr_arg Matrix.det ( by ext i j; by_cases hi : i = j <;> simp [ hi, Algebra.smul_def + ] ); + · simp_all? [ spectrum.mem_iff ]; + -- Since $E$ is diagonal, $E j j - B$ is singular, hence not invertible. + have h_singular : Matrix.det (E j j • 1 - B) = 0 := by + have h_singular : Matrix.det (Q⁻¹ * (E j j • 1 - B) * Q) = 0 := by + simp? [ mul_sub, sub_mul, hE.2 ]; + rw [ Matrix.det_eq_zero_of_row_eq_zero j ] + intro j_1 + subst hij + simp_all only [map_mul, isUnit_iff_ne_zero, ne_eq, not_false_eq_true, nonsing_inv_mul, + sub_apply, + smul_apply, smul_eq_mul] + obtain ⟨left, rfl⟩ := hD + obtain ⟨left_1, rfl⟩ := hE + by_cases h : j = j_1 <;> aesop; + simp_all [ Matrix.det_mul ]; + simp_all? [ Matrix.isUnit_iff_isUnit_det ]; + convert h_singular using 1; + simp [ Algebra.smul_def ]; + · simp_all [ spectrum.mem_iff ]; + +private lemma spectrum_prod_le {d d₂ : Type*} + [Fintype d] [DecidableEq d] [Fintype d₂] [DecidableEq d₂] + {A : Matrix d d 𝕜} {B : Matrix d₂ d₂ 𝕜} + (hA : A.IsHermitian) (hB : B.IsHermitian) : + spectrum ℝ (A ⊗ₖ B) ⊆ spectrum ℝ A * spectrum ℝ B := by + intro x hx + suffices h : ∃ a ∈ spectrum ℝ A, ∃ b ∈ spectrum ℝ B, x = a * b by + rcases h with ⟨a, ha, b, hb, rfl⟩ + exact ⟨a, ha, b, hb, rfl⟩ + obtain ⟨_, ha, _, hb, h₁, ⟨a', rfl⟩, ⟨b', rfl⟩⟩ : ∃ a ∈ spectrum 𝕜 A, ∃ b ∈ spectrum 𝕜 B, + x = a * b ∧ + a ∈ Set.range (algebraMap ℝ 𝕜) ∧ b ∈ Set.range (algebraMap ℝ 𝕜) := by + obtain ⟨a, ha, b, hb, hx_eq⟩ := spectrum_prod_complex hA hB x (by exact hx); + have ha' := ha + have hb' := hb + rw [hA.spectrum_eq_image_range] at ha + rw [hB.spectrum_eq_image_range] at hb + grind + use a', ha, b', hb + simp only [RCLike.algebraMap_eq_ofReal] at h₁ + exact_mod_cast h₁ + +open Kronecker in +open scoped Pointwise in +theorem spectrum_prod {d d₂ : Type*} + [Fintype d] [DecidableEq d] [Fintype d₂] [DecidableEq d₂] + {A : Matrix d d 𝕜} {B : Matrix d₂ d₂ 𝕜} + (hA : A.IsHermitian) (hB : B.IsHermitian) : + spectrum ℝ (A ⊗ₖ B) = spectrum ℝ A * spectrum ℝ B := by + apply subset_antisymm + · exact spectrum_prod_le hA hB + · rintro x ⟨ y, hy, z, hz, rfl ⟩; + -- Since $y$ is an eigenvalue of $A$ and $z$ is an eigenvalue of $B$, there exist + -- eigenvectors $v$ and $w$ such that $A*v = y*v$ and $B*w = z*w$. + obtain ⟨v, hv⟩ : ∃ v : d → 𝕜, v ≠ 0 ∧ A.mulVec v = y • v := by + rw [ spectrum.mem_iff ] at hy; + simp_all? [ Matrix.isUnit_iff_isUnit_det ]; + have := Matrix.exists_mulVec_eq_zero_iff.mpr hy; + simp_all? [ funext_iff, Matrix.mulVec, dotProduct ]; + simp_all? [ sub_mul, Matrix.one_apply, Algebra.algebraMap_eq_smul_one ]; + exact ⟨ this.choose, this.choose_spec.1, fun x => by + linear_combination -this.choose_spec.2 x ⟩ + obtain ⟨w, hw⟩ : ∃ w : d₂ → 𝕜, w ≠ 0 ∧ B.mulVec w = z • w := by + rw [ spectrum.mem_iff ] at hz; + simp_all? [ Matrix.isUnit_iff_isUnit_det ]; + have := Matrix.exists_mulVec_eq_zero_iff.mpr hz; + simp_all? [ Matrix.sub_mulVec ]; + obtain ⟨ w, hw, hw' ⟩ := this; use w; simp_all? [ sub_eq_zero, + Algebra.algebraMap_eq_smul_one ]; + simp_all? [ funext_iff, Matrix.mulVec, dotProduct ]; + simp_all [ Matrix.one_apply]; + refine spectrum.mem_iff.mpr ?_; + -- Consider the vector $v \otimes w$. + set v_tensor_w : (d × d₂) → 𝕜 := fun p => v p.1 * w p.2; + -- We need to show that $v \otimes w$ is an eigenvector of $A \otimes B$ with eigenvalue $yz$. + have h_eigenvector : (Matrix.kroneckerMap (· * ·) A B).mulVec v_tensor_w = (y * z) • + v_tensor_w := by + ext ⟨ i, j ⟩; + simp? [ Matrix.mulVec, dotProduct] at * + simp? [ funext_iff, Matrix.mulVec, dotProduct ] at hv hw ⊢ + erw [ Finset.sum_product ] + simp_all only [v_tensor_w] + obtain ⟨left, right⟩ := hv + obtain ⟨left_1, right_1⟩ := hw + -- By separating the sums, we can apply the given equalities. + have h_separate : ∑ x, ∑ x_1, A i x * B j x_1 * (v x * w x_1) = (∑ x : d, A i x * v x) * + (∑ x_1 : d₂, B j x_1 * w x_1) := by + simp only [mul_left_comm, mul_comm, Finset.mul_sum _ _ _]; + exact Finset.sum_comm.trans ( Finset.sum_congr rfl fun _ _ => Finset.sum_congr rfl fun _ + _ => by ring ); + rw [ h_separate, right, right_1 ] + simp [RCLike.real_smul_eq_coe_mul] + ring_nf + -- Since $v \otimes w$ is an eigenvector of $A \otimes B$ with eigenvalue $yz$, we have + -- $(A \otimes B - yzI)(v \otimes w) = 0$. + have h_eigenvector_zero : ((A ⊗ₖ B) - (y * z) • 1) *ᵥ v_tensor_w = 0 := by + simp [ h_eigenvector, Matrix.sub_mulVec ]; + simp [ Matrix.mulVec, funext_iff ]; + simp [ Matrix.one_apply, dotProduct ]; + -- Since $v \otimes w$ is non-zero, we have $(A \otimes B - yzI)(v \otimes w) = 0$ + -- implies that $A \otimes B - yzI$ is not invertible. + have h_not_invertible : ¬IsUnit (A ⊗ₖ B - (y * z) • 1) := by + simp only [ne_eq, isUnit_iff_isUnit_det, isUnit_iff_ne_zero, Decidable.not_not, + v_tensor_w] at * + rw [ ← Matrix.exists_mulVec_eq_zero_iff ] + refine ⟨ v_tensor_w, ?_, h_eigenvector_zero ⟩; + simp? [ funext_iff ] at hv hw ⊢ + obtain ⟨left, right⟩ := hv + obtain ⟨left_1, right_1⟩ := hw + exact ⟨left.choose, left_1.choose, mul_ne_zero left.choose_spec left_1.choose_spec⟩ + rw [← IsUnit.neg_iff, neg_sub] + convert h_not_invertible using 4 + simp [ Algebra.smul_def ] + +end spectrum_kron + +open ComplexOrder in +open MatrixOrder in +theorem PosDef.zero_lt {n : Type*} [Nonempty n] {A : Matrix n n ℂ} (hA : A.PosDef) : 0 < A := by + apply lt_of_le_of_ne + · replace hA := hA.posSemidef + rwa [Matrix.nonneg_iff_posSemidef] + · rintro rfl; exact absurd (hA.diag_pos (i := Classical.arbitrary n)) (by simp) + + +lemma IsHermitian.spectrum_eq_image_eigenvalues [Fintype n] {A : Matrix n n ℂ} (hA : + A.IsHermitian) : + spectrum ℝ A = Finset.univ.image hA.eigenvalues := by + simpa using hA.spectrum_real_eq_range_eigenvalues + +/- This lemma looks "wrong" in the sense that it's specifically about `Fintype.card foo = +Finset.card bar`, +why not just use the underlying fact `foo = ↑bar`? It turns out this actually gives annoying issues +with dependent rewrites, given the necessary `Fintype` instance. Using the above theorem for + example, +trying `rw [hA.spectrum_eq_image_eigenvalues]` fails because of dependent types. -/ +lemma IsHermitian.card_spectrum_eq_image [Fintype n] {A : Matrix n n ℂ} (hA : A.IsHermitian) + [Fintype (spectrum ℝ A)] : + Fintype.card (spectrum ℝ A) = (Finset.univ.image hA.eigenvalues).card := by + trans (Set.univ.image hA.eigenvalues).toFinset.card + · symm + convert Set.toFinset_card _ + rw [Set.image_univ] + exact Matrix.IsHermitian.spectrum_real_eq_range_eigenvalues hA + · simp + +section iInf_iSup +namespace IsHermitian + +variable {d : Type*} [Fintype d] [DecidableEq d] {A B : Matrix d d ℂ} + +open ComplexOrder + +lemma sub_iInf_eignevalues (hA : A.IsHermitian) : + (A - iInf hA.eigenvalues • 1).PosSemidef := by + rw [Matrix.posSemidef_iff_dotProduct_mulVec] + constructor + · simpa [ Matrix.IsHermitian, sub_eq_add_neg ] using hA + · intro x + have h_eigenvalue : ∀ i, hA.eigenvalues i ≥ iInf hA.eigenvalues := by + -- By definition of infimum, for any eigenvalue $i$, we have $hA.eigenvalues i \geq + -- iInf hA.eigenvalues$. + intros i + apply le_of_forall_le + intro j a + exact le_trans a (ciInf_le ( Finite.bddBelow_range hA.eigenvalues ) i ); + -- Since $A$ is Hermitian, we can diagonalize it as $A = Q \Lambda Q^*$, where $Q$ is + -- unitary and $\Lambda$ is diagonal with the eigenvalues on the diagonal. + obtain ⟨Q, Λ, hQ, hΛ⟩ : ∃ Q : Matrix d d ℂ, ∃ Λ : d → ℂ, Q.conjTranspose * Q = 1 ∧ A = Q * + Matrix.diagonal Λ * Q.conjTranspose ∧ ∀ i, Λ i = Matrix.IsHermitian.eigenvalues hA i := by + refine ⟨hA.eigenvectorUnitary, fun i => (hA.eigenvalues i : ℂ), ?_, + hA.spectral_theorem, fun _ => rfl⟩ + exact Matrix.UnitaryGroup.star_mul_self hA.eigenvectorUnitary + -- Since $Q$ is unitary, we have $Q^* Q = I$, and thus $Q^* (A - \lambda_{\min} I) Q = + -- \Lambda - \lambda_{\min} I$. + have h_diag : Q.conjTranspose * (A - (iInf (Matrix.IsHermitian.eigenvalues hA)) • 1) * Q = + Matrix.diagonal (fun i => Λ i - (iInf (Matrix.IsHermitian.eigenvalues hA))) := by + simp? [ hΛ, mul_sub, sub_mul, mul_assoc, hQ ]; + simp? [ ← mul_assoc, hQ]; + ext i j; by_cases hij : i = j <;> aesop; + -- Since $Q$ is unitary, we have $Q^* (A - \lambda_{\min} I) Q = \Lambda - + -- \lambda_{\min} I$, and thus $x^* (A - \lambda_{\min} I) x = (Q^* x)^* (\Lambda - + -- \lambda_{\min} I) (Q^* x)$. + have h_quad_form : Star.star x ⬝ᵥ (A - (iInf (Matrix.IsHermitian.eigenvalues hA)) • + 1).mulVec x = Star.star (Q.conjTranspose.mulVec x) ⬝ᵥ (Matrix.diagonal (fun i => Λ i - + (iInf (Matrix.IsHermitian.eigenvalues hA)))).mulVec (Q.conjTranspose.mulVec x) := by + rw [ ← h_diag ]; + simp? [ Matrix.mul_assoc, Matrix.dotProduct_mulVec, mul_eq_one_comm.mp hQ]; + simp only [mulVec_conjTranspose, star_star, vecMul_vecMul]; + rw [ ← Matrix.mul_assoc, mul_eq_one_comm.mp hQ, one_mul ]; + simp_all only [ge_iff_le, dotProduct, Pi.star_apply, RCLike.star_def, mulVec, sub_apply, + smul_apply, Complex.real_smul, conjTranspose_apply, star_sum, star_mul', + RingHomCompTriple.comp_apply, RingHom.id_apply]; + simp_all only [implies_true, and_self, diagonal_apply, ite_mul, zero_mul, Finset.sum_ite_eq, + ↓reduceIte]; + -- Since the eigenvalues are real and the sums involving Q and x are complex, the + -- product of a complex number and its conjugate is non-negative. + have h_nonneg : ∀ i, 0 ≤ (∑ x_2, Q x_2 i * star (x x_2)) * (∑ x_2, star (Q x_2 i) * x x_2) := by + intro i + have h_nonneg : 0 ≤ (∑ x_2, Q x_2 i * star (x x_2)) * star (∑ x_2, Q x_2 i * star (x x_2)) + := by + exact mul_star_self_nonneg (∑ x_2, Q x_2 i * star (x x_2)) + convert h_nonneg using 1; + simp [ mul_comm, Finset.mul_sum _ _ _]; + -- Since each term in the sum is a product of a non-negative number and a non-negative + -- eigenvalue difference, the entire sum is non-negative. + have h_sum_nonneg : ∀ i, 0 ≤ (∑ x_2, Q x_2 i * star (x x_2)) * (((↑(hA.eigenvalues i) : ℂ) - + (↑(iInf hA.eigenvalues) : ℂ)) * ∑ x_2, star (Q x_2 i) * x x_2) := by + intro i + specialize h_nonneg i + simp_all only [mul_assoc, mul_comm, mul_left_comm, RCLike.star_def]; + rw [ ← mul_assoc ]; + exact mul_nonneg h_nonneg ( sub_nonneg_of_le <| mod_cast h_eigenvalue i ); + convert! Finset.sum_nonneg fun i _ => h_sum_nonneg i; + rw [ hΛ.1 ] + +lemma iInf_eigenvalues_le_dotProduct_mulVec (hA : A.IsHermitian) (v : d → ℂ) : + iInf hA.eigenvalues * (star v ⬝ᵥ v) ≤ star v ⬝ᵥ A *ᵥ v := by + conv_lhs => + equals (star v ⬝ᵥ (iInf hA.eigenvalues • 1) *ᵥ v) => + simp only [dotProduct, Pi.star_apply, RCLike.star_def, mul_comm, mulVec] + simp [Matrix.one_apply, mul_assoc, mul_left_comm, Finset.mul_sum] + rw [← sub_nonneg, ← dotProduct_sub, ← Matrix.sub_mulVec] + replace hA := sub_iInf_eignevalues hA + rw [Matrix.posSemidef_iff_dotProduct_mulVec] at hA + exact hA.right v + +lemma iInf_eigenvalues_le_of_posSemidef + (hAB : (B - A).PosSemidef) (hA : A.IsHermitian) (hB : B.IsHermitian) : + iInf hA.eigenvalues ≤ iInf hB.eigenvalues := by + rcases isEmpty_or_nonempty d + · simp + contrapose! hAB + rw [posSemidef_iff_dotProduct_mulVec] + simp only [not_and, not_forall] + intro _ + apply exists_lt_of_ciInf_lt at hAB + rcases hAB with ⟨i, hi⟩ + use WithLp.ofLp (hB.eigenvectorBasis i) + simp only [sub_mulVec, dotProduct_sub, sub_nonneg] + rw [hB.mulVec_eigenvectorBasis i] + simp only [dotProduct_smul, Complex.real_smul] + nth_rw 2 [dotProduct_comm] + rw [← EuclideanSpace.inner_eq_star_dotProduct] + intro h + replace h := (iInf_eigenvalues_le_dotProduct_mulVec hA _).trans h + rw [dotProduct_comm, ← EuclideanSpace.inner_eq_star_dotProduct] at h + simp only [OrthonormalBasis.inner_eq_one, mul_one, Complex.real_le_real] at h + order + +open MatrixOrder in +lemma iInf_eigenvalues_le (hAB : A ≤ B) (hA : A.IsHermitian) (hB : B.IsHermitian) : + iInf hA.eigenvalues ≤ iInf hB.eigenvalues := + iInf_eigenvalues_le_of_posSemidef hAB hA hB + +open MatrixOrder in +lemma iInf_eigenvalues_smul_one_le (hA : A.IsHermitian) : iInf hA.eigenvalues • 1 ≤ A := + (PosSemidef.smul_one_le_of_eigenvalues_iff hA (iInf hA.eigenvalues)).mp + (ciInf_le (Finite.bddBelow_range _)) + +end IsHermitian +end iInf_iSup + +section matrix_order + +--Shortcut instances. Having these around speeds things out considerably, in some cases? +open MatrixOrder + +variable {d : Type*} [Fintype d] + +omit [Fintype d] in +lemma _shortcut_posSMulMono [Finite d] : PosSMulMono ℝ (Matrix d d ℂ) := by + classical + let := Fintype.ofFinite d + exact + inferInstance + +omit [Fintype d] in +lemma _shortcut_posSmulReflectLE [Finite d] : PosSMulReflectLE ℝ (Matrix d d ℂ) := by + classical + let := Fintype.ofFinite d + exact + inferInstance + +scoped[MatrixOrder] attribute [instance] Matrix._shortcut_posSMulMono +scoped[MatrixOrder] attribute [instance] Matrix._shortcut_posSmulReflectLE + +end matrix_order + +open ComplexOrder in +theorem IsHermitian.spectrum_subset_Ici_of_sub {d 𝕜 : Type*} [Fintype d] [DecidableEq d] [RCLike 𝕜] + {A x : Matrix d d 𝕜} (hA : A.IsHermitian) (hl : (x - A).PosSemidef) : + spectrum ℝ x ⊆ Set.Ici (⨅ i, hA.eigenvalues i) := by + --Thanks Aristotle + intro μ hμ + obtain ⟨v, hv₁, hv₂⟩ : ∃ v : d → 𝕜, v ≠ 0 ∧ x.mulVec v = μ • v := by + have h_singular : ∃ v : d → 𝕜, v ≠ 0 ∧ (μ • 1 - x).mulVec v = 0 := by + simp only [spectrum.mem_iff, Matrix.isUnit_iff_isUnit_det, isUnit_iff_ne_zero, ne_eq, + Decidable.not_not] at hμ + convert Matrix.exists_mulVec_eq_zero_iff.mpr hμ; + simp [Algebra.smul_def] + refine h_singular.imp fun v h ↦ ⟨h.left, ?_⟩ + simp_all [Matrix.sub_mulVec, sub_eq_iff_eq_add, funext_iff, Matrix.mulVec, dotProduct, + Matrix.one_apply] + -- Since $x - A$ is positive semidefinite, for any eigenvalue $\lambda$ of $x$, we have + -- $\lambda \geq \min(\text{eigenvalues of } A)$. + have h_lower_bound : ∀ (v : d → 𝕜), v ≠ 0 → (star v ⬝ᵥ (x.mulVec v)) ≥ (⨅ i, (hA.eigenvalues + i)) * (star v ⬝ᵥ v) := by + intro v hv_nonzero + have h_eigenvalue : (star v ⬝ᵥ (A.mulVec v)) ≥ (⨅ i, (hA.eigenvalues i)) * (star v ⬝ᵥ v) := by + have h_expand : (star v ⬝ᵥ (A.mulVec v)) = ∑ i, (hA.eigenvalues i) * (star + (hA.eigenvectorBasis i) ⬝ᵥ v) * (star v ⬝ᵥ (hA.eigenvectorBasis i)) := by + have h_decomp : A = ∑ i, (hA.eigenvalues i) • (Matrix.of (fun j k => + (hA.eigenvectorBasis i j) * (star (hA.eigenvectorBasis i k)))) := by + convert Matrix.IsHermitian.spectral_theorem hA using 1; + ext i j + simp only [RCLike.star_def, Matrix.smul_of, Matrix.sum_apply, Matrix.of_apply, + Pi.smul_apply, Matrix.diagonal, Function.comp_apply, Matrix.mul_apply, + Matrix.IsHermitian.eigenvectorUnitary_apply, mul_ite, mul_zero, + Finset.sum_ite_eq', Finset.mem_univ, ↓reduceIte, Matrix.star_apply, + Unitary.conjStarAlgAut_apply] + simp? [ mul_comm, mul_left_comm, Algebra.smul_def ] + congr! 1 + simp [Algebra.algebraMap_eq_smul_one] + -- Substitute the decomposition of $A$ into the expression $(star v ⬝ᵥ (A.mulVec v))$. + have h_subst : (star v ⬝ᵥ (A.mulVec v)) = ∑ i, (hA.eigenvalues i) * (star v ⬝ᵥ + (Matrix.mulVec (Matrix.of (fun j k => (hA.eigenvectorBasis i j) * (star + (hA.eigenvectorBasis i k)))) v)) := by + -- Substitute the decomposition of $A$ into the expression $(star v ⬝ᵥ + -- (A.mulVec v))$ and use the linearity of matrix multiplication. + have h_subst : (star v ⬝ᵥ (A.mulVec v)) = (star v ⬝ᵥ ((∑ i, (hA.eigenvalues i) • + (Matrix.of (fun j k => (hA.eigenvectorBasis i j) * (star (hA.eigenvectorBasis i + k))))).mulVec v)) := by + rw [ ← h_decomp ]; + -- By the linearity of matrix multiplication and the dot product, we can + -- distribute the sum over the dot product. + have h_distribute : (star v ⬝ᵥ (∑ i, (hA.eigenvalues i) • (Matrix.of (fun j k => + (hA.eigenvectorBasis i j) * (star (hA.eigenvectorBasis i k))))).mulVec v) = ∑ i, + (star v ⬝ᵥ ((hA.eigenvalues i) • (Matrix.of (fun j k => (hA.eigenvectorBasis i j) + * (star (hA.eigenvectorBasis i k))))).mulVec v) := by + -- By the linearity of matrix multiplication and the dot product, we can + -- distribute the sum over the dot product. This follows from the fact + -- that matrix multiplication is linear. + have h_distribute : ∀ (M N : Matrix d d 𝕜) (v : d → 𝕜), Star.star v ⬝ᵥ (M + + N).mulVec v = Star.star v ⬝ᵥ M.mulVec v + Star.star v ⬝ᵥ N.mulVec v := by + simp [ Matrix.add_mulVec, dotProduct_add ]; + -- By induction on the number of terms in the sum, we can apply the + -- distributive property repeatedly. + have h_induction : ∀ (n : ℕ) (M : Fin n → Matrix d d 𝕜) (v : d → 𝕜), Star.star v ⬝ᵥ + (∑ i, M i).mulVec v = ∑ i, Star.star v ⬝ᵥ (M i).mulVec v := by + intro n M v + induction n + · simp [*] + · simp [Fin.sum_univ_succ, *] + convert h_induction ( Fintype.card d ) ( fun i => Matrix.of ( hA.eigenvalues ( + Fintype.equivFin d |>.symm i ) • fun j k => hA.eigenvectorBasis ( + Fintype.equivFin d |>.symm i ) j * starRingEnd 𝕜 ( hA.eigenvectorBasis ( + Fintype.equivFin d |>.symm i ) k ) ) ) v using 1; + · rw [ ← Equiv.sum_comp ( Fintype.equivFin d ) ]; + simp [ Fintype.equivFin ]; + · rw [ ← Equiv.sum_comp ( Fintype.equivFin d ) ]; + simp [ Fintype.equivFin ]; + convert h_distribute using 1; + simp only [dotProduct, Pi.star_apply, RCLike.star_def, Matrix.mulVec, Matrix.of_apply, + Finset.mul_sum _ _ _, Matrix.smul_apply, Algebra.smul_mul_assoc, + Algebra.mul_smul_comm]; + simp [ Algebra.smul_def ]; + convert h_subst using 2; + simp only [dotProduct, Pi.star_apply, RCLike.star_def, mul_comm, mul_assoc, Matrix.mulVec, + Matrix.of_apply, mul_eq_mul_left_iff, map_eq_zero]; + simp [ mul_comm, mul_left_comm, Finset.mul_sum _ _ _ ]; + -- Since $\lambda_i \geq \inf(\text{eigenvalues of } A)$ for all $i$, we can bound + -- each term in the sum. + have h_bound : ∀ i, (hA.eigenvalues i) * (star (hA.eigenvectorBasis i) ⬝ᵥ v) * (star v ⬝ᵥ + (hA.eigenvectorBasis i)) ≥ (⨅ i, (hA.eigenvalues i)) * (star (hA.eigenvectorBasis i) + ⬝ᵥ v) * (star v ⬝ᵥ (hA.eigenvectorBasis i)) := by + intro i + have h_eigenvalue_bound : (hA.eigenvalues i) ≥ (⨅ i, (hA.eigenvalues i)) := + ciInf_le (Set.finite_range _).bddBelow _ + -- Since the product of the inner products is real and non-negative, multiplying + -- both sides of the inequality by this product preserves the inequality. + have h_nonneg : 0 ≤ (star (hA.eigenvectorBasis i) ⬝ᵥ v) * (star v ⬝ᵥ + (hA.eigenvectorBasis i)) := by + -- Since the inner product is conjugate symmetric, we have star v ⬝ᵥ + -- (hA.eigenvectorBasis i) = conjugate(star (hA.eigenvectorBasis i) ⬝ᵥ v). + have h_conj_symm : star v ⬝ᵥ (hA.eigenvectorBasis i) = star (star (hA.eigenvectorBasis + i) ⬝ᵥ v) := by + simp [ dotProduct, mul_comm]; + rw [ h_conj_symm ]; + exact mul_star_self_nonneg (star (hA.eigenvectorBasis i) ⬝ᵥ v); + norm_num [ mul_assoc ]; + exact mul_le_mul_of_nonneg_right ( mod_cast h_eigenvalue_bound ) h_nonneg; + -- Since $\sum_{i} (star (hA.eigenvectorBasis i) ⬝ᵥ v) * (star v ⬝ᵥ + -- (hA.eigenvectorBasis i)) = star v ⬝ᵥ v$, we can factor out $(⨅ i, (hA.eigenvalues + -- i))$ from the sum. + have h_sum : ∑ i, (star (hA.eigenvectorBasis i) ⬝ᵥ v) * (star v ⬝ᵥ (hA.eigenvectorBasis + i)) = star v ⬝ᵥ v := by + have h_sum : ∑ i, (star (hA.eigenvectorBasis i) ⬝ᵥ v) • (hA.eigenvectorBasis i) = v := by + have := hA.eigenvectorBasis.sum_repr (WithLp.toLp 2 v); + convert this using 1; + simp only [dotProduct, Pi.star_apply, RCLike.star_def, mul_comm, + hA.eigenvectorBasis.repr_apply_apply, PiLp.inner_apply, RCLike.inner_apply]; + simp only [WithLp.ofLp_sum, WithLp.ofLp_smul] + have key : ∀ (c : d → 𝕜) (f : d → EuclideanSpace 𝕜 d) (w : d → 𝕜), + (∑ x, c x • (f x).ofLp = w) ↔ (∑ x, c x • f x = WithLp.toLp 2 w) := by + intro c f w + conv_lhs => rw [show ∑ x, c x • (f x).ofLp = (∑ x, c x • f x).ofLp from by + rw [WithLp.ofLp_sum]; simp [WithLp.ofLp_smul]] + constructor + · intro h; apply_fun WithLp.toLp 2 at h; simpa using h + · intro h; apply_fun WithLp.ofLp at h; simpa using h + exact key _ _ _ + -- Taking the inner product of both sides of h_sum with star v, we get the desired equality. + have h_inner : star v ⬝ᵥ (∑ i, (star (hA.eigenvectorBasis i) ⬝ᵥ v) • + (hA.eigenvectorBasis i)) = star v ⬝ᵥ v := by + congr 1 + simp_rw [← WithLp.ofLp_smul, ← WithLp.ofLp_sum, h_sum] + convert h_inner using 1; + simp? [ dotProduct, Finset.mul_sum _ _ _ ]; + exact Finset.sum_comm.trans ( Finset.sum_congr rfl fun _ _ => Finset.sum_congr rfl fun _ + _ => by ring ); + rw [ h_expand ]; + refine le_trans ?_ ( Finset.sum_le_sum fun i _ => h_bound i ); + simp only [ mul_assoc]; + rw [ ← Finset.mul_sum _ _ _, h_sum ]; + rw [Matrix.posSemidef_iff_dotProduct_mulVec] at hl + have := hl.2 v + simp? [Matrix.sub_mulVec] at this + exact le_trans h_eigenvalue this; + change (⨅ i, hA.eigenvalues i) ≤ μ + have := h_lower_bound v hv₁ + simp_all only [ne_eq, star, RCLike.star_def, Matrix.dotProduct_mulVec, ge_iff_le, + dotProduct_smul]; + simp_all only [dotProduct, mul_comm, RCLike.mul_conj]; + rw [ Algebra.smul_def ] at this; + -- Since the sum of the squares of the norms of v is positive, we can divide both sides of + -- the inequality by it. + have h_sum_pos : 0 < ∑ x : d, (‖v x‖ : ℝ) ^ 2 := by + contrapose! hv₁; + simp_all only [funext_iff, Pi.zero_apply, not_forall, forall_exists_index, Matrix.mulVec, + Pi.smul_apply] + intro i + rw [← norm_eq_zero] + simpa [ sq_nonneg ] using le_antisymm ( le_trans ( Finset.single_le_sum ( fun a _ => + sq_nonneg ( ‖v a‖ ) ) ( Finset.mem_univ i ) ) hv₁ ) ( sq_nonneg ( ‖v i‖ ) ) + norm_cast at this; + nlinarith + +open ComplexOrder in +theorem IsHermitian.spectrum_subset_Iic_of_sub {d 𝕜 : Type*} [Fintype d] [DecidableEq d] [RCLike 𝕜] + {A x : Matrix d d 𝕜} (hA : A.IsHermitian) (hl : (A - x).PosSemidef) : + spectrum ℝ x ⊆ Set.Iic (⨆ i, hA.eigenvalues i) := by + have h := spectrum_subset_Ici_of_sub hA.neg (x := -x) ?_ + · rcases isEmpty_or_nonempty d + · simp + rw [← spectrum.neg_eq] at h + intro μ hμ + specialize h (Set.neg_mem_neg.mpr hμ) + rw [← Set.mem_neg, Set.neg_Ici] at h + convert! h + rw [iInf, iSup, ← spectrum_real_eq_range_eigenvalues, ← spectrum_real_eq_range_eigenvalues] + rw [← spectrum.neg_eq, csInf_neg ?_ (A.finite_real_spectrum.bddAbove), neg_neg] + exact ContinuousFunctionalCalculus.spectrum_nonempty _ hA + · convert hl using 1 + abel + +open ComplexOrder in +theorem IsHermitian.spectrum_subset_of_mem_Icc {d 𝕜 : Type*} [Fintype d] [DecidableEq d] [RCLike 𝕜] + {A B x : Matrix d d 𝕜} (hA : A.IsHermitian) (hB : B.IsHermitian) + (hl : (x - A).PosSemidef) (hr : (B - x).PosSemidef) : + spectrum ℝ x ⊆ Set.Icc (⨅ i, hA.eigenvalues i) (⨆ i, hB.eigenvalues i) := by + rw [← Set.Ici_inter_Iic] + exact Set.subset_inter (hA.spectrum_subset_Ici_of_sub hl) (hB.spectrum_subset_Iic_of_sub hr) + +/-- +The right partial trace of a matrix is equal to the left partial trace of the matrix reindexed + by swapping the tensor factors. +-/ +theorem traceRight_eq_traceLeft_reindex {n m R : Type*} [Fintype m] [AddCommMonoid R] + (M : Matrix (n × m) (n × m) R) : + M.traceRight = (M.reindex (.prodComm ..) (.prodComm ..)).traceLeft := by + rfl + +open ComplexOrder in +theorem PosSemidef.trace_pos {n 𝕜 : Type*} [Fintype n] [RCLike 𝕜] + {A : Matrix n n 𝕜} (hA : A.PosSemidef) (h : A ≠ 0) : 0 < A.trace := by + apply hA.trace_nonneg.lt_of_ne' + classical + rw [hA.left.trace_eq_sum_eigenvalues] + suffices ∑ i, hA.left.eigenvalues i ≠ 0 from mod_cast this + rwa [ne_eq, Fintype.sum_eq_zero_iff_of_nonneg hA.eigenvalues_nonneg, + hA.left.eigenvalues_eq_zero_iff] + +section traceLeftRight + +variable {m α : Type*} [AddCommGroup α] [Fintype m] +omit [DecidableEq n] + +variable {A B : Matrix (m × n) (m × n) α} +@[simp] +theorem traceLeft_add : (A + B).traceLeft = A.traceLeft + B.traceLeft := by + ext : 2 + simp [Matrix.traceLeft, Finset.sum_add_distrib] + +@[simp] +theorem traceLeft_neg : (-A).traceLeft = -A.traceLeft := by + ext : 2; simp [Matrix.traceLeft] + +@[simp] +theorem traceLeft_sub : (A - B).traceLeft = A.traceLeft - B.traceLeft := by + simp [sub_eq_add_neg] + +variable {A B : Matrix (n × m) (n × m) α} + +@[simp] +theorem traceRight_add : (A + B).traceRight = A.traceRight + B.traceRight := by + ext : 2 + simp [Matrix.traceRight, Finset.sum_add_distrib] + +@[simp] +theorem traceRight_neg : (-A).traceRight = -A.traceRight := by + ext : 2; simp [Matrix.traceRight] + +@[simp] +theorem traceRight_sub : (A - B).traceRight = A.traceRight - B.traceRight := by + simp [sub_eq_add_neg] + +variable {R : Type*} [DistribSMul R α] +@[simp] +theorem traceLeft_smul {A : Matrix (m × n) (m × n) α} (r : R) : + (r • A).traceLeft = r • A.traceLeft := by + ext : 2; simp [Matrix.traceLeft, ← Finset.smul_sum] + +@[simp] +theorem traceRight_smul {A : Matrix (n × m) (n × m) α} (r : R) : + (r • A).traceRight = r • A.traceRight := by + ext : 2; simp [Matrix.traceRight, ← Finset.smul_sum] + +end traceLeftRight + +theorem unitaryGroup_row_norm [Fintype n] (U : Matrix.unitaryGroup n ℂ) (i : n) : + ∑ j, ‖U j i‖^2 = 1 := by + suffices ∑ j, ‖U j i‖^2 = (1 : ℂ) by exact_mod_cast this + simpa [Matrix.mul_apply, Complex.sq_norm, Complex.normSq_eq_conj_mul_self] + using congr($(U.prop.left) i i) + +section finprod + +variable {ι : Type*} {d : ι → Type*} [fι : Fintype ι] +variable {R : Type*} + +/-- The product of a family of matrices on the dependent product of their index types. -/ +def piProd [CommMonoid R] (A : ∀ i, Matrix (d i) (d i) R) : Matrix (∀ i, d i) (∀ i, d i) R := + Matrix.of (fun j k : (∀ i, d i) ↦ ∏ i, A i (j i) (k i)) + +variable {A : ∀ i, Matrix (d i) (d i) R} + +theorem IsHermitian.piProd [CommSemiring R] [StarRing R] (hA : ∀ i, (A i).IsHermitian) : + (piProd A).IsHermitian := by + ext j k + simp? [Matrix.piProd] + exact Finset.prod_congr rfl fun i _ => by + simpa using congr_fun (congr_fun (hA i) (j i)) (k i) + +variable [DecidableEq ι] [∀ i, Fintype (d i)] --[∀ i, DecidableEq (d i)] + +theorem trace_piProd [CommSemiring R] : + (piProd A).trace = ∏ i, (A i).trace := by + symm + simp [trace, piProd, Fintype.prod_sum] + +omit [DecidableEq ι] [∀ i, Fintype (d i)] in +open ComplexOrder MatrixOrder in +theorem PosSemidef.piProd [∀ i, Finite (d i)] [RCLike R] (hA : ∀ i, (A i).PosSemidef) : + (piProd A).PosSemidef := by + classical + let (i : ι) := Fintype.ofFinite (d i) + -- Let B i be the square root of A i. Let BigB be the pi-product of B i. Show that + -- BigB.conjTranspose * BigB equals the pi-product of A i using Fintype.prod_sum. Then use + -- Matrix.PosSemidef.conjTranspose_mul_self to conclude the proof. + obtain ⟨B, hB⟩ : ∃ B : ∀ i, Matrix (d i) (d i) R, ∀ i, (A i) = B i * star (B i) := by + -- By definition of positive semi-definite matrices, each $A_i$ can be written as $B_i^* + -- B_i$ for some matrix $B_i$. + have h_decomp : ∀ i, ∃ B : Matrix (d i) (d i) R, A i = B * star B := by + intro i + obtain ⟨B, hB⟩ : ∃ B : Matrix (d i) (d i) R, A i = B.conjTranspose * B := by + classical + apply CStarAlgebra.nonneg_iff_eq_star_mul_self.mp + exact nonneg_iff_posSemidef.mpr (hA i) + use B.conjTranspose; + convert hB using 1; + simp [ Matrix.star_eq_conjTranspose ]; + exact ⟨ fun i => Classical.choose ( h_decomp i ), fun i => Classical.choose_spec ( h_decomp + i ) ⟩; + have hBigB_conjTranspose_mul_BigB : Matrix.of (fun j k : (∀ i, d i) => ∏ i, (B i * star (B i)) + (j i) (k i)) = Matrix.of (fun j k : (∀ i, d i) => ∏ i, (B i) (j i) (k i)) * star + (Matrix.of (fun j k : (∀ i, d i) => ∏ i, (B i) (j i) (k i))) := by + ext j k; simp? [ Matrix.mul_apply] + simp only [Finset.prod_sum, ← Finset.prod_mul_distrib]; + refine Finset.sum_bij ( fun p hp => fun i => p i ( Finset.mem_univ i ) ) ?_ ?_ ?_ ?_ <;> + simp? +decide; + · simp [ funext_iff ]; + · exact fun b => ⟨ fun i _ => b i, rfl ⟩; + simp only [Matrix.posSemidef_iff_dotProduct_mulVec] at hA ⊢ + simp_all? [Matrix.piProd] + constructor + · ext1 + simp [Matrix.mul_apply, mul_comm] + · intro x + set y := star (Matrix.of (fun j k : (∀ i, d i) => ∏ i, B i (j i) (k i))) *ᵥ x + convert dotProduct_star_self_nonneg y using 1 + simp only [dotProduct_mulVec, y]; + simp only [dotProduct, vecMul, Pi.star_apply, RCLike.star_def, mul_comm, star_apply, of_apply, + star_prod]; + simp only [mul_apply, of_apply, star_apply, star_prod, RCLike.star_def, Finset.mul_sum _ _ _, + mul_left_comm, mulVec, dotProduct, mul_comm, map_sum, map_mul, map_prod, + RingHomCompTriple.comp_apply, RingHom.id_apply, mul_assoc]; + exact Finset.sum_congr rfl fun _ _ => Finset.sum_comm.trans ( Finset.sum_congr rfl fun _ _ + => Finset.sum_congr rfl fun _ _ => by ring ) + +end finprod + +--TODO: Can this be used for `Matrix.reindex_eq_conj` cleanup? +theorem submatrix_eq_mul_mul {d d₂ d₃ R : Type*} [DecidableEq d] [Fintype d] [Semiring R] + (A : Matrix d d R) (e : d₂ → d) (f : d₃ → d) : + A.submatrix e f = (submatrix (α := R) 1 e id : Matrix d₂ d R) * A * (submatrix (α := R) 1 id + f) := by + rw [show id = Equiv.refl d by rfl, Matrix.mul_submatrix_one, Matrix.one_submatrix_mul] + simp + +open scoped Matrix Kronecker in +/-- +The conjugate of a Kronecker product by a Kronecker product is the Kronecker product of the + conjugates (for matrices). +-/ +lemma kronecker_conj_eq {m n p q α : Type*} [CommSemiring α] [StarRing α] [Fintype m] [Fintype n] + (A : Matrix m m α) (B : Matrix n n α) (C : Matrix p m α) (D : Matrix q n α) : + (C ⊗ₖ D) * (A ⊗ₖ B) * (C ⊗ₖ D)ᴴ = (C * A * Cᴴ) ⊗ₖ (D * B * Dᴴ) := by + rw [← Matrix.mul_kronecker_mul] + ext1 + simp only [Matrix.mul_apply, Matrix.kroneckerMap_apply, Matrix.conjTranspose_apply, star_mul'] + simp only [← starRingEnd_apply, mul_comm, Finset.mul_sum, mul_left_comm] + simp only [Finset.sum_mul, mul_assoc, Finset.mul_sum, mul_left_comm] + rw [Fintype.sum_prod_type_right] + +end Matrix diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/Misc.lean b/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/Misc.lean new file mode 100644 index 0000000000..b933a50ed9 --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/Misc.lean @@ -0,0 +1,198 @@ +/- +Copyright (c) 2025 Alex Meiburg. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Alex Meiburg +-/ +module + +public import Mathlib.Analysis.SpecialFunctions.Log.Basic +public import Mathlib.Order.CompletePartialOrder + +/-! Auxiliary finite-dimensional linear algebra and functional calculus lemmas. -/ + +@[expose] public section + +--Can this be rewritten more generally? For `finiteness` to work, I don't know how. +--PR'ed in #33105 +@[aesop (rule_sets := [finiteness]) apply 50%] +theorem ite_eq_top {α : Type*} [Top α] (h : Prop) [Decidable h] {x y : α} (hx : x ≠ ⊤) (hy : y ≠ + ⊤) : + (if h then x else y) ≠ ⊤ := by + split <;> assumption + +section subtype_val_iSup + +/- +When +https://leanprover.zulipchat.com/#narrow/channel/287929-mathlib4/topic/diamond.20in.20ConditionallyCompleteLattice/near/538053239 +is fixed, the declarations below should be changed to +``` +theorem subtype_val_iSup {ι α : Type*} [ConditionallyCompleteLattice α] {s : Set α} {f : ι → α} + [Inhabited ↑s] [s.OrdConnected] (h : ∀ i, f i ∈ s) : + (⨆ i, (⟨f i, h i⟩ : ↑s)).val = ⨆ i, f i := by + sorry + +theorem subtype_val_iSup' {ι α : Type*} [ConditionallyCompleteLattice α] {s : Set α} {f : ι → α} + [Inhabited ↑s] [s.OrdConnected] (h : ∀ i, f i ∈ s) : + ⨆ i, (⟨f i, h i⟩ : ↑s) = ⟨⨆ i, f i, by sorry⟩ := by + rw [Subtype.eq_iff, subtype_val_iSup] +``` +Sadly, though, there's a "diamond" and we need it with the other data (the one we specify more +narrowly +below). +-/ +variable {ι α : Type*} [i : Nonempty ι] [ConditionallyCompleteLattice α] + {f : ι → α} {a b : α} [Fact (a ≤ b)] + +/- This isn't marked as `simp` because rewriting from a sup over a `CompleteLattice` into a +`ConditionallyCompleteLattice` would, pretty often, be undesirable. -/ +theorem subtype_val_iSup (h : ∀ i, f i ∈ Set.Icc a b) : + (⨆ i, (⟨f i, h i⟩ : ↑(Set.Icc a b))).val = ⨆ i, f i := by + simp only [iSup, sSup, Set.range_eq_empty_iff, not_isEmpty_of_nonempty, reduceDIte] + congr 1; ext1 + simp + +theorem subtype_val_iSup' (h : ∀ i, f i ∈ Set.Icc a b) : + ⨆ i, (⟨f i, h i⟩ : ↑(Set.Icc a b)) = + ⟨⨆ i, f i, ⟨(h i.some).1.trans (le_ciSup ⟨b, by intro; grind⟩ _), ciSup_le (h ·|>.2)⟩⟩ := by + rw [Subtype.ext_iff, subtype_val_iSup] + +/- This isn't marked as `simp` because rewriting from a sup over a `CompleteLattice` into a +`ConditionallyCompleteLattice` would, pretty often, be undesirable. -/ +theorem subtype_val_iInf (h : ∀ i, f i ∈ Set.Icc a b) : + (⨅ i, (⟨f i, h i⟩ : ↑(Set.Icc a b))).val = ⨅ i, f i := by + simp only [iInf, sInf, Set.range_eq_empty_iff, not_isEmpty_of_nonempty, reduceDIte] + congr 1; ext1 + simp + +theorem subtype_val_iInf' (h : ∀ i, f i ∈ Set.Icc a b) : + ⨅ i, (⟨f i, h i⟩ : ↑(Set.Icc a b)) = + ⟨⨅ i, f i, ⟨le_ciInf (h ·|>.1), (ciInf_le ⟨a, by intro; grind⟩ _).trans (h i.some).2⟩⟩ := by + rw [Subtype.ext_iff, subtype_val_iInf] + +end subtype_val_iSup + +open scoped ENNReal Topology in +/-- Analogous to `bdd_le_mul_tendsto_zero`, for `ENNReal` (which otherwise lacks a continuous +multiplication function). The product of a sequence that tends to zero with any bounded sequence +also tends to zero. -/ +protected lemma ENNReal.bdd_le_mul_tendsto_zero + {α : Type*} {l : Filter α} {f g : α → ℝ≥0∞} {b : ℝ≥0∞} (hb : b ≠ ⊤) + (hf : l.Tendsto f (𝓝 0)) (hg : ∀ᶠ (x : α) in l, g x ≤ b) : + l.Tendsto (fun x ↦ f x * g x) (𝓝 0) := by + rw [ENNReal.tendsto_nhds_zero] at hf ⊢ + intro ε hεpos + by_cases hb_pos : 0 < b + · filter_upwards [hf (ε / b) (by simp [hb, hεpos.ne']), hg] with x hx₁ hx₂ + grw [hx₁, hx₂, ENNReal.div_mul_cancel hb_pos.ne' hb] + · filter_upwards [hg] with x hx + grind [not_lt, nonpos_iff_eq_zero, mul_zero, zero_le] + +--PULLOUT: Belongs in Mathlib/Algebra/Order/Group/Pointwise/CompleteLattice.lean +-- (after appropriately generalizing to MulPosMono) +open scoped Pointwise in +theorem csInf_mul_nonneg {s t : Set ℝ} + (hs₀ : s.Nonempty) (hs₁ : ∀ x ∈ s, 0 ≤ x) (ht₀ : t.Nonempty) (ht₁ : ∀ x ∈ t, 0 ≤ x) : + sInf (s * t) = sInf s * sInf t := by + apply le_antisymm + · set a := sInf s + set b := sInf t + have h_eps : ∀ ε > 0, ∃ x ∈ s, x < a + ε ∧ ∃ y ∈ t, y < b + ε := by + intro ε ε_pos + obtain ⟨x, hx₁, hx₂⟩ := exists_lt_of_csInf_lt hs₀ (lt_add_of_pos_right a ε_pos) + obtain ⟨y, hy₁, hy₂⟩ := exists_lt_of_csInf_lt ht₀ (lt_add_of_pos_right b ε_pos) + exact ⟨x, hx₁, hx₂, y, hy₁, hy₂⟩ + have h_prod_eps : ∀ ε > 0, ∃ x ∈ s, ∃ y ∈ t, x * y < (a + ε) * (b + ε) := by + intro ε hε + obtain ⟨x, hx₁, hx₂, y, hy₁, hy₂⟩ := h_eps ε hε + exact ⟨x, hx₁, y, hy₁, by nlinarith [hs₁ x hx₁, ht₁ y hy₁]⟩ + have h_lim : Filter.Tendsto (fun ε => (a + ε) * (b + ε)) (nhdsWithin 0 (Set.Ioi 0)) (nhds (a + * b)) := by + exact tendsto_nhdsWithin_of_tendsto_nhds (Continuous.tendsto' (by continuity) _ _ (by + norm_num)) + apply le_of_tendsto_of_tendsto tendsto_const_nhds h_lim + filter_upwards [self_mem_nhdsWithin] with ε hε + specialize h_prod_eps ε hε + choose x hx y hy using h_prod_eps + refine le_trans ?_ hy.right.le + refine csInf_le ⟨0, ?_⟩ ?_ + · rintro x ⟨u, hu, v, hv, rfl⟩ + exact mul_nonneg (hs₁ u hu) (ht₁ v hv) + · exact ⟨_, hx, _, hy.left, rfl⟩ + · apply le_csInf (hs₀.mul ht₀) + rintro _ ⟨x, hx, y, hy, rfl⟩ + apply mul_le_mul + · exact csInf_le ⟨0, hs₁⟩ hx + · exact csInf_le ⟨0, ht₁⟩ hy + · exact Real.sInf_nonneg ht₁ + · exact hs₁ x hx + +/-- +If two functions from finite types have the same multiset of values, there exists a bijection + between the domains that commutes with the functions. +-/ +lemma Multiset.map_univ_eq_iff {α β : Type*} [Fintype α] (f g : α → β) : + Multiset.map f Finset.univ.val = Multiset.map g Finset.univ.val ↔ ∃ (e : α ≃ α), f = g ∘ e := by + apply Iff.intro + · intro a + classical + -- Since these two multisets are equal, their elements must be equal up to permutation. + have h_perm : ∃ e : α ≃ α, ∀ x, f x = g (e x) := by + have h_count_eq : ∀ y : β, Finset.card (Finset.filter (fun x => f x = y) Finset.univ) = + Finset.card (Finset.filter (fun x => g x = y) Finset.univ) := by + intro y; + replace a := congr_arg ( fun m => m.count y ) a; + simp_all? ( config := { decide := Bool.true } ) [ Multiset.count_map ]; + simp_all? [ eq_comm ] + exact a + have h_perm : ∀ y : β, ∃ e : { x : α // f x = y } ≃ { x : α // g x = y }, True := by + intro y + simp_all only [exists_const_iff, and_true] + exact ⟨ Fintype.equivOfCardEq <| by simpa [ Fintype.card_subtype ] using h_count_eq y ⟩; + choose e he using h_perm; + refine ⟨ ?_, ?_ ⟩; + · exact (Equiv.sigmaFiberEquiv f).symm.trans (Equiv.sigmaCongrRight e) |>.trans + (Equiv.sigmaFiberEquiv g) + intro x + specialize e ( f x ) + rename_i e_1 + simp_all only [implies_true, Equiv.trans_apply, Equiv.sigmaCongrRight_apply, + Equiv.sigmaFiberEquiv_apply] + exact Eq.symm ( e_1 ( f x ) ⟨ x, rfl ⟩ |>.2 ); + exact ⟨ h_perm.choose, funext h_perm.choose_spec ⟩; + · intro a + obtain ⟨w, h⟩ := a + subst h + simp_all only [Function.comp_apply, Finset.univ] + -- Since $w$ is a bijection, the multiset of $w(x)$ for $x$ in the original multiset is + -- just a permutation of the original multiset. + have h_perm : Multiset.map (fun x => w x) (Finset.val Fintype.elems) = Finset.val + Fintype.elems := by + exact Multiset.map_univ_val_equiv w; + conv_rhs => rw [ ← h_perm ]; + simp +zetaDelta at * + +/-- +If two functions from finite types have the same multiset of values, there exists a bijection + between the domains that commutes with the functions. +-/ +lemma exists_equiv_of_multiset_map_eq {α β γ : Type*} [Fintype α] [Fintype β] + (f : α → γ) (g : β → γ) (h : Multiset.map f Finset.univ.val = Multiset.map g Finset.univ.val) : + ∃ e : α ≃ β, f = g ∘ e := by + -- Since the multisets of values are equal, the cardinalities of the domains must be equal + -- (as the multiset size is the cardinality of the domain). Thus there exists a bijection `σ + -- : α ≃ β`. + obtain ⟨σ, hσ⟩ : ∃ σ : α ≃ β, Multiset.map f Finset.univ.val = Multiset.map (g ∘ σ) + Finset.univ.val := by + have h_card : Fintype.card α = Fintype.card β := by + simpa using congr_arg Multiset.card h; + obtain σ := Fintype.equivOfCardEq h_card + use σ + have h_multiset_eq : Multiset.map g Finset.univ.val = Multiset.map (g ∘ σ) Finset.univ.val := by + rw [ ← Multiset.map_univ_val_equiv σ ]; + rw [ Multiset.map_map ] + exact h.trans h_multiset_eq; + -- By `Multiset.map_univ_eq_iff`, there exists `e' : α ≃ α` such that `f = (g ∘ σ) ∘ e'`. + obtain ⟨e', he'⟩ : ∃ e' : α ≃ α, f = (g ∘ σ) ∘ e' := by + exact (Multiset.map_univ_eq_iff f (g ∘ ⇑σ)).mp hσ; + exact ⟨ e'.trans σ, by simp_all [ Function.comp ]; grind⟩ diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/Tactic.lean b/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/Tactic.lean new file mode 100644 index 0000000000..d6d62fb313 --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/Tactic.lean @@ -0,0 +1,19 @@ +/- +Copyright (c) 2026 Bryan Ehrlich. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Bryan Ehrlich +-/ + +module + +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.Tactic.Commutes + + +/-! +# Tactic + +Supporting modules for Euclidean Jordan algebras: power associativity, the spectral theorem, the + trace form, Koecher/Alfsen-Shultz, and the frame Peirce decomposition. +-/ + +@[expose] public section diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/Tactic/Commutes.lean b/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/Tactic/Commutes.lean new file mode 100644 index 0000000000..ef276b92a5 --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/Tactic/Commutes.lean @@ -0,0 +1,203 @@ +/- +Copyright (c) 2026 Alex Meiburg. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Alex Meiburg +-/ +module + +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.Tactic.Commutes.Attribute +public import Mathlib.Algebra.BigOperators.Group.List.Basic +public import Mathlib.Algebra.BigOperators.Ring.Finset +public import Mathlib.Algebra.BigOperators.Ring.List +public import Mathlib.Algebra.Group.Action.Defs +public import Mathlib.Algebra.Group.Center +public import Mathlib.Algebra.Group.Commute.Basic +public import Mathlib.Algebra.Group.Commute.Hom +public import Mathlib.Algebra.Group.Commute.Units +public import Mathlib.Algebra.Group.Invertible.Basic +public import Mathlib.Algebra.Group.Opposite +public import Mathlib.Algebra.Group.Pi.Lemmas +public import Mathlib.Algebra.Group.Prod +public import Mathlib.Algebra.GroupWithZero.Commute +public import Mathlib.Algebra.GroupWithZero.Semiconj +public import Mathlib.Algebra.Polynomial.Basic +public import Mathlib.Algebra.Ring.Commute +public import Mathlib.Algebra.Ring.Nat +public import Mathlib.Algebra.Star.SelfAdjoint +public import Mathlib.Data.Int.Cast.Lemmas +public import Mathlib.Data.Matrix.Basic +public import Mathlib.Data.Nat.Cast.Commute +public import Mathlib.Data.Rat.Cast.Defs +public import Mathlib.GroupTheory.GroupAction.Ring +public import Mathlib.LinearAlgebra.Matrix.ZPow + +/-! + +# Tactic for `Commute` + +This tactic uses `aesop` to discharge goals relating to the `Commute` relation. It mostly +tries to do straightforward recursion on expressions, along with some basic normalization +of ring operations. +-/ + +@[expose] public section + +/-- A tactic for proving goals of the `Commute` relation. + +You can use `commutes?` to get a corresponding proof script. +-/ +syntax (name := commutesTac) "commutes" : tactic +macro_rules + | `(tactic| commutes) => `(tactic| + (aesop + (config := { introsTransparency? := some .reducible, terminal := true, + useSimpAll := false, useDefaultSimpSet := false }) + (rule_sets := [$(Lean.mkIdent `Commutes):ident, -default]))) --include `builtin` + +/-- A variant of the `commutes` tactic for proving `Commute` goals that produces +a proof script. -/ +syntax (name := commutesTac?) "commutes?" : tactic +macro_rules + | `(tactic| commutes?) => `(tactic| + (aesop? + (config := { introsTransparency? := some .reducible, terminal := true, + useSimpAll := false, useDefaultSimpSet := false }) + (rule_sets := [$(Lean.mkIdent `Commutes):ident, -default]))) + +attribute [aesop safe apply (rule_sets := [Commutes])] + Commute.all Commute.refl + Commute.one_left Commute.one_right + Commute.pow_self Commute.self_pow Commute.pow_pow_self + Units.commute_coe_inv Units.commute_inv_coe + Commute.self_zpow Commute.zpow_self Commute.zpow_zpow_self + Commute.self_zpow₀ Commute.zpow_self₀ Commute.zpow_zpow_self₀ + Commute.zero_left Commute.zero_right + Commute.neg_one_left Commute.neg_one_right + Nat.cast_commute Nat.commute_cast + Commute.intCast_left Commute.intCast_right + -- Commute.natCast_mul_self Commute.self_natCast_mul --redundant + -- Commute.intCast_mul_self Commute.intCast_mul_self_right --redundant + commute_invOf + NNRat.cast_commute NNRat.commute_cast + Rat.cast_commute Rat.commute_cast + Polynomial.commute_X Polynomial.commute_X_pow + Matrix.commute_diagonal Matrix.scalar_commute + IsStarNormal.star_comm_self + Matrix.Commute.self_zpow Matrix.Commute.zpow_self Matrix.Commute.zpow_zpow_self + +--It's extremely rare that we need to use Commute.symm in the middle of a proof. But it's +--quite common that we specifically need it at the end (or equivalently, at the beginning) +--which is why we give a safe + fast tactic to apply. This often cuts heartbeats by a large +--factor. +attribute [aesop apply 5% (rule_sets := [Commutes])] Commute.symm + +--This essentially does `symm + assumption`. Note that `assumption` is already a builtin. +add_aesop_rules safe tactic (rule_sets := [Commutes]) (by exact Commute.symm ‹_›) + +--Due to indexing OfNat issues, these don't work as `apply` rules. We add them as a tactic. +add_aesop_rules safe tactic (rule_sets := [Commutes]) (by apply Commute.ofNat_left) +add_aesop_rules safe tactic (rule_sets := [Commutes]) (by apply Commute.ofNat_right) + +--Try to normalize ring operations +add_aesop_rules safe tactic (rule_sets := [Commutes]) (by apply Commute.ofNat_right) + +attribute [aesop apply 50% (rule_sets := [Commutes])] + Commute.mul_left Commute.mul_right + Commute.pow_left Commute.pow_right --Commute.pow_pow (redundant) + Commute.smul_left Commute.smul_right + Commute.op Commute.unop + Commute.prod + Commute.zpow_left Commute.zpow_right --Commute.zpow_zpow (redundant) + Commute.zpow_left₀ Commute.zpow_right₀ --Commute.zpow_zpow_self₀ (redundant) + Commute.units_of_val Commute.units_val + Commute.units_inv_left Commute.units_inv_right + Commute.units_zpow_left Commute.units_zpow_right + Commute.ringInverse_ringInverse + Commute.inv_left₀ Commute.inv_right₀ + Commute.div_left Commute.div_right + Commute.neg_left Commute.neg_right + Commute.add_left Commute.add_right + Commute.sub_left Commute.sub_right + -- Commute.natCast_mul_left Commute.natCast_mul_right --redundant + -- Commute.intCast_mul_left Commute.intCast_mul_right --redundant + Commute.invOf_left Commute.invOf_right + Commute.inv_left Commute.inv_right --Commute.inv_inv (redundant) + Commute.list_prod_left Commute.list_prod_right + Commute.list_sum_left Commute.list_sum_right + Commute.sum_left Commute.sum_right + Commute.conj + Commute.pi + Commute.map + Commute.star_star --Commute.star_left Commute.star_right + Matrix.Commute.zpow_left Matrix.Commute.zpow_right --Matrix.Commute.zpow_zpow (redundant) + +--TODO: In `Mathlib.Analysis.Normed.Algebra.Exponential`, tag +-- `Commute.exp_left`, `Commute.exp_right`, `Commute.exp` + +--TODO: In cfc-relatated files, tag `Commute.cfc`, `IsSelfAdjoint.commute_cfc`, +-- `Commute.cfc_real`, `cfc_commute_cfc`, `cfcₙ_commute_cfcₙ`, `Commute.expUnitary` + +attribute [aesop simp (rule_sets := [Commutes])] + List.mem_map --Needed for `Commute.list_prod_left` to be interesting + --Do some weak normalization of ring operations. This is similar to the list + -- of lemmas used in `noncomm_ring`. + mul_one one_mul mul_zero zero_mul add_zero zero_add pow_one pow_zero + one_pow zero_pow + +--Chekcing that `commutes` can now prove several other lemmas, which were not part +-- of the set of rules given above. +example : type_of% @Commute.natCast_mul_self := by + commutes + +example : type_of% @Commute.natCast_mul_left := by + commutes + +example : type_of% @Commute.zpow_zpow_self₀ := by + commutes + +example : type_of% @Commute.natCast_mul_natCast_mul := by + commutes + +example : type_of% @Commute.intCast_mul_self := by + commutes + +example : type_of% @Commute.intCast_mul_left := by + commutes + +example : type_of% @Matrix.Commute.zpow_zpow := by + commutes + +--Very basic example: all natural numbers commute. +example (A B : Nat) : Commute A B := by + commutes + +--Check that we can also recognize assumptions stated in equivalent forms +example (R : Type*) [NonUnitalNonAssocSemiring R] (x y : R) (h : SemiconjBy y x x) : + Commute (x + y) x := by + commutes + +example (R : Type*) [Semiring R] (x y : R) (h : x * y * 1 = 0 + y * x) : + Commute (y ^ 2) (x + 1) := by + commutes + +--Example of a more complex goal that it can prove. +example (l : List ℕ) (R : Type*) [Ring R] (x y : R) (h : Commute y x) : + Commute (l.map (fun n ↦ n • x + y ^ n - 37)).prod x := by + commutes --Runs in 5676 heartbeats + +--Produced proof script from `commutes?` for the above goal. Takes 850 heartbeats +example (l : List ℕ) (R : Type*) [Ring R] (x y : R) (h : Commute y x) : + Commute (l.map (fun n ↦ n • x + y ^ n - 37)).prod x := by + apply Commute.list_prod_left + intro x_1 a + simp only [List.mem_map] at * + obtain ⟨w, h_1⟩ := a + obtain ⟨left, right⟩ := h_1 + subst right + apply Commute.sub_left + · apply Commute.add_left + · apply Commute.smul_left + rfl + · apply Commute.pow_left + exact h + · apply Commute.ofNat_left diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/Tactic/Commutes/Attribute.lean b/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/Tactic/Commutes/Attribute.lean new file mode 100644 index 0000000000..d673b60a88 --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/Vendor/Tactic/Commutes/Attribute.lean @@ -0,0 +1,21 @@ +/- +Copyright (c) 2026 Alex Meiburg. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Alex Meiburg +-/ +module + +public import Mathlib.Init +public import Aesop.Frontend.Command + +/-! +# Commutes Rule Set + +This module defines the `Commutes` Aesop rule set which is used by the +`commutes` tactic. Aesop rule sets only become visible once the file in which +they're declared is imported, so we must put this declaration into its own file. +-/ + +@[expose] public section + +declare_aesop_rule_sets [Commutes] (default := false) diff --git a/LeanPool/EuclideanJordan/EuclideanJordan/Witness.lean b/LeanPool/EuclideanJordan/EuclideanJordan/Witness.lean new file mode 100644 index 0000000000..a7cb7224c5 --- /dev/null +++ b/LeanPool/EuclideanJordan/EuclideanJordan/Witness.lean @@ -0,0 +1,268 @@ +/- +Copyright (c) 2026 Bryan Ehrlich. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Bryan Ehrlich +-/ +module + +public import LeanPool.EuclideanJordan.EuclideanJordan.FormallyReal +public import LeanPool.EuclideanJordan.EuclideanJordan.Block +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.Jordan +public import LeanPool.EuclideanJordan.EuclideanJordan.Vendor.HermitianMat.Inner + + + +/-! +# Non-vacuity of the Peirce layer, on the Hermitian-matrix carrier + +`EuclideanJordan/Peirce.lean` and `EuclideanJordan/PeirceMul.lean` are stated over an abstract + real commutative +Jordan algebra and are *conditional* throughout: every theorem assumes `c ∘ c = c`, and the +sharper ones assume an element with `c ∘ y = ½ y`. Conditional theorems are worth exactly +as much as their hypotheses are satisfiable. This file is the check that they are. + +Four things are verified, and they are different claims. + +**1. The instance stack resolves on `H₂(ℂ)`.** The four typeclass hypotheses of the Peirce +layer — `NonUnitalNonAssocCommRing`, `IsCommJordan`, `Module ℝ`, `IsScalarTower ℝ` — are all +satisfied by `HermitianMat (Fin 2) ℂ` under the scoped Jordan instances of +`EuclideanJordan/Vendor/HermitianMat/Jordan.lean` (namespace `HermMul`). Nothing had to be +built for this: the vendored file carries `IsCommJordan (HermitianMat d 𝕜)` already. +★ This is also why the layer assumes `IsScalarTower ℝ J J` rather than `SMulCommClass`: +they are interchangeable for a commutative product, and `IsScalarTower` is the one the +carrier supplies. + +**2. The `1/2`-eigenspace is not zero.** This is the part that could have gone wrong +silently. Every rule in `EuclideanJordan/PeirceMul.lean` mentioning `J_{1/2}` would be + *vacuously true* +on a carrier where the half-space is trivial — and the half-space **is** trivial for the two +idempotents one reaches for first, `0` and `1`. So a witness is exhibited: `cWit` is the +rank-one projection `diag(1,0)`, `xWit` is the off-diagonal `[[0,1],[1,0]]`, and +`cWit_mul_xWit` proves `cWit ∘ xWit = ½ · xWit` with `xWit ≠ 0`. + +**3. `HermitianMat` is formally real** (§4 below), so `EuclideanJordan/FormallyReal.lean`'s + no-nilpotents +theorem is live rather than conditional on an uninhabited hypothesis. + +**4. The diagonal matrix units are a complete Jordan frame** (§5 below), witnessing +`IsOrthIdemFamily` — which had **no** carrier here until then, leaving every theorem of +`EuclideanJordan/Frame.lean` and `EuclideanJordan/Block.lean` conditional on a structure nothing + was known to satisfy. + +★ **Scope.** This is non-vacuity, not coverage: it shows the Peirce hypotheses have a model +with all three components nonzero, and nothing more. The rank-two carrier is used because it +is the smallest place where a `1/2`-eigenvector exists, not because rank two matters here. +-/ + +@[expose] public section + +open HermMul EuclideanJordan + +namespace EuclideanJordan.Witness + +/-- The rank-two carrier. -/ +abbrev H2 := HermitianMat (Fin 2) ℂ + +/-! ### 1. The instance stack -/ + +noncomputable example : NonUnitalNonAssocCommRing H2 := inferInstance +noncomputable example : IsCommJordan H2 := inferInstance +noncomputable example : Module ℝ H2 := inferInstance +noncomputable example : IsScalarTower ℝ H2 H2 := inferInstance + +/-! ### 2. An idempotent with a nonzero `1/2`-eigenspace -/ + +/-- The rank-one projection `diag(1,0)`. -/ +noncomputable def cWit : H2 := HermitianMat.diagonal ℂ ![1, 0] + +/-- The off-diagonal Hermitian matrix `[[0,1],[1,0]]`. -/ +def xWit : H2 := + ⟨!![0, 1; 1, 0], by + simp only [selfAdjoint.mem_iff, Matrix.star_eq_conjTranspose] + ext i j + fin_cases i <;> fin_cases j <;> simp⟩ + +theorem cWit_mat : cWit.mat = Matrix.diagonal (fun i => ((![1, 0] : Fin 2 → ℝ) i : ℂ)) := rfl + +theorem xWit_mat : xWit.mat = !![0, 1; 1, 0] := rfl + +theorem xWit_ne_zero : xWit ≠ 0 := by + intro h + have h2 : xWit.mat 0 1 = (0 : H2).mat 0 1 := by rw [h] + rw [xWit_mat] at h2 + simp at h2 + +/-- `cWit` is an idempotent for the Jordan product. -/ +theorem cWit_idem : cWit * cWit = cWit := by + apply HermitianMat.ext + rw [mul_eq_symmMul, HermitianMat.symmMul_toMat, cWit_mat] + ext i j + fin_cases i <;> fin_cases j <;> simp + norm_num + +/-- **The half-space is inhabited by a nonzero element**, so every `J_{1/2}` rule in +`EuclideanJordan/PeirceMul.lean` has content on this carrier. -/ +theorem cWit_mul_xWit : cWit * xWit = (2 : ℝ)⁻¹ • xWit := by + apply HermitianMat.ext + rw [mul_eq_symmMul, HermitianMat.symmMul_toMat, cWit_mat, xWit_mat] + change _ = (2 : ℝ)⁻¹ • xWit.mat + rw [xWit_mat] + ext i j + fin_cases i <;> fin_cases j <;> simp [Matrix.vecMul_diagonal] + +/-! ### 3. The layer, exercised + +Three theorems of the abstract layer instantiated at the witness. They are corollaries with +no new content — the point is that the hypotheses discharge against a real carrier rather +than remaining hypothetical. -/ + +/-- `eigen_half_mul_half` at the witness: `xWit ∘ xWit` has no `1/2`-component. -/ +theorem witness_half_mul_half : cWit * (cWit * (xWit * xWit)) = cWit * (xWit * xWit) := + eigen_half_mul_half cWit_idem cWit_mul_xWit cWit_mul_xWit + +/-- `peirceHalf_mul_half_eq_zero` at the witness. -/ +theorem witness_peirceHalf_eq_zero : peirceHalf cWit (xWit * xWit) = 0 := + peirceHalf_mul_half_eq_zero cWit_idem cWit_mul_xWit cWit_mul_xWit + +/-- `exists_peirce_decomposition` at the witness: `xWit` itself splits. -/ +theorem witness_decomposition : + ∃ y₁ yₕ y₀ : H2, cWit * y₁ = y₁ ∧ cWit * yₕ = (2 : ℝ)⁻¹ • yₕ ∧ cWit * y₀ = 0 + ∧ xWit = y₁ + yₕ + y₀ := + exists_peirce_decomposition cWit_idem xWit + +/-- **The middle branch of `eigenvalue_trichotomy` is attained.** Without this the +trichotomy would still be true with an empty `1/2` case, and every `J_{1/2}` rule with it. + +★ This statement replaced a genuinely vacuous one, and the replacement is the finding. The +first version here read `(2:ℝ)⁻¹ = 0 ∨ (2:ℝ)⁻¹ = (2:ℝ)⁻¹ ∨ (2:ℝ)⁻¹ = 1`, discharged by +`eigenvalue_trichotomy` at the witness — a disjunction whose middle disjunct is `rfl`, so it +is provable with no witness, no carrier and no Jordan identity. A vacuous statement inside +the very file written to rule out vacuity. -/ +theorem witness_half_attained : ∃ y : H2, y ≠ 0 ∧ cWit * y = (2 : ℝ)⁻¹ • y := + ⟨xWit, xWit_ne_zero, cWit_mul_xWit⟩ + +/-! ### 4. Formal reality on the Hermitian-matrix carrier + +★★★ **This section closes the exposure `EuclideanJordan/FormallyReal.lean` declares in its own + docstring**: +that `IsFormallyReal` had no carrier here, so every theorem depending on it was conditional on +an uninhabited hypothesis. + +`HermitianMat d 𝕜` is formally real, and the proof is the classical one: the Jordan square of +a Hermitian matrix is its matrix square, whose trace is the squared Frobenius norm, so a +vanishing sum of squares is a vanishing sum of non-negative reals. + +★ Nothing new is built here — `inner_self_nonneg` and `InnerProductCore.definite` are vendored +(`EuclideanJordan/Vendor/HermitianMat/Inner.lean`) and the Jordan-square identity is +`symmMul_self`. What was +missing was, once again, the *application*. -/ + +section FormallyReal + +variable {d : Type*} [Fintype d] [DecidableEq d] {𝕜 : Type*} [RCLike 𝕜] + +/-- The trace functional is additive along a `Finset` sum. -/ +private theorem inner_sum_left_one {ι : Type*} (t : Finset ι) (g : ι → HermitianMat d 𝕜) : + inner ℝ (∑ j ∈ t, g j) (1 : HermitianMat d 𝕜) + = ∑ j ∈ t, inner ℝ (g j) (1 : HermitianMat d 𝕜) := by + induction t using Finset.cons_induction with + | empty => simp + | cons a t ha ih => rw [Finset.sum_cons, HermitianMat.inner_add_left, ih, Finset.sum_cons] + +/-- The Jordan square has the same trace as the matrix square: `Tr[(A ∘ A)·1] = Tr[A·A]`. -/ +private theorem inner_mul_self_one (A : HermitianMat d 𝕜) : + inner ℝ (A * A) (1 : HermitianMat d 𝕜) = inner ℝ A A := by + rw [HermitianMat.inner_def, HermitianMat.inner_def, mul_eq_symmMul, HermitianMat.symmMul_self] + simp + +omit [DecidableEq d] in +/-- **`H_d(𝕜)` is formally real.** -/ +instance instIsFormallyReal : EuclideanJordan.IsFormallyReal (HermitianMat d 𝕜) where + eq_zero_of_sum_mul_self := by + let : DecidableEq d := Classical.decEq d + intro ι s f hsum i hi + have h0 : ∑ j ∈ s, inner ℝ (f j * f j) (1 : HermitianMat d 𝕜) = 0 := by + rw [← inner_sum_left_one, hsum] + simp + have h1 : ∑ j ∈ s, inner ℝ (f j) (f j) = 0 := by + rw [← h0] + exact Finset.sum_congr rfl fun j _ => (inner_mul_self_one (f j)).symm + have h2 : inner ℝ (f i) (f i) = 0 := + (Finset.sum_eq_zero_iff_of_nonneg + (fun j _ => HermitianMat.inner_self_nonneg (f j))).mp h1 i hi + exact HermitianMat.InnerProductCore.definite (f i) h2 + +/-- The nilpotence theorem, live on the carrier: a Hermitian matrix with a vanishing +Jordan power is zero. -/ +theorem hermitian_eq_zero_of_jpow_eq_zero {A : HermitianMat d 𝕜} {n : ℕ} + (h : EuclideanJordan.jpow A n = 0) : A = 0 := + EuclideanJordan.eq_zero_of_jpow_eq_zero n h + +/-- Albert's theorem, live on the carrier. -/ +theorem hermitian_jpow_mul_jpow (A : HermitianMat d 𝕜) (m n : ℕ) : + EuclideanJordan.jpow A m * EuclideanJordan.jpow A n + = EuclideanJordan.jpow A (m + n + 1) := + EuclideanJordan.jpow_mul_jpow A m n + +end FormallyReal + +/-! ### 5. A Jordan frame on the carrier + +★★ **This closes the last vacuity exposure in the EJA layer.** `IsOrthIdemFamily` +(`EuclideanJordan/Frame.lean`) had **no witness**, so every theorem of + `EuclideanJordan/Frame.lean` and +`EuclideanJordan/Block.lean` was conditional on a structure nothing was known to satisfy — the same +exposure that section 4 closed for `IsFormallyReal`. The diagonal matrix units supply it. + +★ Completeness (`∑ i, p i = 1`) is proved here even though **no theorem in the abstract layer +assumes it** — deliberately, per `EuclideanJordan/Frame.lean`'s docstring, since completeness is + what the +spectral theorem produces rather than what the Peirce theory needs. Having it on the carrier +shows the abstract results are not being kept general by weakening past what the intended +model satisfies. -/ + +section JordanFrame + +variable {d : Type*} [Fintype d] [DecidableEq d] + +/-- The diagonal matrix units `E_ii`, as Hermitian matrices. -/ +noncomputable def diagFrame (i : d) : HermitianMat d ℂ := + HermitianMat.diagonal ℂ (fun j => if j = i then 1 else 0) + +omit [Fintype d] in +theorem diagFrame_mat (i : d) : + (diagFrame i).mat = Matrix.diagonal (fun j => ((if j = i then (1 : ℝ) else 0 : ℝ) : ℂ)) := + rfl + +/-- **The diagonal matrix units are a family of orthogonal idempotents.** -/ +theorem diagFrame_orthIdem : + EuclideanJordan.IsOrthIdemFamily (diagFrame (d := d)) where + idem i := by + apply HermitianMat.ext + rw [mul_eq_symmMul, HermitianMat.symmMul_toMat, diagFrame_mat, + Matrix.diagonal_mul_diagonal] + ext a b + by_cases h : a = b <;> simp [Matrix.smul_apply, h] + split_ifs <;> norm_num + orth i j hij := by + apply HermitianMat.ext + rw [mul_eq_symmMul, HermitianMat.symmMul_toMat, diagFrame_mat, diagFrame_mat, + Matrix.diagonal_mul_diagonal, Matrix.diagonal_mul_diagonal] + ext a b + by_cases h : a = b <;> simp [Matrix.smul_apply, h] + split_ifs with h1 h2 <;> simp_all + +/-- The frame is **complete**: the matrix units sum to the identity. -/ +theorem diagFrame_sum : (∑ i, diagFrame (d := d) i) = 1 := by + have hsum : (∑ i, diagFrame (d := d) i).mat = ∑ i, (diagFrame (d := d) i).mat := by simp + apply HermitianMat.ext + rw [hsum] + ext a b + simp only [Matrix.sum_apply, diagFrame_mat, Matrix.diagonal_apply] + by_cases h : a = b + · subst h; simp [apply_ite ((↑) : ℝ → ℂ)] + · simp [h] + +end JordanFrame + +end EuclideanJordan.Witness diff --git a/LeanPool/EuclideanJordan/FramePeirceSolution.lean b/LeanPool/EuclideanJordan/FramePeirceSolution.lean new file mode 100644 index 0000000000..c9233286e7 --- /dev/null +++ b/LeanPool/EuclideanJordan/FramePeirceSolution.lean @@ -0,0 +1,338 @@ +/- +Copyright (c) 2026 Bryan Ehrlich. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Bryan Ehrlich +-/ +module + +public import Mathlib.Algebra.BigOperators.Fin +public import Mathlib.Algebra.DirectSum.Module +public import Mathlib.Analysis.InnerProductSpace.Basic +public import Mathlib.Data.Sym.Sym2 +public import Mathlib.LinearAlgebra.FiniteDimensional.Defs +public import Mathlib.LinearAlgebra.Dimension.Finrank +public import LeanPool.EuclideanJordan.EuclideanJordan.FramePeirceMul + + + +/-! +# The Peirce decomposition of a Euclidean Jordan algebra relative to a Jordan frame + +A **Euclidean Jordan algebra** is a real inner-product space `J` carrying a commutative bilinear +product `∘` with unit `1`, satisfying the Jordan identity `x ∘ (x² ∘ y) = x² ∘ (x ∘ y)` and the +associativity of the inner product `⟪x ∘ y, z⟫ = ⟪y, x ∘ z⟫` (Faraut–Korányi, *Analysis on +Symmetric Cones*, Definition III.1.1). `EuclideanJordanAlgebra` below is exactly that, written +as a class. + +An idempotent `c` (`c ∘ c = c`) is **primitive** when it is nonzero and cannot be split: the only +idempotents `d` of the Peirce subalgebra `J₂(c) = {x | c ∘ x = x}` are `0` and `c`. A **Jordan +frame** is a family `p₁, …, pₙ` of pairwise-orthogonal (`pᵢ ∘ pⱼ = 0` for `i ≠ j`) primitive +idempotents that is complete (`∑ᵢ pᵢ = 1`). + +Left multiplication `L_c : x ↦ c ∘ x` by an idempotent is diagonalisable with eigenvalues `1`, +`½` and `0`; that is the Peirce decomposition at a single idempotent. For a Jordan frame the +operators `L_{p₁}, …, L_{pₙ}` are simultaneously diagonalisable, and the surviving joint +eigenspaces — the **blocks** — are indexed by *unordered pairs* of indices: + +* `V_{ii} := {x | pᵢ ∘ x = x}`, the `1`-eigenspace of `L_{pᵢ}`; +* `V_{ij} := {x | pᵢ ∘ x = ½ • x ∧ pⱼ ∘ x = ½ • x}` for `i ≠ j`, the joint `½`-eigenspace of + `L_{pᵢ}` and `L_{pⱼ}`. + +This file states the two theorems that turn that list of subspaces into a decomposition of `J`: + +1. **`frameBlock_isInternal`** — the blocks are an internal direct sum, `J = ⨁_{i ≤ j} V_{ij}`. + Equivalently: they are independent and they span. +2. **`finrank_frameBlock_diag`** — each diagonal block is a line, `dim_ℝ V_{ii} = 1`. (It is in + fact `ℝ ∙ pᵢ`, but the dimension is the form the coordinatization consumes.) + +Together these are Faraut–Korányi Theorem IV.2.1. They are the starting point of the +Jordan–von Neumann–Wigner classification: once `J = ⨁_{i ≤ j} V_{ij}` with one-dimensional +diagonal, the off-diagonal blocks `V_{ij}` all carry a common composition algebra structure and +`J` is recognised as a matrix algebra `H_n(K)`. The multiplication table of the blocks +(`V_{ij} ∘ V_{jk} ⊆ V_{ik}`, `V_{ij} ∘ V_{kl} = 0` for disjoint index pairs, and so on) is the +next step and is *not* stated here. + +## Why the blocks are indexed by `Sym2 (Fin n)` + +`V_{ij}` and `V_{ji}` are literally the same subspace — the defining conditions are a conjunction +that is symmetric in `i` and `j`, and the eigenvalue `blockCoef i j` is symmetric too. So the +honest index set for the family is the type of *unordered* pairs `Sym2 (Fin n)`, and the direct +sum runs over it with no double counting. That is what "`⨁_{i ≤ j}`" means in the informal +statement, and using `Sym2` rather than `{q : Fin n × Fin n // q.1 ≤ q.2}` avoids having to +choose a representative. `frameBlockRaw` is the ordered-pair family, `frameBlockRaw_comm` is its +symmetry, and `frameBlock` is the descent of the former along the latter; `frameBlock_mk` +(`frameBlock F s(i, j) = frameBlockRaw F i j`, by `rfl`) is the bridge a reader should use to see +what the statement says at a concrete pair of indices. + +`DirectSum.IsInternal (frameBlock F)` is Mathlib's predicate saying that the canonical map +`⨁_{s : Sym2 (Fin n)} V_s → J` is bijective — that is, independence *and* spanning, which is the +full strength of the decomposition and not merely the spanning half. + +## What is and is not assumed + +Assumed for both theorems: `J` is a real inner-product space (`NormedAddCommGroup` plus +`InnerProductSpace ℝ`) carrying `EuclideanJordanAlgebra`, and `F : JordanFrame J n` is a Jordan +frame of some cardinality `n`, carried as **data**. + +★ **Finite-dimensionality is assumed only for the second theorem.** `frameBlock_isInternal` +holds with no dimension hypothesis at all; `finrank_frameBlock_diag` takes +`[FiniteDimensional ℝ J]`, because the proof that a primitive idempotent's Peirce subalgebra is a +line runs the spectral theorem inside that subalgebra, and the spectral theorem is false without +the dimension hypothesis (`ℝ[X]` satisfies every other hypothesis with no nonconstant +resolution). + +★ **The inner product is an arbitrary associative one, not the trace form.** Faraut–Korányi fix +`⟪x, y⟫ = tr(x ∘ y)`; the class below asks only that *some* positive-definite associative inner +product exist. That is the weaker hypothesis, so the theorems below are the stronger statements. +Positive-definiteness is not stated as a field: it is already part of `InnerProductSpace ℝ J`. +Formal reality is not a hypothesis either — it follows from the associativity of the inner +product, by pairing a vanishing sum of squares against `1`. + +★ **Primitivity is a formal hypothesis of both theorems; only the second one spends it.** +`JordanFrame` carries primitivity, so `frameBlock_isInternal` assumes it formally even though its +proof never uses it. The stronger statement — that the blocks of a merely orthogonal complete +idempotent family already decompose `J` — is **not formalized here**. It is +`dim V_{ii} = 1` that cashes primitivity out, and that is why the two theorems are stated +together: the decomposition is useless for classification without the one-dimensionality. + +★ **No claim is made about the rank of `J`.** A frame is carried as data of a given cardinality +`n`; that `n` equals the rank of `J`, or that all frames have the same cardinality +(Faraut–Korányi IV.2.5, conjugacy of frames), is neither assumed nor concluded here. Do not read +`frameBlock_isInternal` as a statement about `rank J`. + +Not assumed: no associativity or power-associativity as a hypothesis, no simplicity, no +classification, no identification of `J` with a matrix algebra, no ordered-space structure, no +continuity beyond what the norm gives for free, and no `n ≥ 3`. + +## The vocabulary used here + +Everything the two statements mention is defined below from Mathlib alone: the class +`EuclideanJordanAlgebra`, the predicates `IsOrthIdemFamily` and `IsPrimitive`, the structure +`JordanFrame`, the eigenspace `eigSub`, the eigenvalue `blockCoef`, and the block families +`frameBlockRaw` and `frameBlock`. Nothing else is imported beyond core Mathlib. + +## This file + +Repeats the definitions and the two theorem statements of `FramePeirceChallenge.lean` verbatim, +imports the reference library, and discharges them from `EuclideanJordan.frameBlock_isInternal` +(`EuclideanJordan/FramePeirce.lean`) and `EuclideanJordan.finrank_frameBlock_diag` +(`EuclideanJordan/FramePeirceMul.lean`). + +The bridge is short by construction. The local class `EuclideanJordanAlgebra` carries the same +fields as `EuclideanJordan.EuclideanJordanAlgebra`, so an instance of the library's class is +assembled from ours field by field, with `toMul` and `toOne` taken from ours — which is what +makes the two `*` and the two `1` the *same* operations rather than merely isomorphic ones. The +local `IsOrthIdemFamily`, `IsPrimitive`, `JordanFrame`, `eigSub`, `blockCoef`, `frameBlockRaw` +and `frameBlock` are then definitionally the library's, so each proof is a single `exact` once +the instance and the frame have been transported. + +★ The library instance is introduced *inside* the proof bodies with `let`, never at the top +level of this file. That is deliberate. An ambient `EuclideanJordan.EuclideanJordanAlgebra J` +brings its derived `NonUnitalNonAssocCommRing J` into scope, and `Submodule ℝ J` would then +elaborate its `AddCommMonoid J` argument through the ring rather than through the norm; the +resulting type is definitionally equal to, but not syntactically the same as, the one the +challenge file states. The contract here is that the two files' declaration types agree on the +nose, so the extra instance is kept out of every statement. +-/ + +@[expose] public section + +noncomputable section + +namespace JordanFramePeirce + +/-- A **Euclidean Jordan algebra**: a real inner-product space carrying a commutative bilinear +product with unit, satisfying the Jordan identity and the associativity of the inner product. + +This is Faraut–Korányi's definition (FK III.1.1) with two deliberate weakenings, both of which +make the theorems below *stronger*: finite-dimensionality is not a field (it is carried as a +separate `[FiniteDimensional ℝ J]` argument exactly where it is needed), and the inner product is +an arbitrary associative one rather than the Jordan trace form. + +Distributivity and homogeneity are stated on the left only; commutativity supplies the right-hand +versions. -/ +class EuclideanJordanAlgebra (J : Type*) [NormedAddCommGroup J] [InnerProductSpace ℝ J] + extends Mul J, One J where + /-- The Jordan product is commutative. -/ + mul_comm : ∀ x y : J, x * y = y * x + /-- The Jordan product is additive in its left argument. -/ + add_mul : ∀ x y z : J, (x + y) * z = x * z + y * z + /-- The Jordan product is homogeneous in its left argument. -/ + smul_mul : ∀ (r : ℝ) (x y : J), (r • x) * y = r • (x * y) + /-- `1` is a unit for the Jordan product. -/ + one_mul : ∀ x : J, (1 : J) * x = x + /-- The Jordan identity, `x ∘ (x² ∘ y) = x² ∘ (x ∘ y)`. -/ + jordan : ∀ x y : J, x * ((x * x) * y) = (x * x) * (x * y) + /-- The inner product is associative: `⟪x ∘ y, z⟫ = ⟪y, x ∘ z⟫`. This is what "Euclidean" + adds to "formally real". -/ + inner_assoc : ∀ x y z : J, inner ℝ (x * y) z = inner ℝ y (x * z) + +variable {J : Type*} [NormedAddCommGroup J] [InnerProductSpace ℝ J] [EuclideanJordanAlgebra J] + +namespace EuclideanJordanAlgebra + +/-- Left multiplication by `0` is `0` — the one ring axiom the class does not state, obtained +from additivity at `(0, 0, a)`. -/ +theorem zero_mul' (a : J) : (0 : J) * a = 0 := by + have h : (0 : J) * a + (0 : J) * a = (0 : J) * a + 0 := by + rw [add_zero, ← add_mul, add_zero] + exact add_left_cancel h + +theorem mul_zero' (a : J) : a * (0 : J) = 0 := by rw [mul_comm, zero_mul'] + +theorem mul_add' (a x y : J) : a * (x + y) = a * x + a * y := by + rw [mul_comm a (x + y), add_mul, mul_comm x a, mul_comm y a] + +theorem mul_smul' (r : ℝ) (a x : J) : a * (r • x) = r • (a * x) := by + rw [mul_comm a (r • x), smul_mul, mul_comm x a] + +end EuclideanJordanAlgebra + +/-! ## Orthogonal idempotent families, primitivity, and Jordan frames -/ + +/-- A family of pairwise-orthogonal idempotents. Completeness is deliberately *not* part of this +predicate; it is a separate field of `JordanFrame`. -/ +structure IsOrthIdemFamily {n : ℕ} (p : Fin n → J) : Prop where + /-- Each member is idempotent. -/ + idem : ∀ i, p i * p i = p i + /-- Distinct members are orthogonal. -/ + orth : ∀ i j, i ≠ j → p i * p j = 0 + +/-- A **primitive idempotent**: a nonzero idempotent that cannot be split, i.e. the only +idempotents of the Peirce subalgebra `J₂(c) = {x | c ∘ x = x}` are `0` and `c` itself. + +The third clause is stated in the ambient algebra — `d` idempotent with `c ∘ d = d`, which is +membership in `J₂(c)` — rather than over a subtype, so that it can be checked without first +producing the subalgebra. -/ +def IsPrimitive (c : J) : Prop := + c * c = c ∧ c ≠ 0 ∧ ∀ d : J, d * d = d → c * d = d → d = 0 ∨ d = c + +/-- A **Jordan frame**: a complete family of pairwise-orthogonal primitive idempotents. + +Carried as data, indexed by `Fin n`, so that its cardinality is available without any +well-definedness theorem. In particular `n` is *not* asserted to be the rank of `J`. -/ +structure JordanFrame (J : Type*) [NormedAddCommGroup J] [InnerProductSpace ℝ J] + [EuclideanJordanAlgebra J] (n : ℕ) where + /-- The idempotents. -/ + p : Fin n → J + /-- They are idempotent and pairwise orthogonal. -/ + orthIdem : IsOrthIdemFamily p + /-- Each is primitive. -/ + primitive : ∀ i, IsPrimitive (p i) + /-- They sum to the unit. -/ + complete : ∑ i, p i = 1 + +/-! ## The blocks -/ + +/-- The `r`-eigenspace of `L_a : x ↦ a ∘ x`, as a submodule. -/ +def eigSub (a : J) (r : ℝ) : Submodule ℝ J where + carrier := {x : J | a * x = r • x} + add_mem' := fun {u v} hu hv => by + change a * (u + v) = r • (u + v) + rw [EuclideanJordanAlgebra.mul_add', hu, hv, smul_add] + zero_mem' := by + change a * 0 = r • (0 : J) + rw [EuclideanJordanAlgebra.mul_zero', smul_zero] + smul_mem' := fun t x hx => by + change a * (t • x) = r • (t • x) + rw [EuclideanJordanAlgebra.mul_smul', hx, smul_comm] + +@[simp] theorem mem_eigSub {a : J} {r : ℝ} {x : J} : x ∈ eigSub a r ↔ a * x = r • x := Iff.rfl + +variable {n : ℕ} + +/-- The eigenvalue attached to the pair `(i, j)`: `1` on the diagonal, `½` off it. -/ +def blockCoef (i j : Fin n) : ℝ := if i = j then 1 else (2 : ℝ)⁻¹ + +theorem blockCoef_comm (i j : Fin n) : blockCoef i j = blockCoef j i := by + unfold blockCoef + by_cases h : i = j + · simp [h] + · simp [h, Ne.symm h] + +/-- `V_{ij}` before it is pushed through `Sym2`: the joint `blockCoef i j`-eigenspace of `L_{pᵢ}` +and `L_{pⱼ}`. On the diagonal this is `J₂(pᵢ) = {x | pᵢ ∘ x = x}`; off it, the joint +`½`-eigenspace. -/ +def frameBlockRaw (F : JordanFrame J n) (i j : Fin n) : Submodule ℝ J := + eigSub (F.p i) (blockCoef i j) ⊓ eigSub (F.p j) (blockCoef i j) + +theorem frameBlockRaw_comm (F : JordanFrame J n) (i j : Fin n) : + frameBlockRaw F i j = frameBlockRaw F j i := by + unfold frameBlockRaw + rw [blockCoef_comm i j, inf_comm] + +/-- **`V_{ij}`**, indexed by unordered pairs. For `i ≠ j` the joint `½`-eigenspace of `L_{pᵢ}` +and `L_{pⱼ}`; on the diagonal, `J₂(pᵢ)`. -/ +def frameBlock (F : JordanFrame J n) : Sym2 (Fin n) → Submodule ℝ J := + Sym2.lift ⟨frameBlockRaw F, frameBlockRaw_comm F⟩ + +@[simp] theorem frameBlock_mk (F : JordanFrame J n) (i j : Fin n) : + frameBlock F s(i, j) = frameBlockRaw F i j := rfl + +theorem mem_frameBlock_diag {F : JordanFrame J n} {i : Fin n} {x : J} : + x ∈ frameBlock F s(i, i) ↔ F.p i * x = x := by + simp [frameBlockRaw, blockCoef] + +theorem mem_frameBlock_off {F : JordanFrame J n} {i j : Fin n} (hij : i ≠ j) {x : J} : + x ∈ frameBlock F s(i, j) ↔ F.p i * x = (2 : ℝ)⁻¹ • x ∧ F.p j * x = (2 : ℝ)⁻¹ • x := by + simp [frameBlockRaw, blockCoef, hij] + +/-! ## The two theorems -/ + +/-- **The frame Peirce decomposition: `J = ⨁_{i ≤ j} V_{ij}`.** + +For a Jordan frame `p₁, …, pₙ` of a Euclidean Jordan algebra `J`, the blocks `V_{ij}` — indexed +by unordered pairs, so that `V_{ij}` and `V_{ji}` are counted once — form an internal direct sum +decomposition of `J`: the canonical map `⨁_{s : Sym2 (Fin n)} V_s → J` is bijective. That is +independence *and* spanning. + +Reference: J. Faraut and A. Korányi, *Analysis on Symmetric Cones*, Oxford 1994, Theorem IV.2.1. + +No dimension hypothesis is needed. Primitivity remains a *formal hypothesis* of this statement — +it is carried by `JordanFrame` — but the proof does not spend it: see the module docstring. -/ +theorem frameBlock_isInternal (F : JordanFrame J n) : DirectSum.IsInternal (frameBlock F) := by + let _lib : EuclideanJordan.EuclideanJordanAlgebra J := + { toMul := inferInstance + toOne := inferInstance + mul_comm := EuclideanJordanAlgebra.mul_comm + add_mul := EuclideanJordanAlgebra.add_mul + smul_mul := EuclideanJordanAlgebra.smul_mul + one_mul := EuclideanJordanAlgebra.one_mul + jordan := EuclideanJordanAlgebra.jordan + inner_assoc := EuclideanJordanAlgebra.inner_assoc } + exact EuclideanJordan.frameBlock_isInternal + { p := F.p + orthIdem := ⟨F.orthIdem.idem, F.orthIdem.orth⟩ + primitive := F.primitive + complete := F.complete } + +/-- **The diagonal blocks are lines: `dim V_{ii} = 1`.** + +This is where primitivity of the frame's members is spent, and where finite-dimensionality is +needed. `V_{ii}` is the Peirce subalgebra `J₂(pᵢ)`, which is itself a Euclidean Jordan algebra +with unit `pᵢ`; the spectral theorem inside it writes every element as a real combination of +idempotents of `J₂(pᵢ)`, and primitivity says each of those is `0` or `pᵢ`. So +`V_{ii} = ℝ ∙ pᵢ`, and `pᵢ ≠ 0`. + +Reference: J. Faraut and A. Korányi, *Analysis on Symmetric Cones*, Oxford 1994, Theorem IV.2.1. + +★ This is a statement about one block of a frame carried as data. It is *not* a statement about +`rank J`, and nothing here converts it into one. -/ +theorem finrank_frameBlock_diag [FiniteDimensional ℝ J] (F : JordanFrame J n) (i : Fin n) : + Module.finrank ℝ ↥(frameBlock F s(i, i)) = 1 := by + let _lib : EuclideanJordan.EuclideanJordanAlgebra J := + { toMul := inferInstance + toOne := inferInstance + mul_comm := EuclideanJordanAlgebra.mul_comm + add_mul := EuclideanJordanAlgebra.add_mul + smul_mul := EuclideanJordanAlgebra.smul_mul + one_mul := EuclideanJordanAlgebra.one_mul + jordan := EuclideanJordanAlgebra.jordan + inner_assoc := EuclideanJordanAlgebra.inner_assoc } + exact EuclideanJordan.finrank_frameBlock_diag + { p := F.p + orthIdem := ⟨F.orthIdem.idem, F.orthIdem.orth⟩ + primitive := F.primitive + complete := F.complete } i + +end JordanFramePeirce diff --git a/LeanPool/EuclideanJordan/KoecherSolution.lean b/LeanPool/EuclideanJordan/KoecherSolution.lean new file mode 100644 index 0000000000..94846aef4f --- /dev/null +++ b/LeanPool/EuclideanJordan/KoecherSolution.lean @@ -0,0 +1,119 @@ +/- +Copyright (c) 2026 Bryan Ehrlich. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Bryan Ehrlich +-/ +module + +public import Mathlib.Algebra.BigOperators.Fin +public import Mathlib.Algebra.Module.LinearMap.Defs +public import Mathlib.Analysis.Normed.Module.Basic +public import Mathlib.LinearAlgebra.FiniteDimensional.Defs +public import LeanPool.EuclideanJordan.EuclideanJordan.OrderAuto + + + +/-! +# Solution: Koecher / Alfsen–Shultz, a unital linear order isomorphism is a Jordan automorphism + +Let `J` be a finite-dimensional formally real (Euclidean) Jordan algebra with unit `e`, ordered by +its cone of sums of squares. If a linear bijection `Φ : J ≃ₗ[ℝ] J` fixes `e` and preserves that +cone **in both directions**, then `Φ` preserves the Jordan product. This is the classical theorem +of Koecher; see Alfsen–Shultz, *Geometry of State Spaces*, Theorem 2.80. + +The content is that a Euclidean Jordan algebra's *order* determines its *multiplication*: the +order-automorphism group of the cone that fix the unit is exactly the Jordan-automorphism group. +It is the algebraic core of the Koecher–Vinberg circle of ideas, and the step by which +order-theoretic hypotheses in quantum foundations become algebraic ones. + +## The vocabulary used here + +Everything the statement mentions is defined below from Mathlib alone. In particular **no Jordan +ring instance is used**: the multiplication is a bundled `ℝ`-bilinear map `m : J →ₗ[ℝ] J →ₗ[ℝ] J`, +and commutativity, the Jordan identity, formal reality and unitality are ordinary hypotheses +stated in terms of `m`. Likewise **no order instance is used**: the cone is the predicate `IsSoS` +defined below, and `Φ`'s order-compatibility is the biconditional `horder`. + +Consequently the statement elaborates against a bare `NormedAddCommGroup`/`Module ℝ`/`Module.Finite` +carrier, and a reader can check that it says what it should without consulting any library. + +## What is and is not assumed + +Assumed: `m` is `ℝ`-bilinear (by type), commutative (`hcomm`), satisfies the Jordan identity +`(a ∘ b) ∘ (a ∘ a) = a ∘ (b ∘ (a ∘ a))` (`hjordan`), is formally real in the finite-sum sense +(`hfr`: a finite sum of squares vanishes only if every summand's argument is `0`), and has `e` as a +two-sided unit (`he`, which is one-sided only because `m` is commutative). `J` is finitely +generated as an `ℝ`-module. `Φ` is `ℝ`-linear and bijective **by type**, fixes `e`, and satisfies +`IsSoS m x ↔ IsSoS m (Φ x)` for every `x`. + +★ The **biconditional** in `horder` is load-bearing and is not a convenience. The order-theoretic +characterisation of idempotents used in the proof (`c` is idempotent iff `0 ≤ c ≤ e` and no nonzero +cone element lies below both `c` and `e - c`) contains a universal quantifier over the cone, and +transporting that clause along `Φ⁻¹` consumes the reflecting direction. A one-directional +hypothesis `IsSoS m x → IsSoS m (Φ x)` is genuinely weaker. + +Not assumed: no inner product, no trace form, no continuity or boundedness of `Φ`, no associativity +or power-associativity as a hypothesis, no positive-definiteness beyond `hfr`, no `OrderedSpace` +structure, no simplicity, no classification, and no identification of `J` with a matrix algebra. + +★ **The norm is never used.** `[NormedAddCommGroup J]` appears only so that this statement matches +the one proved in the reference library, whose finite-dimensionality plumbing is set up over a +normed carrier; it is an extra hypothesis, so it makes the theorem below weaker rather than +stronger, and the argument does not touch it. + +★ This is **not** the van Imhoff–Roelands theorem (arXiv:1904.09278), which works in JB-generality +and *concludes* linearity from order-isomorphy. Here `Φ` is linear by type, and that is the whole +difference. + +## This file + +Repeats the definition and the theorem statement of `KoecherChallenge.lean` verbatim, imports the +reference library, and discharges the theorem from `EuclideanJordan.orderIso_preservesJordan`. The +local `IsSoS` is the same existential as `EuclideanJordan.IsSoS`, so the bridge is definitional. +-/ + +@[expose] public section + +namespace KoecherAlfsenShultz + +open Finset + +/-- **The positive cone**: `z` is a finite sum of squares of the bilinear product `m`. + +The sums-of-squares reading, rather than the single-square reading, is what makes this usable as a +*definition*: closure under addition is a concatenation of index sets, whereas closure of the +single-square set under addition is a theorem requiring the spectral decomposition. Over a +Euclidean Jordan algebra the two predicates coincide, but that is a result, not a convention. + +The empty sum is allowed (`k = 0`), so `0` lies in the cone. -/ +def IsSoS {J : Type*} [AddCommGroup J] [Module ℝ J] (m : J →ₗ[ℝ] J →ₗ[ℝ] J) (z : J) : Prop := + ∃ (k : ℕ) (f : Fin k → J), z = ∑ i, m (f i) (f i) + +variable {J : Type*} [NormedAddCommGroup J] [Module ℝ J] [Module.Finite ℝ J] + +/-- **Koecher / Alfsen–Shultz.** On a finite-dimensional formally real Jordan algebra, a linear +bijection that fixes the unit and preserves the cone of sums of squares in both directions +preserves the Jordan product — that is, it is a Jordan automorphism. + +The hypotheses, in order: `hcomm` and `hjordan` make the bilinear map `m` a Jordan multiplication; +`hfr` is formal reality (`∑ᵢ m (f i) (f i) = 0 → ∀ i, f i = 0`), which together with finite +dimensionality makes `J` Euclidean; `he` says `e` is the unit; `hunital` and `horder` say `Φ` is a +unital order isomorphism for the cone `IsSoS m`. The conclusion `Φ (m x y) = m (Φ x) (Φ y)` holds +for all `x y : J`. + +Reference: M. Koecher; see also E. M. Alfsen and F. W. Shultz, *Geometry of State Spaces of +Operator Algebras*, Birkhäuser 2003, Theorem 2.80. + +For what is and is not assumed — in particular why the biconditional in `horder` cannot be weakened +to an implication, and why the norm on `J` is inert — see the module docstring above. -/ +theorem orderIso_preservesJordan (m : J →ₗ[ℝ] J →ₗ[ℝ] J) + (hcomm : ∀ x y : J, m x y = m y x) + (hjordan : ∀ a b : J, m (m a b) (m a a) = m a (m b (m a a))) + (hfr : ∀ (k : ℕ) (f : Fin k → J), (∑ i, m (f i) (f i)) = 0 → ∀ i, f i = 0) + (e : J) (he : ∀ y : J, m e y = y) + (Φ : J ≃ₗ[ℝ] J) (hunital : Φ e = e) + (horder : ∀ x : J, IsSoS m x ↔ IsSoS m (Φ x)) (x y : J) : + Φ (m x y) = m (Φ x) (Φ y) := + EuclideanJordan.orderIso_preservesJordan m hcomm hjordan hfr e he Φ hunital horder x y + +end KoecherAlfsenShultz diff --git a/LeanPool/EuclideanJordan/SpectralSolution.lean b/LeanPool/EuclideanJordan/SpectralSolution.lean new file mode 100644 index 0000000000..fc6c4120ba --- /dev/null +++ b/LeanPool/EuclideanJordan/SpectralSolution.lean @@ -0,0 +1,69 @@ +/- +Copyright (c) 2026 Bryan Ehrlich. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Bryan Ehrlich +-/ +module + +public import Mathlib.Analysis.InnerProductSpace.Basic +public import Mathlib.LinearAlgebra.FiniteDimensional.Defs +public import Mathlib.LinearAlgebra.BilinearMap +public import Mathlib.Algebra.BigOperators.Fin +public import Mathlib.Basic.Real.Basic +public import LeanPool.EuclideanJordan.EuclideanJordan.Spectral + + +/-! +# Solution: the single-element spectral theorem for a Euclidean Jordan algebra + +Repeats the statement of `SpectralChallenge.lean` verbatim and discharges it from +`EuclideanJordan.spectral_resolution_bilinear` in `EuclideanJordan/Spectral.lean`. + +The delegation is a single application because the library theorem is stated in exactly this +vocabulary: the product enters as a bundled bilinear map, so no Jordan-algebra instance has to +exist before the statement elaborates. The instances the proof needs (a non-unital commutative +ring on `J`, `IsCommJordan`, `IsScalarTower ℝ J J`, `IsFormallyReal J`) are built inside the +library from `m`, `hcomm`, `hjordan` and `hfr`, and none of them escapes into the statement. +-/ + +@[expose] public section + +namespace JordanSpectral + +variable {J : Type*} [NormedAddCommGroup J] [InnerProductSpace ℝ J] + +/-- **The single-element spectral theorem for a Euclidean Jordan algebra.** + +Let `J` be a finite-dimensional real vector space and `m : J →ₗ[ℝ] J →ₗ[ℝ] J` a bilinear +product on it which is commutative (`hcomm`), satisfies the Jordan identity (`hjordan`), is +formally real (`hfr` : a vanishing sum of squares has vanishing summands), and has a unit `e` +(`he`). Then every `x : J` admits a **spectral resolution**: a finite family of idempotents +`q i` for `m`, pairwise orthogonal, summing to the unit, with `x` a real combination of them. + +This is Theorem III.1.1 of J. Faraut and A. Koranyi, *Analysis on Symmetric Cones*, Oxford +University Press (1994); the underlying classification is P. Jordan, J. von Neumann and +E. Wigner, *On an algebraic generalization of the quantum mechanical formalism*, Ann. of Math. +35 (1934) 29-64. + +Everything the statement mentions is Mathlib: bilinear maps, `Finset.sum` over `Fin n`, real +scalar multiplication. In particular "idempotent", "orthogonal" and "complete" are written out +inline as `m (q i) (q i) = q i`, `i ≠ j → m (q i) (q j) = 0`, and `∑ i, q i = e`. + +What is and is not assumed. No associativity, no power-associativity, no positivity of the +inner product against `m` (indeed no hypothesis at all connects `m` to `⟪·, ·⟫`), no ordering, no +trace, no Peirce decomposition, no simplicity. Finite-dimensionality is essential: `ℝ[X]` under +polynomial multiplication satisfies every other hypothesis and has only the idempotents `0` and +`1`. -/ +theorem spectral_resolution_bilinear [FiniteDimensional ℝ J] (m : J →ₗ[ℝ] J →ₗ[ℝ] J) + (hcomm : ∀ x y : J, m x y = m y x) + (hjordan : ∀ a b : J, m (m a b) (m a a) = m a (m b (m a a))) + (hfr : ∀ (k : ℕ) (f : Fin k → J), (∑ i, m (f i) (f i)) = 0 → ∀ i, f i = 0) + (e : J) (he : ∀ y : J, m e y = y) (x : J) : + ∃ (n : ℕ) (q : Fin n → J) (lam : Fin n → ℝ), + (∀ i, m (q i) (q i) = q i) ∧ + (∀ i j, i ≠ j → m (q i) (q j) = 0) ∧ + (∑ i, q i) = e ∧ + x = ∑ i, lam i • q i := + EuclideanJordan.spectral_resolution_bilinear m hcomm hjordan hfr e he x + +end JordanSpectral diff --git a/LeanPool/EuclideanJordan/StructureSolution.lean b/LeanPool/EuclideanJordan/StructureSolution.lean new file mode 100644 index 0000000000..4eab444b4c --- /dev/null +++ b/LeanPool/EuclideanJordan/StructureSolution.lean @@ -0,0 +1,512 @@ +/- +Copyright (c) 2026 Bryan Ehrlich. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Bryan Ehrlich +-/ +module + +public import Mathlib.Algebra.Jordan.Basic +public import Mathlib.Algebra.BigOperators.Fin +public import Mathlib.Algebra.DirectSum.Module +public import Mathlib.Analysis.InnerProductSpace.Basic +public import Mathlib.Basic.Real.Basic +public import Mathlib.Data.Sym.Sym2 +public import Mathlib.LinearAlgebra.BilinearMap +public import Mathlib.LinearAlgebra.Dimension.Finrank +public import Mathlib.LinearAlgebra.FiniteDimensional.Defs +public import Mathlib.LinearAlgebra.Trace +public import LeanPool.EuclideanJordan.EuclideanJordan.TraceForm +public import LeanPool.EuclideanJordan.EuclideanJordan.FramePeirceMul + + + +/-! +# Solution: the trace form and the frame Peirce decomposition + +Repeats the definitions and the six statements of `StructureChallenge.lean` verbatim and +discharges them from the accompanying `EuclideanJordan` development: Part I from +`EuclideanJordan/TraceForm.lean`, Part II from `EuclideanJordan/FramePeirce.lean` and +`EuclideanJordan/FramePeirceMul.lean`. + +The two parts are independent. Part I is stated over +`[NonUnitalNonAssocCommRing J] [Module ℝ J] [IsScalarTower ℝ J J]`; Part II over +`[NormedAddCommGroup J] [InnerProductSpace ℝ J]` with its own `EuclideanJordanAlgebra` class. +Neither part's vocabulary appears in the other's statements, no theorem of one is used in the +proof of the other, and the binders of all six statements are exactly those of the challenge +file. The part-level docstrings below are the originals and describe each bridge in detail. +-/ + +@[expose] public section + +/-! # Part I: the Jordan trace form -/ + +/-! +# Solution: the Jordan trace form + +Repeats the definitions and the four statements of Part I of `StructureChallenge.lean` verbatim +and discharges them from `EuclideanJordan/TraceForm.lean`. + +The definitions here are syntactic copies of the library's `EuclideanJordan.mulL`, +`EuclideanJordan.mulLₗ`, `EuclideanJordan.jtr` and `EuclideanJordan.traceForm`, so they are +definitionally equal to them (the proof fields differ only up to proof irrelevance) and each +bridge is the library theorem applied on the nose. + +The one piece of real work is formal reality. The challenge states it as a hypothesis over +`Fin k`, the shape `SpectralChallenge.lean` uses, while the library's +`EuclideanJordan.IsFormallyReal` is a class quantifying over an arbitrary `Finset`. The two differ +only by reindexing along `Finset.equivFin`, done inline in each of the two positivity proofs. +-/ + +namespace EuclideanJordan.StructureSolution + +namespace JordanTraceForm + +variable {J : Type*} [NonUnitalNonAssocCommRing J] [Module ℝ J] [IsScalarTower ℝ J J] + +/-- In a *commutative* algebra the scalar-tower rule `(r • a) * b = r • (a * b)` already gives +the `SMulCommClass` rule on the other side, so only `IsScalarTower ℝ J J` has to be assumed. -/ +theorem mul_smul_comm' (r : ℝ) (a b : J) : a * (r • b) = r • (a * b) := by + rw [mul_comm, smul_mul_assoc, mul_comm] + +/-- **The Jordan multiplication operator** `L_c : y ↦ c * y`, as an `ℝ`-linear map. Its +`ℝ`-linearity is exactly what the scalar tower buys, and it is what makes `L_c` traceable. -/ +def mulL (c : J) : J →ₗ[ℝ] J where + toFun y := c * y + map_add' := mul_add c + map_smul' r y := mul_smul_comm' r c y + +@[simp] theorem mulL_apply (c y : J) : mulL c y = c * y := rfl + +/-- `L_·` bundled as a linear map in the multiplier, which is what makes `jtr` linear. -/ +def mulLₗ : J →ₗ[ℝ] J →ₗ[ℝ] J where + toFun := mulL + map_add' a b := by ext y; simp only [mulL_apply, LinearMap.add_apply, add_mul] + map_smul' r a := by + ext y + simp only [mulL_apply, LinearMap.smul_apply, RingHom.id_apply, smul_mul_assoc] + +@[simp] theorem mulLₗ_apply (a : J) : mulLₗ a = mulL a := rfl + +/-- **The Jordan trace functional** `x ↦ tr(L_x)`, as an `ℝ`-linear form. Not normalised: see +the module docstring. -/ +noncomputable def jtr : J →ₗ[ℝ] ℝ := (LinearMap.trace ℝ J).comp mulLₗ + +@[simp] theorem jtr_apply (x : J) : jtr x = LinearMap.trace ℝ J (mulL x) := rfl + +/-- **The Jordan trace form** `τ(x, y) = tr(L_{x * y})`, bundled as an `ℝ`-bilinear form. + +Bilinearity is not a theorem below because it is the *type*: the four `mk₂` fields are additivity +and homogeneity in each argument, and they are immediate from linearity of `jtr` and +bilinearity of the product. -/ +noncomputable def traceForm : J →ₗ[ℝ] J →ₗ[ℝ] ℝ := + LinearMap.mk₂ ℝ (fun x y => jtr (x * y)) + (fun x x' y => by rw [add_mul, map_add]) + (fun r x y => by rw [smul_mul_assoc, map_smul, smul_eq_mul]) + (fun x y y' => by rw [mul_add, map_add]) + (fun r x y => by rw [mul_smul_comm' r x y, map_smul, smul_eq_mul]) + +@[simp] theorem traceForm_apply (x y : J) : traceForm x y = jtr (x * y) := rfl + +/-- **The trace form is symmetric**: `τ(x, y) = τ(y, x)`. + +No Jordan identity, no finite dimension, no formal reality: this is commutativity of the product +underneath `jtr`, and it is registered at that generality deliberately. -/ +theorem traceForm_comm (x y : J) : traceForm x y = traceForm y x := + EuclideanJordan.traceForm_comm x y + +/-- **The trace form is associative**: `τ(x * y, z) = τ(y, x * z)`. + +This is the compatibility that the standard presentation of a Euclidean Jordan algebra *assumes* +of its inner product, here proved of a form manufactured from the multiplication alone. It is the +main theorem of this file. + +Note the hypotheses, which are weaker than one expects. Beyond the commutative product and the +`ℝ`-module structure only `IsCommJordan` — the Jordan identity — is assumed: **no finite +dimension, no formal reality, no unit, no positivity, no idempotents, no spectral theory.** +`LinearMap.trace` is total, so the statement is meaningful (and true) even when `J` has no finite +basis and every trace in sight is `0`. -/ +theorem traceForm_assoc [IsCommJordan J] (x y z : J) : + traceForm (x * y) z = traceForm y (x * z) := + EuclideanJordan.traceForm_assoc x y z + +/-- **The trace form is positive semidefinite**: `τ(x, x) ≥ 0`. + +`hfr` is formal reality: a vanishing sum of squares has vanishing summands. With +`Module.Finite ℝ J` it yields a spectral resolution `x = ∑ᵢ λᵢ qᵢ` into orthogonal idempotents, +whence `x * x = ∑ᵢ λᵢ² qᵢ` and `τ(x, x) = ∑ᵢ λᵢ² tr(L_{qᵢ})`; and for an idempotent `c` the Peirce +split `L_c = P₁(c) + ½ P_{1/2}(c)` writes `tr(L_c)` as a nonnegative combination of traces of +idempotent endomorphisms, which are the ranks of their ranges. + +Formal reality is essential: `ℂ` over `ℝ` is a finite-dimensional commutative associative Jordan +algebra with `τ(i, i) = -2`. See the module docstring, which is also honest about the weaker +role `Module.Finite ℝ J` plays in this particular statement. -/ +theorem traceForm_self_nonneg [IsCommJordan J] [Module.Finite ℝ J] + (hfr : ∀ (k : ℕ) (f : Fin k → J), (∑ i, f i * f i) = 0 → ∀ i, f i = 0) (x : J) : + 0 ≤ traceForm x x := by + have : EuclideanJordan.IsFormallyReal J := by + classical + refine ⟨fun {ι} s f hsum i hi => ?_⟩ + have key : (∑ k : Fin s.card, f (s.equivFin.symm k) * f (s.equivFin.symm k)) = 0 := by + rw [show (∑ k : Fin s.card, f (s.equivFin.symm k) * f (s.equivFin.symm k)) + = ∑ a : {y // y ∈ s}, f a * f a from + Equiv.sum_comp s.equivFin.symm (fun a : {y // y ∈ s} => f a * f a), + Finset.sum_coe_sort s (fun a => f a * f a)] + exact hsum + simpa using hfr s.card (fun k => f (s.equivFin.symm k)) key (s.equivFin ⟨i, hi⟩) + exact EuclideanJordan.traceForm_self_nonneg x + +/-- **The trace form is definite**: `τ(x, x) = 0 ↔ x = 0`. + +Together with `traceForm_comm`, `traceForm_assoc` and `traceForm_self_nonneg` this is the whole +of the assertion that `τ` is a symmetric associative positive definite bilinear form — the +Euclidean form supplied by the multiplication itself. It is only the form: unitality, which a +Euclidean Jordan algebra also requires, is neither assumed nor concluded here. + +The nontrivial direction is `→`. It rests on a sharpening of the estimate behind +`traceForm_self_nonneg`: for a **nonzero** idempotent `c` one has `tr(L_c) ≥ 1`, because +`P₁(c) c = c` makes the range of the Peirce projection `P₁(c)` nonzero, hence of rank at least +one. So a vanishing `∑ᵢ λᵢ² tr(L_{qᵢ})` kills every `λᵢ` whose idempotent is nonzero, and the +terms with `qᵢ = 0` contribute nothing to `x` anyway. -/ +theorem traceForm_self_eq_zero_iff [IsCommJordan J] [Module.Finite ℝ J] + (hfr : ∀ (k : ℕ) (f : Fin k → J), (∑ i, f i * f i) = 0 → ∀ i, f i = 0) (x : J) : + traceForm x x = 0 ↔ x = 0 := by + have : EuclideanJordan.IsFormallyReal J := by + classical + refine ⟨fun {ι} s f hsum i hi => ?_⟩ + have key : (∑ k : Fin s.card, f (s.equivFin.symm k) * f (s.equivFin.symm k)) = 0 := by + rw [show (∑ k : Fin s.card, f (s.equivFin.symm k) * f (s.equivFin.symm k)) + = ∑ a : {y // y ∈ s}, f a * f a from + Equiv.sum_comp s.equivFin.symm (fun a : {y // y ∈ s} => f a * f a), + Finset.sum_coe_sort s (fun a => f a * f a)] + exact hsum + simpa using hfr s.card (fun k => f (s.equivFin.symm k)) key (s.equivFin ⟨i, hi⟩) + exact EuclideanJordan.traceForm_self_eq_zero_iff x + +end JordanTraceForm + +/-! # Part II: the frame Peirce decomposition -/ + +/-! +# The Peirce decomposition of a Euclidean Jordan algebra relative to a Jordan frame + +A **Euclidean Jordan algebra** is a real inner-product space `J` carrying a commutative bilinear +product `∘` with unit `1`, satisfying the Jordan identity `x ∘ (x² ∘ y) = x² ∘ (x ∘ y)` and the +associativity of the inner product `⟪x ∘ y, z⟫ = ⟪y, x ∘ z⟫` (Faraut–Korányi, *Analysis on +Symmetric Cones*, Definition III.1.1). `EuclideanJordanAlgebra` below is exactly that, written +as a class. + +An idempotent `c` (`c ∘ c = c`) is **primitive** when it is nonzero and cannot be split: the only +idempotents `d` of the Peirce subalgebra `J₂(c) = {x | c ∘ x = x}` are `0` and `c`. A **Jordan +frame** is a family `p₁, …, pₙ` of pairwise-orthogonal (`pᵢ ∘ pⱼ = 0` for `i ≠ j`) primitive +idempotents that is complete (`∑ᵢ pᵢ = 1`). + +Left multiplication `L_c : x ↦ c ∘ x` by an idempotent is diagonalisable with eigenvalues `1`, +`½` and `0`; that is the Peirce decomposition at a single idempotent. For a Jordan frame the +operators `L_{p₁}, …, L_{pₙ}` are simultaneously diagonalisable, and the surviving joint +eigenspaces — the **blocks** — are indexed by *unordered pairs* of indices: + +* `V_{ii} := {x | pᵢ ∘ x = x}`, the `1`-eigenspace of `L_{pᵢ}`; +* `V_{ij} := {x | pᵢ ∘ x = ½ • x ∧ pⱼ ∘ x = ½ • x}` for `i ≠ j`, the joint `½`-eigenspace of + `L_{pᵢ}` and `L_{pⱼ}`. + +This file states the two theorems that turn that list of subspaces into a decomposition of `J`: + +1. **`frameBlock_isInternal`** — the blocks are an internal direct sum, `J = ⨁_{i ≤ j} V_{ij}`. + Equivalently: they are independent and they span. +2. **`finrank_frameBlock_diag`** — each diagonal block is a line, `dim_ℝ V_{ii} = 1`. (It is in + fact `ℝ ∙ pᵢ`, but the dimension is the form the coordinatization consumes.) + +Together these are Faraut–Korányi Theorem IV.2.1. They are the starting point of the +Jordan–von Neumann–Wigner classification: once `J = ⨁_{i ≤ j} V_{ij}` with one-dimensional +diagonal, the off-diagonal blocks `V_{ij}` all carry a common composition algebra structure and +`J` is recognised as a matrix algebra `H_n(K)`. The multiplication table of the blocks +(`V_{ij} ∘ V_{jk} ⊆ V_{ik}`, `V_{ij} ∘ V_{kl} = 0` for disjoint index pairs, and so on) is the +next step and is *not* stated here. + +## Why the blocks are indexed by `Sym2 (Fin n)` + +`V_{ij}` and `V_{ji}` are literally the same subspace — the defining conditions are a conjunction +that is symmetric in `i` and `j`, and the eigenvalue `blockCoef i j` is symmetric too. So the +honest index set for the family is the type of *unordered* pairs `Sym2 (Fin n)`, and the direct +sum runs over it with no double counting. That is what "`⨁_{i ≤ j}`" means in the informal +statement, and using `Sym2` rather than `{q : Fin n × Fin n // q.1 ≤ q.2}` avoids having to +choose a representative. `frameBlockRaw` is the ordered-pair family, `frameBlockRaw_comm` is its +symmetry, and `frameBlock` is the descent of the former along the latter; `frameBlock_mk` +(`frameBlock F s(i, j) = frameBlockRaw F i j`, by `rfl`) is the bridge a reader should use to see +what the statement says at a concrete pair of indices. + +`DirectSum.IsInternal (frameBlock F)` is Mathlib's predicate saying that the canonical map +`⨁_{s : Sym2 (Fin n)} V_s → J` is bijective — that is, independence *and* spanning, which is the +full strength of the decomposition and not merely the spanning half. + +## What is and is not assumed + +Assumed for both theorems: `J` is a real inner-product space (`NormedAddCommGroup` plus +`InnerProductSpace ℝ`) carrying `EuclideanJordanAlgebra`, and `F : JordanFrame J n` is a Jordan +frame of some cardinality `n`, carried as **data**. + +★ **Finite-dimensionality is assumed only for the second theorem.** `frameBlock_isInternal` +holds with no dimension hypothesis at all; `finrank_frameBlock_diag` takes +`[FiniteDimensional ℝ J]`, because the proof that a primitive idempotent's Peirce subalgebra is a +line runs the spectral theorem inside that subalgebra, and the spectral theorem is false without +the dimension hypothesis (`ℝ[X]` satisfies every other hypothesis with no nonconstant +resolution). + +★ **The inner product is an arbitrary associative one, not the trace form.** Faraut–Korányi fix +`⟪x, y⟫ = tr(x ∘ y)`; the class below asks only that *some* positive-definite associative inner +product exist. That is the weaker hypothesis, so the theorems below are the stronger statements. +Positive-definiteness is not stated as a field: it is already part of `InnerProductSpace ℝ J`. +Formal reality is not a hypothesis either — it follows from the associativity of the inner +product, by pairing a vanishing sum of squares against `1`. + +★ **Primitivity is a formal hypothesis of both theorems; only the second one spends it.** +`JordanFrame` carries primitivity, so `frameBlock_isInternal` assumes it formally even though its +proof never uses it. The stronger statement — that the blocks of a merely orthogonal complete +idempotent family already decompose `J` — is **not formalized here**. It is +`dim V_{ii} = 1` that cashes primitivity out, and that is why the two theorems are stated +together: the decomposition is useless for classification without the one-dimensionality. + +★ **No claim is made about the rank of `J`.** A frame is carried as data of a given cardinality +`n`; that `n` equals the rank of `J`, or that all frames have the same cardinality +(Faraut–Korányi IV.2.5, conjugacy of frames), is neither assumed nor concluded here. Do not read +`frameBlock_isInternal` as a statement about `rank J`. + +Not assumed: no associativity or power-associativity as a hypothesis, no simplicity, no +classification, no identification of `J` with a matrix algebra, no ordered-space structure, no +continuity beyond what the norm gives for free, and no `n ≥ 3`. + +## The vocabulary used here + +Everything the two statements mention is defined below from Mathlib alone: the class +`EuclideanJordanAlgebra`, the predicates `IsOrthIdemFamily` and `IsPrimitive`, the structure +`JordanFrame`, the eigenspace `eigSub`, the eigenvalue `blockCoef`, and the block families +`frameBlockRaw` and `frameBlock`. Nothing else is imported beyond core Mathlib. + +## This file + +Repeats the definitions and the two theorem statements of Part II of `StructureChallenge.lean` + verbatim, +imports the reference library, and discharges them from `EuclideanJordan.frameBlock_isInternal` +(`EuclideanJordan/FramePeirce.lean`) and `EuclideanJordan.finrank_frameBlock_diag` +(`EuclideanJordan/FramePeirceMul.lean`). + +The bridge is short by construction. The local class `EuclideanJordanAlgebra` carries the same +fields as `EuclideanJordan.EuclideanJordanAlgebra`, so an instance of the library's class is +assembled from ours field by field, with `toMul` and `toOne` taken from ours — which is what +makes the two `*` and the two `1` the *same* operations rather than merely isomorphic ones. The +local `IsOrthIdemFamily`, `IsPrimitive`, `JordanFrame`, `eigSub`, `blockCoef`, `frameBlockRaw` +and `frameBlock` are then definitionally the library's, so each proof is a single `exact` once +the instance and the frame have been transported. + +★ The library instance is introduced *inside* the proof bodies with `let`, never at the top +level of this file. That is deliberate. An ambient `EuclideanJordan.EuclideanJordanAlgebra J` +brings its derived `NonUnitalNonAssocCommRing J` into scope, and `Submodule ℝ J` would then +elaborate its `AddCommMonoid J` argument through the ring rather than through the norm; the +resulting type is definitionally equal to, but not syntactically the same as, the one the +challenge file states. The contract here is that the two files' declaration types agree on the +nose, so the extra instance is kept out of every statement. +-/ + +noncomputable section + +namespace JordanFramePeirce + +/-- A **Euclidean Jordan algebra**: a real inner-product space carrying a commutative bilinear +product with unit, satisfying the Jordan identity and the associativity of the inner product. + +This is Faraut–Korányi's definition (FK III.1.1) with two deliberate weakenings, both of which +make the theorems below *stronger*: finite-dimensionality is not a field (it is carried as a +separate `[FiniteDimensional ℝ J]` argument exactly where it is needed), and the inner product is +an arbitrary associative one rather than the Jordan trace form. + +Distributivity and homogeneity are stated on the left only; commutativity supplies the right-hand +versions. -/ +class EuclideanJordanAlgebra (J : Type*) [NormedAddCommGroup J] [InnerProductSpace ℝ J] + extends Mul J, One J where + /-- The Jordan product is commutative. -/ + mul_comm : ∀ x y : J, x * y = y * x + /-- The Jordan product is additive in its left argument. -/ + add_mul : ∀ x y z : J, (x + y) * z = x * z + y * z + /-- The Jordan product is homogeneous in its left argument. -/ + smul_mul : ∀ (r : ℝ) (x y : J), (r • x) * y = r • (x * y) + /-- `1` is a unit for the Jordan product. -/ + one_mul : ∀ x : J, (1 : J) * x = x + /-- The Jordan identity, `x ∘ (x² ∘ y) = x² ∘ (x ∘ y)`. -/ + jordan : ∀ x y : J, x * ((x * x) * y) = (x * x) * (x * y) + /-- The inner product is associative: `⟪x ∘ y, z⟫ = ⟪y, x ∘ z⟫`. This is what "Euclidean" + adds to "formally real". -/ + inner_assoc : ∀ x y z : J, inner ℝ (x * y) z = inner ℝ y (x * z) + +variable {J : Type*} [NormedAddCommGroup J] [InnerProductSpace ℝ J] [EuclideanJordanAlgebra J] + +namespace EuclideanJordanAlgebra + +/-- Left multiplication by `0` is `0` — the one ring axiom the class does not state, obtained +from additivity at `(0, 0, a)`. -/ +theorem zero_mul' (a : J) : (0 : J) * a = 0 := by + have h : (0 : J) * a + (0 : J) * a = (0 : J) * a + 0 := by + rw [add_zero, ← add_mul, add_zero] + exact add_left_cancel h + +theorem mul_zero' (a : J) : a * (0 : J) = 0 := by rw [mul_comm, zero_mul'] + +theorem mul_add' (a x y : J) : a * (x + y) = a * x + a * y := by + rw [mul_comm a (x + y), add_mul, mul_comm x a, mul_comm y a] + +theorem mul_smul' (r : ℝ) (a x : J) : a * (r • x) = r • (a * x) := by + rw [mul_comm a (r • x), smul_mul, mul_comm x a] + +end EuclideanJordanAlgebra + +/-! ## Orthogonal idempotent families, primitivity, and Jordan frames -/ + +/-- A family of pairwise-orthogonal idempotents. Completeness is deliberately *not* part of this +predicate; it is a separate field of `JordanFrame`. -/ +structure IsOrthIdemFamily {n : ℕ} (p : Fin n → J) : Prop where + /-- Each member is idempotent. -/ + idem : ∀ i, p i * p i = p i + /-- Distinct members are orthogonal. -/ + orth : ∀ i j, i ≠ j → p i * p j = 0 + +/-- A **primitive idempotent**: a nonzero idempotent that cannot be split, i.e. the only +idempotents of the Peirce subalgebra `J₂(c) = {x | c ∘ x = x}` are `0` and `c` itself. + +The third clause is stated in the ambient algebra — `d` idempotent with `c ∘ d = d`, which is +membership in `J₂(c)` — rather than over a subtype, so that it can be checked without first +producing the subalgebra. -/ +def IsPrimitive (c : J) : Prop := + c * c = c ∧ c ≠ 0 ∧ ∀ d : J, d * d = d → c * d = d → d = 0 ∨ d = c + +/-- A **Jordan frame**: a complete family of pairwise-orthogonal primitive idempotents. + +Carried as data, indexed by `Fin n`, so that its cardinality is available without any +well-definedness theorem. In particular `n` is *not* asserted to be the rank of `J`. -/ +structure JordanFrame (J : Type*) [NormedAddCommGroup J] [InnerProductSpace ℝ J] + [EuclideanJordanAlgebra J] (n : ℕ) where + /-- The idempotents. -/ + p : Fin n → J + /-- They are idempotent and pairwise orthogonal. -/ + orthIdem : IsOrthIdemFamily p + /-- Each is primitive. -/ + primitive : ∀ i, IsPrimitive (p i) + /-- They sum to the unit. -/ + complete : ∑ i, p i = 1 + +/-! ## The blocks -/ + +/-- The `r`-eigenspace of `L_a : x ↦ a ∘ x`, as a submodule. -/ +def eigSub (a : J) (r : ℝ) : Submodule ℝ J where + carrier := {x : J | a * x = r • x} + add_mem' := fun {u v} hu hv => by + change a * (u + v) = r • (u + v) + rw [EuclideanJordanAlgebra.mul_add', hu, hv, smul_add] + zero_mem' := by + change a * 0 = r • (0 : J) + rw [EuclideanJordanAlgebra.mul_zero', smul_zero] + smul_mem' := fun t x hx => by + change a * (t • x) = r • (t • x) + rw [EuclideanJordanAlgebra.mul_smul', hx, smul_comm] + +@[simp] theorem mem_eigSub {a : J} {r : ℝ} {x : J} : x ∈ eigSub a r ↔ a * x = r • x := Iff.rfl + +variable {n : ℕ} + +/-- The eigenvalue attached to the pair `(i, j)`: `1` on the diagonal, `½` off it. -/ +def blockCoef (i j : Fin n) : ℝ := if i = j then 1 else (2 : ℝ)⁻¹ + +theorem blockCoef_comm (i j : Fin n) : blockCoef i j = blockCoef j i := by + unfold blockCoef + by_cases h : i = j + · simp [h] + · simp [h, Ne.symm h] + +/-- `V_{ij}` before it is pushed through `Sym2`: the joint `blockCoef i j`-eigenspace of `L_{pᵢ}` +and `L_{pⱼ}`. On the diagonal this is `J₂(pᵢ) = {x | pᵢ ∘ x = x}`; off it, the joint +`½`-eigenspace. -/ +def frameBlockRaw (F : JordanFrame J n) (i j : Fin n) : Submodule ℝ J := + eigSub (F.p i) (blockCoef i j) ⊓ eigSub (F.p j) (blockCoef i j) + +theorem frameBlockRaw_comm (F : JordanFrame J n) (i j : Fin n) : + frameBlockRaw F i j = frameBlockRaw F j i := by + unfold frameBlockRaw + rw [blockCoef_comm i j, inf_comm] + +/-- **`V_{ij}`**, indexed by unordered pairs. For `i ≠ j` the joint `½`-eigenspace of `L_{pᵢ}` +and `L_{pⱼ}`; on the diagonal, `J₂(pᵢ)`. -/ +def frameBlock (F : JordanFrame J n) : Sym2 (Fin n) → Submodule ℝ J := + Sym2.lift ⟨frameBlockRaw F, frameBlockRaw_comm F⟩ + +@[simp] theorem frameBlock_mk (F : JordanFrame J n) (i j : Fin n) : + frameBlock F s(i, j) = frameBlockRaw F i j := rfl + +theorem mem_frameBlock_diag {F : JordanFrame J n} {i : Fin n} {x : J} : + x ∈ frameBlock F s(i, i) ↔ F.p i * x = x := by + simp [frameBlockRaw, blockCoef] + +theorem mem_frameBlock_off {F : JordanFrame J n} {i j : Fin n} (hij : i ≠ j) {x : J} : + x ∈ frameBlock F s(i, j) ↔ F.p i * x = (2 : ℝ)⁻¹ • x ∧ F.p j * x = (2 : ℝ)⁻¹ • x := by + simp [frameBlockRaw, blockCoef, hij] + +/-! ## The two theorems -/ + +/-- **The frame Peirce decomposition: `J = ⨁_{i ≤ j} V_{ij}`.** + +For a Jordan frame `p₁, …, pₙ` of a Euclidean Jordan algebra `J`, the blocks `V_{ij}` — indexed +by unordered pairs, so that `V_{ij}` and `V_{ji}` are counted once — form an internal direct sum +decomposition of `J`: the canonical map `⨁_{s : Sym2 (Fin n)} V_s → J` is bijective. That is +independence *and* spanning. + +Reference: J. Faraut and A. Korányi, *Analysis on Symmetric Cones*, Oxford 1994, Theorem IV.2.1. + +No dimension hypothesis is needed. Primitivity remains a *formal hypothesis* of this statement — +it is carried by `JordanFrame` — but the proof does not spend it: see the module docstring. -/ +theorem frameBlock_isInternal (F : JordanFrame J n) : DirectSum.IsInternal (frameBlock F) := by + let _lib : EuclideanJordan.EuclideanJordanAlgebra J := + { toMul := inferInstance + toOne := inferInstance + mul_comm := EuclideanJordanAlgebra.mul_comm + add_mul := EuclideanJordanAlgebra.add_mul + smul_mul := EuclideanJordanAlgebra.smul_mul + one_mul := EuclideanJordanAlgebra.one_mul + jordan := EuclideanJordanAlgebra.jordan + inner_assoc := EuclideanJordanAlgebra.inner_assoc } + exact EuclideanJordan.frameBlock_isInternal + { p := F.p + orthIdem := ⟨F.orthIdem.idem, F.orthIdem.orth⟩ + primitive := F.primitive + complete := F.complete } + +/-- **The diagonal blocks are lines: `dim V_{ii} = 1`.** + +This is where primitivity of the frame's members is spent, and where finite-dimensionality is +needed. `V_{ii}` is the Peirce subalgebra `J₂(pᵢ)`, which is itself a Euclidean Jordan algebra +with unit `pᵢ`; the spectral theorem inside it writes every element as a real combination of +idempotents of `J₂(pᵢ)`, and primitivity says each of those is `0` or `pᵢ`. So +`V_{ii} = ℝ ∙ pᵢ`, and `pᵢ ≠ 0`. + +Reference: J. Faraut and A. Korányi, *Analysis on Symmetric Cones*, Oxford 1994, Theorem IV.2.1. + +★ This is a statement about one block of a frame carried as data. It is *not* a statement about +`rank J`, and nothing here converts it into one. -/ +theorem finrank_frameBlock_diag [FiniteDimensional ℝ J] (F : JordanFrame J n) (i : Fin n) : + Module.finrank ℝ ↥(frameBlock F s(i, i)) = 1 := by + let _lib : EuclideanJordan.EuclideanJordanAlgebra J := + { toMul := inferInstance + toOne := inferInstance + mul_comm := EuclideanJordanAlgebra.mul_comm + add_mul := EuclideanJordanAlgebra.add_mul + smul_mul := EuclideanJordanAlgebra.smul_mul + one_mul := EuclideanJordanAlgebra.one_mul + jordan := EuclideanJordanAlgebra.jordan + inner_assoc := EuclideanJordanAlgebra.inner_assoc } + exact EuclideanJordan.finrank_frameBlock_diag + { p := F.p + orthIdem := ⟨F.orthIdem.idem, F.orthIdem.orth⟩ + primitive := F.primitive + complete := F.complete } i + +end JordanFramePeirce + +end + +end EuclideanJordan.StructureSolution diff --git a/LeanPool/EuclideanJordan/TraceFormSolution.lean b/LeanPool/EuclideanJordan/TraceFormSolution.lean new file mode 100644 index 0000000000..c358a1f2af --- /dev/null +++ b/LeanPool/EuclideanJordan/TraceFormSolution.lean @@ -0,0 +1,159 @@ +/- +Copyright (c) 2026 Bryan Ehrlich. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Bryan Ehrlich +-/ +module + +public import Mathlib.Algebra.Jordan.Basic +public import Mathlib.Algebra.BigOperators.Fin +public import Mathlib.LinearAlgebra.BilinearMap +public import Mathlib.LinearAlgebra.Trace +public import Mathlib.LinearAlgebra.FiniteDimensional.Defs +public import Mathlib.Basic.Real.Basic +public import LeanPool.EuclideanJordan.EuclideanJordan.TraceForm + + +/-! +# Solution: the Jordan trace form + +Repeats the definitions and the four statements of `TraceFormChallenge.lean` verbatim and +discharges them from `EuclideanJordan/TraceForm.lean`. + +The definitions here are syntactic copies of the library's `EuclideanJordan.mulL`, +`EuclideanJordan.mulLₗ`, `EuclideanJordan.jtr` and `EuclideanJordan.traceForm`, so they are +definitionally equal to them (the proof fields differ only up to proof irrelevance) and each +bridge is the library theorem applied on the nose. + +The one piece of real work is formal reality. The challenge states it as a hypothesis over +`Fin k`, the shape `SpectralChallenge.lean` uses, while the library's +`EuclideanJordan.IsFormallyReal` is a class quantifying over an arbitrary `Finset`. The two differ +only by reindexing along `Finset.equivFin`, done inline in each of the two positivity proofs. +-/ + +@[expose] public section + +namespace JordanTraceForm + +variable {J : Type*} [NonUnitalNonAssocCommRing J] [Module ℝ J] [IsScalarTower ℝ J J] + +/-- In a *commutative* algebra the scalar-tower rule `(r • a) * b = r • (a * b)` already gives +the `SMulCommClass` rule on the other side, so only `IsScalarTower ℝ J J` has to be assumed. -/ +theorem mul_smul_comm' (r : ℝ) (a b : J) : a * (r • b) = r • (a * b) := by + rw [mul_comm, smul_mul_assoc, mul_comm] + +/-- **The Jordan multiplication operator** `L_c : y ↦ c * y`, as an `ℝ`-linear map. Its +`ℝ`-linearity is exactly what the scalar tower buys, and it is what makes `L_c` traceable. -/ +def mulL (c : J) : J →ₗ[ℝ] J where + toFun y := c * y + map_add' := mul_add c + map_smul' r y := mul_smul_comm' r c y + +@[simp] theorem mulL_apply (c y : J) : mulL c y = c * y := rfl + +/-- `L_·` bundled as a linear map in the multiplier, which is what makes `jtr` linear. -/ +def mulLₗ : J →ₗ[ℝ] J →ₗ[ℝ] J where + toFun := mulL + map_add' a b := by ext y; simp only [mulL_apply, LinearMap.add_apply, add_mul] + map_smul' r a := by + ext y + simp only [mulL_apply, LinearMap.smul_apply, RingHom.id_apply, smul_mul_assoc] + +@[simp] theorem mulLₗ_apply (a : J) : mulLₗ a = mulL a := rfl + +/-- **The Jordan trace functional** `x ↦ tr(L_x)`, as an `ℝ`-linear form. Not normalised: see +the module docstring. -/ +noncomputable def jtr : J →ₗ[ℝ] ℝ := (LinearMap.trace ℝ J).comp mulLₗ + +@[simp] theorem jtr_apply (x : J) : jtr x = LinearMap.trace ℝ J (mulL x) := rfl + +/-- **The Jordan trace form** `τ(x, y) = tr(L_{x * y})`, bundled as an `ℝ`-bilinear form. + +Bilinearity is not a theorem below because it is the *type*: the four `mk₂` fields are additivity +and homogeneity in each argument, and they are immediate from linearity of `jtr` and +bilinearity of the product. -/ +noncomputable def traceForm : J →ₗ[ℝ] J →ₗ[ℝ] ℝ := + LinearMap.mk₂ ℝ (fun x y => jtr (x * y)) + (fun x x' y => by rw [add_mul, map_add]) + (fun r x y => by rw [smul_mul_assoc, map_smul, smul_eq_mul]) + (fun x y y' => by rw [mul_add, map_add]) + (fun r x y => by rw [mul_smul_comm' r x y, map_smul, smul_eq_mul]) + +@[simp] theorem traceForm_apply (x y : J) : traceForm x y = jtr (x * y) := rfl + +/-- **The trace form is symmetric**: `τ(x, y) = τ(y, x)`. + +No Jordan identity, no finite dimension, no formal reality: this is commutativity of the product +underneath `jtr`, and it is registered at that generality deliberately. -/ +theorem traceForm_comm (x y : J) : traceForm x y = traceForm y x := + EuclideanJordan.traceForm_comm x y + +/-- **The trace form is associative**: `τ(x * y, z) = τ(y, x * z)`. + +This is the compatibility that the standard presentation of a Euclidean Jordan algebra *assumes* +of its inner product, here proved of a form manufactured from the multiplication alone. It is the +main theorem of this file. + +Note the hypotheses, which are weaker than one expects. Beyond the commutative product and the +`ℝ`-module structure only `IsCommJordan` — the Jordan identity — is assumed: **no finite +dimension, no formal reality, no unit, no positivity, no idempotents, no spectral theory.** +`LinearMap.trace` is total, so the statement is meaningful (and true) even when `J` has no finite +basis and every trace in sight is `0`. -/ +theorem traceForm_assoc [IsCommJordan J] (x y z : J) : + traceForm (x * y) z = traceForm y (x * z) := + EuclideanJordan.traceForm_assoc x y z + +/-- **The trace form is positive semidefinite**: `τ(x, x) ≥ 0`. + +`hfr` is formal reality: a vanishing sum of squares has vanishing summands. With +`Module.Finite ℝ J` it yields a spectral resolution `x = ∑ᵢ λᵢ qᵢ` into orthogonal idempotents, +whence `x * x = ∑ᵢ λᵢ² qᵢ` and `τ(x, x) = ∑ᵢ λᵢ² tr(L_{qᵢ})`; and for an idempotent `c` the Peirce +split `L_c = P₁(c) + ½ P_{1/2}(c)` writes `tr(L_c)` as a nonnegative combination of traces of +idempotent endomorphisms, which are the ranks of their ranges. + +Formal reality is essential: `ℂ` over `ℝ` is a finite-dimensional commutative associative Jordan +algebra with `τ(i, i) = -2`. See the module docstring, which is also honest about the weaker +role `Module.Finite ℝ J` plays in this particular statement. -/ +theorem traceForm_self_nonneg [IsCommJordan J] [Module.Finite ℝ J] + (hfr : ∀ (k : ℕ) (f : Fin k → J), (∑ i, f i * f i) = 0 → ∀ i, f i = 0) (x : J) : + 0 ≤ traceForm x x := by + have : EuclideanJordan.IsFormallyReal J := by + classical + refine ⟨fun {ι} s f hsum i hi => ?_⟩ + have key : (∑ k : Fin s.card, f (s.equivFin.symm k) * f (s.equivFin.symm k)) = 0 := by + rw [show (∑ k : Fin s.card, f (s.equivFin.symm k) * f (s.equivFin.symm k)) + = ∑ a : {y // y ∈ s}, f a * f a from + Equiv.sum_comp s.equivFin.symm (fun a : {y // y ∈ s} => f a * f a), + Finset.sum_coe_sort s (fun a => f a * f a)] + exact hsum + simpa using hfr s.card (fun k => f (s.equivFin.symm k)) key (s.equivFin ⟨i, hi⟩) + exact EuclideanJordan.traceForm_self_nonneg x + +/-- **The trace form is definite**: `τ(x, x) = 0 ↔ x = 0`. + +Together with `traceForm_comm`, `traceForm_assoc` and `traceForm_self_nonneg` this is the whole +of the assertion that `τ` is a symmetric associative positive definite bilinear form — the +Euclidean form supplied by the multiplication itself. It is only the form: unitality, which a +Euclidean Jordan algebra also requires, is neither assumed nor concluded here. + +The nontrivial direction is `→`. It rests on a sharpening of the estimate behind +`traceForm_self_nonneg`: for a **nonzero** idempotent `c` one has `tr(L_c) ≥ 1`, because +`P₁(c) c = c` makes the range of the Peirce projection `P₁(c)` nonzero, hence of rank at least +one. So a vanishing `∑ᵢ λᵢ² tr(L_{qᵢ})` kills every `λᵢ` whose idempotent is nonzero, and the +terms with `qᵢ = 0` contribute nothing to `x` anyway. -/ +theorem traceForm_self_eq_zero_iff [IsCommJordan J] [Module.Finite ℝ J] + (hfr : ∀ (k : ℕ) (f : Fin k → J), (∑ i, f i * f i) = 0 → ∀ i, f i = 0) (x : J) : + traceForm x x = 0 ↔ x = 0 := by + have : EuclideanJordan.IsFormallyReal J := by + classical + refine ⟨fun {ι} s f hsum i hi => ?_⟩ + have key : (∑ k : Fin s.card, f (s.equivFin.symm k) * f (s.equivFin.symm k)) = 0 := by + rw [show (∑ k : Fin s.card, f (s.equivFin.symm k) * f (s.equivFin.symm k)) + = ∑ a : {y // y ∈ s}, f a * f a from + Equiv.sum_comp s.equivFin.symm (fun a : {y // y ∈ s} => f a * f a), + Finset.sum_coe_sort s (fun a => f a * f a)] + exact hsum + simpa using hfr s.card (fun k => f (s.equivFin.symm k)) key (s.equivFin ⟨i, hi⟩) + exact EuclideanJordan.traceForm_self_eq_zero_iff x + +end JordanTraceForm diff --git a/LeanPool/projects.yml b/LeanPool/projects.yml index bdc7ab8604..8c9571b09b 100644 --- a/LeanPool/projects.yml +++ b/LeanPool/projects.yml @@ -10170,6 +10170,42 @@ projects: msc: - '90C35' - '05C21' + - slug: euclidean-jordan + title: Euclidean Jordan algebras and the frame Peirce decomposition + summary: 'The frame Peirce decomposition: relative to a Jordan frame, a Euclidean Jordan + algebra is the internal direct sum of the blocks V_ij over unordered pairs of frame indices, + and in finite dimension the diagonal blocks are lines, dim V_ii = 1 - the Faraut-Koranyi + table entry. The frame is carried as data, so frame conjugacy is sidestepped and nothing + here claims anything about the rank of J. Machine checked, not peer reviewed, no priority + claim.' + branch: nonassociative algebra + entry_module: LeanPool.EuclideanJordan + authors: + - Bryan Ehrlich + source: + url: https://github.com/ehrlich-b/euclidean-jordan + github_repo: ehrlich-b/euclidean-jordan + commit: a83407e4dbf73c77c31a2291c02f20b37a5ebbaf + license: Apache-2.0 + status: verified + main_declarations: + - EuclideanJordan.frameBlock_isInternal + main_results: + - declaration: EuclideanJordan.frameBlock_isInternal + informal: A Euclidean Jordan algebra is the internal direct sum of the Peirce blocks associated + with a Jordan frame. + - declaration: EuclideanJordan.finrank_frameBlock_diag + informal: In finite dimension, every diagonal block of a Jordan frame has dimension one. + tags: + - nonassociative-algebra + msc: + - 17C20 + - 17C27 + - 17C37 + - 17C65 + - 17A15 + - 46L70 + provenance: AI - slug: asymptotic-triangle-packing title: Finite near-regular hypergraph rounding summary: >-