From bfc990cd48dc51f4185953fa209c2125d7e89a23 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sat, 24 Feb 2024 18:10:50 +0100 Subject: [PATCH 1/6] allow construction of tensor products of free modules --- src/sage/combinat/free_module.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/sage/combinat/free_module.py b/src/sage/combinat/free_module.py index 8a97fbdc796..35f30886d70 100644 --- a/src/sage/combinat/free_module.py +++ b/src/sage/combinat/free_module.py @@ -503,10 +503,13 @@ def change_ring(self, R): return self construction = self.construction() if construction is not None: - functor, _ = construction + functor, args = construction from sage.categories.pushout import VectorFunctor if isinstance(functor, VectorFunctor): return functor(R) + from sage.categories.tensor import TensorProductFunctor + if isinstance(functor, TensorProductFunctor): + return functor([f.change_ring(R) for f in args]) raise NotImplementedError('the method change_ring() has not yet been implemented') # For backwards compatibility From ad7d8fd84f2801f5e1fea55d672b04bfa15e3437 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sat, 24 Feb 2024 18:15:31 +0100 Subject: [PATCH 2/6] add doctest --- src/sage/combinat/free_module.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/sage/combinat/free_module.py b/src/sage/combinat/free_module.py index 35f30886d70..67f5b86237d 100644 --- a/src/sage/combinat/free_module.py +++ b/src/sage/combinat/free_module.py @@ -498,6 +498,11 @@ def change_ring(self, R): Free module generated by {'a', 'b', 'c'} over Rational Field sage: F_QQ.change_ring(ZZ) == F True + + sage: T = F.tensor(F); T + Free module generated by {'a', 'b', 'c'} over Integer Ring # Free module generated by {'a', 'b', 'c'} over Integer Ring + sage: T.change_ring(QQ) + Free module generated by {'a', 'b', 'c'} over Rational Field # Free module generated by {'a', 'b', 'c'} over Rational Field """ if R is self.base_ring(): return self From 845e37ffa40d89f3ee88c2f6a6c5ca56070e5591 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sun, 7 Apr 2024 21:56:23 +0200 Subject: [PATCH 3/6] break expected output of doctest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matthias Köppe --- src/sage/combinat/free_module.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/free_module.py b/src/sage/combinat/free_module.py index d70f3539cd4..eb5e8797084 100644 --- a/src/sage/combinat/free_module.py +++ b/src/sage/combinat/free_module.py @@ -500,9 +500,11 @@ def change_ring(self, R): True sage: T = F.tensor(F); T - Free module generated by {'a', 'b', 'c'} over Integer Ring # Free module generated by {'a', 'b', 'c'} over Integer Ring + Free module generated by {'a', 'b', 'c'} over Integer Ring + # Free module generated by {'a', 'b', 'c'} over Integer Ring sage: T.change_ring(QQ) - Free module generated by {'a', 'b', 'c'} over Rational Field # Free module generated by {'a', 'b', 'c'} over Rational Field + Free module generated by {'a', 'b', 'c'} over Rational Field + # Free module generated by {'a', 'b', 'c'} over Rational Field """ if R is self.base_ring(): return self From f50ac558928ef7029864466e705f79c98ed5547f Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 8 Apr 2024 10:52:11 +0200 Subject: [PATCH 4/6] remove whitespace --- src/sage/combinat/free_module.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/free_module.py b/src/sage/combinat/free_module.py index eb5e8797084..2f153396fc0 100644 --- a/src/sage/combinat/free_module.py +++ b/src/sage/combinat/free_module.py @@ -500,10 +500,10 @@ def change_ring(self, R): True sage: T = F.tensor(F); T - Free module generated by {'a', 'b', 'c'} over Integer Ring + Free module generated by {'a', 'b', 'c'} over Integer Ring # Free module generated by {'a', 'b', 'c'} over Integer Ring sage: T.change_ring(QQ) - Free module generated by {'a', 'b', 'c'} over Rational Field + Free module generated by {'a', 'b', 'c'} over Rational Field # Free module generated by {'a', 'b', 'c'} over Rational Field """ if R is self.base_ring(): From ddcc8ec96a7726765edcd81d0420c060a3c7a684 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Tue, 9 Apr 2024 08:52:02 +0200 Subject: [PATCH 5/6] also allow Cartesian products --- src/sage/combinat/free_module.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/sage/combinat/free_module.py b/src/sage/combinat/free_module.py index 2f153396fc0..5daabd153fb 100644 --- a/src/sage/combinat/free_module.py +++ b/src/sage/combinat/free_module.py @@ -505,6 +505,15 @@ def change_ring(self, R): sage: T.change_ring(QQ) Free module generated by {'a', 'b', 'c'} over Rational Field # Free module generated by {'a', 'b', 'c'} over Rational Field + + sage: G = CombinatorialFreeModule(ZZ, ['x','y']); G + Free module generated by {'x', 'y'} over Integer Ring + sage: T = F.cartesian_product(G); T + Free module generated by {'a', 'b', 'c'} over Integer Ring + (+) Free module generated by {'x', 'y'} over Integer Ring + sage: T.change_ring(QQ) + Free module generated by {'a', 'b', 'c'} over Rational Field + (+) Free module generated by {'x', 'y'} over Rational Field """ if R is self.base_ring(): return self @@ -515,7 +524,8 @@ def change_ring(self, R): if isinstance(functor, VectorFunctor): return functor(R) from sage.categories.tensor import TensorProductFunctor - if isinstance(functor, TensorProductFunctor): + from sage.categories.cartesian_product import CartesianProductFunctor + if isinstance(functor, (TensorProductFunctor, CartesianProductFunctor)): return functor([f.change_ring(R) for f in args]) raise NotImplementedError('the method change_ring() has not yet been implemented') From c8e1693c180c3a99f67907cbca91e20fda50b455 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Tue, 9 Apr 2024 08:58:43 +0200 Subject: [PATCH 6/6] codestyle fixes --- src/sage/combinat/free_module.py | 790 ++++++++++++++++--------------- 1 file changed, 397 insertions(+), 393 deletions(-) diff --git a/src/sage/combinat/free_module.py b/src/sage/combinat/free_module.py index 5daabd153fb..12bbd4385db 100644 --- a/src/sage/combinat/free_module.py +++ b/src/sage/combinat/free_module.py @@ -1023,10 +1023,10 @@ def from_vector(self, vector, order=None, coerce=True): if order is None: order = self.get_order() if not coerce or vector.base_ring() is self.base_ring(): - return self._from_dict({order[i]: c for i,c in vector.items()}, + return self._from_dict({order[i]: c for i, c in vector.items()}, coerce=False) R = self.base_ring() - return self._from_dict({order[i]: R(c) for i,c in vector.items() if R(c)}, + return self._from_dict({order[i]: R(c) for i, c in vector.items() if R(c)}, coerce=False, remove_zeros=False) def sum(self, iter_of_elements): @@ -1263,430 +1263,434 @@ def _from_dict(self, d, coerce=False, remove_zeros=True): class CombinatorialFreeModule_Tensor(CombinatorialFreeModule): - """ - Tensor Product of Free Modules + """ + Tensor Product of Free Modules - EXAMPLES: + EXAMPLES: - We construct two free modules, assign them short names, and construct their tensor product:: + We construct two free modules, assign them short names, and construct their tensor product:: - sage: F = CombinatorialFreeModule(ZZ, [1,2]); F.rename("F") - sage: G = CombinatorialFreeModule(ZZ, [3,4]); G.rename("G") - sage: T = tensor([F, G]); T - F # G + sage: F = CombinatorialFreeModule(ZZ, [1,2]); F.rename("F") + sage: G = CombinatorialFreeModule(ZZ, [3,4]); G.rename("G") + sage: T = tensor([F, G]); T + F # G - sage: T.category() - Category of tensor products of - finite dimensional modules with basis over Integer Ring + sage: T.category() + Category of tensor products of + finite dimensional modules with basis over Integer Ring - sage: T.construction() # todo: not implemented - [tensor, ] + sage: T.construction() # todo: not implemented + [tensor, ] - T is a free module, with same base ring as F and G:: + T is a free module, with same base ring as F and G:: - sage: T.base_ring() - Integer Ring + sage: T.base_ring() + Integer Ring - The basis of T is indexed by tuples of basis indices of F and G:: + The basis of T is indexed by tuples of basis indices of F and G:: - sage: T.basis().keys() - Image of Cartesian product of {1, 2}, {3, 4} - by The map from Cartesian product of {1, 2}, {3, 4} - sage: T.basis().keys().list() - [(1, 3), (1, 4), (2, 3), (2, 4)] + sage: T.basis().keys() + Image of Cartesian product of {1, 2}, {3, 4} + by The map from Cartesian product of {1, 2}, {3, 4} + sage: T.basis().keys().list() + [(1, 3), (1, 4), (2, 3), (2, 4)] - FIXME: Should elements of a CartesianProduct be tuples (making them hashable)? + FIXME: Should elements of a CartesianProduct be tuples (making them hashable)? - Here are the basis elements themselves:: + Here are the basis elements themselves:: - sage: T.basis().cardinality() - 4 - sage: list(T.basis()) - [B[1] # B[3], B[1] # B[4], B[2] # B[3], B[2] # B[4]] + sage: T.basis().cardinality() + 4 + sage: list(T.basis()) + [B[1] # B[3], B[1] # B[4], B[2] # B[3], B[2] # B[4]] - The tensor product is associative and flattens sub tensor products:: + The tensor product is associative and flattens sub tensor products:: - sage: H = CombinatorialFreeModule(ZZ, [5,6]); H.rename("H") - sage: tensor([F, tensor([G, H])]) - F # G # H - sage: tensor([tensor([F, G]), H]) - F # G # H - sage: tensor([F, G, H]) - F # G # H + sage: H = CombinatorialFreeModule(ZZ, [5,6]); H.rename("H") + sage: tensor([F, tensor([G, H])]) + F # G # H + sage: tensor([tensor([F, G]), H]) + F # G # H + sage: tensor([F, G, H]) + F # G # H - We now compute the tensor product of elements of free modules:: + We now compute the tensor product of elements of free modules:: - sage: f = F.monomial(1) + 2 * F.monomial(2) - sage: g = 2*G.monomial(3) + G.monomial(4) - sage: h = H.monomial(5) + H.monomial(6) - sage: tensor([f, g]) - 2*B[1] # B[3] + B[1] # B[4] + 4*B[2] # B[3] + 2*B[2] # B[4] + sage: f = F.monomial(1) + 2 * F.monomial(2) + sage: g = 2*G.monomial(3) + G.monomial(4) + sage: h = H.monomial(5) + H.monomial(6) + sage: tensor([f, g]) + 2*B[1] # B[3] + B[1] # B[4] + 4*B[2] # B[3] + 2*B[2] # B[4] - Again, the tensor product is associative on elements:: + Again, the tensor product is associative on elements:: - sage: tensor([f, tensor([g, h])]) == tensor([f, g, h]) - True - sage: tensor([tensor([f, g]), h]) == tensor([f, g, h]) - True + sage: tensor([f, tensor([g, h])]) == tensor([f, g, h]) + True + sage: tensor([tensor([f, g]), h]) == tensor([f, g, h]) + True - Note further that the tensor product spaces need not preexist:: + Note further that the tensor product spaces need not preexist:: - sage: t = tensor([f, g, h]) - sage: t.parent() - F # G # H + sage: t = tensor([f, g, h]) + sage: t.parent() + F # G # H + TESTS:: + + sage: tensor([tensor([F, G]), H]) == tensor([F, G, H]) + True + sage: tensor([F, tensor([G, H])]) == tensor([F, G, H]) + True + """ + @staticmethod + def __classcall_private__(cls, modules, **options): + """ TESTS:: + sage: F = CombinatorialFreeModule(ZZ, [1,2]) + sage: G = CombinatorialFreeModule(ZZ, [3,4]) + sage: H = CombinatorialFreeModule(ZZ, [4]) sage: tensor([tensor([F, G]), H]) == tensor([F, G, H]) True sage: tensor([F, tensor([G, H])]) == tensor([F, G, H]) True + + Check that :issue:`19608` is fixed:: + + sage: T = tensor([F, G, H]) + sage: T in Modules(ZZ).FiniteDimensional() + True """ - @staticmethod - def __classcall_private__(cls, modules, **options): - """ - TESTS:: - - sage: F = CombinatorialFreeModule(ZZ, [1,2]) - sage: G = CombinatorialFreeModule(ZZ, [3,4]) - sage: H = CombinatorialFreeModule(ZZ, [4]) - sage: tensor([tensor([F, G]), H]) == tensor([F, G, H]) - True - sage: tensor([F, tensor([G, H])]) == tensor([F, G, H]) - True - - Check that :issue:`19608` is fixed:: - - sage: T = tensor([F, G, H]) - sage: T in Modules(ZZ).FiniteDimensional() - True - """ - assert (len(modules) > 0) - R = modules[0].base_ring() - assert (all(module in ModulesWithBasis(R)) for module in modules) - # should check the base ring - # flatten the list of modules so that tensor(A, tensor(B,C)) gets rewritten into tensor(A, B, C) - modules = sum([module._sets if isinstance(module, CombinatorialFreeModule_Tensor) else (module,) for module in modules], ()) - if all('FiniteDimensional' in M.category().axioms() for M in modules): - options['category'] = options['category'].FiniteDimensional() - return super(CombinatorialFreeModule.Tensor, cls).__classcall__(cls, modules, **options) - - def __init__(self, modules, **options): - """ - TESTS:: - - sage: F = CombinatorialFreeModule(ZZ, [1,2]); F - F - """ - self._sets = modules - indices = CartesianProduct_iters(*[module.basis().keys() - for module in modules]).map(tuple) - CombinatorialFreeModule.__init__(self, modules[0].base_ring(), indices, **options) - # the following is not the best option, but it's better than nothing. - if 'tensor_symbol' in options: - self._print_options['tensor_symbol'] = options['tensor_symbol'] - - def _repr_(self): - r""" - This is customizable by setting - ``self.print_options('tensor_symbol'=...)``. - - TESTS:: - - sage: F = CombinatorialFreeModule(ZZ, [1,2,3]) - sage: G = CombinatorialFreeModule(ZZ, [1,2,3,8]) - sage: F.rename("F") - sage: G.rename("G") - sage: T = tensor([F, G]) - sage: T # indirect doctest - F # G - sage: T.print_options(tensor_symbol=' @ ') # note the spaces - sage: T # indirect doctest - F @ G - - To avoid a side\--effect on another doctest, we revert the change:: - - sage: T.print_options(tensor_symbol=' # ') - """ - from sage.categories.tensor import tensor - if hasattr(self, "_print_options"): - symb = self._print_options['tensor_symbol'] - if symb is None: - symb = tensor.symbol - else: + assert (len(modules) > 0) + R = modules[0].base_ring() + assert (all(module in ModulesWithBasis(R)) for module in modules) + # should check the base ring + # flatten the list of modules so that tensor(A, tensor(B,C)) gets rewritten into tensor(A, B, C) + modules = sum([module._sets if isinstance(module, CombinatorialFreeModule_Tensor) else (module,) for module in modules], ()) + if all('FiniteDimensional' in M.category().axioms() for M in modules): + options['category'] = options['category'].FiniteDimensional() + return super(CombinatorialFreeModule.Tensor, cls).__classcall__(cls, modules, **options) + + def __init__(self, modules, **options): + """ + TESTS:: + + sage: F = CombinatorialFreeModule(ZZ, [1,2]); F + F + """ + self._sets = modules + indices = CartesianProduct_iters(*[module.basis().keys() + for module in modules]).map(tuple) + CombinatorialFreeModule.__init__(self, modules[0].base_ring(), indices, **options) + # the following is not the best option, but it's better than nothing. + if 'tensor_symbol' in options: + self._print_options['tensor_symbol'] = options['tensor_symbol'] + + def _repr_(self): + r""" + This is customizable by setting + ``self.print_options('tensor_symbol'=...)``. + + TESTS:: + + sage: F = CombinatorialFreeModule(ZZ, [1,2,3]) + sage: G = CombinatorialFreeModule(ZZ, [1,2,3,8]) + sage: F.rename("F") + sage: G.rename("G") + sage: T = tensor([F, G]) + sage: T # indirect doctest + F # G + sage: T.print_options(tensor_symbol=' @ ') # note the spaces + sage: T # indirect doctest + F @ G + + To avoid a side\--effect on another doctest, we revert the change:: + + sage: T.print_options(tensor_symbol=' # ') + """ + from sage.categories.tensor import tensor + if hasattr(self, "_print_options"): + symb = self._print_options['tensor_symbol'] + if symb is None: symb = tensor.symbol - return symb.join("%s" % module for module in self._sets) - # TODO: make this overridable by setting _name - - def tensor_factors(self): - """ - Return the tensor factors of this tensor product. - - EXAMPLES:: - - sage: F = CombinatorialFreeModule(ZZ, [1,2]) - sage: F.rename("F") - sage: G = CombinatorialFreeModule(ZZ, [3,4]) - sage: G.rename("G") - sage: T = tensor([F, G]); T - F # G - sage: T.tensor_factors() - (F, G) - """ - return self._sets - - def _ascii_art_(self, term): - """ - TESTS:: - - sage: R = NonCommutativeSymmetricFunctions(QQ).R() # needs sage.combinat - sage: Partitions.options(diagram_str="#", convention="french") # needs sage.combinat - sage: s = ascii_art(tensor((R[1,2], R[3,1,2]))); s # needs sage.combinat - R # R - # ### - ## # - ## - - Check that the breakpoints are correct (:issue:`29202`):: - - sage: s._breakpoints # needs sage.combinat - [6] - """ - if hasattr(self, "_print_options"): - symb = self._print_options['tensor_symbol'] - if symb is None: - symb = tensor.symbol - else: + else: + symb = tensor.symbol + return symb.join("%s" % module for module in self._sets) + # TODO: make this overridable by setting _name + + def tensor_factors(self): + """ + Return the tensor factors of this tensor product. + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(ZZ, [1,2]) + sage: F.rename("F") + sage: G = CombinatorialFreeModule(ZZ, [3,4]) + sage: G.rename("G") + sage: T = tensor([F, G]); T + F # G + sage: T.tensor_factors() + (F, G) + """ + return self._sets + + def _ascii_art_(self, term): + """ + TESTS:: + + sage: R = NonCommutativeSymmetricFunctions(QQ).R() # needs sage.combinat + sage: Partitions.options(diagram_str="#", convention="french") # needs sage.combinat + sage: s = ascii_art(tensor((R[1,2], R[3,1,2]))); s # needs sage.combinat + R # R + # ### + ## # + ## + + Check that the breakpoints are correct (:issue:`29202`):: + + sage: s._breakpoints # needs sage.combinat + [6] + """ + if hasattr(self, "_print_options"): + symb = self._print_options['tensor_symbol'] + if symb is None: symb = tensor.symbol - return ascii_art(*(module._ascii_art_term(t) - for module, t in zip(self._sets, term)), - sep=AsciiArt([symb], breakpoints=[len(symb)])) - - _ascii_art_term = _ascii_art_ - - def _unicode_art_(self, term): - """ - TESTS:: - - sage: R = NonCommutativeSymmetricFunctions(QQ).R() # needs sage.combinat - sage: Partitions.options(diagram_str="#", convention="french") # needs sage.combinat - sage: s = unicode_art(tensor((R[1,2], R[3,1,2]))); s # needs sage.combinat - R ⊗ R - ┌┐ ┌┬┬┐ - ├┼┐ └┴┼┤ - └┴┘ ├┼┐ - └┴┘ - - Check that the breakpoints are correct (:issue:`29202`):: - - sage: s._breakpoints # needs sage.combinat - [7] - """ - if hasattr(self, "_print_options"): - symb = self._print_options['tensor_symbol'] - if symb is None: - symb = tensor.unicode_symbol - else: + else: + symb = tensor.symbol + return ascii_art(*(module._ascii_art_term(t) + for module, t in zip(self._sets, term)), + sep=AsciiArt([symb], breakpoints=[len(symb)])) + + _ascii_art_term = _ascii_art_ + + def _unicode_art_(self, term): + """ + TESTS:: + + sage: R = NonCommutativeSymmetricFunctions(QQ).R() # needs sage.combinat + sage: Partitions.options(diagram_str="#", convention="french") # needs sage.combinat + sage: s = unicode_art(tensor((R[1,2], R[3,1,2]))); s # needs sage.combinat + R ⊗ R + ┌┐ ┌┬┬┐ + ├┼┐ └┴┼┤ + └┴┘ ├┼┐ + └┴┘ + + Check that the breakpoints are correct (:issue:`29202`):: + + sage: s._breakpoints # needs sage.combinat + [7] + """ + if hasattr(self, "_print_options"): + symb = self._print_options['tensor_symbol'] + if symb is None: symb = tensor.unicode_symbol - return unicode_art(*(module._unicode_art_term(t) - for module, t in zip(self._sets, term)), - sep=UnicodeArt([symb], breakpoints=[len(symb)])) - - _unicode_art_term = _unicode_art_ - - def _latex_(self): - r""" - TESTS:: - - sage: F = CombinatorialFreeModule(ZZ, [1,2,3]) - sage: G = CombinatorialFreeModule(ZZ, [1,2,3,8]) - sage: F.rename("F") - sage: G.rename("G") - sage: latex(tensor([F, F, G])) # indirect doctest - \text{\texttt{F}} \otimes \text{\texttt{F}} \otimes \text{\texttt{G}} - sage: F._latex_ = lambda : "F" - sage: G._latex_ = lambda : "G" - sage: latex(tensor([F, F, G])) # indirect doctest - F \otimes F \otimes G - """ - from sage.misc.latex import latex - symb = " \\otimes " - return symb.join("%s" % latex(module) for module in self._sets) - - def _repr_term(self, term): - """ - TESTS:: - - sage: F = CombinatorialFreeModule(ZZ, [1,2,3], prefix="F") - sage: G = CombinatorialFreeModule(ZZ, [1,2,3,4], prefix="G") - sage: f = F.monomial(1) + 2 * F.monomial(2) - sage: g = 2*G.monomial(3) + G.monomial(4) - sage: tensor([f, g]) # indirect doctest - 2*F[1] # G[3] + F[1] # G[4] + 4*F[2] # G[3] + 2*F[2] # G[4] - """ - if hasattr(self, "_print_options"): - symb = self._print_options['tensor_symbol'] - if symb is None: - symb = tensor.symbol - else: + else: + symb = tensor.unicode_symbol + return unicode_art(*(module._unicode_art_term(t) + for module, t in zip(self._sets, term)), + sep=UnicodeArt([symb], breakpoints=[len(symb)])) + + _unicode_art_term = _unicode_art_ + + def _latex_(self): + r""" + TESTS:: + + sage: F = CombinatorialFreeModule(ZZ, [1,2,3]) + sage: G = CombinatorialFreeModule(ZZ, [1,2,3,8]) + sage: F.rename("F") + sage: G.rename("G") + sage: latex(tensor([F, F, G])) # indirect doctest + \text{\texttt{F}} \otimes \text{\texttt{F}} \otimes \text{\texttt{G}} + sage: F._latex_ = lambda : "F" + sage: G._latex_ = lambda : "G" + sage: latex(tensor([F, F, G])) # indirect doctest + F \otimes F \otimes G + """ + from sage.misc.latex import latex + symb = " \\otimes " + return symb.join("%s" % latex(module) for module in self._sets) + + def _repr_term(self, term): + """ + TESTS:: + + sage: F = CombinatorialFreeModule(ZZ, [1,2,3], prefix="F") + sage: G = CombinatorialFreeModule(ZZ, [1,2,3,4], prefix="G") + sage: f = F.monomial(1) + 2 * F.monomial(2) + sage: g = 2*G.monomial(3) + G.monomial(4) + sage: tensor([f, g]) # indirect doctest + 2*F[1] # G[3] + F[1] # G[4] + 4*F[2] # G[3] + 2*F[2] # G[4] + """ + if hasattr(self, "_print_options"): + symb = self._print_options['tensor_symbol'] + if symb is None: symb = tensor.symbol - return symb.join(module._repr_term(t) for (module, t) in zip(self._sets, term)) - - def _latex_term(self, term): - r""" - TESTS:: - - sage: F = CombinatorialFreeModule(ZZ, [1,2,3], prefix='x') - sage: G = CombinatorialFreeModule(ZZ, [1,2,3,4], prefix='y') - sage: f = F.monomial(1) + 2 * F.monomial(2) - sage: g = 2*G.monomial(3) + G.monomial(4) - sage: latex(tensor([f, g])) # indirect doctest - 2 x_{1} \otimes y_{3} + x_{1} \otimes y_{4} + 4 x_{2} \otimes y_{3} + 2 x_{2} \otimes y_{4} - """ - symb = " \\otimes " - return symb.join(module._latex_term(t) for (module, t) in zip(self._sets, term)) - - @cached_method - def tensor_constructor(self, modules): - r""" - INPUT: - - - ``modules`` -- a tuple `(F_1,\dots,F_n)` of - free modules whose tensor product is self - - Returns the canonical multilinear morphism from - `F_1 \times \dots \times F_n` to `F_1 \otimes \dots \otimes F_n` - - EXAMPLES:: - - sage: F = CombinatorialFreeModule(ZZ, [1,2]); F.rename("F") - sage: G = CombinatorialFreeModule(ZZ, [3,4]); G.rename("G") - sage: H = CombinatorialFreeModule(ZZ, [5,6]); H.rename("H") - - sage: f = F.monomial(1) + 2*F.monomial(2) - sage: g = 2*G.monomial(3) + G.monomial(4) - sage: h = H.monomial(5) + H.monomial(6) - sage: FG = tensor([F, G]) - sage: phi_fg = FG.tensor_constructor((F, G)) - sage: phi_fg(f, g) - 2*B[1] # B[3] + B[1] # B[4] + 4*B[2] # B[3] + 2*B[2] # B[4] - - sage: FGH = tensor([F, G, H]) - sage: phi_fgh = FGH.tensor_constructor((F, G, H)) - sage: phi_fgh(f, g, h) - 2*B[1] # B[3] # B[5] + 2*B[1] # B[3] # B[6] + B[1] # B[4] # B[5] - + B[1] # B[4] # B[6] + 4*B[2] # B[3] # B[5] + 4*B[2] # B[3] # B[6] - + 2*B[2] # B[4] # B[5] + 2*B[2] # B[4] # B[6] - - sage: phi_fg_h = FGH.tensor_constructor((FG, H)) - sage: phi_fg_h(phi_fg(f, g), h) - 2*B[1] # B[3] # B[5] + 2*B[1] # B[3] # B[6] + B[1] # B[4] # B[5] - + B[1] # B[4] # B[6] + 4*B[2] # B[3] # B[5] + 4*B[2] # B[3] # B[6] - + 2*B[2] # B[4] # B[5] + 2*B[2] # B[4] # B[6] - """ - assert (module in ModulesWithBasis(self.base_ring()) for module in modules) - assert (tensor(modules) == self) - # a list l such that l[i] is True if modules[i] is readily a tensor product - is_tensor = [isinstance(module, CombinatorialFreeModule_Tensor) for module in modules] - # the tensor_constructor, on basis elements - result = self.monomial * CartesianProductWithFlattening(is_tensor) - # TODO: make this into an element of Hom( A x B, C ) when those will exist - for i in range(len(modules)): - result = modules[i]._module_morphism(result, position=i, - codomain=self) - return result - - def _tensor_of_elements(self, elements): - """ - Returns the tensor product of the specified elements. - The result should be in ``self``. - - EXAMPLES:: - - sage: F = CombinatorialFreeModule(ZZ, [1,2]); F.rename("F") - sage: G = CombinatorialFreeModule(ZZ, [3,4]); G.rename("G") - sage: H = CombinatorialFreeModule(ZZ, [5,6]); H.rename("H") - - sage: f = F.monomial(1) + 2 * F.monomial(2) - sage: g = 2*G.monomial(3) + G.monomial(4) - sage: h = H.monomial(5) + H.monomial(6) - - sage: GH = tensor([G, H]) - sage: gh = GH._tensor_of_elements([g, h]); gh - 2*B[3] # B[5] + 2*B[3] # B[6] + B[4] # B[5] + B[4] # B[6] - - sage: FGH = tensor([F, G, H]) - sage: FGH._tensor_of_elements([f, g, h]) - 2*B[1] # B[3] # B[5] + 2*B[1] # B[3] # B[6] + B[1] # B[4] # B[5] + B[1] # B[4] # B[6] + 4*B[2] # B[3] # B[5] + 4*B[2] # B[3] # B[6] + 2*B[2] # B[4] # B[5] + 2*B[2] # B[4] # B[6] - - sage: FGH._tensor_of_elements([f, gh]) - 2*B[1] # B[3] # B[5] + 2*B[1] # B[3] # B[6] + B[1] # B[4] # B[5] + B[1] # B[4] # B[6] + 4*B[2] # B[3] # B[5] + 4*B[2] # B[3] # B[6] + 2*B[2] # B[4] # B[5] + 2*B[2] # B[4] # B[6] - """ - return self.tensor_constructor(tuple(element.parent() for element in elements))(*elements) - - def _coerce_map_from_(self, R): - """ - Return ``True`` if there is a coercion from ``R`` into ``self`` and - ``False`` otherwise. The things that coerce into ``self`` are: - - - Anything with a coercion into ``self.base_ring()``. - - - A tensor algebra whose factors have a coercion into the - corresponding factors of ``self``. - - TESTS:: - - sage: C = CombinatorialFreeModule(ZZ, ZZ) - sage: C2 = CombinatorialFreeModule(ZZ, NN) - sage: M = C.module_morphism(lambda x: C2.monomial(abs(x)), codomain=C2) - sage: M.register_as_coercion() - sage: C2(C.basis()[3]) - B[3] - sage: C2(C.basis()[3] + C.basis()[-3]) - 2*B[3] - sage: S = C.tensor(C) - sage: S2 = C2.tensor(C2) - sage: S2.has_coerce_map_from(S) - True - sage: S.has_coerce_map_from(S2) - False - sage: S.an_element() - 3*B[0] # B[-1] + 2*B[0] # B[0] + 2*B[0] # B[1] - sage: S2(S.an_element()) - 2*B[0] # B[0] + 5*B[0] # B[1] - - :: - - sage: C = CombinatorialFreeModule(ZZ, Set([1,2])) - sage: D = CombinatorialFreeModule(ZZ, Set([2,4])) - sage: f = C.module_morphism(on_basis=lambda x: D.monomial(2*x), codomain=D) - sage: f.register_as_coercion() - sage: T = tensor((C,C)) - sage: p = D.an_element() - sage: T(tensor((p,p))) - Traceback (most recent call last): - ... - NotImplementedError - sage: T = tensor((D,D)) - sage: p = C.an_element() - sage: T(tensor((p,p))) - 4*B[2] # B[2] + 4*B[2] # B[4] + 4*B[4] # B[2] + 4*B[4] # B[4] - """ - if ((R in ModulesWithBasis(self.base_ring()).TensorProducts() or - R in GradedAlgebrasWithBasis(self.base_ring()).SignedTensorProducts()) - and isinstance(R, CombinatorialFreeModule_Tensor) - and len(R._sets) == len(self._sets) - and all(self._sets[i].has_coerce_map_from(M) - for i, M in enumerate(R._sets))): - modules = R._sets - vector_map = [self._sets[i]._internal_coerce_map_from(M) - for i, M in enumerate(modules)] - return R.module_morphism(lambda x: self._tensor_of_elements( - [vector_map[i](M.monomial(x[i])) - for i, M in enumerate(modules)]), codomain=self) - - return super()._coerce_map_from_(R) + else: + symb = tensor.symbol + return symb.join(module._repr_term(t) for (module, t) in zip(self._sets, term)) + + def _latex_term(self, term): + r""" + TESTS:: + + sage: F = CombinatorialFreeModule(ZZ, [1,2,3], prefix='x') + sage: G = CombinatorialFreeModule(ZZ, [1,2,3,4], prefix='y') + sage: f = F.monomial(1) + 2 * F.monomial(2) + sage: g = 2*G.monomial(3) + G.monomial(4) + sage: latex(tensor([f, g])) # indirect doctest + 2 x_{1} \otimes y_{3} + x_{1} \otimes y_{4} + 4 x_{2} \otimes y_{3} + 2 x_{2} \otimes y_{4} + """ + symb = " \\otimes " + return symb.join(module._latex_term(t) for (module, t) in zip(self._sets, term)) + + @cached_method + def tensor_constructor(self, modules): + r""" + INPUT: + + - ``modules`` -- a tuple `(F_1,\dots,F_n)` of + free modules whose tensor product is self + + Returns the canonical multilinear morphism from + `F_1 \times \dots \times F_n` to `F_1 \otimes \dots \otimes F_n` + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(ZZ, [1,2]); F.rename("F") + sage: G = CombinatorialFreeModule(ZZ, [3,4]); G.rename("G") + sage: H = CombinatorialFreeModule(ZZ, [5,6]); H.rename("H") + + sage: f = F.monomial(1) + 2*F.monomial(2) + sage: g = 2*G.monomial(3) + G.monomial(4) + sage: h = H.monomial(5) + H.monomial(6) + sage: FG = tensor([F, G]) + sage: phi_fg = FG.tensor_constructor((F, G)) + sage: phi_fg(f, g) + 2*B[1] # B[3] + B[1] # B[4] + 4*B[2] # B[3] + 2*B[2] # B[4] + + sage: FGH = tensor([F, G, H]) + sage: phi_fgh = FGH.tensor_constructor((F, G, H)) + sage: phi_fgh(f, g, h) + 2*B[1] # B[3] # B[5] + 2*B[1] # B[3] # B[6] + B[1] # B[4] # B[5] + + B[1] # B[4] # B[6] + 4*B[2] # B[3] # B[5] + 4*B[2] # B[3] # B[6] + + 2*B[2] # B[4] # B[5] + 2*B[2] # B[4] # B[6] + + sage: phi_fg_h = FGH.tensor_constructor((FG, H)) + sage: phi_fg_h(phi_fg(f, g), h) + 2*B[1] # B[3] # B[5] + 2*B[1] # B[3] # B[6] + B[1] # B[4] # B[5] + + B[1] # B[4] # B[6] + 4*B[2] # B[3] # B[5] + 4*B[2] # B[3] # B[6] + + 2*B[2] # B[4] # B[5] + 2*B[2] # B[4] # B[6] + """ + assert (module in ModulesWithBasis(self.base_ring()) for module in modules) + assert (tensor(modules) == self) + # a list l such that l[i] is True if modules[i] is readily a tensor product + is_tensor = [isinstance(module, CombinatorialFreeModule_Tensor) for module in modules] + # the tensor_constructor, on basis elements + result = self.monomial * CartesianProductWithFlattening(is_tensor) + # TODO: make this into an element of Hom( A x B, C ) when those will exist + for i in range(len(modules)): + result = modules[i]._module_morphism(result, position=i, + codomain=self) + return result + + def _tensor_of_elements(self, elements): + """ + Returns the tensor product of the specified elements. + The result should be in ``self``. + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(ZZ, [1,2]); F.rename("F") + sage: G = CombinatorialFreeModule(ZZ, [3,4]); G.rename("G") + sage: H = CombinatorialFreeModule(ZZ, [5,6]); H.rename("H") + + sage: f = F.monomial(1) + 2 * F.monomial(2) + sage: g = 2*G.monomial(3) + G.monomial(4) + sage: h = H.monomial(5) + H.monomial(6) + + sage: GH = tensor([G, H]) + sage: gh = GH._tensor_of_elements([g, h]); gh + 2*B[3] # B[5] + 2*B[3] # B[6] + B[4] # B[5] + B[4] # B[6] + + sage: FGH = tensor([F, G, H]) + sage: FGH._tensor_of_elements([f, g, h]) + 2*B[1] # B[3] # B[5] + 2*B[1] # B[3] # B[6] + B[1] # B[4] # B[5] + + B[1] # B[4] # B[6] + 4*B[2] # B[3] # B[5] + 4*B[2] # B[3] # B[6] + + 2*B[2] # B[4] # B[5] + 2*B[2] # B[4] # B[6] + + sage: FGH._tensor_of_elements([f, gh]) + 2*B[1] # B[3] # B[5] + 2*B[1] # B[3] # B[6] + B[1] # B[4] # B[5] + + B[1] # B[4] # B[6] + 4*B[2] # B[3] # B[5] + 4*B[2] # B[3] # B[6] + + 2*B[2] # B[4] # B[5] + 2*B[2] # B[4] # B[6] + """ + return self.tensor_constructor(tuple(element.parent() for element in elements))(*elements) + + def _coerce_map_from_(self, R): + """ + Return ``True`` if there is a coercion from ``R`` into ``self`` and + ``False`` otherwise. The things that coerce into ``self`` are: + + - Anything with a coercion into ``self.base_ring()``. + + - A tensor algebra whose factors have a coercion into the + corresponding factors of ``self``. + + TESTS:: + + sage: C = CombinatorialFreeModule(ZZ, ZZ) + sage: C2 = CombinatorialFreeModule(ZZ, NN) + sage: M = C.module_morphism(lambda x: C2.monomial(abs(x)), codomain=C2) + sage: M.register_as_coercion() + sage: C2(C.basis()[3]) + B[3] + sage: C2(C.basis()[3] + C.basis()[-3]) + 2*B[3] + sage: S = C.tensor(C) + sage: S2 = C2.tensor(C2) + sage: S2.has_coerce_map_from(S) + True + sage: S.has_coerce_map_from(S2) + False + sage: S.an_element() + 3*B[0] # B[-1] + 2*B[0] # B[0] + 2*B[0] # B[1] + sage: S2(S.an_element()) + 2*B[0] # B[0] + 5*B[0] # B[1] + + :: + + sage: C = CombinatorialFreeModule(ZZ, Set([1,2])) + sage: D = CombinatorialFreeModule(ZZ, Set([2,4])) + sage: f = C.module_morphism(on_basis=lambda x: D.monomial(2*x), codomain=D) + sage: f.register_as_coercion() + sage: T = tensor((C,C)) + sage: p = D.an_element() + sage: T(tensor((p,p))) + Traceback (most recent call last): + ... + NotImplementedError + sage: T = tensor((D,D)) + sage: p = C.an_element() + sage: T(tensor((p,p))) + 4*B[2] # B[2] + 4*B[2] # B[4] + 4*B[4] # B[2] + 4*B[4] # B[4] + """ + if ((R in ModulesWithBasis(self.base_ring()).TensorProducts() or + R in GradedAlgebrasWithBasis(self.base_ring()).SignedTensorProducts()) + and isinstance(R, CombinatorialFreeModule_Tensor) + and len(R._sets) == len(self._sets) + and all(self._sets[i].has_coerce_map_from(M) + for i, M in enumerate(R._sets))): + modules = R._sets + vector_map = [self._sets[i]._internal_coerce_map_from(M) + for i, M in enumerate(modules)] + return R.module_morphism(lambda x: self._tensor_of_elements( + [vector_map[i](M.monomial(x[i])) + for i, M in enumerate(modules)]), codomain=self) + + return super()._coerce_map_from_(R) class CartesianProductWithFlattening: