X-Git-Url: http://gitweb.michael.orlitzky.com/?p=sage.d.git;a=blobdiff_plain;f=mjo%2Feja%2Feja_algebra.py;h=5bf597565b2ae292032c3f0a532763d7889cd2a8;hp=799490044dbb4d0834577f435afd2dbc89429baf;hb=db1f7761ebf564221669137ae07476ea45d82a2c;hpb=78bee5c30c1cd2828d9834fc7d652db21331d4fe diff --git a/mjo/eja/eja_algebra.py b/mjo/eja/eja_algebra.py index 7994900..5bf5975 100644 --- a/mjo/eja/eja_algebra.py +++ b/mjo/eja/eja_algebra.py @@ -64,8 +64,7 @@ from itertools import repeat from sage.algebras.quatalg.quaternion_algebra import QuaternionAlgebra from sage.categories.magmatic_algebras import MagmaticAlgebras from sage.categories.sets_cat import cartesian_product -from sage.combinat.free_module import (CombinatorialFreeModule, - CombinatorialFreeModule_CartesianProduct) +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 @@ -142,7 +141,7 @@ class FiniteDimensionalEJA(CombinatorialFreeModule): cartesian_product=False, check_field=True, check_axioms=True, - prefix='e'): + prefix="b"): n = len(basis) @@ -153,11 +152,6 @@ class FiniteDimensionalEJA(CombinatorialFreeModule): # we've specified a real embedding. raise ValueError("scalar field is not real") - from mjo.eja.eja_utils import _change_ring - # If the basis given to us wasn't over the field that it's - # supposed to be over, fix that. Or, you know, crash. - basis = tuple( _change_ring(b, field) for b in basis ) - if check_axioms: # Check commutativity of the Jordan and inner-products. # This has to be done before we build the multiplication @@ -211,7 +205,7 @@ class FiniteDimensionalEJA(CombinatorialFreeModule): # ambient vector space V that our (vectorized) basis lives in, # as well as a subspace W of V spanned by those (vectorized) # basis elements. The W-coordinates are the coefficients that - # we see in things like x = 1*e1 + 2*e2. + # we see in things like x = 1*b1 + 2*b2. vector_basis = basis degree = 0 @@ -338,16 +332,16 @@ class FiniteDimensionalEJA(CombinatorialFreeModule): sage: set_random_seed() sage: J = random_eja() sage: n = J.dimension() - sage: ei = J.zero() - sage: ej = J.zero() - sage: ei_ej = J.zero()*J.zero() + sage: bi = J.zero() + sage: bj = J.zero() + sage: bi_bj = J.zero()*J.zero() sage: if n > 0: ....: i = ZZ.random_element(n) ....: j = ZZ.random_element(n) - ....: ei = J.monomial(i) - ....: ej = J.monomial(j) - ....: ei_ej = J.product_on_basis(i,j) - sage: ei*ej == ei_ej + ....: bi = J.monomial(i) + ....: bj = J.monomial(j) + ....: bi_bj = J.product_on_basis(i,j) + sage: bi*bj == bi_bj True """ @@ -619,7 +613,7 @@ class FiniteDimensionalEJA(CombinatorialFreeModule): sage: J2 = RealSymmetricEJA(2) sage: J = cartesian_product([J1,J2]) sage: J( (J1.matrix_basis()[1], J2.matrix_basis()[2]) ) - e1 + e5 + b1 + b5 TESTS: @@ -894,15 +888,15 @@ class FiniteDimensionalEJA(CombinatorialFreeModule): sage: J = JordanSpinEJA(4) sage: J.multiplication_table() +----++----+----+----+----+ - | * || e0 | e1 | e2 | e3 | + | * || b0 | b1 | b2 | b3 | +====++====+====+====+====+ - | e0 || e0 | e1 | e2 | e3 | + | b0 || b0 | b1 | b2 | b3 | +----++----+----+----+----+ - | e1 || e1 | e0 | 0 | 0 | + | b1 || b1 | b0 | 0 | 0 | +----++----+----+----+----+ - | e2 || e2 | 0 | e0 | 0 | + | b2 || b2 | 0 | b0 | 0 | +----++----+----+----+----+ - | e3 || e3 | 0 | 0 | e0 | + | b3 || b3 | 0 | 0 | b0 | +----++----+----+----+----+ """ @@ -956,7 +950,7 @@ class FiniteDimensionalEJA(CombinatorialFreeModule): sage: J = RealSymmetricEJA(2) sage: J.basis() - Finite family {0: e0, 1: e1, 2: e2} + Finite family {0: b0, 1: b1, 2: b2} sage: J.matrix_basis() ( [1 0] [ 0 0.7071067811865475?] [0 0] @@ -967,7 +961,7 @@ class FiniteDimensionalEJA(CombinatorialFreeModule): sage: J = JordanSpinEJA(2) sage: J.basis() - Finite family {0: e0, 1: e1} + Finite family {0: b0, 1: b1} sage: J.matrix_basis() ( [1] [0] @@ -1049,20 +1043,20 @@ class FiniteDimensionalEJA(CombinatorialFreeModule): sage: J = HadamardEJA(5) sage: J.one() - e0 + e1 + e2 + e3 + e4 + b0 + b1 + b2 + b3 + b4 The unit element in the Hadamard EJA is inherited in the subalgebras generated by its elements:: sage: J = HadamardEJA(5) sage: J.one() - e0 + e1 + e2 + e3 + e4 + b0 + b1 + b2 + b3 + b4 sage: x = sum(J.gens()) sage: A = x.subalgebra_generated_by(orthonormalize=False) sage: A.one() - f0 + c0 sage: A.one().superalgebra_element() - e0 + e1 + e2 + e3 + e4 + b0 + b1 + b2 + b3 + b4 TESTS: @@ -1731,6 +1725,21 @@ class ConcreteEJA(RationalBasisEJA): class MatrixEJA: + @staticmethod + def jordan_product(X,Y): + return (X*Y + Y*X)/2 + + @staticmethod + def trace_inner_product(X,Y): + r""" + A trace inner-product for matrices that aren't embedded in the + reals. + """ + # We take the norm (absolute value) because Octonions() isn't + # smart enough yet to coerce its one() into the base field. + return (X*Y).trace().abs() + +class RealEmbeddedMatrixEJA(MatrixEJA): @staticmethod def dimension_over_reals(): r""" @@ -1776,9 +1785,6 @@ class MatrixEJA: raise ValueError("the matrix 'M' must be a real embedding") return M - @staticmethod - def jordan_product(X,Y): - return (X*Y + Y*X)/2 @classmethod def trace_inner_product(cls,X,Y): @@ -1787,29 +1793,11 @@ class MatrixEJA: SETUP:: - sage: from mjo.eja.eja_algebra import (RealSymmetricEJA, - ....: ComplexHermitianEJA, + sage: from mjo.eja.eja_algebra import (ComplexHermitianEJA, ....: QuaternionHermitianEJA) EXAMPLES:: - This gives the same answer as it would if we computed the trace - from the unembedded (original) matrices:: - - sage: set_random_seed() - sage: J = RealSymmetricEJA.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() - sage: actual = J.trace_inner_product(Xe,Ye) - sage: actual == expected - True - - :: - sage: set_random_seed() sage: J = ComplexHermitianEJA.random_instance() sage: x,y = J.random_elements(2) @@ -1837,27 +1825,15 @@ class MatrixEJA: True """ - Xu = cls.real_unembed(X) - Yu = cls.real_unembed(Y) - tr = (Xu*Yu).trace() + # 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() - try: - # Works in QQ, AA, RDF, et cetera. - return tr.real() - except AttributeError: - # A quaternion doesn't have a real() method, but does - # have coefficient_tuple() method that returns the - # coefficients of 1, i, j, and k -- in that order. - return tr.coefficient_tuple()[0] - - -class RealMatrixEJA(MatrixEJA): - @staticmethod - def dimension_over_reals(): - return 1 - - -class RealSymmetricEJA(ConcreteEJA, RealMatrixEJA): +class RealSymmetricEJA(ConcreteEJA, MatrixEJA): """ The rank-n simple EJA consisting of real symmetric n-by-n matrices, the usual symmetric Jordan product, and the trace inner @@ -1870,13 +1846,13 @@ class RealSymmetricEJA(ConcreteEJA, RealMatrixEJA): EXAMPLES:: sage: J = RealSymmetricEJA(2) - sage: e0, e1, e2 = J.gens() - sage: e0*e0 - e0 - sage: e1*e1 - 1/2*e0 + 1/2*e2 - sage: e2*e2 - e2 + sage: b0, b1, b2 = J.gens() + sage: b0*b0 + b0 + sage: b1*b1 + 1/2*b0 + 1/2*b2 + sage: b2*b2 + b2 In theory, our "field" can be any subfield of the reals:: @@ -1923,7 +1899,7 @@ class RealSymmetricEJA(ConcreteEJA, RealMatrixEJA): """ @classmethod - def _denormalized_basis(cls, n): + def _denormalized_basis(cls, n, field): """ Return a basis for the space of real symmetric n-by-n matrices. @@ -1935,7 +1911,7 @@ class RealSymmetricEJA(ConcreteEJA, RealMatrixEJA): sage: set_random_seed() sage: n = ZZ.random_element(1,5) - sage: B = RealSymmetricEJA._denormalized_basis(n) + sage: B = RealSymmetricEJA._denormalized_basis(n,ZZ) sage: all( M.is_symmetric() for M in B) True @@ -1945,7 +1921,7 @@ class RealSymmetricEJA(ConcreteEJA, RealMatrixEJA): S = [] for i in range(n): for j in range(i+1): - Eij = matrix(ZZ, n, lambda k,l: k==i and l==j) + Eij = matrix(field, n, lambda k,l: k==i and l==j) if i == j: Sij = Eij else: @@ -1966,7 +1942,7 @@ class RealSymmetricEJA(ConcreteEJA, RealMatrixEJA): n = ZZ.random_element(cls._max_random_instance_size() + 1) return cls(n, **kwargs) - def __init__(self, n, **kwargs): + def __init__(self, n, field=AA, **kwargs): # We know this is a valid EJA, but will double-check # if the user passes check_axioms=True. if "check_axioms" not in kwargs: kwargs["check_axioms"] = False @@ -1975,9 +1951,10 @@ class RealSymmetricEJA(ConcreteEJA, RealMatrixEJA): if n <= 1: associative = True - super().__init__(self._denormalized_basis(n), + super().__init__(self._denormalized_basis(n,field), self.jordan_product, self.trace_inner_product, + field=field, associative=associative, **kwargs) @@ -1985,12 +1962,12 @@ class RealSymmetricEJA(ConcreteEJA, RealMatrixEJA): # 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)) -class ComplexMatrixEJA(MatrixEJA): +class ComplexMatrixEJA(RealEmbeddedMatrixEJA): # A manual dictionary-cache for the complex_extension() method, # since apparently @classmethods can't also be @cached_methods. _complex_extension = {} @@ -2197,7 +2174,7 @@ class ComplexHermitianEJA(ConcreteEJA, ComplexMatrixEJA): """ @classmethod - def _denormalized_basis(cls, n): + def _denormalized_basis(cls, n, field): """ Returns a basis for the space of complex Hermitian n-by-n matrices. @@ -2215,15 +2192,14 @@ class ComplexHermitianEJA(ConcreteEJA, ComplexMatrixEJA): sage: set_random_seed() sage: n = ZZ.random_element(1,5) - sage: B = ComplexHermitianEJA._denormalized_basis(n) + sage: B = ComplexHermitianEJA._denormalized_basis(n,ZZ) sage: all( M.is_symmetric() for M in B) True """ - field = ZZ - R = PolynomialRing(field, 'z') + R = PolynomialRing(ZZ, 'z') z = R.gen() - F = field.extension(z**2 + 1, 'I') + F = ZZ.extension(z**2 + 1, 'I') I = F.gen(1) # This is like the symmetric case, but we need to be careful: @@ -2254,12 +2230,12 @@ class ComplexHermitianEJA(ConcreteEJA, ComplexMatrixEJA): # "erase" E_ij Eij[i,j] = 0 - # Since we embedded these, we can drop back to the "field" that we - # started with instead of the complex extension "F". + # 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 ) - def __init__(self, n, **kwargs): + def __init__(self, n, field=AA, **kwargs): # We know this is a valid EJA, but will double-check # if the user passes check_axioms=True. if "check_axioms" not in kwargs: kwargs["check_axioms"] = False @@ -2268,9 +2244,10 @@ class ComplexHermitianEJA(ConcreteEJA, ComplexMatrixEJA): if n <= 1: associative = True - super().__init__(self._denormalized_basis(n), + super().__init__(self._denormalized_basis(n,field), self.jordan_product, self.trace_inner_product, + field=field, associative=associative, **kwargs) # TODO: this could be factored out somehow, but is left here @@ -2292,7 +2269,7 @@ class ComplexHermitianEJA(ConcreteEJA, ComplexMatrixEJA): n = ZZ.random_element(cls._max_random_instance_size() + 1) return cls(n, **kwargs) -class QuaternionMatrixEJA(MatrixEJA): +class QuaternionMatrixEJA(RealEmbeddedMatrixEJA): # A manual dictionary-cache for the quaternion_extension() method, # since apparently @classmethods can't also be @cached_methods. @@ -2498,7 +2475,7 @@ class QuaternionHermitianEJA(ConcreteEJA, QuaternionMatrixEJA): """ @classmethod - def _denormalized_basis(cls, n): + def _denormalized_basis(cls, n, field): """ Returns a basis for the space of quaternion Hermitian n-by-n matrices. @@ -2516,12 +2493,11 @@ class QuaternionHermitianEJA(ConcreteEJA, QuaternionMatrixEJA): sage: set_random_seed() sage: n = ZZ.random_element(1,5) - sage: B = QuaternionHermitianEJA._denormalized_basis(n) + sage: B = QuaternionHermitianEJA._denormalized_basis(n,ZZ) sage: all( M.is_symmetric() for M in B ) True """ - field = ZZ Q = QuaternionAlgebra(QQ,-1,-1) I,J,K = Q.gens() @@ -2565,12 +2541,12 @@ class QuaternionHermitianEJA(ConcreteEJA, QuaternionMatrixEJA): # "erase" E_ij Eij[i,j] = 0 - # Since we embedded these, we can drop back to the "field" that we - # started with instead of the quaternion algebra "Q". + # Since we embedded the entries, we can drop back to the + # desired real "field" instead of the quaternion algebra "Q". return tuple( s.change_ring(field) for s in S ) - def __init__(self, n, **kwargs): + def __init__(self, n, field=AA, **kwargs): # We know this is a valid EJA, but will double-check # if the user passes check_axioms=True. if "check_axioms" not in kwargs: kwargs["check_axioms"] = False @@ -2579,9 +2555,10 @@ class QuaternionHermitianEJA(ConcreteEJA, QuaternionMatrixEJA): if n <= 1: associative = True - super().__init__(self._denormalized_basis(n), + super().__init__(self._denormalized_basis(n,field), self.jordan_product, self.trace_inner_product, + field=field, associative=associative, **kwargs) @@ -2627,19 +2604,19 @@ class HadamardEJA(ConcreteEJA): This multiplication table can be verified by hand:: sage: J = HadamardEJA(3) - sage: e0,e1,e2 = J.gens() - sage: e0*e0 - e0 - sage: e0*e1 + sage: b0,b1,b2 = J.gens() + sage: b0*b0 + b0 + sage: b0*b1 0 - sage: e0*e2 + sage: b0*b2 0 - sage: e1*e1 - e1 - sage: e1*e2 + sage: b1*b1 + b1 + sage: b1*b2 0 - sage: e2*e2 - e2 + sage: b2*b2 + b2 TESTS: @@ -2649,7 +2626,7 @@ class HadamardEJA(ConcreteEJA): (r0, r1, r2) """ - def __init__(self, n, **kwargs): + def __init__(self, n, field=AA, **kwargs): if n == 0: jordan_product = lambda x,y: x inner_product = lambda x,y: x @@ -2670,10 +2647,12 @@ class HadamardEJA(ConcreteEJA): if "orthonormalize" not in kwargs: kwargs["orthonormalize"] = False if "check_axioms" not in kwargs: kwargs["check_axioms"] = False - column_basis = tuple( b.column() for b in FreeModule(ZZ, n).basis() ) + column_basis = tuple( b.column() + for b in FreeModule(field, n).basis() ) super().__init__(column_basis, jordan_product, inner_product, + field=field, associative=True, **kwargs) self.rank.set_cache(n) @@ -2781,7 +2760,7 @@ class BilinearFormEJA(ConcreteEJA): True """ - def __init__(self, B, **kwargs): + def __init__(self, B, field=AA, **kwargs): # The matrix "B" is supplied by the user in most cases, # so it makes sense to check whether or not its positive- # definite unless we are specifically asked not to... @@ -2809,7 +2788,8 @@ class BilinearFormEJA(ConcreteEJA): return P([z0] + zbar.list()) n = B.nrows() - column_basis = tuple( b.column() for b in FreeModule(ZZ, n).basis() ) + column_basis = tuple( b.column() + for b in FreeModule(field, n).basis() ) # TODO: I haven't actually checked this, but it seems legit. associative = False @@ -2819,6 +2799,7 @@ class BilinearFormEJA(ConcreteEJA): super().__init__(column_basis, jordan_product, inner_product, + field=field, associative=associative, **kwargs) @@ -2880,20 +2861,20 @@ class JordanSpinEJA(BilinearFormEJA): This multiplication table can be verified by hand:: sage: J = JordanSpinEJA(4) - sage: e0,e1,e2,e3 = J.gens() - sage: e0*e0 - e0 - sage: e0*e1 - e1 - sage: e0*e2 - e2 - sage: e0*e3 - e3 - sage: e1*e2 + sage: b0,b1,b2,b3 = J.gens() + sage: b0*b0 + b0 + sage: b0*b1 + b1 + sage: b0*b2 + b2 + sage: b0*b3 + b3 + sage: b1*b2 0 - sage: e1*e3 + sage: b1*b3 0 - sage: e2*e3 + sage: b2*b3 0 We can change the generator prefix:: @@ -2914,7 +2895,7 @@ class JordanSpinEJA(BilinearFormEJA): True """ - def __init__(self, n, **kwargs): + def __init__(self, n, *args, **kwargs): # This is a special case of the BilinearFormEJA with the # identity matrix as its bilinear form. B = matrix.identity(ZZ, n) @@ -2925,7 +2906,7 @@ class JordanSpinEJA(BilinearFormEJA): # But also don't pass check_field=False here, because the user # can pass in a field! - super().__init__(B, **kwargs) + super().__init__(B, *args, **kwargs) @staticmethod def _max_random_instance_size(): @@ -3105,23 +3086,23 @@ class CartesianProductEJA(FiniteDimensionalEJA): sage: J = cartesian_product([J1,cartesian_product([J2,J3])]) sage: J.multiplication_table() +----++----+----+----+ - | * || e0 | e1 | e2 | + | * || b0 | b1 | b2 | +====++====+====+====+ - | e0 || e0 | 0 | 0 | + | b0 || b0 | 0 | 0 | +----++----+----+----+ - | e1 || 0 | e1 | 0 | + | b1 || 0 | b1 | 0 | +----++----+----+----+ - | e2 || 0 | 0 | e2 | + | b2 || 0 | 0 | b2 | +----++----+----+----+ sage: HadamardEJA(3).multiplication_table() +----++----+----+----+ - | * || e0 | e1 | e2 | + | * || b0 | b1 | b2 | +====++====+====+====+ - | e0 || e0 | 0 | 0 | + | b0 || b0 | 0 | 0 | +----++----+----+----+ - | e1 || 0 | e1 | 0 | + | b1 || 0 | b1 | 0 | +----++----+----+----+ - | e2 || 0 | 0 | e2 | + | b2 || 0 | 0 | b2 | +----++----+----+----+ TESTS: @@ -3480,17 +3461,15 @@ class RationalBasisCartesianProductEJA(CartesianProductEJA, RationalBasisEJA.CartesianProduct = RationalBasisCartesianProductEJA -random_eja = ConcreteEJA.random_instance - -# def random_eja(*args, **kwargs): -# J1 = ConcreteEJA.random_instance(*args, **kwargs) - -# # This might make Cartesian products appear roughly as often as -# # any other ConcreteEJA. -# if ZZ.random_element(len(ConcreteEJA.__subclasses__()) + 1) == 0: -# # Use random_eja() again so we can get more than two factors. -# J2 = random_eja(*args, **kwargs) -# J = cartesian_product([J1,J2]) -# return J -# else: -# return J1 +def random_eja(*args, **kwargs): + J1 = ConcreteEJA.random_instance(*args, **kwargs) + + # This might make Cartesian products appear roughly as often as + # any other ConcreteEJA. + if ZZ.random_element(len(ConcreteEJA.__subclasses__()) + 1) == 0: + # Use random_eja() again so we can get more than two factors. + J2 = random_eja(*args, **kwargs) + J = cartesian_product([J1,J2]) + return J + else: + return J1