]> gitweb.michael.orlitzky.com - sage.d.git/blobdiff - mjo/eja/euclidean_jordan_algebra.py
eja: add algebra constructors to the global namespace.
[sage.d.git] / mjo / eja / euclidean_jordan_algebra.py
index 31717eeeb8e6b9daf37f8992eaa7f52fc1127470..70a77701b342e36eec827d3ec151b87bf67fe902 100644 (file)
@@ -6,8 +6,7 @@ what can be supported in a general Jordan Algebra.
 """
 
 from sage.categories.finite_dimensional_algebras_with_basis import FiniteDimensionalAlgebrasWithBasis
-from sage.categories.morphism import SetMorphism
-from sage.modules.vector_space_morphism import VectorSpaceMorphism
+from sage.categories.map import Map
 from sage.structure.element import is_Matrix
 from sage.structure.category_object import normalize_names
 
@@ -15,23 +14,39 @@ from sage.algebras.finite_dimensional_algebras.finite_dimensional_algebra import
 from sage.algebras.finite_dimensional_algebras.finite_dimensional_algebra_element import FiniteDimensionalAlgebraElement
 
 
-class FiniteDimensionalEuclideanJordanAlgebraOperator(VectorSpaceMorphism):
+class FiniteDimensionalEuclideanJordanAlgebraOperator(Map):
     def __init__(self, domain_eja, codomain_eja, mat):
-        # We save these so that we can output them as part of our
-        # text representation. Overriding the domain/codomain methods
-        # doesn't work because the EJAs aren't (directly) vector spaces.
-        self._domain_eja = domain_eja
-        self._codomain_eja = codomain_eja
-
-        # Otherwise, we just feed everything to the vector space morphism
-        # constructor.
-        V = domain_eja.vector_space()
-        W = codomain_eja.vector_space()
-        homspace = V.Hom(W)
-        VectorSpaceMorphism.__init__(self, homspace, mat)
-
-
-    def __call__(self, x):
+        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.
 
@@ -45,42 +60,10 @@ class FiniteDimensionalEuclideanJordanAlgebraOperator(VectorSpaceMorphism):
             True
 
         """
-        # Overriding the single-underscore _call_ didn't work?
-        if x not in self._domain_eja:
-            raise ValueError("argument does not live in the operator's domain")
-        return self._codomain_eja(self.matrix()*x.vector())
-
-
-    def _repr_(self):
-        r"""
+        return self.codomain()(self.matrix()*x.vector())
 
-        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_eja,
-                                   self._codomain_eja)
-
-
-    def __add__(self, other):
+    def _add_(self, other):
         """
         Add the ``other`` EJA operator to this one.
 
@@ -112,16 +95,56 @@ class FiniteDimensionalEuclideanJordanAlgebraOperator(VectorSpaceMorphism):
             sage: f + g
             Traceback (most recent call last):
             ...
-            ValueError: operator (co)domains must match
+            TypeError: unsupported operand parent(s) for +: ...
 
         """
-        if not (self._domain_eja == other._domain_eja and
-                self._codomain_eja == other._codomain_eja):
-            raise ValueError("operator (co)domains must match")
         return FiniteDimensionalEuclideanJordanAlgebraOperator(
-                self._domain_eja,
-                self._codomain_eja,
-                VectorSpaceMorphism.__add__(self,other))
+                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):
@@ -144,31 +167,58 @@ class FiniteDimensionalEuclideanJordanAlgebraOperator(VectorSpaceMorphism):
 
         """
         return FiniteDimensionalEuclideanJordanAlgebraOperator(
-                self._codomain_eja,
-                self._domain_eja,
-                VectorSpaceMorphism.__invert__(self))
+                self.codomain(),
+                self.domain(),
+                ~self.matrix())
+
 
     def __mul__(self, other):
         """
-        Compose this EJA operator with the ``other`` one, or scale it by
-        an element of its base ring.
+        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_eja.base_ring():
+        if other in self.codomain().base_ring():
             return FiniteDimensionalEuclideanJordanAlgebraOperator(
-                self._domain_eja,
-                self._codomain_eja,
-                self._matrix*other)
-
-        if not (self._domain_eja == other._codomain_eja):
-            raise ValueError("operator (co)domains must be compatible")
+                self.domain(),
+                self.codomain(),
+                self.matrix()*other)
 
-        return FiniteDimensionalEuclideanJordanAlgebraOperator(
-                other._domain_eja,
-                self._codomain_eja,
-                VectorSpaceMorphism.__mul__(self,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):
+    def _neg_(self):
         """
         Negate this EJA operator.
 
@@ -188,9 +238,9 @@ class FiniteDimensionalEuclideanJordanAlgebraOperator(VectorSpaceMorphism):
 
         """
         return FiniteDimensionalEuclideanJordanAlgebraOperator(
-                self._domain_eja,
-                self._codomain_eja,
-                VectorSpaceMorphism.__neg__(self))
+                self.domain(),
+                self.codomain(),
+                -self.matrix())
 
 
     def __pow__(self, n):
@@ -224,20 +274,100 @@ class FiniteDimensionalEuclideanJordanAlgebraOperator(VectorSpaceMorphism):
             cols = self.domain().dimension()
             mat = matrix.identity(self.base_ring(), rows, cols)
         else:
-            mat = VectorSpaceMorphism.__pow__(self,n)
+            mat = self.matrix()**n
 
         return FiniteDimensionalEuclideanJordanAlgebraOperator(
-                 self._domain_eja,
-                 self._codomain_eja,
+                 self.domain(),
+                 self.codomain(),
                  mat)
 
-    def __sub__(self, other):
+
+    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')
+
+
 class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
     @staticmethod
     def __classcall_private__(cls,
@@ -651,7 +781,7 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
 
                 sage: set_random_seed()
                 sage: x = random_eja().random_element()
-                sage: x.operator_matrix()*x.vector() == (x^2).vector()
+                sage: x.operator()(x) == (x^2)
                 True
 
             A few examples of power-associativity::
@@ -670,19 +800,18 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
                 sage: x = random_eja().random_element()
                 sage: m = ZZ.random_element(0,10)
                 sage: n = ZZ.random_element(0,10)
-                sage: Lxm = (x^m).operator_matrix()
-                sage: Lxn = (x^n).operator_matrix()
+                sage: Lxm = (x^m).operator()
+                sage: Lxn = (x^n).operator()
                 sage: Lxm*Lxn == Lxn*Lxm
                 True
 
             """
-            A = self.parent()
             if n == 0:
-                return A.one()
+                return self.parent().one()
             elif n == 1:
                 return self
             else:
-                return A( (self.operator_matrix()**(n-1))*self.vector() )
+                return (self.operator()**(n-1))(self)
 
 
         def apply_univariate_polynomial(self, p):
@@ -853,12 +982,63 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
                 sage: lhs == rhs
                 True
 
+            Test the first polarization identity from my notes, Koecher Chapter
+            III, or from Baes (2.3)::
+
+                sage: set_random_seed()
+                sage: J = random_eja()
+                sage: x = J.random_element()
+                sage: y = J.random_element()
+                sage: Lx = x.operator()
+                sage: Ly = y.operator()
+                sage: Lxx = (x*x).operator()
+                sage: Lxy = (x*y).operator()
+                sage: bool(2*Lx*Lxy + Ly*Lxx == 2*Lxy*Lx + Lxx*Ly)
+                True
+
+            Test the second polarization identity from my notes or from
+            Baes (2.4)::
+
+                sage: set_random_seed()
+                sage: J = random_eja()
+                sage: x = J.random_element()
+                sage: y = J.random_element()
+                sage: z = J.random_element()
+                sage: Lx = x.operator()
+                sage: Ly = y.operator()
+                sage: Lz = z.operator()
+                sage: Lzy = (z*y).operator()
+                sage: Lxy = (x*y).operator()
+                sage: Lxz = (x*z).operator()
+                sage: bool(Lx*Lzy + Lz*Lxy + Ly*Lxz == Lzy*Lx + Lxy*Lz + Lxz*Ly)
+                True
+
+            Test the third polarization identity from my notes or from
+            Baes (2.5)::
+
+                sage: set_random_seed()
+                sage: J = random_eja()
+                sage: u = J.random_element()
+                sage: y = J.random_element()
+                sage: z = J.random_element()
+                sage: Lu = u.operator()
+                sage: Ly = y.operator()
+                sage: Lz = z.operator()
+                sage: Lzy = (z*y).operator()
+                sage: Luy = (u*y).operator()
+                sage: Luz = (u*z).operator()
+                sage: Luyz = (u*(y*z)).operator()
+                sage: lhs = Lu*Lzy + Lz*Luy + Ly*Luz
+                sage: rhs = Luyz + Ly*Lu*Lz + Lz*Lu*Ly
+                sage: bool(lhs == rhs)
+                True
+
             """
             if not other in self.parent():
                 raise TypeError("'other' must live in the same algebra")
 
-            A = self.operator_matrix()
-            B = other.operator_matrix()
+            A = self.operator()
+            B = other.operator()
             return (A*B == B*A)
 
 
@@ -1115,7 +1295,7 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
             Our parent class defines ``left_matrix`` and ``matrix``
             methods whose names are misleading. We don't want them.
             """
-            raise NotImplementedError("use operator_matrix() instead")
+            raise NotImplementedError("use operator().matrix() instead")
 
         matrix = left_matrix
 
@@ -1186,11 +1366,8 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
             # and subalgebra_generated_by() must be the same, and in
             # the same order!
             elt = assoc_subalg(V.coordinates(self.vector()))
+            return elt.operator().minimal_polynomial()
 
-            # We get back a symbolic polynomial in 'x' but want a real
-            # polynomial in 't'.
-            p_of_x = elt.operator_matrix().minimal_polynomial()
-            return p_of_x.change_variable_name('t')
 
 
         def natural_representation(self):
@@ -1259,85 +1436,11 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
 
             """
             P = self.parent()
+            fda_elt = FiniteDimensionalAlgebraElement(P, self)
             return FiniteDimensionalEuclideanJordanAlgebraOperator(
-                     P,P,
-                     self.operator_matrix() )
-
-
-
-        def operator_matrix(self):
-            """
-            Return the matrix that represents left- (or right-)
-            multiplication by this element in the parent algebra.
-
-            We implement this ourselves to work around the fact that
-            our parent class represents everything with row vectors.
-
-            EXAMPLES:
-
-            Test the first polarization identity from my notes, Koecher Chapter
-            III, or from Baes (2.3)::
-
-                sage: set_random_seed()
-                sage: J = random_eja()
-                sage: x = J.random_element()
-                sage: y = J.random_element()
-                sage: Lx = x.operator_matrix()
-                sage: Ly = y.operator_matrix()
-                sage: Lxx = (x*x).operator_matrix()
-                sage: Lxy = (x*y).operator_matrix()
-                sage: bool(2*Lx*Lxy + Ly*Lxx == 2*Lxy*Lx + Lxx*Ly)
-                True
-
-            Test the second polarization identity from my notes or from
-            Baes (2.4)::
-
-                sage: set_random_seed()
-                sage: J = random_eja()
-                sage: x = J.random_element()
-                sage: y = J.random_element()
-                sage: z = J.random_element()
-                sage: Lx = x.operator_matrix()
-                sage: Ly = y.operator_matrix()
-                sage: Lz = z.operator_matrix()
-                sage: Lzy = (z*y).operator_matrix()
-                sage: Lxy = (x*y).operator_matrix()
-                sage: Lxz = (x*z).operator_matrix()
-                sage: bool(Lx*Lzy + Lz*Lxy + Ly*Lxz == Lzy*Lx + Lxy*Lz + Lxz*Ly)
-                True
-
-            Test the third polarization identity from my notes or from
-            Baes (2.5)::
-
-                sage: set_random_seed()
-                sage: J = random_eja()
-                sage: u = J.random_element()
-                sage: y = J.random_element()
-                sage: z = J.random_element()
-                sage: Lu = u.operator_matrix()
-                sage: Ly = y.operator_matrix()
-                sage: Lz = z.operator_matrix()
-                sage: Lzy = (z*y).operator_matrix()
-                sage: Luy = (u*y).operator_matrix()
-                sage: Luz = (u*z).operator_matrix()
-                sage: Luyz = (u*(y*z)).operator_matrix()
-                sage: lhs = Lu*Lzy + Lz*Luy + Ly*Luz
-                sage: rhs = Luyz + Ly*Lu*Lz + Lz*Lu*Ly
-                sage: bool(lhs == rhs)
-                True
-
-            Ensure that our operator's ``matrix`` method agrees with
-            this implementation::
-
-                sage: set_random_seed()
-                sage: J = random_eja()
-                sage: x = J.random_element()
-                sage: x.operator().matrix() == x.operator_matrix()
-                True
-
-            """
-            fda_elt = FiniteDimensionalAlgebraElement(self.parent(), self)
-            return fda_elt.matrix().transpose()
+                     P,
+                     P,
+                     fda_elt.matrix().transpose() )
 
 
         def quadratic_representation(self, other=None):
@@ -1482,13 +1585,13 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
                 sage: x.subalgebra_generated_by().is_associative()
                 True
 
-            Squaring in the subalgebra should be the same thing as
-            squaring in the superalgebra::
+            Squaring in the subalgebra should work the same as in
+            the superalgebra::
 
                 sage: set_random_seed()
                 sage: x = random_eja().random_element()
                 sage: u = x.subalgebra_generated_by().random_element()
-                sage: u.operator_matrix()*u.vector() == (u**2).vector()
+                sage: u.operator()(u) == u^2
                 True
 
             """
@@ -1559,7 +1662,7 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
             s = 0
             minimal_dim = V.dimension()
             for i in xrange(1, V.dimension()):
-                this_dim = (u**i).operator_matrix().image().dimension()
+                this_dim = (u**i).operator().matrix().image().dimension()
                 if this_dim < minimal_dim:
                     minimal_dim = this_dim
                     s = i
@@ -1576,7 +1679,7 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
             # Beware, solve_right() means that we're using COLUMN vectors.
             # Our FiniteDimensionalAlgebraElement superclass uses rows.
             u_next = u**(s+1)
-            A = u_next.operator_matrix()
+            A = u_next.operator().matrix()
             c_coordinates = A.solve_right(u_next.vector())
 
             # Now c_coordinates is the idempotent we want, but it's in