]> gitweb.michael.orlitzky.com - sage.d.git/blobdiff - mjo/eja/eja_algebra.py
eja: update todo, and rename "natural" to "matrix".
[sage.d.git] / mjo / eja / eja_algebra.py
index c569098b3d3e5d6fbceef6352e22861dd125a7f8..9d53bc5fdfd8fca177e43e49a187387fbe5a6085 100644 (file)
@@ -24,15 +24,12 @@ from sage.combinat.free_module import CombinatorialFreeModule
 from sage.matrix.constructor import matrix
 from sage.matrix.matrix_space import MatrixSpace
 from sage.misc.cachefunc import cached_method
-from sage.misc.lazy_import import lazy_import
 from sage.misc.table import table
 from sage.modules.free_module import FreeModule, VectorSpace
 from sage.rings.all import (ZZ, QQ, AA, QQbar, RR, RLF, CLF,
                             PolynomialRing,
                             QuadraticField)
 from mjo.eja.eja_element import FiniteDimensionalEuclideanJordanAlgebraElement
-lazy_import('mjo.eja.eja_subalgebra',
-            'FiniteDimensionalEuclideanJordanSubalgebra')
 from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
 from mjo.eja.eja_utils import _mat2vec
 
@@ -57,7 +54,7 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule):
             sage: J(1)
             Traceback (most recent call last):
             ...
-            ValueError: not a naturally-represented algebra element
+            ValueError: not an element of this algebra
 
         """
         return None
@@ -67,7 +64,7 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule):
                  mult_table,
                  prefix='e',
                  category=None,
-                 natural_basis=None,
+                 matrix_basis=None,
                  check_field=True,
                  check_axioms=True):
         """
@@ -118,7 +115,7 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule):
             if not all( len(l) == n for l in mult_table ):
                 raise ValueError("multiplication table is not square")
 
-        self._natural_basis = natural_basis
+        self._matrix_basis = matrix_basis
 
         if category is None:
             category = MagmaticAlgebras(field).FiniteDimensional()
@@ -152,7 +149,7 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule):
 
     def _element_constructor_(self, elt):
         """
-        Construct an element of this algebra from its natural
+        Construct an element of this algebra from its vector or matrix
         representation.
 
         This gets called only after the parent element _call_ method
@@ -180,13 +177,13 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule):
             sage: J(A)
             Traceback (most recent call last):
             ...
-            ArithmeticError: vector is not in free module
+            ValueError: not an element of this algebra
 
         TESTS:
 
         Ensure that we can convert any element of the two non-matrix
-        simple algebras (whose natural representations are their usual
-        vector representations) back and forth faithfully::
+        simple algebras (whose matrix representations are columns)
+        back and forth faithfully::
 
             sage: set_random_seed()
             sage: J = HadamardEJA.random_instance()
@@ -197,9 +194,8 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule):
             sage: x = J.random_element()
             sage: J(x.to_vector().column()) == x
             True
-
         """
-        msg = "not a naturally-represented algebra element"
+        msg = "not an element of this algebra"
         if elt == 0:
             # The superclass implementation of random_element()
             # needs to be able to coerce "0" into the algebra.
@@ -211,20 +207,23 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule):
             # that the integer 3 belongs to the space of 2-by-2 matrices.
             raise ValueError(msg)
 
-        natural_basis = self.natural_basis()
-        basis_space = natural_basis[0].matrix_space()
-        if elt not in basis_space:
+        if elt not in self.matrix_space():
             raise ValueError(msg)
 
         # Thanks for nothing! Matrix spaces aren't vector spaces in
-        # Sage, so we have to figure out its natural-basis coordinates
+        # Sage, so we have to figure out its matrix-basis coordinates
         # ourselves. We use the basis space's ring instead of the
         # element's ring because the basis space might be an algebraic
         # closure whereas the base ring of the 3-by-3 identity matrix
         # could be QQ instead of QQbar.
-        V = VectorSpace(basis_space.base_ring(), elt.nrows()*elt.ncols())
-        W = V.span_of_basis( _mat2vec(s) for s in natural_basis )
-        coords =  W.coordinate_vector(_mat2vec(elt))
+        V = VectorSpace(self.base_ring(), elt.nrows()*elt.ncols())
+        W = V.span_of_basis( _mat2vec(s) for s in self.matrix_basis() )
+
+        try:
+            coords =  W.coordinate_vector(_mat2vec(elt))
+        except ArithmeticError:  # vector is not in free module
+            raise ValueError(msg)
+
         return self.from_vector(coords)
 
     def _repr_(self):
@@ -374,6 +373,28 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule):
 
         return (t**r + sum( a[k]*(t**k) for k in range(r) ))
 
+    def coordinate_polynomial_ring(self):
+        r"""
+        The multivariate polynomial ring in which this algebra's
+        :meth:`characteristic_polynomial_of` lives.
+
+        SETUP::
+
+            sage: from mjo.eja.eja_algebra import (HadamardEJA,
+            ....:                                  RealSymmetricEJA)
+
+        EXAMPLES::
+
+            sage: J = HadamardEJA(2)
+            sage: J.coordinate_polynomial_ring()
+            Multivariate Polynomial Ring in X1, X2...
+            sage: J = RealSymmetricEJA(3,QQ)
+            sage: J.coordinate_polynomial_ring()
+            Multivariate Polynomial Ring in X1, X2, X3, X4, X5, X6...
+
+        """
+        var_names = tuple( "X%d" % z for z in range(1, self.dimension()+1) )
+        return PolynomialRing(self.base_ring(), var_names)
 
     def inner_product(self, x, y):
         """
@@ -491,18 +512,33 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule):
         return table(M, header_row=True, header_column=True, frame=True)
 
 
-    def natural_basis(self):
+    def matrix_basis(self):
         """
-        Return a more-natural representation of this algebra's basis.
+        Return an (often more natural) representation of this algebras
+        basis as an ordered tuple of matrices.
 
-        Every finite-dimensional Euclidean Jordan Algebra is a direct
-        sum of five simple algebras, four of which comprise Hermitian
-        matrices. This method returns the original "natural" basis
-        for our underlying vector space. (Typically, the natural basis
-        is used to construct the multiplication table in the first place.)
+        Every finite-dimensional Euclidean Jordan Algebra is a, up to
+        Jordan isomorphism, a direct sum of five simple
+        algebras---four of which comprise Hermitian matrices. And the
+        last type of algebra can of course be thought of as `n`-by-`1`
+        column matrices (ambiguusly called column vectors) to avoid
+        special cases. As a result, matrices (and column vectors) are
+        a natural representation format for Euclidean Jordan algebra
+        elements.
 
-        Note that this will always return a matrix. The standard basis
-        in `R^n` will be returned as `n`-by-`1` column matrices.
+        But, when we construct an algebra from a basis of matrices,
+        those matrix representations are lost in favor of coordinate
+        vectors *with respect to* that basis. We could eventually
+        convert back if we tried hard enough, but having the original
+        representations handy is valuable enough that we simply store
+        them and return them from this method.
+
+        Why implement this for non-matrix algebras? Avoiding special
+        cases for the :class:`BilinearFormEJA` pays with simplicity in
+        its own right. But mainly, we would like to be able to assume
+        that elements of a :class:`DirectSumEJA` can be displayed
+        nicely, without having to have special classes for direct sums
+        one of whose components was a matrix algebra.
 
         SETUP::
 
@@ -514,7 +550,7 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule):
             sage: J = RealSymmetricEJA(2)
             sage: J.basis()
             Finite family {0: e0, 1: e1, 2: e2}
-            sage: J.natural_basis()
+            sage: J.matrix_basis()
             (
             [1 0]  [                  0 0.7071067811865475?]  [0 0]
             [0 0], [0.7071067811865475?                   0], [0 1]
@@ -525,36 +561,38 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule):
             sage: J = JordanSpinEJA(2)
             sage: J.basis()
             Finite family {0: e0, 1: e1}
-            sage: J.natural_basis()
+            sage: J.matrix_basis()
             (
             [1]  [0]
             [0], [1]
             )
-
         """
-        if self._natural_basis is None:
-            M = self.natural_basis_space()
+        if self._matrix_basis is None:
+            M = self.matrix_space()
             return tuple( M(b.to_vector()) for b in self.basis() )
         else:
-            return self._natural_basis
+            return self._matrix_basis
 
 
-    def natural_basis_space(self):
+    def matrix_space(self):
         """
-        Return the matrix space in which this algebra's natural basis
-        elements live.
+        Return the matrix space in which this algebra's elements live, if
+        we think of them as matrices (including column vectors of the
+        appropriate size).
 
         Generally this will be an `n`-by-`1` column-vector space,
         except when the algebra is trivial. There it's `n`-by-`n`
-        (where `n` is zero), to ensure that two elements of the
-        natural basis space (empty matrices) can be multiplied.
+        (where `n` is zero), to ensure that two elements of the matrix
+        space (empty matrices) can be multiplied.
+
+        Matrix algebras override this with something more useful.
         """
         if self.is_trivial():
             return MatrixSpace(self.base_ring(), 0)
-        elif self._natural_basis is None or len(self._natural_basis) == 0:
+        elif self._matrix_basis is None or len(self._matrix_basis) == 0:
             return MatrixSpace(self.base_ring(), self.dimension(), 1)
         else:
-            return self._natural_basis[0].matrix_space()
+            return self._matrix_basis[0].matrix_space()
 
 
     @cached_method
@@ -681,22 +719,22 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule):
             Vector space of degree 6 and dimension 2...
             sage: J1
             Euclidean Jordan algebra of dimension 3...
-            sage: J0.one().natural_representation()
+            sage: J0.one().to_matrix()
             [0 0 0]
             [0 0 0]
             [0 0 1]
             sage: orig_df = AA.options.display_format
             sage: AA.options.display_format = 'radical'
-            sage: J.from_vector(J5.basis()[0]).natural_representation()
+            sage: J.from_vector(J5.basis()[0]).to_matrix()
             [          0           0 1/2*sqrt(2)]
             [          0           0           0]
             [1/2*sqrt(2)           0           0]
-            sage: J.from_vector(J5.basis()[1]).natural_representation()
+            sage: J.from_vector(J5.basis()[1]).to_matrix()
             [          0           0           0]
             [          0           0 1/2*sqrt(2)]
             [          0 1/2*sqrt(2)           0]
             sage: AA.options.display_format = orig_df
-            sage: J1.one().natural_representation()
+            sage: J1.one().to_matrix()
             [1 0 0]
             [0 1 0]
             [0 0 0]
@@ -746,6 +784,8 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule):
         if not c.is_idempotent():
             raise ValueError("element is not idempotent: %s" % c)
 
+        from mjo.eja.eja_subalgebra import FiniteDimensionalEuclideanJordanSubalgebra
+
         # Default these to what they should be if they turn out to be
         # trivial, because eigenspaces_left() won't return eigenvalues
         # corresponding to trivial spaces (e.g. it returns only the
@@ -854,8 +894,7 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule):
         of" function.
         """
         n = self.dimension()
-        var_names = [ "X" + str(z) for z in range(1,n+1) ]
-        R = PolynomialRing(self.base_ring(), var_names)
+        R = self.coordinate_polynomial_ring()
         vars = R.gens()
         F = R.fraction_field()
 
@@ -1081,26 +1120,25 @@ class ConcreteEuclideanJordanAlgebra:
 
     TESTS:
 
-    Our natural basis is normalized with respect to the natural inner
-    product unless we specify otherwise::
+    Our basis is normalized with respect to the algebra's inner
+    product, unless we specify otherwise::
 
         sage: set_random_seed()
         sage: J = ConcreteEuclideanJordanAlgebra.random_instance()
         sage: all( b.norm() == 1 for b in J.gens() )
         True
 
-    Since our natural basis is normalized with respect to the natural
-    inner product, and since we know that this algebra is an EJA, any
+    Since our basis is orthonormal with respect to the algebra's inner
+    product, and since we know that this algebra is an EJA, any
     left-multiplication operator's matrix will be symmetric because
-    natural->EJA basis representation is an isometry and within the EJA
-    the operator is self-adjoint by the Jordan axiom::
+    natural->EJA basis representation is an isometry and within the
+    EJA the operator is self-adjoint by the Jordan axiom::
 
         sage: set_random_seed()
         sage: J = ConcreteEuclideanJordanAlgebra.random_instance()
         sage: x = J.random_element()
-        sage: x.operator().matrix().is_symmetric()
+        sage: x.operator().is_self_adjoint()
         True
-
     """
 
     @staticmethod
@@ -1162,7 +1200,7 @@ class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra):
                 field = field.extension(p, 'sqrt2', embedding=RLF(2).sqrt())
                 basis = tuple( s.change_ring(field) for s in basis )
             self._basis_normalizers = tuple(
-                ~(self.natural_inner_product(s,s).sqrt()) for s in basis )
+                ~(self.matrix_inner_product(s,s).sqrt()) for s in basis )
             basis = tuple(s*c for (s,c) in zip(basis,self._basis_normalizers))
 
         # Now compute the multiplication and inner product tables.
@@ -1183,7 +1221,7 @@ class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra):
                     # HACK: ignore the error here if we don't need the
                     # inner product (as is the case when we construct
                     # a dummy QQ-algebra for fast charpoly coefficients.
-                    ip_table[i][j] = self.natural_inner_product(basis[i],
+                    ip_table[i][j] = self.matrix_inner_product(basis[i],
                                                                 basis[j])
                 except:
                     pass
@@ -1196,7 +1234,7 @@ class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra):
 
         super(MatrixEuclideanJordanAlgebra, self).__init__(field,
                                                            mult_table,
-                                                           natural_basis=basis,
+                                                           matrix_basis=basis,
                                                            **kwargs)
 
         if algebra_dim == 0:
@@ -1219,7 +1257,7 @@ class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra):
             # entries in a nice field already. Just compute the thing.
             return super(MatrixEuclideanJordanAlgebra, self)._charpoly_coefficients()
 
-        basis = ( (b/n) for (b,n) in zip(self.natural_basis(),
+        basis = ( (b/n) for (b,n) in zip(self.matrix_basis(),
                                          self._basis_normalizers) )
 
         # Do this over the rationals and convert back at the end.
@@ -1279,7 +1317,7 @@ class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra):
         raise NotImplementedError
 
     @classmethod
-    def natural_inner_product(cls,X,Y):
+    def matrix_inner_product(cls,X,Y):
         Xu = cls.real_unembed(X)
         Yu = cls.real_unembed(Y)
         tr = (Xu*Yu).trace()
@@ -1358,9 +1396,9 @@ class RealSymmetricEJA(RealMatrixEuclideanJordanAlgebra,
         sage: set_random_seed()
         sage: J = RealSymmetricEJA.random_instance()
         sage: x,y = J.random_elements(2)
-        sage: actual = (x*y).natural_representation()
-        sage: X = x.natural_representation()
-        sage: Y = y.natural_representation()
+        sage: actual = (x*y).to_matrix()
+        sage: X = x.to_matrix()
+        sage: Y = y.to_matrix()
         sage: expected = (X*Y + Y*X)/2
         sage: actual == expected
         True
@@ -1559,9 +1597,9 @@ class ComplexMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra):
 
 
     @classmethod
-    def natural_inner_product(cls,X,Y):
+    def matrix_inner_product(cls,X,Y):
         """
-        Compute a natural inner product in this algebra directly from
+        Compute a matrix inner product in this algebra directly from
         its real embedding.
 
         SETUP::
@@ -1576,17 +1614,17 @@ class ComplexMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra):
             sage: set_random_seed()
             sage: J = ComplexHermitianEJA.random_instance()
             sage: x,y = J.random_elements(2)
-            sage: Xe = x.natural_representation()
-            sage: Ye = y.natural_representation()
+            sage: Xe = x.to_matrix()
+            sage: Ye = y.to_matrix()
             sage: X = ComplexHermitianEJA.real_unembed(Xe)
             sage: Y = ComplexHermitianEJA.real_unembed(Ye)
             sage: expected = (X*Y).trace().real()
-            sage: actual = ComplexHermitianEJA.natural_inner_product(Xe,Ye)
+            sage: actual = ComplexHermitianEJA.matrix_inner_product(Xe,Ye)
             sage: actual == expected
             True
 
         """
-        return RealMatrixEuclideanJordanAlgebra.natural_inner_product(X,Y)/2
+        return RealMatrixEuclideanJordanAlgebra.matrix_inner_product(X,Y)/2
 
 
 class ComplexHermitianEJA(ComplexMatrixEuclideanJordanAlgebra,
@@ -1627,9 +1665,9 @@ class ComplexHermitianEJA(ComplexMatrixEuclideanJordanAlgebra,
         sage: set_random_seed()
         sage: J = ComplexHermitianEJA.random_instance()
         sage: x,y = J.random_elements(2)
-        sage: actual = (x*y).natural_representation()
-        sage: X = x.natural_representation()
-        sage: Y = y.natural_representation()
+        sage: actual = (x*y).to_matrix()
+        sage: X = x.to_matrix()
+        sage: Y = y.to_matrix()
         sage: expected = (X*Y + Y*X)/2
         sage: actual == expected
         True
@@ -1854,9 +1892,9 @@ class QuaternionMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra):
 
 
     @classmethod
-    def natural_inner_product(cls,X,Y):
+    def matrix_inner_product(cls,X,Y):
         """
-        Compute a natural inner product in this algebra directly from
+        Compute a matrix inner product in this algebra directly from
         its real embedding.
 
         SETUP::
@@ -1871,17 +1909,17 @@ class QuaternionMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra):
             sage: set_random_seed()
             sage: J = QuaternionHermitianEJA.random_instance()
             sage: x,y = J.random_elements(2)
-            sage: Xe = x.natural_representation()
-            sage: Ye = y.natural_representation()
+            sage: Xe = x.to_matrix()
+            sage: Ye = y.to_matrix()
             sage: X = QuaternionHermitianEJA.real_unembed(Xe)
             sage: Y = QuaternionHermitianEJA.real_unembed(Ye)
             sage: expected = (X*Y).trace().coefficient_tuple()[0]
-            sage: actual = QuaternionHermitianEJA.natural_inner_product(Xe,Ye)
+            sage: actual = QuaternionHermitianEJA.matrix_inner_product(Xe,Ye)
             sage: actual == expected
             True
 
         """
-        return RealMatrixEuclideanJordanAlgebra.natural_inner_product(X,Y)/4
+        return RealMatrixEuclideanJordanAlgebra.matrix_inner_product(X,Y)/4
 
 
 class QuaternionHermitianEJA(QuaternionMatrixEuclideanJordanAlgebra,
@@ -1922,9 +1960,9 @@ class QuaternionHermitianEJA(QuaternionMatrixEuclideanJordanAlgebra,
         sage: set_random_seed()
         sage: J = QuaternionHermitianEJA.random_instance()
         sage: x,y = J.random_elements(2)
-        sage: actual = (x*y).natural_representation()
-        sage: X = x.natural_representation()
-        sage: Y = y.natural_representation()
+        sage: actual = (x*y).to_matrix()
+        sage: X = x.to_matrix()
+        sage: Y = y.to_matrix()
         sage: expected = (X*Y + Y*X)/2
         sage: actual == expected
         True