]> gitweb.michael.orlitzky.com - sage.d.git/commitdiff
eja: eliminate remaining real-embeddings.
authorMichael Orlitzky <michael@orlitzky.com>
Tue, 9 Mar 2021 03:31:07 +0000 (22:31 -0500)
committerMichael Orlitzky <michael@orlitzky.com>
Tue, 9 Mar 2021 03:31:07 +0000 (22:31 -0500)
mjo/eja/eja_algebra.py

index 827d40214cac92cb6e1ee46eb17f4285a44398f6..3027075374a177ba5f72bbd690a2c07dad02d136 100644 (file)
@@ -1744,84 +1744,6 @@ class MatrixEJA:
         """
         return (X*Y).trace().real()
 
-class RealEmbeddedMatrixEJA(MatrixEJA):
-    @staticmethod
-    def dimension_over_reals():
-        r"""
-        The dimension of this matrix's base ring over the reals.
-
-        The reals are dimension one over themselves, obviously; that's
-        just `\mathbb{R}^{1}`. Likewise, the complex numbers `a + bi`
-        have dimension two. Finally, the quaternions have dimension
-        four over the reals.
-
-        This is used to determine the size of the matrix returned from
-        :meth:`real_embed`, among other things.
-        """
-        raise NotImplementedError
-
-    @classmethod
-    def real_embed(cls,M):
-        """
-        Embed the matrix ``M`` into a space of real matrices.
-
-        The matrix ``M`` can have entries in any field at the moment:
-        the real numbers, complex numbers, or quaternions. And although
-        they are not a field, we can probably support octonions at some
-        point, too. This function returns a real matrix that "acts like"
-        the original with respect to matrix multiplication; i.e.
-
-          real_embed(M*N) = real_embed(M)*real_embed(N)
-
-        """
-        if M.ncols() != M.nrows():
-            raise ValueError("the matrix 'M' must be square")
-        return M
-
-
-    @classmethod
-    def real_unembed(cls,M):
-        """
-        The inverse of :meth:`real_embed`.
-        """
-        if M.ncols() != M.nrows():
-            raise ValueError("the matrix 'M' must be square")
-        if not ZZ(M.nrows()).mod(cls.dimension_over_reals()).is_zero():
-            raise ValueError("the matrix 'M' must be a real embedding")
-        return M
-
-
-    @classmethod
-    def trace_inner_product(cls,X,Y):
-        r"""
-        Compute the trace inner-product of two real-embeddings.
-
-        SETUP::
-
-            sage: from mjo.eja.eja_algebra import ComplexHermitianEJA
-
-        EXAMPLES::
-
-            sage: set_random_seed()
-            sage: J = ComplexHermitianEJA.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().real()
-            sage: actual = J.trace_inner_product(Xe,Ye)
-            sage: actual == expected
-            True
-
-        """
-        # This does in fact compute the real part of the trace.
-        # If we compute the trace of e.g. a complex matrix M,
-        # then we do so by adding up its diagonal entries --
-        # call them z_1 through z_n. The real embedding of z_1
-        # will be a 2-by-2 REAL matrix [a, b; -b, a] whose trace
-        # as a REAL matrix will be 2*a = 2*Re(z_1). And so forth.
-        return (X*Y).trace()/cls.dimension_over_reals()
 
 class RealSymmetricEJA(RationalBasisEJA, ConcreteEJA, MatrixEJA):
     """
@@ -1957,155 +1879,7 @@ class RealSymmetricEJA(RationalBasisEJA, ConcreteEJA, MatrixEJA):
 
 
 
-class ComplexMatrixEJA(RealEmbeddedMatrixEJA):
-    # A manual dictionary-cache for the complex_extension() method,
-    # since apparently @classmethods can't also be @cached_methods.
-    _complex_extension = {}
-
-    @classmethod
-    def complex_extension(cls,field):
-        r"""
-        The complex field that we embed/unembed, as an extension
-        of the given ``field``.
-        """
-        if field in cls._complex_extension:
-            return cls._complex_extension[field]
-
-        # Sage doesn't know how to adjoin the complex "i" (the root of
-        # x^2 + 1) to a field in a general way. Here, we just enumerate
-        # all of the cases that I have cared to support so far.
-        if field is AA:
-            # Sage doesn't know how to embed AA into QQbar, i.e. how
-            # to adjoin sqrt(-1) to AA.
-            F = QQbar
-        elif not field.is_exact():
-            # RDF or RR
-            F = field.complex_field()
-        else:
-            # Works for QQ and... maybe some other fields.
-            R = PolynomialRing(field, 'z')
-            z = R.gen()
-            F = field.extension(z**2 + 1, 'I', embedding=CLF(-1).sqrt())
-
-        cls._complex_extension[field] = F
-        return F
-
-    @staticmethod
-    def dimension_over_reals():
-        return 2
-
-    @classmethod
-    def real_embed(cls,M):
-        """
-        Embed the n-by-n complex matrix ``M`` into the space of real
-        matrices of size 2n-by-2n via the map the sends each entry `z = a +
-        bi` to the block matrix ``[[a,b],[-b,a]]``.
-
-        SETUP::
-
-            sage: from mjo.eja.eja_algebra import ComplexMatrixEJA
-
-        EXAMPLES::
-
-            sage: F = QuadraticField(-1, 'I')
-            sage: x1 = F(4 - 2*i)
-            sage: x2 = F(1 + 2*i)
-            sage: x3 = F(-i)
-            sage: x4 = F(6)
-            sage: M = matrix(F,2,[[x1,x2],[x3,x4]])
-            sage: ComplexMatrixEJA.real_embed(M)
-            [ 4 -2| 1  2]
-            [ 2  4|-2  1]
-            [-----+-----]
-            [ 0 -1| 6  0]
-            [ 1  0| 0  6]
-
-        TESTS:
-
-        Embedding is a homomorphism (isomorphism, in fact)::
-
-            sage: set_random_seed()
-            sage: n = ZZ.random_element(3)
-            sage: F = QuadraticField(-1, 'I')
-            sage: X = random_matrix(F, n)
-            sage: Y = random_matrix(F, n)
-            sage: Xe = ComplexMatrixEJA.real_embed(X)
-            sage: Ye = ComplexMatrixEJA.real_embed(Y)
-            sage: XYe = ComplexMatrixEJA.real_embed(X*Y)
-            sage: Xe*Ye == XYe
-            True
-
-        """
-        super().real_embed(M)
-        n = M.nrows()
-
-        # We don't need any adjoined elements...
-        field = M.base_ring().base_ring()
-
-        blocks = []
-        for z in M.list():
-            a = z.real()
-            b = z.imag()
-            blocks.append(matrix(field, 2, [ [ a, b],
-                                             [-b, a] ]))
-
-        return matrix.block(field, n, blocks)
-
-
-    @classmethod
-    def real_unembed(cls,M):
-        """
-        The inverse of _embed_complex_matrix().
-
-        SETUP::
-
-            sage: from mjo.eja.eja_algebra import ComplexMatrixEJA
-
-        EXAMPLES::
-
-            sage: A = matrix(QQ,[ [ 1,  2,   3,  4],
-            ....:                 [-2,  1,  -4,  3],
-            ....:                 [ 9,  10, 11, 12],
-            ....:                 [-10, 9, -12, 11] ])
-            sage: ComplexMatrixEJA.real_unembed(A)
-            [  2*I + 1   4*I + 3]
-            [ 10*I + 9 12*I + 11]
-
-        TESTS:
-
-        Unembedding is the inverse of embedding::
-
-            sage: set_random_seed()
-            sage: F = QuadraticField(-1, 'I')
-            sage: M = random_matrix(F, 3)
-            sage: Me = ComplexMatrixEJA.real_embed(M)
-            sage: ComplexMatrixEJA.real_unembed(Me) == M
-            True
-
-        """
-        super().real_unembed(M)
-        n = ZZ(M.nrows())
-        d = cls.dimension_over_reals()
-        F = cls.complex_extension(M.base_ring())
-        i = F.gen()
-
-        # Go top-left to bottom-right (reading order), converting every
-        # 2-by-2 block we see to a single complex element.
-        elements = []
-        for k in range(n/d):
-            for j in range(n/d):
-                submat = M[d*k:d*k+d,d*j:d*j+d]
-                if submat[0,0] != submat[1,1]:
-                    raise ValueError('bad on-diagonal submatrix')
-                if submat[0,1] != -submat[1,0]:
-                    raise ValueError('bad off-diagonal submatrix')
-                z = submat[0,0] + submat[0,1]*i
-                elements.append(z)
-
-        return matrix(F, n/d, elements)
-
-
-class ComplexHermitianEJA(RationalBasisEJA, ConcreteEJA, ComplexMatrixEJA):
+class ComplexHermitianEJA(RationalBasisEJA, ConcreteEJA, MatrixEJA):
     """
     The rank-n simple EJA consisting of complex Hermitian n-by-n
     matrices over the real numbers, the usual symmetric Jordan product,
@@ -2182,48 +1956,51 @@ class ComplexHermitianEJA(RationalBasisEJA, ConcreteEJA, ComplexMatrixEJA):
 
             sage: set_random_seed()
             sage: n = ZZ.random_element(1,5)
-            sage: B = ComplexHermitianEJA._denormalized_basis(n,ZZ)
-            sage: all( M.is_symmetric() for M in  B)
+            sage: B = ComplexHermitianEJA._denormalized_basis(n,QQ)
+            sage: all( M.is_hermitian() for M in  B)
             True
 
         """
-        R = PolynomialRing(ZZ, 'z')
-        z = R.gen()
-        F = ZZ.extension(z**2 + 1, 'I')
-        I = F.gen(1)
+        from mjo.hurwitz import ComplexMatrixAlgebra
+        A = ComplexMatrixAlgebra(n, scalars=field)
+        es = A.entry_algebra_gens()
 
-        # This is like the symmetric case, but we need to be careful:
-        #
-        #   * We want conjugate-symmetry, not just symmetry.
-        #   * The diagonal will (as a result) be real.
-        #
-        S = []
-        Eij = matrix.zero(F,n)
+        basis = []
         for i in range(n):
             for j in range(i+1):
-                # "build" E_ij
-                Eij[i,j] = 1
                 if i == j:
-                    Sij = cls.real_embed(Eij)
-                    S.append(Sij)
+                    E_ii = A.monomial( (i,j,es[0]) )
+                    basis.append(E_ii)
                 else:
-                    # The second one has a minus because it's conjugated.
-                    Eij[j,i] = 1 # Eij = Eij + Eij.transpose()
-                    Sij_real = cls.real_embed(Eij)
-                    S.append(Sij_real)
-                    # Eij = I*Eij - I*Eij.transpose()
-                    Eij[i,j] = I
-                    Eij[j,i] = -I
-                    Sij_imag = cls.real_embed(Eij)
-                    S.append(Sij_imag)
-                    Eij[j,i] = 0
-                # "erase" E_ij
-                Eij[i,j] = 0
-
-        # 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 )
+                    for e in es:
+                        E_ij  = A.monomial( (i,j,e)             )
+                        ec = e.conjugate()
+                        # If the conjugate has a negative sign in front
+                        # of it, (j,i,ec) won't be a monomial!
+                        if (j,i,ec) in A.indices():
+                            E_ij += A.monomial( (j,i,ec) )
+                        else:
+                            E_ij -= A.monomial( (j,i,-ec) )
+                        basis.append(E_ij)
+
+        return tuple( basis )
+
+    @staticmethod
+    def trace_inner_product(X,Y):
+        r"""
+        SETUP::
+
+            sage: from mjo.eja.eja_algebra import ComplexHermitianEJA
 
+        TESTS::
+
+            sage: J = ComplexHermitianEJA(2,field=QQ,orthonormalize=False)
+            sage: I = J.one().to_matrix()
+            sage: J.trace_inner_product(I, -I)
+            -2
+
+        """
+        return (X*Y).trace().real()
 
     def __init__(self, n, field=AA, **kwargs):
         # We know this is a valid EJA, but will double-check
@@ -2244,7 +2021,7 @@ class ComplexHermitianEJA(RationalBasisEJA, ConcreteEJA, ComplexMatrixEJA):
         # 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))
 
     @staticmethod