|
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 |
|
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") |
|
|
|
|
|
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))) |
|
|
|
@property |
|
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() |
|
|
|
@property |
|
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 |
|
|
|
@property |
|
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)]) |
|
|
|
@classmethod |
|
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 |
|
|
|
@property |
|
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 |
|
|