Beispiel #1
0
    def coxeter_diagram(self):
        """
        Return the Coxeter diagram for ``self``.

        EXAMPLES::

            sage: cd = CartanType("A2xB2xF4").coxeter_diagram()
            sage: cd
            Graph on 8 vertices
            sage: cd.edges()
            [(1, 2, 3), (3, 4, 4), (5, 6, 3), (6, 7, 4), (7, 8, 3)]

            sage: CartanType("F4xA2").coxeter_diagram().edges()
            [(1, 2, 3), (2, 3, 4), (3, 4, 3), (5, 6, 3)]

            sage: cd = CartanType("A1xH3").coxeter_diagram(); cd
            Graph on 4 vertices
            sage: cd.edges()
            [(2, 3, 3), (3, 4, 5)]
        """
        from sage.graphs.graph import Graph
        relabelling = self._index_relabelling
        g = Graph(multiedges=False)
        g.add_vertices(self.index_set())
        for i, t in enumerate(self._types):
            for [e1, e2, l] in t.coxeter_diagram().edges():
                g.add_edge(relabelling[i, e1], relabelling[i, e2], label=l)
        return g
Beispiel #2
0
    def cycle_elements(self):
        r"""
        Returns the list of all elements in a cycle for this endofunction.

        OUTPUT:

        list

        EXAMPLES::

            sage: from slabbe import Endofunction
            sage: L = [6, 5, 7, 2, 1, 1, 2, 6, 2, 4]
            sage: f = Endofunction(L)
            sage: f.cycle_elements()   # random order
            [0, 6, 7, 2, 1, 5]

        .. NOTE::

            ``G.cycle_basis()`` is not implemented for directed or
            multiedge graphs (in Networkx).  Hence, the ``cycle_basis``
            method is missing the 2-cycles.
        """
        G = Graph(loops=True)
        for i, a in enumerate(self._list):
            G.add_edge(i,a)
        C = G.cycle_basis()
        L = flatten(C)
        # adding the missing 2-cycle elements
        for i in self.two_cycle_elements():
            L.append(i)
        return L
Beispiel #3
0
    def to_bipartite_graph(self, with_partition=False):
        r"""
        Returns the associated bipartite graph

        INPUT:

        - with_partition -- boolean (default: False)

        OUTPUT:

        - a graph or a pair (graph, partition)

        EXAMPLES::

            sage: H = designs.steiner_triple_system(7).blocks()
            sage: H = Hypergraph(H)
            sage: g = H.to_bipartite_graph(); g
            Graph on 14 vertices
            sage: g.is_regular()
            True
        """
        from sage.graphs.graph import Graph

        G = Graph()
        domain = list(self.domain())
        G.add_vertices(domain)
        for s in self._sets:
            for i in s:
                G.add_edge(s, i)
        if with_partition:
            return (G, [domain, list(self._sets)])
        else:
            return G
    def coxeter_diagram(self):
        """
        Return the Coxeter diagram for ``self``.

        EXAMPLES::

            sage: cd = CartanType("A2xB2xF4").coxeter_diagram()
            sage: cd
            Graph on 8 vertices
            sage: cd.edges()
            [(1, 2, 3), (3, 4, 4), (5, 6, 3), (6, 7, 4), (7, 8, 3)]

            sage: CartanType("F4xA2").coxeter_diagram().edges()
            [(1, 2, 3), (2, 3, 4), (3, 4, 3), (5, 6, 3)]

            sage: cd = CartanType("A1xH3").coxeter_diagram(); cd
            Graph on 4 vertices
            sage: cd.edges()
            [(2, 3, 3), (3, 4, 5)]
        """
        from sage.graphs.graph import Graph
        relabelling = self._index_relabelling
        g = Graph(multiedges=False)
        g.add_vertices(self.index_set())
        for i,t in enumerate(self._types):
            for [e1, e2, l] in t.coxeter_diagram().edges():
                g.add_edge(relabelling[i,e1], relabelling[i,e2], label=l)
        return g
Beispiel #5
0
    def coxeter_diagram(self):
        """
        Returns a Coxeter diagram for type H.

        EXAMPLES::

             sage: ct = CartanType(['H',3])
             sage: ct.coxeter_diagram()
             Graph on 3 vertices
             sage: sorted(ct.coxeter_diagram().edges())
             [(1, 2, 3), (2, 3, 5)]
             sage: ct.coxeter_matrix()
             [1 3 2]
             [3 1 5]
             [2 5 1]

             sage: ct = CartanType(['H',4])
             sage: ct.coxeter_diagram()
             Graph on 4 vertices
             sage: sorted(ct.coxeter_diagram().edges())
             [(1, 2, 3), (2, 3, 3), (3, 4, 5)]
             sage: ct.coxeter_matrix()
             [1 3 2 2]
             [3 1 3 2]
             [2 3 1 5]
             [2 2 5 1]
        """
        from sage.graphs.graph import Graph
        n = self.n
        g = Graph(multiedges=False)
        for i in range(1, n):
            g.add_edge(i, i+1, 3)
        g.set_edge_label(n-1, n, 5)
        return g
Beispiel #6
0
    def to_bipartite_graph(self, with_partition=False):
        r"""
        Returns the associated bipartite graph

        INPUT:

        - with_partition -- boolean (default: False)

        OUTPUT:

        - a graph or a pair (graph, partition)

        EXAMPLES::

            sage: H = designs.steiner_triple_system(7).blocks()
            sage: H = Hypergraph(H)
            sage: g = H.to_bipartite_graph(); g
            Graph on 14 vertices
            sage: g.is_regular()
            True
        """
        from sage.graphs.graph import Graph

        G = Graph()
        domain = list(self.domain())
        G.add_vertices(domain)
        for s in self._sets:
            for i in s:
                G.add_edge(s, i)
        if with_partition:
            return (G, [domain, list(self._sets)])
        else:
            return G
Beispiel #7
0
    def coxeter_diagram(self):
        """
        Returns a Coxeter diagram for type H.

        EXAMPLES::

             sage: ct = CartanType(['H',3])
             sage: ct.coxeter_diagram()
             Graph on 3 vertices
             sage: sorted(ct.coxeter_diagram().edges())
             [(1, 2, 3), (2, 3, 5)]
             sage: ct.coxeter_matrix()
             [1 3 2]
             [3 1 5]
             [2 5 1]

             sage: ct = CartanType(['H',4])
             sage: ct.coxeter_diagram()
             Graph on 4 vertices
             sage: sorted(ct.coxeter_diagram().edges())
             [(1, 2, 3), (2, 3, 3), (3, 4, 5)]
             sage: ct.coxeter_matrix()
             [1 3 2 2]
             [3 1 3 2]
             [2 3 1 5]
             [2 2 5 1]
        """
        from sage.graphs.graph import Graph
        n = self.n
        g = Graph(multiedges=False)
        for i in range(1, n):
            g.add_edge(i, i+1, 3)
        g.set_edge_label(n-1, n, 5)
        return g
Beispiel #8
0
    def to_undirected_graph(self):
        r"""
        Return the undirected graph obtained from the tree nodes and edges.

        The graph is endowed with an embedding, so that it will be displayed
        correctly.

        EXAMPLES::

            sage: t = OrderedTree([])
            sage: t.to_undirected_graph()
            Graph on 1 vertex
            sage: t = OrderedTree([[[]],[],[]])
            sage: t.to_undirected_graph()
            Graph on 5 vertices

        If the tree is labelled, we use its labelling to label the graph. This
        will fail if the labels are not all distinct.
        Otherwise, we use the graph canonical labelling which means that
        two different trees can have the same graph.

        EXAMPLES::

            sage: t = OrderedTree([[[]],[],[]])
            sage: t.canonical_labelling().to_undirected_graph()
            Graph on 5 vertices

        TESTS::

            sage: t.canonical_labelling().to_undirected_graph() == t.to_undirected_graph()
            False
            sage: OrderedTree([[],[]]).to_undirected_graph() == OrderedTree([[[]]]).to_undirected_graph()
            True
            sage: OrderedTree([[],[],[]]).to_undirected_graph() == OrderedTree([[[[]]]]).to_undirected_graph()
            False
        """
        from sage.graphs.graph import Graph
        g = Graph()
        if self in LabelledOrderedTrees():
            relabel = False
        else:
            self = self.canonical_labelling()
            relabel = True
        roots = [self]
        g.add_vertex(name=self.label())
        emb = {self.label(): []}
        while roots:
            node = roots.pop()
            children = reversed([child.label() for child in node])
            emb[node.label()].extend(children)
            for child in node:
                g.add_vertex(name=child.label())
                emb[child.label()] = [node.label()]
                g.add_edge(child.label(), node.label())
                roots.append(child)
        g.set_embedding(emb)
        if relabel:
            g = g.canonical_label()
        return g
Beispiel #9
0
    def to_undirected_graph(self):
        r"""
        Return the undirected graph obtained from the tree nodes and edges.

        The graph is endowed with an embedding, so that it will be displayed
        correctly.

        EXAMPLES::

            sage: t = OrderedTree([])
            sage: t.to_undirected_graph()
            Graph on 1 vertex
            sage: t = OrderedTree([[[]],[],[]])
            sage: t.to_undirected_graph()
            Graph on 5 vertices

        If the tree is labelled, we use its labelling to label the graph. This
        will fail if the labels are not all distinct.
        Otherwise, we use the graph canonical labelling which means that
        two different trees can have the same graph.

        EXAMPLES::

            sage: t = OrderedTree([[[]],[],[]])
            sage: t.canonical_labelling().to_undirected_graph()
            Graph on 5 vertices

        TESTS::

            sage: t.canonical_labelling().to_undirected_graph() == t.to_undirected_graph()
            False
            sage: OrderedTree([[],[]]).to_undirected_graph() == OrderedTree([[[]]]).to_undirected_graph()
            True
            sage: OrderedTree([[],[],[]]).to_undirected_graph() == OrderedTree([[[[]]]]).to_undirected_graph()
            False
        """
        from sage.graphs.graph import Graph
        g = Graph()
        if self in LabelledOrderedTrees():
            relabel = False
        else:
            self = self.canonical_labelling()
            relabel = True
        roots = [self]
        g.add_vertex(name=self.label())
        emb = {self.label(): []}
        while roots:
            node = roots.pop()
            children = reversed([child.label() for child in node])
            emb[node.label()].extend(children)
            for child in node:
                g.add_vertex(name=child.label())
                emb[child.label()] = [node.label()]
                g.add_edge(child.label(), node.label())
                roots.append(child)
        g.set_embedding(emb)
        if relabel:
            g = g.canonical_label()
        return g
Beispiel #10
0
    def is_connected(self, force_computation=False):
        if not force_computation and self._connected:
            return True

        from sage.graphs.graph import Graph
        G = Graph(loops=True, multiedges=True)
        for i in range(self._total_darts):
            if self._active_darts[i]:
                G.add_edge(i,self._vertices[i])
                G.add_edge(i,self._edges[i])
                G.add_edge(i,self._faces[i])
        return G.is_connected()
Beispiel #11
0
    def is_connected(self, force_computation=False):
        if not force_computation and self._connected:
            return True

        from sage.graphs.graph import Graph
        G = Graph(loops=True, multiedges=True)
        for i in xrange(self._total_darts):
            if self._active_darts[i]:
                G.add_edge(i,self._vertices[i])
                G.add_edge(i,self._edges[i])
                G.add_edge(i,self._faces[i])
        return G.is_connected()
Beispiel #12
0
    def dual_equivalence_graph(self):
        r"""
        Return the graph with vertices the orbit of ``self``
        and edges given by the action of the cactus group generators.

        In most implementations the generators `s_{i,i+1}` will act
        as the identity operators. The usual dual equivalence graphs
        are given by replacing the label `i,i+2` by `i` and removing
        edges with other labels.

        EXAMPLES::

            sage: s = path_tableaux.DyckPath([0,1,2,3,2,3,2,1,0])
            sage: s.dual_equivalence_graph().adjacency_matrix()
            [0 1 1 1 0 1 0 1 1 0 0 0 0 0]
            [1 0 1 1 1 1 1 0 1 0 0 1 1 0]
            [1 1 0 1 1 1 0 1 0 1 1 1 0 0]
            [1 1 1 0 1 0 1 1 1 1 0 1 1 0]
            [0 1 1 1 0 0 1 0 0 1 1 0 1 1]
            [1 1 1 0 0 0 1 1 1 1 1 0 1 0]
            [0 1 0 1 1 1 0 1 0 1 1 1 0 1]
            [1 0 1 1 0 1 1 0 1 1 1 1 1 0]
            [1 1 0 1 0 1 0 1 0 1 0 1 1 0]
            [0 0 1 1 1 1 1 1 1 0 0 1 1 1]
            [0 0 1 0 1 1 1 1 0 0 0 1 1 1]
            [0 1 1 1 0 0 1 1 1 1 1 0 1 1]
            [0 1 0 1 1 1 0 1 1 1 1 1 0 1]
            [0 0 0 0 1 0 1 0 0 1 1 1 1 0]
            sage: s = path_tableaux.DyckPath([0,1,2,3,2,1,0])
            sage: sorted(s.dual_equivalence_graph().edges())
            [([0, 1, 0, 1, 0, 1, 0], [0, 1, 0, 1, 2, 1, 0], '4,7'),
             ([0, 1, 0, 1, 0, 1, 0], [0, 1, 2, 1, 0, 1, 0], '2,5'),
             ([0, 1, 0, 1, 0, 1, 0], [0, 1, 2, 1, 2, 1, 0], '2,7'),
             ([0, 1, 0, 1, 2, 1, 0], [0, 1, 2, 1, 0, 1, 0], '2,6'),
             ([0, 1, 0, 1, 2, 1, 0], [0, 1, 2, 1, 2, 1, 0], '1,4'),
             ([0, 1, 0, 1, 2, 1, 0], [0, 1, 2, 3, 2, 1, 0], '2,7'),
             ([0, 1, 2, 1, 0, 1, 0], [0, 1, 2, 1, 2, 1, 0], '4,7'),
             ([0, 1, 2, 1, 0, 1, 0], [0, 1, 2, 3, 2, 1, 0], '3,7'),
             ([0, 1, 2, 1, 2, 1, 0], [0, 1, 2, 3, 2, 1, 0], '3,6')]
        """
        from sage.graphs.graph import Graph
        from itertools import combinations

        G = Graph()
        orb = self.orbit()

        for a in orb:
            for i,j in combinations(range(1,self.size()+1),2):
                b = a.cactus(i,j)
                if a != b:
                    G.add_edge(a,b,"%d,%d" % (i,j))
        return G
Beispiel #13
0
def CayleyGraph(vspace, gens, directed=False):
  """
  Generate a Cayley Graph over given vector space with edges
  generated by given generators. The graph is optionally directed.
  Try e.g. CayleyGraph(VectorSpace(GF(2), 3), [(1,0,0), (0,1,0), (0,0,1)])
  """
  G = Graph()
  for v in vspace:
    G.add_vertex(tuple(v))
    for g in gens:
      g2 = vspace(g)
      G.add_edge(tuple(v), tuple(v + g2))
  return G
Beispiel #14
0
def CayleyGraph(vspace, gens, directed=False):
    """
  Generate a Cayley Graph over given vector space with edges
  generated by given generators. The graph is optionally directed.
  Try e.g. CayleyGraph(VectorSpace(GF(2), 3), [(1,0,0), (0,1,0), (0,0,1)])
  """
    G = Graph()
    for v in vspace:
        G.add_vertex(tuple(v))
        for g in gens:
            g2 = vspace(g)
            G.add_edge(tuple(v), tuple(v + g2))
    return G
Beispiel #15
0
    def _spring_layout(self):
        r"""
        Return a spring layout for the vertices.

        The layout is computed by creating a graph `G` on the vertices *and*
        sets of the hypergraph. Each set is then made adjacent in `G` with all
        vertices it contains before a spring layout is computed for this
        graph. The position of the vertices in the hypergraph is the position of
        the same vertices in the graph's layout.

        .. NOTE::

            This method also returns the position of the "fake" vertices,
            i.e. those representing the sets.

        EXAMPLES::

            sage: H = Hypergraph([{1,2,3},{2,3,4},{3,4,5},{4,5,6}]); H
            Hypergraph on 6 vertices containing 4 sets
            sage: L = H._spring_layout()
            sage: L # random
            {1: (0.238, -0.926),
             2: (0.672, -0.518),
             3: (0.449, -0.225),
             4: (0.782, 0.225),
             5: (0.558, 0.518),
             6: (0.992, 0.926),
             {3, 4, 5}: (0.504, 0.173),
             {2, 3, 4}: (0.727, -0.173),
             {4, 5, 6}: (0.838, 0.617),
             {1, 2, 3}: (0.393, -0.617)}
            sage: all(v in L for v in H.domain())
            True
            sage: all(v in L for v in H._sets)
            True
        """
        from sage.graphs.graph import Graph

        g = Graph()
        for s in self._sets:
            for x in s:
                g.add_edge(s, x)

        _ = g.plot(iterations=50000, save_pos=True)

        # The values are rounded as TikZ does not like accuracy.
        return {
            k: (round(x, 3), round(y, 3))
            for k, (x, y) in g.get_pos().items()
        }
Beispiel #16
0
    def to_undirected_graph(self):
        r"""
        Return the undirected graph obtained from the tree nodes and edges.

        EXAMPLES::

            sage: t = OrderedTree([])
            sage: t.to_undirected_graph()
            Graph on 1 vertex
            sage: t = OrderedTree([[[]],[],[]])
            sage: t.to_undirected_graph()
            Graph on 5 vertices

        If the tree is labelled, we use its labelling to label the graph.
        Otherwise, we use the graph canonical labelling which means that
        two different trees can have the same graph.

        EXAMPLES::

            sage: t = OrderedTree([[[]],[],[]])
            sage: t.canonical_labelling().to_undirected_graph()
            Graph on 5 vertices
            sage: t.canonical_labelling().to_undirected_graph() == t.to_undirected_graph()
            False
            sage: OrderedTree([[],[]]).to_undirected_graph() == OrderedTree([[[]]]).to_undirected_graph()
            True
            sage: OrderedTree([[],[],[]]).to_undirected_graph() == OrderedTree([[[[]]]]).to_undirected_graph()
            False
        """
        from sage.graphs.graph import Graph

        g = Graph()
        if self in LabelledOrderedTrees():
            relabel = False
        else:
            self = self.canonical_labelling()
            relabel = True
        roots = [self]
        g.add_vertex(name=self.label())
        while len(roots) != 0:
            node = roots.pop()
            for child in node:
                g.add_vertex(name=child.label())
                g.add_edge(child.label(), node.label())
                roots.append(child)
        if relabel:
            g = g.canonical_label()
        return g
Beispiel #17
0
    def to_undirected_graph(self):
        r"""
        Return the undirected graph obtained from the tree nodes and edges.

        EXAMPLES::

            sage: t = OrderedTree([])
            sage: t.to_undirected_graph()
            Graph on 1 vertex
            sage: t = OrderedTree([[[]],[],[]])
            sage: t.to_undirected_graph()
            Graph on 5 vertices

        If the tree is labelled, we use its labelling to label the graph.
        Otherwise, we use the graph canonical labelling which means that
        two different trees can have the same graph.

        EXAMPLES::

            sage: t = OrderedTree([[[]],[],[]])
            sage: t.canonical_labelling().to_undirected_graph()
            Graph on 5 vertices
            sage: t.canonical_labelling().to_undirected_graph() == t.to_undirected_graph()
            False
            sage: OrderedTree([[],[]]).to_undirected_graph() == OrderedTree([[[]]]).to_undirected_graph()
            True
            sage: OrderedTree([[],[],[]]).to_undirected_graph() == OrderedTree([[[[]]]]).to_undirected_graph()
            False
        """
        from sage.graphs.graph import Graph
        g = Graph()
        if self in LabelledOrderedTrees():
            relabel = False
        else:
            self = self.canonical_labelling()
            relabel = True
        roots = [self]
        g.add_vertex(name=self.label())
        while len(roots) != 0:
            node = roots.pop()
            for child in node:
                g.add_vertex(name=child.label())
                g.add_edge(child.label(), node.label())
                roots.append(child)
        if (relabel):
            g = g.canonical_label()
        return g
Beispiel #18
0
    def _spring_layout(self):
        r"""
        Return a spring layout for the vertices.

        The layout is computed by creating a graph `G` on the vertices *and*
        sets of the hypergraph. Each set is then made adjacent in `G` with all
        vertices it contains before a spring layout is computed for this
        graph. The position of the vertices in the hypergraph is the position of
        the same vertices in the graph's layout.

        .. NOTE::

            This method also returns the position of the "fake" vertices,
            i.e. those representing the sets.

        EXAMPLES::

            sage: H = Hypergraph([{1,2,3},{2,3,4},{3,4,5},{4,5,6}]); H
            Hypergraph on 6 vertices containing 4 sets
            sage: L = H._spring_layout()
            sage: L # random
            {1: (0.238, -0.926),
             2: (0.672, -0.518),
             3: (0.449, -0.225),
             4: (0.782, 0.225),
             5: (0.558, 0.518),
             6: (0.992, 0.926),
             {3, 4, 5}: (0.504, 0.173),
             {2, 3, 4}: (0.727, -0.173),
             {4, 5, 6}: (0.838, 0.617),
             {1, 2, 3}: (0.393, -0.617)}
            sage: all(v in L for v in H.domain())
            True
            sage: all(v in L for v in H._sets)
            True
        """
        from sage.graphs.graph import Graph

        g = Graph()
        for s in self._sets:
            for x in s:
                g.add_edge(s,x)

        _ = g.plot(iterations = 50000,save_pos=True)

        # The values are rounded as TikZ does not like accuracy.
        return {k:(round(x,3),round(y,3)) for k,(x,y) in g.get_pos().items()}
Beispiel #19
0
    def _check(self):
        r"""
        Check that the data of the Ribbon graph is coherent
        """
        from sage.graphs.graph import Graph
        G = Graph()

        if len(self._active_darts) != self._total_darts:
            raise ValueError, "the length of active darts is not total_darts"
        if self._active_darts.count(True) != self._num_darts:
            raise ValueError, "the number of darts do not coincide with active darts"

        for i in xrange(self._total_darts):
            if self._active_darts[i]:
                G.add_edge(i,self._vertices[i])
                G.add_edge(i,self._edges[i])
                G.add_edge(i,self._faces[i])
                if self._vertices[i] is None or self._vertices_inv[i] is None:
                    raise ValueError, "dart %d is active but has no vertex" %i
                if self._edges[i] is None or self._edges_inv[i] is None:
                    raise ValueError, "dart %d is active but has no edge" %i
                if self._faces[i] is None or self._faces_inv[i] is None:
                    raise ValueError, "dart %d is active but has no face" %i

                if self._vertices[self._vertices_inv[i]] != i:
                    raise ValueError, "vertices is not the inverse of vertices_inv"
                if self._edges[self._edges_inv[i]] != i:
                    raise ValueError, "edges is not the inverse of edges_inv"
                if self._faces[self._faces_inv[i]] != i:
                    raise ValueError, "faces is not the inverse of faces_inv"

                if self._faces[self._edges[self._vertices[i]]] != i:
                    raise ValueError, "the Ribbon graph condition vef=() is not satisfied for %d" %i
                if self._edges[i] == i or self._edges[self._edges[i]] != i:
                    raise ValueError, "edges is not an involution without fixed point for %d" %i

            else:
                if self._vertices[i] is not None or self._vertices_inv[i] is not None:
                    raise ValueError, "dart %d is not active but has a vertex" %i
                if self._edges[i] is not None or self._edges_inv[i] is not None:
                    raise ValueError, "dart %d is not active but has an edge" %i
                if self._faces[i] is not None or self._faces_inv[i] is not None:
                    raise ValueError, "dart %d is not active but has a face" %i

        if not G.is_connected():
            raise ValueError, "the graph is not connected"
Beispiel #20
0
    def graph(self):
        r"""
        EXAMPLES::

            sage: from slabbe import DoubleRootedTree
            sage: edges = [(0,5),(1,2),(2,6),(3,2),(4,1),(5,7),(7,1),(8,2),(9,4)]
            sage: D = DoubleRootedTree(edges, 6, 0)
            sage: D.graph()
            Graph on 10 vertices
        """
        if self._graph is None:
            G = Graph()
            G.add_vertex(self._rootA)
            G.add_vertex(self._rootB)
            for a,b in self._edges:
                G.add_edge(a,b)
            self._graph = G
        return self._graph
Beispiel #21
0
def mutant_neighborhood_graph(link):
    G = Graph()
    isosig = link.exterior().isometry_signature(of_link=True)
    isosig_to_link_dict = {isosig: link.PD_code()}
    links_boundary = [link]
    while len(links_boundary) > 0:
        new_links_boundary = []
        for L in links_boundary:
            L_iso = L.exterior().isometry_signature(of_link=True)
            mutants = all_mutants(L)
            for M in mutants:
                isosig = M.exterior().isometry_signature(of_link=True)
                if isosig not in isosig_to_link_dict:
                    new_links_boundary.append(M)
                    isosig_to_link_dict[isosig] = M.PD_code()
                G.add_edge(L_iso, isosig)
        links_boundary = new_links_boundary

    return G, isosig_to_link_dict
Beispiel #22
0
def underlying_graph(G):
    r"""
    Given a graph `G` with multi-edges, returns a graph where all the
    multi-edges are replaced with a single edge.

    EXAMPLES::

        sage: from sage.graphs.tutte_polynomial import underlying_graph
        sage: G = Graph(multiedges=True)
        sage: G.add_edges([(0,1,'a'),(0,1,'b')])
        sage: G.edges()
        [(0, 1, 'a'), (0, 1, 'b')]
        sage: underlying_graph(G).edges()
        [(0, 1, None)]
    """
    g = Graph()
    g.allow_loops(True)
    for edge in set(G.edges(labels=False)):
        g.add_edge(edge)
    return g
Beispiel #23
0
def underlying_graph(G):
    r"""
    Given a graph `G` with multi-edges, returns a graph where all the
    multi-edges are replaced with a single edge.

    EXAMPLES::

        sage: from sage.graphs.tutte_polynomial import underlying_graph
        sage: G = Graph(multiedges=True)
        sage: G.add_edges([(0,1,'a'),(0,1,'b')])
        sage: G.edges()
        [(0, 1, 'a'), (0, 1, 'b')]
        sage: underlying_graph(G).edges()
        [(0, 1, None)]
    """
    g = Graph()
    g.allow_loops(True)
    for edge in set(G.edges(labels=False)):
        g.add_edge(edge)
    return g
Beispiel #24
0
    def coxeter_graph(self):
        """
        Return the Coxeter graph of ``self``.

        EXAMPLES::

            sage: W = CoxeterGroup(['H',3], implementation="reflection")
            sage: G = W.coxeter_graph(); G
            Graph on 3 vertices
            sage: G.edges()
            [(1, 2, None), (2, 3, 5)]
            sage: CoxeterGroup(G) is W
            True
            sage: G = Graph([(0, 1, 3), (1, 2, oo)])
            sage: W = CoxeterGroup(G)
            sage: W.coxeter_graph() == G
            True
            sage: CoxeterGroup(W.coxeter_graph()) is W
            True
        """
        G = Graph()
        G.add_vertices(self.index_set())
        for i, row in enumerate(self._matrix.rows()):
            for j, val in enumerate(row[i + 1 :]):
                if val == 3:
                    G.add_edge(self._index_set[i], self._index_set[i + 1 + j])
                elif val > 3:
                    G.add_edge(self._index_set[i], self._index_set[i + 1 + j], val)
                elif val == -1:  # FIXME: Hack because there is no ZZ\cup\{\infty\}
                    G.add_edge(self._index_set[i], self._index_set[i + 1 + j], infinity)
        return G
    def coxeter_graph(self):
        """
        Return the Coxeter graph of ``self``.

        EXAMPLES::

            sage: W = CoxeterGroup(['H',3], implementation="reflection")
            sage: G = W.coxeter_graph(); G
            Graph on 3 vertices
            sage: G.edges()
            [(1, 2, None), (2, 3, 5)]
            sage: CoxeterGroup(G) is W
            True
            sage: G = Graph([(0, 1, 3), (1, 2, oo)])
            sage: W = CoxeterGroup(G)
            sage: W.coxeter_graph() == G
            True
            sage: CoxeterGroup(W.coxeter_graph()) is W
            True
        """
        G = Graph()
        G.add_vertices(self.index_set())
        for i, row in enumerate(self._matrix.rows()):
            for j, val in enumerate(row[i + 1:]):
                if val == 3:
                    G.add_edge(self._index_set[i], self._index_set[i + 1 + j])
                elif val > 3:
                    G.add_edge(self._index_set[i], self._index_set[i + 1 + j],
                               val)
                elif val == -1:  # FIXME: Hack because there is no ZZ\cup\{\infty\}
                    G.add_edge(self._index_set[i], self._index_set[i + 1 + j],
                               infinity)
        return G
Beispiel #26
0
    def to_graph(self):
        r"""
        Returns the graph corresponding to the perfect matching.

        OUTPUT:

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

        EXAMPLES::

            sage: PerfectMatching([[1,3], [4,2]]).to_graph().edges(labels=False)
            [(1, 3), (2, 4)]
            sage: PerfectMatching([[1,4], [3,2]]).to_graph().edges(labels=False)
            [(1, 4), (2, 3)]
            sage: PerfectMatching([]).to_graph().edges(labels=False)
            []
        """
        from sage.graphs.graph import Graph
        G = Graph()
        for a, b in self.value:
            G.add_edge((a, b))
        return G
Beispiel #27
0
    def to_graph(self):
        r"""
        Returns the graph corresponding to the perfect matching.

        OUTPUT:

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

        EXAMPLES::

            sage: PerfectMatching([[1,3], [4,2]]).to_graph().edges(labels=False)
            [(1, 3), (2, 4)]
            sage: PerfectMatching([[1,4], [3,2]]).to_graph().edges(labels=False)
            [(1, 4), (2, 3)]
            sage: PerfectMatching([]).to_graph().edges(labels=False)
            []
        """
        from sage.graphs.graph import Graph
        G = Graph()
        for a, b in self.value:
            G.add_edge((a, b))
        return G
    def connection_graph(self):
        """
        Return the graph which has the variables of this system as
        vertices and edges between two variables if they appear in the
        same polynomial.

        EXAMPLE::

            sage: B.<x,y,z> = BooleanPolynomialRing()
            sage: F = Sequence([x*y + y + 1, z + 1])
            sage: F.connection_graph()
            Graph on 3 vertices
        """
        V = sorted(self.variables())
        from sage.graphs.graph import Graph
        g = Graph()
        g.add_vertices(sorted(V))
        for f in self:
            v = f.variables()
            a,tail = v[0],v[1:]
            for b in tail:
                g.add_edge((a,b))
        return g
    def connection_graph(self):
        """
        Return the graph which has the variables of this system as
        vertices and edges between two variables if they appear in the
        same polynomial.

        EXAMPLE::

            sage: B.<x,y,z> = BooleanPolynomialRing()
            sage: F = Sequence([x*y + y + 1, z + 1])
            sage: F.connection_graph()
            Graph on 3 vertices
        """
        V = sorted(self.variables())
        from sage.graphs.graph import Graph
        g = Graph()
        g.add_vertices(sorted(V))
        for f in self:
            v = f.variables()
            a,tail = v[0],v[1:]
            for b in tail:
                g.add_edge((a,b))
        return g
def decode_fgraph_string(s):
    data = s.split("_")
    n = decode_6bits(data[0])
    f = [decode_6bits(data[1][i]) for i in range(n)]

    G = Graph()
    G.add_vertices(range(n))

    # read in the adjacency matrix
    cur = 0
    val = decode_6bits(data[2][cur])
    mask = 1 << 5  # start with the high bit
    for j in range(
            n):  # adj matrix is bit packed in colex order, as in graph6 format
        for i in range(j):
            if val & mask != 0:  # test whether that bit is nonzero
                G.add_edge(i, j)
            mask >>= 1
            if mask == 0:  # mask has become 0
                cur += 1
                val = decode_6bits(data[2][cur])
                mask = 1 << 5

    return G, f
Beispiel #31
0
def IntervalGraph(intervals, points_ordered = False):
    r"""
    Returns the graph corresponding to the given intervals.

    An interval graph is built from a list `(a_i,b_i)_{1\leq i \leq n}` of
    intervals : to each interval of the list is associated one vertex, two
    vertices being adjacent if the two corresponding (closed) intervals
    intersect.

    INPUT:

    - ``intervals`` -- the list of pairs `(a_i,b_i)` defining the graph.

    - ``points_ordered`` -- states whether every interval `(a_i,b_i)` of
      `intervals` satisfies `a_i<b_i`. If satisfied then setting
      ``points_ordered`` to ``True`` will speed up the creation of the graph.

    .. NOTE::

        * The vertices are named 0, 1, 2, and so on. The intervals used
          to create the graph are saved with the graph and can be recovered
          using ``get_vertex()`` or ``get_vertices()``.

    EXAMPLE:

    The following line creates the sequence of intervals
    `(i, i+2)` for i in `[0, ..., 8]`::

        sage: intervals = [(i,i+2) for i in range(9)]

    In the corresponding graph ::

        sage: g = graphs.IntervalGraph(intervals)
        sage: g.get_vertex(3)
        (3, 5)
        sage: neigh = g.neighbors(3)
        sage: for v in neigh: print g.get_vertex(v)
        (1, 3)
        (2, 4)
        (4, 6)
        (5, 7)

    The is_interval() method verifies that this graph is an interval graph. ::

        sage: g.is_interval()
        True

    The intervals in the list need not be distinct. ::

        sage: intervals = [ (1,2), (1,2), (1,2), (2,3), (3,4) ]
        sage: g = graphs.IntervalGraph(intervals,True)
        sage: g.clique_maximum()
        [0, 1, 2, 3]
        sage: g.get_vertices()
        {0: (1, 2), 1: (1, 2), 2: (1, 2), 3: (2, 3), 4: (3, 4)}

    The endpoints of the intervals are not ordered we get the same graph
    (except for the vertex labels). ::

        sage: rev_intervals = [ (2,1), (2,1), (2,1), (3,2), (4,3) ]
        sage: h = graphs.IntervalGraph(rev_intervals,False)
        sage: h.get_vertices()
        {0: (2, 1), 1: (2, 1), 2: (2, 1), 3: (3, 2), 4: (4, 3)}
        sage: g.edges() == h.edges()
        True
    """

    n = len(intervals)
    g = Graph(n)

    if points_ordered:
        for i in xrange(n-1):
            li,ri = intervals[i]
            for j in xrange(i+1,n):
                lj,rj = intervals[j]
                if ri < lj or rj < li: continue
                g.add_edge(i,j)
    else:
        for i in xrange(n-1):
            I = intervals[i]
            for j in xrange(i+1,n):
                J = intervals[j]
                if max(I) < min(J) or max(J) < min(I): continue
                g.add_edge(i,j)

    rep = dict( zip(range(n),intervals) )
    g.set_vertices(rep)

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

    Definition:

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

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

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

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

    INPUT:

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

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

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

    .. SEEALSO:

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

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

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

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

    EXAMPLES::

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

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

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

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

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

    TESTS::

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

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

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

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

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

    return g
Beispiel #33
0
def ChessboardGraphGenerator(dim_list,
                             rook=True,
                             rook_radius=None,
                             bishop=True,
                             bishop_radius=None,
                             knight=True,
                             knight_x=1,
                             knight_y=2,
                             relabel=False):
    r"""
    Returns a Graph built on a `d`-dimensional chessboard with prescribed
    dimensions and interconnections.

    This function allows to generate many kinds of graphs corresponding to legal
    movements on a `d`-dimensional chessboard: Queen Graph, King Graph, Knight
    Graphs, Bishop Graph, and many generalizations. It also allows to avoid
    redondant code.

    INPUT:

    - ``dim_list`` -- an iterable object (list, set, dict) providing the
      dimensions `n_1, n_2, \ldots, n_d`, with `n_i \geq 1`, of the chessboard.

    - ``rook`` -- (default: ``True``) boolean value indicating if the chess
      piece is able to move as a rook, that is at any distance along a
      dimension.

    - ``rook_radius`` -- (default: None) integer value restricting the rook-like
      movements to distance at most `rook_radius`.

    - ``bishop`` -- (default: ``True``) boolean value indicating if the chess
      piece is able to move like a bishop, that is along diagonals.

    - ``bishop_radius`` -- (default: None) integer value restricting the
      bishop-like movements to distance at most `bishop_radius`.

    - ``knight`` -- (default: ``True``) boolean value indicating if the chess
      piece is able to move like a knight.

    - ``knight_x`` -- (default: 1) integer indicating the number on steps the
      chess piece moves in one dimension when moving like a knight.

    - ``knight_y`` -- (default: 2) integer indicating the number on steps the
      chess piece moves in the second dimension when moving like a knight.

    - ``relabel`` -- (default: ``False``) a boolean set to ``True`` if vertices
      must be relabeled as integers.

    OUTPUT:

    - A Graph build on a `d`-dimensional chessboard with prescribed dimensions,
      and with edges according given parameters.

    - A string encoding the dimensions. This is mainly useful for providing
      names to graphs.

    EXAMPLES:

    A `(2,2)`-King Graph is isomorphic to the complete graph on 4 vertices::

        sage: G, _ = graphs.ChessboardGraphGenerator( [2,2] )
        sage: G.is_isomorphic( graphs.CompleteGraph(4) )
        True

    A Rook's Graph in 2 dimensions is isomporphic to the Cartesian product of 2
    complete graphs::

        sage: G, _ = graphs.ChessboardGraphGenerator( [3,4], rook=True, rook_radius=None, bishop=False, knight=False )
        sage: H = ( graphs.CompleteGraph(3) ).cartesian_product( graphs.CompleteGraph(4) )
        sage: G.is_isomorphic(H)
        True

    TESTS:

    Giving dimensions less than 2::

        sage: graphs.ChessboardGraphGenerator( [0, 2] )
        Traceback (most recent call last):
        ...
        ValueError: The dimensions must be positive integers larger than 1.

    Giving non integer dimensions::

        sage: graphs.ChessboardGraphGenerator( [4.5, 2] )
        Traceback (most recent call last):
        ...
        ValueError: The dimensions must be positive integers larger than 1.

    Giving too few dimensions::

        sage: graphs.ChessboardGraphGenerator( [2] )
        Traceback (most recent call last):
        ...
        ValueError: The chessboard must have at least 2 dimensions.

    Giving a non-iterable object as first parameter::

        sage: graphs.ChessboardGraphGenerator( 2, 3 )
        Traceback (most recent call last):
        ...
        TypeError: The first parameter must be an iterable object.

    Giving too small rook radius::

        sage: graphs.ChessboardGraphGenerator( [2, 3], rook=True, rook_radius=0 )
        Traceback (most recent call last):
        ...
        ValueError: The rook_radius must be either None or have an integer value >= 1.

    Giving wrong values for knights movements::

        sage: graphs.ChessboardGraphGenerator( [2, 3], rook=False, bishop=False, knight=True, knight_x=1, knight_y=-1 )
        Traceback (most recent call last):
        ...
        ValueError: The knight_x and knight_y values must be integers of value >= 1.
    """
    from sage.rings.integer_ring import ZZ

    # We decode the dimensions of the chessboard
    try:
        dim = list(dim_list)
        nb_dim = len(dim)
    except TypeError:
        raise TypeError('The first parameter must be an iterable object.')
    if nb_dim < 2:
        raise ValueError('The chessboard must have at least 2 dimensions.')
    if any(a not in ZZ or a < 1 for a in dim):
        raise ValueError(
            'The dimensions must be positive integers larger than 1.')
    dimstr = str(tuple(dim))

    # We check the radius toward neighbors
    if rook:
        if rook_radius is None:
            rook_radius = max(dim)
        elif not rook_radius in ZZ or rook_radius < 1:
            raise ValueError(
                'The rook_radius must be either None or have an integer value >= 1.'
            )
    if bishop:
        if bishop_radius is None:
            bishop_radius = max(dim)
        elif not bishop_radius in ZZ or bishop_radius < 1:
            raise ValueError(
                'The bishop_radius must be either None or have an integer value >= 1.'
            )
    if knight and (not knight_x in ZZ or not knight_y in ZZ or knight_x < 1
                   or knight_y < 1):
        raise ValueError(
            'The knight_x and knight_y values must be integers of value >= 1.')

    # We build the set of vertices of the d-dimensionnal chessboard
    from itertools import product
    V = [list(x) for x in list(product(*[range(_) for _ in dim]))]

    from sage.combinat.combination import Combinations
    combin = Combinations(range(nb_dim), 2)

    from sage.graphs.graph import Graph
    G = Graph()
    for u in V:
        uu = tuple(u)
        G.add_vertex(uu)

        if rook:
            # We add edges to vertices we can reach when moving in one dimension
            for d in xrange(nb_dim):
                v = u[:]
                for k in xrange(v[d] + 1, min(dim[d], v[d] + 1 + rook_radius)):
                    v[d] = k
                    G.add_edge(uu, tuple(v))

        if bishop or knight:
            # We add edges to vertices we can reach when moving in two dimensions
            for dx, dy in combin:
                n = dim[dx]
                m = dim[dy]
                v = u[:]
                i = u[dx]
                j = u[dy]

                if bishop:
                    # Diagonal
                    for k in xrange(1, min(n - i, m - j, bishop_radius + 1)):
                        v[dx] = i + k
                        v[dy] = j + k
                        G.add_edge(uu, tuple(v))

                    # Anti-diagonal
                    for k in xrange(min(i, m - j - 1, bishop_radius)):
                        v[dx] = i - k - 1
                        v[dy] = j + k + 1
                        G.add_edge(uu, tuple(v))

                if knight:
                    # Moving knight_x in one dimension and knight_y in another
                    # dimension
                    if i + knight_y < n:
                        if j + knight_x < m:
                            v[dx] = i + knight_y
                            v[dy] = j + knight_x
                            G.add_edge(uu, tuple(v))
                        if j - knight_x >= 0:
                            v[dx] = i + knight_y
                            v[dy] = j - knight_x
                            G.add_edge(uu, tuple(v))
                    if j + knight_y < m:
                        if i + knight_x < n:
                            v[dx] = i + knight_x
                            v[dy] = j + knight_y
                            G.add_edge(uu, tuple(v))
                        if i - knight_x >= 0:
                            v[dx] = i - knight_x
                            v[dy] = j + knight_y
                            G.add_edge(uu, tuple(v))

    if relabel:
        G.relabel(inplace=True)
    return G, dimstr
Beispiel #34
0
def AffineOrthogonalPolarGraph(d, q, sign="+"):
    r"""
    Returns the affine polar graph `VO^+(d,q),VO^-(d,q)` or `VO(d,q)`.

    Affine Polar graphs are built from a `d`-dimensional vector space over
    `F_q`, and a quadratic form which is hyperbolic, elliptic or parabolic
    according to the value of ``sign``.

    Note that `VO^+(d,q),VO^-(d,q)` are strongly regular graphs, while `VO(d,q)`
    is not.

    For more information on Affine Polar graphs, see `Affine Polar
    Graphs page of Andries Brouwer's website
    <http://www.win.tue.nl/~aeb/graphs/VO.html>`_.

    INPUT:

    - ``d`` (integer) -- ``d`` must be even if ``sign != None``, and odd
      otherwise.

    - ``q`` (integer) -- a power of a prime number, as `F_q` must exist.

    - ``sign`` -- must be equal to ``"+"``, ``"-"``, or ``None`` to compute
      (respectively) `VO^+(d,q),VO^-(d,q)` or `VO(d,q)`. By default
      ``sign="+"``.

    .. NOTE::

        The graph `VO^\epsilon(d,q)` is the graph induced by the
        non-neighbors of a vertex in an :meth:`Orthogonal Polar Graph
        <OrthogonalPolarGraph>` `O^\epsilon(d+2,q)`.

    EXAMPLES:

    The :meth:`Brouwer-Haemers graph <BrouwerHaemersGraph>` is isomorphic to
    `VO^-(4,3)`::

        sage: g = graphs.AffineOrthogonalPolarGraph(4,3,"-")
        sage: g.is_isomorphic(graphs.BrouwerHaemersGraph())
        True

    Some examples from `Brouwer's table or strongly regular graphs
    <http://www.win.tue.nl/~aeb/graphs/srg/srgtab.html>`_::

        sage: g = graphs.AffineOrthogonalPolarGraph(6,2,"-"); g
        Affine Polar Graph VO^-(6,2): Graph on 64 vertices
        sage: g.is_strongly_regular(parameters=True)
        (64, 27, 10, 12)
        sage: g = graphs.AffineOrthogonalPolarGraph(6,2,"+"); g
        Affine Polar Graph VO^+(6,2): Graph on 64 vertices
        sage: g.is_strongly_regular(parameters=True)
        (64, 35, 18, 20)

    When ``sign is None``::

        sage: g = graphs.AffineOrthogonalPolarGraph(5,2,None); g
        Affine Polar Graph VO^-(5,2): Graph on 32 vertices
        sage: g.is_strongly_regular(parameters=True)
        False
        sage: g.is_regular()
        True
        sage: g.is_vertex_transitive()
        True
    """
    if sign in ["+", "-"]:
        s = 1 if sign == "+" else -1
        if d % 2 == 1:
            raise ValueError("d must be even when sign!=None")
    else:
        if d % 2 == 0:
            raise ValueError("d must be odd when sign==None")
        s = 0

    from sage.interfaces.gap import gap
    from sage.rings.finite_rings.constructor import FiniteField
    from sage.modules.free_module import VectorSpace
    from sage.matrix.constructor import Matrix
    from sage.libs.gap.libgap import libgap
    from itertools import combinations

    M = Matrix(
        libgap.InvariantQuadraticForm(libgap.GeneralOrthogonalGroup(
            s, d, q))['matrix'])
    F = libgap.GF(q).sage()
    V = list(VectorSpace(F, d))

    G = Graph()
    G.add_vertices([tuple(_) for _ in V])
    for x, y in combinations(V, 2):
        if not (x - y) * M * (x - y):
            G.add_edge(tuple(x), tuple(y))

    G.name("Affine Polar Graph VO^" + str('+' if s == 1 else '-') + "(" +
           str(d) + "," + str(q) + ")")
    G.relabel()
    return G
Beispiel #35
0
def PermutationGraph(second_permutation, first_permutation=None):
    r"""
    Build a permutation graph from one permutation or from two lists.

    Definition:

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

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

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

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

    INPUT:

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

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

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

    .. SEEALSO::

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

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

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

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

    EXAMPLES::

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

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

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

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

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

    TESTS::

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

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

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

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

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

    return g
Beispiel #36
0
def RandomBipartite(n1,n2, p):
    r"""
    Returns a bipartite graph with `n1+n2` vertices
    such that any edge from `[n1]` to `[n2]` exists
    with probability `p`.

    INPUT:

        - ``n1,n2`` : Cardinalities of the two sets
        - ``p``   : Probability for an edge to exist


    EXAMPLE::

        sage: g=graphs.RandomBipartite(5,2,0.5)
        sage: g.vertices()
        [(0, 0), (0, 1), (0, 2), (0, 3), (0, 4), (1, 0), (1, 1)]

    TESTS::

        sage: g=graphs.RandomBipartite(5,-3,0.5)
        Traceback (most recent call last):
        ...
        ValueError: n1 and n2 should be integers strictly greater than 0
        sage: g=graphs.RandomBipartite(5,3,1.5)
        Traceback (most recent call last):
        ...
        ValueError: Parameter p is a probability, and so should be a real value between 0 and 1

    Trac ticket #12155::

        sage: graphs.RandomBipartite(5,6,.2).complement()
        complement(Random bipartite graph of size 5+6 with edge probability 0.200000000000000): Graph on 11 vertices
    """
    if not (p>=0 and p<=1):
        raise ValueError, "Parameter p is a probability, and so should be a real value between 0 and 1"
    if not (n1>0 and n2>0):
        raise ValueError, "n1 and n2 should be integers strictly greater than 0"

    from numpy.random import uniform

    g=Graph(name="Random bipartite graph of size "+str(n1) +"+"+str(n2)+" with edge probability "+str(p))

    S1=[(0,i) for i in range(n1)]
    S2=[(1,i) for i in range(n2)]
    g.add_vertices(S1)
    g.add_vertices(S2)

    for w in range(n2):
        for v in range(n1):
            if uniform()<=p :
                g.add_edge((0,v),(1,w))

    pos = {}
    for i in range(n1):
        pos[(0,i)] = (0, i/(n1-1.0))
    for i in range(n2):
        pos[(1,i)] = (1, i/(n2-1.0))

    g.set_pos(pos)

    return g
Beispiel #37
0
    def restricted_automorphism_group(self, vertex_labels=None):
        r"""
        Return the restricted automorphism group.

        First, let the linear automorphism group be the subgroup of
        the Euclidean group `E(d) = GL(d,\RR) \ltimes \RR^d`
        preserving the `d`-dimensional polyhedron. The Euclidean group
        acts in the usual way `\vec{x}\mapsto A\vec{x}+b` on the
        ambient space. The restricted automorphism group is the
        subgroup of the linear automorphism group generated by
        permutations of vertices. If the polytope is full-dimensional,
        it is equal to the full (unrestricted) automorphism group.

        INPUT:

        - ``vertex_labels`` -- a tuple or ``None`` (default). The
          labels of the vertices that will be used in the output
          permutation group. By default, the vertices are used
          themselves.

        OUTPUT:

        A
        :class:`PermutationGroup<sage.groups.perm_gps.permgroup.PermutationGroup_generic>`
        acting on the vertices (or the ``vertex_labels``, if
        specified).

        REFERENCES:

        [BSS2009]_

        EXAMPLES::

            sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL
            sage: Z3square = LatticePolytope_PPL((0,0), (1,2), (2,1), (3,3))
            sage: Z3square.restricted_automorphism_group(vertex_labels=(1,2,3,4))
            Permutation Group with generators [(2,3), (1,2)(3,4), (1,4)]
            sage: G = Z3square.restricted_automorphism_group(); G
            Permutation Group with generators [((1,2),(2,1)),
            ((0,0),(1,2))((2,1),(3,3)), ((0,0),(3,3))]
            sage: tuple(G.domain()) == Z3square.vertices()
            True
            sage: G.orbit(Z3square.vertices()[0])
            ((0, 0), (1, 2), (3, 3), (2, 1))

            sage: cell24 = LatticePolytope_PPL(
            ....: (1,0,0,0),(0,1,0,0),(0,0,1,0),(0,0,0,1),(1,-1,-1,1),(0,0,-1,1),
            ....: (0,-1,0,1),(-1,0,0,1),(1,0,0,-1),(0,1,0,-1),(0,0,1,-1),(-1,1,1,-1),
            ....: (1,-1,-1,0),(0,0,-1,0),(0,-1,0,0),(-1,0,0,0),(1,-1,0,0),(1,0,-1,0),
            ....: (0,1,1,-1),(-1,1,1,0),(-1,1,0,0),(-1,0,1,0),(0,-1,-1,1),(0,0,0,-1))
            sage: cell24.restricted_automorphism_group().cardinality()
            1152
        """
        if not self.is_full_dimensional():
            return self.affine_lattice_polytope().\
                restricted_automorphism_group(vertex_labels=vertex_labels)
        if vertex_labels is None:
            vertex_labels = self.vertices()
        from sage.groups.perm_gps.permgroup import PermutationGroup
        from sage.graphs.graph import Graph
        # good coordinates for the vertices
        v_list = []
        for v in self.minimized_generators():
            assert v.divisor().is_one()
            v_coords = (1,) + v.coefficients()
            v_list.append(vector(v_coords))

        # Finally, construct the graph
        Qinv = sum( v.column() * v.row() for v in v_list ).inverse()
        G = Graph()
        for i in range(0,len(v_list)):
            for j in range(i+1,len(v_list)):
                v_i = v_list[i]
                v_j = v_list[j]
                G.add_edge(vertex_labels[i], vertex_labels[j], v_i * Qinv * v_j)
        return G.automorphism_group(edge_labels=True)
Beispiel #38
0
def rectangular_diagram(gauss):
    """
    Return a rectangular diagram and crossing coordinates.

    INPUT:

    - signed Gauss code

    OUTPUT:

    - graph whose vertices are the corners of the knot diagram

    - positions of the horizontal and vertical crossings

    EXAMPLES::

        sage: from sage.knots.gauss_code import rectangular_diagram
        sage: G = [4,1,5,2,1,3,6,4,2,5,3,6]
        sage: rectangular_diagram(G)
        (Graph on 18 vertices, ([(1, 3), (3, 7), (4, 6), (6, 2)],
         [(4, 3), (6, 5)]))

        sage: G = [1,2,3,1,2,3]
        sage: rectangular_diagram(G)
        (Graph on 10 vertices, ([(1, 3), (2, 2)], [(2, 1)]))

    TESTS::

        sage: rectangular_diagram([])
        (Graph on 4 vertices, ([], []))
    """
    if not gauss:
        G = Graph()
        verts = [(0, 0), (1, 0), (1, 1), (0, 1)]
        for i in range(4):
            G.add_edge(verts[i], verts[(i + 1) % 4])
        G.set_pos({v: v for v in verts})
        return G, ([], [])

    changed, positive, negative, ori = recover_orientations(gauss)

    n = len(changed)
    G = Graph()
    horizontal = []
    vertical = []
    for a, b in positive:
        G.add_edge((a, a), (b, a))
        G.add_edge((b, a), (b, b))
        G.add_edge((a + 1, a + 1), (b + 1, a + 1))
        G.add_edge((b + 1, a + 1), (b + 1, b + 1))
        letter = changed[a]
        if b != a + 1:
            if ori[letter - 1] == -1:
                horizontal.append((b, a + 1))
            else:
                vertical.append((b, a + 1))
    for a, b in negative:
        G.add_edge((a, a), (a, b))
        G.add_edge((a, b), (b, b))
        G.add_edge((a + 1, a + 1), (a + 1, b + 1))
        G.add_edge((a + 1, b + 1), (b + 1, b + 1))
        letter = changed[a]
        if a + 1 != b:
            if ori[letter - 1] == 1:
                horizontal.append((a + 1, b))
            else:
                vertical.append((a + 1, b))

    # closing the loop on either side
    total = positive + negative
    debut = next(iter(b for a, b in total if a == 0))
    fin = next(iter(n - 1 - a for a, b in total if b == n - 1))
    if debut > fin:
        G.add_edge((0, 0), (0, n))
        G.add_edge((0, n), (n, n))
    else:
        G.add_edge((0, 0), (n, 0))
        G.add_edge((n, 0), (n, n))

    # remove useless vertices and double edges
    for a, b in list(G):
        (x, y), (xx, yy) = sorted(G.neighbors((a, b)))
        if x == xx == a:
            G.delete_vertex((a, b))
            G.add_edge((a, y), (a, yy))
        elif y == yy == b:
            G.delete_vertex((a, b))
            G.add_edge((x, b), (xx, b))

    # renumber lines and columns to remove empty ones
    lignes = sorted(set(a for a, _ in G))
    colonnes = sorted(set(b for _, b in G))
    d_lig = {a: i for i, a in enumerate(lignes)}
    d_col = {b: i for i, b in enumerate(colonnes)}

    horizontal = sorted((d_lig[a], d_col[b]) for a, b in horizontal)
    vertical = sorted((d_lig[a], d_col[b]) for a, b in vertical)

    def standard(ab):
        a, b = ab
        return (d_lig[a], d_col[b])

    G.relabel(standard)

    # setting the positions
    positions = {ab: ab for ab in G}
    G.set_pos(positions)
    return G, (horizontal, vertical)
Beispiel #39
0
def is_partial_cube(G, certificate=False):
    r"""
    Test whether the given graph is a partial cube.

    A partial cube is a graph that can be isometrically embedded into a
    hypercube, i.e., its vertices can be labelled with (0,1)-vectors of some
    fixed length such that the distance between any two vertices in the graph
    equals the Hamming distance of their labels.

    Originally written by D. Eppstein for the PADS library
    (http://www.ics.uci.edu/~eppstein/PADS/), see also
    [Eppstein2008]_.  The algorithm runs in `O(n^2)` time, where `n`
    is the number of vertices. See the documentation of
    :mod:`~sage.graphs.partial_cube` for an overview of the algorithm.

    INPUT:

    - ``certificate`` (boolean; ``False``) -- The function returns ``True``
      or ``False`` according to the graph, when ``certificate = False``. When
      ``certificate = True`` and the graph is a partial cube, the function
      returns ``(True, mapping)``, where ``mapping`` is an isometric mapping of
      the vertices of the graph to the vertices of a hypercube ((0, 1)-strings
      of a fixed length). When ``certificate = True`` and the graph is not a
      partial cube, ``(False, None)`` is returned.

    EXAMPLES:

    The Petersen graph is not a partial cube::

        sage: g = graphs.PetersenGraph()
        sage: g.is_partial_cube()
        False

    All prisms are partial cubes::

        sage: g = graphs.CycleGraph(10).cartesian_product(graphs.CompleteGraph(2))
        sage: g.is_partial_cube()
        True

    TESTS:

    The returned mapping is an isometric embedding into a hypercube::

        sage: g = graphs.DesarguesGraph()
        sage: _, m = g.is_partial_cube(certificate = True)
        sage: m # random
        {0: '00000',
         1: '00001',
         2: '00011',
         3: '01011',
         4: '11011',
         5: '11111',
         6: '11110',
         7: '11100',
         8: '10100',
         9: '00100',
         10: '01000',
         11: '10001',
         12: '00111',
         13: '01010',
         14: '11001',
         15: '10111',
         16: '01110',
         17: '11000',
         18: '10101',
         19: '00110'}
        sage: all(all(g.distance(u, v) == len([i for i in range(len(m[u])) if m[u][i] != m[v][i]]) for v in m) for u in m)
        True

    A graph without vertices is trivially a partial cube::

        sage: Graph().is_partial_cube(certificate = True)
        (True, {})

    """
    G._scream_if_not_simple()

    if G.order() == 0:
        if certificate:
            return (True, {})
        else:
            return True

    if certificate:
        fail = (False, None)
    else:
        fail = False

    if not G.is_connected():
        return fail
    n = G.order()

    # Initial sanity check: are there few enough edges?
    # Needed so that we don't try to use union-find on a dense
    # graph and incur superquadratic runtimes.
    if 1 << (2*G.size()//n) > n:
        return fail

    # Check for bipartiteness.
    # This ensures also that each contraction will be bipartite.
    if not G.is_bipartite():
        return fail

    # Set up data structures for algorithm:
    # - contracted: contracted graph at current stage of algorithm
    # - unionfind: union find data structure representing known edge equivalences
    # - available: limit on number of remaining available labels
    from sage.graphs.digraph import DiGraph
    from sage.graphs.graph import Graph
    from sage.sets.disjoint_set import DisjointSet
    contracted = DiGraph({v: {w: (v, w) for w in G[v]} for v in G})
    unionfind = DisjointSet(contracted.edges(labels = False))
    available = n-1

    # Main contraction loop in place of the original algorithm's recursion
    while contracted.order() > 1:
        # Find max degree vertex in contracted, and update label limit
        deg, root = max((contracted.out_degree(v), v) for v in contracted)
        if deg > available:
            return fail
        available -= deg

        # Set up bitvectors on vertices
        bitvec = {v:0 for v in contracted}
        neighbors = {}
        for i, neighbor in enumerate(contracted[root]):
            bitvec[neighbor] = 1 << i
            neighbors[1 << i] = neighbor

        # Breadth first search to propagate bitvectors to the rest of the graph
        for level in breadth_first_level_search(contracted, root):
            for v in level:
                for w in level[v]:
                    bitvec[w] |= bitvec[v]

        # Make graph of labeled edges and union them together
        labeled = Graph([contracted.vertices(), []])
        for v, w in contracted.edge_iterator(labels = False):
            diff = bitvec[v]^bitvec[w]
            if not diff or bitvec[w] &~ bitvec[v] == 0:
                continue    # zero edge or wrong direction
            if diff not in neighbors:
                return fail
            neighbor = neighbors[diff]
            unionfind.union(contracted.edge_label(v, w),
                            contracted.edge_label(root, neighbor))
            unionfind.union(contracted.edge_label(w, v),
                            contracted.edge_label(neighbor, root))
            labeled.add_edge(v, w)

        # Map vertices to components of labeled-edge graph
        component = {}
        for i, SCC in enumerate(labeled.connected_components()):
            for v in SCC:
                component[v] = i

        # generate new compressed subgraph
        newgraph = DiGraph()
        for v, w, t in contracted.edge_iterator():
            if bitvec[v] == bitvec[w]:
                vi = component[v]
                wi = component[w]
                if vi == wi:
                    return fail
                if newgraph.has_edge(vi, wi):
                    unionfind.union(newgraph.edge_label(vi, wi), t)
                else:
                    newgraph.add_edge(vi, wi, t)
        contracted = newgraph

    # Make a digraph with edges labeled by the equivalence classes in unionfind
    g = DiGraph({v: {w: unionfind.find((v, w)) for w in G[v]} for v in G})

    # Associates to a vertex the token that acts on it, an check that
    # no two edges on a single vertex have the same label
    action = {}
    for v in g:
        action[v] = set(t for _, _, t in g.edge_iterator(v))
        if len(action[v]) != g.out_degree(v):
            return fail

    # Associate every token to its reverse
    reverse = {}
    for v, w, t in g.edge_iterator():
        rt = g.edge_label(w, v)
        reverse[t] = rt
        reverse[rt] = t

    current = initialState = next(g.vertex_iterator())

    # A token T is said to be 'active' for a vertex u if it takes u
    # one step closer to the source in terms of distance. The 'source'
    # is initially 'initialState'. See the module's documentation for
    # more explanations.

    # Find list of tokens that lead to the initial state
    activeTokens = set()
    for level in breadth_first_level_search(g, initialState):
        for v in level:
            for w in level[v]:
                activeTokens.add(g.edge_label(w, v))
    for t in activeTokens:
        if reverse[t] in activeTokens:
            return fail
    activeTokens = list(activeTokens)

    # Rest of data structure: point from states to list and list to states
    state_to_active_token = {v: -1 for v in g}
    token_to_states = [[] for i in activeTokens] # (i.e. vertices on which each token acts)

    def scan(v):
        """Find the next token that is effective for v."""
        a = next(i for i in range(state_to_active_token[v]+1, len(activeTokens))
                 if activeTokens[i] is not None
                    and activeTokens[i] in action[v])
        state_to_active_token[v] = a
        token_to_states[a].append(v)

    # Initialize isometric embedding into a hypercube
    if certificate:
        dim = 0
        tokmap = {}
        for t in reverse:
            if t not in tokmap:
                tokmap[t] = tokmap[reverse[t]] = 1 << dim
                dim += 1
        embed = {initialState: 0}

    # Set initial active states
    for v in g:
        if v != current:
            try:
                scan(v)
            except StopIteration:
                return fail

    # Traverse the graph, maintaining active tokens
    for prev, current, fwd in depth_first_traversal(g, initialState):
        if not fwd:
            prev, current = current, prev
        elif certificate:
            embed[current] = embed[prev] ^ tokmap[g.edge_label(prev, current)]

        # Add token to end of list, point to it from old state
        activeTokens.append(g.edge_label(prev, current))
        state_to_active_token[prev] = len(activeTokens) - 1
        token_to_states.append([prev])

        # Inactivate reverse token, find new token for its states
        #
        # (the 'active' token of 'current' is necessarily the label of
        #  (current, previous))
        activeTokens[state_to_active_token[current]] = None
        for v in token_to_states[state_to_active_token[current]]:
            if v != current:
                try:
                    scan(v)
                except StopIteration:
                    return fail

    # All checks passed, return the result
    if certificate:
        format = "{0:0%db}" % dim
        return (True, {v: format.format(l) for v, l in embed.items()})
    else:
        return True
Beispiel #40
0
def RandomTree(n):
    """
    Returns a random tree on `n` nodes numbered `0` through `n-1`.

    By Cayley's theorem, there are `n^{n-2}` trees with vertex
    set `\{0,1,...,n-1\}`. This constructor chooses one of these uniformly
    at random.

    ALGORITHM:

    The algoritm works by generating an `(n-2)`-long
    random sequence of numbers chosen independently and uniformly
    from `\{0,1,\ldots,n-1\}` and then applies an inverse
    Prufer transformation.

    INPUT:

    -  ``n`` - number of vertices in the tree

    EXAMPLE::

        sage: G = graphs.RandomTree(10)
        sage: G.is_tree()
        True
        sage: G.show() # long

    TESTS:

    Ensuring that we encounter no unexpected surprise ::

        sage: all( graphs.RandomTree(10).is_tree()
        ....:      for i in range(100) )
        True

    """
    from sage.misc.prandom import randint
    g = Graph()

    # create random Prufer code
    code = [randint(0, n - 1) for i in xrange(n - 2)]

    # We count the number of symbols of each type.
    # count[k] is the no. of times k appears in code
    #
    # (count[k] is set to -1 when the corresponding vertex is not
    # available anymore)
    count = [0 for i in xrange(n)]
    for k in code:
        count[k] += 1

    g.add_vertices(range(n))

    for s in code:
        for x in range(n):
            if count[x] == 0:
                break

        count[x] = -1
        g.add_edge(x, s)
        count[s] -= 1

    # Adding as an edge the last two available vertices
    last_edge = [v for v in range(n) if count[v] != -1]
    g.add_edge(last_edge)

    return g
Beispiel #41
0
def _check_pbd(B,v,S):
    r"""
    Checks that ``B`` is a PBD on ``v`` points with given block sizes ``S``.

    The points of the balanced incomplete block design are implicitely assumed
    to be `\{0, ..., v-1\}`.

    INPUT:

    - ``B`` -- a list of blocks

    - ``v`` (integer) -- number of points

    - ``S`` -- list of integers `\geq 2`.

    EXAMPLE::

        sage: designs.balanced_incomplete_block_design(40,4).blocks() # indirect doctest
        [[0, 1, 2, 12], [0, 3, 6, 9], [0, 4, 8, 10],
         [0, 5, 7, 11], [0, 13, 26, 39], [0, 14, 25, 28],
         [0, 15, 27, 38], [0, 16, 22, 32], [0, 17, 23, 34],
        ...
        sage: from sage.combinat.designs.bibd import _check_pbd
        sage: _check_pbd([[1],[]],1,[1,0])
        Traceback (most recent call last):
        ...
        RuntimeError: All integers of S must be >=2

    TESTS::

        sage: _check_pbd([[1,2]],2,[2])
        Traceback (most recent call last):
        ...
        RuntimeError: The PBD covers a point 2 which is not in {0, 1}
        sage: _check_pbd([[1,2]]*2,2,[2])
        Traceback (most recent call last):
        ...
        RuntimeError: The pair (1,2) is covered more than once
        sage: _check_pbd([],2,[2])
        Traceback (most recent call last):
        ...
        RuntimeError: The pair (0,1) is not covered
        sage: _check_pbd([[1,2],[1]],2,[2])
        Traceback (most recent call last):
        ...
        RuntimeError: A block has size 1 while S=[2]
    """
    from itertools import combinations
    from sage.graphs.graph import Graph

    for X in B:
        if len(X) not in S:
            raise RuntimeError("A block has size {} while S={}".format(len(X),S))

    if any(x < 2 for x in S):
        raise RuntimeError("All integers of S must be >=2")

    if v == 0 or v == 1:
        if B:
            raise RuntimeError("A PBD with v<=1 is expected to be empty.")

    g = Graph()
    g.add_vertices(range(v))
    m = 0
    for X in B:
        for i,j in combinations(X,2):
            g.add_edge(i,j)
            m_tmp = g.size()
            if m_tmp != m+1:
                raise RuntimeError("The pair ({},{}) is covered more than once".format(i,j))
            m = m_tmp

    if g.vertices() != range(v):
        from sage.sets.integer_range import IntegerRange
        p = (set(g.vertices())-set(range(v))).pop()
        raise RuntimeError("The PBD covers a point {} which is not in {}".format(p,IntegerRange(v)))

    if not g.is_clique():
        for p1 in g:
            if g.degree(p1) != v-1:
                break
        neighbors = g.neighbors(p1)+[p1]
        p2 = (set(g.vertices())-set(neighbors)).pop()
        raise RuntimeError("The pair ({},{}) is not covered".format(p1,p2))

    return B
Beispiel #42
0
def ChessboardGraphGenerator(dim_list,
                             rook = True,    rook_radius = None,
                             bishop = True,  bishop_radius = None,
                             knight = True, knight_x = 1, knight_y = 2,
                             relabel = False):
    r"""
    Returns a Graph built on a `d`-dimensional chessboard with prescribed
    dimensions and interconnections.

    This function allows to generate many kinds of graphs corresponding to legal
    movements on a `d`-dimensional chessboard: Queen Graph, King Graph, Knight
    Graphs, Bishop Graph, and many generalizations. It also allows to avoid
    redondant code.

    INPUT:

    - ``dim_list`` -- an iterable object (list, set, dict) providing the
      dimensions `n_1, n_2, \ldots, n_d`, with `n_i \geq 1`, of the chessboard.

    - ``rook`` -- (default: ``True``) boolean value indicating if the chess
      piece is able to move as a rook, that is at any distance along a
      dimension.

    - ``rook_radius`` -- (default: None) integer value restricting the rook-like
      movements to distance at most `rook_radius`.

    - ``bishop`` -- (default: ``True``) boolean value indicating if the chess
      piece is able to move like a bishop, that is along diagonals.

    - ``bishop_radius`` -- (default: None) integer value restricting the
      bishop-like movements to distance at most `bishop_radius`.

    - ``knight`` -- (default: ``True``) boolean value indicating if the chess
      piece is able to move like a knight.

    - ``knight_x`` -- (default: 1) integer indicating the number on steps the
      chess piece moves in one dimension when moving like a knight.

    - ``knight_y`` -- (default: 2) integer indicating the number on steps the
      chess piece moves in the second dimension when moving like a knight.

    - ``relabel`` -- (default: ``False``) a boolean set to ``True`` if vertices
      must be relabeled as integers.

    OUTPUT:

    - A Graph build on a `d`-dimensional chessboard with prescribed dimensions,
      and with edges according given parameters.

    - A string encoding the dimensions. This is mainly useful for providing
      names to graphs.

    EXAMPLES:

    A `(2,2)`-King Graph is isomorphic to the complete graph on 4 vertices::

        sage: G, _ = graphs.ChessboardGraphGenerator( [2,2] )
        sage: G.is_isomorphic( graphs.CompleteGraph(4) )
        True

    A Rook's Graph in 2 dimensions is isomporphic to the Cartesian product of 2
    complete graphs::

        sage: G, _ = graphs.ChessboardGraphGenerator( [3,4], rook=True, rook_radius=None, bishop=False, knight=False )
        sage: H = ( graphs.CompleteGraph(3) ).cartesian_product( graphs.CompleteGraph(4) )
        sage: G.is_isomorphic(H)
        True

    TESTS:

    Giving dimensions less than 2::

        sage: graphs.ChessboardGraphGenerator( [0, 2] )
        Traceback (most recent call last):
        ...
        ValueError: The dimensions must be positive integers larger than 1.

    Giving non integer dimensions::

        sage: graphs.ChessboardGraphGenerator( [4.5, 2] )
        Traceback (most recent call last):
        ...
        ValueError: The dimensions must be positive integers larger than 1.

    Giving too few dimensions::

        sage: graphs.ChessboardGraphGenerator( [2] )
        Traceback (most recent call last):
        ...
        ValueError: The chessboard must have at least 2 dimensions.

    Giving a non-iterable object as first parameter::

        sage: graphs.ChessboardGraphGenerator( 2, 3 )
        Traceback (most recent call last):
        ...
        TypeError: The first parameter must be an iterable object.

    Giving too small rook radius::

        sage: graphs.ChessboardGraphGenerator( [2, 3], rook=True, rook_radius=0 )
        Traceback (most recent call last):
        ...
        ValueError: The rook_radius must be either None or have an integer value >= 1.

    Giving wrong values for knights movements::

        sage: graphs.ChessboardGraphGenerator( [2, 3], rook=False, bishop=False, knight=True, knight_x=1, knight_y=-1 )
        Traceback (most recent call last):
        ...
        ValueError: The knight_x and knight_y values must be integers of value >= 1.
    """
    from sage.rings.integer_ring import ZZ

    # We decode the dimensions of the chessboard
    try:
        dim = list(dim_list)
        nb_dim = len(dim)
    except TypeError:
        raise TypeError('The first parameter must be an iterable object.')
    if nb_dim < 2:
        raise ValueError('The chessboard must have at least 2 dimensions.')
    if any(a not in ZZ or a < 1 for a in dim):
        raise ValueError('The dimensions must be positive integers larger than 1.')
    dimstr = str(tuple(dim))

    # We check the radius toward neighbors
    if rook:
        if rook_radius is None:
            rook_radius = max(dim)
        elif not rook_radius in ZZ or rook_radius < 1:
            raise ValueError('The rook_radius must be either None or have an integer value >= 1.')
    if bishop:
        if bishop_radius is None:
            bishop_radius = max(dim)
        elif not bishop_radius in ZZ or bishop_radius < 1:
            raise ValueError('The bishop_radius must be either None or have an integer value >= 1.')
    if knight and ( not knight_x in ZZ or not knight_y in ZZ or knight_x < 1 or knight_y < 1 ):
        raise ValueError('The knight_x and knight_y values must be integers of value >= 1.')

    # We build the set of vertices of the d-dimensionnal chessboard
    from itertools import product
    V = [list(x) for x in list(product(*[range(_) for _ in dim]))]

    from sage.combinat.combination import Combinations
    combin = Combinations(range(nb_dim),2)

    from sage.graphs.graph import Graph
    G = Graph()
    for u in V:
        uu = tuple(u)
        G.add_vertex(uu)

        if rook:
            # We add edges to vertices we can reach when moving in one dimension
            for d in xrange(nb_dim):
                v = u[:]
                for k in xrange(v[d]+1, min(dim[d],v[d]+1+rook_radius)):
                    v[d] = k
                    G.add_edge( uu, tuple(v) )

        if bishop or knight:
            # We add edges to vertices we can reach when moving in two dimensions
            for dx,dy in combin:
                n = dim[dx]
                m = dim[dy]
                v = u[:]
                i = u[dx]
                j = u[dy]

                if bishop:
                    # Diagonal
                    for k in xrange(1, min(n-i,m-j,bishop_radius+1)):
                        v[dx] = i+k
                        v[dy] = j+k
                        G.add_edge( uu, tuple(v) )

                    # Anti-diagonal
                    for k in xrange(min(i, m-j-1, bishop_radius)):
                        v[dx] = i-k-1
                        v[dy] = j+k+1
                        G.add_edge( uu, tuple(v) )

                if knight:
                    # Moving knight_x in one dimension and knight_y in another
                    # dimension
                    if i+knight_y < n:
                        if j+knight_x < m:
                            v[dx] = i+knight_y
                            v[dy] = j+knight_x
                            G.add_edge( uu, tuple(v) )
                        if j-knight_x >= 0:
                            v[dx] = i+knight_y
                            v[dy] = j-knight_x
                            G.add_edge( uu, tuple(v) )
                    if j+knight_y < m:
                        if i+knight_x < n:
                            v[dx] = i+knight_x
                            v[dy] = j+knight_y
                            G.add_edge( uu, tuple(v) )
                        if i-knight_x >= 0:
                            v[dx] = i-knight_x
                            v[dy] = j+knight_y
                            G.add_edge( uu, tuple(v) )

    if relabel:
        G.relabel( inplace=True )
    return G, dimstr
Beispiel #43
0
def ToleranceGraph(tolrep):
    r"""
    Returns the graph generated by the tolerance representation ``tolrep``.

    The tolerance representation ``tolrep`` is described by the list
    `((l_0,r_0,t_0), (l_1,r_1,t_1), ..., (l_k,r_k,t_k))` where `I_i = (l_i,r_i)`
    denotes a closed interval on the real line with `l_i < r_i` and `t_i` a
    positive value, called tolerance. This representation generates the
    tolerance graph with the vertex set {0,1, ..., k} and the edge set `{(i,j):
    |I_i \cap I_j| \ge \min{t_i, t_j}}` where `|I_i \cap I_j|` denotes the
    length of the intersection of `I_i` and `I_j`.

    INPUT:

    - ``tolrep`` -- list of triples `(l_i,r_i,t_i)` where `(l_i,r_i)` denotes a
      closed interval on the real line and `t_i` a positive value.

    .. NOTE::

        The vertices are named 0, 1, ..., k. The tolerance representation used
        to create the graph is saved with the graph and can be recovered using
        ``get_vertex()`` or ``get_vertices()``.

    EXAMPLE:

    The following code creates a tolerance representation ``tolrep``, generates
    its tolerance graph ``g``, and applies some checks::

        sage: tolrep = [(1,4,3),(1,2,1),(2,3,1),(0,3,3)]
        sage: g = graphs.ToleranceGraph(tolrep)
        sage: g.get_vertex(3)
        (0, 3, 3)
        sage: neigh = g.neighbors(3)
        sage: for v in neigh: print g.get_vertex(v)
        (1, 2, 1)
        (2, 3, 1)
        sage: g.is_interval()
        False
        sage: g.is_weakly_chordal()
        True

    The intervals in the list need not be distinct ::

        sage: tolrep2 = [(0,4,5),(1,2,1),(2,3,1),(0,4,5)]
        sage: g2 = graphs.ToleranceGraph(tolrep2)
        sage: g2.get_vertices()
        {0: (0, 4, 5), 1: (1, 2, 1), 2: (2, 3, 1), 3: (0, 4, 5)}
        sage: g2.is_isomorphic(g)
        True

    Real values are also allowed ::

        sage: tolrep = [(0.1,3.3,4.4),(1.1,2.5,1.1),(1.4,4.4,3.3)]
        sage: g = graphs.ToleranceGraph(tolrep)
        sage: g.is_isomorphic(graphs.PathGraph(3))
        True

    TEST:

    Giving negative third value::

        sage: tolrep = [(0.1,3.3,-4.4),(1.1,2.5,1.1),(1.4,4.4,3.3)]
        sage: g = graphs.ToleranceGraph(tolrep)
        Traceback (most recent call last):
        ...
        ValueError: Invalid tolerance representation at position 0; third value must be positive!
    """
    n = len(tolrep)

    for i in xrange(n):
        if tolrep[i][2] <= 0:
            raise ValueError("Invalid tolerance representation at position "+str(i)+"; third value must be positive!")

    g = Graph(n)

    for i in xrange(n-1):
        li,ri,ti = tolrep[i]
        for j in xrange(i+1,n):
            lj,rj,tj = tolrep[j]
            if min(ri,rj) - max(li,lj) >= min(ti,tj):
                g.add_edge(i,j)

    rep = dict( zip(range(n),tolrep) )
    g.set_vertices(rep)

    return g
Beispiel #44
0
def AffineOrthogonalPolarGraph(d,q,sign="+"):
    r"""
    Returns the affine polar graph `VO^+(d,q),VO^-(d,q)` or `VO(d,q)`.

    Affine Polar graphs are built from a `d`-dimensional vector space over
    `F_q`, and a quadratic form which is hyperbolic, elliptic or parabolic
    according to the value of ``sign``.

    Note that `VO^+(d,q),VO^-(d,q)` are strongly regular graphs, while `VO(d,q)`
    is not.

    For more information on Affine Polar graphs, see `Affine Polar
    Graphs page of Andries Brouwer's website
    <http://www.win.tue.nl/~aeb/graphs/VO.html>`_.

    INPUT:

    - ``d`` (integer) -- ``d`` must be even if ``sign != None``, and odd
      otherwise.

    - ``q`` (integer) -- a power of a prime number, as `F_q` must exist.

    - ``sign`` -- must be equal to ``"+"``, ``"-"``, or ``None`` to compute
      (respectively) `VO^+(d,q),VO^-(d,q)` or `VO(d,q)`. By default
      ``sign="+"``.

    .. NOTE::

        The graph `VO^\epsilon(d,q)` is the graph induced by the
        non-neighbors of a vertex in an :meth:`Orthogonal Polar Graph
        <OrthogonalPolarGraph>` `O^\epsilon(d+2,q)`.

    EXAMPLES:

    The :meth:`Brouwer-Haemers graph <BrouwerHaemersGraph>` is isomorphic to
    `VO^-(4,3)`::

        sage: g = graphs.AffineOrthogonalPolarGraph(4,3,"-")
        sage: g.is_isomorphic(graphs.BrouwerHaemersGraph())
        True

    Some examples from `Brouwer's table or strongly regular graphs
    <http://www.win.tue.nl/~aeb/graphs/srg/srgtab.html>`_::

        sage: g = graphs.AffineOrthogonalPolarGraph(6,2,"-"); g
        Affine Polar Graph VO^-(6,2): Graph on 64 vertices
        sage: g.is_strongly_regular(parameters=True)
        (64, 27, 10, 12)
        sage: g = graphs.AffineOrthogonalPolarGraph(6,2,"+"); g
        Affine Polar Graph VO^+(6,2): Graph on 64 vertices
        sage: g.is_strongly_regular(parameters=True)
        (64, 35, 18, 20)

    When ``sign is None``::

        sage: g = graphs.AffineOrthogonalPolarGraph(5,2,None); g
        Affine Polar Graph VO^-(5,2): Graph on 32 vertices
        sage: g.is_strongly_regular(parameters=True)
        False
        sage: g.is_regular()
        True
        sage: g.is_vertex_transitive()
        True
    """
    if sign in ["+","-"]:
        s = 1 if sign == "+" else -1
        if d%2 == 1:
            raise ValueError("d must be even when sign!=None")
    else:
        if d%2 == 0:
            raise ValueError("d must be odd when sign==None")
        s = 0

    from sage.interfaces.gap import gap
    from sage.modules.free_module import VectorSpace
    from sage.matrix.constructor import Matrix
    from sage.libs.gap.libgap import libgap
    from itertools import combinations

    M = Matrix(libgap.InvariantQuadraticForm(libgap.GeneralOrthogonalGroup(s,d,q))['matrix'])
    F = libgap.GF(q).sage()
    V = list(VectorSpace(F,d))

    G = Graph()
    G.add_vertices([tuple(_) for _ in V])
    for x,y in combinations(V,2):
        if not (x-y)*M*(x-y):
            G.add_edge(tuple(x),tuple(y))

    G.name("Affine Polar Graph VO^"+str('+' if s == 1 else '-')+"("+str(d)+","+str(q)+")")
    G.relabel()
    return G
Beispiel #45
0
def CycleGraph(n):
    r"""
    Returns a cycle graph with n nodes.

    A cycle graph is a basic structure which is also typically called an
    `n`-gon.

    PLOTTING: Upon construction, the position dictionary is filled to override
    the spring-layout algorithm. By convention, each cycle graph will be
    displayed with the first (0) node at the top, with the rest following in a
    counterclockwise manner.

    The cycle graph is a good opportunity to compare efficiency of filling a
    position dictionary vs. using the spring-layout algorithm for
    plotting. Because the cycle graph is very symmetric, the resulting plots
    should be similar (in cases of small `n`).

    Filling the position dictionary in advance adds `O(n)` to the constructor.

    EXAMPLES: Compare plotting using the predefined layout and networkx::

        sage: import networkx
        sage: n = networkx.cycle_graph(23)
        sage: spring23 = Graph(n)
        sage: posdict23 = graphs.CycleGraph(23)
        sage: spring23.show() # long time
        sage: posdict23.show() # long time

    We next view many cycle graphs as a Sage graphics array. First we use the
    ``CycleGraph`` constructor, which fills in the position dictionary::

        sage: g = []
        sage: j = []
        sage: for i in range(9):
        ....:     k = graphs.CycleGraph(i+3)
        ....:     g.append(k)
        sage: for i in range(3):
        ....:     n = []
        ....:     for m in range(3):
        ....:         n.append(g[3*i + m].plot(vertex_size=50, vertex_labels=False))
        ....:     j.append(n)
        sage: G = sage.plot.graphics.GraphicsArray(j)
        sage: G.show() # long time

    Compare to plotting with the spring-layout algorithm::

        sage: g = []
        sage: j = []
        sage: for i in range(9):
        ....:     spr = networkx.cycle_graph(i+3)
        ....:     k = Graph(spr)
        ....:     g.append(k)
        sage: for i in range(3):
        ....:     n = []
        ....:     for m in range(3):
        ....:         n.append(g[3*i + m].plot(vertex_size=50, vertex_labels=False))
        ....:     j.append(n)
        sage: G = sage.plot.graphics.GraphicsArray(j)
        sage: G.show() # long time

    TESTS:

    The input parameter must be a positive integer::

        sage: G = graphs.CycleGraph(-1)
        Traceback (most recent call last):
        ...
        ValueError: parameter n must be a positive integer
    """
    if n < 0:
        raise ValueError("parameter n must be a positive integer")

    G = Graph(n, name="Cycle graph")
    if n == 1:
        G.set_pos({0: (0, 0)})
    elif n == 2:
        G.add_edge(0, 1)
        G.set_pos({0: (0, 1), 1: (0, -1)})
    else:
        pos_dict = {}
        for i in range(n):
            x = float(cos((pi / 2) + ((2 * pi) / n) * i))
            y = float(sin((pi / 2) + ((2 * pi) / n) * i))
            pos_dict[i] = (x, y)
        G.set_pos(pos_dict)
        G.add_cycle(list(range(n)))
    return G
Beispiel #46
0
def GridGraph(dim_list):
    """
    Returns an n-dimensional grid graph.

    INPUT:


    -  ``dim_list`` - a list of integers representing the
       number of nodes to extend in each dimension.


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

    EXAMPLES::

        sage: G = graphs.GridGraph([2,3,4])
        sage: G.show()  # long time

    ::

        sage: C = graphs.CubeGraph(4)
        sage: G = graphs.GridGraph([2,2,2,2])
        sage: C.show()  # long time
        sage: G.show()  # long time

    TESTS:

    The graph name contains the dimension::

        sage: g = graphs.GridGraph([5, 7])
        sage: g.name()
        'Grid Graph for [5, 7]'
        sage: g = graphs.GridGraph([2, 3, 4])
        sage: g.name()
        'Grid Graph for [2, 3, 4]'
        sage: g = graphs.GridGraph([2, 4, 3])
        sage: g.name()
        'Grid Graph for [2, 4, 3]'

    One dimensional grids (i.e., path) have simple vertex labels::

        sage: g = graphs.GridGraph([5])
        sage: g.vertices()
        [0, 1, 2, 3, 4]

    The graph is correct::

        sage: dim = [randint(1,4) for i in range(4)]
        sage: g = graphs.GridGraph(dim)
        sage: import networkx
        sage: h = Graph( networkx.grid_graph(list(dim)) )
        sage: g.is_isomorphic(h)
        True

    Trivial cases::

        sage: g = graphs.GridGraph([]); g; g.vertices()
        Grid Graph for []: Graph on 0 vertices
        []
        sage: g = graphs.GridGraph([1]); g; g.vertices()
        Grid Graph for [1]: Graph on 1 vertex
        [0]
        sage: g = graphs.GridGraph([2]); g; g.vertices()
        Grid Graph for [2]: Graph on 2 vertices
        [0, 1]
        sage: g = graphs.GridGraph([1,1]); g; g.vertices()
        Grid Graph for [1, 1]: Graph on 1 vertex
        [(0, 0)]
        sage: g = graphs.GridGraph([1, 1, 1]); g; g.vertices()
        Grid Graph for [1, 1, 1]: Graph on 1 vertex
        [(0, 0, 0)]
        sage: g = graphs.GridGraph([1,1,2]); g; g.vertices()
        Grid Graph for [1, 1, 2]: Graph on 2 vertices
        [(0, 0, 0), (0, 0, 1)]

    All dimensions must be positive integers::

        sage: g = graphs.GridGraph([2,-1,3])
        Traceback (most recent call last):
        ...
        ValueError: All dimensions must be positive integers !
    """
    dim = [int(a) for a in dim_list]
    if any(a <= 0 for a in dim):
        raise ValueError("All dimensions must be positive integers !")

    g = Graph()
    n_dim = len(dim)
    if n_dim==1:
        # Vertices are labeled from 0 to dim[0]-1
        g = PathGraph(dim[0])
    elif n_dim==2:
        # We use the Grid2dGraph generator to also get the positions
        g = Grid2dGraph(*dim)
    elif n_dim>2:
        # Vertices are tuples of dimension n_dim, and the graph contains at
        # least vertex (0, 0, ..., 0)
        g.add_vertex(tuple([0]*n_dim))
        import itertools
        for u in itertools.product(*[range(d) for d in dim]):
            for i in range(n_dim):
                if u[i]+1<dim[i]:
                    v = list(u)
                    v[i] = u[i]+1
                    g.add_edge(u, tuple(v))

    g.name("Grid Graph for {}".format(dim))
    return g
Beispiel #47
0
def RandomBicubicPlanar(n):
    """
    Return the graph of a random bipartite cubic map with `3 n` edges.

    INPUT:

    `n` -- an integer (at least `1`)

    OUTPUT:

    a graph with multiple edges (no embedding is provided)

    The algorithm used is described in [Schaeffer99]_. This samples
    a random rooted bipartite cubic map, chosen uniformly at random.

    First one creates a random binary tree with `n` vertices. Next one
    turns this into a blossoming tree (at random) and reads the
    contour word of this blossoming tree.

    Then one performs a rotation on this word so that this becomes a
    balanced word. There are three ways to do that, one is picked at
    random. Then a graph is build from the balanced word by iterated
    closure (adding edges).

    In the returned graph, the three edges incident to any given
    vertex are colored by the integers 0, 1 and 2.

    .. SEEALSO:: the auxiliary method :func:`blossoming_contour`

    EXAMPLES::

        sage: n = randint(200, 300)
        sage: G = graphs.RandomBicubicPlanar(n)
        sage: G.order() == 2*n
        True
        sage: G.size() == 3*n
        True
        sage: G.is_bipartite() and G.is_planar() and G.is_regular(3)
        True
        sage: dic = {'red':[v for v in G.vertices() if v[0] == 'n'],
        ....:        'blue': [v for v in G.vertices() if v[0] != 'n']}
        sage: G.plot(vertex_labels=False,vertex_size=20,vertex_colors=dic)
        Graphics object consisting of ... graphics primitives

    .. PLOT::
        :width: 300 px

        G = graphs.RandomBicubicPlanar(200)
        V0 = [v for v in G.vertices() if v[0] == 'n']
        V1 = [v for v in G.vertices() if v[0] != 'n']
        dic = {'red': V0, 'blue': V1}
        sphinx_plot(G.plot(vertex_labels=False,vertex_colors=dic))

    REFERENCES:

    .. [Schaeffer99] Gilles Schaeffer, *Random Sampling of Large Planar Maps and Convex Polyhedra*,
       Annual ACM Symposium on Theory of Computing (Atlanta, GA, 1999)
    """
    from sage.combinat.binary_tree import BinaryTrees
    from sage.rings.finite_rings.integer_mod_ring import Zmod
    if not n:
        raise ValueError("n must be at least 1")
    # first pick a random binary tree
    t = BinaryTrees(n).random_element()

    # next pick a random blossoming of this tree, compute its contour
    contour = blossoming_contour(t) + [('xb',)]   # adding the final xb

    # first step : rotate the contour word to one of 3 balanced
    N = len(contour)
    double_contour = contour + contour
    pile = []
    not_touched = [i for i in range(N) if contour[i][0] in ['x', 'xb']]
    for i, w in enumerate(double_contour):
        if w[0] == 'x' and i < N:
            pile.append(i)
        elif w[0] == 'xb' and (i % N) in not_touched:
            if pile:
                j = pile.pop()
                not_touched.remove(i % N)
                not_touched.remove(j)

    # random choice among 3 possibilities for a balanced word
    idx = not_touched[randint(0, 2)]
    w = contour[idx + 1:] + contour[:idx + 1]

    # second step : create the graph by closure from the balanced word
    G = Graph(multiedges=True)

    pile = []
    Z3 = Zmod(3)
    colour = Z3.zero()
    not_touched = [i for i, v in enumerate(w) if v[0] in ['x', 'xb']]
    for i, v in enumerate(w):
        # internal edges
        if v[0] == 'i':
            colour += 1
            if w[i + 1][0] == 'n':
                G.add_edge((w[i], w[i + 1], colour))
        elif v[0] == 'n':
            colour += 2
        elif v[0] == 'x':
            pile.append(i)
        elif v[0] == 'xb' and i in not_touched:
            if pile:
                j = pile.pop()
                G.add_edge((w[i + 1], w[j - 1], colour))
                not_touched.remove(i)
                not_touched.remove(j)

    # there remains to add three edges to elements of "not_touched"
    # from a new vertex labelled "n"
    for i in not_touched:
        taken_colours = [edge[2] for edge in G.edges_incident(w[i - 1])]
        colour = [u for u in Z3 if u not in taken_colours][0]
        G.add_edge((('n', -1), w[i - 1], colour))

    return G
Beispiel #48
0
def RandomTree(n):
    """
    Returns a random tree on `n` nodes numbered `0` through `n-1`.

    By Cayley's theorem, there are `n^{n-2}` trees with vertex
    set `\{0,1,...,n-1\}`. This constructor chooses one of these uniformly
    at random.

    ALGORITHM:

    The algoritm works by generating an `(n-2)`-long
    random sequence of numbers chosen independently and uniformly
    from `\{0,1,\ldots,n-1\}` and then applies an inverse
    Prufer transformation.

    INPUT:

    -  ``n`` - number of vertices in the tree

    EXAMPLE::

        sage: G = graphs.RandomTree(10)
        sage: G.is_tree()
        True
        sage: G.show() # long

    TESTS:

    Ensuring that we encounter no unexpected surprise ::

        sage: all( graphs.RandomTree(10).is_tree()
        ....:      for i in range(100) )
        True

    """
    from sage.misc.prandom import randint
    g = Graph()

    # create random Prufer code
    code = [ randint(0,n-1) for i in xrange(n-2) ]

    # We count the number of symbols of each type.
    # count[k] is the no. of times k appears in code
    #
    # (count[k] is set to -1 when the corresponding vertex is not
    # available anymore)
    count = [ 0 for i in xrange(n) ]
    for k in code:
        count[k] += 1

    g.add_vertices(range(n))

    for s in code:
        for x in range(n):
            if count[x] == 0:
                break

        count[x] = -1
        g.add_edge(x,s)
        count[s] -= 1

    # Adding as an edge the last two available vertices
    last_edge = [ v for v in range(n) if count[v] != -1 ]
    g.add_edge(last_edge)

    return g
Beispiel #49
0
def ToleranceGraph(tolrep):
    r"""
    Return the graph generated by the tolerance representation ``tolrep``.

    The tolerance representation ``tolrep`` is described by the list
    `((l_0,r_0,t_0), (l_1,r_1,t_1), ..., (l_k,r_k,t_k))` where `I_i = (l_i,r_i)`
    denotes a closed interval on the real line with `l_i < r_i` and `t_i` a
    positive value, called tolerance. This representation generates the
    tolerance graph with the vertex set {0,1, ..., k} and the edge set `{(i,j):
    |I_i \cap I_j| \ge \min{t_i, t_j}}` where `|I_i \cap I_j|` denotes the
    length of the intersection of `I_i` and `I_j`.

    INPUT:

    - ``tolrep`` -- list of triples `(l_i,r_i,t_i)` where `(l_i,r_i)` denotes a
      closed interval on the real line and `t_i` a positive value.

    .. NOTE::

        The vertices are named 0, 1, ..., k. The tolerance representation used
        to create the graph is saved with the graph and can be recovered using
        ``get_vertex()`` or ``get_vertices()``.

    EXAMPLES:

    The following code creates a tolerance representation ``tolrep``, generates
    its tolerance graph ``g``, and applies some checks::

        sage: tolrep = [(1,4,3),(1,2,1),(2,3,1),(0,3,3)]
        sage: g = graphs.ToleranceGraph(tolrep)
        sage: g.get_vertex(3)
        (0, 3, 3)
        sage: neigh = g.neighbors(3)
        sage: for v in neigh: print(g.get_vertex(v))
        (1, 2, 1)
        (2, 3, 1)
        sage: g.is_interval()
        False
        sage: g.is_weakly_chordal()
        True

    The intervals in the list need not be distinct ::

        sage: tolrep2 = [(0,4,5),(1,2,1),(2,3,1),(0,4,5)]
        sage: g2 = graphs.ToleranceGraph(tolrep2)
        sage: g2.get_vertices()
        {0: (0, 4, 5), 1: (1, 2, 1), 2: (2, 3, 1), 3: (0, 4, 5)}
        sage: g2.is_isomorphic(g)
        True

    Real values are also allowed ::

        sage: tolrep = [(0.1,3.3,4.4),(1.1,2.5,1.1),(1.4,4.4,3.3)]
        sage: g = graphs.ToleranceGraph(tolrep)
        sage: g.is_isomorphic(graphs.PathGraph(3))
        True

    TESTS:

    Giving negative third value::

        sage: tolrep = [(0.1,3.3,-4.4),(1.1,2.5,1.1),(1.4,4.4,3.3)]
        sage: g = graphs.ToleranceGraph(tolrep)
        Traceback (most recent call last):
        ...
        ValueError: Invalid tolerance representation at position 0; third value must be positive!
    """
    n = len(tolrep)

    for i in range(n):
        if tolrep[i][2] <= 0:
            raise ValueError("Invalid tolerance representation at position " +
                             str(i) + "; third value must be positive!")

    g = Graph(n)

    for i in range(n - 1):
        li, ri, ti = tolrep[i]
        for j in range(i + 1, n):
            lj, rj, tj = tolrep[j]
            if min(ri, rj) - max(li, lj) >= min(ti, tj):
                g.add_edge(i, j)

    rep = dict(zip(range(n), tolrep))
    g.set_vertices(rep)

    return g
Beispiel #50
0
def IntervalGraph(intervals, points_ordered=False):
    r"""
    Return the graph corresponding to the given intervals.

    An interval graph is built from a list `(a_i,b_i)_{1\leq i \leq n}` of
    intervals : to each interval of the list is associated one vertex, two
    vertices being adjacent if the two corresponding (closed) intervals
    intersect.

    INPUT:

    - ``intervals`` -- the list of pairs `(a_i,b_i)` defining the graph.

    - ``points_ordered`` -- states whether every interval `(a_i,b_i)` of
      `intervals` satisfies `a_i<b_i`. If satisfied then setting
      ``points_ordered`` to ``True`` will speed up the creation of the graph.

    .. NOTE::

        * The vertices are named 0, 1, 2, and so on. The intervals used
          to create the graph are saved with the graph and can be recovered
          using ``get_vertex()`` or ``get_vertices()``.

    EXAMPLES:

    The following line creates the sequence of intervals
    `(i, i+2)` for i in `[0, ..., 8]`::

        sage: intervals = [(i,i+2) for i in range(9)]

    In the corresponding graph ::

        sage: g = graphs.IntervalGraph(intervals)
        sage: g.get_vertex(3)
        (3, 5)
        sage: neigh = g.neighbors(3)
        sage: for v in neigh: print(g.get_vertex(v))
        (1, 3)
        (2, 4)
        (4, 6)
        (5, 7)

    The is_interval() method verifies that this graph is an interval graph. ::

        sage: g.is_interval()
        True

    The intervals in the list need not be distinct. ::

        sage: intervals = [ (1,2), (1,2), (1,2), (2,3), (3,4) ]
        sage: g = graphs.IntervalGraph(intervals,True)
        sage: g.clique_maximum()
        [0, 1, 2, 3]
        sage: g.get_vertices()
        {0: (1, 2), 1: (1, 2), 2: (1, 2), 3: (2, 3), 4: (3, 4)}

    The endpoints of the intervals are not ordered we get the same graph
    (except for the vertex labels). ::

        sage: rev_intervals = [ (2,1), (2,1), (2,1), (3,2), (4,3) ]
        sage: h = graphs.IntervalGraph(rev_intervals,False)
        sage: h.get_vertices()
        {0: (2, 1), 1: (2, 1), 2: (2, 1), 3: (3, 2), 4: (4, 3)}
        sage: g.edges() == h.edges()
        True
    """
    intervals = list(intervals)
    n = len(intervals)
    g = Graph(n)

    if points_ordered:
        for i in range(n - 1):
            li, ri = intervals[i]
            for j in range(i + 1, n):
                lj, rj = intervals[j]
                if ri < lj or rj < li:
                    continue
                g.add_edge(i, j)
    else:
        for i in range(n - 1):
            I = intervals[i]
            for j in range(i + 1, n):
                J = intervals[j]
                if max(I) < min(J) or max(J) < min(I):
                    continue
                g.add_edge(i, j)

    rep = dict(zip(range(n), intervals))
    g.set_vertices(rep)

    return g
Beispiel #51
0
def RandomBicubicPlanar(n):
    """
    Return the graph of a random bipartite cubic map with `3 n` edges.

    INPUT:

    `n` -- an integer (at least `1`)

    OUTPUT:

    a graph with multiple edges (no embedding is provided)

    The algorithm used is described in [Schaeffer99]_. This samples
    a random rooted bipartite cubic map, chosen uniformly at random.

    First one creates a random binary tree with `n` vertices. Next one
    turns this into a blossoming tree (at random) and reads the
    contour word of this blossoming tree.

    Then one performs a rotation on this word so that this becomes a
    balanced word. There are three ways to do that, one is picked at
    random. Then a graph is build from the balanced word by iterated
    closure (adding edges).

    In the returned graph, the three edges incident to any given
    vertex are colored by the integers 0, 1 and 2.

    .. SEEALSO:: the auxiliary method :func:`blossoming_contour`

    EXAMPLES::

        sage: n = randint(200, 300)
        sage: G = graphs.RandomBicubicPlanar(n)
        sage: G.order() == 2*n
        True
        sage: G.size() == 3*n
        True
        sage: G.is_bipartite() and G.is_planar() and G.is_regular(3)
        True
        sage: dic = {'red':[v for v in G.vertices() if v[0] == 'n'],
        ....:        'blue': [v for v in G.vertices() if v[0] != 'n']}
        sage: G.plot(vertex_labels=False,vertex_size=20,vertex_colors=dic)
        Graphics object consisting of ... graphics primitives

    .. PLOT::
        :width: 300 px

        G = graphs.RandomBicubicPlanar(200)
        V0 = [v for v in G.vertices() if v[0] == 'n']
        V1 = [v for v in G.vertices() if v[0] != 'n']
        dic = {'red': V0, 'blue': V1}
        sphinx_plot(G.plot(vertex_labels=False,vertex_colors=dic))

    REFERENCES:

    .. [Schaeffer99] Gilles Schaeffer, *Random Sampling of Large Planar Maps and Convex Polyhedra*,
       Annual ACM Symposium on Theory of Computing (Atlanta, GA, 1999)
    """
    from sage.combinat.binary_tree import BinaryTrees
    from sage.rings.finite_rings.integer_mod_ring import Zmod
    if not n:
        raise ValueError("n must be at least 1")
    # first pick a random binary tree
    t = BinaryTrees(n).random_element()

    # next pick a random blossoming of this tree, compute its contour
    contour = blossoming_contour(t) + [('xb', )]  # adding the final xb

    # first step : rotate the contour word to one of 3 balanced
    N = len(contour)
    double_contour = contour + contour
    pile = []
    not_touched = [i for i in range(N) if contour[i][0] in ['x', 'xb']]
    for i, w in enumerate(double_contour):
        if w[0] == 'x' and i < N:
            pile.append(i)
        elif w[0] == 'xb' and (i % N) in not_touched:
            if pile:
                j = pile.pop()
                not_touched.remove(i % N)
                not_touched.remove(j)

    # random choice among 3 possibilities for a balanced word
    idx = not_touched[randint(0, 2)]
    w = contour[idx + 1:] + contour[:idx + 1]

    # second step : create the graph by closure from the balanced word
    G = Graph(multiedges=True)

    pile = []
    Z3 = Zmod(3)
    colour = Z3.zero()
    not_touched = [i for i, v in enumerate(w) if v[0] in ['x', 'xb']]
    for i, v in enumerate(w):
        # internal edges
        if v[0] == 'i':
            colour += 1
            if w[i + 1][0] == 'n':
                G.add_edge((w[i], w[i + 1], colour))
        elif v[0] == 'n':
            colour += 2
        elif v[0] == 'x':
            pile.append(i)
        elif v[0] == 'xb' and i in not_touched:
            if pile:
                j = pile.pop()
                G.add_edge((w[i + 1], w[j - 1], colour))
                not_touched.remove(i)
                not_touched.remove(j)

    # there remains to add three edges to elements of "not_touched"
    # from a new vertex labelled "n"
    for i in not_touched:
        taken_colours = [edge[2] for edge in G.edges_incident(w[i - 1])]
        colour = [u for u in Z3 if u not in taken_colours][0]
        G.add_edge((('n', -1), w[i - 1], colour))

    return G
    def restricted_automorphism_group(self, vertex_labels=None):
        r"""
        Return the restricted automorphism group.

        First, let the linear automorphism group be the subgroup of
        the Euclidean group `E(d) = GL(d,\RR) \ltimes \RR^d`
        preserving the `d`-dimensional polyhedron. The Euclidean group
        acts in the usual way `\vec{x}\mapsto A\vec{x}+b` on the
        ambient space. The restricted automorphism group is the
        subgroup of the linear automorphism group generated by
        permutations of vertices. If the polytope is full-dimensional,
        it is equal to the full (unrestricted) automorphism group.

        INPUT:

        - ``vertex_labels`` -- a tuple or ``None`` (default). The
          labels of the vertices that will be used in the output
          permutation group. By default, the vertices are used
          themselves.

        OUTPUT:

        A
        :class:`PermutationGroup<sage.groups.perm_gps.permgroup.PermutationGroup_generic>`
        acting on the vertices (or the ``vertex_labels``, if
        specified).

        REFERENCES:

        [BSS2009]_

        EXAMPLES::

            sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL
            sage: Z3square = LatticePolytope_PPL((0,0), (1,2), (2,1), (3,3))
            sage: Z3square.restricted_automorphism_group(vertex_labels=(1,2,3,4)) == PermutationGroup([[(2,3)],[(1,2),(3,4)]])
            True
            sage: G = Z3square.restricted_automorphism_group()
            sage: G == PermutationGroup([[((1,2),(2,1))],[((0,0),(1,2)),((2,1),(3,3))],[((0,0),(3,3))]])
            True
            sage: set(G.domain()) == set(Z3square.vertices())
            True
            sage: set(map(tuple,G.orbit(Z3square.vertices()[0]))) == set([(0, 0), (1, 2), (3, 3), (2, 1)])
            True
            sage: cell24 = LatticePolytope_PPL(
            ....: (1,0,0,0),(0,1,0,0),(0,0,1,0),(0,0,0,1),(1,-1,-1,1),(0,0,-1,1),
            ....: (0,-1,0,1),(-1,0,0,1),(1,0,0,-1),(0,1,0,-1),(0,0,1,-1),(-1,1,1,-1),
            ....: (1,-1,-1,0),(0,0,-1,0),(0,-1,0,0),(-1,0,0,0),(1,-1,0,0),(1,0,-1,0),
            ....: (0,1,1,-1),(-1,1,1,0),(-1,1,0,0),(-1,0,1,0),(0,-1,-1,1),(0,0,0,-1))
            sage: cell24.restricted_automorphism_group().cardinality()
            1152
        """
        if not self.is_full_dimensional():
            return self.affine_lattice_polytope().\
                restricted_automorphism_group(vertex_labels=vertex_labels)
        if vertex_labels is None:
            vertex_labels = self.vertices()
        from sage.graphs.graph import Graph
        # good coordinates for the vertices
        v_list = []
        for v in self.minimized_generators():
            assert v.divisor() == 1
            v_coords = (1,) + tuple(Integer(mpz) for mpz in v.coefficients())
            v_list.append(vector(v_coords))

        # Finally, construct the graph
        Qinv = sum( v.column() * v.row() for v in v_list ).inverse()
        G = Graph()
        for i in range(len(v_list)):
            for j in range(i+1,len(v_list)):
                v_i = v_list[i]
                v_j = v_list[j]
                G.add_edge(vertex_labels[i], vertex_labels[j], v_i * Qinv * v_j)
        return G.automorphism_group(edge_labels=True)
Beispiel #53
0
def PermutationGraph(second_permutation, first_permutation = None):
    r"""
    Builds a permutation graph from one (or two) permutations.

    General definition

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

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

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

    INPUT:

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

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

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

    .. SEEALSO:

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

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

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

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

    EXAMPLE::

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

    TESTS::

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

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

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

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

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

    return g
Beispiel #54
0
def is_partial_cube(G, certificate=False):
    r"""
    Test whether the given graph is a partial cube.

    A partial cube is a graph that can be isometrically embedded into a
    hypercube, i.e., its vertices can be labelled with (0,1)-vectors of some
    fixed length such that the distance between any two vertices in the graph
    equals the Hamming distance of their labels.

    Originally written by D. Eppstein for the PADS library
    (http://www.ics.uci.edu/~eppstein/PADS/), see also
    [Epp2008]_.  The algorithm runs in `O(n^2)` time, where `n`
    is the number of vertices. See the documentation of
    :mod:`~sage.graphs.partial_cube` for an overview of the algorithm.

    INPUT:

    - ``certificate`` -- boolean (default: ``False``); this function returns
      ``True`` or ``False`` according to the graph, when ``certificate =
      False``. When ``certificate = True`` and the graph is a partial cube, the
      function returns ``(True, mapping)``, where ``mapping`` is an isometric
      mapping of the vertices of the graph to the vertices of a hypercube
      ((0, 1)-strings of a fixed length). When ``certificate = True`` and the
      graph is not a partial cube, ``(False, None)`` is returned.

    EXAMPLES:

    The Petersen graph is not a partial cube::

        sage: g = graphs.PetersenGraph()
        sage: g.is_partial_cube()
        False

    All prisms are partial cubes::

        sage: g = graphs.CycleGraph(10).cartesian_product(graphs.CompleteGraph(2))
        sage: g.is_partial_cube()
        True

    TESTS:

    The returned mapping is an isometric embedding into a hypercube::

        sage: g = graphs.DesarguesGraph()
        sage: _, m = g.is_partial_cube(certificate=True)
        sage: m # random
        {0: '00000',
         1: '00001',
         2: '00011',
         3: '01011',
         4: '11011',
         5: '11111',
         6: '11110',
         7: '11100',
         8: '10100',
         9: '00100',
         10: '01000',
         11: '10001',
         12: '00111',
         13: '01010',
         14: '11001',
         15: '10111',
         16: '01110',
         17: '11000',
         18: '10101',
         19: '00110'}
        sage: all(all(g.distance(u, v) == len([i for i in range(len(m[u])) if m[u][i] != m[v][i]]) for v in m) for u in m)
        True

    A graph without vertices is trivially a partial cube::

        sage: Graph().is_partial_cube(certificate=True)
        (True, {})

    """
    G._scream_if_not_simple()

    if not G.order():
        if certificate:
            return (True, {})
        else:
            return True

    if certificate:
        fail = (False, None)
    else:
        fail = False

    if not G.is_connected():
        return fail
    n = G.order()

    # Initial sanity check: are there few enough edges?
    # Needed so that we don't try to use union-find on a dense
    # graph and incur superquadratic runtimes.
    if 1 << (2 * G.size() // n) > n:
        return fail

    # Check for bipartiteness.
    # This ensures also that each contraction will be bipartite.
    if not G.is_bipartite():
        return fail

    # Set up data structures for algorithm:
    # - contracted: contracted graph at current stage of algorithm
    # - unionfind: union find data structure representing known edge equivalences
    # - available: limit on number of remaining available labels
    from sage.graphs.digraph import DiGraph
    from sage.graphs.graph import Graph
    from sage.sets.disjoint_set import DisjointSet
    contracted = DiGraph({v: {w: (v, w) for w in G[v]} for v in G})
    unionfind = DisjointSet(contracted.edges(labels=False))
    available = n - 1

    # Main contraction loop in place of the original algorithm's recursion
    while contracted.order() > 1:
        # Find max degree vertex in contracted, and update label limit
        deg, root = max([(contracted.out_degree(v), v) for v in contracted],
                        key=lambda x: x[0])
        if deg > available:
            return fail
        available -= deg

        # Set up bitvectors on vertices
        bitvec = {v: 0 for v in contracted}
        neighbors = {}
        for i, neighbor in enumerate(contracted[root]):
            bitvec[neighbor] = 1 << i
            neighbors[1 << i] = neighbor

        # Breadth first search to propagate bitvectors to the rest of the graph
        for level in breadth_first_level_search(contracted, root):
            for v in level:
                for w in level[v]:
                    bitvec[w] |= bitvec[v]

        # Make graph of labeled edges and union them together
        labeled = Graph([contracted.vertices(), []])
        for v, w in contracted.edge_iterator(labels=False):
            diff = bitvec[v] ^ bitvec[w]
            if not diff or not bitvec[w] & ~bitvec[v]:
                continue  # zero edge or wrong direction
            if diff not in neighbors:
                return fail
            neighbor = neighbors[diff]
            unionfind.union(contracted.edge_label(v, w),
                            contracted.edge_label(root, neighbor))
            unionfind.union(contracted.edge_label(w, v),
                            contracted.edge_label(neighbor, root))
            labeled.add_edge(v, w)

        # Map vertices to components of labeled-edge graph
        component = {}
        for i, SCC in enumerate(labeled.connected_components()):
            for v in SCC:
                component[v] = i

        # generate new compressed subgraph
        newgraph = DiGraph()
        for v, w, t in contracted.edge_iterator():
            if bitvec[v] == bitvec[w]:
                vi = component[v]
                wi = component[w]
                if vi == wi:
                    return fail
                if newgraph.has_edge(vi, wi):
                    unionfind.union(newgraph.edge_label(vi, wi), t)
                else:
                    newgraph.add_edge(vi, wi, t)
        contracted = newgraph

    # Make a digraph with edges labeled by the equivalence classes in unionfind
    g = DiGraph({v: {w: unionfind.find((v, w)) for w in G[v]} for v in G})

    # Associates to a vertex the token that acts on it, an check that
    # no two edges on a single vertex have the same label
    action = {}
    for v in g:
        action[v] = set(t for _, _, t in g.edge_iterator(v))
        if len(action[v]) != g.out_degree(v):
            return fail

    # Associate every token to its reverse
    reverse = {}
    for v, w, t in g.edge_iterator():
        rt = g.edge_label(w, v)
        reverse[t] = rt
        reverse[rt] = t

    current = initialState = next(g.vertex_iterator())

    # A token T is said to be 'active' for a vertex u if it takes u
    # one step closer to the source in terms of distance. The 'source'
    # is initially 'initialState'. See the module's documentation for
    # more explanations.

    # Find list of tokens that lead to the initial state
    activeTokens = set()
    for level in breadth_first_level_search(g, initialState):
        for v in level:
            for w in level[v]:
                activeTokens.add(g.edge_label(w, v))
    for t in activeTokens:
        if reverse[t] in activeTokens:
            return fail
    activeTokens = list(activeTokens)

    # Rest of data structure: point from states to list and list to states
    state_to_active_token = {v: -1 for v in g}
    token_to_states = [[] for i in activeTokens
                       ]  # (i.e. vertices on which each token acts)

    def scan(v):
        """Find the next token that is effective for v."""
        a = next(
            i for i in range(state_to_active_token[v] + 1, len(activeTokens))
            if activeTokens[i] is not None and activeTokens[i] in action[v])
        state_to_active_token[v] = a
        token_to_states[a].append(v)

    # Initialize isometric embedding into a hypercube
    if certificate:
        dim = 0
        tokmap = {}
        for t in reverse:
            if t not in tokmap:
                tokmap[t] = tokmap[reverse[t]] = 1 << dim
                dim += 1
        embed = {initialState: 0}

    # Set initial active states
    for v in g:
        if v != current:
            try:
                scan(v)
            except StopIteration:
                return fail

    # Traverse the graph, maintaining active tokens
    for prev, current, fwd in depth_first_traversal(g, initialState):
        if not fwd:
            prev, current = current, prev
        elif certificate:
            embed[current] = embed[prev] ^ tokmap[g.edge_label(prev, current)]

        # Add token to end of list, point to it from old state
        activeTokens.append(g.edge_label(prev, current))
        state_to_active_token[prev] = len(activeTokens) - 1
        token_to_states.append([prev])

        # Inactivate reverse token, find new token for its states
        #
        # (the 'active' token of 'current' is necessarily the label of
        #  (current, previous))
        activeTokens[state_to_active_token[current]] = None
        for v in token_to_states[state_to_active_token[current]]:
            if v != current:
                try:
                    scan(v)
                except StopIteration:
                    return fail

    # All checks passed, return the result
    if certificate:
        format = "{0:0%db}" % dim
        return (True, {v: format.format(l) for v, l in embed.items()})
    else:
        return True
Beispiel #55
0
def GridGraph(dim_list):
    """
    Returns an n-dimensional grid graph.

    INPUT:


    -  ``dim_list`` - a list of integers representing the
       number of nodes to extend in each dimension.


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

    EXAMPLES::

        sage: G = graphs.GridGraph([2,3,4])
        sage: G.show()  # long time

    ::

        sage: C = graphs.CubeGraph(4)
        sage: G = graphs.GridGraph([2,2,2,2])
        sage: C.show()  # long time
        sage: G.show()  # long time

    TESTS:

    The graph name contains the dimension::

        sage: g = graphs.GridGraph([5, 7])
        sage: g.name()
        'Grid Graph for [5, 7]'
        sage: g = graphs.GridGraph([2, 3, 4])
        sage: g.name()
        'Grid Graph for [2, 3, 4]'
        sage: g = graphs.GridGraph([2, 4, 3])
        sage: g.name()
        'Grid Graph for [2, 4, 3]'

    One dimensional grids (i.e., path) have simple vertex labels::

        sage: g = graphs.GridGraph([5])
        sage: g.vertices()
        [0, 1, 2, 3, 4]

    The graph is correct::

        sage: dim = [randint(1,4) for i in range(4)]
        sage: g = graphs.GridGraph(dim)
        sage: import networkx
        sage: h = Graph( networkx.grid_graph(list(dim)) )
        sage: g.is_isomorphic(h)
        True

    Trivial cases::

        sage: g = graphs.GridGraph([]); g; g.vertices()
        Grid Graph for []: Graph on 0 vertices
        []
        sage: g = graphs.GridGraph([1]); g; g.vertices()
        Grid Graph for [1]: Graph on 1 vertex
        [0]
        sage: g = graphs.GridGraph([2]); g; g.vertices()
        Grid Graph for [2]: Graph on 2 vertices
        [0, 1]
        sage: g = graphs.GridGraph([1,1]); g; g.vertices()
        Grid Graph for [1, 1]: Graph on 1 vertex
        [(0, 0)]
        sage: g = graphs.GridGraph([1, 1, 1]); g; g.vertices()
        Grid Graph for [1, 1, 1]: Graph on 1 vertex
        [(0, 0, 0)]
        sage: g = graphs.GridGraph([1,1,2]); g; g.vertices()
        Grid Graph for [1, 1, 2]: Graph on 2 vertices
        [(0, 0, 0), (0, 0, 1)]

    All dimensions must be positive integers::

        sage: g = graphs.GridGraph([2,-1,3])
        Traceback (most recent call last):
        ...
        ValueError: All dimensions must be positive integers !
    """
    dim = [int(a) for a in dim_list]
    if any(a <= 0 for a in dim):
        raise ValueError("All dimensions must be positive integers !")

    g = Graph()
    n_dim = len(dim)
    if n_dim==1:
        # Vertices are labeled from 0 to dim[0]-1
        g = PathGraph(dim[0])
    elif n_dim==2:
        # We use the Grid2dGraph generator to also get the positions
        g = Grid2dGraph(*dim)
    elif n_dim>2:
        # Vertices are tuples of dimension n_dim, and the graph contains at
        # least vertex (0, 0, ..., 0)
        g.add_vertex(tuple([0]*n_dim))
        import itertools
        for u in itertools.product(*[range(d) for d in dim]):
            for i in range(n_dim):
                if u[i]+1<dim[i]:
                    v = list(u)
                    v[i] = u[i]+1
                    g.add_edge(u, tuple(v))

    g.name("Grid Graph for {}".format(dim))
    return g
Beispiel #56
0
def RandomBipartite(n1, n2, p):
    r"""
    Returns a bipartite graph with `n1+n2` vertices
    such that any edge from `[n1]` to `[n2]` exists
    with probability `p`.

    INPUT:

        - ``n1,n2`` : Cardinalities of the two sets
        - ``p``   : Probability for an edge to exist


    EXAMPLE::

        sage: g=graphs.RandomBipartite(5,2,0.5)
        sage: g.vertices()
        [(0, 0), (0, 1), (0, 2), (0, 3), (0, 4), (1, 0), (1, 1)]

    TESTS::

        sage: g=graphs.RandomBipartite(5,-3,0.5)
        Traceback (most recent call last):
        ...
        ValueError: n1 and n2 should be integers strictly greater than 0
        sage: g=graphs.RandomBipartite(5,3,1.5)
        Traceback (most recent call last):
        ...
        ValueError: Parameter p is a probability, and so should be a real value between 0 and 1

    Trac ticket #12155::

        sage: graphs.RandomBipartite(5,6,.2).complement()
        complement(Random bipartite graph of size 5+6 with edge probability 0.200000000000000): Graph on 11 vertices
    """
    if not (p >= 0 and p <= 1):
        raise ValueError, "Parameter p is a probability, and so should be a real value between 0 and 1"
    if not (n1 > 0 and n2 > 0):
        raise ValueError, "n1 and n2 should be integers strictly greater than 0"

    from numpy.random import uniform
    from sage.graphs.all import Graph

    g = Graph(name="Random bipartite graph of size " + str(n1) + "+" +
              str(n2) + " with edge probability " + str(p))

    S1 = [(0, i) for i in range(n1)]
    S2 = [(1, i) for i in range(n2)]
    g.add_vertices(S1)
    g.add_vertices(S2)

    for w in range(n2):
        for v in range(n1):
            if uniform() <= p:
                g.add_edge((0, v), (1, w))

    pos = {}
    for i in range(n1):
        pos[(0, i)] = (0, i / (n1 - 1.0))
    for i in range(n2):
        pos[(1, i)] = (1, i / (n2 - 1.0))

    g.set_pos(pos)

    return g
Beispiel #57
0
def CycleGraph(n):
    r"""
    Returns a cycle graph with n nodes.

    A cycle graph is a basic structure which is also typically called an
    `n`-gon.

    PLOTTING: Upon construction, the position dictionary is filled to override
    the spring-layout algorithm. By convention, each cycle graph will be
    displayed with the first (0) node at the top, with the rest following in a
    counterclockwise manner.

    The cycle graph is a good opportunity to compare efficiency of filling a
    position dictionary vs. using the spring-layout algorithm for
    plotting. Because the cycle graph is very symmetric, the resulting plots
    should be similar (in cases of small `n`).

    Filling the position dictionary in advance adds `O(n)` to the constructor.

    EXAMPLES: Compare plotting using the predefined layout and networkx::

        sage: import networkx
        sage: n = networkx.cycle_graph(23)
        sage: spring23 = Graph(n)
        sage: posdict23 = graphs.CycleGraph(23)
        sage: spring23.show() # long time
        sage: posdict23.show() # long time

    We next view many cycle graphs as a Sage graphics array. First we use the
    ``CycleGraph`` constructor, which fills in the position dictionary::

        sage: g = []
        sage: j = []
        sage: for i in range(9):
        ....:     k = graphs.CycleGraph(i+3)
        ....:     g.append(k)
        sage: for i in range(3):
        ....:     n = []
        ....:     for m in range(3):
        ....:         n.append(g[3*i + m].plot(vertex_size=50, vertex_labels=False))
        ....:     j.append(n)
        sage: G = sage.plot.graphics.GraphicsArray(j)
        sage: G.show() # long time

    Compare to plotting with the spring-layout algorithm::

        sage: g = []
        sage: j = []
        sage: for i in range(9):
        ....:     spr = networkx.cycle_graph(i+3)
        ....:     k = Graph(spr)
        ....:     g.append(k)
        sage: for i in range(3):
        ....:     n = []
        ....:     for m in range(3):
        ....:         n.append(g[3*i + m].plot(vertex_size=50, vertex_labels=False))
        ....:     j.append(n)
        sage: G = sage.plot.graphics.GraphicsArray(j)
        sage: G.show() # long time

    TESTS:

    The input parameter must be a positive integer::

        sage: G = graphs.CycleGraph(-1)
        Traceback (most recent call last):
        ...
        ValueError: parameter n must be a positive integer
    """
    if n < 0:
        raise ValueError("parameter n must be a positive integer")

    G = Graph(n, name="Cycle graph")
    if n == 1:
        G.set_pos({0:(0, 0)})
    elif n == 2:
        G.add_edge(0, 1)
        G.set_pos({0:(0, 1), 1:(0, -1)})
    else:
        pos_dict = {}
        for i in range(n):
            x = float(cos((pi/2) + ((2*pi)/n)*i))
            y = float(sin((pi/2) + ((2*pi)/n)*i))
            pos_dict[i] = (x, y)
        G.set_pos(pos_dict)
        G.add_cycle(list(range(n)))
    return G