X-Git-Url: http://gitweb.michael.orlitzky.com/?a=blobdiff_plain;f=mjo%2Feja%2Feja_algebra.py;h=12207b7c5a8e897738ab21a73361883cae03626f;hb=5d646c586de50b571d2983b546a05899bf0c20c2;hp=02ed966540132c07af408a1a9fb4e6eebc220c31;hpb=b05705ab04692f738a57a6ef387662ba5ea46ceb;p=sage.d.git diff --git a/mjo/eja/eja_algebra.py b/mjo/eja/eja_algebra.py index 02ed966..12207b7 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 @@ -238,7 +251,10 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): """ (A_of_x, x, xr, detA) = self._charpoly_matrix_system() R = A_of_x.base_ring() - if i >= self.rank(): + + if i == self.rank(): + return R.one() + if i > self.rank(): # Guaranteed by theory return R.zero() @@ -347,7 +363,7 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): SETUP:: - sage: from mjo.eja.eja_algebra import JordanSpinEJA + sage: from mjo.eja.eja_algebra import JordanSpinEJA, TrivialEJA EXAMPLES: @@ -361,12 +377,22 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): sage: p(*xvec) t^2 - 2*t + 1 + By definition, the characteristic polynomial is a monic + degree-zero polynomial in a rank-zero algebra. Note that + Cayley-Hamilton is indeed satisfied since the polynomial + ``1`` evaluates to the identity element of the algebra on + any argument:: + + sage: J = TrivialEJA() + sage: J.characteristic_polynomial() + 1 + """ r = self.rank() n = self.dimension() - # The list of coefficient polynomials a_1, a_2, ..., a_n. - a = [ self._charpoly_coeff(i) for i in range(n) ] + # The list of coefficient polynomials a_0, a_1, a_2, ..., a_n. + a = [ self._charpoly_coeff(i) for i in range(r+1) ] # We go to a bit of trouble here to reorder the # indeterminates, so that it's easier to evaluate the @@ -378,17 +404,6 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): S = PolynomialRing(S, R.variable_names()) t = S(t) - # Note: all entries past the rth should be zero. The - # coefficient of the highest power (x^r) is 1, but it doesn't - # appear in the solution vector which contains coefficients - # for the other powers (to make them sum to x^r). - if (r < n): - a[r] = 1 # corresponds to x^r - else: - # When the rank is equal to the dimension, trying to - # assign a[r] goes out-of-bounds. - a.append(1) # corresponds to x^r - return sum( a[k]*(t**k) for k in xrange(len(a)) ) @@ -429,15 +444,19 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): SETUP:: - sage: from mjo.eja.eja_algebra import ComplexHermitianEJA + sage: from mjo.eja.eja_algebra import (ComplexHermitianEJA, + ....: TrivialEJA) EXAMPLES:: sage: J = ComplexHermitianEJA(3) sage: J.is_trivial() False - sage: A = J.zero().subalgebra_generated_by() - sage: A.is_trivial() + + :: + + sage: J = TrivialEJA() + sage: J.is_trivial() True """ @@ -612,14 +631,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. @@ -684,11 +695,15 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): TESTS: Ensure that every EJA that we know how to construct has a - positive integer rank:: + positive integer rank, unless the algebra is trivial in + which case its rank will be zero:: sage: set_random_seed() - sage: r = random_eja().rank() - sage: r in ZZ and r > 0 + sage: J = random_eja() + sage: r = J.rank() + sage: r in ZZ + True + sage: r > 0 or (r == 0 and J.is_trivial()) True """ @@ -761,6 +776,11 @@ class KnownRankEJA(object): 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) @@ -839,32 +859,10 @@ class RealCartesianProductEJA(FiniteDimensionalEuclideanJordanAlgebra, return x.to_vector().inner_product(y.to_vector()) -def random_eja(): +def random_eja(field=QQ, nontrivial=False): """ Return a "random" finite-dimensional Euclidean Jordan Algebra. - ALGORITHM: - - For now, we choose a random natural number ``n`` (greater than zero) - and then give you back one of the following: - - * The cartesian product of the rational numbers ``n`` times; this is - ``QQ^n`` with the Hadamard product. - - * The Jordan spin algebra on ``QQ^n``. - - * The ``n``-by-``n`` rational symmetric matrices with the symmetric - product. - - * The ``n``-by-``n`` complex-rational Hermitian matrices embedded - in the space of ``2n``-by-``2n`` real symmetric matrices. - - * The ``n``-by-``n`` quaternion-rational Hermitian matrices embedded - in the space of ``4n``-by-``4n`` real symmetric matrices. - - Later this might be extended to return Cartesian products of the - EJAs above. - SETUP:: sage: from mjo.eja.eja_algebra import random_eja @@ -875,8 +873,11 @@ def random_eja(): Euclidean Jordan algebra of dimension... """ - classname = choice(KnownRankEJA.__subclasses__()) - return classname.random_instance() + eja_classes = KnownRankEJA.__subclasses__() + if nontrivial: + eja_classes.remove(TrivialEJA) + classname = choice(eja_classes) + return classname.random_instance(field=field) @@ -911,7 +912,7 @@ class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra): z = R.gen() p = z**2 - 2 if p.is_irreducible(): - field = NumberField(p, 'sqrt2', embedding=RLF(2).sqrt()) + 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 basis ) @@ -940,8 +941,9 @@ class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra): else: basis = ( (b/n) for (b,n) in izip(self.natural_basis(), self._basis_normalizers) ) - field = self.base_ring().base_ring() # yeeeaahhhhhhh - J = MatrixEuclideanJordanAlgebra(field, + + # Do this over the rationals and convert back at the end. + J = MatrixEuclideanJordanAlgebra(QQ, basis, self.rank(), normalize_basis=False) @@ -950,7 +952,14 @@ class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra): # 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 @@ -1013,6 +1022,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 @@ -1067,6 +1077,14 @@ class RealSymmetricEJA(RealMatrixEuclideanJordanAlgebra, KnownRankEJA): 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`:: @@ -1207,15 +1225,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 @@ -1256,10 +1276,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 @@ -1320,6 +1342,16 @@ class ComplexHermitianEJA(ComplexMatrixEuclideanJordanAlgebra, KnownRankEJA): 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`:: @@ -1398,7 +1430,7 @@ class ComplexHermitianEJA(ComplexMatrixEuclideanJordanAlgebra, KnownRankEJA): """ 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: @@ -1533,7 +1565,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. @@ -1605,6 +1637,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`:: @@ -1705,7 +1747,10 @@ class QuaternionHermitianEJA(QuaternionMatrixEuclideanJordanAlgebra, S.append(Sij_J) Sij_K = cls.real_embed(K*Eij - K*Eij.transpose()) S.append(Sij_K) - return 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): @@ -1797,3 +1842,40 @@ class JordanSpinEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA): """ return x.to_vector().inner_product(y.to_vector()) + + +class TrivialEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA): + """ + The trivial Euclidean Jordan algebra consisting of only a zero element. + + SETUP:: + + sage: from mjo.eja.eja_algebra import TrivialEJA + + EXAMPLES:: + + sage: J = TrivialEJA() + sage: J.dimension() + 0 + sage: J.zero() + 0 + sage: J.one() + 0 + sage: 7*J.one()*12*J.one() + 0 + sage: J.one().inner_product(J.one()) + 0 + sage: J.one().norm() + 0 + sage: J.one().subalgebra_generated_by() + Euclidean Jordan algebra of dimension 0 over Rational Field + sage: J.rank() + 0 + + """ + def __init__(self, field=QQ, **kwargs): + mult_table = [] + 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)