X-Git-Url: http://gitweb.michael.orlitzky.com/?a=blobdiff_plain;f=mjo%2Feja%2Feja_algebra.py;h=ff2b5d7a9c52ff5c13df7d7aa3682899b9a30559;hb=cd164e717e7224ec1af16d31152555f0c7cf49cf;hp=689a3db016437d1e6eda5c6372e52a3513896671;hpb=3a476bd1ea5aef3ecd375e71d50342f1441fd35d;p=sage.d.git diff --git a/mjo/eja/eja_algebra.py b/mjo/eja/eja_algebra.py index 689a3db..ff2b5d7 100644 --- a/mjo/eja/eja_algebra.py +++ b/mjo/eja/eja_algebra.py @@ -17,7 +17,7 @@ from sage.misc.lazy_import import lazy_import from sage.misc.prandom import choice from sage.misc.table import table from sage.modules.free_module import FreeModule, VectorSpace -from sage.rings.all import (ZZ, QQ, RR, RLF, CLF, +from sage.rings.all import (ZZ, QQ, AA, QQbar, RR, RLF, CLF, PolynomialRing, QuadraticField) from mjo.eja.eja_element import FiniteDimensionalEuclideanJordanAlgebraElement @@ -207,8 +207,8 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): Ensure that it says what we think it says:: - sage: JordanSpinEJA(2, field=QQ) - Euclidean Jordan algebra of dimension 2 over Rational Field + sage: JordanSpinEJA(2, field=AA) + Euclidean Jordan algebra of dimension 2 over Algebraic Real Field sage: JordanSpinEJA(3, field=RDF) Euclidean Jordan algebra of dimension 3 over Real Double Field @@ -551,8 +551,8 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): Finite family {0: e0, 1: e1, 2: e2} sage: J.natural_basis() ( - [1 0] [ 0 1/2*sqrt2] [0 0] - [0 0], [1/2*sqrt2 0], [0 1] + [1 0] [ 0 0.7071067811865475?] [0 0] + [0 0], [0.7071067811865475? 0], [0 1] ) :: @@ -757,7 +757,7 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): J5 = VectorSpace(self.base_ring(), 0) # eigenvalue one-half J1 = trivial # eigenvalue one - for (eigval, eigspace) in c.operator().matrix().left_eigenspaces(): + for (eigval, eigspace) in c.operator().matrix().right_eigenspaces(): if eigval == ~(self.base_ring()(2)): J5 = eigspace else: @@ -791,19 +791,21 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): True """ - return tuple( self.random_element() for idx in range(count) ) - + return tuple( self.random_element() for idx in range(count) ) + @cached_method def rank(self): """ Return the rank of this EJA. ALGORITHM: - The author knows of no algorithm to compute the rank of an EJA - where only the multiplication table is known. In lieu of one, we - require the rank to be specified when the algebra is created, - and simply pass along that number here. + We first compute the polynomial "column matrices" `p_{k}` that + evaluate to `x^k` on the coordinates of `x`. Then, we begin + adding them to a matrix one at a time, and trying to solve the + system that makes `p_{0}`,`p_{1}`,..., `p_{s-1}` add up to + `p_{s}`. This will succeed only when `s` is the rank of the + algebra, as proven in a recent draft paper of mine. SETUP:: @@ -848,8 +850,80 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): sage: r > 0 or (r == 0 and J.is_trivial()) True + Ensure that computing the rank actually works, since the ranks + of all simple algebras are known and will be cached by default:: + + sage: J = HadamardEJA(4) + sage: J.rank.clear_cache() + sage: J.rank() + 4 + + :: + + sage: J = JordanSpinEJA(4) + sage: J.rank.clear_cache() + sage: J.rank() + 2 + + :: + + sage: J = RealSymmetricEJA(3) + sage: J.rank.clear_cache() + sage: J.rank() + 3 + + :: + + sage: J = ComplexHermitianEJA(2) + sage: J.rank.clear_cache() + sage: J.rank() + 2 + + :: + + sage: J = QuaternionHermitianEJA(2) + sage: J.rank.clear_cache() + sage: J.rank() + 2 + """ - return self._rank + n = self.dimension() + if n == 0: + return 0 + elif n == 1: + return 1 + + var_names = [ "X" + str(z) for z in range(1,n+1) ] + R = PolynomialRing(self.base_ring(), var_names) + vars = R.gens() + + def L_x_i_j(i,j): + # From a result in my book, these are the entries of the + # basis representation of L_x. + return sum( vars[k]*self.monomial(k).operator().matrix()[i,j] + for k in range(n) ) + + L_x = matrix(R, n, n, L_x_i_j) + x_powers = [ vars[k]*(L_x**k)*self.one().to_vector() + for k in range(n) ] + + # Can assume n >= 2 + M = matrix([x_powers[0]]) + old_rank = 1 + + for d in range(1,n): + M = matrix(M.rows() + [x_powers[d]]) + M.echelonize() + # TODO: we've basically solved the system here. + # We should save the echelonized matrix somehow + # so that it can be reused in the charpoly method. + new_rank = M.rank() + if new_rank == old_rank: + return new_rank + else: + old_rank = new_rank + + return n def vector_space(self): @@ -911,7 +985,7 @@ class KnownRankEJA(object): return 5 @classmethod - def random_instance(cls, field=QQ, **kwargs): + def random_instance(cls, field=AA, **kwargs): """ Return a random instance of this type of algebra. @@ -967,7 +1041,7 @@ class HadamardEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA): (r0, r1, r2) """ - def __init__(self, n, field=QQ, **kwargs): + def __init__(self, n, field=AA, **kwargs): V = VectorSpace(field, n) mult_table = [ [ V.gen(i)*(i == j) for j in range(n) ] for i in range(n) ] @@ -1000,7 +1074,7 @@ class HadamardEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA): return x.to_vector().inner_product(y.to_vector()) -def random_eja(field=QQ, nontrivial=False): +def random_eja(field=AA, nontrivial=False): """ Return a "random" finite-dimensional Euclidean Jordan Algebra. @@ -1069,6 +1143,28 @@ class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra): **kwargs) + def _rank_computation(self): + r""" + Override the parent method with something that tries to compute + over a faster (non-extension) field. + """ + if self._basis_normalizers is None: + # We didn't normalize, so assume that the basis we started + # with had entries in a nice field. + return super(MatrixEuclideanJordanAlgebra, self)._rank_computation() + else: + basis = ( (b/n) for (b,n) in zip(self.natural_basis(), + self._basis_normalizers) ) + + # Do this over the rationals and convert back at the end. + # Only works because we know the entries of the basis are + # integers. + J = MatrixEuclideanJordanAlgebra(QQ, + basis, + self.rank(), + normalize_basis=False) + return J._rank_computation() + @cached_method def _charpoly_coeff(self, i): """ @@ -1220,8 +1316,8 @@ class RealSymmetricEJA(RealMatrixEuclideanJordanAlgebra, KnownRankEJA): In theory, our "field" can be any subfield of the reals:: - sage: RealSymmetricEJA(2, AA) - Euclidean Jordan algebra of dimension 3 over Algebraic Real Field + sage: RealSymmetricEJA(2, RDF) + Euclidean Jordan algebra of dimension 3 over Real Double Field sage: RealSymmetricEJA(2, RR) Euclidean Jordan algebra of dimension 3 over Real Field with 53 bits of precision @@ -1313,7 +1409,7 @@ class RealSymmetricEJA(RealMatrixEuclideanJordanAlgebra, KnownRankEJA): return 4 # Dimension 10 - def __init__(self, n, field=QQ, **kwargs): + def __init__(self, n, field=AA, **kwargs): basis = self._denormalized_basis(n, field) super(RealSymmetricEJA, self).__init__(field, basis, n, **kwargs) @@ -1333,7 +1429,7 @@ class ComplexMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra): EXAMPLES:: - sage: F = QuadraticField(-1, 'i') + sage: F = QuadraticField(-1, 'I') sage: x1 = F(4 - 2*i) sage: x2 = F(1 + 2*i) sage: x3 = F(-i) @@ -1353,7 +1449,7 @@ class ComplexMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra): sage: set_random_seed() sage: n_max = ComplexMatrixEuclideanJordanAlgebra._max_test_case_size() sage: n = ZZ.random_element(n_max) - sage: F = QuadraticField(-1, 'i') + sage: F = QuadraticField(-1, 'I') sage: X = random_matrix(F, n) sage: Y = random_matrix(F, n) sage: Xe = ComplexMatrixEuclideanJordanAlgebra.real_embed(X) @@ -1396,15 +1492,15 @@ class ComplexMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra): ....: [ 9, 10, 11, 12], ....: [-10, 9, -12, 11] ]) sage: ComplexMatrixEuclideanJordanAlgebra.real_unembed(A) - [ 2*i + 1 4*i + 3] - [ 10*i + 9 12*i + 11] + [ 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: F = QuadraticField(-1, 'I') sage: M = random_matrix(F, 3) sage: Me = ComplexMatrixEuclideanJordanAlgebra.real_embed(M) sage: ComplexMatrixEuclideanJordanAlgebra.real_unembed(Me) == M @@ -1422,7 +1518,12 @@ class ComplexMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra): field = M.base_ring() R = PolynomialRing(field, 'z') z = R.gen() - F = field.extension(z**2 + 1, 'i', embedding=CLF(-1).sqrt()) + 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 + else: + F = field.extension(z**2 + 1, 'I', embedding=CLF(-1).sqrt()) i = F.gen() # Go top-left to bottom-right (reading order), converting every @@ -1463,7 +1564,7 @@ class ComplexMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra): sage: Ye = y.natural_representation() sage: X = ComplexHermitianEJA.real_unembed(Xe) sage: Y = ComplexHermitianEJA.real_unembed(Ye) - sage: expected = (X*Y).trace().vector()[0] + sage: expected = (X*Y).trace().real() sage: actual = ComplexHermitianEJA.natural_inner_product(Xe,Ye) sage: actual == expected True @@ -1487,8 +1588,8 @@ class ComplexHermitianEJA(ComplexMatrixEuclideanJordanAlgebra, KnownRankEJA): In theory, our "field" can be any subfield of the reals:: - sage: ComplexHermitianEJA(2, AA) - Euclidean Jordan algebra of dimension 4 over Algebraic Real Field + sage: ComplexHermitianEJA(2, RDF) + Euclidean Jordan algebra of dimension 4 over Real Double Field sage: ComplexHermitianEJA(2, RR) Euclidean Jordan algebra of dimension 4 over Real Field with 53 bits of precision @@ -1598,7 +1699,7 @@ class ComplexHermitianEJA(ComplexMatrixEuclideanJordanAlgebra, KnownRankEJA): return ( s.change_ring(field) for s in S ) - def __init__(self, n, field=QQ, **kwargs): + def __init__(self, n, field=AA, **kwargs): basis = self._denormalized_basis(n,field) super(ComplexHermitianEJA,self).__init__(field, basis, n, **kwargs) @@ -1650,7 +1751,7 @@ class QuaternionMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra): if M.ncols() != n: raise ValueError("the matrix 'M' must be square") - F = QuadraticField(-1, 'i') + F = QuadraticField(-1, 'I') i = F.gen() blocks = [] @@ -1726,10 +1827,10 @@ class QuaternionMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra): raise ValueError('bad on-diagonal submatrix') if submat[0,1] != -submat[1,0].conjugate(): raise ValueError('bad off-diagonal submatrix') - z = submat[0,0].vector()[0] # real part - z += submat[0,0].vector()[1]*i # imag part - z += submat[0,1].vector()[0]*j # real part - z += submat[0,1].vector()[1]*k # imag part + z = submat[0,0].real() + z += submat[0,0].imag()*i + z += submat[0,1].real()*j + z += submat[0,1].imag()*k elements.append(z) return matrix(Q, n/4, elements) @@ -1782,8 +1883,8 @@ class QuaternionHermitianEJA(QuaternionMatrixEuclideanJordanAlgebra, In theory, our "field" can be any subfield of the reals:: - sage: QuaternionHermitianEJA(2, AA) - Euclidean Jordan algebra of dimension 6 over Algebraic Real Field + sage: QuaternionHermitianEJA(2, RDF) + Euclidean Jordan algebra of dimension 6 over Real Double Field sage: QuaternionHermitianEJA(2, RR) Euclidean Jordan algebra of dimension 6 over Real Field with 53 bits of precision @@ -1894,7 +1995,7 @@ class QuaternionHermitianEJA(QuaternionMatrixEuclideanJordanAlgebra, return ( s.change_ring(field) for s in S ) - def __init__(self, n, field=QQ, **kwargs): + def __init__(self, n, field=AA, **kwargs): basis = self._denormalized_basis(n,field) super(QuaternionHermitianEJA,self).__init__(field, basis, n, **kwargs) @@ -1954,7 +2055,7 @@ class BilinearFormEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA): sage: actual == expected True """ - def __init__(self, n, field=QQ, B=None, **kwargs): + def __init__(self, n, field=AA, B=None, **kwargs): if B is None: self._B = matrix.identity(field, max(0,n-1)) else: @@ -1994,23 +2095,20 @@ class BilinearFormEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA): TESTS: - Ensure that this is one-half of the trace inner-product:: + Ensure that this is one-half of the trace inner-product when + the algebra isn't just the reals (when ``n`` isn't one). This + is in Faraut and Koranyi, and also my "On the symmetry..." + paper:: sage: set_random_seed() - sage: n = ZZ.random_element(5) - sage: M = matrix.random(QQ, n-1, algorithm='unimodular') + sage: n = ZZ.random_element(2,5) + sage: M = matrix.random(QQ, max(0,n-1), algorithm='unimodular') sage: B = M.transpose()*M sage: J = BilinearFormEJA(n, B=B) - sage: eis = VectorSpace(M.base_ring(), M.ncols()).basis() - sage: V = J.vector_space() - sage: sis = [ J.from_vector(V([0] + (M.inverse()*ei).list())) - ....: for ei in eis ] - sage: actual = [ sis[i]*sis[j] - ....: for i in range(n-1) - ....: for j in range(n-1) ] - sage: expected = [ J.one() if i == j else J.zero() - ....: for i in range(n-1) - ....: for j in range(n-1) ] + sage: x = J.random_element() + sage: y = J.random_element() + sage: x.inner_product(y) == (x*y).trace()/2 + True """ xvec = x.to_vector() @@ -2019,7 +2117,8 @@ class BilinearFormEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA): ybar = yvec[1:] return x[0]*y[0] + (self._B*xbar).inner_product(ybar) -class JordanSpinEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA): + +class JordanSpinEJA(BilinearFormEJA): """ The rank-2 simple EJA consisting of real vectors ``x=(x0, x_bar)`` with the usual inner product and jordan product ``x*y = @@ -2056,42 +2155,9 @@ class JordanSpinEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA): sage: JordanSpinEJA(2, prefix='B').gens() (B0, B1) - """ - def __init__(self, n, field=QQ, **kwargs): - V = VectorSpace(field, n) - mult_table = [[V.zero() for j in range(n)] for i in range(n)] - for i in range(n): - for j in range(n): - x = V.gen(i) - y = V.gen(j) - x0 = x[0] - xbar = x[1:] - y0 = y[0] - ybar = y[1:] - # z = x*y - z0 = x.inner_product(y) - zbar = y0*xbar + x0*ybar - z = V([z0] + zbar.list()) - mult_table[i][j] = z - - # The rank of the spin algebra is two, unless we're in a - # one-dimensional ambient space (because the rank is bounded by - # the ambient dimension). - fdeja = super(JordanSpinEJA, self) - return fdeja.__init__(field, mult_table, rank=min(n,2), **kwargs) - - def inner_product(self, x, y): - """ - Faster to reimplement than to use natural representations. - - SETUP:: - - sage: from mjo.eja.eja_algebra import JordanSpinEJA + TESTS: - TESTS: - - Ensure that this is the usual inner product for the algebras - over `R^n`:: + Ensure that we have the usual inner product on `R^n`:: sage: set_random_seed() sage: J = JordanSpinEJA.random_instance() @@ -2101,8 +2167,11 @@ class JordanSpinEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA): sage: x.inner_product(y) == J.natural_inner_product(X,Y) True - """ - return x.to_vector().inner_product(y.to_vector()) + """ + def __init__(self, n, field=AA, **kwargs): + # This is a special case of the BilinearFormEJA with the identity + # matrix as its bilinear form. + return super(JordanSpinEJA, self).__init__(n, field, **kwargs) class TrivialEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA): @@ -2129,12 +2198,12 @@ class TrivialEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA): sage: J.one().norm() 0 sage: J.one().subalgebra_generated_by() - Euclidean Jordan algebra of dimension 0 over Rational Field + Euclidean Jordan algebra of dimension 0 over Algebraic Real Field sage: J.rank() 0 """ - def __init__(self, field=QQ, **kwargs): + def __init__(self, field=AA, **kwargs): mult_table = [] fdeja = super(TrivialEJA, self) # The rank is zero using my definition, namely the dimension of the