X-Git-Url: http://gitweb.michael.orlitzky.com/?a=blobdiff_plain;f=mjo%2Feja%2Feja_algebra.py;h=658957556cf382a62d0d252359ce735019996973;hb=08aba469c5f8d8947a543f8882fa676ed165e7ee;hp=79ccc7904830dc739e4af0a532c1bace2a801936;hpb=9528af011cbb4d5e6a38ef972e0d14e7928d5eef;p=sage.d.git diff --git a/mjo/eja/eja_algebra.py b/mjo/eja/eja_algebra.py index 79ccc79..6589575 100644 --- a/mjo/eja/eja_algebra.py +++ b/mjo/eja/eja_algebra.py @@ -16,13 +16,9 @@ from sage.misc.cachefunc import cached_method from sage.misc.prandom import choice from sage.misc.table import table from sage.modules.free_module import FreeModule, VectorSpace -from sage.rings.integer_ring import ZZ -from sage.rings.number_field.number_field import NumberField, QuadraticField -from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing -from sage.rings.rational_field import QQ -from sage.rings.real_lazy import CLF, RLF -from sage.structure.element import is_Matrix - +from sage.rings.all import (ZZ, QQ, RR, RLF, CLF, + PolynomialRing, + QuadraticField) from mjo.eja.eja_element import FiniteDimensionalEuclideanJordanAlgebraElement from mjo.eja.eja_utils import _mat2vec @@ -41,11 +37,12 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): rank, prefix='e', category=None, - natural_basis=None): + natural_basis=None, + check=True): """ SETUP:: - sage: from mjo.eja.eja_algebra import random_eja + sage: from mjo.eja.eja_algebra import (JordanSpinEJA, random_eja) EXAMPLES: @@ -57,7 +54,23 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): sage: x*y == y*x True + TESTS: + + The ``field`` we're given must be real:: + + sage: JordanSpinEJA(2,QQbar) + Traceback (most recent call last): + ... + ValueError: field is not real + """ + if check: + if not field.is_subring(RR): + # Note: this does return true for the real algebraic + # field, and any quadratic field where we've specified + # a real embedding. + raise ValueError('field is not real') + self._rank = rank self._natural_basis = natural_basis @@ -153,26 +166,6 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): 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): """ Return a string representation of ``self``. @@ -426,8 +419,8 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): EXAMPLES: - Our inner product satisfies the Jordan axiom, which is also - referred to as "associativity" for a symmetric bilinear form:: + Our inner product is "associative," which means the following for + a symmetric bilinear form:: sage: set_random_seed() sage: J = random_eja() @@ -456,9 +449,6 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): sage: J = ComplexHermitianEJA(3) sage: J.is_trivial() False - sage: A = J.zero().subalgebra_generated_by() - sage: A.is_trivial() - True """ return self.dimension() == 0 @@ -632,14 +622,6 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): return self.linear_combination(zip(self.gens(), coeffs)) - def random_element(self): - # Temporary workaround for https://trac.sagemath.org/ticket/28327 - if self.is_trivial(): - return self.zero() - else: - s = super(FiniteDimensionalEuclideanJordanAlgebra, self) - return s.random_element() - def random_elements(self, count): """ Return ``count`` random elements as a tuple. @@ -660,27 +642,6 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): """ return tuple( self.random_element() for idx in xrange(count) ) - @classmethod - def random_instance(cls, field=QQ, **kwargs): - """ - Return a random instance of this type of algebra. - - In subclasses for algebras that we know how to construct, this - is a shortcut for constructing test cases and examples. - """ - if cls is FiniteDimensionalEuclideanJordanAlgebra: - # Red flag! But in theory we could do this I guess. The - # only finite-dimensional exceptional EJA is the - # octononions. So, we could just create an EJA from an - # associative matrix algebra (generated by a subset of - # elements) with the symmetric product. Or, we could punt - # to random_eja() here, override it in our subclasses, and - # not worry about it. - raise NotImplementedError - - n = ZZ.random_element(cls._max_test_case_size()) + 1 - return cls(n, field, **kwargs) - def rank(self): """ @@ -757,7 +718,57 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): Element = FiniteDimensionalEuclideanJordanAlgebraElement -class RealCartesianProductEJA(FiniteDimensionalEuclideanJordanAlgebra): +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=QQ, **kwargs): + """ + Return a random instance of this type of algebra. + + Beware, this will crash for "most instances" because the + constructor below looks wrong. + """ + n = ZZ.random_element(cls._max_test_case_size()) + 1 + return cls(n, field, **kwargs) + + +class RealCartesianProductEJA(FiniteDimensionalEuclideanJordanAlgebra, + KnownRankEJA): """ Return the Euclidean Jordan Algebra corresponding to the set `R^n` under the Hadamard product. @@ -830,7 +841,7 @@ class RealCartesianProductEJA(FiniteDimensionalEuclideanJordanAlgebra): return x.to_vector().inner_product(y.to_vector()) -def random_eja(): +def random_eja(field=QQ): """ Return a "random" finite-dimensional Euclidean Jordan Algebra. @@ -866,12 +877,8 @@ def random_eja(): Euclidean Jordan algebra of dimension... """ - classname = choice([RealCartesianProductEJA, - JordanSpinEJA, - RealSymmetricEJA, - ComplexHermitianEJA, - QuaternionHermitianEJA]) - return classname.random_instance() + classname = choice(KnownRankEJA.__subclasses__()) + return classname.random_instance(field=field) @@ -885,17 +892,20 @@ class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra): # field can have dimension 4 (quaternions) too. return 2 - @classmethod - def _denormalized_basis(cls, n, field): - raise NotImplementedError - - def __init__(self, n, field=QQ, normalize_basis=True, **kwargs): - S = self._denormalized_basis(n, field) - + def __init__(self, field, basis, rank, 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 + of the former when the product is known (like it is here). + """ # Used in this class's fast _charpoly_coeff() override. self._basis_normalizers = None - if n > 1 and normalize_basis: + # We're going to loop through this a few times, so now's a good + # time to ensure that it isn't a generator expression. + basis = tuple(basis) + + if rank > 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. @@ -903,19 +913,19 @@ class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra): z = R.gen() p = z**2 - 2 if p.is_irreducible(): - field = NumberField(p, 'sqrt2', embedding=RLF(2).sqrt()) - S = [ s.change_ring(field) for s in S ] + field = field.extension(p, 'sqrt2', embedding=RLF(2).sqrt()) + basis = tuple( s.change_ring(field) for s in basis ) self._basis_normalizers = tuple( - ~(self.natural_inner_product(s,s).sqrt()) for s in S ) - S = tuple( s*c for (s,c) in zip(S,self._basis_normalizers) ) + ~(self.natural_inner_product(s,s).sqrt()) for s in basis ) + basis = tuple(s*c for (s,c) in izip(basis,self._basis_normalizers)) - Qs = self.multiplication_table_from_matrix_basis(S) + Qs = self.multiplication_table_from_matrix_basis(basis) fdeja = super(MatrixEuclideanJordanAlgebra, self) return fdeja.__init__(field, Qs, - rank=n, - natural_basis=S, + rank=rank, + natural_basis=basis, **kwargs) @@ -930,17 +940,27 @@ class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra): # with had entries in a nice field. return super(MatrixEuclideanJordanAlgebra, self)._charpoly_coeff(i) else: - # If we didn't unembed first, this number would be wrong - # by a power-of-two factor for complex/quaternion matrices. - n = self.real_unembed(self.natural_basis_space().zero()).nrows() - field = self.base_ring().base_ring() # yeeeeaaaahhh - J = self.__class__(n, field, False) + basis = ( (b/n) for (b,n) in izip(self.natural_basis(), + self._basis_normalizers) ) + + # 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) # p might be missing some vars, have to substitute "optionally" pairs = izip(x.base_ring().gens(), self._basis_normalizers) substitutions = { v: v*c for (v,c) in pairs } - return p.subs(substitutions) + result = p.subs(substitutions) + + # The result of "subs" can be either a coefficient-ring + # element or a polynomial. Gotta handle both cases. + if result in QQ: + return self.base_ring()(result) + else: + return result.change_ring(self.base_ring()) @staticmethod @@ -1003,6 +1023,7 @@ class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra): Xu = cls.real_unembed(X) Yu = cls.real_unembed(Y) tr = (Xu*Yu).trace() + if tr in RLF: # It's real already. return tr @@ -1022,29 +1043,21 @@ class RealMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra): @staticmethod def real_embed(M): """ - Embed the matrix ``M`` into a space of real matrices. - - The matrix ``M`` can have entries in any field at the moment: - the real numbers, complex numbers, or quaternions. And although - they are not a field, we can probably support octonions at some - point, too. This function returns a real matrix that "acts like" - the original with respect to matrix multiplication; i.e. - - real_embed(M*N) = real_embed(M)*real_embed(N) - + The identity function, for embedding real matrices into real + matrices. """ return M - @staticmethod def real_unembed(M): """ - The inverse of :meth:`real_embed`. + The identity function, for unembedding real matrices from real + matrices. """ return M -class RealSymmetricEJA(RealMatrixEuclideanJordanAlgebra): +class RealSymmetricEJA(RealMatrixEuclideanJordanAlgebra, KnownRankEJA): """ The rank-n simple EJA consisting of real symmetric n-by-n matrices, the usual symmetric Jordan product, and the trace inner @@ -1065,6 +1078,14 @@ class RealSymmetricEJA(RealMatrixEuclideanJordanAlgebra): sage: e2*e2 e2 + In theory, our "field" can be any subfield of the reals:: + + sage: RealSymmetricEJA(2, AA) + Euclidean Jordan algebra of dimension 3 over Algebraic Real Field + sage: RealSymmetricEJA(2, RR) + Euclidean Jordan algebra of dimension 3 over Real Field with + 53 bits of precision + TESTS: The dimension of this algebra is `(n^2 + n) / 2`:: @@ -1144,7 +1165,7 @@ class RealSymmetricEJA(RealMatrixEuclideanJordanAlgebra): else: Sij = Eij + Eij.transpose() S.append(Sij) - return tuple(S) + return S @staticmethod @@ -1152,6 +1173,10 @@ class RealSymmetricEJA(RealMatrixEuclideanJordanAlgebra): return 4 # Dimension 10 + def __init__(self, n, field=QQ, **kwargs): + basis = self._denormalized_basis(n, field) + super(RealSymmetricEJA, self).__init__(field, basis, n, **kwargs) + class ComplexMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra): @staticmethod @@ -1201,15 +1226,17 @@ class ComplexMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra): n = M.nrows() if M.ncols() != n: raise ValueError("the matrix 'M' must be square") - field = M.base_ring() + + # We don't need any adjoined elements... + field = M.base_ring().base_ring() + blocks = [] for z in M.list(): - a = z.vector()[0] # real part, I guess - b = z.vector()[1] # imag part, I guess + a = z.list()[0] # real part, I guess + b = z.list()[1] # imag part, I guess blocks.append(matrix(field, 2, [[a,b],[-b,a]])) - # We can drop the imaginaries here. - return matrix.block(field.base_ring(), n, blocks) + return matrix.block(field, n, blocks) @staticmethod @@ -1250,10 +1277,12 @@ class ComplexMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra): if not n.mod(2).is_zero(): raise ValueError("the matrix 'M' must be a complex embedding") - field = M.base_ring() # This should already have sqrt2 + # If "M" was normalized, its base ring might have roots + # adjoined and they can stick around after unembedding. + field = M.base_ring() R = PolynomialRing(field, 'z') z = R.gen() - F = NumberField(z**2 + 1,'i', embedding=CLF(-1).sqrt()) + F = field.extension(z**2 + 1, 'i', embedding=CLF(-1).sqrt()) i = F.gen() # Go top-left to bottom-right (reading order), converting every @@ -1303,7 +1332,7 @@ class ComplexMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra): return RealMatrixEuclideanJordanAlgebra.natural_inner_product(X,Y)/2 -class ComplexHermitianEJA(ComplexMatrixEuclideanJordanAlgebra): +class ComplexHermitianEJA(ComplexMatrixEuclideanJordanAlgebra, KnownRankEJA): """ The rank-n simple EJA consisting of complex Hermitian n-by-n matrices over the real numbers, the usual symmetric Jordan product, @@ -1314,6 +1343,16 @@ class ComplexHermitianEJA(ComplexMatrixEuclideanJordanAlgebra): sage: from mjo.eja.eja_algebra import ComplexHermitianEJA + EXAMPLES: + + In theory, our "field" can be any subfield of the reals:: + + sage: ComplexHermitianEJA(2, AA) + Euclidean Jordan algebra of dimension 4 over Algebraic Real Field + sage: ComplexHermitianEJA(2, RR) + Euclidean Jordan algebra of dimension 4 over Real Field with + 53 bits of precision + TESTS: The dimension of this algebra is `n^2`:: @@ -1364,6 +1403,7 @@ class ComplexHermitianEJA(ComplexMatrixEuclideanJordanAlgebra): True """ + @classmethod def _denormalized_basis(cls, n, field): """ @@ -1391,7 +1431,7 @@ class ComplexHermitianEJA(ComplexMatrixEuclideanJordanAlgebra): """ R = PolynomialRing(field, 'z') z = R.gen() - F = NumberField(z**2 + 1, 'I', embedding=CLF(-1).sqrt()) + F = field.extension(z**2 + 1, 'I') I = F.gen() # This is like the symmetric case, but we need to be careful: @@ -1415,9 +1455,13 @@ class ComplexHermitianEJA(ComplexMatrixEuclideanJordanAlgebra): # Since we embedded these, we can drop back to the "field" that we # started with instead of the complex extension "F". - return tuple( s.change_ring(field) for s in S ) + return ( s.change_ring(field) for s in S ) + def __init__(self, n, field=QQ, **kwargs): + basis = self._denormalized_basis(n,field) + super(ComplexHermitianEJA,self).__init__(field, basis, n, **kwargs) + class QuaternionMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra): @staticmethod @@ -1522,7 +1566,7 @@ class QuaternionMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra): if M.ncols() != n: raise ValueError("the matrix 'M' must be square") if not n.mod(4).is_zero(): - raise ValueError("the matrix 'M' must be a complex embedding") + raise ValueError("the matrix 'M' must be a quaternion embedding") # Use the base ring of the matrix to ensure that its entries can be # multiplied by elements of the quaternion algebra. @@ -1582,7 +1626,8 @@ class QuaternionMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra): return RealMatrixEuclideanJordanAlgebra.natural_inner_product(X,Y)/4 -class QuaternionHermitianEJA(QuaternionMatrixEuclideanJordanAlgebra): +class QuaternionHermitianEJA(QuaternionMatrixEuclideanJordanAlgebra, + KnownRankEJA): """ The rank-n simple EJA consisting of self-adjoint n-by-n quaternion matrices, the usual symmetric Jordan product, and the @@ -1593,6 +1638,16 @@ class QuaternionHermitianEJA(QuaternionMatrixEuclideanJordanAlgebra): sage: from mjo.eja.eja_algebra import QuaternionHermitianEJA + EXAMPLES: + + In theory, our "field" can be any subfield of the reals:: + + sage: QuaternionHermitianEJA(2, AA) + Euclidean Jordan algebra of dimension 6 over Algebraic Real Field + sage: QuaternionHermitianEJA(2, RR) + Euclidean Jordan algebra of dimension 6 over Real Field with + 53 bits of precision + TESTS: The dimension of this algebra is `2*n^2 - n`:: @@ -1693,11 +1748,18 @@ class QuaternionHermitianEJA(QuaternionMatrixEuclideanJordanAlgebra): S.append(Sij_J) Sij_K = cls.real_embed(K*Eij - K*Eij.transpose()) S.append(Sij_K) - return tuple(S) + # Since we embedded these, we can drop back to the "field" that we + # started with instead of the quaternion algebra "Q". + return ( s.change_ring(field) for s in S ) + + + def __init__(self, n, field=QQ, **kwargs): + basis = self._denormalized_basis(n,field) + super(QuaternionHermitianEJA,self).__init__(field, basis, n, **kwargs) -class JordanSpinEJA(FiniteDimensionalEuclideanJordanAlgebra): +class JordanSpinEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA): """ The rank-2 simple EJA consisting of real vectors ``x=(x0, x_bar)`` with the usual inner product and jordan product ``x*y =