+ if self.is_regular():
+ return self.minimal_polynomial()
+ else:
+ raise NotImplementedError('irregular element')
+
+
+ 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 = JordanSpinSimpleEJA(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 = RealSymmetricSimpleEJA(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 = ComplexHermitianSimpleEJA(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 = JordanSpinSimpleEJA(2)
+ sage: e0,e1 = J.gens()
+ sage: x = e0 + e1
+ sage: x.det()
+ 0
+ sage: J = JordanSpinSimpleEJA(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 = JordanSpinSimpleEJA(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.
+ """
+ return not self.det().is_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 = JordanSpinSimpleEJA(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()