Ejemplo n.º 1
0
    def basis_of_simple_closed_curves(self):
        from sage.graphs.digraph import DiGraph
        from sage.all import randint

        n = self._origami.nb_squares()
        C = self.chain_space()
        G = DiGraph(multiedges=True,loops=True,implementation='c_graph')

        for i in xrange(2*n):
            G.add_edge(self._starts[i], self._ends[i], i)

        waiting = [0]
        gens = []
        reps = [None] * G.num_verts()
        reps[0] = C.zero()

        while waiting:
            x = waiting.pop(randint(0,len(waiting)-1))
            for v0,v1,e in G.outgoing_edges(x):
                if reps[v1] is not None:
                    gens.append(reps[v0] + C.gen(e) - reps[v1])
                else:
                    reps[v1] = reps[v0] + C.gen(e)
                    waiting.append(v1)

        return gens
Ejemplo n.º 2
0
    def plot(self, label_elements=True, element_labels=None,
            label_font_size=12,label_font_color='black', layout = "acyclic", **kwds):
        """
        Returns a Graphics object corresponding to the Hasse diagram.

        EXAMPLES::

            sage: uc = [[2,3], [], [1], [1], [1], [3,4]]
            sage: elm_lbls = Permutations(3).list()
            sage: P = Poset(uc,elm_lbls)
            sage: H = P._hasse_diagram
            sage: levels = H.level_sets()
            sage: heights = dict([[i, levels[i]] for i in range(len(levels))])
            sage: type(H.plot(label_elements=True))
            <class 'sage.plot.graphics.Graphics'>

        ::

            sage: P = Posets.SymmetricGroupBruhatIntervalPoset([1,2,3,4], [3,4,1,2])
            sage: P._hasse_diagram.plot()
        """
        # Set element_labels to default to the vertex set.
        if element_labels is None:
            element_labels = range(self.num_verts())

        # Create the underlying graph.
        graph = DiGraph(self)
        graph.relabel(element_labels)

        return graph.plot(layout = layout, **kwds)
Ejemplo n.º 3
0
    def relabel(self, relabelling, inplace=False, **kwds):
        """
        Return the relabelling Dynkin diagram of ``self``.

        EXAMPLES::

            sage: D = DynkinDiagram(['C',3])
            sage: D.relabel({1:0, 2:4, 3:1})
            O---O=<=O
            0   4   1
            C3 relabelled by {1: 0, 2: 4, 3: 1}
            sage: D
            O---O=<=O
            1   2   3
            C3
        """
        if inplace:
            DiGraph.relabel(self, relabelling, inplace, **kwds)
            G = self
        else:
            # We must make a copy of ourselves first because of DiGraph's
            #   relabel default behavior is to do so in place, and if not
            #   then it recurses on itself with no argument for inplace
            G = self.copy().relabel(relabelling, inplace=True, **kwds)
        if self._cartan_type is not None:
            G._cartan_type = self._cartan_type.relabel(relabelling)
        return G
Ejemplo n.º 4
0
    def RandomDirectedGNP(self, n, p):
        r"""
        Returns a random digraph on `n` nodes. Each edge is
        inserted independently with probability `p`.
        
        REFERENCES:

        - [1] P. Erdos and A. Renyi, On Random Graphs, Publ.  Math. 6,
          290 (1959).

        - [2] E. N. Gilbert, Random Graphs, Ann. Math.  Stat., 30,
          1141 (1959).
        
        PLOTTING: When plotting, this graph will use the default
        spring-layout algorithm, unless a position dictionary is
        specified.
        
        EXAMPLE::
        
            sage: D = digraphs.RandomDirectedGNP(10, .2)
            sage: D.num_verts()
            10
            sage: D.edges(labels=False)
            [(0, 1), (0, 3), (0, 6), (0, 8), (1, 4), (3, 7), (4, 1), (4, 8), (5, 2), (5, 6), (5, 8), (6, 4), (7, 6), (8, 4), (8, 5), (8, 7), (8, 9), (9, 3), (9, 4), (9, 6)]
        """
        from sage.misc.prandom import random
        D = DiGraph(n)
        for i in xrange(n):
            for j in xrange(i):
                if random() < p:
                    D.add_edge(i,j)
            for j in xrange(i+1,n):
                if random() < p:
                    D.add_edge(i,j)
        return D
Ejemplo n.º 5
0
    def tree(self):
        r"""
        Returns the Huffman tree corresponding to the current encoding.

        INPUT:

        - None.

        OUTPUT:

        - The binary tree representing a Huffman code.

        EXAMPLES::

            sage: from sage.coding.source_coding.huffman import Huffman
            sage: str = "Sage is my most favorite general purpose computer algebra system"
            sage: h = Huffman(str)
            sage: T = h.tree(); T
            Digraph on 39 vertices
            sage: T.show(figsize=[20,20])
            <BLANKLINE>
        """
        from sage.graphs.digraph import DiGraph
        g = DiGraph()
        g.add_edges(self._generate_edges(self._tree))
        return g
Ejemplo n.º 6
0
    def inclusion_digraph(self):
        r"""
        Returns the class inclusion digraph

        Upon the first call, this loads the database from the local
        XML file. Subsequent calls are cached.

        EXAMPLES::

            sage: g = graph_classes.inclusion_digraph(); g
            Digraph on ... vertices
        """
        classes    = self.classes()
        inclusions = self.inclusions()

        from sage.graphs.digraph import DiGraph
        inclusion_digraph = DiGraph()
        inclusion_digraph.add_vertices(classes.keys())

        for edge in inclusions:
            if edge.get("confidence","") == "unpublished":
                continue
            inclusion_digraph.add_edge(edge['super'], edge['sub'])

        return inclusion_digraph
Ejemplo n.º 7
0
    def __init__(self, t=None, index_set=None, odd_isotropic_roots=[],
                 **options):
        """
        Initialize ``self``.

        EXAMPLES::

            sage: d = DynkinDiagram(["A", 3])
            sage: TestSuite(d).run()
        """
        if isinstance(t, DiGraph):
            if isinstance(t, DynkinDiagram_class):
                self._cartan_type = t._cartan_type
                self._odd_isotropic_roots = tuple(odd_isotropic_roots)
            else:
                self._cartan_type = None
                self._odd_isotropic_roots = ()
            DiGraph.__init__(self, data=t, **options)
            return

        DiGraph.__init__(self, **options)
        self._cartan_type = t
        self._odd_isotropic_roots = tuple(odd_isotropic_roots)
        if index_set is not None:
            self.add_vertices(index_set)
        elif t is not None:
            self.add_vertices(t.index_set())
Ejemplo n.º 8
0
    def as_digraph(self):
        """
        Returns a directed graph version of ``self``.

        .. WARNING::

            At this time, the output makes sense only if ``self`` is a
            labelled binary tree with no repeated labels and no ``None``
            labels.

        EXAMPLES::

           sage: LT = LabelledOrderedTrees()
           sage: t1 = LT([LT([],label=6),LT([],label=1)],label=9)
           sage: t1.as_digraph()
           Digraph on 3 vertices

           sage: t = BinaryTree([[None, None],[[],None]]);
           sage: lt = t.canonical_labelling()
           sage: lt.as_digraph()
           Digraph on 4 vertices
        """
        from sage.graphs.digraph import DiGraph
        resu = dict([[self.label(),
                    [t.label() for t in self if not t.is_empty()]]])
        resu = DiGraph(resu)
        for t in self:
            if not t.is_empty():
                resu = resu.union(t.as_digraph())
        return resu
Ejemplo n.º 9
0
    def Tournament(self,n):
        r"""
        Returns a tournament on `n` vertices.

        In this tournament there is an edge from `i` to `j` if `i<j`.

        INPUT:

        - ``n`` (integer) -- number of vertices in the tournament.

        EXAMPLES::

            sage: g = digraphs.Tournament(5)
            sage: g.vertices()
            [0, 1, 2, 3, 4]
            sage: g.size()
            10
            sage: g.automorphism_group().cardinality()
            1
        """
        if n<0:
            raise ValueError("The number of vertices must be a positive integer.")

        g = DiGraph()
        g.name("Tournament on "+str(n)+" vertices")

        for i in range(n-1):
            for j in range(i+1, n):
                g.add_edge(i,j)

        if n:
            from sage.graphs.graph_plot import _circle_embedding
            _circle_embedding(g, range(n))

        return g
Ejemplo n.º 10
0
        def bhz_poset(self):
            r"""
            Return the Bergeron-Hohlweg-Zabrocki partial order on the Coxeter
            group.

            This is a partial order on the elements of a finite
            Coxeter group `W`, which is distinct from the Bruhat
            order, the weak order and the shard intersection order. It
            was defined in [BHZ05]_.

            This partial order is not a lattice, as there is no unique
            maximal element. It can be succintly defined as follows.

            Let `u` and `v` be two elements of the Coxeter group `W`. Let
            `S(u)` be the support of `u`. Then `u \leq v` if and only
            if `v_{S(u)} = u` (here `v = v^I v_I` denotes the usual
            parabolic decomposition with respect to the standard parabolic
            subgroup `W_I`).

            .. SEEALSO::

                :meth:`bruhat_poset`, :meth:`shard_poset`, :meth:`weak_poset`

            EXAMPLES::

                sage: W = CoxeterGroup(['A',3], base_ring=ZZ)
                sage: P = W.bhz_poset(); P
                Finite poset containing 24 elements
                sage: P.relations_number()
                103
                sage: P.chain_polynomial()
                34*q^4 + 90*q^3 + 79*q^2 + 24*q + 1
                sage: len(P.maximal_elements())
                13

            REFERENCE:

            .. [BHZ05] \N. Bergeron, C. Hohlweg, and M. Zabrocki, *Posets
               related to the Connectivity Set of Coxeter Groups*.
               :arxiv:`math/0509271v3`
            """
            from sage.graphs.digraph import DiGraph
            from sage.combinat.posets.posets import Poset

            def covered_by(ux, vy):
                u, iu, Su = ux
                v, iv, Sv = vy
                if len(Sv) != len(Su) + 1:
                    return False
                if not all(u in Sv for u in Su):
                    return False
                return all((v * iu).has_descent(x, positive=True) for x in Su)

            vertices = [(u, u.inverse(),
                         tuple(set(u.reduced_word_reverse_iterator())))
                        for u in self]
            dg = DiGraph([vertices, covered_by])
            dg.relabel(lambda x: x[0])
            return Poset(dg, cover_relations=True)
Ejemplo n.º 11
0
    def Circulant(self,n,integers):
        r"""
        Returns a circulant digraph on `n` vertices from a set of integers.

        INPUT:

        - ``n`` (integer) -- number of vertices.

        - ``integers`` -- the list of integers such that there is an edge from
          `i` to `j` if and only if ``(j-i)%n in integers``.

        EXAMPLE::

            sage: digraphs.Circulant(13,[3,5,7])
            Circulant graph ([3, 5, 7]): Digraph on 13 vertices

        TESTS::

            sage: digraphs.Circulant(13,[3,5,7,"hey"])
            Traceback (most recent call last):
            ...
            ValueError: The list must contain only relative integers.
            sage: digraphs.Circulant(-2,[3,5,7,3])
            Traceback (most recent call last):
            ...
            ValueError: n must be a positive integer
            sage: digraphs.Circulant(3,[3,5,7,3.4])
            Traceback (most recent call last):
            ...
            ValueError: The list must contain only relative integers.
        """
        from sage.graphs.graph_plot import _circle_embedding
        from sage.rings.integer_ring import ZZ

        # Bad input and loops
        loops = False
        if not n in ZZ or n <= 0:
            raise ValueError("n must be a positive integer")

        for i in integers:
            if not i in ZZ:
                raise ValueError("The list must contain only relative integers.")
            if (i%n) == 0:
                loops = True

        G=DiGraph(n, name="Circulant graph ("+str(integers)+")", loops=loops)

        _circle_embedding(G, range(n))
        for v in range(n):
            G.add_edges([(v,(v+j)%n) for j in integers])

        return G
Ejemplo n.º 12
0
    def as_graph(self):
        r"""
        Return the graph associated to self
        """
        from sage.graphs.digraph import DiGraph

        G = DiGraph(multiedges=True,loops=True)
        d = self.degree()
        g = [self.g_tuple(i) for i in xrange(4)]
        for i in xrange(d):
            for j in xrange(4):
                G.add_edge(i,g[j][i],j)
        return G
Ejemplo n.º 13
0
 def __init__(self, data = None, arcs = None, edges = None,
              multiedges = True, loops = True, **kargs):
     init = True
     if data is None:
         if edges is None:
             edges = []
         if arcs is None:
             arcs = []
     else:
         if isinstance(data, MixedGraph):
             if edges is not None or arcs is not None:
                 raise ValueError("Edges or arcs should not be specified with a MixedGraph")
             self._edges = data._edges
             self._arcs = data._arcs
             init = False
         elif isinstance(data, Graph):
             if edges is not None:
                 raise ValueError("Edges should not be specified with a Graph")
             edges = data.edges(labels=False)
             if arcs is None:
                 arcs = []
         elif isinstance(data, DiGraph):
             if arcs is not None:
                 raise ValueError("Arcs should not be specified with a DiGraph")
             arcs = data.edges(labels=False)
             if edges is None:
                 edges = []
         elif arcs is not None and edges is None:
             edges = data
             data = None
         else:
             if edges is not None or arcs is not None:
                 raise ValueError("Edges or arcs should not be specified with other data")
             self._edges = []
             self._arcs = []
             for i, e in enumerate(data):
                 u, v = e
                 if isinstance(e, (set, frozenset, Set_generic)):
                     self._edges.append((u, v, i))
                 else:
                     self._arcs.append((u, v, i))
             init = False
     if init:
         n = len(edges)
         self._edges = [(u, v, i) for i, (u, v) in enumerate(edges)]
         self._arcs = [(u, v, i+n) for i, (u, v) in enumerate(arcs)]
     DiGraph.__init__(self, self._edges + self._arcs,
                      multiedges = multiedges, loops = loops, **kargs)
     if isinstance(data, GenericGraph) and data._pos is not None and \
         kargs.setdefault('pos', None) is None and len(data) == len(self):
             self._pos = data._pos
Ejemplo n.º 14
0
    def add_edge(self, i, j, label=1):
        """
        EXAMPLES::

            sage: from sage.combinat.root_system.dynkin_diagram import DynkinDiagram_class
            sage: d = DynkinDiagram_class(CartanType(['A',3]))
            sage: list(sorted(d.edges()))
            []
            sage: d.add_edge(2, 3)
            sage: list(sorted(d.edges()))
            [(2, 3, 1), (3, 2, 1)]
        """
        DiGraph.add_edge(self, i, j, label)
        if not self.has_edge(j,i):
            self.add_edge(j,i,1)
Ejemplo n.º 15
0
    def __getitem__(self, i):
        r"""
        With a tuple (i,j) as argument, returns the scalar product
        `\langle
                \alpha^\vee_i, \alpha_j\rangle`.

        Otherwise, behaves as the usual DiGraph.__getitem__

        EXAMPLES: We use the `C_4` dynkin diagram as a cartan
        matrix::

            sage: g = DynkinDiagram(['C',4])
            sage: matrix([[g[i,j] for j in range(1,5)] for i in range(1,5)])
            [ 2 -1  0  0]
            [-1  2 -1  0]
            [ 0 -1  2 -2]
            [ 0  0 -1  2]

        The neighbors of a node can still be obtained in the usual way::

            sage: [g[i] for i in range(1,5)]
            [[2], [1, 3], [2, 4], [3]]
        """
        if not isinstance(i, tuple):
            return DiGraph.__getitem__(self,i)
        [i,j] = i
        if i == j:
            return 2
        elif self.has_edge(j, i):
            return -self.edge_label(j, i)
        else:
            return 0
Ejemplo n.º 16
0
    def YoungsLatticePrincipalOrderIdeal(lam):
        """
        Return the principal order ideal of the
        partition `lam` in Young's Lattice.

        INPUT:

        - ``lam`` -- a partition

        EXAMPLES::

            sage: P = Posets.YoungsLatticePrincipalOrderIdeal(Partition([2,2]))
            sage: P
            Finite lattice containing 6 elements
            sage: P.cover_relations()
            [[[], [1]],
             [[1], [1, 1]],
             [[1], [2]],
             [[1, 1], [2, 1]],
             [[2], [2, 1]],
             [[2, 1], [2, 2]]]
        """
        from sage.misc.flatten import flatten
        from sage.combinat.partition import Partition

        def lower_covers(l):
            """
            Nested function returning those partitions obtained
            from the partition `l` by removing
            a single cell.
            """
            return [l.remove_cell(c[0], c[1]) for c in l.removable_cells()]

        def contained_partitions(l):
            """
            Nested function returning those partitions contained in
            the partition `l`
            """
            if l == Partition([]):
                return l
            return flatten([l, [contained_partitions(m)
                                for m in lower_covers(l)]])

        ideal = list(set(contained_partitions(lam)))
        H = DiGraph(dict([[p, lower_covers(p)] for p in ideal]))
        return LatticePoset(H.reverse())
Ejemplo n.º 17
0
    def Path(self,n):
        r"""
        Returns a directed path on `n` vertices.

        INPUT:

        - ``n`` (integer) -- number of vertices in the path.

        EXAMPLES::

            sage: g = digraphs.Path(5)
            sage: g.vertices()
            [0, 1, 2, 3, 4]
            sage: g.size()
            4
            sage: g.automorphism_group().cardinality()
            1
        """
        g = DiGraph(n)
        g.name("Path")

        if n:
            g.add_path(range(n))

        g.set_pos({i:(i,0) for i in range(n)})
        return g
Ejemplo n.º 18
0
    def Path(self,n):
        r"""
        Returns a directed path on `n` vertices.

        INPUT:

        - ``n`` (integer) -- number of vertices in the path.

        EXAMPLES::

            sage: g = digraphs.Path(5)
            sage: g.vertices()
            [0, 1, 2, 3, 4]
            sage: g.size()
            4
            sage: g.automorphism_group().cardinality()
            1
        """
        if n<0:
            raise ValueError("The number of vertices must be a positive integer.")

        g = DiGraph()
        g.name("Path on "+str(n)+" vertices")

        if n:
            g.add_path(range(n))

        g.set_pos({i:(i,0) for i in range(n)})
        return g
 def quiver_v2(self):
     #if hasattr(self, "_quiver_cache"):
     #    return self._quiver_cache
     from sage.combinat.subset import Subsets
     from sage.graphs.digraph import DiGraph
     Q = DiGraph(multiedges=True)
     Q.add_vertices(self.j_transversal())
     g = self.associated_graph()
     for U in Subsets(g.vertices()):
         for W in Subsets(U):
             h = g.subgraph(U.difference(W))
             n = h.connected_components_number() - 1
             if n > 0:
                 u = self.j_class_representative(self.j_class_index(self(''.join(U))))
                 w = self.j_class_representative(self.j_class_index(self(''.join(W))))
                 for i in range(n):
                     Q.add_edge(w, u, i)
     return Q
Ejemplo n.º 20
0
    def __init__(self, t = None):
        """
        INPUT:

        - ``t`` -- a Cartan type or ``None``

        EXAMPLES::

            sage: d = DynkinDiagram(["A", 3])
            sage: d == loads(dumps(d))
            True

        Implementation note: if a Cartan type is given, then the nodes
        are initialized from the index set of this Cartan type.
        """
        DiGraph.__init__(self)
        self._cartan_type = t
        if t is not None:
            self.add_vertices(t.index_set())
Ejemplo n.º 21
0
    def TransitiveTournament(self, n):
        r"""
        Returns a transitive tournament on `n` vertices.

        In this tournament there is an edge from `i` to `j` if `i<j`.

        See :wikipedia:`Tournament_(graph_theory)`

        INPUT:

        - ``n`` (integer) -- number of vertices in the tournament.

        EXAMPLES::

            sage: g = digraphs.TransitiveTournament(5)
            sage: g.vertices()
            [0, 1, 2, 3, 4]
            sage: g.size()
            10
            sage: g.automorphism_group().cardinality()
            1

        TESTS::

            sage: digraphs.TransitiveTournament(-1)
            Traceback (most recent call last):
            ...
            ValueError: The number of vertices cannot be strictly negative!
        """
        g = DiGraph(n)
        g.name("Transitive Tournament")

        for i in range(n - 1):
            for j in range(i + 1, n):
                g.add_edge(i, j)

        if n:
            from sage.graphs.graph_plot import _circle_embedding

            _circle_embedding(g, range(n))

        return g
Ejemplo n.º 22
0
def shard_preorder_graph(runs):
    """
    Return the preorder attached to a tuple of decreasing runs.

    This is a directed graph, whose vertices correspond to the runs.

    There is an edge from a run `R` to a run `S` if `R` is before `S`
    in the list of runs and the two intervals defined by the initial and
    final indices of `R` and `S` overlap.

    This only depends on the initial and final indices of the runs.
    For this reason, this input can also be given in that shorten way.

    INPUT:

    - a tuple of tuples, the runs of a permutation, or

    - a tuple of pairs `(i,j)`, each one standing for a run from `i` to `j`.

    OUTPUT:

    a directed graph, with vertices labelled by integers

    EXAMPLES::

        sage: from sage.combinat.shard_order import shard_preorder_graph
        sage: s = Permutation([2,8,3,9,6,4,5,1,7])
        sage: def cut(lr):
        ....:     return tuple((r[0], r[-1]) for r in lr)
        sage: shard_preorder_graph(cut(s.decreasing_runs()))
        Digraph on 5 vertices
        sage: s = Permutation([9,4,3,2,8,6,5,1,7])
        sage: P = shard_preorder_graph(s.decreasing_runs())
        sage: P.is_isomorphic(digraphs.TransitiveTournament(3))
        True
    """
    N = len(runs)
    dg = DiGraph(N)
    dg.add_edges((i, j) for i in range(N - 1)
                 for j in range(i + 1, N)
                 if runs[i][-1] < runs[j][0] and runs[j][-1] < runs[i][0])
    return dg
    def attach(self, *graph_attachments):
        """
        Attach graphs to the ground vertices in the specified manner.

        INPUT:

        - ``graph_attachments`` -- a list of (graph, attachment) pairs, where
          an attachment is a source : destination dictionary of of vertices.
          The length of the list should equal the number of ground vertices.
          The graphs should all have distinct ground vertices.

        OUTPUT:

        The Kontsevich graph constructed by attaching the input graphs to the
        ground vertices in the manner specified. The new ground vertices are
        the concatenation of the input ground vertices.
        """
        graphs, attachments = zip(*graph_attachments)
        assert len(graph_attachments) == len(self.ground_vertices())
        assert all(g.internal_vertices_normalized() for g in graphs)
        assert all(isinstance(a, dict) for a in attachments)
        assert len(graphs) == 1 or \
            not set.intersection(*[set(g.ground_vertices()) for g in graphs])
        import operator
        new_ground = reduce(operator.add, [g.ground_vertices() for g in graphs])
        # Compute the offsets at which the numbering of the attachments'
        # internal vertices should start.
        sizes = [len(g.internal_vertices()) for g in graphs]
        offsets = [len(self.internal_vertices()) + p \
                   for p in partial_sums(sizes)]
        # Relabel attachments.
        attachment_graphs = [graphs[n].relabel({v : v + offsets[n] \
                               for v in graphs[n].internal_vertices()}, \
                               inplace=False) \
                             for n in range(0, len(self.ground_vertices()))]
        # Compute new attachment points.
        attachment_points = [{src : dst + offsets[n] \
                                    if dst in graphs[n].internal_vertices()
                                    else dst
                              for (src, dst) in attachments[n].iteritems()}
                             for n in range(0, len(self.ground_vertices()))]
        G = self.copy(immutable=False)
        G.relabel({v : 'old %s' % v for v in G.ground_vertices()})
        old_ground = G.ground_vertices()
        for g in attachment_graphs:
            G = DiGraph.union(G,g)
        for n in range(0, len(self.ground_vertices())):
            for edge_in in G.incoming_edges(old_ground[n]):
                src = edge_in[0]
                label = edge_in[2]
                G.add_edge(src, attachment_points[n][src], label)
            G.delete_vertex(old_ground[n])
        return KontsevichGraph(G, ground_vertices=new_ground, immutable=True)
    def plot(self, **kwargs):
        """
        Return a graphics object representing the Kontsevich graph.

        INPUT:

        - ``edge_labels`` (boolean, default True) -- if True, show edge labels.
        - ``indices`` (boolean, default False) -- if True, show indices as
          edge labels instead of L and R; see :meth:`._latex_`.
        - ``upright`` (boolean, default False) -- if True, try to plot the
          graph with the ground vertices on the bottom and the rest on top.
        """
        if not 'edge_labels' in kwargs:
            kwargs['edge_labels'] = True        # show edge labels by default
        if 'indices' in kwargs:
            del kwargs['indices']
            KG = DiGraph(self)
            for (k,e) in enumerate(self.edges()):
                KG.delete_edge(e)
                KG.add_edge((e[0], e[1], chr(97 + k)))
            return KG.plot(**kwargs)
        if len(self.ground_vertices()) == 2 and 'upright' in kwargs:
            del kwargs['upright']
            kwargs['save_pos'] = True
            DiGraph.plot(self, **kwargs)
            positions = self.get_pos()
            # translate F to origin:
            F_pos = vector(positions[self.ground_vertices()[0]])
            for p in positions:
                positions[p] = list(vector(positions[p]) - F_pos)
            # scale F - G distance to 1:
            G_len = abs(vector(positions[self.ground_vertices()[1]]))
            for p in positions:
                positions[p] = list(vector(positions[p])/G_len)
            # rotate the vector F - G to (1,0)
            from math import atan2
            theta = -atan2(positions[self.ground_vertices()[1]][1], positions[self.ground_vertices()[1]][0])
            for p in positions:
                positions[p] = list(matrix([[cos(theta),-(sin(theta))],[sin(theta),cos(theta)]]) * vector(positions[p]))
            # flip if most things are below the x-axis:
            if len([(x,y) for (x,y) in positions.values() if y < 0])/len(self.internal_vertices()) > 0.5:
                for p in positions:
                    positions[p] = [positions[p][0], -positions[p][1]]
        return DiGraph.plot(self, **kwargs)
Ejemplo n.º 25
0
    def digraph(self):
        r"""
        Return a DiGraph representation of this Kleber tree.

        EXAMPLES::

            sage: KT = KleberTree(['D', 4], [[2, 2]])
            sage: KT.digraph()
            Digraph on 3 vertices
        """
        d = {}
        for x in self:
            d[x] = {}
            if x.parent_node is None:
                continue
            d[x][x.parent_node] = tuple(x.up_root.to_vector())
        G = DiGraph(d)

        if have_dot2tex():
            G.set_latex_options(format="dot2tex", edge_labels=True)
                                # edge_options = lambda (u,v,label): ({"backward":label ==0}))
        return G
Ejemplo n.º 26
0
    def relabel(self, relabelling, inplace=False, **kwds):
        """
        Return the relabelling Dynkin diagram of ``self``.

        EXAMPLES::

            sage: D = DynkinDiagram(['C',3])
            sage: D.relabel({1:0, 2:4, 3:1})
            O---O=<=O
            0   4   1
            C3 relabelled by {1: 0, 2: 4, 3: 1}
            sage: D
            O---O=<=O
            1   2   3
            C3

            sage: D = DynkinDiagram(['A', [1,2]])
            sage: Dp = D.relabel({-1:4, 0:-3, 1:3, 2:2}); Dp
            O---X---O---O
            4   -3  3   2
            A1|2 relabelled by {0: -3, 1: 3, 2: 2, -1: 4}
            sage: Dp.odd_isotropic_roots()
            (-3,)
        """
        if inplace:
            DiGraph.relabel(self, relabelling, inplace, **kwds)
            G = self
        else:
            # We must make a copy of ourselves first because of DiGraph's
            #   relabel default behavior is to do so in place, and if not
            #   then it recurses on itself with no argument for inplace
            G = self.copy().relabel(relabelling, inplace=True, **kwds)
        if isinstance(relabelling, dict):
            relabelling = relabelling.__getitem__
        new_odds = [relabelling(i) for i in self._odd_isotropic_roots]
        G._odd_isotropic_roots = tuple(new_odds)
        if self._cartan_type is not None:
            G._cartan_type = self._cartan_type.relabel(relabelling)
        return G
Ejemplo n.º 27
0
    def __init__(self, t = None, **options):
        """
        INPUT:

        - ``t`` -- a Cartan type or ``None``

        EXAMPLES::

            sage: d = DynkinDiagram(["A", 3])
            sage: TestSuite(d).run()

        Check that the correct type is returned when copied::

            sage: d = DynkinDiagram(["A", 3])
            sage: type(copy(d))
            <class 'sage.combinat.root_system.dynkin_diagram.DynkinDiagram_class'>

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

            sage: cd = copy(d)
            sage: cd.add_vertex(4)
            sage: d.vertices() != cd.vertices()
            True

        Implementation note: if a Cartan type is given, then the nodes
        are initialized from the index set of this Cartan type.
        """
        if isinstance(t, DiGraph):
            if isinstance(t, DynkinDiagram_class):
                self._cartan_type = t._cartan_type
            else:
                self._cartan_type = None
            DiGraph.__init__(self, data=t, **options)
            return

        DiGraph.__init__(self, **options)
        self._cartan_type = t
        if t is not None:
            self.add_vertices(t.index_set())
Ejemplo n.º 28
0
    def _digraph(self):
        r"""
        Constructs the underlying digraph and stores the result as an
        attribute.

        EXAMPLES::
        
            sage: from sage.combinat.yang_baxter_graph import SwapIncreasingOperator
            sage: ops = [SwapIncreasingOperator(i) for i in range(2)]
            sage: Y = YangBaxterGraph(root=(1,2,3), operators=ops)
            sage: Y._digraph
            Digraph on 6 vertices
        """
        digraph = DiGraph()
        digraph.add_vertex(self._root)
        queue = [self._root]
        while queue:
            u = queue.pop()
            for (v, l) in self._succesors(u):
                if v not in digraph:
                    queue.append(v)
                digraph.add_edge(u, v, l)
        return digraph
Ejemplo n.º 29
0
 def plot(self, **kwds):
     """
     EXAMPLES::
 
         sage: X = gauge_theories.threeSU(3)
         sage: print X.plot().description()
     """
     g = DiGraph(loops=True, sparse=True, multiedges=True)
     for G in self._gauge_groups:
         g.add_vertex(G)
     for field in self._fields:
         if isinstance(field, FieldBifundamental):
             g.add_edge(field.src, field.dst, field)
         if isinstance(field, FieldAdjoint):
             g.add_edge(field.node, field.node, field)
     return g.plot(vertex_labels=True, edge_labels=True, graph_border=True)
Ejemplo n.º 30
0
    def IntegerPartitions(n):
        """
        Returns the poset of integer partitions on the integer ``n``.

        A partition of a positive integer `n` is a non-increasing list
        of positive integers that sum to `n`. If `p` and `q` are
        integer partitions of `n`, then `p` covers `q` if and only
        if `q` is obtained from `p` by joining two parts of `p`
        (and sorting, if necessary).

        EXAMPLES::

            sage: P = Posets.IntegerPartitions(7); P
            Finite poset containing 15 elements
            sage: len(P.cover_relations())
            28
        """
        def lower_covers(partition):
            r"""
            Nested function for computing the lower covers
            of elements in the poset of integer partitions.
            """
            lc = []
            for i in range(0,len(partition)-1):
                for j in range(i+1,len(partition)):
                    new_partition = partition[:]
                    del new_partition[j]
                    del new_partition[i]
                    new_partition.append(partition[i]+partition[j])
                    new_partition.sort(reverse=True)
                    tup = tuple(new_partition)
                    if tup not in lc:
                        lc.append(tup)
            return lc
        from sage.combinat.partition import Partitions
        H = DiGraph(dict([[tuple(p),lower_covers(p)] for p in Partitions(n)]))
        return Poset(H.reverse())
Ejemplo n.º 31
0
class RCToKRTBijectionTypeDTwisted(RCToKRTBijectionTypeD,
                                   RCToKRTBijectionTypeA2Even):
    r"""
    Specific implementation of the bijection from rigged configurations to
    tensor products of KR tableaux for type `D_{n+1}^{(2)}`.
    """
    def run(self, verbose=False, build_graph=False):
        """
        Run the bijection from rigged configurations to tensor product of KR
        tableaux for type `D_{n+1}^{(2)}`.

        INPUT:

        - ``verbose`` -- (default: ``False``) display each step in the
          bijection
        - ``build_graph`` -- (default: ``False``) build the graph of each
          step of the bijection

        EXAMPLES::

            sage: RC = RiggedConfigurations(['D', 4, 2], [[3, 1]])
            sage: x = RC(partition_list=[[],[1],[1]])
            sage: from sage.combinat.rigged_configurations.bij_type_D_twisted import RCToKRTBijectionTypeDTwisted
            sage: RCToKRTBijectionTypeDTwisted(x).run()
            [[1], [3], [-2]]
            sage: bij = RCToKRTBijectionTypeDTwisted(x)
            sage: bij.run(build_graph=True)
            [[1], [3], [-2]]
            sage: bij._graph
            Digraph on 6 vertices
        """
        from sage.combinat.crystals.letters import CrystalOfLetters
        letters = CrystalOfLetters(
            self.rigged_con.parent()._cartan_type.classical())

        # This is technically bad, but because the first thing we do is append
        #   an empty list to ret_crystal_path, we correct this. We do it this
        #   way so that we do not have to remove an empty list after the
        #   bijection has been performed.
        ret_crystal_path = []

        for dim in self.rigged_con.parent().dims:
            ret_crystal_path.append([])

            # Iterate over each column
            for dummy_var in range(dim[1]):
                # Split off a new column if necessary
                if self.cur_dims[0][1] > 1:
                    self.cur_dims[0][1] -= 1
                    self.cur_dims.insert(0, [dim[0], 1])

                    # Perform the corresponding splitting map on rigged configurations
                    # All it does is update the vacancy numbers on the RC side
                    for a in range(self.n):
                        self._update_vacancy_numbers(a)

                    if build_graph:
                        y = self.rigged_con.parent()(
                            *[x._clone() for x in self.cur_partitions],
                            use_vacancy_numbers=True)
                        self._graph.append(
                            [self._graph[-1][1], (y, len(self._graph)), 'ls'])

                # Check to see if we are a spinor
                if dim[0] == self.n:
                    if verbose:
                        print("====================")
                        print(
                            repr(self.rigged_con.parent()(
                                *self.cur_partitions,
                                use_vacancy_numbers=True)))
                        print("--------------------")
                        print(ret_crystal_path)
                        print("--------------------\n")
                        print("Applying doubling map")
                    self.doubling_map()

                    if build_graph:
                        y = self.rigged_con.parent()(
                            *[x._clone() for x in self.cur_partitions],
                            use_vacancy_numbers=True)
                        self._graph.append(
                            [self._graph[-1][1], (y, len(self._graph)), '2x'])

                while self.cur_dims[0][0] > 0:
                    if verbose:
                        print("====================")
                        print(
                            repr(self.rigged_con.parent()(
                                *self.cur_partitions,
                                use_vacancy_numbers=True)))
                        print("--------------------")
                        print(ret_crystal_path)
                        print("--------------------\n")

                    self.cur_dims[0][0] -= 1  # This takes care of the indexing
                    b = self.next_state(self.cur_dims[0][0])

                    # Make sure we have a crystal letter
                    ret_crystal_path[-1].append(letters(b))  # Append the rank

                    if build_graph:
                        y = self.rigged_con.parent()(
                            *[x._clone() for x in self.cur_partitions],
                            use_vacancy_numbers=True)
                        self._graph.append([
                            self._graph[-1][1], (y, len(self._graph)),
                            letters(b)
                        ])

                self.cur_dims.pop(0)  # Pop off the leading column

                # Check to see if we were a spinor
                if dim[0] == self.n:
                    if verbose:
                        print("====================")
                        print(
                            repr(self.rigged_con.parent()(
                                *self.cur_partitions)))
                        print("--------------------")
                        print(ret_crystal_path)
                        print("--------------------\n")
                        print("Applying halving map")
                    self.halving_map()

                    if build_graph:
                        y = self.rigged_con.parent()(
                            *[x._clone() for x in self.cur_partitions],
                            use_vacancy_numbers=True)
                        self._graph.append([
                            self._graph[-1][1], (y, len(self._graph)), '1/2x'
                        ])

        if build_graph:
            self._graph.pop(0)  # Remove the dummy at the start
            from sage.graphs.digraph import DiGraph
            from sage.graphs.dot2tex_utils import have_dot2tex
            self._graph = DiGraph(self._graph)
            if have_dot2tex():
                self._graph.set_latex_options(format="dot2tex",
                                              edge_labels=True)

        return self.KRT(pathlist=ret_crystal_path)

    def next_state(self, height):
        r"""
        Build the next state for type `D_{n+1}^{(2)}`.

        TESTS::

            sage: RC = RiggedConfigurations(['D', 4, 2], [[2, 1]])
            sage: from sage.combinat.rigged_configurations.bij_type_D_twisted import RCToKRTBijectionTypeDTwisted
            sage: bijection = RCToKRTBijectionTypeDTwisted(RC(partition_list=[[2],[2,2],[2,2]]))
            sage: bijection.next_state(0)
            -1
        """
        n = self.n
        ell = [None] * (2 * n)
        case_S = [False] * n
        case_Q = False
        b = None

        # Calculate the rank and ell values

        last_size = 0
        for a in range(height, n - 1):
            ell[a] = self._find_singular_string(self.cur_partitions[a],
                                                last_size)

            if ell[a] is None:
                b = a + 1
                break
            else:
                last_size = self.cur_partitions[a][ell[a]]

        if b is None:
            partition = self.cur_partitions[n - 1]
            # Modified version of _find_singular_string()
            for i in reversed(range(len(partition))):
                if partition[i] >= last_size:
                    if partition.vacancy_numbers[i] == partition.rigging[i]:
                        if partition[i] == 1:
                            b = 'E'
                        else:
                            last_size = partition[i]
                        case_S[n - 1] = True
                        ell[2 * n - 1] = i
                        break
                    elif partition.vacancy_numbers[i] - 1 == partition.rigging[
                            i] and not case_Q:
                        case_Q = True
                        # Check if it is singular as well
                        block_size = partition[i]
                        for j in reversed(range(i)):
                            if partition[j] != block_size:
                                break
                            elif partition.vacancy_numbers[
                                    j] == partition.rigging[j]:
                                case_Q = False
                                break
                        if case_Q:
                            last_size = partition[i] + 1
                            ell[n - 1] = i

            if ell[2 * n - 1] is None:
                if not case_Q:
                    b = n
                else:
                    b = 0

        if b is None:
            # Now go back
            for a in reversed(range(n - 1)):
                if a >= height and self.cur_partitions[a][ell[a]] == last_size:
                    ell[n + a] = ell[a]
                    case_S[a] = True
                else:
                    ell[n + a] = self._find_singular_string(
                        self.cur_partitions[a], last_size)

                    if ell[n + a] is None:
                        b = -(a + 2)
                        break
                    else:
                        last_size = self.cur_partitions[a][ell[n + a]]

        if b is None:
            b = -1

        # Determine the new rigged configuration by removing boxes from the
        #   selected string and then making the new string singular
        if case_S[0]:
            row_num = None
            row_num_bar = self.cur_partitions[0].remove_cell(ell[n], 2)
        else:
            row_num = self.cur_partitions[0].remove_cell(ell[0])
            row_num_bar = self.cur_partitions[0].remove_cell(ell[n])
        for a in range(1, n - 1):
            if case_S[a]:
                row_num_next = None
                row_num_bar_next = self.cur_partitions[a].remove_cell(
                    ell[n + a], 2)
            else:
                row_num_next = self.cur_partitions[a].remove_cell(ell[a])
                row_num_bar_next = self.cur_partitions[a].remove_cell(ell[n +
                                                                          a])

            self._update_vacancy_numbers(a - 1)
            if row_num is not None:
                self.cur_partitions[a -
                                    1].rigging[row_num] = self.cur_partitions[
                                        a - 1].vacancy_numbers[row_num]
            if row_num_bar is not None:
                self.cur_partitions[
                    a - 1].rigging[row_num_bar] = self.cur_partitions[
                        a - 1].vacancy_numbers[row_num_bar]
            row_num = row_num_next
            row_num_bar = row_num_bar_next

        if case_Q:
            row_num_next = self.cur_partitions[n - 1].remove_cell(ell[n - 1])
            if case_S[n - 1]:
                row_num_bar_next = self.cur_partitions[n - 1].remove_cell(
                    ell[2 * n - 1])
            else:
                row_num_bar_next = None
        elif case_S[n - 1]:
            row_num_next = None
            row_num_bar_next = self.cur_partitions[n - 1].remove_cell(
                ell[2 * n - 1], 2)
        else:
            row_num_next = None
            row_num_bar_next = None

        self._update_vacancy_numbers(n - 2)
        if row_num is not None:
            self.cur_partitions[n - 2].rigging[row_num] = self.cur_partitions[
                n - 2].vacancy_numbers[row_num]
        if row_num_bar is not None:
            self.cur_partitions[n -
                                2].rigging[row_num_bar] = self.cur_partitions[
                                    n - 2].vacancy_numbers[row_num_bar]

        self._update_vacancy_numbers(n - 1)
        if height == n:
            self._correct_vacancy_nums()
        if row_num_next is not None:
            self.cur_partitions[n -
                                1].rigging[row_num_next] = self.cur_partitions[
                                    n - 1].vacancy_numbers[row_num_next]
        if row_num_bar_next is not None:
            if case_Q:
                vac_num = self.cur_partitions[
                    n - 1].vacancy_numbers[row_num_bar_next]
                self.cur_partitions[n - 1].rigging[row_num_bar_next] = vac_num
                block_len = self.cur_partitions[n - 1][row_num_bar_next]
                j = row_num_bar_next + 1
                length = len(self.cur_partitions[n - 1])
                # Find the place for the quasisingular rigging
                while j < length and self.cur_partitions[n-1][j] == block_len \
                  and self.cur_partitions[n-1].rigging[j] == vac_num:
                    j += 1
                self.cur_partitions[n - 1].rigging[j - 1] = vac_num - 1
            else:
                self.cur_partitions[
                    n - 1].rigging[row_num_bar_next] = self.cur_partitions[
                        n - 1].vacancy_numbers[row_num_bar_next]

        return (b)
Ejemplo n.º 32
0
    def DeBruijn(self,k,n):
        r"""
        Returns the De Bruijn diraph with parameters `k,n`.

        The De Bruijn digraph with parameters `k,n` is built
        upon a set of vertices equal to the set of words of
        length `n` from a dictionary of `k` letters.

        In this digraph, there is an arc `w_1w_2` if `w_2`
        can be obtained from `w_1` by removing the leftmost
        letter and adding a new letter at its right end.
        For more information, see the
        `Wikipedia article on De Bruijn graph
        <http://en.wikipedia.org/wiki/De_Bruijn_graph>`_.

        INPUT:

        - ``k`` -- Two possibilities for this parameter :
              - an integer equal to the cardinality of the
                alphabet to use.
              - An iterable object to be used as the set
                of letters
        - ``n`` -- An integer equal to the length of words in
          the De Bruijn digraph.

        EXAMPLES::

            sage: db=digraphs.DeBruijn(2,2); db
            De Bruijn digraph (k=2, n=2): Looped digraph on 4 vertices
            sage: db.order()
            4
            sage: db.size()
            8

        TESTS::

            sage: digraphs.DeBruijn(5,0)
            De Bruijn digraph (k=5, n=0): Looped multi-digraph on 1 vertex
            sage: digraphs.DeBruijn(0,0)
            De Bruijn digraph (k=0, n=0): Looped multi-digraph on 0 vertices

        """
        from sage.combinat.words.words import Words
        from sage.rings.integer import Integer

        W = Words(range(k) if isinstance(k, Integer) else k, n)
        A = Words(range(k) if isinstance(k, Integer) else k, 1)
        g = DiGraph(loops=True)

        if n == 0 :
            g.allow_multiple_edges(True)
            v = W[0]
            for a in A:
                g.add_edge(v.string_rep(), v.string_rep(), a.string_rep())
        else:
            for w in W:
                ww = w[1:]
                for a in A:
                    g.add_edge(w.string_rep(), (ww*a).string_rep(), a.string_rep())

        g.name( "De Bruijn digraph (k=%s, n=%s)"%(k,n) )
        return g
Ejemplo n.º 33
0
    def run(self, verbose=False, build_graph=False):
        """
        Run the bijection from rigged configurations to tensor product of KR
        tableaux for type `B_n^{(1)}`.

        INPUT:

        - ``verbose`` -- (default: ``False``) display each step in the
          bijection
        - ``build_graph`` -- (default: ``False``) build the graph of each
          step of the bijection

        EXAMPLES::

            sage: RC = RiggedConfigurations(['B', 3, 1], [[2, 1]])
            sage: from sage.combinat.rigged_configurations.bij_type_B import RCToKRTBijectionTypeB
            sage: RCToKRTBijectionTypeB(RC(partition_list=[[1],[1,1],[1]])).run()
            [[3], [0]]

            sage: RC = RiggedConfigurations(['B', 3, 1], [[3, 1]])
            sage: x = RC(partition_list=[[],[1],[1]])
            sage: RCToKRTBijectionTypeB(x).run()
            [[1], [3], [-2]]
            sage: bij = RCToKRTBijectionTypeB(x)
            sage: bij.run(build_graph=True)
            [[1], [3], [-2]]
            sage: bij._graph
            Digraph on 6 vertices
        """
        from sage.combinat.crystals.letters import CrystalOfLetters
        letters = CrystalOfLetters(self.rigged_con.parent()._cartan_type.classical())

        # This is technically bad, but because the first thing we do is append
        #   an empty list to ret_crystal_path, we correct this. We do it this
        #   way so that we do not have to remove an empty list after the
        #   bijection has been performed.
        ret_crystal_path = []

        for dim in self.rigged_con.parent().dims:
            ret_crystal_path.append([])

            # Check to see if we are a spinor
            if dim[0] == self.n:
                # Perform the spinor bijection by converting to type A_{2n-1}^{(2)}
                #   doing the bijection there and pulling back

                from sage.combinat.rigged_configurations.bij_type_A2_odd import RCToKRTBijectionTypeA2Odd
                from sage.combinat.rigged_configurations.rigged_configurations import RiggedConfigurations
                from sage.combinat.rigged_configurations.rigged_partition import RiggedPartition, RiggedPartitionTypeB
        
                # Convert to a type A_{2n-1}^{(2)} RC
                RC = RiggedConfigurations(['A', 2*self.n-1, 2], self.cur_dims)
                if verbose:
                    print("====================")
                    print(repr(RC(*self.cur_partitions, use_vacancy_numbers=True)))
                    print("--------------------")
                    print(ret_crystal_path)
                    print("--------------------\n")
                    print("Applying doubling map\n")
                # Convert the n-th partition into a regular rigged partition
                self.cur_partitions[-1] = RiggedPartition(self.cur_partitions[-1]._list,
                                                          self.cur_partitions[-1].rigging,
                                                          self.cur_partitions[-1].vacancy_numbers)

                bij = RCToKRTBijectionTypeA2Odd(RC(*self.cur_partitions, use_vacancy_numbers=True))
                for i in range(len(self.cur_dims)):
                    if bij.cur_dims[i][0] != self.n:
                        bij.cur_dims[i][1] *= 2
                for i in range(self.n-1):
                    for j in range(len(bij.cur_partitions[i])):
                        bij.cur_partitions[i]._list[j] *= 2
                        bij.cur_partitions[i].rigging[j] *= 2
                        bij.cur_partitions[i].vacancy_numbers[j] *= 2

                if build_graph:
                    y = self.rigged_con.parent()(*[x._clone() for x in self.cur_partitions], use_vacancy_numbers=True)
                    self._graph.append([self._graph[-1][1], (y, len(self._graph)), '2x'])

                # Perform the type A_{2n-1}^{(2)} bijection

                # Iterate over each column
                for dummy_var in range(dim[1]):
                    # Split off a new column if necessary
                    if bij.cur_dims[0][1] > 1:
                        bij.cur_dims[0][1] -= 1
                        bij.cur_dims.insert(0, [dim[0], 1])

                        # Perform the corresponding splitting map on rigged configurations
                        # All it does is update the vacancy numbers on the RC side
                        for a in range(self.n):
                            bij._update_vacancy_numbers(a)

                        if build_graph:
                            y = self.rigged_con.parent()(*[x._clone() for x in self.cur_partitions], use_vacancy_numbers=True)
                            self._graph.append([self._graph[-1][1], (y, len(self._graph)), 'ls'])

                    while bij.cur_dims[0][0]: # > 0:
                        if verbose:
                            print("====================")
                            print(repr(RC(*bij.cur_partitions, use_vacancy_numbers=True)))
                            print("--------------------")
                            print(ret_crystal_path)
                            print("--------------------\n")

                        ht = bij.cur_dims[0][0]
                        bij.cur_dims[0][0] = bij._next_index(ht)
                        b = bij.next_state(ht)
                        # Make sure we have a crystal letter
                        ret_crystal_path[-1].append(letters(b)) # Append the rank

                        if build_graph:
                            y = self.rigged_con.parent()(*[x._clone() for x in self.cur_partitions], use_vacancy_numbers=True)
                            self._graph.append([self._graph[-1][1], (y, len(self._graph)), letters(b)])

                    bij.cur_dims.pop(0) # Pop off the leading column

                self.cur_dims.pop(0) # Pop off the spin rectangle

                self.cur_partitions = bij.cur_partitions
                # Convert the n-th partition back into the special type B one
                self.cur_partitions[-1] = RiggedPartitionTypeB(self.cur_partitions[-1])

                # Convert back to a type B_n^{(1)}
                if verbose:
                    print("====================")
                    print(repr(self.rigged_con.parent()(*bij.cur_partitions, use_vacancy_numbers=True)))
                    print("--------------------")
                    print(ret_crystal_path)
                    print("--------------------\n")
                    print("Applying halving map\n")

                for i in range(self.n-1):
                    for j in range(len(self.cur_partitions[i])):
                        self.cur_partitions[i]._list[j] //= 2
                        self.cur_partitions[i].rigging[j] //= 2
                        self.cur_partitions[i].vacancy_numbers[j] //= 2

                if build_graph:
                    y = self.rigged_con.parent()(*[x._clone() for x in self.cur_partitions], use_vacancy_numbers=True)
                    self._graph.append([self._graph[-1][1], (y, len(self._graph)), '1/2x'])
            else:
                # Perform the regular type B_n^{(1)} bijection

                # Iterate over each column
                for dummy_var in range(dim[1]):
                    # Split off a new column if necessary
                    if self.cur_dims[0][1] > 1:
                        if verbose:
                            print("====================")
                            print(repr(self.rigged_con.parent()(*self.cur_partitions, use_vacancy_numbers=True)))
                            print("--------------------")
                            print(ret_crystal_path)
                            print("--------------------\n")
                            print("Applying column split")

                        self.cur_dims[0][1] -= 1
                        self.cur_dims.insert(0, [dim[0], 1])

                        # Perform the corresponding splitting map on rigged configurations
                        # All it does is update the vacancy numbers on the RC side
                        for a in range(self.n):
                            self._update_vacancy_numbers(a)

                        if build_graph:
                            y = self.rigged_con.parent()(*[x._clone() for x in self.cur_partitions], use_vacancy_numbers=True)
                            self._graph.append([self._graph[-1][1], (y, len(self._graph)), '2x'])

                    while self.cur_dims[0][0]: #> 0:
                        if verbose:
                            print("====================")
                            print(repr(self.rigged_con.parent()(*self.cur_partitions, use_vacancy_numbers=True)))
                            print("--------------------")
                            print(ret_crystal_path)
                            print("--------------------\n")

                        self.cur_dims[0][0] -= 1 # This takes care of the indexing
                        b = self.next_state(self.cur_dims[0][0])

                        # Make sure we have a crystal letter
                        ret_crystal_path[-1].append(letters(b)) # Append the rank

                        if build_graph:
                            y = self.rigged_con.parent()(*[x._clone() for x in self.cur_partitions], use_vacancy_numbers=True)
                            self._graph.append([self._graph[-1][1], (y, len(self._graph)), letters(b)])

                    self.cur_dims.pop(0) # Pop off the leading column

        if build_graph:
            self._graph.pop(0) # Remove the dummy at the start
            from sage.graphs.digraph import DiGraph
            from sage.graphs.dot2tex_utils import have_dot2tex
            self._graph = DiGraph(self._graph)
            if have_dot2tex():
                self._graph.set_latex_options(format="dot2tex", edge_labels=True)

        return self.KRT(pathlist=ret_crystal_path)
    def ButterflyGraph(self, n, vertices='strings'):
        """
        Returns a n-dimensional butterfly graph. The vertices consist of
        pairs (v,i), where v is an n-dimensional tuple (vector) with binary
        entries (or a string representation of such) and i is an integer in
        [0..n]. A directed edge goes from (v,i) to (w,i+1) if v and w are
        identical except for possibly v[i] != w[i].

        A butterfly graph has `(2^n)(n+1)` vertices and
        `n2^{n+1}` edges.

        INPUT:


        -  ``vertices`` - 'strings' (default) or 'vectors',
           specifying whether the vertices are zero-one strings or actually
           tuples over GF(2).


        EXAMPLES::

            sage: digraphs.ButterflyGraph(2).edges(labels=False)
            [(('00', 0), ('00', 1)),
            (('00', 0), ('10', 1)),
            (('00', 1), ('00', 2)),
            (('00', 1), ('01', 2)),
            (('01', 0), ('01', 1)),
            (('01', 0), ('11', 1)),
            (('01', 1), ('00', 2)),
            (('01', 1), ('01', 2)),
            (('10', 0), ('00', 1)),
            (('10', 0), ('10', 1)),
            (('10', 1), ('10', 2)),
            (('10', 1), ('11', 2)),
            (('11', 0), ('01', 1)),
            (('11', 0), ('11', 1)),
            (('11', 1), ('10', 2)),
            (('11', 1), ('11', 2))]
            sage: digraphs.ButterflyGraph(2,vertices='vectors').edges(labels=False)
            [(((0, 0), 0), ((0, 0), 1)),
            (((0, 0), 0), ((1, 0), 1)),
            (((0, 0), 1), ((0, 0), 2)),
            (((0, 0), 1), ((0, 1), 2)),
            (((0, 1), 0), ((0, 1), 1)),
            (((0, 1), 0), ((1, 1), 1)),
            (((0, 1), 1), ((0, 0), 2)),
            (((0, 1), 1), ((0, 1), 2)),
            (((1, 0), 0), ((0, 0), 1)),
            (((1, 0), 0), ((1, 0), 1)),
            (((1, 0), 1), ((1, 0), 2)),
            (((1, 0), 1), ((1, 1), 2)),
            (((1, 1), 0), ((0, 1), 1)),
            (((1, 1), 0), ((1, 1), 1)),
            (((1, 1), 1), ((1, 0), 2)),
            (((1, 1), 1), ((1, 1), 2))]
        """
        # We could switch to Sage integers to handle arbitrary n.
        if vertices == 'strings':
            if n >= 31:
                raise NotImplementedError(
                    "vertices='strings' is only valid for n<=30.")
            from sage.graphs.generic_graph_pyx import binary
            butterfly = {}
            for v in xrange(2**n):
                for i in range(n):
                    w = v
                    w ^= (1 << i)  # push 1 to the left by i and xor with w
                    bv = binary(v)
                    bw = binary(w)
                    # pad and reverse the strings
                    padded_bv = ('0' * (n - len(bv)) + bv)[::-1]
                    padded_bw = ('0' * (n - len(bw)) + bw)[::-1]
                    butterfly[(padded_bv, i)] = [(padded_bv, i + 1),
                                                 (padded_bw, i + 1)]
        elif vertices == 'vectors':
            from sage.modules.free_module import VectorSpace
            from sage.rings.finite_rings.constructor import FiniteField
            from copy import copy
            butterfly = {}
            for v in VectorSpace(FiniteField(2), n):
                for i in xrange(n):
                    w = copy(v)
                    w[i] += 1  # Flip the ith bit
                    # We must call tuple since vectors are mutable.  To obtain
                    # a vector from the tuple t, just call vector(t).
                    butterfly[(tuple(v), i)] = [(tuple(v), i + 1),
                                                (tuple(w), i + 1)]
        else:
            raise NotImplementedError(
                "vertices must be 'strings' or 'vectors'.")
        return DiGraph(butterfly)
    def __call__(self,
                 vertices,
                 property=lambda x: True,
                 augment='edges',
                 size=None,
                 implementation='c_graph',
                 sparse=True):
        """
        Accesses the generator of isomorphism class representatives.
        Iterates over distinct, exhaustive representatives.

        INPUT:


        -  ``vertices`` - natural number

        -  ``property`` - any property to be tested on digraphs
           before generation.

        -  ``augment`` - choices:

        -  ``'vertices'`` - augments by adding a vertex, and
           edges incident to that vertex. In this case, all digraphs on up to
           n=vertices are generated. If for any digraph G satisfying the
           property, every subgraph, obtained from G by deleting one vertex
           and only edges incident to that vertex, satisfies the property,
           then this will generate all digraphs with that property. If this
           does not hold, then all the digraphs generated will satisfy the
           property, but there will be some missing.

        -  ``'edges'`` - augments a fixed number of vertices by
           adding one edge In this case, all digraphs on exactly n=vertices
           are generated. If for any graph G satisfying the property, every
           subgraph, obtained from G by deleting one edge but not the vertices
           incident to that edge, satisfies the property, then this will
           generate all digraphs with that property. If this does not hold,
           then all the digraphs generated will satisfy the property, but
           there will be some missing.

        -  ``implementation`` - which underlying implementation to use (see DiGraph?)

        -  ``sparse`` - ignored if implementation is not ``c_graph``

        EXAMPLES: Print digraphs on 2 or less vertices.

        ::

            sage: for D in digraphs(2, augment='vertices'):
            ...    print D
            ...
            Digraph on 0 vertices
            Digraph on 1 vertex
            Digraph on 2 vertices
            Digraph on 2 vertices
            Digraph on 2 vertices

        Print digraphs on 3 vertices.

        ::

            sage: for D in digraphs(3):
            ...    print D
            Digraph on 3 vertices
            Digraph on 3 vertices
            ...
            Digraph on 3 vertices
            Digraph on 3 vertices

        For more examples, see the class level documentation, or type

        ::

            sage: digraphs? # not tested

        REFERENCE:

        - Brendan D. McKay, Isomorph-Free Exhaustive generation.
          Journal of Algorithms Volume 26, Issue 2, February 1998,
          pages 306-324.
        """
        if size is not None:
            extra_property = lambda x: x.size() == size
        else:
            extra_property = lambda x: True
        if augment == 'vertices':
            from sage.graphs.graph_generators import canaug_traverse_vert
            g = DiGraph(implementation=implementation, sparse=sparse)
            for gg in canaug_traverse_vert(g, [],
                                           vertices,
                                           property,
                                           dig=True,
                                           implementation=implementation,
                                           sparse=sparse):
                if extra_property(gg):
                    yield gg
        elif augment == 'edges':
            from sage.graphs.graph_generators import canaug_traverse_edge
            g = DiGraph(vertices, implementation=implementation, sparse=sparse)
            gens = []
            for i in range(vertices - 1):
                gen = range(i)
                gen.append(i + 1)
                gen.append(i)
                gen += range(i + 2, vertices)
                gens.append(gen)
            for gg in canaug_traverse_edge(g,
                                           gens,
                                           property,
                                           dig=True,
                                           implementation=implementation,
                                           sparse=sparse):
                if extra_property(gg):
                    yield gg
        else:
            raise NotImplementedError()
    def DeBruijn(self, k, n, vertices='strings'):
        r"""
        Returns the De Bruijn digraph with parameters `k,n`.

        The De Bruijn digraph with parameters `k,n` is built upon a set of
        vertices equal to the set of words of length `n` from a dictionary of
        `k` letters.

        In this digraph, there is an arc `w_1w_2` if `w_2` can be obtained from
        `w_1` by removing the leftmost letter and adding a new letter at its
        right end.  For more information, see the
        :wikipedia:`Wikipedia article on De Bruijn graph <De_Bruijn_graph>`.

        INPUT:

        - ``k`` -- Two possibilities for this parameter :
              - An integer equal to the cardinality of the alphabet to use, that
                is the degree of the digraph to be produced.
              - An iterable object to be used as the set of letters. The degree
                of the resulting digraph is the cardinality of the set of
                letters.

        - ``n`` -- An integer equal to the length of words in the De Bruijn
          digraph when ``vertices == 'strings'``, and also to the diameter of
          the digraph.

        - ``vertices`` -- 'strings' (default) or 'integers', specifying whether
          the vertices are words build upon an alphabet or integers.

        EXAMPLES::

            sage: db=digraphs.DeBruijn(2,2); db
            De Bruijn digraph (k=2, n=2): Looped digraph on 4 vertices
            sage: db.order()
            4
            sage: db.size()
            8

        TESTS::

            sage: digraphs.DeBruijn(5,0)
            De Bruijn digraph (k=5, n=0): Looped multi-digraph on 1 vertex
            sage: digraphs.DeBruijn(0,0)
            De Bruijn digraph (k=0, n=0): Looped multi-digraph on 0 vertices
        """
        from sage.combinat.words.words import Words
        from sage.rings.integer import Integer

        W = Words(range(k) if isinstance(k, Integer) else k, n)
        A = Words(range(k) if isinstance(k, Integer) else k, 1)
        g = DiGraph(loops=True)

        if vertices == 'strings':
            if n == 0:
                g.allow_multiple_edges(True)
                v = W[0]
                for a in A:
                    g.add_edge(v.string_rep(), v.string_rep(), a.string_rep())
            else:
                for w in W:
                    ww = w[1:]
                    for a in A:
                        g.add_edge(w.string_rep(), (ww * a).string_rep(),
                                   a.string_rep())
        else:
            d = W.size_of_alphabet()
            g = digraphs.GeneralizedDeBruijn(d**n, d)

        g.name("De Bruijn digraph (k=%s, n=%s)" % (k, n))
        return g
    def Circuit(self, n):
        r"""
        Returns the circuit on `n` vertices

        The circuit is an oriented ``CycleGraph``

        EXAMPLE:

        A circuit is the smallest strongly connected digraph::

            sage: circuit = digraphs.Circuit(15)
            sage: len(circuit.strongly_connected_components()) == 1
            True
        """
        g = DiGraph(n)
        g.name("Circuit")

        if n == 0:
            return g
        elif n == 1:
            g.allow_loops(True)
            g.add_edge(0, 0)
            return g
        else:
            g.add_edges([(i, i + 1) for i in xrange(n - 1)])
            g.add_edge(n - 1, 0)
            return g
Ejemplo n.º 38
0
def test_sinks_then_source():
    G = DiGraph([(0, 1), (0, 2)], format='list_of_edges')
    ex = DiGraphExtended(G)
    assert ex.step('sink') == [0]
Ejemplo n.º 39
0
def test_no_sinks(type):
    G = DiGraph()
    G.add_cycle([0, 1, 2])
    ex = DiGraphExtended(G)
    with pytest.raises(RuntimeError):
        ex.step(type)
    def Kautz(self, k, D, vertices='strings'):
        r"""
        Returns the Kautz digraph of degree `d` and diameter `D`.

        The Kautz digraph has been defined in [Kautz68]_. The Kautz digraph of
        degree `d` and diameter `D` has `d^{D-1}(d+1)` vertices. This digraph is
        build upon a set of vertices equal to the set of words of length `D`
        from an alphabet of `d+1` letters such that consecutive letters are
        differents. There is an arc from vertex `u` to vertex `v` if `v` can be
        obtained from `u` by removing the leftmost letter and adding a new
        letter, distinct from the rightmost letter of `u`, at the right end.

        The Kautz digraph of degree `d` and diameter `D` is isomorphic to the
        digraph of Imase and Itoh [II83]_ of degree `d` and order
        `d^{D-1}(d+1)`.

        See also the
        :wikipedia:`Wikipedia article on Kautz Graphs <Kautz_graph>`.

        INPUTS:

        - ``k`` -- Two possibilities for this parameter :
            - An integer equal to the degree of the digraph to be produced, that
              is the cardinality minus one of the alphabet to use.
            - An iterable object to be used as the set of letters. The degree of
              the resulting digraph is the cardinality of the set of letters
              minus one.

        - ``D`` -- An integer equal to the diameter of the digraph, and also to
              the length of a vertex label when ``vertices == 'strings'``.

        - ``vertices`` -- 'strings' (default) or 'integers', specifying whether
                      the vertices are words build upon an alphabet or integers.


        EXAMPLES::

            sage: K = digraphs.Kautz(2, 3)
            sage: K.is_isomorphic(digraphs.ImaseItoh(12, 2), certify = True)
            (True, {'201': 5, '120': 9, '202': 4, '212': 7, '210': 6, '010': 0, '121': 8, '012': 1, '021': 2, '020': 3, '102': 10, '101': 11})

            sage: K = digraphs.Kautz([1,'a','B'], 2)
            sage: K.edges()
            [('1B', 'B1', '1'), ('1B', 'Ba', 'a'), ('1a', 'a1', '1'), ('1a', 'aB', 'B'), ('B1', '1B', 'B'), ('B1', '1a', 'a'), ('Ba', 'a1', '1'), ('Ba', 'aB', 'B'), ('a1', '1B', 'B'), ('a1', '1a', 'a'), ('aB', 'B1', '1'), ('aB', 'Ba', 'a')]

            sage: K = digraphs.Kautz([1,'aA','BB'], 2)
            sage: K.edges()
            [('1,BB', 'BB,1', '1'), ('1,BB', 'BB,aA', 'aA'), ('1,aA', 'aA,1', '1'), ('1,aA', 'aA,BB', 'BB'), ('BB,1', '1,BB', 'BB'), ('BB,1', '1,aA', 'aA'), ('BB,aA', 'aA,1', '1'), ('BB,aA', 'aA,BB', 'BB'), ('aA,1', '1,BB', 'BB'), ('aA,1', '1,aA', 'aA'), ('aA,BB', 'BB,1', '1'), ('aA,BB', 'BB,aA', 'aA')]


        TESTS:

        An exception is raised when the degree is less than one::

            sage: G = digraphs.Kautz(0, 2)
            Traceback (most recent call last):
            ...
            ValueError: Kautz digraphs are defined for degree at least one.

            sage: G = digraphs.Kautz(['a'], 2)
            Traceback (most recent call last):
            ...
            ValueError: Kautz digraphs are defined for degree at least one.

        An exception is raised when the diameter of the graph is less than one::

            sage: G = digraphs.Kautz(2, 0)
            Traceback (most recent call last):
            ...
            ValueError: Kautz digraphs are defined for diameter at least one.


        REFERENCE:

        .. [Kautz68] W. H. Kautz. Bounds on directed (d, k) graphs. Theory of
          cellular logic networks and machines, AFCRL-68-0668, SRI Project 7258,
          Final Rep., pp. 20-28, 1968.
        """
        if D < 1:
            raise ValueError(
                "Kautz digraphs are defined for diameter at least one.")

        from sage.combinat.words.words import Words
        from sage.rings.integer import Integer

        my_alphabet = Words(
            [str(i) for i in range(k + 1)] if isinstance(k, Integer) else k, 1)
        if my_alphabet.size_of_alphabet() < 2:
            raise ValueError(
                "Kautz digraphs are defined for degree at least one.")

        if vertices == 'strings':

            # We start building the set of vertices
            V = [i for i in my_alphabet]
            for i in range(D - 1):
                VV = []
                for w in V:
                    VV += [w * a for a in my_alphabet if not w.has_suffix(a)]
                V = VV

            # We now build the set of arcs
            G = DiGraph()
            for u in V:
                for a in my_alphabet:
                    if not u.has_suffix(a):
                        G.add_edge(u.string_rep(), (u[1:] * a).string_rep(),
                                   a.string_rep())

        else:
            d = my_alphabet.size_of_alphabet() - 1
            G = digraphs.ImaseItoh((d + 1) * (d**(D - 1)), d)

        G.name("Kautz digraph (k=%s, D=%s)" % (k, D))
        return G
Ejemplo n.º 41
0
def test_sinks_sources_simple():
    G = DiGraph([(0, 1)], format='list_of_edges')
    ex = DiGraphExtended(G)

    assert ex.sources() == [0]
    assert ex.sinks() == [1]
    def RandomPoset(n, p):
        r"""
        Generate a random poset on ``n`` vertices according to a
        probability ``p``.

        INPUT:

        - ``n`` - number of vertices, a non-negative integer

        - ``p`` - a probability, a real number between 0 and 1 (inclusive)

        OUTPUT:

        A poset on ``n`` vertices.  The construction decides to make an
        ordered pair of vertices comparable in the poset with probability
        ``p``, however a pair is not made comparable if it would violate
        the defining properties of a poset, such as transitivity.

        So in practice, once the probability exceeds a small number the
        generated posets may be very similar to a chain.  So to create
        interesting examples, keep the probability small, perhaps on the
        order of `1/n`.

        EXAMPLES::

            sage: Posets.RandomPoset(17,.15)
            Finite poset containing 17 elements

        TESTS::

            sage: Posets.RandomPoset('junk', 0.5)
            Traceback (most recent call last):
            ...
            TypeError: number of elements must be an integer, not junk

            sage: Posets.RandomPoset(-6, 0.5)
            Traceback (most recent call last):
            ...
            ValueError: number of elements must be non-negative, not -6

            sage: Posets.RandomPoset(6, 'garbage')
            Traceback (most recent call last):
            ...
            TypeError: probability must be a real number, not garbage

            sage: Posets.RandomPoset(6, -0.5)
            Traceback (most recent call last):
            ...
            ValueError: probability must be between 0 and 1, not -0.5
        """
        try:
            n = Integer(n)
        except TypeError:
            raise TypeError(
                "number of elements must be an integer, not {0}".format(n))
        if n < 0:
            raise ValueError(
                "number of elements must be non-negative, not {0}".format(n))
        try:
            p = float(p)
        except Exception:
            raise TypeError(
                "probability must be a real number, not {0}".format(p))
        if p < 0 or p > 1:
            raise ValueError(
                "probability must be between 0 and 1, not {0}".format(p))

        D = DiGraph(loops=False, multiedges=False)
        D.add_vertices(range(n))
        for i in range(n):
            for j in range(n):
                if random.random() < p:
                    D.add_edge(i, j)
                    if not D.is_directed_acyclic():
                        D.delete_edge(i, j)
        return Poset(D, cover_relations=False)
Ejemplo n.º 43
0
    def run(self, verbose=False, build_graph=False):
        """
        Run the bijection from rigged configurations to tensor product of KR
        tableaux.

        INPUT:

        - ``verbose`` -- (default: ``False``) display each step in the
          bijection
        - ``build_graph`` -- (default: ``False``) build the graph of each
          step of the bijection

        EXAMPLES::

            sage: RC = RiggedConfigurations(['A', 4, 1], [[2, 1]])
            sage: x = RC(partition_list=[[1],[1],[1],[1]])
            sage: from sage.combinat.rigged_configurations.bij_type_A import RCToKRTBijectionTypeA
            sage: RCToKRTBijectionTypeA(x).run()
            [[2], [5]]
            sage: bij = RCToKRTBijectionTypeA(x)
            sage: bij.run(build_graph=True)
            [[2], [5]]
            sage: bij._graph
            Digraph on 3 vertices
        """
        from sage.combinat.crystals.letters import CrystalOfLetters
        letters = CrystalOfLetters(
            self.rigged_con.parent()._cartan_type.classical())

        # This is technically bad, but because the first thing we do is append
        #   an empty list to ret_crystal_path, we correct this. We do it this
        #   way so that we do not have to remove an empty list after the
        #   bijection has been performed.
        ret_crystal_path = []

        for dim in self.rigged_con.parent().dims:
            ret_crystal_path.append([])

            # Iterate over each column
            for dummy_var in range(dim[1]):
                # Split off a new column if necessary
                if self.cur_dims[0][1] > 1:
                    if verbose:
                        print("====================")
                        print(
                            repr(self.rigged_con.parent()(
                                *self.cur_partitions,
                                use_vacancy_numbers=True)))
                        print("--------------------")
                        print(ret_crystal_path)
                        print("--------------------\n")
                        print("Applying column split")

                    self.cur_dims[0][1] -= 1
                    self.cur_dims.insert(0, [dim[0], 1])

                    # Perform the corresponding splitting map on rigged configurations
                    # All it does is update the vacancy numbers on the RC side
                    for a in range(self.n):
                        self._update_vacancy_numbers(a)

                    if build_graph:
                        y = self.rigged_con.parent()(
                            *[x._clone() for x in self.cur_partitions],
                            use_vacancy_numbers=True)
                        self._graph.append(
                            [self._graph[-1][1], (y, len(self._graph)), 'ls'])

                while self.cur_dims[0][0] > 0:
                    if verbose:
                        print("====================")
                        print(
                            repr(self.rigged_con.parent()(
                                *self.cur_partitions,
                                use_vacancy_numbers=True)))
                        print("--------------------")
                        print(ret_crystal_path)
                        print("--------------------\n")

                    self.cur_dims[0][0] -= 1  # This takes care of the indexing
                    b = self.next_state(self.cur_dims[0][0])

                    # Make sure we have a crystal letter
                    ret_crystal_path[-1].append(letters(b))  # Append the rank

                    if build_graph:
                        y = self.rigged_con.parent()(
                            *[x._clone() for x in self.cur_partitions],
                            use_vacancy_numbers=True)
                        self._graph.append([
                            self._graph[-1][1], (y, len(self._graph)),
                            letters(b)
                        ])

                self.cur_dims.pop(0)  # Pop off the leading column

        if build_graph:
            self._graph.pop(0)  # Remove the dummy at the start
            from sage.graphs.digraph import DiGraph
            from sage.graphs.dot2tex_utils import have_dot2tex
            self._graph = DiGraph(self._graph)
            if have_dot2tex():
                self._graph.set_latex_options(format="dot2tex",
                                              edge_labels=True)

        # Basic check to make sure we end with the empty configuration
        #tot_len = sum([len(rp) for rp in self.cur_partitions])
        #if tot_len != 0:
        #    print "Invalid bijection end for:"
        #    print self.rigged_con
        #    print "-----------------------"
        #    print self.cur_partitions
        #    raise ValueError("Invalid bijection end")
        return self.KRT(pathlist=ret_crystal_path)
Ejemplo n.º 44
0
class RCToKRTBijectionAbstract:
    """
    Root abstract class for the bijection from rigged configurations to
    tensor product of Kirillov-Reshetikhin tableaux.

    This class holds the state of the bijection and generates the next state.
    This class should never be created directly.
    """
    def __init__(self, RC_element):
        """
        Initialize the bijection helper.

        INPUT:

        - ``RC_element`` -- The rigged configuration

        EXAMPLES::

            sage: RC = RiggedConfigurations(['A', 4, 1], [[2, 1]])
            sage: from sage.combinat.rigged_configurations.bij_abstract_class import RCToKRTBijectionAbstract
            sage: bijection = RCToKRTBijectionAbstract(RC(partition_list=[[1],[1],[1],[1]]))
            sage: TestSuite(bijection).run()
        """
        # Make a mutable clone of the rigged configuration for the bijection
        # This will be deleted when the bijection is completed
        self.rigged_con = RC_element.__copy__()
        self.n = RC_element.parent().cartan_type().classical().rank()
        self.KRT = RC_element.parent(
        ).tensor_product_of_kirillov_reshetikhin_tableaux()

        # Make a (deep) copy of the dimensions for the bijection
        self.cur_dims = [list(x[:]) for x in self.rigged_con.parent().dims]

        # Note that this implementation of the bijection is destructive to cur_partitions,
        #   therefore we will make a (deep) copy of the partitions.
        # TODO: Convert from cur_partitions to rigged_con
        self.cur_partitions = deepcopy(list(self.rigged_con)[:])

        # This is a dummy edge to start the process
        cp = RC_element.__copy__()
        cp.set_immutable()
        self._graph = [[[], (cp, 0)]]

        # Compute the current L matrix
#        self.L = {}
#        for dim in self.rigged_con.parent().dims:
#            if self.L.has_key(dim[0]):
#                row = self.L[dim[0]]
#                if row.has_key(dim[1]):
#                    row[dim[1]] += 1
#                else:
#                    row[dim[1]] = 1
#            else:
#                self.L[dim[0]] = {dim[1]:1}

    def __eq__(self, rhs):
        r"""
        Check equality.

        This is only here for pickling check. This is a temporary placeholder
        class, and as such, should never be compared.

        TESTS::

            sage: RC = RiggedConfigurations(['A', 4, 1], [[2, 1]])
            sage: from sage.combinat.rigged_configurations.bij_type_A import RCToKRTBijectionTypeA
            sage: bijection = RCToKRTBijectionTypeA(RC(partition_list=[[1],[1],[1],[1]]))
            sage: bijection2 = RCToKRTBijectionTypeA(RC(partition_list=[[1],[1],[1],[1]]))
            sage: bijection == bijection2
            True
        """
        return isinstance(rhs, RCToKRTBijectionAbstract)

    def run(self, verbose=False, build_graph=False):
        """
        Run the bijection from rigged configurations to tensor product of KR
        tableaux.

        INPUT:

        - ``verbose`` -- (default: ``False``) display each step in the
          bijection
        - ``build_graph`` -- (default: ``False``) build the graph of each
          step of the bijection

        EXAMPLES::

            sage: RC = RiggedConfigurations(['A', 4, 1], [[2, 1]])
            sage: x = RC(partition_list=[[1],[1],[1],[1]])
            sage: from sage.combinat.rigged_configurations.bij_type_A import RCToKRTBijectionTypeA
            sage: RCToKRTBijectionTypeA(x).run()
            [[2], [5]]
            sage: bij = RCToKRTBijectionTypeA(x)
            sage: bij.run(build_graph=True)
            [[2], [5]]
            sage: bij._graph
            Digraph on 3 vertices
        """
        from sage.combinat.crystals.letters import CrystalOfLetters
        letters = CrystalOfLetters(
            self.rigged_con.parent()._cartan_type.classical())

        # This is technically bad, but because the first thing we do is append
        #   an empty list to ret_crystal_path, we correct this. We do it this
        #   way so that we do not have to remove an empty list after the
        #   bijection has been performed.
        ret_crystal_path = []

        for dim in self.rigged_con.parent().dims:
            ret_crystal_path.append([])

            # Iterate over each column
            for dummy_var in range(dim[1]):
                # Split off a new column if necessary
                if self.cur_dims[0][1] > 1:
                    if verbose:
                        print("====================")
                        print(
                            repr(self.rigged_con.parent()(
                                *self.cur_partitions,
                                use_vacancy_numbers=True)))
                        print("--------------------")
                        print(ret_crystal_path)
                        print("--------------------\n")
                        print("Applying column split")

                    self.cur_dims[0][1] -= 1
                    self.cur_dims.insert(0, [dim[0], 1])

                    # Perform the corresponding splitting map on rigged configurations
                    # All it does is update the vacancy numbers on the RC side
                    for a in range(self.n):
                        self._update_vacancy_numbers(a)

                    if build_graph:
                        y = self.rigged_con.parent()(
                            *[x._clone() for x in self.cur_partitions],
                            use_vacancy_numbers=True)
                        self._graph.append(
                            [self._graph[-1][1], (y, len(self._graph)), 'ls'])

                while self.cur_dims[0][0] > 0:
                    if verbose:
                        print("====================")
                        print(
                            repr(self.rigged_con.parent()(
                                *self.cur_partitions,
                                use_vacancy_numbers=True)))
                        print("--------------------")
                        print(ret_crystal_path)
                        print("--------------------\n")

                    self.cur_dims[0][0] -= 1  # This takes care of the indexing
                    b = self.next_state(self.cur_dims[0][0])

                    # Make sure we have a crystal letter
                    ret_crystal_path[-1].append(letters(b))  # Append the rank

                    if build_graph:
                        y = self.rigged_con.parent()(
                            *[x._clone() for x in self.cur_partitions],
                            use_vacancy_numbers=True)
                        self._graph.append([
                            self._graph[-1][1], (y, len(self._graph)),
                            letters(b)
                        ])

                self.cur_dims.pop(0)  # Pop off the leading column

        if build_graph:
            self._graph.pop(0)  # Remove the dummy at the start
            from sage.graphs.digraph import DiGraph
            from sage.graphs.dot2tex_utils import have_dot2tex
            self._graph = DiGraph(self._graph)
            if have_dot2tex():
                self._graph.set_latex_options(format="dot2tex",
                                              edge_labels=True)

        # Basic check to make sure we end with the empty configuration
        #tot_len = sum([len(rp) for rp in self.cur_partitions])
        #if tot_len != 0:
        #    print "Invalid bijection end for:"
        #    print self.rigged_con
        #    print "-----------------------"
        #    print self.cur_partitions
        #    raise ValueError("Invalid bijection end")
        return self.KRT(pathlist=ret_crystal_path)

    @abstract_method
    def next_state(self, height):
        """
        Build the next state in the bijection.

        TESTS::

            sage: RC = RiggedConfigurations(['A', 4, 1], [[2, 1]])
            sage: from sage.combinat.rigged_configurations.bij_type_A import RCToKRTBijectionTypeA
            sage: bijection = RCToKRTBijectionTypeA(RC(partition_list=[[1],[1],[1],[1]]))
            sage: bijection.next_state(0)
            5
            sage: bijection.cur_partitions
            [(/)
            , (/)
            , (/)
            , (/)
            ]
        """

    def _update_vacancy_numbers(self, a):
        r"""
        Update the vacancy numbers during the bijection.

        INPUT:

        - ``a`` -- The index of the partition to update

        TESTS::

            sage: RC = RiggedConfigurations(['A', 4, 1], [[2, 1]])
            sage: from sage.combinat.rigged_configurations.bij_abstract_class import RCToKRTBijectionAbstract
            sage: bijection = RCToKRTBijectionAbstract(RC(partition_list=[[1],[1],[1],[1]]))
            sage: bijection._update_vacancy_numbers(2)
        """

        # Nothing to do if there the rigged partition is empty
        if len(self.cur_partitions[a]) == 0:
            return

        partition = self.cur_partitions[a]

        # Setup the first block
        block_len = partition[0]
        vac_num = self.rigged_con.parent()._calc_vacancy_number(
            self.cur_partitions, a, 0, dims=self.cur_dims)

        for i, row_len in enumerate(self.cur_partitions[a]):
            # If we've gone to a different sized block, then update the
            #   values which change when moving to a new block size
            if block_len != row_len:
                vac_num = self.rigged_con.parent()._calc_vacancy_number(
                    self.cur_partitions, a, i, dims=self.cur_dims)
                block_len = row_len

            partition.vacancy_numbers[i] = vac_num

    def _find_singular_string(self, partition, last_size):
        r"""
        Return the index of the singular string or ``None`` if not found.

        Helper method to find a singular string at least as long as
        ``last_size``.

        INPUT:

        - ``partition`` -- The partition to look in

        - ``last_size`` -- The last size found

        TESTS::

            sage: RC = RiggedConfigurations(['A', 4, 1], [[2, 1]])
            sage: from sage.combinat.rigged_configurations.bij_abstract_class import RCToKRTBijectionAbstract
            sage: bijection = RCToKRTBijectionAbstract(RC(partition_list=[[1],[1],[1],[1]]))
            sage: bijection._find_singular_string(bijection.cur_partitions[2], 2)
            sage: bijection._find_singular_string(bijection.cur_partitions[2], 0)
            0
        """
        for i in reversed(range(0, len(partition))):
            if partition[i] >= last_size and \
              partition.vacancy_numbers[i] == partition.rigging[i]:
                return i
Ejemplo n.º 45
0
 def sageView(self):
     """ Visualise l'arbre dans sage """
     from sage.graphs.digraph import DiGraph
     g = DiGraph(self.sageGraph())
     p = g.plot(layout='tree')
     p.show()
Ejemplo n.º 46
0
    def markov_chain_digraph(self, action='promotion', labeling='identity'):
        r"""
        Returns the digraph of the action of generalized promotion or tau on ``self``

        INPUT:

        - ``action`` -- 'promotion' or 'tau' (default: 'promotion')
        - ``labeling`` -- 'identity' or 'source' (default: 'identity')

        .. todo::

            - generalize this feature by accepting a family of operators as input
            - move up in some appropriate category

        This method creates a graph with vertices being the linear extensions of a given finite
        poset and an edge from `\pi` to `\pi'` if `\pi' = \pi \partial_i` where `\partial_i` is
        the promotion operator (see :meth:`promotion`) if ``action`` is set to ``promotion``
        and `\tau_i` (see :meth:`tau`) if ``action`` is set to ``tau``. The label of the edge
        is `i` (resp. `\pi_i`) if ``labeling`` is set to ``identity`` (resp. ``source``).

        EXAMPLES::

            sage: P = Poset(([1,2,3,4], [[1,3],[1,4],[2,3]]), linear_extension = True)
            sage: L = P.linear_extensions()
            sage: G = L.markov_chain_digraph(); G
            Looped multi-digraph on 5 vertices
            sage: sorted(G.vertices(), key = repr)
            [[1, 2, 3, 4], [1, 2, 4, 3], [1, 4, 2, 3], [2, 1, 3, 4], [2, 1, 4, 3]]
            sage: sorted(G.edges(), key = repr)
            [([1, 2, 3, 4], [1, 2, 3, 4], 4), ([1, 2, 3, 4], [1, 2, 4, 3], 2), ([1, 2, 3, 4], [1, 2, 4, 3], 3),
            ([1, 2, 3, 4], [2, 1, 4, 3], 1), ([1, 2, 4, 3], [1, 2, 3, 4], 3), ([1, 2, 4, 3], [1, 2, 4, 3], 4),
            ([1, 2, 4, 3], [1, 4, 2, 3], 2), ([1, 2, 4, 3], [2, 1, 3, 4], 1), ([1, 4, 2, 3], [1, 2, 3, 4], 1),
            ([1, 4, 2, 3], [1, 2, 3, 4], 2), ([1, 4, 2, 3], [1, 4, 2, 3], 3), ([1, 4, 2, 3], [1, 4, 2, 3], 4),
            ([2, 1, 3, 4], [1, 2, 4, 3], 1), ([2, 1, 3, 4], [2, 1, 3, 4], 4), ([2, 1, 3, 4], [2, 1, 4, 3], 2),
            ([2, 1, 3, 4], [2, 1, 4, 3], 3), ([2, 1, 4, 3], [1, 4, 2, 3], 1), ([2, 1, 4, 3], [2, 1, 3, 4], 2),
            ([2, 1, 4, 3], [2, 1, 3, 4], 3), ([2, 1, 4, 3], [2, 1, 4, 3], 4)]

            sage: G = L.markov_chain_digraph(labeling = 'source')
            sage: sorted(G.vertices(), key = repr)
            [[1, 2, 3, 4], [1, 2, 4, 3], [1, 4, 2, 3], [2, 1, 3, 4], [2, 1, 4, 3]]
            sage: sorted(G.edges(), key = repr)
            [([1, 2, 3, 4], [1, 2, 3, 4], 4), ([1, 2, 3, 4], [1, 2, 4, 3], 2), ([1, 2, 3, 4], [1, 2, 4, 3], 3),
            ([1, 2, 3, 4], [2, 1, 4, 3], 1), ([1, 2, 4, 3], [1, 2, 3, 4], 4), ([1, 2, 4, 3], [1, 2, 4, 3], 3),
            ([1, 2, 4, 3], [1, 4, 2, 3], 2), ([1, 2, 4, 3], [2, 1, 3, 4], 1), ([1, 4, 2, 3], [1, 2, 3, 4], 1),
            ([1, 4, 2, 3], [1, 2, 3, 4], 4), ([1, 4, 2, 3], [1, 4, 2, 3], 2), ([1, 4, 2, 3], [1, 4, 2, 3], 3),
            ([2, 1, 3, 4], [1, 2, 4, 3], 2), ([2, 1, 3, 4], [2, 1, 3, 4], 4), ([2, 1, 3, 4], [2, 1, 4, 3], 1),
            ([2, 1, 3, 4], [2, 1, 4, 3], 3), ([2, 1, 4, 3], [1, 4, 2, 3], 2), ([2, 1, 4, 3], [2, 1, 3, 4], 1),
            ([2, 1, 4, 3], [2, 1, 3, 4], 4), ([2, 1, 4, 3], [2, 1, 4, 3], 3)]

        The edges of the graph are by default colored using blue for
        edge 1, red for edge 2, green for edge 3, and yellow for edge 4::

            sage: view(G) # optional - dot2tex graphviz, not tested (opens external window)

        Alternatively, one may get the graph of the action of the ``tau`` operator::

            sage: G = L.markov_chain_digraph(action='tau'); G
            Looped multi-digraph on 5 vertices
            sage: sorted(G.vertices(), key = repr)
            [[1, 2, 3, 4], [1, 2, 4, 3], [1, 4, 2, 3], [2, 1, 3, 4], [2, 1, 4, 3]]
            sage: sorted(G.edges(), key = repr)
            [([1, 2, 3, 4], [1, 2, 3, 4], 2), ([1, 2, 3, 4], [1, 2, 4, 3], 3), ([1, 2, 3, 4], [2, 1, 3, 4], 1),
            ([1, 2, 4, 3], [1, 2, 3, 4], 3), ([1, 2, 4, 3], [1, 4, 2, 3], 2), ([1, 2, 4, 3], [2, 1, 4, 3], 1),
            ([1, 4, 2, 3], [1, 2, 4, 3], 2), ([1, 4, 2, 3], [1, 4, 2, 3], 1), ([1, 4, 2, 3], [1, 4, 2, 3], 3),
            ([2, 1, 3, 4], [1, 2, 3, 4], 1), ([2, 1, 3, 4], [2, 1, 3, 4], 2), ([2, 1, 3, 4], [2, 1, 4, 3], 3),
            ([2, 1, 4, 3], [1, 2, 4, 3], 1), ([2, 1, 4, 3], [2, 1, 3, 4], 3), ([2, 1, 4, 3], [2, 1, 4, 3], 2)]
            sage: view(G) # optional - dot2tex graphviz, not tested (opens external window)

        .. seealso:: :meth:`markov_chain_transition_matrix`, :meth:`promotion`, :meth:`tau`

        TESTS::

            sage: P = Poset(([1,2,3,4], [[1,3],[1,4],[2,3]]), linear_extension = True, facade = True)
            sage: L = P.linear_extensions()
            sage: G = L.markov_chain_digraph(labeling = 'source'); G
            Looped multi-digraph on 5 vertices
        """
        d = dict([x, dict([y, []] for y in self)] for x in self)
        if action == 'promotion':
            R = range(self.poset().cardinality())
        else:
            R = range(self.poset().cardinality() - 1)
        if labeling == 'source':
            for x in self:
                for i in R:
                    child = getattr(x, action)(i + 1)
                    d[x][child] += [self.poset().unwrap(x[i])]
        else:
            for x in self:
                for i in R:
                    child = getattr(x, action)(i + 1)
                    d[x][child] += [i + 1]
        G = DiGraph(d, format="dict_of_dicts")
        if have_dot2tex():
            G.set_latex_options(format="dot2tex",
                                edge_labels=True,
                                color_by_label={
                                    1: "blue",
                                    2: "red",
                                    3: "green",
                                    4: "yellow"
                                })
            #G.set_latex_options(format="dot2tex", edge_labels = True, color_by_label = {1:"green", 2:"blue", 3:"brown", 4:"red"})
        return G
    def GeneralizedDeBruijn(self, n, d):
        r"""
        Returns the generalized de Bruijn digraph of order `n` and degree `d`.

        The generalized de Bruijn digraph has been defined in [RPK80]_
        [RPK83]_. It has vertex set `V=\{0, 1,..., n-1\}` and there is an arc
        from vertex `u \in V` to all vertices `v \in V` such that
        `v \equiv (u*d + a) \mod{n}` with `0 \leq a < d`.

        When `n = d^{D}`, the generalized de Bruijn digraph is isomorphic to the
        de Bruijn digraph of degree `d` and diameter `D`.

        INPUTS:

        - ``n`` -- is the number of vertices of the digraph

        - ``d`` -- is the degree of the digraph

        .. SEEALSO::

            * :meth:`sage.graphs.generic_graph.GenericGraph.is_circulant` --
              checks whether a (di)graph is circulant, and/or returns all
              possible sets of parameters.

        EXAMPLE::

            sage: GB = digraphs.GeneralizedDeBruijn(8, 2)
            sage: GB.is_isomorphic(digraphs.DeBruijn(2, 3), certify = True)
            (True, {0: '000', 1: '001', 2: '010', 3: '011', 4: '100', 5: '101', 6: '110', 7: '111'})

        TESTS:

        An exception is raised when the degree is less than one::

            sage: G = digraphs.GeneralizedDeBruijn(2, 0)
            Traceback (most recent call last):
            ...
            ValueError: The generalized de Bruijn digraph is defined for degree at least one.

        An exception is raised when the order of the graph is less than one::

            sage: G = digraphs.GeneralizedDeBruijn(0, 2)
            Traceback (most recent call last):
            ...
            ValueError: The generalized de Bruijn digraph is defined for at least one vertex.


        REFERENCES:

        .. [RPK80] S. M. Reddy, D. K. Pradhan, and J. Kuhl. Directed graphs with
          minimal diameter and maximal connectivity, School Eng., Oakland Univ.,
          Rochester MI, Tech. Rep., July 1980.

        .. [RPK83] S. Reddy, P. Raghavan, and J. Kuhl. A Class of Graphs for
          Processor Interconnection. *IEEE International Conference on Parallel
          Processing*, pages 154-157, Los Alamitos, Ca., USA, August 1983.
        """
        if n < 1:
            raise ValueError(
                "The generalized de Bruijn digraph is defined for at least one vertex."
            )
        if d < 1:
            raise ValueError(
                "The generalized de Bruijn digraph is defined for degree at least one."
            )

        GB = DiGraph(loops=True)
        GB.allow_multiple_edges(True)
        for u in xrange(n):
            for a in xrange(u * d, u * d + d):
                GB.add_edge(u, a % n)

        GB.name("Generalized de Bruijn digraph (n=%s, d=%s)" % (n, d))
        return GB
Ejemplo n.º 48
0
    def uncompactify(self):
        r"""
        Returns the tree obtained from self by splitting edges so that they
        are labelled by exactly one letter. The resulting tree is
        isomorphic to the suffix trie.

        EXAMPLES::

            sage: from sage.combinat.words.suffix_trees import ImplicitSuffixTree, SuffixTrie
            sage: abbab = Words("ab")("abbab")
            sage: s = SuffixTrie(abbab)
            sage: t = ImplicitSuffixTree(abbab)
            sage: t.uncompactify().is_isomorphic(s.to_digraph())
            True
        """
        tree = self.to_digraph(word_labels=True)
        newtree = DiGraph()
        newtree.add_vertices(range(tree.order()))
        new_node = tree.order() + 1
        for (u, v, label) in tree.edge_iterator():
            if len(label) == 1:
                newtree.add_edge(u, v)
            else:
                newtree.add_edge(u, new_node, label[0])
                for w in label[1:-1]:
                    newtree.add_edge(new_node, new_node + 1, w)
                    new_node += 1
                newtree.add_edge(new_node, v, label[-1])
                new_node += 1
        return newtree
Ejemplo n.º 49
0
def class_graph(top, depth=5, name_filter=None, classes=None, as_graph=True):
    """
    Returns the class inheritance graph of a module, class, or object

    INPUT:

     - ``top`` -- the module, class, or object to start with (e.g. ``sage``, ``Integer``, ``3``)
     - ``depth`` -- maximal recursion depth within submodules (default: 5)
     - ``name_filter`` -- e.g. 'sage.rings' to only consider classes in :mod:`sage.rings`
     - ``classes`` -- optional dictionary to be filled in (it is also returned)
     - ``as_graph`` -- a boolean (default: True)

    OUTPUT:

     - An oriented graph, with class names as vertices, and an edge
       from each class to each of its bases.

    EXAMPLES:

    We construct the inheritance graph of the classes within a given module::

        sage: from sage.rings.polynomial.padics import polynomial_padic_capped_relative_dense, polynomial_padic_flat
        sage: G = class_graph(sage.rings.polynomial.padics); G
        Digraph on 6 vertices
        sage: G.vertices()
        ['Polynomial', 'Polynomial_generic_dense', 'Polynomial_generic_domain', 'Polynomial_padic', 'Polynomial_padic_capped_relative_dense', 'Polynomial_padic_flat']
        sage: G.edges(labels=False)
        [('Polynomial_padic', 'Polynomial'), ('Polynomial_padic_capped_relative_dense', 'Polynomial_generic_domain'), ('Polynomial_padic_capped_relative_dense', 'Polynomial_padic'), ('Polynomial_padic_flat', 'Polynomial_generic_dense'), ('Polynomial_padic_flat', 'Polynomial_padic')]

    We construct the inheritance graph of a given class::

        sage: class_graph(Parent).edges(labels=False)
        [('CategoryObject', 'SageObject'), ('Parent', 'CategoryObject'), ('SageObject', 'object')]

    We construct the inheritance graph of the class of an object::

        sage: class_graph([1,2,3]).edges(labels=False)
        [('list', 'object')]

    .. warning:: the output of ``class_graph`` used to be a dictionary
       mapping each class name to the list of names of its bases. This
       can be emulated by setting the option ``as_graph`` to ``False``::

        sage: class_graph(sage.rings.polynomial.padics, depth=2, as_graph=False)
        {'Polynomial_padic': ['Polynomial'],
        'Polynomial_padic_capped_relative_dense': ['Polynomial_generic_domain', 'Polynomial_padic'],
        'Polynomial_padic_flat': ['Polynomial_generic_dense', 'Polynomial_padic']}

    .. note:: the ``classes`` and ``as_graph`` options are mostly
       intended for internal recursive use.

    .. note:: ``class_graph`` does not yet handle nested classes

    TESTS::

        sage: G = class_graph(sage.rings.polynomial.padics, depth=2); G
        Digraph on 6 vertices
    """
    # This function descends recursively down the submodules of the
    # top module (if ``top`` is a module) and then down the hierarchy
    # of classes. Along the way, the result is stored in the "global"
    # dictionary ``classes`` which associates to each class the list
    # of its bases.

    # Termination
    if depth < 0:
        return classes

    # (first recursive call)
    if classes is None:
        classes = dict()

    # Build the list ``children`` of submodules (resp. base classes)
    # of ``top`` the function will recurse through
    if inspect.ismodule(top):
        if top.__name__.endswith('.all'):  # Ignore sage.rings.all and friends
            return classes
        if name_filter is None:
            name_filter = top.__name__
        children = [
            item for item in top.__dict__.values()
            if inspect.ismodule(item) or inspect.isclass(item)
        ]
        depth = depth - 1
    elif inspect.isclass(top):
        if name_filter is None:
            name_filter = ""
        if not top.__module__.startswith(name_filter):
            return classes
        children = top.__bases__
        classes[top.__name__] = [e.__name__ for e in children]
    else:  # top is a plain Python object; inspect its class
        children = [top.__class__]

    # Recurse
    for child in children:
        class_graph(child,
                    depth=depth,
                    name_filter=name_filter,
                    classes=classes,
                    as_graph=False)

    # (first recursive call): construct the graph
    if as_graph:
        from sage.graphs.digraph import DiGraph
        return DiGraph(classes)
    else:
        return classes
Ejemplo n.º 50
0
    def cardinality(self):
        r"""
        Return the number of linear extensions by using the determinant
        formula for counting linear extensions of mobiles.

        EXAMPLES::

            sage: from sage.combinat.posets.mobile import MobilePoset
            sage: M = MobilePoset(DiGraph([[0,1,2,3,4,5,6,7,8], [(1,0),(3,0),(2,1),(2,3),(4,
            ....: 3), (5,4),(5,6),(7,4),(7,8)]]))
            sage: M.linear_extensions().cardinality()
            1098

            sage: M1 = posets.RibbonPoset(6, [1,3])
            sage: M1.linear_extensions().cardinality()
            61

            sage: P = posets.MobilePoset(posets.RibbonPoset(7, [1,3]), {1:
            ....: [posets.YoungDiagramPoset([3, 2], dual=True)], 3: [posets.DoubleTailedDiamond(6)]},
            ....: anchor=(4, 2, posets.ChainPoset(6)))
            sage: P.linear_extensions().cardinality()
            361628701868606400
        """
        import sage.combinat.posets.d_complete as dc
        import sage.combinat.posets.posets as fp
        # Find folds
        if self._poset._anchor:
            anchor_index = self._poset._ribbon.index(self._poset._anchor[0])
        else:
            anchor_index = len(self._poset._ribbon)

        folds_up = []
        folds_down = []

        for ind, r in enumerate(self._poset._ribbon[:-1]):
            if ind < anchor_index and self._poset.is_greater_than(
                    r, self._poset._ribbon[ind + 1]):
                folds_up.append((self._poset._ribbon[ind + 1], r))
            elif ind >= anchor_index and self._poset.is_less_than(
                    r, self._poset._ribbon[ind + 1]):
                folds_down.append((r, self._poset._ribbon[ind + 1]))

        if not folds_up and not folds_down:
            return dc.DCompletePoset(
                self._poset).linear_extensions().cardinality()

        # Get ordered connected components
        cr = self._poset.cover_relations()
        foldless_cr = [
            tuple(c) for c in cr
            if tuple(c) not in folds_up and tuple(c) not in folds_down
        ]

        elmts = list(self._poset._elements)
        poset_components = DiGraph([elmts, foldless_cr])
        ordered_poset_components = [
            poset_components.connected_component_containing_vertex(f[1],
                                                                   sort=False)
            for f in folds_up
        ]
        ordered_poset_components.extend(
            poset_components.connected_component_containing_vertex(f[0],
                                                                   sort=False)
            for f in folds_down)
        ordered_poset_components.append(
            poset_components.connected_component_containing_vertex(
                folds_down[-1][1] if folds_down else folds_up[-1][0],
                sort=False))

        # Return determinant

        # Consoludate the folds lists
        folds = folds_up
        folds.extend(folds_down)

        mat = []
        for i in range(len(folds) + 1):
            mat_poset = dc.DCompletePoset(
                self._poset.subposet(ordered_poset_components[i]))
            row = [0] * (i - 1 if i - 1 > 0 else 0) + [1
                                                       ] * (1 if i >= 1 else 0)
            row.append(1 / mat_poset.hook_product())
            for j, f in enumerate(folds[i:]):
                next_poset = self._poset.subposet(
                    ordered_poset_components[j + i + 1])
                mat_poset = dc.DCompletePoset(
                    next_poset.slant_sum(mat_poset, f[0], f[1]))
                row.append(1 / mat_poset.hook_product())

            mat.append(row)
        return matrix(QQ, mat).determinant() * factorial(
            self._poset.cardinality())
    def tournaments_nauty(self,
                          n,
                          min_out_degree=None,
                          max_out_degree=None,
                          strongly_connected=False,
                          debug=False,
                          options=""):
        r"""
        Returns all tournaments on `n` vertices using Nauty.

        INPUT:

        - ``n`` (integer) -- number of vertices.

        - ``min_out_degree``, ``max_out_degree`` (integers) -- if set to
          ``None`` (default), then the min/max out-degree is not constrained.

        - ``debug`` (boolean) -- if ``True`` the first line of genbg's output to
          standard error is captured and the first call to the generator's
          ``next()`` function will return this line as a string.  A line leading
          with ">A" indicates a successful initiation of the program with some
          information on the arguments, while a line beginning with ">E"
          indicates an error with the input.

        - ``options`` (string) -- anything else that should be forwarded as
          input to Nauty's genbg. See its documentation for more information :
          `<http://cs.anu.edu.au/~bdm/nauty/>`_.


        .. NOTE::

            To use this method you must first install the Nauty spkg.

        EXAMPLES::

            sage: for g in digraphs.tournaments_nauty(4): # optional - nauty
            ....:    print g.edges(labels = False)        # optional - nauty
            [(1, 0), (2, 0), (2, 1), (3, 0), (3, 1), (3, 2)]
            [(1, 0), (1, 3), (2, 0), (2, 1), (3, 0), (3, 2)]
            [(0, 2), (1, 0), (2, 1), (3, 0), (3, 1), (3, 2)]
            [(0, 2), (0, 3), (1, 0), (2, 1), (3, 1), (3, 2)]
            sage: tournaments = digraphs.tournaments_nauty
            sage: [len(list(tournaments(x))) for x in range(1,8)] # optional - nauty
            [1, 1, 2, 4, 12, 56, 456]
            sage: [len(list(tournaments(x, strongly_connected = True))) for x in range(1,9)] # optional - nauty
            [1, 0, 1, 1, 6, 35, 353, 6008]
        """
        import subprocess
        from sage.misc.package import is_package_installed
        if not is_package_installed("nauty"):
            raise TypeError(
                "The optional nauty spkg does not seem to be installed")

        nauty_input = options

        if min_out_degree is None:
            min_out_degree = 0
        if max_out_degree is None:
            max_out_degree = n - 1

        nauty_input += " -d" + str(min_out_degree)
        nauty_input += " -D" + str(max_out_degree)

        if strongly_connected:
            nauty_input += " -c"

        nauty_input += " " + str(n) + " "

        sp = subprocess.Popen("nauty-gentourng {0}".format(nauty_input),
                              shell=True,
                              stdin=subprocess.PIPE,
                              stdout=subprocess.PIPE,
                              stderr=subprocess.PIPE,
                              close_fds=True)

        if debug:
            yield sp.stderr.readline()

        gen = sp.stdout
        while True:
            try:
                s = gen.next()
            except StopIteration:
                raise StopIteration("Exhausted list of graphs from nauty geng")

            G = DiGraph(n)
            i = 0
            j = 1
            for b in s[:-1]:
                if b == '0':
                    G.add_edge(i, j)
                else:
                    G.add_edge(j, i)

                if j == n - 1:
                    i += 1
                    j = i + 1
                else:
                    j += 1

            yield G
    def ImaseItoh(self, n, d):
        r"""
        Returns the digraph of Imase and Itoh of order `n` and degree `d`.

        The digraph of Imase and Itoh has been defined in [II83]_. It has vertex
        set `V=\{0, 1,..., n-1\}` and there is an arc from vertex `u \in V` to
        all vertices `v \in V` such that `v \equiv (-u*d-a-1) \mod{n}` with
        `0 \leq a < d`.

        When `n = d^{D}`, the digraph of Imase and Itoh is isomorphic to the de
        Bruijn digraph of degree `d` and diameter `D`. When `n = d^{D-1}(d+1)`,
        the digraph of Imase and Itoh is isomorphic to the Kautz digraph
        [Kautz68]_ of degree `d` and diameter `D`.

        INPUTS:

        - ``n`` -- is the number of vertices of the digraph

        - ``d`` -- is the degree of the digraph

        EXAMPLES::

            sage: II = digraphs.ImaseItoh(8, 2)
            sage: II.is_isomorphic(digraphs.DeBruijn(2, 3), certify = True)
            (True, {0: '010', 1: '011', 2: '000', 3: '001', 4: '110', 5: '111', 6: '100', 7: '101'})

            sage: II = digraphs.ImaseItoh(12, 2)
            sage: II.is_isomorphic(digraphs.Kautz(2, 3), certify = True)
            (True, {0: '010', 1: '012', 2: '021', 3: '020', 4: '202', 5: '201', 6: '210', 7: '212', 8: '121', 9: '120', 10: '102', 11: '101'})


        TESTS:

        An exception is raised when the degree is less than one::

            sage: G = digraphs.ImaseItoh(2, 0)
            Traceback (most recent call last):
            ...
            ValueError: The digraph of Imase and Itoh is defined for degree at least one.

        An exception is raised when the order of the graph is less than two::

            sage: G = digraphs.ImaseItoh(1, 2)
            Traceback (most recent call last):
            ...
            ValueError: The digraph of Imase and Itoh is defined for at least two vertices.


        REFERENCE:

        .. [II83] M. Imase and M. Itoh. A design for directed graphs with
          minimum diameter, *IEEE Trans. Comput.*, vol. C-32, pp. 782-784, 1983.
        """
        if n < 2:
            raise ValueError(
                "The digraph of Imase and Itoh is defined for at least two vertices."
            )
        if d < 1:
            raise ValueError(
                "The digraph of Imase and Itoh is defined for degree at least one."
            )

        II = DiGraph(loops=True)
        II.allow_multiple_edges(True)
        for u in xrange(n):
            for a in xrange(-u * d - d, -u * d):
                II.add_edge(u, a % n)

        II.name("Imase and Itoh digraph (n=%s, d=%s)" % (n, d))
        return II
Ejemplo n.º 53
0
def find_cartan_type_from_matrix(CM):
    r"""
    Find a Cartan type by direct comparison of Dynkin diagrams given from
    the generalized Cartan matrix ``CM`` and return ``None`` if not found.

    INPUT:

    - ``CM`` -- a generalized Cartan matrix

    EXAMPLES::

        sage: from sage.combinat.root_system.cartan_matrix import find_cartan_type_from_matrix
        sage: CM = CartanMatrix([[2,-1,-1], [-1,2,-1], [-1,-1,2]])
        sage: find_cartan_type_from_matrix(CM)
        ['A', 2, 1]
        sage: CM = CartanMatrix([[2,-1,0], [-1,2,-2], [0,-1,2]])
        sage: find_cartan_type_from_matrix(CM)
        ['C', 3] relabelled by {1: 0, 2: 1, 3: 2}
        sage: CM = CartanMatrix([[2,-1,-2], [-1,2,-1], [-2,-1,2]])
        sage: find_cartan_type_from_matrix(CM)
    """
    types = []
    for S in CM.dynkin_diagram().connected_components_subgraphs():
        S = DiGraph(S)  # We need a simple digraph here
        n = S.num_verts()
        # Build the list to test based upon rank
        if n == 1:
            types.append(CartanType(['A', 1]))
            continue

        test = [['A', n]]
        if n >= 2:
            if n == 2:
                test += [['G', 2], ['A', 2, 2]]
            test += [['B', n], ['A', n - 1, 1]]
        if n >= 3:
            if n == 3:
                test.append(['G', 2, 1])
            test += [['C', n], ['BC', n - 1, 2], ['C', n - 1, 1]]
        if n >= 4:
            if n == 4:
                test.append(['F', 4])
            test += [['D', n], ['B', n - 1, 1]]
        if n >= 5:
            if n == 5:
                test.append(['F', 4, 1])
            test.append(['D', n - 1, 1])
        if n == 6:
            test.append(['E', 6])
        elif n == 7:
            test += [['E', 7], ['E', 6, 1]]
        elif n == 8:
            test += [['E', 8], ['E', 7, 1]]
        elif n == 9:
            test.append(['E', 8, 1])

        # Test every possible Cartan type and its dual
        found = False
        for x in test:
            ct = CartanType(x)
            T = DiGraph(ct.dynkin_diagram())  # We need a simple digraph here
            iso, match = T.is_isomorphic(S, certificate=True, edge_labels=True)
            if iso:
                types.append(ct.relabel(match))
                found = True
                break

            if ct == ct.dual():
                continue  # self-dual, so nothing more to test

            ct = ct.dual()
            T = DiGraph(ct.dynkin_diagram())  # We need a simple digraph here
            iso, match = T.is_isomorphic(S, certificate=True, edge_labels=True)
            if iso:
                types.append(ct.relabel(match))
                found = True
                break
        if not found:
            return None

    return CartanType(types)
Ejemplo n.º 54
0
        def cayley_graph(self, side="right", simple=False, elements = None, generators = None, connecting_set = None):
            r"""
            Return the Cayley graph for this finite semigroup.

            INPUT:

            - ``side`` -- "left", "right", or "twosided":
              the side on which the generators act (default:"right")
            - ``simple`` -- boolean (default:False):
              if True, returns a simple graph (no loops, no labels,
              no multiple edges)
            - ``generators`` -- a list, tuple, or family of elements
              of ``self`` (default: ``self.semigroup_generators()``)
            - ``connecting_set`` -- alias for ``generators``; deprecated
            - ``elements`` -- a list (or iterable) of elements of ``self``

            OUTPUT:

            - :class:`DiGraph`

            EXAMPLES:

            We start with the (right) Cayley graphs of some classical groups::

                sage: D4 = DihedralGroup(4); D4
                Dihedral group of order 8 as a permutation group
                sage: G = D4.cayley_graph()
                sage: show(G, color_by_label=True, edge_labels=True)
                sage: A5 = AlternatingGroup(5); A5
                Alternating group of order 5!/2 as a permutation group
                sage: G = A5.cayley_graph()
                sage: G.show3d(color_by_label=True, edge_size=0.01, edge_size2=0.02, vertex_size=0.03)
                sage: G.show3d(vertex_size=0.03, edge_size=0.01, edge_size2=0.02, vertex_colors={(1,1,1):G.vertices()}, bgcolor=(0,0,0), color_by_label=True, xres=700, yres=700, iterations=200) # long time (less than a minute)
                sage: G.num_edges()
                120

                sage: w = WeylGroup(['A',3])
                sage: d = w.cayley_graph(); d
                Digraph on 24 vertices
                sage: d.show3d(color_by_label=True, edge_size=0.01, vertex_size=0.03)

            Alternative generators may be specified::

                sage: G = A5.cayley_graph(generators=[A5.gens()[0]])
                sage: G.num_edges()
                60
                sage: g=PermutationGroup([(i+1,j+1) for i in range(5) for j in range(5) if j!=i])
                sage: g.cayley_graph(generators=[(1,2),(2,3)])
                Digraph on 120 vertices

            If ``elements`` is specified, then only the subgraph
            induced and those elements is returned. Here we use it to
            display the Cayley graph of the free monoid truncated on
            the elements of length at most 3::

                sage: M = Monoids().example(); M
                An example of a monoid: the free monoid generated by ('a', 'b', 'c', 'd')
                sage: elements = [ M.prod(w) for w in sum((list(Words(M.semigroup_generators(),k)) for k in range(4)),[]) ]
                sage: G = M.cayley_graph(elements = elements)
                sage: G.num_verts(), G.num_edges()
                (85, 84)
                sage: G.show3d(color_by_label=True, edge_size=0.001, vertex_size=0.01)

            We now illustrate the ``side`` and ``simple`` options on
            a semigroup::

                sage: S = FiniteSemigroups().example(alphabet=('a','b'))
                sage: g = S.cayley_graph(simple=True)
                sage: g.vertices()
                ['a', 'ab', 'b', 'ba']
                sage: g.edges()
                [('a', 'ab', None), ('b', 'ba', None)]

            ::

                sage: g = S.cayley_graph(side="left", simple=True)
                sage: g.vertices()
                ['a', 'ab', 'b', 'ba']
                sage: g.edges()
                [('a', 'ba', None), ('ab', 'ba', None), ('b', 'ab', None),
                ('ba', 'ab', None)]

            ::

                sage: g = S.cayley_graph(side="twosided", simple=True)
                sage: g.vertices()
                ['a', 'ab', 'b', 'ba']
                sage: g.edges()
                [('a', 'ab', None), ('a', 'ba', None), ('ab', 'ba', None),
                ('b', 'ab', None), ('b', 'ba', None), ('ba', 'ab', None)]

            ::

                sage: g = S.cayley_graph(side="twosided")
                sage: g.vertices()
                ['a', 'ab', 'b', 'ba']
                sage: g.edges()
                [('a', 'a', (0, 'left')), ('a', 'a', (0, 'right')), ('a', 'ab', (1, 'right')), ('a', 'ba', (1, 'left')), ('ab', 'ab', (0, 'left')), ('ab', 'ab', (0, 'right')), ('ab', 'ab', (1, 'right')), ('ab', 'ba', (1, 'left')), ('b', 'ab', (0, 'left')), ('b', 'b', (1, 'left')), ('b', 'b', (1, 'right')), ('b', 'ba', (0, 'right')), ('ba', 'ab', (0, 'left')), ('ba', 'ba', (0, 'right')), ('ba', 'ba', (1, 'left')), ('ba', 'ba', (1, 'right'))]

            ::

                sage: s1 = SymmetricGroup(1); s = s1.cayley_graph(); s.vertices()
                [()]

            TESTS::

                sage: SymmetricGroup(2).cayley_graph(side="both")
                Traceback (most recent call last):
                ...
                ValueError: option 'side' must be 'left', 'right' or 'twosided'

            .. TODO::

                - Add more options for constructing subgraphs of the
                  Cayley graph, handling the standard use cases when
                  exploring large/infinite semigroups (a predicate,
                  generators of an ideal, a maximal length in term of the
                  generators)

                - Specify good default layout/plot/latex options in the graph

                - Generalize to combinatorial modules with module generators / operators

            AUTHORS:

            - Bobby Moretti (2007-08-10)
            - Robert Miller (2008-05-01): editing
            - Nicolas M. Thiery (2008-12): extension to semigroups,
              ``side``, ``simple``, and ``elements`` options, ...
            """
            from sage.graphs.digraph import DiGraph
            from groups import Groups
            if not side in ["left", "right", "twosided"]:
                raise ValueError("option 'side' must be 'left', 'right' or 'twosided'")
            if elements is None:
                assert self.is_finite(), "elements should be specified for infinite semigroups"
                elements = list(self)
            elements_set = set(elements)
            if simple or self in Groups():
                result = DiGraph()
            else:
                result = DiGraph(multiedges = True, loops = True)
            result.add_vertices(elements)

            if connecting_set is not None:
                generators = connecting_set
            if generators is None:
                generators = self.semigroup_generators()
            if isinstance(generators, (list, tuple)):
                generators = dict((self(g), self(g)) for g in generators)
            left  = (side == "left"  or side == "twosided")
            right = (side == "right" or side == "twosided")
            def add_edge(source, target, label, side_label):
                """
                Skips edges whose targets are not in elements
                Return an appropriate edge given the options
                """
                if target not in elements_set: return
                if simple:
                    result.add_edge([source, target])
                elif side == "twosided":
                    result.add_edge([source, target, (label, side_label)])
                else:
                    result.add_edge([source, target, label])
            for x in elements:
                for i in generators.keys():
                    if left:
                        add_edge(x, generators[i]*x, i, "left" )
                    if right:
                        add_edge(x, x*generators[i], i, "right")
            return result
    def RandomDirectedGNM(self, n, m, loops=False):
        r"""
        Returns a random labelled digraph on `n` nodes and `m` arcs.

        INPUT:

        - ``n`` (integer) -- number of vertices.

        - ``m`` (integer) -- number of edges.

        - ``loops`` (boolean) -- whether to allow loops (set to ``False`` by
          default).

        PLOTTING: When plotting, this graph will use the default spring-layout
        algorithm, unless a position dictionary is specified.

        EXAMPLE::

            sage: D = digraphs.RandomDirectedGNM(10, 5)
            sage: D.num_verts()
            10
            sage: D.edges(labels=False)
            [(0, 3), (1, 5), (5, 1), (7, 0), (8, 5)]

        With loops::

            sage: D = digraphs.RandomDirectedGNM(10, 100, loops = True)
            sage: D.num_verts()
            10
            sage: D.loops()
            [(0, 0, None), (1, 1, None), (2, 2, None), (3, 3, None), (4, 4, None), (5, 5, None), (6, 6, None), (7, 7, None), (8, 8, None), (9, 9, None)]

        TESTS::

            sage: digraphs.RandomDirectedGNM(10,-3)
            Traceback (most recent call last):
            ...
            ValueError: The number of edges must satisfy 0<= m <= n(n-1) when no loops are allowed, and 0<= m <= n^2 otherwise.

            sage: digraphs.RandomDirectedGNM(10,100)
            Traceback (most recent call last):
            ...
            ValueError: The number of edges must satisfy 0<= m <= n(n-1) when no loops are allowed, and 0<= m <= n^2 otherwise.
        """
        n, m = int(n), int(m)

        # The random graph is built by drawing randomly and uniformly two
        # integers u,v, and adding the corresponding edge if it does not exist,
        # as many times as necessary.

        # When the graph is dense, we actually compute its complement. This will
        # prevent us from drawing the same pair u,v too many times.

        from sage.misc.prandom import _pyrand
        rand = _pyrand()
        D = DiGraph(n, loops=loops)

        # Ensuring the parameters n,m make sense.
        #
        # If the graph is dense, we actually want to build its complement. We
        # update m accordingly.

        good_input = True
        is_dense = False

        if m < 0:
            good_input = False

        if loops:
            if m > n * n:
                good_input = False
            elif m > n * n / 2:
                is_dense = True
                m = n * n - m

        else:
            if m > n * (n - 1):
                good_input = False
            elif m > n * (n - 1) / 2:
                is_dense = True
                m = n * (n - 1) - m

        if not good_input:
            raise ValueError(
                "The number of edges must satisfy 0<= m <= n(n-1) when no loops are allowed, and 0<= m <= n^2 otherwise."
            )

        # When the given number of edges defines a density larger than 1/2, it
        # should be faster to compute the complement of the graph (less edges to
        # generate), then to return its complement. This being said, the
        # .complement() method for sparse graphs is very slow at the moment.

        # Similarly, it is faster to test whether a pair belongs to a dictionary
        # than to test the adjacency of two vertices in a graph. For these
        # reasons, the following code mainly works on dictionaries.

        adj = dict((i, dict()) for i in range(n))

        # We fill the dictionary structure, but add the corresponding edge in
        # the graph only if is_dense is False. If it is true, we will add the
        # edges in a second phase.

        while m > 0:

            # It is better to obtain random numbers this way than by calling the
            # randint or randrange method. This, because they are very expensive
            # when trying to compute MANY random integers, and because the
            # following lines is precisely what they do anyway, after checking
            # their parameters are correct.

            u = int(rand.random() * n)
            v = int(rand.random() * n)

            if (u != v or loops) and (not v in adj[u]):
                adj[u][v] = 1
                m -= 1
                if not is_dense:
                    D.add_edge(u, v)

        # If is_dense is True, it means the graph has not been built. We fill D
        # with the complement of the edges stored in the adj dictionary

        if is_dense:
            for u in range(n):
                for v in range(n):
                    if ((u != v) or loops) and (not (v in adj[u])):
                        D.add_edge(u, v)

        return D
Ejemplo n.º 56
0
def line_graph(self, labels=True):
    """
    Returns the line graph of the (di)graph.

    INPUT:

    - ``labels`` (boolean) -- whether edge labels should be taken in
      consideration. If ``labels=True``, the vertices of the line graph will be
      triples ``(u,v,label)``, and pairs of vertices otherwise.

      This is set to ``True`` by default.

    The line graph of an undirected graph G is an undirected graph H such that
    the vertices of H are the edges of G and two vertices e and f of H are
    adjacent if e and f share a common vertex in G. In other words, an edge in H
    represents a path of length 2 in G.

    The line graph of a directed graph G is a directed graph H such that the
    vertices of H are the edges of G and two vertices e and f of H are adjacent
    if e and f share a common vertex in G and the terminal vertex of e is the
    initial vertex of f. In other words, an edge in H represents a (directed)
    path of length 2 in G.

    .. NOTE::

        As a :class:`Graph` object only accepts hashable objects as vertices
        (and as the vertices of the line graph are the edges of the graph), this
        code will fail if edge labels are not hashable. You can also set the
        argument ``labels=False`` to ignore labels.

    .. SEEALSO::

        - The :mod:`line_graph <sage.graphs.line_graph>` module.

        - :meth:`~sage.graphs.graph_generators.GraphGenerators.line_graph_forbidden_subgraphs`
          -- the forbidden subgraphs of a line graph.

        - :meth:`~Graph.is_line_graph` -- tests whether a graph is a line graph.

    EXAMPLES::

        sage: g = graphs.CompleteGraph(4)
        sage: h = g.line_graph()
        sage: h.vertices()
        [(0, 1, None),
        (0, 2, None),
        (0, 3, None),
        (1, 2, None),
        (1, 3, None),
        (2, 3, None)]
        sage: h.am()
        [0 1 1 1 1 0]
        [1 0 1 1 0 1]
        [1 1 0 0 1 1]
        [1 1 0 0 1 1]
        [1 0 1 1 0 1]
        [0 1 1 1 1 0]
        sage: h2 = g.line_graph(labels=False)
        sage: h2.vertices()
        [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]
        sage: h2.am() == h.am()
        True
        sage: g = DiGraph([[1..4],lambda i,j: i<j])
        sage: h = g.line_graph()
        sage: h.vertices()
        [(1, 2, None),
        (1, 3, None),
        (1, 4, None),
        (2, 3, None),
        (2, 4, None),
        (3, 4, None)]
        sage: h.edges()
        [((1, 2, None), (2, 3, None), None),
         ((1, 2, None), (2, 4, None), None),
         ((1, 3, None), (3, 4, None), None),
         ((2, 3, None), (3, 4, None), None)]

    Tests:

    :trac:`13787`::

        sage: g = graphs.KneserGraph(7,1)
        sage: C = graphs.CompleteGraph(7)
        sage: C.is_isomorphic(g)
        True
        sage: C.line_graph().is_isomorphic(g.line_graph())
        True
    """
    if self._directed:
        from sage.graphs.digraph import DiGraph
        G = DiGraph()
        G.add_vertices(self.edges(labels=labels))
        for v in self:
            # Connect appropriate incident edges of the vertex v
            G.add_edges([(e,f) for e in self.incoming_edge_iterator(v, labels=labels) \
                         for f in self.outgoing_edge_iterator(v, labels=labels)])
        return G
    else:
        from sage.graphs.all import Graph
        G = Graph()

        # We must sort the edges' endpoints so that (1,2,None) is seen as
        # the same edge as (2,1,None).
        #
        # We do so by comparing hashes, just in case all the natural order
        # (<) on vertices would not be a total order (for instance when
        # vertices are sets). If two adjacent vertices have the same hash,
        # then we store the pair in the dictionary of conflicts

        conflicts = {}

        # 1) List of vertices in the line graph
        elist = []
        for e in self.edge_iterator(labels=labels):
            if hash(e[0]) < hash(e[1]):
                elist.append(e)
            elif hash(e[0]) > hash(e[1]):
                elist.append((e[1], e[0]) + e[2:])
            else:
                # Settle the conflict arbitrarily
                conflicts[e] = e
                conflicts[(e[1], e[0]) + e[2:]] = e
                elist.append(e)

        G.add_vertices(elist)

        # 2) adjacencies in the line graph
        for v in self:
            elist = []

            # Add the edge to the list, according to hashes, as previously
            for e in self.edge_iterator(v, labels=labels):
                if hash(e[0]) < hash(e[1]):
                    elist.append(e)
                elif hash(e[0]) > hash(e[1]):
                    elist.append((e[1], e[0]) + e[2:])
                else:
                    elist.append(conflicts[e])

            # Alls pairs of elements in elist are edges of the
            # line graph
            while elist:
                x = elist.pop()
                for y in elist:
                    G.add_edge(x, y)

        return G
Ejemplo n.º 57
0
    def Circuit(self,n):
        r"""
        Returns the circuit on `n` vertices

        The circuit is an oriented ``CycleGraph``

        EXAMPLE:

        A circuit is the smallest strongly connected digraph::

            sage: circuit = digraphs.Circuit(15)
            sage: len(circuit.strongly_connected_components()) == 1
            True
        """
        if n<0:
            raise ValueError("The number of vertices must be a positive integer.")

        g = DiGraph()
        g.name("Circuit on "+str(n)+" vertices")

        if n==0:
            return g
        elif n == 1:
            g.allow_loops(True)
            g.add_edge(0,0)
            return g
        else:
            g.add_edges([(i,i+1) for i in xrange(n-1)])
            g.add_edge(n-1,0)
            return g        
Ejemplo n.º 58
0
def lattice_from_incidences(atom_to_coatoms,
                            coatom_to_atoms,
                            face_constructor=None,
                            required_atoms=None,
                            key=None,
                            **kwds):
    r"""
    Compute an atomic and coatomic lattice from the incidence between
    atoms and coatoms.

    INPUT:

    - ``atom_to_coatoms`` -- list, ``atom_to_coatom[i]`` should list all
      coatoms over the ``i``-th atom;

    - ``coatom_to_atoms`` -- list, ``coatom_to_atom[i]`` should list all
      atoms under the ``i``-th coatom;

    - ``face_constructor`` -- function or class taking as the first two
      arguments sorted :class:`tuple` of integers and any keyword arguments.
      It will be called to construct a face over atoms passed as the first
      argument and under coatoms passed as the second argument. Default
      implementation will just return these two tuples as a tuple;

    - ``required_atoms`` -- list of atoms (default:None). Each
      non-empty "face" requires at least one of the specified atoms
      present. Used to ensure that each face has a vertex.

    - ``key`` -- any hashable value (default: None). It is passed down
      to :class:`~sage.combinat.posets.posets.FinitePoset`.

    - all other keyword arguments will be passed to ``face_constructor`` on
      each call.

    OUTPUT:

    - :class:`finite poset <sage.combinat.posets.posets.FinitePoset>` with
      elements constructed by ``face_constructor``.

    .. NOTE::

        In addition to the specified partial order, finite posets in Sage have
        internal total linear order of elements which extends the partial one.
        This function will try to make this internal order to start with the
        bottom and atoms in the order corresponding to ``atom_to_coatoms`` and
        to finish with coatoms in the order corresponding to
        ``coatom_to_atoms`` and the top. This may not be possible if atoms and
        coatoms are the same, in which case the preference is given to the
        first list.

    ALGORITHM:

    The detailed description of the used algorithm is given in [KP2002]_.

    The code of this function follows the pseudo-code description in the
    section 2.5 of the paper, although it is mostly based on frozen sets
    instead of sorted lists - this makes the implementation easier and should
    not cost a big performance penalty. (If one wants to make this function
    faster, it should be probably written in Cython.)

    While the title of the paper mentions only polytopes, the algorithm (and
    the implementation provided here) is applicable to any atomic and coatomic
    lattice if both incidences are given, see Section 3.4.

    In particular, this function can be used for strictly convex cones and
    complete fans.

    REFERENCES: [KP2002]_

    AUTHORS:

    - Andrey Novoseltsev (2010-05-13) with thanks to Marshall Hampton for the
      reference.

    EXAMPLES:

    Let us construct the lattice of subsets of {0, 1, 2}.
    Our atoms are {0}, {1}, and {2}, while our coatoms are {0,1}, {0,2}, and
    {1,2}. Then incidences are ::

        sage: atom_to_coatoms = [(0,1), (0,2), (1,2)]
        sage: coatom_to_atoms = [(0,1), (0,2), (1,2)]

    and we can compute the lattice as ::

        sage: from sage.geometry.cone import lattice_from_incidences
        sage: L = lattice_from_incidences(
        ....:                     atom_to_coatoms, coatom_to_atoms)
        sage: L
        Finite lattice containing 8 elements with distinguished linear extension
        sage: for level in L.level_sets(): print(level)
        [((), (0, 1, 2))]
        [((0,), (0, 1)), ((1,), (0, 2)), ((2,), (1, 2))]
        [((0, 1), (0,)), ((0, 2), (1,)), ((1, 2), (2,))]
        [((0, 1, 2), ())]

    For more involved examples see the *source code* of
    :meth:`sage.geometry.cone.ConvexRationalPolyhedralCone.face_lattice` and
    :meth:`sage.geometry.fan.RationalPolyhedralFan._compute_cone_lattice`.
    """
    def default_face_constructor(atoms, coatoms, **kwds):
        return (atoms, coatoms)

    if face_constructor is None:
        face_constructor = default_face_constructor
    atom_to_coatoms = [frozenset(atc) for atc in atom_to_coatoms]
    A = frozenset(range(len(atom_to_coatoms)))  # All atoms
    coatom_to_atoms = [frozenset(cta) for cta in coatom_to_atoms]
    C = frozenset(range(len(coatom_to_atoms)))  # All coatoms
    # Comments with numbers correspond to steps in Section 2.5 of the article
    L = DiGraph(1)  # 3: initialize L
    faces = {}
    atoms = frozenset()
    coatoms = C
    faces[atoms, coatoms] = 0
    next_index = 1
    Q = [(atoms, coatoms)]  # 4: initialize Q with the empty face
    while Q:  # 5
        q_atoms, q_coatoms = Q.pop()  # 6: remove some q from Q
        q = faces[q_atoms, q_coatoms]
        # 7: compute H = {closure(q+atom) : atom not in atoms of q}
        H = {}
        candidates = set(A.difference(q_atoms))
        for atom in candidates:
            coatoms = q_coatoms.intersection(atom_to_coatoms[atom])
            atoms = A
            for coatom in coatoms:
                atoms = atoms.intersection(coatom_to_atoms[coatom])
            H[atom] = (atoms, coatoms)
        # 8: compute the set G of minimal sets in H
        minimals = set([])
        while candidates:
            candidate = candidates.pop()
            atoms = H[candidate][0]
            if atoms.isdisjoint(candidates) and atoms.isdisjoint(minimals):
                minimals.add(candidate)
        # Now G == {H[atom] : atom in minimals}
        for atom in minimals:  # 9: for g in G:
            g_atoms, g_coatoms = H[atom]
            if required_atoms is not None:
                if g_atoms.isdisjoint(required_atoms):
                    continue
            if (g_atoms, g_coatoms) in faces:
                g = faces[g_atoms, g_coatoms]
            else:  # 11: if g was newly created
                g = next_index
                faces[g_atoms, g_coatoms] = g
                next_index += 1
                Q.append((g_atoms, g_coatoms))  # 12
            L.add_edge(q, g)  # 14

    # End of algorithm, now construct a FiniteLatticePoset.

    # In principle, it is recommended to use Poset or in this case perhaps
    # even LatticePoset, but it seems to take several times more time
    # than the above computation, makes unnecessary copies, and crashes.
    # So for now we will mimic the relevant code from Poset.

    # Enumeration of graph vertices must be a linear extension of the poset
    new_order = L.topological_sort()
    # Make sure that coatoms are in the end in proper order
    tail = [
        faces[atomes, frozenset([coatom])]
        for coatom, atomes in enumerate(coatom_to_atoms)
    ]
    tail.append(faces[A, frozenset()])
    new_order = [n for n in new_order if n not in tail] + tail
    # Make sure that atoms are in the beginning in proper order
    head = [0]  # We know that the empty face has index 0
    head.extend(faces[frozenset([atom]), coatoms]
                for atom, coatoms in enumerate(atom_to_coatoms)
                if required_atoms is None or atom in required_atoms)
    new_order = head + [n for n in new_order if n not in head]
    # "Invert" this list to a dictionary
    labels = {}
    for new, old in enumerate(new_order):
        labels[old] = new
    L.relabel(labels)
    # Construct the actual poset elements
    elements = [None] * next_index
    for face, index in faces.items():
        atoms, coatoms = face
        elements[labels[index]] = face_constructor(tuple(sorted(atoms)),
                                                   tuple(sorted(coatoms)),
                                                   **kwds)
    D = {i: f for i, f in enumerate(elements)}
    L.relabel(D)
    return FiniteLatticePoset(L, elements, key=key)
Ejemplo n.º 59
0
        def quantum_bruhat_graph(self, index_set=()):
            r"""
            Return the quantum Bruhat graph of the quotient of the Weyl
            group by a parabolic subgroup `W_J`.

            INPUT:

            - ``index_set`` -- (default: ()) a tuple `J` of nodes of
              the Dynkin diagram

            By default, the value for ``index_set`` indicates that the
            subgroup is trivial and the quotient is the full Weyl group.

            EXAMPLES::

                sage: W = WeylGroup(['A',3], prefix="s")
                sage: g = W.quantum_bruhat_graph((1,3))
                sage: g
                Parabolic Quantum Bruhat Graph of Weyl Group of type ['A', 3] (as a matrix group acting on the ambient space) for nodes (1, 3): Digraph on 6 vertices
                sage: g.vertices()
                [s2*s3*s1*s2, s3*s1*s2, s1*s2, s3*s2, s2, 1]
                sage: g.edges()
                [(s2*s3*s1*s2, s2, alpha[2]),
                 (s3*s1*s2, s2*s3*s1*s2, alpha[1] + alpha[2] + alpha[3]),
                 (s3*s1*s2, 1, alpha[2]),
                 (s1*s2, s3*s1*s2, alpha[2] + alpha[3]),
                 (s3*s2, s3*s1*s2, alpha[1] + alpha[2]),
                 (s2, s1*s2, alpha[1] + alpha[2]),
                 (s2, s3*s2, alpha[2] + alpha[3]),
                 (1, s2, alpha[2])]
                sage: W = WeylGroup(['A',3,1], prefix="s")
                sage: g = W.quantum_bruhat_graph()
                Traceback (most recent call last):
                ...
                ValueError: the Cartan type ['A', 3, 1] is not finite
            """
            if not self.cartan_type().is_finite():
                raise ValueError("the Cartan type {} is not finite".format(self.cartan_type()))

            # This is a modified form of quantum_bruhat_successors.
            # It does not do any error checking and also is more efficient
            #   with how it handles memory and checks by using data stored
            #   at this function level rather than recomputing everything.
            lattice = self.cartan_type().root_system().root_lattice()
            NPR = lattice.nonparabolic_positive_roots(index_set)
            NPR_sum = sum(NPR)
            NPR_data = {}
            double_rho = lattice.sum(lattice.positive_roots()) # = 2 * \rho
            for alpha in NPR:
                ref = alpha.associated_reflection()
                alphacheck = alpha.associated_coroot()
                NPR_data[alpha] = [self.from_reduced_word(ref), # the element
                                   len(ref) == double_rho.scalar(alphacheck) - 1, # is_quantum
                                   NPR_sum.scalar(alphacheck)] # the scalar
            # We also create a temporary cache of lengths as they are
            #   relatively expensive to compute and needed frequently
            visited = {}
            todo = {self.one()}
            len_cache = {}
            def length(x):
                if x in len_cache:
                    return len_cache[x]
                len_cache[x] = x.length()
                return len_cache[x]
            while todo:
                x = todo.pop()
                w_length_plus_one = length(x) + 1
                adj = {}
                for alpha in NPR:
                    elt, is_quantum, scalar = NPR_data[alpha]
                    wr = x * elt
                    wrc = wr.coset_representative(index_set)
                    # coset_representative returns wr if nothing gets changed
                    if wrc is wr and length(wrc) == w_length_plus_one:
                        if wrc not in visited:
                            todo.add(wrc)
                        adj[wr] = alpha
                    elif is_quantum and length(wrc) == w_length_plus_one - scalar:
                        if wrc not in visited:
                            todo.add(wrc)
                        adj[wrc] = alpha
                visited[x] = adj

            from sage.graphs.digraph import DiGraph
            return DiGraph(visited,
                           name="Parabolic Quantum Bruhat Graph of %s for nodes %s"%(self, index_set),
                           format="dict_of_dicts",
                           data_structure="static_sparse")
Ejemplo n.º 60
0
class RCToKRTBijectionTypeB(RCToKRTBijectionTypeC):
    r"""
    Specific implementation of the bijection from rigged configurations to
    tensor products of KR tableaux for type `B_n^{(1)}`.
    """
    def run(self, verbose=False, build_graph=False):
        """
        Run the bijection from rigged configurations to tensor product of KR
        tableaux for type `B_n^{(1)}`.

        INPUT:

        - ``verbose`` -- (default: ``False``) display each step in the
          bijection
        - ``build_graph`` -- (default: ``False``) build the graph of each
          step of the bijection

        EXAMPLES::

            sage: RC = RiggedConfigurations(['B', 3, 1], [[2, 1]])
            sage: from sage.combinat.rigged_configurations.bij_type_B import RCToKRTBijectionTypeB
            sage: RCToKRTBijectionTypeB(RC(partition_list=[[1],[1,1],[1]])).run()
            [[3], [0]]

            sage: RC = RiggedConfigurations(['B', 3, 1], [[3, 1]])
            sage: x = RC(partition_list=[[],[1],[1]])
            sage: RCToKRTBijectionTypeB(x).run()
            [[1], [3], [-2]]
            sage: bij = RCToKRTBijectionTypeB(x)
            sage: bij.run(build_graph=True)
            [[1], [3], [-2]]
            sage: bij._graph
            Digraph on 6 vertices
        """
        from sage.combinat.crystals.letters import CrystalOfLetters
        letters = CrystalOfLetters(self.rigged_con.parent()._cartan_type.classical())

        # This is technically bad, but because the first thing we do is append
        #   an empty list to ret_crystal_path, we correct this. We do it this
        #   way so that we do not have to remove an empty list after the
        #   bijection has been performed.
        ret_crystal_path = []

        for dim in self.rigged_con.parent().dims:
            ret_crystal_path.append([])

            # Check to see if we are a spinor
            if dim[0] == self.n:
                # Perform the spinor bijection by converting to type A_{2n-1}^{(2)}
                #   doing the bijection there and pulling back

                from sage.combinat.rigged_configurations.bij_type_A2_odd import RCToKRTBijectionTypeA2Odd
                from sage.combinat.rigged_configurations.rigged_configurations import RiggedConfigurations
                from sage.combinat.rigged_configurations.rigged_partition import RiggedPartition, RiggedPartitionTypeB
        
                # Convert to a type A_{2n-1}^{(2)} RC
                RC = RiggedConfigurations(['A', 2*self.n-1, 2], self.cur_dims)
                if verbose:
                    print("====================")
                    print(repr(RC(*self.cur_partitions, use_vacancy_numbers=True)))
                    print("--------------------")
                    print(ret_crystal_path)
                    print("--------------------\n")
                    print("Applying doubling map\n")
                # Convert the n-th partition into a regular rigged partition
                self.cur_partitions[-1] = RiggedPartition(self.cur_partitions[-1]._list,
                                                          self.cur_partitions[-1].rigging,
                                                          self.cur_partitions[-1].vacancy_numbers)

                bij = RCToKRTBijectionTypeA2Odd(RC(*self.cur_partitions, use_vacancy_numbers=True))
                for i in range(len(self.cur_dims)):
                    if bij.cur_dims[i][0] != self.n:
                        bij.cur_dims[i][1] *= 2
                for i in range(self.n-1):
                    for j in range(len(bij.cur_partitions[i])):
                        bij.cur_partitions[i]._list[j] *= 2
                        bij.cur_partitions[i].rigging[j] *= 2
                        bij.cur_partitions[i].vacancy_numbers[j] *= 2

                if build_graph:
                    y = self.rigged_con.parent()(*[x._clone() for x in self.cur_partitions], use_vacancy_numbers=True)
                    self._graph.append([self._graph[-1][1], (y, len(self._graph)), '2x'])

                # Perform the type A_{2n-1}^{(2)} bijection

                # Iterate over each column
                for dummy_var in range(dim[1]):
                    # Split off a new column if necessary
                    if bij.cur_dims[0][1] > 1:
                        bij.cur_dims[0][1] -= 1
                        bij.cur_dims.insert(0, [dim[0], 1])

                        # Perform the corresponding splitting map on rigged configurations
                        # All it does is update the vacancy numbers on the RC side
                        for a in range(self.n):
                            bij._update_vacancy_numbers(a)

                        if build_graph:
                            y = self.rigged_con.parent()(*[x._clone() for x in self.cur_partitions], use_vacancy_numbers=True)
                            self._graph.append([self._graph[-1][1], (y, len(self._graph)), 'ls'])

                    while bij.cur_dims[0][0]: # > 0:
                        if verbose:
                            print("====================")
                            print(repr(RC(*bij.cur_partitions, use_vacancy_numbers=True)))
                            print("--------------------")
                            print(ret_crystal_path)
                            print("--------------------\n")

                        ht = bij.cur_dims[0][0]
                        bij.cur_dims[0][0] = bij._next_index(ht)
                        b = bij.next_state(ht)
                        # Make sure we have a crystal letter
                        ret_crystal_path[-1].append(letters(b)) # Append the rank

                        if build_graph:
                            y = self.rigged_con.parent()(*[x._clone() for x in self.cur_partitions], use_vacancy_numbers=True)
                            self._graph.append([self._graph[-1][1], (y, len(self._graph)), letters(b)])

                    bij.cur_dims.pop(0) # Pop off the leading column

                self.cur_dims.pop(0) # Pop off the spin rectangle

                self.cur_partitions = bij.cur_partitions
                # Convert the n-th partition back into the special type B one
                self.cur_partitions[-1] = RiggedPartitionTypeB(self.cur_partitions[-1])

                # Convert back to a type B_n^{(1)}
                if verbose:
                    print("====================")
                    print(repr(self.rigged_con.parent()(*bij.cur_partitions, use_vacancy_numbers=True)))
                    print("--------------------")
                    print(ret_crystal_path)
                    print("--------------------\n")
                    print("Applying halving map\n")

                for i in range(self.n-1):
                    for j in range(len(self.cur_partitions[i])):
                        self.cur_partitions[i]._list[j] //= 2
                        self.cur_partitions[i].rigging[j] //= 2
                        self.cur_partitions[i].vacancy_numbers[j] //= 2

                if build_graph:
                    y = self.rigged_con.parent()(*[x._clone() for x in self.cur_partitions], use_vacancy_numbers=True)
                    self._graph.append([self._graph[-1][1], (y, len(self._graph)), '1/2x'])
            else:
                # Perform the regular type B_n^{(1)} bijection

                # Iterate over each column
                for dummy_var in range(dim[1]):
                    # Split off a new column if necessary
                    if self.cur_dims[0][1] > 1:
                        if verbose:
                            print("====================")
                            print(repr(self.rigged_con.parent()(*self.cur_partitions, use_vacancy_numbers=True)))
                            print("--------------------")
                            print(ret_crystal_path)
                            print("--------------------\n")
                            print("Applying column split")

                        self.cur_dims[0][1] -= 1
                        self.cur_dims.insert(0, [dim[0], 1])

                        # Perform the corresponding splitting map on rigged configurations
                        # All it does is update the vacancy numbers on the RC side
                        for a in range(self.n):
                            self._update_vacancy_numbers(a)

                        if build_graph:
                            y = self.rigged_con.parent()(*[x._clone() for x in self.cur_partitions], use_vacancy_numbers=True)
                            self._graph.append([self._graph[-1][1], (y, len(self._graph)), '2x'])

                    while self.cur_dims[0][0]: #> 0:
                        if verbose:
                            print("====================")
                            print(repr(self.rigged_con.parent()(*self.cur_partitions, use_vacancy_numbers=True)))
                            print("--------------------")
                            print(ret_crystal_path)
                            print("--------------------\n")

                        self.cur_dims[0][0] -= 1 # This takes care of the indexing
                        b = self.next_state(self.cur_dims[0][0])

                        # Make sure we have a crystal letter
                        ret_crystal_path[-1].append(letters(b)) # Append the rank

                        if build_graph:
                            y = self.rigged_con.parent()(*[x._clone() for x in self.cur_partitions], use_vacancy_numbers=True)
                            self._graph.append([self._graph[-1][1], (y, len(self._graph)), letters(b)])

                    self.cur_dims.pop(0) # Pop off the leading column

        if build_graph:
            self._graph.pop(0) # Remove the dummy at the start
            from sage.graphs.digraph import DiGraph
            from sage.graphs.dot2tex_utils import have_dot2tex
            self._graph = DiGraph(self._graph)
            if have_dot2tex():
                self._graph.set_latex_options(format="dot2tex", edge_labels=True)

        return self.KRT(pathlist=ret_crystal_path)

    def next_state(self, height):
        r"""
        Build the next state for type `B_n^{(1)}`.

        TESTS::

            sage: RC = RiggedConfigurations(['B', 3, 1], [[2, 1]])
            sage: from sage.combinat.rigged_configurations.bij_type_B import RCToKRTBijectionTypeB
            sage: bijection = RCToKRTBijectionTypeB(RC(partition_list=[[1],[1,1],[1]]))
            sage: bijection.next_state(0)
            0
        """
        n = self.n
        ell = [None] * (2*n)
        case_S = False
        case_Q = False
        b = None

        # Calculate the rank and ell values

        last_size = 0
        for a in range(height, n-1):
            ell[a] = self._find_singular_string(self.cur_partitions[a], last_size)

            if ell[a] is None:
                b = a + 1
                break
            else:
                last_size = self.cur_partitions[a][ell[a]]

        # Special case for n
        if b is None:
            last_size = 2 * last_size - 1
            partition = self.cur_partitions[n-1]
            # Modified version of _find_singular_string()
            for i in reversed(range(len(partition))):
                if partition[i] == last_size \
                  and partition.vacancy_numbers[i] == partition.rigging[i]:
                    case_Q = True
                    ell[n-1] = i
                elif partition[i] > last_size:
                    if not case_Q and partition.vacancy_numbers[i] - 1 == partition.rigging[i]:
                        case_Q = True
                        # Check if the block is singular as well
                        block_size = partition[i]
                        for j in reversed(range(i)):
                            if partition[j] != block_size:
                                break
                            elif partition.vacancy_numbers[j] == partition.rigging[j]:
                                case_Q = False
                                ell[2*n-1] = j
                                last_size = partition[j]
                                case_S = True
                                break
                        if not case_Q: # We found a singular string above the quasi-singular one
                            break
                        ell[n-1] = i
                        last_size = partition[i]
                        # Now check for case QS
                    elif partition.vacancy_numbers[i] == partition.rigging[i]:
                        ell[2*n-1] = i
                        last_size = partition[i]
                        case_S = True
                        break

            if ell[2*n-1] is None:
                if not case_Q:
                    b = n
                else:
                    b = 0

        if b is None:
            # Now go back
            last_size = (last_size + 1) // 2
            for a in reversed(range(n - 1)):
                # Modified form of _find_singular_string
                end = ell[a]
                if a < height:
                    end = len(self.cur_partitions[a])
                for i in reversed(range(0, end)):
                    if self.cur_partitions[a][i] >= last_size and \
                      self.cur_partitions[a].vacancy_numbers[i] == self.cur_partitions[a].rigging[i]:
                        ell[n + a] = i
                        break

                if ell[n + a] is None:
                    b = -(a + 2)
                    break
                else:
                    last_size = self.cur_partitions[a][ell[n + a]]

        if b is None:
            b = -1

        # Determine the new rigged configuration by removing boxes from the
        #   selected string and then making the new string singular

        # Determine if we need to make the n-th string quasisingular
        make_quasisingular = case_Q and case_S and \
                (ell[2*n-2] is None
                 or self.cur_partitions[n-1][ell[2*n-1]]
                    < 2*self.cur_partitions[n-2][ell[2*n-2]])

        row_num = self.cur_partitions[0].remove_cell(ell[0])
        row_num_bar = self.cur_partitions[0].remove_cell(ell[n])
        for a in range(1, n-1):
            row_num_next = self.cur_partitions[a].remove_cell(ell[a])
            row_num_bar_next = self.cur_partitions[a].remove_cell(ell[n+a])

            self._update_vacancy_numbers(a - 1)
            if row_num is not None:
                self.cur_partitions[a-1].rigging[row_num] = self.cur_partitions[a-1].vacancy_numbers[row_num]
            if row_num_bar is not None:
                self.cur_partitions[a-1].rigging[row_num_bar] = self.cur_partitions[a-1].vacancy_numbers[row_num_bar]
            row_num = row_num_next
            row_num_bar = row_num_bar_next

        if case_Q:
            if case_S:
                row_num_next = self.cur_partitions[n-1].remove_cell(ell[n-1])
                row_num_bar_next = self.cur_partitions[n-1].remove_cell(ell[2*n-1])
            else:
                row_num_next = self.cur_partitions[n-1].remove_cell(ell[n-1])
                row_num_bar_next = None
        elif case_S:
            row_num_next = self.cur_partitions[n-1].remove_cell(ell[2*n-1], 2)
            row_num_bar_next = None
        else:
            row_num_next = None
            row_num_bar_next = None
            
        self._update_vacancy_numbers(n - 2)
        if row_num is not None:
            self.cur_partitions[n-2].rigging[row_num] = self.cur_partitions[n-2].vacancy_numbers[row_num]
        if row_num_bar is not None:
            self.cur_partitions[n-2].rigging[row_num_bar] = self.cur_partitions[n-2].vacancy_numbers[row_num_bar]

        self._update_vacancy_numbers(n - 1)
        if row_num_next is not None:
            self.cur_partitions[n-1].rigging[row_num_next] = self.cur_partitions[n-1].vacancy_numbers[row_num_next]
        if row_num_bar_next is not None: # If we enter here, it means case (Q, S) holds
            vac_num = self.cur_partitions[n-1].vacancy_numbers[row_num_bar_next]
            self.cur_partitions[n-1].rigging[row_num_bar_next] = vac_num
            if make_quasisingular:
                block_len = self.cur_partitions[n-1][row_num_bar_next]
                j = row_num_bar_next + 1
                length = len(self.cur_partitions[n-1])
                # Find the place for the quasisingular rigging
                while j < length and self.cur_partitions[n-1][j] == block_len \
                  and self.cur_partitions[n-1].rigging[j] == vac_num:
                    j += 1
                self.cur_partitions[n-1].rigging[j-1] = vac_num - 1

        return(b)