def verify_representation(self):
        r"""
        Verify the representation: tests that the images of the simple
        transpositions are involutions and tests that the braid relations
        hold.

        EXAMPLES::

            sage: spc = SymmetricGroupRepresentation([1,1,1])
            sage: spc.verify_representation()
            True
            sage: spc = SymmetricGroupRepresentation([4,2,1])
            sage: spc.verify_representation()
            True
        """
        n = self._partition.size()
        transpositions = []
        for i in range(1,n):
            si = Permutation(range(1,i) + [i+1,i] + range(i+2,n+1))
            transpositions.append(si)
        repn_matrices = map(self.representation_matrix, transpositions)
        for (i,si) in enumerate(repn_matrices):
            for (j,sj) in enumerate(repn_matrices):
                if i == j:
                    if si*sj != si.parent().identity_matrix():
                        return False, "si si != 1 for i = %s" % (i,)
                elif abs(i-j) > 1:
                    if si*sj != sj*si:
                        return False, "si sj != sj si for (i,j) =(%s,%s)" % (i,j)
                else:
                    if si*sj*si != sj*si*sj:
                        return False, "si sj si != sj si sj for (i,j) = (%s,%s)" % (i,j)
        return True
Example #2
0
    def to_non_crossing_set_partition(self):
        r"""
        Returns the noncrossing set partition (on half as many elements) 
        corresponding to the perfect matching if the perfect matching is 
        noncrossing, and otherwise gives an error.

        OUTPUT:

            The realization of ``self`` as a noncrossing set partition.

        EXAMPLES::

            sage: PerfectMatching([[1,3], [4,2]]).to_non_crossing_set_partition()
            Traceback (most recent call last):
            ...
            ValueError: matching must be non-crossing
            sage: PerfectMatching([[1,4], [3,2]]).to_non_crossing_set_partition()
            {{1, 2}}
            sage: PerfectMatching([]).to_non_crossing_set_partition()
            {}
        """
        from sage.combinat.set_partition import SetPartition        
        if not self.is_non_crossing():
            raise ValueError("matching must be non-crossing")
        else:
            perm = self.to_permutation()
            perm2 = Permutation([(perm[2*i])/2 for i in range(len(perm)/2)])
        return SetPartition(perm2.cycle_tuples())
Example #3
0
        def _element_constructor_(self, x):
            r"""
            Convert ``x`` into ``self``.

            EXAMPLES::

                sage: R = algebras.FQSym(QQ).G()
                sage: x, y, z = R([1]), R([2,1]), R([3,2,1])
                sage: R(x)
                G[1]
                sage: R(x+4*y)
                G[1] + 4*G[2, 1]
                sage: R(1)
                G[]

                sage: D = algebras.FQSym(ZZ).G()
                sage: X, Y, Z = D([1]), D([2,1]), D([3,2,1])
                sage: R(X-Y).parent()
                Free Quasi-symmetric functions over Rational Field in the G basis

                sage: R([1, 3, 2])
                G[1, 3, 2]
                sage: R(Permutation([1, 3, 2]))
                G[1, 3, 2]
                sage: R(SymmetricGroup(4)(Permutation([1,3,4,2])))
                G[1, 3, 4, 2]

                sage: RF = algebras.FQSym(QQ).F()
                sage: R(RF([2, 3, 4, 1]))
                G[4, 1, 2, 3]
                sage: R(RF([3, 2, 4, 1]))
                G[4, 2, 1, 3]
                sage: DF = algebras.FQSym(ZZ).F()
                sage: D(DF([2, 3, 4, 1]))
                G[4, 1, 2, 3]
                sage: R(DF([2, 3, 4, 1]))
                G[4, 1, 2, 3]
                sage: RF(R[2, 3, 4, 1])
                F[4, 1, 2, 3]
            """
            if isinstance(x, (list, tuple, PermutationGroupElement)):
                x = Permutation(x)
            try:
                P = x.parent()
                if isinstance(P, FreeQuasisymmetricFunctions.G):
                    if P is self:
                        return x
                    return self.element_class(self, x.monomial_coefficients())
            except AttributeError:
                pass
            return CombinatorialFreeModule._element_constructor_(self, x)
Example #4
0
    def SymmetricGroupBruhatIntervalPoset(start, end):
        """
        The poset of permutations with respect to Bruhat order.

        INPUT:

        - ``start`` - list permutation

        - ``end`` - list permutation (same n, of course)

        .. note::

           Must have ``start`` <= ``end``.

        EXAMPLES:

        Any interval is rank symmetric if and only if it avoids these
        permutations::

            sage: P1 = Posets.SymmetricGroupBruhatIntervalPoset([1,2,3,4], [3,4,1,2])
            sage: P2 = Posets.SymmetricGroupBruhatIntervalPoset([1,2,3,4], [4,2,3,1])
            sage: ranks1 = [P1.rank(v) for v in P1]
            sage: ranks2 = [P2.rank(v) for v in P2]
            sage: [ranks1.count(i) for i in uniq(ranks1)]
            [1, 3, 5, 4, 1]
            sage: [ranks2.count(i) for i in uniq(ranks2)]
            [1, 3, 5, 6, 4, 1]

        """
        start = Permutation(start)
        end = Permutation(end)
        if len(start) != len(end):
            raise TypeError("Start (%s) and end (%s) must have same length."%(start, end))
        if not start.bruhat_lequal(end):
            raise TypeError("Must have start (%s) <= end (%s) in Bruhat order."%(start, end))
        unseen = [start]
        nodes = {}
        while len(unseen) > 0:
            perm = unseen.pop(0)
            nodes[perm] = [succ_perm for succ_perm in perm.bruhat_succ()
                                if succ_perm.bruhat_lequal(end)]
            for succ_perm in nodes[perm]:
                if succ_perm not in nodes:
                    unseen.append(succ_perm)
        return Poset(nodes)
    def scalar_product_matrix(self, permutation=None):
        r"""
        Return the scalar product matrix corresponding to ``permutation``.

        The entries are given by the scalar products of ``u`` and
        ``permutation.action(v)``, where ``u`` is a vertex in the underlying
        Yang-Baxter graph and ``v`` is a vertex in the dual graph.

        EXAMPLES::

            sage: spc = SymmetricGroupRepresentation([3,1], 'specht')
            sage: spc.scalar_product_matrix()
            [ 1  0  0]
            [ 0 -1  0]
            [ 0  0  1]
        """
        if permutation is None:
            permutation = Permutation(range(1,1+self._partition.size()))
        Q = matrix(QQ, len(self._yang_baxter_graph))
        for (i,v) in enumerate(self._dual_vertices):
            for (j,u) in enumerate(self._yang_baxter_graph):
                Q[i,j] = self.scalar_product(tuple(permutation.action(v)), u)
        return Q
    def __call__(self, x, orientation=False):
        """
        Input is a simplex of the domain. Output is the image simplex.

        If the optional argument ``orientation`` is ``True``, then this
        returns a pair ``(image simplex, oriented)`` where ``oriented``
        is 1 or `-1` depending on whether the map preserves or reverses
        the orientation of the image simplex.

        EXAMPLES::

            sage: S = simplicial_complexes.Sphere(2)
            sage: T = simplicial_complexes.Sphere(3)
            sage: S
            Minimal triangulation of the 2-sphere
            sage: T
            Minimal triangulation of the 3-sphere
            sage: f = {0:0,1:1,2:2,3:3}
            sage: H = Hom(S,T)
            sage: x = H(f)
            sage: from sage.topology.simplicial_complex import Simplex
            sage: x(Simplex([0,2,3]))
            (0, 2, 3)

        An orientation-reversing example::

            sage: X = SimplicialComplex([[0,1]], is_mutable=False)
            sage: g = Hom(X,X)({0:1, 1:0})
            sage: g(Simplex([0,1]))
            (0, 1)
            sage: g(Simplex([0,1]), orientation=True)
            ((0, 1), -1)
        """
        dim = self.domain().dimension()
        if not isinstance(x, Simplex) or x.dimension(
        ) > dim or x not in self.domain().faces()[x.dimension()]:
            raise ValueError("x must be a simplex of the source of f")
        tup = x.tuple()
        fx = []
        for j in tup:
            fx.append(self._vertex_dictionary[j])
        if orientation:
            if len(set(fx)) == len(tup):
                oriented = Permutation(convert_perm(fx)).signature()
            else:
                oriented = 1
            return (Simplex(set(fx)), oriented)
        else:
            return Simplex(set(fx))
Example #7
0
def sort_subscript(subscript):
    """
    A subscript is a range of integers.  This function sorts a subscript
    in the sense of arranging it in ascending order.  The return values
    are the sign of the subscript and the sorted subscript, where the
    sign is defined as follows:

    #. sign == 0 if two or more entries in the subscript were equal.
    #. sign == +1, -1 if a positive (resp. negative) permutation was used to sort the subscript.

    INPUT:

    - ``subscript`` -- a subscript, i.e. a range of not necessarily
        distinct integers

    OUTPUT:

    - Sign of the permutation used to arrange the subscript, where 0
        means that the original subscript had two or more entries that
        were the same

    - Sorted subscript.

    EXAMPLES::

        sage: from sage.tensor.differential_form_element import sort_subscript
        sage: sort_subscript((1, 3, 2))
        (-1, (1, 2, 3))
        sage: sort_subscript((1, 3))
        (1, (1, 3))
        sage: sort_subscript((4, 2, 7, 9, 8))
        (1, (2, 4, 7, 8, 9))

    """
    if len(subscript) == 0:
        return 1, ()

    sub_list = list(subscript)
    sub_list.sort()
    offsets = [subscript.index(x) + 1 for x in sub_list]

    # Check that offsets is a true permutation of 1..n
    n = len(offsets)
    if sum(offsets) != n * (n + 1) / 2:
        sign = 0
    else:
        sign = Permutation(offsets).signature()

    return sign, tuple(sub_list)
Example #8
0
    def __init__(self, l, pi=None):
        """
        EXAMPLES::

            sage: a = AugmentedLatticeDiagramFilling([[1,6],[2],[3,4,2],[],[],[5,5]])
            sage: a == loads(dumps(a))
            True
            sage: pi = Permutation([2,3,1]).to_permutation_group_element()
            sage: a = AugmentedLatticeDiagramFilling([[1,6],[2],[3,4,2],[],[],[5,5]],pi)
            sage: a == loads(dumps(a))
            True
        """
        if pi is None:
            pi = Permutation([1]).to_permutation_group_element()
        self._list = [[pi(i + 1)] + li for i, li in enumerate(l)]
Example #9
0
    def permutation_group_element(self):
        """
        Returns self as a permutation group element.

        EXAMPLES::

            sage: p = PermutationGroupElement((2,3,4))
            sage: P = species.PermutationSpecies()
            sage: a = P.structures(["a", "b", "c", "d"]).random_element(); a
            ['a', 'c', 'b', 'd']
            sage: a.permutation_group_element()
            (2,3)
        """
        from sage.groups.all import PermutationGroupElement
        return Permutation(self._list).to_permutation_group_element()
    def verify_representation(self):
        r"""
        Verify the representation: tests that the images of the simple
        transpositions are involutions and tests that the braid relations
        hold.

        EXAMPLES::

            sage: spc = SymmetricGroupRepresentation([1,1,1])
            sage: spc.verify_representation()
            True
            sage: spc = SymmetricGroupRepresentation([4,2,1])
            sage: spc.verify_representation()
            True
        """
        n = self._partition.size()
        transpositions = []
        for i in range(1, n):
            si = Permutation(
                list(range(1, i)) + [i + 1, i] + list(range(i + 2, n + 1)))
            transpositions.append(si)
        repn_matrices = [self.representation_matrix(_) for _ in transpositions]
        for (i, si) in enumerate(repn_matrices):
            for (j, sj) in enumerate(repn_matrices):
                if i == j:
                    if si * sj != si.parent().identity_matrix():
                        return False, "si si != 1 for i = %s" % (i, )
                elif abs(i - j) > 1:
                    if si * sj != sj * si:
                        return False, "si sj != sj si for (i,j) =(%s,%s)" % (i,
                                                                             j)
                else:
                    if si * sj * si != sj * si * sj:
                        return False, "si sj si != sj si sj for (i,j) = (%s,%s)" % (
                            i, j)
        return True
Example #11
0
        def from_symmetric_group_algebra(self, x):
            """
            Return the element of `FQSym` corresponding to the element
            `x` of a symmetric group algebra.

            EXAMPLES::

                sage: A = algebras.FQSym(QQ).F()
                sage: SGA4 = SymmetricGroupAlgebra(QQ, 4)
                sage: x = SGA4([1,3,2,4]) + 5/2 * SGA4([1,2,4,3])
                sage: A.from_symmetric_group_algebra(x)
                5/2*F[1, 2, 4, 3] + F[1, 3, 2, 4]
                sage: A.from_symmetric_group_algebra(SGA4.zero())
                0
            """
            return self._from_dict({Permutation(key): c for (key, c) in x})
Example #12
0
    def canonical_label(self):
        """
        EXAMPLES::

            sage: P = species.PermutationSpecies()
            sage: S = P.structures(["a", "b", "c"])
            sage: [s.canonical_label() for s in S]
            [['a', 'b', 'c'],
             ['b', 'a', 'c'],
             ['b', 'a', 'c'],
             ['b', 'c', 'a'],
             ['b', 'c', 'a'],
             ['b', 'a', 'c']]
        """
        P = self.parent()
        return P._canonical_rep_from_partition(self.__class__, self._labels, Permutation(self._list).cycle_type())
Example #13
0
    def to_permutation(self):
        """
        Return a permutation with the entries of ``self`` obtained by reading
        ``self`` row by row, from the bottommost to the topmost row, with
        each row being read from left to right, in English convention.
        See :meth:`to_word_by_row()`.

        EXAMPLES::

            sage: SkewTableau([[None,2],[3,4],[None],[1]]).to_permutation()
            [1, 3, 4, 2]
            sage: SkewTableau([[None]]).to_permutation()
            []
        """
        from sage.combinat.permutation import Permutation
        return Permutation(self.to_word())
Example #14
0
    def diagonal_reading_word(self):
        r"""
        Return a diagonal word of the labelled Dyck path corresponding to parking
        function (see [Hag08]_ p. 75).

        INPUT:

        - ``self`` -- parking function word

        OUTPUT:

        - returns a word, read diagonally from NE to SW of the pretty print of the
          labelled Dyck path that corresponds to ``self`` and the same size as ``self``

        EXAMPLES::

            sage: PF = ParkingFunction([6, 1, 5, 2, 2, 1, 5])
            sage: PF.diagonal_reading_word()
            [5, 1, 7, 4, 6, 3, 2]

        ::

            sage: ParkingFunction([1, 1, 1]).diagonal_reading_word()
            [3, 2, 1]
            sage: ParkingFunction([1, 2, 3]).diagonal_reading_word()
            [3, 2, 1]
            sage: ParkingFunction([1, 1, 3, 4]).diagonal_reading_word()
            [2, 4, 3, 1]

        ::

            sage: ParkingFunction([1, 1, 1]).diagonal_word()
            [3, 2, 1]
            sage: ParkingFunction([1, 2, 3]).diagonal_word()
            [3, 2, 1]
            sage: ParkingFunction([1, 4, 3, 1]).diagonal_word()
            [4, 2, 3, 1]
        """
        L = self.to_labelling_permutation()
        D = self.to_area_sequence()
        m = max(D)
        return Permutation([
            L[-j - 1] for i in range(m + 1) for j in range(len(L))
            if D[-j - 1] == m - i
        ])
Example #15
0
    def __call__(self, u):
        r"""
        Return the object obtained from swapping the items in positions
        ``i`` and ``i+1`` of ``u``.

        EXAMPLES::

            sage: from sage.combinat.yang_baxter_graph import SwapOperator
            sage: s3 = SwapOperator(3)
            sage: s3((1,2,3,4,5))
            (1, 2, 3, 5, 4)
            sage: s3([1,2,3,4,5])
            [1, 2, 3, 5, 4]
        """
        i = self._position
        if isinstance(u, Permutation):
            return Permutation(u[:i] + u[i:i+2][::-1] + u[i+2:])
        return type(u)(u[:i] + u[i:i+2][::-1] + u[i+2:])
Example #16
0
 def sign(self):
     r"""
     EXAMPLES::
     
         sage: from EkEkstar import kFace
         sage: kFace((0,0,0,0,0),(1,2,3,4,5)).sign()
         1
         sage: kFace((0,0,0,0,0),(1,2,3,4,4)).sign()
         0
         sage: kFace((0,0,0,0,0),(1,2,3,5,4)).sign()
         -1
     """
     sorted_type = self.sorted_type()
     if all(sorted_type[i] < sorted_type[i + 1]
            for i in range(len(sorted_type) - 1)):
         p = argsort(self._type) + 1
         return Permutation(p).sign()
     else:
         return 0
Example #17
0
    def _element_constructor_(self, x):
        """
        Coerce x into self.

        EXAMPLES::

            sage: X = SchubertPolynomialRing(QQ)
            sage: X._element_constructor_([2,1,3])
            X[2, 1]
            sage: X._element_constructor_(Permutation([2,1,3]))
            X[2, 1]

            sage: R.<x1, x2, x3> = QQ[]
            sage: X(x1^2*x2)
            X[3, 2, 1]

        TESTS:

        We check that :trac:`12924` is fixed::

            sage: X = SchubertPolynomialRing(QQ)
            sage: X._element_constructor_([1,2,1])
            Traceback (most recent call last):
            ...
            ValueError: The input [1, 2, 1] is not a valid permutation
        """
        if isinstance(x, list):
            #checking the input to avoid symmetrica crashing Sage, see trac 12924
            if not x in Permutations():
                raise ValueError("The input %s is not a valid permutation" %
                                 (x))
            perm = Permutation(x).remove_extra_fixed_points()
            return self._from_dict({perm: self.base_ring().one()})
        elif isinstance(x, Permutation):
            if not list(x) in Permutations():
                raise ValueError("The input %s is not a valid permutation" %
                                 (x))
            perm = x.remove_extra_fixed_points()
            return self._from_dict({perm: self.base_ring().one()})
        elif is_MPolynomial(x):
            return symmetrica.t_POLYNOM_SCHUBERT(x)
        else:
            raise TypeError
Example #18
0
        def __getitem__(self, p):
            """
            Return the basis element indexed by ``p``.

            INPUT:

            - ``p`` -- a permutation

            EXAMPLES::

                sage: R = algebras.FQSym(QQ).F()
                sage: R[[1, 3, 2]]
                F[1, 3, 2]
                sage: R[Permutation([1, 3, 2])]
                F[1, 3, 2]
                sage: R[SymmetricGroup(4)(Permutation([1,3,4,2]))]
                F[1, 3, 4, 2]
            """
            return self.monomial(Permutation(p))
Example #19
0
    def to_permutation(self):
        r"""
        Returns the permutation corresponding to the perfect matching.

        OUTPUT:

            The realization of ``self`` as a permutation.

        EXAMPLES::

            sage: PerfectMatching([[1,3], [4,2]]).to_permutation()
            [3, 4, 1, 2]
            sage: PerfectMatching([[1,4], [3,2]]).to_permutation()
            [4, 3, 2, 1]
            sage: PerfectMatching([]).to_permutation()
            []
        """
        from sage.combinat.permutation import Permutation
        return Permutation(self.__dict__['value'])
Example #20
0
    def _an_element_(self):
        r"""
        Returns an element of ``self``.

        EXAMPLES::

            sage: F.<a> = GF(4)
            sage: SemimonomialTransformationGroup(F, 3).an_element() # indirect doctest
            ((a, 1, 1); (1,3,2), Ring endomorphism of Finite Field in a of size 2^2 Defn: a |--> a + 1)
        """
        R = self.base_ring()
        v = [R.primitive_element()] + [R.one()] * (self.degree() - 1)
        p = Permutation([self.degree()] + [i for i in range(1, self.degree())])

        if not R.is_prime_field():
            f = R.hom([R.gen()**R.characteristic()])
        else:
            f = R.Hom(R).identity()
        return self(0, v, p, f)
Example #21
0
    def to_permutation(self):
        """
        Return the permutation corresponding to ``self``.

        EXAMPLES::

            sage: D = Derangements(4)
            sage: p = D([4,3,2,1]).to_permutation(); p
            [4, 3, 2, 1]
            sage: type(p)
            <class 'sage.combinat.permutation.StandardPermutations_all_with_category.element_class'>
            sage: D = Derangements([1, 3, 3, 4])
            sage: D[0].to_permutation()
            Traceback (most recent call last):
            ...
            ValueError: Can only convert to a permutation for derangements of [1, 2, ..., n]
        """
        if self.parent()._set != tuple(range(1, len(self)+1)):
            raise ValueError("Can only convert to a permutation for derangements of [1, 2, ..., n]")
        return Permutation(list(self))
Example #22
0
    def cars_permutation(
            self):  # indices are parking spaces, entries are car labels
        r"""
        Return the sequence of cars that take parking spots 1 through `n`
        and corresponding to the parking function.

        For example, ``cars_permutation(PF) = [2, 4, 5, 6, 3, 1, 7]``
        means that car 2 takes spots 1, car 4 takes spot 2, ..., car 1 takes spot 6 and
        car 7 takes spot 7.

        INPUT:

        - ``self`` -- parking function word

        OUTPUT:

        - the permutation of cars corresponding to the parking function
          and which is the same size as parking function

        EXAMPLES::

            sage: PF = ParkingFunction([6, 1, 5, 2, 2, 1, 5])
            sage: PF.cars_permutation()
            [2, 4, 5, 6, 3, 1, 7]

        ::

            sage: ParkingFunction([3,1,1,4]).cars_permutation()
            [2, 3, 1, 4]
            sage: ParkingFunction([4,1,1,1]).cars_permutation()
            [2, 3, 4, 1]
            sage: ParkingFunction([2,1,4,1]).cars_permutation()
            [2, 1, 4, 3]
        """
        out = {}
        for i in range(len(self)):
            j = 0
            while self[i] + j in out:
                j += 1
            out[self[i] + j] = i
        return Permutation([out[i + 1] + 1 for i in range(len(self))])
Example #23
0
    def scalar_product(self, u, v):
        r"""
        Return ``0`` if ``u+v`` is not a permutation, and the signature of the
        permutation otherwise.

        This is the scalar product of a vertex ``u`` of the underlying
        Yang-Baxter graph with the vertex ``v`` in the 'dual' Yang-Baxter
        graph.

        EXAMPLES::

            sage: spc = SymmetricGroupRepresentation([3,2], 'specht')
            sage: spc.scalar_product((1,0,2,1,0),(0,3,0,3,0))
            -1
            sage: spc.scalar_product((1,0,2,1,0),(3,0,0,3,0))
            0
        """
        uv = [a + v[i] + 1 for (i, a) in enumerate(u)]
        if uv not in Permutations():
            return 0
        else:
            return Permutation(uv).signature()
Example #24
0
    def gens(self):
        r"""
        Return a tuple of generators of ``self``.

        EXAMPLES::

            sage: F.<a> = GF(4)
            sage: SemimonomialTransformationGroup(F, 3).gens()
            [((a, 1, 1); (), Ring endomorphism of Finite Field in a of size 2^2
              Defn: a |--> a), ((1, 1, 1); (1,2,3), Ring endomorphism of Finite Field in a of size 2^2
              Defn: a |--> a), ((1, 1, 1); (1,2), Ring endomorphism of Finite Field in a of size 2^2
              Defn: a |--> a), ((1, 1, 1); (), Ring endomorphism of Finite Field in a of size 2^2
              Defn: a |--> a + 1)]
        """
        from sage.groups.perm_gps.permgroup_named import SymmetricGroup
        R = self.base_ring()
        l = [self(v=([R.primitive_element()] + [R.one()]*(self.degree() - 1)))]
        for g in SymmetricGroup(self.degree()).gens():
            l.append(self(perm=Permutation(g)))
        if R.is_field() and not R.is_prime_field():
            l.append(self(autom=R.hom([R.primitive_element()**R.characteristic()])))
        return l
Example #25
0
    def __mul__(self, other):
        """
        Product of two symmetric ideals.

        Since the generators of a symmetric ideal are subject to a
        permutation action, they in fact stand for a set of
        polynomials. Hence, when multiplying two symmetric ideals, it
        does not suffice to simply multiply the respective generators.

        EXAMPLES::

            sage: X.<x> = InfinitePolynomialRing(QQ)
            sage: I=X*(x[1])
            sage: I*I         # indirect doctest
            Symmetric Ideal (x_1^2, x_2*x_1) of Infinite polynomial ring in x over Rational Field

        """
        # determine maximal generator index
        PARENT = self.ring()
        if (not isinstance(other,
                           self.__class__)) or self.ring() != other.ring():
            if hasattr(other, 'gens'):
                other = SymmetricIdeal(PARENT, other.gens(), coerce=True)
        other = other.symmetrisation()
        sN = max([X.max_index() for X in self.gens()] + [1])
        oN = max([X.max_index() for X in other.gens()] + [1])

        from sage.combinat.permutation import Permutation
        P = Permutation(list(range(2, sN + oN + 1)) + [1])
        oGen = list(other.gens())
        SymL = oGen
        for i in range(sN):
            oGen = [X**P for X in oGen]
            SymL = SymL + oGen
        # Now, SymL contains all necessary permutations of the second factor
        OUT = []
        for X in self.gens():
            OUT.extend([X * Y for Y in SymL])
        return SymmetricIdeal(PARENT, OUT, coerce=False).interreduction()
    def to_permutation(self):
        """
        Return the corresponding permutation if ``self`` is a permutation
        matrix.

        EXAMPLES::

            sage: A = AlternatingSignMatrices(3)
            sage: asm = A([[0,1,0],[1,0,0],[0,0,1]])
            sage: p = asm.to_permutation(); p
            [2, 1, 3]
            sage: parent(p)
            Standard permutations
            sage: asm = A([[0,1,0],[1,-1,1],[0,1,0]])
            sage: asm.to_permutation()
            Traceback (most recent call last):
            ...
            ValueError: Not a permutation matrix
        """
        if not self.is_permutation():
            raise ValueError('Not a permutation matrix')
        asm_matrix = self.to_matrix()
        return Permutation([ j+1 for (i,j) in asm_matrix.nonzero_positions() ])
def galois_action_on_embeddings(G_K):
    K = G_K.number_field()
    Kgal = G_K.splitting_field()
    embeddings = K.embeddings(Kgal)
    # first a shortcut in the case where G_K is normal in S_d since then it doesn't
    # matter for our application since we only care about the image
    # of the galois group in S_d
    d = K.absolute_degree()
    G_K_roots = TransitiveGroup(d, G_K.transitive_number())
    if G_K_roots.is_normal(SymmetricGroup(d)):
        id_G_K = G_K.Hom(G_K).identity()
        return G_K, id_G_K, id_G_K, Kgal, embeddings

    # now for the actual computation
    permutations = []
    for g in G_K.gens():
        phi = g.as_hom()
        g_perm = Permutation(
            [embeddings.index(phi * emb) + 1 for emb in embeddings]).inverse()
        permutations.append(g_perm)
    G_K_emb = PermutationGroup(permutations, canonicalize=False)
    to_emb = G_K.hom(G_K_emb.gens())
    from_emb = G_K_emb.hom(G_K.gens())
    return G_K_emb, to_emb, from_emb, Kgal, embeddings
Example #28
0
def braid_from_piecewise(strands):
    r"""
    Compute the braid corresponding to the piecewise linear curves strands.

    INPUT:

    - ``strands`` -- a list of lists of tuples ``(t, c)``, where ``t``
      is a number bewteen 0 and 1, and ``c`` is a complex number

    OUTPUT:

    The braid formed by the piecewise linear strands.

    EXAMPLES::

        sage: from sage.schemes.curves.zariski_vankampen import braid_from_piecewise # optional - sirocco
        sage: paths = [[(0, I), (0.2, -1 - 0.5*I), (0.8, -1), (1, -I)],
        ....:          [(0, -1), (0.5, -I), (1, 1)],
        ....:          [(0, 1), (0.5, 1 + I), (1, I)]]
        sage: braid_from_piecewise(paths) # optional - sirocco
        s0*s1
    """
    L = strands
    i = min(val[1][0] for val in L)
    totalpoints = [[[a[0][1].real(), a[0][1].imag()]] for a in L]
    indices = [1 for a in range(len(L))]
    while i < 1:
        for j, val in enumerate(L):
            if val[indices[j]][0] > i:
                xaux = val[indices[j] - 1][1]
                yaux = val[indices[j]][1]
                aaux = val[indices[j] - 1][0]
                baux = val[indices[j]][0]
                interpola = xaux + (yaux - xaux)*(i - aaux)/(baux - aaux)
                totalpoints[j].append([interpola.real(), interpola.imag()])
            else:
                totalpoints[j].append([val[indices[j]][1].real(), val[indices[j]][1].imag()])
                indices[j] = indices[j] + 1
        i = min(val[indices[k]][0] for k,val in enumerate(L))

    for j, val in enumerate(L):
         totalpoints[j].append([val[-1][1].real(), val[-1][1].imag()])

    braid = []
    G = SymmetricGroup(len(totalpoints))
    def sgn(x, y): # Opposite sign of cmp
        if x < y:
            return 1
        if x > y:
            return -1
        return 0
    for i in range(len(totalpoints[0]) - 1):
        l1 = [totalpoints[j][i] for j in range(len(L))]
        l2 = [totalpoints[j][i+1] for j in range(len(L))]
        M = [[l1[s], l2[s]] for s in range(len(l1))]
        M.sort()
        l1 = [a[0] for a in M]
        l2 = [a[1] for a in M]
        cruces = []
        for j in range(len(l2)):
            for k in range(j):
                if l2[j] < l2[k]:
                    t = (l1[j][0] - l1[k][0])/(l2[k][0] - l1[k][0] + l1[j][0] - l2[j][0])
                    s = sgn(l1[k][1]*(1 - t) + t*l2[k][1], l1[j][1]*(1 - t) + t*l2[j][1])
                    cruces.append([t, k, j, s])
        if cruces:
            cruces.sort()
            P = G(Permutation([]))
            while cruces:
                # we select the crosses in the same t
                crucesl = [c for c in cruces if c[0]==cruces[0][0]]
                crossesl = [(P(c[2]+1) - P(c[1]+1),c[1],c[2],c[3]) for c in crucesl]
                cruces = cruces[len(crucesl):]
                while crossesl:
                    crossesl.sort()
                    c = crossesl.pop(0)
                    braid.append(c[3]*min(map(P, [c[1] + 1, c[2] + 1])))
                    P = G(Permutation([(c[1] + 1, c[2] + 1)]))*P
                    crossesl = [(P(c[2]+1) - P(c[1]+1),c[1],c[2],c[3]) for c in crossesl]

    B = BraidGroup(len(L))
    return B(braid)
Example #29
0
    def __classcall_private__(cls, parts):
        """
        Create a perfect matching from ``parts`` with the appropriate parent.

        This function tries to recognize the input (it can be either a list or
        a tuple of pairs, or a fix-point free involution given as a list or as
        a permutation), constructs the parent (enumerated set of
        PerfectMatchings of the ground set) and calls the __init__ function to
        construct our object.

        EXAMPLES::

            sage: m = PerfectMatching([('a','e'),('b','c'),('d','f')]);m
            [('a', 'e'), ('b', 'c'), ('d', 'f')]
            sage: isinstance(m, PerfectMatching)
            True
            sage: n = PerfectMatching([3, 8, 1, 7, 6, 5, 4, 2]);n
            [(1, 3), (2, 8), (4, 7), (5, 6)]
            sage: n.parent()
            Perfect matchings of {1, 2, 3, 4, 5, 6, 7, 8}
            sage: PerfectMatching([(1, 4), (2, 3), (5, 6)]).is_noncrossing()
            True

        The function checks that the given list or permutation is
        a valid perfect matching (i.e. a list of pairs with pairwise
        disjoint elements or a fix point free involution) and raises
        a ``ValueError`` otherwise::

            sage: PerfectMatching([(1, 2, 3), (4, 5)])
            Traceback (most recent call last):
            ...
            ValueError: [(1, 2, 3), (4, 5)] is not an element of
             Perfect matchings of {1, 2, 3, 4, 5}

        TESTS::

            sage: m = PerfectMatching([('a','e'),('b','c'),('d','f')])
            sage: TestSuite(m).run()
            sage: m = PerfectMatching([])
            sage: TestSuite(m).run()
            sage: PerfectMatching(6)
            Traceback (most recent call last):
            ...
            TypeError: 'sage.rings.integer.Integer' object is not iterable
            sage: PerfectMatching([(1,2,3)])
            Traceback (most recent call last):
            ...
            ValueError: [(1, 2, 3)] is not an element of
             Perfect matchings of {1, 2, 3}

            sage: PerfectMatching([(1,1)])
            Traceback (most recent call last):
            ...
            ValueError: [(1)] is not an element of Perfect matchings of {1}

            sage: PerfectMatching(Permutation([4,2,1,3]))
            Traceback (most recent call last):
            ...
            ValueError: permutation p (= [4, 2, 1, 3]) is not a
             fixed point free involution
        """
        if ((isinstance(parts, list) and
             all((isinstance(x, (int, Integer)) for x in parts)))
            or isinstance(parts, Permutation)):
            s = Permutation(parts)
            if not all(e == 2 for e in s.cycle_type()):
                raise ValueError("permutation p (= {}) is not a "
                                 "fixed point free involution".format(s))
            parts = s.to_cycles()

        base_set = frozenset(e for p in parts for e in p)
        P = PerfectMatchings(base_set)
        return P(parts)
Example #30
0
        def verschiebung(self, n):
            r"""
            Return the image of the symmetric function ``self`` under the
            `n`-th Verschiebung operator.

            The `n`-th Verschiebung operator `\mathbf{V}_n` is defined to be
            the unique algebra endomorphism `V` of the ring of symmetric
            functions that satisfies `V(h_r) = h_{r/n}` for every positive
            integer `r` divisible by `n`, and satisfies `V(h_r) = 0` for
            every positive integer `r` not divisible by `n`. This operator
            `\mathbf{V}_n` is a Hopf algebra endomorphism. For every
            nonnegative integer `r` with `n \mid r`, it satisfies

            .. MATH::

                \mathbf{V}_n(h_r) = h_{r/n},
                \quad \mathbf{V}_n(p_r) = n p_{r/n},
                \quad \mathbf{V}_n(e_r) = (-1)^{r - r/n} e_{r/n}

            (where `h` is the complete homogeneous basis, `p` is the
            powersum basis, and `e` is the elementary basis). For every
            nonnegative integer `r` with `n \nmid r`, it satisfes

            .. MATH::

                \mathbf{V}_n(h_r) = \mathbf{V}_n(p_r) = \mathbf{V}_n(e_r) = 0.

            The `n`-th Verschiebung operator is also called the `n`-th
            Verschiebung endomorphism. Its name derives from the Verschiebung
            (German for "shift") endomorphism of the Witt vectors.

            The `n`-th Verschiebung operator is adjoint to the `n`-th
            Frobenius operator (see :meth:`frobenius` for its definition)
            with respect to the Hall scalar product (:meth:`scalar`).

            The action of the `n`-th Verschiebung operator on the Schur basis
            can also be computed explicitly. The following (probably clumsier
            than necessary) description can be obtained by solving exercise
            7.61 in Stanley's [STA]_.

            Let `\lambda` be a partition. Let `n` be a positive integer. If
            the `n`-core of `\lambda` is nonempty, then
            `\mathbf{V}_n(s_\lambda) = 0`. Otherwise, the following method
            computes `\mathbf{V}_n(s_\lambda)`: Write the partition `\lambda`
            in the form `(\lambda_1, \lambda_2, \ldots, \lambda_{ns})` for some
            nonnegative integer `s`. (If `n` does not divide the length of
            `\lambda`, then this is achieved by adding trailing zeroes to
            `\lambda`.) Set `\beta_i = \lambda_i + ns - i` for every
            `s \in \{ 1, 2, \ldots, ns \}`. Then,
            `(\beta_1, \beta_2, \ldots, \beta_{ns})` is a strictly decreasing
            sequence of nonnegative integers. Stably sort the list
            `(1, 2, \ldots, ns)` in order of (weakly) increasing remainder of
            `-1 - \beta_i` modulo `n`. Let `\xi` be the sign of the
            permutation that is used for this sorting. Let `\psi` be the sign
            of the permutation that is used to stably sort the list
            `(1, 2, \ldots, ns)` in order of (weakly) increasing remainder of
            `i - 1` modulo `n`. (Notice that `\psi = (-1)^{n(n-1)s(s-1)/4}`.)
            Then, `\mathbf{V}_n(s_\lambda) = \xi \psi \prod_{i = 0}^{n - 1}
            s_{\lambda^{(i)}}`, where
            `(\lambda^{(0)}, \lambda^{(1)}, \ldots, \lambda^{(n - 1)})`
            is the `n`-quotient of `\lambda`.

            INPUT:

            - ``n`` -- a positive integer

            OUTPUT:

            The result of applying the `n`-th Verschiebung operator (on the ring of
            symmetric functions) to ``self``.

            EXAMPLES::

                sage: Sym = SymmetricFunctions(ZZ)
                sage: s = Sym.s()
                sage: s[5].verschiebung(2)
                0
                sage: s[6].verschiebung(6)
                s[1]
                sage: s[6,3].verschiebung(3)
                s[2, 1] + s[3]
                sage: s[6,3,1].verschiebung(2)
                -s[3, 2]
                sage: s[3,2,1].verschiebung(1)
                s[3, 2, 1]
                sage: s([]).verschiebung(1)
                s[]
                sage: s([]).verschiebung(4)
                s[]

            TESTS:

            Let us check that this method on the powersum basis gives the
            same result as the implementation in sfa.py on the monomial
            basis::

                sage: Sym = SymmetricFunctions(QQ)
                sage: s = Sym.s(); h = Sym.h()
                sage: all( h(s(lam)).verschiebung(3) == h(s(lam).verschiebung(3))
                ....:      for lam in Partitions(6) )
                True
                sage: all( s(h(lam)).verschiebung(2) == s(h(lam).verschiebung(2))
                ....:      for lam in Partitions(4) )
                True
                sage: all( s(h(lam)).verschiebung(5) == s(h(lam).verschiebung(5))
                ....:      for lam in Partitions(10) )
                True
                sage: all( s(h(lam)).verschiebung(2) == s(h(lam).verschiebung(2))
                ....:      for lam in Partitions(8) )
                True
                sage: all( s(h(lam)).verschiebung(3) == s(h(lam).verschiebung(3))
                ....:      for lam in Partitions(12) )
                True
                sage: all( s(h(lam)).verschiebung(3) == s(h(lam).verschiebung(3))
                ....:      for lam in Partitions(9) )
                True
            """
            # Extra hack for the n == 1 case, since lam.quotient(1)
            # (for lam being a partition) returns a partition rather than
            # a partition tuple.
            if n == 1:
                return self

            parent = self.parent()
            s_coords_of_self = self.monomial_coefficients().items()
            result = parent.zero()
            from sage.combinat.permutation import Permutation
            for (lam, coeff) in s_coords_of_self:
                if len(lam.core(n)) == 0:
                    quotient = lam.quotient(n)
                    quotient_prod = parent.prod([parent(part) for part in quotient])
                    # Now, compute the sign of quotient_prod in the
                    # n-th Verschiebung of lam.
                    len_lam = len(lam)
                    ns = len_lam + ((- len_lam) % n)
                    s = ns // n   # This is actually ns / n, as we have n | ns.
                    beta_list = lam.beta_numbers(ns)
                    zipped_beta_list = zip(beta_list, range(1, ns + 1))
                    zipped_beta_list.sort(key = lambda a: (- 1 - a[0]) % n)
                    # We are using the fact that sort is a stable sort.
                    perm_list = [a[1] for a in zipped_beta_list]
                    if Permutation(perm_list).sign() == 1:
                        minus_sign = False
                    else:
                        minus_sign = True
                    if (n * s * (n-1) * (s-1)) % 8 == 4:
                        minus_sign = not minus_sign
                    if minus_sign:
                        result -= coeff * quotient_prod
                    else:
                        result += coeff * quotient_prod
            return result
Example #31
0
    def divided_difference(self, i, algorithm="sage"):
        r"""
        Return the ``i``-th divided difference operator, applied to
        ``self``.
        Here, ``i`` can be either a permutation or a positive integer.

        INPUT:
        
        - ``i`` -- permutation or positive integer

        - ``algorithm`` -- (default: ``'sage'``) either ``'sage'``
          or ``'symmetrica'``; this determines which software is
          called for the computation

        OUTPUT:

        The result of applying the ``i``-th divided difference
        operator to ``self``.
        
        If `i` is a positive integer, then the `i`-th divided
        difference operator `\delta_i` is the linear operator sending
        each polynomial `f = f(x_1, x_2, \ldots, x_n)` (in
        `n \geq i+1` variables) to the polynomial

        .. MATH::

            \frac{f - f_i}{x_i - x_{i+1}}, \qquad \text{ where }
            f_i = f(x_1, x_2, ..., x_{i-1}, x_{i+1}, x_i,
            x_{i+1}, ..., x_n) .

        If `\sigma` is a permutation in the `n`-th symmetric group,
        then the `\sigma`-th divided difference operator `\delta_\sigma`
        is the composition
        `\delta_{i_1} \delta_{i_2} \cdots \delta_{i_k}`, where
        `\sigma = s_{i_1} \circ s_{i_2} \circ \cdots \circ s_{i_k}` is
        any reduced expression for `\sigma` (the precise choice of
        reduced expression is immaterial).

        .. NOTE::

            The :meth:`expand` method results in a polynomial
            in `n` variables named ``x0, x1, ..., x(n-1)`` rather than
            `x_1, x_2, \ldots, x_n`.
            The variable named ``xi`` corresponds to `x_{i+1}`.
            Thus, ``self.divided_difference(i)`` involves the variables
            ``x(i-1)`` and ``xi`` getting switched (in the numerator).

        EXAMPLES::

            sage: X = SchubertPolynomialRing(ZZ)
            sage: a = X([3,2,1])
            sage: a.divided_difference(1)
            X[2, 3, 1]
            sage: a.divided_difference([3,2,1])
            X[1]
            sage: a.divided_difference(5)
            0

        Any divided difference of `0` is `0`::

            sage: X.zero().divided_difference(2)
            0

        This is compatible when a permutation is given as input::

            sage: a = X([3,2,4,1])
            sage: a.divided_difference([2,3,1])
            0
            sage: a.divided_difference(1).divided_difference(2)
            0

        ::

            sage: a = X([4,3,2,1])
            sage: a.divided_difference([2,3,1])
            X[3, 2, 4, 1]
            sage: a.divided_difference(1).divided_difference(2)
            X[3, 2, 4, 1]
            sage: a.divided_difference([4,1,3,2])
            X[1, 4, 2, 3]
            sage: b = X([4, 1, 3, 2])
            sage: b.divided_difference(1).divided_difference(2)
            X[1, 3, 4, 2]
            sage: b.divided_difference(1).divided_difference(2).divided_difference(3)
            X[1, 3, 2]
            sage: b.divided_difference(1).divided_difference(2).divided_difference(3).divided_difference(2)
            X[1]
            sage: b.divided_difference(1).divided_difference(2).divided_difference(3).divided_difference(3)
            0
            sage: b.divided_difference(1).divided_difference(2).divided_difference(1)
            0

        TESTS:

        Check that :trac:`23403` is fixed::

            sage: X = SchubertPolynomialRing(ZZ)
            sage: a = X([3,2,4,1])
            sage: a.divided_difference(2)
            0
            sage: a.divided_difference([3,2,1])
            0
            sage: a.divided_difference(0)
            Traceback (most recent call last):
            ...
            ValueError: cannot apply \delta_{0} to a (= X[3, 2, 4, 1])
        """
        if not self: # if self is 0
            return self
        Perms = Permutations()
        if i in ZZ:
            if algorithm == "sage":
                if i <= 0:
                    raise ValueError(r"cannot apply \delta_{%s} to a (= %s)" % (i, self))
                # The operator `\delta_i` sends the Schubert
                # polynomial `X_\pi` (where `\pi` is a finitely supported
                # permutation of `\{1, 2, 3, \ldots\}`) to:
                # - the Schubert polynomial X_\sigma`, where `\sigma` is
                #   obtained from `\pi` by switching the values at `i` and `i+1`,
                #   if `i` is a descent of `\pi` (that is, `\pi(i) > \pi(i+1)`);
                # - `0` otherwise.
                # Notice that distinct `\pi`s lead to distinct `\sigma`s,
                # so we can use `_from_dict` here.
                res_dict = {}
                for pi, coeff in self:
                    pi = pi[:]
                    n = len(pi)
                    if n <= i:
                        continue
                    if pi[i-1] < pi[i]:
                        continue
                    pi[i-1], pi[i] = pi[i], pi[i-1]
                    pi = Perms(pi).remove_extra_fixed_points()
                    res_dict[pi] = coeff
                return self.parent()._from_dict(res_dict)
            else: # if algorithm == "symmetrica":
                return symmetrica.divdiff_schubert(i, self)
        elif i in Perms:
            if algorithm == "sage":
                i = Permutation(i)
                redw = i.reduced_word()
                res_dict = {}
                for pi, coeff in self:
                    next_pi = False
                    pi = pi[:]
                    n = len(pi)
                    for j in redw:
                        if n <= j:
                            next_pi = True
                            break
                        if pi[j-1] < pi[j]:
                            next_pi = True
                            break
                        pi[j-1], pi[j] = pi[j], pi[j-1]
                    if next_pi:
                        continue
                    pi = Perms(pi).remove_extra_fixed_points()
                    res_dict[pi] = coeff
                return self.parent()._from_dict(res_dict)
            else: # if algorithm == "symmetrica":
                return symmetrica.divdiff_perm_schubert(i, self)
        else:
            raise TypeError("i must either be an integer or permutation")
Example #32
0
def PermutationGraph(second_permutation, first_permutation=None):
    r"""
    Build a permutation graph from one permutation or from two lists.

    Definition:

    If `\sigma` is a permutation of `\{ 1, 2, \ldots, n \}`, then the
    permutation graph of `\sigma` is the graph on vertex set
    `\{ 1, 2, \ldots, n \}` in which two vertices `i` and `j` satisfying
    `i < j` are connected by an edge if and only if
    `\sigma^{-1}(i) > \sigma^{-1}(j)`. A visual way to construct this
    graph is as follows:

      Take two horizontal lines in the euclidean plane, and mark points
      `1, ..., n` from left to right on the first of them. On the second
      one, still from left to right, mark `n` points
      `\sigma(1), \sigma(2), \ldots, \sigma(n)`.
      Now, link by a segment the two points marked with `1`, then link
      together the points marked with `2`, and so on. The permutation
      graph of `\sigma` is the intersection graph of those segments: there
      exists a vertex in this graph for each element from `1` to `n`, two
      vertices `i, j` being adjacent if the segments `i` and `j` cross
      each other.

    The set of edges of the permutation graph can thus be identified with
    the set of inversions of the inverse of the given permutation
    `\sigma`.

    A more general notion of permutation graph can be defined as
    follows: If `S` is a set, and `(a_1, a_2, \ldots, a_n)` and
    `(b_1, b_2, \ldots, b_n)` are two lists of elements of `S`, each of
    which lists contains every element of `S` exactly once, then the
    permutation graph defined by these two lists is the graph on the
    vertex set `S` in which two vertices `i` and `j` are connected by an
    edge if and only if the order in which these vertices appear in the
    list `(a_1, a_2, \ldots, a_n)` is the opposite of the order in which
    they appear in the list `(b_1, b_2, \ldots, b_n)`. When
    `(a_1, a_2, \ldots, a_n) = (1, 2, \ldots, n)`, this graph is the
    permutation graph of the permutation
    `(b_1, b_2, \ldots, b_n) \in S_n`. Notice that `S` does not have to
    be a set of integers here, but can be a set of strings, tuples, or
    anything else. We can still use the above visual description to
    construct the permutation graph, but now we have to mark points
    `a_1, a_2, \ldots, a_n` from left to right on the first horizontal
    line and points `b_1, b_2, \ldots, b_n` from left to right on the
    second horizontal line.

    INPUT:

    - ``second_permutation`` -- the unique permutation/list defining the graph,
      or the second of the two (if the graph is to be built from two
      permutations/lists).

    - ``first_permutation`` (optional) -- the first of the two
      permutations/lists from which the graph should be built, if it is to be
      built from two permutations/lists.

      When ``first_permutation is None`` (default), it is set to be equal to
      ``sorted(second_permutation)``, which yields the expected ordering when
      the elements of the graph are integers.

    .. SEEALSO::

      - Recognition of Permutation graphs in the :mod:`comparability module
        <sage.graphs.comparability>`.

      - Drawings of permutation graphs as intersection graphs of segments is
        possible through the
        :meth:`~sage.combinat.permutation.Permutation.show` method of
        :class:`~sage.combinat.permutation.Permutation` objects.

        The correct argument to use in this case is ``show(representation =
        "braid")``.

      - :meth:`~sage.combinat.permutation.Permutation.inversions`

    EXAMPLES::

        sage: p = Permutations(5).random_element()
        sage: PG = graphs.PermutationGraph(p)
        sage: edges = PG.edges(labels=False)
        sage: set(edges) == set(p.inverse().inversions())
        True

        sage: PG = graphs.PermutationGraph([3,4,5,1,2])
        sage: sorted(PG.edges())
        [(1, 3, None),
         (1, 4, None),
         (1, 5, None),
         (2, 3, None),
         (2, 4, None),
         (2, 5, None)]
        sage: PG = graphs.PermutationGraph([3,4,5,1,2], [1,4,2,5,3])
        sage: sorted(PG.edges())
        [(1, 3, None),
         (1, 4, None),
         (1, 5, None),
         (2, 3, None),
         (2, 5, None),
         (3, 4, None),
         (3, 5, None)]
        sage: PG = graphs.PermutationGraph([1,4,2,5,3], [3,4,5,1,2])
        sage: sorted(PG.edges())
        [(1, 3, None),
         (1, 4, None),
         (1, 5, None),
         (2, 3, None),
         (2, 5, None),
         (3, 4, None),
         (3, 5, None)]

        sage: PG = graphs.PermutationGraph(Permutation([1,3,2]), Permutation([1,2,3]))
        sage: sorted(PG.edges())
        [(2, 3, None)]

        sage: graphs.PermutationGraph([]).edges()
        []
        sage: graphs.PermutationGraph([], []).edges()
        []

        sage: PG = graphs.PermutationGraph("graph", "phrag")
        sage: sorted(PG.edges())
        [('a', 'g', None),
         ('a', 'h', None),
         ('a', 'p', None),
         ('g', 'h', None),
         ('g', 'p', None),
         ('g', 'r', None),
         ('h', 'r', None),
         ('p', 'r', None)]

    TESTS::

        sage: graphs.PermutationGraph([1, 2, 3], [4, 5, 6])
        Traceback (most recent call last):
        ...
        ValueError: The two permutations do not contain the same set of elements ...
    """
    if first_permutation is None:
        first_permutation = sorted(second_permutation)
    else:
        if set(second_permutation) != set(first_permutation):
            raise ValueError("The two permutations do not contain the same " +
                             "set of elements ! It is going to be pretty " +
                             "hard to define a permutation graph from that !")

    vertex_to_index = {}
    for i, v in enumerate(first_permutation):
        vertex_to_index[v] = i + 1

    from sage.combinat.permutation import Permutation
    p2 = Permutation([vertex_to_index[x] for x in second_permutation])
    p2 = p2.inverse()

    g = Graph(name="Permutation graph for " + str(second_permutation))
    g.add_vertices(second_permutation)

    for u, v in p2.inversions():
        g.add_edge(first_permutation[u - 1], first_permutation[v - 1])

    return g
Example #33
0
def PermutationGraph(second_permutation, first_permutation = None):
    r"""
    Build a permutation graph from one permutation or from two lists.

    Definition:

    If `\sigma` is a permutation of `\{ 1, 2, \ldots, n \}`, then the
    permutation graph of `\sigma` is the graph on vertex set
    `\{ 1, 2, \ldots, n \}` in which two vertices `i` and `j` satisfying
    `i < j` are connected by an edge if and only if
    `\sigma^{-1}(i) > \sigma^{-1}(j)`. A visual way to construct this
    graph is as follows:

      Take two horizontal lines in the euclidean plane, and mark points
      `1, ..., n` from left to right on the first of them. On the second
      one, still from left to right, mark `n` points
      `\sigma(1), \sigma(2), \ldots, \sigma(n)`.
      Now, link by a segment the two points marked with `1`, then link
      together the points marked with `2`, and so on. The permutation
      graph of `\sigma` is the intersection graph of those segments: there
      exists a vertex in this graph for each element from `1` to `n`, two
      vertices `i, j` being adjacent if the segments `i` and `j` cross
      each other.

    The set of edges of the permutation graph can thus be identified with
    the set of inversions of the inverse of the given permutation
    `\sigma`.

    A more general notion of permutation graph can be defined as
    follows: If `S` is a set, and `(a_1, a_2, \ldots, a_n)` and
    `(b_1, b_2, \ldots, b_n)` are two lists of elements of `S`, each of
    which lists contains every element of `S` exactly once, then the
    permutation graph defined by these two lists is the graph on the
    vertex set `S` in which two vertices `i` and `j` are connected by an
    edge if and only if the order in which these vertices appear in the
    list `(a_1, a_2, \ldots, a_n)` is the opposite of the order in which
    they appear in the list `(b_1, b_2, \ldots, b_n)`. When
    `(a_1, a_2, \ldots, a_n) = (1, 2, \ldots, n)`, this graph is the
    permutation graph of the permutation
    `(b_1, b_2, \ldots, b_n) \in S_n`. Notice that `S` does not have to
    be a set of integers here, but can be a set of strings, tuples, or
    anything else. We can still use the above visual description to
    construct the permutation graph, but now we have to mark points
    `a_1, a_2, \ldots, a_n` from left to right on the first horizontal
    line and points `b_1, b_2, \ldots, b_n` from left to right on the
    second horizontal line.

    INPUT:

    - ``second_permutation`` -- the unique permutation/list defining the graph,
      or the second of the two (if the graph is to be built from two
      permutations/lists).

    - ``first_permutation`` (optional) -- the first of the two
      permutations/lists from which the graph should be built, if it is to be
      built from two permutations/lists.

      When ``first_permutation is None`` (default), it is set to be equal to
      ``sorted(second_permutation)``, which yields the expected ordering when
      the elements of the graph are integers.

    .. SEEALSO:

      - Recognition of Permutation graphs in the :mod:`comparability module
        <sage.graphs.comparability>`.

      - Drawings of permutation graphs as intersection graphs of segments is
        possible through the
        :meth:`~sage.combinat.permutation.Permutation.show` method of
        :class:`~sage.combinat.permutation.Permutation` objects.

        The correct argument to use in this case is ``show(representation =
        "braid")``.

      - :meth:`~sage.combinat.permutation.Permutation.inversions`

    EXAMPLES::

        sage: p = Permutations(5).random_element()
        sage: PG = graphs.PermutationGraph(p)
        sage: edges = PG.edges(labels=False)
        sage: set(edges) == set(p.inverse().inversions())
        True

        sage: PG = graphs.PermutationGraph([3,4,5,1,2])
        sage: sorted(PG.edges())
        [(1, 3, None),
         (1, 4, None),
         (1, 5, None),
         (2, 3, None),
         (2, 4, None),
         (2, 5, None)]
        sage: PG = graphs.PermutationGraph([3,4,5,1,2], [1,4,2,5,3])
        sage: sorted(PG.edges())
        [(1, 3, None),
         (1, 4, None),
         (1, 5, None),
         (2, 3, None),
         (2, 5, None),
         (3, 4, None),
         (3, 5, None)]
        sage: PG = graphs.PermutationGraph([1,4,2,5,3], [3,4,5,1,2])
        sage: sorted(PG.edges())
        [(1, 3, None),
         (1, 4, None),
         (1, 5, None),
         (2, 3, None),
         (2, 5, None),
         (3, 4, None),
         (3, 5, None)]

        sage: PG = graphs.PermutationGraph(Permutation([1,3,2]), Permutation([1,2,3]))
        sage: sorted(PG.edges())
        [(2, 3, None)]

        sage: graphs.PermutationGraph([]).edges()
        []
        sage: graphs.PermutationGraph([], []).edges()
        []

        sage: PG = graphs.PermutationGraph("graph", "phrag")
        sage: sorted(PG.edges())
        [('a', 'g', None),
         ('a', 'h', None),
         ('a', 'p', None),
         ('g', 'h', None),
         ('g', 'p', None),
         ('g', 'r', None),
         ('h', 'r', None),
         ('p', 'r', None)]

    TESTS::

        sage: graphs.PermutationGraph([1, 2, 3], [4, 5, 6])
        Traceback (most recent call last):
        ...
        ValueError: The two permutations do not contain the same set of elements ...
    """
    if first_permutation is None:
        first_permutation = sorted(second_permutation)
    else:
        if set(second_permutation) != set(first_permutation):
            raise ValueError("The two permutations do not contain the same "+
                             "set of elements ! It is going to be pretty "+
                             "hard to define a permutation graph from that !")

    vertex_to_index = {}
    for i, v in enumerate(first_permutation):
        vertex_to_index[v] = i+1

    from sage.combinat.permutation import Permutation
    p2 = Permutation([vertex_to_index[x] for x in second_permutation])
    p2 = p2.inverse()

    g = Graph(name="Permutation graph for "+str(second_permutation))
    g.add_vertices(second_permutation)

    for u,v in p2.inversions():
        g.add_edge(first_permutation[u-1], first_permutation[v-1])

    return g
def gale_ryser_theorem(p1, p2, algorithm="gale"):
    r"""
        Returns the binary matrix given by the Gale-Ryser theorem.

        The Gale Ryser theorem asserts that if `p_1,p_2` are two
        partitions of `n` of respective lengths `k_1,k_2`, then there is
        a binary `k_1\times k_2` matrix `M` such that `p_1` is the vector
        of row sums and `p_2` is the vector of column sums of `M`, if
        and only if the conjugate of `p_2` dominates `p_1`.

        INPUT:

        - ``p1, p2``-- list of integers representing the vectors
          of row/column sums

        - ``algorithm`` -- two possible string values :

            - ``"ryser"`` implements the construction due
              to Ryser [Ryser63]_.

            - ``"gale"`` (default) implements the construction due to Gale [Gale57]_.

        OUTPUT:

        - A binary matrix if it exists, ``None`` otherwise.

        Gale's Algorithm:

        (Gale [Gale57]_): A matrix satisfying the constraints of its
        sums can be defined as the solution of the following
        Linear Program, which Sage knows how to solve.

        .. MATH::

            \forall i&\sum_{j=1}^{k_2} b_{i,j}=p_{1,j}\\
            \forall i&\sum_{j=1}^{k_1} b_{j,i}=p_{2,j}\\
            &b_{i,j}\mbox{ is a binary variable}

        Ryser's Algorithm:

        (Ryser [Ryser63]_): The construction of an `m\times n` matrix `A=A_{r,s}`,
        due to Ryser, is described as follows. The
        construction works if and only if have `s\preceq r^*`.

        * Construct the `m\times n` matrix `B` from `r` by defining
          the `i`-th row of `B` to be the vector whose first `r_i`
          entries are `1`, and the remainder are 0's, `1\leq i\leq
          m`.  This maximal matrix `B` with row sum `r` and ones left
          justified has column sum `r^{*}`.

        * Shift the last `1` in certain rows of `B` to column `n` in
          order to achieve the sum `s_n`.  Call this `B` again.

          * The `1`'s in column n are to appear in those
            rows in which `A` has the largest row sums, giving
            preference to the bottom-most positions in case of ties.
          * Note: When this step automatically "fixes" other columns,
            one must skip ahead to the first column index
            with a wrong sum in the step below.

        * Proceed inductively to construct columns `n-1`, ..., `2`, `1`.

        * Set `A = B`. Return `A`.

        EXAMPLES:

        Computing the matrix for `p_1=p_2=2+2+1` ::

            sage: from sage.combinat.integer_vector import gale_ryser_theorem
            sage: p1 = [2,2,1]
            sage: p2 = [2,2,1]
            sage: print gale_ryser_theorem(p1, p2)     # not tested
            [1 1 0]
            [1 0 1]
            [0 1 0]
            sage: A = gale_ryser_theorem(p1, p2)
            sage: rs = [sum(x) for x in A.rows()]
            sage: cs = [sum(x) for x in A.columns()]
            sage: p1 == rs; p2 == cs
            True
            True

        Or for a non-square matrix with `p_1=3+3+2+1` and `p_2=3+2+2+1+1`, using Ryser's algorithm ::

            sage: from sage.combinat.integer_vector import gale_ryser_theorem
            sage: p1 = [3,3,1,1]
            sage: p2 = [3,3,1,1]
            sage: gale_ryser_theorem(p1, p2, algorithm = "ryser")
            [1 1 0 1]
            [1 1 1 0]
            [0 1 0 0]
            [1 0 0 0]
            sage: p1 = [4,2,2]
            sage: p2 = [3,3,1,1]
            sage: gale_ryser_theorem(p1, p2, algorithm = "ryser")
            [1 1 1 1]
            [1 1 0 0]
            [1 1 0 0]
            sage: p1 = [4,2,2,0]
            sage: p2 = [3,3,1,1,0,0]
            sage: gale_ryser_theorem(p1, p2, algorithm = "ryser")
            [1 1 1 1 0 0]
            [1 1 0 0 0 0]
            [1 1 0 0 0 0]
            [0 0 0 0 0 0]
            sage: p1 = [3,3,2,1]
            sage: p2 = [3,2,2,1,1]
            sage: print gale_ryser_theorem(p1, p2, algorithm="gale")  # not tested
            [1 1 1 0 0]
            [1 1 0 0 1]
            [1 0 1 0 0]
            [0 0 0 1 0]

        With `0` in the sequences, and with unordered inputs ::

            sage: from sage.combinat.integer_vector import gale_ryser_theorem
            sage: gale_ryser_theorem([3,3,0,1,1,0], [3,1,3,1,0], algorithm = "ryser")
            [1 0 1 1 0]
            [1 1 1 0 0]
            [0 0 0 0 0]
            [0 0 1 0 0]
            [1 0 0 0 0]
            [0 0 0 0 0]
            sage: p1 = [3,1,1,1,1]; p2 = [3,2,2,0]
            sage: gale_ryser_theorem(p1, p2, algorithm = "ryser")
            [1 1 1 0]
            [0 0 1 0]
            [0 1 0 0]
            [1 0 0 0]
            [1 0 0 0]

        TESTS:

        This test created a random bipartite graph on `n+m` vertices. Its
        adjacency matrix is binary, and it is used to create some
        "random-looking" sequences which correspond to an existing matrix. The
        ``gale_ryser_theorem`` is then called on these sequences, and the output
        checked for correction.::

            sage: def test_algorithm(algorithm, low = 10, high = 50):
            ...      n,m = randint(low,high), randint(low,high)
            ...      g = graphs.RandomBipartite(n, m, .3)
            ...      s1 = sorted(g.degree([(0,i) for i in range(n)]), reverse = True)
            ...      s2 = sorted(g.degree([(1,i) for i in range(m)]), reverse = True)
            ...      m = gale_ryser_theorem(s1, s2, algorithm = algorithm)
            ...      ss1 = sorted(map(lambda x : sum(x) , m.rows()), reverse = True)
            ...      ss2 = sorted(map(lambda x : sum(x) , m.columns()), reverse = True)
            ...      if ((ss1 == s1) and (ss2 == s2)):
            ...          return True
            ...      return False

            sage: for algorithm in ["gale", "ryser"]:                            # long time
            ...      for i in range(50):                                         # long time
            ...         if not test_algorithm(algorithm, 3, 10):                 # long time
            ...             print "Something wrong with algorithm ", algorithm   # long time
            ...             break                                                # long time

        Null matrix::

            sage: gale_ryser_theorem([0,0,0],[0,0,0,0], algorithm="gale")
            [0 0 0 0]
            [0 0 0 0]
            [0 0 0 0]
            sage: gale_ryser_theorem([0,0,0],[0,0,0,0], algorithm="ryser")
            [0 0 0 0]
            [0 0 0 0]
            [0 0 0 0]

        REFERENCES:

        ..  [Ryser63] H. J. Ryser, Combinatorial Mathematics,
                Carus Monographs, MAA, 1963.
        ..  [Gale57] D. Gale, A theorem on flows in networks, Pacific J. Math.
                7(1957)1073-1082.
        """
    from sage.combinat.partition import Partition
    from sage.matrix.constructor import matrix

    if not (is_gale_ryser(p1, p2)):
        return False

    if algorithm == "ryser":  # ryser's algorithm
        from sage.combinat.permutation import Permutation

        # Sorts the sequences if they are not, and remembers the permutation
        # applied
        tmp = sorted(enumerate(p1), reverse=True, key=lambda x: x[1])
        r = [x[1] for x in tmp if x[1] > 0]
        r_permutation = [
            x - 1 for x in Permutation([x[0] + 1 for x in tmp]).inverse()
        ]
        m = len(r)

        tmp = sorted(enumerate(p2), reverse=True, key=lambda x: x[1])
        s = [x[1] for x in tmp if x[1] > 0]
        s_permutation = [
            x - 1 for x in Permutation([x[0] + 1 for x in tmp]).inverse()
        ]
        n = len(s)

        A0 = matrix([[1] * r[j] + [0] * (n - r[j]) for j in range(m)])

        for k in range(1, n + 1):
            goodcols = [i for i in range(n) if s[i] == sum(A0.column(i))]
            if sum(A0.column(n - k)) != s[n - k]:
                A0 = _slider01(A0, s[n - k], n - k, p1, p2, goodcols)

        # If we need to add empty rows/columns
        if len(p1) != m:
            A0 = A0.stack(matrix([[0] * n] * (len(p1) - m)))

        if len(p2) != n:
            A0 = A0.transpose().stack(matrix([[0] * len(p1)] *
                                             (len(p2) - n))).transpose()

        # Applying the permutations to get a matrix satisfying the
        # order given by the input
        A0 = A0.matrix_from_rows_and_columns(r_permutation, s_permutation)
        return A0

    elif algorithm == "gale":
        from sage.numerical.mip import MixedIntegerLinearProgram
        k1, k2 = len(p1), len(p2)
        p = MixedIntegerLinearProgram()
        b = p.new_variable(binary=True)
        for (i, c) in enumerate(p1):
            p.add_constraint(p.sum([b[i, j] for j in xrange(k2)]) == c)
        for (i, c) in enumerate(p2):
            p.add_constraint(p.sum([b[j, i] for j in xrange(k1)]) == c)
        p.set_objective(None)
        p.solve()
        b = p.get_values(b)
        M = [[0] * k2 for i in xrange(k1)]
        for i in xrange(k1):
            for j in xrange(k2):
                M[i][j] = int(b[i, j])
        return matrix(M)

    else:
        raise ValueError(
            "The only two algorithms available are \"gale\" and \"ryser\"")
Example #35
0
def cycle_string(lis):
    from sage.combinat.permutation import Permutation
    return Permutation(lis).cycle_string()
Example #36
0
    def __classcall_private__(cls, p):
        r"""
        This function tries to recognize the input (it can be either a list or
        a tuple of pairs, or a fix-point free involution given as a list or as
        a permutation), constructs the parent (enumerated set of
        PerfectMatchings of the ground set) and calls the __init__ function to
        construct our object.

        EXAMPLES::

            sage: m = PerfectMatching([('a','e'),('b','c'),('d','f')]);m
            [('a', 'e'), ('b', 'c'), ('d', 'f')]
            sage: isinstance(m,PerfectMatching)
            True
            sage: n = PerfectMatching([3, 8, 1, 7, 6, 5, 4, 2]);n
            [(1, 3), (2, 8), (4, 7), (5, 6)]
            sage: n.parent()
            Set of perfect matchings of {1, 2, 3, 4, 5, 6, 7, 8}
            sage: PerfectMatching([(1, 4), (2, 3), (5, 6)]).is_non_crossing()
            True

        The function checks that the given list or permutation is a valid perfect
        matching (i.e. a list of pairs with pairwise disjoint elements  or a
        fixpoint-free involution) and raises a ValueError otherwise:

            sage: PerfectMatching([(1, 2, 3), (4, 5)])
            Traceback (most recent call last):
            ...
            ValueError: [(1, 2, 3), (4, 5)] is not a valid perfect matching: all elements of the list must be pairs

        If you know your datas are in a good format, use directly
        ``PerfectMatchings(objects)(data)``.

        TESTS::

             sage: m = PerfectMatching([('a','e'),('b','c'),('d','f')])
             sage: TestSuite(m).run()
             sage: m = PerfectMatching([])
             sage: TestSuite(m).run()
             sage: PerfectMatching(6)
             Traceback (most recent call last):
             ...
             ValueError: cannot convert p (= 6) to a PerfectMatching
             sage: PerfectMatching([(1,2,3)])
             Traceback (most recent call last):
             ...
             ValueError: [(1, 2, 3)] is not a valid perfect matching:
             all elements of the list must be pairs
             sage: PerfectMatching([(1,1)])
             Traceback (most recent call last):
             ...
             ValueError: [(1, 1)] is not a valid perfect matching:
             there are some repetitions
             sage: PerfectMatching(Permutation([4,2,1,3]))
             Traceback (most recent call last):
             ...
             ValueError: The permutation p (= [4, 2, 1, 3]) is not a fixed point free involution
        """
        # we have to extract from the argument p the set of objects of the
        # matching and the list of pairs.
        # First case: p is a list (resp tuple) of lists (resp tuple).
        if (isinstance(p, list) or isinstance(p, tuple)) and (
                all([isinstance(x, list) or isinstance(x, tuple) for x in p])):
            objects = Set(flatten(p))
            data = (map(tuple, p))
            #check if the data are correct
            if not all([len(t) == 2 for t in data]):
                raise ValueError("%s is not a valid perfect matching:\n"
                                 "all elements of the list must be pairs" % p)
            if len(objects) < 2*len(data):
                raise ValueError("%s is not a valid perfect matching:\n"
                                 "there are some repetitions" % p)
        # Second case: p is a permutation or a list of integers, we have to
        # check if it is a fix-point-free involution.
        elif ((isinstance(p, list) and
               all(map(lambda x: (isinstance(x, Integer) or isinstance(x, int)), p)))
              or isinstance(p, Permutation)):
            p = Permutation(p)
            n = len(p)
            if not(p.cycle_type() == [2 for i in range(n//2)]):
                raise ValueError("The permutation p (= %s) is not a "
                                 "fixed point free involution" % p)
            objects = Set(range(1, n+1))
            data = p.to_cycles()
        # Third case: p is already a perfect matching, we return p directly
        elif isinstance(p, PerfectMatching):
            return p
        else:
            raise ValueError("cannot convert p (= %s) to a PerfectMatching" % p)
        # Finally, we create the parent and the element using the element
        # class of the parent. Note: as this function is private, when we
        # create an object via parent.element_class(...), __init__ is directly
        # executed and we do not have an infinite loop.
        return PerfectMatchings(objects)(data)
Example #37
0
def PermutationGraph(second_permutation, first_permutation = None):
    r"""
    Builds a permutation graph from one (or two) permutations.

    General definition

    A Permutation Graph can be encoded by a permutation `\sigma`
    of `1, ..., n`. It is then built in the following way :

      Take two horizontal lines in the euclidean plane, and mark points `1, ...,
      n` from left to right on the first of them. On the second one, still from
      left to right, mark point in the order in which they appear in `\sigma`.
      Now, link by a segment the two points marked with 1, then link together
      the points marked with 2, and so on. The permutation graph defined by the
      permutation is the intersection graph of those segments : there exists a
      point in this graph for each element from `1` to `n`, two vertices `i, j`
      being adjacent if the segments `i` and `j` cross each other.

    The set of edges of the resulting graph is equal to the set of inversions of
    the inverse of the given permutation.

    INPUT:

    - ``second_permutation`` -- the permutation from which the graph should be
      built. It corresponds to the ordering of the elements on the second line
      (see previous definition)

    - ``first_permutation`` (optional) -- the ordering of the elements on the
      *first* line. This is useful when the elements have no natural ordering,
      for instance when they are strings, or tuples, or anything else.

      When ``first_permutation == None`` (default), it is set to be equal to
      ``sorted(second_permutation)``, which just yields the expected
      ordering when the elements of the graph are integers.

    .. SEEALSO:

      - Recognition of Permutation graphs in the :mod:`comparability module
        <sage.graphs.comparability>`.

      - Drawings of permutation graphs as intersection graphs of segments is
        possible through the
        :meth:`~sage.combinat.permutation.Permutation.show` method of
        :class:`~sage.combinat.permutation.Permutation` objects.

        The correct argument to use in this case is ``show(representation =
        "braid")``.

      - :meth:`~sage.combinat.permutation.Permutation.inversions`

    EXAMPLE::

        sage: p = Permutations(5).random_element()
        sage: edges = graphs.PermutationGraph(p).edges(labels =False)
        sage: set(edges) == set(p.inverse().inversions())
        True

    TESTS::

        sage: graphs.PermutationGraph([1, 2, 3], [4, 5, 6])
        Traceback (most recent call last):
        ...
        ValueError: The two permutations do not contain the same set of elements ...
    """
    if first_permutation == None:
        first_permutation = sorted(second_permutation)
    else:
        if set(second_permutation) != set(first_permutation):
            raise ValueError("The two permutations do not contain the same "+
                             "set of elements ! It is going to be pretty "+
                             "hard to define a permutation graph from that !")

    vertex_to_index = {}
    for i, v in enumerate(first_permutation):
        vertex_to_index[v] = i+1

    from sage.combinat.permutation import Permutation
    p2 = Permutation(map(lambda x:vertex_to_index[x], second_permutation))
    p1 = Permutation(map(lambda x:vertex_to_index[x], first_permutation))
    p2 = p2 * p1.inverse()
    p2 = p2.inverse()

    g = Graph(name="Permutation graph for "+str(second_permutation))
    g.add_vertices(second_permutation)

    for u,v in p2.inversions():
        g.add_edge(first_permutation[u-1], first_permutation[v-1])

    return g
        def cycle_index(self, parent = None):
            r"""
            INPUT:

             - ``self`` - a permutation group `G`
             - ``parent`` -- a free module with basis indexed by partitions,
               or behave as such, with a ``term`` and ``sum`` method
               (default: the symmetric functions over the rational field in the p basis)

            Returns the *cycle index* of `G`, which is a gadget counting
            the elements of `G` by cycle type, averaged over the group:

            .. math::

                P = \frac{1}{|G|} \sum_{g\in G} p_{ \operatorname{cycle\ type}(g) }

            EXAMPLES:

            Among the permutations of the symmetric group `S_4`, there is
            the identity, 6 cycles of length 2, 3 products of two cycles
            of length 2, 8 cycles of length 3, and 6 cycles of length 4::

                sage: S4 = SymmetricGroup(4)
                sage: P = S4.cycle_index()
                sage: 24 * P
                p[1, 1, 1, 1] + 6*p[2, 1, 1] + 3*p[2, 2] + 8*p[3, 1] + 6*p[4]

            If `l = (l_1,\dots,l_k)` is a partition, ``|G| P[l]`` is the number
            of elements of `G` with cycles of length `(p_1,\dots,p_k)`::

                sage: 24 * P[ Partition([3,1]) ]
                8

            The cycle index plays an important role in the enumeration of
            objects modulo the action of a group (Polya enumeration), via
            the use of symmetric functions and plethysms. It is therefore
            encoded as a symmetric function, expressed in the powersum
            basis::

                sage: P.parent()
                Symmetric Functions over Rational Field in the powersum basis

            This symmetric function can have some nice properties; for
            example, for the symmetric group `S_n`, we get the complete
            symmetric function `h_n`::

                sage: S = SymmetricFunctions(QQ); h = S.h()
                sage: h( P )
                h[4]

            TODO: add some simple examples of Polya enumeration, once it
            will be easy to expand symmetric functions on any alphabet.

            Here are the cycle indices of some permutation groups::

                sage: 6 * CyclicPermutationGroup(6).cycle_index()
                p[1, 1, 1, 1, 1, 1] + p[2, 2, 2] + 2*p[3, 3] + 2*p[6]

                sage: 60 * AlternatingGroup(5).cycle_index()
                p[1, 1, 1, 1, 1] + 15*p[2, 2, 1] + 20*p[3, 1, 1] + 24*p[5]

                sage: for G in TransitiveGroups(5):               # optional - database_gap # long time
                ...       G.cardinality() * G.cycle_index()
                p[1, 1, 1, 1, 1] + 4*p[5]
                p[1, 1, 1, 1, 1] + 5*p[2, 2, 1] + 4*p[5]
                p[1, 1, 1, 1, 1] + 5*p[2, 2, 1] + 10*p[4, 1] + 4*p[5]
                p[1, 1, 1, 1, 1] + 15*p[2, 2, 1] + 20*p[3, 1, 1] + 24*p[5]
                p[1, 1, 1, 1, 1] + 10*p[2, 1, 1, 1] + 15*p[2, 2, 1] + 20*p[3, 1, 1] + 20*p[3, 2] + 30*p[4, 1] + 24*p[5]

            One may specify another parent for the result::

                sage: F = CombinatorialFreeModule(QQ, Partitions())
                sage: P = CyclicPermutationGroup(6).cycle_index(parent = F)
                sage: 6 * P
                B[[1, 1, 1, 1, 1, 1]] + B[[2, 2, 2]] + 2*B[[3, 3]] + 2*B[[6]]
                sage: P.parent() is F
                True

            This parent should have a ``term`` and ``sum`` method::

                sage: CyclicPermutationGroup(6).cycle_index(parent = QQ)
                Traceback (most recent call last):
                  ...
                AssertionError: `parent` should be (or behave as) a free module with basis indexed by partitions

            REFERENCES:

             .. [Ker1991] A. Kerber. Algebraic combinatorics via finite group actions, 2.2 p. 70.
               BI-Wissenschaftsverlag, Mannheim, 1991.

            AUTHORS:

             - Nicolas Borie and Nicolas M. Thiery

            TESTS::

                sage: P = PermutationGroup([]); P
                Permutation Group with generators [()]
                sage: P.cycle_index()
                p[1]
                sage: P = PermutationGroup([[(1)]]); P
                Permutation Group with generators [()]
                sage: P.cycle_index()
                p[1]
            """
            from sage.combinat.permutation import Permutation
            if parent is None:
                 from sage.rings.rational_field import QQ
                 from sage.combinat.sf.sf import SymmetricFunctions
                 parent = SymmetricFunctions(QQ).powersum()
            else:
                assert hasattr(parent, "term") and hasattr(parent, "sum"), \
                    "`parent` should be (or behave as) a free module with basis indexed by partitions"
            base_ring = parent.base_ring()
            # TODO: use self.conjugacy_classes() once available
            from sage.interfaces.gap import gap
            CC = ([Permutation(self(C.Representative())).cycle_type(), base_ring(C.Size())] for C in gap(self).ConjugacyClasses())
            return parent.sum( parent.term( partition, coeff ) for (partition, coeff) in CC)/self.cardinality()
Example #39
0
def RSK_inverse(p, q, output='array', insertion='RSK'):
    r"""
    Return the generalized permutation corresponding to the pair of
    tableaux `(p,q)` under the inverse of the Robinson-Schensted-Knuth
    algorithm.

    For more information on the bijection, see :func:`RSK`.

    INPUT:

    - ``p``, ``q`` -- Two semi-standard tableaux of the same shape, or
      (in the case when Hecke insertion is used) an increasing tableau and
      a set-valued tableau of the same shape (see the note below for the
      format of the set-valued tableau)

    - ``output`` -- (Default: ``'array'``) if ``q`` is semi-standard:

      - ``'array'`` -- as a two-line array (i.e. generalized permutation or
        biword)
      -  ``'matrix'`` -- as an integer matrix

      and if ``q`` is standard, we can have the output:

      - ``'word'`` -- as a word

      and additionally if ``p`` is standard, we can also have the output:

      - ``'permutation'`` -- as a permutation

    - ``insertion`` -- (Default: ``RSK``) The insertion algorithm used in the
      bijection. Currently the following are supported:

      - ``'RSK'`` -- Robinson-Schensted-Knuth insertion
      - ``'EG'`` -- Edelman-Greene insertion
      - ``'hecke'`` -- Hecke insertion

    .. NOTE::

        In the case of Hecke insertion, the input variable ``q`` should
        be a set-valued tableau, encoded as a tableau whose entries are
        strictly increasing tuples of positive integers. Each such tuple
        encodes the set of its entries.

    EXAMPLES:

    If both ``p`` and ``q`` are standard::

        sage: t1 = Tableau([[1, 2, 5], [3], [4]])
        sage: t2 = Tableau([[1, 2, 3], [4], [5]])
        sage: RSK_inverse(t1, t2)
        [[1, 2, 3, 4, 5], [1, 4, 5, 3, 2]]
        sage: RSK_inverse(t1, t2, 'word')
        word: 14532
        sage: RSK_inverse(t1, t2, 'matrix')
        [1 0 0 0 0]
        [0 0 0 1 0]
        [0 0 0 0 1]
        [0 0 1 0 0]
        [0 1 0 0 0]
        sage: RSK_inverse(t1, t2, 'permutation')
        [1, 4, 5, 3, 2]
        sage: RSK_inverse(t1, t1, 'permutation')
        [1, 4, 3, 2, 5]
        sage: RSK_inverse(t2, t2, 'permutation')
        [1, 2, 5, 4, 3]
        sage: RSK_inverse(t2, t1, 'permutation')
        [1, 5, 4, 2, 3]

    If the first tableau is semistandard::

        sage: p = Tableau([[1,2,2],[3]]); q = Tableau([[1,2,4],[3]])
        sage: ret = RSK_inverse(p, q); ret
        [[1, 2, 3, 4], [1, 3, 2, 2]]
        sage: RSK_inverse(p, q, 'word')
        word: 1322

    In general::

        sage: p = Tableau([[1,2,2],[2]]); q = Tableau([[1,3,3],[2]])
        sage: RSK_inverse(p, q)
        [[1, 2, 3, 3], [2, 1, 2, 2]]
        sage: RSK_inverse(p, q, 'matrix')
        [0 1]
        [1 0]
        [0 2]

    Using Edelman-Greene insertion::

        sage: pq = RSK([2,1,2,3,2], insertion='EG'); pq
        [[[1, 2, 3], [2, 3]], [[1, 3, 4], [2, 5]]]
        sage: RSK_inverse(*pq, insertion='EG')
        [2, 1, 2, 3, 2]

    Using Hecke insertion::

        sage: w = [5, 4, 1, 3, 4, 2, 5, 1, 2, 1, 4, 2, 4]
        sage: pq = RSK(w, insertion='hecke')
        sage: RSK_inverse(*pq, insertion='hecke', output='list')
        [5, 4, 1, 3, 4, 2, 5, 1, 2, 1, 4, 2, 4]

    .. NOTE::

        The constructor of ``Tableau`` accepts not only semistandard
        tableaux, but also arbitrary lists that are fillings of a
        partition diagram. (And such lists are used, e.g., for the
        set-valued tableau ``q`` that is passed to
        ``RSK_inverse(p, q, insertion='hecke')``.)
        The user is responsible for ensuring that the tableaux passed to
        ``RSK_inverse`` are of the right types (semistandard, standard,
        increasing, set-valued as needed).

    TESTS:

    From empty tableaux::

        sage: RSK_inverse(Tableau([]), Tableau([]))
        [[], []]

    Check that :func:`RSK_inverse` is the inverse of :func:`RSK` on the
    different types of inputs/outputs::

        sage: f = lambda p: RSK_inverse(*RSK(p), output='permutation')
        sage: all(p == f(p) for n in range(7) for p in Permutations(n))
        True
        sage: all(RSK_inverse(*RSK(w), output='word') == w for n in range(4) for w in Words(5, n))
        True
        sage: from sage.combinat.integer_matrices import IntegerMatrices
        sage: M = IntegerMatrices([1,2,2,1], [3,1,1,1])
        sage: all(RSK_inverse(*RSK(m), output='matrix') == m for m in M)
        True

        sage: n = ZZ.random_element(200)
        sage: p = Permutations(n).random_element()
        sage: is_fine = True if p == f(p) else p ; is_fine
        True

    Same for Edelman-Greene (but we are checking only the reduced words that
    can be obtained using the ``reduced_word()`` method from permutations)::

        sage: g = lambda w: RSK_inverse(*RSK(w, insertion='EG'), insertion='EG', output='permutation')
        sage: all(p.reduced_word() == g(p.reduced_word()) for n in range(7) for p in Permutations(n))
        True

        sage: n = ZZ.random_element(200)
        sage: p = Permutations(n).random_element()
        sage: is_fine = True if p == f(p) else p ; is_fine
        True

    Both tableaux must be of the same shape::

        sage: RSK_inverse(Tableau([[1,2,3]]), Tableau([[1,2]]))
        Traceback (most recent call last):
        ...
        ValueError: p(=[[1, 2, 3]]) and q(=[[1, 2]]) must have the same shape
    """
    if insertion == 'hecke':
        return hecke_insertion_reverse(p, q, output)

    if p.shape() != q.shape():
        raise ValueError("p(=%s) and q(=%s) must have the same shape"%(p, q))
    from sage.combinat.tableau import SemistandardTableaux
    if p not in SemistandardTableaux():
        raise ValueError("p(=%s) must be a semistandard tableau"%p)

    from bisect import bisect_left
    # Make a copy of p since this is destructive to it
    p_copy = [list(row) for row in p]

    if q.is_standard():
        rev_word = [] # This will be our word in reverse
        d = dict((qij,i) for i, Li in enumerate(q) for qij in Li)
        # d is now a dictionary which assigns to each integer k the
        # number of the row of q containing k.

        use_EG = (insertion == 'EG')

        for i in reversed(d.values()): # Delete last entry from i-th row of p_copy
            x = p_copy[i].pop() # Always the right-most entry
            for row in reversed(p_copy[:i]):
                y_pos = bisect_left(row,x) - 1
                if use_EG and row[y_pos] == x - 1 and y_pos < len(row)-1 and row[y_pos+1] == x:
                    # Nothing to do except decrement x by 1.
                    # (Case 1 on p. 74 of Edelman-Greene [EG1987]_.)
                    x -= 1
                else:
                    # switch x and y
                    x, row[y_pos] = row[y_pos], x
            rev_word.append(x)

        if use_EG:
            return list(reversed(rev_word))
        if output == 'word':
            from sage.combinat.words.word import Word
            return Word(reversed(rev_word))
        if output == 'matrix':
            return to_matrix(list(range(1, len(rev_word)+1)), list(reversed(rev_word)))
        if output == 'array':
            return [list(range(1, len(rev_word)+1)), list(reversed(rev_word))]
        if output == 'permutation':
            if not p.is_standard():
                raise TypeError("p must be standard to have a valid permutation as output")
            from sage.combinat.permutation import Permutation
            return Permutation(reversed(rev_word))
        raise ValueError("invalid output option")

    # Checks
    if insertion != 'RSK':
        raise NotImplementedError("only RSK is implemented for non-standard q")
    if q not in SemistandardTableaux():
        raise ValueError("q(=%s) must be a semistandard tableau"%q)

    upper_row = []
    lower_row = []
    #upper_row and lower_row will be the upper and lower rows of the
    #generalized permutation we get as a result, but both reversed.
    d = {}
    for row, Li in enumerate(q):
        for col, val in enumerate(Li):
            if val in d:
                d[val][col] = row
            else:
                d[val] = {col: row}
    #d is now a double family such that for every integers k and j,
    #the value d[k][j] is the row i such that the (i, j)-th cell of
    #q is filled with k.
    for value, row_dict in reversed(d.items()):
        for i in reversed(row_dict.values()):
            x = p_copy[i].pop() # Always the right-most entry
            for row in reversed(p_copy[:i]):
                y = bisect_left(row,x) - 1
                x, row[y] = row[y], x
            upper_row.append(value)
            lower_row.append(x)

    if output == 'matrix':
        return to_matrix(list(reversed(upper_row)), list(reversed(lower_row)))
    if output == 'array':
        return [list(reversed(upper_row)), list(reversed(lower_row))]
    if output in ['permutation', 'word']:
        raise TypeError("q must be standard to have a %s as valid output"%output)
    raise ValueError("invalid output option")
Example #40
0
    def _element_constructor_(self,
                              arg1,
                              v=None,
                              perm=None,
                              autom=None,
                              check=True):
        r"""
        Coerce ``arg1`` into this permutation group, if ``arg1`` is 0,
        then we will try to coerce ``(v, perm, autom)``.

        INPUT:

        - ``arg1`` (optional) -- either the integers 0, 1 or an element of ``self``

        - ``v`` (optional) -- a vector of length ``self.degree()``

        - ``perm`` (optional) -- a permutaton of degree ``self.degree()``

        - ``autom`` (optional) -- an automorphism of the ring

        EXAMPLES::

            sage: F.<a> = GF(9)
            sage: S = SemimonomialTransformationGroup(F, 4)
            sage: S(1)
            ((1, 1, 1, 1); (), Ring endomorphism of Finite Field in a of size 3^2 Defn: a |--> a)
            sage: g = S(v=[1,1,1,a])
            sage: S(g)
            ((1, 1, 1, a); (), Ring endomorphism of Finite Field in a of size 3^2 Defn: a |--> a)
            sage: S(perm=Permutation('(1,2)(3,4)'))
            ((1, 1, 1, 1); (1,2)(3,4), Ring endomorphism of Finite Field in a of size 3^2 Defn: a |--> a)
            sage: S(autom=F.hom([a**3]))
            ((1, 1, 1, 1); (), Ring endomorphism of Finite Field in a of size 3^2 Defn: a |--> 2*a + 1)
        """
        from sage.categories.homset import End
        R = self.base_ring()
        if arg1 == 0:
            if v is None:
                v = [R.one()] * self.degree()
            if perm is None:
                perm = Permutation(range(1, self.degree() + 1))
            if autom is None:
                autom = R.hom(R.gens())

            if check:
                try:
                    v = [R(x) for x in v]
                except TypeError:
                    raise TypeError('the vector attribute %s ' % v +
                                    'should be iterable')
                if len(v) != self.degree():
                    raise ValueError('the length of the vector is %s,' %
                                     len(v) + ' should be %s' % self.degree())
                if not all(x.parent() is R and x.is_unit() for x in v):
                    raise ValueError('there is at least one element in the ' +
                                     'list %s not lying in %s ' % (v, R) +
                                     'or which is not invertible')
                try:
                    perm = Permutation(perm)
                except TypeError:
                    raise TypeError('the permutation attribute %s ' % perm +
                                    'could not be converted to a permutation')
                if len(perm) != self.degree():
                    raise ValueError('the permutation length is %s,' %
                                     len(perm) +
                                     ' should be %s' % self.degree())

                try:
                    if autom.parent() != End(R):
                        autom = End(R)(autom)
                except TypeError:
                    raise TypeError('%s of type %s' % (autom, type(autom)) +
                                    ' is not coerceable to an automorphism')
            return self.Element(self, v, perm, autom)
        else:
            try:
                if arg1.parent() is self:
                    return arg1
            except AttributeError:
                pass
            try:
                from sage.rings.integer import Integer
                if Integer(arg1) == 1:
                    return self()
            except TypeError:
                pass
            raise TypeError('the first argument must be an integer' +
                            ' or an element of this group')