X-Git-Url: http://gitweb.michael.orlitzky.com/?a=blobdiff_plain;f=mjo%2Feja%2Feja_algebra.py;h=6252cc29a729472ef7f182cd3393ab3db98b6b58;hb=39d8d3190b721ea21e0e86618d774437bc1eeb35;hp=222b12c9ea0074792c807471eb546d1c45282e71;hpb=8adc54235f68f871cdbb66e8854a5a50ce4ad751;p=sage.d.git diff --git a/mjo/eja/eja_algebra.py b/mjo/eja/eja_algebra.py index 222b12c..6252cc2 100644 --- a/mjo/eja/eja_algebra.py +++ b/mjo/eja/eja_algebra.py @@ -3,6 +3,17 @@ Euclidean Jordan Algebras. These are formally-real Jordan Algebras; specifically those where u^2 + v^2 = 0 implies that u = v = 0. They are used in optimization, and have some additional nice methods beyond what can be supported in a general Jordan Algebra. + + +SETUP:: + + sage: from mjo.eja.eja_algebra import random_eja + +EXAMPLES:: + + sage: random_eja() + Euclidean Jordan algebra of dimension... + """ from itertools import repeat @@ -14,7 +25,6 @@ from sage.matrix.constructor import matrix from sage.matrix.matrix_space import MatrixSpace from sage.misc.cachefunc import cached_method from sage.misc.lazy_import import lazy_import -from sage.misc.prandom import choice from sage.misc.table import table from sage.modules.free_module import FreeModule, VectorSpace from sage.rings.all import (ZZ, QQ, AA, QQbar, RR, RLF, CLF, @@ -23,6 +33,7 @@ from sage.rings.all import (ZZ, QQ, AA, QQbar, RR, RLF, CLF, from mjo.eja.eja_element import FiniteDimensionalEuclideanJordanAlgebraElement lazy_import('mjo.eja.eja_subalgebra', 'FiniteDimensionalEuclideanJordanSubalgebra') +from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator from mjo.eja.eja_utils import _mat2vec class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): @@ -216,25 +227,6 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): coords = W.coordinate_vector(_mat2vec(elt)) return self.from_vector(coords) - @staticmethod - def _max_random_instance_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. - """ - raise NotImplementedError - def _repr_(self): """ Return a string representation of ``self``. @@ -588,6 +580,16 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): sage: actual == expected True + Ensure that the cached unit element (often precomputed by + hand) agrees with the computed one:: + + sage: set_random_seed() + sage: J = random_eja() + sage: cached = J.one() + sage: J.one.clear_cache() + sage: J.one() == cached + True + """ # We can brute-force compute the matrices of the operators # that correspond to the basis elements of this algebra. @@ -832,16 +834,6 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): return tuple( self.random_element(thorough) 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. - """ - n = ZZ.random_element(cls._max_random_instance_size() + 1) - return cls(n, field, **kwargs) @cached_method def _charpoly_coefficients(self): @@ -1003,32 +995,6 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): Element = FiniteDimensionalEuclideanJordanAlgebraElement - -def random_eja(field=AA): - """ - Return a "random" finite-dimensional Euclidean Jordan Algebra. - - SETUP:: - - sage: from mjo.eja.eja_algebra import random_eja - - TESTS:: - - sage: random_eja() - Euclidean Jordan algebra of dimension... - - """ - classname = choice([TrivialEJA, - HadamardEJA, - JordanSpinEJA, - RealSymmetricEJA, - ComplexHermitianEJA, - QuaternionHermitianEJA]) - return classname.random_instance(field=field) - - - - class RationalBasisEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra): r""" Algebras whose basis consists of vectors with rational @@ -1088,12 +1054,72 @@ class RationalBasisEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebr return tuple(map(lambda x: x.change_ring(self.base_ring()), a)) -class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra): +class ConcreteEuclideanJordanAlgebra: + r""" + A class for the Euclidean Jordan algebras that we know by name. + + These are the Jordan algebras whose basis, multiplication table, + rank, and so on are known a priori. More to the point, they are + the Euclidean Jordan algebras for which we are able to conjure up + a "random instance." + + SETUP:: + + sage: from mjo.eja.eja_algebra import ConcreteEuclideanJordanAlgebra + + TESTS: + + Our natural basis is normalized with respect to the natural inner + product unless we specify otherwise:: + + sage: set_random_seed() + sage: J = ConcreteEuclideanJordanAlgebra.random_instance() + sage: all( b.norm() == 1 for b in J.gens() ) + True + + Since our natural basis is normalized with respect to the natural + inner product, and since we know that this algebra is an EJA, any + left-multiplication operator's matrix will be symmetric because + natural->EJA basis representation is an isometry and within the EJA + the operator is self-adjoint by the Jordan axiom:: + + sage: set_random_seed() + sage: J = ConcreteEuclideanJordanAlgebra.random_instance() + sage: x = J.random_element() + sage: x.operator().matrix().is_symmetric() + True + + """ + @staticmethod def _max_random_instance_size(): - # Play it safe, since this will be squared and the underlying - # field can have dimension 4 (quaternions) too. - return 2 + """ + 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 ambiguous -- 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. + + This method must be implemented in each subclass. + """ + raise NotImplementedError + + @classmethod + def random_instance(cls, field=AA, **kwargs): + """ + Return a random instance of this type of algebra. + + This method should be implemented in each subclass. + """ + from sage.misc.prandom import choice + eja_class = choice(cls.__subclasses__()) + return eja_class.random_instance(field) + + +class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra): def __init__(self, field, basis, normalize_basis=True, **kwargs): """ @@ -1108,7 +1134,8 @@ class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra): # time to ensure that it isn't a generator expression. basis = tuple(basis) - if len(basis) > 1 and normalize_basis: + algebra_dim = len(basis) + if algebra_dim > 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. @@ -1129,6 +1156,14 @@ class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra): natural_basis=basis, **kwargs) + if algebra_dim == 0: + self.one.set_cache(self.zero()) + else: + n = basis[0].nrows() + # The identity wrt (A,B) -> (AB + BA)/2 is independent of the + # details of this algebra. + self.one.set_cache(self(matrix.identity(field,n))) + @cached_method def _charpoly_coefficients(self): @@ -1268,7 +1303,8 @@ class RealMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra): return M -class RealSymmetricEJA(RealMatrixEuclideanJordanAlgebra): +class RealSymmetricEJA(RealMatrixEuclideanJordanAlgebra, + ConcreteEuclideanJordanAlgebra): """ The rank-n simple EJA consisting of real symmetric n-by-n matrices, the usual symmetric Jordan product, and the trace inner @@ -1327,25 +1363,6 @@ class RealSymmetricEJA(RealMatrixEuclideanJordanAlgebra): sage: RealSymmetricEJA(3, prefix='q').gens() (q0, q1, q2, q3, q4, q5) - Our natural basis is normalized with respect to the natural inner - product unless we specify otherwise:: - - sage: set_random_seed() - sage: J = RealSymmetricEJA.random_instance() - sage: all( b.norm() == 1 for b in J.gens() ) - True - - Since our natural basis is normalized with respect to the natural - inner product, and since we know that this algebra is an EJA, any - left-multiplication operator's matrix will be symmetric because - natural->EJA basis representation is an isometry and within the EJA - the operator is self-adjoint by the Jordan axiom:: - - sage: set_random_seed() - sage: x = RealSymmetricEJA.random_instance().random_element() - sage: x.operator().matrix().is_symmetric() - True - We can construct the (trivial) algebra of rank zero:: sage: RealSymmetricEJA(0) @@ -1388,6 +1405,13 @@ class RealSymmetricEJA(RealMatrixEuclideanJordanAlgebra): def _max_random_instance_size(): return 4 # Dimension 10 + @classmethod + def random_instance(cls, field=AA, **kwargs): + """ + Return a random instance of this type of algebra. + """ + n = ZZ.random_element(cls._max_random_instance_size() + 1) + return cls(n, field, **kwargs) def __init__(self, n, field=AA, **kwargs): basis = self._denormalized_basis(n, field) @@ -1431,8 +1455,7 @@ class ComplexMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra): Embedding is a homomorphism (isomorphism, in fact):: sage: set_random_seed() - sage: n_max = ComplexMatrixEuclideanJordanAlgebra._max_random_instance_size() - sage: n = ZZ.random_element(n_max) + sage: n = ZZ.random_element(3) sage: F = QuadraticField(-1, 'I') sage: X = random_matrix(F, n) sage: Y = random_matrix(F, n) @@ -1557,7 +1580,8 @@ class ComplexMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra): return RealMatrixEuclideanJordanAlgebra.natural_inner_product(X,Y)/2 -class ComplexHermitianEJA(ComplexMatrixEuclideanJordanAlgebra): +class ComplexHermitianEJA(ComplexMatrixEuclideanJordanAlgebra, + ConcreteEuclideanJordanAlgebra): """ The rank-n simple EJA consisting of complex Hermitian n-by-n matrices over the real numbers, the usual symmetric Jordan product, @@ -1608,25 +1632,6 @@ class ComplexHermitianEJA(ComplexMatrixEuclideanJordanAlgebra): sage: ComplexHermitianEJA(2, prefix='z').gens() (z0, z1, z2, z3) - Our natural basis is normalized with respect to the natural inner - product unless we specify otherwise:: - - sage: set_random_seed() - sage: J = ComplexHermitianEJA.random_instance() - sage: all( b.norm() == 1 for b in J.gens() ) - True - - Since our natural basis is normalized with respect to the natural - inner product, and since we know that this algebra is an EJA, any - left-multiplication operator's matrix will be symmetric because - natural->EJA basis representation is an isometry and within the EJA - the operator is self-adjoint by the Jordan axiom:: - - sage: set_random_seed() - sage: x = ComplexHermitianEJA.random_instance().random_element() - sage: x.operator().matrix().is_symmetric() - True - We can construct the (trivial) algebra of rank zero:: sage: ComplexHermitianEJA(0) @@ -1696,6 +1701,17 @@ class ComplexHermitianEJA(ComplexMatrixEuclideanJordanAlgebra): **kwargs) self.rank.set_cache(n) + @staticmethod + def _max_random_instance_size(): + return 3 # Dimension 9 + + @classmethod + def random_instance(cls, field=AA, **kwargs): + """ + Return a random instance of this type of algebra. + """ + n = ZZ.random_element(cls._max_random_instance_size() + 1) + return cls(n, field, **kwargs) class QuaternionMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra): @staticmethod @@ -1727,8 +1743,7 @@ class QuaternionMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra): Embedding is a homomorphism (isomorphism, in fact):: sage: set_random_seed() - sage: n_max = QuaternionMatrixEuclideanJordanAlgebra._max_random_instance_size() - sage: n = ZZ.random_element(n_max) + sage: n = ZZ.random_element(2) sage: Q = QuaternionAlgebra(QQ,-1,-1) sage: X = random_matrix(Q, n) sage: Y = random_matrix(Q, n) @@ -1860,8 +1875,9 @@ class QuaternionMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra): return RealMatrixEuclideanJordanAlgebra.natural_inner_product(X,Y)/4 -class QuaternionHermitianEJA(QuaternionMatrixEuclideanJordanAlgebra): - """ +class QuaternionHermitianEJA(QuaternionMatrixEuclideanJordanAlgebra, + ConcreteEuclideanJordanAlgebra): + r""" The rank-n simple EJA consisting of self-adjoint n-by-n quaternion matrices, the usual symmetric Jordan product, and the real-part-of-trace inner product. It has dimension `2n^2 - n` over @@ -1911,25 +1927,6 @@ class QuaternionHermitianEJA(QuaternionMatrixEuclideanJordanAlgebra): sage: QuaternionHermitianEJA(2, prefix='a').gens() (a0, a1, a2, a3, a4, a5) - Our natural basis is normalized with respect to the natural inner - product unless we specify otherwise:: - - sage: set_random_seed() - sage: J = QuaternionHermitianEJA.random_instance() - sage: all( b.norm() == 1 for b in J.gens() ) - True - - Since our natural basis is normalized with respect to the natural - inner product, and since we know that this algebra is an EJA, any - left-multiplication operator's matrix will be symmetric because - natural->EJA basis representation is an isometry and within the EJA - the operator is self-adjoint by the Jordan axiom:: - - sage: set_random_seed() - sage: x = QuaternionHermitianEJA.random_instance().random_element() - sage: x.operator().matrix().is_symmetric() - True - We can construct the (trivial) algebra of rank zero:: sage: QuaternionHermitianEJA(0) @@ -2000,8 +1997,24 @@ class QuaternionHermitianEJA(QuaternionMatrixEuclideanJordanAlgebra): **kwargs) self.rank.set_cache(n) + @staticmethod + def _max_random_instance_size(): + r""" + The maximum rank of a random QuaternionHermitianEJA. + """ + return 2 # Dimension 6 -class HadamardEJA(RationalBasisEuclideanJordanAlgebra): + @classmethod + def random_instance(cls, field=AA, **kwargs): + """ + Return a random instance of this type of algebra. + """ + n = ZZ.random_element(cls._max_random_instance_size() + 1) + return cls(n, field, **kwargs) + + +class HadamardEJA(RationalBasisEuclideanJordanAlgebra, + ConcreteEuclideanJordanAlgebra): """ Return the Euclidean Jordan Algebra corresponding to the set `R^n` under the Hadamard product. @@ -2052,10 +2065,27 @@ class HadamardEJA(RationalBasisEuclideanJordanAlgebra): **kwargs) self.rank.set_cache(n) + if n == 0: + self.one.set_cache( self.zero() ) + else: + self.one.set_cache( sum(self.gens()) ) + @staticmethod def _max_random_instance_size(): + r""" + The maximum dimension of a random HadamardEJA. + """ return 5 + @classmethod + def random_instance(cls, field=AA, **kwargs): + """ + Return a random instance of this type of algebra. + """ + n = ZZ.random_element(cls._max_random_instance_size() + 1) + return cls(n, field, **kwargs) + + def inner_product(self, x, y): """ Faster to reimplement than to use natural representations. @@ -2081,14 +2111,20 @@ class HadamardEJA(RationalBasisEuclideanJordanAlgebra): return x.to_vector().inner_product(y.to_vector()) -class BilinearFormEJA(RationalBasisEuclideanJordanAlgebra): +class BilinearFormEJA(RationalBasisEuclideanJordanAlgebra, + ConcreteEuclideanJordanAlgebra): 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 = - (x0*y0 + , x0*y_bar + y0*x_bar)`` where ``B`` is a - symmetric positive-definite "bilinear form" matrix. It has - dimension `n` over the reals, and reduces to the ``JordanSpinEJA`` - when ``B`` is the identity matrix of order ``n-1``. + (,y_bar>, x0*y_bar + y0*x_bar)`` where `B = 1 \times B22` is + a symmetric positive-definite "bilinear form" matrix. Its + dimension is the size of `B`, and it has rank two in dimensions + larger than two. It reduces to the ``JordanSpinEJA`` when `B` is + the identity matrix of order ``n``. + + We insist that the one-by-one upper-left identity block of `B` be + passed in as well so that we can be passed a matrix of size zero + to construct a trivial algebra. SETUP:: @@ -2100,16 +2136,32 @@ class BilinearFormEJA(RationalBasisEuclideanJordanAlgebra): When no bilinear form is specified, the identity matrix is used, and the resulting algebra is the Jordan spin algebra:: - sage: J0 = BilinearFormEJA(3) + sage: B = matrix.identity(AA,3) + sage: J0 = BilinearFormEJA(B) sage: J1 = JordanSpinEJA(3) sage: J0.multiplication_table() == J0.multiplication_table() True + An error is raised if the matrix `B` does not correspond to a + positive-definite bilinear form:: + + sage: B = matrix.random(QQ,2,3) + sage: J = BilinearFormEJA(B) + Traceback (most recent call last): + ... + ValueError: bilinear form is not positive-definite + sage: B = matrix.zero(QQ,3) + sage: J = BilinearFormEJA(B) + Traceback (most recent call last): + ... + ValueError: bilinear form is not positive-definite + TESTS: We can create a zero-dimensional algebra:: - sage: J = BilinearFormEJA(0) + sage: B = matrix.identity(AA,0) + sage: J = BilinearFormEJA(B) sage: J.basis() Finite family {} @@ -2121,8 +2173,11 @@ class BilinearFormEJA(RationalBasisEuclideanJordanAlgebra): sage: set_random_seed() sage: n = ZZ.random_element(5) sage: M = matrix.random(QQ, max(0,n-1), algorithm='unimodular') - sage: B = M.transpose()*M - sage: J = BilinearFormEJA(n, B=B) + sage: B11 = matrix.identity(QQ,1) + sage: B22 = M.transpose()*M + sage: B = block_matrix(2,2,[ [B11,0 ], + ....: [0, B22 ] ]) + sage: J = BilinearFormEJA(B) sage: eis = VectorSpace(M.base_ring(), M.ncols()).basis() sage: V = J.vector_space() sage: sis = [ J.from_vector(V([0] + (M.inverse()*ei).list())) @@ -2136,11 +2191,12 @@ class BilinearFormEJA(RationalBasisEuclideanJordanAlgebra): sage: actual == expected True """ - def __init__(self, n, field=AA, B=None, **kwargs): - if B is None: - self._B = matrix.identity(field, max(0,n-1)) - else: - self._B = B + def __init__(self, B, field=AA, **kwargs): + self._B = B + n = B.nrows() + + if not B.is_positive_definite(): + raise ValueError("bilinear form is not positive-definite") V = VectorSpace(field, n) mult_table = [[V.zero() for j in range(n)] for i in range(n)] @@ -2152,7 +2208,7 @@ class BilinearFormEJA(RationalBasisEuclideanJordanAlgebra): xbar = x[1:] y0 = y[0] ybar = y[1:] - z0 = x0*y0 + (self._B*xbar).inner_product(ybar) + z0 = (B*x).inner_product(y) zbar = y0*xbar + x0*ybar z = V([z0] + zbar.list()) mult_table[i][j] = z @@ -2166,10 +2222,42 @@ class BilinearFormEJA(RationalBasisEuclideanJordanAlgebra): **kwargs) self.rank.set_cache(min(n,2)) + if n == 0: + self.one.set_cache( self.zero() ) + else: + self.one.set_cache( self.monomial(0) ) + @staticmethod def _max_random_instance_size(): + r""" + The maximum dimension of a random BilinearFormEJA. + """ return 5 + @classmethod + def random_instance(cls, field=AA, **kwargs): + """ + Return a random instance of this algebra. + """ + n = ZZ.random_element(cls._max_random_instance_size() + 1) + if n.is_zero(): + B = matrix.identity(field, n) + return cls(B, field, **kwargs) + + B11 = matrix.identity(field,1) + M = matrix.random(field, n-1) + I = matrix.identity(field, n-1) + alpha = field.zero() + while alpha.is_zero(): + alpha = field.random_element().abs() + B22 = M.transpose()*M + alpha*I + + from sage.matrix.special import block_matrix + B = block_matrix(2,2, [ [B11, ZZ(0) ], + [ZZ(0), B22 ] ]) + + return cls(B, field, **kwargs) + def inner_product(self, x, y): r""" Half of the trace inner product. @@ -2189,21 +2277,15 @@ class BilinearFormEJA(RationalBasisEuclideanJordanAlgebra): paper:: sage: set_random_seed() - sage: n = ZZ.random_element(2,5) - sage: M = matrix.random(QQ, max(0,n-1), algorithm='unimodular') - sage: B = M.transpose()*M - sage: J = BilinearFormEJA(n, B=B) + sage: J = BilinearFormEJA.random_instance() + sage: n = J.dimension() sage: x = J.random_element() sage: y = J.random_element() - sage: x.inner_product(y) == (x*y).trace()/2 + sage: (n == 1) or (x.inner_product(y) == (x*y).trace()/2) True """ - xvec = x.to_vector() - xbar = xvec[1:] - yvec = y.to_vector() - ybar = yvec[1:] - return x[0]*y[0] + (self._B*xbar).inner_product(ybar) + return (self._B*x.to_vector()).inner_product(y.to_vector()) class JordanSpinEJA(BilinearFormEJA): @@ -2259,10 +2341,29 @@ class JordanSpinEJA(BilinearFormEJA): def __init__(self, n, field=AA, **kwargs): # This is a special case of the BilinearFormEJA with the identity # matrix as its bilinear form. - super(JordanSpinEJA, self).__init__(n, field, **kwargs) + B = matrix.identity(field, n) + super(JordanSpinEJA, self).__init__(B, field, **kwargs) + + @staticmethod + def _max_random_instance_size(): + r""" + The maximum dimension of a random JordanSpinEJA. + """ + return 5 + + @classmethod + def random_instance(cls, field=AA, **kwargs): + """ + Return a random instance of this type of algebra. + + Needed here to override the implementation for ``BilinearFormEJA``. + """ + n = ZZ.random_element(cls._max_random_instance_size() + 1) + return cls(n, field, **kwargs) -class TrivialEJA(FiniteDimensionalEuclideanJordanAlgebra): +class TrivialEJA(FiniteDimensionalEuclideanJordanAlgebra, + ConcreteEuclideanJordanAlgebra): """ The trivial Euclidean Jordan algebra consisting of only a zero element. @@ -2300,6 +2401,7 @@ class TrivialEJA(FiniteDimensionalEuclideanJordanAlgebra): # The rank is zero using my definition, namely the dimension of the # largest subalgebra generated by any element. self.rank.set_cache(0) + self.one.set_cache( self.zero() ) @classmethod def random_instance(cls, field=AA, **kwargs): @@ -2317,7 +2419,8 @@ class DirectSumEJA(FiniteDimensionalEuclideanJordanAlgebra): SETUP:: - sage: from mjo.eja.eja_algebra import (HadamardEJA, + sage: from mjo.eja.eja_algebra import (random_eja, + ....: HadamardEJA, ....: RealSymmetricEJA, ....: DirectSumEJA) @@ -2331,8 +2434,25 @@ class DirectSumEJA(FiniteDimensionalEuclideanJordanAlgebra): sage: J.rank() 5 + TESTS: + + The external direct sum construction is only valid when the two factors + have the same base ring; an error is raised otherwise:: + + sage: set_random_seed() + sage: J1 = random_eja(AA) + sage: J2 = random_eja(QQ) + sage: J = DirectSumEJA(J1,J2) + Traceback (most recent call last): + ... + ValueError: algebras must share the same base field + """ - def __init__(self, J1, J2, field=AA, **kwargs): + def __init__(self, J1, J2, **kwargs): + if J1.base_ring() != J2.base_ring(): + raise ValueError("algebras must share the same base field") + field = J1.base_ring() + self._factors = (J1, J2) n1 = J1.dimension() n2 = J2.dimension() @@ -2404,9 +2524,16 @@ class DirectSumEJA(FiniteDimensionalEuclideanJordanAlgebra): """ (J1,J2) = self.factors() - n = J1.dimension() - pi_left = lambda x: J1.from_vector(x.to_vector()[:n]) - pi_right = lambda x: J2.from_vector(x.to_vector()[n:]) + m = J1.dimension() + n = J2.dimension() + V_basis = self.vector_space().basis() + # Need to specify the dimensions explicitly so that we don't + # wind up with a zero-by-zero matrix when we want e.g. a + # zero-by-two matrix (important for composing things). + P1 = matrix(self.base_ring(), m, m+n, V_basis[:m]) + P2 = matrix(self.base_ring(), n, m+n, V_basis[m:]) + pi_left = FiniteDimensionalEuclideanJordanAlgebraOperator(self,J1,P1) + pi_right = FiniteDimensionalEuclideanJordanAlgebraOperator(self,J2,P2) return (pi_left, pi_right) def inclusions(self): @@ -2415,7 +2542,8 @@ class DirectSumEJA(FiniteDimensionalEuclideanJordanAlgebra): SETUP:: - sage: from mjo.eja.eja_algebra import (JordanSpinEJA, + sage: from mjo.eja.eja_algebra import (random_eja, + ....: JordanSpinEJA, ....: RealSymmetricEJA, ....: DirectSumEJA) @@ -2440,14 +2568,39 @@ class DirectSumEJA(FiniteDimensionalEuclideanJordanAlgebra): sage: J.one().to_vector() (1, 0, 0, 1, 0, 1) + TESTS: + + Composing a projection with the corresponding inclusion should + produce the identity map, and mismatching them should produce + the zero map:: + + sage: set_random_seed() + sage: J1 = random_eja() + sage: J2 = random_eja() + sage: J = DirectSumEJA(J1,J2) + sage: (iota_left, iota_right) = J.inclusions() + sage: (pi_left, pi_right) = J.projections() + sage: pi_left*iota_left == J1.one().operator() + True + sage: pi_right*iota_right == J2.one().operator() + True + sage: (pi_left*iota_right).is_zero() + True + sage: (pi_right*iota_left).is_zero() + True + """ (J1,J2) = self.factors() - n = J1.dimension() + m = J1.dimension() + n = J2.dimension() V_basis = self.vector_space().basis() - I1 = matrix.column(self.base_ring(), V_basis[:n]) - I2 = matrix.column(self.base_ring(), V_basis[n:]) - iota_left = lambda x: self.from_vector(I1*x.to_vector()) - iota_right = lambda x: self.from_vector(I2*+x.to_vector()) + # Need to specify the dimensions explicitly so that we don't + # wind up with a zero-by-zero matrix when we want e.g. a + # two-by-zero matrix (important for composing things). + I1 = matrix.column(self.base_ring(), m, m+n, V_basis[:m]) + I2 = matrix.column(self.base_ring(), n, m+n, V_basis[m:]) + iota_left = FiniteDimensionalEuclideanJordanAlgebraOperator(J1,self,I1) + iota_right = FiniteDimensionalEuclideanJordanAlgebraOperator(J2,self,I2) return (iota_left, iota_right) def inner_product(self, x, y): @@ -2466,7 +2619,7 @@ class DirectSumEJA(FiniteDimensionalEuclideanJordanAlgebra): EXAMPLE:: - sage: J1 = HadamardEJA(3) + sage: J1 = HadamardEJA(3,QQ) sage: J2 = QuaternionHermitianEJA(2,QQ,normalize_basis=False) sage: J = DirectSumEJA(J1,J2) sage: x1 = J1.one() @@ -2488,3 +2641,7 @@ class DirectSumEJA(FiniteDimensionalEuclideanJordanAlgebra): y2 = pi_right(y) return (x1.inner_product(y1) + x2.inner_product(y2)) + + + +random_eja = ConcreteEuclideanJordanAlgebra.random_instance