+ Element = FiniteDimensionalEJAElement
+
+ def __init__(self,
+ basis,
+ jordan_product,
+ inner_product,
+ field=AA,
+ orthonormalize=True,
+ associative=False,
+ check_field=True,
+ check_axioms=True,
+ prefix='e'):
+
+ 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 the basis given to us wasn't over the field that it's
+ # supposed to be over, fix that. Or, you know, crash.
+ basis = tuple( b.change_ring(field) for b in basis )
+
+ 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()
+ 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.
+ n = len(basis)
+ super().__init__(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*e1 + 2*e2.
+ vector_basis = basis
+
+ from sage.structure.element import is_Matrix
+ basis_is_matrices = False
+
+ degree = 0
+ if n > 0:
+ if is_Matrix(basis[0]):
+ if basis[0].is_square():
+ # TODO: this ugly is_square() hack works around the problem
+ # of passing to_matrix()ed vectors in as the basis from a
+ # subalgebra. They aren't REALLY matrices, at least not of
+ # the type that we assume here... Ugh.
+ basis_is_matrices = True
+ from mjo.eja.eja_utils import _vec2mat
+ vector_basis = tuple( map(_mat2vec,basis) )
+ degree = basis[0].nrows()**2
+ else:
+ # convert from column matrices to vectors, yuck
+ basis = tuple( map(_mat2vec,basis) )
+ vector_basis = basis
+ degree = basis[0].degree()
+ else:
+ degree = basis[0].degree()
+
+ # Build an ambient space that fits...
+ V = VectorSpace(field, degree)
+
+ # We overwrite the name "vector_basis" in a second, but never modify it
+ # in place, to this effectively makes a copy of it.
+ deortho_vector_basis = vector_basis
+ self._deortho_matrix = None
+
+ if orthonormalize:
+ from mjo.eja.eja_utils import gram_schmidt
+ if basis_is_matrices:
+ vector_ip = lambda x,y: inner_product(_vec2mat(x), _vec2mat(y))
+ vector_basis = gram_schmidt(vector_basis, vector_ip)
+ else:
+ vector_basis = gram_schmidt(vector_basis, inner_product)
+
+ # Normalize the "matrix" basis, too!
+ basis = vector_basis
+
+ if basis_is_matrices:
+ basis = tuple( map(_vec2mat,basis) )
+
+ # Save the matrix "basis" for later... this is the last time we'll
+ # reference it in this constructor.
+ if basis_is_matrices:
+ self._matrix_basis = basis
+ else:
+ MS = MatrixSpace(self.base_ring(), degree, 1)
+ self._matrix_basis = tuple( MS(b) for b in basis )
+
+ # Now create the vector space for the algebra...
+ 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.zero(field, n)
+ self._multiplication_table = [ [0 for j in range(i+1)] for i in range(n) ]
+
+ print("vector_basis:")
+ print(vector_basis)
+ # 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 = vector_basis[i]
+ q_j = vector_basis[j]
+
+ if basis_is_matrices:
+ q_i = _vec2mat(q_i)
+ q_j = _vec2mat(q_j)
+
+ elt = jordan_product(q_i, q_j)
+ ip = inner_product(q_i, q_j)
+
+ if basis_is_matrices:
+ # do another mat2vec because the multiplication
+ # table is in terms of vectors
+ elt = _mat2vec(elt)
+
+ # TODO: the jordan product turns things back into
+ # matrices here even if they're supposed to be
+ # vectors. ugh. Can we get rid of vectors all together
+ # please?
+ elt = W.coordinate_vector(elt)
+ self._multiplication_table[i][j] = self.from_vector(elt)
+ 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")
+
+