Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/graphs/generic_graph.py
8815 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.to_dictionary` | Creates a dictionary encoding the graph.
16
:meth:`~GenericGraph.copy` | Return a copy of the graph.
17
:meth:`~GenericGraph.adjacency_matrix` | Returns the adjacency matrix of the (di)graph.
18
:meth:`~GenericGraph.incidence_matrix` | Returns an incidence matrix of the (di)graph
19
:meth:`~GenericGraph.distance_matrix` | Returns the distance matrix of the (strongly) connected (di)graph
20
:meth:`~GenericGraph.weighted_adjacency_matrix` | Returns the weighted adjacency matrix of the graph
21
:meth:`~GenericGraph.kirchhoff_matrix` | Returns the Kirchhoff matrix (a.k.a. the Laplacian) of the graph.
22
:meth:`~GenericGraph.get_boundary` | Returns the boundary of the (di)graph.
23
:meth:`~GenericGraph.set_boundary` | Sets the boundary of the (di)graph.
24
:meth:`~GenericGraph.has_loops` | Returns whether there are loops in the (di)graph.
25
:meth:`~GenericGraph.allows_loops` | Returns whether loops are permitted in the (di)graph.
26
:meth:`~GenericGraph.allow_loops` | Changes whether loops are permitted in the (di)graph.
27
:meth:`~GenericGraph.loops` | Returns any loops in the (di)graph.
28
:meth:`~GenericGraph.has_multiple_edges` | Returns whether there are multiple edges in the (di)graph.
29
:meth:`~GenericGraph.allows_multiple_edges` | Returns whether multiple edges are permitted in the (di)graph.
30
:meth:`~GenericGraph.allow_multiple_edges` | Changes whether multiple edges are permitted in the (di)graph.
31
:meth:`~GenericGraph.multiple_edges` | Returns any multiple edges in the (di)graph.
32
:meth:`~GenericGraph.name` | Returns or sets the graph's name.
33
:meth:`~GenericGraph.weighted` | Whether the (di)graph is to be considered as a weighted (di)graph.
34
:meth:`~GenericGraph.antisymmetric` | Tests whether the graph is antisymmetric
35
:meth:`~GenericGraph.density` | Returns the density
36
:meth:`~GenericGraph.order` | Returns the number of vertices.
37
:meth:`~GenericGraph.size` | Returns the number of edges.
38
:meth:`~GenericGraph.add_vertex` | Creates an isolated vertex.
39
:meth:`~GenericGraph.add_vertices` | Add vertices to the (di)graph from an iterable container
40
:meth:`~GenericGraph.delete_vertex` | Deletes a vertex, removing all incident edges.
41
:meth:`~GenericGraph.delete_vertices` | Remove vertices from the (di)graph taken from an iterable container of vertices.
42
:meth:`~GenericGraph.has_vertex` | Return True if vertex is one of the vertices of this graph.
43
:meth:`~GenericGraph.random_vertex` | Returns a random vertex of self.
44
:meth:`~GenericGraph.random_edge` | Returns a random edge of self.
45
:meth:`~GenericGraph.vertex_boundary` | Returns a list of all vertices in the external boundary of vertices1, intersected with vertices2.
46
:meth:`~GenericGraph.set_vertices` | Associate arbitrary objects with each vertex
47
:meth:`~GenericGraph.set_vertex` | Associate an arbitrary object with a vertex.
48
:meth:`~GenericGraph.get_vertex` | Retrieve the object associated with a given vertex.
49
:meth:`~GenericGraph.get_vertices` | Return a dictionary of the objects associated to each vertex.
50
:meth:`~GenericGraph.loop_vertices` | Returns a list of vertices with loops.
51
:meth:`~GenericGraph.vertex_iterator` | Returns an iterator over the vertices.
52
:meth:`~GenericGraph.neighbor_iterator` | Return an iterator over neighbors of vertex.
53
:meth:`~GenericGraph.vertices` | Return a list of the vertices.
54
:meth:`~GenericGraph.neighbors` | Return a list of neighbors (in and out if directed) of vertex.
55
:meth:`~GenericGraph.merge_vertices` | Merge vertices.
56
:meth:`~GenericGraph.add_edge` | Adds an edge from u and v.
57
:meth:`~GenericGraph.add_edges` | Add edges from an iterable container.
58
:meth:`~GenericGraph.subdivide_edge` | Subdivides an edge `k` times.
59
:meth:`~GenericGraph.subdivide_edges` | Subdivides k times edges from an iterable container.
60
:meth:`~GenericGraph.delete_edge` | Delete the edge from u to v
61
:meth:`~GenericGraph.delete_edges` | Delete edges from an iterable container.
62
:meth:`~GenericGraph.delete_multiedge` | Deletes all edges from u and v.
63
:meth:`~GenericGraph.set_edge_label` | Set the edge label of a given edge.
64
:meth:`~GenericGraph.has_edge` | Returns True if (u, v) is an edge, False otherwise.
65
:meth:`~GenericGraph.edges` | Return a list of edges.
66
:meth:`~GenericGraph.edge_boundary` | Returns a list of edges `(u,v,l)` with `u` in ``vertices1``
67
:meth:`~GenericGraph.edge_iterator` | Returns an iterator over edges.
68
:meth:`~GenericGraph.edges_incident` | Returns incident edges to some vertices.
69
:meth:`~GenericGraph.edge_label` | Returns the label of an edge.
70
:meth:`~GenericGraph.edge_labels` | Returns a list of edge labels.
71
:meth:`~GenericGraph.remove_multiple_edges` | Removes all multiple edges, retaining one edge for each.
72
:meth:`~GenericGraph.remove_loops` | Removes loops on vertices in vertices. If vertices is None, removes all loops.
73
:meth:`~GenericGraph.loop_edges` | Returns a list of all loops in the graph.
74
:meth:`~GenericGraph.number_of_loops` | Returns the number of edges that are loops.
75
:meth:`~GenericGraph.clear` | Empties the graph of vertices and edges and removes name, boundary, associated objects, and position information.
76
:meth:`~GenericGraph.degree` | Gives the degree (in + out for digraphs) of a vertex or of vertices.
77
:meth:`~GenericGraph.average_degree` | Returns the average degree of the graph.
78
:meth:`~GenericGraph.degree_histogram` | Returns a list, whose ith entry is the frequency of degree i.
79
:meth:`~GenericGraph.degree_iterator` | Returns an iterator over the degrees of the (di)graph.
80
:meth:`~GenericGraph.degree_sequence` | Return the degree sequence of this (di)graph.
81
:meth:`~GenericGraph.random_subgraph` | Return a random subgraph that contains each vertex with prob. p.
82
:meth:`~GenericGraph.add_cycle` | Adds a cycle to the graph with the given vertices.
83
:meth:`~GenericGraph.add_path` | Adds a cycle to the graph with the given vertices.
84
:meth:`~GenericGraph.complement` | Returns the complement of the (di)graph.
85
:meth:`~GenericGraph.line_graph` | Returns the line graph of the (di)graph.
86
:meth:`~GenericGraph.to_simple` | Returns a simple version of itself (i.e., undirected and loops and multiple edges are removed).
87
:meth:`~GenericGraph.disjoint_union` | Returns the disjoint union of self and other.
88
:meth:`~GenericGraph.union` | Returns the union of self and other.
89
:meth:`~GenericGraph.relabel` | Relabels the vertices of ``self``
90
:meth:`~GenericGraph.degree_to_cell` | Returns the number of edges from vertex to an edge in cell.
91
:meth:`~GenericGraph.subgraph` | Returns the subgraph containing the given vertices and edges.
92
:meth:`~GenericGraph.is_subgraph` | Tests whether self is a subgraph of other.
93
94
**Graph products:**
95
96
.. csv-table::
97
:class: contentstable
98
:widths: 30, 70
99
:delim: |
100
101
:meth:`~GenericGraph.cartesian_product` | Returns the Cartesian product of self and other.
102
:meth:`~GenericGraph.tensor_product` | Returns the tensor product, also called the categorical product, of self and other.
103
:meth:`~GenericGraph.lexicographic_product` | Returns the lexicographic product of self and other.
104
:meth:`~GenericGraph.strong_product` | Returns the strong product of self and other.
105
:meth:`~GenericGraph.disjunctive_product` | Returns the disjunctive product of self and other.
106
107
**Paths and cycles:**
108
109
.. csv-table::
110
:class: contentstable
111
:widths: 30, 70
112
:delim: |
113
114
:meth:`~GenericGraph.eulerian_orientation` | Returns a DiGraph which is an Eulerian orientation of the current graph.
115
:meth:`~GenericGraph.eulerian_circuit` | Return a list of edges forming an eulerian circuit if one exists.
116
:meth:`~GenericGraph.cycle_basis` | Returns a list of cycles which form a basis of the cycle space of ``self``.
117
: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.
118
:meth:`~GenericGraph.all_paths` | Returns a list of all paths (also lists) between a pair of vertices in the (di)graph.
119
:meth:`~GenericGraph.triangles_count` | Returns the number of triangles in the (di)graph.
120
121
**Linear algebra:**
122
123
.. csv-table::
124
:class: contentstable
125
:widths: 30, 70
126
:delim: |
127
128
:meth:`~GenericGraph.spectrum` | Returns a list of the eigenvalues of the adjacency matrix.
129
:meth:`~GenericGraph.eigenvectors` | Returns the *right* eigenvectors of the adjacency matrix of the graph.
130
:meth:`~GenericGraph.eigenspaces` | Returns the *right* eigenspaces of the adjacency matrix of the graph.
131
132
**Some metrics:**
133
134
.. csv-table::
135
:class: contentstable
136
:widths: 30, 70
137
:delim: |
138
139
:meth:`~GenericGraph.cluster_triangles` | Returns the number of triangles for nbunch of vertices as a dictionary keyed by vertex.
140
:meth:`~GenericGraph.clustering_average` | Returns the average clustering coefficient.
141
:meth:`~GenericGraph.clustering_coeff` | Returns the clustering coefficient for each vertex in nbunch
142
:meth:`~GenericGraph.cluster_transitivity` | Returns the transitivity (fraction of transitive triangles) of the graph.
143
:meth:`~GenericGraph.szeged_index` | Returns the Szeged index of the graph.
144
145
146
**Automorphism group:**
147
148
.. csv-table::
149
:class: contentstable
150
:widths: 30, 70
151
:delim: |
152
153
:meth:`~GenericGraph.coarsest_equitable_refinement` | Returns the coarsest partition which is finer than the input partition, and equitable with respect to self.
154
: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.
155
:meth:`~GenericGraph.is_vertex_transitive` | Returns whether the automorphism group of self is transitive within the partition provided
156
:meth:`~GenericGraph.is_isomorphic` | Tests for isomorphism between self and other.
157
: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.
158
159
**Graph properties:**
160
161
.. csv-table::
162
:class: contentstable
163
:widths: 30, 70
164
:delim: |
165
166
:meth:`~GenericGraph.is_eulerian` | Return true if the graph has a (closed) tour that visits each edge exactly once.
167
:meth:`~GenericGraph.is_planar` | Tests whether the graph is planar.
168
:meth:`~GenericGraph.is_circular_planar` | Tests whether the graph is circular planar (outerplanar)
169
:meth:`~GenericGraph.is_regular` | Return ``True`` if this graph is (`k`-)regular.
170
:meth:`~GenericGraph.is_chordal` | Tests whether the given graph is chordal.
171
:meth:`~GenericGraph.is_circulant` | Tests whether the graph is a circulant graph.
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.blocks_and_cuts_tree` | Computes the blocks-and-cuts tree of the graph.
230
:meth:`~GenericGraph.is_cut_edge` | Returns True if the input edge is a cut-edge or a bridge.
231
:meth:`~GenericGraph.is_cut_vertex` | Returns True if the input vertex is a cut-vertex.
232
:meth:`~GenericGraph.edge_cut` | Returns a minimum edge cut between vertices `s` and `t`
233
:meth:`~GenericGraph.vertex_cut` | Returns a minimum vertex cut between non-adjacent vertices `s` and `t`
234
:meth:`~GenericGraph.flow` | Returns a maximum flow in the graph from ``x`` to ``y``
235
:meth:`~GenericGraph.edge_disjoint_paths` | Returns a list of edge-disjoint paths between two vertices
236
:meth:`~GenericGraph.vertex_disjoint_paths` | Returns a list of vertex-disjoint paths between two vertices as given by Menger's theorem.
237
:meth:`~GenericGraph.edge_connectivity` | Returns the edge connectivity of the graph.
238
:meth:`~GenericGraph.vertex_connectivity` | Returns the vertex connectivity of the graph.
239
:meth:`~GenericGraph.transitive_closure` | Computes the transitive closure of a graph and returns it.
240
:meth:`~GenericGraph.transitive_reduction` | Returns a transitive reduction of a graph.
241
:meth:`~GenericGraph.min_spanning_tree` | Returns the edges of a minimum spanning tree.
242
:meth:`~GenericGraph.spanning_trees_count` | Returns the number of spanning trees in a graph.
243
244
**Plot/embedding-related methods:**
245
246
.. csv-table::
247
:class: contentstable
248
:widths: 30, 70
249
:delim: |
250
251
:meth:`~GenericGraph.set_embedding` | Sets a combinatorial embedding dictionary to ``_embedding`` attribute.
252
:meth:`~GenericGraph.get_embedding` | Returns the attribute _embedding if it exists.
253
:meth:`~GenericGraph.faces` | Returns the faces of an embedded graph.
254
:meth:`~GenericGraph.get_pos` | Returns the position dictionary
255
:meth:`~GenericGraph.set_pos` | Sets the position dictionary.
256
:meth:`~GenericGraph.set_planar_positions` | Compute a planar layout for self using Schnyder's algorithm
257
:meth:`~GenericGraph.layout_planar` | Uses Schnyder's algorithm to compute a planar layout for self.
258
:meth:`~GenericGraph.is_drawn_free_of_edge_crossings` | Tests whether the position dictionary gives a planar embedding.
259
:meth:`~GenericGraph.latex_options` | Returns an instance of :class:`~sage.graphs.graph_latex.GraphLatex` for the graph.
260
:meth:`~GenericGraph.set_latex_options` | Sets multiple options for rendering a graph with LaTeX.
261
:meth:`~GenericGraph.layout` | Returns a layout for the vertices of this graph.
262
:meth:`~GenericGraph.layout_spring` | Computes a spring layout for this graph
263
:meth:`~GenericGraph.layout_ranked` | Computes a ranked layout for this graph
264
:meth:`~GenericGraph.layout_extend_randomly` | Extends randomly a partial layout
265
:meth:`~GenericGraph.layout_circular` | Computes a circular layout for this graph
266
:meth:`~GenericGraph.layout_tree` | Computes an ordered tree layout for this graph, which should be a tree (no non-oriented cycles).
267
:meth:`~GenericGraph.layout_graphviz` | Calls ``graphviz`` to compute a layout of the vertices of this graph.
268
:meth:`~GenericGraph.graphplot` | Returns a GraphPlot object.
269
:meth:`~GenericGraph.plot` | Returns a graphics object representing the (di)graph.
270
:meth:`~GenericGraph.show` | Shows the (di)graph.
271
:meth:`~GenericGraph.plot3d` | Plot a graph in three dimensions.
272
:meth:`~GenericGraph.show3d` | Plots the graph using Tachyon, and shows the resulting plot.
273
:meth:`~GenericGraph.graphviz_string` | Returns a representation in the dot language.
274
:meth:`~GenericGraph.graphviz_to_file_named` | Write a representation in the dot in a file.
275
276
**Algorithmically hard stuff:**
277
278
.. csv-table::
279
:class: contentstable
280
:widths: 30, 70
281
:delim: |
282
283
:meth:`~GenericGraph.steiner_tree` | Returns a tree of minimum weight connecting the given set of vertices.
284
:meth:`~GenericGraph.edge_disjoint_spanning_trees` | Returns the desired number of edge-disjoint spanning trees/arborescences.
285
:meth:`~GenericGraph.feedback_vertex_set` | Computes the minimum feedback vertex set of a (di)graph.
286
:meth:`~GenericGraph.multiway_cut` | Returns a minimum edge multiway cut
287
:meth:`~GenericGraph.max_cut` | Returns a maximum edge cut of the graph.
288
:meth:`~GenericGraph.longest_path` | Returns a longest path of ``self``.
289
:meth:`~GenericGraph.traveling_salesman_problem` | Solves the traveling salesman problem (TSP)
290
:meth:`~GenericGraph.is_hamiltonian` | Tests whether the current graph is Hamiltonian.
291
:meth:`~GenericGraph.hamiltonian_cycle` | Returns a Hamiltonian cycle/circuit of the current graph/digraph
292
:meth:`~GenericGraph.multicommodity_flow` | Solves a multicommodity flow problem.
293
:meth:`~GenericGraph.disjoint_routed_paths` | Returns a set of disjoint routed paths.
294
:meth:`~GenericGraph.dominating_set` | Returns a minimum dominating set of the graph
295
:meth:`~GenericGraph.subgraph_search` | Returns a copy of ``G`` in ``self``.
296
:meth:`~GenericGraph.subgraph_search_count` | Returns the number of labelled occurences of ``G`` in ``self``.
297
:meth:`~GenericGraph.subgraph_search_iterator` | Returns an iterator over the labelled copies of ``G`` in ``self``.
298
:meth:`~GenericGraph.characteristic_polynomial` | Returns the characteristic polynomial of the adjacency matrix of the (di)graph.
299
:meth:`~GenericGraph.genus` | Returns the minimal genus of the graph.
300
301
Methods
302
-------
303
"""
304
305
from sage.misc.decorators import options
306
from sage.misc.cachefunc import cached_method
307
from sage.misc.prandom import random
308
from sage.rings.integer_ring import ZZ
309
from sage.rings.integer import Integer
310
from sage.rings.rational import Rational
311
from generic_graph_pyx import GenericGraph_pyx, spring_layout_fast
312
from sage.graphs.dot2tex_utils import assert_have_dot2tex
313
from sage.misc.superseded import deprecation, deprecated_function_alias
314
315
class GenericGraph(GenericGraph_pyx):
316
"""
317
Base class for graphs and digraphs.
318
"""
319
320
# Nice defaults for plotting arrays of graphs (see sage.misc.functional.show)
321
graphics_array_defaults = {'layout': 'circular', 'vertex_size':50, 'vertex_labels':False, 'graph_border':True}
322
323
def __init__(self):
324
r"""
325
Every graph carries a dictionary of options, which is set
326
here to ``None``. Some options are added to the global
327
:data:`sage.misc.latex.latex` instance which will insure
328
that if LaTeX is used to render the graph,
329
then the right packages are loaded and MathJax reacts
330
properly.
331
332
Most other initialization is done in the directed
333
and undirected subclasses.
334
335
TESTS::
336
337
sage: g = Graph()
338
sage: g
339
Graph on 0 vertices
340
"""
341
self._latex_opts = None
342
343
def __add__(self, other_graph):
344
"""
345
Returns the disjoint union of self and other.
346
347
If there are common vertices to both, they will be renamed.
348
349
EXAMPLES::
350
351
sage: G = graphs.CycleGraph(3)
352
sage: H = graphs.CycleGraph(4)
353
sage: J = G + H; J
354
Cycle graph disjoint_union Cycle graph: Graph on 7 vertices
355
sage: J.vertices()
356
[0, 1, 2, 3, 4, 5, 6]
357
"""
358
if isinstance(other_graph, GenericGraph):
359
return self.disjoint_union(other_graph, verbose_relabel=False)
360
361
def __eq__(self, other):
362
"""
363
Comparison of self and other. For equality, must be in the same
364
class, have the same settings for loops and multiedges, output the
365
same vertex list (in order) and the same adjacency matrix.
366
367
Note that this is _not_ an isomorphism test.
368
369
EXAMPLES::
370
371
sage: G = graphs.EmptyGraph()
372
sage: H = Graph()
373
sage: G == H
374
True
375
sage: G.to_directed() == H.to_directed()
376
True
377
sage: G = graphs.RandomGNP(8,.9999)
378
sage: H = graphs.CompleteGraph(8)
379
sage: G == H # most often true
380
True
381
sage: G = Graph( {0:[1,2,3,4,5,6,7]} )
382
sage: H = Graph( {1:[0], 2:[0], 3:[0], 4:[0], 5:[0], 6:[0], 7:[0]} )
383
sage: G == H
384
True
385
sage: G.allow_loops(True)
386
sage: G == H
387
False
388
sage: G = graphs.RandomGNP(9,.3).to_directed()
389
sage: H = graphs.RandomGNP(9,.3).to_directed()
390
sage: G == H # most often false
391
False
392
sage: G = Graph(multiedges=True, sparse=True)
393
sage: G.add_edge(0,1)
394
sage: H = copy(G)
395
sage: H.add_edge(0,1)
396
sage: G == H
397
False
398
399
Note that graphs must be considered weighted, or Sage will not pay
400
attention to edge label data in equality testing::
401
402
sage: foo = Graph(sparse=True)
403
sage: foo.add_edges([(0, 1, 1), (0, 2, 2)])
404
sage: bar = Graph(sparse=True)
405
sage: bar.add_edges([(0, 1, 2), (0, 2, 1)])
406
sage: foo == bar
407
True
408
sage: foo.weighted(True)
409
sage: foo == bar
410
False
411
sage: bar.weighted(True)
412
sage: foo == bar
413
False
414
415
"""
416
# inputs must be (di)graphs:
417
if not isinstance(other, GenericGraph):
418
raise TypeError("cannot compare graph to non-graph (%s)"%str(other))
419
from sage.graphs.all import Graph
420
g1_is_graph = isinstance(self, Graph) # otherwise, DiGraph
421
g2_is_graph = isinstance(other, Graph) # otherwise, DiGraph
422
423
if (g1_is_graph != g2_is_graph):
424
return False
425
if self.allows_multiple_edges() != other.allows_multiple_edges():
426
return False
427
if self.allows_loops() != other.allows_loops():
428
return False
429
if self.order() != other.order():
430
return False
431
if self.size() != other.size():
432
return False
433
if any(x not in other for x in self):
434
return False
435
if self._weighted != other._weighted:
436
return False
437
438
verts = self.vertices()
439
# Finally, we are prepared to check edges:
440
if not self.allows_multiple_edges():
441
for i in verts:
442
for j in verts:
443
if self.has_edge(i,j) != other.has_edge(i,j):
444
return False
445
if self.has_edge(i,j) and self._weighted and other._weighted:
446
if self.edge_label(i,j) != other.edge_label(i,j):
447
return False
448
return True
449
else:
450
for i in verts:
451
for j in verts:
452
if self.has_edge(i, j):
453
edges1 = self.edge_label(i, j)
454
else:
455
edges1 = []
456
if other.has_edge(i, j):
457
edges2 = other.edge_label(i, j)
458
else:
459
edges2 = []
460
if len(edges1) != len(edges2):
461
return False
462
if sorted(edges1) != sorted(edges2) and self._weighted and other._weighted:
463
return False
464
return True
465
466
@cached_method
467
def __hash__(self):
468
"""
469
Only immutable graphs are hashable.
470
471
The hash value of an immutable graph relies on the tuple
472
of vertices and the tuple of edges. The resulting value
473
is cached.
474
475
EXAMPLE::
476
477
sage: G = graphs.PetersenGraph()
478
sage: {G:1}[G]
479
Traceback (most recent call last):
480
...
481
TypeError: This graph is mutable, and thus not hashable. Create
482
an immutable copy by `g.copy(immutable=True)`
483
sage: G_imm = Graph(G, data_structure="static_sparse")
484
sage: G_imm == G
485
True
486
sage: {G_imm:1}[G_imm] # indirect doctest
487
1
488
sage: G_imm.__hash__() is G_imm.__hash__()
489
True
490
491
"""
492
if getattr(self, "_immutable", False):
493
return hash((tuple(self.vertices()), tuple(self.edges())))
494
raise TypeError("This graph is mutable, and thus not hashable. "
495
"Create an immutable copy by `g.copy(immutable=True)`")
496
497
def __mul__(self, n):
498
"""
499
Returns the sum of a graph with itself n times.
500
501
EXAMPLES::
502
503
sage: G = graphs.CycleGraph(3)
504
sage: H = G*3; H
505
Cycle graph disjoint_union Cycle graph disjoint_union Cycle graph: Graph on 9 vertices
506
sage: H.vertices()
507
[0, 1, 2, 3, 4, 5, 6, 7, 8]
508
sage: H = G*1; H
509
Cycle graph: Graph on 3 vertices
510
"""
511
if isinstance(n, (int, long, Integer)):
512
if n < 1:
513
raise TypeError('multiplication of a graph and a nonpositive integer is not defined')
514
if n == 1:
515
from copy import copy
516
return copy(self)
517
return sum([self]*(n-1), self)
518
else:
519
raise TypeError('multiplication of a graph and something other than an integer is not defined')
520
521
def __ne__(self, other):
522
"""
523
Tests for inequality, complement of __eq__.
524
525
EXAMPLES::
526
527
sage: g = Graph()
528
sage: g2 = copy(g)
529
sage: g == g
530
True
531
sage: g != g
532
False
533
sage: g2 == g
534
True
535
sage: g2 != g
536
False
537
sage: g is g
538
True
539
sage: g2 is g
540
False
541
"""
542
return (not (self == other))
543
544
def __rmul__(self, n):
545
"""
546
Returns the sum of a graph with itself n times.
547
548
EXAMPLES::
549
550
sage: G = graphs.CycleGraph(3)
551
sage: H = int(3)*G; H
552
Cycle graph disjoint_union Cycle graph disjoint_union Cycle graph: Graph on 9 vertices
553
sage: H.vertices()
554
[0, 1, 2, 3, 4, 5, 6, 7, 8]
555
"""
556
return self*n
557
558
def __str__(self):
559
"""
560
str(G) returns the name of the graph, unless the name is the empty
561
string, in which case it returns the default representation.
562
563
EXAMPLES::
564
565
sage: G = graphs.PetersenGraph()
566
sage: str(G)
567
'Petersen graph'
568
"""
569
if self.name():
570
return self.name()
571
else:
572
return repr(self)
573
574
def _bit_vector(self):
575
"""
576
Returns a string representing the edges of the (simple) graph for
577
graph6 and dig6 strings.
578
579
EXAMPLES::
580
581
sage: G = graphs.PetersenGraph()
582
sage: G._bit_vector()
583
'101001100110000010000001001000010110000010110'
584
sage: len([a for a in G._bit_vector() if a == '1'])
585
15
586
sage: G.num_edges()
587
15
588
"""
589
self._scream_if_not_simple()
590
vertices = self.vertices()
591
n = len(vertices)
592
if self._directed:
593
total_length = n*n
594
bit = lambda x,y : x*n + y
595
else:
596
total_length = int(n*(n - 1))/int(2)
597
n_ch_2 = lambda b : int(b*(b-1))/int(2)
598
bit = lambda x,y : n_ch_2(max([x,y])) + min([x,y])
599
bit_vector = set()
600
for u,v,_ in self.edge_iterator():
601
bit_vector.add(bit(vertices.index(u), vertices.index(v)))
602
bit_vector = sorted(bit_vector)
603
s = []
604
j = 0
605
for i in bit_vector:
606
s.append( '0'*(i - j) + '1' )
607
j = i + 1
608
s = "".join(s)
609
s += '0'*(total_length-len(s))
610
return s
611
612
def _latex_(self):
613
r"""
614
615
Returns a string to render the graph using LaTeX.
616
617
To adjust the string, use the
618
:meth:`set_latex_options` method to set options,
619
or call the :meth:`latex_options` method to
620
get a :class:`~sage.graphs.graph_latex.GraphLatex`
621
object that may be used to also customize the
622
output produced here. Possible options are documented at
623
:meth:`sage.graphs.graph_latex.GraphLatex.set_option`.
624
625
EXAMPLES::
626
627
sage: from sage.graphs.graph_latex import check_tkz_graph
628
sage: check_tkz_graph() # random - depends on TeX installation
629
sage: g = graphs.CompleteGraph(2)
630
sage: print g._latex_()
631
\begin{tikzpicture}
632
%
633
\useasboundingbox (0,0) rectangle (5.0cm,5.0cm);
634
%
635
\definecolor{cv0}{rgb}{0.0,0.0,0.0}
636
\definecolor{cfv0}{rgb}{1.0,1.0,1.0}
637
\definecolor{clv0}{rgb}{0.0,0.0,0.0}
638
\definecolor{cv1}{rgb}{0.0,0.0,0.0}
639
\definecolor{cfv1}{rgb}{1.0,1.0,1.0}
640
\definecolor{clv1}{rgb}{0.0,0.0,0.0}
641
\definecolor{cv0v1}{rgb}{0.0,0.0,0.0}
642
%
643
\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}
644
\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}
645
%
646
\Edge[lw=0.1cm,style={color=cv0v1,},](v0)(v1)
647
%
648
\end{tikzpicture}
649
"""
650
from sage.graphs.graph_latex import setup_latex_preamble
651
setup_latex_preamble()
652
653
return self.latex_options().latex()
654
655
def _matrix_(self, R=None):
656
"""
657
Returns the adjacency matrix of the graph over the specified ring.
658
659
EXAMPLES::
660
661
sage: G = graphs.CompleteBipartiteGraph(2,3)
662
sage: m = matrix(G); m.parent()
663
Full MatrixSpace of 5 by 5 dense matrices over Integer Ring
664
sage: m
665
[0 0 1 1 1]
666
[0 0 1 1 1]
667
[1 1 0 0 0]
668
[1 1 0 0 0]
669
[1 1 0 0 0]
670
sage: G._matrix_()
671
[0 0 1 1 1]
672
[0 0 1 1 1]
673
[1 1 0 0 0]
674
[1 1 0 0 0]
675
[1 1 0 0 0]
676
sage: factor(m.charpoly())
677
x^3 * (x^2 - 6)
678
"""
679
if R is None:
680
return self.am()
681
else:
682
return self.am().change_ring(R)
683
684
def _repr_(self):
685
"""
686
Return a string representation of self.
687
688
EXAMPLES::
689
690
sage: G = graphs.PetersenGraph()
691
sage: G._repr_()
692
'Petersen graph: Graph on 10 vertices'
693
"""
694
name = ""
695
if self.allows_loops():
696
name += "looped "
697
if self.allows_multiple_edges():
698
name += "multi-"
699
if self._directed:
700
name += "di"
701
name += "graph on %d vert"%self.order()
702
if self.order() == 1:
703
name += "ex"
704
else:
705
name += "ices"
706
name = name.capitalize()
707
if self.name() != '':
708
name = self.name() + ": " + name
709
return name
710
711
### Formats
712
713
def __copy__(self, implementation='c_graph', data_structure=None,
714
sparse=None, immutable=None):
715
"""
716
Return a copy of the graph.
717
718
INPUT:
719
720
- ``implementation`` - string (default: 'c_graph') the implementation
721
goes here. Current options are only 'networkx' or 'c_graph'.
722
723
- ``sparse`` (boolean) -- ``sparse=True`` is an alias for
724
``data_structure="sparse"``, and ``sparse=False`` is an alias for
725
``data_structure="dense"``. Only used when
726
``implementation='c_graph'`` and ``data_structure=None``.
727
728
- ``data_structure`` -- one of ``"sparse"``, ``"static_sparse"``, or
729
``"dense"``. See the documentation of :class:`Graph` or
730
:class:`DiGraph`. Only used when ``implementation='c_graph'``.
731
732
- ``immutable`` (boolean) -- whether to create a mutable/immutable
733
copy. Only used when ``implementation='c_graph'`` and
734
``data_structure=None``.
735
736
* ``immutable=None`` (default) means that the graph and its copy will
737
behave the same way.
738
739
* ``immutable=True`` is a shortcut for
740
``data_structure='static_sparse'`` and ``implementation='c_graph'``
741
742
* ``immutable=False`` sets ``implementation`` to ``'c_graph'``. When
743
``immutable=False`` is used to copy an immutable graph, the data
744
structure used is ``"sparse"`` unless anything else is specified.
745
746
.. NOTE::
747
748
If the graph uses
749
:class:`~sage.graphs.base.static_sparse_backend.StaticSparseBackend`
750
and the ``_immutable`` flag, then ``self`` is returned rather than a
751
copy (unless one of the optional arguments is used).
752
753
OUTPUT:
754
755
A Graph object.
756
757
.. warning::
758
759
Please use this method only if you need to copy but change the
760
underlying implementation. Otherwise simply do ``copy(g)``
761
instead of ``g.copy()``.
762
763
EXAMPLES::
764
765
sage: g=Graph({0:[0,1,1,2]},loops=True,multiedges=True,sparse=True)
766
sage: g==copy(g)
767
True
768
sage: g=DiGraph({0:[0,1,1,2],1:[0,1]},loops=True,multiedges=True,sparse=True)
769
sage: g==copy(g)
770
True
771
772
Note that vertex associations are also kept::
773
774
sage: d = {0 : graphs.DodecahedralGraph(), 1 : graphs.FlowerSnark(), 2 : graphs.MoebiusKantorGraph(), 3 : graphs.PetersenGraph() }
775
sage: T = graphs.TetrahedralGraph()
776
sage: T.set_vertices(d)
777
sage: T2 = copy(T)
778
sage: T2.get_vertex(0)
779
Dodecahedron: Graph on 20 vertices
780
781
Notice that the copy is at least as deep as the objects::
782
783
sage: T2.get_vertex(0) is T.get_vertex(0)
784
False
785
786
Examples of the keywords in use::
787
788
sage: G = graphs.CompleteGraph(19)
789
sage: H = G.copy(implementation='c_graph')
790
sage: H == G; H is G
791
True
792
False
793
sage: G1 = G.copy(sparse=True)
794
sage: G1==G
795
True
796
sage: G1 is G
797
False
798
sage: G2 = copy(G)
799
sage: G2 is G
800
False
801
802
TESTS:
803
804
We make copies of the ``_pos`` and ``_boundary`` attributes::
805
806
sage: g = graphs.PathGraph(3)
807
sage: h = copy(g)
808
sage: h._pos is g._pos
809
False
810
sage: h._boundary is g._boundary
811
False
812
813
We make sure that one can make immutable copies by providing the
814
``data_structure`` optional argument, and that copying an immutable graph
815
returns the graph::
816
817
sage: G = graphs.PetersenGraph()
818
sage: hash(G)
819
Traceback (most recent call last):
820
...
821
TypeError: This graph is mutable, and thus not hashable. Create an
822
immutable copy by `g.copy(immutable=True)`
823
sage: g = G.copy(immutable=True)
824
sage: hash(g) # random
825
1833517720
826
sage: g==G
827
True
828
sage: g is copy(g) is g.copy()
829
True
830
831
``immutable=True`` is a short-cut for ``data_structure='static_sparse'``::
832
833
sage: g is g.copy(data_structure='static_sparse') is g.copy(immutable=True)
834
True
835
836
If a graph pretends to be immutable, but does not use the static sparse
837
backend, then the copy is not identic with the graph, even though it is
838
considered to be hashable::
839
840
sage: P = Poset(([1,2,3,4], [[1,3],[1,4],[2,3]]), linear_extension=True, facade = False)
841
sage: H = P.hasse_diagram()
842
sage: H._immutable = True
843
sage: hash(H) # random
844
-1843552882
845
sage: copy(H) is H
846
False
847
848
TESTS:
849
850
Bad input::
851
852
sage: G.copy(data_structure="sparse", sparse=False)
853
Traceback (most recent call last):
854
...
855
ValueError: You cannot define 'immutable' or 'sparse' when 'data_structure' has a value.
856
sage: G.copy(data_structure="sparse", immutable=True)
857
Traceback (most recent call last):
858
...
859
ValueError: You cannot define 'immutable' or 'sparse' when 'data_structure' has a value.
860
sage: G.copy(immutable=True, sparse=False)
861
Traceback (most recent call last):
862
...
863
ValueError: There is no dense immutable backend at the moment.
864
865
Which backend ?::
866
867
sage: G.copy(data_structure="sparse")._backend
868
<class 'sage.graphs.base.sparse_graph.SparseGraphBackend'>
869
sage: G.copy(data_structure="dense")._backend
870
<class 'sage.graphs.base.dense_graph.DenseGraphBackend'>
871
sage: G.copy(data_structure="static_sparse")._backend
872
<class 'sage.graphs.base.static_sparse_backend.StaticSparseBackend'>
873
sage: G.copy(immutable=True)._backend
874
<class 'sage.graphs.base.static_sparse_backend.StaticSparseBackend'>
875
sage: G.copy(immutable=True, sparse=True)._backend
876
<class 'sage.graphs.base.static_sparse_backend.StaticSparseBackend'>
877
sage: G.copy(immutable=False, sparse=True)._backend
878
<class 'sage.graphs.base.sparse_graph.SparseGraphBackend'>
879
sage: G.copy(immutable=False, sparse=False)._backend
880
<class 'sage.graphs.base.sparse_graph.SparseGraphBackend'>
881
sage: Graph(implementation="networkx").copy(implementation='c_graph')._backend
882
<class 'sage.graphs.base.sparse_graph.SparseGraphBackend'>
883
884
Fake immutable graphs::
885
886
sage: G._immutable = True
887
sage: G.copy()._backend
888
<class 'sage.graphs.base.sparse_graph.SparseGraphBackend'>
889
"""
890
# Which data structure should be used ?
891
if implementation != 'c_graph':
892
# We do not care about the value of data_structure. But let's check
893
# the user did not define too much.
894
if data_structure != None or immutable != None or sparse != None:
895
raise ValueError("'data_structure' 'immutable' and 'sparse' can"
896
" only be defined when 'implementation'='c_graph'")
897
elif data_structure != None:
898
# data_structure is already defined so there is nothing left to do
899
# here ! Did the user try to define too much ?
900
if immutable != None or sparse != None:
901
raise ValueError("You cannot define 'immutable' or 'sparse' "
902
"when 'data_structure' has a value.")
903
# At this point :
904
# - implementation is 'c_graph'
905
# - data_structure is None.
906
elif immutable is True:
907
data_structure = 'static_sparse'
908
if sparse is False:
909
raise ValueError("There is no dense immutable backend at the moment.")
910
elif immutable is False:
911
# If the users requests a mutable graph and input is immutable, we
912
# chose the 'sparse' cgraph backend. Unless the user explicitly
913
# asked for something different.
914
if getattr(self, '_immutable', False):
915
data_structure = 'dense' if sparse is False else 'sparse'
916
elif sparse is True:
917
data_structure = "sparse"
918
elif sparse is False:
919
data_structure = "dense"
920
921
# Immutable copy of an immutable graph ? return self !
922
if getattr(self, '_immutable', False):
923
from sage.graphs.base.static_sparse_backend import StaticSparseBackend
924
if (isinstance(self._backend, StaticSparseBackend) and
925
implementation=='c_graph' and
926
(data_structure=='static_sparse' or data_structure is None)):
927
return self
928
929
if data_structure is None:
930
from sage.graphs.base.dense_graph import DenseGraphBackend
931
if isinstance(self._backend, DenseGraphBackend):
932
data_structure = "dense"
933
else:
934
data_structure = "sparse"
935
936
from copy import copy
937
G = self.__class__(self, name=self.name(), pos=copy(self._pos), boundary=copy(self._boundary), implementation=implementation, data_structure=data_structure)
938
939
attributes_to_copy = ('_assoc', '_embedding')
940
for attr in attributes_to_copy:
941
if hasattr(self, attr):
942
copy_attr = {}
943
old_attr = getattr(self, attr)
944
if isinstance(old_attr, dict):
945
for v,value in old_attr.iteritems():
946
try:
947
copy_attr[v] = value.copy()
948
except AttributeError:
949
from copy import copy
950
copy_attr[v] = copy(value)
951
setattr(G, attr, copy_attr)
952
else:
953
setattr(G, attr, copy(old_attr))
954
955
G._weighted = self._weighted
956
return G
957
958
copy = __copy__
959
960
def _scream_if_not_simple(self, allow_loops=False, allow_multiple_edges=False):
961
r"""
962
Raises an exception if the graph is not simple.
963
964
This function is called by some functions of the Graph library when they
965
have not been written for simple graphs only (i.e. no loops nor multiple
966
edges). It raises an exception inviting the user to convert the graph to
967
a simple graph first, before calling the function again.
968
969
Note that this function does not checl the existence of loops or
970
multiple edges, which would take linear time : it merely checks that the
971
graph *does not allow* multiple edges nor loops, which takes a constant
972
time.
973
974
INPUT:
975
976
- ``allow_loops`` (boolean) -- whether to tolerate loops. Set to
977
``False`` by default.
978
979
- ``allow_multiple_edges`` (boolean) -- whether to tolerate multiple
980
edges. Set to ``False`` by default.
981
982
.. SEEALSO::
983
984
* :meth:`allow_loops`
985
* :meth:`allow_multiple_edges`
986
987
EXAMPLES::
988
989
sage: g = graphs.PetersenGraph()
990
sage: g._scream_if_not_simple()
991
sage: g.allow_loops(True)
992
sage: g.allow_multiple_edges(True)
993
sage: g._scream_if_not_simple()
994
Traceback (most recent call last):
995
...
996
ValueError: This method is not known to work on graphs with multiedges/loops. Perhaps this method can be updated to handle them, but in the meantime if you want to use it please disallow multiedges/loops using allow_multiple_edges() and allow_loops().
997
sage: g.allow_multiple_edges(True)
998
sage: g._scream_if_not_simple()
999
Traceback (most recent call last):
1000
...
1001
ValueError: This method is not known to work on graphs with multiedges/loops. Perhaps this method can be updated to handle them, but in the meantime if you want to use it please disallow multiedges/loops using allow_multiple_edges() and allow_loops().
1002
sage: g._scream_if_not_simple(allow_loops=True)
1003
Traceback (most recent call last):
1004
...
1005
ValueError: This method is not known to work on graphs with multiedges. Perhaps this method can be updated to handle them, but in the meantime if you want to use it please disallow multiedges using allow_multiple_edges().
1006
sage: g._scream_if_not_simple(allow_multiple_edges=True)
1007
Traceback (most recent call last):
1008
...
1009
ValueError: This method is not known to work on graphs with loops. Perhaps this method can be updated to handle them, but in the meantime if you want to use it please disallow loops using allow_loops().
1010
"""
1011
if ((not allow_loops and self.allows_loops()) or
1012
(not allow_multiple_edges and self.allows_multiple_edges())):
1013
if allow_loops is False and allow_multiple_edges is False:
1014
name = "multiedges/loops"
1015
functions = "allow_multiple_edges() and allow_loops()"
1016
elif allow_loops is False:
1017
name = "loops"
1018
functions = "allow_loops()"
1019
else:
1020
name = "multiedges"
1021
functions = "allow_multiple_edges()"
1022
msg = ("This method is not known to work on graphs with "+name+". "+
1023
"Perhaps this method can be updated to handle them, but in the "+
1024
"meantime if you want to use it please disallow "+name+" using "+
1025
functions+".")
1026
raise ValueError(msg)
1027
1028
def networkx_graph(self, copy=True):
1029
"""
1030
Creates a new NetworkX graph from the Sage graph.
1031
1032
INPUT:
1033
1034
1035
- ``copy`` - if False, and the underlying
1036
implementation is a NetworkX graph, then the actual object itself
1037
is returned.
1038
1039
1040
EXAMPLES::
1041
1042
sage: G = graphs.TetrahedralGraph()
1043
sage: N = G.networkx_graph()
1044
sage: type(N)
1045
<class 'networkx.classes.graph.Graph'>
1046
1047
::
1048
1049
sage: G = graphs.TetrahedralGraph()
1050
sage: G = Graph(G, implementation='networkx')
1051
sage: N = G.networkx_graph()
1052
sage: G._backend._nxg is N
1053
False
1054
1055
::
1056
1057
sage: G = Graph(graphs.TetrahedralGraph(), implementation='networkx')
1058
sage: N = G.networkx_graph(copy=False)
1059
sage: G._backend._nxg is N
1060
True
1061
"""
1062
try:
1063
if copy:
1064
return self._backend._nxg.copy()
1065
else:
1066
return self._backend._nxg
1067
except Exception:
1068
import networkx
1069
if self._directed and self.allows_multiple_edges():
1070
class_type = networkx.MultiDiGraph
1071
elif self._directed:
1072
class_type = networkx.DiGraph
1073
elif self.allows_multiple_edges():
1074
class_type = networkx.MultiGraph
1075
else:
1076
class_type = networkx.Graph
1077
N = class_type(selfloops=self.allows_loops(), multiedges=self.allows_multiple_edges(),
1078
name=self.name())
1079
N.add_nodes_from(self.vertices())
1080
for u,v,l in self.edges():
1081
if l is None:
1082
N.add_edge(u,v)
1083
else:
1084
from networkx import NetworkXError
1085
try:
1086
N.add_edge(u,v,l)
1087
except (TypeError, ValueError, NetworkXError):
1088
N.add_edge(u,v,weight=l)
1089
return N
1090
1091
def to_dictionary(self, edge_labels=False, multiple_edges=False):
1092
r"""
1093
Returns the graph as a dictionary.
1094
1095
INPUT:
1096
1097
- ``edge_labels`` (boolean) -- whether to include edge labels in the
1098
output.
1099
1100
- ``multiple_edges`` (boolean) -- whether to include multiple edges in
1101
the output.
1102
1103
OUTPUT:
1104
1105
The output depends on the input:
1106
1107
* If ``edge_labels == False`` and ``multiple_edges == False``, the
1108
output is a dictionary associating to each vertex the list of its
1109
neighbors.
1110
1111
* If ``edge_labels == False`` and ``multiple_edges == True``, the output
1112
is a dictionary the same as previously with one difference: the
1113
neighbors are listed with multiplicity.
1114
1115
* If ``edge_labels == True`` and ``multiple_edges == False``, the output
1116
is a dictionary associating to each vertex `u` [a dictionary
1117
associating to each vertex `v` incident to `u` the label of edge
1118
`(u,v)`].
1119
1120
* If ``edge_labels == True`` and ``multiple_edges == True``, the output
1121
is a dictionary associating to each vertex `u` [a dictionary
1122
associating to each vertex `v` incident to `u` [the list of labels of
1123
all edges between `u` and `v`]].
1124
1125
.. NOTE::
1126
1127
When used on directed graphs, the explanations above can be understood
1128
by replacing the word "neigbours" by "out-neighbors"
1129
1130
EXAMPLES::
1131
1132
sage: g = graphs.PetersenGraph().to_dictionary()
1133
sage: [(key, sorted(g[key])) for key in g]
1134
[(0, [1, 4, 5]),
1135
(1, [0, 2, 6]),
1136
(2, [1, 3, 7]),
1137
(3, [2, 4, 8]),
1138
(4, [0, 3, 9]),
1139
(5, [0, 7, 8]),
1140
(6, [1, 8, 9]),
1141
(7, [2, 5, 9]),
1142
(8, [3, 5, 6]),
1143
(9, [4, 6, 7])]
1144
sage: graphs.PetersenGraph().to_dictionary(multiple_edges=True)
1145
{0: [1, 4, 5], 1: [0, 2, 6],
1146
2: [1, 3, 7], 3: [2, 4, 8],
1147
4: [0, 3, 9], 5: [0, 7, 8],
1148
6: [1, 8, 9], 7: [2, 5, 9],
1149
8: [3, 5, 6], 9: [4, 6, 7]}
1150
sage: graphs.PetersenGraph().to_dictionary(edge_labels=True)
1151
{0: {1: None, 4: None, 5: None},
1152
1: {0: None, 2: None, 6: None},
1153
2: {1: None, 3: None, 7: None},
1154
3: {8: None, 2: None, 4: None},
1155
4: {0: None, 9: None, 3: None},
1156
5: {0: None, 8: None, 7: None},
1157
6: {8: None, 1: None, 9: None},
1158
7: {9: None, 2: None, 5: None},
1159
8: {3: None, 5: None, 6: None},
1160
9: {4: None, 6: None, 7: None}}
1161
sage: graphs.PetersenGraph().to_dictionary(edge_labels=True,multiple_edges=True)
1162
{0: {1: [None], 4: [None], 5: [None]},
1163
1: {0: [None], 2: [None], 6: [None]},
1164
2: {1: [None], 3: [None], 7: [None]},
1165
3: {8: [None], 2: [None], 4: [None]},
1166
4: {0: [None], 9: [None], 3: [None]},
1167
5: {0: [None], 8: [None], 7: [None]},
1168
6: {8: [None], 1: [None], 9: [None]},
1169
7: {9: [None], 2: [None], 5: [None]},
1170
8: {3: [None], 5: [None], 6: [None]},
1171
9: {4: [None], 6: [None], 7: [None]}}
1172
"""
1173
1174
# Returning the resuls as a dictionary of lists
1175
#
1176
# dictionary :
1177
# {vertex : [list of (out-)neighbors]}
1178
1179
if not edge_labels and not multiple_edges:
1180
d = {}
1181
1182
if self.is_directed():
1183
for u in self:
1184
d[u]=self.neighbors_out(u)
1185
else:
1186
for u in self:
1187
d[u]=self.neighbors(u)
1188
1189
1190
# Returning the result as a dictionary of lists
1191
#
1192
# dictionary :
1193
# {vertex : [list of (out-)neighbors, with multiplicity]}
1194
elif not edge_labels and multiple_edges:
1195
d={v:[] for v in self}
1196
1197
if self.is_directed():
1198
for u,v in self.edge_iterator(labels = False):
1199
d[u].append(v)
1200
1201
else:
1202
for u,v in self.edge_iterator(labels = False):
1203
d[u].append(v)
1204
d[v].append(u)
1205
1206
# Returning the result as a dictionary of dictionaries
1207
#
1208
# Each vertex is associated with the dictionary associating to each of
1209
# its neighbors the corresponding edge label.
1210
#
1211
# dictionary :
1212
# {v : dictionary }
1213
# {neighbor u of v : label of edge u,v}
1214
1215
elif edge_labels and not multiple_edges:
1216
d={v:{} for v in self}
1217
1218
if self.is_directed():
1219
for u,v,l in self.edge_iterator():
1220
d[u][v] = l
1221
1222
else:
1223
for u,v,l in self.edge_iterator():
1224
d[u][v] = l
1225
d[v][u] = l
1226
1227
# Returning the result as a dictionary of dictionaries
1228
#
1229
# Each vertex is associated with the dictionary associating to each of
1230
# its neighbors the list of edge labels between the two vertices
1231
#
1232
# dictionary :
1233
# {v : dictionary }
1234
# {neighbor u of v : [labels of edges between u and v]}
1235
1236
elif edge_labels and multiple_edges:
1237
d={v:{} for v in self}
1238
1239
if self.is_directed():
1240
for u,v,l in self.edge_iterator():
1241
if not v in d[u]:
1242
d[u][v] = []
1243
d[u][v].append(l)
1244
1245
else:
1246
for u,v,l in self.edge_iterator():
1247
if not v in d[u]:
1248
d[u][v] = []
1249
d[v][u] = []
1250
1251
d[u][v].append(l)
1252
d[v][u].append(l)
1253
1254
return d
1255
1256
def adjacency_matrix(self, sparse=None, boundary_first=False):
1257
"""
1258
Returns the adjacency matrix of the (di)graph.
1259
1260
Each vertex is represented by its position in the list returned by the
1261
vertices() function.
1262
1263
The matrix returned is over the integers. If a different ring is
1264
desired, use either the change_ring function or the matrix
1265
function.
1266
1267
INPUT:
1268
1269
- ``sparse`` - whether to represent with a sparse
1270
matrix
1271
1272
- ``boundary_first`` - whether to represent the
1273
boundary vertices in the upper left block
1274
1275
EXAMPLES::
1276
1277
sage: G = graphs.CubeGraph(4)
1278
sage: G.adjacency_matrix()
1279
[0 1 1 0 1 0 0 0 1 0 0 0 0 0 0 0]
1280
[1 0 0 1 0 1 0 0 0 1 0 0 0 0 0 0]
1281
[1 0 0 1 0 0 1 0 0 0 1 0 0 0 0 0]
1282
[0 1 1 0 0 0 0 1 0 0 0 1 0 0 0 0]
1283
[1 0 0 0 0 1 1 0 0 0 0 0 1 0 0 0]
1284
[0 1 0 0 1 0 0 1 0 0 0 0 0 1 0 0]
1285
[0 0 1 0 1 0 0 1 0 0 0 0 0 0 1 0]
1286
[0 0 0 1 0 1 1 0 0 0 0 0 0 0 0 1]
1287
[1 0 0 0 0 0 0 0 0 1 1 0 1 0 0 0]
1288
[0 1 0 0 0 0 0 0 1 0 0 1 0 1 0 0]
1289
[0 0 1 0 0 0 0 0 1 0 0 1 0 0 1 0]
1290
[0 0 0 1 0 0 0 0 0 1 1 0 0 0 0 1]
1291
[0 0 0 0 1 0 0 0 1 0 0 0 0 1 1 0]
1292
[0 0 0 0 0 1 0 0 0 1 0 0 1 0 0 1]
1293
[0 0 0 0 0 0 1 0 0 0 1 0 1 0 0 1]
1294
[0 0 0 0 0 0 0 1 0 0 0 1 0 1 1 0]
1295
1296
::
1297
1298
sage: matrix(GF(2),G) # matrix over GF(2)
1299
[0 1 1 0 1 0 0 0 1 0 0 0 0 0 0 0]
1300
[1 0 0 1 0 1 0 0 0 1 0 0 0 0 0 0]
1301
[1 0 0 1 0 0 1 0 0 0 1 0 0 0 0 0]
1302
[0 1 1 0 0 0 0 1 0 0 0 1 0 0 0 0]
1303
[1 0 0 0 0 1 1 0 0 0 0 0 1 0 0 0]
1304
[0 1 0 0 1 0 0 1 0 0 0 0 0 1 0 0]
1305
[0 0 1 0 1 0 0 1 0 0 0 0 0 0 1 0]
1306
[0 0 0 1 0 1 1 0 0 0 0 0 0 0 0 1]
1307
[1 0 0 0 0 0 0 0 0 1 1 0 1 0 0 0]
1308
[0 1 0 0 0 0 0 0 1 0 0 1 0 1 0 0]
1309
[0 0 1 0 0 0 0 0 1 0 0 1 0 0 1 0]
1310
[0 0 0 1 0 0 0 0 0 1 1 0 0 0 0 1]
1311
[0 0 0 0 1 0 0 0 1 0 0 0 0 1 1 0]
1312
[0 0 0 0 0 1 0 0 0 1 0 0 1 0 0 1]
1313
[0 0 0 0 0 0 1 0 0 0 1 0 1 0 0 1]
1314
[0 0 0 0 0 0 0 1 0 0 0 1 0 1 1 0]
1315
1316
::
1317
1318
sage: D = DiGraph( { 0: [1,2,3], 1: [0,2], 2: [3], 3: [4], 4: [0,5], 5: [1] } )
1319
sage: D.adjacency_matrix()
1320
[0 1 1 1 0 0]
1321
[1 0 1 0 0 0]
1322
[0 0 0 1 0 0]
1323
[0 0 0 0 1 0]
1324
[1 0 0 0 0 1]
1325
[0 1 0 0 0 0]
1326
1327
TESTS::
1328
1329
sage: graphs.CubeGraph(8).adjacency_matrix().parent()
1330
Full MatrixSpace of 256 by 256 dense matrices over Integer Ring
1331
sage: graphs.CubeGraph(9).adjacency_matrix().parent()
1332
Full MatrixSpace of 512 by 512 sparse matrices over Integer Ring
1333
"""
1334
n = self.order()
1335
if sparse is None:
1336
if n <= 256 or self.density() > 0.05:
1337
sparse=False
1338
else:
1339
sparse=True
1340
1341
verts = self.vertices(boundary_first=boundary_first)
1342
new_indices = dict((v,i) for i,v in enumerate(verts))
1343
D = {}
1344
directed = self._directed
1345
multiple_edges = self.allows_multiple_edges()
1346
for i,j,l in self.edge_iterator():
1347
i = new_indices[i]
1348
j = new_indices[j]
1349
if multiple_edges and (i,j) in D:
1350
D[(i,j)] += 1
1351
if not directed and i != j:
1352
D[(j,i)] += 1
1353
else:
1354
D[(i,j)] = 1
1355
if not directed and i != j:
1356
D[(j,i)] = 1
1357
from sage.matrix.constructor import matrix
1358
M = matrix(ZZ, n, n, D, sparse=sparse)
1359
return M
1360
1361
am = adjacency_matrix # shorter call makes life easier
1362
1363
def incidence_matrix(self, sparse=True):
1364
"""
1365
Returns the incidence matrix of the (di)graph.
1366
1367
Each row is a vertex, and each column is an edge. Note that in the case
1368
of graphs, there is a choice of orientation for each edge.
1369
1370
EXAMPLES::
1371
1372
sage: G = graphs.CubeGraph(3)
1373
sage: G.incidence_matrix()
1374
[-1 -1 -1 0 0 0 0 0 0 0 0 0]
1375
[ 0 0 1 -1 -1 0 0 0 0 0 0 0]
1376
[ 0 1 0 0 0 -1 -1 0 0 0 0 0]
1377
[ 0 0 0 0 1 0 1 -1 0 0 0 0]
1378
[ 1 0 0 0 0 0 0 0 -1 -1 0 0]
1379
[ 0 0 0 1 0 0 0 0 0 1 -1 0]
1380
[ 0 0 0 0 0 1 0 0 1 0 0 -1]
1381
[ 0 0 0 0 0 0 0 1 0 0 1 1]
1382
1383
1384
A well known result states that the product of the incidence matrix
1385
with its transpose is in fact the Kirchhoff matrix::
1386
1387
sage: G = graphs.PetersenGraph()
1388
sage: G.incidence_matrix()*G.incidence_matrix().transpose() == G.kirchhoff_matrix()
1389
True
1390
1391
::
1392
1393
sage: D = DiGraph( { 0: [1,2,3], 1: [0,2], 2: [3], 3: [4], 4: [0,5], 5: [1] } )
1394
sage: D.incidence_matrix()
1395
[-1 -1 -1 0 0 0 0 0 1 1]
1396
[ 0 0 1 -1 0 0 0 1 -1 0]
1397
[ 0 1 0 1 -1 0 0 0 0 0]
1398
[ 1 0 0 0 1 -1 0 0 0 0]
1399
[ 0 0 0 0 0 1 -1 0 0 -1]
1400
[ 0 0 0 0 0 0 1 -1 0 0]
1401
"""
1402
from sage.matrix.constructor import matrix
1403
from copy import copy
1404
n = self.order()
1405
verts = self.vertices()
1406
d = [0]*n
1407
cols = []
1408
if self._directed:
1409
for i, j, l in self.edge_iterator():
1410
col = copy(d)
1411
i = verts.index(i)
1412
j = verts.index(j)
1413
col[i] = -1
1414
col[j] = 1
1415
cols.append(col)
1416
else:
1417
for i, j, l in self.edge_iterator():
1418
col = copy(d)
1419
i,j = (i,j) if i <= j else (j,i)
1420
i = verts.index(i)
1421
j = verts.index(j)
1422
col[i] = -1
1423
col[j] = 1
1424
cols.append(col)
1425
cols.sort()
1426
return matrix(cols, sparse=sparse).transpose()
1427
1428
def distance_matrix(self):
1429
"""
1430
Returns the distance matrix of the (strongly) connected (di)graph.
1431
1432
The distance matrix of a (strongly) connected (di)graph is a matrix whose
1433
rows and columns are indexed with the vertices of the (di) graph. The
1434
intersection of a row and column contains the respective distance between
1435
the vertices indexed at these position.
1436
1437
.. WARNING::
1438
1439
The ordering of vertices in the matrix has no reason to correspond
1440
to the order of vertices in
1441
:meth:`~sage.graphs.generic_graph.GenericGraph.vertices`. In
1442
particular, if two integers `i,j` are vertices of a graph `G` with
1443
distance matrix ``M``, then ``M[i][i]`` is not necessarily the
1444
distance between vertices `i` and `j`.
1445
1446
EXAMPLES::
1447
1448
sage: G = graphs.CubeGraph(3)
1449
sage: G.distance_matrix()
1450
[0 1 1 2 1 2 2 3]
1451
[1 0 2 1 2 1 3 2]
1452
[1 2 0 1 2 3 1 2]
1453
[2 1 1 0 3 2 2 1]
1454
[1 2 2 3 0 1 1 2]
1455
[2 1 3 2 1 0 2 1]
1456
[2 3 1 2 1 2 0 1]
1457
[3 2 2 1 2 1 1 0]
1458
1459
The well known result of Graham and Pollak states that the determinant of
1460
the distance matrix of any tree of order n is (-1)^{n-1}(n-1)2^{n-2} ::
1461
1462
sage: all(T.distance_matrix().det() == (-1)^9*(9)*2^8 for T in graphs.trees(10))
1463
True
1464
1465
.. SEEALSO::
1466
1467
* :meth:`~sage.graphs.generic_graph.GenericGraph.distance_all_pairs`
1468
-- computes the distance between any two vertices.
1469
"""
1470
1471
from sage.matrix.constructor import matrix
1472
1473
n = self.order()
1474
ret = matrix(n,n)
1475
V = self.vertices()
1476
1477
dist = self.distance_all_pairs()
1478
1479
for i in xrange(n):
1480
for j in xrange(i+1,n):
1481
d = (dist[V[i]])[V[j]]
1482
if d > n :
1483
raise ValueError("Input (di)graph must be (strongly) connected.")
1484
ret[i,j] = ret[j,i] = d
1485
1486
return ret
1487
1488
def weighted_adjacency_matrix(self, sparse=True, boundary_first=False):
1489
"""
1490
Returns the weighted adjacency matrix of the graph.
1491
1492
Each vertex is represented by its position in the list returned by the
1493
vertices() function.
1494
1495
EXAMPLES::
1496
1497
sage: G = Graph(sparse=True, weighted=True)
1498
sage: G.add_edges([(0,1,1),(1,2,2),(0,2,3),(0,3,4)])
1499
sage: M = G.weighted_adjacency_matrix(); M
1500
[0 1 3 4]
1501
[1 0 2 0]
1502
[3 2 0 0]
1503
[4 0 0 0]
1504
sage: H = Graph(data=M, format='weighted_adjacency_matrix', sparse=True)
1505
sage: H == G
1506
True
1507
1508
The following doctest verifies that \#4888 is fixed::
1509
1510
sage: G = DiGraph({0:{}, 1:{0:1}, 2:{0:1}}, weighted = True,sparse=True)
1511
sage: G.weighted_adjacency_matrix()
1512
[0 0 0]
1513
[1 0 0]
1514
[1 0 0]
1515
1516
"""
1517
if self.has_multiple_edges():
1518
raise NotImplementedError("don't know how to represent weights for a multigraph")
1519
1520
verts = self.vertices(boundary_first=boundary_first)
1521
new_indices = dict((v,i) for i,v in enumerate(verts))
1522
1523
D = {}
1524
if self._directed:
1525
for i,j,l in self.edge_iterator():
1526
i = new_indices[i]
1527
j = new_indices[j]
1528
D[(i,j)] = l
1529
else:
1530
for i,j,l in self.edge_iterator():
1531
i = new_indices[i]
1532
j = new_indices[j]
1533
D[(i,j)] = l
1534
D[(j,i)] = l
1535
from sage.matrix.constructor import matrix
1536
M = matrix(self.num_verts(), D, sparse=sparse)
1537
return M
1538
1539
def kirchhoff_matrix(self, weighted=None, indegree=True, normalized=False, **kwds):
1540
"""
1541
Returns the Kirchhoff matrix (a.k.a. the Laplacian) of the graph.
1542
1543
The Kirchhoff matrix is defined to be `D - M`, where `D` is
1544
the diagonal degree matrix (each diagonal entry is the degree
1545
of the corresponding vertex), and `M` is the adjacency matrix.
1546
If ``normalized`` is ``True``, then the returned matrix is
1547
`D^{-1/2}(D-M)D^{-1/2}`.
1548
1549
( In the special case of DiGraphs, `D` is defined as the diagonal
1550
in-degree matrix or diagonal out-degree matrix according to the
1551
value of ``indegree``)
1552
1553
INPUT:
1554
1555
- ``weighted`` -- Binary variable :
1556
- If ``True``, the weighted adjacency matrix is used for `M`,
1557
and the diagonal matrix `D` takes into account the weight of edges
1558
(replace in the definition "degree" by "sum of the incident edges" ).
1559
- Else, each edge is assumed to have weight 1.
1560
1561
Default is to take weights into consideration if and only if the graph is
1562
weighted.
1563
1564
- ``indegree`` -- Binary variable :
1565
- If ``True``, each diagonal entry of `D` is equal to the
1566
in-degree of the corresponding vertex.
1567
- Else, each diagonal entry of `D` is equal to the
1568
out-degree of the corresponding vertex.
1569
1570
By default, ``indegree`` is set to ``True``
1571
1572
( This variable only matters when the graph is a digraph )
1573
1574
- ``normalized`` -- Binary variable :
1575
1576
- If ``True``, the returned matrix is
1577
`D^{-1/2}(D-M)D^{-1/2}`, a normalized version of the
1578
Laplacian matrix.
1579
(More accurately, the normalizing matrix used is equal to `D^{-1/2}`
1580
only for non-isolated vertices. If vertex `i` is isolated, then
1581
diagonal entry `i` in the matrix is 1, rather than a division by
1582
zero.)
1583
- Else, the matrix `D-M` is returned
1584
1585
Note that any additional keywords will be passed on to either
1586
the ``adjacency_matrix`` or ``weighted_adjacency_matrix`` method.
1587
1588
AUTHORS:
1589
1590
- Tom Boothby
1591
- Jason Grout
1592
1593
EXAMPLES::
1594
1595
sage: G = Graph(sparse=True)
1596
sage: G.add_edges([(0,1,1),(1,2,2),(0,2,3),(0,3,4)])
1597
sage: M = G.kirchhoff_matrix(weighted=True); M
1598
[ 8 -1 -3 -4]
1599
[-1 3 -2 0]
1600
[-3 -2 5 0]
1601
[-4 0 0 4]
1602
sage: M = G.kirchhoff_matrix(); M
1603
[ 3 -1 -1 -1]
1604
[-1 2 -1 0]
1605
[-1 -1 2 0]
1606
[-1 0 0 1]
1607
sage: G.set_boundary([2,3])
1608
sage: M = G.kirchhoff_matrix(weighted=True, boundary_first=True); M
1609
[ 5 0 -3 -2]
1610
[ 0 4 -4 0]
1611
[-3 -4 8 -1]
1612
[-2 0 -1 3]
1613
sage: M = G.kirchhoff_matrix(boundary_first=True); M
1614
[ 2 0 -1 -1]
1615
[ 0 1 -1 0]
1616
[-1 -1 3 -1]
1617
[-1 0 -1 2]
1618
sage: M = G.laplacian_matrix(boundary_first=True); M
1619
[ 2 0 -1 -1]
1620
[ 0 1 -1 0]
1621
[-1 -1 3 -1]
1622
[-1 0 -1 2]
1623
sage: M = G.laplacian_matrix(boundary_first=True, sparse=False); M
1624
[ 2 0 -1 -1]
1625
[ 0 1 -1 0]
1626
[-1 -1 3 -1]
1627
[-1 0 -1 2]
1628
sage: M = G.laplacian_matrix(normalized=True); M
1629
[ 1 -1/6*sqrt(3)*sqrt(2) -1/6*sqrt(3)*sqrt(2) -1/3*sqrt(3)]
1630
[-1/6*sqrt(3)*sqrt(2) 1 -1/2 0]
1631
[-1/6*sqrt(3)*sqrt(2) -1/2 1 0]
1632
[ -1/3*sqrt(3) 0 0 1]
1633
1634
sage: Graph({0:[],1:[2]}).laplacian_matrix(normalized=True)
1635
[ 0 0 0]
1636
[ 0 1 -1]
1637
[ 0 -1 1]
1638
1639
A weighted directed graph with loops, changing the variable ``indegree`` ::
1640
1641
sage: G = DiGraph({1:{1:2,2:3}, 2:{1:4}}, weighted=True,sparse=True)
1642
sage: G.laplacian_matrix()
1643
[ 4 -3]
1644
[-4 3]
1645
1646
::
1647
1648
sage: G = DiGraph({1:{1:2,2:3}, 2:{1:4}}, weighted=True,sparse=True)
1649
sage: G.laplacian_matrix(indegree=False)
1650
[ 3 -3]
1651
[-4 4]
1652
"""
1653
from sage.matrix.constructor import diagonal_matrix
1654
from sage.functions.all import sqrt
1655
1656
if weighted is None:
1657
weighted = self._weighted
1658
1659
if weighted:
1660
M = self.weighted_adjacency_matrix(**kwds)
1661
else:
1662
M = self.adjacency_matrix(**kwds)
1663
1664
D = M.parent(0)
1665
1666
if M.is_sparse():
1667
row_sums = {}
1668
if indegree:
1669
for (i,j), entry in M.dict().iteritems():
1670
row_sums[j] = row_sums.get(j, 0) + entry
1671
else:
1672
for (i,j), entry in M.dict().iteritems():
1673
row_sums[i] = row_sums.get(i, 0) + entry
1674
1675
1676
for i in range(M.nrows()):
1677
D[i,i] += row_sums.get(i, 0)
1678
1679
else:
1680
if indegree:
1681
col_sums=[sum(v) for v in M.columns()]
1682
for i in range(M.nrows()):
1683
D[i,i] += col_sums[i]
1684
else:
1685
row_sums=[sum(v) for v in M.rows()]
1686
for i in range(M.nrows()):
1687
D[i,i] += row_sums[i]
1688
1689
if normalized:
1690
Dsqrt = diagonal_matrix([1/sqrt(D[i,i]) if D[i,i]>0 else 1 \
1691
for i in range(D.nrows())])
1692
return Dsqrt*(D-M)*Dsqrt
1693
else:
1694
return D-M
1695
1696
laplacian_matrix = kirchhoff_matrix
1697
1698
### Attributes
1699
1700
def get_boundary(self):
1701
"""
1702
Returns the boundary of the (di)graph.
1703
1704
EXAMPLES::
1705
1706
sage: G = graphs.PetersenGraph()
1707
sage: G.set_boundary([0,1,2,3,4])
1708
sage: G.get_boundary()
1709
[0, 1, 2, 3, 4]
1710
"""
1711
return self._boundary
1712
1713
def set_boundary(self, boundary):
1714
"""
1715
Sets the boundary of the (di)graph.
1716
1717
EXAMPLES::
1718
1719
sage: G = graphs.PetersenGraph()
1720
sage: G.set_boundary([0,1,2,3,4])
1721
sage: G.get_boundary()
1722
[0, 1, 2, 3, 4]
1723
sage: G.set_boundary((1..4))
1724
sage: G.get_boundary()
1725
[1, 2, 3, 4]
1726
"""
1727
if isinstance(boundary,list):
1728
self._boundary = boundary
1729
else:
1730
self._boundary = list(boundary)
1731
1732
def set_embedding(self, embedding):
1733
"""
1734
Sets a combinatorial embedding dictionary to ``_embedding`` attribute.
1735
1736
Dictionary is organized with vertex labels as keys and a list of
1737
each vertex's neighbors in clockwise order.
1738
1739
Dictionary is error-checked for validity.
1740
1741
INPUT:
1742
1743
- ``embedding`` - a dictionary
1744
1745
EXAMPLES::
1746
1747
sage: G = graphs.PetersenGraph()
1748
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]})
1749
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]})
1750
Traceback (most recent call last):
1751
...
1752
ValueError: embedding is not valid for Petersen graph
1753
"""
1754
if self._check_embedding_validity(embedding):
1755
self._embedding = embedding
1756
else:
1757
raise ValueError('embedding is not valid for %s'%self)
1758
1759
def get_embedding(self):
1760
"""
1761
Returns the attribute _embedding if it exists.
1762
1763
``_embedding`` is a dictionary organized with vertex labels as keys and a
1764
list of each vertex's neighbors in clockwise order.
1765
1766
Error-checked to insure valid embedding is returned.
1767
1768
EXAMPLES::
1769
1770
sage: G = graphs.PetersenGraph()
1771
sage: G.genus()
1772
1
1773
sage: G.get_embedding()
1774
{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]}
1775
"""
1776
if self._check_embedding_validity():
1777
return self._embedding
1778
else:
1779
raise ValueError('%s has been modified and the embedding is no longer valid'%self)
1780
1781
def _check_embedding_validity(self, embedding=None):
1782
"""
1783
Checks whether an _embedding attribute is well defined.
1784
1785
If the ``_embedding`` attribute exists, it is checked for
1786
accuracy. Returns True if everything is okay, False otherwise.
1787
1788
If embedding=None will test the attribute _embedding.
1789
1790
EXAMPLES::
1791
1792
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]}
1793
sage: G = graphs.PetersenGraph()
1794
sage: G._check_embedding_validity(d)
1795
True
1796
1797
TESTS::
1798
1799
sage: G.check_embedding_validity(d)
1800
doctest:...: DeprecationWarning: check_embedding_validity is deprecated. Please use _check_embedding_validity instead.
1801
See http://trac.sagemath.org/15551 for details.
1802
True
1803
1804
"""
1805
if embedding is None:
1806
embedding = getattr(self, '_embedding', None)
1807
if embedding is None:
1808
return False
1809
if len(embedding) != self.order():
1810
return False
1811
if self._directed:
1812
connected = lambda u,v : self.has_edge(u,v) or self.has_edge(v,u)
1813
else:
1814
connected = lambda u,v : self.has_edge(u,v)
1815
for v in embedding:
1816
if not self.has_vertex(v):
1817
return False
1818
if len(embedding[v]) != len(self.neighbors(v)):
1819
return False
1820
for u in embedding[v]:
1821
if not connected(v,u):
1822
return False
1823
return True
1824
1825
check_embedding_validity = deprecated_function_alias(15551, _check_embedding_validity)
1826
1827
def has_loops(self):
1828
"""
1829
Returns whether there are loops in the (di)graph.
1830
1831
EXAMPLES::
1832
1833
sage: G = Graph(loops=True); G
1834
Looped graph on 0 vertices
1835
sage: G.has_loops()
1836
False
1837
sage: G.allows_loops()
1838
True
1839
sage: G.add_edge((0,0))
1840
sage: G.has_loops()
1841
True
1842
sage: G.loops()
1843
[(0, 0, None)]
1844
sage: G.allow_loops(False); G
1845
Graph on 1 vertex
1846
sage: G.has_loops()
1847
False
1848
sage: G.edges()
1849
[]
1850
1851
sage: D = DiGraph(loops=True); D
1852
Looped digraph on 0 vertices
1853
sage: D.has_loops()
1854
False
1855
sage: D.allows_loops()
1856
True
1857
sage: D.add_edge((0,0))
1858
sage: D.has_loops()
1859
True
1860
sage: D.loops()
1861
[(0, 0, None)]
1862
sage: D.allow_loops(False); D
1863
Digraph on 1 vertex
1864
sage: D.has_loops()
1865
False
1866
sage: D.edges()
1867
[]
1868
"""
1869
if self.allows_loops():
1870
for v in self:
1871
if self.has_edge(v,v):
1872
return True
1873
return False
1874
1875
def allows_loops(self):
1876
"""
1877
Returns whether loops are permitted in the (di)graph.
1878
1879
EXAMPLES::
1880
1881
sage: G = Graph(loops=True); G
1882
Looped graph on 0 vertices
1883
sage: G.has_loops()
1884
False
1885
sage: G.allows_loops()
1886
True
1887
sage: G.add_edge((0,0))
1888
sage: G.has_loops()
1889
True
1890
sage: G.loops()
1891
[(0, 0, None)]
1892
sage: G.allow_loops(False); G
1893
Graph on 1 vertex
1894
sage: G.has_loops()
1895
False
1896
sage: G.edges()
1897
[]
1898
1899
sage: D = DiGraph(loops=True); D
1900
Looped digraph on 0 vertices
1901
sage: D.has_loops()
1902
False
1903
sage: D.allows_loops()
1904
True
1905
sage: D.add_edge((0,0))
1906
sage: D.has_loops()
1907
True
1908
sage: D.loops()
1909
[(0, 0, None)]
1910
sage: D.allow_loops(False); D
1911
Digraph on 1 vertex
1912
sage: D.has_loops()
1913
False
1914
sage: D.edges()
1915
[]
1916
"""
1917
return self._backend.loops(None)
1918
1919
def allow_loops(self, new, check=True):
1920
"""
1921
Changes whether loops are permitted in the (di)graph.
1922
1923
INPUT:
1924
1925
- ``new`` - boolean.
1926
1927
EXAMPLES::
1928
1929
sage: G = Graph(loops=True); G
1930
Looped graph on 0 vertices
1931
sage: G.has_loops()
1932
False
1933
sage: G.allows_loops()
1934
True
1935
sage: G.add_edge((0,0))
1936
sage: G.has_loops()
1937
True
1938
sage: G.loops()
1939
[(0, 0, None)]
1940
sage: G.allow_loops(False); G
1941
Graph on 1 vertex
1942
sage: G.has_loops()
1943
False
1944
sage: G.edges()
1945
[]
1946
1947
sage: D = DiGraph(loops=True); D
1948
Looped digraph on 0 vertices
1949
sage: D.has_loops()
1950
False
1951
sage: D.allows_loops()
1952
True
1953
sage: D.add_edge((0,0))
1954
sage: D.has_loops()
1955
True
1956
sage: D.loops()
1957
[(0, 0, None)]
1958
sage: D.allow_loops(False); D
1959
Digraph on 1 vertex
1960
sage: D.has_loops()
1961
False
1962
sage: D.edges()
1963
[]
1964
"""
1965
if new is False and check:
1966
self.remove_loops()
1967
self._backend.loops(new)
1968
1969
def loops(self, labels=True):
1970
"""
1971
Returns any loops in the (di)graph.
1972
1973
INPUT:
1974
1975
- ``new`` -- deprecated
1976
1977
- ``labels`` -- whether returned edges have labels ((u,v,l)) or not ((u,v)).
1978
1979
EXAMPLES::
1980
1981
sage: G = Graph(loops=True); G
1982
Looped graph on 0 vertices
1983
sage: G.has_loops()
1984
False
1985
sage: G.allows_loops()
1986
True
1987
sage: G.add_edge((0,0))
1988
sage: G.has_loops()
1989
True
1990
sage: G.loops()
1991
[(0, 0, None)]
1992
sage: G.allow_loops(False); G
1993
Graph on 1 vertex
1994
sage: G.has_loops()
1995
False
1996
sage: G.edges()
1997
[]
1998
1999
sage: D = DiGraph(loops=True); D
2000
Looped digraph on 0 vertices
2001
sage: D.has_loops()
2002
False
2003
sage: D.allows_loops()
2004
True
2005
sage: D.add_edge((0,0))
2006
sage: D.has_loops()
2007
True
2008
sage: D.loops()
2009
[(0, 0, None)]
2010
sage: D.allow_loops(False); D
2011
Digraph on 1 vertex
2012
sage: D.has_loops()
2013
False
2014
sage: D.edges()
2015
[]
2016
2017
sage: G = graphs.PetersenGraph()
2018
sage: G.loops()
2019
[]
2020
2021
"""
2022
loops = []
2023
for v in self:
2024
loops += self.edge_boundary([v], [v], labels)
2025
return loops
2026
2027
def has_multiple_edges(self, to_undirected=False):
2028
"""
2029
Returns whether there are multiple edges in the (di)graph.
2030
2031
INPUT:
2032
2033
- ``to_undirected`` -- (default: False) If True, runs the test on the undirected version of a DiGraph.
2034
Otherwise, treats DiGraph edges (u,v) and (v,u) as unique individual edges.
2035
2036
EXAMPLES::
2037
2038
sage: G = Graph(multiedges=True,sparse=True); G
2039
Multi-graph on 0 vertices
2040
sage: G.has_multiple_edges()
2041
False
2042
sage: G.allows_multiple_edges()
2043
True
2044
sage: G.add_edges([(0,1)]*3)
2045
sage: G.has_multiple_edges()
2046
True
2047
sage: G.multiple_edges()
2048
[(0, 1, None), (0, 1, None), (0, 1, None)]
2049
sage: G.allow_multiple_edges(False); G
2050
Graph on 2 vertices
2051
sage: G.has_multiple_edges()
2052
False
2053
sage: G.edges()
2054
[(0, 1, None)]
2055
2056
sage: D = DiGraph(multiedges=True,sparse=True); D
2057
Multi-digraph on 0 vertices
2058
sage: D.has_multiple_edges()
2059
False
2060
sage: D.allows_multiple_edges()
2061
True
2062
sage: D.add_edges([(0,1)]*3)
2063
sage: D.has_multiple_edges()
2064
True
2065
sage: D.multiple_edges()
2066
[(0, 1, None), (0, 1, None), (0, 1, None)]
2067
sage: D.allow_multiple_edges(False); D
2068
Digraph on 2 vertices
2069
sage: D.has_multiple_edges()
2070
False
2071
sage: D.edges()
2072
[(0, 1, None)]
2073
2074
sage: G = DiGraph({1:{2: 'h'}, 2:{1:'g'}},sparse=True)
2075
sage: G.has_multiple_edges()
2076
False
2077
sage: G.has_multiple_edges(to_undirected=True)
2078
True
2079
sage: G.multiple_edges()
2080
[]
2081
sage: G.multiple_edges(to_undirected=True)
2082
[(1, 2, 'h'), (2, 1, 'g')]
2083
"""
2084
if self.allows_multiple_edges() or (self._directed and to_undirected):
2085
if self._directed:
2086
for u in self:
2087
s = set()
2088
for a,b,c in self.outgoing_edge_iterator(u):
2089
if b in s:
2090
return True
2091
s.add(b)
2092
if to_undirected:
2093
for a,b,c in self.incoming_edge_iterator(u):
2094
if a in s:
2095
return True
2096
s.add(a)
2097
else:
2098
for u in self:
2099
s = set()
2100
for a,b,c in self.edge_iterator(u):
2101
if a is u:
2102
if b in s:
2103
return True
2104
s.add(b)
2105
if b is u:
2106
if a in s:
2107
return True
2108
s.add(a)
2109
return False
2110
2111
def allows_multiple_edges(self):
2112
"""
2113
Returns whether multiple edges are permitted in the (di)graph.
2114
2115
EXAMPLES::
2116
2117
sage: G = Graph(multiedges=True,sparse=True); G
2118
Multi-graph on 0 vertices
2119
sage: G.has_multiple_edges()
2120
False
2121
sage: G.allows_multiple_edges()
2122
True
2123
sage: G.add_edges([(0,1)]*3)
2124
sage: G.has_multiple_edges()
2125
True
2126
sage: G.multiple_edges()
2127
[(0, 1, None), (0, 1, None), (0, 1, None)]
2128
sage: G.allow_multiple_edges(False); G
2129
Graph on 2 vertices
2130
sage: G.has_multiple_edges()
2131
False
2132
sage: G.edges()
2133
[(0, 1, None)]
2134
2135
sage: D = DiGraph(multiedges=True,sparse=True); D
2136
Multi-digraph on 0 vertices
2137
sage: D.has_multiple_edges()
2138
False
2139
sage: D.allows_multiple_edges()
2140
True
2141
sage: D.add_edges([(0,1)]*3)
2142
sage: D.has_multiple_edges()
2143
True
2144
sage: D.multiple_edges()
2145
[(0, 1, None), (0, 1, None), (0, 1, None)]
2146
sage: D.allow_multiple_edges(False); D
2147
Digraph on 2 vertices
2148
sage: D.has_multiple_edges()
2149
False
2150
sage: D.edges()
2151
[(0, 1, None)]
2152
"""
2153
return self._backend.multiple_edges(None)
2154
2155
def allow_multiple_edges(self, new, check=True):
2156
"""
2157
Changes whether multiple edges are permitted in the (di)graph.
2158
2159
INPUT:
2160
2161
- ``new`` - boolean.
2162
2163
EXAMPLES::
2164
2165
sage: G = Graph(multiedges=True,sparse=True); G
2166
Multi-graph on 0 vertices
2167
sage: G.has_multiple_edges()
2168
False
2169
sage: G.allows_multiple_edges()
2170
True
2171
sage: G.add_edges([(0,1)]*3)
2172
sage: G.has_multiple_edges()
2173
True
2174
sage: G.multiple_edges()
2175
[(0, 1, None), (0, 1, None), (0, 1, None)]
2176
sage: G.allow_multiple_edges(False); G
2177
Graph on 2 vertices
2178
sage: G.has_multiple_edges()
2179
False
2180
sage: G.edges()
2181
[(0, 1, None)]
2182
2183
sage: D = DiGraph(multiedges=True,sparse=True); D
2184
Multi-digraph on 0 vertices
2185
sage: D.has_multiple_edges()
2186
False
2187
sage: D.allows_multiple_edges()
2188
True
2189
sage: D.add_edges([(0,1)]*3)
2190
sage: D.has_multiple_edges()
2191
True
2192
sage: D.multiple_edges()
2193
[(0, 1, None), (0, 1, None), (0, 1, None)]
2194
sage: D.allow_multiple_edges(False); D
2195
Digraph on 2 vertices
2196
sage: D.has_multiple_edges()
2197
False
2198
sage: D.edges()
2199
[(0, 1, None)]
2200
"""
2201
seen = set()
2202
2203
# TODO: this should be much faster for c_graphs, but for now we just do this
2204
if self.allows_multiple_edges() and new is False and check:
2205
for u,v,l in self.multiple_edges():
2206
if (u,v) in seen:
2207
self.delete_edge(u,v,l)
2208
else:
2209
seen.add((u,v))
2210
2211
self._backend.multiple_edges(new)
2212
2213
def multiple_edges(self, to_undirected=False, labels=True):
2214
"""
2215
Returns any multiple edges in the (di)graph.
2216
2217
EXAMPLES::
2218
2219
sage: G = Graph(multiedges=True,sparse=True); G
2220
Multi-graph on 0 vertices
2221
sage: G.has_multiple_edges()
2222
False
2223
sage: G.allows_multiple_edges()
2224
True
2225
sage: G.add_edges([(0,1)]*3)
2226
sage: G.has_multiple_edges()
2227
True
2228
sage: G.multiple_edges()
2229
[(0, 1, None), (0, 1, None), (0, 1, None)]
2230
sage: G.allow_multiple_edges(False); G
2231
Graph on 2 vertices
2232
sage: G.has_multiple_edges()
2233
False
2234
sage: G.edges()
2235
[(0, 1, None)]
2236
2237
sage: D = DiGraph(multiedges=True,sparse=True); D
2238
Multi-digraph on 0 vertices
2239
sage: D.has_multiple_edges()
2240
False
2241
sage: D.allows_multiple_edges()
2242
True
2243
sage: D.add_edges([(0,1)]*3)
2244
sage: D.has_multiple_edges()
2245
True
2246
sage: D.multiple_edges()
2247
[(0, 1, None), (0, 1, None), (0, 1, None)]
2248
sage: D.allow_multiple_edges(False); D
2249
Digraph on 2 vertices
2250
sage: D.has_multiple_edges()
2251
False
2252
sage: D.edges()
2253
[(0, 1, None)]
2254
2255
sage: G = DiGraph({1:{2: 'h'}, 2:{1:'g'}},sparse=True)
2256
sage: G.has_multiple_edges()
2257
False
2258
sage: G.has_multiple_edges(to_undirected=True)
2259
True
2260
sage: G.multiple_edges()
2261
[]
2262
sage: G.multiple_edges(to_undirected=True)
2263
[(1, 2, 'h'), (2, 1, 'g')]
2264
"""
2265
from sage.misc.superseded import deprecation
2266
multi_edges = []
2267
if self._directed and not to_undirected:
2268
for v in self:
2269
for u in self.neighbor_in_iterator(v):
2270
edges = self.edge_boundary([u], [v], labels)
2271
if len(edges) > 1:
2272
multi_edges += edges
2273
else:
2274
to_undirected *= self._directed
2275
for v in self:
2276
for u in self.neighbor_iterator(v):
2277
if hash(u) >= hash(v):
2278
edges = self.edge_boundary([v], [u], labels)
2279
if to_undirected:
2280
edges += self.edge_boundary([u],[v], labels)
2281
if len(edges) > 1:
2282
multi_edges += edges
2283
return multi_edges
2284
2285
def name(self, new=None):
2286
"""
2287
Returns or sets the graph's name.
2288
2289
INPUT:
2290
2291
- ``new`` - if not None, then this becomes the new name of the (di)graph.
2292
(if new == '', removes any name)
2293
2294
EXAMPLES::
2295
2296
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]}
2297
sage: G = Graph(d); G
2298
Graph on 10 vertices
2299
sage: G.name("Petersen Graph"); G
2300
Petersen Graph: Graph on 10 vertices
2301
sage: G.name(new=""); G
2302
Graph on 10 vertices
2303
sage: G.name()
2304
''
2305
2306
Name of an immutable graph :trac:`15681` ::
2307
2308
sage: g = graphs.PetersenGraph()
2309
sage: gi = g.copy(immutable=True)
2310
sage: gi.name()
2311
'Petersen graph'
2312
sage: gi.name("Hey")
2313
Traceback (most recent call last):
2314
...
2315
NotImplementedError: An immutable graph does not change name
2316
"""
2317
if new is None:
2318
return getattr(self, '_name', "")
2319
2320
if getattr(self, '_immutable', False):
2321
raise NotImplementedError("An immutable graph does not change name")
2322
2323
self._name = str(new)
2324
2325
def get_pos(self, dim = 2):
2326
"""
2327
Returns the position dictionary, a dictionary specifying the
2328
coordinates of each vertex.
2329
2330
EXAMPLES: By default, the position of a graph is None::
2331
2332
sage: G = Graph()
2333
sage: G.get_pos()
2334
sage: G.get_pos() is None
2335
True
2336
sage: P = G.plot(save_pos=True)
2337
sage: G.get_pos()
2338
{}
2339
2340
Some of the named graphs come with a pre-specified positioning::
2341
2342
sage: G = graphs.PetersenGraph()
2343
sage: G.get_pos()
2344
{0: (...e-17, 1.0),
2345
...
2346
9: (0.475..., 0.154...)}
2347
"""
2348
if dim == 2:
2349
return self._pos
2350
elif dim == 3:
2351
return getattr(self, "_pos3d", None)
2352
else:
2353
raise ValueError("dim must be 2 or 3")
2354
2355
def _check_pos_validity(self, pos=None, dim = 2):
2356
r"""
2357
Checks whether pos specifies two (resp. 3) coordinates for every vertex (and no more vertices).
2358
2359
INPUT:
2360
2361
- ``pos`` - a position dictionary for a set of vertices
2362
- ``dim`` - 2 or 3 (default: 3
2363
2364
OUTPUT:
2365
2366
If ``pos`` is ``None`` then the position dictionary of ``self`` is
2367
investigated, otherwise the position dictionary provided in ``pos`` is
2368
investigated. The function returns ``True`` if the dictionary is of the
2369
correct form for ``self``.
2370
2371
EXAMPLES::
2372
2373
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]}
2374
sage: G = graphs.PetersenGraph()
2375
sage: G._check_pos_validity(p)
2376
True
2377
2378
TESTS::
2379
2380
sage: G.check_pos_validity(p)
2381
doctest:...: DeprecationWarning: check_pos_validity is deprecated. Please use _check_pos_validity instead.
2382
See http://trac.sagemath.org/15551 for details.
2383
True
2384
"""
2385
if pos is None:
2386
pos = self.get_pos(dim = dim)
2387
if pos is None:
2388
return False
2389
if len(pos) != self.order():
2390
return False
2391
for v in pos:
2392
if not self.has_vertex(v):
2393
return False
2394
if len(pos[v]) != dim:
2395
return False
2396
return True
2397
2398
check_pos_validity = deprecated_function_alias(15551,_check_pos_validity)
2399
2400
def set_pos(self, pos, dim = 2):
2401
"""
2402
Sets the position dictionary, a dictionary specifying the
2403
coordinates of each vertex.
2404
2405
EXAMPLES: Note that set_pos will allow you to do ridiculous things,
2406
which will not blow up until plotting::
2407
2408
sage: G = graphs.PetersenGraph()
2409
sage: G.get_pos()
2410
{0: (..., ...),
2411
...
2412
9: (..., ...)}
2413
2414
::
2415
2416
sage: G.set_pos('spam')
2417
sage: P = G.plot()
2418
Traceback (most recent call last):
2419
...
2420
TypeError: string indices must be integers, not str
2421
"""
2422
if dim == 2:
2423
self._pos = pos
2424
elif dim == 3:
2425
self._pos3d = pos
2426
else:
2427
raise ValueError("dim must be 2 or 3")
2428
2429
def weighted(self, new=None):
2430
"""
2431
Whether the (di)graph is to be considered as a weighted (di)graph.
2432
2433
INPUT:
2434
2435
- ``new`` (optional bool): If it is provided, then the weightedness
2436
flag is set accordingly. This is not allowed for immutable graphs.
2437
2438
.. NOTE::
2439
2440
Changing the weightedness flag changes the ``==``-class of
2441
a graph and is thus not allowed for immutable graphs.
2442
2443
Edge weightings can still exist for (di)graphs ``G`` where
2444
``G.weighted()`` is ``False``.
2445
2446
EXAMPLES:
2447
2448
Here we have two graphs with different labels, but ``weighted()`` is
2449
``False`` for both, so we just check for the presence of edges::
2450
2451
sage: G = Graph({0:{1:'a'}}, sparse=True)
2452
sage: H = Graph({0:{1:'b'}}, sparse=True)
2453
sage: G == H
2454
True
2455
2456
Now one is weighted and the other is not, and thus the graphs are
2457
not equal::
2458
2459
sage: G.weighted(True)
2460
sage: H.weighted()
2461
False
2462
sage: G == H
2463
False
2464
2465
However, if both are weighted, then we finally compare 'a' to 'b'::
2466
2467
sage: H.weighted(True)
2468
sage: G == H
2469
False
2470
2471
TESTS:
2472
2473
Ensure that :trac:`10490` is fixed: allows a weighted graph to be
2474
set as unweighted. ::
2475
2476
sage: G = Graph({1:{2:3}})
2477
sage: G.weighted()
2478
False
2479
sage: G.weighted('a')
2480
sage: G.weighted(True)
2481
sage: G.weighted()
2482
True
2483
sage: G.weighted('a')
2484
sage: G.weighted()
2485
True
2486
sage: G.weighted(False)
2487
sage: G.weighted()
2488
False
2489
sage: G.weighted('a')
2490
sage: G.weighted()
2491
False
2492
sage: G.weighted(True)
2493
sage: G.weighted()
2494
True
2495
2496
Ensure that graphs using the static sparse backend can not be mutated
2497
using this method, as fixed in :trac:`15278`::
2498
2499
sage: G = graphs.PetersenGraph()
2500
sage: G.weighted()
2501
False
2502
sage: H = copy(G)
2503
sage: H == G
2504
True
2505
sage: H.weighted(True)
2506
sage: H == G
2507
False
2508
sage: G_imm = Graph(G, data_structure="static_sparse")
2509
sage: G_imm == G
2510
True
2511
sage: G_imm.weighted()
2512
False
2513
sage: G_imm.weighted(True)
2514
Traceback (most recent call last):
2515
...
2516
TypeError: This graph is immutable and can thus not be changed.
2517
Create a mutable copy, e.g., by `g.copy(immutable=False)`
2518
sage: G_mut = G_imm.copy(immutable=False)
2519
sage: G_mut == G_imm
2520
True
2521
sage: G_mut.weighted(True)
2522
sage: G_mut == G_imm
2523
False
2524
sage: G_mut == H
2525
True
2526
2527
"""
2528
if new is not None:
2529
if getattr(self, '_immutable', False):
2530
raise TypeError("This graph is immutable and can thus not be changed. "
2531
"Create a mutable copy, e.g., by `g.copy(immutable=False)`")
2532
if new in [True, False]:
2533
self._weighted = new
2534
else:
2535
return self._weighted
2536
2537
### Properties
2538
2539
def antisymmetric(self):
2540
r"""
2541
Tests whether the graph is antisymmetric.
2542
2543
A graph represents an antisymmetric relation if there being a path
2544
from a vertex x to a vertex y implies that there is not a path from
2545
y to x unless x=y.
2546
2547
A directed acyclic graph is antisymmetric. An undirected graph is
2548
never antisymmetric unless it is just a union of isolated
2549
vertices.
2550
2551
::
2552
2553
sage: graphs.RandomGNP(20,0.5).antisymmetric()
2554
False
2555
sage: digraphs.RandomDirectedGNR(20,0.5).antisymmetric()
2556
True
2557
"""
2558
if not self._directed:
2559
if self.size()-len(self.loop_edges())>0:
2560
return False
2561
else:
2562
return True
2563
from copy import copy
2564
g = copy(self)
2565
g.allow_multiple_edges(False)
2566
g.allow_loops(False)
2567
g = g.transitive_closure()
2568
gpaths = g.edges(labels=False)
2569
for e in gpaths:
2570
if (e[1],e[0]) in gpaths:
2571
return False
2572
return True
2573
2574
def density(self):
2575
"""
2576
Returns the density (number of edges divided by number of possible
2577
edges).
2578
2579
In the case of a multigraph, raises an error, since there is an
2580
infinite number of possible edges.
2581
2582
EXAMPLES::
2583
2584
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]}
2585
sage: G = Graph(d); G.density()
2586
1/3
2587
sage: G = Graph({0:[1,2], 1:[0] }); G.density()
2588
2/3
2589
sage: G = DiGraph({0:[1,2], 1:[0] }); G.density()
2590
1/2
2591
2592
Note that there are more possible edges on a looped graph::
2593
2594
sage: G.allow_loops(True)
2595
sage: G.density()
2596
1/3
2597
"""
2598
if self.has_multiple_edges():
2599
raise TypeError("Density is not well-defined for multigraphs.")
2600
n = self.order()
2601
if self.allows_loops():
2602
if n == 0:
2603
return Rational(0)
2604
if self._directed:
2605
return Rational(self.size())/Rational(n**2)
2606
else:
2607
return Rational(self.size())/Rational((n**2 + n)/2)
2608
else:
2609
if n < 2:
2610
return Rational(0)
2611
if self._directed:
2612
return Rational(self.size())/Rational((n**2 - n))
2613
else:
2614
return Rational(self.size())/Rational((n**2 - n)/2)
2615
2616
def is_eulerian(self, path=False):
2617
r"""
2618
Return true if the graph has a (closed) tour that visits each edge exactly
2619
once.
2620
2621
INPUT:
2622
2623
- ``path`` -- by default this function finds if the graph contains a closed
2624
tour visiting each edge once, i.e. an eulerian cycle. If you want to test
2625
the existence of an eulerian path, set this argument to ``True``. Graphs
2626
with this property are sometimes called semi-eulerian.
2627
2628
OUTPUT:
2629
2630
``True`` or ``False`` for the closed tour case. For an open tour search
2631
(``path``=``True``) the function returns ``False`` if the graph is not
2632
semi-eulerian, or a tuple (u, v) in the other case. This tuple defines the
2633
edge that would make the graph eulerian, i.e. close an existing open tour.
2634
This edge may or may not be already present in the graph.
2635
2636
EXAMPLES::
2637
2638
sage: graphs.CompleteGraph(4).is_eulerian()
2639
False
2640
sage: graphs.CycleGraph(4).is_eulerian()
2641
True
2642
sage: g = DiGraph({0:[1,2], 1:[2]}); g.is_eulerian()
2643
False
2644
sage: g = DiGraph({0:[2], 1:[3], 2:[0,1], 3:[2]}); g.is_eulerian()
2645
True
2646
sage: g = DiGraph({0:[1], 1:[2], 2:[0], 3:[]}); g.is_eulerian()
2647
True
2648
sage: g = Graph([(1,2), (2,3), (3,1), (4,5), (5,6), (6,4)]); g.is_eulerian()
2649
False
2650
2651
::
2652
2653
sage: g = DiGraph({0: [1]}); g.is_eulerian(path=True)
2654
(1, 0)
2655
sage: graphs.CycleGraph(4).is_eulerian(path=True)
2656
False
2657
sage: g = DiGraph({0: [1], 1: [2,3], 2: [4]}); g.is_eulerian(path=True)
2658
False
2659
2660
::
2661
2662
sage: g = Graph({0:[1,2,3], 1:[2,3], 2:[3,4], 3:[4]}, multiedges=True)
2663
sage: g.is_eulerian()
2664
False
2665
sage: e = g.is_eulerian(path=True); e
2666
(0, 1)
2667
sage: g.add_edge(e)
2668
sage: g.is_eulerian(path=False)
2669
True
2670
sage: g.is_eulerian(path=True)
2671
False
2672
2673
TESTS::
2674
2675
sage: g = Graph({0:[], 1:[], 2:[], 3:[]}); g.is_eulerian()
2676
True
2677
"""
2678
2679
# unconnected graph can still be eulerian if all components
2680
# up to one doesn't contain any edge
2681
nontrivial_components = 0
2682
for cc in self.connected_components():
2683
if len(cc) > 1:
2684
nontrivial_components += 1
2685
if nontrivial_components > 1:
2686
return False
2687
2688
uv = [None, None]
2689
if self._directed:
2690
for v in self.vertex_iterator():
2691
# loops don't matter since they count in both the in and out degree.
2692
if self.in_degree(v) != self.out_degree(v):
2693
if path:
2694
diff = self.out_degree(v) - self.in_degree(v)
2695
if abs(diff) > 1:
2696
return False
2697
else:
2698
# if there was another vertex with the same sign of difference...
2699
if uv[(diff+1)/2] != None:
2700
return False # ... the graph is not semi-eulerian
2701
else:
2702
uv[(diff+1)/2] = v
2703
else:
2704
return False
2705
else:
2706
for v in self.vertex_iterator():
2707
# loops don't matter since they add an even number to the degree
2708
if self.degree(v) % 2 != 0:
2709
if not path:
2710
return False
2711
else:
2712
if uv[0] is None or uv[1] is None:
2713
uv[0 if uv[0] is None else 1] = v
2714
else:
2715
return False
2716
2717
if path and (uv[0] is None or uv[1] is None):
2718
return False
2719
2720
return True if not path else tuple(uv)
2721
2722
def order(self):
2723
"""
2724
Returns the number of vertices. Note that len(G) returns the number
2725
of vertices in G also.
2726
2727
EXAMPLES::
2728
2729
sage: G = graphs.PetersenGraph()
2730
sage: G.order()
2731
10
2732
2733
::
2734
2735
sage: G = graphs.TetrahedralGraph()
2736
sage: len(G)
2737
4
2738
"""
2739
return self._backend.num_verts()
2740
2741
__len__ = order
2742
2743
num_verts = order
2744
2745
def size(self):
2746
"""
2747
Returns the number of edges.
2748
2749
EXAMPLES::
2750
2751
sage: G = graphs.PetersenGraph()
2752
sage: G.size()
2753
15
2754
"""
2755
return self._backend.num_edges(self._directed)
2756
2757
num_edges = size
2758
2759
### Orientations
2760
2761
def eulerian_orientation(self):
2762
r"""
2763
Returns a DiGraph which is an Eulerian orientation of the current graph.
2764
2765
An Eulerian graph being a graph such that any vertex has an even degree,
2766
an Eulerian orientation of a graph is an orientation of its edges such
2767
that each vertex `v` verifies `d^+(v)=d^-(v)=d(v)/2`, where `d^+` and
2768
`d^-` respectively represent the out-degree and the in-degree of a vertex.
2769
2770
If the graph is not Eulerian, the orientation verifies for any vertex `v`
2771
that `| d^+(v)-d^-(v) | \leq 1`.
2772
2773
ALGORITHM:
2774
2775
This algorithm is a random walk through the edges of the graph, which
2776
orients the edges according to the walk. When a vertex is reached which
2777
has no non-oriented edge ( this vertex must have odd degree ), the
2778
walk resumes at another vertex of odd degree, if any.
2779
2780
This algorithm has complexity `O(m)`, where `m` is the number of edges
2781
in the graph.
2782
2783
EXAMPLES:
2784
2785
The CubeGraph with parameter 4, which is regular of even degree, has an
2786
Eulerian orientation such that `d^+=d^-`::
2787
2788
sage: g=graphs.CubeGraph(4)
2789
sage: g.degree()
2790
[4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4]
2791
sage: o=g.eulerian_orientation()
2792
sage: o.in_degree()
2793
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
2794
sage: o.out_degree()
2795
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
2796
2797
Secondly, the Petersen Graph, which is 3 regular has an orientation
2798
such that the difference between `d^+` and `d^-` is at most 1::
2799
2800
sage: g=graphs.PetersenGraph()
2801
sage: o=g.eulerian_orientation()
2802
sage: o.in_degree()
2803
[2, 2, 2, 2, 2, 1, 1, 1, 1, 1]
2804
sage: o.out_degree()
2805
[1, 1, 1, 1, 1, 2, 2, 2, 2, 2]
2806
"""
2807
from copy import copy
2808
g=copy(self)
2809
from sage.graphs.digraph import DiGraph
2810
d=DiGraph()
2811
d.add_vertices(g.vertex_iterator())
2812
2813
2814
# list of vertices of odd degree
2815
from itertools import izip
2816
odd=[x for (x,deg) in izip(g.vertex_iterator(),g.degree_iterator()) if deg%2==1]
2817
2818
# Picks the first vertex, which is preferably an odd one
2819
if len(odd)>0:
2820
v=odd.pop()
2821
else:
2822
v=g.edge_iterator(labels=None).next()[0]
2823
odd.append(v)
2824
# Stops when there is no edge left
2825
while True:
2826
2827
# If there is an edge adjacent to the current one
2828
if g.degree(v)>0:
2829
e = g.edge_iterator(v).next()
2830
g.delete_edge(e)
2831
if e[0]!=v:
2832
e=(e[1],e[0],e[2])
2833
d.add_edge(e)
2834
v=e[1]
2835
2836
# The current vertex is isolated
2837
else:
2838
odd.remove(v)
2839
2840
# jumps to another odd vertex if possible
2841
if len(odd)>0:
2842
v=odd.pop()
2843
# Else jumps to an ever vertex which is not isolated
2844
elif g.size()>0:
2845
v=g.edge_iterator().next()[0]
2846
odd.append(v)
2847
# If there is none, we are done !
2848
else:
2849
return d
2850
2851
def eulerian_circuit(self, return_vertices=False, labels=True, path=False):
2852
r"""
2853
Return a list of edges forming an eulerian circuit if one exists.
2854
Otherwise return False.
2855
2856
This is implemented using Hierholzer's algorithm.
2857
2858
INPUT:
2859
2860
- ``return_vertices`` -- (default: ``False``) optionally provide a list of
2861
vertices for the path
2862
2863
- ``labels`` -- (default: ``True``) whether to return edges with labels
2864
(3-tuples)
2865
2866
- ``path`` -- (default: ``False``) find an eulerian path instead
2867
2868
OUTPUT:
2869
2870
either ([edges], [vertices]) or [edges] of an Eulerian circuit (or path)
2871
2872
EXAMPLES::
2873
2874
sage: g=graphs.CycleGraph(5);
2875
sage: g.eulerian_circuit()
2876
[(0, 4, None), (4, 3, None), (3, 2, None), (2, 1, None), (1, 0, None)]
2877
sage: g.eulerian_circuit(labels=False)
2878
[(0, 4), (4, 3), (3, 2), (2, 1), (1, 0)]
2879
2880
::
2881
2882
sage: g = graphs.CompleteGraph(7)
2883
sage: edges, vertices = g.eulerian_circuit(return_vertices=True)
2884
sage: vertices
2885
[0, 6, 5, 4, 6, 3, 5, 2, 4, 3, 2, 6, 1, 5, 0, 4, 1, 3, 0, 2, 1, 0]
2886
2887
::
2888
2889
sage: graphs.CompleteGraph(4).eulerian_circuit()
2890
False
2891
2892
A disconnected graph can be eulerian::
2893
2894
sage: g = Graph({0: [], 1: [2], 2: [3], 3: [1], 4: []})
2895
sage: g.eulerian_circuit(labels=False)
2896
[(1, 3), (3, 2), (2, 1)]
2897
2898
::
2899
2900
sage: g = DiGraph({0: [1], 1: [2, 4], 2:[3], 3:[1]})
2901
sage: g.eulerian_circuit(labels=False, path=True)
2902
[(0, 1), (1, 2), (2, 3), (3, 1), (1, 4)]
2903
2904
::
2905
2906
sage: g = Graph({0:[1,2,3], 1:[2,3], 2:[3,4], 3:[4]})
2907
sage: g.is_eulerian(path=True)
2908
(0, 1)
2909
sage: g.eulerian_circuit(labels=False, path=True)
2910
[(1, 3), (3, 4), (4, 2), (2, 3), (3, 0), (0, 2), (2, 1), (1, 0)]
2911
2912
TESTS::
2913
2914
sage: Graph({'H': ['G','L','L','D'], 'L': ['G','D']}).eulerian_circuit(labels=False)
2915
[('H', 'D'), ('D', 'L'), ('L', 'G'), ('G', 'H'), ('H', 'L'), ('L', 'H')]
2916
sage: Graph({0: [0, 1, 1, 1, 1]}).eulerian_circuit(labels=False)
2917
[(0, 1), (1, 0), (0, 1), (1, 0), (0, 0)]
2918
"""
2919
# trivial case
2920
if self.order() == 0:
2921
return ([], []) if return_vertices else []
2922
2923
# check if the graph has proper properties to be eulerian
2924
edge = self.is_eulerian(path=path)
2925
if not edge:
2926
return False
2927
if path:
2928
start_vertex = edge[0]
2929
2930
edges = []
2931
vertices = []
2932
2933
# we'll remove edges as we go, so let's preserve the graph structure
2934
if self.is_directed():
2935
g = self.reverse() # so the output will be in the proper order
2936
else:
2937
from copy import copy
2938
g = copy(self)
2939
2940
if not path:
2941
# get the first vertex with degree>0
2942
start_vertex = None
2943
for v in g.vertex_iterator():
2944
if g.degree(v) != 0:
2945
start_vertex = v
2946
break
2947
2948
# (where to return?, what was the way?)
2949
stack = [ (start_vertex, None) ]
2950
2951
while len(stack) != 0:
2952
v, e = stack.pop()
2953
2954
degr = g.out_degree(v) if self.is_directed() else g.degree(v)
2955
if degr == 0:
2956
vertices.append(v)
2957
if e != None:
2958
edges.append(e if labels else (e[0], e[1]))
2959
else:
2960
if self.is_directed():
2961
next_edge = g.outgoing_edge_iterator(v).next()
2962
else:
2963
next_edge = g.edge_iterator(v).next()
2964
2965
if next_edge[0] == v: # in the undirected case we want to
2966
# save the direction of traversal
2967
next_edge_new = (next_edge[1], next_edge[0], next_edge[2])
2968
else:
2969
next_edge_new = next_edge
2970
next_vertex = next_edge_new[0]
2971
2972
stack.append((v, e))
2973
stack.append((next_vertex, next_edge_new))
2974
2975
g.delete_edge(next_edge)
2976
2977
if return_vertices:
2978
return edges, vertices
2979
else:
2980
return edges
2981
2982
def min_spanning_tree(self,
2983
weight_function=lambda e: 1,
2984
algorithm="Kruskal",
2985
starting_vertex=None,
2986
check=False):
2987
r"""
2988
Returns the edges of a minimum spanning tree.
2989
2990
INPUT:
2991
2992
- ``weight_function`` -- A function that takes an edge and returns a
2993
numeric weight. Defaults to assigning each edge a weight of 1.
2994
2995
- ``algorithm`` -- The algorithm to use in computing a minimum spanning
2996
tree of ``G``. The default is to use Kruskal's algorithm. The
2997
following algorithms are supported:
2998
2999
- ``"Kruskal"`` -- Kruskal's algorithm.
3000
3001
- ``"Prim_fringe"`` -- a variant of Prim's algorithm.
3002
``"Prim_fringe"`` ignores the labels on the edges.
3003
3004
- ``"Prim_edge"`` -- a variant of Prim's algorithm.
3005
3006
- ``NetworkX`` -- Uses NetworkX's minimum spanning tree
3007
implementation.
3008
3009
- ``starting_vertex`` -- The vertex from which to begin the search
3010
for a minimum spanning tree.
3011
3012
- ``check`` -- Boolean; default: ``False``. Whether to first perform
3013
sanity checks on the input graph ``G``. If appropriate, ``check``
3014
is passed on to any minimum spanning tree functions that are
3015
invoked from the current method. See the documentation of the
3016
corresponding functions for details on what sort of sanity checks
3017
will be performed.
3018
3019
OUTPUT:
3020
3021
The edges of a minimum spanning tree of ``G``, if one exists, otherwise
3022
returns the empty list.
3023
3024
.. seealso::
3025
3026
- :func:`sage.graphs.spanning_tree.kruskal`
3027
3028
EXAMPLES:
3029
3030
Kruskal's algorithm::
3031
3032
sage: g = graphs.CompleteGraph(5)
3033
sage: len(g.min_spanning_tree())
3034
4
3035
sage: weight = lambda e: 1 / ((e[0] + 1) * (e[1] + 1))
3036
sage: g.min_spanning_tree(weight_function=weight)
3037
[(3, 4, None), (2, 4, None), (1, 4, None), (0, 4, None)]
3038
sage: g = graphs.PetersenGraph()
3039
sage: g.allow_multiple_edges(True)
3040
sage: g.weighted(True)
3041
sage: g.add_edges(g.edges())
3042
sage: g.min_spanning_tree()
3043
[(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)]
3044
3045
Prim's algorithm::
3046
3047
sage: g = graphs.CompleteGraph(5)
3048
sage: g.min_spanning_tree(algorithm='Prim_edge', starting_vertex=2, weight_function=weight)
3049
[(2, 4, None), (3, 4, None), (1, 4, None), (0, 4, None)]
3050
sage: g.min_spanning_tree(algorithm='Prim_fringe', starting_vertex=2, weight_function=weight)
3051
[(2, 4), (4, 3), (4, 1), (4, 0)]
3052
"""
3053
if algorithm == "Kruskal":
3054
from spanning_tree import kruskal
3055
return kruskal(self, wfunction=weight_function, check=check)
3056
elif algorithm == "Prim_fringe":
3057
if starting_vertex is None:
3058
v = self.vertex_iterator().next()
3059
else:
3060
v = starting_vertex
3061
tree = set([v])
3062
edges = []
3063
# Initialize fringe_list with v's neighbors. Fringe_list
3064
# contains fringe_vertex: (vertex_in_tree, weight) for each
3065
# fringe vertex.
3066
fringe_list = dict([u, (weight_function((v, u)), v)] for u in self[v])
3067
cmp_fun = lambda x: fringe_list[x]
3068
for i in range(self.order() - 1):
3069
# find the smallest-weight fringe vertex
3070
u = min(fringe_list, key=cmp_fun)
3071
edges.append((fringe_list[u][1], u))
3072
tree.add(u)
3073
fringe_list.pop(u)
3074
# update fringe list
3075
for neighbor in [v for v in self[u] if v not in tree]:
3076
w = weight_function((u, neighbor))
3077
if neighbor not in fringe_list or fringe_list[neighbor][0] > w:
3078
fringe_list[neighbor] = (w, u)
3079
return edges
3080
elif algorithm == "Prim_edge":
3081
if starting_vertex is None:
3082
v = self.vertex_iterator().next()
3083
else:
3084
v = starting_vertex
3085
sorted_edges = sorted(self.edges(), key=weight_function)
3086
tree = set([v])
3087
edges = []
3088
for _ in range(self.order() - 1):
3089
# Find a minimum-weight edge connecting a vertex in the tree
3090
# to something outside the tree. Remove the edges between
3091
# tree vertices for efficiency.
3092
i = 0
3093
while True:
3094
e = sorted_edges[i]
3095
v0, v1 = e[0], e[1]
3096
if v0 in tree:
3097
del sorted_edges[i]
3098
if v1 in tree:
3099
continue
3100
edges.append(e)
3101
tree.add(v1)
3102
break
3103
elif v1 in tree:
3104
del sorted_edges[i]
3105
edges.append(e)
3106
tree.add(v0)
3107
break
3108
else:
3109
i += 1
3110
return edges
3111
elif algorithm == "NetworkX":
3112
import networkx
3113
G = networkx.Graph([(u, v, dict(weight=weight_function((u, v)))) for u, v, l in self.edge_iterator()])
3114
return list(networkx.mst(G))
3115
else:
3116
raise NotImplementedError("Minimum Spanning Tree algorithm '%s' is not implemented." % algorithm)
3117
3118
def spanning_trees_count(self, root_vertex=None):
3119
"""
3120
Returns the number of spanning trees in a graph.
3121
3122
In the case of a digraph, counts the number of spanning out-trees rooted
3123
in ``root_vertex``. Default is to set first vertex as root.
3124
3125
This computation uses Kirchhoff's Matrix Tree Theorem [1] to calculate
3126
the number of spanning trees. For complete graphs on `n` vertices the
3127
result can also be reached using Cayley's formula: the number of
3128
spanning trees are `n^(n-2)`.
3129
3130
For digraphs, the augmented Kirchhoff Matrix as defined in [2] is
3131
used for calculations. Here the result is the number of out-trees
3132
rooted at a specific vertex.
3133
3134
INPUT:
3135
3136
- ``root_vertex`` -- integer (default: the first vertex) This is the vertex
3137
that will be used as root for all spanning out-trees if the graph
3138
is a directed graph. This argument is ignored if the graph is not a digraph.
3139
3140
REFERENCES:
3141
3142
- [1] http://mathworld.wolfram.com/MatrixTreeTheorem.html
3143
3144
- [2] Lih-Hsing Hsu, Cheng-Kuan Lin, "Graph Theory and
3145
Interconnection Networks"
3146
3147
AUTHORS:
3148
3149
- Anders Jonsson (2009-10-10)
3150
3151
EXAMPLES::
3152
3153
sage: G = graphs.PetersenGraph()
3154
sage: G.spanning_trees_count()
3155
2000
3156
3157
::
3158
3159
sage: n = 11
3160
sage: G = graphs.CompleteGraph(n)
3161
sage: ST = G.spanning_trees_count()
3162
sage: ST == n^(n-2)
3163
True
3164
3165
::
3166
3167
sage: M=matrix(3,3,[0,1,0,0,0,1,1,1,0])
3168
sage: D=DiGraph(M)
3169
sage: D.spanning_trees_count()
3170
1
3171
sage: D.spanning_trees_count(0)
3172
1
3173
sage: D.spanning_trees_count(2)
3174
2
3175
3176
"""
3177
3178
if self.order() == 0:
3179
return 0
3180
3181
if not self.is_directed():
3182
M = self.kirchhoff_matrix()
3183
M.subdivide(1,1)
3184
M2 = M.subdivision(1,1)
3185
return M2.determinant()
3186
else:
3187
if root_vertex is None:
3188
root_vertex=self.vertex_iterator().next()
3189
if root_vertex not in self.vertices():
3190
raise ValueError("Vertex (%s) not in the graph."%root_vertex)
3191
3192
M=self.kirchhoff_matrix()
3193
3194
index=self.vertices().index(root_vertex)
3195
M[index,index]+=1
3196
return abs(M.determinant())
3197
3198
def cycle_basis(self, output='vertex'):
3199
r"""
3200
Returns a list of cycles which form a basis of the cycle space
3201
of ``self``.
3202
3203
A basis of cycles of a graph is a minimal collection of cycles
3204
(considered as sets of edges) such that the edge set of any
3205
cycle in the graph can be written as a `Z/2Z` sum of the
3206
cycles in the basis.
3207
3208
INPUT:
3209
3210
- ``output`` (``'vertex'`` (default) or ``'edge'``) -- whether
3211
every cycle is given as a list of vertices or a list of
3212
edges.
3213
3214
OUTPUT:
3215
3216
A list of lists, each of them representing the vertices (or
3217
the edges) of a cycle in a basis.
3218
3219
ALGORITHM:
3220
3221
Uses the NetworkX library for graphs without multiple edges.
3222
3223
Otherwise, by the standard algorithm using a spanning tree.
3224
3225
EXAMPLE:
3226
3227
A cycle basis in Petersen's Graph ::
3228
3229
sage: g = graphs.PetersenGraph()
3230
sage: g.cycle_basis()
3231
[[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]]
3232
3233
One can also get the result as a list of lists of edges::
3234
3235
sage: g.cycle_basis(output='edge')
3236
[[(1, 2, None), (2, 7, None), (7, 5, None), (5, 0, None),
3237
(0, 1, None)], [(8, 3, None), (3, 2, None), (2, 7, None),
3238
(7, 5, None), (5, 8, None)], [(4, 3, None), (3, 2, None),
3239
(2, 7, None), (7, 5, None), (5, 0, None), (0, 4, None)],
3240
[(4, 9, None), (9, 7, None), (7, 5, None), (5, 0, None),
3241
(0, 4, None)], [(8, 6, None), (6, 9, None), (9, 7, None),
3242
(7, 5, None), (5, 8, None)], [(1, 6, None), (6, 9, None),
3243
(9, 7, None), (7, 5, None), (5, 0, None), (0, 1, None)]]
3244
3245
Checking the given cycles are algebraically free::
3246
3247
sage: g = graphs.RandomGNP(30,.4)
3248
sage: basis = g.cycle_basis()
3249
3250
Building the space of (directed) edges over `Z/2Z`. On the way,
3251
building a dictionary associating an unique vector to each
3252
undirected edge::
3253
3254
sage: m = g.size()
3255
sage: edge_space = VectorSpace(FiniteField(2),m)
3256
sage: edge_vector = dict( zip( g.edges(labels = False), edge_space.basis() ) )
3257
sage: for (u,v),vec in edge_vector.items():
3258
... edge_vector[(v,u)] = vec
3259
3260
Defining a lambda function associating a vector to the
3261
vertices of a cycle::
3262
3263
sage: vertices_to_edges = lambda x : zip( x, x[1:] + [x[0]] )
3264
sage: cycle_to_vector = lambda x : sum( edge_vector[e] for e in vertices_to_edges(x) )
3265
3266
Finally checking the cycles are a free set::
3267
3268
sage: basis_as_vectors = map( cycle_to_vector, basis )
3269
sage: edge_space.span(basis_as_vectors).rank() == len(basis)
3270
True
3271
3272
For undirected graphs with multiple edges::
3273
3274
sage: G = Graph([(0,2,'a'),(0,2,'b'),(0,1,'c'),(1,2,'d')])
3275
sage: G.cycle_basis()
3276
[[0, 2], [2, 1, 0]]
3277
sage: G.cycle_basis(output='edge')
3278
[[(0, 2, 'a'), (2, 0, 'b')], [(2, 1, 'd'), (1, 0, 'c'),
3279
(0, 2, 'a')]]
3280
3281
Disconnected graph::
3282
3283
sage: G.add_cycle(["Hey", "Wuuhuu", "Really ?"])
3284
sage: G.cycle_basis()
3285
[[0, 2], [2, 1, 0], ['Really ?', 'Hey', 'Wuuhuu']]
3286
sage: G.cycle_basis(output='edge')
3287
[[(0, 2, 'a'), (2, 0, 'b')], [(2, 1, 'd'), (1, 0, 'c'), (0, 2, 'a')],
3288
[('Really ?', 'Hey', None), ('Hey', 'Wuuhuu', None), ('Wuuhuu', 'Really ?', None)]]
3289
3290
Graph that allows multiple edges but does not contain any::
3291
3292
sage: G = graphs.CycleGraph(3)
3293
sage: G.allow_multiple_edges(True)
3294
sage: G.cycle_basis()
3295
[[2, 1, 0]]
3296
3297
Not yet implemented for directed graphs with multiple edges::
3298
3299
sage: G = DiGraph([(0,2,'a'),(0,2,'b'),(0,1,'c'),(1,2,'d')])
3300
sage: G.cycle_basis()
3301
Traceback (most recent call last):
3302
...
3303
NotImplementedError: not implemented for directed graphs
3304
with multiple edges
3305
"""
3306
if not output in ['vertex', 'edge']:
3307
raise ValueError('output must be either vertex or edge')
3308
3309
if self.allows_multiple_edges():
3310
if self.is_directed():
3311
raise NotImplementedError('not implemented for directed '
3312
'graphs with multiple edges')
3313
3314
if not self.is_connected():
3315
return sum([g.cycle_basis(output=output)
3316
for g in self.connected_components_subgraphs()],
3317
[])
3318
3319
T = self.min_spanning_tree()
3320
return [self.subgraph(edges=T + [e]).is_forest(certificate=True,
3321
output=output)[1]
3322
for e in self.edges() if not e in T]
3323
3324
# second case: there are no multiple edges
3325
import networkx
3326
cycle_basis_v = networkx.cycle_basis(self.networkx_graph(copy=False))
3327
if output == 'vertex':
3328
return cycle_basis_v
3329
3330
def vertices_to_edges(x):
3331
return [(u[0], u[1], self.edge_label(u[0], u[1]))
3332
for u in zip(x, x[1:] + [x[0]])]
3333
return map(vertices_to_edges, cycle_basis_v)
3334
3335
3336
### Planarity
3337
3338
def is_planar(self, on_embedding=None, kuratowski=False, set_embedding=False, set_pos=False):
3339
"""
3340
Returns True if the graph is planar, and False otherwise. This
3341
wraps the reference implementation provided by John Boyer of the
3342
linear time planarity algorithm by edge addition due to Boyer
3343
Myrvold. (See reference code in graphs.planarity).
3344
3345
Note - the argument on_embedding takes precedence over
3346
set_embedding. This means that only the on_embedding
3347
combinatorial embedding will be tested for planarity and no
3348
_embedding attribute will be set as a result of this function
3349
call, unless on_embedding is None.
3350
3351
REFERENCE:
3352
3353
- [1] John M. Boyer and Wendy J. Myrvold, On the Cutting Edge:
3354
Simplified O(n) Planarity by Edge Addition. Journal of Graph
3355
Algorithms and Applications, Vol. 8, No. 3, pp. 241-273,
3356
2004.
3357
3358
INPUT:
3359
3360
3361
- ``kuratowski`` - returns a tuple with boolean as
3362
first entry. If the graph is nonplanar, will return the Kuratowski
3363
subgraph or minor as the second tuple entry. If the graph is
3364
planar, returns None as the second entry.
3365
3366
- ``on_embedding`` - the embedding dictionary to test
3367
planarity on. (i.e.: will return True or False only for the given
3368
embedding.)
3369
3370
- ``set_embedding`` - whether or not to set the
3371
instance field variable that contains a combinatorial embedding
3372
(clockwise ordering of neighbors at each vertex). This value will
3373
only be set if a planar embedding is found. It is stored as a
3374
Python dict: v1: [n1,n2,n3] where v1 is a vertex and n1,n2,n3 are
3375
its neighbors.
3376
3377
- ``set_pos`` - whether or not to set the position
3378
dictionary (for plotting) to reflect the combinatorial embedding.
3379
Note that this value will default to False if set_emb is set to
3380
False. Also, the position dictionary will only be updated if a
3381
planar embedding is found.
3382
3383
3384
EXAMPLES::
3385
3386
sage: g = graphs.CubeGraph(4)
3387
sage: g.is_planar()
3388
False
3389
3390
::
3391
3392
sage: g = graphs.CircularLadderGraph(4)
3393
sage: g.is_planar(set_embedding=True)
3394
True
3395
sage: g.get_embedding()
3396
{0: [1, 4, 3],
3397
1: [2, 5, 0],
3398
2: [3, 6, 1],
3399
3: [0, 7, 2],
3400
4: [0, 5, 7],
3401
5: [1, 6, 4],
3402
6: [2, 7, 5],
3403
7: [4, 6, 3]}
3404
3405
::
3406
3407
sage: g = graphs.PetersenGraph()
3408
sage: (g.is_planar(kuratowski=True))[1].adjacency_matrix()
3409
[0 1 0 0 0 1 0 0 0]
3410
[1 0 1 0 0 0 1 0 0]
3411
[0 1 0 1 0 0 0 1 0]
3412
[0 0 1 0 0 0 0 0 1]
3413
[0 0 0 0 0 0 1 1 0]
3414
[1 0 0 0 0 0 0 1 1]
3415
[0 1 0 0 1 0 0 0 1]
3416
[0 0 1 0 1 1 0 0 0]
3417
[0 0 0 1 0 1 1 0 0]
3418
3419
::
3420
3421
sage: k43 = graphs.CompleteBipartiteGraph(4,3)
3422
sage: result = k43.is_planar(kuratowski=True); result
3423
(False, Graph on 6 vertices)
3424
sage: result[1].is_isomorphic(graphs.CompleteBipartiteGraph(3,3))
3425
True
3426
3427
Multi-edged and looped graphs are partially supported::
3428
3429
sage: G = Graph({0:[1,1]}, multiedges=True)
3430
sage: G.is_planar()
3431
True
3432
sage: G.is_planar(on_embedding={})
3433
Traceback (most recent call last):
3434
...
3435
NotImplementedError: Cannot compute with embeddings of multiple-edged or looped graphs.
3436
sage: G.is_planar(set_pos=True)
3437
Traceback (most recent call last):
3438
...
3439
NotImplementedError: Cannot compute with embeddings of multiple-edged or looped graphs.
3440
sage: G.is_planar(set_embedding=True)
3441
Traceback (most recent call last):
3442
...
3443
NotImplementedError: Cannot compute with embeddings of multiple-edged or looped graphs.
3444
sage: G.is_planar(kuratowski=True)
3445
(True, None)
3446
3447
::
3448
3449
sage: G = graphs.CompleteGraph(5)
3450
sage: G = Graph(G, multiedges=True)
3451
sage: G.add_edge(0,1)
3452
sage: G.is_planar()
3453
False
3454
sage: b,k = G.is_planar(kuratowski=True)
3455
sage: b
3456
False
3457
sage: k.vertices()
3458
[0, 1, 2, 3, 4]
3459
3460
"""
3461
if self.has_multiple_edges() or self.has_loops():
3462
if set_embedding or (on_embedding is not None) or set_pos:
3463
raise NotImplementedError("Cannot compute with embeddings of multiple-edged or looped graphs.")
3464
else:
3465
return self.to_simple().is_planar(kuratowski=kuratowski)
3466
if on_embedding:
3467
if self._check_embedding_validity(on_embedding):
3468
return (0 == self.genus(minimal=False,set_embedding=False,on_embedding=on_embedding))
3469
else:
3470
raise ValueError('on_embedding is not a valid embedding for %s'%self)
3471
else:
3472
from sage.graphs.planarity import is_planar
3473
G = self.to_undirected()
3474
planar = is_planar(G,kuratowski=kuratowski,set_pos=set_pos,set_embedding=set_embedding)
3475
if kuratowski:
3476
bool_result = planar[0]
3477
else:
3478
bool_result = planar
3479
if bool_result:
3480
if set_pos:
3481
self._pos = G._pos
3482
if set_embedding:
3483
self._embedding = G._embedding
3484
return planar
3485
3486
def is_circular_planar(self, on_embedding=None, kuratowski=False,
3487
set_embedding=True, boundary = None,
3488
ordered=False, set_pos=False):
3489
"""
3490
Tests whether the graph is circular planar (outerplanar)
3491
3492
A graph is circular planar if it has a planar embedding in which all
3493
vertices can be drawn in order on a circle. This method can also be used
3494
to check the existence of a planar embedding in which the vertices of a
3495
specific set (the *boundary*) can be drawn on a circle, all other
3496
vertices being drawn inside of the circle. An order can be defined on
3497
the vertices of the boundary in order to define how they are to appear
3498
on the circle.
3499
3500
INPUT:
3501
3502
- ``kuratowski`` (boolean) - if set to True, returns a tuple with
3503
boolean first entry and the Kuratowski subgraph or minor as the
3504
second entry (see OUTPUT below). It is set to ``False`` by default.
3505
3506
- ``on_embedding`` (boolean) - the embedding dictionary to test
3507
planarity on. (i.e.: will return ``True`` or ``False`` only for the
3508
given embedding). It is set to ``False`` by default.
3509
3510
- ``set_embedding`` (boolean) - whether or not to set the instance field
3511
variable that contains a combinatorial embedding (clockwise ordering
3512
of neighbors at each vertex). This value will only be set if a
3513
circular planar embedding is found. It is stored as a Python dict:
3514
``v1: [n1,n2,n3]`` where ``v1`` is a vertex and ``n1,n2,n3`` are its
3515
neighbors. It is set to ``True`` by default.
3516
3517
- ``boundary`` - a set of vertices that are required to be drawn on the
3518
circle, all others being drawn inside of it. It is set to ``None`` by
3519
default, meaning that *all* vertices should be drawn on the boundary.
3520
3521
- ``ordered`` (boolean) - whether or not to consider the order of the
3522
boundary. It is set to ``False`` by default, and required
3523
``boundary`` to be defined.
3524
3525
- ``set_pos`` - whether or not to set the position dictionary (for
3526
plotting) to reflect the combinatorial embedding. Note that this
3527
value will default to False if set_emb is set to False. Also, the
3528
position dictionary will only be updated if a circular planar
3529
embedding is found.
3530
3531
OUTPUT:
3532
3533
The method returns ``True`` if the graph is circular planar, and
3534
``False`` if it is not.
3535
3536
If ``kuratowski`` is set to ``True``, then this function will return a
3537
tuple, whose first entry is a boolean and whose second entry is the
3538
Kuratowski subgraph or minor isolated by the Boyer-Myrvold
3539
algorithm. Note that this graph might contain a vertex or edges that
3540
were not in the initial graph. These would be elements referred to below
3541
as parts of the wheel and the star, which were added to the graph to
3542
require that the boundary can be drawn on the boundary of a disc, with
3543
all other vertices drawn inside (and no edge crossings). For more
3544
information, see [Kirkman]_.
3545
3546
ALGORITHM:
3547
3548
This is a linear time algorithm to test for circular planarity. It
3549
relies on the edge-addition planarity algorithm due to
3550
Boyer-Myrvold. We accomplish linear time for circular planarity by
3551
modifying the graph before running the general planarity
3552
algorithm.
3553
3554
REFERENCE:
3555
3556
.. [BM04] John M. Boyer and Wendy J. Myrvold, On the Cutting Edge:
3557
Simplified O(n) Planarity by Edge Addition. Journal of Graph
3558
Algorithms and Applications, Vol. 8, No. 3, pp. 241-273,
3559
2004.
3560
3561
.. [Kirkman] Kirkman, Emily A. O(n) Circular Planarity Testing.
3562
[Online] Available: soon!
3563
3564
EXAMPLES::
3565
3566
sage: g439 = Graph({1:[5,7], 2:[5,6], 3:[6,7], 4:[5,6,7]})
3567
sage: g439.set_boundary([1,2,3,4])
3568
sage: g439.show()
3569
sage: g439.is_circular_planar(boundary = [1,2,3,4])
3570
False
3571
sage: g439.is_circular_planar(kuratowski=True, boundary = [1,2,3,4])
3572
(False, Graph on 8 vertices)
3573
sage: g439.is_circular_planar(kuratowski=True, boundary = [1,2,3])
3574
(True, None)
3575
sage: g439.get_embedding()
3576
{1: [7, 5],
3577
2: [5, 6],
3578
3: [6, 7],
3579
4: [7, 6, 5],
3580
5: [1, 4, 2],
3581
6: [2, 4, 3],
3582
7: [3, 4, 1]}
3583
3584
Order matters::
3585
3586
sage: K23 = graphs.CompleteBipartiteGraph(2,3)
3587
sage: K23.is_circular_planar(boundary = [0,1,2,3])
3588
True
3589
sage: K23.is_circular_planar(ordered=True, boundary = [0,1,2,3])
3590
False
3591
3592
With a different order::
3593
3594
sage: K23.is_circular_planar(set_embedding=True, boundary = [0,2,1,3])
3595
True
3596
"""
3597
if ordered and boundary is None:
3598
raise ValueError("boundary must be set when ordered is True.")
3599
3600
if boundary is None:
3601
boundary = self
3602
3603
# A local copy of self
3604
from sage.graphs.planarity import is_planar
3605
graph = self.to_undirected()
3606
if hasattr(graph, '_embedding'):
3607
del(graph._embedding)
3608
3609
# Adds a new vertex to the graph
3610
extra = 0
3611
while graph.has_vertex(extra):
3612
extra=extra+1
3613
graph.add_vertex(extra)
3614
3615
# Adds edges from the new vertex to all vertices of the boundary
3616
for vertex in boundary:
3617
graph.add_edge(vertex,extra)
3618
3619
extra_edges = []
3620
3621
# When ordered is True, we need a way to make sure that the ordering is
3622
# respected.
3623
if ordered:
3624
3625
# We add edges between consecutive vertices of the boundary (only
3626
# len(boundary)-1 are actually sufficient)
3627
for u,v in zip(boundary[:-1],boundary[1:]):
3628
if not graph.has_edge(u,v):
3629
extra_edges.append((u,v))
3630
3631
graph.add_edges(extra_edges)
3632
3633
result = is_planar(graph,kuratowski=kuratowski,set_embedding=set_embedding,circular=True)
3634
3635
if kuratowski:
3636
bool_result = result[0]
3637
else:
3638
bool_result = result
3639
3640
if bool_result:
3641
graph.delete_vertex(extra)
3642
graph.delete_edges(extra_edges)
3643
3644
if hasattr(graph,'_embedding'):
3645
# strip the embedding to fit original graph
3646
for u,v in extra_edges:
3647
graph._embedding[u].pop(graph._embedding[u].index(v))
3648
graph._embedding[v].pop(graph._embedding[v].index(u))
3649
for w in boundary:
3650
graph._embedding[w].pop(graph._embedding[w].index(extra))
3651
3652
if set_embedding:
3653
self._embedding = graph._embedding.copy()
3654
3655
if (set_pos and set_embedding):
3656
self.set_planar_positions()
3657
3658
del graph
3659
return result
3660
3661
# TODO: rename into _layout_planar
3662
def set_planar_positions(self, test = False, **layout_options):
3663
"""
3664
Compute a planar layout for self using Schnyder's algorithm,
3665
and save it as default layout.
3666
3667
EXAMPLES::
3668
3669
sage: g = graphs.CycleGraph(7)
3670
sage: g.set_planar_positions(test=True)
3671
True
3672
3673
This method is deprecated since Sage-4.4.1.alpha2. Please use instead:
3674
3675
sage: g.layout(layout = "planar", save_pos = True)
3676
{0: [1, 1], 1: [2, 2], 2: [3, 2], 3: [1, 4], 4: [5, 1], 5: [0, 5], 6: [1, 0]}
3677
"""
3678
self.layout(layout = "planar", save_pos = True, test = test, **layout_options)
3679
if test: # Optional error-checking, ( looking for edge-crossings O(n^2) ).
3680
return self.is_drawn_free_of_edge_crossings() # returns true if tests pass
3681
else:
3682
return
3683
3684
def layout_planar(self, set_embedding=False, on_embedding=None, external_face=None, test=False, circular=False, **options):
3685
"""
3686
Uses Schnyder's algorithm to compute a planar layout for self,
3687
raising an error if self is not planar.
3688
3689
INPUT:
3690
3691
- ``set_embedding`` - if True, sets the combinatorial
3692
embedding used (see self.get_embedding())
3693
3694
- ``on_embedding`` - dict: provide a combinatorial
3695
embedding
3696
3697
- ``external_face`` - ignored
3698
3699
- ``test`` - if True, perform sanity tests along the way
3700
3701
- ``circular`` - ignored
3702
3703
3704
EXAMPLES::
3705
3706
sage: g = graphs.PathGraph(10)
3707
sage: g.set_planar_positions(test=True)
3708
True
3709
sage: g = graphs.BalancedTree(3,4)
3710
sage: g.set_planar_positions(test=True)
3711
True
3712
sage: g = graphs.CycleGraph(7)
3713
sage: g.set_planar_positions(test=True)
3714
True
3715
sage: g = graphs.CompleteGraph(5)
3716
sage: g.set_planar_positions(test=True,set_embedding=True)
3717
Traceback (most recent call last):
3718
...
3719
ValueError: Complete graph is not a planar graph
3720
"""
3721
from sage.graphs.schnyder import _triangulate, _normal_label, _realizer, _compute_coordinates
3722
3723
G = self.to_undirected()
3724
try:
3725
G._embedding = self._embedding
3726
except AttributeError:
3727
pass
3728
embedding_copy = None
3729
if set_embedding:
3730
if not (G.is_planar(set_embedding=True)):
3731
raise ValueError('%s is not a planar graph'%self)
3732
embedding_copy = G._embedding
3733
else:
3734
if on_embedding is not None:
3735
if G._check_embedding_validity(on_embedding):
3736
if not (G.is_planar(on_embedding=on_embedding)):
3737
raise ValueError('provided embedding is not a planar embedding for %s'%self )
3738
else:
3739
raise ValueError('provided embedding is not a valid embedding for %s. Try putting set_embedding=True'%self)
3740
else:
3741
if hasattr(G,'_embedding'):
3742
if G._check_embedding_validity():
3743
if not (G.is_planar(on_embedding=G._embedding)):
3744
raise ValueError('%s has nonplanar _embedding attribute. Try putting set_embedding=True'%self)
3745
embedding_copy = G._embedding
3746
else:
3747
raise ValueError('provided embedding is not a valid embedding for %s. Try putting set_embedding=True'%self)
3748
else:
3749
G.is_planar(set_embedding=True)
3750
3751
# The following is what was breaking the code. It is where we were specifying the external
3752
# face ahead of time. This is definitely a TODO:
3753
#
3754
# Running is_planar(set_embedding=True) has set attribute self._embedding
3755
#if external_face is None:
3756
# faces = faces( self, self._embedding )
3757
# faces.sort(key=len)
3758
# external_face = faces[-1]
3759
3760
#n = len(external_face)
3761
#other_added_edges = []
3762
#if n > 3:
3763
# v1, v2, v3 = external_face[0][0], external_face[int(n/3)][0], external_face[int(2*n/3)][0]
3764
# if not self.has_edge( (v1,v2) ):
3765
# self.add_edge( (v1, v2) )
3766
# other_added_edges.append( (v1, v2) )
3767
# if not self.has_edge( (v2,v3) ):
3768
# self.add_edge( (v2, v3) )
3769
# other_added_edges.append( (v2, v3) )
3770
# if not self.has_edge( (v3,v1) ):
3771
# self.add_edge( (v3, v1) )
3772
# other_added_edges.append( (v3, v1) )
3773
# if not self.is_planar(set_embedding=True): # get new combinatorial embedding (with added edges)
3774
# raise ValueError('modified graph %s is not planar. Try specifying an external face'%self)
3775
3776
# Triangulate the graph
3777
extra_edges = _triangulate( G, G._embedding)
3778
3779
# Optional error-checking
3780
if test:
3781
G.is_planar(set_embedding=True) # to get new embedding
3782
test_faces = G.faces(G._embedding)
3783
for face in test_faces:
3784
if len(face) != 3:
3785
raise RuntimeError('BUG: Triangulation returned face: %s'%face)
3786
3787
G.is_planar(set_embedding=True)
3788
faces = G.faces(G._embedding)
3789
# Assign a normal label to the graph
3790
label = _normal_label( G, G._embedding, faces[0] )
3791
3792
# Get dictionary of tree nodes from the realizer
3793
tree_nodes = _realizer( G, label)
3794
3795
# Compute the coordinates and store in position dictionary (attr self._pos)
3796
_compute_coordinates( G, tree_nodes )
3797
3798
# Delete all the edges added to the graph
3799
#G.delete_edges( extra_edges )
3800
#self.delete_edges( other_added_edges )
3801
3802
if embedding_copy is not None:
3803
self._embedding = embedding_copy
3804
3805
return G._pos
3806
3807
def is_drawn_free_of_edge_crossings(self):
3808
"""
3809
Returns True is the position dictionary for this graph is set and
3810
that position dictionary gives a planar embedding.
3811
3812
This simply checks all pairs of edges that don't share a vertex to
3813
make sure that they don't intersect.
3814
3815
.. note::
3816
3817
This function require that _pos attribute is set. (Returns
3818
False otherwise.)
3819
3820
EXAMPLES::
3821
3822
sage: D = graphs.DodecahedralGraph()
3823
sage: D.set_planar_positions()
3824
sage: D.is_drawn_free_of_edge_crossings()
3825
True
3826
"""
3827
if self._pos is None:
3828
return False
3829
3830
G = self.to_undirected()
3831
for edge1 in G.edges(labels = False):
3832
for edge2 in G.edges(labels = False):
3833
if edge1[0] == edge2[0] or edge1[0] == edge2[1] or edge1[1] == edge2[0] or edge1[1] == edge2[1]:
3834
continue
3835
p1, p2 = self._pos[edge1[0]], self._pos[edge1[1]]
3836
dy = Rational(p2[1] - p1[1])
3837
dx = Rational(p2[0] - p1[0])
3838
q1, q2 = self._pos[edge2[0]], self._pos[edge2[1]]
3839
db = Rational(q2[1] - q1[1])
3840
da = Rational(q2[0] - q1[0])
3841
if(da * dy == db * dx):
3842
if dx != 0:
3843
t1 = Rational(q1[0] - p1[0])/dx
3844
t2 = Rational(q2[0] - p1[0])/dx
3845
if (0 <= t1 and t1 <= 1) or (0 <= t2 and t2 <= 1):
3846
if p1[1] + t1 * dy == q1[1] or p1[1] + t2 * dy == q2[1]:
3847
return False
3848
else:
3849
t1 = Rational(q1[1] - p1[1])/dy
3850
t2 = Rational(q2[1] - p1[1])/dy
3851
if (0 <= t1 and t1 <= 1) or (0 <= t2 and t2 <= 1):
3852
if p1[0] + t1 * dx == q1[0] or p1[0] + t2 * dx == q2[0]:
3853
return False
3854
else:
3855
s = (dx * Rational(q1[1] - p1[1]) + dy * Rational(p1[0] - q1[0])) / (da * dy - db * dx)
3856
t = (da * Rational(p1[1] - q1[1]) + db * Rational(q1[0] - p1[0])) / (db * dx - da * dy)
3857
3858
if s >= 0 and s <= 1 and t >= 0 and t <= 1:
3859
print 'fail on', p1, p2, ' : ',q1, q2
3860
print edge1, edge2
3861
return False
3862
return True
3863
3864
def genus(self, set_embedding=True, on_embedding=None, minimal=True, maximal=False, circular=False, ordered=True):
3865
"""
3866
Returns the minimal genus of the graph. The genus of a compact
3867
surface is the number of handles it has. The genus of a graph is
3868
the minimal genus of the surface it can be embedded into.
3869
3870
Note - This function uses Euler's formula and thus it is necessary
3871
to consider only connected graphs.
3872
3873
INPUT:
3874
3875
- ``set_embedding (boolean)`` - whether or not to
3876
store an embedding attribute of the computed (minimal) genus of the
3877
graph. (Default is True).
3878
3879
- ``on_embedding (dict)`` - a combinatorial embedding
3880
to compute the genus of the graph on. Note that this must be a
3881
valid embedding for the graph. The dictionary structure is given
3882
by: vertex1: [neighbor1, neighbor2, neighbor3], vertex2: [neighbor]
3883
where there is a key for each vertex in the graph and a (clockwise)
3884
ordered list of each vertex's neighbors as values. on_embedding
3885
takes precedence over a stored _embedding attribute if minimal is
3886
set to False. Note that as a shortcut, the user can enter
3887
on_embedding=True to compute the genus on the current _embedding
3888
attribute. (see eg's.)
3889
3890
- ``minimal (boolean)`` - whether or not to compute
3891
the minimal genus of the graph (i.e., testing all embeddings). If
3892
minimal is False, then either maximal must be True or on_embedding
3893
must not be None. If on_embedding is not None, it will take
3894
priority over minimal. Similarly, if maximal is True, it will take
3895
priority over minimal.
3896
3897
- ``maximal (boolean)`` - whether or not to compute
3898
the maximal genus of the graph (i.e., testing all embeddings). If
3899
maximal is False, then either minimal must be True or on_embedding
3900
must not be None. If on_embedding is not None, it will take
3901
priority over maximal. However, maximal takes priority over the
3902
default minimal.
3903
3904
- ``circular (boolean)`` - whether or not to compute
3905
the genus preserving a planar embedding of the boundary. (Default
3906
is False). If circular is True, on_embedding is not a valid
3907
option.
3908
3909
- ``ordered (boolean)`` - if circular is True, then
3910
whether or not the boundary order may be permuted. (Default is
3911
True, which means the boundary order is preserved.)
3912
3913
EXAMPLES::
3914
3915
sage: g = graphs.PetersenGraph()
3916
sage: g.genus() # tests for minimal genus by default
3917
1
3918
sage: g.genus(on_embedding=True, maximal=True) # on_embedding overrides minimal and maximal arguments
3919
1
3920
sage: g.genus(maximal=True) # setting maximal to True overrides default minimal=True
3921
3
3922
sage: g.genus(on_embedding=g.get_embedding()) # can also send a valid combinatorial embedding dict
3923
3
3924
sage: (graphs.CubeGraph(3)).genus()
3925
0
3926
sage: K23 = graphs.CompleteBipartiteGraph(2,3)
3927
sage: K23.genus()
3928
0
3929
sage: K33 = graphs.CompleteBipartiteGraph(3,3)
3930
sage: K33.genus()
3931
1
3932
3933
Using the circular argument, we can compute the minimal genus
3934
preserving a planar, ordered boundary::
3935
3936
sage: cube = graphs.CubeGraph(2)
3937
sage: cube.set_boundary(['01','10'])
3938
sage: cube.genus()
3939
0
3940
sage: cube.is_circular_planar()
3941
True
3942
sage: cube.genus(circular=True)
3943
0
3944
sage: cube.genus(circular=True, on_embedding=True)
3945
0
3946
sage: cube.genus(circular=True, maximal=True)
3947
Traceback (most recent call last):
3948
...
3949
NotImplementedError: Cannot compute the maximal genus of a genus respecting a boundary.
3950
3951
Note: not everything works for multigraphs, looped graphs or digraphs. But the
3952
minimal genus is ultimately computable for every connected graph -- but the
3953
embedding we obtain for the simple graph can't be easily converted to an
3954
embedding of a non-simple graph. Also, the maximal genus of a multigraph does
3955
not trivially correspond to that of its simple graph.
3956
3957
sage: G = DiGraph({ 0 : [0,1,1,1], 1 : [2,2,3,3], 2 : [1,3,3], 3:[0,3]})
3958
sage: G.genus()
3959
Traceback (most recent call last):
3960
...
3961
NotImplementedError: Can't work with embeddings of non-simple graphs
3962
sage: G.to_simple().genus()
3963
0
3964
sage: G.genus(set_embedding=False)
3965
0
3966
sage: G.genus(maximal=True, set_embedding=False)
3967
Traceback (most recent call last):
3968
...
3969
NotImplementedError: Can't compute the maximal genus of a graph with loops or multiple edges
3970
3971
3972
We break graphs with cut vertices into their blocks, which greatly speeds up
3973
computation of minimal genus. This is not implemented for maximal genus.
3974
3975
sage: K5 = graphs.CompleteGraph(5)
3976
sage: G = K5.copy()
3977
sage: s = 4
3978
sage: for i in range(1,100):
3979
... k = K5.relabel(range(s,s+5),inplace=False)
3980
... G.add_edges(k.edges())
3981
... s += 4
3982
...
3983
sage: G.genus()
3984
100
3985
3986
"""
3987
if not self.is_connected():
3988
raise TypeError("Graph must be connected to use Euler's Formula to compute minimal genus.")
3989
3990
G = self.to_simple()
3991
verts = G.order()
3992
edges = G.size()
3993
3994
if maximal:
3995
minimal = False
3996
3997
if circular:
3998
if maximal:
3999
raise NotImplementedError("Cannot compute the maximal genus of a genus respecting a boundary.")
4000
boundary = G.get_boundary()
4001
if hasattr(G, '_embedding'):
4002
del(G._embedding)
4003
4004
extra = 0
4005
while G.has_vertex(extra):
4006
extra=extra+1
4007
G.add_vertex(extra)
4008
verts += 1
4009
4010
for vertex in boundary:
4011
G.add_edge(vertex,extra)
4012
4013
extra_edges = []
4014
if ordered: # WHEEL
4015
for i in range(len(boundary)-1):
4016
if not G.has_edge(boundary[i],boundary[i+1]):
4017
G.add_edge(boundary[i],boundary[i+1])
4018
extra_edges.append((boundary[i],boundary[i+1]))
4019
if not G.has_edge(boundary[-1],boundary[0]):
4020
G.add_edge(boundary[-1],boundary[0])
4021
extra_edges.append((boundary[-1],boundary[0]))
4022
# else STAR (empty list of extra edges)
4023
4024
edges = G.size()
4025
4026
if on_embedding is not None:
4027
if self.has_loops() or self.is_directed() or self.has_multiple_edges():
4028
raise NotImplementedError("Can't work with embeddings of non-simple graphs")
4029
if on_embedding: #i.e., if on_embedding True (returns False if on_embedding is of type dict)
4030
try:
4031
faces = len(self.faces(self._embedding))
4032
except AttributeError:
4033
raise AttributeError('graph must have attribute _embedding set to compute current (embedded) genus')
4034
return (2-verts+edges-faces)/2
4035
else: # compute genus on the provided dict
4036
faces = len(self.faces(on_embedding))
4037
return (2-verts+edges-faces)/2
4038
else: # then compute either maximal or minimal genus of all embeddings
4039
import genus
4040
4041
if set_embedding:
4042
if self.has_loops() or self.is_directed() or self.has_multiple_edges():
4043
raise NotImplementedError("Can't work with embeddings of non-simple graphs")
4044
if minimal:
4045
B,C = G.blocks_and_cut_vertices()
4046
embedding = {}
4047
g = 0
4048
for block in B:
4049
H = G.subgraph(block)
4050
g += genus.simple_connected_graph_genus(H, set_embedding = True, check = False, minimal = True)
4051
emb = H.get_embedding()
4052
for v in emb:
4053
if embedding.has_key(v):
4054
embedding[v] += emb[v]
4055
else:
4056
embedding[v] = emb[v]
4057
self._embedding = embedding
4058
else:
4059
g = genus.simple_connected_graph_genus(G, set_embedding = True, check = False, minimal = minimal)
4060
self._embedding = G._embedding
4061
return g
4062
else:
4063
if maximal and (self.has_multiple_edges() or self.has_loops()):
4064
raise NotImplementedError("Can't compute the maximal genus of a graph with loops or multiple edges")
4065
if minimal:
4066
B,C = G.blocks_and_cut_vertices()
4067
g = 0
4068
for block in B:
4069
H = G.subgraph(block)
4070
g += genus.simple_connected_graph_genus(H, set_embedding = False, check = False, minimal = True)
4071
return g
4072
else:
4073
return genus.simple_connected_graph_genus(G, set_embedding = False, check=False, minimal=minimal)
4074
4075
def faces(self, embedding = None):
4076
"""
4077
Return the faces of an embedded graph.
4078
4079
A combinatorial embedding of a graph is a clockwise ordering of the
4080
neighbors of each vertex. From this information one can define the faces
4081
of the embedding, which is what this method returns.
4082
4083
INPUT:
4084
4085
- ``embedding`` - a combinatorial embedding dictionary. Format:
4086
``{v1:[v2,v3], v2:[v1], v3:[v1]}`` (clockwise ordering of neighbors at
4087
each vertex). If set to ``None`` (default) the method will use the
4088
embedding stored as ``self._embedding``. If none is stored, the method
4089
will compute the set of faces from the embedding returned by
4090
:meth:`is_planar` (if the graph is, of course, planar).
4091
4092
.. NOTE::
4093
4094
``embedding`` is an ordered list based on the hash order of the
4095
vertices of graph. To avoid confusion, it might be best to set the
4096
rot_sys based on a 'nice_copy' of the graph.
4097
4098
.. SEEALSO::
4099
4100
* :meth:`set_embedding`
4101
* :meth:`get_embedding`
4102
* :meth:`is_planar`
4103
4104
EXAMPLES::
4105
4106
sage: T = graphs.TetrahedralGraph()
4107
sage: T.faces({0: [1, 3, 2], 1: [0, 2, 3], 2: [0, 3, 1], 3: [0, 1, 2]})
4108
[[(0, 1), (1, 2), (2, 0)],
4109
[(3, 2), (2, 1), (1, 3)],
4110
[(2, 3), (3, 0), (0, 2)],
4111
[(0, 3), (3, 1), (1, 0)]]
4112
4113
With no embedding provided::
4114
4115
sage: graphs.TetrahedralGraph().faces()
4116
[[(0, 1), (1, 2), (2, 0)],
4117
[(3, 2), (2, 1), (1, 3)],
4118
[(2, 3), (3, 0), (0, 2)],
4119
[(0, 3), (3, 1), (1, 0)]]
4120
4121
With no embedding provided (non-planar graph)::
4122
4123
sage: graphs.PetersenGraph().faces()
4124
Traceback (most recent call last):
4125
...
4126
ValueError: No embedding is provided and the graph is not planar.
4127
4128
TESTS:
4129
4130
:trac:`15551` deprecated the ``trace_faces`` name::
4131
4132
sage: T.trace_faces({0: [1, 3, 2], 1: [0, 2, 3], 2: [0, 3, 1], 3: [0, 1, 2]})
4133
doctest:...: DeprecationWarning: trace_faces is deprecated. Please use faces instead.
4134
See http://trac.sagemath.org/15551 for details.
4135
[[(0, 1), (1, 2), (2, 0)], [(3, 2), (2, 1), (1, 3)], [(2, 3), (3, 0), (0, 2)], [(0, 3), (3, 1), (1, 0)]]
4136
4137
"""
4138
# Which embedding should we use ?
4139
if embedding is None:
4140
# Is self._embedding available ?
4141
if self._check_embedding_validity():
4142
embedding = self._embedding
4143
else:
4144
if self.is_planar(set_embedding=True):
4145
embedding = self._embedding
4146
self._embedding = None
4147
else:
4148
raise ValueError("No embedding is provided and the graph is not planar.")
4149
4150
from sage.sets.set import Set
4151
4152
# Establish set of possible edges
4153
edgeset = Set([])
4154
for edge in self.to_undirected().edges():
4155
edgeset = edgeset.union(Set([(edge[0],edge[1]),(edge[1],edge[0])]))
4156
4157
# Storage for face paths
4158
faces = []
4159
path = []
4160
for edge in edgeset:
4161
path.append(edge)
4162
edgeset -= Set([edge])
4163
break # (Only one iteration)
4164
4165
# Trace faces
4166
while (len(edgeset) > 0):
4167
neighbors = embedding[path[-1][-1]]
4168
next_node = neighbors[(neighbors.index(path[-1][-2])+1)%(len(neighbors))]
4169
tup = (path[-1][-1],next_node)
4170
if tup == path[0]:
4171
faces.append(path)
4172
path = []
4173
for edge in edgeset:
4174
path.append(edge)
4175
edgeset -= Set([edge])
4176
break # (Only one iteration)
4177
else:
4178
path.append(tup)
4179
edgeset -= Set([tup])
4180
if (len(path) != 0): faces.append(path)
4181
return faces
4182
4183
trace_faces = deprecated_function_alias(15551, faces)
4184
4185
### Connectivity
4186
4187
def is_connected(self):
4188
"""
4189
Indicates whether the (di)graph is connected. Note that in a graph,
4190
path connected is equivalent to connected.
4191
4192
EXAMPLES::
4193
4194
sage: G = Graph( { 0 : [1, 2], 1 : [2], 3 : [4, 5], 4 : [5] } )
4195
sage: G.is_connected()
4196
False
4197
sage: G.add_edge(0,3)
4198
sage: G.is_connected()
4199
True
4200
sage: D = DiGraph( { 0 : [1, 2], 1 : [2], 3 : [4, 5], 4 : [5] } )
4201
sage: D.is_connected()
4202
False
4203
sage: D.add_edge(0,3)
4204
sage: D.is_connected()
4205
True
4206
sage: D = DiGraph({1:[0], 2:[0]})
4207
sage: D.is_connected()
4208
True
4209
"""
4210
if self.order() == 0:
4211
return True
4212
4213
try:
4214
return self._backend.is_connected()
4215
except AttributeError:
4216
v = self.vertex_iterator().next()
4217
conn_verts = list(self.depth_first_search(v, ignore_direction=True))
4218
return len(conn_verts) == self.num_verts()
4219
4220
def connected_components(self):
4221
"""
4222
Returns the list of connected components.
4223
4224
Returns a list of lists of vertices, each list representing a
4225
connected component. The list is ordered from largest to smallest
4226
component.
4227
4228
EXAMPLES::
4229
4230
sage: G = Graph( { 0 : [1, 3], 1 : [2], 2 : [3], 4 : [5, 6], 5 : [6] } )
4231
sage: G.connected_components()
4232
[[0, 1, 2, 3], [4, 5, 6]]
4233
sage: D = DiGraph( { 0 : [1, 3], 1 : [2], 2 : [3], 4 : [5, 6], 5 : [6] } )
4234
sage: D.connected_components()
4235
[[0, 1, 2, 3], [4, 5, 6]]
4236
"""
4237
seen = set()
4238
components = []
4239
for v in self:
4240
if v not in seen:
4241
c = self.connected_component_containing_vertex(v)
4242
seen.update(c)
4243
components.append(c)
4244
components.sort(lambda comp1, comp2: cmp(len(comp2), len(comp1)))
4245
return components
4246
4247
def connected_components_number(self):
4248
"""
4249
Returns the number of connected components.
4250
4251
EXAMPLES::
4252
4253
sage: G = Graph( { 0 : [1, 3], 1 : [2], 2 : [3], 4 : [5, 6], 5 : [6] } )
4254
sage: G.connected_components_number()
4255
2
4256
sage: D = DiGraph( { 0 : [1, 3], 1 : [2], 2 : [3], 4 : [5, 6], 5 : [6] } )
4257
sage: D.connected_components_number()
4258
2
4259
"""
4260
return len(self.connected_components())
4261
4262
def connected_components_subgraphs(self):
4263
"""
4264
Returns a list of connected components as graph objects.
4265
4266
EXAMPLES::
4267
4268
sage: G = Graph( { 0 : [1, 3], 1 : [2], 2 : [3], 4 : [5, 6], 5 : [6] } )
4269
sage: L = G.connected_components_subgraphs()
4270
sage: graphs_list.show_graphs(L)
4271
sage: D = DiGraph( { 0 : [1, 3], 1 : [2], 2 : [3], 4 : [5, 6], 5 : [6] } )
4272
sage: L = D.connected_components_subgraphs()
4273
sage: graphs_list.show_graphs(L)
4274
"""
4275
cc = self.connected_components()
4276
list = []
4277
for c in cc:
4278
list.append(self.subgraph(c, inplace=False))
4279
return list
4280
4281
def connected_component_containing_vertex(self, vertex):
4282
"""
4283
Returns a list of the vertices connected to vertex.
4284
4285
EXAMPLES::
4286
4287
sage: G = Graph( { 0 : [1, 3], 1 : [2], 2 : [3], 4 : [5, 6], 5 : [6] } )
4288
sage: G.connected_component_containing_vertex(0)
4289
[0, 1, 2, 3]
4290
sage: D = DiGraph( { 0 : [1, 3], 1 : [2], 2 : [3], 4 : [5, 6], 5 : [6] } )
4291
sage: D.connected_component_containing_vertex(0)
4292
[0, 1, 2, 3]
4293
"""
4294
try:
4295
c = list(self._backend.depth_first_search(vertex, ignore_direction=True))
4296
except AttributeError:
4297
c = list(self.depth_first_search(vertex, ignore_direction=True))
4298
4299
c.sort()
4300
return c
4301
4302
def blocks_and_cut_vertices(self):
4303
"""
4304
Computes the blocks and cut vertices of the graph.
4305
4306
In the case of a digraph, this computation is done on the underlying
4307
graph.
4308
4309
A cut vertex is one whose deletion increases the number of
4310
connected components. A block is a maximal induced subgraph which
4311
itself has no cut vertices. Two distinct blocks cannot overlap in
4312
more than a single cut vertex.
4313
4314
OUTPUT: ``( B, C )``, where ``B`` is a list of blocks- each is
4315
a list of vertices and the blocks are the corresponding induced
4316
subgraphs-and ``C`` is a list of cut vertices.
4317
4318
ALGORITHM:
4319
4320
We implement the algorithm proposed by Tarjan in [Tarjan72]_. The
4321
original version is recursive. We emulate the recursion using a stack.
4322
4323
.. SEEALSO:: :meth:`blocks_and_cuts_tree`
4324
4325
EXAMPLES::
4326
4327
sage: graphs.PetersenGraph().blocks_and_cut_vertices()
4328
([[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]], [])
4329
sage: graphs.PathGraph(6).blocks_and_cut_vertices()
4330
([[4, 5], [3, 4], [2, 3], [1, 2], [0, 1]], [1, 2, 3, 4])
4331
sage: graphs.CycleGraph(7).blocks_and_cut_vertices()
4332
([[0, 1, 2, 3, 4, 5, 6]], [])
4333
sage: graphs.KrackhardtKiteGraph().blocks_and_cut_vertices()
4334
([[8, 9], [7, 8], [0, 1, 2, 3, 4, 5, 6, 7]], [7, 8])
4335
sage: G=Graph() # make a bowtie graph where 0 is a cut vertex
4336
sage: G.add_vertices(range(5))
4337
sage: G.add_edges([(0,1),(0,2),(0,3),(0,4),(1,2),(3,4)])
4338
sage: G.blocks_and_cut_vertices()
4339
([[0, 1, 2], [0, 3, 4]], [0])
4340
sage: graphs.StarGraph(3).blocks_and_cut_vertices()
4341
([[0, 1], [0, 2], [0, 3]], [0])
4342
4343
TESTS::
4344
4345
sage: Graph(0).blocks_and_cut_vertices()
4346
([], [])
4347
sage: Graph(1).blocks_and_cut_vertices()
4348
([[0]], [])
4349
sage: Graph(2).blocks_and_cut_vertices()
4350
Traceback (most recent call last):
4351
...
4352
NotImplementedError: ...
4353
4354
REFERENCE:
4355
4356
.. [Tarjan72] R.E. Tarjan. Depth-First Search and Linear Graph
4357
Algorithms. SIAM J. Comput. 1(2): 146-160 (1972).
4358
"""
4359
if not self: # empty graph
4360
return [],[]
4361
4362
start = self.vertex_iterator().next() # source
4363
4364
if len(self) == 1: # only one vertex
4365
return [[start]],[]
4366
4367
if not self.is_connected():
4368
raise NotImplementedError("Blocks and cut vertices is currently only implemented for connected graphs.")
4369
4370
# Each vertex is number with an integer from 1...|V(G)|, corresponding
4371
# to the order in which it is discovered during the DFS.
4372
number = {}
4373
num = 1
4374
4375
# Associates to each vertex v the smallest number of a vertex that can
4376
# be reached from v in the orientation of the graph that the algorithm
4377
# creates.
4378
low_point = {}
4379
4380
# Associates to each vertex an iterator over its neighbors
4381
neighbors = {}
4382
4383
blocks = []
4384
cut_vertices = set()
4385
4386
stack = [start]
4387
edge_stack = []
4388
start_already_seen = False
4389
4390
while stack:
4391
v = stack[-1]
4392
4393
# The first time we meet v
4394
if not v in number:
4395
# We number the vertices in the order they are reached during
4396
# DFS
4397
number[v] = num
4398
neighbors[v] = self.neighbor_iterator(v)
4399
low_point[v] = num
4400
num += 1
4401
4402
try:
4403
# We consider the next of its neighbors
4404
w = neighbors[v].next()
4405
4406
# If we never met w before, we remember the direction of edge
4407
# vw, and add w to the stack.
4408
if not w in number:
4409
edge_stack.append( (v,w) )
4410
stack.append(w)
4411
4412
# If w is an ancestor of v in the DFS tree, we remember the
4413
# direction of edge vw
4414
elif number[w]<number[v]:
4415
edge_stack.append( (v,w) )
4416
low_point[v] = min(low_point[v], number[w])
4417
4418
# We went through all of v's neighbors
4419
except StopIteration:
4420
# We trackback, so w takes the value of v and we pop the stack
4421
w = stack.pop()
4422
4423
# Test termination of the algorithm
4424
if not stack:
4425
break
4426
4427
v = stack[-1]
4428
4429
# Propagating the information : low_point[v] indicates the
4430
# smallest vertex (the vertex x with smallest number[x]) that
4431
# can be reached from v
4432
low_point[v] = min(low_point[v], low_point[w])
4433
4434
# The situation in which there is no path from w to an ancestor
4435
# of v : we have identified a new biconnected component
4436
if low_point[w] >= number[v]:
4437
new_block = set()
4438
nw = number[w]
4439
u1,u2 = edge_stack.pop()
4440
while number[u1] >= nw:
4441
new_block.add(u1)
4442
u1,u2 = edge_stack.pop()
4443
new_block.add(u1)
4444
blocks.append(sorted(list(new_block)))
4445
4446
# We update the set of cut vertices.
4447
#
4448
# If v is start, then we add it only if it belongs to
4449
# several blocks.
4450
if (not v is start) or start_already_seen:
4451
cut_vertices.add(v)
4452
else:
4453
start_already_seen = True
4454
4455
return blocks,sorted(list(cut_vertices))
4456
4457
def blocks_and_cuts_tree(self):
4458
"""
4459
Returns the blocks-and-cuts tree of ``self``.
4460
4461
This new graph has two different kinds of vertices, some representing
4462
the blocks (type B) and some other the cut vertices of the graph
4463
``self`` (type C).
4464
4465
There is an edge between a vertex `u` of type B and a vertex `v` of type
4466
C if the cut-vertex corresponding to `v` is in the block corresponding
4467
to `u`.
4468
4469
The resulting graph is a tree, with the additional characteristic
4470
property that the distance between two leaves is even.
4471
4472
When ``self`` is biconnected, the tree is reduced to a single node of
4473
type `B`.
4474
4475
.. SEEALSO:: :meth:`blocks_and_cut_vertices`
4476
4477
EXAMPLES:
4478
4479
sage: T = graphs.KrackhardtKiteGraph().blocks_and_cuts_tree(); T
4480
Graph on 5 vertices
4481
sage: T.is_isomorphic(graphs.PathGraph(5))
4482
True
4483
4484
The distance between two leaves is even::
4485
4486
sage: T = graphs.RandomTree(40).blocks_and_cuts_tree()
4487
sage: T.is_tree()
4488
True
4489
sage: leaves = [v for v in T if T.degree(v) == 1]
4490
sage: all(T.distance(u,v) % 2 == 0 for u in leaves for v in leaves)
4491
True
4492
4493
The tree of a biconnected graph has a single vertex, of type `B`::
4494
4495
sage: T = graphs.PetersenGraph().blocks_and_cuts_tree()
4496
sage: T.vertices()
4497
[('B', (0, 1, 2, 3, 4, 5, 6, 7, 8, 9))]
4498
4499
REFERENCES:
4500
4501
.. [HarPri] F. Harary and G. Prins. The block-cutpoint-tree of
4502
a graph. Publ. Math. Debrecen 13 1966 103-107.
4503
.. [Gallai] T. Gallai, Elementare Relationen bezueglich der
4504
Glieder und trennenden Punkte von Graphen, Magyar
4505
Tud. Akad. Mat. Kutato Int. Kozl. 9 (1964) 235-236
4506
"""
4507
from sage.graphs.graph import Graph
4508
B, C = self.blocks_and_cut_vertices()
4509
B = map(tuple, B)
4510
G = Graph()
4511
for bloc in B:
4512
G.add_vertex(('B', bloc))
4513
for c in bloc:
4514
if c in C:
4515
G.add_edge(('B', bloc), ('C', c))
4516
return G
4517
4518
def is_cut_edge(self, u, v=None, label=None):
4519
"""
4520
Returns True if the input edge is a cut-edge or a bridge.
4521
4522
A cut edge (or bridge) is an edge that when removed increases
4523
the number of connected components. This function works with
4524
simple graphs as well as graphs with loops and multiedges. In
4525
a digraph, a cut edge is an edge that when removed increases
4526
the number of (weakly) connected components.
4527
4528
INPUT: The following forms are accepted
4529
4530
- G.is_cut_edge( 1, 2 )
4531
4532
- G.is_cut_edge( (1, 2) )
4533
4534
- G.is_cut_edge( 1, 2, 'label' )
4535
4536
- G.is_cut_edge( (1, 2, 'label') )
4537
4538
OUTPUT:
4539
4540
- Returns True if (u,v) is a cut edge, False otherwise
4541
4542
EXAMPLES::
4543
4544
sage: G = graphs.CompleteGraph(4)
4545
sage: G.is_cut_edge(0,2)
4546
False
4547
4548
sage: G = graphs.CompleteGraph(4)
4549
sage: G.add_edge((0,5,'silly'))
4550
sage: G.is_cut_edge((0,5,'silly'))
4551
True
4552
4553
sage: G = Graph([[0,1],[0,2],[3,4],[4,5],[3,5]])
4554
sage: G.is_cut_edge((0,1))
4555
True
4556
4557
sage: G = Graph([[0,1],[0,2],[1,1]])
4558
sage: G.allow_loops(True)
4559
sage: G.is_cut_edge((1,1))
4560
False
4561
4562
sage: G = digraphs.Circuit(5)
4563
sage: G.is_cut_edge((0,1))
4564
False
4565
4566
sage: G = graphs.CompleteGraph(6)
4567
sage: G.is_cut_edge((0,7))
4568
Traceback (most recent call last):
4569
...
4570
ValueError: edge not in graph
4571
"""
4572
if label is None:
4573
if v is None:
4574
try:
4575
u, v, label = u
4576
except ValueError:
4577
u, v = u
4578
label = None
4579
4580
if not self.has_edge(u,v):
4581
raise ValueError('edge not in graph')
4582
4583
# If edge (u,v) is a pending edge, it is also a cut-edge
4584
if self.degree(u) == 1 or self.degree(v) == 1:
4585
return True
4586
elif self.allows_multiple_edges():
4587
# If we have two or more edges between u and v, it is not a cut-edge
4588
if len([(uu,vv) for uu,vv,ll in self.edges_incident(u) if uu == v or vv == v]) > 1:
4589
return False
4590
4591
self.delete_edge(u,v,label)
4592
if self.is_directed():
4593
# (u,v) is a cut-edge if u is not in the connected
4594
# component containing v of self-(u,v)
4595
sol = not u in self.connected_component_containing_vertex(v)
4596
else:
4597
# (u,v) is a cut-edge if there is no path from u to v in
4598
# self-(u,v)
4599
sol = not self.distance(u,v) < self.order()
4600
4601
self.add_edge(u,v,label)
4602
return sol
4603
4604
4605
def is_cut_vertex(self, u, weak=False):
4606
r"""
4607
Returns True if the input vertex is a cut-vertex.
4608
4609
A vertex is a cut-vertex if its removal from the (di)graph increases the
4610
number of (strongly) connected components. Isolated vertices or leafs
4611
are not cut-vertices. This function works with simple graphs as well as
4612
graphs with loops and multiple edges.
4613
4614
INPUT:
4615
4616
- ``u`` -- a vertex
4617
4618
- ``weak`` -- (default: ``False``) boolean set to `True` if the
4619
connectivity of directed graphs is to be taken in the weak sense, that
4620
is ignoring edges orientations.
4621
4622
OUTPUT:
4623
4624
Returns True if ``u`` is a cut-vertex, and False otherwise.
4625
4626
EXAMPLES:
4627
4628
Giving a LollipopGraph(4,2), that is a complete graph with 4 vertices with a pending edge::
4629
4630
sage: G = graphs.LollipopGraph(4,2)
4631
sage: G.is_cut_vertex(0)
4632
False
4633
sage: G.is_cut_vertex(3)
4634
True
4635
4636
Comparing the weak and strong connectivity of a digraph::
4637
4638
sage: D = digraphs.Circuit(6)
4639
sage: D.is_strongly_connected()
4640
True
4641
sage: D.is_cut_vertex(2)
4642
True
4643
sage: D.is_cut_vertex(2, weak=True)
4644
False
4645
4646
Giving a vertex that is not in the graph::
4647
4648
sage: G = graphs.CompleteGraph(6)
4649
sage: G.is_cut_vertex(7)
4650
Traceback (most recent call last):
4651
...
4652
ValueError: The input vertex is not in the vertex set.
4653
"""
4654
if not u in self:
4655
raise ValueError('The input vertex is not in the vertex set.')
4656
4657
# Initialization
4658
if not self.is_directed() or weak:
4659
# Weak connectivity
4660
4661
if self.degree(u) < 2:
4662
# An isolated or a leaf vertex is not a cut vertex
4663
return False
4664
4665
neighbors_func = [self.neighbor_iterator]
4666
start = self.neighbor_iterator(u).next()
4667
CC = set(self.vertex_iterator())
4668
4669
else:
4670
# Strong connectivity for digraphs
4671
4672
if self.out_degree(u) == 0 or self.in_degree(u) == 0:
4673
# A vertex without in or out neighbors is not a cut vertex
4674
return False
4675
4676
# We consider only the strongly connected component containing u
4677
CC = set(self.strongly_connected_component_containing_vertex(u))
4678
4679
# We perform two DFS starting from an out neighbor of u and avoiding
4680
# u. The first DFS follows the edges directions, and the second is
4681
# in the reverse order. If both allow to reach all neighbors of u,
4682
# then u is not a cut vertex
4683
neighbors_func = [self.neighbor_out_iterator, self.neighbor_in_iterator]
4684
start = self.neighbor_out_iterator(u).next()
4685
4686
CC.discard(u)
4687
CC.discard(start)
4688
for neighbors in neighbors_func:
4689
4690
# We perform a DFS starting from a neighbor of u and avoiding u
4691
queue = [start]
4692
seen = set(queue)
4693
targets = set(self.neighbor_iterator(u))&CC
4694
targets.discard(start)
4695
while queue and targets:
4696
v = queue.pop()
4697
for w in neighbors(v):
4698
if not w in seen and w in CC:
4699
seen.add(w)
4700
queue.append(w)
4701
targets.discard(w)
4702
4703
# If some neighbors cannot be reached, u is a cut vertex.
4704
if targets:
4705
return True
4706
4707
return False
4708
4709
4710
def steiner_tree(self,vertices, weighted = False, solver = None, verbose = 0):
4711
r"""
4712
Returns a tree of minimum weight connecting the given
4713
set of vertices.
4714
4715
Definition :
4716
4717
Computing a minimum spanning tree in a graph can be done in `n
4718
\log(n)` time (and in linear time if all weights are equal) where
4719
`n = V + E`. On the other hand, if one is given a large (possibly
4720
weighted) graph and a subset of its vertices, it is NP-Hard to
4721
find a tree of minimum weight connecting the given set of
4722
vertices, which is then called a Steiner Tree.
4723
4724
`Wikipedia article on Steiner Trees
4725
<http://en.wikipedia.org/wiki/Steiner_tree_problem>`_.
4726
4727
INPUT:
4728
4729
- ``vertices`` -- the vertices to be connected by the Steiner
4730
Tree.
4731
4732
- ``weighted`` (boolean) -- Whether to consider the graph as
4733
weighted, and use each edge's label as a weight, considering
4734
``None`` as a weight of `1`. If ``weighted=False`` (default)
4735
all edges are considered to have a weight of `1`.
4736
4737
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
4738
solver to be used. If set to ``None``, the default one is used. For
4739
more information on LP solvers and which default solver is used, see
4740
the method
4741
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
4742
of the class
4743
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
4744
4745
- ``verbose`` -- integer (default: ``0``). Sets the level of
4746
verbosity. Set to 0 by default, which means quiet.
4747
4748
4749
.. NOTE::
4750
4751
* This problem being defined on undirected graphs, the
4752
orientation is not considered if the current graph is
4753
actually a digraph.
4754
4755
* The graph is assumed not to have multiple edges.
4756
4757
ALGORITHM:
4758
4759
Solved through Linear Programming.
4760
4761
COMPLEXITY:
4762
4763
NP-Hard.
4764
4765
Note that this algorithm first checks whether the given
4766
set of vertices induces a connected graph, returning one of its
4767
spanning trees if ``weighted`` is set to ``False``, and thus
4768
answering very quickly in some cases
4769
4770
EXAMPLES:
4771
4772
The Steiner Tree of the first 5 vertices in a random graph is,
4773
of course, always a tree ::
4774
4775
sage: g = graphs.RandomGNP(30,.5)
4776
sage: st = g.steiner_tree(g.vertices()[:5])
4777
sage: st.is_tree()
4778
True
4779
4780
And all the 5 vertices are contained in this tree ::
4781
4782
sage: all([v in st for v in g.vertices()[:5] ])
4783
True
4784
4785
An exception is raised when the problem is impossible, i.e.
4786
if the given vertices are not all included in the same
4787
connected component ::
4788
4789
sage: g = 2 * graphs.PetersenGraph()
4790
sage: st = g.steiner_tree([5,15])
4791
Traceback (most recent call last):
4792
...
4793
ValueError: The given vertices do not all belong to the same connected component. This problem has no solution !
4794
"""
4795
self._scream_if_not_simple(allow_loops=True)
4796
4797
if self.is_directed():
4798
from sage.graphs.all import Graph
4799
g = Graph(self)
4800
else:
4801
g = self
4802
4803
if g.has_multiple_edges():
4804
raise ValueError("The graph is expected not to have multiple edges.")
4805
4806
# Can the problem be solved ? Are all the vertices in the same
4807
# connected component ?
4808
cc = g.connected_component_containing_vertex(vertices[0])
4809
if not all([v in cc for v in vertices]):
4810
raise ValueError("The given vertices do not all belong to the same connected component. This problem has no solution !")
4811
4812
# Can it be solved using the min spanning tree algorithm ?
4813
if not weighted:
4814
gg = g.subgraph(vertices)
4815
if gg.is_connected():
4816
st = g.subgraph(edges = gg.min_spanning_tree())
4817
st.delete_vertices([v for v in g if st.degree(v) == 0])
4818
return st
4819
4820
# Then, LP formulation
4821
from sage.numerical.mip import MixedIntegerLinearProgram
4822
p = MixedIntegerLinearProgram(maximization = False, solver = solver)
4823
4824
# Reorder an edge
4825
R = lambda (x,y) : (x,y) if x<y else (y,x)
4826
4827
# edges used in the Steiner Tree
4828
edges = p.new_variable()
4829
4830
# relaxed edges to test for acyclicity
4831
r_edges = p.new_variable()
4832
4833
# Whether a vertex is in the Steiner Tree
4834
vertex = p.new_variable()
4835
for v in g:
4836
for e in g.edges_incident(v, labels=False):
4837
p.add_constraint(vertex[v] - edges[R(e)], min = 0)
4838
4839
# We must have the given vertices in our tree
4840
for v in vertices:
4841
p.add_constraint(p.sum([edges[R(e)] for e in g.edges_incident(v,labels=False)]), min=1)
4842
4843
# The number of edges is equal to the number of vertices in our tree minus 1
4844
p.add_constraint(p.sum([vertex[v] for v in g]) - p.sum([edges[R(e)] for e in g.edges(labels=None)]), max = 1, min = 1)
4845
4846
# There are no cycles in our graph
4847
4848
for u,v in g.edges(labels = False):
4849
p.add_constraint( r_edges[(u,v)]+ r_edges[(v,u)] - edges[R((u,v))] , min = 0 )
4850
4851
eps = 1/(5*Integer(g.order()))
4852
for v in g:
4853
p.add_constraint(p.sum([r_edges[(u,v)] for u in g.neighbors(v)]), max = 1-eps)
4854
4855
4856
# Objective
4857
if weighted:
4858
w = lambda (x,y) : g.edge_label(x,y) if g.edge_label(x,y) is not None else 1
4859
else:
4860
w = lambda (x,y) : 1
4861
4862
p.set_objective(p.sum([w(e)*edges[R(e)] for e in g.edges(labels = False)]))
4863
4864
p.set_binary(edges)
4865
p.solve(log = verbose)
4866
4867
edges = p.get_values(edges)
4868
4869
st = g.subgraph(edges=[e for e in g.edges(labels = False) if edges[R(e)] == 1])
4870
st.delete_vertices([v for v in g if st.degree(v) == 0])
4871
return st
4872
4873
def edge_disjoint_spanning_trees(self,k, root=None, solver = None, verbose = 0):
4874
r"""
4875
Returns the desired number of edge-disjoint spanning
4876
trees/arborescences.
4877
4878
INPUT:
4879
4880
- ``k`` (integer) -- the required number of edge-disjoint
4881
spanning trees/arborescences
4882
4883
- ``root`` (vertex) -- root of the disjoint arborescences
4884
when the graph is directed.
4885
If set to ``None``, the first vertex in the graph is picked.
4886
4887
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
4888
solver to be used. If set to ``None``, the default one is used. For
4889
more information on LP solvers and which default solver is used, see
4890
the method
4891
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
4892
of the class
4893
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
4894
4895
- ``verbose`` -- integer (default: ``0``). Sets the level of
4896
verbosity. Set to 0 by default, which means quiet.
4897
4898
ALGORITHM:
4899
4900
Mixed Integer Linear Program. The formulation can be found
4901
in [LPForm]_.
4902
4903
There are at least two possible rewritings of this method
4904
which do not use Linear Programming:
4905
4906
* The algorithm presented in the paper entitled "A short
4907
proof of the tree-packing theorem", by Thomas Kaiser
4908
[KaisPacking]_.
4909
4910
* The implementation of a Matroid class and of the Matroid
4911
Union Theorem (see section 42.3 of [SchrijverCombOpt]_),
4912
applied to the cycle Matroid (see chapter 51 of
4913
[SchrijverCombOpt]_).
4914
4915
EXAMPLES:
4916
4917
The Petersen Graph does have a spanning tree (it is connected)::
4918
4919
sage: g = graphs.PetersenGraph()
4920
sage: [T] = g.edge_disjoint_spanning_trees(1)
4921
sage: T.is_tree()
4922
True
4923
4924
Though, it does not have 2 edge-disjoint trees (as it has less
4925
than `2(|V|-1)` edges)::
4926
4927
sage: g.edge_disjoint_spanning_trees(2)
4928
Traceback (most recent call last):
4929
...
4930
ValueError: This graph does not contain the required number of trees/arborescences !
4931
4932
By Edmond's theorem, a graph which is `k`-connected always has `k` edge-disjoint
4933
arborescences, regardless of the root we pick::
4934
4935
sage: g = digraphs.RandomDirectedGNP(28,.3) # reduced from 30 to 28, cf. #9584
4936
sage: k = Integer(g.edge_connectivity())
4937
sage: arborescences = g.edge_disjoint_spanning_trees(k) # long time (up to 15s on sage.math, 2011)
4938
sage: all([a.is_directed_acyclic() for a in arborescences]) # long time
4939
True
4940
sage: all([a.is_connected() for a in arborescences]) # long time
4941
True
4942
4943
In the undirected case, we can only ensure half of it::
4944
4945
sage: g = graphs.RandomGNP(30,.3)
4946
sage: k = floor(Integer(g.edge_connectivity())/2)
4947
sage: trees = g.edge_disjoint_spanning_trees(k)
4948
sage: all([t.is_tree() for t in trees])
4949
True
4950
4951
REFERENCES:
4952
4953
.. [LPForm] Nathann Cohen,
4954
Several Graph problems and their Linear Program formulations,
4955
http://hal.archives-ouvertes.fr/inria-00504914/en
4956
4957
.. [KaisPacking] Thomas Kaiser
4958
A short proof of the tree-packing theorem
4959
:arxiv:`0911.2809`
4960
4961
.. [SchrijverCombOpt] Alexander Schrijver
4962
Combinatorial optimization: polyhedra and efficiency
4963
2003
4964
"""
4965
4966
from sage.numerical.mip import MixedIntegerLinearProgram, MIPSolverException
4967
4968
p = MixedIntegerLinearProgram(solver = solver)
4969
p.set_objective(None)
4970
4971
# The colors we can use
4972
colors = range(0,k)
4973
4974
# edges[j][e] is equal to one if and only if edge e belongs to color j
4975
edges = p.new_variable(dim=2)
4976
4977
if root is None:
4978
root = self.vertex_iterator().next()
4979
4980
# r_edges is a relaxed variable grater than edges. It is used to
4981
# check the presence of cycles
4982
r_edges = p.new_variable(dim=2)
4983
4984
epsilon = 1/(3*(Integer(self.order())))
4985
4986
if self.is_directed():
4987
# Does nothing ot an edge.. Useful when out of "if self.directed"
4988
S = lambda (x,y) : (x,y)
4989
4990
# An edge belongs to at most arborescence
4991
for e in self.edges(labels=False):
4992
p.add_constraint(p.sum([edges[j][e] for j in colors]), max=1)
4993
4994
4995
for j in colors:
4996
# each color class has self.order()-1 edges
4997
p.add_constraint(p.sum([edges[j][e] for e in self.edges(labels=None)]), min=self.order()-1)
4998
4999
# Each vertex different from the root has indegree equals to one
5000
for v in self.vertices():
5001
if v is not root:
5002
p.add_constraint(p.sum([edges[j][e] for e in self.incoming_edges(v, labels=None)]), max=1, min=1)
5003
else:
5004
p.add_constraint(p.sum([edges[j][e] for e in self.incoming_edges(v, labels=None)]), max=0, min=0)
5005
5006
# r_edges is larger than edges
5007
for u,v in self.edges(labels=None):
5008
if self.has_edge(v,u):
5009
if v<u:
5010
p.add_constraint(r_edges[j][(u,v)] + r_edges[j][(v, u)] - edges[j][(u,v)] - edges[j][(v,u)], min=0)
5011
else:
5012
p.add_constraint(r_edges[j][(u,v)] + r_edges[j][(v, u)] - edges[j][(u,v)], min=0)
5013
5014
from sage.graphs.digraph import DiGraph
5015
D = DiGraph()
5016
D.add_vertices(self.vertices())
5017
D.set_pos(self.get_pos())
5018
classes = [D.copy() for j in colors]
5019
5020
else:
5021
5022
# Sort an edge
5023
S = lambda (x,y) : (x,y) if x<y else (y,x)
5024
5025
# An edge belongs to at most one arborescence
5026
for e in self.edges(labels=False):
5027
p.add_constraint(p.sum([edges[j][S(e)] for j in colors]), max=1)
5028
5029
5030
for j in colors:
5031
# each color class has self.order()-1 edges
5032
p.add_constraint(p.sum([edges[j][S(e)] for e in self.edges(labels=None)]), min=self.order()-1)
5033
5034
# Each vertex is in the tree
5035
for v in self.vertices():
5036
p.add_constraint(p.sum([edges[j][S(e)] for e in self.edges_incident(v, labels=None)]), min=1)
5037
5038
# r_edges is larger than edges
5039
for u,v in self.edges(labels=None):
5040
p.add_constraint(r_edges[j][(u,v)] + r_edges[j][(v, u)] - edges[j][S((u,v))], min=0)
5041
5042
from sage.graphs.graph import Graph
5043
D = Graph()
5044
D.add_vertices(self.vertices())
5045
D.set_pos(self.get_pos())
5046
classes = [D.copy() for j in colors]
5047
5048
# no cycles
5049
for j in colors:
5050
for v in self:
5051
p.add_constraint(p.sum(r_edges[j][(u,v)] for u in self.neighbors(v)), max=1-epsilon)
5052
5053
p.set_binary(edges)
5054
5055
try:
5056
p.solve(log = verbose)
5057
5058
except MIPSolverException:
5059
raise ValueError("This graph does not contain the required number of trees/arborescences !")
5060
5061
edges = p.get_values(edges)
5062
5063
for j,g in enumerate(classes):
5064
for e in self.edges(labels=False):
5065
if edges[j][S(e)] == 1:
5066
g.add_edge(e)
5067
if len(list(g.breadth_first_search(root))) != self.order():
5068
raise RuntimeError("The computation seems to have gone wrong somewhere..."+
5069
"This is probably because of the value of epsilon, but"+
5070
" in any case please report this bug, with the graph "+
5071
"that produced it ! ;-)")
5072
5073
return classes
5074
5075
def edge_cut(self, s, t, value_only=True, use_edge_labels=False, vertices=False, method="FF", solver=None, verbose=0):
5076
r"""
5077
Returns a minimum edge cut between vertices `s` and `t`
5078
represented by a list of edges.
5079
5080
A minimum edge cut between two vertices `s` and `t` of self
5081
is a set `A` of edges of minimum weight such that the graph
5082
obtained by removing `A` from self is disconnected. For more
5083
information, see the
5084
`Wikipedia article on cuts
5085
<http://en.wikipedia.org/wiki/Cut_%28graph_theory%29>`_.
5086
5087
INPUT:
5088
5089
- ``s`` -- source vertex
5090
5091
- ``t`` -- sink vertex
5092
5093
- ``value_only`` -- boolean (default: ``True``). When set to
5094
``True``, only the weight of a minimum cut is returned.
5095
Otherwise, a list of edges of a minimum cut is also returned.
5096
5097
- ``use_edge_labels`` -- boolean (default: ``False``). When set to
5098
``True``, computes a weighted minimum cut where each edge has
5099
a weight defined by its label (if an edge has no label, `1`
5100
is assumed). Otherwise, each edge has weight `1`.
5101
5102
- ``vertices`` -- boolean (default: ``False``). When set to
5103
``True``, returns a list of edges in the edge cut and the
5104
two sets of vertices that are disconnected by the cut.
5105
5106
Note: ``vertices=True`` implies ``value_only=False``.
5107
5108
- ``method`` -- There are currently two different
5109
implementations of this method :
5110
5111
* If ``method = "FF"`` (default), a Python
5112
implementation of the Ford-Fulkerson algorithm is
5113
used.
5114
5115
* If ``method = "LP"``, the flow problem is solved using
5116
Linear Programming.
5117
5118
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
5119
solver to be used. If set to ``None``, the default one is used. For
5120
more information on LP solvers and which default solver is used, see
5121
the method
5122
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
5123
of the class
5124
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
5125
5126
- ``verbose`` -- integer (default: ``0``). Sets the level of
5127
verbosity. Set to 0 by default, which means quiet.
5128
5129
.. NOTE::
5130
5131
The use of Linear Programming for non-integer problems may
5132
possibly mean the presence of a (slight) numerical noise.
5133
5134
OUTPUT:
5135
5136
Real number or tuple, depending on the given arguments
5137
(examples are given below).
5138
5139
EXAMPLES:
5140
5141
A basic application in the Pappus graph::
5142
5143
sage: g = graphs.PappusGraph()
5144
sage: g.edge_cut(1, 2, value_only=True)
5145
3
5146
5147
Or on Petersen's graph, with the corresponding bipartition of
5148
the vertex set::
5149
5150
sage: g = graphs.PetersenGraph()
5151
sage: g.edge_cut(0, 3, vertices=True)
5152
[3, [(0, 1, None), (0, 4, None), (0, 5, None)], [[0], [1, 2, 3, 4, 5, 6, 7, 8, 9]]]
5153
5154
If the graph is a path with randomly weighted edges::
5155
5156
sage: g = graphs.PathGraph(15)
5157
sage: for (u,v) in g.edge_iterator(labels=None):
5158
... g.set_edge_label(u,v,random())
5159
5160
The edge cut between the two ends is the edge of minimum weight::
5161
5162
sage: minimum = min([l for u,v,l in g.edge_iterator()])
5163
sage: minimum == g.edge_cut(0, 14, use_edge_labels=True)
5164
True
5165
sage: [value,[e]] = g.edge_cut(0, 14, use_edge_labels=True, value_only=False)
5166
sage: g.edge_label(e[0],e[1]) == minimum
5167
True
5168
5169
The two sides of the edge cut are obviously shorter paths::
5170
5171
sage: value,edges,[set1,set2] = g.edge_cut(0, 14, use_edge_labels=True, vertices=True)
5172
sage: g.subgraph(set1).is_isomorphic(graphs.PathGraph(len(set1)))
5173
True
5174
sage: g.subgraph(set2).is_isomorphic(graphs.PathGraph(len(set2)))
5175
True
5176
sage: len(set1) + len(set2) == g.order()
5177
True
5178
5179
TESTS:
5180
5181
If method is set to an exotic value::
5182
5183
sage: g = graphs.PetersenGraph()
5184
sage: g.edge_cut(0,1, method="Divination")
5185
Traceback (most recent call last):
5186
...
5187
ValueError: The method argument has to be equal to either "FF" or "LP"
5188
5189
Same result for both methods::
5190
5191
sage: g = graphs.RandomGNP(20,.3)
5192
sage: for u,v in g.edges(labels=False):
5193
... g.set_edge_label(u,v,round(random(),5))
5194
sage: g.edge_cut(0,1, method="FF") == g.edge_cut(0,1,method="LP")
5195
True
5196
5197
Rounded return value when using the LP method::
5198
5199
sage: g = graphs.PappusGraph()
5200
sage: g.edge_cut(1, 2, value_only=True, method = "LP")
5201
3
5202
"""
5203
self._scream_if_not_simple(allow_loops=True)
5204
if vertices:
5205
value_only = False
5206
5207
if use_edge_labels:
5208
weight = lambda x: x if (x!={} and x is not None) else 1
5209
else:
5210
weight = lambda x: 1
5211
5212
if method == "FF":
5213
if value_only:
5214
return self.flow(s,t,value_only=value_only,use_edge_labels=use_edge_labels, method=method)
5215
5216
flow_value, flow_graph = self.flow(s,t,value_only=value_only,use_edge_labels=use_edge_labels, method=method)
5217
g = self.copy()
5218
for u,v,l in flow_graph.edge_iterator():
5219
if (not use_edge_labels or
5220
(weight(g.edge_label(u,v)) == weight(l))):
5221
g.delete_edge(u,v)
5222
5223
return_value = [flow_value]
5224
5225
reachable_from_s = list(g.breadth_first_search(s))
5226
5227
return_value.append(self.edge_boundary(reachable_from_s))
5228
5229
if vertices:
5230
return_value.append([reachable_from_s,list(set(self.vertices())-set(reachable_from_s))])
5231
5232
return return_value
5233
5234
if method != "LP":
5235
raise ValueError("The method argument has to be equal to either \"FF\" or \"LP\"")
5236
5237
from sage.numerical.mip import MixedIntegerLinearProgram
5238
g = self
5239
p = MixedIntegerLinearProgram(maximization=False, solver=solver)
5240
b = p.new_variable(dim=2)
5241
v = p.new_variable()
5242
5243
# Some vertices belong to part 1, others to part 0
5244
p.add_constraint(v[s], min=0, max=0)
5245
p.add_constraint(v[t], min=1, max=1)
5246
5247
if g.is_directed():
5248
5249
# we minimize the number of edges
5250
p.set_objective(p.sum([weight(w) * b[x][y] for (x,y,w) in g.edges()]))
5251
5252
# Adjacent vertices can belong to different parts only if the
5253
# edge that connects them is part of the cut
5254
for (x,y) in g.edges(labels=None):
5255
p.add_constraint(v[x] + b[x][y] - v[y], min=0)
5256
5257
else:
5258
# we minimize the number of edges
5259
p.set_objective(p.sum([weight(w) * b[min(x,y)][max(x,y)] for (x,y,w) in g.edges()]))
5260
# Adjacent vertices can belong to different parts only if the
5261
# edge that connects them is part of the cut
5262
for (x,y) in g.edges(labels=None):
5263
p.add_constraint(v[x] + b[min(x,y)][max(x,y)] - v[y], min=0)
5264
p.add_constraint(v[y] + b[min(x,y)][max(x,y)] - v[x], min=0)
5265
5266
p.set_binary(v)
5267
p.set_binary(b)
5268
5269
if value_only:
5270
if use_edge_labels:
5271
return p.solve(objective_only=True, log=verbose)
5272
else:
5273
return Integer(round(p.solve(objective_only=True, log=verbose)))
5274
else:
5275
obj = p.solve(log=verbose)
5276
5277
if use_edge_labels is False:
5278
obj = Integer(round(obj))
5279
5280
b = p.get_values(b)
5281
answer = [obj]
5282
if g.is_directed():
5283
answer.append([(x,y) for (x,y) in g.edges(labels=None) if b[x][y] == 1])
5284
else:
5285
answer.append([(x,y) for (x,y) in g.edges(labels=None) if b[min(x,y)][max(x,y)] == 1])
5286
5287
if vertices:
5288
v = p.get_values(v)
5289
l0 = []
5290
l1 = []
5291
for x in g.vertex_iterator():
5292
if v.has_key(x) and v[x] == 1:
5293
l1.append(x)
5294
else:
5295
l0.append(x)
5296
answer.append([l0, l1])
5297
return tuple(answer)
5298
5299
def vertex_cut(self, s, t, value_only=True, vertices=False, solver=None, verbose=0):
5300
r"""
5301
Returns a minimum vertex cut between non-adjacent vertices `s` and `t`
5302
represented by a list of vertices.
5303
5304
A vertex cut between two non-adjacent vertices is a set `U`
5305
of vertices of self such that the graph obtained by removing
5306
`U` from self is disconnected. For more information, see the
5307
`Wikipedia article on cuts
5308
<http://en.wikipedia.org/wiki/Cut_%28graph_theory%29>`_.
5309
5310
INPUT:
5311
5312
- ``value_only`` -- boolean (default: ``True``). When set to
5313
``True``, only the size of the minimum cut is returned.
5314
5315
- ``vertices`` -- boolean (default: ``False``). When set to
5316
``True``, also returns the two sets of vertices that
5317
are disconnected by the cut. Implies ``value_only``
5318
set to False.
5319
5320
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
5321
solver to be used. If set to ``None``, the default one is used. For
5322
more information on LP solvers and which default solver is used, see
5323
the method
5324
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
5325
of the class
5326
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
5327
5328
- ``verbose`` -- integer (default: ``0``). Sets the level of
5329
verbosity. Set to 0 by default, which means quiet.
5330
5331
OUTPUT:
5332
5333
Real number or tuple, depending on the given arguments
5334
(examples are given below).
5335
5336
EXAMPLE:
5337
5338
A basic application in the Pappus graph::
5339
5340
sage: g = graphs.PappusGraph()
5341
sage: g.vertex_cut(1, 16, value_only=True)
5342
3
5343
5344
In the bipartite complete graph `K_{2,8}`, a cut between the two
5345
vertices in the size `2` part consists of the other `8` vertices::
5346
5347
sage: g = graphs.CompleteBipartiteGraph(2, 8)
5348
sage: [value, vertices] = g.vertex_cut(0, 1, value_only=False)
5349
sage: print value
5350
8
5351
sage: vertices == range(2,10)
5352
True
5353
5354
Clearly, in this case the two sides of the cut are singletons ::
5355
5356
sage: [value, vertices, [set1, set2]] = g.vertex_cut(0,1, vertices=True)
5357
sage: len(set1) == 1
5358
True
5359
sage: len(set2) == 1
5360
True
5361
"""
5362
from sage.numerical.mip import MixedIntegerLinearProgram
5363
g = self
5364
if g.has_edge(s,t):
5365
raise ValueError("There can be no vertex cut between adjacent vertices !")
5366
if vertices:
5367
value_only = False
5368
5369
p = MixedIntegerLinearProgram(maximization=False, solver=solver)
5370
b = p.new_variable()
5371
v = p.new_variable()
5372
5373
# Some vertices belong to part 1, some others to part 0
5374
p.add_constraint(v[s], min=0, max=0)
5375
p.add_constraint(v[t], min=1, max=1)
5376
5377
# b indicates whether the vertices belong to the cut
5378
p.add_constraint(b[s], min=0, max=0)
5379
p.add_constraint(b[t], min=0, max=0)
5380
5381
if g.is_directed():
5382
5383
p.set_objective(p.sum([b[x] for x in g.vertices()]))
5384
5385
# adjacent vertices belong to the same part except if one of them
5386
# belongs to the cut
5387
for (x,y) in g.edges(labels=None):
5388
p.add_constraint(v[x] + b[y] - v[y], min=0)
5389
5390
else:
5391
p.set_objective(p.sum([b[x] for x in g.vertices()]))
5392
# adjacent vertices belong to the same part except if one of them
5393
# belongs to the cut
5394
for (x,y) in g.edges(labels=None):
5395
p.add_constraint(v[x] + b[y] - v[y],min=0)
5396
p.add_constraint(v[y] + b[x] - v[x],min=0)
5397
5398
p.set_binary(b)
5399
p.set_binary(v)
5400
5401
if value_only:
5402
return Integer(round(p.solve(objective_only=True, log=verbose)))
5403
else:
5404
obj = Integer(round(p.solve(log=verbose)))
5405
b = p.get_values(b)
5406
answer = [obj,[x for x in g if b[x] == 1]]
5407
if vertices:
5408
v = p.get_values(v)
5409
l0 = []
5410
l1 = []
5411
for x in g.vertex_iterator():
5412
# if the vertex is not in the cut
5413
if not (b.has_key(x) and b[x] == 1):
5414
if (v.has_key(x) and v[x] == 1):
5415
l1.append(x)
5416
else:
5417
l0.append(x)
5418
answer.append([l0, l1])
5419
return tuple(answer)
5420
5421
5422
def multiway_cut(self, vertices, value_only = False, use_edge_labels = False, solver = None, verbose = 0):
5423
r"""
5424
Returns a minimum edge multiway cut corresponding to the
5425
given set of vertices
5426
( cf. http://www.d.kth.se/~viggo/wwwcompendium/node92.html )
5427
represented by a list of edges.
5428
5429
A multiway cut for a vertex set `S` in a graph or a digraph
5430
`G` is a set `C` of edges such that any two vertices `u,v`
5431
in `S` are disconnected when removing the edges from `C` from `G`.
5432
5433
Such a cut is said to be minimum when its cardinality
5434
(or weight) is minimum.
5435
5436
INPUT:
5437
5438
- ``vertices`` (iterable)-- the set of vertices
5439
5440
- ``value_only`` (boolean)
5441
5442
- When set to ``True``, only the value of a minimum
5443
multiway cut is returned.
5444
5445
- When set to ``False`` (default), the list of edges
5446
is returned
5447
5448
- ``use_edge_labels`` (boolean)
5449
- When set to ``True``, computes a weighted minimum cut
5450
where each edge has a weight defined by its label. ( if
5451
an edge has no label, `1` is assumed )
5452
5453
- when set to ``False`` (default), each edge has weight `1`.
5454
5455
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
5456
solver to be used. If set to ``None``, the default one is used. For
5457
more information on LP solvers and which default solver is used, see
5458
the method
5459
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
5460
of the class
5461
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
5462
5463
- ``verbose`` -- integer (default: ``0``). Sets the level of
5464
verbosity. Set to 0 by default, which means quiet.
5465
5466
EXAMPLES:
5467
5468
Of course, a multiway cut between two vertices correspond
5469
to a minimum edge cut ::
5470
5471
sage: g = graphs.PetersenGraph()
5472
sage: g.edge_cut(0,3) == g.multiway_cut([0,3], value_only = True)
5473
True
5474
5475
As Petersen's graph is `3`-regular, a minimum multiway cut
5476
between three vertices contains at most `2\times 3` edges
5477
(which could correspond to the neighborhood of 2
5478
vertices)::
5479
5480
sage: g.multiway_cut([0,3,9], value_only = True) == 2*3
5481
True
5482
5483
In this case, though, the vertices are an independent set.
5484
If we pick instead vertices `0,9,` and `7`, we can save `4`
5485
edges in the multiway cut ::
5486
5487
sage: g.multiway_cut([0,7,9], value_only = True) == 2*3 - 1
5488
True
5489
5490
This example, though, does not work in the directed case anymore,
5491
as it is not possible in Petersen's graph to mutualise edges ::
5492
5493
sage: g = DiGraph(g)
5494
sage: g.multiway_cut([0,7,9], value_only = True) == 3*3
5495
True
5496
5497
Of course, a multiway cut between the whole vertex set
5498
contains all the edges of the graph::
5499
5500
sage: C = g.multiway_cut(g.vertices())
5501
sage: set(C) == set(g.edges())
5502
True
5503
"""
5504
self._scream_if_not_simple(allow_loops=True)
5505
from sage.numerical.mip import MixedIntegerLinearProgram
5506
from itertools import combinations, chain
5507
5508
p = MixedIntegerLinearProgram(maximization = False, solver= solver)
5509
5510
# height[c][v] represents the height of vertex v for commodity c
5511
height = p.new_variable(dim = 2)
5512
5513
# cut[e] represents whether e is in the cut
5514
cut = p.new_variable(binary = True)
5515
5516
# Reorder
5517
R = lambda x,y : (x,y) if x<y else (y,x)
5518
5519
# Weight function
5520
if use_edge_labels:
5521
w = lambda l : l if l is not None else 1
5522
else:
5523
w = lambda l : 1
5524
5525
if self.is_directed():
5526
5527
p.set_objective( p.sum([ w(l) * cut[u,v] for u,v,l in self.edge_iterator() ]) )
5528
5529
for s,t in chain( combinations(vertices,2), map(lambda (x,y) : (y,x), combinations(vertices,2))) :
5530
# For each commodity, the source is at height 0
5531
# and the destination is at height 1
5532
p.add_constraint( height[(s,t)][s], min = 0, max = 0)
5533
p.add_constraint( height[(s,t)][t], min = 1, max = 1)
5534
5535
# given a commodity (s,t), the height of two adjacent vertices u,v
5536
# can differ of at most the value of the edge between them
5537
for u,v in self.edges(labels = False):
5538
p.add_constraint( height[(s,t)][u] - height[(s,t)][v] - cut[u,v], max = 0)
5539
5540
else:
5541
5542
p.set_objective( p.sum([ w(l) * cut[R(u,v)] for u,v,l in self.edge_iterator() ]) )
5543
5544
for s,t in combinations(vertices,2):
5545
# For each commodity, the source is at height 0
5546
# and the destination is at height 1
5547
p.add_constraint( height[(s,t)][s], min = 0, max = 0)
5548
p.add_constraint( height[(s,t)][t], min = 1, max = 1)
5549
5550
# given a commodity (s,t), the height of two adjacent vertices u,v
5551
# can differ of at most the value of the edge between them
5552
for u,v in self.edges(labels = False):
5553
p.add_constraint( height[(s,t)][u] - height[(s,t)][v] - cut[R(u,v)], max = 0)
5554
p.add_constraint( height[(s,t)][v] - height[(s,t)][u] - cut[R(u,v)], max = 0)
5555
5556
if value_only:
5557
if use_edge_labels:
5558
return p.solve(objective_only = True, log = verbose)
5559
else:
5560
return Integer(round(p.solve(objective_only = True, log = verbose)))
5561
5562
p.solve(log = verbose)
5563
5564
cut = p.get_values(cut)
5565
5566
if self.is_directed():
5567
return filter(lambda (u,v,l) : cut[u,v] == 1, self.edge_iterator())
5568
5569
else:
5570
return filter(lambda (u,v,l) : cut[R(u,v)] ==1, self.edge_iterator())
5571
5572
5573
def max_cut(self, value_only=True, use_edge_labels=False, vertices=False, solver=None, verbose=0):
5574
r"""
5575
Returns a maximum edge cut of the graph. For more information, see the
5576
`Wikipedia article on cuts
5577
<http://en.wikipedia.org/wiki/Cut_%28graph_theory%29>`_.
5578
5579
INPUT:
5580
5581
- ``value_only`` -- boolean (default: ``True``)
5582
5583
- When set to ``True`` (default), only the value is returned.
5584
5585
- When set to ``False``, both the value and a maximum edge cut
5586
are returned.
5587
5588
- ``use_edge_labels`` -- boolean (default: ``False``)
5589
5590
- When set to ``True``, computes a maximum weighted cut
5591
where each edge has a weight defined by its label. (If
5592
an edge has no label, `1` is assumed.)
5593
5594
- When set to ``False``, each edge has weight `1`.
5595
5596
- ``vertices`` -- boolean (default: ``False``)
5597
5598
- When set to ``True``, also returns the two sets of
5599
vertices that are disconnected by the cut. This implies
5600
``value_only=False``.
5601
5602
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
5603
solver to be used. If set to ``None``, the default one is used. For
5604
more information on LP solvers and which default solver is used, see
5605
the method
5606
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
5607
of the class
5608
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
5609
5610
- ``verbose`` -- integer (default: ``0``). Sets the level of
5611
verbosity. Set to 0 by default, which means quiet.
5612
5613
EXAMPLE:
5614
5615
Quite obviously, the max cut of a bipartite graph
5616
is the number of edges, and the two sets of vertices
5617
are the the two sides ::
5618
5619
sage: g = graphs.CompleteBipartiteGraph(5,6)
5620
sage: [ value, edges, [ setA, setB ]] = g.max_cut(vertices=True)
5621
sage: value == 5*6
5622
True
5623
sage: bsetA, bsetB = map(list,g.bipartite_sets())
5624
sage: (bsetA == setA and bsetB == setB ) or ((bsetA == setB and bsetB == setA ))
5625
True
5626
5627
The max cut of a Petersen graph::
5628
5629
sage: g=graphs.PetersenGraph()
5630
sage: g.max_cut()
5631
12
5632
5633
"""
5634
self._scream_if_not_simple(allow_loops=True)
5635
g=self
5636
5637
if vertices:
5638
value_only=False
5639
5640
if use_edge_labels:
5641
from sage.rings.real_mpfr import RR
5642
weight = lambda x: x if x in RR else 1
5643
else:
5644
weight = lambda x: 1
5645
5646
if g.is_directed():
5647
reorder_edge = lambda x,y : (x,y)
5648
else:
5649
reorder_edge = lambda x,y : (x,y) if x<= y else (y,x)
5650
5651
from sage.numerical.mip import MixedIntegerLinearProgram
5652
5653
p = MixedIntegerLinearProgram(maximization=True, solver=solver)
5654
5655
in_set = p.new_variable(dim=2)
5656
in_cut = p.new_variable(dim=1)
5657
5658
# A vertex has to be in some set
5659
for v in g:
5660
p.add_constraint(in_set[0][v]+in_set[1][v],max=1,min=1)
5661
5662
# There is no empty set
5663
p.add_constraint(p.sum([in_set[1][v] for v in g]),min=1)
5664
p.add_constraint(p.sum([in_set[0][v] for v in g]),min=1)
5665
5666
if g.is_directed():
5667
# There is no edge from set 0 to set 1 which
5668
# is not in the cut
5669
# Besides, an edge can only be in the cut if its vertices
5670
# belong to different sets
5671
for (u,v) in g.edge_iterator(labels=None):
5672
p.add_constraint(in_set[0][u] + in_set[1][v] - in_cut[(u,v)], max = 1)
5673
p.add_constraint(in_set[0][u] + in_set[0][v] + in_cut[(u,v)], max = 2)
5674
p.add_constraint(in_set[1][u] + in_set[1][v] + in_cut[(u,v)], max = 2)
5675
else:
5676
5677
# Two adjacent vertices are in different sets if and only if
5678
# the edge between them is in the cut
5679
5680
for (u,v) in g.edge_iterator(labels=None):
5681
p.add_constraint(in_set[0][u]+in_set[1][v]-in_cut[reorder_edge(u,v)],max=1)
5682
p.add_constraint(in_set[1][u]+in_set[0][v]-in_cut[reorder_edge(u,v)],max=1)
5683
p.add_constraint(in_set[0][u] + in_set[0][v] + in_cut[reorder_edge(u,v)], max = 2)
5684
p.add_constraint(in_set[1][u] + in_set[1][v] + in_cut[reorder_edge(u,v)], max = 2)
5685
5686
5687
p.set_binary(in_set)
5688
p.set_binary(in_cut)
5689
5690
p.set_objective(p.sum([weight(l ) * in_cut[reorder_edge(u,v)] for (u,v,l ) in g.edge_iterator()]))
5691
5692
if value_only:
5693
obj = p.solve(objective_only=True, log=verbose)
5694
return obj if use_edge_labels else Integer(round(obj))
5695
else:
5696
obj = p.solve(log=verbose)
5697
5698
if use_edge_labels:
5699
obj = Integer(round(obj))
5700
5701
val = [obj]
5702
5703
in_cut = p.get_values(in_cut)
5704
in_set = p.get_values(in_set)
5705
5706
edges = []
5707
for (u,v,l) in g.edge_iterator():
5708
if in_cut[reorder_edge(u,v)] == 1:
5709
edges.append((u,v,l))
5710
5711
val.append(edges)
5712
5713
if vertices:
5714
a = []
5715
b = []
5716
for v in g:
5717
if in_set[0][v] == 1:
5718
a.append(v)
5719
else:
5720
b.append(v)
5721
val.append([a,b])
5722
5723
return val
5724
5725
def longest_path(self, s=None, t=None, use_edge_labels=False, algorithm="MILP", solver=None, verbose=0):
5726
r"""
5727
Returns a longest path of ``self``.
5728
5729
INPUT:
5730
5731
- ``s`` (vertex) -- forces the source of the path (the method then
5732
returns the longest path starting at ``s``). The argument is set to
5733
``None`` by default, which means that no constraint is set upon the
5734
first vertex in the path.
5735
5736
- ``t`` (vertex) -- forces the destination of the path (the method then
5737
returns the longest path ending at ``t``). The argument is set to
5738
``None`` by default, which means that no constraint is set upon the
5739
last vertex in the path.
5740
5741
- ``use_edge_labels`` (boolean) -- whether the labels on the edges are
5742
to be considered as weights (a label set to ``None`` or ``{}`` being
5743
considered as a weight of `1`). Set to ``False`` by default.
5744
5745
- ``algorithm`` -- one of ``"MILP"`` (default) or ``"backtrack"``. Two
5746
remarks on this respect:
5747
5748
* While the MILP formulation returns an exact answer, the
5749
backtrack algorithm is a randomized heuristic.
5750
5751
* As the backtrack algorithm does not support edge weighting,
5752
setting ``use_edge_labels=True`` will force the use of the MILP
5753
algorithm.
5754
5755
- ``solver`` -- (default: ``None``) Specify the Linear Program (LP)
5756
solver to be used. If set to ``None``, the default one is used. For
5757
more information on LP solvers and which default solver is used, see
5758
the method
5759
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
5760
of the class
5761
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
5762
5763
- ``verbose`` -- integer (default: ``0``). Sets the level of
5764
verbosity. Set to 0 by default, which means quiet.
5765
5766
.. NOTE::
5767
5768
The length of a path is assumed to be the number of its edges, or
5769
the sum of their labels.
5770
5771
OUTPUT:
5772
5773
A subgraph of ``self`` corresponding to a (directed if ``self`` is
5774
directed) longest path. If ``use_edge_labels == True``, a pair ``weight,
5775
path`` is returned.
5776
5777
ALGORITHM:
5778
5779
Mixed Integer Linear Programming. (This problem is known to be NP-Hard).
5780
5781
EXAMPLES:
5782
5783
Petersen's graph being hypohamiltonian, it has a longest path
5784
of length `n-2`::
5785
5786
sage: g = graphs.PetersenGraph()
5787
sage: lp = g.longest_path()
5788
sage: lp.order() >= g.order() - 2
5789
True
5790
5791
The heuristic totally agrees::
5792
5793
sage: g = graphs.PetersenGraph()
5794
sage: g.longest_path(algorithm="backtrack").edges()
5795
[(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)]
5796
5797
Let us compute longest paths on random graphs with random weights. Each
5798
time, we ensure the resulting graph is indeed a path::
5799
5800
sage: for i in range(20):
5801
... g = graphs.RandomGNP(15, 0.3)
5802
... for u, v in g.edges(labels=False):
5803
... g.set_edge_label(u, v, random())
5804
... lp = g.longest_path()
5805
... if (not lp.is_forest() or
5806
... not max(lp.degree()) <= 2 or
5807
... not lp.is_connected()):
5808
... print("Error!")
5809
... break
5810
5811
TESTS:
5812
5813
The argument ``algorithm`` must be either ``'backtrack'`` or
5814
``'MILP'``::
5815
5816
sage: graphs.PetersenGraph().longest_path(algorithm="abc")
5817
Traceback (most recent call last):
5818
...
5819
ValueError: algorithm must be either 'backtrack' or 'MILP'
5820
5821
Disconnected graphs not weighted::
5822
5823
sage: g1 = graphs.PetersenGraph()
5824
sage: g2 = 2 * g1
5825
sage: lp1 = g1.longest_path()
5826
sage: lp2 = g2.longest_path()
5827
sage: len(lp1) == len(lp2)
5828
True
5829
5830
Disconnected graphs weighted::
5831
5832
sage: g1 = graphs.PetersenGraph()
5833
sage: for u,v in g.edges(labels=False):
5834
... g.set_edge_label(u, v, random())
5835
sage: g2 = 2 * g1
5836
sage: lp1 = g1.longest_path(use_edge_labels=True)
5837
sage: lp2 = g2.longest_path(use_edge_labels=True)
5838
sage: lp1[0] == lp2[0]
5839
True
5840
5841
Empty graphs::
5842
5843
sage: Graph().longest_path()
5844
Graph on 0 vertices
5845
sage: Graph().longest_path(use_edge_labels=True)
5846
[0, Graph on 0 vertices]
5847
sage: graphs.EmptyGraph().longest_path()
5848
Graph on 0 vertices
5849
sage: graphs.EmptyGraph().longest_path(use_edge_labels=True)
5850
[0, Graph on 0 vertices]
5851
5852
Trivial graphs::
5853
5854
sage: G = Graph()
5855
sage: G.add_vertex(0)
5856
sage: G.longest_path()
5857
Graph on 0 vertices
5858
sage: G.longest_path(use_edge_labels=True)
5859
[0, Graph on 0 vertices]
5860
sage: graphs.CompleteGraph(1).longest_path()
5861
Graph on 0 vertices
5862
sage: graphs.CompleteGraph(1).longest_path(use_edge_labels=True)
5863
[0, Graph on 0 vertices]
5864
5865
Random test for digraphs::
5866
5867
sage: for i in range(20):
5868
... g = digraphs.RandomDirectedGNP(15, 0.3)
5869
... for u, v in g.edges(labels=False):
5870
... g.set_edge_label(u, v, random())
5871
... lp = g.longest_path()
5872
... if (not lp.is_directed_acyclic() or
5873
... not max(lp.out_degree()) <= 1 or
5874
... not max(lp.in_degree()) <= 1 or
5875
... not lp.is_connected()):
5876
... print("Error!")
5877
... print g.edges()
5878
... break
5879
5880
:trac:`13019`::
5881
5882
sage: g = graphs.CompleteGraph(5).to_directed()
5883
sage: g.longest_path(s=1,t=2)
5884
Subgraph of (Complete graph): Digraph on 5 vertices
5885
5886
:trac:`14412`::
5887
5888
sage: l = [(0, 1), (0, 3), (2, 0)]
5889
sage: G = DiGraph(l)
5890
sage: G.longest_path().edges()
5891
[(0, 1, None), (2, 0, None)]
5892
"""
5893
self._scream_if_not_simple()
5894
5895
if use_edge_labels:
5896
algorithm = "MILP"
5897
if algorithm not in ("backtrack", "MILP"):
5898
raise ValueError("algorithm must be either 'backtrack' or 'MILP'")
5899
5900
# Quick improvement
5901
if not self.is_connected():
5902
if use_edge_labels:
5903
return max(g.longest_path(s=s, t=t,
5904
use_edge_labels=use_edge_labels,
5905
algorithm=algorithm)
5906
for g in self.connected_components_subgraphs())
5907
else:
5908
return max((g.longest_path(s=s, t=t,
5909
use_edge_labels=use_edge_labels,
5910
algorithm=algorithm)
5911
for g in self.connected_components_subgraphs()),
5912
key=lambda x: x.order())
5913
5914
# Stupid cases
5915
# - Graph having <= 1 vertex.
5916
#
5917
# - The source has outdegree 0 in a directed graph, or
5918
# degree 0, or is not a vertex of the graph.
5919
#
5920
# - The destination has indegree 0 in a directed graph, or
5921
# degree 0, or is not a vertex of the graph.
5922
#
5923
# - Both s and t are specified, but there is no path between
5924
# the two in a directed graph (the graph is connected).
5925
if (self.order() <= 1 or
5926
(s is not None and (
5927
(s not in self) or
5928
(self._directed and self.out_degree(s) == 0) or
5929
(not self._directed and self.degree(s) == 0))) or
5930
(t is not None and (
5931
(t not in self) or
5932
(self._directed and self.in_degree(t) == 0) or
5933
(not self._directed and self.degree(t) == 0))) or
5934
(self._directed and (s is not None) and (t is not None) and
5935
len(self.shortest_path(s, t)) == 0)):
5936
if self._directed:
5937
from sage.graphs.all import DiGraph
5938
return [0, DiGraph()] if use_edge_labels else DiGraph()
5939
from sage.graphs.all import Graph
5940
return [0, Graph()] if use_edge_labels else Graph()
5941
5942
# Calling the backtrack heuristic if asked
5943
if algorithm == "backtrack":
5944
from sage.graphs.generic_graph_pyx import find_hamiltonian as fh
5945
x = fh(self, find_path=True)[1]
5946
return self.subgraph(vertices=x, edges=zip(x[:-1], x[1:]))
5947
5948
##################
5949
# LP Formulation #
5950
##################
5951
5952
# Epsilon... Must be less than 1/(n+1), but we want to avoid
5953
# numerical problems...
5954
epsilon = 1/(2*float(self.order()))
5955
5956
# Associating a weight to a label
5957
if use_edge_labels:
5958
weight = lambda x: x if (x is not None and x != {}) else 1
5959
else:
5960
weight = lambda x: 1
5961
5962
from sage.numerical.mip import MixedIntegerLinearProgram
5963
p = MixedIntegerLinearProgram(solver=solver)
5964
5965
# edge_used[(u,v)] == 1 if (u,v) is used
5966
edge_used = p.new_variable(binary=True)
5967
5968
# relaxed version of the previous variable, to prevent the
5969
# creation of cycles
5970
r_edge_used = p.new_variable()
5971
5972
# vertex_used[v] == 1 if vertex v is used
5973
vertex_used = p.new_variable(binary=True)
5974
5975
if self._directed:
5976
5977
# if edge uv is used, vu can not be
5978
for u, v in self.edges(labels=False):
5979
if self.has_edge(v, u):
5980
p.add_constraint(edge_used[(u,v)] + edge_used[(v,u)] <= 1)
5981
5982
# A vertex is used if one of its incident edges is
5983
for u,v in self.edges(labels = False):
5984
p.add_constraint(vertex_used[v] >= edge_used[(u,v)])
5985
p.add_constraint(vertex_used[u] >= edge_used[(u,v)])
5986
5987
# A path is a tree. If n vertices are used, at most n-1 edges are
5988
p.add_constraint(
5989
p.sum(vertex_used[v] for v in self)
5990
- p.sum(edge_used[e] for e in self.edges(labels=False))
5991
== 1)
5992
5993
# A vertex has at most one incoming used edge and at most
5994
# one outgoing used edge
5995
for v in self:
5996
p.add_constraint(
5997
p.sum(edge_used[(u,v)] for u in self.neighbors_in(v)) <= 1)
5998
p.add_constraint(
5999
p.sum(edge_used[(v,u)] for u in self.neighbors_out(v)) <= 1)
6000
6001
# r_edge_used is "more" than edge_used, though it ignores
6002
# the direction
6003
for u, v in self.edges(labels=False):
6004
p.add_constraint(r_edge_used[(u,v)] + r_edge_used[(v,u)]
6005
>= edge_used[(u,v)])
6006
6007
# No cycles
6008
for v in self:
6009
p.add_constraint(
6010
p.sum(r_edge_used[(u,v)] for u in self.neighbors(v))
6011
<= 1-epsilon)
6012
6013
# Enforcing the source if asked.. If s is set, it has no
6014
# incoming edge and exactly one son
6015
if s is not None:
6016
p.add_constraint(
6017
p.sum(edge_used[(u,s)] for u in self.neighbors_in(s)),
6018
max=0, min=0)
6019
p.add_constraint(
6020
p.sum(edge_used[(s,u)] for u in self.neighbors_out(s)),
6021
min=1, max=1)
6022
6023
# Enforcing the destination if asked.. If t is set, it has
6024
# no outgoing edge and exactly one parent
6025
if t is not None:
6026
p.add_constraint(
6027
p.sum(edge_used[(u,t)] for u in self.neighbors_in(t)),
6028
min=1, max=1)
6029
p.add_constraint(
6030
p.sum(edge_used[(t,u)] for u in self.neighbors_out(t)),
6031
max=0, min=0)
6032
6033
# Defining the objective
6034
p.set_objective(
6035
p.sum(weight(l) * edge_used[(u,v)] for u, v, l in self.edges()))
6036
else:
6037
# f_edge_used calls edge_used through reordering u and v
6038
# to avoid having two different variables
6039
f_edge_used = lambda u, v: edge_used[
6040
(u,v) if hash(u) < hash(v) else (v,u)]
6041
# A vertex is used if one of its incident edges is
6042
for v in self:
6043
for u in self.neighbors(v):
6044
p.add_constraint(vertex_used[v] - f_edge_used(u,v), min=0)
6045
# A path is a tree. If n vertices are used, at most n-1 edges are
6046
p.add_constraint(
6047
p.sum(vertex_used[v] for v in self)
6048
- p.sum(f_edge_used(u,v) for u, v in self.edges(labels=False)),
6049
min=1, max=1)
6050
# A vertex has at most two incident edges used
6051
for v in self:
6052
p.add_constraint(
6053
p.sum(f_edge_used(u,v) for u in self.neighbors(v)), max=2)
6054
# r_edge_used is "more" than edge_used
6055
for u, v in self.edges(labels=False):
6056
p.add_constraint(r_edge_used[(u,v)]
6057
+ r_edge_used[(v,u)]
6058
- f_edge_used(u,v),
6059
min=0)
6060
# No cycles
6061
for v in self:
6062
p.add_constraint(
6063
p.sum(r_edge_used[(u,v)] for u in self.neighbors(v)),
6064
max=1-epsilon)
6065
# Enforcing the destination if asked.. If s or t are set,
6066
# they have exactly one incident edge
6067
if s is not None:
6068
p.add_constraint(
6069
p.sum(f_edge_used(s,u) for u in self.neighbors(s)),
6070
max=1, min=1)
6071
if t is not None:
6072
p.add_constraint(
6073
p.sum(f_edge_used(t,u) for u in self.neighbors(t)),
6074
max=1, min=1)
6075
# Defining the objective
6076
p.set_objective(p.sum(weight(l) * f_edge_used(u,v)
6077
for u, v, l in self.edges()))
6078
# Computing the result. No exception has to be raised, as this
6079
# problem always has a solution (there is at least one edge,
6080
# and a path from s to t if they are specified).
6081
p.solve(log=verbose)
6082
edge_used = p.get_values(edge_used)
6083
vertex_used = p.get_values(vertex_used)
6084
if self._directed:
6085
g = self.subgraph(
6086
vertices=(v for v in self if vertex_used[v] == 1),
6087
edges=((u,v,l) for u, v, l in self.edges()
6088
if edge_used[(u,v)] == 1))
6089
else:
6090
g = self.subgraph(
6091
vertices=(v for v in self if vertex_used[v] == 1),
6092
edges=((u,v,l) for u, v, l in self.edges()
6093
if f_edge_used(u,v) == 1))
6094
if use_edge_labels:
6095
return sum(map(weight, g.edge_labels())), g
6096
else:
6097
return g
6098
6099
def traveling_salesman_problem(self, use_edge_labels = False, solver = None, constraint_generation = None, verbose = 0, verbose_constraints = False):
6100
r"""
6101
Solves the traveling salesman problem (TSP)
6102
6103
Given a graph (resp. a digraph) `G` with weighted edges, the traveling
6104
salesman problem consists in finding a Hamiltonian cycle (resp. circuit)
6105
of the graph of minimum cost.
6106
6107
This TSP is one of the most famous NP-Complete problems, this function
6108
can thus be expected to take some time before returning its result.
6109
6110
INPUT:
6111
6112
- ``use_edge_labels`` (boolean) -- whether to consider the weights of
6113
the edges.
6114
6115
- If set to ``False`` (default), all edges are assumed to weight
6116
`1`
6117
6118
- If set to ``True``, the weights are taken into account, and the
6119
circuit returned is the one minimizing the sum of the weights.
6120
6121
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
6122
solver to be used. If set to ``None``, the default one is used. For
6123
more information on LP solvers and which default solver is used, see
6124
the method
6125
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
6126
of the class
6127
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
6128
6129
- ``constraint_generation`` (boolean) -- whether to use constraint
6130
generation when solving the Mixed Integer Linear Program.
6131
6132
When ``constraint_generation = None``, constraint generation is used
6133
whenever the graph has a density larger than 70%.
6134
6135
- ``verbose`` -- integer (default: ``0``). Sets the level of
6136
verbosity. Set to 0 by default, which means quiet.
6137
6138
- ``verbose_constraints`` -- whether to display which constraints are
6139
being generated.
6140
6141
OUTPUT:
6142
6143
A solution to the TSP, as a ``Graph`` object whose vertex set is `V(G)`,
6144
and whose edges are only those of the solution.
6145
6146
ALGORITHM:
6147
6148
This optimization problem is solved through the use of Linear
6149
Programming.
6150
6151
NOTE:
6152
6153
- This function is correctly defined for both graph and digraphs. In
6154
the second case, the returned cycle is a circuit of optimal cost.
6155
6156
EXAMPLES:
6157
6158
The Heawood graph is known to be Hamiltonian::
6159
6160
sage: g = graphs.HeawoodGraph()
6161
sage: tsp = g.traveling_salesman_problem()
6162
sage: tsp
6163
TSP from Heawood graph: Graph on 14 vertices
6164
6165
The solution to the TSP has to be connected ::
6166
6167
sage: tsp.is_connected()
6168
True
6169
6170
It must also be a `2`-regular graph::
6171
6172
sage: tsp.is_regular(k=2)
6173
True
6174
6175
And obviously it is a subgraph of the Heawood graph::
6176
6177
sage: all([ e in g.edges() for e in tsp.edges()])
6178
True
6179
6180
On the other hand, the Petersen Graph is known not to
6181
be Hamiltonian::
6182
6183
sage: g = graphs.PetersenGraph()
6184
sage: tsp = g.traveling_salesman_problem()
6185
Traceback (most recent call last):
6186
...
6187
ValueError: The given graph is not hamiltonian
6188
6189
6190
One easy way to change is is obviously to add to this graph the edges
6191
corresponding to a Hamiltonian cycle.
6192
6193
If we do this by setting the cost of these new edges to `2`, while the
6194
others are set to `1`, we notice that not all the edges we added are
6195
used in the optimal solution ::
6196
6197
sage: for u, v in g.edges(labels = None):
6198
... g.set_edge_label(u,v,1)
6199
6200
sage: cycle = graphs.CycleGraph(10)
6201
sage: for u,v in cycle.edges(labels = None):
6202
... if not g.has_edge(u,v):
6203
... g.add_edge(u,v)
6204
... g.set_edge_label(u,v,2)
6205
6206
sage: tsp = g.traveling_salesman_problem(use_edge_labels = True)
6207
sage: sum( tsp.edge_labels() ) < 2*10
6208
True
6209
6210
If we pick `1/2` instead of `2` as a cost for these new edges, they
6211
clearly become the optimal solution::
6212
6213
sage: for u,v in cycle.edges(labels = None):
6214
... g.set_edge_label(u,v,1/2)
6215
6216
sage: tsp = g.traveling_salesman_problem(use_edge_labels = True)
6217
sage: sum( tsp.edge_labels() ) == (1/2)*10
6218
True
6219
6220
TESTS:
6221
6222
Comparing the results returned according to the value of
6223
``constraint_generation``. First, for graphs::
6224
6225
sage: from operator import itemgetter
6226
sage: n = 20
6227
sage: for i in range(20):
6228
... g = Graph()
6229
... g.allow_multiple_edges(False)
6230
... for u,v in graphs.RandomGNP(n,.2).edges(labels = False):
6231
... g.add_edge(u,v,round(random(),5))
6232
... for u,v in graphs.CycleGraph(n).edges(labels = False):
6233
... if not g.has_edge(u,v):
6234
... g.add_edge(u,v,round(random(),5))
6235
... v1 = g.traveling_salesman_problem(constraint_generation = False, use_edge_labels = True)
6236
... v2 = g.traveling_salesman_problem(use_edge_labels = True)
6237
... c1 = sum(map(itemgetter(2), v1.edges()))
6238
... c2 = sum(map(itemgetter(2), v2.edges()))
6239
... if c1 != c2:
6240
... print "Error !",c1,c2
6241
... break
6242
6243
Then for digraphs::
6244
6245
sage: from operator import itemgetter
6246
sage: set_random_seed(0)
6247
sage: n = 20
6248
sage: for i in range(20):
6249
... g = DiGraph()
6250
... g.allow_multiple_edges(False)
6251
... for u,v in digraphs.RandomDirectedGNP(n,.2).edges(labels = False):
6252
... g.add_edge(u,v,round(random(),5))
6253
... for u,v in digraphs.Circuit(n).edges(labels = False):
6254
... if not g.has_edge(u,v):
6255
... g.add_edge(u,v,round(random(),5))
6256
... v2 = g.traveling_salesman_problem(use_edge_labels = True)
6257
... v1 = g.traveling_salesman_problem(constraint_generation = False, use_edge_labels = True)
6258
... c1 = sum(map(itemgetter(2), v1.edges()))
6259
... c2 = sum(map(itemgetter(2), v2.edges()))
6260
... if c1 != c2:
6261
... print "Error !",c1,c2
6262
... print "With constraint generation :",c2
6263
... print "Without constraint generation :",c1
6264
... break
6265
6266
"""
6267
self._scream_if_not_simple()
6268
6269
if constraint_generation is None:
6270
if self.density() > .7:
6271
constraint_generation = False
6272
else:
6273
constraint_generation = True
6274
6275
###############################
6276
# Quick checks of connectivity #
6277
###############################
6278
6279
# TODO : Improve it by checking vertex-connectivity instead of
6280
# edge-connectivity.... But calling the vertex_connectivity (which
6281
# builds a LP) is way too slow. These tests only run traversals written
6282
# in Cython --> hence FAST
6283
6284
if self.is_directed():
6285
if not self.is_strongly_connected():
6286
raise ValueError("The given graph is not hamiltonian")
6287
6288
else:
6289
# Checks whether the graph is 2-connected
6290
if not self.strong_orientation().is_strongly_connected():
6291
raise ValueError("The given graph is not hamiltonian")
6292
6293
############################
6294
# Deal with multiple edges #
6295
############################
6296
6297
if self.has_multiple_edges():
6298
g = self.copy()
6299
multi = self.multiple_edges()
6300
g.delete_edges(multi)
6301
g.allow_multiple_edges(False)
6302
if use_edge_labels:
6303
e = {}
6304
6305
for u,v,l in multi:
6306
u,v = (u,v) if u<v else (v,u)
6307
6308
# The weight of an edge is the minimum over
6309
# the weights of the parallel edges
6310
6311
# new value *if* ( none other *or* new==None and last > 1 *else* change nothing
6312
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)]
6313
6314
g.add_edges([(u,v) for (u,v),l in e.iteritems()])
6315
6316
else:
6317
from sage.sets.set import Set
6318
g.add_edges(Set([ (min(u,v),max(u,v)) for u,v,l in multi]))
6319
6320
else:
6321
g = self
6322
6323
from sage.numerical.mip import MixedIntegerLinearProgram
6324
from sage.numerical.mip import MIPSolverException
6325
6326
weight = lambda l : l if (l is not None and l) else 1
6327
6328
####################################################
6329
# Constraint-generation formulation of the problem #
6330
####################################################
6331
6332
if constraint_generation:
6333
6334
p = MixedIntegerLinearProgram(maximization = False,
6335
solver = solver,
6336
constraint_generation = True)
6337
6338
6339
# Directed Case #
6340
#################
6341
if g.is_directed():
6342
6343
from sage.graphs.all import DiGraph
6344
b = p.new_variable(binary = True, dim = 2)
6345
6346
# Objective function
6347
if use_edge_labels:
6348
p.set_objective(p.sum([ weight(l)*b[u][v]
6349
for u,v,l in g.edges()]))
6350
6351
# All the vertices have in-degree 1 and out-degree 1
6352
for v in g:
6353
p.add_constraint(p.sum([b[u][v] for u in g.neighbors_in(v)]),
6354
min = 1,
6355
max = 1)
6356
p.add_constraint(p.sum([b[v][u] for u in g.neighbors_out(v)]),
6357
min = 1,
6358
max = 1)
6359
6360
# Initial Solve
6361
try:
6362
p.solve(log = verbose)
6363
except MIPSolverException:
6364
raise ValueError("The given graph is not hamiltonian")
6365
6366
while True:
6367
# We build the DiGraph representing the current solution
6368
h = DiGraph()
6369
for u,v,l in g.edges():
6370
if p.get_values(b[u][v]) == 1:
6371
h.add_edge(u,v,l)
6372
6373
# If there is only one circuit, we are done !
6374
cc = h.connected_components()
6375
if len(cc) == 1:
6376
break
6377
6378
# Adding the corresponding constraint
6379
if verbose_constraints:
6380
print "Adding a constraint on set",cc[0]
6381
6382
6383
p.add_constraint(p.sum(b[u][v] for u,v in
6384
g.edge_boundary(cc[0], labels = False)),
6385
min = 1)
6386
6387
try:
6388
p.solve(log = verbose)
6389
except MIPSolverException:
6390
raise ValueError("The given graph is not hamiltonian")
6391
6392
# Undirected Case #
6393
###################
6394
else:
6395
6396
from sage.graphs.all import Graph
6397
b = p.new_variable(binary = True)
6398
B = lambda u,v : b[frozenset((u,v))]
6399
6400
# Objective function
6401
if use_edge_labels:
6402
p.set_objective(p.sum([ weight(l)*B(u,v)
6403
for u,v,l in g.edges()]) )
6404
6405
# All the vertices have degree 2
6406
for v in g:
6407
p.add_constraint(p.sum([ B(u,v) for u in g.neighbors(v)]),
6408
min = 2,
6409
max = 2)
6410
6411
# Initial Solve
6412
try:
6413
p.solve(log = verbose)
6414
except MIPSolverException:
6415
raise ValueError("The given graph is not hamiltonian")
6416
6417
while True:
6418
# We build the DiGraph representing the current solution
6419
h = Graph()
6420
for u,v,l in g.edges():
6421
if p.get_values(B(u,v)) == 1:
6422
h.add_edge(u,v,l)
6423
6424
# If there is only one circuit, we are done !
6425
cc = h.connected_components()
6426
if len(cc) == 1:
6427
break
6428
6429
# Adding the corresponding constraint
6430
if verbose_constraints:
6431
print "Adding a constraint on set",cc[0]
6432
6433
p.add_constraint(p.sum(B(u,v) for u,v in
6434
g.edge_boundary(cc[0], labels = False)),
6435
min = 2)
6436
6437
try:
6438
p.solve(log = verbose)
6439
except MIPSolverException:
6440
raise ValueError("The given graph is not hamiltonian")
6441
6442
# We can now return the TSP !
6443
answer = self.subgraph(edges = h.edges())
6444
answer.set_pos(self.get_pos())
6445
answer.name("TSP from "+g.name())
6446
return answer
6447
6448
#################################################
6449
# ILP formulation without constraint generation #
6450
#################################################
6451
6452
p = MixedIntegerLinearProgram(maximization = False, solver = solver)
6453
6454
f = p.new_variable()
6455
r = p.new_variable()
6456
6457
eps = 1/(2*Integer(g.order()))
6458
x = g.vertex_iterator().next()
6459
6460
6461
if g.is_directed():
6462
6463
# returns the variable corresponding to arc u,v
6464
E = lambda u,v : f[(u,v)]
6465
6466
# All the vertices have in-degree 1 and out-degree 1
6467
for v in g:
6468
p.add_constraint(p.sum([ f[(u,v)] for u in g.neighbors_in(v)]),
6469
min = 1,
6470
max = 1)
6471
6472
p.add_constraint(p.sum([ f[(v,u)] for u in g.neighbors_out(v)]),
6473
min = 1,
6474
max = 1)
6475
6476
# r is greater than f
6477
for u,v in g.edges(labels = None):
6478
if g.has_edge(v,u):
6479
if u < v:
6480
p.add_constraint( r[(u,v)] + r[(v,u)]- f[(u,v)] - f[(v,u)], min = 0)
6481
6482
# no 2-cycles
6483
p.add_constraint( f[(u,v)] + f[(v,u)], max = 1)
6484
6485
else:
6486
p.add_constraint( r[(u,v)] + r[(v,u)] - f[(u,v)], min = 0)
6487
6488
# defining the answer when g is directed
6489
from sage.graphs.all import DiGraph
6490
tsp = DiGraph()
6491
else:
6492
6493
# reorders the edge as they can appear in the two different ways
6494
R = lambda x,y : frozenset((x,y))
6495
6496
# returns the variable corresponding to arc u,v
6497
E = lambda u,v : f[R(u,v)]
6498
6499
# All the vertices have degree 2
6500
for v in g:
6501
p.add_constraint(p.sum([ E(u,v) for u in g.neighbors(v)]),
6502
min = 2,
6503
max = 2)
6504
6505
# r is greater than f
6506
for u,v in g.edges(labels = None):
6507
p.add_constraint( r[(u,v)] + r[(v,u)] - E(u,v), min = 0)
6508
6509
from sage.graphs.all import Graph
6510
6511
# defining the answer when g is not directed
6512
tsp = Graph()
6513
6514
# no cycle which does not contain x
6515
for v in g:
6516
if v != x:
6517
p.add_constraint(p.sum([ r[(u,v)] for u in g.neighbors(v)]),max = 1-eps)
6518
6519
if use_edge_labels:
6520
p.set_objective(p.sum([ weight(l)*E(u,v) for u,v,l in g.edges()]) )
6521
else:
6522
p.set_objective(None)
6523
6524
p.set_binary(f)
6525
6526
try:
6527
obj = p.solve(log = verbose)
6528
f = p.get_values(f)
6529
tsp.add_vertices(g.vertices())
6530
tsp.set_pos(g.get_pos())
6531
tsp.name("TSP from "+g.name())
6532
tsp.add_edges([(u,v,l) for u,v,l in g.edges() if E(u,v) == 1])
6533
6534
return tsp
6535
6536
except MIPSolverException:
6537
raise ValueError("The given graph is not Hamiltonian")
6538
6539
6540
def hamiltonian_cycle(self, algorithm='tsp' ):
6541
r"""
6542
Returns a Hamiltonian cycle/circuit of the current graph/digraph
6543
6544
A graph (resp. digraph) is said to be Hamiltonian
6545
if it contains as a subgraph a cycle (resp. a circuit)
6546
going through all the vertices.
6547
6548
Computing a Hamiltonian cycle/circuit being NP-Complete,
6549
this algorithm could run for some time depending on
6550
the instance.
6551
6552
ALGORITHM:
6553
6554
See ``Graph.traveling_salesman_problem`` for 'tsp' algorithm and
6555
``find_hamiltonian`` from ``sage.graphs.generic_graph_pyx``
6556
for 'backtrack' algorithm.
6557
6558
INPUT:
6559
6560
- ``algorithm`` - one of 'tsp' or 'backtrack'.
6561
6562
OUTPUT:
6563
6564
If using the 'tsp' algorithm, returns a Hamiltonian cycle/circuit if it
6565
exists; otherwise, raises a ``ValueError`` exception. If using the
6566
'backtrack' algorithm, returns a pair (B,P). If B is True then P is a
6567
Hamiltonian cycle and if B is False, P is a longest path found by the
6568
algorithm. Observe that if B is False, the graph may still be Hamiltonian.
6569
The 'backtrack' algorithm is only implemented for undirected
6570
graphs.
6571
6572
.. WARNING::
6573
6574
The 'backtrack' algorithm may loop endlessly on graphs
6575
with vertices of degree 1.
6576
6577
NOTE:
6578
6579
This function, as ``is_hamiltonian``, computes a Hamiltonian
6580
cycle if it exists: the user should *NOT* test for
6581
Hamiltonicity using ``is_hamiltonian`` before calling this
6582
function, as it would result in computing it twice.
6583
6584
The backtrack algorithm is only implemented for undirected graphs.
6585
6586
EXAMPLES:
6587
6588
The Heawood Graph is known to be Hamiltonian ::
6589
6590
sage: g = graphs.HeawoodGraph()
6591
sage: g.hamiltonian_cycle()
6592
TSP from Heawood graph: Graph on 14 vertices
6593
6594
The Petersen Graph, though, is not ::
6595
6596
sage: g = graphs.PetersenGraph()
6597
sage: g.hamiltonian_cycle()
6598
Traceback (most recent call last):
6599
...
6600
ValueError: The given graph is not hamiltonian
6601
6602
Now, using the backtrack algorithm in the Heawood graph ::
6603
6604
sage: G=graphs.HeawoodGraph()
6605
sage: G.hamiltonian_cycle(algorithm='backtrack')
6606
(True, [11, 10, 1, 2, 3, 4, 9, 8, 7, 6, 5, 0, 13, 12])
6607
6608
And now in the Petersen graph ::
6609
6610
sage: G=graphs.PetersenGraph()
6611
sage: G.hamiltonian_cycle(algorithm='backtrack')
6612
(False, [6, 8, 5, 0, 1, 2, 7, 9, 4, 3])
6613
6614
Finally, we test the algorithm in a cube graph, which is Hamiltonian ::
6615
6616
sage: G=graphs.CubeGraph(3)
6617
sage: G.hamiltonian_cycle(algorithm='backtrack')
6618
(True, ['010', '110', '100', '000', '001', '101', '111', '011'])
6619
6620
"""
6621
if algorithm=='tsp':
6622
from sage.numerical.mip import MIPSolverException
6623
6624
try:
6625
return self.traveling_salesman_problem(use_edge_labels = False)
6626
except MIPSolverException:
6627
raise ValueError("The given graph is not Hamiltonian")
6628
elif algorithm=='backtrack':
6629
from sage.graphs.generic_graph_pyx import find_hamiltonian as fh
6630
return fh( self )
6631
6632
else:
6633
raise ValueError("``algorithm`` (%s) should be 'tsp' or 'backtrack'."%(algorithm))
6634
6635
def feedback_vertex_set(self, value_only=False, solver=None, verbose=0, constraint_generation = True):
6636
r"""
6637
Computes the minimum feedback vertex set of a (di)graph.
6638
6639
The minimum feedback vertex set of a (di)graph is a set of vertices that
6640
intersect all of its cycles. Equivalently, a minimum feedback vertex
6641
set of a (di)graph is a set `S` of vertices such that the digraph `G-S`
6642
is acyclic. For more information, see :wikipedia:`Feedback_vertex_set`.
6643
6644
INPUT:
6645
6646
- ``value_only`` -- boolean (default: ``False``)
6647
6648
- When set to ``True``, only the minimum cardinal of a minimum vertex
6649
set is returned.
6650
6651
- When set to ``False``, the ``Set`` of vertices of a minimal feedback
6652
vertex set is returned.
6653
6654
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
6655
solver to be used. If set to ``None``, the default one is used. For
6656
more information on LP solvers and which default solver is used,
6657
see the method
6658
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
6659
of the class
6660
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
6661
6662
- ``verbose`` -- integer (default: ``0``). Sets the level of
6663
verbosity. Set to 0 by default, which means quiet.
6664
6665
- ``constraint_generation`` (boolean) -- whether to use constraint
6666
generation when solving the Mixed Integer Linear Program (default:
6667
``True``).
6668
6669
ALGORITHMS:
6670
6671
(Constraints generation)
6672
6673
When the parameter ``constraint_generation`` is enabled (default) the
6674
following MILP formulation is used to solve the problem:
6675
6676
.. MATH::
6677
6678
\mbox{Minimize : }&\sum_{v\in G} b_{v}\\
6679
\mbox{Such that : }&\\
6680
&\forall C\text{ circuits }\subseteq G, \sum_{v\in C}b_{v}\geq 1\\
6681
6682
As the number of circuits contained in a graph is exponential, this LP
6683
is solved through constraint generation. This means that the solver is
6684
sequentially asked to solve the problem, knowing only a portion of the
6685
circuits contained in `G`, each time adding to the list of its
6686
constraints the circuit which its last answer had left intact.
6687
6688
(Another formulation based on an ordering of the vertices)
6689
6690
When the graph is directed, a second (and very slow) formulation is
6691
available, which should only be used to check the result of the first
6692
implementation in case of doubt.
6693
6694
.. MATH::
6695
6696
\mbox{Minimize : }&\sum_{v\in G} b_v\\
6697
\mbox{Such that : }&\\
6698
&\forall (u,v)\in G, d_u-d_v+nb_u+nb_v\geq 0\\
6699
&\forall u\in G, 0\leq d_u\leq |G|\\
6700
6701
A brief explanation:
6702
6703
An acyclic digraph can be seen as a poset, and every poset has a linear
6704
extension. This means that in any acyclic digraph the vertices can be
6705
ordered with a total order `<` in such a way that if `(u,v)\in G`, then
6706
`u<v`. Thus, this linear program is built in order to assign to each
6707
vertex `v` a number `d_v\in [0,\dots,n-1]` such that if there exists an
6708
edge `(u,v)\in G` then either `d_v<d_u` or one of `u` or `v` is removed.
6709
The number of vertices removed is then minimized, which is the
6710
objective.
6711
6712
EXAMPLES:
6713
6714
The necessary example::
6715
6716
sage: g = graphs.PetersenGraph()
6717
sage: fvs = g.feedback_vertex_set()
6718
sage: len(fvs)
6719
3
6720
sage: g.delete_vertices(fvs)
6721
sage: g.is_forest()
6722
True
6723
6724
In a digraph built from a graph, any edge is replaced by arcs going in
6725
the two opposite directions, thus creating a cycle of length two.
6726
Hence, to remove all the cycles from the graph, each edge must see one
6727
of its neighbors removed: a feedback vertex set is in this situation a
6728
vertex cover::
6729
6730
sage: cycle = graphs.CycleGraph(5)
6731
sage: dcycle = DiGraph(cycle)
6732
sage: cycle.vertex_cover(value_only=True)
6733
3
6734
sage: feedback = dcycle.feedback_vertex_set()
6735
sage: len(feedback)
6736
3
6737
sage: (u,v,l) = cycle.edge_iterator().next()
6738
sage: u in feedback or v in feedback
6739
True
6740
6741
For a circuit, the minimum feedback arc set is clearly `1`::
6742
6743
sage: circuit = digraphs.Circuit(5)
6744
sage: circuit.feedback_vertex_set(value_only=True) == 1
6745
True
6746
6747
TESTS:
6748
6749
Comparing with/without constraint generation::
6750
6751
sage: g = digraphs.RandomDirectedGNP(10,.3)
6752
sage: x = g.feedback_vertex_set(value_only = True)
6753
sage: y = g.feedback_vertex_set(value_only = True,
6754
....: constraint_generation = False)
6755
sage: x == y
6756
True
6757
6758
Bad algorithm::
6759
6760
sage: g = graphs.PetersenGraph()
6761
sage: g.feedback_vertex_set(constraint_generation = False)
6762
Traceback (most recent call last):
6763
...
6764
ValueError: The only implementation available for undirected graphs is with constraint_generation set to True.
6765
"""
6766
if not constraint_generation and not self.is_directed():
6767
raise ValueError("The only implementation available for "
6768
"undirected graphs is with constraint_generation "
6769
"set to True.")
6770
6771
# It would be a pity to start a LP if the graph is already acyclic
6772
if ((not self.is_directed() and self.is_forest()) or
6773
( self.is_directed() and self.is_directed_acyclic())):
6774
if value_only:
6775
return 0
6776
return []
6777
6778
from sage.numerical.mip import MixedIntegerLinearProgram
6779
6780
########################################
6781
# Constraint Generation Implementation #
6782
########################################
6783
if constraint_generation:
6784
6785
p = MixedIntegerLinearProgram(constraint_generation = True,
6786
maximization = False)
6787
6788
# A variable for each vertex
6789
b = p.new_variable(binary = True)
6790
6791
# Variables are binary, and their coefficient in the objective is 1
6792
6793
p.set_objective(p.sum( b[v] for v in self))
6794
6795
p.solve(log = verbose)
6796
6797
# For as long as we do not break because the digraph is
6798
# acyclic....
6799
while True:
6800
6801
# Building the graph without the edges removed by the LP
6802
h = self.subgraph(vertices =
6803
[v for v in self if p.get_values(b[v]) == 0])
6804
6805
# Is the graph acyclic ?
6806
if self.is_directed():
6807
isok, certificate = h.is_directed_acyclic(certificate = True)
6808
else:
6809
isok, certificate = h.is_forest(certificate = True)
6810
6811
# If so, we are done !
6812
if isok:
6813
break
6814
6815
if verbose:
6816
print "Adding a constraint on circuit: ",certificate
6817
6818
# There is a circuit left. Let's add the corresponding
6819
# constraint !
6820
6821
p.add_constraint(p.sum(b[v] for v in certificate), min = 1)
6822
6823
obj = p.solve(log = verbose)
6824
6825
if value_only:
6826
return obj
6827
6828
else:
6829
6830
# listing the edges contained in the MFVS
6831
return [v for v in self if p.get_values(b[v]) == 1]
6832
6833
else:
6834
6835
######################################
6836
# Ordering-based MILP Implementation #
6837
######################################
6838
6839
p = MixedIntegerLinearProgram(maximization = False, solver = solver)
6840
6841
b = p.new_variable(binary = True)
6842
d = p.new_variable(integer = True)
6843
n = self.order()
6844
6845
# The removed vertices cover all the back arcs ( third condition )
6846
for (u,v) in self.edges(labels = None):
6847
p.add_constraint(d[u]-d[v]+n*(b[u]+b[v]), min = 1)
6848
6849
for u in self:
6850
p.add_constraint(d[u], max = n)
6851
6852
p.set_objective(p.sum([b[v] for v in self]))
6853
6854
if value_only:
6855
return Integer(round(p.solve(objective_only = True, log = verbose)))
6856
else:
6857
p.solve(log=verbose)
6858
b_sol = p.get_values(b)
6859
6860
return [v for v in self if b_sol[v] == 1]
6861
6862
def flow(self, x, y, value_only=True, integer=False, use_edge_labels=True, vertex_bound=False, method = None, solver=None, verbose=0):
6863
r"""
6864
Returns a maximum flow in the graph from ``x`` to ``y``
6865
represented by an optimal valuation of the edges. For more
6866
information, see the
6867
`Wikipedia article on maximum flow
6868
<http://en.wikipedia.org/wiki/Max_flow>`_.
6869
6870
As an optimization problem, is can be expressed this way :
6871
6872
.. MATH::
6873
6874
\mbox{Maximize : }&\sum_{e\in G.edges()} w_e b_e\\
6875
\mbox{Such that : }&\forall v \in G, \sum_{(u,v)\in G.edges()} b_{(u,v)}\leq 1\\
6876
&\forall x\in G, b_x\mbox{ is a binary variable}
6877
6878
INPUT:
6879
6880
- ``x`` -- Source vertex
6881
6882
- ``y`` -- Sink vertex
6883
6884
- ``value_only`` -- boolean (default: ``True``)
6885
6886
- When set to ``True``, only the value of a maximal
6887
flow is returned.
6888
6889
- When set to ``False``, is returned a pair whose first element
6890
is the value of the maximum flow, and whose second value is
6891
a flow graph (a copy of the current graph, such that each edge
6892
has the flow using it as a label, the edges without flow being
6893
omitted).
6894
6895
- ``integer`` -- boolean (default: ``False``)
6896
6897
- When set to ``True``, computes an optimal solution under the
6898
constraint that the flow going through an edge has to be an
6899
integer.
6900
6901
- ``use_edge_labels`` -- boolean (default: ``True``)
6902
6903
- When set to ``True``, computes a maximum flow
6904
where each edge has a capacity defined by its label. (If
6905
an edge has no label, `1` is assumed.)
6906
6907
- When set to ``False``, each edge has capacity `1`.
6908
6909
- ``vertex_bound`` -- boolean (default: ``False``)
6910
6911
- When set to ``True``, sets the maximum flow leaving
6912
a vertex different from `x` to `1` (useful for vertex
6913
connectivity parameters).
6914
6915
- ``method`` -- There are currently two different
6916
implementations of this method :
6917
6918
* If ``method = "FF"``, a Python implementation of the
6919
Ford-Fulkerson algorithm is used (only available when
6920
``vertex_bound = False``)
6921
6922
* If ``method = "LP"``, the flow problem is solved using
6923
Linear Programming.
6924
6925
* If ``method = None`` (default), the Ford-Fulkerson
6926
implementation is used iif ``vertex_bound = False``.
6927
6928
- ``solver`` -- Specify a Linear Program solver to be used.
6929
If set to ``None``, the default one is used. function of
6930
``MixedIntegerLinearProgram``. See the documentation of
6931
``MixedIntegerLinearProgram.solve`` for more information.
6932
6933
Only useful when LP is used to solve the flow problem.
6934
6935
- ``verbose`` (integer) -- sets the level of verbosity. Set to 0
6936
by default (quiet).
6937
6938
Only useful when LP is used to solve the flow problem.
6939
6940
.. NOTE::
6941
6942
Even though the two different implementations are meant to
6943
return the same Flow values, they can not be expected to
6944
return the same Flow graphs.
6945
6946
Besides, the use of Linear Programming may possibly mean a
6947
(slight) numerical noise.
6948
6949
EXAMPLES:
6950
6951
Two basic applications of the flow method for the ``PappusGraph`` and the
6952
``ButterflyGraph`` with parameter `2` ::
6953
6954
sage: g=graphs.PappusGraph()
6955
sage: g.flow(1,2)
6956
3
6957
6958
::
6959
6960
sage: b=digraphs.ButterflyGraph(2)
6961
sage: b.flow(('00',1),('00',2))
6962
1
6963
6964
The flow method can be used to compute a matching in a bipartite graph
6965
by linking a source `s` to all the vertices of the first set and linking
6966
a sink `t` to all the vertices of the second set, then computing
6967
a maximum `s-t` flow ::
6968
6969
sage: g = DiGraph()
6970
sage: g.add_edges([('s',i) for i in range(4)])
6971
sage: g.add_edges([(i,4+j) for i in range(4) for j in range(4)])
6972
sage: g.add_edges([(4+i,'t') for i in range(4)])
6973
sage: [cardinal, flow_graph] = g.flow('s','t',integer=True,value_only=False)
6974
sage: flow_graph.delete_vertices(['s','t'])
6975
sage: len(flow_graph.edges())
6976
4
6977
6978
TESTS:
6979
6980
An exception if raised when forcing "FF" with ``vertex_bound = True``::
6981
6982
sage: g = graphs.PetersenGraph()
6983
sage: g.flow(0,1,vertex_bound = True, method = "FF")
6984
Traceback (most recent call last):
6985
...
6986
ValueError: This method does not support both vertex_bound=True and method="FF".
6987
6988
Or if the method is different from the expected values::
6989
6990
sage: g.flow(0,1, method="Divination")
6991
Traceback (most recent call last):
6992
...
6993
ValueError: The method argument has to be equal to either "FF", "LP" or None
6994
6995
The two methods are indeed returning the same results (possibly with
6996
some numerical noise, cf. :trac:`12362`)::
6997
6998
sage: g = graphs.RandomGNP(20,.3)
6999
sage: for u,v in g.edges(labels=False):
7000
... g.set_edge_label(u,v,round(random(),5))
7001
sage: flow_ff = g.flow(0,1, method="FF")
7002
sage: flow_lp = g.flow(0,1,method="LP")
7003
sage: abs(flow_ff-flow_lp) < 0.01
7004
True
7005
"""
7006
self._scream_if_not_simple(allow_loops=True)
7007
if vertex_bound and method == "FF":
7008
raise ValueError("This method does not support both vertex_bound=True and method=\"FF\".")
7009
7010
if (method == "FF" or
7011
(method is None and not vertex_bound)):
7012
return self._ford_fulkerson(x,y, value_only=value_only, integer=integer, use_edge_labels=use_edge_labels)
7013
7014
if method != "LP" and not method is None:
7015
raise ValueError("The method argument has to be equal to either \"FF\", \"LP\" or None")
7016
7017
7018
from sage.numerical.mip import MixedIntegerLinearProgram
7019
g=self
7020
p=MixedIntegerLinearProgram(maximization=True, solver = solver)
7021
flow=p.new_variable(dim=1)
7022
7023
if use_edge_labels:
7024
from sage.rings.real_mpfr import RR
7025
capacity=lambda x: x if x in RR else 1
7026
else:
7027
capacity=lambda x: 1
7028
7029
if g.is_directed():
7030
# This function return the balance of flow at X
7031
flow_sum=lambda X: p.sum([flow[(X,v)] for (u,v) in g.outgoing_edges([X],labels=None)])-p.sum([flow[(u,X)] for (u,v) in g.incoming_edges([X],labels=None)])
7032
7033
# The flow leaving x
7034
flow_leaving = lambda X : p.sum([flow[(uu,vv)] for (uu,vv) in g.outgoing_edges([X],labels=None)])
7035
7036
# The flow to be considered when defining the capacity contraints
7037
capacity_sum = lambda u,v : flow[(u,v)]
7038
7039
else:
7040
# This function return the balance of flow at X
7041
flow_sum=lambda X:p.sum([flow[(X,v)]-flow[(v,X)] for v in g[X]])
7042
7043
# The flow leaving x
7044
flow_leaving = lambda X : p.sum([flow[(X,vv)] for vv in g[X]])
7045
7046
# The flow to be considered when defining the capacity contraints
7047
capacity_sum = lambda u,v : flow[(u,v)] + flow[(v,u)]
7048
7049
# Maximizes the flow leaving x
7050
p.set_objective(flow_sum(x))
7051
7052
# Elsewhere, the flow is equal to 0
7053
for v in g:
7054
if v!=x and v!=y:
7055
p.add_constraint(flow_sum(v),min=0,max=0)
7056
7057
# Capacity constraints
7058
for (u,v,w) in g.edges():
7059
p.add_constraint(capacity_sum(u,v),max=capacity(w))
7060
7061
# No vertex except the sources can send more than 1
7062
if vertex_bound:
7063
for v in g:
7064
if v!=x and v!=y:
7065
p.add_constraint(flow_leaving(v),max=1)
7066
7067
if integer:
7068
p.set_integer(flow)
7069
7070
7071
if value_only:
7072
return p.solve(objective_only=True, log = verbose)
7073
7074
obj=p.solve(log = verbose)
7075
7076
if integer or use_edge_labels is False:
7077
obj = Integer(round(obj))
7078
7079
flow=p.get_values(flow)
7080
# Builds a clean flow Draph
7081
flow_graph = g._build_flow_graph(flow, integer=integer)
7082
7083
# Which could be a Graph
7084
if not self.is_directed():
7085
from sage.graphs.graph import Graph
7086
flow_graph = Graph(flow_graph)
7087
7088
return [obj,flow_graph]
7089
7090
def _ford_fulkerson(self, s, t, use_edge_labels = False, integer = False, value_only = True):
7091
r"""
7092
Python implementation of the Ford-Fulkerson algorithm.
7093
7094
This method is a Python implementation of the Ford-Fulkerson
7095
max-flow algorithm, which is (slightly) faster than the LP
7096
implementation.
7097
7098
INPUT:
7099
7100
- ``s`` -- Source vertex
7101
7102
- ``t`` -- Sink vertex
7103
7104
- ``value_only`` -- boolean (default: ``True``)
7105
7106
- When set to ``True``, only the value of a maximal
7107
flow is returned.
7108
7109
- When set to ``False``, is returned a pair whose first element
7110
is the value of the maximum flow, and whose second value is
7111
a flow graph (a copy of the current graph, such that each edge
7112
has the flow using it as a label, the edges without flow being
7113
omitted).
7114
7115
- ``integer`` -- boolean (default: ``False``)
7116
7117
- When set to ``True``, computes an optimal solution under the
7118
constraint that the flow going through an edge has to be an
7119
integer.
7120
7121
- ``use_edge_labels`` -- boolean (default: ``True``)
7122
7123
- When set to ``True``, computes a maximum flow
7124
where each edge has a capacity defined by its label. (If
7125
an edge has no label, `1` is assumed.)
7126
7127
- When set to ``False``, each edge has capacity `1`.
7128
7129
EXAMPLES:
7130
7131
Two basic applications of the flow method for the ``PappusGraph`` and the
7132
``ButterflyGraph`` with parameter `2` ::
7133
7134
sage: g=graphs.PappusGraph()
7135
sage: g._ford_fulkerson(1,2)
7136
3
7137
7138
::
7139
7140
sage: b=digraphs.ButterflyGraph(2)
7141
sage: b._ford_fulkerson(('00',1),('00',2))
7142
1
7143
7144
The flow method can be used to compute a matching in a bipartite graph
7145
by linking a source `s` to all the vertices of the first set and linking
7146
a sink `t` to all the vertices of the second set, then computing
7147
a maximum `s-t` flow ::
7148
7149
sage: g = DiGraph()
7150
sage: g.add_edges([('s',i) for i in range(4)])
7151
sage: g.add_edges([(i,4+j) for i in range(4) for j in range(4)])
7152
sage: g.add_edges([(4+i,'t') for i in range(4)])
7153
sage: [cardinal, flow_graph] = g._ford_fulkerson('s','t',integer=True,value_only=False)
7154
sage: flow_graph.delete_vertices(['s','t'])
7155
sage: len(flow_graph.edges(labels=None))
7156
4
7157
"""
7158
from sage.graphs.digraph import DiGraph
7159
from sage.functions.other import floor
7160
7161
# Whether we should consider the edges labeled
7162
if use_edge_labels:
7163
l_capacity=lambda x: 1 if (x is None or x == {}) else (floor(x) if integer else x)
7164
else:
7165
l_capacity=lambda x: 1
7166
7167
directed = self.is_directed()
7168
7169
# Associated to each edge (u,v) of the flow graph its capacity
7170
capacity = {}
7171
# Associates to each edge (u,v) of the graph the (directed)
7172
# flow going through it
7173
flow = {}
7174
7175
# Residual graph. Only contains edge on which some flow can be
7176
# sent. This can happen both when the flow going through the
7177
# current edge is strictly less than its capacity, or when
7178
# there exists a back arc with non-null flow
7179
residual = DiGraph()
7180
7181
# Initializing the variables
7182
if directed:
7183
for u,v,l in self.edge_iterator():
7184
if l_capacity(l) > 0:
7185
capacity[(u,v)] = l_capacity(l) + capacity.get((u,v),0)
7186
capacity[(v,u)] = capacity.get((v,u),0)
7187
residual.add_edge(u,v)
7188
flow[(u,v)] = 0
7189
flow[(v,u)] = 0
7190
else:
7191
for u,v,l in self.edge_iterator():
7192
if l_capacity(l) > 0:
7193
capacity[(u,v)] = l_capacity(l) + capacity.get((u,v),0)
7194
capacity[(v,u)] = l_capacity(l) + capacity.get((v,u),0)
7195
residual.add_edge(u,v)
7196
residual.add_edge(v,u)
7197
flow[(u,v)] = 0
7198
flow[(v,u)] = 0
7199
7200
# Reqrites a path as a list of edges :
7201
# ex : [0,1,2,3,4,5] becomes [(0,1), (1,2), (2,3), (3,4), (4,5)]
7202
path_to_edges = lambda P : zip(P[:-1],P[1:])
7203
7204
# Rewrites a path as a list of edges labeled with their
7205
# available capacity
7206
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))
7207
7208
# Total flow going from s to t
7209
flow_intensity = 0
7210
7211
while True:
7212
7213
# If there is a shortest path from s to t
7214
path = residual.shortest_path(s,t)
7215
if not path:
7216
break
7217
7218
# We are rewriting the shortest path as a sequence of
7219
# edges whose labels are their available capacities
7220
edges = path_to_labelled_edges(path)
7221
7222
# minimum capacity available on the whole path
7223
epsilon = min(map( lambda x : x[2], edges))
7224
7225
flow_intensity = flow_intensity + epsilon
7226
7227
# Updating variables
7228
for uu,vv,ll in edges:
7229
7230
# The flow on the back arc
7231
other = flow[(vv,uu)]
7232
flow[(uu,vv)] = flow[(uu,vv)] + max(0,epsilon-other)
7233
flow[(vv,uu)] = other - min(other, epsilon)
7234
7235
# If the current edge is fully used, we do not need it
7236
# anymore in the residual graph
7237
if capacity[(uu,vv)] - flow[(uu,vv)] + flow[(vv,uu)] == 0:
7238
residual.delete_edge(uu,vv)
7239
7240
# If the back arc does not exist, it now does as the
7241
# edge (uu,vv) has a flow of at least epsilon>0
7242
if not residual.has_edge(vv,uu):
7243
residual.add_edge(vv,uu,epsilon)
7244
7245
if value_only:
7246
return flow_intensity
7247
7248
# Building and returning the flow graph
7249
g = DiGraph()
7250
g.add_edges([(x,y,l) for ((x,y),l) in flow.iteritems() if l > 0])
7251
g.set_pos(self.get_pos())
7252
7253
return flow_intensity, g
7254
7255
def multicommodity_flow(self, terminals, integer=True, use_edge_labels=False,vertex_bound=False, solver=None, verbose=0):
7256
r"""
7257
Solves a multicommodity flow problem.
7258
7259
In the multicommodity flow problem, we are given a set of pairs
7260
`(s_i, t_i)`, called terminals meaning that `s_i` is willing
7261
some flow to `t_i`.
7262
7263
Even though it is a natural generalisation of the flow problem
7264
this version of it is NP-Complete to solve when the flows
7265
are required to be integer.
7266
7267
For more information, see the
7268
:wikipedia:`Wikipedia page on multicommodity flows
7269
<Multi-commodity_flow_problem>`.
7270
7271
INPUT:
7272
7273
- ``terminals`` -- a list of pairs `(s_i, t_i)` or triples
7274
`(s_i, t_i, w_i)` representing a flow from `s_i` to `t_i`
7275
of intensity `w_i`. When the pairs are of size `2`, a intensity
7276
of `1` is assumed.
7277
7278
- ``integer`` (boolean) -- whether to require an integer multicommodity
7279
flow
7280
7281
- ``use_edge_labels`` (boolean) -- whether to consider the label of edges
7282
as numerical values representing a capacity. If set to ``False``, a capacity
7283
of `1` is assumed
7284
7285
- ``vertex_bound`` (boolean) -- whether to require that a vertex can stand at most
7286
`1` commodity of flow through it of intensity `1`. Terminals can obviously
7287
still send or receive several units of flow even though vertex_bound is set
7288
to ``True``, as this parameter is meant to represent topological properties.
7289
7290
- ``solver`` -- Specify a Linear Program solver to be used.
7291
If set to ``None``, the default one is used.
7292
function of ``MixedIntegerLinearProgram``. See the documentation of ``MixedIntegerLinearProgram.solve``
7293
for more informations.
7294
7295
- ``verbose`` (integer) -- sets the level of verbosity. Set to 0
7296
by default (quiet).
7297
7298
ALGORITHM:
7299
7300
(Mixed Integer) Linear Program, depending on the value of ``integer``.
7301
7302
EXAMPLE:
7303
7304
An easy way to obtain a satisfiable multiflow is to compute
7305
a matching in a graph, and to consider the paired vertices
7306
as terminals ::
7307
7308
sage: g = graphs.PetersenGraph()
7309
sage: matching = [(u,v) for u,v,_ in g.matching()]
7310
sage: h = g.multicommodity_flow(matching)
7311
sage: len(h)
7312
5
7313
7314
We could also have considered ``g`` as symmetric and computed
7315
the multiflow in this version instead. In this case, however
7316
edges can be used in both directions at the same time::
7317
7318
sage: h = DiGraph(g).multicommodity_flow(matching)
7319
sage: len(h)
7320
5
7321
7322
An exception is raised when the problem has no solution ::
7323
7324
sage: h = g.multicommodity_flow([(u,v,3) for u,v in matching])
7325
Traceback (most recent call last):
7326
...
7327
ValueError: The multiflow problem has no solution
7328
"""
7329
self._scream_if_not_simple(allow_loops=True)
7330
from sage.numerical.mip import MixedIntegerLinearProgram
7331
g=self
7332
p=MixedIntegerLinearProgram(maximization=True, solver = solver)
7333
7334
# Adding the intensity if not present
7335
terminals = [(x if len(x) == 3 else (x[0],x[1],1)) for x in terminals]
7336
7337
# defining the set of terminals
7338
set_terminals = set([])
7339
for s,t,_ in terminals:
7340
set_terminals.add(s)
7341
set_terminals.add(t)
7342
7343
# flow[i][(u,v)] is the flow of commodity i going from u to v
7344
flow=p.new_variable(dim=2)
7345
7346
# Whether to use edge labels
7347
if use_edge_labels:
7348
from sage.rings.real_mpfr import RR
7349
capacity=lambda x: x if x in RR else 1
7350
else:
7351
capacity=lambda x: 1
7352
7353
if g.is_directed():
7354
# This function return the balance of flow at X
7355
flow_sum=lambda i,X: p.sum([flow[i][(X,v)] for (u,v) in g.outgoing_edges([X],labels=None)])-p.sum([flow[i][(u,X)] for (u,v) in g.incoming_edges([X],labels=None)])
7356
7357
# The flow leaving x
7358
flow_leaving = lambda i,X : p.sum([flow[i][(uu,vv)] for (uu,vv) in g.outgoing_edges([X],labels=None)])
7359
7360
# the flow to consider when defining the capacity contraints
7361
capacity_sum = lambda i,u,v : flow[i][(u,v)]
7362
7363
else:
7364
# This function return the balance of flow at X
7365
flow_sum=lambda i,X:p.sum([flow[i][(X,v)]-flow[i][(v,X)] for v in g[X]])
7366
7367
# The flow leaving x
7368
flow_leaving = lambda i, X : p.sum([flow[i][(X,vv)] for vv in g[X]])
7369
7370
# the flow to consider when defining the capacity contraints
7371
capacity_sum = lambda i,u,v : flow[i][(u,v)] + flow[i][(v,u)]
7372
7373
7374
# Flow constraints
7375
for i,(s,t,l) in enumerate(terminals):
7376
for v in g:
7377
if v == s:
7378
p.add_constraint(flow_sum(i,v),min=l,max=l)
7379
elif v == t:
7380
p.add_constraint(flow_sum(i,v),min=-l,max=-l)
7381
else:
7382
p.add_constraint(flow_sum(i,v),min=0,max=0)
7383
7384
7385
# Capacity constraints
7386
for (u,v,w) in g.edges():
7387
p.add_constraint(p.sum([capacity_sum(i,u,v) for i in range(len(terminals))]),max=capacity(w))
7388
7389
7390
if vertex_bound:
7391
7392
# Any vertex
7393
for v in g.vertices():
7394
7395
# which is an endpoint
7396
if v in set_terminals:
7397
for i,(s,t,_) in enumerate(terminals):
7398
7399
# only tolerates the commodities of which it is an endpoint
7400
if not (v==s or v==t):
7401
p.add_constraint(flow_leaving(i,v), max = 0)
7402
7403
# which is not an endpoint
7404
else:
7405
# can stand at most 1 unit of flow through itself
7406
p.add_constraint(p.sum([flow_leaving(i,v) for i in range(len(terminals))]), max = 1)
7407
7408
p.set_objective(None)
7409
7410
if integer:
7411
p.set_integer(flow)
7412
7413
from sage.numerical.mip import MIPSolverException
7414
7415
try:
7416
obj=p.solve(log = verbose)
7417
except MIPSolverException:
7418
raise ValueError("The multiflow problem has no solution")
7419
7420
flow=p.get_values(flow)
7421
7422
# building clean flow digraphs
7423
flow_graphs = [g._build_flow_graph(flow[i], integer=integer) for i in range(len(terminals))]
7424
7425
# which could be .. graphs !
7426
if not self.is_directed():
7427
from sage.graphs.graph import Graph
7428
flow_graphs = map(Graph, flow_graphs)
7429
7430
return flow_graphs
7431
7432
def _build_flow_graph(self, flow, integer):
7433
r"""
7434
Builds a "clean" flow graph
7435
7436
It build it, then looks for circuits and removes them
7437
7438
INPUT:
7439
7440
- ``flow`` -- a dictionary associating positive numerical values
7441
to edges
7442
7443
- ``integer`` (boolean) -- whether the values from ``flow`` are the solution
7444
of an integer flow. In this case, a value of less than .5 is assumed to be 0
7445
7446
7447
EXAMPLE:
7448
7449
This method is tested in ``flow`` and ``multicommodity_flow``::
7450
7451
sage: g = Graph()
7452
sage: g.add_edge(0,1)
7453
sage: f = g._build_flow_graph({(0,1):1}, True)
7454
7455
The method removes zero-cost flow cycles and updates the values accordingly::
7456
7457
sage: g = digraphs.DeBruijn(2,3)
7458
sage: flow = {('001','010'):1,('010','100'):1,('010','101'):1,('101','010'):1}
7459
sage: flow_graph = g._build_flow_graph(flow,True)
7460
sage: flow_graph.edges()
7461
[('001', '010', 1), ('010', '100', 1)]
7462
sage: flow = {('001','010'):2,('010','101'):3,('101','011'):2,('101','010'):1}
7463
sage: flow_graph = g._build_flow_graph(flow,True)
7464
sage: flow_graph.edges()
7465
[('001', '010', 2), ('010', '101', 2), ('101', '011', 2)]
7466
7467
Isolated zero-cost flow cycles are also removed::
7468
7469
sage: g = digraphs.DeBruijn(2,3)
7470
sage: flow = {('000','001'):1,('010','101'):1,('101','010'):1}
7471
sage: flow_graph = g._build_flow_graph(flow,True)
7472
sage: flow_graph.edges()
7473
[('000', '001', 1)]
7474
"""
7475
7476
from sage.graphs.digraph import DiGraph
7477
g = DiGraph()
7478
7479
# add significant edges
7480
for (u,v),l in flow.iteritems():
7481
if l > 0 and not (integer and l<.5):
7482
g.add_edge(u,v,l)
7483
7484
# stupid way to find Cycles. Will be fixed by #8932
7485
# for any vertex, for any of its in-neighbors, tried to find a cycle
7486
for v in g:
7487
for u in g.neighbor_in_iterator(v):
7488
7489
# the edge from u to v could have been removed in a previous iteration
7490
if not g.has_edge(u,v):
7491
break
7492
sp = g.shortest_path(v,u)
7493
if sp != []:
7494
7495
#find the minimm value along the cycle.
7496
m = g.edge_label(u,v)
7497
for i in range(len(sp)-1):
7498
m = min(m,g.edge_label(sp[i],sp[i+1]))
7499
7500
# removes it from all the edges of the cycle
7501
sp.append(v)
7502
for i in range(len(sp)-1):
7503
l = g.edge_label(sp[i],sp[i+1]) - m
7504
7505
# an edge with flow 0 is removed
7506
if l == 0:
7507
g.delete_edge(sp[i],sp[i+1])
7508
else:
7509
g.set_edge_label(sp[i],sp[i+1],l)
7510
7511
# if integer is set, round values and deletes zeroes
7512
if integer:
7513
for (u,v,l) in g.edges():
7514
if l<.5:
7515
g.delete_edge(u,v)
7516
else:
7517
g.set_edge_label(u,v, int(round(l)))
7518
7519
# returning a graph with the same embedding, the corresponding name, etc ...
7520
h = self.subgraph(edges=[])
7521
h.delete_vertices([v for v in self if (v not in g) or g.degree(v)==0])
7522
h.add_edges(g.edges())
7523
7524
return h
7525
7526
def disjoint_routed_paths(self,pairs, solver=None, verbose=0):
7527
r"""
7528
Returns a set of disjoint routed paths.
7529
7530
Given a set of pairs `(s_i,t_i)`, a set
7531
of disjoint routed paths is a set of
7532
`s_i-t_i` paths which can interset at their endpoints
7533
and are vertex-disjoint otherwise.
7534
7535
INPUT:
7536
7537
- ``pairs`` -- list of pairs of vertices
7538
7539
- ``solver`` -- Specify a Linear Program solver to be used.
7540
If set to ``None``, the default one is used.
7541
function of ``MixedIntegerLinearProgram``. See the documentation of ``MixedIntegerLinearProgram.solve``
7542
for more informations.
7543
7544
- ``verbose`` (integer) -- sets the level of verbosity. Set to `0`
7545
by default (quiet).
7546
7547
EXAMPLE:
7548
7549
Given a grid, finding two vertex-disjoint
7550
paths, the first one from the top-left corner
7551
to the bottom-left corner, and the second from
7552
the top-right corner to the bottom-right corner
7553
is easy ::
7554
7555
sage: g = graphs.GridGraph([5,5])
7556
sage: p1,p2 = g.disjoint_routed_paths( [((0,0), (0,4)), ((4,4), (4,0))])
7557
7558
Though there is obviously no solution to the problem
7559
in which each corner is sending information to the opposite
7560
one::
7561
7562
sage: g = graphs.GridGraph([5,5])
7563
sage: p1,p2 = g.disjoint_routed_paths( [((0,0), (4,4)), ((0,4), (4,0))])
7564
Traceback (most recent call last):
7565
...
7566
ValueError: The disjoint routed paths do not exist.
7567
"""
7568
7569
try:
7570
return self.multicommodity_flow(pairs, vertex_bound = True, solver=solver, verbose=verbose)
7571
except ValueError:
7572
raise ValueError("The disjoint routed paths do not exist.")
7573
7574
def edge_disjoint_paths(self, s, t, method = "FF"):
7575
r"""
7576
Returns a list of edge-disjoint paths between two
7577
vertices as given by Menger's theorem.
7578
7579
The edge version of Menger's theorem asserts that the size
7580
of the minimum edge cut between two vertices `s` and`t`
7581
(the minimum number of edges whose removal disconnects `s`
7582
and `t`) is equal to the maximum number of pairwise
7583
edge-independent paths from `s` to `t`.
7584
7585
This function returns a list of such paths.
7586
7587
INPUT:
7588
7589
- ``method`` -- There are currently two different
7590
implementations of this method :
7591
7592
* If ``method = "FF"`` (default), a Python implementation of the
7593
Ford-Fulkerson algorithm is used.
7594
7595
* If ``method = "LP"``, the flow problem is solved using
7596
Linear Programming.
7597
7598
.. NOTE::
7599
7600
This function is topological: it does not take the eventual
7601
weights of the edges into account.
7602
7603
EXAMPLE:
7604
7605
In a complete bipartite graph ::
7606
7607
sage: g = graphs.CompleteBipartiteGraph(2,3)
7608
sage: g.edge_disjoint_paths(0,1)
7609
[[0, 2, 1], [0, 3, 1], [0, 4, 1]]
7610
"""
7611
7612
[obj, flow_graph] = self.flow(s,t,value_only=False, integer=True, use_edge_labels=False, method=method)
7613
7614
paths = []
7615
7616
while True:
7617
path = flow_graph.shortest_path(s,t)
7618
if not path:
7619
break
7620
v = s
7621
edges = []
7622
for w in path:
7623
edges.append((v,w))
7624
v=w
7625
flow_graph.delete_edges(edges)
7626
paths.append(path)
7627
7628
return paths
7629
7630
def vertex_disjoint_paths(self, s, t):
7631
r"""
7632
Returns a list of vertex-disjoint paths between two
7633
vertices as given by Menger's theorem.
7634
7635
The vertex version of Menger's theorem asserts that the size
7636
of the minimum vertex cut between two vertices `s` and`t`
7637
(the minimum number of vertices whose removal disconnects `s`
7638
and `t`) is equal to the maximum number of pairwise
7639
vertex-independent paths from `s` to `t`.
7640
7641
This function returns a list of such paths.
7642
7643
EXAMPLE:
7644
7645
In a complete bipartite graph ::
7646
7647
sage: g = graphs.CompleteBipartiteGraph(2,3)
7648
sage: g.vertex_disjoint_paths(0,1)
7649
[[0, 2, 1], [0, 3, 1], [0, 4, 1]]
7650
"""
7651
7652
[obj, flow_graph] = self.flow(s,t,value_only=False, integer=True, use_edge_labels=False, vertex_bound=True)
7653
7654
paths = []
7655
7656
while True:
7657
path = flow_graph.shortest_path(s,t)
7658
if not path:
7659
break
7660
flow_graph.delete_vertices(path[1:-1])
7661
paths.append(path)
7662
7663
return paths
7664
7665
def dominating_set(self, independent=False, value_only=False, solver=None, verbose=0):
7666
r"""
7667
Returns a minimum dominating set of the graph
7668
represented by the list of its vertices. For more information, see the
7669
`Wikipedia article on dominating sets
7670
<http://en.wikipedia.org/wiki/Dominating_set>`_.
7671
7672
A minimum dominating set `S` of a graph `G` is
7673
a set of its vertices of minimal cardinality such
7674
that any vertex of `G` is in `S` or has one of its neighbors
7675
in `S`.
7676
7677
As an optimization problem, it can be expressed as:
7678
7679
.. MATH::
7680
7681
\mbox{Minimize : }&\sum_{v\in G} b_v\\
7682
\mbox{Such that : }&\forall v \in G, b_v+\sum_{(u,v)\in G.edges()} b_u\geq 1\\
7683
&\forall x\in G, b_x\mbox{ is a binary variable}
7684
7685
INPUT:
7686
7687
- ``independent`` -- boolean (default: ``False``). If
7688
``independent=True``, computes a minimum independent dominating set.
7689
7690
- ``value_only`` -- boolean (default: ``False``)
7691
7692
- If ``True``, only the cardinality of a minimum
7693
dominating set is returned.
7694
7695
- If ``False`` (default), a minimum dominating set
7696
is returned as the list of its vertices.
7697
7698
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
7699
solver to be used. If set to ``None``, the default one is used. For
7700
more information on LP solvers and which default solver is used, see
7701
the method
7702
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
7703
of the class
7704
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
7705
7706
- ``verbose`` -- integer (default: ``0``). Sets the level of
7707
verbosity. Set to 0 by default, which means quiet.
7708
7709
EXAMPLES:
7710
7711
A basic illustration on a ``PappusGraph``::
7712
7713
sage: g=graphs.PappusGraph()
7714
sage: g.dominating_set(value_only=True)
7715
5
7716
7717
If we build a graph from two disjoint stars, then link their centers
7718
we will find a difference between the cardinality of an independent set
7719
and a stable independent set::
7720
7721
sage: g = 2 * graphs.StarGraph(5)
7722
sage: g.add_edge(0,6)
7723
sage: len(g.dominating_set())
7724
2
7725
sage: len(g.dominating_set(independent=True))
7726
6
7727
7728
"""
7729
from sage.numerical.mip import MixedIntegerLinearProgram
7730
g=self
7731
p=MixedIntegerLinearProgram(maximization=False, solver=solver)
7732
b=p.new_variable()
7733
7734
# For any vertex v, one of its neighbors or v itself is in
7735
# the minimum dominating set
7736
for v in g.vertices():
7737
p.add_constraint(b[v]+p.sum([b[u] for u in g.neighbors(v)]),min=1)
7738
7739
7740
if independent:
7741
# no two adjacent vertices are in the set
7742
for (u,v) in g.edges(labels=None):
7743
p.add_constraint(b[u]+b[v],max=1)
7744
7745
# Minimizes the number of vertices used
7746
p.set_objective(p.sum([b[v] for v in g.vertices()]))
7747
7748
p.set_integer(b)
7749
7750
if value_only:
7751
return Integer(round(p.solve(objective_only=True, log=verbose)))
7752
else:
7753
p.solve(log=verbose)
7754
b=p.get_values(b)
7755
return [v for v in g.vertices() if b[v]==1]
7756
7757
def edge_connectivity(self, value_only=True, use_edge_labels=False, vertices=False, solver=None, verbose=0):
7758
r"""
7759
Returns the edge connectivity of the graph. For more information, see
7760
the
7761
`Wikipedia article on connectivity
7762
<http://en.wikipedia.org/wiki/Connectivity_(graph_theory)>`_.
7763
7764
.. NOTE::
7765
7766
When the graph is a directed graph, this method actually computes
7767
the *strong* connectivity, (i.e. a directed graph is strongly
7768
`k`-connected if there are `k` disjoint paths between any two
7769
vertices `u, v`). If you do not want to consider strong
7770
connectivity, the best is probably to convert your ``DiGraph``
7771
object to a ``Graph`` object, and compute the connectivity of this
7772
other graph.
7773
7774
INPUT:
7775
7776
- ``value_only`` -- boolean (default: ``True``)
7777
7778
- When set to ``True`` (default), only the value is returned.
7779
7780
- When set to ``False``, both the value and a minimum edge cut
7781
are returned.
7782
7783
- ``use_edge_labels`` -- boolean (default: ``False``)
7784
7785
- When set to ``True``, computes a weighted minimum cut
7786
where each edge has a weight defined by its label. (If
7787
an edge has no label, `1` is assumed.)
7788
7789
- When set to ``False``, each edge has weight `1`.
7790
7791
- ``vertices`` -- boolean (default: ``False``)
7792
7793
- When set to ``True``, also returns the two sets of
7794
vertices that are disconnected by the cut. Implies
7795
``value_only=False``.
7796
7797
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
7798
solver to be used. If set to ``None``, the default one is used. For
7799
more information on LP solvers and which default solver is used, see
7800
the method
7801
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
7802
of the class
7803
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
7804
7805
- ``verbose`` -- integer (default: ``0``). Sets the level of
7806
verbosity. Set to 0 by default, which means quiet.
7807
7808
EXAMPLES:
7809
7810
A basic application on the PappusGraph::
7811
7812
sage: g = graphs.PappusGraph()
7813
sage: g.edge_connectivity()
7814
3
7815
7816
The edge connectivity of a complete graph ( and of a random graph )
7817
is its minimum degree, and one of the two parts of the bipartition
7818
is reduced to only one vertex. The cutedges isomorphic to a
7819
Star graph::
7820
7821
sage: g = graphs.CompleteGraph(5)
7822
sage: [ value, edges, [ setA, setB ]] = g.edge_connectivity(vertices=True)
7823
sage: print value
7824
4
7825
sage: len(setA) == 1 or len(setB) == 1
7826
True
7827
sage: cut = Graph()
7828
sage: cut.add_edges(edges)
7829
sage: cut.is_isomorphic(graphs.StarGraph(4))
7830
True
7831
7832
Even if obviously in any graph we know that the edge connectivity
7833
is less than the minimum degree of the graph::
7834
7835
sage: g = graphs.RandomGNP(10,.3)
7836
sage: min(g.degree()) >= g.edge_connectivity()
7837
True
7838
7839
If we build a tree then assign to its edges a random value, the
7840
minimum cut will be the edge with minimum value::
7841
7842
sage: g = graphs.RandomGNP(15,.5)
7843
sage: tree = Graph()
7844
sage: tree.add_edges(g.min_spanning_tree())
7845
sage: for u,v in tree.edge_iterator(labels=None):
7846
... tree.set_edge_label(u,v,random())
7847
sage: minimum = min([l for u,v,l in tree.edge_iterator()])
7848
sage: [value, [(u,v,l)]] = tree.edge_connectivity(value_only=False, use_edge_labels=True)
7849
sage: l == minimum
7850
True
7851
7852
When ``value_only = True``, this function is optimized for small
7853
connectivity values and does not need to build a linear program.
7854
7855
It is the case for connected graphs which are not
7856
connected ::
7857
7858
sage: g = 2 * graphs.PetersenGraph()
7859
sage: g.edge_connectivity()
7860
0.0
7861
7862
Or if they are just 1-connected ::
7863
7864
sage: g = graphs.PathGraph(10)
7865
sage: g.edge_connectivity()
7866
1.0
7867
7868
For directed graphs, the strong connectivity is tested
7869
through the dedicated function ::
7870
7871
sage: g = digraphs.ButterflyGraph(3)
7872
sage: g.edge_connectivity()
7873
0.0
7874
"""
7875
self._scream_if_not_simple(allow_loops=True)
7876
g=self
7877
7878
if vertices:
7879
value_only=False
7880
7881
if use_edge_labels:
7882
from sage.rings.real_mpfr import RR
7883
weight=lambda x: x if x in RR else 1
7884
else:
7885
weight=lambda x: 1
7886
7887
7888
# Better methods for small connectivity tests,
7889
# when one is not interested in cuts...
7890
if value_only and not use_edge_labels:
7891
7892
if self.is_directed():
7893
if not self.is_strongly_connected():
7894
return 0.0
7895
7896
else:
7897
if not self.is_connected():
7898
return 0.0
7899
7900
h = self.strong_orientation()
7901
if not h.is_strongly_connected():
7902
return 1.0
7903
7904
7905
if g.is_directed():
7906
reorder_edge = lambda x,y : (x,y)
7907
else:
7908
reorder_edge = lambda x,y : (x,y) if x<= y else (y,x)
7909
7910
from sage.numerical.mip import MixedIntegerLinearProgram
7911
7912
p = MixedIntegerLinearProgram(maximization=False, solver=solver)
7913
7914
in_set = p.new_variable(dim=2)
7915
in_cut = p.new_variable(dim=1)
7916
7917
7918
# A vertex has to be in some set
7919
for v in g:
7920
p.add_constraint(in_set[0][v]+in_set[1][v],max=1,min=1)
7921
7922
# There is no empty set
7923
p.add_constraint(p.sum([in_set[1][v] for v in g]),min=1)
7924
p.add_constraint(p.sum([in_set[0][v] for v in g]),min=1)
7925
7926
if g.is_directed():
7927
# There is no edge from set 0 to set 1 which
7928
# is not in the cut
7929
for (u,v) in g.edge_iterator(labels=None):
7930
p.add_constraint(in_set[0][u] + in_set[1][v] - in_cut[(u,v)], max = 1)
7931
else:
7932
7933
# Two adjacent vertices are in different sets if and only if
7934
# the edge between them is in the cut
7935
7936
for (u,v) in g.edge_iterator(labels=None):
7937
p.add_constraint(in_set[0][u]+in_set[1][v]-in_cut[reorder_edge(u,v)],max=1)
7938
p.add_constraint(in_set[1][u]+in_set[0][v]-in_cut[reorder_edge(u,v)],max=1)
7939
7940
7941
p.set_binary(in_set)
7942
p.set_binary(in_cut)
7943
7944
p.set_objective(p.sum([weight(l ) * in_cut[reorder_edge(u,v)] for (u,v,l) in g.edge_iterator()]))
7945
7946
obj = p.solve(objective_only=value_only, log=verbose)
7947
7948
if use_edge_labels is False:
7949
obj = Integer(round(obj))
7950
7951
if value_only:
7952
return obj
7953
7954
else:
7955
val = [obj]
7956
7957
in_cut = p.get_values(in_cut)
7958
in_set = p.get_values(in_set)
7959
7960
edges = []
7961
for (u,v,l) in g.edge_iterator():
7962
if in_cut[reorder_edge(u,v)] == 1:
7963
edges.append((u,v,l))
7964
7965
val.append(edges)
7966
7967
if vertices:
7968
a = []
7969
b = []
7970
for v in g:
7971
if in_set[0][v] == 1:
7972
a.append(v)
7973
else:
7974
b.append(v)
7975
val.append([a,b])
7976
7977
return val
7978
7979
def vertex_connectivity(self, value_only=True, sets=False, solver=None, verbose=0):
7980
r"""
7981
Returns the vertex connectivity of the graph. For more information,
7982
see the
7983
`Wikipedia article on connectivity
7984
<http://en.wikipedia.org/wiki/Connectivity_(graph_theory)>`_.
7985
7986
.. NOTE::
7987
7988
* When the graph is a directed graph, this method actually computes
7989
the *strong* connectivity, (i.e. a directed graph is strongly
7990
`k`-connected if there are `k` disjoint paths between any two
7991
vertices `u, v`). If you do not want to consider strong
7992
connectivity, the best is probably to convert your ``DiGraph``
7993
object to a ``Graph`` object, and compute the connectivity of this
7994
other graph.
7995
7996
* By convention, a complete graph on `n` vertices is `n-1`
7997
connected. In this case, no certificate can be given as there is
7998
no pair of vertices split by a cut of size `k-1`. For this reason,
7999
the certificates returned in this situation are empty.
8000
8001
INPUT:
8002
8003
- ``value_only`` -- boolean (default: ``True``)
8004
8005
- When set to ``True`` (default), only the value is returned.
8006
8007
- When set to ``False`` , both the value and a minimum vertex cut are
8008
returned.
8009
8010
- ``sets`` -- boolean (default: ``False``)
8011
8012
- When set to ``True``, also returns the two sets of
8013
vertices that are disconnected by the cut.
8014
Implies ``value_only=False``
8015
8016
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
8017
solver to be used. If set to ``None``, the default one is used. For
8018
more information on LP solvers and which default solver is used, see
8019
the method
8020
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
8021
of the class
8022
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
8023
8024
- ``verbose`` -- integer (default: ``0``). Sets the level of
8025
verbosity. Set to 0 by default, which means quiet.
8026
8027
EXAMPLES:
8028
8029
A basic application on a ``PappusGraph``::
8030
8031
sage: g=graphs.PappusGraph()
8032
sage: g.vertex_connectivity()
8033
3
8034
8035
In a grid, the vertex connectivity is equal to the
8036
minimum degree, in which case one of the two sets is
8037
of cardinality `1`::
8038
8039
sage: g = graphs.GridGraph([ 3,3 ])
8040
sage: [value, cut, [ setA, setB ]] = g.vertex_connectivity(sets=True)
8041
sage: len(setA) == 1 or len(setB) == 1
8042
True
8043
8044
A vertex cut in a tree is any internal vertex::
8045
8046
sage: g = graphs.RandomGNP(15,.5)
8047
sage: tree = Graph()
8048
sage: tree.add_edges(g.min_spanning_tree())
8049
sage: [val, [cut_vertex]] = tree.vertex_connectivity(value_only=False)
8050
sage: tree.degree(cut_vertex) > 1
8051
True
8052
8053
When ``value_only = True``, this function is optimized for small
8054
connectivity values and does not need to build a linear program.
8055
8056
It is the case for connected graphs which are not connected::
8057
8058
sage: g = 2 * graphs.PetersenGraph()
8059
sage: g.vertex_connectivity()
8060
0
8061
8062
Or if they are just 1-connected::
8063
8064
sage: g = graphs.PathGraph(10)
8065
sage: g.vertex_connectivity()
8066
1
8067
8068
For directed graphs, the strong connectivity is tested
8069
through the dedicated function::
8070
8071
sage: g = digraphs.ButterflyGraph(3)
8072
sage: g.vertex_connectivity()
8073
0
8074
8075
A complete graph on `10` vertices is `9`-connected::
8076
8077
sage: g = graphs.CompleteGraph(10)
8078
sage: g.vertex_connectivity()
8079
9
8080
8081
A complete digraph on `10` vertices is `9`-connected::
8082
8083
sage: g = DiGraph(graphs.CompleteGraph(10))
8084
sage: g.vertex_connectivity()
8085
9
8086
"""
8087
g=self
8088
8089
if sets:
8090
value_only=False
8091
8092
# When the graph is complete, the MILP below is infeasible.
8093
if ((not g.is_directed() and g.is_clique())
8094
or
8095
(g.is_directed() and g.size() >= (g.order()-1)*g.order() and
8096
((not g.allows_loops() and not g.allows_multiple_edges())
8097
or
8098
all(g.has_edge(u,v) for u in g for v in g if v != u)))):
8099
if value_only:
8100
return g.order()-1
8101
elif not sets:
8102
return g.order()-1, []
8103
else:
8104
return g.order()-1, [], [[],[]]
8105
8106
if value_only:
8107
if self.is_directed():
8108
if not self.is_strongly_connected():
8109
return 0
8110
8111
else:
8112
if not self.is_connected():
8113
return 0
8114
8115
if len(self.blocks_and_cut_vertices()[0]) > 1:
8116
return 1
8117
8118
if g.is_directed():
8119
reorder_edge = lambda x,y : (x,y)
8120
else:
8121
reorder_edge = lambda x,y : (x,y) if x<= y else (y,x)
8122
8123
from sage.numerical.mip import MixedIntegerLinearProgram
8124
8125
p = MixedIntegerLinearProgram(maximization=False, solver=solver)
8126
8127
# Sets 0 and 2 are "real" sets while set 1 represents the cut
8128
in_set = p.new_variable(dim=2)
8129
8130
8131
# A vertex has to be in some set
8132
for v in g:
8133
p.add_constraint(in_set[0][v]+in_set[1][v]+in_set[2][v],max=1,min=1)
8134
8135
# There is no empty set
8136
p.add_constraint(p.sum([in_set[0][v] for v in g]),min=1)
8137
p.add_constraint(p.sum([in_set[2][v] for v in g]),min=1)
8138
8139
if g.is_directed():
8140
# There is no edge from set 0 to set 1 which
8141
# is not in the cut
8142
for (u,v) in g.edge_iterator(labels=None):
8143
p.add_constraint(in_set[0][u] + in_set[2][v], max = 1)
8144
else:
8145
8146
# Two adjacent vertices are in different sets if and only if
8147
# the edge between them is in the cut
8148
8149
for (u,v) in g.edge_iterator(labels=None):
8150
p.add_constraint(in_set[0][u]+in_set[2][v],max=1)
8151
p.add_constraint(in_set[2][u]+in_set[0][v],max=1)
8152
8153
p.set_binary(in_set)
8154
8155
p.set_objective(p.sum([in_set[1][v] for v in g]))
8156
8157
if value_only:
8158
return Integer(round(p.solve(objective_only=True, log=verbose)))
8159
else:
8160
val = [Integer(round(p.solve(log=verbose)))]
8161
8162
in_set = p.get_values(in_set)
8163
8164
cut = []
8165
a = []
8166
b = []
8167
8168
for v in g:
8169
if in_set[0][v] == 1:
8170
a.append(v)
8171
elif in_set[1][v]==1:
8172
cut.append(v)
8173
else:
8174
b.append(v)
8175
8176
val.append(cut)
8177
8178
if sets:
8179
val.append([a,b])
8180
8181
return val
8182
8183
### Vertex handlers
8184
8185
def add_vertex(self, name=None):
8186
"""
8187
Creates an isolated vertex. If the vertex already exists, then
8188
nothing is done.
8189
8190
INPUT:
8191
8192
- ``name`` - Name of the new vertex. If no name is
8193
specified, then the vertex will be represented by the least integer
8194
not already representing a vertex. Name must be an immutable
8195
object, and cannot be None.
8196
8197
As it is implemented now, if a graph `G` has a large number
8198
of vertices with numeric labels, then G.add_vertex() could
8199
potentially be slow, if name is None.
8200
8201
OUTPUT:
8202
8203
If ``name``=``None``, the new vertex name is returned. ``None`` otherwise.
8204
8205
EXAMPLES::
8206
8207
sage: G = Graph(); G.add_vertex(); G
8208
0
8209
Graph on 1 vertex
8210
8211
::
8212
8213
sage: D = DiGraph(); D.add_vertex(); D
8214
0
8215
Digraph on 1 vertex
8216
8217
"""
8218
return self._backend.add_vertex(name)
8219
8220
def add_vertices(self, vertices):
8221
"""
8222
Add vertices to the (di)graph from an iterable container of
8223
vertices. Vertices that already exist in the graph will not be
8224
added again.
8225
8226
INPUT:
8227
8228
- ``vertices``: iterator of vertex labels. A new label is created, used and returned in
8229
the output list for all ``None`` values in ``vertices``.
8230
8231
OUTPUT:
8232
8233
Generated names of new vertices if there is at least one ``None`` value
8234
present in ``vertices``. ``None`` otherwise.
8235
8236
EXAMPLES::
8237
8238
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]}
8239
sage: G = Graph(d)
8240
sage: G.add_vertices([10,11,12])
8241
sage: G.vertices()
8242
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
8243
sage: G.add_vertices(graphs.CycleGraph(25).vertices())
8244
sage: G.vertices()
8245
[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]
8246
8247
::
8248
8249
sage: G = Graph()
8250
sage: G.add_vertices([1,2,3])
8251
sage: G.add_vertices([4,None,None,5])
8252
[0, 6]
8253
8254
"""
8255
return self._backend.add_vertices(vertices)
8256
8257
def delete_vertex(self, vertex, in_order=False):
8258
"""
8259
Deletes vertex, removing all incident edges. Deleting a
8260
non-existent vertex will raise an exception.
8261
8262
INPUT:
8263
8264
8265
- ``in_order`` - (default False) If True, this
8266
deletes the ith vertex in the sorted list of vertices, i.e.
8267
G.vertices()[i]
8268
8269
8270
EXAMPLES::
8271
8272
sage: G = Graph(graphs.WheelGraph(9))
8273
sage: G.delete_vertex(0); G.show()
8274
8275
::
8276
8277
sage: D = DiGraph({0:[1,2,3,4,5],1:[2],2:[3],3:[4],4:[5],5:[1]})
8278
sage: D.delete_vertex(0); D
8279
Digraph on 5 vertices
8280
sage: D.vertices()
8281
[1, 2, 3, 4, 5]
8282
sage: D.delete_vertex(0)
8283
Traceback (most recent call last):
8284
...
8285
RuntimeError: Vertex (0) not in the graph.
8286
8287
::
8288
8289
sage: G = graphs.CompleteGraph(4).line_graph(labels=False)
8290
sage: G.vertices()
8291
[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]
8292
sage: G.delete_vertex(0, in_order=True)
8293
sage: G.vertices()
8294
[(0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]
8295
sage: G = graphs.PathGraph(5)
8296
sage: G.set_vertices({0: 'no delete', 1: 'delete'})
8297
sage: G.set_boundary([1,2])
8298
sage: G.delete_vertex(1)
8299
sage: G.get_vertices()
8300
{0: 'no delete', 2: None, 3: None, 4: None}
8301
sage: G.get_boundary()
8302
[2]
8303
sage: G.get_pos()
8304
{0: (0, 0), 2: (2, 0), 3: (3, 0), 4: (4, 0)}
8305
"""
8306
if in_order:
8307
vertex = self.vertices()[vertex]
8308
if vertex not in self:
8309
raise RuntimeError("Vertex (%s) not in the graph."%str(vertex))
8310
8311
self._backend.del_vertex(vertex)
8312
attributes_to_update = ('_pos', '_assoc', '_embedding')
8313
for attr in attributes_to_update:
8314
if hasattr(self, attr) and getattr(self, attr) is not None:
8315
getattr(self, attr).pop(vertex, None)
8316
self._boundary = [v for v in self._boundary if v != vertex]
8317
8318
def delete_vertices(self, vertices):
8319
"""
8320
Remove vertices from the (di)graph taken from an iterable container
8321
of vertices. Deleting a non-existent vertex will raise an
8322
exception.
8323
8324
EXAMPLES::
8325
8326
sage: D = DiGraph({0:[1,2,3,4,5],1:[2],2:[3],3:[4],4:[5],5:[1]})
8327
sage: D.delete_vertices([1,2,3,4,5]); D
8328
Digraph on 1 vertex
8329
sage: D.vertices()
8330
[0]
8331
sage: D.delete_vertices([1])
8332
Traceback (most recent call last):
8333
...
8334
RuntimeError: Vertex (1) not in the graph.
8335
8336
"""
8337
for vertex in vertices:
8338
if vertex not in self:
8339
raise RuntimeError("Vertex (%s) not in the graph."%str(vertex))
8340
8341
self._backend.del_vertices(vertices)
8342
attributes_to_update = ('_pos', '_assoc', '_embedding')
8343
for attr in attributes_to_update:
8344
if hasattr(self, attr) and getattr(self, attr) is not None:
8345
attr_dict = getattr(self, attr)
8346
for vertex in vertices:
8347
attr_dict.pop(vertex, None)
8348
8349
self._boundary = [v for v in self._boundary if v not in vertices]
8350
8351
def has_vertex(self, vertex):
8352
"""
8353
Return True if vertex is one of the vertices of this graph.
8354
8355
INPUT:
8356
8357
8358
- ``vertex`` - an integer
8359
8360
8361
OUTPUT:
8362
8363
8364
- ``bool`` - True or False
8365
8366
8367
EXAMPLES::
8368
8369
sage: g = Graph({0:[1,2,3], 2:[4]}); g
8370
Graph on 5 vertices
8371
sage: 2 in g
8372
True
8373
sage: 10 in g
8374
False
8375
sage: graphs.PetersenGraph().has_vertex(99)
8376
False
8377
"""
8378
try:
8379
hash(vertex)
8380
except Exception:
8381
return False
8382
return self._backend.has_vertex(vertex)
8383
8384
__contains__ = has_vertex
8385
8386
def random_vertex(self, **kwds):
8387
r"""
8388
Returns a random vertex of self.
8389
8390
INPUT:
8391
8392
- ``**kwds`` - arguments to be passed down to the
8393
``vertex_iterator`` method.
8394
8395
EXAMPLE:
8396
8397
The returned value is a vertex of self::
8398
8399
sage: g = graphs.PetersenGraph()
8400
sage: v = g.random_vertex()
8401
sage: v in g
8402
True
8403
"""
8404
from sage.misc.prandom import randint
8405
it = self.vertex_iterator(**kwds)
8406
for i in xrange(0, randint(0, self.order() - 1)):
8407
it.next()
8408
return it.next()
8409
8410
def random_edge(self,**kwds):
8411
r"""
8412
Returns a random edge of self.
8413
8414
INPUT:
8415
8416
- ``**kwds`` - arguments to be passed down to the
8417
``edge_iterator`` method.
8418
8419
EXAMPLE:
8420
8421
The returned value is an edge of self::
8422
8423
sage: g = graphs.PetersenGraph()
8424
sage: u,v = g.random_edge(labels=False)
8425
sage: g.has_edge(u,v)
8426
True
8427
8428
As the ``edges()`` method would, this function returns
8429
by default a triple ``(u,v,l)`` of values, in which
8430
``l`` is the label of edge `(u,v)`::
8431
8432
sage: g.random_edge()
8433
(3, 4, None)
8434
"""
8435
from sage.misc.prandom import randint
8436
it = self.edge_iterator(**kwds)
8437
for i in xrange(0, randint(0, self.size() - 1)):
8438
it.next()
8439
return it.next()
8440
8441
def vertex_boundary(self, vertices1, vertices2=None):
8442
"""
8443
Returns a list of all vertices in the external boundary of
8444
vertices1, intersected with vertices2. If vertices2 is None, then
8445
vertices2 is the complement of vertices1. This is much faster if
8446
vertices1 is smaller than vertices2.
8447
8448
The external boundary of a set of vertices is the union of the
8449
neighborhoods of each vertex in the set. Note that in this
8450
implementation, since vertices2 defaults to the complement of
8451
vertices1, if a vertex `v` has a loop, then
8452
vertex_boundary(v) will not contain `v`.
8453
8454
In a digraph, the external boundary of a vertex v are those
8455
vertices u with an arc (v, u).
8456
8457
EXAMPLES::
8458
8459
sage: G = graphs.CubeGraph(4)
8460
sage: l = ['0111', '0000', '0001', '0011', '0010', '0101', '0100', '1111', '1101', '1011', '1001']
8461
sage: G.vertex_boundary(['0000', '1111'], l)
8462
['0111', '0001', '0010', '0100', '1101', '1011']
8463
8464
::
8465
8466
sage: D = DiGraph({0:[1,2], 3:[0]})
8467
sage: D.vertex_boundary([0])
8468
[1, 2]
8469
"""
8470
vertices1 = [v for v in vertices1 if v in self]
8471
output = set()
8472
if self._directed:
8473
for v in vertices1:
8474
output.update(self.neighbor_out_iterator(v))
8475
else:
8476
for v in vertices1:
8477
output.update(self.neighbor_iterator(v))
8478
if vertices2 is not None:
8479
output.intersection_update(vertices2)
8480
return list(output)
8481
8482
def set_vertices(self, vertex_dict):
8483
"""
8484
Associate arbitrary objects with each vertex, via an association
8485
dictionary.
8486
8487
INPUT:
8488
8489
8490
- ``vertex_dict`` - the association dictionary
8491
8492
8493
EXAMPLES::
8494
8495
sage: d = {0 : graphs.DodecahedralGraph(), 1 : graphs.FlowerSnark(), 2 : graphs.MoebiusKantorGraph(), 3 : graphs.PetersenGraph() }
8496
sage: d[2]
8497
Moebius-Kantor Graph: Graph on 16 vertices
8498
sage: T = graphs.TetrahedralGraph()
8499
sage: T.vertices()
8500
[0, 1, 2, 3]
8501
sage: T.set_vertices(d)
8502
sage: T.get_vertex(1)
8503
Flower Snark: Graph on 20 vertices
8504
"""
8505
if hasattr(self, '_assoc') is False:
8506
self._assoc = {}
8507
8508
self._assoc.update(vertex_dict)
8509
8510
def set_vertex(self, vertex, object):
8511
"""
8512
Associate an arbitrary object with a vertex.
8513
8514
INPUT:
8515
8516
8517
- ``vertex`` - which vertex
8518
8519
- ``object`` - object to associate to vertex
8520
8521
8522
EXAMPLES::
8523
8524
sage: T = graphs.TetrahedralGraph()
8525
sage: T.vertices()
8526
[0, 1, 2, 3]
8527
sage: T.set_vertex(1, graphs.FlowerSnark())
8528
sage: T.get_vertex(1)
8529
Flower Snark: Graph on 20 vertices
8530
"""
8531
if hasattr(self, '_assoc') is False:
8532
self._assoc = {}
8533
8534
self._assoc[vertex] = object
8535
8536
def get_vertex(self, vertex):
8537
"""
8538
Retrieve the object associated with a given vertex.
8539
8540
INPUT:
8541
8542
8543
- ``vertex`` - the given vertex
8544
8545
8546
EXAMPLES::
8547
8548
sage: d = {0 : graphs.DodecahedralGraph(), 1 : graphs.FlowerSnark(), 2 : graphs.MoebiusKantorGraph(), 3 : graphs.PetersenGraph() }
8549
sage: d[2]
8550
Moebius-Kantor Graph: Graph on 16 vertices
8551
sage: T = graphs.TetrahedralGraph()
8552
sage: T.vertices()
8553
[0, 1, 2, 3]
8554
sage: T.set_vertices(d)
8555
sage: T.get_vertex(1)
8556
Flower Snark: Graph on 20 vertices
8557
"""
8558
if hasattr(self, '_assoc') is False:
8559
return None
8560
8561
return self._assoc.get(vertex, None)
8562
8563
def get_vertices(self, verts=None):
8564
"""
8565
Return a dictionary of the objects associated to each vertex.
8566
8567
INPUT:
8568
8569
8570
- ``verts`` - iterable container of vertices
8571
8572
8573
EXAMPLES::
8574
8575
sage: d = {0 : graphs.DodecahedralGraph(), 1 : graphs.FlowerSnark(), 2 : graphs.MoebiusKantorGraph(), 3 : graphs.PetersenGraph() }
8576
sage: T = graphs.TetrahedralGraph()
8577
sage: T.set_vertices(d)
8578
sage: T.get_vertices([1,2])
8579
{1: Flower Snark: Graph on 20 vertices,
8580
2: Moebius-Kantor Graph: Graph on 16 vertices}
8581
"""
8582
if verts is None:
8583
verts = self.vertices()
8584
8585
if hasattr(self, '_assoc') is False:
8586
return dict.fromkeys(verts, None)
8587
8588
output = {}
8589
8590
for v in verts:
8591
output[v] = self._assoc.get(v, None)
8592
8593
return output
8594
8595
def loop_vertices(self):
8596
"""
8597
Returns a list of vertices with loops.
8598
8599
EXAMPLES::
8600
8601
sage: G = Graph({0 : [0], 1: [1,2,3], 2: [3]}, loops=True)
8602
sage: G.loop_vertices()
8603
[0, 1]
8604
"""
8605
if self.allows_loops():
8606
return [v for v in self if self.has_edge(v,v)]
8607
else:
8608
return []
8609
8610
def vertex_iterator(self, vertices=None):
8611
"""
8612
Returns an iterator over the given vertices.
8613
8614
Returns False if not given a vertex, sequence, iterator or None. None is
8615
equivalent to a list of every vertex. Note that ``for v in G`` syntax is
8616
allowed.
8617
8618
INPUT:
8619
8620
- ``vertices`` - iterated vertices are these
8621
intersected with the vertices of the (di)graph
8622
8623
EXAMPLES::
8624
8625
sage: P = graphs.PetersenGraph()
8626
sage: for v in P.vertex_iterator():
8627
... print v
8628
...
8629
0
8630
1
8631
2
8632
...
8633
8
8634
9
8635
8636
::
8637
8638
sage: G = graphs.TetrahedralGraph()
8639
sage: for i in G:
8640
... print i
8641
0
8642
1
8643
2
8644
3
8645
8646
Note that since the intersection option is available, the
8647
vertex_iterator() function is sub-optimal, speed-wise, but note the
8648
following optimization::
8649
8650
sage: timeit V = P.vertices() # not tested
8651
100000 loops, best of 3: 8.85 [micro]s per loop
8652
sage: timeit V = list(P.vertex_iterator()) # not tested
8653
100000 loops, best of 3: 5.74 [micro]s per loop
8654
sage: timeit V = list(P._nxg.adj.iterkeys()) # not tested
8655
100000 loops, best of 3: 3.45 [micro]s per loop
8656
8657
In other words, if you want a fast vertex iterator, call the
8658
dictionary directly.
8659
"""
8660
return self._backend.iterator_verts(vertices)
8661
8662
__iter__ = vertex_iterator
8663
8664
def neighbor_iterator(self, vertex):
8665
"""
8666
Return an iterator over neighbors of vertex.
8667
8668
EXAMPLES::
8669
8670
sage: G = graphs.CubeGraph(3)
8671
sage: for i in G.neighbor_iterator('010'):
8672
... print i
8673
011
8674
000
8675
110
8676
sage: D = G.to_directed()
8677
sage: for i in D.neighbor_iterator('010'):
8678
... print i
8679
011
8680
000
8681
110
8682
8683
::
8684
8685
sage: D = DiGraph({0:[1,2], 3:[0]})
8686
sage: list(D.neighbor_iterator(0))
8687
[1, 2, 3]
8688
"""
8689
return self._backend.iterator_nbrs(vertex)
8690
8691
def vertices(self, key=None, boundary_first=False):
8692
r"""
8693
Return a list of the vertices.
8694
8695
INPUT:
8696
8697
- ``key`` - default: ``None`` - a function that takes
8698
a vertex as its one argument and returns a value that
8699
can be used for comparisons in the sorting algorithm.
8700
8701
- ``boundary_first`` - default: ``False`` - if ``True``,
8702
return the boundary vertices first.
8703
8704
OUTPUT:
8705
8706
The vertices of the list.
8707
8708
.. warning::
8709
8710
There is always an attempt to sort the list before
8711
returning the result. However, since any object may
8712
be a vertex, there is no guarantee that any two
8713
vertices will be comparable. With default objects
8714
for vertices (all integers), or when all the vertices
8715
are of the same simple type, then there should not be
8716
a problem with how the vertices will be sorted. However,
8717
if you need to guarantee a total order for the sort,
8718
use the ``key`` argument, as illustrated in the
8719
examples below.
8720
8721
EXAMPLES::
8722
8723
sage: P = graphs.PetersenGraph()
8724
sage: P.vertices()
8725
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
8726
8727
If you do not care about sorted output and you are
8728
concerned about the time taken to sort, consider the
8729
following alternatives. The moral is: if you want a
8730
fast vertex iterator, call the dictionary directly. ::
8731
8732
sage: timeit V = P.vertices() # not tested
8733
100000 loops, best of 3: 8.85 [micro]s per loop
8734
sage: timeit V = list(P.vertex_iterator()) # not tested
8735
100000 loops, best of 3: 5.74 [micro]s per loop
8736
sage: timeit V = list(P._nxg.adj.iterkeys()) # not tested
8737
100000 loops, best of 3: 3.45 [micro]s per loop
8738
8739
We illustrate various ways to use a ``key`` to sort the list::
8740
8741
sage: H=graphs.HanoiTowerGraph(3,3,labels=False)
8742
sage: H.vertices()
8743
[0, 1, 2, 3, 4, ... 22, 23, 24, 25, 26]
8744
sage: H.vertices(key=lambda x: -x)
8745
[26, 25, 24, 23, 22, ... 4, 3, 2, 1, 0]
8746
8747
::
8748
8749
sage: G=graphs.HanoiTowerGraph(3,3)
8750
sage: G.vertices()
8751
[(0, 0, 0), (0, 0, 1), (0, 0, 2), (0, 1, 0), ... (2, 2, 1), (2, 2, 2)]
8752
sage: G.vertices(key = lambda x: (x[1], x[2], x[0]))
8753
[(0, 0, 0), (1, 0, 0), (2, 0, 0), (0, 0, 1), ... (1, 2, 2), (2, 2, 2)]
8754
8755
The discriminant of a polynomial is a function that returns an integer.
8756
We build a graph whose vertices are polynomials, and use the discriminant
8757
function to provide an ordering. Note that since functions are first-class
8758
objects in Python, we can specify precisely the function from the Sage library
8759
that we wish to use as the key. ::
8760
8761
sage: t = polygen(QQ, 't')
8762
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]})
8763
sage: dsc = sage.rings.polynomial.polynomial_rational_flint.Polynomial_rational_flint.discriminant
8764
sage: verts = K.vertices(key=dsc)
8765
sage: verts
8766
[t^2 + 2, t^2, 5*t, 4*t^2 - 6]
8767
sage: [x.discriminant() for x in verts]
8768
[-8, 0, 1, 96]
8769
8770
If boundary vertices are requested first, then they are sorted
8771
separately from the remainder (which are also sorted). ::
8772
8773
sage: P = graphs.PetersenGraph()
8774
sage: P.set_boundary((5..9))
8775
sage: P.vertices(boundary_first=True)
8776
[5, 6, 7, 8, 9, 0, 1, 2, 3, 4]
8777
sage: P.vertices(boundary_first=True, key=lambda x: -x)
8778
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
8779
"""
8780
if not boundary_first:
8781
return sorted(list(self.vertex_iterator()), key=key)
8782
8783
bdy_verts = []
8784
int_verts = []
8785
for v in self.vertex_iterator():
8786
if v in self._boundary:
8787
bdy_verts.append(v)
8788
else:
8789
int_verts.append(v)
8790
return sorted(bdy_verts, key=key) + sorted(int_verts, key=key)
8791
8792
def neighbors(self, vertex):
8793
"""
8794
Return a list of neighbors (in and out if directed) of vertex.
8795
8796
G[vertex] also works.
8797
8798
EXAMPLES::
8799
8800
sage: P = graphs.PetersenGraph()
8801
sage: sorted(P.neighbors(3))
8802
[2, 4, 8]
8803
sage: sorted(P[4])
8804
[0, 3, 9]
8805
"""
8806
return list(self.neighbor_iterator(vertex))
8807
8808
__getitem__ = neighbors
8809
8810
8811
def merge_vertices(self,vertices):
8812
r"""
8813
Merge vertices.
8814
8815
This function replaces a set `S` of vertices by a single vertex
8816
`v_{new}`, such that the edge `uv_{new}` exists if and only if
8817
`\exists v'\in S: (u,v')\in G`.
8818
8819
The new vertex is named after the first vertex in the list
8820
given in argument. If this first name is None, a new vertex
8821
is created.
8822
8823
In the case of multigraphs, the multiplicity is preserved.
8824
8825
INPUT:
8826
8827
- ``vertices`` -- the set of vertices to be merged
8828
8829
EXAMPLE::
8830
8831
sage: g=graphs.CycleGraph(3)
8832
sage: g.merge_vertices([0,1])
8833
sage: g.edges()
8834
[(0, 2, None)]
8835
8836
sage: # With a Multigraph :
8837
sage: g=graphs.CycleGraph(3)
8838
sage: g.allow_multiple_edges(True)
8839
sage: g.merge_vertices([0,1])
8840
sage: g.edges(labels=False)
8841
[(0, 2), (0, 2)]
8842
8843
sage: P=graphs.PetersenGraph()
8844
sage: P.merge_vertices([5,7])
8845
sage: P.vertices()
8846
[0, 1, 2, 3, 4, 5, 6, 8, 9]
8847
8848
sage: g=graphs.CycleGraph(5)
8849
sage: g.vertices()
8850
[0, 1, 2, 3, 4]
8851
sage: g.merge_vertices([None, 1, 3])
8852
sage: g.edges(labels=False)
8853
[(0, 4), (0, 5), (2, 5), (4, 5)]
8854
8855
"""
8856
8857
if len(vertices) > 0 and vertices[0] is None:
8858
vertices[0] = self.add_vertex()
8859
8860
if self.is_directed():
8861
out_edges=self.edge_boundary(vertices)
8862
in_edges=self.edge_boundary([v for v in self if not v in vertices])
8863
self.delete_vertices(vertices[1:])
8864
self.add_edges([(vertices[0],v,l) for (u,v,l) in out_edges if u!=vertices[0]])
8865
self.add_edges([(v,vertices[0],l) for (v,u,l) in in_edges if u!=vertices[0]])
8866
else:
8867
edges=self.edge_boundary(vertices)
8868
self.delete_vertices(vertices[1:])
8869
add_edges=[]
8870
for (u,v,l) in edges:
8871
if v in vertices and v != vertices[0]:
8872
add_edges.append((vertices[0],u,l))
8873
if u in vertices and u!=vertices[0]:
8874
add_edges.append((vertices[0],v,l))
8875
self.add_edges(add_edges)
8876
8877
### Edge handlers
8878
8879
def add_edge(self, u, v=None, label=None):
8880
"""
8881
Adds an edge from u and v.
8882
8883
INPUT: The following forms are all accepted:
8884
8885
- G.add_edge( 1, 2 )
8886
- G.add_edge( (1, 2) )
8887
- G.add_edges( [ (1, 2) ])
8888
- G.add_edge( 1, 2, 'label' )
8889
- G.add_edge( (1, 2, 'label') )
8890
- G.add_edges( [ (1, 2, 'label') ] )
8891
8892
WARNING: The following intuitive input results in nonintuitive
8893
output::
8894
8895
sage: G = Graph()
8896
sage: G.add_edge((1,2), 'label')
8897
sage: G.networkx_graph().adj # random output order
8898
{'label': {(1, 2): None}, (1, 2): {'label': None}}
8899
8900
Use one of these instead::
8901
8902
sage: G = Graph()
8903
sage: G.add_edge((1,2), label="label")
8904
sage: G.networkx_graph().adj # random output order
8905
{1: {2: 'label'}, 2: {1: 'label'}}
8906
8907
::
8908
8909
sage: G = Graph()
8910
sage: G.add_edge(1,2,'label')
8911
sage: G.networkx_graph().adj # random output order
8912
{1: {2: 'label'}, 2: {1: 'label'}}
8913
8914
The following syntax is supported, but note that you must use
8915
the ``label`` keyword::
8916
8917
sage: G = Graph()
8918
sage: G.add_edge((1,2), label='label')
8919
sage: G.edges()
8920
[(1, 2, 'label')]
8921
sage: G = Graph()
8922
sage: G.add_edge((1,2), 'label')
8923
sage: G.edges()
8924
[('label', (1, 2), None)]
8925
8926
Vertex name cannot be None, so::
8927
8928
sage: G = Graph()
8929
sage: G.add_edge(None, 4)
8930
sage: G.vertices()
8931
[0, 4]
8932
"""
8933
if label is None:
8934
if v is None:
8935
try:
8936
u, v, label = u
8937
except Exception:
8938
try:
8939
u, v = u
8940
except Exception:
8941
pass
8942
else:
8943
if v is None:
8944
try:
8945
u, v = u
8946
except Exception:
8947
pass
8948
if not self.allows_loops() and u==v:
8949
return
8950
self._backend.add_edge(u, v, label, self._directed)
8951
8952
def add_edges(self, edges):
8953
"""
8954
Add edges from an iterable container.
8955
8956
EXAMPLES::
8957
8958
sage: G = graphs.DodecahedralGraph()
8959
sage: H = Graph()
8960
sage: H.add_edges( G.edge_iterator() ); H
8961
Graph on 20 vertices
8962
sage: G = graphs.DodecahedralGraph().to_directed()
8963
sage: H = DiGraph()
8964
sage: H.add_edges( G.edge_iterator() ); H
8965
Digraph on 20 vertices
8966
"""
8967
for e in edges:
8968
self.add_edge(e)
8969
8970
def subdivide_edge(self, *args):
8971
"""
8972
Subdivides an edge `k` times.
8973
8974
INPUT:
8975
8976
The following forms are all accepted to subdivide `8` times
8977
the edge between vertices `1` and `2` labeled with
8978
``"my_label"``.
8979
8980
- ``G.subdivide_edge( 1, 2, 8 )``
8981
- ``G.subdivide_edge( (1, 2), 8 )``
8982
- ``G.subdivide_edge( (1, 2, "my_label"), 8 )``
8983
8984
.. NOTE::
8985
8986
* If the given edge is labelled with `l`, all the edges
8987
created by the subdivision will have the same label.
8988
8989
* If no label is given, the label used will be the one
8990
returned by the method :meth:`edge_label` on the pair
8991
``u,v``
8992
8993
EXAMPLE:
8994
8995
Subdividing `5` times an edge in a path of length
8996
`3` makes it a path of length `8`::
8997
8998
sage: g = graphs.PathGraph(3)
8999
sage: edge = g.edges()[0]
9000
sage: g.subdivide_edge(edge, 5)
9001
sage: g.is_isomorphic(graphs.PathGraph(8))
9002
True
9003
9004
Subdividing a labelled edge in two ways ::
9005
9006
sage: g = Graph()
9007
sage: g.add_edge(0,1,"label1")
9008
sage: g.add_edge(1,2,"label2")
9009
sage: print sorted(g.edges())
9010
[(0, 1, 'label1'), (1, 2, 'label2')]
9011
9012
Specifying the label::
9013
9014
sage: g.subdivide_edge(0,1,"label1", 3)
9015
sage: print sorted(g.edges())
9016
[(0, 3, 'label1'), (1, 2, 'label2'), (1, 5, 'label1'), (3, 4, 'label1'), (4, 5, 'label1')]
9017
9018
The lazy way::
9019
9020
sage: g.subdivide_edge(1,2,"label2", 5)
9021
sage: print sorted(g.edges())
9022
[(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')]
9023
9024
If too many arguments are given, an exception is raised ::
9025
9026
sage: g.subdivide_edge(0,1,1,1,1,1,1,1,1,1,1)
9027
Traceback (most recent call last):
9028
...
9029
ValueError: This method takes at most 4 arguments !
9030
9031
The same goes when the given edge does not exist::
9032
9033
sage: g.subdivide_edge(0,1,"fake_label",5)
9034
Traceback (most recent call last):
9035
...
9036
ValueError: The given edge does not exist.
9037
9038
.. SEEALSO::
9039
9040
- :meth:`subdivide_edges` -- subdivides multiples edges at a time
9041
9042
"""
9043
9044
if len(args) == 2:
9045
edge, k = args
9046
9047
if len(edge) == 2:
9048
u,v = edge
9049
l = self.edge_label(u,v)
9050
elif len(edge) == 3:
9051
u,v,l = edge
9052
9053
elif len(args) == 3:
9054
u, v, k = args
9055
l = self.edge_label(u,v)
9056
9057
elif len(args) == 4:
9058
u, v, l, k = args
9059
9060
else:
9061
raise ValueError("This method takes at most 4 arguments !")
9062
9063
if not self.has_edge(u,v,l):
9064
raise ValueError("The given edge does not exist.")
9065
9066
for i in xrange(k):
9067
self.add_vertex()
9068
9069
self.delete_edge(u,v,l)
9070
9071
edges = []
9072
for uu in self.vertices()[-k:] + [v]:
9073
edges.append((u,uu,l))
9074
u = uu
9075
9076
self.add_edges(edges)
9077
9078
def subdivide_edges(self, edges, k):
9079
"""
9080
Subdivides k times edges from an iterable container.
9081
9082
For more information on the behaviour of this method, please
9083
refer to the documentation of :meth:`subdivide_edge`.
9084
9085
INPUT:
9086
9087
- ``edges`` -- a list of edges
9088
9089
- ``k`` (integer) -- common length of the subdivisions
9090
9091
9092
.. NOTE::
9093
9094
If a given edge is labelled with `l`, all the edges
9095
created by its subdivision will have the same label.
9096
9097
EXAMPLE:
9098
9099
If we are given the disjoint union of several paths::
9100
9101
sage: paths = [2,5,9]
9102
sage: paths = map(graphs.PathGraph, paths)
9103
sage: g = Graph()
9104
sage: for P in paths:
9105
... g = g + P
9106
9107
... subdividing edges in each of them will only change their
9108
lengths::
9109
9110
sage: edges = [P.edges()[0] for P in g.connected_components_subgraphs()]
9111
sage: k = 6
9112
sage: g.subdivide_edges(edges, k)
9113
9114
Let us check this by creating the graph we expect to have built
9115
through subdivision::
9116
9117
sage: paths2 = [2+k, 5+k, 9+k]
9118
sage: paths2 = map(graphs.PathGraph, paths2)
9119
sage: g2 = Graph()
9120
sage: for P in paths2:
9121
... g2 = g2 + P
9122
sage: g.is_isomorphic(g2)
9123
True
9124
9125
.. SEEALSO::
9126
9127
- :meth:`subdivide_edge` -- subdivides one edge
9128
"""
9129
for e in edges:
9130
self.subdivide_edge(e, k)
9131
9132
def delete_edge(self, u, v=None, label=None):
9133
r"""
9134
Delete the edge from u to v, returning silently if vertices or edge
9135
does not exist.
9136
9137
INPUT: The following forms are all accepted:
9138
9139
- G.delete_edge( 1, 2 )
9140
- G.delete_edge( (1, 2) )
9141
- G.delete_edges( [ (1, 2) ] )
9142
- G.delete_edge( 1, 2, 'label' )
9143
- G.delete_edge( (1, 2, 'label') )
9144
- G.delete_edges( [ (1, 2, 'label') ] )
9145
9146
EXAMPLES::
9147
9148
sage: G = graphs.CompleteGraph(19).copy(implementation='c_graph')
9149
sage: G.size()
9150
171
9151
sage: G.delete_edge( 1, 2 )
9152
sage: G.delete_edge( (3, 4) )
9153
sage: G.delete_edges( [ (5, 6), (7, 8) ] )
9154
sage: G.size()
9155
167
9156
9157
Note that NetworkX accidentally deletes these edges, even though the
9158
labels do not match up::
9159
9160
sage: N = graphs.CompleteGraph(19).copy(implementation='networkx')
9161
sage: N.size()
9162
171
9163
sage: N.delete_edge( 1, 2 )
9164
sage: N.delete_edge( (3, 4) )
9165
sage: N.delete_edges( [ (5, 6), (7, 8) ] )
9166
sage: N.size()
9167
167
9168
sage: N.delete_edge( 9, 10, 'label' )
9169
sage: N.delete_edge( (11, 12, 'label') )
9170
sage: N.delete_edges( [ (13, 14, 'label') ] )
9171
sage: N.size()
9172
167
9173
sage: N.has_edge( (11, 12) )
9174
True
9175
9176
However, CGraph backends handle things properly::
9177
9178
sage: G.delete_edge( 9, 10, 'label' )
9179
sage: G.delete_edge( (11, 12, 'label') )
9180
sage: G.delete_edges( [ (13, 14, 'label') ] )
9181
sage: G.size()
9182
167
9183
9184
::
9185
9186
sage: C = graphs.CompleteGraph(19).to_directed(sparse=True)
9187
sage: C.size()
9188
342
9189
sage: C.delete_edge( 1, 2 )
9190
sage: C.delete_edge( (3, 4) )
9191
sage: C.delete_edges( [ (5, 6), (7, 8) ] )
9192
9193
sage: D = graphs.CompleteGraph(19).to_directed(sparse=True, implementation='networkx')
9194
sage: D.size()
9195
342
9196
sage: D.delete_edge( 1, 2 )
9197
sage: D.delete_edge( (3, 4) )
9198
sage: D.delete_edges( [ (5, 6), (7, 8) ] )
9199
sage: D.delete_edge( 9, 10, 'label' )
9200
sage: D.delete_edge( (11, 12, 'label') )
9201
sage: D.delete_edges( [ (13, 14, 'label') ] )
9202
sage: D.size()
9203
338
9204
sage: D.has_edge( (11, 12) )
9205
True
9206
9207
::
9208
9209
sage: C.delete_edge( 9, 10, 'label' )
9210
sage: C.delete_edge( (11, 12, 'label') )
9211
sage: C.delete_edges( [ (13, 14, 'label') ] )
9212
sage: C.size() # correct!
9213
338
9214
sage: C.has_edge( (11, 12) ) # correct!
9215
True
9216
9217
"""
9218
if label is None:
9219
if v is None:
9220
try:
9221
u, v, label = u
9222
except Exception:
9223
u, v = u
9224
label = None
9225
self._backend.del_edge(u, v, label, self._directed)
9226
9227
def delete_edges(self, edges):
9228
"""
9229
Delete edges from an iterable container.
9230
9231
EXAMPLES::
9232
9233
sage: K12 = graphs.CompleteGraph(12)
9234
sage: K4 = graphs.CompleteGraph(4)
9235
sage: K12.size()
9236
66
9237
sage: K12.delete_edges(K4.edge_iterator())
9238
sage: K12.size()
9239
60
9240
9241
::
9242
9243
sage: K12 = graphs.CompleteGraph(12).to_directed()
9244
sage: K4 = graphs.CompleteGraph(4).to_directed()
9245
sage: K12.size()
9246
132
9247
sage: K12.delete_edges(K4.edge_iterator())
9248
sage: K12.size()
9249
120
9250
"""
9251
for e in edges:
9252
self.delete_edge(e)
9253
9254
def delete_multiedge(self, u, v):
9255
"""
9256
Deletes all edges from u and v.
9257
9258
EXAMPLES::
9259
9260
sage: G = Graph(multiedges=True,sparse=True)
9261
sage: G.add_edges([(0,1), (0,1), (0,1), (1,2), (2,3)])
9262
sage: G.edges()
9263
[(0, 1, None), (0, 1, None), (0, 1, None), (1, 2, None), (2, 3, None)]
9264
sage: G.delete_multiedge( 0, 1 )
9265
sage: G.edges()
9266
[(1, 2, None), (2, 3, None)]
9267
9268
::
9269
9270
sage: D = DiGraph(multiedges=True,sparse=True)
9271
sage: D.add_edges([(0,1,1), (0,1,2), (0,1,3), (1,0), (1,2), (2,3)])
9272
sage: D.edges()
9273
[(0, 1, 1), (0, 1, 2), (0, 1, 3), (1, 0, None), (1, 2, None), (2, 3, None)]
9274
sage: D.delete_multiedge( 0, 1 )
9275
sage: D.edges()
9276
[(1, 0, None), (1, 2, None), (2, 3, None)]
9277
"""
9278
if self.allows_multiple_edges():
9279
for l in self.edge_label(u, v):
9280
self.delete_edge(u, v, l)
9281
else:
9282
self.delete_edge(u, v)
9283
9284
def set_edge_label(self, u, v, l):
9285
"""
9286
Set the edge label of a given edge.
9287
9288
.. note::
9289
9290
There can be only one edge from u to v for this to make
9291
sense. Otherwise, an error is raised.
9292
9293
INPUT:
9294
9295
9296
- ``u, v`` - the vertices (and direction if digraph)
9297
of the edge
9298
9299
- ``l`` - the new label
9300
9301
9302
EXAMPLES::
9303
9304
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)
9305
sage: SD.set_edge_label(1, 18, 'discrete')
9306
sage: SD.set_edge_label(4, 7, 'discrete')
9307
sage: SD.set_edge_label(2, 5, 'h = 0')
9308
sage: SD.set_edge_label(7, 18, 'h = 0')
9309
sage: SD.set_edge_label(7, 10, 'aut')
9310
sage: SD.set_edge_label(8, 10, 'aut')
9311
sage: SD.set_edge_label(8, 9, 'label')
9312
sage: SD.set_edge_label(8, 6, 'no label')
9313
sage: SD.set_edge_label(13, 17, 'k > h')
9314
sage: SD.set_edge_label(13, 14, 'k = h')
9315
sage: SD.set_edge_label(17, 15, 'v_k finite')
9316
sage: SD.set_edge_label(14, 15, 'v_k m.c.r.')
9317
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]}
9318
sage: SD.plot(pos=posn, vertex_size=400, vertex_colors={'#FFFFFF':range(1,19)}, edge_labels=True).show() # long time
9319
9320
::
9321
9322
sage: G = graphs.HeawoodGraph()
9323
sage: for u,v,l in G.edges():
9324
... G.set_edge_label(u,v,'(' + str(u) + ',' + str(v) + ')')
9325
sage: G.edges()
9326
[(0, 1, '(0,1)'),
9327
(0, 5, '(0,5)'),
9328
(0, 13, '(0,13)'),
9329
...
9330
(11, 12, '(11,12)'),
9331
(12, 13, '(12,13)')]
9332
9333
::
9334
9335
sage: g = Graph({0: [0,1,1,2]}, loops=True, multiedges=True, sparse=True)
9336
sage: g.set_edge_label(0,0,'test')
9337
sage: g.edges()
9338
[(0, 0, 'test'), (0, 1, None), (0, 1, None), (0, 2, None)]
9339
sage: g.add_edge(0,0,'test2')
9340
sage: g.set_edge_label(0,0,'test3')
9341
Traceback (most recent call last):
9342
...
9343
RuntimeError: Cannot set edge label, since there are multiple edges from 0 to 0.
9344
9345
::
9346
9347
sage: dg = DiGraph({0 : [1], 1 : [0]}, sparse=True)
9348
sage: dg.set_edge_label(0,1,5)
9349
sage: dg.set_edge_label(1,0,9)
9350
sage: dg.outgoing_edges(1)
9351
[(1, 0, 9)]
9352
sage: dg.incoming_edges(1)
9353
[(0, 1, 5)]
9354
sage: dg.outgoing_edges(0)
9355
[(0, 1, 5)]
9356
sage: dg.incoming_edges(0)
9357
[(1, 0, 9)]
9358
9359
::
9360
9361
sage: G = Graph({0:{1:1}}, sparse=True)
9362
sage: G.num_edges()
9363
1
9364
sage: G.set_edge_label(0,1,1)
9365
sage: G.num_edges()
9366
1
9367
"""
9368
if self.allows_multiple_edges():
9369
if len(self.edge_label(u, v)) > 1:
9370
raise RuntimeError("Cannot set edge label, since there are multiple edges from %s to %s."%(u,v))
9371
self._backend.set_edge_label(u, v, l, self._directed)
9372
9373
def has_edge(self, u, v=None, label=None):
9374
r"""
9375
Returns True if (u, v) is an edge, False otherwise.
9376
9377
INPUT: The following forms are accepted by NetworkX:
9378
9379
- G.has_edge( 1, 2 )
9380
- G.has_edge( (1, 2) )
9381
- G.has_edge( 1, 2, 'label' )
9382
- G.has_edge( (1, 2, 'label') )
9383
9384
EXAMPLES::
9385
9386
sage: graphs.EmptyGraph().has_edge(9,2)
9387
False
9388
sage: DiGraph().has_edge(9,2)
9389
False
9390
sage: G = Graph(sparse=True)
9391
sage: G.add_edge(0,1,"label")
9392
sage: G.has_edge(0,1,"different label")
9393
False
9394
sage: G.has_edge(0,1,"label")
9395
True
9396
"""
9397
if label is None:
9398
if v is None:
9399
try:
9400
u, v, label = u
9401
except Exception:
9402
u, v = u
9403
label = None
9404
return self._backend.has_edge(u, v, label)
9405
9406
def edges(self, labels=True, sort=True, key=None):
9407
r"""
9408
Return a list of edges.
9409
9410
Each edge is a triple (u,v,l) where u and v are vertices and l is a
9411
label. If the parameter ``labels`` is False then a list of couple (u,v)
9412
is returned where u and v are vertices.
9413
9414
INPUT:
9415
9416
- ``labels`` - default: ``True`` - if ``False``, each
9417
edge is simply a pair (u,v) of vertices.
9418
9419
- ``sort`` - default: ``True`` - if ``True``, edges are
9420
sorted according to the default ordering.
9421
9422
- ``key`` - default: ``None`` - a function takes an edge
9423
(a pair or a triple, according to the ``labels`` keyword)
9424
as its one argument and returns a value that can be used
9425
for comparisons in the sorting algorithm.
9426
9427
OUTPUT: A list of tuples. It is safe to change the returned list.
9428
9429
.. warning::
9430
9431
Since any object may be a vertex, there is no guarantee
9432
that any two vertices will be comparable, and thus no
9433
guarantee how two edges may compare. With default
9434
objects for vertices (all integers), or when all the
9435
vertices are of the same simple type, then there should
9436
not be a problem with how the vertices will be sorted.
9437
However, if you need to guarantee a total order for
9438
the sorting of the edges, use the ``key`` argument,
9439
as illustrated in the examples below.
9440
9441
EXAMPLES::
9442
9443
sage: graphs.DodecahedralGraph().edges()
9444
[(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)]
9445
9446
::
9447
9448
sage: graphs.DodecahedralGraph().edges(labels=False)
9449
[(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)]
9450
9451
::
9452
9453
sage: D = graphs.DodecahedralGraph().to_directed()
9454
sage: D.edges()
9455
[(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)]
9456
sage: D.edges(labels = False)
9457
[(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)]
9458
9459
The default is to sort the returned list in the default fashion, as in the above examples.
9460
this can be overridden by specifying a key function. This first example just ignores
9461
the labels in the third component of the triple. ::
9462
9463
sage: G=graphs.CycleGraph(5)
9464
sage: G.edges(key = lambda x: (x[1],-x[0]))
9465
[(0, 1, None), (1, 2, None), (2, 3, None), (3, 4, None), (0, 4, None)]
9466
9467
We set the labels to characters and then perform a default sort
9468
followed by a sort according to the labels. ::
9469
9470
sage: G=graphs.CycleGraph(5)
9471
sage: for e in G.edges():
9472
... G.set_edge_label(e[0], e[1], chr(ord('A')+e[0]+5*e[1]))
9473
sage: G.edges(sort=True)
9474
[(0, 1, 'F'), (0, 4, 'U'), (1, 2, 'L'), (2, 3, 'R'), (3, 4, 'X')]
9475
sage: G.edges(key=lambda x: x[2])
9476
[(0, 1, 'F'), (1, 2, 'L'), (2, 3, 'R'), (0, 4, 'U'), (3, 4, 'X')]
9477
9478
TESTS:
9479
9480
It is an error to turn off sorting while providing a key function for sorting. ::
9481
9482
sage: P=graphs.PetersenGraph()
9483
sage: P.edges(sort=False, key=lambda x: x)
9484
Traceback (most recent call last):
9485
...
9486
ValueError: sort keyword is False, yet a key function is given
9487
"""
9488
if not(sort) and key:
9489
raise ValueError('sort keyword is False, yet a key function is given')
9490
L = list(self.edge_iterator(labels=labels))
9491
if sort:
9492
L.sort(key=key)
9493
return L
9494
9495
def edge_boundary(self, vertices1, vertices2=None, labels=True, sort=True):
9496
"""
9497
Returns a list of edges `(u,v,l)` with `u` in ``vertices1``
9498
and `v` in ``vertices2``. If ``vertices2`` is ``None``, then
9499
it is set to the complement of ``vertices1``.
9500
9501
In a digraph, the external boundary of a vertex `v` are those
9502
vertices `u` with an arc `(v, u)`.
9503
9504
INPUT:
9505
9506
9507
- ``labels`` - if ``False``, each edge is a tuple `(u,v)` of
9508
vertices.
9509
9510
9511
EXAMPLES::
9512
9513
sage: K = graphs.CompleteBipartiteGraph(9,3)
9514
sage: len(K.edge_boundary( [0,1,2,3,4,5,6,7,8], [9,10,11] ))
9515
27
9516
sage: K.size()
9517
27
9518
9519
Note that the edge boundary preserves direction::
9520
9521
sage: K = graphs.CompleteBipartiteGraph(9,3).to_directed()
9522
sage: len(K.edge_boundary( [0,1,2,3,4,5,6,7,8], [9,10,11] ))
9523
27
9524
sage: K.size()
9525
54
9526
9527
::
9528
9529
sage: D = DiGraph({0:[1,2], 3:[0]})
9530
sage: D.edge_boundary([0])
9531
[(0, 1, None), (0, 2, None)]
9532
sage: D.edge_boundary([0], labels=False)
9533
[(0, 1), (0, 2)]
9534
9535
TESTS::
9536
9537
sage: G = graphs.DiamondGraph()
9538
sage: G.edge_boundary([0,1])
9539
[(0, 2, None), (1, 2, None), (1, 3, None)]
9540
sage: G.edge_boundary([0], [0])
9541
[]
9542
sage: G.edge_boundary([2], [0])
9543
[(0, 2, None)]
9544
"""
9545
vertices1 = set([v for v in vertices1 if v in self])
9546
if self._directed:
9547
if vertices2 is not None:
9548
vertices2 = set([v for v in vertices2 if v in self])
9549
output = [e for e in self.outgoing_edge_iterator(vertices1,labels=labels)
9550
if e[1] in vertices2]
9551
else:
9552
output = [e for e in self.outgoing_edge_iterator(vertices1,labels=labels)
9553
if e[1] not in vertices1]
9554
else:
9555
if vertices2 is not None:
9556
vertices2 = set([v for v in vertices2 if v in self])
9557
output = [e for e in self.edge_iterator(vertices1,labels=labels)
9558
if (e[0] in vertices1 and e[1] in vertices2) or
9559
(e[1] in vertices1 and e[0] in vertices2)]
9560
else:
9561
output = [e for e in self.edge_iterator(vertices1,labels=labels)
9562
if e[1] not in vertices1 or e[0] not in vertices1]
9563
if sort:
9564
output.sort()
9565
return output
9566
9567
def edge_iterator(self, vertices=None, labels=True, ignore_direction=False):
9568
"""
9569
Returns an iterator over edges.
9570
9571
The iterator returned is over the edges incident with any vertex given
9572
in the parameter ``vertices``. If the graph is directed, iterates over
9573
edges going out only. If vertices is None, then returns an iterator over
9574
all edges. If self is directed, returns outgoing edges only.
9575
9576
INPUT:
9577
9578
- ``vertices`` - (default: None) a vertex, a list of vertices or None
9579
9580
- ``labels`` - if False, each edge is a tuple (u,v) of
9581
vertices.
9582
9583
- ``ignore_direction`` - bool (default: False) - only applies
9584
to directed graphs. If True, searches across edges in either
9585
direction.
9586
9587
9588
EXAMPLES::
9589
9590
sage: for i in graphs.PetersenGraph().edge_iterator([0]):
9591
... print i
9592
(0, 1, None)
9593
(0, 4, None)
9594
(0, 5, None)
9595
sage: D = DiGraph( { 0 : [1,2], 1: [0] } )
9596
sage: for i in D.edge_iterator([0]):
9597
... print i
9598
(0, 1, None)
9599
(0, 2, None)
9600
9601
::
9602
9603
sage: G = graphs.TetrahedralGraph()
9604
sage: list(G.edge_iterator(labels=False))
9605
[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]
9606
9607
::
9608
9609
sage: D = DiGraph({1:[0], 2:[0]})
9610
sage: list(D.edge_iterator(0))
9611
[]
9612
sage: list(D.edge_iterator(0, ignore_direction=True))
9613
[(1, 0, None), (2, 0, None)]
9614
"""
9615
if vertices is None:
9616
vertices = self
9617
elif vertices in self:
9618
vertices = [vertices]
9619
else:
9620
vertices = [v for v in vertices if v in self]
9621
if ignore_direction and self._directed:
9622
from itertools import chain
9623
return chain(self._backend.iterator_out_edges(vertices, labels),
9624
self._backend.iterator_in_edges(vertices, labels))
9625
elif self._directed:
9626
return self._backend.iterator_out_edges(vertices, labels)
9627
else:
9628
return self._backend.iterator_edges(vertices, labels)
9629
9630
def edges_incident(self, vertices=None, labels=True, sort=True):
9631
"""
9632
Returns incident edges to some vertices.
9633
9634
If ``vertices` is a vertex, then it returns the list of edges incident to
9635
that vertex. If ``vertices`` is a list of vertices then it returns the
9636
list of all edges adjacent to those vertices. If ``vertices``
9637
is None, returns a list of all edges in graph. For digraphs, only
9638
lists outward edges.
9639
9640
INPUT:
9641
9642
- ``vertices`` - object (default: None) - a vertex, a list of vertices
9643
or None.
9644
9645
- ``labels`` - bool (default: True) - if False, each edge is a tuple
9646
(u,v) of vertices.
9647
9648
- ``sort`` - bool (default: True) - if True the returned list is sorted.
9649
9650
9651
EXAMPLES::
9652
9653
sage: graphs.PetersenGraph().edges_incident([0,9], labels=False)
9654
[(0, 1), (0, 4), (0, 5), (4, 9), (6, 9), (7, 9)]
9655
sage: D = DiGraph({0:[1]})
9656
sage: D.edges_incident([0])
9657
[(0, 1, None)]
9658
sage: D.edges_incident([1])
9659
[]
9660
9661
TESTS::
9662
9663
sage: G = Graph({0:[0]}, loops=True) # ticket 9581
9664
sage: G.edges_incident(0)
9665
[(0, 0, None)]
9666
"""
9667
if vertices in self:
9668
vertices = [vertices]
9669
9670
if sort:
9671
return sorted(self.edge_iterator(vertices=vertices,labels=labels))
9672
return list(self.edge_iterator(vertices=vertices,labels=labels))
9673
9674
def edge_label(self, u, v=None):
9675
"""
9676
Returns the label of an edge. Note that if the graph allows
9677
multiple edges, then a list of labels on the edge is returned.
9678
9679
EXAMPLES::
9680
9681
sage: G = Graph({0 : {1 : 'edgelabel'}}, sparse=True)
9682
sage: G.edges(labels=False)
9683
[(0, 1)]
9684
sage: G.edge_label( 0, 1 )
9685
'edgelabel'
9686
sage: D = DiGraph({0 : {1 : 'edgelabel'}}, sparse=True)
9687
sage: D.edges(labels=False)
9688
[(0, 1)]
9689
sage: D.edge_label( 0, 1 )
9690
'edgelabel'
9691
9692
::
9693
9694
sage: G = Graph(multiedges=True, sparse=True)
9695
sage: [G.add_edge(0,1,i) for i in range(1,6)]
9696
[None, None, None, None, None]
9697
sage: sorted(G.edge_label(0,1))
9698
[1, 2, 3, 4, 5]
9699
9700
TESTS::
9701
9702
sage: G = Graph()
9703
sage: G.add_edge(0,1,[7])
9704
sage: G.add_edge(0,2,[7])
9705
sage: G.edge_label(0,1)[0] += 1
9706
sage: G.edges()
9707
[(0, 1, [8]), (0, 2, [7])]
9708
9709
"""
9710
return self._backend.get_edge_label(u,v)
9711
9712
def edge_labels(self):
9713
"""
9714
Returns a list of edge labels.
9715
9716
EXAMPLES::
9717
9718
sage: G = Graph({0:{1:'x',2:'z',3:'a'}, 2:{5:'out'}}, sparse=True)
9719
sage: G.edge_labels()
9720
['x', 'z', 'a', 'out']
9721
sage: G = DiGraph({0:{1:'x',2:'z',3:'a'}, 2:{5:'out'}}, sparse=True)
9722
sage: G.edge_labels()
9723
['x', 'z', 'a', 'out']
9724
"""
9725
labels = []
9726
for u,v,l in self.edges():
9727
labels.append(l)
9728
return labels
9729
9730
def remove_multiple_edges(self):
9731
"""
9732
Removes all multiple edges, retaining one edge for each.
9733
9734
EXAMPLES::
9735
9736
sage: G = Graph(multiedges=True, sparse=True)
9737
sage: G.add_edges( [ (0,1), (0,1), (0,1), (0,1), (1,2) ] )
9738
sage: G.edges(labels=False)
9739
[(0, 1), (0, 1), (0, 1), (0, 1), (1, 2)]
9740
9741
::
9742
9743
sage: G.remove_multiple_edges()
9744
sage: G.edges(labels=False)
9745
[(0, 1), (1, 2)]
9746
9747
::
9748
9749
sage: D = DiGraph(multiedges=True, sparse=True)
9750
sage: D.add_edges( [ (0,1,1), (0,1,2), (0,1,3), (0,1,4), (1,2) ] )
9751
sage: D.edges(labels=False)
9752
[(0, 1), (0, 1), (0, 1), (0, 1), (1, 2)]
9753
sage: D.remove_multiple_edges()
9754
sage: D.edges(labels=False)
9755
[(0, 1), (1, 2)]
9756
"""
9757
if self.allows_multiple_edges():
9758
if self._directed:
9759
for v in self:
9760
for u in self.neighbor_in_iterator(v):
9761
edges = self.edge_boundary([u], [v])
9762
if len(edges) > 1:
9763
self.delete_edges(edges[1:])
9764
else:
9765
for v in self:
9766
for u in self.neighbor_iterator(v):
9767
edges = self.edge_boundary([v], [u])
9768
if len(edges) > 1:
9769
self.delete_edges(edges[1:])
9770
9771
def remove_loops(self, vertices=None):
9772
"""
9773
Removes loops on vertices in vertices. If vertices is None, removes
9774
all loops.
9775
9776
EXAMPLE
9777
9778
::
9779
9780
sage: G = Graph(4, loops=True)
9781
sage: G.add_edges( [ (0,0), (1,1), (2,2), (3,3), (2,3) ] )
9782
sage: G.edges(labels=False)
9783
[(0, 0), (1, 1), (2, 2), (2, 3), (3, 3)]
9784
sage: G.remove_loops()
9785
sage: G.edges(labels=False)
9786
[(2, 3)]
9787
sage: G.allows_loops()
9788
True
9789
sage: G.has_loops()
9790
False
9791
9792
sage: D = DiGraph(4, loops=True)
9793
sage: D.add_edges( [ (0,0), (1,1), (2,2), (3,3), (2,3) ] )
9794
sage: D.edges(labels=False)
9795
[(0, 0), (1, 1), (2, 2), (2, 3), (3, 3)]
9796
sage: D.remove_loops()
9797
sage: D.edges(labels=False)
9798
[(2, 3)]
9799
sage: D.allows_loops()
9800
True
9801
sage: D.has_loops()
9802
False
9803
"""
9804
if vertices is None:
9805
vertices = self
9806
for v in vertices:
9807
if self.has_edge(v,v):
9808
self.delete_multiedge(v,v)
9809
9810
def loop_edges(self):
9811
"""
9812
Returns a list of all loops in the graph.
9813
9814
EXAMPLES::
9815
9816
sage: G = Graph(4, loops=True)
9817
sage: G.add_edges( [ (0,0), (1,1), (2,2), (3,3), (2,3) ] )
9818
sage: G.loop_edges()
9819
[(0, 0, None), (1, 1, None), (2, 2, None), (3, 3, None)]
9820
9821
::
9822
9823
sage: D = DiGraph(4, loops=True)
9824
sage: D.add_edges( [ (0,0), (1,1), (2,2), (3,3), (2,3) ] )
9825
sage: D.loop_edges()
9826
[(0, 0, None), (1, 1, None), (2, 2, None), (3, 3, None)]
9827
9828
::
9829
9830
sage: G = Graph(4, loops=True, multiedges=True, sparse=True)
9831
sage: G.add_edges([(i,i) for i in range(4)])
9832
sage: G.loop_edges()
9833
[(0, 0, None), (1, 1, None), (2, 2, None), (3, 3, None)]
9834
"""
9835
if self.allows_multiple_edges():
9836
return [(v,v,l) for v in self.loop_vertices() for l in self.edge_label(v,v)]
9837
else:
9838
return [(v,v,self.edge_label(v,v)) for v in self.loop_vertices()]
9839
9840
def number_of_loops(self):
9841
"""
9842
Returns the number of edges that are loops.
9843
9844
EXAMPLES::
9845
9846
sage: G = Graph(4, loops=True)
9847
sage: G.add_edges( [ (0,0), (1,1), (2,2), (3,3), (2,3) ] )
9848
sage: G.edges(labels=False)
9849
[(0, 0), (1, 1), (2, 2), (2, 3), (3, 3)]
9850
sage: G.number_of_loops()
9851
4
9852
9853
::
9854
9855
sage: D = DiGraph(4, loops=True)
9856
sage: D.add_edges( [ (0,0), (1,1), (2,2), (3,3), (2,3) ] )
9857
sage: D.edges(labels=False)
9858
[(0, 0), (1, 1), (2, 2), (2, 3), (3, 3)]
9859
sage: D.number_of_loops()
9860
4
9861
"""
9862
return len(self.loop_edges())
9863
9864
### Modifications
9865
9866
def clear(self):
9867
"""
9868
Empties the graph of vertices and edges and removes name, boundary,
9869
associated objects, and position information.
9870
9871
EXAMPLES::
9872
9873
sage: G=graphs.CycleGraph(4); G.set_vertices({0:'vertex0'})
9874
sage: G.order(); G.size()
9875
4
9876
4
9877
sage: len(G._pos)
9878
4
9879
sage: G.name()
9880
'Cycle graph'
9881
sage: G.get_vertex(0)
9882
'vertex0'
9883
sage: H = G.copy(implementation='c_graph', sparse=True)
9884
sage: H.clear()
9885
sage: H.order(); H.size()
9886
0
9887
0
9888
sage: len(H._pos)
9889
0
9890
sage: H.name()
9891
''
9892
sage: H.get_vertex(0)
9893
sage: H = G.copy(implementation='c_graph', sparse=False)
9894
sage: H.clear()
9895
sage: H.order(); H.size()
9896
0
9897
0
9898
sage: len(H._pos)
9899
0
9900
sage: H.name()
9901
''
9902
sage: H.get_vertex(0)
9903
sage: H = G.copy(implementation='networkx')
9904
sage: H.clear()
9905
sage: H.order(); H.size()
9906
0
9907
0
9908
sage: len(H._pos)
9909
0
9910
sage: H.name()
9911
''
9912
sage: H.get_vertex(0)
9913
"""
9914
self.name('')
9915
self.delete_vertices(self.vertices())
9916
9917
### Degree functions
9918
9919
def degree(self, vertices=None, labels=False):
9920
"""
9921
Gives the degree (in + out for digraphs) of a vertex or of
9922
vertices.
9923
9924
INPUT:
9925
9926
9927
- ``vertices`` - If vertices is a single vertex,
9928
returns the number of neighbors of vertex. If vertices is an
9929
iterable container of vertices, returns a list of degrees. If
9930
vertices is None, same as listing all vertices.
9931
9932
- ``labels`` - see OUTPUT
9933
9934
9935
OUTPUT: Single vertex- an integer. Multiple vertices- a list of
9936
integers. If labels is True, then returns a dictionary mapping each
9937
vertex to its degree.
9938
9939
EXAMPLES::
9940
9941
sage: P = graphs.PetersenGraph()
9942
sage: P.degree(5)
9943
3
9944
9945
::
9946
9947
sage: K = graphs.CompleteGraph(9)
9948
sage: K.degree()
9949
[8, 8, 8, 8, 8, 8, 8, 8, 8]
9950
9951
::
9952
9953
sage: D = DiGraph( { 0: [1,2,3], 1: [0,2], 2: [3], 3: [4], 4: [0,5], 5: [1] } )
9954
sage: D.degree(vertices = [0,1,2], labels=True)
9955
{0: 5, 1: 4, 2: 3}
9956
sage: D.degree()
9957
[5, 4, 3, 3, 3, 2]
9958
"""
9959
if labels:
9960
return dict(self.degree_iterator(vertices,labels))
9961
elif vertices in self and not labels:
9962
return self.degree_iterator(vertices,labels).next()
9963
else:
9964
return list(self.degree_iterator(vertices,labels))
9965
9966
def average_degree(self):
9967
r"""
9968
Returns the average degree of the graph.
9969
9970
The average degree of a graph `G=(V,E)` is equal to
9971
``\frac {2|E|}{|V|}``.
9972
9973
EXAMPLES:
9974
9975
The average degree of a regular graph is equal to the
9976
degree of any vertex::
9977
9978
sage: g = graphs.CompleteGraph(5)
9979
sage: g.average_degree() == 4
9980
True
9981
9982
The average degree of a tree is always strictly less than
9983
`2`::
9984
9985
sage: g = graphs.RandomGNP(20,.5)
9986
sage: tree = Graph()
9987
sage: tree.add_edges(g.min_spanning_tree())
9988
sage: tree.average_degree() < 2
9989
True
9990
9991
For any graph, it is equal to ``\frac {2|E|}{|V|}``::
9992
9993
sage: g = graphs.RandomGNP(50,.8)
9994
sage: g.average_degree() == 2*g.size()/g.order()
9995
True
9996
"""
9997
9998
return 2*Integer(self.size())/Integer(self.order())
9999
10000
def degree_histogram(self):
10001
"""
10002
Returns a list, whose ith entry is the frequency of degree i.
10003
10004
EXAMPLES::
10005
10006
sage: G = graphs.Grid2dGraph(9,12)
10007
sage: G.degree_histogram()
10008
[0, 0, 4, 34, 70]
10009
10010
::
10011
10012
sage: G = graphs.Grid2dGraph(9,12).to_directed()
10013
sage: G.degree_histogram()
10014
[0, 0, 0, 0, 4, 0, 34, 0, 70]
10015
"""
10016
degree_sequence = self.degree()
10017
dmax = max(degree_sequence) + 1
10018
frequency = [0]*dmax
10019
for d in degree_sequence:
10020
frequency[d] += 1
10021
return frequency
10022
10023
def degree_iterator(self, vertices=None, labels=False):
10024
"""
10025
Returns an iterator over the degrees of the (di)graph.
10026
10027
In the case of a digraph, the degree is defined as the sum of the
10028
in-degree and the out-degree, i.e. the total number of edges incident to
10029
a given vertex.
10030
10031
INPUT:
10032
10033
- ``labels`` (boolean) -- if set to ``False`` (default) the method
10034
returns an iterator over degrees. Otherwise it returns an iterator
10035
over tuples (vertex, degree).
10036
10037
- ``vertices`` - if specified, restrict to this
10038
subset.
10039
10040
10041
EXAMPLES::
10042
10043
sage: G = graphs.Grid2dGraph(3,4)
10044
sage: for i in G.degree_iterator():
10045
... print i
10046
3
10047
4
10048
2
10049
...
10050
2
10051
4
10052
sage: for i in G.degree_iterator(labels=True):
10053
... print i
10054
((0, 1), 3)
10055
((1, 2), 4)
10056
((0, 0), 2)
10057
...
10058
((0, 3), 2)
10059
((1, 1), 4)
10060
10061
::
10062
10063
sage: D = graphs.Grid2dGraph(2,4).to_directed()
10064
sage: for i in D.degree_iterator():
10065
... print i
10066
6
10067
6
10068
...
10069
4
10070
6
10071
sage: for i in D.degree_iterator(labels=True):
10072
... print i
10073
((0, 1), 6)
10074
((1, 2), 6)
10075
...
10076
((0, 3), 4)
10077
((1, 1), 6)
10078
"""
10079
if vertices is None:
10080
vertices = self
10081
elif vertices in self:
10082
vertices = [vertices]
10083
else:
10084
vertices = [v for v in vertices if v in self]
10085
if labels:
10086
filter = lambda v, self: (v, self._backend.degree(v, self._directed))
10087
else:
10088
filter = lambda v, self: self._backend.degree(v, self._directed)
10089
for v in vertices:
10090
yield filter(v, self)
10091
10092
def degree_sequence(self):
10093
r"""
10094
Return the degree sequence of this (di)graph.
10095
10096
EXAMPLES:
10097
10098
The degree sequence of an undirected graph::
10099
10100
sage: g = Graph({1: [2, 5], 2: [1, 5, 3, 4], 3: [2, 5], 4: [3], 5: [2, 3]})
10101
sage: g.degree_sequence()
10102
[4, 3, 3, 2, 2]
10103
10104
The degree sequence of a digraph::
10105
10106
sage: g = DiGraph({1: [2, 5, 6], 2: [3, 6], 3: [4, 6], 4: [6], 5: [4, 6]})
10107
sage: g.degree_sequence()
10108
[5, 3, 3, 3, 3, 3]
10109
10110
Degree sequences of some common graphs::
10111
10112
sage: graphs.PetersenGraph().degree_sequence()
10113
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3]
10114
sage: graphs.HouseGraph().degree_sequence()
10115
[3, 3, 2, 2, 2]
10116
sage: graphs.FlowerSnark().degree_sequence()
10117
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]
10118
"""
10119
return sorted(self.degree_iterator(), reverse=True)
10120
10121
def is_regular(self, k = None):
10122
"""
10123
Return ``True`` if this graph is (`k`-)regular.
10124
10125
INPUT:
10126
10127
- ``k`` (default: ``None``) - the degree of regularity to
10128
check for
10129
10130
EXAMPLES::
10131
10132
sage: G = graphs.HoffmanSingletonGraph()
10133
sage: G.is_regular()
10134
True
10135
sage: G.is_regular(9)
10136
False
10137
10138
So the Hoffman-Singleton graph is regular, but not
10139
9-regular. In fact, we can now find the degree easily as
10140
follows::
10141
10142
sage: G.degree_iterator().next()
10143
7
10144
10145
The house graph is not regular::
10146
10147
sage: graphs.HouseGraph().is_regular()
10148
False
10149
10150
A graph without vertices is `k`-regular for every `k`::
10151
10152
sage: Graph().is_regular()
10153
True
10154
"""
10155
if self.order() == 0:
10156
return True
10157
10158
deg_it = self.degree_iterator()
10159
if k is None:
10160
k = deg_it.next()
10161
10162
for d in deg_it:
10163
if d != k:
10164
return False
10165
10166
return True
10167
10168
### Substructures
10169
10170
def subgraph(self, vertices=None, edges=None, inplace=False,
10171
vertex_property=None, edge_property=None, algorithm=None):
10172
"""
10173
Returns the subgraph containing the given vertices and edges.
10174
10175
If either vertices or edges are not specified, they are assumed to be
10176
all vertices or edges. If edges are not specified, returns the subgraph
10177
induced by the vertices.
10178
10179
INPUT:
10180
10181
10182
- ``inplace`` - Using inplace is True will simply
10183
delete the extra vertices and edges from the current graph. This
10184
will modify the graph.
10185
10186
- ``vertices`` - Vertices can be a single vertex or an
10187
iterable container of vertices, e.g. a list, set, graph, file or
10188
numeric array. If not passed, defaults to the entire graph.
10189
10190
- ``edges`` - As with vertices, edges can be a single
10191
edge or an iterable container of edges (e.g., a list, set, file,
10192
numeric array, etc.). If not edges are not specified, then all
10193
edges are assumed and the returned graph is an induced subgraph. In
10194
the case of multiple edges, specifying an edge as (u,v) means to
10195
keep all edges (u,v), regardless of the label.
10196
10197
- ``vertex_property`` - If specified, this is
10198
expected to be a function on vertices, which is intersected with
10199
the vertices specified, if any are.
10200
10201
- ``edge_property`` - If specified, this is expected
10202
to be a function on edges, which is intersected with the edges
10203
specified, if any are.
10204
10205
- ``algorithm`` - If ``algorithm=delete`` or ``inplace=True``,
10206
then the graph is constructed by deleting edges and
10207
vertices. If ``add``, then the graph is constructed by
10208
building a new graph from the appropriate vertices and
10209
edges. If not specified, then the algorithm is chosen based
10210
on the number of vertices in the subgraph.
10211
10212
10213
EXAMPLES::
10214
10215
sage: G = graphs.CompleteGraph(9)
10216
sage: H = G.subgraph([0,1,2]); H
10217
Subgraph of (Complete graph): Graph on 3 vertices
10218
sage: G
10219
Complete graph: Graph on 9 vertices
10220
sage: J = G.subgraph(edges=[(0,1)])
10221
sage: J.edges(labels=False)
10222
[(0, 1)]
10223
sage: J.vertices()==G.vertices()
10224
True
10225
sage: G.subgraph([0,1,2], inplace=True); G
10226
Subgraph of (Complete graph): Graph on 3 vertices
10227
sage: G.subgraph()==G
10228
True
10229
10230
::
10231
10232
sage: D = graphs.CompleteGraph(9).to_directed()
10233
sage: H = D.subgraph([0,1,2]); H
10234
Subgraph of (Complete graph): Digraph on 3 vertices
10235
sage: H = D.subgraph(edges=[(0,1), (0,2)])
10236
sage: H.edges(labels=False)
10237
[(0, 1), (0, 2)]
10238
sage: H.vertices()==D.vertices()
10239
True
10240
sage: D
10241
Complete graph: Digraph on 9 vertices
10242
sage: D.subgraph([0,1,2], inplace=True); D
10243
Subgraph of (Complete graph): Digraph on 3 vertices
10244
sage: D.subgraph()==D
10245
True
10246
10247
A more complicated example involving multiple edges and labels.
10248
10249
::
10250
10251
sage: G = Graph(multiedges=True, sparse=True)
10252
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')])
10253
sage: G.subgraph(edges=[(0,1), (0,2,'d'), (0,2,'not in graph')]).edges()
10254
[(0, 1, 'a'), (0, 1, 'b'), (0, 1, 'c'), (0, 2, 'd')]
10255
sage: J = G.subgraph(vertices=[0,1], edges=[(0,1,'a'), (0,2,'c')])
10256
sage: J.edges()
10257
[(0, 1, 'a')]
10258
sage: J.vertices()
10259
[0, 1]
10260
sage: G.subgraph(vertices=G.vertices())==G
10261
True
10262
10263
::
10264
10265
sage: D = DiGraph(multiedges=True, sparse=True)
10266
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')])
10267
sage: D.subgraph(edges=[(0,1), (0,2,'d'), (0,2,'not in graph')]).edges()
10268
[(0, 1, 'a'), (0, 1, 'b'), (0, 2, 'd')]
10269
sage: H = D.subgraph(vertices=[0,1], edges=[(0,1,'a'), (0,2,'c')])
10270
sage: H.edges()
10271
[(0, 1, 'a')]
10272
sage: H.vertices()
10273
[0, 1]
10274
10275
Using the property arguments::
10276
10277
sage: P = graphs.PetersenGraph()
10278
sage: S = P.subgraph(vertex_property = lambda v : v%2 == 0)
10279
sage: S.vertices()
10280
[0, 2, 4, 6, 8]
10281
10282
::
10283
10284
sage: C = graphs.CubeGraph(2)
10285
sage: S = C.subgraph(edge_property=(lambda e: e[0][0] == e[1][0]))
10286
sage: C.edges()
10287
[('00', '01', None), ('00', '10', None), ('01', '11', None), ('10', '11', None)]
10288
sage: S.edges()
10289
[('00', '01', None), ('10', '11', None)]
10290
10291
10292
The algorithm is not specified, then a reasonable choice is made for speed.
10293
10294
::
10295
10296
sage: g=graphs.PathGraph(1000)
10297
sage: g.subgraph(range(10)) # uses the 'add' algorithm
10298
Subgraph of (Path Graph): Graph on 10 vertices
10299
10300
10301
10302
TESTS: The appropriate properties are preserved.
10303
10304
::
10305
10306
sage: g = graphs.PathGraph(10)
10307
sage: g.is_planar(set_embedding=True)
10308
True
10309
sage: g.set_vertices(dict((v, 'v%d'%v) for v in g.vertices()))
10310
sage: h = g.subgraph([3..5])
10311
sage: h.get_pos().keys()
10312
[3, 4, 5]
10313
sage: h.get_vertices()
10314
{3: 'v3', 4: 'v4', 5: 'v5'}
10315
"""
10316
if vertices is None:
10317
vertices=self.vertices()
10318
elif vertices in self:
10319
vertices=[vertices]
10320
else:
10321
vertices=list(vertices)
10322
10323
if vertex_property is not None:
10324
vertices = [v for v in vertices if vertex_property(v)]
10325
10326
if algorithm is not None and algorithm not in ("delete", "add"):
10327
raise ValueError('algorithm should be None, "delete", or "add"')
10328
10329
if inplace or len(vertices)>0.05*self.order() or algorithm=="delete":
10330
return self._subgraph_by_deleting(vertices=vertices, edges=edges,
10331
inplace=inplace,
10332
edge_property=edge_property)
10333
else:
10334
return self._subgraph_by_adding(vertices=vertices, edges=edges,
10335
edge_property=edge_property)
10336
10337
def _subgraph_by_adding(self, vertices=None, edges=None, edge_property=None):
10338
"""
10339
Returns the subgraph containing the given vertices and edges.
10340
The edges also satisfy the edge_property, if it is not None.
10341
The subgraph is created by creating a new empty graph and
10342
adding the necessary vertices, edges, and other properties.
10343
10344
INPUT:
10345
10346
- ``vertices`` - Vertices is a list of vertices
10347
10348
- ``edges`` - Edges can be a single edge or an iterable
10349
container of edges (e.g., a list, set, file, numeric array,
10350
etc.). If not edges are not specified, then all edges are
10351
assumed and the returned graph is an induced subgraph. In
10352
the case of multiple edges, specifying an edge as (u,v)
10353
means to keep all edges (u,v), regardless of the label.
10354
10355
- ``edge_property`` - If specified, this is expected
10356
to be a function on edges, which is intersected with the edges
10357
specified, if any are.
10358
10359
10360
EXAMPLES::
10361
10362
sage: G = graphs.CompleteGraph(9)
10363
sage: H = G._subgraph_by_adding([0,1,2]); H
10364
Subgraph of (Complete graph): Graph on 3 vertices
10365
sage: G
10366
Complete graph: Graph on 9 vertices
10367
sage: J = G._subgraph_by_adding(vertices=G.vertices(), edges=[(0,1)])
10368
sage: J.edges(labels=False)
10369
[(0, 1)]
10370
sage: J.vertices()==G.vertices()
10371
True
10372
sage: G._subgraph_by_adding(vertices=G.vertices())==G
10373
True
10374
10375
::
10376
10377
sage: D = graphs.CompleteGraph(9).to_directed()
10378
sage: H = D._subgraph_by_adding([0,1,2]); H
10379
Subgraph of (Complete graph): Digraph on 3 vertices
10380
sage: H = D._subgraph_by_adding(vertices=D.vertices(), edges=[(0,1), (0,2)])
10381
sage: H.edges(labels=False)
10382
[(0, 1), (0, 2)]
10383
sage: H.vertices()==D.vertices()
10384
True
10385
sage: D
10386
Complete graph: Digraph on 9 vertices
10387
sage: D._subgraph_by_adding(D.vertices())==D
10388
True
10389
10390
A more complicated example involving multiple edges and labels.
10391
10392
::
10393
10394
sage: G = Graph(multiedges=True, sparse=True)
10395
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')])
10396
sage: G._subgraph_by_adding(G.vertices(), edges=[(0,1), (0,2,'d'), (0,2,'not in graph')]).edges()
10397
[(0, 1, 'a'), (0, 1, 'b'), (0, 1, 'c'), (0, 2, 'd')]
10398
sage: J = G._subgraph_by_adding(vertices=[0,1], edges=[(0,1,'a'), (0,2,'c')])
10399
sage: J.edges()
10400
[(0, 1, 'a')]
10401
sage: J.vertices()
10402
[0, 1]
10403
sage: G._subgraph_by_adding(vertices=G.vertices())==G
10404
True
10405
10406
::
10407
10408
sage: D = DiGraph(multiedges=True, sparse=True)
10409
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')])
10410
sage: D._subgraph_by_adding(vertices=D.vertices(), edges=[(0,1), (0,2,'d'), (0,2,'not in graph')]).edges()
10411
[(0, 1, 'a'), (0, 1, 'b'), (0, 2, 'd')]
10412
sage: H = D._subgraph_by_adding(vertices=[0,1], edges=[(0,1,'a'), (0,2,'c')])
10413
sage: H.edges()
10414
[(0, 1, 'a')]
10415
sage: H.vertices()
10416
[0, 1]
10417
10418
Using the property arguments::
10419
10420
sage: C = graphs.CubeGraph(2)
10421
sage: S = C._subgraph_by_adding(vertices=C.vertices(), edge_property=(lambda e: e[0][0] == e[1][0]))
10422
sage: C.edges()
10423
[('00', '01', None), ('00', '10', None), ('01', '11', None), ('10', '11', None)]
10424
sage: S.edges()
10425
[('00', '01', None), ('10', '11', None)]
10426
10427
TESTS: Properties of the graph are preserved.
10428
10429
::
10430
10431
sage: g = graphs.PathGraph(10)
10432
sage: g.is_planar(set_embedding=True)
10433
True
10434
sage: g.set_vertices(dict((v, 'v%d'%v) for v in g.vertices()))
10435
sage: h = g._subgraph_by_adding([3..5])
10436
sage: h.get_pos().keys()
10437
[3, 4, 5]
10438
sage: h.get_vertices()
10439
{3: 'v3', 4: 'v4', 5: 'v5'}
10440
"""
10441
G = self.__class__(weighted=self._weighted, loops=self.allows_loops(),
10442
multiedges= self.allows_multiple_edges())
10443
G.name("Subgraph of (%s)"%self.name())
10444
G.add_vertices(vertices)
10445
if edges is not None:
10446
if G._directed:
10447
edges_graph = (e for e in self.edge_iterator(vertices) if e[1] in vertices)
10448
edges_to_keep_labeled = [e for e in edges if len(e)==3]
10449
edges_to_keep_unlabeled = [e for e in edges if len(e)==2]
10450
else:
10451
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)
10452
edges_to_keep_labeled = [sorted(e[0:2])+[e[2]] for e in edges if len(e)==3]
10453
edges_to_keep_unlabeled = [sorted(e) for e in edges if len(e)==2]
10454
edges_to_keep = [tuple(e) for e in edges_graph if e in edges_to_keep_labeled
10455
or e[0:2] in edges_to_keep_unlabeled]
10456
else:
10457
edges_to_keep=[e for e in self.edge_iterator(vertices) if e[0] in vertices and e[1] in vertices]
10458
10459
if edge_property is not None:
10460
edges_to_keep = [e for e in edges_to_keep if edge_property(e)]
10461
G.add_edges(edges_to_keep)
10462
10463
attributes_to_update = ('_pos', '_assoc')
10464
for attr in attributes_to_update:
10465
if hasattr(self, attr) and getattr(self, attr) is not None:
10466
value = dict([(v, getattr(self, attr).get(v, None)) for v in G])
10467
setattr(G, attr,value)
10468
10469
G._boundary = [v for v in self._boundary if v in G]
10470
10471
return G
10472
10473
def _subgraph_by_deleting(self, vertices=None, edges=None, inplace=False,
10474
edge_property=None):
10475
"""
10476
Returns the subgraph containing the given vertices and edges.
10477
The edges also satisfy the edge_property, if it is not None.
10478
The subgraph is created by creating deleting things that are
10479
not needed.
10480
10481
INPUT:
10482
10483
- ``vertices`` - Vertices is a list of vertices
10484
10485
- ``edges`` - Edges can be a single edge or an iterable
10486
container of edges (e.g., a list, set, file, numeric array,
10487
etc.). If not edges are not specified, then all edges are
10488
assumed and the returned graph is an induced subgraph. In
10489
the case of multiple edges, specifying an edge as (u,v)
10490
means to keep all edges (u,v), regardless of the label.
10491
10492
- ``edge_property`` - If specified, this is expected
10493
to be a function on edges, which is intersected with the edges
10494
specified, if any are.
10495
10496
- ``inplace`` - Using inplace is True will simply
10497
delete the extra vertices and edges from the current graph. This
10498
will modify the graph.
10499
10500
10501
EXAMPLES::
10502
10503
sage: G = graphs.CompleteGraph(9)
10504
sage: H = G._subgraph_by_deleting([0,1,2]); H
10505
Subgraph of (Complete graph): Graph on 3 vertices
10506
sage: G
10507
Complete graph: Graph on 9 vertices
10508
sage: J = G._subgraph_by_deleting(vertices=G.vertices(), edges=[(0,1)])
10509
sage: J.edges(labels=False)
10510
[(0, 1)]
10511
sage: J.vertices()==G.vertices()
10512
True
10513
sage: G._subgraph_by_deleting([0,1,2], inplace=True); G
10514
Subgraph of (Complete graph): Graph on 3 vertices
10515
sage: G._subgraph_by_deleting(vertices=G.vertices())==G
10516
True
10517
10518
::
10519
10520
sage: D = graphs.CompleteGraph(9).to_directed()
10521
sage: H = D._subgraph_by_deleting([0,1,2]); H
10522
Subgraph of (Complete graph): Digraph on 3 vertices
10523
sage: H = D._subgraph_by_deleting(vertices=D.vertices(), edges=[(0,1), (0,2)])
10524
sage: H.edges(labels=False)
10525
[(0, 1), (0, 2)]
10526
sage: H.vertices()==D.vertices()
10527
True
10528
sage: D
10529
Complete graph: Digraph on 9 vertices
10530
sage: D._subgraph_by_deleting([0,1,2], inplace=True); D
10531
Subgraph of (Complete graph): Digraph on 3 vertices
10532
sage: D._subgraph_by_deleting(D.vertices())==D
10533
True
10534
10535
A more complicated example involving multiple edges and labels.
10536
10537
::
10538
10539
sage: G = Graph(multiedges=True, sparse=True)
10540
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')])
10541
sage: G._subgraph_by_deleting(G.vertices(), edges=[(0,1), (0,2,'d'), (0,2,'not in graph')]).edges()
10542
[(0, 1, 'a'), (0, 1, 'b'), (0, 1, 'c'), (0, 2, 'd')]
10543
sage: J = G._subgraph_by_deleting(vertices=[0,1], edges=[(0,1,'a'), (0,2,'c')])
10544
sage: J.edges()
10545
[(0, 1, 'a')]
10546
sage: J.vertices()
10547
[0, 1]
10548
sage: G._subgraph_by_deleting(vertices=G.vertices())==G
10549
True
10550
10551
::
10552
10553
sage: D = DiGraph(multiedges=True, sparse=True)
10554
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')])
10555
sage: D._subgraph_by_deleting(vertices=D.vertices(), edges=[(0,1), (0,2,'d'), (0,2,'not in graph')]).edges()
10556
[(0, 1, 'a'), (0, 1, 'b'), (0, 2, 'd')]
10557
sage: H = D._subgraph_by_deleting(vertices=[0,1], edges=[(0,1,'a'), (0,2,'c')])
10558
sage: H.edges()
10559
[(0, 1, 'a')]
10560
sage: H.vertices()
10561
[0, 1]
10562
10563
Using the property arguments::
10564
10565
sage: C = graphs.CubeGraph(2)
10566
sage: S = C._subgraph_by_deleting(vertices=C.vertices(), edge_property=(lambda e: e[0][0] == e[1][0]))
10567
sage: C.edges()
10568
[('00', '01', None), ('00', '10', None), ('01', '11', None), ('10', '11', None)]
10569
sage: S.edges()
10570
[('00', '01', None), ('10', '11', None)]
10571
10572
TESTS: Properties of the graph are preserved.
10573
10574
::
10575
10576
sage: g = graphs.PathGraph(10)
10577
sage: g.is_planar(set_embedding=True)
10578
True
10579
sage: g.set_vertices(dict((v, 'v%d'%v) for v in g.vertices()))
10580
sage: h = g._subgraph_by_deleting([3..5])
10581
sage: h.get_pos().keys()
10582
[3, 4, 5]
10583
sage: h.get_vertices()
10584
{3: 'v3', 4: 'v4', 5: 'v5'}
10585
"""
10586
if inplace:
10587
G = self
10588
else:
10589
G = self.copy()
10590
G.name("Subgraph of (%s)"%self.name())
10591
10592
G.delete_vertices([v for v in G if v not in vertices])
10593
10594
edges_to_delete=[]
10595
if edges is not None:
10596
if G._directed:
10597
edges_graph = G.edge_iterator()
10598
edges_to_keep_labeled = [e for e in edges if len(e)==3]
10599
edges_to_keep_unlabeled = [e for e in edges if len(e)==2]
10600
else:
10601
edges_graph = [sorted(e[0:2])+[e[2]] for e in G.edge_iterator()]
10602
edges_to_keep_labeled = [sorted(e[0:2])+[e[2]] for e in edges if len(e)==3]
10603
edges_to_keep_unlabeled = [sorted(e) for e in edges if len(e)==2]
10604
for e in edges_graph:
10605
if e not in edges_to_keep_labeled and e[0:2] not in edges_to_keep_unlabeled:
10606
edges_to_delete.append(tuple(e))
10607
if edge_property is not None:
10608
for e in G.edge_iterator():
10609
if not edge_property(e):
10610
# We might get duplicate edges, but this does
10611
# handle the case of multiple edges.
10612
edges_to_delete.append(e)
10613
10614
G.delete_edges(edges_to_delete)
10615
if not inplace:
10616
return G
10617
10618
def subgraph_search(self, G, induced=False):
10619
r"""
10620
Returns a copy of ``G`` in ``self``.
10621
10622
INPUT:
10623
10624
- ``G`` -- the graph whose copy we are looking for in ``self``.
10625
10626
- ``induced`` -- boolean (default: ``False``). Whether or not to
10627
search for an induced copy of ``G`` in ``self``.
10628
10629
OUTPUT:
10630
10631
- If ``induced=False``, return a copy of ``G`` in this graph.
10632
Otherwise, return an induced copy of ``G`` in ``self``. If ``G``
10633
is the empty graph, return the empty graph since it is a subgraph
10634
of every graph. Now suppose ``G`` is not the empty graph. If there
10635
is no copy (induced or otherwise) of ``G`` in ``self``, we return
10636
``None``.
10637
10638
.. NOTE::
10639
10640
This method also works on digraphs.
10641
10642
.. SEEALSO::
10643
10644
- :meth:`~GenericGraph.subgraph_search_count` -- Counts the number
10645
of copies of a graph `H` inside of a graph `G`
10646
10647
- :meth:`~GenericGraph.subgraph_search_iterator` -- Iterate on the
10648
copies of a graph `H` inside of a graph `G`
10649
10650
ALGORITHM:
10651
10652
Brute-force search.
10653
10654
EXAMPLES:
10655
10656
The Petersen graph contains the path graph `P_5`::
10657
10658
sage: g = graphs.PetersenGraph()
10659
sage: h1 = g.subgraph_search(graphs.PathGraph(5)); h1
10660
Subgraph of (Petersen graph): Graph on 5 vertices
10661
sage: h1.vertices(); h1.edges(labels=False)
10662
[0, 1, 2, 3, 4]
10663
[(0, 1), (1, 2), (2, 3), (3, 4)]
10664
sage: I1 = g.subgraph_search(graphs.PathGraph(5), induced=True); I1
10665
Subgraph of (Petersen graph): Graph on 5 vertices
10666
sage: I1.vertices(); I1.edges(labels=False)
10667
[0, 1, 2, 3, 8]
10668
[(0, 1), (1, 2), (2, 3), (3, 8)]
10669
10670
It also contains the claw `K_{1,3}`::
10671
10672
sage: h2 = g.subgraph_search(graphs.ClawGraph()); h2
10673
Subgraph of (Petersen graph): Graph on 4 vertices
10674
sage: h2.vertices(); h2.edges(labels=False)
10675
[0, 1, 4, 5]
10676
[(0, 1), (0, 4), (0, 5)]
10677
sage: I2 = g.subgraph_search(graphs.ClawGraph(), induced=True); I2
10678
Subgraph of (Petersen graph): Graph on 4 vertices
10679
sage: I2.vertices(); I2.edges(labels=False)
10680
[0, 1, 4, 5]
10681
[(0, 1), (0, 4), (0, 5)]
10682
10683
Of course the induced copies are isomorphic to the graphs we were
10684
looking for::
10685
10686
sage: I1.is_isomorphic(graphs.PathGraph(5))
10687
True
10688
sage: I2.is_isomorphic(graphs.ClawGraph())
10689
True
10690
10691
However, the Petersen graph does not contain a subgraph isomorphic to
10692
`K_3`::
10693
10694
sage: g.subgraph_search(graphs.CompleteGraph(3)) is None
10695
True
10696
10697
Nor does it contain a nonempty induced subgraph isomorphic to `P_6`::
10698
10699
sage: g.subgraph_search(graphs.PathGraph(6), induced=True) is None
10700
True
10701
10702
The empty graph is a subgraph of every graph::
10703
10704
sage: g.subgraph_search(graphs.EmptyGraph())
10705
Graph on 0 vertices
10706
sage: g.subgraph_search(graphs.EmptyGraph(), induced=True)
10707
Graph on 0 vertices
10708
10709
The subgraph may just have edges missing::
10710
10711
sage: k3=graphs.CompleteGraph(3); p3=graphs.PathGraph(3)
10712
sage: k3.relabel(list('abc'))
10713
sage: s=k3.subgraph_search(p3)
10714
sage: s.edges(labels=False)
10715
[('a', 'b'), ('b', 'c')]
10716
10717
Of course, `P_3` is not an induced subgraph of `K_3`, though::
10718
10719
sage: k3=graphs.CompleteGraph(3); p3=graphs.PathGraph(3)
10720
sage: k3.relabel(list('abc'))
10721
sage: k3.subgraph_search(p3, induced=True) is None
10722
True
10723
10724
TESTS:
10725
10726
Inside of a small graph (:trac:`13906`)::
10727
10728
sage: Graph(5).subgraph_search(Graph(1))
10729
Graph on 1 vertex
10730
"""
10731
from sage.graphs.generic_graph_pyx import SubgraphSearch
10732
from sage.graphs.graph_generators import GraphGenerators
10733
10734
if G.order() == 0:
10735
return GraphGenerators().EmptyGraph()
10736
10737
# SubgraphSearch assumes the graph we are searching for has order at least 2.
10738
if G.order() == 1:
10739
if self.order() >= 1:
10740
import graph
10741
return graph.Graph({ self.vertices()[0]:[]})
10742
else:
10743
return None
10744
10745
S = SubgraphSearch(self, G, induced = induced)
10746
10747
for g in S:
10748
if induced:
10749
return self.subgraph(g)
10750
else:
10751
Gcopy=G.copy()
10752
Gcopy.relabel(g)
10753
return self.subgraph(vertices=Gcopy.vertices(), edges=Gcopy.edges())
10754
10755
return None
10756
10757
def subgraph_search_count(self, G, induced=False):
10758
r"""
10759
Returns the number of labelled occurences of ``G`` in ``self``.
10760
10761
INPUT:
10762
10763
- ``G`` -- the graph whose copies we are looking for in
10764
``self``.
10765
10766
- ``induced`` -- boolean (default: ``False``). Whether or not
10767
to count induced copies of ``G`` in ``self``.
10768
10769
ALGORITHM:
10770
10771
Brute-force search.
10772
10773
.. NOTE::
10774
10775
This method also works on digraphs.
10776
10777
.. SEEALSO::
10778
10779
- :meth:`~GenericGraph.subgraph_search` -- finds an subgraph
10780
isomorphic to `H` inside of a graph `G`
10781
10782
- :meth:`~GenericGraph.subgraph_search_iterator` -- Iterate on the
10783
copies of a graph `H` inside of a graph `G`
10784
10785
EXAMPLES:
10786
10787
Counting the number of paths `P_5` in a PetersenGraph::
10788
10789
sage: g = graphs.PetersenGraph()
10790
sage: g.subgraph_search_count(graphs.PathGraph(5))
10791
240
10792
10793
Requiring these subgraphs be induced::
10794
10795
sage: g.subgraph_search_count(graphs.PathGraph(5), induced = True)
10796
120
10797
10798
If we define the graph `T_k` (the transitive tournament on `k`
10799
vertices) as the graph on `\{0, ..., k-1\}` such that `ij \in
10800
T_k` iif `i<j`, how many directed triangles can be found in
10801
`T_5` ? The answer is of course `0` ::
10802
10803
sage: T5 = DiGraph()
10804
sage: T5.add_edges([(i,j) for i in xrange(5) for j in xrange(i+1, 5)])
10805
sage: T5.subgraph_search_count(digraphs.Circuit(3))
10806
0
10807
10808
If we count instead the number of `T_3` in `T_5`, we expect
10809
the answer to be `{5 \choose 3}`::
10810
10811
sage: T3 = T5.subgraph([0,1,2])
10812
sage: T5.subgraph_search_count(T3)
10813
10
10814
sage: binomial(5,3)
10815
10
10816
10817
The empty graph is a subgraph of every graph::
10818
10819
sage: g.subgraph_search_count(graphs.EmptyGraph())
10820
1
10821
10822
TESTS:
10823
10824
Inside of a small graph (:trac:`13906`)::
10825
10826
sage: Graph(5).subgraph_search_count(Graph(1))
10827
5
10828
"""
10829
from sage.graphs.generic_graph_pyx import SubgraphSearch
10830
10831
if G.order() == 0:
10832
return 1
10833
10834
if self.order() == 0:
10835
return 0
10836
10837
if G.order() == 1:
10838
return self.order()
10839
10840
S = SubgraphSearch(self, G, induced = induced)
10841
10842
return S.cardinality()
10843
10844
def subgraph_search_iterator(self, G, induced=False):
10845
r"""
10846
Returns an iterator over the labelled copies of ``G`` in ``self``.
10847
10848
INPUT:
10849
10850
- ``G`` -- the graph whose copies we are looking for in
10851
``self``.
10852
10853
- ``induced`` -- boolean (default: ``False``). Whether or not
10854
to iterate over the induced copies of ``G`` in ``self``.
10855
10856
ALGORITHM:
10857
10858
Brute-force search.
10859
10860
OUTPUT:
10861
10862
Iterator over the labelled copies of ``G`` in ``self``, as
10863
*lists*. For each value `(v_1, v_2, ..., v_k)` returned,
10864
the first vertex of `G` is associated with `v_1`, the
10865
second with `v_2`, etc ...
10866
10867
.. NOTE::
10868
10869
This method also works on digraphs.
10870
10871
.. SEEALSO::
10872
10873
- :meth:`~GenericGraph.subgraph_search` -- finds an subgraph
10874
isomorphic to `H` inside of a graph `G`
10875
10876
- :meth:`~GenericGraph.subgraph_search_count` -- Counts the number
10877
of copies of a graph `H` inside of a graph `G`
10878
10879
EXAMPLE:
10880
10881
Iterating through all the labelled `P_3` of `P_5`::
10882
10883
sage: g = graphs.PathGraph(5)
10884
sage: for p in g.subgraph_search_iterator(graphs.PathGraph(3)):
10885
... print p
10886
[0, 1, 2]
10887
[1, 2, 3]
10888
[2, 1, 0]
10889
[2, 3, 4]
10890
[3, 2, 1]
10891
[4, 3, 2]
10892
10893
TESTS:
10894
10895
Inside of a small graph (:trac:`13906`)::
10896
10897
sage: list(Graph(5).subgraph_search_iterator(Graph(1)))
10898
[Graph on 1 vertex, Graph on 1 vertex, Graph on 1 vertex, Graph on 1 vertex, Graph on 1 vertex]
10899
"""
10900
10901
if G.order() == 0:
10902
from sage.graphs.graph_generators import GraphGenerators
10903
return [GraphGenerators().EmptyGraph()]
10904
10905
elif self.order() == 0:
10906
return []
10907
10908
elif G.order() == 1:
10909
import graph
10910
return iter([graph.Graph({v:[]}) for v in self.vertices()])
10911
else:
10912
from sage.graphs.generic_graph_pyx import SubgraphSearch
10913
return SubgraphSearch(self, G, induced = induced)
10914
10915
def random_subgraph(self, p, inplace=False):
10916
"""
10917
Return a random subgraph that contains each vertex with prob. p.
10918
10919
EXAMPLES::
10920
10921
sage: P = graphs.PetersenGraph()
10922
sage: P.random_subgraph(.25)
10923
Subgraph of (Petersen graph): Graph on 4 vertices
10924
"""
10925
vertices = []
10926
p = float(p)
10927
for v in self:
10928
if random() < p:
10929
vertices.append(v)
10930
return self.subgraph(vertices=vertices, inplace=inplace)
10931
10932
def is_chordal(self, certificate = False, algorithm = "B"):
10933
r"""
10934
Tests whether the given graph is chordal.
10935
10936
A Graph `G` is said to be chordal if it contains no induced hole (a
10937
cycle of length at least 4).
10938
10939
Alternatively, chordality can be defined using a Perfect Elimination
10940
Order :
10941
10942
A Perfect Elimination Order of a graph `G` is an ordering `v_1,...,v_n`
10943
of its vertex set such that for all `i`, the neighbors of `v_i` whose
10944
index is greater that `i` induce a complete subgraph in `G`. Hence, the
10945
graph `G` can be totally erased by successively removing vertices whose
10946
neighborhood is a clique (also called *simplicial* vertices)
10947
[Fulkerson65]_.
10948
10949
(It can be seen that if `G` contains an induced hole, then it can not
10950
have a perfect elimination order. Indeed, if we write `h_1,...,h_k` the
10951
`k` vertices of such a hole, then the first of those vertices to be
10952
removed would have two non-adjacent neighbors in the graph.)
10953
10954
A Graph is then chordal if and only if it has a Perfect Elimination
10955
Order.
10956
10957
INPUT:
10958
10959
- ``certificate`` (boolean) -- Whether to return a certificate.
10960
10961
* If ``certificate = False`` (default), returns ``True`` or
10962
``False`` accordingly.
10963
10964
* If ``certificate = True``, returns :
10965
10966
* ``(True, peo)`` when the graph is chordal, where ``peo`` is a
10967
perfect elimination order of its vertices.
10968
10969
* ``(False, Hole)`` when the graph is not chordal, where
10970
``Hole`` (a ``Graph`` object) is an induced subgraph of
10971
``self`` isomorphic to a hole.
10972
10973
- ``algorithm`` -- Two algorithms are available for this method (see
10974
next section), which can be selected by setting ``algorithm = "A"`` or
10975
``algorithm = "B"`` (default). While they will agree on whether the
10976
given graph is chordal, they can not be expected to return the same
10977
certificates.
10978
10979
ALGORITHM:
10980
10981
This algorithm works through computing a Lex BFS on the graph, then
10982
checking whether the order is a Perfect Elimination Order by computing
10983
for each vertex `v` the subgraph induces by its non-deleted neighbors,
10984
then testing whether this graph is complete.
10985
10986
This problem can be solved in `O(m)` [Rose75]_ ( where `m` is the number
10987
of edges in the graph ) but this implementation is not linear because of
10988
the complexity of Lex BFS.
10989
10990
.. NOTE::
10991
10992
Because of a past bug (#11735, #11961), the first implementation
10993
(algorithm A) of this method sometimes returned as certificates
10994
subgraphs which were **not** holes. Since then, this bug has been
10995
fixed and the values are now double-checked before being returned,
10996
so that the algorithm only returns correct values or raises an
10997
exception. In the case where an exception is raised, the user is
10998
advised to switch to the other algorithm. And to **please** report
10999
the bug :-)
11000
11001
EXAMPLES:
11002
11003
The lexicographic product of a Path and a Complete Graph
11004
is chordal ::
11005
11006
sage: g = graphs.PathGraph(5).lexicographic_product(graphs.CompleteGraph(3))
11007
sage: g.is_chordal()
11008
True
11009
11010
The same goes with the product of a random lobster
11011
( which is a tree ) and a Complete Graph ::
11012
11013
sage: g = graphs.RandomLobster(10,.5,.5).lexicographic_product(graphs.CompleteGraph(3))
11014
sage: g.is_chordal()
11015
True
11016
11017
The disjoint union of chordal graphs is still chordal::
11018
11019
sage: (2*g).is_chordal()
11020
True
11021
11022
Let us check the certificate given by Sage is indeed a perfect elimintion order::
11023
11024
sage: (_, peo) = g.is_chordal(certificate = True)
11025
sage: for v in peo:
11026
... if not g.subgraph(g.neighbors(v)).is_clique():
11027
... print "This should never happen !"
11028
... g.delete_vertex(v)
11029
sage: print "Everything is fine !"
11030
Everything is fine !
11031
11032
Of course, the Petersen Graph is not chordal as it has girth 5 ::
11033
11034
sage: g = graphs.PetersenGraph()
11035
sage: g.girth()
11036
5
11037
sage: g.is_chordal()
11038
False
11039
11040
We can even obtain such a cycle as a certificate ::
11041
11042
sage: (_, hole) = g.is_chordal(certificate = True)
11043
sage: hole
11044
Subgraph of (Petersen graph): Graph on 5 vertices
11045
sage: hole.is_isomorphic(graphs.CycleGraph(5))
11046
True
11047
11048
TESTS:
11049
11050
This shouldn't raise exceptions (:trac:`10899`)::
11051
11052
sage: Graph(1).is_chordal()
11053
True
11054
sage: for g in graphs(5):
11055
....: _ = g.is_chordal()
11056
11057
REFERENCES:
11058
11059
.. [Rose75] Rose, D.J. and Tarjan, R.E.,
11060
Algorithmic aspects of vertex elimination,
11061
Proceedings of seventh annual ACM symposium on Theory of computing
11062
Page 254, ACM 1975
11063
11064
.. [Fulkerson65] Fulkerson, D.R. and Gross, OA
11065
Incidence matrices and interval graphs
11066
Pacific J. Math 1965
11067
Vol. 15, number 3, pages 835--855
11068
11069
TESTS:
11070
11071
Trac Ticket #11735::
11072
11073
sage: g = Graph({3:[2,1,4],2:[1],4:[1],5:[2,1,4]})
11074
sage: _, g1 = g.is_chordal(certificate=True); g1.is_chordal()
11075
False
11076
sage: g1.is_isomorphic(graphs.CycleGraph(g1.order()))
11077
True
11078
"""
11079
self._scream_if_not_simple()
11080
11081
# If the graph is not connected, we are computing the result on each component
11082
if not self.is_connected():
11083
11084
# If the user wants a certificate, we had no choice but to
11085
# collect the perfect elimination orders... But we return
11086
# a hole immediately if we find any !
11087
if certificate:
11088
peo = []
11089
for gg in self.connected_components_subgraphs():
11090
11091
b, certif = gg.is_chordal(certificate = True)
11092
if not b:
11093
return False, certif
11094
else:
11095
peo.extend(certif)
11096
11097
return True, peo
11098
11099
# One line if no certificate is requested
11100
else:
11101
return all( gg.is_chordal() for gg in self.connected_components_subgraphs() )
11102
11103
hole = None
11104
g = self.copy()
11105
11106
# Algorithms
11107
#
11108
# They find the perfect elimination ordering or produce a hole
11109
11110
if algorithm == "A":
11111
11112
peo,t_peo = self.lex_BFS(tree=True)
11113
peo.reverse()
11114
11115
# Iteratively removing vertices and checking everything is fine.
11116
for v in peo:
11117
11118
if t_peo.out_degree(v) == 0:
11119
g.delete_vertex(v)
11120
continue
11121
11122
x = t_peo.neighbor_out_iterator(v).next()
11123
S = self.neighbors(x)+[x]
11124
11125
if not frozenset(g.neighbors(v)).issubset(S):
11126
11127
# Do we need to return a hole ?
11128
if certificate:
11129
11130
# In this case, let us take two nonadjacent neighbors of v
11131
# In this case, let us take two nonadjacent neighbors of
11132
# v. In order to do so, we pick a vertex y which is a
11133
# neighbor of v but is not adjacent to x, which we know
11134
# exists by the test written two lines above.
11135
11136
for y in g.neighbors(v):
11137
if y not in S:
11138
break
11139
11140
g.delete_vertices([vv for vv in g.neighbors(v) if vv != y and vv != x])
11141
g.delete_vertex(v)
11142
11143
# Our hole is v + (a shortest path between x and y not
11144
# containing v or any of its neighbors).
11145
11146
hole = self.subgraph([v] + g.shortest_path(x,y))
11147
11148
# End of the algorithm
11149
break
11150
else:
11151
return False
11152
11153
g.delete_vertex(v)
11154
11155
elif algorithm == "B":
11156
11157
peo,t_peo = self.lex_BFS(reverse=True, tree=True)
11158
11159
# Remembering the (closed) neighborhoods of each vertex
11160
neighbors_subsets = dict([(v,self.neighbors(v)+[v]) for v in g])
11161
pos_in_peo = dict(zip(peo, range(self.order())))
11162
11163
# Iteratively removing vertices and checking everything is fine.
11164
for v in reversed(peo):
11165
11166
if (t_peo.out_degree(v)>0 and
11167
not frozenset([v1 for v1 in g.neighbors(v) if pos_in_peo[v1] > pos_in_peo[v]]).issubset(
11168
neighbors_subsets[t_peo.neighbor_out_iterator(v).next()])):
11169
11170
# Do we need to return a hole ?
11171
if certificate:
11172
11173
# In this case, let us take two nonadjacent neighbors of
11174
# v. In order to do so, we pick a vertex y which is a
11175
# neighbor of v but is not adjacent to x, which we know
11176
# exists by the test written two lines above.
11177
max_tup = (-1, 0)
11178
nb1 = [u for u in g.neighbors(v) if pos_in_peo[u] > pos_in_peo[v]]
11179
for xi in nb1:
11180
for yi in nb1:
11181
if not yi in neighbors_subsets[xi]:
11182
new_tup = (pos_in_peo[xi], pos_in_peo[yi])
11183
if max_tup < new_tup:
11184
max_tup = new_tup
11185
x, y = xi, yi
11186
11187
# Our hole is v + (a shortest path between x and y not
11188
# containing v or any of its neighbors).
11189
11190
#g.delete_vertices([vv for vv in g.vertices() if pos_in_peo[vv] < pos_in_peo[v]])
11191
11192
g.delete_vertices([vv for vv in g.neighbors(v) if vv != y and vv != x])
11193
g.delete_vertex(v)
11194
11195
hole = self.subgraph([v] + g.shortest_path(x,y))
11196
11197
# End of the algorithm
11198
break
11199
else:
11200
return False
11201
11202
11203
# Returning values
11204
# ----------------
11205
11206
# 1- The graph is not chordal
11207
11208
if not hole is None:
11209
# There was a bug there once, so it's better to check the
11210
# answer is valid, especally when it is so cheap ;-)
11211
11212
if hole.order() <= 3 or not hole.is_regular(k=2):
11213
raise RuntimeError("the graph is not chordal, and something went wrong in the computation of the certificate. Please report this bug, providing the graph if possible!")
11214
11215
return (False, hole)
11216
11217
11218
# 2- The graph is chordal
11219
if certificate:
11220
return True, peo
11221
11222
else:
11223
return True
11224
11225
def is_circulant(self, certificate = False):
11226
r"""
11227
Tests whether the graph is circulant.
11228
11229
For more information on circulant graphs, see the
11230
:wikipedia:`Wikipedia page on circulant graphs
11231
<Circulant_graph>`.
11232
11233
INPUT:
11234
11235
- ``certificate`` (boolean) -- whether to return a certificate for
11236
yes-answers. See OUTPUT section. Set to ``False`` by default.
11237
11238
OUTPUT:
11239
11240
When ``certificate`` is set to ``False`` (default) this method only
11241
returns ``True`` or ``False`` answers. When ``certificate`` is set to
11242
``True``, the method either returns ``(False, None)`` or ``(True,
11243
lists_of_parameters)`` each element of ``lists_of_parameters`` can be
11244
used to define the graph as a circulant graph.
11245
11246
See the documentation of
11247
:func:`~sage.graphs.graph_generators.GraphGenerators.CirculantGraph` and
11248
:meth:`~sage.graphs.digraph_generators.DiGraphGenerators.Circulant` for
11249
more information, and the examples below.
11250
11251
.. SEEALSO::
11252
11253
:meth:`~sage.graphs.graph_generators.GraphGenerators.CirculantGraph`
11254
-- a constructor for circulant graphs.
11255
11256
EXAMPLES:
11257
11258
The Petersen graph is not a circulant graph::
11259
11260
sage: g = graphs.PetersenGraph()
11261
sage: g.is_circulant()
11262
False
11263
11264
A cycle is obviously a circulant graph, but several sets of parameters
11265
can be used to define it::
11266
11267
sage: g = graphs.CycleGraph(5)
11268
sage: g.is_circulant(certificate = True)
11269
(True, [(5, [1, 4]), (5, [2, 3])])
11270
11271
The same goes for directed graphs::
11272
11273
sage: g = digraphs.Circuit(5)
11274
sage: g.is_circulant(certificate = True)
11275
(True, [(5, [1]), (5, [3]), (5, [2]), (5, [4])])
11276
11277
With this information, it is very easy to create (and plot) all possible
11278
drawings of a circulant graph::
11279
11280
sage: g = graphs.CirculantGraph(13, [2, 3, 10, 11])
11281
sage: for param in g.is_circulant(certificate = True)[1]:
11282
... graphs.CirculantGraph(*param)
11283
Circulant graph ([2, 3, 10, 11]): Graph on 13 vertices
11284
Circulant graph ([1, 5, 8, 12]): Graph on 13 vertices
11285
Circulant graph ([4, 6, 7, 9]): Graph on 13 vertices
11286
11287
TESTS::
11288
11289
sage: digraphs.DeBruijn(3,1).is_circulant(certificate = True)
11290
(True, [(3, [0, 1, 2])])
11291
sage: Graph(1).is_circulant(certificate = True)
11292
(True, (1, []))
11293
sage: Graph(0).is_circulant(certificate = True)
11294
(True, (0, []))
11295
sage: Graph([(0,0)]).is_circulant(certificate = True)
11296
(True, (1, [0]))
11297
"""
11298
self._scream_if_not_simple(allow_loops=True)
11299
# Stupid cases
11300
if self.order() <= 1:
11301
if certificate:
11302
return (True,(self.order(),[0] if self.size() else []))
11303
else:
11304
return True
11305
11306
certif_list = []
11307
11308
# The automorphism group, the translation between the vertices of self
11309
# and 1..n, and the orbits.
11310
ag, orbits = self.automorphism_group([self.vertices()],
11311
order=False,
11312
return_group=True,
11313
orbits=True)
11314
11315
# Not transitive ? Not a circulant graph !
11316
if len(orbits) != 1:
11317
return (False, None) if certificate else False
11318
11319
# We go through all conjugacy classes of the automorphism
11320
# group, and only keep the cycles of length n
11321
for e in ag.conjugacy_classes_representatives():
11322
cycles = e.cycle_tuples()
11323
11324
# If the automorphism is not the identity and has exactly one
11325
# cycle that contains all vertices.
11326
if ((not cycles) or
11327
len(cycles[0]) != self.order()):
11328
continue
11329
11330
# From now on the graph is a circulant graph !
11331
11332
if not certificate:
11333
return True
11334
11335
# We build the list of integers defining the circulant graph, and
11336
# add it to the list.
11337
parameters = []
11338
cycle = cycles[0]
11339
u = cycle[0]
11340
integers = [i for i,v in enumerate(cycle) if self.has_edge(u,v)]
11341
certif_list.append((self.order(),integers))
11342
11343
if not certificate:
11344
return False
11345
else:
11346
if certif_list:
11347
return (True, certif_list)
11348
else:
11349
return (False, None)
11350
11351
def is_interval(self, certificate = False):
11352
r"""
11353
Check whether self is an interval graph
11354
11355
INPUT:
11356
11357
- ``certificate`` (boolean) -- The function returns ``True``
11358
or ``False`` according to the graph, when ``certificate =
11359
False`` (default). When ``certificate = True`` and the graph
11360
is an interval graph, a dictionary whose keys are the
11361
vertices and values are pairs of integers are returned
11362
instead of ``True``. They correspond to an embedding of the
11363
interval graph, each vertex being represented by an interval
11364
going from the first of the two values to the second.
11365
11366
ALGORITHM:
11367
11368
Through the use of PQ-Trees
11369
11370
AUTHOR:
11371
11372
Nathann Cohen (implementation)
11373
11374
EXAMPLES:
11375
11376
A Petersen Graph is not chordal, nor car it be an interval
11377
graph ::
11378
11379
sage: g = graphs.PetersenGraph()
11380
sage: g.is_interval()
11381
False
11382
11383
Though we can build intervals from the corresponding random
11384
generator::
11385
11386
sage: g = graphs.RandomIntervalGraph(20)
11387
sage: g.is_interval()
11388
True
11389
11390
This method can also return, given an interval graph, a
11391
possible embedding (we can actually compute all of them
11392
through the PQ-Tree structures)::
11393
11394
sage: g = Graph(':S__@_@A_@AB_@AC_@ACD_@ACDE_ACDEF_ACDEFG_ACDEGH_ACDEGHI_ACDEGHIJ_ACDEGIJK_ACDEGIJKL_ACDEGIJKLMaCEGIJKNaCEGIJKNaCGIJKNPaCIP', loops=False, multiedges=False)
11395
sage: d = g.is_interval(certificate = True)
11396
sage: print d # not tested
11397
{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)}
11398
11399
From this embedding, we can clearly build an interval graph
11400
isomorphic to the previous one::
11401
11402
sage: g2 = graphs.IntervalGraph(d.values())
11403
sage: g2.is_isomorphic(g)
11404
True
11405
11406
.. SEEALSO::
11407
11408
- :mod:`Interval Graph Recognition <sage.graphs.pq_trees>`.
11409
11410
- :meth:`PQ <sage.graphs.pq_trees.PQ>`
11411
-- Implementation of PQ-Trees.
11412
11413
"""
11414
self._scream_if_not_simple()
11415
11416
# An interval graph first is a chordal graph. Without this,
11417
# there is no telling how we should find its maximal cliques,
11418
# by the way :-)
11419
11420
if not self.is_chordal():
11421
return False
11422
11423
# First, we need to gather the list of maximal cliques, which
11424
# is easy as the graph is chordal
11425
11426
cliques = []
11427
11428
# As we will be deleting vertices ...
11429
g = self.copy()
11430
11431
for cc in self.connected_components_subgraphs():
11432
11433
# We pick a perfect elimination order for every connected
11434
# component. We will then iteratively take the last vertex
11435
# in the order (a simplicial vertex) and consider the
11436
# clique it forms with its neighbors. If we do not have an
11437
# inclusion-wise larger clique in our list, we add it !
11438
11439
peo = cc.lex_BFS()
11440
11441
11442
11443
while peo:
11444
v = peo.pop()
11445
clique = frozenset( [v] + cc.neighbors(v))
11446
cc.delete_vertex(v)
11447
11448
if not any([clique.issubset(c) for c in cliques]):
11449
cliques.append(clique)
11450
11451
from sage.graphs.pq_trees import reorder_sets
11452
11453
try:
11454
ordered_sets = reorder_sets(cliques)
11455
if not certificate:
11456
return True
11457
11458
except ValueError:
11459
return False
11460
11461
# We are now listing the maximal cliques in the given order,
11462
# and keeping track of the vertices appearing/disappearing
11463
11464
current = set([])
11465
beg = {}
11466
end = {}
11467
11468
i = 0
11469
11470
ordered_sets.append([])
11471
for S in map(set,ordered_sets):
11472
for v in current-S:
11473
end[v] = i
11474
i = i + 1
11475
11476
for v in S-current:
11477
beg[v] = i
11478
i = i + 1
11479
11480
current = S
11481
11482
11483
return dict([(v, (beg[v], end[v])) for v in self])
11484
11485
11486
def is_gallai_tree(self):
11487
r"""
11488
Returns whether the current graph is a Gallai tree.
11489
11490
A graph is a Gallai tree if and only if it is
11491
connected and its `2`-connected components are all
11492
isomorphic to complete graphs or odd cycles.
11493
11494
A connected graph is not degree-choosable if and
11495
only if it is a Gallai tree [erdos1978choos]_.
11496
11497
REFERENCES:
11498
11499
.. [erdos1978choos] Erdos, P. and Rubin, A.L. and Taylor, H.
11500
Proc. West Coast Conf. on Combinatorics
11501
Graph Theory and Computing, Congressus Numerantium
11502
vol 26, pages 125--157, 1979
11503
11504
EXAMPLES:
11505
11506
A complete graph is, or course, a Gallai Tree::
11507
11508
sage: g = graphs.CompleteGraph(15)
11509
sage: g.is_gallai_tree()
11510
True
11511
11512
The Petersen Graph is not::
11513
11514
sage: g = graphs.PetersenGraph()
11515
sage: g.is_gallai_tree()
11516
False
11517
11518
A Graph built from vertex-disjoint complete graphs
11519
linked by one edge to a special vertex `-1` is a
11520
''star-shaped'' Gallai tree ::
11521
11522
sage: g = 8 * graphs.CompleteGraph(6)
11523
sage: g.add_edges([(-1,c[0]) for c in g.connected_components()])
11524
sage: g.is_gallai_tree()
11525
True
11526
"""
11527
self._scream_if_not_simple()
11528
if not self.is_connected():
11529
return False
11530
11531
for c in self.blocks_and_cut_vertices()[0]:
11532
gg = self.subgraph(c)
11533
# is it an odd cycle ? a complete graph ?
11534
if not ( (len(c)%2 == 1 and gg.size() == len(c)+1) or gg.is_clique() ):
11535
return False
11536
11537
return True
11538
11539
def is_clique(self, vertices=None, directed_clique=False):
11540
"""
11541
Tests whether a set of vertices is a clique
11542
11543
A clique is a set of vertices such that there is an edge between any two
11544
vertices.
11545
11546
INPUT:
11547
11548
- ``vertices`` - Vertices can be a single vertex or an
11549
iterable container of vertices, e.g. a list, set, graph, file or
11550
numeric array. If not passed, defaults to the entire graph.
11551
11552
- ``directed_clique`` - (default False) If set to
11553
False, only consider the underlying undirected graph. If set to
11554
True and the graph is directed, only return True if all possible
11555
edges in _both_ directions exist.
11556
11557
EXAMPLES::
11558
11559
sage: g = graphs.CompleteGraph(4)
11560
sage: g.is_clique([1,2,3])
11561
True
11562
sage: g.is_clique()
11563
True
11564
sage: h = graphs.CycleGraph(4)
11565
sage: h.is_clique([1,2])
11566
True
11567
sage: h.is_clique([1,2,3])
11568
False
11569
sage: h.is_clique()
11570
False
11571
sage: i = graphs.CompleteGraph(4).to_directed()
11572
sage: i.delete_edge([0,1])
11573
sage: i.is_clique()
11574
True
11575
sage: i.is_clique(directed_clique=True)
11576
False
11577
"""
11578
if directed_clique and self._directed:
11579
subgraph=self.subgraph(vertices)
11580
subgraph.allow_loops(False)
11581
subgraph.allow_multiple_edges(False)
11582
n=subgraph.order()
11583
return subgraph.size()==n*(n-1)
11584
else:
11585
if vertices is None:
11586
subgraph = self
11587
else:
11588
subgraph=self.subgraph(vertices)
11589
11590
if self._directed:
11591
subgraph = subgraph.to_simple()
11592
11593
n=subgraph.order()
11594
return subgraph.size()==n*(n-1)/2
11595
11596
def is_independent_set(self, vertices=None):
11597
"""
11598
Returns True if the set ``vertices`` is an independent
11599
set, False if not. An independent set is a set of vertices such
11600
that there is no edge between any two vertices.
11601
11602
INPUT:
11603
11604
- ``vertices`` - Vertices can be a single vertex or an
11605
iterable container of vertices, e.g. a list, set, graph, file or
11606
numeric array. If not passed, defaults to the entire graph.
11607
11608
EXAMPLES::
11609
11610
sage: graphs.CycleGraph(4).is_independent_set([1,3])
11611
True
11612
sage: graphs.CycleGraph(4).is_independent_set([1,2,3])
11613
False
11614
"""
11615
return self.subgraph(vertices).size()==0
11616
11617
def is_subgraph(self, other, induced=True):
11618
"""
11619
Tests whether ``self`` is a subgraph of ``other``.
11620
11621
.. WARNING::
11622
11623
Please note that this method does not check whether ``self``
11624
contains a subgraph *isomorphic* to ``other``, but only if it
11625
directly contains it as a subgraph !
11626
11627
By default ``induced`` is ``True`` for backwards compatibility.
11628
11629
INPUT:
11630
11631
- ``induced`` - boolean (default: ``True``) If set to ``True`` tests
11632
whether ``self`` is an *induced* subgraph of ``other`` that is if
11633
the vertices of ``self`` are also vertices of ``other``, and the
11634
edges of ``self`` are equal to the edges of ``other`` between the
11635
vertices contained in ``self`.
11636
If set to ``False`` tests whether ``self`` is a subgraph of ``other``
11637
that is if all vertices of ``self`` are also in ``other`` and all
11638
edges of ``self`` are also in ``other``.
11639
11640
OUTPUT:
11641
11642
boolean -- ``True`` iff ``self`` is a (possibly induced) subgraph of ``other``.
11643
11644
.. SEEALSO::
11645
11646
If you are interested in the (possibly induced) subgraphs isomorphic
11647
to ``self`` in ``other``, you are looking for the following methods:
11648
11649
- :meth:`~GenericGraph.subgraph_search` -- finds a subgraph
11650
isomorphic to `G` inside of a `self`
11651
11652
- :meth:`~GenericGraph.subgraph_search_count` -- Counts the number
11653
of such copies.
11654
11655
- :meth:`~GenericGraph.subgraph_search_iterator` -- Iterate over all
11656
the copies of `G` contained in `self`.
11657
11658
EXAMPLES:
11659
11660
sage: P = graphs.PetersenGraph()
11661
sage: G = P.subgraph(range(6))
11662
sage: G.is_subgraph(P)
11663
True
11664
11665
sage: H=graphs.CycleGraph(5)
11666
sage: G=graphs.PathGraph(5)
11667
sage: G.is_subgraph(H)
11668
False
11669
sage: G.is_subgraph(H, induced=False)
11670
True
11671
sage: H.is_subgraph(G, induced=False)
11672
False
11673
11674
TESTS:
11675
11676
Raise an error when self and other are of different types::
11677
11678
sage: Graph([(0,1)]).is_subgraph( DiGraph([(0,1)]) )
11679
Traceback (most recent call last):
11680
...
11681
ValueError: The input parameter must be a Graph.
11682
sage: DiGraph([(0,1)]).is_subgraph( Graph([(0,1)]) )
11683
Traceback (most recent call last):
11684
...
11685
ValueError: The input parameter must be a DiGraph.
11686
"""
11687
from sage.graphs.graph import Graph
11688
from sage.graphs.digraph import DiGraph
11689
if isinstance(self,Graph) and not isinstance(other,Graph):
11690
raise ValueError('The input parameter must be a Graph.')
11691
11692
if isinstance(self,DiGraph) and not isinstance(other,DiGraph):
11693
raise ValueError('The input parameter must be a DiGraph.')
11694
11695
if self.num_verts() > other.num_verts():
11696
return False
11697
11698
if any(v not in other for v in self.vertex_iterator()):
11699
return False
11700
11701
if induced:
11702
return other.subgraph(self.vertices()) == self
11703
else:
11704
self._scream_if_not_simple(allow_loops=True)
11705
return all(other.has_edge(e) for e in self.edge_iterator())
11706
11707
### Cluster
11708
11709
def cluster_triangles(self, nbunch=None, with_labels=False):
11710
r"""
11711
Returns the number of triangles for the set `nbunch` of vertices
11712
as a dictionary keyed by vertex.
11713
11714
See also section "Clustering" in chapter "Algorithms" of [HSSNX]_.
11715
11716
INPUT:
11717
11718
- ``nbunch`` - The vertices to inspect. If ``nbunch=None``,
11719
returns data for all vertices in the graph.
11720
11721
REFERENCE:
11722
11723
.. [HSSNX] Aric Hagberg, Dan Schult and Pieter Swart. NetworkX
11724
documentation. [Online] Available:
11725
http://networkx.github.io/documentation/latest/reference/index.html
11726
11727
EXAMPLES::
11728
11729
sage: (graphs.FruchtGraph()).cluster_triangles().values()
11730
[1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0]
11731
sage: (graphs.FruchtGraph()).cluster_triangles()
11732
{0: 1, 1: 1, 2: 0, 3: 1, 4: 1, 5: 1, 6: 1, 7: 1, 8: 0, 9: 1, 10: 1, 11: 0}
11733
sage: (graphs.FruchtGraph()).cluster_triangles(nbunch=[0,1,2])
11734
{0: 1, 1: 1, 2: 0}
11735
"""
11736
import networkx
11737
return networkx.triangles(self.networkx_graph(copy=False), nbunch)
11738
11739
def clustering_average(self):
11740
r"""
11741
Returns the average clustering coefficient.
11742
11743
The clustering coefficient of a node `i` is the fraction
11744
of existing triangles containing node `i` and all
11745
possible triangles containing `i`: `c_i = T(i) / \binom {k_i} 2`
11746
where `T(i)` is the number of existing triangles through `i`, and
11747
`k_i` is the degree of vertex `i`.
11748
11749
A coefficient for the whole graph is the average of the `c_i`.
11750
11751
See also section "Clustering" in chapter "Algorithms" of [HSSNX]_.
11752
11753
EXAMPLES::
11754
11755
sage: (graphs.FruchtGraph()).clustering_average()
11756
0.25
11757
"""
11758
import networkx
11759
return networkx.average_clustering(self.networkx_graph(copy=False))
11760
11761
def clustering_coeff(self, nodes=None, weight=False, return_vertex_weights=True):
11762
r"""
11763
Returns the clustering coefficient for each vertex in ``nodes`` as
11764
a dictionary keyed by vertex.
11765
11766
For an unweighted graph, the clustering coefficient of a node `i`
11767
is the fraction of existing triangles containing node `i` and all
11768
possible triangles containing `i`: `c_i = T(i) / \binom {k_i} 2`
11769
where `T(i)` is the number of existing triangles through `i`, and
11770
`k_i` is the degree of vertex `i`.
11771
11772
For weighted graphs the clustering is defined as the geometric
11773
average of the subgraph edge weights, normalized by the
11774
maximum weight in the network.
11775
11776
The value of `c_i` is assigned `0` if `k_i < 2`.
11777
11778
See also section "Clustering" in chapter "Algorithms" of [HSSNX]_.
11779
11780
INPUT:
11781
11782
- ``nodes`` - the vertices to inspect (default ``None``, returns data
11783
on all vertices in graph)
11784
11785
- ``weight`` - string or boolean (default is ``False``). If it is
11786
a string it used the indicated edge property as weight.
11787
``weight = True`` is equivalent to ``weight = 'weight'``
11788
11789
- ``return_vertex_weights`` is a boolean ensuring backwards
11790
compatibility with deprecated features of NetworkX 1.2. It
11791
should be set to ``False`` for all production code.
11792
11793
EXAMPLES::
11794
11795
sage: (graphs.FruchtGraph()).clustering_coeff().values()
11796
[0.3333333333333333, 0.3333333333333333, 0.0, 0.3333333333333333,
11797
0.3333333333333333, 0.3333333333333333, 0.3333333333333333,
11798
0.3333333333333333, 0.0, 0.3333333333333333, 0.3333333333333333,
11799
0.0]
11800
sage: (graphs.FruchtGraph()).clustering_coeff()
11801
{0: 0.3333333333333333, 1: 0.3333333333333333, 2: 0.0,
11802
3: 0.3333333333333333, 4: 0.3333333333333333,
11803
5: 0.3333333333333333, 6: 0.3333333333333333,
11804
7: 0.3333333333333333, 8: 0.0, 9: 0.3333333333333333,
11805
10: 0.3333333333333333, 11: 0.0}
11806
11807
sage: (graphs.FruchtGraph()).clustering_coeff(weight=True,
11808
... return_vertex_weights=False)
11809
{0: 0.3333333333333333, 1: 0.3333333333333333, 2: 0.0,
11810
3: 0.3333333333333333, 4: 0.3333333333333333,
11811
5: 0.3333333333333333, 6: 0.3333333333333333,
11812
7: 0.3333333333333333, 8: 0.0, 9: 0.3333333333333333,
11813
10: 0.3333333333333333, 11: 0.0}
11814
sage: (graphs.FruchtGraph()).clustering_coeff(nodes=[0,1,2])
11815
{0: 0.3333333333333333, 1: 0.3333333333333333, 2: 0.0}
11816
11817
sage: (graphs.FruchtGraph()).clustering_coeff(nodes=[0,1,2],
11818
... weight=True, return_vertex_weights=False)
11819
{0: 0.3333333333333333, 1: 0.3333333333333333, 2: 0.0}
11820
11821
TESTS:
11822
11823
Doctests that demonstrate the deprecation of the two-dictionary
11824
return value due to the NetworkX API change after 1.2. The
11825
return_vertex_weights keyword is provided with a default value
11826
of True for backwards compatibility with older versions of Sage.
11827
When the deprecation period has expired and the keyword is
11828
removed, these doctests should be removed as well. ::
11829
11830
sage: (graphs.FruchtGraph()).clustering_coeff(weight=True,
11831
... return_vertex_weights=True)
11832
doctest:...: DeprecationWarning: The option 'return_vertex_weights'
11833
has been deprecated. Only offered for backwards compatibility with
11834
NetworkX 1.2.
11835
See http://trac.sagemath.org/12806 for details.
11836
({0: 0.3333333333333333, 1: 0.3333333333333333, 2: 0.0,
11837
3: 0.3333333333333333, 4: 0.3333333333333333,
11838
5: 0.3333333333333333, 6: 0.3333333333333333,
11839
7: 0.3333333333333333, 8: 0.0, 9: 0.3333333333333333,
11840
10: 0.3333333333333333, 11: 0.0}, {0: 0.08333333333333333,
11841
1: 0.08333333333333333, 2: 0.08333333333333333,
11842
3: 0.08333333333333333, 4: 0.08333333333333333,
11843
5: 0.08333333333333333, 6: 0.08333333333333333,
11844
7: 0.08333333333333333, 8: 0.08333333333333333,
11845
9: 0.08333333333333333, 10: 0.08333333333333333,
11846
11: 0.08333333333333333})
11847
11848
sage: (graphs.FruchtGraph()).clustering_coeff(nodes=[0, 1, 2],
11849
... weight=True, return_vertex_weights=True)
11850
({0: 0.3333333333333333, 1: 0.3333333333333333, 2: 0.0},
11851
{0: 0.3333333333333333, 1: 0.3333333333333333,
11852
2: 0.3333333333333333})
11853
"""
11854
import networkx
11855
if weight and return_vertex_weights:
11856
# Running in compatibility mode with deprecated NetworkX 1.2 features
11857
# All this code should be removed when the deprecation warning expires
11858
from sage.misc.superseded import deprecation
11859
deprecation(12806, "The option 'return_vertex_weights' has been " +\
11860
"deprecated. Only offered for backwards" +\
11861
" compatibility with NetworkX 1.2.")
11862
G = self.networkx_graph(copy=False)
11863
if G.is_directed():
11864
raise NetworkXError("Clustering algorithms are not defined for directed graphs.")
11865
clusterc={}
11866
weights={}
11867
for v,d,t in networkx.cluster._triangles_and_degree_iter(G,nodes):
11868
weights[v]=float(d*(d-1))
11869
if t==0:
11870
clusterc[v]=0.0
11871
else:
11872
clusterc[v]=t/float(d*(d-1))
11873
scale=1./sum(weights.itervalues())
11874
for v,w in weights.iteritems():
11875
weights[v]=w*scale
11876
return clusterc,weights
11877
11878
else:
11879
return networkx.clustering(self.networkx_graph(copy=False), nodes, weight=weight)
11880
11881
def cluster_transitivity(self):
11882
r"""
11883
Returns the transitivity (fraction of transitive triangles) of the
11884
graph.
11885
11886
Transitivity is the fraction of all existing triangles and all
11887
connected triples (triads), `T = 3\times\text{triangles}
11888
/ \text{triads}`.
11889
11890
See also section "Clustering" in chapter "Algorithms" of [HSSNX]_.
11891
11892
EXAMPLES::
11893
11894
sage: (graphs.FruchtGraph()).cluster_transitivity()
11895
0.25
11896
"""
11897
import networkx
11898
return networkx.transitivity(self.networkx_graph(copy=False))
11899
11900
### Distance
11901
11902
def distance(self, u, v, by_weight=False):
11903
"""
11904
Returns the (directed) distance from u to v in the (di)graph, i.e.
11905
the length of the shortest path from u to v.
11906
11907
INPUT:
11908
11909
- ``by_weight`` - if ``False``, uses a breadth first
11910
search. If True, takes edge weightings into account, using
11911
Dijkstra's algorithm.
11912
11913
EXAMPLES::
11914
11915
sage: G = graphs.CycleGraph(9)
11916
sage: G.distance(0,1)
11917
1
11918
sage: G.distance(0,4)
11919
4
11920
sage: G.distance(0,5)
11921
4
11922
sage: G = Graph( {0:[], 1:[]} )
11923
sage: G.distance(0,1)
11924
+Infinity
11925
sage: G = Graph( { 0: {1: 1}, 1: {2: 1}, 2: {3: 1}, 3: {4: 2}, 4: {0: 2} }, sparse = True)
11926
sage: G.plot(edge_labels=True).show() # long time
11927
sage: G.distance(0, 3)
11928
2
11929
sage: G.distance(0, 3, by_weight=True)
11930
3
11931
11932
"""
11933
return self.shortest_path_length(u, v, by_weight = by_weight)
11934
11935
def distance_all_pairs(self, algorithm = "auto"):
11936
r"""
11937
Returns the distances between all pairs of vertices.
11938
11939
INPUT:
11940
11941
- ``"algorithm"`` (string) -- two algorithms are available
11942
11943
* ``algorithm = "BFS"`` in which case the distances are computed
11944
through `n` different breadth-first-search.
11945
11946
* ``algorithm = "Floyd-Warshall"``, in which case the
11947
Floyd-Warshall algorithm is used.
11948
11949
* ``algorithm = "auto"``, in which case the Floyd-Warshall
11950
algorithm is used for graphs on less than 20 vertices, and BFS
11951
otherwise.
11952
11953
The default is ``algorithm = "BFS"``.
11954
11955
OUTPUT:
11956
11957
A doubly indexed dictionary
11958
11959
.. NOTE::
11960
11961
There is a Cython version of this method that is usually
11962
much faster for large graphs, as most of the time is
11963
actually spent building the final double
11964
dictionary. Everything on the subject is to be found in the
11965
:mod:`~sage.graphs.distances_all_pairs` module.
11966
11967
EXAMPLE:
11968
11969
The Petersen Graph::
11970
11971
sage: g = graphs.PetersenGraph()
11972
sage: print g.distance_all_pairs()
11973
{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}}
11974
11975
Testing on Random Graphs::
11976
11977
sage: g = graphs.RandomGNP(20,.3)
11978
sage: distances = g.distance_all_pairs()
11979
sage: all([g.distance(0,v) == distances[0][v] for v in g])
11980
True
11981
11982
.. SEEALSO::
11983
11984
* :meth:`~sage.graphs.generic_graph.GenericGraph.distance_matrix`
11985
"""
11986
if algorithm == "auto":
11987
if self.order() <= 20:
11988
algorithm = "Floyd-Warshall"
11989
else:
11990
algorithm = "BFS"
11991
11992
if algorithm == "BFS":
11993
from sage.graphs.distances_all_pairs import distances_all_pairs
11994
return distances_all_pairs(self)
11995
11996
elif algorithm == "Floyd-Warshall":
11997
from sage.graphs.distances_all_pairs import floyd_warshall
11998
return floyd_warshall(self,paths = False, distances = True)
11999
12000
else:
12001
raise ValueError("The algorithm keyword can be equal to either \"BFS\" or \"Floyd-Warshall\" or \"auto\"")
12002
12003
12004
def eccentricity(self, v=None, dist_dict=None, with_labels=False):
12005
"""
12006
Return the eccentricity of vertex (or vertices) v.
12007
12008
The eccentricity of a vertex is the maximum distance to any other
12009
vertex.
12010
12011
INPUT:
12012
12013
12014
- ``v`` - either a single vertex or a list of
12015
vertices. If it is not specified, then it is taken to be all
12016
vertices.
12017
12018
- ``dist_dict`` - optional, a dict of dicts of
12019
distance.
12020
12021
- ``with_labels`` - Whether to return a list or a
12022
dict.
12023
12024
12025
EXAMPLES::
12026
12027
sage: G = graphs.KrackhardtKiteGraph()
12028
sage: G.eccentricity()
12029
[4, 4, 4, 4, 4, 3, 3, 2, 3, 4]
12030
sage: G.vertices()
12031
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
12032
sage: G.eccentricity(7)
12033
2
12034
sage: G.eccentricity([7,8,9])
12035
[3, 4, 2]
12036
sage: G.eccentricity([7,8,9], with_labels=True) == {8: 3, 9: 4, 7: 2}
12037
True
12038
sage: G = Graph( { 0 : [], 1 : [], 2 : [1] } )
12039
sage: G.eccentricity()
12040
[+Infinity, +Infinity, +Infinity]
12041
sage: G = Graph({0:[]})
12042
sage: G.eccentricity(with_labels=True)
12043
{0: 0}
12044
sage: G = Graph({0:[], 1:[]})
12045
sage: G.eccentricity(with_labels=True)
12046
{0: +Infinity, 1: +Infinity}
12047
"""
12048
if v is None:
12049
if dist_dict is None:
12050
from sage.graphs.distances_all_pairs import eccentricity
12051
12052
if with_labels:
12053
return dict(zip(self.vertices(), eccentricity(self)))
12054
else:
12055
return eccentricity(self)
12056
12057
v = self.vertices()
12058
elif not isinstance(v, list):
12059
v = [v]
12060
e = {}
12061
infinite = False
12062
for u in v:
12063
if dist_dict is None:
12064
length = self.shortest_path_lengths(u)
12065
else:
12066
length = dist_dict[u]
12067
if len(length) != self.num_verts():
12068
infinite = True
12069
break
12070
e[u] = max(length.values())
12071
if infinite:
12072
from sage.rings.infinity import Infinity
12073
for u in v:
12074
e[u] = Infinity
12075
if with_labels:
12076
return e
12077
else:
12078
if len(e)==1: return e.values()[0] # return single value
12079
return e.values()
12080
12081
def radius(self):
12082
"""
12083
Returns the radius of the (di)graph.
12084
12085
The radius is defined to be the minimum eccentricity of any vertex,
12086
where the eccentricity is the maximum distance to any other
12087
vertex.
12088
12089
EXAMPLES: The more symmetric a graph is, the smaller (diameter -
12090
radius) is.
12091
12092
::
12093
12094
sage: G = graphs.BarbellGraph(9, 3)
12095
sage: G.radius()
12096
3
12097
sage: G.diameter()
12098
6
12099
12100
::
12101
12102
sage: G = graphs.OctahedralGraph()
12103
sage: G.radius()
12104
2
12105
sage: G.diameter()
12106
2
12107
12108
TEST::
12109
12110
sage: g = Graph()
12111
sage: g.radius()
12112
Traceback (most recent call last):
12113
...
12114
ValueError: This method has no meaning on empty graphs.
12115
"""
12116
if self.order() == 0:
12117
raise ValueError("This method has no meaning on empty graphs.")
12118
12119
return min(self.eccentricity())
12120
12121
def center(self):
12122
"""
12123
Returns the set of vertices in the center, i.e. whose eccentricity
12124
is equal to the radius of the (di)graph.
12125
12126
In other words, the center is the set of vertices achieving the
12127
minimum eccentricity.
12128
12129
EXAMPLES::
12130
12131
sage: G = graphs.DiamondGraph()
12132
sage: G.center()
12133
[1, 2]
12134
sage: P = graphs.PetersenGraph()
12135
sage: P.subgraph(P.center()) == P
12136
True
12137
sage: S = graphs.StarGraph(19)
12138
sage: S.center()
12139
[0]
12140
sage: G = Graph()
12141
sage: G.center()
12142
[]
12143
sage: G.add_vertex()
12144
0
12145
sage: G.center()
12146
[0]
12147
"""
12148
e = self.eccentricity(with_labels=True)
12149
try:
12150
r = min(e.values())
12151
except Exception:
12152
return []
12153
return [v for v in e if e[v]==r]
12154
12155
def diameter(self):
12156
"""
12157
Returns the largest distance between any two vertices. Returns
12158
Infinity if the (di)graph is not connected.
12159
12160
EXAMPLES::
12161
12162
sage: G = graphs.PetersenGraph()
12163
sage: G.diameter()
12164
2
12165
sage: G = Graph( { 0 : [], 1 : [], 2 : [1] } )
12166
sage: G.diameter()
12167
+Infinity
12168
12169
Although max( ) is usually defined as -Infinity, since the diameter
12170
will never be negative, we define it to be zero::
12171
12172
sage: G = graphs.EmptyGraph()
12173
sage: G.diameter()
12174
0
12175
12176
"""
12177
12178
if self.order() > 0:
12179
return max(self.eccentricity())
12180
else:
12181
return 0
12182
12183
def distance_graph(self, dist):
12184
r"""
12185
Returns the graph on the same vertex set as
12186
the original graph but vertices are adjacent
12187
in the returned graph if and only if they are
12188
at specified distances in the original graph.
12189
12190
INPUT:
12191
12192
- ``dist`` is a nonnegative integer or
12193
a list of nonnegative integers.
12194
``Infinity`` may be used here to describe
12195
vertex pairs in separate components.
12196
12197
OUTPUT:
12198
12199
The returned value is an undirected graph. The
12200
vertex set is identical to the calling graph, but edges
12201
of the returned graph join vertices whose distance in
12202
the calling graph are present in the input ``dist``.
12203
Loops will only be present if distance 0 is included. If
12204
the original graph has a position dictionary specifying
12205
locations of vertices for plotting, then this information
12206
is copied over to the distance graph. In some instances
12207
this layout may not be the best, and might even be confusing
12208
when edges run on top of each other due to symmetries
12209
chosen for the layout.
12210
12211
EXAMPLES::
12212
12213
sage: G = graphs.CompleteGraph(3)
12214
sage: H = G.cartesian_product(graphs.CompleteGraph(2))
12215
sage: K = H.distance_graph(2)
12216
sage: K.am()
12217
[0 0 0 1 0 1]
12218
[0 0 1 0 1 0]
12219
[0 1 0 0 0 1]
12220
[1 0 0 0 1 0]
12221
[0 1 0 1 0 0]
12222
[1 0 1 0 0 0]
12223
12224
To obtain the graph where vertices are adjacent if their
12225
distance apart is ``d`` or less use a ``range()`` command
12226
to create the input, using ``d+1`` as the input to ``range``.
12227
Notice that this will include distance 0 and hence place a loop
12228
at each vertex. To avoid this, use ``range(1,d+1)``. ::
12229
12230
sage: G = graphs.OddGraph(4)
12231
sage: d = G.diameter()
12232
sage: n = G.num_verts()
12233
sage: H = G.distance_graph(range(d+1))
12234
sage: H.is_isomorphic(graphs.CompleteGraph(n))
12235
False
12236
sage: H = G.distance_graph(range(1,d+1))
12237
sage: H.is_isomorphic(graphs.CompleteGraph(n))
12238
True
12239
12240
A complete collection of distance graphs will have
12241
adjacency matrices that sum to the matrix of all ones. ::
12242
12243
sage: P = graphs.PathGraph(20)
12244
sage: all_ones = sum([P.distance_graph(i).am() for i in range(20)])
12245
sage: all_ones == matrix(ZZ, 20, 20, [1]*400)
12246
True
12247
12248
Four-bit strings differing in one bit is the same as
12249
four-bit strings differing in three bits. ::
12250
12251
sage: G = graphs.CubeGraph(4)
12252
sage: H = G.distance_graph(3)
12253
sage: G.is_isomorphic(H)
12254
True
12255
12256
The graph of eight-bit strings, adjacent if different
12257
in an odd number of bits. ::
12258
12259
sage: G = graphs.CubeGraph(8) # long time
12260
sage: H = G.distance_graph([1,3,5,7]) # long time
12261
sage: degrees = [0]*sum([binomial(8,j) for j in [1,3,5,7]]) # long time
12262
sage: degrees.append(2^8) # long time
12263
sage: degrees == H.degree_histogram() # long time
12264
True
12265
12266
An example of using ``Infinity`` as the distance in
12267
a graph that is not connected. ::
12268
12269
sage: G = graphs.CompleteGraph(3)
12270
sage: H = G.disjoint_union(graphs.CompleteGraph(2))
12271
sage: L = H.distance_graph(Infinity)
12272
sage: L.am()
12273
[0 0 0 1 1]
12274
[0 0 0 1 1]
12275
[0 0 0 1 1]
12276
[1 1 1 0 0]
12277
[1 1 1 0 0]
12278
12279
TESTS:
12280
12281
Empty input, or unachievable distances silently yield empty graphs. ::
12282
12283
sage: G = graphs.CompleteGraph(5)
12284
sage: G.distance_graph([]).num_edges()
12285
0
12286
sage: G = graphs.CompleteGraph(5)
12287
sage: G.distance_graph(23).num_edges()
12288
0
12289
12290
It is an error to provide a distance that is not an integer type. ::
12291
12292
sage: G = graphs.CompleteGraph(5)
12293
sage: G.distance_graph('junk')
12294
Traceback (most recent call last):
12295
...
12296
TypeError: unable to convert x (=junk) to an integer
12297
12298
It is an error to provide a negative distance. ::
12299
12300
sage: G = graphs.CompleteGraph(5)
12301
sage: G.distance_graph(-3)
12302
Traceback (most recent call last):
12303
...
12304
ValueError: Distance graph for a negative distance (d=-3) is not defined
12305
12306
AUTHOR:
12307
12308
Rob Beezer, 2009-11-25
12309
"""
12310
from sage.rings.infinity import Infinity
12311
from copy import copy
12312
# If input is not a list, make a list with this single object
12313
if not isinstance(dist, list):
12314
dist = [dist]
12315
# Create a list of positive integer (or infinite) distances
12316
distances = []
12317
for d in dist:
12318
if d == Infinity:
12319
distances.append(d)
12320
else:
12321
dint = ZZ(d)
12322
if dint < 0:
12323
raise ValueError('Distance graph for a negative distance (d=%d) is not defined' % dint)
12324
distances.append(dint)
12325
# Build a graph on the same vertex set, with loops for distance 0
12326
vertices = {}
12327
for v in self.vertex_iterator():
12328
vertices[v] = {}
12329
positions = copy(self.get_pos())
12330
if ZZ(0) in distances:
12331
looped = True
12332
else:
12333
looped = False
12334
from sage.graphs.all import Graph
12335
D = Graph(vertices, pos=positions, multiedges=False, loops=looped)
12336
if len(distances) == 1:
12337
dstring = "distance " + str(distances[0])
12338
else:
12339
dstring = "distances " + str(sorted(distances))
12340
D.name("Distance graph for %s in " % dstring + self.name())
12341
12342
# Create the appropriate edges
12343
d = self.distance_all_pairs()
12344
for u in self.vertex_iterator():
12345
for v in self.vertex_iterator():
12346
if d[u][v] in distances:
12347
D.add_edge(u,v)
12348
return D
12349
12350
def girth(self):
12351
"""
12352
Computes the girth of the graph. For directed graphs, computes the
12353
girth of the undirected graph.
12354
12355
The girth is the length of the shortest cycle in the graph. Graphs
12356
without cycles have infinite girth.
12357
12358
EXAMPLES::
12359
12360
sage: graphs.TetrahedralGraph().girth()
12361
3
12362
sage: graphs.CubeGraph(3).girth()
12363
4
12364
sage: graphs.PetersenGraph().girth()
12365
5
12366
sage: graphs.HeawoodGraph().girth()
12367
6
12368
sage: graphs.trees(9).next().girth()
12369
+Infinity
12370
12371
12372
.. SEEALSO::
12373
12374
* :meth:`~sage.graphs.graph.Graph.odd_girth` -- computes
12375
the odd girth of a graph.
12376
12377
TESTS:
12378
12379
Prior to :trac:`12243`, the girth computation assumed
12380
vertices were integers (and failed). The example below
12381
tests the computation for graphs with vertices that are
12382
not integers. In this example the vertices are sets. ::
12383
12384
sage: G = graphs.OddGraph(3)
12385
sage: type(G.vertices()[0])
12386
<class 'sage.sets.set.Set_object_enumerated_with_category'>
12387
sage: G.girth()
12388
5
12389
12390
Ticket :trac:`12355`::
12391
12392
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)])
12393
sage: H.girth()
12394
3
12395
12396
Girth < 3 (see :trac:`12355`)::
12397
12398
sage: g = graphs.PetersenGraph()
12399
sage: g.allow_multiple_edges(True)
12400
sage: g.allow_loops(True)
12401
sage: g.girth()
12402
5
12403
sage: g.add_edge(0,0)
12404
sage: g.girth()
12405
1
12406
sage: g.delete_edge(0,0)
12407
sage: g.add_edge(0,1)
12408
sage: g.girth()
12409
2
12410
sage: g.delete_edge(0,1)
12411
sage: g.girth()
12412
5
12413
sage: g = DiGraph(g)
12414
sage: g.girth()
12415
2
12416
"""
12417
12418
# Cases where girth <= 2
12419
if self.has_loops():
12420
return 1
12421
if self.is_directed():
12422
if any(self.has_edge(v,u) for u,v in self.edges(labels = False)):
12423
return 2
12424
else:
12425
if self.has_multiple_edges():
12426
return 2
12427
12428
n = self.num_verts()
12429
best = n+1
12430
seen = {}
12431
for w in self.vertex_iterator():
12432
seen[w] = None
12433
span = set([w])
12434
depth = 1
12435
thisList = set([w])
12436
while 2*depth <= best and 3 < best:
12437
nextList = set()
12438
for v in thisList:
12439
for u in self.neighbors(v):
12440
if u in seen: continue
12441
if not u in span:
12442
span.add(u)
12443
nextList.add(u)
12444
else:
12445
if u in thisList:
12446
best = depth*2-1
12447
break
12448
if u in nextList:
12449
best = depth*2
12450
if best == 2*depth-1:
12451
break
12452
thisList = nextList
12453
depth += 1
12454
if best == n+1:
12455
from sage.rings.infinity import Infinity
12456
return Infinity
12457
return best
12458
12459
12460
12461
def periphery(self):
12462
"""
12463
Returns the set of vertices in the periphery, i.e. whose
12464
eccentricity is equal to the diameter of the (di)graph.
12465
12466
In other words, the periphery is the set of vertices achieving the
12467
maximum eccentricity.
12468
12469
EXAMPLES::
12470
12471
sage: G = graphs.DiamondGraph()
12472
sage: G.periphery()
12473
[0, 3]
12474
sage: P = graphs.PetersenGraph()
12475
sage: P.subgraph(P.periphery()) == P
12476
True
12477
sage: S = graphs.StarGraph(19)
12478
sage: S.periphery()
12479
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
12480
sage: G = Graph()
12481
sage: G.periphery()
12482
[]
12483
sage: G.add_vertex()
12484
0
12485
sage: G.periphery()
12486
[0]
12487
"""
12488
e = self.eccentricity(with_labels=True)
12489
try:
12490
r = max(e.values())
12491
except Exception:
12492
return []
12493
return [v for v in e if e[v]==r]
12494
12495
### Paths
12496
12497
def interior_paths(self, start, end):
12498
"""
12499
Returns an exhaustive list of paths (also lists) through only
12500
interior vertices from vertex start to vertex end in the
12501
(di)graph.
12502
12503
Note - start and end do not necessarily have to be boundary
12504
vertices.
12505
12506
INPUT:
12507
12508
12509
- ``start`` - the vertex of the graph to search for
12510
paths from
12511
12512
- ``end`` - the vertex of the graph to search for
12513
paths to
12514
12515
12516
EXAMPLES::
12517
12518
sage: eg1 = Graph({0:[1,2], 1:[4], 2:[3,4], 4:[5], 5:[6]})
12519
sage: sorted(eg1.all_paths(0,6))
12520
[[0, 1, 4, 5, 6], [0, 2, 4, 5, 6]]
12521
sage: eg2 = copy(eg1)
12522
sage: eg2.set_boundary([0,1,3])
12523
sage: sorted(eg2.interior_paths(0,6))
12524
[[0, 2, 4, 5, 6]]
12525
sage: sorted(eg2.all_paths(0,6))
12526
[[0, 1, 4, 5, 6], [0, 2, 4, 5, 6]]
12527
sage: eg3 = graphs.PetersenGraph()
12528
sage: eg3.set_boundary([0,1,2,3,4])
12529
sage: sorted(eg3.all_paths(1,4))
12530
[[1, 0, 4],
12531
[1, 0, 5, 7, 2, 3, 4],
12532
[1, 0, 5, 7, 2, 3, 8, 6, 9, 4],
12533
[1, 0, 5, 7, 9, 4],
12534
[1, 0, 5, 7, 9, 6, 8, 3, 4],
12535
[1, 0, 5, 8, 3, 2, 7, 9, 4],
12536
[1, 0, 5, 8, 3, 4],
12537
[1, 0, 5, 8, 6, 9, 4],
12538
[1, 0, 5, 8, 6, 9, 7, 2, 3, 4],
12539
[1, 2, 3, 4],
12540
[1, 2, 3, 8, 5, 0, 4],
12541
[1, 2, 3, 8, 5, 7, 9, 4],
12542
[1, 2, 3, 8, 6, 9, 4],
12543
[1, 2, 3, 8, 6, 9, 7, 5, 0, 4],
12544
[1, 2, 7, 5, 0, 4],
12545
[1, 2, 7, 5, 8, 3, 4],
12546
[1, 2, 7, 5, 8, 6, 9, 4],
12547
[1, 2, 7, 9, 4],
12548
[1, 2, 7, 9, 6, 8, 3, 4],
12549
[1, 2, 7, 9, 6, 8, 5, 0, 4],
12550
[1, 6, 8, 3, 2, 7, 5, 0, 4],
12551
[1, 6, 8, 3, 2, 7, 9, 4],
12552
[1, 6, 8, 3, 4],
12553
[1, 6, 8, 5, 0, 4],
12554
[1, 6, 8, 5, 7, 2, 3, 4],
12555
[1, 6, 8, 5, 7, 9, 4],
12556
[1, 6, 9, 4],
12557
[1, 6, 9, 7, 2, 3, 4],
12558
[1, 6, 9, 7, 2, 3, 8, 5, 0, 4],
12559
[1, 6, 9, 7, 5, 0, 4],
12560
[1, 6, 9, 7, 5, 8, 3, 4]]
12561
sage: sorted(eg3.interior_paths(1,4))
12562
[[1, 6, 8, 5, 7, 9, 4], [1, 6, 9, 4]]
12563
sage: dg = DiGraph({0:[1,3,4], 1:[3], 2:[0,3,4],4:[3]}, boundary=[4])
12564
sage: sorted(dg.all_paths(0,3))
12565
[[0, 1, 3], [0, 3], [0, 4, 3]]
12566
sage: sorted(dg.interior_paths(0,3))
12567
[[0, 1, 3], [0, 3]]
12568
sage: ug = dg.to_undirected()
12569
sage: sorted(ug.all_paths(0,3))
12570
[[0, 1, 3], [0, 2, 3], [0, 2, 4, 3], [0, 3], [0, 4, 2, 3], [0, 4, 3]]
12571
sage: sorted(ug.interior_paths(0,3))
12572
[[0, 1, 3], [0, 2, 3], [0, 3]]
12573
"""
12574
from copy import copy
12575
H = copy(self)
12576
for vertex in self.get_boundary():
12577
if (vertex != start and vertex != end):
12578
H.delete_edges(H.edges_incident(vertex))
12579
return H.all_paths(start, end)
12580
12581
def all_paths(self, start, end):
12582
"""
12583
Returns a list of all paths (also lists) between a pair of
12584
vertices (start, end) in the (di)graph. If ``start`` is the same
12585
vertex as ``end``, then ``[[start]]`` is returned -- a list
12586
containing the 1-vertex, 0-edge path "``start``".
12587
12588
EXAMPLES::
12589
12590
sage: eg1 = Graph({0:[1,2], 1:[4], 2:[3,4], 4:[5], 5:[6]})
12591
sage: eg1.all_paths(0,6)
12592
[[0, 1, 4, 5, 6], [0, 2, 4, 5, 6]]
12593
sage: eg2 = graphs.PetersenGraph()
12594
sage: sorted(eg2.all_paths(1,4))
12595
[[1, 0, 4],
12596
[1, 0, 5, 7, 2, 3, 4],
12597
[1, 0, 5, 7, 2, 3, 8, 6, 9, 4],
12598
[1, 0, 5, 7, 9, 4],
12599
[1, 0, 5, 7, 9, 6, 8, 3, 4],
12600
[1, 0, 5, 8, 3, 2, 7, 9, 4],
12601
[1, 0, 5, 8, 3, 4],
12602
[1, 0, 5, 8, 6, 9, 4],
12603
[1, 0, 5, 8, 6, 9, 7, 2, 3, 4],
12604
[1, 2, 3, 4],
12605
[1, 2, 3, 8, 5, 0, 4],
12606
[1, 2, 3, 8, 5, 7, 9, 4],
12607
[1, 2, 3, 8, 6, 9, 4],
12608
[1, 2, 3, 8, 6, 9, 7, 5, 0, 4],
12609
[1, 2, 7, 5, 0, 4],
12610
[1, 2, 7, 5, 8, 3, 4],
12611
[1, 2, 7, 5, 8, 6, 9, 4],
12612
[1, 2, 7, 9, 4],
12613
[1, 2, 7, 9, 6, 8, 3, 4],
12614
[1, 2, 7, 9, 6, 8, 5, 0, 4],
12615
[1, 6, 8, 3, 2, 7, 5, 0, 4],
12616
[1, 6, 8, 3, 2, 7, 9, 4],
12617
[1, 6, 8, 3, 4],
12618
[1, 6, 8, 5, 0, 4],
12619
[1, 6, 8, 5, 7, 2, 3, 4],
12620
[1, 6, 8, 5, 7, 9, 4],
12621
[1, 6, 9, 4],
12622
[1, 6, 9, 7, 2, 3, 4],
12623
[1, 6, 9, 7, 2, 3, 8, 5, 0, 4],
12624
[1, 6, 9, 7, 5, 0, 4],
12625
[1, 6, 9, 7, 5, 8, 3, 4]]
12626
sage: dg = DiGraph({0:[1,3], 1:[3], 2:[0,3]})
12627
sage: sorted(dg.all_paths(0,3))
12628
[[0, 1, 3], [0, 3]]
12629
sage: ug = dg.to_undirected()
12630
sage: sorted(ug.all_paths(0,3))
12631
[[0, 1, 3], [0, 2, 3], [0, 3]]
12632
12633
Starting and ending at the same vertex (see :trac:`13006`)::
12634
12635
sage: graphs.CompleteGraph(4).all_paths(2,2)
12636
[[2]]
12637
"""
12638
if self.is_directed():
12639
iterator=self.neighbor_out_iterator
12640
else:
12641
iterator=self.neighbor_iterator
12642
if start == end:
12643
return [[start]]
12644
all_paths = [] # list of
12645
act_path = [] # the current path
12646
act_path_iter = [] # the neighbor/successor-iterators of the current path
12647
done = False
12648
s=start
12649
while not done:
12650
if s==end: # if path completes, add to list
12651
all_paths.append(act_path+[s])
12652
else:
12653
if s not in act_path: # we want vertices just once in a path
12654
act_path.append(s) # extend current path
12655
act_path_iter.append(iterator(s)) # save the state of the neighbor/successor-iterator of the current vertex
12656
s=None
12657
while (s is None) and not done:
12658
try:
12659
s=act_path_iter[-1].next() # try to get the next neighbor/successor, ...
12660
except (StopIteration): # ... if there is none ...
12661
act_path.pop() # ... go one step back
12662
act_path_iter.pop()
12663
if len(act_path)==0: # there is no other vertex ...
12664
done = True # ... so we are done
12665
return all_paths
12666
12667
12668
def triangles_count(self, algorithm='iter'):
12669
"""
12670
Returns the number of triangles in the (di)graph.
12671
12672
For digraphs, we count the number of directed circuit of length 3.
12673
12674
INPUT:
12675
12676
- ``algorithm`` -- (default: ``'matrix'``) specifies the algorithm to
12677
use among:
12678
12679
- ``'matrix'`` uses the trace of the cube of the adjacency matrix.
12680
12681
- ``'iter'`` iterates over the pairs of neighbors of each
12682
vertex. This is faster for sparse graphs.
12683
12684
EXAMPLES:
12685
12686
The Petersen graph is triangle free and thus::
12687
12688
sage: G = graphs.PetersenGraph()
12689
sage: G.triangles_count()
12690
0
12691
12692
Any triple of vertices in the complete graph induces a triangle so we have::
12693
12694
sage: G = graphs.CompleteGraph(150)
12695
sage: G.triangles_count() == binomial(150,3)
12696
True
12697
12698
The 2-dimensional DeBruijn graph of 2 symbols has 2 directed C3::
12699
12700
sage: G = digraphs.DeBruijn(2,2)
12701
sage: G.triangles_count()
12702
2
12703
12704
The directed n-cycle is trivially triangle free for n > 3::
12705
12706
sage: G = digraphs.Circuit(10)
12707
sage: G.triangles_count()
12708
0
12709
12710
TESTS:
12711
12712
Comparison on algorithms::
12713
12714
sage: for i in xrange(10): # long test
12715
... G = graphs.RandomBarabasiAlbert(50,2)
12716
... tm = G.triangles_count(algorithm='matrix')
12717
... te = G.triangles_count(algorithm='iter')
12718
... if tm!=te:
12719
... print "That's not good!"
12720
12721
Asking for an unknown algorithm::
12722
12723
sage: G = Graph()
12724
sage: G.triangles_count(algorithm='tip top')
12725
Traceback (most recent call last):
12726
...
12727
ValueError: Algorithm 'tip top' not yet implemented. Please contribute.
12728
12729
"""
12730
if self.is_directed():
12731
from sage.graphs.digraph_generators import digraphs
12732
return self.subgraph_search_count(digraphs.Circuit(3))/3
12733
12734
else:
12735
if algorithm=='iter':
12736
from sage.combinat.combination import Combinations
12737
tr = 0
12738
ggnx = self.networkx_graph()
12739
for u in ggnx.nodes_iter():
12740
tr += sum(ggnx.has_edge(v,w) for v,w in Combinations(ggnx.neighbors(u),2))
12741
return tr/3
12742
12743
elif algorithm=='matrix':
12744
return (self.adjacency_matrix()**3).trace()/6
12745
12746
else:
12747
raise ValueError("Algorithm '%s' not yet implemented. Please contribute." %(algorithm))
12748
12749
def shortest_path(self, u, v, by_weight=False, bidirectional=True):
12750
"""
12751
Returns a list of vertices representing some shortest path from u
12752
to v: if there is no path from u to v, the list is empty.
12753
12754
INPUT:
12755
12756
12757
- ``by_weight`` - if False, uses a breadth first
12758
search. If True, takes edge weightings into account, using
12759
Dijkstra's algorithm.
12760
12761
- ``bidirectional`` - if True, the algorithm will
12762
expand vertices from u and v at the same time, making two spheres
12763
of half the usual radius. This generally doubles the speed
12764
(consider the total volume in each case).
12765
12766
12767
EXAMPLES::
12768
12769
sage: D = graphs.DodecahedralGraph()
12770
sage: D.shortest_path(4, 9)
12771
[4, 17, 16, 12, 13, 9]
12772
sage: D.shortest_path(5, 5)
12773
[5]
12774
sage: D.delete_edges(D.edges_incident(13))
12775
sage: D.shortest_path(13, 4)
12776
[]
12777
sage: G = Graph( { 0: [1], 1: [2], 2: [3], 3: [4], 4: [0] })
12778
sage: G.plot(edge_labels=True).show() # long time
12779
sage: G.shortest_path(0, 3)
12780
[0, 4, 3]
12781
sage: G = Graph( { 0: {1: 1}, 1: {2: 1}, 2: {3: 1}, 3: {4: 2}, 4: {0: 2} }, sparse = True)
12782
sage: G.shortest_path(0, 3, by_weight=True)
12783
[0, 1, 2, 3]
12784
""" # TODO- multiple edges??
12785
if u == v: # to avoid a NetworkX bug
12786
return [u]
12787
import networkx
12788
if by_weight:
12789
if bidirectional:
12790
try:
12791
L = self._backend.bidirectional_dijkstra(u,v)
12792
except AttributeError:
12793
try:
12794
L = networkx.bidirectional_dijkstra(self.networkx_graph(copy=False), u, v)[1]
12795
except Exception:
12796
L = False
12797
else:
12798
L = networkx.dijkstra_path(self.networkx_graph(copy=False), u, v)
12799
else:
12800
if bidirectional:
12801
# If the graph is a C_graph, use shortest_path from its backend !
12802
try:
12803
L = self._backend.shortest_path(u,v)
12804
except AttributeError:
12805
L = networkx.shortest_path(self.networkx_graph(copy=False), u, v)
12806
else:
12807
try:
12808
L = networkx.single_source_shortest_path(self.networkx_graph(copy=False), u)[v]
12809
except Exception:
12810
L = False
12811
if L:
12812
return L
12813
else:
12814
return []
12815
12816
def shortest_path_length(self, u, v, by_weight=False,
12817
bidirectional=True,
12818
weight_sum=None):
12819
"""
12820
Returns the minimal length of paths from u to v.
12821
12822
If there is no path from u to v, returns Infinity.
12823
12824
INPUT:
12825
12826
- ``by_weight`` - if False, uses a breadth first
12827
search. If True, takes edge weightings into account, using
12828
Dijkstra's algorithm.
12829
12830
- ``bidirectional`` - if True, the algorithm will
12831
expand vertices from u and v at the same time, making two spheres
12832
of half the usual radius. This generally doubles the speed
12833
(consider the total volume in each case).
12834
12835
- ``weight_sum`` - if False, returns the number of
12836
edges in the path. If True, returns the sum of the weights of these
12837
edges. Default behavior is to have the same value as by_weight.
12838
12839
EXAMPLES::
12840
12841
sage: D = graphs.DodecahedralGraph()
12842
sage: D.shortest_path_length(4, 9)
12843
5
12844
sage: D.shortest_path_length(5, 5)
12845
0
12846
sage: D.delete_edges(D.edges_incident(13))
12847
sage: D.shortest_path_length(13, 4)
12848
+Infinity
12849
sage: G = Graph( { 0: {1: 1}, 1: {2: 1}, 2: {3: 1}, 3: {4: 2}, 4: {0: 2} }, sparse = True)
12850
sage: G.plot(edge_labels=True).show() # long time
12851
sage: G.shortest_path_length(0, 3)
12852
2
12853
sage: G.shortest_path_length(0, 3, by_weight=True)
12854
3
12855
"""
12856
if weight_sum is None:
12857
weight_sum = by_weight
12858
path = self.shortest_path(u, v, by_weight, bidirectional)
12859
length = len(path) - 1
12860
if length == -1:
12861
from sage.rings.infinity import Infinity
12862
return Infinity
12863
if weight_sum:
12864
wt = 0
12865
for j in range(length):
12866
wt += self.edge_label(path[j], path[j+1])
12867
return wt
12868
else:
12869
return length
12870
12871
def shortest_paths(self, u, by_weight=False, cutoff=None):
12872
"""
12873
Returns a dictionary associating to each vertex v a shortest path from u
12874
to v, if it exists.
12875
12876
INPUT:
12877
12878
- ``by_weight`` - if False, uses a breadth first
12879
search. If True, uses Dijkstra's algorithm to find the shortest
12880
paths by weight.
12881
12882
- ``cutoff`` - integer depth to stop search.
12883
12884
(ignored if ``by_weight == True``)
12885
12886
EXAMPLES::
12887
12888
sage: D = graphs.DodecahedralGraph()
12889
sage: D.shortest_paths(0)
12890
{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]}
12891
12892
All these paths are obviously induced graphs::
12893
12894
sage: all([D.subgraph(p).is_isomorphic(graphs.PathGraph(len(p)) )for p in D.shortest_paths(0).values()])
12895
True
12896
12897
::
12898
12899
sage: D.shortest_paths(0, cutoff=2)
12900
{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]}
12901
sage: G = Graph( { 0: {1: 1}, 1: {2: 1}, 2: {3: 1}, 3: {4: 2}, 4: {0: 2} }, sparse=True)
12902
sage: G.plot(edge_labels=True).show() # long time
12903
sage: G.shortest_paths(0, by_weight=True)
12904
{0: [0], 1: [0, 1], 2: [0, 1, 2], 3: [0, 1, 2, 3], 4: [0, 4]}
12905
"""
12906
12907
if by_weight:
12908
import networkx
12909
return networkx.single_source_dijkstra_path(self.networkx_graph(copy=False), u)
12910
else:
12911
try:
12912
return self._backend.shortest_path_all_vertices(u, cutoff)
12913
except AttributeError:
12914
return networkx.single_source_shortest_path(self.networkx_graph(copy=False), u, cutoff)
12915
12916
def shortest_path_lengths(self, u, by_weight=False, weight_sums=None):
12917
"""
12918
Returns a dictionary of shortest path lengths keyed by targets that
12919
are connected by a path from u.
12920
12921
INPUT:
12922
12923
12924
- ``by_weight`` - if False, uses a breadth first
12925
search. If True, takes edge weightings into account, using
12926
Dijkstra's algorithm.
12927
12928
EXAMPLES::
12929
12930
sage: D = graphs.DodecahedralGraph()
12931
sage: D.shortest_path_lengths(0)
12932
{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}
12933
sage: G = Graph( { 0: {1: 1}, 1: {2: 1}, 2: {3: 1}, 3: {4: 2}, 4: {0: 2} }, sparse=True )
12934
sage: G.plot(edge_labels=True).show() # long time
12935
sage: G.shortest_path_lengths(0, by_weight=True)
12936
{0: 0, 1: 1, 2: 2, 3: 3, 4: 2}
12937
"""
12938
paths = self.shortest_paths(u, by_weight)
12939
if by_weight:
12940
weights = {}
12941
for v in paths:
12942
wt = 0
12943
path = paths[v]
12944
for j in range(len(path) - 1):
12945
wt += self.edge_label(path[j], path[j+1])
12946
weights[v] = wt
12947
return weights
12948
else:
12949
lengths = {}
12950
for v in paths:
12951
lengths[v] = len(paths[v]) - 1
12952
return lengths
12953
12954
def shortest_path_all_pairs(self, by_weight=False, default_weight=1, algorithm = "auto"):
12955
"""
12956
Computes a shortest path between each pair of vertices.
12957
12958
INPUT:
12959
12960
12961
- ``by_weight`` - Whether to use the labels defined over the edges as
12962
weights. If ``False`` (default), the distance between `u` and `v` is
12963
the minimum number of edges of a path from `u` to `v`.
12964
12965
- ``default_weight`` - (defaults to 1) The default weight to assign
12966
edges that don't have a weight (i.e., a label).
12967
12968
Implies ``by_weight == True``.
12969
12970
- ``algorithm`` -- four options :
12971
12972
* ``"BFS"`` -- the computation is done through a BFS
12973
centered on each vertex successively. Only implemented
12974
when ``default_weight = 1`` and ``by_weight = False``.
12975
12976
* ``"Floyd-Warshall-Cython"`` -- through the Cython implementation of
12977
the Floyd-Warshall algorithm.
12978
12979
* ``"Floyd-Warshall-Python"`` -- through the Python implementation of
12980
the Floyd-Warshall algorithm.
12981
12982
* ``"auto"`` -- use the fastest algorithm depending on the input
12983
(``"BFS"`` if possible, and ``"Floyd-Warshall-Python"`` otherwise)
12984
12985
This is the default value.
12986
12987
OUTPUT:
12988
12989
A tuple ``(dist, pred)``. They are both dicts of dicts. The first
12990
indicates the length ``dist[u][v]`` of the shortest weighted path
12991
from `u` to `v`. The second is a compact representation of all the
12992
paths- it indicates the predecessor ``pred[u][v]`` of `v` in the
12993
shortest path from `u` to `v`.
12994
12995
.. NOTE::
12996
12997
Three different implementations are actually available through this method :
12998
12999
* BFS (Cython)
13000
* Floyd-Warshall (Cython)
13001
* Floyd-Warshall (Python)
13002
13003
The BFS algorithm is the fastest of the three, then comes the Cython
13004
implementation of Floyd-Warshall, and last the Python
13005
implementation. The first two implementations, however, only compute
13006
distances based on the topological distance (each edge is of weight
13007
1, or equivalently the length of a path is its number of
13008
edges). Besides, they do not deal with graphs larger than 65536
13009
vertices (which already represents 16GB of ram).
13010
13011
.. NOTE::
13012
13013
There is a Cython version of this method that is usually
13014
much faster for large graphs, as most of the time is
13015
actually spent building the final double
13016
dictionary. Everything on the subject is to be found in the
13017
:mod:`~sage.graphs.distances_all_pairs` module.
13018
13019
EXAMPLES::
13020
13021
sage: G = Graph( { 0: {1: 1}, 1: {2: 1}, 2: {3: 1}, 3: {4: 2}, 4: {0: 2} }, sparse=True )
13022
sage: G.plot(edge_labels=True).show() # long time
13023
sage: dist, pred = G.shortest_path_all_pairs(by_weight = True)
13024
sage: dist
13025
{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}}
13026
sage: pred
13027
{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}}
13028
sage: pred[0]
13029
{0: None, 1: 0, 2: 1, 3: 2, 4: 0}
13030
13031
So for example the shortest weighted path from `0` to `3` is obtained as
13032
follows. The predecessor of `3` is ``pred[0][3] == 2``, the predecessor
13033
of `2` is ``pred[0][2] == 1``, and the predecessor of `1` is
13034
``pred[0][1] == 0``.
13035
13036
::
13037
13038
sage: G = Graph( { 0: {1:None}, 1: {2:None}, 2: {3: 1}, 3: {4: 2}, 4: {0: 2} }, sparse=True )
13039
sage: G.shortest_path_all_pairs()
13040
({0: {0: 0, 1: 1, 2: 2, 3: 2, 4: 1},
13041
1: {0: 1, 1: 0, 2: 1, 3: 2, 4: 2},
13042
2: {0: 2, 1: 1, 2: 0, 3: 1, 4: 2},
13043
3: {0: 2, 1: 2, 2: 1, 3: 0, 4: 1},
13044
4: {0: 1, 1: 2, 2: 2, 3: 1, 4: 0}},
13045
{0: {0: None, 1: 0, 2: 1, 3: 4, 4: 0},
13046
1: {0: 1, 1: None, 2: 1, 3: 2, 4: 0},
13047
2: {0: 1, 1: 2, 2: None, 3: 2, 4: 3},
13048
3: {0: 4, 1: 2, 2: 3, 3: None, 4: 3},
13049
4: {0: 4, 1: 0, 2: 3, 3: 4, 4: None}})
13050
sage: G.shortest_path_all_pairs(by_weight = True)
13051
({0: {0: 0, 1: 1, 2: 2, 3: 3, 4: 2},
13052
1: {0: 1, 1: 0, 2: 1, 3: 2, 4: 3},
13053
2: {0: 2, 1: 1, 2: 0, 3: 1, 4: 3},
13054
3: {0: 3, 1: 2, 2: 1, 3: 0, 4: 2},
13055
4: {0: 2, 1: 3, 2: 3, 3: 2, 4: 0}},
13056
{0: {0: None, 1: 0, 2: 1, 3: 2, 4: 0},
13057
1: {0: 1, 1: None, 2: 1, 3: 2, 4: 0},
13058
2: {0: 1, 1: 2, 2: None, 3: 2, 4: 3},
13059
3: {0: 1, 1: 2, 2: 3, 3: None, 4: 3},
13060
4: {0: 4, 1: 0, 2: 3, 3: 4, 4: None}})
13061
sage: G.shortest_path_all_pairs(default_weight=200)
13062
({0: {0: 0, 1: 200, 2: 5, 3: 4, 4: 2},
13063
1: {0: 200, 1: 0, 2: 200, 3: 201, 4: 202},
13064
2: {0: 5, 1: 200, 2: 0, 3: 1, 4: 3},
13065
3: {0: 4, 1: 201, 2: 1, 3: 0, 4: 2},
13066
4: {0: 2, 1: 202, 2: 3, 3: 2, 4: 0}},
13067
{0: {0: None, 1: 0, 2: 3, 3: 4, 4: 0},
13068
1: {0: 1, 1: None, 2: 1, 3: 2, 4: 0},
13069
2: {0: 4, 1: 2, 2: None, 3: 2, 4: 3},
13070
3: {0: 4, 1: 2, 2: 3, 3: None, 4: 3},
13071
4: {0: 4, 1: 0, 2: 3, 3: 4, 4: None}})
13072
13073
Checking the distances are equal regardless of the algorithm used::
13074
13075
sage: g = graphs.Grid2dGraph(5,5)
13076
sage: d1, _ = g.shortest_path_all_pairs(algorithm="BFS")
13077
sage: d2, _ = g.shortest_path_all_pairs(algorithm="Floyd-Warshall-Cython")
13078
sage: d3, _ = g.shortest_path_all_pairs(algorithm="Floyd-Warshall-Python")
13079
sage: d1 == d2 == d3
13080
True
13081
13082
Checking a random path is valid ::
13083
13084
sage: dist, path = g.shortest_path_all_pairs(algorithm="BFS")
13085
sage: u,v = g.random_vertex(), g.random_vertex()
13086
sage: p = [v]
13087
sage: while p[0] != None:
13088
... p.insert(0,path[u][p[0]])
13089
sage: len(p) == dist[u][v] + 2
13090
True
13091
13092
TESTS:
13093
13094
Wrong name for ``algorithm``::
13095
13096
sage: g.shortest_path_all_pairs(algorithm="Bob")
13097
Traceback (most recent call last):
13098
...
13099
ValueError: The algorithm keyword can only be set to "auto", "BFS", "Floyd-Warshall-Python" or "Floyd-Warshall-Cython"
13100
"""
13101
if default_weight != 1:
13102
by_weight = True
13103
13104
if algorithm == "auto":
13105
if by_weight is False:
13106
algorithm = "BFS"
13107
else:
13108
algorithm = "Floyd-Warshall-Python"
13109
13110
if algorithm == "BFS":
13111
from sage.graphs.distances_all_pairs import distances_and_predecessors_all_pairs
13112
return distances_and_predecessors_all_pairs(self)
13113
13114
elif algorithm == "Floyd-Warshall-Cython":
13115
from sage.graphs.distances_all_pairs import floyd_warshall
13116
return floyd_warshall(self, distances = True)
13117
13118
elif algorithm != "Floyd-Warshall-Python":
13119
raise ValueError("The algorithm keyword can only be set to "+
13120
"\"auto\","+
13121
" \"BFS\", "+
13122
"\"Floyd-Warshall-Python\" or "+
13123
"\"Floyd-Warshall-Cython\"")
13124
13125
from sage.rings.infinity import Infinity
13126
dist = {}
13127
pred = {}
13128
verts = self.vertices()
13129
for u in verts:
13130
du = {}
13131
pu = {}
13132
for v in verts:
13133
if self.has_edge(u, v):
13134
if by_weight is False:
13135
du[v] = 1
13136
else:
13137
edge_label = self.edge_label(u, v)
13138
if edge_label is None or edge_label == {}:
13139
du[v] = default_weight
13140
else:
13141
du[v] = edge_label
13142
pu[v] = u
13143
else:
13144
du[v] = Infinity
13145
pu[v] = None
13146
du[u] = 0
13147
dist[u] = du
13148
pred[u] = pu
13149
13150
for w in verts:
13151
dw = dist[w]
13152
for u in verts:
13153
du = dist[u]
13154
for v in verts:
13155
if du[v] > du[w] + dw[v]:
13156
du[v] = du[w] + dw[v]
13157
pred[u][v] = pred[w][v]
13158
13159
return dist, pred
13160
13161
def average_distance(self):
13162
r"""
13163
Returns the average distance between vertices of the graph.
13164
13165
Formally, for a graph `G` this value is equal to
13166
`\frac 1 {n(n-1)} \sum_{u,v\in G} d(u,v)` where `d(u,v)`
13167
denotes the distance between vertices `u` and `v` and `n`
13168
is the number of vertices in `G`.
13169
13170
EXAMPLE:
13171
13172
From [GYLL93]_::
13173
13174
sage: g=graphs.PathGraph(10)
13175
sage: w=lambda x: (x*(x*x -1)/6)/(x*(x-1)/2)
13176
sage: g.average_distance()==w(10)
13177
True
13178
13179
REFERENCE:
13180
13181
.. [GYLL93] I. Gutman, Y.-N. Yeh, S.-L. Lee, and Y.-L. Luo. Some recent
13182
results in the theory of the Wiener number. *Indian Journal of
13183
Chemistry*, 32A:651--661, 1993.
13184
13185
TEST::
13186
13187
sage: g = Graph()
13188
sage: g.average_distance()
13189
Traceback (most recent call last):
13190
...
13191
ValueError: The graph must have at least two vertices for this value to be defined
13192
"""
13193
if self.order() < 2:
13194
raise ValueError("The graph must have at least two vertices for this value to be defined")
13195
13196
return Integer(self.wiener_index())/Integer((self.order()*(self.order()-1))/2)
13197
13198
def szeged_index(self):
13199
r"""
13200
Returns the Szeged index of the graph.
13201
13202
For any `uv\in E(G)`, let
13203
`N_u(uv) = \{w\in G:d(u,w)<d(v,w)\}, n_u(uv)=|N_u(uv)|`
13204
13205
The Szeged index of a graph is then defined as [1]:
13206
`\sum_{uv \in E(G)}n_u(uv)\times n_v(uv)`
13207
13208
EXAMPLE:
13209
13210
True for any connected graph [1]::
13211
13212
sage: g=graphs.PetersenGraph()
13213
sage: g.wiener_index()<= g.szeged_index()
13214
True
13215
13216
True for all trees [1]::
13217
13218
sage: g=Graph()
13219
sage: g.add_edges(graphs.CubeGraph(5).min_spanning_tree())
13220
sage: g.wiener_index() == g.szeged_index()
13221
True
13222
13223
13224
REFERENCE:
13225
13226
[1] Klavzar S., Rajapakse A., Gutman I. (1996). The Szeged and the
13227
Wiener index of graphs. Applied Mathematics Letters, 9 (5), pp. 45-49.
13228
"""
13229
distances=self.distance_all_pairs()
13230
s=0
13231
for (u,v) in self.edges(labels=None):
13232
du=distances[u]
13233
dv=distances[v]
13234
n1=n2=0
13235
for w in self:
13236
if du[w] < dv[w]:
13237
n1+=1
13238
elif dv[w] < du[w]:
13239
n2+=1
13240
s+=(n1*n2)
13241
return s
13242
13243
### Searches
13244
13245
def breadth_first_search(self, start, ignore_direction=False,
13246
distance=None, neighbors=None):
13247
"""
13248
Returns an iterator over the vertices in a breadth-first ordering.
13249
13250
INPUT:
13251
13252
13253
- ``start`` - vertex or list of vertices from which to start
13254
the traversal
13255
13256
- ``ignore_direction`` - (default False) only applies to
13257
directed graphs. If True, searches across edges in either
13258
direction.
13259
13260
- ``distance`` - the maximum distance from the ``start`` nodes
13261
to traverse. The ``start`` nodes are distance zero from
13262
themselves.
13263
13264
- ``neighbors`` - a function giving the neighbors of a vertex.
13265
The function should take a vertex and return a list of
13266
vertices. For a graph, ``neighbors`` is by default the
13267
:meth:`.neighbors` function of the graph. For a digraph,
13268
the ``neighbors`` function defaults to the
13269
:meth:`.successors` function of the graph.
13270
13271
.. SEEALSO::
13272
13273
- :meth:`breadth_first_search <sage.graphs.base.c_graph.CGraphBackend.breadth_first_search>`
13274
-- breadth-first search for fast compiled graphs.
13275
13276
- :meth:`depth_first_search <sage.graphs.base.c_graph.CGraphBackend.depth_first_search>`
13277
-- depth-first search for fast compiled graphs.
13278
13279
- :meth:`depth_first_search` -- depth-first search for generic graphs.
13280
13281
EXAMPLES::
13282
13283
sage: G = Graph( { 0: [1], 1: [2], 2: [3], 3: [4], 4: [0]} )
13284
sage: list(G.breadth_first_search(0))
13285
[0, 1, 4, 2, 3]
13286
13287
By default, the edge direction of a digraph is respected, but this
13288
can be overridden by the ``ignore_direction`` parameter::
13289
13290
sage: D = DiGraph( { 0: [1,2,3], 1: [4,5], 2: [5], 3: [6], 5: [7], 6: [7], 7: [0]})
13291
sage: list(D.breadth_first_search(0))
13292
[0, 1, 2, 3, 4, 5, 6, 7]
13293
sage: list(D.breadth_first_search(0, ignore_direction=True))
13294
[0, 1, 2, 3, 7, 4, 5, 6]
13295
13296
You can specify a maximum distance in which to search. A
13297
distance of zero returns the ``start`` vertices::
13298
13299
sage: D = DiGraph( { 0: [1,2,3], 1: [4,5], 2: [5], 3: [6], 5: [7], 6: [7], 7: [0]})
13300
sage: list(D.breadth_first_search(0,distance=0))
13301
[0]
13302
sage: list(D.breadth_first_search(0,distance=1))
13303
[0, 1, 2, 3]
13304
13305
Multiple starting vertices can be specified in a list::
13306
13307
sage: D = DiGraph( { 0: [1,2,3], 1: [4,5], 2: [5], 3: [6], 5: [7], 6: [7], 7: [0]})
13308
sage: list(D.breadth_first_search([0]))
13309
[0, 1, 2, 3, 4, 5, 6, 7]
13310
sage: list(D.breadth_first_search([0,6]))
13311
[0, 6, 1, 2, 3, 7, 4, 5]
13312
sage: list(D.breadth_first_search([0,6],distance=0))
13313
[0, 6]
13314
sage: list(D.breadth_first_search([0,6],distance=1))
13315
[0, 6, 1, 2, 3, 7]
13316
sage: list(D.breadth_first_search(6,ignore_direction=True,distance=2))
13317
[6, 3, 7, 0, 5]
13318
13319
More generally, you can specify a ``neighbors`` function. For
13320
example, you can traverse the graph backwards by setting
13321
``neighbors`` to be the :meth:`.neighbors_in` function of the graph::
13322
13323
sage: D = DiGraph( { 0: [1,2,3], 1: [4,5], 2: [5], 3: [6], 5: [7], 6: [7], 7: [0]})
13324
sage: list(D.breadth_first_search(5,neighbors=D.neighbors_in, distance=2))
13325
[5, 1, 2, 0]
13326
sage: list(D.breadth_first_search(5,neighbors=D.neighbors_out, distance=2))
13327
[5, 7, 0]
13328
sage: list(D.breadth_first_search(5,neighbors=D.neighbors, distance=2))
13329
[5, 1, 2, 7, 0, 4, 6]
13330
13331
13332
TESTS::
13333
13334
sage: D = DiGraph({1:[0], 2:[0]})
13335
sage: list(D.breadth_first_search(0))
13336
[0]
13337
sage: list(D.breadth_first_search(0, ignore_direction=True))
13338
[0, 1, 2]
13339
13340
"""
13341
# Preferably use the Cython implementation
13342
if neighbors is None and not isinstance(start,list) and distance is None and hasattr(self._backend,"breadth_first_search"):
13343
for v in self._backend.breadth_first_search(start, ignore_direction = ignore_direction):
13344
yield v
13345
else:
13346
if neighbors is None:
13347
if not self._directed or ignore_direction:
13348
neighbors=self.neighbor_iterator
13349
else:
13350
neighbors=self.neighbor_out_iterator
13351
seen=set([])
13352
if isinstance(start, list):
13353
queue=[(v,0) for v in start]
13354
else:
13355
queue=[(start,0)]
13356
13357
for v,d in queue:
13358
yield v
13359
seen.add(v)
13360
13361
while len(queue)>0:
13362
v,d = queue.pop(0)
13363
if distance is None or d<distance:
13364
for w in neighbors(v):
13365
if w not in seen:
13366
seen.add(w)
13367
queue.append((w, d+1))
13368
yield w
13369
13370
def depth_first_search(self, start, ignore_direction=False,
13371
distance=None, neighbors=None):
13372
"""
13373
Returns an iterator over the vertices in a depth-first ordering.
13374
13375
INPUT:
13376
13377
13378
- ``start`` - vertex or list of vertices from which to start
13379
the traversal
13380
13381
- ``ignore_direction`` - (default False) only applies to
13382
directed graphs. If True, searches across edges in either
13383
direction.
13384
13385
- ``distance`` - the maximum distance from the ``start`` nodes
13386
to traverse. The ``start`` nodes are distance zero from
13387
themselves.
13388
13389
- ``neighbors`` - a function giving the neighbors of a vertex.
13390
The function should take a vertex and return a list of
13391
vertices. For a graph, ``neighbors`` is by default the
13392
:meth:`.neighbors` function of the graph. For a digraph,
13393
the ``neighbors`` function defaults to the
13394
:meth:`.successors` function of the graph.
13395
13396
.. SEEALSO::
13397
13398
- :meth:`breadth_first_search`
13399
13400
- :meth:`breadth_first_search <sage.graphs.base.c_graph.CGraphBackend.breadth_first_search>`
13401
-- breadth-first search for fast compiled graphs.
13402
13403
- :meth:`depth_first_search <sage.graphs.base.c_graph.CGraphBackend.depth_first_search>`
13404
-- depth-first search for fast compiled graphs.
13405
13406
EXAMPLES::
13407
13408
sage: G = Graph( { 0: [1], 1: [2], 2: [3], 3: [4], 4: [0]} )
13409
sage: list(G.depth_first_search(0))
13410
[0, 4, 3, 2, 1]
13411
13412
By default, the edge direction of a digraph is respected, but this
13413
can be overridden by the ``ignore_direction`` parameter::
13414
13415
13416
sage: D = DiGraph( { 0: [1,2,3], 1: [4,5], 2: [5], 3: [6], 5: [7], 6: [7], 7: [0]})
13417
sage: list(D.depth_first_search(0))
13418
[0, 3, 6, 7, 2, 5, 1, 4]
13419
sage: list(D.depth_first_search(0, ignore_direction=True))
13420
[0, 7, 6, 3, 5, 2, 1, 4]
13421
13422
You can specify a maximum distance in which to search. A
13423
distance of zero returns the ``start`` vertices::
13424
13425
sage: D = DiGraph( { 0: [1,2,3], 1: [4,5], 2: [5], 3: [6], 5: [7], 6: [7], 7: [0]})
13426
sage: list(D.depth_first_search(0,distance=0))
13427
[0]
13428
sage: list(D.depth_first_search(0,distance=1))
13429
[0, 3, 2, 1]
13430
13431
Multiple starting vertices can be specified in a list::
13432
13433
sage: D = DiGraph( { 0: [1,2,3], 1: [4,5], 2: [5], 3: [6], 5: [7], 6: [7], 7: [0]})
13434
sage: list(D.depth_first_search([0]))
13435
[0, 3, 6, 7, 2, 5, 1, 4]
13436
sage: list(D.depth_first_search([0,6]))
13437
[0, 3, 6, 7, 2, 5, 1, 4]
13438
sage: list(D.depth_first_search([0,6],distance=0))
13439
[0, 6]
13440
sage: list(D.depth_first_search([0,6],distance=1))
13441
[0, 3, 2, 1, 6, 7]
13442
sage: list(D.depth_first_search(6,ignore_direction=True,distance=2))
13443
[6, 7, 5, 0, 3]
13444
13445
More generally, you can specify a ``neighbors`` function. For
13446
example, you can traverse the graph backwards by setting
13447
``neighbors`` to be the :meth:`.neighbors_in` function of the graph::
13448
13449
sage: D = DiGraph( { 0: [1,2,3], 1: [4,5], 2: [5], 3: [6], 5: [7], 6: [7], 7: [0]})
13450
sage: list(D.depth_first_search(5,neighbors=D.neighbors_in, distance=2))
13451
[5, 2, 0, 1]
13452
sage: list(D.depth_first_search(5,neighbors=D.neighbors_out, distance=2))
13453
[5, 7, 0]
13454
sage: list(D.depth_first_search(5,neighbors=D.neighbors, distance=2))
13455
[5, 7, 6, 0, 2, 1, 4]
13456
13457
TESTS::
13458
13459
sage: D = DiGraph({1:[0], 2:[0]})
13460
sage: list(D.depth_first_search(0))
13461
[0]
13462
sage: list(D.depth_first_search(0, ignore_direction=True))
13463
[0, 2, 1]
13464
13465
"""
13466
# Preferably use the Cython implementation
13467
if neighbors is None and not isinstance(start,list) and distance is None and hasattr(self._backend,"depth_first_search"):
13468
for v in self._backend.depth_first_search(start, ignore_direction = ignore_direction):
13469
yield v
13470
else:
13471
if neighbors is None:
13472
if not self._directed or ignore_direction:
13473
neighbors=self.neighbor_iterator
13474
else:
13475
neighbors=self.neighbor_out_iterator
13476
seen=set([])
13477
if isinstance(start, list):
13478
# Reverse the list so that the initial vertices come out in the same order
13479
queue=[(v,0) for v in reversed(start)]
13480
else:
13481
queue=[(start,0)]
13482
13483
while len(queue)>0:
13484
v,d = queue.pop()
13485
if v not in seen:
13486
yield v
13487
seen.add(v)
13488
if distance is None or d<distance:
13489
for w in neighbors(v):
13490
if w not in seen:
13491
queue.append((w, d+1))
13492
13493
def lex_BFS(self,reverse=False,tree=False, initial_vertex = None):
13494
r"""
13495
Performs a Lex BFS on the graph.
13496
13497
A Lex BFS ( or Lexicographic Breadth-First Search ) is a Breadth
13498
First Search used for the recognition of Chordal Graphs. For more
13499
information, see the
13500
`Wikipedia article on Lex-BFS
13501
<http://en.wikipedia.org/wiki/Lexicographic_breadth-first_search>`_.
13502
13503
INPUT:
13504
13505
- ``reverse`` (boolean) -- whether to return the vertices
13506
in discovery order, or the reverse.
13507
13508
``False`` by default.
13509
13510
- ``tree`` (boolean) -- whether to return the discovery
13511
directed tree (each vertex being linked to the one that
13512
saw it for the first time)
13513
13514
``False`` by default.
13515
13516
- ``initial_vertex`` -- the first vertex to consider.
13517
13518
``None`` by default.
13519
13520
ALGORITHM:
13521
13522
This algorithm maintains for each vertex left in the graph
13523
a code corresponding to the vertices already removed. The
13524
vertex of maximal code ( according to the lexicographic
13525
order ) is then removed, and the codes are updated.
13526
13527
This algorithm runs in time `O(n^2)` ( where `n` is the
13528
number of vertices in the graph ), which is not optimal.
13529
An optimal algorithm would run in time `O(m)` ( where `m`
13530
is the number of edges in the graph ), and require the use
13531
of a doubly-linked list which are not available in python
13532
and can not really be written efficiently. This could be
13533
done in Cython, though.
13534
13535
EXAMPLE:
13536
13537
A Lex BFS is obviously an ordering of the vertices::
13538
13539
sage: g = graphs.PetersenGraph()
13540
sage: len(g.lex_BFS()) == g.order()
13541
True
13542
13543
For a Chordal Graph, a reversed Lex BFS is a Perfect
13544
Elimination Order ::
13545
13546
sage: g = graphs.PathGraph(3).lexicographic_product(graphs.CompleteGraph(2))
13547
sage: g.lex_BFS(reverse=True)
13548
[(2, 1), (2, 0), (1, 1), (1, 0), (0, 1), (0, 0)]
13549
13550
13551
And the vertices at the end of the tree of discovery are, for
13552
chordal graphs, simplicial vertices (their neighborhood is
13553
a complete graph)::
13554
13555
sage: g = graphs.ClawGraph().lexicographic_product(graphs.CompleteGraph(2))
13556
sage: v = g.lex_BFS()[-1]
13557
sage: peo, tree = g.lex_BFS(initial_vertex = v, tree=True)
13558
sage: leaves = [v for v in tree if tree.in_degree(v) ==0]
13559
sage: all([g.subgraph(g.neighbors(v)).is_clique() for v in leaves])
13560
True
13561
13562
TESTS:
13563
13564
There were some problems with the following call in the past (trac 10899) -- now
13565
it should be fine::
13566
13567
sage: Graph(1).lex_BFS(tree=True)
13568
([0], Digraph on 1 vertex)
13569
13570
"""
13571
id_inv = dict([(i,v) for (v,i) in zip(self.vertices(),range(self.order()))])
13572
code = [[] for i in range(self.order())]
13573
m = self.am()
13574
13575
l = lambda x : code[x]
13576
vertices = set(range(self.order()))
13577
13578
value = []
13579
pred = [-1]*self.order()
13580
13581
add_element = (lambda y:value.append(id_inv[y])) if not reverse else (lambda y: value.insert(0,id_inv[y]))
13582
13583
# Should we take care of the first vertex we pick ?
13584
first = True if initial_vertex is not None else False
13585
13586
13587
while vertices:
13588
13589
if not first:
13590
v = max(vertices,key=l)
13591
else:
13592
v = self.vertices().index(initial_vertex)
13593
first = False
13594
13595
vertices.remove(v)
13596
vector = m.column(v)
13597
for i in vertices:
13598
code[i].append(vector[i])
13599
if vector[i]:
13600
pred[i] = v
13601
add_element(v)
13602
13603
if tree:
13604
from sage.graphs.digraph import DiGraph
13605
g = DiGraph(sparse=True)
13606
g.add_vertices(self.vertices())
13607
edges = [(id_inv[i], id_inv[pred[i]]) for i in range(self.order()) if pred[i]!=-1]
13608
g.add_edges(edges)
13609
return value, g
13610
13611
else:
13612
return value
13613
13614
### Constructors
13615
13616
def add_cycle(self, vertices):
13617
"""
13618
Adds a cycle to the graph with the given vertices. If the vertices
13619
are already present, only the edges are added.
13620
13621
For digraphs, adds the directed cycle, whose orientation is
13622
determined by the list. Adds edges (vertices[u], vertices[u+1]) and
13623
(vertices[-1], vertices[0]).
13624
13625
INPUT:
13626
13627
- ``vertices`` -- a list of indices for the vertices of
13628
the cycle to be added.
13629
13630
13631
EXAMPLES::
13632
13633
sage: G = Graph()
13634
sage: G.add_vertices(range(10)); G
13635
Graph on 10 vertices
13636
sage: show(G)
13637
sage: G.add_cycle(range(20)[10:20])
13638
sage: show(G)
13639
sage: G.add_cycle(range(10))
13640
sage: show(G)
13641
13642
::
13643
13644
sage: D = DiGraph()
13645
sage: D.add_cycle(range(4))
13646
sage: D.edges()
13647
[(0, 1, None), (1, 2, None), (2, 3, None), (3, 0, None)]
13648
"""
13649
self.add_path(vertices)
13650
self.add_edge(vertices[-1], vertices[0])
13651
13652
def add_path(self, vertices):
13653
"""
13654
Adds a cycle to the graph with the given vertices. If the vertices
13655
are already present, only the edges are added.
13656
13657
For digraphs, adds the directed path vertices[0], ...,
13658
vertices[-1].
13659
13660
INPUT:
13661
13662
13663
- ``vertices`` - a list of indices for the vertices of
13664
the cycle to be added.
13665
13666
13667
EXAMPLES::
13668
13669
sage: G = Graph()
13670
sage: G.add_vertices(range(10)); G
13671
Graph on 10 vertices
13672
sage: show(G)
13673
sage: G.add_path(range(20)[10:20])
13674
sage: show(G)
13675
sage: G.add_path(range(10))
13676
sage: show(G)
13677
13678
::
13679
13680
sage: D = DiGraph()
13681
sage: D.add_path(range(4))
13682
sage: D.edges()
13683
[(0, 1, None), (1, 2, None), (2, 3, None)]
13684
"""
13685
vert1 = vertices[0]
13686
for v in vertices[1:]:
13687
self.add_edge(vert1, v)
13688
vert1 = v
13689
13690
def complement(self):
13691
"""
13692
Returns the complement of the (di)graph.
13693
13694
The complement of a graph has the same vertices, but exactly those
13695
edges that are not in the original graph. This is not well defined
13696
for graphs with multiple edges.
13697
13698
EXAMPLES::
13699
13700
sage: P = graphs.PetersenGraph()
13701
sage: P.plot() # long time
13702
sage: PC = P.complement()
13703
sage: PC.plot() # long time
13704
13705
::
13706
13707
sage: graphs.TetrahedralGraph().complement().size()
13708
0
13709
sage: graphs.CycleGraph(4).complement().edges()
13710
[(0, 2, None), (1, 3, None)]
13711
sage: graphs.CycleGraph(4).complement()
13712
complement(Cycle graph): Graph on 4 vertices
13713
sage: G = Graph(multiedges=True, sparse=True)
13714
sage: G.add_edges([(0,1)]*3)
13715
sage: G.complement()
13716
Traceback (most recent call last):
13717
...
13718
TypeError: complement not well defined for (di)graphs with multiple edges
13719
13720
TESTS:
13721
13722
We check that :trac:`15699` is fixed::
13723
13724
sage: G = graphs.PathGraph(5).copy(immutable=True)
13725
sage: G.complement()
13726
complement(Path Graph): Graph on 5 vertices
13727
"""
13728
if self.has_multiple_edges():
13729
raise TypeError('complement not well defined for (di)graphs with multiple edges')
13730
self._scream_if_not_simple()
13731
G = self.copy(immutable=False) # Make sure it's a mutable copy
13732
G.delete_edges(G.edges())
13733
G.name('complement(%s)'%self.name())
13734
for u in self:
13735
for v in self:
13736
if not self.has_edge(u,v):
13737
G.add_edge(u,v)
13738
if getattr(self, '_immutable', False):
13739
return G.copy(immutable=True)
13740
return G
13741
13742
def to_simple(self):
13743
"""
13744
Returns a simple version of itself (i.e., undirected and loops and
13745
multiple edges are removed).
13746
13747
EXAMPLES::
13748
13749
sage: G = DiGraph(loops=True,multiedges=True,sparse=True)
13750
sage: G.add_edges( [ (0,0), (1,1), (2,2), (2,3,1), (2,3,2), (3,2) ] )
13751
sage: G.edges(labels=False)
13752
[(0, 0), (1, 1), (2, 2), (2, 3), (2, 3), (3, 2)]
13753
sage: H=G.to_simple()
13754
sage: H.edges(labels=False)
13755
[(2, 3)]
13756
sage: H.is_directed()
13757
False
13758
sage: H.allows_loops()
13759
False
13760
sage: H.allows_multiple_edges()
13761
False
13762
"""
13763
g=self.to_undirected()
13764
g.allow_loops(False)
13765
g.allow_multiple_edges(False)
13766
return g
13767
13768
def disjoint_union(self, other, verbose_relabel=True):
13769
"""
13770
Returns the disjoint union of self and other.
13771
13772
INPUT:
13773
13774
- ``verbose_relabel`` - (defaults to True) If True, each
13775
vertex v in the first graph will be named '0,v' and each
13776
vertex u in the second graph will be named '1,u' in the
13777
final graph. If False, the vertices of the first graph and
13778
the second graph will be relabeled with consecutive
13779
integers.
13780
13781
.. SEEALSO::
13782
13783
* :meth:`~sage.graphs.generic_graph.GenericGraph.union`
13784
13785
* :meth:`~Graph.join`
13786
13787
EXAMPLES::
13788
13789
sage: G = graphs.CycleGraph(3)
13790
sage: H = graphs.CycleGraph(4)
13791
sage: J = G.disjoint_union(H); J
13792
Cycle graph disjoint_union Cycle graph: Graph on 7 vertices
13793
sage: J.vertices()
13794
[(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (1, 3)]
13795
sage: J = G.disjoint_union(H, verbose_relabel=False); J
13796
Cycle graph disjoint_union Cycle graph: Graph on 7 vertices
13797
sage: J.vertices()
13798
[0, 1, 2, 3, 4, 5, 6]
13799
13800
::
13801
13802
sage: G=Graph({'a': ['b']})
13803
sage: G.name("Custom path")
13804
sage: G.name()
13805
'Custom path'
13806
sage: H=graphs.CycleGraph(3)
13807
sage: J=G.disjoint_union(H); J
13808
Custom path disjoint_union Cycle graph: Graph on 5 vertices
13809
sage: J.vertices()
13810
[(0, 'a'), (0, 'b'), (1, 0), (1, 1), (1, 2)]
13811
"""
13812
if (self._directed and not other._directed) or (not self._directed and other._directed):
13813
raise TypeError('both arguments must be of the same class')
13814
13815
if not verbose_relabel:
13816
r_self = {}; r_other = {}; i = 0
13817
for v in self:
13818
r_self[v] = i; i += 1
13819
for v in other:
13820
r_other[v] = i; i += 1
13821
G = self.relabel(r_self, inplace=False).union(other.relabel(r_other, inplace=False))
13822
else:
13823
r_self = dict([[v,(0,v)] for v in self])
13824
r_other = dict([[v,(1,v)] for v in other])
13825
G = self.relabel(r_self, inplace=False).union(other.relabel(r_other, inplace=False))
13826
13827
G.name('%s disjoint_union %s'%(self.name(), other.name()))
13828
return G
13829
13830
def union(self, other):
13831
"""
13832
Returns the union of self and other.
13833
13834
If the graphs have common vertices, the common vertices will be
13835
identified.
13836
13837
.. SEEALSO::
13838
13839
* :meth:`~sage.graphs.generic_graph.GenericGraph.disjoint_union`
13840
13841
* :meth:`~Graph.join`
13842
13843
EXAMPLES::
13844
13845
sage: G = graphs.CycleGraph(3)
13846
sage: H = graphs.CycleGraph(4)
13847
sage: J = G.union(H); J
13848
Graph on 4 vertices
13849
sage: J.vertices()
13850
[0, 1, 2, 3]
13851
sage: J.edges(labels=False)
13852
[(0, 1), (0, 2), (0, 3), (1, 2), (2, 3)]
13853
"""
13854
if (self._directed and not other._directed) or (not self._directed and other._directed):
13855
raise TypeError('both arguments must be of the same class')
13856
if self._directed:
13857
from sage.graphs.all import DiGraph
13858
G = DiGraph()
13859
else:
13860
from sage.graphs.all import Graph
13861
G = Graph()
13862
G.add_vertices(self.vertices())
13863
G.add_vertices(other.vertices())
13864
G.add_edges(self.edges())
13865
G.add_edges(other.edges())
13866
return G
13867
13868
def cartesian_product(self, other):
13869
r"""
13870
Returns the Cartesian product of self and other.
13871
13872
The Cartesian product of `G` and `H` is the graph `L` with vertex set
13873
`V(L)` equal to the Cartesian product of the vertices `V(G)` and `V(H)`,
13874
and `((u,v), (w,x))` is an edge iff either - `(u, w)` is an edge of self
13875
and `v = x`, or - `(v, x)` is an edge of other and `u = w`.
13876
13877
.. SEEALSO::
13878
13879
- :meth:`~sage.graphs.graph_decompositions.graph_products.is_cartesian_product`
13880
-- factorization of graphs according to the cartesian product
13881
13882
- :mod:`~sage.graphs.graph_decompositions.graph_products`
13883
-- a module on graph products.
13884
13885
TESTS:
13886
13887
Cartesian product of graphs::
13888
13889
sage: G = Graph([(0,1),(1,2)])
13890
sage: H = Graph([('a','b')])
13891
sage: C1 = G.cartesian_product(H)
13892
sage: C1.edges(labels=None)
13893
[((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'))]
13894
sage: C2 = H.cartesian_product(G)
13895
sage: C1.is_isomorphic(C2)
13896
True
13897
13898
Construction of a Toroidal grid::
13899
13900
sage: A = graphs.CycleGraph(3)
13901
sage: B = graphs.CycleGraph(4)
13902
sage: T = A.cartesian_product(B)
13903
sage: T.is_isomorphic( graphs.ToroidalGrid2dGraph(3,4) )
13904
True
13905
13906
Cartesian product of digraphs::
13907
13908
sage: P = DiGraph([(0,1)])
13909
sage: B = digraphs.DeBruijn( ['a','b'], 2 )
13910
sage: Q = P.cartesian_product(B)
13911
sage: Q.edges(labels=None)
13912
[((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'))]
13913
sage: Q.strongly_connected_components_digraph().num_verts()
13914
2
13915
sage: V = Q.strongly_connected_component_containing_vertex( (0, 'aa') )
13916
sage: B.is_isomorphic( Q.subgraph(V) )
13917
True
13918
"""
13919
self._scream_if_not_simple(allow_loops=True)
13920
if self._directed and other._directed:
13921
from sage.graphs.all import DiGraph
13922
G = DiGraph( loops = (self.has_loops() or other.has_loops()) )
13923
elif (not self._directed) and (not other._directed):
13924
from sage.graphs.all import Graph
13925
G = Graph()
13926
else:
13927
raise TypeError('the graphs should be both directed or both undirected')
13928
13929
G.add_vertices( [(u,v) for u in self for v in other] )
13930
for u,w in self.edge_iterator(labels=None):
13931
for v in other:
13932
G.add_edge((u,v), (w,v))
13933
for v,x in other.edge_iterator(labels=None):
13934
for u in self:
13935
G.add_edge((u,v), (u,x))
13936
return G
13937
13938
def tensor_product(self, other):
13939
r"""
13940
Returns the tensor product of self and other.
13941
13942
The tensor product of `G` and `H` is the graph `L` with vertex set
13943
`V(L)` equal to the Cartesian product of the vertices `V(G)` and `V(H)`,
13944
and `((u,v), (w,x))` is an edge iff - `(u, w)` is an edge of self, and -
13945
`(v, x)` is an edge of other.
13946
13947
The tensor product is also known as the categorical product and the
13948
kronecker product (refering to the kronecker matrix product). See
13949
:wikipedia:`Wikipedia article on the Kronecker product <Kronecker_product>`.
13950
13951
EXAMPLES::
13952
13953
sage: Z = graphs.CompleteGraph(2)
13954
sage: C = graphs.CycleGraph(5)
13955
sage: T = C.tensor_product(Z); T
13956
Graph on 10 vertices
13957
sage: T.size()
13958
10
13959
sage: T.plot() # long time
13960
13961
::
13962
13963
sage: D = graphs.DodecahedralGraph()
13964
sage: P = graphs.PetersenGraph()
13965
sage: T = D.tensor_product(P); T
13966
Graph on 200 vertices
13967
sage: T.size()
13968
900
13969
sage: T.plot() # long time
13970
13971
TESTS:
13972
13973
Tensor product of graphs::
13974
13975
sage: G = Graph([(0,1), (1,2)])
13976
sage: H = Graph([('a','b')])
13977
sage: T = G.tensor_product(H)
13978
sage: T.edges(labels=None)
13979
[((0, 'a'), (1, 'b')), ((0, 'b'), (1, 'a')), ((1, 'a'), (2, 'b')), ((1, 'b'), (2, 'a'))]
13980
sage: T.is_isomorphic( H.tensor_product(G) )
13981
True
13982
13983
Tensor product of digraphs::
13984
13985
sage: I = DiGraph([(0,1), (1,2)])
13986
sage: J = DiGraph([('a','b')])
13987
sage: T = I.tensor_product(J)
13988
sage: T.edges(labels=None)
13989
[((0, 'a'), (1, 'b')), ((1, 'a'), (2, 'b'))]
13990
sage: T.is_isomorphic( J.tensor_product(I) )
13991
True
13992
13993
The tensor product of two DeBruijn digraphs of same diameter is a DeBruijn digraph::
13994
13995
sage: B1 = digraphs.DeBruijn(2, 3)
13996
sage: B2 = digraphs.DeBruijn(3, 3)
13997
sage: T = B1.tensor_product( B2 )
13998
sage: T.is_isomorphic( digraphs.DeBruijn( 2*3, 3) )
13999
True
14000
"""
14001
self._scream_if_not_simple(allow_loops=True)
14002
if self._directed and other._directed:
14003
from sage.graphs.all import DiGraph
14004
G = DiGraph( loops = (self.has_loops() or other.has_loops()) )
14005
elif (not self._directed) and (not other._directed):
14006
from sage.graphs.all import Graph
14007
G = Graph()
14008
else:
14009
raise TypeError('the graphs should be both directed or both undirected')
14010
G.add_vertices( [(u, v) for u in self for v in other] )
14011
for u, w in self.edge_iterator(labels=None):
14012
for v, x in other.edge_iterator(labels=None):
14013
G.add_edge((u, v), (w, x))
14014
if not G._directed:
14015
G.add_edge((u, x), (w, v))
14016
return G
14017
14018
categorical_product = tensor_product
14019
kronecker_product = tensor_product
14020
14021
def lexicographic_product(self, other):
14022
r"""
14023
Returns the lexicographic product of self and other.
14024
14025
The lexicographic product of `G` and `H` is the graph `L` with vertex
14026
set `V(L)=V(G)\times V(H)`, and `((u,v), (w,x))` is an edge iff :
14027
14028
* `(u, w)` is an edge of `G`, or
14029
* `u = w` and `(v, x)` is an edge of `H`.
14030
14031
EXAMPLES::
14032
14033
sage: Z = graphs.CompleteGraph(2)
14034
sage: C = graphs.CycleGraph(5)
14035
sage: L = C.lexicographic_product(Z); L
14036
Graph on 10 vertices
14037
sage: L.plot() # long time
14038
14039
::
14040
14041
sage: D = graphs.DodecahedralGraph()
14042
sage: P = graphs.PetersenGraph()
14043
sage: L = D.lexicographic_product(P); L
14044
Graph on 200 vertices
14045
sage: L.plot() # long time
14046
14047
TESTS:
14048
14049
Lexicographic product of graphs::
14050
14051
sage: G = Graph([(0,1), (1,2)])
14052
sage: H = Graph([('a','b')])
14053
sage: T = G.lexicographic_product(H)
14054
sage: T.edges(labels=None)
14055
[((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'))]
14056
sage: T.is_isomorphic( H.lexicographic_product(G) )
14057
False
14058
14059
Lexicographic product of digraphs::
14060
14061
sage: I = DiGraph([(0,1), (1,2)])
14062
sage: J = DiGraph([('a','b')])
14063
sage: T = I.lexicographic_product(J)
14064
sage: T.edges(labels=None)
14065
[((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'))]
14066
sage: T.is_isomorphic( J.lexicographic_product(I) )
14067
False
14068
"""
14069
self._scream_if_not_simple(allow_loops=True)
14070
if self._directed and other._directed:
14071
from sage.graphs.all import DiGraph
14072
G = DiGraph( loops = (self.has_loops() or other.has_loops()) )
14073
elif (not self._directed) and (not other._directed):
14074
from sage.graphs.all import Graph
14075
G = Graph()
14076
else:
14077
raise TypeError('the graphs should be both directed or both undirected')
14078
G.add_vertices( [(u,v) for u in self for v in other] )
14079
for u,w in self.edge_iterator(labels=None):
14080
for v in other:
14081
for x in other:
14082
G.add_edge((u,v), (w,x))
14083
for u in self:
14084
for v,x in other.edge_iterator(labels=None):
14085
G.add_edge((u,v), (u,x))
14086
return G
14087
14088
def strong_product(self, other):
14089
r"""
14090
Returns the strong product of self and other.
14091
14092
The strong product of `G` and `H` is the graph `L` with vertex set
14093
`V(L)=V(G)\times V(H)`, and `((u,v), (w,x))` is an edge of `L` iff
14094
either :
14095
14096
* `(u, w)` is an edge of `G` and `v = x`, or
14097
* `(v, x)` is an edge of `H` and `u = w`, or
14098
* `(u, w)` is an edge of `G` and `(v, x)` is an edge of `H`.
14099
14100
In other words, the edges of the strong product is the union of the
14101
edges of the tensor and Cartesian products.
14102
14103
EXAMPLES::
14104
14105
sage: Z = graphs.CompleteGraph(2)
14106
sage: C = graphs.CycleGraph(5)
14107
sage: S = C.strong_product(Z); S
14108
Graph on 10 vertices
14109
sage: S.plot() # long time
14110
14111
::
14112
14113
sage: D = graphs.DodecahedralGraph()
14114
sage: P = graphs.PetersenGraph()
14115
sage: S = D.strong_product(P); S
14116
Graph on 200 vertices
14117
sage: S.plot() # long time
14118
14119
TESTS:
14120
14121
Strong product of graphs is commutative::
14122
14123
sage: G = Graph([(0,1), (1,2)])
14124
sage: H = Graph([('a','b')])
14125
sage: T = G.strong_product(H)
14126
sage: T.is_isomorphic( H.strong_product(G) )
14127
True
14128
14129
Strong product of digraphs is commutative::
14130
14131
sage: I = DiGraph([(0,1), (1,2)])
14132
sage: J = DiGraph([('a','b')])
14133
sage: T = I.strong_product(J)
14134
sage: T.is_isomorphic( J.strong_product(I) )
14135
True
14136
14137
Counting the edges (see :trac:`13699`)::
14138
14139
sage: g = graphs.RandomGNP(5,.5)
14140
sage: gn,gm = g.order(), g.size()
14141
sage: h = graphs.RandomGNP(5,.5)
14142
sage: hn,hm = h.order(), h.size()
14143
sage: product_size = g.strong_product(h).size()
14144
sage: expected = gm*hn + hm*gn + 2*gm*hm
14145
sage: if product_size != expected:
14146
... print "Something is really wrong here...", product_size, "!=", expected
14147
"""
14148
self._scream_if_not_simple(allow_loops=True)
14149
if self._directed and other._directed:
14150
from sage.graphs.all import DiGraph
14151
G = DiGraph( loops = (self.has_loops() or other.has_loops()) )
14152
elif (not self._directed) and (not other._directed):
14153
from sage.graphs.all import Graph
14154
G = Graph()
14155
else:
14156
raise TypeError('the graphs should be both directed or both undirected')
14157
14158
G.add_vertices( [(u,v) for u in self for v in other] )
14159
for u,w in self.edge_iterator(labels=None):
14160
for v in other:
14161
G.add_edge((u,v), (w,v))
14162
for v,x in other.edge_iterator(labels=None):
14163
G.add_edge((u,v), (w,x))
14164
if not self._directed:
14165
G.add_edge((w,v), (u,x))
14166
for v,x in other.edge_iterator(labels=None):
14167
for u in self:
14168
G.add_edge((u,v), (u,x))
14169
return G
14170
14171
def disjunctive_product(self, other):
14172
r"""
14173
Returns the disjunctive product of self and other.
14174
14175
The disjunctive product of `G` and `H` is the graph `L` with vertex set
14176
`V(L)=V(G)\times V(H)`, and `((u,v), (w,x))` is an edge iff either :
14177
14178
* `(u, w)` is an edge of `G`, or
14179
* `(v, x)` is an edge of `H`.
14180
14181
EXAMPLES::
14182
14183
sage: Z = graphs.CompleteGraph(2)
14184
sage: D = Z.disjunctive_product(Z); D
14185
Graph on 4 vertices
14186
sage: D.plot() # long time
14187
14188
::
14189
14190
sage: C = graphs.CycleGraph(5)
14191
sage: D = C.disjunctive_product(Z); D
14192
Graph on 10 vertices
14193
sage: D.plot() # long time
14194
14195
TESTS:
14196
14197
Disjunctive product of graphs::
14198
14199
sage: G = Graph([(0,1), (1,2)])
14200
sage: H = Graph([('a','b')])
14201
sage: T = G.disjunctive_product(H)
14202
sage: T.edges(labels=None)
14203
[((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'))]
14204
sage: T.is_isomorphic( H.disjunctive_product(G) )
14205
True
14206
14207
Disjunctive product of digraphs::
14208
14209
sage: I = DiGraph([(0,1), (1,2)])
14210
sage: J = DiGraph([('a','b')])
14211
sage: T = I.disjunctive_product(J)
14212
sage: T.edges(labels=None)
14213
[((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'))]
14214
sage: T.is_isomorphic( J.disjunctive_product(I) )
14215
True
14216
"""
14217
self._scream_if_not_simple(allow_loops=True)
14218
if self._directed and other._directed:
14219
from sage.graphs.all import DiGraph
14220
G = DiGraph( loops = (self.has_loops() or other.has_loops()) )
14221
elif (not self._directed) and (not other._directed):
14222
from sage.graphs.all import Graph
14223
G = Graph()
14224
else:
14225
raise TypeError('the graphs should be both directed or both undirected')
14226
14227
G.add_vertices( [(u,v) for u in self for v in other] )
14228
for u,w in self.edge_iterator(labels=None):
14229
for v in other:
14230
for x in other:
14231
G.add_edge((u,v), (w,x))
14232
for v,x in other.edge_iterator(labels=None):
14233
for u in self:
14234
for w in self:
14235
G.add_edge((u,v), (w,x))
14236
return G
14237
14238
def transitive_closure(self):
14239
r"""
14240
Computes the transitive closure of a graph and returns it. The
14241
original graph is not modified.
14242
14243
The transitive closure of a graph G has an edge (x,y) if and only
14244
if there is a path between x and y in G.
14245
14246
The transitive closure of any strongly connected component of a
14247
graph is a complete graph. In particular, the transitive closure of
14248
a connected undirected graph is a complete graph. The transitive
14249
closure of a directed acyclic graph is a directed acyclic graph
14250
representing the full partial order.
14251
14252
EXAMPLES::
14253
14254
sage: g=graphs.PathGraph(4)
14255
sage: g.transitive_closure()
14256
Transitive closure of Path Graph: Graph on 4 vertices
14257
sage: g.transitive_closure()==graphs.CompleteGraph(4)
14258
True
14259
sage: g=DiGraph({0:[1,2], 1:[3], 2:[4,5]})
14260
sage: g.transitive_closure().edges(labels=False)
14261
[(0, 1), (0, 2), (0, 3), (0, 4), (0, 5), (1, 3), (2, 4), (2, 5)]
14262
14263
"""
14264
from copy import copy
14265
G = copy(self)
14266
G.name('Transitive closure of ' + self.name())
14267
for v in G:
14268
# todo optimization opportunity: we are adding edges that
14269
# are already in the graph and we are adding edges
14270
# one at a time.
14271
for e in G.breadth_first_search(v):
14272
G.add_edge((v,e))
14273
return G
14274
14275
def transitive_reduction(self):
14276
r"""
14277
Returns a transitive reduction of a graph. The original graph is
14278
not modified.
14279
14280
A transitive reduction H of G has a path from x to y if and only if
14281
there was a path from x to y in G. Deleting any edge of H destroys
14282
this property. A transitive reduction is not unique in general. A
14283
transitive reduction has the same transitive closure as the
14284
original graph.
14285
14286
A transitive reduction of a complete graph is a tree. A transitive
14287
reduction of a tree is itself.
14288
14289
EXAMPLES::
14290
14291
sage: g=graphs.PathGraph(4)
14292
sage: g.transitive_reduction()==g
14293
True
14294
sage: g=graphs.CompleteGraph(5)
14295
sage: edges = g.transitive_reduction().edges(); len(edges)
14296
4
14297
sage: g=DiGraph({0:[1,2], 1:[2,3,4,5], 2:[4,5]})
14298
sage: g.transitive_reduction().size()
14299
5
14300
"""
14301
from copy import copy
14302
from sage.rings.infinity import Infinity
14303
G = copy(self)
14304
for e in self.edge_iterator():
14305
# Try deleting the edge, see if we still have a path
14306
# between the vertices.
14307
G.delete_edge(e)
14308
if G.distance(e[0],e[1])==Infinity:
14309
# oops, we shouldn't have deleted it
14310
G.add_edge(e)
14311
return G
14312
14313
def is_transitively_reduced(self):
14314
r"""
14315
Tests whether the digraph is transitively reduced.
14316
14317
A digraph is transitively reduced if it is equal to its transitive
14318
reduction.
14319
14320
EXAMPLES::
14321
14322
sage: d = DiGraph({0:[1],1:[2],2:[3]})
14323
sage: d.is_transitively_reduced()
14324
True
14325
14326
sage: d = DiGraph({0:[1,2],1:[2]})
14327
sage: d.is_transitively_reduced()
14328
False
14329
14330
sage: d = DiGraph({0:[1,2],1:[2],2:[]})
14331
sage: d.is_transitively_reduced()
14332
False
14333
"""
14334
from copy import copy
14335
from sage.rings.infinity import Infinity
14336
G = copy(self)
14337
for e in self.edge_iterator():
14338
G.delete_edge(e)
14339
if G.distance(e[0],e[1]) == Infinity:
14340
G.add_edge(e)
14341
else:
14342
return False
14343
return True
14344
14345
14346
### Visualization
14347
14348
def _color_by_label(self, format='hex', as_function=False, default_color = "black"):
14349
"""
14350
Coloring of the edges according to their label for plotting
14351
14352
INPUT:
14353
14354
- ``format`` -- "rgbtuple", "hex", ``True`` (same as "hex"),
14355
or a function or dictionary assigning colors to labels
14356
(default: "hex")
14357
- ``default_color`` -- a color (as a string) or None (default: "black")
14358
- ``as_function`` -- boolean (default: ``False``)
14359
14360
OUTPUT: A coloring of the edges of this graph.
14361
14362
If ``as_function`` is ``True``, then the coloring is returned
14363
as a function assigning a color to each label. Otherwise (the
14364
default, for backward compatibility), the coloring is returned
14365
as a dictionary assigning to each color the list of the edges
14366
of the graph of that color.
14367
14368
This is factored out from plot() for use in 3d plots, etc.
14369
14370
If ``format`` is a function, then it is used directly as
14371
coloring. Otherwise, for each label a default color is chosen
14372
along a rainbow (see :func:`sage.plot.colors.rainbow`). If
14373
``format`` is a dictionary, then the colors specified there
14374
override the default choices.
14375
14376
EXAMPLES:
14377
14378
We consider the Cayley graph of the symmetric group, whose
14379
edges are labelled by the numbers 1,2, and 3::
14380
14381
sage: G = SymmetricGroup(4).cayley_graph()
14382
sage: set(G.edge_labels())
14383
set([1, 2, 3])
14384
14385
We first request the coloring as a function::
14386
14387
sage: f = G._color_by_label(as_function=True)
14388
sage: [f(1), f(2), f(3)]
14389
['#00ff00', '#ff0000', '#0000ff']
14390
sage: f = G._color_by_label({1: "blue", 2: "red", 3: "green"}, as_function=True)
14391
sage: [f(1), f(2), f(3)]
14392
['blue', 'red', 'green']
14393
sage: f = G._color_by_label({1: "red"}, as_function=True)
14394
sage: [f(1), f(2), f(3)]
14395
['red', 'black', 'black']
14396
sage: f = G._color_by_label({1: "red"}, as_function=True, default_color = 'blue')
14397
sage: [f(1), f(2), f(3)]
14398
['red', 'blue', 'blue']
14399
14400
The default output is a dictionary assigning edges to colors::
14401
14402
sage: G._color_by_label()
14403
{'#00ff00': [((1,4,3,2), (1,4,3), 1), ... ((1,2)(3,4), (3,4), 1)],
14404
'#ff0000': [((1,4,3,2), (1,4,2), 2), ... ((1,2)(3,4), (1,3,4,2), 2)],
14405
'#0000ff': [((1,4,3,2), (1,3,2), 3), ... ((1,2)(3,4), (1,2), 3)]}
14406
14407
sage: G._color_by_label({1: "blue", 2: "red", 3: "green"})
14408
{'blue': [((1,4,3,2), (1,4,3), 1), ... ((1,2)(3,4), (3,4), 1)],
14409
'green': [((1,4,3,2), (1,3,2), 3), ... ((1,2)(3,4), (1,2), 3)],
14410
'red': [((1,4,3,2), (1,4,2), 2), ... ((1,2)(3,4), (1,3,4,2), 2)]}
14411
14412
TESTS:
14413
14414
We check what happens when several labels have the same color::
14415
14416
sage: result = G._color_by_label({1: "blue", 2: "blue", 3: "green"})
14417
sage: result.keys()
14418
['blue', 'green']
14419
sage: len(result['blue'])
14420
48
14421
sage: len(result['green'])
14422
24
14423
"""
14424
if format is True:
14425
format = "hex"
14426
if isinstance(format, str):
14427
# Find all labels; this is slower and huglier than:
14428
# labels = set(edge[2] for edge in self.edge_iterator())
14429
# but works with non hashable edge labels, and keeps backward
14430
# compatibility for the label ordering.
14431
labels = []
14432
for edge in self.edge_iterator():
14433
label = edge[2]
14434
if label not in labels:
14435
labels.append(label)
14436
14437
from sage.plot.colors import rainbow
14438
colors = rainbow(len(labels), format=format)
14439
color_of_label = dict(zip(labels, colors))
14440
color_of_label = color_of_label.__getitem__
14441
elif isinstance(format, dict):
14442
color_of_label = lambda label: format.get(label, default_color)
14443
else:
14444
# This assumes that ``format`` is already a function
14445
color_of_label = format
14446
14447
if as_function:
14448
return color_of_label
14449
14450
edges_by_color = {}
14451
for edge in self.edge_iterator():
14452
color = color_of_label(edge[2])
14453
if color in edges_by_color:
14454
edges_by_color[color].append(edge)
14455
else:
14456
edges_by_color[color] = [edge]
14457
return edges_by_color
14458
14459
def latex_options(self):
14460
r"""
14461
Returns an instance of
14462
:class:`~sage.graphs.graph_latex.GraphLatex` for the graph.
14463
14464
Changes to this object will affect the LaTeX version of the graph. For
14465
a full explanation of how to use LaTeX to render graphs, see the
14466
introduction to the :mod:`~sage.graphs.graph_latex` module.
14467
14468
EXAMPLES::
14469
14470
sage: g = graphs.PetersenGraph()
14471
sage: opts = g.latex_options()
14472
sage: opts
14473
LaTeX options for Petersen graph: {}
14474
sage: opts.set_option('tkz_style', 'Classic')
14475
sage: opts
14476
LaTeX options for Petersen graph: {'tkz_style': 'Classic'}
14477
"""
14478
if self._latex_opts is None:
14479
from sage.graphs.graph_latex import GraphLatex
14480
self._latex_opts = GraphLatex(self)
14481
return self._latex_opts
14482
14483
def set_latex_options(self, **kwds):
14484
r"""
14485
Sets multiple options for rendering a graph with LaTeX.
14486
14487
INPUTS:
14488
14489
- ``kwds`` - any number of option/value pairs to set many graph
14490
latex options at once (a variable number, in any
14491
order). Existing values are overwritten, new values are
14492
added. Existing values can be cleared by setting the value
14493
to ``None``. Possible options are documented at
14494
:meth:`sage.graphs.graph_latex.GraphLatex.set_option`.
14495
14496
This method is a convenience for setting the options of a graph
14497
directly on an instance of the graph. For a full explanation of
14498
how to use LaTeX to render graphs, see the introduction to the
14499
:mod:`~sage.graphs.graph_latex` module.
14500
14501
EXAMPLES::
14502
14503
sage: g = graphs.PetersenGraph()
14504
sage: g.set_latex_options(tkz_style = 'Welsh')
14505
sage: opts = g.latex_options()
14506
sage: opts.get_option('tkz_style')
14507
'Welsh'
14508
"""
14509
opts = self.latex_options()
14510
opts.set_options(**kwds)
14511
14512
14513
def layout(self, layout = None, pos = None, dim = 2, save_pos = False, **options):
14514
"""
14515
Returns a layout for the vertices of this graph.
14516
14517
INPUT:
14518
14519
- layout -- one of "acyclic", "circular", "ranked", "graphviz", "planar", "spring", or "tree"
14520
14521
- pos -- a dictionary of positions or None (the default)
14522
14523
- save_pos -- a boolean
14524
14525
- layout options -- (see below)
14526
14527
If ``layout=algorithm`` is specified, this algorithm is used
14528
to compute the positions.
14529
14530
Otherwise, if ``pos`` is specified, use the given positions.
14531
14532
Otherwise, try to fetch previously computed and saved positions.
14533
14534
Otherwise use the default layout (usually the spring layout)
14535
14536
If ``save_pos = True``, the layout is saved for later use.
14537
14538
EXAMPLES::
14539
14540
sage: g = digraphs.ButterflyGraph(1)
14541
sage: g.layout()
14542
{('1', 1): [2.50..., -0.545...],
14543
('0', 0): [2.22..., 0.832...],
14544
('1', 0): [1.12..., -0.830...],
14545
('0', 1): [0.833..., 0.543...]}
14546
14547
sage: 1+1
14548
2
14549
sage: x = g.layout(layout = "acyclic_dummy", save_pos = True)
14550
sage: x = {('1', 1): [41, 18], ('0', 0): [41, 90], ('1', 0): [140, 90], ('0', 1): [141, 18]}
14551
14552
{('1', 1): [41, 18], ('0', 0): [41, 90], ('1', 0): [140, 90], ('0', 1): [141, 18]}
14553
14554
14555
sage: g.layout(dim = 3)
14556
{('1', 1): [1.07..., -0.260..., 0.927...],
14557
('0', 0): [2.02..., 0.528..., 0.343...],
14558
('1', 0): [0.674..., -0.528..., -0.343...],
14559
('0', 1): [1.61..., 0.260..., -0.927...]}
14560
14561
Here is the list of all the available layout options::
14562
14563
sage: from sage.graphs.graph_plot import layout_options
14564
sage: for key, value in list(sorted(layout_options.iteritems())):
14565
... print "option", key, ":", value
14566
option by_component : Whether to do the spring layout by connected component -- a boolean.
14567
option dim : The dimension of the layout -- 2 or 3.
14568
option heights : A dictionary mapping heights to the list of vertices at this height.
14569
option iterations : The number of times to execute the spring layout algorithm.
14570
option layout : A layout algorithm -- one of : "acyclic", "circular" (plots the graph with vertices evenly distributed on a circle), "ranked", "graphviz", "planar", "spring" (traditional spring layout, using the graph's current positions as initial positions), or "tree" (the tree will be plotted in levels, depending on minimum distance for the root).
14571
option prog : Which graphviz layout program to use -- one of "circo", "dot", "fdp", "neato", or "twopi".
14572
option save_pos : Whether or not to save the computed position for the graph.
14573
option spring : Use spring layout to finalize the current layout.
14574
option tree_orientation : The direction of tree branches -- 'up', 'down', 'left' or 'right'.
14575
option tree_root : A vertex designation for drawing trees. A vertex of the tree to be used as the root for the ``layout='tree'`` option. If no root is specified, then one is chosen close to the center of the tree. Ignored unless ``layout='tree'``
14576
14577
Some of them only apply to certain layout algorithms. For
14578
details, see :meth:`.layout_acyclic`, :meth:`.layout_planar`,
14579
:meth:`.layout_circular`, :meth:`.layout_spring`, ...
14580
14581
..warning: unknown optional arguments are silently ignored
14582
14583
..warning: ``graphviz`` and ``dot2tex`` are currently required
14584
to obtain a nice 'acyclic' layout. See
14585
:meth:`.layout_graphviz` for installation instructions.
14586
14587
A subclass may implement another layout algorithm `blah`, by
14588
implementing a method ``.layout_blah``. It may override
14589
the default layout by overriding :meth:`.layout_default`, and
14590
similarly override the predefined layouts.
14591
14592
TODO: use this feature for all the predefined graphs classes
14593
(like for the Petersen graph, ...), rather than systematically
14594
building the layout at construction time.
14595
"""
14596
if layout is None:
14597
if pos is None:
14598
pos = self.get_pos(dim = dim)
14599
if pos is None:
14600
layout = 'default'
14601
14602
if hasattr(self, "layout_%s"%layout):
14603
pos = getattr(self, "layout_%s"%layout)(dim = dim, **options)
14604
elif layout is not None:
14605
raise ValueError("unknown layout algorithm: %s"%layout)
14606
14607
if len(pos) < self.order():
14608
pos = self.layout_extend_randomly(pos, dim = dim)
14609
14610
if save_pos:
14611
self.set_pos(pos, dim = dim)
14612
return pos
14613
14614
14615
def layout_spring(self, by_component = True, **options):
14616
"""
14617
Computes a spring layout for this graph
14618
14619
INPUT:
14620
14621
- ``iterations`` -- a positive integer
14622
- ``dim`` -- 2 or 3 (default: 2)
14623
14624
OUTPUT: a dictionary mapping vertices to positions
14625
14626
Returns a layout computed by randomly arranging the vertices
14627
along the given heights
14628
14629
EXAMPLES::
14630
14631
sage: g = graphs.LadderGraph(3) #TODO!!!!
14632
sage: g.layout_spring()
14633
{0: [1.28..., -0.943...],
14634
1: [1.57..., -0.101...],
14635
2: [1.83..., 0.747...],
14636
3: [0.531..., -0.757...],
14637
4: [0.795..., 0.108...],
14638
5: [1.08..., 0.946...]}
14639
sage: g = graphs.LadderGraph(7)
14640
sage: g.plot(layout = "spring")
14641
"""
14642
return spring_layout_fast(self, by_component = by_component, **options)
14643
14644
layout_default = layout_spring
14645
14646
# if not isinstance(graph.get_pos(), dict):
14647
# if graph.is_planar():
14648
# graph.set_planar_positions()
14649
# else:
14650
# import sage.graphs.generic_graph_pyx as ggp
14651
# graph.set_pos(ggp.spring_layout_fast_split(graph, iterations=1000))
14652
14653
def layout_ranked(self, heights = None, dim = 2, spring = False, **options):
14654
"""
14655
Computes a ranked layout for this graph
14656
14657
INPUT:
14658
14659
- heights -- a dictionary mapping heights to the list of vertices at this height
14660
14661
OUTPUT: a dictionary mapping vertices to positions
14662
14663
Returns a layout computed by randomly arranging the vertices
14664
along the given heights
14665
14666
EXAMPLES::
14667
14668
sage: g = graphs.LadderGraph(3)
14669
sage: g.layout_ranked(heights = dict( (i,[i, i+3]) for i in range(3) ))
14670
{0: [0.668..., 0],
14671
1: [0.667..., 1],
14672
2: [0.677..., 2],
14673
3: [1.34..., 0],
14674
4: [1.33..., 1],
14675
5: [1.33..., 2]}
14676
sage: g = graphs.LadderGraph(7)
14677
sage: g.plot(layout = "ranked", heights = dict( (i,[i, i+7]) for i in range(7) ))
14678
"""
14679
assert heights is not None
14680
14681
from sage.misc.randstate import current_randstate
14682
random = current_randstate().python_random().random
14683
14684
if self.order() == 0:
14685
return {}
14686
14687
pos = {}
14688
mmax = max([len(ccc) for ccc in heights.values()])
14689
ymin = min(heights.keys())
14690
ymax = max(heights.keys())
14691
dist = (max(ymax-ymin, 1)) / (mmax+1.0)
14692
for height in heights:
14693
num_xs = len(heights[height])
14694
if num_xs == 0:
14695
continue
14696
j = (mmax - num_xs)/2.0
14697
for k in range(num_xs):
14698
pos[heights[height][k]] = [ dist*(j+k+1) + random()*(dist*0.03) for i in range(dim-1) ] + [height]
14699
if spring:
14700
# This does not work that well in 2d, since the vertices on
14701
# the same level are unlikely to cross. It is also hard to
14702
# set a good equilibrium distance (parameter k in
14703
# run_spring). If k<1, the layout gets squished
14704
# horizontally. If k>1, then two adjacent vertices in
14705
# consecutive levels tend to be further away than desired.
14706
newpos = spring_layout_fast(self,
14707
vpos = pos,
14708
dim = dim,
14709
height = True,
14710
**options)
14711
# spring_layout_fast actually *does* touch the last coordinates
14712
# (conversion to floats + translation)
14713
# We restore back the original height.
14714
for x in self.vertices():
14715
newpos[x][dim-1] = pos[x][dim-1]
14716
pos = newpos
14717
return pos
14718
14719
def layout_extend_randomly(self, pos, dim = 2):
14720
"""
14721
Extends randomly a partial layout
14722
14723
INPUT:
14724
14725
- ``pos``: a dictionary mapping vertices to positions
14726
14727
OUTPUT: a dictionary mapping vertices to positions
14728
14729
The vertices not referenced in ``pos`` are assigned random
14730
positions within the box delimited by the other vertices.
14731
14732
EXAMPLES::
14733
14734
sage: H = digraphs.ButterflyGraph(1)
14735
sage: H.layout_extend_randomly({('0',0): (0,0), ('1',1): (1,1)})
14736
{('1', 1): (1, 1),
14737
('0', 0): (0, 0),
14738
('1', 0): [0.111..., 0.514...],
14739
('0', 1): [0.0446..., 0.332...]}
14740
"""
14741
assert dim == 2 # 3d not yet implemented
14742
from sage.misc.randstate import current_randstate
14743
random = current_randstate().python_random().random
14744
14745
xmin, xmax,ymin, ymax = self._layout_bounding_box(pos)
14746
14747
dx = xmax - xmin
14748
dy = ymax - ymin
14749
# Check each vertex position is in pos, add position
14750
# randomly within the plot range if none is defined
14751
for v in self:
14752
if not v in pos:
14753
pos[v] = [xmin + dx*random(), ymin + dy*random()]
14754
return pos
14755
14756
14757
def layout_circular(self, dim = 2, **options):
14758
"""
14759
Computes a circular layout for this graph
14760
14761
OUTPUT: a dictionary mapping vertices to positions
14762
14763
EXAMPLES::
14764
14765
sage: G = graphs.CirculantGraph(7,[1,3])
14766
sage: G.layout_circular()
14767
{0: [6.12...e-17, 1.0],
14768
1: [-0.78..., 0.62...],
14769
2: [-0.97..., -0.22...],
14770
3: [-0.43..., -0.90...],
14771
4: [0.43..., -0.90...],
14772
5: [0.97..., -0.22...],
14773
6: [0.78..., 0.62...]}
14774
sage: G.plot(layout = "circular")
14775
"""
14776
assert dim == 2, "3D circular layout not implemented"
14777
from math import sin, cos, pi
14778
verts = self.vertices()
14779
n = len(verts)
14780
pos = {}
14781
for i in range(n):
14782
x = float(cos((pi/2) + ((2*pi)/n)*i))
14783
y = float(sin((pi/2) + ((2*pi)/n)*i))
14784
pos[verts[i]] = [x,y]
14785
return pos
14786
14787
def layout_tree(self, tree_orientation = "down", tree_root = None, dim = 2, **options):
14788
r"""
14789
Computes an ordered tree layout for this graph, which should
14790
be a tree (no non-oriented cycles).
14791
14792
INPUT:
14793
14794
- ``tree_root`` -- the root vertex. By default ``None``. In
14795
this case, a vertex is chosen close to the center of the
14796
tree.
14797
14798
- ``tree_orientation`` -- the direction in which the tree is
14799
growing, can be 'up', 'down', 'left' or 'right' (default is
14800
'down')
14801
14802
OUTPUT: a dictionary mapping vertices to positions
14803
14804
EXAMPLES::
14805
14806
sage: T = graphs.RandomLobster(25, 0.3, 0.3)
14807
sage: T.show(layout='tree', tree_orientation='up')
14808
14809
sage: G = graphs.HoffmanSingletonGraph()
14810
sage: T = Graph()
14811
sage: T.add_edges(G.min_spanning_tree(starting_vertex=0))
14812
sage: T.show(layout='tree', tree_root=0)
14813
14814
sage: G = graphs.BalancedTree(2, 2)
14815
sage: G.layout_tree(tree_root = 0)
14816
{0: (1.5, 0),
14817
1: (2.5, -1),
14818
2: (0.5, -1),
14819
3: (3.0, -2),
14820
4: (2.0, -2),
14821
5: (1.0, -2),
14822
6: (0.0, -2)}
14823
14824
sage: G = graphs.BalancedTree(2,4)
14825
sage: G.plot(layout="tree", tree_root = 0, tree_orientation = "up")
14826
14827
sage: G = graphs.RandomTree(80)
14828
sage: G.plot(layout="tree", tree_orientation = "right")
14829
14830
TESTS::
14831
14832
sage: G = graphs.CycleGraph(3)
14833
sage: G.plot(layout='tree')
14834
Traceback (most recent call last):
14835
...
14836
RuntimeError: Cannot use tree layout on this graph: self.is_tree() returns False.
14837
"""
14838
if not(dim == 2):
14839
raise ValueError('only implemented in 2D')
14840
14841
from sage.graphs.all import Graph
14842
if not Graph(self).is_tree():
14843
raise RuntimeError("Cannot use tree layout on this graph: self.is_tree() returns False.")
14844
14845
n = self.order()
14846
vertices = self.vertices()
14847
14848
if tree_root is None:
14849
root = self.center()[0]
14850
else:
14851
root = tree_root
14852
14853
pos = {}
14854
14855
# The children and parent of each vertex
14856
children = {root:self.neighbors(root)}
14857
parent = {u:root for u in children[root]}
14858
14859
# stack[i] is the list of children of stick[i] which have not been given
14860
# a position yet.
14861
stack = [list(children[root])]
14862
stick = [root]
14863
14864
# obstruction[y] is the smallest value of x to which a vertex at height
14865
# y can be assigned. All vertices at height y which have already been
14866
# assigned are on the left of (x-1,y).
14867
obstruction = [0.0]*self.num_verts()
14868
14869
if tree_orientation in ['down', 'left']:
14870
o = -1
14871
elif tree_orientation in ['up', 'right']:
14872
o = 1
14873
else:
14874
raise ValueError('orientation should be "up", "down", "left" or "right"')
14875
14876
def slide(v, dx):
14877
"""
14878
shift the vertex v and its descendants to the right by dx
14879
14880
Precondition: v and its descendents have already had their
14881
positions computed.
14882
"""
14883
level = [v]
14884
while level:
14885
nextlevel = []
14886
for u in level:
14887
x, y = pos[u]
14888
x += dx
14889
obstruction[y] = max(x+1, obstruction[y])
14890
pos[u] = x, y
14891
nextlevel += children[u]
14892
14893
level = nextlevel
14894
14895
while stack:
14896
C = stack[-1]
14897
14898
# If all the children of stick[-1] have been given a position
14899
if len(C) == 0:
14900
p = stick.pop()
14901
stack.pop()
14902
cp = children[p]
14903
y = o*len(stack)
14904
14905
if len(cp) == 0:
14906
# If p has no children, we draw it at the leftmost position
14907
# which has not been forbidden
14908
x = obstruction[y]
14909
pos[p] = x, y
14910
else:
14911
# If p has children, we put v on a vertical line going
14912
# through the barycenter of its children
14913
x = sum([pos[c][0] for c in cp])/len(cp)
14914
pos[p] = x, y
14915
ox = obstruction[y]
14916
if x < ox:
14917
slide(p, ox-x)
14918
x = ox
14919
14920
# If the vertex to the right of p has not children, we want it
14921
# at distance 1 from p
14922
obstruction[y] = x+1
14923
14924
# Otherwise, we take one of the children and add it to the
14925
# stack. Note that this vertex is removed from the list C.
14926
else:
14927
t = C.pop()
14928
14929
pt = parent[t]
14930
ct = [u for u in self.neighbors(t) if u != pt]
14931
children[t] = ct
14932
14933
for c in ct:
14934
parent[c] = t
14935
14936
stack.append([c for c in ct])
14937
stick.append(t)
14938
14939
if tree_orientation in ['right', 'left']:
14940
return {p:(py,px) for p,(px,py) in pos.iteritems()}
14941
14942
return pos
14943
14944
def layout_graphviz(self, dim = 2, prog = 'dot', **options):
14945
"""
14946
Calls ``graphviz`` to compute a layout of the vertices of this graph.
14947
14948
INPUT:
14949
14950
- ``prog`` -- one of "dot", "neato", "twopi", "circo", or "fdp"
14951
14952
EXAMPLES::
14953
14954
sage: g = digraphs.ButterflyGraph(2)
14955
sage: g.layout_graphviz() # optional - dot2tex, graphviz
14956
{('...', ...): [...,...],
14957
('...', ...): [...,...],
14958
('...', ...): [...,...],
14959
('...', ...): [...,...],
14960
('...', ...): [...,...],
14961
('...', ...): [...,...],
14962
('...', ...): [...,...],
14963
('...', ...): [...,...],
14964
('...', ...): [...,...],
14965
('...', ...): [...,...],
14966
('...', ...): [...,...],
14967
('...', ...): [...,...]}
14968
sage: g.plot(layout = "graphviz") # optional - dot2tex, graphviz
14969
14970
Note: the actual coordinates are not deterministic
14971
14972
By default, an acyclic layout is computed using ``graphviz``'s
14973
``dot`` layout program. One may specify an alternative layout
14974
program::
14975
14976
sage: g.plot(layout = "graphviz", prog = "dot") # optional - dot2tex, graphviz
14977
sage: g.plot(layout = "graphviz", prog = "neato") # optional - dot2tex, graphviz
14978
sage: g.plot(layout = "graphviz", prog = "twopi") # optional - dot2tex, graphviz
14979
sage: g.plot(layout = "graphviz", prog = "fdp") # optional - dot2tex, graphviz
14980
sage: g = graphs.BalancedTree(5,2)
14981
sage: g.plot(layout = "graphviz", prog = "circo") # optional - dot2tex, graphviz
14982
14983
TODO: put here some cool examples showcasing graphviz features.
14984
14985
This requires ``graphviz`` and the ``dot2tex`` spkg. Here are
14986
some installation tips:
14987
14988
- Install graphviz >= 2.14 so that the programs dot, neato, ...
14989
are in your path. The graphviz suite can be download from
14990
http://graphviz.org.
14991
14992
- Download dot2tex-2.8.?.spkg from http://trac.sagemath.org/sage_trac/ticket/7004
14993
and install it with ``sage -i dot2tex-2.8.?.spkg``
14994
14995
TODO: use the graphviz functionality of Networkx 1.0 once it
14996
will be merged into Sage.
14997
"""
14998
assert_have_dot2tex()
14999
assert dim == 2, "3D graphviz layout not implemented"
15000
15001
key = self._keys_for_vertices()
15002
key_to_vertex = dict( (key(v), v) for v in self )
15003
15004
import dot2tex
15005
positions = dot2tex.dot2tex(self.graphviz_string(**options), format = "positions", prog = prog)
15006
15007
return dict( (key_to_vertex[key], pos) for (key, pos) in positions.iteritems() )
15008
15009
def _layout_bounding_box(self, pos):
15010
"""
15011
INPUT:
15012
15013
- pos -- a dictionary of positions
15014
15015
Returns a bounding box around the specified positions
15016
15017
EXAMPLES::
15018
15019
sage: Graph()._layout_bounding_box( {} )
15020
[-1, 1, -1, 1]
15021
sage: Graph()._layout_bounding_box( {0: [3,5], 1: [2,7], 2: [-4,2] } )
15022
[-4, 3, 2, 7]
15023
sage: Graph()._layout_bounding_box( {0: [3,5], 1: [3.00000000001,4.999999999999999] } )
15024
[2, 4.00000000001000, 4.00000000000000, 6]
15025
"""
15026
xs = [pos[v][0] for v in pos]
15027
ys = [pos[v][1] for v in pos]
15028
if len(xs) == 0:
15029
xmin = -1
15030
xmax = 1
15031
ymin = -1
15032
ymax = 1
15033
else:
15034
xmin = min(xs)
15035
xmax = max(xs)
15036
ymin = min(ys)
15037
ymax = max(ys)
15038
15039
if xmax - xmin < 0.00000001:
15040
xmax += 1
15041
xmin -= 1
15042
15043
if ymax - ymin < 0.00000001:
15044
ymax += 1
15045
ymin -= 1
15046
15047
return [xmin, xmax, ymin, ymax]
15048
15049
def graphplot(self, **options):
15050
"""
15051
Returns a GraphPlot object.
15052
15053
EXAMPLES:
15054
15055
Creating a graphplot object uses the same options as graph.plot()::
15056
15057
sage: g = Graph({}, loops=True, multiedges=True, sparse=True)
15058
sage: g.add_edges([(0,0,'a'),(0,0,'b'),(0,1,'c'),(0,1,'d'),
15059
... (0,1,'e'),(0,1,'f'),(0,1,'f'),(2,1,'g'),(2,2,'h')])
15060
sage: g.set_boundary([0,1])
15061
sage: GP = g.graphplot(edge_labels=True, color_by_label=True, edge_style='dashed')
15062
sage: GP.plot()
15063
15064
We can modify the graphplot object. Notice that the changes are cumulative::
15065
15066
sage: GP.set_edges(edge_style='solid')
15067
sage: GP.plot()
15068
sage: GP.set_vertices(talk=True)
15069
sage: GP.plot()
15070
"""
15071
from sage.graphs.graph_plot import GraphPlot
15072
return GraphPlot(graph=self, options=options)
15073
15074
@options()
15075
def plot(self, **options):
15076
r"""
15077
Returns a graphics object representing the (di)graph.
15078
15079
INPUT:
15080
15081
- ``pos`` - an optional positioning dictionary
15082
15083
- ``layout`` - what kind of layout to use, takes precedence
15084
over pos
15085
15086
- 'circular' -- plots the graph with vertices evenly
15087
distributed on a circle
15088
15089
- 'spring' - uses the traditional spring layout, using the
15090
graph's current positions as initial positions
15091
15092
- 'tree' - the (di)graph must be a tree. One can specify
15093
the root of the tree using the keyword tree_root,
15094
otherwise a root will be selected at random. Then the
15095
tree will be plotted in levels, depending on minimum
15096
distance for the root.
15097
15098
- ``vertex_labels`` - whether to print vertex labels
15099
15100
- ``edge_labels`` - whether to print edge labels. By default,
15101
False, but if True, the result of str(l) is printed on the
15102
edge for each label l. Labels equal to None are not printed
15103
(to set edge labels, see set_edge_label).
15104
15105
- ``vertex_size`` - size of vertices displayed
15106
15107
- ``vertex_shape`` - the shape to draw the vertices (Not
15108
available for multiedge digraphs.)
15109
15110
- ``graph_border`` - whether to include a box around the graph
15111
15112
- ``vertex_colors`` - optional dictionary to specify vertex
15113
colors: each key is a color recognizable by matplotlib, and
15114
each corresponding entry is a list of vertices. If a vertex
15115
is not listed, it looks invisible on the resulting plot (it
15116
doesn't get drawn).
15117
15118
- ``edge_colors`` - a dictionary specifying edge colors: each
15119
key is a color recognized by matplotlib, and each entry is a
15120
list of edges.
15121
15122
- ``partition`` - a partition of the vertex set. if specified,
15123
plot will show each cell in a different color. vertex_colors
15124
takes precedence.
15125
15126
- ``talk`` - if true, prints large vertices with white
15127
backgrounds so that labels are legible on slides
15128
15129
- ``iterations`` - how many iterations of the spring layout
15130
algorithm to go through, if applicable
15131
15132
- ``color_by_label`` - a boolean or dictionary or function (default: False)
15133
whether to color each edge with a different color according
15134
to its label; the colors are chosen along a rainbow, unless
15135
they are specified by a function or dictionary mapping
15136
labels to colors; this option is incompatible with
15137
``edge_color`` and ``edge_colors``.
15138
15139
- ``heights`` - if specified, this is a dictionary from a set
15140
of floating point heights to a set of vertices
15141
15142
- ``edge_style`` - keyword arguments passed into the
15143
edge-drawing routine. This currently only works for
15144
directed graphs, since we pass off the undirected graph to
15145
networkx
15146
15147
- ``tree_root`` - a vertex of the tree to be used as the root
15148
for the layout="tree" option. If no root is specified, then one
15149
is chosen at random. Ignored unless layout='tree'.
15150
15151
- ``tree_orientation`` - "up" or "down" (default is "down").
15152
If "up" (resp., "down"), then the root of the tree will
15153
appear on the bottom (resp., top) and the tree will grow
15154
upwards (resp. downwards). Ignored unless layout='tree'.
15155
15156
- ``save_pos`` - save position computed during plotting
15157
15158
.. NOTE::
15159
15160
- See the documentation of the :mod:`sage.graphs.graph_plot` module
15161
for information and examples of how to define parameters that will
15162
be applied to **all** graph plots.
15163
15164
- Default parameters for this method *and a specific graph* can also
15165
be set through the :class:`~sage.misc.decorators.options`
15166
mechanism. For more information on this different way to set
15167
default parameters, see the help of the :class:`options decorator
15168
<sage.misc.decorators.options>`.
15169
15170
- See also the :mod:`sage.graphs.graph_latex` module for ways to use
15171
LaTeX to produce an image of a graph.
15172
15173
EXAMPLES::
15174
15175
sage: from sage.graphs.graph_plot import graphplot_options
15176
sage: list(sorted(graphplot_options.iteritems()))
15177
[...]
15178
15179
sage: from math import sin, cos, pi
15180
sage: P = graphs.PetersenGraph()
15181
sage: d = {'#FF0000':[0,5], '#FF9900':[1,6], '#FFFF00':[2,7], '#00FF00':[3,8], '#0000FF':[4,9]}
15182
sage: pos_dict = {}
15183
sage: for i in range(5):
15184
... x = float(cos(pi/2 + ((2*pi)/5)*i))
15185
... y = float(sin(pi/2 + ((2*pi)/5)*i))
15186
... pos_dict[i] = [x,y]
15187
...
15188
sage: for i in range(10)[5:]:
15189
... x = float(0.5*cos(pi/2 + ((2*pi)/5)*i))
15190
... y = float(0.5*sin(pi/2 + ((2*pi)/5)*i))
15191
... pos_dict[i] = [x,y]
15192
...
15193
sage: pl = P.plot(pos=pos_dict, vertex_colors=d)
15194
sage: pl.show()
15195
15196
::
15197
15198
sage: C = graphs.CubeGraph(8)
15199
sage: P = C.plot(vertex_labels=False, vertex_size=0, graph_border=True)
15200
sage: P.show()
15201
15202
::
15203
15204
sage: G = graphs.HeawoodGraph()
15205
sage: for u,v,l in G.edges():
15206
... G.set_edge_label(u,v,'(' + str(u) + ',' + str(v) + ')')
15207
sage: G.plot(edge_labels=True).show()
15208
15209
::
15210
15211
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)
15212
sage: for u,v,l in D.edges():
15213
... D.set_edge_label(u,v,'(' + str(u) + ',' + str(v) + ')')
15214
sage: D.plot(edge_labels=True, layout='circular').show()
15215
15216
::
15217
15218
sage: from sage.plot.colors import rainbow
15219
sage: C = graphs.CubeGraph(5)
15220
sage: R = rainbow(5)
15221
sage: edge_colors = {}
15222
sage: for i in range(5):
15223
... edge_colors[R[i]] = []
15224
sage: for u,v,l in C.edges():
15225
... for i in range(5):
15226
... if u[i] != v[i]:
15227
... edge_colors[R[i]].append((u,v,l))
15228
sage: C.plot(vertex_labels=False, vertex_size=0, edge_colors=edge_colors).show()
15229
15230
::
15231
15232
sage: D = graphs.DodecahedralGraph()
15233
sage: Pi = [[6,5,15,14,7],[16,13,8,2,4],[12,17,9,3,1],[0,19,18,10,11]]
15234
sage: D.show(partition=Pi)
15235
15236
::
15237
15238
sage: G = graphs.PetersenGraph()
15239
sage: G.allow_loops(True)
15240
sage: G.add_edge(0,0)
15241
sage: G.show()
15242
15243
::
15244
15245
sage: D = DiGraph({0:[0,1], 1:[2], 2:[3]}, loops=True)
15246
sage: D.show()
15247
sage: D.show(edge_colors={(0,1,0):[(0,1,None),(1,2,None)],(0,0,0):[(2,3,None)]})
15248
15249
::
15250
15251
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]}
15252
sage: g = Graph({0:[1], 1:[2], 2:[3], 3:[4], 4:[0]})
15253
sage: g.plot(pos=pos, layout='spring', iterations=0)
15254
15255
::
15256
15257
sage: G = Graph()
15258
sage: P = G.plot()
15259
sage: P.axes()
15260
False
15261
sage: G = DiGraph()
15262
sage: P = G.plot()
15263
sage: P.axes()
15264
False
15265
15266
::
15267
15268
sage: G = graphs.PetersenGraph()
15269
sage: G.get_pos()
15270
{0: (6.12..., 1.0...),
15271
1: (-0.95..., 0.30...),
15272
2: (-0.58..., -0.80...),
15273
3: (0.58..., -0.80...),
15274
4: (0.95..., 0.30...),
15275
5: (1.53..., 0.5...),
15276
6: (-0.47..., 0.15...),
15277
7: (-0.29..., -0.40...),
15278
8: (0.29..., -0.40...),
15279
9: (0.47..., 0.15...)}
15280
sage: P = G.plot(save_pos=True, layout='spring')
15281
15282
The following illustrates the format of a position dictionary.
15283
15284
sage: G.get_pos() # currently random across platforms, see #9593
15285
{0: [1.17..., -0.855...],
15286
1: [1.81..., -0.0990...],
15287
2: [1.35..., 0.184...],
15288
3: [1.51..., 0.644...],
15289
4: [2.00..., -0.507...],
15290
5: [0.597..., -0.236...],
15291
6: [2.04..., 0.687...],
15292
7: [1.46..., -0.473...],
15293
8: [0.902..., 0.773...],
15294
9: [2.48..., -0.119...]}
15295
15296
::
15297
15298
sage: T = list(graphs.trees(7))
15299
sage: t = T[3]
15300
sage: t.plot(heights={0:[0], 1:[4,5,1], 2:[2], 3:[3,6]})
15301
15302
::
15303
15304
sage: T = list(graphs.trees(7))
15305
sage: t = T[3]
15306
sage: t.plot(heights={0:[0], 1:[4,5,1], 2:[2], 3:[3,6]})
15307
sage: t.set_edge_label(0,1,-7)
15308
sage: t.set_edge_label(0,5,3)
15309
sage: t.set_edge_label(0,5,99)
15310
sage: t.set_edge_label(1,2,1000)
15311
sage: t.set_edge_label(3,2,'spam')
15312
sage: t.set_edge_label(2,6,3/2)
15313
sage: t.set_edge_label(0,4,66)
15314
sage: t.plot(heights={0:[0], 1:[4,5,1], 2:[2], 3:[3,6]}, edge_labels=True)
15315
15316
::
15317
15318
sage: T = list(graphs.trees(7))
15319
sage: t = T[3]
15320
sage: t.plot(layout='tree')
15321
15322
::
15323
15324
sage: t = DiGraph('JCC???@A??GO??CO??GO??')
15325
sage: t.plot(layout='tree', tree_root=0, tree_orientation="up")
15326
sage: D = DiGraph({0:[1,2,3], 2:[1,4], 3:[0]})
15327
sage: D.plot()
15328
15329
sage: D = DiGraph(multiedges=True,sparse=True)
15330
sage: for i in range(5):
15331
... D.add_edge((i,i+1,'a'))
15332
... D.add_edge((i,i-1,'b'))
15333
sage: D.plot(edge_labels=True,edge_colors=D._color_by_label())
15334
sage: D.plot(edge_labels=True, color_by_label={'a':'blue', 'b':'red'}, edge_style='dashed')
15335
15336
sage: g = Graph({}, loops=True, multiedges=True,sparse=True)
15337
sage: g.add_edges([(0,0,'a'),(0,0,'b'),(0,1,'c'),(0,1,'d'),
15338
... (0,1,'e'),(0,1,'f'),(0,1,'f'),(2,1,'g'),(2,2,'h')])
15339
sage: g.plot(edge_labels=True, color_by_label=True, edge_style='dashed')
15340
15341
::
15342
15343
sage: S = SupersingularModule(389)
15344
sage: H = S.hecke_matrix(2)
15345
sage: D = DiGraph(H,sparse=True)
15346
sage: P = D.plot()
15347
15348
::
15349
15350
sage: G=Graph({'a':['a','b','b','b','e'],'b':['c','d','e'],'c':['c','d','d','d'],'d':['e']},sparse=True)
15351
sage: G.show(pos={'a':[0,1],'b':[1,1],'c':[2,0],'d':[1,0],'e':[0,0]})
15352
15353
TESTS::
15354
15355
sage: G = DiGraph({0:{1:'a', 2:'a'}, 1:{0:'b'}, 2:{0:'c'}})
15356
sage: p = G.plot(edge_labels=True, color_by_label={'a':'yellow', 'b':'purple'}); p
15357
sage: sorted([x.options()['rgbcolor'] for x in p if isinstance(x, sage.plot.arrow.CurveArrow)])
15358
['black', 'purple', 'yellow', 'yellow']
15359
"""
15360
return self.graphplot(**options).plot()
15361
15362
def show(self, **kwds):
15363
"""
15364
Shows the (di)graph.
15365
15366
INPUT:
15367
15368
This method accepts any option understood by
15369
:meth:`~sage.graphs.generic_graph.GenericGraph.plot` (graph-specific) or
15370
by :meth:`sage.plot.graphics.Graphics.show`.
15371
15372
.. NOTE::
15373
15374
See the documentation of the :mod:`sage.graphs.graph_plot` module
15375
for information on default arguments of this method.
15376
15377
EXAMPLES::
15378
15379
sage: C = graphs.CubeGraph(8)
15380
sage: P = C.plot(vertex_labels=False, vertex_size=0, graph_border=True)
15381
sage: P.show() # long time (3s on sage.math, 2011)
15382
"""
15383
from graph_plot import graphplot_options
15384
15385
# This dictionary only contains the options that graphplot
15386
# understands. These options are removed from kwds at the same
15387
# time.
15388
plot_kwds = {k:kwds.pop(k) for k in graphplot_options if k in kwds}
15389
15390
return self.graphplot(**plot_kwds).show(**kwds)
15391
15392
def plot3d(self, bgcolor=(1,1,1),
15393
vertex_colors=None, vertex_size=0.06, vertex_labels=False,
15394
edge_colors=None, edge_size=0.02, edge_size2=0.0325,
15395
pos3d=None, color_by_label=False,
15396
engine='jmol', **kwds):
15397
r"""
15398
Plot a graph in three dimensions.
15399
15400
See also the :mod:`sage.graphs.graph_latex` module for ways to use LaTeX
15401
to produce an image of a graph.
15402
15403
INPUT:
15404
15405
- ``bgcolor`` - rgb tuple (default: (1,1,1))
15406
15407
- ``vertex_size`` - float (default: 0.06)
15408
15409
- ``vertex_labels`` -- a boolean (default: False)
15410
whether to display vertices using text labels instead of spheres
15411
15412
- ``vertex_colors`` - optional dictionary to specify
15413
vertex colors: each key is a color recognizable by tachyon (rgb
15414
tuple (default: (1,0,0))), and each corresponding entry is a list
15415
of vertices. If a vertex is not listed, it looks invisible on the
15416
resulting plot (it doesn't get drawn).
15417
15418
- ``edge_colors`` - a dictionary specifying edge
15419
colors: each key is a color recognized by tachyon ( default:
15420
(0,0,0) ), and each entry is a list of edges.
15421
15422
- ``color_by_label`` - a boolean or dictionary or function (default: False)
15423
whether to color each edge with a different color according
15424
to its label; the colors are chosen along a rainbow, unless
15425
they are specified by a function or dictionary mapping
15426
labels to colors; this option is incompatible with
15427
``edge_color`` and ``edge_colors``.
15428
15429
- ``edge_size`` - float (default: 0.02)
15430
15431
- ``edge_size2`` - float (default: 0.0325), used for
15432
Tachyon sleeves
15433
15434
- ``pos3d`` - a position dictionary for the vertices
15435
15436
- ``layout``, ``iterations``, ... - layout options; see :meth:`.layout`
15437
15438
- ``engine`` - which renderer to use. Options:
15439
15440
- ``'jmol'`` - default
15441
15442
- ``'tachyon'``
15443
15444
- ``xres`` - resolution
15445
15446
- ``yres`` - resolution
15447
15448
- ``**kwds`` - passed on to the rendering engine
15449
15450
15451
EXAMPLES::
15452
15453
sage: G = graphs.CubeGraph(5)
15454
sage: G.plot3d(iterations=500, edge_size=None, vertex_size=0.04) # long time
15455
15456
We plot a fairly complicated Cayley graph::
15457
15458
sage: A5 = AlternatingGroup(5); A5
15459
Alternating group of order 5!/2 as a permutation group
15460
sage: G = A5.cayley_graph()
15461
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
15462
15463
Some Tachyon examples::
15464
15465
sage: D = graphs.DodecahedralGraph()
15466
sage: P3D = D.plot3d(engine='tachyon')
15467
sage: P3D.show() # long time
15468
15469
::
15470
15471
sage: G = graphs.PetersenGraph()
15472
sage: G.plot3d(engine='tachyon', vertex_colors={(0,0,1):G.vertices()}).show() # long time
15473
15474
::
15475
15476
sage: C = graphs.CubeGraph(4)
15477
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
15478
15479
::
15480
15481
sage: K = graphs.CompleteGraph(3)
15482
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
15483
15484
A directed version of the dodecahedron
15485
15486
::
15487
15488
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: []} )
15489
sage: D.plot3d().show() # long time
15490
15491
::
15492
15493
sage: P = graphs.PetersenGraph().to_directed()
15494
sage: from sage.plot.colors import rainbow
15495
sage: edges = P.edges()
15496
sage: R = rainbow(len(edges), 'rgbtuple')
15497
sage: edge_colors = {}
15498
sage: for i in range(len(edges)):
15499
... edge_colors[R[i]] = [edges[i]]
15500
sage: P.plot3d(engine='tachyon', edge_colors=edge_colors).show() # long time
15501
15502
15503
::
15504
15505
sage: G=Graph({'a':['a','b','b','b','e'],'b':['c','d','e'],'c':['c','d','d','d'],'d':['e']},sparse=True)
15506
sage: G.show3d()
15507
Traceback (most recent call last):
15508
...
15509
NotImplementedError: 3D plotting of multiple edges or loops not implemented.
15510
15511
TESTS::
15512
15513
sage: G = DiGraph({0:{1:'a', 2:'a'}, 1:{0:'b'}, 2:{0:'c'}})
15514
sage: p = G.plot3d(edge_labels=True, color_by_label={'a':'yellow', 'b':'cyan'})
15515
sage: s = p.x3d_str()
15516
15517
This 3D plot contains four yellow objects (two cylinders and
15518
two cones), two black objects and 2 cyan objects::
15519
15520
sage: s.count("Material diffuseColor='1.0 1.0 0.0'")
15521
4
15522
sage: s.count("Material diffuseColor='0.0 0.0 0.0'")
15523
2
15524
sage: s.count("Material diffuseColor='0.0 1.0 1.0'")
15525
2
15526
15527
.. SEEALSO::
15528
15529
- :meth:`plot`
15530
- :meth:`graphviz_string`
15531
"""
15532
import graph_plot
15533
layout_options = dict( (key,kwds[key]) for key in kwds.keys() if key in graph_plot.layout_options )
15534
kwds = dict( (key,kwds[key]) for key in kwds.keys() if key not in graph_plot.layout_options )
15535
if pos3d is None:
15536
pos3d = self.layout(dim=3, **layout_options)
15537
15538
if self.has_multiple_edges() or self.has_loops():
15539
raise NotImplementedError("3D plotting of multiple edges or loops not implemented.")
15540
if engine == 'jmol':
15541
from sage.plot.plot3d.all import sphere, line3d, arrow3d, text3d
15542
from sage.plot.plot3d.texture import Texture
15543
kwds.setdefault('aspect_ratio', [1,1,1])
15544
verts = self.vertices()
15545
15546
if vertex_colors is None:
15547
vertex_colors = { (1,0,0) : verts }
15548
15549
if color_by_label:
15550
if edge_colors is None:
15551
# do the coloring
15552
edge_colors = self._color_by_label(format=color_by_label)
15553
elif edge_colors is None:
15554
edge_colors = { (0,0,0) : self.edges() }
15555
15556
# by default turn off the frame
15557
if not kwds.has_key('frame'):
15558
kwds['frame'] = False
15559
# by default make the background given by bgcolor
15560
if not kwds.has_key('background'):
15561
kwds['background'] = bgcolor
15562
try:
15563
graphic = 0
15564
for color in vertex_colors:
15565
texture = Texture(color=color, ambient=0.1, diffuse=0.9, specular=0.03)
15566
for v in vertex_colors[color]:
15567
if vertex_labels:
15568
graphic += text3d(repr(v), pos3d[v])
15569
else:
15570
graphic += sphere(center=pos3d[v], size=vertex_size, texture=texture, **kwds)
15571
if self._directed:
15572
for color in edge_colors:
15573
for u, v, l in edge_colors[color]:
15574
graphic += arrow3d(pos3d[u], pos3d[v], radius=edge_size, color=color, closed=False, **kwds)
15575
15576
else:
15577
for color in edge_colors:
15578
texture = Texture(color=color, ambient=0.1, diffuse=0.9, specular=0.03)
15579
for u, v, l in edge_colors[color]:
15580
graphic += line3d([pos3d[u], pos3d[v]], radius=edge_size, texture=texture, closed=False, **kwds)
15581
15582
return graphic
15583
15584
except KeyError:
15585
raise KeyError("Oops! You haven't specified positions for all the vertices.")
15586
15587
elif engine == 'tachyon':
15588
TT, pos3d = tachyon_vertex_plot(self, bgcolor=bgcolor, vertex_colors=vertex_colors,
15589
vertex_size=vertex_size, pos3d=pos3d, **kwds)
15590
edges = self.edges()
15591
15592
if color_by_label:
15593
if edge_colors is None:
15594
# do the coloring
15595
edge_colors = self._color_by_label(format=color_by_label)
15596
15597
if edge_colors is None:
15598
edge_colors = { (0,0,0) : edges }
15599
15600
i = 0
15601
15602
for color in edge_colors:
15603
i += 1
15604
TT.texture('edge_color_%d'%i, ambient=0.1, diffuse=0.9, specular=0.03, opacity=1.0, color=color)
15605
if self._directed:
15606
for u,v,l in edge_colors[color]:
15607
TT.fcylinder( (pos3d[u][0],pos3d[u][1],pos3d[u][2]),
15608
(pos3d[v][0],pos3d[v][1],pos3d[v][2]), edge_size,'edge_color_%d'%i)
15609
TT.fcylinder( (0.25*pos3d[u][0] + 0.75*pos3d[v][0],
15610
0.25*pos3d[u][1] + 0.75*pos3d[v][1],
15611
0.25*pos3d[u][2] + 0.75*pos3d[v][2],),
15612
(pos3d[v][0],pos3d[v][1],pos3d[v][2]), edge_size2,'edge_color_%d'%i)
15613
else:
15614
for u, v, l in edge_colors[color]:
15615
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)
15616
15617
return TT
15618
15619
else:
15620
raise TypeError("Rendering engine (%s) not implemented."%engine)
15621
15622
def show3d(self, bgcolor=(1,1,1), vertex_colors=None, vertex_size=0.06,
15623
edge_colors=None, edge_size=0.02, edge_size2=0.0325,
15624
pos3d=None, color_by_label=False,
15625
engine='jmol', **kwds):
15626
"""
15627
Plots the graph using Tachyon, and shows the resulting plot.
15628
15629
INPUT:
15630
15631
15632
- ``bgcolor`` - rgb tuple (default: (1,1,1))
15633
15634
- ``vertex_size`` - float (default: 0.06)
15635
15636
- ``vertex_colors`` - optional dictionary to specify
15637
vertex colors: each key is a color recognizable by tachyon (rgb
15638
tuple (default: (1,0,0))), and each corresponding entry is a list
15639
of vertices. If a vertex is not listed, it looks invisible on the
15640
resulting plot (it doesn't get drawn).
15641
15642
- ``edge_colors`` - a dictionary specifying edge
15643
colors: each key is a color recognized by tachyon ( default:
15644
(0,0,0) ), and each entry is a list of edges.
15645
15646
- ``edge_size`` - float (default: 0.02)
15647
15648
- ``edge_size2`` - float (default: 0.0325), used for
15649
Tachyon sleeves
15650
15651
- ``pos3d`` - a position dictionary for the vertices
15652
15653
- ``iterations`` - how many iterations of the spring
15654
layout algorithm to go through, if applicable
15655
15656
- ``engine`` - which renderer to use. Options:
15657
15658
- ``'jmol'`` - default 'tachyon'
15659
15660
- ``xres`` - resolution
15661
15662
- ``yres`` - resolution
15663
15664
- ``**kwds`` - passed on to the Tachyon command
15665
15666
15667
EXAMPLES::
15668
15669
sage: G = graphs.CubeGraph(5)
15670
sage: G.show3d(iterations=500, edge_size=None, vertex_size=0.04) # long time
15671
15672
We plot a fairly complicated Cayley graph::
15673
15674
sage: A5 = AlternatingGroup(5); A5
15675
Alternating group of order 5!/2 as a permutation group
15676
sage: G = A5.cayley_graph()
15677
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
15678
15679
Some Tachyon examples::
15680
15681
sage: D = graphs.DodecahedralGraph()
15682
sage: D.show3d(engine='tachyon') # long time
15683
15684
::
15685
15686
sage: G = graphs.PetersenGraph()
15687
sage: G.show3d(engine='tachyon', vertex_colors={(0,0,1):G.vertices()}) # long time
15688
15689
::
15690
15691
sage: C = graphs.CubeGraph(4)
15692
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
15693
15694
::
15695
15696
sage: K = graphs.CompleteGraph(3)
15697
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
15698
"""
15699
self.plot3d(bgcolor=bgcolor, vertex_colors=vertex_colors,
15700
edge_colors=edge_colors, vertex_size=vertex_size, engine=engine,
15701
edge_size=edge_size, edge_size2=edge_size2, pos3d=pos3d,
15702
color_by_label=color_by_label, **kwds).show()
15703
15704
def _keys_for_vertices(self):
15705
"""
15706
Returns a function mapping each vertex to a unique and hopefully
15707
readable string
15708
15709
EXAMPLE::
15710
15711
sage: g = graphs.Grid2dGraph(5,5)
15712
sage: g._keys_for_vertices()
15713
<function key at ...
15714
"""
15715
from sage.graphs.dot2tex_utils import key, key_with_hash
15716
if len(set(key(v) for v in self)) < self.num_verts():
15717
# There was a collision in the keys; we include a hash to be safe.
15718
return key_with_hash
15719
else:
15720
return key
15721
15722
### String representation to be used by other programs
15723
15724
@options(labels="string",
15725
vertex_labels=True,edge_labels=False,
15726
edge_color=None,edge_colors=None,
15727
edge_options = (),
15728
color_by_label=False)
15729
def graphviz_string(self, **options):
15730
r"""
15731
Returns a representation in the dot language.
15732
15733
The dot language is a text based format for graphs. It is used
15734
by the software suite graphviz. The specifications of the
15735
language are available on the web (see the reference [dotspec]_).
15736
15737
INPUT:
15738
15739
- ``labels`` - "string" or "latex" (default: "string"). If labels is
15740
string latex command are not interpreted. This option stands for both
15741
vertex labels and edge labels.
15742
15743
- ``vertex_labels`` - boolean (default: True) whether to add the labels
15744
on vertices.
15745
15746
- ``edge_labels`` - boolean (default: False) whether to add
15747
the labels on edges.
15748
15749
- ``edge_color`` - (default: None) specify a default color for the
15750
edges.
15751
15752
- ``edge_colors`` - (default: None) a dictionary whose keys
15753
are colors and values are list of edges. The list of edges need not to
15754
be complete in which case the default color is used.
15755
15756
- ``color_by_label`` - a boolean or dictionary or function (default: False)
15757
whether to color each edge with a different color according
15758
to its label; the colors are chosen along a rainbow, unless
15759
they are specified by a function or dictionary mapping
15760
labels to colors; this option is incompatible with
15761
``edge_color`` and ``edge_colors``.
15762
15763
- ``edge_options`` - a function (or tuple thereof) mapping
15764
edges to a dictionary of options for this edge.
15765
15766
EXAMPLES::
15767
15768
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)
15769
sage: print G.graphviz_string(edge_labels=True)
15770
graph {
15771
"0" [label="0"];
15772
"1" [label="1"];
15773
"2" [label="2"];
15774
"3" [label="3"];
15775
<BLANKLINE>
15776
"0" -- "1";
15777
"0" -- "2";
15778
"1" -- "2";
15779
"2" -- "3" [label="foo"];
15780
}
15781
15782
A variant, with the labels in latex, for post-processing with ``dot2tex``::
15783
15784
sage: print G.graphviz_string(edge_labels=True,labels = "latex")
15785
graph {
15786
node [shape="plaintext"];
15787
"0" [label=" ", texlbl="$0$"];
15788
"1" [label=" ", texlbl="$1$"];
15789
"2" [label=" ", texlbl="$2$"];
15790
"3" [label=" ", texlbl="$3$"];
15791
<BLANKLINE>
15792
"0" -- "1";
15793
"0" -- "2";
15794
"1" -- "2";
15795
"2" -- "3" [label=" ", texlbl="$\text{\texttt{foo}}$"];
15796
}
15797
15798
Same, with a digraph and a color for edges::
15799
15800
sage: G = DiGraph({0:{1:None,2:None}, 1:{2:None}, 2:{3:'foo'}, 3:{}} ,sparse=True)
15801
sage: print G.graphviz_string(edge_color="red")
15802
digraph {
15803
"0" [label="0"];
15804
"1" [label="1"];
15805
"2" [label="2"];
15806
"3" [label="3"];
15807
<BLANKLINE>
15808
edge [color="red"];
15809
"0" -> "1";
15810
"0" -> "2";
15811
"1" -> "2";
15812
"2" -> "3";
15813
}
15814
15815
A digraph using latex labels for vertices and edges::
15816
15817
sage: f(x) = -1/x
15818
sage: g(x) = 1/(x+1)
15819
sage: G = DiGraph()
15820
sage: G.add_edges([(i,f(i),f) for i in (1,2,1/2,1/4)])
15821
sage: G.add_edges([(i,g(i),g) for i in (1,2,1/2,1/4)])
15822
sage: print G.graphviz_string(labels="latex",edge_labels=True)
15823
digraph {
15824
node [shape="plaintext"];
15825
"2/3" [label=" ", texlbl="$\frac{2}{3}$"];
15826
"1/3" [label=" ", texlbl="$\frac{1}{3}$"];
15827
"1/2" [label=" ", texlbl="$\frac{1}{2}$"];
15828
"1" [label=" ", texlbl="$1$"];
15829
"1/4" [label=" ", texlbl="$\frac{1}{4}$"];
15830
"4/5" [label=" ", texlbl="$\frac{4}{5}$"];
15831
"-4" [label=" ", texlbl="$-4$"];
15832
"2" [label=" ", texlbl="$2$"];
15833
"-2" [label=" ", texlbl="$-2$"];
15834
"-1/2" [label=" ", texlbl="$-\frac{1}{2}$"];
15835
"-1" [label=" ", texlbl="$-1$"];
15836
<BLANKLINE>
15837
"1/2" -> "-2" [label=" ", texlbl="$x \ {\mapsto}\ -\frac{1}{x}$"];
15838
"1/2" -> "2/3" [label=" ", texlbl="$x \ {\mapsto}\ \frac{1}{x + 1}$"];
15839
"1" -> "-1" [label=" ", texlbl="$x \ {\mapsto}\ -\frac{1}{x}$"];
15840
"1" -> "1/2" [label=" ", texlbl="$x \ {\mapsto}\ \frac{1}{x + 1}$"];
15841
"1/4" -> "-4" [label=" ", texlbl="$x \ {\mapsto}\ -\frac{1}{x}$"];
15842
"1/4" -> "4/5" [label=" ", texlbl="$x \ {\mapsto}\ \frac{1}{x + 1}$"];
15843
"2" -> "-1/2" [label=" ", texlbl="$x \ {\mapsto}\ -\frac{1}{x}$"];
15844
"2" -> "1/3" [label=" ", texlbl="$x \ {\mapsto}\ \frac{1}{x + 1}$"];
15845
}
15846
15847
sage: print G.graphviz_string(labels="latex",color_by_label=True)
15848
digraph {
15849
node [shape="plaintext"];
15850
"2/3" [label=" ", texlbl="$\frac{2}{3}$"];
15851
"1/3" [label=" ", texlbl="$\frac{1}{3}$"];
15852
"1/2" [label=" ", texlbl="$\frac{1}{2}$"];
15853
"1" [label=" ", texlbl="$1$"];
15854
"1/4" [label=" ", texlbl="$\frac{1}{4}$"];
15855
"4/5" [label=" ", texlbl="$\frac{4}{5}$"];
15856
"-4" [label=" ", texlbl="$-4$"];
15857
"2" [label=" ", texlbl="$2$"];
15858
"-2" [label=" ", texlbl="$-2$"];
15859
"-1/2" [label=" ", texlbl="$-\frac{1}{2}$"];
15860
"-1" [label=" ", texlbl="$-1$"];
15861
<BLANKLINE>
15862
"1/2" -> "-2" [color = "#ff0000"];
15863
"1/2" -> "2/3" [color = "#00ffff"];
15864
"1" -> "-1" [color = "#ff0000"];
15865
"1" -> "1/2" [color = "#00ffff"];
15866
"1/4" -> "-4" [color = "#ff0000"];
15867
"1/4" -> "4/5" [color = "#00ffff"];
15868
"2" -> "-1/2" [color = "#ff0000"];
15869
"2" -> "1/3" [color = "#00ffff"];
15870
}
15871
15872
sage: print G.graphviz_string(labels="latex",color_by_label={ f: "red", g: "blue" })
15873
digraph {
15874
node [shape="plaintext"];
15875
"2/3" [label=" ", texlbl="$\frac{2}{3}$"];
15876
"1/3" [label=" ", texlbl="$\frac{1}{3}$"];
15877
"1/2" [label=" ", texlbl="$\frac{1}{2}$"];
15878
"1" [label=" ", texlbl="$1$"];
15879
"1/4" [label=" ", texlbl="$\frac{1}{4}$"];
15880
"4/5" [label=" ", texlbl="$\frac{4}{5}$"];
15881
"-4" [label=" ", texlbl="$-4$"];
15882
"2" [label=" ", texlbl="$2$"];
15883
"-2" [label=" ", texlbl="$-2$"];
15884
"-1/2" [label=" ", texlbl="$-\frac{1}{2}$"];
15885
"-1" [label=" ", texlbl="$-1$"];
15886
<BLANKLINE>
15887
"1/2" -> "-2" [color = "red"];
15888
"1/2" -> "2/3" [color = "blue"];
15889
"1" -> "-1" [color = "red"];
15890
"1" -> "1/2" [color = "blue"];
15891
"1/4" -> "-4" [color = "red"];
15892
"1/4" -> "4/5" [color = "blue"];
15893
"2" -> "-1/2" [color = "red"];
15894
"2" -> "1/3" [color = "blue"];
15895
}
15896
15897
Edge-specific options can also be specified by providing a
15898
function (or tuple thereof) which maps each edge to a
15899
dictionary of options. Valid options are "color", "backward"
15900
(a boolean), "dot" (a string containing a sequence of options
15901
in dot format), "label" (a string), "label_style" ("string" or
15902
"latex"), "edge_string" ("--" or "->"). Here we state that the
15903
graph should be laid out so that edges starting from ``1`` are
15904
going backward (e.g. going up instead of down)::
15905
15906
sage: def edge_options((u,v,label)):
15907
... return { "backward": u == 1 }
15908
sage: print G.graphviz_string(edge_options = edge_options)
15909
digraph {
15910
"2/3" [label="2/3"];
15911
"1/3" [label="1/3"];
15912
"1/2" [label="1/2"];
15913
"1" [label="1"];
15914
"1/4" [label="1/4"];
15915
"4/5" [label="4/5"];
15916
"-4" [label="-4"];
15917
"2" [label="2"];
15918
"-2" [label="-2"];
15919
"-1/2" [label="-1/2"];
15920
"-1" [label="-1"];
15921
<BLANKLINE>
15922
"1/2" -> "-2";
15923
"1/2" -> "2/3";
15924
"-1" -> "1" [dir=back];
15925
"1/2" -> "1" [dir=back];
15926
"1/4" -> "-4";
15927
"1/4" -> "4/5";
15928
"2" -> "-1/2";
15929
"2" -> "1/3";
15930
}
15931
15932
We now test all options::
15933
15934
sage: def edge_options((u,v,label)):
15935
... options = { "color": { f: "red", g: "blue" }[label] }
15936
... if (u,v) == (1/2, -2): options["label"] = "coucou"; options["label_style"] = "string"
15937
... if (u,v) == (1/2,2/3): options["dot"] = "x=1,y=2"
15938
... if (u,v) == (1, -1): options["label_style"] = "latex"
15939
... if (u,v) == (1, 1/2): options["edge_string"] = "<-"
15940
... if (u,v) == (1/2, 1): options["backward"] = True
15941
... return options
15942
sage: print G.graphviz_string(edge_options = edge_options)
15943
digraph {
15944
"2/3" [label="2/3"];
15945
"1/3" [label="1/3"];
15946
"1/2" [label="1/2"];
15947
"1" [label="1"];
15948
"1/4" [label="1/4"];
15949
"4/5" [label="4/5"];
15950
"-4" [label="-4"];
15951
"2" [label="2"];
15952
"-2" [label="-2"];
15953
"-1/2" [label="-1/2"];
15954
"-1" [label="-1"];
15955
<BLANKLINE>
15956
"1/2" -> "-2" [label="coucou", color = "red"];
15957
"1/2" -> "2/3" [x=1,y=2, color = "blue"];
15958
"1" -> "-1" [label=" ", texlbl="$x \ {\mapsto}\ -\frac{1}{x}$", color = "red"];
15959
"1" <- "1/2" [color = "blue"];
15960
"1/4" -> "-4" [color = "red"];
15961
"1/4" -> "4/5" [color = "blue"];
15962
"2" -> "-1/2" [color = "red"];
15963
"2" -> "1/3" [color = "blue"];
15964
}
15965
15966
TESTS:
15967
15968
The following digraph has tuples as vertices::
15969
15970
sage: print digraphs.ButterflyGraph(1).graphviz_string()
15971
digraph {
15972
"1,1" [label="('1', 1)"];
15973
"0,0" [label="('0', 0)"];
15974
"1,0" [label="('1', 0)"];
15975
"0,1" [label="('0', 1)"];
15976
<BLANKLINE>
15977
"0,0" -> "1,1";
15978
"0,0" -> "0,1";
15979
"1,0" -> "1,1";
15980
"1,0" -> "0,1";
15981
}
15982
15983
The following digraph has vertices with newlines in their
15984
string representations::
15985
15986
sage: m1 = matrix(3,3)
15987
sage: m2 = matrix(3,3, 1)
15988
sage: m1.set_immutable()
15989
sage: m2.set_immutable()
15990
sage: g = DiGraph({ m1: [m2] })
15991
sage: print g.graphviz_string()
15992
digraph {
15993
"000000000" [label="[0 0 0]\n\
15994
[0 0 0]\n\
15995
[0 0 0]"];
15996
"100010001" [label="[1 0 0]\n\
15997
[0 1 0]\n\
15998
[0 0 1]"];
15999
<BLANKLINE>
16000
"000000000" -> "100010001";
16001
}
16002
16003
REFERENCES:
16004
16005
.. [dotspec] http://www.graphviz.org/doc/info/lang.html
16006
16007
"""
16008
from sage.graphs.dot2tex_utils import quoted_latex, quoted_str
16009
16010
if self.is_directed():
16011
graph_string = "digraph"
16012
default_edge_string = "->"
16013
else:
16014
graph_string = "graph"
16015
default_edge_string = "--"
16016
16017
edge_option_functions = options['edge_options']
16018
if not isinstance(edge_option_functions, (tuple,list)):
16019
edge_option_functions = [edge_option_functions]
16020
else:
16021
edge_option_functions = list(edge_option_functions)
16022
16023
if options['edge_color'] is not None:
16024
default_color = options['edge_color']
16025
else:
16026
default_color = None
16027
16028
if options['color_by_label'] is not False:
16029
color_by_label = self._color_by_label(format = options['color_by_label'], as_function = True, default_color=default_color)
16030
edge_option_functions.append(lambda (u,v,label): {"color": color_by_label(label)})
16031
elif options['edge_colors'] is not None:
16032
if not isinstance(options['edge_colors'],dict):
16033
raise ValueError("incorrect format for edge_colors")
16034
color_by_edge = {}
16035
for color in options['edge_colors'].keys():
16036
for edge in options['edge_colors'][color]:
16037
assert isinstance(edge, tuple) and len(edge) >= 2 and len(edge) <= 3,\
16038
"%s is not a valid format for edge"%(edge)
16039
u = edge[0]
16040
v = edge[1]
16041
assert self.has_edge(*edge), "%s is not an edge"%(edge)
16042
if len(edge) == 2:
16043
if self.has_multiple_edges():
16044
for label in self.edge_label(u,v):
16045
color_by_edge[(u,v,label)] = color
16046
else:
16047
label = self.edge_label(u,v)
16048
color_by_edge[(u,v,label)] = color
16049
elif len(edge) == 3:
16050
color_by_edge[edge] = color
16051
16052
edge_option_functions.append(lambda edge: {"color": color_by_edge[edge]} if edge in color_by_edge else {})
16053
16054
else:
16055
edges_by_color = []
16056
not_colored_edges = self.edge_iterator(labels=True)
16057
16058
key = self._keys_for_vertices()
16059
16060
s = '%s {\n' % graph_string
16061
if (options['vertex_labels'] and
16062
options['labels'] == "latex"): # not a perfect option name
16063
# TODO: why do we set this only for latex labels?
16064
s += ' node [shape="plaintext"];\n'
16065
for v in self.vertex_iterator():
16066
if not options['vertex_labels']:
16067
node_options = ""
16068
elif options['labels'] == "latex":
16069
node_options = " [label=\" \", texlbl=\"$%s$\"]"%quoted_latex(v)
16070
else:
16071
node_options = " [label=\"%s\"]" %quoted_str(v)
16072
16073
s += ' "%s"%s;\n'%(key(v),node_options)
16074
16075
s += "\n"
16076
if default_color is not None:
16077
s += 'edge [color="%s"];\n'%default_color
16078
16079
for (u,v,label) in self.edge_iterator():
16080
edge_options = {
16081
'backward': False,
16082
'dot': None,
16083
'edge_string': default_edge_string,
16084
'color' : default_color,
16085
'label' : label,
16086
'label_style': options['labels'] if options['edge_labels'] else None
16087
}
16088
for f in edge_option_functions:
16089
edge_options.update(f((u,v,label)))
16090
16091
dot_options = []
16092
16093
if edge_options['dot'] is not None:
16094
assert isinstance(edge_options['dot'], str)
16095
dot_options.append(edge_options['dot'])
16096
16097
label = edge_options['label']
16098
if label is not None and edge_options['label_style'] is not None:
16099
if edge_options['label_style'] == 'latex':
16100
dot_options.append('label=" ", texlbl="$%s$"'%quoted_latex(label))
16101
else:
16102
dot_options.append('label="%s"'% label)
16103
16104
if edge_options['color'] != default_color:
16105
dot_options.append('color = "%s"'%edge_options['color'])
16106
16107
if edge_options['backward']:
16108
v,u = u,v
16109
dot_options.append('dir=back')
16110
16111
s+= ' "%s" %s "%s"' % (key(u), edge_options['edge_string'], key(v))
16112
if len(dot_options) > 0:
16113
s += " [" + ", ".join(dot_options)+"]"
16114
s+= ";\n"
16115
s += "}"
16116
16117
return s
16118
16119
def graphviz_to_file_named(self, filename, **options):
16120
r"""
16121
Write a representation in the dot in a file.
16122
16123
The dot language is a plaintext format for graph structures. See the
16124
documentation of :meth:`.graphviz_string` for available options.
16125
16126
INPUT:
16127
16128
``filename`` - the name of the file to write in
16129
16130
``options`` - options for the graphviz string
16131
16132
EXAMPLES::
16133
16134
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)
16135
sage: tempfile = os.path.join(SAGE_TMP, 'temp_graphviz')
16136
sage: G.graphviz_to_file_named(tempfile, edge_labels=True)
16137
sage: print open(tempfile).read()
16138
graph {
16139
"0" [label="0"];
16140
"1" [label="1"];
16141
"2" [label="2"];
16142
"3" [label="3"];
16143
<BLANKLINE>
16144
"0" -- "1";
16145
"0" -- "2";
16146
"1" -- "2";
16147
"2" -- "3" [label="foo"];
16148
}
16149
"""
16150
return open(filename, 'wt').write(self.graphviz_string(**options))
16151
16152
### Spectrum
16153
16154
def spectrum(self, laplacian=False):
16155
r"""
16156
Returns a list of the eigenvalues of the adjacency matrix.
16157
16158
INPUT:
16159
16160
- ``laplacian`` - if ``True``, use the Laplacian matrix
16161
(see :meth:`kirchhoff_matrix`)
16162
16163
OUTPUT:
16164
16165
A list of the eigenvalues, including multiplicities, sorted
16166
with the largest eigenvalue first.
16167
16168
EXAMPLES::
16169
16170
sage: P = graphs.PetersenGraph()
16171
sage: P.spectrum()
16172
[3, 1, 1, 1, 1, 1, -2, -2, -2, -2]
16173
sage: P.spectrum(laplacian=True)
16174
[5, 5, 5, 5, 2, 2, 2, 2, 2, 0]
16175
sage: D = P.to_directed()
16176
sage: D.delete_edge(7,9)
16177
sage: D.spectrum()
16178
[2.9032119259..., 1, 1, 1, 1, 0.8060634335..., -1.7092753594..., -2, -2, -2]
16179
16180
::
16181
16182
sage: C = graphs.CycleGraph(8)
16183
sage: C.spectrum()
16184
[2, 1.4142135623..., 1.4142135623..., 0, 0, -1.4142135623..., -1.4142135623..., -2]
16185
16186
A digraph may have complex eigenvalues. Previously, the complex parts
16187
of graph eigenvalues were being dropped. For a 3-cycle, we have::
16188
16189
sage: T = DiGraph({0:[1], 1:[2], 2:[0]})
16190
sage: T.spectrum()
16191
[1, -0.5000000000... + 0.8660254037...*I, -0.5000000000... - 0.8660254037...*I]
16192
16193
TESTS:
16194
16195
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. ::
16196
16197
sage: H = graphs.HoffmanSingletonGraph()
16198
sage: evals = H.spectrum()
16199
sage: lap = map(lambda x : 7 - x, evals)
16200
sage: lap.sort(reverse=True)
16201
sage: lap == H.spectrum(laplacian=True)
16202
True
16203
"""
16204
# Ideally the spectrum should return something like a Factorization object
16205
# containing each eigenvalue once, along with its multiplicity.
16206
# This function, returning a list. could then just be renamed "eigenvalues"
16207
if laplacian:
16208
M = self.kirchhoff_matrix()
16209
else:
16210
M = self.adjacency_matrix()
16211
evals = M.eigenvalues()
16212
evals.sort(reverse=True)
16213
return evals
16214
16215
def characteristic_polynomial(self, var='x', laplacian=False):
16216
r"""
16217
Returns the characteristic polynomial of the adjacency matrix of
16218
the (di)graph.
16219
16220
Let `G` be a (simple) graph with adjacency matrix `A`. Let `I` be the
16221
identity matrix of dimensions the same as `A`. The characteristic
16222
polynomial of `G` is defined as the determinant `\det(xI - A)`.
16223
16224
.. note::
16225
16226
``characteristic_polynomial`` and ``charpoly`` are aliases and
16227
thus provide exactly the same method.
16228
16229
INPUT:
16230
16231
- ``x`` -- (default: ``'x'``) the variable of the characteristic
16232
polynomial.
16233
16234
- ``laplacian`` -- (default: ``False``) if ``True``, use the
16235
Laplacian matrix.
16236
16237
.. SEEALSO::
16238
16239
- :meth:`kirchhoff_matrix`
16240
16241
- :meth:`laplacian_matrix`
16242
16243
EXAMPLES::
16244
16245
sage: P = graphs.PetersenGraph()
16246
sage: P.characteristic_polynomial()
16247
x^10 - 15*x^8 + 75*x^6 - 24*x^5 - 165*x^4 + 120*x^3 + 120*x^2 - 160*x + 48
16248
sage: P.charpoly()
16249
x^10 - 15*x^8 + 75*x^6 - 24*x^5 - 165*x^4 + 120*x^3 + 120*x^2 - 160*x + 48
16250
sage: P.characteristic_polynomial(laplacian=True)
16251
x^10 - 30*x^9 + 390*x^8 - 2880*x^7 + 13305*x^6 -
16252
39882*x^5 + 77640*x^4 - 94800*x^3 + 66000*x^2 - 20000*x
16253
"""
16254
if laplacian:
16255
return self.kirchhoff_matrix().charpoly(var=var)
16256
else:
16257
return self.adjacency_matrix().charpoly(var=var)
16258
16259
# alias, consistent with linear algebra code
16260
charpoly = characteristic_polynomial
16261
16262
def eigenvectors(self, laplacian=False):
16263
r"""
16264
Returns the *right* eigenvectors of the adjacency matrix of the graph.
16265
16266
INPUT:
16267
16268
- ``laplacian`` - if True, use the Laplacian matrix
16269
(see :meth:`kirchhoff_matrix`)
16270
16271
OUTPUT:
16272
16273
A list of triples. Each triple begins with an eigenvalue of
16274
the adjacency matrix of the graph. This is followed by
16275
a list of eigenvectors for the eigenvalue, when the
16276
eigenvectors are placed on the right side of the matrix.
16277
Together, the eigenvectors form a basis for the eigenspace.
16278
The triple concludes with the algebraic multiplicity of
16279
the eigenvalue.
16280
16281
For some graphs, the exact eigenspaces provided by
16282
:meth:`eigenspaces` provide additional insight into
16283
the structure of the eigenspaces.
16284
16285
EXAMPLES::
16286
16287
sage: P = graphs.PetersenGraph()
16288
sage: P.eigenvectors()
16289
[(3, [
16290
(1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
16291
], 1), (-2, [
16292
(1, 0, 0, 0, -1, -1, -1, 0, 1, 1),
16293
(0, 1, 0, 0, -1, 0, -2, -1, 1, 2),
16294
(0, 0, 1, 0, -1, 1, -1, -2, 0, 2),
16295
(0, 0, 0, 1, -1, 1, 0, -1, -1, 1)
16296
], 4), (1, [
16297
(1, 0, 0, 0, 0, 1, -1, 0, 0, -1),
16298
(0, 1, 0, 0, 0, -1, 1, -1, 0, 0),
16299
(0, 0, 1, 0, 0, 0, -1, 1, -1, 0),
16300
(0, 0, 0, 1, 0, 0, 0, -1, 1, -1),
16301
(0, 0, 0, 0, 1, -1, 0, 0, -1, 1)
16302
], 5)]
16303
16304
Eigenspaces for the Laplacian should be identical since the
16305
Petersen graph is regular. However, since the output also
16306
contains the eigenvalues, the two outputs are slightly
16307
different. ::
16308
16309
sage: P.eigenvectors(laplacian=True)
16310
[(0, [
16311
(1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
16312
], 1), (5, [
16313
(1, 0, 0, 0, -1, -1, -1, 0, 1, 1),
16314
(0, 1, 0, 0, -1, 0, -2, -1, 1, 2),
16315
(0, 0, 1, 0, -1, 1, -1, -2, 0, 2),
16316
(0, 0, 0, 1, -1, 1, 0, -1, -1, 1)
16317
], 4), (2, [
16318
(1, 0, 0, 0, 0, 1, -1, 0, 0, -1),
16319
(0, 1, 0, 0, 0, -1, 1, -1, 0, 0),
16320
(0, 0, 1, 0, 0, 0, -1, 1, -1, 0),
16321
(0, 0, 0, 1, 0, 0, 0, -1, 1, -1),
16322
(0, 0, 0, 0, 1, -1, 0, 0, -1, 1)
16323
], 5)]
16324
16325
::
16326
16327
sage: C = graphs.CycleGraph(8)
16328
sage: C.eigenvectors()
16329
[(2, [
16330
(1, 1, 1, 1, 1, 1, 1, 1)
16331
], 1), (-2, [
16332
(1, -1, 1, -1, 1, -1, 1, -1)
16333
], 1), (0, [
16334
(1, 0, -1, 0, 1, 0, -1, 0),
16335
(0, 1, 0, -1, 0, 1, 0, -1)
16336
], 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)]
16337
16338
A digraph may have complex eigenvalues. Previously, the complex parts
16339
of graph eigenvalues were being dropped. For a 3-cycle, we have::
16340
16341
sage: T = DiGraph({0:[1], 1:[2], 2:[0]})
16342
sage: T.eigenvectors()
16343
[(1, [
16344
(1, 1, 1)
16345
], 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)]
16346
"""
16347
if laplacian:
16348
M = self.kirchhoff_matrix()
16349
else:
16350
M = self.adjacency_matrix()
16351
return M.right_eigenvectors()
16352
16353
def eigenspaces(self, laplacian=False):
16354
r"""
16355
Returns the *right* eigenspaces of the adjacency matrix of the graph.
16356
16357
INPUT:
16358
16359
- ``laplacian`` - if True, use the Laplacian matrix
16360
(see :meth:`kirchhoff_matrix`)
16361
16362
OUTPUT:
16363
16364
A list of pairs. Each pair is an eigenvalue of the
16365
adjacency matrix of the graph, followed by
16366
the vector space that is the eigenspace for that eigenvalue,
16367
when the eigenvectors are placed on the right of the matrix.
16368
16369
For some graphs, some of the the eigenspaces are described
16370
exactly by vector spaces over a
16371
:func:`~sage.rings.number_field.number_field.NumberField`.
16372
For numerical eigenvectors use :meth:`eigenvectors`.
16373
16374
EXAMPLES::
16375
16376
sage: P = graphs.PetersenGraph()
16377
sage: P.eigenspaces()
16378
[
16379
(3, Vector space of degree 10 and dimension 1 over Rational Field
16380
User basis matrix:
16381
[1 1 1 1 1 1 1 1 1 1]),
16382
(-2, Vector space of degree 10 and dimension 4 over Rational Field
16383
User basis matrix:
16384
[ 1 0 0 0 -1 -1 -1 0 1 1]
16385
[ 0 1 0 0 -1 0 -2 -1 1 2]
16386
[ 0 0 1 0 -1 1 -1 -2 0 2]
16387
[ 0 0 0 1 -1 1 0 -1 -1 1]),
16388
(1, Vector space of degree 10 and dimension 5 over Rational Field
16389
User basis matrix:
16390
[ 1 0 0 0 0 1 -1 0 0 -1]
16391
[ 0 1 0 0 0 -1 1 -1 0 0]
16392
[ 0 0 1 0 0 0 -1 1 -1 0]
16393
[ 0 0 0 1 0 0 0 -1 1 -1]
16394
[ 0 0 0 0 1 -1 0 0 -1 1])
16395
]
16396
16397
Eigenspaces for the Laplacian should be identical since the
16398
Petersen graph is regular. However, since the output also
16399
contains the eigenvalues, the two outputs are slightly
16400
different. ::
16401
16402
sage: P.eigenspaces(laplacian=True)
16403
[
16404
(0, Vector space of degree 10 and dimension 1 over Rational Field
16405
User basis matrix:
16406
[1 1 1 1 1 1 1 1 1 1]),
16407
(5, Vector space of degree 10 and dimension 4 over Rational Field
16408
User basis matrix:
16409
[ 1 0 0 0 -1 -1 -1 0 1 1]
16410
[ 0 1 0 0 -1 0 -2 -1 1 2]
16411
[ 0 0 1 0 -1 1 -1 -2 0 2]
16412
[ 0 0 0 1 -1 1 0 -1 -1 1]),
16413
(2, Vector space of degree 10 and dimension 5 over Rational Field
16414
User basis matrix:
16415
[ 1 0 0 0 0 1 -1 0 0 -1]
16416
[ 0 1 0 0 0 -1 1 -1 0 0]
16417
[ 0 0 1 0 0 0 -1 1 -1 0]
16418
[ 0 0 0 1 0 0 0 -1 1 -1]
16419
[ 0 0 0 0 1 -1 0 0 -1 1])
16420
]
16421
16422
Notice how one eigenspace below is described with a square root of
16423
2. For the two possible values (positive and negative) there is a
16424
corresponding eigenspace. ::
16425
16426
sage: C = graphs.CycleGraph(8)
16427
sage: C.eigenspaces()
16428
[
16429
(2, Vector space of degree 8 and dimension 1 over Rational Field
16430
User basis matrix:
16431
[1 1 1 1 1 1 1 1]),
16432
(-2, Vector space of degree 8 and dimension 1 over Rational Field
16433
User basis matrix:
16434
[ 1 -1 1 -1 1 -1 1 -1]),
16435
(0, Vector space of degree 8 and dimension 2 over Rational Field
16436
User basis matrix:
16437
[ 1 0 -1 0 1 0 -1 0]
16438
[ 0 1 0 -1 0 1 0 -1]),
16439
(a3, Vector space of degree 8 and dimension 2 over Number Field in a3 with defining polynomial x^2 - 2
16440
User basis matrix:
16441
[ 1 0 -1 -a3 -1 0 1 a3]
16442
[ 0 1 a3 1 0 -1 -a3 -1])
16443
]
16444
16445
A digraph may have complex eigenvalues and eigenvectors.
16446
For a 3-cycle, we have::
16447
16448
sage: T = DiGraph({0:[1], 1:[2], 2:[0]})
16449
sage: T.eigenspaces()
16450
[
16451
(1, Vector space of degree 3 and dimension 1 over Rational Field
16452
User basis matrix:
16453
[1 1 1]),
16454
(a1, Vector space of degree 3 and dimension 1 over Number Field in a1 with defining polynomial x^2 + x + 1
16455
User basis matrix:
16456
[ 1 a1 -a1 - 1])
16457
]
16458
"""
16459
if laplacian:
16460
M = self.kirchhoff_matrix()
16461
else:
16462
M = self.adjacency_matrix()
16463
# could pass format='all' to get QQbar eigenvalues and eigenspaces
16464
# which would be a change in default behavior
16465
return M.right_eigenspaces(format='galois', algebraic_multiplicity=False)
16466
16467
### Automorphism and isomorphism
16468
16469
def relabel(self, perm=None, inplace=True, return_map=False, check_input = True, complete_partial_function = True):
16470
r"""
16471
Relabels the vertices of ``self``
16472
16473
INPUT:
16474
16475
- ``perm`` -- a function, dictionary, list, permutation, or
16476
``None`` (default: ``None``)
16477
16478
- ``inplace`` -- a boolean (default: ``True``)
16479
16480
- ``return_map`` -- a boolean (default: ``False``)
16481
16482
- ``check_input`` (boolean) -- whether to test input for
16483
correctness. *This can potentially be very time-consuming !*.
16484
16485
- ``complete_partial_function`` (boolean) -- whether to automatically
16486
complete the permutation if some elements of the graph are not
16487
associated with any new name. In this case, those elements are not
16488
relabeled *This can potentially be very time-consuming !*.
16489
16490
If ``perm`` is a function ``f``, then each vertex ``v`` is
16491
relabeled to ``f(v)``.
16492
16493
If ``perm`` is a dictionary ``d``, then each vertex ``v``
16494
(which should be a key of ``d``) is relabeled to ``d[v]``.
16495
Similarly, if ``perm`` is a list or tuple ``l`` of length
16496
``n``, then each vertex (which should be in `\{0,1,...,n-1\}`)
16497
is relabeled to ``l[v]``.
16498
16499
If ``perm`` is a permutation, then each vertex ``v`` is
16500
relabeled to ``perm(v)``. Caveat: this assumes that the
16501
vertices are labelled `\{0,1,...,n-1\}`; since permutations
16502
act by default on the set `\{1,2,...,n\}`, this is achieved by
16503
identifying `n` and `0`.
16504
16505
If ``perm`` is ``None``, the graph is relabeled to be on the
16506
vertices `\{0,1,...,n-1\}`.
16507
16508
.. note:: at this point, only injective relabeling are supported.
16509
16510
If ``inplace`` is ``True``, the graph is modified in place and
16511
``None`` is returned. Otherwise a relabeled copy of the graph
16512
is returned.
16513
16514
If ``return_map`` is ``True`` a dictionary representing the
16515
relabelling map is returned (incompatible with ``inplace==False``).
16516
16517
EXAMPLES::
16518
16519
sage: G = graphs.PathGraph(3)
16520
sage: G.am()
16521
[0 1 0]
16522
[1 0 1]
16523
[0 1 0]
16524
16525
Relabeling using a dictionary. Note that the dictionary does not define
16526
the new label of vertex `0`::
16527
16528
sage: G.relabel({1:2,2:1}, inplace=False).am()
16529
[0 0 1]
16530
[0 0 1]
16531
[1 1 0]
16532
16533
This is because the method automatically "extends" the relabeling to the
16534
missing vertices (whose label will not change). Checking that all
16535
vertices have an image can require some time, and this feature can be
16536
disabled (at your own risk)::
16537
16538
sage: G.relabel({1:2,2:1}, inplace=False, complete_partial_function = False).am()
16539
Traceback (most recent call last):
16540
...
16541
KeyError: 0
16542
16543
Relabeling using a list::
16544
16545
sage: G.relabel([0,2,1], inplace=False).am()
16546
[0 0 1]
16547
[0 0 1]
16548
[1 1 0]
16549
16550
Relabeling using a tuple::
16551
16552
sage: G.relabel((0,2,1), inplace=False).am()
16553
[0 0 1]
16554
[0 0 1]
16555
[1 1 0]
16556
16557
Relabeling using a Sage permutation::
16558
16559
sage: G = graphs.PathGraph(3)
16560
sage: from sage.groups.perm_gps.permgroup_named import SymmetricGroup
16561
sage: S = SymmetricGroup(3)
16562
sage: gamma = S('(1,2)')
16563
sage: G.relabel(gamma, inplace=False).am()
16564
[0 0 1]
16565
[0 0 1]
16566
[1 1 0]
16567
16568
Relabeling using an injective function::
16569
16570
sage: G.edges()
16571
[(0, 1, None), (1, 2, None)]
16572
sage: H = G.relabel(lambda i: i+10, inplace=False)
16573
sage: H.vertices()
16574
[10, 11, 12]
16575
sage: H.edges()
16576
[(10, 11, None), (11, 12, None)]
16577
16578
Relabeling using a non injective function has no meaning::
16579
16580
sage: G.edges()
16581
[(0, 1, None), (1, 2, None)]
16582
sage: G.relabel(lambda i: 0, inplace=False)
16583
Traceback (most recent call last):
16584
...
16585
NotImplementedError: Non injective relabeling
16586
16587
But this test can be disabled, which leads to ... problems::
16588
16589
sage: G.edges()
16590
[(0, 1, None), (1, 2, None)]
16591
sage: G.relabel(lambda i: 0, check_input = False)
16592
sage: G.edges()
16593
[(0, 0, None)]
16594
16595
Relabeling to simpler labels::
16596
16597
sage: G = graphs.CubeGraph(3)
16598
sage: G.vertices()
16599
['000', '001', '010', '011', '100', '101', '110', '111']
16600
sage: G.relabel()
16601
sage: G.vertices()
16602
[0, 1, 2, 3, 4, 5, 6, 7]
16603
16604
Recovering the relabeling with ``return_map``::
16605
16606
sage: G = graphs.CubeGraph(3)
16607
sage: expecting = {'000': 0, '001': 1, '010': 2, '011': 3, '100': 4, '101': 5, '110': 6, '111': 7}
16608
sage: G.relabel(return_map=True) == expecting
16609
True
16610
16611
::
16612
16613
sage: G = graphs.PathGraph(3)
16614
sage: G.relabel(lambda i: i+10, return_map=True)
16615
{0: 10, 1: 11, 2: 12}
16616
16617
TESTS::
16618
16619
sage: P = Graph(graphs.PetersenGraph())
16620
sage: P.delete_edge([0,1])
16621
sage: P.add_edge((4,5))
16622
sage: P.add_edge((2,6))
16623
sage: P.delete_vertices([0,1])
16624
sage: P.relabel()
16625
16626
The attributes are properly updated too
16627
16628
::
16629
16630
sage: G = graphs.PathGraph(5)
16631
sage: G.set_vertices({0: 'before', 1: 'delete', 2: 'after'})
16632
sage: G.set_boundary([1,2,3])
16633
sage: G.delete_vertex(1)
16634
sage: G.relabel()
16635
sage: G.get_vertices()
16636
{0: 'before', 1: 'after', 2: None, 3: None}
16637
sage: G.get_boundary()
16638
[1, 2]
16639
sage: G.get_pos()
16640
{0: (0, 0), 1: (2, 0), 2: (3, 0), 3: (4, 0)}
16641
16642
Check that :trac:`12477` is fixed::
16643
16644
sage: g = Graph({1:[2,3]})
16645
sage: rel = {1:'a', 2:'b'}
16646
sage: g.relabel(rel)
16647
sage: g.vertices()
16648
[3, 'a', 'b']
16649
sage: rel
16650
{1: 'a', 2: 'b'}
16651
16652
Immutable graphs cannot be relabeled::
16653
16654
sage: Graph(graphs.PetersenGraph(), immutable=True).relabel({})
16655
Traceback (most recent call last):
16656
...
16657
ValueError: To relabel an immutable graph use inplace=False
16658
"""
16659
from sage.groups.perm_gps.permgroup_element import PermutationGroupElement
16660
16661
if not inplace:
16662
G = self.copy(immutable=False)
16663
perm2 = G.relabel(perm,
16664
return_map= return_map,
16665
check_input = check_input,
16666
complete_partial_function = complete_partial_function)
16667
16668
if getattr(self, "_immutable", False):
16669
G = self.__class__(G, immutable = True)
16670
16671
if return_map:
16672
return G, perm2
16673
else:
16674
return G
16675
16676
if getattr(self, "_immutable", False):
16677
raise ValueError("To relabel an immutable graph use inplace=False")
16678
16679
# If perm is not a dictionary, we build one !
16680
16681
if perm is None:
16682
verts = self.vertices() # vertices() returns a sorted list:
16683
perm = {}; i = 0 # this guarantees consistent relabeling
16684
for v in verts:
16685
perm[v] = i
16686
i += 1
16687
16688
complete_partial_function = False
16689
check_input = False
16690
16691
elif isinstance(perm, dict):
16692
16693
# If all vertices do not have a new label, the code will touch the
16694
# dictionary. Let us keep the one we received from the user clean !
16695
from copy import copy
16696
perm = copy(perm)
16697
16698
elif isinstance(perm, (list, tuple)):
16699
perm = dict( [ [i,perm[i]] for i in xrange(len(perm)) ] )
16700
16701
elif isinstance(perm, PermutationGroupElement):
16702
n = self.order()
16703
ddict = {}
16704
for i in xrange(1,n):
16705
ddict[i] = perm(i)%n
16706
if n > 0:
16707
ddict[0] = perm(n)%n
16708
perm = ddict
16709
16710
elif callable(perm):
16711
perm = dict( [ i, perm(i) ] for i in self.vertices() )
16712
complete_partial_function = False
16713
16714
else:
16715
raise TypeError("Type of perm is not supported for relabeling.")
16716
16717
# Whether to complete the relabeling function if some vertices do not
16718
# appear in the permutation.
16719
if complete_partial_function:
16720
for v in self:
16721
if v not in perm:
16722
perm[v] = v
16723
16724
# Whether to check input
16725
if check_input:
16726
if len(set(perm.values())) < len(perm):
16727
raise NotImplementedError("Non injective relabeling")
16728
16729
for v in perm.iterkeys():
16730
if v in self:
16731
try:
16732
hash(perm[v])
16733
except TypeError:
16734
raise ValueError("perm dictionary must be of the format {a:a1, b:b1, ...} where a,b,... are vertices and a1,b1,... are hashable")
16735
16736
self._backend.relabel(perm, self._directed)
16737
16738
attributes_to_update = ('_pos', '_assoc', '_embedding')
16739
for attr in attributes_to_update:
16740
if hasattr(self, attr) and getattr(self, attr) is not None:
16741
new_attr = {}
16742
for v,value in getattr(self, attr).iteritems():
16743
new_attr[perm[v]] = value
16744
16745
setattr(self, attr, new_attr)
16746
16747
self._boundary = [perm[v] for v in self._boundary]
16748
16749
if return_map:
16750
return perm
16751
16752
def degree_to_cell(self, vertex, cell):
16753
"""
16754
Returns the number of edges from vertex to an edge in cell. In the
16755
case of a digraph, returns a tuple (in_degree, out_degree).
16756
16757
EXAMPLES::
16758
16759
sage: G = graphs.CubeGraph(3)
16760
sage: cell = G.vertices()[:3]
16761
sage: G.degree_to_cell('011', cell)
16762
2
16763
sage: G.degree_to_cell('111', cell)
16764
0
16765
16766
::
16767
16768
sage: D = DiGraph({ 0:[1,2,3], 1:[3,4], 3:[4,5]})
16769
sage: cell = [0,1,2]
16770
sage: D.degree_to_cell(5, cell)
16771
(0, 0)
16772
sage: D.degree_to_cell(3, cell)
16773
(2, 0)
16774
sage: D.degree_to_cell(0, cell)
16775
(0, 2)
16776
"""
16777
if self._directed:
16778
in_neighbors_in_cell = set([a for a,_,_ in self.incoming_edges(vertex)]) & set(cell)
16779
out_neighbors_in_cell = set([a for _,a,_ in self.outgoing_edges(vertex)]) & set(cell)
16780
return (len(in_neighbors_in_cell), len(out_neighbors_in_cell))
16781
else:
16782
neighbors_in_cell = set(self.neighbors(vertex)) & set(cell)
16783
return len(neighbors_in_cell)
16784
16785
def is_equitable(self, partition, quotient_matrix=False):
16786
"""
16787
Checks whether the given partition is equitable with respect to
16788
self.
16789
16790
A partition is equitable with respect to a graph if for every pair
16791
of cells C1, C2 of the partition, the number of edges from a vertex
16792
of C1 to C2 is the same, over all vertices in C1.
16793
16794
INPUT:
16795
16796
16797
- ``partition`` - a list of lists
16798
16799
- ``quotient_matrix`` - (default False) if True, and
16800
the partition is equitable, returns a matrix over the integers
16801
whose rows and columns represent cells of the partition, and whose
16802
i,j entry is the number of vertices in cell j adjacent to each
16803
vertex in cell i (since the partition is equitable, this is well
16804
defined)
16805
16806
16807
EXAMPLES::
16808
16809
sage: G = graphs.PetersenGraph()
16810
sage: G.is_equitable([[0,4],[1,3,5,9],[2,6,8],[7]])
16811
False
16812
sage: G.is_equitable([[0,4],[1,3,5,9],[2,6,8,7]])
16813
True
16814
sage: G.is_equitable([[0,4],[1,3,5,9],[2,6,8,7]], quotient_matrix=True)
16815
[1 2 0]
16816
[1 0 2]
16817
[0 2 1]
16818
16819
::
16820
16821
sage: ss = (graphs.WheelGraph(6)).line_graph(labels=False)
16822
sage: prt = [[(0, 1)], [(0, 2), (0, 3), (0, 4), (1, 2), (1, 4)], [(2, 3), (3, 4)]]
16823
16824
::
16825
16826
sage: ss.is_equitable(prt)
16827
Traceback (most recent call last):
16828
...
16829
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.
16830
16831
::
16832
16833
sage: ss = (graphs.WheelGraph(5)).line_graph(labels=False)
16834
sage: ss.is_equitable(prt)
16835
False
16836
"""
16837
from sage.misc.flatten import flatten
16838
from sage.misc.misc import uniq
16839
if sorted(flatten(partition, max_level=1)) != self.vertices():
16840
raise TypeError("Partition (%s) is not valid for this graph: vertices are incorrect."%partition)
16841
if any(len(cell)==0 for cell in partition):
16842
raise TypeError("Partition (%s) is not valid for this graph: there is a cell of length 0."%partition)
16843
if quotient_matrix:
16844
from sage.matrix.constructor import Matrix
16845
from sage.rings.integer_ring import IntegerRing
16846
n = len(partition)
16847
M = Matrix(IntegerRing(), n)
16848
for i in xrange(n):
16849
for j in xrange(n):
16850
cell_i = partition[i]
16851
cell_j = partition[j]
16852
degrees = [self.degree_to_cell(u, cell_j) for u in cell_i]
16853
if len(uniq(degrees)) > 1:
16854
return False
16855
if self._directed:
16856
M[i, j] = degrees[0][0]
16857
else:
16858
M[i, j] = degrees[0]
16859
return M
16860
else:
16861
for cell1 in partition:
16862
for cell2 in partition:
16863
degrees = [self.degree_to_cell(u, cell2) for u in cell1]
16864
if len(uniq(degrees)) > 1:
16865
return False
16866
return True
16867
16868
def coarsest_equitable_refinement(self, partition, sparse=True):
16869
"""
16870
Returns the coarsest partition which is finer than the input
16871
partition, and equitable with respect to self.
16872
16873
A partition is equitable with respect to a graph if for every pair
16874
of cells C1, C2 of the partition, the number of edges from a vertex
16875
of C1 to C2 is the same, over all vertices in C1.
16876
16877
A partition P1 is finer than P2 (P2 is coarser than P1) if every
16878
cell of P1 is a subset of a cell of P2.
16879
16880
INPUT:
16881
16882
16883
- ``partition`` - a list of lists
16884
16885
- ``sparse`` - (default False) whether to use sparse
16886
or dense representation- for small graphs, use dense for speed
16887
16888
16889
EXAMPLES::
16890
16891
sage: G = graphs.PetersenGraph()
16892
sage: G.coarsest_equitable_refinement([[0],range(1,10)])
16893
[[0], [2, 3, 6, 7, 8, 9], [1, 4, 5]]
16894
sage: G = graphs.CubeGraph(3)
16895
sage: verts = G.vertices()
16896
sage: Pi = [verts[:1], verts[1:]]
16897
sage: Pi
16898
[['000'], ['001', '010', '011', '100', '101', '110', '111']]
16899
sage: G.coarsest_equitable_refinement(Pi)
16900
[['000'], ['011', '101', '110'], ['111'], ['001', '010', '100']]
16901
16902
Note that given an equitable partition, this function returns that
16903
partition::
16904
16905
sage: P = graphs.PetersenGraph()
16906
sage: prt = [[0], [1, 4, 5], [2, 3, 6, 7, 8, 9]]
16907
sage: P.coarsest_equitable_refinement(prt)
16908
[[0], [1, 4, 5], [2, 3, 6, 7, 8, 9]]
16909
16910
::
16911
16912
sage: ss = (graphs.WheelGraph(6)).line_graph(labels=False)
16913
sage: prt = [[(0, 1)], [(0, 2), (0, 3), (0, 4), (1, 2), (1, 4)], [(2, 3), (3, 4)]]
16914
sage: ss.coarsest_equitable_refinement(prt)
16915
Traceback (most recent call last):
16916
...
16917
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.
16918
16919
::
16920
16921
sage: ss = (graphs.WheelGraph(5)).line_graph(labels=False)
16922
sage: ss.coarsest_equitable_refinement(prt)
16923
[[(0, 1)], [(1, 2), (1, 4)], [(0, 3)], [(0, 2), (0, 4)], [(2, 3), (3, 4)]]
16924
16925
ALGORITHM: Brendan D. McKay's Master's Thesis, University of
16926
Melbourne, 1976.
16927
"""
16928
from sage.misc.flatten import flatten
16929
if sorted(flatten(partition, max_level=1)) != self.vertices():
16930
raise TypeError("Partition (%s) is not valid for this graph: vertices are incorrect."%partition)
16931
if any(len(cell)==0 for cell in partition):
16932
raise TypeError("Partition (%s) is not valid for this graph: there is a cell of length 0."%partition)
16933
if self.has_multiple_edges():
16934
raise TypeError("Refinement function does not support multiple edges.")
16935
from copy import copy
16936
G = copy(self)
16937
perm_to = G.relabel(return_map=True)
16938
partition = [[perm_to[b] for b in cell] for cell in partition]
16939
perm_from = {}
16940
for v in self:
16941
perm_from[perm_to[v]] = v
16942
n = G.num_verts()
16943
if sparse:
16944
from sage.graphs.base.sparse_graph import SparseGraph
16945
CG = SparseGraph(n)
16946
else:
16947
from sage.graphs.base.dense_graph import DenseGraph
16948
CG = DenseGraph(n)
16949
for i in range(n):
16950
for j in range(n):
16951
if G.has_edge(i,j):
16952
CG.add_arc(i,j)
16953
16954
from sage.groups.perm_gps.partn_ref.refinement_graphs import coarsest_equitable_refinement
16955
result = coarsest_equitable_refinement(CG, partition, G._directed)
16956
return [[perm_from[b] for b in cell] for cell in result]
16957
16958
def automorphism_group(self, partition=None, verbosity=0,
16959
edge_labels=False, order=False,
16960
return_group=True, orbits=False):
16961
"""
16962
Returns the largest subgroup of the automorphism group of the
16963
(di)graph whose orbit partition is finer than the partition given.
16964
If no partition is given, the unit partition is used and the entire
16965
automorphism group is given.
16966
16967
INPUT:
16968
16969
- ``partition`` - default is the unit partition,
16970
otherwise computes the subgroup of the full automorphism group
16971
respecting the partition.
16972
16973
- ``edge_labels`` - default False, otherwise allows
16974
only permutations respecting edge labels.
16975
16976
- ``order`` - (default False) if True, compute the
16977
order of the automorphism group
16978
16979
- ``return_group`` - default True
16980
16981
- ``orbits`` - returns the orbits of the group acting
16982
on the vertices of the graph
16983
16984
.. WARNING::
16985
16986
Since :trac:`14319` the domain of the automorphism group is equal to
16987
the graph's vertex set, and the ``translation`` argument has become
16988
useless.
16989
16990
OUTPUT: The order of the output is group, order, orbits. However, there
16991
are options to turn each of these on or off.
16992
16993
EXAMPLES:
16994
16995
Graphs::
16996
16997
sage: graphs_query = GraphQuery(display_cols=['graph6'],num_vertices=4)
16998
sage: L = graphs_query.get_graphs_list()
16999
sage: graphs_list.show_graphs(L)
17000
sage: for g in L:
17001
... G = g.automorphism_group()
17002
... G.order(), G.gens()
17003
(24, [(2,3), (1,2), (0,1)])
17004
(4, [(2,3), (0,1)])
17005
(2, [(1,2)])
17006
(6, [(1,2), (0,1)])
17007
(6, [(2,3), (1,2)])
17008
(8, [(1,2), (0,1)(2,3)])
17009
(2, [(0,1)(2,3)])
17010
(2, [(1,2)])
17011
(8, [(2,3), (0,1), (0,2)(1,3)])
17012
(4, [(2,3), (0,1)])
17013
(24, [(2,3), (1,2), (0,1)])
17014
sage: C = graphs.CubeGraph(4)
17015
sage: G = C.automorphism_group()
17016
sage: M = G.character_table() # random order of rows, thus abs() below
17017
sage: QQ(M.determinant()).abs()
17018
712483534798848
17019
sage: G.order()
17020
384
17021
17022
::
17023
17024
sage: D = graphs.DodecahedralGraph()
17025
sage: G = D.automorphism_group()
17026
sage: A5 = AlternatingGroup(5)
17027
sage: Z2 = CyclicPermutationGroup(2)
17028
sage: H = A5.direct_product(Z2)[0] #see documentation for direct_product to explain the [0]
17029
sage: G.is_isomorphic(H)
17030
True
17031
17032
Multigraphs::
17033
17034
sage: G = Graph(multiedges=True,sparse=True)
17035
sage: G.add_edge(('a', 'b'))
17036
sage: G.add_edge(('a', 'b'))
17037
sage: G.add_edge(('a', 'b'))
17038
sage: G.automorphism_group()
17039
Permutation Group with generators [('a','b')]
17040
17041
Digraphs::
17042
17043
sage: D = DiGraph( { 0:[1], 1:[2], 2:[3], 3:[4], 4:[0] } )
17044
sage: D.automorphism_group()
17045
Permutation Group with generators [(0,1,2,3,4)]
17046
17047
Edge labeled graphs::
17048
17049
sage: G = Graph(sparse=True)
17050
sage: G.add_edges( [(0,1,'a'),(1,2,'b'),(2,3,'c'),(3,4,'b'),(4,0,'a')] )
17051
sage: G.automorphism_group(edge_labels=True)
17052
Permutation Group with generators [(1,4)(2,3)]
17053
17054
::
17055
17056
sage: G = Graph({0 : {1 : 7}})
17057
sage: G.automorphism_group(edge_labels=True)
17058
Permutation Group with generators [(0,1)]
17059
17060
sage: foo = Graph(sparse=True)
17061
sage: bar = Graph(implementation='c_graph',sparse=True)
17062
sage: foo.add_edges([(0,1,1),(1,2,2), (2,3,3)])
17063
sage: bar.add_edges([(0,1,1),(1,2,2), (2,3,3)])
17064
sage: foo.automorphism_group(edge_labels=True)
17065
Permutation Group with generators [()]
17066
sage: foo.automorphism_group()
17067
Permutation Group with generators [(0,3)(1,2)]
17068
sage: bar.automorphism_group(edge_labels=True)
17069
Permutation Group with generators [()]
17070
17071
You can also ask for just the order of the group::
17072
17073
sage: G = graphs.PetersenGraph()
17074
sage: G.automorphism_group(return_group=False, order=True)
17075
120
17076
17077
Or, just the orbits (note that each graph here is vertex transitive)
17078
17079
::
17080
17081
sage: G = graphs.PetersenGraph()
17082
sage: G.automorphism_group(return_group=False, orbits=True)
17083
[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]]
17084
sage: G.automorphism_group(partition=[[0],range(1,10)], return_group=False, orbits=True)
17085
[[0], [2, 3, 6, 7, 8, 9], [1, 4, 5]]
17086
sage: C = graphs.CubeGraph(3)
17087
sage: C.automorphism_group(orbits=True, return_group=False)
17088
[['000', '001', '010', '011', '100', '101', '110', '111']]
17089
17090
TESTS:
17091
17092
We get a KeyError when given an invalid partition (trac #6087)::
17093
17094
sage: g=graphs.CubeGraph(3)
17095
sage: g.relabel()
17096
sage: g.automorphism_group(partition=[[0,1,2],[3,4,5]])
17097
Traceback (most recent call last):
17098
...
17099
KeyError: 6
17100
17101
Labeled automorphism group::
17102
17103
sage: digraphs.DeBruijn(3,2).automorphism_group()
17104
Permutation Group with generators [('01','02')('10','20')('11','22')('12','21'), ('00','11')('01','10')('02','12')('20','21')]
17105
sage: d = digraphs.DeBruijn(3,2)
17106
sage: d.allow_multiple_edges(True)
17107
sage: d.add_edge(d.edges()[0])
17108
sage: d.automorphism_group()
17109
Permutation Group with generators [('01','02')('10','20')('11','22')('12','21')]
17110
17111
The labeling is correct::
17112
17113
sage: g = graphs.PetersenGraph()
17114
sage: ag = g.automorphism_group()
17115
sage: for u,v in g.edges(labels = False):
17116
... if len(ag.orbit((u,v),action="OnPairs")) != 30:
17117
... print "ARggggggggggggg !!!"
17118
17119
Empty group, correct domain::
17120
17121
sage: Graph({'a':['a'], 'b':[]}).automorphism_group()
17122
Permutation Group with generators [()]
17123
sage: Graph({'a':['a'], 'b':[]}).automorphism_group().domain()
17124
{'a', 'b'}
17125
"""
17126
from sage.groups.perm_gps.partn_ref.refinement_graphs import search_tree
17127
from sage.groups.perm_gps.permgroup import PermutationGroup
17128
dig = (self._directed or self.has_loops())
17129
if partition is None:
17130
partition = [self.vertices()]
17131
if edge_labels or self.has_multiple_edges():
17132
G, partition, relabeling = graph_isom_equivalent_non_edge_labeled_graph(self, partition, return_relabeling=True, ignore_edge_labels=(not edge_labels))
17133
G_vertices = sum(partition, [])
17134
G_to = {}
17135
for i in xrange(len(G_vertices)):
17136
G_to[G_vertices[i]] = i
17137
from sage.graphs.all import Graph, DiGraph
17138
DoDG = DiGraph if self._directed else Graph
17139
H = DoDG(len(G_vertices), implementation='c_graph', loops=G.allows_loops())
17140
HB = H._backend
17141
for u,v in G.edge_iterator(labels=False):
17142
u = G_to[u]; v = G_to[v]
17143
HB.add_edge(u,v,None,G._directed)
17144
GC = HB._cg
17145
partition = [[G_to[v] for v in cell] for cell in partition]
17146
A = search_tree(GC, partition, lab=False, dict_rep=True, dig=dig, verbosity=verbosity, order=order)
17147
if order:
17148
a,b,c = A
17149
else:
17150
a,b = A
17151
b_new = {}
17152
for v in G_to:
17153
b_new[v] = b[G_to[v]]
17154
b = b_new
17155
# b is a translation of the labellings
17156
acting_vertices = {}
17157
translation_d = {}
17158
m = G.order()
17159
for v in self:
17160
if b[relabeling[v]] == m:
17161
translation_d[v] = self.order()
17162
acting_vertices[v] = 0
17163
else:
17164
translation_d[v] = b[relabeling[v]]
17165
acting_vertices[v] = b[relabeling[v]]
17166
real_aut_gp = []
17167
n = self.order()
17168
for gen in a:
17169
gen_restr = [0]*n
17170
for v in self.vertex_iterator():
17171
gen_restr[acting_vertices[v]] = gen[acting_vertices[v]]
17172
if gen_restr not in real_aut_gp:
17173
real_aut_gp.append(gen_restr)
17174
id = range(n)
17175
if id in real_aut_gp:
17176
real_aut_gp.remove(id)
17177
a = real_aut_gp
17178
b = translation_d
17179
else:
17180
G_vertices = sum(partition, [])
17181
G_to = {}
17182
for i in xrange(len(G_vertices)):
17183
G_to[G_vertices[i]] = i
17184
from sage.graphs.all import Graph, DiGraph
17185
DoDG = DiGraph if self._directed else Graph
17186
H = DoDG(len(G_vertices), implementation='c_graph', loops=self.allows_loops())
17187
HB = H._backend
17188
for u,v in self.edge_iterator(labels=False):
17189
u = G_to[u]; v = G_to[v]
17190
HB.add_edge(u,v,None,self._directed)
17191
GC = HB._cg
17192
partition = [[G_to[v] for v in cell] for cell in partition]
17193
17194
if return_group:
17195
A = search_tree(GC, partition, dict_rep=True, lab=False, dig=dig, verbosity=verbosity, order=order)
17196
if order:
17197
a,b,c = A
17198
else:
17199
a,b = A
17200
b_new = {}
17201
for v in G_to:
17202
b_new[v] = b[G_to[v]]
17203
b = b_new
17204
else:
17205
a = search_tree(GC, partition, dict_rep=False, lab=False, dig=dig, verbosity=verbosity, order=order)
17206
if order:
17207
a,c = a
17208
17209
output = []
17210
if return_group:
17211
if len(a) != 0:
17212
# We translate the integer permutations into a collection of
17213
# cycles.
17214
from sage.combinat.permutation import Permutation
17215
gens = [Permutation([x+1 for x in aa]).to_cycles() for aa in a]
17216
17217
# We relabel the cycles using the vertices' names instead of integers
17218
n = self.order()
17219
int_to_vertex = {((i+1) if i != n else 1):v for v,i in b.iteritems()}
17220
gens = [ [ tuple([int_to_vertex[i] for i in cycle]) for cycle in gen] for gen in gens]
17221
output.append(PermutationGroup(gens = gens, domain = int_to_vertex.values()))
17222
else:
17223
output.append(PermutationGroup([[]], domain = self.vertices()))
17224
if order:
17225
output.append(c)
17226
if orbits:
17227
G_from = {}
17228
for v in G_to:
17229
G_from[G_to[v]] = v
17230
from sage.groups.perm_gps.partn_ref.refinement_graphs import get_orbits
17231
output.append([[G_from[v] for v in W] for W in get_orbits(a, self.num_verts())])
17232
17233
# A Python switch statement!
17234
return { 0: None,
17235
1: output[0],
17236
2: tuple(output),
17237
3: tuple(output),
17238
4: tuple(output)
17239
}[len(output)]
17240
17241
def is_vertex_transitive(self, partition=None, verbosity=0,
17242
edge_labels=False, order=False,
17243
return_group=True, orbits=False):
17244
"""
17245
Returns whether the automorphism group of self is transitive within
17246
the partition provided, by default the unit partition of the
17247
vertices of self (thus by default tests for vertex transitivity in
17248
the usual sense).
17249
17250
EXAMPLES::
17251
17252
sage: G = Graph({0:[1],1:[2]})
17253
sage: G.is_vertex_transitive()
17254
False
17255
sage: P = graphs.PetersenGraph()
17256
sage: P.is_vertex_transitive()
17257
True
17258
sage: D = graphs.DodecahedralGraph()
17259
sage: D.is_vertex_transitive()
17260
True
17261
sage: R = graphs.RandomGNP(2000, .01)
17262
sage: R.is_vertex_transitive()
17263
False
17264
"""
17265
if partition is None:
17266
partition = [self.vertices()]
17267
17268
for p in partition:
17269
if len(p) == 0:
17270
continue
17271
d = self.degree(p[0])
17272
if not all(self.degree(x) == d for x in p):
17273
return False
17274
17275
new_partition = self.automorphism_group(partition,
17276
verbosity=verbosity, edge_labels=edge_labels,
17277
order=False, return_group=False, orbits=True)
17278
17279
return (len(partition) == len(new_partition))
17280
17281
def is_hamiltonian(self):
17282
r"""
17283
Tests whether the current graph is Hamiltonian.
17284
17285
A graph (resp. digraph) is said to be Hamiltonian
17286
if it contains as a subgraph a cycle (resp. a circuit)
17287
going through all the vertices.
17288
17289
Testing for Hamiltonicity being NP-Complete, this
17290
algorithm could run for some time depending on
17291
the instance.
17292
17293
ALGORITHM:
17294
17295
See ``Graph.traveling_salesman_problem``.
17296
17297
OUTPUT:
17298
17299
Returns ``True`` if a Hamiltonian cycle/circuit exists, and
17300
``False`` otherwise.
17301
17302
NOTE:
17303
17304
This function, as ``hamiltonian_cycle`` and
17305
``traveling_salesman_problem``, computes a Hamiltonian
17306
cycle if it exists: the user should *NOT* test for
17307
Hamiltonicity using ``is_hamiltonian`` before calling
17308
``hamiltonian_cycle`` or ``traveling_salesman_problem``
17309
as it would result in computing it twice.
17310
17311
EXAMPLES:
17312
17313
The Heawood Graph is known to be Hamiltonian ::
17314
17315
sage: g = graphs.HeawoodGraph()
17316
sage: g.is_hamiltonian()
17317
True
17318
17319
The Petergraph, though, is not ::
17320
17321
sage: g = graphs.PetersenGraph()
17322
sage: g.is_hamiltonian()
17323
False
17324
17325
TESTS:
17326
17327
When no solver is installed, a
17328
``OptionalPackageNotFoundError`` exception is raised::
17329
17330
sage: from sage.misc.exceptions import OptionalPackageNotFoundError
17331
sage: try:
17332
... g = graphs.ChvatalGraph()
17333
... if not g.is_hamiltonian():
17334
... print "There is something wrong here !"
17335
... except OptionalPackageNotFoundError:
17336
... pass
17337
"""
17338
17339
try:
17340
tsp = self.traveling_salesman_problem(use_edge_labels = False)
17341
return True
17342
17343
except ValueError:
17344
return False
17345
17346
def is_isomorphic(self, other, certify=False, verbosity=0, edge_labels=False):
17347
"""
17348
Tests for isomorphism between self and other.
17349
17350
INPUT:
17351
17352
17353
- ``certify`` - if True, then output is (a,b), where a
17354
is a boolean and b is either a map or None.
17355
17356
- ``edge_labels`` - default False, otherwise allows
17357
only permutations respecting edge labels.
17358
17359
17360
EXAMPLES: Graphs::
17361
17362
sage: from sage.groups.perm_gps.permgroup_named import SymmetricGroup
17363
sage: D = graphs.DodecahedralGraph()
17364
sage: E = copy(D)
17365
sage: gamma = SymmetricGroup(20).random_element()
17366
sage: E.relabel(gamma)
17367
sage: D.is_isomorphic(E)
17368
True
17369
17370
::
17371
17372
sage: D = graphs.DodecahedralGraph()
17373
sage: S = SymmetricGroup(20)
17374
sage: gamma = S.random_element()
17375
sage: E = copy(D)
17376
sage: E.relabel(gamma)
17377
sage: a,b = D.is_isomorphic(E, certify=True); a
17378
True
17379
sage: from sage.plot.graphics import GraphicsArray
17380
sage: from sage.graphs.generic_graph_pyx import spring_layout_fast
17381
sage: position_D = spring_layout_fast(D)
17382
sage: position_E = {}
17383
sage: for vert in position_D:
17384
... position_E[b[vert]] = position_D[vert]
17385
sage: GraphicsArray([D.plot(pos=position_D), E.plot(pos=position_E)]).show() # long time
17386
17387
::
17388
17389
sage: g=graphs.HeawoodGraph()
17390
sage: g.is_isomorphic(g)
17391
True
17392
17393
Multigraphs::
17394
17395
sage: G = Graph(multiedges=True,sparse=True)
17396
sage: G.add_edge((0,1,1))
17397
sage: G.add_edge((0,1,2))
17398
sage: G.add_edge((0,1,3))
17399
sage: G.add_edge((0,1,4))
17400
sage: H = Graph(multiedges=True,sparse=True)
17401
sage: H.add_edge((3,4))
17402
sage: H.add_edge((3,4))
17403
sage: H.add_edge((3,4))
17404
sage: H.add_edge((3,4))
17405
sage: G.is_isomorphic(H)
17406
True
17407
17408
Digraphs::
17409
17410
sage: A = DiGraph( { 0 : [1,2] } )
17411
sage: B = DiGraph( { 1 : [0,2] } )
17412
sage: A.is_isomorphic(B, certify=True)
17413
(True, {0: 1, 1: 0, 2: 2})
17414
17415
Edge labeled graphs::
17416
17417
sage: G = Graph(sparse=True)
17418
sage: G.add_edges( [(0,1,'a'),(1,2,'b'),(2,3,'c'),(3,4,'b'),(4,0,'a')] )
17419
sage: H = G.relabel([1,2,3,4,0], inplace=False)
17420
sage: G.is_isomorphic(H, edge_labels=True)
17421
True
17422
17423
Edge labeled digraphs::
17424
17425
sage: G = DiGraph()
17426
sage: G.add_edges( [(0,1,'a'),(1,2,'b'),(2,3,'c'),(3,4,'b'),(4,0,'a')] )
17427
sage: H = G.relabel([1,2,3,4,0], inplace=False)
17428
sage: G.is_isomorphic(H, edge_labels=True)
17429
True
17430
sage: G.is_isomorphic(H, edge_labels=True, certify=True)
17431
(True, {0: 1, 1: 2, 2: 3, 3: 4, 4: 0})
17432
17433
TESTS::
17434
17435
sage: g1 = '~?A[~~{ACbCwV_~__OOcCW_fAA{CF{CCAAAC__bCCCwOOV___~____OOOOcCCCW___fAAAA'+\
17436
... '{CCCF{CCCCAAAAAC____bCCCCCwOOOOV_____~_O@ACG_@ACGOo@ACG?{?`A?GV_GO@AC}@?_OGC'+\
17437
... 'C?_OI@?K?I@?_OM?_OGD?F_A@OGC@{A@?_OG?O@?gCA?@_GCA@O?B_@OGCA?BoA@?gC?@{A?GO`?'+\
17438
... '??_GO@AC??E?O`?CG??[?O`A?G??{?GO`A???|A?_GOC`AC@_OCGACEAGS?HA?_SA`aO@G?cOC_N'+\
17439
... 'G_C@AOP?GnO@_GACOE?g?`OGACCOGaGOc?HA?`GORCG_AO@B?K@[`A?OCI@A@By?_K@?SCABA?H?'+\
17440
... 'SA?a@GC`CH?Q?C_c?cGRC@G_AOCOa@Ax?QC?_GOo_CNg@A?oC@CaCGO@CGA_O`?GSGPAGOC_@OO_'+\
17441
... 'aCHaG?cO@CB?_`Ax?GQC?_cAOCG^OGAC@_D?IGO`?D?O_I?HAOO`AGOHA?cC?oAO`AW_Q?HCACAC'+\
17442
... 'GO`[_OCHA?_cCACG^O_@CAGO`A?GCOGc@?I?OQOC?IGC_o@CAGCCE?A@DBG_OA@C_CP?OG_VA_CO'+\
17443
... 'G@D?_OA_DFgA@CO?aH?Ga@?a?_I?S@A@@Oa@?@P@GCO_AACO_a_?`K_GCQ@?cAOG_OGAwQ@?K?cC'+\
17444
... 'GH?I?ABy@C?G_S@@GCA@C`?OI?_D?OP@G?IGGP@O_AGCP?aG?GCPAX?cA?OGSGCGCAGCJ`?oAGCC'+\
17445
... 'HAA?A_CG^O@CAG_GCSCAGCCGOCG@OA_`?`?g_OACG_`CAGOAO_H?a_?`AXA?OGcAAOP?a@?CGVAC'+\
17446
... 'OG@_AGG`OA_?O`|?Ga?COKAAGCA@O`A?a?S@?HCG`?_?gO`AGGaC?PCAOGI?A@GO`K_CQ@?GO_`O'+\
17447
... 'GCAACGVAG@_COOCQ?g?I?O`ByC?G_P?O`A?H@G?_P?`OAGC?gD?_C@_GCAGDG_OA@CCPC?AOQ??g'+\
17448
... '_R@_AGCO____OCC_@OAbaOC?g@C_H?AOOC@?a`y?PC?G`@OOH??cOG_OOAG@_COAP?WA?_KAGC@C'+\
17449
... '_CQ@?HAACH??c@P?_AWGaC?P?gA_C_GAD?I?Awa?S@?K?`C_GAOGCS?@|?COGaA@CAAOQ?AGCAGO'+\
17450
... 'ACOG@_G_aC@_G@CA@@AHA?OGc?WAAH@G?P?_?cH_`CAGOGACc@@GA?S?CGVCG@OA_CICAOOC?PO?'+\
17451
... 'OG^OG_@CAC_cC?AOP?_OICG@?oAGCO_GO_GB@?_OG`AH?cA?OH?`P??cC_O?SCGR@O_AGCAI?Q?_'+\
17452
... 'GGS?D?O`[OI?_D@@CCA?cCA_?_O`By?_PC?IGAGOQ?@A@?aO`A?Q@?K?__`_E?_GCA@CGO`C_GCQ'+\
17453
... '@A?gAOQ?@C?DCACGR@GCO_AGPA@@GAA?A_CO`Aw_I?S@?SCB@?OC_?_P@ACNgOC@A?aCGOCAGCA@'+\
17454
... 'CA?H@GG_C@AOGa?OOG_O?g_OA?oDC_AO@GOCc?@P?_A@D??cC``O?cGAOGD?@OA_CAGCA?_cwKA?'+\
17455
... '`?OWGG?_PO?I?S?H@?^OGAC@_Aa@CAGC?a@?_Q?@H?_OCHA?OQA_P?_G_O?WA?_IG_Q?HC@A@ADC'+\
17456
... 'A?AI?AC_?QAWOHA?cAGG_I?S?G_OG@GA?`[D?O_IA?`GGCS?OA_?c@?Q?^OAC@_G_Ca@CA@?OGCO'+\
17457
... 'H@G@A@?GQC?_Q@GP?_OG?IGGB?OCGaG?cO@A__QGC?E?A@CH@G?GRAGOC_@GGOW@O?O_OGa?_c?G'+\
17458
... 'V@CGA_OOaC?a_?a?A_CcC@?CNgA?oC@GGE@?_OH?a@?_?QA`A@?QC?_KGGO_OGCAa@?A?_KCGPC@'+\
17459
... 'G_AOAGPGC?D@?a_A?@GGO`KH?Q?C_QGAA_?gOG_OA?_GG`AwH?SA?`?cAI?A@D?I?@?QA?`By?K@'+\
17460
... '?O`GGACA@CGCA@CC_?WO`?`A?OCH?`OCA@COG?I?oC@ACGPCG_AO@_aAA?Aa?g?GD@G?CO`AWOc?'+\
17461
... 'HA?OcG_?g@OGCAAAOC@ACJ_`OGACAGCS?CAGI?A`@?OCACG^'
17462
sage: g2 = '~?A[??osR?WARSETCJ_QWASehOXQg`QwChK?qSeFQ_sTIaWIV?XIR?KAC?B?`?COCG?o?O_'+\
17463
... '@_?`??B?`?o@_O_WCOCHC@_?`W?E?AD_O?WCCeO?WCSEGAGAIaA@_?aw?OK?ER?`?@_HQXA?B@Q_'+\
17464
... 'pA?a@Qg_`?o?h[?GOK@IR?@A?BEQcoCG?K\IB?GOCWiTC?GOKWIV??CGEKdH_H_?CB?`?DC??_WC'+\
17465
... 'G?SO?AP?O_?g_?D_?`?C__?D_?`?CCo??@_O_XDC???WCGEGg_??a?`G_aa??E?AD_@cC??K?CJ?'+\
17466
... '@@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'+\
17467
... 'SO`?P?hSO?@DCGOK?IV???K_`A@_HQWC??_cCG?KXIRG?@D?GO?WySEG?@D?GOCWiTCC??a_CGEK'+\
17468
... 'DJ_@??K_@A@bHQWAW?@@K??_WCG?g_?CSO?A@_O_@P??Gg_?Ca?`?@P??Gg_?D_?`?C__?EOO?Ao'+\
17469
... '?O_AAW?@@K???WCGEPP??Gg_??B?`?pDC??aa??AGACaAIG?@DC??K?CJ?BGG?@cC??K?CJ?@@K?'+\
17470
... '?_e?G?KAAR?PP??Gg_A?B?a_oAIG?@DC?OCOCTC?Gg_?CSO@?o?P[??X@??K__A@_?qW??OR??GH'+\
17471
... '`A?B?Qco?Gg_?CSO`?@_hOW?AIG?@DCGOCOITC??PP??Gg`A@_@Qw??@cC??qACGE?dH_O?AAW?@'+\
17472
... '@GGO?WqSeO?AIG?@D?GO?WySEG?@DC??a_CGAKTIaA??PP??Gg@A@b@Qw?O?BGG?@c?GOKXIR?KA'+\
17473
... 'C?H_?CCo?A@_O_?WCG@P??Gg_?CB?`?COCG@P??Gg_?Ca?`?E?AC?g_?CSO?Ao?O_@_?`@GG?@cC'+\
17474
... '??k?CG??WCGOR??GH_??B?`?o@_O`DC??aa???KACB?a?`AIG?@DC??COCHC@_?`AIG?@DC??K?C'+\
17475
... 'J??o?O`cC??qA??E?AD_O?WC?OR??GH_A?B?_cq?B?_AIG?@DC?O?WCSEGAGA?Gg_?CSO@?P?PSO'+\
17476
... 'OK?C?PP??Gg_A@_?aw?OK?C?X@??K__A@_?qWCG?K??GH_?CCo`?@_HQXA?B??AIG?@DCGO?WISE'+\
17477
... 'GOCO??PP??Gg`A?a@Qg_`?o??@DC??aaCGE?DJ_@A@_??BGG?@cCGOK@IR?@A?BO?AAW?@@GGO?W'+\
17478
... 'qSe?`?@g?@DC??a_CG?K\IB?GOCQ??PP??Gg@A?bDQg_@A@_O?AIG?@D?GOKWIV??CGE@??K__?E'+\
17479
... 'O?`?pchK?_SA_OI@OGD?gCA_SA@OI?c@H?Q?c_H?QOC_HGAOCc?QOC_HGAOCc@GAQ?c@H?QD?gCA'+\
17480
... '_SA@OI@?gD?_SA_OKA_SA@OI@?gD?_SA_OI@OHI?c_H?QOC_HGAOCc@GAQ?eC_H?QOC_HGAOCc@G'+\
17481
... 'AQ?c@XD?_SA_OI@OGD?gCA_SA@PKGO`A@ACGSGO`?`ACICGO_?ACGOcGO`?O`AC`ACHACGO???^?'+\
17482
... '????}Bw????Fo^???????Fo?}?????Bw?^?Bw?????GO`AO`AC`ACGACGOcGO`??aCGO_O`ADACG'+\
17483
... 'OGO`A@ACGOA???@{?N_@{?????Fo?}????OFo????N_}????@{????Bw?OACGOgO`A@ACGSGO`?`'+\
17484
... 'ACG?OaCGO_GO`AO`AC`ACGACGO_@G???Fo^?????}Bw????Fo??AC@{?????Fo?}?Fo?????^??A'+\
17485
... 'OGO`AO`AC@ACGQCGO_GO`A?HAACGOgO`A@ACGOGO`A`ACG?GQ??^?Bw?????N_@{?????Fo?QC??'+\
17486
... 'Fo^?????}????@{Fo???CHACGO_O`ACACGOgO`A@ACGO@AOcGO`?O`AC`ACGACGOcGO`?@GQFo??'+\
17487
... '??N_????^@{????Bw??`GRw?????N_@{?????Fo?}???HAO_OI@OGD?gCA_SA@OI@?gDK_??C@GA'+\
17488
... 'Q?c@H?Q?c_H?QOC_HEW????????????????????????~~~~~'
17489
sage: G1 = Graph(g1)
17490
sage: G2 = Graph(g2)
17491
sage: G1.is_isomorphic(G2)
17492
True
17493
17494
Ensure that isomorphic looped graphs with non-range vertex labels report
17495
correctly (:trac:`10814`, fixed by :trac:`8395`)::
17496
17497
sage: G1 = Graph([(0,1), (1,1)])
17498
sage: G2 = Graph([(0,2), (2,2)])
17499
sage: G1.is_isomorphic(G2)
17500
True
17501
sage: G = Graph(multiedges = True, loops = True)
17502
sage: H = Graph(multiedges = True, loops = True)
17503
sage: G.add_edges([(0,1,0),(1,0,1),(1,1,2),(0,0,3)])
17504
sage: H.add_edges([(0,1,3),(1,0,2),(1,1,1),(0,0,0)])
17505
sage: G.is_isomorphic(H, certify=True)
17506
(True, {0: 0, 1: 1})
17507
sage: set_random_seed(0)
17508
sage: D = digraphs.RandomDirectedGNP(6, .2)
17509
sage: D.is_isomorphic(D, certify = True)
17510
(True, {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5})
17511
sage: D.is_isomorphic(D,edge_labels=True, certify = True)
17512
(True, {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5})
17513
17514
Ensure that trac :trac:`11620` is fixed::
17515
17516
sage: G1 = DiGraph([(0, 0, 'c'), (0, 4, 'b'), (0, 5, 'c'),
17517
... (0, 5, 't'), (1, 1, 'c'), (1, 3,'c'), (1, 3, 't'), (1, 5, 'b'),
17518
... (2, 2, 'c'), (2, 3, 'b'), (2, 4, 'c'),(2, 4, 't'), (3, 1, 't'),
17519
... (3, 2, 'b'), (3, 2, 'c'), (3, 4, 'c'), (4, 0,'b'), (4, 0, 'c'),
17520
... (4, 2, 't'), (4, 5, 'c'), (5, 0, 't'), (5, 1, 'b'), (5, 1, 'c'),
17521
... (5, 3, 'c')], loops=True, multiedges=True)
17522
sage: G2 = G1.relabel({0:4, 1:5, 2:3, 3:2, 4:1,5:0}, inplace=False)
17523
sage: G1.canonical_label(edge_labels=True) == G2.canonical_label(edge_labels=True)
17524
True
17525
sage: G1.is_isomorphic(G2,edge_labels=True)
17526
True
17527
17528
Ensure that :trac:`13114` is fixed ::
17529
17530
sage: g = Graph([(0, 0, 0), (0, 2, 0), (1, 1, 0), (1, 2, 0), (1, 2, 1), (2, 2, 0)])
17531
sage: gg = Graph([(0, 0, 0), (0, 1, 0), (1, 1, 0), (1, 2, 0), (2, 2, 0), (2, 2, 1)])
17532
sage: g.is_isomorphic(gg)
17533
False
17534
17535
Ensure that trac:`14777` is fixed ::
17536
17537
sage: g = Graph()
17538
sage: h = Graph()
17539
sage: g.is_isomorphic(h)
17540
True
17541
"""
17542
from sage.groups.perm_gps.partn_ref.refinement_graphs import isomorphic
17543
possible = True
17544
if self.num_verts() == 0 and other.num_verts() == 0:
17545
return True
17546
if self._directed != other._directed:
17547
possible = False
17548
if self.order() != other.order():
17549
possible = False
17550
if self.size() != other.size():
17551
possible = False
17552
if not possible and certify:
17553
return False, None
17554
elif not possible:
17555
return False
17556
self_vertices = self.vertices()
17557
other_vertices = other.vertices()
17558
if edge_labels or self.has_multiple_edges():
17559
if edge_labels and sorted(self.edge_labels()) != sorted(other.edge_labels()):
17560
return (False, None) if certify else False
17561
else:
17562
G, partition, relabeling, G_edge_labels = graph_isom_equivalent_non_edge_labeled_graph(self, return_relabeling=True, ignore_edge_labels=(not edge_labels), return_edge_labels=True)
17563
self_vertices = sum(partition,[])
17564
G2, partition2, relabeling2, G2_edge_labels = graph_isom_equivalent_non_edge_labeled_graph(other, return_relabeling=True, ignore_edge_labels=(not edge_labels), return_edge_labels=True)
17565
if map(len, partition) != map(len, partition2):
17566
return (False, None) if certify else False
17567
multilabel = (lambda e:e) if edge_labels else (lambda e:map(lambda el: [None, el[1]], e))
17568
if map(multilabel, G_edge_labels) != map(multilabel, G2_edge_labels):
17569
return (False, None) if certify else False
17570
partition2 = sum(partition2,[])
17571
other_vertices = partition2
17572
else:
17573
G = self; partition = [self_vertices]
17574
G2 = other; partition2 = other_vertices
17575
G_to = {}
17576
for i in xrange(len(self_vertices)):
17577
G_to[self_vertices[i]] = i
17578
from sage.graphs.all import Graph, DiGraph
17579
DoDG = DiGraph if self._directed else Graph
17580
H = DoDG(len(self_vertices), implementation='c_graph', loops=G.allows_loops())
17581
HB = H._backend
17582
for u,v in G.edge_iterator(labels=False):
17583
u = G_to[u]; v = G_to[v]
17584
HB.add_edge(u,v,None,G._directed)
17585
G = HB._cg
17586
partition = [[G_to[v] for v in cell] for cell in partition]
17587
GC = G
17588
G2_to = {}
17589
for i in xrange(len(other_vertices)):
17590
G2_to[other_vertices[i]] = i
17591
H2 = DoDG(len(other_vertices), implementation='c_graph', loops=G2.allows_loops())
17592
H2B = H2._backend
17593
for u,v in G2.edge_iterator(labels=False):
17594
u = G2_to[u]; v = G2_to[v]
17595
H2B.add_edge(u,v,None,G2._directed)
17596
G2 = H2B._cg
17597
partition2 = [G2_to[v] for v in partition2]
17598
GC2 = G2
17599
isom = isomorphic(GC, GC2, partition, partition2, (self._directed or self.has_loops()), 1)
17600
17601
if not isom and certify:
17602
return False, None
17603
elif not isom:
17604
return False
17605
elif not certify:
17606
return True
17607
else:
17608
isom_trans = {}
17609
if edge_labels:
17610
relabeling2_inv = {}
17611
for x in relabeling2:
17612
relabeling2_inv[relabeling2[x]] = x
17613
for v in self.vertices():
17614
isom_trans[v] = relabeling2_inv[other_vertices[isom[G_to[relabeling[v]]]]]
17615
else:
17616
for v in self.vertices():
17617
isom_trans[v] = other_vertices[isom[G_to[v]]]
17618
return True, isom_trans
17619
17620
def canonical_label(self, partition=None, certify=False, verbosity=0, edge_labels=False):
17621
"""
17622
Returns the unique graph on `\{0,1,...,n-1\}` ( ``n = self.order()`` ) which
17623
17624
- is isomorphic to self,
17625
17626
- is invariant in the isomorphism class.
17627
17628
In other words, given two graphs ``G`` and ``H`` which are isomorphic,
17629
suppose ``G_c`` and ``H_c`` are the graphs returned by
17630
``canonical_label``. Then the following hold:
17631
17632
- ``G_c == H_c``
17633
17634
- ``G_c.adjacency_matrix() == H_c.adjacency_matrix()``
17635
17636
- ``G_c.graph6_string() == H_c.graph6_string()``
17637
17638
INPUT:
17639
17640
- ``partition`` - if given, the canonical label with
17641
respect to this set partition will be computed. The default is the unit
17642
set partition.
17643
17644
- ``certify`` - if True, a dictionary mapping from the
17645
(di)graph to its canonical label will be given.
17646
17647
- ``verbosity`` - gets passed to nice: prints helpful
17648
output.
17649
17650
- ``edge_labels`` - default False, otherwise allows
17651
only permutations respecting edge labels.
17652
17653
17654
EXAMPLES::
17655
17656
sage: D = graphs.DodecahedralGraph()
17657
sage: E = D.canonical_label(); E
17658
Dodecahedron: Graph on 20 vertices
17659
sage: D.canonical_label(certify=True)
17660
(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})
17661
sage: D.is_isomorphic(E)
17662
True
17663
17664
Multigraphs::
17665
17666
sage: G = Graph(multiedges=True,sparse=True)
17667
sage: G.add_edge((0,1))
17668
sage: G.add_edge((0,1))
17669
sage: G.add_edge((0,1))
17670
sage: G.canonical_label()
17671
Multi-graph on 2 vertices
17672
sage: Graph('A?', implementation='c_graph').canonical_label()
17673
Graph on 2 vertices
17674
17675
Digraphs::
17676
17677
sage: P = graphs.PetersenGraph()
17678
sage: DP = P.to_directed()
17679
sage: DP.canonical_label().adjacency_matrix()
17680
[0 0 0 0 0 0 0 1 1 1]
17681
[0 0 0 0 1 0 1 0 0 1]
17682
[0 0 0 1 0 0 1 0 1 0]
17683
[0 0 1 0 0 1 0 0 0 1]
17684
[0 1 0 0 0 1 0 0 1 0]
17685
[0 0 0 1 1 0 0 1 0 0]
17686
[0 1 1 0 0 0 0 1 0 0]
17687
[1 0 0 0 0 1 1 0 0 0]
17688
[1 0 1 0 1 0 0 0 0 0]
17689
[1 1 0 1 0 0 0 0 0 0]
17690
17691
Edge labeled graphs::
17692
17693
sage: G = Graph(sparse=True)
17694
sage: G.add_edges( [(0,1,'a'),(1,2,'b'),(2,3,'c'),(3,4,'b'),(4,0,'a')] )
17695
sage: G.canonical_label(edge_labels=True)
17696
Graph on 5 vertices
17697
sage: G.canonical_label(edge_labels=True,certify=True)
17698
(Graph on 5 vertices, {0: 4, 1: 3, 2: 0, 3: 1, 4: 2})
17699
"""
17700
from sage.groups.perm_gps.partn_ref.refinement_graphs import search_tree
17701
from copy import copy
17702
17703
dig = (self.has_loops() or self._directed)
17704
if partition is None:
17705
partition = [self.vertices()]
17706
if edge_labels or self.has_multiple_edges():
17707
G, partition, relabeling = graph_isom_equivalent_non_edge_labeled_graph(self, partition, return_relabeling=True)
17708
G_vertices = sum(partition, [])
17709
G_to = {}
17710
for i in xrange(len(G_vertices)):
17711
G_to[G_vertices[i]] = i
17712
from sage.graphs.all import Graph, DiGraph
17713
DoDG = DiGraph if self._directed else Graph
17714
H = DoDG(len(G_vertices), implementation='c_graph', loops=G.allows_loops())
17715
HB = H._backend
17716
for u,v in G.edge_iterator(labels=False):
17717
u = G_to[u]; v = G_to[v]
17718
HB.add_edge(u,v,None,G._directed)
17719
GC = HB._cg
17720
partition = [[G_to[v] for v in cell] for cell in partition]
17721
a,b,c = search_tree(GC, partition, certify=True, dig=dig, verbosity=verbosity)
17722
# c is a permutation to the canonical label of G, which depends only on isomorphism class of self.
17723
H = copy(self)
17724
c_new = {}
17725
for v in self.vertices():
17726
c_new[v] = c[G_to[relabeling[v]]]
17727
H.relabel(c_new)
17728
if certify:
17729
return H, c_new
17730
else:
17731
return H
17732
G_vertices = sum(partition, [])
17733
G_to = {}
17734
for i in xrange(len(G_vertices)):
17735
G_to[G_vertices[i]] = i
17736
from sage.graphs.all import Graph, DiGraph
17737
DoDG = DiGraph if self._directed else Graph
17738
H = DoDG(len(G_vertices), implementation='c_graph', loops=self.allows_loops())
17739
HB = H._backend
17740
for u,v in self.edge_iterator(labels=False):
17741
u = G_to[u]; v = G_to[v]
17742
HB.add_edge(u,v,None,self._directed)
17743
GC = HB._cg
17744
partition = [[G_to[v] for v in cell] for cell in partition]
17745
a,b,c = search_tree(GC, partition, certify=True, dig=dig, verbosity=verbosity)
17746
H = copy(self)
17747
c_new = {}
17748
for v in G_to:
17749
c_new[v] = c[G_to[v]]
17750
H.relabel(c_new)
17751
if certify:
17752
return H, c_new
17753
else:
17754
return H
17755
17756
import types
17757
17758
import sage.graphs.distances_all_pairs
17759
GenericGraph.distances_distribution = types.MethodType(sage.graphs.distances_all_pairs.distances_distribution, None, GenericGraph)
17760
GenericGraph.wiener_index = types.MethodType(sage.graphs.distances_all_pairs.wiener_index, None, GenericGraph)
17761
17762
# From Python modules
17763
import sage.graphs.line_graph
17764
GenericGraph.line_graph = sage.graphs.line_graph.line_graph
17765
17766
def tachyon_vertex_plot(g, bgcolor=(1,1,1),
17767
vertex_colors=None,
17768
vertex_size=0.06,
17769
pos3d=None,
17770
**kwds):
17771
"""
17772
Helper function for plotting graphs in 3d with Tachyon. Returns a
17773
plot containing only the vertices, as well as the 3d position
17774
dictionary used for the plot.
17775
17776
INPUT:
17777
- `pos3d` - a 3D layout of the vertices
17778
- various rendering options
17779
17780
EXAMPLES::
17781
17782
sage: G = graphs.TetrahedralGraph()
17783
sage: from sage.graphs.generic_graph import tachyon_vertex_plot
17784
sage: T,p = tachyon_vertex_plot(G, pos3d = G.layout(dim=3))
17785
sage: type(T)
17786
<class 'sage.plot.plot3d.tachyon.Tachyon'>
17787
sage: type(p)
17788
<type 'dict'>
17789
"""
17790
assert pos3d is not None
17791
from math import sqrt
17792
from sage.plot.plot3d.tachyon import Tachyon
17793
17794
c = [0,0,0]
17795
r = []
17796
verts = g.vertices()
17797
17798
if vertex_colors is None:
17799
vertex_colors = { (1,0,0) : verts }
17800
try:
17801
for v in verts:
17802
c[0] += pos3d[v][0]
17803
c[1] += pos3d[v][1]
17804
c[2] += pos3d[v][2]
17805
except KeyError:
17806
raise KeyError("Oops! You haven't specified positions for all the vertices.")
17807
17808
order = g.order()
17809
c[0] = c[0]/order
17810
c[1] = c[1]/order
17811
c[2] = c[2]/order
17812
for v in verts:
17813
pos3d[v][0] = pos3d[v][0] - c[0]
17814
pos3d[v][1] = pos3d[v][1] - c[1]
17815
pos3d[v][2] = pos3d[v][2] - c[2]
17816
r.append(abs(sqrt((pos3d[v][0])**2 + (pos3d[v][1])**2 + (pos3d[v][2])**2)))
17817
r = max(r)
17818
if r == 0:
17819
r = 1
17820
for v in verts:
17821
pos3d[v][0] = pos3d[v][0]/r
17822
pos3d[v][1] = pos3d[v][1]/r
17823
pos3d[v][2] = pos3d[v][2]/r
17824
TT = Tachyon(camera_center=(1.4,1.4,1.4), antialiasing=13, **kwds)
17825
TT.light((4,3,2), 0.02, (1,1,1))
17826
TT.texture('bg', ambient=1, diffuse=1, specular=0, opacity=1.0, color=bgcolor)
17827
TT.plane((-1.6,-1.6,-1.6), (1.6,1.6,1.6), 'bg')
17828
17829
i = 0
17830
for color in vertex_colors:
17831
i += 1
17832
TT.texture('node_color_%d'%i, ambient=0.1, diffuse=0.9,
17833
specular=0.03, opacity=1.0, color=color)
17834
for v in vertex_colors[color]:
17835
TT.sphere((pos3d[v][0],pos3d[v][1],pos3d[v][2]), vertex_size, 'node_color_%d'%i)
17836
17837
return TT, pos3d
17838
17839
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):
17840
"""
17841
Helper function for canonical labeling of edge labeled (di)graphs.
17842
17843
Translates to a bipartite incidence-structure type graph
17844
appropriate for computing canonical labels of edge labeled and/or multi-edge graphs.
17845
Note that this is actually computationally equivalent to
17846
implementing a change on an inner loop of the main algorithm-
17847
namely making the refinement procedure sort for each label.
17848
17849
If the graph is a multigraph, it is translated to a non-multigraph,
17850
where each edge is labeled with a dictionary describing how many
17851
edges of each label were originally there. Then in either case we
17852
are working on a graph without multiple edges. At this point, we
17853
create another (bipartite) graph, whose left vertices are the
17854
original vertices of the graph, and whose right vertices represent
17855
the edges. We partition the left vertices as they were originally,
17856
and the right vertices by common labels: only automorphisms taking
17857
edges to like-labeled edges are allowed, and this additional
17858
partition information enforces this on the bipartite graph.
17859
17860
INPUT:
17861
17862
- ``g`` -- Graph or DiGraph
17863
- ``partition`` -- (default:None) if given, the partition of the vertices is as well relabeled
17864
- ``standard_label`` -- (default:None) the standard label is not considered to be changed
17865
- ``return_relabeling`` -- (defaut:False) if True, a dictionary containing the relabeling is returned
17866
- ``return_edge_labels`` -- (defaut:False) if True, the different edge_labels are returned (useful if inplace is True)
17867
- ``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.
17868
17869
OUTPUT:
17870
17871
- if not inplace: the unlabeled graph without multiple edges
17872
- the partition of the vertices
17873
- if return_relabeling: a dictionary containing the relabeling
17874
- if return_edge_labels: the list of (former) edge labels is returned
17875
17876
EXAMPLES::
17877
17878
sage: from sage.graphs.generic_graph import graph_isom_equivalent_non_edge_labeled_graph
17879
17880
sage: G = Graph(multiedges=True,sparse=True)
17881
sage: G.add_edges( (0,1,i) for i in range(10) )
17882
sage: G.add_edge(1,2,'string')
17883
sage: G.add_edge(2,123)
17884
sage: g = graph_isom_equivalent_non_edge_labeled_graph(G, partition=[[0,123],[1,2]]); g
17885
[Graph on 6 vertices, [[0, 3], [1, 2], [4], [5]]]
17886
17887
sage: g = graph_isom_equivalent_non_edge_labeled_graph(G); g
17888
[Graph on 6 vertices, [[0, 1, 2, 3], [4], [5]]]
17889
sage: g[0].edges()
17890
[(0, 4, None), (1, 4, None), (1, 5, None), (2, 3, None), (2, 5, None)]
17891
17892
sage: g = graph_isom_equivalent_non_edge_labeled_graph(G,standard_label='string',return_edge_labels=True); g
17893
[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]]]]
17894
sage: g[0].edges()
17895
[(0, 4, None), (1, 2, None), (1, 4, None), (2, 5, None), (3, 5, None)]
17896
17897
sage: graph_isom_equivalent_non_edge_labeled_graph(G,inplace=True)
17898
[[[0, 1, 2, 3], [4], [5]]]
17899
sage: G.edges()
17900
[(0, 4, None), (1, 4, None), (1, 5, None), (2, 3, None), (2, 5, None)]
17901
17902
Ensure that #14108 is fixed::
17903
17904
sage: G=DiGraph([[0,0],[0,0],[0,0],[1,1],[1,1],[1,1]])
17905
sage: H=DiGraph([[0,0],[0,0],[0,0],[0,0],[1,1],[1,1]])
17906
sage: G.is_isomorphic(H)
17907
False
17908
sage: H=DiGraph([[0,0],[0,0],[0,0],[0,0],[0,0],[1,1],[1,1]])
17909
sage: HH=DiGraph([[0,0],[0,0],[0,0],[0,0],[1,1],[1,1],[1,1]])
17910
sage: H.is_isomorphic(HH)
17911
False
17912
sage: H.is_isomorphic(HH, edge_labels=True)
17913
False
17914
17915
"""
17916
from copy import copy
17917
from sage.graphs.all import Graph, DiGraph
17918
17919
g_has_multiple_edges = g.has_multiple_edges()
17920
17921
if g_has_multiple_edges:
17922
if g._directed:
17923
G = DiGraph(loops=g.allows_loops(),sparse=True)
17924
edge_iter = g._backend.iterator_in_edges(g,True)
17925
else:
17926
G = Graph(loops=g.allows_loops(),sparse=True)
17927
edge_iter = g._backend.iterator_edges(g,True)
17928
for u,v,l in edge_iter:
17929
if ignore_edge_labels:
17930
l = None
17931
if not G.has_edge(u,v):
17932
G.add_edge(u,v,[[l,1]])
17933
else:
17934
label_list = copy( G.edge_label(u,v) )
17935
seen_label = False
17936
for i in xrange(len(label_list)):
17937
if label_list[i][0] == l:
17938
label_list[i][1] += 1
17939
G.set_edge_label(u,v,label_list)
17940
seen_label = True
17941
break
17942
if not seen_label:
17943
label_list.append([l,1])
17944
label_list.sort()
17945
G.set_edge_label(u,v,label_list)
17946
if G.order() < g.order():
17947
G.add_vertices(g)
17948
if inplace:
17949
g._backend = G._backend
17950
elif not inplace:
17951
G = copy( g )
17952
else:
17953
G = g
17954
17955
G_order = G.order()
17956
V = range(G_order)
17957
if G.vertices() != V:
17958
relabel_dict = G.relabel(return_map=True)
17959
else:
17960
relabel_dict = dict( (i,i) for i in xrange(G_order) )
17961
if partition is None:
17962
partition = [V]
17963
else:
17964
partition = [ [ relabel_dict[i] for i in part ] for part in partition ]
17965
17966
if G._directed:
17967
edge_iter = G._backend.iterator_in_edges(G,True)
17968
else:
17969
edge_iter = G._backend.iterator_edges(G,True)
17970
17971
edges = [ edge for edge in edge_iter ]
17972
edge_labels = sorted([ label for v1,v2,label in edges if not label == standard_label])
17973
i = 1
17974
17975
# edge_labels is sorted. We now remove values which are not unique
17976
while i < len(edge_labels):
17977
if edge_labels[i] == edge_labels[i-1]:
17978
edge_labels.pop(i)
17979
else:
17980
i += 1
17981
i = G_order
17982
edge_partition = [(el,[]) for el in edge_labels]
17983
17984
if g_has_multiple_edges: standard_label = [[standard_label,1]]
17985
17986
for u,v,l in edges:
17987
if not l == standard_label:
17988
for el, part in edge_partition:
17989
if el == l:
17990
part.append(i)
17991
break
17992
17993
G._backend.add_edge(u,i,None,True)
17994
G._backend.add_edge(i,v,None,True)
17995
G.delete_edge(u,v)
17996
i += 1
17997
elif standard_label is not None:
17998
G._backend.set_edge_label(u,v,None,True)
17999
18000
# Should we pay attention to edge labels ?
18001
if ignore_edge_labels:
18002
18003
# If there are no multiple edges, we can just say that all edges are
18004
# equivalent to each other without any further consideration.
18005
if not g_has_multiple_edges:
18006
edge_partition = [el[1] for el in sorted(edge_partition)]
18007
edge_partition = [sum(edge_partition,[])]
18008
18009
# An edge between u and v with label l and multiplicity k being encoded
18010
# as an uv edge with label [l,k], we must not assume that an edge with
18011
# multiplicity 2 is equivalent to a simple edge !
18012
# Hence, we still distinguish edges with different multiplicity
18013
if g_has_multiple_edges:
18014
18015
# Compute the multiplicity the label
18016
multiplicity = lambda x : sum(map(lambda y:y[1],x))
18017
18018
# Sort the edge according to their multiplicity
18019
edge_partition = sorted([[multiplicity(el),part] for el, part in sorted(edge_partition)])
18020
18021
# Gather together the edges with same multiplicity
18022
i = 1
18023
while i < len(edge_partition):
18024
if edge_partition[i][0] == edge_partition[i-1][0]:
18025
edge_partition[i-1][1].extend(edge_partition[i][1])
18026
edge_partition.pop(i)
18027
else:
18028
i += 1
18029
18030
# now edge_partition has shape [[multiplicity, list_of_edges],
18031
# [multiplicity, liste of edges], ...], and we can flatted it to
18032
# [list of edges, list of edges, ...]
18033
edge_partition = [el[1] for el in sorted(edge_partition)]
18034
18035
# Now the edges are partitionned according to the multiplicity they
18036
# represent, and edge labels are forgotten.
18037
18038
else:
18039
edge_partition = [el[1] for el in sorted(edge_partition)]
18040
18041
new_partition = [ part for part in partition + edge_partition if not part == [] ]
18042
18043
return_data = []
18044
if not inplace:
18045
return_data.append( G )
18046
return_data.append( new_partition )
18047
if return_relabeling:
18048
return_data.append( relabel_dict )
18049
if return_edge_labels:
18050
return_data.append( edge_labels )
18051
return return_data
18052
18053