Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve is_empty and is_finite in a few cases #39444

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/sage/categories/homset.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,8 @@ def Hom(X, Y, category=None, check=True):
sage: R = Set_PythonType(int)
sage: S = Set_PythonType(float)
sage: Hom(R, S)
Set of Morphisms from Set of Python objects of class 'int' to Set of Python objects of class 'float' in Category of sets
Set of Morphisms from Set of Python objects of class 'int'
to Set of Python objects of class 'float' in Category of infinite sets

Checks that the domain and codomain are in the specified
category. Case of a non parent::
Expand Down
76 changes: 67 additions & 9 deletions src/sage/categories/sets_cat.py
Original file line number Diff line number Diff line change
Expand Up @@ -2373,8 +2373,32 @@
False
sage: cartesian_product([S1,S2,S1]).is_empty()
True
"""
return any(c.is_empty() for c in self.cartesian_factors())

Even when some parent did not implement ``is_empty``,
as long as one element is nonempty, the result can be determined::

sage: C = ConditionSet(QQ, lambda x: x > 0)
sage: C.is_empty()
Traceback (most recent call last):
...
AttributeError...
sage: cartesian_product([C,[]]).is_empty()
True
sage: cartesian_product([C,C]).is_empty()
Traceback (most recent call last):
...
NotImplementedError...
"""
last_exception = None
for c in self.cartesian_factors():
try:
if c.is_empty():
return True
except (AttributeError, NotImplementedError) as e:
last_exception = e
if last_exception is not None:
raise NotImplementedError from last_exception
return False

def is_finite(self):
r"""
Expand All @@ -2391,18 +2415,52 @@
False
sage: cartesian_product([ZZ, Set(), ZZ]).is_finite()
True

TESTS:

This should still work even if some parent does not implement
``is_finite``::

sage: known_infinite_set = ZZ
sage: unknown_infinite_set = Set([1]) + ConditionSet(QQ, lambda x: x > 0)
sage: unknown_infinite_set.is_empty()
False
sage: unknown_infinite_set.is_finite()
Traceback (most recent call last):
...
AttributeError...
sage: cartesian_product([unknown_infinite_set, known_infinite_set]).is_finite()
False
sage: unknown_empty_set = ConditionSet(QQ, lambda x: False)
sage: cartesian_product([known_infinite_set, unknown_empty_set]).is_finite()
Traceback (most recent call last):
...
NotImplementedError...
sage: cartesian_product([unknown_infinite_set, Set([])]).is_finite()
True
"""
f = self.cartesian_factors()
try:
# Note: some parent might not implement "is_empty". So we
# carefully isolate this test.
test = any(c.is_empty() for c in f)
if self.is_empty():
return True
except (AttributeError, NotImplementedError):
pass
else:
if test:
return test
return all(c.is_finite() for c in f)
# it is unknown whether some set may be empty
if all(c.is_finite() for c in self.cartesian_factors()):
return True

Check warning on line 2450 in src/sage/categories/sets_cat.py

View check run for this annotation

Codecov / codecov/patch

src/sage/categories/sets_cat.py#L2450

Added line #L2450 was not covered by tests
raise NotImplementedError

# in this case, all sets are definitely nonempty
last_exception = None
for c in self.cartesian_factors():
try:
if not c.is_finite():
return False
except (AttributeError, NotImplementedError) as e:
last_exception = e
if last_exception is not None:
raise NotImplementedError from last_exception
return True

Check warning on line 2463 in src/sage/categories/sets_cat.py

View check run for this annotation

Codecov / codecov/patch

src/sage/categories/sets_cat.py#L2461-L2463

Added lines #L2461 - L2463 were not covered by tests

def cardinality(self):
r"""
Expand Down
30 changes: 30 additions & 0 deletions src/sage/combinat/words/words.py
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,37 @@ class FiniteWords(AbstractLanguage):
sage: W = FiniteWords('ab')
sage: W
Finite words over {'a', 'b'}

TESTS::

sage: FiniteWords('ab').is_finite()
False
sage: FiniteWords([]).is_finite()
True
"""

def __init__(self, alphabet=None, category=None):
if category is None:
category = Sets()
if alphabet:
category = category.Infinite()
else:
category = category.Finite()
super().__init__(alphabet, category)

def is_empty(self):
"""
Return False, because the empty word is in the set.

TESTS::

sage: FiniteWords('ab').is_empty()
False
sage: FiniteWords([]).is_empty()
False
"""
return False

def cardinality(self):
r"""
Return the cardinality of this set.
Expand Down
10 changes: 8 additions & 2 deletions src/sage/sets/family.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -968,8 +968,14 @@ class LazyFamily(AbstractFamily):
category = InfiniteEnumeratedSets()
elif isinstance(set, (list, tuple, range)):
category = FiniteEnumeratedSets()
else:
category = EnumeratedSets()
else: # some sets such as QQ implements is_finite() but is not in InfiniteEnumeratedSets()
try:
if set.is_finite():
category = FiniteEnumeratedSets()
else:
category = InfiniteEnumeratedSets()
except (AttributeError, NotImplementedError):
category = EnumeratedSets()

Parent.__init__(self, category=category)

Expand Down
6 changes: 3 additions & 3 deletions src/sage/sets/pythonclass.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ cpdef Set_PythonType(typ):
S is a parent which models the set of all lists::

sage: S.category()
Category of sets
Category of infinite sets
"""
try:
return _type_set_cache[typ]
Expand Down Expand Up @@ -82,11 +82,11 @@ cdef class Set_PythonType_class(Set_generic):

sage: from sage.sets.pythonclass import Set_PythonType
sage: Set_PythonType(float).category()
Category of sets
Category of infinite sets
"""
if not isinstance(typ, type):
raise TypeError(f"must be initialized with a class, not {typ!r}")
super().__init__(category=Sets())
super().__init__(category=Sets().Finite() if typ is bool else Sets().Infinite())
self._type = <type>typ

def _element_constructor_(self, *args, **kwds):
Expand Down
32 changes: 32 additions & 0 deletions src/sage/sets/set.py
Original file line number Diff line number Diff line change
Expand Up @@ -1520,6 +1520,38 @@ def _sympy_(self):
sympy_init()
return Union(self._X._sympy_(), self._Y._sympy_())

def __bool__(self):
"""
Return ``True`` if this set is not empty.

EXAMPLES::

sage: bool(Set(GF(3)).union(Set(GF(2))))
True
sage: bool(Set(GF(3)).intersection(Set(GF(2))))
False

TESTS:

This should still work in the case the first set is nonempty
and the second set has :meth:`is_empty` unimplemented::

sage: C = ConditionSet(QQ, lambda x: x > 0)
sage: C.is_empty()
Traceback (most recent call last):
...
AttributeError...
sage: C.is_finite()
Traceback (most recent call last):
...
AttributeError...
sage: bool(Set([1]) + C)
True
sage: (Set([1]) + C).is_empty()
False
"""
return bool(self._X) or bool(self._Y)


class Set_object_intersection(Set_object_binary):
"""
Expand Down
Loading