]> gitweb.michael.orlitzky.com - sage.d.git/blobdiff - mjo/eja/eja_algebra.py
eja: define matrix basis/space for Cartesian product algebras.
[sage.d.git] / mjo / eja / eja_algebra.py
index c7c0df8de0e67aef03b391099ea6a72374dce13a..065339617b4b711dc52cd4a498cf59e59a6c18b1 100644 (file)
@@ -64,7 +64,8 @@ class FiniteDimensionalEJA(CombinatorialFreeModule):
                  associative=False,
                  check_field=True,
                  check_axioms=True,
-                 prefix='e'):
+                 prefix='e',
+                 category=None):
 
         if check_field:
             if not field.is_subring(RR):
@@ -93,11 +94,12 @@ class FiniteDimensionalEJA(CombinatorialFreeModule):
                 raise ValueError("inner-product is not commutative")
 
 
-        category = MagmaticAlgebras(field).FiniteDimensional()
-        category = category.WithBasis().Unital()
-        if associative:
-            # Element subalgebras can take advantage of this.
-            category = category.Associative()
+        if category is None:
+            category = MagmaticAlgebras(field).FiniteDimensional()
+            category = category.WithBasis().Unital()
+            if associative:
+                # Element subalgebras can take advantage of this.
+                category = category.Associative()
 
         # Call the superclass constructor so that we can use its from_vector()
         # method to build our multiplication table.
@@ -309,9 +311,9 @@ class FiniteDimensionalEJA(CombinatorialFreeModule):
         return ``True``, unless this algebra was constructed with
         ``check_axioms=False`` and passed an invalid multiplication table.
         """
-        return all( (self.monomial(i)**2)*(self.monomial(i)*self.monomial(j))
+        return all( (self.gens()[i]**2)*(self.gens()[i]*self.gens()[j])
                     ==
-                    (self.monomial(i))*((self.monomial(i)**2)*self.monomial(j))
+                    (self.gens()[i])*((self.gens()[i]**2)*self.gens()[j])
                     for i in range(self.dimension())
                     for j in range(self.dimension()) )
 
@@ -333,9 +335,9 @@ class FiniteDimensionalEJA(CombinatorialFreeModule):
         for i in range(self.dimension()):
             for j in range(self.dimension()):
                 for k in range(self.dimension()):
-                    x = self.monomial(i)
-                    y = self.monomial(j)
-                    z = self.monomial(k)
+                    x = self.gens()[i]
+                    y = self.gens()[j]
+                    z = self.gens()[k]
                     diff = (x*y).inner_product(z) - x.inner_product(y*z)
 
                     if self.base_ring().is_exact():
@@ -658,8 +660,8 @@ class FiniteDimensionalEJA(CombinatorialFreeModule):
 
         # And to each subsequent row, prepend an entry that belongs to
         # the left-side "header column."
-        M += [ [self.monomial(i)] + [ self.product_on_basis(i,j)
-                                      for j in range(n) ]
+        M += [ [self.gens()[i]] + [ self.product_on_basis(i,j)
+                                    for j in range(n) ]
                for i in range(n) ]
 
         return table(M, header_row=True, header_column=True, frame=True)
@@ -1127,7 +1129,7 @@ class FiniteDimensionalEJA(CombinatorialFreeModule):
         def L_x_i_j(i,j):
             # From a result in my book, these are the entries of the
             # basis representation of L_x.
-            return sum( vars[k]*self.monomial(k).operator().matrix()[i,j]
+            return sum( vars[k]*self.gens()[k].operator().matrix()[i,j]
                         for k in range(n) )
 
         L_x = matrix(F, n, n, L_x_i_j)
@@ -2688,7 +2690,8 @@ class CartesianProductEJA(CombinatorialFreeModule_CartesianProduct,
 
     SETUP::
 
-        sage: from mjo.eja.eja_algebra import (CartesianProductEJA,
+        sage: from mjo.eja.eja_algebra import (random_eja,
+        ....:                                  CartesianProductEJA,
         ....:                                  HadamardEJA,
         ....:                                  JordanSpinEJA,
         ....:                                  RealSymmetricEJA)
@@ -2709,12 +2712,50 @@ class CartesianProductEJA(CombinatorialFreeModule_CartesianProduct,
     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)
+        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
 
     TESTS:
 
@@ -2726,50 +2767,129 @@ class CartesianProductEJA(CombinatorialFreeModule_CartesianProduct,
         Traceback (most recent call last):
         ...
         ValueError: all factors must share the same base field
+
+    The "cached" Jordan and inner products are the componentwise
+    ones::
+
+        sage: set_random_seed()
+        sage: J1 = random_eja()
+        sage: J2 = random_eja()
+        sage: J = cartesian_product([J1,J2])
+        sage: x,y = J.random_elements(2)
+        sage: x*y == J.cartesian_jordan_product(x,y)
+        True
+        sage: x.inner_product(y) == J.cartesian_inner_product(x,y)
+        True
+
+    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
+
     """
     def __init__(self, modules, **kwargs):
-        CombinatorialFreeModule_CartesianProduct.__init__(self, modules, **kwargs)
+        CombinatorialFreeModule_CartesianProduct.__init__(self,
+                                                          modules,
+                                                          **kwargs)
         field = modules[0].base_ring()
         if not all( J.base_ring() == field for J in modules ):
             raise ValueError("all factors must share the same base field")
 
-        M = cartesian_product( [J.matrix_space() for J in modules] )
+        basis = tuple( b.to_vector().column() for b in self.basis() )
 
-        m = len(modules)
-        W = VectorSpace(field,m)
-        self._matrix_basis = []
-        for k in range(m):
-            for a in modules[k].matrix_basis():
-                v = W.zero().list()
-                v[k] = a
-                self._matrix_basis.append(M(v))
+        # Define jordan/inner products that operate on the basis.
+        def jordan_product(x_mat,y_mat):
+            x = self.from_vector(_mat2vec(x_mat))
+            y = self.from_vector(_mat2vec(y_mat))
+            return self.cartesian_jordan_product(x,y).to_vector().column()
 
-        self._matrix_basis = tuple(self._matrix_basis)
+        def inner_product(x_mat, y_mat):
+            x = self.from_vector(_mat2vec(x_mat))
+            y = self.from_vector(_mat2vec(y_mat))
+            return self.cartesian_inner_product(x,y)
 
-        n = len(self._matrix_basis)
-        # TODO:
-        #
-        # Initialize the FDEJA class, too. Does this override the
-        # initialization that we did for the
-        # CombinatorialFreeModule_CartesianProduct class? If not, we
-        # will probably have to duplicate some of the work (i.e. one
-        # of the constructors).  Since the CartesianProduct one is
-        # smaller, that makes the most sense to copy/paste if it comes
-        # down to that.
+        # Use whatever category the superclass came up with. Usually
+        # some join of the EJA and Cartesian product
+        # categories. There's no need to check the field since it
+        # already came from an EJA.
         #
-
+        # If you want the basis to be orthonormalized, orthonormalize
+        # the factors.
+        FiniteDimensionalEJA.__init__(self,
+                                      basis,
+                                      jordan_product,
+                                      inner_product,
+                                      field=field,
+                                      orthonormalize=False,
+                                      check_field=False,
+                                      check_axioms=False,
+                                      category=self.category())
+
+        ones = tuple(J.one() for J in modules)
+        self.one.set_cache(self._cartesian_product_of_elements(ones))
         self.rank.set_cache(sum(J.rank() for J in modules))
 
+        # Now that everything else is ready, we clobber our computed
+        # matrix basis with the "correct" one consisting of ordered
+        # tuples. Since we didn't orthonormalize our basis, we can
+        # create these from the basis that was handed to us; that is,
+        # we don't need to use the one that the earlier __init__()
+        # method came up with.
+        m = len(self.cartesian_factors())
+        MS = self.matrix_space()
+        self._matrix_basis = tuple(
+            MS(tuple( self.cartesian_projection(i)(b).to_matrix()
+                      for i in range(m) ))
+            for b in self.basis()
+        )
+
+    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)
+            ....:                                  RealSymmetricEJA,
+            ....:                                  ComplexHermitianEJA)
 
-        EXAMPLES::
+        EXAMPLES:
+
+        The projection morphisms are Euclidean Jordan algebra
+        operators::
 
             sage: J1 = HadamardEJA(2)
             sage: J2 = RealSymmetricEJA(2)
@@ -2796,6 +2916,21 @@ class CartesianProductEJA(CombinatorialFreeModule_CartesianProduct,
             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::
@@ -2811,12 +2946,8 @@ class CartesianProductEJA(CombinatorialFreeModule_CartesianProduct,
 
         """
         Ji = self.cartesian_factors()[i]
-        # We reimplement the CombinatorialFreeModule superclass method
-        # because if we don't, something gets messed up with the caching
-        # and the answer changes the second time you run it. See the TESTS.
-        Pi = self._module_morphism(lambda j_t: Ji.monomial(j_t[1])
-                                   if i == j_t[0] else Ji.zero(),
-                                   codomain=Ji)
+        # Requires the fix on Trac 31421/31422 to work!
+        Pi = super().cartesian_projection(i)
         return FiniteDimensionalEJAOperator(self,Ji,Pi.matrix())
 
     @cached_method
@@ -2825,18 +2956,18 @@ class CartesianProductEJA(CombinatorialFreeModule_CartesianProduct,
         SETUP::
 
             sage: from mjo.eja.eja_algebra import (random_eja,
+            ....:                                  JordanSpinEJA,
             ....:                                  HadamardEJA,
             ....:                                  RealSymmetricEJA)
 
-        EXAMPLES::
+        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
-            foo
-            sage: J.cartesian_embedding
-            bar
             sage: J.cartesian_embedding(0)
             Linear operator between finite-dimensional Euclidean Jordan
             algebras represented by the matrix:
@@ -2864,6 +2995,29 @@ class CartesianProductEJA(CombinatorialFreeModule_CartesianProduct,
             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::
@@ -2877,161 +3031,137 @@ class CartesianProductEJA(CombinatorialFreeModule_CartesianProduct,
             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]
-        # We reimplement the CombinatorialFreeModule superclass method
-        # because if we don't, something gets messed up with the caching
-        # and the answer changes the second time you run it. See the TESTS.
-        Ei = Ji._module_morphism(lambda t: self.monomial((i, t)), codomain=self)
+        # Requires the fix on Trac 31421/31422 to work!
+        Ei = super().cartesian_embedding(i)
         return FiniteDimensionalEJAOperator(Ji,self,Ei.matrix())
 
 
-FiniteDimensionalEJA.CartesianProduct = CartesianProductEJA
+    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.
 
-#     def projections(self):
-#         r"""
-#         Return a pair of projections onto this algebra's factors.
-
-#         SETUP::
-
-#             sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
-#             ....:                                  ComplexHermitianEJA,
-#             ....:                                  DirectSumEJA)
-
-#         EXAMPLES::
-
-#             sage: J1 = JordanSpinEJA(2)
-#             sage: J2 = ComplexHermitianEJA(2)
-#             sage: J = DirectSumEJA(J1,J2)
-#             sage: (pi_left, pi_right) = J.projections()
-#             sage: J.one().to_vector()
-#             (1, 0, 1, 0, 0, 1)
-#             sage: pi_left(J.one()).to_vector()
-#             (1, 0)
-#             sage: pi_right(J.one()).to_vector()
-#             (1, 0, 0, 1)
-
-#         """
-#         (J1,J2) = self.factors()
-#         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 = FiniteDimensionalEJAOperator(self,J1,P1)
-#         pi_right = FiniteDimensionalEJAOperator(self,J2,P2)
-#         return (pi_left, pi_right)
-
-#     def inclusions(self):
-#         r"""
-#         Return the pair of inclusion maps from our factors into us.
-
-#         SETUP::
-
-#             sage: from mjo.eja.eja_algebra import (random_eja,
-#             ....:                                  JordanSpinEJA,
-#             ....:                                  RealSymmetricEJA,
-#             ....:                                  DirectSumEJA)
-
-#         EXAMPLES::
-
-#             sage: J1 = JordanSpinEJA(3)
-#             sage: J2 = RealSymmetricEJA(2)
-#             sage: J = DirectSumEJA(J1,J2)
-#             sage: (iota_left, iota_right) = J.inclusions()
-#             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:
-
-#         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()
-#         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
-#         # 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 = FiniteDimensionalEJAOperator(J1,self,I1)
-#         iota_right = FiniteDimensionalEJAOperator(J2,self,I2)
-#         return (iota_left, iota_right)
-
-#     def inner_product(self, x, y):
-#         r"""
-#         The standard Cartesian inner-product.
-
-#         We project ``x`` and ``y`` onto our factors, and add up the
-#         inner-products from the subalgebras.
-
-#         SETUP::
-
-
-#             sage: from mjo.eja.eja_algebra import (HadamardEJA,
-#             ....:                                  QuaternionHermitianEJA,
-#             ....:                                  DirectSumEJA)
-
-#         EXAMPLE::
-
-#             sage: J1 = HadamardEJA(3,field=QQ)
-#             sage: J2 = QuaternionHermitianEJA(2,field=QQ,orthonormalize=False)
-#             sage: J = DirectSumEJA(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: J.one().inner_product(J.one())
-#             5
-
-#         """
-#         (pi_left, pi_right) = self.projections()
-#         x1 = pi_left(x)
-#         x2 = pi_right(x)
-#         y1 = pi_left(y)
-#         y2 = pi_right(y)
-
-#         return (x1.inner_product(y1) + x2.inner_product(y2))
+        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 )
+
+
+    def _element_constructor_(self, elt):
+        r"""
+        Construct an element of this algebra from an ordered tuple.
+
+        We just apply the element constructor from each of our factors
+        to the corresponding component of the tuple, and package up
+        the result.
+
+        SETUP::
+
+            sage: from mjo.eja.eja_algebra import (HadamardEJA,
+            ....:                                  RealSymmetricEJA)
+
+        EXAMPLES::
+
+            sage: J1 = HadamardEJA(3)
+            sage: J2 = RealSymmetricEJA(2)
+            sage: J = cartesian_product([J1,J2])
+            sage: J( (J1.matrix_basis()[1], J2.matrix_basis()[2]) )
+            e(0, 1) + e(1, 2)
+        """
+        m = len(self.cartesian_factors())
+        z = tuple( self.cartesian_factors()[i](elt[i]) for i in range(m) )
+        return self._cartesian_product_of_elements(z)
+
+    Element = FiniteDimensionalEJAElement
+
+
+FiniteDimensionalEJA.CartesianProduct = CartesianProductEJA
 random_eja = ConcreteEJA.random_instance