sympy - Serbipunk/notes GitHub Wiki

permutation

    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](/Serbipunk/notes/wiki/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](/Serbipunk/notes/wiki/1,-2))*Permutation([1, 3](/Serbipunk/notes/wiki/1,-3))*Permutation([2, 3](/Serbipunk/notes/wiki/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](/Serbipunk/notes/wiki/1,-2],-[9))
    Permutation([0, 2, 1], size=10)
    >>> Permutation([1, 2](/Serbipunk/notes/wiki/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](/Serbipunk/notes/wiki/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](/Serbipunk/notes/wiki/0,-1],-[2],-[3)
    Any permutation can be factored into transpositions of pairs of elements:
    >>> Permutation([1, 2], [3, 4, 5](/Serbipunk/notes/wiki/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](/Serbipunk/notes/wiki/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