X-Git-Url: http://gitweb.michael.orlitzky.com/?a=blobdiff_plain;f=mjo%2Feja%2Feja_algebra.py;h=7ab32e25219e5f35d1d99386f2c69a67ffb034e5;hb=1bef02ef2a4f20d65849d2f2ec9603620f53daef;hp=1ccbf2e302e7cd43ae1877e3fda6fdcaa3f5e5de;hpb=928b7d49fda98ff105c92293b5797bb7a2b9873a;p=sage.d.git diff --git a/mjo/eja/eja_algebra.py b/mjo/eja/eja_algebra.py index 1ccbf2e..7ab32e2 100644 --- a/mjo/eja/eja_algebra.py +++ b/mjo/eja/eja_algebra.py @@ -1929,11 +1929,11 @@ class ConcreteEJA(FiniteDimensionalEJA): return eja_class.random_instance(max_dimension, *args, **kwargs) -class MatrixEJA(FiniteDimensionalEJA): +class HermitianMatrixEJA(FiniteDimensionalEJA): @staticmethod def _denormalized_basis(A): """ - Returns a basis for the space of complex Hermitian n-by-n matrices. + Returns a basis for the given Hermitian matrix space. Why do we embed these? Basically, because all of numerical linear algebra assumes that you're working with vectors consisting of `n` @@ -1946,13 +1946,13 @@ class MatrixEJA(FiniteDimensionalEJA): sage: from mjo.hurwitz import (ComplexMatrixAlgebra, ....: QuaternionMatrixAlgebra, ....: OctonionMatrixAlgebra) - sage: from mjo.eja.eja_algebra import MatrixEJA + sage: from mjo.eja.eja_algebra import HermitianMatrixEJA TESTS:: sage: n = ZZ.random_element(1,5) sage: A = MatrixSpace(QQ, n) - sage: B = MatrixEJA._denormalized_basis(A) + sage: B = HermitianMatrixEJA._denormalized_basis(A) sage: all( M.is_hermitian() for M in B) True @@ -1960,7 +1960,7 @@ class MatrixEJA(FiniteDimensionalEJA): sage: n = ZZ.random_element(1,5) sage: A = ComplexMatrixAlgebra(n, scalars=QQ) - sage: B = MatrixEJA._denormalized_basis(A) + sage: B = HermitianMatrixEJA._denormalized_basis(A) sage: all( M.is_hermitian() for M in B) True @@ -1968,7 +1968,7 @@ class MatrixEJA(FiniteDimensionalEJA): sage: n = ZZ.random_element(1,5) sage: A = QuaternionMatrixAlgebra(n, scalars=QQ) - sage: B = MatrixEJA._denormalized_basis(A) + sage: B = HermitianMatrixEJA._denormalized_basis(A) sage: all( M.is_hermitian() for M in B ) True @@ -1976,7 +1976,7 @@ class MatrixEJA(FiniteDimensionalEJA): sage: n = ZZ.random_element(1,5) sage: A = OctonionMatrixAlgebra(n, scalars=QQ) - sage: B = MatrixEJA._denormalized_basis(A) + sage: B = HermitianMatrixEJA._denormalized_basis(A) sage: all( M.is_hermitian() for M in B ) True @@ -2081,7 +2081,7 @@ class MatrixEJA(FiniteDimensionalEJA): self.rank.set_cache(matrix_space.nrows()) self.one.set_cache( self(matrix_space.one()) ) -class RealSymmetricEJA(MatrixEJA, RationalBasisEJA, ConcreteEJA): +class RealSymmetricEJA(HermitianMatrixEJA, RationalBasisEJA, ConcreteEJA): """ The rank-n simple EJA consisting of real symmetric n-by-n matrices, the usual symmetric Jordan product, and the trace inner @@ -2173,7 +2173,7 @@ class RealSymmetricEJA(MatrixEJA, RationalBasisEJA, ConcreteEJA): -class ComplexHermitianEJA(MatrixEJA, RationalBasisEJA, ConcreteEJA): +class ComplexHermitianEJA(HermitianMatrixEJA, RationalBasisEJA, ConcreteEJA): """ The rank-n simple EJA consisting of complex Hermitian n-by-n matrices over the real numbers, the usual symmetric Jordan product, @@ -2272,7 +2272,7 @@ class ComplexHermitianEJA(MatrixEJA, RationalBasisEJA, ConcreteEJA): return cls(n, **kwargs) -class QuaternionHermitianEJA(MatrixEJA, RationalBasisEJA, ConcreteEJA): +class QuaternionHermitianEJA(HermitianMatrixEJA, RationalBasisEJA, ConcreteEJA): r""" The rank-n simple EJA consisting of self-adjoint n-by-n quaternion matrices, the usual symmetric Jordan product, and the @@ -2360,7 +2360,7 @@ class QuaternionHermitianEJA(MatrixEJA, RationalBasisEJA, ConcreteEJA): n = ZZ.random_element(max_size + 1) return cls(n, **kwargs) -class OctonionHermitianEJA(MatrixEJA, RationalBasisEJA, ConcreteEJA): +class OctonionHermitianEJA(HermitianMatrixEJA, RationalBasisEJA, ConcreteEJA): r""" SETUP:: @@ -3542,3 +3542,234 @@ def random_eja(max_dimension=None, *args, **kwargs): # if the sub-call also Decides on a cartesian product. J2 = random_eja(new_max_dimension, *args, **kwargs) return cartesian_product([J1,J2]) + + +class ComplexSkewSymmetricEJA(RationalBasisEJA, ConcreteEJA): + r""" + The skew-symmetric EJA of order `2n` described in Faraut and + Koranyi's Exercise III.1.b. It has dimension `2n^2 - n`. + + It is (not obviously) isomorphic to the QuaternionHermitianEJA of + order `n`, as can be inferred by comparing rank/dimension or + explicitly from their "characteristic polynomial of" functions, + which just so happen to align nicely. + + SETUP:: + + sage: from mjo.eja.eja_algebra import (ComplexSkewSymmetricEJA, + ....: QuaternionHermitianEJA) + sage: from mjo.eja.eja_operator import FiniteDimensionalEJAOperator + + EXAMPLES: + + This EJA is isomorphic to the quaternions:: + + sage: J = ComplexSkewSymmetricEJA(2, field=QQ, orthonormalize=False) + sage: K = QuaternionHermitianEJA(2, field=QQ, orthonormalize=False) + sage: jordan_isom_matrix = matrix.diagonal(QQ,[-1,1,1,1,1,-1]) + sage: phi = FiniteDimensionalEJAOperator(J,K,jordan_isom_matrix) + sage: all( phi(x*y) == phi(x)*phi(y) + ....: for x in J.gens() + ....: for y in J.gens() ) + True + sage: x,y = J.random_elements(2) + sage: phi(x*y) == phi(x)*phi(y) + True + + TESTS: + + Random elements should satisfy the same conditions that the basis + elements do:: + + sage: K = ComplexSkewSymmetricEJA.random_instance(field=QQ, + ....: orthonormalize=False) + sage: x,y = K.random_elements(2) + sage: z = x*y + sage: x = x.to_matrix() + sage: y = y.to_matrix() + sage: z = z.to_matrix() + sage: all( e.is_skew_symmetric() for e in (x,y,z) ) + True + sage: J = -K.one().to_matrix() + sage: all( e*J == J*e.conjugate() for e in (x,y,z) ) + True + + The power law in Faraut & Koranyi's II.7.a is satisfied. + We're in a subalgebra of theirs, but powers are still + defined the same:: + + sage: K = ComplexSkewSymmetricEJA.random_instance(field=QQ, + ....: orthonormalize=False) + sage: x = K.random_element() + sage: k = ZZ.random_element(5) + sage: actual = x^k + sage: J = -K.one().to_matrix() + sage: expected = K(-J*(J*x.to_matrix())^k) + sage: actual == expected + True + + """ + @staticmethod + def _max_random_instance_size(max_dimension): + # Obtained by solving d = 2n^2 - n, which comes from noticing + # that, in 2x2 block form, any element of this algebra has a + # free skew-symmetric top-left block, a Hermitian top-right + # block, and two bottom blocks that are determined by the top. + # The ZZ-int-ZZ thing is just "floor." + return ZZ(int(ZZ(8*max_dimension + 1).sqrt()/4 + 1/4)) + + @classmethod + def random_instance(cls, max_dimension=None, *args, **kwargs): + """ + Return a random instance of this type of algebra. + """ + class_max_d = cls._max_random_instance_dimension() + if (max_dimension is None or max_dimension > class_max_d): + max_dimension = class_max_d + max_size = cls._max_random_instance_size(max_dimension) + n = ZZ.random_element(max_size + 1) + return cls(n, **kwargs) + + @staticmethod + def _denormalized_basis(A): + """ + SETUP:: + + sage: from mjo.hurwitz import ComplexMatrixAlgebra + sage: from mjo.eja.eja_algebra import ComplexSkewSymmetricEJA + + TESTS: + + The basis elements are all skew-Hermitian:: + + sage: d_max = ComplexSkewSymmetricEJA._max_random_instance_dimension() + sage: n_max = ComplexSkewSymmetricEJA._max_random_instance_size(d_max) + sage: n = ZZ.random_element(n_max + 1) + sage: A = ComplexMatrixAlgebra(2*n, scalars=QQ) + sage: B = ComplexSkewSymmetricEJA._denormalized_basis(A) + sage: all( M.is_skew_symmetric() for M in B) + True + + The basis elements ``b`` all satisfy ``b*J == J*b.conjugate()``, + as in the definition of the algebra:: + + sage: d_max = ComplexSkewSymmetricEJA._max_random_instance_dimension() + sage: n_max = ComplexSkewSymmetricEJA._max_random_instance_size(d_max) + sage: n = ZZ.random_element(n_max + 1) + sage: A = ComplexMatrixAlgebra(2*n, scalars=QQ) + sage: I_n = matrix.identity(ZZ, n) + sage: J = matrix.block(ZZ, 2, 2, (0, I_n, -I_n, 0), subdivide=False) + sage: J = A.from_list(J.rows()) + sage: B = ComplexSkewSymmetricEJA._denormalized_basis(A) + sage: all( b*J == J*b.conjugate() for b in B ) + True + + """ + es = A.entry_algebra_gens() + gen = lambda A,m: A.monomial(m) + + basis = [] + + # The size of the blocks. We're going to treat these thing as + # 2x2 block matrices, + # + # [ x1 x2 ] + # [ -x2-conj x1-conj ] + # + # where x1 is skew-symmetric and x2 is Hermitian. + # + m = A.nrows()/2 + + # We only loop through the top half of the matrix, because the + # bottom can be constructed from the top. + for i in range(m): + # First do the top-left block, which is skew-symmetric. + # We can compute the bottom-right block in the process. + for j in range(i+1): + if i != j: + # Skew-symmetry implies zeros for (i == j). + for e in es: + # Top-left block's entry. + E_ij = gen(A, (i,j,e)) + E_ij -= gen(A, (j,i,e)) + + # Bottom-right block's entry. + F_ij = gen(A, (i+m,j+m,e)).conjugate() + F_ij -= gen(A, (j+m,i+m,e)).conjugate() + + basis.append(E_ij + F_ij) + + # Now do the top-right block, which is Hermitian, and compute + # the bottom-left block along the way. + for j in range(m,i+m+1): + if (i+m) == j: + # Hermitian matrices have real diagonal entries. + # Top-right block's entry. + E_ii = gen(A, (i,j,es[0])) + + # Bottom-left block's entry. Don't conjugate + # 'cause it's real. + E_ii -= gen(A, (i+m,j-m,es[0])) + basis.append(E_ii) + else: + for e in es: + # Top-right block's entry. BEWARE! We're not + # reflecting across the main diagonal as in + # (i,j)~(j,i). We're only reflecting across + # the diagonal for the top-right block. + E_ij = gen(A, (i,j,e)) + + # Shift it back to non-offset coords, transpose, + # conjugate, and put it back: + # + # (i,j) -> (i,j-m) -> (j-m, i) -> (j-m, i+m) + E_ij += gen(A, (j-m,i+m,e)).conjugate() + + # Bottom-left's block's below-diagonal entry. + # Just shift the top-right coords down m and + # left m. + F_ij = -gen(A, (i+m,j-m,e)).conjugate() + F_ij += -gen(A, (j,i,e)) # double-conjugate cancels + + basis.append(E_ij + F_ij) + + return tuple( basis ) + + @staticmethod + @cached_method + def _J_matrix(matrix_space): + n = matrix_space.nrows() // 2 + F = matrix_space.base_ring() + I_n = matrix.identity(F, n) + J = matrix.block(F, 2, 2, (0, I_n, -I_n, 0), subdivide=False) + return matrix_space.from_list(J.rows()) + + def J_matrix(self): + return ComplexSkewSymmetricEJA._J_matrix(self.matrix_space()) + + def __init__(self, n, field=AA, **kwargs): + # New code; always check the axioms. + #if "check_axioms" not in kwargs: kwargs["check_axioms"] = False + + from mjo.hurwitz import ComplexMatrixAlgebra + A = ComplexMatrixAlgebra(2*n, scalars=field) + J = ComplexSkewSymmetricEJA._J_matrix(A) + + def jordan_product(X,Y): + return (X*J*Y + Y*J*X)/2 + + def inner_product(X,Y): + return (X.conjugate_transpose()*Y).trace().real() + + super().__init__(self._denormalized_basis(A), + jordan_product, + inner_product, + field=field, + matrix_space=A, + **kwargs) + + # This algebra is conjectured (by me) to be isomorphic to + # the quaternion Hermitian EJA of size n, and the rank + # would follow from that. + #self.rank.set_cache(n) + self.one.set_cache( self(-J) )