diff --git a/src/sage/categories/sets_cat.py b/src/sage/categories/sets_cat.py index ba13c6bc9a4..89a23b7fc4e 100644 --- a/src/sage/categories/sets_cat.py +++ b/src/sage/categories/sets_cat.py @@ -2373,8 +2373,32 @@ def is_empty(self): 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""" @@ -2391,18 +2415,52 @@ def is_finite(self): 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 + 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 def cardinality(self): r""" diff --git a/src/sage/sets/family.pyx b/src/sage/sets/family.pyx index 0ccc4606ded..8e4659658cc 100644 --- a/src/sage/sets/family.pyx +++ b/src/sage/sets/family.pyx @@ -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) diff --git a/src/sage/sets/set.py b/src/sage/sets/set.py index 123c4366af0..2b39a23f4c0 100644 --- a/src/sage/sets/set.py +++ b/src/sage/sets/set.py @@ -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): """