+ def __init__(self,
+ basis,
+ jordan_product,
+ inner_product,
+ field=AA,
+ orthonormalize=True,
+ prefix='e',
+ category=None,
+ check_field=True,
+ check_axioms=True):
+
+ if check_field:
+ # Abuse the check_field parameter to check that the entries of
+ # out basis (in ambient coordinates) are in the field QQ.
+ if not all( all(b_i in QQ for b_i in b.list()) for b in basis ):
+ raise TypeError("basis not rational")
+
+ # Temporary(?) hack to ensure that the matrix and vector bases
+ # are over the same ring.
+ basis = tuple( b.change_ring(field) for b in basis )
+
+ n = len(basis)
+ vector_basis = basis
+
+ from sage.structure.element import is_Matrix
+ basis_is_matrices = False
+
+ degree = 0
+ if n > 0:
+ if is_Matrix(basis[0]):
+ basis_is_matrices = True
+ from mjo.eja.eja_utils import _vec2mat
+ vector_basis = tuple( map(_mat2vec,basis) )
+ degree = basis[0].nrows()**2
+ else:
+ degree = basis[0].degree()
+
+ V = VectorSpace(field, degree)
+
+ # If we were asked to orthonormalize, and if the orthonormal
+ # basis is different from the given one, then we also want to
+ # compute multiplication and inner-product tables for the
+ # deorthonormalized basis. These can be used later to
+ # construct a deorthonormalized copy of this algebra over QQ
+ # in which several operations are much faster.
+ self._rational_algebra = None
+
+ if orthonormalize:
+ if self.base_ring() is not QQ:
+ # There's no point in constructing the extra algebra if this
+ # one is already rational. If the original basis is rational
+ # but normalization would make it irrational, then this whole
+ # constructor will just fail anyway as it tries to stick an
+ # irrational number into a rational algebra.
+ #
+ # Note: the same Jordan and inner-products work here,
+ # because they are necessarily defined with respect to
+ # ambient coordinates and not any particular basis.
+ self._rational_algebra = RationalBasisEuclideanJordanAlgebra(
+ basis,
+ jordan_product,
+ inner_product,
+ field=QQ,
+ orthonormalize=False,
+ prefix=prefix,
+ category=category,
+ check_field=False,
+ check_axioms=False)
+
+ # Compute the deorthonormalized tables before we orthonormalize
+ # the given basis. The "check" parameter here guarantees that
+ # the basis is linearly-independent.
+ W = V.span_of_basis( vector_basis, check=check_axioms)
+
+ # 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):
+ # given 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)
+
+ # 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) )
+
+ W = V.span_of_basis( vector_basis, check=check_axioms)
+
+ # 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 )
+
+ # If the superclass constructor is going to verify the
+ # symmetry of this table, it has better at least be
+ # square...
+ if check_axioms:
+ mult_table = [ [0 for j in range(n)] for i in range(n) ]
+ ip_table = [ [0 for j in range(n)] for i in range(n) ]
+ else:
+ mult_table = [ [0 for j in range(i+1)] for i in range(n) ]
+ ip_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 = 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)
+
+ elt = W.coordinate_vector(elt)
+ mult_table[i][j] = elt
+ ip_table[i][j] = ip
+ if check_axioms:
+ # The tables are square if we're verifying that they
+ # are commutative.
+ mult_table[j][i] = elt
+ ip_table[j][i] = ip
+
+ if basis_is_matrices:
+ for m in basis:
+ m.set_immutable()
+ else:
+ basis = tuple( x.column() for x in basis )
+
+ super().__init__(field,
+ mult_table,
+ ip_table,
+ prefix,
+ category,
+ basis, # matrix basis
+ check_field,
+ check_axioms)
+