]> gitweb.michael.orlitzky.com - sage.d.git/blobdiff - mjo/eja/eja_operator.py
COPYING,LICENSE: add (AGPL-3.0+)
[sage.d.git] / mjo / eja / eja_operator.py
index 1f10048812301baa8c13c4d49f5e4475a9cc04d7..689b7ecec26c09a8423ca440ee6b00429d50073b 100644 (file)
@@ -2,17 +2,53 @@ from sage.matrix.constructor import matrix
 from sage.categories.all import FreeModules
 from sage.categories.map import Map
 
-class FiniteDimensionalEuclideanJordanAlgebraOperator(Map):
+class EJAOperator(Map):
+    r"""
+    An operator between two finite-dimensional Euclidean Jordan algebras.
+
+    SETUP::
+
+        sage: from mjo.eja.eja_algebra import HadamardEJA
+        sage: from mjo.eja.eja_operator import EJAOperator
+
+    EXAMPLES:
+
+    The domain and codomain must be finite-dimensional Euclidean
+    Jordan algebras; if either is not, then an error is raised::
+
+        sage: J = HadamardEJA(3)
+        sage: V = VectorSpace(J.base_ring(), 3)
+        sage: M = matrix.identity(J.base_ring(), 3)
+        sage: EJAOperator(V,J,M)
+        Traceback (most recent call last):
+        ...
+        TypeError: domain must be a finite-dimensional Euclidean
+        Jordan algebra
+        sage: EJAOperator(J,V,M)
+        Traceback (most recent call last):
+        ...
+        TypeError: codomain must be a finite-dimensional Euclidean
+        Jordan algebra
+
+    """
+
     def __init__(self, domain_eja, codomain_eja, mat):
-        # if not (
-        #   isinstance(domain_eja, FiniteDimensionalEuclideanJordanAlgebra) and
-        #   isinstance(codomain_eja, FiniteDimensionalEuclideanJordanAlgebra) ):
-        #     raise ValueError('(co)domains must be finite-dimensional Euclidean '
-        #                      'Jordan algebras')
+        from mjo.eja.eja_algebra import EJA
+
+        # I guess we should check this, because otherwise you could
+        # pass in pretty much anything algebraish.
+        if not isinstance(domain_eja, EJA):
+            raise TypeError('domain must be a finite-dimensional '
+                            'Euclidean Jordan algebra')
+        if not isinstance(codomain_eja, EJA):
+            raise TypeError('codomain must be a finite-dimensional '
+                            'Euclidean Jordan algebra')
 
         F = domain_eja.base_ring()
         if not (F == codomain_eja.base_ring()):
             raise ValueError("domain and codomain must have the same base ring")
+        if not (F == mat.base_ring()):
+            raise ValueError("domain and matrix must have the same base ring")
 
         # We need to supply something here to avoid getting the
         # default Homset of the parent FiniteDimensionalAlgebra class,
@@ -25,7 +61,7 @@ class FiniteDimensionalEuclideanJordanAlgebraOperator(Map):
         # The Map initializer will set our parent to a homset, which
         # is explicitly NOT what we want, because these ain't algebra
         # homomorphisms.
-        super(FiniteDimensionalEuclideanJordanAlgebraOperator,self).__init__(parent)
+        super().__init__(parent)
 
         # Keep a matrix around to do all of the real work. It would
         # be nice if we could use a VectorSpaceMorphism instead, but
@@ -40,20 +76,20 @@ class FiniteDimensionalEuclideanJordanAlgebraOperator(Map):
 
         SETUP::
 
-            sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
+            sage: from mjo.eja.eja_operator import EJAOperator
             sage: from mjo.eja.eja_algebra import JordanSpinEJA
 
         EXAMPLES::
 
             sage: J = JordanSpinEJA(3)
-            sage: x = J.linear_combination(zip(range(len(J.gens())), J.gens()))
+            sage: x = J.linear_combination(zip(J.gens(),range(len(J.gens()))))
             sage: id = identity_matrix(J.base_ring(), J.dimension())
-            sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id)
+            sage: f = EJAOperator(J,J,id)
             sage: f(x) == x
             True
 
         """
-        return self.codomain()(self.matrix()*x.vector())
+        return self.codomain().from_vector(self.matrix()*x.to_vector())
 
 
     def _add_(self, other):
@@ -62,7 +98,7 @@ class FiniteDimensionalEuclideanJordanAlgebraOperator(Map):
 
         SETUP::
 
-            sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
+            sage: from mjo.eja.eja_operator import EJAOperator
             sage: from mjo.eja.eja_algebra import (
             ....:   JordanSpinEJA,
             ....:   RealSymmetricEJA )
@@ -73,32 +109,33 @@ class FiniteDimensionalEuclideanJordanAlgebraOperator(Map):
 
             sage: J = RealSymmetricEJA(2)
             sage: id = identity_matrix(J.base_ring(), J.dimension())
-            sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id)
-            sage: g = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id)
+            sage: f = EJAOperator(J,J,id)
+            sage: g = EJAOperator(J,J,id)
             sage: f + g
             Linear operator between finite-dimensional Euclidean Jordan
             algebras represented by the matrix:
             [2 0 0]
             [0 2 0]
             [0 0 2]
-            Domain: Euclidean Jordan algebra of degree 3 over Rational Field
-            Codomain: Euclidean Jordan algebra of degree 3 over Rational Field
+            Domain: Euclidean Jordan algebra of dimension 3 over...
+            Codomain: Euclidean Jordan algebra of dimension 3 over...
 
         If you try to add two identical vector space operators but on
         different EJAs, that should blow up::
 
             sage: J1 = RealSymmetricEJA(2)
+            sage: id1 = identity_matrix(J1.base_ring(), 3)
             sage: J2 = JordanSpinEJA(3)
-            sage: id = identity_matrix(QQ, 3)
-            sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J1,J1,id)
-            sage: g = FiniteDimensionalEuclideanJordanAlgebraOperator(J2,J2,id)
+            sage: id2 = identity_matrix(J2.base_ring(), 3)
+            sage: f = EJAOperator(J1,J1,id1)
+            sage: g = EJAOperator(J2,J2,id2)
             sage: f + g
             Traceback (most recent call last):
             ...
             TypeError: unsupported operand parent(s) for +: ...
 
         """
-        return FiniteDimensionalEuclideanJordanAlgebraOperator(
+        return EJAOperator(
                 self.domain(),
                 self.codomain(),
                 self.matrix() + other.matrix())
@@ -111,35 +148,33 @@ class FiniteDimensionalEuclideanJordanAlgebraOperator(Map):
 
         SETUP::
 
-            sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
+            sage: from mjo.eja.eja_operator import EJAOperator
             sage: from mjo.eja.eja_algebra import (
             ....:   JordanSpinEJA,
-            ....:   RealCartesianProductEJA,
+            ....:   HadamardEJA,
             ....:   RealSymmetricEJA)
 
         EXAMPLES::
 
             sage: J1 = JordanSpinEJA(3)
-            sage: J2 = RealCartesianProductEJA(2)
+            sage: J2 = HadamardEJA(2)
             sage: J3 = RealSymmetricEJA(1)
-            sage: mat1 = matrix(QQ, [[1,2,3],
+            sage: mat1 = matrix(AA, [[1,2,3],
             ....:                    [4,5,6]])
-            sage: mat2 = matrix(QQ, [[7,8]])
-            sage: g = FiniteDimensionalEuclideanJordanAlgebraOperator(J1,
-            ....:                                                     J2,
-            ....:                                                     mat1)
-            sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J2,
-            ....:                                                     J3,
-            ....:                                                     mat2)
+            sage: mat2 = matrix(AA, [[7,8]])
+            sage: g = EJAOperator(J1, J2, mat1)
+            sage: f = EJAOperator(J2, J3, mat2)
             sage: f*g
             Linear operator between finite-dimensional Euclidean Jordan
             algebras represented by the matrix:
             [39 54 69]
-            Domain: Euclidean Jordan algebra of degree 3 over Rational Field
-            Codomain: Euclidean Jordan algebra of degree 1 over Rational Field
+            Domain: Euclidean Jordan algebra of dimension 3 over
+            Algebraic Real Field
+            Codomain: Euclidean Jordan algebra of dimension 1 over
+            Algebraic Real Field
 
         """
-        return FiniteDimensionalEuclideanJordanAlgebraOperator(
+        return EJAOperator(
           other.domain(),
           self.codomain(),
           self.matrix()*other.matrix())
@@ -161,25 +196,25 @@ class FiniteDimensionalEuclideanJordanAlgebraOperator(Map):
 
         SETUP::
 
-            sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
+            sage: from mjo.eja.eja_operator import EJAOperator
             sage: from mjo.eja.eja_algebra import RealSymmetricEJA
 
         EXAMPLES::
 
             sage: J = RealSymmetricEJA(2)
             sage: id = identity_matrix(J.base_ring(), J.dimension())
-            sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id)
+            sage: f = EJAOperator(J,J,id)
             sage: ~f
             Linear operator between finite-dimensional Euclidean Jordan
             algebras represented by the matrix:
             [1 0 0]
             [0 1 0]
             [0 0 1]
-            Domain: Euclidean Jordan algebra of degree 3 over Rational Field
-            Codomain: Euclidean Jordan algebra of degree 3 over Rational Field
+            Domain: Euclidean Jordan algebra of dimension 3 over...
+            Codomain: Euclidean Jordan algebra of dimension 3 over...
 
         """
-        return FiniteDimensionalEuclideanJordanAlgebraOperator(
+        return EJAOperator(
                 self.codomain(),
                 self.domain(),
                 ~self.matrix())
@@ -196,7 +231,7 @@ class FiniteDimensionalEuclideanJordanAlgebraOperator(Map):
 
         SETUP::
 
-            sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
+            sage: from mjo.eja.eja_operator import EJAOperator
             sage: from mjo.eja.eja_algebra import RealSymmetricEJA
 
         EXAMPLES:
@@ -204,36 +239,40 @@ class FiniteDimensionalEuclideanJordanAlgebraOperator(Map):
         We can scale an operator on a rational algebra by a rational number::
 
             sage: J = RealSymmetricEJA(2)
-            sage: e0,e1,e2 = J.gens()
-            sage: x = 2*e0 + 4*e1 + 16*e2
+            sage: b0,b1,b2 = J.gens()
+            sage: x = 2*b0 + 4*b1 + 16*b2
             sage: x.operator()
             Linear operator between finite-dimensional Euclidean Jordan algebras
             represented by the matrix:
-            [ 2  4  0]
+            [ 2  2  0]
             [ 2  9  2]
-            [ 0  4 16]
-            Domain: Euclidean Jordan algebra of degree 3 over Rational Field
-            Codomain: Euclidean Jordan algebra of degree 3 over Rational Field
+            [ 0  2 16]
+            Domain: Euclidean Jordan algebra of dimension 3 over...
+            Codomain: Euclidean Jordan algebra of dimension 3 over...
             sage: x.operator()*(1/2)
             Linear operator between finite-dimensional Euclidean Jordan algebras
             represented by the matrix:
-            [  1   2   0]
+            [  1   1   0]
             [  1 9/2   1]
-            [  0   2   8]
-            Domain: Euclidean Jordan algebra of degree 3 over Rational Field
-            Codomain: Euclidean Jordan algebra of degree 3 over Rational Field
+            [  0   1   8]
+            Domain: Euclidean Jordan algebra of dimension 3 over...
+            Codomain: Euclidean Jordan algebra of dimension 3 over...
 
         """
-        if other in self.codomain().base_ring():
-            return FiniteDimensionalEuclideanJordanAlgebraOperator(
-                self.domain(),
-                self.codomain(),
-                self.matrix()*other)
+        try:
+            if other in self.codomain().base_ring():
+                return EJAOperator(
+                    self.domain(),
+                    self.codomain(),
+                    self.matrix()*other)
+        except NotImplementedError:
+            # This can happen with certain arguments if the base_ring()
+            # is weird and doesn't know how to test membership.
+            pass
 
         # This should eventually delegate to _composition_ after performing
         # some sanity checks for us.
-        mor = super(FiniteDimensionalEuclideanJordanAlgebraOperator,self)
-        return mor.__mul__(other)
+        return super().__mul__(other)
 
 
     def _neg_(self):
@@ -242,25 +281,25 @@ class FiniteDimensionalEuclideanJordanAlgebraOperator(Map):
 
         SETUP::
 
-            sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
+            sage: from mjo.eja.eja_operator import EJAOperator
             sage: from mjo.eja.eja_algebra import RealSymmetricEJA
 
         EXAMPLES::
 
             sage: J = RealSymmetricEJA(2)
             sage: id = identity_matrix(J.base_ring(), J.dimension())
-            sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id)
+            sage: f = EJAOperator(J,J,id)
             sage: -f
             Linear operator between finite-dimensional Euclidean Jordan
             algebras represented by the matrix:
             [-1  0  0]
             [ 0 -1  0]
             [ 0  0 -1]
-            Domain: Euclidean Jordan algebra of degree 3 over Rational Field
-            Codomain: Euclidean Jordan algebra of degree 3 over Rational Field
+            Domain: Euclidean Jordan algebra of dimension 3 over...
+            Codomain: Euclidean Jordan algebra of dimension 3 over...
 
         """
-        return FiniteDimensionalEuclideanJordanAlgebraOperator(
+        return EJAOperator(
                 self.domain(),
                 self.codomain(),
                 -self.matrix())
@@ -272,7 +311,7 @@ class FiniteDimensionalEuclideanJordanAlgebraOperator(Map):
 
         SETUP::
 
-            sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
+            sage: from mjo.eja.eja_operator import EJAOperator
             sage: from mjo.eja.eja_algebra import RealSymmetricEJA
 
         TESTS:
@@ -282,15 +321,15 @@ class FiniteDimensionalEuclideanJordanAlgebraOperator(Map):
 
             sage: J = RealSymmetricEJA(2)
             sage: id = identity_matrix(J.base_ring(), J.dimension())
-            sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id)
+            sage: f = EJAOperator(J,J,id)
             sage: f^0 + f^1 + f^2
             Linear operator between finite-dimensional Euclidean Jordan
             algebras represented by the matrix:
             [3 0 0]
             [0 3 0]
             [0 0 3]
-            Domain: Euclidean Jordan algebra of degree 3 over Rational Field
-            Codomain: Euclidean Jordan algebra of degree 3 over Rational Field
+            Domain: Euclidean Jordan algebra of dimension 3 over...
+            Codomain: Euclidean Jordan algebra of dimension 3 over...
 
         """
         if (n == 1):
@@ -304,7 +343,7 @@ class FiniteDimensionalEuclideanJordanAlgebraOperator(Map):
         else:
             mat = self.matrix()**n
 
-        return FiniteDimensionalEuclideanJordanAlgebraOperator(
+        return EJAOperator(
                  self.domain(),
                  self.codomain(),
                  mat)
@@ -318,20 +357,22 @@ class FiniteDimensionalEuclideanJordanAlgebraOperator(Map):
 
         SETUP::
 
-            sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
+            sage: from mjo.eja.eja_operator import EJAOperator
             sage: from mjo.eja.eja_algebra import JordanSpinEJA
 
         EXAMPLES::
 
             sage: J = JordanSpinEJA(2)
             sage: id = identity_matrix(J.base_ring(), J.dimension())
-            sage: FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id)
+            sage: EJAOperator(J,J,id)
             Linear operator between finite-dimensional Euclidean Jordan
             algebras represented by the matrix:
             [1 0]
             [0 1]
-            Domain: Euclidean Jordan algebra of degree 2 over Rational Field
-            Codomain: Euclidean Jordan algebra of degree 2 over Rational Field
+            Domain: Euclidean Jordan algebra of dimension 2 over
+            Algebraic Real Field
+            Codomain: Euclidean Jordan algebra of dimension 2 over
+            Algebraic Real Field
 
         """
         msg = ("Linear operator between finite-dimensional Euclidean Jordan "
@@ -350,27 +391,187 @@ class FiniteDimensionalEuclideanJordanAlgebraOperator(Map):
 
         SETUP::
 
-            sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
+            sage: from mjo.eja.eja_operator import EJAOperator
             sage: from mjo.eja.eja_algebra import RealSymmetricEJA
 
         EXAMPLES::
 
             sage: J = RealSymmetricEJA(2)
             sage: id = identity_matrix(J.base_ring(),J.dimension())
-            sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id)
+            sage: f = EJAOperator(J,J,id)
             sage: f - (f*2)
             Linear operator between finite-dimensional Euclidean Jordan
             algebras represented by the matrix:
             [-1  0  0]
             [ 0 -1  0]
             [ 0  0 -1]
-            Domain: Euclidean Jordan algebra of degree 3 over Rational Field
-            Codomain: Euclidean Jordan algebra of degree 3 over Rational Field
+            Domain: Euclidean Jordan algebra of dimension 3 over...
+            Codomain: Euclidean Jordan algebra of dimension 3 over...
 
         """
         return (self + (-other))
 
 
+    def is_self_adjoint(self):
+        r"""
+        Return whether or not this operator is self-adjoint.
+
+        At least in Sage, the fact that the base field is real means
+        that the algebra elements have to be real as well (this is why
+        we embed the complex numbers and quaternions). As a result, the
+        matrix of this operator will contain only real entries, and it
+        suffices to check only symmetry, not conjugate symmetry.
+
+        SETUP::
+
+            sage: from mjo.eja.eja_algebra import (JordanSpinEJA)
+
+        EXAMPLES::
+
+            sage: J = JordanSpinEJA(4)
+            sage: J.one().operator().is_self_adjoint()
+            True
+
+        """
+        return self.matrix().is_symmetric()
+
+
+    def is_zero(self):
+        r"""
+        Return whether or not this map is the zero operator.
+
+        SETUP::
+
+            sage: from mjo.eja.eja_operator import EJAOperator
+            sage: from mjo.eja.eja_algebra import (random_eja,
+            ....:                                  JordanSpinEJA,
+            ....:                                  RealSymmetricEJA)
+
+        EXAMPLES::
+
+            sage: J1 = JordanSpinEJA(2)
+            sage: J2 = RealSymmetricEJA(2)
+            sage: R = J1.base_ring()
+            sage: M = matrix(R, [ [0, 0],
+            ....:                 [0, 0],
+            ....:                 [0, 0] ])
+            sage: L = EJAOperator(J1,J2,M)
+            sage: L.is_zero()
+            True
+            sage: M = matrix(R, [ [0, 0],
+            ....:                 [0, 1],
+            ....:                 [0, 0] ])
+            sage: L = EJAOperator(J1,J2,M)
+            sage: L.is_zero()
+            False
+
+        TESTS:
+
+        The left-multiplication-by-zero operation on a given algebra
+        is its zero map::
+
+            sage: J = random_eja()
+            sage: J.zero().operator().is_zero()
+            True
+
+        """
+        return self.matrix().is_zero()
+
+
+    def inverse(self):
+        """
+        Return the inverse of this operator, if it exists.
+
+        The reason this method is not simply an alias for the built-in
+        :meth:`__invert__` is that the built-in inversion is a bit magic
+        since it's intended to be a unary operator. If we alias ``inverse``
+        to ``__invert__``, then we wind up having to call e.g. ``A.inverse``
+        without parentheses.
+
+        SETUP::
+
+            sage: from mjo.eja.eja_algebra import RealSymmetricEJA, random_eja
+
+        EXAMPLES::
+
+            sage: J = RealSymmetricEJA(2)
+            sage: x = sum(J.gens())
+            sage: x.operator().inverse().matrix()
+            [3/2  -1 1/2]
+            [ -1   2  -1]
+            [1/2  -1 3/2]
+            sage: x.operator().matrix().inverse()
+            [3/2  -1 1/2]
+            [ -1   2  -1]
+            [1/2  -1 3/2]
+
+        TESTS:
+
+        The identity operator is its own inverse::
+
+            sage: J = random_eja()
+            sage: idJ = J.one().operator()
+            sage: idJ.inverse() == idJ
+            True
+
+        The inverse of the inverse is the operator we started with::
+
+            sage: x = random_eja().random_element()
+            sage: L = x.operator()
+            sage: not L.is_invertible() or (L.inverse().inverse() == L)
+            True
+
+        """
+        return ~self
+
+
+    def is_invertible(self):
+        """
+        Return whether or not this operator is invertible.
+
+        SETUP::
+
+            sage: from mjo.eja.eja_algebra import (RealSymmetricEJA,
+            ....:                                  TrivialEJA,
+            ....:                                  random_eja)
+
+        EXAMPLES::
+
+            sage: J = RealSymmetricEJA(2)
+            sage: x = sum(J.gens())
+            sage: x.operator().matrix()
+            [  1 1/2   0]
+            [1/2   1 1/2]
+            [  0 1/2   1]
+            sage: x.operator().matrix().is_invertible()
+            True
+            sage: x.operator().is_invertible()
+            True
+
+        The zero operator is invertible in a trivial algebra::
+
+            sage: J = TrivialEJA()
+            sage: J.zero().operator().is_invertible()
+            True
+
+        TESTS:
+
+        The identity operator is always invertible::
+
+            sage: J = random_eja()
+            sage: J.one().operator().is_invertible()
+            True
+
+        The zero operator is never invertible in a nontrivial algebra::
+
+            sage: J = random_eja()
+            sage: not J.is_trivial() and J.zero().operator().is_invertible()
+            False
+
+        """
+        return self.matrix().is_invertible()
+
+
     def matrix(self):
         """
         Return the matrix representation of this operator with respect
@@ -378,14 +579,14 @@ class FiniteDimensionalEuclideanJordanAlgebraOperator(Map):
 
         SETUP::
 
-            sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
+            sage: from mjo.eja.eja_operator import EJAOperator
             sage: from mjo.eja.eja_algebra import RealSymmetricEJA
 
         EXAMPLES::
 
             sage: J = RealSymmetricEJA(2)
             sage: mat = matrix(J.base_ring(), J.dimension(), range(9))
-            sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,mat)
+            sage: f = EJAOperator(J,J,mat)
             sage: f.matrix()
             [0 1 2]
             [3 4 5]
@@ -402,7 +603,7 @@ class FiniteDimensionalEuclideanJordanAlgebraOperator(Map):
 
         SETUP::
 
-            sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
+            sage: from mjo.eja.eja_operator import EJAOperator
             sage: from mjo.eja.eja_algebra import RealSymmetricEJA
 
         EXAMPLES::
@@ -414,3 +615,62 @@ class FiniteDimensionalEuclideanJordanAlgebraOperator(Map):
         """
         # The matrix method returns a polynomial in 'x' but want one in 't'.
         return self.matrix().minimal_polynomial().change_variable_name('t')
+
+
+    def spectral_decomposition(self):
+        """
+        Return the spectral decomposition of this operator as a list of
+        (eigenvalue, orthogonal projector) pairs.
+
+        This is the unique spectral decomposition, up to the order of
+        the projection operators, with distinct eigenvalues. So, the
+        projections are generally onto subspaces of dimension greater
+        than one.
+
+        SETUP::
+
+            sage: from mjo.eja.eja_algebra import RealSymmetricEJA
+
+        EXAMPLES::
+
+            sage: J = RealSymmetricEJA(4)
+            sage: x = sum(J.gens())
+            sage: A = x.subalgebra_generated_by()
+            sage: L0x = A(x).operator()
+            sage: sd = L0x.spectral_decomposition()
+            sage: l0 = sd[0][0]
+            sage: l1 = sd[1][0]
+            sage: P0 = sd[0][1]
+            sage: P1 = sd[1][1]
+            sage: P0*l0 + P1*l1 == L0x
+            True
+            sage: P0 + P1 == P0^0 # the identity
+            True
+            sage: P0^2 == P0
+            True
+            sage: P1^2 == P1
+            True
+            sage: P0*P1 == A.zero().operator()
+            True
+            sage: P1*P0 == A.zero().operator()
+            True
+
+        """
+        if not self.matrix().is_symmetric():
+            raise ValueError('algebra basis is not orthonormal')
+
+        D,P = self.matrix().jordan_form(subdivide=False,transformation=True)
+        eigenvalues = D.diagonal()
+        us = P.columns()
+        projectors = []
+        for i in range(len(us)):
+            # they won't be normalized, but they have to be
+            # for the spectral theorem to work.
+            us[i] = us[i]/us[i].norm()
+            mat = us[i].column()*us[i].row()
+            Pi = EJAOperator(
+                   self.domain(),
+                   self.codomain(),
+                   mat)
+            projectors.append(Pi)
+        return list(zip(eigenvalues, projectors))