]> gitweb.michael.orlitzky.com - sage.d.git/commitdiff
eja: replace the Hom stuff with a custom EJA operator class.
authorMichael Orlitzky <michael@orlitzky.com>
Sat, 27 Jul 2019 15:05:42 +0000 (11:05 -0400)
committerMichael Orlitzky <michael@orlitzky.com>
Mon, 29 Jul 2019 03:19:01 +0000 (23:19 -0400)
Implementing homsets as linear operators was always a hack, since
linear operators will in general not be algebra homomorphisms. We
wound up re-implementing addition, subtraction, etc. of operators
anyway, so in hindsight, there is no extra difficulty in creating our
own EJA operator class as a subclass of VectorSpaceMorphism.

This commit throws out the EJA morphism stuff, and replaces it with
an equivalent EJA operator class to remain mathematically sound.

mjo/eja/euclidean_jordan_algebra.py

index 24ea73151e2907c8a99d97efc1a72dd32ac1838f..31717eeeb8e6b9daf37f8992eaa7f52fc1127470 100644 (file)
@@ -7,406 +7,235 @@ 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.structure.element import is_Matrix
 from sage.structure.category_object import normalize_names
 
 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.algebras.finite_dimensional_algebras.finite_dimensional_algebra_morphism import FiniteDimensionalAlgebraMorphism, FiniteDimensionalAlgebraHomset
 
 
-class FiniteDimensionalEuclideanJordanAlgebraHomset(FiniteDimensionalAlgebraHomset):
+class FiniteDimensionalEuclideanJordanAlgebraOperator(VectorSpaceMorphism):
+    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
 
-    def has_coerce_map_from(self, S):
-        """
-        EXAMPLES::
-
-            sage: J = RealSymmetricEJA(2)
-            sage: H = J.Hom(J)
-            sage: H.has_coerce_map_from(QQ)
-            True
-
-        """
-        try:
-            # The Homset classes override has_coerce_map_from() with
-            # something that crashes when it's given e.g. QQ.
-            if S.is_subring(self.codomain().base_ring()):
-                return True
-        except:
-            pclass = super(FiniteDimensionalEuclideanJordanAlgebraHomset, self)
-            return pclass.has_coerce_map_from(S)
-
-
-    def _coerce_map_from_(self, S):
-        """
-        EXAMPLES::
-
-            sage: J = RealSymmetricEJA(2)
-            sage: H = J.Hom(J)
-            sage: H.coerce(2)
-            Morphism from Euclidean Jordan algebra of degree 3 over Rational
-            Field to Euclidean Jordan algebra of degree 3 over Rational Field
-            given by matrix
-            [2 0 0]
-            [0 2 0]
-            [0 0 2]
-
-        """
-        C = self.codomain()
-        R = C.base_ring()
-        if S.is_subring(R):
-            h = S.hom(self.codomain())
-            return SetMorphism(Hom(S,C), lambda x: h(x).operator())
+        # 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):
         """
-        EXAMPLES::
-
-            sage: J = RealSymmetricEJA(2)
-            sage: H = J.Hom(J)
-            sage: H(2)
-            Morphism from Euclidean Jordan algebra of degree 3 over Rational
-            Field to Euclidean Jordan algebra of degree 3 over Rational Field
-            given by matrix
-            [2 0 0]
-            [0 2 0]
-            [0 0 2]
-
-        """
-        if x in self.base_ring():
-            cols = self.domain().dimension()
-            rows = self.codomain().dimension()
-            x = x*identity_matrix(self.codomain().base_ring(), rows, cols)
-        return FiniteDimensionalEuclideanJordanAlgebraMorphism(self, x)
-
-
-    def one(self):
-        """
-        Return the identity morphism, but as a member of the right
-        space (so that we can add it, multiply it, etc.)
-        """
-        cols = self.domain().dimension()
-        rows = self.codomain().dimension()
-        mat = identity_matrix(self.base_ring(), rows, cols)
-        return FiniteDimensionalEuclideanJordanAlgebraMorphism(self, mat)
-
-
-
-class FiniteDimensionalEuclideanJordanAlgebraMorphism(FiniteDimensionalAlgebraMorphism):
-    """
-    A linear map between two finite-dimensional EJAs.
-
-    This is a very thin wrapper around FiniteDimensionalAlgebraMorphism
-    that does only a few things:
-
-      1. Avoids the ``unitary`` and ``check`` arguments to the constructor
-         that will always be ``False``. This is necessary because these
-         are homomorphisms with respect to ADDITION, but the SageMath
-         machinery wants to check that they're homomorphisms with respect
-         to (Jordan) MULTIPLICATION. That obviously doesn't work.
-
-      2. Inputs and outputs the underlying matrix with respect to COLUMN
-         vectors, unlike the parent class.
-
-      3. Allows us to add, subtract, negate, multiply (compose), and
-         invert morphisms in the obvious way.
-
-    If this seems a bit heavyweight, it is. I would have been happy to
-    use a the ring morphism that underlies the finite-dimensional
-    algebra morphism, but they don't seem to be callable on elements of
-    our EJA, and you can't add/multiply/etc. them.
-    """
-    def _add_(self, other):
-        """
-        Add two EJA morphisms in the obvious way.
+        Allow this operator to be called only on elements of an EJA.
 
         EXAMPLES::
 
-            sage: J = RealSymmetricEJA(3)
-            sage: x = J.zero()
-            sage: y = J.one()
-            sage: x.operator() + y.operator()
-            Morphism from Euclidean Jordan algebra of degree 6 over Rational
-            Field to Euclidean Jordan algebra of degree 6 over Rational Field
-            given by matrix
-            [1 0 0 0 0 0]
-            [0 1 0 0 0 0]
-            [0 0 1 0 0 0]
-            [0 0 0 1 0 0]
-            [0 0 0 0 1 0]
-            [0 0 0 0 0 1]
-
-        TESTS::
-
-            sage: set_random_seed()
-            sage: J = random_eja()
-            sage: x = J.random_element()
-            sage: y = J.random_element()
-            sage: (x.operator() + y.operator()) in J.Hom(J)
+            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
 
         """
-        P = self.parent()
-        if not other in P:
-            raise ValueError("summands must live in the same space")
+        # 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())
 
-        return FiniteDimensionalEuclideanJordanAlgebraMorphism(
-                  P,
-                  self.matrix() + other.matrix() )
 
+    def _repr_(self):
+        r"""
 
-    def __init__(self, parent, f):
-        FiniteDimensionalAlgebraMorphism.__init__(self,
-                                                  parent,
-                                                  f.transpose(),
-                                                  unitary=False,
-                                                  check=False)
-
+        A text representation of this linear operator on a Euclidean
+        Jordan Algebra.
 
-    def __invert__(self):
-        """
         EXAMPLES::
 
-            sage: J = RealSymmetricEJA(2)
-            sage: x = J.linear_combination(zip(range(len(J.gens())), J.gens()))
-            sage: x.is_invertible()
-            True
-            sage: ~x.operator()
-            Morphism from Euclidean Jordan algebra of degree 3 over Rational
-            Field to Euclidean Jordan algebra of degree 3 over Rational Field
-            given by matrix
-            [-3/2    2 -1/2]
-            [   1    0    0]
-            [-1/2    0  1/2]
-            sage: x.operator_matrix().inverse()
-            [-3/2    2 -1/2]
-            [   1    0    0]
-            [-1/2    0  1/2]
-
-        TESTS:
-
-        Beware, ``x`` being invertible isn't sufficient for its operator
-        to be invertible below::
-
-            sage: set_random_seed()
-            sage: J = random_eja()
-            sage: x = J.random_element()
-            sage: id = J.Hom(J).one()
-            sage: not x.operator_matrix().is_invertible() or (
-            ....:     ~x.operator()*x.operator() == id )
-            True
+            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
 
         """
-        A = self.matrix()
-        if not A.is_invertible():
-            raise ValueError("morphism is not invertible")
+        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)
 
-        P = self.parent()
-        return FiniteDimensionalEuclideanJordanAlgebraMorphism(self.parent(),
-                                                                A.inverse())
 
-    def _lmul_(self, right):
+    def __add__(self, other):
         """
-        Compose two EJA morphisms using multiplicative notation.
+        Add the ``other`` EJA operator to this one.
 
-        EXAMPLES::
-
-            sage: J = RealSymmetricEJA(2)
-            sage: x = J.zero()
-            sage: y = J.one()
-            sage: x.operator() * y.operator()
-            Morphism from Euclidean Jordan algebra of degree 3 over Rational
-            Field to Euclidean Jordan algebra of degree 3 over Rational Field
-            given by matrix
-            [0 0 0]
-            [0 0 0]
-            [0 0 0]
+        EXAMPLES:
 
-        ::
+        When we add two EJA operators, we get another one back::
 
             sage: J = RealSymmetricEJA(2)
-            sage: x = J.linear_combination(zip(range(len(J.gens())), J.gens()))
-            sage: x.operator()
-            Morphism from Euclidean Jordan algebra of degree 3 over Rational
-            Field to Euclidean Jordan algebra of degree 3 over Rational Field
-            given by matrix
-            [  0   1   0]
-            [1/2   1 1/2]
-            [  0   1   2]
-            sage: 2*x.operator()
-            Morphism from Euclidean Jordan algebra of degree 3 over Rational
-            Field to Euclidean Jordan algebra of degree 3 over Rational Field
-            given by matrix
-            [0 2 0]
-            [1 2 1]
-            [0 2 4]
-            sage: x.operator()*2
-            Morphism from Euclidean Jordan algebra of degree 3 over Rational
-            Field to Euclidean Jordan algebra of degree 3 over Rational Field
-            given by matrix
+            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]
-            [1 2 1]
-            [0 2 4]
+            [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):
+            ...
+            ValueError: operator (co)domains must match
 
-        TESTS::
+        """
+        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))
 
-            sage: set_random_seed()
-            sage: J = random_eja()
-            sage: x = J.random_element()
-            sage: y = J.random_element()
-            sage: (x.operator() * y.operator()) in J.Hom(J)
-            True
 
+    def __invert__(self):
         """
-        try:
-            # I think the morphism classes break the coercion framework
-            # somewhere along the way, so we have to do this ourselves.
-            right = self.parent().coerce(right)
-        except:
-            pass
-
-        if not right.codomain() is self.domain():
-            raise ValueError("(co)domains must agree for composition")
+        Invert this EJA operator.
 
-        return FiniteDimensionalEuclideanJordanAlgebraMorphism(
-                 self.parent(),
-                 self.matrix()*right.matrix() )
+        EXAMPLES::
 
-    __mul__ = _lmul_
+            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_eja,
+                self._domain_eja,
+                VectorSpaceMorphism.__invert__(self))
 
-    def __pow__(self, n):
+    def __mul__(self, other):
         """
+        Compose this EJA operator with the ``other`` one, or scale it by
+        an element of its base ring.
+        """
+        if other in self._codomain_eja.base_ring():
+            return FiniteDimensionalEuclideanJordanAlgebraOperator(
+                self._domain_eja,
+                self._codomain_eja,
+                self._matrix*other)
 
-        TESTS::
-
-            sage: J = JordanSpinEJA(4)
-            sage: e0,e1,e2,e3 = J.gens()
-            sage: x = -5/2*e0 + 1/2*e2 + 20*e3
-            sage: Qx = x.quadratic_representation()
-            sage: Qx^0
-            Morphism from Euclidean Jordan algebra of degree 4 over Rational
-            Field to Euclidean Jordan algebra of degree 4 over Rational Field
-            given by matrix
-            [1 0 0 0]
-            [0 1 0 0]
-            [0 0 1 0]
-            [0 0 0 1]
-            sage: (x^0).quadratic_representation() == Qx^0
-            True
+        if not (self._domain_eja == other._codomain_eja):
+            raise ValueError("operator (co)domains must be compatible")
 
-        """
-        if n == 0:
-            # We get back the stupid identity morphism which doesn't
-            # live in the right space.
-            return self.parent().one()
-        elif n == 1:
-            return self
-        else:
-            return FiniteDimensionalAlgebraMorphism.__pow__(self,n)
+        return FiniteDimensionalEuclideanJordanAlgebraOperator(
+                other._domain_eja,
+                self._codomain_eja,
+                VectorSpaceMorphism.__mul__(self,other))
 
 
-    def _neg_(self):
+    def __neg__(self):
         """
-        Negate this morphism.
+        Negate this EJA operator.
 
         EXAMPLES::
 
             sage: J = RealSymmetricEJA(2)
-            sage: x = J.one()
-            sage: -x.operator()
-            Morphism from Euclidean Jordan algebra of degree 3 over Rational
-            Field to Euclidean Jordan algebra of degree 3 over Rational Field
-            given by matrix
+            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]
-
-        TESTS::
-
-            sage: set_random_seed()
-            sage: J = random_eja()
-            sage: x = J.random_element()
-            sage: -x.operator() in J.Hom(J)
-            True
+            Domain: Euclidean Jordan algebra of degree 3 over Rational Field
+            Codomain: Euclidean Jordan algebra of degree 3 over Rational Field
 
         """
-        return FiniteDimensionalEuclideanJordanAlgebraMorphism(
-                  self.parent(),
-                  -self.matrix() )
+        return FiniteDimensionalEuclideanJordanAlgebraOperator(
+                self._domain_eja,
+                self._codomain_eja,
+                VectorSpaceMorphism.__neg__(self))
 
 
-    def _repr_(self):
+    def __pow__(self, n):
         """
-        We override only the representation that is shown to the user,
-        because we want the matrix to be with respect to COLUMN vectors.
+        Raise this EJA operator to the power ``n``.
 
         TESTS:
 
-        Ensure that we see the transpose of the underlying matrix object:
-
-            sage: J = RealSymmetricEJA(3)
-            sage: x = J.linear_combination(zip(range(len(J.gens())), J.gens()))
-            sage: L = x.operator()
-            sage: L
-            Morphism from Euclidean Jordan algebra of degree 6 over Rational
-            Field to Euclidean Jordan algebra of degree 6 over Rational Field
-            given by matrix
-            [  0   1   2   0   0   0]
-            [1/2 3/2   2 1/2   1   0]
-            [  1   2 5/2   0 1/2   1]
-            [  0   1   0   3   4   0]
-            [  0   1 1/2   2   4   2]
-            [  0   0   2   0   4   5]
-            sage: L._matrix
-            [  0 1/2   1   0   0   0]
-            [  1 3/2   2   1   1   0]
-            [  2   2 5/2   0 1/2   2]
-            [  0 1/2   0   3   2   0]
-            [  0   1 1/2   4   4   4]
-            [  0   0   1   0   2   5]
-
-        """
-        return "Morphism from {} to {} given by matrix\n{}".format(
-            self.domain(), self.codomain(), self.matrix())
-
-
-    def __sub__(self, other):
-        """
-        Subtract one morphism from another using addition and negation.
-
-        EXAMPLES::
+        Ensure that we get back another EJA operator that can be added,
+        subtracted, et cetera::
 
             sage: J = RealSymmetricEJA(2)
-            sage: L1 = J.one().operator()
-            sage: L1 - L1
-            Morphism from Euclidean Jordan algebra of degree 3 over Rational
-            Field to Euclidean Jordan algebra of degree 3 over Rational
-            Field given by matrix
-            [0 0 0]
-            [0 0 0]
-            [0 0 0]
-
-        TESTS::
-
-            sage: set_random_seed()
-            sage: J = random_eja()
-            sage: x = J.random_element()
-            sage: y = J.random_element()
-            sage: x.operator() - y.operator() in J.Hom(J)
-            True
+            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
 
         """
-        return self + (-other)
+        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 = VectorSpaceMorphism.__pow__(self,n)
 
+        return FiniteDimensionalEuclideanJordanAlgebraOperator(
+                 self._domain_eja,
+                 self._codomain_eja,
+                 mat)
 
-    def matrix(self):
+    def __sub__(self, other):
         """
-        Return the matrix of this morphism with respect to a left-action
-        on column vectors.
+        Subtract ``other`` from this EJA operator.
         """
-        return FiniteDimensionalAlgebraMorphism.matrix(self).transpose()
+        return (self + (-other))
 
 
 class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
@@ -445,15 +274,6 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
                                  natural_basis=natural_basis)
 
 
-    def _Hom_(self, B, cat):
-        """
-        Construct a homset of ``self`` and ``B``.
-        """
-        return FiniteDimensionalEuclideanJordanAlgebraHomset(self,
-                                                             B,
-                                                             category=cat)
-
-
     def __init__(self,
                  field,
                  mult_table,
@@ -1439,8 +1259,8 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
 
             """
             P = self.parent()
-            return FiniteDimensionalEuclideanJordanAlgebraMorphism(
-                     Hom(P,P),
+            return FiniteDimensionalEuclideanJordanAlgebraOperator(
+                     P,P,
                      self.operator_matrix() )
 
 
@@ -1506,6 +1326,15 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
                 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()
@@ -1557,10 +1386,10 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
                 sage: 2*Qxy == (x+y).quadratic_representation() - Qx - Qy
                 True
 
-            Property 2:
+            Property 2 (multiply on the right for :trac:`28272`):
 
                 sage: alpha = QQ.random_element()
-                sage: (alpha*x).quadratic_representation() == (alpha^2)*Qx
+                sage: (alpha*x).quadratic_representation() == Qx*(alpha^2)
                 True
 
             Property 3: