746 lines
20 KiB
Python
746 lines
20 KiB
Python
|
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)))
|
||
|
|
||
|
@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
|