return (J0, J5, J1)
+ def orthogonal_idempotents(self):
+ r"""
+ Generate a set of `r` orthogonal idempotents for this algebra,
+ where `r` is its rank.
+
+ 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.
+ """
+ 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:
+ pass
+ if len(subalgebras) >= 2:
+ # apply this method recursively.
+ return tuple( c.superalgebra_element()
+ for subalgebra in subalgebras
+ for c in subalgebra.orthogonal_idempotents() )
+
+ # 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!")
+
+
def random_elements(self, count):
"""
Return ``count`` random elements as a tuple.
TESTS:
- Ensure that this is one-half of the trace inner-product::
+ Ensure that this is one-half of the trace inner-product when
+ the algebra isn't just the reals (when ``n`` isn't one). This
+ is in Faraut and Koranyi, and also my "On the symmetry..."
+ paper::
sage: set_random_seed()
- sage: n = ZZ.random_element(5)
- sage: M = matrix.random(QQ, n-1, algorithm='unimodular')
+ sage: n = ZZ.random_element(2,5)
+ sage: M = matrix.random(QQ, max(0,n-1), algorithm='unimodular')
sage: B = M.transpose()*M
sage: J = BilinearFormEJA(n, B=B)
- sage: eis = VectorSpace(M.base_ring(), M.ncols()).basis()
- sage: V = J.vector_space()
- sage: sis = [ J.from_vector(V([0] + (M.inverse()*ei).list()))
- ....: for ei in eis ]
- sage: actual = [ sis[i]*sis[j]
- ....: for i in range(n-1)
- ....: for j in range(n-1) ]
- sage: expected = [ J.one() if i == j else J.zero()
- ....: for i in range(n-1)
- ....: for j in range(n-1) ]
+ sage: x = J.random_element()
+ sage: y = J.random_element()
+ sage: x.inner_product(y) == (x*y).trace()/2
+ True
"""
xvec = x.to_vector()
ybar = yvec[1:]
return x[0]*y[0] + (self._B*xbar).inner_product(ybar)
-class JordanSpinEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA):
+
+class JordanSpinEJA(BilinearFormEJA):
"""
The rank-2 simple EJA consisting of real vectors ``x=(x0, x_bar)``
with the usual inner product and jordan product ``x*y =
sage: JordanSpinEJA(2, prefix='B').gens()
(B0, B1)
- """
- def __init__(self, n, field=QQ, **kwargs):
- V = VectorSpace(field, n)
- mult_table = [[V.zero() for j in range(n)] for i in range(n)]
- for i in range(n):
- for j in range(n):
- x = V.gen(i)
- y = V.gen(j)
- x0 = x[0]
- xbar = x[1:]
- y0 = y[0]
- ybar = y[1:]
- # z = x*y
- z0 = x.inner_product(y)
- zbar = y0*xbar + x0*ybar
- z = V([z0] + zbar.list())
- mult_table[i][j] = z
-
- # The rank of the spin algebra is two, unless we're in a
- # one-dimensional ambient space (because the rank is bounded by
- # the ambient dimension).
- fdeja = super(JordanSpinEJA, self)
- return fdeja.__init__(field, mult_table, rank=min(n,2), **kwargs)
-
- def inner_product(self, x, y):
- """
- Faster to reimplement than to use natural representations.
-
- SETUP::
-
- sage: from mjo.eja.eja_algebra import JordanSpinEJA
-
- TESTS:
+ TESTS:
- Ensure that this is the usual inner product for the algebras
- over `R^n`::
+ Ensure that we have the usual inner product on `R^n`::
sage: set_random_seed()
sage: J = JordanSpinEJA.random_instance()
sage: x.inner_product(y) == J.natural_inner_product(X,Y)
True
- """
- return x.to_vector().inner_product(y.to_vector())
+ """
+ def __init__(self, n, field=QQ, **kwargs):
+ # This is a special case of the BilinearFormEJA with the identity
+ # matrix as its bilinear form.
+ return super(JordanSpinEJA, self).__init__(n, field, **kwargs)
class TrivialEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA):