]> gitweb.michael.orlitzky.com - sage.d.git/commitdiff
eja: separate out the operator class and rename both files.
authorMichael Orlitzky <michael@orlitzky.com>
Sun, 28 Jul 2019 16:56:46 +0000 (12:56 -0400)
committerMichael Orlitzky <michael@orlitzky.com>
Mon, 29 Jul 2019 03:19:01 +0000 (23:19 -0400)
Let's start organizing. It's also time to get the test suite
working. I've added stupid SETUP blocks to each docstring to load the
stuff being tested.

mjo/eja/eja_algebra.py [moved from mjo/eja/euclidean_jordan_algebra.py with 85% similarity]
mjo/eja/eja_operator.py [new file with mode: 0644]

similarity index 85%
rename from mjo/eja/euclidean_jordan_algebra.py
rename to mjo/eja/eja_algebra.py
index 70a77701b342e36eec827d3ec151b87bf67fe902..8ab6afa33e8b8ccb9241099f5420fd035bd7b65c 100644 (file)
@@ -5,367 +5,15 @@ are used in optimization, and have some additional nice methods beyond
 what can be supported in a general Jordan Algebra.
 """
 
-from sage.categories.finite_dimensional_algebras_with_basis import FiniteDimensionalAlgebrasWithBasis
-from sage.categories.map import Map
-from sage.structure.element import is_Matrix
-from sage.structure.category_object import normalize_names
+from sage.all import *
 
 from sage.algebras.finite_dimensional_algebras.finite_dimensional_algebra import FiniteDimensionalAlgebra
 from sage.algebras.finite_dimensional_algebras.finite_dimensional_algebra_element import FiniteDimensionalAlgebraElement
+from sage.categories.finite_dimensional_algebras_with_basis import FiniteDimensionalAlgebrasWithBasis
+from sage.structure.element import is_Matrix
+from sage.structure.category_object import normalize_names
 
-
-class FiniteDimensionalEuclideanJordanAlgebraOperator(Map):
-    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')
-
-        F = domain_eja.base_ring()
-        if not (F == codomain_eja.base_ring()):
-            raise ValueError("domain and codomain must have the same base ring")
-
-        # We need to supply something here to avoid getting the
-        # default Homset of the parent FiniteDimensionalAlgebra class,
-        # which messes up e.g. equality testing. We use FreeModules(F)
-        # instead of VectorSpaces(F) because our characteristic polynomial
-        # algorithm will need to F to be a polynomial ring at some point.
-        # When F is a field, FreeModules(F) returns VectorSpaces(F) anyway.
-        parent = Hom(domain_eja, codomain_eja, FreeModules(F))
-
-        # 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)
-
-        # Keep a matrix around to do all of the real work. It would
-        # be nice if we could use a VectorSpaceMorphism instead, but
-        # those use row vectors that we don't want to accidentally
-        # expose to our users.
-        self._matrix = mat
-
-
-    def _call_(self, x):
-        """
-        Allow this operator to be called only on elements of an EJA.
-
-        EXAMPLES::
-
-            sage: J = JordanSpinEJA(3)
-            sage: x = J.linear_combination(zip(range(len(J.gens())), J.gens()))
-            sage: id = identity_matrix(J.base_ring(), J.dimension())
-            sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id)
-            sage: f(x) == x
-            True
-
-        """
-        return self.codomain()(self.matrix()*x.vector())
-
-
-    def _add_(self, other):
-        """
-        Add the ``other`` EJA operator to this one.
-
-        EXAMPLES:
-
-        When we add two EJA operators, we get another one back::
-
-            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 + 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
-
-        If you try to add two identical vector space operators but on
-        different EJAs, that should blow up::
-
-            sage: J1 = RealSymmetricEJA(2)
-            sage: J2 = JordanSpinEJA(3)
-            sage: id = identity_matrix(QQ, 3)
-            sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J1,J1,id)
-            sage: g = FiniteDimensionalEuclideanJordanAlgebraOperator(J2,J2,id)
-            sage: f + g
-            Traceback (most recent call last):
-            ...
-            TypeError: unsupported operand parent(s) for +: ...
-
-        """
-        return FiniteDimensionalEuclideanJordanAlgebraOperator(
-                self.domain(),
-                self.codomain(),
-                self.matrix() + other.matrix())
-
-
-    def _composition_(self, other, homset):
-        """
-        Compose two EJA operators to get another one (and NOT a formal
-        composite object) back.
-
-        EXAMPLES::
-
-            sage: J1 = JordanSpinEJA(3)
-            sage: J2 = RealCartesianProductEJA(2)
-            sage: J3 = RealSymmetricEJA(1)
-            sage: mat1 = matrix(QQ, [[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: 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
-
-        """
-        return FiniteDimensionalEuclideanJordanAlgebraOperator(
-          other.domain(),
-          self.codomain(),
-          self.matrix()*other.matrix())
-
-
-    def __eq__(self, other):
-        if self.domain() != other.domain():
-            return False
-        if self.codomain() != other.codomain():
-            return False
-        if self.matrix() != other.matrix():
-            return False
-        return True
-
-
-    def __invert__(self):
-        """
-        Invert this EJA operator.
-
-        EXAMPLES::
-
-            sage: J = RealSymmetricEJA(2)
-            sage: id = identity_matrix(J.base_ring(), J.dimension())
-            sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(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
-
-        """
-        return FiniteDimensionalEuclideanJordanAlgebraOperator(
-                self.codomain(),
-                self.domain(),
-                ~self.matrix())
-
-
-    def __mul__(self, other):
-        """
-        Compose two EJA operators, or scale myself by an element of the
-        ambient vector space.
-
-        We need to override the real ``__mul__`` function to prevent the
-        coercion framework from throwing an error when it fails to convert
-        a base ring element into a morphism.
-
-        EXAMPLES:
-
-        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: x.operator()
-            Linear operator between finite-dimensional Euclidean Jordan algebras
-            represented by the matrix:
-            [ 2  4  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
-            sage: x.operator()*(1/2)
-            Linear operator between finite-dimensional Euclidean Jordan algebras
-            represented by the matrix:
-            [  1   2   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
-
-        """
-        if other in self.codomain().base_ring():
-            return FiniteDimensionalEuclideanJordanAlgebraOperator(
-                self.domain(),
-                self.codomain(),
-                self.matrix()*other)
-
-        # This should eventually delegate to _composition_ after performing
-        # some sanity checks for us.
-        mor = super(FiniteDimensionalEuclideanJordanAlgebraOperator,self)
-        return mor.__mul__(other)
-
-
-    def _neg_(self):
-        """
-        Negate this EJA operator.
-
-        EXAMPLES::
-
-            sage: J = RealSymmetricEJA(2)
-            sage: id = identity_matrix(J.base_ring(), J.dimension())
-            sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(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
-
-        """
-        return FiniteDimensionalEuclideanJordanAlgebraOperator(
-                self.domain(),
-                self.codomain(),
-                -self.matrix())
-
-
-    def __pow__(self, n):
-        """
-        Raise this EJA operator to the power ``n``.
-
-        TESTS:
-
-        Ensure that we get back another EJA operator that can be added,
-        subtracted, et cetera::
-
-            sage: J = RealSymmetricEJA(2)
-            sage: id = identity_matrix(J.base_ring(), J.dimension())
-            sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(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
-
-        """
-        if (n == 1):
-            return self
-        elif (n == 0):
-            # Raising a vector space morphism to the zero power gives
-            # you back a special IdentityMorphism that is useless to us.
-            rows = self.codomain().dimension()
-            cols = self.domain().dimension()
-            mat = matrix.identity(self.base_ring(), rows, cols)
-        else:
-            mat = self.matrix()**n
-
-        return FiniteDimensionalEuclideanJordanAlgebraOperator(
-                 self.domain(),
-                 self.codomain(),
-                 mat)
-
-
-    def _repr_(self):
-        r"""
-
-        A text representation of this linear operator on a Euclidean
-        Jordan Algebra.
-
-        EXAMPLES::
-
-            sage: J = JordanSpinEJA(2)
-            sage: id = identity_matrix(J.base_ring(), J.dimension())
-            sage: FiniteDimensionalEuclideanJordanAlgebraOperator(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
-
-        """
-        msg = ("Linear operator between finite-dimensional Euclidean Jordan "
-                "algebras represented by the matrix:\n",
-               "{!r}\n",
-               "Domain: {}\n",
-               "Codomain: {}")
-        return ''.join(msg).format(self.matrix(),
-                                   self.domain(),
-                                   self.codomain())
-
-
-    def _sub_(self, other):
-        """
-        Subtract ``other`` from this EJA operator.
-
-        EXAMPLES::
-
-            sage: J = RealSymmetricEJA(2)
-            sage: id = identity_matrix(J.base_ring(),J.dimension())
-            sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(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
-
-        """
-        return (self + (-other))
-
-
-    def matrix(self):
-        """
-        Return the matrix representation of this operator with respect
-        to the default bases of its (co)domain.
-
-        EXAMPLES::
-
-            sage: J = RealSymmetricEJA(2)
-            sage: mat = matrix(J.base_ring(), J.dimension(), range(9))
-            sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,mat)
-            sage: f.matrix()
-            [0 1 2]
-            [3 4 5]
-            [6 7 8]
-
-        """
-        return self._matrix
-
-
-    def minimal_polynomial(self):
-        """
-        Return the minimal polynomial of this linear operator,
-        in the variable ``t``.
-
-        EXAMPLES::
-
-            sage: J = RealSymmetricEJA(3)
-            sage: J.one().operator().minimal_polynomial()
-            t - 1
-
-        """
-        # The matrix method returns a polynomial in 'x' but want one in 't'.
-        return self.matrix().minimal_polynomial().change_variable_name('t')
+from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
 
 
 class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
@@ -413,6 +61,10 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
                  rank=None,
                  natural_basis=None):
         """
+        SETUP::
+
+            sage: from mjo.eja.eja_algebra import random_eja
+
         EXAMPLES:
 
         By definition, Jordan multiplication commutes::
@@ -569,6 +221,10 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
             other words, we don't have to do a change of basis before e.g.
             computing the trace or determinant.
 
+        SETUP::
+
+            sage: from mjo.eja.eja_algebra import JordanSpinEJA
+
         EXAMPLES:
 
         The characteristic polynomial in the spin algebra is given in
@@ -620,6 +276,10 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
         subclasses if they are sure that the necessary properties are
         satisfied.
 
+        SETUP::
+
+            sage: from mjo.eja.eja_algebra import random_eja
+
         EXAMPLES:
 
         The inner product must satisfy its axiom for this algebra to truly
@@ -652,6 +312,11 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
         Note that this will always return a matrix. The standard basis
         in `R^n` will be returned as `n`-by-`1` column matrices.
 
+        SETUP::
+
+            sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
+            ....:                                  RealSymmetricEJA)
+
         EXAMPLES::
 
             sage: J = RealSymmetricEJA(2)
@@ -690,10 +355,15 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
         else:
             return self._rank
 
+
     def vector_space(self):
         """
         Return the vector space that underlies this algebra.
 
+        SETUP::
+
+            sage: from mjo.eja.eja_algebra import RealSymmetricEJA
+
         EXAMPLES::
 
             sage: J = RealSymmetricEJA(2)
@@ -721,6 +391,11 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
 
         def __init__(self, A, elt=None):
             """
+
+            SETUP::
+
+                sage: from mjo.eja.eja_algebra import RealSymmetricEJA
+
             EXAMPLES:
 
             The identity in `S^n` is converted to the identity in the EJA::
@@ -777,6 +452,10 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
                 instead of column vectors! We, on the other hand, assume column
                 vectors everywhere.
 
+            SETUP::
+
+                sage: from mjo.eja.eja_algebra import random_eja
+
             EXAMPLES::
 
                 sage: set_random_seed()
@@ -825,6 +504,11 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
             operation is ultimately kosher. This function sidesteps
             the CAS to get the answer we want and expect.
 
+            SETUP::
+
+                sage: from mjo.eja.eja_algebra import (RealCartesianProductEJA,
+                ....:                                  random_eja)
+
             EXAMPLES::
 
                 sage: R = PolynomialRing(QQ, 't')
@@ -861,6 +545,10 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
             """
             Return the characteristic polynomial of this element.
 
+            SETUP::
+
+                sage: from mjo.eja.eja_algebra import RealCartesianProductEJA
+
             EXAMPLES:
 
             The rank of `R^3` is three, and the minimal polynomial of
@@ -896,6 +584,15 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
             """
             Return the parent algebra's inner product of myself and ``other``.
 
+            SETUP::
+
+                sage: from mjo.eja.eja_algebra import (
+                ....:   ComplexHermitianEJA,
+                ....:   JordanSpinEJA,
+                ....:   QuaternionHermitianEJA,
+                ....:   RealSymmetricEJA,
+                ....:   random_eja)
+
             EXAMPLES:
 
             The inner product in the Jordan spin algebra is the usual
@@ -959,6 +656,10 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
             Return whether or not this element operator-commutes
             with ``other``.
 
+            SETUP::
+
+                sage: from mjo.eja.eja_algebra import random_eja
+
             EXAMPLES:
 
             The definition of a Jordan algebra says that any element
@@ -1046,6 +747,11 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
             """
             Return my determinant, the product of my eigenvalues.
 
+            SETUP::
+
+                sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
+                ....:                                  random_eja)
+
             EXAMPLES::
 
                 sage: J = JordanSpinEJA(2)
@@ -1092,6 +798,11 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
             We appeal to the quadratic representation as in Koecher's
             Theorem 12 in Chapter III, Section 5.
 
+            SETUP::
+
+                sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
+                ....:                                  random_eja)
+
             EXAMPLES:
 
             The inverse in the spin factor algebra is given in Alizadeh's
@@ -1168,6 +879,10 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
             whether or not the paren't algebra's zero element is a root
             of this element's minimal polynomial.
 
+            SETUP::
+
+                sage: from mjo.eja.eja_algebra import random_eja
+
             TESTS:
 
             The identity element is always invertible::
@@ -1199,6 +914,10 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
             an assocoative subalgebra and we're nilpotent there if and
             only if we're nilpotent here (probably).
 
+            SETUP::
+
+                sage: from mjo.eja.eja_algebra import random_eja
+
             TESTS:
 
             The identity element is never nilpotent::
@@ -1239,6 +958,10 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
             """
             Return whether or not this is a regular element.
 
+            SETUP::
+
+                sage: from mjo.eja.eja_algebra import JordanSpinEJA
+
             EXAMPLES:
 
             The identity element always has degree one, but any element
@@ -1267,6 +990,10 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
             and figuring out its dimension (that is, whether or not
             they're linearly dependent).
 
+            SETUP::
+
+                sage: from mjo.eja.eja_algebra import JordanSpinEJA
+
             EXAMPLES::
 
                 sage: J = JordanSpinEJA(4)
@@ -1312,6 +1039,11 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
             polynomial of this element's operator matrix (in that
             subalgebra). This works by Baes Proposition 2.3.16.
 
+            SETUP::
+
+                sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
+                ....:                                  random_eja)
+
             TESTS:
 
             The minimal polynomial of the identity and zero elements are
@@ -1380,6 +1112,11 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
             "natural" representation of this element as a Hermitian
             matrix, if it has one. If not, you get the usual representation.
 
+            SETUP::
+
+                sage: from mjo.eja.eja_algebra import (ComplexHermitianEJA,
+                ....:                                  QuaternionHermitianEJA)
+
             EXAMPLES::
 
                 sage: J = ComplexHermitianEJA(3)
@@ -1423,6 +1160,10 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
             Return the left-multiplication-by-this-element
             operator on the ambient algebra.
 
+            SETUP::
+
+                sage: from mjo.eja.eja_algebra import random_eja
+
             TESTS::
 
                 sage: set_random_seed()
@@ -1447,6 +1188,11 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
             """
             Return the quadratic representation of this element.
 
+            SETUP::
+
+                sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
+                ....:                                  random_eja)
+
             EXAMPLES:
 
             The explicit form in the spin factor algebra is given by
@@ -1578,6 +1324,10 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
             Return the associative subalgebra of the parent EJA generated
             by this element.
 
+            SETUP::
+
+                sage: from mjo.eja.eja_algebra import random_eja
+
             TESTS::
 
                 sage: set_random_seed()
@@ -1635,6 +1385,10 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
             Find an idempotent in the associative subalgebra I generate
             using Proposition 2.3.5 in Baes.
 
+            SETUP::
+
+                sage: from mjo.eja.eja_algebra import random_eja
+
             TESTS::
 
                 sage: set_random_seed()
@@ -1695,6 +1449,12 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
             """
             Return my trace, the sum of my eigenvalues.
 
+            SETUP::
+
+                sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
+                ....:                                  RealCartesianProductEJA,
+                ....:                                  random_eja)
+
             EXAMPLES::
 
                 sage: J = JordanSpinEJA(3)
@@ -1732,6 +1492,10 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
             """
             Return the trace inner product of myself and ``other``.
 
+            SETUP::
+
+                sage: from mjo.eja.eja_algebra import random_eja
+
             TESTS:
 
             The trace inner product is commutative::
@@ -1788,6 +1552,10 @@ class RealCartesianProductEJA(FiniteDimensionalEuclideanJordanAlgebra):
     copies of the spin algebra. Once Cartesian product algebras
     are implemented, this can go.
 
+    SETUP::
+
+        sage: from mjo.eja.eja_algebra import RealCartesianProductEJA
+
     EXAMPLES:
 
     This multiplication table can be verified by hand::
@@ -1851,6 +1619,10 @@ def random_eja():
     Later this might be extended to return Cartesian products of the
     EJAs above.
 
+    SETUP::
+
+        sage: from mjo.eja.eja_algebra import random_eja
+
     TESTS::
 
         sage: random_eja()
@@ -1895,6 +1667,10 @@ def _complex_hermitian_basis(n, field=QQ):
     """
     Returns a basis for the space of complex Hermitian n-by-n matrices.
 
+    SETUP::
+
+        sage: from mjo.eja.eja_algebra import _complex_hermitian_basis
+
     TESTS::
 
         sage: set_random_seed()
@@ -1932,6 +1708,10 @@ def _quaternion_hermitian_basis(n, field=QQ):
     """
     Returns a basis for the space of quaternion Hermitian n-by-n matrices.
 
+    SETUP::
+
+        sage: from mjo.eja.eja_algebra import _quaternion_hermitian_basis
+
     TESTS::
 
         sage: set_random_seed()
@@ -2029,6 +1809,10 @@ def _embed_complex_matrix(M):
     matrices of size 2n-by-2n via the map the sends each entry `z = a +
     bi` to the block matrix ``[[a,b],[-b,a]]``.
 
+    SETUP::
+
+        sage: from mjo.eja.eja_algebra import _embed_complex_matrix
+
     EXAMPLES::
 
         sage: F = QuadraticField(-1,'i')
@@ -2077,6 +1861,11 @@ def _unembed_complex_matrix(M):
     """
     The inverse of _embed_complex_matrix().
 
+    SETUP::
+
+        sage: from mjo.eja.eja_algebra import (_embed_complex_matrix,
+        ....:                                  _unembed_complex_matrix)
+
     EXAMPLES::
 
         sage: A = matrix(QQ,[ [ 1,  2,   3,  4],
@@ -2131,6 +1920,10 @@ def _embed_quaternion_matrix(M):
     ``[[a + bi, c+di],[-c + di, a-bi]]`, and then embedding those into
     a real matrix.
 
+    SETUP::
+
+        sage: from mjo.eja.eja_algebra import _embed_quaternion_matrix
+
     EXAMPLES::
 
         sage: Q = QuaternionAlgebra(QQ,-1,-1)
@@ -2184,6 +1977,11 @@ def _unembed_quaternion_matrix(M):
     """
     The inverse of _embed_quaternion_matrix().
 
+    SETUP::
+
+        sage: from mjo.eja.eja_algebra import (_embed_quaternion_matrix,
+        ....:                                  _unembed_quaternion_matrix)
+
     EXAMPLES::
 
         sage: M = matrix(QQ, [[ 1,  2,  3,  4],
@@ -2250,6 +2048,10 @@ class RealSymmetricEJA(FiniteDimensionalEuclideanJordanAlgebra):
     matrices, the usual symmetric Jordan product, and the trace inner
     product. It has dimension `(n^2 + n)/2` over the reals.
 
+    SETUP::
+
+        sage: from mjo.eja.eja_algebra import RealSymmetricEJA
+
     EXAMPLES::
 
         sage: J = RealSymmetricEJA(2)
@@ -2311,6 +2113,10 @@ class ComplexHermitianEJA(FiniteDimensionalEuclideanJordanAlgebra):
     and the real-part-of-trace inner product. It has dimension `n^2` over
     the reals.
 
+    SETUP::
+
+        sage: from mjo.eja.eja_algebra import ComplexHermitianEJA
+
     TESTS:
 
     The degree of this algebra is `n^2`::
@@ -2368,6 +2174,10 @@ class QuaternionHermitianEJA(FiniteDimensionalEuclideanJordanAlgebra):
     real-part-of-trace inner product. It has dimension `2n^2 - n` over
     the reals.
 
+    SETUP::
+
+        sage: from mjo.eja.eja_algebra import QuaternionHermitianEJA
+
     TESTS:
 
     The degree of this algebra is `n^2`::
@@ -2427,6 +2237,10 @@ class JordanSpinEJA(FiniteDimensionalEuclideanJordanAlgebra):
     (<x_bar,y_bar>, x0*y_bar + y0*x_bar)``. It has dimension `n` over
     the reals.
 
+    SETUP::
+
+        sage: from mjo.eja.eja_algebra import JordanSpinEJA
+
     EXAMPLES:
 
     This multiplication table can be verified by hand::
diff --git a/mjo/eja/eja_operator.py b/mjo/eja/eja_operator.py
new file mode 100644 (file)
index 0000000..b225b29
--- /dev/null
@@ -0,0 +1,416 @@
+from sage.all import matrix
+from sage.categories.all import FreeModules
+from sage.categories.map import Map
+
+class FiniteDimensionalEuclideanJordanAlgebraOperator(Map):
+    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')
+
+        F = domain_eja.base_ring()
+        if not (F == codomain_eja.base_ring()):
+            raise ValueError("domain and codomain must have the same base ring")
+
+        # We need to supply something here to avoid getting the
+        # default Homset of the parent FiniteDimensionalAlgebra class,
+        # which messes up e.g. equality testing. We use FreeModules(F)
+        # instead of VectorSpaces(F) because our characteristic polynomial
+        # algorithm will need to F to be a polynomial ring at some point.
+        # When F is a field, FreeModules(F) returns VectorSpaces(F) anyway.
+        parent = domain_eja.Hom(codomain_eja, FreeModules(F))
+
+        # 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)
+
+        # Keep a matrix around to do all of the real work. It would
+        # be nice if we could use a VectorSpaceMorphism instead, but
+        # those use row vectors that we don't want to accidentally
+        # expose to our users.
+        self._matrix = mat
+
+
+    def _call_(self, x):
+        """
+        Allow this operator to be called only on elements of an EJA.
+
+        SETUP::
+
+            sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
+            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: id = identity_matrix(J.base_ring(), J.dimension())
+            sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id)
+            sage: f(x) == x
+            True
+
+        """
+        return self.codomain()(self.matrix()*x.vector())
+
+
+    def _add_(self, other):
+        """
+        Add the ``other`` EJA operator to this one.
+
+        SETUP::
+
+            sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
+            sage: from mjo.eja.eja_algebra import (
+            ....:   JordanSpinEJA,
+            ....:   RealSymmetricEJA )
+
+        EXAMPLES:
+
+        When we add two EJA operators, we get another one back::
+
+            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 + 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
+
+        If you try to add two identical vector space operators but on
+        different EJAs, that should blow up::
+
+            sage: J1 = RealSymmetricEJA(2)
+            sage: J2 = JordanSpinEJA(3)
+            sage: id = identity_matrix(QQ, 3)
+            sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J1,J1,id)
+            sage: g = FiniteDimensionalEuclideanJordanAlgebraOperator(J2,J2,id)
+            sage: f + g
+            Traceback (most recent call last):
+            ...
+            TypeError: unsupported operand parent(s) for +: ...
+
+        """
+        return FiniteDimensionalEuclideanJordanAlgebraOperator(
+                self.domain(),
+                self.codomain(),
+                self.matrix() + other.matrix())
+
+
+    def _composition_(self, other, homset):
+        """
+        Compose two EJA operators to get another one (and NOT a formal
+        composite object) back.
+
+        SETUP::
+
+            sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
+            sage: from mjo.eja.eja_algebra import (
+            ....:   JordanSpinEJA,
+            ....:   RealCartesianProductEJA,
+            ....:   RealSymmetricEJA)
+
+        EXAMPLES::
+
+            sage: J1 = JordanSpinEJA(3)
+            sage: J2 = RealCartesianProductEJA(2)
+            sage: J3 = RealSymmetricEJA(1)
+            sage: mat1 = matrix(QQ, [[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: 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
+
+        """
+        return FiniteDimensionalEuclideanJordanAlgebraOperator(
+          other.domain(),
+          self.codomain(),
+          self.matrix()*other.matrix())
+
+
+    def __eq__(self, other):
+        if self.domain() != other.domain():
+            return False
+        if self.codomain() != other.codomain():
+            return False
+        if self.matrix() != other.matrix():
+            return False
+        return True
+
+
+    def __invert__(self):
+        """
+        Invert this EJA operator.
+
+        SETUP::
+
+            sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
+            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
+            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
+
+        """
+        return FiniteDimensionalEuclideanJordanAlgebraOperator(
+                self.codomain(),
+                self.domain(),
+                ~self.matrix())
+
+
+    def __mul__(self, other):
+        """
+        Compose two EJA operators, or scale myself by an element of the
+        ambient vector space.
+
+        We need to override the real ``__mul__`` function to prevent the
+        coercion framework from throwing an error when it fails to convert
+        a base ring element into a morphism.
+
+        SETUP::
+
+            sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
+            sage: from mjo.eja.eja_algebra import RealSymmetricEJA
+
+        EXAMPLES:
+
+        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: x.operator()
+            Linear operator between finite-dimensional Euclidean Jordan algebras
+            represented by the matrix:
+            [ 2  4  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
+            sage: x.operator()*(1/2)
+            Linear operator between finite-dimensional Euclidean Jordan algebras
+            represented by the matrix:
+            [  1   2   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
+
+        """
+        if other in self.codomain().base_ring():
+            return FiniteDimensionalEuclideanJordanAlgebraOperator(
+                self.domain(),
+                self.codomain(),
+                self.matrix()*other)
+
+        # This should eventually delegate to _composition_ after performing
+        # some sanity checks for us.
+        mor = super(FiniteDimensionalEuclideanJordanAlgebraOperator,self)
+        return mor.__mul__(other)
+
+
+    def _neg_(self):
+        """
+        Negate this EJA operator.
+
+        SETUP::
+
+            sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
+            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
+            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
+
+        """
+        return FiniteDimensionalEuclideanJordanAlgebraOperator(
+                self.domain(),
+                self.codomain(),
+                -self.matrix())
+
+
+    def __pow__(self, n):
+        """
+        Raise this EJA operator to the power ``n``.
+
+        SETUP::
+
+            sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
+            sage: from mjo.eja.eja_algebra import RealSymmetricEJA
+
+        TESTS:
+
+        Ensure that we get back another EJA operator that can be added,
+        subtracted, et cetera::
+
+            sage: J = RealSymmetricEJA(2)
+            sage: id = identity_matrix(J.base_ring(), J.dimension())
+            sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(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
+
+        """
+        if (n == 1):
+            return self
+        elif (n == 0):
+            # Raising a vector space morphism to the zero power gives
+            # you back a special IdentityMorphism that is useless to us.
+            rows = self.codomain().dimension()
+            cols = self.domain().dimension()
+            mat = matrix.identity(self.base_ring(), rows, cols)
+        else:
+            mat = self.matrix()**n
+
+        return FiniteDimensionalEuclideanJordanAlgebraOperator(
+                 self.domain(),
+                 self.codomain(),
+                 mat)
+
+
+    def _repr_(self):
+        r"""
+
+        A text representation of this linear operator on a Euclidean
+        Jordan Algebra.
+
+        SETUP::
+
+            sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
+            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)
+            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
+
+        """
+        msg = ("Linear operator between finite-dimensional Euclidean Jordan "
+                "algebras represented by the matrix:\n",
+               "{!r}\n",
+               "Domain: {}\n",
+               "Codomain: {}")
+        return ''.join(msg).format(self.matrix(),
+                                   self.domain(),
+                                   self.codomain())
+
+
+    def _sub_(self, other):
+        """
+        Subtract ``other`` from this EJA operator.
+
+        SETUP::
+
+            sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
+            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 - (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
+
+        """
+        return (self + (-other))
+
+
+    def matrix(self):
+        """
+        Return the matrix representation of this operator with respect
+        to the default bases of its (co)domain.
+
+        SETUP::
+
+            sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
+            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.matrix()
+            [0 1 2]
+            [3 4 5]
+            [6 7 8]
+
+        """
+        return self._matrix
+
+
+    def minimal_polynomial(self):
+        """
+        Return the minimal polynomial of this linear operator,
+        in the variable ``t``.
+
+        SETUP::
+
+            sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
+            sage: from mjo.eja.eja_algebra import RealSymmetricEJA
+
+        EXAMPLES::
+
+            sage: J = RealSymmetricEJA(3)
+            sage: J.one().operator().minimal_polynomial()
+            t - 1
+
+        """
+        # The matrix method returns a polynomial in 'x' but want one in 't'.
+        return self.matrix().minimal_polynomial().change_variable_name('t')