From 667e0df9c07589c03616ad8cf42eebe5c86de50b Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Wed, 24 Feb 2021 18:54:10 -0500 Subject: [PATCH] eja: handle tuples in parent algebras rather than in subclasses. This is "necessary" because we won't always have a Cartesian product algebra when our basis consists of tuples. Particularly in element-subalgebras of Cartesian product algebras. It leads to more special-casing, but whatever. Someday SageMath will know that both matrix spaces and Cartesian products of vector spaces are themselves vector spaces. --- mjo/eja/eja_algebra.py | 110 ++++++++++++++++---------------------- mjo/eja/eja_element.py | 64 +++++++++------------- mjo/eja/eja_subalgebra.py | 29 +--------- mjo/eja/eja_utils.py | 16 ++++++ 4 files changed, 89 insertions(+), 130 deletions(-) diff --git a/mjo/eja/eja_algebra.py b/mjo/eja/eja_algebra.py index 9ba146b..8118505 100644 --- a/mjo/eja/eja_algebra.py +++ b/mjo/eja/eja_algebra.py @@ -31,10 +31,9 @@ from sage.modules.free_module import FreeModule, VectorSpace from sage.rings.all import (ZZ, QQ, AA, QQbar, RR, RLF, CLF, PolynomialRing, QuadraticField) -from mjo.eja.eja_element import (CartesianProductEJAElement, - FiniteDimensionalEJAElement) +from mjo.eja.eja_element import FiniteDimensionalEJAElement from mjo.eja.eja_operator import FiniteDimensionalEJAOperator -from mjo.eja.eja_utils import _mat2vec +from mjo.eja.eja_utils import _all2list, _mat2vec class FiniteDimensionalEJA(CombinatorialFreeModule): r""" @@ -76,6 +75,18 @@ class FiniteDimensionalEJA(CombinatorialFreeModule): check_axioms=True, prefix='e'): + # Keep track of whether or not the matrix basis consists of + # tuples, since we need special cases for them damned near + # everywhere. This is INDEPENDENT of whether or not the + # algebra is a cartesian product, since a subalgebra of a + # cartesian product will have a basis of tuples, but will not + # in general itself be a cartesian product algebra. + self._matrix_basis_is_cartesian = False + n = len(basis) + if n > 0: + if hasattr(basis[0], 'cartesian_factors'): + self._matrix_basis_is_cartesian = True + if check_field: if not field.is_subring(RR): # Note: this does return true for the real algebraic @@ -89,7 +100,13 @@ class FiniteDimensionalEJA(CombinatorialFreeModule): # The field for a cartesian product algebra comes from one # of its factors and is the same for all factors, so # there's no need to "reapply" it on product algebras. - basis = tuple( b.change_ring(field) for b in basis ) + if self._matrix_basis_is_cartesian: + # OK since if n == 0, the basis does not consist of tuples. + P = basis[0].parent() + basis = tuple( P(tuple(b_i.change_ring(field) for b_i in b)) + for b in basis ) + else: + basis = tuple( b.change_ring(field) for b in basis ) if check_axioms: @@ -118,7 +135,6 @@ class FiniteDimensionalEJA(CombinatorialFreeModule): # Call the superclass constructor so that we can use its from_vector() # method to build our multiplication table. - n = len(basis) CombinatorialFreeModule.__init__(self, field, range(n), @@ -133,17 +149,9 @@ class FiniteDimensionalEJA(CombinatorialFreeModule): # we see in things like x = 1*e1 + 2*e2. vector_basis = basis - def flatten(b): - # flatten a vector, matrix, or cartesian product of those - # things into a long list. - if cartesian_product: - return sum(( b_i.list() for b_i in b ), []) - else: - return b.list() - degree = 0 if n > 0: - degree = len(flatten(basis[0])) + degree = len(_all2list(basis[0])) # Build an ambient space that fits our matrix basis when # written out as "long vectors." @@ -157,7 +165,7 @@ class FiniteDimensionalEJA(CombinatorialFreeModule): # Save a copy of the un-orthonormalized basis for later. # Convert it to ambient V (vector) coordinates while we're # at it, because we'd have to do it later anyway. - deortho_vector_basis = tuple( V(flatten(b)) for b in basis ) + deortho_vector_basis = tuple( V(_all2list(b)) for b in basis ) from mjo.eja.eja_utils import gram_schmidt basis = tuple(gram_schmidt(basis, inner_product)) @@ -169,7 +177,7 @@ class FiniteDimensionalEJA(CombinatorialFreeModule): # Now create the vector space for the algebra, which will have # its own set of non-ambient coordinates (in terms of the # supplied basis). - vector_basis = tuple( V(flatten(b)) for b in basis ) + vector_basis = tuple( V(_all2list(b)) for b in basis ) W = V.span_of_basis( vector_basis, check=check_axioms) if orthonormalize: @@ -201,7 +209,7 @@ class FiniteDimensionalEJA(CombinatorialFreeModule): # The jordan product returns a matrixy answer, so we # have to convert it to the algebra coordinates. elt = jordan_product(q_i, q_j) - elt = W.coordinate_vector(V(flatten(elt))) + elt = W.coordinate_vector(V(_all2list(elt))) self._multiplication_table[i][j] = self.from_vector(elt) if not orthonormalize: @@ -414,6 +422,15 @@ class FiniteDimensionalEJA(CombinatorialFreeModule): ... ValueError: not an element of this algebra + Tuples work as well, provided that the matrix basis for the + algebra consists of them:: + + sage: J1 = HadamardEJA(3) + sage: J2 = RealSymmetricEJA(2) + sage: J = cartesian_product([J1,J2]) + sage: J( (J1.matrix_basis()[1], J2.matrix_basis()[2]) ) + e(0, 1) + e(1, 2) + TESTS: Ensure that we can convert any element of the two non-matrix @@ -459,14 +476,20 @@ class FiniteDimensionalEJA(CombinatorialFreeModule): # closure whereas the base ring of the 3-by-3 identity matrix # could be QQ instead of QQbar. # + # And, we also have to handle Cartesian product bases (when + # the matric basis consists of tuples) here. The "good news" + # is that we're already converting everything to long vectors, + # and that strategy works for tuples as well. + # # We pass check=False because the matrix basis is "guaranteed" # to be linearly independent... right? Ha ha. - V = VectorSpace(self.base_ring(), elt.nrows()*elt.ncols()) - W = V.span_of_basis( (_mat2vec(s) for s in self.matrix_basis()), + elt = _all2list(elt) + V = VectorSpace(self.base_ring(), len(elt)) + W = V.span_of_basis( (V(_all2list(s)) for s in self.matrix_basis()), check=False) try: - coords = W.coordinate_vector(_mat2vec(elt)) + coords = W.coordinate_vector(V(elt)) except ArithmeticError: # vector is not in free module raise ValueError(msg) @@ -1267,9 +1290,6 @@ class FiniteDimensionalEJA(CombinatorialFreeModule): def subalgebra(self, basis, **kwargs): r""" Create a subalgebra of this algebra from the given basis. - - This is a simple wrapper around a subalgebra class constructor - that can be overridden in subclasses. """ from mjo.eja.eja_subalgebra import FiniteDimensionalEJASubalgebra return FiniteDimensionalEJASubalgebra(self, basis, **kwargs) @@ -1293,7 +1313,6 @@ class FiniteDimensionalEJA(CombinatorialFreeModule): return self.zero().to_vector().parent().ambient_vector_space() - Element = FiniteDimensionalEJAElement class RationalBasisEJA(FiniteDimensionalEJA): r""" @@ -2844,6 +2863,9 @@ class CartesianProductEJA(CombinatorialFreeModule_CartesianProduct, True """ + Element = FiniteDimensionalEJAElement + + def __init__(self, algebras, **kwargs): CombinatorialFreeModule_CartesianProduct.__init__(self, algebras, @@ -3107,46 +3129,6 @@ class CartesianProductEJA(CombinatorialFreeModule_CartesianProduct, return FiniteDimensionalEJAOperator(Ji,self,Ei.matrix()) - def _element_constructor_(self, elt): - r""" - Construct an element of this algebra from an ordered tuple. - - We just apply the element constructor from each of our factors - to the corresponding component of the tuple, and package up - the result. - - SETUP:: - - sage: from mjo.eja.eja_algebra import (HadamardEJA, - ....: RealSymmetricEJA) - - EXAMPLES:: - - sage: J1 = HadamardEJA(3) - sage: J2 = RealSymmetricEJA(2) - sage: J = cartesian_product([J1,J2]) - sage: J( (J1.matrix_basis()[1], J2.matrix_basis()[2]) ) - e(0, 1) + e(1, 2) - """ - m = len(self.cartesian_factors()) - try: - z = tuple( self.cartesian_factors()[i](elt[i]) for i in range(m) ) - return self._cartesian_product_of_elements(z) - except: - raise ValueError("not an element of this algebra") - - def subalgebra(self, basis, **kwargs): - r""" - Create a subalgebra of this algebra from the given basis. - - This overrides the superclass method to use a special class - for Cartesian products. - """ - from mjo.eja.eja_subalgebra import CartesianProductEJASubalgebra - return CartesianProductEJASubalgebra(self, basis, **kwargs) - - Element = CartesianProductEJAElement - FiniteDimensionalEJA.CartesianProduct = CartesianProductEJA diff --git a/mjo/eja/eja_element.py b/mjo/eja/eja_element.py index 42e5782..9044860 100644 --- a/mjo/eja/eja_element.py +++ b/mjo/eja/eja_element.py @@ -1107,14 +1107,35 @@ class FiniteDimensionalEJAElement(IndexedFreeModuleElement): [0 0 0 0 0 0 1 0] [0 0 0 0 0 0 0 1] + This also works in Cartesian product algebras:: + + sage: J1 = HadamardEJA(1) + sage: J2 = RealSymmetricEJA(2) + sage: J = cartesian_product([J1,J2]) + sage: x = sum(J.gens()) + sage: x.to_matrix()[0] + [1] + sage: x.to_matrix()[1] + [ 1 0.7071067811865475?] + [0.7071067811865475? 1] + """ 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()) ) + if self.parent()._matrix_basis_is_cartesian: + # Aaaaand linear combinations don't work in Cartesian + # product spaces, even though they provide a method + # with that name. + pairs = zip(B, self.to_vector()) + return sum( ( W(tuple(alpha*b_i for b_i in b)) + for (b,alpha) in pairs ), + W.zero()) + else: + # 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()) ) @@ -1615,38 +1636,3 @@ class FiniteDimensionalEJAElement(IndexedFreeModuleElement): """ return self.trace_inner_product(self).sqrt() - - - -class CartesianProductEJAElement(FiniteDimensionalEJAElement): - - def to_matrix(self): - r""" - SETUP:: - - sage: from mjo.eja.eja_algebra import (HadamardEJA, - ....: RealSymmetricEJA) - - EXAMPLES:: - - sage: J1 = HadamardEJA(1) - sage: J2 = RealSymmetricEJA(2) - sage: J = cartesian_product([J1,J2]) - sage: x = sum(J.gens()) - sage: x.to_matrix()[0] - [1] - sage: x.to_matrix()[1] - [ 1 0.7071067811865475?] - [0.7071067811865475? 1] - - """ - B = self.parent().matrix_basis() - W = self.parent().matrix_space() - - # Aaaaand linear combinations don't work in Cartesian - # product spaces, even though they provide a method - # with that name. - pairs = zip(B, self.to_vector()) - return sum( ( W(tuple(alpha*b_i for b_i in b)) - for (b,alpha) in pairs ), - W.zero()) diff --git a/mjo/eja/eja_subalgebra.py b/mjo/eja/eja_subalgebra.py index e2d12d2..3b8c67d 100644 --- a/mjo/eja/eja_subalgebra.py +++ b/mjo/eja/eja_subalgebra.py @@ -1,11 +1,7 @@ from sage.matrix.constructor import matrix -from sage.combinat.free_module import CombinatorialFreeModule_CartesianProduct - -from mjo.eja.eja_algebra import (CartesianProductEJA, - FiniteDimensionalEJA) -from mjo.eja.eja_element import (CartesianProductEJAElement, - FiniteDimensionalEJAElement) +from mjo.eja.eja_algebra import FiniteDimensionalEJA +from mjo.eja.eja_element import FiniteDimensionalEJAElement class FiniteDimensionalEJASubalgebraElement(FiniteDimensionalEJAElement): """ @@ -234,24 +230,3 @@ class FiniteDimensionalEJASubalgebra(FiniteDimensionalEJA): Element = FiniteDimensionalEJASubalgebraElement - - - -class CartesianProductEJASubalgebraElement(CartesianProductEJAElement, - FiniteDimensionalEJASubalgebraElement): - pass - -class CartesianProductEJASubalgebra(CartesianProductEJA, - FiniteDimensionalEJASubalgebra): - - def __init__(self, superalgebra, basis, **kwargs): - CombinatorialFreeModule_CartesianProduct.__init__(self, - superalgebra.cartesian_factors()) - FiniteDimensionalEJASubalgebra.__init__(self, - superalgebra, - basis, - cartesian_product=True, - **kwargs) - - - Element = CartesianProductEJASubalgebraElement diff --git a/mjo/eja/eja_utils.py b/mjo/eja/eja_utils.py index b6e0c7d..29edf5b 100644 --- a/mjo/eja/eja_utils.py +++ b/mjo/eja/eja_utils.py @@ -2,6 +2,22 @@ from sage.functions.other import sqrt from sage.matrix.constructor import matrix from sage.modules.free_module_element import vector +def _all2list(x): + r""" + Flatten a vector, matrix, or cartesian product of those things + into a long list. + """ + if hasattr(x, 'list'): + # Easy case... + return x.list() + if hasattr(x, 'cartesian_factors'): + # If it's a formal cartesian product space element, then + # we also know what to do... + return sum(( x_i.list() for x_i in x ), []) + else: + # But what if it's a tuple or something else? + return sum( map(_all2list,x), [] ) + def _mat2vec(m): return vector(m.base_ring(), m.list()) -- 2.43.2