]> gitweb.michael.orlitzky.com - sage.d.git/blobdiff - mjo/eja/eja_element.py
eja: define the trace of an element in a trivial algebra.
[sage.d.git] / mjo / eja / eja_element.py
index 107603ce712ecf79d6de89a2e4fd573f17738798..bd45b179541487bd82e3b06768a902a77cd0899d 100644 (file)
@@ -1,3 +1,7 @@
+# -*- coding: utf-8 -*-
+
+from itertools import izip
+
 from sage.matrix.constructor import matrix
 from sage.modules.free_module import VectorSpace
 from sage.modules.with_basis.indexed_element import IndexedFreeModuleElement
 from sage.matrix.constructor import matrix
 from sage.modules.free_module import VectorSpace
 from sage.modules.with_basis.indexed_element import IndexedFreeModuleElement
@@ -25,69 +29,6 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement):
                       dir(self.__class__) )
 
 
                       dir(self.__class__) )
 
 
-    def __init__(self, A, elt):
-        """
-
-        SETUP::
-
-            sage: from mjo.eja.eja_algebra import (RealSymmetricEJA,
-            ....:                                  random_eja)
-
-        EXAMPLES:
-
-        The identity in `S^n` is converted to the identity in the EJA::
-
-            sage: J = RealSymmetricEJA(3)
-            sage: I = matrix.identity(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
-
-        TESTS:
-
-        Ensure that we can convert any element of the parent's
-        underlying vector space back into an algebra element whose
-        vector representation is what we started with::
-
-            sage: set_random_seed()
-            sage: J = random_eja()
-            sage: v = J.vector_space().random_element()
-            sage: J(v).to_vector() == v
-            True
-
-        """
-        # 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).
-        ifme = super(FiniteDimensionalEuclideanJordanAlgebraElement, self)
-        try:
-            ifme.__init__(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.coordinate_vector(_mat2vec(elt))
-                ifme.__init__(A, coords)
 
 
     def __pow__(self, n):
 
 
     def __pow__(self, n):
@@ -95,7 +36,7 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement):
         Return ``self`` raised to the power ``n``.
 
         Jordan algebras are always power-associative; see for
         Return ``self`` raised to the power ``n``.
 
         Jordan algebras are always power-associative; see for
-        example Faraut and Koranyi, Proposition II.1.2 (ii).
+        example Faraut and Korányi, Proposition II.1.2 (ii).
 
         We have to override this because our superclass uses row
         vectors instead of column vectors! We, on the other hand,
 
         We have to override this because our superclass uses row
         vectors instead of column vectors! We, on the other hand,
@@ -141,7 +82,7 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement):
         elif n == 1:
             return self
         else:
         elif n == 1:
             return self
         else:
-            return (self.operator()**(n-1))(self)
+            return (self**(n-1))*self
 
 
     def apply_univariate_polynomial(self, p):
 
 
     def apply_univariate_polynomial(self, p):
@@ -228,6 +169,21 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement):
             sage: x.apply_univariate_polynomial(p)
             0
 
             sage: x.apply_univariate_polynomial(p)
             0
 
+        The characteristic polynomials of the zero and unit elements
+        should be what we think they are in a subalgebra, too::
+
+            sage: J = RealCartesianProductEJA(3)
+            sage: p1 = J.one().characteristic_polynomial()
+            sage: q1 = J.zero().characteristic_polynomial()
+            sage: e0,e1,e2 = J.gens()
+            sage: A = (e0 + 2*e1 + 3*e2).subalgebra_generated_by() # dim 3
+            sage: p2 = A.one().characteristic_polynomial()
+            sage: q2 = A.zero().characteristic_polynomial()
+            sage: p1 == p2
+            True
+            sage: q1 == q2
+            True
+
         """
         p = self.parent().characteristic_polynomial()
         return p(*self.to_vector())
         """
         p = self.parent().characteristic_polynomial()
         return p(*self.to_vector())
@@ -257,7 +213,7 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement):
             sage: y = vector(QQ,[4,5,6])
             sage: x.inner_product(y)
             32
             sage: y = vector(QQ,[4,5,6])
             sage: x.inner_product(y)
             32
-            sage: J(x).inner_product(J(y))
+            sage: J.from_vector(x).inner_product(J.from_vector(y))
             32
 
         The inner product on `S^n` is `<X,Y> = trace(X*Y)`, where
             32
 
         The inner product on `S^n` is `<X,Y> = trace(X*Y)`, where
@@ -291,9 +247,8 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement):
 
             sage: set_random_seed()
             sage: J = random_eja()
 
             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
+            sage: x,y = J.random_elements(2)
+            sage: x.inner_product(y) in RLF
             True
 
         """
             True
 
         """
@@ -328,9 +283,7 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement):
         Test Lemma 1 from Chapter III of Koecher::
 
             sage: set_random_seed()
         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: u,v = random_eja().random_elements(2)
             sage: lhs = u.operator_commutes_with(u*v)
             sage: rhs = v.operator_commutes_with(u^2)
             sage: lhs == rhs
             sage: lhs = u.operator_commutes_with(u*v)
             sage: rhs = v.operator_commutes_with(u^2)
             sage: lhs == rhs
@@ -340,9 +293,7 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement):
         Chapter III, or from Baes (2.3)::
 
             sage: set_random_seed()
         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: x,y = random_eja().random_elements(2)
             sage: Lx = x.operator()
             sage: Ly = y.operator()
             sage: Lxx = (x*x).operator()
             sage: Lx = x.operator()
             sage: Ly = y.operator()
             sage: Lxx = (x*x).operator()
@@ -354,10 +305,7 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement):
         Baes (2.4)::
 
             sage: set_random_seed()
         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: x,y,z = random_eja().random_elements(3)
             sage: Lx = x.operator()
             sage: Ly = y.operator()
             sage: Lz = z.operator()
             sage: Lx = x.operator()
             sage: Ly = y.operator()
             sage: Lz = z.operator()
@@ -371,10 +319,7 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement):
         Baes (2.5)::
 
             sage: set_random_seed()
         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: u,y,z = random_eja().random_elements(3)
             sage: Lu = u.operator()
             sage: Ly = y.operator()
             sage: Lz = z.operator()
             sage: Lu = u.operator()
             sage: Ly = y.operator()
             sage: Lz = z.operator()
@@ -431,6 +376,15 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement):
             sage: x.is_invertible() == (x.det() != 0)
             True
 
             sage: x.is_invertible() == (x.det() != 0)
             True
 
+        Ensure that the determinant is multiplicative on an associative
+        subalgebra as in Faraut and Korányi's Proposition II.2.2::
+
+            sage: set_random_seed()
+            sage: J = random_eja().random_element().subalgebra_generated_by()
+            sage: x,y = J.random_elements(2)
+            sage: (x*y).det() == x.det()*y.det()
+            True
+
         """
         P = self.parent()
         r = P.rank()
         """
         P = self.parent()
         r = P.rank()
@@ -453,7 +407,8 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement):
 
         SETUP::
 
 
         SETUP::
 
-            sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
+            sage: from mjo.eja.eja_algebra import (ComplexHermitianEJA,
+            ....:                                  JordanSpinEJA,
             ....:                                  random_eja)
 
         EXAMPLES:
             ....:                                  random_eja)
 
         EXAMPLES:
@@ -462,8 +417,7 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement):
         Example 11.11::
 
             sage: set_random_seed()
         Example 11.11::
 
             sage: set_random_seed()
-            sage: n = ZZ.random_element(1,10)
-            sage: J = JordanSpinEJA(n)
+            sage: J = JordanSpinEJA.random_instance()
             sage: x = J.random_element()
             sage: while not x.is_invertible():
             ....:     x = J.random_element()
             sage: x = J.random_element()
             sage: while not x.is_invertible():
             ....:     x = J.random_element()
@@ -473,9 +427,16 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement):
             sage: coeff = ~(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: coeff = ~(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)
+            sage: x.inverse() == J.from_vector(x_inverse)
             True
 
             True
 
+        Trying to invert a non-invertible element throws an error:
+
+            sage: JordanSpinEJA(3).zero().inverse()
+            Traceback (most recent call last):
+            ...
+            ValueError: element is not invertible
+
         TESTS:
 
         The identity element is its own inverse::
         TESTS:
 
         The identity element is its own inverse::
@@ -501,13 +462,32 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement):
             sage: (not x.is_invertible()) or (x.inverse().inverse() == x)
             True
 
             sage: (not x.is_invertible()) or (x.inverse().inverse() == x)
             True
 
-        The zero element is never invertible::
+        Proposition II.2.3 in Faraut and Korányi says that the inverse
+        of an element is the inverse of its left-multiplication operator
+        applied to the algebra's identity, when that inverse exists::
 
             sage: set_random_seed()
 
             sage: set_random_seed()
-            sage: J = random_eja().zero().inverse()
-            Traceback (most recent call last):
-            ...
-            ValueError: element is not invertible
+            sage: J = random_eja()
+            sage: x = J.random_element()
+            sage: (not x.operator().is_invertible()) or (
+            ....:    x.operator().inverse()(J.one()) == x.inverse() )
+            True
+
+        Proposition II.2.4 in Faraut and Korányi gives a formula for
+        the inverse based on the characteristic polynomial and the
+        Cayley-Hamilton theorem for Euclidean Jordan algebras::
+
+            sage: set_random_seed()
+            sage: J = ComplexHermitianEJA(3)
+            sage: x = J.random_element()
+            sage: while not x.is_invertible():
+            ....:     x = J.random_element()
+            sage: r = J.rank()
+            sage: a = x.characteristic_polynomial().coefficients(sparse=False)
+            sage: expected  = (-1)^(r+1)/x.det()
+            sage: expected *= sum( a[i+1]*x^i for i in range(r) )
+            sage: x.inverse() == expected
+            True
 
         """
         if not self.is_invertible():
 
         """
         if not self.is_invertible():
@@ -545,15 +525,23 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement):
             sage: J.one().is_invertible()
             True
 
             sage: J.one().is_invertible()
             True
 
-        The zero element is never invertible::
+        The zero element is never invertible in a non-trivial algebra::
 
             sage: set_random_seed()
             sage: J = random_eja()
 
             sage: set_random_seed()
             sage: J = random_eja()
-            sage: J.zero().is_invertible()
+            sage: (not J.is_trivial()) and J.zero().is_invertible()
             False
 
         """
             False
 
         """
-        zero = self.parent().zero()
+        if self.is_zero():
+            if self.parent().is_trivial():
+                return True
+            else:
+                return False
+
+        # In fact, we only need to know if the constant term is non-zero,
+        # so we can pass in the field's zero element instead.
+        zero = self.base_ring().zero()
         p = self.minimal_polynomial()
         return not (p(zero) == zero)
 
         p = self.minimal_polynomial()
         return not (p(zero) == zero)
 
@@ -585,10 +573,11 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement):
 
         TESTS:
 
 
         TESTS:
 
-        The identity element is never nilpotent::
+        The identity element is never nilpotent, except in a trivial EJA::
 
             sage: set_random_seed()
 
             sage: set_random_seed()
-            sage: random_eja().one().is_nilpotent()
+            sage: J = random_eja()
+            sage: J.one().is_nilpotent() and not J.is_trivial()
             False
 
         The additive identity is always nilpotent::
             False
 
         The additive identity is always nilpotent::
@@ -632,11 +621,11 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement):
         TESTS:
 
         The zero element should never be regular, unless the parent
         TESTS:
 
         The zero element should never be regular, unless the parent
-        algebra has dimension one::
+        algebra has dimension less than or equal to one::
 
             sage: set_random_seed()
             sage: J = random_eja()
 
             sage: set_random_seed()
             sage: J = random_eja()
-            sage: J.dimension() == 1 or not J.zero().is_regular()
+            sage: J.dimension() <= 1 or not J.zero().is_regular()
             True
 
         The unit element isn't regular unless the algebra happens to
             True
 
         The unit element isn't regular unless the algebra happens to
@@ -644,7 +633,7 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement):
 
             sage: set_random_seed()
             sage: J = random_eja()
 
             sage: set_random_seed()
             sage: J = random_eja()
-            sage: J.dimension() == 1 or not J.one().is_regular()
+            sage: J.dimension() <= 1 or not J.one().is_regular()
             True
 
         """
             True
 
         """
@@ -681,22 +670,24 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement):
         aren't multiples of the identity are regular::
 
             sage: set_random_seed()
         aren't multiples of the identity are regular::
 
             sage: set_random_seed()
-            sage: n = ZZ.random_element(1,10)
-            sage: J = JordanSpinEJA(n)
+            sage: J = JordanSpinEJA.random_instance()
             sage: x = J.random_element()
             sage: x == x.coefficient(0)*J.one() or x.degree() == 2
             True
 
         TESTS:
 
             sage: x = J.random_element()
             sage: x == x.coefficient(0)*J.one() or x.degree() == 2
             True
 
         TESTS:
 
-        The zero and unit elements are both of degree one::
+        The zero and unit elements are both of degree one in nontrivial
+        algebras::
 
             sage: set_random_seed()
             sage: J = random_eja()
 
             sage: set_random_seed()
             sage: J = random_eja()
-            sage: J.zero().degree()
-            1
-            sage: J.one().degree()
-            1
+            sage: d = J.zero().degree()
+            sage: (J.is_trivial() and d == 0) or d == 1
+            True
+            sage: d = J.one().degree()
+            sage: (J.is_trivial() and d == 0) or d == 1
+            True
 
         Our implementation agrees with the definition::
 
 
         Our implementation agrees with the definition::
 
@@ -706,6 +697,11 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement):
             True
 
         """
             True
 
         """
+        if self.is_zero() and not self.parent().is_trivial():
+            # The minimal polynomial of zero in a nontrivial algebra
+            # is "t"; in a trivial algebra it's "1" by convention
+            # (it's an empty product).
+            return 1
         return self.subalgebra_generated_by().dimension()
 
 
         return self.subalgebra_generated_by().dimension()
 
 
@@ -734,6 +730,7 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement):
         SETUP::
 
             sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
         SETUP::
 
             sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
+            ....:                                  RealSymmetricEJA,
             ....:                                  random_eja)
 
         TESTS:
             ....:                                  random_eja)
 
         TESTS:
@@ -759,10 +756,12 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement):
         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
         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::
+        identity. We require the dimension of the algebra to be at least
+        two here so that said elements actually exist::
 
             sage: set_random_seed()
 
             sage: set_random_seed()
-            sage: n = ZZ.random_element(2,10)
+            sage: n_max = max(2, JordanSpinEJA._max_test_case_size())
+            sage: n = ZZ.random_element(2, n_max)
             sage: J = JordanSpinEJA(n)
             sage: y = J.random_element()
             sage: while y == y.coefficient(0)*J.one():
             sage: J = JordanSpinEJA(n)
             sage: y = J.random_element()
             sage: while y == y.coefficient(0)*J.one():
@@ -783,9 +782,36 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement):
             sage: x.apply_univariate_polynomial(p)
             0
 
             sage: x.apply_univariate_polynomial(p)
             0
 
+        The minimal polynomial is invariant under a change of basis,
+        and in particular, a re-scaling of the basis::
+
+            sage: set_random_seed()
+            sage: n_max = RealSymmetricEJA._max_test_case_size()
+            sage: n = ZZ.random_element(1, n_max)
+            sage: J1 = RealSymmetricEJA(n,QQ)
+            sage: J2 = RealSymmetricEJA(n,QQ,normalize_basis=False)
+            sage: X = random_matrix(QQ,n)
+            sage: X = X*X.transpose()
+            sage: x1 = J1(X)
+            sage: x2 = J2(X)
+            sage: x1.minimal_polynomial() == x2.minimal_polynomial()
+            True
+
         """
         """
+        if self.is_zero():
+            # We would generate a zero-dimensional subalgebra
+            # where the minimal polynomial would be constant.
+            # That might be correct, but only if *this* algebra
+            # is trivial too.
+            if not self.parent().is_trivial():
+                # Pretty sure we know what the minimal polynomial of
+                # the zero operator is going to be. This ensures
+                # consistency of e.g. the polynomial variable returned
+                # in the "normal" case without us having to think about it.
+                return self.operator().minimal_polynomial()
+
         A = self.subalgebra_generated_by()
         A = self.subalgebra_generated_by()
-        return A.element_class(A,self).operator().minimal_polynomial()
+        return A(self).operator().minimal_polynomial()
 
 
 
 
 
 
@@ -808,7 +834,7 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement):
 
             sage: J = ComplexHermitianEJA(3)
             sage: J.one()
 
             sage: J = ComplexHermitianEJA(3)
             sage: J.one()
-            e0 + e5 + e8
+            e0 + e3 + e8
             sage: J.one().natural_representation()
             [1 0 0 0 0 0]
             [0 1 0 0 0 0]
             sage: J.one().natural_representation()
             [1 0 0 0 0 0]
             [0 1 0 0 0 0]
@@ -821,7 +847,7 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement):
 
             sage: J = QuaternionHermitianEJA(3)
             sage: J.one()
 
             sage: J = QuaternionHermitianEJA(3)
             sage: J.one()
-            e0 + e9 + e14
+            e0 + e5 + 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]
             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]
@@ -838,8 +864,35 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement):
 
         """
         B = self.parent().natural_basis()
 
         """
         B = self.parent().natural_basis()
-        W = B[0].matrix_space()
-        return W.linear_combination(zip(B,self.to_vector()))
+        W = self.parent().natural_basis_space()
+        return W.linear_combination(izip(B,self.to_vector()))
+
+
+    def norm(self):
+        """
+        The norm of this element with respect to :meth:`inner_product`.
+
+        SETUP::
+
+            sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
+            ....:                                  RealCartesianProductEJA)
+
+        EXAMPLES::
+
+            sage: J = RealCartesianProductEJA(2)
+            sage: x = sum(J.gens())
+            sage: x.norm()
+            sqrt(2)
+
+        ::
+
+            sage: J = JordanSpinEJA(4)
+            sage: x = sum(J.gens())
+            sage: x.norm()
+            2
+
+        """
+        return self.inner_product(self).sqrt()
 
 
     def operator(self):
 
 
     def operator(self):
@@ -855,8 +908,7 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement):
 
             sage: set_random_seed()
             sage: J = random_eja()
 
             sage: set_random_seed()
             sage: J = random_eja()
-            sage: x = J.random_element()
-            sage: y = J.random_element()
+            sage: x,y = J.random_elements(2)
             sage: x.operator()(y) == x*y
             True
             sage: y.operator()(x) == x*y
             sage: x.operator()(y) == x*y
             True
             sage: y.operator()(x) == x*y
@@ -864,10 +916,12 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement):
 
         """
         P = self.parent()
 
         """
         P = self.parent()
+        left_mult_by_self = lambda y: self*y
+        L = P.module_morphism(function=left_mult_by_self, codomain=P)
         return FiniteDimensionalEuclideanJordanAlgebraOperator(
                  P,
                  P,
         return FiniteDimensionalEuclideanJordanAlgebraOperator(
                  P,
                  P,
-                 self.to_matrix() )
+                 L.matrix() )
 
 
     def quadratic_representation(self, other=None):
 
 
     def quadratic_representation(self, other=None):
@@ -885,10 +939,9 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement):
         Alizadeh's Example 11.12::
 
             sage: set_random_seed()
         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 = JordanSpinEJA.random_instance().random_element()
             sage: x_vec = x.to_vector()
             sage: x_vec = x.to_vector()
+            sage: n = x_vec.degree()
             sage: x0 = x_vec[0]
             sage: x_bar = x_vec[1:]
             sage: A = matrix(QQ, 1, [x_vec.inner_product(x_vec)])
             sage: x0 = x_vec[0]
             sage: x_bar = x_vec[1:]
             sage: A = matrix(QQ, 1, [x_vec.inner_product(x_vec)])
@@ -905,8 +958,7 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement):
 
             sage: set_random_seed()
             sage: J = random_eja()
 
             sage: set_random_seed()
             sage: J = random_eja()
-            sage: x = J.random_element()
-            sage: y = J.random_element()
+            sage: x,y = J.random_elements(2)
             sage: Lx = x.operator()
             sage: Lxx = (x*x).operator()
             sage: Qx = x.quadratic_representation()
             sage: Lx = x.operator()
             sage: Lxx = (x*x).operator()
             sage: Qx = x.quadratic_representation()
@@ -923,7 +975,7 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement):
 
         Property 2 (multiply on the right for :trac:`28272`):
 
 
         Property 2 (multiply on the right for :trac:`28272`):
 
-            sage: alpha = QQ.random_element()
+            sage: alpha = J.base_ring().random_element()
             sage: (alpha*x).quadratic_representation() == Qx*(alpha^2)
             True
 
             sage: (alpha*x).quadratic_representation() == Qx*(alpha^2)
             True
 
@@ -951,10 +1003,10 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement):
             sage: not x.is_invertible() or (
             ....:   x.quadratic_representation(x.inverse())*Qx
             ....:   ==
             sage: not x.is_invertible() or (
             ....:   x.quadratic_representation(x.inverse())*Qx
             ....:   ==
-            ....:   2*x.operator()*Qex - Qx )
+            ....:   2*Lx*Qex - Qx )
             True
 
             True
 
-            sage: 2*x.operator()*Qex - Qx == Lxx
+            sage: 2*Lx*Qex - Qx == Lxx
             True
 
         Property 5:
             True
 
         Property 5:
@@ -991,21 +1043,95 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement):
 
 
 
 
 
 
+    def spectral_decomposition(self):
+        """
+        Return the unique spectral decomposition of this element.
+
+        ALGORITHM:
+
+        Following Faraut and Korányi's Theorem III.1.1, we restrict this
+        element's left-multiplication-by operator to the subalgebra it
+        generates. We then compute the spectral decomposition of that
+        operator, and the spectral projectors we get back must be the
+        left-multiplication-by operators for the idempotents we
+        seek. Thus applying them to the identity element gives us those
+        idempotents.
+
+        Since the eigenvalues are required to be distinct, we take
+        the spectral decomposition of the zero element to be zero
+        times the identity element of the algebra (which is idempotent,
+        obviously).
+
+        SETUP::
+
+            sage: from mjo.eja.eja_algebra import RealSymmetricEJA
+
+        EXAMPLES:
+
+        The spectral decomposition of the identity is ``1`` times itself,
+        and the spectral decomposition of zero is ``0`` times the identity::
+
+            sage: J = RealSymmetricEJA(3,AA)
+            sage: J.one()
+            e0 + e2 + e5
+            sage: J.one().spectral_decomposition()
+            [(1, e0 + e2 + e5)]
+            sage: J.zero().spectral_decomposition()
+            [(0, e0 + e2 + e5)]
+
+        TESTS::
+
+            sage: J = RealSymmetricEJA(4,AA)
+            sage: x = sum(J.gens())
+            sage: sd = x.spectral_decomposition()
+            sage: l0 = sd[0][0]
+            sage: l1 = sd[1][0]
+            sage: c0 = sd[0][1]
+            sage: c1 = sd[1][1]
+            sage: c0.inner_product(c1) == 0
+            True
+            sage: c0.is_idempotent()
+            True
+            sage: c1.is_idempotent()
+            True
+            sage: c0 + c1 == J.one()
+            True
+            sage: l0*c0 + l1*c1 == x
+            True
 
 
-    def subalgebra_generated_by(self):
+        """
+        P = self.parent()
+        A = self.subalgebra_generated_by(orthonormalize_basis=True)
+        result = []
+        for (evalue, proj) in A(self).operator().spectral_decomposition():
+            result.append( (evalue, proj(A.one()).superalgebra_element()) )
+        return result
+
+    def subalgebra_generated_by(self, orthonormalize_basis=False):
         """
         Return the associative subalgebra of the parent EJA generated
         by this element.
 
         """
         Return the associative subalgebra of the parent EJA generated
         by this element.
 
+        Since our parent algebra is unital, we want "subalgebra" to mean
+        "unital subalgebra" as well; thus the subalgebra that an element
+        generates will itself be a Euclidean Jordan algebra after
+        restricting the algebra operations appropriately. This is the
+        subalgebra that Faraut and Korányi work with in section II.2, for
+        example.
+
         SETUP::
 
             sage: from mjo.eja.eja_algebra import random_eja
 
         SETUP::
 
             sage: from mjo.eja.eja_algebra import random_eja
 
-        TESTS::
+        TESTS:
+
+        This subalgebra, being composed of only powers, is associative::
 
             sage: set_random_seed()
 
             sage: set_random_seed()
-            sage: x = random_eja().random_element()
-            sage: x.subalgebra_generated_by().is_associative()
+            sage: x0 = random_eja().random_element()
+            sage: A = x0.subalgebra_generated_by()
+            sage: x,y,z = A.random_elements(3)
+            sage: (x*y)*z == x*(y*z)
             True
 
         Squaring in the subalgebra should work the same as in
             True
 
         Squaring in the subalgebra should work the same as in
@@ -1017,8 +1143,18 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement):
             sage: A(x^2) == A(x)*A(x)
             True
 
             sage: A(x^2) == A(x)*A(x)
             True
 
+        By definition, the subalgebra generated by the zero element is
+        the one-dimensional algebra generated by the identity
+        element... unless the original algebra was trivial, in which
+        case the subalgebra is trivial too::
+
+            sage: set_random_seed()
+            sage: A = random_eja().zero().subalgebra_generated_by()
+            sage: (A.is_trivial() and A.dimension() == 0) or A.dimension() == 1
+            True
+
         """
         """
-        return FiniteDimensionalEuclideanJordanElementSubalgebra(self)
+        return FiniteDimensionalEuclideanJordanElementSubalgebra(self, orthonormalize_basis)
 
 
     def subalgebra_idempotent(self):
 
 
     def subalgebra_idempotent(self):
@@ -1071,7 +1207,7 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement):
         # Our FiniteDimensionalAlgebraElement superclass uses rows.
         u_next = u**(s+1)
         A = u_next.operator().matrix()
         # Our FiniteDimensionalAlgebraElement superclass uses rows.
         u_next = u**(s+1)
         A = u_next.operator().matrix()
-        c = J(A.solve_right(u_next.to_vector()))
+        c = J.from_vector(A.solve_right(u_next.to_vector()))
 
         # Now c is the idempotent we want, but it still lives in the subalgebra.
         return c.superalgebra_element()
 
         # Now c is the idempotent we want, but it still lives in the subalgebra.
         return c.superalgebra_element()
@@ -1106,12 +1242,18 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement):
 
             sage: set_random_seed()
             sage: J = random_eja()
 
             sage: set_random_seed()
             sage: J = random_eja()
-            sage: J.random_element().trace() in J.base_ring()
+            sage: J.random_element().trace() in RLF
             True
 
         """
         P = self.parent()
         r = P.rank()
             True
 
         """
         P = self.parent()
         r = P.rank()
+
+        if r == 0:
+            # Special case for the trivial algebra where
+            # the trace is an empty sum.
+            return P.base_ring().zero()
+
         p = P._charpoly_coeff(r-1)
         # The _charpoly_coeff function already adds the factor of
         # -1 to ensure that _charpoly_coeff(r-1) is really what
         p = P._charpoly_coeff(r-1)
         # The _charpoly_coeff function already adds the factor of
         # -1 to ensure that _charpoly_coeff(r-1) is really what
@@ -1130,22 +1272,16 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement):
 
         TESTS:
 
 
         TESTS:
 
-        The trace inner product is commutative::
+        The trace inner product is commutative, bilinear, and associative::
 
             sage: set_random_seed()
             sage: J = random_eja()
 
             sage: set_random_seed()
             sage: J = random_eja()
-            sage: x = J.random_element(); y = J.random_element()
+            sage: x,y,z = J.random_elements(3)
+            sage: # commutative
             sage: x.trace_inner_product(y) == y.trace_inner_product(x)
             True
             sage: x.trace_inner_product(y) == y.trace_inner_product(x)
             True
-
-        The trace inner product is bilinear::
-
-            sage: set_random_seed()
-            sage: J = random_eja()
-            sage: x = J.random_element()
-            sage: y = J.random_element()
-            sage: z = J.random_element()
-            sage: a = QQ.random_element();
+            sage: # bilinear
+            sage: a = J.base_ring().random_element();
             sage: actual = (a*(x+z)).trace_inner_product(y)
             sage: expected = ( a*x.trace_inner_product(y) +
             ....:              a*z.trace_inner_product(y) )
             sage: actual = (a*(x+z)).trace_inner_product(y)
             sage: expected = ( a*x.trace_inner_product(y) +
             ....:              a*z.trace_inner_product(y) )
@@ -1156,15 +1292,7 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement):
             ....:              a*x.trace_inner_product(z) )
             sage: actual == expected
             True
             ....:              a*x.trace_inner_product(z) )
             sage: actual == expected
             True
-
-        The trace inner product satisfies the compatibility
-        condition in the definition of 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: # associative
             sage: (x*y).trace_inner_product(z) == y.trace_inner_product(x*z)
             True
 
             sage: (x*y).trace_inner_product(z) == y.trace_inner_product(x*z)
             True
 
@@ -1173,3 +1301,30 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement):
             raise TypeError("'other' must live in the same algebra")
 
         return (self*other).trace()
             raise TypeError("'other' must live in the same algebra")
 
         return (self*other).trace()
+
+
+    def trace_norm(self):
+        """
+        The norm of this element with respect to :meth:`trace_inner_product`.
+
+        SETUP::
+
+            sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
+            ....:                                  RealCartesianProductEJA)
+
+        EXAMPLES::
+
+            sage: J = RealCartesianProductEJA(2)
+            sage: x = sum(J.gens())
+            sage: x.trace_norm()
+            sqrt(2)
+
+        ::
+
+            sage: J = JordanSpinEJA(4)
+            sage: x = sum(J.gens())
+            sage: x.trace_norm()
+            2*sqrt(2)
+
+        """
+        return self.trace_inner_product(self).sqrt()