diff --git a/src/doc/en/reference/combinat/module_list.rst b/src/doc/en/reference/combinat/module_list.rst index 4abd0e8f295..8ee512b5bc7 100644 --- a/src/doc/en/reference/combinat/module_list.rst +++ b/src/doc/en/reference/combinat/module_list.rst @@ -162,6 +162,7 @@ Comprehensive Module list sage/combinat/permutation_nk sage/combinat/posets/__init__ sage/combinat/posets/all + sage/combinat/posets/cartesian_product sage/combinat/posets/elements sage/combinat/posets/hasse_diagram sage/combinat/posets/incidence_algebras diff --git a/src/sage/categories/covariant_functorial_construction.py b/src/sage/categories/covariant_functorial_construction.py index cc2ea36216e..47ed9d8e370 100644 --- a/src/sage/categories/covariant_functorial_construction.py +++ b/src/sage/categories/covariant_functorial_construction.py @@ -201,7 +201,7 @@ def _repr_(self): """ return "The %s functorial construction"%self._functor_name - def __call__(self, args): + def __call__(self, args, **kwargs): """ Functorial construction application @@ -220,7 +220,7 @@ def __call__(self, args): args = tuple(args) # a bit brute force; let's see if this becomes a bottleneck later assert(all( hasattr(arg, self._functor_name) for arg in args)) assert(len(args) > 0) - return getattr(args[0], self._functor_name)(*args[1:]) + return getattr(args[0], self._functor_name)(*args[1:], **kwargs) class FunctorialConstructionCategory(Category): # Should this be CategoryWithBase? """ diff --git a/src/sage/categories/posets.py b/src/sage/categories/posets.py index 5dc0113ada8..52baeec2e41 100644 --- a/src/sage/categories/posets.py +++ b/src/sage/categories/posets.py @@ -683,6 +683,9 @@ def is_antichain_of_poset(self, o): """ return all(not self.lt(x,y) for x in o for y in o) + CartesianProduct = LazyImport( + 'sage.combinat.posets.cartesian_product', 'CartesianProductPoset') + class ElementMethods: pass # TODO: implement xy, x>=y appropriately once #10130 is resolved diff --git a/src/sage/categories/sets_cat.py b/src/sage/categories/sets_cat.py index caa21f260a4..db3c5332b69 100644 --- a/src/sage/categories/sets_cat.py +++ b/src/sage/categories/sets_cat.py @@ -1400,10 +1400,32 @@ def _test_cardinality(self, **options): # Functorial constructions CartesianProduct = CartesianProduct - def cartesian_product(*parents): + def cartesian_product(*parents, **kwargs): """ Return the cartesian product of the parents. + INPUT: + + - ``parents`` -- a list (or other iterable) of parents. + + - ``category`` -- (default: ``None``) the category the + cartesian product belongs to. If ``None`` is passed, + then + :meth:`~sage.categories.covariant_functorial_construction.CovariantFactorialConstruction.category_from_parents` + is used to determine the category. + + - ``extra_category`` -- (default: ``None``) a category + that is added to the cartesian product in addition + to the categories obtained from the parents. + + - other keyword arguments will passed on to the class used + for this cartesian product (see also + :class:`~sage.sets.cartesian_product.CartesianProduct`). + + OUTPUT: + + The cartesian product. + EXAMPLES:: sage: C = AlgebrasWithBasis(QQ) @@ -1423,10 +1445,31 @@ def cartesian_product(*parents): sage: C.category() Join of Category of rings and ... and Category of Cartesian products of commutative additive groups + + :: + + sage: cartesian_product([ZZ, ZZ], category=Sets()).category() + Category of sets + sage: cartesian_product([ZZ, ZZ]).category() + Join of + Category of Cartesian products of commutative rings and + Category of Cartesian products of enumerated sets + sage: cartesian_product([ZZ, ZZ], extra_category=Posets()).category() + Join of + Category of Cartesian products of commutative rings and + Category of posets and + Category of Cartesian products of enumerated sets """ - return parents[0].CartesianProduct( - parents, - category = cartesian_product.category_from_parents(parents)) + category = kwargs.pop('category', None) + extra_category = kwargs.pop('extra_category', None) + + category = category or cartesian_product.category_from_parents(parents) + if extra_category: + if isinstance(category, (list, tuple)): + category = tuple(category) + (extra_category,) + else: + category = category & extra_category + return parents[0].CartesianProduct(parents, category=category, **kwargs) def algebra(self, base_ring, category=None): """ diff --git a/src/sage/combinat/posets/__init__.py b/src/sage/combinat/posets/__init__.py index 3e87a7a6012..9f5c8c89853 100644 --- a/src/sage/combinat/posets/__init__.py +++ b/src/sage/combinat/posets/__init__.py @@ -15,6 +15,8 @@ - :ref:`sage.combinat.posets.incidence_algebras` +- :ref:`sage.combinat.posets.cartesian_product` + - :ref:`sage.combinat.tamari_lattices` - :ref:`sage.combinat.interval_posets` - :ref:`sage.combinat.shard_order` diff --git a/src/sage/combinat/posets/cartesian_product.py b/src/sage/combinat/posets/cartesian_product.py new file mode 100644 index 00000000000..10bb66d5fe6 --- /dev/null +++ b/src/sage/combinat/posets/cartesian_product.py @@ -0,0 +1,505 @@ +""" +Cartesian products of Posets + +AUTHORS: + +- Daniel Krenn (2015) + +""" +#***************************************************************************** +# Copyright (C) 2015 Daniel Krenn +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.sets.cartesian_product import CartesianProduct + + +class CartesianProductPoset(CartesianProduct): + r""" + A class implementing cartesian products of posets (and elements + thereof). Compared to :class:`CartesianProduct` you are able to + specify an order for comparison of the elements. + + INPUT: + + - ``sets`` -- a tuple of parents. + + - ``category`` -- a subcategory of + ``Sets().CartesianProducts() & Posets()``. + + - ``order`` -- a string or function specifying an order less or equal. + It can be one of the following: + + - ``'native'`` -- elements are ordered by their native ordering, + i.e., the order the wrapped elements (tuples) provide. + + - ``'lex'`` -- elements are ordered lexicographically. + + - ``'product'`` -- an element is less or equal to another + element, if less or equal is true for all its components + (cartesian projections). + + - A function which performs the comparison `\leq`. It takes two + input arguments and outputs a boolean. + + Other keyword arguments (``kwargs``) are passed to the constructor + of :class:`CartesianProduct`. + + EXAMPLES:: + + sage: P = Poset((srange(3), lambda left, right: left <= right)) + sage: Cl = cartesian_product((P, P), order='lex') + sage: Cl((1, 1)) <= Cl((2, 0)) + True + sage: Cp = cartesian_product((P, P), order='product') + sage: Cp((1, 1)) <= Cp((2, 0)) + False + sage: def le_sum(left, right): + ....: return (sum(left) < sum(right) or + ....: sum(left) == sum(right) and left[0] <= right[0]) + sage: Cs = cartesian_product((P, P), order=le_sum) + sage: Cs((1, 1)) <= Cs((2, 0)) + True + + TESTS:: + + sage: Cl.category() + Join of Category of finite posets and + Category of Cartesian products of finite enumerated sets + sage: TestSuite(Cl).run() + sage: Cp.category() + Join of Category of finite posets and + Category of Cartesian products of finite enumerated sets + sage: TestSuite(Cp).run() + + .. SEEALSO: + + :class:`CartesianProduct` + """ + + def __init__(self, sets, category, order=None, **kwargs): + r""" + See :class:`CartesianProductPoset` for details. + + TESTS:: + + sage: P = Poset((srange(3), lambda left, right: left <= right)) + sage: C = cartesian_product((P, P), order='notexisting') + Traceback (most recent call last): + ... + ValueError: No order 'notexisting' known. + sage: C = cartesian_product((P, P), category=(Groups(),)) + sage: C.category() + Join of Category of groups and Category of posets + """ + if order is None: + self._le_ = self.le_product + elif isinstance(order, str): + try: + self._le_ = getattr(self, 'le_' + order) + except AttributeError: + raise ValueError("No order '%s' known." % (order,)) + else: + self._le_ = order + + from sage.categories.category import Category + from sage.categories.posets import Posets + if not isinstance(category, tuple): + category = (category,) + category = Category.join(category + (Posets(),)) + super(CartesianProductPoset, self).__init__( + sets, category, **kwargs) + + + def le(self, left, right): + r""" + Test whether ``left`` is less than or equal to ``right``. + + INPUT: + + - ``left`` -- an element. + + - ``right`` -- an element. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This method uses the order defined on creation of this + cartesian product. See :class:`CartesianProductPoset`. + + EXAMPLES:: + + sage: P = Posets.ChainPoset(10) + sage: def le_sum(left, right): + ....: return (sum(left) < sum(right) or + ....: sum(left) == sum(right) and left[0] <= right[0]) + sage: C = cartesian_product((P, P), order=le_sum) + sage: C.le(C((1, 6)), C((6, 1))) + True + sage: C.le(C((6, 1)), C((1, 6))) + False + sage: C.le(C((1, 6)), C((6, 6))) + True + sage: C.le(C((6, 6)), C((1, 6))) + False + """ + return self._le_(left, right) + + + def le_lex(self, left, right): + r""" + Test whether ``left`` is lexicographically smaller or equal + to ``right``. + + INPUT: + + - ``left`` -- an element. + + - ``right`` -- an element. + + OUTPUT: + + A boolean. + + EXAMPLES:: + + sage: P = Poset((srange(2), lambda left, right: left <= right)) + sage: Q = cartesian_product((P, P), order='lex') + sage: T = [Q((0, 0)), Q((1, 1)), Q((0, 1)), Q((1, 0))] + sage: for a in T: + ....: for b in T: + ....: assert(Q.le(a, b) == (a <= b)) + ....: print '%s <= %s = %s' % (a, b, a <= b) + (0, 0) <= (0, 0) = True + (0, 0) <= (1, 1) = True + (0, 0) <= (0, 1) = True + (0, 0) <= (1, 0) = True + (1, 1) <= (0, 0) = False + (1, 1) <= (1, 1) = True + (1, 1) <= (0, 1) = False + (1, 1) <= (1, 0) = False + (0, 1) <= (0, 0) = False + (0, 1) <= (1, 1) = True + (0, 1) <= (0, 1) = True + (0, 1) <= (1, 0) = True + (1, 0) <= (0, 0) = False + (1, 0) <= (1, 1) = True + (1, 0) <= (0, 1) = False + (1, 0) <= (1, 0) = True + """ + for l, r, S in \ + zip(left.value, right.value, self.cartesian_factors()): + if l == r: + continue + if S.le(l, r): + return True + if S.le(r, l): + return False + return True # equal + + + def le_product(self, left, right): + r""" + Test whether ``left`` is component-wise smaller or equal + to ``right``. + + INPUT: + + - ``left`` -- an element. + + - ``right`` -- an element. + + OUTPUT: + + A boolean. + + The comparison is ``True`` if the result of the + comparision in each component is ``True``. + + EXAMPLES:: + + sage: P = Poset((srange(2), lambda left, right: left <= right)) + sage: Q = cartesian_product((P, P), order='product') + sage: T = [Q((0, 0)), Q((1, 1)), Q((0, 1)), Q((1, 0))] + sage: for a in T: + ....: for b in T: + ....: assert(Q.le(a, b) == (a <= b)) + ....: print '%s <= %s = %s' % (a, b, a <= b) + (0, 0) <= (0, 0) = True + (0, 0) <= (1, 1) = True + (0, 0) <= (0, 1) = True + (0, 0) <= (1, 0) = True + (1, 1) <= (0, 0) = False + (1, 1) <= (1, 1) = True + (1, 1) <= (0, 1) = False + (1, 1) <= (1, 0) = False + (0, 1) <= (0, 0) = False + (0, 1) <= (1, 1) = True + (0, 1) <= (0, 1) = True + (0, 1) <= (1, 0) = False + (1, 0) <= (0, 0) = False + (1, 0) <= (1, 1) = True + (1, 0) <= (0, 1) = False + (1, 0) <= (1, 0) = True + """ + return all( + S.le(l, r) + for l, r, S in + zip(left.value, right.value, self.cartesian_factors())) + + + def le_native(self, left, right): + r""" + Test whether ``left`` is smaller or equal to ``right`` in the order + provided by the elements themselves. + + INPUT: + + - ``left`` -- an element. + + - ``right`` -- an element. + + OUTPUT: + + A boolean. + + EXAMPLES:: + + sage: P = Poset((srange(2), lambda left, right: left <= right)) + sage: Q = cartesian_product((P, P), order='native') + sage: T = [Q((0, 0)), Q((1, 1)), Q((0, 1)), Q((1, 0))] + sage: for a in T: + ....: for b in T: + ....: assert(Q.le(a, b) == (a <= b)) + ....: print '%s <= %s = %s' % (a, b, a <= b) + (0, 0) <= (0, 0) = True + (0, 0) <= (1, 1) = True + (0, 0) <= (0, 1) = True + (0, 0) <= (1, 0) = True + (1, 1) <= (0, 0) = False + (1, 1) <= (1, 1) = True + (1, 1) <= (0, 1) = False + (1, 1) <= (1, 0) = False + (0, 1) <= (0, 0) = False + (0, 1) <= (1, 1) = True + (0, 1) <= (0, 1) = True + (0, 1) <= (1, 0) = True + (1, 0) <= (0, 0) = False + (1, 0) <= (1, 1) = True + (1, 0) <= (0, 1) = False + (1, 0) <= (1, 0) = True + """ + return left.value <= right.value + + + class Element(CartesianProduct.Element): + + def _le_(self, other): + r""" + Return if this element is less or equal to ``other``. + + INPUT: + + - ``other`` -- an element. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This method calls :meth:`CartesianProductPoset.le`. Override + it in inherited class to change this. + + It can be assumed that this element and ``other`` have + the same parent. + + TESTS:: + + sage: QQ.CartesianProduct = sage.combinat.posets.cartesian_product.CartesianProductPoset # needed until #19269 is fixed + sage: def le_sum(left, right): + ....: return (sum(left) < sum(right) or + ....: sum(left) == sum(right) and left[0] <= right[0]) + sage: C = cartesian_product((QQ, QQ), order=le_sum) + sage: C((1/3, 2)) <= C((2, 1/3)) # indirect doctest + True + sage: C((1/3, 2)) <= C((2, 2)) # indirect doctest + True + """ + return self.parent().le(self, other) + + + def __le__(self, other): + r""" + Return if this element is less than or equal to ``other``. + + INPUT: + + - ``other`` -- an element. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This method uses the coercion framework to find a + suitable common parent. + + This method can be deleted once :trac:`10130` is fixed and + provides these methods automatically. + + TESTS:: + + sage: from sage.combinat.posets.cartesian_product import CartesianProductPoset + sage: QQ.CartesianProduct = CartesianProductPoset # needed until #19269 is fixed + sage: def le_sum(left, right): + ....: return (sum(left) < sum(right) or + ....: sum(left) == sum(right) and left[0] <= right[0]) + sage: C = cartesian_product((QQ, QQ), order=le_sum) + sage: C((1/3, 2)) <= C((2, 1/3)) + True + sage: C((1/3, 2)) <= C((2, 2)) + True + + The following example tests that the coercion gets involved in + comparisons; it can be simplified once #18182 is in merged. + :: + + sage: class MyCP(CartesianProductPoset): + ....: def _coerce_map_from_(self, S): + ....: if isinstance(S, self.__class__): + ....: S_factors = S.cartesian_factors() + ....: R_factors = self.cartesian_factors() + ....: if len(S_factors) == len(R_factors): + ....: if all(r.has_coerce_map_from(s) + ....: for r,s in zip(R_factors, S_factors)): + ....: return True + sage: QQ.CartesianProduct = MyCP + sage: A = cartesian_product((QQ, ZZ), order=le_sum) + sage: B = cartesian_product((QQ, QQ), order=le_sum) + sage: A((1/2, 4)) <= B((1/2, 5)) + True + """ + from sage.structure.element import have_same_parent + if have_same_parent(self, other): + return self._le_(other) + + from sage.structure.element import get_coercion_model + import operator + try: + return get_coercion_model().bin_op(self, other, operator.le) + except TypeError: + return False + + + def __ge__(self, other): + r""" + Return if this element is greater than or equal to ``other``. + + INPUT: + + - ``other`` -- an element. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This method uses the coercion framework to find a + suitable common parent. + + This method can be deleted once :trac:`10130` is fixed and + provides these methods automatically. + + TESTS:: + + sage: QQ.CartesianProduct = sage.combinat.posets.cartesian_product.CartesianProductPoset # needed until #19269 is fixed + sage: def le_sum(left, right): + ....: return (sum(left) < sum(right) or + ....: sum(left) == sum(right) and left[0] <= right[0]) + sage: C = cartesian_product((QQ, QQ), order=le_sum) + sage: C((1/3, 2)) >= C((2, 1/3)) + False + sage: C((1/3, 2)) >= C((2, 2)) + False + """ + return other.__le__(self) + + + def __lt__(self, other): + r""" + Return if this element is less than ``other``. + + INPUT: + + - ``other`` -- an element. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This method uses the coercion framework to find a + suitable common parent. + + This method can be deleted once :trac:`10130` is fixed and + provides these methods automatically. + + TESTS:: + + sage: QQ.CartesianProduct = sage.combinat.posets.cartesian_product.CartesianProductPoset # needed until #19269 is fixed + sage: def le_sum(left, right): + ....: return (sum(left) < sum(right) or + ....: sum(left) == sum(right) and left[0] <= right[0]) + sage: C = cartesian_product((QQ, QQ), order=le_sum) + sage: C((1/3, 2)) < C((2, 1/3)) + True + sage: C((1/3, 2)) < C((2, 2)) + True + """ + return not self == other and self.__le__(other) + + + def __gt__(self, other): + r""" + Return if this element is greater than ``other``. + + INPUT: + + - ``other`` -- an element. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This method uses the coercion framework to find a + suitable common parent. + + This method can be deleted once :trac:`10130` is fixed and + provides these methods automatically. + + TESTS:: + + sage: QQ.CartesianProduct = sage.combinat.posets.cartesian_product.CartesianProductPoset # needed until #19269 is fixed + sage: def le_sum(left, right): + ....: return (sum(left) < sum(right) or + ....: sum(left) == sum(right) and left[0] <= right[0]) + sage: C = cartesian_product((QQ, QQ), order=le_sum) + sage: C((1/3, 2)) > C((2, 1/3)) + False + sage: C((1/3, 2)) > C((2, 2)) + False + """ + return not self == other and other.__le__(self) diff --git a/src/sage/sets/cartesian_product.py b/src/sage/sets/cartesian_product.py index a208f257250..a4ff5cc257e 100644 --- a/src/sage/sets/cartesian_product.py +++ b/src/sage/sets/cartesian_product.py @@ -4,7 +4,6 @@ AUTHORS: - Nicolas Thiery (2010-03): initial version - """ #***************************************************************************** # Copyright (C) 2008 Nicolas Thiery , @@ -62,6 +61,8 @@ def __init__(self, sets, category, flatten=False): ``flatten`` is current ignored, and reserved for future use. + No other keyword arguments (``kwargs``) are accepted. + TESTS:: sage: from sage.sets.cartesian_product import CartesianProduct @@ -71,6 +72,10 @@ def __init__(self, sets, category, flatten=False): sage: C.an_element() (1/2, 1, 1) sage: TestSuite(C).run() + sage: cartesian_product([ZZ, ZZ], blub=None) + Traceback (most recent call last): + ... + TypeError: __init__() got an unexpected keyword argument 'blub' """ self._sets = tuple(sets) Parent.__init__(self, category=category)