+class CartesianProductEJA(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
+
+ Cartesian products of Cartesian products work::
+
+ sage: J1 = JordanSpinEJA(1)
+ sage: J2 = JordanSpinEJA(1)
+ sage: J3 = JordanSpinEJA(1)
+ sage: J = cartesian_product([J1,cartesian_product([J2,J3])])
+ sage: J.multiplication_table()
+ +----++----+----+----+
+ | * || e0 | e1 | e2 |
+ +====++====+====+====+
+ | e0 || e0 | 0 | 0 |
+ +----++----+----+----+
+ | e1 || 0 | e1 | 0 |
+ +----++----+----+----+
+ | e2 || 0 | 0 | e2 |
+ +----++----+----+----+
+ sage: HadamardEJA(3).multiplication_table()
+ +----++----+----+----+
+ | * || e0 | e1 | e2 |
+ +====++====+====+====+
+ | e0 || e0 | 0 | 0 |
+ +----++----+----+----+
+ | e1 || 0 | e1 | 0 |
+ +----++----+----+----+
+ | e2 || 0 | 0 | e2 |
+ +----++----+----+----+
+
+ 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, factors, **kwargs):
+ m = len(factors)
+ if m == 0:
+ return TrivialEJA()
+
+ self._sets = factors
+
+ field = factors[0].base_ring()
+ if not all( J.base_ring() == field for J in factors ):
+ raise ValueError("all factors must share the same base field")
+
+ associative = all( f.is_associative() for f in factors )
+
+ MS = self.matrix_space()
+ basis = []
+ zero = MS.zero()
+ for i in range(m):
+ for b in factors[i].matrix_basis():
+ z = list(zero)
+ z[i] = b
+ basis.append(z)
+
+ basis = tuple( MS(b) for b in basis )
+
+ # Define jordan/inner products that operate on that matrix_basis.
+ def jordan_product(x,y):
+ return MS(tuple(
+ (factors[i](x[i])*factors[i](y[i])).to_matrix()
+ for i in range(m)
+ ))
+
+ def inner_product(x, y):
+ return sum(
+ factors[i](x[i]).inner_product(factors[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().to_matrix() for J in factors)
+ self.one.set_cache(self(ones))
+ self.rank.set_cache(sum(J.rank() for J in factors))
+
+ def cartesian_factors(self):
+ # Copy/pasted from CombinatorialFreeModule_CartesianProduct.
+ return self._sets
+
+ def cartesian_factor(self, i):
+ r"""
+ Return the ``i``th factor of this algebra.
+ """
+ return self._sets[i]
+
+ def _repr_(self):
+ # Copy/pasted from CombinatorialFreeModule_CartesianProduct.
+ from sage.categories.cartesian_product import cartesian_product
+ return cartesian_product.symbol.join("%s" % factor
+ for factor in self._sets)
+
+ 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)
+
+ """
+ from sage.categories.cartesian_product import cartesian_product
+ return cartesian_product( [J.matrix_space()
+ for J in self.cartesian_factors()] )
+
+ @cached_method
+ def cartesian_projection(self, i):
+ r"""
+ SETUP::
+
+ sage: from mjo.eja.eja_algebra import (random_eja,
+ ....: JordanSpinEJA,
+ ....: HadamardEJA,
+ ....: RealSymmetricEJA,
+ ....: ComplexHermitianEJA)
+
+ EXAMPLES:
+
+ The projection morphisms are Euclidean Jordan algebra
+ operators::
+
+ sage: J1 = HadamardEJA(2)
+ sage: J2 = RealSymmetricEJA(2)
+ sage: J = cartesian_product([J1,J2])
+ sage: J.cartesian_projection(0)
+ Linear operator between finite-dimensional Euclidean Jordan
+ algebras represented by the matrix:
+ [1 0 0 0 0]
+ [0 1 0 0 0]
+ Domain: Euclidean Jordan algebra of dimension 2 over Algebraic
+ Real Field (+) Euclidean Jordan algebra of dimension 3 over
+ Algebraic Real Field
+ Codomain: Euclidean Jordan algebra of dimension 2 over Algebraic
+ Real Field
+ sage: J.cartesian_projection(1)
+ Linear operator between finite-dimensional Euclidean Jordan
+ algebras represented by the matrix:
+ [0 0 1 0 0]
+ [0 0 0 1 0]
+ [0 0 0 0 1]
+ Domain: Euclidean Jordan algebra of dimension 2 over Algebraic
+ Real Field (+) Euclidean Jordan algebra of dimension 3 over
+ Algebraic Real Field
+ Codomain: Euclidean Jordan algebra of dimension 3 over Algebraic
+ Real Field
+
+ The projections work the way you'd expect on the vector
+ representation of an element::
+
+ sage: J1 = JordanSpinEJA(2)
+ sage: J2 = ComplexHermitianEJA(2)
+ sage: J = cartesian_product([J1,J2])
+ sage: pi_left = J.cartesian_projection(0)
+ sage: pi_right = J.cartesian_projection(1)
+ sage: pi_left(J.one()).to_vector()
+ (1, 0)
+ sage: pi_right(J.one()).to_vector()
+ (1, 0, 0, 1)
+ sage: J.one().to_vector()
+ (1, 0, 1, 0, 0, 1)
+
+ TESTS:
+
+ The answer never changes::
+
+ sage: set_random_seed()
+ sage: J1 = random_eja()
+ sage: J2 = random_eja()
+ sage: J = cartesian_product([J1,J2])
+ sage: P0 = J.cartesian_projection(0)
+ sage: P1 = J.cartesian_projection(0)
+ sage: P0 == P1
+ True
+
+ """
+ offset = sum( self.cartesian_factor(k).dimension()
+ for k in range(i) )
+ Ji = self.cartesian_factor(i)
+ Pi = self._module_morphism(lambda j: Ji.monomial(j - offset),
+ codomain=Ji)
+
+ return FiniteDimensionalEJAOperator(self,Ji,Pi.matrix())
+
+ @cached_method
+ def cartesian_embedding(self, i):
+ r"""
+ SETUP::