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 | |