Spaces:
Sleeping
Sleeping
from sympy.core import Basic, Dict, sympify, Tuple | |
from sympy.core.numbers import Integer | |
from sympy.core.sorting import default_sort_key | |
from sympy.core.sympify import _sympify | |
from sympy.functions.combinatorial.numbers import bell | |
from sympy.matrices import zeros | |
from sympy.sets.sets import FiniteSet, Union | |
from sympy.utilities.iterables import flatten, group | |
from sympy.utilities.misc import as_int | |
from collections import defaultdict | |
class Partition(FiniteSet): | |
""" | |
This class represents an abstract partition. | |
A partition is a set of disjoint sets whose union equals a given set. | |
See Also | |
======== | |
sympy.utilities.iterables.partitions, | |
sympy.utilities.iterables.multiset_partitions | |
""" | |
_rank = None | |
_partition = None | |
def __new__(cls, *partition): | |
""" | |
Generates a new partition object. | |
This method also verifies if the arguments passed are | |
valid and raises a ValueError if they are not. | |
Examples | |
======== | |
Creating Partition from Python lists: | |
>>> from sympy.combinatorics import Partition | |
>>> a = Partition([1, 2], [3]) | |
>>> a | |
Partition({3}, {1, 2}) | |
>>> a.partition | |
[[1, 2], [3]] | |
>>> len(a) | |
2 | |
>>> a.members | |
(1, 2, 3) | |
Creating Partition from Python sets: | |
>>> Partition({1, 2, 3}, {4, 5}) | |
Partition({4, 5}, {1, 2, 3}) | |
Creating Partition from SymPy finite sets: | |
>>> from sympy import FiniteSet | |
>>> a = FiniteSet(1, 2, 3) | |
>>> b = FiniteSet(4, 5) | |
>>> Partition(a, b) | |
Partition({4, 5}, {1, 2, 3}) | |
""" | |
args = [] | |
dups = False | |
for arg in partition: | |
if isinstance(arg, list): | |
as_set = set(arg) | |
if len(as_set) < len(arg): | |
dups = True | |
break # error below | |
arg = as_set | |
args.append(_sympify(arg)) | |
if not all(isinstance(part, FiniteSet) for part in args): | |
raise ValueError( | |
"Each argument to Partition should be " \ | |
"a list, set, or a FiniteSet") | |
# sort so we have a canonical reference for RGS | |
U = Union(*args) | |
if dups or len(U) < sum(len(arg) for arg in args): | |
raise ValueError("Partition contained duplicate elements.") | |
obj = FiniteSet.__new__(cls, *args) | |
obj.members = tuple(U) | |
obj.size = len(U) | |
return obj | |
def sort_key(self, order=None): | |
"""Return a canonical key that can be used for sorting. | |
Ordering is based on the size and sorted elements of the partition | |
and ties are broken with the rank. | |
Examples | |
======== | |
>>> from sympy import default_sort_key | |
>>> from sympy.combinatorics import Partition | |
>>> from sympy.abc import x | |
>>> a = Partition([1, 2]) | |
>>> b = Partition([3, 4]) | |
>>> c = Partition([1, x]) | |
>>> d = Partition(list(range(4))) | |
>>> l = [d, b, a + 1, a, c] | |
>>> l.sort(key=default_sort_key); l | |
[Partition({1, 2}), Partition({1}, {2}), Partition({1, x}), Partition({3, 4}), Partition({0, 1, 2, 3})] | |
""" | |
if order is None: | |
members = self.members | |
else: | |
members = tuple(sorted(self.members, | |
key=lambda w: default_sort_key(w, order))) | |
return tuple(map(default_sort_key, (self.size, members, self.rank))) | |
def partition(self): | |
"""Return partition as a sorted list of lists. | |
Examples | |
======== | |
>>> from sympy.combinatorics import Partition | |
>>> Partition([1], [2, 3]).partition | |
[[1], [2, 3]] | |
""" | |
if self._partition is None: | |
self._partition = sorted([sorted(p, key=default_sort_key) | |
for p in self.args]) | |
return self._partition | |
def __add__(self, other): | |
""" | |
Return permutation whose rank is ``other`` greater than current rank, | |
(mod the maximum rank for the set). | |
Examples | |
======== | |
>>> from sympy.combinatorics import Partition | |
>>> a = Partition([1, 2], [3]) | |
>>> a.rank | |
1 | |
>>> (a + 1).rank | |
2 | |
>>> (a + 100).rank | |
1 | |
""" | |
other = as_int(other) | |
offset = self.rank + other | |
result = RGS_unrank((offset) % | |
RGS_enum(self.size), | |
self.size) | |
return Partition.from_rgs(result, self.members) | |
def __sub__(self, other): | |
""" | |
Return permutation whose rank is ``other`` less than current rank, | |
(mod the maximum rank for the set). | |
Examples | |
======== | |
>>> from sympy.combinatorics import Partition | |
>>> a = Partition([1, 2], [3]) | |
>>> a.rank | |
1 | |
>>> (a - 1).rank | |
0 | |
>>> (a - 100).rank | |
1 | |
""" | |
return self.__add__(-other) | |
def __le__(self, other): | |
""" | |
Checks if a partition is less than or equal to | |
the other based on rank. | |
Examples | |
======== | |
>>> from sympy.combinatorics import Partition | |
>>> a = Partition([1, 2], [3, 4, 5]) | |
>>> b = Partition([1], [2, 3], [4], [5]) | |
>>> a.rank, b.rank | |
(9, 34) | |
>>> a <= a | |
True | |
>>> a <= b | |
True | |
""" | |
return self.sort_key() <= sympify(other).sort_key() | |
def __lt__(self, other): | |
""" | |
Checks if a partition is less than the other. | |
Examples | |
======== | |
>>> from sympy.combinatorics import Partition | |
>>> a = Partition([1, 2], [3, 4, 5]) | |
>>> b = Partition([1], [2, 3], [4], [5]) | |
>>> a.rank, b.rank | |
(9, 34) | |
>>> a < b | |
True | |
""" | |
return self.sort_key() < sympify(other).sort_key() | |
def rank(self): | |
""" | |
Gets the rank of a partition. | |
Examples | |
======== | |
>>> from sympy.combinatorics import Partition | |
>>> a = Partition([1, 2], [3], [4, 5]) | |
>>> a.rank | |
13 | |
""" | |
if self._rank is not None: | |
return self._rank | |
self._rank = RGS_rank(self.RGS) | |
return self._rank | |
def RGS(self): | |
""" | |
Returns the "restricted growth string" of the partition. | |
Explanation | |
=========== | |
The RGS is returned as a list of indices, L, where L[i] indicates | |
the block in which element i appears. For example, in a partition | |
of 3 elements (a, b, c) into 2 blocks ([c], [a, b]) the RGS is | |
[1, 1, 0]: "a" is in block 1, "b" is in block 1 and "c" is in block 0. | |
Examples | |
======== | |
>>> from sympy.combinatorics import Partition | |
>>> a = Partition([1, 2], [3], [4, 5]) | |
>>> a.members | |
(1, 2, 3, 4, 5) | |
>>> a.RGS | |
(0, 0, 1, 2, 2) | |
>>> a + 1 | |
Partition({3}, {4}, {5}, {1, 2}) | |
>>> _.RGS | |
(0, 0, 1, 2, 3) | |
""" | |
rgs = {} | |
partition = self.partition | |
for i, part in enumerate(partition): | |
for j in part: | |
rgs[j] = i | |
return tuple([rgs[i] for i in sorted( | |
[i for p in partition for i in p], key=default_sort_key)]) | |
def from_rgs(self, rgs, elements): | |
""" | |
Creates a set partition from a restricted growth string. | |
Explanation | |
=========== | |
The indices given in rgs are assumed to be the index | |
of the element as given in elements *as provided* (the | |
elements are not sorted by this routine). Block numbering | |
starts from 0. If any block was not referenced in ``rgs`` | |
an error will be raised. | |
Examples | |
======== | |
>>> from sympy.combinatorics import Partition | |
>>> Partition.from_rgs([0, 1, 2, 0, 1], list('abcde')) | |
Partition({c}, {a, d}, {b, e}) | |
>>> Partition.from_rgs([0, 1, 2, 0, 1], list('cbead')) | |
Partition({e}, {a, c}, {b, d}) | |
>>> a = Partition([1, 4], [2], [3, 5]) | |
>>> Partition.from_rgs(a.RGS, a.members) | |
Partition({2}, {1, 4}, {3, 5}) | |
""" | |
if len(rgs) != len(elements): | |
raise ValueError('mismatch in rgs and element lengths') | |
max_elem = max(rgs) + 1 | |
partition = [[] for i in range(max_elem)] | |
j = 0 | |
for i in rgs: | |
partition[i].append(elements[j]) | |
j += 1 | |
if not all(p for p in partition): | |
raise ValueError('some blocks of the partition were empty.') | |
return Partition(*partition) | |
class IntegerPartition(Basic): | |
""" | |
This class represents an integer partition. | |
Explanation | |
=========== | |
In number theory and combinatorics, a partition of a positive integer, | |
``n``, also called an integer partition, is a way of writing ``n`` as a | |
list of positive integers that sum to n. Two partitions that differ only | |
in the order of summands are considered to be the same partition; if order | |
matters then the partitions are referred to as compositions. For example, | |
4 has five partitions: [4], [3, 1], [2, 2], [2, 1, 1], and [1, 1, 1, 1]; | |
the compositions [1, 2, 1] and [1, 1, 2] are the same as partition | |
[2, 1, 1]. | |
See Also | |
======== | |
sympy.utilities.iterables.partitions, | |
sympy.utilities.iterables.multiset_partitions | |
References | |
========== | |
.. [1] https://en.wikipedia.org/wiki/Partition_%28number_theory%29 | |
""" | |
_dict = None | |
_keys = None | |
def __new__(cls, partition, integer=None): | |
""" | |
Generates a new IntegerPartition object from a list or dictionary. | |
Explanation | |
=========== | |
The partition can be given as a list of positive integers or a | |
dictionary of (integer, multiplicity) items. If the partition is | |
preceded by an integer an error will be raised if the partition | |
does not sum to that given integer. | |
Examples | |
======== | |
>>> from sympy.combinatorics.partitions import IntegerPartition | |
>>> a = IntegerPartition([5, 4, 3, 1, 1]) | |
>>> a | |
IntegerPartition(14, (5, 4, 3, 1, 1)) | |
>>> print(a) | |
[5, 4, 3, 1, 1] | |
>>> IntegerPartition({1:3, 2:1}) | |
IntegerPartition(5, (2, 1, 1, 1)) | |
If the value that the partition should sum to is given first, a check | |
will be made to see n error will be raised if there is a discrepancy: | |
>>> IntegerPartition(10, [5, 4, 3, 1]) | |
Traceback (most recent call last): | |
... | |
ValueError: The partition is not valid | |
""" | |
if integer is not None: | |
integer, partition = partition, integer | |
if isinstance(partition, (dict, Dict)): | |
_ = [] | |
for k, v in sorted(partition.items(), reverse=True): | |
if not v: | |
continue | |
k, v = as_int(k), as_int(v) | |
_.extend([k]*v) | |
partition = tuple(_) | |
else: | |
partition = tuple(sorted(map(as_int, partition), reverse=True)) | |
sum_ok = False | |
if integer is None: | |
integer = sum(partition) | |
sum_ok = True | |
else: | |
integer = as_int(integer) | |
if not sum_ok and sum(partition) != integer: | |
raise ValueError("Partition did not add to %s" % integer) | |
if any(i < 1 for i in partition): | |
raise ValueError("All integer summands must be greater than one") | |
obj = Basic.__new__(cls, Integer(integer), Tuple(*partition)) | |
obj.partition = list(partition) | |
obj.integer = integer | |
return obj | |
def prev_lex(self): | |
"""Return the previous partition of the integer, n, in lexical order, | |
wrapping around to [1, ..., 1] if the partition is [n]. | |
Examples | |
======== | |
>>> from sympy.combinatorics.partitions import IntegerPartition | |
>>> p = IntegerPartition([4]) | |
>>> print(p.prev_lex()) | |
[3, 1] | |
>>> p.partition > p.prev_lex().partition | |
True | |
""" | |
d = defaultdict(int) | |
d.update(self.as_dict()) | |
keys = self._keys | |
if keys == [1]: | |
return IntegerPartition({self.integer: 1}) | |
if keys[-1] != 1: | |
d[keys[-1]] -= 1 | |
if keys[-1] == 2: | |
d[1] = 2 | |
else: | |
d[keys[-1] - 1] = d[1] = 1 | |
else: | |
d[keys[-2]] -= 1 | |
left = d[1] + keys[-2] | |
new = keys[-2] | |
d[1] = 0 | |
while left: | |
new -= 1 | |
if left - new >= 0: | |
d[new] += left//new | |
left -= d[new]*new | |
return IntegerPartition(self.integer, d) | |
def next_lex(self): | |
"""Return the next partition of the integer, n, in lexical order, | |
wrapping around to [n] if the partition is [1, ..., 1]. | |
Examples | |
======== | |
>>> from sympy.combinatorics.partitions import IntegerPartition | |
>>> p = IntegerPartition([3, 1]) | |
>>> print(p.next_lex()) | |
[4] | |
>>> p.partition < p.next_lex().partition | |
True | |
""" | |
d = defaultdict(int) | |
d.update(self.as_dict()) | |
key = self._keys | |
a = key[-1] | |
if a == self.integer: | |
d.clear() | |
d[1] = self.integer | |
elif a == 1: | |
if d[a] > 1: | |
d[a + 1] += 1 | |
d[a] -= 2 | |
else: | |
b = key[-2] | |
d[b + 1] += 1 | |
d[1] = (d[b] - 1)*b | |
d[b] = 0 | |
else: | |
if d[a] > 1: | |
if len(key) == 1: | |
d.clear() | |
d[a + 1] = 1 | |
d[1] = self.integer - a - 1 | |
else: | |
a1 = a + 1 | |
d[a1] += 1 | |
d[1] = d[a]*a - a1 | |
d[a] = 0 | |
else: | |
b = key[-2] | |
b1 = b + 1 | |
d[b1] += 1 | |
need = d[b]*b + d[a]*a - b1 | |
d[a] = d[b] = 0 | |
d[1] = need | |
return IntegerPartition(self.integer, d) | |
def as_dict(self): | |
"""Return the partition as a dictionary whose keys are the | |
partition integers and the values are the multiplicity of that | |
integer. | |
Examples | |
======== | |
>>> from sympy.combinatorics.partitions import IntegerPartition | |
>>> IntegerPartition([1]*3 + [2] + [3]*4).as_dict() | |
{1: 3, 2: 1, 3: 4} | |
""" | |
if self._dict is None: | |
groups = group(self.partition, multiple=False) | |
self._keys = [g[0] for g in groups] | |
self._dict = dict(groups) | |
return self._dict | |
def conjugate(self): | |
""" | |
Computes the conjugate partition of itself. | |
Examples | |
======== | |
>>> from sympy.combinatorics.partitions import IntegerPartition | |
>>> a = IntegerPartition([6, 3, 3, 2, 1]) | |
>>> a.conjugate | |
[5, 4, 3, 1, 1, 1] | |
""" | |
j = 1 | |
temp_arr = list(self.partition) + [0] | |
k = temp_arr[0] | |
b = [0]*k | |
while k > 0: | |
while k > temp_arr[j]: | |
b[k - 1] = j | |
k -= 1 | |
j += 1 | |
return b | |
def __lt__(self, other): | |
"""Return True if self is less than other when the partition | |
is listed from smallest to biggest. | |
Examples | |
======== | |
>>> from sympy.combinatorics.partitions import IntegerPartition | |
>>> a = IntegerPartition([3, 1]) | |
>>> a < a | |
False | |
>>> b = a.next_lex() | |
>>> a < b | |
True | |
>>> a == b | |
False | |
""" | |
return list(reversed(self.partition)) < list(reversed(other.partition)) | |
def __le__(self, other): | |
"""Return True if self is less than other when the partition | |
is listed from smallest to biggest. | |
Examples | |
======== | |
>>> from sympy.combinatorics.partitions import IntegerPartition | |
>>> a = IntegerPartition([4]) | |
>>> a <= a | |
True | |
""" | |
return list(reversed(self.partition)) <= list(reversed(other.partition)) | |
def as_ferrers(self, char='#'): | |
""" | |
Prints the ferrer diagram of a partition. | |
Examples | |
======== | |
>>> from sympy.combinatorics.partitions import IntegerPartition | |
>>> print(IntegerPartition([1, 1, 5]).as_ferrers()) | |
##### | |
# | |
# | |
""" | |
return "\n".join([char*i for i in self.partition]) | |
def __str__(self): | |
return str(list(self.partition)) | |
def random_integer_partition(n, seed=None): | |
""" | |
Generates a random integer partition summing to ``n`` as a list | |
of reverse-sorted integers. | |
Examples | |
======== | |
>>> from sympy.combinatorics.partitions import random_integer_partition | |
For the following, a seed is given so a known value can be shown; in | |
practice, the seed would not be given. | |
>>> random_integer_partition(100, seed=[1, 1, 12, 1, 2, 1, 85, 1]) | |
[85, 12, 2, 1] | |
>>> random_integer_partition(10, seed=[1, 2, 3, 1, 5, 1]) | |
[5, 3, 1, 1] | |
>>> random_integer_partition(1) | |
[1] | |
""" | |
from sympy.core.random import _randint | |
n = as_int(n) | |
if n < 1: | |
raise ValueError('n must be a positive integer') | |
randint = _randint(seed) | |
partition = [] | |
while (n > 0): | |
k = randint(1, n) | |
mult = randint(1, n//k) | |
partition.append((k, mult)) | |
n -= k*mult | |
partition.sort(reverse=True) | |
partition = flatten([[k]*m for k, m in partition]) | |
return partition | |
def RGS_generalized(m): | |
""" | |
Computes the m + 1 generalized unrestricted growth strings | |
and returns them as rows in matrix. | |
Examples | |
======== | |
>>> from sympy.combinatorics.partitions import RGS_generalized | |
>>> RGS_generalized(6) | |
Matrix([ | |
[ 1, 1, 1, 1, 1, 1, 1], | |
[ 1, 2, 3, 4, 5, 6, 0], | |
[ 2, 5, 10, 17, 26, 0, 0], | |
[ 5, 15, 37, 77, 0, 0, 0], | |
[ 15, 52, 151, 0, 0, 0, 0], | |
[ 52, 203, 0, 0, 0, 0, 0], | |
[203, 0, 0, 0, 0, 0, 0]]) | |
""" | |
d = zeros(m + 1) | |
for i in range(m + 1): | |
d[0, i] = 1 | |
for i in range(1, m + 1): | |
for j in range(m): | |
if j <= m - i: | |
d[i, j] = j * d[i - 1, j] + d[i - 1, j + 1] | |
else: | |
d[i, j] = 0 | |
return d | |
def RGS_enum(m): | |
""" | |
RGS_enum computes the total number of restricted growth strings | |
possible for a superset of size m. | |
Examples | |
======== | |
>>> from sympy.combinatorics.partitions import RGS_enum | |
>>> from sympy.combinatorics import Partition | |
>>> RGS_enum(4) | |
15 | |
>>> RGS_enum(5) | |
52 | |
>>> RGS_enum(6) | |
203 | |
We can check that the enumeration is correct by actually generating | |
the partitions. Here, the 15 partitions of 4 items are generated: | |
>>> a = Partition(list(range(4))) | |
>>> s = set() | |
>>> for i in range(20): | |
... s.add(a) | |
... a += 1 | |
... | |
>>> assert len(s) == 15 | |
""" | |
if (m < 1): | |
return 0 | |
elif (m == 1): | |
return 1 | |
else: | |
return bell(m) | |
def RGS_unrank(rank, m): | |
""" | |
Gives the unranked restricted growth string for a given | |
superset size. | |
Examples | |
======== | |
>>> from sympy.combinatorics.partitions import RGS_unrank | |
>>> RGS_unrank(14, 4) | |
[0, 1, 2, 3] | |
>>> RGS_unrank(0, 4) | |
[0, 0, 0, 0] | |
""" | |
if m < 1: | |
raise ValueError("The superset size must be >= 1") | |
if rank < 0 or RGS_enum(m) <= rank: | |
raise ValueError("Invalid arguments") | |
L = [1] * (m + 1) | |
j = 1 | |
D = RGS_generalized(m) | |
for i in range(2, m + 1): | |
v = D[m - i, j] | |
cr = j*v | |
if cr <= rank: | |
L[i] = j + 1 | |
rank -= cr | |
j += 1 | |
else: | |
L[i] = int(rank / v + 1) | |
rank %= v | |
return [x - 1 for x in L[1:]] | |
def RGS_rank(rgs): | |
""" | |
Computes the rank of a restricted growth string. | |
Examples | |
======== | |
>>> from sympy.combinatorics.partitions import RGS_rank, RGS_unrank | |
>>> RGS_rank([0, 1, 2, 1, 3]) | |
42 | |
>>> RGS_rank(RGS_unrank(4, 7)) | |
4 | |
""" | |
rgs_size = len(rgs) | |
rank = 0 | |
D = RGS_generalized(rgs_size) | |
for i in range(1, rgs_size): | |
n = len(rgs[(i + 1):]) | |
m = max(rgs[0:i]) | |
rank += D[n, m + 1] * rgs[i] | |
return rank | |