Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/graphs/graph.py
8815 views
1
r"""
2
Undirected graphs
3
4
This module implements functions and operations involving undirected
5
graphs.
6
7
**Graph basic operations:**
8
9
.. csv-table::
10
:class: contentstable
11
:widths: 30, 70
12
:delim: |
13
14
:meth:`~Graph.write_to_eps` | Writes a plot of the graph to ``filename`` in ``eps`` format.
15
:meth:`~Graph.to_undirected` | Since the graph is already undirected, simply returns a copy of itself.
16
:meth:`~Graph.to_directed` | Returns a directed version of the graph.
17
:meth:`~Graph.sparse6_string` | Returns the sparse6 representation of the graph as an ASCII string.
18
:meth:`~Graph.graph6_string` | Returns the graph6 representation of the graph as an ASCII string.
19
:meth:`~Graph.bipartite_sets` | Returns `(X,Y)` where X and Y are the nodes in each bipartite set of graph.
20
:meth:`~Graph.bipartite_color` | Returns a dictionary with vertices as the keys and the color class as the values.
21
:meth:`~Graph.is_directed` | Since graph is undirected, returns False.
22
:meth:`~Graph.join` | Returns the join of self and other.
23
24
25
**Distances:**
26
27
.. csv-table::
28
:class: contentstable
29
:widths: 30, 70
30
:delim: |
31
32
:meth:`~Graph.centrality_closeness` | Returns the closeness centrality (1/average distance to all vertices)
33
:meth:`~Graph.centrality_degree` | Returns the degree centrality
34
:meth:`~Graph.centrality_betweenness` | Returns the betweenness centrality
35
36
37
**Graph properties:**
38
39
.. csv-table::
40
:class: contentstable
41
:widths: 30, 70
42
:delim: |
43
44
:meth:`~Graph.is_prime` | Tests whether the current graph is prime.
45
:meth:`~Graph.is_split` | Returns ``True`` if the graph is a Split graph, ``False`` otherwise.
46
:meth:`~Graph.is_triangle_free` | Returns whether ``self`` is triangle-free.
47
:meth:`~Graph.is_bipartite` | Returns True if graph G is bipartite, False if not.
48
:meth:`~Graph.is_line_graph` | Tests wether the graph is a line graph.
49
:meth:`~Graph.is_odd_hole_free` | Tests whether ``self`` contains an induced odd hole.
50
:meth:`~Graph.is_even_hole_free` | Tests whether ``self`` contains an induced even hole.
51
:meth:`~Graph.is_cartesian_product` | Tests whether ``self`` is a cartesian product of graphs.
52
:meth:`~Graph.is_long_hole_free` | Tests whether ``self`` contains an induced cycle of length at least 5.
53
:meth:`~Graph.is_long_antihole_free` | Tests whether ``self`` contains an induced anticycle of length at least 5.
54
:meth:`~Graph.is_weakly_chordal` | Tests whether ``self`` is weakly chordal.
55
:meth:`~Graph.is_strongly_regular` | Tests whether ``self`` is strongly regular.
56
:meth:`~Graph.is_distance_regular` | Tests whether ``self`` is distance-regular.
57
:meth:`~Graph.is_tree` | Return True if the graph is a tree.
58
:meth:`~Graph.is_forest` | Return True if the graph is a forest, i.e. a disjoint union of trees.
59
:meth:`~Graph.is_overfull` | Tests whether the current graph is overfull.
60
:meth:`~Graph.odd_girth` | Returns the odd girth of ``self``.
61
:meth:`~Graph.is_edge_transitive` | Returns true if self is edge-transitive.
62
:meth:`~Graph.is_arc_transitive` | Returns true if self is arc-transitive.
63
:meth:`~Graph.is_half_transitive` | Returns true if self is a half-transitive graph.
64
:meth:`~Graph.is_semi_symmetric` | Returns true if self is a semi-symmetric graph.
65
66
**Connectivity and orientations:**
67
68
.. csv-table::
69
:class: contentstable
70
:widths: 30, 70
71
:delim: |
72
73
:meth:`~Graph.gomory_hu_tree` | Returns a Gomory-Hu tree of self.
74
:meth:`~Graph.minimum_outdegree_orientation` | Returns an orientation of ``self`` with the smallest possible maximum outdegree
75
:meth:`~Graph.bounded_outdegree_orientation` | Computes an orientation of ``self`` such that every vertex `v` has out-degree less than `b(v)`
76
:meth:`~Graph.strong_orientation` | Returns a strongly connected orientation of the current graph.
77
:meth:`~Graph.degree_constrained_subgraph` | Returns a degree-constrained subgraph.
78
79
**Clique-related methods:**
80
81
.. csv-table::
82
:class: contentstable
83
:widths: 30, 70
84
:delim: |
85
86
:meth:`~Graph.clique_complex` | Returns the clique complex of self
87
:meth:`~Graph.cliques_containing_vertex` | Returns the cliques containing each vertex
88
:meth:`~Graph.cliques_vertex_clique_number` | Returns a dictionary of sizes of the largest maximal cliques containing each vertex
89
:meth:`~Graph.cliques_get_clique_bipartite` | Returns a bipartite graph constructed such that maximal cliques are the right vertices and the left vertices are retained from the given graph
90
:meth:`~Graph.cliques_get_max_clique_graph` | Returns a graph constructed with maximal cliques as vertices, and edges between maximal cliques sharing vertices.
91
:meth:`~Graph.cliques_number_of` | Returns a dictionary of the number of maximal cliques containing each vertex, keyed by vertex.
92
:meth:`~Graph.clique_number` | Returns the order of the largest clique of the graph.
93
:meth:`~Graph.clique_maximum` | Returns the vertex set of a maximal order complete subgraph.
94
:meth:`~Graph.cliques_maximum` | Returns the list of all maximum cliques
95
:meth:`~Graph.cliques_maximal` | Returns the list of all maximal cliques
96
97
98
**Algorithmically hard stuff:**
99
100
.. csv-table::
101
:class: contentstable
102
:widths: 30, 70
103
:delim: |
104
105
:meth:`~Graph.vertex_cover` | Returns a minimum vertex cover of self
106
:meth:`~Graph.independent_set` | Returns a maximum independent set.
107
:meth:`~Graph.topological_minor` | Returns a topological `H`-minor from ``self`` if one exists.
108
:meth:`~Graph.convexity_properties` | Returns a ``ConvexityProperties`` objet corresponding to ``self``.
109
:meth:`~Graph.matching_polynomial` | Computes the matching polynomial of the graph `G`.
110
:meth:`~Graph.rank_decomposition` | Returns an rank-decomposition of ``self`` achieving optiml rank-width.
111
:meth:`~Graph.minor` | Returns the vertices of a minor isomorphic to `H` in the current graph.
112
:meth:`~Graph.independent_set_of_representatives` | Returns an independent set of representatives.
113
:meth:`~Graph.coloring` | Returns the first (optimal) proper vertex-coloring found.
114
:meth:`~Graph.has_homomorphism_to` | Checks whether there is a morphism between two graphs.
115
:meth:`~Graph.chromatic_number` | Returns the minimal number of colors needed to color the vertices of the graph.
116
:meth:`~Graph.chromatic_polynomial` | Returns the chromatic polynomial of the graph.
117
:meth:`~Graph.tutte_polynomial` | Returns the Tutte polynomial of the graph.
118
:meth:`~Graph.is_perfect` | Tests whether the graph is perfect.
119
120
121
122
**Leftovers:**
123
124
.. csv-table::
125
:class: contentstable
126
:widths: 30, 70
127
:delim: |
128
129
:meth:`~Graph.cores` | Returns the core number for each vertex in an ordered list.
130
:meth:`~Graph.matching` | Returns a maximum weighted matching of the graph
131
:meth:`~Graph.fractional_chromatic_index` | Computes the fractional chromatic index of ``self``
132
:meth:`~Graph.kirchhoff_symanzik_polynomial` | Returns the Kirchhoff-Symanzik polynomial of the graph.
133
:meth:`~Graph.modular_decomposition` | Returns the modular decomposition of the current graph.
134
:meth:`~Graph.maximum_average_degree` | Returns the Maximum Average Degree (MAD) of the current graph.
135
:meth:`~Graph.two_factor_petersen` | Returns a decomposition of the graph into 2-factors.
136
:meth:`~Graph.ihara_zeta_function_inverse` | Returns the inverse of the zeta function of the graph.
137
138
AUTHORS:
139
140
- Robert L. Miller (2006-10-22): initial version
141
142
- William Stein (2006-12-05): Editing
143
144
- Robert L. Miller (2007-01-13): refactoring, adjusting for
145
NetworkX-0.33, fixed plotting bugs (2007-01-23): basic tutorial,
146
edge labels, loops, multiple edges and arcs (2007-02-07): graph6
147
and sparse6 formats, matrix input
148
149
- Emily Kirkmann (2007-02-11): added graph_border option to plot
150
and show
151
152
- Robert L. Miller (2007-02-12): vertex color-maps, graph
153
boundaries, graph6 helper functions in Cython
154
155
- Robert L. Miller Sage Days 3 (2007-02-17-21): 3d plotting in
156
Tachyon
157
158
- Robert L. Miller (2007-02-25): display a partition
159
160
- Robert L. Miller (2007-02-28): associate arbitrary objects to
161
vertices, edge and arc label display (in 2d), edge coloring
162
163
- Robert L. Miller (2007-03-21): Automorphism group, isomorphism
164
check, canonical label
165
166
- Robert L. Miller (2007-06-07-09): NetworkX function wrapping
167
168
- Michael W. Hansen (2007-06-09): Topological sort generation
169
170
- Emily Kirkman, Robert L. Miller Sage Days 4: Finished wrapping
171
NetworkX
172
173
- Emily Kirkman (2007-07-21): Genus (including circular planar,
174
all embeddings and all planar embeddings), all paths, interior
175
paths
176
177
- Bobby Moretti (2007-08-12): fixed up plotting of graphs with
178
edge colors differentiated by label
179
180
- Jason Grout (2007-09-25): Added functions, bug fixes, and
181
general enhancements
182
183
- Robert L. Miller (Sage Days 7): Edge labeled graph isomorphism
184
185
- Tom Boothby (Sage Days 7): Miscellaneous awesomeness
186
187
- Tom Boothby (2008-01-09): Added graphviz output
188
189
- David Joyner (2009-2): Fixed docstring bug related to GAP.
190
191
- Stephen Hartke (2009-07-26): Fixed bug in blocks_and_cut_vertices()
192
that caused an incorrect result when the vertex 0 was a cut vertex.
193
194
- Stephen Hartke (2009-08-22): Fixed bug in blocks_and_cut_vertices()
195
where the list of cut_vertices is not treated as a set.
196
197
- Anders Jonsson (2009-10-10): Counting of spanning trees and out-trees added.
198
199
- Nathann Cohen (2009-09) : Cliquer, Connectivity, Flows
200
and everything that uses Linear Programming
201
and class numerical.MIP
202
203
- Nicolas M. Thiery (2010-02): graph layout code refactoring, dot2tex/graphviz interface
204
205
- David Coudert (2012-04) : Reduction rules in vertex_cover.
206
207
- Birk Eisermann (2012-06): added recognition of weakly chordal graphs and
208
long-hole-free / long-antihole-free graphs
209
210
- Alexandre P. Zuge (2013-07): added join operation.
211
212
213
Graph Format
214
------------
215
216
Supported formats
217
~~~~~~~~~~~~~~~~~
218
219
Sage Graphs can be created from a wide range of inputs. A few
220
examples are covered here.
221
222
223
- NetworkX dictionary format:
224
225
::
226
227
sage: d = {0: [1,4,5], 1: [2,6], 2: [3,7], 3: [4,8], 4: [9], \
228
5: [7, 8], 6: [8,9], 7: [9]}
229
sage: G = Graph(d); G
230
Graph on 10 vertices
231
sage: G.plot().show() # or G.show()
232
233
- A NetworkX graph:
234
235
::
236
237
sage: import networkx
238
sage: K = networkx.complete_bipartite_graph(12,7)
239
sage: G = Graph(K)
240
sage: G.degree()
241
[7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 12, 12, 12, 12, 12, 12, 12]
242
243
- graph6 or sparse6 format:
244
245
::
246
247
sage: s = ':I`AKGsaOs`cI]Gb~'
248
sage: G = Graph(s, sparse=True); G
249
Looped multi-graph on 10 vertices
250
sage: G.plot().show() # or G.show()
251
252
Note that the ``\`` character is an escape character in Python, and
253
also a character used by graph6 strings:
254
255
::
256
257
sage: G = Graph('Ihe\n@GUA')
258
Traceback (most recent call last):
259
...
260
RuntimeError: The string (Ihe) seems corrupt: for n = 10, the string is too short.
261
262
In Python, the escaped character ``\`` is represented by ``\\``:
263
264
::
265
266
sage: G = Graph('Ihe\\n@GUA')
267
sage: G.plot().show() # or G.show()
268
269
- adjacency matrix: In an adjacency matrix, each column and each
270
row represent a vertex. If a 1 shows up in row `i`, column
271
`j`, there is an edge `(i,j)`.
272
273
::
274
275
sage: M = Matrix([(0,1,0,0,1,1,0,0,0,0),(1,0,1,0,0,0,1,0,0,0), \
276
(0,1,0,1,0,0,0,1,0,0), (0,0,1,0,1,0,0,0,1,0),(1,0,0,1,0,0,0,0,0,1), \
277
(1,0,0,0,0,0,0,1,1,0), (0,1,0,0,0,0,0,0,1,1),(0,0,1,0,0,1,0,0,0,1), \
278
(0,0,0,1,0,1,1,0,0,0), (0,0,0,0,1,0,1,1,0,0)])
279
sage: M
280
[0 1 0 0 1 1 0 0 0 0]
281
[1 0 1 0 0 0 1 0 0 0]
282
[0 1 0 1 0 0 0 1 0 0]
283
[0 0 1 0 1 0 0 0 1 0]
284
[1 0 0 1 0 0 0 0 0 1]
285
[1 0 0 0 0 0 0 1 1 0]
286
[0 1 0 0 0 0 0 0 1 1]
287
[0 0 1 0 0 1 0 0 0 1]
288
[0 0 0 1 0 1 1 0 0 0]
289
[0 0 0 0 1 0 1 1 0 0]
290
sage: G = Graph(M); G
291
Graph on 10 vertices
292
sage: G.plot().show() # or G.show()
293
294
- incidence matrix: In an incidence matrix, each row represents a
295
vertex and each column represents an edge.
296
297
::
298
299
sage: M = Matrix([(-1,0,0,0,1,0,0,0,0,0,-1,0,0,0,0), \
300
(1,-1,0,0,0,0,0,0,0,0,0,-1,0,0,0),(0,1,-1,0,0,0,0,0,0,0,0,0,-1,0,0), \
301
(0,0,1,-1,0,0,0,0,0,0,0,0,0,-1,0),(0,0,0,1,-1,0,0,0,0,0,0,0,0,0,-1), \
302
(0,0,0,0,0,-1,0,0,0,1,1,0,0,0,0),(0,0,0,0,0,0,0,1,-1,0,0,1,0,0,0), \
303
(0,0,0,0,0,1,-1,0,0,0,0,0,1,0,0),(0,0,0,0,0,0,0,0,1,-1,0,0,0,1,0), \
304
(0,0,0,0,0,0,1,-1,0,0,0,0,0,0,1)])
305
sage: M
306
[-1 0 0 0 1 0 0 0 0 0 -1 0 0 0 0]
307
[ 1 -1 0 0 0 0 0 0 0 0 0 -1 0 0 0]
308
[ 0 1 -1 0 0 0 0 0 0 0 0 0 -1 0 0]
309
[ 0 0 1 -1 0 0 0 0 0 0 0 0 0 -1 0]
310
[ 0 0 0 1 -1 0 0 0 0 0 0 0 0 0 -1]
311
[ 0 0 0 0 0 -1 0 0 0 1 1 0 0 0 0]
312
[ 0 0 0 0 0 0 0 1 -1 0 0 1 0 0 0]
313
[ 0 0 0 0 0 1 -1 0 0 0 0 0 1 0 0]
314
[ 0 0 0 0 0 0 0 0 1 -1 0 0 0 1 0]
315
[ 0 0 0 0 0 0 1 -1 0 0 0 0 0 0 1]
316
sage: G = Graph(M); G
317
Graph on 10 vertices
318
sage: G.plot().show() # or G.show()
319
sage: DiGraph(matrix(2,[0,0,-1,1]), format="incidence_matrix")
320
Traceback (most recent call last):
321
...
322
ValueError: There must be two nonzero entries (-1 & 1) per column.
323
324
- a list of edges::
325
326
sage: g = Graph([(1,3),(3,8),(5,2)])
327
sage: g
328
Graph on 5 vertices
329
330
Generators
331
----------
332
333
If you wish to iterate through all the isomorphism types of graphs,
334
type, for example::
335
336
sage: for g in graphs(4):
337
... print g.spectrum()
338
[0, 0, 0, 0]
339
[1, 0, 0, -1]
340
[1.4142135623..., 0, 0, -1.4142135623...]
341
[2, 0, -1, -1]
342
[1.7320508075..., 0, 0, -1.7320508075...]
343
[1, 1, -1, -1]
344
[1.6180339887..., 0.6180339887..., -0.6180339887..., -1.6180339887...]
345
[2.1700864866..., 0.3111078174..., -1, -1.4811943040...]
346
[2, 0, 0, -2]
347
[2.5615528128..., 0, -1, -1.5615528128...]
348
[3, -1, -1, -1]
349
350
For some commonly used graphs to play with, type
351
352
::
353
354
sage: graphs.[tab] # not tested
355
356
and hit {tab}. Most of these graphs come with their own custom
357
plot, so you can see how people usually visualize these graphs.
358
359
::
360
361
sage: G = graphs.PetersenGraph()
362
sage: G.plot().show() # or G.show()
363
sage: G.degree_histogram()
364
[0, 0, 0, 10]
365
sage: G.adjacency_matrix()
366
[0 1 0 0 1 1 0 0 0 0]
367
[1 0 1 0 0 0 1 0 0 0]
368
[0 1 0 1 0 0 0 1 0 0]
369
[0 0 1 0 1 0 0 0 1 0]
370
[1 0 0 1 0 0 0 0 0 1]
371
[1 0 0 0 0 0 0 1 1 0]
372
[0 1 0 0 0 0 0 0 1 1]
373
[0 0 1 0 0 1 0 0 0 1]
374
[0 0 0 1 0 1 1 0 0 0]
375
[0 0 0 0 1 0 1 1 0 0]
376
377
::
378
379
sage: S = G.subgraph([0,1,2,3])
380
sage: S.plot().show() # or S.show()
381
sage: S.density()
382
1/2
383
384
::
385
386
sage: G = GraphQuery(display_cols=['graph6'], num_vertices=7, diameter=5)
387
sage: L = G.get_graphs_list()
388
sage: graphs_list.show_graphs(L)
389
390
.. _Graph:labels:
391
392
Labels
393
------
394
395
Each vertex can have any hashable object as a label. These are
396
things like strings, numbers, and tuples. Each edge is given a
397
default label of ``None``, but if specified, edges can
398
have any label at all. Edges between vertices `u` and
399
`v` are represented typically as ``(u, v, l)``, where
400
``l`` is the label for the edge.
401
402
Note that vertex labels themselves cannot be mutable items::
403
404
sage: M = Matrix( [[0,0],[0,0]] )
405
sage: G = Graph({ 0 : { M : None } })
406
Traceback (most recent call last):
407
...
408
TypeError: mutable matrices are unhashable
409
410
However, if one wants to define a dictionary, with the same keys
411
and arbitrary objects for entries, one can make that association::
412
413
sage: d = {0 : graphs.DodecahedralGraph(), 1 : graphs.FlowerSnark(), \
414
2 : graphs.MoebiusKantorGraph(), 3 : graphs.PetersenGraph() }
415
sage: d[2]
416
Moebius-Kantor Graph: Graph on 16 vertices
417
sage: T = graphs.TetrahedralGraph()
418
sage: T.vertices()
419
[0, 1, 2, 3]
420
sage: T.set_vertices(d)
421
sage: T.get_vertex(1)
422
Flower Snark: Graph on 20 vertices
423
424
Database
425
--------
426
427
There is a database available for searching for graphs that satisfy
428
a certain set of parameters, including number of vertices and
429
edges, density, maximum and minimum degree, diameter, radius, and
430
connectivity. To see a list of all search parameter keywords broken
431
down by their designated table names, type
432
433
::
434
435
sage: graph_db_info()
436
{...}
437
438
For more details on data types or keyword input, enter
439
440
::
441
442
sage: GraphQuery? # not tested
443
444
The results of a query can be viewed with the show method, or can be
445
viewed individually by iterating through the results:
446
447
::
448
449
sage: Q = GraphQuery(display_cols=['graph6'],num_vertices=7, diameter=5)
450
sage: Q.show()
451
Graph6
452
--------------------
453
F?`po
454
F?gqg
455
F@?]O
456
F@OKg
457
F@R@o
458
FA_pW
459
FEOhW
460
FGC{o
461
FIAHo
462
463
Show each graph as you iterate through the results:
464
465
::
466
467
sage: for g in Q:
468
... show(g)
469
470
Visualization
471
-------------
472
473
To see a graph `G` you are working with, there
474
are three main options. You can view the graph in two dimensions via
475
matplotlib with ``show()``. ::
476
477
sage: G = graphs.RandomGNP(15,.3)
478
sage: G.show()
479
480
And you can view it in three dimensions via jmol with ``show3d()``. ::
481
482
sage: G.show3d()
483
484
Or it can be rendered with `\mbox{\rm\LaTeX}`. This requires the right
485
additions to a standard `\mbox{\rm\TeX}` installation. Then standard
486
Sage commands, such as ``view(G)`` will display the graph, or
487
``latex(G)`` will produce a string suitable for inclusion in a
488
`\mbox{\rm\LaTeX}` document. More details on this are at
489
the :mod:`sage.graphs.graph_latex` module. ::
490
491
sage: from sage.graphs.graph_latex import check_tkz_graph
492
sage: check_tkz_graph() # random - depends on TeX installation
493
sage: latex(G)
494
\begin{tikzpicture}
495
...
496
\end{tikzpicture}
497
498
Mutability
499
----------
500
501
Graphs are mutable, and thus unusable as dictionary keys, unless
502
``data_structure="static_sparse"`` is used::
503
504
sage: G = graphs.PetersenGraph()
505
sage: {G:1}[G]
506
Traceback (most recent call last):
507
...
508
TypeError: This graph is mutable, and thus not hashable. Create an immutable copy by `g.copy(immutable=True)`
509
sage: G_immutable = Graph(G, immutable=True)
510
sage: G_immutable == G
511
True
512
sage: {G_immutable:1}[G_immutable]
513
1
514
515
Methods
516
-------
517
"""
518
519
#*****************************************************************************
520
# Copyright (C) 2006 - 2007 Robert L. Miller <[email protected]>
521
#
522
# Distributed under the terms of the GNU General Public License (GPL)
523
# http://www.gnu.org/licenses/
524
#*****************************************************************************
525
526
from sage.rings.integer import Integer
527
from sage.misc.superseded import deprecated_function_alias
528
from sage.misc.superseded import deprecation
529
import sage.graphs.generic_graph_pyx as generic_graph_pyx
530
from sage.graphs.generic_graph import GenericGraph
531
from sage.graphs.digraph import DiGraph
532
from sage.combinat.combinatorial_map import combinatorial_map
533
534
class Graph(GenericGraph):
535
r"""
536
Undirected graph.
537
538
A graph is a set of vertices connected by edges. See also the
539
:wikipedia:`Wikipedia article on graphs <Graph_(mathematics)>`.
540
541
One can very easily create a graph in Sage by typing::
542
543
sage: g = Graph()
544
545
By typing the name of the graph, one can get some basic information
546
about it::
547
548
sage: g
549
Graph on 0 vertices
550
551
This graph is not very interesting as it is by default the empty graph.
552
But Sage contains a large collection of pre-defined graph classes that
553
can be listed this way:
554
555
* Within a Sage session, type ``graphs.``
556
(Do not press "Enter", and do not forget the final period ".")
557
558
* Hit "tab".
559
560
You will see a list of methods which will construct named graphs. For
561
example::
562
563
sage: g = graphs.PetersenGraph()
564
sage: g.plot()
565
566
or::
567
568
sage: g = graphs.ChvatalGraph()
569
sage: g.plot()
570
571
In order to obtain more information about these graph constructors, access
572
the documentation using the command ``graphs.RandomGNP?``.
573
574
Once you have defined the graph you want, you can begin to work on it
575
by using the almost 200 functions on graphs in the Sage library!
576
If your graph is named ``g``, you can list these functions as previously
577
this way
578
579
* Within a Sage session, type ``g.``
580
(Do not press "Enter", and do not forget the final period "." )
581
582
* Hit "tab".
583
584
As usual, you can get some information about what these functions do by
585
typing (e.g. if you want to know about the ``diameter()`` method)
586
``g.diameter?``.
587
588
If you have defined a graph ``g`` having several connected components
589
(i.e. ``g.is_connected()`` returns False), you can print each one of its
590
connected components with only two lines::
591
592
sage: for component in g.connected_components():
593
... g.subgraph(component).plot()
594
595
596
INPUT:
597
598
- ``data`` -- can be any of the following (see the ``format`` argument):
599
600
#. An integer specifying the number of vertices
601
602
#. A dictionary of dictionaries
603
604
#. A dictionary of lists
605
606
#. A NumPy matrix or ndarray
607
608
#. A Sage adjacency matrix or incidence matrix
609
610
#. A pygraphviz graph
611
612
#. A SciPy sparse matrix
613
614
#. A NetworkX graph
615
616
- ``pos`` - a positioning dictionary: for example, the
617
spring layout from NetworkX for the 5-cycle is::
618
619
{0: [-0.91679746, 0.88169588],
620
1: [ 0.47294849, 1.125 ],
621
2: [ 1.125 ,-0.12867615],
622
3: [ 0.12743933,-1.125 ],
623
4: [-1.125 ,-0.50118505]}
624
625
- ``name`` - (must be an explicitly named parameter,
626
i.e., ``name="complete")`` gives the graph a name
627
628
- ``loops`` - boolean, whether to allow loops (ignored
629
if data is an instance of the ``Graph`` class)
630
631
- ``multiedges`` - boolean, whether to allow multiple
632
edges (ignored if data is an instance of the ``Graph`` class)
633
634
- ``weighted`` - whether graph thinks of itself as
635
weighted or not. See ``self.weighted()``
636
637
- ``format`` - if None, Graph tries to guess- can be
638
several values, including:
639
640
- ``'int'`` - an integer specifying the number of vertices in an
641
edge-free graph with vertices labelled from 0 to n-1
642
643
- ``'graph6'`` - Brendan McKay's graph6 format, in a
644
string (if the string has multiple graphs, the first graph is
645
taken)
646
647
- ``'sparse6'`` - Brendan McKay's sparse6 format, in a
648
string (if the string has multiple graphs, the first graph is
649
taken)
650
651
- ``'adjacency_matrix'`` - a square Sage matrix M,
652
with M[i,j] equal to the number of edges {i,j}
653
654
- ``'weighted_adjacency_matrix'`` - a square Sage
655
matrix M, with M[i,j] equal to the weight of the single edge {i,j}.
656
Given this format, weighted is ignored (assumed True).
657
658
- ``'incidence_matrix'`` - a Sage matrix, with one
659
column C for each edge, where if C represents {i, j}, C[i] is -1
660
and C[j] is 1
661
662
- ``'elliptic_curve_congruence'`` - data must be an
663
iterable container of elliptic curves, and the graph produced has
664
each curve as a vertex (it's Cremona label) and an edge E-F
665
labelled p if and only if E is congruent to F mod p
666
667
- ``NX`` - data must be a NetworkX Graph.
668
669
.. NOTE::
670
671
As Sage's default edge labels is ``None`` while NetworkX uses
672
``{}``, the ``{}`` labels of a NetworkX graph are automatically
673
set to ``None`` when it is converted to a Sage graph. This
674
behaviour can be overruled by setting the keyword
675
``convert_empty_dict_labels_to_None`` to ``False`` (it is
676
``True`` by default).
677
678
- ``boundary`` - a list of boundary vertices, if
679
empty, graph is considered as a 'graph without boundary'
680
681
- ``implementation`` - what to use as a backend for
682
the graph. Currently, the options are either 'networkx' or
683
'c_graph'
684
685
- ``sparse`` (boolean) -- ``sparse=True`` is an alias for
686
``data_structure="sparse"``, and ``sparse=False`` is an alias for
687
``data_structure="dense"``.
688
689
- ``data_structure`` -- one of the following
690
691
* ``"dense"`` -- selects the :mod:`~sage.graphs.base.dense_graph`
692
backend.
693
694
* ``"sparse"`` -- selects the :mod:`~sage.graphs.base.sparse_graph`
695
backend.
696
697
* ``"static_sparse"`` -- selects the
698
:mod:`~sage.graphs.base.static_sparse_backend` (this backend is faster
699
than the sparse backend and smaller in memory, and it is immutable, so
700
that the resulting graphs can be used as dictionary keys).
701
702
*Only available when* ``implementation == 'c_graph'``
703
704
- ``immutable`` (boolean) -- whether to create a immutable graph. Note that
705
``immutable=True`` is actually a shortcut for
706
``data_structure='static_sparse'``. Set to ``False`` by default, only
707
available when ``implementation='c_graph'``
708
709
- ``vertex_labels`` - only for implementation == 'c_graph'.
710
Whether to allow any object as a vertex (slower), or
711
only the integers 0, ..., n-1, where n is the number of vertices.
712
713
- ``convert_empty_dict_labels_to_None`` - this arguments sets
714
the default edge labels used by NetworkX (empty dictionaries)
715
to be replaced by None, the default Sage edge label. It is
716
set to ``True`` iff a NetworkX graph is on the input.
717
718
EXAMPLES:
719
720
We illustrate the first seven input formats (the other two
721
involve packages that are currently not standard in Sage):
722
723
#. An integer giving the number of vertices::
724
725
sage: g = Graph(5); g
726
Graph on 5 vertices
727
sage: g.vertices()
728
[0, 1, 2, 3, 4]
729
sage: g.edges()
730
[]
731
732
#. A dictionary of dictionaries::
733
734
sage: g = Graph({0:{1:'x',2:'z',3:'a'}, 2:{5:'out'}}); g
735
Graph on 5 vertices
736
737
The labels ('x', 'z', 'a', 'out') are labels for edges. For
738
example, 'out' is the label for the edge on 2 and 5. Labels can be
739
used as weights, if all the labels share some common parent.
740
741
::
742
743
sage: a,b,c,d,e,f = sorted(SymmetricGroup(3))
744
sage: Graph({b:{d:'c',e:'p'}, c:{d:'p',e:'c'}})
745
Graph on 4 vertices
746
747
#. A dictionary of lists::
748
749
sage: g = Graph({0:[1,2,3], 2:[4]}); g
750
Graph on 5 vertices
751
752
#. A list of vertices and a function describing adjacencies. Note
753
that the list of vertices and the function must be enclosed in a
754
list (i.e., [list of vertices, function]).
755
756
Construct the Paley graph over GF(13).
757
758
::
759
760
sage: g=Graph([GF(13), lambda i,j: i!=j and (i-j).is_square()])
761
sage: g.vertices()
762
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
763
sage: g.adjacency_matrix()
764
[0 1 0 1 1 0 0 0 0 1 1 0 1]
765
[1 0 1 0 1 1 0 0 0 0 1 1 0]
766
[0 1 0 1 0 1 1 0 0 0 0 1 1]
767
[1 0 1 0 1 0 1 1 0 0 0 0 1]
768
[1 1 0 1 0 1 0 1 1 0 0 0 0]
769
[0 1 1 0 1 0 1 0 1 1 0 0 0]
770
[0 0 1 1 0 1 0 1 0 1 1 0 0]
771
[0 0 0 1 1 0 1 0 1 0 1 1 0]
772
[0 0 0 0 1 1 0 1 0 1 0 1 1]
773
[1 0 0 0 0 1 1 0 1 0 1 0 1]
774
[1 1 0 0 0 0 1 1 0 1 0 1 0]
775
[0 1 1 0 0 0 0 1 1 0 1 0 1]
776
[1 0 1 1 0 0 0 0 1 1 0 1 0]
777
778
Construct the line graph of a complete graph.
779
780
::
781
782
sage: g=graphs.CompleteGraph(4)
783
sage: line_graph=Graph([g.edges(labels=false), \
784
lambda i,j: len(set(i).intersection(set(j)))>0], \
785
loops=False)
786
sage: line_graph.vertices()
787
[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]
788
sage: line_graph.adjacency_matrix()
789
[0 1 1 1 1 0]
790
[1 0 1 1 0 1]
791
[1 1 0 0 1 1]
792
[1 1 0 0 1 1]
793
[1 0 1 1 0 1]
794
[0 1 1 1 1 0]
795
796
#. A NumPy matrix or ndarray::
797
798
sage: import numpy
799
sage: A = numpy.array([[0,1,1],[1,0,1],[1,1,0]])
800
sage: Graph(A)
801
Graph on 3 vertices
802
803
#. A graph6 or sparse6 string: Sage automatically recognizes
804
whether a string is in graph6 or sparse6 format::
805
806
sage: s = ':I`AKGsaOs`cI]Gb~'
807
sage: Graph(s,sparse=True)
808
Looped multi-graph on 10 vertices
809
810
::
811
812
sage: G = Graph('G?????')
813
sage: G = Graph("G'?G?C")
814
Traceback (most recent call last):
815
...
816
RuntimeError: The string seems corrupt: valid characters are
817
?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
818
sage: G = Graph('G??????')
819
Traceback (most recent call last):
820
...
821
RuntimeError: The string (G??????) seems corrupt: for n = 8, the string is too long.
822
823
::
824
825
sage: G = Graph(":I'AKGsaOs`cI]Gb~")
826
Traceback (most recent call last):
827
...
828
RuntimeError: The string seems corrupt: valid characters are
829
?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
830
831
There are also list functions to take care of lists of graphs::
832
833
sage: s = ':IgMoqoCUOqeb\n:I`AKGsaOs`cI]Gb~\n:I`EDOAEQ?PccSsge\N\n'
834
sage: graphs_list.from_sparse6(s)
835
[Looped multi-graph on 10 vertices, Looped multi-graph on 10 vertices, Looped multi-graph on 10 vertices]
836
837
#. A Sage matrix:
838
Note: If format is not specified, then Sage assumes a symmetric square
839
matrix is an adjacency matrix, otherwise an incidence matrix.
840
841
- an adjacency matrix::
842
843
sage: M = graphs.PetersenGraph().am(); M
844
[0 1 0 0 1 1 0 0 0 0]
845
[1 0 1 0 0 0 1 0 0 0]
846
[0 1 0 1 0 0 0 1 0 0]
847
[0 0 1 0 1 0 0 0 1 0]
848
[1 0 0 1 0 0 0 0 0 1]
849
[1 0 0 0 0 0 0 1 1 0]
850
[0 1 0 0 0 0 0 0 1 1]
851
[0 0 1 0 0 1 0 0 0 1]
852
[0 0 0 1 0 1 1 0 0 0]
853
[0 0 0 0 1 0 1 1 0 0]
854
sage: Graph(M)
855
Graph on 10 vertices
856
857
::
858
859
sage: Graph(matrix([[1,2],[2,4]]),loops=True,sparse=True)
860
Looped multi-graph on 2 vertices
861
862
sage: M = Matrix([[0,1,-1],[1,0,-1/2],[-1,-1/2,0]]); M
863
[ 0 1 -1]
864
[ 1 0 -1/2]
865
[ -1 -1/2 0]
866
sage: G = Graph(M,sparse=True); G
867
Graph on 3 vertices
868
sage: G.weighted()
869
True
870
871
- an incidence matrix::
872
873
sage: M = Matrix(6, [-1,0,0,0,1, 1,-1,0,0,0, 0,1,-1,0,0, 0,0,1,-1,0, 0,0,0,1,-1, 0,0,0,0,0]); M
874
[-1 0 0 0 1]
875
[ 1 -1 0 0 0]
876
[ 0 1 -1 0 0]
877
[ 0 0 1 -1 0]
878
[ 0 0 0 1 -1]
879
[ 0 0 0 0 0]
880
sage: Graph(M)
881
Graph on 6 vertices
882
883
sage: Graph(Matrix([[1],[1],[1]]))
884
Traceback (most recent call last):
885
...
886
ValueError: Non-symmetric or non-square matrix assumed to be an incidence matrix: There must be two nonzero entries (-1 & 1) per column.
887
sage: Graph(Matrix([[1],[1],[0]]))
888
Traceback (most recent call last):
889
...
890
ValueError: Non-symmetric or non-square matrix assumed to be an incidence matrix: Each column represents an edge: -1 goes to 1.
891
892
sage: M = Matrix([[0,1,-1],[1,0,-1],[-1,-1,0]]); M
893
[ 0 1 -1]
894
[ 1 0 -1]
895
[-1 -1 0]
896
sage: Graph(M,sparse=True)
897
Graph on 3 vertices
898
899
sage: M = Matrix([[0,1,1],[1,0,1],[-1,-1,0]]); M
900
[ 0 1 1]
901
[ 1 0 1]
902
[-1 -1 0]
903
sage: Graph(M)
904
Traceback (most recent call last):
905
...
906
ValueError: Non-symmetric or non-square matrix assumed to be an incidence matrix: Each column represents an edge: -1 goes to 1.
907
908
::
909
910
sage: MA = Matrix([[1,2,0], [0,2,0], [0,0,1]]) # trac 9714
911
sage: MI = Graph(MA, format='adjacency_matrix').incidence_matrix(); MI
912
[-1 -1 0 0 0 1]
913
[ 1 1 0 1 1 0]
914
[ 0 0 1 0 0 0]
915
sage: Graph(MI).edges(labels=None)
916
[(0, 0), (0, 1), (0, 1), (1, 1), (1, 1), (2, 2)]
917
918
sage: M = Matrix([[1], [-1]]); M
919
[ 1]
920
[-1]
921
sage: Graph(M).edges()
922
[(0, 1, None)]
923
924
#. a list of edges, or labelled edges::
925
926
sage: g = Graph([(1,3),(3,8),(5,2)])
927
sage: g
928
Graph on 5 vertices
929
930
::
931
932
sage: g = Graph([(1,2,"Peace"),(7,-9,"and"),(77,2, "Love")])
933
sage: g
934
Graph on 5 vertices
935
sage: g = Graph([(0, 2, '0'), (0, 2, '1'), (3, 3, '2')])
936
sage: g.loops()
937
[(3, 3, '2')]
938
939
#. A NetworkX MultiGraph::
940
941
sage: import networkx
942
sage: g = networkx.MultiGraph({0:[1,2,3], 2:[4]})
943
sage: Graph(g)
944
Graph on 5 vertices
945
946
#. A NetworkX graph::
947
948
sage: import networkx
949
sage: g = networkx.Graph({0:[1,2,3], 2:[4]})
950
sage: DiGraph(g)
951
Digraph on 5 vertices
952
953
Note that in all cases, we copy the NetworkX structure.
954
955
::
956
957
sage: import networkx
958
sage: g = networkx.Graph({0:[1,2,3], 2:[4]})
959
sage: G = Graph(g, implementation='networkx')
960
sage: H = Graph(g, implementation='networkx')
961
sage: G._backend._nxg is H._backend._nxg
962
False
963
964
All these graphs are mutable and can thus not be used as a dictionary
965
key::
966
967
sage: {G:1}[H]
968
Traceback (most recent call last):
969
...
970
TypeError: This graph is mutable, and thus not hashable. Create an immutable copy by `g.copy(immutable=True)`
971
972
When providing the optional arguments ``data_structure="static_sparse"``
973
or ``immutable=True`` (both mean the same), then an immutable graph
974
results. Note that this does not use the NetworkX data structure::
975
976
sage: G_imm = Graph(g, immutable=True)
977
sage: H_imm = Graph(g, data_structure='static_sparse')
978
sage: G_imm == H_imm == G == H
979
True
980
sage: hasattr(G_imm._backend, "_nxg")
981
False
982
sage: {G_imm:1}[H_imm]
983
1
984
985
"""
986
_directed = False
987
988
def __init__(self, data=None, pos=None, loops=None, format=None,
989
boundary=None, weighted=None, implementation='c_graph',
990
data_structure="sparse", vertex_labels=True, name=None,
991
multiedges=None, convert_empty_dict_labels_to_None=None,
992
sparse=True, immutable=False):
993
"""
994
TESTS::
995
996
sage: G = Graph()
997
sage: loads(dumps(G)) == G
998
True
999
sage: a = matrix(2,2,[1,0,0,1])
1000
sage: Graph(a).adjacency_matrix() == a
1001
True
1002
1003
sage: a = matrix(2,2,[2,0,0,1])
1004
sage: Graph(a,sparse=True).adjacency_matrix() == a
1005
True
1006
1007
The positions are copied when the graph is built from
1008
another graph ::
1009
1010
sage: g = graphs.PetersenGraph()
1011
sage: h = Graph(g)
1012
sage: g.get_pos() == h.get_pos()
1013
True
1014
1015
Or from a DiGraph ::
1016
1017
sage: d = DiGraph(g)
1018
sage: h = Graph(d)
1019
sage: g.get_pos() == h.get_pos()
1020
True
1021
1022
Loops are not counted as multiedges (see :trac:`11693`) and edges are
1023
not counted twice ::
1024
1025
sage: Graph([[1,1]],multiedges=False).num_edges()
1026
1
1027
sage: Graph([[1,2],[1,2]],multiedges=True).num_edges()
1028
2
1029
1030
Invalid sequence of edges given as an input (they do not all
1031
have the same length)::
1032
1033
sage: g = Graph([(1,2),(2,3),(2,3,4)])
1034
Traceback (most recent call last):
1035
...
1036
ValueError: Edges input must all follow the same format.
1037
1038
1039
Two different labels given for the same edge in a graph
1040
without multiple edges::
1041
1042
sage: g = Graph([(1,2,3),(1,2,4)], multiedges = False)
1043
Traceback (most recent call last):
1044
...
1045
ValueError: Two different labels given for the same edge in a graph without multiple edges.
1046
1047
The same edge included more than once in a graph without
1048
multiple edges::
1049
1050
sage: g = Graph([[1,2],[1,2]],multiedges=False)
1051
Traceback (most recent call last):
1052
...
1053
ValueError: Non-multigraph input dict has multiple edges (1,2)
1054
1055
An empty list or dictionary defines a simple graph
1056
(:trac:`10441` and :trac:`12910`)::
1057
1058
sage: Graph([])
1059
Graph on 0 vertices
1060
sage: Graph({})
1061
Graph on 0 vertices
1062
sage: # not "Multi-graph on 0 vertices"
1063
1064
Verify that the int format works as expected (:trac:`12557`)::
1065
1066
sage: Graph(2).adjacency_matrix()
1067
[0 0]
1068
[0 0]
1069
sage: Graph(3) == Graph(3,format='int')
1070
True
1071
1072
Problem with weighted adjacency matrix (:trac:`13919`)::
1073
1074
sage: B = {0:{1:2,2:5,3:4},1:{2:2,4:7},2:{3:1,4:4,5:3},3:{5:4},4:{5:1,6:5},5:{6:7}}
1075
sage: grafo3 = Graph(B,weighted=True)
1076
sage: matad = grafo3.weighted_adjacency_matrix()
1077
sage: grafo4 = Graph(matad,format = "adjacency_matrix", weighted=True)
1078
sage: grafo4.shortest_path(0,6,by_weight=True)
1079
[0, 1, 2, 5, 4, 6]
1080
1081
Get rid of mutable default argument for `boundary` (:trac:`14794`)::
1082
1083
sage: G = Graph(boundary=None)
1084
sage: G._boundary
1085
[]
1086
1087
Graphs returned when setting ``immutable=False`` are mutable::
1088
1089
sage: g = graphs.PetersenGraph()
1090
sage: g = Graph(g.edges(),immutable=False)
1091
sage: g.add_edge("Hey", "Heyyyyyyy")
1092
1093
And their name is set::
1094
1095
sage: g = graphs.PetersenGraph()
1096
sage: Graph(g, immutable=True)
1097
Petersen graph: Graph on 10 vertices
1098
"""
1099
GenericGraph.__init__(self)
1100
msg = ''
1101
from sage.structure.element import is_Matrix
1102
from sage.misc.misc import uniq
1103
1104
if sparse == False:
1105
if data_structure != "sparse":
1106
raise ValueError("The 'sparse' argument is an alias for "
1107
"'data_structure'. Please do not define both.")
1108
data_structure = "dense"
1109
1110
if format is None and isinstance(data, str):
1111
if data[:10] == ">>graph6<<":
1112
data = data[10:]
1113
format = 'graph6'
1114
elif data[:11] == ">>sparse6<<":
1115
data = data[11:]
1116
format = 'sparse6'
1117
elif data[0] == ':':
1118
format = 'sparse6'
1119
else:
1120
format = 'graph6'
1121
if format is None and is_Matrix(data):
1122
if data.is_square() and data == data.transpose():
1123
format = 'adjacency_matrix'
1124
else:
1125
format = 'incidence_matrix'
1126
msg += "Non-symmetric or non-square matrix assumed to be an incidence matrix: "
1127
if format is None and isinstance(data, Graph):
1128
format = 'Graph'
1129
from sage.graphs.all import DiGraph
1130
if format is None and isinstance(data, DiGraph):
1131
data = data.to_undirected()
1132
format = 'Graph'
1133
if format is None and isinstance(data,list) and \
1134
len(data)>=2 and callable(data[1]):
1135
format = 'rule'
1136
if format is None and isinstance(data,dict):
1137
keys = data.keys()
1138
if len(keys) == 0: format = 'dict_of_dicts'
1139
else:
1140
if isinstance(data[keys[0]], list):
1141
format = 'dict_of_lists'
1142
elif isinstance(data[keys[0]], dict):
1143
format = 'dict_of_dicts'
1144
if format is None and hasattr(data, 'adj'):
1145
import networkx
1146
if isinstance(data, (networkx.DiGraph, networkx.MultiDiGraph)):
1147
data = data.to_undirected()
1148
format = 'NX'
1149
elif isinstance(data, (networkx.Graph, networkx.MultiGraph)):
1150
format = 'NX'
1151
if format is None and isinstance(data, (int, Integer)):
1152
format = 'int'
1153
if format is None and data is None:
1154
format = 'int'
1155
data = 0
1156
1157
# Input is a list of edges
1158
if format is None and isinstance(data, list):
1159
1160
# If we are given a list (we assume it is a list of
1161
# edges), we convert the problem to a dict_of_dicts or a
1162
# dict_of_lists
1163
1164
edges = data
1165
1166
# Along the process, we are assuming all edges have the
1167
# same length. If it is not true, a ValueError will occur
1168
try:
1169
1170
# The list is empty
1171
if not data:
1172
data = {}
1173
format = 'dict_of_dicts'
1174
1175
# The edges are not labelled
1176
elif len(data[0]) == 2:
1177
data = {}
1178
for u,v in edges:
1179
if not u in data:
1180
data[u] = []
1181
if not v in data:
1182
data[v] = []
1183
data[u].append(v)
1184
1185
format = 'dict_of_lists'
1186
1187
# The edges are labelled
1188
elif len(data[0]) == 3:
1189
data = {}
1190
for u,v,l in edges:
1191
1192
if not u in data:
1193
data[u] = {}
1194
if not v in data:
1195
data[v] = {}
1196
1197
# Now the keys exists, and are dictionaries ...
1198
1199
1200
# If we notice for the first time that there
1201
# are multiple edges, we update the whole
1202
# dictionary so that data[u][v] is a list
1203
1204
if (multiedges is None and (u in data[v])):
1205
multiedges = True
1206
for uu, dd in data.iteritems():
1207
for vv, ddd in dd.iteritems():
1208
dd[vv] = [ddd]
1209
1210
# If multiedges is set to False while the same
1211
# edge is present in the list with different
1212
# values of its label
1213
elif (multiedges is False and
1214
(u in data[v] and data[v][u] != l)):
1215
raise ValueError("MULTIEDGE")
1216
1217
# Now we are behaving as if multiedges == None
1218
# means multiedges = False. If something bad
1219
# happens later, the whole dictionary will be
1220
# updated anyway
1221
1222
if multiedges is True:
1223
if v not in data[u]:
1224
data[u][v] = []
1225
data[v][u] = []
1226
1227
data[u][v].append(l)
1228
1229
if u != v:
1230
data[v][u].append(l)
1231
1232
else:
1233
data[u][v] = l
1234
data[v][u] = l
1235
1236
format = 'dict_of_dicts'
1237
1238
except ValueError as ve:
1239
if str(ve) == "MULTIEDGE":
1240
raise ValueError("Two different labels given for the same edge in a graph without multiple edges.")
1241
else:
1242
raise ValueError("Edges input must all follow the same format.")
1243
1244
1245
if format is None:
1246
import networkx
1247
data = networkx.MultiGraph(data)
1248
format = 'NX'
1249
1250
# At this point, format has been set.
1251
1252
# adjust for empty dicts instead of None in NetworkX default edge labels
1253
if convert_empty_dict_labels_to_None is None:
1254
convert_empty_dict_labels_to_None = (format == 'NX')
1255
1256
verts = None
1257
1258
if format == 'graph6':
1259
if loops is None: loops = False
1260
if weighted is None: weighted = False
1261
if multiedges is None: multiedges = False
1262
if not isinstance(data, str):
1263
raise ValueError('If input format is graph6, then data must be a string.')
1264
n = data.find('\n')
1265
if n == -1:
1266
n = len(data)
1267
ss = data[:n]
1268
n, s = generic_graph_pyx.N_inverse(ss)
1269
m = generic_graph_pyx.R_inverse(s, n)
1270
expected = n*(n-1)/2 + (6 - n*(n-1)/2)%6
1271
if len(m) > expected:
1272
raise RuntimeError("The string (%s) seems corrupt: for n = %d, the string is too long."%(ss,n))
1273
elif len(m) < expected:
1274
raise RuntimeError("The string (%s) seems corrupt: for n = %d, the string is too short."%(ss,n))
1275
num_verts = n
1276
elif format == 'sparse6':
1277
if loops is None: loops = True
1278
if weighted is None: weighted = False
1279
if multiedges is None: multiedges = True
1280
from math import ceil, floor
1281
from sage.misc.functional import log
1282
n = data.find('\n')
1283
if n == -1:
1284
n = len(data)
1285
s = data[:n]
1286
n, s = generic_graph_pyx.N_inverse(s[1:])
1287
if n == 0:
1288
edges = []
1289
else:
1290
k = int(ceil(log(n,2)))
1291
ords = [ord(i) for i in s]
1292
if any(o > 126 or o < 63 for o in ords):
1293
raise RuntimeError("The string seems corrupt: valid characters are \n" + ''.join([chr(i) for i in xrange(63,127)]))
1294
bits = ''.join([generic_graph_pyx.binary(o-63).zfill(6) for o in ords])
1295
b = []
1296
x = []
1297
for i in xrange(int(floor(len(bits)/(k+1)))):
1298
b.append(int(bits[(k+1)*i:(k+1)*i+1],2))
1299
x.append(int(bits[(k+1)*i+1:(k+1)*i+k+1],2))
1300
v = 0
1301
edges = []
1302
for i in xrange(len(b)):
1303
if b[i] == 1:
1304
v += 1
1305
if x[i] > v:
1306
v = x[i]
1307
else:
1308
if v < n:
1309
edges.append((x[i],v))
1310
num_verts = n
1311
elif format in ['adjacency_matrix', 'incidence_matrix']:
1312
assert is_Matrix(data)
1313
if format == 'weighted_adjacency_matrix':
1314
if weighted is False:
1315
raise ValueError("Format was weighted_adjacency_matrix but weighted was False.")
1316
if weighted is None: weighted = True
1317
if multiedges is None: multiedges = False
1318
format = 'adjacency_matrix'
1319
if format == 'adjacency_matrix':
1320
entries = uniq(data.list())
1321
for e in entries:
1322
try:
1323
e = int(e)
1324
assert e >= 0
1325
except Exception:
1326
if weighted is False:
1327
raise ValueError("Non-weighted graph's"+
1328
" adjacency matrix must have only nonnegative"+
1329
" integer entries")
1330
weighted = True
1331
if multiedges is None: multiedges = False
1332
break
1333
1334
if weighted is None:
1335
weighted = False
1336
1337
if multiedges is None:
1338
multiedges = ((not weighted) and sorted(entries) != [0,1])
1339
1340
for i in xrange(data.nrows()):
1341
if data[i,i] != 0:
1342
if loops is None: loops = True
1343
elif not loops:
1344
raise ValueError("Non-looped graph's adjacency"+
1345
" matrix must have zeroes on the diagonal.")
1346
break
1347
num_verts = data.nrows()
1348
elif format == 'incidence_matrix':
1349
try:
1350
positions = []
1351
for c in data.columns():
1352
NZ = c.nonzero_positions()
1353
if len(NZ) == 1:
1354
if loops is None:
1355
loops = True
1356
elif not loops:
1357
msg += "There must be two nonzero entries (-1 & 1) per column."
1358
assert False
1359
positions.append((NZ[0], NZ[0]))
1360
elif len(NZ) != 2:
1361
msg += "There must be two nonzero entries (-1 & 1) per column."
1362
assert False
1363
else:
1364
positions.append(tuple(NZ))
1365
L = uniq(c.list())
1366
L.sort()
1367
1368
if data.nrows() != (2 if len(NZ) == 2 else 1):
1369
desirable = [-1, 0, 1] if len(NZ) == 2 else [0, 1]
1370
else:
1371
desirable = [-1, 1] if len(NZ) == 2 else [1]
1372
1373
if L != desirable:
1374
msg += "Each column represents an edge: -1 goes to 1."
1375
assert False
1376
if loops is None: loops = False
1377
if weighted is None: weighted = False
1378
if multiedges is None:
1379
total = len(positions)
1380
multiedges = ( len(uniq(positions)) < total )
1381
except AssertionError:
1382
raise ValueError(msg)
1383
num_verts = data.nrows()
1384
elif format == 'Graph':
1385
if loops is None: loops = data.allows_loops()
1386
elif not loops and data.has_loops():
1387
raise ValueError("No loops but input graph has loops.")
1388
if multiedges is None: multiedges = data.allows_multiple_edges()
1389
elif not multiedges:
1390
e = data.edges(labels=False)
1391
e = [sorted(f) for f in e]
1392
if len(e) != len(uniq(e)):
1393
raise ValueError("No multiple edges but input graph"+
1394
" has multiple edges.")
1395
if weighted is None: weighted = data.weighted()
1396
num_verts = data.num_verts()
1397
verts = data.vertex_iterator()
1398
if data.get_pos() is not None:
1399
pos = data.get_pos().copy()
1400
1401
elif format == 'rule':
1402
f = data[1]
1403
if loops is None: loops = any(f(v,v) for v in data[0])
1404
if multiedges is None: multiedges = False
1405
if weighted is None: weighted = False
1406
num_verts = len(data[0])
1407
verts = data[0]
1408
elif format == 'dict_of_dicts':
1409
if not all(isinstance(data[u], dict) for u in data):
1410
raise ValueError("Input dict must be a consistent format.")
1411
verts = set(data.keys())
1412
if loops is None or loops is False:
1413
for u in data:
1414
if u in data[u]:
1415
if loops is None: loops = True
1416
elif loops is False:
1417
raise ValueError("No loops but dict has loops.")
1418
break
1419
if loops is None: loops = False
1420
if weighted is None: weighted = False
1421
for u in data:
1422
for v in data[u]:
1423
if v not in verts: verts.add(v)
1424
if hash(u) > hash(v):
1425
if v in data and u in data[v]:
1426
if data[u][v] != data[v][u]:
1427
raise ValueError("Dict does not agree on edge (%s,%s)"%(u,v))
1428
continue
1429
if multiedges is not False and not isinstance(data[u][v], list):
1430
if multiedges is None: multiedges = False
1431
if multiedges:
1432
raise ValueError("Dict of dicts for multigraph must be in the format {v : {u : list}}")
1433
if multiedges is None and len(data) > 0:
1434
multiedges = True
1435
num_verts = len(verts)
1436
elif format == 'dict_of_lists':
1437
if not all(isinstance(data[u], list) for u in data):
1438
raise ValueError("Input dict must be a consistent format.")
1439
verts = set(data.keys())
1440
if loops is None or loops is False:
1441
for u in data:
1442
if u in data[u]:
1443
if loops is None: loops = True
1444
elif loops is False:
1445
raise ValueError("No loops but dict has loops.")
1446
break
1447
if loops is None: loops = False
1448
if weighted is None: weighted = False
1449
for u in data:
1450
verts=verts.union([v for v in data[u] if v not in verts])
1451
if len(uniq(data[u])) != len(data[u]):
1452
if multiedges is False:
1453
from sage.misc.prandom import choice
1454
raise ValueError("Non-multigraph input dict has multiple edges (%s,%s)"%(u, choice([v for v in data[u] if data[u].count(v) > 1])))
1455
if multiedges is None: multiedges = True
1456
if multiedges is None: multiedges = False
1457
num_verts = len(verts)
1458
elif format == 'NX':
1459
if weighted is None:
1460
if isinstance(data, networkx.Graph):
1461
weighted = False
1462
if multiedges is None:
1463
multiedges = False
1464
if loops is None:
1465
loops = False
1466
else:
1467
weighted = True
1468
if multiedges is None:
1469
multiedges = True
1470
if loops is None:
1471
loops = True
1472
num_verts = data.order()
1473
verts = data.nodes()
1474
data = data.adj
1475
format = 'dict_of_dicts'
1476
elif format in ['int', 'elliptic_curve_congruence']:
1477
if weighted is None: weighted = False
1478
if multiedges is None: multiedges = False
1479
if loops is None: loops = False
1480
if format == 'int': num_verts = data
1481
else:
1482
num_verts = len(data)
1483
curves = data
1484
verts = [curve.cremona_label() for curve in data]
1485
1486
# weighted, multiedges, loops, verts and num_verts should now be set
1487
1488
if implementation == 'networkx':
1489
import networkx
1490
from sage.graphs.base.graph_backends import NetworkXGraphBackend
1491
if format == 'Graph':
1492
self._backend = NetworkXGraphBackend(data.networkx_graph())
1493
self._weighted = weighted
1494
self.allow_loops(loops)
1495
self.allow_multiple_edges(multiedges)
1496
else:
1497
if multiedges:
1498
self._backend = NetworkXGraphBackend(networkx.MultiGraph())
1499
else:
1500
self._backend = NetworkXGraphBackend(networkx.Graph())
1501
self._weighted = weighted
1502
self.allow_loops(loops)
1503
self.allow_multiple_edges(multiedges)
1504
if verts is not None:
1505
self.add_vertices(verts)
1506
else:
1507
self.add_vertices(range(num_verts))
1508
elif implementation == 'c_graph':
1509
if multiedges or weighted:
1510
if data_structure == "dense":
1511
raise RuntimeError("Multiedge and weighted c_graphs must be sparse.")
1512
1513
if immutable:
1514
data_structure = 'static_sparse'
1515
1516
# If the data structure is static_sparse, we first build a graph
1517
# using the sparse data structure, then reencode the resulting graph
1518
# as a static sparse graph.
1519
from sage.graphs.base.sparse_graph import SparseGraphBackend
1520
from sage.graphs.base.dense_graph import DenseGraphBackend
1521
if data_structure in ["sparse", "static_sparse"]:
1522
CGB = SparseGraphBackend
1523
elif data_structure == "dense":
1524
CGB = DenseGraphBackend
1525
else:
1526
raise ValueError("data_structure must be equal to 'sparse', "
1527
"'static_sparse' or 'dense'")
1528
if format == 'Graph':
1529
self._backend = CGB(0, directed=False)
1530
self.add_vertices(verts)
1531
self._weighted = weighted
1532
self.allow_loops(loops, check=False)
1533
self.allow_multiple_edges(multiedges, check=False)
1534
for u,v,l in data.edge_iterator():
1535
self._backend.add_edge(u,v,l,False)
1536
else:
1537
if verts is not None:
1538
self._backend = CGB(0, directed=False)
1539
self.add_vertices(verts)
1540
else:
1541
self._backend = CGB(num_verts, directed=False)
1542
self._weighted = weighted
1543
self.allow_loops(loops, check=False)
1544
self.allow_multiple_edges(multiedges, check=False)
1545
self._backend.directed = False
1546
else:
1547
raise NotImplementedError("Supported implementations: networkx, c_graph.")
1548
1549
if format == 'graph6':
1550
k = 0
1551
for i in xrange(n):
1552
for j in xrange(i):
1553
if m[k] == '1':
1554
self.add_edge(i, j)
1555
k += 1
1556
1557
elif format == 'sparse6':
1558
for i,j in edges:
1559
self.add_edge(i,j)
1560
1561
elif format == 'adjacency_matrix':
1562
e = []
1563
if weighted:
1564
for i,j in data.nonzero_positions():
1565
if i <= j:
1566
e.append((i,j,data[i][j]))
1567
elif multiedges:
1568
for i,j in data.nonzero_positions():
1569
if i <= j:
1570
e += [(i,j)]*int(data[i][j])
1571
else:
1572
for i,j in data.nonzero_positions():
1573
if i <= j:
1574
e.append((i,j))
1575
self.add_edges(e)
1576
1577
elif format == 'incidence_matrix':
1578
self.add_edges(positions)
1579
1580
elif format == 'Graph':
1581
self.name(data.name())
1582
1583
elif format == 'rule':
1584
verts = list(verts)
1585
for u in xrange(num_verts):
1586
for v in xrange(u+1):
1587
uu,vv = verts[u], verts[v]
1588
if f(uu,vv):
1589
self.add_edge(uu,vv)
1590
1591
elif format == 'dict_of_dicts':
1592
if convert_empty_dict_labels_to_None:
1593
for u in data:
1594
for v in data[u]:
1595
if hash(u) <= hash(v) or v not in data or u not in data[v]:
1596
if multiedges:
1597
self.add_edges([(u,v,l) for l in data[u][v]])
1598
else:
1599
self.add_edge((u,v,data[u][v] if data[u][v] != {} else None))
1600
else:
1601
for u in data:
1602
for v in data[u]:
1603
if hash(u) <= hash(v) or v not in data or u not in data[v]:
1604
if multiedges:
1605
self.add_edges([(u,v,l) for l in data[u][v]])
1606
else:
1607
self.add_edge((u,v,data[u][v]))
1608
1609
elif format == 'dict_of_lists':
1610
for u in data:
1611
for v in data[u]:
1612
if multiedges or hash(u) <= hash(v) or \
1613
v not in data or u not in data[v]:
1614
self.add_edge(u,v)
1615
1616
elif format == 'elliptic_curve_congruence':
1617
from sage.rings.arith import lcm, prime_divisors
1618
from sage.rings.fast_arith import prime_range
1619
from sage.misc.misc import prod
1620
for i in xrange(self.order()):
1621
for j in xrange(i):
1622
E = curves[i]
1623
F = curves[j]
1624
M = E.conductor()
1625
N = F.conductor()
1626
MN = lcm(M, N)
1627
p_MN = prime_divisors(MN)
1628
lim = prod([(j^(MN.ord(j)) + j^(MN.ord(j)-1)) for j in p_MN])
1629
a_E = E.anlist(lim)
1630
a_F = F.anlist(lim)
1631
l_list = [p for p in prime_range(lim) if p not in p_MN ]
1632
p_edges = l_list
1633
for l in l_list:
1634
n = a_E[l] - a_F[l]
1635
if n != 0:
1636
P = prime_divisors(n)
1637
p_edges = [p for p in p_edges if p in P]
1638
if len(p_edges) > 0:
1639
self.add_edge(E.cremona_label(), F.cremona_label(), str(p_edges)[1:-1])
1640
else:
1641
assert format == 'int'
1642
1643
self._pos = pos
1644
self._boundary = boundary if boundary is not None else []
1645
if format != 'Graph' or name is not None:
1646
self.name(name)
1647
1648
if data_structure == "static_sparse":
1649
from sage.graphs.base.static_sparse_backend import StaticSparseBackend
1650
ib = StaticSparseBackend(self, loops = loops, multiedges = multiedges)
1651
self._backend = ib
1652
self._immutable = True
1653
1654
### Formats
1655
def graph6_string(self):
1656
"""
1657
Returns the graph6 representation of the graph as an ASCII string.
1658
Only valid for simple (no loops, multiple edges) graphs on 0 to
1659
262143 vertices.
1660
1661
EXAMPLES::
1662
1663
sage: G = graphs.KrackhardtKiteGraph()
1664
sage: G.graph6_string()
1665
'IvUqwK@?G'
1666
"""
1667
n = self.order()
1668
if n > 262143:
1669
raise ValueError('graph6 format supports graphs on 0 to 262143 vertices only.')
1670
elif self.has_loops() or self.has_multiple_edges():
1671
raise ValueError('graph6 format supports only simple graphs (no loops, no multiple edges)')
1672
else:
1673
return generic_graph_pyx.N(n) + generic_graph_pyx.R(self._bit_vector())
1674
1675
def sparse6_string(self):
1676
"""
1677
Returns the sparse6 representation of the graph as an ASCII string.
1678
Only valid for undirected graphs on 0 to 262143 vertices, but loops
1679
and multiple edges are permitted.
1680
1681
EXAMPLES::
1682
1683
sage: G = graphs.BullGraph()
1684
sage: G.sparse6_string()
1685
':Da@en'
1686
1687
::
1688
1689
sage: G = Graph()
1690
sage: G.sparse6_string()
1691
':?'
1692
1693
::
1694
1695
sage: G = Graph(loops=True, multiedges=True,data_structure="sparse")
1696
sage: Graph(':?',data_structure="sparse") == G
1697
True
1698
"""
1699
n = self.order()
1700
if n == 0:
1701
return ':?'
1702
if n > 262143:
1703
raise ValueError('sparse6 format supports graphs on 0 to 262143 vertices only.')
1704
else:
1705
vertices = self.vertices()
1706
n = len(vertices)
1707
edges = self.edges(labels=False)
1708
for i in xrange(len(edges)): # replace edge labels with natural numbers (by index in vertices)
1709
edges[i] = (vertices.index(edges[i][0]),vertices.index(edges[i][1]))
1710
# order edges 'reverse lexicographically', that is, for
1711
# edge (a,b) and edge (c,d) first compare b and d, then a and c;
1712
edges.sort(key=lambda e: (e[1],e[0]))
1713
1714
# encode bit vector
1715
from math import ceil
1716
from sage.misc.functional import log
1717
k = int(ceil(log(n,2)))
1718
v = 0
1719
i = 0
1720
m = 0
1721
s = ''
1722
while m < len(edges):
1723
if edges[m][1] > v + 1:
1724
sp = generic_graph_pyx.binary(edges[m][1])
1725
sp = '0'*(k-len(sp)) + sp
1726
s += '1' + sp
1727
v = edges[m][1]
1728
elif edges[m][1] == v + 1:
1729
sp = generic_graph_pyx.binary(edges[m][0])
1730
sp = '0'*(k-len(sp)) + sp
1731
s += '1' + sp
1732
v += 1
1733
m += 1
1734
else:
1735
sp = generic_graph_pyx.binary(edges[m][0])
1736
sp = '0'*(k-len(sp)) + sp
1737
s += '0' + sp
1738
m += 1
1739
1740
# encode s as a 6-string, as in R(x), but padding with 1's
1741
# pad on the right to make a multiple of 6
1742
s = s + ( '1' * ((6 - len(s))%6) )
1743
1744
# split into groups of 6, and convert numbers to decimal, adding 63
1745
six_bits = ''
1746
for i in range(len(s)/6):
1747
six_bits += chr( int( s[6*i:6*(i+1)], 2) + 63 )
1748
return ':' + generic_graph_pyx.N(n) + six_bits
1749
1750
### Attributes
1751
1752
@combinatorial_map(name="partition of connected components")
1753
def to_partition(self):
1754
"""
1755
Return the partition of connected components of ``self``.
1756
1757
EXAMPLES::
1758
1759
sage: for x in graphs(3): print x.to_partition()
1760
[1, 1, 1]
1761
[2, 1]
1762
[3]
1763
[3]
1764
"""
1765
from sage.combinat.partition import Partition
1766
return Partition(sorted([len(y) for y in self.connected_components()], reverse=True))
1767
1768
def is_directed(self):
1769
"""
1770
Since graph is undirected, returns False.
1771
1772
EXAMPLES::
1773
1774
sage: Graph().is_directed()
1775
False
1776
"""
1777
return False
1778
1779
### Properties
1780
def is_tree(self, certificate=False, output='vertex'):
1781
"""
1782
Tests if the graph is a tree
1783
1784
INPUT:
1785
1786
- ``certificate`` (boolean) -- whether to return a certificate. The
1787
method only returns boolean answers when ``certificate = False``
1788
(default). When it is set to ``True``, it either answers ``(True,
1789
None)`` when the graph is a tree and ``(False, cycle)`` when it
1790
contains a cycle. It returns ``(False, None)`` when the graph is not
1791
connected.
1792
1793
- ``output`` (``'vertex'`` (default) or ``'edge'``) -- whether the
1794
certificate is given as a list of vertices or a list of
1795
edges.
1796
1797
When the certificate cycle is given as a list of edges, the
1798
edges are given as `(v_i, v_{i+1}, l)` where `v_1, v_2, \dots,
1799
v_n` are the vertices of the cycles (in their cyclic order).
1800
1801
EXAMPLES::
1802
1803
sage: all(T.is_tree() for T in graphs.trees(15))
1804
True
1805
1806
The empty graph is not considered to be a tree::
1807
1808
sage: graphs.EmptyGraph().is_tree()
1809
False
1810
1811
With certificates::
1812
1813
sage: g = graphs.RandomTree(30)
1814
sage: g.is_tree(certificate=True)
1815
(True, None)
1816
sage: g.add_edge(10,-1)
1817
sage: g.add_edge(11,-1)
1818
sage: isit, cycle = g.is_tree(certificate=True)
1819
sage: isit
1820
False
1821
sage: -1 in cycle
1822
True
1823
1824
One can also ask for the certificate as a list of edges::
1825
1826
sage: g = graphs.CycleGraph(4)
1827
sage: g.is_tree(certificate=True, output='edge')
1828
(False, [(3, 2, None), (2, 1, None), (1, 0, None), (0, 3, None)])
1829
1830
This is useful for graphs with multiple edges::
1831
1832
sage: G = Graph([(1, 2, 'a'), (1, 2, 'b')])
1833
sage: G.is_tree(certificate=True)
1834
(False, [1, 2])
1835
sage: G.is_tree(certificate=True, output='edge')
1836
(False, [(1, 2, 'a'), (2, 1, 'b')])
1837
1838
TESTS:
1839
1840
:trac:`14434` is fixed::
1841
1842
sage: g = Graph({0:[1,4,5],3:[4,8,9],4:[9],5:[7,8],7:[9]})
1843
sage: _,cycle = g.is_tree(certificate=True)
1844
sage: g.size()
1845
10
1846
sage: g.add_cycle(cycle)
1847
sage: g.size()
1848
10
1849
"""
1850
if not output in ['vertex', 'edge']:
1851
raise ValueError('output must be either vertex or edge')
1852
1853
if self.order() == 0:
1854
return False
1855
1856
if not self.is_connected():
1857
return (False, None) if certificate else False
1858
1859
if certificate:
1860
if self.num_verts() == self.num_edges() + 1:
1861
return (True, None)
1862
1863
if self.has_multiple_edges():
1864
if output == 'vertex':
1865
return (False, list(self.multiple_edges()[0][:2]))
1866
edge1, edge2 = self.multiple_edges()[:2]
1867
if edge1[0] != edge2[0]:
1868
return (False, [edge1, edge2])
1869
return (False, [edge1, (edge2[1], edge2[0], edge2[2])])
1870
1871
if output == 'edge':
1872
if self.allows_multiple_edges():
1873
def vertices_to_edges(x):
1874
return [(u[0], u[1], self.edge_label(u[0], u[1])[0])
1875
for u in zip(x, x[1:] + [x[0]])]
1876
else:
1877
def vertices_to_edges(x):
1878
return [(u[0], u[1], self.edge_label(u[0], u[1]))
1879
for u in zip(x, x[1:] + [x[0]])]
1880
1881
# This code is a depth-first search that looks for a cycle in the
1882
# graph. We *know* it exists as there are too many edges around.
1883
n = self.order()
1884
seen = {}
1885
u = self.vertex_iterator().next()
1886
seen[u] = u
1887
stack = [(u, v) for v in self.neighbor_iterator(u)]
1888
while stack:
1889
u, v = stack.pop(-1)
1890
if v in seen:
1891
continue
1892
for w in self.neighbors(v):
1893
if u == w:
1894
continue
1895
elif w in seen:
1896
cycle = [v, w]
1897
while u != w:
1898
cycle.insert(0, u)
1899
u = seen[u]
1900
if output == 'vertex':
1901
return (False, cycle)
1902
return (False, vertices_to_edges(cycle))
1903
else:
1904
stack.append((v, w))
1905
seen[v] = u
1906
1907
else:
1908
return self.num_verts() == self.num_edges() + 1
1909
1910
def is_forest(self, certificate=False, output='vertex'):
1911
"""
1912
Tests if the graph is a forest, i.e. a disjoint union of trees.
1913
1914
INPUT:
1915
1916
- ``certificate`` (boolean) -- whether to return a certificate. The
1917
method only returns boolean answers when ``certificate = False``
1918
(default). When it is set to ``True``, it either answers ``(True,
1919
None)`` when the graph is a forest and ``(False, cycle)`` when it
1920
contains a cycle.
1921
1922
- ``output`` (``'vertex'`` (default) or ``'edge'``) -- whether the
1923
certificate is given as a list of vertices or a list of
1924
edges.
1925
1926
EXAMPLES::
1927
1928
sage: seven_acre_wood = sum(graphs.trees(7), Graph())
1929
sage: seven_acre_wood.is_forest()
1930
True
1931
1932
With certificates::
1933
1934
sage: g = graphs.RandomTree(30)
1935
sage: g.is_forest(certificate=True)
1936
(True, None)
1937
sage: (2*g + graphs.PetersenGraph() + g).is_forest(certificate=True)
1938
(False, [63, 62, 61, 60, 64])
1939
"""
1940
number_of_connected_components = len(self.connected_components())
1941
isit = (self.num_verts() ==
1942
self.num_edges() + number_of_connected_components)
1943
1944
if not certificate:
1945
return isit
1946
else:
1947
if isit:
1948
return (True, None)
1949
# The graph contains a cycle, and the user wants to see it.
1950
1951
# No need to copy the graph
1952
if number_of_connected_components == 1:
1953
return self.is_tree(certificate=True, output=output)
1954
1955
# We try to find a cycle in each connected component
1956
for gg in self.connected_components_subgraphs():
1957
isit, cycle = gg.is_tree(certificate=True, output=output)
1958
if not isit:
1959
return (False, cycle)
1960
1961
def is_overfull(self):
1962
r"""
1963
Tests whether the current graph is overfull.
1964
1965
A graph `G` on `n` vertices and `m` edges is said to
1966
be overfull if:
1967
1968
- `n` is odd
1969
1970
- It satisfies `2m > (n-1)\Delta(G)`, where
1971
`\Delta(G)` denotes the maximum degree
1972
among all vertices in `G`.
1973
1974
An overfull graph must have a chromatic index of `\Delta(G)+1`.
1975
1976
EXAMPLES:
1977
1978
A complete graph of order `n > 1` is overfull if and only if `n` is
1979
odd::
1980
1981
sage: graphs.CompleteGraph(6).is_overfull()
1982
False
1983
sage: graphs.CompleteGraph(7).is_overfull()
1984
True
1985
sage: graphs.CompleteGraph(1).is_overfull()
1986
False
1987
1988
The claw graph is not overfull::
1989
1990
sage: from sage.graphs.graph_coloring import edge_coloring
1991
sage: g = graphs.ClawGraph()
1992
sage: g
1993
Claw graph: Graph on 4 vertices
1994
sage: edge_coloring(g, value_only=True)
1995
3
1996
sage: g.is_overfull()
1997
False
1998
1999
The Holt graph is an example of a overfull graph::
2000
2001
sage: G = graphs.HoltGraph()
2002
sage: G.is_overfull()
2003
True
2004
2005
Checking that all complete graphs `K_n` for even `0 \leq n \leq 100`
2006
are not overfull::
2007
2008
sage: def check_overfull_Kn_even(n):
2009
... i = 0
2010
... while i <= n:
2011
... if graphs.CompleteGraph(i).is_overfull():
2012
... print "A complete graph of even order cannot be overfull."
2013
... return
2014
... i += 2
2015
... print "Complete graphs of even order up to %s are not overfull." % n
2016
...
2017
sage: check_overfull_Kn_even(100) # long time
2018
Complete graphs of even order up to 100 are not overfull.
2019
2020
The null graph, i.e. the graph with no vertices, is not overfull::
2021
2022
sage: Graph().is_overfull()
2023
False
2024
sage: graphs.CompleteGraph(0).is_overfull()
2025
False
2026
2027
Checking that all complete graphs `K_n` for odd `1 < n \leq 100`
2028
are overfull::
2029
2030
sage: def check_overfull_Kn_odd(n):
2031
... i = 3
2032
... while i <= n:
2033
... if not graphs.CompleteGraph(i).is_overfull():
2034
... print "A complete graph of odd order > 1 must be overfull."
2035
... return
2036
... i += 2
2037
... print "Complete graphs of odd order > 1 up to %s are overfull." % n
2038
...
2039
sage: check_overfull_Kn_odd(100) # long time
2040
Complete graphs of odd order > 1 up to 100 are overfull.
2041
2042
The Petersen Graph, though, is not overfull while
2043
its chromatic index is `\Delta+1`::
2044
2045
sage: g = graphs.PetersenGraph()
2046
sage: g.is_overfull()
2047
False
2048
sage: from sage.graphs.graph_coloring import edge_coloring
2049
sage: max(g.degree()) + 1 == edge_coloring(g, value_only=True)
2050
True
2051
"""
2052
# # A possible optimized version. But the gain in speed is very little.
2053
# return bool(self._backend.num_verts() & 1) and ( # odd order n
2054
# 2 * self._backend.num_edges(self._directed) > #2m > \Delta(G)*(n-1)
2055
# max(self.degree()) * (self._backend.num_verts() - 1))
2056
# unoptimized version
2057
return (self.order() % 2 == 1) and (
2058
2 * self.size() > max(self.degree()) * (self.order() - 1))
2059
2060
def is_even_hole_free(self, certificate = False):
2061
r"""
2062
Tests whether ``self`` contains an induced even hole.
2063
2064
A Hole is a cycle of length at least 4 (included). It is said
2065
to be even (resp. odd) if its length is even (resp. odd).
2066
2067
Even-hole-free graphs always contain a bisimplicial vertex,
2068
which ensures that their chromatic number is at most twice
2069
their clique number [ABCHRS08]_.
2070
2071
INPUT:
2072
2073
- ``certificate`` (boolean) -- When ``certificate = False``,
2074
this method only returns ``True`` or ``False``. If
2075
``certificate = True``, the subgraph found is returned
2076
instead of ``False``.
2077
2078
EXAMPLE:
2079
2080
Is the Petersen Graph even-hole-free ::
2081
2082
sage: g = graphs.PetersenGraph()
2083
sage: g.is_even_hole_free()
2084
False
2085
2086
As any chordal graph is hole-free, interval graphs behave the
2087
same way::
2088
2089
sage: g = graphs.RandomIntervalGraph(20)
2090
sage: g.is_even_hole_free()
2091
True
2092
2093
It is clear, though, that a random Bipartite Graph which is
2094
not a forest has an even hole::
2095
2096
sage: g = graphs.RandomBipartite(10, 10, .5)
2097
sage: g.is_even_hole_free() and not g.is_forest()
2098
False
2099
2100
We can check the certificate returned is indeed an even
2101
cycle::
2102
2103
sage: if not g.is_forest():
2104
... cycle = g.is_even_hole_free(certificate = True)
2105
... if cycle.order() % 2 == 1:
2106
... print "Error !"
2107
... if not cycle.is_isomorphic(
2108
... graphs.CycleGraph(cycle.order())):
2109
... print "Error !"
2110
...
2111
sage: print "Everything is Fine !"
2112
Everything is Fine !
2113
2114
TESTS:
2115
2116
Bug reported in :trac:`9925`, and fixed by :trac:`9420`::
2117
2118
sage: g = Graph(':SiBFGaCEF_@CE`DEGH`CEFGaCDGaCDEHaDEF`CEH`ABCDEF', loops=False, multiedges=False)
2119
sage: g.is_even_hole_free()
2120
False
2121
sage: g.is_even_hole_free(certificate = True)
2122
Subgraph of (): Graph on 4 vertices
2123
2124
Making sure there are no other counter-examples around ::
2125
2126
sage: t = lambda x : (Graph(x).is_forest() or
2127
... isinstance(Graph(x).is_even_hole_free(certificate = True),Graph))
2128
sage: all( t(graphs.RandomBipartite(10,10,.5)) for i in range(100) )
2129
True
2130
2131
REFERENCE:
2132
2133
.. [ABCHRS08] L. Addario-Berry, M. Chudnovsky, F. Havet, B. Reed, P. Seymour
2134
Bisimplicial vertices in even-hole-free graphs
2135
Journal of Combinatorial Theory, Series B
2136
vol 98, n.6 pp 1119-1164, 2008
2137
"""
2138
from sage.graphs.graph_generators import GraphGenerators
2139
2140
girth = self.girth()
2141
2142
if girth > self.order():
2143
start = 4
2144
2145
elif girth % 2 == 0:
2146
if not certificate:
2147
return False
2148
start = girth
2149
2150
else:
2151
start = girth + 1
2152
2153
while start <= self.order():
2154
2155
2156
subgraph = self.subgraph_search(GraphGenerators().CycleGraph(start), induced = True)
2157
2158
if not subgraph is None:
2159
if certificate:
2160
return subgraph
2161
else:
2162
return False
2163
2164
start = start + 2
2165
2166
return True
2167
2168
def is_odd_hole_free(self, certificate = False):
2169
r"""
2170
Tests whether ``self`` contains an induced odd hole.
2171
2172
A Hole is a cycle of length at least 4 (included). It is said
2173
to be even (resp. odd) if its length is even (resp. odd).
2174
2175
It is interesting to notice that while it is polynomial to
2176
check whether a graph has an odd hole or an odd antihole [CRST06]_, it is
2177
not known whether testing for one of these two cases
2178
independently is polynomial too.
2179
2180
INPUT:
2181
2182
- ``certificate`` (boolean) -- When ``certificate = False``,
2183
this method only returns ``True`` or ``False``. If
2184
``certificate = True``, the subgraph found is returned
2185
instead of ``False``.
2186
2187
EXAMPLE:
2188
2189
Is the Petersen Graph odd-hole-free ::
2190
2191
sage: g = graphs.PetersenGraph()
2192
sage: g.is_odd_hole_free()
2193
False
2194
2195
Which was to be expected, as its girth is 5 ::
2196
2197
sage: g.girth()
2198
5
2199
2200
We can check the certificate returned is indeed a 5-cycle::
2201
2202
sage: cycle = g.is_odd_hole_free(certificate = True)
2203
sage: cycle.is_isomorphic(graphs.CycleGraph(5))
2204
True
2205
2206
As any chordal graph is hole-free, no interval graph has an odd hole::
2207
2208
sage: g = graphs.RandomIntervalGraph(20)
2209
sage: g.is_odd_hole_free()
2210
True
2211
2212
REFERENCES:
2213
2214
.. [CRST06] M. Chudnovsky, G. Cornuejols, X. Liu, P. Seymour, K. Vuskovic
2215
Recognizing berge graphs
2216
Combinatorica vol 25, n 2, pages 143--186
2217
2005
2218
"""
2219
from sage.graphs.graph_generators import GraphGenerators
2220
2221
girth = self.odd_girth()
2222
2223
if girth > self.order():
2224
return True
2225
if girth == 3:
2226
start = 5
2227
2228
else:
2229
if not certificate:
2230
return False
2231
start = girth
2232
2233
while start <= self.order():
2234
2235
subgraph = self.subgraph_search(GraphGenerators().CycleGraph(start), induced = True)
2236
2237
if not subgraph is None:
2238
if certificate:
2239
return subgraph
2240
else:
2241
return False
2242
2243
start += 2
2244
2245
return True
2246
2247
def is_bipartite(self, certificate = False):
2248
"""
2249
Returns ``True`` if graph `G` is bipartite, ``False`` if not.
2250
2251
Traverse the graph G with breadth-first-search and color nodes.
2252
2253
INPUT:
2254
2255
- ``certificate`` -- whether to return a certificate (``False`` by
2256
default). If set to ``True``, the certificate returned in a proper
2257
2-coloring when `G` is bipartite, and an odd cycle otherwise.
2258
2259
EXAMPLES::
2260
2261
sage: graphs.CycleGraph(4).is_bipartite()
2262
True
2263
sage: graphs.CycleGraph(5).is_bipartite()
2264
False
2265
sage: graphs.RandomBipartite(100,100,0.7).is_bipartite()
2266
True
2267
2268
A random graph is very rarely bipartite::
2269
2270
sage: g = graphs.PetersenGraph()
2271
sage: g.is_bipartite()
2272
False
2273
sage: false, oddcycle = g.is_bipartite(certificate = True)
2274
sage: len(oddcycle) % 2
2275
1
2276
"""
2277
color = {}
2278
2279
# For any uncolored vertex in the graph (to ensure we do the right job
2280
# when the graph is not connected !)
2281
for u in self:
2282
if u in color:
2283
continue
2284
2285
# Let us run a BFS starting from u
2286
queue = [u]
2287
color[u] = 1
2288
while queue:
2289
v = queue.pop(0)
2290
c = 1-color[v]
2291
for w in self.neighbor_iterator(v):
2292
2293
# If the vertex has already been colored
2294
if w in color:
2295
2296
# The graph is not bipartite !
2297
if color[w] == color[v]:
2298
2299
# Should we return an odd cycle ?
2300
if certificate:
2301
2302
# We build the first half of the cycle, i.e. a
2303
# u-w path
2304
cycle = self.shortest_path(u,w)
2305
2306
# The second half is a v-u path, but there may
2307
# be common vertices in the two paths. But we
2308
# can avoid that !
2309
2310
for v in self.shortest_path(v,u):
2311
if v in cycle:
2312
return False, cycle[cycle.index(v):]
2313
else:
2314
cycle.append(v)
2315
else:
2316
return False
2317
2318
# We color a new vertex
2319
else:
2320
color[w] = c
2321
queue.append(w)
2322
if certificate:
2323
return True, color
2324
else:
2325
return True
2326
2327
def is_triangle_free(self, algorithm='bitset'):
2328
r"""
2329
Returns whether ``self`` is triangle-free
2330
2331
INPUT:
2332
2333
- ``algorithm`` -- (default: ``'bitset'``) specifies the algorithm to
2334
use among:
2335
2336
- ``'matrix'`` -- tests if the trace of the adjacency matrix is
2337
positive.
2338
2339
- ``'bitset'`` -- encodes adjacencies into bitsets and uses fast
2340
bitset operations to test if the input graph contains a
2341
triangle. This method is generaly faster than stantard matrix
2342
multiplication.
2343
2344
EXAMPLE:
2345
2346
The Petersen Graph is triangle-free::
2347
2348
sage: g = graphs.PetersenGraph()
2349
sage: g.is_triangle_free()
2350
True
2351
2352
or a complete Bipartite Graph::
2353
2354
sage: G = graphs.CompleteBipartiteGraph(5,6)
2355
sage: G.is_triangle_free(algorithm='matrix')
2356
True
2357
sage: G.is_triangle_free(algorithm='bitset')
2358
True
2359
2360
a tripartite graph, though, contains many triangles::
2361
2362
sage: G = (3 * graphs.CompleteGraph(5)).complement()
2363
sage: G.is_triangle_free(algorithm='matrix')
2364
False
2365
sage: G.is_triangle_free(algorithm='bitset')
2366
False
2367
2368
TESTS:
2369
2370
Comparison of algorithms::
2371
2372
sage: for i in xrange(10): # long test
2373
... G = graphs.RandomBarabasiAlbert(50,2)
2374
... bm = G.is_triangle_free(algorithm='matrix')
2375
... bb = G.is_triangle_free(algorithm='bitset')
2376
... if bm != bb:
2377
... print "That's not good!"
2378
2379
Asking for an unknown algorithm::
2380
sage: g.is_triangle_free(algorithm='tip top')
2381
Traceback (most recent call last):
2382
...
2383
ValueError: Algorithm 'tip top' not yet implemented. Please contribute.
2384
"""
2385
if algorithm=='bitset':
2386
from sage.misc.bitset import Bitset
2387
N = self.num_verts()
2388
map = {}
2389
i = 0
2390
B = {}
2391
for u in self.vertex_iterator():
2392
map[u] = i
2393
i += 1
2394
B[u] = Bitset(capacity=N)
2395
# map adjacency to bitsets
2396
for u,v in self.edge_iterator(labels=None):
2397
B[u].add(map[v])
2398
B[v].add(map[u])
2399
# map lengths 2 paths to bitsets
2400
BB = Bitset(capacity=N)
2401
for u in self.vertex_iterator():
2402
BB.clear()
2403
for v in self.vertex_iterator():
2404
if B[u]&B[v]:
2405
BB.add(map[v])
2406
# search for triangles
2407
if B[u]&BB:
2408
return False
2409
return True
2410
2411
elif algorithm=='matrix':
2412
return (self.adjacency_matrix()**3).trace() == 0
2413
2414
else:
2415
raise ValueError("Algorithm '%s' not yet implemented. Please contribute." %(algorithm))
2416
2417
def is_split(self):
2418
r"""
2419
Returns ``True`` if the graph is a Split graph, ``False`` otherwise.
2420
2421
A Graph `G` is said to be a split graph if its vertices `V(G)`
2422
can be partitioned into two sets `K` and `I` such that the
2423
vertices of `K` induce a complete graph, and those of `I` are
2424
an independent set.
2425
2426
There is a simple test to check whether a graph is a split
2427
graph (see, for instance, the book "Graph Classes, a survey"
2428
[GraphClasses]_ page 203) :
2429
2430
Given the degree sequence `d_1 \geq ... \geq d_n` of `G`, a graph
2431
is a split graph if and only if :
2432
2433
.. MATH::
2434
2435
\sum_{i=1}^\omega d_i = \omega (\omega - 1) + \sum_{i=\omega + 1}^nd_i
2436
2437
where `\omega = max \{i:d_i\geq i-1\}`.
2438
2439
2440
EXAMPLES:
2441
2442
Split graphs are, in particular, chordal graphs. Hence, The Petersen graph
2443
can not be split::
2444
2445
sage: graphs.PetersenGraph().is_split()
2446
False
2447
2448
We can easily build some "random" split graph by creating a
2449
complete graph, and adding vertices only connected
2450
to some random vertices of the clique::
2451
2452
sage: g = graphs.CompleteGraph(10)
2453
sage: sets = Subsets(Set(range(10)))
2454
sage: for i in range(10, 25):
2455
... g.add_edges([(i,k) for k in sets.random_element()])
2456
sage: g.is_split()
2457
True
2458
2459
Another caracterisation of split graph states that a graph is a split graph
2460
if and only if does not contain the 4-cycle, 5-cycle or 2K_2 as an induced
2461
subgraph. Hence for the above graph we have::
2462
2463
sage: sum([g.subgraph_search_count(H,induced=True) for H in [graphs.CycleGraph(4),graphs.CycleGraph(5), 2*graphs.CompleteGraph(2)]])
2464
0
2465
2466
2467
REFERENCES:
2468
2469
.. [GraphClasses] A. Brandstadt, VB Le and JP Spinrad
2470
Graph classes: a survey
2471
SIAM Monographs on Discrete Mathematics and Applications},
2472
1999
2473
"""
2474
self._scream_if_not_simple()
2475
# our degree sequence is numbered from 0 to n-1, so to avoid
2476
# any mistake, let's fix it :-)
2477
degree_sequence = [0] + sorted(self.degree(), reverse = True)
2478
2479
for (i, d) in enumerate(degree_sequence):
2480
if d >= i - 1:
2481
omega = i
2482
else:
2483
break
2484
2485
left = sum(degree_sequence[:omega + 1])
2486
right = omega * (omega - 1) + sum(degree_sequence[omega + 1:])
2487
2488
return left == right
2489
2490
def is_perfect(self, certificate = False):
2491
r"""
2492
Tests whether the graph is perfect.
2493
2494
A graph `G` is said to be perfect if `\chi(H)=\omega(H)` hold
2495
for any induced subgraph `H\subseteq_i G` (and so for `G`
2496
itself, too), where `\chi(H)` represents the chromatic number
2497
of `H`, and `\omega(H)` its clique number. The Strong Perfect
2498
Graph Theorem [SPGT]_ gives another characterization of
2499
perfect graphs:
2500
2501
A graph is perfect if and only if it contains no odd hole
2502
(cycle on an odd number `k` of vertices, `k>3`) nor any odd
2503
antihole (complement of a hole) as an induced subgraph.
2504
2505
INPUT:
2506
2507
- ``certificate`` (boolean) -- whether to return
2508
a certificate (default : ``False``)
2509
2510
OUTPUT:
2511
2512
When ``certificate = False``, this function returns
2513
a boolean value. When ``certificate = True``, it returns
2514
a subgraph of ``self`` isomorphic to an odd hole or an odd
2515
antihole if any, and ``None`` otherwise.
2516
2517
EXAMPLE:
2518
2519
A Bipartite Graph is always perfect ::
2520
2521
sage: g = graphs.RandomBipartite(8,4,.5)
2522
sage: g.is_perfect()
2523
True
2524
2525
So is the line graph of a bipartite graph::
2526
2527
sage: g = graphs.RandomBipartite(4,3,0.7)
2528
sage: g.line_graph().is_perfect() # long time
2529
True
2530
2531
As well as the Cartesian product of two complete graphs::
2532
2533
sage: g = graphs.CompleteGraph(3).cartesian_product(graphs.CompleteGraph(3))
2534
sage: g.is_perfect()
2535
True
2536
2537
Interval Graphs, which are chordal graphs, too ::
2538
2539
sage: g = graphs.RandomIntervalGraph(7)
2540
sage: g.is_perfect()
2541
True
2542
2543
The PetersenGraph, which is triangle-free and
2544
has chromatic number 3 is obviously not perfect::
2545
2546
sage: g = graphs.PetersenGraph()
2547
sage: g.is_perfect()
2548
False
2549
2550
We can obtain an induced 5-cycle as a certificate::
2551
2552
sage: g.is_perfect(certificate = True)
2553
Subgraph of (Petersen graph): Graph on 5 vertices
2554
2555
TEST:
2556
2557
Check that :trac:`13546` has been fixed::
2558
2559
sage: Graph(':FgGE@I@GxGs', loops=False, multiedges=False).is_perfect()
2560
False
2561
sage: g = Graph({0: [2, 3, 4, 5],
2562
... 1: [3, 4, 5, 6],
2563
... 2: [0, 4, 5, 6],
2564
... 3: [0, 1, 5, 6],
2565
... 4: [0, 1, 2, 6],
2566
... 5: [0, 1, 2, 3],
2567
... 6: [1, 2, 3, 4]})
2568
sage: g.is_perfect()
2569
False
2570
2571
REFERENCES:
2572
2573
.. [SPGT] M. Chudnovsky, N. Robertson, P. Seymour, R. Thomas.
2574
The strong perfect graph theorem
2575
Annals of Mathematics
2576
vol 164, number 1, pages 51--230
2577
2006
2578
2579
TESTS::
2580
2581
sage: Graph(':Ab').is_perfect()
2582
Traceback (most recent call last):
2583
...
2584
ValueError: This method is only defined for simple graphs, and yours is not one of them !
2585
sage: g = Graph()
2586
sage: g.allow_loops(True)
2587
sage: g.add_edge(0,0)
2588
sage: g.edges()
2589
[(0, 0, None)]
2590
sage: g.is_perfect()
2591
Traceback (most recent call last):
2592
...
2593
ValueError: This method is only defined for simple graphs, and yours is not one of them !
2594
2595
"""
2596
2597
if self.has_multiple_edges() or self.has_loops():
2598
raise ValueError("This method is only defined for simple graphs,"
2599
" and yours is not one of them !")
2600
if self.is_bipartite():
2601
2602
return True if not certificate else None
2603
2604
self_complement = self.complement()
2605
2606
self_complement.remove_loops()
2607
self_complement.remove_multiple_edges()
2608
2609
if self_complement.is_bipartite():
2610
return True if not certificate else None
2611
2612
answer = self.is_odd_hole_free(certificate = certificate)
2613
if not (answer is True):
2614
return answer
2615
2616
return self_complement.is_odd_hole_free(certificate = certificate)
2617
2618
def odd_girth(self):
2619
r"""
2620
Returns the odd girth of self.
2621
2622
The odd girth of a graph is defined as the smallest cycle of odd length.
2623
2624
OUTPUT:
2625
2626
The odd girth of ``self``.
2627
2628
EXAMPLES:
2629
2630
The McGee graph has girth 7 and therefore its odd girth is 7 as well. ::
2631
2632
sage: G = graphs.McGeeGraph()
2633
sage: G.odd_girth()
2634
7
2635
2636
Any complete graph on more than 2 vertices contains a triangle and has
2637
thus odd girth 3. ::
2638
2639
sage: G = graphs.CompleteGraph(10)
2640
sage: G.odd_girth()
2641
3
2642
2643
Every bipartite graph has no odd cycles and consequently odd girth of
2644
infinity. ::
2645
2646
sage: G = graphs.CompleteBipartiteGraph(100,100)
2647
sage: G.odd_girth()
2648
+Infinity
2649
2650
.. SEEALSO::
2651
2652
* :meth:`~sage.graphs.generic_graph.GenericGraph.girth` -- computes
2653
the girth of a graph.
2654
2655
REFERENCES:
2656
2657
The property relating the odd girth to the coefficients of the
2658
characteristic polynomial is an old result from algebraic graph theory
2659
see
2660
2661
.. [Har62] Harary, F (1962). The determinant of the adjacency matrix of
2662
a graph, SIAM Review 4, 202-210
2663
2664
.. [Biggs93] Biggs, N. L. Algebraic Graph Theory, 2nd ed. Cambridge,
2665
England: Cambridge University Press, pp. 45, 1993.
2666
2667
TESTS::
2668
2669
sage: graphs.CycleGraph(5).odd_girth()
2670
5
2671
sage: graphs.CycleGraph(11).odd_girth()
2672
11
2673
"""
2674
ch = ((self.am()).charpoly()).coeffs()
2675
n = self.order()
2676
2677
for i in xrange(n-1,-1,-2):
2678
if ch[i] != 0:
2679
return n-i
2680
2681
from sage.rings.infinity import Infinity
2682
2683
return Infinity
2684
2685
def is_edge_transitive(self):
2686
"""
2687
Returns true if self is an edge transitive graph.
2688
2689
A graph is edge-transitive if its automorphism group acts transitively
2690
on its edge set.
2691
2692
Equivalently, if there exists for any pair of edges `uv,u'v'\in E(G)` an
2693
automorphism `\phi` of `G` such that `\phi(uv)=u'v'` (note this does not
2694
necessarily mean that `\phi(u)=u'` and `\phi(v)=v'`).
2695
2696
See :wikipedia:`the wikipedia article on edge-transitive graphs
2697
<Edge-transitive_graph>` for more information.
2698
2699
.. SEEALSO::
2700
2701
- :meth:`~Graph.is_arc_transitive`
2702
- :meth:`~Graph.is_half_transitive`
2703
- :meth:`~Graph.is_semi_symmetric`
2704
2705
EXAMPLES::
2706
2707
sage: P = graphs.PetersenGraph()
2708
sage: P.is_edge_transitive()
2709
True
2710
sage: C = graphs.CubeGraph(3)
2711
sage: C.is_edge_transitive()
2712
True
2713
sage: G = graphs.GrayGraph()
2714
sage: G.is_edge_transitive()
2715
True
2716
sage: P = graphs.PathGraph(4)
2717
sage: P.is_edge_transitive()
2718
False
2719
"""
2720
from sage.interfaces.gap import gap
2721
2722
if self.size() == 0:
2723
return True
2724
2725
A = self.automorphism_group()
2726
e = self.edge_iterator(labels=False).next()
2727
e = [A._domain_to_gap[e[0]], A._domain_to_gap[e[1]]]
2728
2729
return gap("OrbitLength("+str(A._gap_())+",Set(" + str(e) + "),OnSets);") == self.size()
2730
2731
def is_arc_transitive(self):
2732
"""
2733
Returns true if self is an arc-transitive graph
2734
2735
A graph is arc-transitive if its automorphism group acts transitively on
2736
its pairs of adjacent vertices.
2737
2738
Equivalently, if there exists for any pair of edges `uv,u'v'\in E(G)` an
2739
automorphism `\phi_1` of `G` such that `\phi_1(u)=u'` and
2740
`\phi_1(v)=v'`, as well as another automorphism `\phi_2` of `G` such
2741
that `\phi_2(u)=v'` and `\phi_2(v)=u'`
2742
2743
See :wikipedia:`the wikipedia article on arc-transitive graphs
2744
<arc-transitive_graph>` for more information.
2745
2746
.. SEEALSO::
2747
2748
- :meth:`~Graph.is_edge_transitive`
2749
- :meth:`~Graph.is_half_transitive`
2750
- :meth:`~Graph.is_semi_symmetric`
2751
2752
EXAMPLES::
2753
2754
sage: P = graphs.PetersenGraph()
2755
sage: P.is_arc_transitive()
2756
True
2757
sage: G = graphs.GrayGraph()
2758
sage: G.is_arc_transitive()
2759
False
2760
"""
2761
2762
from sage.interfaces.gap import gap
2763
2764
if self.size() == 0:
2765
return True
2766
2767
A = self.automorphism_group()
2768
e = self.edge_iterator(labels=False).next()
2769
e = [A._domain_to_gap[e[0]], A._domain_to_gap[e[1]]]
2770
2771
return gap("OrbitLength("+str(A._gap_())+",Set(" + str(e) + "),OnTuples);") == 2*self.size()
2772
2773
def is_half_transitive(self):
2774
"""
2775
Returns true if self is a half-transitive graph.
2776
2777
A graph is is half-transitive if it is both vertex and edge transitive
2778
but not arc-transitive.
2779
2780
See :wikipedia:`the wikipedia article on half-transitive graphs
2781
<half-transitive_graph>` for more information.
2782
2783
.. SEEALSO::
2784
2785
- :meth:`~Graph.is_edge_transitive`
2786
- :meth:`~Graph.is_arc_transitive`
2787
- :meth:`~Graph.is_semi_symmetric`
2788
2789
EXAMPLES:
2790
2791
The Petersen Graph is not half-transitive::
2792
2793
sage: P = graphs.PetersenGraph()
2794
sage: P.is_half_transitive()
2795
False
2796
2797
The smallest half-transitive graph is the Holt Graph::
2798
2799
sage: H = graphs.HoltGraph()
2800
sage: H.is_half_transitive()
2801
True
2802
"""
2803
2804
# A half-transitive graph always has only vertices of even degree
2805
if not all(d%2 == 0 for d in self.degree_iterator()):
2806
return False
2807
2808
return (self.is_edge_transitive() and
2809
self.is_vertex_transitive() and
2810
not self.is_arc_transitive())
2811
2812
def is_semi_symmetric(self):
2813
"""
2814
Returns true if self is semi-symmetric.
2815
2816
A graph is semi-symmetric if it is regular, edge-transitve but not
2817
vertex-transitive.
2818
2819
See :wikipedia:`the wikipedia article on semi-symmetric graphs
2820
<Semi-symmetric_graph>` for more information.
2821
2822
.. SEEALSO::
2823
2824
- :meth:`~Graph.is_edge_transitive`
2825
- :meth:`~Graph.is_arc_transitive`
2826
- :meth:`~Graph.is_half_transitive`
2827
2828
EXAMPLES:
2829
2830
The Petersen graph is not semi-symmetric::
2831
2832
sage: P = graphs.PetersenGraph()
2833
sage: P.is_semi_symmetric()
2834
False
2835
2836
The Gray graph is the smallest possible semi-symmetric graph::
2837
2838
sage: G = graphs.GrayGraph()
2839
sage: G.is_semi_symmetric()
2840
True
2841
2842
Another well known semi-symmetric graph is the Ljubljana graph::
2843
2844
sage: L = graphs.LjubljanaGraph()
2845
sage: L.is_semi_symmetric()
2846
True
2847
"""
2848
# A semi-symmetric graph is always bipartite
2849
if not self.is_bipartite() :
2850
return False
2851
2852
return (self.is_regular() and
2853
self.is_edge_transitive() and not
2854
self.is_vertex_transitive())
2855
2856
def degree_constrained_subgraph(self, bounds=None, solver=None, verbose=0):
2857
r"""
2858
Returns a degree-constrained subgraph.
2859
2860
Given a graph `G` and two functions `f, g:V(G)\rightarrow \mathbb Z`
2861
such that `f \leq g`, a degree-constrained subgraph in `G` is
2862
a subgraph `G' \subseteq G` such that for any vertex `v \in G`,
2863
`f(v) \leq d_{G'}(v) \leq g(v)`.
2864
2865
INPUT:
2866
2867
- ``bounds`` -- (default: ``None``) Two possibilities:
2868
2869
- A dictionary whose keys are the vertices, and values a pair of
2870
real values ``(min,max)`` corresponding to the values
2871
`(f(v),g(v))`.
2872
2873
- A function associating to each vertex a pair of
2874
real values ``(min,max)`` corresponding to the values
2875
`(f(v),g(v))`.
2876
2877
2878
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
2879
solver to be used. If set to ``None``, the default one is used. For
2880
more information on LP solvers and which default solver is used, see
2881
the method
2882
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
2883
of the class
2884
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
2885
2886
- ``verbose`` -- integer (default: ``0``). Sets the level of
2887
verbosity. Set to 0 by default, which means quiet.
2888
2889
OUTPUT:
2890
2891
- When a solution exists, this method outputs the degree-constained
2892
subgraph as a Graph object.
2893
2894
- When no solution exists, returns ``False``.
2895
2896
.. NOTE::
2897
2898
- This algorithm computes the degree-constrained subgraph of minimum weight.
2899
- If the graph's edges are weighted, these are taken into account.
2900
- This problem can be solved in polynomial time.
2901
2902
EXAMPLES:
2903
2904
Is there a perfect matching in an even cycle? ::
2905
2906
sage: g = graphs.CycleGraph(6)
2907
sage: bounds = lambda x: [1,1]
2908
sage: m = g.degree_constrained_subgraph(bounds=bounds)
2909
sage: m.size()
2910
3
2911
"""
2912
self._scream_if_not_simple()
2913
from sage.numerical.mip import MixedIntegerLinearProgram, MIPSolverException
2914
2915
p = MixedIntegerLinearProgram(maximization=False, solver=solver)
2916
b = p.new_variable()
2917
2918
reorder = lambda x,y: (x,y) if x<y else (y,x)
2919
2920
if bounds is None:
2921
raise ValueError,"The `bounds` keyword can not be equal to None"
2922
elif isinstance(bounds,dict):
2923
f_bounds = lambda x: bounds[x]
2924
else:
2925
f_bounds = bounds
2926
2927
2928
if self.weighted():
2929
from sage.rings.real_mpfr import RR
2930
weight = lambda x: x if x in RR else 1
2931
else:
2932
weight = lambda x: 1
2933
2934
for v in self:
2935
minimum,maximum = f_bounds(v)
2936
p.add_constraint(p.sum([ b[reorder(x,y)]*weight(l) for x,y,l in self.edges_incident(v)]), min=minimum, max=maximum)
2937
2938
p.set_objective(p.sum([ b[reorder(x,y)]*weight(l) for x,y,l in self.edge_iterator()]))
2939
p.set_binary(b)
2940
2941
try:
2942
p.solve(log=verbose)
2943
g = self.copy()
2944
b = p.get_values(b)
2945
g.delete_edges([(x,y) for x,y,_ in g.edge_iterator() if b[reorder(x,y)] < 0.5])
2946
return g
2947
2948
2949
except MIPSolverException:
2950
return False
2951
2952
2953
### Orientations
2954
2955
def strong_orientation(self):
2956
r"""
2957
Returns a strongly connected orientation of the current graph. See
2958
also the :wikipedia:`Strongly_connected_component`.
2959
2960
An orientation of an undirected graph is a digraph obtained by
2961
giving an unique direction to each of its edges. An orientation
2962
is said to be strong if there is a directed path between each
2963
pair of vertices.
2964
2965
If the graph is 2-edge-connected, a strongly connected orientation
2966
can be found in linear time. If the given graph is not 2-connected,
2967
the orientation returned will ensure that each 2-connected component
2968
has a strongly connected orientation.
2969
2970
OUTPUT:
2971
2972
A digraph representing an orientation of the current graph.
2973
2974
.. NOTE::
2975
2976
- This method assumes the graph is connected.
2977
- This algorithm works in O(m).
2978
2979
EXAMPLE:
2980
2981
For a 2-regular graph, a strong orientation gives to each vertex
2982
an out-degree equal to 1::
2983
2984
sage: g = graphs.CycleGraph(5)
2985
sage: g.strong_orientation().out_degree()
2986
[1, 1, 1, 1, 1]
2987
2988
The Petersen Graph is 2-edge connected. It then has a strongly
2989
connected orientation::
2990
2991
sage: g = graphs.PetersenGraph()
2992
sage: o = g.strong_orientation()
2993
sage: len(o.strongly_connected_components())
2994
1
2995
2996
The same goes for the CubeGraph in any dimension ::
2997
2998
2999
sage: all(len(graphs.CubeGraph(i).strong_orientation().strongly_connected_components()) == 1 for i in xrange(2,6))
3000
True
3001
"""
3002
from sage.graphs.all import DiGraph
3003
d = DiGraph(multiedges=self.allows_multiple_edges())
3004
3005
id = {}
3006
i = 0
3007
3008
# The algorithm works through a depth-first search. Any edge
3009
# used in the depth-first search is oriented in the direction
3010
# in which it has been used. All the other edges are oriented
3011
# backward
3012
3013
v = self.vertex_iterator().next()
3014
seen = {}
3015
i=1
3016
3017
# Time at which the vertices have been discovered
3018
seen[v] = i
3019
3020
# indicates the stack of edges to explore
3021
next = self.edges_incident(v)
3022
3023
while next:
3024
e = next.pop(-1)
3025
# We assume e[0] to be a `seen` vertex
3026
e = e if seen.get(e[0],False) != False else (e[1],e[0],e[2])
3027
3028
# If we discovered a new vertex
3029
if seen.get(e[1],False) == False:
3030
d.add_edge(e)
3031
next.extend([ee for ee in self.edges_incident(e[1]) if (((e[0],e[1]) != (ee[0],ee[1])) and ((e[0],e[1]) != (ee[1],ee[0])))])
3032
i+=1
3033
seen[e[1]]=i
3034
3035
# Else, we orient the edges backward
3036
else:
3037
if seen[e[0]] < seen[e[1]]:
3038
d.add_edge((e[1],e[0],e[2]))
3039
else:
3040
d.add_edge(e)
3041
3042
# Case of multiple edges. If another edge has already been inserted, we add the new one
3043
# in the opposite direction.
3044
tmp = None
3045
for e in self.multiple_edges():
3046
if tmp == (e[0],e[1]):
3047
if d.has_edge(e[0].e[1]):
3048
d.add_edge(e[1],e[0],e[2])
3049
else:
3050
d.add_edge(e)
3051
tmp = (e[0],e[1])
3052
3053
return d
3054
3055
def minimum_outdegree_orientation(self, use_edge_labels=False, solver=None, verbose=0):
3056
r"""
3057
Returns an orientation of ``self`` with the smallest possible maximum
3058
outdegree.
3059
3060
Given a Graph `G`, is is polynomial to compute an orientation
3061
`D` of the edges of `G` such that the maximum out-degree in
3062
`D` is minimized. This problem, though, is NP-complete in the
3063
weighted case [AMOZ06]_.
3064
3065
INPUT:
3066
3067
- ``use_edge_labels`` -- boolean (default: ``False``)
3068
3069
- When set to ``True``, uses edge labels as weights to
3070
compute the orientation and assumes a weight of `1`
3071
when there is no value available for a given edge.
3072
3073
- When set to ``False`` (default), gives a weight of 1
3074
to all the edges.
3075
3076
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
3077
solver to be used. If set to ``None``, the default one is used. For
3078
more information on LP solvers and which default solver is used, see
3079
the method
3080
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
3081
of the class
3082
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
3083
3084
- ``verbose`` -- integer (default: ``0``). Sets the level of
3085
verbosity. Set to 0 by default, which means quiet.
3086
3087
EXAMPLE:
3088
3089
Given a complete bipartite graph `K_{n,m}`, the maximum out-degree
3090
of an optimal orientation is `\left\lceil \frac {nm} {n+m}\right\rceil`::
3091
3092
sage: g = graphs.CompleteBipartiteGraph(3,4)
3093
sage: o = g.minimum_outdegree_orientation()
3094
sage: max(o.out_degree()) == ceil((4*3)/(3+4))
3095
True
3096
3097
REFERENCES:
3098
3099
.. [AMOZ06] Asahiro, Y. and Miyano, E. and Ono, H. and Zenmyo, K.
3100
Graph orientation algorithms to minimize the maximum outdegree
3101
Proceedings of the 12th Computing: The Australasian Theory Symposium
3102
Volume 51, page 20
3103
Australian Computer Society, Inc. 2006
3104
"""
3105
self._scream_if_not_simple()
3106
if self.is_directed():
3107
raise ValueError("Cannot compute an orientation of a DiGraph. "+\
3108
"Please convert it to a Graph if you really mean it.")
3109
3110
if use_edge_labels:
3111
from sage.rings.real_mpfr import RR
3112
weight = lambda u,v : self.edge_label(u,v) if self.edge_label(u,v) in RR else 1
3113
else:
3114
weight = lambda u,v : 1
3115
3116
from sage.numerical.mip import MixedIntegerLinearProgram
3117
3118
p = MixedIntegerLinearProgram(maximization=False, solver=solver)
3119
3120
# The orientation of an edge is boolean
3121
# and indicates whether the edge uv
3122
# with u<v goes from u to v ( equal to 0 )
3123
# or from v to u ( equal to 1)
3124
orientation = p.new_variable(dim=2)
3125
3126
degree = p.new_variable()
3127
3128
# Whether an edge adjacent to a vertex u counts
3129
# positively or negatively
3130
outgoing = lambda u,v,variable : (1-variable) if u>v else variable
3131
3132
for u in self:
3133
p.add_constraint(p.sum([weight(u,v)*outgoing(u,v,orientation[min(u,v)][max(u,v)]) for v in self.neighbors(u)])-degree['max'],max=0)
3134
3135
p.set_objective(degree['max'])
3136
3137
p.set_binary(orientation)
3138
3139
p.solve(log=verbose)
3140
3141
orientation = p.get_values(orientation)
3142
3143
# All the edges from self are doubled in O
3144
# ( one in each direction )
3145
from sage.graphs.digraph import DiGraph
3146
O = DiGraph(self)
3147
3148
# Builds the list of edges that should be removed
3149
edges=[]
3150
3151
for u,v in self.edge_iterator(labels=None):
3152
# assumes u<v
3153
if u>v:
3154
u,v=v,u
3155
3156
if orientation[min(u,v)][max(u,v)] == 1:
3157
edges.append((max(u,v),min(u,v)))
3158
else:
3159
edges.append((min(u,v),max(u,v)))
3160
3161
O.delete_edges(edges)
3162
3163
return O
3164
3165
def bounded_outdegree_orientation(self, bound):
3166
r"""
3167
Computes an orientation of ``self`` such that every vertex `v`
3168
has out-degree less than `b(v)`
3169
3170
INPUT:
3171
3172
- ``bound`` -- Maximum bound on the out-degree. Can be of
3173
three different types :
3174
3175
* An integer `k`. In this case, computes an orientation
3176
whose maximum out-degree is less than `k`.
3177
3178
* A dictionary associating to each vertex its associated
3179
maximum out-degree.
3180
3181
* A function associating to each vertex its associated
3182
maximum out-degree.
3183
3184
OUTPUT:
3185
3186
A DiGraph representing the orientation if it exists. A
3187
``ValueError`` exception is raised otherwise.
3188
3189
ALGORITHM:
3190
3191
The problem is solved through a maximum flow :
3192
3193
Given a graph `G`, we create a ``DiGraph`` `D` defined on
3194
`E(G)\cup V(G)\cup \{s,t\}`. We then link `s` to all of `V(G)`
3195
(these edges having a capacity equal to the bound associated
3196
to each element of `V(G)`), and all the elements of `E(G)` to
3197
`t` . We then link each `v \in V(G)` to each of its incident
3198
edges in `G`. A maximum integer flow of value `|E(G)|`
3199
corresponds to an admissible orientation of `G`. Otherwise,
3200
none exists.
3201
3202
EXAMPLES:
3203
3204
There is always an orientation of a graph `G` such that a
3205
vertex `v` has out-degree at most `\lceil \frac {d(v)} 2
3206
\rceil`::
3207
3208
sage: g = graphs.RandomGNP(40, .4)
3209
sage: b = lambda v : ceil(g.degree(v)/2)
3210
sage: D = g.bounded_outdegree_orientation(b)
3211
sage: all( D.out_degree(v) <= b(v) for v in g )
3212
True
3213
3214
3215
Chvatal's graph, being 4-regular, can be oriented in such a
3216
way that its maximum out-degree is 2::
3217
3218
sage: g = graphs.ChvatalGraph()
3219
sage: D = g.bounded_outdegree_orientation(2)
3220
sage: max(D.out_degree())
3221
2
3222
3223
For any graph `G`, it is possible to compute an orientation
3224
such that the maximum out-degree is at most the maximum
3225
average degree of `G` divided by 2. Anything less, though, is
3226
impossible.
3227
3228
sage: g = graphs.RandomGNP(40, .4)
3229
sage: mad = g.maximum_average_degree()
3230
3231
Hence this is possible ::
3232
3233
sage: d = g.bounded_outdegree_orientation(ceil(mad/2))
3234
3235
While this is not::
3236
3237
sage: try:
3238
... g.bounded_outdegree_orientation(ceil(mad/2-1))
3239
... print "Error"
3240
... except ValueError:
3241
... pass
3242
3243
TESTS:
3244
3245
As previously for random graphs, but more intensively::
3246
3247
sage: for i in xrange(30): # long time (up to 6s on sage.math, 2012)
3248
... g = graphs.RandomGNP(40, .4)
3249
... b = lambda v : ceil(g.degree(v)/2)
3250
... D = g.bounded_outdegree_orientation(b)
3251
... if not (
3252
... all( D.out_degree(v) <= b(v) for v in g ) or
3253
... D.size() != g.size()):
3254
... print "Something wrong happened"
3255
3256
"""
3257
self._scream_if_not_simple()
3258
from sage.graphs.all import DiGraph
3259
n = self.order()
3260
3261
if n == 0:
3262
return DiGraph()
3263
3264
vertices = self.vertices()
3265
vertices_id = dict(map(lambda (x,y):(y,x), list(enumerate(vertices))))
3266
3267
b = {}
3268
3269
3270
# Checking the input type. We make a dictionay out of it
3271
if isinstance(bound, dict):
3272
b = bound
3273
else:
3274
try:
3275
b = dict(zip(vertices,map(bound, vertices)))
3276
3277
except TypeError:
3278
b = dict(zip(vertices, [bound]*n))
3279
3280
d = DiGraph()
3281
3282
# Adding the edges (s,v) and ((u,v),t)
3283
d.add_edges( ('s', vertices_id[v], b[v]) for v in vertices)
3284
3285
d.add_edges( ((vertices_id[u], vertices_id[v]), 't', 1)
3286
for (u,v) in self.edges(labels=None) )
3287
3288
# each v is linked to its incident edges
3289
3290
for u,v in self.edges(labels = None):
3291
u,v = vertices_id[u], vertices_id[v]
3292
d.add_edge(u, (u,v), 1)
3293
d.add_edge(v, (u,v), 1)
3294
3295
# Solving the maximum flow
3296
value, flow = d.flow('s','t', value_only = False, integer = True, use_edge_labels = True)
3297
3298
if value != self.size():
3299
raise ValueError("No orientation exists for the given bound")
3300
3301
D = DiGraph()
3302
D.add_vertices(vertices)
3303
3304
# The flow graph may not contain all the vertices, if they are
3305
# not part of the flow...
3306
3307
for u in [x for x in range(n) if x in flow]:
3308
3309
for (uu,vv) in flow.neighbors_out(u):
3310
v = vv if vv != u else uu
3311
D.add_edge(vertices[u], vertices[v])
3312
3313
# I do not like when a method destroys the embedding ;-)
3314
3315
D.set_pos(self.get_pos())
3316
3317
return D
3318
3319
3320
### Coloring
3321
3322
def bipartite_color(self):
3323
"""
3324
Returns a dictionary with vertices as the keys and the color class
3325
as the values. Fails with an error if the graph is not bipartite.
3326
3327
EXAMPLES::
3328
3329
sage: graphs.CycleGraph(4).bipartite_color()
3330
{0: 1, 1: 0, 2: 1, 3: 0}
3331
sage: graphs.CycleGraph(5).bipartite_color()
3332
Traceback (most recent call last):
3333
...
3334
RuntimeError: Graph is not bipartite.
3335
"""
3336
isit, certificate = self.is_bipartite(certificate = True)
3337
3338
if isit:
3339
return certificate
3340
else:
3341
raise RuntimeError("Graph is not bipartite.")
3342
3343
def bipartite_sets(self):
3344
"""
3345
Returns `(X,Y)` where `X` and `Y` are the nodes in each bipartite set of
3346
graph `G`. Fails with an error if graph is not bipartite.
3347
3348
EXAMPLES::
3349
3350
sage: graphs.CycleGraph(4).bipartite_sets()
3351
(set([0, 2]), set([1, 3]))
3352
sage: graphs.CycleGraph(5).bipartite_sets()
3353
Traceback (most recent call last):
3354
...
3355
RuntimeError: Graph is not bipartite.
3356
"""
3357
color = self.bipartite_color()
3358
left = set([])
3359
right = set([])
3360
3361
for u,s in color.iteritems():
3362
if s:
3363
left.add(u)
3364
else:
3365
right.add(u)
3366
3367
return left, right
3368
3369
def chromatic_number(self, algorithm="DLX", verbose = 0):
3370
r"""
3371
Returns the minimal number of colors needed to color the vertices
3372
of the graph `G`.
3373
3374
INPUT:
3375
3376
- ``algorithm`` -- Select an algorithm from the following supported
3377
algorithms:
3378
3379
- If ``algorithm="DLX"`` (default), the chromatic number is
3380
computed using the dancing link algorithm. It is
3381
inefficient speedwise to compute the chromatic number through
3382
the dancing link algorithm because this algorithm computes
3383
*all* the possible colorings to check that one exists.
3384
3385
- If ``algorithm="CP"``, the chromatic number is computed
3386
using the coefficients of the chromatic polynomial. Again, this
3387
method is inefficient in terms of speed and it only useful for
3388
small graphs.
3389
3390
- If ``algorithm="MILP"``, the chromatic number is computed using a
3391
mixed integer linear program. The performance of this implementation
3392
is affected by whether optional MILP solvers have been installed
3393
(see the :mod:`MILP module <sage.numerical.mip>`, or Sage's tutorial
3394
on Linear Programming).
3395
3396
- ``verbose`` -- integer (default: ``0``). Sets the level of verbosity
3397
for the MILP algorithm. Its default value is 0, which means *quiet*.
3398
3399
.. SEEALSO::
3400
3401
For more functions related to graph coloring, see the
3402
module :mod:`sage.graphs.graph_coloring`.
3403
3404
EXAMPLES::
3405
3406
sage: G = Graph({0: [1, 2, 3], 1: [2]})
3407
sage: G.chromatic_number(algorithm="DLX")
3408
3
3409
sage: G.chromatic_number(algorithm="MILP")
3410
3
3411
sage: G.chromatic_number(algorithm="CP")
3412
3
3413
3414
A bipartite graph has (by definition) chromatic number 2::
3415
3416
sage: graphs.RandomBipartite(50,50,0.7).chromatic_number()
3417
2
3418
3419
A complete multipartite graph with k parts has chromatic number k::
3420
3421
sage: all(graphs.CompleteMultipartiteGraph([5]*i).chromatic_number() == i for i in xrange(2,5))
3422
True
3423
3424
The complete graph has the largest chromatic number from all the graphs
3425
of order n. Namely its chromatic number is n::
3426
3427
sage: all(graphs.CompleteGraph(i).chromatic_number() == i for i in xrange(10))
3428
True
3429
3430
The Kneser graph with parameters (n,2) for n > 3 has chromatic number n-2::
3431
3432
sage: all(graphs.KneserGraph(i,2).chromatic_number() == i-2 for i in xrange(4,6))
3433
True
3434
3435
A snark has chromatic index 4 hence its line graph has chromatic number 4::
3436
3437
sage: graphs.FlowerSnark().line_graph().chromatic_number()
3438
4
3439
3440
TESTS::
3441
3442
sage: G = Graph({0: [1, 2, 3], 1: [2]})
3443
sage: G.chromatic_number(algorithm="foo")
3444
Traceback (most recent call last):
3445
...
3446
ValueError: The 'algorithm' keyword must be set to either 'DLX', 'MILP' or 'CP'.
3447
"""
3448
self._scream_if_not_simple(allow_multiple_edges=True)
3449
# default built-in algorithm; bad performance
3450
if algorithm == "DLX":
3451
from sage.graphs.graph_coloring import chromatic_number
3452
return chromatic_number(self)
3453
# Algorithm with good performance, but requires an optional
3454
# package: choose any of GLPK or CBC.
3455
elif algorithm == "MILP":
3456
from sage.graphs.graph_coloring import vertex_coloring
3457
return vertex_coloring(self, value_only=True, verbose = verbose)
3458
# another algorithm with bad performance; only good for small graphs
3459
elif algorithm == "CP":
3460
f = self.chromatic_polynomial()
3461
i = 0
3462
while f(i) == 0:
3463
i += 1
3464
return i
3465
else:
3466
raise ValueError("The 'algorithm' keyword must be set to either 'DLX', 'MILP' or 'CP'.")
3467
3468
def coloring(self, algorithm="DLX", hex_colors=False, verbose = 0):
3469
r"""
3470
Returns the first (optimal) proper vertex-coloring found.
3471
3472
INPUT:
3473
3474
- ``algorithm`` -- Select an algorithm from the following supported
3475
algorithms:
3476
3477
- If ``algorithm="DLX"`` (default), the coloring is computed using the
3478
dancing link algorithm.
3479
3480
- If ``algorithm="MILP"``, the coloring is computed using a mixed
3481
integer linear program. The performance of this implementation is
3482
affected by whether optional MILP solvers have been installed (see
3483
the :mod:`MILP module <sage.numerical.mip>`).
3484
3485
- ``hex_colors`` -- (default: ``False``) if ``True``, return a
3486
dictionary which can easily be used for plotting.
3487
3488
- ``verbose`` -- integer (default: ``0``). Sets the level of verbosity
3489
for the MILP algorithm. Its default value is 0, which means *quiet*.
3490
3491
.. SEEALSO::
3492
3493
For more functions related to graph coloring, see the
3494
module :mod:`sage.graphs.graph_coloring`.
3495
3496
EXAMPLES::
3497
3498
sage: G = Graph("Fooba")
3499
sage: P = G.coloring(algorithm="MILP"); P
3500
[[2, 1, 3], [0, 6, 5], [4]]
3501
sage: P = G.coloring(algorithm="DLX"); P
3502
[[1, 2, 3], [0, 5, 6], [4]]
3503
sage: G.plot(partition=P)
3504
sage: H = G.coloring(hex_colors=True, algorithm="MILP")
3505
sage: for c in sorted(H.keys()):
3506
... print c, H[c]
3507
#0000ff [4]
3508
#00ff00 [0, 6, 5]
3509
#ff0000 [2, 1, 3]
3510
sage: H = G.coloring(hex_colors=True, algorithm="DLX")
3511
sage: for c in sorted(H.keys()):
3512
... print c, H[c]
3513
#0000ff [4]
3514
#00ff00 [1, 2, 3]
3515
#ff0000 [0, 5, 6]
3516
sage: G.plot(vertex_colors=H)
3517
3518
TESTS::
3519
3520
sage: G.coloring(algorithm="foo")
3521
Traceback (most recent call last):
3522
...
3523
ValueError: The 'algorithm' keyword must be set to either 'DLX' or 'MILP'.
3524
"""
3525
self._scream_if_not_simple(allow_multiple_edges=True)
3526
if algorithm == "MILP":
3527
from sage.graphs.graph_coloring import vertex_coloring
3528
return vertex_coloring(self, hex_colors=hex_colors, verbose = verbose)
3529
elif algorithm == "DLX":
3530
from sage.graphs.graph_coloring import first_coloring
3531
return first_coloring(self, hex_colors=hex_colors)
3532
else:
3533
raise ValueError("The 'algorithm' keyword must be set to either 'DLX' or 'MILP'.")
3534
3535
def matching(self, value_only=False, algorithm="Edmonds", use_edge_labels=True, solver=None, verbose=0):
3536
r"""
3537
Returns a maximum weighted matching of the graph
3538
represented by the list of its edges. For more information, see the
3539
`Wikipedia article on matchings
3540
<http://en.wikipedia.org/wiki/Matching_%28graph_theory%29>`_.
3541
3542
Given a graph `G` such that each edge `e` has a weight `w_e`,
3543
a maximum matching is a subset `S` of the edges of `G` of
3544
maximum weight such that no two edges of `S` are incident
3545
with each other.
3546
3547
As an optimization problem, it can be expressed as:
3548
3549
.. math::
3550
3551
\mbox{Maximize : }&\sum_{e\in G.edges()} w_e b_e\\
3552
\mbox{Such that : }&\forall v \in G, \sum_{(u,v)\in G.edges()} b_{(u,v)}\leq 1\\
3553
&\forall x\in G, b_x\mbox{ is a binary variable}
3554
3555
INPUT:
3556
3557
- ``value_only`` -- boolean (default: ``False``). When set to
3558
``True``, only the cardinal (or the weight) of the matching is
3559
returned.
3560
3561
- ``algorithm`` -- string (default: ``"Edmonds"``)
3562
3563
- ``"Edmonds"`` selects Edmonds' algorithm as implemented in NetworkX
3564
3565
- ``"LP"`` uses a Linear Program formulation of the matching problem
3566
3567
- ``use_edge_labels`` -- boolean (default: ``False``)
3568
3569
- When set to ``True``, computes a weighted matching where each edge
3570
is weighted by its label. (If an edge has no label, `1` is assumed.)
3571
3572
- When set to ``False``, each edge has weight `1`.
3573
3574
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
3575
solver to be used. If set to ``None``, the default one is used. For
3576
more information on LP solvers and which default solver is used, see
3577
the method
3578
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
3579
of the class
3580
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
3581
3582
- ``verbose`` -- integer (default: ``0``). Sets the level of
3583
verbosity. Set to 0 by default, which means quiet.
3584
Only useful when ``algorithm == "LP"``.
3585
3586
ALGORITHM:
3587
3588
The problem is solved using Edmond's algorithm implemented in
3589
NetworkX, or using Linear Programming depending on the value of
3590
``algorithm``.
3591
3592
EXAMPLES:
3593
3594
Maximum matching in a Pappus Graph::
3595
3596
sage: g = graphs.PappusGraph()
3597
sage: g.matching(value_only=True)
3598
9.0
3599
3600
Same test with the Linear Program formulation::
3601
3602
sage: g = graphs.PappusGraph()
3603
sage: g.matching(algorithm="LP", value_only=True)
3604
9.0
3605
3606
TESTS:
3607
3608
If ``algorithm`` is set to anything different from ``"Edmonds"`` or
3609
``"LP"``, an exception is raised::
3610
3611
sage: g = graphs.PappusGraph()
3612
sage: g.matching(algorithm="somethingdifferent")
3613
Traceback (most recent call last):
3614
...
3615
ValueError: algorithm must be set to either "Edmonds" or "LP"
3616
"""
3617
self._scream_if_not_simple(allow_loops=True)
3618
from sage.rings.real_mpfr import RR
3619
weight = lambda x: x if x in RR else 1
3620
3621
if algorithm == "Edmonds":
3622
import networkx
3623
if use_edge_labels:
3624
g = networkx.Graph()
3625
for u, v, l in self.edges():
3626
g.add_edge(u, v, attr_dict={"weight": weight(l)})
3627
else:
3628
g = self.networkx_graph(copy=False)
3629
d = networkx.max_weight_matching(g)
3630
if value_only:
3631
if use_edge_labels:
3632
return sum([weight(self.edge_label(u, v))
3633
for u, v in d.iteritems()]) * 0.5
3634
else:
3635
return Integer(len(d)/2)
3636
else:
3637
return [(u, v, self.edge_label(u, v))
3638
for u, v in d.iteritems() if u < v]
3639
3640
elif algorithm == "LP":
3641
from sage.numerical.mip import MixedIntegerLinearProgram
3642
g = self
3643
# returns the weight of an edge considering it may not be
3644
# weighted ...
3645
p = MixedIntegerLinearProgram(maximization=True, solver=solver)
3646
b = p.new_variable(dim=2)
3647
p.set_objective(
3648
p.sum([weight(w) * b[min(u, v)][max(u, v)]
3649
for u, v, w in g.edges()]))
3650
# for any vertex v, there is at most one edge incident to v in
3651
# the maximum matching
3652
for v in g.vertex_iterator():
3653
p.add_constraint(
3654
p.sum([b[min(u, v)][max(u, v)]
3655
for u in g.neighbors(v)]), max=1)
3656
p.set_binary(b)
3657
if value_only:
3658
if use_edge_labels:
3659
return p.solve(objective_only=True, log=verbose)
3660
else:
3661
return Integer(round(p.solve(objective_only=True, log=verbose)))
3662
else:
3663
p.solve(log=verbose)
3664
b = p.get_values(b)
3665
return [(u, v, w) for u, v, w in g.edges()
3666
if b[min(u, v)][max(u, v)] == 1]
3667
3668
else:
3669
raise ValueError('algorithm must be set to either "Edmonds" or "LP"')
3670
3671
def has_homomorphism_to(self, H, core = False, solver = None, verbose = 0):
3672
r"""
3673
Checks whether there is a homomorphism between two graphs.
3674
3675
A homomorphism from a graph `G` to a graph `H` is a function
3676
`\phi:V(G)\mapsto V(H)` such that for any edge `uv \in E(G)` the pair
3677
`\phi(u)\phi(v)` is an edge of `H`.
3678
3679
Saying that a graph can be `k`-colored is equivalent to saying that it
3680
has a homomorphism to `K_k`, the complete graph on `k` elements.
3681
3682
For more information, see the `Wikipedia article on graph homomorphisms
3683
<Graph_homomorphism>`_.
3684
3685
INPUT:
3686
3687
- ``H`` -- the graph to which ``self`` should be sent.
3688
3689
- ``core`` (boolean) -- whether to minimize the size of the mapping's
3690
image (see note below). This is set to ``False`` by default.
3691
3692
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
3693
solver to be used. If set to ``None``, the default one is used. For
3694
more information on LP solvers and which default solver is used, see
3695
the method
3696
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
3697
of the class
3698
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
3699
3700
- ``verbose`` -- integer (default: ``0``). Sets the level of
3701
verbosity. Set to 0 by default, which means quiet.
3702
3703
.. NOTE::
3704
3705
One can compute the core of a graph (with respect to homomorphism)
3706
with this method ::
3707
3708
sage: g = graphs.CycleGraph(10)
3709
sage: mapping = g.has_homomorphism_to(g, core = True)
3710
sage: print "The size of the core is",len(set(mapping.values()))
3711
The size of the core is 2
3712
3713
OUTPUT:
3714
3715
This method returns ``False`` when the homomorphism does not exist, and
3716
returns the homomorphism otherwise as a dictionnary associating a vertex
3717
of `H` to a vertex of `G`.
3718
3719
EXAMPLE:
3720
3721
Is Petersen's graph 3-colorable::
3722
3723
sage: P = graphs.PetersenGraph()
3724
sage: P.has_homomorphism_to(graphs.CompleteGraph(3)) is not False
3725
True
3726
3727
An odd cycle admits a homomorphism to a smaller odd cycle, but not to an
3728
even cycle::
3729
3730
sage: g = graphs.CycleGraph(9)
3731
sage: g.has_homomorphism_to(graphs.CycleGraph(5)) is not False
3732
True
3733
sage: g.has_homomorphism_to(graphs.CycleGraph(7)) is not False
3734
True
3735
sage: g.has_homomorphism_to(graphs.CycleGraph(4)) is not False
3736
False
3737
"""
3738
self._scream_if_not_simple()
3739
from sage.numerical.mip import MixedIntegerLinearProgram, MIPSolverException
3740
p = MixedIntegerLinearProgram(solver=solver, maximization = False)
3741
b = p.new_variable(binary = True)
3742
3743
# Each vertex has an image
3744
for ug in self:
3745
p.add_constraint(p.sum([b[ug,uh] for uh in H]) == 1)
3746
3747
nonedges = H.complement().edges(labels = False)
3748
for ug,vg in self.edges(labels = False):
3749
# Two adjacent vertices cannot be mapped to the same element
3750
for uh in H:
3751
p.add_constraint(b[ug,uh] + b[vg,uh] <= 1)
3752
3753
# Two adjacent vertices cannot be mapped to no adjacent vertices
3754
for uh,vh in nonedges:
3755
p.add_constraint(b[ug,uh] + b[vg,vh] <= 1)
3756
p.add_constraint(b[ug,vh] + b[vg,uh] <= 1)
3757
3758
# Minimize the mapping's size
3759
if core:
3760
3761
# the value of m is one if the corresponding vertex of h is used.
3762
m = p.new_variable()
3763
for uh in H:
3764
for ug in self:
3765
p.add_constraint(b[ug,uh] <= m[uh])
3766
3767
p.set_objective(p.sum([m[vh] for vh in H]))
3768
3769
try:
3770
p.solve(log = verbose)
3771
b = p.get_values(b)
3772
mapping = dict(map(lambda y:y[0],filter(lambda x:x[1], b.items())))
3773
return mapping
3774
3775
except MIPSolverException:
3776
return False
3777
3778
def fractional_chromatic_index(self, verbose_constraints = 0, verbose = 0):
3779
r"""
3780
Computes the fractional chromatic index of ``self``
3781
3782
The fractional chromatic index is a relaxed version of edge-coloring. An
3783
edge coloring of a graph being actually a covering of its edges into the
3784
smallest possible number of matchings, the fractional chromatic index of
3785
a graph `G` is the smallest real value `\chi_f(G)` such that there
3786
exists a list of matchings `M_1, ..., M_k` of `G` and coefficients
3787
`\alpha_1, ..., \alpha_k` with the property that each edge is covered by
3788
the matchings in the following relaxed way
3789
3790
.. MATH::
3791
3792
\forall e \in E(G), \sum_{e \in M_i} \alpha_i \geq 1
3793
3794
For more information, see the `Wikipedia article on fractional coloring
3795
<http://en.wikipedia.org/wiki/Fractional_coloring>`_.
3796
3797
ALGORITHM:
3798
3799
The fractional chromatic index is computed through Linear Programming
3800
through its dual. The LP solved by sage is actually:
3801
3802
.. MATH::
3803
3804
\mbox{Maximize : }&\sum_{e\in E(G)} r_{e}\\
3805
\mbox{Such that : }&\\
3806
&\forall M\text{ matching }\subseteq G, \sum_{e\in M}r_{v}\leq 1\\
3807
3808
INPUT:
3809
3810
- ``verbose_constraints`` -- whether to display which constraints are
3811
being generated.
3812
3813
- ``verbose`` -- level of verbosity required from the LP solver
3814
3815
.. NOTE::
3816
3817
This implementation can be improved by computing matchings through a
3818
LP formulation, and not using the Python implementation of Edmonds'
3819
algorithm (which requires to copy the graph, etc). It may be more
3820
efficient to write the matching problem as a LP, as we would then
3821
just have to update the weights on the edges between each call to
3822
``solve`` (and so avoiding the generation of all the constraints).
3823
3824
3825
EXAMPLE:
3826
3827
The fractional chromatic index of a `C_5` is `5/2`::
3828
3829
sage: g = graphs.CycleGraph(5)
3830
sage: g.fractional_chromatic_index()
3831
2.5
3832
"""
3833
self._scream_if_not_simple()
3834
from sage.numerical.mip import MixedIntegerLinearProgram
3835
3836
g = self.copy()
3837
p = MixedIntegerLinearProgram(constraint_generation = True)
3838
3839
# One variable per edge
3840
r = p.new_variable(dim = 2)
3841
R = lambda x,y : r[x][y] if x<y else r[y][x]
3842
3843
# We want to maximize the sum of weights on the edges
3844
p.set_objective( p.sum( R(u,v) for u,v in g.edges(labels = False)))
3845
3846
# Each edge being by itself a matching, its weight can not be more than
3847
# 1
3848
3849
for u,v in g.edges(labels = False):
3850
p.add_constraint( R(u,v), max = 1)
3851
3852
obj = p.solve(log = verbose)
3853
3854
while True:
3855
3856
# Updating the value on the edges of g
3857
for u,v in g.edges(labels = False):
3858
g.set_edge_label(u,v,p.get_values(R(u,v)))
3859
3860
# Computing a matching of maximum weight...
3861
3862
matching = g.matching()
3863
3864
# If the maximum matching has weight at most 1, we are done !
3865
if sum(map(lambda x:x[2],matching)) <= 1:
3866
break
3867
3868
# Otherwise, we add a new constraint
3869
3870
if verbose_constraints:
3871
print "Adding a constraint on matching : ",matching
3872
3873
p.add_constraint( p.sum( R(u,v) for u,v,_ in matching), max = 1)
3874
3875
# And solve again
3876
obj = p.solve(log = verbose)
3877
3878
# Accomplished !
3879
return obj
3880
3881
def maximum_average_degree(self, value_only=True, solver = None, verbose = 0):
3882
r"""
3883
Returns the Maximum Average Degree (MAD) of the current graph.
3884
3885
The Maximum Average Degree (MAD) of a graph is defined as
3886
the average degree of its densest subgraph. More formally,
3887
``Mad(G) = \max_{H\subseteq G} Ad(H)``, where `Ad(G)` denotes
3888
the average degree of `G`.
3889
3890
This can be computed in polynomial time.
3891
3892
INPUT:
3893
3894
- ``value_only`` (boolean) -- ``True`` by default
3895
3896
- If ``value_only=True``, only the numerical
3897
value of the `MAD` is returned.
3898
3899
- Else, the subgraph of `G` realizing the `MAD`
3900
is returned.
3901
3902
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
3903
solver to be used. If set to ``None``, the default one is used. For
3904
more information on LP solvers and which default solver is used, see
3905
the method
3906
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
3907
of the class
3908
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
3909
3910
- ``verbose`` -- integer (default: ``0``). Sets the level of
3911
verbosity. Set to 0 by default, which means quiet.
3912
3913
EXAMPLES:
3914
3915
In any graph, the `Mad` is always larger than the average
3916
degree::
3917
3918
sage: g = graphs.RandomGNP(20,.3)
3919
sage: mad_g = g.maximum_average_degree()
3920
sage: g.average_degree() <= mad_g
3921
True
3922
3923
Unlike the average degree, the `Mad` of the disjoint
3924
union of two graphs is the maximum of the `Mad` of each
3925
graphs::
3926
3927
sage: h = graphs.RandomGNP(20,.3)
3928
sage: mad_h = h.maximum_average_degree()
3929
sage: (g+h).maximum_average_degree() == max(mad_g, mad_h)
3930
True
3931
3932
The subgraph of a regular graph realizing the maximum
3933
average degree is always the whole graph ::
3934
3935
sage: g = graphs.CompleteGraph(5)
3936
sage: mad_g = g.maximum_average_degree(value_only=False)
3937
sage: g.is_isomorphic(mad_g)
3938
True
3939
3940
This also works for complete bipartite graphs ::
3941
3942
sage: g = graphs.CompleteBipartiteGraph(3,4)
3943
sage: mad_g = g.maximum_average_degree(value_only=False)
3944
sage: g.is_isomorphic(mad_g)
3945
True
3946
"""
3947
self._scream_if_not_simple()
3948
g = self
3949
from sage.numerical.mip import MixedIntegerLinearProgram
3950
3951
p = MixedIntegerLinearProgram(maximization=True, solver = solver)
3952
3953
d = p.new_variable()
3954
one = p.new_variable()
3955
3956
# Reorders u and v so that uv and vu are not considered
3957
# to be different edges
3958
reorder = lambda u,v : (min(u,v),max(u,v))
3959
3960
for u,v in g.edge_iterator(labels=False):
3961
p.add_constraint( one[ reorder(u,v) ] - 2*d[u] , max = 0 )
3962
p.add_constraint( one[ reorder(u,v) ] - 2*d[v] , max = 0 )
3963
3964
p.add_constraint( p.sum([d[v] for v in g]), max = 1)
3965
3966
p.set_objective( p.sum([ one[reorder(u,v)] for u,v in g.edge_iterator(labels=False)]) )
3967
3968
obj = p.solve(log = verbose)
3969
3970
# Paying attention to numerical error :
3971
# The zero values could be something like 0.000000000001
3972
# so I can not write l > 0
3973
# And the non-zero, though they should be equal to
3974
# 1/(order of the optimal subgraph) may be a bit lower
3975
3976
# setting the minimum to 1/(10 * size of the whole graph )
3977
# should be safe :-)
3978
m = 1/(10 *Integer(g.order()))
3979
g_mad = g.subgraph([v for v,l in p.get_values(d).iteritems() if l>m ])
3980
3981
if value_only:
3982
return g_mad.average_degree()
3983
else:
3984
return g_mad
3985
3986
def independent_set_of_representatives(self, family, solver=None, verbose=0):
3987
r"""
3988
Returns an independent set of representatives.
3989
3990
Given a graph `G` and and a family `F=\{F_i:i\in [1,...,k]\}` of
3991
subsets of ``g.vertices()``, an Independent Set of Representatives
3992
(ISR) is an assignation of a vertex `v_i\in F_i` to each set `F_i`
3993
such that `v_i != v_j` if `i<j` (they are representatives) and the
3994
set `\cup_{i}v_i` is an independent set in `G`.
3995
3996
It generalizes, for example, graph coloring and graph list coloring.
3997
3998
(See [AhaBerZiv07]_ for more information.)
3999
4000
INPUT:
4001
4002
- ``family`` -- A list of lists defining the family `F`
4003
(actually, a Family of subsets of ``G.vertices()``).
4004
4005
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
4006
solver to be used. If set to ``None``, the default one is used. For
4007
more information on LP solvers and which default solver is used, see
4008
the method
4009
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
4010
of the class
4011
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
4012
4013
- ``verbose`` -- integer (default: ``0``). Sets the level of
4014
verbosity. Set to 0 by default, which means quiet.
4015
4016
OUTPUT:
4017
4018
- A list whose `i^{\mbox{th}}` element is the representative of the
4019
`i^{\mbox{th}}` element of the ``family`` list. If there is no ISR,
4020
``None`` is returned.
4021
4022
EXAMPLES:
4023
4024
For a bipartite graph missing one edge, the solution is as expected::
4025
4026
sage: g = graphs.CompleteBipartiteGraph(3,3)
4027
sage: g.delete_edge(1,4)
4028
sage: g.independent_set_of_representatives([[0,1,2],[3,4,5]])
4029
[1, 4]
4030
4031
The Petersen Graph is 3-colorable, which can be expressed as an
4032
independent set of representatives problem : take 3 disjoint copies
4033
of the Petersen Graph, each one representing one color. Then take
4034
as a partition of the set of vertices the family defined by the three
4035
copies of each vertex. The ISR of such a family
4036
defines a 3-coloring::
4037
4038
sage: g = 3 * graphs.PetersenGraph()
4039
sage: n = g.order()/3
4040
sage: f = [[i,i+n,i+2*n] for i in xrange(n)]
4041
sage: isr = g.independent_set_of_representatives(f)
4042
sage: c = [floor(i/n) for i in isr]
4043
sage: color_classes = [[],[],[]]
4044
sage: for v,i in enumerate(c):
4045
... color_classes[i].append(v)
4046
sage: for classs in color_classes:
4047
... g.subgraph(classs).size() == 0
4048
True
4049
True
4050
True
4051
4052
REFERENCE:
4053
4054
.. [AhaBerZiv07] R. Aharoni and E. Berger and R. Ziv
4055
Independent systems of representatives in weighted graphs
4056
Combinatorica vol 27, num 3, p253--267
4057
2007
4058
4059
"""
4060
4061
from sage.numerical.mip import MixedIntegerLinearProgram
4062
p=MixedIntegerLinearProgram(solver=solver)
4063
4064
# Boolean variable indicating whether the vertex
4065
# is the representative of some set
4066
vertex_taken=p.new_variable()
4067
4068
# Boolean variable in two dimension whose first
4069
# element is a vertex and whose second element
4070
# is one of the sets given as arguments.
4071
# When true, indicated that the vertex is the representant
4072
# of the corresponding set
4073
4074
classss=p.new_variable(dim=2)
4075
4076
# Associates to the vertices the classes
4077
# to which they belong
4078
4079
lists=dict([(v,[]) for v in self.vertex_iterator()])
4080
for i,f in enumerate(family):
4081
[lists[v].append(i) for v in f]
4082
4083
# a classss has exactly one representant
4084
p.add_constraint(p.sum([classss[v][i] for v in f]),max=1,min=1)
4085
4086
# A vertex represents at most one classss (vertex_taken is binary), and
4087
# vertex_taken[v]==1 if v is the representative of some classss
4088
4089
[p.add_constraint(p.sum([classss[v][i] for i in lists[v]])-vertex_taken[v],max=0) for v in self.vertex_iterator()]
4090
4091
# Two adjacent vertices can not both be representants of a set
4092
4093
for (u,v) in self.edges(labels=None):
4094
p.add_constraint(vertex_taken[u]+vertex_taken[v],max=1)
4095
4096
p.set_objective(None)
4097
4098
p.set_binary(vertex_taken)
4099
p.set_binary(classss)
4100
4101
try:
4102
p.solve(log=verbose)
4103
except Exception:
4104
return None
4105
4106
classss=p.get_values(classss)
4107
4108
repr=[]
4109
for i,f in enumerate(family):
4110
for v in f:
4111
if classss[v][i]==1:
4112
repr.append(v)
4113
break
4114
4115
return repr
4116
4117
def minor(self, H, solver=None, verbose=0):
4118
r"""
4119
Returns the vertices of a minor isomorphic to `H` in the current graph.
4120
4121
We say that a graph `G` has a `H`-minor (or that it has
4122
a graph isomorphic to `H` as a minor), if for all `h\in H`,
4123
there exist disjoint sets `S_h \subseteq V(G)` such that
4124
once the vertices of each `S_h` have been merged to create
4125
a new graph `G'`, this new graph contains `H` as a subgraph.
4126
4127
For more information, see the
4128
`Wikipedia article on graph minor <http://en.wikipedia.org/wiki/Minor_%28graph_theory%29>`_.
4129
4130
INPUT:
4131
4132
- ``H`` -- The minor to find for in the current graph.
4133
4134
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
4135
solver to be used. If set to ``None``, the default one is used. For
4136
more information on LP solvers and which default solver is used, see
4137
the method
4138
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
4139
of the class
4140
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
4141
4142
- ``verbose`` -- integer (default: ``0``). Sets the level of
4143
verbosity. Set to 0 by default, which means quiet.
4144
4145
OUTPUT:
4146
4147
A dictionary associating to each vertex of `H` the set of vertices
4148
in the current graph representing it.
4149
4150
ALGORITHM:
4151
4152
Mixed Integer Linear Programming
4153
4154
COMPLEXITY:
4155
4156
Theoretically, when `H` is fixed, testing for the existence of
4157
a `H`-minor is polynomial. The known algorithms are highly
4158
exponential in `H`, though.
4159
4160
.. NOTE::
4161
4162
This function can be expected to be *very* slow, especially
4163
where the minor does not exist.
4164
4165
EXAMPLES:
4166
4167
Trying to find a minor isomorphic to `K_4` in
4168
the `4\times 4` grid::
4169
4170
sage: g = graphs.GridGraph([4,4])
4171
sage: h = graphs.CompleteGraph(4)
4172
sage: L = g.minor(h)
4173
sage: gg = g.subgraph(flatten(L.values(), max_level = 1))
4174
sage: _ = [gg.merge_vertices(l) for l in L.values() if len(l)>1]
4175
sage: gg.is_isomorphic(h)
4176
True
4177
4178
We can also try to prove this way that the Petersen graph
4179
is not planar, as it has a `K_5` minor::
4180
4181
sage: g = graphs.PetersenGraph()
4182
sage: K5_minor = g.minor(graphs.CompleteGraph(5)) # long time
4183
4184
And even a `K_{3,3}` minor::
4185
4186
sage: K33_minor = g.minor(graphs.CompleteBipartiteGraph(3,3)) # long time
4187
4188
(It is much faster to use the linear-time test of
4189
planarity in this situation, though.)
4190
4191
As there is no cycle in a tree, looking for a `K_3` minor is useless.
4192
This function will raise an exception in this case::
4193
4194
sage: g = graphs.RandomGNP(20,.5)
4195
sage: g = g.subgraph(edges = g.min_spanning_tree())
4196
sage: g.is_tree()
4197
True
4198
sage: L = g.minor(graphs.CompleteGraph(3))
4199
Traceback (most recent call last):
4200
...
4201
ValueError: This graph has no minor isomorphic to H !
4202
"""
4203
self._scream_if_not_simple()
4204
H._scream_if_not_simple()
4205
from sage.numerical.mip import MixedIntegerLinearProgram, MIPSolverException
4206
p = MixedIntegerLinearProgram(solver=solver)
4207
4208
# sorts an edge
4209
S = lambda (x,y) : (x,y) if x<y else (y,x)
4210
4211
# rs = Representative set of a vertex
4212
# for h in H, v in G is such that rs[h][v] == 1 if and only if v
4213
# is a representant of h in self
4214
rs = p.new_variable(dim=2)
4215
4216
for v in self:
4217
p.add_constraint(p.sum([rs[h][v] for h in H]), max = 1)
4218
4219
# We ensure that the set of representatives of a
4220
# vertex h contains a tree, and thus is connected
4221
4222
# edges represents the edges of the tree
4223
edges = p.new_variable(dim = 2)
4224
4225
# there can be a edge for h between two vertices
4226
# only if those vertices represent h
4227
for u,v in self.edges(labels=None):
4228
for h in H:
4229
p.add_constraint(edges[h][S((u,v))] - rs[h][u], max = 0 )
4230
p.add_constraint(edges[h][S((u,v))] - rs[h][v], max = 0 )
4231
4232
# The number of edges of the tree in h is exactly the cardinal
4233
# of its representative set minus 1
4234
4235
for h in H:
4236
p.add_constraint(p.sum([edges[h][S(e)] for e in self.edges(labels=None)])-p.sum([rs[h][v] for v in self]), min=-1, max=-1)
4237
4238
# a tree has no cycle
4239
epsilon = 1/(5*Integer(self.order()))
4240
r_edges = p.new_variable(dim=2)
4241
4242
for h in H:
4243
for u,v in self.edges(labels=None):
4244
p.add_constraint(r_edges[h][(u,v)] + r_edges[h][(v,u)] - edges[h][S((u,v))], min = 0)
4245
4246
for v in self:
4247
p.add_constraint(p.sum([r_edges[h][(u,v)] for u in self.neighbors(v)]), max = 1-epsilon)
4248
4249
# Once the representative sets are described, we must ensure
4250
# there are arcs corresponding to those of H between them
4251
h_edges = p.new_variable(dim=2)
4252
4253
for h1, h2 in H.edges(labels=None):
4254
4255
for v1, v2 in self.edges(labels=None):
4256
4257
p.add_constraint(h_edges[(h1,h2)][S((v1,v2))] - rs[h2][v2], max = 0)
4258
p.add_constraint(h_edges[(h1,h2)][S((v1,v2))] - rs[h1][v1], max = 0)
4259
4260
p.add_constraint(h_edges[(h2,h1)][S((v1,v2))] - rs[h1][v2], max = 0)
4261
p.add_constraint(h_edges[(h2,h1)][S((v1,v2))] - rs[h2][v1], max = 0)
4262
4263
p.add_constraint(p.sum([h_edges[(h1,h2)][S(e)] + h_edges[(h2,h1)][S(e)] for e in self.edges(labels=None) ]), min = 1)
4264
4265
p.set_binary(rs)
4266
p.set_binary(edges)
4267
4268
p.set_objective(None)
4269
4270
try:
4271
p.solve(log=verbose)
4272
except MIPSolverException:
4273
raise ValueError("This graph has no minor isomorphic to H !")
4274
4275
rs = p.get_values(rs)
4276
4277
rs_dict = {}
4278
for h in H:
4279
rs_dict[h] = [v for v in self if rs[h][v]==1]
4280
4281
return rs_dict
4282
4283
### Convexity
4284
4285
def convexity_properties(self):
4286
r"""
4287
Returns a ``ConvexityProperties`` object corresponding to ``self``.
4288
4289
This object contains the methods related to convexity in graphs (convex
4290
hull, hull number) and caches useful information so that it becomes
4291
comparatively cheaper to compute the convex hull of many different sets
4292
of the same graph.
4293
4294
.. SEEALSO::
4295
4296
In order to know what can be done through this object, please refer
4297
to module :mod:`sage.graphs.convexity_properties`.
4298
4299
.. NOTE::
4300
4301
If you want to compute many convex hulls, keep this object in memory
4302
! When it is created, it builds a table of useful information to
4303
compute convex hulls. As a result ::
4304
4305
sage: g = graphs.PetersenGraph()
4306
sage: g.convexity_properties().hull([1, 3])
4307
[1, 2, 3]
4308
sage: g.convexity_properties().hull([3, 7])
4309
[2, 3, 7]
4310
4311
Is a terrible waste of computations, while ::
4312
4313
sage: g = graphs.PetersenGraph()
4314
sage: CP = g.convexity_properties()
4315
sage: CP.hull([1, 3])
4316
[1, 2, 3]
4317
sage: CP.hull([3, 7])
4318
[2, 3, 7]
4319
4320
Makes perfect sense.
4321
"""
4322
from sage.graphs.convexity_properties import ConvexityProperties
4323
return ConvexityProperties(self)
4324
4325
### Centrality
4326
4327
def centrality_betweenness(self, k=None, normalized=True, weight=None,
4328
endpoints=False, seed=None):
4329
r"""
4330
Returns the betweenness centrality (fraction of number of
4331
shortest paths that go through each vertex) as a dictionary
4332
keyed by vertices. The betweenness is normalized by default to
4333
be in range (0,1). This wraps NetworkX's implementation of the
4334
algorithm described in [Brandes2003]_.
4335
4336
Measures of the centrality of a vertex within a graph determine
4337
the relative importance of that vertex to its graph. Vertices
4338
that occur on more shortest paths between other vertices have
4339
higher betweenness than vertices that occur on less.
4340
4341
INPUT:
4342
4343
4344
- ``normalized`` - boolean (default True) - if set to False,
4345
result is not normalized.
4346
4347
- ``k`` - integer or None (default None) - if set to an integer,
4348
use ``k`` node samples to estimate betweenness. Higher values
4349
give better approximations.
4350
4351
- ``weight`` - None or string. If set to a string, use that
4352
attribute of the nodes as weight. ``weight = True`` is
4353
equivalent to ``weight = "weight"``
4354
4355
- ``endpoints`` - Boolean. If set to True it includes the
4356
endpoints in the shortest paths count
4357
4358
REFERENCE:
4359
4360
.. [Brandes2003] Ulrik Brandes. (2003). Faster Evaluation of
4361
Shortest-Path Based Centrality Indices. [Online] Available:
4362
http://citeseer.ist.psu.edu/viewdoc/summary?doi=10.1.1.43.1504
4363
4364
EXAMPLES::
4365
4366
sage: (graphs.ChvatalGraph()).centrality_betweenness()
4367
{0: 0.06969696969696969, 1: 0.06969696969696969,
4368
2: 0.0606060606060606, 3: 0.0606060606060606,
4369
4: 0.06969696969696969, 5: 0.06969696969696969,
4370
6: 0.0606060606060606, 7: 0.0606060606060606,
4371
8: 0.0606060606060606, 9: 0.0606060606060606,
4372
10: 0.0606060606060606, 11: 0.0606060606060606}
4373
sage: (graphs.ChvatalGraph()).centrality_betweenness(
4374
... normalized=False)
4375
{0: 3.833333333333333, 1: 3.833333333333333, 2: 3.333333333333333,
4376
3: 3.333333333333333, 4: 3.833333333333333, 5: 3.833333333333333,
4377
6: 3.333333333333333, 7: 3.333333333333333, 8: 3.333333333333333,
4378
9: 3.333333333333333, 10: 3.333333333333333,
4379
11: 3.333333333333333}
4380
sage: D = DiGraph({0:[1,2,3], 1:[2], 3:[0,1]})
4381
sage: D.show(figsize=[2,2])
4382
sage: D = D.to_undirected()
4383
sage: D.show(figsize=[2,2])
4384
sage: D.centrality_betweenness()
4385
{0: 0.16666666666666666, 1: 0.16666666666666666, 2: 0.0, 3: 0.0}
4386
"""
4387
import networkx
4388
return networkx.betweenness_centrality(self.networkx_graph(copy=False),
4389
k=k, normalized=normalized, weight=weight, endpoints=endpoints,
4390
seed=seed)
4391
4392
def centrality_degree(self, v=None):
4393
r"""
4394
Returns the degree centrality (fraction of vertices connected to)
4395
as a dictionary of values keyed by vertex. The degree centrality is
4396
normalized to be in range (0,1).
4397
4398
Measures of the centrality of a vertex within a graph determine the
4399
relative importance of that vertex to its graph. Degree centrality
4400
measures the number of links incident upon a vertex.
4401
4402
INPUT:
4403
4404
4405
- ``v`` - a vertex label (to find degree centrality of
4406
only one vertex)
4407
4408
4409
EXAMPLES::
4410
4411
sage: (graphs.ChvatalGraph()).centrality_degree()
4412
{0: 0.36363636363636365, 1: 0.36363636363636365, 2: 0.36363636363636365, 3: 0.36363636363636365, 4: 0.36363636363636365, 5: 0.36363636363636365, 6: 0.36363636363636365, 7: 0.36363636363636365, 8: 0.36363636363636365, 9: 0.36363636363636365, 10: 0.36363636363636365, 11: 0.36363636363636365}
4413
sage: D = DiGraph({0:[1,2,3], 1:[2], 3:[0,1]})
4414
sage: D.show(figsize=[2,2])
4415
sage: D = D.to_undirected()
4416
sage: D.show(figsize=[2,2])
4417
sage: D.centrality_degree()
4418
{0: 1.0, 1: 1.0, 2: 0.6666666666666666, 3: 0.6666666666666666}
4419
sage: D.centrality_degree(v=1)
4420
1.0
4421
"""
4422
import networkx
4423
if v==None:
4424
return networkx.degree_centrality(self.networkx_graph(copy=False))
4425
else:
4426
return networkx.degree_centrality(self.networkx_graph(copy=False))[v]
4427
4428
def centrality_closeness(self, v=None):
4429
r"""
4430
Returns the closeness centrality (1/average distance to all
4431
vertices) as a dictionary of values keyed by vertex. The degree
4432
centrality is normalized to be in range (0,1).
4433
4434
Measures of the centrality of a vertex within a graph determine the
4435
relative importance of that vertex to its graph. 'Closeness
4436
centrality may be defined as the total graph-theoretic distance of
4437
a given vertex from all other vertices... Closeness is an inverse
4438
measure of centrality in that a larger value indicates a less
4439
central actor while a smaller value indicates a more central
4440
actor,' [Borgatti95]_.
4441
4442
INPUT:
4443
4444
4445
- ``v`` - a vertex label (to find degree centrality of
4446
only one vertex)
4447
4448
4449
REFERENCE:
4450
4451
.. [Borgatti95] Stephen P Borgatti. (1995). Centrality and AIDS.
4452
[Online] Available:
4453
http://www.analytictech.com/networks/centaids.htm
4454
4455
EXAMPLES::
4456
4457
sage: (graphs.ChvatalGraph()).centrality_closeness()
4458
{0: 0.61111111111111..., 1: 0.61111111111111..., 2: 0.61111111111111..., 3: 0.61111111111111..., 4: 0.61111111111111..., 5: 0.61111111111111..., 6: 0.61111111111111..., 7: 0.61111111111111..., 8: 0.61111111111111..., 9: 0.61111111111111..., 10: 0.61111111111111..., 11: 0.61111111111111...}
4459
sage: D = DiGraph({0:[1,2,3], 1:[2], 3:[0,1]})
4460
sage: D.show(figsize=[2,2])
4461
sage: D = D.to_undirected()
4462
sage: D.show(figsize=[2,2])
4463
sage: D.centrality_closeness()
4464
{0: 1.0, 1: 1.0, 2: 0.75, 3: 0.75}
4465
sage: D.centrality_closeness(v=1)
4466
1.0
4467
"""
4468
import networkx
4469
return networkx.closeness_centrality(self.networkx_graph(copy=False), v)
4470
4471
### Constructors
4472
4473
def to_directed(self, implementation='c_graph', data_structure=None,
4474
sparse=None):
4475
"""
4476
Returns a directed version of the graph. A single edge becomes two
4477
edges, one in each direction.
4478
4479
INPUT:
4480
4481
- ``implementation`` - string (default: 'networkx') the
4482
implementation goes here. Current options are only
4483
'networkx' or 'c_graph'.
4484
4485
- ``data_structure`` -- one of ``"sparse"``, ``"static_sparse"``, or
4486
``"dense"``. See the documentation of :class:`Graph` or
4487
:class:`DiGraph`.
4488
4489
- ``sparse`` (boolean) -- ``sparse=True`` is an alias for
4490
``data_structure="sparse"``, and ``sparse=False`` is an alias for
4491
``data_structure="dense"``.
4492
4493
EXAMPLES::
4494
4495
sage: graphs.PetersenGraph().to_directed()
4496
Petersen graph: Digraph on 10 vertices
4497
"""
4498
if sparse != None:
4499
if data_structure != None:
4500
raise ValueError("The 'sparse' argument is an alias for "
4501
"'data_structure'. Please do not define both.")
4502
data_structure = "sparse" if sparse else "dense"
4503
4504
if data_structure is None:
4505
from sage.graphs.base.dense_graph import DenseGraphBackend
4506
from sage.graphs.base.sparse_graph import SparseGraphBackend
4507
if isinstance(self._backend, DenseGraphBackend):
4508
data_structure = "dense"
4509
elif isinstance(self._backend, SparseGraphBackend):
4510
data_structure = "sparse"
4511
else:
4512
data_structure = "static_sparse"
4513
from sage.graphs.all import DiGraph
4514
D = DiGraph(name=self.name(), pos=self._pos, boundary=self._boundary,
4515
multiedges=self.allows_multiple_edges(),
4516
implementation=implementation, data_structure=data_structure)
4517
D.name(self.name())
4518
D.add_vertices(self.vertex_iterator())
4519
for u,v,l in self.edge_iterator():
4520
D.add_edge(u,v,l)
4521
D.add_edge(v,u,l)
4522
if hasattr(self, '_embedding'):
4523
from copy import copy
4524
D._embedding = copy(self._embedding)
4525
D._weighted = self._weighted
4526
return D
4527
4528
def to_undirected(self):
4529
"""
4530
Since the graph is already undirected, simply returns a copy of
4531
itself.
4532
4533
EXAMPLES::
4534
4535
sage: graphs.PetersenGraph().to_undirected()
4536
Petersen graph: Graph on 10 vertices
4537
"""
4538
from copy import copy
4539
return copy(self)
4540
4541
def join(self, other, verbose_relabel=True):
4542
"""
4543
Returns the join of self and other.
4544
4545
INPUT:
4546
4547
- ``verbose_relabel`` - (defaults to True) If True, each vertex `v` in
4548
the first graph will be named '0,v' and each vertex u in the second
4549
graph will be named'1,u' in the final graph. If False, the vertices
4550
of the first graph and the second graph will be relabeled with
4551
consecutive integers.
4552
4553
.. SEEALSO::
4554
4555
* :meth:`~sage.graphs.generic_graph.GenericGraph.union`
4556
4557
* :meth:`~sage.graphs.generic_graph.GenericGraph.disjoint_union`
4558
4559
EXAMPLES::
4560
4561
sage: G = graphs.CycleGraph(3)
4562
sage: H = Graph(2)
4563
sage: J = G.join(H); J
4564
Cycle graph join : Graph on 5 vertices
4565
sage: J.vertices()
4566
[(0, 0), (0, 1), (0, 2), (1, 0), (1, 1)]
4567
sage: J = G.join(H, verbose_relabel=False); J
4568
Cycle graph join : Graph on 5 vertices
4569
sage: J.vertices()
4570
[0, 1, 2, 3, 4]
4571
sage: J.edges()
4572
[(0, 1, None), (0, 2, None), (0, 3, None), (0, 4, None), (1, 2, None), (1, 3, None), (1, 4, None), (2, 3, None), (2, 4, None)]
4573
4574
::
4575
4576
sage: G = Graph(3)
4577
sage: G.name("Graph on 3 vertices")
4578
sage: H = Graph(2)
4579
sage: H.name("Graph on 2 vertices")
4580
sage: J = G.join(H); J
4581
Graph on 3 vertices join Graph on 2 vertices: Graph on 5 vertices
4582
sage: J.vertices()
4583
[(0, 0), (0, 1), (0, 2), (1, 0), (1, 1)]
4584
sage: J = G.join(H, verbose_relabel=False); J
4585
Graph on 3 vertices join Graph on 2 vertices: Graph on 5 vertices
4586
sage: J.edges()
4587
[(0, 3, None), (0, 4, None), (1, 3, None), (1, 4, None), (2, 3, None), (2, 4, None)]
4588
"""
4589
G = self.disjoint_union(other, verbose_relabel)
4590
if not verbose_relabel:
4591
G.add_edges((u,v) for u in range(self.order())
4592
for v in range(self.order(), self.order()+other.order()))
4593
else:
4594
G.add_edges(((0,u), (1,v)) for u in self.vertices()
4595
for v in other.vertices())
4596
4597
G.name('%s join %s'%(self.name(), other.name()))
4598
return G
4599
4600
### Visualization
4601
4602
def write_to_eps(self, filename, **options):
4603
r"""
4604
Writes a plot of the graph to ``filename`` in ``eps`` format.
4605
4606
INPUT:
4607
4608
- ``filename`` -- a string
4609
- ``**options`` -- same layout options as :meth:`.layout`
4610
4611
EXAMPLES::
4612
4613
sage: P = graphs.PetersenGraph()
4614
sage: P.write_to_eps(tmp_filename(ext='.eps'))
4615
4616
It is relatively simple to include this file in a LaTeX
4617
document. ``\usepackagegraphics`` must appear in the
4618
preamble, and ``\includegraphics{filename.eps}`` will include
4619
the file. To compile the document to ``pdf`` with ``pdflatex``
4620
the file needs first to be converted to ``pdf``, for example
4621
with ``ps2pdf filename.eps filename.pdf``.
4622
"""
4623
from sage.graphs.print_graphs import print_graph_eps
4624
pos = self.layout(**options)
4625
[xmin, xmax, ymin, ymax] = self._layout_bounding_box(pos)
4626
for v in pos:
4627
pos[v] = (1.8*(pos[v][0] - xmin)/(xmax - xmin) - 0.9, 1.8*(pos[v][1] - ymin)/(ymax - ymin) - 0.9)
4628
if filename[-4:] != '.eps':
4629
filename += '.eps'
4630
f = open(filename, 'w')
4631
f.write( print_graph_eps(self.vertices(), self.edge_iterator(), pos) )
4632
f.close()
4633
4634
def topological_minor(self, H, vertices = False, paths = False, solver=None, verbose=0):
4635
r"""
4636
Returns a topological `H`-minor from ``self`` if one exists.
4637
4638
We say that a graph `G` has a topological `H`-minor (or that
4639
it has a graph isomorphic to `H` as a topological minor), if
4640
`G` contains a subdivision of a graph isomorphic to `H` (=
4641
obtained from `H` through arbitrary subdivision of its edges)
4642
as a subgraph.
4643
4644
For more information, see the `Wikipedia article on graph minor
4645
:wikipedia:`Minor_(graph_theory)`.
4646
4647
INPUT:
4648
4649
- ``H`` -- The topological minor to find in the current graph.
4650
4651
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
4652
solver to be used. If set to ``None``, the default one is used. For
4653
more information on LP solvers and which default solver is used, see
4654
the method
4655
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
4656
of the class
4657
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
4658
4659
- ``verbose`` -- integer (default: ``0``). Sets the level of
4660
verbosity. Set to 0 by default, which means quiet.
4661
4662
OUTPUT:
4663
4664
The topological `H`-minor found is returned as a subgraph `M`
4665
of ``self``, such that the vertex `v` of `M` that represents a
4666
vertex `h\in H` has ``h`` as a label (see
4667
:meth:`get_vertex <sage.graphs.generic_graph.GenericGraph.get_vertex>`
4668
and
4669
:meth:`set_vertex <sage.graphs.generic_graph.GenericGraph.set_vertex>`),
4670
and such that every edge of `M` has as a label the edge of `H`
4671
it (partially) represents.
4672
4673
If no topological minor is found, this method returns
4674
``False``.
4675
4676
ALGORITHM:
4677
4678
Mixed Integer Linear Programming.
4679
4680
COMPLEXITY:
4681
4682
Theoretically, when `H` is fixed, testing for the existence of
4683
a topological `H`-minor is polynomial. The known algorithms
4684
are highly exponential in `H`, though.
4685
4686
.. NOTE::
4687
4688
This function can be expected to be *very* slow, especially where
4689
the topological minor does not exist.
4690
4691
(CPLEX seems to be *much* more efficient than GLPK on this kind of
4692
problem)
4693
4694
EXAMPLES:
4695
4696
Petersen's graph has a topological `K_4`-minor::
4697
4698
sage: g = graphs.PetersenGraph()
4699
sage: g.topological_minor(graphs.CompleteGraph(4))
4700
Subgraph of (Petersen graph): Graph on ...
4701
4702
And a topological `K_{3,3}`-minor::
4703
4704
sage: g.topological_minor(graphs.CompleteBipartiteGraph(3,3))
4705
Subgraph of (Petersen graph): Graph on ...
4706
4707
And of course, a tree has no topological `C_3`-minor::
4708
4709
sage: g = graphs.RandomGNP(15,.3)
4710
sage: g = g.subgraph(edges = g.min_spanning_tree())
4711
sage: g.topological_minor(graphs.CycleGraph(3))
4712
False
4713
"""
4714
self._scream_if_not_simple()
4715
H._scream_if_not_simple()
4716
# Useful alias ...
4717
G = self
4718
4719
from sage.numerical.mip import MixedIntegerLinearProgram, MIPSolverException
4720
p = MixedIntegerLinearProgram()
4721
4722
# This is an existence problem
4723
p.set_objective(None)
4724
4725
#######################
4726
# Vertex representant #
4727
#######################
4728
#
4729
# v_repr[h][g] = 1 if vertex h from H is represented by vertex
4730
# g from G, 0 otherwise
4731
4732
v_repr = p.new_variable(binary = True, dim = 2)
4733
4734
# Exactly one representant per vertex of H
4735
for h in H:
4736
p.add_constraint( p.sum( v_repr[h][g] for g in G), min = 1, max = 1)
4737
4738
# A vertex of G can only represent one vertex of H
4739
for g in G:
4740
p.add_constraint( p.sum( v_repr[h][g] for h in H), max = 1)
4741
4742
4743
###################
4744
# Is representent #
4745
###################
4746
#
4747
# is_repr[v] = 1 if v represents some vertex of H
4748
4749
is_repr = p.new_variable(binary = True)
4750
4751
for g in G:
4752
for h in H:
4753
p.add_constraint( v_repr[h][g] - is_repr[g], max = 0)
4754
4755
4756
###################################
4757
# paths between the representents #
4758
###################################
4759
#
4760
# For any edge (h1,h2) in H, we have a corresponding path in G
4761
# between the representants of h1 and h2. Which means there is
4762
# a flow of intensity 1 from one to the other.
4763
# We are then writing a flow problem for each edge of H.
4764
#
4765
# The variable flow[(h1,h2)][(g1,g2)] indicates the amount of
4766
# flow on the edge (g1,g2) representing the edge (h1,h2).
4767
4768
flow = p.new_variable(binary = True, dim = 2)
4769
4770
# This lambda function returns the balance of flow
4771
# corresponding to commodity C at vertex v v
4772
4773
flow_in = lambda C, v : p.sum( flow[C][(v,u)] for u in G.neighbors(v) )
4774
flow_out = lambda C, v : p.sum( flow[C][(u,v)] for u in G.neighbors(v) )
4775
4776
flow_balance = lambda C, v : flow_in(C,v) - flow_out(C,v)
4777
4778
for h1,h2 in H.edges(labels = False):
4779
4780
for v in G:
4781
4782
# The flow balance depends on whether the vertex v is
4783
# a representant of h1 or h2 in G, or a reprensentant
4784
# of none
4785
4786
p.add_constraint( flow_balance((h1,h2),v) == v_repr[h1][v] - v_repr[h2][v] )
4787
4788
#############################
4789
# Internal vertex of a path #
4790
#############################
4791
#
4792
# is_internal[C][g] = 1 if a vertex v from G is located on the
4793
# path representing the edge (=commodity) C
4794
4795
is_internal = p.new_variable(dim = 2, binary = True)
4796
4797
# When is a vertex internal for a commodity ?
4798
for C in H.edges(labels = False):
4799
for g in G:
4800
p.add_constraint( flow_in(C,g) + flow_out(C,g) - is_internal[C][g], max = 1)
4801
4802
4803
############################
4804
# Two paths do not cross ! #
4805
############################
4806
4807
# A vertex can only be internal for one commodity, and zero if
4808
# the vertex is a representent
4809
4810
for g in G:
4811
p.add_constraint( p.sum( is_internal[C][g] for C in H.edges(labels = False))
4812
+ is_repr[g], max = 1 )
4813
4814
# (The following inequalities are not necessary, but they seem
4815
# to be of help (the solvers find the answer quicker when they
4816
# are added)
4817
4818
# The flow on one edge can go in only one direction. Besides,
4819
# it can belong to at most one commodity and has a maximum
4820
# intensity of 1.
4821
4822
for g1,g2 in G.edges(labels = None):
4823
4824
p.add_constraint( p.sum( flow[C][(g1,g2)] for C in H.edges(labels = False) )
4825
+ p.sum( flow[C][(g2,g1)] for C in H.edges(labels = False) ),
4826
max = 1)
4827
4828
4829
# Now we can solve the problem itself !
4830
4831
try:
4832
p.solve(solver = solver, log = verbose)
4833
4834
except MIPSolverException:
4835
return False
4836
4837
4838
minor = G.subgraph()
4839
4840
is_repr = p.get_values(is_repr)
4841
v_repr = p.get_values(v_repr)
4842
flow = p.get_values(flow)
4843
4844
4845
for u,v in minor.edges(labels = False):
4846
used = False
4847
for C in H.edges(labels = False):
4848
4849
if flow[C][(u,v)] + flow[C][(v,u)] > .5:
4850
used = True
4851
minor.set_edge_label(u,v,C)
4852
break
4853
if not used:
4854
minor.delete_edge(u,v)
4855
4856
minor.delete_vertices( [v for v in minor
4857
if minor.degree(v) == 0 ] )
4858
4859
for g in minor:
4860
if is_repr[g] > .5:
4861
for h in H:
4862
if v_repr[h][v] > .5:
4863
minor.set_vertex(g,h)
4864
break
4865
4866
return minor
4867
4868
### Cliques
4869
4870
def cliques_maximal(self):
4871
"""
4872
Returns the list of all maximal cliques, with each clique represented
4873
by a list of vertices. A clique is an induced complete subgraph, and a
4874
maximal clique is one not contained in a larger one.
4875
4876
.. NOTE::
4877
4878
Currently only implemented for undirected graphs. Use to_undirected
4879
to convert a digraph to an undirected graph.
4880
4881
ALGORITHM:
4882
4883
This function is based on NetworkX's implementation of the Bron and
4884
Kerbosch Algorithm [BroKer1973]_.
4885
4886
REFERENCE:
4887
4888
.. [BroKer1973] Coen Bron and Joep Kerbosch. (1973). Algorithm 457:
4889
Finding All Cliques of an Undirected Graph. Commun. ACM. v
4890
16. n 9. pages 575-577. ACM Press. [Online] Available:
4891
http://www.ram.org/computing/rambin/rambin.html
4892
4893
EXAMPLES::
4894
4895
sage: graphs.ChvatalGraph().cliques_maximal()
4896
[[0, 1], [0, 4], [0, 6], [0, 9], [2, 1], [2, 3], [2, 6], [2, 8], [3, 4], [3, 7], [3, 9], [5, 1], [5, 4], [5, 10], [5, 11], [7, 1], [7, 8], [7, 11], [8, 4], [8, 10], [10, 6], [10, 9], [11, 6], [11, 9]]
4897
sage: G = Graph({0:[1,2,3], 1:[2], 3:[0,1]})
4898
sage: G.show(figsize=[2,2])
4899
sage: G.cliques_maximal()
4900
[[0, 1, 2], [0, 1, 3]]
4901
sage: C=graphs.PetersenGraph()
4902
sage: C.cliques_maximal()
4903
[[0, 1], [0, 4], [0, 5], [2, 1], [2, 3], [2, 7], [3, 4], [3, 8], [6, 1], [6, 8], [6, 9], [7, 5], [7, 9], [8, 5], [9, 4]]
4904
sage: C = Graph('DJ{')
4905
sage: C.cliques_maximal()
4906
[[4, 0], [4, 1, 2, 3]]
4907
4908
"""
4909
import networkx
4910
return sorted(networkx.find_cliques(self.networkx_graph(copy=False)))
4911
4912
cliques = deprecated_function_alias(5739, cliques_maximal)
4913
4914
def clique_maximum(self, algorithm="Cliquer"):
4915
"""
4916
Returns the vertex set of a maximal order complete subgraph.
4917
4918
INPUT:
4919
4920
- ``algorithm`` -- the algorithm to be used :
4921
4922
- If ``algorithm = "Cliquer"`` (default) - This wraps the C program
4923
Cliquer [NisOst2003]_.
4924
4925
- If ``algorithm = "MILP"``, the problem is solved through a Mixed
4926
Integer Linear Program.
4927
4928
(see :class:`~sage.numerical.mip.MixedIntegerLinearProgram`)
4929
4930
.. NOTE::
4931
4932
Currently only implemented for undirected graphs. Use to_undirected
4933
to convert a digraph to an undirected graph.
4934
4935
ALGORITHM:
4936
4937
This function is based on Cliquer [NisOst2003]_.
4938
4939
EXAMPLES:
4940
4941
Using Cliquer (default)::
4942
4943
sage: C=graphs.PetersenGraph()
4944
sage: C.clique_maximum()
4945
[7, 9]
4946
sage: C = Graph('DJ{')
4947
sage: C.clique_maximum()
4948
[1, 2, 3, 4]
4949
4950
Through a Linear Program::
4951
4952
sage: len(C.clique_maximum(algorithm = "MILP"))
4953
4
4954
4955
TESTS:
4956
4957
Wrong algorithm::
4958
4959
sage: C.clique_maximum(algorithm = "BFS")
4960
Traceback (most recent call last):
4961
...
4962
NotImplementedError: Only 'MILP' and 'Cliquer' are supported.
4963
"""
4964
self._scream_if_not_simple(allow_multiple_edges=True)
4965
if algorithm=="Cliquer":
4966
from sage.graphs.cliquer import max_clique
4967
return max_clique(self)
4968
elif algorithm == "MILP":
4969
return self.complement().independent_set(algorithm = algorithm)
4970
else:
4971
raise NotImplementedError("Only 'MILP' and 'Cliquer' are supported.")
4972
4973
def clique_number(self, algorithm="Cliquer", cliques=None):
4974
r"""
4975
Returns the order of the largest clique of the graph (the clique
4976
number).
4977
4978
.. NOTE::
4979
4980
Currently only implemented for undirected graphs. Use ``to_undirected``
4981
to convert a digraph to an undirected graph.
4982
4983
INPUT:
4984
4985
- ``algorithm`` -- the algorithm to be used :
4986
4987
- If ``algorithm = "Cliquer"`` - This wraps the C program Cliquer [NisOst2003]_.
4988
4989
- If ``algorithm = "networkx"`` - This function is based on NetworkX's implementation
4990
of the Bron and Kerbosch Algorithm [BroKer1973]_.
4991
4992
- If ``algorithm = "MILP"``, the problem is solved through a Mixed
4993
Integer Linear Program.
4994
4995
(see :class:`~sage.numerical.mip.MixedIntegerLinearProgram`)
4996
4997
- ``cliques`` - an optional list of cliques that can be input if
4998
already computed. Ignored unless ``algorithm=="networkx"``.
4999
5000
ALGORITHM:
5001
5002
This function is based on Cliquer [NisOst2003]_ and [BroKer1973]_.
5003
5004
EXAMPLES::
5005
5006
sage: C = Graph('DJ{')
5007
sage: C.clique_number()
5008
4
5009
sage: G = Graph({0:[1,2,3], 1:[2], 3:[0,1]})
5010
sage: G.show(figsize=[2,2])
5011
sage: G.clique_number()
5012
3
5013
5014
By definition the clique number of a complete graph is its order::
5015
5016
sage: all(graphs.CompleteGraph(i).clique_number() == i for i in xrange(1,15))
5017
True
5018
5019
A non-empty graph without edges has a clique number of 1::
5020
5021
sage: all((i*graphs.CompleteGraph(1)).clique_number() == 1 for i in xrange(1,15))
5022
True
5023
5024
A complete multipartite graph with k parts has clique number k::
5025
5026
sage: all((i*graphs.CompleteMultipartiteGraph(i*[5])).clique_number() == i for i in xrange(1,6))
5027
True
5028
5029
TESTS::
5030
5031
sage: g = graphs.PetersenGraph()
5032
sage: g.clique_number(algorithm="MILP")
5033
2
5034
"""
5035
self._scream_if_not_simple(allow_loops=False)
5036
if algorithm=="Cliquer":
5037
from sage.graphs.cliquer import clique_number
5038
return clique_number(self)
5039
elif algorithm=="networkx":
5040
import networkx
5041
return networkx.graph_clique_number(self.networkx_graph(copy=False),cliques)
5042
elif algorithm == "MILP":
5043
return len(self.complement().independent_set(algorithm = algorithm))
5044
else:
5045
raise NotImplementedError("Only 'networkx' 'MILP' and 'Cliquer' are supported.")
5046
5047
def cliques_number_of(self, vertices=None, cliques=None):
5048
"""
5049
Returns a dictionary of the number of maximal cliques containing each
5050
vertex, keyed by vertex. (Returns a single value if
5051
only one input vertex).
5052
5053
.. NOTE::
5054
5055
Currently only implemented for undirected graphs. Use to_undirected
5056
to convert a digraph to an undirected graph.
5057
5058
INPUT:
5059
5060
- ``vertices`` - the vertices to inspect (default is
5061
entire graph)
5062
5063
- ``cliques`` - list of cliques (if already
5064
computed)
5065
5066
5067
EXAMPLES::
5068
5069
sage: C = Graph('DJ{')
5070
sage: C.cliques_number_of()
5071
{0: 1, 1: 1, 2: 1, 3: 1, 4: 2}
5072
sage: E = C.cliques_maximal()
5073
sage: E
5074
[[4, 0], [4, 1, 2, 3]]
5075
sage: C.cliques_number_of(cliques=E)
5076
{0: 1, 1: 1, 2: 1, 3: 1, 4: 2}
5077
sage: F = graphs.Grid2dGraph(2,3)
5078
sage: X = F.cliques_number_of()
5079
sage: for v in sorted(X.iterkeys()):
5080
... print v, X[v]
5081
(0, 0) 2
5082
(0, 1) 3
5083
(0, 2) 2
5084
(1, 0) 2
5085
(1, 1) 3
5086
(1, 2) 2
5087
sage: F.cliques_number_of(vertices=[(0, 1), (1, 2)])
5088
{(0, 1): 3, (1, 2): 2}
5089
sage: G = Graph({0:[1,2,3], 1:[2], 3:[0,1]})
5090
sage: G.show(figsize=[2,2])
5091
sage: G.cliques_number_of()
5092
{0: 2, 1: 2, 2: 1, 3: 1}
5093
"""
5094
import networkx
5095
return networkx.number_of_cliques(self.networkx_graph(copy=False), vertices, cliques)
5096
5097
def cliques_get_max_clique_graph(self, name=''):
5098
"""
5099
Returns a graph constructed with maximal cliques as vertices, and
5100
edges between maximal cliques with common members in the original
5101
graph.
5102
5103
.. NOTE::
5104
5105
Currently only implemented for undirected graphs. Use to_undirected
5106
to convert a digraph to an undirected graph.
5107
5108
INPUT:
5109
5110
- ``name`` - The name of the new graph.
5111
5112
EXAMPLES::
5113
5114
sage: (graphs.ChvatalGraph()).cliques_get_max_clique_graph()
5115
Graph on 24 vertices
5116
sage: ((graphs.ChvatalGraph()).cliques_get_max_clique_graph()).show(figsize=[2,2], vertex_size=20, vertex_labels=False)
5117
sage: G = Graph({0:[1,2,3], 1:[2], 3:[0,1]})
5118
sage: G.show(figsize=[2,2])
5119
sage: G.cliques_get_max_clique_graph()
5120
Graph on 2 vertices
5121
sage: (G.cliques_get_max_clique_graph()).show(figsize=[2,2])
5122
"""
5123
import networkx
5124
return Graph(networkx.make_max_clique_graph(self.networkx_graph(copy=False), name=name, create_using=networkx.MultiGraph()))
5125
5126
def cliques_get_clique_bipartite(self, **kwds):
5127
"""
5128
Returns a bipartite graph constructed such that maximal cliques are the
5129
right vertices and the left vertices are retained from the given
5130
graph. Right and left vertices are connected if the bottom vertex
5131
belongs to the clique represented by a top vertex.
5132
5133
.. NOTE::
5134
5135
Currently only implemented for undirected graphs. Use to_undirected
5136
to convert a digraph to an undirected graph.
5137
5138
EXAMPLES::
5139
5140
sage: (graphs.ChvatalGraph()).cliques_get_clique_bipartite()
5141
Bipartite graph on 36 vertices
5142
sage: ((graphs.ChvatalGraph()).cliques_get_clique_bipartite()).show(figsize=[2,2], vertex_size=20, vertex_labels=False)
5143
sage: G = Graph({0:[1,2,3], 1:[2], 3:[0,1]})
5144
sage: G.show(figsize=[2,2])
5145
sage: G.cliques_get_clique_bipartite()
5146
Bipartite graph on 6 vertices
5147
sage: (G.cliques_get_clique_bipartite()).show(figsize=[2,2])
5148
"""
5149
from bipartite_graph import BipartiteGraph
5150
import networkx
5151
return BipartiteGraph(networkx.make_clique_bipartite(self.networkx_graph(copy=False), **kwds))
5152
5153
def independent_set(self, algorithm = "Cliquer", value_only = False, reduction_rules = True, solver = None, verbosity = 0):
5154
r"""
5155
Returns a maximum independent set.
5156
5157
An independent set of a graph is a set of pairwise non-adjacent
5158
vertices. A maximum independent set is an independent set of maximum
5159
cardinality. It induces an empty subgraph.
5160
5161
Equivalently, an independent set is defined as the complement of a
5162
vertex cover.
5163
5164
INPUT:
5165
5166
- ``algorithm`` -- the algorithm to be used
5167
5168
* If ``algorithm = "Cliquer"`` (default), the problem is solved
5169
using Cliquer [NisOst2003]_.
5170
5171
(see the :mod:`Cliquer modules <sage.graphs.cliquer>`)
5172
5173
* If ``algorithm = "MILP"``, the problem is solved through a Mixed
5174
Integer Linear Program.
5175
5176
(see :class:`~sage.numerical.mip.MixedIntegerLinearProgram`)
5177
5178
- ``value_only`` -- boolean (default: ``False``). If set to ``True``,
5179
only the size of a maximum independent set is returned. Otherwise,
5180
a maximum independent set is returned as a list of vertices.
5181
5182
- ``reduction_rules`` -- (default: ``True``) Specify if the reductions
5183
rules from kernelization must be applied as pre-processing or not.
5184
See [ACFLSS04]_ for more details. Note that depending on the
5185
instance, it might be faster to disable reduction rules.
5186
5187
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
5188
solver to be used. If set to ``None``, the default one is used. For
5189
more information on LP solvers and which default solver is used, see
5190
the method
5191
:meth:`~sage.numerical.mip.MixedIntegerLinearProgram.solve`
5192
of the class
5193
:class:`~sage.numerical.mip.MixedIntegerLinearProgram`.
5194
5195
- ``verbosity`` -- non-negative integer (default: ``0``). Set the level
5196
of verbosity you want from the linear program solver. Since the
5197
problem of computing an independent set is `NP`-complete, its solving
5198
may take some time depending on the graph. A value of 0 means that
5199
there will be no message printed by the solver. This option is only
5200
useful if ``algorithm="MILP"``.
5201
5202
.. NOTE::
5203
5204
While Cliquer is usually (and by far) the most efficient of the two
5205
implementations, the Mixed Integer Linear Program formulation
5206
sometimes proves faster on very "symmetrical" graphs.
5207
5208
EXAMPLES:
5209
5210
Using Cliquer::
5211
5212
sage: C = graphs.PetersenGraph()
5213
sage: C.independent_set()
5214
[0, 3, 6, 7]
5215
5216
As a linear program::
5217
5218
sage: C = graphs.PetersenGraph()
5219
sage: len(C.independent_set(algorithm = "MILP"))
5220
4
5221
"""
5222
my_cover = self.vertex_cover(algorithm=algorithm, value_only=value_only, reduction_rules=reduction_rules, solver=solver, verbosity=verbosity)
5223
if value_only:
5224
return self.order() - my_cover
5225
else:
5226
return [u for u in self.vertices() if not u in my_cover]
5227
5228
5229
def vertex_cover(self, algorithm = "Cliquer", value_only = False,
5230
reduction_rules = True, solver = None, verbosity = 0):
5231
r"""
5232
Returns a minimum vertex cover of self represented by a set of vertices.
5233
5234
A minimum vertex cover of a graph is a set `S` of vertices such that
5235
each edge is incident to at least one element of `S`, and such that `S`
5236
is of minimum cardinality. For more information, see the
5237
:wikipedia:`Wikipedia article on vertex cover <Vertex_cover>`.
5238
5239
Equivalently, a vertex cover is defined as the complement of an
5240
independent set.
5241
5242
As an optimization problem, it can be expressed as follows:
5243
5244
.. MATH::
5245
5246
\mbox{Minimize : }&\sum_{v\in G} b_v\\
5247
\mbox{Such that : }&\forall (u,v) \in G.edges(), b_u+b_v\geq 1\\
5248
&\forall x\in G, b_x\mbox{ is a binary variable}
5249
5250
INPUT:
5251
5252
- ``algorithm`` -- string (default: ``"Cliquer"``). Indicating
5253
which algorithm to use. It can be one of those two values.
5254
5255
- ``"Cliquer"`` will compute a minimum vertex cover
5256
using the Cliquer package.
5257
5258
- ``"MILP"`` will compute a minimum vertex cover through a mixed
5259
integer linear program.
5260
5261
- ``value_only`` -- boolean (default: ``False``). If set to ``True``,
5262
only the size of a minimum vertex cover is returned. Otherwise,
5263
a minimum vertex cover is returned as a list of vertices.
5264
5265
- ``reduction_rules`` -- (default: ``True``) Specify if the reductions
5266
rules from kernelization must be applied as pre-processing or not.
5267
See [ACFLSS04]_ for more details. Note that depending on the
5268
instance, it might be faster to disable reduction rules.
5269
5270
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
5271
solver to be used. If set to ``None``, the default one is used. For
5272
more information on LP solvers and which default solver is used, see
5273
the method
5274
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
5275
of the class
5276
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
5277
5278
- ``verbosity`` -- non-negative integer (default: ``0``). Set the level
5279
of verbosity you want from the linear program solver. Since the
5280
problem of computing a vertex cover is `NP`-complete, its solving may
5281
take some time depending on the graph. A value of 0 means that there
5282
will be no message printed by the solver. This option is only useful
5283
if ``algorithm="MILP"``.
5284
5285
EXAMPLES:
5286
5287
On the Pappus graph::
5288
5289
sage: g = graphs.PappusGraph()
5290
sage: g.vertex_cover(value_only=True)
5291
9
5292
5293
TESTS:
5294
5295
The two algorithms should return the same result::
5296
5297
sage: g = graphs.RandomGNP(10,.5)
5298
sage: vc1 = g.vertex_cover(algorithm="MILP")
5299
sage: vc2 = g.vertex_cover(algorithm="Cliquer")
5300
sage: len(vc1) == len(vc2)
5301
True
5302
5303
The cardinality of the vertex cover is unchanged when reduction rules are used. First for trees::
5304
5305
sage: for i in range(20):
5306
... g = graphs.RandomTree(20)
5307
... vc1_set = g.vertex_cover()
5308
... vc1 = len(vc1_set)
5309
... vc2 = g.vertex_cover(value_only = True, reduction_rules = False)
5310
... if vc1 != vc2:
5311
... print "Error :", vc1, vc2
5312
... print "With reduction rules :", vc1
5313
... print "Without reduction rules :", vc2
5314
... break
5315
... g.delete_vertices(vc1_set)
5316
... if g.size() != 0:
5317
... print "This thing is not a vertex cover !"
5318
5319
Then for random GNP graphs::
5320
5321
sage: for i in range(20):
5322
... g = graphs.RandomGNP(50,4/50)
5323
... vc1_set = g.vertex_cover()
5324
... vc1 = len(vc1_set)
5325
... vc2 = g.vertex_cover(value_only = True, reduction_rules = False)
5326
... if vc1 != vc2:
5327
... print "Error :", vc1, vc2
5328
... print "With reduction rules :", vc1
5329
... print "Without reduction rules :", vc2
5330
... break
5331
... g.delete_vertices(vc1_set)
5332
... if g.size() != 0:
5333
... print "This thing is not a vertex cover !"
5334
5335
5336
Given a wrong algorithm::
5337
5338
sage: graphs.PetersenGraph().vertex_cover(algorithm = "guess")
5339
Traceback (most recent call last):
5340
...
5341
ValueError: The algorithm must be either "Cliquer" or "MILP".
5342
5343
REFERENCE:
5344
5345
.. [ACFLSS04] F. N. Abu-Khzam, R. L. Collins, M. R. Fellows, M. A.
5346
Langston, W. H. Suters, and C. T. Symons: Kernelization Algorithm for
5347
the Vertex Cover Problem: Theory and Experiments. *SIAM ALENEX/ANALCO*
5348
2004: 62-69.
5349
"""
5350
self._scream_if_not_simple(allow_multiple_edges=True)
5351
g = self
5352
5353
ppset = []
5354
folded_vertices = []
5355
5356
###################
5357
# Reduction rules #
5358
###################
5359
5360
if reduction_rules:
5361
# We apply simple reduction rules allowing to identify vertices that
5362
# belongs to an optimal vertex cover
5363
5364
# We first create manually a copy of the graph to prevent creating
5365
# multi-edges when merging vertices, if edges have labels (e.g., weights).
5366
g = self.copy()
5367
5368
degree_at_most_two = set([u for u,du in g.degree(labels = True).items() if du <= 2])
5369
5370
while degree_at_most_two:
5371
5372
u = degree_at_most_two.pop()
5373
du = g.degree(u)
5374
5375
if du == 0:
5376
# RULE 1: isolated vertices are not part of the cover. We
5377
# simply remove them from the graph. The degree of such
5378
# vertices may have been reduced to 0 while applying other
5379
# reduction rules
5380
g.delete_vertex(u)
5381
5382
elif du == 1:
5383
# RULE 2: If a vertex u has degree 1, we select its neighbor
5384
# v and remove both u and v from g.
5385
v = g.neighbors(u)[0]
5386
ppset.append(v)
5387
g.delete_vertex(u)
5388
5389
for w in g.neighbors(v):
5390
if g.degree(w) <= 3:
5391
# The degree of w will be at most two after the
5392
# deletion of v
5393
degree_at_most_two.add(w)
5394
5395
g.delete_vertex(v)
5396
degree_at_most_two.discard(v)
5397
5398
elif du == 2:
5399
v,w = g.neighbors(u)
5400
5401
if g.has_edge(v,w):
5402
# RULE 3: If the neighbors v and w of a degree 2 vertex
5403
# u are incident, then we select both v and w and remove
5404
# u, v, and w from g.
5405
ppset.append(v)
5406
ppset.append(w)
5407
g.delete_vertex(u)
5408
neigh = set(g.neighbors(v) + g.neighbors(w)).difference(set([v,w]))
5409
g.delete_vertex(v)
5410
g.delete_vertex(w)
5411
5412
for z in neigh:
5413
if g.degree(z) <= 2:
5414
degree_at_most_two.add(z)
5415
5416
else:
5417
# RULE 4, folded vertices: If the neighbors v and w of a
5418
# degree 2 vertex u are not incident, then we contract
5419
# edges (u, v), (u,w). Then, if the solution contains u,
5420
# we replace it with v and w. Otherwise, we let u in the
5421
# solution.
5422
neigh = set(g.neighbors(v) + g.neighbors(w)).difference(set([u,v,w]))
5423
g.delete_vertex(v)
5424
g.delete_vertex(w)
5425
for z in neigh:
5426
g.add_edge(u,z)
5427
5428
folded_vertices += [(u,v,w)]
5429
5430
if g.degree(u) <= 2:
5431
degree_at_most_two.add(u)
5432
5433
degree_at_most_two.discard(v)
5434
degree_at_most_two.discard(w)
5435
5436
5437
# RULE 5:
5438
# TODO: add extra reduction rules
5439
5440
5441
##################
5442
# Main Algorithm #
5443
##################
5444
5445
if g.order() == 0:
5446
# Reduction rules were sufficients to get the solution
5447
size_cover_g = 0
5448
cover_g = []
5449
5450
elif algorithm == "Cliquer":
5451
from sage.graphs.cliquer import max_clique
5452
independent = max_clique(g.complement())
5453
if value_only:
5454
size_cover_g = g.order() - len(independent)
5455
else:
5456
cover_g = [u for u in g.vertices() if not u in independent]
5457
5458
elif algorithm == "MILP":
5459
5460
from sage.numerical.mip import MixedIntegerLinearProgram
5461
p = MixedIntegerLinearProgram(maximization=False, solver=solver)
5462
b = p.new_variable()
5463
5464
# minimizes the number of vertices in the set
5465
p.set_objective(p.sum([b[v] for v in g.vertices()]))
5466
5467
# an edge contains at least one vertex of the minimum vertex cover
5468
for (u,v) in g.edges(labels=None):
5469
p.add_constraint(b[u] + b[v], min=1)
5470
5471
p.set_binary(b)
5472
5473
if value_only:
5474
size_cover_g = p.solve(objective_only=True, log=verbosity)
5475
else:
5476
p.solve(log=verbosity)
5477
b = p.get_values(b)
5478
cover_g = [v for v in g.vertices() if b[v] == 1]
5479
else:
5480
raise ValueError("The algorithm must be either \"Cliquer\" or \"MILP\".")
5481
5482
#########################
5483
# Returning the results #
5484
#########################
5485
5486
# We finally reconstruct the solution according the reduction rules
5487
if value_only:
5488
return len(ppset) + len(folded_vertices) + size_cover_g
5489
else:
5490
# RULES 2 and 3:
5491
cover_g.extend(ppset)
5492
# RULE 4:
5493
folded_vertices.reverse()
5494
for u,v,w in folded_vertices:
5495
if u in cover_g:
5496
cover_g.remove(u)
5497
cover_g += [v,w]
5498
else:
5499
cover_g += [u]
5500
cover_g.sort()
5501
return cover_g
5502
5503
def cliques_vertex_clique_number(self, algorithm="cliquer", vertices=None,
5504
cliques=None):
5505
"""
5506
Returns a dictionary of sizes of the largest maximal cliques containing
5507
each vertex, keyed by vertex. (Returns a single value if only one
5508
input vertex).
5509
5510
.. NOTE::
5511
5512
Currently only implemented for undirected graphs. Use to_undirected
5513
to convert a digraph to an undirected graph.
5514
5515
INPUT:
5516
5517
- ``algorithm`` - either ``cliquer`` or ``networkx``
5518
5519
- ``cliquer`` - This wraps the C program Cliquer [NisOst2003]_.
5520
5521
- ``networkx`` - This function is based on NetworkX's implementation
5522
of the Bron and Kerbosch Algorithm [BroKer1973]_.
5523
5524
- ``vertices`` - the vertices to inspect (default is entire graph).
5525
Ignored unless ``algorithm=='networkx'``.
5526
5527
- ``cliques`` - list of cliques (if already computed). Ignored unless
5528
``algorithm=='networkx'``.
5529
5530
EXAMPLES::
5531
5532
sage: C = Graph('DJ{')
5533
sage: C.cliques_vertex_clique_number()
5534
{0: 2, 1: 4, 2: 4, 3: 4, 4: 4}
5535
sage: E = C.cliques_maximal()
5536
sage: E
5537
[[4, 0], [4, 1, 2, 3]]
5538
sage: C.cliques_vertex_clique_number(cliques=E,algorithm="networkx")
5539
{0: 2, 1: 4, 2: 4, 3: 4, 4: 4}
5540
sage: F = graphs.Grid2dGraph(2,3)
5541
sage: X = F.cliques_vertex_clique_number(algorithm="networkx")
5542
sage: for v in sorted(X.iterkeys()):
5543
... print v, X[v]
5544
(0, 0) 2
5545
(0, 1) 2
5546
(0, 2) 2
5547
(1, 0) 2
5548
(1, 1) 2
5549
(1, 2) 2
5550
sage: F.cliques_vertex_clique_number(vertices=[(0, 1), (1, 2)])
5551
{(0, 1): 2, (1, 2): 2}
5552
sage: G = Graph({0:[1,2,3], 1:[2], 3:[0,1]})
5553
sage: G.show(figsize=[2,2])
5554
sage: G.cliques_vertex_clique_number()
5555
{0: 3, 1: 3, 2: 3, 3: 3}
5556
5557
"""
5558
5559
if algorithm=="cliquer":
5560
from sage.graphs.cliquer import clique_number
5561
if vertices==None:
5562
vertices=self
5563
value={}
5564
for v in vertices:
5565
value[v] = 1+clique_number(self.subgraph(self.neighbors(v)))
5566
self.subgraph(self.neighbors(v)).plot()
5567
return value
5568
elif algorithm=="networkx":
5569
import networkx
5570
return networkx.node_clique_number(self.networkx_graph(copy=False),vertices, cliques)
5571
else:
5572
raise NotImplementedError("Only 'networkx' and 'cliquer' are supported.")
5573
5574
def cliques_containing_vertex(self, vertices=None, cliques=None):
5575
"""
5576
Returns the cliques containing each vertex, represented as a dictionary
5577
of lists of lists, keyed by vertex. (Returns a single list if only one
5578
input vertex).
5579
5580
.. NOTE::
5581
5582
Currently only implemented for undirected graphs. Use to_undirected
5583
to convert a digraph to an undirected graph.
5584
5585
INPUT:
5586
5587
- ``vertices`` - the vertices to inspect (default is
5588
entire graph)
5589
5590
- ``cliques`` - list of cliques (if already
5591
computed)
5592
5593
EXAMPLES::
5594
5595
sage: C = Graph('DJ{')
5596
sage: C.cliques_containing_vertex()
5597
{0: [[4, 0]], 1: [[4, 1, 2, 3]], 2: [[4, 1, 2, 3]], 3: [[4, 1, 2, 3]], 4: [[4, 0], [4, 1, 2, 3]]}
5598
sage: E = C.cliques_maximal()
5599
sage: E
5600
[[4, 0], [4, 1, 2, 3]]
5601
sage: C.cliques_containing_vertex(cliques=E)
5602
{0: [[4, 0]], 1: [[4, 1, 2, 3]], 2: [[4, 1, 2, 3]], 3: [[4, 1, 2, 3]], 4: [[4, 0], [4, 1, 2, 3]]}
5603
sage: F = graphs.Grid2dGraph(2,3)
5604
sage: X = F.cliques_containing_vertex()
5605
sage: for v in sorted(X.iterkeys()):
5606
... print v, X[v]
5607
(0, 0) [[(0, 1), (0, 0)], [(1, 0), (0, 0)]]
5608
(0, 1) [[(0, 1), (0, 0)], [(0, 1), (0, 2)], [(0, 1), (1, 1)]]
5609
(0, 2) [[(0, 1), (0, 2)], [(1, 2), (0, 2)]]
5610
(1, 0) [[(1, 0), (0, 0)], [(1, 0), (1, 1)]]
5611
(1, 1) [[(0, 1), (1, 1)], [(1, 2), (1, 1)], [(1, 0), (1, 1)]]
5612
(1, 2) [[(1, 2), (0, 2)], [(1, 2), (1, 1)]]
5613
sage: F.cliques_containing_vertex(vertices=[(0, 1), (1, 2)])
5614
{(0, 1): [[(0, 1), (0, 0)], [(0, 1), (0, 2)], [(0, 1), (1, 1)]], (1, 2): [[(1, 2), (0, 2)], [(1, 2), (1, 1)]]}
5615
sage: G = Graph({0:[1,2,3], 1:[2], 3:[0,1]})
5616
sage: G.show(figsize=[2,2])
5617
sage: G.cliques_containing_vertex()
5618
{0: [[0, 1, 2], [0, 1, 3]], 1: [[0, 1, 2], [0, 1, 3]], 2: [[0, 1, 2]], 3: [[0, 1, 3]]}
5619
5620
"""
5621
import networkx
5622
return networkx.cliques_containing_node(self.networkx_graph(copy=False),vertices, cliques)
5623
5624
def clique_complex(self):
5625
"""
5626
Returns the clique complex of self. This is the largest simplicial complex on
5627
the vertices of self whose 1-skeleton is self.
5628
5629
This is only makes sense for undirected simple graphs.
5630
5631
EXAMPLES::
5632
5633
sage: g = Graph({0:[1,2],1:[2],4:[]})
5634
sage: g.clique_complex()
5635
Simplicial complex with vertex set (0, 1, 2, 4) and facets {(4,), (0, 1, 2)}
5636
5637
sage: h = Graph({0:[1,2,3,4],1:[2,3,4],2:[3]})
5638
sage: x = h.clique_complex()
5639
sage: x
5640
Simplicial complex with vertex set (0, 1, 2, 3, 4) and facets {(0, 1, 4), (0, 1, 2, 3)}
5641
sage: i = x.graph()
5642
sage: i==h
5643
True
5644
sage: x==i.clique_complex()
5645
True
5646
5647
"""
5648
if self.is_directed() or self.has_loops() or self.has_multiple_edges():
5649
raise ValueError("Self must be an undirected simple graph to have a clique complex.")
5650
import sage.homology.simplicial_complex
5651
C = sage.homology.simplicial_complex.SimplicialComplex(self.cliques_maximal(), maximality_check=True)
5652
C._graph = self
5653
return C
5654
5655
### Miscellaneous
5656
5657
def cores(self, k = None, with_labels=False):
5658
"""
5659
Returns the core number for each vertex in an ordered list.
5660
5661
(for homomorphisms cores, see the :meth:`Graph.has_homomorphism_to`
5662
method)
5663
5664
**DEFINITIONS**
5665
5666
* *K-cores* in graph theory were introduced by Seidman in 1983 and by
5667
Bollobas in 1984 as a method of (destructively) simplifying graph
5668
topology to aid in analysis and visualization. They have been more
5669
recently defined as the following by Batagelj et al:
5670
5671
*Given a graph `G` with vertices set `V` and edges set `E`, the
5672
`k`-core of `G` is the graph obtained from `G` by recursively removing
5673
the vertices with degree less than `k`, for as long as there are any.*
5674
5675
This operation can be useful to filter or to study some properties of
5676
the graphs. For instance, when you compute the 2-core of graph G, you
5677
are cutting all the vertices which are in a tree part of graph. (A
5678
tree is a graph with no loops). [WPkcore]_
5679
5680
[PSW1996]_ defines a `k`-core of `G` as the largest subgraph (it is
5681
unique) of `G` with minimum degree at least `k`.
5682
5683
* Core number of a vertex
5684
5685
The core number of a vertex `v` is the largest integer `k` such that
5686
`v` belongs to the `k`-core of `G`.
5687
5688
* Degeneracy
5689
5690
The *degeneracy* of a graph `G`, usually denoted `\delta^*(G)`, is the
5691
smallest integer `k` such that the graph `G` can be reduced to the
5692
empty graph by iteratively removing vertices of degree `\leq
5693
k`. Equivalently, `\delta^*(G)=k` if `k` is the smallest integer such
5694
that the `k`-core of `G` is empty.
5695
5696
**IMPLEMENTATION**
5697
5698
This implementation is based on the NetworkX implementation of
5699
the algorithm described in [BZ]_.
5700
5701
**INPUT**
5702
5703
- ``k`` (integer)
5704
5705
* If ``k = None`` (default), returns the core number for each vertex.
5706
5707
* If ``k`` is an integer, returns a pair ``(ordering, core)``, where
5708
``core`` is the list of vertices in the `k`-core of ``self``, and
5709
``ordering`` is an elimination order for the other vertices such
5710
that each vertex is of degree strictly less than `k` when it is to
5711
be eliminated from the graph.
5712
5713
- ``with_labels`` (boolean)
5714
5715
* When set to ``False``, and ``k = None``, the method returns a list
5716
whose `i` th element is the core number of the `i` th vertex. When
5717
set to ``True``, the method returns a dictionary whose keys are
5718
vertices, and whose values are the corresponding core numbers.
5719
5720
By default, ``with_labels = False``.
5721
5722
.. SEEALSO::
5723
5724
* Graph cores is also a notion related to graph homomorphisms. For
5725
this second meaning, see :meth:`Graph.has_homomorphism_to`.
5726
5727
REFERENCE:
5728
5729
.. [WPkcore] K-core. Wikipedia. (2007). [Online] Available:
5730
:wikipedia:`K-core`
5731
5732
.. [PSW1996] Boris Pittel, Joel Spencer and Nicholas Wormald. Sudden
5733
Emergence of a Giant k-Core in a Random
5734
Graph. (1996). J. Combinatorial Theory. Ser B 67. pages
5735
111-151. [Online] Available:
5736
http://cs.nyu.edu/cs/faculty/spencer/papers/k-core.pdf
5737
5738
.. [BZ] Vladimir Batagelj and Matjaz Zaversnik. An `O(m)`
5739
Algorithm for Cores Decomposition of
5740
Networks. :arxiv:`cs/0310049v1`.
5741
5742
EXAMPLES::
5743
5744
sage: (graphs.FruchtGraph()).cores()
5745
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]
5746
sage: (graphs.FruchtGraph()).cores(with_labels=True)
5747
{0: 3, 1: 3, 2: 3, 3: 3, 4: 3, 5: 3, 6: 3, 7: 3, 8: 3, 9: 3, 10: 3, 11: 3}
5748
sage: a=random_matrix(ZZ,20,x=2,sparse=True, density=.1)
5749
sage: b=Graph(20)
5750
sage: b.add_edges(a.nonzero_positions())
5751
sage: cores=b.cores(with_labels=True); cores
5752
{0: 3, 1: 3, 2: 3, 3: 3, 4: 2, 5: 2, 6: 3, 7: 1, 8: 3, 9: 3, 10: 3, 11: 3, 12: 3, 13: 3, 14: 2, 15: 3, 16: 3, 17: 3, 18: 3, 19: 3}
5753
sage: [v for v,c in cores.items() if c>=2] # the vertices in the 2-core
5754
[0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
5755
5756
Checking the 2-core of a random lobster is indeed the empty set::
5757
5758
sage: g = graphs.RandomLobster(20,.5,.5)
5759
sage: ordering, core = g.cores(2)
5760
sage: len(core) == 0
5761
True
5762
"""
5763
self._scream_if_not_simple()
5764
# compute the degrees of each vertex
5765
degrees=self.degree(labels=True)
5766
5767
# sort vertices by degree. Store in a list and keep track of
5768
# where a specific degree starts (effectively, the list is
5769
# sorted by bins).
5770
verts= sorted( degrees.keys(), key=lambda x: degrees[x])
5771
bin_boundaries=[0]
5772
curr_degree=0
5773
for i,v in enumerate(verts):
5774
if degrees[v]>curr_degree:
5775
bin_boundaries.extend([i]*(degrees[v]-curr_degree))
5776
curr_degree=degrees[v]
5777
vert_pos = dict((v,pos) for pos,v in enumerate(verts))
5778
# Set up initial guesses for core and lists of neighbors.
5779
core= degrees
5780
nbrs=dict((v,set(self.neighbors(v))) for v in self)
5781
# form vertex core building up from smallest
5782
for v in verts:
5783
5784
# If all the vertices have a degree larger than k, we can
5785
# return our answer if k != None
5786
if k is not None and core[v] >= k:
5787
return verts[:vert_pos[v]], verts[vert_pos[v]:]
5788
5789
for u in nbrs[v]:
5790
if core[u] > core[v]:
5791
nbrs[u].remove(v)
5792
5793
# cleverly move u to the end of the next smallest
5794
# bin (i.e., subtract one from the degree of u).
5795
# We do this by swapping u with the first vertex
5796
# in the bin that contains u, then incrementing
5797
# the bin boundary for the bin that contains u.
5798
pos=vert_pos[u]
5799
bin_start=bin_boundaries[core[u]]
5800
vert_pos[u]=bin_start
5801
vert_pos[verts[bin_start]]=pos
5802
verts[bin_start],verts[pos]=verts[pos],verts[bin_start]
5803
bin_boundaries[core[u]]+=1
5804
core[u] -= 1
5805
5806
if k is not None:
5807
return verts, []
5808
5809
if with_labels:
5810
return core
5811
else:
5812
return core.values()
5813
5814
def modular_decomposition(self):
5815
r"""
5816
Returns the modular decomposition of the current graph.
5817
5818
Crash course on modular decomposition:
5819
5820
A module `M` of a graph `G` is a proper subset of its vertices
5821
such that for all `u \in V(G)-M, v,w\in M` the relation `u
5822
\sim v \Leftrightarrow u \sim w` holds, where `\sim` denotes
5823
the adjacency relation in `G`. Equivalently, `M \subset V(G)`
5824
is a module if all its vertices have the same adjacency
5825
relations with each vertex outside of the module (vertex by
5826
vertex).
5827
5828
Hence, for a set like a module, it is very easy to encode the
5829
information of the adjacencies between the vertices inside and
5830
outside the module -- we can actually add a new vertex `v_M`
5831
to our graph representing our module `M`, and let `v_M` be
5832
adjacent to `u\in V(G)-M` if and only if some `v\in M` (and
5833
hence all the vertices contained in the module) is adjacent to
5834
`u`. We can now independently (and recursively) study the
5835
structure of our module `M` and the new graph `G-M+\{v_M\}`,
5836
without any loss of information.
5837
5838
Here are two very simple modules :
5839
5840
* A connected component `C` (or the union of some --but
5841
not all-- of them) of a disconnected graph `G`, for
5842
instance, is a module, as no vertex of `C` has a
5843
neighbor outside of it.
5844
5845
* An anticomponent `C` (or the union of some --but not
5846
all-- of them) of an non-anticonnected graph `G`, for
5847
the same reason (it is just the complement of the
5848
previous graph !).
5849
5850
These modules being of special interest, the disjoint union of
5851
graphs is called a Parallel composition, and the complement of
5852
a disjoint union is called a Parallel composition. A graph
5853
whose only modules are singletons is called Prime.
5854
5855
For more information on modular decomposition, in particular
5856
for an explanation of the terms "Parallel," "Prime" and
5857
"Serie," see the `Wikipedia article on modular decomposition
5858
<http://en.wikipedia.org/wiki/Modular_decomposition>`_.
5859
5860
You may also be interested in the survey from Michel Habib and
5861
Christophe Paul entitled "A survey on Algorithmic aspects of
5862
modular decomposition" [HabPau10]_.
5863
5864
OUTPUT:
5865
5866
A pair of two values (recursively encoding the decomposition) :
5867
5868
* The type of the current module :
5869
5870
* ``"Parallel"``
5871
* ``"Prime"``
5872
* ``"Serie"``
5873
5874
* The list of submodules (as list of pairs ``(type, list)``,
5875
recursively...) or the vertex's name if the module is a
5876
singleton.
5877
5878
EXAMPLES:
5879
5880
The Bull Graph is prime::
5881
5882
sage: graphs.BullGraph().modular_decomposition()
5883
('Prime', [3, 4, 0, 1, 2])
5884
5885
The Petersen Graph too::
5886
5887
sage: graphs.PetersenGraph().modular_decomposition()
5888
('Prime', [2, 6, 3, 9, 7, 8, 0, 1, 5, 4])
5889
5890
This a clique on 5 vertices with 2 pendant edges, though, has a more
5891
interesting decomposition ::
5892
5893
sage: g = graphs.CompleteGraph(5)
5894
sage: g.add_edge(0,5)
5895
sage: g.add_edge(0,6)
5896
sage: g.modular_decomposition()
5897
('Serie', [0, ('Parallel', [5, ('Serie', [1, 4, 3, 2]), 6])])
5898
5899
ALGORITHM:
5900
5901
This function uses a C implementation of a 2-step algorithm
5902
implemented by Fabien de Montgolfier [FMDec]_ :
5903
5904
* Computation of a factorizing permutation [HabibViennot1999]_.
5905
5906
* Computation of the tree itself [CapHabMont02]_.
5907
5908
.. SEEALSO::
5909
5910
- :meth:`is_prime` -- Tests whether a graph is prime.
5911
5912
REFERENCE:
5913
5914
.. [FMDec] Fabien de Montgolfier
5915
http://www.liafa.jussieu.fr/~fm/algos/index.html
5916
5917
.. [HabibViennot1999] Michel Habib, Christiphe Paul, Laurent Viennot
5918
Partition refinement techniques: An interesting algorithmic tool kit
5919
International Journal of Foundations of Computer Science
5920
vol. 10 n2 pp.147--170, 1999
5921
5922
.. [CapHabMont02] C. Capelle, M. Habib et F. de Montgolfier
5923
Graph decomposition and Factorising Permutations
5924
Discrete Mathematics and Theoretical Computer Sciences, vol 5 no. 1 , 2002.
5925
5926
.. [HabPau10] Michel Habib and Christophe Paul
5927
A survey of the algorithmic aspects of modular decomposition
5928
Computer Science Review
5929
vol 4, number 1, pages 41--59, 2010
5930
http://www.lirmm.fr/~paul/md-survey.pdf
5931
"""
5932
self._scream_if_not_simple()
5933
from sage.misc.stopgap import stopgap
5934
stopgap("Graph.modular_decomposition is known to return wrong results",13744)
5935
5936
from sage.graphs.modular_decomposition.modular_decomposition import modular_decomposition
5937
5938
D = modular_decomposition(self)
5939
5940
id_label = dict(enumerate(self.vertices()))
5941
5942
relabel = lambda x : (x[0], map(relabel,x[1])) if isinstance(x,tuple) else id_label[x]
5943
5944
return relabel(D)
5945
5946
def is_prime(self):
5947
r"""
5948
Tests whether the current graph is prime. A graph is prime if
5949
all its modules are trivial (i.e. empty, all of the graph or
5950
singletons)-- see `self.modular_decomposition?`.
5951
5952
EXAMPLE:
5953
5954
The Petersen Graph and the Bull Graph are both prime ::
5955
5956
sage: graphs.PetersenGraph().is_prime()
5957
True
5958
sage: graphs.BullGraph().is_prime()
5959
True
5960
5961
Though quite obviously, the disjoint union of them is not::
5962
5963
sage: (graphs.PetersenGraph() + graphs.BullGraph()).is_prime()
5964
False
5965
"""
5966
5967
D = self.modular_decomposition()
5968
5969
return D[0] == "Prime" and len(D[1]) == self.order()
5970
5971
def _gomory_hu_tree(self, vertices=None, method="FF"):
5972
r"""
5973
Returns a Gomory-Hu tree associated to self.
5974
5975
This function is the private counterpart of ``gomory_hu_tree()``,
5976
with the difference that it has an optional argument
5977
needed for recursive computations, which the user is not
5978
interested in defining himself.
5979
5980
See the documentation of ``gomory_hu_tree()`` for more information.
5981
5982
INPUT:
5983
5984
- ``vertices`` - a set of "real" vertices, as opposed to the
5985
fakes one introduced during the computations. This variable is
5986
useful for the algorithm and for recursion purposes.
5987
5988
- ``method`` -- There are currently two different
5989
implementations of this method :
5990
5991
* If ``method = "FF"`` (default), a Python
5992
implementation of the Ford-Fulkerson algorithm is
5993
used.
5994
5995
* If ``method = "LP"``, the flow problem is solved using
5996
Linear Programming.
5997
5998
EXAMPLE:
5999
6000
This function is actually tested in ``gomory_hu_tree()``, this
6001
example is only present to have a doctest coverage of 100%.
6002
6003
sage: g = graphs.PetersenGraph()
6004
sage: t = g._gomory_hu_tree()
6005
"""
6006
self._scream_if_not_simple()
6007
from sage.sets.set import Set
6008
6009
# The default capacity of an arc is 1
6010
from sage.rings.real_mpfr import RR
6011
capacity = lambda label: label if label in RR else 1
6012
6013
# Keeping the graph's embedding
6014
pos = False
6015
6016
# Small case, not really a problem ;-)
6017
if self.order() == 1:
6018
return self.copy()
6019
6020
# This is a sign that this is the first call
6021
# to this recursive function
6022
if vertices is None:
6023
# Now is the time to care about positions
6024
pos = self.get_pos()
6025
6026
# if the graph is not connected, returns the union
6027
# of the Gomory-Hu tree of each component
6028
if not self.is_connected():
6029
g = Graph()
6030
for cc in self.connected_components_subgraphs():
6031
g = g.union(cc._gomory_hu_tree(method=method))
6032
g.set_pos(self.get_pos())
6033
return g
6034
# All the vertices is this graph are the "real ones"
6035
vertices = Set(self.vertices())
6036
6037
# There may be many vertices, though only one which is "real"
6038
if len(vertices) == 1:
6039
g = Graph()
6040
g.add_vertex(vertices[0])
6041
return g
6042
6043
# Take any two vertices
6044
u,v = vertices[0:2]
6045
6046
# Recovers the following values
6047
# flow is the connectivity between u and v
6048
# edges of a min cut
6049
# sets1, sets2 are the two sides of the edge cut
6050
flow,edges,[set1,set2] = self.edge_cut(u, v, use_edge_labels=True, vertices=True, method=method)
6051
6052
# One graph for each part of the previous one
6053
g1,g2 = self.subgraph(set1), self.subgraph(set2)
6054
6055
# Adding the fake vertex to each part
6056
g1_v = Set(set2)
6057
g2_v = Set(set1)
6058
g1.add_vertex(g1_v)
6059
g1.add_vertex(g2_v)
6060
6061
# Each part of the graph had many edges going to the other part
6062
# Now that we have a new fake vertex in each part
6063
# we just say that the edges which were in the cut and going
6064
# to the other side are now going to this fake vertex
6065
6066
# We must preserve the labels. They sum.
6067
6068
for e in edges:
6069
x,y = e[0],e[1]
6070
# Assumes x is in g1
6071
if x in g2:
6072
x,y = y,x
6073
# If the edge x-g1_v exists, adds to its label the capacity of arc xy
6074
if g1.has_edge(x, g1_v):
6075
g1.set_edge_label(x, g1_v, g1.edge_label(x, g1_v) + capacity(self.edge_label(x, y)))
6076
else:
6077
# Otherwise, creates it with the good label
6078
g1.add_edge(x, g1_v, capacity(self.edge_label(x, y)))
6079
# Same thing for g2
6080
if g2.has_edge(y, g2_v):
6081
g2.set_edge_label(y, g2_v, g2.edge_label(y, g2_v) + capacity(self.edge_label(x, y)))
6082
else:
6083
g2.add_edge(y, g2_v, capacity(self.edge_label(x, y)))
6084
6085
# Recursion for the two new graphs... The new "real" vertices are the intersection with
6086
# with the previous set of "real" vertices
6087
g1_tree = g1._gomory_hu_tree(vertices=(vertices & Set(g1.vertices())), method=method)
6088
g2_tree = g2._gomory_hu_tree(vertices=(vertices & Set(g2.vertices())), method=method)
6089
6090
# Union of the two partial trees ( it is disjoint, but
6091
# disjoint_union does not preserve the name of the vertices )
6092
g = g1_tree.union(g2_tree)
6093
6094
# An edge to connect them, with the appropriate label
6095
g.add_edge(g1_tree.vertex_iterator().next(), g2_tree.vertex_iterator().next(), flow)
6096
6097
if pos:
6098
g.set_pos(pos)
6099
6100
return g
6101
6102
def gomory_hu_tree(self, method="FF"):
6103
r"""
6104
Returns a Gomory-Hu tree of self.
6105
6106
Given a tree `T` with labeled edges representing capacities, it is very
6107
easy to determine the maximal flow between any pair of vertices :
6108
it is the minimal label on the edges of the unique path between them.
6109
6110
Given a graph `G`, a Gomory-Hu tree `T` of `G` is a tree
6111
with the same set of vertices, and such that the maximal flow
6112
between any two vertices is the same in `G` as in `T`. See the
6113
`Wikipedia article on Gomory-Hu tree <http://en.wikipedia.org/wiki/Gomory%E2%80%93Hu_tree>`_.
6114
Note that, in general, a graph admits more than one Gomory-Hu tree.
6115
6116
INPUT:
6117
6118
- ``method`` -- There are currently two different
6119
implementations of this method :
6120
6121
* If ``method = "FF"`` (default), a Python
6122
implementation of the Ford-Fulkerson algorithm is
6123
used.
6124
6125
* If ``method = "LP"``, the flow problems are solved
6126
using Linear Programming.
6127
6128
OUTPUT:
6129
6130
A graph with labeled edges
6131
6132
EXAMPLE:
6133
6134
Taking the Petersen graph::
6135
6136
sage: g = graphs.PetersenGraph()
6137
sage: t = g.gomory_hu_tree()
6138
6139
Obviously, this graph is a tree::
6140
6141
sage: t.is_tree()
6142
True
6143
6144
Note that if the original graph is not connected, then the
6145
Gomory-Hu tree is in fact a forest::
6146
6147
sage: (2*g).gomory_hu_tree().is_forest()
6148
True
6149
sage: (2*g).gomory_hu_tree().is_connected()
6150
False
6151
6152
On the other hand, such a tree has lost nothing of the initial
6153
graph connectedness::
6154
6155
sage: all([ t.flow(u,v) == g.flow(u,v) for u,v in Subsets( g.vertices(), 2 ) ])
6156
True
6157
6158
Just to make sure, we can check that the same is true for two vertices
6159
in a random graph::
6160
6161
sage: g = graphs.RandomGNP(20,.3)
6162
sage: t = g.gomory_hu_tree()
6163
sage: g.flow(0,1) == t.flow(0,1)
6164
True
6165
6166
And also the min cut::
6167
6168
sage: g.edge_connectivity() == min(t.edge_labels())
6169
True
6170
"""
6171
return self._gomory_hu_tree(method=method)
6172
6173
def two_factor_petersen(self):
6174
r"""
6175
Returns a decomposition of the graph into 2-factors.
6176
6177
Petersen's 2-factor decomposition theorem asserts that any
6178
`2r`-regular graph `G` can be decomposed into 2-factors.
6179
Equivalently, it means that the edges of any `2r`-regular
6180
graphs can be partitionned in `r` sets `C_1,\dots,C_r` such
6181
that for all `i`, the set `C_i` is a disjoint union of cycles
6182
( a 2-regular graph ).
6183
6184
As any graph of maximal degree `\Delta` can be completed into
6185
a regular graph of degree `2\lceil\frac\Delta 2\rceil`, this
6186
result also means that the edges of any graph of degree `\Delta`
6187
can be partitionned in `r=2\lceil\frac\Delta 2\rceil` sets
6188
`C_1,\dots,C_r` such that for all `i`, the set `C_i` is a
6189
graph of maximal degree `2` ( a disjoint union of paths
6190
and cycles ).
6191
6192
EXAMPLE:
6193
6194
The Complete Graph on `7` vertices is a `6`-regular graph, so it can
6195
be edge-partitionned into `2`-regular graphs::
6196
6197
sage: g = graphs.CompleteGraph(7)
6198
sage: classes = g.two_factor_petersen()
6199
sage: for c in classes:
6200
... gg = Graph()
6201
... gg.add_edges(c)
6202
... print max(gg.degree())<=2
6203
True
6204
True
6205
True
6206
sage: Set(set(classes[0]) | set(classes[1]) | set(classes[2])).cardinality() == g.size()
6207
True
6208
6209
::
6210
6211
sage: g = graphs.CirculantGraph(24, [7, 11])
6212
sage: cl = g.two_factor_petersen()
6213
sage: g.plot(edge_colors={'black':cl[0], 'red':cl[1]})
6214
6215
"""
6216
self._scream_if_not_simple()
6217
d = self.eulerian_orientation()
6218
6219
# This new graph is bipartite, and built the following way :
6220
#
6221
# To each vertex v of the digraph are associated two vertices,
6222
# a sink (-1,v) and a source (1,v)
6223
# Any edge (u,v) in the digraph is then added as ((-1,u),(1,v))
6224
6225
from sage.graphs.graph import Graph
6226
g = Graph()
6227
g.add_edges([((-1,u),(1,v)) for (u,v) in d.edge_iterator(labels=None)])
6228
6229
# This new bipartite graph is now edge_colored
6230
from sage.graphs.graph_coloring import edge_coloring
6231
classes = edge_coloring(g)
6232
6233
# The edges in the classes are of the form ((-1,u),(1,v))
6234
# and have to be translated back to (u,v)
6235
classes_b = []
6236
for c in classes:
6237
classes_b.append([(u,v) for ((uu,u),(vv,v)) in c])
6238
6239
return classes_b
6240
6241
def kirchhoff_symanzik_polynomial(self, name='t'):
6242
"""
6243
Return the Kirchhoff-Symanzik polynomial of a graph.
6244
6245
This is a polynomial in variables `t_e` (each of them representing an
6246
edge of the graph `G`) defined as a sum over all spanning trees:
6247
6248
.. MATH::
6249
6250
\Psi_G(t) = \sum_{T\subseteq V\\atop{\\text{a spanning tree}}} \prod_{e \\not\in E(T)} t_e
6251
6252
This is also called the first Symanzik polynomial or the Kirchhoff
6253
polynomial.
6254
6255
INPUT:
6256
6257
- ``name``: name of the variables (default: ``'t'``)
6258
6259
OUTPUT:
6260
6261
- a polynomial with integer coefficients
6262
6263
ALGORITHM:
6264
6265
This is computed here using a determinant, as explained in Section
6266
3.1 of [Marcolli2009]_.
6267
6268
As an intermediate step, one computes a cycle basis `\mathcal C` of
6269
`G` and a rectangular `|\mathcal C| \\times |E(G)|` matrix with
6270
entries in `\{-1,0,1\}`, which describes which edge belong to which
6271
cycle of `\mathcal C` and their respective orientations.
6272
6273
More precisely, after fixing an arbitrary orientation for each edge
6274
`e\in E(G)` and each cycle `C\in\mathcal C`, one gets a sign for
6275
every incident pair (edge, cycle) which is `1` if the orientation
6276
coincide and `-1` otherwise.
6277
6278
EXAMPLES:
6279
6280
For the cycle of length 5::
6281
6282
sage: G = graphs.CycleGraph(5)
6283
sage: G.kirchhoff_symanzik_polynomial()
6284
t0 + t1 + t2 + t3 + t4
6285
6286
One can use another letter for variables::
6287
6288
sage: G.kirchhoff_symanzik_polynomial(name='u')
6289
u0 + u1 + u2 + u3 + u4
6290
6291
For the 'coffee bean' graph::
6292
6293
sage: G = Graph([(0,1,'a'),(0,1,'b'),(0,1,'c')])
6294
sage: G.kirchhoff_symanzik_polynomial()
6295
t0*t1 + t0*t2 + t1*t2
6296
6297
For the 'parachute' graph::
6298
6299
sage: G = Graph([(0,2,'a'),(0,2,'b'),(0,1,'c'),(1,2,'d')])
6300
sage: G.kirchhoff_symanzik_polynomial()
6301
t0*t1 + t0*t2 + t1*t2 + t1*t3 + t2*t3
6302
6303
For the complete graph with 4 vertices::
6304
6305
sage: G = graphs.CompleteGraph(4)
6306
sage: G.kirchhoff_symanzik_polynomial()
6307
t0*t1*t3 + t0*t2*t3 + t1*t2*t3 + t0*t1*t4 + t0*t2*t4 + t1*t2*t4
6308
+ t1*t3*t4 + t2*t3*t4 + t0*t1*t5 + t0*t2*t5 + t1*t2*t5 + t0*t3*t5
6309
+ t2*t3*t5 + t0*t4*t5 + t1*t4*t5 + t3*t4*t5
6310
6311
REFERENCES:
6312
6313
.. [Marcolli2009] Matilde Marcolli, Feynman Motives, Chapter 3,
6314
Feynman integrals and algebraic varieties,
6315
http://www.its.caltech.edu/~matilde/LectureN3.pdf
6316
6317
.. [Brown2011] Francis Brown, Multiple zeta values and periods: From
6318
moduli spaces to Feynman integrals, in Contemporary Mathematics vol
6319
539
6320
"""
6321
from sage.matrix.constructor import matrix
6322
from sage.rings.integer_ring import ZZ
6323
from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
6324
6325
edges = self.edges()
6326
cycles = self.cycle_basis(output='edge')
6327
6328
edge2int = {e: j for j, e in enumerate(edges)}
6329
circuit_mtrx = matrix(ZZ, self.size(), len(cycles))
6330
for i, cycle in enumerate(cycles):
6331
for edge in cycle:
6332
if edge in edges:
6333
circuit_mtrx[edge2int[edge], i] = +1
6334
else:
6335
circuit_mtrx[edge2int[(edge[1], edge[0], edge[2])], i] = -1
6336
6337
D = matrix.diagonal(PolynomialRing(ZZ, name, self.size()).gens())
6338
return (circuit_mtrx.transpose() * D * circuit_mtrx).determinant()
6339
6340
def ihara_zeta_function_inverse(self):
6341
"""
6342
Compute the inverse of the Ihara zeta function of the graph
6343
6344
This is a polynomial in one variable with integer coefficients. The
6345
Ihara zeta function itself is the inverse of this polynomial.
6346
6347
See :wikipedia:`Ihara zeta function`
6348
6349
ALGORITHM:
6350
6351
This is computed here using the determinant of a square matrix
6352
of size twice the number of edges, related to the adjacency
6353
matrix of the line graph, see for example Proposition 9
6354
in [ScottStorm]_.
6355
6356
EXAMPLES::
6357
6358
sage: G = graphs.CompleteGraph(4)
6359
sage: factor(G.ihara_zeta_function_inverse())
6360
(2*t - 1) * (t + 1)^2 * (t - 1)^3 * (2*t^2 + t + 1)^3
6361
6362
sage: G = graphs.CompleteGraph(5)
6363
sage: factor(G.ihara_zeta_function_inverse())
6364
(-1) * (3*t - 1) * (t + 1)^5 * (t - 1)^6 * (3*t^2 + t + 1)^4
6365
6366
sage: G = graphs.PetersenGraph()
6367
sage: factor(G.ihara_zeta_function_inverse())
6368
(-1) * (2*t - 1) * (t + 1)^5 * (t - 1)^6 * (2*t^2 + 2*t + 1)^4
6369
* (2*t^2 - t + 1)^5
6370
6371
sage: G = graphs.RandomTree(10)
6372
sage: G.ihara_zeta_function_inverse()
6373
1
6374
6375
REFERENCES:
6376
6377
.. [HST] Matthew D. Horton, H. M. Stark, and Audrey A. Terras,
6378
What are zeta functions of graphs and what are they good for?
6379
in Quantum graphs and their applications, 173-189,
6380
Contemp. Math., Vol. 415
6381
6382
.. [Terras] Audrey Terras, Zeta functions of graphs: a stroll through
6383
the garden, Cambridge Studies in Advanced Mathematics, Vol. 128
6384
6385
.. [ScottStorm] Geoffrey Scott and Christopher Storm, The coefficients
6386
of the Ihara zeta function, Involve (http://msp.org/involve/2008/1-2/involve-v1-n2-p08-p.pdf)
6387
"""
6388
from sage.matrix.constructor import matrix
6389
from sage.rings.integer_ring import ZZ
6390
from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
6391
6392
ring = PolynomialRing(ZZ, 't')
6393
t = ring.gen()
6394
6395
N = self.size()
6396
6397
labeled_g = DiGraph()
6398
labeled_g.add_edges([(u, v, i) for i, (u, v) in
6399
enumerate(self.edges(labels=False))])
6400
labeled_g.add_edges([(v, u, i + N) for i, (u, v) in
6401
enumerate(self.edges(labels=False))])
6402
6403
M = matrix(ring, 2 * N, 2 * N, ring.one())
6404
for u, v, i in labeled_g.edges():
6405
for vv, ww, j in labeled_g.outgoing_edges(v):
6406
M[i, j] += -t
6407
M[i, (i + N) % (2 * N)] += t # fixing the 2-cycles
6408
6409
return M.determinant()
6410
6411
# Aliases to functions defined in Cython modules
6412
import types
6413
6414
import sage.graphs.weakly_chordal
6415
Graph.is_long_hole_free = types.MethodType(sage.graphs.weakly_chordal.is_long_hole_free, None, Graph)
6416
Graph.is_long_antihole_free = types.MethodType(sage.graphs.weakly_chordal.is_long_antihole_free, None, Graph)
6417
Graph.is_weakly_chordal = types.MethodType(sage.graphs.weakly_chordal.is_weakly_chordal, None, Graph)
6418
6419
import sage.graphs.chrompoly
6420
Graph.chromatic_polynomial = types.MethodType(sage.graphs.chrompoly.chromatic_polynomial, None, Graph)
6421
6422
import sage.graphs.graph_decompositions.rankwidth
6423
Graph.rank_decomposition = types.MethodType(sage.graphs.graph_decompositions.rankwidth.rank_decomposition, None, Graph)
6424
6425
import sage.graphs.matchpoly
6426
Graph.matching_polynomial = types.MethodType(sage.graphs.matchpoly.matching_polynomial, None, Graph)
6427
6428
import sage.graphs.cliquer
6429
Graph.cliques_maximum = types.MethodType(sage.graphs.cliquer.all_max_clique, None, Graph)
6430
6431
import sage.graphs.graph_decompositions.graph_products
6432
Graph.is_cartesian_product = types.MethodType(sage.graphs.graph_decompositions.graph_products.is_cartesian_product, None, Graph)
6433
6434
import sage.graphs.distances_all_pairs
6435
Graph.is_distance_regular = types.MethodType(sage.graphs.distances_all_pairs.is_distance_regular, None, Graph)
6436
6437
import sage.graphs.base.static_dense_graph
6438
Graph.is_strongly_regular = types.MethodType(sage.graphs.base.static_dense_graph.is_strongly_regular, None, Graph)
6439
6440
# From Python modules
6441
import sage.graphs.line_graph
6442
Graph.is_line_graph = sage.graphs.line_graph.is_line_graph
6443
6444
from sage.graphs.tutte_polynomial import tutte_polynomial
6445
Graph.tutte_polynomial = tutte_polynomial
6446
6447
6448
def compare_edges(x, y):
6449
"""
6450
This function has been deprecated.
6451
6452
Compare edge x to edge y, return -1 if x y, 1 if x y, else 0.
6453
6454
TEST::
6455
6456
sage: G = graphs.PetersenGraph()
6457
sage: E = G.edges()
6458
sage: from sage.graphs.graph import compare_edges
6459
sage: compare_edges(E[0], E[2])
6460
doctest:...: DeprecationWarning: compare_edges(x,y) is deprecated. Use statement 'cmp(x[1],y[1]) or cmp(x[0],y[0])' instead.
6461
See http://trac.sagemath.org/13192 for details.
6462
-1
6463
"""
6464
from sage.misc.superseded import deprecation
6465
deprecation(13192, "compare_edges(x,y) is deprecated. Use statement 'cmp(x[1],y[1]) or cmp(x[0],y[0])' instead.")
6466
if x[1] < y[1]:
6467
return -1
6468
elif x[1] > y[1]:
6469
return 1
6470
elif x[1] == y[1]:
6471
if x[0] < y[0]:
6472
return -1
6473
if x[0] > y[0]:
6474
return 1
6475
else:
6476
return 0
6477
6478
6479