X-Git-Url: http://gitweb.michael.orlitzky.com/?a=blobdiff_plain;f=mjo%2Feja%2Feja_algebra.py;h=9198a8b124e041e4edcc4351271db01345accaa0;hb=9dd1e4c84fa17c8fe9d758a4fec9e090965c5cb9;hp=dfb15c627fe021a3d3b47348a42ea56dc666e6fc;hpb=8698debba196d8746c1a32d8e6866085b6cb2161;p=sage.d.git diff --git a/mjo/eja/eja_algebra.py b/mjo/eja/eja_algebra.py index dfb15c6..9198a8b 100644 --- a/mjo/eja/eja_algebra.py +++ b/mjo/eja/eja_algebra.py @@ -26,13 +26,30 @@ lazy_import('mjo.eja.eja_subalgebra', from mjo.eja.eja_utils import _mat2vec class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): - # This is an ugly hack needed to prevent the category framework - # from implementing a coercion from our base ring (e.g. the - # rationals) into the algebra. First of all -- such a coercion is - # nonsense to begin with. But more importantly, it tries to do so - # in the category of rings, and since our algebras aren't - # associative they generally won't be rings. - _no_generic_basering_coercion = True + + def _coerce_map_from_base_ring(self): + """ + Disable the map from the base ring into the algebra. + + Performing a nonsense conversion like this automatically + is counterpedagogical. The fallback is to try the usual + element constructor, which should also fail. + + SETUP:: + + sage: from mjo.eja.eja_algebra import random_eja + + TESTS:: + + sage: set_random_seed() + sage: J = random_eja() + sage: J(1) + Traceback (most recent call last): + ... + ValueError: not a naturally-represented algebra element + + """ + return None def __init__(self, field, @@ -94,8 +111,10 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): # long run to have the multiplication table be in terms of # algebra elements. We do this after calling the superclass # constructor so that from_vector() knows what to do. - self._multiplication_table = [ map(lambda x: self.from_vector(x), ls) - for ls in mult_table ] + self._multiplication_table = [ + list(map(lambda x: self.from_vector(x), ls)) + for ls in mult_table + ] def _element_constructor_(self, elt): @@ -109,7 +128,7 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): SETUP:: sage: from mjo.eja.eja_algebra import (JordanSpinEJA, - ....: RealCartesianProductEJA, + ....: HadamardEJA, ....: RealSymmetricEJA) EXAMPLES: @@ -137,7 +156,7 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): vector representations) back and forth faithfully:: sage: set_random_seed() - sage: J = RealCartesianProductEJA.random_instance() + sage: J = HadamardEJA.random_instance() sage: x = J.random_element() sage: J(x.to_vector().column()) == x True @@ -147,15 +166,22 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): True """ + msg = "not a naturally-represented algebra element" if elt == 0: # The superclass implementation of random_element() # needs to be able to coerce "0" into the algebra. return self.zero() + elif elt in self.base_ring(): + # Ensure that no base ring -> algebra coercion is performed + # by this method. There's some stupidity in sage that would + # otherwise propagate to this method; for example, sage thinks + # that the integer 3 belongs to the space of 2-by-2 matrices. + raise ValueError(msg) natural_basis = self.natural_basis() basis_space = natural_basis[0].matrix_space() if elt not in basis_space: - raise ValueError("not a naturally-represented algebra element") + raise ValueError(msg) # Thanks for nothing! Matrix spaces aren't vector spaces in # Sage, so we have to figure out its natural-basis coordinates @@ -580,12 +606,12 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): SETUP:: - sage: from mjo.eja.eja_algebra import (RealCartesianProductEJA, + sage: from mjo.eja.eja_algebra import (HadamardEJA, ....: random_eja) EXAMPLES:: - sage: J = RealCartesianProductEJA(5) + sage: J = HadamardEJA(5) sage: J.one() e0 + e1 + e2 + e3 + e4 @@ -747,6 +773,105 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): 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: + 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!") + + def random_elements(self, count): """ Return ``count`` random elements as a tuple. @@ -901,8 +1026,7 @@ class KnownRankEJA(object): return cls(n, field, **kwargs) -class RealCartesianProductEJA(FiniteDimensionalEuclideanJordanAlgebra, - KnownRankEJA): +class HadamardEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA): """ Return the Euclidean Jordan Algebra corresponding to the set `R^n` under the Hadamard product. @@ -913,13 +1037,13 @@ class RealCartesianProductEJA(FiniteDimensionalEuclideanJordanAlgebra, SETUP:: - sage: from mjo.eja.eja_algebra import RealCartesianProductEJA + sage: from mjo.eja.eja_algebra import HadamardEJA EXAMPLES: This multiplication table can be verified by hand:: - sage: J = RealCartesianProductEJA(3) + sage: J = HadamardEJA(3) sage: e0,e1,e2 = J.gens() sage: e0*e0 e0 @@ -938,7 +1062,7 @@ class RealCartesianProductEJA(FiniteDimensionalEuclideanJordanAlgebra, We can change the generator prefix:: - sage: RealCartesianProductEJA(3, prefix='r').gens() + sage: HadamardEJA(3, prefix='r').gens() (r0, r1, r2) """ @@ -947,7 +1071,7 @@ class RealCartesianProductEJA(FiniteDimensionalEuclideanJordanAlgebra, mult_table = [ [ V.gen(i)*(i == j) for j in range(n) ] for i in range(n) ] - fdeja = super(RealCartesianProductEJA, self) + fdeja = super(HadamardEJA, self) return fdeja.__init__(field, mult_table, rank=n, **kwargs) def inner_product(self, x, y): @@ -956,7 +1080,7 @@ class RealCartesianProductEJA(FiniteDimensionalEuclideanJordanAlgebra, SETUP:: - sage: from mjo.eja.eja_algebra import RealCartesianProductEJA + sage: from mjo.eja.eja_algebra import HadamardEJA TESTS: @@ -964,7 +1088,7 @@ class RealCartesianProductEJA(FiniteDimensionalEuclideanJordanAlgebra, over `R^n`:: sage: set_random_seed() - sage: J = RealCartesianProductEJA.random_instance() + sage: J = HadamardEJA.random_instance() sage: x,y = J.random_elements(2) sage: X = x.natural_representation() sage: Y = y.natural_representation() @@ -1874,11 +1998,129 @@ class QuaternionHermitianEJA(QuaternionMatrixEuclideanJordanAlgebra, super(QuaternionHermitianEJA,self).__init__(field, basis, n, **kwargs) -class JordanSpinEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA): +class BilinearFormEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA): + r""" + The rank-2 simple EJA consisting of real vectors ``x=(x0, x_bar)`` + with the half-trace inner product and jordan product ``x*y = + (x0*y0 + , x0*y_bar + y0*x_bar)`` where ``B`` is a + symmetric positive-definite "bilinear form" matrix. It has + dimension `n` over the reals, and reduces to the ``JordanSpinEJA`` + when ``B`` is the identity matrix of order ``n-1``. + + SETUP:: + + sage: from mjo.eja.eja_algebra import (BilinearFormEJA, + ....: JordanSpinEJA) + + EXAMPLES: + + When no bilinear form is specified, the identity matrix is used, + and the resulting algebra is the Jordan spin algebra:: + + sage: J0 = BilinearFormEJA(3) + sage: J1 = JordanSpinEJA(3) + sage: J0.multiplication_table() == J0.multiplication_table() + True + + TESTS: + + We can create a zero-dimensional algebra:: + + sage: J = BilinearFormEJA(0) + sage: J.basis() + Finite family {} + + We can check the multiplication condition given in the Jordan, von + Neumann, and Wigner paper (and also discussed on my "On the + symmetry..." paper). Note that this relies heavily on the standard + choice of basis, as does anything utilizing the bilinear form matrix:: + + sage: set_random_seed() + sage: n = ZZ.random_element(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: actual == expected + True + """ + def __init__(self, n, field=QQ, B=None, **kwargs): + if B is None: + self._B = matrix.identity(field, max(0,n-1)) + else: + self._B = B + + 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:] + z0 = x0*y0 + (self._B*xbar).inner_product(ybar) + zbar = y0*xbar + x0*ybar + z = V([z0] + zbar.list()) + mult_table[i][j] = z + + # The rank of this algebra is two, unless we're in a + # one-dimensional ambient space (because the rank is bounded + # by the ambient dimension). + fdeja = super(BilinearFormEJA, self) + return fdeja.__init__(field, mult_table, rank=min(n,2), **kwargs) + + def inner_product(self, x, y): + r""" + Half of the trace inner product. + + This is defined so that the special case of the Jordan spin + algebra gets the usual inner product. + + SETUP:: + + sage: from mjo.eja.eja_algebra import BilinearFormEJA + + TESTS: + + 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(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: x = J.random_element() + sage: y = J.random_element() + sage: x.inner_product(y) == (x*y).trace()/2 + True + + """ + xvec = x.to_vector() + xbar = xvec[1:] + yvec = y.to_vector() + ybar = yvec[1:] + return x[0]*y[0] + (self._B*xbar).inner_product(ybar) + + +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 = - (, x0*y_bar + y0*x_bar)``. It has dimension `n` over + (, x0*y_bar + y0*x_bar)``. It has dimension `n` over the reals. SETUP:: @@ -1911,42 +2153,9 @@ class JordanSpinEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA): 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() @@ -1956,8 +2165,11 @@ class JordanSpinEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA): 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):