+ @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
+
+ """
+ Ji = self.cartesian_factors()[i]
+ # Requires the fix on Trac 31421/31422 to work!
+ Pi = super().cartesian_projection(i)
+ return FiniteDimensionalEJAOperator(self,Ji,Pi.matrix())
+
+ @cached_method
+ def cartesian_embedding(self, i):
+ r"""
+ SETUP::
+
+ sage: from mjo.eja.eja_algebra import (random_eja,
+ ....: JordanSpinEJA,
+ ....: HadamardEJA,
+ ....: RealSymmetricEJA)
+
+ EXAMPLES:
+
+ The embedding morphisms are Euclidean Jordan algebra
+ operators::
+
+ sage: J1 = HadamardEJA(2)
+ sage: J2 = RealSymmetricEJA(2)
+ sage: J = cartesian_product([J1,J2])
+ sage: J.cartesian_embedding(0)
+ Linear operator between finite-dimensional Euclidean Jordan
+ algebras represented by the matrix:
+ [1 0]
+ [0 1]
+ [0 0]
+ [0 0]
+ [0 0]
+ Domain: Euclidean Jordan algebra of dimension 2 over
+ Algebraic Real Field
+ Codomain: Euclidean Jordan algebra of dimension 2 over
+ Algebraic Real Field (+) Euclidean Jordan algebra of
+ dimension 3 over Algebraic Real Field
+ sage: J.cartesian_embedding(1)
+ Linear operator between finite-dimensional Euclidean Jordan
+ algebras represented by the matrix:
+ [0 0 0]
+ [0 0 0]
+ [1 0 0]
+ [0 1 0]
+ [0 0 1]
+ Domain: Euclidean Jordan algebra of dimension 3 over
+ Algebraic Real Field
+ Codomain: Euclidean Jordan algebra of dimension 2 over
+ Algebraic Real Field (+) Euclidean Jordan algebra of
+ dimension 3 over Algebraic Real Field
+
+ The embeddings work the way you'd expect on the vector
+ representation of an element::
+
+ sage: J1 = JordanSpinEJA(3)
+ sage: J2 = RealSymmetricEJA(2)
+ sage: J = cartesian_product([J1,J2])
+ sage: iota_left = J.cartesian_embedding(0)
+ sage: iota_right = J.cartesian_embedding(1)
+ sage: iota_left(J1.zero()) == J.zero()
+ True
+ sage: iota_right(J2.zero()) == J.zero()
+ True
+ sage: J1.one().to_vector()
+ (1, 0, 0)
+ sage: iota_left(J1.one()).to_vector()
+ (1, 0, 0, 0, 0, 0)
+ sage: J2.one().to_vector()
+ (1, 0, 1)
+ sage: iota_right(J2.one()).to_vector()
+ (0, 0, 0, 1, 0, 1)
+ sage: J.one().to_vector()
+ (1, 0, 0, 1, 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: E0 = J.cartesian_embedding(0)
+ sage: E1 = J.cartesian_embedding(0)
+ sage: E0 == E1
+ True
+
+ 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 = cartesian_product([J1,J2])
+ sage: iota_left = J.cartesian_embedding(0)
+ sage: iota_right = J.cartesian_embedding(1)
+ sage: pi_left = J.cartesian_projection(0)
+ sage: pi_right = J.cartesian_projection(1)
+ 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
+
+ """
+ Ji = self.cartesian_factors()[i]
+ # Requires the fix on Trac 31421/31422 to work!
+ Ei = super().cartesian_embedding(i)
+ return FiniteDimensionalEJAOperator(Ji,self,Ei.matrix())
+
+
+ def cartesian_jordan_product(self, x, y):
+ r"""
+ The componentwise Jordan product.
+
+ We project ``x`` and ``y`` onto our factors, and add up the
+ Jordan products from the subalgebras. This may still be useful
+ after (if) the default Jordan product in the Cartesian product
+ algebra is overridden.
+
+ SETUP::
+
+ sage: from mjo.eja.eja_algebra import (HadamardEJA,
+ ....: JordanSpinEJA)
+
+ EXAMPLE::
+
+ sage: J1 = HadamardEJA(3)
+ sage: J2 = JordanSpinEJA(3)
+ sage: J = cartesian_product([J1,J2])
+ sage: x1 = J1.from_vector(vector(QQ,(1,2,1)))
+ sage: y1 = J1.from_vector(vector(QQ,(1,0,2)))
+ sage: x2 = J2.from_vector(vector(QQ,(1,2,3)))
+ sage: y2 = J2.from_vector(vector(QQ,(1,1,1)))
+ sage: z1 = J.from_vector(vector(QQ,(1,2,1,1,2,3)))
+ sage: z2 = J.from_vector(vector(QQ,(1,0,2,1,1,1)))
+ sage: (x1*y1).to_vector()
+ (1, 0, 2)
+ sage: (x2*y2).to_vector()
+ (6, 3, 4)
+ sage: J.cartesian_jordan_product(z1,z2).to_vector()
+ (1, 0, 2, 6, 3, 4)
+
+ """
+ m = len(self.cartesian_factors())
+ projections = ( self.cartesian_projection(i) for i in range(m) )
+ products = ( P(x)*P(y) for P in projections )
+ return self._cartesian_product_of_elements(tuple(products))
+
+ def cartesian_inner_product(self, x, y):
+ r"""
+ The standard componentwise Cartesian inner-product.
+
+ We project ``x`` and ``y`` onto our factors, and add up the
+ inner-products from the subalgebras. This may still be useful
+ after (if) the default inner product in the Cartesian product
+ algebra is overridden.
+
+ SETUP::
+
+ sage: from mjo.eja.eja_algebra import (HadamardEJA,
+ ....: QuaternionHermitianEJA)
+
+ EXAMPLE::
+
+ sage: J1 = HadamardEJA(3,field=QQ)
+ sage: J2 = QuaternionHermitianEJA(2,field=QQ,orthonormalize=False)
+ sage: J = cartesian_product([J1,J2])
+ sage: x1 = J1.one()
+ sage: x2 = x1
+ sage: y1 = J2.one()
+ sage: y2 = y1
+ sage: x1.inner_product(x2)
+ 3
+ sage: y1.inner_product(y2)
+ 2
+ sage: z1 = J._cartesian_product_of_elements((x1,y1))
+ sage: z2 = J._cartesian_product_of_elements((x2,y2))
+ sage: J.cartesian_inner_product(z1,z2)
+ 5
+
+ """
+ m = len(self.cartesian_factors())
+ projections = ( self.cartesian_projection(i) for i in range(m) )
+ return sum( P(x).inner_product(P(y)) for P in projections )
+
+
+ Element = FiniteDimensionalEJAElement
+
+
+class FiniteDimensionalEJA_CartesianProduct(CartesianProductEJA):
+ r"""
+ A wrapper around the :class:`CartesianProductEJA` class that gets
+ used by the ``cartesian_product`` functor. Its one job is to set
+ ``orthonormalize=False``, since ``cartesian_product()`` can't be
+ made to pass that option through. And if we try to orthonormalize
+ over the rationals, we get conversion errors. If you want a non-
+ standard Jordan product or inner product, or if you want to
+ orthonormalize the basis, use :class:`CartesianProductEJA`
+ directly.
+ """
+ def __init__(self, modules, **options):
+ CombinatorialFreeModule_CartesianProduct.__init__(self,
+ modules,
+ **options)
+ CartesianProductEJA.__init__(self, modules, orthonormalize=False)
+
+
+FiniteDimensionalEJA.CartesianProduct = FiniteDimensionalEJA_CartesianProduct
+random_eja = ConcreteEJA.random_instance