From f1ddf1e9eee634161aad87b9c2de0194efb17879 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Wed, 25 Nov 2020 17:55:49 -0500 Subject: [PATCH] eja: update todo, and rename "natural" to "matrix". We've already completed one TODO by adding is_self_adjoint() for operators. Another is completed by deciding that I don't want to drop natural representations for non-matrix algebras. But I do want to rename them, to "matrix representations," because that's what they are. --- mjo/eja/TODO | 9 ++- mjo/eja/eja_algebra.py | 155 +++++++++++++++++++++----------------- mjo/eja/eja_element.py | 26 +++---- mjo/eja/eja_subalgebra.py | 30 ++++---- 4 files changed, 117 insertions(+), 103 deletions(-) diff --git a/mjo/eja/TODO b/mjo/eja/TODO index 2660156..f0901ca 100644 --- a/mjo/eja/TODO +++ b/mjo/eja/TODO @@ -14,7 +14,7 @@ 6. Pass already_echelonized (default: False) and echelon_basis (default: None) into the subalgebra constructor. The value of already_echelonized can be passed to V.span_of_basis() to save - some time, and usinf e.g. FreeModule_submodule_with_basis_field + some time, and using e.g. FreeModule_submodule_with_basis_field we may somehow be able to pass the echelon basis straight in to save time. @@ -35,7 +35,8 @@ 9. Compute the scalar in the general natural_inner_product() for matrices, so no overrides are necessary. -10. Eliminate "natural representation" for non-matrix algebras. - -11. The main EJA element constructor is happy to convert between +10. The main EJA element constructor is happy to convert between e.g. HadamardEJA(3) and JordanSpinEJA(3). + +11. Figure out if CombinatorialFreeModule's use of IndexedGenerators + can be used to replace the matrix_basis(). diff --git a/mjo/eja/eja_algebra.py b/mjo/eja/eja_algebra.py index 2ea1a92..9d53bc5 100644 --- a/mjo/eja/eja_algebra.py +++ b/mjo/eja/eja_algebra.py @@ -64,7 +64,7 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): mult_table, prefix='e', category=None, - natural_basis=None, + matrix_basis=None, check_field=True, check_axioms=True): """ @@ -115,7 +115,7 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): if not all( len(l) == n for l in mult_table ): raise ValueError("multiplication table is not square") - self._natural_basis = natural_basis + self._matrix_basis = matrix_basis if category is None: category = MagmaticAlgebras(field).FiniteDimensional() @@ -149,7 +149,7 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): def _element_constructor_(self, elt): """ - Construct an element of this algebra from its natural + Construct an element of this algebra from its vector or matrix representation. This gets called only after the parent element _call_ method @@ -182,8 +182,8 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): TESTS: Ensure that we can convert any element of the two non-matrix - simple algebras (whose natural representations are their usual - vector representations) back and forth faithfully:: + simple algebras (whose matrix representations are columns) + back and forth faithfully:: sage: set_random_seed() sage: J = HadamardEJA.random_instance() @@ -194,7 +194,6 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): sage: x = J.random_element() sage: J(x.to_vector().column()) == x True - """ msg = "not an element of this algebra" if elt == 0: @@ -208,19 +207,17 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): # that the integer 3 belongs to the space of 2-by-2 matrices. raise ValueError(msg) - natural_basis = self.natural_basis() - basis_space = natural_basis[0].matrix_space() - if elt not in basis_space: + if elt not in self.matrix_space(): raise ValueError(msg) # Thanks for nothing! Matrix spaces aren't vector spaces in - # Sage, so we have to figure out its natural-basis coordinates + # Sage, so we have to figure out its matrix-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 ) + V = VectorSpace(self.base_ring(), elt.nrows()*elt.ncols()) + W = V.span_of_basis( _mat2vec(s) for s in self.matrix_basis() ) try: coords = W.coordinate_vector(_mat2vec(elt)) @@ -515,18 +512,33 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): return table(M, header_row=True, header_column=True, frame=True) - def natural_basis(self): + def matrix_basis(self): """ - Return a more-natural representation of this algebra's basis. + Return an (often more natural) representation of this algebras + basis as an ordered tuple of matrices. + + Every finite-dimensional Euclidean Jordan Algebra is a, up to + Jordan isomorphism, a direct sum of five simple + algebras---four of which comprise Hermitian matrices. And the + last type of algebra can of course be thought of as `n`-by-`1` + column matrices (ambiguusly called column vectors) to avoid + special cases. As a result, matrices (and column vectors) are + a natural representation format for Euclidean Jordan algebra + elements. - Every finite-dimensional Euclidean Jordan Algebra is a direct - sum of five simple algebras, four of which comprise Hermitian - matrices. This method returns the original "natural" basis - for our underlying vector space. (Typically, the natural basis - is used to construct the multiplication table in the first place.) + But, when we construct an algebra from a basis of matrices, + those matrix representations are lost in favor of coordinate + vectors *with respect to* that basis. We could eventually + convert back if we tried hard enough, but having the original + representations handy is valuable enough that we simply store + them and return them from this method. - Note that this will always return a matrix. The standard basis - in `R^n` will be returned as `n`-by-`1` column matrices. + Why implement this for non-matrix algebras? Avoiding special + cases for the :class:`BilinearFormEJA` pays with simplicity in + its own right. But mainly, we would like to be able to assume + that elements of a :class:`DirectSumEJA` can be displayed + nicely, without having to have special classes for direct sums + one of whose components was a matrix algebra. SETUP:: @@ -538,7 +550,7 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): sage: J = RealSymmetricEJA(2) sage: J.basis() Finite family {0: e0, 1: e1, 2: e2} - sage: J.natural_basis() + sage: J.matrix_basis() ( [1 0] [ 0 0.7071067811865475?] [0 0] [0 0], [0.7071067811865475? 0], [0 1] @@ -549,36 +561,38 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): sage: J = JordanSpinEJA(2) sage: J.basis() Finite family {0: e0, 1: e1} - sage: J.natural_basis() + sage: J.matrix_basis() ( [1] [0] [0], [1] ) - """ - if self._natural_basis is None: - M = self.natural_basis_space() + if self._matrix_basis is None: + M = self.matrix_space() return tuple( M(b.to_vector()) for b in self.basis() ) else: - return self._natural_basis + return self._matrix_basis - def natural_basis_space(self): + def matrix_space(self): """ - Return the matrix space in which this algebra's natural basis - elements live. + Return the matrix space in which this algebra's elements live, if + we think of them as matrices (including column vectors of the + appropriate size). Generally this will be an `n`-by-`1` column-vector space, except when the algebra is trivial. There it's `n`-by-`n` - (where `n` is zero), to ensure that two elements of the - natural basis space (empty matrices) can be multiplied. + (where `n` is zero), to ensure that two elements of the matrix + space (empty matrices) can be multiplied. + + Matrix algebras override this with something more useful. """ if self.is_trivial(): return MatrixSpace(self.base_ring(), 0) - elif self._natural_basis is None or len(self._natural_basis) == 0: + elif self._matrix_basis is None or len(self._matrix_basis) == 0: return MatrixSpace(self.base_ring(), self.dimension(), 1) else: - return self._natural_basis[0].matrix_space() + return self._matrix_basis[0].matrix_space() @cached_method @@ -705,22 +719,22 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): Vector space of degree 6 and dimension 2... sage: J1 Euclidean Jordan algebra of dimension 3... - sage: J0.one().natural_representation() + sage: J0.one().to_matrix() [0 0 0] [0 0 0] [0 0 1] sage: orig_df = AA.options.display_format sage: AA.options.display_format = 'radical' - sage: J.from_vector(J5.basis()[0]).natural_representation() + sage: J.from_vector(J5.basis()[0]).to_matrix() [ 0 0 1/2*sqrt(2)] [ 0 0 0] [1/2*sqrt(2) 0 0] - sage: J.from_vector(J5.basis()[1]).natural_representation() + sage: J.from_vector(J5.basis()[1]).to_matrix() [ 0 0 0] [ 0 0 1/2*sqrt(2)] [ 0 1/2*sqrt(2) 0] sage: AA.options.display_format = orig_df - sage: J1.one().natural_representation() + sage: J1.one().to_matrix() [1 0 0] [0 1 0] [0 0 0] @@ -1106,26 +1120,25 @@ class ConcreteEuclideanJordanAlgebra: TESTS: - Our natural basis is normalized with respect to the natural inner - product unless we specify otherwise:: + Our basis is normalized with respect to the algebra's inner + product, unless we specify otherwise:: sage: set_random_seed() sage: J = ConcreteEuclideanJordanAlgebra.random_instance() sage: all( b.norm() == 1 for b in J.gens() ) True - Since our natural basis is normalized with respect to the natural - inner product, and since we know that this algebra is an EJA, any + Since our basis is orthonormal with respect to the algebra's inner + product, and since we know that this algebra is an EJA, any left-multiplication operator's matrix will be symmetric because - natural->EJA basis representation is an isometry and within the EJA - the operator is self-adjoint by the Jordan axiom:: + natural->EJA basis representation is an isometry and within the + EJA the operator is self-adjoint by the Jordan axiom:: sage: set_random_seed() sage: J = ConcreteEuclideanJordanAlgebra.random_instance() sage: x = J.random_element() sage: x.operator().is_self_adjoint() True - """ @staticmethod @@ -1187,7 +1200,7 @@ class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra): field = field.extension(p, 'sqrt2', embedding=RLF(2).sqrt()) basis = tuple( s.change_ring(field) for s in basis ) self._basis_normalizers = tuple( - ~(self.natural_inner_product(s,s).sqrt()) for s in basis ) + ~(self.matrix_inner_product(s,s).sqrt()) for s in basis ) basis = tuple(s*c for (s,c) in zip(basis,self._basis_normalizers)) # Now compute the multiplication and inner product tables. @@ -1208,7 +1221,7 @@ class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra): # HACK: ignore the error here if we don't need the # inner product (as is the case when we construct # a dummy QQ-algebra for fast charpoly coefficients. - ip_table[i][j] = self.natural_inner_product(basis[i], + ip_table[i][j] = self.matrix_inner_product(basis[i], basis[j]) except: pass @@ -1221,7 +1234,7 @@ class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra): super(MatrixEuclideanJordanAlgebra, self).__init__(field, mult_table, - natural_basis=basis, + matrix_basis=basis, **kwargs) if algebra_dim == 0: @@ -1244,7 +1257,7 @@ class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra): # entries in a nice field already. Just compute the thing. return super(MatrixEuclideanJordanAlgebra, self)._charpoly_coefficients() - basis = ( (b/n) for (b,n) in zip(self.natural_basis(), + basis = ( (b/n) for (b,n) in zip(self.matrix_basis(), self._basis_normalizers) ) # Do this over the rationals and convert back at the end. @@ -1304,7 +1317,7 @@ class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra): raise NotImplementedError @classmethod - def natural_inner_product(cls,X,Y): + def matrix_inner_product(cls,X,Y): Xu = cls.real_unembed(X) Yu = cls.real_unembed(Y) tr = (Xu*Yu).trace() @@ -1383,9 +1396,9 @@ class RealSymmetricEJA(RealMatrixEuclideanJordanAlgebra, sage: set_random_seed() sage: J = RealSymmetricEJA.random_instance() sage: x,y = J.random_elements(2) - sage: actual = (x*y).natural_representation() - sage: X = x.natural_representation() - sage: Y = y.natural_representation() + sage: actual = (x*y).to_matrix() + sage: X = x.to_matrix() + sage: Y = y.to_matrix() sage: expected = (X*Y + Y*X)/2 sage: actual == expected True @@ -1584,9 +1597,9 @@ class ComplexMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra): @classmethod - def natural_inner_product(cls,X,Y): + def matrix_inner_product(cls,X,Y): """ - Compute a natural inner product in this algebra directly from + Compute a matrix inner product in this algebra directly from its real embedding. SETUP:: @@ -1601,17 +1614,17 @@ class ComplexMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra): sage: set_random_seed() sage: J = ComplexHermitianEJA.random_instance() sage: x,y = J.random_elements(2) - sage: Xe = x.natural_representation() - sage: Ye = y.natural_representation() + sage: Xe = x.to_matrix() + sage: Ye = y.to_matrix() sage: X = ComplexHermitianEJA.real_unembed(Xe) sage: Y = ComplexHermitianEJA.real_unembed(Ye) sage: expected = (X*Y).trace().real() - sage: actual = ComplexHermitianEJA.natural_inner_product(Xe,Ye) + sage: actual = ComplexHermitianEJA.matrix_inner_product(Xe,Ye) sage: actual == expected True """ - return RealMatrixEuclideanJordanAlgebra.natural_inner_product(X,Y)/2 + return RealMatrixEuclideanJordanAlgebra.matrix_inner_product(X,Y)/2 class ComplexHermitianEJA(ComplexMatrixEuclideanJordanAlgebra, @@ -1652,9 +1665,9 @@ class ComplexHermitianEJA(ComplexMatrixEuclideanJordanAlgebra, sage: set_random_seed() sage: J = ComplexHermitianEJA.random_instance() sage: x,y = J.random_elements(2) - sage: actual = (x*y).natural_representation() - sage: X = x.natural_representation() - sage: Y = y.natural_representation() + sage: actual = (x*y).to_matrix() + sage: X = x.to_matrix() + sage: Y = y.to_matrix() sage: expected = (X*Y + Y*X)/2 sage: actual == expected True @@ -1879,9 +1892,9 @@ class QuaternionMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra): @classmethod - def natural_inner_product(cls,X,Y): + def matrix_inner_product(cls,X,Y): """ - Compute a natural inner product in this algebra directly from + Compute a matrix inner product in this algebra directly from its real embedding. SETUP:: @@ -1896,17 +1909,17 @@ class QuaternionMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra): sage: set_random_seed() sage: J = QuaternionHermitianEJA.random_instance() sage: x,y = J.random_elements(2) - sage: Xe = x.natural_representation() - sage: Ye = y.natural_representation() + sage: Xe = x.to_matrix() + sage: Ye = y.to_matrix() sage: X = QuaternionHermitianEJA.real_unembed(Xe) sage: Y = QuaternionHermitianEJA.real_unembed(Ye) sage: expected = (X*Y).trace().coefficient_tuple()[0] - sage: actual = QuaternionHermitianEJA.natural_inner_product(Xe,Ye) + sage: actual = QuaternionHermitianEJA.matrix_inner_product(Xe,Ye) sage: actual == expected True """ - return RealMatrixEuclideanJordanAlgebra.natural_inner_product(X,Y)/4 + return RealMatrixEuclideanJordanAlgebra.matrix_inner_product(X,Y)/4 class QuaternionHermitianEJA(QuaternionMatrixEuclideanJordanAlgebra, @@ -1947,9 +1960,9 @@ class QuaternionHermitianEJA(QuaternionMatrixEuclideanJordanAlgebra, sage: set_random_seed() sage: J = QuaternionHermitianEJA.random_instance() sage: x,y = J.random_elements(2) - sage: actual = (x*y).natural_representation() - sage: X = x.natural_representation() - sage: Y = y.natural_representation() + sage: actual = (x*y).to_matrix() + sage: X = x.to_matrix() + sage: Y = y.to_matrix() sage: expected = (X*Y + Y*X)/2 sage: actual == expected True diff --git a/mjo/eja/eja_element.py b/mjo/eja/eja_element.py index 120870b..6547668 100644 --- a/mjo/eja/eja_element.py +++ b/mjo/eja/eja_element.py @@ -971,15 +971,16 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement): - def natural_representation(self): + def to_matrix(self): """ - Return a more-natural representation of this element. + Return an (often more natural) representation of this element as a + matrix. - Every finite-dimensional Euclidean Jordan Algebra is a - direct sum of five simple algebras, four of which comprise - Hermitian matrices. This method returns the original - "natural" representation of this element as a Hermitian - matrix, if it has one. If not, you get the usual representation. + Every finite-dimensional Euclidean Jordan Algebra is a direct + sum of five simple algebras, four of which comprise Hermitian + matrices. This method returns a "natural" matrix + representation of this element as either a Hermitian matrix or + column vector. SETUP:: @@ -991,7 +992,7 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement): sage: J = ComplexHermitianEJA(3) sage: J.one() e0 + e3 + e8 - sage: J.one().natural_representation() + sage: J.one().to_matrix() [1 0 0 0 0 0] [0 1 0 0 0 0] [0 0 1 0 0 0] @@ -1004,7 +1005,7 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement): sage: J = QuaternionHermitianEJA(3) sage: J.one() e0 + e5 + e14 - sage: J.one().natural_representation() + sage: J.one().to_matrix() [1 0 0 0 0 0 0 0 0 0 0 0] [0 1 0 0 0 0 0 0 0 0 0 0] [0 0 1 0 0 0 0 0 0 0 0 0] @@ -1017,15 +1018,14 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement): [0 0 0 0 0 0 0 0 0 1 0 0] [0 0 0 0 0 0 0 0 0 0 1 0] [0 0 0 0 0 0 0 0 0 0 0 1] - """ - B = self.parent().natural_basis() - W = self.parent().natural_basis_space() + B = self.parent().matrix_basis() + W = self.parent().matrix_space() # This is just a manual "from_vector()", but of course # matrix spaces aren't vector spaces in sage, so they # don't have a from_vector() method. - return W.linear_combination(zip(B,self.to_vector())) + return W.linear_combination( zip(B, self.to_vector()) ) def norm(self): diff --git a/mjo/eja/eja_subalgebra.py b/mjo/eja/eja_subalgebra.py index 6a9d10f..e7e559f 100644 --- a/mjo/eja/eja_subalgebra.py +++ b/mjo/eja/eja_subalgebra.py @@ -11,14 +11,14 @@ class FiniteDimensionalEuclideanJordanSubalgebraElement(FiniteDimensionalEuclide TESTS:: - The natural representation of an element in the subalgebra is - the same as its natural representation in the superalgebra:: + The matrix representation of an element in the subalgebra is + the same as its matrix representation in the superalgebra:: sage: set_random_seed() sage: A = random_eja().random_element().subalgebra_generated_by() sage: y = A.random_element() - sage: actual = y.natural_representation() - sage: expected = y.superalgebra_element().natural_representation() + sage: actual = y.to_matrix() + sage: expected = y.superalgebra_element().to_matrix() sage: actual == expected True @@ -121,11 +121,11 @@ class FiniteDimensionalEuclideanJordanSubalgebra(FiniteDimensionalEuclideanJorda sage: E22 = matrix(AA, [ [0,0], ....: [0,1] ]) sage: K1 = FiniteDimensionalEuclideanJordanSubalgebra(J, (J(E11),)) - sage: K1.one().natural_representation() + sage: K1.one().to_matrix() [1 0] [0 0] sage: K2 = FiniteDimensionalEuclideanJordanSubalgebra(J, (J(E22),)) - sage: K2.one().natural_representation() + sage: K2.one().to_matrix() [0 0] [0 1] @@ -192,7 +192,7 @@ class FiniteDimensionalEuclideanJordanSubalgebra(FiniteDimensionalEuclideanJorda mult_table[i][j] = W.coordinate_vector(product_vector) self._inner_product_matrix = matrix(field, ip_table) - natural_basis = tuple( b.natural_representation() for b in basis ) + matrix_basis = tuple( b.to_matrix() for b in basis ) self._vector_space = W @@ -202,7 +202,7 @@ class FiniteDimensionalEuclideanJordanSubalgebra(FiniteDimensionalEuclideanJorda mult_table, prefix=prefix, category=category, - natural_basis=natural_basis, + matrix_basis=matrix_basis, check_field=False, check_axioms=check_axioms) @@ -256,16 +256,16 @@ class FiniteDimensionalEuclideanJordanSubalgebra(FiniteDimensionalEuclideanJorda - def natural_basis_space(self): + def matrix_space(self): """ - Return the natural basis space of this algebra, which is identical - to that of its superalgebra. + Return the matrix space of this algebra, which is identical to + that of its superalgebra. - This is correct "by definition," and avoids a mismatch when the - subalgebra is trivial (with no natural basis to infer anything - from) and the parent is not. + This is correct "by definition," and avoids a mismatch when + the subalgebra is trivial (with no matrix basis elements to + infer anything from) and the parent is not. """ - return self.superalgebra().natural_basis_space() + return self.superalgebra().matrix_space() def superalgebra(self): -- 2.44.2