+ def __init__(self, n, **kwargs):
+ # This is a special case of the BilinearFormEJA with the
+ # identity matrix as its bilinear form.
+ B = matrix.identity(ZZ, n)
+
+ # Don't orthonormalize because our basis is already
+ # orthonormal with respect to our inner-product.
+ if "orthonormalize" not in kwargs: kwargs["orthonormalize"] = False
+
+ # But also don't pass check_field=False here, because the user
+ # can pass in a field!
+ super(JordanSpinEJA, self).__init__(B, **kwargs)
+
+ @staticmethod
+ def _max_random_instance_size():
+ r"""
+ The maximum dimension of a random JordanSpinEJA.
+ """
+ return 5
+
+ @classmethod
+ def random_instance(cls, **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, **kwargs)
+
+
+class TrivialEJA(ConcreteEJA):
+ """
+ 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 Algebraic Real Field
+ sage: J.rank()
+ 0
+
+ """
+ def __init__(self, **kwargs):
+ jordan_product = lambda x,y: x
+ inner_product = lambda x,y: 0
+ basis = ()
+
+ # New defaults for keyword arguments
+ if "orthonormalize" not in kwargs: kwargs["orthonormalize"] = False
+ if "check_axioms" not in kwargs: kwargs["check_axioms"] = False
+
+ super(TrivialEJA, self).__init__(basis,
+ jordan_product,
+ inner_product,
+ **kwargs)
+ # 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, **kwargs):
+ # We don't take a "size" argument so the superclass method is
+ # inappropriate for us.
+ return cls(**kwargs)
+
+
+class CartesianProductEJA(CombinatorialFreeModule_CartesianProduct,
+ FiniteDimensionalEJA):
+ r"""
+ The external (orthogonal) direct sum of two or more Euclidean
+ Jordan algebras. Every Euclidean Jordan algebra decomposes into an
+ orthogonal direct sum of simple Euclidean Jordan algebras which is
+ then isometric to a Cartesian product, so no generality is lost by
+ providing only this construction.
+
+ SETUP::
+
+ sage: from mjo.eja.eja_algebra import (random_eja,
+ ....: CartesianProductEJA,
+ ....: HadamardEJA,
+ ....: JordanSpinEJA,
+ ....: RealSymmetricEJA)
+
+ EXAMPLES:
+
+ The Jordan product is inherited from our factors and implemented by
+ our CombinatorialFreeModule Cartesian product superclass::
+
+ sage: set_random_seed()
+ sage: J1 = HadamardEJA(2)
+ sage: J2 = RealSymmetricEJA(2)
+ sage: J = cartesian_product([J1,J2])
+ sage: x,y = J.random_elements(2)
+ sage: x*y in J
+ True
+
+ The ability to retrieve the original factors is implemented by our
+ CombinatorialFreeModule Cartesian product superclass::
+
+ sage: J1 = HadamardEJA(2, field=QQ)
+ sage: J2 = JordanSpinEJA(3, field=QQ)
+ sage: J = cartesian_product([J1,J2])
+ sage: J.cartesian_factors()
+ (Euclidean Jordan algebra of dimension 2 over Rational Field,
+ Euclidean Jordan algebra of dimension 3 over Rational Field)
+
+ You can provide more than two factors::
+
+ sage: J1 = HadamardEJA(2)
+ sage: J2 = JordanSpinEJA(3)
+ sage: J3 = RealSymmetricEJA(3)
+ sage: cartesian_product([J1,J2,J3])
+ Euclidean Jordan algebra of dimension 2 over Algebraic Real
+ Field (+) Euclidean Jordan algebra of dimension 3 over Algebraic
+ Real Field (+) Euclidean Jordan algebra of dimension 6 over
+ Algebraic Real Field
+
+ Rank is additive on a Cartesian product::
+
+ sage: J1 = HadamardEJA(1)
+ sage: J2 = RealSymmetricEJA(2)
+ sage: J = cartesian_product([J1,J2])
+ sage: J1.rank.clear_cache()
+ sage: J2.rank.clear_cache()
+ sage: J.rank.clear_cache()
+ sage: J.rank()
+ 3
+ sage: J.rank() == J1.rank() + J2.rank()
+ True
+
+ The same rank computation works over the rationals, with whatever
+ basis you like::
+
+ sage: J1 = HadamardEJA(1, field=QQ, orthonormalize=False)
+ sage: J2 = RealSymmetricEJA(2, field=QQ, orthonormalize=False)
+ sage: J = cartesian_product([J1,J2])
+ sage: J1.rank.clear_cache()
+ sage: J2.rank.clear_cache()
+ sage: J.rank.clear_cache()
+ sage: J.rank()
+ 3
+ sage: J.rank() == J1.rank() + J2.rank()
+ True
+
+ The product algebra will be associative if and only if all of its
+ components are associative::
+
+ sage: J1 = HadamardEJA(2)
+ sage: J1.is_associative()
+ True
+ sage: J2 = HadamardEJA(3)
+ sage: J2.is_associative()
+ True
+ sage: J3 = RealSymmetricEJA(3)
+ sage: J3.is_associative()
+ False
+ sage: CP1 = cartesian_product([J1,J2])
+ sage: CP1.is_associative()
+ True
+ sage: CP2 = cartesian_product([J1,J3])
+ sage: CP2.is_associative()
+ False
+
+ TESTS:
+
+ All factors must share the same base field::
+
+ sage: J1 = HadamardEJA(2, field=QQ)
+ sage: J2 = RealSymmetricEJA(2)
+ sage: CartesianProductEJA((J1,J2))
+ Traceback (most recent call last):
+ ...
+ ValueError: all factors must share the same base field
+
+ The cached unit element is the same one that would be computed::
+
+ sage: set_random_seed() # long time
+ sage: J1 = random_eja() # long time
+ sage: J2 = random_eja() # long time
+ sage: J = cartesian_product([J1,J2]) # long time
+ sage: actual = J.one() # long time
+ sage: J.one.clear_cache() # long time
+ sage: expected = J.one() # long time
+ sage: actual == expected # long time
+ True
+
+ """
+ Element = FiniteDimensionalEJAElement
+
+
+ def __init__(self, algebras, **kwargs):
+ CombinatorialFreeModule_CartesianProduct.__init__(self,
+ algebras,
+ **kwargs)
+ field = algebras[0].base_ring()
+ if not all( J.base_ring() == field for J in algebras ):
+ raise ValueError("all factors must share the same base field")
+
+ associative = all( m.is_associative() for m in algebras )
+
+ # The definition of matrix_space() and self.basis() relies
+ # only on the stuff in the CFM_CartesianProduct class, which
+ # we've already initialized.
+ Js = self.cartesian_factors()
+ m = len(Js)
+ MS = self.matrix_space()
+ basis = tuple(
+ MS(tuple( self.cartesian_projection(i)(b).to_matrix()
+ for i in range(m) ))
+ for b in self.basis()
+ )
+
+ # Define jordan/inner products that operate on that matrix_basis.
+ def jordan_product(x,y):
+ return MS(tuple(
+ (Js[i](x[i])*Js[i](y[i])).to_matrix() for i in range(m)
+ ))
+
+ def inner_product(x, y):
+ return sum(
+ Js[i](x[i]).inner_product(Js[i](y[i])) for i in range(m)
+ )
+
+ # There's no need to check the field since it already came
+ # from an EJA. Likewise the axioms are guaranteed to be
+ # satisfied, unless the guy writing this class sucks.
+ #
+ # If you want the basis to be orthonormalized, orthonormalize
+ # the factors.
+ FiniteDimensionalEJA.__init__(self,
+ basis,
+ jordan_product,
+ inner_product,
+ field=field,
+ orthonormalize=False,
+ associative=associative,
+ cartesian_product=True,
+ check_field=False,
+ check_axioms=False)
+
+ ones = tuple(J.one() for J in algebras)
+ self.one.set_cache(self._cartesian_product_of_elements(ones))
+ self.rank.set_cache(sum(J.rank() for J in algebras))
+
+ def matrix_space(self):
+ r"""
+ Return the space that our matrix basis lives in as a Cartesian
+ product.
+
+ SETUP::
+
+ sage: from mjo.eja.eja_algebra import (HadamardEJA,
+ ....: RealSymmetricEJA)
+
+ EXAMPLES::
+
+ sage: J1 = HadamardEJA(1)
+ sage: J2 = RealSymmetricEJA(2)
+ sage: J = cartesian_product([J1,J2])
+ sage: J.matrix_space()
+ The Cartesian product of (Full MatrixSpace of 1 by 1 dense
+ matrices over Algebraic Real Field, Full MatrixSpace of 2
+ by 2 dense matrices over Algebraic Real Field)