/devel/sage-main/sage/graphs/graph.py
Python | 5876 lines | 5631 code | 5 blank | 240 comment | 14 complexity | 8982a646dd053ee38919b63f8f5ac3ad MD5 | raw file
Large files files are truncated, but you can click here to view the full file
- r"""
- Undirected graphs
- This module implements functions and operations involving undirected
- graphs.
- **Graph basic operations:**
- .. csv-table::
- :class: contentstable
- :widths: 30, 70
- :delim: |
- :meth:`~Graph.write_to_eps` | Writes a plot of the graph to ``filename`` in ``eps`` format.
- :meth:`~Graph.to_undirected` | Since the graph is already undirected, simply returns a copy of itself.
- :meth:`~Graph.to_directed` | Returns a directed version of the graph.
- :meth:`~Graph.sparse6_string` | Returns the sparse6 representation of the graph as an ASCII string.
- :meth:`~Graph.graph6_string` | Returns the graph6 representation of the graph as an ASCII string.
- :meth:`~Graph.bipartite_sets` | Returns `(X,Y)` where X and Y are the nodes in each bipartite set of graph.
- :meth:`~Graph.bipartite_color` | Returns a dictionary with vertices as the keys and the color class as the values.
- :meth:`~Graph.is_directed` | Since graph is undirected, returns False.
- **Distances:**
- .. csv-table::
- :class: contentstable
- :widths: 30, 70
- :delim: |
- :meth:`~Graph.centrality_closeness` | Returns the closeness centrality (1/average distance to all vertices)
- :meth:`~Graph.centrality_degree` | Returns the degree centrality
- :meth:`~Graph.centrality_betweenness` | Returns the betweenness centrality
- **Graph properties:**
- .. csv-table::
- :class: contentstable
- :widths: 30, 70
- :delim: |
- :meth:`~Graph.is_prime` | Tests whether the current graph is prime.
- :meth:`~Graph.is_split` | Returns ``True`` if the graph is a Split graph, ``False`` otherwise.
- :meth:`~Graph.is_triangle_free` | Returns whether ``self`` is triangle-free.
- :meth:`~Graph.is_bipartite` | Returns True if graph G is bipartite, False if not.
- :meth:`~Graph.is_line_graph` | Tests wether the graph is a line graph.
- :meth:`~Graph.is_odd_hole_free` | Tests whether ``self`` contains an induced odd hole.
- :meth:`~Graph.is_even_hole_free` | Tests whether ``self`` contains an induced even hole.
- :meth:`~Graph.is_cartesian_product` | Tests whether ``self`` is a cartesian product of graphs.
- :meth:`~Graph.is_long_hole_free` | Tests whether ``self`` contains an induced cycle of length at least 5.
- :meth:`~Graph.is_long_antihole_free` | Tests whether ``self`` contains an induced anticycle of length at least 5.
- :meth:`~Graph.is_weakly_chordal` | Tests whether ``self`` is weakly chordal.
- :meth:`~Graph.is_strongly_regular` | Tests whether ``self`` is strongly regular.
- :meth:`~Graph.odd_girth` | Returns the odd girth of ``self``.
- :meth:`~Graph.is_edge_transitive` | Returns true if self is edge-transitive.
- :meth:`~Graph.is_arc_transitive` | Returns true if self is arc-transitive.
- :meth:`~Graph.is_half_transitive` | Returns true if self is a half-transitive graph.
- :meth:`~Graph.is_semi_symmetric` | Returns true if self is a semi-symmetric graph.
- **Connectivity and orientations:**
- .. csv-table::
- :class: contentstable
- :widths: 30, 70
- :delim: |
- :meth:`~Graph.gomory_hu_tree` | Returns a Gomory-Hu tree of self.
- :meth:`~Graph.minimum_outdegree_orientation` | Returns an orientation of ``self`` with the smallest possible maximum outdegree
- :meth:`~Graph.bounded_outdegree_orientation` | Computes an orientation of ``self`` such that every vertex `v` has out-degree less than `b(v)`
- :meth:`~Graph.strong_orientation` | Returns a strongly connected orientation of the current graph.
- :meth:`~Graph.degree_constrained_subgraph` | Returns a degree-constrained subgraph.
- **Clique-related methods:**
- .. csv-table::
- :class: contentstable
- :widths: 30, 70
- :delim: |
- :meth:`~Graph.clique_complex` | Returns the clique complex of self
- :meth:`~Graph.cliques_containing_vertex` | Returns the cliques containing each vertex
- :meth:`~Graph.cliques_vertex_clique_number` | Returns a dictionary of sizes of the largest maximal cliques containing each vertex
- :meth:`~Graph.cliques_get_clique_bipartite` | Returns a bipartite graph constructed such that maximal cliques are the right vertices and the left vertices are retained from the given graph
- :meth:`~Graph.cliques_get_max_clique_graph` | Returns a graph constructed with maximal cliques as vertices, and edges between maximal cliques sharing vertices.
- :meth:`~Graph.cliques_number_of` | Returns a dictionary of the number of maximal cliques containing each vertex, keyed by vertex.
- :meth:`~Graph.clique_number` | Returns the order of the largest clique of the graph.
- :meth:`~Graph.clique_maximum` | Returns the vertex set of a maximal order complete subgraph.
- :meth:`~Graph.cliques_maximum` | Returns the list of all maximum cliques
- :meth:`~Graph.cliques_maximal` | Returns the list of all maximal cliques
- **Algorithmically hard stuff:**
- .. csv-table::
- :class: contentstable
- :widths: 30, 70
- :delim: |
- :meth:`~Graph.vertex_cover` | Returns a minimum vertex cover of self
- :meth:`~Graph.independent_set` | Returns a maximum independent set.
- :meth:`~Graph.topological_minor` | Returns a topological `H`-minor from ``self`` if one exists.
- :meth:`~Graph.convexity_properties` | Returns a ``ConvexityProperties`` objet corresponding to ``self``.
- :meth:`~Graph.matching_polynomial` | Computes the matching polynomial of the graph `G`.
- :meth:`~Graph.rank_decomposition` | Returns an rank-decomposition of ``self`` achieving optiml rank-width.
- :meth:`~Graph.minor` | Returns the vertices of a minor isomorphic to `H` in the current graph.
- :meth:`~Graph.independent_set_of_representatives` | Returns an independent set of representatives.
- :meth:`~Graph.coloring` | Returns the first (optimal) proper vertex-coloring found.
- :meth:`~Graph.has_homomorphism_to` | Checks whether there is a morphism between two graphs.
- :meth:`~Graph.chromatic_number` | Returns the minimal number of colors needed to color the vertices of the graph.
- :meth:`~Graph.chromatic_polynomial` | Returns the chromatic polynomial of the graph.
- :meth:`~Graph.is_perfect` | Tests whether the graph is perfect.
- **Leftovers:**
- .. csv-table::
- :class: contentstable
- :widths: 30, 70
- :delim: |
- :meth:`~Graph.cores` | Returns the core number for each vertex in an ordered list.
- :meth:`~Graph.matching` | Returns a maximum weighted matching of the graph
- :meth:`~Graph.fractional_chromatic_index` | Computes the fractional chromatic index of ``self``
- :meth:`~Graph.modular_decomposition` | Returns the modular decomposition of the current graph.
- :meth:`~Graph.maximum_average_degree` | Returns the Maximum Average Degree (MAD) of the current graph.
- :meth:`~Graph.two_factor_petersen` | Returns a decomposition of the graph into 2-factors.
- AUTHORS:
- - Robert L. Miller (2006-10-22): initial version
- - William Stein (2006-12-05): Editing
- - Robert L. Miller (2007-01-13): refactoring, adjusting for
- NetworkX-0.33, fixed plotting bugs (2007-01-23): basic tutorial,
- edge labels, loops, multiple edges and arcs (2007-02-07): graph6
- and sparse6 formats, matrix input
- - Emily Kirkmann (2007-02-11): added graph_border option to plot
- and show
- - Robert L. Miller (2007-02-12): vertex color-maps, graph
- boundaries, graph6 helper functions in Cython
- - Robert L. Miller Sage Days 3 (2007-02-17-21): 3d plotting in
- Tachyon
- - Robert L. Miller (2007-02-25): display a partition
- - Robert L. Miller (2007-02-28): associate arbitrary objects to
- vertices, edge and arc label display (in 2d), edge coloring
- - Robert L. Miller (2007-03-21): Automorphism group, isomorphism
- check, canonical label
- - Robert L. Miller (2007-06-07-09): NetworkX function wrapping
- - Michael W. Hansen (2007-06-09): Topological sort generation
- - Emily Kirkman, Robert L. Miller Sage Days 4: Finished wrapping
- NetworkX
- - Emily Kirkman (2007-07-21): Genus (including circular planar,
- all embeddings and all planar embeddings), all paths, interior
- paths
- - Bobby Moretti (2007-08-12): fixed up plotting of graphs with
- edge colors differentiated by label
- - Jason Grout (2007-09-25): Added functions, bug fixes, and
- general enhancements
- - Robert L. Miller (Sage Days 7): Edge labeled graph isomorphism
- - Tom Boothby (Sage Days 7): Miscellaneous awesomeness
- - Tom Boothby (2008-01-09): Added graphviz output
- - David Joyner (2009-2): Fixed docstring bug related to GAP.
- - Stephen Hartke (2009-07-26): Fixed bug in blocks_and_cut_vertices()
- that caused an incorrect result when the vertex 0 was a cut vertex.
- - Stephen Hartke (2009-08-22): Fixed bug in blocks_and_cut_vertices()
- where the list of cut_vertices is not treated as a set.
- - Anders Jonsson (2009-10-10): Counting of spanning trees and out-trees added.
- - Nathann Cohen (2009-09) : Cliquer, Connectivity, Flows
- and everything that uses Linear Programming
- and class numerical.MIP
- - Nicolas M. Thiery (2010-02): graph layout code refactoring, dot2tex/graphviz interface
- - David Coudert (2012-04) : Reduction rules in vertex_cover.
- - Birk Eisermann (2012-06): added recognition of weakly chordal graphs and
- long-hole-free / long-antihole-free graphs
- Graph Format
- ------------
- Supported formats
- ~~~~~~~~~~~~~~~~~
- Sage Graphs can be created from a wide range of inputs. A few
- examples are covered here.
- - NetworkX dictionary format:
- ::
- sage: d = {0: [1,4,5], 1: [2,6], 2: [3,7], 3: [4,8], 4: [9], \
- 5: [7, 8], 6: [8,9], 7: [9]}
- sage: G = Graph(d); G
- Graph on 10 vertices
- sage: G.plot().show() # or G.show()
- - A NetworkX graph:
- ::
- sage: import networkx
- sage: K = networkx.complete_bipartite_graph(12,7)
- sage: G = Graph(K)
- sage: G.degree()
- [7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 12, 12, 12, 12, 12, 12, 12]
- - graph6 or sparse6 format:
- ::
- sage: s = ':I`AKGsaOs`cI]Gb~'
- sage: G = Graph(s, sparse=True); G
- Looped multi-graph on 10 vertices
- sage: G.plot().show() # or G.show()
- Note that the ``\`` character is an escape character in Python, and
- also a character used by graph6 strings:
- ::
- sage: G = Graph('Ihe\n@GUA')
- Traceback (most recent call last):
- ...
- RuntimeError: The string (Ihe) seems corrupt: for n = 10, the string is too short.
- In Python, the escaped character ``\`` is represented by ``\\``:
- ::
- sage: G = Graph('Ihe\\n@GUA')
- sage: G.plot().show() # or G.show()
- - adjacency matrix: In an adjacency matrix, each column and each
- row represent a vertex. If a 1 shows up in row `i`, column
- `j`, there is an edge `(i,j)`.
- ::
- sage: M = Matrix([(0,1,0,0,1,1,0,0,0,0),(1,0,1,0,0,0,1,0,0,0), \
- (0,1,0,1,0,0,0,1,0,0), (0,0,1,0,1,0,0,0,1,0),(1,0,0,1,0,0,0,0,0,1), \
- (1,0,0,0,0,0,0,1,1,0), (0,1,0,0,0,0,0,0,1,1),(0,0,1,0,0,1,0,0,0,1), \
- (0,0,0,1,0,1,1,0,0,0), (0,0,0,0,1,0,1,1,0,0)])
- sage: M
- [0 1 0 0 1 1 0 0 0 0]
- [1 0 1 0 0 0 1 0 0 0]
- [0 1 0 1 0 0 0 1 0 0]
- [0 0 1 0 1 0 0 0 1 0]
- [1 0 0 1 0 0 0 0 0 1]
- [1 0 0 0 0 0 0 1 1 0]
- [0 1 0 0 0 0 0 0 1 1]
- [0 0 1 0 0 1 0 0 0 1]
- [0 0 0 1 0 1 1 0 0 0]
- [0 0 0 0 1 0 1 1 0 0]
- sage: G = Graph(M); G
- Graph on 10 vertices
- sage: G.plot().show() # or G.show()
- - incidence matrix: In an incidence matrix, each row represents a
- vertex and each column represents an edge.
- ::
- sage: M = Matrix([(-1,0,0,0,1,0,0,0,0,0,-1,0,0,0,0), \
- (1,-1,0,0,0,0,0,0,0,0,0,-1,0,0,0),(0,1,-1,0,0,0,0,0,0,0,0,0,-1,0,0), \
- (0,0,1,-1,0,0,0,0,0,0,0,0,0,-1,0),(0,0,0,1,-1,0,0,0,0,0,0,0,0,0,-1), \
- (0,0,0,0,0,-1,0,0,0,1,1,0,0,0,0),(0,0,0,0,0,0,0,1,-1,0,0,1,0,0,0), \
- (0,0,0,0,0,1,-1,0,0,0,0,0,1,0,0),(0,0,0,0,0,0,0,0,1,-1,0,0,0,1,0), \
- (0,0,0,0,0,0,1,-1,0,0,0,0,0,0,1)])
- sage: M
- [-1 0 0 0 1 0 0 0 0 0 -1 0 0 0 0]
- [ 1 -1 0 0 0 0 0 0 0 0 0 -1 0 0 0]
- [ 0 1 -1 0 0 0 0 0 0 0 0 0 -1 0 0]
- [ 0 0 1 -1 0 0 0 0 0 0 0 0 0 -1 0]
- [ 0 0 0 1 -1 0 0 0 0 0 0 0 0 0 -1]
- [ 0 0 0 0 0 -1 0 0 0 1 1 0 0 0 0]
- [ 0 0 0 0 0 0 0 1 -1 0 0 1 0 0 0]
- [ 0 0 0 0 0 1 -1 0 0 0 0 0 1 0 0]
- [ 0 0 0 0 0 0 0 0 1 -1 0 0 0 1 0]
- [ 0 0 0 0 0 0 1 -1 0 0 0 0 0 0 1]
- sage: G = Graph(M); G
- Graph on 10 vertices
- sage: G.plot().show() # or G.show()
- sage: DiGraph(matrix(2,[0,0,-1,1]), format="incidence_matrix")
- Traceback (most recent call last):
- ...
- ValueError: There must be two nonzero entries (-1 & 1) per column.
- - a list of edges::
- sage: g = Graph([(1,3),(3,8),(5,2)])
- sage: g
- Graph on 5 vertices
- Generators
- ----------
- If you wish to iterate through all the isomorphism types of graphs,
- type, for example::
- sage: for g in graphs(4):
- ... print g.spectrum()
- [0, 0, 0, 0]
- [1, 0, 0, -1]
- [1.4142135623..., 0, 0, -1.4142135623...]
- [2, 0, -1, -1]
- [1.7320508075..., 0, 0, -1.7320508075...]
- [1, 1, -1, -1]
- [1.6180339887..., 0.6180339887..., -0.6180339887..., -1.6180339887...]
- [2.1700864866..., 0.3111078174..., -1, -1.4811943040...]
- [2, 0, 0, -2]
- [2.5615528128..., 0, -1, -1.5615528128...]
- [3, -1, -1, -1]
- For some commonly used graphs to play with, type
- ::
- sage: graphs.[tab] # not tested
- and hit {tab}. Most of these graphs come with their own custom
- plot, so you can see how people usually visualize these graphs.
- ::
- sage: G = graphs.PetersenGraph()
- sage: G.plot().show() # or G.show()
- sage: G.degree_histogram()
- [0, 0, 0, 10]
- sage: G.adjacency_matrix()
- [0 1 0 0 1 1 0 0 0 0]
- [1 0 1 0 0 0 1 0 0 0]
- [0 1 0 1 0 0 0 1 0 0]
- [0 0 1 0 1 0 0 0 1 0]
- [1 0 0 1 0 0 0 0 0 1]
- [1 0 0 0 0 0 0 1 1 0]
- [0 1 0 0 0 0 0 0 1 1]
- [0 0 1 0 0 1 0 0 0 1]
- [0 0 0 1 0 1 1 0 0 0]
- [0 0 0 0 1 0 1 1 0 0]
- ::
- sage: S = G.subgraph([0,1,2,3])
- sage: S.plot().show() # or S.show()
- sage: S.density()
- 1/2
- ::
- sage: G = GraphQuery(display_cols=['graph6'], num_vertices=7, diameter=5)
- sage: L = G.get_graphs_list()
- sage: graphs_list.show_graphs(L)
- .. _Graph:labels:
- Labels
- ------
- Each vertex can have any hashable object as a label. These are
- things like strings, numbers, and tuples. Each edge is given a
- default label of ``None``, but if specified, edges can
- have any label at all. Edges between vertices `u` and
- `v` are represented typically as ``(u, v, l)``, where
- ``l`` is the label for the edge.
- Note that vertex labels themselves cannot be mutable items::
- sage: M = Matrix( [[0,0],[0,0]] )
- sage: G = Graph({ 0 : { M : None } })
- Traceback (most recent call last):
- ...
- TypeError: mutable matrices are unhashable
- However, if one wants to define a dictionary, with the same keys
- and arbitrary objects for entries, one can make that association::
- sage: d = {0 : graphs.DodecahedralGraph(), 1 : graphs.FlowerSnark(), \
- 2 : graphs.MoebiusKantorGraph(), 3 : graphs.PetersenGraph() }
- sage: d[2]
- Moebius-Kantor Graph: Graph on 16 vertices
- sage: T = graphs.TetrahedralGraph()
- sage: T.vertices()
- [0, 1, 2, 3]
- sage: T.set_vertices(d)
- sage: T.get_vertex(1)
- Flower Snark: Graph on 20 vertices
- Database
- --------
- There is a database available for searching for graphs that satisfy
- a certain set of parameters, including number of vertices and
- edges, density, maximum and minimum degree, diameter, radius, and
- connectivity. To see a list of all search parameter keywords broken
- down by their designated table names, type
- ::
- sage: graph_db_info()
- {...}
- For more details on data types or keyword input, enter
- ::
- sage: GraphQuery? # not tested
- The results of a query can be viewed with the show method, or can be
- viewed individually by iterating through the results:
- ::
- sage: Q = GraphQuery(display_cols=['graph6'],num_vertices=7, diameter=5)
- sage: Q.show()
- Graph6
- --------------------
- F@?]O
- F@OKg
- F?`po
- F?gqg
- FIAHo
- F@R@o
- FA_pW
- FGC{o
- FEOhW
- Show each graph as you iterate through the results:
- ::
- sage: for g in Q:
- ... show(g)
- Visualization
- -------------
- To see a graph `G` you are working with, there
- are three main options. You can view the graph in two dimensions via
- matplotlib with ``show()``. ::
- sage: G = graphs.RandomGNP(15,.3)
- sage: G.show()
- And you can view it in three dimensions via jmol with ``show3d()``. ::
- sage: G.show3d()
- Or it can be rendered with `\mbox{\rm\LaTeX}`. This requires the right
- additions to a standard `\mbox{\rm\TeX}` installation. Then standard
- Sage commands, such as ``view(G)`` will display the graph, or
- ``latex(G)`` will produce a string suitable for inclusion in a
- `\mbox{\rm\LaTeX}` document. More details on this are at
- the :mod:`sage.graphs.graph_latex` module. ::
- sage: from sage.graphs.graph_latex import check_tkz_graph
- sage: check_tkz_graph() # random - depends on TeX installation
- sage: latex(G)
- \begin{tikzpicture}
- ...
- \end{tikzpicture}
- Methods
- -------
- """
- #*****************************************************************************
- # Copyright (C) 2006 - 2007 Robert L. Miller <rlmillster@gmail.com>
- #
- # Distributed under the terms of the GNU General Public License (GPL)
- # http://www.gnu.org/licenses/
- #*****************************************************************************
- from sage.rings.integer import Integer
- from sage.misc.superseded import deprecated_function_alias
- import sage.graphs.generic_graph_pyx as generic_graph_pyx
- from sage.graphs.generic_graph import GenericGraph
- from sage.graphs.digraph import DiGraph
- class Graph(GenericGraph):
- r"""
- Undirected graph.
- A graph is a set of vertices connected by edges. See also the
- :wikipedia:`Wikipedia article on graphs <Graph_(mathematics)>`.
- One can very easily create a graph in Sage by typing::
- sage: g = Graph()
- By typing the name of the graph, one can get some basic information
- about it::
- sage: g
- Graph on 0 vertices
- This graph is not very interesting as it is by default the empty graph.
- But Sage contains a large collection of pre-defined graph classes that
- can be listed this way:
- * Within a Sage session, type ``graphs.``
- (Do not press "Enter", and do not forget the final period ".")
- * Hit "tab".
- You will see a list of methods which will construct named graphs. For
- example::
- sage: g = graphs.PetersenGraph()
- sage: g.plot()
- or::
- sage: g = graphs.ChvatalGraph()
- sage: g.plot()
- In order to obtain more information about these graph constructors, access
- the documentation using the command ``graphs.RandomGNP?``.
- Once you have defined the graph you want, you can begin to work on it
- by using the almost 200 functions on graphs in the Sage library!
- If your graph is named ``g``, you can list these functions as previously
- this way
- * Within a Sage session, type ``g.``
- (Do not press "Enter", and do not forget the final period "." )
- * Hit "tab".
- As usual, you can get some information about what these functions do by
- typing (e.g. if you want to know about the ``diameter()`` method)
- ``g.diameter?``.
- If you have defined a graph ``g`` having several connected components
- (i.e. ``g.is_connected()`` returns False), you can print each one of its
- connected components with only two lines::
- sage: for component in g.connected_components():
- ... g.subgraph(component).plot()
- INPUT:
- - ``data`` -- can be any of the following (see the ``format`` argument):
- #. An integer specifying the number of vertices
- #. A dictionary of dictionaries
- #. A dictionary of lists
- #. A NumPy matrix or ndarray
- #. A Sage adjacency matrix or incidence matrix
- #. A pygraphviz graph
- #. A SciPy sparse matrix
- #. A NetworkX graph
- - ``pos`` - a positioning dictionary: for example, the
- spring layout from NetworkX for the 5-cycle is::
- {0: [-0.91679746, 0.88169588],
- 1: [ 0.47294849, 1.125 ],
- 2: [ 1.125 ,-0.12867615],
- 3: [ 0.12743933,-1.125 ],
- 4: [-1.125 ,-0.50118505]}
-
- - ``name`` - (must be an explicitly named parameter,
- i.e., ``name="complete")`` gives the graph a name
-
- - ``loops`` - boolean, whether to allow loops (ignored
- if data is an instance of the ``Graph`` class)
-
- - ``multiedges`` - boolean, whether to allow multiple
- edges (ignored if data is an instance of the ``Graph`` class)
-
- - ``weighted`` - whether graph thinks of itself as
- weighted or not. See ``self.weighted()``
-
- - ``format`` - if None, Graph tries to guess- can be
- several values, including:
-
- - ``'int'`` - an integer specifying the number of vertices in an
- edge-free graph with vertices labelled from 0 to n-1
- - ``'graph6'`` - Brendan McKay's graph6 format, in a
- string (if the string has multiple graphs, the first graph is
- taken)
- - ``'sparse6'`` - Brendan McKay's sparse6 format, in a
- string (if the string has multiple graphs, the first graph is
- taken)
-
- - ``'adjacency_matrix'`` - a square Sage matrix M,
- with M[i,j] equal to the number of edges {i,j}
- - ``'weighted_adjacency_matrix'`` - a square Sage
- matrix M, with M[i,j] equal to the weight of the single edge {i,j}.
- Given this format, weighted is ignored (assumed True).
- - ``'incidence_matrix'`` - a Sage matrix, with one
- column C for each edge, where if C represents {i, j}, C[i] is -1
- and C[j] is 1
-
- - ``'elliptic_curve_congruence'`` - data must be an
- iterable container of elliptic curves, and the graph produced has
- each curve as a vertex (it's Cremona label) and an edge E-F
- labelled p if and only if E is congruent to F mod p
- - ``NX`` - data must be a NetworkX Graph.
-
- .. NOTE::
- As Sage's default edge labels is ``None`` while NetworkX uses
- ``{}``, the ``{}`` labels of a NetworkX graph are automatically
- set to ``None`` when it is converted to a Sage graph. This
- behaviour can be overruled by setting the keyword
- ``convert_empty_dict_labels_to_None`` to ``False`` (it is
- ``True`` by default).
-
- - ``boundary`` - a list of boundary vertices, if
- empty, graph is considered as a 'graph without boundary'
-
- - ``implementation`` - what to use as a backend for
- the graph. Currently, the options are either 'networkx' or
- 'c_graph'
-
- - ``sparse`` - only for implementation == 'c_graph'.
- Whether to use sparse or dense graphs as backend. Note that
- currently dense graphs do not have edge labels, nor can they be
- multigraphs
-
- - ``vertex_labels`` - only for implementation == 'c_graph'.
- Whether to allow any object as a vertex (slower), or
- only the integers 0, ..., n-1, where n is the number of vertices.
-
- - ``convert_empty_dict_labels_to_None`` - this arguments sets
- the default edge labels used by NetworkX (empty dictionaries)
- to be replaced by None, the default Sage edge label. It is
- set to ``True`` iff a NetworkX graph is on the input.
-
-
- EXAMPLES:
- We illustrate the first seven input formats (the other two
- involve packages that are currently not standard in Sage):
-
- #. An integer giving the number of vertices::
-
- sage: g = Graph(5); g
- Graph on 5 vertices
- sage: g.vertices()
- [0, 1, 2, 3, 4]
- sage: g.edges()
- []
- #. A dictionary of dictionaries::
-
- sage: g = Graph({0:{1:'x',2:'z',3:'a'}, 2:{5:'out'}}); g
- Graph on 5 vertices
-
- The labels ('x', 'z', 'a', 'out') are labels for edges. For
- example, 'out' is the label for the edge on 2 and 5. Labels can be
- used as weights, if all the labels share some common parent.
- ::
- sage: a,b,c,d,e,f = sorted(SymmetricGroup(3))
- sage: Graph({b:{d:'c',e:'p'}, c:{d:'p',e:'c'}})
- Graph on 4 vertices
-
- #. A dictionary of lists::
-
- sage: g = Graph({0:[1,2,3], 2:[4]}); g
- Graph on 5 vertices
-
- #. A list of vertices and a function describing adjacencies. Note
- that the list of vertices and the function must be enclosed in a
- list (i.e., [list of vertices, function]).
-
- Construct the Paley graph over GF(13).
-
- ::
-
- sage: g=Graph([GF(13), lambda i,j: i!=j and (i-j).is_square()])
- sage: g.vertices()
- [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
- sage: g.adjacency_matrix()
- [0 1 0 1 1 0 0 0 0 1 1 0 1]
- [1 0 1 0 1 1 0 0 0 0 1 1 0]
- [0 1 0 1 0 1 1 0 0 0 0 1 1]
- [1 0 1 0 1 0 1 1 0 0 0 0 1]
- [1 1 0 1 0 1 0 1 1 0 0 0 0]
- [0 1 1 0 1 0 1 0 1 1 0 0 0]
- [0 0 1 1 0 1 0 1 0 1 1 0 0]
- [0 0 0 1 1 0 1 0 1 0 1 1 0]
- [0 0 0 0 1 1 0 1 0 1 0 1 1]
- [1 0 0 0 0 1 1 0 1 0 1 0 1]
- [1 1 0 0 0 0 1 1 0 1 0 1 0]
- [0 1 1 0 0 0 0 1 1 0 1 0 1]
- [1 0 1 1 0 0 0 0 1 1 0 1 0]
-
- Construct the line graph of a complete graph.
-
- ::
-
- sage: g=graphs.CompleteGraph(4)
- sage: line_graph=Graph([g.edges(labels=false), \
- lambda i,j: len(set(i).intersection(set(j)))>0], \
- loops=False)
- sage: line_graph.vertices()
- [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]
- sage: line_graph.adjacency_matrix()
- [0 1 1 1 1 0]
- [1 0 1 1 0 1]
- [1 1 0 0 1 1]
- [1 1 0 0 1 1]
- [1 0 1 1 0 1]
- [0 1 1 1 1 0]
-
- #. A NumPy matrix or ndarray::
-
- sage: import numpy
- sage: A = numpy.array([[0,1,1],[1,0,1],[1,1,0]])
- sage: Graph(A)
- Graph on 3 vertices
-
- #. A graph6 or sparse6 string: Sage automatically recognizes
- whether a string is in graph6 or sparse6 format::
-
- sage: s = ':I`AKGsaOs`cI]Gb~'
- sage: Graph(s,sparse=True)
- Looped multi-graph on 10 vertices
-
- ::
-
- sage: G = Graph('G?????')
- sage: G = Graph("G'?G?C")
- Traceback (most recent call last):
- ...
- RuntimeError: The string seems corrupt: valid characters are
- ?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
- sage: G = Graph('G??????')
- Traceback (most recent call last):
- ...
- RuntimeError: The string (G??????) seems corrupt: for n = 8, the string is too long.
-
- ::
-
- sage: G = Graph(":I'AKGsaOs`cI]Gb~")
- Traceback (most recent call last):
- ...
- RuntimeError: The string seems corrupt: valid characters are
- ?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
-
- There are also list functions to take care of lists of graphs::
-
- sage: s = ':IgMoqoCUOqeb\n:I`AKGsaOs`cI]Gb~\n:I`EDOAEQ?PccSsge\N\n'
- sage: graphs_list.from_sparse6(s)
- [Looped multi-graph on 10 vertices, Looped multi-graph on 10 vertices, Looped multi-graph on 10 vertices]
-
- #. A Sage matrix:
- Note: If format is not specified, then Sage assumes a symmetric square
- matrix is an adjacency matrix, otherwise an incidence matrix.
-
- - an adjacency matrix::
-
- sage: M = graphs.PetersenGraph().am(); M
- [0 1 0 0 1 1 0 0 0 0]
- [1 0 1 0 0 0 1 0 0 0]
- [0 1 0 1 0 0 0 1 0 0]
- [0 0 1 0 1 0 0 0 1 0]
- [1 0 0 1 0 0 0 0 0 1]
- [1 0 0 0 0 0 0 1 1 0]
- [0 1 0 0 0 0 0 0 1 1]
- [0 0 1 0 0 1 0 0 0 1]
- [0 0 0 1 0 1 1 0 0 0]
- [0 0 0 0 1 0 1 1 0 0]
- sage: Graph(M)
- Graph on 10 vertices
- ::
- sage: Graph(matrix([[1,2],[2,4]]),loops=True,sparse=True)
- Looped multi-graph on 2 vertices
-
- sage: M = Matrix([[0,1,-1],[1,0,-1/2],[-1,-1/2,0]]); M
- [ 0 1 -1]
- [ 1 0 -1/2]
- [ -1 -1/2 0]
- sage: G = Graph(M,sparse=True); G
- Graph on 3 vertices
- sage: G.weighted()
- True
- - an incidence matrix::
- sage: M = Matrix(6, [-1,0,0,0,1, 1,-1,0,0,0, 0,1,-1,0,0, 0,0,1,-1,0, 0,0,0,1,-1, 0,0,0,0,0]); M
- [-1 0 0 0 1]
- [ 1 -1 0 0 0]
- [ 0 1 -1 0 0]
- [ 0 0 1 -1 0]
- [ 0 0 0 1 -1]
- [ 0 0 0 0 0]
- sage: Graph(M)
- Graph on 6 vertices
-
- sage: Graph(Matrix([[1],[1],[1]]))
- Traceback (most recent call last):
- ...
- ValueError: Non-symmetric or non-square matrix assumed to be an incidence matrix: There must be two nonzero entries (-1 & 1) per column.
- sage: Graph(Matrix([[1],[1],[0]]))
- Traceback (most recent call last):
- ...
- ValueError: Non-symmetric or non-square matrix assumed to be an incidence matrix: Each column represents an edge: -1 goes to 1.
- sage: M = Matrix([[0,1,-1],[1,0,-1],[-1,-1,0]]); M
- [ 0 1 -1]
- [ 1 0 -1]
- [-1 -1 0]
- sage: Graph(M,sparse=True)
- Graph on 3 vertices
- sage: M = Matrix([[0,1,1],[1,0,1],[-1,-1,0]]); M
- [ 0 1 1]
- [ 1 0 1]
- [-1 -1 0]
- sage: Graph(M)
- Traceback (most recent call last):
- ...
- ValueError: Non-symmetric or non-square matrix assumed to be an incidence matrix: Each column represents an edge: -1 goes to 1.
- ::
- sage: MA = Matrix([[1,2,0], [0,2,0], [0,0,1]]) # trac 9714
- sage: MI = Graph(MA, format='adjacency_matrix').incidence_matrix(); MI
- [-1 -1 0 0 0 1]
- [ 1 1 0 1 1 0]
- [ 0 0 1 0 0 0]
- sage: Graph(MI).edges(labels=None)
- [(0, 0), (0, 1), (0, 1), (1, 1), (1, 1), (2, 2)]
- sage: M = Matrix([[1], [-1]]); M
- [ 1]
- [-1]
- sage: Graph(M).edges()
- [(0, 1, None)]
- #. a list of edges, or labelled edges::
- sage: g = Graph([(1,3),(3,8),(5,2)])
- sage: g
- Graph on 5 vertices
- ::
- sage: g = Graph([(1,2,"Peace"),(7,-9,"and"),(77,2, "Love")])
- sage: g
- Graph on 5 vertices
- sage: g = Graph([(0, 2, '0'), (0, 2, '1'), (3, 3, '2')])
- sage: g.loops()
- [(3, 3, '2')]
- #. A NetworkX MultiGraph::
- sage: import networkx
- sage: g = networkx.MultiGraph({0:[1,2,3], 2:[4]})
- sage: Graph(g)
- Graph on 5 vertices
-
- #. A NetworkX graph::
-
- sage: import networkx
- sage: g = networkx.Graph({0:[1,2,3], 2:[4]})
- sage: DiGraph(g)
- Digraph on 5 vertices
-
- Note that in all cases, we copy the NetworkX structure.
-
- ::
-
- sage: import networkx
- sage: g = networkx.Graph({0:[1,2,3], 2:[4]})
- sage: G = Graph(g, implementation='networkx')
- sage: H = Graph(g, implementation='networkx')
- sage: G._backend._nxg is H._backend._nxg
- False
- """
- _directed = False
-
- def __init__(self, data=None, pos=None, loops=None, format=None,
- boundary=[], weighted=None, implementation='c_graph',
- sparse=True, vertex_labels=True, name=None,
- multiedges=None, convert_empty_dict_labels_to_None=None):
- """
- TESTS::
-
- sage: G = Graph()
- sage: loads(dumps(G)) == G
- True
- sage: a = matrix(2,2,[1,0,0,1])
- sage: Graph(a).adjacency_matrix() == a
- True
- sage: a = matrix(2,2,[2,0,0,1])
- sage: Graph(a,sparse=True).adjacency_matrix() == a
- True
- The positions are copied when the graph is built from
- another graph ::
- sage: g = graphs.PetersenGraph()
- sage: h = Graph(g)
- sage: g.get_pos() == h.get_pos()
- True
- Or from a DiGraph ::
- sage: d = DiGraph(g)
- sage: h = Graph(d)
- sage: g.get_pos() == h.get_pos()
- True
- Loops are not counted as multiedges (see trac 11693) and edges
- are not counted twice ::
- sage: Graph([[1,1]],multiedges=False).num_edges()
- 1
- sage: Graph([[1,2],[1,2]],multiedges=True).num_edges()
- 2
- Invalid sequence of edges given as an input (they do not all
- have the same length)::
- sage: g = Graph([(1,2),(2,3),(2,3,4)])
- Traceback (most recent call last):
- ...
- ValueError: Edges input must all follow the same format.
- Two different labels given for the same edge in a graph
- without multiple edges::
- sage: g = Graph([(1,2,3),(1,2,4)], multiedges = False)
- Traceback (most recent call last):
- ...
- ValueError: Two different labels given for the same edge in a graph without multiple edges.
- The same edge included more than once in a graph without
- multiple edges::
- sage: g = Graph([[1,2],[1,2]],multiedges=False)
- Traceback (most recent call last):
- ...
- ValueError: Non-multigraph input dict has multiple edges (1,2)
- An empty list or dictionary defines a simple graph
- (:trac:`10441` and :trac:`12910`)::
- sage: Graph([])
- Graph on 0 vertices
- sage: Graph({})
- Graph on 0 vertices
- sage: # not "Multi-graph on 0 vertices"
- Verify that the int format works as expected (:trac:`12557`)::
- sage: Graph(2).adjacency_matrix()
- [0 0]
- [0 0]
- sage: Graph(3) == Graph(3,format='int')
- True
- Problem with weighted adjacency matrix (:trac:`13919`)::
- sage: B = {0:{1:2,2:5,3:4},1:{2:2,4:7},2:{3:1,4:4,5:3},3:{5:4},4:{5:1,6:5},5:{6:7}}
- sage: grafo3 = Graph(B,weighted=True)
- sage: matad = grafo3.weighted_adjacency_matrix()
- sage: grafo4 = Graph(matad,format = "adjacency_matrix", weighted=True)
- sage: grafo4.shortest_path(0,6,by_weight=True)
- [0, 1, 2, 5, 4, 6]
- """
- GenericGraph.__init__(self)
- msg = ''
- from sage.structure.element import is_Matrix
- from sage.misc.misc import uniq
- if format is None and isinstance(data, str):
- if data[:10] == ">>graph6<<":
- data = data[10:]
- format = 'graph6'
- elif data[:11] == ">>sparse6<<":
- data = data[11:]
- format = 'sparse6'
- elif data[0] == ':':
- format = 'sparse6'
- else:
- format = 'graph6'
- if format is None and is_Matrix(data):
- if data.is_square() and data == data.transpose():
- format = 'adjacency_matrix'
- else:
- format = 'incidence_matrix'
- msg += "Non-symmetric or non-square matrix assumed to be an incidence matrix: "
- if format is None and isinstance(data, Graph):
- format = 'Graph'
- from sage.graphs.all import DiGraph
- if format is None and isinstance(data, DiGraph):
- data = data.to_undirected()
- format = 'Graph'
- if format is None and isinstance(data,list) and \
- len(data)>=2 and callable(data[1]):
- format = 'rule'
- if format is None and isinstance(data,dict):
- keys = data.keys()
- if len(keys) == 0: format = 'dict_of_dicts'
- else:
- if isinstance(data[keys[0]], list):
- format = 'dict_of_lists'
- elif isinstance(data[keys[0]], dict):
- format = 'dict_of_dicts'
- if format is None and hasattr(data, 'adj'):
- import networkx
- if isinstance(data, (networkx.DiGraph, networkx.MultiDiGraph)):
- data = data.to_undirected()
- format = 'NX'
- elif isinstance(data, (networkx.Graph, networkx.MultiGraph)):
- format = 'NX'
- if format is None and isinstance(data, (int, Integer)):
- format = 'int'
- if format is None and data is None:
- format = 'int'
- data = 0
- # Input is a list of edges
- if format is None and isinstance(data, list):
- # If we are given a list (we assume it is a list of
- # edges), we convert the problem to a dict_of_dicts or a
- # dict_of_lists
- edges = data
- # Along the process, we are assuming all edges have the
- # same length. If it is not true, a ValueError will occur
- try:
- # The list is empty
- if not data:
- data = {}
- format = 'dict_of_dicts'
- # The edges are not labelled
- elif len(data[0]) == 2:
- data = {}
- for u,v in edges:
- if not u in data:
- data[u] = []
- if not v in data:
- data[v] = []
- data[u].append(v)
- format = 'dict_of_lists'
- # The edges are labelled
- elif len(data[0]) == 3:
- data = {}
- for u,v,l in edges:
- if not u in data:
- data[u] = {}
- if not v in data:
- data[v] = {}
- # Now the keys exists, and are dictionaries ...
- # If we notice for the first time that there
- # are multiple edges, we update the whole
- # dictionary so that data[u][v] is a list
- if (multiedges is None and (u in data[v])):
- multiedges = True
- for uu, dd in data.iteritems():
- for vv, ddd in dd.iteritems():
- dd[vv] = [ddd]
- # If multiedges is set to False while the same
- # edge is present in the list with different
- # values of its label
- elif (multiedges is False and
- (u in data[v] and data[v][u] != l)):
- raise ValueError("MULTIEDGE")
- # Now we are behaving as if multiedges == None
- # means multiedges = False. If something bad
- # happens later, the whole dictionary will be
- # updated anyway
- if multiedges is True:
- if v not in data[u]:
- data[u][v] = []
- data[v][u] = []
- data[u][v].append(l)
- if u != v:
- data[v][u].append(l)
- else:
- data[u][v] = l
- data[v][u] = l
- format = 'dict_of_dicts'
- except ValueError as ve:
- if str(ve) == "MULTIEDGE":
- raise ValueError("Two different labels given for the same edge in a graph without multiple edges.")
- else:
- raise ValueError("Edges input must all follow the same format.")
-
- if format is None:
- import networkx
- data = networkx.MultiGraph(data)
- format = 'NX'
-
- # At this point, format has been set.
-
- # adjust for empty dicts instead of None in NetworkX default edge labels
- if convert_empty_dict_labels_to_None is None:
- convert_empty_dict_labels_to_None = (format == 'NX')
- verts = None
- if format == 'graph6':
- if loops is None: loops = False
- if weighted is None: weighted = False
- if multiedges is None: multiedges = False
- if not isinstance(data, str):
- raise ValueError('If input format is graph6, then data must be a string.')
- n = data.find('\n')
- if n == -1:
- n = len(data)
- ss = data[:n]
- n, s = generic_graph_pyx.N_inverse(ss)
- m = generic_graph_pyx.R_inverse(s, n)
- expected = n*(n-1)/2 + (6 - n*(n-1)/2)%6
- if len(m) > expected:
- raise RuntimeError("The string (%s) seems corrupt: for n = %d, the string is too long."%(ss,n))
- elif len(m) < expected:
- raise RuntimeError("The string (%s) seems corrupt: for n = %d, the string is too short."%(ss,n))
- num_verts = n
- elif format == 'sparse6':
- if loops is None: loops = True
- if weighted is None: weighted = False
- if multiedges is None: multiedges = True
- from math import ceil, floor
- from sage.misc.functional import log
- n = data.find('\n')
- if n == -1:
- n = len(data)
- s = data[:n]
- n, s = generic_graph_pyx.N_inverse(s[1:])
- if n == 0:
- edges = []
- else:
- k = int(ceil(log(n,2)))
- ords = [ord(i) for i in s]
- if any(o > 126 or o < 63 for o in ords):
- raise RuntimeError("The string seems corrupt: valid characters are \n" + ''.join([chr(i) for i in xrange(63,127)]))
- bits = ''.join([generic_graph_pyx.binary(o-63).zfill(6) for o in ords])
- b = []
- x = []
- for i in xrange(int(floor(len(bits)/(k+1)))):
- b.append(int(bits[(k+1)*i:(k+1)*i+1],2))
- x.append(int(bits[(k+1)*i+1:(k+1)*i+k+1],2))
- v = 0
- edges = []
- for i in xrange(len(b)):
- if b[i] == 1:
- v += 1
- if x[i] > v:
- v = x[i]
- else:
- if v < n:
- edges.append((x[i],v))
- num_verts = n
- elif format in ['adjacency_matrix', 'incidence_matrix']:
- assert is_Matrix(data)
- if format == 'weighted_adjacency_matrix':
- if weighted is False:
- raise ValueError("Format was weighted_adjacency_matrix but weighted was False.")
- if weighted is None: weighted = True
- if multiedges is None: multiedges = False
- format = 'adjacency_matrix'
- if format == 'adjacency_matrix':
- entries = uniq(data.list())
- for e in entries:
- try:
- e = int(e)
- assert e >= 0
- except StandardError:
- if weighted is False:
- raise ValueError("Non-weighted graph's"+
- " adjacency matrix must have only nonnegative"+
- " integer entries")
- weighted = True
- if multiedges is None: multiedges = False
- break
- if weighted is None:
- weighted = False
- if multiedges is None:
- multiedges = ((not weighted) and sorted(entries) != [0,1])
- for i in xrange(data.nrows()):
- if data[i,i] != 0:
- if loops is None: loops = True
- elif not loops:
- raise ValueError("Non-looped graph's adjacency"+
- " matrix must have zeroes on the diagonal.")
- break
- num_verts = data.nrows()
- elif format == 'incidence_matrix':
- try:
- positions = []
- for c in data.columns():
- NZ = c.nonzero_positions()
- if len(NZ) == 1:
- if loops is None:
- loops = True
- elif not loops:
- msg += "There must be two nonzero entries (-1 & 1) per column."
- assert False
- positions.append((NZ[0], NZ[0]))
- elif len(NZ) != 2:
- msg += "There must be two nonzero entries (-1 & 1) per column."
- assert False
- else:
- positions.append(tuple(NZ))
- L = uniq(c.list())
- L.sort()
- if data.nrows() != (2 if len(NZ) == 2 else 1):
- desirable = [-1, 0, 1] if len(NZ) == 2 else [0, 1]
- else:
- desirable = [-1, 1] if len(NZ) == 2 else [1]
- if L != desirable:
- msg += "Each column represents an edge: -1 goes to 1."
- assert False
- if loops is None: loops = False
- if weighted is None: weighted = False
- if multiedges is None:
- total = len(positions)
- multiedges = ( len(uniq(positions)) < total )
- except AssertionError:
- raise ValueError(msg)
- num_verts = data.nrows()
- elif format == 'Graph':
- if loops is None: loops = data.allows_loops()
- elif not loops and data.has_loops():
- raise ValueError("No loops but input graph has loops.")
- if multiedges is None: multiedges = data.allows_multiple_edges()
- elif not multiedges:
- e = data.edges(labels=False)
- e = [sorted(f) for f in e]
- if len(e) != len(uniq(e)):
- raise ValueError("No multiple edges but input graph"+
- " has multiple edges.")
- if weighted is None: weighted = data.weighted()
- num_verts = data.num_verts()
- verts = data.vertex_iterator()
- if data.get_pos() is not None:
- pos = data.get_pos().copy()
- elif format == 'rule':
- f = data[1]
- if loops is None: loops = any(f(v,v) for v in data[0])
- if multiedges is None: multiedges = False
- if weighted is None: weighted = False
- num_verts = len(data[0])
- verts = data[0]
- elif format == 'dict_of_dicts':
- if not all(isinstance(data[u], dict) for u in data):
- raise ValueError("Input dict must be a consistent format.")
- verts = set(data.keys())
- if loops is None or loops is False:
- for u in data:
- if u in data[u]:
- if loops is None: loops = True
- elif loops is False:
- raise ValueError("No loops but dict has loops.")
- break
- if loops is None: loops = False
- if weighted is None: weighted = False
- for u in data:
- for v in data[u]:
- if v not in verts: verts.add(v)
- if hash(u) > hash(v):
- if v in data and u in data[v]:
- if data[u][v] != data[v][u]:
- raise ValueError("Dict does not agree on edge (%s,%s)"%(u,v))
- continue
- if multiedges is not False and not isinstance(data[u][v], list):
- if multiedges is None: multiedges = False
- if multiedges:
- raise ValueError("Dict of dicts for multigraph must be in the format {v : {u : list}}")
- if multiedges is None and len(data) > 0:
- multiedges = True
- num_verts = len(verts)
- elif format == 'dict_of_lists':
- if not all(isinstance(data[u], list) for u in data):
- raise ValueError("Input dict must be a consistent format.")
- verts = set(data.keys())
- if loops is None or loops is False:
- for u in data:
- if u in data[u]:
- if loops is None: loops = True
- elif loops is False:
- raise ValueError("No loops but dict has loops.")
- break
- if loops is None: loops = False
- if weighted is None: weighted = False
- for u in data:
- verts=verts.union([v for v in data[u] if v not in verts])
- …
Large files files are truncated, but you can click here to view the full file