From 9b3789c61e3532b148cd99c1cbaf7e8568cd22e4 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sat, 1 Nov 2025 10:40:05 +0100 Subject: [PATCH 01/26] initial implementation with documentation --- src/sage/sets/primes.py | 408 +++++++++++++++++++++++++--------------- 1 file changed, 258 insertions(+), 150 deletions(-) diff --git a/src/sage/sets/primes.py b/src/sage/sets/primes.py index ac33901a73b..84ba396519a 100644 --- a/src/sage/sets/primes.py +++ b/src/sage/sets/primes.py @@ -1,187 +1,295 @@ """ -The set of prime numbers +The set of prime numbers and its subsets defined by congruence conditions AUTHORS: - William Stein (2005): original version - - Florent Hivert (2009-11): adapted to the category framework. + - Florent Hivert (2009-11): adapted to the category framework + - Xavier Caruso (2025-10): implement congruence conditions """ + # **************************************************************************** # Copyright (C) 2005 William Stein # 2009 Florent Hivert +# 2025 Xavier Caruso # # Distributed under the terms of the GNU General Public License (GPL) # # https://www.gnu.org/licenses/ # **************************************************************************** + from sage.rings.integer_ring import ZZ from .set import Set_generic +from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets from sage.categories.infinite_enumerated_sets import InfiniteEnumeratedSets -from sage.arith.misc import nth_prime +from sage.arith.misc import euler_phi from sage.structure.unique_representation import UniqueRepresentation class Primes(Set_generic, UniqueRepresentation): - """ - The set of prime numbers. - - EXAMPLES:: - - sage: P = Primes(); P - Set of all prime numbers: 2, 3, 5, 7, ... - - We show various operations on the set of prime numbers:: - - sage: P.cardinality() - +Infinity - sage: R = Primes() - sage: P == R - True - sage: 5 in P - True - sage: 100 in P - False - - sage: len(P) - Traceback (most recent call last): - ... - NotImplementedError: infinite set - """ @staticmethod - def __classcall__(cls, proof=True): - """ - TESTS:: - - sage: Primes(proof=True) is Primes() - True - sage: Primes(proof=False) is Primes() - False - """ - return super().__classcall__(cls, proof) - - def __init__(self, proof): - """ - EXAMPLES:: - - sage: P = Primes(); P - Set of all prime numbers: 2, 3, 5, 7, ... - - sage: Q = Primes(proof=False); Q - Set of all prime numbers: 2, 3, 5, 7, ... - - TESTS:: - - sage: P.category() - Category of facade infinite enumerated sets - sage: TestSuite(P).run() # needs sage.libs.pari - - sage: Q.category() - Category of facade infinite enumerated sets - sage: TestSuite(Q).run() # needs sage.libs.pari - - The set of primes can be compared to various things, - but is only equal to itself:: - - sage: P = Primes() - sage: R = Primes() - sage: P == R - True - sage: P != R - False - sage: Q = [1,2,3] - sage: Q != P # indirect doctest - True - sage: R. = ZZ[] - sage: P != x^2+x - True - """ - super().__init__(facade=ZZ, - category=InfiniteEnumeratedSets()) - self.__proof = proof + def __classcall_private__(cls, modulus=1, classes=None, exceptions=None): + modulus = ZZ(modulus) + if modulus == 0: + raise ValueError("modulus must be nonzero") + if modulus < 0: + modulus = -modulus + if classes is None: + classes = [1] + indic = modulus * [False] + if exceptions is None: + exceptions = {} + for c in classes: + indic[ZZ(c) % modulus] = True + for c in range(modulus): + g = modulus.gcd(c) + if g > 1: + if indic[c]: + if c == 0: + if modulus not in exceptions: + exceptions[modulus] = True + else: + if c not in exceptions: + exceptions[c] = True + indic[c] = None + for p, mult in modulus.factor(): + while mult > 0: + m = modulus // p + add_true = [] + add_false = [] + add_excluded = [] + for c in range(m): + cs = [indic[c + m*i] for i in range(p) if indic[c + m*i] is not None] + if not cs: + pass + elif all(cs): + if m.gcd(c) == 1: + add_true.append(c) + for i in range(p): + j = c + m*i + if indic[j] is None: + if j == 0: + add_excluded.append(modulus) + else: + add_excluded.append(j) + elif not any(cs): + if m.gcd(c) == 1: + add_false.append(c) + else: + mult = 0 + break + else: + for c in add_true: + indic[c] = True + for c in add_false: + indic[c] = False + for c in add_excluded: + if c not in exceptions: + exceptions[c] = False + modulus = m + mult -= 1 + classes = tuple([c for c in range(modulus) if indic[c] is True]) + excep = [] + for c, v in list(exceptions.items()): + c = ZZ(c) + if c.is_prime() and (v != (indic[c % modulus] is True)): + excep.append((c, v)) + excep.sort() + return cls.__classcall__(cls, modulus, classes, tuple(excep)) + + def __init__(self, modulus, classes, exceptions): + if classes: + category = InfiniteEnumeratedSets() + else: + category = FiniteEnumeratedSets() + super().__init__(facade=ZZ, category=category) + self._modulus = modulus + self._classes = set(classes) + self._exceptions = {} + self._included = [] + self._excluded = [] + for c, v in exceptions: + self._exceptions[c] = v + if v: + self._included.append(c) + else: + self._excluded.append(c) + self._included.sort() + self._excluded.sort() def _repr_(self): - """ - Representation of the set of primes. - - EXAMPLES:: - - sage: P = Primes(); P - Set of all prime numbers: 2, 3, 5, 7, ... - """ - return "Set of all prime numbers: 2, 3, 5, 7, ..." + classes = sorted(list(self._classes)) + sc = ", ".join([str(c) for c in classes]) + si = ", ".join([str(i) for i in self._included]) + se = ", ".join([str(e) for e in self._excluded]) + if sc == "": + if si == "": + return "Empty set of primes" + else: + return "Finite set of primes: %s" % si + if self._modulus == 1: + s = "Set of all prime numbers" + else: + s = "Set of prime numbers congruent to %s modulo %s" % (sc, self._modulus) + if si != "": + s += " with %s included" % si + if se != "": + if si == "": + s += " with %s excluded" % se + else: + s += " and %s excluded" % se + s += ": %s, ..." % (", ".join([str(c) for c in self.first_n(4)])) + return s def __contains__(self, x): - """ - Check whether an object is a prime number. - - EXAMPLES:: - - sage: P = Primes() - sage: 5 in P - True - sage: 100 in P - False - sage: 1.5 in P - False - sage: e in P # needs sage.symbolic - False - """ try: if x not in ZZ: return False - return ZZ(x).is_prime() except TypeError: return False - - def _an_element_(self): - """ - Return a typical prime number. - - EXAMPLES:: - - sage: P = Primes() - sage: P._an_element_() - 43 - """ - return ZZ(43) - - def first(self): - """ - Return the first prime number. - - EXAMPLES:: - - sage: P = Primes() - sage: P.first() - 2 - """ - return ZZ(2) + x = ZZ(x) + if not x.is_prime(): + return False + e = self._exceptions.get(x, None) + return (e is True) or (e is None and x % self._modulus in self._classes) def next(self, pr): - """ - Return the next prime number. + pr = ZZ(pr) + if not self._classes: + if not (self._included and pr < self._included[-1]): + raise ValueError("no element greater that %s in this set" % pr) + min = 0 + max = len(self._included) + while min < max: + i = (min + max) // 2 + print(min, max, i) + if self._included[i] <= pr: + min = i + 1 + if self._included[i] > pr: + max = i + return self._included[min] + while True: + pr = pr.next_prime() + e = self._exceptions.get(pr, None) + if (e is True) or (e is None and pr % self._modulus in self._classes): + return pr - EXAMPLES:: + def first(self): + return self.next(1) + + def first_n(self, n): + pr = 1 + ans = [] + for _ in range(n): + try: + pr = self.next(pr) + except ValueError: + return ans + ans.append(pr) + return ans + + def _an_element_(self, n): + if self.is_finite(): + if self._included: + return self._included[0] + raise ValueError("this set is empty") + return self.next(42) - sage: P = Primes() - sage: P.next(5) # needs sage.libs.pari - 7 - """ - pr = pr.next_prime(self.__proof) + def unrank(self, n): + pr = 1 + for _ in range(n): + try: + pr = self.next(pr) + except ValueError: + raise ValueError("this set has less than %s elements" % n) return pr - def unrank(self, n): - """ - Return the n-th prime number. - - EXAMPLES:: - - sage: P = Primes() - sage: P.unrank(0) # needs sage.libs.pari - 2 - sage: P.unrank(5) # needs sage.libs.pari - 13 - sage: P.unrank(42) # needs sage.libs.pari - 191 - """ - return nth_prime(n + 1) + def is_empty(self): + return not bool(self._classes) and not bool(self._exceptions) + + def is_finite(self): + return not bool(self._classes) + + def is_cofinite(self): + return self._modulus == 1 and bool(self._classes) + + def density(self): + return len(self._classes) / euler_phi(self._modulus) + + def include(self, elements, check=True): + if elements in ZZ: + elements = [elements] + exceptions = self._exceptions.copy() + for x in elements: + if check and not x.is_prime(): + raise ValueError("%s is not a prime number" % x) + exceptions[x] = True + return Primes(self._modulus, self._classes, exceptions) + + def exclude(self, elements): + if elements in ZZ: + elements = [elements] + exceptions = self._exceptions.copy() + for x in elements: + exceptions[x] = False + return Primes(self._modulus, self._classes, exceptions) + + def complement_in_primes(self): + modulus = self._modulus + classes = [c for c in range(modulus) + if c % self._modulus not in self._classes] + exceptions = {c: not v for c, v in self._exceptions.items()} + return Primes(modulus, classes, exceptions) + + def intersection(self, other): + if other in ZZ: + return self + if not isinstance(other, Primes): + raise NotImplementedError("boolean operations are only implemented with other sets of primes") + modulus = self._modulus.lcm(other._modulus) + classes = [c for c in range(modulus) + if (c % self._modulus in self._classes + and c % other._modulus in other._classes)] + exceptions = {} + for c, v in self._exceptions.items(): + if v and c in other: + exceptions[c] = True + if not v: + exceptions[c] = False + for c, v in other._exceptions.items(): + if v and c in self: + exceptions[c] = True + if not v: + exceptions[c] = False + return Primes(modulus, classes, exceptions) + + def union(self, other): + if other is ZZ: + return ZZ + if not isinstance(other, Primes): + raise NotImplementedError("boolean operations are only implemented with other sets of primes") + modulus = self._modulus.lcm(other._modulus) + classes = [c for c in range(modulus) + if (c % self._modulus in self._classes + or c % other._modulus in other._classes)] + exceptions = {} + for c, v in self._exceptions.items(): + if v: + exceptions[c] = True + if not v and c not in other: + exceptions[c] = False + for c, v in other._exceptions.items(): + if v: + exceptions[c] = True + if not v and c not in self: + exceptions[c] = False + return Primes(modulus, classes, exceptions) + + def is_subset(self, other): + return self.intersection(other) == self + + def is_supset(self, other): + return self.intersection(other) == self + + def is_disjoint(self, other): + return self.intersection(other).is_empty() From 1c808d5f0b1ea8b74774751e86c1da5a7078245c Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sat, 1 Nov 2025 16:09:16 +0100 Subject: [PATCH 02/26] documentation + small fixes/improvements --- src/sage/sets/primes.py | 674 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 609 insertions(+), 65 deletions(-) diff --git a/src/sage/sets/primes.py b/src/sage/sets/primes.py index 84ba396519a..dcb249295e9 100644 --- a/src/sage/sets/primes.py +++ b/src/sage/sets/primes.py @@ -19,6 +19,7 @@ # **************************************************************************** from sage.rings.integer_ring import ZZ +from sage.rings.infinity import infinity from .set import Set_generic from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets from sage.categories.infinite_enumerated_sets import InfiniteEnumeratedSets @@ -27,8 +28,80 @@ class Primes(Set_generic, UniqueRepresentation): + r""" + The set of prime numbers and some of its subsets. + + EXAMPLES: + + The set of all primes numbers:: + + sage: P = Primes(); P + Set of all prime numbers: 2, 3, 5, 7, ... + + The arguments `modulus` and `classes` allows for constructing + subsets given by congruence conditions:: + + sage: Primes(modulus=4) + Set of prime numbers congruent to 1 modulo 4: 5, 13, 17, 29, ... + + We see that, by default, Sagemath selects to congruence class `1`. + The user can user pass in explicitely other classes:: + + sage: Primes(modulus=4, classes=[3]) + Set of prime numbers congruent to 3 modulo 4: 3, 7, 11, 19, ... + sage: Primes(modulus=8, classes=[1, 3]) + Set of prime numbers congruent to 1, 3 modulo 8: 3, 11, 17, 19, ... + + When possible, the congruence conditions are simplified:: + + sage: Primes(modulus=8, classes=[1, 5]) + Set of prime numbers congruent to 1 modulo 4: 5, 13, 17, 29, ... + + We show various operations:: + + sage: P = Primes(modulus=4); P + Set of prime numbers congruent to 1 modulo 4: 5, 13, 17, 29, ... + sage: P.cardinality() + +Infinity + sage: P[:10] + [5, 13, 17, 29, 37, 41, 53, 61, 73, 89] + sage: P.next(500) + 509 + + :: + + sage: Q = Primes(modulus=4, classes=[3]) + sage: PQ = P.union(Q) + sage: PQ + Set of all prime numbers with 2 excluded: 3, 5, 7, 11, ... + sage: PQ.complement_in_primes() + Finite set of prime numbers: 2 + sage: PQ.complement_in_primes().cardinality() + 1 + """ @staticmethod - def __classcall_private__(cls, modulus=1, classes=None, exceptions=None): + def __classcall__(cls, modulus=1, classes=None, exceptions=None): + """ + Normalize the input. + + TESTS:: + + sage: Primes(modulus=10) + Set of prime numbers congruent to 1 modulo 5: 11, 31, 41, 61, ... + sage: Primes(modulus=10) == Primes(modulus=5) + True + + sage: Primes(modulus=9, classes=[1, 3, 4, 7]) + Set of prime numbers congruent to 1 modulo 3 with 3 included: 3, 7, 13, 19, ... + + sage: Primes(modulus=5, exceptions={7: True, 11: False}) + Set of prime numbers congruent to 1 modulo 5 with 7 included and 11 excluded: 7, 31, 41, 61, ... + + sage: Primes(modulus=0) + Traceback (most recent call last): + ... + ValueError: modulus must be nonzero + """ modulus = ZZ(modulus) if modulus == 0: raise ValueError("modulus must be nonzero") @@ -36,9 +109,16 @@ def __classcall_private__(cls, modulus=1, classes=None, exceptions=None): modulus = -modulus if classes is None: classes = [1] - indic = modulus * [False] if exceptions is None: exceptions = {} + if isinstance(exceptions, (tuple, list)): + exceptions = {c: v for c, v in exceptions} + + # We replace congruences of the form + # p = a (mod n) with gcd(a, n) > 1 + # (which only includes a finite number of primes) + # by exceptions + indic = modulus * [False] for c in classes: indic[ZZ(c) % modulus] = True for c in range(modulus): @@ -52,6 +132,9 @@ def __classcall_private__(cls, modulus=1, classes=None, exceptions=None): if c not in exceptions: exceptions[c] = True indic[c] = None + + # We normalize the congruence conditions + # by minimizing the modulus for p, mult in modulus.factor(): while mult > 0: m = modulus // p @@ -88,16 +171,36 @@ def __classcall_private__(cls, modulus=1, classes=None, exceptions=None): exceptions[c] = False modulus = m mult -= 1 + + # We format the final result classes = tuple([c for c in range(modulus) if indic[c] is True]) - excep = [] - for c, v in list(exceptions.items()): + exceptions_list = [] + for c, v in exceptions.items(): c = ZZ(c) if c.is_prime() and (v != (indic[c % modulus] is True)): - excep.append((c, v)) - excep.sort() - return cls.__classcall__(cls, modulus, classes, tuple(excep)) + exceptions_list.append((c, v)) + exceptions_list.sort() + + return super().__classcall__(cls, modulus, classes, tuple(exceptions_list)) def __init__(self, modulus, classes, exceptions): + r""" + Initialize this set. + + TESTS:: + + sage: P = Primes(modulus=4) + sage: P.category() + Category of facade infinite enumerated sets + sage: TestSuite(P).run() + + :: + + sage: Q = Primes(classes=[]).include([2, 3, 5]) + sage: Q.category() + Category of facade finite enumerated sets + sage: TestSuite(Q).run() + """ if classes: category = InfiniteEnumeratedSets() else: @@ -106,42 +209,82 @@ def __init__(self, modulus, classes, exceptions): self._modulus = modulus self._classes = set(classes) self._exceptions = {} - self._included = [] - self._excluded = [] + self._elements = [] for c, v in exceptions: self._exceptions[c] = v - if v: - self._included.append(c) - else: - self._excluded.append(c) - self._included.sort() - self._excluded.sort() + if v and not classes: + self._elements.append(c) + self._elements.sort() def _repr_(self): + r""" + Return a string representation of this subset. + + TESTS:: + + sage: Primes(modulus=4).include(2).exclude(5) # indirect doctest + Set of prime numbers congruent to 1 modulo 4 with 2 included and 5 excluded: 2, 13, 17, 29, ... + + sage: E = Primes(classes=[]) + sage: E # indirect doctest + Empty set of prime numbers + + sage: E.include(range(50), check=False) # indirect doctest + Finite set of prime numbers: 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47 + """ classes = sorted(list(self._classes)) sc = ", ".join([str(c) for c in classes]) - si = ", ".join([str(i) for i in self._included]) - se = ", ".join([str(e) for e in self._excluded]) - if sc == "": - if si == "": - return "Empty set of primes" + included = [] + excluded = [] + for c, v in self._exceptions.items(): + if v: + included.append(c) + else: + excluded.append(c) + si = ", ".join([str(i) for i in sorted(included)]) + se = ", ".join([str(e) for e in sorted(excluded)]) + if not classes: + if not included: + return "Empty set of prime numbers" else: - return "Finite set of primes: %s" % si + return "Finite set of prime numbers: %s" % si if self._modulus == 1: s = "Set of all prime numbers" else: s = "Set of prime numbers congruent to %s modulo %s" % (sc, self._modulus) - if si != "": + if included: s += " with %s included" % si - if se != "": - if si == "": + if excluded: + if not included: s += " with %s excluded" % se else: s += " and %s excluded" % se - s += ": %s, ..." % (", ".join([str(c) for c in self.first_n(4)])) + s += ": %s, ..." % (", ".join([str(c) for c in self[:4]])) return s def __contains__(self, x): + r""" + Return ``True`` if `x` is in this set; ``False`` otherwise. + + INPUT: + + - ``x`` -- an integer + + EXAMPLES:: + + sage: P = Primes(modulus=4) + sage: 3 in P + False + sage: 9 in P + False + sage: 13 in P + True + + TESTS:: + + sage: x in P + False + """ try: if x not in ZZ: return False @@ -153,80 +296,333 @@ def __contains__(self, x): e = self._exceptions.get(x, None) return (e is True) or (e is None and x % self._modulus in self._classes) - def next(self, pr): - pr = ZZ(pr) + def cardinality(self): + r""" + Return the cardinality of this set. + + EXAMPLES:: + + sage: P = Primes(modulus=4); P + Set of prime numbers congruent to 1 modulo 4: 5, 13, 17, 29, ... + sage: P.cardinality() + +Infinity + + :: + + sage: P = Primes(modulus=4, classes=[2]); P + Finite set of prime numbers: 2 + sage: P.cardinality() + 1 + """ + if self.is_finite(): + return ZZ(len(self._elements)) + else: + return infinity + + def first(self, n=None): + r""" + Return the first element in this set. + + EXAMPLES:: + + sage: P = Primes(modulus=5); P + Set of prime numbers congruent to 1 modulo 5: 11, 31, 41, 61, ... + sage: P.first() + 11 + """ + if self.is_empty(): + return + return self.next(1) + + def next(self, x): + r""" + Return the smallest element in this set strictly + greater than ``x``. + + INPUT: + + - ``x`` -- an integer + + EXAMPLES:: + + sage: P = Primes(modulus=5); P + Set of prime numbers congruent to 1 modulo 5: 11, 31, 41, 61, ... + sage: P.next(1000) + 1021 + + If there is no element greater than the given bound, an + error is raised:: + + sage: P = Primes(modulus=10, classes=[2, 5]); P + Finite set of prime numbers: 2, 5 + sage: P.next(10) + Traceback (most recent call last): + ... + ValueError: no element greater that 10 in this set + """ + x = ZZ(x) if not self._classes: - if not (self._included and pr < self._included[-1]): - raise ValueError("no element greater that %s in this set" % pr) + if not (self._elements and x < self._elements[-1]): + raise ValueError("no element greater that %s in this set" % x) min = 0 - max = len(self._included) + max = len(self._elements) while min < max: i = (min + max) // 2 - print(min, max, i) - if self._included[i] <= pr: + if self._elements[i] <= x: min = i + 1 - if self._included[i] > pr: + if self._elements[i] > x: max = i - return self._included[min] + return self._elements[min] while True: - pr = pr.next_prime() - e = self._exceptions.get(pr, None) - if (e is True) or (e is None and pr % self._modulus in self._classes): - return pr + x = x.next_prime() + e = self._exceptions.get(x, None) + if (e is True) or (e is None and x % self._modulus in self._classes): + return x - def first(self): - return self.next(1) + def _an_element_(self): + r""" + Return an element in this set. + + EXAMPLES:: + + sage: P = Primes() + sage: P.an_element() # indirect doctest + 43 - def first_n(self, n): - pr = 1 - ans = [] - for _ in range(n): - try: - pr = self.next(pr) - except ValueError: - return ans - ans.append(pr) - return ans - - def _an_element_(self, n): + If the set is empty, an error is raised:: + + sage: P = Primes(modulus=12, classes=[4]) + sage: P.an_element() # indirect doctest + Traceback (most recent call last): + ... + ValueError: this set is empty + """ if self.is_finite(): - if self._included: - return self._included[0] + if self._elements: + return self._elements[0] raise ValueError("this set is empty") return self.next(42) def unrank(self, n): - pr = 1 - for _ in range(n): - try: - pr = self.next(pr) - except ValueError: - raise ValueError("this set has less than %s elements" % n) - return pr + r""" + Return the ``n``-th element of this set. + + INPUT: + + - ``n`` -- an integer + + EXAMPLES:: + + sage: P = Primes(modulus=5); P + Set of prime numbers congruent to 1 modulo 5: 11, 31, 41, 61, ... + sage: P[0] # indirect doctest + 11 + sage: P[10] # indirect doctest + 211 + + If there is less than `n` elements in this set, an error + is raised:: + + sage: P = Primes(modulus=10, classes=[2, 5]); P + Finite set of prime numbers: 2, 5 + sage: P[1] + 5 + sage: P[2] # indirect doctest + Traceback (most recent call last): + ... + IndexError: this set has not enough elements + + TESTS:: + + sage: P.unrank(-1) + Traceback (most recent call last): + ... + IndexError: index must be nonnegative + """ + if n < 0: + raise IndexError("index must be nonnegative") + if self.is_finite(): + if len(self._elements) <= n: + raise IndexError("this set has not enough elements") + return self._elements[n] + if self._elements: + x = self._elements[-1] + else: + x = 1 + while len(self._elements) <= n: + x = self.next(x) + self._elements.append(x) + return self._elements[n] def is_empty(self): + r""" + Return ``True`` if this set is empty; ``False`` otherwise. + + EXAMPLES:: + + sage: P = Primes(modulus=6); P + Set of prime numbers congruent to 1 modulo 3: 7, 13, 19, 31, ... + sage: P.is_empty() + False + + :: + + sage: P = Primes(modulus=6, classes=[6]); P + Empty set of prime numbers + sage: P.is_empty() + True + + .. SEEALSO:: + + :meth:`is_finite`, :meth:`is_cofinite` + """ return not bool(self._classes) and not bool(self._exceptions) def is_finite(self): + r""" + Return ``True`` if this set is finite; ``False`` otherwise. + + EXAMPLES:: + + sage: P = Primes(modulus=5); P + Set of prime numbers congruent to 1 modulo 5: 11, 31, 41, 61, ... + sage: P.is_finite() + False + + :: + + sage: P = Primes(modulus=10, classes=[2, 5]); P + Finite set of prime numbers: 2, 5 + sage: P.is_finite() + True + + .. SEEALSO:: + + :meth:`is_empty`, :meth:`is_cofinite` + """ return not bool(self._classes) def is_cofinite(self): + r""" + Return ``True`` if this set is cofinite in the set + of all prime numbers; ``False`` otherwise. + + EXAMPLES:: + + sage: P = Primes(modulus=4); P + Set of prime numbers congruent to 1 modulo 4: 5, 13, 17, 29, ... + sage: P.is_cofinite() + False + + :: + + sage: P = Primes(modulus=4, classes=[1, 3]); P + Set of all prime numbers with 2 excluded: 3, 5, 7, 11, ... + sage: P.is_cofinite() + True + + .. SEEALSO:: + + :meth:`is_empty`, :meth:`is_finite` + """ return self._modulus == 1 and bool(self._classes) def density(self): + r""" + Return the density of this set in the set of all + prime numbers. + + EXAMPLES:: + + sage: P = Primes(modulus=4); P + Set of prime numbers congruent to 1 modulo 4: 5, 13, 17, 29, ... + sage: P.density() + 1/2 + """ return len(self._classes) / euler_phi(self._modulus) def include(self, elements, check=True): + r""" + Return this set with the integers in ``elements`` included. + + INPUT: + + - ``elements`` -- an integer, or a tuple/list of integers + + - ``check`` -- a boolean (default: ``True``); if ``False``, + do not raise an error if we try to add composite numbers + + EXAMPLES:: + + sage: P = Primes(modulus=5); P + Set of prime numbers congruent to 1 modulo 5: 11, 31, 41, 61, ... + sage: P.include(2) + Set of prime numbers congruent to 1 modulo 5 with 2 included: 2, 11, 31, 41, ... + sage: P.include([2, 3]) + Set of prime numbers congruent to 1 modulo 5 with 2, 3 included: 2, 3, 11, 31, ... + + If we try to include an element which is already in the set, + nothing changes:: + + sage: P.include(11) + Set of prime numbers congruent to 1 modulo 5: 11, 31, 41, 61, ... + + Trying to include a composite number results in an error:: + + sage: P.include(10) + Traceback (most recent call last): + ... + ValueError: 10 is not a prime number + + We can avoid this by passing in ``check=False``; in this case, + composite numbers are however not added to the set. + This behavior can be convenient if one wants to add all prime + numbers in a range:: + + sage: P.include(range(20, 30), check=False) + Set of prime numbers congruent to 1 modulo 5 with 23, 29 included: 11, 23, 29, 31, ... + + .. SEEALSO:: + + :meth:`exclude` + """ if elements in ZZ: elements = [elements] exceptions = self._exceptions.copy() for x in elements: + x = ZZ(x) if check and not x.is_prime(): raise ValueError("%s is not a prime number" % x) exceptions[x] = True return Primes(self._modulus, self._classes, exceptions) def exclude(self, elements): + r""" + Return this set with the integers in ``elements`` excluded. + + INPUT: + + - ``elements`` -- an integer, or a tuple/list of integers + + EXAMPLES:: + + sage: P = Primes(modulus=5); P + Set of prime numbers congruent to 1 modulo 5: 11, 31, 41, 61, ... + sage: P.exclude(11) + Set of prime numbers congruent to 1 modulo 5 with 11 excluded: 31, 41, 61, 71, ... + sage: P.exclude([11, 31]) + Set of prime numbers congruent to 1 modulo 5 with 11, 31 excluded: 41, 61, 71, 101, ... + + If we try to exclude an element which is not in the set, + nothing changes:: + + sage: P.exclude(2) + Set of prime numbers congruent to 1 modulo 5: 11, 31, 41, 61, ... + + .. SEEALSO:: + + :meth:`include` + """ if elements in ZZ: elements = [elements] exceptions = self._exceptions.copy() @@ -235,6 +631,32 @@ def exclude(self, elements): return Primes(self._modulus, self._classes, exceptions) def complement_in_primes(self): + r""" + Return the complement of this set in the set of all prime + numbers. + + EXAMPLES:: + + sage: P = Primes(modulus=4); P + Set of prime numbers congruent to 1 modulo 4: 5, 13, 17, 29, ... + sage: Q = P.complement_in_primes(); Q + Set of prime numbers congruent to 3 modulo 4 with 2 included: 2, 3, 7, 11, ... + + We check that the union of `P` and `Q` is the whole set of + prime numbers:: + + sage: P.union(Q) + Set of all prime numbers: 2, 3, 5, 7, ... + + and that the intersection is empty:: + + sage: P.intersection(Q) + Empty set of prime numbers + + .. SEEALSO:: + + :meth:`intersection`, :meth:`union` + """ modulus = self._modulus classes = [c for c in range(modulus) if c % self._modulus not in self._classes] @@ -242,10 +664,39 @@ def complement_in_primes(self): return Primes(modulus, classes, exceptions) def intersection(self, other): - if other in ZZ: + r""" + Return the intesection of this set with ``other``. + + INPUT: + + - ``other`` -- a subset of the set of prime numbers + + EXAMPLES:: + + sage: P = Primes(modulus=5); P + Set of prime numbers congruent to 1 modulo 5: 11, 31, 41, 61, ... + sage: Q = Primes(modulus=3, classes=[2]); Q + Set of prime numbers congruent to 2 modulo 3: 2, 5, 11, 17, ... + sage: P.intersection(Q) + Set of prime numbers congruent to 11 modulo 15: 11, 41, 71, 101, ... + + TESTS:: + + sage: P.intersection(ZZ) == P + True + sage: P.intersection(RR) + Traceback (most recent call last): + ... + NotImplementedError: boolean operations are only implemented with other sets of prime numbers + + .. SEEALSO:: + + :meth:`complement_in_primes`, :meth:`union` + """ + if other is ZZ: return self if not isinstance(other, Primes): - raise NotImplementedError("boolean operations are only implemented with other sets of primes") + raise NotImplementedError("boolean operations are only implemented with other sets of prime numbers") modulus = self._modulus.lcm(other._modulus) classes = [c for c in range(modulus) if (c % self._modulus in self._classes @@ -264,10 +715,39 @@ def intersection(self, other): return Primes(modulus, classes, exceptions) def union(self, other): + r""" + Return the intesection of this set with ``other``. + + INPUT: + + - ``other`` -- a subset of the set of prime numbers + + EXAMPLES:: + + sage: P = Primes(modulus=5); P + Set of prime numbers congruent to 1 modulo 5: 11, 31, 41, 61, ... + sage: Q = Primes(modulus=3, classes=[2]); Q + Set of prime numbers congruent to 2 modulo 3: 2, 5, 11, 17, ... + sage: P.union(Q) + Set of prime numbers congruent to 1, 2, 8, 11, 14 modulo 15 with 5 included: 2, 5, 11, 17, ... + + TESTS:: + + sage: P.union(ZZ) == ZZ + True + sage: P.union(RR) + Traceback (most recent call last): + ... + NotImplementedError: boolean operations are only implemented with other sets of prime numbers + + .. SEEALSO:: + + :meth:`complement_in_primes`, :meth:`intersection` + """ if other is ZZ: return ZZ if not isinstance(other, Primes): - raise NotImplementedError("boolean operations are only implemented with other sets of primes") + raise NotImplementedError("boolean operations are only implemented with other sets of prime numbers") modulus = self._modulus.lcm(other._modulus) classes = [c for c in range(modulus) if (c % self._modulus in self._classes @@ -286,10 +766,74 @@ def union(self, other): return Primes(modulus, classes, exceptions) def is_subset(self, other): + r""" + Return ``True`` is this set of is subset of ``other``; + ``False`` otherwise. + + EXAMPLES:: + + sage: P = Primes(modulus=4); P + Set of prime numbers congruent to 1 modulo 4: 5, 13, 17, 29, ... + sage: Q = Primes(modulus=8); Q + Set of prime numbers congruent to 1 modulo 8: 17, 41, 73, 89, ... + sage: P.is_subset(Q) + False + sage: Q.is_subset(P) + True + + .. SEEALSO:: + + :meth:`is_supset`, :meth:`is_disjoint` + """ return self.intersection(other) == self def is_supset(self, other): - return self.intersection(other) == self + r""" + Return ``True`` is this set of is supset of ``other``; + ``False`` otherwise. + + EXAMPLES:: + + sage: P = Primes(modulus=4); P + Set of prime numbers congruent to 1 modulo 4: 5, 13, 17, 29, ... + sage: Q = Primes(modulus=8); Q + Set of prime numbers congruent to 1 modulo 8: 17, 41, 73, 89, ... + sage: P.is_supset(Q) + True + sage: Q.is_supset(P) + False + + .. SEEALSO:: + + :meth:`is_subset`, :meth:`is_disjoint` + """ + return self.intersection(other) == other def is_disjoint(self, other): + r""" + Return ``True`` is this set of is disjoint from ``other``; + ``False`` otherwise. + + EXAMPLES:: + + sage: P = Primes(modulus=4); P + Set of prime numbers congruent to 1 modulo 4: 5, 13, 17, 29, ... + sage: Q = Primes(modulus=4, classes=[3]); Q + Set of prime numbers congruent to 3 modulo 4: 3, 7, 11, 19, ... + sage: P.is_disjoint(Q) + True + + :: + + sage: R = Primes(modulus=5, classes=[3]); R + Set of prime numbers congruent to 3 modulo 5: 3, 13, 23, 43, ... + sage: P.is_disjoint(R) + False + sage: Q.is_disjoint(R) + False + + .. SEEALSO:: + + :meth:`is_subset`, :meth:`is_disjoint` + """ return self.intersection(other).is_empty() From 7f14ce1587c5a887e5b2a44bf75b89cb5746e4b5 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sat, 1 Nov 2025 17:52:12 +0100 Subject: [PATCH 03/26] fix typos in documentation --- src/sage/sets/primes.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/sage/sets/primes.py b/src/sage/sets/primes.py index dcb249295e9..f1071214af9 100644 --- a/src/sage/sets/primes.py +++ b/src/sage/sets/primes.py @@ -38,14 +38,14 @@ class Primes(Set_generic, UniqueRepresentation): sage: P = Primes(); P Set of all prime numbers: 2, 3, 5, 7, ... - The arguments `modulus` and `classes` allows for constructing + The arguments ``modulus`` and ``classes`` allows for constructing subsets given by congruence conditions:: sage: Primes(modulus=4) Set of prime numbers congruent to 1 modulo 4: 5, 13, 17, 29, ... - We see that, by default, Sagemath selects to congruence class `1`. - The user can user pass in explicitely other classes:: + We see that, by default, Sagemath selects the congruence class `1`. + The user can however pass in explicitely other classes:: sage: Primes(modulus=4, classes=[3]) Set of prime numbers congruent to 3 modulo 4: 3, 7, 11, 19, ... @@ -57,7 +57,7 @@ class Primes(Set_generic, UniqueRepresentation): sage: Primes(modulus=8, classes=[1, 5]) Set of prime numbers congruent to 1 modulo 4: 5, 13, 17, 29, ... - We show various operations:: + We show various operations that can be performed on these sets:: sage: P = Primes(modulus=4); P Set of prime numbers congruent to 1 modulo 4: 5, 13, 17, 29, ... @@ -465,7 +465,7 @@ def is_empty(self): sage: P.is_empty() False - :: + :: sage: P = Primes(modulus=6, classes=[6]); P Empty set of prime numbers @@ -489,7 +489,7 @@ def is_finite(self): sage: P.is_finite() False - :: + :: sage: P = Primes(modulus=10, classes=[2, 5]); P Finite set of prime numbers: 2, 5 @@ -514,7 +514,7 @@ def is_cofinite(self): sage: P.is_cofinite() False - :: + :: sage: P = Primes(modulus=4, classes=[1, 3]); P Set of all prime numbers with 2 excluded: 3, 5, 7, 11, ... From c64f23581489ab273b22abb1f7f9bdcdb34450a2 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sat, 1 Nov 2025 18:31:58 +0100 Subject: [PATCH 04/26] Martin's suggestions Co-authored-by: Martin Rubey --- src/sage/sets/primes.py | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/src/sage/sets/primes.py b/src/sage/sets/primes.py index f1071214af9..c8986862e0f 100644 --- a/src/sage/sets/primes.py +++ b/src/sage/sets/primes.py @@ -44,15 +44,15 @@ class Primes(Set_generic, UniqueRepresentation): sage: Primes(modulus=4) Set of prime numbers congruent to 1 modulo 4: 5, 13, 17, 29, ... - We see that, by default, Sagemath selects the congruence class `1`. - The user can however pass in explicitely other classes:: + By default the congruence class `1` is selected, but we can specify any + subset of congruence classes:: sage: Primes(modulus=4, classes=[3]) Set of prime numbers congruent to 3 modulo 4: 3, 7, 11, 19, ... sage: Primes(modulus=8, classes=[1, 3]) Set of prime numbers congruent to 1, 3 modulo 8: 3, 11, 17, 19, ... - When possible, the congruence conditions are simplified:: + If possible, the congruence conditions are simplified:: sage: Primes(modulus=8, classes=[1, 5]) Set of prime numbers congruent to 1 modulo 4: 5, 13, 17, 29, ... @@ -117,20 +117,19 @@ def __classcall__(cls, modulus=1, classes=None, exceptions=None): # We replace congruences of the form # p = a (mod n) with gcd(a, n) > 1 # (which only includes a finite number of primes) - # by exceptions + # with exceptions indic = modulus * [False] for c in classes: indic[ZZ(c) % modulus] = True for c in range(modulus): - g = modulus.gcd(c) - if g > 1: + if modulus.gcd(c) > 1: if indic[c]: if c == 0: if modulus not in exceptions: exceptions[modulus] = True else: if c not in exceptions: - exceptions[c] = True + exceptions[ZZ(c)] = True indic[c] = None # We normalize the congruence conditions @@ -174,11 +173,8 @@ def __classcall__(cls, modulus=1, classes=None, exceptions=None): # We format the final result classes = tuple([c for c in range(modulus) if indic[c] is True]) - exceptions_list = [] - for c, v in exceptions.items(): - c = ZZ(c) - if c.is_prime() and (v != (indic[c % modulus] is True)): - exceptions_list.append((c, v)) + exceptions_list = [(c, v) for c, v in exceptions.items() + if c.is_prime() and (v != (indic[c % modulus] is True))] exceptions_list.sort() return super().__classcall__(cls, modulus, classes, tuple(exceptions_list)) @@ -208,12 +204,8 @@ def __init__(self, modulus, classes, exceptions): super().__init__(facade=ZZ, category=category) self._modulus = modulus self._classes = set(classes) - self._exceptions = {} - self._elements = [] - for c, v in exceptions: - self._exceptions[c] = v - if v and not classes: - self._elements.append(c) + self._exceptions = dict(exceptions) + self._elements = [c for c, v in exceptions if v and not classes] self._elements.sort() def _repr_(self): @@ -316,8 +308,7 @@ def cardinality(self): """ if self.is_finite(): return ZZ(len(self._elements)) - else: - return infinity + return infinity def first(self, n=None): r""" From 14e2cebb4676ce1c86519cf2088f80351db8e72d Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sat, 1 Nov 2025 18:44:36 +0100 Subject: [PATCH 05/26] add INPUT section + allow modulus=0 --- src/sage/sets/primes.py | 47 +++++++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/src/sage/sets/primes.py b/src/sage/sets/primes.py index c8986862e0f..d119a420a87 100644 --- a/src/sage/sets/primes.py +++ b/src/sage/sets/primes.py @@ -25,6 +25,7 @@ from sage.categories.infinite_enumerated_sets import InfiniteEnumeratedSets from sage.arith.misc import euler_phi from sage.structure.unique_representation import UniqueRepresentation +from sage.categories.sets_cat import EmptySetError class Primes(Set_generic, UniqueRepresentation): @@ -57,6 +58,11 @@ class Primes(Set_generic, UniqueRepresentation): sage: Primes(modulus=8, classes=[1, 5]) Set of prime numbers congruent to 1 modulo 4: 5, 13, 17, 29, ... + We can create a finite set of primes by passing in ``modulus=0``:: + + sage: Primes(modulus=0, classes=[2, 3, 5, 11]) + Finite set of prime numbers: 2, 3, 5, 11 + We show various operations that can be performed on these sets:: sage: P = Primes(modulus=4); P @@ -84,6 +90,19 @@ def __classcall__(cls, modulus=1, classes=None, exceptions=None): """ Normalize the input. + INPUT: + + - ``modulus`` -- an integer (default: ``1``) + + - ``classes`` -- a list of integers (default: ``[1]``), the + congruence classes (modulo ``modulus``) included in this + set + + - ``exceptions`` -- a dictionary with items of the form + ``c: v`` where ``c`` is an integer and ``v`` is a boolean; + if ``v`` is ``True`` (resp. ``False``) and ``c`` is added + to (resp. removed from) this set + TESTS:: sage: Primes(modulus=10) @@ -96,15 +115,8 @@ def __classcall__(cls, modulus=1, classes=None, exceptions=None): sage: Primes(modulus=5, exceptions={7: True, 11: False}) Set of prime numbers congruent to 1 modulo 5 with 7 included and 11 excluded: 7, 31, 41, 61, ... - - sage: Primes(modulus=0) - Traceback (most recent call last): - ... - ValueError: modulus must be nonzero """ modulus = ZZ(modulus) - if modulus == 0: - raise ValueError("modulus must be nonzero") if modulus < 0: modulus = -modulus if classes is None: @@ -114,6 +126,12 @@ def __classcall__(cls, modulus=1, classes=None, exceptions=None): if isinstance(exceptions, (tuple, list)): exceptions = {c: v for c, v in exceptions} + if modulus == 0: + for c in classes: + exceptions[c] = True + modulus = ZZ(1) + classes = [] + # We replace congruences of the form # p = a (mod n) with gcd(a, n) > 1 # (which only includes a finite number of primes) @@ -205,8 +223,11 @@ def __init__(self, modulus, classes, exceptions): self._modulus = modulus self._classes = set(classes) self._exceptions = dict(exceptions) - self._elements = [c for c, v in exceptions if v and not classes] - self._elements.sort() + if classes: + self._elements = [] + else: + self._elements = [c for c, v in exceptions if v] + self._elements.sort() def _repr_(self): r""" @@ -322,7 +343,7 @@ def first(self, n=None): 11 """ if self.is_empty(): - return + raise EmptySetError return self.next(1) def next(self, x): @@ -344,7 +365,7 @@ def next(self, x): If there is no element greater than the given bound, an error is raised:: - sage: P = Primes(modulus=10, classes=[2, 5]); P + sage: P = Primes(modulus=0, classes=[2, 5]); P Finite set of prime numbers: 2, 5 sage: P.next(10) Traceback (most recent call last): @@ -414,7 +435,7 @@ def unrank(self, n): If there is less than `n` elements in this set, an error is raised:: - sage: P = Primes(modulus=10, classes=[2, 5]); P + sage: P = Primes(modulus=0, classes=[2, 5]); P Finite set of prime numbers: 2, 5 sage: P[1] 5 @@ -482,7 +503,7 @@ def is_finite(self): :: - sage: P = Primes(modulus=10, classes=[2, 5]); P + sage: P = Primes(modulus=0, classes=[2, 5]); P Finite set of prime numbers: 2, 5 sage: P.is_finite() True From 78ba9816f9768a10b554ba44d27e813ee0d60aec Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sat, 1 Nov 2025 18:46:24 +0100 Subject: [PATCH 06/26] typo --- src/sage/sets/primes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/sets/primes.py b/src/sage/sets/primes.py index d119a420a87..86324ce4eb3 100644 --- a/src/sage/sets/primes.py +++ b/src/sage/sets/primes.py @@ -100,7 +100,7 @@ def __classcall__(cls, modulus=1, classes=None, exceptions=None): - ``exceptions`` -- a dictionary with items of the form ``c: v`` where ``c`` is an integer and ``v`` is a boolean; - if ``v`` is ``True`` (resp. ``False``) and ``c`` is added + if ``v`` is ``True`` (resp. ``False``) then ``c`` is added to (resp. removed from) this set TESTS:: From cdce9642d4faa35f70cb99330a57adedb1f0ac09 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sat, 1 Nov 2025 18:52:12 +0100 Subject: [PATCH 07/26] no longer use classes=[] to create finite set of primes --- src/sage/sets/primes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/sets/primes.py b/src/sage/sets/primes.py index 86324ce4eb3..2ce553384ba 100644 --- a/src/sage/sets/primes.py +++ b/src/sage/sets/primes.py @@ -120,7 +120,7 @@ def __classcall__(cls, modulus=1, classes=None, exceptions=None): if modulus < 0: modulus = -modulus if classes is None: - classes = [1] + classes = [ZZ(1)] if exceptions is None: exceptions = {} if isinstance(exceptions, (tuple, list)): @@ -210,7 +210,7 @@ def __init__(self, modulus, classes, exceptions): :: - sage: Q = Primes(classes=[]).include([2, 3, 5]) + sage: Q = Primes(modulus=0, classes=[2, 3, 5]) sage: Q.category() Category of facade finite enumerated sets sage: TestSuite(Q).run() @@ -238,7 +238,7 @@ def _repr_(self): sage: Primes(modulus=4).include(2).exclude(5) # indirect doctest Set of prime numbers congruent to 1 modulo 4 with 2 included and 5 excluded: 2, 13, 17, 29, ... - sage: E = Primes(classes=[]) + sage: E = Primes(modulus=0) sage: E # indirect doctest Empty set of prime numbers From d5346586a921608ab1caf5e9baa07691cc626b16 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sun, 2 Nov 2025 04:22:15 +0100 Subject: [PATCH 08/26] Update src/sage/sets/primes.py Co-authored-by: Martin Rubey --- src/sage/sets/primes.py | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/sage/sets/primes.py b/src/sage/sets/primes.py index 2ce553384ba..6d4c4b3704d 100644 --- a/src/sage/sets/primes.py +++ b/src/sage/sets/primes.py @@ -373,23 +373,24 @@ def next(self, x): ValueError: no element greater that 10 in this set """ x = ZZ(x) - if not self._classes: - if not (self._elements and x < self._elements[-1]): - raise ValueError("no element greater that %s in this set" % x) - min = 0 - max = len(self._elements) - while min < max: - i = (min + max) // 2 - if self._elements[i] <= x: - min = i + 1 - if self._elements[i] > x: - max = i - return self._elements[min] - while True: - x = x.next_prime() - e = self._exceptions.get(x, None) - if (e is True) or (e is None and x % self._modulus in self._classes): - return x + if self._classes: + while True: + x = x.next_prime() + e = self._exceptions.get(x, None) + if (e is True) or (e is None and x % self._modulus in self._classes): + return x + + if not self._elements or x >= self._elements[-1]: + raise ValueError("no element greater that %s in this set" % x) + min = 0 + max = len(self._elements) + while min < max: + i = (min + max) // 2 + if self._elements[i] <= x: + min = i + 1 + if self._elements[i] > x: + max = i + return self._elements[min] def _an_element_(self): r""" From f07403f33acbad30a3e669278425df9282276eed Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sun, 2 Nov 2025 04:22:45 +0100 Subject: [PATCH 09/26] Update src/sage/sets/primes.py Co-authored-by: Martin Rubey --- src/sage/sets/primes.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/sage/sets/primes.py b/src/sage/sets/primes.py index 6d4c4b3704d..184fb558e32 100644 --- a/src/sage/sets/primes.py +++ b/src/sage/sets/primes.py @@ -714,17 +714,10 @@ def intersection(self, other): classes = [c for c in range(modulus) if (c % self._modulus in self._classes and c % other._modulus in other._classes)] - exceptions = {} - for c, v in self._exceptions.items(): - if v and c in other: - exceptions[c] = True - if not v: - exceptions[c] = False - for c, v in other._exceptions.items(): - if v and c in self: - exceptions[c] = True - if not v: - exceptions[c] = False + exceptions = {c: v for c, v in self._exceptions.items() + if not v or c in other} + exceptions.update((c, v) for c, v in other._exceptions.items() + if not v or c in self) return Primes(modulus, classes, exceptions) def union(self, other): From 4e2f8ac04323d166e0da2e3abc3908491aa2f2eb Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sun, 2 Nov 2025 04:23:15 +0100 Subject: [PATCH 10/26] Update src/sage/sets/primes.py Co-authored-by: Martin Rubey --- src/sage/sets/primes.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/sage/sets/primes.py b/src/sage/sets/primes.py index 184fb558e32..07282800bb1 100644 --- a/src/sage/sets/primes.py +++ b/src/sage/sets/primes.py @@ -738,6 +738,12 @@ def union(self, other): Set of prime numbers congruent to 1, 2, 8, 11, 14 modulo 15 with 5 included: 2, 5, 11, 17, ... TESTS:: + sage: P = Primes(modulus=5, exceptions={5: True, 11: False}); P + Set of prime numbers congruent to 1 modulo 5 with 5 included and 11 excluded: 5, 31, 41, 61, ... + sage: Q = Primes(modulus=4, exceptions={13: False, 11: True}); Q + Set of prime numbers congruent to 1 modulo 4 with 11 included and 13 excluded: 5, 11, 17, 29, ... + sage: P.union(Q) + Set of prime numbers congruent to 1, 9, 11, 13, 17 modulo 20 with 5 included and 13 excluded: 5, 11, 17, 29, ... sage: P.union(ZZ) == ZZ True From 243811d53245c8849b8e71630f36273b55e3c44f Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sun, 2 Nov 2025 04:28:27 +0100 Subject: [PATCH 11/26] replicate changes in union to intersection --- src/sage/sets/primes.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/sage/sets/primes.py b/src/sage/sets/primes.py index 07282800bb1..2695911cb5e 100644 --- a/src/sage/sets/primes.py +++ b/src/sage/sets/primes.py @@ -695,6 +695,15 @@ def intersection(self, other): TESTS:: + sage: P = Primes(modulus=5, exceptions={5: True, 11: False}); P + Set of prime numbers congruent to 1 modulo 5 with 5 included and 11 excluded: 5, 31, 41, 61, ... + sage: Q = Primes(modulus=4, exceptions={13: False, 11: True}); Q + Set of prime numbers congruent to 1 modulo 4 with 11 included and 13 excluded: 5, 11, 17, 29, ... + sage: P.intersection(Q) + Set of prime numbers congruent to 1 modulo 20 with 5 included: 5, 41, 61, 101, ... + + :: + sage: P.intersection(ZZ) == P True sage: P.intersection(RR) @@ -738,6 +747,7 @@ def union(self, other): Set of prime numbers congruent to 1, 2, 8, 11, 14 modulo 15 with 5 included: 2, 5, 11, 17, ... TESTS:: + sage: P = Primes(modulus=5, exceptions={5: True, 11: False}); P Set of prime numbers congruent to 1 modulo 5 with 5 included and 11 excluded: 5, 31, 41, 61, ... sage: Q = Primes(modulus=4, exceptions={13: False, 11: True}); Q @@ -745,6 +755,8 @@ def union(self, other): sage: P.union(Q) Set of prime numbers congruent to 1, 9, 11, 13, 17 modulo 20 with 5 included and 13 excluded: 5, 11, 17, 29, ... + :: + sage: P.union(ZZ) == ZZ True sage: P.union(RR) @@ -764,17 +776,10 @@ def union(self, other): classes = [c for c in range(modulus) if (c % self._modulus in self._classes or c % other._modulus in other._classes)] - exceptions = {} - for c, v in self._exceptions.items(): - if v: - exceptions[c] = True - if not v and c not in other: - exceptions[c] = False - for c, v in other._exceptions.items(): - if v: - exceptions[c] = True - if not v and c not in self: - exceptions[c] = False + exceptions = {c: v for c, v in self._exceptions.items() + if v or c not in other} + exceptions.update((c, v) for c, v in other._exceptions.items() + if v or c not in self) return Primes(modulus, classes, exceptions) def is_subset(self, other): From 1157434e83725d44de6592bf9539cccdb9ed4e35 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sun, 2 Nov 2025 04:52:53 +0100 Subject: [PATCH 12/26] almost equality --- src/sage/sets/primes.py | 128 ++++++++++++++++++++++++++++++++++------ 1 file changed, 110 insertions(+), 18 deletions(-) diff --git a/src/sage/sets/primes.py b/src/sage/sets/primes.py index 2695911cb5e..a701259b229 100644 --- a/src/sage/sets/primes.py +++ b/src/sage/sets/primes.py @@ -189,13 +189,14 @@ def __classcall__(cls, modulus=1, classes=None, exceptions=None): modulus = m mult -= 1 - # We format the final result + # We format the final result and make it hashable classes = tuple([c for c in range(modulus) if indic[c] is True]) - exceptions_list = [(c, v) for c, v in exceptions.items() - if c.is_prime() and (v != (indic[c % modulus] is True))] - exceptions_list.sort() + exceptions = [(c, v) for c, v in exceptions.items() + if c.is_prime() and (v != (indic[c % modulus] is True))] + exceptions.sort() + exceptions = tuple(exceptions) - return super().__classcall__(cls, modulus, classes, tuple(exceptions_list)) + return super().__classcall__(cls, modulus, classes, exceptions) def __init__(self, modulus, classes, exceptions): r""" @@ -782,11 +783,47 @@ def union(self, other): if v or c not in self) return Primes(modulus, classes, exceptions) - def is_subset(self, other): + def is_almost_equal(self, other): r""" - Return ``True`` is this set of is subset of ``other``; + Return ``True`` if this set only differs from ``other`` + by a finite set; return ``False`` otherwise. + + INPUT: + + - ``other`` -- a subset of the set of prime numbers + + EXAMPLES:: + + sage: P = Primes(modulus=20, classes=[1, 2]); P + Set of prime numbers congruent to 1 modulo 20 with 2 included: 2, 41, 61, 101, ... + sage: Q = Primes(modulus=20, classes=[1, 5]); Q + Set of prime numbers congruent to 1 modulo 20 with 5 included: 5, 41, 61, 101, ... + sage: P.is_almost_equal(Q) + True + + :: + + sage: R = Primes(modulus=10); R + Set of prime numbers congruent to 1 modulo 5: 11, 31, 41, 61, ... + sage: P.is_almost_equal(R) + False + """ + if not isinstance(other, Primes): + return False # or raise an error? + return self._modulus == other._modulus and self._classes == other._classes + + def is_subset(self, other, almost=False): + r""" + Return ``True`` if this set of is subset of ``other``; ``False`` otherwise. + INPUT: + + - ``other`` -- a subset of the set of prime numbers + + - ``almost`` -- a boolean (default: ``False``); if ``True``, + the inclusion is only checked up to a finite set + EXAMPLES:: sage: P = Primes(modulus=4); P @@ -798,17 +835,38 @@ def is_subset(self, other): sage: Q.is_subset(P) True + When ``almost=True``, the inclusion is only checked up to a + finite set:: + + sage: Q2 = Q.include(2); Q2 + Set of prime numbers congruent to 1 modulo 8 with 2 included: 2, 17, 41, 73, ... + sage: Q2.is_subset(P) + False + sage: Q2.is_subset(P, almost=True) + True + .. SEEALSO:: - :meth:`is_supset`, :meth:`is_disjoint` + :meth:`is_supset`, :meth:`is_disjoint`, :meth:`is_almost_equal` """ - return self.intersection(other) == self + P = self.intersection(other) + if almost: + return P.is_almost_equal(self) + else: + return P == self - def is_supset(self, other): + def is_supset(self, other, almost=False): r""" - Return ``True`` is this set of is supset of ``other``; + Return ``True`` if this set of is supset of ``other``; ``False`` otherwise. + INPUT: + + - ``other`` -- a subset of the set of prime numbers + + - ``almost`` -- a boolean (default: ``False``); if ``True``, + the inclusion is only checked up to a finite set + EXAMPLES:: sage: P = Primes(modulus=4); P @@ -820,16 +878,37 @@ def is_supset(self, other): sage: Q.is_supset(P) False + When ``almost=True``, the inclusion is only checked up to a + finite set:: + + sage: Q2 = Q.include(2); Q2 + Set of prime numbers congruent to 1 modulo 8 with 2 included: 2, 17, 41, 73, ... + sage: P.is_supset(Q2) + False + sage: P.is_supset(Q2, almost=True) + True + .. SEEALSO:: - :meth:`is_subset`, :meth:`is_disjoint` + :meth:`is_subset`, :meth:`is_disjoint`, :meth:`is_almost_equal` """ - return self.intersection(other) == other + P = self.intersection(other) + if almost: + return P.is_almost_equal(other) + else: + return P == other - def is_disjoint(self, other): + def is_disjoint(self, other, almost=False): r""" - Return ``True`` is this set of is disjoint from ``other``; - ``False`` otherwise. + Return ``True`` if the intersection of this set with ``other`` + is empty (resp. finite) if ``almost`` is ``False`` (resp. ``True``); + return ``False`` otherwise. + + INPUT: + + - ``other`` -- a subset of the set of prime numbers + + - ``almost`` -- a boolean (default: ``False``) EXAMPLES:: @@ -849,8 +928,21 @@ def is_disjoint(self, other): sage: Q.is_disjoint(R) False + We illustrate the behavior when ``almost=True``:: + + sage: Q5 = Q.include(5); Q5 + Set of prime numbers congruent to 3 modulo 4 with 5 included: 3, 5, 7, 11, ... + sage: P.is_disjoint(Q5) + False + sage: P.is_disjoint(Q5, almost=True) + True + .. SEEALSO:: - :meth:`is_subset`, :meth:`is_disjoint` + :meth:`is_subset`, :meth:`is_disjoint`, :meth:`is_almost_equal` """ - return self.intersection(other).is_empty() + P = self.intersection(other) + if almost: + return P.is_finite() + else: + return P.is_empty() From 39b048b859c07c5c0f6a891ba41880be89f17690 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sun, 2 Nov 2025 05:04:24 +0100 Subject: [PATCH 13/26] uniformize notation for exceptions: (c, v) -> (x, b) --- src/sage/sets/primes.py | 57 ++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/src/sage/sets/primes.py b/src/sage/sets/primes.py index a701259b229..61766758c23 100644 --- a/src/sage/sets/primes.py +++ b/src/sage/sets/primes.py @@ -99,8 +99,8 @@ def __classcall__(cls, modulus=1, classes=None, exceptions=None): set - ``exceptions`` -- a dictionary with items of the form - ``c: v`` where ``c`` is an integer and ``v`` is a boolean; - if ``v`` is ``True`` (resp. ``False``) then ``c`` is added + ``x: b`` where ``x`` is an integer and ``b`` is a boolean; + if ``b`` is ``True`` (resp. ``False``) then ``x`` is added to (resp. removed from) this set TESTS:: @@ -124,7 +124,7 @@ def __classcall__(cls, modulus=1, classes=None, exceptions=None): if exceptions is None: exceptions = {} if isinstance(exceptions, (tuple, list)): - exceptions = {c: v for c, v in exceptions} + exceptions = {x: b for x, b in exceptions} if modulus == 0: for c in classes: @@ -132,10 +132,10 @@ def __classcall__(cls, modulus=1, classes=None, exceptions=None): modulus = ZZ(1) classes = [] - # We replace congruences of the form + # We replace each congruence of the form # p = a (mod n) with gcd(a, n) > 1 - # (which only includes a finite number of primes) - # with exceptions + # (which includes at most one prime number) + # with an exception indic = modulus * [False] for c in classes: indic[ZZ(c) % modulus] = True @@ -145,9 +145,8 @@ def __classcall__(cls, modulus=1, classes=None, exceptions=None): if c == 0: if modulus not in exceptions: exceptions[modulus] = True - else: - if c not in exceptions: - exceptions[ZZ(c)] = True + elif c not in exceptions: + exceptions[ZZ(c)] = True indic[c] = None # We normalize the congruence conditions @@ -183,16 +182,16 @@ def __classcall__(cls, modulus=1, classes=None, exceptions=None): indic[c] = True for c in add_false: indic[c] = False - for c in add_excluded: - if c not in exceptions: - exceptions[c] = False + for x in add_excluded: + if x not in exceptions: + exceptions[x] = False modulus = m mult -= 1 # We format the final result and make it hashable classes = tuple([c for c in range(modulus) if indic[c] is True]) - exceptions = [(c, v) for c, v in exceptions.items() - if c.is_prime() and (v != (indic[c % modulus] is True))] + exceptions = [(x, b) for x, b in exceptions.items() + if x.is_prime() and (b != (indic[x % modulus] is True))] exceptions.sort() exceptions = tuple(exceptions) @@ -227,7 +226,7 @@ def __init__(self, modulus, classes, exceptions): if classes: self._elements = [] else: - self._elements = [c for c, v in exceptions if v] + self._elements = [x for x, _ in exceptions] self._elements.sort() def _repr_(self): @@ -250,11 +249,11 @@ def _repr_(self): sc = ", ".join([str(c) for c in classes]) included = [] excluded = [] - for c, v in self._exceptions.items(): - if v: - included.append(c) + for x, b in self._exceptions.items(): + if b: + included.append(x) else: - excluded.append(c) + excluded.append(x) si = ", ".join([str(i) for i in sorted(included)]) se = ", ".join([str(e) for e in sorted(excluded)]) if not classes: @@ -273,7 +272,7 @@ def _repr_(self): s += " with %s excluded" % se else: s += " and %s excluded" % se - s += ": %s, ..." % (", ".join([str(c) for c in self[:4]])) + s += ": %s, ..." % (", ".join([str(n) for n in self[:4]])) return s def __contains__(self, x): @@ -674,7 +673,7 @@ def complement_in_primes(self): modulus = self._modulus classes = [c for c in range(modulus) if c % self._modulus not in self._classes] - exceptions = {c: not v for c, v in self._exceptions.items()} + exceptions = {x: not b for x, b in self._exceptions.items()} return Primes(modulus, classes, exceptions) def intersection(self, other): @@ -724,10 +723,10 @@ def intersection(self, other): classes = [c for c in range(modulus) if (c % self._modulus in self._classes and c % other._modulus in other._classes)] - exceptions = {c: v for c, v in self._exceptions.items() - if not v or c in other} - exceptions.update((c, v) for c, v in other._exceptions.items() - if not v or c in self) + exceptions = {x: b for x, b in self._exceptions.items() + if not b or x in other} + exceptions.update((x, b) for x, b in other._exceptions.items() + if not b or x in self) return Primes(modulus, classes, exceptions) def union(self, other): @@ -777,10 +776,10 @@ def union(self, other): classes = [c for c in range(modulus) if (c % self._modulus in self._classes or c % other._modulus in other._classes)] - exceptions = {c: v for c, v in self._exceptions.items() - if v or c not in other} - exceptions.update((c, v) for c, v in other._exceptions.items() - if v or c not in self) + exceptions = {x: b for x, b in self._exceptions.items() + if b or x not in other} + exceptions.update((x, b) for x, b in other._exceptions.items() + if b or x not in self) return Primes(modulus, classes, exceptions) def is_almost_equal(self, other): From 09e14fa71228ade88c5b0f307d9b8313bc1634a2 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sun, 2 Nov 2025 05:06:58 +0100 Subject: [PATCH 14/26] needs pari --- src/sage/sets/primes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sage/sets/primes.py b/src/sage/sets/primes.py index 61766758c23..44b3bddfcde 100644 --- a/src/sage/sets/primes.py +++ b/src/sage/sets/primes.py @@ -1,3 +1,4 @@ +# sage.doctest: needs sage.libs.pari """ The set of prime numbers and its subsets defined by congruence conditions From 99052e8a98bf907c977da34369e07f90f3d179f4 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sun, 2 Nov 2025 05:24:16 +0100 Subject: [PATCH 15/26] use the dict constructor --- src/sage/sets/primes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/sets/primes.py b/src/sage/sets/primes.py index 44b3bddfcde..930507ef243 100644 --- a/src/sage/sets/primes.py +++ b/src/sage/sets/primes.py @@ -124,8 +124,8 @@ def __classcall__(cls, modulus=1, classes=None, exceptions=None): classes = [ZZ(1)] if exceptions is None: exceptions = {} - if isinstance(exceptions, (tuple, list)): - exceptions = {x: b for x, b in exceptions} + if not isinstance(exceptions, dict): + exceptions = dict(exceptions) if modulus == 0: for c in classes: From 4491e9c9d244941f4486bc962ea3226092b74763 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sun, 2 Nov 2025 08:01:31 +0100 Subject: [PATCH 16/26] add getters --- src/sage/sets/primes.py | 144 ++++++++++++++++++++++++++++++++++------ 1 file changed, 125 insertions(+), 19 deletions(-) diff --git a/src/sage/sets/primes.py b/src/sage/sets/primes.py index 930507ef243..b310b2d99b2 100644 --- a/src/sage/sets/primes.py +++ b/src/sage/sets/primes.py @@ -1,6 +1,6 @@ # sage.doctest: needs sage.libs.pari """ -The set of prime numbers and its subsets defined by congruence conditions +Set and subsets of prime numbers AUTHORS: @@ -29,6 +29,16 @@ from sage.categories.sets_cat import EmptySetError +def repr_list(items, left=4, right=2): + if len(items) <= left + right + 1: + s = [str(item) for item in items] + else: + s = [str(item) for item in items[:left]] + s += ["..."] + s += [str(item) for item in items[-right:]] + return ", ".join(s) + + class Primes(Set_generic, UniqueRepresentation): r""" The set of prime numbers and some of its subsets. @@ -129,7 +139,7 @@ def __classcall__(cls, modulus=1, classes=None, exceptions=None): if modulus == 0: for c in classes: - exceptions[c] = True + exceptions[ZZ(c)] = True modulus = ZZ(1) classes = [] @@ -230,6 +240,110 @@ def __init__(self, modulus, classes, exceptions): self._elements = [x for x, _ in exceptions] self._elements.sort() + def congruence_classes(self): + r""" + Return the congruence classes selected in the subset + of prime numbers. + + OUTPUT: + + A pair ``(modulus, list of classes)`` + + EXAMPLES:: + + sage: P = Primes(modulus=4) + sage: P + Set of prime numbers congruent to 1 modulo 4: 5, 13, 17, 29, ... + sage: P.congruence_classes() + (4, [1]) + + If possible, the congruence classes are simplified:: + + sage: P = Primes(modulus=10, classes=[1, 3]) + sage: P + Set of prime numbers congruent to 1, 3 modulo 5: 3, 11, 13, 23, ... + sage: P.congruence_classes() + (5, [1, 3]) + + If this subset is finite, the output of this method is always `(1, [])`. + The elements of the subset can be retreived using the method :meth:`list` + or :meth:`included`:: + + sage: P = Primes(modulus=0, classes=range(50)) + sage: P + Finite set of prime numbers: 2, 3, 5, 7, ..., 43, 47 + sage: P.congruence_classes() + (1, []) + sage: list(P) + [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47] + + .. SEEALSO:: + + :meth:`included`, :meth:`excluded` + """ + classes = list(self._classes) + classes.sort() + return (self._modulus, classes) + + def included(self): + r""" + Return the list of elements which are additionally included + (that are, outside the congruence classes) to this set. + + EXAMPLES:: + + sage: P = Primes(modulus=4) + sage: P + Set of prime numbers congruent to 1 modulo 4: 5, 13, 17, 29, ... + sage: P.included() + [] + + :: + + sage: Q = P.include(2) + sage: Q + Set of prime numbers congruent to 1 modulo 4 with 2 included: 2, 5, 13, 17, ... + sage: Q.included() + [2] + + .. SEEALSO:: + + :meth:`excluded`, :meth:`congruence_classes` + """ + included = [x for x, b in self._exceptions.items() if b] + included.sort() + return included + + def excluded(self): + r""" + Return the list of elements which are excluded, that are the + elements in the congruence classes defining this subset but + not in this subset. + + EXAMPLES:: + + sage: P = Primes(modulus=4) + sage: P + Set of prime numbers congruent to 1 modulo 4: 5, 13, 17, 29, ... + sage: P.excluded() + [] + + :: + + sage: Q = P.exclude(5) + sage: Q + Set of prime numbers congruent to 1 modulo 4 with 5 excluded: 13, 17, 29, 37, ... + sage: Q.excluded() + [5] + + .. SEEALSO:: + + :meth:`included`, :meth:`congruence_classes` + """ + excluded = [x for x, b in self._exceptions.items() if not b] + excluded.sort() + return excluded + def _repr_(self): r""" Return a string representation of this subset. @@ -244,35 +358,27 @@ def _repr_(self): Empty set of prime numbers sage: E.include(range(50), check=False) # indirect doctest - Finite set of prime numbers: 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47 + Finite set of prime numbers: 2, 3, 5, 7, ..., 43, 47 """ - classes = sorted(list(self._classes)) - sc = ", ".join([str(c) for c in classes]) - included = [] - excluded = [] - for x, b in self._exceptions.items(): - if b: - included.append(x) - else: - excluded.append(x) - si = ", ".join([str(i) for i in sorted(included)]) - se = ", ".join([str(e) for e in sorted(excluded)]) + _, classes = self.congruence_classes() + included = self.included() + excluded = self.excluded() if not classes: if not included: return "Empty set of prime numbers" else: - return "Finite set of prime numbers: %s" % si + return "Finite set of prime numbers: %s" % repr_list(included) if self._modulus == 1: s = "Set of all prime numbers" else: - s = "Set of prime numbers congruent to %s modulo %s" % (sc, self._modulus) + s = "Set of prime numbers congruent to %s modulo %s" % (repr_list(classes), self._modulus) if included: - s += " with %s included" % si + s += " with %s included" % repr_list(included) if excluded: if not included: - s += " with %s excluded" % se + s += " with %s excluded" % repr_list(excluded) else: - s += " and %s excluded" % se + s += " and %s excluded" % repr_list(excluded) s += ": %s, ..." % (", ".join([str(n) for n in self[:4]])) return s From 31525d0bc2f275d1ea1cc6e61a4c2f874437bce9 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sun, 2 Nov 2025 08:08:27 +0100 Subject: [PATCH 17/26] missing doctest --- src/sage/sets/primes.py | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/src/sage/sets/primes.py b/src/sage/sets/primes.py index b310b2d99b2..1bec3da397d 100644 --- a/src/sage/sets/primes.py +++ b/src/sage/sets/primes.py @@ -29,7 +29,31 @@ from sage.categories.sets_cat import EmptySetError -def repr_list(items, left=4, right=2): +def _repr_items(items, left=4, right=2): + r""" + Return a string representation of the items in ``items`` + with possible ellipsis. + + INPUT: + + - ``items`` -- a list + + - ``left`` -- an integer (default: ``4``), the maximum + number of items listed at the beginning + + - ``right`` -- an integer (default: ``2``), the maximum + number of items listed at the end + + EXAMPLES:: + + sage: from sage.sets.primes import _repr_items + sage: _repr_items(range(5)) + '0, 1, 2, 3, 4' + sage: _repr_items(range(10)) + '0, 1, 2, 3, ..., 8, 9' + sage: _repr_items(range(10), left=3, right=3) + '0, 1, 2, ..., 7, 8, 9' + """ if len(items) <= left + right + 1: s = [str(item) for item in items] else: @@ -367,18 +391,18 @@ def _repr_(self): if not included: return "Empty set of prime numbers" else: - return "Finite set of prime numbers: %s" % repr_list(included) + return "Finite set of prime numbers: %s" % _repr_items(included) if self._modulus == 1: s = "Set of all prime numbers" else: - s = "Set of prime numbers congruent to %s modulo %s" % (repr_list(classes), self._modulus) + s = "Set of prime numbers congruent to %s modulo %s" % (_repr_items(classes), self._modulus) if included: - s += " with %s included" % repr_list(included) + s += " with %s included" % _repr_items(included) if excluded: if not included: - s += " with %s excluded" % repr_list(excluded) + s += " with %s excluded" % _repr_items(excluded) else: - s += " and %s excluded" % repr_list(excluded) + s += " and %s excluded" % _repr_items(excluded) s += ": %s, ..." % (", ".join([str(n) for n in self[:4]])) return s From 2ddb3817f14c7020ea51ecf61573b5e6b6d70856 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sun, 2 Nov 2025 08:29:20 +0100 Subject: [PATCH 18/26] method in_range --- src/sage/sets/primes.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/sage/sets/primes.py b/src/sage/sets/primes.py index 1bec3da397d..af6cb34141b 100644 --- a/src/sage/sets/primes.py +++ b/src/sage/sets/primes.py @@ -547,6 +547,35 @@ def _an_element_(self): raise ValueError("this set is empty") return self.next(42) + def in_range(self, start, stop=None): + r""" + Return the list of the elements of this set which are + in the given range. + + EXAMPLES:: + + sage: P = Primes(modulus=3); P + Set of prime numbers congruent to 1 modulo 3: 7, 13, 19, 31, ... + sage: P.in_range(50, 100) + [61, 67, 73, 79, 97] + + When a unique integer is passed, it is interpreted as the + upper bound:: + + sage: P.in_range(50) + [7, 13, 19, 31, 37, 43] + """ + if stop is None: + stop = start + start = 1 + elements = [] + x = start - 1 + while True: + x = self.next(x) + if x >= stop: + return elements + elements.append(x) + def unrank(self, n): r""" Return the ``n``-th element of this set. From fd9b973f9480cf1d8b650b0e8c2116e1c5bb58d0 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sun, 2 Nov 2025 19:38:39 +0100 Subject: [PATCH 19/26] Update src/sage/sets/primes.py Co-authored-by: Martin Rubey --- src/sage/sets/primes.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/sage/sets/primes.py b/src/sage/sets/primes.py index af6cb34141b..15a5eb4bdc7 100644 --- a/src/sage/sets/primes.py +++ b/src/sage/sets/primes.py @@ -1016,8 +1016,7 @@ def is_subset(self, other, almost=False): def is_supset(self, other, almost=False): r""" - Return ``True`` if this set of is supset of ``other``; - ``False`` otherwise. + Return whether this set contains the set ``other`` as a subset. INPUT: From 14f14ed22c0a784341dc1039e46ea81177fab9b8 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sun, 2 Nov 2025 19:40:03 +0100 Subject: [PATCH 20/26] Update src/sage/sets/primes.py Co-authored-by: Martin Rubey --- src/sage/sets/primes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/sets/primes.py b/src/sage/sets/primes.py index 15a5eb4bdc7..f982b0a353c 100644 --- a/src/sage/sets/primes.py +++ b/src/sage/sets/primes.py @@ -559,7 +559,7 @@ def in_range(self, start, stop=None): sage: P.in_range(50, 100) [61, 67, 73, 79, 97] - When a unique integer is passed, it is interpreted as the + When a single integer is passed, it is interpreted as the upper bound:: sage: P.in_range(50) From 60bf706111c7f6e039c109bcfbb59f251fd2df71 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sun, 2 Nov 2025 19:45:53 +0100 Subject: [PATCH 21/26] supset -> superset --- src/sage/sets/primes.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/sage/sets/primes.py b/src/sage/sets/primes.py index af6cb34141b..f02d1ba94b9 100644 --- a/src/sage/sets/primes.py +++ b/src/sage/sets/primes.py @@ -1006,7 +1006,7 @@ def is_subset(self, other, almost=False): .. SEEALSO:: - :meth:`is_supset`, :meth:`is_disjoint`, :meth:`is_almost_equal` + :meth:`is_superset`, :meth:`is_disjoint`, :meth:`is_almost_equal` """ P = self.intersection(other) if almost: @@ -1014,7 +1014,7 @@ def is_subset(self, other, almost=False): else: return P == self - def is_supset(self, other, almost=False): + def is_superset(self, other, almost=False): r""" Return ``True`` if this set of is supset of ``other``; ``False`` otherwise. @@ -1032,9 +1032,9 @@ def is_supset(self, other, almost=False): Set of prime numbers congruent to 1 modulo 4: 5, 13, 17, 29, ... sage: Q = Primes(modulus=8); Q Set of prime numbers congruent to 1 modulo 8: 17, 41, 73, 89, ... - sage: P.is_supset(Q) + sage: P.is_superset(Q) True - sage: Q.is_supset(P) + sage: Q.is_superset(P) False When ``almost=True``, the inclusion is only checked up to a @@ -1042,9 +1042,9 @@ def is_supset(self, other, almost=False): sage: Q2 = Q.include(2); Q2 Set of prime numbers congruent to 1 modulo 8 with 2 included: 2, 17, 41, 73, ... - sage: P.is_supset(Q2) + sage: P.is_superset(Q2) False - sage: P.is_supset(Q2, almost=True) + sage: P.is_superset(Q2, almost=True) True .. SEEALSO:: From dcc0f162f067055ca320f989145195689e1348df Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sun, 2 Nov 2025 23:47:49 +0100 Subject: [PATCH 22/26] typos and rephrasing Co-authored-by: Martin Rubey --- src/sage/sets/primes.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/sage/sets/primes.py b/src/sage/sets/primes.py index 6eb5d2a52f6..87d61752096 100644 --- a/src/sage/sets/primes.py +++ b/src/sage/sets/primes.py @@ -290,7 +290,7 @@ def congruence_classes(self): (5, [1, 3]) If this subset is finite, the output of this method is always `(1, [])`. - The elements of the subset can be retreived using the method :meth:`list` + The elements of the subset can be retrieved using the method :meth:`list` or :meth:`included`:: sage: P = Primes(modulus=0, classes=range(50)) @@ -838,7 +838,7 @@ def complement_in_primes(self): def intersection(self, other): r""" - Return the intesection of this set with ``other``. + Return the intersection of this set with ``other``. INPUT: @@ -891,7 +891,7 @@ def intersection(self, other): def union(self, other): r""" - Return the intesection of this set with ``other``. + Return the union of this set and ``other``. INPUT: @@ -1058,9 +1058,8 @@ def is_superset(self, other, almost=False): def is_disjoint(self, other, almost=False): r""" - Return ``True`` if the intersection of this set with ``other`` - is empty (resp. finite) if ``almost`` is ``False`` (resp. ``True``); - return ``False`` otherwise. + Return whether the intersection of this set with ``other`` + is empty (or finite, if ``almost`` is ``True``). INPUT: From 66e5d69c6e7d542ce856f44a49142728613486b1 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Mon, 3 Nov 2025 07:35:18 +0100 Subject: [PATCH 23/26] improve intersection/union and remove in_range --- src/sage/sets/primes.py | 121 +++++++++++++++++++++------------------- 1 file changed, 64 insertions(+), 57 deletions(-) diff --git a/src/sage/sets/primes.py b/src/sage/sets/primes.py index 87d61752096..8f81bc11995 100644 --- a/src/sage/sets/primes.py +++ b/src/sage/sets/primes.py @@ -225,8 +225,8 @@ def __classcall__(cls, modulus=1, classes=None, exceptions=None): # We format the final result and make it hashable classes = tuple([c for c in range(modulus) if indic[c] is True]) - exceptions = [(x, b) for x, b in exceptions.items() - if x.is_prime() and (b != (indic[x % modulus] is True))] + exceptions = [(ZZ(x), b) for x, b in exceptions.items() + if ZZ(x).is_prime() and (b != (indic[x % modulus] is True))] exceptions.sort() exceptions = tuple(exceptions) @@ -547,35 +547,6 @@ def _an_element_(self): raise ValueError("this set is empty") return self.next(42) - def in_range(self, start, stop=None): - r""" - Return the list of the elements of this set which are - in the given range. - - EXAMPLES:: - - sage: P = Primes(modulus=3); P - Set of prime numbers congruent to 1 modulo 3: 7, 13, 19, 31, ... - sage: P.in_range(50, 100) - [61, 67, 73, 79, 97] - - When a single integer is passed, it is interpreted as the - upper bound:: - - sage: P.in_range(50) - [7, 13, 19, 31, 37, 43] - """ - if stop is None: - stop = start - start = 1 - elements = [] - x = start - 1 - while True: - x = self.next(x) - if x >= stop: - return elements - elements.append(x) - def unrank(self, n): r""" Return the ``n``-th element of this set. @@ -853,6 +824,11 @@ def intersection(self, other): sage: P.intersection(Q) Set of prime numbers congruent to 11 modulo 15: 11, 41, 71, 101, ... + It is also possible to take the intersection with a range:: + + sage: P.intersection(range(100)) + Finite set of prime numbers: 11, 31, 41, 61, 71 + TESTS:: sage: P = Primes(modulus=5, exceptions={5: True, 11: False}); P @@ -869,7 +845,7 @@ def intersection(self, other): sage: P.intersection(RR) Traceback (most recent call last): ... - NotImplementedError: boolean operations are only implemented with other sets of prime numbers + NotImplementedError: object does not support iteration .. SEEALSO:: @@ -877,16 +853,24 @@ def intersection(self, other): """ if other is ZZ: return self - if not isinstance(other, Primes): - raise NotImplementedError("boolean operations are only implemented with other sets of prime numbers") - modulus = self._modulus.lcm(other._modulus) - classes = [c for c in range(modulus) - if (c % self._modulus in self._classes - and c % other._modulus in other._classes)] - exceptions = {x: b for x, b in self._exceptions.items() - if not b or x in other} - exceptions.update((x, b) for x, b in other._exceptions.items() - if not b or x in self) + if isinstance(other, Primes): + modulus = self._modulus.lcm(other._modulus) + classes = [c for c in range(modulus) + if (c % self._modulus in self._classes + and c % other._modulus in other._classes)] + exceptions = {x: b for x, b in self._exceptions.items() + if not b or x in other} + exceptions.update((x, b) for x, b in other._exceptions.items() + if not b or x in self) + elif self.is_finite(): + modulus = 1 + classes = [] + exceptions = {x: True for x in self if x in other} + else: + # we try to enumerate the elements of "other" + modulus = 1 + classes = [] + exceptions = {x: True for x in list(other) if x in self} return Primes(modulus, classes, exceptions) def union(self, other): @@ -922,7 +906,7 @@ def union(self, other): sage: P.union(RR) Traceback (most recent call last): ... - NotImplementedError: boolean operations are only implemented with other sets of prime numbers + NotImplementedError: object does not support iteration .. SEEALSO:: @@ -930,22 +914,32 @@ def union(self, other): """ if other is ZZ: return ZZ - if not isinstance(other, Primes): - raise NotImplementedError("boolean operations are only implemented with other sets of prime numbers") - modulus = self._modulus.lcm(other._modulus) - classes = [c for c in range(modulus) - if (c % self._modulus in self._classes - or c % other._modulus in other._classes)] - exceptions = {x: b for x, b in self._exceptions.items() - if b or x not in other} - exceptions.update((x, b) for x, b in other._exceptions.items() - if b or x not in self) + if isinstance(other, Primes): + modulus = self._modulus.lcm(other._modulus) + classes = [c for c in range(modulus) + if (c % self._modulus in self._classes + or c % other._modulus in other._classes)] + exceptions = {x: b for x, b in self._exceptions.items() + if b or x not in other} + exceptions.update((x, b) for x, b in other._exceptions.items() + if b or x not in self) + else: + # we try to enumerate the elements of "other" + modulus = self._modulus + classes = self._classes + exceptions = self._exceptions.copy() + for x in list(other): + x = ZZ(x) + if x.is_prime(): + exceptions[x] = True + else: + raise NotImplementedError("the result of the union is a subset of the set of prime numbers") return Primes(modulus, classes, exceptions) def is_almost_equal(self, other): r""" - Return ``True`` if this set only differs from ``other`` - by a finite set; return ``False`` otherwise. + Return whether this set only differs from ``other`` + by a finite set. INPUT: @@ -973,8 +967,7 @@ def is_almost_equal(self, other): def is_subset(self, other, almost=False): r""" - Return ``True`` if this set of is subset of ``other``; - ``False`` otherwise. + Return whether this set is a subset of ``other``. INPUT: @@ -1004,10 +997,17 @@ def is_subset(self, other, almost=False): sage: Q2.is_subset(P, almost=True) True + TESTS:: + + sage: P.is_subset(ZZ) + True + .. SEEALSO:: :meth:`is_superset`, :meth:`is_disjoint`, :meth:`is_almost_equal` """ + if other is ZZ: + return True P = self.intersection(other) if almost: return P.is_almost_equal(self) @@ -1046,10 +1046,17 @@ def is_superset(self, other, almost=False): sage: P.is_superset(Q2, almost=True) True + TESTS:: + + sage: P.is_superset(ZZ) + False + .. SEEALSO:: :meth:`is_subset`, :meth:`is_disjoint`, :meth:`is_almost_equal` """ + if other is ZZ: + return False P = self.intersection(other) if almost: return P.is_almost_equal(other) From c24b6cbd2d0a49efed3b518e48514905937cc89e Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Tue, 4 Nov 2025 07:40:33 +0100 Subject: [PATCH 24/26] improve intersection and union --- src/sage/sets/primes.py | 46 +++++++++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/src/sage/sets/primes.py b/src/sage/sets/primes.py index 8f81bc11995..462cd4cc8da 100644 --- a/src/sage/sets/primes.py +++ b/src/sage/sets/primes.py @@ -19,6 +19,7 @@ # https://www.gnu.org/licenses/ # **************************************************************************** +from sage.rings.semirings.non_negative_integer_semiring import NN from sage.rings.integer_ring import ZZ from sage.rings.infinity import infinity from .set import Set_generic @@ -840,18 +841,20 @@ def intersection(self, other): :: + sage: P.intersection(NN) == P + True sage: P.intersection(ZZ) == P True sage: P.intersection(RR) Traceback (most recent call last): ... - NotImplementedError: object does not support iteration + NotImplementedError: intersection with general infinite sets is not implemented .. SEEALSO:: :meth:`complement_in_primes`, :meth:`union` """ - if other is ZZ: + if other is NN or other is ZZ: return self if isinstance(other, Primes): modulus = self._modulus.lcm(other._modulus) @@ -862,15 +865,30 @@ def intersection(self, other): if not b or x in other} exceptions.update((x, b) for x, b in other._exceptions.items() if not b or x in self) - elif self.is_finite(): - modulus = 1 - classes = [] - exceptions = {x: True for x in self if x in other} else: - # we try to enumerate the elements of "other" modulus = 1 classes = [] - exceptions = {x: True for x in list(other) if x in self} + if isinstance(other, range) and other.step == 1: + exceptions = {} + x = other.start - 1 + while True: + try: + x = self.next(x) + except ValueError: + break + if x >= other.stop: + break + exceptions[x] = True + elif self.is_finite() and hasattr(other, "__contains__"): + # this would not work reliably if ``other`` does not + # implement ``__contains__``, because ``x in other`` + # then consumes ``other`` + exceptions = {x: True for x in self if x in other} + else: + if hasattr(other, "is_finite") and not other.is_finite(): + raise NotImplementedError("intersection with general infinite sets is not implemented") + # if other is infinite but does not know it, this will loop forever + exceptions = {x: True for x in other if x in self} return Primes(modulus, classes, exceptions) def union(self, other): @@ -901,17 +919,21 @@ def union(self, other): :: + sage: P.union(NN) == NN + True sage: P.union(ZZ) == ZZ True sage: P.union(RR) Traceback (most recent call last): ... - NotImplementedError: object does not support iteration + NotImplementedError: union with general infinite sets is not implemented .. SEEALSO:: :meth:`complement_in_primes`, :meth:`intersection` """ + if other is NN: + return NN if other is ZZ: return ZZ if isinstance(other, Primes): @@ -924,11 +946,13 @@ def union(self, other): exceptions.update((x, b) for x, b in other._exceptions.items() if b or x not in self) else: - # we try to enumerate the elements of "other" + # we try to enumerate the elements of "other + if hasattr(other, "is_finite") and not other.is_finite(): + raise NotImplementedError("union with general infinite sets is not implemented") modulus = self._modulus classes = self._classes exceptions = self._exceptions.copy() - for x in list(other): + for x in other: x = ZZ(x) if x.is_prime(): exceptions[x] = True From 05fc6d2a5c329fe84bcacf6647332c030dcaa0ef Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Tue, 4 Nov 2025 07:44:20 +0100 Subject: [PATCH 25/26] add Martin's doctest --- src/sage/sets/primes.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/sage/sets/primes.py b/src/sage/sets/primes.py index 462cd4cc8da..17c7b30c84b 100644 --- a/src/sage/sets/primes.py +++ b/src/sage/sets/primes.py @@ -850,6 +850,11 @@ def intersection(self, other): ... NotImplementedError: intersection with general infinite sets is not implemented + sage: P = Primes(modulus=0, classes=range(30)) + sage: P.intersection(reversed([13, 7, 11, 37])) + Finite set of prime numbers: 7, 11, 13 + + .. SEEALSO:: :meth:`complement_in_primes`, :meth:`union` From 1ef8180f47e241bf1c30a1b041b7fbcd5bae41bf Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Tue, 4 Nov 2025 07:48:06 +0100 Subject: [PATCH 26/26] typo --- src/sage/sets/primes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/sets/primes.py b/src/sage/sets/primes.py index 17c7b30c84b..eb76e073990 100644 --- a/src/sage/sets/primes.py +++ b/src/sage/sets/primes.py @@ -951,7 +951,7 @@ def union(self, other): exceptions.update((x, b) for x, b in other._exceptions.items() if b or x not in self) else: - # we try to enumerate the elements of "other + # we try to enumerate the elements of "other" if hasattr(other, "is_finite") and not other.is_finite(): raise NotImplementedError("union with general infinite sets is not implemented") modulus = self._modulus