]> gitweb.michael.orlitzky.com - sage.d.git/blobdiff - mjo/eja/eja_algebra.py
eja: add projections for DirectSumEJA.
[sage.d.git] / mjo / eja / eja_algebra.py
index 26fe1929be872b393fe41926eeda801dbd0a9436..3f91ee3ae72c6281c9238804f146944d2c431f1e 100644 (file)
@@ -1007,81 +1007,6 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule):
     Element = FiniteDimensionalEuclideanJordanAlgebraElement
 
 
-class HadamardEJA(FiniteDimensionalEuclideanJordanAlgebra):
-    """
-    Return the Euclidean Jordan Algebra corresponding to the set
-    `R^n` under the Hadamard product.
-
-    Note: this is nothing more than the Cartesian product of ``n``
-    copies of the spin algebra. Once Cartesian product algebras
-    are implemented, this can go.
-
-    SETUP::
-
-        sage: from mjo.eja.eja_algebra import HadamardEJA
-
-    EXAMPLES:
-
-    This multiplication table can be verified by hand::
-
-        sage: J = HadamardEJA(3)
-        sage: e0,e1,e2 = J.gens()
-        sage: e0*e0
-        e0
-        sage: e0*e1
-        0
-        sage: e0*e2
-        0
-        sage: e1*e1
-        e1
-        sage: e1*e2
-        0
-        sage: e2*e2
-        e2
-
-    TESTS:
-
-    We can change the generator prefix::
-
-        sage: HadamardEJA(3, prefix='r').gens()
-        (r0, r1, r2)
-
-    """
-    def __init__(self, n, field=AA, **kwargs):
-        V = VectorSpace(field, n)
-        mult_table = [ [ V.gen(i)*(i == j) for j in range(n) ]
-                       for i in range(n) ]
-
-        super(HadamardEJA, self).__init__(field,
-                                          mult_table,
-                                          check_axioms=False,
-                                          **kwargs)
-        self.rank.set_cache(n)
-
-    def inner_product(self, x, y):
-        """
-        Faster to reimplement than to use natural representations.
-
-        SETUP::
-
-            sage: from mjo.eja.eja_algebra import HadamardEJA
-
-        TESTS:
-
-        Ensure that this is the usual inner product for the algebras
-        over `R^n`::
-
-            sage: set_random_seed()
-            sage: J = HadamardEJA.random_instance()
-            sage: x,y = J.random_elements(2)
-            sage: X = x.natural_representation()
-            sage: Y = y.natural_representation()
-            sage: x.inner_product(y) == J.natural_inner_product(X,Y)
-            True
-
-        """
-        return x.to_vector().inner_product(y.to_vector())
-
 
 def random_eja(field=AA):
     """
@@ -1108,6 +1033,65 @@ def random_eja(field=AA):
 
 
 
+class RationalBasisEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra):
+    r"""
+    Algebras whose basis consists of vectors with rational
+    entries. Equivalently, algebras whose multiplication tables
+    contain only rational coefficients.
+
+    When an EJA has a basis that can be made rational, we can speed up
+    the computation of its characteristic polynomial by doing it over
+    ``QQ``. All of the named EJA constructors that we provide fall
+    into this category.
+    """
+    @cached_method
+    def _charpoly_coefficients(self):
+        r"""
+        Override the parent method with something that tries to compute
+        over a faster (non-extension) field.
+
+        SETUP::
+
+            sage: from mjo.eja.eja_algebra import JordanSpinEJA
+
+        EXAMPLES:
+
+        The base ring of the resulting polynomial coefficients is what
+        it should be, and not the rationals (unless the algebra was
+        already over the rationals)::
+
+            sage: J = JordanSpinEJA(3)
+            sage: J._charpoly_coefficients()
+            (X1^2 - X2^2 - X3^2, -2*X1)
+            sage: a0 = J._charpoly_coefficients()[0]
+            sage: J.base_ring()
+            Algebraic Real Field
+            sage: a0.base_ring()
+            Algebraic Real Field
+
+        """
+        if self.base_ring() is QQ:
+            # There's no need to construct *another* algebra over the
+            # rationals if this one is already over the rationals.
+            superclass = super(RationalBasisEuclideanJordanAlgebra, self)
+            return superclass._charpoly_coefficients()
+
+        mult_table = tuple(
+            map(lambda x: x.to_vector(), ls)
+            for ls in self._multiplication_table
+        )
+
+        # Do the computation over the rationals. The answer will be
+        # the same, because our basis coordinates are (essentially)
+        # rational.
+        J = FiniteDimensionalEuclideanJordanAlgebra(QQ,
+                                                    mult_table,
+                                                    check_field=False,
+                                                    check_axioms=False)
+        a = J._charpoly_coefficients()
+        return tuple(map(lambda x: x.change_ring(self.base_ring()), a))
+
+
 class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra):
     @staticmethod
     def _max_test_case_size():
@@ -1156,44 +1140,44 @@ class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra):
         Override the parent method with something that tries to compute
         over a faster (non-extension) field.
         """
-        if self._basis_normalizers is None:
-            # We didn't normalize, so assume that the basis we started
-            # with had entries in a nice field.
+        if self._basis_normalizers is None or self.base_ring() is QQ:
+            # We didn't normalize, or the basis we started with had
+            # entries in a nice field already. Just compute the thing.
             return super(MatrixEuclideanJordanAlgebra, self)._charpoly_coefficients()
-        else:
-            basis = ( (b/n) for (b,n) in zip(self.natural_basis(),
-                                             self._basis_normalizers) )
-
-            # Do this over the rationals and convert back at the end.
-            # Only works because we know the entries of the basis are
-            # integers. The argument ``check_axioms=False`` is required
-            # because the trace inner-product method for this
-            # class is a stub and can't actually be checked.
-            J = MatrixEuclideanJordanAlgebra(QQ,
-                                             basis,
-                                             normalize_basis=False,
-                                             check_field=False,
-                                             check_axioms=False)
-            a = J._charpoly_coefficients()
-
-            # Unfortunately, changing the basis does change the
-            # coefficients of the characteristic polynomial, but since
-            # these are really the coefficients of the "characteristic
-            # polynomial of" function, everything is still nice and
-            # unevaluated. It's therefore "obvious" how scaling the
-            # basis affects the coordinate variables X1, X2, et
-            # cetera. Scaling the first basis vector up by "n" adds a
-            # factor of 1/n into every "X1" term, for example. So here
-            # we simply undo the basis_normalizer scaling that we
-            # performed earlier.
-            #
-            # The a[0] access here is safe because trivial algebras
-            # won't have any basis normalizers and therefore won't
-            # make it to this "else" branch.
-            XS = a[0].parent().gens()
-            subs_dict = { XS[i]: self._basis_normalizers[i]*XS[i]
-                          for i in range(len(XS)) }
-            return tuple( a_i.subs(subs_dict) for a_i in a )
+
+        basis = ( (b/n) for (b,n) in zip(self.natural_basis(),
+                                         self._basis_normalizers) )
+
+        # Do this over the rationals and convert back at the end.
+        # Only works because we know the entries of the basis are
+        # integers. The argument ``check_axioms=False`` is required
+        # because the trace inner-product method for this
+        # class is a stub and can't actually be checked.
+        J = MatrixEuclideanJordanAlgebra(QQ,
+                                         basis,
+                                         normalize_basis=False,
+                                         check_field=False,
+                                         check_axioms=False)
+        a = J._charpoly_coefficients()
+
+        # Unfortunately, changing the basis does change the
+        # coefficients of the characteristic polynomial, but since
+        # these are really the coefficients of the "characteristic
+        # polynomial of" function, everything is still nice and
+        # unevaluated. It's therefore "obvious" how scaling the
+        # basis affects the coordinate variables X1, X2, et
+        # cetera. Scaling the first basis vector up by "n" adds a
+        # factor of 1/n into every "X1" term, for example. So here
+        # we simply undo the basis_normalizer scaling that we
+        # performed earlier.
+        #
+        # The a[0] access here is safe because trivial algebras
+        # won't have any basis normalizers and therefore won't
+        # make it to this "else" branch.
+        XS = a[0].parent().gens()
+        subs_dict = { XS[i]: self._basis_normalizers[i]*XS[i]
+                      for i in range(len(XS)) }
+        return tuple( a_i.subs(subs_dict) for a_i in a )
 
 
     @staticmethod
@@ -2021,7 +2005,83 @@ class QuaternionHermitianEJA(QuaternionMatrixEuclideanJordanAlgebra):
         self.rank.set_cache(n)
 
 
-class BilinearFormEJA(FiniteDimensionalEuclideanJordanAlgebra):
+class HadamardEJA(RationalBasisEuclideanJordanAlgebra):
+    """
+    Return the Euclidean Jordan Algebra corresponding to the set
+    `R^n` under the Hadamard product.
+
+    Note: this is nothing more than the Cartesian product of ``n``
+    copies of the spin algebra. Once Cartesian product algebras
+    are implemented, this can go.
+
+    SETUP::
+
+        sage: from mjo.eja.eja_algebra import HadamardEJA
+
+    EXAMPLES:
+
+    This multiplication table can be verified by hand::
+
+        sage: J = HadamardEJA(3)
+        sage: e0,e1,e2 = J.gens()
+        sage: e0*e0
+        e0
+        sage: e0*e1
+        0
+        sage: e0*e2
+        0
+        sage: e1*e1
+        e1
+        sage: e1*e2
+        0
+        sage: e2*e2
+        e2
+
+    TESTS:
+
+    We can change the generator prefix::
+
+        sage: HadamardEJA(3, prefix='r').gens()
+        (r0, r1, r2)
+
+    """
+    def __init__(self, n, field=AA, **kwargs):
+        V = VectorSpace(field, n)
+        mult_table = [ [ V.gen(i)*(i == j) for j in range(n) ]
+                       for i in range(n) ]
+
+        super(HadamardEJA, self).__init__(field,
+                                          mult_table,
+                                          check_axioms=False,
+                                          **kwargs)
+        self.rank.set_cache(n)
+
+    def inner_product(self, x, y):
+        """
+        Faster to reimplement than to use natural representations.
+
+        SETUP::
+
+            sage: from mjo.eja.eja_algebra import HadamardEJA
+
+        TESTS:
+
+        Ensure that this is the usual inner product for the algebras
+        over `R^n`::
+
+            sage: set_random_seed()
+            sage: J = HadamardEJA.random_instance()
+            sage: x,y = J.random_elements(2)
+            sage: X = x.natural_representation()
+            sage: Y = y.natural_representation()
+            sage: x.inner_product(y) == J.natural_inner_product(X,Y)
+            True
+
+        """
+        return x.to_vector().inner_product(y.to_vector())
+
+
+class BilinearFormEJA(RationalBasisEuclideanJordanAlgebra):
     r"""
     The rank-2 simple EJA consisting of real vectors ``x=(x0, x_bar)``
     with the half-trace inner product and jordan product ``x*y =
@@ -2264,6 +2324,7 @@ class DirectSumEJA(FiniteDimensionalEuclideanJordanAlgebra):
 
     """
     def __init__(self, J1, J2, field=AA, **kwargs):
+        self._factors = (J1, J2)
         n1 = J1.dimension()
         n2 = J2.dimension()
         n = n1+n2
@@ -2285,3 +2346,56 @@ class DirectSumEJA(FiniteDimensionalEuclideanJordanAlgebra):
                                            check_axioms=False,
                                            **kwargs)
         self.rank.set_cache(J1.rank() + J2.rank())
+
+
+    def factors(self):
+        r"""
+        Return the pair of this algebra's factors.
+
+        SETUP::
+
+            sage: from mjo.eja.eja_algebra import (HadamardEJA,
+            ....:                                  JordanSpinEJA,
+            ....:                                  DirectSumEJA)
+
+        EXAMPLES::
+
+            sage: J1 = HadamardEJA(2,QQ)
+            sage: J2 = JordanSpinEJA(3,QQ)
+            sage: J = DirectSumEJA(J1,J2)
+            sage: J.factors()
+            (Euclidean Jordan algebra of dimension 2 over Rational Field,
+             Euclidean Jordan algebra of dimension 3 over Rational Field)
+
+        """
+        return self._factors
+
+    def projections(self):
+        r"""
+        Return a pair of projections onto this algebra's factors.
+
+        SETUP::
+
+            sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
+            ....:                                  ComplexHermitianEJA,
+            ....:                                  DirectSumEJA)
+
+        EXAMPLES::
+
+            sage: J1 = JordanSpinEJA(2)
+            sage: J2 = ComplexHermitianEJA(2)
+            sage: J = DirectSumEJA(J1,J2)
+            sage: (pi_left, pi_right) = J.projections()
+            sage: J.one().to_vector()
+            (1, 0, 1, 0, 0, 1)
+            sage: pi_left(J.one()).to_vector()
+            (1, 0)
+            sage: pi_right(J.one()).to_vector()
+            (1, 0, 0, 1)
+
+        """
+        (J1,J2) = self.factors()
+        n = J1.dimension()
+        pi_left  = lambda x: J1.from_vector(x.to_vector()[:n])
+        pi_right = lambda x: J2.from_vector(x.to_vector()[n:])
+        return (pi_left, pi_right)