+ basis,
+ jordan_product,
+ inner_product,
+ field=AA,
+ matrix_space=None,
+ orthonormalize=True,
+ associative=None,
+ cartesian_product=False,
+ check_field=True,
+ check_axioms=True,
+ prefix="b"):
+
+ n = len(basis)
+
+ if check_field:
+ if not field.is_subring(RR):
+ # Note: this does return true for the real algebraic
+ # field, the rationals, and any quadratic field where
+ # we've specified a real embedding.
+ raise ValueError("scalar field is not real")
+
+ if check_axioms:
+ # Check commutativity of the Jordan and inner-products.
+ # This has to be done before we build the multiplication
+ # and inner-product tables/matrices, because we take
+ # advantage of symmetry in the process.
+ if not all( jordan_product(bi,bj) == jordan_product(bj,bi)
+ for bi in basis
+ for bj in basis ):
+ raise ValueError("Jordan product is not commutative")
+
+ if not all( inner_product(bi,bj) == inner_product(bj,bi)
+ for bi in basis
+ for bj in basis ):
+ raise ValueError("inner-product is not commutative")
+
+
+ category = MagmaticAlgebras(field).FiniteDimensional()
+ category = category.WithBasis().Unital().Commutative()
+
+ if n <= 1:
+ # All zero- and one-dimensional algebras are just the real
+ # numbers with (some positive multiples of) the usual
+ # multiplication as its Jordan and inner-product.
+ associative = True
+ if associative is None:
+ # We should figure it out. As with check_axioms, we have to do
+ # this without the help of the _jordan_product_is_associative()
+ # method because we need to know the category before we
+ # initialize the algebra.
+ associative = all( jordan_product(jordan_product(bi,bj),bk)
+ ==
+ jordan_product(bi,jordan_product(bj,bk))
+ for bi in basis
+ for bj in basis
+ for bk in basis)
+
+ if associative:
+ # Element subalgebras can take advantage of this.
+ category = category.Associative()
+ if cartesian_product:
+ # Use join() here because otherwise we only get the
+ # "Cartesian product of..." and not the things themselves.
+ category = category.join([category,
+ category.CartesianProducts()])
+
+ # Call the superclass constructor so that we can use its from_vector()
+ # method to build our multiplication table.
+ CombinatorialFreeModule.__init__(self,
+ field,
+ range(n),
+ prefix=prefix,
+ category=category,
+ bracket=False)
+
+ # Now comes all of the hard work. We'll be constructing an
+ # ambient vector space V that our (vectorized) basis lives in,
+ # as well as a subspace W of V spanned by those (vectorized)
+ # basis elements. The W-coordinates are the coefficients that
+ # we see in things like x = 1*b1 + 2*b2.
+ vector_basis = basis
+
+ degree = 0
+ if n > 0:
+ degree = len(_all2list(basis[0]))
+
+ # Build an ambient space that fits our matrix basis when
+ # written out as "long vectors."
+ V = VectorSpace(field, degree)
+
+ # The matrix that will hole the orthonormal -> unorthonormal
+ # coordinate transformation.
+ self._deortho_matrix = None
+
+ if orthonormalize:
+ # Save a copy of the un-orthonormalized basis for later.
+ # Convert it to ambient V (vector) coordinates while we're
+ # at it, because we'd have to do it later anyway.
+ deortho_vector_basis = tuple( V(_all2list(b)) for b in basis )
+
+ from mjo.eja.eja_utils import gram_schmidt
+ basis = tuple(gram_schmidt(basis, inner_product))
+
+ # Save the (possibly orthonormalized) matrix basis for
+ # later, as well as the space that its elements live in.
+ # In most cases we can deduce the matrix space, but when
+ # n == 0 (that is, there are no basis elements) we cannot.
+ self._matrix_basis = basis
+ if matrix_space is None:
+ self._matrix_space = self._matrix_basis[0].parent()
+ else:
+ self._matrix_space = matrix_space
+
+ # Now create the vector space for the algebra, which will have
+ # its own set of non-ambient coordinates (in terms of the
+ # supplied basis).
+ vector_basis = tuple( V(_all2list(b)) for b in basis )
+ W = V.span_of_basis( vector_basis, check=check_axioms)
+
+ if orthonormalize:
+ # Now "W" is the vector space of our algebra coordinates. The
+ # variables "X1", "X2",... refer to the entries of vectors in
+ # W. Thus to convert back and forth between the orthonormal
+ # coordinates and the given ones, we need to stick the original
+ # basis in W.
+ U = V.span_of_basis( deortho_vector_basis, check=check_axioms)
+ self._deortho_matrix = matrix( U.coordinate_vector(q)
+ for q in vector_basis )
+
+
+ # Now we actually compute the multiplication and inner-product
+ # tables/matrices using the possibly-orthonormalized basis.
+ self._inner_product_matrix = matrix.identity(field, n)
+ self._multiplication_table = [ [0 for j in range(i+1)]
+ for i in range(n) ]
+
+ # Note: the Jordan and inner-products are defined in terms
+ # of the ambient basis. It's important that their arguments
+ # are in ambient coordinates as well.
+ for i in range(n):
+ for j in range(i+1):
+ # ortho basis w.r.t. ambient coords
+ q_i = basis[i]
+ q_j = basis[j]
+
+ # The jordan product returns a matrixy answer, so we
+ # have to convert it to the algebra coordinates.
+ elt = jordan_product(q_i, q_j)
+ elt = W.coordinate_vector(V(_all2list(elt)))
+ self._multiplication_table[i][j] = self.from_vector(elt)
+
+ if not orthonormalize:
+ # If we're orthonormalizing the basis with respect
+ # to an inner-product, then the inner-product
+ # matrix with respect to the resulting basis is
+ # just going to be the identity.
+ ip = inner_product(q_i, q_j)
+ self._inner_product_matrix[i,j] = ip
+ self._inner_product_matrix[j,i] = ip
+
+ self._inner_product_matrix._cache = {'hermitian': True}
+ self._inner_product_matrix.set_immutable()
+
+ if check_axioms:
+ if not self._is_jordanian():
+ raise ValueError("Jordan identity does not hold")
+ if not self._inner_product_is_associative():
+ raise ValueError("inner product is not associative")
+
+
+ def _coerce_map_from_base_ring(self):
+ """
+ Disable the map from the base ring into the algebra.
+
+ Performing a nonsense conversion like this automatically
+ is counterpedagogical. The fallback is to try the usual
+ element constructor, which should also fail.
+
+ SETUP::
+
+ sage: from mjo.eja.eja_algebra import random_eja
+
+ TESTS::
+
+ sage: set_random_seed()
+ sage: J = random_eja()
+ sage: J(1)
+ Traceback (most recent call last):
+ ...
+ ValueError: not an element of this algebra
+
+ """
+ return None
+
+
+ def product_on_basis(self, i, j):
+ r"""
+ Returns the Jordan product of the `i` and `j`th basis elements.
+
+ This completely defines the Jordan product on the algebra, and
+ is used direclty by our superclass machinery to implement
+ :meth:`product`.
+
+ SETUP::
+
+ sage: from mjo.eja.eja_algebra import random_eja
+
+ TESTS::
+
+ sage: set_random_seed()
+ sage: J = random_eja()
+ sage: n = J.dimension()
+ sage: bi = J.zero()
+ sage: bj = J.zero()
+ sage: bi_bj = J.zero()*J.zero()
+ sage: if n > 0:
+ ....: i = ZZ.random_element(n)
+ ....: j = ZZ.random_element(n)
+ ....: bi = J.monomial(i)
+ ....: bj = J.monomial(j)
+ ....: bi_bj = J.product_on_basis(i,j)
+ sage: bi*bj == bi_bj
+ True
+
+ """
+ # We only stored the lower-triangular portion of the
+ # multiplication table.
+ if j <= i:
+ return self._multiplication_table[i][j]
+ else:
+ return self._multiplication_table[j][i]
+
+ def inner_product(self, x, y):