From 9861c4860c4bb1158243c06544cb54d1d9e3b411 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Mon, 8 Mar 2021 22:31:07 -0500 Subject: [PATCH] eja: eliminate remaining real-embeddings. --- mjo/eja/eja_algebra.py | 299 ++++++----------------------------------- 1 file changed, 38 insertions(+), 261 deletions(-) diff --git a/mjo/eja/eja_algebra.py b/mjo/eja/eja_algebra.py index 827d402..3027075 100644 --- a/mjo/eja/eja_algebra.py +++ b/mjo/eja/eja_algebra.py @@ -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 -- 2.43.2