+ def _repr_(self):
+ """
+ Return a string representation of ``self``.
+ """
+ fmt = "Euclidean Jordan algebra of degree {} over {}"
+ return fmt.format(self.degree(), self.base_ring())
+
+
+
+ @cached_method
+ def _charpoly_coeff(self, i):
+ """
+ Return the coefficient polynomial "a_{i}" of this algebra's
+ general characteristic polynomial.
+
+ Having this be a separate cached method lets us compute and
+ store the trace/determinant (a_{r-1} and a_{0} respectively)
+ separate from the entire characteristic polynomial.
+ """
+ (A_of_x, x) = self._charpoly_matrix()
+ R = A_of_x.base_ring()
+ A_cols = A_of_x.columns()
+ A_cols[i] = (x**self.rank()).vector()
+ numerator = column_matrix(A_of_x.base_ring(), A_cols).det()
+ denominator = A_of_x.det()
+
+ # We're relying on the theory here to ensure that each a_i is
+ # indeed back in R, and the added negative signs are to make
+ # the whole charpoly expression sum to zero.
+ return R(-numerator/denominator)
+
+
+ @cached_method
+ def _charpoly_matrix(self):
+ """
+ Compute the matrix whose entries A_ij are polynomials in
+ X1,...,XN. This same matrix is used in more than one method and
+ it's not so fast to construct.
+ """
+ r = self.rank()
+ n = self.dimension()
+
+ # Construct a new algebra over a multivariate polynomial ring...
+ names = ['X' + str(i) for i in range(1,n+1)]
+ R = PolynomialRing(self.base_ring(), names)
+ J = FiniteDimensionalEuclideanJordanAlgebra(R,
+ self._multiplication_table,
+ rank=r)
+
+ idmat = identity_matrix(J.base_ring(), n)
+
+ x = J(vector(R, R.gens()))
+ l1 = [column_matrix((x**k).vector()) for k in range(r)]
+ l2 = [idmat.column(k-1).column() for k in range(r+1, n+1)]
+ A_of_x = block_matrix(R, 1, n, (l1 + l2))
+ return (A_of_x, x)
+
+
+ @cached_method
+ def characteristic_polynomial(self):
+ """
+ EXAMPLES:
+
+ The characteristic polynomial in the spin algebra is given in
+ Alizadeh, Example 11.11::
+
+ sage: J = JordanSpinEJA(3)
+ sage: p = J.characteristic_polynomial(); p
+ X1^2 - X2^2 - X3^2 + (-2*t)*X1 + t^2
+ sage: xvec = J.one().vector()
+ sage: p(*xvec)
+ t^2 - 2*t + 1
+
+ """
+ r = self.rank()
+ n = self.dimension()
+
+ # The list of coefficient polynomials a_1, a_2, ..., a_n.
+ a = [ self._charpoly_coeff(i) for i in range(n) ]
+
+ # We go to a bit of trouble here to reorder the
+ # indeterminates, so that it's easier to evaluate the
+ # characteristic polynomial at x's coordinates and get back
+ # something in terms of t, which is what we want.
+ R = a[0].parent()
+ S = PolynomialRing(self.base_ring(),'t')
+ t = S.gen(0)
+ S = PolynomialRing(S, R.variable_names())
+ t = S(t)
+
+ # Note: all entries past the rth should be zero. The
+ # coefficient of the highest power (x^r) is 1, but it doesn't
+ # appear in the solution vector which contains coefficients
+ # for the other powers (to make them sum to x^r).
+ if (r < n):
+ a[r] = 1 # corresponds to x^r
+ else:
+ # When the rank is equal to the dimension, trying to
+ # assign a[r] goes out-of-bounds.
+ a.append(1) # corresponds to x^r
+
+ return sum( a[k]*(t**k) for k in range(len(a)) )
+
+
+ def inner_product(self, x, y):
+ """
+ The inner product associated with this Euclidean Jordan algebra.
+
+ Defaults to the trace inner product, but can be overridden by
+ subclasses if they are sure that the necessary properties are
+ satisfied.
+
+ EXAMPLES:
+
+ The inner product must satisfy its axiom for this algebra to truly
+ be a Euclidean Jordan Algebra::
+
+ sage: set_random_seed()
+ sage: J = random_eja()
+ sage: x = J.random_element()
+ sage: y = J.random_element()
+ sage: z = J.random_element()
+ sage: (x*y).inner_product(z) == y.inner_product(x*z)
+ True
+
+ """
+ if (not x in self) or (not y in self):
+ raise TypeError("arguments must live in this algebra")
+ return x.trace_inner_product(y)
+
+
+ def natural_basis(self):
+ """
+ Return a more-natural representation of this algebra's basis.
+
+ Every finite-dimensional Euclidean Jordan Algebra is a direct
+ sum of five simple algebras, four of which comprise Hermitian
+ matrices. This method returns the original "natural" basis
+ for our underlying vector space. (Typically, the natural basis
+ is used to construct the multiplication table in the first place.)
+
+ Note that this will always return a matrix. The standard basis
+ in `R^n` will be returned as `n`-by-`1` column matrices.
+
+ EXAMPLES::
+
+ sage: J = RealSymmetricEJA(2)
+ sage: J.basis()
+ Family (e0, e1, e2)
+ sage: J.natural_basis()
+ (
+ [1 0] [0 1] [0 0]
+ [0 0], [1 0], [0 1]
+ )
+
+ ::
+
+ sage: J = JordanSpinEJA(2)
+ sage: J.basis()
+ Family (e0, e1)
+ sage: J.natural_basis()
+ (
+ [1] [0]
+ [0], [1]
+ )
+
+ """
+ if self._natural_basis is None:
+ return tuple( b.vector().column() for b in self.basis() )
+ else:
+ return self._natural_basis
+
+
+ def rank(self):
+ """
+ Return the rank of this EJA.
+ """
+ if self._rank is None:
+ raise ValueError("no rank specified at genesis")
+ else:
+ return self._rank
+
+
+ class Element(FiniteDimensionalAlgebraElement):
+ """
+ An element of a Euclidean Jordan algebra.
+ """
+
+ def __init__(self, A, elt=None):
+ """
+ EXAMPLES:
+
+ The identity in `S^n` is converted to the identity in the EJA::
+
+ sage: J = RealSymmetricEJA(3)
+ sage: I = identity_matrix(QQ,3)
+ sage: J(I) == J.one()
+ True
+
+ This skew-symmetric matrix can't be represented in the EJA::
+
+ sage: J = RealSymmetricEJA(3)
+ sage: A = matrix(QQ,3, lambda i,j: i-j)
+ sage: J(A)
+ Traceback (most recent call last):
+ ...
+ ArithmeticError: vector is not in free module
+
+ """
+ # Goal: if we're given a matrix, and if it lives in our
+ # parent algebra's "natural ambient space," convert it
+ # into an algebra element.
+ #
+ # The catch is, we make a recursive call after converting
+ # the given matrix into a vector that lives in the algebra.
+ # This we need to try the parent class initializer first,
+ # to avoid recursing forever if we're given something that
+ # already fits into the algebra, but also happens to live
+ # in the parent's "natural ambient space" (this happens with
+ # vectors in R^n).
+ try:
+ FiniteDimensionalAlgebraElement.__init__(self, A, elt)
+ except ValueError:
+ natural_basis = A.natural_basis()
+ if elt in natural_basis[0].matrix_space():
+ # Thanks for nothing! Matrix spaces aren't vector
+ # spaces in Sage, so we have to figure out its
+ # natural-basis coordinates ourselves.
+ V = VectorSpace(elt.base_ring(), elt.nrows()**2)
+ W = V.span( _mat2vec(s) for s in natural_basis )
+ coords = W.coordinates(_mat2vec(elt))
+ FiniteDimensionalAlgebraElement.__init__(self, A, coords)
+
+ def __pow__(self, n):
+ """
+ Return ``self`` raised to the power ``n``.
+
+ Jordan algebras are always power-associative; see for
+ example Faraut and Koranyi, Proposition II.1.2 (ii).
+
+ .. WARNING:
+
+ We have to override this because our superclass uses row vectors
+ instead of column vectors! We, on the other hand, assume column
+ vectors everywhere.
+
+ EXAMPLES::
+
+ sage: set_random_seed()
+ sage: x = random_eja().random_element()
+ sage: x.operator_matrix()*x.vector() == (x^2).vector()
+ True
+
+ A few examples of power-associativity::
+
+ sage: set_random_seed()
+ sage: x = random_eja().random_element()
+ sage: x*(x*x)*(x*x) == x^5
+ True
+ sage: (x*x)*(x*x*x) == x^5
+ True
+
+ We also know that powers operator-commute (Koecher, Chapter
+ III, Corollary 1)::
+
+ sage: set_random_seed()
+ sage: x = random_eja().random_element()
+ sage: m = ZZ.random_element(0,10)
+ sage: n = ZZ.random_element(0,10)
+ sage: Lxm = (x^m).operator_matrix()
+ sage: Lxn = (x^n).operator_matrix()
+ sage: Lxm*Lxn == Lxn*Lxm
+ True
+
+ """
+ A = self.parent()
+ if n == 0:
+ return A.one()
+ elif n == 1:
+ return self
+ else:
+ return A( (self.operator_matrix()**(n-1))*self.vector() )
+
+
+ def apply_univariate_polynomial(self, p):
+ """
+ Apply the univariate polynomial ``p`` to this element.
+
+ A priori, SageMath won't allow us to apply a univariate
+ polynomial to an element of an EJA, because we don't know
+ that EJAs are rings (they are usually not associative). Of
+ course, we know that EJAs are power-associative, so the
+ operation is ultimately kosher. This function sidesteps
+ the CAS to get the answer we want and expect.
+
+ EXAMPLES::
+
+ sage: R = PolynomialRing(QQ, 't')
+ sage: t = R.gen(0)
+ sage: p = t^4 - t^3 + 5*t - 2
+ sage: J = RealCartesianProductEJA(5)
+ sage: J.one().apply_univariate_polynomial(p) == 3*J.one()
+ True
+
+ TESTS:
+
+ We should always get back an element of the algebra::
+
+ sage: set_random_seed()
+ sage: p = PolynomialRing(QQ, 't').random_element()
+ sage: J = random_eja()
+ sage: x = J.random_element()
+ sage: x.apply_univariate_polynomial(p) in J
+ True
+
+ """
+ if len(p.variables()) > 1:
+ raise ValueError("not a univariate polynomial")
+ P = self.parent()
+ R = P.base_ring()
+ # Convert the coeficcients to the parent's base ring,
+ # because a priori they might live in an (unnecessarily)
+ # larger ring for which P.sum() would fail below.
+ cs = [ R(c) for c in p.coefficients(sparse=False) ]
+ return P.sum( cs[k]*(self**k) for k in range(len(cs)) )
+
+
+ def characteristic_polynomial(self):
+ """
+ Return the characteristic polynomial of this element.
+
+ EXAMPLES:
+
+ The rank of `R^3` is three, and the minimal polynomial of
+ the identity element is `(t-1)` from which it follows that
+ the characteristic polynomial should be `(t-1)^3`::
+
+ sage: J = RealCartesianProductEJA(3)
+ sage: J.one().characteristic_polynomial()
+ t^3 - 3*t^2 + 3*t - 1
+
+ Likewise, the characteristic of the zero element in the
+ rank-three algebra `R^{n}` should be `t^{3}`::
+
+ sage: J = RealCartesianProductEJA(3)
+ sage: J.zero().characteristic_polynomial()
+ t^3
+
+ The characteristic polynomial of an element should evaluate
+ to zero on that element::
+
+ sage: set_random_seed()
+ sage: x = RealCartesianProductEJA(3).random_element()
+ sage: p = x.characteristic_polynomial()
+ sage: x.apply_univariate_polynomial(p)
+ 0
+
+ """
+ p = self.parent().characteristic_polynomial()
+ return p(*self.vector())
+
+
+ def inner_product(self, other):
+ """
+ Return the parent algebra's inner product of myself and ``other``.
+
+ EXAMPLES:
+
+ The inner product in the Jordan spin algebra is the usual
+ inner product on `R^n` (this example only works because the
+ basis for the Jordan algebra is the standard basis in `R^n`)::
+
+ sage: J = JordanSpinEJA(3)
+ sage: x = vector(QQ,[1,2,3])
+ sage: y = vector(QQ,[4,5,6])
+ sage: x.inner_product(y)
+ 32
+ sage: J(x).inner_product(J(y))
+ 32
+
+ The inner product on `S^n` is `<X,Y> = trace(X*Y)`, where
+ multiplication is the usual matrix multiplication in `S^n`,
+ so the inner product of the identity matrix with itself
+ should be the `n`::
+
+ sage: J = RealSymmetricEJA(3)
+ sage: J.one().inner_product(J.one())
+ 3
+
+ Likewise, the inner product on `C^n` is `<X,Y> =
+ Re(trace(X*Y))`, where we must necessarily take the real
+ part because the product of Hermitian matrices may not be
+ Hermitian::
+
+ sage: J = ComplexHermitianEJA(3)
+ sage: J.one().inner_product(J.one())
+ 3
+
+ Ditto for the quaternions::
+
+ sage: J = QuaternionHermitianEJA(3)
+ sage: J.one().inner_product(J.one())
+ 3
+
+ TESTS:
+
+ Ensure that we can always compute an inner product, and that
+ it gives us back a real number::
+
+ sage: set_random_seed()
+ sage: J = random_eja()
+ sage: x = J.random_element()
+ sage: y = J.random_element()
+ sage: x.inner_product(y) in RR
+ True
+
+ """
+ P = self.parent()
+ if not other in P:
+ raise TypeError("'other' must live in the same algebra")
+
+ return P.inner_product(self, other)
+
+
+ def operator_commutes_with(self, other):
+ """
+ Return whether or not this element operator-commutes
+ with ``other``.
+
+ EXAMPLES:
+
+ The definition of a Jordan algebra says that any element
+ operator-commutes with its square::
+
+ sage: set_random_seed()
+ sage: x = random_eja().random_element()
+ sage: x.operator_commutes_with(x^2)
+ True
+
+ TESTS:
+
+ Test Lemma 1 from Chapter III of Koecher::
+
+ sage: set_random_seed()
+ sage: J = random_eja()
+ sage: u = J.random_element()
+ sage: v = J.random_element()
+ sage: lhs = u.operator_commutes_with(u*v)
+ sage: rhs = v.operator_commutes_with(u^2)
+ sage: lhs == rhs
+ True
+
+ """
+ if not other in self.parent():
+ raise TypeError("'other' must live in the same algebra")
+
+ A = self.operator_matrix()
+ B = other.operator_matrix()
+ return (A*B == B*A)
+
+
+ def det(self):
+ """
+ Return my determinant, the product of my eigenvalues.
+
+ EXAMPLES::
+
+ sage: J = JordanSpinEJA(2)
+ sage: e0,e1 = J.gens()
+ sage: x = e0 + e1
+ sage: x.det()
+ 0
+ sage: J = JordanSpinEJA(3)
+ sage: e0,e1,e2 = J.gens()
+ sage: x = e0 + e1 + e2
+ sage: x.det()
+ -1
+
+ """
+ cs = self.characteristic_polynomial().coefficients(sparse=False)
+ r = len(cs) - 1
+ if r >= 0:
+ return cs[0] * (-1)**r
+ else:
+ raise ValueError('charpoly had no coefficients')
+
+
+ def inverse(self):
+ """
+ Return the Jordan-multiplicative inverse of this element.
+
+ We can't use the superclass method because it relies on the
+ algebra being associative.
+
+ EXAMPLES:
+
+ The inverse in the spin factor algebra is given in Alizadeh's
+ Example 11.11::
+
+ sage: set_random_seed()
+ sage: n = ZZ.random_element(1,10)
+ sage: J = JordanSpinEJA(n)
+ sage: x = J.random_element()
+ sage: while x.is_zero():
+ ....: x = J.random_element()
+ sage: x_vec = x.vector()
+ sage: x0 = x_vec[0]
+ sage: x_bar = x_vec[1:]
+ sage: coeff = 1/(x0^2 - x_bar.inner_product(x_bar))
+ sage: inv_vec = x_vec.parent()([x0] + (-x_bar).list())
+ sage: x_inverse = coeff*inv_vec
+ sage: x.inverse() == J(x_inverse)
+ True
+
+ TESTS:
+
+ The identity element is its own inverse::
+
+ sage: set_random_seed()
+ sage: J = random_eja()
+ sage: J.one().inverse() == J.one()
+ True
+
+ If an element has an inverse, it acts like one. TODO: this
+ can be a lot less ugly once ``is_invertible`` doesn't crash
+ on irregular elements::
+
+ sage: set_random_seed()
+ sage: J = random_eja()
+ sage: x = J.random_element()
+ sage: try:
+ ....: x.inverse()*x == J.one()
+ ....: except:
+ ....: True
+ True
+
+ """
+ if self.parent().is_associative():
+ elt = FiniteDimensionalAlgebraElement(self.parent(), self)
+ return elt.inverse()
+
+ # TODO: we can do better once the call to is_invertible()
+ # doesn't crash on irregular elements.
+ #if not self.is_invertible():
+ # raise ValueError('element is not invertible')
+
+ # We do this a little different than the usual recursive
+ # call to a finite-dimensional algebra element, because we
+ # wind up with an inverse that lives in the subalgebra and
+ # we need information about the parent to convert it back.
+ V = self.span_of_powers()
+ assoc_subalg = self.subalgebra_generated_by()
+ # Mis-design warning: the basis used for span_of_powers()
+ # and subalgebra_generated_by() must be the same, and in
+ # the same order!
+ elt = assoc_subalg(V.coordinates(self.vector()))
+
+ # This will be in the subalgebra's coordinates...
+ fda_elt = FiniteDimensionalAlgebraElement(assoc_subalg, elt)
+ subalg_inverse = fda_elt.inverse()
+
+ # So we have to convert back...
+ basis = [ self.parent(v) for v in V.basis() ]
+ pairs = zip(subalg_inverse.vector(), basis)
+ return self.parent().linear_combination(pairs)
+
+
+ def is_invertible(self):
+ """
+ Return whether or not this element is invertible.
+
+ We can't use the superclass method because it relies on
+ the algebra being associative.
+
+ ALGORITHM:
+
+ The usual way to do this is to check if the determinant is
+ zero, but we need the characteristic polynomial for the
+ determinant. The minimal polynomial is a lot easier to get,
+ so we use Corollary 2 in Chapter V of Koecher to check
+ whether or not the paren't algebra's zero element is a root
+ of this element's minimal polynomial.
+
+ TESTS:
+
+ The identity element is always invertible::
+
+ sage: set_random_seed()
+ sage: J = random_eja()
+ sage: J.one().is_invertible()
+ True
+
+ The zero element is never invertible::
+
+ sage: set_random_seed()
+ sage: J = random_eja()
+ sage: J.zero().is_invertible()
+ False
+
+ """
+ zero = self.parent().zero()
+ p = self.minimal_polynomial()
+ return not (p(zero) == zero)
+
+
+ def is_nilpotent(self):
+ """
+ Return whether or not some power of this element is zero.
+
+ The superclass method won't work unless we're in an
+ associative algebra, and we aren't. However, we generate
+ an assocoative subalgebra and we're nilpotent there if and
+ only if we're nilpotent here (probably).
+
+ TESTS:
+
+ The identity element is never nilpotent::
+
+ sage: set_random_seed()
+ sage: random_eja().one().is_nilpotent()
+ False
+
+ The additive identity is always nilpotent::
+
+ sage: set_random_seed()
+ sage: random_eja().zero().is_nilpotent()
+ True
+
+ """
+ # The element we're going to call "is_nilpotent()" on.
+ # Either myself, interpreted as an element of a finite-
+ # dimensional algebra, or an element of an associative
+ # subalgebra.
+ elt = None
+
+ if self.parent().is_associative():
+ elt = FiniteDimensionalAlgebraElement(self.parent(), self)
+ else:
+ V = self.span_of_powers()
+ assoc_subalg = self.subalgebra_generated_by()
+ # Mis-design warning: the basis used for span_of_powers()
+ # and subalgebra_generated_by() must be the same, and in
+ # the same order!
+ elt = assoc_subalg(V.coordinates(self.vector()))
+
+ # Recursive call, but should work since elt lives in an
+ # associative algebra.
+ return elt.is_nilpotent()
+
+
+ def is_regular(self):
+ """
+ Return whether or not this is a regular element.
+
+ EXAMPLES:
+
+ The identity element always has degree one, but any element
+ linearly-independent from it is regular::
+
+ sage: J = JordanSpinEJA(5)
+ sage: J.one().is_regular()
+ False
+ sage: e0, e1, e2, e3, e4 = J.gens() # e0 is the identity
+ sage: for x in J.gens():
+ ....: (J.one() + x).is_regular()
+ False
+ True
+ True
+ True
+ True
+
+ """
+ return self.degree() == self.parent().rank()
+
+
+ def degree(self):
+ """
+ Compute the degree of this element the straightforward way
+ according to the definition; by appending powers to a list
+ and figuring out its dimension (that is, whether or not
+ they're linearly dependent).
+
+ EXAMPLES::
+
+ sage: J = JordanSpinEJA(4)
+ sage: J.one().degree()
+ 1
+ sage: e0,e1,e2,e3 = J.gens()
+ sage: (e0 - e1).degree()
+ 2
+
+ In the spin factor algebra (of rank two), all elements that
+ aren't multiples of the identity are regular::
+
+ sage: set_random_seed()
+ sage: n = ZZ.random_element(1,10)
+ sage: J = JordanSpinEJA(n)
+ sage: x = J.random_element()
+ sage: x == x.coefficient(0)*J.one() or x.degree() == 2
+ True
+
+ """
+ return self.span_of_powers().dimension()
+
+
+ def minimal_polynomial(self):
+ """
+ Return the minimal polynomial of this element,
+ as a function of the variable `t`.
+
+ ALGORITHM:
+
+ We restrict ourselves to the associative subalgebra
+ generated by this element, and then return the minimal
+ polynomial of this element's operator matrix (in that
+ subalgebra). This works by Baes Proposition 2.3.16.
+
+ TESTS:
+
+ The minimal polynomial of the identity and zero elements are
+ always the same::
+
+ sage: set_random_seed()
+ sage: J = random_eja()
+ sage: J.one().minimal_polynomial()
+ t - 1
+ sage: J.zero().minimal_polynomial()
+ t
+
+ The degree of an element is (by one definition) the degree
+ of its minimal polynomial::
+
+ sage: set_random_seed()
+ sage: x = random_eja().random_element()
+ sage: x.degree() == x.minimal_polynomial().degree()
+ True
+
+ The minimal polynomial and the characteristic polynomial coincide
+ and are known (see Alizadeh, Example 11.11) for all elements of
+ the spin factor algebra that aren't scalar multiples of the
+ identity::
+
+ sage: set_random_seed()
+ sage: n = ZZ.random_element(2,10)
+ sage: J = JordanSpinEJA(n)
+ sage: y = J.random_element()
+ sage: while y == y.coefficient(0)*J.one():
+ ....: y = J.random_element()
+ sage: y0 = y.vector()[0]
+ sage: y_bar = y.vector()[1:]
+ sage: actual = y.minimal_polynomial()
+ sage: t = PolynomialRing(J.base_ring(),'t').gen(0)
+ sage: expected = t^2 - 2*y0*t + (y0^2 - norm(y_bar)^2)
+ sage: bool(actual == expected)
+ True
+
+ The minimal polynomial should always kill its element::
+
+ sage: set_random_seed()
+ sage: x = random_eja().random_element()
+ sage: p = x.minimal_polynomial()
+ sage: x.apply_univariate_polynomial(p)
+ 0
+
+ """
+ V = self.span_of_powers()
+ assoc_subalg = self.subalgebra_generated_by()
+ # Mis-design warning: the basis used for span_of_powers()
+ # and subalgebra_generated_by() must be the same, and in
+ # the same order!
+ elt = assoc_subalg(V.coordinates(self.vector()))
+
+ # We get back a symbolic polynomial in 'x' but want a real
+ # polynomial in 't'.
+ p_of_x = elt.operator_matrix().minimal_polynomial()
+ return p_of_x.change_variable_name('t')
+
+
+ def natural_representation(self):
+ """
+ Return a more-natural representation of this element.
+
+ Every finite-dimensional Euclidean Jordan Algebra is a
+ direct sum of five simple algebras, four of which comprise
+ Hermitian matrices. This method returns the original
+ "natural" representation of this element as a Hermitian
+ matrix, if it has one. If not, you get the usual representation.
+
+ EXAMPLES::
+
+ sage: J = ComplexHermitianEJA(3)
+ sage: J.one()
+ e0 + e5 + e8
+ sage: J.one().natural_representation()
+ [1 0 0 0 0 0]
+ [0 1 0 0 0 0]
+ [0 0 1 0 0 0]
+ [0 0 0 1 0 0]
+ [0 0 0 0 1 0]
+ [0 0 0 0 0 1]
+
+ ::
+
+ sage: J = QuaternionHermitianEJA(3)
+ sage: J.one()
+ e0 + e9 + e14
+ sage: J.one().natural_representation()
+ [1 0 0 0 0 0 0 0 0 0 0 0]
+ [0 1 0 0 0 0 0 0 0 0 0 0]
+ [0 0 1 0 0 0 0 0 0 0 0 0]
+ [0 0 0 1 0 0 0 0 0 0 0 0]
+ [0 0 0 0 1 0 0 0 0 0 0 0]
+ [0 0 0 0 0 1 0 0 0 0 0 0]
+ [0 0 0 0 0 0 1 0 0 0 0 0]
+ [0 0 0 0 0 0 0 1 0 0 0 0]
+ [0 0 0 0 0 0 0 0 1 0 0 0]
+ [0 0 0 0 0 0 0 0 0 1 0 0]
+ [0 0 0 0 0 0 0 0 0 0 1 0]
+ [0 0 0 0 0 0 0 0 0 0 0 1]
+
+ """
+ B = self.parent().natural_basis()
+ W = B[0].matrix_space()
+ return W.linear_combination(zip(self.vector(), B))
+
+
+ def operator_matrix(self):
+ """
+ Return the matrix that represents left- (or right-)
+ multiplication by this element in the parent algebra.
+
+ We have to override this because the superclass method
+ returns a matrix that acts on row vectors (that is, on
+ the right).
+
+ EXAMPLES:
+
+ Test the first polarization identity from my notes, Koecher Chapter
+ III, or from Baes (2.3)::
+
+ sage: set_random_seed()
+ sage: J = random_eja()
+ sage: x = J.random_element()
+ sage: y = J.random_element()
+ sage: Lx = x.operator_matrix()
+ sage: Ly = y.operator_matrix()
+ sage: Lxx = (x*x).operator_matrix()
+ sage: Lxy = (x*y).operator_matrix()
+ sage: bool(2*Lx*Lxy + Ly*Lxx == 2*Lxy*Lx + Lxx*Ly)
+ True
+
+ Test the second polarization identity from my notes or from
+ Baes (2.4)::
+
+ sage: set_random_seed()
+ sage: J = random_eja()
+ sage: x = J.random_element()
+ sage: y = J.random_element()
+ sage: z = J.random_element()
+ sage: Lx = x.operator_matrix()
+ sage: Ly = y.operator_matrix()
+ sage: Lz = z.operator_matrix()
+ sage: Lzy = (z*y).operator_matrix()
+ sage: Lxy = (x*y).operator_matrix()
+ sage: Lxz = (x*z).operator_matrix()
+ sage: bool(Lx*Lzy + Lz*Lxy + Ly*Lxz == Lzy*Lx + Lxy*Lz + Lxz*Ly)
+ True
+
+ Test the third polarization identity from my notes or from
+ Baes (2.5)::
+
+ sage: set_random_seed()
+ sage: J = random_eja()
+ sage: u = J.random_element()
+ sage: y = J.random_element()
+ sage: z = J.random_element()
+ sage: Lu = u.operator_matrix()
+ sage: Ly = y.operator_matrix()
+ sage: Lz = z.operator_matrix()
+ sage: Lzy = (z*y).operator_matrix()
+ sage: Luy = (u*y).operator_matrix()
+ sage: Luz = (u*z).operator_matrix()
+ sage: Luyz = (u*(y*z)).operator_matrix()
+ sage: lhs = Lu*Lzy + Lz*Luy + Ly*Luz
+ sage: rhs = Luyz + Ly*Lu*Lz + Lz*Lu*Ly
+ sage: bool(lhs == rhs)
+ True
+
+ """
+ fda_elt = FiniteDimensionalAlgebraElement(self.parent(), self)
+ return fda_elt.matrix().transpose()
+
+
+ def quadratic_representation(self, other=None):
+ """
+ Return the quadratic representation of this element.
+
+ EXAMPLES:
+
+ The explicit form in the spin factor algebra is given by
+ Alizadeh's Example 11.12::
+
+ sage: set_random_seed()
+ sage: n = ZZ.random_element(1,10)
+ sage: J = JordanSpinEJA(n)
+ sage: x = J.random_element()
+ sage: x_vec = x.vector()
+ sage: x0 = x_vec[0]
+ sage: x_bar = x_vec[1:]
+ sage: A = matrix(QQ, 1, [x_vec.inner_product(x_vec)])
+ sage: B = 2*x0*x_bar.row()
+ sage: C = 2*x0*x_bar.column()
+ sage: D = identity_matrix(QQ, n-1)
+ sage: D = (x0^2 - x_bar.inner_product(x_bar))*D
+ sage: D = D + 2*x_bar.tensor_product(x_bar)
+ sage: Q = block_matrix(2,2,[A,B,C,D])
+ sage: Q == x.quadratic_representation()
+ True
+
+ Test all of the properties from Theorem 11.2 in Alizadeh::
+
+ sage: set_random_seed()
+ sage: J = random_eja()
+ sage: x = J.random_element()
+ sage: y = J.random_element()
+
+ Property 1:
+
+ sage: actual = x.quadratic_representation(y)
+ sage: expected = ( (x+y).quadratic_representation()
+ ....: -x.quadratic_representation()
+ ....: -y.quadratic_representation() ) / 2
+ sage: actual == expected
+ True