X-Git-Url: http://gitweb.michael.orlitzky.com/?a=blobdiff_plain;f=mjo%2Feja%2Feja_algebra.py;h=f327bf51aada40b33fd05fb0d1c38c124d4b545e;hb=debb0160c47109a5f2060bffd2c618f2a3a19551;hp=4091d03fd6a40351e7d4c2789847d1db6fa0e807;hpb=4d0f89a814fe6ab91a17af023c35caefaada2893;p=sage.d.git diff --git a/mjo/eja/eja_algebra.py b/mjo/eja/eja_algebra.py index 4091d03..f327bf5 100644 --- a/mjo/eja/eja_algebra.py +++ b/mjo/eja/eja_algebra.py @@ -236,10 +236,12 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): return self._multiplication_table[i][j] @cached_method - def characteristic_polynomial(self): + def characteristic_polynomial_of(self): """ - Return a characteristic polynomial that works for all elements - of this algebra. + Return the algebra's "characteristic polynomial of" function, + which is itself a multivariate polynomial that, when evaluated + at the coordinates of some algebra element, returns that + element's characteristic polynomial. The resulting polynomial has `n+1` variables, where `n` is the dimension of this algebra. The first `n` variables correspond to @@ -259,7 +261,7 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): Alizadeh, Example 11.11:: sage: J = JordanSpinEJA(3) - sage: p = J.characteristic_polynomial(); p + sage: p = J.characteristic_polynomial_of(); p X1^2 - X2^2 - X3^2 + (-2*t)*X1 + t^2 sage: xvec = J.one().to_vector() sage: p(*xvec) @@ -272,7 +274,7 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): any argument:: sage: J = TrivialEJA() - sage: J.characteristic_polynomial() + sage: J.characteristic_polynomial_of() 1 """ @@ -438,8 +440,15 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): """ Return the matrix space in which this algebra's natural basis elements live. + + Generally this will be an `n`-by-`1` column-vector space, + except when the algebra is trivial. There it's `n`-by-`n` + (where `n` is zero), to ensure that two elements of the + natural basis space (empty matrices) can be multiplied. """ - if self._natural_basis is None or len(self._natural_basis) == 0: + if self.is_trivial(): + return MatrixSpace(self.base_ring(), 0) + elif self._natural_basis is None or len(self._natural_basis) == 0: return MatrixSpace(self.base_ring(), self.dimension(), 1) else: return self._natural_basis[0].matrix_space() @@ -572,6 +581,25 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): Vector space of degree 6 and dimension 2... sage: J1 Euclidean Jordan algebra of dimension 3... + sage: J0.one().natural_representation() + [0 0 0] + [0 0 0] + [0 0 1] + sage: orig_df = AA.options.display_format + sage: AA.options.display_format = 'radical' + sage: J.from_vector(J5.basis()[0]).natural_representation() + [ 0 0 1/2*sqrt(2)] + [ 0 0 0] + [1/2*sqrt(2) 0 0] + sage: J.from_vector(J5.basis()[1]).natural_representation() + [ 0 0 0] + [ 0 0 1/2*sqrt(2)] + [ 0 1/2*sqrt(2) 0] + sage: AA.options.display_format = orig_df + sage: J1.one().natural_representation() + [1 0 0] + [0 1 0] + [0 0 0] TESTS: @@ -586,9 +614,10 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): sage: J1.superalgebra() == J and J1.dimension() == J.dimension() True - The identity elements in the two subalgebras are the - projections onto their respective subspaces of the - superalgebra's identity element:: + The decomposition is into eigenspaces, and its components are + therefore necessarily orthogonal. Moreover, the identity + elements in the two subalgebras are the projections onto their + respective subspaces of the superalgebra's identity element:: sage: set_random_seed() sage: J = random_eja() @@ -598,6 +627,16 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): ....: x = J.random_element() sage: c = x.subalgebra_idempotent() sage: J0,J5,J1 = J.peirce_decomposition(c) + sage: ipsum = 0 + sage: for (w,y,z) in zip(J0.basis(), J5.basis(), J1.basis()): + ....: w = w.superalgebra_element() + ....: y = J.from_vector(y) + ....: z = z.superalgebra_element() + ....: ipsum += w.inner_product(y).abs() + ....: ipsum += w.inner_product(z).abs() + ....: ipsum += y.inner_product(z).abs() + sage: ipsum + 0 sage: J1(c) == J1.one() True sage: J0(J.one() - c) == J0.one() @@ -633,10 +672,61 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): return (J0, J5, J1) - def random_elements(self, count): + def random_element(self, thorough=False): + r""" + Return a random element of this algebra. + + Our algebra superclass method only returns a linear + combination of at most two basis elements. We instead + want the vector space "random element" method that + returns a more diverse selection. + + INPUT: + + - ``thorough`` -- (boolean; default False) whether or not we + should generate irrational coefficients for the random + element when our base ring is irrational; this slows the + algebra operations to a crawl, but any truly random method + should include them + + """ + # For a general base ring... maybe we can trust this to do the + # right thing? Unlikely, but. + V = self.vector_space() + v = V.random_element() + + if self.base_ring() is AA: + # The "random element" method of the algebraic reals is + # stupid at the moment, and only returns integers between + # -2 and 2, inclusive: + # + # https://trac.sagemath.org/ticket/30875 + # + # Instead, we implement our own "random vector" method, + # and then coerce that into the algebra. We use the vector + # space degree here instead of the dimension because a + # subalgebra could (for example) be spanned by only two + # vectors, each with five coordinates. We need to + # generate all five coordinates. + if thorough: + v *= QQbar.random_element().real() + else: + v *= QQ.random_element() + + return self.from_vector(V.coordinate_vector(v)) + + def random_elements(self, count, thorough=False): """ Return ``count`` random elements as a tuple. + INPUT: + + - ``thorough`` -- (boolean; default False) whether or not we + should generate irrational coefficients for the random + elements when our base ring is irrational; this slows the + algebra operations to a crawl, but any truly random method + should include them + SETUP:: sage: from mjo.eja.eja_algebra import JordanSpinEJA @@ -651,7 +741,8 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): True """ - return tuple( self.random_element() for idx in range(count) ) + return tuple( self.random_element(thorough) + for idx in range(count) ) @classmethod def random_instance(cls, field=AA, **kwargs): @@ -666,7 +757,7 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): # there's only one. return cls(field) - n = ZZ.random_element(cls._max_test_case_size()) + 1 + n = ZZ.random_element(cls._max_test_case_size() + 1) return cls(n, field, **kwargs) @cached_method @@ -903,7 +994,7 @@ class HadamardEJA(FiniteDimensionalEuclideanJordanAlgebra): return x.to_vector().inner_product(y.to_vector()) -def random_eja(field=AA, nontrivial=False): +def random_eja(field=AA): """ Return a "random" finite-dimensional Euclidean Jordan Algebra. @@ -917,21 +1008,17 @@ def random_eja(field=AA, nontrivial=False): Euclidean Jordan algebra of dimension... """ - eja_classes = [HadamardEJA, - JordanSpinEJA, - RealSymmetricEJA, - ComplexHermitianEJA, - QuaternionHermitianEJA] - if not nontrivial: - eja_classes.append(TrivialEJA) - classname = choice(eja_classes) + classname = choice([TrivialEJA, + HadamardEJA, + JordanSpinEJA, + RealSymmetricEJA, + ComplexHermitianEJA, + QuaternionHermitianEJA]) return classname.random_instance(field=field) - - class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra): @staticmethod def _max_test_case_size(): @@ -1006,8 +1093,10 @@ class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra): # we simply undo the basis_normalizer scaling that we # performed earlier. # - # TODO: make this access safe. - XS = a[0].variables() + # The a[0] access here is safe because trivial algebras + # won't have any basis normalizers and therefore won't + # make it to this "else" branch. + XS = a[0].parent().gens() subs_dict = { XS[i]: self._basis_normalizers[i]*XS[i] for i in range(len(XS)) } return tuple( a_i.subs(subs_dict) for a_i in a ) @@ -1028,6 +1117,9 @@ class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra): # is supposed to hold the entire long vector, and the subspace W # of V will be spanned by the vectors that arise from symmetric # matrices. Thus for S^2, dim(V) == 4 and dim(W) == 3. + if len(basis) == 0: + return [] + field = basis[0].base_ring() dimension = basis[0].nrows() @@ -1185,6 +1277,11 @@ class RealSymmetricEJA(RealMatrixEuclideanJordanAlgebra): sage: x.operator().matrix().is_symmetric() True + We can construct the (trivial) algebra of rank zero:: + + sage: RealSymmetricEJA(0) + Euclidean Jordan algebra of dimension 0 over Algebraic Real Field + """ @classmethod def _denormalized_basis(cls, n, field): @@ -1458,6 +1555,11 @@ class ComplexHermitianEJA(ComplexMatrixEuclideanJordanAlgebra): sage: x.operator().matrix().is_symmetric() True + We can construct the (trivial) algebra of rank zero:: + + sage: ComplexHermitianEJA(0) + Euclidean Jordan algebra of dimension 0 over Algebraic Real Field + """ @classmethod @@ -1753,6 +1855,11 @@ class QuaternionHermitianEJA(QuaternionMatrixEuclideanJordanAlgebra): sage: x.operator().matrix().is_symmetric() True + We can construct the (trivial) algebra of rank zero:: + + sage: QuaternionHermitianEJA(0) + Euclidean Jordan algebra of dimension 0 over Algebraic Real Field + """ @classmethod def _denormalized_basis(cls, n, field): @@ -2027,3 +2134,50 @@ class TrivialEJA(FiniteDimensionalEuclideanJordanAlgebra): # largest subalgebra generated by any element. fdeja.__init__(field, mult_table, **kwargs) self.rank.set_cache(0) + + +class DirectSumEJA(FiniteDimensionalEuclideanJordanAlgebra): + r""" + The external (orthogonal) direct sum of two other Euclidean Jordan + algebras. Essentially the Cartesian product of its two factors. + Every Euclidean Jordan algebra decomposes into an orthogonal + direct sum of simple Euclidean Jordan algebras, so no generality + is lost by providing only this construction. + + SETUP:: + + sage: from mjo.eja.eja_algebra import (HadamardEJA, + ....: RealSymmetricEJA, + ....: DirectSumEJA) + + EXAMPLES:: + + sage: J1 = HadamardEJA(2) + sage: J2 = RealSymmetricEJA(3) + sage: J = DirectSumEJA(J1,J2) + sage: J.dimension() + 8 + sage: J.rank() + 5 + + """ + def __init__(self, J1, J2, field=AA, **kwargs): + n1 = J1.dimension() + n2 = J2.dimension() + n = n1+n2 + V = VectorSpace(field, n) + mult_table = [ [ V.zero() for j in range(n) ] + for i in range(n) ] + for i in range(n1): + for j in range(n1): + p = (J1.monomial(i)*J1.monomial(j)).to_vector() + mult_table[i][j] = V(p.list() + [field.zero()]*n2) + + for i in range(n2): + for j in range(n2): + p = (J2.monomial(i)*J2.monomial(j)).to_vector() + mult_table[n1+i][n1+j] = V([field.zero()]*n1 + p.list()) + + fdeja = super(DirectSumEJA, self) + fdeja.__init__(field, mult_table, **kwargs) + self.rank.set_cache(J1.rank() + J2.rank())