X-Git-Url: http://gitweb.michael.orlitzky.com/?a=blobdiff_plain;f=mjo%2Feja%2Feja_algebra.py;h=f7983c857d8fdceffac3dbdfeaa6bf70fd7e811e;hb=97d4dcfae8b0c5ab1577da2ba3629e0a4169c789;hp=06f6f531ac46305da59ec2b4290ba808602cea50;hpb=0fd07263cc543e345f3cd7668938f8a0de70641f;p=sage.d.git diff --git a/mjo/eja/eja_algebra.py b/mjo/eja/eja_algebra.py index 06f6f53..f7983c8 100644 --- a/mjo/eja/eja_algebra.py +++ b/mjo/eja/eja_algebra.py @@ -9,14 +9,16 @@ from sage.algebras.quatalg.quaternion_algebra import QuaternionAlgebra from sage.categories.magmatic_algebras import MagmaticAlgebras from sage.combinat.free_module import CombinatorialFreeModule from sage.matrix.constructor import matrix +from sage.matrix.matrix_space import MatrixSpace from sage.misc.cachefunc import cached_method from sage.misc.prandom import choice from sage.misc.table import table from sage.modules.free_module import FreeModule, VectorSpace from sage.rings.integer_ring import ZZ -from sage.rings.number_field.number_field import QuadraticField +from sage.rings.number_field.number_field import NumberField from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.rational_field import QQ +from sage.rings.real_lazy import CLF, RLF from sage.structure.element import is_Matrix from mjo.eja.eja_element import FiniteDimensionalEuclideanJordanAlgebraElement @@ -134,13 +136,17 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): return self.zero() natural_basis = self.natural_basis() - if elt not in natural_basis[0].matrix_space(): + basis_space = natural_basis[0].matrix_space() + if elt not in basis_space: raise ValueError("not a naturally-represented algebra element") - # Thanks for nothing! Matrix spaces aren't vector - # spaces in Sage, so we have to figure out its - # natural-basis coordinates ourselves. - V = VectorSpace(elt.base_ring(), elt.nrows()*elt.ncols()) + # Thanks for nothing! Matrix spaces aren't vector spaces in + # Sage, so we have to figure out its natural-basis coordinates + # ourselves. We use the basis space's ring instead of the + # element's ring because the basis space might be an algebraic + # closure whereas the base ring of the 3-by-3 identity matrix + # could be QQ instead of QQbar. + V = VectorSpace(basis_space.base_ring(), elt.nrows()*elt.ncols()) W = V.span_of_basis( _mat2vec(s) for s in natural_basis ) coords = W.coordinate_vector(_mat2vec(elt)) return self.from_vector(coords) @@ -497,8 +503,8 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): Finite family {0: e0, 1: e1, 2: e2} sage: J.natural_basis() ( - [1 0] [0 1] [0 0] - [0 0], [1 0], [0 1] + [1 0] [ 0 1/2*sqrt2] [0 0] + [0 0], [1/2*sqrt2 0], [0 1] ) :: @@ -514,11 +520,23 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): """ if self._natural_basis is None: - return tuple( b.to_vector().column() for b in self.basis() ) + M = self.natural_basis_space() + return tuple( M(b.to_vector()) for b in self.basis() ) else: return self._natural_basis + def natural_basis_space(self): + """ + Return the matrix space in which this algebra's natural basis + elements live. + """ + if self._natural_basis is None or len(self._natural_basis) == 0: + return MatrixSpace(self.base_ring(), self.dimension(), 1) + else: + return self._natural_basis[0].matrix_space() + + @cached_method def one(self): """ @@ -661,7 +679,7 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): sage: J = RealSymmetricEJA(2) sage: J.vector_space() - Vector space of dimension 3 over Rational Field + Vector space of dimension 3 over... """ return self.zero().to_vector().parent().ambient_vector_space() @@ -709,6 +727,17 @@ class RealCartesianProductEJA(FiniteDimensionalEuclideanJordanAlgebra): sage: RealCartesianProductEJA(3, prefix='r').gens() (r0, r1, r2) + Our inner product satisfies the Jordan axiom:: + + sage: set_random_seed() + sage: n = ZZ.random_element(1,5) + sage: J = RealCartesianProductEJA(n) + sage: x = J.random_element() + sage: y = J.random_element() + sage: z = J.random_element() + sage: (x*y).inner_product(z) == y.inner_product(x*z) + True + """ def __init__(self, n, field=QQ, **kwargs): V = VectorSpace(field, n) @@ -773,9 +802,22 @@ def random_eja(): -def _real_symmetric_basis(n, field=QQ): +def _real_symmetric_basis(n, field): """ Return a basis for the space of real symmetric n-by-n matrices. + + SETUP:: + + sage: from mjo.eja.eja_algebra import _real_symmetric_basis + + TESTS:: + + sage: set_random_seed() + sage: n = ZZ.random_element(1,5) + sage: B = _real_symmetric_basis(n, QQbar) + sage: all( M.is_symmetric() for M in B) + True + """ # The basis of symmetric matrices, as matrices, in their R^(n-by-n) # coordinates. @@ -786,16 +828,23 @@ def _real_symmetric_basis(n, field=QQ): if i == j: Sij = Eij else: - # Beware, orthogonal but not normalized! Sij = Eij + Eij.transpose() + # Now normalize it. + Sij = Sij / _real_symmetric_matrix_ip(Sij,Sij).sqrt() S.append(Sij) return tuple(S) -def _complex_hermitian_basis(n, field=QQ): +def _complex_hermitian_basis(n, field): """ Returns a basis for the space of complex Hermitian n-by-n matrices. + Why do we embed these? Basically, because all of numerical linear + algebra assumes that you're working with vectors consisting of `n` + entries from a field and scalars from the same field. There's no way + to tell SageMath that (for example) the vectors contain complex + numbers, while the scalar field is real. + SETUP:: sage: from mjo.eja.eja_algebra import _complex_hermitian_basis @@ -804,11 +853,17 @@ def _complex_hermitian_basis(n, field=QQ): sage: set_random_seed() sage: n = ZZ.random_element(1,5) - sage: all( M.is_symmetric() for M in _complex_hermitian_basis(n) ) + sage: R = PolynomialRing(QQ, 'z') + sage: z = R.gen() + sage: field = NumberField(z**2 - 2, 'sqrt2', embedding=RLF(2).sqrt()) + sage: B = _complex_hermitian_basis(n, field) + sage: all( M.is_symmetric() for M in B) True """ - F = QuadraticField(-1, 'I') + R = PolynomialRing(field, 'z') + z = R.gen() + F = NumberField(z**2 + 1, 'I', embedding=CLF(-1).sqrt()) I = F.gen() # This is like the symmetric case, but we need to be careful: @@ -819,7 +874,7 @@ def _complex_hermitian_basis(n, field=QQ): S = [] for i in xrange(n): for j in xrange(i+1): - Eij = matrix(field, n, lambda k,l: k==i and l==j) + Eij = matrix(F, n, lambda k,l: k==i and l==j) if i == j: Sij = _embed_complex_matrix(Eij) S.append(Sij) @@ -830,13 +885,25 @@ def _complex_hermitian_basis(n, field=QQ): S.append(Sij_real) Sij_imag = _embed_complex_matrix(I*Eij - I*Eij.transpose()) S.append(Sij_imag) - return tuple(S) + + # Normalize these with our inner product before handing them back. + # And since we embedded them, we can drop back to the "field" that + # we started with instead of the complex extension "F". + return tuple( (s / _complex_hermitian_matrix_ip(s,s).sqrt()).change_ring(field) + for s in S ) + -def _quaternion_hermitian_basis(n, field=QQ): +def _quaternion_hermitian_basis(n, field): """ Returns a basis for the space of quaternion Hermitian n-by-n matrices. + Why do we embed these? Basically, because all of numerical linear + algebra assumes that you're working with vectors consisting of `n` + entries from a field and scalars from the same field. There's no way + to tell SageMath that (for example) the vectors contain complex + numbers, while the scalar field is real. + SETUP:: sage: from mjo.eja.eja_algebra import _quaternion_hermitian_basis @@ -845,7 +912,8 @@ def _quaternion_hermitian_basis(n, field=QQ): sage: set_random_seed() sage: n = ZZ.random_element(1,5) - sage: all( M.is_symmetric() for M in _quaternion_hermitian_basis(n) ) + sage: B = _quaternion_hermitian_basis(n, QQ) + sage: all( M.is_symmetric() for M in B ) True """ @@ -921,7 +989,9 @@ def _embed_complex_matrix(M): EXAMPLES:: - sage: F = QuadraticField(-1,'i') + sage: R = PolynomialRing(QQ, 'z') + sage: z = R.gen() + sage: F = NumberField(z**2 + 1, 'i', embedding=CLF(-1).sqrt()) sage: x1 = F(4 - 2*i) sage: x2 = F(1 + 2*i) sage: x3 = F(-i) @@ -940,7 +1010,9 @@ def _embed_complex_matrix(M): sage: set_random_seed() sage: n = ZZ.random_element(5) - sage: F = QuadraticField(-1, 'i') + sage: R = PolynomialRing(QQ, 'z') + sage: z = R.gen() + sage: F = NumberField(z**2 + 1, 'i', embedding=CLF(-1).sqrt()) sage: X = random_matrix(F, n) sage: Y = random_matrix(F, n) sage: actual = _embed_complex_matrix(X) * _embed_complex_matrix(Y) @@ -955,8 +1027,8 @@ def _embed_complex_matrix(M): field = M.base_ring() blocks = [] for z in M.list(): - a = z.real() - b = z.imag() + a = z.vector()[0] # real part, I guess + b = z.vector()[1] # imag part, I guess blocks.append(matrix(field, 2, [[a,b],[-b,a]])) # We can drop the imaginaries here. @@ -987,7 +1059,9 @@ def _unembed_complex_matrix(M): Unembedding is the inverse of embedding:: sage: set_random_seed() - sage: F = QuadraticField(-1, 'i') + sage: R = PolynomialRing(QQ, 'z') + sage: z = R.gen() + sage: F = NumberField(z**2 + 1, 'i', embedding=CLF(-1).sqrt()) sage: M = random_matrix(F, 3) sage: _unembed_complex_matrix(_embed_complex_matrix(M)) == M True @@ -999,7 +1073,10 @@ def _unembed_complex_matrix(M): if not n.mod(2).is_zero(): raise ValueError("the matrix 'M' must be a complex embedding") - F = QuadraticField(-1, 'i') + field = M.base_ring() # This should already have sqrt2 + R = PolynomialRing(field, 'z') + z = R.gen() + F = NumberField(z**2 + 1,'i', embedding=CLF(-1).sqrt()) i = F.gen() # Go top-left to bottom-right (reading order), converting every @@ -1060,7 +1137,9 @@ def _embed_quaternion_matrix(M): if M.ncols() != n: raise ValueError("the matrix 'M' must be square") - F = QuadraticField(-1, 'i') + R = PolynomialRing(QQ, 'z') + z = R.gen() + F = NumberField(z**2 + 1, 'i', embedding=CLF(-1).sqrt()) i = F.gen() blocks = [] @@ -1147,6 +1226,15 @@ def _matrix_ip(X,Y): Y_mat = Y.natural_representation() return (X_mat*Y_mat).trace() +def _real_symmetric_matrix_ip(X,Y): + return (X*Y).trace() + +def _complex_hermitian_matrix_ip(X,Y): + # This takes EMBEDDED matrices. + Xu = _unembed_complex_matrix(X) + Yu = _unembed_complex_matrix(Y) + # The trace need not be real; consider Xu = (i*I) and Yu = I. + return ((Xu*Yu).trace()).vector()[0] # real part, I guess class RealSymmetricEJA(FiniteDimensionalEuclideanJordanAlgebra): """ @@ -1165,7 +1253,7 @@ class RealSymmetricEJA(FiniteDimensionalEuclideanJordanAlgebra): sage: e0*e0 e0 sage: e1*e1 - e0 + e2 + 1/2*e0 + 1/2*e2 sage: e2*e2 e2 @@ -1200,9 +1288,47 @@ class RealSymmetricEJA(FiniteDimensionalEuclideanJordanAlgebra): sage: RealSymmetricEJA(3, prefix='q').gens() (q0, q1, q2, q3, q4, q5) + Our inner product satisfies the Jordan axiom:: + + sage: set_random_seed() + sage: n = ZZ.random_element(1,5) + sage: J = RealSymmetricEJA(n) + sage: x = J.random_element() + sage: y = J.random_element() + sage: z = J.random_element() + sage: (x*y).inner_product(z) == y.inner_product(x*z) + True + + Our basis is normalized with respect to the natural inner product:: + + sage: set_random_seed() + sage: n = ZZ.random_element(1,5) + sage: J = RealSymmetricEJA(n) + sage: all( b.norm() == 1 for b in J.gens() ) + True + + Left-multiplication operators are symmetric because they satisfy + the Jordan axiom:: + + sage: set_random_seed() + sage: n = ZZ.random_element(1,5) + sage: x = RealSymmetricEJA(n).random_element() + sage: x.operator().matrix().is_symmetric() + True + """ def __init__(self, n, field=QQ, **kwargs): - S = _real_symmetric_basis(n, field=field) + if n > 1: + # We'll need sqrt(2) to normalize the basis, and this + # winds up in the multiplication table, so the whole + # algebra needs to be over the field extension. + R = PolynomialRing(field, 'z') + z = R.gen() + p = z**2 - 2 + if p.is_irreducible(): + field = NumberField(p, 'sqrt2', embedding=RLF(2).sqrt()) + + S = _real_symmetric_basis(n, field) Qs = _multiplication_table_from_matrix_basis(S) fdeja = super(RealSymmetricEJA, self) @@ -1213,7 +1339,9 @@ class RealSymmetricEJA(FiniteDimensionalEuclideanJordanAlgebra): **kwargs) def inner_product(self, x, y): - return _matrix_ip(x,y) + X = x.natural_representation() + Y = y.natural_representation() + return _real_symmetric_matrix_ip(X,Y) class ComplexHermitianEJA(FiniteDimensionalEuclideanJordanAlgebra): @@ -1258,9 +1386,47 @@ class ComplexHermitianEJA(FiniteDimensionalEuclideanJordanAlgebra): sage: ComplexHermitianEJA(2, prefix='z').gens() (z0, z1, z2, z3) + Our inner product satisfies the Jordan axiom:: + + sage: set_random_seed() + sage: n = ZZ.random_element(1,5) + sage: J = ComplexHermitianEJA(n) + sage: x = J.random_element() + sage: y = J.random_element() + sage: z = J.random_element() + sage: (x*y).inner_product(z) == y.inner_product(x*z) + True + + Our basis is normalized with respect to the natural inner product:: + + sage: set_random_seed() + sage: n = ZZ.random_element(1,4) + sage: J = ComplexHermitianEJA(n) + sage: all( b.norm() == 1 for b in J.gens() ) + True + + Left-multiplication operators are symmetric because they satisfy + the Jordan axiom:: + + sage: set_random_seed() + sage: n = ZZ.random_element(1,5) + sage: x = ComplexHermitianEJA(n).random_element() + sage: x.operator().matrix().is_symmetric() + True + """ def __init__(self, n, field=QQ, **kwargs): - S = _complex_hermitian_basis(n) + if n > 1: + # We'll need sqrt(2) to normalize the basis, and this + # winds up in the multiplication table, so the whole + # algebra needs to be over the field extension. + R = PolynomialRing(field, 'z') + z = R.gen() + p = z**2 - 2 + if p.is_irreducible(): + field = NumberField(p, 'sqrt2', embedding=RLF(2).sqrt()) + + S = _complex_hermitian_basis(n, field) Qs = _multiplication_table_from_matrix_basis(S) fdeja = super(ComplexHermitianEJA, self) @@ -1272,14 +1438,9 @@ class ComplexHermitianEJA(FiniteDimensionalEuclideanJordanAlgebra): def inner_product(self, x, y): - # Since a+bi on the diagonal is represented as - # - # a + bi = [ a b ] - # [ -b a ], - # - # we'll double-count the "a" entries if we take the trace of - # the embedding. - return _matrix_ip(x,y)/2 + X = x.natural_representation() + Y = y.natural_representation() + return _complex_hermitian_matrix_ip(X,Y) class QuaternionHermitianEJA(FiniteDimensionalEuclideanJordanAlgebra): @@ -1324,9 +1485,20 @@ class QuaternionHermitianEJA(FiniteDimensionalEuclideanJordanAlgebra): sage: QuaternionHermitianEJA(2, prefix='a').gens() (a0, a1, a2, a3, a4, a5) + Our inner product satisfies the Jordan axiom:: + + sage: set_random_seed() + sage: n = ZZ.random_element(1,5) + sage: J = QuaternionHermitianEJA(n) + sage: x = J.random_element() + sage: y = J.random_element() + sage: z = J.random_element() + sage: (x*y).inner_product(z) == y.inner_product(x*z) + True + """ def __init__(self, n, field=QQ, **kwargs): - S = _quaternion_hermitian_basis(n) + S = _quaternion_hermitian_basis(n, field) Qs = _multiplication_table_from_matrix_basis(S) fdeja = super(QuaternionHermitianEJA, self) @@ -1386,6 +1558,17 @@ class JordanSpinEJA(FiniteDimensionalEuclideanJordanAlgebra): sage: JordanSpinEJA(2, prefix='B').gens() (B0, B1) + Our inner product satisfies the Jordan axiom:: + + sage: set_random_seed() + sage: n = ZZ.random_element(1,5) + sage: J = JordanSpinEJA(n) + sage: x = J.random_element() + sage: y = J.random_element() + sage: z = J.random_element() + sage: (x*y).inner_product(z) == y.inner_product(x*z) + True + """ def __init__(self, n, field=QQ, **kwargs): V = VectorSpace(field, n)