]> gitweb.michael.orlitzky.com - sage.d.git/blobdiff - mjo/eja/eja_algebra.py
eja: factor out a class for real-embedded matrices.
[sage.d.git] / mjo / eja / eja_algebra.py
index c55061e34fb2b7a4d04f127d74148984ce21f9fa..5bf597565b2ae292032c3f0a532763d7889cd2a8 100644 (file)
@@ -152,11 +152,6 @@ class FiniteDimensionalEJA(CombinatorialFreeModule):
                 # we've specified a real embedding.
                 raise ValueError("scalar field is not real")
 
-        from mjo.eja.eja_utils import _change_ring
-        # If the basis given to us wasn't over the field that it's
-        # supposed to be over, fix that. Or, you know, crash.
-        basis = tuple( _change_ring(b, field) for b in basis )
-
         if check_axioms:
             # Check commutativity of the Jordan and inner-products.
             # This has to be done before we build the multiplication
@@ -1730,6 +1725,21 @@ class ConcreteEJA(RationalBasisEJA):
 
 
 class MatrixEJA:
+    @staticmethod
+    def jordan_product(X,Y):
+        return (X*Y + Y*X)/2
+
+    @staticmethod
+    def trace_inner_product(X,Y):
+        r"""
+        A trace inner-product for matrices that aren't embedded in the
+        reals.
+        """
+        # We take the norm (absolute value) because Octonions() isn't
+        # smart enough yet to coerce its one() into the base field.
+        return (X*Y).trace().abs()
+
+class RealEmbeddedMatrixEJA(MatrixEJA):
     @staticmethod
     def dimension_over_reals():
         r"""
@@ -1775,9 +1785,6 @@ class MatrixEJA:
             raise ValueError("the matrix 'M' must be a real embedding")
         return M
 
-    @staticmethod
-    def jordan_product(X,Y):
-        return (X*Y + Y*X)/2
 
     @classmethod
     def trace_inner_product(cls,X,Y):
@@ -1786,29 +1793,11 @@ class MatrixEJA:
 
         SETUP::
 
-            sage: from mjo.eja.eja_algebra import (RealSymmetricEJA,
-            ....:                                  ComplexHermitianEJA,
+            sage: from mjo.eja.eja_algebra import (ComplexHermitianEJA,
             ....:                                  QuaternionHermitianEJA)
 
         EXAMPLES::
 
-        This gives the same answer as it would if we computed the trace
-        from the unembedded (original) matrices::
-
-            sage: set_random_seed()
-            sage: J = RealSymmetricEJA.random_instance()
-            sage: x,y = J.random_elements(2)
-            sage: Xe = x.to_matrix()
-            sage: Ye = y.to_matrix()
-            sage: X = J.real_unembed(Xe)
-            sage: Y = J.real_unembed(Ye)
-            sage: expected = (X*Y).trace()
-            sage: actual = J.trace_inner_product(Xe,Ye)
-            sage: actual == expected
-            True
-
-        ::
-
             sage: set_random_seed()
             sage: J = ComplexHermitianEJA.random_instance()
             sage: x,y = J.random_elements(2)
@@ -1844,14 +1833,7 @@ class MatrixEJA:
         # as a REAL matrix will be 2*a = 2*Re(z_1). And so forth.
         return (X*Y).trace()/cls.dimension_over_reals()
 
-
-class RealMatrixEJA(MatrixEJA):
-    @staticmethod
-    def dimension_over_reals():
-        return 1
-
-
-class RealSymmetricEJA(ConcreteEJA, RealMatrixEJA):
+class RealSymmetricEJA(ConcreteEJA, MatrixEJA):
     """
     The rank-n simple EJA consisting of real symmetric n-by-n
     matrices, the usual symmetric Jordan product, and the trace inner
@@ -1917,7 +1899,7 @@ class RealSymmetricEJA(ConcreteEJA, RealMatrixEJA):
 
     """
     @classmethod
-    def _denormalized_basis(cls, n):
+    def _denormalized_basis(cls, n, field):
         """
         Return a basis for the space of real symmetric n-by-n matrices.
 
@@ -1929,7 +1911,7 @@ class RealSymmetricEJA(ConcreteEJA, RealMatrixEJA):
 
             sage: set_random_seed()
             sage: n = ZZ.random_element(1,5)
-            sage: B = RealSymmetricEJA._denormalized_basis(n)
+            sage: B = RealSymmetricEJA._denormalized_basis(n,ZZ)
             sage: all( M.is_symmetric() for M in  B)
             True
 
@@ -1939,7 +1921,7 @@ class RealSymmetricEJA(ConcreteEJA, RealMatrixEJA):
         S = []
         for i in range(n):
             for j in range(i+1):
-                Eij = matrix(ZZ, n, lambda k,l: k==i and l==j)
+                Eij = matrix(field, n, lambda k,l: k==i and l==j)
                 if i == j:
                     Sij = Eij
                 else:
@@ -1960,7 +1942,7 @@ class RealSymmetricEJA(ConcreteEJA, RealMatrixEJA):
         n = ZZ.random_element(cls._max_random_instance_size() + 1)
         return cls(n, **kwargs)
 
-    def __init__(self, n, **kwargs):
+    def __init__(self, n, field=AA, **kwargs):
         # We know this is a valid EJA, but will double-check
         # if the user passes check_axioms=True.
         if "check_axioms" not in kwargs: kwargs["check_axioms"] = False
@@ -1969,9 +1951,10 @@ class RealSymmetricEJA(ConcreteEJA, RealMatrixEJA):
         if n <= 1:
             associative = True
 
-        super().__init__(self._denormalized_basis(n),
+        super().__init__(self._denormalized_basis(n,field),
                          self.jordan_product,
                          self.trace_inner_product,
+                         field=field,
                          associative=associative,
                          **kwargs)
 
@@ -1979,12 +1962,12 @@ class RealSymmetricEJA(ConcreteEJA, RealMatrixEJA):
         # because the MatrixEJA is not presently a subclass of the
         # FDEJA class that defines rank() and one().
         self.rank.set_cache(n)
-        idV = matrix.identity(ZZ, self.dimension_over_reals()*n)
+        idV = self.matrix_space().one()
         self.one.set_cache(self(idV))
 
 
 
-class ComplexMatrixEJA(MatrixEJA):
+class ComplexMatrixEJA(RealEmbeddedMatrixEJA):
     # A manual dictionary-cache for the complex_extension() method,
     # since apparently @classmethods can't also be @cached_methods.
     _complex_extension = {}
@@ -2191,7 +2174,7 @@ class ComplexHermitianEJA(ConcreteEJA, ComplexMatrixEJA):
     """
 
     @classmethod
-    def _denormalized_basis(cls, n):
+    def _denormalized_basis(cls, n, field):
         """
         Returns a basis for the space of complex Hermitian n-by-n matrices.
 
@@ -2209,15 +2192,14 @@ class ComplexHermitianEJA(ConcreteEJA, ComplexMatrixEJA):
 
             sage: set_random_seed()
             sage: n = ZZ.random_element(1,5)
-            sage: B = ComplexHermitianEJA._denormalized_basis(n)
+            sage: B = ComplexHermitianEJA._denormalized_basis(n,ZZ)
             sage: all( M.is_symmetric() for M in  B)
             True
 
         """
-        field = ZZ
-        R = PolynomialRing(field, 'z')
+        R = PolynomialRing(ZZ, 'z')
         z = R.gen()
-        F = field.extension(z**2 + 1, 'I')
+        F = ZZ.extension(z**2 + 1, 'I')
         I = F.gen(1)
 
         # This is like the symmetric case, but we need to be careful:
@@ -2248,12 +2230,12 @@ class ComplexHermitianEJA(ConcreteEJA, ComplexMatrixEJA):
                 # "erase" E_ij
                 Eij[i,j] = 0
 
-        # Since we embedded these, we can drop back to the "field" that we
-        # started with instead of the complex extension "F".
+        # Since we embedded the entries, we can drop back to the
+        # desired real "field" instead of the extension "F".
         return tuple( s.change_ring(field) for s in S )
 
 
-    def __init__(self, n, **kwargs):
+    def __init__(self, n, field=AA, **kwargs):
         # We know this is a valid EJA, but will double-check
         # if the user passes check_axioms=True.
         if "check_axioms" not in kwargs: kwargs["check_axioms"] = False
@@ -2262,9 +2244,10 @@ class ComplexHermitianEJA(ConcreteEJA, ComplexMatrixEJA):
         if n <= 1:
             associative = True
 
-        super().__init__(self._denormalized_basis(n),
+        super().__init__(self._denormalized_basis(n,field),
                          self.jordan_product,
                          self.trace_inner_product,
+                         field=field,
                          associative=associative,
                          **kwargs)
         # TODO: this could be factored out somehow, but is left here
@@ -2286,7 +2269,7 @@ class ComplexHermitianEJA(ConcreteEJA, ComplexMatrixEJA):
         n = ZZ.random_element(cls._max_random_instance_size() + 1)
         return cls(n, **kwargs)
 
-class QuaternionMatrixEJA(MatrixEJA):
+class QuaternionMatrixEJA(RealEmbeddedMatrixEJA):
 
     # A manual dictionary-cache for the quaternion_extension() method,
     # since apparently @classmethods can't also be @cached_methods.
@@ -2492,7 +2475,7 @@ class QuaternionHermitianEJA(ConcreteEJA, QuaternionMatrixEJA):
 
     """
     @classmethod
-    def _denormalized_basis(cls, n):
+    def _denormalized_basis(cls, n, field):
         """
         Returns a basis for the space of quaternion Hermitian n-by-n matrices.
 
@@ -2510,12 +2493,11 @@ class QuaternionHermitianEJA(ConcreteEJA, QuaternionMatrixEJA):
 
             sage: set_random_seed()
             sage: n = ZZ.random_element(1,5)
-            sage: B = QuaternionHermitianEJA._denormalized_basis(n)
+            sage: B = QuaternionHermitianEJA._denormalized_basis(n,ZZ)
             sage: all( M.is_symmetric() for M in B )
             True
 
         """
-        field = ZZ
         Q = QuaternionAlgebra(QQ,-1,-1)
         I,J,K = Q.gens()
 
@@ -2559,12 +2541,12 @@ class QuaternionHermitianEJA(ConcreteEJA, QuaternionMatrixEJA):
                 # "erase" E_ij
                 Eij[i,j] = 0
 
-        # Since we embedded these, we can drop back to the "field" that we
-        # started with instead of the quaternion algebra "Q".
+        # Since we embedded the entries, we can drop back to the
+        # desired real "field" instead of the quaternion algebra "Q".
         return tuple( s.change_ring(field) for s in S )
 
 
-    def __init__(self, n, **kwargs):
+    def __init__(self, n, field=AA, **kwargs):
         # We know this is a valid EJA, but will double-check
         # if the user passes check_axioms=True.
         if "check_axioms" not in kwargs: kwargs["check_axioms"] = False
@@ -2573,9 +2555,10 @@ class QuaternionHermitianEJA(ConcreteEJA, QuaternionMatrixEJA):
         if n <= 1:
             associative = True
 
-        super().__init__(self._denormalized_basis(n),
+        super().__init__(self._denormalized_basis(n,field),
                          self.jordan_product,
                          self.trace_inner_product,
+                         field=field,
                          associative=associative,
                          **kwargs)
 
@@ -2643,7 +2626,7 @@ class HadamardEJA(ConcreteEJA):
         (r0, r1, r2)
 
     """
-    def __init__(self, n, **kwargs):
+    def __init__(self, n, field=AA, **kwargs):
         if n == 0:
             jordan_product = lambda x,y: x
             inner_product = lambda x,y: x
@@ -2664,10 +2647,12 @@ class HadamardEJA(ConcreteEJA):
         if "orthonormalize" not in kwargs: kwargs["orthonormalize"] = False
         if "check_axioms" not in kwargs: kwargs["check_axioms"] = False
 
-        column_basis = tuple( b.column() for b in FreeModule(ZZ, n).basis() )
+        column_basis = tuple( b.column()
+                              for b in FreeModule(field, n).basis() )
         super().__init__(column_basis,
                          jordan_product,
                          inner_product,
+                         field=field,
                          associative=True,
                          **kwargs)
         self.rank.set_cache(n)
@@ -2775,7 +2760,7 @@ class BilinearFormEJA(ConcreteEJA):
         True
 
     """
-    def __init__(self, B, **kwargs):
+    def __init__(self, B, field=AA, **kwargs):
         # The matrix "B" is supplied by the user in most cases,
         # so it makes sense to check whether or not its positive-
         # definite unless we are specifically asked not to...
@@ -2803,7 +2788,8 @@ class BilinearFormEJA(ConcreteEJA):
             return P([z0] + zbar.list())
 
         n = B.nrows()
-        column_basis = tuple( b.column() for b in FreeModule(ZZ, n).basis() )
+        column_basis = tuple( b.column()
+                              for b in FreeModule(field, n).basis() )
 
         # TODO: I haven't actually checked this, but it seems legit.
         associative = False
@@ -2813,6 +2799,7 @@ class BilinearFormEJA(ConcreteEJA):
         super().__init__(column_basis,
                          jordan_product,
                          inner_product,
+                         field=field,
                          associative=associative,
                          **kwargs)
 
@@ -2908,7 +2895,7 @@ class JordanSpinEJA(BilinearFormEJA):
             True
 
     """
-    def __init__(self, n, **kwargs):
+    def __init__(self, n, *args, **kwargs):
         # This is a special case of the BilinearFormEJA with the
         # identity matrix as its bilinear form.
         B = matrix.identity(ZZ, n)
@@ -2919,7 +2906,7 @@ class JordanSpinEJA(BilinearFormEJA):
 
         # But also don't pass check_field=False here, because the user
         # can pass in a field!
-        super().__init__(B, **kwargs)
+        super().__init__(B, *args, **kwargs)
 
     @staticmethod
     def _max_random_instance_size():