Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/graphs/generic_graph.py
4045 views
1
r"""
2
Generic graphs
3
4
This module implements the base class for graphs and digraphs, and methods that
5
can be applied on both. Here is what it can do:
6
7
**Basic Graph operations:**
8
9
.. csv-table::
10
:class: contentstable
11
:widths: 30, 70
12
:delim: |
13
14
:meth:`~GenericGraph.networkx_graph` | Creates a new NetworkX graph from the Sage graph
15
:meth:`~GenericGraph.adjacency_matrix` | Returns the adjacency matrix of the (di)graph.
16
:meth:`~GenericGraph.incidence_matrix` | Returns an incidence matrix of the (di)graph
17
:meth:`~GenericGraph.weighted_adjacency_matrix` | Returns the weighted adjacency matrix of the graph
18
:meth:`~GenericGraph.kirchhoff_matrix` | Returns the Kirchhoff matrix (a.k.a. the Laplacian) of the graph.
19
:meth:`~GenericGraph.get_boundary` | Returns the boundary of the (di)graph.
20
:meth:`~GenericGraph.set_boundary` | Sets the boundary of the (di)graph.
21
:meth:`~GenericGraph.has_loops` | Returns whether there are loops in the (di)graph.
22
:meth:`~GenericGraph.allows_loops` | Returns whether loops are permitted in the (di)graph.
23
:meth:`~GenericGraph.allow_loops` | Changes whether loops are permitted in the (di)graph.
24
:meth:`~GenericGraph.loops` | Returns any loops in the (di)graph.
25
:meth:`~GenericGraph.has_multiple_edges` | Returns whether there are multiple edges in the (di)graph.
26
:meth:`~GenericGraph.allows_multiple_edges` | Returns whether multiple edges are permitted in the (di)graph.
27
:meth:`~GenericGraph.allow_multiple_edges` | Changes whether multiple edges are permitted in the (di)graph.
28
:meth:`~GenericGraph.multiple_edges` | Returns any multiple edges in the (di)graph.
29
:meth:`~GenericGraph.name` | Returns or sets the graph's name.
30
:meth:`~GenericGraph.weighted` | Whether the (di)graph is to be considered as a weighted (di)graph.
31
:meth:`~GenericGraph.antisymmetric` | Tests whether the graph is antisymmetric
32
:meth:`~GenericGraph.density` | Returns the density
33
:meth:`~GenericGraph.order` | Returns the number of vertices.
34
:meth:`~GenericGraph.size` | Returns the number of edges.
35
:meth:`~GenericGraph.add_vertex` | Creates an isolated vertex.
36
:meth:`~GenericGraph.add_vertices` | Add vertices to the (di)graph from an iterable container
37
:meth:`~GenericGraph.delete_vertex` | Deletes a vertex, removing all incident edges.
38
:meth:`~GenericGraph.delete_vertices` | Remove vertices from the (di)graph taken from an iterable container of vertices.
39
:meth:`~GenericGraph.has_vertex` | Return True if vertex is one of the vertices of this graph.
40
:meth:`~GenericGraph.random_vertex` | Returns a random vertex of self.
41
:meth:`~GenericGraph.random_edge` | Returns a random edge of self.
42
:meth:`~GenericGraph.vertex_boundary` | Returns a list of all vertices in the external boundary of vertices1, intersected with vertices2.
43
:meth:`~GenericGraph.set_vertices` | Associate arbitrary objects with each vertex
44
:meth:`~GenericGraph.set_vertex` | Associate an arbitrary object with a vertex.
45
:meth:`~GenericGraph.get_vertex` | Retrieve the object associated with a given vertex.
46
:meth:`~GenericGraph.get_vertices` | Return a dictionary of the objects associated to each vertex.
47
:meth:`~GenericGraph.loop_vertices` | Returns a list of vertices with loops.
48
:meth:`~GenericGraph.vertex_iterator` | Returns an iterator over the vertices.
49
:meth:`~GenericGraph.neighbor_iterator` | Return an iterator over neighbors of vertex.
50
:meth:`~GenericGraph.vertices` | Return a list of the vertices.
51
:meth:`~GenericGraph.neighbors` | Return a list of neighbors (in and out if directed) of vertex.
52
:meth:`~GenericGraph.merge_vertices` | Merge vertices.
53
:meth:`~GenericGraph.add_edge` | Adds an edge from u and v.
54
:meth:`~GenericGraph.add_edges` | Add edges from an iterable container.
55
:meth:`~GenericGraph.subdivide_edge` | Subdivides an edge `k` times.
56
:meth:`~GenericGraph.subdivide_edges` | Subdivides k times edges from an iterable container.
57
:meth:`~GenericGraph.delete_edge` | Delete the edge from u to v
58
:meth:`~GenericGraph.delete_edges` | Delete edges from an iterable container.
59
:meth:`~GenericGraph.delete_multiedge` | Deletes all edges from u and v.
60
:meth:`~GenericGraph.set_edge_label` | Set the edge label of a given edge.
61
:meth:`~GenericGraph.has_edge` | Returns True if (u, v) is an edge, False otherwise.
62
:meth:`~GenericGraph.edges` | Return a list of edges.
63
:meth:`~GenericGraph.edge_boundary` | Returns a list of edges `(u,v,l)` with `u` in ``vertices1``
64
:meth:`~GenericGraph.edge_iterator` | Returns an iterator over edges.
65
:meth:`~GenericGraph.edges_incident` | Returns incident edges to some vertices.
66
:meth:`~GenericGraph.edge_label` | Returns the label of an edge.
67
:meth:`~GenericGraph.edge_labels` | Returns a list of edge labels.
68
:meth:`~GenericGraph.remove_multiple_edges` | Removes all multiple edges, retaining one edge for each.
69
:meth:`~GenericGraph.remove_loops` | Removes loops on vertices in vertices. If vertices is None, removes all loops.
70
:meth:`~GenericGraph.loop_edges` | Returns a list of all loops in the graph.
71
:meth:`~GenericGraph.number_of_loops` | Returns the number of edges that are loops.
72
:meth:`~GenericGraph.clear` | Empties the graph of vertices and edges and removes name, boundary, associated objects, and position information.
73
:meth:`~GenericGraph.degree` | Gives the degree (in + out for digraphs) of a vertex or of vertices.
74
:meth:`~GenericGraph.average_degree` | Returns the average degree of the graph.
75
:meth:`~GenericGraph.degree_histogram` | Returns a list, whose ith entry is the frequency of degree i.
76
:meth:`~GenericGraph.degree_iterator` | Returns an iterator over the degrees of the (di)graph.
77
:meth:`~GenericGraph.degree_sequence` | Return the degree sequence of this (di)graph.
78
:meth:`~GenericGraph.random_subgraph` | Return a random subgraph that contains each vertex with prob. p.
79
:meth:`~GenericGraph.add_cycle` | Adds a cycle to the graph with the given vertices.
80
:meth:`~GenericGraph.add_path` | Adds a cycle to the graph with the given vertices.
81
:meth:`~GenericGraph.complement` | Returns the complement of the (di)graph.
82
:meth:`~GenericGraph.line_graph` | Returns the line graph of the (di)graph.
83
:meth:`~GenericGraph.to_simple` | Returns a simple version of itself (i.e., undirected and loops and multiple edges are removed).
84
:meth:`~GenericGraph.disjoint_union` | Returns the disjoint union of self and other.
85
:meth:`~GenericGraph.union` | Returns the union of self and other.
86
:meth:`~GenericGraph.relabel` | Relabels the vertices of ``self``
87
:meth:`~GenericGraph.degree_to_cell` | Returns the number of edges from vertex to an edge in cell.
88
:meth:`~GenericGraph.subgraph` | Returns the subgraph containing the given vertices and edges.
89
:meth:`~GenericGraph.is_subgraph` | Tests whether self is a subgraph of other.
90
91
92
93
**Graph products:**
94
95
.. csv-table::
96
:class: contentstable
97
:widths: 30, 70
98
:delim: |
99
100
:meth:`~GenericGraph.cartesian_product` | Returns the Cartesian product of self and other.
101
:meth:`~GenericGraph.tensor_product` | Returns the tensor product, also called the categorical product, of self and other.
102
:meth:`~GenericGraph.lexicographic_product` | Returns the lexicographic product of self and other.
103
:meth:`~GenericGraph.strong_product` | Returns the strong product of self and other.
104
:meth:`~GenericGraph.disjunctive_product` | Returns the disjunctive product of self and other.
105
106
**Paths and cycles:**
107
108
.. csv-table::
109
:class: contentstable
110
:widths: 30, 70
111
:delim: |
112
113
:meth:`~GenericGraph.eulerian_orientation` | Returns a DiGraph which is an Eulerian orientation of the current graph.
114
:meth:`~GenericGraph.eulerian_circuit` | Return a list of edges forming an eulerian circuit if one exists.
115
:meth:`~GenericGraph.cycle_basis` | Returns a list of cycles which form a basis of the cycle space of ``self``.
116
:meth:`~GenericGraph.interior_paths` | Returns an exhaustive list of paths (also lists) through only interior vertices from vertex start to vertex end in the (di)graph.
117
:meth:`~GenericGraph.all_paths` | Returns a list of all paths (also lists) between a pair of vertices in the (di)graph.
118
119
**Linear algebra:**
120
121
.. csv-table::
122
:class: contentstable
123
:widths: 30, 70
124
:delim: |
125
126
:meth:`~GenericGraph.spectrum` | Returns a list of the eigenvalues of the adjacency matrix.
127
:meth:`~GenericGraph.eigenvectors` | Returns the *right* eigenvectors of the adjacency matrix of the graph.
128
:meth:`~GenericGraph.eigenspaces` | Returns the *right* eigenspaces of the adjacency matrix of the graph.
129
130
**Some metrics:**
131
132
.. csv-table::
133
:class: contentstable
134
:widths: 30, 70
135
:delim: |
136
137
:meth:`~GenericGraph.cluster_triangles` | Returns the number of triangles for nbunch of vertices as a dictionary keyed by vertex.
138
:meth:`~GenericGraph.clustering_average` | Returns the average clustering coefficient.
139
:meth:`~GenericGraph.clustering_coeff` | Returns the clustering coefficient for each vertex in nbunch
140
:meth:`~GenericGraph.cluster_transitivity` | Returns the transitivity (fraction of transitive triangles) of the graph.
141
:meth:`~GenericGraph.szeged_index` | Returns the Szeged index of the graph.
142
143
144
**Automorphism group:**
145
146
.. csv-table::
147
:class: contentstable
148
:widths: 30, 70
149
:delim: |
150
151
:meth:`~GenericGraph.coarsest_equitable_refinement` | Returns the coarsest partition which is finer than the input partition, and equitable with respect to self.
152
:meth:`~GenericGraph.automorphism_group` | Returns the largest subgroup of the automorphism group of the (di)graph whose orbit partition is finer than the partition given.
153
:meth:`~GenericGraph.is_vertex_transitive` | Returns whether the automorphism group of self is transitive within the partition provided
154
:meth:`~GenericGraph.is_isomorphic` | Tests for isomorphism between self and other.
155
:meth:`~GenericGraph.canonical_label` | Returns the unique graph on `\{0,1,...,n-1\}` ( ``n = self.order()`` ) which 1) is isomorphic to self 2) is invariant in the isomorphism class.
156
157
**Graph properties:**
158
159
.. csv-table::
160
:class: contentstable
161
:widths: 30, 70
162
:delim: |
163
164
:meth:`~GenericGraph.is_eulerian` | Return true if the graph has a (closed) tour that visits each edge exactly once.
165
:meth:`~GenericGraph.is_tree` | Return True if the graph is a tree.
166
:meth:`~GenericGraph.is_forest` | Return True if the graph is a forest, i.e. a disjoint union of trees.
167
:meth:`~GenericGraph.is_overfull` | Tests whether the current graph is overfull.
168
:meth:`~GenericGraph.is_planar` | Tests whether the graph is planar.
169
:meth:`~GenericGraph.is_circular_planar` | Tests whether the graph is circular planar (outerplanar)
170
:meth:`~GenericGraph.is_regular` | Return ``True`` if this graph is (`k`-)regular.
171
:meth:`~GenericGraph.is_chordal` | Tests whether the given graph is chordal.
172
:meth:`~GenericGraph.is_interval` | Check whether self is an interval graph
173
:meth:`~GenericGraph.is_gallai_tree` | Returns whether the current graph is a Gallai tree.
174
:meth:`~GenericGraph.is_clique` | Tests whether a set of vertices is a clique
175
:meth:`~GenericGraph.is_independent_set` | Tests whether a set of vertices is an independent set
176
:meth:`~GenericGraph.is_transitively_reduced` | Tests whether the digraph is transitively reduced.
177
:meth:`~GenericGraph.is_equitable` | Checks whether the given partition is equitable with respect to self.
178
179
**Traversals:**
180
181
.. csv-table::
182
:class: contentstable
183
:widths: 30, 70
184
:delim: |
185
186
:meth:`~GenericGraph.breadth_first_search` | Returns an iterator over the vertices in a breadth-first ordering.
187
:meth:`~GenericGraph.depth_first_search` | Returns an iterator over the vertices in a depth-first ordering.
188
:meth:`~GenericGraph.lex_BFS` | Performs a Lex BFS on the graph.
189
190
**Distances:**
191
192
.. csv-table::
193
:class: contentstable
194
:widths: 30, 70
195
:delim: |
196
197
:meth:`~GenericGraph.distance` | Returns the (directed) distance from u to v in the (di)graph
198
:meth:`~GenericGraph.distance_all_pairs` | Returns the distances between all pairs of vertices.
199
:meth:`~GenericGraph.distances_distribution` | Returns the distances distribution of the (di)graph in a dictionary.
200
:meth:`~GenericGraph.eccentricity` | Return the eccentricity of vertex (or vertices) v.
201
:meth:`~GenericGraph.radius` | Returns the radius of the (di)graph.
202
:meth:`~GenericGraph.center` | Returns the set of vertices in the center of the graph
203
:meth:`~GenericGraph.diameter` | Returns the largest distance between any two vertices.
204
:meth:`~GenericGraph.distance_graph` | Returns the graph on the same vertex set as the original graph but vertices are adjacent in the returned graph if and only if they are at specified distances in the original graph.
205
:meth:`~GenericGraph.girth` | Computes the girth of the graph.
206
:meth:`~GenericGraph.periphery` | Returns the set of vertices in the periphery
207
:meth:`~GenericGraph.shortest_path` | Returns a list of vertices representing some shortest path from `u` to `v`
208
:meth:`~GenericGraph.shortest_path_length` | Returns the minimal length of paths from u to v
209
:meth:`~GenericGraph.shortest_paths` | Returns a dictionary associating to each vertex v a shortest path from u to v, if it exists.
210
:meth:`~GenericGraph.shortest_path_lengths` | Returns a dictionary of shortest path lengths keyed by targets that are connected by a path from u.
211
:meth:`~GenericGraph.shortest_path_all_pairs` | Computes a shortest path between each pair of vertices.
212
:meth:`~GenericGraph.wiener_index` | Returns the Wiener index of the graph.
213
:meth:`~GenericGraph.average_distance` | Returns the average distance between vertices of the graph.
214
215
216
**Flows, connectivity, trees:**
217
218
.. csv-table::
219
:class: contentstable
220
:widths: 30, 70
221
:delim: |
222
223
:meth:`~GenericGraph.is_connected` | Tests whether the (di)graph is connected.
224
:meth:`~GenericGraph.connected_components` | Returns the list of connected components
225
:meth:`~GenericGraph.connected_components_number` | Returns the number of connected components.
226
:meth:`~GenericGraph.connected_components_subgraphs` | Returns a list of connected components as graph objects.
227
:meth:`~GenericGraph.connected_component_containing_vertex` | Returns a list of the vertices connected to vertex.
228
:meth:`~GenericGraph.blocks_and_cut_vertices` | Computes the blocks and cut vertices of the graph.
229
:meth:`~GenericGraph.edge_cut` | Returns a minimum edge cut between vertices `s` and `t`
230
:meth:`~GenericGraph.vertex_cut` | Returns a minimum vertex cut between non-adjacent vertices `s` and `t`
231
:meth:`~GenericGraph.flow` | Returns a maximum flow in the graph from ``x`` to ``y``
232
:meth:`~GenericGraph.edge_disjoint_paths` | Returns a list of edge-disjoint paths between two vertices
233
:meth:`~GenericGraph.vertex_disjoint_paths` | Returns a list of vertex-disjoint paths between two vertices as given by Menger's theorem.
234
:meth:`~GenericGraph.edge_connectivity` | Returns the edge connectivity of the graph.
235
:meth:`~GenericGraph.vertex_connectivity` | Returns the vertex connectivity of the graph.
236
:meth:`~GenericGraph.transitive_closure` | Computes the transitive closure of a graph and returns it.
237
:meth:`~GenericGraph.transitive_reduction` | Returns a transitive reduction of a graph.
238
:meth:`~GenericGraph.min_spanning_tree` | Returns the edges of a minimum spanning tree.
239
:meth:`~GenericGraph.spanning_trees_count` | Returns the number of spanning trees in a graph.
240
241
**Plot/embedding-related methods:**
242
243
.. csv-table::
244
:class: contentstable
245
:widths: 30, 70
246
:delim: |
247
248
:meth:`~GenericGraph.set_embedding` | Sets a combinatorial embedding dictionary to ``_embedding`` attribute.
249
:meth:`~GenericGraph.get_embedding` | Returns the attribute _embedding if it exists.
250
:meth:`~GenericGraph.check_embedding_validity` | Checks whether an ``_embedding`` attribute is well defined
251
:meth:`~GenericGraph.get_pos` | Returns the position dictionary
252
:meth:`~GenericGraph.check_pos_validity` | Checks whether pos specifies two (resp. 3) coordinates for every vertex (and no more vertices).
253
:meth:`~GenericGraph.set_pos` | Sets the position dictionary.
254
:meth:`~GenericGraph.set_planar_positions` | Compute a planar layout for self using Schnyder's algorithm
255
:meth:`~GenericGraph.layout_planar` | Uses Schnyder's algorithm to compute a planar layout for self.
256
:meth:`~GenericGraph.is_drawn_free_of_edge_crossings` | Tests whether the position dictionary gives a planar embedding.
257
:meth:`~GenericGraph.latex_options` | Returns an instance of :class:`~sage.graphs.graph_latex.GraphLatex` for the graph.
258
:meth:`~GenericGraph.set_latex_options` | Sets multiple options for rendering a graph with LaTeX.
259
:meth:`~GenericGraph.layout` | Returns a layout for the vertices of this graph.
260
:meth:`~GenericGraph.layout_spring` | Computes a spring layout for this graph
261
:meth:`~GenericGraph.layout_ranked` | Computes a ranked layout for this graph
262
:meth:`~GenericGraph.layout_extend_randomly` | Extends randomly a partial layout
263
:meth:`~GenericGraph.layout_circular` | Computes a circular layout for this graph
264
:meth:`~GenericGraph.layout_tree` | Computes an ordered tree layout for this graph, which should be a tree (no non-oriented cycles).
265
:meth:`~GenericGraph.layout_graphviz` | Calls ``graphviz`` to compute a layout of the vertices of this graph.
266
:meth:`~GenericGraph.graphplot` | Returns a GraphPlot object.
267
:meth:`~GenericGraph.plot` | Returns a graphics object representing the (di)graph.
268
:meth:`~GenericGraph.show` | Shows the (di)graph.
269
:meth:`~GenericGraph.plot3d` | Plot a graph in three dimensions.
270
:meth:`~GenericGraph.show3d` | Plots the graph using Tachyon, and shows the resulting plot.
271
:meth:`~GenericGraph.graphviz_string` | Returns a representation in the dot language.
272
:meth:`~GenericGraph.graphviz_to_file_named` | Write a representation in the dot in a file.
273
274
**Algorithmically hard stuff:**
275
276
.. csv-table::
277
:class: contentstable
278
:widths: 30, 70
279
:delim: |
280
281
:meth:`~GenericGraph.steiner_tree` | Returns a tree of minimum weight connecting the given set of vertices.
282
:meth:`~GenericGraph.edge_disjoint_spanning_trees` | Returns the desired number of edge-disjoint spanning trees/arborescences.
283
:meth:`~GenericGraph.multiway_cut` | Returns a minimum edge multiway cut
284
:meth:`~GenericGraph.max_cut` | Returns a maximum edge cut of the graph.
285
:meth:`~GenericGraph.longest_path` | Returns a longest path of ``self``.
286
:meth:`~GenericGraph.traveling_salesman_problem` | Solves the traveling salesman problem (TSP)
287
:meth:`~GenericGraph.is_hamiltonian` | Tests whether the current graph is Hamiltonian.
288
:meth:`~GenericGraph.hamiltonian_cycle` | Returns a Hamiltonian cycle/circuit of the current graph/digraph
289
:meth:`~GenericGraph.multicommodity_flow` | Solves a multicommodity flow problem.
290
:meth:`~GenericGraph.disjoint_routed_paths` | Returns a set of disjoint routed paths.
291
:meth:`~GenericGraph.dominating_set` | Returns a minimum dominating set of the graph
292
:meth:`~GenericGraph.subgraph_search` | Returns a copy of ``G`` in ``self``.
293
:meth:`~GenericGraph.subgraph_search_count` | Returns the number of labelled occurences of ``G`` in ``self``.
294
:meth:`~GenericGraph.subgraph_search_iterator` | Returns an iterator over the labelled copies of ``G`` in ``self``.
295
:meth:`~GenericGraph.characteristic_polynomial` | Returns the characteristic polynomial of the adjacency matrix of the (di)graph.
296
:meth:`~GenericGraph.genus` | Returns the minimal genus of the graph.
297
:meth:`~GenericGraph.trace_faces` | A helper function for finding the genus of a graph.
298
299
**Graph stuff that should not be in this file:**
300
301
.. csv-table::
302
:class: contentstable
303
:widths: 30, 70
304
:delim: |
305
306
:meth:`~GenericGraph.minimum_outdegree_orientation` | Returns an orientation of ``self`` with the smallest possible maximum outdegree
307
:meth:`~GenericGraph.matching` | Returns a maximum weighted matching of the graph
308
:meth:`~GenericGraph.maximum_average_degree` | Returns the Maximum Average Degree (MAD) of the current graph.
309
:meth:`~GenericGraph.cores` | Returns the core number for each vertex in an ordered list.
310
311
312
Methods
313
-------
314
"""
315
316
from sage.misc.decorators import options
317
from sage.misc.prandom import random
318
from sage.rings.integer_ring import ZZ
319
from sage.rings.integer import Integer
320
from sage.rings.rational import Rational
321
from sage.groups.perm_gps.partn_ref.refinement_graphs import isomorphic, search_tree
322
from generic_graph_pyx import GenericGraph_pyx, spring_layout_fast
323
from sage.graphs.dot2tex_utils import assert_have_dot2tex
324
325
class GenericGraph(GenericGraph_pyx):
326
"""
327
Base class for graphs and digraphs.
328
"""
329
330
# Nice defaults for plotting arrays of graphs (see sage.misc.functional.show)
331
graphics_array_defaults = {'layout': 'circular', 'vertex_size':50, 'vertex_labels':False, 'graph_border':True}
332
333
def __init__(self):
334
r"""
335
Every graph carries a dictionary of options, which is set
336
here to ``None``. Some options are added to the global
337
:data:`sage.misc.latex.latex` instance which will insure
338
that if `\mbox{\rm\LaTeX}` is used to render the graph,
339
then the right packages are loaded and jsMath reacts
340
properly.
341
342
Most other initialization is done in the directed
343
and undirected subclasses.
344
345
TESTS::
346
347
sage: g = Graph()
348
sage: g
349
Graph on 0 vertices
350
"""
351
self._latex_opts = None
352
# FIXME: this has nothing to do here. Ideally, we would want
353
# this to be run just once, just before the creation of a full
354
# latex file including a graph.
355
# There should be a Sage-wide strategy for handling this.
356
from sage.graphs.graph_latex import setup_latex_preamble
357
setup_latex_preamble()
358
359
def __add__(self, other_graph):
360
"""
361
Returns the disjoint union of self and other.
362
363
If there are common vertices to both, they will be renamed.
364
365
EXAMPLES::
366
367
sage: G = graphs.CycleGraph(3)
368
sage: H = graphs.CycleGraph(4)
369
sage: J = G + H; J
370
Cycle graph disjoint_union Cycle graph: Graph on 7 vertices
371
sage: J.vertices()
372
[0, 1, 2, 3, 4, 5, 6]
373
"""
374
if isinstance(other_graph, GenericGraph):
375
return self.disjoint_union(other_graph, verbose_relabel=False)
376
377
def __eq__(self, other):
378
"""
379
Comparison of self and other. For equality, must be in the same
380
class, have the same settings for loops and multiedges, output the
381
same vertex list (in order) and the same adjacency matrix.
382
383
Note that this is _not_ an isomorphism test.
384
385
EXAMPLES::
386
387
sage: G = graphs.EmptyGraph()
388
sage: H = Graph()
389
sage: G == H
390
True
391
sage: G.to_directed() == H.to_directed()
392
True
393
sage: G = graphs.RandomGNP(8,.9999)
394
sage: H = graphs.CompleteGraph(8)
395
sage: G == H # most often true
396
True
397
sage: G = Graph( {0:[1,2,3,4,5,6,7]} )
398
sage: H = Graph( {1:[0], 2:[0], 3:[0], 4:[0], 5:[0], 6:[0], 7:[0]} )
399
sage: G == H
400
True
401
sage: G.allow_loops(True)
402
sage: G == H
403
False
404
sage: G = graphs.RandomGNP(9,.3).to_directed()
405
sage: H = graphs.RandomGNP(9,.3).to_directed()
406
sage: G == H # most often false
407
False
408
sage: G = Graph(multiedges=True, sparse=True)
409
sage: G.add_edge(0,1)
410
sage: H = copy(G)
411
sage: H.add_edge(0,1)
412
sage: G == H
413
False
414
415
Note that graphs must be considered weighted, or Sage will not pay
416
attention to edge label data in equality testing::
417
418
sage: foo = Graph(sparse=True)
419
sage: foo.add_edges([(0, 1, 1), (0, 2, 2)])
420
sage: bar = Graph(sparse=True)
421
sage: bar.add_edges([(0, 1, 2), (0, 2, 1)])
422
sage: foo == bar
423
True
424
sage: foo.weighted(True)
425
sage: foo == bar
426
False
427
sage: bar.weighted(True)
428
sage: foo == bar
429
False
430
431
"""
432
# inputs must be (di)graphs:
433
if not isinstance(other, GenericGraph):
434
raise TypeError("Cannot compare graph to non-graph (%s)."%str(other))
435
from sage.graphs.all import Graph
436
g1_is_graph = isinstance(self, Graph) # otherwise, DiGraph
437
g2_is_graph = isinstance(other, Graph) # otherwise, DiGraph
438
if g1_is_graph != g2_is_graph:
439
return False
440
if self.allows_multiple_edges() != other.allows_multiple_edges():
441
return False
442
if self.allows_loops() != other.allows_loops():
443
return False
444
if self.vertices() != other.vertices():
445
return False
446
if self._weighted != other._weighted:
447
return False
448
verts = self.vertices()
449
# Finally, we are prepared to check edges:
450
if not self.allows_multiple_edges():
451
for i in verts:
452
for j in verts:
453
if self.has_edge(i,j) != other.has_edge(i,j):
454
return False
455
if self.has_edge(i,j) and self._weighted and other._weighted:
456
if self.edge_label(i,j) != other.edge_label(i,j):
457
return False
458
return True
459
else:
460
for i in verts:
461
for j in verts:
462
if self.has_edge(i, j):
463
edges1 = self.edge_label(i, j)
464
else:
465
edges1 = []
466
if other.has_edge(i, j):
467
edges2 = other.edge_label(i, j)
468
else:
469
edges2 = []
470
if len(edges1) != len(edges2):
471
return False
472
if sorted(edges1) != sorted(edges2) and self._weighted and other._weighted:
473
return False
474
return True
475
476
def __hash__(self):
477
"""
478
Since graphs are mutable, they should not be hashable, so we return
479
a type error.
480
481
EXAMPLES::
482
483
sage: hash(Graph())
484
Traceback (most recent call last):
485
...
486
TypeError: graphs are mutable, and thus not hashable
487
"""
488
if getattr(self, "_immutable", False):
489
return hash((tuple(self.vertices()), tuple(self.edges())))
490
raise TypeError, "graphs are mutable, and thus not hashable"
491
492
def __mul__(self, n):
493
"""
494
Returns the sum of a graph with itself n times.
495
496
EXAMPLES::
497
498
sage: G = graphs.CycleGraph(3)
499
sage: H = G*3; H
500
Cycle graph disjoint_union Cycle graph disjoint_union Cycle graph: Graph on 9 vertices
501
sage: H.vertices()
502
[0, 1, 2, 3, 4, 5, 6, 7, 8]
503
sage: H = G*1; H
504
Cycle graph: Graph on 3 vertices
505
"""
506
if isinstance(n, (int, long, Integer)):
507
if n < 1:
508
raise TypeError('Multiplication of a graph and a nonpositive integer is not defined.')
509
if n == 1:
510
from copy import copy
511
return copy(self)
512
return sum([self]*(n-1), self)
513
else:
514
raise TypeError('Multiplication of a graph and something other than an integer is not defined.')
515
516
def __ne__(self, other):
517
"""
518
Tests for inequality, complement of __eq__.
519
520
EXAMPLES::
521
522
sage: g = Graph()
523
sage: g2 = copy(g)
524
sage: g == g
525
True
526
sage: g != g
527
False
528
sage: g2 == g
529
True
530
sage: g2 != g
531
False
532
sage: g is g
533
True
534
sage: g2 is g
535
False
536
"""
537
return (not (self == other))
538
539
def __rmul__(self, n):
540
"""
541
Returns the sum of a graph with itself n times.
542
543
EXAMPLES::
544
545
sage: G = graphs.CycleGraph(3)
546
sage: H = int(3)*G; H
547
Cycle graph disjoint_union Cycle graph disjoint_union Cycle graph: Graph on 9 vertices
548
sage: H.vertices()
549
[0, 1, 2, 3, 4, 5, 6, 7, 8]
550
"""
551
return self*n
552
553
def __str__(self):
554
"""
555
str(G) returns the name of the graph, unless the name is the empty
556
string, in which case it returns the default representation.
557
558
EXAMPLES::
559
560
sage: G = graphs.PetersenGraph()
561
sage: str(G)
562
'Petersen graph'
563
"""
564
if self.name():
565
return self.name()
566
else:
567
return repr(self)
568
569
def _bit_vector(self):
570
"""
571
Returns a string representing the edges of the (simple) graph for
572
graph6 and dig6 strings.
573
574
EXAMPLES::
575
576
sage: G = graphs.PetersenGraph()
577
sage: G._bit_vector()
578
'101001100110000010000001001000010110000010110'
579
sage: len([a for a in G._bit_vector() if a == '1'])
580
15
581
sage: G.num_edges()
582
15
583
"""
584
vertices = self.vertices()
585
n = len(vertices)
586
if self._directed:
587
total_length = n*n
588
bit = lambda x,y : x*n + y
589
else:
590
total_length = int(n*(n - 1))/int(2)
591
n_ch_2 = lambda b : int(b*(b-1))/int(2)
592
bit = lambda x,y : n_ch_2(max([x,y])) + min([x,y])
593
bit_vector = set()
594
for u,v,_ in self.edge_iterator():
595
bit_vector.add(bit(vertices.index(u), vertices.index(v)))
596
bit_vector = sorted(bit_vector)
597
s = []
598
j = 0
599
for i in bit_vector:
600
s.append( '0'*(i - j) + '1' )
601
j = i + 1
602
s = "".join(s)
603
s += '0'*(total_length-len(s))
604
return s
605
606
def _latex_(self):
607
r""" Returns a string to render the graph using
608
`\mbox{\rm{\LaTeX}}`.
609
610
To adjust the string, use the
611
:meth:`set_latex_options` method to set options,
612
or call the :meth:`latex_options` method to
613
get a :class:`~sage.graphs.graph_latex.GraphLatex`
614
object that may be used to also customize the
615
output produced here. Possible options are documented at
616
:meth:`sage.graphs.graph_latex.GraphLatex.set_option`.
617
618
EXAMPLES::
619
620
sage: from sage.graphs.graph_latex import check_tkz_graph
621
sage: check_tkz_graph() # random - depends on TeX installation
622
sage: g = graphs.CompleteGraph(2)
623
sage: print g._latex_()
624
\begin{tikzpicture}
625
%
626
\useasboundingbox (0,0) rectangle (5.0cm,5.0cm);
627
%
628
\definecolor{cv0}{rgb}{0.0,0.0,0.0}
629
\definecolor{cfv0}{rgb}{1.0,1.0,1.0}
630
\definecolor{clv0}{rgb}{0.0,0.0,0.0}
631
\definecolor{cv1}{rgb}{0.0,0.0,0.0}
632
\definecolor{cfv1}{rgb}{1.0,1.0,1.0}
633
\definecolor{clv1}{rgb}{0.0,0.0,0.0}
634
\definecolor{cv0v1}{rgb}{0.0,0.0,0.0}
635
%
636
\Vertex[style={minimum size=1.0cm,draw=cv0,fill=cfv0,text=clv0,shape=circle},LabelOut=false,L=\hbox{$0$},x=5.0cm,y=5.0cm]{v0}
637
\Vertex[style={minimum size=1.0cm,draw=cv1,fill=cfv1,text=clv1,shape=circle},LabelOut=false,L=\hbox{$1$},x=0.0cm,y=0.0cm]{v1}
638
%
639
\Edge[lw=0.1cm,style={color=cv0v1,},](v0)(v1)
640
%
641
\end{tikzpicture}
642
"""
643
return self.latex_options().latex()
644
645
def _matrix_(self, R=None):
646
"""
647
Returns the adjacency matrix of the graph over the specified ring.
648
649
EXAMPLES::
650
651
sage: G = graphs.CompleteBipartiteGraph(2,3)
652
sage: m = matrix(G); m.parent()
653
Full MatrixSpace of 5 by 5 dense matrices over Integer Ring
654
sage: m
655
[0 0 1 1 1]
656
[0 0 1 1 1]
657
[1 1 0 0 0]
658
[1 1 0 0 0]
659
[1 1 0 0 0]
660
sage: G._matrix_()
661
[0 0 1 1 1]
662
[0 0 1 1 1]
663
[1 1 0 0 0]
664
[1 1 0 0 0]
665
[1 1 0 0 0]
666
sage: factor(m.charpoly())
667
x^3 * (x^2 - 6)
668
"""
669
if R is None:
670
return self.am()
671
else:
672
return self.am().change_ring(R)
673
674
def _repr_(self):
675
"""
676
Return a string representation of self.
677
678
EXAMPLES::
679
680
sage: G = graphs.PetersenGraph()
681
sage: G._repr_()
682
'Petersen graph: Graph on 10 vertices'
683
"""
684
name = ""
685
if self.allows_loops():
686
name += "looped "
687
if self.allows_multiple_edges():
688
name += "multi-"
689
if self._directed:
690
name += "di"
691
name += "graph on %d vert"%self.order()
692
if self.order() == 1:
693
name += "ex"
694
else:
695
name += "ices"
696
name = name.capitalize()
697
if self.name() != '':
698
name = self.name() + ": " + name
699
return name
700
701
### Formats
702
703
def __copy__(self, implementation='c_graph', sparse=None):
704
"""
705
Creates a copy of the graph.
706
707
INPUT:
708
709
- ``implementation`` - string (default: 'networkx') the
710
implementation goes here. Current options are only
711
'networkx' or 'c_graph'.
712
713
- ``sparse`` - boolean (default: None) whether the
714
graph given is sparse or not.
715
716
OUTPUT:
717
718
A Graph object.
719
720
.. warning::
721
722
Please use this method only if you need to copy but change the
723
underlying implementation. Otherwise simply do ``copy(g)``
724
instead of doing ``g.copy()``.
725
726
EXAMPLES::
727
728
sage: g=Graph({0:[0,1,1,2]},loops=True,multiedges=True,sparse=True)
729
sage: g==copy(g)
730
True
731
sage: g=DiGraph({0:[0,1,1,2],1:[0,1]},loops=True,multiedges=True,sparse=True)
732
sage: g==copy(g)
733
True
734
735
Note that vertex associations are also kept::
736
737
sage: d = {0 : graphs.DodecahedralGraph(), 1 : graphs.FlowerSnark(), 2 : graphs.MoebiusKantorGraph(), 3 : graphs.PetersenGraph() }
738
sage: T = graphs.TetrahedralGraph()
739
sage: T.set_vertices(d)
740
sage: T2 = copy(T)
741
sage: T2.get_vertex(0)
742
Dodecahedron: Graph on 20 vertices
743
744
Notice that the copy is at least as deep as the objects::
745
746
sage: T2.get_vertex(0) is T.get_vertex(0)
747
False
748
749
Examples of the keywords in use::
750
751
sage: G = graphs.CompleteGraph(19)
752
sage: H = G.copy(implementation='c_graph')
753
sage: H == G; H is G
754
True
755
False
756
sage: G1 = G.copy(sparse=True)
757
sage: G1==G
758
True
759
sage: G1 is G
760
False
761
sage: G2 = copy(G)
762
sage: G2 is G
763
False
764
765
TESTS: We make copies of the _pos and _boundary attributes.
766
767
::
768
769
sage: g = graphs.PathGraph(3)
770
sage: h = copy(g)
771
sage: h._pos is g._pos
772
False
773
sage: h._boundary is g._boundary
774
False
775
"""
776
if sparse is None:
777
from sage.graphs.base.dense_graph import DenseGraphBackend
778
sparse = (not isinstance(self._backend, DenseGraphBackend))
779
from copy import copy
780
G = self.__class__(self, name=self.name(), pos=copy(self._pos), boundary=copy(self._boundary), implementation=implementation, sparse=sparse)
781
782
attributes_to_copy = ('_assoc', '_embedding')
783
for attr in attributes_to_copy:
784
if hasattr(self, attr):
785
copy_attr = {}
786
old_attr = getattr(self, attr)
787
if isinstance(old_attr, dict):
788
for v,value in old_attr.iteritems():
789
try:
790
copy_attr[v] = value.copy()
791
except AttributeError:
792
from copy import copy
793
copy_attr[v] = copy(value)
794
setattr(G, attr, copy_attr)
795
else:
796
setattr(G, attr, copy(old_attr))
797
798
G._weighted = self._weighted
799
return G
800
801
copy = __copy__
802
803
def networkx_graph(self, copy=True):
804
"""
805
Creates a new NetworkX graph from the Sage graph.
806
807
INPUT:
808
809
810
- ``copy`` - if False, and the underlying
811
implementation is a NetworkX graph, then the actual object itself
812
is returned.
813
814
815
EXAMPLES::
816
817
sage: G = graphs.TetrahedralGraph()
818
sage: N = G.networkx_graph()
819
sage: type(N)
820
<class 'networkx.classes.graph.Graph'>
821
822
::
823
824
sage: G = graphs.TetrahedralGraph()
825
sage: G = Graph(G, implementation='networkx')
826
sage: N = G.networkx_graph()
827
sage: G._backend._nxg is N
828
False
829
830
::
831
832
sage: G = Graph(graphs.TetrahedralGraph(), implementation='networkx')
833
sage: N = G.networkx_graph(copy=False)
834
sage: G._backend._nxg is N
835
True
836
"""
837
try:
838
if copy:
839
from copy import copy
840
return self._backend._nxg.copy()
841
else:
842
return self._backend._nxg
843
except:
844
import networkx
845
if self._directed and self.allows_multiple_edges():
846
class_type = networkx.MultiDiGraph
847
elif self._directed:
848
class_type = networkx.DiGraph
849
elif self.allows_multiple_edges():
850
class_type = networkx.MultiGraph
851
else:
852
class_type = networkx.Graph
853
N = class_type(selfloops=self.allows_loops(), multiedges=self.allows_multiple_edges(),
854
name=self.name())
855
N.add_nodes_from(self.vertices())
856
for u,v,l in self.edges():
857
if l is None:
858
N.add_edge(u,v)
859
else:
860
from networkx import NetworkXError
861
try:
862
N.add_edge(u,v,l)
863
except (TypeError, ValueError, NetworkXError):
864
N.add_edge(u,v,weight=l)
865
return N
866
867
def adjacency_matrix(self, sparse=None, boundary_first=False):
868
"""
869
Returns the adjacency matrix of the (di)graph.
870
871
Each vertex is represented by its position in the list returned by the
872
vertices() function.
873
874
The matrix returned is over the integers. If a different ring is
875
desired, use either the change_ring function or the matrix
876
function.
877
878
INPUT:
879
880
- ``sparse`` - whether to represent with a sparse
881
matrix
882
883
- ``boundary_first`` - whether to represent the
884
boundary vertices in the upper left block
885
886
EXAMPLES::
887
888
sage: G = graphs.CubeGraph(4)
889
sage: G.adjacency_matrix()
890
[0 1 1 0 1 0 0 0 1 0 0 0 0 0 0 0]
891
[1 0 0 1 0 1 0 0 0 1 0 0 0 0 0 0]
892
[1 0 0 1 0 0 1 0 0 0 1 0 0 0 0 0]
893
[0 1 1 0 0 0 0 1 0 0 0 1 0 0 0 0]
894
[1 0 0 0 0 1 1 0 0 0 0 0 1 0 0 0]
895
[0 1 0 0 1 0 0 1 0 0 0 0 0 1 0 0]
896
[0 0 1 0 1 0 0 1 0 0 0 0 0 0 1 0]
897
[0 0 0 1 0 1 1 0 0 0 0 0 0 0 0 1]
898
[1 0 0 0 0 0 0 0 0 1 1 0 1 0 0 0]
899
[0 1 0 0 0 0 0 0 1 0 0 1 0 1 0 0]
900
[0 0 1 0 0 0 0 0 1 0 0 1 0 0 1 0]
901
[0 0 0 1 0 0 0 0 0 1 1 0 0 0 0 1]
902
[0 0 0 0 1 0 0 0 1 0 0 0 0 1 1 0]
903
[0 0 0 0 0 1 0 0 0 1 0 0 1 0 0 1]
904
[0 0 0 0 0 0 1 0 0 0 1 0 1 0 0 1]
905
[0 0 0 0 0 0 0 1 0 0 0 1 0 1 1 0]
906
907
::
908
909
sage: matrix(GF(2),G) # matrix over GF(2)
910
[0 1 1 0 1 0 0 0 1 0 0 0 0 0 0 0]
911
[1 0 0 1 0 1 0 0 0 1 0 0 0 0 0 0]
912
[1 0 0 1 0 0 1 0 0 0 1 0 0 0 0 0]
913
[0 1 1 0 0 0 0 1 0 0 0 1 0 0 0 0]
914
[1 0 0 0 0 1 1 0 0 0 0 0 1 0 0 0]
915
[0 1 0 0 1 0 0 1 0 0 0 0 0 1 0 0]
916
[0 0 1 0 1 0 0 1 0 0 0 0 0 0 1 0]
917
[0 0 0 1 0 1 1 0 0 0 0 0 0 0 0 1]
918
[1 0 0 0 0 0 0 0 0 1 1 0 1 0 0 0]
919
[0 1 0 0 0 0 0 0 1 0 0 1 0 1 0 0]
920
[0 0 1 0 0 0 0 0 1 0 0 1 0 0 1 0]
921
[0 0 0 1 0 0 0 0 0 1 1 0 0 0 0 1]
922
[0 0 0 0 1 0 0 0 1 0 0 0 0 1 1 0]
923
[0 0 0 0 0 1 0 0 0 1 0 0 1 0 0 1]
924
[0 0 0 0 0 0 1 0 0 0 1 0 1 0 0 1]
925
[0 0 0 0 0 0 0 1 0 0 0 1 0 1 1 0]
926
927
::
928
929
sage: D = DiGraph( { 0: [1,2,3], 1: [0,2], 2: [3], 3: [4], 4: [0,5], 5: [1] } )
930
sage: D.adjacency_matrix()
931
[0 1 1 1 0 0]
932
[1 0 1 0 0 0]
933
[0 0 0 1 0 0]
934
[0 0 0 0 1 0]
935
[1 0 0 0 0 1]
936
[0 1 0 0 0 0]
937
938
TESTS::
939
940
sage: graphs.CubeGraph(8).adjacency_matrix().parent()
941
Full MatrixSpace of 256 by 256 dense matrices over Integer Ring
942
sage: graphs.CubeGraph(9).adjacency_matrix().parent()
943
Full MatrixSpace of 512 by 512 sparse matrices over Integer Ring
944
"""
945
n = self.order()
946
if sparse is None:
947
if n <= 256 or self.density() > 0.05:
948
sparse=False
949
else:
950
sparse=True
951
952
verts = self.vertices(boundary_first=boundary_first)
953
new_indices = dict((v,i) for i,v in enumerate(verts))
954
D = {}
955
directed = self._directed
956
multiple_edges = self.allows_multiple_edges()
957
for i,j,l in self.edge_iterator():
958
i = new_indices[i]
959
j = new_indices[j]
960
if multiple_edges and (i,j) in D:
961
D[(i,j)] += 1
962
if not directed and i != j:
963
D[(j,i)] += 1
964
else:
965
D[(i,j)] = 1
966
if not directed and i != j:
967
D[(j,i)] = 1
968
from sage.rings.integer_ring import IntegerRing
969
from sage.matrix.constructor import matrix
970
M = matrix(IntegerRing(), n, n, D, sparse=sparse)
971
return M
972
973
am = adjacency_matrix # shorter call makes life easier
974
975
def incidence_matrix(self, sparse=True):
976
"""
977
Returns the incidence matrix of the (di)graph.
978
979
Each row is a vertex, and each column is an edge. Note that in the case
980
of graphs, there is a choice of orientation for each edge.
981
982
EXAMPLES::
983
984
sage: G = graphs.CubeGraph(3)
985
sage: G.incidence_matrix()
986
[-1 -1 -1 0 0 0 0 0 0 0 0 0]
987
[ 0 0 1 -1 -1 0 0 0 0 0 0 0]
988
[ 0 1 0 0 0 -1 -1 0 0 0 0 0]
989
[ 0 0 0 0 1 0 1 -1 0 0 0 0]
990
[ 1 0 0 0 0 0 0 0 -1 -1 0 0]
991
[ 0 0 0 1 0 0 0 0 0 1 -1 0]
992
[ 0 0 0 0 0 1 0 0 1 0 0 -1]
993
[ 0 0 0 0 0 0 0 1 0 0 1 1]
994
995
::
996
997
sage: D = DiGraph( { 0: [1,2,3], 1: [0,2], 2: [3], 3: [4], 4: [0,5], 5: [1] } )
998
sage: D.incidence_matrix()
999
[-1 -1 -1 0 0 0 0 0 1 1]
1000
[ 0 0 1 -1 0 0 0 1 -1 0]
1001
[ 0 1 0 1 -1 0 0 0 0 0]
1002
[ 1 0 0 0 1 -1 0 0 0 0]
1003
[ 0 0 0 0 0 1 -1 0 0 -1]
1004
[ 0 0 0 0 0 0 1 -1 0 0]
1005
"""
1006
from sage.matrix.constructor import matrix
1007
from copy import copy
1008
n = self.order()
1009
verts = self.vertices()
1010
d = [0]*n
1011
cols = []
1012
if self._directed:
1013
for i, j, l in self.edge_iterator():
1014
col = copy(d)
1015
i = verts.index(i)
1016
j = verts.index(j)
1017
col[i] = -1
1018
col[j] = 1
1019
cols.append(col)
1020
else:
1021
for i, j, l in self.edge_iterator():
1022
col = copy(d)
1023
i,j = (i,j) if i <= j else (j,i)
1024
i = verts.index(i)
1025
j = verts.index(j)
1026
col[i] = -1
1027
col[j] = 1
1028
cols.append(col)
1029
cols.sort()
1030
return matrix(cols, sparse=sparse).transpose()
1031
1032
def weighted_adjacency_matrix(self, sparse=True, boundary_first=False):
1033
"""
1034
Returns the weighted adjacency matrix of the graph.
1035
1036
Each vertex is represented by its position in the list returned by the
1037
vertices() function.
1038
1039
EXAMPLES::
1040
1041
sage: G = Graph(sparse=True, weighted=True)
1042
sage: G.add_edges([(0,1,1),(1,2,2),(0,2,3),(0,3,4)])
1043
sage: M = G.weighted_adjacency_matrix(); M
1044
[0 1 3 4]
1045
[1 0 2 0]
1046
[3 2 0 0]
1047
[4 0 0 0]
1048
sage: H = Graph(data=M, format='weighted_adjacency_matrix', sparse=True)
1049
sage: H == G
1050
True
1051
1052
The following doctest verifies that \#4888 is fixed::
1053
1054
sage: G = DiGraph({0:{}, 1:{0:1}, 2:{0:1}}, weighted = True,sparse=True)
1055
sage: G.weighted_adjacency_matrix()
1056
[0 0 0]
1057
[1 0 0]
1058
[1 0 0]
1059
1060
"""
1061
if self.has_multiple_edges():
1062
raise NotImplementedError, "Don't know how to represent weights for a multigraph."
1063
1064
verts = self.vertices(boundary_first=boundary_first)
1065
new_indices = dict((v,i) for i,v in enumerate(verts))
1066
1067
D = {}
1068
if self._directed:
1069
for i,j,l in self.edge_iterator():
1070
i = new_indices[i]
1071
j = new_indices[j]
1072
D[(i,j)] = l
1073
else:
1074
for i,j,l in self.edge_iterator():
1075
i = new_indices[i]
1076
j = new_indices[j]
1077
D[(i,j)] = l
1078
D[(j,i)] = l
1079
from sage.matrix.constructor import matrix
1080
M = matrix(self.num_verts(), D, sparse=sparse)
1081
return M
1082
1083
def kirchhoff_matrix(self, weighted=None, indegree=True, normalized=False, **kwds):
1084
"""
1085
Returns the Kirchhoff matrix (a.k.a. the Laplacian) of the graph.
1086
1087
The Kirchhoff matrix is defined to be `D - M`, where `D` is
1088
the diagonal degree matrix (each diagonal entry is the degree
1089
of the corresponding vertex), and `M` is the adjacency matrix.
1090
If ``normalized`` is ``True``, then the returned matrix is
1091
`D^{-1/2}(D-M)D^{-1/2}`.
1092
1093
( In the special case of DiGraphs, `D` is defined as the diagonal
1094
in-degree matrix or diagonal out-degree matrix according to the
1095
value of ``indegree``)
1096
1097
INPUT:
1098
1099
- ``weighted`` -- Binary variable :
1100
- If ``True``, the weighted adjacency matrix is used for `M`,
1101
and the diagonal matrix `D` takes into account the weight of edges
1102
(replace in the definition "degree" by "sum of the incident edges" ).
1103
- Else, each edge is assumed to have weight 1.
1104
1105
Default is to take weights into consideration if and only if the graph is
1106
weighted.
1107
1108
- ``indegree`` -- Binary variable :
1109
- If ``True``, each diagonal entry of `D` is equal to the
1110
in-degree of the corresponding vertex.
1111
- Else, each diagonal entry of `D` is equal to the
1112
out-degree of the corresponding vertex.
1113
1114
By default, ``indegree`` is set to ``True``
1115
1116
( This variable only matters when the graph is a digraph )
1117
1118
- ``normalized`` -- Binary variable :
1119
1120
- If ``True``, the returned matrix is
1121
`D^{-1/2}(D-M)D^{-1/2}`, a normalized version of the
1122
Laplacian matrix.
1123
(More accurately, the normalizing matrix used is equal to `D^{-1/2}`
1124
only for non-isolated vertices. If vertex `i` is isolated, then
1125
diagonal entry `i` in the matrix is 1, rather than a division by
1126
zero.)
1127
- Else, the matrix `D-M` is returned
1128
1129
Note that any additional keywords will be passed on to either
1130
the ``adjacency_matrix`` or ``weighted_adjacency_matrix`` method.
1131
1132
AUTHORS:
1133
1134
- Tom Boothby
1135
- Jason Grout
1136
1137
EXAMPLES::
1138
1139
sage: G = Graph(sparse=True)
1140
sage: G.add_edges([(0,1,1),(1,2,2),(0,2,3),(0,3,4)])
1141
sage: M = G.kirchhoff_matrix(weighted=True); M
1142
[ 8 -1 -3 -4]
1143
[-1 3 -2 0]
1144
[-3 -2 5 0]
1145
[-4 0 0 4]
1146
sage: M = G.kirchhoff_matrix(); M
1147
[ 3 -1 -1 -1]
1148
[-1 2 -1 0]
1149
[-1 -1 2 0]
1150
[-1 0 0 1]
1151
sage: G.set_boundary([2,3])
1152
sage: M = G.kirchhoff_matrix(weighted=True, boundary_first=True); M
1153
[ 5 0 -3 -2]
1154
[ 0 4 -4 0]
1155
[-3 -4 8 -1]
1156
[-2 0 -1 3]
1157
sage: M = G.kirchhoff_matrix(boundary_first=True); M
1158
[ 2 0 -1 -1]
1159
[ 0 1 -1 0]
1160
[-1 -1 3 -1]
1161
[-1 0 -1 2]
1162
sage: M = G.laplacian_matrix(boundary_first=True); M
1163
[ 2 0 -1 -1]
1164
[ 0 1 -1 0]
1165
[-1 -1 3 -1]
1166
[-1 0 -1 2]
1167
sage: M = G.laplacian_matrix(boundary_first=True, sparse=False); M
1168
[ 2 0 -1 -1]
1169
[ 0 1 -1 0]
1170
[-1 -1 3 -1]
1171
[-1 0 -1 2]
1172
sage: M = G.laplacian_matrix(normalized=True); M
1173
[ 1 -1/6*sqrt(2)*sqrt(3) -1/6*sqrt(2)*sqrt(3) -1/3*sqrt(3)]
1174
[-1/6*sqrt(2)*sqrt(3) 1 -1/2 0]
1175
[-1/6*sqrt(2)*sqrt(3) -1/2 1 0]
1176
[ -1/3*sqrt(3) 0 0 1]
1177
sage: Graph({0:[],1:[2]}).laplacian_matrix(normalized=True)
1178
[ 0 0 0]
1179
[ 0 1 -1]
1180
[ 0 -1 1]
1181
1182
A weighted directed graph with loops, changing the variable ``indegree`` ::
1183
1184
sage: G = DiGraph({1:{1:2,2:3}, 2:{1:4}}, weighted=True,sparse=True)
1185
sage: G.laplacian_matrix()
1186
[ 4 -3]
1187
[-4 3]
1188
1189
::
1190
1191
sage: G = DiGraph({1:{1:2,2:3}, 2:{1:4}}, weighted=True,sparse=True)
1192
sage: G.laplacian_matrix(indegree=False)
1193
[ 3 -3]
1194
[-4 4]
1195
"""
1196
from sage.matrix.constructor import matrix, diagonal_matrix
1197
from sage.rings.integer_ring import IntegerRing
1198
from sage.functions.all import sqrt
1199
1200
if weighted is None:
1201
weighted = self._weighted
1202
1203
if weighted:
1204
M = self.weighted_adjacency_matrix(**kwds)
1205
else:
1206
M = self.adjacency_matrix(**kwds)
1207
1208
D = M.parent(0)
1209
1210
if M.is_sparse():
1211
row_sums = {}
1212
if indegree:
1213
for (i,j), entry in M.dict().iteritems():
1214
row_sums[j] = row_sums.get(j, 0) + entry
1215
else:
1216
for (i,j), entry in M.dict().iteritems():
1217
row_sums[i] = row_sums.get(i, 0) + entry
1218
1219
1220
for i in range(M.nrows()):
1221
D[i,i] += row_sums.get(i, 0)
1222
1223
else:
1224
if indegree:
1225
col_sums=[sum(v) for v in M.columns()]
1226
for i in range(M.nrows()):
1227
D[i,i] += col_sums[i]
1228
else:
1229
row_sums=[sum(v) for v in M.rows()]
1230
for i in range(M.nrows()):
1231
D[i,i] += row_sums[i]
1232
1233
if normalized:
1234
Dsqrt = diagonal_matrix([1/sqrt(D[i,i]) if D[i,i]>0 else 1 \
1235
for i in range(D.nrows())])
1236
return Dsqrt*(D-M)*Dsqrt
1237
else:
1238
return D-M
1239
1240
laplacian_matrix = kirchhoff_matrix
1241
1242
### Attributes
1243
1244
def get_boundary(self):
1245
"""
1246
Returns the boundary of the (di)graph.
1247
1248
EXAMPLES::
1249
1250
sage: G = graphs.PetersenGraph()
1251
sage: G.set_boundary([0,1,2,3,4])
1252
sage: G.get_boundary()
1253
[0, 1, 2, 3, 4]
1254
"""
1255
return self._boundary
1256
1257
def set_boundary(self, boundary):
1258
"""
1259
Sets the boundary of the (di)graph.
1260
1261
EXAMPLES::
1262
1263
sage: G = graphs.PetersenGraph()
1264
sage: G.set_boundary([0,1,2,3,4])
1265
sage: G.get_boundary()
1266
[0, 1, 2, 3, 4]
1267
sage: G.set_boundary((1..4))
1268
sage: G.get_boundary()
1269
[1, 2, 3, 4]
1270
"""
1271
if isinstance(boundary,list):
1272
self._boundary = boundary
1273
else:
1274
self._boundary = list(boundary)
1275
1276
def set_embedding(self, embedding):
1277
"""
1278
Sets a combinatorial embedding dictionary to ``_embedding`` attribute.
1279
1280
Dictionary is organized with vertex labels as keys and a list of
1281
each vertex's neighbors in clockwise order.
1282
1283
Dictionary is error-checked for validity.
1284
1285
INPUT:
1286
1287
- ``embedding`` - a dictionary
1288
1289
EXAMPLES::
1290
1291
sage: G = graphs.PetersenGraph()
1292
sage: G.set_embedding({0: [1, 5, 4], 1: [0, 2, 6], 2: [1, 3, 7], 3: [8, 2, 4], 4: [0, 9, 3], 5: [0, 8, 7], 6: [8, 1, 9], 7: [9, 2, 5], 8: [3, 5, 6], 9: [4, 6, 7]})
1293
sage: G.set_embedding({'s': [1, 5, 4], 1: [0, 2, 6], 2: [1, 3, 7], 3: [8, 2, 4], 4: [0, 9, 3], 5: [0, 8, 7], 6: [8, 1, 9], 7: [9, 2, 5], 8: [3, 5, 6], 9: [4, 6, 7]})
1294
Traceback (most recent call last):
1295
...
1296
Exception: embedding is not valid for Petersen graph
1297
"""
1298
if self.check_embedding_validity(embedding):
1299
self._embedding = embedding
1300
else:
1301
raise Exception('embedding is not valid for %s'%self)
1302
1303
def get_embedding(self):
1304
"""
1305
Returns the attribute _embedding if it exists.
1306
1307
``_embedding`` is a dictionary organized with vertex labels as keys and a
1308
list of each vertex's neighbors in clockwise order.
1309
1310
Error-checked to insure valid embedding is returned.
1311
1312
EXAMPLES::
1313
1314
sage: G = graphs.PetersenGraph()
1315
sage: G.genus()
1316
1
1317
sage: G.get_embedding()
1318
{0: [1, 4, 5], 1: [0, 2, 6], 2: [1, 3, 7], 3: [2, 4, 8], 4: [0, 3, 9], 5: [0, 7, 8], 6: [1, 9, 8], 7: [2, 5, 9], 8: [3, 6, 5], 9: [4, 6, 7]}
1319
"""
1320
if self.check_embedding_validity():
1321
return self._embedding
1322
else:
1323
raise Exception('%s has been modified and the embedding is no longer valid.'%self)
1324
1325
def check_embedding_validity(self, embedding=None):
1326
"""
1327
Checks whether an _embedding attribute is well defined.
1328
1329
If the ``_embedding`` attribute exists, it is checked for
1330
accuracy. Returns True if everything is okay, False otherwise.
1331
1332
If embedding=None will test the attribute _embedding.
1333
1334
EXAMPLES::
1335
1336
sage: d = {0: [1, 5, 4], 1: [0, 2, 6], 2: [1, 3, 7], 3: [8, 2, 4], 4: [0, 9, 3], 5: [0, 8, 7], 6: [8, 1, 9], 7: [9, 2, 5], 8: [3, 5, 6], 9: [4, 6, 7]}
1337
sage: G = graphs.PetersenGraph()
1338
sage: G.check_embedding_validity(d)
1339
True
1340
"""
1341
if embedding is None:
1342
embedding = getattr(self, '_embedding', None)
1343
if embedding is None:
1344
return False
1345
if len(embedding) != self.order():
1346
return False
1347
if self._directed:
1348
connected = lambda u,v : self.has_edge(u,v) or self.has_edge(v,u)
1349
else:
1350
connected = lambda u,v : self.has_edge(u,v)
1351
for v in embedding:
1352
if not self.has_vertex(v):
1353
return False
1354
if len(embedding[v]) != len(self.neighbors(v)):
1355
return False
1356
for u in embedding[v]:
1357
if not connected(v,u):
1358
return False
1359
return True
1360
1361
def has_loops(self):
1362
"""
1363
Returns whether there are loops in the (di)graph.
1364
1365
EXAMPLES::
1366
1367
sage: G = Graph(loops=True); G
1368
Looped graph on 0 vertices
1369
sage: G.has_loops()
1370
False
1371
sage: G.allows_loops()
1372
True
1373
sage: G.add_edge((0,0))
1374
sage: G.has_loops()
1375
True
1376
sage: G.loops()
1377
[(0, 0, None)]
1378
sage: G.allow_loops(False); G
1379
Graph on 1 vertex
1380
sage: G.has_loops()
1381
False
1382
sage: G.edges()
1383
[]
1384
1385
sage: D = DiGraph(loops=True); D
1386
Looped digraph on 0 vertices
1387
sage: D.has_loops()
1388
False
1389
sage: D.allows_loops()
1390
True
1391
sage: D.add_edge((0,0))
1392
sage: D.has_loops()
1393
True
1394
sage: D.loops()
1395
[(0, 0, None)]
1396
sage: D.allow_loops(False); D
1397
Digraph on 1 vertex
1398
sage: D.has_loops()
1399
False
1400
sage: D.edges()
1401
[]
1402
"""
1403
if self.allows_loops():
1404
for v in self:
1405
if self.has_edge(v,v):
1406
return True
1407
return False
1408
1409
def allows_loops(self):
1410
"""
1411
Returns whether loops are permitted in the (di)graph.
1412
1413
EXAMPLES::
1414
1415
sage: G = Graph(loops=True); G
1416
Looped graph on 0 vertices
1417
sage: G.has_loops()
1418
False
1419
sage: G.allows_loops()
1420
True
1421
sage: G.add_edge((0,0))
1422
sage: G.has_loops()
1423
True
1424
sage: G.loops()
1425
[(0, 0, None)]
1426
sage: G.allow_loops(False); G
1427
Graph on 1 vertex
1428
sage: G.has_loops()
1429
False
1430
sage: G.edges()
1431
[]
1432
1433
sage: D = DiGraph(loops=True); D
1434
Looped digraph on 0 vertices
1435
sage: D.has_loops()
1436
False
1437
sage: D.allows_loops()
1438
True
1439
sage: D.add_edge((0,0))
1440
sage: D.has_loops()
1441
True
1442
sage: D.loops()
1443
[(0, 0, None)]
1444
sage: D.allow_loops(False); D
1445
Digraph on 1 vertex
1446
sage: D.has_loops()
1447
False
1448
sage: D.edges()
1449
[]
1450
"""
1451
return self._backend.loops(None)
1452
1453
def allow_loops(self, new, check=True):
1454
"""
1455
Changes whether loops are permitted in the (di)graph.
1456
1457
INPUT:
1458
1459
- ``new`` - boolean.
1460
1461
EXAMPLES::
1462
1463
sage: G = Graph(loops=True); G
1464
Looped graph on 0 vertices
1465
sage: G.has_loops()
1466
False
1467
sage: G.allows_loops()
1468
True
1469
sage: G.add_edge((0,0))
1470
sage: G.has_loops()
1471
True
1472
sage: G.loops()
1473
[(0, 0, None)]
1474
sage: G.allow_loops(False); G
1475
Graph on 1 vertex
1476
sage: G.has_loops()
1477
False
1478
sage: G.edges()
1479
[]
1480
1481
sage: D = DiGraph(loops=True); D
1482
Looped digraph on 0 vertices
1483
sage: D.has_loops()
1484
False
1485
sage: D.allows_loops()
1486
True
1487
sage: D.add_edge((0,0))
1488
sage: D.has_loops()
1489
True
1490
sage: D.loops()
1491
[(0, 0, None)]
1492
sage: D.allow_loops(False); D
1493
Digraph on 1 vertex
1494
sage: D.has_loops()
1495
False
1496
sage: D.edges()
1497
[]
1498
"""
1499
if new is False and check:
1500
self.remove_loops()
1501
self._backend.loops(new)
1502
1503
def loops(self, new=None, labels=True):
1504
"""
1505
Returns any loops in the (di)graph.
1506
1507
INPUT:
1508
1509
- ``new`` -- deprecated
1510
1511
- ``labels`` -- whether returned edges have labels ((u,v,l)) or not ((u,v)).
1512
1513
EXAMPLES::
1514
1515
sage: G = Graph(loops=True); G
1516
Looped graph on 0 vertices
1517
sage: G.has_loops()
1518
False
1519
sage: G.allows_loops()
1520
True
1521
sage: G.add_edge((0,0))
1522
sage: G.has_loops()
1523
True
1524
sage: G.loops()
1525
[(0, 0, None)]
1526
sage: G.allow_loops(False); G
1527
Graph on 1 vertex
1528
sage: G.has_loops()
1529
False
1530
sage: G.edges()
1531
[]
1532
1533
sage: D = DiGraph(loops=True); D
1534
Looped digraph on 0 vertices
1535
sage: D.has_loops()
1536
False
1537
sage: D.allows_loops()
1538
True
1539
sage: D.add_edge((0,0))
1540
sage: D.has_loops()
1541
True
1542
sage: D.loops()
1543
[(0, 0, None)]
1544
sage: D.allow_loops(False); D
1545
Digraph on 1 vertex
1546
sage: D.has_loops()
1547
False
1548
sage: D.edges()
1549
[]
1550
1551
sage: G = graphs.PetersenGraph()
1552
sage: G.loops()
1553
[]
1554
1555
"""
1556
from sage.misc.misc import deprecation
1557
if new is not None:
1558
deprecation("The function loops is replaced by allow_loops and allows_loops.")
1559
loops = []
1560
for v in self:
1561
loops += self.edge_boundary([v], [v], labels)
1562
return loops
1563
1564
def has_multiple_edges(self, to_undirected=False):
1565
"""
1566
Returns whether there are multiple edges in the (di)graph.
1567
1568
INPUT:
1569
1570
- ``to_undirected`` -- (default: False) If True, runs the test on the undirected version of a DiGraph.
1571
Otherwise, treats DiGraph edges (u,v) and (v,u) as unique individual edges.
1572
1573
EXAMPLES::
1574
1575
sage: G = Graph(multiedges=True,sparse=True); G
1576
Multi-graph on 0 vertices
1577
sage: G.has_multiple_edges()
1578
False
1579
sage: G.allows_multiple_edges()
1580
True
1581
sage: G.add_edges([(0,1)]*3)
1582
sage: G.has_multiple_edges()
1583
True
1584
sage: G.multiple_edges()
1585
[(0, 1, None), (0, 1, None), (0, 1, None)]
1586
sage: G.allow_multiple_edges(False); G
1587
Graph on 2 vertices
1588
sage: G.has_multiple_edges()
1589
False
1590
sage: G.edges()
1591
[(0, 1, None)]
1592
1593
sage: D = DiGraph(multiedges=True,sparse=True); D
1594
Multi-digraph on 0 vertices
1595
sage: D.has_multiple_edges()
1596
False
1597
sage: D.allows_multiple_edges()
1598
True
1599
sage: D.add_edges([(0,1)]*3)
1600
sage: D.has_multiple_edges()
1601
True
1602
sage: D.multiple_edges()
1603
[(0, 1, None), (0, 1, None), (0, 1, None)]
1604
sage: D.allow_multiple_edges(False); D
1605
Digraph on 2 vertices
1606
sage: D.has_multiple_edges()
1607
False
1608
sage: D.edges()
1609
[(0, 1, None)]
1610
1611
sage: G = DiGraph({1:{2: 'h'}, 2:{1:'g'}},sparse=True)
1612
sage: G.has_multiple_edges()
1613
False
1614
sage: G.has_multiple_edges(to_undirected=True)
1615
True
1616
sage: G.multiple_edges()
1617
[]
1618
sage: G.multiple_edges(to_undirected=True)
1619
[(1, 2, 'h'), (2, 1, 'g')]
1620
"""
1621
if self.allows_multiple_edges() or (self._directed and to_undirected):
1622
if self._directed:
1623
for u in self:
1624
s = set()
1625
for a,b,c in self.outgoing_edge_iterator(u):
1626
if b in s:
1627
return True
1628
s.add(b)
1629
if to_undirected:
1630
for a,b,c in self.incoming_edge_iterator(u):
1631
if a in s:
1632
return True
1633
s.add(a)
1634
else:
1635
for u in self:
1636
s = set()
1637
for a,b,c in self.edge_iterator(u):
1638
if a is u:
1639
if b in s:
1640
return True
1641
s.add(b)
1642
if b is u:
1643
if a in s:
1644
return True
1645
s.add(a)
1646
return False
1647
1648
def allows_multiple_edges(self):
1649
"""
1650
Returns whether multiple edges are permitted in the (di)graph.
1651
1652
EXAMPLES::
1653
1654
sage: G = Graph(multiedges=True,sparse=True); G
1655
Multi-graph on 0 vertices
1656
sage: G.has_multiple_edges()
1657
False
1658
sage: G.allows_multiple_edges()
1659
True
1660
sage: G.add_edges([(0,1)]*3)
1661
sage: G.has_multiple_edges()
1662
True
1663
sage: G.multiple_edges()
1664
[(0, 1, None), (0, 1, None), (0, 1, None)]
1665
sage: G.allow_multiple_edges(False); G
1666
Graph on 2 vertices
1667
sage: G.has_multiple_edges()
1668
False
1669
sage: G.edges()
1670
[(0, 1, None)]
1671
1672
sage: D = DiGraph(multiedges=True,sparse=True); D
1673
Multi-digraph on 0 vertices
1674
sage: D.has_multiple_edges()
1675
False
1676
sage: D.allows_multiple_edges()
1677
True
1678
sage: D.add_edges([(0,1)]*3)
1679
sage: D.has_multiple_edges()
1680
True
1681
sage: D.multiple_edges()
1682
[(0, 1, None), (0, 1, None), (0, 1, None)]
1683
sage: D.allow_multiple_edges(False); D
1684
Digraph on 2 vertices
1685
sage: D.has_multiple_edges()
1686
False
1687
sage: D.edges()
1688
[(0, 1, None)]
1689
"""
1690
return self._backend.multiple_edges(None)
1691
1692
def allow_multiple_edges(self, new, check=True):
1693
"""
1694
Changes whether multiple edges are permitted in the (di)graph.
1695
1696
INPUT:
1697
1698
- ``new`` - boolean.
1699
1700
EXAMPLES::
1701
1702
sage: G = Graph(multiedges=True,sparse=True); G
1703
Multi-graph on 0 vertices
1704
sage: G.has_multiple_edges()
1705
False
1706
sage: G.allows_multiple_edges()
1707
True
1708
sage: G.add_edges([(0,1)]*3)
1709
sage: G.has_multiple_edges()
1710
True
1711
sage: G.multiple_edges()
1712
[(0, 1, None), (0, 1, None), (0, 1, None)]
1713
sage: G.allow_multiple_edges(False); G
1714
Graph on 2 vertices
1715
sage: G.has_multiple_edges()
1716
False
1717
sage: G.edges()
1718
[(0, 1, None)]
1719
1720
sage: D = DiGraph(multiedges=True,sparse=True); D
1721
Multi-digraph on 0 vertices
1722
sage: D.has_multiple_edges()
1723
False
1724
sage: D.allows_multiple_edges()
1725
True
1726
sage: D.add_edges([(0,1)]*3)
1727
sage: D.has_multiple_edges()
1728
True
1729
sage: D.multiple_edges()
1730
[(0, 1, None), (0, 1, None), (0, 1, None)]
1731
sage: D.allow_multiple_edges(False); D
1732
Digraph on 2 vertices
1733
sage: D.has_multiple_edges()
1734
False
1735
sage: D.edges()
1736
[(0, 1, None)]
1737
"""
1738
seen = set()
1739
1740
# TODO: this should be much faster for c_graphs, but for now we just do this
1741
if self.allows_multiple_edges() and new is False and check:
1742
for u,v,l in self.multiple_edges():
1743
if (u,v) in seen:
1744
self.delete_edge(u,v,l)
1745
else:
1746
seen.add((u,v))
1747
1748
self._backend.multiple_edges(new)
1749
1750
def multiple_edges(self, new=None, to_undirected=False, labels=True):
1751
"""
1752
Returns any multiple edges in the (di)graph.
1753
1754
EXAMPLES::
1755
1756
sage: G = Graph(multiedges=True,sparse=True); G
1757
Multi-graph on 0 vertices
1758
sage: G.has_multiple_edges()
1759
False
1760
sage: G.allows_multiple_edges()
1761
True
1762
sage: G.add_edges([(0,1)]*3)
1763
sage: G.has_multiple_edges()
1764
True
1765
sage: G.multiple_edges()
1766
[(0, 1, None), (0, 1, None), (0, 1, None)]
1767
sage: G.allow_multiple_edges(False); G
1768
Graph on 2 vertices
1769
sage: G.has_multiple_edges()
1770
False
1771
sage: G.edges()
1772
[(0, 1, None)]
1773
1774
sage: D = DiGraph(multiedges=True,sparse=True); D
1775
Multi-digraph on 0 vertices
1776
sage: D.has_multiple_edges()
1777
False
1778
sage: D.allows_multiple_edges()
1779
True
1780
sage: D.add_edges([(0,1)]*3)
1781
sage: D.has_multiple_edges()
1782
True
1783
sage: D.multiple_edges()
1784
[(0, 1, None), (0, 1, None), (0, 1, None)]
1785
sage: D.allow_multiple_edges(False); D
1786
Digraph on 2 vertices
1787
sage: D.has_multiple_edges()
1788
False
1789
sage: D.edges()
1790
[(0, 1, None)]
1791
1792
sage: G = DiGraph({1:{2: 'h'}, 2:{1:'g'}},sparse=True)
1793
sage: G.has_multiple_edges()
1794
False
1795
sage: G.has_multiple_edges(to_undirected=True)
1796
True
1797
sage: G.multiple_edges()
1798
[]
1799
sage: G.multiple_edges(to_undirected=True)
1800
[(1, 2, 'h'), (2, 1, 'g')]
1801
"""
1802
from sage.misc.misc import deprecation
1803
if new is not None:
1804
deprecation("The function multiple_edges is replaced by allow_multiple_edges and allows_multiple_edges.")
1805
multi_edges = []
1806
if self._directed and not to_undirected:
1807
for v in self:
1808
for u in self.neighbor_in_iterator(v):
1809
edges = self.edge_boundary([u], [v], labels)
1810
if len(edges) > 1:
1811
multi_edges += edges
1812
else:
1813
to_undirected *= self._directed
1814
for v in self:
1815
for u in self.neighbor_iterator(v):
1816
if hash(u) >= hash(v):
1817
edges = self.edge_boundary([v], [u], labels)
1818
if to_undirected:
1819
edges += self.edge_boundary([u],[v], labels)
1820
if len(edges) > 1:
1821
multi_edges += edges
1822
return multi_edges
1823
1824
def name(self, new=None):
1825
"""
1826
Returns or sets the graph's name.
1827
1828
INPUT:
1829
1830
- ``new`` - if not None, then this becomes the new name of the (di)graph.
1831
(if new == '', removes any name)
1832
1833
EXAMPLES::
1834
1835
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]}
1836
sage: G = Graph(d); G
1837
Graph on 10 vertices
1838
sage: G.name("Petersen Graph"); G
1839
Petersen Graph: Graph on 10 vertices
1840
sage: G.name(new=""); G
1841
Graph on 10 vertices
1842
sage: G.name()
1843
''
1844
"""
1845
return self._backend.name(new)
1846
1847
def get_pos(self, dim = 2):
1848
"""
1849
Returns the position dictionary, a dictionary specifying the
1850
coordinates of each vertex.
1851
1852
EXAMPLES: By default, the position of a graph is None::
1853
1854
sage: G = Graph()
1855
sage: G.get_pos()
1856
sage: G.get_pos() is None
1857
True
1858
sage: P = G.plot(save_pos=True)
1859
sage: G.get_pos()
1860
{}
1861
1862
Some of the named graphs come with a pre-specified positioning::
1863
1864
sage: G = graphs.PetersenGraph()
1865
sage: G.get_pos()
1866
{0: (...e-17, 1.0),
1867
...
1868
9: (0.475..., 0.154...)}
1869
"""
1870
if dim == 2:
1871
return self._pos
1872
elif dim == 3:
1873
return getattr(self, "_pos3d", None)
1874
else:
1875
raise ValueError("dim must be 2 or 3")
1876
1877
def check_pos_validity(self, pos=None, dim = 2):
1878
r"""
1879
Checks whether pos specifies two (resp. 3) coordinates for every vertex (and no more vertices).
1880
1881
INPUT:
1882
1883
- ``pos`` - a position dictionary for a set of vertices
1884
- ``dim`` - 2 or 3 (default: 3
1885
1886
OUTPUT:
1887
1888
If ``pos`` is ``None`` then the position dictionary of ``self`` is
1889
investigated, otherwise the position dictionary provided in ``pos`` is
1890
investigated. The function returns ``True`` if the dictionary is of the
1891
correct form for ``self``.
1892
1893
EXAMPLES::
1894
1895
sage: p = {0: [1, 5], 1: [0, 2], 2: [1, 3], 3: [8, 2], 4: [0, 9], 5: [0, 8], 6: [8, 1], 7: [9, 5], 8: [3, 5], 9: [6, 7]}
1896
sage: G = graphs.PetersenGraph()
1897
sage: G.check_pos_validity(p)
1898
True
1899
"""
1900
if pos is None:
1901
pos = self.get_pos(dim = dim)
1902
if pos is None:
1903
return False
1904
if len(pos) != self.order():
1905
return False
1906
for v in pos:
1907
if not self.has_vertex(v):
1908
return False
1909
if len(pos[v]) != dim:
1910
return False
1911
return True
1912
1913
def set_pos(self, pos, dim = 2):
1914
"""
1915
Sets the position dictionary, a dictionary specifying the
1916
coordinates of each vertex.
1917
1918
EXAMPLES: Note that set_pos will allow you to do ridiculous things,
1919
which will not blow up until plotting::
1920
1921
sage: G = graphs.PetersenGraph()
1922
sage: G.get_pos()
1923
{0: (..., ...),
1924
...
1925
9: (..., ...)}
1926
1927
::
1928
1929
sage: G.set_pos('spam')
1930
sage: P = G.plot()
1931
Traceback (most recent call last):
1932
...
1933
TypeError: string indices must be integers, not str
1934
"""
1935
if dim == 2:
1936
self._pos = pos
1937
elif dim == 3:
1938
self._pos3d = pos
1939
else:
1940
raise ValueError("dim must be 2 or 3")
1941
1942
def weighted(self, new=None):
1943
"""
1944
Whether the (di)graph is to be considered as a weighted (di)graph.
1945
1946
Note that edge weightings can still exist for (di)graphs ``G`` where
1947
``G.weighted()`` is ``False``.
1948
1949
EXAMPLES:
1950
1951
Here we have two graphs with different labels, but ``weighted()`` is
1952
``False`` for both, so we just check for the presence of edges::
1953
1954
sage: G = Graph({0:{1:'a'}}, sparse=True)
1955
sage: H = Graph({0:{1:'b'}}, sparse=True)
1956
sage: G == H
1957
True
1958
1959
Now one is weighted and the other is not, and thus the graphs are
1960
not equal::
1961
1962
sage: G.weighted(True)
1963
sage: H.weighted()
1964
False
1965
sage: G == H
1966
False
1967
1968
However, if both are weighted, then we finally compare 'a' to 'b'::
1969
1970
sage: H.weighted(True)
1971
sage: G == H
1972
False
1973
1974
TESTS:
1975
1976
Ensure that ticket #10490 is fixed: allows a weighted graph to be
1977
set as unweighted. ::
1978
1979
sage: G = Graph({1:{2:3}})
1980
sage: G.weighted()
1981
False
1982
sage: G.weighted('a')
1983
sage: G.weighted(True)
1984
sage: G.weighted()
1985
True
1986
sage: G.weighted('a')
1987
sage: G.weighted()
1988
True
1989
sage: G.weighted(False)
1990
sage: G.weighted()
1991
False
1992
sage: G.weighted('a')
1993
sage: G.weighted()
1994
False
1995
sage: G.weighted(True)
1996
sage: G.weighted()
1997
True
1998
"""
1999
if new is not None:
2000
if new in [True, False]:
2001
self._weighted = new
2002
else:
2003
return self._weighted
2004
2005
### Properties
2006
2007
def antisymmetric(self):
2008
r"""
2009
Tests whether the graph is antisymmetric.
2010
2011
A graph represents an antisymmetric relation if there being a path
2012
from a vertex x to a vertex y implies that there is not a path from
2013
y to x unless x=y.
2014
2015
A directed acyclic graph is antisymmetric. An undirected graph is
2016
never antisymmetric unless it is just a union of isolated
2017
vertices.
2018
2019
::
2020
2021
sage: graphs.RandomGNP(20,0.5).antisymmetric()
2022
False
2023
sage: digraphs.RandomDirectedGNR(20,0.5).antisymmetric()
2024
True
2025
"""
2026
if not self._directed:
2027
if self.size()-len(self.loop_edges())>0:
2028
return False
2029
else:
2030
return True
2031
from copy import copy
2032
g = copy(self)
2033
g.allow_multiple_edges(False)
2034
g.allow_loops(False)
2035
g = g.transitive_closure()
2036
gpaths = g.edges(labels=False)
2037
for e in gpaths:
2038
if (e[1],e[0]) in gpaths:
2039
return False
2040
return True
2041
2042
def density(self):
2043
"""
2044
Returns the density (number of edges divided by number of possible
2045
edges).
2046
2047
In the case of a multigraph, raises an error, since there is an
2048
infinite number of possible edges.
2049
2050
EXAMPLES::
2051
2052
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]}
2053
sage: G = Graph(d); G.density()
2054
1/3
2055
sage: G = Graph({0:[1,2], 1:[0] }); G.density()
2056
2/3
2057
sage: G = DiGraph({0:[1,2], 1:[0] }); G.density()
2058
1/2
2059
2060
Note that there are more possible edges on a looped graph::
2061
2062
sage: G.allow_loops(True)
2063
sage: G.density()
2064
1/3
2065
"""
2066
if self.has_multiple_edges():
2067
raise TypeError("Density is not well-defined for multigraphs.")
2068
from sage.rings.rational import Rational
2069
n = self.order()
2070
if self.allows_loops():
2071
if n == 0:
2072
return Rational(0)
2073
if self._directed:
2074
return Rational(self.size())/Rational(n**2)
2075
else:
2076
return Rational(self.size())/Rational((n**2 + n)/2)
2077
else:
2078
if n < 2:
2079
return Rational(0)
2080
if self._directed:
2081
return Rational(self.size())/Rational((n**2 - n))
2082
else:
2083
return Rational(self.size())/Rational((n**2 - n)/2)
2084
2085
def is_eulerian(self, path=False):
2086
r"""
2087
Return true if the graph has a (closed) tour that visits each edge exactly
2088
once.
2089
2090
INPUT:
2091
2092
- ``path`` -- by default this function finds if the graph contains a closed
2093
tour visiting each edge once, i.e. an eulerian cycle. If you want to test
2094
the existence of an eulerian path, set this argument to ``True``. Graphs
2095
with this property are sometimes called semi-eulerian.
2096
2097
OUTPUT:
2098
2099
``True`` or ``False`` for the closed tour case. For an open tour search
2100
(``path``=``True``) the function returns ``False`` if the graph is not
2101
semi-eulerian, or a tuple (u, v) in the other case. This tuple defines the
2102
edge that would make the graph eulerian, i.e. close an existing open tour.
2103
This edge may or may not be already present in the graph.
2104
2105
EXAMPLES::
2106
2107
sage: graphs.CompleteGraph(4).is_eulerian()
2108
False
2109
sage: graphs.CycleGraph(4).is_eulerian()
2110
True
2111
sage: g = DiGraph({0:[1,2], 1:[2]}); g.is_eulerian()
2112
False
2113
sage: g = DiGraph({0:[2], 1:[3], 2:[0,1], 3:[2]}); g.is_eulerian()
2114
True
2115
sage: g = DiGraph({0:[1], 1:[2], 2:[0], 3:[]}); g.is_eulerian()
2116
True
2117
sage: g = Graph([(1,2), (2,3), (3,1), (4,5), (5,6), (6,4)]); g.is_eulerian()
2118
False
2119
2120
::
2121
2122
sage: g = DiGraph({0: [1]}); g.is_eulerian(path=True)
2123
(1, 0)
2124
sage: graphs.CycleGraph(4).is_eulerian(path=True)
2125
False
2126
sage: g = DiGraph({0: [1], 1: [2,3], 2: [4]}); g.is_eulerian(path=True)
2127
False
2128
2129
::
2130
2131
sage: g = Graph({0:[1,2,3], 1:[2,3], 2:[3,4], 3:[4]}, multiedges=True)
2132
sage: g.is_eulerian()
2133
False
2134
sage: e = g.is_eulerian(path=True); e
2135
(0, 1)
2136
sage: g.add_edge(e)
2137
sage: g.is_eulerian(path=False)
2138
True
2139
sage: g.is_eulerian(path=True)
2140
False
2141
2142
TESTS::
2143
2144
sage: g = Graph({0:[], 1:[], 2:[], 3:[]}); g.is_eulerian()
2145
True
2146
"""
2147
2148
# unconnected graph can still be eulerian if all components
2149
# up to one doesn't contain any edge
2150
nontrivial_components = 0
2151
for cc in self.connected_components():
2152
if len(cc) > 1:
2153
nontrivial_components += 1
2154
if nontrivial_components > 1:
2155
return False
2156
2157
uv = [None, None]
2158
if self._directed:
2159
for v in self.vertex_iterator():
2160
# loops don't matter since they count in both the in and out degree.
2161
if self.in_degree(v) != self.out_degree(v):
2162
if path:
2163
diff = self.out_degree(v) - self.in_degree(v)
2164
if abs(diff) > 1:
2165
return False
2166
else:
2167
# if there was another vertex with the same sign of difference...
2168
if uv[(diff+1)/2] != None:
2169
return False # ... the graph is not semi-eulerian
2170
else:
2171
uv[(diff+1)/2] = v
2172
else:
2173
return False
2174
else:
2175
for v in self.vertex_iterator():
2176
# loops don't matter since they add an even number to the degree
2177
if self.degree(v) % 2 != 0:
2178
if not path:
2179
return False
2180
else:
2181
if uv[0] is None or uv[1] is None:
2182
uv[0 if uv[0] is None else 1] = v
2183
else:
2184
return False
2185
2186
if path and (uv[0] is None or uv[1] is None):
2187
return False
2188
2189
return True if not path else tuple(uv)
2190
2191
def is_tree(self):
2192
"""
2193
Return True if the graph is a tree.
2194
2195
EXAMPLES::
2196
2197
sage: for g in graphs.trees(6):
2198
... g.is_tree()
2199
True
2200
True
2201
True
2202
True
2203
True
2204
True
2205
"""
2206
if not self.is_connected():
2207
return False
2208
if self.num_verts() != self.num_edges() + 1:
2209
return False
2210
return True
2211
2212
def is_forest(self):
2213
"""
2214
Return True if the graph is a forest, i.e. a disjoint union of
2215
trees.
2216
2217
EXAMPLES::
2218
2219
sage: seven_acre_wood = sum(graphs.trees(7), Graph())
2220
sage: seven_acre_wood.is_forest()
2221
True
2222
"""
2223
number_of_connected_components = len(self.connected_components())
2224
2225
return self.num_verts() == self.num_edges() + number_of_connected_components
2226
2227
def is_overfull(self):
2228
r"""
2229
Tests whether the current graph is overfull.
2230
2231
A graph `G` on `n` vertices and `m` edges is said to
2232
be overfull if:
2233
2234
- `n` is odd
2235
2236
- It satisfies `2m > (n-1)\Delta(G)`, where
2237
`\Delta(G)` denotes the maximum degree
2238
among all vertices in `G`.
2239
2240
An overfull graph must have a chromatic index of `\Delta(G)+1`.
2241
2242
EXAMPLES:
2243
2244
A complete graph of order `n > 1` is overfull if and only if `n` is
2245
odd::
2246
2247
sage: graphs.CompleteGraph(6).is_overfull()
2248
False
2249
sage: graphs.CompleteGraph(7).is_overfull()
2250
True
2251
sage: graphs.CompleteGraph(1).is_overfull()
2252
False
2253
2254
The claw graph is not overfull::
2255
2256
sage: from sage.graphs.graph_coloring import edge_coloring
2257
sage: g = graphs.ClawGraph()
2258
sage: g
2259
Claw graph: Graph on 4 vertices
2260
sage: edge_coloring(g, value_only=True)
2261
3
2262
sage: g.is_overfull()
2263
False
2264
2265
Checking that all complete graphs `K_n` for even `0 \leq n \leq 100`
2266
are not overfull::
2267
2268
sage: def check_overfull_Kn_even(n):
2269
... i = 0
2270
... while i <= n:
2271
... if graphs.CompleteGraph(i).is_overfull():
2272
... print "A complete graph of even order cannot be overfull."
2273
... return
2274
... i += 2
2275
... print "Complete graphs of even order up to %s are not overfull." % n
2276
...
2277
sage: check_overfull_Kn_even(100) # long time
2278
Complete graphs of even order up to 100 are not overfull.
2279
2280
The null graph, i.e. the graph with no vertices, is not overfull::
2281
2282
sage: Graph().is_overfull()
2283
False
2284
sage: graphs.CompleteGraph(0).is_overfull()
2285
False
2286
2287
Checking that all complete graphs `K_n` for odd `1 < n \leq 100`
2288
are overfull::
2289
2290
sage: def check_overfull_Kn_odd(n):
2291
... i = 3
2292
... while i <= n:
2293
... if not graphs.CompleteGraph(i).is_overfull():
2294
... print "A complete graph of odd order > 1 must be overfull."
2295
... return
2296
... i += 2
2297
... print "Complete graphs of odd order > 1 up to %s are overfull." % n
2298
...
2299
sage: check_overfull_Kn_odd(100) # long time
2300
Complete graphs of odd order > 1 up to 100 are overfull.
2301
2302
The Petersen Graph, though, is not overfull while
2303
its chromatic index is `\Delta+1`::
2304
2305
sage: g = graphs.PetersenGraph()
2306
sage: g.is_overfull()
2307
False
2308
sage: from sage.graphs.graph_coloring import edge_coloring
2309
sage: max(g.degree()) + 1 == edge_coloring(g, value_only=True)
2310
True
2311
"""
2312
# # A possible optimized version. But the gain in speed is very little.
2313
# return bool(self._backend.num_verts() & 1) and ( # odd order n
2314
# 2 * self._backend.num_edges(self._directed) > #2m > \Delta(G)*(n-1)
2315
# max(self.degree()) * (self._backend.num_verts() - 1))
2316
# unoptimized version
2317
return (self.order() % 2 == 1) and (
2318
2 * self.size() > max(self.degree()) * (self.order() - 1))
2319
2320
def order(self):
2321
"""
2322
Returns the number of vertices. Note that len(G) returns the number
2323
of vertices in G also.
2324
2325
EXAMPLES::
2326
2327
sage: G = graphs.PetersenGraph()
2328
sage: G.order()
2329
10
2330
2331
::
2332
2333
sage: G = graphs.TetrahedralGraph()
2334
sage: len(G)
2335
4
2336
"""
2337
return self._backend.num_verts()
2338
2339
__len__ = order
2340
2341
num_verts = order
2342
2343
def size(self):
2344
"""
2345
Returns the number of edges.
2346
2347
EXAMPLES::
2348
2349
sage: G = graphs.PetersenGraph()
2350
sage: G.size()
2351
15
2352
"""
2353
return self._backend.num_edges(self._directed)
2354
2355
num_edges = size
2356
2357
### Orientations
2358
2359
def eulerian_orientation(self):
2360
r"""
2361
Returns a DiGraph which is an Eulerian orientation of the current graph.
2362
2363
An Eulerian graph being a graph such that any vertex has an even degree,
2364
an Eulerian orientation of a graph is an orientation of its edges such
2365
that each vertex `v` verifies `d^+(v)=d^-(v)=d(v)/2`, where `d^+` and
2366
`d^-` respectively represent the out-degree and the in-degree of a vertex.
2367
2368
If the graph is not Eulerian, the orientation verifies for any vertex `v`
2369
that `| d^+(v)-d^-(v) | \leq 1`.
2370
2371
ALGORITHM:
2372
2373
This algorithm is a random walk through the edges of the graph, which
2374
orients the edges according to the walk. When a vertex is reached which
2375
has no non-oriented edge ( this vertex must have odd degree ), the
2376
walk resumes at another vertex of odd degree, if any.
2377
2378
This algorithm has complexity `O(m)`, where `m` is the number of edges
2379
in the graph.
2380
2381
EXAMPLES:
2382
2383
The CubeGraph with parameter 4, which is regular of even degree, has an
2384
Eulerian orientation such that `d^+=d^-`::
2385
2386
sage: g=graphs.CubeGraph(4)
2387
sage: g.degree()
2388
[4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4]
2389
sage: o=g.eulerian_orientation()
2390
sage: o.in_degree()
2391
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
2392
sage: o.out_degree()
2393
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
2394
2395
Secondly, the Petersen Graph, which is 3 regular has an orientation
2396
such that the difference between `d^+` and `d^-` is at most 1::
2397
2398
sage: g=graphs.PetersenGraph()
2399
sage: o=g.eulerian_orientation()
2400
sage: o.in_degree()
2401
[2, 2, 2, 2, 2, 1, 1, 1, 1, 1]
2402
sage: o.out_degree()
2403
[1, 1, 1, 1, 1, 2, 2, 2, 2, 2]
2404
"""
2405
from copy import copy
2406
g=copy(self)
2407
from sage.graphs.digraph import DiGraph
2408
d=DiGraph()
2409
d.add_vertices(g.vertex_iterator())
2410
2411
2412
# list of vertices of odd degree
2413
from itertools import izip
2414
odd=[x for (x,deg) in izip(g.vertex_iterator(),g.degree_iterator()) if deg%2==1]
2415
2416
# Picks the first vertex, which is preferably an odd one
2417
if len(odd)>0:
2418
v=odd.pop()
2419
else:
2420
v=g.edge_iterator(labels=None).next()[0]
2421
odd.append(v)
2422
# Stops when there is no edge left
2423
while True:
2424
2425
# If there is an edge adjacent to the current one
2426
if g.degree(v)>0:
2427
e = g.edge_iterator(v).next()
2428
g.delete_edge(e)
2429
if e[0]!=v:
2430
e=(e[1],e[0],e[2])
2431
d.add_edge(e)
2432
v=e[1]
2433
2434
# The current vertex is isolated
2435
else:
2436
odd.remove(v)
2437
2438
# jumps to another odd vertex if possible
2439
if len(odd)>0:
2440
v=odd.pop()
2441
# Else jumps to an ever vertex which is not isolated
2442
elif g.size()>0:
2443
v=g.edge_iterator().next()[0]
2444
odd.append(v)
2445
# If there is none, we are done !
2446
else:
2447
return d
2448
2449
def eulerian_circuit(self, return_vertices=False, labels=True, path=False):
2450
r"""
2451
Return a list of edges forming an eulerian circuit if one exists.
2452
Otherwise return False.
2453
2454
This is implemented using Hierholzer's algorithm.
2455
2456
INPUT:
2457
2458
- ``return_vertices`` -- (default: ``False``) optionally provide a list of
2459
vertices for the path
2460
2461
- ``labels`` -- (default: ``True``) whether to return edges with labels
2462
(3-tuples)
2463
2464
- ``path`` -- (default: ``False``) find an eulerian path instead
2465
2466
OUTPUT:
2467
2468
either ([edges], [vertices]) or [edges] of an Eulerian circuit (or path)
2469
2470
EXAMPLES::
2471
2472
sage: g=graphs.CycleGraph(5);
2473
sage: g.eulerian_circuit()
2474
[(0, 4, None), (4, 3, None), (3, 2, None), (2, 1, None), (1, 0, None)]
2475
sage: g.eulerian_circuit(labels=False)
2476
[(0, 4), (4, 3), (3, 2), (2, 1), (1, 0)]
2477
2478
::
2479
2480
sage: g = graphs.CompleteGraph(7)
2481
sage: edges, vertices = g.eulerian_circuit(return_vertices=True)
2482
sage: vertices
2483
[0, 6, 5, 4, 6, 3, 5, 2, 4, 3, 2, 6, 1, 5, 0, 4, 1, 3, 0, 2, 1, 0]
2484
2485
::
2486
2487
sage: graphs.CompleteGraph(4).eulerian_circuit()
2488
False
2489
2490
A disconnected graph can be eulerian::
2491
2492
sage: g = Graph({0: [], 1: [2], 2: [3], 3: [1], 4: []})
2493
sage: g.eulerian_circuit(labels=False)
2494
[(1, 3), (3, 2), (2, 1)]
2495
2496
::
2497
2498
sage: g = DiGraph({0: [1], 1: [2, 4], 2:[3], 3:[1]})
2499
sage: g.eulerian_circuit(labels=False, path=True)
2500
[(0, 1), (1, 2), (2, 3), (3, 1), (1, 4)]
2501
2502
::
2503
2504
sage: g = Graph({0:[1,2,3], 1:[2,3], 2:[3,4], 3:[4]})
2505
sage: g.is_eulerian(path=True)
2506
(0, 1)
2507
sage: g.eulerian_circuit(labels=False, path=True)
2508
[(1, 3), (3, 4), (4, 2), (2, 3), (3, 0), (0, 2), (2, 1), (1, 0)]
2509
2510
TESTS::
2511
2512
sage: Graph({'H': ['G','L','L','D'], 'L': ['G','D']}).eulerian_circuit(labels=False)
2513
[('H', 'D'), ('D', 'L'), ('L', 'G'), ('G', 'H'), ('H', 'L'), ('L', 'H')]
2514
sage: Graph({0: [0, 1, 1, 1, 1]}).eulerian_circuit(labels=False)
2515
[(0, 1), (1, 0), (0, 1), (1, 0), (0, 0)]
2516
"""
2517
# trivial case
2518
if self.order() == 0:
2519
return ([], []) if return_vertices else []
2520
2521
# check if the graph has proper properties to be eulerian
2522
edge = self.is_eulerian(path=path)
2523
if not edge:
2524
return False
2525
if path:
2526
start_vertex = edge[0]
2527
2528
edges = []
2529
vertices = []
2530
2531
# we'll remove edges as we go, so let's preserve the graph structure
2532
if self.is_directed():
2533
g = self.reverse() # so the output will be in the proper order
2534
else:
2535
from copy import copy
2536
g = copy(self)
2537
2538
if not path:
2539
# get the first vertex with degree>0
2540
start_vertex = None
2541
for v in g.vertex_iterator():
2542
if g.degree(v) != 0:
2543
start_vertex = v
2544
break
2545
2546
# (where to return?, what was the way?)
2547
stack = [ (start_vertex, None) ]
2548
2549
while len(stack) != 0:
2550
v, e = stack.pop()
2551
2552
degr = g.out_degree(v) if self.is_directed() else g.degree(v)
2553
if degr == 0:
2554
vertices.append(v)
2555
if e != None:
2556
edges.append(e if labels else (e[0], e[1]))
2557
else:
2558
if self.is_directed():
2559
next_edge = g.outgoing_edge_iterator(v).next()
2560
else:
2561
next_edge = g.edge_iterator(v).next()
2562
2563
if next_edge[0] == v: # in the undirected case we want to
2564
# save the direction of traversal
2565
next_edge_new = (next_edge[1], next_edge[0], next_edge[2])
2566
else:
2567
next_edge_new = next_edge
2568
next_vertex = next_edge_new[0]
2569
2570
stack.append((v, e))
2571
stack.append((next_vertex, next_edge_new))
2572
2573
g.delete_edge(next_edge)
2574
2575
if return_vertices:
2576
return edges, vertices
2577
else:
2578
return edges
2579
2580
def min_spanning_tree(self,
2581
weight_function=lambda e: 1,
2582
algorithm="Kruskal",
2583
starting_vertex=None,
2584
check=False):
2585
r"""
2586
Returns the edges of a minimum spanning tree.
2587
2588
INPUT:
2589
2590
- ``weight_function`` -- A function that takes an edge and returns a
2591
numeric weight. Defaults to assigning each edge a weight of 1.
2592
2593
- ``algorithm`` -- The algorithm to use in computing a minimum spanning
2594
tree of ``G``. The default is to use Kruskal's algorithm. The
2595
following algorithms are supported:
2596
2597
- ``"Kruskal"`` -- Kruskal's algorithm.
2598
2599
- ``"Prim_fringe"`` -- a variant of Prim's algorithm.
2600
``"Prim_fringe"`` ignores the labels on the edges.
2601
2602
- ``"Prim_edge"`` -- a variant of Prim's algorithm.
2603
2604
- ``NetworkX`` -- Uses NetworkX's minimum spanning tree
2605
implementation.
2606
2607
- ``starting_vertex`` -- The vertex from which to begin the search
2608
for a minimum spanning tree.
2609
2610
- ``check`` -- Boolean; default: ``False``. Whether to first perform
2611
sanity checks on the input graph ``G``. If appropriate, ``check``
2612
is passed on to any minimum spanning tree functions that are
2613
invoked from the current method. See the documentation of the
2614
corresponding functions for details on what sort of sanity checks
2615
will be performed.
2616
2617
OUTPUT:
2618
2619
The edges of a minimum spanning tree of ``G``, if one exists, otherwise
2620
returns the empty list.
2621
2622
.. seealso::
2623
2624
- :func:`sage.graphs.spanning_tree.kruskal`
2625
2626
EXAMPLES:
2627
2628
Kruskal's algorithm::
2629
2630
sage: g = graphs.CompleteGraph(5)
2631
sage: len(g.min_spanning_tree())
2632
4
2633
sage: weight = lambda e: 1 / ((e[0] + 1) * (e[1] + 1))
2634
sage: g.min_spanning_tree(weight_function=weight)
2635
[(3, 4, None), (2, 4, None), (1, 4, None), (0, 4, None)]
2636
sage: g = graphs.PetersenGraph()
2637
sage: g.allow_multiple_edges(True)
2638
sage: g.weighted(True)
2639
sage: g.add_edges(g.edges())
2640
sage: g.min_spanning_tree()
2641
[(0, 1, None), (0, 4, None), (0, 5, None), (1, 2, None), (1, 6, None), (2, 3, None), (2, 7, None), (3, 8, None), (4, 9, None)]
2642
2643
Prim's algorithm::
2644
2645
sage: g = graphs.CompleteGraph(5)
2646
sage: g.min_spanning_tree(algorithm='Prim_edge', starting_vertex=2, weight_function=weight)
2647
[(2, 4, None), (3, 4, None), (1, 4, None), (0, 4, None)]
2648
sage: g.min_spanning_tree(algorithm='Prim_fringe', starting_vertex=2, weight_function=weight)
2649
[(2, 4), (4, 3), (4, 1), (4, 0)]
2650
"""
2651
if algorithm == "Kruskal":
2652
from spanning_tree import kruskal
2653
return kruskal(self, wfunction=weight_function, check=check)
2654
elif algorithm == "Prim_fringe":
2655
if starting_vertex is None:
2656
v = self.vertex_iterator().next()
2657
else:
2658
v = starting_vertex
2659
tree = set([v])
2660
edges = []
2661
# Initialize fringe_list with v's neighbors. Fringe_list
2662
# contains fringe_vertex: (vertex_in_tree, weight) for each
2663
# fringe vertex.
2664
fringe_list = dict([u, (weight_function((v, u)), v)] for u in self[v])
2665
cmp_fun = lambda x: fringe_list[x]
2666
for i in range(self.order() - 1):
2667
# find the smallest-weight fringe vertex
2668
u = min(fringe_list, key=cmp_fun)
2669
edges.append((fringe_list[u][1], u))
2670
tree.add(u)
2671
fringe_list.pop(u)
2672
# update fringe list
2673
for neighbor in [v for v in self[u] if v not in tree]:
2674
w = weight_function((u, neighbor))
2675
if neighbor not in fringe_list or fringe_list[neighbor][0] > w:
2676
fringe_list[neighbor] = (w, u)
2677
return edges
2678
elif algorithm == "Prim_edge":
2679
if starting_vertex is None:
2680
v = self.vertex_iterator().next()
2681
else:
2682
v = starting_vertex
2683
sorted_edges = sorted(self.edges(), key=weight_function)
2684
tree = set([v])
2685
edges = []
2686
for _ in range(self.order() - 1):
2687
# Find a minimum-weight edge connecting a vertex in the tree
2688
# to something outside the tree. Remove the edges between
2689
# tree vertices for efficiency.
2690
i = 0
2691
while True:
2692
e = sorted_edges[i]
2693
v0, v1 = e[0], e[1]
2694
if v0 in tree:
2695
del sorted_edges[i]
2696
if v1 in tree:
2697
continue
2698
edges.append(e)
2699
tree.add(v1)
2700
break
2701
elif v1 in tree:
2702
del sorted_edges[i]
2703
edges.append(e)
2704
tree.add(v0)
2705
break
2706
else:
2707
i += 1
2708
return edges
2709
elif algorithm == "NetworkX":
2710
import networkx
2711
G = networkx.Graph([(u, v, dict(weight=weight_function((u, v)))) for u, v, l in self.edge_iterator()])
2712
return list(networkx.mst(G))
2713
else:
2714
raise NotImplementedError("Minimum Spanning Tree algorithm '%s' is not implemented." % algorithm)
2715
2716
def spanning_trees_count(self, root_vertex=None):
2717
"""
2718
Returns the number of spanning trees in a graph.
2719
2720
In the case of a digraph, counts the number of spanning out-trees rooted
2721
in ``root_vertex``. Default is to set first vertex as root.
2722
2723
This computation uses Kirchhoff's Matrix Tree Theorem [1] to calculate
2724
the number of spanning trees. For complete graphs on `n` vertices the
2725
result can also be reached using Cayley's formula: the number of
2726
spanning trees are `n^(n-2)`.
2727
2728
For digraphs, the augmented Kirchhoff Matrix as defined in [2] is
2729
used for calculations. Here the result is the number of out-trees
2730
rooted at a specific vertex.
2731
2732
INPUT:
2733
2734
- ``root_vertex`` -- integer (default: the first vertex) This is the vertex
2735
that will be used as root for all spanning out-trees if the graph
2736
is a directed graph. This argument is ignored if the graph is not a digraph.
2737
2738
REFERENCES:
2739
2740
- [1] http://mathworld.wolfram.com/MatrixTreeTheorem.html
2741
2742
- [2] Lih-Hsing Hsu, Cheng-Kuan Lin, "Graph Theory and
2743
Interconnection Networks"
2744
2745
AUTHORS:
2746
2747
- Anders Jonsson (2009-10-10)
2748
2749
EXAMPLES::
2750
2751
sage: G = graphs.PetersenGraph()
2752
sage: G.spanning_trees_count()
2753
2000
2754
2755
::
2756
2757
sage: n = 11
2758
sage: G = graphs.CompleteGraph(n)
2759
sage: ST = G.spanning_trees_count()
2760
sage: ST == n^(n-2)
2761
True
2762
2763
::
2764
2765
sage: M=matrix(3,3,[0,1,0,0,0,1,1,1,0])
2766
sage: D=DiGraph(M)
2767
sage: D.spanning_trees_count()
2768
1
2769
sage: D.spanning_trees_count(0)
2770
1
2771
sage: D.spanning_trees_count(2)
2772
2
2773
2774
"""
2775
if self.is_directed() == False:
2776
M=self.kirchhoff_matrix()
2777
M.subdivide(1,1)
2778
M2 = M.subdivision(1,1)
2779
return abs(M2.determinant())
2780
else:
2781
if root_vertex == None:
2782
root_vertex=self.vertex_iterator().next()
2783
if root_vertex not in self.vertices():
2784
raise ValueError, ("Vertex (%s) not in the graph."%root_vertex)
2785
2786
M=self.kirchhoff_matrix()
2787
2788
index=self.vertices().index(root_vertex)
2789
M[index,index]+=1
2790
return abs(M.determinant())
2791
2792
def cycle_basis(self):
2793
r"""
2794
Returns a list of cycles which form a basis of the cycle space
2795
of ``self``.
2796
2797
A basis of cycles of a graph is a minimal collection of cycles
2798
(considered as sets of edges) such that the edge set of any
2799
cycle in the graph can be written as a `Z/2Z` sum of the
2800
cycles in the basis.
2801
2802
OUTPUT:
2803
2804
A list of lists, each of them representing the vertices of a
2805
cycle in a basis.
2806
2807
ALGORITHM:
2808
2809
Uses the NetworkX library.
2810
2811
EXAMPLE:
2812
2813
A cycle basis in Petersen's Graph ::
2814
2815
sage: g = graphs.PetersenGraph()
2816
sage: g.cycle_basis()
2817
[[1, 2, 7, 5, 0], [8, 3, 2, 7, 5], [4, 3, 2, 7, 5, 0], [4, 9, 7, 5, 0], [8, 6, 9, 7, 5], [1, 6, 9, 7, 5, 0]]
2818
2819
Checking the given cycles are algebraically free::
2820
2821
sage: g = graphs.RandomGNP(30,.4)
2822
sage: basis = g.cycle_basis()
2823
2824
Building the space of (directed) edges over `Z/2Z`. On the way,
2825
building a dictionary associating an unique vector to each
2826
undirected edge::
2827
2828
sage: m = g.size()
2829
sage: edge_space = VectorSpace(FiniteField(2),m)
2830
sage: edge_vector = dict( zip( g.edges(labels = False), edge_space.basis() ) )
2831
sage: for (u,v),vec in edge_vector.items():
2832
... edge_vector[(v,u)] = vec
2833
2834
Defining a lambda function associating a vector to the
2835
vertices of a cycle::
2836
2837
sage: vertices_to_edges = lambda x : zip( x, x[1:] + [x[0]] )
2838
sage: cycle_to_vector = lambda x : sum( edge_vector[e] for e in vertices_to_edges(x) )
2839
2840
Finally checking the cycles are a free set::
2841
2842
sage: basis_as_vectors = map( cycle_to_vector, basis )
2843
sage: edge_space.span(basis_as_vectors).rank() == len(basis)
2844
True
2845
"""
2846
2847
import networkx
2848
return networkx.cycle_basis(self.networkx_graph(copy=False))
2849
2850
def minimum_outdegree_orientation(self, use_edge_labels=False, solver=None, verbose=0):
2851
r"""
2852
Returns an orientation of ``self`` with the smallest possible maximum
2853
outdegree.
2854
2855
Given a Graph `G`, is is polynomial to compute an orientation
2856
`D` of the edges of `G` such that the maximum out-degree in
2857
`D` is minimized. This problem, though, is NP-complete in the
2858
weighted case [AMOZ06]_.
2859
2860
INPUT:
2861
2862
- ``use_edge_labels`` -- boolean (default: ``False``)
2863
2864
- When set to ``True``, uses edge labels as weights to
2865
compute the orientation and assumes a weight of `1`
2866
when there is no value available for a given edge.
2867
2868
- When set to ``False`` (default), gives a weight of 1
2869
to all the edges.
2870
2871
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
2872
solver to be used. If set to ``None``, the default one is used. For
2873
more information on LP solvers and which default solver is used, see
2874
the method
2875
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
2876
of the class
2877
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
2878
2879
- ``verbose`` -- integer (default: ``0``). Sets the level of
2880
verbosity. Set to 0 by default, which means quiet.
2881
2882
EXAMPLE:
2883
2884
Given a complete bipartite graph `K_{n,m}`, the maximum out-degree
2885
of an optimal orientation is `\left\lceil \frac {nm} {n+m}\right\rceil`::
2886
2887
sage: g = graphs.CompleteBipartiteGraph(3,4)
2888
sage: o = g.minimum_outdegree_orientation()
2889
sage: max(o.out_degree()) == ceil((4*3)/(3+4))
2890
True
2891
2892
REFERENCES:
2893
2894
.. [AMOZ06] Asahiro, Y. and Miyano, E. and Ono, H. and Zenmyo, K.
2895
Graph orientation algorithms to minimize the maximum outdegree
2896
Proceedings of the 12th Computing: The Australasian Theory Symposium
2897
Volume 51, page 20
2898
Australian Computer Society, Inc. 2006
2899
"""
2900
2901
if self.is_directed():
2902
raise ValueError("Cannot compute an orientation of a DiGraph. "+\
2903
"Please convert it to a Graph if you really mean it.")
2904
2905
if use_edge_labels:
2906
from sage.rings.real_mpfr import RR
2907
weight = lambda u,v : self.edge_label(u,v) if self.edge_label(u,v) in RR else 1
2908
else:
2909
weight = lambda u,v : 1
2910
2911
from sage.numerical.mip import MixedIntegerLinearProgram, Sum
2912
2913
p = MixedIntegerLinearProgram(maximization=False, solver=solver)
2914
2915
# The orientation of an edge is boolean
2916
# and indicates whether the edge uv
2917
# with u<v goes from u to v ( equal to 0 )
2918
# or from v to u ( equal to 1)
2919
orientation = p.new_variable(dim=2)
2920
2921
degree = p.new_variable()
2922
2923
# Whether an edge adjacent to a vertex u counts
2924
# positively or negatively
2925
outgoing = lambda u,v,variable : (1-variable) if u>v else variable
2926
2927
for u in self:
2928
p.add_constraint(Sum([weight(u,v)*outgoing(u,v,orientation[min(u,v)][max(u,v)]) for v in self.neighbors(u)])-degree['max'],max=0)
2929
2930
p.set_objective(degree['max'])
2931
2932
p.set_binary(orientation)
2933
2934
p.solve(log=verbose)
2935
2936
orientation = p.get_values(orientation)
2937
2938
# All the edges from self are doubled in O
2939
# ( one in each direction )
2940
from sage.graphs.digraph import DiGraph
2941
O = DiGraph(self)
2942
2943
# Builds the list of edges that should be removed
2944
edges=[]
2945
2946
for u,v in self.edge_iterator(labels=None):
2947
# assumes u<v
2948
if u>v:
2949
u,v=v,u
2950
2951
if orientation[min(u,v)][max(u,v)] == 1:
2952
edges.append((max(u,v),min(u,v)))
2953
else:
2954
edges.append((min(u,v),max(u,v)))
2955
2956
O.delete_edges(edges)
2957
2958
return O
2959
2960
### Planarity
2961
2962
def is_planar(self, on_embedding=None, kuratowski=False, set_embedding=False, set_pos=False):
2963
"""
2964
Returns True if the graph is planar, and False otherwise. This
2965
wraps the reference implementation provided by John Boyer of the
2966
linear time planarity algorithm by edge addition due to Boyer
2967
Myrvold. (See reference code in graphs.planarity).
2968
2969
Note - the argument on_embedding takes precedence over
2970
set_embedding. This means that only the on_embedding
2971
combinatorial embedding will be tested for planarity and no
2972
_embedding attribute will be set as a result of this function
2973
call, unless on_embedding is None.
2974
2975
REFERENCE:
2976
2977
- [1] John M. Boyer and Wendy J. Myrvold, On the Cutting Edge:
2978
Simplified O(n) Planarity by Edge Addition. Journal of Graph
2979
Algorithms and Applications, Vol. 8, No. 3, pp. 241-273,
2980
2004.
2981
2982
INPUT:
2983
2984
2985
- ``kuratowski`` - returns a tuple with boolean as
2986
first entry. If the graph is nonplanar, will return the Kuratowski
2987
subgraph or minor as the second tuple entry. If the graph is
2988
planar, returns None as the second entry.
2989
2990
- ``on_embedding`` - the embedding dictionary to test
2991
planarity on. (i.e.: will return True or False only for the given
2992
embedding.)
2993
2994
- ``set_embedding`` - whether or not to set the
2995
instance field variable that contains a combinatorial embedding
2996
(clockwise ordering of neighbors at each vertex). This value will
2997
only be set if a planar embedding is found. It is stored as a
2998
Python dict: v1: [n1,n2,n3] where v1 is a vertex and n1,n2,n3 are
2999
its neighbors.
3000
3001
- ``set_pos`` - whether or not to set the position
3002
dictionary (for plotting) to reflect the combinatorial embedding.
3003
Note that this value will default to False if set_emb is set to
3004
False. Also, the position dictionary will only be updated if a
3005
planar embedding is found.
3006
3007
3008
EXAMPLES::
3009
3010
sage: g = graphs.CubeGraph(4)
3011
sage: g.is_planar()
3012
False
3013
3014
::
3015
3016
sage: g = graphs.CircularLadderGraph(4)
3017
sage: g.is_planar(set_embedding=True)
3018
True
3019
sage: g.get_embedding()
3020
{0: [1, 4, 3],
3021
1: [2, 5, 0],
3022
2: [3, 6, 1],
3023
3: [0, 7, 2],
3024
4: [0, 5, 7],
3025
5: [1, 6, 4],
3026
6: [2, 7, 5],
3027
7: [4, 6, 3]}
3028
3029
::
3030
3031
sage: g = graphs.PetersenGraph()
3032
sage: (g.is_planar(kuratowski=True))[1].adjacency_matrix()
3033
[0 1 0 0 0 1 0 0 0]
3034
[1 0 1 0 0 0 1 0 0]
3035
[0 1 0 1 0 0 0 1 0]
3036
[0 0 1 0 0 0 0 0 1]
3037
[0 0 0 0 0 0 1 1 0]
3038
[1 0 0 0 0 0 0 1 1]
3039
[0 1 0 0 1 0 0 0 1]
3040
[0 0 1 0 1 1 0 0 0]
3041
[0 0 0 1 0 1 1 0 0]
3042
3043
::
3044
3045
sage: k43 = graphs.CompleteBipartiteGraph(4,3)
3046
sage: result = k43.is_planar(kuratowski=True); result
3047
(False, Graph on 6 vertices)
3048
sage: result[1].is_isomorphic(graphs.CompleteBipartiteGraph(3,3))
3049
True
3050
3051
Multi-edged and looped graphs are partially supported::
3052
3053
sage: G = Graph({0:[1,1]}, multiedges=True)
3054
sage: G.is_planar()
3055
True
3056
sage: G.is_planar(on_embedding={})
3057
Traceback (most recent call last):
3058
...
3059
NotImplementedError: Cannot compute with embeddings of multiple-edged or looped graphs.
3060
sage: G.is_planar(set_pos=True)
3061
Traceback (most recent call last):
3062
...
3063
NotImplementedError: Cannot compute with embeddings of multiple-edged or looped graphs.
3064
sage: G.is_planar(set_embedding=True)
3065
Traceback (most recent call last):
3066
...
3067
NotImplementedError: Cannot compute with embeddings of multiple-edged or looped graphs.
3068
sage: G.is_planar(kuratowski=True)
3069
(True, None)
3070
3071
::
3072
3073
sage: G = graphs.CompleteGraph(5)
3074
sage: G = Graph(G, multiedges=True)
3075
sage: G.add_edge(0,1)
3076
sage: G.is_planar()
3077
False
3078
sage: b,k = G.is_planar(kuratowski=True)
3079
sage: b
3080
False
3081
sage: k.vertices()
3082
[0, 1, 2, 3, 4]
3083
3084
"""
3085
if self.has_multiple_edges() or self.has_loops():
3086
if set_embedding or (on_embedding is not None) or set_pos:
3087
raise NotImplementedError("Cannot compute with embeddings of multiple-edged or looped graphs.")
3088
else:
3089
return self.to_simple().is_planar(kuratowski=kuratowski)
3090
if on_embedding:
3091
if self.check_embedding_validity(on_embedding):
3092
return (0 == self.genus(minimal=False,set_embedding=False,on_embedding=on_embedding))
3093
else:
3094
raise Exception('on_embedding is not a valid embedding for %s.'%self)
3095
else:
3096
from sage.graphs.planarity import is_planar
3097
G = self.to_undirected()
3098
planar = is_planar(G,kuratowski=kuratowski,set_pos=set_pos,set_embedding=set_embedding)
3099
if kuratowski:
3100
bool_result = planar[0]
3101
else:
3102
bool_result = planar
3103
if bool_result:
3104
if set_pos:
3105
self._pos = G._pos
3106
if set_embedding:
3107
self._embedding = G._embedding
3108
return planar
3109
3110
def is_circular_planar(self, ordered=True, on_embedding=None, kuratowski=False, set_embedding=False, set_pos=False):
3111
"""
3112
Tests whether the graph is circular planar (outerplanar)
3113
3114
A graph (with nonempty boundary) is circular planar if it has a
3115
planar embedding in which all boundary vertices can be drawn in
3116
order on a disc boundary, with all the interior vertices drawn
3117
inside the disc.
3118
3119
Returns True if the graph is circular planar, and False if it is
3120
not. If kuratowski is set to True, then this function will return a
3121
tuple, with boolean first entry and second entry the Kuratowski
3122
subgraph or minor isolated by the Boyer-Myrvold algorithm. Note
3123
that this graph might contain a vertex or edges that were not in
3124
the initial graph. These would be elements referred to below as
3125
parts of the wheel and the star, which were added to the graph to
3126
require that the boundary can be drawn on the boundary of a disc,
3127
with all other vertices drawn inside (and no edge crossings). For
3128
more information, refer to reference [2].
3129
3130
This is a linear time algorithm to test for circular planarity. It
3131
relies on the edge-addition planarity algorithm due to
3132
Boyer-Myrvold. We accomplish linear time for circular planarity by
3133
modifying the graph before running the general planarity
3134
algorithm.
3135
3136
REFERENCE:
3137
3138
- [1] John M. Boyer and Wendy J. Myrvold, On the Cutting Edge:
3139
Simplified O(n) Planarity by Edge Addition. Journal of Graph
3140
Algorithms and Applications, Vol. 8, No. 3, pp. 241-273,
3141
2004.
3142
3143
- [2] Kirkman, Emily A. O(n) Circular Planarity
3144
Testing. [Online] Available: soon!
3145
3146
INPUT:
3147
3148
- ``ordered`` - whether or not to consider the order
3149
of the boundary (set ordered=False to see if there is any possible
3150
boundary order that will satisfy circular planarity)
3151
3152
- ``kuratowski`` - if set to True, returns a tuple
3153
with boolean first entry and the Kuratowski subgraph or minor as
3154
the second entry. See notes above.
3155
3156
- ``on_embedding`` - the embedding dictionary to test
3157
planarity on. (i.e.: will return True or False only for the given
3158
embedding.)
3159
3160
- ``set_embedding`` - whether or not to set the
3161
instance field variable that contains a combinatorial embedding
3162
(clockwise ordering of neighbors at each vertex). This value will
3163
only be set if a circular planar embedding is found. It is stored
3164
as a Python dict: v1: [n1,n2,n3] where v1 is a vertex and n1,n2,n3
3165
are its neighbors.
3166
3167
- ``set_pos`` - whether or not to set the position
3168
dictionary (for plotting) to reflect the combinatorial embedding.
3169
Note that this value will default to False if set_emb is set to
3170
False. Also, the position dictionary will only be updated if a
3171
circular planar embedding is found.
3172
3173
EXAMPLES::
3174
3175
sage: g439 = Graph({1:[5,7], 2:[5,6], 3:[6,7], 4:[5,6,7]})
3176
sage: g439.set_boundary([1,2,3,4])
3177
sage: g439.show(figsize=[2,2], vertex_labels=True, vertex_size=175)
3178
sage: g439.is_circular_planar()
3179
False
3180
sage: g439.is_circular_planar(kuratowski=True)
3181
(False, Graph on 7 vertices)
3182
sage: g439.set_boundary([1,2,3])
3183
sage: g439.is_circular_planar(set_embedding=True, set_pos=False)
3184
True
3185
sage: g439.is_circular_planar(kuratowski=True)
3186
(True, None)
3187
sage: g439.get_embedding()
3188
{1: [7, 5],
3189
2: [5, 6],
3190
3: [6, 7],
3191
4: [7, 6, 5],
3192
5: [4, 2, 1],
3193
6: [4, 3, 2],
3194
7: [3, 4, 1]}
3195
3196
Order matters::
3197
3198
sage: K23 = graphs.CompleteBipartiteGraph(2,3)
3199
sage: K23.set_boundary([0,1,2,3])
3200
sage: K23.is_circular_planar()
3201
False
3202
sage: K23.is_circular_planar(ordered=False)
3203
True
3204
sage: K23.set_boundary([0,2,1,3]) # Diff Order!
3205
sage: K23.is_circular_planar(set_embedding=True)
3206
True
3207
3208
For graphs without a boundary, circular planar is the same as planar::
3209
3210
sage: g = graphs.KrackhardtKiteGraph()
3211
sage: g.is_circular_planar()
3212
True
3213
3214
"""
3215
boundary = self.get_boundary()
3216
if not boundary:
3217
return self.is_planar(on_embedding, kuratowski, set_embedding, set_pos)
3218
3219
from sage.graphs.planarity import is_planar
3220
graph = self.to_undirected()
3221
if hasattr(graph, '_embedding'):
3222
del(graph._embedding)
3223
3224
extra = 0
3225
while graph.has_vertex(extra):
3226
extra=extra+1
3227
graph.add_vertex(extra)
3228
3229
for vertex in boundary:
3230
graph.add_edge(vertex,extra)
3231
3232
extra_edges = []
3233
if ordered: # WHEEL
3234
for i in range(len(boundary)-1):
3235
if not graph.has_edge(boundary[i],boundary[i+1]):
3236
graph.add_edge(boundary[i],boundary[i+1])
3237
extra_edges.append((boundary[i],boundary[i+1]))
3238
if not graph.has_edge(boundary[-1],boundary[0]):
3239
graph.add_edge(boundary[-1],boundary[0])
3240
extra_edges.append((boundary[-1],boundary[0]))
3241
# else STAR (empty list of extra edges)
3242
3243
result = is_planar(graph,kuratowski=kuratowski,set_embedding=set_embedding,circular=True)
3244
3245
if kuratowski:
3246
bool_result = result[0]
3247
else:
3248
bool_result = result
3249
3250
if bool_result:
3251
graph.delete_vertex(extra)
3252
graph.delete_edges(extra_edges)
3253
3254
if hasattr(graph,'_embedding'):
3255
# strip the embedding to fit original graph
3256
for u,v in extra_edges:
3257
graph._embedding[u].pop(graph._embedding[u].index(v))
3258
graph._embedding[v].pop(graph._embedding[v].index(u))
3259
for w in boundary:
3260
graph._embedding[w].pop(graph._embedding[w].index(extra))
3261
3262
if set_embedding:
3263
self._embedding = graph._embedding
3264
3265
if (set_pos and set_embedding):
3266
self.set_planar_positions()
3267
return result
3268
3269
# TODO: rename into _layout_planar
3270
def set_planar_positions(self, test = False, **layout_options):
3271
"""
3272
Compute a planar layout for self using Schnyder's algorithm,
3273
and save it as default layout.
3274
3275
EXAMPLES::
3276
3277
sage: g = graphs.CycleGraph(7)
3278
sage: g.set_planar_positions(test=True)
3279
True
3280
3281
This method is deprecated. Please use instead:
3282
3283
sage: g.layout(layout = "planar", save_pos = True)
3284
{0: [1, 1], 1: [2, 2], 2: [3, 2], 3: [1, 4], 4: [5, 1], 5: [0, 5], 6: [1, 0]}
3285
"""
3286
self.layout(layout = "planar", save_pos = True, test = test, **layout_options)
3287
if test: # Optional error-checking, ( looking for edge-crossings O(n^2) ).
3288
return self.is_drawn_free_of_edge_crossings() # returns true if tests pass
3289
else:
3290
return
3291
3292
def layout_planar(self, set_embedding=False, on_embedding=None, external_face=None, test=False, circular=False, **options):
3293
"""
3294
Uses Schnyder's algorithm to compute a planar layout for self,
3295
raising an error if self is not planar.
3296
3297
INPUT:
3298
3299
- ``set_embedding`` - if True, sets the combinatorial
3300
embedding used (see self.get_embedding())
3301
3302
- ``on_embedding`` - dict: provide a combinatorial
3303
embedding
3304
3305
- ``external_face`` - ignored
3306
3307
- ``test`` - if True, perform sanity tests along the way
3308
3309
- ``circular`` - ignored
3310
3311
3312
EXAMPLES::
3313
3314
sage: g = graphs.PathGraph(10)
3315
sage: g.set_planar_positions(test=True)
3316
True
3317
sage: g = graphs.BalancedTree(3,4)
3318
sage: g.set_planar_positions(test=True)
3319
True
3320
sage: g = graphs.CycleGraph(7)
3321
sage: g.set_planar_positions(test=True)
3322
True
3323
sage: g = graphs.CompleteGraph(5)
3324
sage: g.set_planar_positions(test=True,set_embedding=True)
3325
Traceback (most recent call last):
3326
...
3327
Exception: Complete graph is not a planar graph.
3328
"""
3329
from sage.graphs.schnyder import _triangulate, _normal_label, _realizer, _compute_coordinates
3330
3331
G = self.to_undirected()
3332
try:
3333
G._embedding = self._embedding
3334
except AttributeError:
3335
pass
3336
embedding_copy = None
3337
if set_embedding:
3338
if not (G.is_planar(set_embedding=True)):
3339
raise Exception('%s is not a planar graph.'%self)
3340
embedding_copy = G._embedding
3341
else:
3342
if on_embedding is not None:
3343
if G.check_embedding_validity(on_embedding):
3344
if not (G.is_planar(on_embedding=on_embedding)):
3345
raise Exception( 'Provided embedding is not a planar embedding for %s.'%self )
3346
else:
3347
raise Exception('Provided embedding is not a valid embedding for %s. Try putting set_embedding=True.'%self)
3348
else:
3349
if hasattr(G,'_embedding'):
3350
if G.check_embedding_validity():
3351
if not (G.is_planar(on_embedding=G._embedding)):
3352
raise Exception('%s has nonplanar _embedding attribute. Try putting set_embedding=True.'%self)
3353
embedding_copy = G._embedding
3354
else:
3355
raise Exception('Provided embedding is not a valid embedding for %s. Try putting set_embedding=True.'%self)
3356
else:
3357
G.is_planar(set_embedding=True)
3358
3359
# The following is what was breaking the code. It is where we were specifying the external
3360
# face ahead of time. This is definitely a TODO:
3361
#
3362
# Running is_planar(set_embedding=True) has set attribute self._embedding
3363
#if external_face is None:
3364
# faces = trace_faces( self, self._embedding )
3365
# faces.sort(key=len)
3366
# external_face = faces[-1]
3367
3368
#n = len(external_face)
3369
#other_added_edges = []
3370
#if n > 3:
3371
# v1, v2, v3 = external_face[0][0], external_face[int(n/3)][0], external_face[int(2*n/3)][0]
3372
# if not self.has_edge( (v1,v2) ):
3373
# self.add_edge( (v1, v2) )
3374
# other_added_edges.append( (v1, v2) )
3375
# if not self.has_edge( (v2,v3) ):
3376
# self.add_edge( (v2, v3) )
3377
# other_added_edges.append( (v2, v3) )
3378
# if not self.has_edge( (v3,v1) ):
3379
# self.add_edge( (v3, v1) )
3380
# other_added_edges.append( (v3, v1) )
3381
# if not self.is_planar(set_embedding=True): # get new combinatorial embedding (with added edges)
3382
# raise Exception('Modified graph %s is not planar. Try specifying an external face.'%self)
3383
3384
# Triangulate the graph
3385
extra_edges = _triangulate( G, G._embedding)
3386
3387
# Optional error-checking
3388
if test:
3389
G.is_planar(set_embedding=True) # to get new embedding
3390
test_faces = G.trace_faces(G._embedding)
3391
for face in test_faces:
3392
if len(face) != 3:
3393
raise Exception('BUG: Triangulation returned face: %s'%face)
3394
3395
G.is_planar(set_embedding=True)
3396
faces = G.trace_faces(G._embedding)
3397
# Assign a normal label to the graph
3398
label = _normal_label( G, G._embedding, faces[0] )
3399
3400
# Get dictionary of tree nodes from the realizer
3401
tree_nodes = _realizer( G, label)
3402
3403
# Compute the coordinates and store in position dictionary (attr self._pos)
3404
_compute_coordinates( G, tree_nodes )
3405
3406
# Delete all the edges added to the graph
3407
#G.delete_edges( extra_edges )
3408
#self.delete_edges( other_added_edges )
3409
3410
if embedding_copy is not None:
3411
self._embedding = embedding_copy
3412
3413
return G._pos
3414
3415
def is_drawn_free_of_edge_crossings(self):
3416
"""
3417
Returns True is the position dictionary for this graph is set and
3418
that position dictionary gives a planar embedding.
3419
3420
This simply checks all pairs of edges that don't share a vertex to
3421
make sure that they don't intersect.
3422
3423
.. note::
3424
3425
This function require that _pos attribute is set. (Returns
3426
False otherwise.)
3427
3428
EXAMPLES::
3429
3430
sage: D = graphs.DodecahedralGraph()
3431
sage: D.set_planar_positions()
3432
sage: D.is_drawn_free_of_edge_crossings()
3433
True
3434
"""
3435
if self._pos is None:
3436
return False
3437
3438
G = self.to_undirected()
3439
for edge1 in G.edges(labels = False):
3440
for edge2 in G.edges(labels = False):
3441
if edge1[0] == edge2[0] or edge1[0] == edge2[1] or edge1[1] == edge2[0] or edge1[1] == edge2[1]:
3442
continue
3443
p1, p2 = self._pos[edge1[0]], self._pos[edge1[1]]
3444
dy = Rational(p2[1] - p1[1])
3445
dx = Rational(p2[0] - p1[0])
3446
q1, q2 = self._pos[edge2[0]], self._pos[edge2[1]]
3447
db = Rational(q2[1] - q1[1])
3448
da = Rational(q2[0] - q1[0])
3449
if(da * dy == db * dx):
3450
if dx != 0:
3451
t1 = Rational(q1[0] - p1[0])/dx
3452
t2 = Rational(q2[0] - p1[0])/dx
3453
if (0 <= t1 and t1 <= 1) or (0 <= t2 and t2 <= 1):
3454
if p1[1] + t1 * dy == q1[1] or p1[1] + t2 * dy == q2[1]:
3455
return False
3456
else:
3457
t1 = Rational(q1[1] - p1[1])/dy
3458
t2 = Rational(q2[1] - p1[1])/dy
3459
if (0 <= t1 and t1 <= 1) or (0 <= t2 and t2 <= 1):
3460
if p1[0] + t1 * dx == q1[0] or p1[0] + t2 * dx == q2[0]:
3461
return False
3462
else:
3463
s = (dx * Rational(q1[1] - p1[1]) + dy * Rational(p1[0] - q1[0])) / (da * dy - db * dx)
3464
t = (da * Rational(p1[1] - q1[1]) + db * Rational(q1[0] - p1[0])) / (db * dx - da * dy)
3465
3466
if s >= 0 and s <= 1 and t >= 0 and t <= 1:
3467
print 'fail on', p1, p2, ' : ',q1, q2
3468
print edge1, edge2
3469
return False
3470
return True
3471
3472
def genus(self, set_embedding=True, on_embedding=None, minimal=True, maximal=False, circular=False, ordered=True):
3473
"""
3474
Returns the minimal genus of the graph. The genus of a compact
3475
surface is the number of handles it has. The genus of a graph is
3476
the minimal genus of the surface it can be embedded into.
3477
3478
Note - This function uses Euler's formula and thus it is necessary
3479
to consider only connected graphs.
3480
3481
INPUT:
3482
3483
3484
- ``set_embedding (boolean)`` - whether or not to
3485
store an embedding attribute of the computed (minimal) genus of the
3486
graph. (Default is True).
3487
3488
- ``on_embedding (dict)`` - a combinatorial embedding
3489
to compute the genus of the graph on. Note that this must be a
3490
valid embedding for the graph. The dictionary structure is given
3491
by: vertex1: [neighbor1, neighbor2, neighbor3], vertex2: [neighbor]
3492
where there is a key for each vertex in the graph and a (clockwise)
3493
ordered list of each vertex's neighbors as values. on_embedding
3494
takes precedence over a stored _embedding attribute if minimal is
3495
set to False. Note that as a shortcut, the user can enter
3496
on_embedding=True to compute the genus on the current _embedding
3497
attribute. (see eg's.)
3498
3499
- ``minimal (boolean)`` - whether or not to compute
3500
the minimal genus of the graph (i.e., testing all embeddings). If
3501
minimal is False, then either maximal must be True or on_embedding
3502
must not be None. If on_embedding is not None, it will take
3503
priority over minimal. Similarly, if maximal is True, it will take
3504
priority over minimal.
3505
3506
- ``maximal (boolean)`` - whether or not to compute
3507
the maximal genus of the graph (i.e., testing all embeddings). If
3508
maximal is False, then either minimal must be True or on_embedding
3509
must not be None. If on_embedding is not None, it will take
3510
priority over maximal. However, maximal takes priority over the
3511
default minimal.
3512
3513
- ``circular (boolean)`` - whether or not to compute
3514
the genus preserving a planar embedding of the boundary. (Default
3515
is False). If circular is True, on_embedding is not a valid
3516
option.
3517
3518
- ``ordered (boolean)`` - if circular is True, then
3519
whether or not the boundary order may be permuted. (Default is
3520
True, which means the boundary order is preserved.)
3521
3522
EXAMPLES::
3523
3524
sage: g = graphs.PetersenGraph()
3525
sage: g.genus() # tests for minimal genus by default
3526
1
3527
sage: g.genus(on_embedding=True, maximal=True) # on_embedding overrides minimal and maximal arguments
3528
1
3529
sage: g.genus(maximal=True) # setting maximal to True overrides default minimal=True
3530
3
3531
sage: g.genus(on_embedding=g.get_embedding()) # can also send a valid combinatorial embedding dict
3532
3
3533
sage: (graphs.CubeGraph(3)).genus()
3534
0
3535
sage: K23 = graphs.CompleteBipartiteGraph(2,3)
3536
sage: K23.genus()
3537
0
3538
sage: K33 = graphs.CompleteBipartiteGraph(3,3)
3539
sage: K33.genus()
3540
1
3541
3542
Using the circular argument, we can compute the minimal genus
3543
preserving a planar, ordered boundary::
3544
3545
sage: cube = graphs.CubeGraph(2)
3546
sage: cube.set_boundary(['01','10'])
3547
sage: cube.genus()
3548
0
3549
sage: cube.is_circular_planar()
3550
True
3551
sage: cube.genus(circular=True)
3552
0
3553
sage: cube.genus(circular=True, on_embedding=True)
3554
0
3555
sage: cube.genus(circular=True, maximal=True)
3556
Traceback (most recent call last):
3557
...
3558
NotImplementedError: Cannot compute the maximal genus of a genus respecting a boundary.
3559
3560
Note: not everything works for multigraphs, looped graphs or digraphs. But the
3561
minimal genus is ultimately computable for every connected graph -- but the
3562
embedding we obtain for the simple graph can't be easily converted to an
3563
embedding of a non-simple graph. Also, the maximal genus of a multigraph does
3564
not trivially correspond to that of its simple graph.
3565
3566
sage: G = DiGraph({ 0 : [0,1,1,1], 1 : [2,2,3,3], 2 : [1,3,3], 3:[0,3]})
3567
sage: G.genus()
3568
Traceback (most recent call last):
3569
...
3570
NotImplementedError: Can't work with embeddings of non-simple graphs
3571
sage: G.to_simple().genus()
3572
0
3573
sage: G.genus(set_embedding=False)
3574
0
3575
sage: G.genus(maximal=True, set_embedding=False)
3576
Traceback (most recent call last):
3577
...
3578
NotImplementedError: Can't compute the maximal genus of a graph with loops or multiple edges
3579
3580
3581
We break graphs with cut vertices into their blocks, which greatly speeds up
3582
computation of minimal genus. This is not implemented for maximal genus.
3583
3584
sage: K5 = graphs.CompleteGraph(5)
3585
sage: G = K5.copy()
3586
sage: s = 4
3587
sage: for i in range(1,100):
3588
... k = K5.relabel(range(s,s+5),inplace=False)
3589
... G.add_edges(k.edges())
3590
... s += 4
3591
...
3592
sage: G.genus()
3593
100
3594
3595
"""
3596
if not self.is_connected():
3597
raise TypeError("Graph must be connected to use Euler's Formula to compute minimal genus.")
3598
3599
G = self.to_simple()
3600
verts = G.order()
3601
edges = G.size()
3602
3603
if maximal:
3604
minimal = False
3605
3606
if circular:
3607
if maximal:
3608
raise NotImplementedError, "Cannot compute the maximal genus of a genus respecting a boundary."
3609
boundary = G.get_boundary()
3610
if hasattr(G, '_embedding'):
3611
del(G._embedding)
3612
3613
extra = 0
3614
while G.has_vertex(extra):
3615
extra=extra+1
3616
G.add_vertex(extra)
3617
verts += 1
3618
3619
for vertex in boundary:
3620
G.add_edge(vertex,extra)
3621
3622
extra_edges = []
3623
if ordered: # WHEEL
3624
for i in range(len(boundary)-1):
3625
if not G.has_edge(boundary[i],boundary[i+1]):
3626
G.add_edge(boundary[i],boundary[i+1])
3627
extra_edges.append((boundary[i],boundary[i+1]))
3628
if not G.has_edge(boundary[-1],boundary[0]):
3629
G.add_edge(boundary[-1],boundary[0])
3630
extra_edges.append((boundary[-1],boundary[0]))
3631
# else STAR (empty list of extra edges)
3632
3633
edges = G.size()
3634
3635
if on_embedding is not None:
3636
if self.has_loops() or self.is_directed() or self.has_multiple_edges():
3637
raise NotImplementedError, "Can't work with embeddings of non-simple graphs"
3638
if on_embedding: #i.e., if on_embedding True (returns False if on_embedding is of type dict)
3639
if not hasattr(self,'_embedding'):
3640
raise Exception("Graph must have attribute _embedding set to compute current (embedded) genus.")
3641
faces = len(self.trace_faces(self._embedding))
3642
return (2-verts+edges-faces)/2
3643
else: # compute genus on the provided dict
3644
faces = len(self.trace_faces(on_embedding))
3645
return (2-verts+edges-faces)/2
3646
else: # then compute either maximal or minimal genus of all embeddings
3647
import genus
3648
3649
if set_embedding:
3650
if self.has_loops() or self.is_directed() or self.has_multiple_edges():
3651
raise NotImplementedError, "Can't work with embeddings of non-simple graphs"
3652
if minimal:
3653
B,C = G.blocks_and_cut_vertices()
3654
embedding = {}
3655
g = 0
3656
for block in B:
3657
H = G.subgraph(block)
3658
g += genus.simple_connected_graph_genus(H, set_embedding = True, check = False, minimal = True)
3659
emb = H.get_embedding()
3660
for v in emb:
3661
if embedding.has_key(v):
3662
embedding[v] += emb[v]
3663
else:
3664
embedding[v] = emb[v]
3665
self._embedding = embedding
3666
else:
3667
g = genus.simple_connected_graph_genus(G, set_embedding = True, check = False, minimal = minimal)
3668
self._embedding = G._embedding
3669
return g
3670
else:
3671
if maximal and (self.has_multiple_edges() or self.has_loops()):
3672
raise NotImplementedError, "Can't compute the maximal genus of a graph with loops or multiple edges"
3673
if minimal:
3674
B,C = G.blocks_and_cut_vertices()
3675
g = 0
3676
for block in B:
3677
H = G.subgraph(block)
3678
g += genus.simple_connected_graph_genus(H, set_embedding = False, check = False, minimal = True)
3679
return g
3680
else:
3681
return genus.simple_connected_graph_genus(G, set_embedding = False, check=False, minimal=minimal)
3682
3683
3684
3685
def trace_faces(self, comb_emb):
3686
"""
3687
A helper function for finding the genus of a graph. Given a graph
3688
and a combinatorial embedding (rot_sys), this function will
3689
compute the faces (returned as a list of lists of edges (tuples) of
3690
the particular embedding.
3691
3692
Note - rot_sys is an ordered list based on the hash order of the
3693
vertices of graph. To avoid confusion, it might be best to set the
3694
rot_sys based on a 'nice_copy' of the graph.
3695
3696
INPUT:
3697
3698
3699
- ``comb_emb`` - a combinatorial embedding
3700
dictionary. Format: v1:[v2,v3], v2:[v1], v3:[v1] (clockwise
3701
ordering of neighbors at each vertex.)
3702
3703
3704
EXAMPLES::
3705
3706
sage: T = graphs.TetrahedralGraph()
3707
sage: T.trace_faces({0: [1, 3, 2], 1: [0, 2, 3], 2: [0, 3, 1], 3: [0, 1, 2]})
3708
[[(0, 1), (1, 2), (2, 0)],
3709
[(3, 2), (2, 1), (1, 3)],
3710
[(2, 3), (3, 0), (0, 2)],
3711
[(0, 3), (3, 1), (1, 0)]]
3712
"""
3713
from sage.sets.set import Set
3714
3715
# Establish set of possible edges
3716
edgeset = Set([])
3717
for edge in self.to_undirected().edges():
3718
edgeset = edgeset.union(Set([(edge[0],edge[1]),(edge[1],edge[0])]))
3719
3720
# Storage for face paths
3721
faces = []
3722
path = []
3723
for edge in edgeset:
3724
path.append(edge)
3725
edgeset -= Set([edge])
3726
break # (Only one iteration)
3727
3728
# Trace faces
3729
while (len(edgeset) > 0):
3730
neighbors = comb_emb[path[-1][-1]]
3731
next_node = neighbors[(neighbors.index(path[-1][-2])+1)%(len(neighbors))]
3732
tup = (path[-1][-1],next_node)
3733
if tup == path[0]:
3734
faces.append(path)
3735
path = []
3736
for edge in edgeset:
3737
path.append(edge)
3738
edgeset -= Set([edge])
3739
break # (Only one iteration)
3740
else:
3741
path.append(tup)
3742
edgeset -= Set([tup])
3743
if (len(path) != 0): faces.append(path)
3744
return faces
3745
3746
### Connectivity
3747
3748
def is_connected(self):
3749
"""
3750
Indicates whether the (di)graph is connected. Note that in a graph,
3751
path connected is equivalent to connected.
3752
3753
EXAMPLES::
3754
3755
sage: G = Graph( { 0 : [1, 2], 1 : [2], 3 : [4, 5], 4 : [5] } )
3756
sage: G.is_connected()
3757
False
3758
sage: G.add_edge(0,3)
3759
sage: G.is_connected()
3760
True
3761
sage: D = DiGraph( { 0 : [1, 2], 1 : [2], 3 : [4, 5], 4 : [5] } )
3762
sage: D.is_connected()
3763
False
3764
sage: D.add_edge(0,3)
3765
sage: D.is_connected()
3766
True
3767
sage: D = DiGraph({1:[0], 2:[0]})
3768
sage: D.is_connected()
3769
True
3770
"""
3771
if self.order() == 0:
3772
return True
3773
3774
try:
3775
return self._backend.is_connected()
3776
except AttributeError:
3777
v = self.vertex_iterator().next()
3778
conn_verts = list(self.depth_first_search(v, ignore_direction=True))
3779
return len(conn_verts) == self.num_verts()
3780
3781
def connected_components(self):
3782
"""
3783
Returns the list of connected components.
3784
3785
Returns a list of lists of vertices, each list representing a
3786
connected component. The list is ordered from largest to smallest
3787
component.
3788
3789
EXAMPLES::
3790
3791
sage: G = Graph( { 0 : [1, 3], 1 : [2], 2 : [3], 4 : [5, 6], 5 : [6] } )
3792
sage: G.connected_components()
3793
[[0, 1, 2, 3], [4, 5, 6]]
3794
sage: D = DiGraph( { 0 : [1, 3], 1 : [2], 2 : [3], 4 : [5, 6], 5 : [6] } )
3795
sage: D.connected_components()
3796
[[0, 1, 2, 3], [4, 5, 6]]
3797
"""
3798
seen = set()
3799
components = []
3800
for v in self:
3801
if v not in seen:
3802
c = self.connected_component_containing_vertex(v)
3803
seen.update(c)
3804
components.append(c)
3805
components.sort(lambda comp1, comp2: cmp(len(comp2), len(comp1)))
3806
return components
3807
3808
def connected_components_number(self):
3809
"""
3810
Returns the number of connected components.
3811
3812
EXAMPLES::
3813
3814
sage: G = Graph( { 0 : [1, 3], 1 : [2], 2 : [3], 4 : [5, 6], 5 : [6] } )
3815
sage: G.connected_components_number()
3816
2
3817
sage: D = DiGraph( { 0 : [1, 3], 1 : [2], 2 : [3], 4 : [5, 6], 5 : [6] } )
3818
sage: D.connected_components_number()
3819
2
3820
"""
3821
return len(self.connected_components())
3822
3823
def connected_components_subgraphs(self):
3824
"""
3825
Returns a list of connected components as graph objects.
3826
3827
EXAMPLES::
3828
3829
sage: G = Graph( { 0 : [1, 3], 1 : [2], 2 : [3], 4 : [5, 6], 5 : [6] } )
3830
sage: L = G.connected_components_subgraphs()
3831
sage: graphs_list.show_graphs(L)
3832
sage: D = DiGraph( { 0 : [1, 3], 1 : [2], 2 : [3], 4 : [5, 6], 5 : [6] } )
3833
sage: L = D.connected_components_subgraphs()
3834
sage: graphs_list.show_graphs(L)
3835
"""
3836
cc = self.connected_components()
3837
list = []
3838
for c in cc:
3839
list.append(self.subgraph(c, inplace=False))
3840
return list
3841
3842
def connected_component_containing_vertex(self, vertex):
3843
"""
3844
Returns a list of the vertices connected to vertex.
3845
3846
EXAMPLES::
3847
3848
sage: G = Graph( { 0 : [1, 3], 1 : [2], 2 : [3], 4 : [5, 6], 5 : [6] } )
3849
sage: G.connected_component_containing_vertex(0)
3850
[0, 1, 2, 3]
3851
sage: D = DiGraph( { 0 : [1, 3], 1 : [2], 2 : [3], 4 : [5, 6], 5 : [6] } )
3852
sage: D.connected_component_containing_vertex(0)
3853
[0, 1, 2, 3]
3854
"""
3855
try:
3856
c = list(self._backend.depth_first_search(vertex, ignore_direction=True))
3857
except AttributeError:
3858
c = list(self.depth_first_search(vertex, ignore_direction=True))
3859
3860
c.sort()
3861
return c
3862
3863
def blocks_and_cut_vertices(self):
3864
"""
3865
Computes the blocks and cut vertices of the graph.
3866
3867
In the case of a digraph, this computation is done on the underlying
3868
graph.
3869
3870
A cut vertex is one whose deletion increases the number of
3871
connected components. A block is a maximal induced subgraph which
3872
itself has no cut vertices. Two distinct blocks cannot overlap in
3873
more than a single cut vertex.
3874
3875
OUTPUT: ``( B, C )``, where ``B`` is a list of blocks- each is
3876
a list of vertices and the blocks are the corresponding induced
3877
subgraphs-and ``C`` is a list of cut vertices.
3878
3879
EXAMPLES::
3880
3881
sage: graphs.PetersenGraph().blocks_and_cut_vertices()
3882
([[6, 4, 9, 7, 5, 8, 3, 2, 1, 0]], [])
3883
sage: graphs.PathGraph(6).blocks_and_cut_vertices()
3884
([[5, 4], [4, 3], [3, 2], [2, 1], [1, 0]], [4, 3, 2, 1])
3885
sage: graphs.CycleGraph(7).blocks_and_cut_vertices()
3886
([[6, 5, 4, 3, 2, 1, 0]], [])
3887
sage: graphs.KrackhardtKiteGraph().blocks_and_cut_vertices()
3888
([[9, 8], [8, 7], [7, 4, 6, 5, 2, 3, 1, 0]], [8, 7])
3889
sage: G=Graph() # make a bowtie graph where 0 is a cut vertex
3890
sage: G.add_vertices(range(5))
3891
sage: G.add_edges([(0,1),(0,2),(0,3),(0,4),(1,2),(3,4)])
3892
sage: G.blocks_and_cut_vertices()
3893
([[2, 1, 0], [4, 3, 0]], [0])
3894
sage: graphs.StarGraph(3).blocks_and_cut_vertices()
3895
([[1, 0], [2, 0], [3, 0]], [0])
3896
3897
TESTS::
3898
3899
sage: Graph(0).blocks_and_cut_vertices()
3900
([], [])
3901
sage: Graph(1).blocks_and_cut_vertices()
3902
([0], [])
3903
sage: Graph(2).blocks_and_cut_vertices()
3904
Traceback (most recent call last):
3905
...
3906
NotImplementedError: ...
3907
3908
ALGORITHM: 8.3.8 in [Jungnickel05]_. Notice that the termination condition on
3909
line (23) of the algorithm uses ``p[v] == 0`` which in the book
3910
means that the parent is undefined; in this case, `v` must be the
3911
root `s`. Since our vertex names start with `0`, we substitute instead
3912
the condition ``v == s``. This is the terminating condition used
3913
in the general Depth First Search tree in Algorithm 8.2.1.
3914
3915
REFERENCE:
3916
3917
.. [Jungnickel05] D. Jungnickel, Graphs, Networks and Algorithms,
3918
Springer, 2005.
3919
"""
3920
if not self: # empty graph
3921
return [],[]
3922
3923
s = self.vertex_iterator().next() # source
3924
3925
if len(self) == 1: # only one vertex
3926
return [s],[]
3927
3928
if not self.is_connected():
3929
raise NotImplementedError("Blocks and cut vertices is currently only implemented for connected graphs.")
3930
3931
nr = {} # enumerate
3932
p = {} # predecessors
3933
L = {}
3934
visited_edges = set()
3935
i = 1
3936
v = s # visited
3937
nr[s] = 1
3938
L[s] = 1
3939
C = [] # cuts
3940
B = [] # blocks
3941
S = [s] #stack
3942
its = {}
3943
while True:
3944
while True:
3945
for u in self.neighbor_iterator(v):
3946
if not (v,u) in visited_edges: break
3947
else:
3948
break
3949
visited_edges.add((v,u))
3950
visited_edges.add((u,v))
3951
if u not in nr:
3952
p[u] = v
3953
i += 1
3954
nr[u] = i
3955
L[u] = i
3956
S.append(u)
3957
v = u
3958
else:
3959
L[v] = min( L[v], nr[u] )
3960
3961
if v is s:
3962
break
3963
3964
pv = p[v]
3965
if L[v] < nr[pv]:
3966
L[pv] = min( L[pv], L[v] )
3967
v = pv
3968
continue
3969
3970
if pv not in C:
3971
if pv is not s or\
3972
not all([(s,u) in visited_edges for u in self.neighbor_iterator(s)]):
3973
C.append(pv)
3974
3975
B_k = []
3976
while True:
3977
u = S.pop()
3978
B_k.append(u)
3979
if u == v: break
3980
B_k.append(pv)
3981
B.append(B_k)
3982
3983
v = pv
3984
return B, C
3985
3986
3987
def steiner_tree(self,vertices, weighted = False, solver = None, verbose = 0):
3988
r"""
3989
Returns a tree of minimum weight connecting the given
3990
set of vertices.
3991
3992
Definition :
3993
3994
Computing a minimum spanning tree in a graph can be done in `n
3995
\log(n)` time (and in linear time if all weights are equal) where
3996
`n = V + E`. On the other hand, if one is given a large (possibly
3997
weighted) graph and a subset of its vertices, it is NP-Hard to
3998
find a tree of minimum weight connecting the given set of
3999
vertices, which is then called a Steiner Tree.
4000
4001
`Wikipedia article on Steiner Trees
4002
<http://en.wikipedia.org/wiki/Steiner_tree_problem>`_.
4003
4004
INPUT:
4005
4006
- ``vertices`` -- the vertices to be connected by the Steiner
4007
Tree.
4008
4009
- ``weighted`` (boolean) -- Whether to consider the graph as
4010
weighted, and use each edge's label as a weight, considering
4011
``None`` as a weight of `1`. If ``weighted=False`` (default)
4012
all edges are considered to have a weight of `1`.
4013
4014
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
4015
solver to be used. If set to ``None``, the default one is used. For
4016
more information on LP solvers and which default solver is used, see
4017
the method
4018
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
4019
of the class
4020
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
4021
4022
- ``verbose`` -- integer (default: ``0``). Sets the level of
4023
verbosity. Set to 0 by default, which means quiet.
4024
4025
4026
.. NOTE::
4027
4028
* This problem being defined on undirected graphs, the
4029
orientation is not considered if the current graph is
4030
actually a digraph.
4031
4032
* The graph is assumed not to have multiple edges.
4033
4034
ALGORITHM:
4035
4036
Solved through Linear Programming.
4037
4038
COMPLEXITY:
4039
4040
NP-Hard.
4041
4042
Note that this algorithm first checks whether the given
4043
set of vertices induces a connected graph, returning one of its
4044
spanning trees if ``weighted`` is set to ``False``, and thus
4045
answering very quickly in some cases
4046
4047
EXAMPLES:
4048
4049
The Steiner Tree of the first 5 vertices in a random graph is,
4050
of course, always a tree ::
4051
4052
sage: g = graphs.RandomGNP(30,.5)
4053
sage: st = g.steiner_tree(g.vertices()[:5])
4054
sage: st.is_tree()
4055
True
4056
4057
And all the 5 vertices are contained in this tree ::
4058
4059
sage: all([v in st for v in g.vertices()[:5] ])
4060
True
4061
4062
An exception is raised when the problem is impossible, i.e.
4063
if the given vertices are not all included in the same
4064
connected component ::
4065
4066
sage: g = 2 * graphs.PetersenGraph()
4067
sage: st = g.steiner_tree([5,15])
4068
Traceback (most recent call last):
4069
...
4070
ValueError: The given vertices do not all belong to the same connected component. This problem has no solution !
4071
"""
4072
4073
if self.is_directed():
4074
g = Graph(self)
4075
else:
4076
g = self
4077
4078
if g.has_multiple_edges():
4079
raise ValueError("The graph is expected not to have multiple edges.")
4080
4081
# Can the problem be solved ? Are all the vertices in the same
4082
# connected component ?
4083
cc = g.connected_component_containing_vertex(vertices[0])
4084
if not all([v in cc for v in vertices]):
4085
raise ValueError("The given vertices do not all belong to the same connected component. This problem has no solution !")
4086
4087
# Can it be solved using the min spanning tree algorithm ?
4088
if not weighted:
4089
gg = g.subgraph(vertices)
4090
if gg.is_connected():
4091
st = g.subgraph(edges = gg.min_spanning_tree())
4092
st.delete_vertices([v for v in g if st.degree(v) == 0])
4093
return st
4094
4095
# Then, LP formulation
4096
from sage.numerical.mip import MixedIntegerLinearProgram, Sum
4097
p = MixedIntegerLinearProgram(maximization = False, solver = solver)
4098
4099
# Reorder an edge
4100
R = lambda (x,y) : (x,y) if x<y else (y,x)
4101
4102
# edges used in the Steiner Tree
4103
edges = p.new_variable()
4104
4105
# relaxed edges to test for acyclicity
4106
r_edges = p.new_variable()
4107
4108
# Whether a vertex is in the Steiner Tree
4109
vertex = p.new_variable()
4110
for v in g:
4111
for e in g.edges_incident(v, labels=False):
4112
p.add_constraint(vertex[v] - edges[R(e)], min = 0)
4113
4114
# We must have the given vertices in our tree
4115
for v in vertices:
4116
p.add_constraint(Sum([edges[R(e)] for e in g.edges_incident(v,labels=False)]), min=1)
4117
4118
# The number of edges is equal to the number of vertices in our tree minus 1
4119
p.add_constraint(Sum([vertex[v] for v in g]) - Sum([edges[R(e)] for e in g.edges(labels=None)]), max = 1, min = 1)
4120
4121
# There are no cycles in our graph
4122
4123
for u,v in g.edges(labels = False):
4124
p.add_constraint( r_edges[(u,v)]+ r_edges[(v,u)] - edges[R((u,v))] , min = 0 )
4125
4126
eps = 1/(5*Integer(g.order()))
4127
for v in g:
4128
p.add_constraint(Sum([r_edges[(u,v)] for u in g.neighbors(v)]), max = 1-eps)
4129
4130
4131
# Objective
4132
if weighted:
4133
w = lambda (x,y) : g.edge_label(x,y) if g.edge_label(x,y) is not None else 1
4134
else:
4135
w = lambda (x,y) : 1
4136
4137
p.set_objective(Sum([w(e)*edges[R(e)] for e in g.edges(labels = False)]))
4138
4139
p.set_binary(edges)
4140
p.solve(log = verbose)
4141
4142
edges = p.get_values(edges)
4143
4144
st = g.subgraph(edges=[e for e in g.edges(labels = False) if edges[R(e)] == 1])
4145
st.delete_vertices([v for v in g if st.degree(v) == 0])
4146
return st
4147
4148
def edge_disjoint_spanning_trees(self,k, root=None, solver = None, verbose = 0):
4149
r"""
4150
Returns the desired number of edge-disjoint spanning
4151
trees/arborescences.
4152
4153
INPUT:
4154
4155
- ``k`` (integer) -- the required number of edge-disjoint
4156
spanning trees/arborescences
4157
4158
- ``root`` (vertex) -- root of the disjoint arborescences
4159
when the graph is directed.
4160
If set to ``None``, the first vertex in the graph is picked.
4161
4162
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
4163
solver to be used. If set to ``None``, the default one is used. For
4164
more information on LP solvers and which default solver is used, see
4165
the method
4166
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
4167
of the class
4168
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
4169
4170
- ``verbose`` -- integer (default: ``0``). Sets the level of
4171
verbosity. Set to 0 by default, which means quiet.
4172
4173
ALGORITHM:
4174
4175
Mixed Integer Linear Program. The formulation can be found
4176
in [JVNC]_.
4177
4178
There are at least two possible rewritings of this method
4179
which do not use Linear Programming:
4180
4181
* The algorithm presented in the paper entitled "A short
4182
proof of the tree-packing theorem", by Thomas Kaiser
4183
[KaisPacking]_.
4184
4185
* The implementation of a Matroid class and of the Matroid
4186
Union Theorem (see section 42.3 of [SchrijverCombOpt]_),
4187
applied to the cycle Matroid (see chapter 51 of
4188
[SchrijverCombOpt]_).
4189
4190
EXAMPLES:
4191
4192
The Petersen Graph does have a spanning tree (it is connected)::
4193
4194
sage: g = graphs.PetersenGraph()
4195
sage: [T] = g.edge_disjoint_spanning_trees(1)
4196
sage: T.is_tree()
4197
True
4198
4199
Though, it does not have 2 edge-disjoint trees (as it has less
4200
than `2(|V|-1)` edges)::
4201
4202
sage: g.edge_disjoint_spanning_trees(2)
4203
Traceback (most recent call last):
4204
...
4205
ValueError: This graph does not contain the required number of trees/arborescences !
4206
4207
By Edmond's theorem, a graph which is `k`-connected always has `k` edge-disjoint
4208
arborescences, regardless of the root we pick::
4209
4210
sage: g = digraphs.RandomDirectedGNP(28,.3) # reduced from 30 to 28, cf. #9584
4211
sage: k = Integer(g.edge_connectivity())
4212
sage: arborescences = g.edge_disjoint_spanning_trees(k) # long time (up to 15s on sage.math, 2011)
4213
sage: all([a.is_directed_acyclic() for a in arborescences]) # long time
4214
True
4215
sage: all([a.is_connected() for a in arborescences]) # long time
4216
True
4217
4218
In the undirected case, we can only ensure half of it::
4219
4220
sage: g = graphs.RandomGNP(30,.3)
4221
sage: k = floor(Integer(g.edge_connectivity())/2)
4222
sage: trees = g.edge_disjoint_spanning_trees(k)
4223
sage: all([t.is_tree() for t in trees])
4224
True
4225
4226
REFERENCES:
4227
4228
.. [JVNC] David Joyner, Minh Van Nguyen, and Nathann Cohen,
4229
Algorithmic Graph Theory,
4230
http://code.google.com/p/graph-theory-algorithms-book/
4231
4232
.. [KaisPacking] Thomas Kaiser
4233
A short proof of the tree-packing theorem
4234
http://arxiv.org/abs/0911.2809
4235
4236
.. [SchrijverCombOpt] Alexander Schrijver
4237
Combinatorial optimization: polyhedra and efficiency
4238
2003
4239
"""
4240
4241
from sage.numerical.mip import MixedIntegerLinearProgram, MIPSolverException, Sum
4242
4243
p = MixedIntegerLinearProgram(solver = solver)
4244
p.set_objective(None)
4245
4246
# The colors we can use
4247
colors = range(0,k)
4248
4249
# edges[j][e] is equal to one if and only if edge e belongs to color j
4250
edges = p.new_variable(dim=2)
4251
4252
if root == None:
4253
root = self.vertex_iterator().next()
4254
4255
# r_edges is a relaxed variable grater than edges. It is used to
4256
# check the presence of cycles
4257
r_edges = p.new_variable(dim=2)
4258
4259
epsilon = 1/(3*(Integer(self.order())))
4260
4261
if self.is_directed():
4262
# Does nothing ot an edge.. Useful when out of "if self.directed"
4263
S = lambda (x,y) : (x,y)
4264
4265
# An edge belongs to at most arborescence
4266
for e in self.edges(labels=False):
4267
p.add_constraint(Sum([edges[j][e] for j in colors]), max=1)
4268
4269
4270
for j in colors:
4271
# each color class has self.order()-1 edges
4272
p.add_constraint(Sum([edges[j][e] for e in self.edges(labels=None)]), min=self.order()-1)
4273
4274
# Each vertex different from the root has indegree equals to one
4275
for v in self.vertices():
4276
if v is not root:
4277
p.add_constraint(Sum([edges[j][e] for e in self.incoming_edges(v, labels=None)]), max=1, min=1)
4278
else:
4279
p.add_constraint(Sum([edges[j][e] for e in self.incoming_edges(v, labels=None)]), max=0, min=0)
4280
4281
# r_edges is larger than edges
4282
for u,v in self.edges(labels=None):
4283
if self.has_edge(v,u):
4284
if v<u:
4285
p.add_constraint(r_edges[j][(u,v)] + r_edges[j][(v, u)] - edges[j][(u,v)] - edges[j][(v,u)], min=0)
4286
else:
4287
p.add_constraint(r_edges[j][(u,v)] + r_edges[j][(v, u)] - edges[j][(u,v)], min=0)
4288
4289
from sage.graphs.digraph import DiGraph
4290
D = DiGraph()
4291
D.add_vertices(self.vertices())
4292
D.set_pos(self.get_pos())
4293
classes = [D.copy() for j in colors]
4294
4295
else:
4296
4297
# Sort an edge
4298
S = lambda (x,y) : (x,y) if x<y else (y,x)
4299
4300
# An edge belongs to at most one arborescence
4301
for e in self.edges(labels=False):
4302
p.add_constraint(Sum([edges[j][S(e)] for j in colors]), max=1)
4303
4304
4305
for j in colors:
4306
# each color class has self.order()-1 edges
4307
p.add_constraint(Sum([edges[j][S(e)] for e in self.edges(labels=None)]), min=self.order()-1)
4308
4309
# Each vertex is in the tree
4310
for v in self.vertices():
4311
p.add_constraint(Sum([edges[j][S(e)] for e in self.edges_incident(v, labels=None)]), min=1)
4312
4313
# r_edges is larger than edges
4314
for u,v in self.edges(labels=None):
4315
p.add_constraint(r_edges[j][(u,v)] + r_edges[j][(v, u)] - edges[j][S((u,v))], min=0)
4316
4317
from sage.graphs.graph import Graph
4318
D = Graph()
4319
D.add_vertices(self.vertices())
4320
D.set_pos(self.get_pos())
4321
classes = [D.copy() for j in colors]
4322
4323
# no cycles
4324
for j in colors:
4325
for v in self:
4326
p.add_constraint(Sum(r_edges[j][(u,v)] for u in self.neighbors(v)), max=1-epsilon)
4327
4328
p.set_binary(edges)
4329
4330
try:
4331
p.solve(log = verbose)
4332
4333
except MIPSolverException:
4334
raise ValueError("This graph does not contain the required number of trees/arborescences !")
4335
4336
edges = p.get_values(edges)
4337
4338
for j,g in enumerate(classes):
4339
for e in self.edges(labels=False):
4340
if edges[j][S(e)] == 1:
4341
g.add_edge(e)
4342
if len(list(g.breadth_first_search(root))) != self.order():
4343
raise RuntimeError("The computation seems to have gone wrong somewhere..."+
4344
"This is probably because of the value of epsilon, but"+
4345
" in any case please report this bug, with the graph "+
4346
"that produced it ! ;-)")
4347
4348
return classes
4349
4350
def edge_cut(self, s, t, value_only=True, use_edge_labels=False, vertices=False, method="FF", solver=None, verbose=0):
4351
r"""
4352
Returns a minimum edge cut between vertices `s` and `t`
4353
represented by a list of edges.
4354
4355
A minimum edge cut between two vertices `s` and `t` of self
4356
is a set `A` of edges of minimum weight such that the graph
4357
obtained by removing `A` from self is disconnected. For more
4358
information, see the
4359
`Wikipedia article on cuts
4360
<http://en.wikipedia.org/wiki/Cut_%28graph_theory%29>`_.
4361
4362
INPUT:
4363
4364
- ``s`` -- source vertex
4365
4366
- ``t`` -- sink vertex
4367
4368
- ``value_only`` -- boolean (default: ``True``). When set to
4369
``True``, only the weight of a minimum cut is returned.
4370
Otherwise, a list of edges of a minimum cut is also returned.
4371
4372
- ``use_edge_labels`` -- boolean (default: ``False``). When set to
4373
``True``, computes a weighted minimum cut where each edge has
4374
a weight defined by its label (if an edge has no label, `1`
4375
is assumed). Otherwise, each edge has weight `1`.
4376
4377
- ``vertices`` -- boolean (default: ``False``). When set to
4378
``True``, returns a list of edges in the edge cut and the
4379
two sets of vertices that are disconnected by the cut.
4380
4381
Note: ``vertices=True`` implies ``value_only=False``.
4382
4383
- ``method`` -- There are currently two different
4384
implementations of this method :
4385
4386
* If ``method = "FF"`` (default), a Python
4387
implementation of the Ford-Fulkerson algorithm is
4388
used.
4389
4390
* If ``method = "LP"``, the flow problem is solved using
4391
Linear Programming.
4392
4393
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
4394
solver to be used. If set to ``None``, the default one is used. For
4395
more information on LP solvers and which default solver is used, see
4396
the method
4397
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
4398
of the class
4399
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
4400
4401
- ``verbose`` -- integer (default: ``0``). Sets the level of
4402
verbosity. Set to 0 by default, which means quiet.
4403
4404
.. NOTE::
4405
4406
The use of Linear Programming for non-integer problems may
4407
possibly mean the presence of a (slight) numerical noise.
4408
4409
OUTPUT:
4410
4411
Real number or tuple, depending on the given arguments
4412
(examples are given below).
4413
4414
EXAMPLES:
4415
4416
A basic application in the Pappus graph::
4417
4418
sage: g = graphs.PappusGraph()
4419
sage: g.edge_cut(1, 2, value_only=True)
4420
3
4421
4422
Or on Petersen's graph, with the corresponding bipartition of
4423
the vertex set::
4424
4425
sage: g = graphs.PetersenGraph()
4426
sage: g.edge_cut(0, 3, vertices=True)
4427
[3, [(0, 1, None), (0, 4, None), (0, 5, None)], [[0], [1, 2, 3, 4, 5, 6, 7, 8, 9]]]
4428
4429
If the graph is a path with randomly weighted edges::
4430
4431
sage: g = graphs.PathGraph(15)
4432
sage: for (u,v) in g.edge_iterator(labels=None):
4433
... g.set_edge_label(u,v,random())
4434
4435
The edge cut between the two ends is the edge of minimum weight::
4436
4437
sage: minimum = min([l for u,v,l in g.edge_iterator()])
4438
sage: minimum == g.edge_cut(0, 14, use_edge_labels=True)
4439
True
4440
sage: [value,[e]] = g.edge_cut(0, 14, use_edge_labels=True, value_only=False)
4441
sage: g.edge_label(e[0],e[1]) == minimum
4442
True
4443
4444
The two sides of the edge cut are obviously shorter paths::
4445
4446
sage: value,edges,[set1,set2] = g.edge_cut(0, 14, use_edge_labels=True, vertices=True)
4447
sage: g.subgraph(set1).is_isomorphic(graphs.PathGraph(len(set1)))
4448
True
4449
sage: g.subgraph(set2).is_isomorphic(graphs.PathGraph(len(set2)))
4450
True
4451
sage: len(set1) + len(set2) == g.order()
4452
True
4453
4454
TESTS:
4455
4456
If method is set to an exotic value::
4457
4458
sage: g = graphs.PetersenGraph()
4459
sage: g.edge_cut(0,1, method="Divination")
4460
Traceback (most recent call last):
4461
...
4462
ValueError: The method argument has to be equal to either "FF" or "LP"
4463
4464
Same result for both methods::
4465
4466
sage: g = graphs.RandomGNP(20,.3)
4467
sage: for u,v in g.edges(labels=False):
4468
... g.set_edge_label(u,v,round(random(),5))
4469
sage: g.edge_cut(0,1, method="FF") == g.edge_cut(0,1,method="LP")
4470
True
4471
4472
Rounded return value when using the LP method::
4473
4474
sage: g = graphs.PappusGraph()
4475
sage: g.edge_cut(1, 2, value_only=True, method = "LP")
4476
3
4477
"""
4478
4479
if vertices:
4480
value_only = False
4481
4482
if use_edge_labels:
4483
weight = lambda x: x if (x!={} and x is not None) else 1
4484
else:
4485
weight = lambda x: 1
4486
4487
if method == "FF":
4488
if value_only:
4489
return self.flow(s,t,value_only=value_only,use_edge_labels=use_edge_labels, method=method)
4490
4491
flow_value, flow_graph = self.flow(s,t,value_only=value_only,use_edge_labels=use_edge_labels, method=method)
4492
g = self.copy()
4493
for u,v,l in flow_graph.edge_iterator():
4494
if (not use_edge_labels or
4495
(weight(g.edge_label(u,v)) == weight(l))):
4496
g.delete_edge(u,v)
4497
4498
return_value = [flow_value]
4499
4500
reachable_from_s = list(g.breadth_first_search(s))
4501
4502
return_value.append(self.edge_boundary(reachable_from_s))
4503
4504
if vertices:
4505
return_value.append([reachable_from_s,list(set(self.vertices())-set(reachable_from_s))])
4506
4507
return return_value
4508
4509
if method != "LP":
4510
raise ValueError("The method argument has to be equal to either \"FF\" or \"LP\"")
4511
4512
from sage.numerical.mip import MixedIntegerLinearProgram, Sum
4513
g = self
4514
p = MixedIntegerLinearProgram(maximization=False, solver=solver)
4515
b = p.new_variable(dim=2)
4516
v = p.new_variable()
4517
4518
# Some vertices belong to part 1, others to part 0
4519
p.add_constraint(v[s], min=0, max=0)
4520
p.add_constraint(v[t], min=1, max=1)
4521
4522
if g.is_directed():
4523
4524
# we minimize the number of edges
4525
p.set_objective(Sum([weight(w) * b[x][y] for (x,y,w) in g.edges()]))
4526
4527
# Adjacent vertices can belong to different parts only if the
4528
# edge that connects them is part of the cut
4529
for (x,y) in g.edges(labels=None):
4530
p.add_constraint(v[x] + b[x][y] - v[y], min=0)
4531
4532
else:
4533
# we minimize the number of edges
4534
p.set_objective(Sum([weight(w) * b[min(x,y)][max(x,y)] for (x,y,w) in g.edges()]))
4535
# Adjacent vertices can belong to different parts only if the
4536
# edge that connects them is part of the cut
4537
for (x,y) in g.edges(labels=None):
4538
p.add_constraint(v[x] + b[min(x,y)][max(x,y)] - v[y], min=0)
4539
p.add_constraint(v[y] + b[min(x,y)][max(x,y)] - v[x], min=0)
4540
4541
p.set_binary(v)
4542
p.set_binary(b)
4543
4544
if value_only:
4545
if use_edge_labels:
4546
return p.solve(objective_only=True, log=verbose)
4547
else:
4548
return Integer(round(p.solve(objective_only=True, log=verbose)))
4549
else:
4550
obj = p.solve(log=verbose)
4551
4552
if use_edge_labels is False:
4553
obj = Integer(round(obj))
4554
4555
b = p.get_values(b)
4556
answer = [obj]
4557
if g.is_directed():
4558
answer.append([(x,y) for (x,y) in g.edges(labels=None) if b[x][y] == 1])
4559
else:
4560
answer.append([(x,y) for (x,y) in g.edges(labels=None) if b[min(x,y)][max(x,y)] == 1])
4561
4562
if vertices:
4563
v = p.get_values(v)
4564
l0 = []
4565
l1 = []
4566
for x in g.vertex_iterator():
4567
if v.has_key(x) and v[x] == 1:
4568
l1.append(x)
4569
else:
4570
l0.append(x)
4571
answer.append([l0, l1])
4572
return tuple(answer)
4573
4574
def vertex_cut(self, s, t, value_only=True, vertices=False, solver=None, verbose=0):
4575
r"""
4576
Returns a minimum vertex cut between non-adjacent vertices `s` and `t`
4577
represented by a list of vertices.
4578
4579
A vertex cut between two non-adjacent vertices is a set `U`
4580
of vertices of self such that the graph obtained by removing
4581
`U` from self is disconnected. For more information, see the
4582
`Wikipedia article on cuts
4583
<http://en.wikipedia.org/wiki/Cut_%28graph_theory%29>`_.
4584
4585
INPUT:
4586
4587
- ``value_only`` -- boolean (default: ``True``). When set to
4588
``True``, only the size of the minimum cut is returned.
4589
4590
- ``vertices`` -- boolean (default: ``False``). When set to
4591
``True``, also returns the two sets of vertices that
4592
are disconnected by the cut. Implies ``value_only``
4593
set to False.
4594
4595
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
4596
solver to be used. If set to ``None``, the default one is used. For
4597
more information on LP solvers and which default solver is used, see
4598
the method
4599
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
4600
of the class
4601
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
4602
4603
- ``verbose`` -- integer (default: ``0``). Sets the level of
4604
verbosity. Set to 0 by default, which means quiet.
4605
4606
OUTPUT:
4607
4608
Real number or tuple, depending on the given arguments
4609
(examples are given below).
4610
4611
EXAMPLE:
4612
4613
A basic application in the Pappus graph::
4614
4615
sage: g = graphs.PappusGraph()
4616
sage: g.vertex_cut(1, 16, value_only=True)
4617
3
4618
4619
In the bipartite complete graph `K_{2,8}`, a cut between the two
4620
vertices in the size `2` part consists of the other `8` vertices::
4621
4622
sage: g = graphs.CompleteBipartiteGraph(2, 8)
4623
sage: [value, vertices] = g.vertex_cut(0, 1, value_only=False)
4624
sage: print value
4625
8
4626
sage: vertices == range(2,10)
4627
True
4628
4629
Clearly, in this case the two sides of the cut are singletons ::
4630
4631
sage: [value, vertices, [set1, set2]] = g.vertex_cut(0,1, vertices=True)
4632
sage: len(set1) == 1
4633
True
4634
sage: len(set2) == 1
4635
True
4636
"""
4637
from sage.numerical.mip import MixedIntegerLinearProgram, Sum
4638
g = self
4639
if g.has_edge(s,t):
4640
raise ValueError, "There can be no vertex cut between adjacent vertices !"
4641
if vertices:
4642
value_only = False
4643
4644
p = MixedIntegerLinearProgram(maximization=False, solver=solver)
4645
b = p.new_variable()
4646
v = p.new_variable()
4647
4648
# Some vertices belong to part 1, some others to part 0
4649
p.add_constraint(v[s], min=0, max=0)
4650
p.add_constraint(v[t], min=1, max=1)
4651
4652
# b indicates whether the vertices belong to the cut
4653
p.add_constraint(b[s], min=0, max=0)
4654
p.add_constraint(b[t], min=0, max=0)
4655
4656
if g.is_directed():
4657
4658
p.set_objective(Sum([b[x] for x in g.vertices()]))
4659
4660
# adjacent vertices belong to the same part except if one of them
4661
# belongs to the cut
4662
for (x,y) in g.edges(labels=None):
4663
p.add_constraint(v[x] + b[y] - v[y], min=0)
4664
4665
else:
4666
p.set_objective(Sum([b[x] for x in g.vertices()]))
4667
# adjacent vertices belong to the same part except if one of them
4668
# belongs to the cut
4669
for (x,y) in g.edges(labels=None):
4670
p.add_constraint(v[x] + b[y] - v[y],min=0)
4671
p.add_constraint(v[y] + b[x] - v[x],min=0)
4672
4673
p.set_binary(b)
4674
p.set_binary(v)
4675
4676
if value_only:
4677
return Integer(round(p.solve(objective_only=True, log=verbose)))
4678
else:
4679
obj = Integer(round(p.solve(log=verbose)))
4680
b = p.get_values(b)
4681
answer = [obj,[x for x in g if b[x] == 1]]
4682
if vertices:
4683
v = p.get_values(v)
4684
l0 = []
4685
l1 = []
4686
for x in g.vertex_iterator():
4687
# if the vertex is not in the cut
4688
if not (b.has_key(x) and b[x] == 1):
4689
if (v.has_key(x) and v[x] == 1):
4690
l1.append(x)
4691
else:
4692
l0.append(x)
4693
answer.append([l0, l1])
4694
return tuple(answer)
4695
4696
4697
def multiway_cut(self, vertices, value_only = False, use_edge_labels = False, solver = None, verbose = 0):
4698
r"""
4699
Returns a minimum edge multiway cut corresponding to the
4700
given set of vertices
4701
( cf. http://www.d.kth.se/~viggo/wwwcompendium/node92.html )
4702
represented by a list of edges.
4703
4704
A multiway cut for a vertex set `S` in a graph or a digraph
4705
`G` is a set `C` of edges such that any two vertices `u,v`
4706
in `S` are disconnected when removing the edges from `C` from `G`.
4707
4708
Such a cut is said to be minimum when its cardinality
4709
(or weight) is minimum.
4710
4711
INPUT:
4712
4713
- ``vertices`` (iterable)-- the set of vertices
4714
4715
- ``value_only`` (boolean)
4716
4717
- When set to ``True``, only the value of a minimum
4718
multiway cut is returned.
4719
4720
- When set to ``False`` (default), the list of edges
4721
is returned
4722
4723
- ``use_edge_labels`` (boolean)
4724
- When set to ``True``, computes a weighted minimum cut
4725
where each edge has a weight defined by its label. ( if
4726
an edge has no label, `1` is assumed )
4727
4728
- when set to ``False`` (default), each edge has weight `1`.
4729
4730
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
4731
solver to be used. If set to ``None``, the default one is used. For
4732
more information on LP solvers and which default solver is used, see
4733
the method
4734
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
4735
of the class
4736
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
4737
4738
- ``verbose`` -- integer (default: ``0``). Sets the level of
4739
verbosity. Set to 0 by default, which means quiet.
4740
4741
EXAMPLES:
4742
4743
Of course, a multiway cut between two vertices correspond
4744
to a minimum edge cut ::
4745
4746
sage: g = graphs.PetersenGraph()
4747
sage: g.edge_cut(0,3) == g.multiway_cut([0,3], value_only = True)
4748
True
4749
4750
As Petersen's graph is `3`-regular, a minimum multiway cut
4751
between three vertices contains at most `2\times 3` edges
4752
(which could correspond to the neighborhood of 2
4753
vertices)::
4754
4755
sage: g.multiway_cut([0,3,9], value_only = True) == 2*3
4756
True
4757
4758
In this case, though, the vertices are an independent set.
4759
If we pick instead vertices `0,9,` and `7`, we can save `4`
4760
edges in the multiway cut ::
4761
4762
sage: g.multiway_cut([0,7,9], value_only = True) == 2*3 - 1
4763
True
4764
4765
This example, though, does not work in the directed case anymore,
4766
as it is not possible in Petersen's graph to mutualise edges ::
4767
4768
sage: g = DiGraph(g)
4769
sage: g.multiway_cut([0,7,9], value_only = True) == 3*3
4770
True
4771
4772
Of course, a multiway cut between the whole vertex set
4773
contains all the edges of the graph::
4774
4775
sage: C = g.multiway_cut(g.vertices())
4776
sage: set(C) == set(g.edges())
4777
True
4778
"""
4779
from sage.numerical.mip import MixedIntegerLinearProgram, Sum
4780
from itertools import combinations, chain
4781
4782
p = MixedIntegerLinearProgram(maximization = False, solver= solver)
4783
4784
# height[c][v] represents the height of vertex v for commodity c
4785
height = p.new_variable(dim = 2)
4786
4787
# cut[e] represents whether e is in the cut
4788
cut = p.new_variable()
4789
4790
# Reorder
4791
R = lambda x,y : (x,y) if x<y else (y,x)
4792
4793
# Weight function
4794
if use_edge_labels:
4795
w = lambda l : l if l is not None else 1
4796
else:
4797
w = lambda l : 1
4798
4799
if self.is_directed():
4800
4801
p.set_objective( Sum([ w(l) * cut[u,v] for u,v,l in self.edge_iterator() ]) )
4802
4803
for s,t in chain( combinations(vertices,2), map(lambda (x,y) : (y,x), combinations(vertices,2))) :
4804
# For each commodity, the source is at height 0
4805
# and the destination is at height 1
4806
p.add_constraint( height[(s,t)][s], min = 0, max = 0)
4807
p.add_constraint( height[(s,t)][t], min = 1, max = 1)
4808
4809
# given a commodity (s,t), the height of two adjacent vertices u,v
4810
# can differ of at most the value of the edge between them
4811
for u,v in self.edges(labels = False):
4812
p.add_constraint( height[(s,t)][u] - height[(s,t)][v] - cut[u,v], max = 0)
4813
4814
else:
4815
4816
p.set_objective( Sum([ w(l) * cut[R(u,v)] for u,v,l in self.edge_iterator() ]) )
4817
4818
for s,t in combinations(vertices,2):
4819
# For each commodity, the source is at height 0
4820
# and the destination is at height 1
4821
p.add_constraint( height[(s,t)][s], min = 0, max = 0)
4822
p.add_constraint( height[(s,t)][t], min = 1, max = 1)
4823
4824
# given a commodity (s,t), the height of two adjacent vertices u,v
4825
# can differ of at most the value of the edge between them
4826
for u,v in self.edges(labels = False):
4827
p.add_constraint( height[(s,t)][u] - height[(s,t)][v] - cut[R(u,v)], max = 0)
4828
p.add_constraint( height[(s,t)][v] - height[(s,t)][u] - cut[R(u,v)], max = 0)
4829
4830
4831
p.set_binary(cut)
4832
if value_only:
4833
if use_edge_labels:
4834
return p.solve(objective_only = True, log = verbose)
4835
else:
4836
return Integer(round(p.solve(objective_only = True, log = verbose)))
4837
4838
p.solve(log = verbose)
4839
4840
cut = p.get_values(cut)
4841
4842
if self.is_directed():
4843
return filter(lambda (u,v,l) : cut[u,v] > .5, self.edge_iterator())
4844
4845
else:
4846
return filter(lambda (u,v,l) : cut[R(u,v)] > .5, self.edge_iterator())
4847
4848
4849
def max_cut(self, value_only=True, use_edge_labels=False, vertices=False, solver=None, verbose=0):
4850
r"""
4851
Returns a maximum edge cut of the graph. For more information, see the
4852
`Wikipedia article on cuts
4853
<http://en.wikipedia.org/wiki/Cut_%28graph_theory%29>`_.
4854
4855
INPUT:
4856
4857
- ``value_only`` -- boolean (default: ``True``)
4858
4859
- When set to ``True`` (default), only the value is returned.
4860
4861
- When set to ``False``, both the value and a maximum edge cut
4862
are returned.
4863
4864
- ``use_edge_labels`` -- boolean (default: ``False``)
4865
4866
- When set to ``True``, computes a maximum weighted cut
4867
where each edge has a weight defined by its label. (If
4868
an edge has no label, `1` is assumed.)
4869
4870
- When set to ``False``, each edge has weight `1`.
4871
4872
- ``vertices`` -- boolean (default: ``False``)
4873
4874
- When set to ``True``, also returns the two sets of
4875
vertices that are disconnected by the cut. This implies
4876
``value_only=False``.
4877
4878
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
4879
solver to be used. If set to ``None``, the default one is used. For
4880
more information on LP solvers and which default solver is used, see
4881
the method
4882
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
4883
of the class
4884
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
4885
4886
- ``verbose`` -- integer (default: ``0``). Sets the level of
4887
verbosity. Set to 0 by default, which means quiet.
4888
4889
EXAMPLE:
4890
4891
Quite obviously, the max cut of a bipartite graph
4892
is the number of edges, and the two sets of vertices
4893
are the the two sides ::
4894
4895
sage: g = graphs.CompleteBipartiteGraph(5,6)
4896
sage: [ value, edges, [ setA, setB ]] = g.max_cut(vertices=True)
4897
sage: value == 5*6
4898
True
4899
sage: bsetA, bsetB = map(list,g.bipartite_sets())
4900
sage: (bsetA == setA and bsetB == setB ) or ((bsetA == setB and bsetB == setA ))
4901
True
4902
4903
The max cut of a Petersen graph::
4904
4905
sage: g=graphs.PetersenGraph()
4906
sage: g.max_cut()
4907
12
4908
4909
"""
4910
g=self
4911
4912
if vertices:
4913
value_only=False
4914
4915
if use_edge_labels:
4916
from sage.rings.real_mpfr import RR
4917
weight = lambda x: x if x in RR else 1
4918
else:
4919
weight = lambda x: 1
4920
4921
if g.is_directed():
4922
reorder_edge = lambda x,y : (x,y)
4923
else:
4924
reorder_edge = lambda x,y : (x,y) if x<= y else (y,x)
4925
4926
from sage.numerical.mip import MixedIntegerLinearProgram, Sum
4927
4928
p = MixedIntegerLinearProgram(maximization=True, solver=solver)
4929
4930
in_set = p.new_variable(dim=2)
4931
in_cut = p.new_variable(dim=1)
4932
4933
4934
# A vertex has to be in some set
4935
for v in g:
4936
p.add_constraint(in_set[0][v]+in_set[1][v],max=1,min=1)
4937
4938
# There is no empty set
4939
p.add_constraint(Sum([in_set[1][v] for v in g]),min=1)
4940
p.add_constraint(Sum([in_set[0][v] for v in g]),min=1)
4941
4942
if g.is_directed():
4943
# There is no edge from set 0 to set 1 which
4944
# is not in the cut
4945
# Besides, an edge can only be in the cut if its vertices
4946
# belong to different sets
4947
for (u,v) in g.edge_iterator(labels=None):
4948
p.add_constraint(in_set[0][u] + in_set[1][v] - in_cut[(u,v)], max = 1)
4949
p.add_constraint(in_set[0][u] + in_set[0][v] + in_cut[(u,v)], max = 2)
4950
p.add_constraint(in_set[1][u] + in_set[1][v] + in_cut[(u,v)], max = 2)
4951
else:
4952
4953
# Two adjacent vertices are in different sets if and only if
4954
# the edge between them is in the cut
4955
4956
for (u,v) in g.edge_iterator(labels=None):
4957
p.add_constraint(in_set[0][u]+in_set[1][v]-in_cut[reorder_edge(u,v)],max=1)
4958
p.add_constraint(in_set[1][u]+in_set[0][v]-in_cut[reorder_edge(u,v)],max=1)
4959
p.add_constraint(in_set[0][u] + in_set[0][v] + in_cut[reorder_edge(u,v)], max = 2)
4960
p.add_constraint(in_set[1][u] + in_set[1][v] + in_cut[reorder_edge(u,v)], max = 2)
4961
4962
4963
p.set_binary(in_set)
4964
p.set_binary(in_cut)
4965
4966
p.set_objective(Sum([weight(l ) * in_cut[reorder_edge(u,v)] for (u,v,l ) in g.edge_iterator()]))
4967
4968
if value_only:
4969
obj = p.solve(objective_only=True, log=verbose)
4970
return obj if use_edge_labels else Integer(round(obj))
4971
else:
4972
obj = p.solve(log=verbose)
4973
4974
if use_edge_labels:
4975
obj = Integer(round(obj))
4976
4977
val = [obj]
4978
4979
in_cut = p.get_values(in_cut)
4980
in_set = p.get_values(in_set)
4981
4982
edges = []
4983
for (u,v,l) in g.edge_iterator():
4984
if in_cut[reorder_edge(u,v)] == 1:
4985
edges.append((u,v,l))
4986
4987
val.append(edges)
4988
4989
if vertices:
4990
a = []
4991
b = []
4992
for v in g:
4993
if in_set[0][v] == 1:
4994
a.append(v)
4995
else:
4996
b.append(v)
4997
val.append([a,b])
4998
4999
return val
5000
5001
def longest_path(self, s=None, t=None, use_edge_labels=False, algorithm="MILP", solver=None, verbose=0):
5002
r"""
5003
Returns a longest path of ``self``.
5004
5005
INPUT:
5006
5007
- ``s`` (vertex) -- forces the source of the path (the method then
5008
returns the longest path starting at ``s``). The argument is set to
5009
``None`` by default, which means that no constraint is set upon the
5010
first vertex in the path.
5011
5012
- ``t`` (vertex) -- forces the destination of the path (the method then
5013
returns the longest path ending at ``t``). The argument is set to
5014
``None`` by default, which means that no constraint is set upon the
5015
last vertex in the path.
5016
5017
- ``use_edge_labels`` (boolean) -- whether the labels on the edges are
5018
to be considered as weights (a label set to ``None`` or ``{}`` being
5019
considered as a weight of `1`). Set to ``False`` by default.
5020
5021
- ``algorithm`` -- one of ``"MILP"`` (default) or ``"backtrack"``. Two
5022
remarks on this respect:
5023
5024
* While the MILP formulation returns an exact answer, the
5025
backtrack algorithm is a randomized heuristic.
5026
5027
* As the backtrack algorithm does not support edge weighting,
5028
setting ``use_edge_labels=True`` will force the use of the MILP
5029
algorithm.
5030
5031
- ``solver`` -- (default: ``None``) Specify the Linear Program (LP)
5032
solver to be used. If set to ``None``, the default one is used. For
5033
more information on LP solvers and which default solver is used, see
5034
the method
5035
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
5036
of the class
5037
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
5038
5039
- ``verbose`` -- integer (default: ``0``). Sets the level of
5040
verbosity. Set to 0 by default, which means quiet.
5041
5042
.. NOTE::
5043
5044
The length of a path is assumed to be the number of its edges, or
5045
the sum of their labels.
5046
5047
OUTPUT:
5048
5049
A subgraph of ``self`` corresponding to a (directed if ``self`` is
5050
directed) longest path. If ``use_edge_labels == True``, a pair ``weight,
5051
path`` is returned.
5052
5053
ALGORITHM:
5054
5055
Mixed Integer Linear Programming. (This problem is known to be NP-Hard).
5056
5057
EXAMPLES:
5058
5059
Petersen's graph being hypohamiltonian, it has a longest path
5060
of length `n-2`::
5061
5062
sage: g = graphs.PetersenGraph()
5063
sage: lp = g.longest_path()
5064
sage: lp.order() >= g.order() - 2
5065
True
5066
5067
The heuristic totally agrees::
5068
5069
sage: g = graphs.PetersenGraph()
5070
sage: g.longest_path(algorithm="backtrack").edges()
5071
[(0, 1, None), (1, 2, None), (2, 3, None), (3, 4, None), (4, 9, None), (5, 7, None), (5, 8, None), (6, 8, None), (6, 9, None)]
5072
5073
Let us compute longest paths on random graphs with random weights. Each
5074
time, we ensure the resulting graph is indeed a path::
5075
5076
sage: for i in range(20):
5077
... g = graphs.RandomGNP(15, 0.3)
5078
... for u, v in g.edges(labels=False):
5079
... g.set_edge_label(u, v, random())
5080
... lp = g.longest_path()
5081
... if (not lp.is_forest() or
5082
... not max(lp.degree()) <= 2 or
5083
... not lp.is_connected()):
5084
... print("Error!")
5085
... break
5086
5087
TESTS:
5088
5089
The argument ``algorithm`` must be either ``'backtrack'`` or
5090
``'MILP'``::
5091
5092
sage: graphs.PetersenGraph().longest_path(algorithm="abc")
5093
Traceback (most recent call last):
5094
...
5095
ValueError: algorithm must be either 'backtrack' or 'MILP'
5096
5097
Disconnected graphs not weighted::
5098
5099
sage: g1 = graphs.PetersenGraph()
5100
sage: g2 = 2 * g1
5101
sage: lp1 = g1.longest_path()
5102
sage: lp2 = g2.longest_path()
5103
sage: len(lp1) == len(lp2)
5104
True
5105
5106
Disconnected graphs weighted::
5107
5108
sage: g1 = graphs.PetersenGraph()
5109
sage: for u,v in g.edges(labels=False):
5110
... g.set_edge_label(u, v, random())
5111
sage: g2 = 2 * g1
5112
sage: lp1 = g1.longest_path(use_edge_labels=True)
5113
sage: lp2 = g2.longest_path(use_edge_labels=True)
5114
sage: lp1[0] == lp2[0]
5115
True
5116
5117
Empty graphs::
5118
5119
sage: Graph().longest_path()
5120
Graph on 0 vertices
5121
sage: Graph().longest_path(use_edge_labels=True)
5122
[0, Graph on 0 vertices]
5123
sage: graphs.EmptyGraph().longest_path()
5124
Graph on 0 vertices
5125
sage: graphs.EmptyGraph().longest_path(use_edge_labels=True)
5126
[0, Graph on 0 vertices]
5127
5128
Trivial graphs::
5129
5130
sage: G = Graph()
5131
sage: G.add_vertex(0)
5132
sage: G.longest_path()
5133
Graph on 0 vertices
5134
sage: G.longest_path(use_edge_labels=True)
5135
[0, Graph on 0 vertices]
5136
sage: graphs.CompleteGraph(1).longest_path()
5137
Graph on 0 vertices
5138
sage: graphs.CompleteGraph(1).longest_path(use_edge_labels=True)
5139
[0, Graph on 0 vertices]
5140
5141
Random test for digraphs::
5142
5143
sage: for i in range(20):
5144
... g = digraphs.RandomDirectedGNP(15, 0.3)
5145
... for u, v in g.edges(labels=False):
5146
... g.set_edge_label(u, v, random())
5147
... lp = g.longest_path()
5148
... if (not lp.is_directed_acyclic() or
5149
... not max(lp.out_degree()) <= 1 or
5150
... not max(lp.in_degree()) <= 1 or
5151
... not lp.is_connected()):
5152
... print("Error!")
5153
... break
5154
5155
:trac:`13019`::
5156
5157
sage: g = graphs.CompleteGraph(5).to_directed()
5158
sage: g.longest_path(s=1,t=2)
5159
Subgraph of (Complete graph): Digraph on 5 vertices
5160
"""
5161
if use_edge_labels:
5162
algorithm = "MILP"
5163
if algorithm not in ("backtrack", "MILP"):
5164
raise ValueError("algorithm must be either 'backtrack' or 'MILP'")
5165
5166
# Quick improvement
5167
if not self.is_connected():
5168
if use_edge_labels:
5169
return max(g.longest_path(s=s, t=t,
5170
use_edge_labels=use_edge_labels,
5171
algorithm=algorithm)
5172
for g in self.connected_components_subgraphs())
5173
else:
5174
return max((g.longest_path(s=s, t=t,
5175
use_edge_labels=use_edge_labels,
5176
algorithm=algorithm)
5177
for g in self.connected_components_subgraphs()),
5178
key=lambda x: x.order())
5179
5180
# Stupid cases
5181
# - Graph having <= 1 vertex.
5182
#
5183
# - The source has outdegree 0 in a directed graph, or
5184
# degree 0, or is not a vertex of the graph.
5185
#
5186
# - The destination has indegree 0 in a directed graph, or
5187
# degree 0, or is not a vertex of the graph.
5188
#
5189
# - Both s and t are specified, but there is no path between
5190
# the two in a directed graph (the graph is connected).
5191
if (self.order() <= 1 or
5192
(s is not None and (
5193
(s not in self) or
5194
(self._directed and self.out_degree(s) == 0) or
5195
(not self._directed and self.degree(s) == 0))) or
5196
(t is not None and (
5197
(t not in self) or
5198
(self._directed and self.in_degree(t) == 0) or
5199
(not self._directed and self.degree(t) == 0))) or
5200
(self._directed and (s is not None) and (t is not None) and
5201
len(self.shortest_path(s, t)) == 0)):
5202
if self._directed:
5203
from sage.graphs.all import DiGraph
5204
return [0, DiGraph()] if use_edge_labels else DiGraph()
5205
from sage.graphs.all import Graph
5206
return [0, Graph()] if use_edge_labels else Graph()
5207
5208
# Calling the backtrack heuristic if asked
5209
if algorithm == "backtrack":
5210
from sage.graphs.generic_graph_pyx import find_hamiltonian as fh
5211
x = fh(self, find_path=True)[1]
5212
return self.subgraph(vertices=x, edges=zip(x[:-1], x[1:]))
5213
5214
##################
5215
# LP Formulation #
5216
##################
5217
5218
# Epsilon... Must be less than 1/(n+1), but we want to avoid
5219
# numerical problems...
5220
epsilon = 1/(6*float(self.order()))
5221
5222
# Associating a weight to a label
5223
if use_edge_labels:
5224
weight = lambda x: x if (x is not None and x != {}) else 1
5225
else:
5226
weight = lambda x: 1
5227
5228
from sage.numerical.mip import MixedIntegerLinearProgram, Sum
5229
p = MixedIntegerLinearProgram()
5230
5231
# edge_used[(u,v)] == 1 if (u,v) is used
5232
edge_used = p.new_variable(binary=True)
5233
5234
# relaxed version of the previous variable, to prevent the
5235
# creation of cycles
5236
r_edge_used = p.new_variable()
5237
5238
# vertex_used[v] == 1 if vertex v is used
5239
vertex_used = p.new_variable(binary=True)
5240
5241
if self._directed:
5242
# if edge uv is used, vu can not be
5243
for u, v in self.edges(labels=False):
5244
if self.has_edge(v, u):
5245
p.add_constraint(edge_used[(u,v)] + edge_used[(v,u)], max=1)
5246
# A vertex is used if one of its incident edges is
5247
for v in self:
5248
for e in self.incoming_edges(labels=False):
5249
p.add_constraint(vertex_used[v] - edge_used[e], min=0)
5250
for e in self.outgoing_edges(labels=False):
5251
p.add_constraint(vertex_used[v] - edge_used[e], min=0)
5252
# A path is a tree. If n vertices are used, at most n-1 edges are
5253
p.add_constraint(
5254
Sum(vertex_used[v] for v in self)
5255
- Sum(edge_used[e] for e in self.edges(labels=False)),
5256
min=1, max=1)
5257
# A vertex has at most one incoming used edge and at most
5258
# one outgoing used edge
5259
for v in self:
5260
p.add_constraint(
5261
Sum(edge_used[(u,v)] for u in self.neighbors_in(v)),
5262
max=1)
5263
p.add_constraint(
5264
Sum(edge_used[(v,u)] for u in self.neighbors_out(v)),
5265
max=1)
5266
# r_edge_used is "more" than edge_used, though it ignores
5267
# the direction
5268
for u, v in self.edges(labels=False):
5269
p.add_constraint(r_edge_used[(u,v)]
5270
+ r_edge_used[(v,u)]
5271
- edge_used[(u,v)],
5272
min=0)
5273
# No cycles
5274
for v in self:
5275
p.add_constraint(
5276
Sum(r_edge_used[(u,v)] for u in self.neighbors(v)),
5277
max=1-epsilon)
5278
# Enforcing the source if asked.. If s is set, it has no
5279
# incoming edge and exactly one son
5280
if s is not None:
5281
p.add_constraint(
5282
Sum(edge_used[(u,s)] for u in self.neighbors_in(s)),
5283
max=0, min=0)
5284
p.add_constraint(
5285
Sum(edge_used[(s,u)] for u in self.neighbors_out(s)),
5286
min=1, max=1)
5287
# Enforcing the destination if asked.. If t is set, it has
5288
# no outgoing edge and exactly one parent
5289
if t is not None:
5290
p.add_constraint(
5291
Sum(edge_used[(u,t)] for u in self.neighbors_in(t)),
5292
min=1, max=1)
5293
p.add_constraint(
5294
Sum(edge_used[(t,u)] for u in self.neighbors_out(t)),
5295
max=0, min=0)
5296
# Defining the objective
5297
p.set_objective(
5298
Sum(weight(l) * edge_used[(u,v)] for u, v, l in self.edges()))
5299
else:
5300
# f_edge_used calls edge_used through reordering u and v
5301
# to avoid having two different variables
5302
f_edge_used = lambda u, v: edge_used[
5303
(u,v) if hash(u) < hash(v) else (v,u)]
5304
# A vertex is used if one of its incident edges is
5305
for v in self:
5306
for u in self.neighbors(v):
5307
p.add_constraint(vertex_used[v] - f_edge_used(u,v), min=0)
5308
# A path is a tree. If n vertices are used, at most n-1 edges are
5309
p.add_constraint(
5310
Sum(vertex_used[v] for v in self)
5311
- Sum(f_edge_used(u,v) for u, v in self.edges(labels=False)),
5312
min=1, max=1)
5313
# A vertex has at most two incident edges used
5314
for v in self:
5315
p.add_constraint(
5316
Sum(f_edge_used(u,v) for u in self.neighbors(v)), max=2)
5317
# r_edge_used is "more" than edge_used
5318
for u, v in self.edges(labels=False):
5319
p.add_constraint(r_edge_used[(u,v)]
5320
+ r_edge_used[(v,u)]
5321
- f_edge_used(u,v),
5322
min=0)
5323
# No cycles
5324
for v in self:
5325
p.add_constraint(
5326
Sum(r_edge_used[(u,v)] for u in self.neighbors(v)),
5327
max=1-epsilon)
5328
# Enforcing the destination if asked.. If s or t are set,
5329
# they have exactly one incident edge
5330
if s is not None:
5331
p.add_constraint(
5332
Sum(f_edge_used(s,u) for u in self.neighbors(s)),
5333
max=1, min=1)
5334
if t is not None:
5335
p.add_constraint(
5336
Sum(f_edge_used(t,u) for u in self.neighbors(t)),
5337
max=1, min=1)
5338
# Defining the objective
5339
p.set_objective(Sum(weight(l) * f_edge_used(u,v)
5340
for u, v, l in self.edges()))
5341
# Computing the result. No exception has to be raised, as this
5342
# problem always has a solution (there is at least one edge,
5343
# and a path from s to t if they are specified).
5344
p.solve(solver=solver, log=verbose)
5345
edge_used = p.get_values(edge_used)
5346
vertex_used = p.get_values(vertex_used)
5347
if self._directed:
5348
g = self.subgraph(
5349
vertices=(v for v in self if vertex_used[v] >= 0.5),
5350
edges=((u,v,l) for u, v, l in self.edges()
5351
if edge_used[(u,v)] >= 0.5))
5352
else:
5353
g = self.subgraph(
5354
vertices=(v for v in self if vertex_used[v] >= 0.5),
5355
edges=((u,v,l) for u, v, l in self.edges()
5356
if f_edge_used(u,v) >= 0.5))
5357
if use_edge_labels:
5358
return sum(map(weight, g.edge_labels())), g
5359
else:
5360
return g
5361
5362
5363
def traveling_salesman_problem(self, use_edge_labels = False, solver = None, constraint_generation = None, verbose = 0, verbose_constraints = False):
5364
r"""
5365
Solves the traveling salesman problem (TSP)
5366
5367
Given a graph (resp. a digraph) `G` with weighted edges, the traveling
5368
salesman problem consists in finding a Hamiltonian cycle (resp. circuit)
5369
of the graph of minimum cost.
5370
5371
This TSP is one of the most famous NP-Complete problems, this function
5372
can thus be expected to take some time before returning its result.
5373
5374
INPUT:
5375
5376
- ``use_edge_labels`` (boolean) -- whether to consider the weights of
5377
the edges.
5378
5379
- If set to ``False`` (default), all edges are assumed to weight
5380
`1`
5381
5382
- If set to ``True``, the weights are taken into account, and the
5383
circuit returned is the one minimizing the sum of the weights.
5384
5385
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
5386
solver to be used. If set to ``None``, the default one is used. For
5387
more information on LP solvers and which default solver is used, see
5388
the method
5389
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
5390
of the class
5391
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
5392
5393
- ``constraint_generation`` (boolean) -- whether to use constraint
5394
generation when solving the Mixed Integer Linear Program.
5395
5396
When ``constraint_generation = None``, constraint generation is used
5397
whenever the graph has a density larger than 70%.
5398
5399
- ``verbose`` -- integer (default: ``0``). Sets the level of
5400
verbosity. Set to 0 by default, which means quiet.
5401
5402
- ``verbose_constraints`` -- whether to display which constraints are
5403
being generated.
5404
5405
OUTPUT:
5406
5407
A solution to the TSP, as a ``Graph`` object whose vertex set is `V(G)`,
5408
and whose edges are only those of the solution.
5409
5410
ALGORITHM:
5411
5412
This optimization problem is solved through the use of Linear
5413
Programming.
5414
5415
NOTE:
5416
5417
- This function is correctly defined for both graph and digraphs. In
5418
the second case, the returned cycle is a circuit of optimal cost.
5419
5420
EXAMPLES:
5421
5422
The Heawood graph is known to be Hamiltonian::
5423
5424
sage: g = graphs.HeawoodGraph()
5425
sage: tsp = g.traveling_salesman_problem()
5426
sage: tsp
5427
TSP from Heawood graph: Graph on 14 vertices
5428
5429
The solution to the TSP has to be connected ::
5430
5431
sage: tsp.is_connected()
5432
True
5433
5434
It must also be a `2`-regular graph::
5435
5436
sage: tsp.is_regular(k=2)
5437
True
5438
5439
And obviously it is a subgraph of the Heawood graph::
5440
5441
sage: all([ e in g.edges() for e in tsp.edges()])
5442
True
5443
5444
On the other hand, the Petersen Graph is known not to
5445
be Hamiltonian::
5446
5447
sage: g = graphs.PetersenGraph()
5448
sage: tsp = g.traveling_salesman_problem()
5449
Traceback (most recent call last):
5450
...
5451
ValueError: The given graph is not hamiltonian
5452
5453
5454
One easy way to change is is obviously to add to this graph the edges
5455
corresponding to a Hamiltonian cycle.
5456
5457
If we do this by setting the cost of these new edges to `2`, while the
5458
others are set to `1`, we notice that not all the edges we added are
5459
used in the optimal solution ::
5460
5461
sage: for u, v in g.edges(labels = None):
5462
... g.set_edge_label(u,v,1)
5463
5464
sage: cycle = graphs.CycleGraph(10)
5465
sage: for u,v in cycle.edges(labels = None):
5466
... if not g.has_edge(u,v):
5467
... g.add_edge(u,v)
5468
... g.set_edge_label(u,v,2)
5469
5470
sage: tsp = g.traveling_salesman_problem(use_edge_labels = True)
5471
sage: sum( tsp.edge_labels() ) < 2*10
5472
True
5473
5474
If we pick `1/2` instead of `2` as a cost for these new edges, they
5475
clearly become the optimal solution
5476
5477
sage: for u,v in cycle.edges(labels = None):
5478
... g.set_edge_label(u,v,1/2)
5479
5480
sage: tsp = g.traveling_salesman_problem(use_edge_labels = True)
5481
sage: sum( tsp.edge_labels() ) == (1/2)*10
5482
True
5483
5484
TESTS:
5485
5486
Comparing the results returned according to the value of
5487
``constraint_generation``. First, for graphs::
5488
5489
sage: from operator import itemgetter
5490
sage: n = 20
5491
sage: for i in range(20):
5492
... g = Graph()
5493
... g.allow_multiple_edges(False)
5494
... for u,v in graphs.RandomGNP(n,.2).edges(labels = False):
5495
... g.add_edge(u,v,round(random(),5))
5496
... for u,v in graphs.CycleGraph(n).edges(labels = False):
5497
... if not g.has_edge(u,v):
5498
... g.add_edge(u,v,round(random(),5))
5499
... v1 = g.traveling_salesman_problem(constraint_generation = False, use_edge_labels = True)
5500
... v2 = g.traveling_salesman_problem(use_edge_labels = True)
5501
... c1 = sum(map(itemgetter(2), v1.edges()))
5502
... c2 = sum(map(itemgetter(2), v2.edges()))
5503
... if c1 != c2:
5504
... print "Error !",c1,c2
5505
... break
5506
5507
Then for digraphs::
5508
5509
sage: from operator import itemgetter
5510
sage: set_random_seed(0)
5511
sage: n = 20
5512
sage: for i in range(20):
5513
... g = DiGraph()
5514
... g.allow_multiple_edges(False)
5515
... for u,v in digraphs.RandomDirectedGNP(n,.2).edges(labels = False):
5516
... g.add_edge(u,v,round(random(),5))
5517
... for u,v in digraphs.Circuit(n).edges(labels = False):
5518
... if not g.has_edge(u,v):
5519
... g.add_edge(u,v,round(random(),5))
5520
... v2 = g.traveling_salesman_problem(use_edge_labels = True)
5521
... v1 = g.traveling_salesman_problem(constraint_generation = False, use_edge_labels = True)
5522
... c1 = sum(map(itemgetter(2), v1.edges()))
5523
... c2 = sum(map(itemgetter(2), v2.edges()))
5524
... if c1 != c2:
5525
... print "Error !",c1,c2
5526
... print "With constraint generation :",c2
5527
... print "Without constraint generation :",c1
5528
... break
5529
5530
"""
5531
if constraint_generation is None:
5532
if self.density() > .7:
5533
constraint_generation = False
5534
else:
5535
constraint_generation = True
5536
5537
###############################
5538
# Quick checks of connectivity #
5539
###############################
5540
5541
# TODO : Improve it by checking vertex-connectivity instead of
5542
# edge-connectivity.... But calling the vertex_connectivity (which
5543
# builds a LP) is way too slow. These tests only run traversals written
5544
# in Cython --> hence FAST
5545
5546
if self.is_directed():
5547
if not self.is_strongly_connected():
5548
raise ValueError("The given graph is not hamiltonian")
5549
5550
else:
5551
# Checks whether the graph is 2-connected
5552
if not self.strong_orientation().is_strongly_connected():
5553
raise ValueError("The given graph is not hamiltonian")
5554
5555
5556
5557
############################
5558
# Deal with multiple edges #
5559
############################
5560
5561
if self.has_multiple_edges():
5562
g = self.copy()
5563
multi = self.multiple_edges()
5564
g.delete_edges(multi)
5565
g.allow_multiple_edges(False)
5566
if use_edge_labels:
5567
e = {}
5568
5569
for u,v,l in multi:
5570
u,v = (u,v) if u<v else (v,u)
5571
5572
# The weight of an edge is the minimum over
5573
# the weights of the parallel edges
5574
5575
# new value *if* ( none other *or* new==None and last > 1 *else* change nothing
5576
e[(u,v)] = l if (not e.has_key((u,v)) or ( (l is None or l == {}) and e[(u,v)] > 1 )) else e[(u,v)]
5577
5578
g.add_edges([(u,v) for (u,v),l in e.iteritems()])
5579
5580
else:
5581
from sage.sets.set import Set
5582
g.add_edges(Set([ (min(u,v),max(u,v)) for u,v,l in multi]))
5583
5584
else:
5585
g = self
5586
5587
from sage.numerical.mip import MixedIntegerLinearProgram, Sum
5588
from sage.numerical.mip import MIPSolverException
5589
5590
weight = lambda l : l if (l is not None and l) else 1
5591
5592
####################################################
5593
# Constraint-generation formulation of the problem #
5594
####################################################
5595
5596
if constraint_generation:
5597
5598
p = MixedIntegerLinearProgram(maximization = False,
5599
solver = solver,
5600
constraint_generation = True)
5601
5602
5603
# Directed Case #
5604
#################
5605
if g.is_directed():
5606
5607
from sage.graphs.all import DiGraph
5608
b = p.new_variable(binary = True, dim = 2)
5609
5610
# Objective function
5611
if use_edge_labels:
5612
p.set_objective(Sum([ weight(l)*b[u][v]
5613
for u,v,l in g.edges()]))
5614
5615
# All the vertices have in-degree 1 and out-degree 1
5616
for v in g:
5617
p.add_constraint(Sum([b[u][v] for u in g.neighbors_in(v)]),
5618
min = 1,
5619
max = 1)
5620
p.add_constraint(Sum([b[v][u] for u in g.neighbors_out(v)]),
5621
min = 1,
5622
max = 1)
5623
5624
# Initial Solve
5625
try:
5626
p.solve(log = verbose)
5627
except MIPSolverException:
5628
raise ValueError("The given graph is not hamiltonian")
5629
5630
5631
while True:
5632
# We build the DiGraph representing the current solution
5633
h = DiGraph()
5634
for u,v,l in g.edges():
5635
if p.get_values(b[u][v]) > .5:
5636
h.add_edge(u,v,l)
5637
5638
# If there is only one circuit, we are done !
5639
cc = h.connected_components()
5640
if len(cc) == 1:
5641
break
5642
5643
# Adding the corresponding constraint
5644
if verbose_constraints:
5645
print "Adding a constraint on set",cc[0]
5646
5647
5648
p.add_constraint(Sum(b[u][v] for u,v in
5649
g.edge_boundary(cc[0], labels = False)),
5650
min = 1)
5651
5652
try:
5653
p.solve(log = verbose)
5654
except MIPSolverException:
5655
raise ValueError("The given graph is not hamiltonian")
5656
5657
# Undirected Case #
5658
###################
5659
else:
5660
5661
from sage.graphs.all import Graph
5662
b = p.new_variable(binary = True)
5663
B = lambda u,v : b[(u,v)] if u<v else b[(v,u)]
5664
5665
# Objective function
5666
if use_edge_labels:
5667
p.set_objective(Sum([ weight(l)*B(u,v)
5668
for u,v,l in g.edges()]) )
5669
5670
# All the vertices have degree 2
5671
for v in g:
5672
p.add_constraint(Sum([ B(u,v) for u in g.neighbors(v)]),
5673
min = 2,
5674
max = 2)
5675
5676
# Initial Solve
5677
try:
5678
p.solve(log = verbose)
5679
except MIPSolverException:
5680
raise ValueError("The given graph is not hamiltonian")
5681
5682
while True:
5683
# We build the DiGraph representing the current solution
5684
h = Graph()
5685
for u,v,l in g.edges():
5686
if p.get_values(B(u,v)) > .5:
5687
h.add_edge(u,v,l)
5688
5689
# If there is only one circuit, we are done !
5690
cc = h.connected_components()
5691
if len(cc) == 1:
5692
break
5693
5694
# Adding the corresponding constraint
5695
if verbose_constraints:
5696
print "Adding a constraint on set",cc[0]
5697
5698
p.add_constraint(Sum(B(u,v) for u,v in
5699
g.edge_boundary(cc[0], labels = False)),
5700
min = 2)
5701
5702
try:
5703
p.solve(log = verbose)
5704
except MIPSolverException:
5705
raise ValueError("The given graph is not hamiltonian")
5706
5707
5708
5709
# We can now return the TSP !
5710
answer = self.subgraph(edges = h.edges())
5711
answer.set_pos(self.get_pos())
5712
answer.name("TSP from "+g.name())
5713
return answer
5714
5715
#################################################
5716
# ILP formulation without constraint generation #
5717
#################################################
5718
5719
5720
5721
p = MixedIntegerLinearProgram(maximization = False, solver = solver)
5722
5723
f = p.new_variable()
5724
r = p.new_variable()
5725
5726
eps = 1/(2*Integer(g.order()))
5727
x = g.vertex_iterator().next()
5728
5729
5730
if g.is_directed():
5731
5732
# returns the variable corresponding to arc u,v
5733
E = lambda u,v : f[(u,v)]
5734
5735
# All the vertices have in-degree 1 and out-degree 1
5736
for v in g:
5737
p.add_constraint(Sum([ f[(u,v)] for u in g.neighbors_in(v)]),
5738
min = 1,
5739
max = 1)
5740
5741
p.add_constraint(Sum([ f[(v,u)] for u in g.neighbors_out(v)]),
5742
min = 1,
5743
max = 1)
5744
5745
5746
# r is greater than f
5747
for u,v in g.edges(labels = None):
5748
if g.has_edge(v,u):
5749
if u < v:
5750
p.add_constraint( r[(u,v)] + r[(v,u)]- f[(u,v)] - f[(v,u)], min = 0)
5751
5752
# no 2-cycles
5753
p.add_constraint( f[(u,v)] + f[(v,u)], max = 1)
5754
5755
else:
5756
p.add_constraint( r[(u,v)] + r[(v,u)] - f[(u,v)], min = 0)
5757
5758
5759
# defining the answer when g is directed
5760
from sage.graphs.all import DiGraph
5761
tsp = DiGraph()
5762
else:
5763
5764
# reorders the edge as they can appear in the two different ways
5765
R = lambda x,y : (x,y) if x < y else (y,x)
5766
5767
# returns the variable corresponding to arc u,v
5768
E = lambda u,v : f[R(u,v)]
5769
5770
# All the vertices have degree 2
5771
for v in g:
5772
p.add_constraint(Sum([ f[R(u,v)] for u in g.neighbors(v)]),
5773
min = 2,
5774
max = 2)
5775
5776
# r is greater than f
5777
for u,v in g.edges(labels = None):
5778
p.add_constraint( r[(u,v)] + r[(v,u)] - f[R(u,v)], min = 0)
5779
5780
from sage.graphs.all import Graph
5781
5782
# defining the answer when g is not directed
5783
tsp = Graph()
5784
5785
5786
# no cycle which does not contain x
5787
for v in g:
5788
if v != x:
5789
p.add_constraint(Sum([ r[(u,v)] for u in g.neighbors(v)]),max = 1-eps)
5790
5791
5792
5793
5794
if use_edge_labels:
5795
p.set_objective(Sum([ weight(l)*E(u,v) for u,v,l in g.edges()]) )
5796
else:
5797
p.set_objective(None)
5798
5799
p.set_binary(f)
5800
5801
5802
5803
try:
5804
obj = p.solve(log = verbose)
5805
f = p.get_values(f)
5806
tsp.add_vertices(g.vertices())
5807
tsp.set_pos(g.get_pos())
5808
tsp.name("TSP from "+g.name())
5809
tsp.add_edges([(u,v,l) for u,v,l in g.edges() if E(u,v) == 1])
5810
5811
return tsp
5812
5813
except MIPSolverException:
5814
raise ValueError("The given graph is not Hamiltonian")
5815
5816
5817
def hamiltonian_cycle(self, algorithm='tsp' ):
5818
r"""
5819
Returns a Hamiltonian cycle/circuit of the current graph/digraph
5820
5821
A graph (resp. digraph) is said to be Hamiltonian
5822
if it contains as a subgraph a cycle (resp. a circuit)
5823
going through all the vertices.
5824
5825
Computing a Hamiltonian cycle/circuit being NP-Complete,
5826
this algorithm could run for some time depending on
5827
the instance.
5828
5829
ALGORITHM:
5830
5831
See ``Graph.traveling_salesman_problem`` for 'tsp' algorithm and
5832
``find_hamiltonian`` from ``sage.graphs.generic_graph_pyx``
5833
for 'backtrack' algorithm.
5834
5835
INPUT:
5836
5837
- ``algorithm`` - one of 'tsp' or 'backtrack'.
5838
5839
OUTPUT:
5840
5841
If using the 'tsp' algorithm, returns a Hamiltonian cycle/circuit if it
5842
exists; otherwise, raises a ``ValueError`` exception. If using the
5843
'backtrack' algorithm, returns a pair (B,P). If B is True then P is a
5844
Hamiltonian cycle and if B is False, P is a longest path found by the
5845
algorithm. Observe that if B is False, the graph may still be Hamiltonian.
5846
The 'backtrack' algorithm is only implemented for undirected
5847
graphs.
5848
5849
.. WARNING::
5850
5851
The 'backtrack' algorithm may loop endlessly on graphs
5852
with vertices of degree 1.
5853
5854
NOTE:
5855
5856
This function, as ``is_hamiltonian``, computes a Hamiltonian
5857
cycle if it exists : the user should *NOT* test for
5858
Hamiltonicity using ``is_hamiltonian`` before calling this
5859
function, as it would result in computing it twice.
5860
5861
The backtrack algorithm is only implemented for undirected graphs.
5862
5863
EXAMPLES:
5864
5865
The Heawood Graph is known to be Hamiltonian ::
5866
5867
sage: g = graphs.HeawoodGraph()
5868
sage: g.hamiltonian_cycle()
5869
TSP from Heawood graph: Graph on 14 vertices
5870
5871
The Petersen Graph, though, is not ::
5872
5873
sage: g = graphs.PetersenGraph()
5874
sage: g.hamiltonian_cycle()
5875
Traceback (most recent call last):
5876
...
5877
ValueError: The given graph is not hamiltonian
5878
5879
Now, using the backtrack algorithm in the Heawood graph ::
5880
5881
sage: G=graphs.HeawoodGraph()
5882
sage: G.hamiltonian_cycle(algorithm='backtrack')
5883
(True, [11, 10, 1, 2, 3, 4, 9, 8, 7, 6, 5, 0, 13, 12])
5884
5885
And now in the Petersen graph ::
5886
5887
sage: G=graphs.PetersenGraph()
5888
sage: G.hamiltonian_cycle(algorithm='backtrack')
5889
(False, [6, 8, 5, 0, 1, 2, 7, 9, 4, 3])
5890
5891
Finally, we test the algorithm in a cube graph, which is Hamiltonian ::
5892
5893
sage: G=graphs.CubeGraph(3)
5894
sage: G.hamiltonian_cycle(algorithm='backtrack')
5895
(True, ['010', '110', '100', '000', '001', '101', '111', '011'])
5896
5897
"""
5898
if algorithm=='tsp':
5899
from sage.numerical.mip import MIPSolverException
5900
5901
try:
5902
return self.traveling_salesman_problem(use_edge_labels = False)
5903
except MIPSolverException:
5904
raise ValueError("The given graph is not Hamiltonian")
5905
elif algorithm=='backtrack':
5906
from sage.graphs.generic_graph_pyx import find_hamiltonian as fh
5907
return fh( self )
5908
5909
else:
5910
raise ValueError("``algorithm`` (%s) should be 'tsp' or 'backtrack'."%(algorithm))
5911
5912
def flow(self, x, y, value_only=True, integer=False, use_edge_labels=True, vertex_bound=False, method = None, solver=None, verbose=0):
5913
r"""
5914
Returns a maximum flow in the graph from ``x`` to ``y``
5915
represented by an optimal valuation of the edges. For more
5916
information, see the
5917
`Wikipedia article on maximum flow
5918
<http://en.wikipedia.org/wiki/Max_flow>`_.
5919
5920
As an optimization problem, is can be expressed this way :
5921
5922
.. MATH::
5923
5924
\mbox{Maximize : }&\sum_{e\in G.edges()} w_e b_e\\
5925
\mbox{Such that : }&\forall v \in G, \sum_{(u,v)\in G.edges()} b_{(u,v)}\leq 1\\
5926
&\forall x\in G, b_x\mbox{ is a binary variable}
5927
5928
INPUT:
5929
5930
- ``x`` -- Source vertex
5931
5932
- ``y`` -- Sink vertex
5933
5934
- ``value_only`` -- boolean (default: ``True``)
5935
5936
- When set to ``True``, only the value of a maximal
5937
flow is returned.
5938
5939
- When set to ``False``, is returned a pair whose first element
5940
is the value of the maximum flow, and whose second value is
5941
a flow graph (a copy of the current graph, such that each edge
5942
has the flow using it as a label, the edges without flow being
5943
omitted).
5944
5945
- ``integer`` -- boolean (default: ``False``)
5946
5947
- When set to ``True``, computes an optimal solution under the
5948
constraint that the flow going through an edge has to be an
5949
integer.
5950
5951
- ``use_edge_labels`` -- boolean (default: ``True``)
5952
5953
- When set to ``True``, computes a maximum flow
5954
where each edge has a capacity defined by its label. (If
5955
an edge has no label, `1` is assumed.)
5956
5957
- When set to ``False``, each edge has capacity `1`.
5958
5959
- ``vertex_bound`` -- boolean (default: ``False``)
5960
5961
- When set to ``True``, sets the maximum flow leaving
5962
a vertex different from `x` to `1` (useful for vertex
5963
connectivity parameters).
5964
5965
- ``method`` -- There are currently two different
5966
implementations of this method :
5967
5968
* If ``method = "FF"``, a Python implementation of the
5969
Ford-Fulkerson algorithm is used (only available when
5970
``vertex_bound = False``)
5971
5972
* If ``method = "LP"``, the flow problem is solved using
5973
Linear Programming.
5974
5975
* If ``method = None`` (default), the Ford-Fulkerson
5976
implementation is used iif ``vertex_bound = False``.
5977
5978
- ``solver`` -- Specify a Linear Program solver to be used.
5979
If set to ``None``, the default one is used. function of
5980
``MixedIntegerLinearProgram``. See the documentation of
5981
``MixedIntegerLinearProgram.solve`` for more information.
5982
5983
Only useful when LP is used to solve the flow problem.
5984
5985
- ``verbose`` (integer) -- sets the level of verbosity. Set to 0
5986
by default (quiet).
5987
5988
Only useful when LP is used to solve the flow problem.
5989
5990
.. NOTE::
5991
5992
Even though the two different implementations are meant to
5993
return the same Flow values, they can not be expected to
5994
return the same Flow graphs.
5995
5996
Besides, the use of Linear Programming may possibly mean a
5997
(slight) numerical noise.
5998
5999
EXAMPLES:
6000
6001
Two basic applications of the flow method for the ``PappusGraph`` and the
6002
``ButterflyGraph`` with parameter `2` ::
6003
6004
sage: g=graphs.PappusGraph()
6005
sage: g.flow(1,2)
6006
3
6007
6008
::
6009
6010
sage: b=digraphs.ButterflyGraph(2)
6011
sage: b.flow(('00',1),('00',2))
6012
1
6013
6014
The flow method can be used to compute a matching in a bipartite graph
6015
by linking a source `s` to all the vertices of the first set and linking
6016
a sink `t` to all the vertices of the second set, then computing
6017
a maximum `s-t` flow ::
6018
6019
sage: g = DiGraph()
6020
sage: g.add_edges([('s',i) for i in range(4)])
6021
sage: g.add_edges([(i,4+j) for i in range(4) for j in range(4)])
6022
sage: g.add_edges([(4+i,'t') for i in range(4)])
6023
sage: [cardinal, flow_graph] = g.flow('s','t',integer=True,value_only=False)
6024
sage: flow_graph.delete_vertices(['s','t'])
6025
sage: len(flow_graph.edges())
6026
4
6027
6028
TESTS:
6029
6030
An exception if raised when forcing "FF" with ``vertex_bound = True``::
6031
6032
sage: g = graphs.PetersenGraph()
6033
sage: g.flow(0,1,vertex_bound = True, method = "FF")
6034
Traceback (most recent call last):
6035
...
6036
ValueError: This method does not support both vertex_bound=True and method="FF".
6037
6038
Or if the method is different from the expected values::
6039
6040
sage: g.flow(0,1, method="Divination")
6041
Traceback (most recent call last):
6042
...
6043
ValueError: The method argument has to be equal to either "FF", "LP" or None
6044
6045
The two methods are indeed returning the same results (possibly with
6046
some numerical noise, cf. :trac:`12362`)::
6047
6048
sage: g = graphs.RandomGNP(20,.3)
6049
sage: for u,v in g.edges(labels=False):
6050
... g.set_edge_label(u,v,round(random(),5))
6051
sage: flow_ff = g.flow(0,1, method="FF")
6052
sage: flow_lp = g.flow(0,1,method="LP")
6053
sage: abs(flow_ff-flow_lp) < 0.01
6054
True
6055
"""
6056
6057
if vertex_bound == True and method == "FF":
6058
raise ValueError("This method does not support both vertex_bound=True and method=\"FF\".")
6059
6060
if (method == "FF" or
6061
(method == None and vertex_bound == False)):
6062
return self._ford_fulkerson(x,y, value_only=value_only, integer=integer, use_edge_labels=use_edge_labels)
6063
6064
if method != "LP" and method != None:
6065
raise ValueError("The method argument has to be equal to either \"FF\", \"LP\" or None")
6066
6067
6068
from sage.numerical.mip import MixedIntegerLinearProgram, Sum
6069
g=self
6070
p=MixedIntegerLinearProgram(maximization=True, solver = solver)
6071
flow=p.new_variable(dim=1)
6072
6073
if use_edge_labels:
6074
from sage.rings.real_mpfr import RR
6075
capacity=lambda x: x if x in RR else 1
6076
else:
6077
capacity=lambda x: 1
6078
6079
if g.is_directed():
6080
# This function return the balance of flow at X
6081
flow_sum=lambda X: Sum([flow[(X,v)] for (u,v) in g.outgoing_edges([X],labels=None)])-Sum([flow[(u,X)] for (u,v) in g.incoming_edges([X],labels=None)])
6082
6083
# The flow leaving x
6084
flow_leaving = lambda X : Sum([flow[(uu,vv)] for (uu,vv) in g.outgoing_edges([X],labels=None)])
6085
6086
# The flow to be considered when defining the capacity contraints
6087
capacity_sum = lambda u,v : flow[(u,v)]
6088
6089
else:
6090
# This function return the balance of flow at X
6091
flow_sum=lambda X:Sum([flow[(X,v)]-flow[(v,X)] for v in g[X]])
6092
6093
# The flow leaving x
6094
flow_leaving = lambda X : Sum([flow[(X,vv)] for vv in g[X]])
6095
6096
# The flow to be considered when defining the capacity contraints
6097
capacity_sum = lambda u,v : flow[(u,v)] + flow[(v,u)]
6098
6099
# Maximizes the flow leaving x
6100
p.set_objective(flow_sum(x))
6101
6102
# Elsewhere, the flow is equal to 0
6103
for v in g:
6104
if v!=x and v!=y:
6105
p.add_constraint(flow_sum(v),min=0,max=0)
6106
6107
# Capacity constraints
6108
for (u,v,w) in g.edges():
6109
p.add_constraint(capacity_sum(u,v),max=capacity(w))
6110
6111
# No vertex except the sources can send more than 1
6112
if vertex_bound:
6113
for v in g:
6114
if v!=x and v!=y:
6115
p.add_constraint(flow_leaving(v),max=1)
6116
6117
if integer:
6118
p.set_integer(flow)
6119
6120
6121
if value_only:
6122
return p.solve(objective_only=True, log = verbose)
6123
6124
obj=p.solve(log = verbose)
6125
6126
if integer or use_edge_labels is False:
6127
obj = Integer(round(obj))
6128
6129
flow=p.get_values(flow)
6130
# Builds a clean flow Draph
6131
flow_graph = g._build_flow_graph(flow, integer=integer)
6132
6133
# Which could be a Graph
6134
if not self.is_directed():
6135
from sage.graphs.graph import Graph
6136
flow_graph = Graph(flow_graph)
6137
6138
return [obj,flow_graph]
6139
6140
def _ford_fulkerson(self, s, t, use_edge_labels = False, integer = False, value_only = True):
6141
r"""
6142
Python implementation of the Ford-Fulkerson algorithm.
6143
6144
This method is a Python implementation of the Ford-Fulkerson
6145
max-flow algorithm, which is (slightly) faster than the LP
6146
implementation.
6147
6148
INPUT:
6149
6150
- ``s`` -- Source vertex
6151
6152
- ``t`` -- Sink vertex
6153
6154
- ``value_only`` -- boolean (default: ``True``)
6155
6156
- When set to ``True``, only the value of a maximal
6157
flow is returned.
6158
6159
- When set to ``False``, is returned a pair whose first element
6160
is the value of the maximum flow, and whose second value is
6161
a flow graph (a copy of the current graph, such that each edge
6162
has the flow using it as a label, the edges without flow being
6163
omitted).
6164
6165
- ``integer`` -- boolean (default: ``False``)
6166
6167
- When set to ``True``, computes an optimal solution under the
6168
constraint that the flow going through an edge has to be an
6169
integer.
6170
6171
- ``use_edge_labels`` -- boolean (default: ``True``)
6172
6173
- When set to ``True``, computes a maximum flow
6174
where each edge has a capacity defined by its label. (If
6175
an edge has no label, `1` is assumed.)
6176
6177
- When set to ``False``, each edge has capacity `1`.
6178
6179
EXAMPLES:
6180
6181
Two basic applications of the flow method for the ``PappusGraph`` and the
6182
``ButterflyGraph`` with parameter `2` ::
6183
6184
sage: g=graphs.PappusGraph()
6185
sage: g._ford_fulkerson(1,2)
6186
3
6187
6188
::
6189
6190
sage: b=digraphs.ButterflyGraph(2)
6191
sage: b._ford_fulkerson(('00',1),('00',2))
6192
1
6193
6194
The flow method can be used to compute a matching in a bipartite graph
6195
by linking a source `s` to all the vertices of the first set and linking
6196
a sink `t` to all the vertices of the second set, then computing
6197
a maximum `s-t` flow ::
6198
6199
sage: g = DiGraph()
6200
sage: g.add_edges([('s',i) for i in range(4)])
6201
sage: g.add_edges([(i,4+j) for i in range(4) for j in range(4)])
6202
sage: g.add_edges([(4+i,'t') for i in range(4)])
6203
sage: [cardinal, flow_graph] = g._ford_fulkerson('s','t',integer=True,value_only=False)
6204
sage: flow_graph.delete_vertices(['s','t'])
6205
sage: len(flow_graph.edges(labels=None))
6206
4
6207
"""
6208
from sage.graphs.digraph import DiGraph
6209
from sage.functions.other import floor
6210
6211
# Whether we should consider the edges labeled
6212
if use_edge_labels:
6213
l_capacity=lambda x: 1 if (x is None or x == {}) else (floor(x) if integer else x)
6214
else:
6215
l_capacity=lambda x: 1
6216
6217
directed = self.is_directed()
6218
6219
# Associated to each edge (u,v) of the flow graph its capacity
6220
capacity = {}
6221
# Associates to each edge (u,v) of the graph the (directed)
6222
# flow going through it
6223
flow = {}
6224
6225
# Residual graph. Only contains edge on which some flow can be
6226
# sent. This can happen both when the flow going through the
6227
# current edge is strictly less than its capacity, or when
6228
# there exists a back arc with non-null flow
6229
residual = DiGraph()
6230
6231
# Initializing the variables
6232
if directed:
6233
for u,v,l in self.edge_iterator():
6234
if l_capacity(l) > 0:
6235
capacity[(u,v)] = l_capacity(l) + capacity.get((u,v),0)
6236
capacity[(v,u)] = capacity.get((v,u),0)
6237
residual.add_edge(u,v)
6238
flow[(u,v)] = 0
6239
flow[(v,u)] = 0
6240
else:
6241
for u,v,l in self.edge_iterator():
6242
if l_capacity(l) > 0:
6243
capacity[(u,v)] = l_capacity(l) + capacity.get((u,v),0)
6244
capacity[(v,u)] = l_capacity(l) + capacity.get((v,u),0)
6245
residual.add_edge(u,v)
6246
residual.add_edge(v,u)
6247
flow[(u,v)] = 0
6248
flow[(v,u)] = 0
6249
6250
# Reqrites a path as a list of edges :
6251
# ex : [0,1,2,3,4,5] becomes [(0,1), (1,2), (2,3), (3,4), (4,5)]
6252
path_to_edges = lambda P : zip(P[:-1],P[1:])
6253
6254
# Rewrites a path as a list of edges labeled with their
6255
# available capacity
6256
path_to_labelled_edges = lambda P : map(lambda (x,y) : (x,y,capacity[(x,y)]-flow[(x,y)] + flow[(y,x)]),path_to_edges(P))
6257
6258
# Total flow going from s to t
6259
flow_intensity = 0
6260
6261
while True:
6262
6263
# If there is a shortest path from s to t
6264
path = residual.shortest_path(s,t)
6265
if not path:
6266
break
6267
6268
# We are rewriting the shortest path as a sequence of
6269
# edges whose labels are their available capacities
6270
edges = path_to_labelled_edges(path)
6271
6272
# minimum capacity available on the whole path
6273
epsilon = min(map( lambda x : x[2], edges))
6274
6275
flow_intensity = flow_intensity + epsilon
6276
6277
# Updating variables
6278
for uu,vv,ll in edges:
6279
6280
# The flow on the back arc
6281
other = flow[(vv,uu)]
6282
flow[(uu,vv)] = flow[(uu,vv)] + max(0,epsilon-other)
6283
flow[(vv,uu)] = other - min(other, epsilon)
6284
6285
# If the current edge is fully used, we do not need it
6286
# anymore in the residual graph
6287
if capacity[(uu,vv)] - flow[(uu,vv)] + flow[(vv,uu)] == 0:
6288
residual.delete_edge(uu,vv)
6289
6290
# If the back arc does not exist, it now does as the
6291
# edge (uu,vv) has a flow of at least epsilon>0
6292
if not residual.has_edge(vv,uu):
6293
residual.add_edge(vv,uu,epsilon)
6294
6295
if value_only:
6296
return flow_intensity
6297
6298
# Building and returning the flow graph
6299
g = DiGraph()
6300
g.add_edges([(x,y,l) for ((x,y),l) in flow.iteritems() if l > 0])
6301
g.set_pos(self.get_pos())
6302
6303
return flow_intensity, g
6304
6305
def multicommodity_flow(self, terminals, integer=True, use_edge_labels=False,vertex_bound=False, solver=None, verbose=0):
6306
r"""
6307
Solves a multicommodity flow problem.
6308
6309
In the multicommodity flow problem, we are given a set of pairs
6310
`(s_i, t_i)`, called terminals meaning that `s_i` is willing
6311
some flow to `t_i`.
6312
6313
Even though it is a natural generalisation of the flow problem
6314
this version of it is NP-Complete to solve when the flows
6315
are required to be integer.
6316
6317
For more information, see the
6318
`Wikipedia page on multicommodity flows
6319
<http://en.wikipedia.org/wiki/Multi-commodity_flow_problem>`.
6320
6321
INPUT:
6322
6323
- ``terminals`` -- a list of pairs `(s_i, t_i)` or triples
6324
`(s_i, t_i, w_i)` representing a flow from `s_i` to `t_i`
6325
of intensity `w_i`. When the pairs are of size `2`, a intensity
6326
of `1` is assumed.
6327
6328
- ``integer`` (boolean) -- whether to require an integer multicommodity
6329
flow
6330
6331
- ``use_edge_labels`` (boolean) -- whether to consider the label of edges
6332
as numerical values representing a capacity. If set to ``False``, a capacity
6333
of `1` is assumed
6334
6335
- ``vertex_bound`` (boolean) -- whether to require that a vertex can stand at most
6336
`1` commodity of flow through it of intensity `1`. Terminals can obviously
6337
still send or receive several units of flow even though vertex_bound is set
6338
to ``True``, as this parameter is meant to represent topological properties.
6339
6340
- ``solver`` -- Specify a Linear Program solver to be used.
6341
If set to ``None``, the default one is used.
6342
function of ``MixedIntegerLinearProgram``. See the documentation of ``MixedIntegerLinearProgram.solve``
6343
for more informations.
6344
6345
- ``verbose`` (integer) -- sets the level of verbosity. Set to 0
6346
by default (quiet).
6347
6348
ALGORITHM:
6349
6350
(Mixed Integer) Linear Program, depending on the value of ``integer``.
6351
6352
EXAMPLE:
6353
6354
An easy way to obtain a satisfiable multiflow is to compute
6355
a matching in a graph, and to consider the paired vertices
6356
as terminals ::
6357
6358
sage: g = graphs.PetersenGraph()
6359
sage: matching = [(u,v) for u,v,_ in g.matching()]
6360
sage: h = g.multicommodity_flow(matching)
6361
sage: len(h)
6362
5
6363
6364
We could also have considered ``g`` as symmetric and computed
6365
the multiflow in this version instead. In this case, however
6366
edges can be used in both directions at the same time::
6367
6368
sage: h = DiGraph(g).multicommodity_flow(matching)
6369
sage: len(h)
6370
5
6371
6372
An exception is raised when the problem has no solution ::
6373
6374
sage: h = g.multicommodity_flow([(u,v,3) for u,v in matching])
6375
Traceback (most recent call last):
6376
...
6377
ValueError: The multiflow problem has no solution
6378
"""
6379
6380
from sage.numerical.mip import MixedIntegerLinearProgram , Sum
6381
g=self
6382
p=MixedIntegerLinearProgram(maximization=True, solver = solver)
6383
6384
# Adding the intensity if not present
6385
terminals = [(x if len(x) == 3 else (x[0],x[1],1)) for x in terminals]
6386
6387
# defining the set of terminals
6388
set_terminals = set([])
6389
for s,t,_ in terminals:
6390
set_terminals.add(s)
6391
set_terminals.add(t)
6392
6393
# flow[i][(u,v)] is the flow of commodity i going from u to v
6394
flow=p.new_variable(dim=2)
6395
6396
# Whether to use edge labels
6397
if use_edge_labels:
6398
from sage.rings.real_mpfr import RR
6399
capacity=lambda x: x if x in RR else 1
6400
else:
6401
capacity=lambda x: 1
6402
6403
if g.is_directed():
6404
# This function return the balance of flow at X
6405
flow_sum=lambda i,X: Sum([flow[i][(X,v)] for (u,v) in g.outgoing_edges([X],labels=None)])-Sum([flow[i][(u,X)] for (u,v) in g.incoming_edges([X],labels=None)])
6406
6407
# The flow leaving x
6408
flow_leaving = lambda i,X : Sum([flow[i][(uu,vv)] for (uu,vv) in g.outgoing_edges([X],labels=None)])
6409
6410
# the flow to consider when defining the capacity contraints
6411
capacity_sum = lambda i,u,v : flow[i][(u,v)]
6412
6413
else:
6414
# This function return the balance of flow at X
6415
flow_sum=lambda i,X:Sum([flow[i][(X,v)]-flow[i][(v,X)] for v in g[X]])
6416
6417
# The flow leaving x
6418
flow_leaving = lambda i, X : Sum([flow[i][(X,vv)] for vv in g[X]])
6419
6420
# the flow to consider when defining the capacity contraints
6421
capacity_sum = lambda i,u,v : flow[i][(u,v)] + flow[i][(v,u)]
6422
6423
6424
# Flow constraints
6425
for i,(s,t,l) in enumerate(terminals):
6426
for v in g:
6427
if v == s:
6428
p.add_constraint(flow_sum(i,v),min=l,max=l)
6429
elif v == t:
6430
p.add_constraint(flow_sum(i,v),min=-l,max=-l)
6431
else:
6432
p.add_constraint(flow_sum(i,v),min=0,max=0)
6433
6434
6435
# Capacity constraints
6436
for (u,v,w) in g.edges():
6437
p.add_constraint(Sum([capacity_sum(i,u,v) for i in range(len(terminals))]),max=capacity(w))
6438
6439
6440
if vertex_bound:
6441
6442
# Any vertex
6443
for v in g.vertices():
6444
6445
# which is an endpoint
6446
if v in set_terminals:
6447
for i,(s,t,_) in enumerate(terminals):
6448
6449
# only tolerates the commodities of which it is an endpoint
6450
if not (v==s or v==t):
6451
p.add_constraint(flow_leaving(i,v), max = 0)
6452
6453
# which is not an endpoint
6454
else:
6455
# can stand at most 1 unit of flow through itself
6456
p.add_constraint(Sum([flow_leaving(i,v) for i in range(len(terminals))]), max = 1)
6457
6458
p.set_objective(None)
6459
6460
if integer:
6461
p.set_integer(flow)
6462
6463
from sage.numerical.mip import MIPSolverException
6464
6465
try:
6466
obj=p.solve(log = verbose)
6467
except MIPSolverException:
6468
raise ValueError("The multiflow problem has no solution")
6469
6470
flow=p.get_values(flow)
6471
6472
# building clean flow digraphs
6473
flow_graphs = [g._build_flow_graph(flow[i], integer=integer) for i in range(len(terminals))]
6474
6475
# which could be .. graphs !
6476
if not self.is_directed():
6477
from sage.graphs.graph import Graph
6478
flow_graphs = map(Graph, flow_graphs)
6479
6480
return flow_graphs
6481
6482
def _build_flow_graph(self, flow, integer):
6483
r"""
6484
Builds a "clean" flow graph
6485
6486
It build it, then looks for circuits and removes them
6487
6488
INPUT:
6489
6490
- ``flow`` -- a dictionary associating positive numerical values
6491
to edges
6492
6493
- ``integer`` (boolean) -- whether the values from ``flow`` are the solution
6494
of an integer flow. In this case, a value of less than .5 is assumed to be 0
6495
6496
6497
EXAMPLE:
6498
6499
This method is tested in ``flow`` and ``multicommodity_flow``::
6500
6501
sage: g = Graph()
6502
sage: g.add_edge(0,1)
6503
sage: f = g._build_flow_graph({(0,1):1}, True)
6504
"""
6505
6506
from sage.graphs.digraph import DiGraph
6507
g = DiGraph()
6508
6509
# add significant edges
6510
for (u,v),l in flow.iteritems():
6511
if l > 0 and not (integer and l<.5):
6512
g.add_edge(u,v,l)
6513
6514
# stupid way to find Cycles. Will be fixed by #8932
6515
# for any vertex, for any of its in-neighbors, tried to find a cycle
6516
for v in g:
6517
for u in g.neighbor_in_iterator(v):
6518
6519
# the edge from u to v could have been removed in a previous iteration
6520
if not g.has_edge(u,v):
6521
break
6522
sp = g.shortest_path(v,u)
6523
if sp != []:
6524
6525
#find the minimm value along the cycle.
6526
m = g.edge_label(u,v)
6527
for i in range(len(sp)-1):
6528
m = min(m,g.edge_label(sp[i],sp[i+1]))
6529
6530
# removes it from all the edges of the cycle
6531
sp.append(v)
6532
for i in range(len(sp)-1):
6533
l = g.edge_label(sp[i],sp[i+1]) - m
6534
6535
# an edge with flow 0 is removed
6536
if l == 0:
6537
g.delete_edge(sp[i],sp[i+1])
6538
else:
6539
g.set_edge_label(l)
6540
6541
# if integer is set, round values and deletes zeroes
6542
if integer:
6543
for (u,v,l) in g.edges():
6544
if l<.5:
6545
g.delete_edge(u,v)
6546
else:
6547
g.set_edge_label(u,v, round(l))
6548
6549
# returning a graph with the same embedding, the corersponding name, etc ...
6550
h = self.subgraph(edges=[])
6551
h.delete_vertices([v for v in self if (v not in g) or g.degree(v)==0])
6552
h.add_edges(g.edges())
6553
6554
return h
6555
6556
def disjoint_routed_paths(self,pairs, solver=None, verbose=0):
6557
r"""
6558
Returns a set of disjoint routed paths.
6559
6560
Given a set of pairs `(s_i,t_i)`, a set
6561
of disjoint routed paths is a set of
6562
`s_i-t_i` paths which can interset at their endpoints
6563
and are vertex-disjoint otherwise.
6564
6565
INPUT:
6566
6567
- ``pairs`` -- list of pairs of vertices
6568
6569
- ``solver`` -- Specify a Linear Program solver to be used.
6570
If set to ``None``, the default one is used.
6571
function of ``MixedIntegerLinearProgram``. See the documentation of ``MixedIntegerLinearProgram.solve``
6572
for more informations.
6573
6574
- ``verbose`` (integer) -- sets the level of verbosity. Set to `0`
6575
by default (quiet).
6576
6577
EXAMPLE:
6578
6579
Given a grid, finding two vertex-disjoint
6580
paths, the first one from the top-left corner
6581
to the bottom-left corner, and the second from
6582
the top-right corner to the bottom-right corner
6583
is easy ::
6584
6585
sage: g = graphs.GridGraph([5,5])
6586
sage: p1,p2 = g.disjoint_routed_paths( [((0,0), (0,4)), ((4,4), (4,0))])
6587
6588
Though there is obviously no solution to the problem
6589
in which each corner is sending information to the opposite
6590
one::
6591
6592
sage: g = graphs.GridGraph([5,5])
6593
sage: p1,p2 = g.disjoint_routed_paths( [((0,0), (4,4)), ((0,4), (4,0))])
6594
Traceback (most recent call last):
6595
...
6596
ValueError: The disjoint routed paths do not exist.
6597
"""
6598
6599
try:
6600
return self.multicommodity_flow(pairs, vertex_bound = True, solver=solver, verbose=verbose)
6601
except ValueError:
6602
raise ValueError("The disjoint routed paths do not exist.")
6603
6604
def edge_disjoint_paths(self, s, t, method = "FF"):
6605
r"""
6606
Returns a list of edge-disjoint paths between two
6607
vertices as given by Menger's theorem.
6608
6609
The edge version of Menger's theorem asserts that the size
6610
of the minimum edge cut between two vertices `s` and`t`
6611
(the minimum number of edges whose removal disconnects `s`
6612
and `t`) is equal to the maximum number of pairwise
6613
edge-independent paths from `s` to `t`.
6614
6615
This function returns a list of such paths.
6616
6617
INPUT:
6618
6619
- ``method`` -- There are currently two different
6620
implementations of this method :
6621
6622
* If ``method = "FF"`` (default), a Python implementation of the
6623
Ford-Fulkerson algorithm is used.
6624
6625
* If ``method = "LP"``, the flow problem is solved using
6626
Linear Programming.
6627
6628
.. NOTE::
6629
6630
This function is topological : it does not take the eventual
6631
weights of the edges into account.
6632
6633
EXAMPLE:
6634
6635
In a complete bipartite graph ::
6636
6637
sage: g = graphs.CompleteBipartiteGraph(2,3)
6638
sage: g.edge_disjoint_paths(0,1)
6639
[[0, 2, 1], [0, 3, 1], [0, 4, 1]]
6640
"""
6641
6642
[obj, flow_graph] = self.flow(s,t,value_only=False, integer=True, use_edge_labels=False, method=method)
6643
6644
paths = []
6645
6646
while True:
6647
path = flow_graph.shortest_path(s,t)
6648
if not path:
6649
break
6650
v = s
6651
edges = []
6652
for w in path:
6653
edges.append((v,w))
6654
v=w
6655
flow_graph.delete_edges(edges)
6656
paths.append(path)
6657
6658
return paths
6659
6660
def vertex_disjoint_paths(self, s, t):
6661
r"""
6662
Returns a list of vertex-disjoint paths between two
6663
vertices as given by Menger's theorem.
6664
6665
The vertex version of Menger's theorem asserts that the size
6666
of the minimum vertex cut between two vertices `s` and`t`
6667
(the minimum number of vertices whose removal disconnects `s`
6668
and `t`) is equal to the maximum number of pairwise
6669
vertex-independent paths from `s` to `t`.
6670
6671
This function returns a list of such paths.
6672
6673
EXAMPLE:
6674
6675
In a complete bipartite graph ::
6676
6677
sage: g = graphs.CompleteBipartiteGraph(2,3)
6678
sage: g.vertex_disjoint_paths(0,1)
6679
[[0, 2, 1], [0, 3, 1], [0, 4, 1]]
6680
"""
6681
6682
[obj, flow_graph] = self.flow(s,t,value_only=False, integer=True, use_edge_labels=False, vertex_bound=True)
6683
6684
paths = []
6685
6686
while True:
6687
path = flow_graph.shortest_path(s,t)
6688
if not path:
6689
break
6690
flow_graph.delete_vertices(path[1:-1])
6691
paths.append(path)
6692
6693
return paths
6694
6695
def matching(self, value_only=False, algorithm="Edmonds", use_edge_labels=True, solver=None, verbose=0):
6696
r"""
6697
Returns a maximum weighted matching of the graph
6698
represented by the list of its edges. For more information, see the
6699
`Wikipedia article on matchings
6700
<http://en.wikipedia.org/wiki/Matching_%28graph_theory%29>`_.
6701
6702
Given a graph `G` such that each edge `e` has a weight `w_e`,
6703
a maximum matching is a subset `S` of the edges of `G` of
6704
maximum weight such that no two edges of `S` are incident
6705
with each other.
6706
6707
As an optimization problem, it can be expressed as:
6708
6709
.. math::
6710
6711
\mbox{Maximize : }&\sum_{e\in G.edges()} w_e b_e\\
6712
\mbox{Such that : }&\forall v \in G, \sum_{(u,v)\in G.edges()} b_{(u,v)}\leq 1\\
6713
&\forall x\in G, b_x\mbox{ is a binary variable}
6714
6715
INPUT:
6716
6717
- ``value_only`` -- boolean (default: ``False``). When set to
6718
``True``, only the cardinal (or the weight) of the matching is
6719
returned.
6720
6721
- ``algorithm`` -- string (default: ``"Edmonds"``)
6722
6723
- ``"Edmonds"`` selects Edmonds' algorithm as implemented in NetworkX
6724
6725
- ``"LP"`` uses a Linear Program formulation of the matching problem
6726
6727
- ``use_edge_labels`` -- boolean (default: ``False``)
6728
6729
- When set to ``True``, computes a weighted matching where each edge
6730
is weighted by its label. (If an edge has no label, `1` is assumed.)
6731
6732
- When set to ``False``, each edge has weight `1`.
6733
6734
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
6735
solver to be used. If set to ``None``, the default one is used. For
6736
more information on LP solvers and which default solver is used, see
6737
the method
6738
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
6739
of the class
6740
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
6741
6742
- ``verbose`` -- integer (default: ``0``). Sets the level of
6743
verbosity. Set to 0 by default, which means quiet.
6744
Only useful when ``algorithm == "LP"``.
6745
6746
ALGORITHM:
6747
6748
The problem is solved using Edmond's algorithm implemented in
6749
NetworkX, or using Linear Programming depending on the value of
6750
``algorithm``.
6751
6752
EXAMPLES:
6753
6754
Maximum matching in a Pappus Graph::
6755
6756
sage: g = graphs.PappusGraph()
6757
sage: g.matching(value_only=True)
6758
9.0
6759
6760
Same test with the Linear Program formulation::
6761
6762
sage: g = graphs.PappusGraph()
6763
sage: g.matching(algorithm="LP", value_only=True)
6764
9.0
6765
6766
TESTS:
6767
6768
If ``algorithm`` is set to anything different from ``"Edmonds"`` or
6769
``"LP"``, an exception is raised::
6770
6771
sage: g = graphs.PappusGraph()
6772
sage: g.matching(algorithm="somethingdifferent")
6773
Traceback (most recent call last):
6774
...
6775
ValueError: Algorithm must be set to either "Edmonds" or "LP".
6776
"""
6777
from sage.rings.real_mpfr import RR
6778
weight = lambda x: x if x in RR else 1
6779
6780
if algorithm == "Edmonds":
6781
import networkx
6782
if use_edge_labels:
6783
g = networkx.Graph()
6784
for u, v, l in self.edges():
6785
g.add_edge(u, v, attr_dict={"weight": weight(l)})
6786
else:
6787
g = self.networkx_graph(copy=False)
6788
d = networkx.max_weight_matching(g)
6789
if value_only:
6790
if use_edge_labels:
6791
return sum([weight(self.edge_label(u, v))
6792
for u, v in d.iteritems()]) * 0.5
6793
else:
6794
return Integer(len(d))
6795
else:
6796
return [(u, v, self.edge_label(u, v))
6797
for u, v in d.iteritems() if u < v]
6798
6799
elif algorithm == "LP":
6800
from sage.numerical.mip import MixedIntegerLinearProgram, Sum
6801
g = self
6802
# returns the weight of an edge considering it may not be
6803
# weighted ...
6804
p = MixedIntegerLinearProgram(maximization=True, solver=solver)
6805
b = p.new_variable(dim=2)
6806
p.set_objective(
6807
Sum([weight(w) * b[min(u, v)][max(u, v)]
6808
for u, v, w in g.edges()]))
6809
# for any vertex v, there is at most one edge incident to v in
6810
# the maximum matching
6811
for v in g.vertex_iterator():
6812
p.add_constraint(
6813
Sum([b[min(u, v)][max(u, v)]
6814
for u in g.neighbors(v)]), max=1)
6815
p.set_binary(b)
6816
if value_only:
6817
if use_edge_labels:
6818
return p.solve(objective_only=True, log=verbose)
6819
else:
6820
return Integer(round(p.solve(objective_only=True, log=verbose)))
6821
else:
6822
p.solve(log=verbose)
6823
b = p.get_values(b)
6824
return [(u, v, w) for u, v, w in g.edges()
6825
if b[min(u, v)][max(u, v)] == 1]
6826
6827
else:
6828
raise ValueError(
6829
'Algorithm must be set to either "Edmonds" or "LP".')
6830
6831
def dominating_set(self, independent=False, value_only=False, solver=None, verbose=0):
6832
r"""
6833
Returns a minimum dominating set of the graph
6834
represented by the list of its vertices. For more information, see the
6835
`Wikipedia article on dominating sets
6836
<http://en.wikipedia.org/wiki/Dominating_set>`_.
6837
6838
A minimum dominating set `S` of a graph `G` is
6839
a set of its vertices of minimal cardinality such
6840
that any vertex of `G` is in `S` or has one of its neighbors
6841
in `S`.
6842
6843
As an optimization problem, it can be expressed as:
6844
6845
.. MATH::
6846
6847
\mbox{Minimize : }&\sum_{v\in G} b_v\\
6848
\mbox{Such that : }&\forall v \in G, b_v+\sum_{(u,v)\in G.edges()} b_u\geq 1\\
6849
&\forall x\in G, b_x\mbox{ is a binary variable}
6850
6851
INPUT:
6852
6853
- ``independent`` -- boolean (default: ``False``). If
6854
``independent=True``, computes a minimum independent dominating set.
6855
6856
- ``value_only`` -- boolean (default: ``False``)
6857
6858
- If ``True``, only the cardinality of a minimum
6859
dominating set is returned.
6860
6861
- If ``False`` (default), a minimum dominating set
6862
is returned as the list of its vertices.
6863
6864
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
6865
solver to be used. If set to ``None``, the default one is used. For
6866
more information on LP solvers and which default solver is used, see
6867
the method
6868
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
6869
of the class
6870
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
6871
6872
- ``verbose`` -- integer (default: ``0``). Sets the level of
6873
verbosity. Set to 0 by default, which means quiet.
6874
6875
EXAMPLES:
6876
6877
A basic illustration on a ``PappusGraph``::
6878
6879
sage: g=graphs.PappusGraph()
6880
sage: g.dominating_set(value_only=True)
6881
5
6882
6883
If we build a graph from two disjoint stars, then link their centers
6884
we will find a difference between the cardinality of an independent set
6885
and a stable independent set::
6886
6887
sage: g = 2 * graphs.StarGraph(5)
6888
sage: g.add_edge(0,6)
6889
sage: len(g.dominating_set())
6890
2
6891
sage: len(g.dominating_set(independent=True))
6892
6
6893
6894
"""
6895
from sage.numerical.mip import MixedIntegerLinearProgram, Sum
6896
g=self
6897
p=MixedIntegerLinearProgram(maximization=False, solver=solver)
6898
b=p.new_variable()
6899
6900
# For any vertex v, one of its neighbors or v itself is in
6901
# the minimum dominating set
6902
for v in g.vertices():
6903
p.add_constraint(b[v]+Sum([b[u] for u in g.neighbors(v)]),min=1)
6904
6905
6906
if independent:
6907
# no two adjacent vertices are in the set
6908
for (u,v) in g.edges(labels=None):
6909
p.add_constraint(b[u]+b[v],max=1)
6910
6911
# Minimizes the number of vertices used
6912
p.set_objective(Sum([b[v] for v in g.vertices()]))
6913
6914
p.set_integer(b)
6915
6916
if value_only:
6917
return Integer(round(p.solve(objective_only=True, log=verbose)))
6918
else:
6919
p.solve(log=verbose)
6920
b=p.get_values(b)
6921
return [v for v in g.vertices() if b[v]==1]
6922
6923
def edge_connectivity(self, value_only=True, use_edge_labels=False, vertices=False, solver=None, verbose=0):
6924
r"""
6925
Returns the edge connectivity of the graph. For more information, see
6926
the
6927
`Wikipedia article on connectivity
6928
<http://en.wikipedia.org/wiki/Connectivity_(graph_theory)>`_.
6929
6930
.. NOTE::
6931
6932
When the graph is a directed graph, this method actually computes
6933
the *strong* connectivity, (i.e. a directed graph is strongly
6934
`k`-connected if there are `k` disjoint paths between any two
6935
vertices `u, v`). If you do not want to consider strong
6936
connectivity, the best is probably to convert your ``DiGraph``
6937
object to a ``Graph`` object, and compute the connectivity of this
6938
other graph.
6939
6940
INPUT:
6941
6942
- ``value_only`` -- boolean (default: ``True``)
6943
6944
- When set to ``True`` (default), only the value is returned.
6945
6946
- When set to ``False``, both the value and a minimum edge cut
6947
are returned.
6948
6949
- ``use_edge_labels`` -- boolean (default: ``False``)
6950
6951
- When set to ``True``, computes a weighted minimum cut
6952
where each edge has a weight defined by its label. (If
6953
an edge has no label, `1` is assumed.)
6954
6955
- When set to ``False``, each edge has weight `1`.
6956
6957
- ``vertices`` -- boolean (default: ``False``)
6958
6959
- When set to ``True``, also returns the two sets of
6960
vertices that are disconnected by the cut. Implies
6961
``value_only=False``.
6962
6963
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
6964
solver to be used. If set to ``None``, the default one is used. For
6965
more information on LP solvers and which default solver is used, see
6966
the method
6967
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
6968
of the class
6969
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
6970
6971
- ``verbose`` -- integer (default: ``0``). Sets the level of
6972
verbosity. Set to 0 by default, which means quiet.
6973
6974
EXAMPLES:
6975
6976
A basic application on the PappusGraph::
6977
6978
sage: g = graphs.PappusGraph()
6979
sage: g.edge_connectivity()
6980
3
6981
6982
The edge connectivity of a complete graph ( and of a random graph )
6983
is its minimum degree, and one of the two parts of the bipartition
6984
is reduced to only one vertex. The cutedges isomorphic to a
6985
Star graph::
6986
6987
sage: g = graphs.CompleteGraph(5)
6988
sage: [ value, edges, [ setA, setB ]] = g.edge_connectivity(vertices=True)
6989
sage: print value
6990
4
6991
sage: len(setA) == 1 or len(setB) == 1
6992
True
6993
sage: cut = Graph()
6994
sage: cut.add_edges(edges)
6995
sage: cut.is_isomorphic(graphs.StarGraph(4))
6996
True
6997
6998
Even if obviously in any graph we know that the edge connectivity
6999
is less than the minimum degree of the graph::
7000
7001
sage: g = graphs.RandomGNP(10,.3)
7002
sage: min(g.degree()) >= g.edge_connectivity()
7003
True
7004
7005
If we build a tree then assign to its edges a random value, the
7006
minimum cut will be the edge with minimum value::
7007
7008
sage: g = graphs.RandomGNP(15,.5)
7009
sage: tree = Graph()
7010
sage: tree.add_edges(g.min_spanning_tree())
7011
sage: for u,v in tree.edge_iterator(labels=None):
7012
... tree.set_edge_label(u,v,random())
7013
sage: minimum = min([l for u,v,l in tree.edge_iterator()])
7014
sage: [value, [(u,v,l)]] = tree.edge_connectivity(value_only=False, use_edge_labels=True)
7015
sage: l == minimum
7016
True
7017
7018
When ``value_only = True``, this function is optimized for small
7019
connectivity values and does not need to build a linear program.
7020
7021
It is the case for connected graphs which are not
7022
connected ::
7023
7024
sage: g = 2 * graphs.PetersenGraph()
7025
sage: g.edge_connectivity()
7026
0.0
7027
7028
Or if they are just 1-connected ::
7029
7030
sage: g = graphs.PathGraph(10)
7031
sage: g.edge_connectivity()
7032
1.0
7033
7034
For directed graphs, the strong connectivity is tested
7035
through the dedicated function ::
7036
7037
sage: g = digraphs.ButterflyGraph(3)
7038
sage: g.edge_connectivity()
7039
0.0
7040
"""
7041
g=self
7042
7043
if vertices:
7044
value_only=False
7045
7046
if use_edge_labels:
7047
from sage.rings.real_mpfr import RR
7048
weight=lambda x: x if x in RR else 1
7049
else:
7050
weight=lambda x: 1
7051
7052
7053
# Better methods for small connectivity tests,
7054
# when one is not interested in cuts...
7055
if value_only and not use_edge_labels:
7056
7057
if self.is_directed():
7058
if not self.is_strongly_connected():
7059
return 0.0
7060
7061
else:
7062
if not self.is_connected():
7063
return 0.0
7064
7065
h = self.strong_orientation()
7066
if not h.is_strongly_connected():
7067
return 1.0
7068
7069
7070
if g.is_directed():
7071
reorder_edge = lambda x,y : (x,y)
7072
else:
7073
reorder_edge = lambda x,y : (x,y) if x<= y else (y,x)
7074
7075
from sage.numerical.mip import MixedIntegerLinearProgram, Sum
7076
7077
p = MixedIntegerLinearProgram(maximization=False, solver=solver)
7078
7079
in_set = p.new_variable(dim=2)
7080
in_cut = p.new_variable(dim=1)
7081
7082
7083
# A vertex has to be in some set
7084
for v in g:
7085
p.add_constraint(in_set[0][v]+in_set[1][v],max=1,min=1)
7086
7087
# There is no empty set
7088
p.add_constraint(Sum([in_set[1][v] for v in g]),min=1)
7089
p.add_constraint(Sum([in_set[0][v] for v in g]),min=1)
7090
7091
if g.is_directed():
7092
# There is no edge from set 0 to set 1 which
7093
# is not in the cut
7094
for (u,v) in g.edge_iterator(labels=None):
7095
p.add_constraint(in_set[0][u] + in_set[1][v] - in_cut[(u,v)], max = 1)
7096
else:
7097
7098
# Two adjacent vertices are in different sets if and only if
7099
# the edge between them is in the cut
7100
7101
for (u,v) in g.edge_iterator(labels=None):
7102
p.add_constraint(in_set[0][u]+in_set[1][v]-in_cut[reorder_edge(u,v)],max=1)
7103
p.add_constraint(in_set[1][u]+in_set[0][v]-in_cut[reorder_edge(u,v)],max=1)
7104
7105
7106
p.set_binary(in_set)
7107
p.set_binary(in_cut)
7108
7109
p.set_objective(Sum([weight(l ) * in_cut[reorder_edge(u,v)] for (u,v,l) in g.edge_iterator()]))
7110
7111
obj = p.solve(objective_only=value_only, log=verbose)
7112
7113
if use_edge_labels is False:
7114
obj = Integer(round(obj))
7115
7116
if value_only:
7117
return obj
7118
7119
else:
7120
val = [obj]
7121
7122
in_cut = p.get_values(in_cut)
7123
in_set = p.get_values(in_set)
7124
7125
edges = []
7126
for (u,v,l) in g.edge_iterator():
7127
if in_cut[reorder_edge(u,v)] == 1:
7128
edges.append((u,v,l))
7129
7130
val.append(edges)
7131
7132
if vertices:
7133
a = []
7134
b = []
7135
for v in g:
7136
if in_set[0][v] == 1:
7137
a.append(v)
7138
else:
7139
b.append(v)
7140
val.append([a,b])
7141
7142
return val
7143
7144
def vertex_connectivity(self, value_only=True, sets=False, solver=None, verbose=0):
7145
r"""
7146
Returns the vertex connectivity of the graph. For more information,
7147
see the
7148
`Wikipedia article on connectivity
7149
<http://en.wikipedia.org/wiki/Connectivity_(graph_theory)>`_.
7150
7151
.. NOTE::
7152
7153
* When the graph is a directed graph, this method actually computes
7154
the *strong* connectivity, (i.e. a directed graph is strongly
7155
`k`-connected if there are `k` disjoint paths between any two
7156
vertices `u, v`). If you do not want to consider strong
7157
connectivity, the best is probably to convert your ``DiGraph``
7158
object to a ``Graph`` object, and compute the connectivity of this
7159
other graph.
7160
7161
* By convention, a complete graph on `n` vertices is `n-1`
7162
connected. In this case, no certificate can be given as there is
7163
no pair of vertices split by a cut of size `k-1`. For this reason,
7164
the certificates returned in this situation are empty.
7165
7166
INPUT:
7167
7168
- ``value_only`` -- boolean (default: ``True``)
7169
7170
- When set to ``True`` (default), only the value is returned.
7171
7172
- When set to ``False`` , both the value and a minimum vertex cut are
7173
returned.
7174
7175
- ``sets`` -- boolean (default: ``False``)
7176
7177
- When set to ``True``, also returns the two sets of
7178
vertices that are disconnected by the cut.
7179
Implies ``value_only=False``
7180
7181
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
7182
solver to be used. If set to ``None``, the default one is used. For
7183
more information on LP solvers and which default solver is used, see
7184
the method
7185
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
7186
of the class
7187
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
7188
7189
- ``verbose`` -- integer (default: ``0``). Sets the level of
7190
verbosity. Set to 0 by default, which means quiet.
7191
7192
EXAMPLES:
7193
7194
A basic application on a ``PappusGraph``::
7195
7196
sage: g=graphs.PappusGraph()
7197
sage: g.vertex_connectivity()
7198
3
7199
7200
In a grid, the vertex connectivity is equal to the
7201
minimum degree, in which case one of the two sets is
7202
of cardinality `1`::
7203
7204
sage: g = graphs.GridGraph([ 3,3 ])
7205
sage: [value, cut, [ setA, setB ]] = g.vertex_connectivity(sets=True)
7206
sage: len(setA) == 1 or len(setB) == 1
7207
True
7208
7209
A vertex cut in a tree is any internal vertex::
7210
7211
sage: g = graphs.RandomGNP(15,.5)
7212
sage: tree = Graph()
7213
sage: tree.add_edges(g.min_spanning_tree())
7214
sage: [val, [cut_vertex]] = tree.vertex_connectivity(value_only=False)
7215
sage: tree.degree(cut_vertex) > 1
7216
True
7217
7218
When ``value_only = True``, this function is optimized for small
7219
connectivity values and does not need to build a linear program.
7220
7221
It is the case for connected graphs which are not
7222
connected::
7223
7224
sage: g = 2 * graphs.PetersenGraph()
7225
sage: g.vertex_connectivity()
7226
0.0
7227
7228
Or if they are just 1-connected::
7229
7230
sage: g = graphs.PathGraph(10)
7231
sage: g.vertex_connectivity()
7232
1.0
7233
7234
For directed graphs, the strong connectivity is tested
7235
through the dedicated function::
7236
7237
sage: g = digraphs.ButterflyGraph(3)
7238
sage: g.vertex_connectivity()
7239
0.0
7240
7241
A complete graph on `10` vertices is `9`-connected::
7242
7243
sage: g = graphs.CompleteGraph(10)
7244
sage: g.vertex_connectivity()
7245
9
7246
"""
7247
g=self
7248
7249
if sets:
7250
value_only=False
7251
7252
if g.is_clique():
7253
if value_only:
7254
return g.order()-1
7255
elif not sets:
7256
return g.order()-1, []
7257
else:
7258
return g.order()-1, [], [[],[]]
7259
7260
if value_only:
7261
if self.is_directed():
7262
if not self.is_strongly_connected():
7263
return 0.0
7264
7265
else:
7266
if not self.is_connected():
7267
return 0.0
7268
7269
if len(self.blocks_and_cut_vertices()[0]) > 1:
7270
return 1.0
7271
7272
7273
if g.is_directed():
7274
reorder_edge = lambda x,y : (x,y)
7275
else:
7276
reorder_edge = lambda x,y : (x,y) if x<= y else (y,x)
7277
7278
from sage.numerical.mip import MixedIntegerLinearProgram, Sum
7279
7280
p = MixedIntegerLinearProgram(maximization=False, solver=solver)
7281
7282
# Sets 0 and 2 are "real" sets while set 1 represents the cut
7283
in_set = p.new_variable(dim=2)
7284
7285
7286
# A vertex has to be in some set
7287
for v in g:
7288
p.add_constraint(in_set[0][v]+in_set[1][v]+in_set[2][v],max=1,min=1)
7289
7290
# There is no empty set
7291
p.add_constraint(Sum([in_set[0][v] for v in g]),min=1)
7292
p.add_constraint(Sum([in_set[2][v] for v in g]),min=1)
7293
7294
if g.is_directed():
7295
# There is no edge from set 0 to set 1 which
7296
# is not in the cut
7297
for (u,v) in g.edge_iterator(labels=None):
7298
p.add_constraint(in_set[0][u] + in_set[2][v], max = 1)
7299
else:
7300
7301
# Two adjacent vertices are in different sets if and only if
7302
# the edge between them is in the cut
7303
7304
for (u,v) in g.edge_iterator(labels=None):
7305
p.add_constraint(in_set[0][u]+in_set[2][v],max=1)
7306
p.add_constraint(in_set[2][u]+in_set[0][v],max=1)
7307
7308
7309
p.set_binary(in_set)
7310
7311
p.set_objective(Sum([in_set[1][v] for v in g]))
7312
7313
if value_only:
7314
return Integer(round(p.solve(objective_only=True, log=verbose)))
7315
else:
7316
val = [Integer(round(p.solve(log=verbose)))]
7317
7318
in_set = p.get_values(in_set)
7319
7320
7321
cut = []
7322
a = []
7323
b = []
7324
7325
for v in g:
7326
if in_set[0][v] == 1:
7327
a.append(v)
7328
elif in_set[1][v]==1:
7329
cut.append(v)
7330
else:
7331
b.append(v)
7332
7333
7334
val.append(cut)
7335
7336
if sets:
7337
val.append([a,b])
7338
7339
return val
7340
7341
7342
### Vertex handlers
7343
7344
def add_vertex(self, name=None):
7345
"""
7346
Creates an isolated vertex. If the vertex already exists, then
7347
nothing is done.
7348
7349
INPUT:
7350
7351
- ``name`` - Name of the new vertex. If no name is
7352
specified, then the vertex will be represented by the least integer
7353
not already representing a vertex. Name must be an immutable
7354
object, and cannot be None.
7355
7356
As it is implemented now, if a graph `G` has a large number
7357
of vertices with numeric labels, then G.add_vertex() could
7358
potentially be slow, if name is None.
7359
7360
OUTPUT:
7361
7362
If ``name``=``None``, the new vertex name is returned. ``None`` otherwise.
7363
7364
EXAMPLES::
7365
7366
sage: G = Graph(); G.add_vertex(); G
7367
0
7368
Graph on 1 vertex
7369
7370
::
7371
7372
sage: D = DiGraph(); D.add_vertex(); D
7373
0
7374
Digraph on 1 vertex
7375
7376
"""
7377
return self._backend.add_vertex(name)
7378
7379
def add_vertices(self, vertices):
7380
"""
7381
Add vertices to the (di)graph from an iterable container of
7382
vertices. Vertices that already exist in the graph will not be
7383
added again.
7384
7385
INPUT:
7386
7387
- ``vertices``: iterator of vertex labels. A new label is created, used and returned in
7388
the output list for all ``None`` values in ``vertices``.
7389
7390
OUTPUT:
7391
7392
Generated names of new vertices if there is at least one ``None`` value
7393
present in ``vertices``. ``None`` otherwise.
7394
7395
EXAMPLES::
7396
7397
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]}
7398
sage: G = Graph(d)
7399
sage: G.add_vertices([10,11,12])
7400
sage: G.vertices()
7401
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
7402
sage: G.add_vertices(graphs.CycleGraph(25).vertices())
7403
sage: G.vertices()
7404
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]
7405
7406
::
7407
7408
sage: G = Graph()
7409
sage: G.add_vertices([1,2,3])
7410
sage: G.add_vertices([4,None,None,5])
7411
[0, 6]
7412
7413
"""
7414
return self._backend.add_vertices(vertices)
7415
7416
def delete_vertex(self, vertex, in_order=False):
7417
"""
7418
Deletes vertex, removing all incident edges. Deleting a
7419
non-existent vertex will raise an exception.
7420
7421
INPUT:
7422
7423
7424
- ``in_order`` - (default False) If True, this
7425
deletes the ith vertex in the sorted list of vertices, i.e.
7426
G.vertices()[i]
7427
7428
7429
EXAMPLES::
7430
7431
sage: G = Graph(graphs.WheelGraph(9))
7432
sage: G.delete_vertex(0); G.show()
7433
7434
::
7435
7436
sage: D = DiGraph({0:[1,2,3,4,5],1:[2],2:[3],3:[4],4:[5],5:[1]})
7437
sage: D.delete_vertex(0); D
7438
Digraph on 5 vertices
7439
sage: D.vertices()
7440
[1, 2, 3, 4, 5]
7441
sage: D.delete_vertex(0)
7442
Traceback (most recent call last):
7443
...
7444
RuntimeError: Vertex (0) not in the graph.
7445
7446
::
7447
7448
sage: G = graphs.CompleteGraph(4).line_graph(labels=False)
7449
sage: G.vertices()
7450
[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]
7451
sage: G.delete_vertex(0, in_order=True)
7452
sage: G.vertices()
7453
[(0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]
7454
sage: G = graphs.PathGraph(5)
7455
sage: G.set_vertices({0: 'no delete', 1: 'delete'})
7456
sage: G.set_boundary([1,2])
7457
sage: G.delete_vertex(1)
7458
sage: G.get_vertices()
7459
{0: 'no delete', 2: None, 3: None, 4: None}
7460
sage: G.get_boundary()
7461
[2]
7462
sage: G.get_pos()
7463
{0: (0, 0), 2: (2, 0), 3: (3, 0), 4: (4, 0)}
7464
"""
7465
if in_order:
7466
vertex = self.vertices()[vertex]
7467
if vertex not in self:
7468
raise RuntimeError("Vertex (%s) not in the graph."%vertex)
7469
7470
attributes_to_update = ('_pos', '_assoc', '_embedding')
7471
for attr in attributes_to_update:
7472
if hasattr(self, attr) and getattr(self, attr) is not None:
7473
getattr(self, attr).pop(vertex, None)
7474
self._boundary = [v for v in self._boundary if v != vertex]
7475
7476
self._backend.del_vertex(vertex)
7477
7478
def delete_vertices(self, vertices):
7479
"""
7480
Remove vertices from the (di)graph taken from an iterable container
7481
of vertices. Deleting a non-existent vertex will raise an
7482
exception.
7483
7484
EXAMPLES::
7485
7486
sage: D = DiGraph({0:[1,2,3,4,5],1:[2],2:[3],3:[4],4:[5],5:[1]})
7487
sage: D.delete_vertices([1,2,3,4,5]); D
7488
Digraph on 1 vertex
7489
sage: D.vertices()
7490
[0]
7491
sage: D.delete_vertices([1])
7492
Traceback (most recent call last):
7493
...
7494
RuntimeError: Vertex (1) not in the graph.
7495
7496
"""
7497
for vertex in vertices:
7498
if vertex not in self:
7499
raise RuntimeError("Vertex (%s) not in the graph."%vertex)
7500
attributes_to_update = ('_pos', '_assoc', '_embedding')
7501
for attr in attributes_to_update:
7502
if hasattr(self, attr) and getattr(self, attr) is not None:
7503
attr_dict = getattr(self, attr)
7504
for vertex in vertices:
7505
attr_dict.pop(vertex, None)
7506
7507
self._boundary = [v for v in self._boundary if v not in vertices]
7508
7509
self._backend.del_vertices(vertices)
7510
7511
def has_vertex(self, vertex):
7512
"""
7513
Return True if vertex is one of the vertices of this graph.
7514
7515
INPUT:
7516
7517
7518
- ``vertex`` - an integer
7519
7520
7521
OUTPUT:
7522
7523
7524
- ``bool`` - True or False
7525
7526
7527
EXAMPLES::
7528
7529
sage: g = Graph({0:[1,2,3], 2:[4]}); g
7530
Graph on 5 vertices
7531
sage: 2 in g
7532
True
7533
sage: 10 in g
7534
False
7535
sage: graphs.PetersenGraph().has_vertex(99)
7536
False
7537
"""
7538
try:
7539
hash(vertex)
7540
except:
7541
return False
7542
return self._backend.has_vertex(vertex)
7543
7544
__contains__ = has_vertex
7545
7546
def random_vertex(self, **kwds):
7547
r"""
7548
Returns a random vertex of self.
7549
7550
INPUT:
7551
7552
- ``**kwds`` - arguments to be passed down to the
7553
``vertex_iterator`` method.
7554
7555
EXAMPLE:
7556
7557
The returned value is a vertex of self::
7558
7559
sage: g = graphs.PetersenGraph()
7560
sage: v = g.random_vertex()
7561
sage: v in g
7562
True
7563
"""
7564
from sage.misc.prandom import randint
7565
it = self.vertex_iterator(**kwds)
7566
for i in xrange(0, randint(0, self.order() - 1)):
7567
it.next()
7568
return it.next()
7569
7570
def random_edge(self,**kwds):
7571
r"""
7572
Returns a random edge of self.
7573
7574
INPUT:
7575
7576
- ``**kwds`` - arguments to be passed down to the
7577
``edge_iterator`` method.
7578
7579
EXAMPLE:
7580
7581
The returned value is an edge of self::
7582
7583
sage: g = graphs.PetersenGraph()
7584
sage: u,v = g.random_edge(labels=False)
7585
sage: g.has_edge(u,v)
7586
True
7587
7588
As the ``edges()`` method would, this function returns
7589
by default a triple ``(u,v,l)`` of values, in which
7590
``l`` is the label of edge `(u,v)`::
7591
7592
sage: g.random_edge()
7593
(3, 4, None)
7594
"""
7595
from sage.misc.prandom import randint
7596
it = self.edge_iterator(**kwds)
7597
for i in xrange(0, randint(0, self.size() - 1)):
7598
it.next()
7599
return it.next()
7600
7601
def vertex_boundary(self, vertices1, vertices2=None):
7602
"""
7603
Returns a list of all vertices in the external boundary of
7604
vertices1, intersected with vertices2. If vertices2 is None, then
7605
vertices2 is the complement of vertices1. This is much faster if
7606
vertices1 is smaller than vertices2.
7607
7608
The external boundary of a set of vertices is the union of the
7609
neighborhoods of each vertex in the set. Note that in this
7610
implementation, since vertices2 defaults to the complement of
7611
vertices1, if a vertex `v` has a loop, then
7612
vertex_boundary(v) will not contain `v`.
7613
7614
In a digraph, the external boundary of a vertex v are those
7615
vertices u with an arc (v, u).
7616
7617
EXAMPLES::
7618
7619
sage: G = graphs.CubeGraph(4)
7620
sage: l = ['0111', '0000', '0001', '0011', '0010', '0101', '0100', '1111', '1101', '1011', '1001']
7621
sage: G.vertex_boundary(['0000', '1111'], l)
7622
['0111', '0001', '0010', '0100', '1101', '1011']
7623
7624
::
7625
7626
sage: D = DiGraph({0:[1,2], 3:[0]})
7627
sage: D.vertex_boundary([0])
7628
[1, 2]
7629
"""
7630
vertices1 = [v for v in vertices1 if v in self]
7631
output = set()
7632
if self._directed:
7633
for v in vertices1:
7634
output.update(self.neighbor_out_iterator(v))
7635
else:
7636
for v in vertices1:
7637
output.update(self.neighbor_iterator(v))
7638
if vertices2 is not None:
7639
output.intersection_update(vertices2)
7640
return list(output)
7641
7642
def set_vertices(self, vertex_dict):
7643
"""
7644
Associate arbitrary objects with each vertex, via an association
7645
dictionary.
7646
7647
INPUT:
7648
7649
7650
- ``vertex_dict`` - the association dictionary
7651
7652
7653
EXAMPLES::
7654
7655
sage: d = {0 : graphs.DodecahedralGraph(), 1 : graphs.FlowerSnark(), 2 : graphs.MoebiusKantorGraph(), 3 : graphs.PetersenGraph() }
7656
sage: d[2]
7657
Moebius-Kantor Graph: Graph on 16 vertices
7658
sage: T = graphs.TetrahedralGraph()
7659
sage: T.vertices()
7660
[0, 1, 2, 3]
7661
sage: T.set_vertices(d)
7662
sage: T.get_vertex(1)
7663
Flower Snark: Graph on 20 vertices
7664
"""
7665
if hasattr(self, '_assoc') is False:
7666
self._assoc = {}
7667
7668
self._assoc.update(vertex_dict)
7669
7670
def set_vertex(self, vertex, object):
7671
"""
7672
Associate an arbitrary object with a vertex.
7673
7674
INPUT:
7675
7676
7677
- ``vertex`` - which vertex
7678
7679
- ``object`` - object to associate to vertex
7680
7681
7682
EXAMPLES::
7683
7684
sage: T = graphs.TetrahedralGraph()
7685
sage: T.vertices()
7686
[0, 1, 2, 3]
7687
sage: T.set_vertex(1, graphs.FlowerSnark())
7688
sage: T.get_vertex(1)
7689
Flower Snark: Graph on 20 vertices
7690
"""
7691
if hasattr(self, '_assoc') is False:
7692
self._assoc = {}
7693
7694
self._assoc[vertex] = object
7695
7696
def get_vertex(self, vertex):
7697
"""
7698
Retrieve the object associated with a given vertex.
7699
7700
INPUT:
7701
7702
7703
- ``vertex`` - the given vertex
7704
7705
7706
EXAMPLES::
7707
7708
sage: d = {0 : graphs.DodecahedralGraph(), 1 : graphs.FlowerSnark(), 2 : graphs.MoebiusKantorGraph(), 3 : graphs.PetersenGraph() }
7709
sage: d[2]
7710
Moebius-Kantor Graph: Graph on 16 vertices
7711
sage: T = graphs.TetrahedralGraph()
7712
sage: T.vertices()
7713
[0, 1, 2, 3]
7714
sage: T.set_vertices(d)
7715
sage: T.get_vertex(1)
7716
Flower Snark: Graph on 20 vertices
7717
"""
7718
if hasattr(self, '_assoc') is False:
7719
return None
7720
7721
return self._assoc.get(vertex, None)
7722
7723
def get_vertices(self, verts=None):
7724
"""
7725
Return a dictionary of the objects associated to each vertex.
7726
7727
INPUT:
7728
7729
7730
- ``verts`` - iterable container of vertices
7731
7732
7733
EXAMPLES::
7734
7735
sage: d = {0 : graphs.DodecahedralGraph(), 1 : graphs.FlowerSnark(), 2 : graphs.MoebiusKantorGraph(), 3 : graphs.PetersenGraph() }
7736
sage: T = graphs.TetrahedralGraph()
7737
sage: T.set_vertices(d)
7738
sage: T.get_vertices([1,2])
7739
{1: Flower Snark: Graph on 20 vertices,
7740
2: Moebius-Kantor Graph: Graph on 16 vertices}
7741
"""
7742
if verts is None:
7743
verts = self.vertices()
7744
7745
if hasattr(self, '_assoc') is False:
7746
return dict.fromkeys(verts, None)
7747
7748
output = {}
7749
7750
for v in verts:
7751
output[v] = self._assoc.get(v, None)
7752
7753
return output
7754
7755
def loop_vertices(self):
7756
"""
7757
Returns a list of vertices with loops.
7758
7759
EXAMPLES::
7760
7761
sage: G = Graph({0 : [0], 1: [1,2,3], 2: [3]}, loops=True)
7762
sage: G.loop_vertices()
7763
[0, 1]
7764
"""
7765
if self.allows_loops():
7766
return [v for v in self if self.has_edge(v,v)]
7767
else:
7768
return []
7769
7770
def vertex_iterator(self, vertices=None):
7771
"""
7772
Returns an iterator over the given vertices.
7773
7774
Returns False if not given a vertex, sequence, iterator or None. None is
7775
equivalent to a list of every vertex. Note that ``for v in G`` syntax is
7776
allowed.
7777
7778
INPUT:
7779
7780
- ``vertices`` - iterated vertices are these
7781
intersected with the vertices of the (di)graph
7782
7783
EXAMPLES::
7784
7785
sage: P = graphs.PetersenGraph()
7786
sage: for v in P.vertex_iterator():
7787
... print v
7788
...
7789
0
7790
1
7791
2
7792
...
7793
8
7794
9
7795
7796
::
7797
7798
sage: G = graphs.TetrahedralGraph()
7799
sage: for i in G:
7800
... print i
7801
0
7802
1
7803
2
7804
3
7805
7806
Note that since the intersection option is available, the
7807
vertex_iterator() function is sub-optimal, speed-wise, but note the
7808
following optimization::
7809
7810
sage: timeit V = P.vertices() # not tested
7811
100000 loops, best of 3: 8.85 [micro]s per loop
7812
sage: timeit V = list(P.vertex_iterator()) # not tested
7813
100000 loops, best of 3: 5.74 [micro]s per loop
7814
sage: timeit V = list(P._nxg.adj.iterkeys()) # not tested
7815
100000 loops, best of 3: 3.45 [micro]s per loop
7816
7817
In other words, if you want a fast vertex iterator, call the
7818
dictionary directly.
7819
"""
7820
return self._backend.iterator_verts(vertices)
7821
7822
__iter__ = vertex_iterator
7823
7824
def neighbor_iterator(self, vertex):
7825
"""
7826
Return an iterator over neighbors of vertex.
7827
7828
EXAMPLES::
7829
7830
sage: G = graphs.CubeGraph(3)
7831
sage: for i in G.neighbor_iterator('010'):
7832
... print i
7833
011
7834
000
7835
110
7836
sage: D = G.to_directed()
7837
sage: for i in D.neighbor_iterator('010'):
7838
... print i
7839
011
7840
000
7841
110
7842
7843
::
7844
7845
sage: D = DiGraph({0:[1,2], 3:[0]})
7846
sage: list(D.neighbor_iterator(0))
7847
[1, 2, 3]
7848
"""
7849
if self._directed:
7850
return iter(set(self.neighbor_out_iterator(vertex)) \
7851
| set(self.neighbor_in_iterator(vertex)))
7852
else:
7853
return iter(set(self._backend.iterator_nbrs(vertex)))
7854
7855
def vertices(self, key=None, boundary_first=False):
7856
r"""
7857
Return a list of the vertices.
7858
7859
INPUT:
7860
7861
- ``key`` - default: ``None`` - a function that takes
7862
a vertex as its one argument and returns a value that
7863
can be used for comparisons in the sorting algorithm.
7864
7865
- ``boundary_first`` - default: ``False`` - if ``True``,
7866
return the boundary vertices first.
7867
7868
OUTPUT:
7869
7870
The vertices of the list.
7871
7872
.. warning::
7873
7874
There is always an attempt to sort the list before
7875
returning the result. However, since any object may
7876
be a vertex, there is no guarantee that any two
7877
vertices will be comparable. With default objects
7878
for vertices (all integers), or when all the vertices
7879
are of the same simple type, then there should not be
7880
a problem with how the vertices will be sorted. However,
7881
if you need to guarantee a total order for the sort,
7882
use the ``key`` argument, as illustrated in the
7883
examples below.
7884
7885
EXAMPLES::
7886
7887
sage: P = graphs.PetersenGraph()
7888
sage: P.vertices()
7889
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
7890
7891
If you do not care about sorted output and you are
7892
concerned about the time taken to sort, consider the
7893
following alternatives. The moral is: if you want a
7894
fast vertex iterator, call the dictionary directly. ::
7895
7896
sage: timeit V = P.vertices() # not tested
7897
100000 loops, best of 3: 8.85 [micro]s per loop
7898
sage: timeit V = list(P.vertex_iterator()) # not tested
7899
100000 loops, best of 3: 5.74 [micro]s per loop
7900
sage: timeit V = list(P._nxg.adj.iterkeys()) # not tested
7901
100000 loops, best of 3: 3.45 [micro]s per loop
7902
7903
We illustrate various ways to use a ``key`` to sort the list::
7904
7905
sage: H=graphs.HanoiTowerGraph(3,3,labels=False)
7906
sage: H.vertices()
7907
[0, 1, 2, 3, 4, ... 22, 23, 24, 25, 26]
7908
sage: H.vertices(key=lambda x: -x)
7909
[26, 25, 24, 23, 22, ... 4, 3, 2, 1, 0]
7910
7911
::
7912
7913
sage: G=graphs.HanoiTowerGraph(3,3)
7914
sage: G.vertices()
7915
[(0, 0, 0), (0, 0, 1), (0, 0, 2), (0, 1, 0), ... (2, 2, 1), (2, 2, 2)]
7916
sage: G.vertices(key = lambda x: (x[1], x[2], x[0]))
7917
[(0, 0, 0), (1, 0, 0), (2, 0, 0), (0, 0, 1), ... (1, 2, 2), (2, 2, 2)]
7918
7919
The discriminant of a polynomial is a function that returns an integer.
7920
We build a graph whose vertices are polynomials, and use the discriminant
7921
function to provide an ordering. Note that since functions are first-class
7922
objects in Python, we can specify precisely the function from the Sage library
7923
that we wish to use as the key. ::
7924
7925
sage: t = polygen(QQ, 't')
7926
sage: K = Graph({5*t:[t^2], t^2:[t^2+2], t^2+2:[4*t^2-6], 4*t^2-6:[5*t]})
7927
sage: dsc = sage.rings.polynomial.polynomial_rational_flint.Polynomial_rational_flint.discriminant
7928
sage: verts = K.vertices(key=dsc)
7929
sage: verts
7930
[t^2 + 2, t^2, 5*t, 4*t^2 - 6]
7931
sage: [x.discriminant() for x in verts]
7932
[-8, 0, 1, 96]
7933
7934
If boundary vertices are requested first, then they are sorted
7935
separately from the remainder (which are also sorted). ::
7936
7937
sage: P = graphs.PetersenGraph()
7938
sage: P.set_boundary((5..9))
7939
sage: P.vertices(boundary_first=True)
7940
[5, 6, 7, 8, 9, 0, 1, 2, 3, 4]
7941
sage: P.vertices(boundary_first=True, key=lambda x: -x)
7942
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
7943
"""
7944
if not boundary_first:
7945
return sorted(list(self.vertex_iterator()), key=key)
7946
7947
bdy_verts = []
7948
int_verts = []
7949
for v in self.vertex_iterator():
7950
if v in self._boundary:
7951
bdy_verts.append(v)
7952
else:
7953
int_verts.append(v)
7954
return sorted(bdy_verts, key=key) + sorted(int_verts, key=key)
7955
7956
def neighbors(self, vertex):
7957
"""
7958
Return a list of neighbors (in and out if directed) of vertex.
7959
7960
G[vertex] also works.
7961
7962
EXAMPLES::
7963
7964
sage: P = graphs.PetersenGraph()
7965
sage: sorted(P.neighbors(3))
7966
[2, 4, 8]
7967
sage: sorted(P[4])
7968
[0, 3, 9]
7969
"""
7970
return list(self.neighbor_iterator(vertex))
7971
7972
__getitem__ = neighbors
7973
7974
7975
def merge_vertices(self,vertices):
7976
r"""
7977
Merge vertices.
7978
7979
This function replaces a set `S` of vertices by a single vertex
7980
`v_{new}`, such that the edge `uv_{new}` exists if and only if
7981
`\exists v'\in S: (u,v')\in G`.
7982
7983
The new vertex is named after the first vertex in the list
7984
given in argument. If this first name is None, a new vertex
7985
is created.
7986
7987
In the case of multigraphs, the multiplicity is preserved.
7988
7989
INPUT:
7990
7991
- ``vertices`` -- the set of vertices to be merged
7992
7993
EXAMPLE::
7994
7995
sage: g=graphs.CycleGraph(3)
7996
sage: g.merge_vertices([0,1])
7997
sage: g.edges()
7998
[(0, 2, None)]
7999
8000
sage: # With a Multigraph :
8001
sage: g=graphs.CycleGraph(3)
8002
sage: g.allow_multiple_edges(True)
8003
sage: g.merge_vertices([0,1])
8004
sage: g.edges(labels=False)
8005
[(0, 2), (0, 2)]
8006
8007
sage: P=graphs.PetersenGraph()
8008
sage: P.merge_vertices([5,7])
8009
sage: P.vertices()
8010
[0, 1, 2, 3, 4, 5, 6, 8, 9]
8011
8012
sage: g=graphs.CycleGraph(5)
8013
sage: g.vertices()
8014
[0, 1, 2, 3, 4]
8015
sage: g.merge_vertices([None, 1, 3])
8016
sage: g.edges(labels=False)
8017
[(0, 4), (0, 5), (2, 5), (4, 5)]
8018
8019
"""
8020
8021
if len(vertices) > 0 and vertices[0] is None:
8022
vertices[0] = self.add_vertex()
8023
8024
if self.is_directed():
8025
out_edges=self.edge_boundary(vertices)
8026
in_edges=self.edge_boundary([v for v in self if not v in vertices])
8027
self.delete_vertices(vertices[1:])
8028
self.add_edges([(vertices[0],v,l) for (u,v,l) in out_edges if u!=vertices[0]])
8029
self.add_edges([(v,vertices[0],l) for (v,u,l) in in_edges if u!=vertices[0]])
8030
else:
8031
edges=self.edge_boundary(vertices)
8032
self.delete_vertices(vertices[1:])
8033
add_edges=[]
8034
for (u,v,l) in edges:
8035
if v in vertices and v != vertices[0]:
8036
add_edges.append((vertices[0],u,l))
8037
if u in vertices and u!=vertices[0]:
8038
add_edges.append((vertices[0],v,l))
8039
self.add_edges(add_edges)
8040
8041
8042
### Edge handlers
8043
8044
def add_edge(self, u, v=None, label=None):
8045
"""
8046
Adds an edge from u and v.
8047
8048
INPUT: The following forms are all accepted:
8049
8050
- G.add_edge( 1, 2 )
8051
- G.add_edge( (1, 2) )
8052
- G.add_edges( [ (1, 2) ])
8053
- G.add_edge( 1, 2, 'label' )
8054
- G.add_edge( (1, 2, 'label') )
8055
- G.add_edges( [ (1, 2, 'label') ] )
8056
8057
WARNING: The following intuitive input results in nonintuitive
8058
output::
8059
8060
sage: G = Graph()
8061
sage: G.add_edge((1,2), 'label')
8062
sage: G.networkx_graph().adj # random output order
8063
{'label': {(1, 2): None}, (1, 2): {'label': None}}
8064
8065
Use one of these instead::
8066
8067
sage: G = Graph()
8068
sage: G.add_edge((1,2), label="label")
8069
sage: G.networkx_graph().adj # random output order
8070
{1: {2: 'label'}, 2: {1: 'label'}}
8071
8072
::
8073
8074
sage: G = Graph()
8075
sage: G.add_edge(1,2,'label')
8076
sage: G.networkx_graph().adj # random output order
8077
{1: {2: 'label'}, 2: {1: 'label'}}
8078
8079
The following syntax is supported, but note that you must use
8080
the ``label`` keyword::
8081
8082
sage: G = Graph()
8083
sage: G.add_edge((1,2), label='label')
8084
sage: G.edges()
8085
[(1, 2, 'label')]
8086
sage: G = Graph()
8087
sage: G.add_edge((1,2), 'label')
8088
sage: G.edges()
8089
[('label', (1, 2), None)]
8090
8091
Vertex name cannot be None, so::
8092
8093
sage: G = Graph()
8094
sage: G.add_edge(None, 4)
8095
sage: G.vertices()
8096
[0, 4]
8097
"""
8098
if label is None:
8099
if v is None:
8100
try:
8101
u, v, label = u
8102
except:
8103
try:
8104
u, v = u
8105
except:
8106
pass
8107
else:
8108
if v is None:
8109
try:
8110
u, v = u
8111
except:
8112
pass
8113
if not self.allows_loops() and u==v:
8114
return
8115
self._backend.add_edge(u, v, label, self._directed)
8116
8117
def add_edges(self, edges):
8118
"""
8119
Add edges from an iterable container.
8120
8121
EXAMPLES::
8122
8123
sage: G = graphs.DodecahedralGraph()
8124
sage: H = Graph()
8125
sage: H.add_edges( G.edge_iterator() ); H
8126
Graph on 20 vertices
8127
sage: G = graphs.DodecahedralGraph().to_directed()
8128
sage: H = DiGraph()
8129
sage: H.add_edges( G.edge_iterator() ); H
8130
Digraph on 20 vertices
8131
"""
8132
for e in edges:
8133
self.add_edge(e)
8134
8135
def subdivide_edge(self, *args):
8136
"""
8137
Subdivides an edge `k` times.
8138
8139
INPUT:
8140
8141
The following forms are all accepted to subdivide `8` times
8142
the edge between vertices `1` and `2` labeled with
8143
``"my_label"``.
8144
8145
- ``G.subdivide_edge( 1, 2, 8 )``
8146
- ``G.subdivide_edge( (1, 2), 8 )``
8147
- ``G.subdivide_edge( (1, 2, "my_label"), 8 )``
8148
8149
.. NOTE::
8150
8151
* If the given edge is labelled with `l`, all the edges
8152
created by the subdivision will have the same label.
8153
8154
* If no label is given, the label used will be the one
8155
returned by the method :meth:`edge_label` on the pair
8156
``u,v``
8157
8158
EXAMPLE:
8159
8160
Subdividing `5` times an edge in a path of length
8161
`3` makes it a path of length `8`::
8162
8163
sage: g = graphs.PathGraph(3)
8164
sage: edge = g.edges()[0]
8165
sage: g.subdivide_edge(edge, 5)
8166
sage: g.is_isomorphic(graphs.PathGraph(8))
8167
True
8168
8169
Subdividing a labelled edge in two ways ::
8170
8171
sage: g = Graph()
8172
sage: g.add_edge(0,1,"label1")
8173
sage: g.add_edge(1,2,"label2")
8174
sage: print sorted(g.edges())
8175
[(0, 1, 'label1'), (1, 2, 'label2')]
8176
8177
Specifying the label::
8178
8179
sage: g.subdivide_edge(0,1,"label1", 3)
8180
sage: print sorted(g.edges())
8181
[(0, 3, 'label1'), (1, 2, 'label2'), (1, 5, 'label1'), (3, 4, 'label1'), (4, 5, 'label1')]
8182
8183
The lazy way::
8184
8185
sage: g.subdivide_edge(1,2,"label2", 5)
8186
sage: print sorted(g.edges())
8187
[(0, 3, 'label1'), (1, 5, 'label1'), (1, 6, 'label2'), (2, 10, 'label2'), (3, 4, 'label1'), (4, 5, 'label1'), (6, 7, 'label2'), (7, 8, 'label2'), (8, 9, 'label2'), (9, 10, 'label2')]
8188
8189
If too many arguments are given, an exception is raised ::
8190
8191
sage: g.subdivide_edge(0,1,1,1,1,1,1,1,1,1,1)
8192
Traceback (most recent call last):
8193
...
8194
ValueError: This method takes at most 4 arguments !
8195
8196
The same goes when the given edge does not exist::
8197
8198
sage: g.subdivide_edge(0,1,"fake_label",5)
8199
Traceback (most recent call last):
8200
...
8201
ValueError: The given edge does not exist.
8202
8203
.. SEEALSO::
8204
8205
- :meth:`subdivide_edges` -- subdivides multiples edges at a time
8206
8207
"""
8208
8209
if len(args) == 2:
8210
edge, k = args
8211
8212
if len(edge) == 2:
8213
u,v = edge
8214
l = self.edge_label(u,v)
8215
elif len(edge) == 3:
8216
u,v,l = edge
8217
8218
elif len(args) == 3:
8219
u, v, k = args
8220
l = self.edge_label(u,v)
8221
8222
elif len(args) == 4:
8223
u, v, l, k = args
8224
8225
else:
8226
raise ValueError("This method takes at most 4 arguments !")
8227
8228
if not self.has_edge(u,v,l):
8229
raise ValueError("The given edge does not exist.")
8230
8231
for i in xrange(k):
8232
self.add_vertex()
8233
8234
self.delete_edge(u,v,l)
8235
8236
edges = []
8237
for uu in self.vertices()[-k:] + [v]:
8238
edges.append((u,uu,l))
8239
u = uu
8240
8241
self.add_edges(edges)
8242
8243
def subdivide_edges(self, edges, k):
8244
"""
8245
Subdivides k times edges from an iterable container.
8246
8247
For more information on the behaviour of this method, please
8248
refer to the documentation of :meth:`subdivide_edge`.
8249
8250
INPUT:
8251
8252
- ``edges`` -- a list of edges
8253
8254
- ``k`` (integer) -- common length of the subdivisions
8255
8256
8257
.. NOTE::
8258
8259
If a given edge is labelled with `l`, all the edges
8260
created by its subdivision will have the same label.
8261
8262
EXAMPLE:
8263
8264
If we are given the disjoint union of several paths::
8265
8266
sage: paths = [2,5,9]
8267
sage: paths = map(graphs.PathGraph, paths)
8268
sage: g = Graph()
8269
sage: for P in paths:
8270
... g = g + P
8271
8272
... subdividing edges in each of them will only change their
8273
lengths::
8274
8275
sage: edges = [P.edges()[0] for P in g.connected_components_subgraphs()]
8276
sage: k = 6
8277
sage: g.subdivide_edges(edges, k)
8278
8279
Let us check this by creating the graph we expect to have built
8280
through subdivision::
8281
8282
sage: paths2 = [2+k, 5+k, 9+k]
8283
sage: paths2 = map(graphs.PathGraph, paths2)
8284
sage: g2 = Graph()
8285
sage: for P in paths2:
8286
... g2 = g2 + P
8287
sage: g.is_isomorphic(g2)
8288
True
8289
8290
.. SEEALSO::
8291
8292
- :meth:`subdivide_edge` -- subdivides one edge
8293
"""
8294
for e in edges:
8295
self.subdivide_edge(e, k)
8296
8297
def delete_edge(self, u, v=None, label=None):
8298
r"""
8299
Delete the edge from u to v, returning silently if vertices or edge
8300
does not exist.
8301
8302
INPUT: The following forms are all accepted:
8303
8304
- G.delete_edge( 1, 2 )
8305
- G.delete_edge( (1, 2) )
8306
- G.delete_edges( [ (1, 2) ] )
8307
- G.delete_edge( 1, 2, 'label' )
8308
- G.delete_edge( (1, 2, 'label') )
8309
- G.delete_edges( [ (1, 2, 'label') ] )
8310
8311
EXAMPLES::
8312
8313
sage: G = graphs.CompleteGraph(19).copy(implementation='c_graph')
8314
sage: G.size()
8315
171
8316
sage: G.delete_edge( 1, 2 )
8317
sage: G.delete_edge( (3, 4) )
8318
sage: G.delete_edges( [ (5, 6), (7, 8) ] )
8319
sage: G.size()
8320
167
8321
8322
Note that NetworkX accidentally deletes these edges, even though the
8323
labels do not match up::
8324
8325
sage: N = graphs.CompleteGraph(19).copy(implementation='networkx')
8326
sage: N.size()
8327
171
8328
sage: N.delete_edge( 1, 2 )
8329
sage: N.delete_edge( (3, 4) )
8330
sage: N.delete_edges( [ (5, 6), (7, 8) ] )
8331
sage: N.size()
8332
167
8333
sage: N.delete_edge( 9, 10, 'label' )
8334
sage: N.delete_edge( (11, 12, 'label') )
8335
sage: N.delete_edges( [ (13, 14, 'label') ] )
8336
sage: N.size()
8337
167
8338
sage: N.has_edge( (11, 12) )
8339
True
8340
8341
However, CGraph backends handle things properly::
8342
8343
sage: G.delete_edge( 9, 10, 'label' )
8344
sage: G.delete_edge( (11, 12, 'label') )
8345
sage: G.delete_edges( [ (13, 14, 'label') ] )
8346
sage: G.size()
8347
167
8348
8349
::
8350
8351
sage: C = graphs.CompleteGraph(19).to_directed(sparse=True)
8352
sage: C.size()
8353
342
8354
sage: C.delete_edge( 1, 2 )
8355
sage: C.delete_edge( (3, 4) )
8356
sage: C.delete_edges( [ (5, 6), (7, 8) ] )
8357
8358
sage: D = graphs.CompleteGraph(19).to_directed(sparse=True, implementation='networkx')
8359
sage: D.size()
8360
342
8361
sage: D.delete_edge( 1, 2 )
8362
sage: D.delete_edge( (3, 4) )
8363
sage: D.delete_edges( [ (5, 6), (7, 8) ] )
8364
sage: D.delete_edge( 9, 10, 'label' )
8365
sage: D.delete_edge( (11, 12, 'label') )
8366
sage: D.delete_edges( [ (13, 14, 'label') ] )
8367
sage: D.size()
8368
338
8369
sage: D.has_edge( (11, 12) )
8370
True
8371
8372
::
8373
8374
sage: C.delete_edge( 9, 10, 'label' )
8375
sage: C.delete_edge( (11, 12, 'label') )
8376
sage: C.delete_edges( [ (13, 14, 'label') ] )
8377
sage: C.size() # correct!
8378
338
8379
sage: C.has_edge( (11, 12) ) # correct!
8380
True
8381
8382
"""
8383
if label is None:
8384
if v is None:
8385
try:
8386
u, v, label = u
8387
except:
8388
u, v = u
8389
label = None
8390
self._backend.del_edge(u, v, label, self._directed)
8391
8392
def delete_edges(self, edges):
8393
"""
8394
Delete edges from an iterable container.
8395
8396
EXAMPLES::
8397
8398
sage: K12 = graphs.CompleteGraph(12)
8399
sage: K4 = graphs.CompleteGraph(4)
8400
sage: K12.size()
8401
66
8402
sage: K12.delete_edges(K4.edge_iterator())
8403
sage: K12.size()
8404
60
8405
8406
::
8407
8408
sage: K12 = graphs.CompleteGraph(12).to_directed()
8409
sage: K4 = graphs.CompleteGraph(4).to_directed()
8410
sage: K12.size()
8411
132
8412
sage: K12.delete_edges(K4.edge_iterator())
8413
sage: K12.size()
8414
120
8415
"""
8416
for e in edges:
8417
self.delete_edge(e)
8418
8419
def delete_multiedge(self, u, v):
8420
"""
8421
Deletes all edges from u and v.
8422
8423
EXAMPLES::
8424
8425
sage: G = Graph(multiedges=True,sparse=True)
8426
sage: G.add_edges([(0,1), (0,1), (0,1), (1,2), (2,3)])
8427
sage: G.edges()
8428
[(0, 1, None), (0, 1, None), (0, 1, None), (1, 2, None), (2, 3, None)]
8429
sage: G.delete_multiedge( 0, 1 )
8430
sage: G.edges()
8431
[(1, 2, None), (2, 3, None)]
8432
8433
::
8434
8435
sage: D = DiGraph(multiedges=True,sparse=True)
8436
sage: D.add_edges([(0,1,1), (0,1,2), (0,1,3), (1,0), (1,2), (2,3)])
8437
sage: D.edges()
8438
[(0, 1, 1), (0, 1, 2), (0, 1, 3), (1, 0, None), (1, 2, None), (2, 3, None)]
8439
sage: D.delete_multiedge( 0, 1 )
8440
sage: D.edges()
8441
[(1, 0, None), (1, 2, None), (2, 3, None)]
8442
"""
8443
if self.allows_multiple_edges():
8444
for l in self.edge_label(u, v):
8445
self.delete_edge(u, v, l)
8446
else:
8447
self.delete_edge(u, v)
8448
8449
def set_edge_label(self, u, v, l):
8450
"""
8451
Set the edge label of a given edge.
8452
8453
.. note::
8454
8455
There can be only one edge from u to v for this to make
8456
sense. Otherwise, an error is raised.
8457
8458
INPUT:
8459
8460
8461
- ``u, v`` - the vertices (and direction if digraph)
8462
of the edge
8463
8464
- ``l`` - the new label
8465
8466
8467
EXAMPLES::
8468
8469
sage: SD = DiGraph( { 1:[18,2], 2:[5,3], 3:[4,6], 4:[7,2], 5:[4], 6:[13,12], 7:[18,8,10], 8:[6,9,10], 9:[6], 10:[11,13], 11:[12], 12:[13], 13:[17,14], 14:[16,15], 15:[2], 16:[13], 17:[15,13], 18:[13] }, sparse=True)
8470
sage: SD.set_edge_label(1, 18, 'discrete')
8471
sage: SD.set_edge_label(4, 7, 'discrete')
8472
sage: SD.set_edge_label(2, 5, 'h = 0')
8473
sage: SD.set_edge_label(7, 18, 'h = 0')
8474
sage: SD.set_edge_label(7, 10, 'aut')
8475
sage: SD.set_edge_label(8, 10, 'aut')
8476
sage: SD.set_edge_label(8, 9, 'label')
8477
sage: SD.set_edge_label(8, 6, 'no label')
8478
sage: SD.set_edge_label(13, 17, 'k > h')
8479
sage: SD.set_edge_label(13, 14, 'k = h')
8480
sage: SD.set_edge_label(17, 15, 'v_k finite')
8481
sage: SD.set_edge_label(14, 15, 'v_k m.c.r.')
8482
sage: posn = {1:[ 3,-3], 2:[0,2], 3:[0, 13], 4:[3,9], 5:[3,3], 6:[16, 13], 7:[6,1], 8:[6,6], 9:[6,11], 10:[9,1], 11:[10,6], 12:[13,6], 13:[16,2], 14:[10,-6], 15:[0,-10], 16:[14,-6], 17:[16,-10], 18:[6,-4]}
8483
sage: SD.plot(pos=posn, vertex_size=400, vertex_colors={'#FFFFFF':range(1,19)}, edge_labels=True).show() # long time
8484
8485
::
8486
8487
sage: G = graphs.HeawoodGraph()
8488
sage: for u,v,l in G.edges():
8489
... G.set_edge_label(u,v,'(' + str(u) + ',' + str(v) + ')')
8490
sage: G.edges()
8491
[(0, 1, '(0,1)'),
8492
(0, 5, '(0,5)'),
8493
(0, 13, '(0,13)'),
8494
...
8495
(11, 12, '(11,12)'),
8496
(12, 13, '(12,13)')]
8497
8498
::
8499
8500
sage: g = Graph({0: [0,1,1,2]}, loops=True, multiedges=True, sparse=True)
8501
sage: g.set_edge_label(0,0,'test')
8502
sage: g.edges()
8503
[(0, 0, 'test'), (0, 1, None), (0, 1, None), (0, 2, None)]
8504
sage: g.add_edge(0,0,'test2')
8505
sage: g.set_edge_label(0,0,'test3')
8506
Traceback (most recent call last):
8507
...
8508
RuntimeError: Cannot set edge label, since there are multiple edges from 0 to 0.
8509
8510
::
8511
8512
sage: dg = DiGraph({0 : [1], 1 : [0]}, sparse=True)
8513
sage: dg.set_edge_label(0,1,5)
8514
sage: dg.set_edge_label(1,0,9)
8515
sage: dg.outgoing_edges(1)
8516
[(1, 0, 9)]
8517
sage: dg.incoming_edges(1)
8518
[(0, 1, 5)]
8519
sage: dg.outgoing_edges(0)
8520
[(0, 1, 5)]
8521
sage: dg.incoming_edges(0)
8522
[(1, 0, 9)]
8523
8524
::
8525
8526
sage: G = Graph({0:{1:1}}, sparse=True)
8527
sage: G.num_edges()
8528
1
8529
sage: G.set_edge_label(0,1,1)
8530
sage: G.num_edges()
8531
1
8532
"""
8533
if self.allows_multiple_edges():
8534
if len(self.edge_label(u, v)) > 1:
8535
raise RuntimeError("Cannot set edge label, since there are multiple edges from %s to %s."%(u,v))
8536
self._backend.set_edge_label(u, v, l, self._directed)
8537
8538
def has_edge(self, u, v=None, label=None):
8539
r"""
8540
Returns True if (u, v) is an edge, False otherwise.
8541
8542
INPUT: The following forms are accepted by NetworkX:
8543
8544
- G.has_edge( 1, 2 )
8545
- G.has_edge( (1, 2) )
8546
- G.has_edge( 1, 2, 'label' )
8547
8548
EXAMPLES::
8549
8550
sage: graphs.EmptyGraph().has_edge(9,2)
8551
False
8552
sage: DiGraph().has_edge(9,2)
8553
False
8554
sage: G = Graph(sparse=True)
8555
sage: G.add_edge(0,1,"label")
8556
sage: G.has_edge(0,1,"different label")
8557
False
8558
sage: G.has_edge(0,1,"label")
8559
True
8560
"""
8561
if label is None:
8562
if v is None:
8563
try:
8564
u, v, label = u
8565
except:
8566
u, v = u
8567
label = None
8568
return self._backend.has_edge(u, v, label)
8569
8570
def edges(self, labels=True, sort=True, key=None):
8571
r"""
8572
Return a list of edges.
8573
8574
Each edge is a triple (u,v,l) where u and v are vertices and l is a
8575
label. If the parameter ``labels`` is False then a list of couple (u,v)
8576
is returned where u and v are vertices.
8577
8578
INPUT:
8579
8580
- ``labels`` - default: ``True`` - if ``False``, each
8581
edge is simply a pair (u,v) of vertices.
8582
8583
- ``sort`` - default: ``True`` - if ``True``, edges are
8584
sorted according to the default ordering.
8585
8586
- ``key`` - default: ``None`` - a function takes an edge
8587
(a pair or a triple, according to the ``labels`` keyword)
8588
as its one argument and returns a value that can be used
8589
for comparisons in the sorting algorithm.
8590
8591
OUTPUT: A list of tuples. It is safe to change the returned list.
8592
8593
.. warning::
8594
8595
Since any object may be a vertex, there is no guarantee
8596
that any two vertices will be comparable, and thus no
8597
guarantee how two edges may compare. With default
8598
objects for vertices (all integers), or when all the
8599
vertices are of the same simple type, then there should
8600
not be a problem with how the vertices will be sorted.
8601
However, if you need to guarantee a total order for
8602
the sorting of the edges, use the ``key`` argument,
8603
as illustrated in the examples below.
8604
8605
EXAMPLES::
8606
8607
sage: graphs.DodecahedralGraph().edges()
8608
[(0, 1, None), (0, 10, None), (0, 19, None), (1, 2, None), (1, 8, None), (2, 3, None), (2, 6, None), (3, 4, None), (3, 19, None), (4, 5, None), (4, 17, None), (5, 6, None), (5, 15, None), (6, 7, None), (7, 8, None), (7, 14, None), (8, 9, None), (9, 10, None), (9, 13, None), (10, 11, None), (11, 12, None), (11, 18, None), (12, 13, None), (12, 16, None), (13, 14, None), (14, 15, None), (15, 16, None), (16, 17, None), (17, 18, None), (18, 19, None)]
8609
8610
::
8611
8612
sage: graphs.DodecahedralGraph().edges(labels=False)
8613
[(0, 1), (0, 10), (0, 19), (1, 2), (1, 8), (2, 3), (2, 6), (3, 4), (3, 19), (4, 5), (4, 17), (5, 6), (5, 15), (6, 7), (7, 8), (7, 14), (8, 9), (9, 10), (9, 13), (10, 11), (11, 12), (11, 18), (12, 13), (12, 16), (13, 14), (14, 15), (15, 16), (16, 17), (17, 18), (18, 19)]
8614
8615
::
8616
8617
sage: D = graphs.DodecahedralGraph().to_directed()
8618
sage: D.edges()
8619
[(0, 1, None), (0, 10, None), (0, 19, None), (1, 0, None), (1, 2, None), (1, 8, None), (2, 1, None), (2, 3, None), (2, 6, None), (3, 2, None), (3, 4, None), (3, 19, None), (4, 3, None), (4, 5, None), (4, 17, None), (5, 4, None), (5, 6, None), (5, 15, None), (6, 2, None), (6, 5, None), (6, 7, None), (7, 6, None), (7, 8, None), (7, 14, None), (8, 1, None), (8, 7, None), (8, 9, None), (9, 8, None), (9, 10, None), (9, 13, None), (10, 0, None), (10, 9, None), (10, 11, None), (11, 10, None), (11, 12, None), (11, 18, None), (12, 11, None), (12, 13, None), (12, 16, None), (13, 9, None), (13, 12, None), (13, 14, None), (14, 7, None), (14, 13, None), (14, 15, None), (15, 5, None), (15, 14, None), (15, 16, None), (16, 12, None), (16, 15, None), (16, 17, None), (17, 4, None), (17, 16, None), (17, 18, None), (18, 11, None), (18, 17, None), (18, 19, None), (19, 0, None), (19, 3, None), (19, 18, None)]
8620
sage: D.edges(labels = False)
8621
[(0, 1), (0, 10), (0, 19), (1, 0), (1, 2), (1, 8), (2, 1), (2, 3), (2, 6), (3, 2), (3, 4), (3, 19), (4, 3), (4, 5), (4, 17), (5, 4), (5, 6), (5, 15), (6, 2), (6, 5), (6, 7), (7, 6), (7, 8), (7, 14), (8, 1), (8, 7), (8, 9), (9, 8), (9, 10), (9, 13), (10, 0), (10, 9), (10, 11), (11, 10), (11, 12), (11, 18), (12, 11), (12, 13), (12, 16), (13, 9), (13, 12), (13, 14), (14, 7), (14, 13), (14, 15), (15, 5), (15, 14), (15, 16), (16, 12), (16, 15), (16, 17), (17, 4), (17, 16), (17, 18), (18, 11), (18, 17), (18, 19), (19, 0), (19, 3), (19, 18)]
8622
8623
The default is to sort the returned list in the default fashion, as in the above examples.
8624
this can be overridden by specifying a key function. This first example just ignores
8625
the labels in the third component of the triple. ::
8626
8627
sage: G=graphs.CycleGraph(5)
8628
sage: G.edges(key = lambda x: (x[1],-x[0]))
8629
[(0, 1, None), (1, 2, None), (2, 3, None), (3, 4, None), (0, 4, None)]
8630
8631
We set the labels to characters and then perform a default sort
8632
followed by a sort according to the labels. ::
8633
8634
sage: G=graphs.CycleGraph(5)
8635
sage: for e in G.edges():
8636
... G.set_edge_label(e[0], e[1], chr(ord('A')+e[0]+5*e[1]))
8637
sage: G.edges(sort=True)
8638
[(0, 1, 'F'), (0, 4, 'U'), (1, 2, 'L'), (2, 3, 'R'), (3, 4, 'X')]
8639
sage: G.edges(key=lambda x: x[2])
8640
[(0, 1, 'F'), (1, 2, 'L'), (2, 3, 'R'), (0, 4, 'U'), (3, 4, 'X')]
8641
8642
TESTS:
8643
8644
It is an error to turn off sorting while providing a key function for sorting. ::
8645
8646
sage: P=graphs.PetersenGraph()
8647
sage: P.edges(sort=False, key=lambda x: x)
8648
Traceback (most recent call last):
8649
...
8650
ValueError: sort keyword is False, yet a key function is given
8651
"""
8652
if not(sort) and key:
8653
raise ValueError('sort keyword is False, yet a key function is given')
8654
L = list(self.edge_iterator(labels=labels))
8655
if sort:
8656
L.sort(key=key)
8657
return L
8658
8659
def edge_boundary(self, vertices1, vertices2=None, labels=True, sort=True):
8660
"""
8661
Returns a list of edges `(u,v,l)` with `u` in ``vertices1``
8662
and `v` in ``vertices2``. If ``vertices2`` is ``None``, then
8663
it is set to the complement of ``vertices1``.
8664
8665
In a digraph, the external boundary of a vertex `v` are those
8666
vertices `u` with an arc `(v, u)`.
8667
8668
INPUT:
8669
8670
8671
- ``labels`` - if ``False``, each edge is a tuple `(u,v)` of
8672
vertices.
8673
8674
8675
EXAMPLES::
8676
8677
sage: K = graphs.CompleteBipartiteGraph(9,3)
8678
sage: len(K.edge_boundary( [0,1,2,3,4,5,6,7,8], [9,10,11] ))
8679
27
8680
sage: K.size()
8681
27
8682
8683
Note that the edge boundary preserves direction::
8684
8685
sage: K = graphs.CompleteBipartiteGraph(9,3).to_directed()
8686
sage: len(K.edge_boundary( [0,1,2,3,4,5,6,7,8], [9,10,11] ))
8687
27
8688
sage: K.size()
8689
54
8690
8691
::
8692
8693
sage: D = DiGraph({0:[1,2], 3:[0]})
8694
sage: D.edge_boundary([0])
8695
[(0, 1, None), (0, 2, None)]
8696
sage: D.edge_boundary([0], labels=False)
8697
[(0, 1), (0, 2)]
8698
8699
TESTS::
8700
8701
sage: G = graphs.DiamondGraph()
8702
sage: G.edge_boundary([0,1])
8703
[(0, 2, None), (1, 2, None), (1, 3, None)]
8704
sage: G.edge_boundary([0], [0])
8705
[]
8706
sage: G.edge_boundary([2], [0])
8707
[(0, 2, None)]
8708
"""
8709
vertices1 = set([v for v in vertices1 if v in self])
8710
if self._directed:
8711
if vertices2 is not None:
8712
vertices2 = set([v for v in vertices2 if v in self])
8713
output = [e for e in self.outgoing_edge_iterator(vertices1,labels=labels)
8714
if e[1] in vertices2]
8715
else:
8716
output = [e for e in self.outgoing_edge_iterator(vertices1,labels=labels)
8717
if e[1] not in vertices1]
8718
else:
8719
if vertices2 is not None:
8720
vertices2 = set([v for v in vertices2 if v in self])
8721
output = [e for e in self.edge_iterator(vertices1,labels=labels)
8722
if (e[0] in vertices1 and e[1] in vertices2) or
8723
(e[1] in vertices1 and e[0] in vertices2)]
8724
else:
8725
output = [e for e in self.edge_iterator(vertices1,labels=labels)
8726
if e[1] not in vertices1 or e[0] not in vertices1]
8727
if sort:
8728
output.sort()
8729
return output
8730
8731
def edge_iterator(self, vertices=None, labels=True, ignore_direction=False):
8732
"""
8733
Returns an iterator over edges.
8734
8735
The iterator returned is over the edges incident with any vertex given
8736
in the parameter ``vertices``. If the graph is directed, iterates over
8737
edges going out only. If vertices is None, then returns an iterator over
8738
all edges. If self is directed, returns outgoing edges only.
8739
8740
INPUT:
8741
8742
- ``vertices`` - (default: None) a vertex, a list of vertices or None
8743
8744
- ``labels`` - if False, each edge is a tuple (u,v) of
8745
vertices.
8746
8747
- ``ignore_direction`` - bool (default: False) - only applies
8748
to directed graphs. If True, searches across edges in either
8749
direction.
8750
8751
8752
EXAMPLES::
8753
8754
sage: for i in graphs.PetersenGraph().edge_iterator([0]):
8755
... print i
8756
(0, 1, None)
8757
(0, 4, None)
8758
(0, 5, None)
8759
sage: D = DiGraph( { 0 : [1,2], 1: [0] } )
8760
sage: for i in D.edge_iterator([0]):
8761
... print i
8762
(0, 1, None)
8763
(0, 2, None)
8764
8765
::
8766
8767
sage: G = graphs.TetrahedralGraph()
8768
sage: list(G.edge_iterator(labels=False))
8769
[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]
8770
8771
::
8772
8773
sage: D = DiGraph({1:[0], 2:[0]})
8774
sage: list(D.edge_iterator(0))
8775
[]
8776
sage: list(D.edge_iterator(0, ignore_direction=True))
8777
[(1, 0, None), (2, 0, None)]
8778
"""
8779
if vertices is None:
8780
vertices = self
8781
elif vertices in self:
8782
vertices = [vertices]
8783
else:
8784
vertices = [v for v in vertices if v in self]
8785
if ignore_direction and self._directed:
8786
from itertools import chain
8787
return chain(self._backend.iterator_out_edges(vertices, labels),
8788
self._backend.iterator_in_edges(vertices, labels))
8789
elif self._directed:
8790
return self._backend.iterator_out_edges(vertices, labels)
8791
else:
8792
return self._backend.iterator_edges(vertices, labels)
8793
8794
def edges_incident(self, vertices=None, labels=True, sort=True):
8795
"""
8796
Returns incident edges to some vertices.
8797
8798
If ``vertices` is a vertex, then it returns the list of edges incident to
8799
that vertex. If ``vertices`` is a list of vertices then it returns the
8800
list of all edges adjacent to those vertices. If ``vertices``
8801
is None, returns a list of all edges in graph. For digraphs, only
8802
lists outward edges.
8803
8804
INPUT:
8805
8806
- ``vertices`` - object (default: None) - a vertex, a list of vertices
8807
or None.
8808
8809
- ``labels`` - bool (default: True) - if False, each edge is a tuple
8810
(u,v) of vertices.
8811
8812
- ``sort`` - bool (default: True) - if True the returned list is sorted.
8813
8814
8815
EXAMPLES::
8816
8817
sage: graphs.PetersenGraph().edges_incident([0,9], labels=False)
8818
[(0, 1), (0, 4), (0, 5), (4, 9), (6, 9), (7, 9)]
8819
sage: D = DiGraph({0:[1]})
8820
sage: D.edges_incident([0])
8821
[(0, 1, None)]
8822
sage: D.edges_incident([1])
8823
[]
8824
8825
TESTS::
8826
8827
sage: G = Graph({0:[0]}, loops=True) # ticket 9581
8828
sage: G.edges_incident(0)
8829
[(0, 0, None)]
8830
"""
8831
if vertices in self:
8832
vertices = [vertices]
8833
8834
if sort:
8835
return sorted(self.edge_iterator(vertices=vertices,labels=labels))
8836
return list(self.edge_iterator(vertices=vertices,labels=labels))
8837
8838
def edge_label(self, u, v=None):
8839
"""
8840
Returns the label of an edge. Note that if the graph allows
8841
multiple edges, then a list of labels on the edge is returned.
8842
8843
EXAMPLES::
8844
8845
sage: G = Graph({0 : {1 : 'edgelabel'}}, sparse=True)
8846
sage: G.edges(labels=False)
8847
[(0, 1)]
8848
sage: G.edge_label( 0, 1 )
8849
'edgelabel'
8850
sage: D = DiGraph({0 : {1 : 'edgelabel'}}, sparse=True)
8851
sage: D.edges(labels=False)
8852
[(0, 1)]
8853
sage: D.edge_label( 0, 1 )
8854
'edgelabel'
8855
8856
::
8857
8858
sage: G = Graph(multiedges=True, sparse=True)
8859
sage: [G.add_edge(0,1,i) for i in range(1,6)]
8860
[None, None, None, None, None]
8861
sage: sorted(G.edge_label(0,1))
8862
[1, 2, 3, 4, 5]
8863
8864
TESTS::
8865
8866
sage: G = Graph()
8867
sage: G.add_edge(0,1,[7])
8868
sage: G.add_edge(0,2,[7])
8869
sage: G.edge_label(0,1)[0] += 1
8870
sage: G.edges()
8871
[(0, 1, [8]), (0, 2, [7])]
8872
8873
"""
8874
return self._backend.get_edge_label(u,v)
8875
8876
def edge_labels(self):
8877
"""
8878
Returns a list of edge labels.
8879
8880
EXAMPLES::
8881
8882
sage: G = Graph({0:{1:'x',2:'z',3:'a'}, 2:{5:'out'}}, sparse=True)
8883
sage: G.edge_labels()
8884
['x', 'z', 'a', 'out']
8885
sage: G = DiGraph({0:{1:'x',2:'z',3:'a'}, 2:{5:'out'}}, sparse=True)
8886
sage: G.edge_labels()
8887
['x', 'z', 'a', 'out']
8888
"""
8889
labels = []
8890
for u,v,l in self.edges():
8891
labels.append(l)
8892
return labels
8893
8894
def remove_multiple_edges(self):
8895
"""
8896
Removes all multiple edges, retaining one edge for each.
8897
8898
EXAMPLES::
8899
8900
sage: G = Graph(multiedges=True, sparse=True)
8901
sage: G.add_edges( [ (0,1), (0,1), (0,1), (0,1), (1,2) ] )
8902
sage: G.edges(labels=False)
8903
[(0, 1), (0, 1), (0, 1), (0, 1), (1, 2)]
8904
8905
::
8906
8907
sage: G.remove_multiple_edges()
8908
sage: G.edges(labels=False)
8909
[(0, 1), (1, 2)]
8910
8911
::
8912
8913
sage: D = DiGraph(multiedges=True, sparse=True)
8914
sage: D.add_edges( [ (0,1,1), (0,1,2), (0,1,3), (0,1,4), (1,2) ] )
8915
sage: D.edges(labels=False)
8916
[(0, 1), (0, 1), (0, 1), (0, 1), (1, 2)]
8917
sage: D.remove_multiple_edges()
8918
sage: D.edges(labels=False)
8919
[(0, 1), (1, 2)]
8920
"""
8921
if self.allows_multiple_edges():
8922
if self._directed:
8923
for v in self:
8924
for u in self.neighbor_in_iterator(v):
8925
edges = self.edge_boundary([u], [v])
8926
if len(edges) > 1:
8927
self.delete_edges(edges[1:])
8928
else:
8929
for v in self:
8930
for u in self.neighbor_iterator(v):
8931
edges = self.edge_boundary([v], [u])
8932
if len(edges) > 1:
8933
self.delete_edges(edges[1:])
8934
8935
def remove_loops(self, vertices=None):
8936
"""
8937
Removes loops on vertices in vertices. If vertices is None, removes
8938
all loops.
8939
8940
EXAMPLE
8941
8942
::
8943
8944
sage: G = Graph(4, loops=True)
8945
sage: G.add_edges( [ (0,0), (1,1), (2,2), (3,3), (2,3) ] )
8946
sage: G.edges(labels=False)
8947
[(0, 0), (1, 1), (2, 2), (2, 3), (3, 3)]
8948
sage: G.remove_loops()
8949
sage: G.edges(labels=False)
8950
[(2, 3)]
8951
sage: G.allows_loops()
8952
True
8953
sage: G.has_loops()
8954
False
8955
8956
sage: D = DiGraph(4, loops=True)
8957
sage: D.add_edges( [ (0,0), (1,1), (2,2), (3,3), (2,3) ] )
8958
sage: D.edges(labels=False)
8959
[(0, 0), (1, 1), (2, 2), (2, 3), (3, 3)]
8960
sage: D.remove_loops()
8961
sage: D.edges(labels=False)
8962
[(2, 3)]
8963
sage: D.allows_loops()
8964
True
8965
sage: D.has_loops()
8966
False
8967
"""
8968
if vertices is None:
8969
vertices = self
8970
for v in vertices:
8971
if self.has_edge(v,v):
8972
self.delete_multiedge(v,v)
8973
8974
def loop_edges(self):
8975
"""
8976
Returns a list of all loops in the graph.
8977
8978
EXAMPLES::
8979
8980
sage: G = Graph(4, loops=True)
8981
sage: G.add_edges( [ (0,0), (1,1), (2,2), (3,3), (2,3) ] )
8982
sage: G.loop_edges()
8983
[(0, 0, None), (1, 1, None), (2, 2, None), (3, 3, None)]
8984
8985
::
8986
8987
sage: D = DiGraph(4, loops=True)
8988
sage: D.add_edges( [ (0,0), (1,1), (2,2), (3,3), (2,3) ] )
8989
sage: D.loop_edges()
8990
[(0, 0, None), (1, 1, None), (2, 2, None), (3, 3, None)]
8991
8992
::
8993
8994
sage: G = Graph(4, loops=True, multiedges=True, sparse=True)
8995
sage: G.add_edges([(i,i) for i in range(4)])
8996
sage: G.loop_edges()
8997
[(0, 0, None), (1, 1, None), (2, 2, None), (3, 3, None)]
8998
"""
8999
if self.allows_multiple_edges():
9000
return [(v,v,l) for v in self.loop_vertices() for l in self.edge_label(v,v)]
9001
else:
9002
return [(v,v,self.edge_label(v,v)) for v in self.loop_vertices()]
9003
9004
def number_of_loops(self):
9005
"""
9006
Returns the number of edges that are loops.
9007
9008
EXAMPLES::
9009
9010
sage: G = Graph(4, loops=True)
9011
sage: G.add_edges( [ (0,0), (1,1), (2,2), (3,3), (2,3) ] )
9012
sage: G.edges(labels=False)
9013
[(0, 0), (1, 1), (2, 2), (2, 3), (3, 3)]
9014
sage: G.number_of_loops()
9015
4
9016
9017
::
9018
9019
sage: D = DiGraph(4, loops=True)
9020
sage: D.add_edges( [ (0,0), (1,1), (2,2), (3,3), (2,3) ] )
9021
sage: D.edges(labels=False)
9022
[(0, 0), (1, 1), (2, 2), (2, 3), (3, 3)]
9023
sage: D.number_of_loops()
9024
4
9025
"""
9026
return len(self.loop_edges())
9027
9028
### Modifications
9029
9030
def clear(self):
9031
"""
9032
Empties the graph of vertices and edges and removes name, boundary,
9033
associated objects, and position information.
9034
9035
EXAMPLES::
9036
9037
sage: G=graphs.CycleGraph(4); G.set_vertices({0:'vertex0'})
9038
sage: G.order(); G.size()
9039
4
9040
4
9041
sage: len(G._pos)
9042
4
9043
sage: G.name()
9044
'Cycle graph'
9045
sage: G.get_vertex(0)
9046
'vertex0'
9047
sage: H = G.copy(implementation='c_graph', sparse=True)
9048
sage: H.clear()
9049
sage: H.order(); H.size()
9050
0
9051
0
9052
sage: len(H._pos)
9053
0
9054
sage: H.name()
9055
''
9056
sage: H.get_vertex(0)
9057
sage: H = G.copy(implementation='c_graph', sparse=False)
9058
sage: H.clear()
9059
sage: H.order(); H.size()
9060
0
9061
0
9062
sage: len(H._pos)
9063
0
9064
sage: H.name()
9065
''
9066
sage: H.get_vertex(0)
9067
sage: H = G.copy(implementation='networkx')
9068
sage: H.clear()
9069
sage: H.order(); H.size()
9070
0
9071
0
9072
sage: len(H._pos)
9073
0
9074
sage: H.name()
9075
''
9076
sage: H.get_vertex(0)
9077
"""
9078
self.name('')
9079
self.delete_vertices(self.vertices())
9080
9081
### Degree functions
9082
9083
def degree(self, vertices=None, labels=False):
9084
"""
9085
Gives the degree (in + out for digraphs) of a vertex or of
9086
vertices.
9087
9088
INPUT:
9089
9090
9091
- ``vertices`` - If vertices is a single vertex,
9092
returns the number of neighbors of vertex. If vertices is an
9093
iterable container of vertices, returns a list of degrees. If
9094
vertices is None, same as listing all vertices.
9095
9096
- ``labels`` - see OUTPUT
9097
9098
9099
OUTPUT: Single vertex- an integer. Multiple vertices- a list of
9100
integers. If labels is True, then returns a dictionary mapping each
9101
vertex to its degree.
9102
9103
EXAMPLES::
9104
9105
sage: P = graphs.PetersenGraph()
9106
sage: P.degree(5)
9107
3
9108
9109
::
9110
9111
sage: K = graphs.CompleteGraph(9)
9112
sage: K.degree()
9113
[8, 8, 8, 8, 8, 8, 8, 8, 8]
9114
9115
::
9116
9117
sage: D = DiGraph( { 0: [1,2,3], 1: [0,2], 2: [3], 3: [4], 4: [0,5], 5: [1] } )
9118
sage: D.degree(vertices = [0,1,2], labels=True)
9119
{0: 5, 1: 4, 2: 3}
9120
sage: D.degree()
9121
[5, 4, 3, 3, 3, 2]
9122
"""
9123
if labels:
9124
return dict(self.degree_iterator(vertices,labels))
9125
elif vertices in self and not labels:
9126
return self.degree_iterator(vertices,labels).next()
9127
else:
9128
return list(self.degree_iterator(vertices,labels))
9129
9130
def average_degree(self):
9131
r"""
9132
Returns the average degree of the graph.
9133
9134
The average degree of a graph `G=(V,E)` is equal to
9135
``\frac {2|E|}{|V|}``.
9136
9137
EXAMPLES:
9138
9139
The average degree of a regular graph is equal to the
9140
degree of any vertex::
9141
9142
sage: g = graphs.CompleteGraph(5)
9143
sage: g.average_degree() == 4
9144
True
9145
9146
The average degree of a tree is always strictly less than
9147
`2`::
9148
9149
sage: g = graphs.RandomGNP(20,.5)
9150
sage: tree = Graph()
9151
sage: tree.add_edges(g.min_spanning_tree())
9152
sage: tree.average_degree() < 2
9153
True
9154
9155
For any graph, it is equal to ``\frac {2|E|}{|V|}``::
9156
9157
sage: g = graphs.RandomGNP(50,.8)
9158
sage: g.average_degree() == 2*g.size()/g.order()
9159
True
9160
"""
9161
9162
return 2*Integer(self.size())/Integer(self.order())
9163
9164
def maximum_average_degree(self, value_only=True, solver = None, verbose = 0):
9165
r"""
9166
Returns the Maximum Average Degree (MAD) of the current graph.
9167
9168
The Maximum Average Degree (MAD) of a graph is defined as
9169
the average degree of its densest subgraph. More formally,
9170
``Mad(G) = \max_{H\subseteq G} Ad(H)``, where `Ad(G)` denotes
9171
the average degree of `G`.
9172
9173
This can be computed in polynomial time.
9174
9175
INPUT:
9176
9177
- ``value_only`` (boolean) -- ``True`` by default
9178
9179
- If ``value_only=True``, only the numerical
9180
value of the `MAD` is returned.
9181
9182
- Else, the subgraph of `G` realizing the `MAD`
9183
is returned.
9184
9185
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
9186
solver to be used. If set to ``None``, the default one is used. For
9187
more information on LP solvers and which default solver is used, see
9188
the method
9189
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
9190
of the class
9191
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
9192
9193
- ``verbose`` -- integer (default: ``0``). Sets the level of
9194
verbosity. Set to 0 by default, which means quiet.
9195
9196
EXAMPLES:
9197
9198
In any graph, the `Mad` is always larger than the average
9199
degree::
9200
9201
sage: g = graphs.RandomGNP(20,.3)
9202
sage: mad_g = g.maximum_average_degree()
9203
sage: g.average_degree() <= mad_g
9204
True
9205
9206
Unlike the average degree, the `Mad` of the disjoint
9207
union of two graphs is the maximum of the `Mad` of each
9208
graphs::
9209
9210
sage: h = graphs.RandomGNP(20,.3)
9211
sage: mad_h = h.maximum_average_degree()
9212
sage: (g+h).maximum_average_degree() == max(mad_g, mad_h)
9213
True
9214
9215
The subgraph of a regular graph realizing the maximum
9216
average degree is always the whole graph ::
9217
9218
sage: g = graphs.CompleteGraph(5)
9219
sage: mad_g = g.maximum_average_degree(value_only=False)
9220
sage: g.is_isomorphic(mad_g)
9221
True
9222
9223
This also works for complete bipartite graphs ::
9224
9225
sage: g = graphs.CompleteBipartiteGraph(3,4)
9226
sage: mad_g = g.maximum_average_degree(value_only=False)
9227
sage: g.is_isomorphic(mad_g)
9228
True
9229
"""
9230
9231
g = self
9232
from sage.numerical.mip import MixedIntegerLinearProgram, Sum
9233
9234
p = MixedIntegerLinearProgram(maximization=True, solver = solver)
9235
9236
d = p.new_variable()
9237
one = p.new_variable()
9238
9239
# Reorders u and v so that uv and vu are not considered
9240
# to be different edges
9241
reorder = lambda u,v : (min(u,v),max(u,v))
9242
9243
for u,v in g.edge_iterator(labels=False):
9244
p.add_constraint( one[ reorder(u,v) ] - 2*d[u] , max = 0 )
9245
p.add_constraint( one[ reorder(u,v) ] - 2*d[v] , max = 0 )
9246
9247
p.add_constraint( Sum([d[v] for v in g]), max = 1)
9248
9249
p.set_objective( Sum([ one[reorder(u,v)] for u,v in g.edge_iterator(labels=False)]) )
9250
9251
obj = p.solve(log = verbose)
9252
9253
# Paying attention to numerical error :
9254
# The zero values could be something like 0.000000000001
9255
# so I can not write l > 0
9256
# And the non-zero, though they should be equal to
9257
# 1/(order of the optimal subgraph) may be a bit lower
9258
9259
# setting the minimum to 1/(10 * size of the whole graph )
9260
# should be safe :-)
9261
m = 1/(10 *Integer(g.order()))
9262
g_mad = g.subgraph([v for v,l in p.get_values(d).iteritems() if l>m ])
9263
9264
if value_only:
9265
return g_mad.average_degree()
9266
else:
9267
return g_mad
9268
9269
def degree_histogram(self):
9270
"""
9271
Returns a list, whose ith entry is the frequency of degree i.
9272
9273
EXAMPLES::
9274
9275
sage: G = graphs.Grid2dGraph(9,12)
9276
sage: G.degree_histogram()
9277
[0, 0, 4, 34, 70]
9278
9279
::
9280
9281
sage: G = graphs.Grid2dGraph(9,12).to_directed()
9282
sage: G.degree_histogram()
9283
[0, 0, 0, 0, 4, 0, 34, 0, 70]
9284
"""
9285
degree_sequence = self.degree()
9286
dmax = max(degree_sequence) + 1
9287
frequency = [0]*dmax
9288
for d in degree_sequence:
9289
frequency[d] += 1
9290
return frequency
9291
9292
def degree_iterator(self, vertices=None, labels=False):
9293
"""
9294
Returns an iterator over the degrees of the (di)graph.
9295
9296
In the case of a digraph, the degree is defined as the sum of the
9297
in-degree and the out-degree, i.e. the total number of edges incident to
9298
a given vertex.
9299
9300
INPUT:
9301
9302
- ``labels`` (boolean) -- if set to ``False`` (default) the method
9303
returns an iterator over degrees. Otherwise it returns an iterator
9304
over tuples (vertex, degree).
9305
9306
- ``vertices`` - if specified, restrict to this
9307
subset.
9308
9309
9310
EXAMPLES::
9311
9312
sage: G = graphs.Grid2dGraph(3,4)
9313
sage: for i in G.degree_iterator():
9314
... print i
9315
3
9316
4
9317
2
9318
...
9319
2
9320
4
9321
sage: for i in G.degree_iterator(labels=True):
9322
... print i
9323
((0, 1), 3)
9324
((1, 2), 4)
9325
((0, 0), 2)
9326
...
9327
((0, 3), 2)
9328
((1, 1), 4)
9329
9330
::
9331
9332
sage: D = graphs.Grid2dGraph(2,4).to_directed()
9333
sage: for i in D.degree_iterator():
9334
... print i
9335
6
9336
6
9337
...
9338
4
9339
6
9340
sage: for i in D.degree_iterator(labels=True):
9341
... print i
9342
((0, 1), 6)
9343
((1, 2), 6)
9344
...
9345
((0, 3), 4)
9346
((1, 1), 6)
9347
"""
9348
if vertices is None:
9349
vertices = self
9350
elif vertices in self:
9351
vertices = [vertices]
9352
else:
9353
vertices = [v for v in vertices if v in self]
9354
if labels:
9355
filter = lambda v, self: (v, self._backend.degree(v, self._directed))
9356
else:
9357
filter = lambda v, self: self._backend.degree(v, self._directed)
9358
for v in vertices:
9359
yield filter(v, self)
9360
9361
def degree_sequence(self):
9362
r"""
9363
Return the degree sequence of this (di)graph.
9364
9365
EXAMPLES:
9366
9367
The degree sequence of an undirected graph::
9368
9369
sage: g = Graph({1: [2, 5], 2: [1, 5, 3, 4], 3: [2, 5], 4: [3], 5: [2, 3]})
9370
sage: g.degree_sequence()
9371
[4, 3, 3, 2, 2]
9372
9373
The degree sequence of a digraph::
9374
9375
sage: g = DiGraph({1: [2, 5, 6], 2: [3, 6], 3: [4, 6], 4: [6], 5: [4, 6]})
9376
sage: g.degree_sequence()
9377
[5, 3, 3, 3, 3, 3]
9378
9379
Degree sequences of some common graphs::
9380
9381
sage: graphs.PetersenGraph().degree_sequence()
9382
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3]
9383
sage: graphs.HouseGraph().degree_sequence()
9384
[3, 3, 2, 2, 2]
9385
sage: graphs.FlowerSnark().degree_sequence()
9386
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]
9387
"""
9388
return sorted(self.degree_iterator(), reverse=True)
9389
9390
def is_regular(self, k = None):
9391
"""
9392
Return ``True`` if this graph is (`k`-)regular.
9393
9394
INPUT:
9395
9396
- ``k`` (default: ``None``) - the degree of regularity to
9397
check for
9398
9399
EXAMPLES::
9400
9401
sage: G = graphs.HoffmanSingletonGraph()
9402
sage: G.is_regular()
9403
True
9404
sage: G.is_regular(9)
9405
False
9406
9407
So the Hoffman-Singleton graph is regular, but not
9408
9-regular. In fact, we can now find the degree easily as
9409
follows::
9410
9411
sage: G.degree_iterator().next()
9412
7
9413
9414
The house graph is not regular::
9415
9416
sage: graphs.HouseGraph().is_regular()
9417
False
9418
9419
A graph without vertices is `k`-regular for every `k`::
9420
9421
sage: Graph().is_regular()
9422
True
9423
"""
9424
if self.order() == 0:
9425
return True
9426
9427
deg_it = self.degree_iterator()
9428
if k is None:
9429
k = deg_it.next()
9430
9431
for d in deg_it:
9432
if d != k:
9433
return False
9434
9435
return True
9436
9437
9438
9439
### Substructures
9440
9441
def subgraph(self, vertices=None, edges=None, inplace=False,
9442
vertex_property=None, edge_property=None, algorithm=None):
9443
"""
9444
Returns the subgraph containing the given vertices and edges.
9445
9446
If either vertices or edges are not specified, they are assumed to be
9447
all vertices or edges. If edges are not specified, returns the subgraph
9448
induced by the vertices.
9449
9450
INPUT:
9451
9452
9453
- ``inplace`` - Using inplace is True will simply
9454
delete the extra vertices and edges from the current graph. This
9455
will modify the graph.
9456
9457
- ``vertices`` - Vertices can be a single vertex or an
9458
iterable container of vertices, e.g. a list, set, graph, file or
9459
numeric array. If not passed, defaults to the entire graph.
9460
9461
- ``edges`` - As with vertices, edges can be a single
9462
edge or an iterable container of edges (e.g., a list, set, file,
9463
numeric array, etc.). If not edges are not specified, then all
9464
edges are assumed and the returned graph is an induced subgraph. In
9465
the case of multiple edges, specifying an edge as (u,v) means to
9466
keep all edges (u,v), regardless of the label.
9467
9468
- ``vertex_property`` - If specified, this is
9469
expected to be a function on vertices, which is intersected with
9470
the vertices specified, if any are.
9471
9472
- ``edge_property`` - If specified, this is expected
9473
to be a function on edges, which is intersected with the edges
9474
specified, if any are.
9475
9476
- ``algorithm`` - If ``algorithm=delete`` or ``inplace=True``,
9477
then the graph is constructed by deleting edges and
9478
vertices. If ``add``, then the graph is constructed by
9479
building a new graph from the appropriate vertices and
9480
edges. If not specified, then the algorithm is chosen based
9481
on the number of vertices in the subgraph.
9482
9483
9484
EXAMPLES::
9485
9486
sage: G = graphs.CompleteGraph(9)
9487
sage: H = G.subgraph([0,1,2]); H
9488
Subgraph of (Complete graph): Graph on 3 vertices
9489
sage: G
9490
Complete graph: Graph on 9 vertices
9491
sage: J = G.subgraph(edges=[(0,1)])
9492
sage: J.edges(labels=False)
9493
[(0, 1)]
9494
sage: J.vertices()==G.vertices()
9495
True
9496
sage: G.subgraph([0,1,2], inplace=True); G
9497
Subgraph of (Complete graph): Graph on 3 vertices
9498
sage: G.subgraph()==G
9499
True
9500
9501
::
9502
9503
sage: D = graphs.CompleteGraph(9).to_directed()
9504
sage: H = D.subgraph([0,1,2]); H
9505
Subgraph of (Complete graph): Digraph on 3 vertices
9506
sage: H = D.subgraph(edges=[(0,1), (0,2)])
9507
sage: H.edges(labels=False)
9508
[(0, 1), (0, 2)]
9509
sage: H.vertices()==D.vertices()
9510
True
9511
sage: D
9512
Complete graph: Digraph on 9 vertices
9513
sage: D.subgraph([0,1,2], inplace=True); D
9514
Subgraph of (Complete graph): Digraph on 3 vertices
9515
sage: D.subgraph()==D
9516
True
9517
9518
A more complicated example involving multiple edges and labels.
9519
9520
::
9521
9522
sage: G = Graph(multiedges=True, sparse=True)
9523
sage: G.add_edges([(0,1,'a'), (0,1,'b'), (1,0,'c'), (0,2,'d'), (0,2,'e'), (2,0,'f'), (1,2,'g')])
9524
sage: G.subgraph(edges=[(0,1), (0,2,'d'), (0,2,'not in graph')]).edges()
9525
[(0, 1, 'a'), (0, 1, 'b'), (0, 1, 'c'), (0, 2, 'd')]
9526
sage: J = G.subgraph(vertices=[0,1], edges=[(0,1,'a'), (0,2,'c')])
9527
sage: J.edges()
9528
[(0, 1, 'a')]
9529
sage: J.vertices()
9530
[0, 1]
9531
sage: G.subgraph(vertices=G.vertices())==G
9532
True
9533
9534
::
9535
9536
sage: D = DiGraph(multiedges=True, sparse=True)
9537
sage: D.add_edges([(0,1,'a'), (0,1,'b'), (1,0,'c'), (0,2,'d'), (0,2,'e'), (2,0,'f'), (1,2,'g')])
9538
sage: D.subgraph(edges=[(0,1), (0,2,'d'), (0,2,'not in graph')]).edges()
9539
[(0, 1, 'a'), (0, 1, 'b'), (0, 2, 'd')]
9540
sage: H = D.subgraph(vertices=[0,1], edges=[(0,1,'a'), (0,2,'c')])
9541
sage: H.edges()
9542
[(0, 1, 'a')]
9543
sage: H.vertices()
9544
[0, 1]
9545
9546
Using the property arguments::
9547
9548
sage: P = graphs.PetersenGraph()
9549
sage: S = P.subgraph(vertex_property = lambda v : v%2 == 0)
9550
sage: S.vertices()
9551
[0, 2, 4, 6, 8]
9552
9553
::
9554
9555
sage: C = graphs.CubeGraph(2)
9556
sage: S = C.subgraph(edge_property=(lambda e: e[0][0] == e[1][0]))
9557
sage: C.edges()
9558
[('00', '01', None), ('00', '10', None), ('01', '11', None), ('10', '11', None)]
9559
sage: S.edges()
9560
[('00', '01', None), ('10', '11', None)]
9561
9562
9563
The algorithm is not specified, then a reasonable choice is made for speed.
9564
9565
::
9566
9567
sage: g=graphs.PathGraph(1000)
9568
sage: g.subgraph(range(10)) # uses the 'add' algorithm
9569
Subgraph of (Path Graph): Graph on 10 vertices
9570
9571
9572
9573
TESTS: The appropriate properties are preserved.
9574
9575
::
9576
9577
sage: g = graphs.PathGraph(10)
9578
sage: g.is_planar(set_embedding=True)
9579
True
9580
sage: g.set_vertices(dict((v, 'v%d'%v) for v in g.vertices()))
9581
sage: h = g.subgraph([3..5])
9582
sage: h.get_pos().keys()
9583
[3, 4, 5]
9584
sage: h.get_vertices()
9585
{3: 'v3', 4: 'v4', 5: 'v5'}
9586
"""
9587
if vertices is None:
9588
vertices=self.vertices()
9589
elif vertices in self:
9590
vertices=[vertices]
9591
else:
9592
vertices=list(vertices)
9593
9594
if vertex_property is not None:
9595
vertices = [v for v in vertices if vertex_property(v)]
9596
9597
if algorithm is not None and algorithm not in ("delete", "add"):
9598
raise ValueError('algorithm should be None, "delete", or "add"')
9599
9600
if inplace or len(vertices)>0.05*self.order() or algorithm=="delete":
9601
return self._subgraph_by_deleting(vertices=vertices, edges=edges,
9602
inplace=inplace,
9603
edge_property=edge_property)
9604
else:
9605
return self._subgraph_by_adding(vertices=vertices, edges=edges,
9606
edge_property=edge_property)
9607
9608
def _subgraph_by_adding(self, vertices=None, edges=None, edge_property=None):
9609
"""
9610
Returns the subgraph containing the given vertices and edges.
9611
The edges also satisfy the edge_property, if it is not None.
9612
The subgraph is created by creating a new empty graph and
9613
adding the necessary vertices, edges, and other properties.
9614
9615
INPUT:
9616
9617
- ``vertices`` - Vertices is a list of vertices
9618
9619
- ``edges`` - Edges can be a single edge or an iterable
9620
container of edges (e.g., a list, set, file, numeric array,
9621
etc.). If not edges are not specified, then all edges are
9622
assumed and the returned graph is an induced subgraph. In
9623
the case of multiple edges, specifying an edge as (u,v)
9624
means to keep all edges (u,v), regardless of the label.
9625
9626
- ``edge_property`` - If specified, this is expected
9627
to be a function on edges, which is intersected with the edges
9628
specified, if any are.
9629
9630
9631
EXAMPLES::
9632
9633
sage: G = graphs.CompleteGraph(9)
9634
sage: H = G._subgraph_by_adding([0,1,2]); H
9635
Subgraph of (Complete graph): Graph on 3 vertices
9636
sage: G
9637
Complete graph: Graph on 9 vertices
9638
sage: J = G._subgraph_by_adding(vertices=G.vertices(), edges=[(0,1)])
9639
sage: J.edges(labels=False)
9640
[(0, 1)]
9641
sage: J.vertices()==G.vertices()
9642
True
9643
sage: G._subgraph_by_adding(vertices=G.vertices())==G
9644
True
9645
9646
::
9647
9648
sage: D = graphs.CompleteGraph(9).to_directed()
9649
sage: H = D._subgraph_by_adding([0,1,2]); H
9650
Subgraph of (Complete graph): Digraph on 3 vertices
9651
sage: H = D._subgraph_by_adding(vertices=D.vertices(), edges=[(0,1), (0,2)])
9652
sage: H.edges(labels=False)
9653
[(0, 1), (0, 2)]
9654
sage: H.vertices()==D.vertices()
9655
True
9656
sage: D
9657
Complete graph: Digraph on 9 vertices
9658
sage: D._subgraph_by_adding(D.vertices())==D
9659
True
9660
9661
A more complicated example involving multiple edges and labels.
9662
9663
::
9664
9665
sage: G = Graph(multiedges=True, sparse=True)
9666
sage: G.add_edges([(0,1,'a'), (0,1,'b'), (1,0,'c'), (0,2,'d'), (0,2,'e'), (2,0,'f'), (1,2,'g')])
9667
sage: G._subgraph_by_adding(G.vertices(), edges=[(0,1), (0,2,'d'), (0,2,'not in graph')]).edges()
9668
[(0, 1, 'a'), (0, 1, 'b'), (0, 1, 'c'), (0, 2, 'd')]
9669
sage: J = G._subgraph_by_adding(vertices=[0,1], edges=[(0,1,'a'), (0,2,'c')])
9670
sage: J.edges()
9671
[(0, 1, 'a')]
9672
sage: J.vertices()
9673
[0, 1]
9674
sage: G._subgraph_by_adding(vertices=G.vertices())==G
9675
True
9676
9677
::
9678
9679
sage: D = DiGraph(multiedges=True, sparse=True)
9680
sage: D.add_edges([(0,1,'a'), (0,1,'b'), (1,0,'c'), (0,2,'d'), (0,2,'e'), (2,0,'f'), (1,2,'g')])
9681
sage: D._subgraph_by_adding(vertices=D.vertices(), edges=[(0,1), (0,2,'d'), (0,2,'not in graph')]).edges()
9682
[(0, 1, 'a'), (0, 1, 'b'), (0, 2, 'd')]
9683
sage: H = D._subgraph_by_adding(vertices=[0,1], edges=[(0,1,'a'), (0,2,'c')])
9684
sage: H.edges()
9685
[(0, 1, 'a')]
9686
sage: H.vertices()
9687
[0, 1]
9688
9689
Using the property arguments::
9690
9691
sage: C = graphs.CubeGraph(2)
9692
sage: S = C._subgraph_by_adding(vertices=C.vertices(), edge_property=(lambda e: e[0][0] == e[1][0]))
9693
sage: C.edges()
9694
[('00', '01', None), ('00', '10', None), ('01', '11', None), ('10', '11', None)]
9695
sage: S.edges()
9696
[('00', '01', None), ('10', '11', None)]
9697
9698
TESTS: Properties of the graph are preserved.
9699
9700
::
9701
9702
sage: g = graphs.PathGraph(10)
9703
sage: g.is_planar(set_embedding=True)
9704
True
9705
sage: g.set_vertices(dict((v, 'v%d'%v) for v in g.vertices()))
9706
sage: h = g._subgraph_by_adding([3..5])
9707
sage: h.get_pos().keys()
9708
[3, 4, 5]
9709
sage: h.get_vertices()
9710
{3: 'v3', 4: 'v4', 5: 'v5'}
9711
"""
9712
G = self.__class__(weighted=self._weighted, loops=self.allows_loops(),
9713
multiedges= self.allows_multiple_edges())
9714
G.name("Subgraph of (%s)"%self.name())
9715
G.add_vertices(vertices)
9716
if edges is not None:
9717
if G._directed:
9718
edges_graph = (e for e in self.edge_iterator(vertices) if e[1] in vertices)
9719
edges_to_keep_labeled = [e for e in edges if len(e)==3]
9720
edges_to_keep_unlabeled = [e for e in edges if len(e)==2]
9721
else:
9722
edges_graph = (sorted(e[0:2])+[e[2]] for e in self.edge_iterator(vertices) if e[0] in vertices and e[1] in vertices)
9723
edges_to_keep_labeled = [sorted(e[0:2])+[e[2]] for e in edges if len(e)==3]
9724
edges_to_keep_unlabeled = [sorted(e) for e in edges if len(e)==2]
9725
edges_to_keep = [tuple(e) for e in edges_graph if e in edges_to_keep_labeled
9726
or e[0:2] in edges_to_keep_unlabeled]
9727
else:
9728
edges_to_keep=[e for e in self.edge_iterator(vertices) if e[0] in vertices and e[1] in vertices]
9729
9730
if edge_property is not None:
9731
edges_to_keep = [e for e in edges_to_keep if edge_property(e)]
9732
G.add_edges(edges_to_keep)
9733
9734
attributes_to_update = ('_pos', '_assoc')
9735
for attr in attributes_to_update:
9736
if hasattr(self, attr) and getattr(self, attr) is not None:
9737
value = dict([(v, getattr(self, attr).get(v, None)) for v in G])
9738
setattr(G, attr,value)
9739
9740
G._boundary = [v for v in self._boundary if v in G]
9741
9742
return G
9743
9744
def _subgraph_by_deleting(self, vertices=None, edges=None, inplace=False,
9745
edge_property=None):
9746
"""
9747
Returns the subgraph containing the given vertices and edges.
9748
The edges also satisfy the edge_property, if it is not None.
9749
The subgraph is created by creating deleting things that are
9750
not needed.
9751
9752
INPUT:
9753
9754
- ``vertices`` - Vertices is a list of vertices
9755
9756
- ``edges`` - Edges can be a single edge or an iterable
9757
container of edges (e.g., a list, set, file, numeric array,
9758
etc.). If not edges are not specified, then all edges are
9759
assumed and the returned graph is an induced subgraph. In
9760
the case of multiple edges, specifying an edge as (u,v)
9761
means to keep all edges (u,v), regardless of the label.
9762
9763
- ``edge_property`` - If specified, this is expected
9764
to be a function on edges, which is intersected with the edges
9765
specified, if any are.
9766
9767
- ``inplace`` - Using inplace is True will simply
9768
delete the extra vertices and edges from the current graph. This
9769
will modify the graph.
9770
9771
9772
EXAMPLES::
9773
9774
sage: G = graphs.CompleteGraph(9)
9775
sage: H = G._subgraph_by_deleting([0,1,2]); H
9776
Subgraph of (Complete graph): Graph on 3 vertices
9777
sage: G
9778
Complete graph: Graph on 9 vertices
9779
sage: J = G._subgraph_by_deleting(vertices=G.vertices(), edges=[(0,1)])
9780
sage: J.edges(labels=False)
9781
[(0, 1)]
9782
sage: J.vertices()==G.vertices()
9783
True
9784
sage: G._subgraph_by_deleting([0,1,2], inplace=True); G
9785
Subgraph of (Complete graph): Graph on 3 vertices
9786
sage: G._subgraph_by_deleting(vertices=G.vertices())==G
9787
True
9788
9789
::
9790
9791
sage: D = graphs.CompleteGraph(9).to_directed()
9792
sage: H = D._subgraph_by_deleting([0,1,2]); H
9793
Subgraph of (Complete graph): Digraph on 3 vertices
9794
sage: H = D._subgraph_by_deleting(vertices=D.vertices(), edges=[(0,1), (0,2)])
9795
sage: H.edges(labels=False)
9796
[(0, 1), (0, 2)]
9797
sage: H.vertices()==D.vertices()
9798
True
9799
sage: D
9800
Complete graph: Digraph on 9 vertices
9801
sage: D._subgraph_by_deleting([0,1,2], inplace=True); D
9802
Subgraph of (Complete graph): Digraph on 3 vertices
9803
sage: D._subgraph_by_deleting(D.vertices())==D
9804
True
9805
9806
A more complicated example involving multiple edges and labels.
9807
9808
::
9809
9810
sage: G = Graph(multiedges=True, sparse=True)
9811
sage: G.add_edges([(0,1,'a'), (0,1,'b'), (1,0,'c'), (0,2,'d'), (0,2,'e'), (2,0,'f'), (1,2,'g')])
9812
sage: G._subgraph_by_deleting(G.vertices(), edges=[(0,1), (0,2,'d'), (0,2,'not in graph')]).edges()
9813
[(0, 1, 'a'), (0, 1, 'b'), (0, 1, 'c'), (0, 2, 'd')]
9814
sage: J = G._subgraph_by_deleting(vertices=[0,1], edges=[(0,1,'a'), (0,2,'c')])
9815
sage: J.edges()
9816
[(0, 1, 'a')]
9817
sage: J.vertices()
9818
[0, 1]
9819
sage: G._subgraph_by_deleting(vertices=G.vertices())==G
9820
True
9821
9822
::
9823
9824
sage: D = DiGraph(multiedges=True, sparse=True)
9825
sage: D.add_edges([(0,1,'a'), (0,1,'b'), (1,0,'c'), (0,2,'d'), (0,2,'e'), (2,0,'f'), (1,2,'g')])
9826
sage: D._subgraph_by_deleting(vertices=D.vertices(), edges=[(0,1), (0,2,'d'), (0,2,'not in graph')]).edges()
9827
[(0, 1, 'a'), (0, 1, 'b'), (0, 2, 'd')]
9828
sage: H = D._subgraph_by_deleting(vertices=[0,1], edges=[(0,1,'a'), (0,2,'c')])
9829
sage: H.edges()
9830
[(0, 1, 'a')]
9831
sage: H.vertices()
9832
[0, 1]
9833
9834
Using the property arguments::
9835
9836
sage: C = graphs.CubeGraph(2)
9837
sage: S = C._subgraph_by_deleting(vertices=C.vertices(), edge_property=(lambda e: e[0][0] == e[1][0]))
9838
sage: C.edges()
9839
[('00', '01', None), ('00', '10', None), ('01', '11', None), ('10', '11', None)]
9840
sage: S.edges()
9841
[('00', '01', None), ('10', '11', None)]
9842
9843
TESTS: Properties of the graph are preserved.
9844
9845
::
9846
9847
sage: g = graphs.PathGraph(10)
9848
sage: g.is_planar(set_embedding=True)
9849
True
9850
sage: g.set_vertices(dict((v, 'v%d'%v) for v in g.vertices()))
9851
sage: h = g._subgraph_by_deleting([3..5])
9852
sage: h.get_pos().keys()
9853
[3, 4, 5]
9854
sage: h.get_vertices()
9855
{3: 'v3', 4: 'v4', 5: 'v5'}
9856
"""
9857
if inplace:
9858
G = self
9859
else:
9860
G = self.copy()
9861
G.name("Subgraph of (%s)"%self.name())
9862
9863
G.delete_vertices([v for v in G if v not in vertices])
9864
9865
edges_to_delete=[]
9866
if edges is not None:
9867
if G._directed:
9868
edges_graph = G.edge_iterator()
9869
edges_to_keep_labeled = [e for e in edges if len(e)==3]
9870
edges_to_keep_unlabeled = [e for e in edges if len(e)==2]
9871
else:
9872
edges_graph = [sorted(e[0:2])+[e[2]] for e in G.edge_iterator()]
9873
edges_to_keep_labeled = [sorted(e[0:2])+[e[2]] for e in edges if len(e)==3]
9874
edges_to_keep_unlabeled = [sorted(e) for e in edges if len(e)==2]
9875
for e in edges_graph:
9876
if e not in edges_to_keep_labeled and e[0:2] not in edges_to_keep_unlabeled:
9877
edges_to_delete.append(tuple(e))
9878
if edge_property is not None:
9879
for e in G.edge_iterator():
9880
if not edge_property(e):
9881
# We might get duplicate edges, but this does
9882
# handle the case of multiple edges.
9883
edges_to_delete.append(e)
9884
9885
G.delete_edges(edges_to_delete)
9886
if not inplace:
9887
return G
9888
9889
def subgraph_search(self, G, induced=False):
9890
r"""
9891
Returns a copy of ``G`` in ``self``.
9892
9893
INPUT:
9894
9895
- ``G`` -- the graph whose copy we are looking for in ``self``.
9896
9897
- ``induced`` -- boolean (default: ``False``). Whether or not to
9898
search for an induced copy of ``G`` in ``self``.
9899
9900
OUTPUT:
9901
9902
- If ``induced=False``, return a copy of ``G`` in this graph.
9903
Otherwise, return an induced copy of ``G`` in ``self``. If ``G``
9904
is the empty graph, return the empty graph since it is a subgraph
9905
of every graph. Now suppose ``G`` is not the empty graph. If there
9906
is no copy (induced or otherwise) of ``G`` in ``self``, we return
9907
``None``.
9908
9909
.. NOTE::
9910
9911
This method also works on digraphs.
9912
9913
.. SEEALSO::
9914
9915
- :meth:`~GenericGraph.subgraph_search_count` -- Counts the number
9916
of copies of a graph `H` inside of a graph `G`
9917
9918
- :meth:`~GenericGraph.subgraph_search_iterator` -- Iterate on the
9919
copies of a graph `H` inside of a graph `G`
9920
9921
ALGORITHM:
9922
9923
Brute-force search.
9924
9925
EXAMPLES:
9926
9927
The Petersen graph contains the path graph `P_5`::
9928
9929
sage: g = graphs.PetersenGraph()
9930
sage: h1 = g.subgraph_search(graphs.PathGraph(5)); h1
9931
Subgraph of (Petersen graph): Graph on 5 vertices
9932
sage: h1.vertices(); h1.edges(labels=False)
9933
[0, 1, 2, 3, 4]
9934
[(0, 1), (1, 2), (2, 3), (3, 4)]
9935
sage: I1 = g.subgraph_search(graphs.PathGraph(5), induced=True); I1
9936
Subgraph of (Petersen graph): Graph on 5 vertices
9937
sage: I1.vertices(); I1.edges(labels=False)
9938
[0, 1, 2, 3, 8]
9939
[(0, 1), (1, 2), (2, 3), (3, 8)]
9940
9941
It also contains the claw `K_{1,3}`::
9942
9943
sage: h2 = g.subgraph_search(graphs.ClawGraph()); h2
9944
Subgraph of (Petersen graph): Graph on 4 vertices
9945
sage: h2.vertices(); h2.edges(labels=False)
9946
[0, 1, 4, 5]
9947
[(0, 1), (0, 4), (0, 5)]
9948
sage: I2 = g.subgraph_search(graphs.ClawGraph(), induced=True); I2
9949
Subgraph of (Petersen graph): Graph on 4 vertices
9950
sage: I2.vertices(); I2.edges(labels=False)
9951
[0, 1, 4, 5]
9952
[(0, 1), (0, 4), (0, 5)]
9953
9954
Of course the induced copies are isomorphic to the graphs we were
9955
looking for::
9956
9957
sage: I1.is_isomorphic(graphs.PathGraph(5))
9958
True
9959
sage: I2.is_isomorphic(graphs.ClawGraph())
9960
True
9961
9962
However, the Petersen graph does not contain a subgraph isomorphic to
9963
`K_3`::
9964
9965
sage: g.subgraph_search(graphs.CompleteGraph(3)) is None
9966
True
9967
9968
Nor does it contain a nonempty induced subgraph isomorphic to `P_6`::
9969
9970
sage: g.subgraph_search(graphs.PathGraph(6), induced=True) is None
9971
True
9972
9973
The empty graph is a subgraph of every graph::
9974
9975
sage: g.subgraph_search(graphs.EmptyGraph())
9976
Graph on 0 vertices
9977
sage: g.subgraph_search(graphs.EmptyGraph(), induced=True)
9978
Graph on 0 vertices
9979
9980
The subgraph may just have edges missing::
9981
9982
sage: k3=graphs.CompleteGraph(3); p3=graphs.PathGraph(3)
9983
sage: k3.relabel(list('abc'))
9984
sage: s=k3.subgraph_search(p3)
9985
sage: s.edges(labels=False)
9986
[('a', 'b'), ('b', 'c')]
9987
9988
Of course, `P_3` is not an induced subgraph of `K_3`, though::
9989
9990
sage: k3=graphs.CompleteGraph(3); p3=graphs.PathGraph(3)
9991
sage: k3.relabel(list('abc'))
9992
sage: k3.subgraph_search(p3, induced=True) is None
9993
True
9994
9995
"""
9996
from sage.graphs.generic_graph_pyx import SubgraphSearch
9997
from sage.graphs.graph_generators import GraphGenerators
9998
if G.order() == 0:
9999
return GraphGenerators().EmptyGraph()
10000
10001
S = SubgraphSearch(self, G, induced = induced)
10002
10003
for g in S:
10004
if induced:
10005
return self.subgraph(g)
10006
else:
10007
Gcopy=G.copy()
10008
Gcopy.relabel(g)
10009
return self.subgraph(vertices=Gcopy.vertices(), edges=Gcopy.edges())
10010
10011
return None
10012
10013
def subgraph_search_count(self, G, induced=False):
10014
r"""
10015
Returns the number of labelled occurences of ``G`` in ``self``.
10016
10017
INPUT:
10018
10019
- ``G`` -- the graph whose copies we are looking for in
10020
``self``.
10021
10022
- ``induced`` -- boolean (default: ``False``). Whether or not
10023
to count induced copies of ``G`` in ``self``.
10024
10025
ALGORITHM:
10026
10027
Brute-force search.
10028
10029
.. NOTE::
10030
10031
This method also works on digraphs.
10032
10033
.. SEEALSO::
10034
10035
- :meth:`~GenericGraph.subgraph_search` -- finds an subgraph
10036
isomorphic to `H` inside of a graph `G`
10037
10038
- :meth:`~GenericGraph.subgraph_search_iterator` -- Iterate on the
10039
copies of a graph `H` inside of a graph `G`
10040
10041
EXAMPLES:
10042
10043
Counting the number of paths `P_5` in a PetersenGraph::
10044
10045
sage: g = graphs.PetersenGraph()
10046
sage: g.subgraph_search_count(graphs.PathGraph(5))
10047
240
10048
10049
Requiring these subgraphs be induced::
10050
10051
sage: g.subgraph_search_count(graphs.PathGraph(5), induced = True)
10052
120
10053
10054
If we define the graph `T_k` (the transitive tournament on `k`
10055
vertices) as the graph on `\{0, ..., k-1\}` such that `ij \in
10056
T_k` iif `i<j`, how many directed triangles can be found in
10057
`T_5` ? The answer is of course `0` ::
10058
10059
sage: T5 = DiGraph()
10060
sage: T5.add_edges([(i,j) for i in xrange(5) for j in xrange(i+1, 5)])
10061
sage: T5.subgraph_search_count(digraphs.Circuit(3))
10062
0
10063
10064
If we count instead the number of `T_3` in `T_5`, we expect
10065
the answer to be `{5 \choose 3}`::
10066
10067
sage: T3 = T5.subgraph([0,1,2])
10068
sage: T5.subgraph_search_count(T3)
10069
10
10070
sage: binomial(5,3)
10071
10
10072
10073
The empty graph is a subgraph of every graph::
10074
10075
sage: g.subgraph_search_count(graphs.EmptyGraph())
10076
1
10077
"""
10078
from sage.graphs.generic_graph_pyx import SubgraphSearch
10079
10080
if G.order() == 0:
10081
return 1
10082
10083
if self.order() == 0:
10084
return 0
10085
10086
S = SubgraphSearch(self, G, induced = induced)
10087
10088
return S.cardinality()
10089
10090
def subgraph_search_iterator(self, G, induced=False):
10091
r"""
10092
Returns an iterator over the labelled copies of ``G`` in ``self``.
10093
10094
INPUT:
10095
10096
- ``G`` -- the graph whose copies we are looking for in
10097
``self``.
10098
10099
- ``induced`` -- boolean (default: ``False``). Whether or not
10100
to iterate over the induced copies of ``G`` in ``self``.
10101
10102
ALGORITHM:
10103
10104
Brute-force search.
10105
10106
OUTPUT:
10107
10108
Iterator over the labelled copies of ``G`` in ``self``, as
10109
*lists*. For each value `(v_1, v_2, ..., v_k)` returned,
10110
the first vertex of `G` is associated with `v_1`, the
10111
second with `v_2`, etc ...
10112
10113
.. NOTE::
10114
10115
This method also works on digraphs.
10116
10117
.. SEEALSO::
10118
10119
- :meth:`~GenericGraph.subgraph_search` -- finds an subgraph
10120
isomorphic to `H` inside of a graph `G`
10121
10122
- :meth:`~GenericGraph.subgraph_search_count` -- Counts the number
10123
of copies of a graph `H` inside of a graph `G`
10124
10125
EXAMPLE:
10126
10127
Iterating through all the labelled `P_3` of `P_5`::
10128
10129
sage: g = graphs.PathGraph(5)
10130
sage: for p in g.subgraph_search_iterator(graphs.PathGraph(3)):
10131
... print p
10132
[0, 1, 2]
10133
[1, 2, 3]
10134
[2, 1, 0]
10135
[2, 3, 4]
10136
[3, 2, 1]
10137
[4, 3, 2]
10138
"""
10139
10140
if G.order() == 0:
10141
from sage.graphs.graph_generators import GraphGenerators
10142
return [GraphGenerators().EmptyGraph()]
10143
10144
elif self.order() == 0:
10145
return []
10146
10147
else:
10148
from sage.graphs.generic_graph_pyx import SubgraphSearch
10149
return SubgraphSearch(self, G, induced = induced)
10150
10151
def random_subgraph(self, p, inplace=False):
10152
"""
10153
Return a random subgraph that contains each vertex with prob. p.
10154
10155
EXAMPLES::
10156
10157
sage: P = graphs.PetersenGraph()
10158
sage: P.random_subgraph(.25)
10159
Subgraph of (Petersen graph): Graph on 4 vertices
10160
"""
10161
vertices = []
10162
p = float(p)
10163
for v in self:
10164
if random() < p:
10165
vertices.append(v)
10166
return self.subgraph(vertices=vertices, inplace=inplace)
10167
10168
def is_chordal(self, certificate = False, algorithm = "B"):
10169
r"""
10170
Tests whether the given graph is chordal.
10171
10172
A Graph `G` is said to be chordal if it contains no induced hole (a
10173
cycle of length at least 4).
10174
10175
Alternatively, chordality can be defined using a Perfect Elimination
10176
Order :
10177
10178
A Perfect Elimination Order of a graph `G` is an ordering `v_1,...,v_n`
10179
of its vertex set such that for all `i`, the neighbors of `v_i` whose
10180
index is greater that `i` induce a complete subgraph in `G`. Hence, the
10181
graph `G` can be totally erased by successively removing vertices whose
10182
neighborhood is a clique (also called *simplicial* vertices)
10183
[Fulkerson65]_.
10184
10185
(It can be seen that if `G` contains an induced hole, then it can not
10186
have a perfect elimination order. Indeed, if we write `h_1,...,h_k` the
10187
`k` vertices of such a hole, then the first of those vertices to be
10188
removed would have two non-adjacent neighbors in the graph.)
10189
10190
A Graph is then chordal if and only if it has a Perfect Elimination
10191
Order.
10192
10193
INPUT:
10194
10195
- ``certificate`` (boolean) -- Whether to return a certificate.
10196
10197
* If ``certificate = False`` (default), returns ``True`` or
10198
``False`` accordingly.
10199
10200
* If ``certificate = True``, returns :
10201
10202
* ``(True, peo)`` when the graph is chordal, where ``peo`` is a
10203
perfect elimination order of its vertices.
10204
10205
* ``(False, Hole)`` when the graph is not chordal, where
10206
``Hole`` (a ``Graph`` object) is an induced subgraph of
10207
``self`` isomorphic to a hole.
10208
10209
- ``algorithm`` -- Two algorithms are available for this method (see
10210
next section), which can be selected by setting ``algorithm = "A"`` or
10211
``algorithm = "B"`` (default). While they will agree on whether the
10212
given graph is chordal, they can not be expected to return the same
10213
certificates.
10214
10215
ALGORITHM:
10216
10217
This algorithm works through computing a Lex BFS on the graph, then
10218
checking whether the order is a Perfect Elimination Order by computing
10219
for each vertex `v` the subgraph induces by its non-deleted neighbors,
10220
then testing whether this graph is complete.
10221
10222
This problem can be solved in `O(m)` [Rose75]_ ( where `m` is the number
10223
of edges in the graph ) but this implementation is not linear because of
10224
the complexity of Lex BFS.
10225
10226
.. NOTE::
10227
10228
Because of a past bug (#11735, #11961), the first implementation
10229
(algorithm A) of this method sometimes returned as certificates
10230
subgraphs which were **not** holes. Since then, this bug has been
10231
fixed and the values are now double-checked before being returned,
10232
so that the algorithm only returns correct values or raises an
10233
exception. In the case where an exception is raised, the user is
10234
advised to switch to the other algorithm. And to **please** report
10235
the bug :-)
10236
10237
EXAMPLES:
10238
10239
The lexicographic product of a Path and a Complete Graph
10240
is chordal ::
10241
10242
sage: g = graphs.PathGraph(5).lexicographic_product(graphs.CompleteGraph(3))
10243
sage: g.is_chordal()
10244
True
10245
10246
The same goes with the product of a random lobster
10247
( which is a tree ) and a Complete Graph ::
10248
10249
sage: g = graphs.RandomLobster(10,.5,.5).lexicographic_product(graphs.CompleteGraph(3))
10250
sage: g.is_chordal()
10251
True
10252
10253
The disjoint union of chordal graphs is still chordal::
10254
10255
sage: (2*g).is_chordal()
10256
True
10257
10258
Let us check the certificate given by Sage is indeed a perfect elimintion order::
10259
10260
sage: (_, peo) = g.is_chordal(certificate = True)
10261
sage: for v in peo:
10262
... if not g.subgraph(g.neighbors(v)).is_clique():
10263
... print "This should never happen !"
10264
... g.delete_vertex(v)
10265
sage: print "Everything is fine !"
10266
Everything is fine !
10267
10268
Of course, the Petersen Graph is not chordal as it has girth 5 ::
10269
10270
sage: g = graphs.PetersenGraph()
10271
sage: g.girth()
10272
5
10273
sage: g.is_chordal()
10274
False
10275
10276
We can even obtain such a cycle as a certificate ::
10277
10278
sage: (_, hole) = g.is_chordal(certificate = True)
10279
sage: hole
10280
Subgraph of (Petersen graph): Graph on 5 vertices
10281
sage: hole.is_isomorphic(graphs.CycleGraph(5))
10282
True
10283
10284
TESTS:
10285
10286
This shouldn't fail (trac 10899)::
10287
10288
sage: Graph(1).is_chordal()
10289
True
10290
sage: for g in graphs(5):
10291
... try:
10292
... forget = g.is_chordal()
10293
... except:
10294
... print("Oh no.")
10295
10296
REFERENCES:
10297
10298
.. [Rose75] Rose, D.J. and Tarjan, R.E.,
10299
Algorithmic aspects of vertex elimination,
10300
Proceedings of seventh annual ACM symposium on Theory of computing
10301
Page 254, ACM 1975
10302
10303
.. [Fulkerson65] Fulkerson, D.R. and Gross, OA
10304
Incidence matrices and interval graphs
10305
Pacific J. Math 1965
10306
Vol. 15, number 3, pages 835--855
10307
10308
TESTS:
10309
10310
Trac Ticket #11735::
10311
10312
sage: g = Graph({3:[2,1,4],2:[1],4:[1],5:[2,1,4]})
10313
sage: _, g1 = g.is_chordal(certificate=True); g1.is_chordal()
10314
False
10315
sage: g1.is_isomorphic(graphs.CycleGraph(g1.order()))
10316
True
10317
"""
10318
10319
# If the graph is not connected, we are computing the result on each component
10320
if not self.is_connected():
10321
10322
# If the user wants a certificate, we had no choice but to
10323
# collect the perfect elimination orders... But we return
10324
# a hole immediately if we find any !
10325
if certificate:
10326
peo = []
10327
for gg in self.connected_components_subgraphs():
10328
10329
b, certif = gg.is_chordal(certificate = True)
10330
if not b:
10331
return False, certif
10332
else:
10333
peo.extend(certif)
10334
10335
return True, peo
10336
10337
# One line if no certificate is requested
10338
else:
10339
return all( gg.is_chordal() for gg in self.connected_components_subgraphs() )
10340
10341
hole = None
10342
g = self.copy()
10343
10344
# Algorithms
10345
#
10346
# They find the perfect elimination ordering or produce a hole
10347
10348
if algorithm == "A":
10349
10350
peo,t_peo = self.lex_BFS(tree=True)
10351
peo.reverse()
10352
10353
# Iteratively removing vertices and checking everything is fine.
10354
for v in peo:
10355
10356
if t_peo.out_degree(v) == 0:
10357
g.delete_vertex(v)
10358
continue
10359
10360
x = t_peo.neighbor_out_iterator(v).next()
10361
S = self.neighbors(x)+[x]
10362
10363
if not frozenset(g.neighbors(v)).issubset(S):
10364
10365
# Do we need to return a hole ?
10366
if certificate:
10367
10368
# In this case, let us take two nonadjacent neighbors of v
10369
# In this case, let us take two nonadjacent neighbors of
10370
# v. In order to do so, we pick a vertex y which is a
10371
# neighbor of v but is not adjacent to x, which we know
10372
# exists by the test written two lines above.
10373
10374
for y in g.neighbors(v):
10375
if y not in S:
10376
break
10377
10378
g.delete_vertices([vv for vv in g.neighbors(v) if vv != y and vv != x])
10379
g.delete_vertex(v)
10380
10381
# Our hole is v + (a shortest path between x and y not
10382
# containing v or any of its neighbors).
10383
10384
hole = self.subgraph([v] + g.shortest_path(x,y))
10385
10386
# End of the algorithm
10387
break
10388
else:
10389
return False
10390
10391
g.delete_vertex(v)
10392
10393
elif algorithm == "B":
10394
10395
peo,t_peo = self.lex_BFS(reverse=True, tree=True)
10396
10397
# Remembering the (closed) neighborhoods of each vertex
10398
neighbors_subsets = dict([(v,self.neighbors(v)+[v]) for v in g])
10399
pos_in_peo = dict(zip(peo, range(self.order())))
10400
10401
# Iteratively removing vertices and checking everything is fine.
10402
for v in reversed(peo):
10403
10404
if (t_peo.out_degree(v)>0 and
10405
not frozenset([v1 for v1 in g.neighbors(v) if pos_in_peo[v1] > pos_in_peo[v]]).issubset(
10406
neighbors_subsets[t_peo.neighbor_out_iterator(v).next()])):
10407
10408
# Do we need to return a hole ?
10409
if certificate:
10410
10411
# In this case, let us take two nonadjacent neighbors of
10412
# v. In order to do so, we pick a vertex y which is a
10413
# neighbor of v but is not adjacent to x, which we know
10414
# exists by the test written two lines above.
10415
max_tup = (-1, 0)
10416
nb1 = [u for u in g.neighbors(v) if pos_in_peo[u] > pos_in_peo[v]]
10417
for xi in nb1:
10418
for yi in nb1:
10419
if not yi in neighbors_subsets[xi]:
10420
new_tup = (pos_in_peo[xi], pos_in_peo[yi])
10421
if max_tup < new_tup:
10422
max_tup = new_tup
10423
x, y = xi, yi
10424
10425
# Our hole is v + (a shortest path between x and y not
10426
# containing v or any of its neighbors).
10427
10428
#g.delete_vertices([vv for vv in g.vertices() if pos_in_peo[vv] < pos_in_peo[v]])
10429
10430
g.delete_vertices([vv for vv in g.neighbors(v) if vv != y and vv != x])
10431
g.delete_vertex(v)
10432
10433
hole = self.subgraph([v] + g.shortest_path(x,y))
10434
10435
# End of the algorithm
10436
break
10437
else:
10438
return False
10439
10440
10441
# Returning values
10442
# ----------------
10443
10444
# 1- The graph is not chordal
10445
10446
if not hole is None:
10447
# There was a bug there once, so it's better to check the
10448
# answer is valid, especally when it is so cheap ;-)
10449
10450
if hole.order() <= 3 or not hole.is_regular(k=2):
10451
raise Exception("The graph is not chordal, and something went wrong in the computation of the certificate. Please report this bug, providing the graph if possible !")
10452
10453
return (False, hole)
10454
10455
10456
# 2- The graph is chordal
10457
if certificate:
10458
return True, peo
10459
10460
else:
10461
return True
10462
10463
def is_interval(self, certificate = False):
10464
r"""
10465
Check whether self is an interval graph
10466
10467
INPUT:
10468
10469
- ``certificate`` (boolean) -- The function returns ``True``
10470
or ``False`` according to the graph, when ``certificate =
10471
False`` (default). When ``certificate = True`` and the graph
10472
is an interval graph, a dictionary whose keys are the
10473
vertices and values are pairs of integers are returned
10474
instead of ``True``. They correspond to an embedding of the
10475
interval graph, each vertex being represented by an interval
10476
going from the first of the two values to the second.
10477
10478
ALGORITHM:
10479
10480
Through the use of PQ-Trees
10481
10482
AUTHOR:
10483
10484
Nathann Cohen (implementation)
10485
10486
EXAMPLES:
10487
10488
A Petersen Graph is not chordal, nor car it be an interval
10489
graph ::
10490
10491
sage: g = graphs.PetersenGraph()
10492
sage: g.is_interval()
10493
False
10494
10495
Though we can build intervals from the corresponding random
10496
generator::
10497
10498
sage: g = graphs.RandomInterval(20)
10499
sage: g.is_interval()
10500
True
10501
10502
This method can also return, given an interval graph, a
10503
possible embedding (we can actually compute all of them
10504
through the PQ-Tree structures)::
10505
10506
sage: g = Graph(':S__@_@A_@AB_@AC_@ACD_@ACDE_ACDEF_ACDEFG_ACDEGH_ACDEGHI_ACDEGHIJ_ACDEGIJK_ACDEGIJKL_ACDEGIJKLMaCEGIJKNaCEGIJKNaCGIJKNPaCIP')
10507
sage: d = g.is_interval(certificate = True)
10508
sage: print d # not tested
10509
{0: (0, 20), 1: (1, 9), 2: (2, 36), 3: (3, 5), 4: (4, 38), 5: (6, 21), 6: (7, 27), 7: (8, 12), 8: (10, 29), 9: (11, 16), 10: (13, 39), 11: (14, 31), 12: (15, 32), 13: (17, 23), 14: (18, 22), 15: (19, 33), 16: (24, 25), 17: (26, 35), 18: (28, 30), 19: (34, 37)}
10510
10511
From this embedding, we can clearly build an interval graph
10512
isomorphic to the previous one::
10513
10514
sage: g2 = graphs.IntervalGraph(d.values())
10515
sage: g2.is_isomorphic(g)
10516
True
10517
10518
.. SEEALSO::
10519
10520
- :mod:`Interval Graph Recognition <sage.graphs.pq_trees>`.
10521
10522
- :meth:`PQ <sage.graphs.pq_trees.PQ>`
10523
-- Implementation of PQ-Trees.
10524
10525
"""
10526
10527
# An interval graph first is a chordal graph. Without this,
10528
# there is no telling how we should find its maximal cliques,
10529
# by the way :-)
10530
10531
if not self.is_chordal():
10532
return False
10533
10534
# First, we need to gather the list of maximal cliques, which
10535
# is easy as the graph is chordal
10536
10537
cliques = []
10538
10539
# As we will be deleting vertices ...
10540
g = self.copy()
10541
10542
for cc in self.connected_components_subgraphs():
10543
10544
# We pick a perfect elimination order for every connected
10545
# component. We will then iteratively take the last vertex
10546
# in the order (a simplicial vertex) and consider the
10547
# clique it forms with its neighbors. If we do not have an
10548
# inclusion-wise larger clique in our list, we add it !
10549
10550
peo = cc.lex_BFS()
10551
10552
10553
10554
while peo:
10555
v = peo.pop()
10556
clique = frozenset( [v] + cc.neighbors(v))
10557
cc.delete_vertex(v)
10558
10559
if not any([clique.issubset(c) for c in cliques]):
10560
cliques.append(clique)
10561
10562
from sage.graphs.pq_trees import reorder_sets
10563
10564
try:
10565
ordered_sets = reorder_sets(cliques)
10566
if not certificate:
10567
return True
10568
10569
except ValueError:
10570
return False
10571
10572
# We are now listing the maximal cliques in the given order,
10573
# and keeping track of the vertices appearing/disappearing
10574
10575
current = set([])
10576
beg = {}
10577
end = {}
10578
10579
i = 0
10580
10581
ordered_sets.append([])
10582
for S in map(set,ordered_sets):
10583
for v in current-S:
10584
end[v] = i
10585
i = i + 1
10586
10587
for v in S-current:
10588
beg[v] = i
10589
i = i + 1
10590
10591
current = S
10592
10593
10594
return dict([(v, (beg[v], end[v])) for v in self])
10595
10596
10597
def is_gallai_tree(self):
10598
r"""
10599
Returns whether the current graph is a Gallai tree.
10600
10601
A graph is a Gallai tree if and only if it is
10602
connected and its `2`-connected components are all
10603
isomorphic to complete graphs or odd cycles.
10604
10605
A connected graph is not degree-choosable if and
10606
only if it is a Gallai tree [erdos1978choos]_.
10607
10608
REFERENCES:
10609
10610
.. [erdos1978choos] Erdos, P. and Rubin, A.L. and Taylor, H.
10611
Proc. West Coast Conf. on Combinatorics
10612
Graph Theory and Computing, Congressus Numerantium
10613
vol 26, pages 125--157, 1979
10614
10615
EXAMPLES:
10616
10617
A complete graph is, or course, a Gallai Tree::
10618
10619
sage: g = graphs.CompleteGraph(15)
10620
sage: g.is_gallai_tree()
10621
True
10622
10623
The Petersen Graph is not::
10624
10625
sage: g = graphs.PetersenGraph()
10626
sage: g.is_gallai_tree()
10627
False
10628
10629
A Graph built from vertex-disjoint complete graphs
10630
linked by one edge to a special vertex `-1` is a
10631
''star-shaped'' Gallai tree ::
10632
10633
sage: g = 8 * graphs.CompleteGraph(6)
10634
sage: g.add_edges([(-1,c[0]) for c in g.connected_components()])
10635
sage: g.is_gallai_tree()
10636
True
10637
"""
10638
10639
if not self.is_connected():
10640
return False
10641
10642
for c in self.blocks_and_cut_vertices()[0]:
10643
gg = self.subgraph(c)
10644
# is it an odd cycle ? a complete graph ?
10645
if not ( (len(c)%2 == 1 and gg.size() == len(c)+1) or gg.is_clique() ):
10646
return False
10647
10648
return True
10649
10650
def is_clique(self, vertices=None, directed_clique=False):
10651
"""
10652
Tests whether a set of vertices is a clique
10653
10654
A clique is a set of vertices such that there is an edge between any two
10655
vertices.
10656
10657
INPUT:
10658
10659
- ``vertices`` - Vertices can be a single vertex or an
10660
iterable container of vertices, e.g. a list, set, graph, file or
10661
numeric array. If not passed, defaults to the entire graph.
10662
10663
- ``directed_clique`` - (default False) If set to
10664
False, only consider the underlying undirected graph. If set to
10665
True and the graph is directed, only return True if all possible
10666
edges in _both_ directions exist.
10667
10668
EXAMPLES::
10669
10670
sage: g = graphs.CompleteGraph(4)
10671
sage: g.is_clique([1,2,3])
10672
True
10673
sage: g.is_clique()
10674
True
10675
sage: h = graphs.CycleGraph(4)
10676
sage: h.is_clique([1,2])
10677
True
10678
sage: h.is_clique([1,2,3])
10679
False
10680
sage: h.is_clique()
10681
False
10682
sage: i = graphs.CompleteGraph(4).to_directed()
10683
sage: i.delete_edge([0,1])
10684
sage: i.is_clique()
10685
True
10686
sage: i.is_clique(directed_clique=True)
10687
False
10688
"""
10689
if directed_clique and self._directed:
10690
subgraph=self.subgraph(vertices)
10691
subgraph.allow_loops(False)
10692
subgraph.allow_multiple_edges(False)
10693
n=subgraph.order()
10694
return subgraph.size()==n*(n-1)
10695
else:
10696
if vertices is None:
10697
subgraph = self
10698
else:
10699
subgraph=self.subgraph(vertices)
10700
10701
if self._directed:
10702
subgraph = subgraph.to_simple()
10703
10704
n=subgraph.order()
10705
return subgraph.size()==n*(n-1)/2
10706
10707
def is_independent_set(self, vertices=None):
10708
"""
10709
Returns True if the set ``vertices`` is an independent
10710
set, False if not. An independent set is a set of vertices such
10711
that there is no edge between any two vertices.
10712
10713
INPUT:
10714
10715
- ``vertices`` - Vertices can be a single vertex or an
10716
iterable container of vertices, e.g. a list, set, graph, file or
10717
numeric array. If not passed, defaults to the entire graph.
10718
10719
EXAMPLES::
10720
10721
sage: graphs.CycleGraph(4).is_independent_set([1,3])
10722
True
10723
sage: graphs.CycleGraph(4).is_independent_set([1,2,3])
10724
False
10725
"""
10726
return self.subgraph(vertices).to_simple().size()==0
10727
10728
def is_subgraph(self, other):
10729
"""
10730
Tests whether self is an induced subgraph of other.
10731
10732
.. WARNING::
10733
10734
Please note that this method does not check whether ``self``
10735
contains a subgraph *isomorphic* to ``other``, but only if it
10736
directly contains it as a subgraph ! This means that this method
10737
returns ``True`` only if the vertices of ``other`` are also vertices
10738
of ``self``, and that the edges of ``other`` are equal to the edges
10739
of ``self`` between the vertices contained in ``other``.
10740
10741
.. SEEALSO::
10742
10743
If you are interested in the (possibly induced) subgraphs of
10744
``self`` to ``other``, you are looking for the following methods:
10745
10746
- :meth:`~GenericGraph.subgraph_search` -- finds an subgraph
10747
isomorphic to `H` inside of a graph `G`
10748
10749
- :meth:`~GenericGraph.subgraph_search_count` -- Counts the number
10750
of such copies.
10751
10752
- :meth:`~GenericGraph.subgraph_search_iterator` -- Iterate over all
10753
the copies of `H` contained in `G`.
10754
10755
EXAMPLES::
10756
10757
sage: P = graphs.PetersenGraph()
10758
sage: G = P.subgraph(range(6))
10759
sage: G.is_subgraph(P)
10760
True
10761
"""
10762
self_verts = self.vertices()
10763
for v in self_verts:
10764
if v not in other:
10765
return False
10766
return other.subgraph(self_verts) == self
10767
10768
### Cluster
10769
10770
def cluster_triangles(self, nbunch=None, with_labels=False):
10771
r"""
10772
Returns the number of triangles for nbunch of vertices as a
10773
dictionary keyed by vertex.
10774
10775
The clustering coefficient of a graph is the fraction of possible
10776
triangles that are triangles, `c_i = triangles_i /
10777
(k_i\*(k_i-1)/2)` where `k_i` is the degree of vertex `i`, [1]. A
10778
coefficient for the whole graph is the average of the `c_i`.
10779
Transitivity is the fraction of all possible triangles which are
10780
triangles, T = 3\*triangles/triads, [HSSNX]_.
10781
10782
INPUT:
10783
10784
- ``nbunch`` - The vertices to inspect. If
10785
nbunch=None, returns data for all vertices in the graph
10786
10787
REFERENCE:
10788
10789
.. [HSSNX] Aric Hagberg, Dan Schult and Pieter Swart. NetworkX
10790
documentation. [Online] Available:
10791
https://networkx.lanl.gov/reference/networkx/
10792
10793
EXAMPLES::
10794
10795
sage: (graphs.FruchtGraph()).cluster_triangles().values()
10796
[1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0]
10797
sage: (graphs.FruchtGraph()).cluster_triangles()
10798
{0: 1, 1: 1, 2: 0, 3: 1, 4: 1, 5: 1, 6: 1, 7: 1, 8: 0, 9: 1, 10: 1, 11: 0}
10799
sage: (graphs.FruchtGraph()).cluster_triangles(nbunch=[0,1,2])
10800
{0: 1, 1: 1, 2: 0}
10801
"""
10802
import networkx
10803
return networkx.triangles(self.networkx_graph(copy=False), nbunch)
10804
10805
def clustering_average(self):
10806
r"""
10807
Returns the average clustering coefficient.
10808
10809
The clustering coefficient of a graph is the fraction of possible
10810
triangles that are triangles, `c_i = triangles_i /
10811
(k_i\*(k_i-1)/2)` where `k_i` is the degree of vertex `i`, [1]. A
10812
coefficient for the whole graph is the average of the `c_i`.
10813
Transitivity is the fraction of all possible triangles which are
10814
triangles, T = 3\*triangles/triads, [1].
10815
10816
REFERENCE:
10817
10818
- [1] Aric Hagberg, Dan Schult and Pieter Swart. NetworkX
10819
documentation. [Online] Available:
10820
https://networkx.lanl.gov/reference/networkx/
10821
10822
EXAMPLES::
10823
10824
sage: (graphs.FruchtGraph()).clustering_average()
10825
0.25
10826
"""
10827
import networkx
10828
return networkx.average_clustering(self.networkx_graph(copy=False))
10829
10830
def clustering_coeff(self, nbunch=None, weights=False):
10831
r"""
10832
Returns the clustering coefficient for each vertex in nbunch as a
10833
dictionary keyed by vertex.
10834
10835
The clustering coefficient of a graph is the fraction of possible
10836
triangles that are triangles, `c_i = triangles_i /
10837
(k_i\*(k_i-1)/2)` where `k_i` is the degree of vertex `i`, [1]. A
10838
coefficient for the whole graph is the average of the `c_i`.
10839
Transitivity is the fraction of all possible triangles which are
10840
triangles, T = 3\*triangles/triads, [1].
10841
10842
INPUT:
10843
10844
- ``nbunch`` - the vertices to inspect (default
10845
None returns data on all vertices in graph)
10846
10847
- ``weights`` - default is False. If both
10848
with_labels and weights are True, then returns a clustering
10849
coefficient dict and a dict of weights based on degree. Weights are
10850
the fraction of connected triples in the graph that include the
10851
keyed vertex.
10852
10853
10854
REFERENCE:
10855
10856
- [1] Aric Hagberg, Dan Schult and Pieter Swart. NetworkX
10857
documentation. [Online] Available:
10858
https://networkx.lanl.gov/reference/networkx/
10859
10860
EXAMPLES::
10861
10862
sage: (graphs.FruchtGraph()).clustering_coeff().values()
10863
[0.3333333333333333, 0.3333333333333333, 0.0, 0.3333333333333333, 0.3333333333333333, 0.3333333333333333, 0.3333333333333333, 0.3333333333333333, 0.0, 0.3333333333333333, 0.3333333333333333, 0.0]
10864
sage: (graphs.FruchtGraph()).clustering_coeff()
10865
{0: 0.3333333333333333, 1: 0.3333333333333333, 2: 0.0, 3: 0.3333333333333333, 4: 0.3333333333333333, 5: 0.3333333333333333, 6: 0.3333333333333333, 7: 0.3333333333333333, 8: 0.0, 9: 0.3333333333333333, 10: 0.3333333333333333, 11: 0.0}
10866
sage: (graphs.FruchtGraph()).clustering_coeff(weights=True)
10867
({0: 0.3333333333333333, 1: 0.3333333333333333, 2: 0.0, 3: 0.3333333333333333, 4: 0.3333333333333333, 5: 0.3333333333333333, 6: 0.3333333333333333, 7: 0.3333333333333333, 8: 0.0, 9: 0.3333333333333333, 10: 0.3333333333333333, 11: 0.0}, {0: 0.08333333333333333, 1: 0.08333333333333333, 2: 0.08333333333333333, 3: 0.08333333333333333, 4: 0.08333333333333333, 5: 0.08333333333333333, 6: 0.08333333333333333, 7: 0.08333333333333333, 8: 0.08333333333333333, 9: 0.08333333333333333, 10: 0.08333333333333333, 11: 0.08333333333333333})
10868
sage: (graphs.FruchtGraph()).clustering_coeff(nbunch=[0,1,2])
10869
{0: 0.3333333333333333, 1: 0.3333333333333333, 2: 0.0}
10870
sage: (graphs.FruchtGraph()).clustering_coeff(nbunch=[0,1,2],weights=True)
10871
({0: 0.3333333333333333, 1: 0.3333333333333333, 2: 0.0}, {0: 0.3333333333333333, 1: 0.3333333333333333, 2: 0.3333333333333333})
10872
"""
10873
import networkx
10874
return networkx.clustering(self.networkx_graph(copy=False), nbunch, weights)
10875
10876
def cluster_transitivity(self):
10877
r"""
10878
Returns the transitivity (fraction of transitive triangles) of the
10879
graph.
10880
10881
The clustering coefficient of a graph is the fraction of possible
10882
triangles that are triangles, `c_i = triangles_i /
10883
(k_i\*(k_i-1)/2)` where `k_i` is the degree of vertex `i`, [1]. A
10884
coefficient for the whole graph is the average of the `c_i`.
10885
Transitivity is the fraction of all possible triangles which are
10886
triangles, T = 3\*triangles/triads, [1].
10887
10888
REFERENCE:
10889
10890
.. [1] Aric Hagberg, Dan Schult and Pieter Swart. NetworkX
10891
documentation. [Online] Available:
10892
https://networkx.lanl.gov/reference/networkx/
10893
10894
EXAMPLES::
10895
10896
sage: (graphs.FruchtGraph()).cluster_transitivity()
10897
0.25
10898
"""
10899
import networkx
10900
return networkx.transitivity(self.networkx_graph(copy=False))
10901
10902
### Cores
10903
10904
def cores(self, k = None, with_labels=False):
10905
"""
10906
Returns the core number for each vertex in an ordered list.
10907
10908
10909
**DEFINITIONS**
10910
10911
* *K-cores* in graph theory were introduced by Seidman in 1983 and by
10912
Bollobas in 1984 as a method of (destructively) simplifying graph
10913
topology to aid in analysis and visualization. They have been more
10914
recently defined as the following by Batagelj et al:
10915
10916
*Given a graph `G` with vertices set `V` and edges set `E`, the
10917
`k`-core of `G` is the graph obtained from `G` by recursively removing
10918
the vertices with degree less than `k`, for as long as there are any.*
10919
10920
This operation can be useful to filter or to study some properties of
10921
the graphs. For instance, when you compute the 2-core of graph G, you
10922
are cutting all the vertices which are in a tree part of graph. (A
10923
tree is a graph with no loops). [WPkcore]_
10924
10925
[PSW1996]_ defines a `k`-core of `G` as the largest subgraph (it is
10926
unique) of `G` with minimum degree at least `k`.
10927
10928
* Core number of a vertex
10929
10930
The core number of a vertex `v` is the largest integer `k` such that
10931
`v` belongs to the `k`-core of `G`.
10932
10933
* Degeneracy
10934
10935
The *degeneracy* of a graph `G`, usually denoted `\delta^*(G)`, is the
10936
smallest integer `k` such that the graph `G` can be reduced to the
10937
empty graph by iteratively removing vertices of degree `\leq
10938
k`. Equivalently, `\delta^*(G)=k` if `k` is the smallest integer such
10939
that the `k`-core of `G` is empty.
10940
10941
**IMPLEMENTATION**
10942
10943
This implementation is based on the NetworkX implementation of
10944
the algorithm described in [BZ]_.
10945
10946
**INPUT**
10947
10948
- ``k`` (integer)
10949
10950
* If ``k = None`` (default), returns the core number for each vertex.
10951
10952
* If ``k`` is an integer, returns a pair ``(ordering, core)``, where
10953
``core`` is the list of vertices in the `k`-core of ``self``, and
10954
``ordering`` is an elimination order for the other vertices such
10955
that each vertex is of degree strictly less than `k` when it is to
10956
be eliminated from the graph.
10957
10958
- ``with_labels`` (boolean)
10959
10960
* When set to ``False``, and ``k = None``, the method returns a list
10961
whose `i` th element is the core number of the `i` th vertex. When
10962
set to ``True``, the method returns a dictionary whose keys are
10963
vertices, and whose values are the corresponding core numbers.
10964
10965
By default, ``with_labels = False``.
10966
10967
REFERENCE:
10968
10969
.. [WPkcore] K-core. Wikipedia. (2007). [Online] Available:
10970
http://en.wikipedia.org/wiki/K-core
10971
10972
.. [PSW1996] Boris Pittel, Joel Spencer and Nicholas Wormald. Sudden
10973
Emergence of a Giant k-Core in a Random
10974
Graph. (1996). J. Combinatorial Theory. Ser B 67. pages
10975
111-151. [Online] Available:
10976
http://cs.nyu.edu/cs/faculty/spencer/papers/k-core.pdf
10977
10978
.. [BZ] Vladimir Batagelj and Matjaz Zaversnik. An `O(m)`
10979
Algorithm for Cores Decomposition of
10980
Networks. arXiv:cs/0310049v1. [Online] Available:
10981
http://arxiv.org/abs/cs/0310049
10982
10983
EXAMPLES::
10984
10985
sage: (graphs.FruchtGraph()).cores()
10986
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]
10987
sage: (graphs.FruchtGraph()).cores(with_labels=True)
10988
{0: 3, 1: 3, 2: 3, 3: 3, 4: 3, 5: 3, 6: 3, 7: 3, 8: 3, 9: 3, 10: 3, 11: 3}
10989
sage: a=random_matrix(ZZ,20,x=2,sparse=True, density=.1)
10990
sage: b=DiGraph(20)
10991
sage: b.add_edges(a.nonzero_positions())
10992
sage: cores=b.cores(with_labels=True); cores
10993
{0: 3, 1: 3, 2: 3, 3: 3, 4: 2, 5: 2, 6: 3, 7: 1, 8: 3, 9: 3, 10: 3, 11: 3, 12: 3, 13: 3, 14: 2, 15: 3, 16: 3, 17: 3, 18: 3, 19: 3}
10994
sage: [v for v,c in cores.items() if c>=2] # the vertices in the 2-core
10995
[0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
10996
10997
Checking the 2-core of a random lobster is indeed the empty set::
10998
10999
sage: g = graphs.RandomLobster(20,.5,.5)
11000
sage: ordering, core = g.cores(2)
11001
sage: len(core) == 0
11002
True
11003
"""
11004
# compute the degrees of each vertex
11005
degrees=self.degree(labels=True)
11006
11007
# sort vertices by degree. Store in a list and keep track of
11008
# where a specific degree starts (effectively, the list is
11009
# sorted by bins).
11010
verts= sorted( degrees.keys(), key=lambda x: degrees[x])
11011
bin_boundaries=[0]
11012
curr_degree=0
11013
for i,v in enumerate(verts):
11014
if degrees[v]>curr_degree:
11015
bin_boundaries.extend([i]*(degrees[v]-curr_degree))
11016
curr_degree=degrees[v]
11017
vert_pos = dict((v,pos) for pos,v in enumerate(verts))
11018
# Set up initial guesses for core and lists of neighbors.
11019
core= degrees
11020
nbrs=dict((v,set(self.neighbors(v))) for v in self)
11021
# form vertex core building up from smallest
11022
for v in verts:
11023
11024
# If all the vertices have a degree larger than k, we can
11025
# return our answer if k != None
11026
if k is not None and core[v] >= k:
11027
return verts[:vert_pos[v]], verts[vert_pos[v]:]
11028
11029
for u in nbrs[v]:
11030
if core[u] > core[v]:
11031
nbrs[u].remove(v)
11032
11033
# cleverly move u to the end of the next smallest
11034
# bin (i.e., subtract one from the degree of u).
11035
# We do this by swapping u with the first vertex
11036
# in the bin that contains u, then incrementing
11037
# the bin boundary for the bin that contains u.
11038
pos=vert_pos[u]
11039
bin_start=bin_boundaries[core[u]]
11040
vert_pos[u]=bin_start
11041
vert_pos[verts[bin_start]]=pos
11042
verts[bin_start],verts[pos]=verts[pos],verts[bin_start]
11043
bin_boundaries[core[u]]+=1
11044
core[u] -= 1
11045
11046
if k is not None:
11047
return verts, []
11048
11049
if with_labels:
11050
return core
11051
else:
11052
return core.values()
11053
11054
### Distance
11055
11056
def distance(self, u, v, by_weight=False):
11057
"""
11058
Returns the (directed) distance from u to v in the (di)graph, i.e.
11059
the length of the shortest path from u to v.
11060
11061
INPUT:
11062
11063
- ``by_weight`` - if False, uses a breadth first
11064
search. If True, takes edge weightings into account, using
11065
Dijkstra's algorithm.
11066
11067
EXAMPLES::
11068
11069
sage: G = graphs.CycleGraph(9)
11070
sage: G.distance(0,1)
11071
1
11072
sage: G.distance(0,4)
11073
4
11074
sage: G.distance(0,5)
11075
4
11076
sage: G = Graph( {0:[], 1:[]} )
11077
sage: G.distance(0,1)
11078
+Infinity
11079
sage: G = Graph( { 0: {1: 1}, 1: {2: 1}, 2: {3: 1}, 3: {4: 2}, 4: {0: 2} }, sparse = True)
11080
sage: G.plot(edge_labels=True).show() # long time
11081
sage: G.distance(0, 3)
11082
2
11083
sage: G.distance(0, 3, by_weight=True)
11084
3
11085
11086
"""
11087
return self.shortest_path_length(u, v, by_weight = by_weight)
11088
11089
def distance_all_pairs(self, algorithm = "auto"):
11090
r"""
11091
Returns the distances between all pairs of vertices.
11092
11093
INPUT:
11094
11095
- ``"algorithm"`` (string) -- two algorithms are available
11096
11097
* ``algorithm = "BFS"`` in which case the distances are computed
11098
through `n` different breadth-first-search.
11099
11100
* ``algorithm = "Floyd-Warshall"``, in which case the
11101
Floyd-Warshall algorithm is used.
11102
11103
* ``algorithm = "auto"``, in which case the Floyd-Warshall
11104
algorithm is used for graphs on less than 20 vertices, and BFS
11105
otherwise.
11106
11107
The default is ``algorithm = "BFS"``.
11108
11109
OUTPUT:
11110
11111
A doubly indexed dictionary
11112
11113
.. NOTE::
11114
11115
There is a Cython version of this method that is usually
11116
much faster for large graphs, as most of the time is
11117
actually spent building the final double
11118
dictionary. Everything on the subject is to be found in the
11119
:mod:`~sage.graphs.distances_all_pairs` module.
11120
11121
EXAMPLE:
11122
11123
The Petersen Graph::
11124
11125
sage: g = graphs.PetersenGraph()
11126
sage: print g.distance_all_pairs()
11127
{0: {0: 0, 1: 1, 2: 2, 3: 2, 4: 1, 5: 1, 6: 2, 7: 2, 8: 2, 9: 2}, 1: {0: 1, 1: 0, 2: 1, 3: 2, 4: 2, 5: 2, 6: 1, 7: 2, 8: 2, 9: 2}, 2: {0: 2, 1: 1, 2: 0, 3: 1, 4: 2, 5: 2, 6: 2, 7: 1, 8: 2, 9: 2}, 3: {0: 2, 1: 2, 2: 1, 3: 0, 4: 1, 5: 2, 6: 2, 7: 2, 8: 1, 9: 2}, 4: {0: 1, 1: 2, 2: 2, 3: 1, 4: 0, 5: 2, 6: 2, 7: 2, 8: 2, 9: 1}, 5: {0: 1, 1: 2, 2: 2, 3: 2, 4: 2, 5: 0, 6: 2, 7: 1, 8: 1, 9: 2}, 6: {0: 2, 1: 1, 2: 2, 3: 2, 4: 2, 5: 2, 6: 0, 7: 2, 8: 1, 9: 1}, 7: {0: 2, 1: 2, 2: 1, 3: 2, 4: 2, 5: 1, 6: 2, 7: 0, 8: 2, 9: 1}, 8: {0: 2, 1: 2, 2: 2, 3: 1, 4: 2, 5: 1, 6: 1, 7: 2, 8: 0, 9: 2}, 9: {0: 2, 1: 2, 2: 2, 3: 2, 4: 1, 5: 2, 6: 1, 7: 1, 8: 2, 9: 0}}
11128
11129
Testing on Random Graphs::
11130
11131
sage: g = graphs.RandomGNP(20,.3)
11132
sage: distances = g.distance_all_pairs()
11133
sage: all([g.distance(0,v) == distances[0][v] for v in g])
11134
True
11135
"""
11136
if algorithm == "auto":
11137
if self.order() <= 20:
11138
algorithm = "Floyd-Warshall"
11139
else:
11140
algorithm = "BFS"
11141
11142
if algorithm == "BFS":
11143
from sage.graphs.distances_all_pairs import distances_all_pairs
11144
return distances_all_pairs(self)
11145
11146
elif algorithm == "Floyd-Warshall":
11147
from sage.graphs.distances_all_pairs import floyd_warshall
11148
return floyd_warshall(self,paths = False, distances = True)
11149
11150
else:
11151
raise ValueError("The algorithm keyword can be equal to either \"BFS\" or \"Floyd-Warshall\" or \"auto\"")
11152
11153
11154
def distances_distribution(self):
11155
r"""
11156
Returns the distances distribution of the (di)graph in a dictionary.
11157
11158
This method *ignores all edge labels*, so that the distance considered
11159
is the topological distance.
11160
11161
OUTPUT:
11162
11163
A dictionary ``d`` such that the number of pairs of vertices at
11164
distance ``k`` (if any) is equal to `d[k] \cdot |V(G)| \cdot
11165
(|V(G)|-1)`.
11166
11167
.. NOTE::
11168
11169
We consider that two vertices that do not belong to the same
11170
connected component are at infinite distance, and we do not take the
11171
trivial pairs of vertices `(v, v)` at distance `0` into account.
11172
Empty (di)graphs and (di)graphs of order 1 have no paths and so we
11173
return the empty dictionary ``{}``.
11174
11175
EXAMPLES:
11176
11177
An empty Graph::
11178
11179
sage: g = Graph()
11180
sage: g.distances_distribution()
11181
{}
11182
11183
A Graph of order 1::
11184
11185
sage: g = Graph()
11186
sage: g.add_vertex(1)
11187
sage: g.distances_distribution()
11188
{}
11189
11190
A Graph of order 2 without edge::
11191
11192
sage: g = Graph()
11193
sage: g.add_vertices([1,2])
11194
sage: g.distances_distribution()
11195
{+Infinity: 1}
11196
11197
The Petersen Graph::
11198
11199
sage: g = graphs.PetersenGraph()
11200
sage: g.distances_distribution()
11201
{1: 1/3, 2: 2/3}
11202
11203
A graph with multiple disconnected components::
11204
11205
sage: g = graphs.PetersenGraph()
11206
sage: g.add_edge('good','wine')
11207
sage: g.distances_distribution()
11208
{1: 8/33, 2: 5/11, +Infinity: 10/33}
11209
11210
The de Bruijn digraph dB(2,3)::
11211
11212
sage: D = digraphs.DeBruijn(2,3)
11213
sage: D.distances_distribution()
11214
{1: 1/4, 2: 11/28, 3: 5/14}
11215
"""
11216
from sage.graphs.distances_all_pairs import distances_distribution
11217
return distances_distribution(self)
11218
11219
11220
def eccentricity(self, v=None, dist_dict=None, with_labels=False):
11221
"""
11222
Return the eccentricity of vertex (or vertices) v.
11223
11224
The eccentricity of a vertex is the maximum distance to any other
11225
vertex.
11226
11227
INPUT:
11228
11229
11230
- ``v`` - either a single vertex or a list of
11231
vertices. If it is not specified, then it is taken to be all
11232
vertices.
11233
11234
- ``dist_dict`` - optional, a dict of dicts of
11235
distance.
11236
11237
- ``with_labels`` - Whether to return a list or a
11238
dict.
11239
11240
11241
EXAMPLES::
11242
11243
sage: G = graphs.KrackhardtKiteGraph()
11244
sage: G.eccentricity()
11245
[4, 4, 4, 4, 4, 3, 3, 2, 3, 4]
11246
sage: G.vertices()
11247
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
11248
sage: G.eccentricity(7)
11249
2
11250
sage: G.eccentricity([7,8,9])
11251
[3, 4, 2]
11252
sage: G.eccentricity([7,8,9], with_labels=True) == {8: 3, 9: 4, 7: 2}
11253
True
11254
sage: G = Graph( { 0 : [], 1 : [], 2 : [1] } )
11255
sage: G.eccentricity()
11256
[+Infinity, +Infinity, +Infinity]
11257
sage: G = Graph({0:[]})
11258
sage: G.eccentricity(with_labels=True)
11259
{0: 0}
11260
sage: G = Graph({0:[], 1:[]})
11261
sage: G.eccentricity(with_labels=True)
11262
{0: +Infinity, 1: +Infinity}
11263
"""
11264
if v is None:
11265
if dist_dict is None:
11266
from sage.graphs.distances_all_pairs import eccentricity
11267
11268
if with_labels:
11269
return dict(zip(self.vertices(), eccentricity(self)))
11270
else:
11271
return eccentricity(self)
11272
11273
v = self.vertices()
11274
elif not isinstance(v, list):
11275
v = [v]
11276
e = {}
11277
infinite = False
11278
for u in v:
11279
if dist_dict is None:
11280
length = self.shortest_path_lengths(u)
11281
else:
11282
length = dist_dict[u]
11283
if len(length) != self.num_verts():
11284
infinite = True
11285
break
11286
e[u] = max(length.values())
11287
if infinite:
11288
from sage.rings.infinity import Infinity
11289
for u in v:
11290
e[u] = Infinity
11291
if with_labels:
11292
return e
11293
else:
11294
if len(e)==1: return e.values()[0] # return single value
11295
return e.values()
11296
11297
def radius(self):
11298
"""
11299
Returns the radius of the (di)graph.
11300
11301
The radius is defined to be the minimum eccentricity of any vertex,
11302
where the eccentricity is the maximum distance to any other
11303
vertex.
11304
11305
EXAMPLES: The more symmetric a graph is, the smaller (diameter -
11306
radius) is.
11307
11308
::
11309
11310
sage: G = graphs.BarbellGraph(9, 3)
11311
sage: G.radius()
11312
3
11313
sage: G.diameter()
11314
6
11315
11316
::
11317
11318
sage: G = graphs.OctahedralGraph()
11319
sage: G.radius()
11320
2
11321
sage: G.diameter()
11322
2
11323
11324
TEST::
11325
11326
sage: g = Graph()
11327
sage: g.radius()
11328
Traceback (most recent call last):
11329
...
11330
ValueError: This method has no meaning on empty graphs.
11331
"""
11332
if self.order() == 0:
11333
raise ValueError("This method has no meaning on empty graphs.")
11334
11335
return min(self.eccentricity())
11336
11337
def center(self):
11338
"""
11339
Returns the set of vertices in the center, i.e. whose eccentricity
11340
is equal to the radius of the (di)graph.
11341
11342
In other words, the center is the set of vertices achieving the
11343
minimum eccentricity.
11344
11345
EXAMPLES::
11346
11347
sage: G = graphs.DiamondGraph()
11348
sage: G.center()
11349
[1, 2]
11350
sage: P = graphs.PetersenGraph()
11351
sage: P.subgraph(P.center()) == P
11352
True
11353
sage: S = graphs.StarGraph(19)
11354
sage: S.center()
11355
[0]
11356
sage: G = Graph()
11357
sage: G.center()
11358
[]
11359
sage: G.add_vertex()
11360
0
11361
sage: G.center()
11362
[0]
11363
"""
11364
e = self.eccentricity(with_labels=True)
11365
try:
11366
r = min(e.values())
11367
except:
11368
return []
11369
return [v for v in e if e[v]==r]
11370
11371
def diameter(self):
11372
"""
11373
Returns the largest distance between any two vertices. Returns
11374
Infinity if the (di)graph is not connected.
11375
11376
EXAMPLES::
11377
11378
sage: G = graphs.PetersenGraph()
11379
sage: G.diameter()
11380
2
11381
sage: G = Graph( { 0 : [], 1 : [], 2 : [1] } )
11382
sage: G.diameter()
11383
+Infinity
11384
11385
Although max( ) is usually defined as -Infinity, since the diameter
11386
will never be negative, we define it to be zero::
11387
11388
sage: G = graphs.EmptyGraph()
11389
sage: G.diameter()
11390
0
11391
11392
"""
11393
11394
if self.order() > 0:
11395
return max(self.eccentricity())
11396
else:
11397
return 0
11398
11399
def distance_graph(self, dist):
11400
r"""
11401
Returns the graph on the same vertex set as
11402
the original graph but vertices are adjacent
11403
in the returned graph if and only if they are
11404
at specified distances in the original graph.
11405
11406
INPUT:
11407
11408
- ``dist`` is a nonnegative integer or
11409
a list of nonnegative integers.
11410
``Infinity`` may be used here to describe
11411
vertex pairs in separate components.
11412
11413
OUTPUT:
11414
11415
The returned value is an undirected graph. The
11416
vertex set is identical to the calling graph, but edges
11417
of the returned graph join vertices whose distance in
11418
the calling graph are present in the input ``dist``.
11419
Loops will only be present if distance 0 is included. If
11420
the original graph has a position dictionary specifying
11421
locations of vertices for plotting, then this information
11422
is copied over to the distance graph. In some instances
11423
this layout may not be the best, and might even be confusing
11424
when edges run on top of each other due to symmetries
11425
chosen for the layout.
11426
11427
EXAMPLES::
11428
11429
sage: G = graphs.CompleteGraph(3)
11430
sage: H = G.cartesian_product(graphs.CompleteGraph(2))
11431
sage: K = H.distance_graph(2)
11432
sage: K.am()
11433
[0 0 0 1 0 1]
11434
[0 0 1 0 1 0]
11435
[0 1 0 0 0 1]
11436
[1 0 0 0 1 0]
11437
[0 1 0 1 0 0]
11438
[1 0 1 0 0 0]
11439
11440
To obtain the graph where vertices are adjacent if their
11441
distance apart is ``d`` or less use a ``range()`` command
11442
to create the input, using ``d+1`` as the input to ``range``.
11443
Notice that this will include distance 0 and hence place a loop
11444
at each vertex. To avoid this, use ``range(1,d+1)``. ::
11445
11446
sage: G = graphs.OddGraph(4)
11447
sage: d = G.diameter()
11448
sage: n = G.num_verts()
11449
sage: H = G.distance_graph(range(d+1))
11450
sage: H.is_isomorphic(graphs.CompleteGraph(n))
11451
False
11452
sage: H = G.distance_graph(range(1,d+1))
11453
sage: H.is_isomorphic(graphs.CompleteGraph(n))
11454
True
11455
11456
A complete collection of distance graphs will have
11457
adjacency matrices that sum to the matrix of all ones. ::
11458
11459
sage: P = graphs.PathGraph(20)
11460
sage: all_ones = sum([P.distance_graph(i).am() for i in range(20)])
11461
sage: all_ones == matrix(ZZ, 20, 20, [1]*400)
11462
True
11463
11464
Four-bit strings differing in one bit is the same as
11465
four-bit strings differing in three bits. ::
11466
11467
sage: G = graphs.CubeGraph(4)
11468
sage: H = G.distance_graph(3)
11469
sage: G.is_isomorphic(H)
11470
True
11471
11472
The graph of eight-bit strings, adjacent if different
11473
in an odd number of bits. ::
11474
11475
sage: G = graphs.CubeGraph(8) # long time
11476
sage: H = G.distance_graph([1,3,5,7]) # long time
11477
sage: degrees = [0]*sum([binomial(8,j) for j in [1,3,5,7]]) # long time
11478
sage: degrees.append(2^8) # long time
11479
sage: degrees == H.degree_histogram() # long time
11480
True
11481
11482
An example of using ``Infinity`` as the distance in
11483
a graph that is not connected. ::
11484
11485
sage: G = graphs.CompleteGraph(3)
11486
sage: H = G.disjoint_union(graphs.CompleteGraph(2))
11487
sage: L = H.distance_graph(Infinity)
11488
sage: L.am()
11489
[0 0 0 1 1]
11490
[0 0 0 1 1]
11491
[0 0 0 1 1]
11492
[1 1 1 0 0]
11493
[1 1 1 0 0]
11494
11495
TESTS:
11496
11497
Empty input, or unachievable distances silently yield empty graphs. ::
11498
11499
sage: G = graphs.CompleteGraph(5)
11500
sage: G.distance_graph([]).num_edges()
11501
0
11502
sage: G = graphs.CompleteGraph(5)
11503
sage: G.distance_graph(23).num_edges()
11504
0
11505
11506
It is an error to provide a distance that is not an integer type. ::
11507
11508
sage: G = graphs.CompleteGraph(5)
11509
sage: G.distance_graph('junk')
11510
Traceback (most recent call last):
11511
...
11512
TypeError: unable to convert x (=junk) to an integer
11513
11514
It is an error to provide a negative distance. ::
11515
11516
sage: G = graphs.CompleteGraph(5)
11517
sage: G.distance_graph(-3)
11518
Traceback (most recent call last):
11519
...
11520
ValueError: Distance graph for a negative distance (d=-3) is not defined
11521
11522
AUTHOR:
11523
11524
Rob Beezer, 2009-11-25
11525
"""
11526
from sage.rings.infinity import Infinity
11527
from copy import copy
11528
# If input is not a list, make a list with this single object
11529
if not isinstance(dist, list):
11530
dist = [dist]
11531
# Create a list of positive integer (or infinite) distances
11532
distances = []
11533
for d in dist:
11534
if d == Infinity:
11535
distances.append(d)
11536
else:
11537
dint = ZZ(d)
11538
if dint < 0:
11539
raise ValueError('Distance graph for a negative distance (d=%d) is not defined' % dint)
11540
distances.append(dint)
11541
# Build a graph on the same vertex set, with loops for distance 0
11542
vertices = {}
11543
for v in self.vertex_iterator():
11544
vertices[v] = {}
11545
positions = copy(self.get_pos())
11546
if ZZ(0) in distances:
11547
looped = True
11548
else:
11549
looped = False
11550
from sage.graphs.all import Graph
11551
D = Graph(vertices, pos=positions, multiedges=False, loops=looped)
11552
if len(distances) == 1:
11553
dstring = "distance " + str(distances[0])
11554
else:
11555
dstring = "distances " + str(sorted(distances))
11556
D.name("Distance graph for %s in " % dstring + self.name())
11557
11558
# Create the appropriate edges
11559
d = self.distance_all_pairs()
11560
for u in self.vertex_iterator():
11561
for v in self.vertex_iterator():
11562
if d[u][v] in distances:
11563
D.add_edge(u,v)
11564
return D
11565
11566
def girth(self):
11567
"""
11568
Computes the girth of the graph. For directed graphs, computes the
11569
girth of the undirected graph.
11570
11571
The girth is the length of the shortest cycle in the graph. Graphs
11572
without cycles have infinite girth.
11573
11574
EXAMPLES::
11575
11576
sage: graphs.TetrahedralGraph().girth()
11577
3
11578
sage: graphs.CubeGraph(3).girth()
11579
4
11580
sage: graphs.PetersenGraph().girth()
11581
5
11582
sage: graphs.HeawoodGraph().girth()
11583
6
11584
sage: graphs.trees(9).next().girth()
11585
+Infinity
11586
11587
TESTS:
11588
11589
Prior to Trac #12243, the girth computation assumed
11590
vertices were integers (and failed). The example below
11591
tests the computation for graphs with vertices that are
11592
not integers. In this example the vertices are sets. ::
11593
11594
sage: G = graphs.OddGraph(3)
11595
sage: type(G.vertices()[0])
11596
<class 'sage.sets.set.Set_object_enumerated_with_category'>
11597
sage: G.girth()
11598
5
11599
11600
Ticket :trac:`12355`::
11601
11602
sage: H=Graph([(0, 1), (0, 3), (0, 4), (0, 5), (1, 2), (1, 3), (1, 4), (1, 6), (2, 5), (3, 4), (5, 6)])
11603
sage: H.girth()
11604
3
11605
11606
Girth < 3 (see :trac:`12355`)::
11607
11608
sage: g = graphs.PetersenGraph()
11609
sage: g.allow_multiple_edges(True)
11610
sage: g.allow_loops(True)
11611
sage: g.girth()
11612
5
11613
sage: g.add_edge(0,0)
11614
sage: g.girth()
11615
1
11616
sage: g.delete_edge(0,0)
11617
sage: g.add_edge(0,1)
11618
sage: g.girth()
11619
2
11620
sage: g.delete_edge(0,1)
11621
sage: g.girth()
11622
5
11623
sage: g = DiGraph(g)
11624
sage: g.girth()
11625
2
11626
"""
11627
11628
# Cases where girth <= 2
11629
if self.has_loops():
11630
return 1
11631
if self.is_directed():
11632
if any(self.has_edge(v,u) for u,v in self.edges(labels = False)):
11633
return 2
11634
else:
11635
if self.has_multiple_edges():
11636
return 2
11637
11638
n = self.num_verts()
11639
best = n+1
11640
seen = {}
11641
for w in self.vertex_iterator():
11642
seen[w] = None
11643
span = set([w])
11644
depth = 1
11645
thisList = set([w])
11646
while 2*depth <= best and 3 < best:
11647
nextList = set()
11648
for v in thisList:
11649
for u in self.neighbors(v):
11650
if u in seen: continue
11651
if not u in span:
11652
span.add(u)
11653
nextList.add(u)
11654
else:
11655
if u in thisList:
11656
best = depth*2-1
11657
thislList = set()
11658
break
11659
if u in nextList:
11660
best = depth*2
11661
if best == 2*depth-1:
11662
break
11663
thisList = nextList
11664
depth += 1
11665
if best == n+1:
11666
from sage.rings.infinity import Infinity
11667
return Infinity
11668
return best
11669
11670
11671
11672
def periphery(self):
11673
"""
11674
Returns the set of vertices in the periphery, i.e. whose
11675
eccentricity is equal to the diameter of the (di)graph.
11676
11677
In other words, the periphery is the set of vertices achieving the
11678
maximum eccentricity.
11679
11680
EXAMPLES::
11681
11682
sage: G = graphs.DiamondGraph()
11683
sage: G.periphery()
11684
[0, 3]
11685
sage: P = graphs.PetersenGraph()
11686
sage: P.subgraph(P.periphery()) == P
11687
True
11688
sage: S = graphs.StarGraph(19)
11689
sage: S.periphery()
11690
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
11691
sage: G = Graph()
11692
sage: G.periphery()
11693
[]
11694
sage: G.add_vertex()
11695
0
11696
sage: G.periphery()
11697
[0]
11698
"""
11699
e = self.eccentricity(with_labels=True)
11700
try:
11701
r = max(e.values())
11702
except:
11703
return []
11704
return [v for v in e if e[v]==r]
11705
11706
### Paths
11707
11708
def interior_paths(self, start, end):
11709
"""
11710
Returns an exhaustive list of paths (also lists) through only
11711
interior vertices from vertex start to vertex end in the
11712
(di)graph.
11713
11714
Note - start and end do not necessarily have to be boundary
11715
vertices.
11716
11717
INPUT:
11718
11719
11720
- ``start`` - the vertex of the graph to search for
11721
paths from
11722
11723
- ``end`` - the vertex of the graph to search for
11724
paths to
11725
11726
11727
EXAMPLES::
11728
11729
sage: eg1 = Graph({0:[1,2], 1:[4], 2:[3,4], 4:[5], 5:[6]})
11730
sage: sorted(eg1.all_paths(0,6))
11731
[[0, 1, 4, 5, 6], [0, 2, 4, 5, 6]]
11732
sage: eg2 = copy(eg1)
11733
sage: eg2.set_boundary([0,1,3])
11734
sage: sorted(eg2.interior_paths(0,6))
11735
[[0, 2, 4, 5, 6]]
11736
sage: sorted(eg2.all_paths(0,6))
11737
[[0, 1, 4, 5, 6], [0, 2, 4, 5, 6]]
11738
sage: eg3 = graphs.PetersenGraph()
11739
sage: eg3.set_boundary([0,1,2,3,4])
11740
sage: sorted(eg3.all_paths(1,4))
11741
[[1, 0, 4],
11742
[1, 0, 5, 7, 2, 3, 4],
11743
[1, 0, 5, 7, 2, 3, 8, 6, 9, 4],
11744
[1, 0, 5, 7, 9, 4],
11745
[1, 0, 5, 7, 9, 6, 8, 3, 4],
11746
[1, 0, 5, 8, 3, 2, 7, 9, 4],
11747
[1, 0, 5, 8, 3, 4],
11748
[1, 0, 5, 8, 6, 9, 4],
11749
[1, 0, 5, 8, 6, 9, 7, 2, 3, 4],
11750
[1, 2, 3, 4],
11751
[1, 2, 3, 8, 5, 0, 4],
11752
[1, 2, 3, 8, 5, 7, 9, 4],
11753
[1, 2, 3, 8, 6, 9, 4],
11754
[1, 2, 3, 8, 6, 9, 7, 5, 0, 4],
11755
[1, 2, 7, 5, 0, 4],
11756
[1, 2, 7, 5, 8, 3, 4],
11757
[1, 2, 7, 5, 8, 6, 9, 4],
11758
[1, 2, 7, 9, 4],
11759
[1, 2, 7, 9, 6, 8, 3, 4],
11760
[1, 2, 7, 9, 6, 8, 5, 0, 4],
11761
[1, 6, 8, 3, 2, 7, 5, 0, 4],
11762
[1, 6, 8, 3, 2, 7, 9, 4],
11763
[1, 6, 8, 3, 4],
11764
[1, 6, 8, 5, 0, 4],
11765
[1, 6, 8, 5, 7, 2, 3, 4],
11766
[1, 6, 8, 5, 7, 9, 4],
11767
[1, 6, 9, 4],
11768
[1, 6, 9, 7, 2, 3, 4],
11769
[1, 6, 9, 7, 2, 3, 8, 5, 0, 4],
11770
[1, 6, 9, 7, 5, 0, 4],
11771
[1, 6, 9, 7, 5, 8, 3, 4]]
11772
sage: sorted(eg3.interior_paths(1,4))
11773
[[1, 6, 8, 5, 7, 9, 4], [1, 6, 9, 4]]
11774
sage: dg = DiGraph({0:[1,3,4], 1:[3], 2:[0,3,4],4:[3]}, boundary=[4])
11775
sage: sorted(dg.all_paths(0,3))
11776
[[0, 1, 3], [0, 3], [0, 4, 3]]
11777
sage: sorted(dg.interior_paths(0,3))
11778
[[0, 1, 3], [0, 3]]
11779
sage: ug = dg.to_undirected()
11780
sage: sorted(ug.all_paths(0,3))
11781
[[0, 1, 3], [0, 2, 3], [0, 2, 4, 3], [0, 3], [0, 4, 2, 3], [0, 4, 3]]
11782
sage: sorted(ug.interior_paths(0,3))
11783
[[0, 1, 3], [0, 2, 3], [0, 3]]
11784
"""
11785
from copy import copy
11786
H = copy(self)
11787
for vertex in self.get_boundary():
11788
if (vertex != start and vertex != end):
11789
H.delete_edges(H.edges_incident(vertex))
11790
return H.all_paths(start, end)
11791
11792
def all_paths(self, start, end):
11793
"""
11794
Returns a list of all paths (also lists) between a pair of
11795
vertices (start, end) in the (di)graph. If ``start`` is the same
11796
vertex as ``end``, then ``[[start]]`` is returned -- a list
11797
containing the 1-vertex, 0-edge path "``start``".
11798
11799
EXAMPLES::
11800
11801
sage: eg1 = Graph({0:[1,2], 1:[4], 2:[3,4], 4:[5], 5:[6]})
11802
sage: eg1.all_paths(0,6)
11803
[[0, 1, 4, 5, 6], [0, 2, 4, 5, 6]]
11804
sage: eg2 = graphs.PetersenGraph()
11805
sage: sorted(eg2.all_paths(1,4))
11806
[[1, 0, 4],
11807
[1, 0, 5, 7, 2, 3, 4],
11808
[1, 0, 5, 7, 2, 3, 8, 6, 9, 4],
11809
[1, 0, 5, 7, 9, 4],
11810
[1, 0, 5, 7, 9, 6, 8, 3, 4],
11811
[1, 0, 5, 8, 3, 2, 7, 9, 4],
11812
[1, 0, 5, 8, 3, 4],
11813
[1, 0, 5, 8, 6, 9, 4],
11814
[1, 0, 5, 8, 6, 9, 7, 2, 3, 4],
11815
[1, 2, 3, 4],
11816
[1, 2, 3, 8, 5, 0, 4],
11817
[1, 2, 3, 8, 5, 7, 9, 4],
11818
[1, 2, 3, 8, 6, 9, 4],
11819
[1, 2, 3, 8, 6, 9, 7, 5, 0, 4],
11820
[1, 2, 7, 5, 0, 4],
11821
[1, 2, 7, 5, 8, 3, 4],
11822
[1, 2, 7, 5, 8, 6, 9, 4],
11823
[1, 2, 7, 9, 4],
11824
[1, 2, 7, 9, 6, 8, 3, 4],
11825
[1, 2, 7, 9, 6, 8, 5, 0, 4],
11826
[1, 6, 8, 3, 2, 7, 5, 0, 4],
11827
[1, 6, 8, 3, 2, 7, 9, 4],
11828
[1, 6, 8, 3, 4],
11829
[1, 6, 8, 5, 0, 4],
11830
[1, 6, 8, 5, 7, 2, 3, 4],
11831
[1, 6, 8, 5, 7, 9, 4],
11832
[1, 6, 9, 4],
11833
[1, 6, 9, 7, 2, 3, 4],
11834
[1, 6, 9, 7, 2, 3, 8, 5, 0, 4],
11835
[1, 6, 9, 7, 5, 0, 4],
11836
[1, 6, 9, 7, 5, 8, 3, 4]]
11837
sage: dg = DiGraph({0:[1,3], 1:[3], 2:[0,3]})
11838
sage: sorted(dg.all_paths(0,3))
11839
[[0, 1, 3], [0, 3]]
11840
sage: ug = dg.to_undirected()
11841
sage: sorted(ug.all_paths(0,3))
11842
[[0, 1, 3], [0, 2, 3], [0, 3]]
11843
11844
Starting and ending at the same vertex (see :trac:`13006`)::
11845
11846
sage: graphs.CompleteGraph(4).all_paths(2,2)
11847
[[2]]
11848
"""
11849
if self.is_directed():
11850
iterator=self.neighbor_out_iterator
11851
else:
11852
iterator=self.neighbor_iterator
11853
if start == end:
11854
return [[start]]
11855
all_paths = [] # list of
11856
act_path = [] # the current path
11857
act_path_iter = [] # the neighbor/successor-iterators of the current path
11858
done = False
11859
s=start
11860
while not done:
11861
if s==end: # if path completes, add to list
11862
all_paths.append(act_path+[s])
11863
else:
11864
if s not in act_path: # we want vertices just once in a path
11865
act_path.append(s) # extend current path
11866
act_path_iter.append(iterator(s)) # save the state of the neighbor/successor-iterator of the current vertex
11867
s=None
11868
while (s is None) and not done:
11869
try:
11870
s=act_path_iter[-1].next() # try to get the next neighbor/successor, ...
11871
except (StopIteration): # ... if there is none ...
11872
act_path.pop() # ... go one step back
11873
act_path_iter.pop()
11874
if len(act_path)==0: # there is no other vertex ...
11875
done = True # ... so we are done
11876
return all_paths
11877
11878
11879
def shortest_path(self, u, v, by_weight=False, bidirectional=True):
11880
"""
11881
Returns a list of vertices representing some shortest path from u
11882
to v: if there is no path from u to v, the list is empty.
11883
11884
INPUT:
11885
11886
11887
- ``by_weight`` - if False, uses a breadth first
11888
search. If True, takes edge weightings into account, using
11889
Dijkstra's algorithm.
11890
11891
- ``bidirectional`` - if True, the algorithm will
11892
expand vertices from u and v at the same time, making two spheres
11893
of half the usual radius. This generally doubles the speed
11894
(consider the total volume in each case).
11895
11896
11897
EXAMPLES::
11898
11899
sage: D = graphs.DodecahedralGraph()
11900
sage: D.shortest_path(4, 9)
11901
[4, 17, 16, 12, 13, 9]
11902
sage: D.shortest_path(5, 5)
11903
[5]
11904
sage: D.delete_edges(D.edges_incident(13))
11905
sage: D.shortest_path(13, 4)
11906
[]
11907
sage: G = Graph( { 0: [1], 1: [2], 2: [3], 3: [4], 4: [0] })
11908
sage: G.plot(edge_labels=True).show() # long time
11909
sage: G.shortest_path(0, 3)
11910
[0, 4, 3]
11911
sage: G = Graph( { 0: {1: 1}, 1: {2: 1}, 2: {3: 1}, 3: {4: 2}, 4: {0: 2} }, sparse = True)
11912
sage: G.shortest_path(0, 3, by_weight=True)
11913
[0, 1, 2, 3]
11914
""" # TODO- multiple edges??
11915
if u == v: # to avoid a NetworkX bug
11916
return [u]
11917
import networkx
11918
if by_weight:
11919
if bidirectional:
11920
try:
11921
L = self._backend.bidirectional_dijkstra(u,v)
11922
except AttributeError:
11923
try:
11924
L = networkx.bidirectional_dijkstra(self.networkx_graph(copy=False), u, v)[1]
11925
except:
11926
L = False
11927
else:
11928
L = networkx.dijkstra_path(self.networkx_graph(copy=False), u, v)
11929
else:
11930
if bidirectional:
11931
# If the graph is a C_graph, use shortest_path from its backend !
11932
try:
11933
L = self._backend.shortest_path(u,v)
11934
except AttributeError:
11935
L = networkx.shortest_path(self.networkx_graph(copy=False), u, v)
11936
else:
11937
try:
11938
L = networkx.single_source_shortest_path(self.networkx_graph(copy=False), u)[v]
11939
except:
11940
L = False
11941
if L:
11942
return L
11943
else:
11944
return []
11945
11946
def shortest_path_length(self, u, v, by_weight=False,
11947
bidirectional=True,
11948
weight_sum=None):
11949
"""
11950
Returns the minimal length of paths from u to v.
11951
11952
If there is no path from u to v, returns Infinity.
11953
11954
INPUT:
11955
11956
- ``by_weight`` - if False, uses a breadth first
11957
search. If True, takes edge weightings into account, using
11958
Dijkstra's algorithm.
11959
11960
- ``bidirectional`` - if True, the algorithm will
11961
expand vertices from u and v at the same time, making two spheres
11962
of half the usual radius. This generally doubles the speed
11963
(consider the total volume in each case).
11964
11965
- ``weight_sum`` - if False, returns the number of
11966
edges in the path. If True, returns the sum of the weights of these
11967
edges. Default behavior is to have the same value as by_weight.
11968
11969
EXAMPLES::
11970
11971
sage: D = graphs.DodecahedralGraph()
11972
sage: D.shortest_path_length(4, 9)
11973
5
11974
sage: D.shortest_path_length(5, 5)
11975
0
11976
sage: D.delete_edges(D.edges_incident(13))
11977
sage: D.shortest_path_length(13, 4)
11978
+Infinity
11979
sage: G = Graph( { 0: {1: 1}, 1: {2: 1}, 2: {3: 1}, 3: {4: 2}, 4: {0: 2} }, sparse = True)
11980
sage: G.plot(edge_labels=True).show() # long time
11981
sage: G.shortest_path_length(0, 3)
11982
2
11983
sage: G.shortest_path_length(0, 3, by_weight=True)
11984
3
11985
"""
11986
if weight_sum is None:
11987
weight_sum = by_weight
11988
path = self.shortest_path(u, v, by_weight, bidirectional)
11989
length = len(path) - 1
11990
if length == -1:
11991
from sage.rings.infinity import Infinity
11992
return Infinity
11993
if weight_sum:
11994
wt = 0
11995
for j in range(length):
11996
wt += self.edge_label(path[j], path[j+1])
11997
return wt
11998
else:
11999
return length
12000
12001
def shortest_paths(self, u, by_weight=False, cutoff=None):
12002
"""
12003
Returns a dictionary associating to each vertex v a shortest path from u
12004
to v, if it exists.
12005
12006
INPUT:
12007
12008
- ``by_weight`` - if False, uses a breadth first
12009
search. If True, uses Dijkstra's algorithm to find the shortest
12010
paths by weight.
12011
12012
- ``cutoff`` - integer depth to stop search.
12013
12014
(ignored if ``by_weight == True``)
12015
12016
EXAMPLES::
12017
12018
sage: D = graphs.DodecahedralGraph()
12019
sage: D.shortest_paths(0)
12020
{0: [0], 1: [0, 1], 2: [0, 1, 2], 3: [0, 19, 3], 4: [0, 19, 3, 4], 5: [0, 1, 2, 6, 5], 6: [0, 1, 2, 6], 7: [0, 1, 8, 7], 8: [0, 1, 8], 9: [0, 10, 9], 10: [0, 10], 11: [0, 10, 11], 12: [0, 10, 11, 12], 13: [0, 10, 9, 13], 14: [0, 1, 8, 7, 14], 15: [0, 19, 18, 17, 16, 15], 16: [0, 19, 18, 17, 16], 17: [0, 19, 18, 17], 18: [0, 19, 18], 19: [0, 19]}
12021
12022
All these paths are obviously induced graphs::
12023
12024
sage: all([D.subgraph(p).is_isomorphic(graphs.PathGraph(len(p)) )for p in D.shortest_paths(0).values()])
12025
True
12026
12027
::
12028
12029
sage: D.shortest_paths(0, cutoff=2)
12030
{0: [0], 1: [0, 1], 2: [0, 1, 2], 3: [0, 19, 3], 8: [0, 1, 8], 9: [0, 10, 9], 10: [0, 10], 11: [0, 10, 11], 18: [0, 19, 18], 19: [0, 19]}
12031
sage: G = Graph( { 0: {1: 1}, 1: {2: 1}, 2: {3: 1}, 3: {4: 2}, 4: {0: 2} }, sparse=True)
12032
sage: G.plot(edge_labels=True).show() # long time
12033
sage: G.shortest_paths(0, by_weight=True)
12034
{0: [0], 1: [0, 1], 2: [0, 1, 2], 3: [0, 1, 2, 3], 4: [0, 4]}
12035
"""
12036
12037
if by_weight:
12038
import networkx
12039
return networkx.single_source_dijkstra_path(self.networkx_graph(copy=False), u)
12040
else:
12041
try:
12042
return self._backend.shortest_path_all_vertices(u, cutoff)
12043
except AttributeError:
12044
return networkx.single_source_shortest_path(self.networkx_graph(copy=False), u, cutoff)
12045
12046
def shortest_path_lengths(self, u, by_weight=False, weight_sums=None):
12047
"""
12048
Returns a dictionary of shortest path lengths keyed by targets that
12049
are connected by a path from u.
12050
12051
INPUT:
12052
12053
12054
- ``by_weight`` - if False, uses a breadth first
12055
search. If True, takes edge weightings into account, using
12056
Dijkstra's algorithm.
12057
12058
EXAMPLES::
12059
12060
sage: D = graphs.DodecahedralGraph()
12061
sage: D.shortest_path_lengths(0)
12062
{0: 0, 1: 1, 2: 2, 3: 2, 4: 3, 5: 4, 6: 3, 7: 3, 8: 2, 9: 2, 10: 1, 11: 2, 12: 3, 13: 3, 14: 4, 15: 5, 16: 4, 17: 3, 18: 2, 19: 1}
12063
sage: G = Graph( { 0: {1: 1}, 1: {2: 1}, 2: {3: 1}, 3: {4: 2}, 4: {0: 2} }, sparse=True )
12064
sage: G.plot(edge_labels=True).show() # long time
12065
sage: G.shortest_path_lengths(0, by_weight=True)
12066
{0: 0, 1: 1, 2: 2, 3: 3, 4: 2}
12067
"""
12068
paths = self.shortest_paths(u, by_weight)
12069
if by_weight:
12070
weights = {}
12071
for v in paths:
12072
wt = 0
12073
path = paths[v]
12074
for j in range(len(path) - 1):
12075
wt += self.edge_label(path[j], path[j+1])
12076
weights[v] = wt
12077
return weights
12078
else:
12079
lengths = {}
12080
for v in paths:
12081
lengths[v] = len(paths[v]) - 1
12082
return lengths
12083
12084
def shortest_path_all_pairs(self, by_weight=False, default_weight=1, algorithm = "auto"):
12085
"""
12086
Computes a shortest path between each pair of vertices.
12087
12088
INPUT:
12089
12090
12091
- ``by_weight`` - Whether to use the labels defined over the edges as
12092
weights. If ``False`` (default), the distance between `u` and `v` is
12093
the minimum number of edges of a path from `u` to `v`.
12094
12095
- ``default_weight`` - (defaults to 1) The default weight to assign
12096
edges that don't have a weight (i.e., a label).
12097
12098
Implies ``by_weight == True``.
12099
12100
- ``algorithm`` -- four options :
12101
12102
* ``"BFS"`` -- the computation is done through a BFS
12103
centered on each vertex successively. Only implemented
12104
when ``default_weight = 1`` and ``by_weight = False``.
12105
12106
* ``"Floyd-Warshall-Cython"`` -- through the Cython implementation of
12107
the Floyd-Warshall algorithm.
12108
12109
* ``"Floyd-Warshall-Python"`` -- through the Python implementation of
12110
the Floyd-Warshall algorithm.
12111
12112
* ``"auto"`` -- use the fastest algorithm depending on the input
12113
(``"BFS"`` if possible, and ``"Floyd-Warshall-Python"`` otherwise)
12114
12115
This is the default value.
12116
12117
OUTPUT:
12118
12119
A tuple ``(dist, pred)``. They are both dicts of dicts. The first
12120
indicates the length ``dist[u][v]`` of the shortest weighted path
12121
from `u` to `v`. The second is a compact representation of all the
12122
paths- it indicates the predecessor ``pred[u][v]`` of `v` in the
12123
shortest path from `u` to `v`.
12124
12125
.. NOTE::
12126
12127
Three different implementations are actually available through this method :
12128
12129
* BFS (Cython)
12130
* Floyd-Warshall (Cython)
12131
* Floyd-Warshall (Python)
12132
12133
The BFS algorithm is the fastest of the three, then comes the Cython
12134
implementation of Floyd-Warshall, and last the Python
12135
implementation. The first two implementations, however, only compute
12136
distances based on the topological distance (each edge is of weight
12137
1, or equivalently the length of a path is its number of
12138
edges). Besides, they do not deal with graphs larger than 65536
12139
vertices (which already represents 16GB of ram).
12140
12141
.. NOTE::
12142
12143
There is a Cython version of this method that is usually
12144
much faster for large graphs, as most of the time is
12145
actually spent building the final double
12146
dictionary. Everything on the subject is to be found in the
12147
:mod:`~sage.graphs.distances_all_pairs` module.
12148
12149
EXAMPLES::
12150
12151
sage: G = Graph( { 0: {1: 1}, 1: {2: 1}, 2: {3: 1}, 3: {4: 2}, 4: {0: 2} }, sparse=True )
12152
sage: G.plot(edge_labels=True).show() # long time
12153
sage: dist, pred = G.shortest_path_all_pairs(by_weight = True)
12154
sage: dist
12155
{0: {0: 0, 1: 1, 2: 2, 3: 3, 4: 2}, 1: {0: 1, 1: 0, 2: 1, 3: 2, 4: 3}, 2: {0: 2, 1: 1, 2: 0, 3: 1, 4: 3}, 3: {0: 3, 1: 2, 2: 1, 3: 0, 4: 2}, 4: {0: 2, 1: 3, 2: 3, 3: 2, 4: 0}}
12156
sage: pred
12157
{0: {0: None, 1: 0, 2: 1, 3: 2, 4: 0}, 1: {0: 1, 1: None, 2: 1, 3: 2, 4: 0}, 2: {0: 1, 1: 2, 2: None, 3: 2, 4: 3}, 3: {0: 1, 1: 2, 2: 3, 3: None, 4: 3}, 4: {0: 4, 1: 0, 2: 3, 3: 4, 4: None}}
12158
sage: pred[0]
12159
{0: None, 1: 0, 2: 1, 3: 2, 4: 0}
12160
12161
So for example the shortest weighted path from `0` to `3` is obtained as
12162
follows. The predecessor of `3` is ``pred[0][3] == 2``, the predecessor
12163
of `2` is ``pred[0][2] == 1``, and the predecessor of `1` is
12164
``pred[0][1] == 0``.
12165
12166
::
12167
12168
sage: G = Graph( { 0: {1:None}, 1: {2:None}, 2: {3: 1}, 3: {4: 2}, 4: {0: 2} }, sparse=True )
12169
sage: G.shortest_path_all_pairs()
12170
({0: {0: 0, 1: 1, 2: 2, 3: 2, 4: 1},
12171
1: {0: 1, 1: 0, 2: 1, 3: 2, 4: 2},
12172
2: {0: 2, 1: 1, 2: 0, 3: 1, 4: 2},
12173
3: {0: 2, 1: 2, 2: 1, 3: 0, 4: 1},
12174
4: {0: 1, 1: 2, 2: 2, 3: 1, 4: 0}},
12175
{0: {0: None, 1: 0, 2: 1, 3: 4, 4: 0},
12176
1: {0: 1, 1: None, 2: 1, 3: 2, 4: 0},
12177
2: {0: 1, 1: 2, 2: None, 3: 2, 4: 3},
12178
3: {0: 4, 1: 2, 2: 3, 3: None, 4: 3},
12179
4: {0: 4, 1: 0, 2: 3, 3: 4, 4: None}})
12180
sage: G.shortest_path_all_pairs(by_weight = True)
12181
({0: {0: 0, 1: 1, 2: 2, 3: 3, 4: 2},
12182
1: {0: 1, 1: 0, 2: 1, 3: 2, 4: 3},
12183
2: {0: 2, 1: 1, 2: 0, 3: 1, 4: 3},
12184
3: {0: 3, 1: 2, 2: 1, 3: 0, 4: 2},
12185
4: {0: 2, 1: 3, 2: 3, 3: 2, 4: 0}},
12186
{0: {0: None, 1: 0, 2: 1, 3: 2, 4: 0},
12187
1: {0: 1, 1: None, 2: 1, 3: 2, 4: 0},
12188
2: {0: 1, 1: 2, 2: None, 3: 2, 4: 3},
12189
3: {0: 1, 1: 2, 2: 3, 3: None, 4: 3},
12190
4: {0: 4, 1: 0, 2: 3, 3: 4, 4: None}})
12191
sage: G.shortest_path_all_pairs(default_weight=200)
12192
({0: {0: 0, 1: 200, 2: 5, 3: 4, 4: 2},
12193
1: {0: 200, 1: 0, 2: 200, 3: 201, 4: 202},
12194
2: {0: 5, 1: 200, 2: 0, 3: 1, 4: 3},
12195
3: {0: 4, 1: 201, 2: 1, 3: 0, 4: 2},
12196
4: {0: 2, 1: 202, 2: 3, 3: 2, 4: 0}},
12197
{0: {0: None, 1: 0, 2: 3, 3: 4, 4: 0},
12198
1: {0: 1, 1: None, 2: 1, 3: 2, 4: 0},
12199
2: {0: 4, 1: 2, 2: None, 3: 2, 4: 3},
12200
3: {0: 4, 1: 2, 2: 3, 3: None, 4: 3},
12201
4: {0: 4, 1: 0, 2: 3, 3: 4, 4: None}})
12202
12203
Checking the distances are equal regardless of the algorithm used::
12204
12205
sage: g = graphs.Grid2dGraph(5,5)
12206
sage: d1, _ = g.shortest_path_all_pairs(algorithm="BFS")
12207
sage: d2, _ = g.shortest_path_all_pairs(algorithm="Floyd-Warshall-Cython")
12208
sage: d3, _ = g.shortest_path_all_pairs(algorithm="Floyd-Warshall-Python")
12209
sage: d1 == d2 == d3
12210
True
12211
12212
Checking a random path is valid ::
12213
12214
sage: dist, path = g.shortest_path_all_pairs(algorithm="BFS")
12215
sage: u,v = g.random_vertex(), g.random_vertex()
12216
sage: p = [v]
12217
sage: while p[0] != None:
12218
... p.insert(0,path[u][p[0]])
12219
sage: len(p) == dist[u][v] + 2
12220
True
12221
12222
TESTS:
12223
12224
Wrong name for ``algorithm``::
12225
12226
sage: g.shortest_path_all_pairs(algorithm="Bob")
12227
Traceback (most recent call last):
12228
...
12229
ValueError: The algorithm keyword can only be set to "auto", "BFS", "Floyd-Warshall-Python" or "Floyd-Warshall-Cython"
12230
"""
12231
if default_weight != 1:
12232
by_weight = True
12233
12234
if algorithm == "auto":
12235
if by_weight is False:
12236
algorithm = "BFS"
12237
else:
12238
algorithm = "Floyd-Warshall-Python"
12239
12240
if algorithm == "BFS":
12241
from sage.graphs.distances_all_pairs import distances_and_predecessors_all_pairs
12242
return distances_and_predecessors_all_pairs(self)
12243
12244
elif algorithm == "Floyd-Warshall-Cython":
12245
from sage.graphs.distances_all_pairs import floyd_warshall
12246
return floyd_warshall(self, distances = True)
12247
12248
elif algorithm != "Floyd-Warshall-Python":
12249
raise ValueError("The algorithm keyword can only be set to "+
12250
"\"auto\","+
12251
" \"BFS\", "+
12252
"\"Floyd-Warshall-Python\" or "+
12253
"\"Floyd-Warshall-Cython\"")
12254
12255
from sage.rings.infinity import Infinity
12256
dist = {}
12257
pred = {}
12258
verts = self.vertices()
12259
for u in verts:
12260
du = {}
12261
pu = {}
12262
for v in verts:
12263
if self.has_edge(u, v):
12264
if by_weight is False:
12265
du[v] = 1
12266
else:
12267
edge_label = self.edge_label(u, v)
12268
if edge_label is None or edge_label == {}:
12269
du[v] = default_weight
12270
else:
12271
du[v] = edge_label
12272
pu[v] = u
12273
else:
12274
du[v] = Infinity
12275
pu[v] = None
12276
du[u] = 0
12277
dist[u] = du
12278
pred[u] = pu
12279
12280
for w in verts:
12281
dw = dist[w]
12282
for u in verts:
12283
du = dist[u]
12284
for v in verts:
12285
if du[v] > du[w] + dw[v]:
12286
du[v] = du[w] + dw[v]
12287
pred[u][v] = pred[w][v]
12288
12289
return dist, pred
12290
12291
def wiener_index(self):
12292
r"""
12293
Returns the Wiener index of the graph.
12294
12295
The Wiener index of a graph `G` can be defined in two equivalent
12296
ways [KRG96]_ :
12297
12298
- `W(G) = \frac 1 2 \sum_{u,v\in G} d(u,v)` where `d(u,v)` denotes the
12299
distance between vertices `u` and `v`.
12300
12301
- Let `\Omega` be a set of `\frac {n(n-1)} 2` paths in `G` such that
12302
`\Omega` contains exactly one shortest `u-v` path for each set
12303
`\{u,v\}` of vertices in `G`. Besides, `\forall e\in E(G)`, let
12304
`\Omega(e)` denote the paths from `\Omega` containing `e`. We then
12305
have `W(G) = \sum_{e\in E(G)}|\Omega(e)|`.
12306
12307
EXAMPLE:
12308
12309
From [GYLL93b]_, cited in [KRG96]_::
12310
12311
sage: g=graphs.PathGraph(10)
12312
sage: w=lambda x: (x*(x*x -1)/6)
12313
sage: g.wiener_index()==w(10)
12314
True
12315
12316
REFERENCES:
12317
12318
.. [KRG96] S. Klavzar, A. Rajapakse, and I. Gutman. The Szeged and the
12319
Wiener index of graphs. *Applied Mathematics Letters*, 9(5):45--49,
12320
1996.
12321
12322
.. [GYLL93b] I. Gutman, Y.-N. Yeh, S.-L. Lee, and Y.-L. Luo. Some recent
12323
results in the theory of the Wiener number. *Indian Journal of
12324
Chemistry*, 32A:651--661, 1993.
12325
"""
12326
from sage.graphs.distances_all_pairs import wiener_index
12327
return wiener_index(self)
12328
12329
12330
def average_distance(self):
12331
r"""
12332
Returns the average distance between vertices of the graph.
12333
12334
Formally, for a graph `G` this value is equal to
12335
`\frac 1 {n(n-1)} \sum_{u,v\in G} d(u,v)` where `d(u,v)`
12336
denotes the distance between vertices `u` and `v` and `n`
12337
is the number of vertices in `G`.
12338
12339
EXAMPLE:
12340
12341
From [GYLL93]_::
12342
12343
sage: g=graphs.PathGraph(10)
12344
sage: w=lambda x: (x*(x*x -1)/6)/(x*(x-1)/2)
12345
sage: g.average_distance()==w(10)
12346
True
12347
12348
REFERENCE:
12349
12350
.. [GYLL93] I. Gutman, Y.-N. Yeh, S.-L. Lee, and Y.-L. Luo. Some recent
12351
results in the theory of the Wiener number. *Indian Journal of
12352
Chemistry*, 32A:651--661, 1993.
12353
12354
TEST::
12355
12356
sage: g = Graph()
12357
sage: g.average_distance()
12358
Traceback (most recent call last):
12359
...
12360
ValueError: The graph must have at least two vertices for this value to be defined
12361
"""
12362
if self.order() < 2:
12363
raise ValueError("The graph must have at least two vertices for this value to be defined")
12364
12365
return Integer(self.wiener_index())/Integer((self.order()*(self.order()-1))/2)
12366
12367
def szeged_index(self):
12368
r"""
12369
Returns the Szeged index of the graph.
12370
12371
For any `uv\in E(G)`, let
12372
`N_u(uv) = \{w\in G:d(u,w)<d(v,w)\}, n_u(uv)=|N_u(uv)|`
12373
12374
The Szeged index of a graph is then defined as [1]:
12375
`\sum_{uv \in E(G)}n_u(uv)\times n_v(uv)`
12376
12377
EXAMPLE:
12378
12379
True for any connected graph [1]::
12380
12381
sage: g=graphs.PetersenGraph()
12382
sage: g.wiener_index()<= g.szeged_index()
12383
True
12384
12385
True for all trees [1]::
12386
12387
sage: g=Graph()
12388
sage: g.add_edges(graphs.CubeGraph(5).min_spanning_tree())
12389
sage: g.wiener_index() == g.szeged_index()
12390
True
12391
12392
12393
REFERENCE:
12394
12395
[1] Klavzar S., Rajapakse A., Gutman I. (1996). The Szeged and the
12396
Wiener index of graphs. Applied Mathematics Letters, 9 (5), pp. 45-49.
12397
"""
12398
distances=self.distance_all_pairs()
12399
s=0
12400
for (u,v) in self.edges(labels=None):
12401
du=distances[u]
12402
dv=distances[v]
12403
n1=n2=0
12404
for w in self:
12405
if du[w] < dv[w]:
12406
n1+=1
12407
elif dv[w] < du[w]:
12408
n2+=1
12409
s+=(n1*n2)
12410
return s
12411
12412
### Searches
12413
12414
def breadth_first_search(self, start, ignore_direction=False,
12415
distance=None, neighbors=None):
12416
"""
12417
Returns an iterator over the vertices in a breadth-first ordering.
12418
12419
INPUT:
12420
12421
12422
- ``start`` - vertex or list of vertices from which to start
12423
the traversal
12424
12425
- ``ignore_direction`` - (default False) only applies to
12426
directed graphs. If True, searches across edges in either
12427
direction.
12428
12429
- ``distance`` - the maximum distance from the ``start`` nodes
12430
to traverse. The ``start`` nodes are distance zero from
12431
themselves.
12432
12433
- ``neighbors`` - a function giving the neighbors of a vertex.
12434
The function should take a vertex and return a list of
12435
vertices. For a graph, ``neighbors`` is by default the
12436
:meth:`.neighbors` function of the graph. For a digraph,
12437
the ``neighbors`` function defaults to the
12438
:meth:`.successors` function of the graph.
12439
12440
.. SEEALSO::
12441
12442
- :meth:`breadth_first_search <sage.graphs.base.c_graph.CGraphBackend.breadth_first_search>`
12443
-- breadth-first search for fast compiled graphs.
12444
12445
- :meth:`depth_first_search <sage.graphs.base.c_graph.CGraphBackend.depth_first_search>`
12446
-- depth-first search for fast compiled graphs.
12447
12448
- :meth:`depth_first_search` -- depth-first search for generic graphs.
12449
12450
EXAMPLES::
12451
12452
sage: G = Graph( { 0: [1], 1: [2], 2: [3], 3: [4], 4: [0]} )
12453
sage: list(G.breadth_first_search(0))
12454
[0, 1, 4, 2, 3]
12455
12456
By default, the edge direction of a digraph is respected, but this
12457
can be overridden by the ``ignore_direction`` parameter::
12458
12459
sage: D = DiGraph( { 0: [1,2,3], 1: [4,5], 2: [5], 3: [6], 5: [7], 6: [7], 7: [0]})
12460
sage: list(D.breadth_first_search(0))
12461
[0, 1, 2, 3, 4, 5, 6, 7]
12462
sage: list(D.breadth_first_search(0, ignore_direction=True))
12463
[0, 1, 2, 3, 7, 4, 5, 6]
12464
12465
You can specify a maximum distance in which to search. A
12466
distance of zero returns the ``start`` vertices::
12467
12468
sage: D = DiGraph( { 0: [1,2,3], 1: [4,5], 2: [5], 3: [6], 5: [7], 6: [7], 7: [0]})
12469
sage: list(D.breadth_first_search(0,distance=0))
12470
[0]
12471
sage: list(D.breadth_first_search(0,distance=1))
12472
[0, 1, 2, 3]
12473
12474
Multiple starting vertices can be specified in a list::
12475
12476
sage: D = DiGraph( { 0: [1,2,3], 1: [4,5], 2: [5], 3: [6], 5: [7], 6: [7], 7: [0]})
12477
sage: list(D.breadth_first_search([0]))
12478
[0, 1, 2, 3, 4, 5, 6, 7]
12479
sage: list(D.breadth_first_search([0,6]))
12480
[0, 6, 1, 2, 3, 7, 4, 5]
12481
sage: list(D.breadth_first_search([0,6],distance=0))
12482
[0, 6]
12483
sage: list(D.breadth_first_search([0,6],distance=1))
12484
[0, 6, 1, 2, 3, 7]
12485
sage: list(D.breadth_first_search(6,ignore_direction=True,distance=2))
12486
[6, 3, 7, 0, 5]
12487
12488
More generally, you can specify a ``neighbors`` function. For
12489
example, you can traverse the graph backwards by setting
12490
``neighbors`` to be the :meth:`.neighbors_in` function of the graph::
12491
12492
sage: D = DiGraph( { 0: [1,2,3], 1: [4,5], 2: [5], 3: [6], 5: [7], 6: [7], 7: [0]})
12493
sage: list(D.breadth_first_search(5,neighbors=D.neighbors_in, distance=2))
12494
[5, 1, 2, 0]
12495
sage: list(D.breadth_first_search(5,neighbors=D.neighbors_out, distance=2))
12496
[5, 7, 0]
12497
sage: list(D.breadth_first_search(5,neighbors=D.neighbors, distance=2))
12498
[5, 1, 2, 7, 0, 4, 6]
12499
12500
12501
TESTS::
12502
12503
sage: D = DiGraph({1:[0], 2:[0]})
12504
sage: list(D.breadth_first_search(0))
12505
[0]
12506
sage: list(D.breadth_first_search(0, ignore_direction=True))
12507
[0, 1, 2]
12508
12509
"""
12510
# Preferably use the Cython implementation
12511
if neighbors is None and not isinstance(start,list) and distance is None and hasattr(self._backend,"breadth_first_search"):
12512
for v in self._backend.breadth_first_search(start, ignore_direction = ignore_direction):
12513
yield v
12514
else:
12515
if neighbors is None:
12516
if not self._directed or ignore_direction:
12517
neighbors=self.neighbor_iterator
12518
else:
12519
neighbors=self.neighbor_out_iterator
12520
seen=set([])
12521
if isinstance(start, list):
12522
queue=[(v,0) for v in start]
12523
else:
12524
queue=[(start,0)]
12525
12526
for v,d in queue:
12527
yield v
12528
seen.add(v)
12529
12530
while len(queue)>0:
12531
v,d = queue.pop(0)
12532
if distance is None or d<distance:
12533
for w in neighbors(v):
12534
if w not in seen:
12535
seen.add(w)
12536
queue.append((w, d+1))
12537
yield w
12538
12539
def depth_first_search(self, start, ignore_direction=False,
12540
distance=None, neighbors=None):
12541
"""
12542
Returns an iterator over the vertices in a depth-first ordering.
12543
12544
INPUT:
12545
12546
12547
- ``start`` - vertex or list of vertices from which to start
12548
the traversal
12549
12550
- ``ignore_direction`` - (default False) only applies to
12551
directed graphs. If True, searches across edges in either
12552
direction.
12553
12554
- ``distance`` - the maximum distance from the ``start`` nodes
12555
to traverse. The ``start`` nodes are distance zero from
12556
themselves.
12557
12558
- ``neighbors`` - a function giving the neighbors of a vertex.
12559
The function should take a vertex and return a list of
12560
vertices. For a graph, ``neighbors`` is by default the
12561
:meth:`.neighbors` function of the graph. For a digraph,
12562
the ``neighbors`` function defaults to the
12563
:meth:`.successors` function of the graph.
12564
12565
.. SEEALSO::
12566
12567
- :meth:`breadth_first_search`
12568
12569
- :meth:`breadth_first_search <sage.graphs.base.c_graph.CGraphBackend.breadth_first_search>`
12570
-- breadth-first search for fast compiled graphs.
12571
12572
- :meth:`depth_first_search <sage.graphs.base.c_graph.CGraphBackend.depth_first_search>`
12573
-- depth-first search for fast compiled graphs.
12574
12575
EXAMPLES::
12576
12577
sage: G = Graph( { 0: [1], 1: [2], 2: [3], 3: [4], 4: [0]} )
12578
sage: list(G.depth_first_search(0))
12579
[0, 4, 3, 2, 1]
12580
12581
By default, the edge direction of a digraph is respected, but this
12582
can be overridden by the ``ignore_direction`` parameter::
12583
12584
12585
sage: D = DiGraph( { 0: [1,2,3], 1: [4,5], 2: [5], 3: [6], 5: [7], 6: [7], 7: [0]})
12586
sage: list(D.depth_first_search(0))
12587
[0, 3, 6, 7, 2, 5, 1, 4]
12588
sage: list(D.depth_first_search(0, ignore_direction=True))
12589
[0, 7, 6, 3, 5, 2, 1, 4]
12590
12591
You can specify a maximum distance in which to search. A
12592
distance of zero returns the ``start`` vertices::
12593
12594
sage: D = DiGraph( { 0: [1,2,3], 1: [4,5], 2: [5], 3: [6], 5: [7], 6: [7], 7: [0]})
12595
sage: list(D.depth_first_search(0,distance=0))
12596
[0]
12597
sage: list(D.depth_first_search(0,distance=1))
12598
[0, 3, 2, 1]
12599
12600
Multiple starting vertices can be specified in a list::
12601
12602
sage: D = DiGraph( { 0: [1,2,3], 1: [4,5], 2: [5], 3: [6], 5: [7], 6: [7], 7: [0]})
12603
sage: list(D.depth_first_search([0]))
12604
[0, 3, 6, 7, 2, 5, 1, 4]
12605
sage: list(D.depth_first_search([0,6]))
12606
[0, 3, 6, 7, 2, 5, 1, 4]
12607
sage: list(D.depth_first_search([0,6],distance=0))
12608
[0, 6]
12609
sage: list(D.depth_first_search([0,6],distance=1))
12610
[0, 3, 2, 1, 6, 7]
12611
sage: list(D.depth_first_search(6,ignore_direction=True,distance=2))
12612
[6, 7, 5, 0, 3]
12613
12614
More generally, you can specify a ``neighbors`` function. For
12615
example, you can traverse the graph backwards by setting
12616
``neighbors`` to be the :meth:`.neighbors_in` function of the graph::
12617
12618
sage: D = DiGraph( { 0: [1,2,3], 1: [4,5], 2: [5], 3: [6], 5: [7], 6: [7], 7: [0]})
12619
sage: list(D.depth_first_search(5,neighbors=D.neighbors_in, distance=2))
12620
[5, 2, 0, 1]
12621
sage: list(D.depth_first_search(5,neighbors=D.neighbors_out, distance=2))
12622
[5, 7, 0]
12623
sage: list(D.depth_first_search(5,neighbors=D.neighbors, distance=2))
12624
[5, 7, 6, 0, 2, 1, 4]
12625
12626
TESTS::
12627
12628
sage: D = DiGraph({1:[0], 2:[0]})
12629
sage: list(D.depth_first_search(0))
12630
[0]
12631
sage: list(D.depth_first_search(0, ignore_direction=True))
12632
[0, 2, 1]
12633
12634
"""
12635
# Preferably use the Cython implementation
12636
if neighbors is None and not isinstance(start,list) and distance is None and hasattr(self._backend,"depth_first_search"):
12637
for v in self._backend.depth_first_search(start, ignore_direction = ignore_direction):
12638
yield v
12639
else:
12640
if neighbors is None:
12641
if not self._directed or ignore_direction:
12642
neighbors=self.neighbor_iterator
12643
else:
12644
neighbors=self.neighbor_out_iterator
12645
seen=set([])
12646
if isinstance(start, list):
12647
# Reverse the list so that the initial vertices come out in the same order
12648
queue=[(v,0) for v in reversed(start)]
12649
else:
12650
queue=[(start,0)]
12651
12652
while len(queue)>0:
12653
v,d = queue.pop()
12654
if v not in seen:
12655
yield v
12656
seen.add(v)
12657
if distance is None or d<distance:
12658
for w in neighbors(v):
12659
if w not in seen:
12660
queue.append((w, d+1))
12661
12662
def lex_BFS(self,reverse=False,tree=False, initial_vertex = None):
12663
r"""
12664
Performs a Lex BFS on the graph.
12665
12666
A Lex BFS ( or Lexicographic Breadth-First Search ) is a Breadth
12667
First Search used for the recognition of Chordal Graphs. For more
12668
information, see the
12669
`Wikipedia article on Lex-BFS
12670
<http://en.wikipedia.org/wiki/Lexicographic_breadth-first_search>`_.
12671
12672
INPUT:
12673
12674
- ``reverse`` (boolean) -- whether to return the vertices
12675
in discovery order, or the reverse.
12676
12677
``False`` by default.
12678
12679
- ``tree`` (boolean) -- whether to return the discovery
12680
directed tree (each vertex being linked to the one that
12681
saw it for the first time)
12682
12683
``False`` by default.
12684
12685
- ``initial_vertex`` -- the first vertex to consider.
12686
12687
``None`` by default.
12688
12689
ALGORITHM:
12690
12691
This algorithm maintains for each vertex left in the graph
12692
a code corresponding to the vertices already removed. The
12693
vertex of maximal code ( according to the lexicographic
12694
order ) is then removed, and the codes are updated.
12695
12696
This algorithm runs in time `O(n^2)` ( where `n` is the
12697
number of vertices in the graph ), which is not optimal.
12698
An optimal algorithm would run in time `O(m)` ( where `m`
12699
is the number of edges in the graph ), and require the use
12700
of a doubly-linked list which are not available in python
12701
and can not really be written efficiently. This could be
12702
done in Cython, though.
12703
12704
EXAMPLE:
12705
12706
A Lex BFS is obviously an ordering of the vertices::
12707
12708
sage: g = graphs.PetersenGraph()
12709
sage: len(g.lex_BFS()) == g.order()
12710
True
12711
12712
For a Chordal Graph, a reversed Lex BFS is a Perfect
12713
Elimination Order ::
12714
12715
sage: g = graphs.PathGraph(3).lexicographic_product(graphs.CompleteGraph(2))
12716
sage: g.lex_BFS(reverse=True)
12717
[(2, 1), (2, 0), (1, 1), (1, 0), (0, 1), (0, 0)]
12718
12719
12720
And the vertices at the end of the tree of discovery are, for
12721
chordal graphs, simplicial vertices (their neighborhood is
12722
a complete graph)::
12723
12724
sage: g = graphs.ClawGraph().lexicographic_product(graphs.CompleteGraph(2))
12725
sage: v = g.lex_BFS()[-1]
12726
sage: peo, tree = g.lex_BFS(initial_vertex = v, tree=True)
12727
sage: leaves = [v for v in tree if tree.in_degree(v) ==0]
12728
sage: all([g.subgraph(g.neighbors(v)).is_clique() for v in leaves])
12729
True
12730
12731
TESTS:
12732
12733
There were some problems with the following call in the past (trac 10899) -- now
12734
it should be fine::
12735
12736
sage: Graph(1).lex_BFS(tree=True)
12737
([0], Digraph on 1 vertex)
12738
12739
"""
12740
id_inv = dict([(i,v) for (v,i) in zip(self.vertices(),range(self.order()))])
12741
code = [[] for i in range(self.order())]
12742
m = self.am()
12743
12744
l = lambda x : code[x]
12745
vertices = set(range(self.order()))
12746
12747
value = []
12748
pred = [-1]*self.order()
12749
12750
add_element = (lambda y:value.append(id_inv[y])) if not reverse else (lambda y: value.insert(0,id_inv[y]))
12751
12752
# Should we take care of the first vertex we pick ?
12753
first = True if initial_vertex is not None else False
12754
12755
12756
while vertices:
12757
12758
if not first:
12759
v = max(vertices,key=l)
12760
else:
12761
v = self.vertices().index(initial_vertex)
12762
first = False
12763
12764
vertices.remove(v)
12765
vector = m.column(v)
12766
for i in vertices:
12767
code[i].append(vector[i])
12768
if vector[i]:
12769
pred[i] = v
12770
add_element(v)
12771
12772
if tree:
12773
from sage.graphs.digraph import DiGraph
12774
g = DiGraph(sparse=True)
12775
g.add_vertices(self.vertices())
12776
edges = [(id_inv[i], id_inv[pred[i]]) for i in range(self.order()) if pred[i]!=-1]
12777
g.add_edges(edges)
12778
return value, g
12779
12780
else:
12781
return value
12782
12783
### Constructors
12784
12785
def add_cycle(self, vertices):
12786
"""
12787
Adds a cycle to the graph with the given vertices. If the vertices
12788
are already present, only the edges are added.
12789
12790
For digraphs, adds the directed cycle, whose orientation is
12791
determined by the list. Adds edges (vertices[u], vertices[u+1]) and
12792
(vertices[-1], vertices[0]).
12793
12794
INPUT:
12795
12796
- ``vertices`` -- a list of indices for the vertices of
12797
the cycle to be added.
12798
12799
12800
EXAMPLES::
12801
12802
sage: G = Graph()
12803
sage: G.add_vertices(range(10)); G
12804
Graph on 10 vertices
12805
sage: show(G)
12806
sage: G.add_cycle(range(20)[10:20])
12807
sage: show(G)
12808
sage: G.add_cycle(range(10))
12809
sage: show(G)
12810
12811
::
12812
12813
sage: D = DiGraph()
12814
sage: D.add_cycle(range(4))
12815
sage: D.edges()
12816
[(0, 1, None), (1, 2, None), (2, 3, None), (3, 0, None)]
12817
"""
12818
self.add_path(vertices)
12819
self.add_edge(vertices[-1], vertices[0])
12820
12821
def add_path(self, vertices):
12822
"""
12823
Adds a cycle to the graph with the given vertices. If the vertices
12824
are already present, only the edges are added.
12825
12826
For digraphs, adds the directed path vertices[0], ...,
12827
vertices[-1].
12828
12829
INPUT:
12830
12831
12832
- ``vertices`` - a list of indices for the vertices of
12833
the cycle to be added.
12834
12835
12836
EXAMPLES::
12837
12838
sage: G = Graph()
12839
sage: G.add_vertices(range(10)); G
12840
Graph on 10 vertices
12841
sage: show(G)
12842
sage: G.add_path(range(20)[10:20])
12843
sage: show(G)
12844
sage: G.add_path(range(10))
12845
sage: show(G)
12846
12847
::
12848
12849
sage: D = DiGraph()
12850
sage: D.add_path(range(4))
12851
sage: D.edges()
12852
[(0, 1, None), (1, 2, None), (2, 3, None)]
12853
"""
12854
vert1 = vertices[0]
12855
for v in vertices[1:]:
12856
self.add_edge(vert1, v)
12857
vert1 = v
12858
12859
def complement(self):
12860
"""
12861
Returns the complement of the (di)graph.
12862
12863
The complement of a graph has the same vertices, but exactly those
12864
edges that are not in the original graph. This is not well defined
12865
for graphs with multiple edges.
12866
12867
EXAMPLES::
12868
12869
sage: P = graphs.PetersenGraph()
12870
sage: P.plot() # long time
12871
sage: PC = P.complement()
12872
sage: PC.plot() # long time
12873
12874
::
12875
12876
sage: graphs.TetrahedralGraph().complement().size()
12877
0
12878
sage: graphs.CycleGraph(4).complement().edges()
12879
[(0, 2, None), (1, 3, None)]
12880
sage: graphs.CycleGraph(4).complement()
12881
complement(Cycle graph): Graph on 4 vertices
12882
sage: G = Graph(multiedges=True, sparse=True)
12883
sage: G.add_edges([(0,1)]*3)
12884
sage: G.complement()
12885
Traceback (most recent call last):
12886
...
12887
TypeError: Complement not well defined for (di)graphs with multiple edges.
12888
"""
12889
if self.has_multiple_edges():
12890
raise TypeError('Complement not well defined for (di)graphs with multiple edges.')
12891
from copy import copy
12892
G = copy(self)
12893
G.delete_edges(G.edges())
12894
G.name('complement(%s)'%self.name())
12895
for u in self:
12896
for v in self:
12897
if not self.has_edge(u,v):
12898
G.add_edge(u,v)
12899
return G
12900
12901
def line_graph(self, labels=True):
12902
"""
12903
Returns the line graph of the (di)graph.
12904
12905
The line graph of an undirected graph G is an undirected graph H
12906
such that the vertices of H are the edges of G and two vertices e
12907
and f of H are adjacent if e and f share a common vertex in G. In
12908
other words, an edge in H represents a path of length 2 in G.
12909
12910
The line graph of a directed graph G is a directed graph H such
12911
that the vertices of H are the edges of G and two vertices e and f
12912
of H are adjacent if e and f share a common vertex in G and the
12913
terminal vertex of e is the initial vertex of f. In other words, an
12914
edge in H represents a (directed) path of length 2 in G.
12915
12916
EXAMPLES::
12917
12918
sage: g=graphs.CompleteGraph(4)
12919
sage: h=g.line_graph()
12920
sage: h.vertices()
12921
[(0, 1, None),
12922
(0, 2, None),
12923
(0, 3, None),
12924
(1, 2, None),
12925
(1, 3, None),
12926
(2, 3, None)]
12927
sage: h.am()
12928
[0 1 1 1 1 0]
12929
[1 0 1 1 0 1]
12930
[1 1 0 0 1 1]
12931
[1 1 0 0 1 1]
12932
[1 0 1 1 0 1]
12933
[0 1 1 1 1 0]
12934
sage: h2=g.line_graph(labels=False)
12935
sage: h2.vertices()
12936
[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]
12937
sage: h2.am()==h.am()
12938
True
12939
sage: g = DiGraph([[1..4],lambda i,j: i<j])
12940
sage: h = g.line_graph()
12941
sage: h.vertices()
12942
[(1, 2, None),
12943
(1, 3, None),
12944
(1, 4, None),
12945
(2, 3, None),
12946
(2, 4, None),
12947
(3, 4, None)]
12948
sage: h.edges()
12949
[((1, 2, None), (2, 3, None), None),
12950
((1, 2, None), (2, 4, None), None),
12951
((1, 3, None), (3, 4, None), None),
12952
((2, 3, None), (3, 4, None), None)]
12953
"""
12954
if self._directed:
12955
from sage.graphs.digraph import DiGraph
12956
G=DiGraph()
12957
G.add_vertices(self.edges(labels=labels))
12958
for v in self:
12959
# Connect appropriate incident edges of the vertex v
12960
G.add_edges([(e,f) for e in self.incoming_edge_iterator(v, labels=labels) \
12961
for f in self.outgoing_edge_iterator(v, labels=labels)])
12962
return G
12963
else:
12964
from sage.graphs.all import Graph
12965
G=Graph()
12966
# We must sort the edges' endpoints so that (1,2,None) is
12967
# seen as the same edge as (2,1,None).
12968
if labels:
12969
elist=[(min(i[0:2]),max(i[0:2]),i[2] if i[2] != {} else None)
12970
for i in self.edge_iterator()]
12971
else:
12972
elist=[(min(i),max(i))
12973
for i in self.edge_iterator(labels=False)]
12974
G.add_vertices(elist)
12975
for v in self:
12976
if labels:
12977
elist=[(min(i[0:2]),max(i[0:2]),i[2] if i[2] != {} else None)
12978
for i in self.edge_iterator(v)]
12979
else:
12980
elist=[(min(i),max(i))
12981
for i in self.edge_iterator(v, labels=False)]
12982
G.add_edges([(e, f) for e in elist for f in elist])
12983
return G
12984
12985
def to_simple(self):
12986
"""
12987
Returns a simple version of itself (i.e., undirected and loops and
12988
multiple edges are removed).
12989
12990
EXAMPLES::
12991
12992
sage: G = DiGraph(loops=True,multiedges=True,sparse=True)
12993
sage: G.add_edges( [ (0,0), (1,1), (2,2), (2,3,1), (2,3,2), (3,2) ] )
12994
sage: G.edges(labels=False)
12995
[(0, 0), (1, 1), (2, 2), (2, 3), (2, 3), (3, 2)]
12996
sage: H=G.to_simple()
12997
sage: H.edges(labels=False)
12998
[(2, 3)]
12999
sage: H.is_directed()
13000
False
13001
sage: H.allows_loops()
13002
False
13003
sage: H.allows_multiple_edges()
13004
False
13005
"""
13006
g=self.to_undirected()
13007
g.allow_loops(False)
13008
g.allow_multiple_edges(False)
13009
return g
13010
13011
def disjoint_union(self, other, verbose_relabel=True):
13012
"""
13013
Returns the disjoint union of self and other.
13014
13015
If the graphs have common vertices, the vertices will be renamed to
13016
form disjoint sets.
13017
13018
INPUT:
13019
13020
13021
- ``verbose_relabel`` - (defaults to True) If True
13022
and the graphs have common vertices, then each vertex v in the
13023
first graph will be changed to '0,v' and each vertex u in the
13024
second graph will be changed to '1,u'. If False, the vertices of
13025
the first graph and the second graph will be relabeled with
13026
consecutive integers.
13027
13028
13029
EXAMPLES::
13030
13031
sage: G = graphs.CycleGraph(3)
13032
sage: H = graphs.CycleGraph(4)
13033
sage: J = G.disjoint_union(H); J
13034
Cycle graph disjoint_union Cycle graph: Graph on 7 vertices
13035
sage: J.vertices()
13036
[(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (1, 3)]
13037
sage: J = G.disjoint_union(H, verbose_relabel=False); J
13038
Cycle graph disjoint_union Cycle graph: Graph on 7 vertices
13039
sage: J.vertices()
13040
[0, 1, 2, 3, 4, 5, 6]
13041
13042
If the vertices are already disjoint and verbose_relabel is True,
13043
then the vertices are not relabeled.
13044
13045
::
13046
13047
sage: G=Graph({'a': ['b']})
13048
sage: G.name("Custom path")
13049
sage: G.name()
13050
'Custom path'
13051
sage: H=graphs.CycleGraph(3)
13052
sage: J=G.disjoint_union(H); J
13053
Custom path disjoint_union Cycle graph: Graph on 5 vertices
13054
sage: J.vertices()
13055
[0, 1, 2, 'a', 'b']
13056
"""
13057
if (self._directed and not other._directed) or (not self._directed and other._directed):
13058
raise TypeError('Both arguments must be of the same class.')
13059
13060
if not verbose_relabel:
13061
r_self = {}; r_other = {}; i = 0
13062
for v in self:
13063
r_self[v] = i; i += 1
13064
for v in other:
13065
r_other[v] = i; i += 1
13066
G = self.relabel(r_self, inplace=False).union(other.relabel(r_other, inplace=False))
13067
elif any(u==v for u in self for v in other):
13068
r_self = dict([[v,(0,v)] for v in self])
13069
r_other = dict([[v,(1,v)] for v in other])
13070
G = self.relabel(r_self, inplace=False).union(other.relabel(r_other, inplace=False))
13071
else:
13072
G = self.union(other)
13073
13074
G.name('%s disjoint_union %s'%(self.name(), other.name()))
13075
return G
13076
13077
def union(self, other):
13078
"""
13079
Returns the union of self and other.
13080
13081
If the graphs have common vertices, the common vertices will be
13082
identified.
13083
13084
EXAMPLES::
13085
13086
sage: G = graphs.CycleGraph(3)
13087
sage: H = graphs.CycleGraph(4)
13088
sage: J = G.union(H); J
13089
Graph on 4 vertices
13090
sage: J.vertices()
13091
[0, 1, 2, 3]
13092
sage: J.edges(labels=False)
13093
[(0, 1), (0, 2), (0, 3), (1, 2), (2, 3)]
13094
"""
13095
if (self._directed and not other._directed) or (not self._directed and other._directed):
13096
raise TypeError('Both arguments must be of the same class.')
13097
if self._directed:
13098
from sage.graphs.all import DiGraph
13099
G = DiGraph()
13100
else:
13101
from sage.graphs.all import Graph
13102
G = Graph()
13103
G.add_vertices(self.vertices())
13104
G.add_vertices(other.vertices())
13105
G.add_edges(self.edges())
13106
G.add_edges(other.edges())
13107
return G
13108
13109
def cartesian_product(self, other):
13110
r"""
13111
Returns the Cartesian product of self and other.
13112
13113
The Cartesian product of `G` and `H` is the graph `L` with vertex set
13114
`V(L)` equal to the Cartesian product of the vertices `V(G)` and `V(H)`,
13115
and `((u,v), (w,x))` is an edge iff either - `(u, w)` is an edge of self
13116
and `v = x`, or - `(v, x)` is an edge of other and `u = w`.
13117
13118
TESTS:
13119
13120
Cartesian product of graphs::
13121
13122
sage: G = Graph([(0,1),(1,2)])
13123
sage: H = Graph([('a','b')])
13124
sage: C1 = G.cartesian_product(H)
13125
sage: C1.edges(labels=None)
13126
[((0, 'a'), (0, 'b')), ((0, 'a'), (1, 'a')), ((0, 'b'), (1, 'b')), ((1, 'a'), (1, 'b')), ((1, 'a'), (2, 'a')), ((1, 'b'), (2, 'b')), ((2, 'a'), (2, 'b'))]
13127
sage: C2 = H.cartesian_product(G)
13128
sage: C1.is_isomorphic(C2)
13129
True
13130
13131
Construction of a Toroidal grid::
13132
13133
sage: A = graphs.CycleGraph(3)
13134
sage: B = graphs.CycleGraph(4)
13135
sage: T = A.cartesian_product(B)
13136
sage: T.is_isomorphic( graphs.ToroidalGrid2dGraph(3,4) )
13137
True
13138
13139
Cartesian product of digraphs::
13140
13141
sage: P = DiGraph([(0,1)])
13142
sage: B = digraphs.DeBruijn( ['a','b'], 2 )
13143
sage: Q = P.cartesian_product(B)
13144
sage: Q.edges(labels=None)
13145
[((0, 'aa'), (0, 'aa')), ((0, 'aa'), (0, 'ab')), ((0, 'aa'), (1, 'aa')), ((0, 'ab'), (0, 'ba')), ((0, 'ab'), (0, 'bb')), ((0, 'ab'), (1, 'ab')), ((0, 'ba'), (0, 'aa')), ((0, 'ba'), (0, 'ab')), ((0, 'ba'), (1, 'ba')), ((0, 'bb'), (0, 'ba')), ((0, 'bb'), (0, 'bb')), ((0, 'bb'), (1, 'bb')), ((1, 'aa'), (1, 'aa')), ((1, 'aa'), (1, 'ab')), ((1, 'ab'), (1, 'ba')), ((1, 'ab'), (1, 'bb')), ((1, 'ba'), (1, 'aa')), ((1, 'ba'), (1, 'ab')), ((1, 'bb'), (1, 'ba')), ((1, 'bb'), (1, 'bb'))]
13146
sage: Q.strongly_connected_components_digraph().num_verts()
13147
2
13148
sage: V = Q.strongly_connected_component_containing_vertex( (0, 'aa') )
13149
sage: B.is_isomorphic( Q.subgraph(V) )
13150
True
13151
"""
13152
if self._directed and other._directed:
13153
from sage.graphs.all import DiGraph
13154
G = DiGraph( loops = (self.has_loops() or other.has_loops()) )
13155
elif (not self._directed) and (not other._directed):
13156
from sage.graphs.all import Graph
13157
G = Graph()
13158
else:
13159
raise TypeError('The graphs should be both directed or both undirected.')
13160
13161
G.add_vertices( [(u,v) for u in self for v in other] )
13162
for u,w in self.edge_iterator(labels=None):
13163
for v in other:
13164
G.add_edge((u,v), (w,v))
13165
for v,x in other.edge_iterator(labels=None):
13166
for u in self:
13167
G.add_edge((u,v), (u,x))
13168
return G
13169
13170
def tensor_product(self, other):
13171
r"""
13172
Returns the tensor product of self and other.
13173
13174
The tensor product of `G` and `H` is the graph `L` with vertex set
13175
`V(L)` equal to the Cartesian product of the vertices `V(G)` and `V(H)`,
13176
and `((u,v), (w,x))` is an edge iff - `(u, w)` is an edge of self, and -
13177
`(v, x)` is an edge of other.
13178
13179
The tensor product is also knwon as the categorical product and the
13180
kronecker product (refering to the kronecker matrix product). See
13181
:wikipedia:`Wikipedia article on the Kronecker product <Kronecker_product>`.
13182
13183
EXAMPLES::
13184
13185
sage: Z = graphs.CompleteGraph(2)
13186
sage: C = graphs.CycleGraph(5)
13187
sage: T = C.tensor_product(Z); T
13188
Graph on 10 vertices
13189
sage: T.size()
13190
10
13191
sage: T.plot() # long time
13192
13193
::
13194
13195
sage: D = graphs.DodecahedralGraph()
13196
sage: P = graphs.PetersenGraph()
13197
sage: T = D.tensor_product(P); T
13198
Graph on 200 vertices
13199
sage: T.size()
13200
900
13201
sage: T.plot() # long time
13202
13203
TESTS:
13204
13205
Tensor product of graphs::
13206
13207
sage: G = Graph([(0,1), (1,2)])
13208
sage: H = Graph([('a','b')])
13209
sage: T = G.tensor_product(H)
13210
sage: T.edges(labels=None)
13211
[((0, 'a'), (1, 'b')), ((0, 'b'), (1, 'a')), ((1, 'a'), (2, 'b')), ((1, 'b'), (2, 'a'))]
13212
sage: T.is_isomorphic( H.tensor_product(G) )
13213
True
13214
13215
Tensor product of digraphs::
13216
13217
sage: I = DiGraph([(0,1), (1,2)])
13218
sage: J = DiGraph([('a','b')])
13219
sage: T = I.tensor_product(J)
13220
sage: T.edges(labels=None)
13221
[((0, 'a'), (1, 'b')), ((1, 'a'), (2, 'b'))]
13222
sage: T.is_isomorphic( J.tensor_product(I) )
13223
True
13224
13225
The tensor product of two DeBruijn digraphs of same diameter is a DeBruijn digraph::
13226
13227
sage: B1 = digraphs.DeBruijn(2, 3)
13228
sage: B2 = digraphs.DeBruijn(3, 3)
13229
sage: T = B1.tensor_product( B2 )
13230
sage: T.is_isomorphic( digraphs.DeBruijn( 2*3, 3) )
13231
True
13232
"""
13233
if self._directed and other._directed:
13234
from sage.graphs.all import DiGraph
13235
G = DiGraph( loops = (self.has_loops() or other.has_loops()) )
13236
elif (not self._directed) and (not other._directed):
13237
from sage.graphs.all import Graph
13238
G = Graph()
13239
else:
13240
raise TypeError('The graphs should be both directed or both undirected.')
13241
G.add_vertices( [(u,v) for u in self for v in other] )
13242
for u,w in self.edges(labels=None):
13243
for v,x in other.edges(labels=None):
13244
G.add_edge((u,v), (w,x))
13245
if not G._directed:
13246
G.add_edge((u,x), (w,v))
13247
return G
13248
13249
categorical_product = tensor_product
13250
kronecker_product = tensor_product
13251
13252
def lexicographic_product(self, other):
13253
r"""
13254
Returns the lexicographic product of self and other.
13255
13256
The lexicographic product of `G` and `H` is the graph `L` with vertex
13257
set `V(L)` equal to the Cartesian product of the vertices `V(G)` and
13258
`V(H)`, and `((u,v), (w,x))` is an edge iff - `(u, w)` is an edge of
13259
self, or - `u = w` and `(v, x)` is an edge of other.
13260
13261
EXAMPLES::
13262
13263
sage: Z = graphs.CompleteGraph(2)
13264
sage: C = graphs.CycleGraph(5)
13265
sage: L = C.lexicographic_product(Z); L
13266
Graph on 10 vertices
13267
sage: L.plot() # long time
13268
13269
::
13270
13271
sage: D = graphs.DodecahedralGraph()
13272
sage: P = graphs.PetersenGraph()
13273
sage: L = D.lexicographic_product(P); L
13274
Graph on 200 vertices
13275
sage: L.plot() # long time
13276
13277
TESTS:
13278
13279
Lexicographic product of graphs::
13280
13281
sage: G = Graph([(0,1), (1,2)])
13282
sage: H = Graph([('a','b')])
13283
sage: T = G.lexicographic_product(H)
13284
sage: T.edges(labels=None)
13285
[((0, 'a'), (0, 'b')), ((0, 'a'), (1, 'a')), ((0, 'a'), (1, 'b')), ((0, 'b'), (1, 'a')), ((0, 'b'), (1, 'b')), ((1, 'a'), (1, 'b')), ((1, 'a'), (2, 'a')), ((1, 'a'), (2, 'b')), ((1, 'b'), (2, 'a')), ((1, 'b'), (2, 'b')), ((2, 'a'), (2, 'b'))]
13286
sage: T.is_isomorphic( H.lexicographic_product(G) )
13287
False
13288
13289
Lexicographic product of digraphs::
13290
13291
sage: I = DiGraph([(0,1), (1,2)])
13292
sage: J = DiGraph([('a','b')])
13293
sage: T = I.lexicographic_product(J)
13294
sage: T.edges(labels=None)
13295
[((0, 'a'), (0, 'b')), ((0, 'a'), (1, 'a')), ((0, 'a'), (1, 'b')), ((0, 'b'), (1, 'a')), ((0, 'b'), (1, 'b')), ((1, 'a'), (1, 'b')), ((1, 'a'), (2, 'a')), ((1, 'a'), (2, 'b')), ((1, 'b'), (2, 'a')), ((1, 'b'), (2, 'b')), ((2, 'a'), (2, 'b'))]
13296
sage: T.is_isomorphic( J.lexicographic_product(I) )
13297
False
13298
"""
13299
if self._directed and other._directed:
13300
from sage.graphs.all import DiGraph
13301
G = DiGraph( loops = (self.has_loops() or other.has_loops()) )
13302
elif (not self._directed) and (not other._directed):
13303
from sage.graphs.all import Graph
13304
G = Graph()
13305
else:
13306
raise TypeError('The graphs should be both directed or both undirected.')
13307
G.add_vertices( [(u,v) for u in self for v in other] )
13308
for u,w in self.edges(labels=None):
13309
for v in other:
13310
for x in other:
13311
G.add_edge((u,v), (w,x))
13312
for u in self:
13313
for v,x in other.edges(labels=None):
13314
G.add_edge((u,v), (u,x))
13315
return G
13316
13317
def strong_product(self, other):
13318
r"""
13319
Returns the strong product of self and other.
13320
13321
The strong product of `G` and `H` is the graph `L` with vertex set
13322
`V(L)` equal to the Cartesian product of the vertices `V(G)` and `V(H)`,
13323
and `((u,v), (w,x))` is an edge iff either - `(u, w)` is an edge of self
13324
and `v = x`, or - `(v, x)` is an edge of other and `u = w`, or - `(u,
13325
w)` is an edge of self and `(v, x)` is an edge of other. In other words,
13326
the edges of the strong product is the union of the edges of the tensor
13327
and Cartesian products.
13328
13329
EXAMPLES::
13330
13331
sage: Z = graphs.CompleteGraph(2)
13332
sage: C = graphs.CycleGraph(5)
13333
sage: S = C.strong_product(Z); S
13334
Graph on 10 vertices
13335
sage: S.plot() # long time
13336
13337
::
13338
13339
sage: D = graphs.DodecahedralGraph()
13340
sage: P = graphs.PetersenGraph()
13341
sage: S = D.strong_product(P); S
13342
Graph on 200 vertices
13343
sage: S.plot() # long time
13344
13345
TESTS:
13346
13347
Strong product of graphs::
13348
13349
sage: G = Graph([(0,1), (1,2)])
13350
sage: H = Graph([('a','b')])
13351
sage: T = G.strong_product(H)
13352
sage: T.edges(labels=None)
13353
[((0, 'a'), (0, 'b')), ((0, 'a'), (1, 'a')), ((0, 'a'), (1, 'b')), ((0, 'b'), (1, 'b')), ((1, 'a'), (1, 'b')), ((1, 'a'), (2, 'a')), ((1, 'a'), (2, 'b')), ((1, 'b'), (2, 'b')), ((2, 'a'), (2, 'b'))]
13354
sage: T.is_isomorphic( H.strong_product(G) )
13355
True
13356
13357
Strong product of digraphs::
13358
13359
sage: I = DiGraph([(0,1), (1,2)])
13360
sage: J = DiGraph([('a','b')])
13361
sage: T = I.strong_product(J)
13362
sage: T.edges(labels=None)
13363
[((0, 'a'), (0, 'b')), ((0, 'a'), (1, 'a')), ((0, 'a'), (1, 'b')), ((0, 'b'), (1, 'b')), ((1, 'a'), (1, 'b')), ((1, 'a'), (2, 'a')), ((1, 'a'), (2, 'b')), ((1, 'b'), (2, 'b')), ((2, 'a'), (2, 'b'))]
13364
sage: T.is_isomorphic( J.strong_product(I) )
13365
True
13366
"""
13367
if self._directed and other._directed:
13368
from sage.graphs.all import DiGraph
13369
G = DiGraph( loops = (self.has_loops() or other.has_loops()) )
13370
elif (not self._directed) and (not other._directed):
13371
from sage.graphs.all import Graph
13372
G = Graph()
13373
else:
13374
raise TypeError('The graphs should be both directed or both undirected.')
13375
13376
G.add_vertices( [(u,v) for u in self for v in other] )
13377
for u,w in self.edges(labels=None):
13378
for v in other:
13379
G.add_edge((u,v), (w,v))
13380
for v,x in other.edges(labels=None):
13381
G.add_edge((u,v), (w,x))
13382
for v,x in other.edges(labels=None):
13383
for u in self:
13384
G.add_edge((u,v), (u,x))
13385
return G
13386
13387
def disjunctive_product(self, other):
13388
r"""
13389
Returns the disjunctive product of self and other.
13390
13391
The disjunctive product of `G` and `H` is the graph `L` with vertex set
13392
`V(L)` equal to the Cartesian product of the vertices `V(G)` and `V(H)`,
13393
and `((u,v), (w,x))` is an edge iff either - `(u, w)` is an edge of
13394
self, or - `(v, x)` is an edge of other.
13395
13396
EXAMPLES::
13397
13398
sage: Z = graphs.CompleteGraph(2)
13399
sage: D = Z.disjunctive_product(Z); D
13400
Graph on 4 vertices
13401
sage: D.plot() # long time
13402
13403
::
13404
13405
sage: C = graphs.CycleGraph(5)
13406
sage: D = C.disjunctive_product(Z); D
13407
Graph on 10 vertices
13408
sage: D.plot() # long time
13409
13410
TESTS:
13411
13412
Disjunctive product of graphs::
13413
13414
sage: G = Graph([(0,1), (1,2)])
13415
sage: H = Graph([('a','b')])
13416
sage: T = G.disjunctive_product(H)
13417
sage: T.edges(labels=None)
13418
[((0, 'a'), (0, 'b')), ((0, 'a'), (1, 'a')), ((0, 'a'), (1, 'b')), ((0, 'a'), (2, 'b')), ((0, 'b'), (1, 'a')), ((0, 'b'), (1, 'b')), ((0, 'b'), (2, 'a')), ((1, 'a'), (1, 'b')), ((1, 'a'), (2, 'a')), ((1, 'a'), (2, 'b')), ((1, 'b'), (2, 'a')), ((1, 'b'), (2, 'b')), ((2, 'a'), (2, 'b'))]
13419
sage: T.is_isomorphic( H.disjunctive_product(G) )
13420
True
13421
13422
Disjunctive product of digraphs::
13423
13424
sage: I = DiGraph([(0,1), (1,2)])
13425
sage: J = DiGraph([('a','b')])
13426
sage: T = I.disjunctive_product(J)
13427
sage: T.edges(labels=None)
13428
[((0, 'a'), (0, 'b')), ((0, 'a'), (1, 'a')), ((0, 'a'), (1, 'b')), ((0, 'a'), (2, 'b')), ((0, 'b'), (1, 'a')), ((0, 'b'), (1, 'b')), ((1, 'a'), (0, 'b')), ((1, 'a'), (1, 'b')), ((1, 'a'), (2, 'a')), ((1, 'a'), (2, 'b')), ((1, 'b'), (2, 'a')), ((1, 'b'), (2, 'b')), ((2, 'a'), (0, 'b')), ((2, 'a'), (1, 'b')), ((2, 'a'), (2, 'b'))]
13429
sage: T.is_isomorphic( J.disjunctive_product(I) )
13430
True
13431
"""
13432
if self._directed and other._directed:
13433
from sage.graphs.all import DiGraph
13434
G = DiGraph( loops = (self.has_loops() or other.has_loops()) )
13435
elif (not self._directed) and (not other._directed):
13436
from sage.graphs.all import Graph
13437
G = Graph()
13438
else:
13439
raise TypeError('The graphs should be both directed or both undirected.')
13440
13441
G.add_vertices( [(u,v) for u in self for v in other] )
13442
for u,w in self.edges(labels=None):
13443
for v in other:
13444
for x in other:
13445
G.add_edge((u,v), (w,x))
13446
for v,x in other.edges(labels=None):
13447
for u in self:
13448
for w in self:
13449
G.add_edge((u,v), (w,x))
13450
return G
13451
13452
def transitive_closure(self):
13453
r"""
13454
Computes the transitive closure of a graph and returns it. The
13455
original graph is not modified.
13456
13457
The transitive closure of a graph G has an edge (x,y) if and only
13458
if there is a path between x and y in G.
13459
13460
The transitive closure of any strongly connected component of a
13461
graph is a complete graph. In particular, the transitive closure of
13462
a connected undirected graph is a complete graph. The transitive
13463
closure of a directed acyclic graph is a directed acyclic graph
13464
representing the full partial order.
13465
13466
EXAMPLES::
13467
13468
sage: g=graphs.PathGraph(4)
13469
sage: g.transitive_closure()
13470
Transitive closure of Path Graph: Graph on 4 vertices
13471
sage: g.transitive_closure()==graphs.CompleteGraph(4)
13472
True
13473
sage: g=DiGraph({0:[1,2], 1:[3], 2:[4,5]})
13474
sage: g.transitive_closure().edges(labels=False)
13475
[(0, 1), (0, 2), (0, 3), (0, 4), (0, 5), (1, 3), (2, 4), (2, 5)]
13476
13477
"""
13478
from copy import copy
13479
G = copy(self)
13480
G.name('Transitive closure of ' + self.name())
13481
for v in G:
13482
# todo optimization opportunity: we are adding edges that
13483
# are already in the graph and we are adding edges
13484
# one at a time.
13485
for e in G.breadth_first_search(v):
13486
G.add_edge((v,e))
13487
return G
13488
13489
def transitive_reduction(self):
13490
r"""
13491
Returns a transitive reduction of a graph. The original graph is
13492
not modified.
13493
13494
A transitive reduction H of G has a path from x to y if and only if
13495
there was a path from x to y in G. Deleting any edge of H destroys
13496
this property. A transitive reduction is not unique in general. A
13497
transitive reduction has the same transitive closure as the
13498
original graph.
13499
13500
A transitive reduction of a complete graph is a tree. A transitive
13501
reduction of a tree is itself.
13502
13503
EXAMPLES::
13504
13505
sage: g=graphs.PathGraph(4)
13506
sage: g.transitive_reduction()==g
13507
True
13508
sage: g=graphs.CompleteGraph(5)
13509
sage: edges = g.transitive_reduction().edges(); len(edges)
13510
4
13511
sage: g=DiGraph({0:[1,2], 1:[2,3,4,5], 2:[4,5]})
13512
sage: g.transitive_reduction().size()
13513
5
13514
"""
13515
from copy import copy
13516
from sage.rings.infinity import Infinity
13517
G = copy(self)
13518
for e in self.edge_iterator():
13519
# Try deleting the edge, see if we still have a path
13520
# between the vertices.
13521
G.delete_edge(e)
13522
if G.distance(e[0],e[1])==Infinity:
13523
# oops, we shouldn't have deleted it
13524
G.add_edge(e)
13525
return G
13526
13527
def is_transitively_reduced(self):
13528
r"""
13529
Tests whether the digraph is transitively reduced.
13530
13531
A digraph is transitively reduced if it is equal to its transitive
13532
reduction.
13533
13534
EXAMPLES::
13535
13536
sage: d = DiGraph({0:[1],1:[2],2:[3]})
13537
sage: d.is_transitively_reduced()
13538
True
13539
13540
sage: d = DiGraph({0:[1,2],1:[2]})
13541
sage: d.is_transitively_reduced()
13542
False
13543
13544
sage: d = DiGraph({0:[1,2],1:[2],2:[]})
13545
sage: d.is_transitively_reduced()
13546
False
13547
"""
13548
from copy import copy
13549
from sage.rings.infinity import Infinity
13550
G = copy(self)
13551
for e in self.edge_iterator():
13552
G.delete_edge(e)
13553
if G.distance(e[0],e[1]) == Infinity:
13554
G.add_edge(e)
13555
else:
13556
return False
13557
return True
13558
13559
13560
### Visualization
13561
13562
def _color_by_label(self, format='hex', as_function=False, default_color = "black"):
13563
"""
13564
Coloring of the edges according to their label for plotting
13565
13566
INPUT:
13567
13568
- ``format`` -- "rgbtuple", "hex", ``True`` (same as "hex"),
13569
or a function or dictionary assigning colors to labels
13570
(default: "hex")
13571
- ``default_color`` -- a color (as a string) or None (default: "black")
13572
- ``as_function`` -- boolean (default: ``False``)
13573
13574
OUTPUT: A coloring of the edges of this graph.
13575
13576
If ``as_function`` is ``True``, then the coloring is returned
13577
as a function assigning a color to each label. Otherwise (the
13578
default, for backward compatibility), the coloring is returned
13579
as a dictionary assigning to each color the list of the edges
13580
of the graph of that color.
13581
13582
This is factored out from plot() for use in 3d plots, etc.
13583
13584
If ``format`` is a function, then it is used directly as
13585
coloring. Otherwise, for each label a default color is chosen
13586
along a rainbow (see :func:`sage.plot.colors.rainbow`). If
13587
``format`` is a dictionary, then the colors specified there
13588
override the default choices.
13589
13590
EXAMPLES:
13591
13592
We consider the Cayley graph of the symmetric group, whose
13593
edges are labelled by the numbers 1,2, and 3::
13594
13595
sage: G = SymmetricGroup(4).cayley_graph()
13596
sage: set(G.edge_labels())
13597
set([1, 2, 3])
13598
13599
We first request the coloring as a function::
13600
13601
sage: f = G._color_by_label(as_function=True)
13602
sage: [f(1), f(2), f(3)]
13603
['#00ff00', '#ff0000', '#0000ff']
13604
sage: f = G._color_by_label({1: "blue", 2: "red", 3: "green"}, as_function=True)
13605
sage: [f(1), f(2), f(3)]
13606
['blue', 'red', 'green']
13607
sage: f = G._color_by_label({1: "red"}, as_function=True)
13608
sage: [f(1), f(2), f(3)]
13609
['red', 'black', 'black']
13610
sage: f = G._color_by_label({1: "red"}, as_function=True, default_color = 'blue')
13611
sage: [f(1), f(2), f(3)]
13612
['red', 'blue', 'blue']
13613
13614
The default output is a dictionary assigning edges to colors::
13615
13616
sage: G._color_by_label()
13617
{'#00ff00': [((1,4,3,2), (1,4,3), 1), ... ((1,2)(3,4), (3,4), 1)],
13618
'#ff0000': [((1,4,3,2), (1,4,2), 2), ... ((1,2)(3,4), (1,3,4,2), 2)],
13619
'#0000ff': [((1,4,3,2), (1,3,2), 3), ... ((1,2)(3,4), (1,2), 3)]}
13620
13621
sage: G._color_by_label({1: "blue", 2: "red", 3: "green"})
13622
{'blue': [((1,4,3,2), (1,4,3), 1), ... ((1,2)(3,4), (3,4), 1)],
13623
'green': [((1,4,3,2), (1,3,2), 3), ... ((1,2)(3,4), (1,2), 3)],
13624
'red': [((1,4,3,2), (1,4,2), 2), ... ((1,2)(3,4), (1,3,4,2), 2)]}
13625
13626
TESTS:
13627
13628
We check what happens when several labels have the same color::
13629
13630
sage: result = G._color_by_label({1: "blue", 2: "blue", 3: "green"})
13631
sage: result.keys()
13632
['blue', 'green']
13633
sage: len(result['blue'])
13634
48
13635
sage: len(result['green'])
13636
24
13637
"""
13638
if format is True:
13639
format = "hex"
13640
if isinstance(format, str):
13641
# Find all labels; this is slower and huglier than:
13642
# labels = set(edge[2] for edge in self.edge_iterator())
13643
# but works with non hashable edge labels, and keeps backward
13644
# compatibility for the label ordering.
13645
labels = []
13646
for edge in self.edge_iterator():
13647
label = edge[2]
13648
if label not in labels:
13649
labels.append(label)
13650
13651
from sage.plot.colors import rainbow
13652
colors = rainbow(len(labels), format=format)
13653
color_of_label = dict(zip(labels, colors))
13654
color_of_label = color_of_label.__getitem__
13655
elif isinstance(format, dict):
13656
color_of_label = lambda label: format.get(label, default_color)
13657
else:
13658
# This assumes that ``format`` is already a function
13659
color_of_label = format
13660
13661
if as_function:
13662
return color_of_label
13663
13664
edges_by_color = {}
13665
for edge in self.edge_iterator():
13666
color = color_of_label(edge[2])
13667
if color in edges_by_color:
13668
edges_by_color[color].append(edge)
13669
else:
13670
edges_by_color[color] = [edge]
13671
return edges_by_color
13672
13673
def latex_options(self):
13674
r"""
13675
Returns an instance of
13676
:class:`~sage.graphs.graph_latex.GraphLatex` for the graph.
13677
13678
Changes to this object will affect the `\mbox{\rm\LaTeX}`
13679
version of the graph. For a full explanation of
13680
how to use LaTeX to render graphs, see the introduction to the
13681
:mod:`~sage.graphs.graph_latex` module.
13682
13683
EXAMPLES::
13684
13685
sage: g = graphs.PetersenGraph()
13686
sage: opts = g.latex_options()
13687
sage: opts
13688
LaTeX options for Petersen graph: {}
13689
sage: opts.set_option('tkz_style', 'Classic')
13690
sage: opts
13691
LaTeX options for Petersen graph: {'tkz_style': 'Classic'}
13692
"""
13693
if self._latex_opts is None:
13694
from sage.graphs.graph_latex import GraphLatex
13695
self._latex_opts = GraphLatex(self)
13696
return self._latex_opts
13697
13698
def set_latex_options(self, **kwds):
13699
r"""
13700
Sets multiple options for rendering a graph with LaTeX.
13701
13702
INPUTS:
13703
13704
- ``kwds`` - any number of option/value pairs to set many graph
13705
latex options at once (a variable number, in any
13706
order). Existing values are overwritten, new values are
13707
added. Existing values can be cleared by setting the value
13708
to ``None``. Possible options are documented at
13709
:meth:`sage.graphs.graph_latex.GraphLatex.set_option`.
13710
13711
This method is a convenience for setting the options of a graph
13712
directly on an instance of the graph. For a full explanation of
13713
how to use LaTeX to render graphs, see the introduction to the
13714
:mod:`~sage.graphs.graph_latex` module.
13715
13716
EXAMPLES::
13717
13718
sage: g = graphs.PetersenGraph()
13719
sage: g.set_latex_options(tkz_style = 'Welsh')
13720
sage: opts = g.latex_options()
13721
sage: opts.get_option('tkz_style')
13722
'Welsh'
13723
"""
13724
opts = self.latex_options()
13725
opts.set_options(**kwds)
13726
13727
13728
def layout(self, layout = None, pos = None, dim = 2, save_pos = False, **options):
13729
"""
13730
Returns a layout for the vertices of this graph.
13731
13732
INPUT:
13733
13734
- layout -- one of "acyclic", "circular", "ranked", "graphviz", "planar", "spring", or "tree"
13735
13736
- pos -- a dictionary of positions or None (the default)
13737
13738
- save_pos -- a boolean
13739
13740
- layout options -- (see below)
13741
13742
If ``layout=algorithm`` is specified, this algorithm is used
13743
to compute the positions.
13744
13745
Otherwise, if ``pos`` is specified, use the given positions.
13746
13747
Otherwise, try to fetch previously computed and saved positions.
13748
13749
Otherwise use the default layout (usually the spring layout)
13750
13751
If ``save_pos = True``, the layout is saved for later use.
13752
13753
EXAMPLES::
13754
13755
sage: g = digraphs.ButterflyGraph(1)
13756
sage: g.layout()
13757
{('1', 1): [2.50..., -0.545...],
13758
('0', 0): [2.22..., 0.832...],
13759
('1', 0): [1.12..., -0.830...],
13760
('0', 1): [0.833..., 0.543...]}
13761
13762
sage: 1+1
13763
2
13764
sage: x = g.layout(layout = "acyclic_dummy", save_pos = True)
13765
sage: x = {('1', 1): [41, 18], ('0', 0): [41, 90], ('1', 0): [140, 90], ('0', 1): [141, 18]}
13766
13767
{('1', 1): [41, 18], ('0', 0): [41, 90], ('1', 0): [140, 90], ('0', 1): [141, 18]}
13768
13769
13770
sage: g.layout(dim = 3)
13771
{('1', 1): [1.07..., -0.260..., 0.927...],
13772
('0', 0): [2.02..., 0.528..., 0.343...],
13773
('1', 0): [0.674..., -0.528..., -0.343...],
13774
('0', 1): [1.61..., 0.260..., -0.927...]}
13775
13776
Here is the list of all the available layout options::
13777
13778
sage: from sage.graphs.graph_plot import layout_options
13779
sage: list(sorted(layout_options.iteritems()))
13780
[('by_component', 'Whether to do the spring layout by connected component -- a boolean.'),
13781
('dim', 'The dimension of the layout -- 2 or 3.'),
13782
('heights', 'A dictionary mapping heights to the list of vertices at this height.'),
13783
('iterations', 'The number of times to execute the spring layout algorithm.'),
13784
('layout', 'A layout algorithm -- one of "acyclic", "circular", "ranked", "graphviz", "planar", "spring", or "tree".'),
13785
('prog', 'Which graphviz layout program to use -- one of "circo", "dot", "fdp", "neato", or "twopi".'),
13786
('save_pos', 'Whether or not to save the computed position for the graph.'),
13787
('spring', 'Use spring layout to finalize the current layout.'),
13788
('tree_orientation', 'The direction of tree branches -- "up" or "down".'),
13789
('tree_root', 'A vertex designation for drawing trees.')]
13790
13791
Some of them only apply to certain layout algorithms. For
13792
details, see :meth:`.layout_acyclic`, :meth:`.layout_planar`,
13793
:meth:`.layout_circular`, :meth:`.layout_spring`, ...
13794
13795
..warning: unknown optional arguments are silently ignored
13796
13797
..warning: ``graphviz`` and ``dot2tex`` are currently required
13798
to obtain a nice 'acyclic' layout. See
13799
:meth:`.layout_graphviz` for installation instructions.
13800
13801
A subclass may implement another layout algorithm `blah`, by
13802
implementing a method ``.layout_blah``. It may override
13803
the default layout by overriding :meth:`.layout_default`, and
13804
similarly override the predefined layouts.
13805
13806
TODO: use this feature for all the predefined graphs classes
13807
(like for the Petersen graph, ...), rather than systematically
13808
building the layout at construction time.
13809
"""
13810
if layout is None:
13811
if pos is None:
13812
pos = self.get_pos(dim = dim)
13813
if pos is None:
13814
layout = 'default'
13815
13816
if hasattr(self, "layout_%s"%layout):
13817
pos = getattr(self, "layout_%s"%layout)(dim = dim, **options)
13818
elif layout is not None:
13819
raise ValueError, "unknown layout algorithm: %s"%layout
13820
13821
if len(pos) < self.order():
13822
pos = self.layout_extend_randomly(pos, dim = dim)
13823
13824
if save_pos:
13825
self.set_pos(pos, dim = dim)
13826
return pos
13827
13828
13829
def layout_spring(self, by_component = True, **options):
13830
"""
13831
Computes a spring layout for this graph
13832
13833
INPUT:
13834
13835
- ``iterations`` -- a positive integer
13836
- ``dim`` -- 2 or 3 (default: 2)
13837
13838
OUTPUT: a dictionary mapping vertices to positions
13839
13840
Returns a layout computed by randomly arranging the vertices
13841
along the given heights
13842
13843
EXAMPLES::
13844
13845
sage: g = graphs.LadderGraph(3) #TODO!!!!
13846
sage: g.layout_spring()
13847
{0: [1.28..., -0.943...],
13848
1: [1.57..., -0.101...],
13849
2: [1.83..., 0.747...],
13850
3: [0.531..., -0.757...],
13851
4: [0.795..., 0.108...],
13852
5: [1.08..., 0.946...]}
13853
sage: g = graphs.LadderGraph(7)
13854
sage: g.plot(layout = "spring")
13855
"""
13856
return spring_layout_fast(self, by_component = by_component, **options)
13857
13858
layout_default = layout_spring
13859
13860
# if not isinstance(graph.get_pos(), dict):
13861
# if graph.is_planar():
13862
# graph.set_planar_positions()
13863
# else:
13864
# import sage.graphs.generic_graph_pyx as ggp
13865
# graph.set_pos(ggp.spring_layout_fast_split(graph, iterations=1000))
13866
13867
def layout_ranked(self, heights = None, dim = 2, spring = False, **options):
13868
"""
13869
Computes a ranked layout for this graph
13870
13871
INPUT:
13872
13873
- heights -- a dictionary mapping heights to the list of vertices at this height
13874
13875
OUTPUT: a dictionary mapping vertices to positions
13876
13877
Returns a layout computed by randomly arranging the vertices
13878
along the given heights
13879
13880
EXAMPLES::
13881
13882
sage: g = graphs.LadderGraph(3)
13883
sage: g.layout_ranked(heights = dict( (i,[i, i+3]) for i in range(3) ))
13884
{0: [0.668..., 0],
13885
1: [0.667..., 1],
13886
2: [0.677..., 2],
13887
3: [1.34..., 0],
13888
4: [1.33..., 1],
13889
5: [1.33..., 2]}
13890
sage: g = graphs.LadderGraph(7)
13891
sage: g.plot(layout = "ranked", heights = dict( (i,[i, i+7]) for i in range(7) ))
13892
"""
13893
assert heights is not None
13894
13895
from sage.misc.randstate import current_randstate
13896
random = current_randstate().python_random().random
13897
13898
if self.order() == 0:
13899
return {}
13900
13901
pos = {}
13902
mmax = max([len(ccc) for ccc in heights.values()])
13903
ymin = min(heights.keys())
13904
ymax = max(heights.keys())
13905
dist = ((ymax-ymin)/(mmax+1.0))
13906
for height in heights:
13907
num_xs = len(heights[height])
13908
if num_xs == 0:
13909
continue
13910
j = (mmax - num_xs)/2.0
13911
for k in range(num_xs):
13912
pos[heights[height][k]] = [ dist*(j+k+1) + random()*(dist*0.03) for i in range(dim-1) ] + [height]
13913
if spring:
13914
# This does not work that well in 2d, since the vertices on
13915
# the same level are unlikely to cross. It is also hard to
13916
# set a good equilibrium distance (parameter k in
13917
# run_spring). If k<1, the layout gets squished
13918
# horizontally. If k>1, then two adjacent vertices in
13919
# consecutive levels tend to be further away than desired.
13920
newpos = spring_layout_fast(self,
13921
vpos = pos,
13922
dim = dim,
13923
height = True,
13924
**options)
13925
# spring_layout_fast actually *does* touch the last coordinates
13926
# (conversion to floats + translation)
13927
# We restore back the original height.
13928
for x in self.vertices():
13929
newpos[x][dim-1] = pos[x][dim-1]
13930
pos = newpos
13931
return pos
13932
13933
def layout_extend_randomly(self, pos, dim = 2):
13934
"""
13935
Extends randomly a partial layout
13936
13937
INPUT:
13938
13939
- ``pos``: a dictionary mapping vertices to positions
13940
13941
OUTPUT: a dictionary mapping vertices to positions
13942
13943
The vertices not referenced in ``pos`` are assigned random
13944
positions within the box delimited by the other vertices.
13945
13946
EXAMPLES::
13947
13948
sage: H = digraphs.ButterflyGraph(1)
13949
sage: H.layout_extend_randomly({('0',0): (0,0), ('1',1): (1,1)})
13950
{('1', 1): (1, 1),
13951
('0', 0): (0, 0),
13952
('1', 0): [0.111..., 0.514...],
13953
('0', 1): [0.0446..., 0.332...]}
13954
"""
13955
assert dim == 2 # 3d not yet implemented
13956
from sage.misc.randstate import current_randstate
13957
random = current_randstate().python_random().random
13958
13959
xmin, xmax,ymin, ymax = self._layout_bounding_box(pos)
13960
13961
dx = xmax - xmin
13962
dy = ymax - ymin
13963
# Check each vertex position is in pos, add position
13964
# randomly within the plot range if none is defined
13965
for v in self:
13966
if not v in pos:
13967
pos[v] = [xmin + dx*random(), ymin + dy*random()]
13968
return pos
13969
13970
13971
def layout_circular(self, dim = 2, **options):
13972
"""
13973
Computes a circular layout for this graph
13974
13975
OUTPUT: a dictionary mapping vertices to positions
13976
13977
EXAMPLES::
13978
13979
sage: G = graphs.CirculantGraph(7,[1,3])
13980
sage: G.layout_circular()
13981
{0: [6.12...e-17, 1.0],
13982
1: [-0.78..., 0.62...],
13983
2: [-0.97..., -0.22...],
13984
3: [-0.43..., -0.90...],
13985
4: [0.43..., -0.90...],
13986
5: [0.97..., -0.22...],
13987
6: [0.78..., 0.62...]}
13988
sage: G.plot(layout = "circular")
13989
"""
13990
assert dim == 2, "3D circular layout not implemented"
13991
from math import sin, cos, pi
13992
verts = self.vertices()
13993
n = len(verts)
13994
pos = {}
13995
for i in range(n):
13996
x = float(cos((pi/2) + ((2*pi)/n)*i))
13997
y = float(sin((pi/2) + ((2*pi)/n)*i))
13998
pos[verts[i]] = [x,y]
13999
return pos
14000
14001
def layout_tree(self, tree_orientation = "down", tree_root = None, dim = 2, **options):
14002
"""
14003
Computes an ordered tree layout for this graph, which should
14004
be a tree (no non-oriented cycles).
14005
14006
INPUT:
14007
14008
- ``tree_root`` -- a vertex
14009
- ``tree_orientation`` -- "up" or "down"
14010
14011
OUTPUT: a dictionary mapping vertices to positions
14012
14013
EXAMPLES::
14014
14015
sage: G = graphs.BalancedTree(2,2)
14016
sage: G.layout_tree(tree_root = 0)
14017
{0: [1.0..., 2],
14018
1: [0.8..., 1],
14019
2: [1.2..., 1],
14020
3: [0.4..., 0],
14021
4: [0.8..., 0],
14022
5: [1.2..., 0],
14023
6: [1.6..., 0]}
14024
sage: G = graphs.BalancedTree(2,4)
14025
sage: G.plot(layout="tree", tree_root = 0, tree_orientation = "up")
14026
"""
14027
assert dim == 2, "3D tree layout not implemented"
14028
if not self.is_tree():
14029
raise RuntimeError("Cannot use tree layout on this graph: self.is_tree() returns False.")
14030
n = self.order()
14031
vertices = self.vertices()
14032
if tree_root is None:
14033
from sage.misc.prandom import randrange
14034
root = vertices[randrange(n)]
14035
else:
14036
root = tree_root
14037
# BFS search for heights
14038
seen = [root]
14039
queue = [root]
14040
heights = [-1]*n
14041
heights[vertices.index(root)] = 0
14042
while queue:
14043
u = queue.pop(0)
14044
for v in self.neighbors(u):
14045
if v not in seen:
14046
seen.append(v)
14047
queue.append(v)
14048
heights[vertices.index(v)] = heights[vertices.index(u)] + 1
14049
if tree_orientation == 'down':
14050
maxx = max(heights)
14051
heights = [maxx-heights[i] for i in xrange(n)]
14052
heights_dict = {}
14053
for v in vertices:
14054
if not heights_dict.has_key(heights[vertices.index(v)]):
14055
heights_dict[heights[vertices.index(v)]] = [v]
14056
else:
14057
heights_dict[heights[vertices.index(v)]].append(v)
14058
14059
return self.layout_ranked(heights_dict)
14060
14061
def layout_graphviz(self, dim = 2, prog = 'dot', **options):
14062
"""
14063
Calls ``graphviz`` to compute a layout of the vertices of this graph.
14064
14065
INPUT:
14066
14067
- ``prog`` -- one of "dot", "neato", "twopi", "circo", or "fdp"
14068
14069
EXAMPLES::
14070
14071
sage: g = digraphs.ButterflyGraph(2)
14072
sage: g.layout_graphviz() # optional - dot2tex, graphviz
14073
{('...', ...): [...,...],
14074
('...', ...): [...,...],
14075
('...', ...): [...,...],
14076
('...', ...): [...,...],
14077
('...', ...): [...,...],
14078
('...', ...): [...,...],
14079
('...', ...): [...,...],
14080
('...', ...): [...,...],
14081
('...', ...): [...,...],
14082
('...', ...): [...,...],
14083
('...', ...): [...,...],
14084
('...', ...): [...,...]}
14085
sage: g.plot(layout = "graphviz") # optional - dot2tex, graphviz
14086
14087
Note: the actual coordinates are not deterministic
14088
14089
By default, an acyclic layout is computed using ``graphviz``'s
14090
``dot`` layout program. One may specify an alternative layout
14091
program::
14092
14093
sage: g.plot(layout = "graphviz", prog = "dot") # optional - dot2tex, graphviz
14094
sage: g.plot(layout = "graphviz", prog = "neato") # optional - dot2tex, graphviz
14095
sage: g.plot(layout = "graphviz", prog = "twopi") # optional - dot2tex, graphviz
14096
sage: g.plot(layout = "graphviz", prog = "fdp") # optional - dot2tex, graphviz
14097
sage: g = graphs.BalancedTree(5,2)
14098
sage: g.plot(layout = "graphviz", prog = "circo") # optional - dot2tex, graphviz
14099
14100
TODO: put here some cool examples showcasing graphviz features.
14101
14102
This requires ``graphviz`` and the ``dot2tex`` spkg. Here are
14103
some installation tips:
14104
14105
- Install graphviz >= 2.14 so that the programs dot, neato, ...
14106
are in your path. The graphviz suite can be download from
14107
http://graphviz.org.
14108
14109
- Download dot2tex-2.8.?.spkg from http://trac.sagemath.org/sage_trac/ticket/7004
14110
and install it with ``sage -i dot2tex-2.8.?.spkg``
14111
14112
TODO: use the graphviz functionality of Networkx 1.0 once it
14113
will be merged into Sage.
14114
"""
14115
assert_have_dot2tex()
14116
assert dim == 2, "3D graphviz layout not implemented"
14117
14118
key = self._keys_for_vertices()
14119
key_to_vertex = dict( (key(v), v) for v in self )
14120
14121
import dot2tex
14122
positions = dot2tex.dot2tex(self.graphviz_string(**options), format = "positions", prog = prog)
14123
14124
return dict( (key_to_vertex[key], pos) for (key, pos) in positions.iteritems() )
14125
14126
def _layout_bounding_box(self, pos):
14127
"""
14128
INPUT:
14129
14130
- pos -- a dictionary of positions
14131
14132
Returns a bounding box around the specified positions
14133
14134
EXAMPLES::
14135
14136
sage: Graph()._layout_bounding_box( {} )
14137
[-1, 1, -1, 1]
14138
sage: Graph()._layout_bounding_box( {0: [3,5], 1: [2,7], 2: [-4,2] } )
14139
[-4, 3, 2, 7]
14140
sage: Graph()._layout_bounding_box( {0: [3,5], 1: [3.00000000001,4.999999999999999] } )
14141
[2, 4.00000000001000, 4.00000000000000, 6]
14142
"""
14143
xs = [pos[v][0] for v in pos]
14144
ys = [pos[v][1] for v in pos]
14145
if len(xs) == 0:
14146
xmin = -1
14147
xmax = 1
14148
ymin = -1
14149
ymax = 1
14150
else:
14151
xmin = min(xs)
14152
xmax = max(xs)
14153
ymin = min(ys)
14154
ymax = max(ys)
14155
14156
if xmax - xmin < 0.00000001:
14157
xmax += 1
14158
xmin -= 1
14159
14160
if ymax - ymin < 0.00000001:
14161
ymax += 1
14162
ymin -= 1
14163
14164
return [xmin, xmax, ymin, ymax]
14165
14166
14167
14168
@options(vertex_size=200, vertex_labels=True, layout=None,
14169
edge_style='solid', edge_color='black',edge_colors=None, edge_labels=False,
14170
iterations=50, tree_orientation='down', heights=None, graph_border=False,
14171
talk=False, color_by_label=False, partition=None,
14172
dist = .075, max_dist=1.5, loop_size=.075)
14173
def graphplot(self, **options):
14174
"""
14175
Returns a GraphPlot object.
14176
14177
14178
EXAMPLES:
14179
14180
Creating a graphplot object uses the same options as graph.plot()::
14181
14182
sage: g = Graph({}, loops=True, multiedges=True, sparse=True)
14183
sage: g.add_edges([(0,0,'a'),(0,0,'b'),(0,1,'c'),(0,1,'d'),
14184
... (0,1,'e'),(0,1,'f'),(0,1,'f'),(2,1,'g'),(2,2,'h')])
14185
sage: g.set_boundary([0,1])
14186
sage: GP = g.graphplot(edge_labels=True, color_by_label=True, edge_style='dashed')
14187
sage: GP.plot()
14188
14189
We can modify the graphplot object. Notice that the changes are cumulative::
14190
14191
sage: GP.set_edges(edge_style='solid')
14192
sage: GP.plot()
14193
sage: GP.set_vertices(talk=True)
14194
sage: GP.plot()
14195
"""
14196
from sage.graphs.graph_plot import GraphPlot
14197
return GraphPlot(graph=self, options=options)
14198
14199
@options(vertex_size=200, vertex_labels=True, layout=None,
14200
edge_style='solid', edge_color = 'black', edge_colors=None, edge_labels=False,
14201
iterations=50, tree_orientation='down', heights=None, graph_border=False,
14202
talk=False, color_by_label=False, partition=None,
14203
dist = .075, max_dist=1.5, loop_size=.075)
14204
def plot(self, **options):
14205
r"""
14206
Returns a graphics object representing the (di)graph.
14207
See also the :mod:`sage.graphs.graph_latex` module for ways
14208
to use `\mbox{\rm\LaTeX}` to produce an image of a graph.
14209
14210
INPUT:
14211
14212
- ``pos`` - an optional positioning dictionary
14213
14214
- ``layout`` - what kind of layout to use, takes precedence
14215
over pos
14216
14217
- 'circular' -- plots the graph with vertices evenly
14218
distributed on a circle
14219
14220
- 'spring' - uses the traditional spring layout, using the
14221
graph's current positions as initial positions
14222
14223
- 'tree' - the (di)graph must be a tree. One can specify
14224
the root of the tree using the keyword tree_root,
14225
otherwise a root will be selected at random. Then the
14226
tree will be plotted in levels, depending on minimum
14227
distance for the root.
14228
14229
- ``vertex_labels`` - whether to print vertex labels
14230
14231
- ``edge_labels`` - whether to print edge labels. By default,
14232
False, but if True, the result of str(l) is printed on the
14233
edge for each label l. Labels equal to None are not printed
14234
(to set edge labels, see set_edge_label).
14235
14236
- ``vertex_size`` - size of vertices displayed
14237
14238
- ``vertex_shape`` - the shape to draw the vertices (Not
14239
available for multiedge digraphs.)
14240
14241
- ``graph_border`` - whether to include a box around the graph
14242
14243
- ``vertex_colors`` - optional dictionary to specify vertex
14244
colors: each key is a color recognizable by matplotlib, and
14245
each corresponding entry is a list of vertices. If a vertex
14246
is not listed, it looks invisible on the resulting plot (it
14247
doesn't get drawn).
14248
14249
- ``edge_colors`` - a dictionary specifying edge colors: each
14250
key is a color recognized by matplotlib, and each entry is a
14251
list of edges.
14252
14253
- ``partition`` - a partition of the vertex set. if specified,
14254
plot will show each cell in a different color. vertex_colors
14255
takes precedence.
14256
14257
- ``talk`` - if true, prints large vertices with white
14258
backgrounds so that labels are legible on slides
14259
14260
- ``iterations`` - how many iterations of the spring layout
14261
algorithm to go through, if applicable
14262
14263
- ``color_by_label`` - if True, color edges by their labels
14264
14265
- ``heights`` - if specified, this is a dictionary from a set
14266
of floating point heights to a set of vertices
14267
14268
- ``edge_style`` - keyword arguments passed into the
14269
edge-drawing routine. This currently only works for
14270
directed graphs, since we pass off the undirected graph to
14271
networkx
14272
14273
- ``tree_root`` - a vertex of the tree to be used as the root
14274
for the layout="tree" option. If no root is specified, then one
14275
is chosen at random. Ignored unless layout='tree'.
14276
14277
- ``tree_orientation`` - "up" or "down" (default is "down").
14278
If "up" (resp., "down"), then the root of the tree will
14279
appear on the bottom (resp., top) and the tree will grow
14280
upwards (resp. downwards). Ignored unless layout='tree'.
14281
14282
- ``save_pos`` - save position computed during plotting
14283
14284
EXAMPLES::
14285
14286
sage: from sage.graphs.graph_plot import graphplot_options
14287
sage: list(sorted(graphplot_options.iteritems()))
14288
[...]
14289
14290
sage: from math import sin, cos, pi
14291
sage: P = graphs.PetersenGraph()
14292
sage: d = {'#FF0000':[0,5], '#FF9900':[1,6], '#FFFF00':[2,7], '#00FF00':[3,8], '#0000FF':[4,9]}
14293
sage: pos_dict = {}
14294
sage: for i in range(5):
14295
... x = float(cos(pi/2 + ((2*pi)/5)*i))
14296
... y = float(sin(pi/2 + ((2*pi)/5)*i))
14297
... pos_dict[i] = [x,y]
14298
...
14299
sage: for i in range(10)[5:]:
14300
... x = float(0.5*cos(pi/2 + ((2*pi)/5)*i))
14301
... y = float(0.5*sin(pi/2 + ((2*pi)/5)*i))
14302
... pos_dict[i] = [x,y]
14303
...
14304
sage: pl = P.plot(pos=pos_dict, vertex_colors=d)
14305
sage: pl.show()
14306
14307
::
14308
14309
sage: C = graphs.CubeGraph(8)
14310
sage: P = C.plot(vertex_labels=False, vertex_size=0, graph_border=True)
14311
sage: P.show()
14312
14313
::
14314
14315
sage: G = graphs.HeawoodGraph()
14316
sage: for u,v,l in G.edges():
14317
... G.set_edge_label(u,v,'(' + str(u) + ',' + str(v) + ')')
14318
sage: G.plot(edge_labels=True).show()
14319
14320
::
14321
14322
sage: D = DiGraph( { 0: [1, 10, 19], 1: [8, 2], 2: [3, 6], 3: [19, 4], 4: [17, 5], 5: [6, 15], 6: [7], 7: [8, 14], 8: [9], 9: [10, 13], 10: [11], 11: [12, 18], 12: [16, 13], 13: [14], 14: [15], 15: [16], 16: [17], 17: [18], 18: [19], 19: []} , sparse=True)
14323
sage: for u,v,l in D.edges():
14324
... D.set_edge_label(u,v,'(' + str(u) + ',' + str(v) + ')')
14325
sage: D.plot(edge_labels=True, layout='circular').show()
14326
14327
::
14328
14329
sage: from sage.plot.colors import rainbow
14330
sage: C = graphs.CubeGraph(5)
14331
sage: R = rainbow(5)
14332
sage: edge_colors = {}
14333
sage: for i in range(5):
14334
... edge_colors[R[i]] = []
14335
sage: for u,v,l in C.edges():
14336
... for i in range(5):
14337
... if u[i] != v[i]:
14338
... edge_colors[R[i]].append((u,v,l))
14339
sage: C.plot(vertex_labels=False, vertex_size=0, edge_colors=edge_colors).show()
14340
14341
::
14342
14343
sage: D = graphs.DodecahedralGraph()
14344
sage: Pi = [[6,5,15,14,7],[16,13,8,2,4],[12,17,9,3,1],[0,19,18,10,11]]
14345
sage: D.show(partition=Pi)
14346
14347
::
14348
14349
sage: G = graphs.PetersenGraph()
14350
sage: G.allow_loops(True)
14351
sage: G.add_edge(0,0)
14352
sage: G.show()
14353
14354
::
14355
14356
sage: D = DiGraph({0:[0,1], 1:[2], 2:[3]}, loops=True)
14357
sage: D.show()
14358
sage: D.show(edge_colors={(0,1,0):[(0,1,None),(1,2,None)],(0,0,0):[(2,3,None)]})
14359
14360
::
14361
14362
sage: pos = {0:[0.0, 1.5], 1:[-0.8, 0.3], 2:[-0.6, -0.8], 3:[0.6, -0.8], 4:[0.8, 0.3]}
14363
sage: g = Graph({0:[1], 1:[2], 2:[3], 3:[4], 4:[0]})
14364
sage: g.plot(pos=pos, layout='spring', iterations=0)
14365
14366
::
14367
14368
sage: G = Graph()
14369
sage: P = G.plot()
14370
sage: P.axes()
14371
False
14372
sage: G = DiGraph()
14373
sage: P = G.plot()
14374
sage: P.axes()
14375
False
14376
14377
::
14378
14379
sage: G = graphs.PetersenGraph()
14380
sage: G.get_pos()
14381
{0: (6.12..., 1.0...),
14382
1: (-0.95..., 0.30...),
14383
2: (-0.58..., -0.80...),
14384
3: (0.58..., -0.80...),
14385
4: (0.95..., 0.30...),
14386
5: (1.53..., 0.5...),
14387
6: (-0.47..., 0.15...),
14388
7: (-0.29..., -0.40...),
14389
8: (0.29..., -0.40...),
14390
9: (0.47..., 0.15...)}
14391
sage: P = G.plot(save_pos=True, layout='spring')
14392
14393
The following illustrates the format of a position dictionary.
14394
14395
sage: G.get_pos() # currently random across platforms, see #9593
14396
{0: [1.17..., -0.855...],
14397
1: [1.81..., -0.0990...],
14398
2: [1.35..., 0.184...],
14399
3: [1.51..., 0.644...],
14400
4: [2.00..., -0.507...],
14401
5: [0.597..., -0.236...],
14402
6: [2.04..., 0.687...],
14403
7: [1.46..., -0.473...],
14404
8: [0.902..., 0.773...],
14405
9: [2.48..., -0.119...]}
14406
14407
::
14408
14409
sage: T = list(graphs.trees(7))
14410
sage: t = T[3]
14411
sage: t.plot(heights={0:[0], 1:[4,5,1], 2:[2], 3:[3,6]})
14412
14413
::
14414
14415
sage: T = list(graphs.trees(7))
14416
sage: t = T[3]
14417
sage: t.plot(heights={0:[0], 1:[4,5,1], 2:[2], 3:[3,6]})
14418
sage: t.set_edge_label(0,1,-7)
14419
sage: t.set_edge_label(0,5,3)
14420
sage: t.set_edge_label(0,5,99)
14421
sage: t.set_edge_label(1,2,1000)
14422
sage: t.set_edge_label(3,2,'spam')
14423
sage: t.set_edge_label(2,6,3/2)
14424
sage: t.set_edge_label(0,4,66)
14425
sage: t.plot(heights={0:[0], 1:[4,5,1], 2:[2], 3:[3,6]}, edge_labels=True)
14426
14427
::
14428
14429
sage: T = list(graphs.trees(7))
14430
sage: t = T[3]
14431
sage: t.plot(layout='tree')
14432
14433
::
14434
14435
sage: t = DiGraph('JCC???@A??GO??CO??GO??')
14436
sage: t.plot(layout='tree', tree_root=0, tree_orientation="up")
14437
sage: D = DiGraph({0:[1,2,3], 2:[1,4], 3:[0]})
14438
sage: D.plot()
14439
14440
sage: D = DiGraph(multiedges=True,sparse=True)
14441
sage: for i in range(5):
14442
... D.add_edge((i,i+1,'a'))
14443
... D.add_edge((i,i-1,'b'))
14444
sage: D.plot(edge_labels=True,edge_colors=D._color_by_label())
14445
14446
sage: g = Graph({}, loops=True, multiedges=True,sparse=True)
14447
sage: g.add_edges([(0,0,'a'),(0,0,'b'),(0,1,'c'),(0,1,'d'),
14448
... (0,1,'e'),(0,1,'f'),(0,1,'f'),(2,1,'g'),(2,2,'h')])
14449
sage: g.plot(edge_labels=True, color_by_label=True, edge_style='dashed')
14450
14451
::
14452
14453
sage: S = SupersingularModule(389)
14454
sage: H = S.hecke_matrix(2)
14455
sage: D = DiGraph(H,sparse=True)
14456
sage: P = D.plot()
14457
14458
::
14459
14460
sage: G=Graph({'a':['a','b','b','b','e'],'b':['c','d','e'],'c':['c','d','d','d'],'d':['e']},sparse=True)
14461
sage: G.show(pos={'a':[0,1],'b':[1,1],'c':[2,0],'d':[1,0],'e':[0,0]})
14462
14463
"""
14464
from sage.graphs.graph_plot import GraphPlot
14465
return GraphPlot(graph=self, options=options).plot()
14466
14467
def show(self, **kwds):
14468
"""
14469
Shows the (di)graph.
14470
14471
For syntax and lengthy documentation, see G.plot?. Any options not
14472
used by plot will be passed on to the Graphics.show method.
14473
14474
EXAMPLES::
14475
14476
sage: C = graphs.CubeGraph(8)
14477
sage: P = C.plot(vertex_labels=False, vertex_size=0, graph_border=True)
14478
sage: P.show() # long time (3s on sage.math, 2011)
14479
"""
14480
kwds.setdefault('figsize', [4,4])
14481
from graph_plot import graphplot_options
14482
vars = graphplot_options.keys()
14483
plot_kwds = {}
14484
for kwd in vars:
14485
if kwds.has_key(kwd):
14486
plot_kwds[kwd] = kwds.pop(kwd)
14487
self.plot(**plot_kwds).show(**kwds)
14488
14489
def plot3d(self, bgcolor=(1,1,1), vertex_colors=None, vertex_size=0.06,
14490
edge_colors=None, edge_size=0.02, edge_size2=0.0325,
14491
pos3d=None, color_by_label=False,
14492
engine='jmol', **kwds):
14493
r"""
14494
Plot a graph in three dimensions. See also the
14495
:mod:`sage.graphs.graph_latex` module for ways to use
14496
`\mbox{\rm\LaTeX}` to produce an image of a graph.
14497
14498
INPUT:
14499
14500
14501
- ``bgcolor`` - rgb tuple (default: (1,1,1))
14502
14503
- ``vertex_size`` - float (default: 0.06)
14504
14505
- ``vertex_colors`` - optional dictionary to specify
14506
vertex colors: each key is a color recognizable by tachyon (rgb
14507
tuple (default: (1,0,0))), and each corresponding entry is a list
14508
of vertices. If a vertex is not listed, it looks invisible on the
14509
resulting plot (it doesn't get drawn).
14510
14511
- ``edge_colors`` - a dictionary specifying edge
14512
colors: each key is a color recognized by tachyon ( default:
14513
(0,0,0) ), and each entry is a list of edges.
14514
14515
- ``edge_size`` - float (default: 0.02)
14516
14517
- ``edge_size2`` - float (default: 0.0325), used for
14518
Tachyon sleeves
14519
14520
- ``pos3d`` - a position dictionary for the vertices
14521
14522
- ``layout``, ``iterations``, ... - layout options; see :meth:`.layout`
14523
14524
- ``engine`` - which renderer to use. Options:
14525
14526
- ``'jmol'`` - default
14527
14528
- ``'tachyon'``
14529
14530
- ``xres`` - resolution
14531
14532
- ``yres`` - resolution
14533
14534
- ``**kwds`` - passed on to the rendering engine
14535
14536
14537
EXAMPLES::
14538
14539
sage: G = graphs.CubeGraph(5)
14540
sage: G.plot3d(iterations=500, edge_size=None, vertex_size=0.04) # long time
14541
14542
We plot a fairly complicated Cayley graph::
14543
14544
sage: A5 = AlternatingGroup(5); A5
14545
Alternating group of order 5!/2 as a permutation group
14546
sage: G = A5.cayley_graph()
14547
sage: G.plot3d(vertex_size=0.03, edge_size=0.01, vertex_colors={(1,1,1):G.vertices()}, bgcolor=(0,0,0), color_by_label=True, iterations=200) # long time
14548
14549
Some Tachyon examples::
14550
14551
sage: D = graphs.DodecahedralGraph()
14552
sage: P3D = D.plot3d(engine='tachyon')
14553
sage: P3D.show() # long time
14554
14555
::
14556
14557
sage: G = graphs.PetersenGraph()
14558
sage: G.plot3d(engine='tachyon', vertex_colors={(0,0,1):G.vertices()}).show() # long time
14559
14560
::
14561
14562
sage: C = graphs.CubeGraph(4)
14563
sage: C.plot3d(engine='tachyon', edge_colors={(0,1,0):C.edges()}, vertex_colors={(1,1,1):C.vertices()}, bgcolor=(0,0,0)).show() # long time
14564
14565
::
14566
14567
sage: K = graphs.CompleteGraph(3)
14568
sage: K.plot3d(engine='tachyon', edge_colors={(1,0,0):[(0,1,None)], (0,1,0):[(0,2,None)], (0,0,1):[(1,2,None)]}).show() # long time
14569
14570
A directed version of the dodecahedron
14571
14572
::
14573
14574
sage: D = DiGraph( { 0: [1, 10, 19], 1: [8, 2], 2: [3, 6], 3: [19, 4], 4: [17, 5], 5: [6, 15], 6: [7], 7: [8, 14], 8: [9], 9: [10, 13], 10: [11], 11: [12, 18], 12: [16, 13], 13: [14], 14: [15], 15: [16], 16: [17], 17: [18], 18: [19], 19: []} )
14575
sage: D.plot3d().show() # long time
14576
14577
::
14578
14579
sage: P = graphs.PetersenGraph().to_directed()
14580
sage: from sage.plot.colors import rainbow
14581
sage: edges = P.edges()
14582
sage: R = rainbow(len(edges), 'rgbtuple')
14583
sage: edge_colors = {}
14584
sage: for i in range(len(edges)):
14585
... edge_colors[R[i]] = [edges[i]]
14586
sage: P.plot3d(engine='tachyon', edge_colors=edge_colors).show() # long time
14587
14588
14589
::
14590
14591
sage: G=Graph({'a':['a','b','b','b','e'],'b':['c','d','e'],'c':['c','d','d','d'],'d':['e']},sparse=True)
14592
sage: G.show3d()
14593
Traceback (most recent call last):
14594
...
14595
NotImplementedError: 3D plotting of multiple edges or loops not implemented.
14596
14597
"""
14598
import graph_plot
14599
layout_options = dict( (key,kwds[key]) for key in kwds.keys() if key in graph_plot.layout_options )
14600
kwds = dict( (key,kwds[key]) for key in kwds.keys() if key not in graph_plot.layout_options )
14601
if pos3d is None:
14602
pos3d = self.layout(dim=3, **layout_options)
14603
14604
if self.has_multiple_edges() or self.has_loops():
14605
raise NotImplementedError("3D plotting of multiple edges or loops not implemented.")
14606
if engine == 'jmol':
14607
from sage.plot.plot3d.all import sphere, line3d, arrow3d
14608
from sage.plot.plot3d.texture import Texture
14609
kwds.setdefault('aspect_ratio', [1,1,1])
14610
verts = self.vertices()
14611
14612
if vertex_colors is None:
14613
vertex_colors = { (1,0,0) : verts }
14614
14615
if color_by_label:
14616
if edge_colors is None:
14617
# do the coloring
14618
edge_colors = self._color_by_label(format='rgbtuple')
14619
elif edge_colors is None:
14620
edge_colors = { (0,0,0) : self.edges() }
14621
14622
# by default turn off the frame
14623
if not kwds.has_key('frame'):
14624
kwds['frame'] = False
14625
# by default make the background given by bgcolor
14626
if not kwds.has_key('background'):
14627
kwds['background'] = bgcolor
14628
try:
14629
graphic = 0
14630
for color in vertex_colors:
14631
texture = Texture(color=color, ambient=0.1, diffuse=0.9, specular=0.03)
14632
for v in vertex_colors[color]:
14633
graphic += sphere(center=pos3d[v], size=vertex_size, texture=texture, **kwds)
14634
if self._directed:
14635
for color in edge_colors:
14636
for u, v, l in edge_colors[color]:
14637
graphic += arrow3d(pos3d[u], pos3d[v], radius=edge_size, color=color, closed=False, **kwds)
14638
14639
else:
14640
for color in edge_colors:
14641
texture = Texture(color=color, ambient=0.1, diffuse=0.9, specular=0.03)
14642
for u, v, l in edge_colors[color]:
14643
graphic += line3d([pos3d[u], pos3d[v]], radius=edge_size, texture=texture, closed=False, **kwds)
14644
14645
return graphic
14646
14647
except KeyError:
14648
raise KeyError, "Oops! You haven't specified positions for all the vertices."
14649
14650
elif engine == 'tachyon':
14651
TT, pos3d = tachyon_vertex_plot(self, bgcolor=bgcolor, vertex_colors=vertex_colors,
14652
vertex_size=vertex_size, pos3d=pos3d, **kwds)
14653
edges = self.edges()
14654
14655
if color_by_label:
14656
if edge_colors is None:
14657
# do the coloring
14658
edge_colors = self._color_by_label(format='rgbtuple')
14659
14660
if edge_colors is None:
14661
edge_colors = { (0,0,0) : edges }
14662
14663
i = 0
14664
14665
for color in edge_colors:
14666
i += 1
14667
TT.texture('edge_color_%d'%i, ambient=0.1, diffuse=0.9, specular=0.03, opacity=1.0, color=color)
14668
if self._directed:
14669
for u,v,l in edge_colors[color]:
14670
TT.fcylinder( (pos3d[u][0],pos3d[u][1],pos3d[u][2]),
14671
(pos3d[v][0],pos3d[v][1],pos3d[v][2]), edge_size,'edge_color_%d'%i)
14672
TT.fcylinder( (0.25*pos3d[u][0] + 0.75*pos3d[v][0],
14673
0.25*pos3d[u][1] + 0.75*pos3d[v][1],
14674
0.25*pos3d[u][2] + 0.75*pos3d[v][2],),
14675
(pos3d[v][0],pos3d[v][1],pos3d[v][2]), edge_size2,'edge_color_%d'%i)
14676
else:
14677
for u, v, l in edge_colors[color]:
14678
TT.fcylinder( (pos3d[u][0],pos3d[u][1],pos3d[u][2]), (pos3d[v][0],pos3d[v][1],pos3d[v][2]), edge_size,'edge_color_%d'%i)
14679
14680
return TT
14681
14682
else:
14683
raise TypeError("Rendering engine (%s) not implemented."%engine)
14684
14685
def show3d(self, bgcolor=(1,1,1), vertex_colors=None, vertex_size=0.06,
14686
edge_colors=None, edge_size=0.02, edge_size2=0.0325,
14687
pos3d=None, color_by_label=False,
14688
engine='jmol', **kwds):
14689
"""
14690
Plots the graph using Tachyon, and shows the resulting plot.
14691
14692
INPUT:
14693
14694
14695
- ``bgcolor`` - rgb tuple (default: (1,1,1))
14696
14697
- ``vertex_size`` - float (default: 0.06)
14698
14699
- ``vertex_colors`` - optional dictionary to specify
14700
vertex colors: each key is a color recognizable by tachyon (rgb
14701
tuple (default: (1,0,0))), and each corresponding entry is a list
14702
of vertices. If a vertex is not listed, it looks invisible on the
14703
resulting plot (it doesn't get drawn).
14704
14705
- ``edge_colors`` - a dictionary specifying edge
14706
colors: each key is a color recognized by tachyon ( default:
14707
(0,0,0) ), and each entry is a list of edges.
14708
14709
- ``edge_size`` - float (default: 0.02)
14710
14711
- ``edge_size2`` - float (default: 0.0325), used for
14712
Tachyon sleeves
14713
14714
- ``pos3d`` - a position dictionary for the vertices
14715
14716
- ``iterations`` - how many iterations of the spring
14717
layout algorithm to go through, if applicable
14718
14719
- ``engine`` - which renderer to use. Options:
14720
14721
- ``'jmol'`` - default 'tachyon'
14722
14723
- ``xres`` - resolution
14724
14725
- ``yres`` - resolution
14726
14727
- ``**kwds`` - passed on to the Tachyon command
14728
14729
14730
EXAMPLES::
14731
14732
sage: G = graphs.CubeGraph(5)
14733
sage: G.show3d(iterations=500, edge_size=None, vertex_size=0.04) # long time
14734
14735
We plot a fairly complicated Cayley graph::
14736
14737
sage: A5 = AlternatingGroup(5); A5
14738
Alternating group of order 5!/2 as a permutation group
14739
sage: G = A5.cayley_graph()
14740
sage: G.show3d(vertex_size=0.03, edge_size=0.01, edge_size2=0.02, vertex_colors={(1,1,1):G.vertices()}, bgcolor=(0,0,0), color_by_label=True, iterations=200) # long time
14741
14742
Some Tachyon examples::
14743
14744
sage: D = graphs.DodecahedralGraph()
14745
sage: D.show3d(engine='tachyon') # long time
14746
14747
::
14748
14749
sage: G = graphs.PetersenGraph()
14750
sage: G.show3d(engine='tachyon', vertex_colors={(0,0,1):G.vertices()}) # long time
14751
14752
::
14753
14754
sage: C = graphs.CubeGraph(4)
14755
sage: C.show3d(engine='tachyon', edge_colors={(0,1,0):C.edges()}, vertex_colors={(1,1,1):C.vertices()}, bgcolor=(0,0,0)) # long time
14756
14757
::
14758
14759
sage: K = graphs.CompleteGraph(3)
14760
sage: K.show3d(engine='tachyon', edge_colors={(1,0,0):[(0,1,None)], (0,1,0):[(0,2,None)], (0,0,1):[(1,2,None)]}) # long time
14761
"""
14762
self.plot3d(bgcolor=bgcolor, vertex_colors=vertex_colors,
14763
edge_colors=edge_colors, vertex_size=vertex_size, engine=engine,
14764
edge_size=edge_size, edge_size2=edge_size2, pos3d=pos3d,
14765
color_by_label=color_by_label, **kwds).show()
14766
14767
def _keys_for_vertices(self):
14768
"""
14769
Returns a function mapping each vertex to a unique and hopefully
14770
readable string
14771
14772
EXAMPLE::
14773
14774
sage: g = graphs.Grid2dGraph(5,5)
14775
sage: g._keys_for_vertices()
14776
<function key at ...
14777
"""
14778
from sage.graphs.dot2tex_utils import key, key_with_hash
14779
if len(set(key(v) for v in self)) < self.num_verts():
14780
# There was a collision in the keys; we include a hash to be safe.
14781
return key_with_hash
14782
else:
14783
return key
14784
14785
### String representation to be used by other programs
14786
14787
@options(labels="string",
14788
vertex_labels=True,edge_labels=False,
14789
edge_color=None,edge_colors=None,
14790
edge_options = (),
14791
color_by_label=False)
14792
def graphviz_string(self, **options):
14793
r"""
14794
Returns a representation in the dot language.
14795
14796
The dot language is a text based format for graphs. It is used
14797
by the software suite graphviz. The specifications of the
14798
language are available on the web (see the reference [dotspec]_).
14799
14800
INPUT:
14801
14802
- ``labels`` - "string" or "latex" (default: "string"). If labels is
14803
string latex command are not interpreted. This option stands for both
14804
vertex labels and edge labels.
14805
14806
- ``vertex_labels`` - boolean (default: True) whether to add the labels
14807
on vertices.
14808
14809
- ``edge_labels`` - boolean (default: False) whether to add
14810
the labels on edges.
14811
14812
- ``edge_color`` - (default: None) specify a default color for the
14813
edges.
14814
14815
- ``edge_colors`` - (default: None) a dictionary whose keys
14816
are colors and values are list of edges. The list of edges need not to
14817
be complete in which case the default color is used.
14818
14819
- ``color_by_label`` - boolean (default: False): whether to
14820
color each edge with a different color according to its
14821
label. This overwrites the options ``edge_color`` and ``edge_colors``.
14822
14823
- ``edge_options`` - a function (or tuple thereof) mapping
14824
edges to a dictionary of options for this edge.
14825
14826
EXAMPLES::
14827
14828
sage: G = Graph({0:{1:None,2:None}, 1:{0:None,2:None}, 2:{0:None,1:None,3:'foo'}, 3:{2:'foo'}},sparse=True)
14829
sage: print G.graphviz_string(edge_labels=True)
14830
graph {
14831
"0" [label="0"];
14832
"1" [label="1"];
14833
"2" [label="2"];
14834
"3" [label="3"];
14835
<BLANKLINE>
14836
"0" -- "1";
14837
"0" -- "2";
14838
"1" -- "2";
14839
"2" -- "3" [label="foo"];
14840
}
14841
14842
A variant, with the labels in latex, for post-processing with ``dot2tex``::
14843
14844
sage: print G.graphviz_string(edge_labels=True,labels = "latex")
14845
graph {
14846
node [shape="plaintext"];
14847
"0" [label=" ", texlbl="$0$"];
14848
"1" [label=" ", texlbl="$1$"];
14849
"2" [label=" ", texlbl="$2$"];
14850
"3" [label=" ", texlbl="$3$"];
14851
<BLANKLINE>
14852
"0" -- "1";
14853
"0" -- "2";
14854
"1" -- "2";
14855
"2" -- "3" [label=" ", texlbl="$\verb|foo|$"];
14856
}
14857
14858
Same, with a digraph and a color for edges::
14859
14860
sage: G = DiGraph({0:{1:None,2:None}, 1:{2:None}, 2:{3:'foo'}, 3:{}} ,sparse=True)
14861
sage: print G.graphviz_string(edge_color="red")
14862
digraph {
14863
"0" [label="0"];
14864
"1" [label="1"];
14865
"2" [label="2"];
14866
"3" [label="3"];
14867
<BLANKLINE>
14868
edge [color="red"];
14869
"0" -> "1";
14870
"0" -> "2";
14871
"1" -> "2";
14872
"2" -> "3";
14873
}
14874
14875
A digraph using latex labels for vertices and edges::
14876
14877
sage: f(x) = -1/x
14878
sage: g(x) = 1/(x+1)
14879
sage: G = DiGraph()
14880
sage: G.add_edges([(i,f(i),f) for i in (1,2,1/2,1/4)])
14881
sage: G.add_edges([(i,g(i),g) for i in (1,2,1/2,1/4)])
14882
sage: print G.graphviz_string(labels="latex",edge_labels=True)
14883
digraph {
14884
node [shape="plaintext"];
14885
"2/3" [label=" ", texlbl="$\frac{2}{3}$"];
14886
"1/3" [label=" ", texlbl="$\frac{1}{3}$"];
14887
"1/2" [label=" ", texlbl="$\frac{1}{2}$"];
14888
"1" [label=" ", texlbl="$1$"];
14889
"1/4" [label=" ", texlbl="$\frac{1}{4}$"];
14890
"4/5" [label=" ", texlbl="$\frac{4}{5}$"];
14891
"-4" [label=" ", texlbl="$-4$"];
14892
"2" [label=" ", texlbl="$2$"];
14893
"-2" [label=" ", texlbl="$-2$"];
14894
"-1/2" [label=" ", texlbl="$-\frac{1}{2}$"];
14895
"-1" [label=" ", texlbl="$-1$"];
14896
<BLANKLINE>
14897
"1/2" -> "-2" [label=" ", texlbl="$x \ {\mapsto}\ -\frac{1}{x}$"];
14898
"1/2" -> "2/3" [label=" ", texlbl="$x \ {\mapsto}\ \frac{1}{x + 1}$"];
14899
"1" -> "-1" [label=" ", texlbl="$x \ {\mapsto}\ -\frac{1}{x}$"];
14900
"1" -> "1/2" [label=" ", texlbl="$x \ {\mapsto}\ \frac{1}{x + 1}$"];
14901
"1/4" -> "-4" [label=" ", texlbl="$x \ {\mapsto}\ -\frac{1}{x}$"];
14902
"1/4" -> "4/5" [label=" ", texlbl="$x \ {\mapsto}\ \frac{1}{x + 1}$"];
14903
"2" -> "-1/2" [label=" ", texlbl="$x \ {\mapsto}\ -\frac{1}{x}$"];
14904
"2" -> "1/3" [label=" ", texlbl="$x \ {\mapsto}\ \frac{1}{x + 1}$"];
14905
}
14906
14907
sage: print G.graphviz_string(labels="latex",color_by_label=True)
14908
digraph {
14909
node [shape="plaintext"];
14910
"2/3" [label=" ", texlbl="$\frac{2}{3}$"];
14911
"1/3" [label=" ", texlbl="$\frac{1}{3}$"];
14912
"1/2" [label=" ", texlbl="$\frac{1}{2}$"];
14913
"1" [label=" ", texlbl="$1$"];
14914
"1/4" [label=" ", texlbl="$\frac{1}{4}$"];
14915
"4/5" [label=" ", texlbl="$\frac{4}{5}$"];
14916
"-4" [label=" ", texlbl="$-4$"];
14917
"2" [label=" ", texlbl="$2$"];
14918
"-2" [label=" ", texlbl="$-2$"];
14919
"-1/2" [label=" ", texlbl="$-\frac{1}{2}$"];
14920
"-1" [label=" ", texlbl="$-1$"];
14921
<BLANKLINE>
14922
"1/2" -> "-2" [color = "#ff0000"];
14923
"1/2" -> "2/3" [color = "#00ffff"];
14924
"1" -> "-1" [color = "#ff0000"];
14925
"1" -> "1/2" [color = "#00ffff"];
14926
"1/4" -> "-4" [color = "#ff0000"];
14927
"1/4" -> "4/5" [color = "#00ffff"];
14928
"2" -> "-1/2" [color = "#ff0000"];
14929
"2" -> "1/3" [color = "#00ffff"];
14930
}
14931
14932
sage: print G.graphviz_string(labels="latex",color_by_label={ f: "red", g: "blue" })
14933
digraph {
14934
node [shape="plaintext"];
14935
"2/3" [label=" ", texlbl="$\frac{2}{3}$"];
14936
"1/3" [label=" ", texlbl="$\frac{1}{3}$"];
14937
"1/2" [label=" ", texlbl="$\frac{1}{2}$"];
14938
"1" [label=" ", texlbl="$1$"];
14939
"1/4" [label=" ", texlbl="$\frac{1}{4}$"];
14940
"4/5" [label=" ", texlbl="$\frac{4}{5}$"];
14941
"-4" [label=" ", texlbl="$-4$"];
14942
"2" [label=" ", texlbl="$2$"];
14943
"-2" [label=" ", texlbl="$-2$"];
14944
"-1/2" [label=" ", texlbl="$-\frac{1}{2}$"];
14945
"-1" [label=" ", texlbl="$-1$"];
14946
<BLANKLINE>
14947
"1/2" -> "-2" [color = "red"];
14948
"1/2" -> "2/3" [color = "blue"];
14949
"1" -> "-1" [color = "red"];
14950
"1" -> "1/2" [color = "blue"];
14951
"1/4" -> "-4" [color = "red"];
14952
"1/4" -> "4/5" [color = "blue"];
14953
"2" -> "-1/2" [color = "red"];
14954
"2" -> "1/3" [color = "blue"];
14955
}
14956
14957
Edge-specific options can also be specified by providing a
14958
function (or tuple thereof) which maps each edge to a
14959
dictionary of options. Valid options are "color", "backward"
14960
(a boolean), "dot" (a string containing a sequence of options
14961
in dot format), "label" (a string), "label_style" ("string" or
14962
"latex"), "edge_string" ("--" or "->"). Here we state that the
14963
graph should be laid out so that edges starting from ``1`` are
14964
going backward (e.g. going up instead of down)::
14965
14966
sage: def edge_options((u,v,label)):
14967
... return { "backward": u == 1 }
14968
sage: print G.graphviz_string(edge_options = edge_options)
14969
digraph {
14970
"2/3" [label="2/3"];
14971
"1/3" [label="1/3"];
14972
"1/2" [label="1/2"];
14973
"1" [label="1"];
14974
"1/4" [label="1/4"];
14975
"4/5" [label="4/5"];
14976
"-4" [label="-4"];
14977
"2" [label="2"];
14978
"-2" [label="-2"];
14979
"-1/2" [label="-1/2"];
14980
"-1" [label="-1"];
14981
<BLANKLINE>
14982
"1/2" -> "-2";
14983
"1/2" -> "2/3";
14984
"-1" -> "1" [dir=back];
14985
"1/2" -> "1" [dir=back];
14986
"1/4" -> "-4";
14987
"1/4" -> "4/5";
14988
"2" -> "-1/2";
14989
"2" -> "1/3";
14990
}
14991
14992
We now test all options::
14993
14994
sage: def edge_options((u,v,label)):
14995
... options = { "color": { f: "red", g: "blue" }[label] }
14996
... if (u,v) == (1/2, -2): options["label"] = "coucou"; options["label_style"] = "string"
14997
... if (u,v) == (1/2,2/3): options["dot"] = "x=1,y=2"
14998
... if (u,v) == (1, -1): options["label_style"] = "latex"
14999
... if (u,v) == (1, 1/2): options["edge_string"] = "<-"
15000
... if (u,v) == (1/2, 1): options["backward"] = True
15001
... return options
15002
sage: print G.graphviz_string(edge_options = edge_options)
15003
digraph {
15004
"2/3" [label="2/3"];
15005
"1/3" [label="1/3"];
15006
"1/2" [label="1/2"];
15007
"1" [label="1"];
15008
"1/4" [label="1/4"];
15009
"4/5" [label="4/5"];
15010
"-4" [label="-4"];
15011
"2" [label="2"];
15012
"-2" [label="-2"];
15013
"-1/2" [label="-1/2"];
15014
"-1" [label="-1"];
15015
<BLANKLINE>
15016
"1/2" -> "-2" [label="coucou", color = "red"];
15017
"1/2" -> "2/3" [x=1,y=2, color = "blue"];
15018
"1" -> "-1" [label=" ", texlbl="$x \ {\mapsto}\ -\frac{1}{x}$", color = "red"];
15019
"1" <- "1/2" [color = "blue"];
15020
"1/4" -> "-4" [color = "red"];
15021
"1/4" -> "4/5" [color = "blue"];
15022
"2" -> "-1/2" [color = "red"];
15023
"2" -> "1/3" [color = "blue"];
15024
}
15025
15026
TESTS:
15027
15028
The following digraph has tuples as vertices::
15029
15030
sage: print digraphs.ButterflyGraph(1).graphviz_string()
15031
digraph {
15032
"1,1" [label="('1', 1)"];
15033
"0,0" [label="('0', 0)"];
15034
"1,0" [label="('1', 0)"];
15035
"0,1" [label="('0', 1)"];
15036
<BLANKLINE>
15037
"0,0" -> "1,1";
15038
"0,0" -> "0,1";
15039
"1,0" -> "1,1";
15040
"1,0" -> "0,1";
15041
}
15042
15043
The following digraph has vertices with newlines in their
15044
string representations::
15045
15046
sage: m1 = matrix(3,3)
15047
sage: m2 = matrix(3,3, 1)
15048
sage: m1.set_immutable()
15049
sage: m2.set_immutable()
15050
sage: g = DiGraph({ m1: [m2] })
15051
sage: print g.graphviz_string()
15052
digraph {
15053
"000000000" [label="[0 0 0]\n\
15054
[0 0 0]\n\
15055
[0 0 0]"];
15056
"100010001" [label="[1 0 0]\n\
15057
[0 1 0]\n\
15058
[0 0 1]"];
15059
<BLANKLINE>
15060
"000000000" -> "100010001";
15061
}
15062
15063
REFERENCES:
15064
15065
.. [dotspec] http://www.graphviz.org/doc/info/lang.html
15066
15067
"""
15068
import re
15069
from sage.graphs.dot2tex_utils import quoted_latex, quoted_str
15070
15071
if self.is_directed():
15072
graph_string = "digraph"
15073
default_edge_string = "->"
15074
else:
15075
graph_string = "graph"
15076
default_edge_string = "--"
15077
15078
edge_option_functions = options['edge_options']
15079
if not isinstance(edge_option_functions, (tuple,list)):
15080
edge_option_functions = [edge_option_functions]
15081
else:
15082
edge_option_functions = list(edge_option_functions)
15083
15084
if options['edge_color'] is not None:
15085
default_color = options['edge_color']
15086
else:
15087
default_color = None
15088
15089
if options['color_by_label'] is not False:
15090
color_by_label = self._color_by_label(format = options['color_by_label'], as_function = True, default_color=default_color)
15091
edge_option_functions.append(lambda (u,v,label): {"color": color_by_label(label)})
15092
elif options['edge_colors'] is not None:
15093
if not isinstance(options['edge_colors'],dict):
15094
raise ValueError, "incorrect format for edge_colors"
15095
color_by_edge = {}
15096
for color in options['edge_colors'].keys():
15097
for edge in options['edge_colors'][color]:
15098
assert isinstance(edge, tuple) and len(edge) >= 2 and len(edge) <= 3,\
15099
"%s is not a valid format for edge"%(edge)
15100
u = edge[0]
15101
v = edge[1]
15102
assert self.has_edge(*edge), "%s is not an edge"%(edge)
15103
if len(edge) == 2:
15104
if self.has_multiple_edges():
15105
for label in self.edge_label(u,v):
15106
color_by_edge[(u,v,label)] = color
15107
else:
15108
label = self.edge_label(u,v)
15109
color_by_edge[(u,v,label)] = color
15110
elif len(edge) == 3:
15111
color_by_edge[edge] = color
15112
15113
edge_option_functions.append(lambda edge: {"color": color_by_edge[edge]} if edge in color_by_edge else {})
15114
15115
else:
15116
edges_by_color = []
15117
not_colored_edges = self.edge_iterator(labels=True)
15118
15119
key = self._keys_for_vertices()
15120
15121
s = '%s {\n' % graph_string
15122
if (options['vertex_labels'] and
15123
options['labels'] == "latex"): # not a perfect option name
15124
# TODO: why do we set this only for latex labels?
15125
s += ' node [shape="plaintext"];\n'
15126
for v in self.vertex_iterator():
15127
if not options['vertex_labels']:
15128
node_options = ""
15129
elif options['labels'] == "latex":
15130
node_options = " [label=\" \", texlbl=\"$%s$\"]"%quoted_latex(v)
15131
else:
15132
node_options = " [label=\"%s\"]" %quoted_str(v)
15133
15134
s += ' "%s"%s;\n'%(key(v),node_options)
15135
15136
s += "\n"
15137
if default_color is not None:
15138
s += 'edge [color="%s"];\n'%default_color
15139
15140
for (u,v,label) in self.edge_iterator():
15141
edge_options = {
15142
'backward': False,
15143
'dot': None,
15144
'edge_string': default_edge_string,
15145
'color' : default_color,
15146
'label' : label,
15147
'label_style': options['labels'] if options['edge_labels'] else None
15148
}
15149
for f in edge_option_functions:
15150
edge_options.update(f((u,v,label)))
15151
15152
dot_options = []
15153
15154
if edge_options['dot'] is not None:
15155
assert isinstance(edge_options['dot'], str)
15156
dot_options.append(edge_options['dot'])
15157
15158
label = edge_options['label']
15159
if label is not None and edge_options['label_style'] is not None:
15160
if edge_options['label_style'] == 'latex':
15161
dot_options.append('label=" ", texlbl="$%s$"'%quoted_latex(label))
15162
else:
15163
dot_options.append('label="%s"'% label)
15164
15165
if edge_options['color'] != default_color:
15166
dot_options.append('color = "%s"'%edge_options['color'])
15167
15168
if edge_options['backward']:
15169
v,u = u,v
15170
dot_options.append('dir=back')
15171
15172
s+= ' "%s" %s "%s"' % (key(u), edge_options['edge_string'], key(v))
15173
if len(dot_options) > 0:
15174
s += " [" + ", ".join(dot_options)+"]"
15175
s+= ";\n"
15176
s += "}"
15177
15178
return s
15179
15180
def graphviz_to_file_named(self, filename, **options):
15181
r"""
15182
Write a representation in the dot in a file.
15183
15184
The dot language is a plaintext format for graph structures. See the
15185
documentation of :meth:`.graphviz_string` for available options.
15186
15187
INPUT:
15188
15189
``filename`` - the name of the file to write in
15190
15191
``options`` - options for the graphviz string
15192
15193
EXAMPLES::
15194
15195
sage: G = Graph({0:{1:None,2:None}, 1:{0:None,2:None}, 2:{0:None,1:None,3:'foo'}, 3:{2:'foo'}},sparse=True)
15196
sage: tempfile = os.path.join(SAGE_TMP, 'temp_graphviz')
15197
sage: G.graphviz_to_file_named(tempfile, edge_labels=True)
15198
sage: print open(tempfile).read()
15199
graph {
15200
"0" [label="0"];
15201
"1" [label="1"];
15202
"2" [label="2"];
15203
"3" [label="3"];
15204
<BLANKLINE>
15205
"0" -- "1";
15206
"0" -- "2";
15207
"1" -- "2";
15208
"2" -- "3" [label="foo"];
15209
}
15210
"""
15211
return open(filename, 'wt').write(self.graphviz_string(**options))
15212
15213
### Spectrum
15214
15215
def spectrum(self, laplacian=False):
15216
r"""
15217
Returns a list of the eigenvalues of the adjacency matrix.
15218
15219
INPUT:
15220
15221
- ``laplacian`` - if ``True``, use the Laplacian matrix
15222
(see :meth:`kirchhoff_matrix`)
15223
15224
OUTPUT:
15225
15226
A list of the eigenvalues, including multiplicities, sorted
15227
with the largest eigenvalue first.
15228
15229
EXAMPLES::
15230
15231
sage: P = graphs.PetersenGraph()
15232
sage: P.spectrum()
15233
[3, 1, 1, 1, 1, 1, -2, -2, -2, -2]
15234
sage: P.spectrum(laplacian=True)
15235
[5, 5, 5, 5, 2, 2, 2, 2, 2, 0]
15236
sage: D = P.to_directed()
15237
sage: D.delete_edge(7,9)
15238
sage: D.spectrum()
15239
[2.9032119259..., 1, 1, 1, 1, 0.8060634335..., -1.7092753594..., -2, -2, -2]
15240
15241
::
15242
15243
sage: C = graphs.CycleGraph(8)
15244
sage: C.spectrum()
15245
[2, 1.4142135623..., 1.4142135623..., 0, 0, -1.4142135623..., -1.4142135623..., -2]
15246
15247
A digraph may have complex eigenvalues. Previously, the complex parts
15248
of graph eigenvalues were being dropped. For a 3-cycle, we have::
15249
15250
sage: T = DiGraph({0:[1], 1:[2], 2:[0]})
15251
sage: T.spectrum()
15252
[1, -0.5000000000... + 0.8660254037...*I, -0.5000000000... - 0.8660254037...*I]
15253
15254
TESTS:
15255
15256
The Laplacian matrix of a graph is the negative of the adjacency matrix with the degree of each vertex on the diagonal. So for a regular graph, if `\delta` is an eigenvalue of a regular graph of degree `r`, then `r-\delta` will be an eigenvalue of the Laplacian. The Hoffman-Singleton graph is regular of degree 7, so the following will test both the Laplacian construction and the computation of eigenvalues. ::
15257
15258
sage: H = graphs.HoffmanSingletonGraph()
15259
sage: evals = H.spectrum()
15260
sage: lap = map(lambda x : 7 - x, evals)
15261
sage: lap.sort(reverse=True)
15262
sage: lap == H.spectrum(laplacian=True)
15263
True
15264
"""
15265
# Ideally the spectrum should return something like a Factorization object
15266
# containing each eigenvalue once, along with its multiplicity.
15267
# This function, returning a list. could then just be renamed "eigenvalues"
15268
if laplacian:
15269
M = self.kirchhoff_matrix()
15270
else:
15271
M = self.adjacency_matrix()
15272
evals = M.eigenvalues()
15273
evals.sort(reverse=True)
15274
return evals
15275
15276
def characteristic_polynomial(self, var='x', laplacian=False):
15277
r"""
15278
Returns the characteristic polynomial of the adjacency matrix of
15279
the (di)graph.
15280
15281
Let `G` be a (simple) graph with adjacency matrix `A`. Let `I` be the
15282
identity matrix of dimensions the same as `A`. The characteristic
15283
polynomial of `G` is defined as the determinant `\det(xI - A)`.
15284
15285
.. note::
15286
15287
``characteristic_polynomial`` and ``charpoly`` are aliases and
15288
thus provide exactly the same method.
15289
15290
INPUT:
15291
15292
- ``x`` -- (default: ``'x'``) the variable of the characteristic
15293
polynomial.
15294
15295
- ``laplacian`` -- (default: ``False``) if ``True``, use the
15296
Laplacian matrix.
15297
15298
.. SEEALSO::
15299
15300
- :meth:`kirchhoff_matrix`
15301
15302
- :meth:`laplacian_matrix`
15303
15304
EXAMPLES::
15305
15306
sage: P = graphs.PetersenGraph()
15307
sage: P.characteristic_polynomial()
15308
x^10 - 15*x^8 + 75*x^6 - 24*x^5 - 165*x^4 + 120*x^3 + 120*x^2 - 160*x + 48
15309
sage: P.charpoly()
15310
x^10 - 15*x^8 + 75*x^6 - 24*x^5 - 165*x^4 + 120*x^3 + 120*x^2 - 160*x + 48
15311
sage: P.characteristic_polynomial(laplacian=True)
15312
x^10 - 30*x^9 + 390*x^8 - 2880*x^7 + 13305*x^6 -
15313
39882*x^5 + 77640*x^4 - 94800*x^3 + 66000*x^2 - 20000*x
15314
"""
15315
if laplacian:
15316
return self.kirchhoff_matrix().charpoly(var=var)
15317
else:
15318
return self.adjacency_matrix().charpoly(var=var)
15319
15320
# alias, consistent with linear algebra code
15321
charpoly = characteristic_polynomial
15322
15323
def eigenvectors(self, laplacian=False):
15324
r"""
15325
Returns the *right* eigenvectors of the adjacency matrix of the graph.
15326
15327
INPUT:
15328
15329
- ``laplacian`` - if True, use the Laplacian matrix
15330
(see :meth:`kirchhoff_matrix`)
15331
15332
OUTPUT:
15333
15334
A list of triples. Each triple begins with an eigenvalue of
15335
the adjacency matrix of the graph. This is followed by
15336
a list of eigenvectors for the eigenvalue, when the
15337
eigenvectors are placed on the right side of the matrix.
15338
Together, the eigenvectors form a basis for the eigenspace.
15339
The triple concludes with the algebraic multiplicity of
15340
the eigenvalue.
15341
15342
For some graphs, the exact eigenspaces provided by
15343
:meth:`eigenspaces` provide additional insight into
15344
the structure of the eigenspaces.
15345
15346
EXAMPLES::
15347
15348
sage: P = graphs.PetersenGraph()
15349
sage: P.eigenvectors()
15350
[(3, [
15351
(1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
15352
], 1), (-2, [
15353
(1, 0, 0, 0, -1, -1, -1, 0, 1, 1),
15354
(0, 1, 0, 0, -1, 0, -2, -1, 1, 2),
15355
(0, 0, 1, 0, -1, 1, -1, -2, 0, 2),
15356
(0, 0, 0, 1, -1, 1, 0, -1, -1, 1)
15357
], 4), (1, [
15358
(1, 0, 0, 0, 0, 1, -1, 0, 0, -1),
15359
(0, 1, 0, 0, 0, -1, 1, -1, 0, 0),
15360
(0, 0, 1, 0, 0, 0, -1, 1, -1, 0),
15361
(0, 0, 0, 1, 0, 0, 0, -1, 1, -1),
15362
(0, 0, 0, 0, 1, -1, 0, 0, -1, 1)
15363
], 5)]
15364
15365
Eigenspaces for the Laplacian should be identical since the
15366
Petersen graph is regular. However, since the output also
15367
contains the eigenvalues, the two outputs are slightly
15368
different. ::
15369
15370
sage: P.eigenvectors(laplacian=True)
15371
[(0, [
15372
(1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
15373
], 1), (5, [
15374
(1, 0, 0, 0, -1, -1, -1, 0, 1, 1),
15375
(0, 1, 0, 0, -1, 0, -2, -1, 1, 2),
15376
(0, 0, 1, 0, -1, 1, -1, -2, 0, 2),
15377
(0, 0, 0, 1, -1, 1, 0, -1, -1, 1)
15378
], 4), (2, [
15379
(1, 0, 0, 0, 0, 1, -1, 0, 0, -1),
15380
(0, 1, 0, 0, 0, -1, 1, -1, 0, 0),
15381
(0, 0, 1, 0, 0, 0, -1, 1, -1, 0),
15382
(0, 0, 0, 1, 0, 0, 0, -1, 1, -1),
15383
(0, 0, 0, 0, 1, -1, 0, 0, -1, 1)
15384
], 5)]
15385
15386
::
15387
15388
sage: C = graphs.CycleGraph(8)
15389
sage: C.eigenvectors()
15390
[(2, [
15391
(1, 1, 1, 1, 1, 1, 1, 1)
15392
], 1), (-2, [
15393
(1, -1, 1, -1, 1, -1, 1, -1)
15394
], 1), (0, [
15395
(1, 0, -1, 0, 1, 0, -1, 0),
15396
(0, 1, 0, -1, 0, 1, 0, -1)
15397
], 2), (-1.4142135623..., [(1, 0, -1, 1.4142135623..., -1, 0, 1, -1.4142135623...), (0, 1, -1.4142135623..., 1, 0, -1, 1.4142135623..., -1)], 2), (1.4142135623..., [(1, 0, -1, -1.4142135623..., -1, 0, 1, 1.4142135623...), (0, 1, 1.4142135623..., 1, 0, -1, -1.4142135623..., -1)], 2)]
15398
15399
A digraph may have complex eigenvalues. Previously, the complex parts
15400
of graph eigenvalues were being dropped. For a 3-cycle, we have::
15401
15402
sage: T = DiGraph({0:[1], 1:[2], 2:[0]})
15403
sage: T.eigenvectors()
15404
[(1, [
15405
(1, 1, 1)
15406
], 1), (-0.5000000000... - 0.8660254037...*I, [(1, -0.5000000000... - 0.8660254037...*I, -0.5000000000... + 0.8660254037...*I)], 1), (-0.5000000000... + 0.8660254037...*I, [(1, -0.5000000000... + 0.8660254037...*I, -0.5000000000... - 0.8660254037...*I)], 1)]
15407
"""
15408
if laplacian:
15409
M = self.kirchhoff_matrix()
15410
else:
15411
M = self.adjacency_matrix()
15412
return M.right_eigenvectors()
15413
15414
def eigenspaces(self, laplacian=False):
15415
r"""
15416
Returns the *right* eigenspaces of the adjacency matrix of the graph.
15417
15418
INPUT:
15419
15420
- ``laplacian`` - if True, use the Laplacian matrix
15421
(see :meth:`kirchhoff_matrix`)
15422
15423
OUTPUT:
15424
15425
A list of pairs. Each pair is an eigenvalue of the
15426
adjacency matrix of the graph, followed by
15427
the vector space that is the eigenspace for that eigenvalue,
15428
when the eigenvectors are placed on the right of the matrix.
15429
15430
For some graphs, some of the the eigenspaces are described
15431
exactly by vector spaces over a
15432
:class:`~sage.rings.number_field.number_field.NumberField`.
15433
For numerical eigenvectors use :meth:`eigenvectors`.
15434
15435
EXAMPLES::
15436
15437
sage: P = graphs.PetersenGraph()
15438
sage: P.eigenspaces()
15439
[
15440
(3, Vector space of degree 10 and dimension 1 over Rational Field
15441
User basis matrix:
15442
[1 1 1 1 1 1 1 1 1 1]),
15443
(-2, Vector space of degree 10 and dimension 4 over Rational Field
15444
User basis matrix:
15445
[ 1 0 0 0 -1 -1 -1 0 1 1]
15446
[ 0 1 0 0 -1 0 -2 -1 1 2]
15447
[ 0 0 1 0 -1 1 -1 -2 0 2]
15448
[ 0 0 0 1 -1 1 0 -1 -1 1]),
15449
(1, Vector space of degree 10 and dimension 5 over Rational Field
15450
User basis matrix:
15451
[ 1 0 0 0 0 1 -1 0 0 -1]
15452
[ 0 1 0 0 0 -1 1 -1 0 0]
15453
[ 0 0 1 0 0 0 -1 1 -1 0]
15454
[ 0 0 0 1 0 0 0 -1 1 -1]
15455
[ 0 0 0 0 1 -1 0 0 -1 1])
15456
]
15457
15458
Eigenspaces for the Laplacian should be identical since the
15459
Petersen graph is regular. However, since the output also
15460
contains the eigenvalues, the two outputs are slightly
15461
different. ::
15462
15463
sage: P.eigenspaces(laplacian=True)
15464
[
15465
(0, Vector space of degree 10 and dimension 1 over Rational Field
15466
User basis matrix:
15467
[1 1 1 1 1 1 1 1 1 1]),
15468
(5, Vector space of degree 10 and dimension 4 over Rational Field
15469
User basis matrix:
15470
[ 1 0 0 0 -1 -1 -1 0 1 1]
15471
[ 0 1 0 0 -1 0 -2 -1 1 2]
15472
[ 0 0 1 0 -1 1 -1 -2 0 2]
15473
[ 0 0 0 1 -1 1 0 -1 -1 1]),
15474
(2, Vector space of degree 10 and dimension 5 over Rational Field
15475
User basis matrix:
15476
[ 1 0 0 0 0 1 -1 0 0 -1]
15477
[ 0 1 0 0 0 -1 1 -1 0 0]
15478
[ 0 0 1 0 0 0 -1 1 -1 0]
15479
[ 0 0 0 1 0 0 0 -1 1 -1]
15480
[ 0 0 0 0 1 -1 0 0 -1 1])
15481
]
15482
15483
Notice how one eigenspace below is described with a square root of
15484
2. For the two possible values (positive and negative) there is a
15485
corresponding eigenspace. ::
15486
15487
sage: C = graphs.CycleGraph(8)
15488
sage: C.eigenspaces()
15489
[
15490
(2, Vector space of degree 8 and dimension 1 over Rational Field
15491
User basis matrix:
15492
[1 1 1 1 1 1 1 1]),
15493
(-2, Vector space of degree 8 and dimension 1 over Rational Field
15494
User basis matrix:
15495
[ 1 -1 1 -1 1 -1 1 -1]),
15496
(0, Vector space of degree 8 and dimension 2 over Rational Field
15497
User basis matrix:
15498
[ 1 0 -1 0 1 0 -1 0]
15499
[ 0 1 0 -1 0 1 0 -1]),
15500
(a3, Vector space of degree 8 and dimension 2 over Number Field in a3 with defining polynomial x^2 - 2
15501
User basis matrix:
15502
[ 1 0 -1 -a3 -1 0 1 a3]
15503
[ 0 1 a3 1 0 -1 -a3 -1])
15504
]
15505
15506
A digraph may have complex eigenvalues and eigenvectors.
15507
For a 3-cycle, we have::
15508
15509
sage: T = DiGraph({0:[1], 1:[2], 2:[0]})
15510
sage: T.eigenspaces()
15511
[
15512
(1, Vector space of degree 3 and dimension 1 over Rational Field
15513
User basis matrix:
15514
[1 1 1]),
15515
(a1, Vector space of degree 3 and dimension 1 over Number Field in a1 with defining polynomial x^2 + x + 1
15516
User basis matrix:
15517
[ 1 a1 -a1 - 1])
15518
]
15519
"""
15520
if laplacian:
15521
M = self.kirchhoff_matrix()
15522
else:
15523
M = self.adjacency_matrix()
15524
# could pass format='all' to get QQbar eigenvalues and eigenspaces
15525
# which would be a change in default behavior
15526
return M.right_eigenspaces(format='galois', algebraic_multiplicity=False)
15527
15528
### Automorphism and isomorphism
15529
15530
def relabel(self, perm=None, inplace=True, return_map=False):
15531
r"""
15532
Relabels the vertices of ``self``
15533
15534
INPUT:
15535
15536
- ``perm`` -- a function, dictionary, list, permutation, or
15537
``None`` (default: ``None``)
15538
15539
- ``inplace`` -- a boolean (default: ``True``)
15540
15541
- ``return_map`` -- a boolean (default: ``False``)
15542
15543
If ``perm`` is a function ``f``, then each vertex ``v`` is
15544
relabeled to ``f(v)``.
15545
15546
If ``perm`` is a dictionary ``d``, then each vertex ``v``
15547
(which should be a key of ``d``) is relabeled to ``d[v]``.
15548
Similarly, if ``perm`` is a list or tuple ``l`` of length
15549
``n``, then each vertex (which should be in `\{0,1,...,n-1\}`)
15550
is relabeled to ``l[v]``.
15551
15552
If ``perm`` is a permutation, then each vertex ``v`` is
15553
relabeled to ``perm(v)``. Caveat: this assumes that the
15554
vertices are labelled `\{0,1,...,n-1\}`; since permutations
15555
act by default on the set `\{1,2,...,n\}`, this is achieved by
15556
identifying `n` and `0`.
15557
15558
If ``perm`` is ``None``, the graph is relabeled to be on the
15559
vertices `\{0,1,...,n-1\}`.
15560
15561
.. note:: at this point, only injective relabeling are supported.
15562
15563
If ``inplace`` is ``True``, the graph is modified in place and
15564
``None`` is returned. Otherwise a relabeled copy of the graph
15565
is returned.
15566
15567
If ``return_map`` is ``True`` a dictionary representing the
15568
relabelling map is returned (incompatible with ``inplace==False``).
15569
15570
15571
EXAMPLES::
15572
15573
sage: G = graphs.PathGraph(3)
15574
sage: G.am()
15575
[0 1 0]
15576
[1 0 1]
15577
[0 1 0]
15578
15579
Relabeling using a dictionary::
15580
15581
sage: G.relabel({1:2,2:1}, inplace=False).am()
15582
[0 0 1]
15583
[0 0 1]
15584
[1 1 0]
15585
15586
Relabeling using a list::
15587
15588
sage: G.relabel([0,2,1], inplace=False).am()
15589
[0 0 1]
15590
[0 0 1]
15591
[1 1 0]
15592
15593
Relabeling using a tuple::
15594
15595
sage: G.relabel((0,2,1), inplace=False).am()
15596
[0 0 1]
15597
[0 0 1]
15598
[1 1 0]
15599
15600
Relabeling using a Sage permutation::
15601
15602
sage: from sage.groups.perm_gps.permgroup_named import SymmetricGroup
15603
sage: S = SymmetricGroup(3)
15604
sage: gamma = S('(1,2)')
15605
sage: G.relabel(gamma, inplace=False).am()
15606
[0 0 1]
15607
[0 0 1]
15608
[1 1 0]
15609
15610
Relabeling using an injective function::
15611
15612
sage: G.edges()
15613
[(0, 1, None), (1, 2, None)]
15614
sage: H = G.relabel(lambda i: i+10, inplace=False)
15615
sage: H.vertices()
15616
[10, 11, 12]
15617
sage: H.edges()
15618
[(10, 11, None), (11, 12, None)]
15619
15620
Relabeling using a non injective function is not yet supported::
15621
15622
sage: G.edges()
15623
[(0, 1, None), (1, 2, None)]
15624
sage: G.relabel(lambda i: 0, inplace=False)
15625
Traceback (most recent call last):
15626
...
15627
NotImplementedError: Non injective relabeling
15628
15629
Relabeling to simpler labels::
15630
15631
sage: G = graphs.CubeGraph(3)
15632
sage: G.vertices()
15633
['000', '001', '010', '011', '100', '101', '110', '111']
15634
sage: G.relabel()
15635
sage: G.vertices()
15636
[0, 1, 2, 3, 4, 5, 6, 7]
15637
15638
Recovering the relabeling with ``return_map``::
15639
15640
sage: G = graphs.CubeGraph(3)
15641
sage: expecting = {'000': 0, '001': 1, '010': 2, '011': 3, '100': 4, '101': 5, '110': 6, '111': 7}
15642
sage: G.relabel(return_map=True) == expecting
15643
True
15644
15645
::
15646
15647
sage: G = graphs.PathGraph(3)
15648
sage: G.relabel(lambda i: i+10, return_map=True)
15649
{0: 10, 1: 11, 2: 12}
15650
15651
TESTS::
15652
15653
sage: P = Graph(graphs.PetersenGraph())
15654
sage: P.delete_edge([0,1])
15655
sage: P.add_edge((4,5))
15656
sage: P.add_edge((2,6))
15657
sage: P.delete_vertices([0,1])
15658
sage: P.relabel()
15659
15660
The attributes are properly updated too
15661
15662
::
15663
15664
sage: G = graphs.PathGraph(5)
15665
sage: G.set_vertices({0: 'before', 1: 'delete', 2: 'after'})
15666
sage: G.set_boundary([1,2,3])
15667
sage: G.delete_vertex(1)
15668
sage: G.relabel()
15669
sage: G.get_vertices()
15670
{0: 'before', 1: 'after', 2: None, 3: None}
15671
sage: G.get_boundary()
15672
[1, 2]
15673
sage: G.get_pos()
15674
{0: (0, 0), 1: (2, 0), 2: (3, 0), 3: (4, 0)}
15675
15676
Check that #12477 is fixed::
15677
15678
sage: g = Graph({1:[2,3]})
15679
sage: rel = {1:'a', 2:'b'}
15680
sage: g.relabel(rel)
15681
sage: g.vertices()
15682
[3, 'a', 'b']
15683
sage: rel
15684
{1: 'a', 2: 'b'}
15685
"""
15686
from sage.groups.perm_gps.permgroup_element import PermutationGroupElement
15687
15688
# If perm is not a dictionary, we build one !
15689
15690
if perm is None:
15691
verts = self.vertices() # vertices() returns a sorted list:
15692
perm = {}; i = 0 # this guarantees consistent relabeling
15693
for v in verts:
15694
perm[v] = i
15695
i += 1
15696
15697
elif isinstance(perm, dict):
15698
15699
# If all vertices do not have a new label, the code will touch the
15700
# dictionary. Let us keep the one we received from the user clean !
15701
from copy import copy
15702
perm = copy(perm)
15703
15704
elif isinstance(perm, (list, tuple)):
15705
perm = dict( [ [i,perm[i]] for i in xrange(len(perm)) ] )
15706
15707
elif isinstance(perm, PermutationGroupElement):
15708
n = self.order()
15709
ddict = {}
15710
llist = perm.list()
15711
for i in xrange(1,n):
15712
ddict[i] = llist[i-1]%n
15713
if n > 0:
15714
ddict[0] = llist[n-1]%n
15715
perm = ddict
15716
15717
elif callable(perm):
15718
perm = dict( [ i, perm(i) ] for i in self.vertices() )
15719
15720
else:
15721
raise TypeError("Type of perm is not supported for relabeling.")
15722
15723
if not inplace:
15724
from copy import copy
15725
G = copy(self)
15726
G.relabel(perm)
15727
if return_map:
15728
return G, perm
15729
return G
15730
15731
keys = perm.keys()
15732
verts = self.vertices()
15733
if len(set(perm.values())) < len(keys):
15734
raise NotImplementedError, "Non injective relabeling"
15735
for v in verts:
15736
if v not in keys:
15737
perm[v] = v
15738
for v in perm.iterkeys():
15739
if v in verts:
15740
try:
15741
hash(perm[v])
15742
except TypeError:
15743
raise ValueError("perm dictionary must be of the format {a:a1, b:b1, ...} where a,b,... are vertices and a1,b1,... are hashable")
15744
self._backend.relabel(perm, self._directed)
15745
15746
attributes_to_update = ('_pos', '_assoc', '_embedding')
15747
for attr in attributes_to_update:
15748
if hasattr(self, attr) and getattr(self, attr) is not None:
15749
new_attr = {}
15750
for v,value in getattr(self, attr).iteritems():
15751
new_attr[perm[v]] = value
15752
15753
setattr(self, attr, new_attr)
15754
15755
self._boundary = [perm[v] for v in self._boundary]
15756
15757
if return_map:
15758
return perm
15759
15760
def degree_to_cell(self, vertex, cell):
15761
"""
15762
Returns the number of edges from vertex to an edge in cell. In the
15763
case of a digraph, returns a tuple (in_degree, out_degree).
15764
15765
EXAMPLES::
15766
15767
sage: G = graphs.CubeGraph(3)
15768
sage: cell = G.vertices()[:3]
15769
sage: G.degree_to_cell('011', cell)
15770
2
15771
sage: G.degree_to_cell('111', cell)
15772
0
15773
15774
::
15775
15776
sage: D = DiGraph({ 0:[1,2,3], 1:[3,4], 3:[4,5]})
15777
sage: cell = [0,1,2]
15778
sage: D.degree_to_cell(5, cell)
15779
(0, 0)
15780
sage: D.degree_to_cell(3, cell)
15781
(2, 0)
15782
sage: D.degree_to_cell(0, cell)
15783
(0, 2)
15784
"""
15785
if self._directed:
15786
in_neighbors_in_cell = set([a for a,_,_ in self.incoming_edges(vertex)]) & set(cell)
15787
out_neighbors_in_cell = set([a for _,a,_ in self.outgoing_edges(vertex)]) & set(cell)
15788
return (len(in_neighbors_in_cell), len(out_neighbors_in_cell))
15789
else:
15790
neighbors_in_cell = set(self.neighbors(vertex)) & set(cell)
15791
return len(neighbors_in_cell)
15792
15793
def is_equitable(self, partition, quotient_matrix=False):
15794
"""
15795
Checks whether the given partition is equitable with respect to
15796
self.
15797
15798
A partition is equitable with respect to a graph if for every pair
15799
of cells C1, C2 of the partition, the number of edges from a vertex
15800
of C1 to C2 is the same, over all vertices in C1.
15801
15802
INPUT:
15803
15804
15805
- ``partition`` - a list of lists
15806
15807
- ``quotient_matrix`` - (default False) if True, and
15808
the partition is equitable, returns a matrix over the integers
15809
whose rows and columns represent cells of the partition, and whose
15810
i,j entry is the number of vertices in cell j adjacent to each
15811
vertex in cell i (since the partition is equitable, this is well
15812
defined)
15813
15814
15815
EXAMPLES::
15816
15817
sage: G = graphs.PetersenGraph()
15818
sage: G.is_equitable([[0,4],[1,3,5,9],[2,6,8],[7]])
15819
False
15820
sage: G.is_equitable([[0,4],[1,3,5,9],[2,6,8,7]])
15821
True
15822
sage: G.is_equitable([[0,4],[1,3,5,9],[2,6,8,7]], quotient_matrix=True)
15823
[1 2 0]
15824
[1 0 2]
15825
[0 2 1]
15826
15827
::
15828
15829
sage: ss = (graphs.WheelGraph(6)).line_graph(labels=False)
15830
sage: prt = [[(0, 1)], [(0, 2), (0, 3), (0, 4), (1, 2), (1, 4)], [(2, 3), (3, 4)]]
15831
15832
::
15833
15834
sage: ss.is_equitable(prt)
15835
Traceback (most recent call last):
15836
...
15837
TypeError: Partition ([[(0, 1)], [(0, 2), (0, 3), (0, 4), (1, 2), (1, 4)], [(2, 3), (3, 4)]]) is not valid for this graph: vertices are incorrect.
15838
15839
::
15840
15841
sage: ss = (graphs.WheelGraph(5)).line_graph(labels=False)
15842
sage: ss.is_equitable(prt)
15843
False
15844
"""
15845
from sage.misc.flatten import flatten
15846
from sage.misc.misc import uniq
15847
if sorted(flatten(partition, max_level=1)) != self.vertices():
15848
raise TypeError("Partition (%s) is not valid for this graph: vertices are incorrect."%partition)
15849
if any(len(cell)==0 for cell in partition):
15850
raise TypeError("Partition (%s) is not valid for this graph: there is a cell of length 0."%partition)
15851
if quotient_matrix:
15852
from sage.matrix.constructor import Matrix
15853
from sage.rings.integer_ring import IntegerRing
15854
n = len(partition)
15855
M = Matrix(IntegerRing(), n)
15856
for i in xrange(n):
15857
for j in xrange(n):
15858
cell_i = partition[i]
15859
cell_j = partition[j]
15860
degrees = [self.degree_to_cell(u, cell_j) for u in cell_i]
15861
if len(uniq(degrees)) > 1:
15862
return False
15863
if self._directed:
15864
M[i, j] = degrees[0][0]
15865
else:
15866
M[i, j] = degrees[0]
15867
return M
15868
else:
15869
for cell1 in partition:
15870
for cell2 in partition:
15871
degrees = [self.degree_to_cell(u, cell2) for u in cell1]
15872
if len(uniq(degrees)) > 1:
15873
return False
15874
return True
15875
15876
def coarsest_equitable_refinement(self, partition, sparse=True):
15877
"""
15878
Returns the coarsest partition which is finer than the input
15879
partition, and equitable with respect to self.
15880
15881
A partition is equitable with respect to a graph if for every pair
15882
of cells C1, C2 of the partition, the number of edges from a vertex
15883
of C1 to C2 is the same, over all vertices in C1.
15884
15885
A partition P1 is finer than P2 (P2 is coarser than P1) if every
15886
cell of P1 is a subset of a cell of P2.
15887
15888
INPUT:
15889
15890
15891
- ``partition`` - a list of lists
15892
15893
- ``sparse`` - (default False) whether to use sparse
15894
or dense representation- for small graphs, use dense for speed
15895
15896
15897
EXAMPLES::
15898
15899
sage: G = graphs.PetersenGraph()
15900
sage: G.coarsest_equitable_refinement([[0],range(1,10)])
15901
[[0], [2, 3, 6, 7, 8, 9], [1, 4, 5]]
15902
sage: G = graphs.CubeGraph(3)
15903
sage: verts = G.vertices()
15904
sage: Pi = [verts[:1], verts[1:]]
15905
sage: Pi
15906
[['000'], ['001', '010', '011', '100', '101', '110', '111']]
15907
sage: G.coarsest_equitable_refinement(Pi)
15908
[['000'], ['011', '101', '110'], ['111'], ['001', '010', '100']]
15909
15910
Note that given an equitable partition, this function returns that
15911
partition::
15912
15913
sage: P = graphs.PetersenGraph()
15914
sage: prt = [[0], [1, 4, 5], [2, 3, 6, 7, 8, 9]]
15915
sage: P.coarsest_equitable_refinement(prt)
15916
[[0], [1, 4, 5], [2, 3, 6, 7, 8, 9]]
15917
15918
::
15919
15920
sage: ss = (graphs.WheelGraph(6)).line_graph(labels=False)
15921
sage: prt = [[(0, 1)], [(0, 2), (0, 3), (0, 4), (1, 2), (1, 4)], [(2, 3), (3, 4)]]
15922
sage: ss.coarsest_equitable_refinement(prt)
15923
Traceback (most recent call last):
15924
...
15925
TypeError: Partition ([[(0, 1)], [(0, 2), (0, 3), (0, 4), (1, 2), (1, 4)], [(2, 3), (3, 4)]]) is not valid for this graph: vertices are incorrect.
15926
15927
::
15928
15929
sage: ss = (graphs.WheelGraph(5)).line_graph(labels=False)
15930
sage: ss.coarsest_equitable_refinement(prt)
15931
[[(0, 1)], [(1, 2), (1, 4)], [(0, 3)], [(0, 2), (0, 4)], [(2, 3), (3, 4)]]
15932
15933
ALGORITHM: Brendan D. McKay's Master's Thesis, University of
15934
Melbourne, 1976.
15935
"""
15936
from sage.misc.flatten import flatten
15937
if sorted(flatten(partition, max_level=1)) != self.vertices():
15938
raise TypeError("Partition (%s) is not valid for this graph: vertices are incorrect."%partition)
15939
if any(len(cell)==0 for cell in partition):
15940
raise TypeError("Partition (%s) is not valid for this graph: there is a cell of length 0."%partition)
15941
if self.has_multiple_edges():
15942
raise TypeError("Refinement function does not support multiple edges.")
15943
from copy import copy
15944
G = copy(self)
15945
perm_to = G.relabel(return_map=True)
15946
partition = [[perm_to[b] for b in cell] for cell in partition]
15947
perm_from = {}
15948
for v in self:
15949
perm_from[perm_to[v]] = v
15950
n = G.num_verts()
15951
if sparse:
15952
from sage.graphs.base.sparse_graph import SparseGraph
15953
CG = SparseGraph(n)
15954
else:
15955
from sage.graphs.base.dense_graph import DenseGraph
15956
CG = DenseGraph(n)
15957
for i in range(n):
15958
for j in range(n):
15959
if G.has_edge(i,j):
15960
CG.add_arc(i,j)
15961
15962
from sage.groups.perm_gps.partn_ref.refinement_graphs import coarsest_equitable_refinement
15963
result = coarsest_equitable_refinement(CG, partition, G._directed)
15964
return [[perm_from[b] for b in cell] for cell in result]
15965
15966
def automorphism_group(self, partition=None, translation=False,
15967
verbosity=0, edge_labels=False, order=False,
15968
return_group=True, orbits=False):
15969
"""
15970
Returns the largest subgroup of the automorphism group of the
15971
(di)graph whose orbit partition is finer than the partition given.
15972
If no partition is given, the unit partition is used and the entire
15973
automorphism group is given.
15974
15975
INPUT:
15976
15977
15978
- ``translation`` - if True, then output includes a
15979
dictionary translating from keys == vertices to entries == elements
15980
of 1,2,...,n (since permutation groups can currently only act on
15981
positive integers).
15982
15983
- ``partition`` - default is the unit partition,
15984
otherwise computes the subgroup of the full automorphism group
15985
respecting the partition.
15986
15987
- ``edge_labels`` - default False, otherwise allows
15988
only permutations respecting edge labels.
15989
15990
- ``order`` - (default False) if True, compute the
15991
order of the automorphism group
15992
15993
- ``return_group`` - default True
15994
15995
- ``orbits`` - returns the orbits of the group acting
15996
on the vertices of the graph
15997
15998
15999
OUTPUT: The order of the output is group, translation, order,
16000
orbits. However, there are options to turn each of these on or
16001
off.
16002
16003
EXAMPLES: Graphs::
16004
16005
sage: graphs_query = GraphQuery(display_cols=['graph6'],num_vertices=4)
16006
sage: L = graphs_query.get_graphs_list()
16007
sage: graphs_list.show_graphs(L)
16008
sage: for g in L:
16009
... G = g.automorphism_group()
16010
... G.order(), G.gens()
16011
(24, [(2,3), (1,2), (1,4)])
16012
(4, [(2,3), (1,4)])
16013
(2, [(1,2)])
16014
(8, [(1,2), (1,4)(2,3)])
16015
(6, [(1,2), (1,4)])
16016
(6, [(2,3), (1,2)])
16017
(2, [(1,4)(2,3)])
16018
(2, [(1,2)])
16019
(8, [(2,3), (1,3)(2,4), (1,4)])
16020
(4, [(2,3), (1,4)])
16021
(24, [(2,3), (1,2), (1,4)])
16022
sage: C = graphs.CubeGraph(4)
16023
sage: G = C.automorphism_group()
16024
sage: M = G.character_table() # random order of rows, thus abs() below
16025
sage: QQ(M.determinant()).abs()
16026
712483534798848
16027
sage: G.order()
16028
384
16029
16030
::
16031
16032
sage: D = graphs.DodecahedralGraph()
16033
sage: G = D.automorphism_group()
16034
sage: A5 = AlternatingGroup(5)
16035
sage: Z2 = CyclicPermutationGroup(2)
16036
sage: H = A5.direct_product(Z2)[0] #see documentation for direct_product to explain the [0]
16037
sage: G.is_isomorphic(H)
16038
True
16039
16040
Multigraphs::
16041
16042
sage: G = Graph(multiedges=True,sparse=True)
16043
sage: G.add_edge(('a', 'b'))
16044
sage: G.add_edge(('a', 'b'))
16045
sage: G.add_edge(('a', 'b'))
16046
sage: G.automorphism_group()
16047
Permutation Group with generators [(1,2)]
16048
16049
Digraphs::
16050
16051
sage: D = DiGraph( { 0:[1], 1:[2], 2:[3], 3:[4], 4:[0] } )
16052
sage: D.automorphism_group()
16053
Permutation Group with generators [(1,2,3,4,5)]
16054
16055
Edge labeled graphs::
16056
16057
sage: G = Graph(sparse=True)
16058
sage: G.add_edges( [(0,1,'a'),(1,2,'b'),(2,3,'c'),(3,4,'b'),(4,0,'a')] )
16059
sage: G.automorphism_group(edge_labels=True)
16060
Permutation Group with generators [(1,4)(2,3)]
16061
16062
::
16063
16064
sage: G = Graph({0 : {1 : 7}})
16065
sage: G.automorphism_group(translation=True, edge_labels=True)
16066
(Permutation Group with generators [(1,2)], {0: 2, 1: 1})
16067
16068
sage: foo = Graph(sparse=True)
16069
sage: bar = Graph(implementation='c_graph',sparse=True)
16070
sage: foo.add_edges([(0,1,1),(1,2,2), (2,3,3)])
16071
sage: bar.add_edges([(0,1,1),(1,2,2), (2,3,3)])
16072
sage: foo.automorphism_group(translation=True, edge_labels=True)
16073
(Permutation Group with generators [()], {0: 4, 1: 1, 2: 2, 3: 3})
16074
sage: foo.automorphism_group(translation=True)
16075
(Permutation Group with generators [(1,2)(3,4)], {0: 4, 1: 1, 2: 2, 3: 3})
16076
sage: bar.automorphism_group(translation=True, edge_labels=True)
16077
(Permutation Group with generators [()], {0: 4, 1: 1, 2: 2, 3: 3})
16078
sage: bar.automorphism_group(translation=True)
16079
(Permutation Group with generators [(1,2)(3,4)], {0: 4, 1: 1, 2: 2, 3: 3})
16080
16081
You can also ask for just the order of the group::
16082
16083
sage: G = graphs.PetersenGraph()
16084
sage: G.automorphism_group(return_group=False, order=True)
16085
120
16086
16087
Or, just the orbits (note that each graph here is vertex transitive)
16088
16089
::
16090
16091
sage: G = graphs.PetersenGraph()
16092
sage: G.automorphism_group(return_group=False, orbits=True)
16093
[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]]
16094
sage: G.automorphism_group(partition=[[0],range(1,10)], return_group=False, orbits=True)
16095
[[0], [2, 3, 6, 7, 8, 9], [1, 4, 5]]
16096
sage: C = graphs.CubeGraph(3)
16097
sage: C.automorphism_group(orbits=True, return_group=False)
16098
[['000', '001', '010', '011', '100', '101', '110', '111']]
16099
16100
TESTS:
16101
16102
We get a KeyError when given an invalid partition (trac #6087)::
16103
16104
sage: g=graphs.CubeGraph(3)
16105
sage: g.relabel()
16106
sage: g.automorphism_group(partition=[[0,1,2],[3,4,5]])
16107
Traceback (most recent call last):
16108
...
16109
KeyError: 6
16110
16111
"""
16112
from sage.groups.perm_gps.partn_ref.refinement_graphs import perm_group_elt, search_tree
16113
from sage.groups.perm_gps.permgroup import PermutationGroup
16114
dig = (self._directed or self.has_loops())
16115
if partition is None:
16116
partition = [self.vertices()]
16117
if edge_labels or self.has_multiple_edges():
16118
G, partition, relabeling = graph_isom_equivalent_non_edge_labeled_graph(self, partition, return_relabeling=True, ignore_edge_labels=(not edge_labels))
16119
G_vertices = sum(partition, [])
16120
G_to = {}
16121
for i in xrange(len(G_vertices)):
16122
G_to[G_vertices[i]] = i
16123
from sage.graphs.all import Graph, DiGraph
16124
DoDG = DiGraph if self._directed else Graph
16125
H = DoDG(len(G_vertices), implementation='c_graph', loops=G.allows_loops())
16126
HB = H._backend
16127
for u,v in G.edge_iterator(labels=False):
16128
u = G_to[u]; v = G_to[v]
16129
HB.add_edge(u,v,None,G._directed)
16130
GC = HB._cg
16131
partition = [[G_to[v] for v in cell] for cell in partition]
16132
A = search_tree(GC, partition, lab=False, dict_rep=True, dig=dig, verbosity=verbosity, order=order)
16133
if order:
16134
a,b,c = A
16135
else:
16136
a,b = A
16137
b_new = {}
16138
for v in G_to:
16139
b_new[v] = b[G_to[v]]
16140
b = b_new
16141
# b is a translation of the labellings
16142
acting_vertices = {}
16143
translation_d = {}
16144
m = G.order()
16145
for v in self:
16146
if b[relabeling[v]] == m:
16147
translation_d[v] = self.order()
16148
acting_vertices[v] = 0
16149
else:
16150
translation_d[v] = b[relabeling[v]]
16151
acting_vertices[v] = b[relabeling[v]]
16152
real_aut_gp = []
16153
n = self.order()
16154
for gen in a:
16155
gen_restr = [0]*n
16156
for v in self.vertex_iterator():
16157
gen_restr[acting_vertices[v]] = gen[acting_vertices[v]]
16158
if gen_restr not in real_aut_gp:
16159
real_aut_gp.append(gen_restr)
16160
id = range(n)
16161
if id in real_aut_gp:
16162
real_aut_gp.remove(id)
16163
a = real_aut_gp
16164
b = translation_d
16165
else:
16166
G_vertices = sum(partition, [])
16167
G_to = {}
16168
for i in xrange(len(G_vertices)):
16169
G_to[G_vertices[i]] = i
16170
from sage.graphs.all import Graph, DiGraph
16171
DoDG = DiGraph if self._directed else Graph
16172
H = DoDG(len(G_vertices), implementation='c_graph', loops=self.allows_loops())
16173
HB = H._backend
16174
for u,v in self.edge_iterator(labels=False):
16175
u = G_to[u]; v = G_to[v]
16176
HB.add_edge(u,v,None,self._directed)
16177
GC = HB._cg
16178
partition = [[G_to[v] for v in cell] for cell in partition]
16179
if translation:
16180
A = search_tree(GC, partition, dict_rep=True, lab=False, dig=dig, verbosity=verbosity, order=order)
16181
if order:
16182
a,b,c = A
16183
else:
16184
a,b = A
16185
b_new = {}
16186
for v in G_to:
16187
b_new[v] = b[G_to[v]]
16188
b = b_new
16189
else:
16190
a = search_tree(GC, partition, dict_rep=False, lab=False, dig=dig, verbosity=verbosity, order=order)
16191
if order:
16192
a,c = a
16193
output = []
16194
if return_group:
16195
if len(a) != 0:
16196
output.append(PermutationGroup([perm_group_elt(aa) for aa in a]))
16197
else:
16198
output.append(PermutationGroup([[]]))
16199
if translation:
16200
output.append(b)
16201
if order:
16202
output.append(c)
16203
if orbits:
16204
G_from = {}
16205
for v in G_to:
16206
G_from[G_to[v]] = v
16207
from sage.groups.perm_gps.partn_ref.refinement_graphs import get_orbits
16208
output.append([[G_from[v] for v in W] for W in get_orbits(a, self.num_verts())])
16209
16210
# A Python switch statement!
16211
return { 0: None,
16212
1: output[0],
16213
2: tuple(output),
16214
3: tuple(output),
16215
4: tuple(output)
16216
}[len(output)]
16217
16218
def is_vertex_transitive(self, partition=None, verbosity=0,
16219
edge_labels=False, order=False,
16220
return_group=True, orbits=False):
16221
"""
16222
Returns whether the automorphism group of self is transitive within
16223
the partition provided, by default the unit partition of the
16224
vertices of self (thus by default tests for vertex transitivity in
16225
the usual sense).
16226
16227
EXAMPLES::
16228
16229
sage: G = Graph({0:[1],1:[2]})
16230
sage: G.is_vertex_transitive()
16231
False
16232
sage: P = graphs.PetersenGraph()
16233
sage: P.is_vertex_transitive()
16234
True
16235
sage: D = graphs.DodecahedralGraph()
16236
sage: D.is_vertex_transitive()
16237
True
16238
sage: R = graphs.RandomGNP(2000, .01)
16239
sage: R.is_vertex_transitive()
16240
False
16241
"""
16242
if partition is None:
16243
partition = [self.vertices()]
16244
new_partition = self.automorphism_group(partition,
16245
verbosity=verbosity, edge_labels=edge_labels,
16246
order=False, return_group=False, orbits=True)
16247
for cell in partition:
16248
for new_cell in new_partition:
16249
if cell[0] in new_cell:
16250
if any([c not in new_cell for c in cell[1:]]):
16251
return False
16252
return True
16253
16254
def is_hamiltonian(self):
16255
r"""
16256
Tests whether the current graph is Hamiltonian.
16257
16258
A graph (resp. digraph) is said to be Hamiltonian
16259
if it contains as a subgraph a cycle (resp. a circuit)
16260
going through all the vertices.
16261
16262
Testing for Hamiltonicity being NP-Complete, this
16263
algorithm could run for some time depending on
16264
the instance.
16265
16266
ALGORITHM:
16267
16268
See ``Graph.traveling_salesman_problem``.
16269
16270
OUTPUT:
16271
16272
Returns ``True`` if a Hamiltonian cycle/circuit exists, and
16273
``False`` otherwise.
16274
16275
NOTE:
16276
16277
This function, as ``hamiltonian_cycle`` and
16278
``traveling_salesman_problem``, computes a Hamiltonian
16279
cycle if it exists : the user should *NOT* test for
16280
Hamiltonicity using ``is_hamiltonian`` before calling
16281
``hamiltonian_cycle`` or ``traveling_salesman_problem``
16282
as it would result in computing it twice.
16283
16284
EXAMPLES:
16285
16286
The Heawood Graph is known to be Hamiltonian ::
16287
16288
sage: g = graphs.HeawoodGraph()
16289
sage: g.is_hamiltonian()
16290
True
16291
16292
The Petergraph, though, is not ::
16293
16294
sage: g = graphs.PetersenGraph()
16295
sage: g.is_hamiltonian()
16296
False
16297
16298
TESTS:
16299
16300
When no solver is installed, a
16301
``OptionalPackageNotFoundError`` exception is raised::
16302
16303
sage: from sage.misc.exceptions import OptionalPackageNotFoundError
16304
sage: try:
16305
... g = graphs.ChvatalGraph()
16306
... if not g.is_hamiltonian():
16307
... print "There is something wrong here !"
16308
... except OptionalPackageNotFoundError:
16309
... pass
16310
"""
16311
16312
try:
16313
tsp = self.traveling_salesman_problem(use_edge_labels = False)
16314
return True
16315
16316
except ValueError:
16317
return False
16318
16319
def is_isomorphic(self, other, certify=False, verbosity=0, edge_labels=False):
16320
"""
16321
Tests for isomorphism between self and other.
16322
16323
INPUT:
16324
16325
16326
- ``certify`` - if True, then output is (a,b), where a
16327
is a boolean and b is either a map or None.
16328
16329
- ``edge_labels`` - default False, otherwise allows
16330
only permutations respecting edge labels.
16331
16332
16333
EXAMPLES: Graphs::
16334
16335
sage: from sage.groups.perm_gps.permgroup_named import SymmetricGroup
16336
sage: D = graphs.DodecahedralGraph()
16337
sage: E = copy(D)
16338
sage: gamma = SymmetricGroup(20).random_element()
16339
sage: E.relabel(gamma)
16340
sage: D.is_isomorphic(E)
16341
True
16342
16343
::
16344
16345
sage: D = graphs.DodecahedralGraph()
16346
sage: S = SymmetricGroup(20)
16347
sage: gamma = S.random_element()
16348
sage: E = copy(D)
16349
sage: E.relabel(gamma)
16350
sage: a,b = D.is_isomorphic(E, certify=True); a
16351
True
16352
sage: from sage.plot.graphics import GraphicsArray
16353
sage: from sage.graphs.generic_graph_pyx import spring_layout_fast
16354
sage: position_D = spring_layout_fast(D)
16355
sage: position_E = {}
16356
sage: for vert in position_D:
16357
... position_E[b[vert]] = position_D[vert]
16358
sage: GraphicsArray([D.plot(pos=position_D), E.plot(pos=position_E)]).show() # long time
16359
16360
::
16361
16362
sage: g=graphs.HeawoodGraph()
16363
sage: g.is_isomorphic(g)
16364
True
16365
16366
Multigraphs::
16367
16368
sage: G = Graph(multiedges=True,sparse=True)
16369
sage: G.add_edge((0,1,1))
16370
sage: G.add_edge((0,1,2))
16371
sage: G.add_edge((0,1,3))
16372
sage: G.add_edge((0,1,4))
16373
sage: H = Graph(multiedges=True,sparse=True)
16374
sage: H.add_edge((3,4))
16375
sage: H.add_edge((3,4))
16376
sage: H.add_edge((3,4))
16377
sage: H.add_edge((3,4))
16378
sage: G.is_isomorphic(H)
16379
True
16380
16381
Digraphs::
16382
16383
sage: A = DiGraph( { 0 : [1,2] } )
16384
sage: B = DiGraph( { 1 : [0,2] } )
16385
sage: A.is_isomorphic(B, certify=True)
16386
(True, {0: 1, 1: 0, 2: 2})
16387
16388
Edge labeled graphs::
16389
16390
sage: G = Graph(sparse=True)
16391
sage: G.add_edges( [(0,1,'a'),(1,2,'b'),(2,3,'c'),(3,4,'b'),(4,0,'a')] )
16392
sage: H = G.relabel([1,2,3,4,0], inplace=False)
16393
sage: G.is_isomorphic(H, edge_labels=True)
16394
True
16395
16396
Edge labeled digraphs::
16397
16398
sage: G = DiGraph()
16399
sage: G.add_edges( [(0,1,'a'),(1,2,'b'),(2,3,'c'),(3,4,'b'),(4,0,'a')] )
16400
sage: H = G.relabel([1,2,3,4,0], inplace=False)
16401
sage: G.is_isomorphic(H, edge_labels=True)
16402
True
16403
sage: G.is_isomorphic(H, edge_labels=True, certify=True)
16404
(True, {0: 1, 1: 2, 2: 3, 3: 4, 4: 0})
16405
16406
TESTS::
16407
16408
sage: g1 = '~?A[~~{ACbCwV_~__OOcCW_fAA{CF{CCAAAC__bCCCwOOV___~____OOOOcCCCW___fAAAA'+\
16409
... '{CCCF{CCCCAAAAAC____bCCCCCwOOOOV_____~_O@ACG_@ACGOo@ACG?{?`A?GV_GO@AC}@?_OGC'+\
16410
... 'C?_OI@?K?I@?_OM?_OGD?F_A@OGC@{A@?_OG?O@?gCA?@_GCA@O?B_@OGCA?BoA@?gC?@{A?GO`?'+\
16411
... '??_GO@AC??E?O`?CG??[?O`A?G??{?GO`A???|A?_GOC`AC@_OCGACEAGS?HA?_SA`aO@G?cOC_N'+\
16412
... 'G_C@AOP?GnO@_GACOE?g?`OGACCOGaGOc?HA?`GORCG_AO@B?K@[`A?OCI@A@By?_K@?SCABA?H?'+\
16413
... 'SA?a@GC`CH?Q?C_c?cGRC@G_AOCOa@Ax?QC?_GOo_CNg@A?oC@CaCGO@CGA_O`?GSGPAGOC_@OO_'+\
16414
... 'aCHaG?cO@CB?_`Ax?GQC?_cAOCG^OGAC@_D?IGO`?D?O_I?HAOO`AGOHA?cC?oAO`AW_Q?HCACAC'+\
16415
... 'GO`[_OCHA?_cCACG^O_@CAGO`A?GCOGc@?I?OQOC?IGC_o@CAGCCE?A@DBG_OA@C_CP?OG_VA_CO'+\
16416
... 'G@D?_OA_DFgA@CO?aH?Ga@?a?_I?S@A@@Oa@?@P@GCO_AACO_a_?`K_GCQ@?cAOG_OGAwQ@?K?cC'+\
16417
... 'GH?I?ABy@C?G_S@@GCA@C`?OI?_D?OP@G?IGGP@O_AGCP?aG?GCPAX?cA?OGSGCGCAGCJ`?oAGCC'+\
16418
... 'HAA?A_CG^O@CAG_GCSCAGCCGOCG@OA_`?`?g_OACG_`CAGOAO_H?a_?`AXA?OGcAAOP?a@?CGVAC'+\
16419
... 'OG@_AGG`OA_?O`|?Ga?COKAAGCA@O`A?a?S@?HCG`?_?gO`AGGaC?PCAOGI?A@GO`K_CQ@?GO_`O'+\
16420
... 'GCAACGVAG@_COOCQ?g?I?O`ByC?G_P?O`A?H@G?_P?`OAGC?gD?_C@_GCAGDG_OA@CCPC?AOQ??g'+\
16421
... '_R@_AGCO____OCC_@OAbaOC?g@C_H?AOOC@?a`y?PC?G`@OOH??cOG_OOAG@_COAP?WA?_KAGC@C'+\
16422
... '_CQ@?HAACH??c@P?_AWGaC?P?gA_C_GAD?I?Awa?S@?K?`C_GAOGCS?@|?COGaA@CAAOQ?AGCAGO'+\
16423
... 'ACOG@_G_aC@_G@CA@@AHA?OGc?WAAH@G?P?_?cH_`CAGOGACc@@GA?S?CGVCG@OA_CICAOOC?PO?'+\
16424
... 'OG^OG_@CAC_cC?AOP?_OICG@?oAGCO_GO_GB@?_OG`AH?cA?OH?`P??cC_O?SCGR@O_AGCAI?Q?_'+\
16425
... 'GGS?D?O`[OI?_D@@CCA?cCA_?_O`By?_PC?IGAGOQ?@A@?aO`A?Q@?K?__`_E?_GCA@CGO`C_GCQ'+\
16426
... '@A?gAOQ?@C?DCACGR@GCO_AGPA@@GAA?A_CO`Aw_I?S@?SCB@?OC_?_P@ACNgOC@A?aCGOCAGCA@'+\
16427
... 'CA?H@GG_C@AOGa?OOG_O?g_OA?oDC_AO@GOCc?@P?_A@D??cC``O?cGAOGD?@OA_CAGCA?_cwKA?'+\
16428
... '`?OWGG?_PO?I?S?H@?^OGAC@_Aa@CAGC?a@?_Q?@H?_OCHA?OQA_P?_G_O?WA?_IG_Q?HC@A@ADC'+\
16429
... 'A?AI?AC_?QAWOHA?cAGG_I?S?G_OG@GA?`[D?O_IA?`GGCS?OA_?c@?Q?^OAC@_G_Ca@CA@?OGCO'+\
16430
... 'H@G@A@?GQC?_Q@GP?_OG?IGGB?OCGaG?cO@A__QGC?E?A@CH@G?GRAGOC_@GGOW@O?O_OGa?_c?G'+\
16431
... 'V@CGA_OOaC?a_?a?A_CcC@?CNgA?oC@GGE@?_OH?a@?_?QA`A@?QC?_KGGO_OGCAa@?A?_KCGPC@'+\
16432
... 'G_AOAGPGC?D@?a_A?@GGO`KH?Q?C_QGAA_?gOG_OA?_GG`AwH?SA?`?cAI?A@D?I?@?QA?`By?K@'+\
16433
... '?O`GGACA@CGCA@CC_?WO`?`A?OCH?`OCA@COG?I?oC@ACGPCG_AO@_aAA?Aa?g?GD@G?CO`AWOc?'+\
16434
... 'HA?OcG_?g@OGCAAAOC@ACJ_`OGACAGCS?CAGI?A`@?OCACG^'
16435
sage: g2 = '~?A[??osR?WARSETCJ_QWASehOXQg`QwChK?qSeFQ_sTIaWIV?XIR?KAC?B?`?COCG?o?O_'+\
16436
... '@_?`??B?`?o@_O_WCOCHC@_?`W?E?AD_O?WCCeO?WCSEGAGAIaA@_?aw?OK?ER?`?@_HQXA?B@Q_'+\
16437
... 'pA?a@Qg_`?o?h[?GOK@IR?@A?BEQcoCG?K\IB?GOCWiTC?GOKWIV??CGEKdH_H_?CB?`?DC??_WC'+\
16438
... 'G?SO?AP?O_?g_?D_?`?C__?D_?`?CCo??@_O_XDC???WCGEGg_??a?`G_aa??E?AD_@cC??K?CJ?'+\
16439
... '@@K?O?WCCe?aa?G?KAIB?Gg_A?a?ag_@DC?OK?CV??EOO@?o?XK??GH`A?B?Qco?Gg`A?B@Q_o?C'+\
16440
... 'SO`?P?hSO?@DCGOK?IV???K_`A@_HQWC??_cCG?KXIRG?@D?GO?WySEG?@D?GOCWiTCC??a_CGEK'+\
16441
... 'DJ_@??K_@A@bHQWAW?@@K??_WCG?g_?CSO?A@_O_@P??Gg_?Ca?`?@P??Gg_?D_?`?C__?EOO?Ao'+\
16442
... '?O_AAW?@@K???WCGEPP??Gg_??B?`?pDC??aa??AGACaAIG?@DC??K?CJ?BGG?@cC??K?CJ?@@K?'+\
16443
... '?_e?G?KAAR?PP??Gg_A?B?a_oAIG?@DC?OCOCTC?Gg_?CSO@?o?P[??X@??K__A@_?qW??OR??GH'+\
16444
... '`A?B?Qco?Gg_?CSO`?@_hOW?AIG?@DCGOCOITC??PP??Gg`A@_@Qw??@cC??qACGE?dH_O?AAW?@'+\
16445
... '@GGO?WqSeO?AIG?@D?GO?WySEG?@DC??a_CGAKTIaA??PP??Gg@A@b@Qw?O?BGG?@c?GOKXIR?KA'+\
16446
... 'C?H_?CCo?A@_O_?WCG@P??Gg_?CB?`?COCG@P??Gg_?Ca?`?E?AC?g_?CSO?Ao?O_@_?`@GG?@cC'+\
16447
... '??k?CG??WCGOR??GH_??B?`?o@_O`DC??aa???KACB?a?`AIG?@DC??COCHC@_?`AIG?@DC??K?C'+\
16448
... 'J??o?O`cC??qA??E?AD_O?WC?OR??GH_A?B?_cq?B?_AIG?@DC?O?WCSEGAGA?Gg_?CSO@?P?PSO'+\
16449
... 'OK?C?PP??Gg_A@_?aw?OK?C?X@??K__A@_?qWCG?K??GH_?CCo`?@_HQXA?B??AIG?@DCGO?WISE'+\
16450
... 'GOCO??PP??Gg`A?a@Qg_`?o??@DC??aaCGE?DJ_@A@_??BGG?@cCGOK@IR?@A?BO?AAW?@@GGO?W'+\
16451
... 'qSe?`?@g?@DC??a_CG?K\IB?GOCQ??PP??Gg@A?bDQg_@A@_O?AIG?@D?GOKWIV??CGE@??K__?E'+\
16452
... 'O?`?pchK?_SA_OI@OGD?gCA_SA@OI?c@H?Q?c_H?QOC_HGAOCc?QOC_HGAOCc@GAQ?c@H?QD?gCA'+\
16453
... '_SA@OI@?gD?_SA_OKA_SA@OI@?gD?_SA_OI@OHI?c_H?QOC_HGAOCc@GAQ?eC_H?QOC_HGAOCc@G'+\
16454
... 'AQ?c@XD?_SA_OI@OGD?gCA_SA@PKGO`A@ACGSGO`?`ACICGO_?ACGOcGO`?O`AC`ACHACGO???^?'+\
16455
... '????}Bw????Fo^???????Fo?}?????Bw?^?Bw?????GO`AO`AC`ACGACGOcGO`??aCGO_O`ADACG'+\
16456
... 'OGO`A@ACGOA???@{?N_@{?????Fo?}????OFo????N_}????@{????Bw?OACGOgO`A@ACGSGO`?`'+\
16457
... 'ACG?OaCGO_GO`AO`AC`ACGACGO_@G???Fo^?????}Bw????Fo??AC@{?????Fo?}?Fo?????^??A'+\
16458
... 'OGO`AO`AC@ACGQCGO_GO`A?HAACGOgO`A@ACGOGO`A`ACG?GQ??^?Bw?????N_@{?????Fo?QC??'+\
16459
... 'Fo^?????}????@{Fo???CHACGO_O`ACACGOgO`A@ACGO@AOcGO`?O`AC`ACGACGOcGO`?@GQFo??'+\
16460
... '??N_????^@{????Bw??`GRw?????N_@{?????Fo?}???HAO_OI@OGD?gCA_SA@OI@?gDK_??C@GA'+\
16461
... 'Q?c@H?Q?c_H?QOC_HEW????????????????????????~~~~~'
16462
sage: G1 = Graph(g1)
16463
sage: G2 = Graph(g2)
16464
sage: G1.is_isomorphic(G2)
16465
True
16466
16467
Ensure that isomorphic looped graphs with non-range vertex labels report
16468
correctly (trac #10814, fixed by #8395)::
16469
16470
sage: G1 = Graph([(0,1), (1,1)])
16471
sage: G2 = Graph([(0,2), (2,2)])
16472
sage: G1.is_isomorphic(G2)
16473
True
16474
sage: G = Graph(multiedges = True, loops = True)
16475
sage: H = Graph(multiedges = True, loops = True)
16476
sage: G.add_edges([(0,1,0),(1,0,1),(1,1,2),(0,0,3)])
16477
sage: H.add_edges([(0,1,3),(1,0,2),(1,1,1),(0,0,0)])
16478
sage: G.is_isomorphic(H, certify=True)
16479
(True, {0: 0, 1: 1})
16480
sage: set_random_seed(0)
16481
sage: D = digraphs.RandomDirectedGNP(6, .2)
16482
sage: D.is_isomorphic(D, certify = True)
16483
(True, {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5})
16484
sage: D.is_isomorphic(D,edge_labels=True, certify = True)
16485
(True, {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5})
16486
16487
Ensure that trac #11620 is fixed::
16488
16489
sage: G1 = DiGraph([(0, 0, 'c'), (0, 4, 'b'), (0, 5, 'c'),
16490
... (0, 5, 't'), (1, 1, 'c'), (1, 3,'c'), (1, 3, 't'), (1, 5, 'b'),
16491
... (2, 2, 'c'), (2, 3, 'b'), (2, 4, 'c'),(2, 4, 't'), (3, 1, 't'),
16492
... (3, 2, 'b'), (3, 2, 'c'), (3, 4, 'c'), (4, 0,'b'), (4, 0, 'c'),
16493
... (4, 2, 't'), (4, 5, 'c'), (5, 0, 't'), (5, 1, 'b'), (5, 1, 'c'),
16494
... (5, 3, 'c')], loops=True, multiedges=True)
16495
sage: G2 = G1.relabel({0:4, 1:5, 2:3, 3:2, 4:1,5:0}, inplace=False)
16496
sage: G1.canonical_label(edge_labels=True) == G2.canonical_label(edge_labels=True)
16497
True
16498
sage: G1.is_isomorphic(G2,edge_labels=True)
16499
True
16500
16501
"""
16502
possible = True
16503
if self._directed != other._directed:
16504
possible = False
16505
if self.order() != other.order():
16506
possible = False
16507
if self.size() != other.size():
16508
possible = False
16509
if not possible and certify:
16510
return False, None
16511
elif not possible:
16512
return False
16513
self_vertices = self.vertices()
16514
other_vertices = other.vertices()
16515
if edge_labels or self.has_multiple_edges():
16516
if edge_labels and sorted(self.edge_labels()) != sorted(other.edge_labels()):
16517
return False, None if certify else False
16518
else:
16519
G, partition, relabeling = graph_isom_equivalent_non_edge_labeled_graph(self, return_relabeling=True, ignore_edge_labels=(not edge_labels))
16520
self_vertices = sum(partition,[])
16521
G2, partition2, relabeling2 = graph_isom_equivalent_non_edge_labeled_graph(other, return_relabeling=True, ignore_edge_labels=(not edge_labels))
16522
partition2 = sum(partition2,[])
16523
other_vertices = partition2
16524
else:
16525
G = self; partition = [self_vertices]
16526
G2 = other; partition2 = other_vertices
16527
G_to = {}
16528
for i in xrange(len(self_vertices)):
16529
G_to[self_vertices[i]] = i
16530
from sage.graphs.all import Graph, DiGraph
16531
DoDG = DiGraph if self._directed else Graph
16532
H = DoDG(len(self_vertices), implementation='c_graph', loops=G.allows_loops())
16533
HB = H._backend
16534
for u,v in G.edge_iterator(labels=False):
16535
u = G_to[u]; v = G_to[v]
16536
HB.add_edge(u,v,None,G._directed)
16537
G = HB._cg
16538
partition = [[G_to[v] for v in cell] for cell in partition]
16539
GC = G
16540
G2_to = {}
16541
for i in xrange(len(other_vertices)):
16542
G2_to[other_vertices[i]] = i
16543
H2 = DoDG(len(other_vertices), implementation='c_graph', loops=G2.allows_loops())
16544
H2B = H2._backend
16545
for u,v in G2.edge_iterator(labels=False):
16546
u = G2_to[u]; v = G2_to[v]
16547
H2B.add_edge(u,v,None,G2._directed)
16548
G2 = H2B._cg
16549
partition2 = [G2_to[v] for v in partition2]
16550
GC2 = G2
16551
isom = isomorphic(GC, GC2, partition, partition2, (self._directed or self.has_loops()), 1)
16552
16553
if not isom and certify:
16554
return False, None
16555
elif not isom:
16556
return False
16557
elif not certify:
16558
return True
16559
else:
16560
isom_trans = {}
16561
if edge_labels:
16562
relabeling2_inv = {}
16563
for x in relabeling2:
16564
relabeling2_inv[relabeling2[x]] = x
16565
for v in self.vertices():
16566
isom_trans[v] = relabeling2_inv[other_vertices[isom[G_to[relabeling[v]]]]]
16567
else:
16568
for v in self.vertices():
16569
isom_trans[v] = other_vertices[isom[G_to[v]]]
16570
return True, isom_trans
16571
16572
def canonical_label(self, partition=None, certify=False, verbosity=0, edge_labels=False):
16573
"""
16574
Returns the unique graph on `\{0,1,...,n-1\}` ( ``n = self.order()`` ) which
16575
16576
- is isomorphic to self,
16577
16578
- is invariant in the isomorphism class.
16579
16580
In other words, given two graphs ``G`` and ``H`` which are isomorphic,
16581
suppose ``G_c`` and ``H_c`` are the graphs returned by
16582
``canonical_label``. Then the following hold:
16583
16584
- ``G_c == H_c``
16585
16586
- ``G_c.adjacency_matrix() == H_c.adjacency_matrix()``
16587
16588
- ``G_c.graph6_string() == H_c.graph6_string()``
16589
16590
INPUT:
16591
16592
- ``partition`` - if given, the canonical label with
16593
respect to this set partition will be computed. The default is the unit
16594
set partition.
16595
16596
- ``certify`` - if True, a dictionary mapping from the
16597
(di)graph to its canonical label will be given.
16598
16599
- ``verbosity`` - gets passed to nice: prints helpful
16600
output.
16601
16602
- ``edge_labels`` - default False, otherwise allows
16603
only permutations respecting edge labels.
16604
16605
16606
EXAMPLES::
16607
16608
sage: D = graphs.DodecahedralGraph()
16609
sage: E = D.canonical_label(); E
16610
Dodecahedron: Graph on 20 vertices
16611
sage: D.canonical_label(certify=True)
16612
(Dodecahedron: Graph on 20 vertices, {0: 0, 1: 19, 2: 16, 3: 15, 4: 9, 5: 1, 6: 10, 7: 8, 8: 14, 9: 12, 10: 17, 11: 11, 12: 5, 13: 6, 14: 2, 15: 4, 16: 3, 17: 7, 18: 13, 19: 18})
16613
sage: D.is_isomorphic(E)
16614
True
16615
16616
Multigraphs::
16617
16618
sage: G = Graph(multiedges=True,sparse=True)
16619
sage: G.add_edge((0,1))
16620
sage: G.add_edge((0,1))
16621
sage: G.add_edge((0,1))
16622
sage: G.canonical_label()
16623
Multi-graph on 2 vertices
16624
sage: Graph('A?', implementation='c_graph').canonical_label()
16625
Graph on 2 vertices
16626
16627
Digraphs::
16628
16629
sage: P = graphs.PetersenGraph()
16630
sage: DP = P.to_directed()
16631
sage: DP.canonical_label().adjacency_matrix()
16632
[0 0 0 0 0 0 0 1 1 1]
16633
[0 0 0 0 1 0 1 0 0 1]
16634
[0 0 0 1 0 0 1 0 1 0]
16635
[0 0 1 0 0 1 0 0 0 1]
16636
[0 1 0 0 0 1 0 0 1 0]
16637
[0 0 0 1 1 0 0 1 0 0]
16638
[0 1 1 0 0 0 0 1 0 0]
16639
[1 0 0 0 0 1 1 0 0 0]
16640
[1 0 1 0 1 0 0 0 0 0]
16641
[1 1 0 1 0 0 0 0 0 0]
16642
16643
Edge labeled graphs::
16644
16645
sage: G = Graph(sparse=True)
16646
sage: G.add_edges( [(0,1,'a'),(1,2,'b'),(2,3,'c'),(3,4,'b'),(4,0,'a')] )
16647
sage: G.canonical_label(edge_labels=True)
16648
Graph on 5 vertices
16649
sage: G.canonical_label(edge_labels=True,certify=True)
16650
(Graph on 5 vertices, {0: 4, 1: 3, 2: 0, 3: 1, 4: 2})
16651
"""
16652
import sage.groups.perm_gps.partn_ref.refinement_graphs
16653
from sage.groups.perm_gps.partn_ref.refinement_graphs import search_tree
16654
from copy import copy
16655
16656
dig = (self.has_loops() or self._directed)
16657
if partition is None:
16658
partition = [self.vertices()]
16659
if edge_labels or self.has_multiple_edges():
16660
G, partition, relabeling = graph_isom_equivalent_non_edge_labeled_graph(self, partition, return_relabeling=True)
16661
G_vertices = sum(partition, [])
16662
G_to = {}
16663
for i in xrange(len(G_vertices)):
16664
G_to[G_vertices[i]] = i
16665
from sage.graphs.all import Graph, DiGraph
16666
DoDG = DiGraph if self._directed else Graph
16667
H = DoDG(len(G_vertices), implementation='c_graph', loops=G.allows_loops())
16668
HB = H._backend
16669
for u,v in G.edge_iterator(labels=False):
16670
u = G_to[u]; v = G_to[v]
16671
HB.add_edge(u,v,None,G._directed)
16672
GC = HB._cg
16673
partition = [[G_to[v] for v in cell] for cell in partition]
16674
a,b,c = search_tree(GC, partition, certify=True, dig=dig, verbosity=verbosity)
16675
# c is a permutation to the canonical label of G, which depends only on isomorphism class of self.
16676
H = copy(self)
16677
c_new = {}
16678
for v in self.vertices():
16679
c_new[v] = c[G_to[relabeling[v]]]
16680
H.relabel(c_new)
16681
if certify:
16682
return H, c_new
16683
else:
16684
return H
16685
G_vertices = sum(partition, [])
16686
G_to = {}
16687
for i in xrange(len(G_vertices)):
16688
G_to[G_vertices[i]] = i
16689
from sage.graphs.all import Graph, DiGraph
16690
DoDG = DiGraph if self._directed else Graph
16691
H = DoDG(len(G_vertices), implementation='c_graph', loops=self.allows_loops())
16692
HB = H._backend
16693
for u,v in self.edge_iterator(labels=False):
16694
u = G_to[u]; v = G_to[v]
16695
HB.add_edge(u,v,None,self._directed)
16696
GC = HB._cg
16697
partition = [[G_to[v] for v in cell] for cell in partition]
16698
a,b,c = search_tree(GC, partition, certify=True, dig=dig, verbosity=verbosity)
16699
H = copy(self)
16700
c_new = {}
16701
for v in G_to:
16702
c_new[v] = c[G_to[v]]
16703
H.relabel(c_new)
16704
if certify:
16705
return H, c_new
16706
else:
16707
return H
16708
16709
def tachyon_vertex_plot(g, bgcolor=(1,1,1),
16710
vertex_colors=None,
16711
vertex_size=0.06,
16712
pos3d=None,
16713
**kwds):
16714
"""
16715
Helper function for plotting graphs in 3d with Tachyon. Returns a
16716
plot containing only the vertices, as well as the 3d position
16717
dictionary used for the plot.
16718
16719
INPUT:
16720
- `pos3d` - a 3D layout of the vertices
16721
- various rendering options
16722
16723
EXAMPLES::
16724
16725
sage: G = graphs.TetrahedralGraph()
16726
sage: from sage.graphs.generic_graph import tachyon_vertex_plot
16727
sage: T,p = tachyon_vertex_plot(G, pos3d = G.layout(dim=3))
16728
sage: type(T)
16729
<class 'sage.plot.plot3d.tachyon.Tachyon'>
16730
sage: type(p)
16731
<type 'dict'>
16732
"""
16733
assert pos3d is not None
16734
from math import sqrt
16735
from sage.plot.plot3d.tachyon import Tachyon
16736
16737
c = [0,0,0]
16738
r = []
16739
verts = g.vertices()
16740
16741
if vertex_colors is None:
16742
vertex_colors = { (1,0,0) : verts }
16743
try:
16744
for v in verts:
16745
c[0] += pos3d[v][0]
16746
c[1] += pos3d[v][1]
16747
c[2] += pos3d[v][2]
16748
except KeyError:
16749
raise KeyError, "Oops! You haven't specified positions for all the vertices."
16750
16751
order = g.order()
16752
c[0] = c[0]/order
16753
c[1] = c[1]/order
16754
c[2] = c[2]/order
16755
for v in verts:
16756
pos3d[v][0] = pos3d[v][0] - c[0]
16757
pos3d[v][1] = pos3d[v][1] - c[1]
16758
pos3d[v][2] = pos3d[v][2] - c[2]
16759
r.append(abs(sqrt((pos3d[v][0])**2 + (pos3d[v][1])**2 + (pos3d[v][2])**2)))
16760
r = max(r)
16761
if r == 0:
16762
r = 1
16763
for v in verts:
16764
pos3d[v][0] = pos3d[v][0]/r
16765
pos3d[v][1] = pos3d[v][1]/r
16766
pos3d[v][2] = pos3d[v][2]/r
16767
TT = Tachyon(camera_center=(1.4,1.4,1.4), antialiasing=13, **kwds)
16768
TT.light((4,3,2), 0.02, (1,1,1))
16769
TT.texture('bg', ambient=1, diffuse=1, specular=0, opacity=1.0, color=bgcolor)
16770
TT.plane((-1.6,-1.6,-1.6), (1.6,1.6,1.6), 'bg')
16771
16772
i = 0
16773
for color in vertex_colors:
16774
i += 1
16775
TT.texture('node_color_%d'%i, ambient=0.1, diffuse=0.9,
16776
specular=0.03, opacity=1.0, color=color)
16777
for v in vertex_colors[color]:
16778
TT.sphere((pos3d[v][0],pos3d[v][1],pos3d[v][2]), vertex_size, 'node_color_%d'%i)
16779
16780
return TT, pos3d
16781
16782
def graph_isom_equivalent_non_edge_labeled_graph(g, partition=None, standard_label=None, return_relabeling=False, return_edge_labels=False, inplace=False, ignore_edge_labels=False):
16783
"""
16784
Helper function for canonical labeling of edge labeled (di)graphs.
16785
16786
Translates to a bipartite incidence-structure type graph
16787
appropriate for computing canonical labels of edge labeled and/or multi-edge graphs.
16788
Note that this is actually computationally equivalent to
16789
implementing a change on an inner loop of the main algorithm-
16790
namely making the refinement procedure sort for each label.
16791
16792
If the graph is a multigraph, it is translated to a non-multigraph,
16793
where each edge is labeled with a dictionary describing how many
16794
edges of each label were originally there. Then in either case we
16795
are working on a graph without multiple edges. At this point, we
16796
create another (bipartite) graph, whose left vertices are the
16797
original vertices of the graph, and whose right vertices represent
16798
the edges. We partition the left vertices as they were originally,
16799
and the right vertices by common labels: only automorphisms taking
16800
edges to like-labeled edges are allowed, and this additional
16801
partition information enforces this on the bipartite graph.
16802
16803
INPUT:
16804
16805
- ``g`` -- Graph or DiGraph
16806
- ``partition`` -- (default:None) if given, the partition of the vertices is as well relabeled
16807
- ``standard_label`` -- (default:None) the standard label is not considered to be changed
16808
- ``return_relabeling`` -- (defaut:False) if True, a dictionary containing the relabeling is returned
16809
- ``return_edge_labels`` -- (defaut:False) if True, the different edge_labels are returned (useful if inplace is True)
16810
- ``inplace`` -- (default:False) if True, g is modified, otherwise the result is returned. Note that attributes of g are *not* copied for speed issues, only edges and vertices.
16811
16812
OUTPUT:
16813
16814
- if not inplace: the unlabeled graph without multiple edges
16815
- the partition of the vertices
16816
- if return_relabeling: a dictionary containing the relabeling
16817
- if return_edge_labels: the list of (former) edge labels is returned
16818
16819
EXAMPLES::
16820
16821
sage: from sage.graphs.generic_graph import graph_isom_equivalent_non_edge_labeled_graph
16822
16823
sage: G = Graph(multiedges=True,sparse=True)
16824
sage: G.add_edges( (0,1,i) for i in range(10) )
16825
sage: G.add_edge(1,2,'string')
16826
sage: G.add_edge(2,123)
16827
sage: g = graph_isom_equivalent_non_edge_labeled_graph(G, partition=[[0,123],[1,2]]); g
16828
[Graph on 6 vertices, [[0, 3], [1, 2], [4], [5]]]
16829
16830
sage: g = graph_isom_equivalent_non_edge_labeled_graph(G); g
16831
[Graph on 6 vertices, [[0, 1, 2, 3], [4], [5]]]
16832
sage: g[0].edges()
16833
[(0, 4, None), (1, 4, None), (1, 5, None), (2, 3, None), (2, 5, None)]
16834
16835
sage: g = graph_isom_equivalent_non_edge_labeled_graph(G,standard_label='string',return_edge_labels=True); g
16836
[Graph on 6 vertices, [[0, 1, 2, 3], [5], [4]], [[[None, 1]], [[0, 1], [1, 1], [2, 1], [3, 1], [4, 1], [5, 1], [6, 1], [7, 1], [8, 1], [9, 1]], [['string', 1]]]]
16837
sage: g[0].edges()
16838
[(0, 4, None), (1, 2, None), (1, 4, None), (2, 5, None), (3, 5, None)]
16839
16840
sage: graph_isom_equivalent_non_edge_labeled_graph(G,inplace=True)
16841
[[[0, 1, 2, 3], [4], [5]]]
16842
sage: G.edges()
16843
[(0, 4, None), (1, 4, None), (1, 5, None), (2, 3, None), (2, 5, None)]
16844
"""
16845
from copy import copy
16846
from sage.graphs.all import Graph, DiGraph
16847
16848
g_has_multiple_edges = g.has_multiple_edges()
16849
16850
if g_has_multiple_edges:
16851
if g._directed:
16852
G = DiGraph(loops=g.allows_loops(),sparse=True)
16853
edge_iter = g._backend.iterator_in_edges(g,True)
16854
else:
16855
G = Graph(loops=g.allows_loops(),sparse=True)
16856
edge_iter = g._backend.iterator_edges(g,True)
16857
for u,v,l in edge_iter:
16858
if not G.has_edge(u,v):
16859
G.add_edge(u,v,[[l,1]])
16860
else:
16861
label_list = copy( G.edge_label(u,v) )
16862
seen_label = False
16863
for i in xrange(len(label_list)):
16864
if label_list[i][0] == l:
16865
label_list[i][1] += 1
16866
G.set_edge_label(u,v,label_list)
16867
seen_label = True
16868
break
16869
if not seen_label:
16870
label_list.append([l,1])
16871
label_list.sort()
16872
G.set_edge_label(u,v,label_list)
16873
if G.order() < g.order():
16874
G.add_vertices(g)
16875
if inplace:
16876
g._backend = G._backend
16877
elif not inplace:
16878
G = copy( g )
16879
else:
16880
G = g
16881
16882
G_order = G.order()
16883
V = range(G_order)
16884
if G.vertices() != V:
16885
relabel_dict = G.relabel(return_map=True)
16886
else:
16887
relabel_dict = dict( (i,i) for i in xrange(G_order) )
16888
if partition is None:
16889
partition = [V]
16890
else:
16891
partition = [ [ relabel_dict[i] for i in part ] for part in partition ]
16892
16893
if G._directed:
16894
edge_iter = G._backend.iterator_in_edges(G,True)
16895
else:
16896
edge_iter = G._backend.iterator_edges(G,True)
16897
16898
edges = [ edge for edge in edge_iter ]
16899
edge_labels = sorted([ label for v1,v2,label in edges if not label == standard_label])
16900
i = 1
16901
while i < len(edge_labels):
16902
if edge_labels[i] == edge_labels[i-1]:
16903
edge_labels.pop(i)
16904
else:
16905
i += 1
16906
i = G_order
16907
edge_partition = [(el,[]) for el in edge_labels]
16908
16909
if g_has_multiple_edges: standard_label = [[standard_label,1]]
16910
16911
for u,v,l in edges:
16912
if not l == standard_label:
16913
index = edge_labels.index(l)
16914
for el, part in edge_partition:
16915
if el == l:
16916
part.append(i)
16917
break
16918
G._backend.add_edge(u,i,None,True)
16919
G._backend.add_edge(i,v,None,True)
16920
G.delete_edge(u,v)
16921
i += 1
16922
elif standard_label is not None:
16923
G._backend.set_edge_label(u,v,None,True)
16924
16925
edge_partition = [el[1] for el in sorted(edge_partition)]
16926
16927
if ignore_edge_labels:
16928
edge_partition = [sum(edge_partition,[])]
16929
new_partition = [ part for part in partition + edge_partition if not part == [] ]
16930
16931
return_data = []
16932
if not inplace:
16933
return_data.append( G )
16934
return_data.append( new_partition )
16935
if return_relabeling:
16936
return_data.append( relabel_dict )
16937
if return_edge_labels:
16938
return_data.append( edge_labels )
16939
return return_data
16940
16941