+ def peirce_decomposition(self, c):
+ """
+ The Peirce decomposition of this algebra relative to the
+ idempotent ``c``.
+
+ In the future, this can be extended to a complete system of
+ orthogonal idempotents.
+
+ INPUT:
+
+ - ``c`` -- an idempotent of this algebra.
+
+ OUTPUT:
+
+ A triple (J0, J5, J1) containing two subalgebras and one subspace
+ of this algebra,
+
+ - ``J0`` -- the algebra on the eigenspace of ``c.operator()``
+ corresponding to the eigenvalue zero.
+
+ - ``J5`` -- the eigenspace (NOT a subalgebra) of ``c.operator()``
+ corresponding to the eigenvalue one-half.
+
+ - ``J1`` -- the algebra on the eigenspace of ``c.operator()``
+ corresponding to the eigenvalue one.
+
+ These are the only possible eigenspaces for that operator, and this
+ algebra is a direct sum of them. The spaces ``J0`` and ``J1`` are
+ orthogonal, and are subalgebras of this algebra with the appropriate
+ restrictions.
+
+ SETUP::
+
+ sage: from mjo.eja.eja_algebra import random_eja, RealSymmetricEJA
+
+ EXAMPLES:
+
+ The canonical example comes from the symmetric matrices, which
+ decompose into diagonal and off-diagonal parts::
+
+ sage: J = RealSymmetricEJA(3)
+ sage: C = matrix(QQ, [ [1,0,0],
+ ....: [0,1,0],
+ ....: [0,0,0] ])
+ sage: c = J(C)
+ sage: J0,J5,J1 = J.peirce_decomposition(c)
+ sage: J0
+ Euclidean Jordan algebra of dimension 1...
+ sage: J5
+ Vector space of degree 6 and dimension 2...
+ sage: J1
+ Euclidean Jordan algebra of dimension 3...
+
+ TESTS:
+
+ Every algebra decomposes trivially with respect to its identity
+ element::
+
+ sage: set_random_seed()
+ sage: J = random_eja()
+ sage: J0,J5,J1 = J.peirce_decomposition(J.one())
+ sage: J0.dimension() == 0 and J5.dimension() == 0
+ True
+ sage: J1.superalgebra() == J and J1.dimension() == J.dimension()
+ True
+
+ The identity elements in the two subalgebras are the
+ projections onto their respective subspaces of the
+ superalgebra's identity element::
+
+ sage: set_random_seed()
+ sage: J = random_eja()
+ sage: x = J.random_element()
+ sage: if not J.is_trivial():
+ ....: while x.is_nilpotent():
+ ....: x = J.random_element()
+ sage: c = x.subalgebra_idempotent()
+ sage: J0,J5,J1 = J.peirce_decomposition(c)
+ sage: J1(c) == J1.one()
+ True
+ sage: J0(J.one() - c) == J0.one()
+ True
+
+ """
+ if not c.is_idempotent():
+ raise ValueError("element is not idempotent: %s" % c)
+
+ # Default these to what they should be if they turn out to be
+ # trivial, because eigenspaces_left() won't return eigenvalues
+ # corresponding to trivial spaces (e.g. it returns only the
+ # eigenspace corresponding to lambda=1 if you take the
+ # decomposition relative to the identity element).
+ trivial = FiniteDimensionalEuclideanJordanSubalgebra(self, ())
+ J0 = trivial # eigenvalue zero
+ J5 = VectorSpace(self.base_ring(), 0) # eigenvalue one-half
+ J1 = trivial # eigenvalue one
+
+ for (eigval, eigspace) in c.operator().matrix().right_eigenspaces():
+ if eigval == ~(self.base_ring()(2)):
+ J5 = eigspace
+ else:
+ gens = tuple( self.from_vector(b) for b in eigspace.basis() )
+ subalg = FiniteDimensionalEuclideanJordanSubalgebra(self, gens)
+ if eigval == 0:
+ J0 = subalg
+ elif eigval == 1:
+ J1 = subalg
+ else:
+ raise ValueError("unexpected eigenvalue: %s" % eigval)
+
+ return (J0, J5, J1)
+
+
+ def a_jordan_frame(self):
+ r"""
+ Generate a Jordan frame for this algebra.
+
+ This implementation is based on the so-called "central
+ orthogonal idempotents" implemented for (semisimple) centers
+ of SageMath ``FiniteDimensionalAlgebrasWithBasis``. Since all
+ Euclidean Jordan algebas are commutative (and thus equal to
+ their own centers) and semisimple, the method should work more
+ or less as implemented, if it ever worked in the first place.
+ (I don't know the justification for the original implementation.
+ yet).
+
+ How it works: we loop through the algebras generators, looking
+ for their eigenspaces. If there's more than one eigenspace,
+ and if they result in more than one subalgebra, then we split
+ those subalgebras recursively until we get to subalgebras of
+ dimension one (whose idempotent is the unit element). Why does
+ some generator have to produce at least two subalgebras? I
+ dunno. But it seems to work.
+
+ Beware that Koecher defines the "center" of a Jordan algebra to
+ be something else, because the usual definition is stupid in a
+ (necessarily commutative) Jordan algebra.
+
+ SETUP::
+
+ sage: from mjo.eja.eja_algebra import (random_eja,
+ ....: JordanSpinEJA,
+ ....: TrivialEJA)
+
+ EXAMPLES:
+
+ A Jordan frame for the trivial algebra has to be empty
+ (zero-length) since its rank is zero. More to the point, there
+ are no non-zero idempotents in the trivial EJA. This does not
+ cause any problems so long as we adopt the convention that the
+ empty sum is zero, since then the sole element of the trivial
+ EJA has an (empty) spectral decomposition::
+
+ sage: J = TrivialEJA()
+ sage: J.a_jordan_frame()
+ ()
+
+ A one-dimensional algebra has rank one (equal to its dimension),
+ and only one primitive idempotent, namely the algebra's unit
+ element::
+
+ sage: J = JordanSpinEJA(1)
+ sage: J.a_jordan_frame()
+ (e0,)
+
+ TESTS::
+
+ sage: J = random_eja()
+ sage: c = J.a_jordan_frame()
+ sage: all( x^2 == x for x in c )
+ True
+ sage: r = len(c)
+ sage: all( c[i]*c[j] == c[i]*(i==j) for i in range(r)
+ ....: for j in range(r) )
+ True
+
+ """
+ if self.dimension() == 0:
+ return ()
+ if self.dimension() == 1:
+ return (self.one(),)
+
+ for g in self.gens():
+ eigenpairs = g.operator().matrix().right_eigenspaces()
+ if len(eigenpairs) >= 2:
+ subalgebras = []
+ for eigval, eigspace in eigenpairs:
+ # Make sub-EJAs from the matrix eigenspaces...
+ sb = tuple( self.from_vector(b) for b in eigspace.basis() )
+ try:
+ # This will fail if e.g. the eigenspace basis
+ # contains two elements and their product
+ # isn't a linear combination of the two of
+ # them (i.e. the generated EJA isn't actually
+ # two dimensional).
+ s = FiniteDimensionalEuclideanJordanSubalgebra(self, sb)
+ subalgebras.append(s)
+ except ArithmeticError as e:
+ if str(e) == "vector is not in free module":
+ # Ignore only the "not a sub-EJA" error
+ pass
+
+ if len(subalgebras) >= 2:
+ # apply this method recursively.
+ return tuple( c.superalgebra_element()
+ for subalgebra in subalgebras
+ for c in subalgebra.a_jordan_frame() )
+
+ # If we got here, the algebra didn't decompose, at least not when we looked at
+ # the eigenspaces corresponding only to basis elements of the algebra. The
+ # implementation I stole says that this should work because of Schur's Lemma,
+ # so I personally blame Schur's Lemma if it does not.
+ raise Exception("Schur's Lemma didn't work!")
+