|
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 int_valued |
|
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.printing.repr import srepr |
|
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: |
|
|
|
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 does not 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/w/index.php?oldid=499948155#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) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ok = True |
|
if not args: |
|
return cls._af_new(list(range(size or 0))) |
|
elif len(args) > 1: |
|
return cls._af_new(Cycle(*args).list(size)) |
|
if len(args) == 1: |
|
a = args[0] |
|
if isinstance(a, cls): |
|
if size is None or size == a.size: |
|
return a |
|
return cls(a.array_form, size=size) |
|
if isinstance(a, Cycle): |
|
return cls._af_new(a.list(size)) |
|
if not is_sequence(a): |
|
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.") |
|
|
|
|
|
|
|
args = list(args[0]) |
|
|
|
is_cycle = args and is_sequence(args[0]) |
|
if is_cycle: |
|
args = [[int(i) for i in c] for c in args] |
|
else: |
|
args = [int(i) for i in args] |
|
|
|
|
|
|
|
|
|
|
|
|
|
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: |
|
|
|
|
|
c = Cycle() |
|
for ci in args: |
|
c = c(*ci) |
|
aform = c.list() |
|
else: |
|
aform = list(args) |
|
if size and size > len(aform): |
|
|
|
|
|
|
|
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 copy(self): |
|
return self.__class__(self.array_form) |
|
|
|
def __getnewargs__(self): |
|
return (self.array_form,) |
|
|
|
def _hashable_content(self): |
|
|
|
|
|
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: |
|
|
|
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 |
|
|
|
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_valued(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): |
|
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] |
|
""" |
|
|
|
|
|
|
|
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] |
|
|
|
if len(i) != self.size: |
|
raise TypeError( |
|
"{} should have the length {}.".format(i, self.size)) |
|
return [i[j] for j in self._array_form] |
|
|
|
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 attaching 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) -> int: |
|
""" |
|
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 |
|
""" |
|
a = self.array_form |
|
if not a: |
|
return 0 |
|
return max(_a for i, _a in enumerate(a) if _a != i) |
|
|
|
def min(self) -> int: |
|
""" |
|
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 |
|
if not a: |
|
return 0 |
|
return min(_a for i, _a in enumerate(a) if _a != i) |
|
|
|
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] https://www.cp.eng.chula.ac.th/~prabhas//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)^<number of inversions> |
|
|
|
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) |
|
|
|
@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] https://web.archive.org/web/20171008094331/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 |
|
|
|
|
|
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 |
|
|