From 0b62b6f89a628aa84f1242c18890de18a8b7e5ff Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 1 Apr 2024 23:56:20 -0700 Subject: [PATCH 01/14] EnumeratedSets.ParentMethods.map: Replace use of MapCombinatorialClass by ImageSubobject --- src/sage/categories/enumerated_sets.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/sage/categories/enumerated_sets.py b/src/sage/categories/enumerated_sets.py index 08150e61360..513ef129c8a 100644 --- a/src/sage/categories/enumerated_sets.py +++ b/src/sage/categories/enumerated_sets.py @@ -990,8 +990,12 @@ def map(self, f, name=None, *, is_injective=True): ....: '_test_enumerated_set_contains', ....: '_test_some_elements']) """ - from sage.combinat.combinat import MapCombinatorialClass - return MapCombinatorialClass(self, f, name, is_injective=is_injective) + from sage.sets.image_set import ImageSubobject + + image = ImageSubobject(f, self, is_injective=is_injective) + if name: + image.rename(name) + return image # # Consistency test suite for an enumerated set: From 9e34bf49ac5dff79bcb4bb03ae68ad36f1a044af Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 1 Apr 2024 23:59:03 -0700 Subject: [PATCH 02/14] src/sage/combinat/combinat.py: Remove CombinatorialClass and subclasses --- src/sage/combinat/combinat.py | 1005 --------------------------------- 1 file changed, 1005 deletions(-) diff --git a/src/sage/combinat/combinat.py b/src/sage/combinat/combinat.py index 38348c41c1d..3bd938d9742 100644 --- a/src/sage/combinat/combinat.py +++ b/src/sage/combinat/combinat.py @@ -1581,1011 +1581,6 @@ def __init__(self, parent, *args, **kwds): super().__init__(L) super(CombinatorialObject, self).__init__(parent) - -class CombinatorialClass(Parent, metaclass=ClasscallMetaclass): - """ - This class is deprecated, and will disappear as soon as all derived - classes in Sage's library will have been fixed. Please derive - directly from Parent and use the category :class:`EnumeratedSets`, - :class:`FiniteEnumeratedSets`, or :class:`InfiniteEnumeratedSets`, as - appropriate. - - For examples, see:: - - sage: FiniteEnumeratedSets().example() - An example of a finite enumerated set: {1,2,3} - sage: InfiniteEnumeratedSets().example() - An example of an infinite enumerated set: the non negative integers - """ - - def __init__(self, category=None): - """ - TESTS:: - - sage: C = sage.combinat.combinat.CombinatorialClass() - sage: C.category() - Category of enumerated sets - sage: C.__class__ - - sage: isinstance(C, Parent) - True - sage: C = sage.combinat.combinat.CombinatorialClass(category = FiniteEnumeratedSets()) - sage: C.category() - Category of finite enumerated sets - """ - Parent.__init__(self, category=EnumeratedSets().or_subcategory(category)) - - def is_finite(self) -> bool: - """ - Return whether ``self`` is finite or not. - - EXAMPLES:: - - sage: Partitions(5).is_finite() # needs sage.combinat - True - sage: Permutations().is_finite() - False - """ - return self.cardinality() != infinity - - def __getitem__(self, i): - """ - Return the combinatorial object of rank i. - - EXAMPLES:: - - sage: class C(CombinatorialClass): - ....: def __iter__(self): - ....: return iter([1,2,3]) - sage: c = C() - sage: c[0] - 1 - sage: c[2] - 3 - sage: c[4] - Traceback (most recent call last): - ... - ValueError: the value must be between 0 and 2 inclusive - """ - return self.unrank(i) - - def __str__(self) -> str: - """ - Return a string representation of self. - - EXAMPLES:: - - sage: str(Partitions(5)) # needs sage.combinat - 'Partitions of the integer 5' - """ - return repr(self) - - def _repr_(self) -> str: - """ - EXAMPLES:: - - sage: repr(Partitions(5)) # indirect doctest # needs sage.combinat - 'Partitions of the integer 5' - """ - if hasattr(self, '_name') and self._name: - return self._name - else: - return "Combinatorial Class -- REDEFINE ME!" - - def __contains__(self, x) -> bool: - """ - Test whether or not the combinatorial class contains the object x. - This raises a NotImplementedError as a default since _all_ - subclasses of CombinatorialClass should override this. - - Note that we could replace this with a default implementation that - just iterates through the elements of the combinatorial class and - checks for equality. However, since we use __contains__ for - type checking, this operation should be cheap and should be - implemented manually for each combinatorial class. - - EXAMPLES:: - - sage: C = CombinatorialClass() - sage: x in C # needs sage.symbolic - Traceback (most recent call last): - ... - NotImplementedError - """ - raise NotImplementedError - - def __eq__(self, other): - """ - Compare two different combinatorial classes. - - For now, the comparison is done just on their repr's. - - EXAMPLES:: - - sage: # needs sage.combinat - sage: p5 = Partitions(5) - sage: p6 = Partitions(6) - sage: repr(p5) == repr(p6) - False - sage: p5 == p6 - False - """ - return repr(self) == repr(other) - - def __ne__(self, other): - """ - Test unequality of ``self`` and ``other``. - - EXAMPLES:: - - sage: p5 = Partitions(5) # needs sage.combinat - sage: p6 = Partitions(6) # needs sage.combinat - sage: p5 != p6 # needs sage.combinat - True - """ - return not (self == other) - - def __hash__(self): - """ - Create a hash value. This is based on the string representation. - - Note that in Python 3 objects that define __eq__ do not inherit their __hash__ - function. Without an explicit __hash__ they are no longer hashable. - - TESTS:: - - sage: C = CombinatorialClass() - sage: hash(C) == hash(repr(C)) - True - """ - return hash(repr(self)) - - def __cardinality_from_iterator(self) -> Integer | infinity: - """ - Default implementation of cardinality which just goes through the iterator - of the combinatorial class to count the number of objects. - - EXAMPLES:: - - sage: class C(CombinatorialClass): - ....: def __iter__(self): - ....: return iter([1,2,3]) - sage: C().cardinality() #indirect doctest - 3 - """ - c = Integer(0) - one = Integer(1) - for _ in self: - c += one - return c - - cardinality = __cardinality_from_iterator - - # __call__, element_class, and _element_constructor_ are poor - # man's versions of those from Parent. This is for transition, - # until all combinatorial classes are proper parents (in Parent) - # and use coercion, etcc - - def __call__(self, x): - """ - Return x as an element of the combinatorial class's object class. - - EXAMPLES:: - - sage: # needs sage.combinat - sage: p5 = Partitions(5) - sage: a = [2,2,1] - sage: type(a) - - sage: a = p5(a) - sage: type(a) - - sage: p5([2,1]) - Traceback (most recent call last): - ... - ValueError: [2, 1] is not an element of Partitions of the integer 5 - """ - if x in self: - return self._element_constructor_(x) - else: - raise ValueError("%s not in %s" % (x, self)) - - Element = CombinatorialObject # mostly for backward compatibility - - @lazy_attribute - def element_class(self): - """ - This function is a temporary helper so that a CombinatorialClass - behaves as a parent for creating elements. This will disappear when - combinatorial classes will be turned into actual parents (in the - category EnumeratedSets). - - TESTS:: - - sage: P5 = Partitions(5) # needs sage.combinat - sage: P5.element_class # needs sage.combinat - - """ - # assert not isinstance(self, Parent) # Raises an alert if we override the proper definition from Parent - return self.Element - - def _element_constructor_(self, x): - """ - This function is a temporary helper so that a CombinatorialClass - behaves as a parent for creating elements. This will disappear when - combinatorial classes will be turned into actual parents (in the - category EnumeratedSets). - - TESTS:: - - sage: P5 = Partitions(5) # needs sage.combinat - sage: p = P5([3,2]) # indirect doctest # needs sage.combinat - sage: type(p) # needs sage.combinat - - """ - # assert not isinstance(self, Parent) # Raises an alert if we override the proper definition from Parent - return self.element_class(x) - - def __list_from_iterator(self): - """ - The default implementation of list which builds the list from the - iterator. - - EXAMPLES:: - - sage: class C(CombinatorialClass): - ....: def __iter__(self): - ....: return iter([1,2,3]) - sage: C().list() #indirect doctest - [1, 2, 3] - """ - return [x for x in self] - - # Set list to the default implementation - list = __list_from_iterator - - # Set the default object class to be CombinatorialObject - Element = CombinatorialObject - - def __iterator_from_next(self) -> Iterator: - """ - An iterator to use when the .first() and .next(x) methods are provided. - - EXAMPLES:: - - sage: C = CombinatorialClass() - sage: C.first = lambda: 0 - sage: C.next = lambda c: c+1 - sage: it = iter(C) # indirect doctest - sage: [next(it) for _ in range(4)] - [0, 1, 2, 3] - """ - f = self.first() - yield f - while True: - try: - f = self.next(f) - except (TypeError, ValueError): - break - - if f is None or f is False: - break - else: - yield f - - def __iterator_from_previous(self): - """ - An iterator to use when .last() and .previous() are provided. Note - that this requires the combinatorial class to be finite. It is not - recommended to implement combinatorial classes using last and - previous. - - EXAMPLES:: - - sage: C = CombinatorialClass() - sage: C.last = lambda: 4 - sage: def prev(c): - ....: if c <= 1: - ....: return None - ....: else: - ....: return c-1 - sage: C.previous = prev - sage: it = iter(C) # indirect doctest - sage: [next(it) for _ in range(4)] - [1, 2, 3, 4] - """ - l = self.last() - li = [l] - while True: - try: - l = self.previous(l) - except (TypeError, ValueError): - break - - if l is None: - break - else: - li.append(l) - return reversed(li) - - def __iterator_from_unrank(self) -> Iterator: - """ - An iterator to use when .unrank() is provided. - - EXAMPLES:: - - sage: C = CombinatorialClass() - sage: l = [1,2,3] - sage: C.unrank = lambda c: l[c] - sage: list(C) # indirect doctest - [1, 2, 3] - """ - r = 0 - u = self.unrank(r) - yield u - while True: - r += 1 - try: - u = self.unrank(r) - except (TypeError, ValueError, IndexError): - break - - if u is None: - break - else: - yield u - - def __iterator_from_list(self) -> Iterator: - """ - An iterator to use when .list() is provided() - - EXAMPLES:: - - sage: C = CombinatorialClass() - sage: C.list = lambda: [1, 2, 3] - sage: list(C) # indirect doctest - [1, 2, 3] - """ - yield from self.list() - - def __iter__(self): - """ - Allows the combinatorial class to be treated as an iterator. Default - implementation. - - EXAMPLES:: - - sage: p5 = Partitions(5) # needs sage.combinat - sage: [i for i in p5] # needs sage.combinat - [[5], [4, 1], [3, 2], [3, 1, 1], [2, 2, 1], [2, 1, 1, 1], [1, 1, 1, 1, 1]] - sage: C = CombinatorialClass() - sage: iter(C) - Traceback (most recent call last): - ... - NotImplementedError: iterator called but not implemented - """ - # Check whether .first() and .next(x) are overridden in the subclass - if (self.first != self.__first_from_iterator and - self.next != self.__next_from_iterator): - return self.__iterator_from_next() - # Check whether .last() and .previous() are overridden in the subclass - elif (self.last != self.__last_from_iterator and - self.previous != self.__previous_from_iterator): - return self.__iterator_from_previous() - # Check whether .unrank() is overridden in the subclass - elif self.unrank != self.__unrank_from_iterator: - return self.__iterator_from_unrank() - # Check whether .list() is overridden in the subclass - elif self.list != self.__list_from_iterator: - return self.__iterator_from_list() - else: - raise NotImplementedError("iterator called but not implemented") - - def __unrank_from_iterator(self, r): - """ - Default implementation of unrank which goes through the iterator. - - EXAMPLES:: - - sage: C = CombinatorialClass() - sage: C.list = lambda: [1,2,3] - sage: C.unrank(1) # indirect doctest - 2 - """ - counter = 0 - for u in self: - if counter == r: - return u - counter += 1 - raise ValueError("the value must be between %s and %s inclusive" % (0, counter - 1)) - - # Set the default implementation of unrank - unrank = __unrank_from_iterator - - def __random_element_from_unrank(self): - """ - Default implementation of random which uses unrank. - - EXAMPLES:: - - sage: C = CombinatorialClass() - sage: C.list = lambda: [1,2,3] - sage: C.random_element() # random # indirect doctest - 1 - """ - c = self.cardinality() - r = randint(0, c - 1) - return self.unrank(r) - - # Set the default implementation of random - random_element = __random_element_from_unrank - - def __rank_from_iterator(self, obj): - """ - Default implementation of rank which uses iterator. - - EXAMPLES:: - - sage: C = CombinatorialClass() - sage: C.list = lambda: [1,2,3] - sage: C.rank(3) # indirect doctest - 2 - """ - r = 0 - for i in self: - if i == obj: - return r - r += 1 - raise ValueError - - rank = __rank_from_iterator - - def __first_from_iterator(self): - """ - Default implementation for first which uses iterator. - - EXAMPLES:: - - sage: C = CombinatorialClass() - sage: C.list = lambda: [1,2,3] - sage: C.first() # indirect doctest - 1 - """ - for i in self: - return i - - first = __first_from_iterator - - def __last_from_iterator(self): - """ - Default implementation for first which uses iterator. - - EXAMPLES:: - - sage: C = CombinatorialClass() - sage: C.list = lambda: [1,2,3] - sage: C.last() # indirect doctest - 3 - """ - for i in self: - pass - return i - - last = __last_from_iterator - - def __next_from_iterator(self, obj): - """ - Default implementation for next which uses iterator. - - EXAMPLES:: - - sage: C = CombinatorialClass() - sage: C.list = lambda: [1,2,3] - sage: C.next(2) # indirect doctest - 3 - """ - found = False - for i in self: - if found: - return i - if i == obj: - found = True - return None - - next = __next_from_iterator - - def __previous_from_iterator(self, obj): - """ - Default implementation for next which uses iterator. - - EXAMPLES:: - - sage: C = CombinatorialClass() - sage: C.list = lambda: [1,2,3] - sage: C.previous(2) # indirect doctest - 1 - """ - prev = None - for i in self: - if i == obj: - break - prev = i - return prev - - previous = __previous_from_iterator - - def filter(self, f, name=None): - """ - Return the combinatorial subclass of f which consists of the - elements x of ``self`` such that f(x) is ``True``. - - EXAMPLES:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: P = Permutations_CC(3).filter(lambda x: x.avoids([1,2])) - sage: P.list() # needs sage.combinat - [[3, 2, 1]] - """ - return FilteredCombinatorialClass(self, f, name=name) - - def union(self, right_cc, name=None): - """ - Return the combinatorial class representing the union of ``self`` and - ``right_cc``. - - EXAMPLES:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: P = Permutations_CC(2).union(Permutations_CC(1)) - sage: P.list() - [[1, 2], [2, 1], [1]] - """ - if not isinstance(right_cc, CombinatorialClass): - raise TypeError("right_cc must be a CombinatorialClass") - return UnionCombinatorialClass(self, right_cc, name=name) - - def map(self, f, name=None, *, is_injective=True): - r""" - Return the image `\{f(x) | x \in \text{self}\}` of this combinatorial - class by `f`, as a combinatorial class. - - INPUT: - - - ``is_injective`` -- boolean (default: ``True``) whether to assume - that ``f`` is injective. - - EXAMPLES:: - - sage: R = Permutations(3).map(attrcall('reduced_word')); R - Image of Standard permutations of 3 by - The map *.reduced_word() from Standard permutations of 3 - sage: R.cardinality() - 6 - sage: R.list() - [[], [2], [1], [1, 2], [2, 1], [2, 1, 2]] - sage: [ r for r in R] - [[], [2], [1], [1, 2], [2, 1], [2, 1, 2]] - - If the function is not injective, then there may be repeated elements:: - - sage: P = Partitions(4) # needs sage.combinat - sage: P.list() # needs sage.combinat - [[4], [3, 1], [2, 2], [2, 1, 1], [1, 1, 1, 1]] - sage: P.map(len).list() # needs sage.combinat - [1, 2, 2, 3, 4] - - Use ``is_injective=False`` to get a correct result in this case:: - - sage: P.map(len, is_injective=False).list() # needs sage.combinat - [1, 2, 3, 4] - - TESTS:: - - sage: R = Permutations(3).map(attrcall('reduced_word')) - sage: R == loads(dumps(R)) - True - """ - return MapCombinatorialClass(self, f, name, is_injective=is_injective) - - -class FilteredCombinatorialClass(CombinatorialClass): - def __init__(self, combinatorial_class, f, name=None): - """ - A filtered combinatorial class F is a subset of another - combinatorial class C specified by a function f that takes in an - element c of C and returns True if and only if c is in F. - - TESTS:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: Permutations_CC(3).filter(lambda x: x.avoids([1,2])) - Filtered subclass of Standard permutations of 3 - """ - self.f = f - self.combinatorial_class = combinatorial_class - self._name = name - - def __repr__(self): - """ - EXAMPLES:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: P = Permutations_CC(3).filter(lambda x: x.avoids([1,2])) - sage: P.__repr__() - 'Filtered subclass of Standard permutations of 3' - sage: P._name = 'Permutations avoiding [1, 2]' - sage: P.__repr__() - 'Permutations avoiding [1, 2]' - """ - if self._name: - return self._name - else: - return "Filtered subclass of " + repr(self.combinatorial_class) - - def __contains__(self, x) -> bool: - """ - EXAMPLES:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: P = Permutations_CC(3).filter(lambda x: x.avoids([1,2])) - sage: 'cat' in P - False - sage: [4,3,2,1] in P - False - sage: Permutation([1,2,3]) in P # needs sage.combinat - False - sage: Permutation([3,2,1]) in P # needs sage.combinat - True - """ - return x in self.combinatorial_class and self.f(x) - - def cardinality(self) -> Integer: - """ - EXAMPLES:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: P = Permutations_CC(3).filter(lambda x: x.avoids([1,2])) - sage: P.cardinality() # needs sage.combinat - 1 - """ - c = 0 - for _ in self: - c += 1 - return c - - def __iter__(self) -> Iterator: - """ - EXAMPLES:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: P = Permutations_CC(3).filter(lambda x: x.avoids([1,2])) - sage: list(P) # needs sage.combinat - [[3, 2, 1]] - """ - for x in self.combinatorial_class: - if self.f(x): - yield x - - -class UnionCombinatorialClass(CombinatorialClass): - def __init__(self, left_cc, right_cc, name=None): - """ - A UnionCombinatorialClass is a union of two other combinatorial - classes. - - TESTS:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: P = Permutations_CC(3).union(Permutations_CC(2)) - sage: P == loads(dumps(P)) - True - """ - self.left_cc = left_cc - self.right_cc = right_cc - self._name = name - - def __repr__(self) -> str: - """ - TESTS:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: print(repr(Permutations_CC(3).union(Permutations_CC(2)))) - Union combinatorial class of - Standard permutations of 3 - and - Standard permutations of 2 - """ - if self._name: - return self._name - else: - return "Union combinatorial class of \n %s\nand\n %s" % (self.left_cc, self.right_cc) - - def __contains__(self, x) -> bool: - """ - EXAMPLES:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: P = Permutations_CC(3).union(Permutations_CC(2)) - sage: [1,2] in P - True - sage: [3,2,1] in P - True - sage: [1,2,3,4] in P - False - """ - return x in self.left_cc or x in self.right_cc - - def cardinality(self) -> Integer | infinity: - """ - EXAMPLES:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: P = Permutations_CC(3).union(Permutations_CC(2)) - sage: P.cardinality() - 8 - """ - return self.left_cc.cardinality() + self.right_cc.cardinality() - - def list(self): - """ - EXAMPLES:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: P = Permutations_CC(3).union(Permutations_CC(2)) - sage: P.list() - [[1, 2, 3], - [1, 3, 2], - [2, 1, 3], - [2, 3, 1], - [3, 1, 2], - [3, 2, 1], - [1, 2], - [2, 1]] - """ - return self.left_cc.list() + self.right_cc.list() - - def __iter__(self) -> Iterator: - """ - EXAMPLES:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: P = Permutations_CC(3).union(Permutations_CC(2)) - sage: list(P) - [[1, 2, 3], - [1, 3, 2], - [2, 1, 3], - [2, 3, 1], - [3, 1, 2], - [3, 2, 1], - [1, 2], - [2, 1]] - """ - for x in self.left_cc: - yield x - for x in self.right_cc: - yield x - - def first(self): - """ - EXAMPLES:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: P = Permutations_CC(3).union(Permutations_CC(2)) - sage: P.first() - [1, 2, 3] - """ - return self.left_cc.first() - - def last(self): - """ - EXAMPLES:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: P = Permutations_CC(3).union(Permutations_CC(2)) - sage: P.last() - [2, 1] - """ - return self.right_cc.last() - - def rank(self, x): - """ - EXAMPLES:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: P = Permutations_CC(3).union(Permutations_CC(2)) - sage: P.rank(Permutation([2,1])) - 7 - sage: P.rank(Permutation([1,2,3])) - 0 - """ - try: - return self.left_cc.rank(x) - except (TypeError, ValueError): - return self.left_cc.cardinality() + self.right_cc.rank(x) - - def unrank(self, x): - """ - EXAMPLES:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: P = Permutations_CC(3).union(Permutations_CC(2)) - sage: P.unrank(7) - [2, 1] - sage: P.unrank(0) - [1, 2, 3] - """ - try: - return self.left_cc.unrank(x) - except (TypeError, ValueError): - return self.right_cc.unrank(x - self.left_cc.cardinality()) - - -class Permutations_CC(CombinatorialClass): - """ - A testing class for :class:`CombinatorialClass` since :class:`Permutations` - no longer inherits from :class:`CombinatorialClass` in :issue:`14772`. - """ - - def __init__(self, n): - """ - EXAMPLES:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: P = Permutations_CC(4) - sage: loads(dumps(P)) == P - True - """ - from sage.combinat.permutation import StandardPermutations_n - self._permutations = StandardPermutations_n(n) - - def __repr__(self) -> str: - """ - EXAMPLES:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: Permutations_CC(3) - Standard permutations of 3 - """ - return repr(self._permutations) - - def __contains__(self, x) -> bool: - """ - EXAMPLES:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: P = Permutations_CC(3) - sage: [1, 3, 2] in P - True - """ - return x in self._permutations - - def __iter__(self): - """ - EXAMPLES:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: P = Permutations_CC(3) - sage: P.list() - [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]] - """ - return iter(self._permutations) - - -############################################################################## -from sage.sets.image_set import ImageSubobject - - -class MapCombinatorialClass(ImageSubobject, CombinatorialClass): - r""" - The image of a combinatorial class through a function. - - INPUT: - - - ``is_injective`` -- boolean (default: ``True``) whether to assume - that ``f`` is injective. - - See :meth:`CombinatorialClass.map` for examples - - EXAMPLES:: - - sage: # needs sage.groups - sage: R = SymmetricGroup(10).map(attrcall('reduced_word')) - sage: R.an_element() - [9, 8, 7, 6, 5, 4, 3, 2] - sage: R.cardinality() - 3628800 - sage: i = iter(R) - sage: next(i), next(i), next(i) - ([], [1, 2, 3, 4, 5, 6, 7, 8, 9], [1]) - """ - - def __init__(self, cc, f, name=None, *, is_injective=True): - """ - TESTS:: - - sage: Partitions(3).map(attrcall('conjugate')) # needs sage.combinat - Image of Partitions of the integer 3 by The map *.conjugate() - from Partitions of the integer 3 - """ - ImageSubobject.__init__(self, f, cc, is_injective=is_injective) - self.cc = cc - self.f = f - if name: - self.rename(name) - - -############################################################################## -class InfiniteAbstractCombinatorialClass(CombinatorialClass): - r""" - This is an internal class that should not be used directly. A class which - inherits from InfiniteAbstractCombinatorialClass inherits the standard - methods list and count. - - If self._infinite_cclass_slice exists then self.__iter__ returns an - iterator for self, otherwise raise NotImplementedError. The method - self._infinite_cclass_slice is supposed to accept any integer as an - argument and return something which is iterable. - """ - - def cardinality(self) -> Integer | infinity: - """ - Count the elements of the combinatorial class. - - EXAMPLES:: - - sage: R = InfiniteAbstractCombinatorialClass() - doctest:warning... - DeprecationWarning: this class is deprecated, do not use - See https://github.com/sagemath/sage/issues/31545 for details. - - sage: R.cardinality() - +Infinity - """ - return infinity - - def list(self): - """ - Return an error since ``self`` is an infinite combinatorial class. - - EXAMPLES:: - - sage: R = InfiniteAbstractCombinatorialClass() - sage: R.list() - Traceback (most recent call last): - ... - NotImplementedError: infinite list - """ - raise NotImplementedError("infinite list") - - def __iter__(self) -> Iterator: - """ - Return an iterator for the infinite combinatorial class ``self`` if - possible or raise a NotImplementedError. - - EXAMPLES:: - - sage: R = InfiniteAbstractCombinatorialClass() - sage: next(iter(R)) - Traceback (most recent call last): - ... - NotImplementedError - - sage: c = iter(Compositions()) # indirect doctest - sage: next(c), next(c), next(c), next(c), next(c), next(c) - ([], [1], [1, 1], [2], [1, 1, 1], [1, 2]) - sage: next(c), next(c), next(c), next(c), next(c), next(c) - ([2, 1], [3], [1, 1, 1, 1], [1, 1, 2], [1, 2, 1], [1, 3]) - """ - try: - finite = self._infinite_cclass_slice - except AttributeError: - raise NotImplementedError - i = 0 - while True: - yield from finite(i) - i += 1 - - ##################################################### # combinatorial sets/lists From ec050a1d1cd2b4a9997979706f4596e0c510b5c2 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 2 Apr 2024 00:03:18 -0700 Subject: [PATCH 03/14] Remove CombinatorialClass --- src/sage/combinat/all.py | 10 +--------- src/sage/combinat/crystals/tensor_product.py | 2 -- src/sage/sets/family.pyx | 4 +--- 3 files changed, 2 insertions(+), 14 deletions(-) diff --git a/src/sage/combinat/all.py b/src/sage/combinat/all.py index d1f391013e2..4d7a4e060e9 100644 --- a/src/sage/combinat/all.py +++ b/src/sage/combinat/all.py @@ -56,9 +56,7 @@ from sage.misc.lazy_import import lazy_import -from .combinat import (CombinatorialClass, CombinatorialObject, - MapCombinatorialClass, - bell_number, bell_polynomial, bernoulli_polynomial, +from .combinat import (bell_number, bell_polynomial, bernoulli_polynomial, catalan_number, euler_number, fibonacci, fibonacci_sequence, fibonacci_xrange, lucas_number1, lucas_number2, @@ -66,12 +64,6 @@ polygonal_number, stirling_number1, stirling_number2, tuples, unordered_tuples) -lazy_import('sage.combinat.combinat', - ('InfiniteAbstractCombinatorialClass', 'UnionCombinatorialClass', - 'FilteredCombinatorialClass'), - deprecation=(31545, 'this class is deprecated, do not use')) - - from .expnums import expnums from sage.combinat.chas.all import * diff --git a/src/sage/combinat/crystals/tensor_product.py b/src/sage/combinat/crystals/tensor_product.py index 726c974e781..e0482ec74eb 100644 --- a/src/sage/combinat/crystals/tensor_product.py +++ b/src/sage/combinat/crystals/tensor_product.py @@ -583,8 +583,6 @@ def __iter__(self): for x in self.cartesian_product: yield self(*x) -# list = CombinatorialClass._CombinatorialClass__list_from_iterator - def cardinality(self): """ Return the cardinality of ``self``. diff --git a/src/sage/sets/family.pyx b/src/sage/sets/family.pyx index 36a6b0d6b1f..9579c7caab6 100644 --- a/src/sage/sets/family.pyx +++ b/src/sage/sets/family.pyx @@ -55,8 +55,6 @@ from sage.rings.integer import Integer from sage.sets.finite_enumerated_set import FiniteEnumeratedSet from sage.sets.non_negative_integers import NonNegativeIntegers -CombinatorialClass = LazyImport('sage.combinat.combinat', 'CombinatorialClass') - def Family(indices, function=None, hidden_keys=[], hidden_function=None, lazy=False, name=None): r""" @@ -971,7 +969,7 @@ class LazyFamily(AbstractFamily): category = FiniteEnumeratedSets() elif set in InfiniteEnumeratedSets(): category = InfiniteEnumeratedSets() - elif isinstance(set, (list, tuple, range, CombinatorialClass)): + elif isinstance(set, (list, tuple, range)): category = FiniteEnumeratedSets() else: category = EnumeratedSets() From 9617d44c35c935f5d444c1b89b037d1379d429da Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 2 Apr 2024 00:05:21 -0700 Subject: [PATCH 04/14] src/sage/combinat/species/structure.py: Remove use of CombinatorialClass --- src/sage/combinat/species/structure.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/species/structure.py b/src/sage/combinat/species/structure.py index b3003ed0f13..e7e529b0373 100644 --- a/src/sage/combinat/species/structure.py +++ b/src/sage/combinat/species/structure.py @@ -37,8 +37,10 @@ # # http://www.gnu.org/licenses/ #***************************************************************************** -from sage.combinat.combinat import CombinatorialClass, CombinatorialObject +from sage.categories.enumerated_sets import EnumeratedSets +from sage.combinat.combinat import CombinatorialObject from sage.rings.integer import Integer +from sage.structure.parent import Parent from copy import copy @@ -326,7 +328,7 @@ def change_labels(self, labels): ############################################################## -class SpeciesWrapper(CombinatorialClass): +class SpeciesWrapper(Parent): def __init__(self, species, labels, iterator, generating_series, name, structure_class): """ This is a abstract base class for the set of structures of a @@ -350,6 +352,7 @@ def __init__(self, species, labels, iterator, generating_series, name, structure sage: S.cardinality() 1 """ + Parent.__init__(self, category=EnumeratedSets()) self._species = species self._labels = labels self._iterator = iterator From a57171a9f31db72141f0eafda5d3eebb129915df Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 2 Apr 2024 00:25:23 -0700 Subject: [PATCH 05/14] src/sage/combinat/combinat.py: Remove unused imports --- src/sage/combinat/combinat.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/sage/combinat/combinat.py b/src/sage/combinat/combinat.py index 3bd938d9742..382c915db77 100644 --- a/src/sage/combinat/combinat.py +++ b/src/sage/combinat/combinat.py @@ -180,8 +180,6 @@ from sage.misc.lazy_import import lazy_import from sage.misc.lazy_attribute import lazy_attribute from .combinat_cython import _stirling_number2 -from sage.categories.enumerated_sets import EnumeratedSets -from sage.misc.classcall_metaclass import ClasscallMetaclass from sage.misc.inherit_comparison import InheritComparisonClasscallMetaclass from sage.structure.element import Element From dcf5b3d97f3cc4c25a7e4ba997f1b534bed661be Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 2 Apr 2024 00:38:05 -0700 Subject: [PATCH 06/14] src/sage/combinat/species/structure.py (SpeciesWrapper): Use category=EnumeratedSets().Finite() --- src/sage/combinat/species/structure.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/combinat/species/structure.py b/src/sage/combinat/species/structure.py index e7e529b0373..64c4f069436 100644 --- a/src/sage/combinat/species/structure.py +++ b/src/sage/combinat/species/structure.py @@ -352,7 +352,7 @@ def __init__(self, species, labels, iterator, generating_series, name, structure sage: S.cardinality() 1 """ - Parent.__init__(self, category=EnumeratedSets()) + Parent.__init__(self, category=EnumeratedSets().Finite()) self._species = species self._labels = labels self._iterator = iterator From 783df77c9218749f2b9891e7e4aa99d2cc488424 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 2 Apr 2024 09:53:31 -0700 Subject: [PATCH 07/14] src/sage/combinat/all.py: Restore CombinatorialObject --- src/sage/combinat/all.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage/combinat/all.py b/src/sage/combinat/all.py index 4d7a4e060e9..4ab35568bb4 100644 --- a/src/sage/combinat/all.py +++ b/src/sage/combinat/all.py @@ -56,7 +56,8 @@ from sage.misc.lazy_import import lazy_import -from .combinat import (bell_number, bell_polynomial, bernoulli_polynomial, +from .combinat import (CombinatorialObject, + bell_number, bell_polynomial, bernoulli_polynomial, catalan_number, euler_number, fibonacci, fibonacci_sequence, fibonacci_xrange, lucas_number1, lucas_number2, From b9fe3627524e15918d137770d0ce600ed12fc631 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 2 Apr 2024 12:56:38 -0700 Subject: [PATCH 08/14] src/sage/combinat/root_system/root_lattice_realizations.py: Make positive_roots in finite type a *finite* enumerated set; make negative_roots an *injective* image --- src/sage/combinat/root_system/root_lattice_realizations.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/root_system/root_lattice_realizations.py b/src/sage/combinat/root_system/root_lattice_realizations.py index ea1f4d285b2..930450b4a18 100644 --- a/src/sage/combinat/root_system/root_lattice_realizations.py +++ b/src/sage/combinat/root_system/root_lattice_realizations.py @@ -18,6 +18,7 @@ from sage.misc.lazy_import import LazyImport from sage.categories.coxeter_groups import CoxeterGroups from sage.categories.category_types import Category_over_base_ring +from sage.categories.enumerated_sets import EnumeratedSets from sage.categories.modules_with_basis import ModulesWithBasis from sage.structure.element import Element from sage.sets.family import Family @@ -715,7 +716,8 @@ def positive_roots(self, index_set=None): index_set = tuple(self.cartan_type().index_set()) return RecursivelyEnumeratedSet([self.simple_root(i) for i in index_set], attrcall('pred', index_set=index_set), - structure='graded', enumeration='breadth') + structure='graded', enumeration='breadth', + category=EnumeratedSets().Finite()) @cached_method def nonparabolic_positive_roots(self, index_set=None): @@ -1229,7 +1231,7 @@ def negative_roots(self): """ if not self.cartan_type().is_finite(): raise ValueError("%s is not a finite Cartan type" % self.cartan_type()) - return self.positive_roots().map(attrcall('__neg__')) + return self.positive_roots().map(attrcall('__neg__'), is_injective=True) ########################################################################## # coroots From 0ee560ca1e57cdb4b5d842e0416fb74166bf2815 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 2 Apr 2024 20:30:49 -0700 Subject: [PATCH 09/14] ImageSubobject: Implement __eq__, __ne__ --- src/sage/sets/image_set.py | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/sage/sets/image_set.py b/src/sage/sets/image_set.py index 0472d793c19..483ad6bff4b 100644 --- a/src/sage/sets/image_set.py +++ b/src/sage/sets/image_set.py @@ -7,7 +7,7 @@ # 2012 Christian Stump # 2020-2021 Frédéric Chapoton # 2021 Travis Scrimshaw -# 2021 Matthias Koeppe +# 2021-2024 Matthias Koeppe # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -131,6 +131,40 @@ def map(arg): self._domain_subset = domain_subset self._is_injective = is_injective + def __eq__(self, other): + r""" + EXAMPLES:: + + sage: from sage.sets.image_set import ImageSubobject + sage: D = ZZ + sage: def f(x): + ....: return 2 * x + sage: I = ImageSubobject(f, ZZ) + sage: I == ImageSubobject(f, ZZ) + True + """ + if not isinstance(other, ImageSubobject): + return False + return (self._map == other._map + and self._inverse == other._inverse + and self._domain_subset == other._domain_subset + and self._is_injective == other._is_injective + and self.category() == other.category()) + + def __ne__(self, other): + r""" + EXAMPLES:: + + sage: from sage.sets.image_set import ImageSubobject + sage: D = ZZ + sage: def f(x): + ....: return 2 * x + sage: I = ImageSubobject(f, ZZ) + sage: I != ImageSubobject(f, QQ) + True + """ + return not (self == other) + def _element_constructor_(self, x): """ EXAMPLES:: From 29180c08a90df76859676c2dae93ec02def60891 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 2 Apr 2024 23:51:33 -0700 Subject: [PATCH 10/14] SpeciesWrapper: Add _repr_ method --- src/sage/combinat/species/structure.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/sage/combinat/species/structure.py b/src/sage/combinat/species/structure.py index 64c4f069436..fd18e4bd48b 100644 --- a/src/sage/combinat/species/structure.py +++ b/src/sage/combinat/species/structure.py @@ -360,6 +360,18 @@ def __init__(self, species, labels, iterator, generating_series, name, structure self._name = "%s for %s with labels %s" % (name, species, labels) self._structure_class = structure_class if structure_class is not None else species._default_structure_class + def _repr_(self) -> str: + """ + EXAMPLES:: + + sage: from sage.combinat.species.structure import SpeciesWrapper + sage: F = species.SetSpecies() + sage: S = SpeciesWrapper(F, [1,2,3], "_structures", "generating_series", 'Structures', None) + sage: repr(S) # indirect doctest + 'Structures for Set species with labels [1, 2, 3]' + """ + return self._name + def labels(self): """ Returns the labels used on these structures. If `X` is the From dcdd8a29955a320fdb60499c7577a904f8d2df36 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 3 Apr 2024 12:19:08 -0700 Subject: [PATCH 11/14] CartesianProduct_iters: Make hashable if factors are --- src/sage/combinat/cartesian_product.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/sage/combinat/cartesian_product.py b/src/sage/combinat/cartesian_product.py index c5a16d3f8ee..ef151e55ffa 100644 --- a/src/sage/combinat/cartesian_product.py +++ b/src/sage/combinat/cartesian_product.py @@ -112,6 +112,16 @@ def iterfunc(): category=category, cache=False) + def __hash__(self): + r""" + EXAMPLES:: + + sage: from sage.combinat.cartesian_product import CartesianProduct_iters + sage: cp = CartesianProduct_iters((1,2), (3,4)) + sage: hash(cp) == CartesianProduct_iters((1,2), (3,4)) + """ + return hash(tuple(self.iters)) + def __contains__(self, x): """ EXAMPLES:: From fb84820ed1f6c6e757b96d9a4539801ab924f3ef Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 28 Apr 2024 12:38:46 -0700 Subject: [PATCH 12/14] ImageSubobject.__hash__: New --- src/sage/combinat/free_module.py | 2 +- src/sage/sets/image_set.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/sage/combinat/free_module.py b/src/sage/combinat/free_module.py index 12bbd4385db..069ebb88fb6 100644 --- a/src/sage/combinat/free_module.py +++ b/src/sage/combinat/free_module.py @@ -1381,7 +1381,7 @@ def __init__(self, modules, **options): """ self._sets = modules indices = CartesianProduct_iters(*[module.basis().keys() - for module in modules]).map(tuple) + for module in modules]).map(tuple, is_injective=True) 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: diff --git a/src/sage/sets/image_set.py b/src/sage/sets/image_set.py index 483ad6bff4b..acdf1bd0507 100644 --- a/src/sage/sets/image_set.py +++ b/src/sage/sets/image_set.py @@ -165,6 +165,20 @@ def __ne__(self, other): """ return not (self == other) + def __hash__(self): + r""" + TESTS:: + + sage: from sage.sets.image_set import ImageSubobject + sage: def f(x): + ....: return 2 * x + sage: I = ImageSubobject(f, ZZ) + sage: hash(I) == hash(ImageSubobject(f, ZZ)) + True + """ + return hash((self._map, self._inverse, self._domain_subset, + self._is_injective, self.category())) + def _element_constructor_(self, x): """ EXAMPLES:: From 18eae6938d8c365fda799000e1fdb49cee3ca799 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 3 Apr 2024 19:21:44 -0700 Subject: [PATCH 13/14] src/sage/combinat/species/structure.py (SpeciesWrapper): Add __eq__, __ne__ methods --- src/sage/combinat/species/structure.py | 28 ++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/sage/combinat/species/structure.py b/src/sage/combinat/species/structure.py index fd18e4bd48b..c5629212f5d 100644 --- a/src/sage/combinat/species/structure.py +++ b/src/sage/combinat/species/structure.py @@ -360,6 +360,34 @@ def __init__(self, species, labels, iterator, generating_series, name, structure self._name = "%s for %s with labels %s" % (name, species, labels) self._structure_class = structure_class if structure_class is not None else species._default_structure_class + def __eq__(self, other) -> bool: + r""" + EXAMPLES:: + + sage: from sage.combinat.species.structure import SpeciesWrapper + sage: F = species.SetSpecies() + sage: S = SpeciesWrapper(F, [1,2,3], "_structures", "generating_series", 'Structures', None) + sage: S == SpeciesWrapper(F, [1,2,3], "_structures", "generating_series", 'Structures', None) + True + """ + return ((self._species, self._labels, + self._iterator, self._generating_series, + self._name, self._structure_class) == (other._species, other._labels, + other._iterator, other._generating_series, + other._name, other._structure_class)) + + def __ne__(self, other) -> bool: + r""" + EXAMPLES:: + + sage: from sage.combinat.species.structure import SpeciesWrapper + sage: F = species.SetSpecies() + sage: S = SpeciesWrapper(F, [1,2,3], "_structures", "generating_series", 'Structures', None) + sage: S != SpeciesWrapper(F, [1,2,3], "_structures", "generating_series", 'Structures', None) + False + """ + return not (self == other) + def _repr_(self) -> str: """ EXAMPLES:: From 094c106b8bfc3466699e824c6a0a137624385603 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 4 Apr 2024 10:13:07 -0700 Subject: [PATCH 14/14] ImageSubobject.{__eq__,__ne__,__hash__}: Only use domain_subset and map --- src/sage/sets/image_set.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/sage/sets/image_set.py b/src/sage/sets/image_set.py index acdf1bd0507..c98fc8c534b 100644 --- a/src/sage/sets/image_set.py +++ b/src/sage/sets/image_set.py @@ -142,14 +142,25 @@ def __eq__(self, other): sage: I = ImageSubobject(f, ZZ) sage: I == ImageSubobject(f, ZZ) True + + This method does not take into account whether an inverse is provided, + injectivity is declared, or the category:: + + sage: def f_inv(y): + ....: return y // 2 + sage: I == ImageSubobject(f, ZZ, inverse=f_inv) + True + sage: I == ImageSubobject(f, ZZ, is_injective=True) + True + sage: I.category() + Category of enumerated subobjects of sets + sage: I == ImageSubobject(f, ZZ, category=EnumeratedSets().Infinite()) + True """ if not isinstance(other, ImageSubobject): return False return (self._map == other._map - and self._inverse == other._inverse - and self._domain_subset == other._domain_subset - and self._is_injective == other._is_injective - and self.category() == other.category()) + and self._domain_subset == other._domain_subset) def __ne__(self, other): r""" @@ -176,8 +187,7 @@ def __hash__(self): sage: hash(I) == hash(ImageSubobject(f, ZZ)) True """ - return hash((self._map, self._inverse, self._domain_subset, - self._is_injective, self.category())) + return hash((self._map, self._domain_subset)) def _element_constructor_(self, x): """