From ebb0595a1c30afe9690d081672d8dfc88e90af74 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Tue, 9 Mar 2021 16:22:16 -0500 Subject: [PATCH] eja: optionally pass matrix space into FDEJA instead of guessing it. --- mjo/eja/eja_algebra.py | 130 +++++++++++++++++++++----------------- mjo/eja/eja_subalgebra.py | 14 +--- 2 files changed, 74 insertions(+), 70 deletions(-) diff --git a/mjo/eja/eja_algebra.py b/mjo/eja/eja_algebra.py index c6de223..4ee5aba 100644 --- a/mjo/eja/eja_algebra.py +++ b/mjo/eja/eja_algebra.py @@ -104,6 +104,11 @@ class FiniteDimensionalEJA(CombinatorialFreeModule): product. This will be applied to ``basis`` to compute an inner-product table (basically a matrix) for this algebra. + - ``matrix_space`` -- the space that your matrix basis lives in, + or ``None`` (the default). So long as your basis does not have + length zero you can omit this. But in trivial algebras, it is + required. + - ``field`` -- a subfield of the reals (default: ``AA``); the scalar field for the algebra. @@ -128,7 +133,6 @@ class FiniteDimensionalEJA(CombinatorialFreeModule): sage: basis = tuple(b.superalgebra_element() for b in A.basis()) sage: J.subalgebra(basis, orthonormalize=False).is_associative() True - """ Element = FiniteDimensionalEJAElement @@ -137,6 +141,7 @@ class FiniteDimensionalEJA(CombinatorialFreeModule): jordan_product, inner_product, field=AA, + matrix_space=None, orthonormalize=True, associative=None, cartesian_product=False, @@ -236,8 +241,14 @@ class FiniteDimensionalEJA(CombinatorialFreeModule): basis = tuple(gram_schmidt(basis, inner_product)) # Save the (possibly orthonormalized) matrix basis for - # later... + # later, as well as the space that its elements live in. + # In most cases we can deduce the matrix space, but when + # n == 0 (that is, there are no basis elements) we cannot. self._matrix_basis = basis + if matrix_space is None: + self._matrix_space = self._matrix_basis[0].parent() + else: + self._matrix_space = matrix_space # Now create the vector space for the algebra, which will have # its own set of non-ambient coordinates (in terms of the @@ -1030,10 +1041,7 @@ class FiniteDimensionalEJA(CombinatorialFreeModule): the scalar ring Rational Field """ - if self.is_trivial(): - return MatrixSpace(self.base_ring(), 0) - else: - return self.matrix_basis()[0].parent() + return self._matrix_space @cached_method @@ -1610,6 +1618,7 @@ class RationalBasisEJA(FiniteDimensionalEJA): jordan_product, inner_product, field=QQ, + matrix_space=self.matrix_space(), associative=self.is_associative(), orthonormalize=False, check_field=False, @@ -1966,16 +1975,14 @@ class RealSymmetricEJA(RationalBasisEJA, ConcreteEJA, MatrixEJA): self.jordan_product, self.trace_inner_product, field=field, + matrix_space=A, **kwargs) # TODO: this could be factored out somehow, but is left here # because the MatrixEJA is not presently a subclass of the # FDEJA class that defines rank() and one(). self.rank.set_cache(n) - if n == 0: - self.one.set_cache( self.zero() ) - else: - self.one.set_cache(self(A.one())) + self.one.set_cache( self(A.one()) ) @@ -1992,13 +1999,28 @@ class ComplexHermitianEJA(RationalBasisEJA, ConcreteEJA, MatrixEJA): EXAMPLES: - In theory, our "field" can be any subfield of the reals:: + In theory, our "field" can be any subfield of the reals, but we + can't use inexact real fields at the moment because SageMath + doesn't know how to convert their elements into complex numbers, + or even into algebraic reals:: - sage: ComplexHermitianEJA(2, field=RDF, check_axioms=True) - Euclidean Jordan algebra of dimension 4 over Real Double Field - sage: ComplexHermitianEJA(2, field=RR, check_axioms=True) - Euclidean Jordan algebra of dimension 4 over Real Field with - 53 bits of precision + sage: QQbar(RDF(1)) + Traceback (most recent call last): + ... + TypeError: Illegal initializer for algebraic number + sage: AA(RR(1)) + Traceback (most recent call last): + ... + TypeError: Illegal initializer for algebraic number + + This causes the following error when we try to scale a matrix of + complex numbers by an inexact real number:: + + sage: ComplexHermitianEJA(2,field=RR) + Traceback (most recent call last): + ... + TypeError: Unable to coerce entries (=(1.00000000000000, + -0.000000000000000)) to coefficients in Algebraic Real Field TESTS: @@ -2034,7 +2056,6 @@ class ComplexHermitianEJA(RationalBasisEJA, ConcreteEJA, MatrixEJA): sage: ComplexHermitianEJA(0) Euclidean Jordan algebra of dimension 0 over Algebraic Real Field - """ def __init__(self, n, field=AA, **kwargs): # We know this is a valid EJA, but will double-check @@ -2047,16 +2068,15 @@ class ComplexHermitianEJA(RationalBasisEJA, ConcreteEJA, MatrixEJA): self.jordan_product, self.trace_inner_product, field=field, + matrix_space=A, **kwargs) # TODO: this could be factored out somehow, but is left here # because the MatrixEJA is not presently a subclass of the # FDEJA class that defines rank() and one(). self.rank.set_cache(n) - if n == 0: - self.one.set_cache( self.zero() ) - else: - self.one.set_cache(self(A.one())) + self.one.set_cache( self(A.one()) ) + @staticmethod def _max_random_instance_size(): @@ -2139,16 +2159,14 @@ class QuaternionHermitianEJA(RationalBasisEJA, ConcreteEJA, MatrixEJA): self.jordan_product, self.trace_inner_product, field=field, + matrix_space=A, **kwargs) # TODO: this could be factored out somehow, but is left here # because the MatrixEJA is not presently a subclass of the # FDEJA class that defines rank() and one(). self.rank.set_cache(n) - if n == 0: - self.one.set_cache( self.zero() ) - else: - self.one.set_cache(self(A.one())) + self.one.set_cache( self(A.one()) ) @staticmethod @@ -2283,16 +2301,14 @@ class OctonionHermitianEJA(RationalBasisEJA, ConcreteEJA, MatrixEJA): self.jordan_product, self.trace_inner_product, field=field, + matrix_space=A, **kwargs) # TODO: this could be factored out somehow, but is left here # because the MatrixEJA is not presently a subclass of the # FDEJA class that defines rank() and one(). self.rank.set_cache(n) - if n == 0: - self.one.set_cache( self.zero() ) - else: - self.one.set_cache(self(A.one())) + self.one.set_cache( self(A.one()) ) class AlbertEJA(OctonionHermitianEJA): @@ -2357,13 +2373,14 @@ class HadamardEJA(RationalBasisEJA, ConcreteEJA): (r0, r1, r2) """ def __init__(self, n, field=AA, **kwargs): + MS = MatrixSpace(field, n, 1) + if n == 0: jordan_product = lambda x,y: x inner_product = lambda x,y: x else: def jordan_product(x,y): - P = x.parent() - return P( xi*yi for (xi,yi) in zip(x,y) ) + return MS( xi*yi for (xi,yi) in zip(x,y) ) def inner_product(x,y): return (x.T*y)[0,0] @@ -2377,20 +2394,17 @@ class HadamardEJA(RationalBasisEJA, 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(field, n).basis() ) + column_basis = tuple( MS(b) for b in FreeModule(field, n).basis() ) super().__init__(column_basis, jordan_product, inner_product, field=field, + matrix_space=MS, associative=True, **kwargs) self.rank.set_cache(n) - if n == 0: - self.one.set_cache( self.zero() ) - else: - self.one.set_cache( sum(self.gens()) ) + self.one.set_cache( self.sum(self.gens()) ) @staticmethod def _max_random_instance_size(): @@ -2504,22 +2518,22 @@ class BilinearFormEJA(RationalBasisEJA, ConcreteEJA): # verify things, we'll skip the rest of the checks. if "check_axioms" not in kwargs: kwargs["check_axioms"] = False + n = B.nrows() + MS = MatrixSpace(field, n, 1) + def inner_product(x,y): return (y.T*B*x)[0,0] def jordan_product(x,y): - P = x.parent() x0 = x[0,0] xbar = x[1:,0] y0 = y[0,0] ybar = y[1:,0] z0 = inner_product(y,x) zbar = y0*xbar + x0*ybar - return P([z0] + zbar.list()) + return MS([z0] + zbar.list()) - n = B.nrows() - column_basis = tuple( b.column() - for b in FreeModule(field, n).basis() ) + column_basis = tuple( MS(b) for b in FreeModule(field, n).basis() ) # TODO: I haven't actually checked this, but it seems legit. associative = False @@ -2530,6 +2544,7 @@ class BilinearFormEJA(RationalBasisEJA, ConcreteEJA): jordan_product, inner_product, field=field, + matrix_space=MS, associative=associative, **kwargs) @@ -2537,7 +2552,6 @@ class BilinearFormEJA(RationalBasisEJA, ConcreteEJA): # one-dimensional ambient space (because the rank is bounded # by the ambient dimension). self.rank.set_cache(min(n,2)) - if n == 0: self.one.set_cache( self.zero() ) else: @@ -2685,10 +2699,11 @@ class TrivialEJA(RationalBasisEJA, ConcreteEJA): 0 """ - def __init__(self, **kwargs): + def __init__(self, field=AA, **kwargs): jordan_product = lambda x,y: x - inner_product = lambda x,y: 0 + inner_product = lambda x,y: field.zero() basis = () + MS = MatrixSpace(field,0) # New defaults for keyword arguments if "orthonormalize" not in kwargs: kwargs["orthonormalize"] = False @@ -2698,6 +2713,8 @@ class TrivialEJA(RationalBasisEJA, ConcreteEJA): jordan_product, inner_product, associative=True, + field=field, + matrix_space=MS, **kwargs) # The rank is zero using my definition, namely the dimension of the @@ -2875,7 +2892,14 @@ class CartesianProductEJA(FiniteDimensionalEJA): associative = all( f.is_associative() for f in factors ) - MS = self.matrix_space() + # Compute my matrix space. This category isn't perfect, but + # is good enough for what we need to do. + MS_cat = MagmaticAlgebras(field).FiniteDimensional().WithBasis() + MS_cat = MS_cat.Unital().CartesianProducts() + MS_factors = tuple( J.matrix_space() for J in factors ) + from sage.sets.cartesian_product import CartesianProduct + MS = CartesianProduct(MS_factors, MS_cat) + basis = [] zero = MS.zero() for i in range(m): @@ -2910,15 +2934,16 @@ class CartesianProductEJA(FiniteDimensionalEJA): jordan_product, inner_product, field=field, + matrix_space=MS, orthonormalize=False, associative=associative, cartesian_product=True, check_field=False, check_axioms=False) + self.rank.set_cache(sum(J.rank() for J in factors)) ones = tuple(J.one().to_matrix() for J in factors) self.one.set_cache(self(ones)) - self.rank.set_cache(sum(J.rank() for J in factors)) def cartesian_factors(self): # Copy/pasted from CombinatorialFreeModule_CartesianProduct. @@ -2993,16 +3018,7 @@ class CartesianProductEJA(FiniteDimensionalEJA): +----+ """ - scalars = self.cartesian_factor(0).base_ring() - - # This category isn't perfect, but is good enough for what we - # need to do. - cat = MagmaticAlgebras(scalars).FiniteDimensional().WithBasis() - cat = cat.Unital().CartesianProducts() - factors = tuple( J.matrix_space() for J in self.cartesian_factors() ) - - from sage.sets.cartesian_product import CartesianProduct - return CartesianProduct(factors, cat) + return super().matrix_space() @cached_method diff --git a/mjo/eja/eja_subalgebra.py b/mjo/eja/eja_subalgebra.py index 1b86d23..4458a7e 100644 --- a/mjo/eja/eja_subalgebra.py +++ b/mjo/eja/eja_subalgebra.py @@ -171,6 +171,7 @@ class FiniteDimensionalEJASubalgebra(FiniteDimensionalEJA): jordan_product, inner_product, field=field, + matrix_space=superalgebra.matrix_space(), prefix=prefix, **kwargs) @@ -213,19 +214,6 @@ class FiniteDimensionalEJASubalgebra(FiniteDimensionalEJA): return super()._element_constructor_(elt) - - def matrix_space(self): - """ - 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 matrix basis elements to - infer anything from) and the parent is not. - """ - return self.superalgebra().matrix_space() - - def superalgebra(self): """ Return the superalgebra that this algebra was generated from. -- 2.43.2