Source code for pocketpartition.core.numerical_set
__all__ = ['NumericalSet'] # Specify the items to be exported
from functools import cached_property
[docs]
class NumericalSet:
_instances: dict = {}
def __new__(cls, gaps):
gaps_frozenset = frozenset(gaps)
if gaps_frozenset in cls._instances:
return cls._instances[gaps_frozenset]
instance = super(NumericalSet, cls).__new__(cls)
cls._instances[gaps_frozenset] = instance
return instance
def __init__(self, gaps):
"""
Initialize the numerical set with its gaps.
Parameters:
gaps (list of int): The gaps of the numerical set.
"""
self._gaps = frozenset(gaps)
self._frobenius_number = max(self._gaps) if self._gaps else -1
@property
def gaps(self):
return self._gaps
@property
def frobenius_number(self):
return self._frobenius_number
def __eq__(self, other: object) -> bool:
"""
Two numerical sets are equal iff they have the same gaps.
Because instances are singletons, this is equivalent to identity
comparison, but defining ``__eq__`` explicitly allows ``__hash__``
to be defined without triggering Python's implicit hash removal.
"""
if not isinstance(other, NumericalSet):
return NotImplemented
return self._gaps == other._gaps
def __hash__(self) -> int:
"""
Hash based on the frozen gap set.
Enables ``NumericalSet`` (and its subclasses) to be used as dict keys,
in sets, and as arguments to ``@lru_cache``-decorated functions.
"""
return hash(self._gaps)
def __contains__(self, n: int) -> bool:
"""
Return True if n is an element of this numerical set (i.e. not a gap).
O(1) frozenset lookup for n ≤ frobenius_number; all non-negative
integers beyond the Frobenius number are always elements.
Parameters:
n (int): The integer to test.
Returns:
bool: True if n belongs to the numerical set.
"""
if n < 0:
return False
if n > self._frobenius_number:
return True
return n not in self._gaps
def __iter__(self):
"""
Iterate over the elements of this numerical set in ascending order,
starting from 0 and stopping after the Frobenius number + 1
(i.e. yielding every non-gap up to and including frobenius_number + 1,
which is always an element).
Yields:
int: Elements of the numerical set in non-decreasing order.
"""
for n in range(self._frobenius_number + 2):
if n not in self._gaps:
yield n
def __str__(self):
return f"NumericalSet(gaps={sorted(self.gaps)})"
def __repr__(self):
return f"NumericalSet(genus={len(self.gaps)}, frobenius_number={self.frobenius_number})"
[docs]
def atom_monoid_gaps(self):
"""
Compute the gaps of the atom monoid.
Returns:
set of int: The gaps of the atom monoid.
"""
gaps_atom_monoid = set()
gaps = self.gaps.copy()
max_gap = max(gaps) if gaps else 0
for x in range(max_gap + max_gap + 1):
if any(x + t in gaps for t in range(max_gap + 1) if t not in gaps):
gaps_atom_monoid.add(x)
return gaps_atom_monoid
[docs]
def partition(self):
"""
Creates a partition based on a specific walk profile using the gaps attribute.
The walk is defined as follows:
- Start at 0.
- If the current number is in gaps, move up (create a new row with the same length as the current row).
- If the current number is not in gaps, move right (add one box to the current row).
- Continue this process until reaching the maximum number in gaps.
- Collect the lengths of each row at the end of the walk.
The resulting partition is returned as a list of integers in non-increasing order.
Returns:
list: A partition [a1, a2, ..., an] in non-increasing order, representing the profile of the walk.
"""
gaps = self.gaps.copy()
if not gaps:
return []
# Sort the set gaps
gaps = sorted(gaps)
# Initialize the current row and the partition list
current_row_length = 0
partition = []
for i in range(gaps[-1] + 1):
if i in gaps:
if current_row_length > 0:
partition.append(current_row_length)
else:
current_row_length += 1 # Move right means incrementing the row length
# Ensure the partition is in descending order
partition.sort(reverse=True)
return partition
[docs]
def small_elements(self):
"""
Compute the small elements of the numerical set.
Returns:
set of int: The small elements of the numerical set.
"""
gaps = self.gaps.copy()
if not gaps:
return set()
frobenius_number = max(gaps)
small_elements = []
for s in range(frobenius_number):
if s not in gaps:
small_elements.append(s)
return small_elements
@cached_property
def _multiplicity(self):
small = self.small_elements()
if not small:
return 1
nonzero = [e for e in small if e != 0]
return min(nonzero) if nonzero else self.frobenius_number + 1
[docs]
def multiplicity(self):
"""
Compute the multiplicity of the numerical semigroup.
Returns:
int: The multiplicity of the numerical semigroup.
"""
return self._multiplicity