X-Git-Url: http://gitweb.michael.orlitzky.com/?a=blobdiff_plain;f=mjo%2Feja%2Feja_algebra.py;h=ff2b5d7a9c52ff5c13df7d7aa3682899b9a30559;hb=cd164e717e7224ec1af16d31152555f0c7cf49cf;hp=0a260653a7c5480acea6ffc00a9158169e4e7a18;hpb=98da0ce1d1102057e34646889c10dfa01fa9faec;p=sage.d.git diff --git a/mjo/eja/eja_algebra.py b/mjo/eja/eja_algebra.py index 0a26065..ff2b5d7 100644 --- a/mjo/eja/eja_algebra.py +++ b/mjo/eja/eja_algebra.py @@ -773,108 +773,6 @@ 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 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!") - - def random_elements(self, count): """ Return ``count`` random elements as a tuple. @@ -893,19 +791,21 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): True """ - return tuple( self.random_element() for idx in range(count) ) - + return tuple( self.random_element() for idx in range(count) ) + @cached_method def rank(self): """ Return the rank of this EJA. ALGORITHM: - The author knows of no algorithm to compute the rank of an EJA - where only the multiplication table is known. In lieu of one, we - require the rank to be specified when the algebra is created, - and simply pass along that number here. + We first compute the polynomial "column matrices" `p_{k}` that + evaluate to `x^k` on the coordinates of `x`. Then, we begin + adding them to a matrix one at a time, and trying to solve the + system that makes `p_{0}`,`p_{1}`,..., `p_{s-1}` add up to + `p_{s}`. This will succeed only when `s` is the rank of the + algebra, as proven in a recent draft paper of mine. SETUP:: @@ -950,8 +850,80 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): sage: r > 0 or (r == 0 and J.is_trivial()) True + Ensure that computing the rank actually works, since the ranks + of all simple algebras are known and will be cached by default:: + + sage: J = HadamardEJA(4) + sage: J.rank.clear_cache() + sage: J.rank() + 4 + + :: + + sage: J = JordanSpinEJA(4) + sage: J.rank.clear_cache() + sage: J.rank() + 2 + + :: + + sage: J = RealSymmetricEJA(3) + sage: J.rank.clear_cache() + sage: J.rank() + 3 + + :: + + sage: J = ComplexHermitianEJA(2) + sage: J.rank.clear_cache() + sage: J.rank() + 2 + + :: + + sage: J = QuaternionHermitianEJA(2) + sage: J.rank.clear_cache() + sage: J.rank() + 2 + """ - return self._rank + n = self.dimension() + if n == 0: + return 0 + elif n == 1: + return 1 + + var_names = [ "X" + str(z) for z in range(1,n+1) ] + R = PolynomialRing(self.base_ring(), var_names) + vars = R.gens() + + def L_x_i_j(i,j): + # From a result in my book, these are the entries of the + # basis representation of L_x. + return sum( vars[k]*self.monomial(k).operator().matrix()[i,j] + for k in range(n) ) + + L_x = matrix(R, n, n, L_x_i_j) + x_powers = [ vars[k]*(L_x**k)*self.one().to_vector() + for k in range(n) ] + + # Can assume n >= 2 + M = matrix([x_powers[0]]) + old_rank = 1 + + for d in range(1,n): + M = matrix(M.rows() + [x_powers[d]]) + M.echelonize() + # TODO: we've basically solved the system here. + # We should save the echelonized matrix somehow + # so that it can be reused in the charpoly method. + new_rank = M.rank() + if new_rank == old_rank: + return new_rank + else: + old_rank = new_rank + + return n def vector_space(self): @@ -1171,6 +1143,28 @@ class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra): **kwargs) + def _rank_computation(self): + r""" + Override the parent method with something that tries to compute + over a faster (non-extension) field. + """ + if self._basis_normalizers is None: + # We didn't normalize, so assume that the basis we started + # with had entries in a nice field. + return super(MatrixEuclideanJordanAlgebra, self)._rank_computation() + else: + basis = ( (b/n) for (b,n) in zip(self.natural_basis(), + self._basis_normalizers) ) + + # Do this over the rationals and convert back at the end. + # Only works because we know the entries of the basis are + # integers. + J = MatrixEuclideanJordanAlgebra(QQ, + basis, + self.rank(), + normalize_basis=False) + return J._rank_computation() + @cached_method def _charpoly_coeff(self, i): """