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
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,
PolynomialRing,
QuadraticField)
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):
sage: J(1)
Traceback (most recent call last):
...
- ValueError: not a naturally-represented algebra element
+ ValueError: not an element of this algebra
"""
return None
sage: J(A)
Traceback (most recent call last):
...
- ArithmeticError: vector is not in free module
+ ValueError: not an element of this algebra
TESTS:
True
"""
- msg = "not a naturally-represented algebra element"
+ msg = "not an element of this algebra"
if elt == 0:
# The superclass implementation of random_element()
# needs to be able to coerce "0" into the algebra.
# could be QQ instead of QQbar.
V = VectorSpace(basis_space.base_ring(), elt.nrows()*elt.ncols())
W = V.span_of_basis( _mat2vec(s) for s in natural_basis )
- 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.
+ try:
+ coords = W.coordinate_vector(_mat2vec(elt))
+ except ArithmeticError: # vector is not in free module
+ raise ValueError(msg)
- 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
+ return self.from_vector(coords)
def _repr_(self):
"""
return (t**r + sum( a[k]*(t**k) for k in range(r) ))
+ def coordinate_polynomial_ring(self):
+ r"""
+ The multivariate polynomial ring in which this algebra's
+ :meth:`characteristic_polynomial_of` lives.
+
+ SETUP::
+
+ sage: from mjo.eja.eja_algebra import (HadamardEJA,
+ ....: RealSymmetricEJA)
+
+ EXAMPLES::
+
+ sage: J = HadamardEJA(2)
+ sage: J.coordinate_polynomial_ring()
+ Multivariate Polynomial Ring in X1, X2...
+ sage: J = RealSymmetricEJA(3,QQ)
+ sage: J.coordinate_polynomial_ring()
+ Multivariate Polynomial Ring in X1, X2, X3, X4, X5, X6...
+
+ """
+ var_names = tuple( "X%d" % z for z in range(1, self.dimension()+1) )
+ return PolynomialRing(self.base_ring(), var_names)
def inner_product(self, x, y):
"""
SETUP::
- sage: from mjo.eja.eja_algebra import random_eja
+ sage: from mjo.eja.eja_algebra import (random_eja,
+ ....: HadamardEJA,
+ ....: BilinearFormEJA)
EXAMPLES:
sage: (x*y).inner_product(z) == y.inner_product(x*z)
True
+ TESTS:
+
+ Ensure that this is the usual inner product for the algebras
+ over `R^n`::
+
+ sage: set_random_seed()
+ sage: J = HadamardEJA.random_instance()
+ sage: x,y = J.random_elements(2)
+ sage: actual = x.inner_product(y)
+ sage: expected = x.to_vector().inner_product(y.to_vector())
+ sage: actual == expected
+ True
+
+ Ensure that this is one-half of the trace inner-product in a
+ BilinearFormEJA that isn't just the reals (when ``n`` isn't
+ one). This is in Faraut and Koranyi, and also my "On the
+ symmetry..." paper::
+
+ sage: set_random_seed()
+ sage: J = BilinearFormEJA.random_instance()
+ sage: n = J.dimension()
+ sage: x = J.random_element()
+ sage: y = J.random_element()
+ sage: (n == 1) or (x.inner_product(y) == (x*y).trace()/2)
+ True
"""
- X = x.natural_representation()
- Y = y.natural_representation()
- return self.natural_inner_product(X,Y)
+ B = self._inner_product_matrix
+ return (B*x.to_vector()).inner_product(y.to_vector())
def is_trivial(self):
return self._natural_basis[0].matrix_space()
- @staticmethod
- def natural_inner_product(X,Y):
- """
- Compute the inner product of two naturally-represented elements.
-
- For example in the real symmetric matrix EJA, this will compute
- the trace inner-product of two n-by-n symmetric matrices. The
- default should work for the real cartesian product EJA, the
- Jordan spin EJA, and the real symmetric matrices. The others
- will have to be overridden.
- """
- return (X.conjugate_transpose()*Y).trace()
-
-
@cached_method
def one(self):
"""
if not c.is_idempotent():
raise ValueError("element is not idempotent: %s" % c)
+ from mjo.eja.eja_subalgebra import FiniteDimensionalEuclideanJordanSubalgebra
+
# Default these to what they should be if they turn out to be
# trivial, because eigenspaces_left() won't return eigenvalues
# corresponding to trivial spaces (e.g. it returns only the
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):
of" function.
"""
n = self.dimension()
- var_names = [ "X" + str(z) for z in range(1,n+1) ]
- R = PolynomialRing(self.base_ring(), var_names)
+ R = self.coordinate_polynomial_ring()
vars = R.gens()
F = R.fraction_field()
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
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().is_self_adjoint()
+ 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):
"""
basis = tuple(basis)
algebra_dim = len(basis)
+ degree = 0 # size of the matrices
+ if algebra_dim > 0:
+ degree = basis[0].nrows()
+
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
~(self.natural_inner_product(s,s).sqrt()) for s in basis )
basis = tuple(s*c for (s,c) in zip(basis,self._basis_normalizers))
- Qs = self.multiplication_table_from_matrix_basis(basis)
+ # Now compute the multiplication and inner product tables.
+ # We have to do this *after* normalizing the basis, because
+ # scaling affects the answers.
+ V = VectorSpace(field, degree**2)
+ W = V.span_of_basis( _mat2vec(s) for s in basis )
+ mult_table = [[W.zero() for j in range(algebra_dim)]
+ for i in range(algebra_dim)]
+ ip_table = [[W.zero() for j in range(algebra_dim)]
+ for i in range(algebra_dim)]
+ for i in range(algebra_dim):
+ for j in range(algebra_dim):
+ mat_entry = (basis[i]*basis[j] + basis[j]*basis[i])/2
+ mult_table[i][j] = W.coordinate_vector(_mat2vec(mat_entry))
+
+ try:
+ # HACK: ignore the error here if we don't need the
+ # inner product (as is the case when we construct
+ # a dummy QQ-algebra for fast charpoly coefficients.
+ ip_table[i][j] = self.natural_inner_product(basis[i],
+ basis[j])
+ except:
+ pass
+
+ try:
+ # HACK PART DEUX
+ self._inner_product_matrix = matrix(field,ip_table)
+ except:
+ pass
super(MatrixEuclideanJordanAlgebra, self).__init__(field,
- Qs,
+ mult_table,
natural_basis=basis,
**kwargs)
return tuple( a_i.subs(subs_dict) for a_i in a )
- @staticmethod
- def multiplication_table_from_matrix_basis(basis):
- """
- At least three of the five simple Euclidean Jordan algebras have the
- symmetric multiplication (A,B) |-> (AB + BA)/2, where the
- multiplication on the right is matrix multiplication. Given a basis
- for the underlying matrix space, this function returns a
- multiplication table (obtained by looping through the basis
- elements) for an algebra of those matrices.
- """
- # In S^2, for example, we nominally have four coordinates even
- # though the space is of dimension three only. The vector space V
- # 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()
-
- V = VectorSpace(field, dimension**2)
- W = V.span_of_basis( _mat2vec(s) for s in basis )
- n = len(basis)
- mult_table = [[W.zero() for j in range(n)] for i in range(n)]
- for i in range(n):
- for j in range(n):
- mat_entry = (basis[i]*basis[j] + basis[j]*basis[i])/2
- mult_table[i][j] = W.coordinate_vector(_mat2vec(mat_entry))
-
- return mult_table
-
-
@staticmethod
def real_embed(M):
"""
"""
raise NotImplementedError
-
@classmethod
def natural_inner_product(cls,X,Y):
Xu = cls.real_unembed(X)
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
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)
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)
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)
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,
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)
**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
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)
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
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)
**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.
mult_table = [ [ V.gen(i)*(i == j) for j in range(n) ]
for i in range(n) ]
+ # Inner products are real numbers and not algebra
+ # elements, so once we turn the algebra element
+ # into a vector in inner_product(), we never go
+ # back. As a result -- contrary to what we do with
+ # self._multiplication_table -- we store the inner
+ # product table as a plain old matrix and not as
+ # an algebra operator.
+ ip_table = matrix.identity(field,n)
+ self._inner_product_matrix = ip_table
+
super(HadamardEJA, self).__init__(field,
mult_table,
check_axioms=False,
**kwargs)
self.rank.set_cache(n)
- self.one.set_cache( sum(self.gens()) )
+
+ 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
- def inner_product(self, x, y):
+ @classmethod
+ def random_instance(cls, field=AA, **kwargs):
"""
- Faster to reimplement than to use natural representations.
-
- SETUP::
-
- sage: from mjo.eja.eja_algebra import HadamardEJA
-
- TESTS:
-
- Ensure that this is the usual inner product for the algebras
- over `R^n`::
-
- sage: set_random_seed()
- sage: J = HadamardEJA.random_instance()
- sage: x,y = J.random_elements(2)
- sage: X = x.natural_representation()
- sage: Y = y.natural_representation()
- sage: x.inner_product(y) == J.natural_inner_product(X,Y)
- True
-
+ Return a random instance of this type of algebra.
"""
- return x.to_vector().inner_product(y.to_vector())
+ n = ZZ.random_element(cls._max_random_instance_size() + 1)
+ return cls(n, field, **kwargs)
-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 =
True
"""
def __init__(self, B, field=AA, **kwargs):
- self._B = B
n = B.nrows()
if not B.is_positive_definite():
z = V([z0] + zbar.list())
mult_table[i][j] = z
- # The rank of this algebra is two, unless we're in a
- # one-dimensional ambient space (because the rank is bounded
- # by the ambient dimension).
+ # Inner products are real numbers and not algebra
+ # elements, so once we turn the algebra element
+ # into a vector in inner_product(), we never go
+ # back. As a result -- contrary to what we do with
+ # self._multiplication_table -- we store the inner
+ # product table as a plain old matrix and not as
+ # an algebra operator.
+ ip_table = B
+ self._inner_product_matrix = ip_table
+
super(BilinearFormEJA, self).__init__(field,
mult_table,
check_axioms=False,
**kwargs)
+
+ # The rank of this algebra is two, unless we're in a
+ # one-dimensional ambient space (because the rank is bounded
+ # by the ambient dimension).
self.rank.set_cache(min(n,2))
if n == 0:
@staticmethod
def _max_random_instance_size():
+ r"""
+ The maximum dimension of a random BilinearFormEJA.
+ """
return 5
@classmethod
return cls(B, field, **kwargs)
- def inner_product(self, x, y):
- r"""
- Half of the trace inner product.
-
- This is defined so that the special case of the Jordan spin
- algebra gets the usual inner product.
-
- SETUP::
-
- sage: from mjo.eja.eja_algebra import BilinearFormEJA
-
- TESTS:
-
- Ensure that this is one-half of the trace inner-product when
- the algebra isn't just the reals (when ``n`` isn't one). This
- is in Faraut and Koranyi, and also my "On the symmetry..."
- paper::
-
- sage: set_random_seed()
- sage: J = BilinearFormEJA.random_instance()
- sage: n = J.dimension()
- sage: x = J.random_element()
- sage: y = J.random_element()
- sage: (n == 1) or (x.inner_product(y) == (x*y).trace()/2)
- True
-
- """
- return (self._B*x.to_vector()).inner_product(y.to_vector())
-
class JordanSpinEJA(BilinearFormEJA):
"""
sage: set_random_seed()
sage: J = JordanSpinEJA.random_instance()
sage: x,y = J.random_elements(2)
- sage: X = x.natural_representation()
- sage: Y = y.natural_representation()
- sage: x.inner_product(y) == J.natural_inner_product(X,Y)
+ sage: actual = x.inner_product(y)
+ sage: expected = x.to_vector().inner_product(y.to_vector())
+ sage: actual == expected
True
"""
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 cls(n, field, **kwargs)
-class TrivialEJA(FiniteDimensionalEuclideanJordanAlgebra):
+class TrivialEJA(FiniteDimensionalEuclideanJordanAlgebra,
+ ConcreteEuclideanJordanAlgebra):
"""
The trivial Euclidean Jordan algebra consisting of only a zero element.
"""
def __init__(self, field=AA, **kwargs):
mult_table = []
+ self._inner_product_matrix = matrix(field,0)
super(TrivialEJA, self).__init__(field,
mult_table,
check_axioms=False,
SETUP::
- sage: from mjo.eja.eja_algebra import (HadamardEJA,
+ sage: from mjo.eja.eja_algebra import (random_eja,
+ ....: HadamardEJA,
....: RealSymmetricEJA,
....: DirectSumEJA)
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()
"""
(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):
SETUP::
- sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
+ sage: from mjo.eja.eja_algebra import (random_eja,
+ ....: JordanSpinEJA,
....: RealSymmetricEJA,
....: DirectSumEJA)
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):
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()
y2 = pi_right(y)
return (x1.inner_product(y1) + x2.inner_product(y2))
+
+
+
+random_eja = ConcreteEuclideanJordanAlgebra.random_instance