X-Git-Url: http://gitweb.michael.orlitzky.com/?a=blobdiff_plain;f=mjo%2Feja%2Feja_algebra.py;h=d1c0fa2eafd5d3d5e17ccab175e16ee44c3e9d82;hb=417406c6a509462be431961a3dd1f13829b7cd96;hp=ff2b5d7a9c52ff5c13df7d7aa3682899b9a30559;hpb=cd164e717e7224ec1af16d31152555f0c7cf49cf;p=sage.d.git diff --git a/mjo/eja/eja_algebra.py b/mjo/eja/eja_algebra.py index ff2b5d7..d1c0fa2 100644 --- a/mjo/eja/eja_algebra.py +++ b/mjo/eja/eja_algebra.py @@ -54,7 +54,6 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): def __init__(self, field, mult_table, - rank, prefix='e', category=None, natural_basis=None, @@ -91,7 +90,6 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): # a real embedding. raise ValueError('field is not real') - self._rank = rank self._natural_basis = natural_basis if category is None: @@ -194,6 +192,24 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): coords = W.coordinate_vector(_mat2vec(elt)) return self.from_vector(coords) + @staticmethod + def _max_test_case_size(): + """ + Return an integer "size" that is an upper bound on the size of + this algebra when it is used in a random test + case. Unfortunately, the term "size" is quite vague -- when + dealing with `R^n` under either the Hadamard or Jordan spin + product, the "size" refers to the dimension `n`. When dealing + with a matrix algebra (real symmetric or complex/quaternion + Hermitian), it refers to the size of the matrix, which is + far less than the dimension of the underlying vector space. + + We default to five in this class, which is safe in `R^n`. The + matrix algebra subclasses (or any class where the "size" is + interpreted to be far less than the dimension) should override + with a smaller number. + """ + return 5 def _repr_(self): """ @@ -793,9 +809,62 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): """ return tuple( self.random_element() for idx in range(count) ) + @classmethod + def random_instance(cls, field=AA, **kwargs): + """ + Return a random instance of this type of algebra. + + Beware, this will crash for "most instances" because the + constructor below looks wrong. + """ + if cls is TrivialEJA: + # The TrivialEJA class doesn't take an "n" argument because + # there's only one. + return cls(field) + + n = ZZ.random_element(cls._max_test_case_size()) + 1 + return cls(n, field, **kwargs) + @cached_method - def rank(self): + def _charpoly_coefficients(self): + r""" + The `r` polynomial coefficients of the "characteristic polynomial + of" function. """ + n = self.dimension() + var_names = [ "X" + str(z) for z in range(1,n+1) ] + R = PolynomialRing(self.base_ring(), var_names) + vars = R.gens() + F = R.fraction_field() + + 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(F, n, n, L_x_i_j) + # Compute an extra power in case the rank is equal to + # the dimension (otherwise, we would stop at x^(r-1)). + x_powers = [ (L_x**k)*self.one().to_vector() + for k in range(n+1) ] + A = matrix.column(F, x_powers[:n]) + AE = A.extended_echelon_form() + E = AE[:,n:] + A_rref = AE[:,:n] + r = A_rref.rank() + b = x_powers[r] + + # The theory says that only the first "r" coefficients are + # nonzero, and they actually live in the original polynomial + # ring and not the fraction field. We negate them because + # in the actual characteristic polynomial, they get moved + # to the other side where x^r lives. + return -A_rref.solve_right(E*b).change_ring(R)[:r] + + @cached_method + def rank(self): + r""" Return the rank of this EJA. ALGORITHM: @@ -809,7 +878,8 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): SETUP:: - sage: from mjo.eja.eja_algebra import (JordanSpinEJA, + sage: from mjo.eja.eja_algebra import (HadamardEJA, + ....: JordanSpinEJA, ....: RealSymmetricEJA, ....: ComplexHermitianEJA, ....: QuaternionHermitianEJA, @@ -887,43 +957,7 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): 2 """ - 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 + return len(self._charpoly_coefficients()) def vector_space(self): @@ -947,61 +981,7 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): Element = FiniteDimensionalEuclideanJordanAlgebraElement -class KnownRankEJA(object): - """ - A class for algebras that we actually know we can construct. The - main issue is that, for most of our methods to make sense, we need - to know the rank of our algebra. Thus we can't simply generate a - "random" algebra, or even check that a given basis and product - satisfy the axioms; because even if everything looks OK, we wouldn't - know the rank we need to actuallty build the thing. - - Not really a subclass of FDEJA because doing that causes method - resolution errors, e.g. - - TypeError: Error when calling the metaclass bases - Cannot create a consistent method resolution - order (MRO) for bases FiniteDimensionalEuclideanJordanAlgebra, - KnownRankEJA - - """ - @staticmethod - def _max_test_case_size(): - """ - Return an integer "size" that is an upper bound on the size of - this algebra when it is used in a random test - case. Unfortunately, the term "size" is quite vague -- when - dealing with `R^n` under either the Hadamard or Jordan spin - product, the "size" refers to the dimension `n`. When dealing - with a matrix algebra (real symmetric or complex/quaternion - Hermitian), it refers to the size of the matrix, which is - far less than the dimension of the underlying vector space. - - We default to five in this class, which is safe in `R^n`. The - matrix algebra subclasses (or any class where the "size" is - interpreted to be far less than the dimension) should override - with a smaller number. - """ - return 5 - - @classmethod - def random_instance(cls, field=AA, **kwargs): - """ - Return a random instance of this type of algebra. - - Beware, this will crash for "most instances" because the - constructor below looks wrong. - """ - if cls is TrivialEJA: - # The TrivialEJA class doesn't take an "n" argument because - # there's only one. - return cls(field) - - n = ZZ.random_element(cls._max_test_case_size()) + 1 - return cls(n, field, **kwargs) - - -class HadamardEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA): +class HadamardEJA(FiniteDimensionalEuclideanJordanAlgebra): """ Return the Euclidean Jordan Algebra corresponding to the set `R^n` under the Hadamard product. @@ -1047,7 +1027,8 @@ class HadamardEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA): for i in range(n) ] fdeja = super(HadamardEJA, self) - return fdeja.__init__(field, mult_table, rank=n, **kwargs) + fdeja.__init__(field, mult_table, **kwargs) + self.rank.set_cache(n) def inner_product(self, x, y): """ @@ -1088,9 +1069,13 @@ def random_eja(field=AA, nontrivial=False): Euclidean Jordan algebra of dimension... """ - eja_classes = KnownRankEJA.__subclasses__() - if nontrivial: - eja_classes.remove(TrivialEJA) + eja_classes = [HadamardEJA, + JordanSpinEJA, + RealSymmetricEJA, + ComplexHermitianEJA, + QuaternionHermitianEJA] + if not nontrivial: + eja_classes.append(TrivialEJA) classname = choice(eja_classes) return classname.random_instance(field=field) @@ -1106,7 +1091,7 @@ class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra): # field can have dimension 4 (quaternions) too. return 2 - def __init__(self, field, basis, rank, normalize_basis=True, **kwargs): + def __init__(self, field, basis, normalize_basis=True, **kwargs): """ Compared to the superclass constructor, we take a basis instead of a multiplication table because the latter can be computed in terms @@ -1119,7 +1104,7 @@ class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra): # time to ensure that it isn't a generator expression. basis = tuple(basis) - if rank > 1 and normalize_basis: + if len(basis) > 1 and normalize_basis: # We'll need sqrt(2) to normalize the basis, and this # winds up in the multiplication table, so the whole # algebra needs to be over the field extension. @@ -1136,14 +1121,12 @@ class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra): Qs = self.multiplication_table_from_matrix_basis(basis) fdeja = super(MatrixEuclideanJordanAlgebra, self) - return fdeja.__init__(field, - Qs, - rank=rank, - natural_basis=basis, - **kwargs) + fdeja.__init__(field, Qs, natural_basis=basis, **kwargs) + return - def _rank_computation(self): + @cached_method + def rank(self): r""" Override the parent method with something that tries to compute over a faster (non-extension) field. @@ -1151,7 +1134,7 @@ class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra): 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() + return super(MatrixEuclideanJordanAlgebra, self).rank() else: basis = ( (b/n) for (b,n) in zip(self.natural_basis(), self._basis_normalizers) ) @@ -1161,9 +1144,8 @@ class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra): # integers. J = MatrixEuclideanJordanAlgebra(QQ, basis, - self.rank(), normalize_basis=False) - return J._rank_computation() + return J.rank() @cached_method def _charpoly_coeff(self, i): @@ -1182,7 +1164,6 @@ class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra): # Do this over the rationals and convert back at the end. J = MatrixEuclideanJordanAlgebra(QQ, basis, - self.rank(), normalize_basis=False) (_,x,_,_) = J._charpoly_matrix_system() p = J._charpoly_coeff(i) @@ -1293,7 +1274,7 @@ class RealMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra): return M -class RealSymmetricEJA(RealMatrixEuclideanJordanAlgebra, KnownRankEJA): +class RealSymmetricEJA(RealMatrixEuclideanJordanAlgebra): """ The rank-n simple EJA consisting of real symmetric n-by-n matrices, the usual symmetric Jordan product, and the trace inner @@ -1411,7 +1392,8 @@ class RealSymmetricEJA(RealMatrixEuclideanJordanAlgebra, KnownRankEJA): def __init__(self, n, field=AA, **kwargs): basis = self._denormalized_basis(n, field) - super(RealSymmetricEJA, self).__init__(field, basis, n, **kwargs) + super(RealSymmetricEJA, self).__init__(field, basis, **kwargs) + self.rank.set_cache(n) class ComplexMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra): @@ -1573,7 +1555,7 @@ class ComplexMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra): return RealMatrixEuclideanJordanAlgebra.natural_inner_product(X,Y)/2 -class ComplexHermitianEJA(ComplexMatrixEuclideanJordanAlgebra, KnownRankEJA): +class ComplexHermitianEJA(ComplexMatrixEuclideanJordanAlgebra): """ The rank-n simple EJA consisting of complex Hermitian n-by-n matrices over the real numbers, the usual symmetric Jordan product, @@ -1701,7 +1683,8 @@ class ComplexHermitianEJA(ComplexMatrixEuclideanJordanAlgebra, KnownRankEJA): def __init__(self, n, field=AA, **kwargs): basis = self._denormalized_basis(n,field) - super(ComplexHermitianEJA,self).__init__(field, basis, n, **kwargs) + super(ComplexHermitianEJA,self).__init__(field, basis, **kwargs) + self.rank.set_cache(n) class QuaternionMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra): @@ -1867,8 +1850,7 @@ class QuaternionMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra): return RealMatrixEuclideanJordanAlgebra.natural_inner_product(X,Y)/4 -class QuaternionHermitianEJA(QuaternionMatrixEuclideanJordanAlgebra, - KnownRankEJA): +class QuaternionHermitianEJA(QuaternionMatrixEuclideanJordanAlgebra): """ The rank-n simple EJA consisting of self-adjoint n-by-n quaternion matrices, the usual symmetric Jordan product, and the @@ -1997,10 +1979,11 @@ class QuaternionHermitianEJA(QuaternionMatrixEuclideanJordanAlgebra, def __init__(self, n, field=AA, **kwargs): basis = self._denormalized_basis(n,field) - super(QuaternionHermitianEJA,self).__init__(field, basis, n, **kwargs) + super(QuaternionHermitianEJA,self).__init__(field, basis, **kwargs) + self.rank.set_cache(n) -class BilinearFormEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA): +class BilinearFormEJA(FiniteDimensionalEuclideanJordanAlgebra): 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 = @@ -2080,7 +2063,8 @@ class BilinearFormEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA): # 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) + fdeja.__init__(field, mult_table, **kwargs) + self.rank.set_cache(min(n,2)) def inner_product(self, x, y): r""" @@ -2174,7 +2158,7 @@ class JordanSpinEJA(BilinearFormEJA): return super(JordanSpinEJA, self).__init__(n, field, **kwargs) -class TrivialEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA): +class TrivialEJA(FiniteDimensionalEuclideanJordanAlgebra): """ The trivial Euclidean Jordan algebra consisting of only a zero element. @@ -2208,4 +2192,5 @@ class TrivialEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA): fdeja = super(TrivialEJA, self) # The rank is zero using my definition, namely the dimension of the # largest subalgebra generated by any element. - return fdeja.__init__(field, mult_table, rank=0, **kwargs) + fdeja.__init__(field, mult_table, **kwargs) + self.rank.set_cache(0)