import random from collections import defaultdict from collections.abc import Iterable from functools import reduce from sympy.core.parameters import global_parameters from sympy.core.basic import Atom from sympy.core.expr import Expr from sympy.core.numbers import Integer from sympy.core.sympify import _sympify from sympy.matrices import zeros from sympy.polys.polytools import lcm from sympy.utilities.iterables import (flatten, has_variety, minlex, has_dups, runs, is_sequence) from sympy.utilities.misc import as_int from mpmath.libmp.libintmath import ifac from sympy.multipledispatch import dispatch def _af_rmul(a, b): """ Return the product b*a; input and output are array forms. The ith value is a[b[i]]. Examples ======== >>> from sympy.combinatorics.permutations import _af_rmul, Permutation >>> a, b = [1, 0, 2], [0, 2, 1] >>> _af_rmul(a, b) [1, 2, 0] >>> [a[b[i]] for i in range(3)] [1, 2, 0] This handles the operands in reverse order compared to the ``*`` operator: >>> a = Permutation(a) >>> b = Permutation(b) >>> list(a*b) [2, 0, 1] >>> [b(a(i)) for i in range(3)] [2, 0, 1] See Also ======== rmul, _af_rmuln """ return [a[i] for i in b] def _af_rmuln(*abc): """ Given [a, b, c, ...] return the product of ...*c*b*a using array forms. The ith value is a[b[c[i]]]. Examples ======== >>> from sympy.combinatorics.permutations import _af_rmul, Permutation >>> a, b = [1, 0, 2], [0, 2, 1] >>> _af_rmul(a, b) [1, 2, 0] >>> [a[b[i]] for i in range(3)] [1, 2, 0] This handles the operands in reverse order compared to the ``*`` operator: >>> a = Permutation(a); b = Permutation(b) >>> list(a*b) [2, 0, 1] >>> [b(a(i)) for i in range(3)] [2, 0, 1] See Also ======== rmul, _af_rmul """ a = abc m = len(a) if m == 3: p0, p1, p2 = a return [p0[p1[i]] for i in p2] if m == 4: p0, p1, p2, p3 = a return [p0[p1[p2[i]]] for i in p3] if m == 5: p0, p1, p2, p3, p4 = a return [p0[p1[p2[p3[i]]]] for i in p4] if m == 6: p0, p1, p2, p3, p4, p5 = a return [p0[p1[p2[p3[p4[i]]]]] for i in p5] if m == 7: p0, p1, p2, p3, p4, p5, p6 = a return [p0[p1[p2[p3[p4[p5[i]]]]]] for i in p6] if m == 8: p0, p1, p2, p3, p4, p5, p6, p7 = a return [p0[p1[p2[p3[p4[p5[p6[i]]]]]]] for i in p7] if m == 1: return a[0][:] if m == 2: a, b = a return [a[i] for i in b] if m == 0: raise ValueError("String must not be empty") p0 = _af_rmuln(*a[:m//2]) p1 = _af_rmuln(*a[m//2:]) return [p0[i] for i in p1] def _af_parity(pi): """ Computes the parity of a permutation in array form. Explanation =========== The parity of a permutation reflects the parity of the number of inversions in the permutation, i.e., the number of pairs of x and y such that x > y but p[x] < p[y]. Examples ======== >>> from sympy.combinatorics.permutations import _af_parity >>> _af_parity([0, 1, 2, 3]) 0 >>> _af_parity([3, 2, 0, 1]) 1 See Also ======== Permutation """ n = len(pi) a = [0] * n c = 0 for j in range(n): if a[j] == 0: c += 1 a[j] = 1 i = j while pi[i] != j: i = pi[i] a[i] = 1 return (n - c) % 2 def _af_invert(a): """ Finds the inverse, ~A, of a permutation, A, given in array form. Examples ======== >>> from sympy.combinatorics.permutations import _af_invert, _af_rmul >>> A = [1, 2, 0, 3] >>> _af_invert(A) [2, 0, 1, 3] >>> _af_rmul(_, A) [0, 1, 2, 3] See Also ======== Permutation, __invert__ """ inv_form = [0] * len(a) for i, ai in enumerate(a): inv_form[ai] = i return inv_form def _af_pow(a, n): """ Routine for finding powers of a permutation. Examples ======== >>> from sympy.combinatorics import Permutation >>> from sympy.combinatorics.permutations import _af_pow >>> p = Permutation([2, 0, 3, 1]) >>> p.order() 4 >>> _af_pow(p._array_form, 4) [0, 1, 2, 3] """ if n == 0: return list(range(len(a))) if n < 0: return _af_pow(_af_invert(a), -n) if n == 1: return a[:] elif n == 2: b = [a[i] for i in a] elif n == 3: b = [a[a[i]] for i in a] elif n == 4: b = [a[a[a[i]]] for i in a] else: # use binary multiplication b = list(range(len(a))) while 1: if n & 1: b = [b[i] for i in a] n -= 1 if not n: break if n % 4 == 0: a = [a[a[a[i]]] for i in a] n = n // 4 elif n % 2 == 0: a = [a[i] for i in a] n = n // 2 return b def _af_commutes_with(a, b): """ Checks if the two permutations with array forms given by ``a`` and ``b`` commute. Examples ======== >>> from sympy.combinatorics.permutations import _af_commutes_with >>> _af_commutes_with([1, 2, 0], [0, 2, 1]) False See Also ======== Permutation, commutes_with """ return not any(a[b[i]] != b[a[i]] for i in range(len(a) - 1)) class Cycle(dict): """ Wrapper around dict which provides the functionality of a disjoint cycle. Explanation =========== A cycle shows the rule to use to move subsets of elements to obtain a permutation. The Cycle class is more flexible than Permutation in that 1) all elements need not be present in order to investigate how multiple cycles act in sequence and 2) it can contain singletons: >>> from sympy.combinatorics.permutations import Perm, Cycle A Cycle will automatically parse a cycle given as a tuple on the rhs: >>> Cycle(1, 2)(2, 3) (1 3 2) The identity cycle, Cycle(), can be used to start a product: >>> Cycle()(1, 2)(2, 3) (1 3 2) The array form of a Cycle can be obtained by calling the list method (or passing it to the list function) and all elements from 0 will be shown: >>> a = Cycle(1, 2) >>> a.list() [0, 2, 1] >>> list(a) [0, 2, 1] If a larger (or smaller) range is desired use the list method and provide the desired size -- but the Cycle cannot be truncated to a size smaller than the largest element that is out of place: >>> b = Cycle(2, 4)(1, 2)(3, 1, 4)(1, 3) >>> b.list() [0, 2, 1, 3, 4] >>> b.list(b.size + 1) [0, 2, 1, 3, 4, 5] >>> b.list(-1) [0, 2, 1] Singletons are not shown when printing with one exception: the largest element is always shown -- as a singleton if necessary: >>> Cycle(1, 4, 10)(4, 5) (1 5 4 10) >>> Cycle(1, 2)(4)(5)(10) (1 2)(10) The array form can be used to instantiate a Permutation so other properties of the permutation can be investigated: >>> Perm(Cycle(1, 2)(3, 4).list()).transpositions() [(1, 2), (3, 4)] Notes ===== The underlying structure of the Cycle is a dictionary and although the __iter__ method has been redefined to give the array form of the cycle, the underlying dictionary items are still available with the such methods as items(): >>> list(Cycle(1, 2).items()) [(1, 2), (2, 1)] See Also ======== Permutation """ def __missing__(self, arg): """Enter arg into dictionary and return arg.""" return as_int(arg) def __iter__(self): yield from self.list() def __call__(self, *other): """Return product of cycles processed from R to L. Examples ======== >>> from sympy.combinatorics import Cycle >>> Cycle(1, 2)(2, 3) (1 3 2) An instance of a Cycle will automatically parse list-like objects and Permutations that are on the right. It is more flexible than the Permutation in that all elements need not be present: >>> a = Cycle(1, 2) >>> a(2, 3) (1 3 2) >>> a(2, 3)(4, 5) (1 3 2)(4 5) """ rv = Cycle(*other) for k, v in zip(list(self.keys()), [rv[self[k]] for k in self.keys()]): rv[k] = v return rv def list(self, size=None): """Return the cycles as an explicit list starting from 0 up to the greater of the largest value in the cycles and size. Truncation of trailing unmoved items will occur when size is less than the maximum element in the cycle; if this is desired, setting ``size=-1`` will guarantee such trimming. Examples ======== >>> from sympy.combinatorics import Cycle >>> p = Cycle(2, 3)(4, 5) >>> p.list() [0, 1, 3, 2, 5, 4] >>> p.list(10) [0, 1, 3, 2, 5, 4, 6, 7, 8, 9] Passing a length too small will trim trailing, unchanged elements in the permutation: >>> Cycle(2, 4)(1, 2, 4).list(-1) [0, 2, 1] """ if not self and size is None: raise ValueError('must give size for empty Cycle') if size is not None: big = max([i for i in self.keys() if self[i] != i] + [0]) size = max(size, big + 1) else: size = self.size return [self[i] for i in range(size)] def __repr__(self): """We want it to print as a Cycle, not as a dict. Examples ======== >>> from sympy.combinatorics import Cycle >>> Cycle(1, 2) (1 2) >>> print(_) (1 2) >>> list(Cycle(1, 2).items()) [(1, 2), (2, 1)] """ if not self: return 'Cycle()' cycles = Permutation(self).cyclic_form s = ''.join(str(tuple(c)) for c in cycles) big = self.size - 1 if not any(i == big for c in cycles for i in c): s += '(%s)' % big return 'Cycle%s' % s def __str__(self): """We want it to be printed in a Cycle notation with no comma in-between. Examples ======== >>> from sympy.combinatorics import Cycle >>> Cycle(1, 2) (1 2) >>> Cycle(1, 2, 4)(5, 6) (1 2 4)(5 6) """ if not self: return '()' cycles = Permutation(self).cyclic_form s = ''.join(str(tuple(c)) for c in cycles) big = self.size - 1 if not any(i == big for c in cycles for i in c): s += '(%s)' % big s = s.replace(',', '') return s def __init__(self, *args): """Load up a Cycle instance with the values for the cycle. Examples ======== >>> from sympy.combinatorics import Cycle >>> Cycle(1, 2, 6) (1 2 6) """ if not args: return if len(args) == 1: if isinstance(args[0], Permutation): for c in args[0].cyclic_form: self.update(self(*c)) return elif isinstance(args[0], Cycle): for k, v in args[0].items(): self[k] = v return args = [as_int(a) for a in args] if any(i < 0 for i in args): raise ValueError('negative integers are not allowed in a cycle.') if has_dups(args): raise ValueError('All elements must be unique in a cycle.') for i in range(-len(args), 0): self[args[i]] = args[i + 1] @property def size(self): if not self: return 0 return max(self.keys()) + 1 def copy(self): return Cycle(self) class Permutation(Atom): r""" A permutation, alternatively known as an 'arrangement number' or 'ordering' is an arrangement of the elements of an ordered list into a one-to-one mapping with itself. The permutation of a given arrangement is given by indicating the positions of the elements after re-arrangement [2]_. For example, if one started with elements ``[x, y, a, b]`` (in that order) and they were reordered as ``[x, y, b, a]`` then the permutation would be ``[0, 1, 3, 2]``. Notice that (in SymPy) the first element is always referred to as 0 and the permutation uses the indices of the elements in the original ordering, not the elements ``(a, b, ...)`` themselves. >>> from sympy.combinatorics import Permutation >>> from sympy import init_printing >>> init_printing(perm_cyclic=False, pretty_print=False) Permutations Notation ===================== Permutations are commonly represented in disjoint cycle or array forms. Array Notation and 2-line Form ------------------------------------ In the 2-line form, the elements and their final positions are shown as a matrix with 2 rows: [0 1 2 ... n-1] [p(0) p(1) p(2) ... p(n-1)] Since the first line is always ``range(n)``, where n is the size of p, it is sufficient to represent the permutation by the second line, referred to as the "array form" of the permutation. This is entered in brackets as the argument to the Permutation class: >>> p = Permutation([0, 2, 1]); p Permutation([0, 2, 1]) Given i in range(p.size), the permutation maps i to i^p >>> [i^p for i in range(p.size)] [0, 2, 1] The composite of two permutations p*q means first apply p, then q, so i^(p*q) = (i^p)^q which is i^p^q according to Python precedence rules: >>> q = Permutation([2, 1, 0]) >>> [i^p^q for i in range(3)] [2, 0, 1] >>> [i^(p*q) for i in range(3)] [2, 0, 1] One can use also the notation p(i) = i^p, but then the composition rule is (p*q)(i) = q(p(i)), not p(q(i)): >>> [(p*q)(i) for i in range(p.size)] [2, 0, 1] >>> [q(p(i)) for i in range(p.size)] [2, 0, 1] >>> [p(q(i)) for i in range(p.size)] [1, 2, 0] Disjoint Cycle Notation ----------------------- In disjoint cycle notation, only the elements that have shifted are indicated. For example, [1, 3, 2, 0] can be represented as (0, 1, 3)(2). This can be understood from the 2 line format of the given permutation. In the 2-line form, [0 1 2 3] [1 3 2 0] The element in the 0th position is 1, so 0 -> 1. The element in the 1st position is three, so 1 -> 3. And the element in the third position is again 0, so 3 -> 0. Thus, 0 -> 1 -> 3 -> 0, and 2 -> 2. Thus, this can be represented as 2 cycles: (0, 1, 3)(2). In common notation, singular cycles are not explicitly written as they can be inferred implicitly. Only the relative ordering of elements in a cycle matter: >>> Permutation(1,2,3) == Permutation(2,3,1) == Permutation(3,1,2) True The disjoint cycle notation is convenient when representing permutations that have several cycles in them: >>> Permutation(1, 2)(3, 5) == Permutation([[1, 2], [3, 5]]) True It also provides some economy in entry when computing products of permutations that are written in disjoint cycle notation: >>> Permutation(1, 2)(1, 3)(2, 3) Permutation([0, 3, 2, 1]) >>> _ == Permutation([[1, 2]])*Permutation([[1, 3]])*Permutation([[2, 3]]) True Caution: when the cycles have common elements between them then the order in which the permutations are applied matters. This module applies the permutations from *left to right*. >>> Permutation(1, 2)(2, 3) == Permutation([(1, 2), (2, 3)]) True >>> Permutation(1, 2)(2, 3).list() [0, 3, 1, 2] In the above case, (1,2) is computed before (2,3). As 0 -> 0, 0 -> 0, element in position 0 is 0. As 1 -> 2, 2 -> 3, element in position 1 is 3. As 2 -> 1, 1 -> 1, element in position 2 is 1. As 3 -> 3, 3 -> 2, element in position 3 is 2. If the first and second elements had been swapped first, followed by the swapping of the second and third, the result would have been [0, 2, 3, 1]. If, you want to apply the cycles in the conventional right to left order, call the function with arguments in reverse order as demonstrated below: >>> Permutation([(1, 2), (2, 3)][::-1]).list() [0, 2, 3, 1] Entering a singleton in a permutation is a way to indicate the size of the permutation. The ``size`` keyword can also be used. Array-form entry: >>> Permutation([[1, 2], [9]]) Permutation([0, 2, 1], size=10) >>> Permutation([[1, 2]], size=10) Permutation([0, 2, 1], size=10) Cyclic-form entry: >>> Permutation(1, 2, size=10) Permutation([0, 2, 1], size=10) >>> Permutation(9)(1, 2) Permutation([0, 2, 1], size=10) Caution: no singleton containing an element larger than the largest in any previous cycle can be entered. This is an important difference in how Permutation and Cycle handle the ``__call__`` syntax. A singleton argument at the start of a Permutation performs instantiation of the Permutation and is permitted: >>> Permutation(5) Permutation([], size=6) A singleton entered after instantiation is a call to the permutation -- a function call -- and if the argument is out of range it will trigger an error. For this reason, it is better to start the cycle with the singleton: The following fails because there is no element 3: >>> Permutation(1, 2)(3) Traceback (most recent call last): ... IndexError: list index out of range This is ok: only the call to an out of range singleton is prohibited; otherwise the permutation autosizes: >>> Permutation(3)(1, 2) Permutation([0, 2, 1, 3]) >>> Permutation(1, 2)(3, 4) == Permutation(3, 4)(1, 2) True Equality testing ---------------- The array forms must be the same in order for permutations to be equal: >>> Permutation([1, 0, 2, 3]) == Permutation([1, 0]) False Identity Permutation -------------------- The identity permutation is a permutation in which no element is out of place. It can be entered in a variety of ways. All the following create an identity permutation of size 4: >>> I = Permutation([0, 1, 2, 3]) >>> all(p == I for p in [ ... Permutation(3), ... Permutation(range(4)), ... Permutation([], size=4), ... Permutation(size=4)]) True Watch out for entering the range *inside* a set of brackets (which is cycle notation): >>> I == Permutation([range(4)]) False Permutation Printing ==================== There are a few things to note about how Permutations are printed. .. deprecated:: 1.6 Configuring Permutation printing by setting ``Permutation.print_cyclic`` is deprecated. Users should use the ``perm_cyclic`` flag to the printers, as described below. 1) If you prefer one form (array or cycle) over another, you can set ``init_printing`` with the ``perm_cyclic`` flag. >>> from sympy import init_printing >>> p = Permutation(1, 2)(4, 5)(3, 4) >>> p Permutation([0, 2, 1, 4, 5, 3]) >>> init_printing(perm_cyclic=True, pretty_print=False) >>> p (1 2)(3 4 5) 2) Regardless of the setting, a list of elements in the array for cyclic form can be obtained and either of those can be copied and supplied as the argument to Permutation: >>> p.array_form [0, 2, 1, 4, 5, 3] >>> p.cyclic_form [[1, 2], [3, 4, 5]] >>> Permutation(_) == p True 3) Printing is economical in that as little as possible is printed while retaining all information about the size of the permutation: >>> init_printing(perm_cyclic=False, pretty_print=False) >>> Permutation([1, 0, 2, 3]) Permutation([1, 0, 2, 3]) >>> Permutation([1, 0, 2, 3], size=20) Permutation([1, 0], size=20) >>> Permutation([1, 0, 2, 4, 3, 5, 6], size=20) Permutation([1, 0, 2, 4, 3], size=20) >>> p = Permutation([1, 0, 2, 3]) >>> init_printing(perm_cyclic=True, pretty_print=False) >>> p (3)(0 1) >>> init_printing(perm_cyclic=False, pretty_print=False) The 2 was not printed but it is still there as can be seen with the array_form and size methods: >>> p.array_form [1, 0, 2, 3] >>> p.size 4 Short introduction to other methods =================================== The permutation can act as a bijective function, telling what element is located at a given position >>> q = Permutation([5, 2, 3, 4, 1, 0]) >>> q.array_form[1] # the hard way 2 >>> q(1) # the easy way 2 >>> {i: q(i) for i in range(q.size)} # showing the bijection {0: 5, 1: 2, 2: 3, 3: 4, 4: 1, 5: 0} The full cyclic form (including singletons) can be obtained: >>> p.full_cyclic_form [[0, 1], [2], [3]] Any permutation can be factored into transpositions of pairs of elements: >>> Permutation([[1, 2], [3, 4, 5]]).transpositions() [(1, 2), (3, 5), (3, 4)] >>> Permutation.rmul(*[Permutation([ti], size=6) for ti in _]).cyclic_form [[1, 2], [3, 4, 5]] The number of permutations on a set of n elements is given by n! and is called the cardinality. >>> p.size 4 >>> p.cardinality 24 A given permutation has a rank among all the possible permutations of the same elements, but what that rank is depends on how the permutations are enumerated. (There are a number of different methods of doing so.) The lexicographic rank is given by the rank method and this rank is used to increment a permutation with addition/subtraction: >>> p.rank() 6 >>> p + 1 Permutation([1, 0, 3, 2]) >>> p.next_lex() Permutation([1, 0, 3, 2]) >>> _.rank() 7 >>> p.unrank_lex(p.size, rank=7) Permutation([1, 0, 3, 2]) The product of two permutations p and q is defined as their composition as functions, (p*q)(i) = q(p(i)) [6]_. >>> p = Permutation([1, 0, 2, 3]) >>> q = Permutation([2, 3, 1, 0]) >>> list(q*p) [2, 3, 0, 1] >>> list(p*q) [3, 2, 1, 0] >>> [q(p(i)) for i in range(p.size)] [3, 2, 1, 0] The permutation can be 'applied' to any list-like object, not only Permutations: >>> p(['zero', 'one', 'four', 'two']) ['one', 'zero', 'four', 'two'] >>> p('zo42') ['o', 'z', '4', '2'] If you have a list of arbitrary elements, the corresponding permutation can be found with the from_sequence method: >>> Permutation.from_sequence('SymPy') Permutation([1, 3, 2, 0, 4]) Checking if a Permutation is contained in a Group ================================================= Generally if you have a group of permutations G on n symbols, and you're checking if a permutation on less than n symbols is part of that group, the check will fail. Here is an example for n=5 and we check if the cycle (1,2,3) is in G: >>> from sympy import init_printing >>> init_printing(perm_cyclic=True, pretty_print=False) >>> from sympy.combinatorics import Cycle, Permutation >>> from sympy.combinatorics.perm_groups import PermutationGroup >>> G = PermutationGroup(Cycle(2, 3)(4, 5), Cycle(1, 2, 3, 4, 5)) >>> p1 = Permutation(Cycle(2, 5, 3)) >>> p2 = Permutation(Cycle(1, 2, 3)) >>> a1 = Permutation(Cycle(1, 2, 3).list(6)) >>> a2 = Permutation(Cycle(1, 2, 3)(5)) >>> a3 = Permutation(Cycle(1, 2, 3),size=6) >>> for p in [p1,p2,a1,a2,a3]: p, G.contains(p) ((2 5 3), True) ((1 2 3), False) ((5)(1 2 3), True) ((5)(1 2 3), True) ((5)(1 2 3), True) The check for p2 above will fail. Checking if p1 is in G works because SymPy knows G is a group on 5 symbols, and p1 is also on 5 symbols (its largest element is 5). For ``a1``, the ``.list(6)`` call will extend the permutation to 5 symbols, so the test will work as well. In the case of ``a2`` the permutation is being extended to 5 symbols by using a singleton, and in the case of ``a3`` it's extended through the constructor argument ``size=6``. There is another way to do this, which is to tell the ``contains`` method that the number of symbols the group is on doesn't need to match perfectly the number of symbols for the permutation: >>> G.contains(p2,strict=False) True This can be via the ``strict`` argument to the ``contains`` method, and SymPy will try to extend the permutation on its own and then perform the containment check. See Also ======== Cycle References ========== .. [1] Skiena, S. 'Permutations.' 1.1 in Implementing Discrete Mathematics Combinatorics and Graph Theory with Mathematica. Reading, MA: Addison-Wesley, pp. 3-16, 1990. .. [2] Knuth, D. E. The Art of Computer Programming, Vol. 4: Combinatorial Algorithms, 1st ed. Reading, MA: Addison-Wesley, 2011. .. [3] Wendy Myrvold and Frank Ruskey. 2001. Ranking and unranking permutations in linear time. Inf. Process. Lett. 79, 6 (September 2001), 281-284. DOI=10.1016/S0020-0190(01)00141-7 .. [4] D. L. Kreher, D. R. Stinson 'Combinatorial Algorithms' CRC Press, 1999 .. [5] Graham, R. L.; Knuth, D. E.; and Patashnik, O. Concrete Mathematics: A Foundation for Computer Science, 2nd ed. Reading, MA: Addison-Wesley, 1994. .. [6] https://en.wikipedia.org/wiki/Permutation#Product_and_inverse .. [7] https://en.wikipedia.org/wiki/Lehmer_code """ is_Permutation = True _array_form = None _cyclic_form = None _cycle_structure = None _size = None _rank = None def __new__(cls, *args, size=None, **kwargs): """ Constructor for the Permutation object from a list or a list of lists in which all elements of the permutation may appear only once. Examples ======== >>> from sympy.combinatorics import Permutation >>> from sympy import init_printing >>> init_printing(perm_cyclic=False, pretty_print=False) Permutations entered in array-form are left unaltered: >>> Permutation([0, 2, 1]) Permutation([0, 2, 1]) Permutations entered in cyclic form are converted to array form; singletons need not be entered, but can be entered to indicate the largest element: >>> Permutation([[4, 5, 6], [0, 1]]) Permutation([1, 0, 2, 3, 5, 6, 4]) >>> Permutation([[4, 5, 6], [0, 1], [19]]) Permutation([1, 0, 2, 3, 5, 6, 4], size=20) All manipulation of permutations assumes that the smallest element is 0 (in keeping with 0-based indexing in Python) so if the 0 is missing when entering a permutation in array form, an error will be raised: >>> Permutation([2, 1]) Traceback (most recent call last): ... ValueError: Integers 0 through 2 must be present. If a permutation is entered in cyclic form, it can be entered without singletons and the ``size`` specified so those values can be filled in, otherwise the array form will only extend to the maximum value in the cycles: >>> Permutation([[1, 4], [3, 5, 2]], size=10) Permutation([0, 4, 3, 5, 1, 2], size=10) >>> _.array_form [0, 4, 3, 5, 1, 2, 6, 7, 8, 9] """ if size is not None: size = int(size) #a) () #b) (1) = identity #c) (1, 2) = cycle #d) ([1, 2, 3]) = array form #e) ([[1, 2]]) = cyclic form #f) (Cycle) = conversion to permutation #g) (Permutation) = adjust size or return copy ok = True if not args: # a return cls._af_new(list(range(size or 0))) elif len(args) > 1: # c return cls._af_new(Cycle(*args).list(size)) if len(args) == 1: a = args[0] if isinstance(a, cls): # g if size is None or size == a.size: return a return cls(a.array_form, size=size) if isinstance(a, Cycle): # f return cls._af_new(a.list(size)) if not is_sequence(a): # b if size is not None and a + 1 > size: raise ValueError('size is too small when max is %s' % a) return cls._af_new(list(range(a + 1))) if has_variety(is_sequence(ai) for ai in a): ok = False else: ok = False if not ok: raise ValueError("Permutation argument must be a list of ints, " "a list of lists, Permutation or Cycle.") # safe to assume args are valid; this also makes a copy # of the args args = list(args[0]) is_cycle = args and is_sequence(args[0]) if is_cycle: # e args = [[int(i) for i in c] for c in args] else: # d args = [int(i) for i in args] # if there are n elements present, 0, 1, ..., n-1 should be present # unless a cycle notation has been provided. A 0 will be added # for convenience in case one wants to enter permutations where # counting starts from 1. temp = flatten(args) if has_dups(temp) and not is_cycle: raise ValueError('there were repeated elements.') temp = set(temp) if not is_cycle: if temp != set(range(len(temp))): raise ValueError('Integers 0 through %s must be present.' % max(temp)) if size is not None and temp and max(temp) + 1 > size: raise ValueError('max element should not exceed %s' % (size - 1)) if is_cycle: # it's not necessarily canonical so we won't store # it -- use the array form instead c = Cycle() for ci in args: c = c(*ci) aform = c.list() else: aform = list(args) if size and size > len(aform): # don't allow for truncation of permutation which # might split a cycle and lead to an invalid aform # but do allow the permutation size to be increased aform.extend(list(range(len(aform), size))) return cls._af_new(aform) @classmethod def _af_new(cls, perm): """A method to produce a Permutation object from a list; the list is bound to the _array_form attribute, so it must not be modified; this method is meant for internal use only; the list ``a`` is supposed to be generated as a temporary value in a method, so p = Perm._af_new(a) is the only object to hold a reference to ``a``:: Examples ======== >>> from sympy.combinatorics.permutations import Perm >>> from sympy import init_printing >>> init_printing(perm_cyclic=False, pretty_print=False) >>> a = [2, 1, 3, 0] >>> p = Perm._af_new(a) >>> p Permutation([2, 1, 3, 0]) """ p = super().__new__(cls) p._array_form = perm p._size = len(perm) return p def _hashable_content(self): # the array_form (a list) is the Permutation arg, so we need to # return a tuple, instead return tuple(self.array_form) @property def array_form(self): """ Return a copy of the attribute _array_form Examples ======== >>> from sympy.combinatorics import Permutation >>> p = Permutation([[2, 0], [3, 1]]) >>> p.array_form [2, 3, 0, 1] >>> Permutation([[2, 0, 3, 1]]).array_form [3, 2, 0, 1] >>> Permutation([2, 0, 3, 1]).array_form [2, 0, 3, 1] >>> Permutation([[1, 2], [4, 5]]).array_form [0, 2, 1, 3, 5, 4] """ return self._array_form[:] def list(self, size=None): """Return the permutation as an explicit list, possibly trimming unmoved elements if size is less than the maximum element in the permutation; if this is desired, setting ``size=-1`` will guarantee such trimming. Examples ======== >>> from sympy.combinatorics import Permutation >>> p = Permutation(2, 3)(4, 5) >>> p.list() [0, 1, 3, 2, 5, 4] >>> p.list(10) [0, 1, 3, 2, 5, 4, 6, 7, 8, 9] Passing a length too small will trim trailing, unchanged elements in the permutation: >>> Permutation(2, 4)(1, 2, 4).list(-1) [0, 2, 1] >>> Permutation(3).list(-1) [] """ if not self and size is None: raise ValueError('must give size for empty Cycle') rv = self.array_form if size is not None: if size > self.size: rv.extend(list(range(self.size, size))) else: # find first value from rhs where rv[i] != i i = self.size - 1 while rv: if rv[-1] != i: break rv.pop() i -= 1 return rv @property def cyclic_form(self): """ This is used to convert to the cyclic notation from the canonical notation. Singletons are omitted. Examples ======== >>> from sympy.combinatorics import Permutation >>> p = Permutation([0, 3, 1, 2]) >>> p.cyclic_form [[1, 3, 2]] >>> Permutation([1, 0, 2, 4, 3, 5]).cyclic_form [[0, 1], [3, 4]] See Also ======== array_form, full_cyclic_form """ if self._cyclic_form is not None: return list(self._cyclic_form) array_form = self.array_form unchecked = [True] * len(array_form) cyclic_form = [] for i in range(len(array_form)): if unchecked[i]: cycle = [] cycle.append(i) unchecked[i] = False j = i while unchecked[array_form[j]]: j = array_form[j] cycle.append(j) unchecked[j] = False if len(cycle) > 1: cyclic_form.append(cycle) assert cycle == list(minlex(cycle)) cyclic_form.sort() self._cyclic_form = cyclic_form[:] return cyclic_form @property def full_cyclic_form(self): """Return permutation in cyclic form including singletons. Examples ======== >>> from sympy.combinatorics import Permutation >>> Permutation([0, 2, 1]).full_cyclic_form [[0], [1, 2]] """ need = set(range(self.size)) - set(flatten(self.cyclic_form)) rv = self.cyclic_form + [[i] for i in need] rv.sort() return rv @property def size(self): """ Returns the number of elements in the permutation. Examples ======== >>> from sympy.combinatorics import Permutation >>> Permutation([[3, 2], [0, 1]]).size 4 See Also ======== cardinality, length, order, rank """ return self._size def support(self): """Return the elements in permutation, P, for which P[i] != i. Examples ======== >>> from sympy.combinatorics import Permutation >>> p = Permutation([[3, 2], [0, 1], [4]]) >>> p.array_form [1, 0, 3, 2, 4] >>> p.support() [0, 1, 2, 3] """ a = self.array_form return [i for i, e in enumerate(a) if a[i] != i] def __add__(self, other): """Return permutation that is other higher in rank than self. The rank is the lexicographical rank, with the identity permutation having rank of 0. Examples ======== >>> from sympy.combinatorics import Permutation >>> I = Permutation([0, 1, 2, 3]) >>> a = Permutation([2, 1, 3, 0]) >>> I + a.rank() == a True See Also ======== __sub__, inversion_vector """ rank = (self.rank() + other) % self.cardinality rv = self.unrank_lex(self.size, rank) rv._rank = rank return rv def __sub__(self, other): """Return the permutation that is other lower in rank than self. See Also ======== __add__ """ return self.__add__(-other) @staticmethod def rmul(*args): """ Return product of Permutations [a, b, c, ...] as the Permutation whose ith value is a(b(c(i))). a, b, c, ... can be Permutation objects or tuples. Examples ======== >>> from sympy.combinatorics import Permutation >>> a, b = [1, 0, 2], [0, 2, 1] >>> a = Permutation(a); b = Permutation(b) >>> list(Permutation.rmul(a, b)) [1, 2, 0] >>> [a(b(i)) for i in range(3)] [1, 2, 0] This handles the operands in reverse order compared to the ``*`` operator: >>> a = Permutation(a); b = Permutation(b) >>> list(a*b) [2, 0, 1] >>> [b(a(i)) for i in range(3)] [2, 0, 1] Notes ===== All items in the sequence will be parsed by Permutation as necessary as long as the first item is a Permutation: >>> Permutation.rmul(a, [0, 2, 1]) == Permutation.rmul(a, b) True The reverse order of arguments will raise a TypeError. """ rv = args[0] for i in range(1, len(args)): rv = args[i]*rv return rv @classmethod def rmul_with_af(cls, *args): """ same as rmul, but the elements of args are Permutation objects which have _array_form """ a = [x._array_form for x in args] rv = cls._af_new(_af_rmuln(*a)) return rv def mul_inv(self, other): """ other*~self, self and other have _array_form """ a = _af_invert(self._array_form) b = other._array_form return self._af_new(_af_rmul(a, b)) def __rmul__(self, other): """This is needed to coerce other to Permutation in rmul.""" cls = type(self) return cls(other)*self def __mul__(self, other): """ Return the product a*b as a Permutation; the ith value is b(a(i)). Examples ======== >>> from sympy.combinatorics.permutations import _af_rmul, Permutation >>> a, b = [1, 0, 2], [0, 2, 1] >>> a = Permutation(a); b = Permutation(b) >>> list(a*b) [2, 0, 1] >>> [b(a(i)) for i in range(3)] [2, 0, 1] This handles operands in reverse order compared to _af_rmul and rmul: >>> al = list(a); bl = list(b) >>> _af_rmul(al, bl) [1, 2, 0] >>> [al[bl[i]] for i in range(3)] [1, 2, 0] It is acceptable for the arrays to have different lengths; the shorter one will be padded to match the longer one: >>> from sympy import init_printing >>> init_printing(perm_cyclic=False, pretty_print=False) >>> b*Permutation([1, 0]) Permutation([1, 2, 0]) >>> Permutation([1, 0])*b Permutation([2, 0, 1]) It is also acceptable to allow coercion to handle conversion of a single list to the left of a Permutation: >>> [0, 1]*a # no change: 2-element identity Permutation([1, 0, 2]) >>> [[0, 1]]*a # exchange first two elements Permutation([0, 1, 2]) You cannot use more than 1 cycle notation in a product of cycles since coercion can only handle one argument to the left. To handle multiple cycles it is convenient to use Cycle instead of Permutation: >>> [[1, 2]]*[[2, 3]]*Permutation([]) # doctest: +SKIP >>> from sympy.combinatorics.permutations import Cycle >>> Cycle(1, 2)(2, 3) (1 3 2) """ from sympy.combinatorics.perm_groups import PermutationGroup, Coset if isinstance(other, PermutationGroup): return Coset(self, other, dir='-') a = self.array_form # __rmul__ makes sure the other is a Permutation b = other.array_form if not b: perm = a else: b.extend(list(range(len(b), len(a)))) perm = [b[i] for i in a] + b[len(a):] return self._af_new(perm) def commutes_with(self, other): """ Checks if the elements are commuting. Examples ======== >>> from sympy.combinatorics import Permutation >>> a = Permutation([1, 4, 3, 0, 2, 5]) >>> b = Permutation([0, 1, 2, 3, 4, 5]) >>> a.commutes_with(b) True >>> b = Permutation([2, 3, 5, 4, 1, 0]) >>> a.commutes_with(b) False """ a = self.array_form b = other.array_form return _af_commutes_with(a, b) def __pow__(self, n): """ Routine for finding powers of a permutation. Examples ======== >>> from sympy.combinatorics import Permutation >>> from sympy import init_printing >>> init_printing(perm_cyclic=False, pretty_print=False) >>> p = Permutation([2, 0, 3, 1]) >>> p.order() 4 >>> p**4 Permutation([0, 1, 2, 3]) """ if isinstance(n, Permutation): raise NotImplementedError( 'p**p is not defined; do you mean p^p (conjugate)?') n = int(n) return self._af_new(_af_pow(self.array_form, n)) def __rxor__(self, i): """Return self(i) when ``i`` is an int. Examples ======== >>> from sympy.combinatorics import Permutation >>> p = Permutation(1, 2, 9) >>> 2^p == p(2) == 9 True """ if int(i) == i: return self(i) else: raise NotImplementedError( "i^p = p(i) when i is an integer, not %s." % i) def __xor__(self, h): """Return the conjugate permutation ``~h*self*h` `. Explanation =========== If ``a`` and ``b`` are conjugates, ``a = h*b*~h`` and ``b = ~h*a*h`` and both have the same cycle structure. Examples ======== >>> from sympy.combinatorics import Permutation >>> p = Permutation(1, 2, 9) >>> q = Permutation(6, 9, 8) >>> p*q != q*p True Calculate and check properties of the conjugate: >>> c = p^q >>> c == ~q*p*q and p == q*c*~q True The expression q^p^r is equivalent to q^(p*r): >>> r = Permutation(9)(4, 6, 8) >>> q^p^r == q^(p*r) True If the term to the left of the conjugate operator, i, is an integer then this is interpreted as selecting the ith element from the permutation to the right: >>> all(i^p == p(i) for i in range(p.size)) True Note that the * operator as higher precedence than the ^ operator: >>> q^r*p^r == q^(r*p)^r == Permutation(9)(1, 6, 4) True Notes ===== In Python the precedence rule is p^q^r = (p^q)^r which differs in general from p^(q^r) >>> q^p^r (9)(1 4 8) >>> q^(p^r) (9)(1 8 6) For a given r and p, both of the following are conjugates of p: ~r*p*r and r*p*~r. But these are not necessarily the same: >>> ~r*p*r == r*p*~r True >>> p = Permutation(1, 2, 9)(5, 6) >>> ~r*p*r == r*p*~r False The conjugate ~r*p*r was chosen so that ``p^q^r`` would be equivalent to ``p^(q*r)`` rather than ``p^(r*q)``. To obtain r*p*~r, pass ~r to this method: >>> p^~r == r*p*~r True """ if self.size != h.size: raise ValueError("The permutations must be of equal size.") a = [None]*self.size h = h._array_form p = self._array_form for i in range(self.size): a[h[i]] = h[p[i]] return self._af_new(a) def transpositions(self): """ Return the permutation decomposed into a list of transpositions. Explanation =========== It is always possible to express a permutation as the product of transpositions, see [1] Examples ======== >>> from sympy.combinatorics import Permutation >>> p = Permutation([[1, 2, 3], [0, 4, 5, 6, 7]]) >>> t = p.transpositions() >>> t [(0, 7), (0, 6), (0, 5), (0, 4), (1, 3), (1, 2)] >>> print(''.join(str(c) for c in t)) (0, 7)(0, 6)(0, 5)(0, 4)(1, 3)(1, 2) >>> Permutation.rmul(*[Permutation([ti], size=p.size) for ti in t]) == p True References ========== .. [1] https://en.wikipedia.org/wiki/Transposition_%28mathematics%29#Properties """ a = self.cyclic_form res = [] for x in a: nx = len(x) if nx == 2: res.append(tuple(x)) elif nx > 2: first = x[0] for y in x[nx - 1:0:-1]: res.append((first, y)) return res @classmethod def from_sequence(self, i, key=None): """Return the permutation needed to obtain ``i`` from the sorted elements of ``i``. If custom sorting is desired, a key can be given. Examples ======== >>> from sympy.combinatorics import Permutation >>> Permutation.from_sequence('SymPy') (4)(0 1 3) >>> _(sorted("SymPy")) ['S', 'y', 'm', 'P', 'y'] >>> Permutation.from_sequence('SymPy', key=lambda x: x.lower()) (4)(0 2)(1 3) """ ic = list(zip(i, list(range(len(i))))) if key: ic.sort(key=lambda x: key(x[0])) else: ic.sort() return ~Permutation([i[1] for i in ic]) def __invert__(self): """ Return the inverse of the permutation. A permutation multiplied by its inverse is the identity permutation. Examples ======== >>> from sympy.combinatorics import Permutation >>> from sympy import init_printing >>> init_printing(perm_cyclic=False, pretty_print=False) >>> p = Permutation([[2, 0], [3, 1]]) >>> ~p Permutation([2, 3, 0, 1]) >>> _ == p**-1 True >>> p*~p == ~p*p == Permutation([0, 1, 2, 3]) True """ return self._af_new(_af_invert(self._array_form)) def __iter__(self): """Yield elements from array form. Examples ======== >>> from sympy.combinatorics import Permutation >>> list(Permutation(range(3))) [0, 1, 2] """ yield from self.array_form def __repr__(self): from sympy.printing.repr import srepr return srepr(self) def __call__(self, *i): """ Allows applying a permutation instance as a bijective function. Examples ======== >>> from sympy.combinatorics import Permutation >>> p = Permutation([[2, 0], [3, 1]]) >>> p.array_form [2, 3, 0, 1] >>> [p(i) for i in range(4)] [2, 3, 0, 1] If an array is given then the permutation selects the items from the array (i.e. the permutation is applied to the array): >>> from sympy.abc import x >>> p([x, 1, 0, x**2]) [0, x**2, x, 1] """ # list indices can be Integer or int; leave this # as it is (don't test or convert it) because this # gets called a lot and should be fast if len(i) == 1: i = i[0] if not isinstance(i, Iterable): i = as_int(i) if i < 0 or i > self.size: raise TypeError( "{} should be an integer between 0 and {}" .format(i, self.size-1)) return self._array_form[i] # P([a, b, c]) if len(i) != self.size: raise TypeError( "{} should have the length {}.".format(i, self.size)) return [i[j] for j in self._array_form] # P(1, 2, 3) return self*Permutation(Cycle(*i), size=self.size) def atoms(self): """ Returns all the elements of a permutation Examples ======== >>> from sympy.combinatorics import Permutation >>> Permutation([0, 1, 2, 3, 4, 5]).atoms() {0, 1, 2, 3, 4, 5} >>> Permutation([[0, 1], [2, 3], [4, 5]]).atoms() {0, 1, 2, 3, 4, 5} """ return set(self.array_form) def apply(self, i): r"""Apply the permutation to an expression. Parameters ========== i : Expr It should be an integer between $0$ and $n-1$ where $n$ is the size of the permutation. If it is a symbol or a symbolic expression that can have integer values, an ``AppliedPermutation`` object will be returned which can represent an unevaluated function. Notes ===== Any permutation can be defined as a bijective function $\sigma : \{ 0, 1, \dots, n-1 \} \rightarrow \{ 0, 1, \dots, n-1 \}$ where $n$ denotes the size of the permutation. The definition may even be extended for any set with distinctive elements, such that the permutation can even be applied for real numbers or such, however, it is not implemented for now for computational reasons and the integrity with the group theory module. This function is similar to the ``__call__`` magic, however, ``__call__`` magic already has some other applications like permuting an array or attatching new cycles, which would not always be mathematically consistent. This also guarantees that the return type is a SymPy integer, which guarantees the safety to use assumptions. """ i = _sympify(i) if i.is_integer is False: raise NotImplementedError("{} should be an integer.".format(i)) n = self.size if (i < 0) == True or (i >= n) == True: raise NotImplementedError( "{} should be an integer between 0 and {}".format(i, n-1)) if i.is_Integer: return Integer(self._array_form[i]) return AppliedPermutation(self, i) def next_lex(self): """ Returns the next permutation in lexicographical order. If self is the last permutation in lexicographical order it returns None. See [4] section 2.4. Examples ======== >>> from sympy.combinatorics import Permutation >>> p = Permutation([2, 3, 1, 0]) >>> p = Permutation([2, 3, 1, 0]); p.rank() 17 >>> p = p.next_lex(); p.rank() 18 See Also ======== rank, unrank_lex """ perm = self.array_form[:] n = len(perm) i = n - 2 while perm[i + 1] < perm[i]: i -= 1 if i == -1: return None else: j = n - 1 while perm[j] < perm[i]: j -= 1 perm[j], perm[i] = perm[i], perm[j] i += 1 j = n - 1 while i < j: perm[j], perm[i] = perm[i], perm[j] i += 1 j -= 1 return self._af_new(perm) @classmethod def unrank_nonlex(self, n, r): """ This is a linear time unranking algorithm that does not respect lexicographic order [3]. Examples ======== >>> from sympy.combinatorics import Permutation >>> from sympy import init_printing >>> init_printing(perm_cyclic=False, pretty_print=False) >>> Permutation.unrank_nonlex(4, 5) Permutation([2, 0, 3, 1]) >>> Permutation.unrank_nonlex(4, -1) Permutation([0, 1, 2, 3]) See Also ======== next_nonlex, rank_nonlex """ def _unrank1(n, r, a): if n > 0: a[n - 1], a[r % n] = a[r % n], a[n - 1] _unrank1(n - 1, r//n, a) id_perm = list(range(n)) n = int(n) r = r % ifac(n) _unrank1(n, r, id_perm) return self._af_new(id_perm) def rank_nonlex(self, inv_perm=None): """ This is a linear time ranking algorithm that does not enforce lexicographic order [3]. Examples ======== >>> from sympy.combinatorics import Permutation >>> p = Permutation([0, 1, 2, 3]) >>> p.rank_nonlex() 23 See Also ======== next_nonlex, unrank_nonlex """ def _rank1(n, perm, inv_perm): if n == 1: return 0 s = perm[n - 1] t = inv_perm[n - 1] perm[n - 1], perm[t] = perm[t], s inv_perm[n - 1], inv_perm[s] = inv_perm[s], t return s + n*_rank1(n - 1, perm, inv_perm) if inv_perm is None: inv_perm = (~self).array_form if not inv_perm: return 0 perm = self.array_form[:] r = _rank1(len(perm), perm, inv_perm) return r def next_nonlex(self): """ Returns the next permutation in nonlex order [3]. If self is the last permutation in this order it returns None. Examples ======== >>> from sympy.combinatorics import Permutation >>> from sympy import init_printing >>> init_printing(perm_cyclic=False, pretty_print=False) >>> p = Permutation([2, 0, 3, 1]); p.rank_nonlex() 5 >>> p = p.next_nonlex(); p Permutation([3, 0, 1, 2]) >>> p.rank_nonlex() 6 See Also ======== rank_nonlex, unrank_nonlex """ r = self.rank_nonlex() if r == ifac(self.size) - 1: return None return self.unrank_nonlex(self.size, r + 1) def rank(self): """ Returns the lexicographic rank of the permutation. Examples ======== >>> from sympy.combinatorics import Permutation >>> p = Permutation([0, 1, 2, 3]) >>> p.rank() 0 >>> p = Permutation([3, 2, 1, 0]) >>> p.rank() 23 See Also ======== next_lex, unrank_lex, cardinality, length, order, size """ if self._rank is not None: return self._rank rank = 0 rho = self.array_form[:] n = self.size - 1 size = n + 1 psize = int(ifac(n)) for j in range(size - 1): rank += rho[j]*psize for i in range(j + 1, size): if rho[i] > rho[j]: rho[i] -= 1 psize //= n n -= 1 self._rank = rank return rank @property def cardinality(self): """ Returns the number of all possible permutations. Examples ======== >>> from sympy.combinatorics import Permutation >>> p = Permutation([0, 1, 2, 3]) >>> p.cardinality 24 See Also ======== length, order, rank, size """ return int(ifac(self.size)) def parity(self): """ Computes the parity of a permutation. Explanation =========== The parity of a permutation reflects the parity of the number of inversions in the permutation, i.e., the number of pairs of x and y such that ``x > y`` but ``p[x] < p[y]``. Examples ======== >>> from sympy.combinatorics import Permutation >>> p = Permutation([0, 1, 2, 3]) >>> p.parity() 0 >>> p = Permutation([3, 2, 0, 1]) >>> p.parity() 1 See Also ======== _af_parity """ if self._cyclic_form is not None: return (self.size - self.cycles) % 2 return _af_parity(self.array_form) @property def is_even(self): """ Checks if a permutation is even. Examples ======== >>> from sympy.combinatorics import Permutation >>> p = Permutation([0, 1, 2, 3]) >>> p.is_even True >>> p = Permutation([3, 2, 1, 0]) >>> p.is_even True See Also ======== is_odd """ return not self.is_odd @property def is_odd(self): """ Checks if a permutation is odd. Examples ======== >>> from sympy.combinatorics import Permutation >>> p = Permutation([0, 1, 2, 3]) >>> p.is_odd False >>> p = Permutation([3, 2, 0, 1]) >>> p.is_odd True See Also ======== is_even """ return bool(self.parity() % 2) @property def is_Singleton(self): """ Checks to see if the permutation contains only one number and is thus the only possible permutation of this set of numbers Examples ======== >>> from sympy.combinatorics import Permutation >>> Permutation([0]).is_Singleton True >>> Permutation([0, 1]).is_Singleton False See Also ======== is_Empty """ return self.size == 1 @property def is_Empty(self): """ Checks to see if the permutation is a set with zero elements Examples ======== >>> from sympy.combinatorics import Permutation >>> Permutation([]).is_Empty True >>> Permutation([0]).is_Empty False See Also ======== is_Singleton """ return self.size == 0 @property def is_identity(self): return self.is_Identity @property def is_Identity(self): """ Returns True if the Permutation is an identity permutation. Examples ======== >>> from sympy.combinatorics import Permutation >>> p = Permutation([]) >>> p.is_Identity True >>> p = Permutation([[0], [1], [2]]) >>> p.is_Identity True >>> p = Permutation([0, 1, 2]) >>> p.is_Identity True >>> p = Permutation([0, 2, 1]) >>> p.is_Identity False See Also ======== order """ af = self.array_form return not af or all(i == af[i] for i in range(self.size)) def ascents(self): """ Returns the positions of ascents in a permutation, ie, the location where p[i] < p[i+1] Examples ======== >>> from sympy.combinatorics import Permutation >>> p = Permutation([4, 0, 1, 3, 2]) >>> p.ascents() [1, 2] See Also ======== descents, inversions, min, max """ a = self.array_form pos = [i for i in range(len(a) - 1) if a[i] < a[i + 1]] return pos def descents(self): """ Returns the positions of descents in a permutation, ie, the location where p[i] > p[i+1] Examples ======== >>> from sympy.combinatorics import Permutation >>> p = Permutation([4, 0, 1, 3, 2]) >>> p.descents() [0, 3] See Also ======== ascents, inversions, min, max """ a = self.array_form pos = [i for i in range(len(a) - 1) if a[i] > a[i + 1]] return pos def max(self): """ The maximum element moved by the permutation. Examples ======== >>> from sympy.combinatorics import Permutation >>> p = Permutation([1, 0, 2, 3, 4]) >>> p.max() 1 See Also ======== min, descents, ascents, inversions """ max = 0 a = self.array_form for i in range(len(a)): if a[i] != i and a[i] > max: max = a[i] return max def min(self): """ The minimum element moved by the permutation. Examples ======== >>> from sympy.combinatorics import Permutation >>> p = Permutation([0, 1, 4, 3, 2]) >>> p.min() 2 See Also ======== max, descents, ascents, inversions """ a = self.array_form min = len(a) for i in range(len(a)): if a[i] != i and a[i] < min: min = a[i] return min def inversions(self): """ Computes the number of inversions of a permutation. Explanation =========== An inversion is where i > j but p[i] < p[j]. For small length of p, it iterates over all i and j values and calculates the number of inversions. For large length of p, it uses a variation of merge sort to calculate the number of inversions. Examples ======== >>> from sympy.combinatorics import Permutation >>> p = Permutation([0, 1, 2, 3, 4, 5]) >>> p.inversions() 0 >>> Permutation([3, 2, 1, 0]).inversions() 6 See Also ======== descents, ascents, min, max References ========== .. [1] http://www.cp.eng.chula.ac.th/~piak/teaching/algo/algo2008/count-inv.htm """ inversions = 0 a = self.array_form n = len(a) if n < 130: for i in range(n - 1): b = a[i] for c in a[i + 1:]: if b > c: inversions += 1 else: k = 1 right = 0 arr = a[:] temp = a[:] while k < n: i = 0 while i + k < n: right = i + k * 2 - 1 if right >= n: right = n - 1 inversions += _merge(arr, temp, i, i + k, right) i = i + k * 2 k = k * 2 return inversions def commutator(self, x): """Return the commutator of ``self`` and ``x``: ``~x*~self*x*self`` If f and g are part of a group, G, then the commutator of f and g is the group identity iff f and g commute, i.e. fg == gf. Examples ======== >>> from sympy.combinatorics import Permutation >>> from sympy import init_printing >>> init_printing(perm_cyclic=False, pretty_print=False) >>> p = Permutation([0, 2, 3, 1]) >>> x = Permutation([2, 0, 3, 1]) >>> c = p.commutator(x); c Permutation([2, 1, 3, 0]) >>> c == ~x*~p*x*p True >>> I = Permutation(3) >>> p = [I + i for i in range(6)] >>> for i in range(len(p)): ... for j in range(len(p)): ... c = p[i].commutator(p[j]) ... if p[i]*p[j] == p[j]*p[i]: ... assert c == I ... else: ... assert c != I ... References ========== .. [1] https://en.wikipedia.org/wiki/Commutator """ a = self.array_form b = x.array_form n = len(a) if len(b) != n: raise ValueError("The permutations must be of equal size.") inva = [None]*n for i in range(n): inva[a[i]] = i invb = [None]*n for i in range(n): invb[b[i]] = i return self._af_new([a[b[inva[i]]] for i in invb]) def signature(self): """ Gives the signature of the permutation needed to place the elements of the permutation in canonical order. The signature is calculated as (-1)^ Examples ======== >>> from sympy.combinatorics import Permutation >>> p = Permutation([0, 1, 2]) >>> p.inversions() 0 >>> p.signature() 1 >>> q = Permutation([0,2,1]) >>> q.inversions() 1 >>> q.signature() -1 See Also ======== inversions """ if self.is_even: return 1 return -1 def order(self): """ Computes the order of a permutation. When the permutation is raised to the power of its order it equals the identity permutation. Examples ======== >>> from sympy.combinatorics import Permutation >>> from sympy import init_printing >>> init_printing(perm_cyclic=False, pretty_print=False) >>> p = Permutation([3, 1, 5, 2, 4, 0]) >>> p.order() 4 >>> (p**(p.order())) Permutation([], size=6) See Also ======== identity, cardinality, length, rank, size """ return reduce(lcm, [len(cycle) for cycle in self.cyclic_form], 1) def length(self): """ Returns the number of integers moved by a permutation. Examples ======== >>> from sympy.combinatorics import Permutation >>> Permutation([0, 3, 2, 1]).length() 2 >>> Permutation([[0, 1], [2, 3]]).length() 4 See Also ======== min, max, support, cardinality, order, rank, size """ return len(self.support()) @property def cycle_structure(self): """Return the cycle structure of the permutation as a dictionary indicating the multiplicity of each cycle length. Examples ======== >>> from sympy.combinatorics import Permutation >>> Permutation(3).cycle_structure {1: 4} >>> Permutation(0, 4, 3)(1, 2)(5, 6).cycle_structure {2: 2, 3: 1} """ if self._cycle_structure: rv = self._cycle_structure else: rv = defaultdict(int) singletons = self.size for c in self.cyclic_form: rv[len(c)] += 1 singletons -= len(c) if singletons: rv[1] = singletons self._cycle_structure = rv return dict(rv) # make a copy @property def cycles(self): """ Returns the number of cycles contained in the permutation (including singletons). Examples ======== >>> from sympy.combinatorics import Permutation >>> Permutation([0, 1, 2]).cycles 3 >>> Permutation([0, 1, 2]).full_cyclic_form [[0], [1], [2]] >>> Permutation(0, 1)(2, 3).cycles 2 See Also ======== sympy.functions.combinatorial.numbers.stirling """ return len(self.full_cyclic_form) def index(self): """ Returns the index of a permutation. The index of a permutation is the sum of all subscripts j such that p[j] is greater than p[j+1]. Examples ======== >>> from sympy.combinatorics import Permutation >>> p = Permutation([3, 0, 2, 1, 4]) >>> p.index() 2 """ a = self.array_form return sum([j for j in range(len(a) - 1) if a[j] > a[j + 1]]) def runs(self): """ Returns the runs of a permutation. An ascending sequence in a permutation is called a run [5]. Examples ======== >>> from sympy.combinatorics import Permutation >>> p = Permutation([2, 5, 7, 3, 6, 0, 1, 4, 8]) >>> p.runs() [[2, 5, 7], [3, 6], [0, 1, 4, 8]] >>> q = Permutation([1,3,2,0]) >>> q.runs() [[1, 3], [2], [0]] """ return runs(self.array_form) def inversion_vector(self): """Return the inversion vector of the permutation. The inversion vector consists of elements whose value indicates the number of elements in the permutation that are lesser than it and lie on its right hand side. The inversion vector is the same as the Lehmer encoding of a permutation. Examples ======== >>> from sympy.combinatorics import Permutation >>> p = Permutation([4, 8, 0, 7, 1, 5, 3, 6, 2]) >>> p.inversion_vector() [4, 7, 0, 5, 0, 2, 1, 1] >>> p = Permutation([3, 2, 1, 0]) >>> p.inversion_vector() [3, 2, 1] The inversion vector increases lexicographically with the rank of the permutation, the -ith element cycling through 0..i. >>> p = Permutation(2) >>> while p: ... print('%s %s %s' % (p, p.inversion_vector(), p.rank())) ... p = p.next_lex() (2) [0, 0] 0 (1 2) [0, 1] 1 (2)(0 1) [1, 0] 2 (0 1 2) [1, 1] 3 (0 2 1) [2, 0] 4 (0 2) [2, 1] 5 See Also ======== from_inversion_vector """ self_array_form = self.array_form n = len(self_array_form) inversion_vector = [0] * (n - 1) for i in range(n - 1): val = 0 for j in range(i + 1, n): if self_array_form[j] < self_array_form[i]: val += 1 inversion_vector[i] = val return inversion_vector def rank_trotterjohnson(self): """ Returns the Trotter Johnson rank, which we get from the minimal change algorithm. See [4] section 2.4. Examples ======== >>> from sympy.combinatorics import Permutation >>> p = Permutation([0, 1, 2, 3]) >>> p.rank_trotterjohnson() 0 >>> p = Permutation([0, 2, 1, 3]) >>> p.rank_trotterjohnson() 7 See Also ======== unrank_trotterjohnson, next_trotterjohnson """ if self.array_form == [] or self.is_Identity: return 0 if self.array_form == [1, 0]: return 1 perm = self.array_form n = self.size rank = 0 for j in range(1, n): k = 1 i = 0 while perm[i] != j: if perm[i] < j: k += 1 i += 1 j1 = j + 1 if rank % 2 == 0: rank = j1*rank + j1 - k else: rank = j1*rank + k - 1 return rank @classmethod def unrank_trotterjohnson(cls, size, rank): """ Trotter Johnson permutation unranking. See [4] section 2.4. Examples ======== >>> from sympy.combinatorics import Permutation >>> from sympy import init_printing >>> init_printing(perm_cyclic=False, pretty_print=False) >>> Permutation.unrank_trotterjohnson(5, 10) Permutation([0, 3, 1, 2, 4]) See Also ======== rank_trotterjohnson, next_trotterjohnson """ perm = [0]*size r2 = 0 n = ifac(size) pj = 1 for j in range(2, size + 1): pj *= j r1 = (rank * pj) // n k = r1 - j*r2 if r2 % 2 == 0: for i in range(j - 1, j - k - 1, -1): perm[i] = perm[i - 1] perm[j - k - 1] = j - 1 else: for i in range(j - 1, k, -1): perm[i] = perm[i - 1] perm[k] = j - 1 r2 = r1 return cls._af_new(perm) def next_trotterjohnson(self): """ Returns the next permutation in Trotter-Johnson order. If self is the last permutation it returns None. See [4] section 2.4. If it is desired to generate all such permutations, they can be generated in order more quickly with the ``generate_bell`` function. Examples ======== >>> from sympy.combinatorics import Permutation >>> from sympy import init_printing >>> init_printing(perm_cyclic=False, pretty_print=False) >>> p = Permutation([3, 0, 2, 1]) >>> p.rank_trotterjohnson() 4 >>> p = p.next_trotterjohnson(); p Permutation([0, 3, 2, 1]) >>> p.rank_trotterjohnson() 5 See Also ======== rank_trotterjohnson, unrank_trotterjohnson, sympy.utilities.iterables.generate_bell """ pi = self.array_form[:] n = len(pi) st = 0 rho = pi[:] done = False m = n-1 while m > 0 and not done: d = rho.index(m) for i in range(d, m): rho[i] = rho[i + 1] par = _af_parity(rho[:m]) if par == 1: if d == m: m -= 1 else: pi[st + d], pi[st + d + 1] = pi[st + d + 1], pi[st + d] done = True else: if d == 0: m -= 1 st += 1 else: pi[st + d], pi[st + d - 1] = pi[st + d - 1], pi[st + d] done = True if m == 0: return None return self._af_new(pi) def get_precedence_matrix(self): """ Gets the precedence matrix. This is used for computing the distance between two permutations. Examples ======== >>> from sympy.combinatorics import Permutation >>> from sympy import init_printing >>> init_printing(perm_cyclic=False, pretty_print=False) >>> p = Permutation.josephus(3, 6, 1) >>> p Permutation([2, 5, 3, 1, 4, 0]) >>> p.get_precedence_matrix() Matrix([ [0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 1, 0], [1, 1, 0, 1, 1, 1], [1, 1, 0, 0, 1, 0], [1, 0, 0, 0, 0, 0], [1, 1, 0, 1, 1, 0]]) See Also ======== get_precedence_distance, get_adjacency_matrix, get_adjacency_distance """ m = zeros(self.size) perm = self.array_form for i in range(m.rows): for j in range(i + 1, m.cols): m[perm[i], perm[j]] = 1 return m def get_precedence_distance(self, other): """ Computes the precedence distance between two permutations. Explanation =========== Suppose p and p' represent n jobs. The precedence metric counts the number of times a job j is preceded by job i in both p and p'. This metric is commutative. Examples ======== >>> from sympy.combinatorics import Permutation >>> p = Permutation([2, 0, 4, 3, 1]) >>> q = Permutation([3, 1, 2, 4, 0]) >>> p.get_precedence_distance(q) 7 >>> q.get_precedence_distance(p) 7 See Also ======== get_precedence_matrix, get_adjacency_matrix, get_adjacency_distance """ if self.size != other.size: raise ValueError("The permutations must be of equal size.") self_prec_mat = self.get_precedence_matrix() other_prec_mat = other.get_precedence_matrix() n_prec = 0 for i in range(self.size): for j in range(self.size): if i == j: continue if self_prec_mat[i, j] * other_prec_mat[i, j] == 1: n_prec += 1 d = self.size * (self.size - 1)//2 - n_prec return d def get_adjacency_matrix(self): """ Computes the adjacency matrix of a permutation. Explanation =========== If job i is adjacent to job j in a permutation p then we set m[i, j] = 1 where m is the adjacency matrix of p. Examples ======== >>> from sympy.combinatorics import Permutation >>> p = Permutation.josephus(3, 6, 1) >>> p.get_adjacency_matrix() Matrix([ [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0], [0, 0, 0, 0, 0, 1], [0, 1, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0]]) >>> q = Permutation([0, 1, 2, 3]) >>> q.get_adjacency_matrix() Matrix([ [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1], [0, 0, 0, 0]]) See Also ======== get_precedence_matrix, get_precedence_distance, get_adjacency_distance """ m = zeros(self.size) perm = self.array_form for i in range(self.size - 1): m[perm[i], perm[i + 1]] = 1 return m def get_adjacency_distance(self, other): """ Computes the adjacency distance between two permutations. Explanation =========== This metric counts the number of times a pair i,j of jobs is adjacent in both p and p'. If n_adj is this quantity then the adjacency distance is n - n_adj - 1 [1] [1] Reeves, Colin R. Landscapes, Operators and Heuristic search, Annals of Operational Research, 86, pp 473-490. (1999) Examples ======== >>> from sympy.combinatorics import Permutation >>> p = Permutation([0, 3, 1, 2, 4]) >>> q = Permutation.josephus(4, 5, 2) >>> p.get_adjacency_distance(q) 3 >>> r = Permutation([0, 2, 1, 4, 3]) >>> p.get_adjacency_distance(r) 4 See Also ======== get_precedence_matrix, get_precedence_distance, get_adjacency_matrix """ if self.size != other.size: raise ValueError("The permutations must be of the same size.") self_adj_mat = self.get_adjacency_matrix() other_adj_mat = other.get_adjacency_matrix() n_adj = 0 for i in range(self.size): for j in range(self.size): if i == j: continue if self_adj_mat[i, j] * other_adj_mat[i, j] == 1: n_adj += 1 d = self.size - n_adj - 1 return d def get_positional_distance(self, other): """ Computes the positional distance between two permutations. Examples ======== >>> from sympy.combinatorics import Permutation >>> p = Permutation([0, 3, 1, 2, 4]) >>> q = Permutation.josephus(4, 5, 2) >>> r = Permutation([3, 1, 4, 0, 2]) >>> p.get_positional_distance(q) 12 >>> p.get_positional_distance(r) 12 See Also ======== get_precedence_distance, get_adjacency_distance """ a = self.array_form b = other.array_form if len(a) != len(b): raise ValueError("The permutations must be of the same size.") return sum([abs(a[i] - b[i]) for i in range(len(a))]) @classmethod def josephus(cls, m, n, s=1): """Return as a permutation the shuffling of range(n) using the Josephus scheme in which every m-th item is selected until all have been chosen. The returned permutation has elements listed by the order in which they were selected. The parameter ``s`` stops the selection process when there are ``s`` items remaining and these are selected by continuing the selection, counting by 1 rather than by ``m``. Consider selecting every 3rd item from 6 until only 2 remain:: choices chosen ======== ====== 012345 01 345 2 01 34 25 01 4 253 0 4 2531 0 25314 253140 Examples ======== >>> from sympy.combinatorics import Permutation >>> Permutation.josephus(3, 6, 2).array_form [2, 5, 3, 1, 4, 0] References ========== .. [1] https://en.wikipedia.org/wiki/Flavius_Josephus .. [2] https://en.wikipedia.org/wiki/Josephus_problem .. [3] http://www.wou.edu/~burtonl/josephus.html """ from collections import deque m -= 1 Q = deque(list(range(n))) perm = [] while len(Q) > max(s, 1): for dp in range(m): Q.append(Q.popleft()) perm.append(Q.popleft()) perm.extend(list(Q)) return cls(perm) @classmethod def from_inversion_vector(cls, inversion): """ Calculates the permutation from the inversion vector. Examples ======== >>> from sympy.combinatorics import Permutation >>> from sympy import init_printing >>> init_printing(perm_cyclic=False, pretty_print=False) >>> Permutation.from_inversion_vector([3, 2, 1, 0, 0]) Permutation([3, 2, 1, 0, 4, 5]) """ size = len(inversion) N = list(range(size + 1)) perm = [] try: for k in range(size): val = N[inversion[k]] perm.append(val) N.remove(val) except IndexError: raise ValueError("The inversion vector is not valid.") perm.extend(N) return cls._af_new(perm) @classmethod def random(cls, n): """ Generates a random permutation of length ``n``. Uses the underlying Python pseudo-random number generator. Examples ======== >>> from sympy.combinatorics import Permutation >>> Permutation.random(2) in (Permutation([1, 0]), Permutation([0, 1])) True """ perm_array = list(range(n)) random.shuffle(perm_array) return cls._af_new(perm_array) @classmethod def unrank_lex(cls, size, rank): """ Lexicographic permutation unranking. Examples ======== >>> from sympy.combinatorics import Permutation >>> from sympy import init_printing >>> init_printing(perm_cyclic=False, pretty_print=False) >>> a = Permutation.unrank_lex(5, 10) >>> a.rank() 10 >>> a Permutation([0, 2, 4, 1, 3]) See Also ======== rank, next_lex """ perm_array = [0] * size psize = 1 for i in range(size): new_psize = psize*(i + 1) d = (rank % new_psize) // psize rank -= d*psize perm_array[size - i - 1] = d for j in range(size - i, size): if perm_array[j] > d - 1: perm_array[j] += 1 psize = new_psize return cls._af_new(perm_array) def resize(self, n): """Resize the permutation to the new size ``n``. Parameters ========== n : int The new size of the permutation. Raises ====== ValueError If the permutation cannot be resized to the given size. This may only happen when resized to a smaller size than the original. Examples ======== >>> from sympy.combinatorics import Permutation Increasing the size of a permutation: >>> p = Permutation(0, 1, 2) >>> p = p.resize(5) >>> p (4)(0 1 2) Decreasing the size of the permutation: >>> p = p.resize(4) >>> p (3)(0 1 2) If resizing to the specific size breaks the cycles: >>> p.resize(2) Traceback (most recent call last): ... ValueError: The permutation cannot be resized to 2 because the cycle (0, 1, 2) may break. """ aform = self.array_form l = len(aform) if n > l: aform += list(range(l, n)) return Permutation._af_new(aform) elif n < l: cyclic_form = self.full_cyclic_form new_cyclic_form = [] for cycle in cyclic_form: cycle_min = min(cycle) cycle_max = max(cycle) if cycle_min <= n-1: if cycle_max > n-1: raise ValueError( "The permutation cannot be resized to {} " "because the cycle {} may break." .format(n, tuple(cycle))) new_cyclic_form.append(cycle) return Permutation(new_cyclic_form) return self # XXX Deprecated flag print_cyclic = None def _merge(arr, temp, left, mid, right): """ Merges two sorted arrays and calculates the inversion count. Helper function for calculating inversions. This method is for internal use only. """ i = k = left j = mid inv_count = 0 while i < mid and j <= right: if arr[i] < arr[j]: temp[k] = arr[i] k += 1 i += 1 else: temp[k] = arr[j] k += 1 j += 1 inv_count += (mid -i) while i < mid: temp[k] = arr[i] k += 1 i += 1 if j <= right: k += right - j + 1 j += right - j + 1 arr[left:k + 1] = temp[left:k + 1] else: arr[left:right + 1] = temp[left:right + 1] return inv_count Perm = Permutation _af_new = Perm._af_new class AppliedPermutation(Expr): """A permutation applied to a symbolic variable. Parameters ========== perm : Permutation x : Expr Examples ======== >>> from sympy import Symbol >>> from sympy.combinatorics import Permutation Creating a symbolic permutation function application: >>> x = Symbol('x') >>> p = Permutation(0, 1, 2) >>> p.apply(x) AppliedPermutation((0 1 2), x) >>> _.subs(x, 1) 2 """ def __new__(cls, perm, x, evaluate=None): if evaluate is None: evaluate = global_parameters.evaluate perm = _sympify(perm) x = _sympify(x) if not isinstance(perm, Permutation): raise ValueError("{} must be a Permutation instance." .format(perm)) if evaluate: if x.is_Integer: return perm.apply(x) obj = super().__new__(cls, perm, x) return obj @dispatch(Permutation, Permutation) def _eval_is_eq(lhs, rhs): if lhs._size != rhs._size: return None return lhs._array_form == rhs._array_form