Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/graphs/graph.py
4045 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
23
24
**Distances:**
25
26
.. csv-table::
27
:class: contentstable
28
:widths: 30, 70
29
:delim: |
30
31
:meth:`~Graph.centrality_closeness` | Returns the closeness centrality (1/average distance to all vertices)
32
:meth:`~Graph.centrality_degree` | Returns the degree centrality
33
:meth:`~Graph.centrality_betweenness` | Returns the betweenness centrality
34
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
52
53
54
**Connectivity and orientations:**
55
56
.. csv-table::
57
:class: contentstable
58
:widths: 30, 70
59
:delim: |
60
61
:meth:`~Graph.gomory_hu_tree` | Returns a Gomory-Hu tree of self.
62
:meth:`~Graph.bounded_outdegree_orientation` | Computes an orientation of ``self`` such that every vertex `v` has out-degree less than `b(v)`
63
:meth:`~Graph.strong_orientation` | Returns a strongly connected orientation of the current graph.
64
:meth:`~Graph.degree_constrained_subgraph` | Returns a degree-constrained subgraph.
65
66
67
68
**Clique-related methods:**
69
70
.. csv-table::
71
:class: contentstable
72
:widths: 30, 70
73
:delim: |
74
75
:meth:`~Graph.clique_complex` | Returns the clique complex of self
76
:meth:`~Graph.cliques_containing_vertex` | Returns the cliques containing each vertex
77
:meth:`~Graph.cliques_vertex_clique_number` | Returns a dictionary of sizes of the largest maximal cliques containing each vertex
78
: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
79
:meth:`~Graph.cliques_get_max_clique_graph` | Returns a graph constructed with maximal cliques as vertices, and edges between maximal cliques sharing vertices.
80
:meth:`~Graph.cliques_number_of` | Returns a dictionary of the number of maximal cliques containing each vertex, keyed by vertex.
81
:meth:`~Graph.clique_number` | Returns the order of the largest clique of the graph.
82
:meth:`~Graph.clique_maximum` | Returns the vertex set of a maximal order complete subgraph.
83
:meth:`~Graph.cliques_maximum` | Returns the list of all maximum cliques
84
:meth:`~Graph.cliques_maximal` | Returns the list of all maximal cliques
85
86
87
**Algorithmically hard stuff:**
88
89
.. csv-table::
90
:class: contentstable
91
:widths: 30, 70
92
:delim: |
93
94
:meth:`~Graph.vertex_cover` | Returns a minimum vertex cover of self
95
:meth:`~Graph.independent_set` | Returns a maximum independent set.
96
:meth:`~Graph.topological_minor` | Returns a topological `H`-minor from ``self`` if one exists.
97
:meth:`~Graph.convexity_properties` | Returns a ``ConvexityProperties`` objet corresponding to ``self``.
98
:meth:`~Graph.matching_polynomial` | Computes the matching polynomial of the graph `G`.
99
:meth:`~Graph.rank_decomposition` | Returns an rank-decomposition of ``self`` achieving optiml rank-width.
100
:meth:`~Graph.minor` | Returns the vertices of a minor isomorphic to `H` in the current graph.
101
:meth:`~Graph.independent_set_of_representatives` | Returns an independent set of representatives.
102
:meth:`~Graph.coloring` | Returns the first (optimal) proper vertex-coloring found.
103
:meth:`~Graph.chromatic_number` | Returns the minimal number of colors needed to color the vertices of the graph.
104
:meth:`~Graph.chromatic_polynomial` | Returns the chromatic polynomial of the graph.
105
:meth:`~Graph.is_perfect` | Tests whether the graph is perfect.
106
107
108
109
**Leftovers:**
110
111
.. csv-table::
112
:class: contentstable
113
:widths: 30, 70
114
:delim: |
115
116
:meth:`~Graph.fractional_chromatic_index` | Computes the fractional chromatic index of ``self``
117
:meth:`~Graph.modular_decomposition` | Returns the modular decomposition of the current graph.
118
:meth:`~Graph.two_factor_petersen` | Returns a decomposition of the graph into 2-factors.
119
120
121
AUTHORS:
122
123
- Robert L. Miller (2006-10-22): initial version
124
125
- William Stein (2006-12-05): Editing
126
127
- Robert L. Miller (2007-01-13): refactoring, adjusting for
128
NetworkX-0.33, fixed plotting bugs (2007-01-23): basic tutorial,
129
edge labels, loops, multiple edges and arcs (2007-02-07): graph6
130
and sparse6 formats, matrix input
131
132
- Emily Kirkmann (2007-02-11): added graph_border option to plot
133
and show
134
135
- Robert L. Miller (2007-02-12): vertex color-maps, graph
136
boundaries, graph6 helper functions in Cython
137
138
- Robert L. Miller Sage Days 3 (2007-02-17-21): 3d plotting in
139
Tachyon
140
141
- Robert L. Miller (2007-02-25): display a partition
142
143
- Robert L. Miller (2007-02-28): associate arbitrary objects to
144
vertices, edge and arc label display (in 2d), edge coloring
145
146
- Robert L. Miller (2007-03-21): Automorphism group, isomorphism
147
check, canonical label
148
149
- Robert L. Miller (2007-06-07-09): NetworkX function wrapping
150
151
- Michael W. Hansen (2007-06-09): Topological sort generation
152
153
- Emily Kirkman, Robert L. Miller Sage Days 4: Finished wrapping
154
NetworkX
155
156
- Emily Kirkman (2007-07-21): Genus (including circular planar,
157
all embeddings and all planar embeddings), all paths, interior
158
paths
159
160
- Bobby Moretti (2007-08-12): fixed up plotting of graphs with
161
edge colors differentiated by label
162
163
- Jason Grout (2007-09-25): Added functions, bug fixes, and
164
general enhancements
165
166
- Robert L. Miller (Sage Days 7): Edge labeled graph isomorphism
167
168
- Tom Boothby (Sage Days 7): Miscellaneous awesomeness
169
170
- Tom Boothby (2008-01-09): Added graphviz output
171
172
- David Joyner (2009-2): Fixed docstring bug related to GAP.
173
174
- Stephen Hartke (2009-07-26): Fixed bug in blocks_and_cut_vertices()
175
that caused an incorrect result when the vertex 0 was a cut vertex.
176
177
- Stephen Hartke (2009-08-22): Fixed bug in blocks_and_cut_vertices()
178
where the list of cut_vertices is not treated as a set.
179
180
- Anders Jonsson (2009-10-10): Counting of spanning trees and out-trees added.
181
182
- Nathann Cohen (2009-09) : Cliquer, Connectivity, Flows
183
and everything that uses Linear Programming
184
and class numerical.MIP
185
186
- Nicolas M. Thiery (2010-02): graph layout code refactoring, dot2tex/graphviz interface
187
188
- David Coudert (2012-04) : Reduction rules in vertex_cover.
189
190
191
Graph Format
192
------------
193
194
Supported formats
195
~~~~~~~~~~~~~~~~~
196
197
Sage Graphs can be created from a wide range of inputs. A few
198
examples are covered here.
199
200
201
- NetworkX dictionary format:
202
203
::
204
205
sage: d = {0: [1,4,5], 1: [2,6], 2: [3,7], 3: [4,8], 4: [9], \
206
5: [7, 8], 6: [8,9], 7: [9]}
207
sage: G = Graph(d); G
208
Graph on 10 vertices
209
sage: G.plot().show() # or G.show()
210
211
- A NetworkX graph:
212
213
::
214
215
sage: import networkx
216
sage: K = networkx.complete_bipartite_graph(12,7)
217
sage: G = Graph(K)
218
sage: G.degree()
219
[7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 12, 12, 12, 12, 12, 12, 12]
220
221
- graph6 or sparse6 format:
222
223
::
224
225
sage: s = ':I`AKGsaOs`cI]Gb~'
226
sage: G = Graph(s, sparse=True); G
227
Looped multi-graph on 10 vertices
228
sage: G.plot().show() # or G.show()
229
230
Note that the ``\`` character is an escape character in Python, and
231
also a character used by graph6 strings:
232
233
::
234
235
sage: G = Graph('Ihe\n@GUA')
236
Traceback (most recent call last):
237
...
238
RuntimeError: The string (Ihe) seems corrupt: for n = 10, the string is too short.
239
240
In Python, the escaped character ``\`` is represented by ``\\``:
241
242
::
243
244
sage: G = Graph('Ihe\\n@GUA')
245
sage: G.plot().show() # or G.show()
246
247
- adjacency matrix: In an adjacency matrix, each column and each
248
row represent a vertex. If a 1 shows up in row `i`, column
249
`j`, there is an edge `(i,j)`.
250
251
::
252
253
sage: M = Matrix([(0,1,0,0,1,1,0,0,0,0),(1,0,1,0,0,0,1,0,0,0), \
254
(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), \
255
(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), \
256
(0,0,0,1,0,1,1,0,0,0), (0,0,0,0,1,0,1,1,0,0)])
257
sage: M
258
[0 1 0 0 1 1 0 0 0 0]
259
[1 0 1 0 0 0 1 0 0 0]
260
[0 1 0 1 0 0 0 1 0 0]
261
[0 0 1 0 1 0 0 0 1 0]
262
[1 0 0 1 0 0 0 0 0 1]
263
[1 0 0 0 0 0 0 1 1 0]
264
[0 1 0 0 0 0 0 0 1 1]
265
[0 0 1 0 0 1 0 0 0 1]
266
[0 0 0 1 0 1 1 0 0 0]
267
[0 0 0 0 1 0 1 1 0 0]
268
sage: G = Graph(M); G
269
Graph on 10 vertices
270
sage: G.plot().show() # or G.show()
271
272
- incidence matrix: In an incidence matrix, each row represents a
273
vertex and each column represents an edge.
274
275
::
276
277
sage: M = Matrix([(-1,0,0,0,1,0,0,0,0,0,-1,0,0,0,0), \
278
(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), \
279
(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), \
280
(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), \
281
(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), \
282
(0,0,0,0,0,0,1,-1,0,0,0,0,0,0,1)])
283
sage: M
284
[-1 0 0 0 1 0 0 0 0 0 -1 0 0 0 0]
285
[ 1 -1 0 0 0 0 0 0 0 0 0 -1 0 0 0]
286
[ 0 1 -1 0 0 0 0 0 0 0 0 0 -1 0 0]
287
[ 0 0 1 -1 0 0 0 0 0 0 0 0 0 -1 0]
288
[ 0 0 0 1 -1 0 0 0 0 0 0 0 0 0 -1]
289
[ 0 0 0 0 0 -1 0 0 0 1 1 0 0 0 0]
290
[ 0 0 0 0 0 0 0 1 -1 0 0 1 0 0 0]
291
[ 0 0 0 0 0 1 -1 0 0 0 0 0 1 0 0]
292
[ 0 0 0 0 0 0 0 0 1 -1 0 0 0 1 0]
293
[ 0 0 0 0 0 0 1 -1 0 0 0 0 0 0 1]
294
sage: G = Graph(M); G
295
Graph on 10 vertices
296
sage: G.plot().show() # or G.show()
297
sage: DiGraph(matrix(2,[0,0,-1,1]), format="incidence_matrix")
298
Traceback (most recent call last):
299
...
300
ValueError: There must be two nonzero entries (-1 & 1) per column.
301
302
- a list of edges::
303
304
sage: g = Graph([(1,3),(3,8),(5,2)])
305
sage: g
306
Graph on 5 vertices
307
308
Generators
309
----------
310
311
If you wish to iterate through all the isomorphism types of graphs,
312
type, for example::
313
314
sage: for g in graphs(4):
315
... print g.spectrum()
316
[0, 0, 0, 0]
317
[1, 0, 0, -1]
318
[1.4142135623..., 0, 0, -1.4142135623...]
319
[2, 0, -1, -1]
320
[1.7320508075..., 0, 0, -1.7320508075...]
321
[1, 1, -1, -1]
322
[1.6180339887..., 0.6180339887..., -0.6180339887..., -1.6180339887...]
323
[2.1700864866..., 0.3111078174..., -1, -1.4811943040...]
324
[2, 0, 0, -2]
325
[2.5615528128..., 0, -1, -1.5615528128...]
326
[3, -1, -1, -1]
327
328
For some commonly used graphs to play with, type
329
330
::
331
332
sage: graphs.[tab] # not tested
333
334
and hit {tab}. Most of these graphs come with their own custom
335
plot, so you can see how people usually visualize these graphs.
336
337
::
338
339
sage: G = graphs.PetersenGraph()
340
sage: G.plot().show() # or G.show()
341
sage: G.degree_histogram()
342
[0, 0, 0, 10]
343
sage: G.adjacency_matrix()
344
[0 1 0 0 1 1 0 0 0 0]
345
[1 0 1 0 0 0 1 0 0 0]
346
[0 1 0 1 0 0 0 1 0 0]
347
[0 0 1 0 1 0 0 0 1 0]
348
[1 0 0 1 0 0 0 0 0 1]
349
[1 0 0 0 0 0 0 1 1 0]
350
[0 1 0 0 0 0 0 0 1 1]
351
[0 0 1 0 0 1 0 0 0 1]
352
[0 0 0 1 0 1 1 0 0 0]
353
[0 0 0 0 1 0 1 1 0 0]
354
355
::
356
357
sage: S = G.subgraph([0,1,2,3])
358
sage: S.plot().show() # or S.show()
359
sage: S.density()
360
1/2
361
362
::
363
364
sage: G = GraphQuery(display_cols=['graph6'], num_vertices=7, diameter=5)
365
sage: L = G.get_graphs_list()
366
sage: graphs_list.show_graphs(L)
367
368
.. _Graph:labels:
369
370
Labels
371
------
372
373
Each vertex can have any hashable object as a label. These are
374
things like strings, numbers, and tuples. Each edge is given a
375
default label of ``None``, but if specified, edges can
376
have any label at all. Edges between vertices `u` and
377
`v` are represented typically as ``(u, v, l)``, where
378
``l`` is the label for the edge.
379
380
Note that vertex labels themselves cannot be mutable items::
381
382
sage: M = Matrix( [[0,0],[0,0]] )
383
sage: G = Graph({ 0 : { M : None } })
384
Traceback (most recent call last):
385
...
386
TypeError: mutable matrices are unhashable
387
388
However, if one wants to define a dictionary, with the same keys
389
and arbitrary objects for entries, one can make that association::
390
391
sage: d = {0 : graphs.DodecahedralGraph(), 1 : graphs.FlowerSnark(), \
392
2 : graphs.MoebiusKantorGraph(), 3 : graphs.PetersenGraph() }
393
sage: d[2]
394
Moebius-Kantor Graph: Graph on 16 vertices
395
sage: T = graphs.TetrahedralGraph()
396
sage: T.vertices()
397
[0, 1, 2, 3]
398
sage: T.set_vertices(d)
399
sage: T.get_vertex(1)
400
Flower Snark: Graph on 20 vertices
401
402
Database
403
--------
404
405
There is a database available for searching for graphs that satisfy
406
a certain set of parameters, including number of vertices and
407
edges, density, maximum and minimum degree, diameter, radius, and
408
connectivity. To see a list of all search parameter keywords broken
409
down by their designated table names, type
410
411
::
412
413
sage: graph_db_info()
414
{...}
415
416
For more details on data types or keyword input, enter
417
418
::
419
420
sage: GraphQuery? # not tested
421
422
The results of a query can be viewed with the show method, or can be
423
viewed individually by iterating through the results:
424
425
::
426
427
sage: Q = GraphQuery(display_cols=['graph6'],num_vertices=7, diameter=5)
428
sage: Q.show()
429
Graph6
430
--------------------
431
F@?]O
432
F@OKg
433
F?`po
434
F?gqg
435
FIAHo
436
F@R@o
437
FA_pW
438
FGC{o
439
FEOhW
440
441
Show each graph as you iterate through the results:
442
443
::
444
445
sage: for g in Q:
446
... show(g)
447
448
449
Visualization
450
-------------
451
452
To see a graph `G` you are working with, there
453
are three main options. You can view the graph in two dimensions via
454
matplotlib with ``show()``. ::
455
456
sage: G = graphs.RandomGNP(15,.3)
457
sage: G.show()
458
459
And you can view it in three dimensions via jmol with ``show3d()``. ::
460
461
sage: G.show3d()
462
463
Or it can be rendered with `\mbox{\rm\LaTeX}`. This requires the right
464
additions to a standard `\mbox{\rm\TeX}` installation. Then standard
465
Sage commands, such as ``view(G)`` will display the graph, or
466
``latex(G)`` will produce a string suitable for inclusion in a
467
`\mbox{\rm\LaTeX}` document. More details on this are at
468
the :mod:`sage.graphs.graph_latex` module. ::
469
470
sage: from sage.graphs.graph_latex import check_tkz_graph
471
sage: check_tkz_graph() # random - depends on TeX installation
472
sage: latex(G)
473
\begin{tikzpicture}
474
...
475
\end{tikzpicture}
476
477
Methods
478
-------
479
"""
480
481
#*****************************************************************************
482
# Copyright (C) 2006 - 2007 Robert L. Miller <[email protected]>
483
#
484
# Distributed under the terms of the GNU General Public License (GPL)
485
# http://www.gnu.org/licenses/
486
#*****************************************************************************
487
488
from sage.rings.integer import Integer
489
490
import sage.graphs.generic_graph_pyx as generic_graph_pyx
491
from sage.graphs.generic_graph import GenericGraph
492
from sage.graphs.digraph import DiGraph
493
494
class Graph(GenericGraph):
495
r"""
496
Undirected graph.
497
498
A graph is a set of vertices connected by edges. See also the
499
:wikipedia:`Wikipedia article on graphs <Graph_(mathematics)>`.
500
501
One can very easily create a graph in Sage by typing::
502
503
sage: g = Graph()
504
505
By typing the name of the graph, one can get some basic information
506
about it::
507
508
sage: g
509
Graph on 0 vertices
510
511
This graph is not very interesting as it is by default the empty graph.
512
But Sage contains a large collection of pre-defined graph classes that
513
can be listed this way:
514
515
* Within a Sage session, type ``graphs.``
516
(Do not press "Enter", and do not forget the final period ".")
517
* Hit "tab".
518
519
You will see a list of methods which will construct named graphs. For
520
example::
521
522
sage: g = graphs.PetersenGraph()
523
sage: g.plot()
524
525
or::
526
527
sage: g = graphs.ChvatalGraph()
528
sage: g.plot()
529
530
In order to obtain more information about these graph constructors, access
531
the documentation using the command ``graphs.RandomGNP?``.
532
533
Once you have defined the graph you want, you can begin to work on it
534
by using the almost 200 functions on graphs in the Sage library!
535
If your graph is named ``g``, you can list these functions as previously
536
this way
537
538
* Within a Sage session, type ``g.``
539
(Do not press "Enter", and do not forget the final period "." )
540
* Hit "tab".
541
542
As usual, you can get some information about what these functions do by
543
typing (e.g. if you want to know about the ``diameter()`` method)
544
``g.diameter?``.
545
546
If you have defined a graph ``g`` having several connected components
547
(i.e. ``g.is_connected()`` returns False), you can print each one of its
548
connected components with only two lines::
549
550
sage: for component in g.connected_components():
551
... g.subgraph(component).plot()
552
553
554
INPUT:
555
556
- ``data`` -- can be any of the following (see the ``format`` argument):
557
558
#. An integer specifying the number of vertices
559
560
#. A dictionary of dictionaries
561
562
#. A dictionary of lists
563
564
#. A NumPy matrix or ndarray
565
566
#. A Sage adjacency matrix or incidence matrix
567
568
#. A pygraphviz graph
569
570
#. A SciPy sparse matrix
571
572
#. A NetworkX graph
573
574
- ``pos`` - a positioning dictionary: for example, the
575
spring layout from NetworkX for the 5-cycle is::
576
577
{0: [-0.91679746, 0.88169588],
578
1: [ 0.47294849, 1.125 ],
579
2: [ 1.125 ,-0.12867615],
580
3: [ 0.12743933,-1.125 ],
581
4: [-1.125 ,-0.50118505]}
582
583
- ``name`` - (must be an explicitly named parameter,
584
i.e., ``name="complete")`` gives the graph a name
585
586
- ``loops`` - boolean, whether to allow loops (ignored
587
if data is an instance of the ``Graph`` class)
588
589
- ``multiedges`` - boolean, whether to allow multiple
590
edges (ignored if data is an instance of the ``Graph`` class)
591
592
- ``weighted`` - whether graph thinks of itself as
593
weighted or not. See ``self.weighted()``
594
595
- ``format`` - if None, Graph tries to guess- can be
596
several values, including:
597
598
- ``'int'`` - an integer specifying the number of vertices in an
599
edge-free graph with vertices labelled from 0 to n-1
600
601
- ``'graph6'`` - Brendan McKay's graph6 format, in a
602
string (if the string has multiple graphs, the first graph is
603
taken)
604
605
- ``'sparse6'`` - Brendan McKay's sparse6 format, in a
606
string (if the string has multiple graphs, the first graph is
607
taken)
608
609
- ``'adjacency_matrix'`` - a square Sage matrix M,
610
with M[i,j] equal to the number of edges {i,j}
611
612
- ``'weighted_adjacency_matrix'`` - a square Sage
613
matrix M, with M[i,j] equal to the weight of the single edge {i,j}.
614
Given this format, weighted is ignored (assumed True).
615
616
- ``'incidence_matrix'`` - a Sage matrix, with one
617
column C for each edge, where if C represents {i, j}, C[i] is -1
618
and C[j] is 1
619
620
- ``'elliptic_curve_congruence'`` - data must be an
621
iterable container of elliptic curves, and the graph produced has
622
each curve as a vertex (it's Cremona label) and an edge E-F
623
labelled p if and only if E is congruent to F mod p
624
625
- ``NX`` - data must be a NetworkX Graph.
626
627
.. NOTE::
628
629
As Sage's default edge labels is ``None`` while NetworkX uses
630
``{}``, the ``{}`` labels of a NetworkX graph are automatically
631
set to ``None`` when it is converted to a Sage graph. This
632
behaviour can be overruled by setting the keyword
633
``convert_empty_dict_labels_to_None`` to ``False`` (it is
634
``True`` by default).
635
636
- ``boundary`` - a list of boundary vertices, if
637
empty, graph is considered as a 'graph without boundary'
638
639
- ``implementation`` - what to use as a backend for
640
the graph. Currently, the options are either 'networkx' or
641
'c_graph'
642
643
- ``sparse`` - only for implementation == 'c_graph'.
644
Whether to use sparse or dense graphs as backend. Note that
645
currently dense graphs do not have edge labels, nor can they be
646
multigraphs
647
648
- ``vertex_labels`` - only for implementation == 'c_graph'.
649
Whether to allow any object as a vertex (slower), or
650
only the integers 0, ..., n-1, where n is the number of vertices.
651
652
- ``convert_empty_dict_labels_to_None`` - this arguments sets
653
the default edge labels used by NetworkX (empty dictionaries)
654
to be replaced by None, the default Sage edge label. It is
655
set to ``True`` iff a NetworkX graph is on the input.
656
657
658
EXAMPLES:
659
660
We illustrate the first seven input formats (the other two
661
involve packages that are currently not standard in Sage):
662
663
#. An integer giving the number of vertices::
664
665
sage: g = Graph(5); g
666
Graph on 5 vertices
667
sage: g.vertices()
668
[0, 1, 2, 3, 4]
669
sage: g.edges()
670
[]
671
672
#. A dictionary of dictionaries::
673
674
sage: g = Graph({0:{1:'x',2:'z',3:'a'}, 2:{5:'out'}}); g
675
Graph on 5 vertices
676
677
The labels ('x', 'z', 'a', 'out') are labels for edges. For
678
example, 'out' is the label for the edge on 2 and 5. Labels can be
679
used as weights, if all the labels share some common parent.
680
681
::
682
683
sage: a,b,c,d,e,f = sorted(SymmetricGroup(3))
684
sage: Graph({b:{d:'c',e:'p'}, c:{d:'p',e:'c'}})
685
Graph on 4 vertices
686
687
#. A dictionary of lists::
688
689
sage: g = Graph({0:[1,2,3], 2:[4]}); g
690
Graph on 5 vertices
691
692
#. A list of vertices and a function describing adjacencies. Note
693
that the list of vertices and the function must be enclosed in a
694
list (i.e., [list of vertices, function]).
695
696
Construct the Paley graph over GF(13).
697
698
::
699
700
sage: g=Graph([GF(13), lambda i,j: i!=j and (i-j).is_square()])
701
sage: g.vertices()
702
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
703
sage: g.adjacency_matrix()
704
[0 1 0 1 1 0 0 0 0 1 1 0 1]
705
[1 0 1 0 1 1 0 0 0 0 1 1 0]
706
[0 1 0 1 0 1 1 0 0 0 0 1 1]
707
[1 0 1 0 1 0 1 1 0 0 0 0 1]
708
[1 1 0 1 0 1 0 1 1 0 0 0 0]
709
[0 1 1 0 1 0 1 0 1 1 0 0 0]
710
[0 0 1 1 0 1 0 1 0 1 1 0 0]
711
[0 0 0 1 1 0 1 0 1 0 1 1 0]
712
[0 0 0 0 1 1 0 1 0 1 0 1 1]
713
[1 0 0 0 0 1 1 0 1 0 1 0 1]
714
[1 1 0 0 0 0 1 1 0 1 0 1 0]
715
[0 1 1 0 0 0 0 1 1 0 1 0 1]
716
[1 0 1 1 0 0 0 0 1 1 0 1 0]
717
718
Construct the line graph of a complete graph.
719
720
::
721
722
sage: g=graphs.CompleteGraph(4)
723
sage: line_graph=Graph([g.edges(labels=false), \
724
lambda i,j: len(set(i).intersection(set(j)))>0], \
725
loops=False)
726
sage: line_graph.vertices()
727
[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]
728
sage: line_graph.adjacency_matrix()
729
[0 1 1 1 1 0]
730
[1 0 1 1 0 1]
731
[1 1 0 0 1 1]
732
[1 1 0 0 1 1]
733
[1 0 1 1 0 1]
734
[0 1 1 1 1 0]
735
736
#. A NumPy matrix or ndarray::
737
738
sage: import numpy
739
sage: A = numpy.array([[0,1,1],[1,0,1],[1,1,0]])
740
sage: Graph(A)
741
Graph on 3 vertices
742
743
#. A graph6 or sparse6 string: Sage automatically recognizes
744
whether a string is in graph6 or sparse6 format::
745
746
sage: s = ':I`AKGsaOs`cI]Gb~'
747
sage: Graph(s,sparse=True)
748
Looped multi-graph on 10 vertices
749
750
::
751
752
sage: G = Graph('G?????')
753
sage: G = Graph("G'?G?C")
754
Traceback (most recent call last):
755
...
756
RuntimeError: The string seems corrupt: valid characters are
757
?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
758
sage: G = Graph('G??????')
759
Traceback (most recent call last):
760
...
761
RuntimeError: The string (G??????) seems corrupt: for n = 8, the string is too long.
762
763
::
764
765
sage: G = Graph(":I'AKGsaOs`cI]Gb~")
766
Traceback (most recent call last):
767
...
768
RuntimeError: The string seems corrupt: valid characters are
769
?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
770
771
There are also list functions to take care of lists of graphs::
772
773
sage: s = ':IgMoqoCUOqeb\n:I`AKGsaOs`cI]Gb~\n:I`EDOAEQ?PccSsge\N\n'
774
sage: graphs_list.from_sparse6(s)
775
[Looped multi-graph on 10 vertices, Looped multi-graph on 10 vertices, Looped multi-graph on 10 vertices]
776
777
#. A Sage matrix:
778
Note: If format is not specified, then Sage assumes a symmetric square
779
matrix is an adjacency matrix, otherwise an incidence matrix.
780
781
- an adjacency matrix::
782
783
sage: M = graphs.PetersenGraph().am(); M
784
[0 1 0 0 1 1 0 0 0 0]
785
[1 0 1 0 0 0 1 0 0 0]
786
[0 1 0 1 0 0 0 1 0 0]
787
[0 0 1 0 1 0 0 0 1 0]
788
[1 0 0 1 0 0 0 0 0 1]
789
[1 0 0 0 0 0 0 1 1 0]
790
[0 1 0 0 0 0 0 0 1 1]
791
[0 0 1 0 0 1 0 0 0 1]
792
[0 0 0 1 0 1 1 0 0 0]
793
[0 0 0 0 1 0 1 1 0 0]
794
sage: Graph(M)
795
Graph on 10 vertices
796
797
::
798
799
sage: Graph(matrix([[1,2],[2,4]]),loops=True,sparse=True)
800
Looped multi-graph on 2 vertices
801
802
sage: M = Matrix([[0,1,-1],[1,0,-1/2],[-1,-1/2,0]]); M
803
[ 0 1 -1]
804
[ 1 0 -1/2]
805
[ -1 -1/2 0]
806
sage: G = Graph(M,sparse=True); G
807
Graph on 3 vertices
808
sage: G.weighted()
809
True
810
811
- an incidence matrix::
812
813
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
814
[-1 0 0 0 1]
815
[ 1 -1 0 0 0]
816
[ 0 1 -1 0 0]
817
[ 0 0 1 -1 0]
818
[ 0 0 0 1 -1]
819
[ 0 0 0 0 0]
820
sage: Graph(M)
821
Graph on 6 vertices
822
823
sage: Graph(Matrix([[1],[1],[1]]))
824
Traceback (most recent call last):
825
...
826
ValueError: Non-symmetric or non-square matrix assumed to be an incidence matrix: There must be two nonzero entries (-1 & 1) per column.
827
sage: Graph(Matrix([[1],[1],[0]]))
828
Traceback (most recent call last):
829
...
830
ValueError: Non-symmetric or non-square matrix assumed to be an incidence matrix: Each column represents an edge: -1 goes to 1.
831
832
sage: M = Matrix([[0,1,-1],[1,0,-1],[-1,-1,0]]); M
833
[ 0 1 -1]
834
[ 1 0 -1]
835
[-1 -1 0]
836
sage: Graph(M,sparse=True)
837
Graph on 3 vertices
838
839
sage: M = Matrix([[0,1,1],[1,0,1],[-1,-1,0]]); M
840
[ 0 1 1]
841
[ 1 0 1]
842
[-1 -1 0]
843
sage: Graph(M)
844
Traceback (most recent call last):
845
...
846
ValueError: Non-symmetric or non-square matrix assumed to be an incidence matrix: Each column represents an edge: -1 goes to 1.
847
848
::
849
850
sage: MA = Matrix([[1,2,0], [0,2,0], [0,0,1]]) # trac 9714
851
sage: MI = Graph(MA, format='adjacency_matrix').incidence_matrix(); MI
852
[-1 -1 0 0 0 1]
853
[ 1 1 0 1 1 0]
854
[ 0 0 1 0 0 0]
855
sage: Graph(MI).edges(labels=None)
856
[(0, 0), (0, 1), (0, 1), (1, 1), (1, 1), (2, 2)]
857
858
sage: M = Matrix([[1], [-1]]); M
859
[ 1]
860
[-1]
861
sage: Graph(M).edges()
862
[(0, 1, None)]
863
864
#. a list of edges, or labelled edges::
865
866
sage: g = Graph([(1,3),(3,8),(5,2)])
867
sage: g
868
Graph on 5 vertices
869
870
::
871
872
sage: g = Graph([(1,2,"Peace"),(7,-9,"and"),(77,2, "Love")])
873
sage: g
874
Graph on 5 vertices
875
876
#. A NetworkX MultiGraph::
877
878
sage: import networkx
879
sage: g = networkx.MultiGraph({0:[1,2,3], 2:[4]})
880
sage: Graph(g)
881
Graph on 5 vertices
882
883
#. A NetworkX graph::
884
885
sage: import networkx
886
sage: g = networkx.Graph({0:[1,2,3], 2:[4]})
887
sage: DiGraph(g)
888
Digraph on 5 vertices
889
890
Note that in all cases, we copy the NetworkX structure.
891
892
::
893
894
sage: import networkx
895
sage: g = networkx.Graph({0:[1,2,3], 2:[4]})
896
sage: G = Graph(g, implementation='networkx')
897
sage: H = Graph(g, implementation='networkx')
898
sage: G._backend._nxg is H._backend._nxg
899
False
900
"""
901
_directed = False
902
903
def __init__(self, data=None, pos=None, loops=None, format=None,
904
boundary=[], weighted=None, implementation='c_graph',
905
sparse=True, vertex_labels=True, name=None,
906
multiedges=None, convert_empty_dict_labels_to_None=None):
907
"""
908
TESTS::
909
910
sage: G = Graph()
911
sage: loads(dumps(G)) == G
912
True
913
sage: a = matrix(2,2,[1,0,0,1])
914
sage: Graph(a).adjacency_matrix() == a
915
True
916
917
sage: a = matrix(2,2,[2,0,0,1])
918
sage: Graph(a,sparse=True).adjacency_matrix() == a
919
True
920
921
The positions are copied when the graph is built from
922
another graph ::
923
924
sage: g = graphs.PetersenGraph()
925
sage: h = Graph(g)
926
sage: g.get_pos() == h.get_pos()
927
True
928
929
Or from a DiGraph ::
930
931
sage: d = DiGraph(g)
932
sage: h = Graph(d)
933
sage: g.get_pos() == h.get_pos()
934
True
935
936
Loops are not counted as multiedges (see trac 11693) and edges
937
are not counted twice ::
938
939
sage: Graph([[1,1]],multiedges=False).num_edges()
940
1
941
sage: Graph([[1,2],[1,2]],multiedges=True).num_edges()
942
2
943
944
Invalid sequence of edges given as an input (they do not all
945
have the same length)::
946
947
sage: g = Graph([(1,2),(2,3),(2,3,4)])
948
Traceback (most recent call last):
949
...
950
ValueError: Edges input must all follow the same format.
951
952
953
Two different labels given for the same edge in a graph
954
without multiple edges::
955
956
sage: g = Graph([(1,2,3),(1,2,4)], multiedges = False)
957
Traceback (most recent call last):
958
...
959
ValueError: Two different labels given for the same edge in a graph without multiple edges.
960
961
The same edge included more than once in a graph without
962
multiple edges::
963
964
sage: g = Graph([[1,2],[1,2]],multiedges=False)
965
Traceback (most recent call last):
966
...
967
ValueError: Non-multigraph input dict has multiple edges (1,2)
968
969
An empty list or dictionary defines a simple graph
970
(:trac:`10441` and :trac:`12910`)::
971
972
sage: Graph([])
973
Graph on 0 vertices
974
sage: Graph({})
975
Graph on 0 vertices
976
sage: # not "Multi-graph on 0 vertices"
977
978
Verify that the int format works as expected (:trac:`12557`)::
979
980
sage: Graph(2).adjacency_matrix()
981
[0 0]
982
[0 0]
983
sage: Graph(3) == Graph(3,format='int')
984
True
985
986
"""
987
GenericGraph.__init__(self)
988
msg = ''
989
from sage.structure.element import is_Matrix
990
from sage.misc.misc import uniq
991
if format is None and isinstance(data, str):
992
if data[:10] == ">>graph6<<":
993
data = data[10:]
994
format = 'graph6'
995
elif data[:11] == ">>sparse6<<":
996
data = data[11:]
997
format = 'sparse6'
998
elif data[0] == ':':
999
format = 'sparse6'
1000
else:
1001
format = 'graph6'
1002
if format is None and is_Matrix(data):
1003
if data.is_square() and data == data.transpose():
1004
format = 'adjacency_matrix'
1005
else:
1006
format = 'incidence_matrix'
1007
msg += "Non-symmetric or non-square matrix assumed to be an incidence matrix: "
1008
if format is None and isinstance(data, Graph):
1009
format = 'Graph'
1010
from sage.graphs.all import DiGraph
1011
if format is None and isinstance(data, DiGraph):
1012
data = data.to_undirected()
1013
format = 'Graph'
1014
if format is None and isinstance(data,list) and \
1015
len(data)>=2 and callable(data[1]):
1016
format = 'rule'
1017
if format is None and isinstance(data,dict):
1018
keys = data.keys()
1019
if len(keys) == 0: format = 'dict_of_dicts'
1020
else:
1021
if isinstance(data[keys[0]], list):
1022
format = 'dict_of_lists'
1023
elif isinstance(data[keys[0]], dict):
1024
format = 'dict_of_dicts'
1025
if format is None and hasattr(data, 'adj'):
1026
import networkx
1027
if isinstance(data, (networkx.DiGraph, networkx.MultiDiGraph)):
1028
data = data.to_undirected()
1029
format = 'NX'
1030
elif isinstance(data, (networkx.Graph, networkx.MultiGraph)):
1031
format = 'NX'
1032
if format is None and isinstance(data, (int, Integer)):
1033
format = 'int'
1034
if format is None and data is None:
1035
format = 'int'
1036
data = 0
1037
1038
# Input is a list of edges
1039
if format is None and isinstance(data, list):
1040
1041
# If we are given a list (we assume it is a list of
1042
# edges), we convert the problem to a dict_of_dicts or a
1043
# dict_of_lists
1044
1045
edges = data
1046
1047
# Along the process, we are assuming all edges have the
1048
# same length. If it is not true, a ValueError will occur
1049
try:
1050
1051
# The list is empty
1052
if not data:
1053
data = {}
1054
format = 'dict_of_dicts'
1055
1056
# The edges are not labelled
1057
elif len(data[0]) == 2:
1058
data = {}
1059
for u,v in edges:
1060
if not u in data:
1061
data[u] = []
1062
if not v in data:
1063
data[v] = []
1064
data[u].append(v)
1065
1066
format = 'dict_of_lists'
1067
1068
# The edges are labelled
1069
elif len(data[0]) == 3:
1070
data = {}
1071
for u,v,l in edges:
1072
1073
if not u in data:
1074
data[u] = {}
1075
if not v in data:
1076
data[v] = {}
1077
1078
# Now the keys exists, and are dictionaries ...
1079
1080
1081
# If we notice for the first time that there
1082
# are multiple edges, we update the whole
1083
# dictionary so that data[u][v] is a list
1084
1085
if (multiedges is None and
1086
(u in data[v])):
1087
multiedges = True
1088
for uu, dd in data.iteritems():
1089
for vv, ddd in dd.iteritems():
1090
dd[vv] = [ddd]
1091
1092
# If multiedges is set to False while the same
1093
# edge is present in the list with different
1094
# values of its label
1095
elif (multiedges is False and
1096
(u in data[v] and data[v][u] != l)):
1097
raise ValueError("MULTIEDGE")
1098
1099
# Now we are behaving as if multiedges == None
1100
# means multiedges = False. If something bad
1101
# happens later, the whole dictionary will be
1102
# updated anyway
1103
1104
if multiedges is True:
1105
if v not in data[u]:
1106
data[u][v] = []
1107
data[v][u] = []
1108
1109
data[u][v].append(l)
1110
data[v][u].append(l)
1111
1112
else:
1113
data[u][v] = l
1114
data[v][u] = l
1115
1116
format = 'dict_of_dicts'
1117
1118
except ValueError as ve:
1119
if str(ve) == "MULTIEDGE":
1120
raise ValueError("Two different labels given for the same edge in a graph without multiple edges.")
1121
else:
1122
raise ValueError("Edges input must all follow the same format.")
1123
1124
1125
if format is None:
1126
import networkx
1127
data = networkx.MultiGraph(data)
1128
format = 'NX'
1129
1130
# At this point, format has been set.
1131
1132
# adjust for empty dicts instead of None in NetworkX default edge labels
1133
if convert_empty_dict_labels_to_None is None:
1134
convert_empty_dict_labels_to_None = (format == 'NX')
1135
1136
verts = None
1137
1138
if format == 'graph6':
1139
if loops is None: loops = False
1140
if weighted is None: weighted = False
1141
if multiedges is None: multiedges = False
1142
if not isinstance(data, str):
1143
raise ValueError('If input format is graph6, then data must be a string.')
1144
n = data.find('\n')
1145
if n == -1:
1146
n = len(data)
1147
ss = data[:n]
1148
n, s = generic_graph_pyx.N_inverse(ss)
1149
m = generic_graph_pyx.R_inverse(s, n)
1150
expected = n*(n-1)/2 + (6 - n*(n-1)/2)%6
1151
if len(m) > expected:
1152
raise RuntimeError("The string (%s) seems corrupt: for n = %d, the string is too long."%(ss,n))
1153
elif len(m) < expected:
1154
raise RuntimeError("The string (%s) seems corrupt: for n = %d, the string is too short."%(ss,n))
1155
num_verts = n
1156
elif format == 'sparse6':
1157
if loops is None: loops = True
1158
if weighted is None: weighted = False
1159
if multiedges is None: multiedges = True
1160
from math import ceil, floor
1161
from sage.misc.functional import log
1162
n = data.find('\n')
1163
if n == -1:
1164
n = len(data)
1165
s = data[:n]
1166
n, s = generic_graph_pyx.N_inverse(s[1:])
1167
if n == 0:
1168
edges = []
1169
else:
1170
k = int(ceil(log(n,2)))
1171
ords = [ord(i) for i in s]
1172
if any(o > 126 or o < 63 for o in ords):
1173
raise RuntimeError("The string seems corrupt: valid characters are \n" + ''.join([chr(i) for i in xrange(63,127)]))
1174
bits = ''.join([generic_graph_pyx.binary(o-63).zfill(6) for o in ords])
1175
b = []
1176
x = []
1177
for i in xrange(int(floor(len(bits)/(k+1)))):
1178
b.append(int(bits[(k+1)*i:(k+1)*i+1],2))
1179
x.append(int(bits[(k+1)*i+1:(k+1)*i+k+1],2))
1180
v = 0
1181
edges = []
1182
for i in xrange(len(b)):
1183
if b[i] == 1:
1184
v += 1
1185
if x[i] > v:
1186
v = x[i]
1187
else:
1188
if v < n:
1189
edges.append((x[i],v))
1190
num_verts = n
1191
elif format in ['adjacency_matrix', 'incidence_matrix']:
1192
assert is_Matrix(data)
1193
if format == 'weighted_adjacency_matrix':
1194
if weighted is False:
1195
raise ValueError("Format was weighted_adjacency_matrix but weighted was False.")
1196
if weighted is None: weighted = True
1197
if multiedges is None: multiedges = False
1198
format = 'adjacency_matrix'
1199
if format == 'adjacency_matrix':
1200
entries = uniq(data.list())
1201
for e in entries:
1202
try:
1203
e = int(e)
1204
assert e >= 0
1205
except:
1206
if weighted is False:
1207
raise ValueError("Non-weighted graph's"+
1208
" adjacency matrix must have only nonnegative"+
1209
" integer entries")
1210
weighted = True
1211
if multiedges is None: multiedges = False
1212
break
1213
if multiedges is None: multiedges = (sorted(entries) != [0,1])
1214
if weighted is None: weighted = False
1215
for i in xrange(data.nrows()):
1216
if data[i,i] != 0:
1217
if loops is None: loops = True
1218
elif not loops:
1219
raise ValueError("Non-looped graph's adjacency"+
1220
" matrix must have zeroes on the diagonal.")
1221
break
1222
num_verts = data.nrows()
1223
elif format == 'incidence_matrix':
1224
try:
1225
positions = []
1226
for c in data.columns():
1227
NZ = c.nonzero_positions()
1228
if len(NZ) == 1:
1229
if loops is None:
1230
loops = True
1231
elif not loops:
1232
msg += "There must be two nonzero entries (-1 & 1) per column."
1233
assert False
1234
positions.append((NZ[0], NZ[0]))
1235
elif len(NZ) != 2:
1236
msg += "There must be two nonzero entries (-1 & 1) per column."
1237
assert False
1238
else:
1239
positions.append(tuple(NZ))
1240
L = uniq(c.list())
1241
L.sort()
1242
1243
if data.nrows() != (2 if len(NZ) == 2 else 1):
1244
desirable = [-1, 0, 1] if len(NZ) == 2 else [0, 1]
1245
else:
1246
desirable = [-1, 1] if len(NZ) == 2 else [1]
1247
1248
if L != desirable:
1249
msg += "Each column represents an edge: -1 goes to 1."
1250
assert False
1251
if loops is None: loops = False
1252
if weighted is None: weighted = False
1253
if multiedges is None:
1254
total = len(positions)
1255
multiedges = ( len(uniq(positions)) < total )
1256
except AssertionError:
1257
raise ValueError(msg)
1258
num_verts = data.nrows()
1259
elif format == 'Graph':
1260
if loops is None: loops = data.allows_loops()
1261
elif not loops and data.has_loops():
1262
raise ValueError("No loops but input graph has loops.")
1263
if multiedges is None: multiedges = data.allows_multiple_edges()
1264
elif not multiedges:
1265
e = data.edges(labels=False)
1266
e = [sorted(f) for f in e]
1267
if len(e) != len(uniq(e)):
1268
raise ValueError("No multiple edges but input graph"+
1269
" has multiple edges.")
1270
if weighted is None: weighted = data.weighted()
1271
num_verts = data.num_verts()
1272
verts = data.vertex_iterator()
1273
if data.get_pos() is not None:
1274
pos = data.get_pos().copy()
1275
1276
elif format == 'rule':
1277
f = data[1]
1278
if loops is None: loops = any(f(v,v) for v in data[0])
1279
if multiedges is None: multiedges = False
1280
if weighted is None: weighted = False
1281
num_verts = len(data[0])
1282
verts = data[0]
1283
elif format == 'dict_of_dicts':
1284
if not all(isinstance(data[u], dict) for u in data):
1285
raise ValueError("Input dict must be a consistent format.")
1286
verts = set(data.keys())
1287
if loops is None or loops is False:
1288
for u in data:
1289
if u in data[u]:
1290
if loops is None: loops = True
1291
elif loops is False:
1292
raise ValueError("No loops but dict has loops.")
1293
break
1294
if loops is None: loops = False
1295
if weighted is None: weighted = False
1296
for u in data:
1297
for v in data[u]:
1298
if v not in verts: verts.add(v)
1299
if hash(u) > hash(v):
1300
if v in data and u in data[v]:
1301
if data[u][v] != data[v][u]:
1302
raise ValueError("Dict does not agree on edge (%s,%s)"%(u,v))
1303
continue
1304
if multiedges is not False and not isinstance(data[u][v], list):
1305
if multiedges is None: multiedges = False
1306
if multiedges:
1307
raise ValueError("Dict of dicts for multigraph must be in the format {v : {u : list}}")
1308
if multiedges is None and len(data) > 0:
1309
multiedges = True
1310
num_verts = len(verts)
1311
elif format == 'dict_of_lists':
1312
if not all(isinstance(data[u], list) for u in data):
1313
raise ValueError("Input dict must be a consistent format.")
1314
verts = set(data.keys())
1315
if loops is None or loops is False:
1316
for u in data:
1317
if u in data[u]:
1318
if loops is None: loops = True
1319
elif loops is False:
1320
raise ValueError("No loops but dict has loops.")
1321
break
1322
if loops is None: loops = False
1323
if weighted is None: weighted = False
1324
for u in data:
1325
verts=verts.union([v for v in data[u] if v not in verts])
1326
if len(uniq(data[u])) != len(data[u]):
1327
if multiedges is False:
1328
from sage.misc.prandom import choice
1329
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])))
1330
if multiedges is None: multiedges = True
1331
if multiedges is None: multiedges = False
1332
num_verts = len(verts)
1333
elif format == 'NX':
1334
if weighted is None:
1335
if isinstance(data, networkx.Graph):
1336
weighted = False
1337
if multiedges is None:
1338
multiedges = False
1339
if loops is None:
1340
loops = False
1341
else:
1342
weighted = True
1343
if multiedges is None:
1344
multiedges = True
1345
if loops is None:
1346
loops = True
1347
num_verts = data.order()
1348
verts = data.nodes()
1349
data = data.adj
1350
format = 'dict_of_dicts'
1351
elif format in ['int', 'elliptic_curve_congruence']:
1352
if weighted is None: weighted = False
1353
if multiedges is None: multiedges = False
1354
if loops is None: loops = False
1355
if format == 'int': num_verts = data
1356
else:
1357
num_verts = len(data)
1358
curves = data
1359
verts = [curve.cremona_label() for curve in data]
1360
1361
# weighted, multiedges, loops, verts and num_verts should now be set
1362
1363
if implementation == 'networkx':
1364
import networkx
1365
from sage.graphs.base.graph_backends import NetworkXGraphBackend
1366
if format == 'Graph':
1367
self._backend = NetworkXGraphBackend(data.networkx_graph())
1368
self._weighted = weighted
1369
self.allow_loops(loops)
1370
self.allow_multiple_edges(multiedges)
1371
else:
1372
if multiedges:
1373
self._backend = NetworkXGraphBackend(networkx.MultiGraph())
1374
else:
1375
self._backend = NetworkXGraphBackend(networkx.Graph())
1376
self._weighted = weighted
1377
self.allow_loops(loops)
1378
self.allow_multiple_edges(multiedges)
1379
if verts is not None:
1380
self.add_vertices(verts)
1381
else:
1382
self.add_vertices(range(num_verts))
1383
elif implementation == 'c_graph':
1384
if multiedges or weighted:
1385
if not sparse:
1386
raise RuntimeError("Multiedge and weighted c_graphs must be sparse.")
1387
from sage.graphs.base.sparse_graph import SparseGraphBackend
1388
from sage.graphs.base.dense_graph import DenseGraphBackend
1389
CGB = SparseGraphBackend if sparse else DenseGraphBackend
1390
if format == 'Graph':
1391
self._backend = CGB(0, directed=False)
1392
self.add_vertices(verts)
1393
self._weighted = weighted
1394
self.allow_loops(loops, check=False)
1395
self.allow_multiple_edges(multiedges, check=False)
1396
for u,v,l in data.edge_iterator():
1397
self._backend.add_edge(u,v,l,False)
1398
else:
1399
if verts is not None:
1400
self._backend = CGB(0, directed=False)
1401
self.add_vertices(verts)
1402
else:
1403
self._backend = CGB(num_verts, directed=False)
1404
self._weighted = weighted
1405
self.allow_loops(loops, check=False)
1406
self.allow_multiple_edges(multiedges, check=False)
1407
self._backend.directed = False
1408
else:
1409
raise NotImplementedError("Supported implementations: networkx, c_graph.")
1410
1411
if format == 'graph6':
1412
k = 0
1413
for i in xrange(n):
1414
for j in xrange(i):
1415
if m[k] == '1':
1416
self.add_edge(i, j)
1417
k += 1
1418
1419
elif format == 'sparse6':
1420
for i,j in edges:
1421
self.add_edge(i,j)
1422
1423
elif format == 'adjacency_matrix':
1424
e = []
1425
if weighted:
1426
for i,j in data.nonzero_positions():
1427
if i <= j:
1428
e.append((i,j,data[i][j]))
1429
elif multiedges:
1430
for i,j in data.nonzero_positions():
1431
if i <= j:
1432
e += [(i,j)]*int(data[i][j])
1433
else:
1434
for i,j in data.nonzero_positions():
1435
if i <= j:
1436
e.append((i,j))
1437
self.add_edges(e)
1438
1439
elif format == 'incidence_matrix':
1440
self.add_edges(positions)
1441
1442
elif format == 'Graph':
1443
self.name(data.name())
1444
1445
elif format == 'rule':
1446
verts = list(verts)
1447
for u in xrange(num_verts):
1448
for v in xrange(u+1):
1449
uu,vv = verts[u], verts[v]
1450
if f(uu,vv):
1451
self.add_edge(uu,vv)
1452
1453
elif format == 'dict_of_dicts':
1454
if convert_empty_dict_labels_to_None:
1455
for u in data:
1456
for v in data[u]:
1457
if hash(u) <= hash(v) or v not in data or u not in data[v]:
1458
if multiedges:
1459
self.add_edges([(u,v,l) for l in data[u][v]])
1460
else:
1461
self.add_edge((u,v,data[u][v] if data[u][v] != {} else None))
1462
else:
1463
for u in data:
1464
for v in data[u]:
1465
if hash(u) <= hash(v) or v not in data or u not in data[v]:
1466
if multiedges:
1467
self.add_edges([(u,v,l) for l in data[u][v]])
1468
else:
1469
self.add_edge((u,v,data[u][v]))
1470
1471
elif format == 'dict_of_lists':
1472
for u in data:
1473
for v in data[u]:
1474
if multiedges or hash(u) <= hash(v) or \
1475
v not in data or u not in data[v]:
1476
self.add_edge(u,v)
1477
1478
elif format == 'elliptic_curve_congruence':
1479
from sage.rings.arith import lcm, prime_divisors
1480
from sage.rings.fast_arith import prime_range
1481
from sage.misc.misc import prod
1482
for i in xrange(self.order()):
1483
for j in xrange(i):
1484
E = curves[i]
1485
F = curves[j]
1486
M = E.conductor()
1487
N = F.conductor()
1488
MN = lcm(M, N)
1489
p_MN = prime_divisors(MN)
1490
lim = prod([(j^(MN.ord(j)) + j^(MN.ord(j)-1)) for j in p_MN])
1491
a_E = E.anlist(lim)
1492
a_F = F.anlist(lim)
1493
l_list = [p for p in prime_range(lim) if p not in p_MN ]
1494
p_edges = l_list
1495
for l in l_list:
1496
n = a_E[l] - a_F[l]
1497
if n != 0:
1498
P = prime_divisors(n)
1499
p_edges = [p for p in p_edges if p in P]
1500
if len(p_edges) > 0:
1501
self.add_edge(E.cremona_label(), F.cremona_label(), str(p_edges)[1:-1])
1502
else:
1503
assert format == 'int'
1504
1505
self._pos = pos
1506
self._boundary = boundary
1507
if format != 'Graph' or name is not None:
1508
self.name(name)
1509
1510
### Formats
1511
1512
def graph6_string(self):
1513
"""
1514
Returns the graph6 representation of the graph as an ASCII string.
1515
Only valid for simple (no loops, multiple edges) graphs on 0 to
1516
262143 vertices.
1517
1518
EXAMPLES::
1519
1520
sage: G = graphs.KrackhardtKiteGraph()
1521
sage: G.graph6_string()
1522
'IvUqwK@?G'
1523
"""
1524
n = self.order()
1525
if n > 262143:
1526
raise ValueError('graph6 format supports graphs on 0 to 262143 vertices only.')
1527
elif self.has_loops() or self.has_multiple_edges():
1528
raise ValueError('graph6 format supports only simple graphs (no loops, no multiple edges)')
1529
else:
1530
return generic_graph_pyx.N(n) + generic_graph_pyx.R(self._bit_vector())
1531
1532
def sparse6_string(self):
1533
"""
1534
Returns the sparse6 representation of the graph as an ASCII string.
1535
Only valid for undirected graphs on 0 to 262143 vertices, but loops
1536
and multiple edges are permitted.
1537
1538
EXAMPLES::
1539
1540
sage: G = graphs.BullGraph()
1541
sage: G.sparse6_string()
1542
':Da@en'
1543
1544
::
1545
1546
sage: G = Graph()
1547
sage: G.sparse6_string()
1548
':?'
1549
1550
::
1551
1552
sage: G = Graph(loops=True, multiedges=True,sparse=True)
1553
sage: Graph(':?',sparse=True) == G
1554
True
1555
"""
1556
n = self.order()
1557
if n == 0:
1558
return ':?'
1559
if n > 262143:
1560
raise ValueError('sparse6 format supports graphs on 0 to 262143 vertices only.')
1561
else:
1562
vertices = self.vertices()
1563
n = len(vertices)
1564
edges = self.edges(labels=False)
1565
for i in range(len(edges)): # replace edge labels with natural numbers (by index in vertices)
1566
edges[i] = (vertices.index(edges[i][0]),vertices.index(edges[i][1]))
1567
# order edges
1568
edges.sort(compare_edges)
1569
1570
# encode bit vector
1571
from math import ceil
1572
from sage.misc.functional import log
1573
k = int(ceil(log(n,2)))
1574
v = 0
1575
i = 0
1576
m = 0
1577
s = ''
1578
while m < len(edges):
1579
if edges[m][1] > v + 1:
1580
sp = generic_graph_pyx.binary(edges[m][1])
1581
sp = '0'*(k-len(sp)) + sp
1582
s += '1' + sp
1583
v = edges[m][1]
1584
elif edges[m][1] == v + 1:
1585
sp = generic_graph_pyx.binary(edges[m][0])
1586
sp = '0'*(k-len(sp)) + sp
1587
s += '1' + sp
1588
v += 1
1589
m += 1
1590
else:
1591
sp = generic_graph_pyx.binary(edges[m][0])
1592
sp = '0'*(k-len(sp)) + sp
1593
s += '0' + sp
1594
m += 1
1595
1596
# encode s as a 6-string, as in R(x), but padding with 1's
1597
# pad on the right to make a multiple of 6
1598
s = s + ( '1' * ((6 - len(s))%6) )
1599
1600
# split into groups of 6, and convert numbers to decimal, adding 63
1601
six_bits = ''
1602
for i in range(len(s)/6):
1603
six_bits += chr( int( s[6*i:6*(i+1)], 2) + 63 )
1604
return ':' + generic_graph_pyx.N(n) + six_bits
1605
1606
### Attributes
1607
1608
def is_directed(self):
1609
"""
1610
Since graph is undirected, returns False.
1611
1612
EXAMPLES::
1613
1614
sage: Graph().is_directed()
1615
False
1616
"""
1617
return False
1618
1619
### Properties
1620
1621
def is_even_hole_free(self, certificate = False):
1622
r"""
1623
Tests whether ``self`` contains an induced even hole.
1624
1625
A Hole is a cycle of length at least 4 (included). It is said
1626
to be even (resp. odd) if its length is even (resp. odd).
1627
1628
Even-hole-free graphs always contain a bisimplicial vertex,
1629
which ensures that their chromatic number is at most twice
1630
their clique number [ABCHRS08]_.
1631
1632
INPUT:
1633
1634
- ``certificate`` (boolean) -- When ``certificate = False``,
1635
this method only returns ``True`` or ``False``. If
1636
``certificate = True``, the subgraph found is returned
1637
instead of ``False``.
1638
1639
EXAMPLE:
1640
1641
Is the Petersen Graph even-hole-free ::
1642
1643
sage: g = graphs.PetersenGraph()
1644
sage: g.is_even_hole_free()
1645
False
1646
1647
As any chordal graph is hole-free, interval graphs behave the
1648
same way::
1649
1650
sage: g = graphs.RandomInterval(20)
1651
sage: g.is_even_hole_free()
1652
True
1653
1654
It is clear, though, that a random Bipartite Graph which is
1655
not a forest has an even hole::
1656
1657
sage: g = graphs.RandomBipartite(10, 10, .5)
1658
sage: g.is_even_hole_free() and not g.is_forest()
1659
False
1660
1661
We can check the certificate returned is indeed an even
1662
cycle::
1663
1664
sage: if not g.is_forest():
1665
... cycle = g.is_even_hole_free(certificate = True)
1666
... if cycle.order() % 2 == 1:
1667
... print "Error !"
1668
... if not cycle.is_isomorphic(
1669
... graphs.CycleGraph(cycle.order())):
1670
... print "Error !"
1671
...
1672
sage: print "Everything is Fine !"
1673
Everything is Fine !
1674
1675
TESTS:
1676
1677
Bug reported in #9925, and fixed by #9420::
1678
1679
sage: g = Graph(':SiBFGaCEF_@CE`DEGH`CEFGaCDGaCDEHaDEF`CEH`ABCDEF')
1680
sage: g.is_even_hole_free()
1681
False
1682
sage: g.is_even_hole_free(certificate = True)
1683
Subgraph of (): Looped multi-graph on 4 vertices
1684
1685
Making sure there are no other counter-examples around ::
1686
1687
sage: t = lambda x : (Graph(x).is_forest() or
1688
... isinstance(Graph(x).is_even_hole_free(certificate = True),Graph))
1689
sage: all( t(graphs.RandomBipartite(10,10,.5)) for i in range(100) )
1690
True
1691
1692
REFERENCE:
1693
1694
.. [ABCHRS08] L. Addario-Berry, M. Chudnovsky, F. Havet, B. Reed, P. Seymour
1695
Bisimplicial vertices in even-hole-free graphs
1696
Journal of Combinatorial Theory, Series B
1697
vol 98, n.6 pp 1119-1164, 2008
1698
"""
1699
from sage.graphs.graph_generators import GraphGenerators
1700
1701
girth = self.girth()
1702
1703
if girth > self.order():
1704
start = 4
1705
1706
elif girth % 2 == 0:
1707
if not certificate:
1708
return False
1709
start = girth
1710
1711
else:
1712
start = girth + 1
1713
1714
while start <= self.order():
1715
1716
1717
subgraph = self.subgraph_search(GraphGenerators().CycleGraph(start), induced = True)
1718
1719
if not subgraph is None:
1720
if certificate:
1721
return subgraph
1722
else:
1723
return False
1724
1725
start = start + 2
1726
1727
return True
1728
1729
def is_odd_hole_free(self, certificate = False):
1730
r"""
1731
Tests whether ``self`` contains an induced odd hole.
1732
1733
A Hole is a cycle of length at least 4 (included). It is said
1734
to be even (resp. odd) if its length is even (resp. odd).
1735
1736
It is interesting to notice that while it is polynomial to
1737
check whether a graph has an odd hole or an odd antihole [CRST06]_, it is
1738
not known whether testing for one of these two cases
1739
independently is polynomial too.
1740
1741
INPUT:
1742
1743
- ``certificate`` (boolean) -- When ``certificate = False``,
1744
this method only returns ``True`` or ``False``. If
1745
``certificate = True``, the subgraph found is returned
1746
instead of ``False``.
1747
1748
EXAMPLE:
1749
1750
Is the Petersen Graph odd-hole-free ::
1751
1752
sage: g = graphs.PetersenGraph()
1753
sage: g.is_odd_hole_free()
1754
False
1755
1756
Which was to be expected, as its girth is 5 ::
1757
1758
sage: g.girth()
1759
5
1760
1761
We can check the certificate returned is indeed a 5-cycle::
1762
1763
sage: cycle = g.is_odd_hole_free(certificate = True)
1764
sage: cycle.is_isomorphic(graphs.CycleGraph(5))
1765
True
1766
1767
As any chordal graph is hole-free, no interval graph has an odd hole::
1768
1769
sage: g = graphs.RandomInterval(20)
1770
sage: g.is_odd_hole_free()
1771
True
1772
1773
REFERENCES:
1774
1775
.. [CRST06] M. Chudnovsky, G. Cornuejols, X. Liu, P. Seymour, K. Vuskovic
1776
Recognizing berge graphs
1777
Combinatorica vol 25, n 2, pages 143--186
1778
2005
1779
"""
1780
from sage.graphs.graph_generators import GraphGenerators
1781
1782
girth = self.girth()
1783
1784
if girth > self.order() or girth == 3:
1785
start = 5
1786
1787
elif girth % 2 == 1:
1788
if not certificate:
1789
return False
1790
start = girth
1791
1792
else:
1793
start = girth + 1
1794
1795
while start <= self.order():
1796
1797
subgraph = self.subgraph_search(GraphGenerators().CycleGraph(start), induced = True)
1798
1799
if not subgraph is None:
1800
if certificate:
1801
return subgraph
1802
else:
1803
return False
1804
1805
start = start + 2
1806
1807
return True
1808
1809
def is_line_graph(self, certificate = False):
1810
r"""
1811
Tests wether the graph is a line graph.
1812
1813
INPUT:
1814
1815
- ``certificate`` (boolean) -- whether to return a certificate
1816
when the graph is *not* a line graph. When ``certificate``
1817
is set to ``True``, and if the graph is not a line graph,
1818
the method returns a subgraph isomorphic to one of the 9
1819
forbidden induced subgraphs of a line graph (instead of the
1820
usual ``False``)
1821
1822
1823
TODO:
1824
1825
This methods sequentially tests each of the forbidden
1826
subgraphs, which is a very slow method. There exist much
1827
better algorithms, including those which are actually able to
1828
return a graph whose line graph is isomorphic to the given
1829
graph.
1830
1831
EXAMPLES:
1832
1833
A complete graph is always the line graph of a star::
1834
1835
sage: graphs.CompleteGraph(5).is_line_graph()
1836
True
1837
1838
The Petersen Graph not being claw-free, it is not a line
1839
graph:
1840
1841
sage: graphs.PetersenGraph().is_line_graph()
1842
False
1843
1844
This is indeed the subgraph returned::
1845
1846
sage: C = graphs.PetersenGraph().is_line_graph(certificate = True)
1847
sage: C.is_isomorphic(graphs.ClawGraph())
1848
True
1849
"""
1850
from sage.graphs.graph_generators import graphs
1851
1852
for g in graphs.line_graph_forbidden_subgraphs():
1853
h = self.subgraph_search(g, induced = True)
1854
if h is not None:
1855
if certificate:
1856
return h
1857
else:
1858
return False
1859
1860
return True
1861
1862
def is_bipartite(self, certificate = False):
1863
"""
1864
Returns ``True`` if graph `G` is bipartite, ``False`` if not.
1865
1866
Traverse the graph G with breadth-first-search and color nodes.
1867
1868
INPUT:
1869
1870
- ``certificate`` -- whether to return a certificate (``False`` by
1871
default). If set to ``True``, the certificate returned in a proper
1872
2-coloring when `G` is bipartite, and an odd cycle otherwise.
1873
1874
EXAMPLES::
1875
1876
sage: graphs.CycleGraph(4).is_bipartite()
1877
True
1878
sage: graphs.CycleGraph(5).is_bipartite()
1879
False
1880
1881
A random graph is very rarely bipartite::
1882
1883
sage: g = graphs.PetersenGraph()
1884
sage: g.is_bipartite()
1885
False
1886
sage: false, oddcycle = g.is_bipartite(certificate = True)
1887
sage: len(oddcycle) % 2
1888
1
1889
"""
1890
color = {}
1891
1892
# For any uncolored vertex in the graph (to ensure we do the right job
1893
# when the graph is not connected !)
1894
for u in self:
1895
if u in color:
1896
continue
1897
1898
# Let us run a BFS starting from u
1899
queue = [u]
1900
color[u] = 1
1901
while queue:
1902
v = queue.pop(0)
1903
c = 1-color[v]
1904
for w in self.neighbor_iterator(v):
1905
1906
# If the vertex has already been colored
1907
if w in color:
1908
1909
# The graph is not bipartite !
1910
if color[w] == color[v]:
1911
1912
# Should we return an odd cycle ?
1913
if certificate:
1914
1915
# We build the first half of the cycle, i.e. a
1916
# u-w path
1917
cycle = self.shortest_path(u,w)
1918
1919
# The second half is a v-u path, but there may
1920
# be common vertices in the two paths. But we
1921
# can avoid that !
1922
1923
for v in self.shortest_path(v,u):
1924
if v in cycle:
1925
return False, cycle[cycle.index(v):]
1926
else:
1927
cycle.append(v)
1928
else:
1929
return False
1930
1931
# We color a new vertex
1932
else:
1933
color[w] = c
1934
queue.append(w)
1935
if certificate:
1936
return True, color
1937
else:
1938
return True
1939
1940
def is_triangle_free(self):
1941
r"""
1942
Returns whether ``self`` is triangle-free
1943
1944
EXAMPLE:
1945
1946
The Petersen Graph is triangle-free::
1947
1948
sage: g = graphs.PetersenGraph()
1949
sage: g.is_triangle_free()
1950
True
1951
1952
or a complete Bipartite Graph::
1953
1954
sage: g = graphs.CompleteBipartiteGraph(5,6)
1955
sage: g.is_triangle_free()
1956
True
1957
1958
a tripartite graph, though, contains many triangles ::
1959
1960
sage: g = (3 * graphs.CompleteGraph(5)).complement()
1961
sage: g.is_triangle_free()
1962
False
1963
"""
1964
1965
from sage.graphs.graph_generators import graphs
1966
1967
return (self.subgraph_search(graphs.CompleteGraph(3)) is None)
1968
1969
def is_split(self):
1970
r"""
1971
Returns ``True`` if the graph is a Split graph, ``False`` otherwise.
1972
1973
A Graph `G` is said to be a split graph if its vertices `V(G)`
1974
can be partitioned into two sets `K` and `I` such that the
1975
vertices of `K` induce a complete graphe, and those of `I` are
1976
an independent set.
1977
1978
There is a simple test to check whether a graph is a split
1979
graph (see, for instance, the book "Graph Classes, a survey"
1980
[GraphClasses]_ page 203) :
1981
1982
Given the degree sequence `d_1 \geq ... \geq d_n` of `G`, a graph
1983
is a split graph if and only if :
1984
1985
.. MATH::
1986
1987
\sum_{i=1}^\omega d_i = \omega (\omega - 1) + \sum_{i=\omega + 1}^nd_i
1988
1989
where `\omega = max \{i:d_i\geq i-1\}`.
1990
1991
1992
EXAMPLES:
1993
1994
Split graphs are, in particular, chordal graphs. Hence, The Petersen graph
1995
can not be split::
1996
1997
sage: graphs.PetersenGraph().is_split()
1998
False
1999
2000
We can easily build some "random" split graph by creating a
2001
complete graph, and adding vertices only connected
2002
to some random vertices of the clique::
2003
2004
sage: g = graphs.CompleteGraph(10)
2005
sage: sets = Subsets(Set(range(10)))
2006
sage: for i in range(10, 25):
2007
... g.add_edges([(i,k) for k in sets.random_element()])
2008
sage: g.is_split()
2009
True
2010
2011
REFERENCES:
2012
2013
.. [GraphClasses] A. Brandstadt, VB Le and JP Spinrad
2014
Graph classes: a survey
2015
SIAM Monographs on Discrete Mathematics and Applications},
2016
1999
2017
"""
2018
2019
# our degree sequence is numbered from 0 to n-1, so to avoid
2020
# any mistake, let's fix it :-)
2021
degree_sequence = [0] + sorted(self.degree(), reverse = True)
2022
2023
for (i, d) in enumerate(degree_sequence):
2024
if d >= i - 1:
2025
omega = i
2026
else:
2027
break
2028
2029
left = sum(degree_sequence[:omega + 1])
2030
right = omega * (omega - 1) + sum(degree_sequence[omega + 1:])
2031
2032
return left == right
2033
2034
2035
def is_perfect(self, certificate = False):
2036
r"""
2037
Tests whether the graph is perfect.
2038
2039
A graph `G` is said to be perfect if `\chi(H)=\omega(H)` hold
2040
for any induced subgraph `H\subseteq_i G` (and so for `G`
2041
itself, too), where `\chi(H)` represents the chromatic number
2042
of `H`, and `\omega(H)` its clique number. The Strong Perfect
2043
Graph Theorem [SPGT]_ gives another characterization of
2044
perfect graphs:
2045
2046
A graph is perfect if and only if it contains no odd hole
2047
(cycle on an odd number `k` of vertices, `k>3`) nor any odd
2048
antihole (complement of a hole) as an induced subgraph.
2049
2050
INPUT:
2051
2052
- ``certificate`` (boolean) -- whether to return
2053
a certificate (default : ``False``)
2054
2055
OUTPUT:
2056
2057
When ``certificate = False``, this function returns
2058
a boolean value. When ``certificate = True``, it returns
2059
a subgraph of ``self`` isomorphic to an odd hole or an odd
2060
antihole if any, and ``None`` otherwise.
2061
2062
EXAMPLE:
2063
2064
A Bipartite Graph is always perfect ::
2065
2066
sage: g = graphs.RandomBipartite(8,4,.5)
2067
sage: g.is_perfect()
2068
True
2069
2070
Interval Graphs, which are chordal graphs, too ::
2071
2072
sage: g = graphs.RandomInterval(7)
2073
sage: g.is_perfect()
2074
True
2075
2076
The PetersenGraph, which is triangle-free and
2077
has chromatic number 3 is obviously not perfect::
2078
2079
sage: g = graphs.PetersenGraph()
2080
sage: g.is_perfect()
2081
False
2082
2083
We can obtain an induced 5-cycle as a certificate::
2084
2085
sage: g.is_perfect(certificate = True)
2086
Subgraph of (Petersen graph): Graph on 5 vertices
2087
2088
REFERENCES:
2089
2090
.. [SPGT] M. Chudnovsky, N. Robertson, P. Seymour, R. Thomas.
2091
The strong perfect graph theorem
2092
Annals of Mathematics
2093
vol 164, number 1, pages 51--230
2094
2006
2095
"""
2096
2097
from sage.graphs.bipartite_graph import BipartiteGraph
2098
2099
if isinstance(self, BipartiteGraph) or self.is_bipartite():
2100
return True if not certificate else None
2101
2102
self_complement = self.complement()
2103
2104
start_complement = self_complement.girth()
2105
start_self = self.girth()
2106
2107
from sage.graphs.graph_generators import graphs
2108
2109
2110
# In these cases, we know the graph is no perfect.
2111
if start_self == 5:
2112
if certificate:
2113
return self.subgraph_search(graphs.CycleGraph(5), induced = True)
2114
else:
2115
return False
2116
2117
if start_complement == 5:
2118
if certificate:
2119
return self_complement.subgraph_search(graphs.CycleGraph(5), induced = True).complement()
2120
else:
2121
return False
2122
2123
# We are only looking for odd holes of size at least 5
2124
from sage.rings.finite_rings.integer_mod import Mod
2125
2126
start = lambda x : (x+1) if Mod(x,2) == 0 else ( 5 if x == 3 else x )
2127
2128
# these values are possibly the infinity !!!!
2129
2130
start_self = start(start_self) if start_self <= self.order() else self.order()+2
2131
start_complement = start(start_complement) if start_complement <= self.order() else self.order()+2
2132
2133
counter_example = None
2134
2135
for i in range(min(start_self, start_complement), self.order()+1,2):
2136
2137
# trying in self
2138
if i >= start_self:
2139
counter_example = self.subgraph_search(graphs.CycleGraph(i), induced = True)
2140
2141
if counter_example is not None:
2142
break
2143
2144
# trying in the complement
2145
if i >= start_complement:
2146
counter_example = self.subgraph_search(graphs.CycleGraph(i), induced = True)
2147
if counter_example is not None:
2148
counter_example = counter_example.complement()
2149
break
2150
2151
if certificate:
2152
return counter_example
2153
else:
2154
return counter_example is None
2155
2156
2157
def degree_constrained_subgraph(self, bounds=None, solver=None, verbose=0):
2158
r"""
2159
Returns a degree-constrained subgraph.
2160
2161
Given a graph `G` and two functions `f, g:V(G)\rightarrow \mathbb Z`
2162
such that `f \leq g`, a degree-constrained subgraph in `G` is
2163
a subgraph `G' \subseteq G` such that for any vertex `v \in G`,
2164
`f(v) \leq d_{G'}(v) \leq g(v)`.
2165
2166
INPUT:
2167
2168
- ``bounds`` -- (default: ``None``) Two possibilities:
2169
2170
- A dictionary whose keys are the vertices, and values a pair of
2171
real values ``(min,max)`` corresponding to the values
2172
`(f(v),g(v))`.
2173
2174
- A function associating to each vertex a pair of
2175
real values ``(min,max)`` corresponding to the values
2176
`(f(v),g(v))`.
2177
2178
2179
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
2180
solver to be used. If set to ``None``, the default one is used. For
2181
more information on LP solvers and which default solver is used, see
2182
the method
2183
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
2184
of the class
2185
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
2186
2187
- ``verbose`` -- integer (default: ``0``). Sets the level of
2188
verbosity. Set to 0 by default, which means quiet.
2189
2190
OUTPUT:
2191
2192
- When a solution exists, this method outputs the degree-constained
2193
subgraph as a Graph object.
2194
2195
- When no solution exists, returns ``False``.
2196
2197
.. NOTE::
2198
2199
- This algorithm computes the degree-constrained subgraph of minimum weight.
2200
- If the graph's edges are weighted, these are taken into account.
2201
- This problem can be solved in polynomial time.
2202
2203
EXAMPLES:
2204
2205
Is there a perfect matching in an even cycle? ::
2206
2207
sage: g = graphs.CycleGraph(6)
2208
sage: bounds = lambda x: [1,1]
2209
sage: m = g.degree_constrained_subgraph(bounds=bounds)
2210
sage: m.size()
2211
3
2212
"""
2213
2214
from sage.numerical.mip import MixedIntegerLinearProgram, MIPSolverException, Sum
2215
2216
p = MixedIntegerLinearProgram(maximization=False, solver=solver)
2217
b = p.new_variable()
2218
2219
reorder = lambda x,y: (x,y) if x<y else (y,x)
2220
2221
if bounds is None:
2222
raise ValueError,"The `bounds` keyword can not be equal to None"
2223
elif isinstance(bounds,dict):
2224
f_bounds = lambda x: bounds[x]
2225
else:
2226
f_bounds = bounds
2227
2228
2229
if self.weighted():
2230
from sage.rings.real_mpfr import RR
2231
weight = lambda x: x if x in RR else 1
2232
else:
2233
weight = lambda x: 1
2234
2235
for v in self:
2236
minimum,maximum = f_bounds(v)
2237
p.add_constraint(Sum([ b[reorder(x,y)]*weight(l) for x,y,l in self.edges_incident(v)]), min=minimum, max=maximum)
2238
2239
p.set_objective(Sum([ b[reorder(x,y)]*weight(l) for x,y,l in self.edge_iterator()]))
2240
p.set_binary(b)
2241
2242
try:
2243
p.solve(log=verbose)
2244
g = self.copy()
2245
b = p.get_values(b)
2246
g.delete_edges([(x,y) for x,y,_ in g.edge_iterator() if b[reorder(x,y)] < 0.5])
2247
return g
2248
2249
2250
except MIPSolverException:
2251
return False
2252
2253
2254
### Orientations
2255
2256
def strong_orientation(self):
2257
r"""
2258
Returns a strongly connected orientation of the current graph. See
2259
also the :wikipedia:`Strongly_connected_component`.
2260
2261
An orientation of an undirected graph is a digraph obtained by
2262
giving an unique direction to each of its edges. An orientation
2263
is said to be strong if there is a directed path between each
2264
pair of vertices.
2265
2266
If the graph is 2-edge-connected, a strongly connected orientation
2267
can be found in linear time. If the given graph is not 2-connected,
2268
the orientation returned will ensure that each 2-connected component
2269
has a strongly connected orientation.
2270
2271
OUTPUT:
2272
2273
A digraph representing an orientation of the current graph.
2274
2275
.. NOTE::
2276
2277
- This method assumes the graph is connected.
2278
- This algorithm works in O(m).
2279
2280
EXAMPLE:
2281
2282
For a 2-regular graph, a strong orientation gives to each vertex
2283
an out-degree equal to 1::
2284
2285
sage: g = graphs.CycleGraph(5)
2286
sage: g.strong_orientation().out_degree()
2287
[1, 1, 1, 1, 1]
2288
2289
The Petersen Graph is 2-edge connected. It then has a strongly
2290
connected orientation::
2291
2292
sage: g = graphs.PetersenGraph()
2293
sage: o = g.strong_orientation()
2294
sage: len(o.strongly_connected_components())
2295
1
2296
2297
The same goes for the CubeGraph in any dimension ::
2298
2299
sage: for i in range(2,5):
2300
... g = graphs.CubeGraph(i)
2301
... o = g.strong_orientation()
2302
... len(o.strongly_connected_components())
2303
1
2304
1
2305
1
2306
2307
"""
2308
from sage.graphs.all import DiGraph
2309
d = DiGraph(multiedges=self.allows_multiple_edges())
2310
2311
id = {}
2312
i = 0
2313
2314
# The algorithm works through a depth-first search. Any edge
2315
# used in the depth-first search is oriented in the direction
2316
# in which it has been used. All the other edges are oriented
2317
# backward
2318
2319
v = self.vertex_iterator().next()
2320
seen = {}
2321
i=1
2322
2323
# Time at which the vertices have been discovered
2324
seen[v] = i
2325
2326
# indicates the stack of edges to explore
2327
next = self.edges_incident(v)
2328
2329
while next:
2330
e = next.pop(-1)
2331
# We assume e[0] to be a `seen` vertex
2332
e = e if seen.get(e[0],False) != False else (e[1],e[0],e[2])
2333
2334
# If we discovered a new vertex
2335
if seen.get(e[1],False) == False:
2336
d.add_edge(e)
2337
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])))])
2338
i+=1
2339
seen[e[1]]=i
2340
2341
# Else, we orient the edges backward
2342
else:
2343
if seen[e[0]] < seen[e[1]]:
2344
d.add_edge((e[1],e[0],e[2]))
2345
else:
2346
d.add_edge(e)
2347
2348
# Case of multiple edges. If another edge has already been inserted, we add the new one
2349
# in the opposite direction.
2350
tmp = None
2351
for e in self.multiple_edges():
2352
if tmp == (e[0],e[1]):
2353
if d.has_edge(e[0].e[1]):
2354
d.add_edge(e[1],e[0],e[2])
2355
else:
2356
d.add_edge(e)
2357
tmp = (e[0],e[1])
2358
2359
return d
2360
2361
def bounded_outdegree_orientation(self, bound):
2362
r"""
2363
Computes an orientation of ``self`` such that every vertex `v`
2364
has out-degree less than `b(v)`
2365
2366
INPUT:
2367
2368
- ``bound`` -- Maximum bound on the out-degree. Can be of
2369
three different types :
2370
2371
* An integer `k`. In this case, computes an orientation
2372
whose maximum out-degree is less than `k`.
2373
2374
* A dictionary associating to each vertex its associated
2375
maximum out-degree.
2376
2377
* A function associating to each vertex its associated
2378
maximum out-degree.
2379
2380
OUTPUT:
2381
2382
A DiGraph representing the orientation if it exists. A
2383
``ValueError`` exception is raised otherwise.
2384
2385
ALGORITHM:
2386
2387
The problem is solved through a maximum flow :
2388
2389
Given a graph `G`, we create a ``DiGraph`` `D` defined on
2390
`E(G)\cup V(G)\cup \{s,t\}`. We then link `s` to all of `V(G)`
2391
(these edges having a capacity equal to the bound associated
2392
to each element of `V(G)`), and all the elements of `E(G)` to
2393
`t` . We then link each `v \in V(G)` to each of its incident
2394
edges in `G`. A maximum integer flow of value `|E(G)|`
2395
corresponds to an admissible orientation of `G`. Otherwise,
2396
none exists.
2397
2398
EXAMPLES:
2399
2400
There is always an orientation of a graph `G` such that a
2401
vertex `v` has out-degree at most `\lceil \frac {d(v)} 2
2402
\rceil`::
2403
2404
sage: g = graphs.RandomGNP(40, .4)
2405
sage: b = lambda v : ceil(g.degree(v)/2)
2406
sage: D = g.bounded_outdegree_orientation(b)
2407
sage: all( D.out_degree(v) <= b(v) for v in g )
2408
True
2409
2410
2411
Chvatal's graph, being 4-regular, can be oriented in such a
2412
way that its maximum out-degree is 2::
2413
2414
sage: g = graphs.ChvatalGraph()
2415
sage: D = g.bounded_outdegree_orientation(2)
2416
sage: max(D.out_degree())
2417
2
2418
2419
For any graph `G`, it is possible to compute an orientation
2420
such that the maximum out-degree is at most the maximum
2421
average degree of `G` divided by 2. Anything less, though, is
2422
impossible.
2423
2424
sage: g = graphs.RandomGNP(40, .4)
2425
sage: mad = g.maximum_average_degree()
2426
2427
Hence this is possible ::
2428
2429
sage: d = g.bounded_outdegree_orientation(ceil(mad/2))
2430
2431
While this is not::
2432
2433
sage: try:
2434
... g.bounded_outdegree_orientation(ceil(mad/2-1))
2435
... print "Error"
2436
... except ValueError:
2437
... pass
2438
2439
TESTS:
2440
2441
As previously for random graphs, but more intensively::
2442
2443
sage: for i in xrange(30): # long time (up to 6s on sage.math, 2012)
2444
... g = graphs.RandomGNP(40, .4)
2445
... b = lambda v : ceil(g.degree(v)/2)
2446
... D = g.bounded_outdegree_orientation(b)
2447
... if not (
2448
... all( D.out_degree(v) <= b(v) for v in g ) or
2449
... D.size() != g.size()):
2450
... print "Something wrong happened"
2451
2452
"""
2453
from sage.graphs.all import DiGraph
2454
n = self.order()
2455
2456
if n == 0:
2457
return DiGraph()
2458
2459
vertices = self.vertices()
2460
vertices_id = dict(map(lambda (x,y):(y,x), list(enumerate(vertices))))
2461
2462
b = {}
2463
2464
2465
# Checking the input type. We make a dictionay out of it
2466
if isinstance(bound, dict):
2467
b = bound
2468
else:
2469
try:
2470
b = dict(zip(vertices,map(bound, vertices)))
2471
2472
except TypeError:
2473
b = dict(zip(vertices, [bound]*n))
2474
2475
d = DiGraph()
2476
2477
# Adding the edges (s,v) and ((u,v),t)
2478
d.add_edges( ('s', vertices_id[v], b[v]) for v in vertices)
2479
2480
d.add_edges( ((vertices_id[u], vertices_id[v]), 't', 1)
2481
for (u,v) in self.edges(labels=None) )
2482
2483
# each v is linked to its incident edges
2484
2485
for u,v in self.edges(labels = None):
2486
u,v = vertices_id[u], vertices_id[v]
2487
d.add_edge(u, (u,v), 1)
2488
d.add_edge(v, (u,v), 1)
2489
2490
# Solving the maximum flow
2491
value, flow = d.flow('s','t', value_only = False, integer = True, use_edge_labels = True)
2492
2493
if value != self.size():
2494
raise ValueError("No orientation exists for the given bound")
2495
2496
D = DiGraph()
2497
D.add_vertices(vertices)
2498
2499
# The flow graph may not contain all the vertices, if they are
2500
# not part of the flow...
2501
2502
for u in [x for x in range(n) if x in flow]:
2503
2504
for (uu,vv) in flow.neighbors_out(u):
2505
v = vv if vv != u else uu
2506
D.add_edge(vertices[u], vertices[v])
2507
2508
# I do not like when a method destroys the embedding ;-)
2509
2510
D.set_pos(self.get_pos())
2511
2512
return D
2513
2514
2515
### Coloring
2516
2517
def bipartite_color(self):
2518
"""
2519
Returns a dictionary with vertices as the keys and the color class
2520
as the values. Fails with an error if the graph is not bipartite.
2521
2522
EXAMPLES::
2523
2524
sage: graphs.CycleGraph(4).bipartite_color()
2525
{0: 1, 1: 0, 2: 1, 3: 0}
2526
sage: graphs.CycleGraph(5).bipartite_color()
2527
Traceback (most recent call last):
2528
...
2529
RuntimeError: Graph is not bipartite.
2530
"""
2531
isit, certificate = self.is_bipartite(certificate = True)
2532
2533
if isit:
2534
return certificate
2535
else:
2536
raise RuntimeError("Graph is not bipartite.")
2537
2538
def bipartite_sets(self):
2539
"""
2540
Returns `(X,Y)` where `X` and `Y` are the nodes in each bipartite set of
2541
graph `G`. Fails with an error if graph is not bipartite.
2542
2543
EXAMPLES::
2544
2545
sage: graphs.CycleGraph(4).bipartite_sets()
2546
(set([0, 2]), set([1, 3]))
2547
sage: graphs.CycleGraph(5).bipartite_sets()
2548
Traceback (most recent call last):
2549
...
2550
RuntimeError: Graph is not bipartite.
2551
"""
2552
color = self.bipartite_color()
2553
left = set([])
2554
right = set([])
2555
2556
for u,s in color.iteritems():
2557
if s:
2558
left.add(u)
2559
else:
2560
right.add(u)
2561
2562
return left, right
2563
2564
def chromatic_polynomial(self):
2565
"""
2566
Returns the chromatic polynomial of the graph G.
2567
2568
EXAMPLES::
2569
2570
sage: G = Graph({0:[1,2,3],1:[2]})
2571
sage: factor(G.chromatic_polynomial())
2572
(x - 2) * x * (x - 1)^2
2573
2574
::
2575
2576
sage: g = graphs.trees(5).next()
2577
sage: g.chromatic_polynomial().factor()
2578
x * (x - 1)^4
2579
2580
::
2581
2582
sage: seven_acre_wood = sum(graphs.trees(7), Graph())
2583
sage: seven_acre_wood.chromatic_polynomial()
2584
x^77 - 66*x^76 ... - 2515943049305400*x^60 ... - 66*x^12 + x^11
2585
2586
::
2587
2588
sage: for i in range(2,7):
2589
... graphs.CompleteGraph(i).chromatic_polynomial().factor()
2590
(x - 1) * x
2591
(x - 2) * (x - 1) * x
2592
(x - 3) * (x - 2) * (x - 1) * x
2593
(x - 4) * (x - 3) * (x - 2) * (x - 1) * x
2594
(x - 5) * (x - 4) * (x - 3) * (x - 2) * (x - 1) * x
2595
"""
2596
from sage.graphs.chrompoly import chromatic_polynomial
2597
return chromatic_polynomial(self)
2598
2599
def chromatic_number(self, algorithm="DLX", verbose = 0):
2600
r"""
2601
Returns the minimal number of colors needed to color the vertices
2602
of the graph `G`.
2603
2604
INPUT:
2605
2606
- ``algorithm`` -- Select an algorithm from the following supported
2607
algorithms:
2608
2609
- If ``algorithm="DLX"`` (default), the chromatic number is
2610
computed using the dancing link algorithm. It is
2611
inefficient speedwise to compute the chromatic number through
2612
the dancing link algorithm because this algorithm computes
2613
*all* the possible colorings to check that one exists.
2614
2615
- If ``algorithm="CP"``, the chromatic number is computed
2616
using the coefficients of the chromatic polynomial. Again, this
2617
method is inefficient in terms of speed and it only useful for
2618
small graphs.
2619
2620
- If ``algorithm="MILP"``, the chromatic number is computed using a
2621
mixed integer linear program. The performance of this implementation
2622
is affected by whether optional MILP solvers have been installed
2623
(see the :mod:`MILP module <sage.numerical.mip>`, or Sage's tutorial
2624
on Linear Programming).
2625
2626
- ``verbose`` -- integer (default: ``0``). Sets the level of verbosity
2627
for the MILP algorithm. Its default value is 0, which means *quiet*.
2628
2629
.. SEEALSO::
2630
2631
For more functions related to graph coloring, see the
2632
module :mod:`sage.graphs.graph_coloring`.
2633
2634
EXAMPLES::
2635
2636
sage: G = Graph({0: [1, 2, 3], 1: [2]})
2637
sage: G.chromatic_number(algorithm="DLX")
2638
3
2639
sage: G.chromatic_number(algorithm="MILP")
2640
3
2641
sage: G.chromatic_number(algorithm="CP")
2642
3
2643
2644
TESTS::
2645
2646
sage: G = Graph({0: [1, 2, 3], 1: [2]})
2647
sage: G.chromatic_number(algorithm="foo")
2648
Traceback (most recent call last):
2649
...
2650
ValueError: The 'algorithm' keyword must be set to either 'DLX', 'MILP' or 'CP'.
2651
"""
2652
# default built-in algorithm; bad performance
2653
if algorithm == "DLX":
2654
from sage.graphs.graph_coloring import chromatic_number
2655
return chromatic_number(self)
2656
# Algorithm with good performance, but requires an optional
2657
# package: choose any of GLPK or CBC.
2658
elif algorithm == "MILP":
2659
from sage.graphs.graph_coloring import vertex_coloring
2660
return vertex_coloring(self, value_only=True, verbose = verbose)
2661
# another algorithm with bad performance; only good for small graphs
2662
elif algorithm == "CP":
2663
f = self.chromatic_polynomial()
2664
i = 0
2665
while f(i) == 0:
2666
i += 1
2667
return i
2668
else:
2669
raise ValueError("The 'algorithm' keyword must be set to either 'DLX', 'MILP' or 'CP'.")
2670
2671
def coloring(self, algorithm="DLX", hex_colors=False, verbose = 0):
2672
r"""
2673
Returns the first (optimal) proper vertex-coloring found.
2674
2675
INPUT:
2676
2677
- ``algorithm`` -- Select an algorithm from the following supported
2678
algorithms:
2679
2680
- If ``algorithm="DLX"`` (default), the coloring is computed using the
2681
dancing link algorithm.
2682
2683
- If ``algorithm="MILP"``, the coloring is computed using a mixed
2684
integer linear program. The performance of this implementation is
2685
affected by whether optional MILP solvers have been installed (see
2686
the :mod:`MILP module <sage.numerical.mip>`).
2687
2688
- ``hex_colors`` -- (default: ``False``) if ``True``, return a
2689
dictionary which can easily be used for plotting.
2690
2691
- ``verbose`` -- integer (default: ``0``). Sets the level of verbosity
2692
for the MILP algorithm. Its default value is 0, which means *quiet*.
2693
2694
.. SEEALSO::
2695
2696
For more functions related to graph coloring, see the
2697
module :mod:`sage.graphs.graph_coloring`.
2698
2699
EXAMPLES::
2700
2701
sage: G = Graph("Fooba")
2702
sage: P = G.coloring(algorithm="MILP"); P
2703
[[2, 1, 3], [0, 6, 5], [4]]
2704
sage: P = G.coloring(algorithm="DLX"); P
2705
[[1, 2, 3], [0, 5, 6], [4]]
2706
sage: G.plot(partition=P)
2707
sage: H = G.coloring(hex_colors=True, algorithm="MILP")
2708
sage: for c in sorted(H.keys()):
2709
... print c, H[c]
2710
#0000ff [4]
2711
#00ff00 [0, 6, 5]
2712
#ff0000 [2, 1, 3]
2713
sage: H = G.coloring(hex_colors=True, algorithm="DLX")
2714
sage: for c in sorted(H.keys()):
2715
... print c, H[c]
2716
#0000ff [4]
2717
#00ff00 [1, 2, 3]
2718
#ff0000 [0, 5, 6]
2719
sage: G.plot(vertex_colors=H)
2720
2721
TESTS::
2722
2723
sage: G.coloring(algorithm="foo")
2724
Traceback (most recent call last):
2725
...
2726
ValueError: The 'algorithm' keyword must be set to either 'DLX' or 'MILP'.
2727
"""
2728
if algorithm == "MILP":
2729
from sage.graphs.graph_coloring import vertex_coloring
2730
return vertex_coloring(self, hex_colors=hex_colors, verbose = verbose)
2731
elif algorithm == "DLX":
2732
from sage.graphs.graph_coloring import first_coloring
2733
return first_coloring(self, hex_colors=hex_colors)
2734
else:
2735
raise ValueError("The 'algorithm' keyword must be set to either 'DLX' or 'MILP'.")
2736
2737
def fractional_chromatic_index(self, verbose_constraints = 0, verbose = 0):
2738
r"""
2739
Computes the fractional chromatic index of ``self``
2740
2741
The fractional chromatic index is a relaxed version of edge-coloring. An
2742
edge coloring of a graph being actually a covering of its edges into the
2743
smallest possible number of matchings, the fractional chromatic index of
2744
a graph `G` is the smallest real value `\chi_f(G)` such that there
2745
exists a list of matchings `M_1, ..., M_k` of `G` and coefficients
2746
`\alpha_1, ..., \alpha_k` with the property that each edge is covered by
2747
the matchings in the following relaxed way
2748
2749
.. MATH::
2750
2751
\forall e \in E(G), \sum_{e \in M_i} \alpha_i \geq 1
2752
2753
For more information, see the `Wikipedia article on fractional coloring
2754
<http://en.wikipedia.org/wiki/Fractional_coloring>`_.
2755
2756
ALGORITHM:
2757
2758
The fractional chromatic index is computed through Linear Programming
2759
through its dual. The LP solved by sage is actually:
2760
2761
.. MATH::
2762
2763
\mbox{Maximize : }&\sum_{e\in E(G)} r_{e}\\
2764
\mbox{Such that : }&\\
2765
&\forall M\text{ matching }\subseteq G, \sum_{e\in M}r_{v}\leq 1\\
2766
2767
INPUT:
2768
2769
- ``verbose_constraints`` -- whether to display which constraints are
2770
being generated.
2771
2772
- ``verbose`` -- level of verbosity required from the LP solver
2773
2774
.. NOTE::
2775
2776
This implementation can be improved by computing matchings through a
2777
LP formulation, and not using the Python implementation of Edmonds'
2778
algorithm (which requires to copy the graph, etc). It may be more
2779
efficient to write the matching problem as a LP, as we would then
2780
just have to update the weights on the edges between each call to
2781
``solve`` (and so avoiding the generation of all the constraints).
2782
2783
2784
EXAMPLE:
2785
2786
The fractional chromatic index of a `C_5` is `5/2`::
2787
2788
sage: g = graphs.CycleGraph(5)
2789
sage: g.fractional_chromatic_index()
2790
2.5
2791
"""
2792
2793
from sage.numerical.mip import MixedIntegerLinearProgram, Sum
2794
2795
g = self.copy()
2796
p = MixedIntegerLinearProgram(constraint_generation = True)
2797
2798
# One variable per edge
2799
r = p.new_variable(dim = 2)
2800
R = lambda x,y : r[x][y] if x<y else r[y][x]
2801
2802
# We want to maximize the sum of weights on the edges
2803
p.set_objective( Sum( R(u,v) for u,v in g.edges(labels = False)))
2804
2805
# Each edge being by itself a matching, its weight can not be more than
2806
# 1
2807
2808
for u,v in g.edges(labels = False):
2809
p.add_constraint( R(u,v), max = 1)
2810
2811
obj = p.solve(log = verbose)
2812
2813
while True:
2814
2815
# Updating the value on the edges of g
2816
for u,v in g.edges(labels = False):
2817
g.set_edge_label(u,v,p.get_values(R(u,v)))
2818
2819
# Computing a matching of maximum weight...
2820
2821
matching = g.matching()
2822
2823
# If the maximum matching has weight at most 1, we are done !
2824
if sum(map(lambda x:x[2],matching)) <= 1:
2825
break
2826
2827
# Otherwise, we add a new constraint
2828
2829
if verbose_constraints:
2830
print "Adding a constraint on matching : ",matching
2831
2832
p.add_constraint( Sum( R(u,v) for u,v,_ in matching), max = 1)
2833
2834
# And solve again
2835
obj = p.solve(log = verbose)
2836
2837
# Accomplished !
2838
return obj
2839
2840
def independent_set_of_representatives(self, family, solver=None, verbose=0):
2841
r"""
2842
Returns an independent set of representatives.
2843
2844
Given a graph `G` and and a family `F=\{F_i:i\in [1,...,k]\}` of
2845
subsets of ``g.vertices()``, an Independent Set of Reprersentatives
2846
(ISR) is an assignation of a vertex `v_i\in F_i` to each set `F_i`
2847
such that `v_i != v_j` if `i<j` (they are represdentatives) and the
2848
set `\cup_{i}v_i` is an independent set in `G`.
2849
2850
It generalizes, for example, graph coloring and graph list coloring.
2851
2852
(See [AhaBerZiv07]_ for more information.)
2853
2854
INPUT:
2855
2856
- ``family`` -- A list of lists defining the family `F`
2857
(actually, a Family of subsets of ``G.vertices()``).
2858
2859
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
2860
solver to be used. If set to ``None``, the default one is used. For
2861
more information on LP solvers and which default solver is used, see
2862
the method
2863
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
2864
of the class
2865
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
2866
2867
- ``verbose`` -- integer (default: ``0``). Sets the level of
2868
verbosity. Set to 0 by default, which means quiet.
2869
2870
OUTPUT:
2871
2872
- A list whose `i^{\mbox{th}}` element is the representativeof the
2873
`i^{\mbox{th}}` element of the ``family`` list. If there is no ISR,
2874
``None`` is returned.
2875
2876
EXAMPLES:
2877
2878
For a bipartite graph missing one edge, the solution is as expected::
2879
2880
sage: g = graphs.CompleteBipartiteGraph(3,3)
2881
sage: g.delete_edge(1,4)
2882
sage: g.independent_set_of_representatives([[0,1,2],[3,4,5]])
2883
[1, 4]
2884
2885
The Petersen Graph is 3-colorable, which can be expressed as an
2886
independent set of representatives problem : take 3 disjoint copies
2887
of the Petersen Graph, each one representing one color. Then take
2888
as a partition of the set of vertices the family defined by the three
2889
copies of each vertex. The ISR of such a family
2890
defines a 3-coloring::
2891
2892
sage: g = 3 * graphs.PetersenGraph()
2893
sage: n = g.order()/3
2894
sage: f = [[i,i+n,i+2*n] for i in xrange(n)]
2895
sage: isr = g.independent_set_of_representatives(f)
2896
sage: c = [floor(i/n) for i in isr]
2897
sage: color_classes = [[],[],[]]
2898
sage: for v,i in enumerate(c):
2899
... color_classes[i].append(v)
2900
sage: for classs in color_classes:
2901
... g.subgraph(classs).size() == 0
2902
True
2903
True
2904
True
2905
2906
REFERENCE:
2907
2908
.. [AhaBerZiv07] R. Aharoni and E. Berger and R. Ziv
2909
Independent systems of representatives in weighted graphs
2910
Combinatorica vol 27, num 3, p253--267
2911
2007
2912
2913
"""
2914
2915
from sage.numerical.mip import MixedIntegerLinearProgram, Sum
2916
p=MixedIntegerLinearProgram(solver=solver)
2917
2918
# Boolean variable indicating whether the vertex
2919
# is the representative of some set
2920
vertex_taken=p.new_variable()
2921
2922
# Boolean variable in two dimension whose first
2923
# element is a vertex and whose second element
2924
# is one of the sets given as arguments.
2925
# When true, indicated that the vertex is the representent
2926
# of the corresponding set
2927
2928
classss=p.new_variable(dim=2)
2929
2930
# Associates to the vertices the classes
2931
# to which they belong
2932
2933
lists=dict([(v,[]) for v in self.vertex_iterator()])
2934
for i,f in enumerate(family):
2935
[lists[v].append(i) for v in f]
2936
2937
# a classss has exactly one representant
2938
p.add_constraint(Sum([classss[v][i] for v in f]),max=1,min=1)
2939
2940
# A vertex represents at most one classss (vertex_taken is binary), and
2941
# vertex_taken[v]==1 if v is the representative of some classss
2942
2943
[p.add_constraint(Sum([classss[v][i] for i in lists[v]])-vertex_taken[v],max=0) for v in self.vertex_iterator()]
2944
2945
# Two adjacent vertices can not both be representants of a set
2946
2947
for (u,v) in self.edges(labels=None):
2948
p.add_constraint(vertex_taken[u]+vertex_taken[v],max=1)
2949
2950
p.set_objective(None)
2951
2952
p.set_binary(vertex_taken)
2953
p.set_binary(classss)
2954
2955
try:
2956
p.solve(log=verbose)
2957
except:
2958
return None
2959
2960
classss=p.get_values(classss)
2961
2962
repr=[]
2963
for i,f in enumerate(family):
2964
for v in f:
2965
if classss[v][i]==1:
2966
repr.append(v)
2967
break
2968
2969
return repr
2970
2971
def minor(self, H, solver=None, verbose=0):
2972
r"""
2973
Returns the vertices of a minor isomorphic to `H` in the current graph.
2974
2975
We say that a graph `G` has a `H`-minor (or that it has
2976
a graph isomorphic to `H` as a minor), if for all `h\in H`,
2977
there exist disjoint sets `S_h \subseteq V(G)` such that
2978
once the vertices of each `S_h` have been merged to create
2979
a new graph `G'`, this new graph contains `H` as a subgraph.
2980
2981
For more information, see the
2982
`Wikipedia article on graph minor <http://en.wikipedia.org/wiki/Minor_%28graph_theory%29>`_.
2983
2984
INPUT:
2985
2986
- ``H`` -- The minor to find for in the current graph.
2987
2988
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
2989
solver to be used. If set to ``None``, the default one is used. For
2990
more information on LP solvers and which default solver is used, see
2991
the method
2992
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
2993
of the class
2994
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
2995
2996
- ``verbose`` -- integer (default: ``0``). Sets the level of
2997
verbosity. Set to 0 by default, which means quiet.
2998
2999
OUTPUT:
3000
3001
A dictionary associating to each vertex of `H` the set of vertices
3002
in the current graph representing it.
3003
3004
ALGORITHM:
3005
3006
Mixed Integer Linear Programming
3007
3008
COMPLEXITY:
3009
3010
Theoretically, when `H` is fixed, testing for the existence of
3011
a `H`-minor is polynomial. The known algorithms are highly
3012
exponential in `H`, though.
3013
3014
.. NOTE::
3015
3016
This function can be expected to be *very* slow, especially
3017
where the minor does not exist.
3018
3019
EXAMPLES:
3020
3021
Trying to find a minor isomorphic to `K_4` in
3022
the `4\times 4` grid::
3023
3024
sage: g = graphs.GridGraph([4,4])
3025
sage: h = graphs.CompleteGraph(4)
3026
sage: L = g.minor(h)
3027
sage: gg = g.subgraph(flatten(L.values(), max_level = 1))
3028
sage: _ = [gg.merge_vertices(l) for l in L.values() if len(l)>1]
3029
sage: gg.is_isomorphic(h)
3030
True
3031
3032
We can also try to prove this way that the Petersen graph
3033
is not planar, as it has a `K_5` minor::
3034
3035
sage: g = graphs.PetersenGraph()
3036
sage: K5_minor = g.minor(graphs.CompleteGraph(5)) # long time
3037
3038
And even a `K_{3,3}` minor::
3039
3040
sage: K33_minor = g.minor(graphs.CompleteBipartiteGraph(3,3)) # long time
3041
3042
(It is much faster to use the linear-time test of
3043
planarity in this situation, though.)
3044
3045
As there is no cycle in a tree, looking for a `K_3` minor is useless.
3046
This function will raise an exception in this case::
3047
3048
sage: g = graphs.RandomGNP(20,.5)
3049
sage: g = g.subgraph(edges = g.min_spanning_tree())
3050
sage: g.is_tree()
3051
True
3052
sage: L = g.minor(graphs.CompleteGraph(3))
3053
Traceback (most recent call last):
3054
...
3055
ValueError: This graph has no minor isomorphic to H !
3056
"""
3057
3058
from sage.numerical.mip import MixedIntegerLinearProgram, MIPSolverException, Sum
3059
p = MixedIntegerLinearProgram(solver=solver)
3060
3061
# sorts an edge
3062
S = lambda (x,y) : (x,y) if x<y else (y,x)
3063
3064
# rs = Representative set of a vertex
3065
# for h in H, v in G is such that rs[h][v] == 1 if and only if v
3066
# is a representant of h in self
3067
rs = p.new_variable(dim=2)
3068
3069
for v in self:
3070
p.add_constraint(Sum([rs[h][v] for h in H]), max = 1)
3071
3072
# We ensure that the set of representatives of a
3073
# vertex h contains a tree, and thus is connected
3074
3075
# edges represents the edges of the tree
3076
edges = p.new_variable(dim = 2)
3077
3078
# there can be a edge for h between two vertices
3079
# only if those vertices represent h
3080
for u,v in self.edges(labels=None):
3081
for h in H:
3082
p.add_constraint(edges[h][S((u,v))] - rs[h][u], max = 0 )
3083
p.add_constraint(edges[h][S((u,v))] - rs[h][v], max = 0 )
3084
3085
# The number of edges of the tree in h is exactly the cardinal
3086
# of its representative set minus 1
3087
3088
for h in H:
3089
p.add_constraint(Sum([edges[h][S(e)] for e in self.edges(labels=None)])-Sum([rs[h][v] for v in self]), min=-1, max=-1)
3090
3091
# a tree has no cycle
3092
epsilon = 1/(5*Integer(self.order()))
3093
r_edges = p.new_variable(dim=2)
3094
3095
for h in H:
3096
for u,v in self.edges(labels=None):
3097
p.add_constraint(r_edges[h][(u,v)] + r_edges[h][(v,u)] - edges[h][S((u,v))], min = 0)
3098
3099
for v in self:
3100
p.add_constraint(Sum([r_edges[h][(u,v)] for u in self.neighbors(v)]), max = 1-epsilon)
3101
3102
# Once the representative sets are described, we must ensure
3103
# there are arcs corresponding to those of H between them
3104
h_edges = p.new_variable(dim=2)
3105
3106
for h1, h2 in H.edges(labels=None):
3107
3108
for v1, v2 in self.edges(labels=None):
3109
3110
p.add_constraint(h_edges[(h1,h2)][S((v1,v2))] - rs[h2][v2], max = 0)
3111
p.add_constraint(h_edges[(h1,h2)][S((v1,v2))] - rs[h1][v1], max = 0)
3112
3113
p.add_constraint(h_edges[(h2,h1)][S((v1,v2))] - rs[h1][v2], max = 0)
3114
p.add_constraint(h_edges[(h2,h1)][S((v1,v2))] - rs[h2][v1], max = 0)
3115
3116
p.add_constraint(Sum([h_edges[(h1,h2)][S(e)] + h_edges[(h2,h1)][S(e)] for e in self.edges(labels=None) ]), min = 1)
3117
3118
p.set_binary(rs)
3119
p.set_binary(edges)
3120
3121
p.set_objective(None)
3122
3123
try:
3124
p.solve(log=verbose)
3125
except MIPSolverException:
3126
raise ValueError("This graph has no minor isomorphic to H !")
3127
3128
rs = p.get_values(rs)
3129
3130
rs_dict = {}
3131
for h in H:
3132
rs_dict[h] = [v for v in self if rs[h][v]==1]
3133
3134
return rs_dict
3135
3136
3137
def rank_decomposition(self, verbose = False):
3138
"""
3139
Returns an rank-decomposition of ``self`` achieving optiml rank-width.
3140
3141
See the documentation of the ``rankwidth`` module
3142
:mod:`rankwidth <sage.graphs.graph_decompositions.rankwidth>`.
3143
3144
INPUT:
3145
3146
- ``verbose`` (boolean) -- whether to display progress information while
3147
computing the decomposition.
3148
3149
OUTPUT:
3150
3151
A pair ``(rankwidth, decomposition_tree)``, where ``rankwidth`` is a
3152
numerical value and ``decomposition_tree`` is a ternary tree describing
3153
the decomposition (cf. the module's documentation).
3154
3155
See the documentation of the ``rankwidth`` module for more information
3156
on the tree
3157
:mod:`rankwidth <sage.graphs.graph_decompositions.rankwidth>`.
3158
3159
.. WARNING::
3160
3161
The current implementation cannot handle graphs of more than 32 vertices.
3162
3163
EXAMPLE::
3164
3165
sage: g = graphs.PetersenGraph()
3166
sage: rw, tree = g.rank_decomposition()
3167
sage: rw
3168
3
3169
sage: tree
3170
Graph on 19 vertices
3171
sage: tree.is_tree()
3172
True
3173
"""
3174
3175
from sage.graphs.graph_decompositions.rankwidth import rank_decomposition
3176
return rank_decomposition(self, verbose = verbose)
3177
3178
### Matching
3179
3180
def matching_polynomial(self, complement=True, name=None):
3181
"""
3182
Computes the matching polynomial of the graph `G`.
3183
3184
If `p(G, k)` denotes the number of `k`-matchings (matchings with `k`
3185
edges) in `G`, then the matching polynomial is defined as [Godsil93]_:
3186
3187
.. MATH::
3188
3189
\mu(x)=\sum_{k \geq 0} (-1)^k p(G,k) x^{n-2k}
3190
3191
3192
INPUT:
3193
3194
- ``complement`` - (default: ``True``) whether to use Godsil's duality
3195
theorem to compute the matching polynomial from that of the graphs
3196
complement (see ALGORITHM).
3197
3198
- ``name`` - optional string for the variable name in the polynomial
3199
3200
.. NOTE::
3201
3202
The ``complement`` option uses matching polynomials of complete
3203
graphs, which are cached. So if you are crazy enough to try
3204
computing the matching polynomial on a graph with millions of
3205
vertices, you might not want to use this option, since it will end
3206
up caching millions of polynomials of degree in the millions.
3207
3208
ALGORITHM:
3209
3210
The algorithm used is a recursive one, based on the following
3211
observation [Godsil93]_:
3212
3213
- If `e` is an edge of `G`, `G'` is the result of deleting the edge `e`,
3214
and `G''` is the result of deleting each vertex in `e`, then the
3215
matching polynomial of `G` is equal to that of `G'` minus that of
3216
`G''`.
3217
3218
(the algorithm actually computes the *signless* matching polynomial,
3219
for which the recursion is the same when one replaces the substraction
3220
by an addition. It is then converted into the matching polynomial and
3221
returned)
3222
3223
Depending on the value of ``complement``, Godsil's duality theorem
3224
[Godsil93]_ can also be used to compute `\mu(x)` :
3225
3226
.. MATH::
3227
3228
\mu(\overline{G}, x) = \sum_{k \geq 0} p(G,k) \mu( K_{n-2k}, x)
3229
3230
3231
Where `\overline{G}` is the complement of `G`, and `K_n` the complete
3232
graph on `n` vertices.
3233
3234
EXAMPLES::
3235
3236
sage: g = graphs.PetersenGraph()
3237
sage: g.matching_polynomial()
3238
x^10 - 15*x^8 + 75*x^6 - 145*x^4 + 90*x^2 - 6
3239
sage: g.matching_polynomial(complement=False)
3240
x^10 - 15*x^8 + 75*x^6 - 145*x^4 + 90*x^2 - 6
3241
sage: g.matching_polynomial(name='tom')
3242
tom^10 - 15*tom^8 + 75*tom^6 - 145*tom^4 + 90*tom^2 - 6
3243
sage: g = Graph()
3244
sage: L = [graphs.RandomGNP(8, .3) for i in [1..5]]
3245
sage: prod([h.matching_polynomial() for h in L]) == sum(L, g).matching_polynomial() # long time (7s on sage.math, 2011)
3246
True
3247
"""
3248
from matchpoly import matching_polynomial
3249
return matching_polynomial(self, complement=complement, name=name)
3250
3251
### Convexity
3252
3253
def convexity_properties(self):
3254
r"""
3255
Returns a ``ConvexityProperties`` objet corresponding to ``self``.
3256
3257
This object contains the methods related to convexity in graphs (convex
3258
hull, hull number) and caches useful information so that it becomes
3259
comparatively cheaper to compute the convex hull of many different sets
3260
of the same graph.
3261
3262
.. SEEALSO::
3263
3264
In order to know what can be done through this object, please refer
3265
to module :mod:`sage.graphs.convexity_properties`.
3266
3267
.. NOTE::
3268
3269
If you want to compute many convex hulls, keep this object in memory
3270
! When it is created, it builds a table of useful information to
3271
compute convex hulls. As a result ::
3272
3273
sage: g = graphs.PetersenGraph()
3274
sage: g.convexity_properties().hull([1, 3])
3275
[1, 2, 3]
3276
sage: g.convexity_properties().hull([3, 7])
3277
[2, 3, 7]
3278
3279
Is a terrible waste of computations, while ::
3280
3281
sage: g = graphs.PetersenGraph()
3282
sage: CP = g.convexity_properties()
3283
sage: CP.hull([1, 3])
3284
[1, 2, 3]
3285
sage: CP.hull([3, 7])
3286
[2, 3, 7]
3287
3288
Makes perfect sense.
3289
"""
3290
from sage.graphs.convexity_properties import ConvexityProperties
3291
return ConvexityProperties(self)
3292
3293
### Centrality
3294
3295
def centrality_betweenness(self, normalized=True):
3296
r"""
3297
Returns the betweenness centrality (fraction of number of shortest
3298
paths that go through each vertex) as a dictionary keyed by
3299
vertices. The betweenness is normalized by default to be in range
3300
(0,1). This wraps NetworkX's implementation of the algorithm
3301
described in [Brandes2003]_.
3302
3303
Measures of the centrality of a vertex within a graph determine the
3304
relative importance of that vertex to its graph. Vertices that
3305
occur on more shortest paths between other vertices have higher
3306
betweenness than vertices that occur on less.
3307
3308
INPUT:
3309
3310
3311
- ``normalized`` - boolean (default True) - if set to
3312
False, result is not normalized.
3313
3314
3315
REFERENCE:
3316
3317
.. [Brandes2003] Ulrik Brandes. (2003). Faster Evaluation of
3318
Shortest-Path Based Centrality Indices. [Online] Available:
3319
http://citeseer.nj.nec.com/brandes00faster.html
3320
3321
EXAMPLES::
3322
3323
sage: (graphs.ChvatalGraph()).centrality_betweenness()
3324
{0: 0.06969696969696969, 1: 0.06969696969696969, 2: 0.0606060606060606, 3: 0.0606060606060606, 4: 0.06969696969696969, 5: 0.06969696969696969, 6: 0.0606060606060606, 7: 0.0606060606060606, 8: 0.0606060606060606, 9: 0.0606060606060606, 10: 0.0606060606060606, 11: 0.0606060606060606}
3325
sage: (graphs.ChvatalGraph()).centrality_betweenness(normalized=False)
3326
{0: 3.833333333333333, 1: 3.833333333333333, 2: 3.333333333333333, 3: 3.333333333333333, 4: 3.833333333333333, 5: 3.833333333333333, 6: 3.333333333333333, 7: 3.333333333333333, 8: 3.333333333333333, 9: 3.333333333333333, 10: 3.333333333333333, 11: 3.333333333333333}
3327
sage: D = DiGraph({0:[1,2,3], 1:[2], 3:[0,1]})
3328
sage: D.show(figsize=[2,2])
3329
sage: D = D.to_undirected()
3330
sage: D.show(figsize=[2,2])
3331
sage: D.centrality_betweenness()
3332
{0: 0.16666666666666666, 1: 0.16666666666666666, 2: 0.0, 3: 0.0}
3333
"""
3334
import networkx
3335
return networkx.betweenness_centrality(self.networkx_graph(copy=False), normalized)
3336
3337
def centrality_degree(self, v=None):
3338
r"""
3339
Returns the degree centrality (fraction of vertices connected to)
3340
as a dictionary of values keyed by vertex. The degree centrality is
3341
normalized to be in range (0,1).
3342
3343
Measures of the centrality of a vertex within a graph determine the
3344
relative importance of that vertex to its graph. Degree centrality
3345
measures the number of links incident upon a vertex.
3346
3347
INPUT:
3348
3349
3350
- ``v`` - a vertex label (to find degree centrality of
3351
only one vertex)
3352
3353
3354
EXAMPLES::
3355
3356
sage: (graphs.ChvatalGraph()).centrality_degree()
3357
{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}
3358
sage: D = DiGraph({0:[1,2,3], 1:[2], 3:[0,1]})
3359
sage: D.show(figsize=[2,2])
3360
sage: D = D.to_undirected()
3361
sage: D.show(figsize=[2,2])
3362
sage: D.centrality_degree()
3363
{0: 1.0, 1: 1.0, 2: 0.6666666666666666, 3: 0.6666666666666666}
3364
sage: D.centrality_degree(v=1)
3365
1.0
3366
"""
3367
import networkx
3368
if v==None:
3369
return networkx.degree_centrality(self.networkx_graph(copy=False))
3370
else:
3371
return networkx.degree_centrality(self.networkx_graph(copy=False))[v]
3372
3373
def centrality_closeness(self, v=None):
3374
r"""
3375
Returns the closeness centrality (1/average distance to all
3376
vertices) as a dictionary of values keyed by vertex. The degree
3377
centrality is normalized to be in range (0,1).
3378
3379
Measures of the centrality of a vertex within a graph determine the
3380
relative importance of that vertex to its graph. 'Closeness
3381
centrality may be defined as the total graph-theoretic distance of
3382
a given vertex from all other vertices... Closeness is an inverse
3383
measure of centrality in that a larger value indicates a less
3384
central actor while a smaller value indicates a more central
3385
actor,' [Borgatti95]_.
3386
3387
INPUT:
3388
3389
3390
- ``v`` - a vertex label (to find degree centrality of
3391
only one vertex)
3392
3393
3394
REFERENCE:
3395
3396
.. [Borgatti95] Stephen P Borgatti. (1995). Centrality and AIDS.
3397
[Online] Available:
3398
http://www.analytictech.com/networks/centaids.htm
3399
3400
EXAMPLES::
3401
3402
sage: (graphs.ChvatalGraph()).centrality_closeness()
3403
{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...}
3404
sage: D = DiGraph({0:[1,2,3], 1:[2], 3:[0,1]})
3405
sage: D.show(figsize=[2,2])
3406
sage: D = D.to_undirected()
3407
sage: D.show(figsize=[2,2])
3408
sage: D.centrality_closeness()
3409
{0: 1.0, 1: 1.0, 2: 0.75, 3: 0.75}
3410
sage: D.centrality_closeness(v=1)
3411
1.0
3412
"""
3413
import networkx
3414
return networkx.closeness_centrality(self.networkx_graph(copy=False), v)
3415
3416
### Constructors
3417
3418
def to_directed(self, implementation='c_graph', sparse=None):
3419
"""
3420
Returns a directed version of the graph. A single edge becomes two
3421
edges, one in each direction.
3422
3423
EXAMPLES::
3424
3425
sage: graphs.PetersenGraph().to_directed()
3426
Petersen graph: Digraph on 10 vertices
3427
"""
3428
if sparse is None:
3429
from sage.graphs.base.dense_graph import DenseGraphBackend
3430
sparse = (not isinstance(self._backend, DenseGraphBackend))
3431
from sage.graphs.all import DiGraph
3432
D = DiGraph(name=self.name(), pos=self._pos, boundary=self._boundary,
3433
multiedges=self.allows_multiple_edges(),
3434
implementation=implementation, sparse=sparse)
3435
D.name(self.name())
3436
D.add_vertices(self.vertex_iterator())
3437
for u,v,l in self.edge_iterator():
3438
D.add_edge(u,v,l)
3439
D.add_edge(v,u,l)
3440
if hasattr(self, '_embedding'):
3441
from copy import copy
3442
D._embedding = copy(self._embedding)
3443
D._weighted = self._weighted
3444
return D
3445
3446
def to_undirected(self):
3447
"""
3448
Since the graph is already undirected, simply returns a copy of
3449
itself.
3450
3451
EXAMPLES::
3452
3453
sage: graphs.PetersenGraph().to_undirected()
3454
Petersen graph: Graph on 10 vertices
3455
"""
3456
from copy import copy
3457
return copy(self)
3458
3459
### Visualization
3460
3461
def write_to_eps(self, filename, **options):
3462
r"""
3463
Writes a plot of the graph to ``filename`` in ``eps`` format.
3464
3465
INPUT:
3466
3467
- ``filename`` -- a string
3468
- ``**options`` -- same layout options as :meth:`.layout`
3469
3470
EXAMPLES::
3471
3472
sage: P = graphs.PetersenGraph()
3473
sage: P.write_to_eps(tmp_dir() + 'sage.eps')
3474
3475
It is relatively simple to include this file in a LaTeX
3476
document. ``\usepackagegraphics`` must appear in the
3477
preamble, and ``\includegraphics{filename.eps}`` will include
3478
the file. To compile the document to ``pdf`` with ``pdflatex``
3479
the file needs first to be converted to ``pdf``, for example
3480
with ``ps2pdf filename.eps filename.pdf``.
3481
"""
3482
from sage.graphs.print_graphs import print_graph_eps
3483
pos = self.layout(**options)
3484
[xmin, xmax, ymin, ymax] = self._layout_bounding_box(pos)
3485
for v in pos:
3486
pos[v] = (1.8*(pos[v][0] - xmin)/(xmax - xmin) - 0.9, 1.8*(pos[v][1] - ymin)/(ymax - ymin) - 0.9)
3487
if filename[-4:] != '.eps':
3488
filename += '.eps'
3489
f = open(filename, 'w')
3490
f.write( print_graph_eps(self.vertices(), self.edge_iterator(), pos) )
3491
f.close()
3492
3493
def topological_minor(self, H, vertices = False, paths = False, solver=None, verbose=0):
3494
r"""
3495
Returns a topological `H`-minor from ``self`` if one exists.
3496
3497
We say that a graph `G` has a topological `H`-minor (or that
3498
it has a graph isomorphic to `H` as a topological minor), if
3499
`G` contains a subdivision of a graph isomorphic to `H` (=
3500
obtained from `H` through arbitrary subdivision of its edges)
3501
as a subgraph.
3502
3503
For more information, see the `Wikipedia article on graph minor
3504
:wikipedia:`Minor_(graph_theory)`.
3505
3506
INPUT:
3507
3508
- ``H`` -- The topological minor to find in the current graph.
3509
3510
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
3511
solver to be used. If set to ``None``, the default one is used. For
3512
more information on LP solvers and which default solver is used, see
3513
the method
3514
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
3515
of the class
3516
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
3517
3518
- ``verbose`` -- integer (default: ``0``). Sets the level of
3519
verbosity. Set to 0 by default, which means quiet.
3520
3521
OUTPUT:
3522
3523
The topological `H`-minor found is returned as a subgraph `M`
3524
of ``self``, such that the vertex `v` of `M` that represents a
3525
vertex `h\in H` has ``h`` as a label (see
3526
:meth:`get_vertex <sage.graphs.generic_graph.GenericGraph.get_vertex>`
3527
and
3528
:meth:`set_vertex <sage.graphs.generic_graph.GenericGraph.set_vertex>`),
3529
and such that every edge of `M` has as a label the edge of `H`
3530
it (partially) represents.
3531
3532
If no topological minor is found, this method returns
3533
``False``.
3534
3535
ALGORITHM:
3536
3537
Mixed Integer Linear Programming.
3538
3539
COMPLEXITY:
3540
3541
Theoretically, when `H` is fixed, testing for the existence of
3542
a topological `H`-minor is polynomial. The known algorithms
3543
are highly exponential in `H`, though.
3544
3545
.. NOTE::
3546
3547
* This function can be expected to be *very* slow, especially
3548
where the topological minor does not exist.
3549
3550
(CPLEX seems to be *much* more efficient than GLPK on this
3551
kind of problem)
3552
3553
EXAMPLES:
3554
3555
Petersen's graph has a topological `K_4`-minor::
3556
3557
sage: g = graphs.PetersenGraph()
3558
sage: g.topological_minor(graphs.CompleteGraph(4))
3559
Subgraph of (Petersen graph): Graph on ...
3560
3561
And a topological `K_{3,3}`-minor::
3562
3563
sage: g.topological_minor(graphs.CompleteBipartiteGraph(3,3))
3564
Subgraph of (Petersen graph): Graph on ...
3565
3566
And of course, a tree has no topological `C_3`-minor::
3567
3568
sage: g = graphs.RandomGNP(15,.3)
3569
sage: g = g.subgraph(edges = g.min_spanning_tree())
3570
sage: g.topological_minor(graphs.CycleGraph(3))
3571
False
3572
"""
3573
3574
# Useful alias ...
3575
G = self
3576
3577
from sage.numerical.mip import MixedIntegerLinearProgram, Sum, MIPSolverException
3578
p = MixedIntegerLinearProgram()
3579
3580
# This is an existence problem
3581
p.set_objective(None)
3582
3583
#######################
3584
# Vertex representant #
3585
#######################
3586
#
3587
# v_repr[h][g] = 1 if vertex h from H is represented by vertex
3588
# g from G, 0 otherwise
3589
3590
v_repr = p.new_variable(binary = True, dim = 2)
3591
3592
# Exactly one representant per vertex of H
3593
for h in H:
3594
p.add_constraint( Sum( v_repr[h][g] for g in G), min = 1, max = 1)
3595
3596
# A vertex of G can only represent one vertex of H
3597
for g in G:
3598
p.add_constraint( Sum( v_repr[h][g] for h in H), max = 1)
3599
3600
3601
###################
3602
# Is representent #
3603
###################
3604
#
3605
# is_repr[v] = 1 if v represents some vertex of H
3606
3607
is_repr = p.new_variable(binary = True)
3608
3609
for g in G:
3610
for h in H:
3611
p.add_constraint( v_repr[h][g] - is_repr[g], max = 0)
3612
3613
3614
###################################
3615
# paths between the representents #
3616
###################################
3617
#
3618
# For any edge (h1,h2) in H, we have a corresponding path in G
3619
# between the representants of h1 and h2. Which means there is
3620
# a flow of intensity 1 from one to the other.
3621
# We are then writing a flow problem for each edge of H.
3622
#
3623
# The variable flow[(h1,h2)][(g1,g2)] indicates the amount of
3624
# flow on the edge (g1,g2) representing the edge (h1,h2).
3625
3626
flow = p.new_variable(binary = True, dim = 2)
3627
3628
# This lambda function returns the balance of flow
3629
# corresponding to commodity C at vertex v v
3630
3631
flow_in = lambda C, v : Sum( flow[C][(v,u)] for u in G.neighbors(v) )
3632
flow_out = lambda C, v : Sum( flow[C][(u,v)] for u in G.neighbors(v) )
3633
3634
flow_balance = lambda C, v : flow_in(C,v) - flow_out(C,v)
3635
3636
for h1,h2 in H.edges(labels = False):
3637
3638
for v in G:
3639
3640
# The flow balance depends on whether the vertex v is
3641
# a representant of h1 or h2 in G, or a reprensentant
3642
# of none
3643
3644
p.add_constraint( flow_balance((h1,h2),v) == v_repr[h1][v] - v_repr[h2][v] )
3645
3646
#############################
3647
# Internal vertex of a path #
3648
#############################
3649
#
3650
# is_internal[C][g] = 1 if a vertex v from G is located on the
3651
# path representing the edge (=commodity) C
3652
3653
is_internal = p.new_variable(dim = 2, binary = True)
3654
3655
# When is a vertex internal for a commodity ?
3656
for C in H.edges(labels = False):
3657
for g in G:
3658
p.add_constraint( flow_in(C,g) + flow_out(C,g) - is_internal[C][g], max = 1)
3659
3660
3661
############################
3662
# Two paths do not cross ! #
3663
############################
3664
3665
# A vertex can only be internal for one commodity, and zero if
3666
# the vertex is a representent
3667
3668
for g in G:
3669
p.add_constraint( Sum( is_internal[C][g] for C in H.edges(labels = False))
3670
+ is_repr[g], max = 1 )
3671
3672
# (The following inequalities are not necessary, but they seem
3673
# to be of help (the solvers find the answer quicker when they
3674
# are added)
3675
3676
# The flow on one edge can go in only one direction. Besides,
3677
# it can belong to at most one commodity and has a maximum
3678
# intensity of 1.
3679
3680
for g1,g2 in G.edges(labels = None):
3681
3682
p.add_constraint( Sum( flow[C][(g1,g2)] for C in H.edges(labels = False) )
3683
+ Sum( flow[C][(g2,g1)] for C in H.edges(labels = False) ),
3684
max = 1)
3685
3686
3687
# Now we can solve the problem itself !
3688
3689
try:
3690
p.solve(solver = solver, log = verbose)
3691
3692
except MIPSolverException:
3693
return False
3694
3695
3696
minor = G.subgraph()
3697
3698
is_repr = p.get_values(is_repr)
3699
v_repr = p.get_values(v_repr)
3700
flow = p.get_values(flow)
3701
3702
3703
for u,v in minor.edges(labels = False):
3704
used = False
3705
for C in H.edges(labels = False):
3706
3707
if flow[C][(u,v)] + flow[C][(v,u)] > .5:
3708
used = True
3709
minor.set_edge_label(u,v,C)
3710
break
3711
if not used:
3712
minor.delete_edge(u,v)
3713
3714
minor.delete_vertices( [v for v in minor
3715
if minor.degree(v) == 0 ] )
3716
3717
for g in minor:
3718
if is_repr[g] > .5:
3719
for h in H:
3720
if v_repr[h][v] > .5:
3721
minor.set_vertex(g,h)
3722
break
3723
3724
return minor
3725
3726
3727
### Cliques
3728
3729
def cliques_maximal(self):
3730
"""
3731
Returns the list of all maximal cliques, with each clique represented
3732
by a list of vertices. A clique is an induced complete subgraph, and a
3733
maximal clique is one not contained in a larger one.
3734
3735
.. NOTE::
3736
3737
Currently only implemented for undirected graphs. Use to_undirected
3738
to convert a digraph to an undirected graph.
3739
3740
ALGORITHM:
3741
3742
This function is based on NetworkX's implementation of the Bron and
3743
Kerbosch Algorithm [BroKer1973]_.
3744
3745
REFERENCE:
3746
3747
.. [BroKer1973] Coen Bron and Joep Kerbosch. (1973). Algorithm 457:
3748
Finding All Cliques of an Undirected Graph. Commun. ACM. v
3749
16. n 9. pages 575-577. ACM Press. [Online] Available:
3750
http://www.ram.org/computing/rambin/rambin.html
3751
3752
EXAMPLES::
3753
3754
sage: graphs.ChvatalGraph().cliques_maximal()
3755
[[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]]
3756
sage: G = Graph({0:[1,2,3], 1:[2], 3:[0,1]})
3757
sage: G.show(figsize=[2,2])
3758
sage: G.cliques_maximal()
3759
[[0, 1, 2], [0, 1, 3]]
3760
sage: C=graphs.PetersenGraph()
3761
sage: C.cliques_maximal()
3762
[[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]]
3763
sage: C = Graph('DJ{')
3764
sage: C.cliques_maximal()
3765
[[4, 0], [4, 1, 2, 3]]
3766
3767
"""
3768
import networkx
3769
return sorted(networkx.find_cliques(self.networkx_graph(copy=False)))
3770
3771
def cliques(self):
3772
"""
3773
(Deprecated) alias for ``cliques_maximal``. See that function for more
3774
details.
3775
3776
EXAMPLE::
3777
3778
sage: C = Graph('DJ{')
3779
sage: C.cliques()
3780
doctest:...: DeprecationWarning: The function 'cliques' has been deprecated. Use 'cliques_maximal' or 'cliques_maximum'.
3781
[[4, 0], [4, 1, 2, 3]]
3782
3783
"""
3784
from sage.misc.misc import deprecation
3785
deprecation("The function 'cliques' has been deprecated. Use " + \
3786
"'cliques_maximal' or 'cliques_maximum'.")
3787
return self.cliques_maximal()
3788
3789
def cliques_maximum(self):
3790
"""
3791
Returns the list of all maximum cliques, with each clique represented
3792
by a list of vertices. A clique is an induced complete subgraph, and a
3793
maximum clique is one of maximal order.
3794
3795
.. NOTE::
3796
3797
Currently only implemented for undirected graphs. Use to_undirected
3798
to convert a digraph to an undirected graph.
3799
3800
ALGORITHM:
3801
3802
This function is based on Cliquer [NisOst2003]_.
3803
3804
REFERENCE:
3805
3806
.. [NisOst2003] Sampo Niskanen and Patric R. J. Ostergard,
3807
"Cliquer User's Guide, Version 1.0," Communications Laboratory,
3808
Helsinki University of Technology, Espoo, Finland,
3809
Tech. Rep. T48, 2003.
3810
3811
EXAMPLES::
3812
3813
sage: graphs.ChvatalGraph().cliques_maximum()
3814
[[0, 1], [0, 4], [0, 6], [0, 9], [1, 2], [1, 5], [1, 7], [2, 3], [2, 6], [2, 8], [3, 4], [3, 7], [3, 9], [4, 5], [4, 8], [5, 10], [5, 11], [6, 10], [6, 11], [7, 8], [7, 11], [8, 10], [9, 10], [9, 11]]
3815
sage: G = Graph({0:[1,2,3], 1:[2], 3:[0,1]})
3816
sage: G.show(figsize=[2,2])
3817
sage: G.cliques_maximum()
3818
[[0, 1, 2], [0, 1, 3]]
3819
sage: C=graphs.PetersenGraph()
3820
sage: C.cliques_maximum()
3821
[[0, 1], [0, 4], [0, 5], [1, 2], [1, 6], [2, 3], [2, 7], [3, 4], [3, 8], [4, 9], [5, 7], [5, 8], [6, 8], [6, 9], [7, 9]]
3822
sage: C = Graph('DJ{')
3823
sage: C.cliques_maximum()
3824
[[1, 2, 3, 4]]
3825
3826
"""
3827
from sage.graphs.cliquer import all_max_clique
3828
return sorted(all_max_clique(self))
3829
3830
def clique_maximum(self, algorithm="Cliquer"):
3831
"""
3832
Returns the vertex set of a maximal order complete subgraph.
3833
3834
INPUT:
3835
3836
- ``algorithm`` -- the algorithm to be used :
3837
3838
- If ``algorithm = "Cliquer"`` (default) - This wraps the C program
3839
Cliquer [NisOst2003]_.
3840
3841
- If ``algorithm = "MILP"``, the problem is solved through a Mixed
3842
Integer Linear Program.
3843
3844
(see :class:`MixedIntegerLinearProgram <sage.numerical.mip>`)
3845
3846
.. NOTE::
3847
3848
Currently only implemented for undirected graphs. Use to_undirected
3849
to convert a digraph to an undirected graph.
3850
3851
ALGORITHM:
3852
3853
This function is based on Cliquer [NisOst2003]_.
3854
3855
EXAMPLES:
3856
3857
Using Cliquer (default)::
3858
3859
sage: C=graphs.PetersenGraph()
3860
sage: C.clique_maximum()
3861
[7, 9]
3862
sage: C = Graph('DJ{')
3863
sage: C.clique_maximum()
3864
[1, 2, 3, 4]
3865
3866
Through a Linear Program::
3867
3868
sage: len(C.clique_maximum(algorithm = "MILP"))
3869
4
3870
3871
TESTS:
3872
3873
Wrong algorithm::
3874
3875
sage: C.clique_maximum(algorithm = "BFS")
3876
Traceback (most recent call last):
3877
...
3878
NotImplementedError: Only 'MILP' and 'Cliquer' are supported.
3879
"""
3880
if algorithm=="Cliquer":
3881
from sage.graphs.cliquer import max_clique
3882
return max_clique(self)
3883
elif algorithm == "MILP":
3884
return self.complement().independent_set(algorithm = algorithm)
3885
else:
3886
raise NotImplementedError("Only 'MILP' and 'Cliquer' are supported.")
3887
3888
def clique_number(self, algorithm="Cliquer", cliques=None):
3889
r"""
3890
Returns the order of the largest clique of the graph (the clique
3891
number).
3892
3893
.. NOTE::
3894
3895
Currently only implemented for undirected graphs. Use ``to_undirected``
3896
to convert a digraph to an undirected graph.
3897
3898
INPUT:
3899
3900
- ``algorithm`` -- the algorithm to be used :
3901
3902
- If ``algorithm = "Cliquer"`` - This wraps the C program Cliquer [NisOst2003]_.
3903
3904
- If ``algorithm = "networkx"`` - This function is based on NetworkX's implementation
3905
of the Bron and Kerbosch Algorithm [BroKer1973]_.
3906
3907
- If ``algorithm = "MILP"``, the problem is solved through a Mixed
3908
Integer Linear Program.
3909
3910
(see :class:`MixedIntegerLinearProgram <sage.numerical.mip>`)
3911
3912
- ``cliques`` - an optional list of cliques that can be input if
3913
already computed. Ignored unless ``algorithm=="networkx"``.
3914
3915
ALGORITHM:
3916
3917
This function is based on Cliquer [NisOst2003]_ and [BroKer1973]_.
3918
3919
EXAMPLES::
3920
3921
sage: C = Graph('DJ{')
3922
sage: C.clique_number()
3923
4
3924
sage: G = Graph({0:[1,2,3], 1:[2], 3:[0,1]})
3925
sage: G.show(figsize=[2,2])
3926
sage: G.clique_number()
3927
3
3928
3929
TESTS::
3930
3931
sage: g = graphs.PetersenGraph()
3932
sage: g.clique_number(algorithm="MILP")
3933
2
3934
"""
3935
if algorithm=="Cliquer":
3936
from sage.graphs.cliquer import clique_number
3937
return clique_number(self)
3938
elif algorithm=="networkx":
3939
import networkx
3940
return networkx.graph_clique_number(self.networkx_graph(copy=False),cliques)
3941
elif algorithm == "MILP":
3942
return len(self.complement().independent_set(algorithm = algorithm))
3943
else:
3944
raise NotImplementedError("Only 'networkx' 'MILP' and 'Cliquer' are supported.")
3945
3946
def cliques_number_of(self, vertices=None, cliques=None):
3947
"""
3948
Returns a dictionary of the number of maximal cliques containing each
3949
vertex, keyed by vertex. (Returns a single value if
3950
only one input vertex).
3951
3952
.. NOTE::
3953
3954
Currently only implemented for undirected graphs. Use to_undirected
3955
to convert a digraph to an undirected graph.
3956
3957
INPUT:
3958
3959
- ``vertices`` - the vertices to inspect (default is
3960
entire graph)
3961
3962
- ``cliques`` - list of cliques (if already
3963
computed)
3964
3965
3966
EXAMPLES::
3967
3968
sage: C = Graph('DJ{')
3969
sage: C.cliques_number_of()
3970
{0: 1, 1: 1, 2: 1, 3: 1, 4: 2}
3971
sage: E = C.cliques_maximal()
3972
sage: E
3973
[[4, 0], [4, 1, 2, 3]]
3974
sage: C.cliques_number_of(cliques=E)
3975
{0: 1, 1: 1, 2: 1, 3: 1, 4: 2}
3976
sage: F = graphs.Grid2dGraph(2,3)
3977
sage: X = F.cliques_number_of()
3978
sage: for v in sorted(X.iterkeys()):
3979
... print v, X[v]
3980
(0, 0) 2
3981
(0, 1) 3
3982
(0, 2) 2
3983
(1, 0) 2
3984
(1, 1) 3
3985
(1, 2) 2
3986
sage: F.cliques_number_of(vertices=[(0, 1), (1, 2)])
3987
{(0, 1): 3, (1, 2): 2}
3988
sage: G = Graph({0:[1,2,3], 1:[2], 3:[0,1]})
3989
sage: G.show(figsize=[2,2])
3990
sage: G.cliques_number_of()
3991
{0: 2, 1: 2, 2: 1, 3: 1}
3992
"""
3993
import networkx
3994
return networkx.number_of_cliques(self.networkx_graph(copy=False), vertices, cliques)
3995
3996
def cliques_get_max_clique_graph(self, name=''):
3997
"""
3998
Returns a graph constructed with maximal cliques as vertices, and
3999
edges between maximal cliques with common members in the original
4000
graph.
4001
4002
.. NOTE::
4003
4004
Currently only implemented for undirected graphs. Use to_undirected
4005
to convert a digraph to an undirected graph.
4006
4007
INPUT:
4008
4009
- ``name`` - The name of the new graph.
4010
4011
EXAMPLES::
4012
4013
sage: (graphs.ChvatalGraph()).cliques_get_max_clique_graph()
4014
Graph on 24 vertices
4015
sage: ((graphs.ChvatalGraph()).cliques_get_max_clique_graph()).show(figsize=[2,2], vertex_size=20, vertex_labels=False)
4016
sage: G = Graph({0:[1,2,3], 1:[2], 3:[0,1]})
4017
sage: G.show(figsize=[2,2])
4018
sage: G.cliques_get_max_clique_graph()
4019
Graph on 2 vertices
4020
sage: (G.cliques_get_max_clique_graph()).show(figsize=[2,2])
4021
"""
4022
import networkx
4023
return Graph(networkx.make_max_clique_graph(self.networkx_graph(copy=False), name=name, create_using=networkx.MultiGraph()))
4024
4025
def cliques_get_clique_bipartite(self, **kwds):
4026
"""
4027
Returns a bipartite graph constructed such that maximal cliques are the
4028
right vertices and the left vertices are retained from the given
4029
graph. Right and left vertices are connected if the bottom vertex
4030
belongs to the clique represented by a top vertex.
4031
4032
.. NOTE::
4033
4034
Currently only implemented for undirected graphs. Use to_undirected
4035
to convert a digraph to an undirected graph.
4036
4037
EXAMPLES::
4038
4039
sage: (graphs.ChvatalGraph()).cliques_get_clique_bipartite()
4040
Bipartite graph on 36 vertices
4041
sage: ((graphs.ChvatalGraph()).cliques_get_clique_bipartite()).show(figsize=[2,2], vertex_size=20, vertex_labels=False)
4042
sage: G = Graph({0:[1,2,3], 1:[2], 3:[0,1]})
4043
sage: G.show(figsize=[2,2])
4044
sage: G.cliques_get_clique_bipartite()
4045
Bipartite graph on 6 vertices
4046
sage: (G.cliques_get_clique_bipartite()).show(figsize=[2,2])
4047
"""
4048
from bipartite_graph import BipartiteGraph
4049
import networkx
4050
return BipartiteGraph(networkx.make_clique_bipartite(self.networkx_graph(copy=False), **kwds))
4051
4052
def independent_set(self, algorithm = "Cliquer", value_only = False, reduction_rules = True, solver = None, verbosity = 0):
4053
r"""
4054
Returns a maximum independent set.
4055
4056
An independent set of a graph is a set of pairwise non-adjacent
4057
vertices. A maximum independent set is an independent set of maximum
4058
cardinality. It induces an empty subgraph.
4059
4060
Equivalently, an independent set is defined as the complement of a
4061
vertex cover.
4062
4063
INPUT:
4064
4065
- ``algorithm`` -- the algorithm to be used
4066
4067
* If ``algorithm = "Cliquer"`` (default), the problem is solved
4068
using Cliquer [NisOst2003]_.
4069
4070
(see the :mod:`Cliquer modules <sage.graphs.cliquer>`)
4071
4072
* If ``algorithm = "MILP"``, the problem is solved through a Mixed
4073
Integer Linear Program.
4074
4075
(see :class:`MixedIntegerLinearProgram <sage.numerical.mip>`)
4076
4077
- ``value_only`` -- boolean (default: ``False``). If set to ``True``,
4078
only the size of a maximum independent set is returned. Otherwise,
4079
a maximum independent set is returned as a list of vertices.
4080
4081
- ``reduction_rules`` -- (default: ``True``) Specify if the reductions
4082
rules from kernelization must be applied as pre-processing or not.
4083
See [ACFLSS04]_ for more details. Note that depending on the
4084
instance, it might be faster to disable reduction rules.
4085
4086
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
4087
solver to be used. If set to ``None``, the default one is used. For
4088
more information on LP solvers and which default solver is used, see
4089
the method
4090
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
4091
of the class
4092
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
4093
4094
- ``verbosity`` -- non-negative integer (default: ``0``). Set the level
4095
of verbosity you want from the linear program solver. Since the
4096
problem of computing an independent set is `NP`-complete, its solving
4097
may take some time depending on the graph. A value of 0 means that
4098
there will be no message printed by the solver. This option is only
4099
useful if ``algorithm="MILP"``.
4100
4101
.. NOTE::
4102
4103
While Cliquer is usually (and by far) the most efficient of the two
4104
implementations, the Mixed Integer Linear Program formulation
4105
sometimes proves faster on very "symmetrical" graphs.
4106
4107
EXAMPLES:
4108
4109
Using Cliquer::
4110
4111
sage: C = graphs.PetersenGraph()
4112
sage: C.independent_set()
4113
[0, 3, 6, 7]
4114
4115
As a linear program::
4116
4117
sage: C = graphs.PetersenGraph()
4118
sage: len(C.independent_set(algorithm = "MILP"))
4119
4
4120
"""
4121
my_cover = self.vertex_cover(algorithm=algorithm, value_only=value_only, reduction_rules=reduction_rules, solver=solver, verbosity=verbosity)
4122
if value_only:
4123
return self.order() - my_cover
4124
else:
4125
return [u for u in self.vertices() if not u in my_cover]
4126
4127
4128
def vertex_cover(self, algorithm = "Cliquer", value_only = False,
4129
reduction_rules = True, solver = None, verbosity = 0):
4130
r"""
4131
Returns a minimum vertex cover of self represented by a set of vertices.
4132
4133
A minimum vertex cover of a graph is a set `S` of vertices such that
4134
each edge is incident to at least one element of `S`, and such that `S`
4135
is of minimum cardinality. For more information, see the
4136
:wikipedia:`Wikipedia article on vertex cover <Vertex_cover>`.
4137
4138
Equivalently, a vertex cover is defined as the complement of an
4139
independent set.
4140
4141
As an optimization problem, it can be expressed as follows:
4142
4143
.. MATH::
4144
4145
\mbox{Minimize : }&\sum_{v\in G} b_v\\
4146
\mbox{Such that : }&\forall (u,v) \in G.edges(), b_u+b_v\geq 1\\
4147
&\forall x\in G, b_x\mbox{ is a binary variable}
4148
4149
INPUT:
4150
4151
- ``algorithm`` -- string (default: ``"Cliquer"``). Indicating
4152
which algorithm to use. It can be one of those two values.
4153
4154
- ``"Cliquer"`` will compute a minimum vertex cover
4155
using the Cliquer package.
4156
4157
- ``"MILP"`` will compute a minimum vertex cover through a mixed
4158
integer linear program.
4159
4160
- ``value_only`` -- boolean (default: ``False``). If set to ``True``,
4161
only the size of a minimum vertex cover is returned. Otherwise,
4162
a minimum vertex cover is returned as a list of vertices.
4163
4164
- ``reduction_rules`` -- (default: ``True``) Specify if the reductions
4165
rules from kernelization must be applied as pre-processing or not.
4166
See [ACFLSS04]_ for more details. Note that depending on the
4167
instance, it might be faster to disable reduction rules.
4168
4169
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
4170
solver to be used. If set to ``None``, the default one is used. For
4171
more information on LP solvers and which default solver is used, see
4172
the method
4173
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
4174
of the class
4175
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
4176
4177
- ``verbosity`` -- non-negative integer (default: ``0``). Set the level
4178
of verbosity you want from the linear program solver. Since the
4179
problem of computing a vertex cover is `NP`-complete, its solving may
4180
take some time depending on the graph. A value of 0 means that there
4181
will be no message printed by the solver. This option is only useful
4182
if ``algorithm="MILP"``.
4183
4184
EXAMPLES:
4185
4186
On the Pappus graph::
4187
4188
sage: g = graphs.PappusGraph()
4189
sage: g.vertex_cover(value_only=True)
4190
9
4191
4192
TESTS:
4193
4194
The two algorithms should return the same result::
4195
4196
sage: g = graphs.RandomGNP(10,.5)
4197
sage: vc1 = g.vertex_cover(algorithm="MILP")
4198
sage: vc2 = g.vertex_cover(algorithm="Cliquer")
4199
sage: len(vc1) == len(vc2)
4200
True
4201
4202
The cardinality of the vertex cover is unchanged when reduction rules are used. First for trees::
4203
4204
sage: for i in range(20):
4205
... g = graphs.RandomTree(20)
4206
... vc1_set = g.vertex_cover()
4207
... vc1 = len(vc1_set)
4208
... vc2 = g.vertex_cover(value_only = True, reduction_rules = False)
4209
... if vc1 != vc2:
4210
... print "Error :", vc1, vc2
4211
... print "With reduction rules :", vc1
4212
... print "Without reduction rules :", vc2
4213
... break
4214
... g.delete_vertices(vc1_set)
4215
... if g.size() != 0:
4216
... print "This thing is not a vertex cover !"
4217
4218
Then for random GNP graphs::
4219
4220
sage: for i in range(20):
4221
... g = graphs.RandomGNP(50,4/50)
4222
... vc1_set = g.vertex_cover()
4223
... vc1 = len(vc1_set)
4224
... vc2 = g.vertex_cover(value_only = True, reduction_rules = False)
4225
... if vc1 != vc2:
4226
... print "Error :", vc1, vc2
4227
... print "With reduction rules :", vc1
4228
... print "Without reduction rules :", vc2
4229
... break
4230
... g.delete_vertices(vc1_set)
4231
... if g.size() != 0:
4232
... print "This thing is not a vertex cover !"
4233
4234
4235
Given a wrong algorithm::
4236
4237
sage: graphs.PetersenGraph().vertex_cover(algorithm = "guess")
4238
Traceback (most recent call last):
4239
...
4240
ValueError: The algorithm must be either "Cliquer" or "MILP".
4241
4242
REFERENCE:
4243
4244
.. [ACFLSS04] F. N. Abu-Khzam, R. L. Collins, M. R. Fellows, M. A.
4245
Langston, W. H. Suters, and C. T. Symons: Kernelization Algorithm for
4246
the Vertex Cover Problem: Theory and Experiments. *SIAM ALENEX/ANALCO*
4247
2004: 62-69.
4248
"""
4249
g = self
4250
4251
ppset = []
4252
folded_vertices = []
4253
4254
###################
4255
# Reduction rules #
4256
###################
4257
4258
if reduction_rules:
4259
# We apply simple reduction rules allowing to identify vertices that
4260
# belongs to an optimal vertex cover
4261
4262
# We first create manually a copy of the graph to prevent creating
4263
# multi-edges when merging vertices, if edges have labels (e.g., weights).
4264
g = self.copy()
4265
4266
degree_at_most_two = set([u for u,du in g.degree(labels = True).items() if du <= 2])
4267
4268
while degree_at_most_two:
4269
4270
u = degree_at_most_two.pop()
4271
du = g.degree(u)
4272
4273
if du == 0:
4274
# RULE 1: isolated vertices are not part of the cover. We
4275
# simply remove them from the graph. The degree of such
4276
# vertices may have been reduced to 0 while applying other
4277
# reduction rules
4278
g.delete_vertex(u)
4279
4280
elif du == 1:
4281
# RULE 2: If a vertex u has degree 1, we select its neighbor
4282
# v and remove both u and v from g.
4283
v = g.neighbors(u)[0]
4284
ppset.append(v)
4285
g.delete_vertex(u)
4286
4287
for w in g.neighbors(v):
4288
if g.degree(w) <= 3:
4289
# The degree of w will be at most two after the
4290
# deletion of v
4291
degree_at_most_two.add(w)
4292
4293
g.delete_vertex(v)
4294
degree_at_most_two.discard(v)
4295
4296
elif du == 2:
4297
v,w = g.neighbors(u)
4298
4299
if g.has_edge(v,w):
4300
# RULE 3: If the neighbors v and w of a degree 2 vertex
4301
# u are incident, then we select both v and w and remove
4302
# u, v, and w from g.
4303
ppset.append(v)
4304
ppset.append(w)
4305
g.delete_vertex(u)
4306
neigh = set(g.neighbors(v) + g.neighbors(w)).difference(set([v,w]))
4307
g.delete_vertex(v)
4308
g.delete_vertex(w)
4309
4310
for z in neigh:
4311
if g.degree(z) <= 2:
4312
degree_at_most_two.add(z)
4313
4314
else:
4315
# RULE 4, folded vertices: If the neighbors v and w of a
4316
# degree 2 vertex u are not incident, then we contract
4317
# edges (u, v), (u,w). Then, if the solution contains u,
4318
# we replace it with v and w. Otherwise, we let u in the
4319
# solution.
4320
neigh = set(g.neighbors(v) + g.neighbors(w)).difference(set([u,v,w]))
4321
g.delete_vertex(v)
4322
g.delete_vertex(w)
4323
for z in neigh:
4324
g.add_edge(u,z)
4325
4326
folded_vertices += [(u,v,w)]
4327
4328
if g.degree(u) <= 2:
4329
degree_at_most_two.add(u)
4330
4331
degree_at_most_two.discard(v)
4332
degree_at_most_two.discard(w)
4333
4334
4335
# RULE 5:
4336
# TODO: add extra reduction rules
4337
4338
4339
##################
4340
# Main Algorithm #
4341
##################
4342
4343
if g.order() == 0:
4344
# Reduction rules were sufficients to get the solution
4345
size_cover_g = 0
4346
cover_g = []
4347
4348
elif algorithm == "Cliquer":
4349
from sage.graphs.cliquer import max_clique
4350
independent = max_clique(g.complement())
4351
if value_only:
4352
size_cover_g = g.order() - len(independent)
4353
else:
4354
cover_g = [u for u in g.vertices() if not u in independent]
4355
4356
elif algorithm == "MILP":
4357
4358
from sage.numerical.mip import MixedIntegerLinearProgram, Sum
4359
p = MixedIntegerLinearProgram(maximization=False, solver=solver)
4360
b = p.new_variable()
4361
4362
# minimizes the number of vertices in the set
4363
p.set_objective(Sum([b[v] for v in g.vertices()]))
4364
4365
# an edge contains at least one vertex of the minimum vertex cover
4366
for (u,v) in g.edges(labels=None):
4367
p.add_constraint(b[u] + b[v], min=1)
4368
4369
p.set_binary(b)
4370
4371
if value_only:
4372
size_cover_g = p.solve(objective_only=True, log=verbosity)
4373
else:
4374
p.solve(log=verbosity)
4375
b = p.get_values(b)
4376
cover_g = [v for v in g.vertices() if b[v] == 1]
4377
else:
4378
raise ValueError("The algorithm must be either \"Cliquer\" or \"MILP\".")
4379
4380
#########################
4381
# Returning the results #
4382
#########################
4383
4384
# We finally reconstruct the solution according the reduction rules
4385
if value_only:
4386
return len(ppset) + len(folded_vertices) + size_cover_g
4387
else:
4388
# RULES 2 and 3:
4389
cover_g.extend(ppset)
4390
# RULE 4:
4391
folded_vertices.reverse()
4392
for u,v,w in folded_vertices:
4393
if u in cover_g:
4394
cover_g.remove(u)
4395
cover_g += [v,w]
4396
else:
4397
cover_g += [u]
4398
cover_g.sort()
4399
return cover_g
4400
4401
4402
def cliques_vertex_clique_number(self, algorithm="cliquer", vertices=None,
4403
cliques=None):
4404
"""
4405
Returns a dictionary of sizes of the largest maximal cliques containing
4406
each vertex, keyed by vertex. (Returns a single value if only one
4407
input vertex).
4408
4409
.. NOTE::
4410
4411
Currently only implemented for undirected graphs. Use to_undirected
4412
to convert a digraph to an undirected graph.
4413
4414
INPUT:
4415
4416
- ``algorithm`` - either ``cliquer`` or ``networkx``
4417
4418
- ``cliquer`` - This wraps the C program Cliquer [NisOst2003]_.
4419
4420
- ``networkx`` - This function is based on NetworkX's implementation
4421
of the Bron and Kerbosch Algorithm [BroKer1973]_.
4422
4423
- ``vertices`` - the vertices to inspect (default is entire graph).
4424
Ignored unless ``algorithm=='networkx'``.
4425
4426
- ``cliques`` - list of cliques (if already computed). Ignored unless
4427
``algorithm=='networkx'``.
4428
4429
EXAMPLES::
4430
4431
sage: C = Graph('DJ{')
4432
sage: C.cliques_vertex_clique_number()
4433
{0: 2, 1: 4, 2: 4, 3: 4, 4: 4}
4434
sage: E = C.cliques_maximal()
4435
sage: E
4436
[[4, 0], [4, 1, 2, 3]]
4437
sage: C.cliques_vertex_clique_number(cliques=E,algorithm="networkx")
4438
{0: 2, 1: 4, 2: 4, 3: 4, 4: 4}
4439
sage: F = graphs.Grid2dGraph(2,3)
4440
sage: X = F.cliques_vertex_clique_number(algorithm="networkx")
4441
sage: for v in sorted(X.iterkeys()):
4442
... print v, X[v]
4443
(0, 0) 2
4444
(0, 1) 2
4445
(0, 2) 2
4446
(1, 0) 2
4447
(1, 1) 2
4448
(1, 2) 2
4449
sage: F.cliques_vertex_clique_number(vertices=[(0, 1), (1, 2)])
4450
{(0, 1): 2, (1, 2): 2}
4451
sage: G = Graph({0:[1,2,3], 1:[2], 3:[0,1]})
4452
sage: G.show(figsize=[2,2])
4453
sage: G.cliques_vertex_clique_number()
4454
{0: 3, 1: 3, 2: 3, 3: 3}
4455
4456
"""
4457
4458
if algorithm=="cliquer":
4459
from sage.graphs.cliquer import clique_number
4460
if vertices==None:
4461
vertices=self
4462
value={}
4463
for v in vertices:
4464
value[v] = 1+clique_number(self.subgraph(self.neighbors(v)))
4465
self.subgraph(self.neighbors(v)).plot()
4466
return value
4467
elif algorithm=="networkx":
4468
import networkx
4469
return networkx.node_clique_number(self.networkx_graph(copy=False),vertices, cliques)
4470
else:
4471
raise NotImplementedError("Only 'networkx' and 'cliquer' are supported.")
4472
4473
def cliques_containing_vertex(self, vertices=None, cliques=None):
4474
"""
4475
Returns the cliques containing each vertex, represented as a dictionary
4476
of lists of lists, keyed by vertex. (Returns a single list if only one
4477
input vertex).
4478
4479
.. NOTE::
4480
4481
Currently only implemented for undirected graphs. Use to_undirected
4482
to convert a digraph to an undirected graph.
4483
4484
INPUT:
4485
4486
- ``vertices`` - the vertices to inspect (default is
4487
entire graph)
4488
4489
- ``cliques`` - list of cliques (if already
4490
computed)
4491
4492
EXAMPLES::
4493
4494
sage: C = Graph('DJ{')
4495
sage: C.cliques_containing_vertex()
4496
{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]]}
4497
sage: E = C.cliques_maximal()
4498
sage: E
4499
[[4, 0], [4, 1, 2, 3]]
4500
sage: C.cliques_containing_vertex(cliques=E)
4501
{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]]}
4502
sage: F = graphs.Grid2dGraph(2,3)
4503
sage: X = F.cliques_containing_vertex()
4504
sage: for v in sorted(X.iterkeys()):
4505
... print v, X[v]
4506
(0, 0) [[(0, 1), (0, 0)], [(1, 0), (0, 0)]]
4507
(0, 1) [[(0, 1), (0, 0)], [(0, 1), (0, 2)], [(0, 1), (1, 1)]]
4508
(0, 2) [[(0, 1), (0, 2)], [(1, 2), (0, 2)]]
4509
(1, 0) [[(1, 0), (0, 0)], [(1, 0), (1, 1)]]
4510
(1, 1) [[(0, 1), (1, 1)], [(1, 2), (1, 1)], [(1, 0), (1, 1)]]
4511
(1, 2) [[(1, 2), (0, 2)], [(1, 2), (1, 1)]]
4512
sage: F.cliques_containing_vertex(vertices=[(0, 1), (1, 2)])
4513
{(0, 1): [[(0, 1), (0, 0)], [(0, 1), (0, 2)], [(0, 1), (1, 1)]], (1, 2): [[(1, 2), (0, 2)], [(1, 2), (1, 1)]]}
4514
sage: G = Graph({0:[1,2,3], 1:[2], 3:[0,1]})
4515
sage: G.show(figsize=[2,2])
4516
sage: G.cliques_containing_vertex()
4517
{0: [[0, 1, 2], [0, 1, 3]], 1: [[0, 1, 2], [0, 1, 3]], 2: [[0, 1, 2]], 3: [[0, 1, 3]]}
4518
4519
"""
4520
import networkx
4521
return networkx.cliques_containing_node(self.networkx_graph(copy=False),vertices, cliques)
4522
4523
def clique_complex(self):
4524
"""
4525
Returns the clique complex of self. This is the largest simplicial complex on
4526
the vertices of self whose 1-skeleton is self.
4527
4528
This is only makes sense for undirected simple graphs.
4529
4530
EXAMPLES::
4531
4532
sage: g = Graph({0:[1,2],1:[2],4:[]})
4533
sage: g.clique_complex()
4534
Simplicial complex with vertex set (0, 1, 2, 4) and facets {(4,), (0, 1, 2)}
4535
4536
sage: h = Graph({0:[1,2,3,4],1:[2,3,4],2:[3]})
4537
sage: x = h.clique_complex()
4538
sage: x
4539
Simplicial complex with vertex set (0, 1, 2, 3, 4) and facets {(0, 1, 4), (0, 1, 2, 3)}
4540
sage: i = x.graph()
4541
sage: i==h
4542
True
4543
sage: x==i.clique_complex()
4544
True
4545
4546
"""
4547
if self.is_directed() or self.has_loops() or self.has_multiple_edges():
4548
raise ValueError("Self must be an undirected simple graph to have a clique complex.")
4549
import sage.homology.simplicial_complex
4550
C = sage.homology.simplicial_complex.SimplicialComplex(self.vertices(),self.cliques_maximal(),maximality_check=True)
4551
C._graph = self
4552
return C
4553
4554
### Miscellaneous
4555
4556
def modular_decomposition(self):
4557
r"""
4558
Returns the modular decomposition of the current graph.
4559
4560
Crash course on modular decomposition:
4561
4562
A module `M` of a graph `G` is a proper subset of its vertices
4563
such that for all `u \in V(G)-M, v,w\in M` the relation `u
4564
\sim v \Leftrightarrow u \sim w` holds, where `\sim` denotes
4565
the adjacency relation in `G`. Equivalently, `M \subset V(G)`
4566
is a module if all its vertices have the same adjacency
4567
relations with each vertex outside of the module (vertex by
4568
vertex).
4569
4570
Hence, for a set like a module, it is very easy to encode the
4571
information of the adjacencies between the vertices inside and
4572
outside the module -- we can actually add a new vertex `v_M`
4573
to our graph representing our module `M`, and let `v_M` be
4574
adjacent to `u\in V(G)-M` if and only if some `v\in M` (and
4575
hence all the vertices contained in the module) is adjacent to
4576
`u`. We can now independently (and recursively) study the
4577
structure of our module `M` and the new graph `G-M+\{v_M\}`,
4578
without any loss of information.
4579
4580
Here are two very simple modules :
4581
4582
* A connected component `C` (or the union of some --but
4583
not all-- of them) of a disconnected graph `G`, for
4584
instance, is a module, as no vertex of `C` has a
4585
neighbor outside of it.
4586
4587
* An anticomponent `C` (or the union of some --but not
4588
all-- of them) of an non-anticonnected graph `G`, for
4589
the same reason (it is just the complement of the
4590
previous graph !).
4591
4592
These modules being of special interest, the disjoint union of
4593
graphs is called a Parallel composition, and the complement of
4594
a disjoint union is called a Parallel composition. A graph
4595
whose only modules are singletons is called Prime.
4596
4597
For more information on modular decomposition, in particular
4598
for an explanation of the terms "Parallel," "Prime" and
4599
"Serie," see the `Wikipedia article on modular decomposition
4600
<http://en.wikipedia.org/wiki/Modular_decomposition>`_.
4601
4602
You may also be interested in the survey from Michel Habib and
4603
Christophe Paul entitled "A survey on Algorithmic aspects of
4604
modular decomposition" [HabPau10]_.
4605
4606
OUTPUT:
4607
4608
A pair of two values (recursively encoding the decomposition) :
4609
4610
* The type of the current module :
4611
4612
* ``"Parallel"``
4613
* ``"Prime"``
4614
* ``"Serie"``
4615
4616
* The list of submodules (as list of pairs ``(type, list)``,
4617
recursively...) or the vertex's name if the module is a
4618
singleton.
4619
4620
EXAMPLES:
4621
4622
The Bull Graph is prime::
4623
4624
sage: graphs.BullGraph().modular_decomposition()
4625
('Prime', [3, 4, 0, 1, 2])
4626
4627
The Petersen Graph too::
4628
4629
sage: graphs.PetersenGraph().modular_decomposition()
4630
('Prime', [2, 6, 3, 9, 7, 8, 0, 1, 5, 4])
4631
4632
This a clique on 5 vertices with 2 pendant edges, though, has a more
4633
interesting decomposition ::
4634
4635
sage: g = graphs.CompleteGraph(5)
4636
sage: g.add_edge(0,5)
4637
sage: g.add_edge(0,6)
4638
sage: g.modular_decomposition()
4639
('Serie', [0, ('Parallel', [5, ('Serie', [1, 4, 3, 2]), 6])])
4640
4641
ALGORITHM:
4642
4643
This function uses a C implementation of a 2-step algorithm
4644
implemented by Fabien de Montgolfier [FMDec]_ :
4645
4646
* Computation of a factorizing permutation [HabibViennot1999]_.
4647
4648
* Computation of the tree itself [CapHabMont02]_.
4649
4650
.. SEEALSO::
4651
4652
- :meth:`is_prime` -- Tests whether a graph is prime.
4653
4654
REFERENCE:
4655
4656
.. [FMDec] Fabien de Montgolfier
4657
http://www.liafa.jussieu.fr/~fm/algos/index.html
4658
4659
.. [HabibViennot1999] Michel Habib, Christiphe Paul, Laurent Viennot
4660
Partition refinement techniques: An interesting algorithmic tool kit
4661
International Journal of Foundations of Computer Science
4662
vol. 10 n2 pp.147--170, 1999
4663
4664
.. [CapHabMont02] C. Capelle, M. Habib et F. de Montgolfier
4665
Graph decomposition and Factorising Permutations
4666
Discrete Mathematics and Theoretical Computer Sciences, vol 5 no. 1 , 2002.
4667
4668
.. [HabPau10] Michel Habib and Christophe Paul
4669
A survey of the algorithmic aspects of modular decomposition
4670
Computer Science Review
4671
vol 4, number 1, pages 41--59, 2010
4672
http://www.lirmm.fr/~paul/md-survey.pdf
4673
"""
4674
4675
from sage.graphs.modular_decomposition.modular_decomposition import modular_decomposition
4676
4677
D = modular_decomposition(self)
4678
4679
id_label = dict(enumerate(self.vertices()))
4680
4681
relabel = lambda x : (x[0], map(relabel,x[1])) if isinstance(x,tuple) else id_label[x]
4682
4683
return relabel(D)
4684
4685
def is_prime(self):
4686
r"""
4687
Tests whether the current graph is prime. A graph is prime if
4688
all its modules are trivial (i.e. empty, all of the graph or
4689
singletons)-- see `self.modular_decomposition?`.
4690
4691
EXAMPLE:
4692
4693
The Petersen Graph and the Bull Graph are both prime ::
4694
4695
sage: graphs.PetersenGraph().is_prime()
4696
True
4697
sage: graphs.BullGraph().is_prime()
4698
True
4699
4700
Though quite obviously, the disjoint union of them is not::
4701
4702
sage: (graphs.PetersenGraph() + graphs.BullGraph()).is_prime()
4703
False
4704
"""
4705
4706
D = self.modular_decomposition()
4707
4708
return D[0] == "Prime" and len(D[1]) == self.order()
4709
4710
def _gomory_hu_tree(self, vertices=None, method="FF"):
4711
r"""
4712
Returns a Gomory-Hu tree associated to self.
4713
4714
This function is the private counterpart of ``gomory_hu_tree()``,
4715
with the difference that it has an optional argument
4716
needed for recursive computations, which the user is not
4717
interested in defining himself.
4718
4719
See the documentation of ``gomory_hu_tree()`` for more information.
4720
4721
INPUT:
4722
4723
- ``vertices`` - a set of "real" vertices, as opposed to the
4724
fakes one introduced during the computations. This variable is
4725
useful for the algorithm and for recursion purposes.
4726
4727
- ``method`` -- There are currently two different
4728
implementations of this method :
4729
4730
* If ``method = "FF"`` (default), a Python
4731
implementation of the Ford-Fulkerson algorithm is
4732
used.
4733
4734
* If ``method = "LP"``, the flow problem is solved using
4735
Linear Programming.
4736
4737
EXAMPLE:
4738
4739
This function is actually tested in ``gomory_hu_tree()``, this
4740
example is only present to have a doctest coverage of 100%.
4741
4742
sage: g = graphs.PetersenGraph()
4743
sage: t = g._gomory_hu_tree()
4744
"""
4745
from sage.sets.set import Set
4746
4747
# The default capacity of an arc is 1
4748
from sage.rings.real_mpfr import RR
4749
capacity = lambda label: label if label in RR else 1
4750
4751
# Keeping the graph's embedding
4752
pos = False
4753
4754
# Small case, not really a problem ;-)
4755
if self.order() == 1:
4756
return self.copy()
4757
4758
# This is a sign that this is the first call
4759
# to this recursive function
4760
if vertices is None:
4761
# Now is the time to care about positions
4762
pos = self.get_pos()
4763
4764
# if the graph is not connected, returns the union
4765
# of the Gomory-Hu tree of each component
4766
if not self.is_connected():
4767
g = Graph()
4768
for cc in self.connected_components_subgraphs():
4769
g = g.union(cc._gomory_hu_tree(method=method))
4770
g.set_pos(self.get_pos())
4771
return g
4772
# All the vertices is this graph are the "real ones"
4773
vertices = Set(self.vertices())
4774
4775
# There may be many vertices, though only one which is "real"
4776
if len(vertices) == 1:
4777
g = Graph()
4778
g.add_vertex(vertices[0])
4779
return g
4780
4781
# Take any two vertices
4782
u,v = vertices[0:2]
4783
4784
# Recovers the following values
4785
# flow is the connectivity between u and v
4786
# edges of a min cut
4787
# sets1, sets2 are the two sides of the edge cut
4788
flow,edges,[set1,set2] = self.edge_cut(u, v, use_edge_labels=True, vertices=True, method=method)
4789
4790
# One graph for each part of the previous one
4791
g1,g2 = self.subgraph(set1), self.subgraph(set2)
4792
4793
# Adding the fake vertex to each part
4794
g1_v = Set(set2)
4795
g2_v = Set(set1)
4796
g1.add_vertex(g1_v)
4797
g1.add_vertex(g2_v)
4798
4799
# Each part of the graph had many edges going to the other part
4800
# Now that we have a new fake vertex in each part
4801
# we just say that the edges which were in the cut and going
4802
# to the other side are now going to this fake vertex
4803
4804
# We must preserve the labels. They sum.
4805
4806
for e in edges:
4807
x,y = e[0],e[1]
4808
# Assumes x is in g1
4809
if x in g2:
4810
x,y = y,x
4811
# If the edge x-g1_v exists, adds to its label the capacity of arc xy
4812
if g1.has_edge(x, g1_v):
4813
g1.set_edge_label(x, g1_v, g1.edge_label(x, g1_v) + capacity(self.edge_label(x, y)))
4814
else:
4815
# Otherwise, creates it with the good label
4816
g1.add_edge(x, g1_v, capacity(self.edge_label(x, y)))
4817
# Same thing for g2
4818
if g2.has_edge(y, g2_v):
4819
g2.set_edge_label(y, g2_v, g2.edge_label(y, g2_v) + capacity(self.edge_label(x, y)))
4820
else:
4821
g2.add_edge(y, g2_v, capacity(self.edge_label(x, y)))
4822
4823
# Recursion for the two new graphs... The new "real" vertices are the intersection with
4824
# with the previous set of "real" vertices
4825
g1_tree = g1._gomory_hu_tree(vertices=(vertices & Set(g1.vertices())), method=method)
4826
g2_tree = g2._gomory_hu_tree(vertices=(vertices & Set(g2.vertices())), method=method)
4827
4828
# Union of the two partial trees ( it is disjoint, but
4829
# disjoint_union does not preserve the name of the vertices )
4830
g = g1_tree.union(g2_tree)
4831
4832
# An edge to connect them, with the appropriate label
4833
g.add_edge(g1_tree.vertex_iterator().next(), g2_tree.vertex_iterator().next(), flow)
4834
4835
if pos:
4836
g.set_pos(pos)
4837
4838
return g
4839
4840
def gomory_hu_tree(self, method="FF"):
4841
r"""
4842
Returns a Gomory-Hu tree of self.
4843
4844
Given a tree `T` with labeled edges representing capacities, it is very
4845
easy to determine the maximal flow between any pair of vertices :
4846
it is the minimal label on the edges of the unique path between them.
4847
4848
Given a graph `G`, a Gomory-Hu tree `T` of `G` is a tree
4849
with the same set of vertices, and such that the maximal flow
4850
between any two vertices is the same in `G` as in `T`. See the
4851
`Wikipedia article on Gomory-Hu tree <http://en.wikipedia.org/wiki/Gomory%E2%80%93Hu_tree>`_.
4852
Note that, in general, a graph admits more than one Gomory-Hu tree.
4853
4854
INPUT:
4855
4856
- ``method`` -- There are currently two different
4857
implementations of this method :
4858
4859
* If ``method = "FF"`` (default), a Python
4860
implementation of the Ford-Fulkerson algorithm is
4861
used.
4862
4863
* If ``method = "LP"``, the flow problems are solved
4864
using Linear Programming.
4865
4866
OUTPUT:
4867
4868
graph with labeled edges
4869
4870
EXAMPLE:
4871
4872
Taking the Petersen graph::
4873
4874
sage: g = graphs.PetersenGraph()
4875
sage: t = g.gomory_hu_tree()
4876
4877
Obviously, this graph is a tree::
4878
4879
sage: t.is_tree()
4880
True
4881
4882
Note that if the original graph is not connected, then the
4883
Gomory-Hu tree is in fact a forest::
4884
4885
sage: (2*g).gomory_hu_tree().is_forest()
4886
True
4887
sage: (2*g).gomory_hu_tree().is_connected()
4888
False
4889
4890
On the other hand, such a tree has lost nothing of the initial
4891
graph connectedness::
4892
4893
sage: all([ t.flow(u,v) == g.flow(u,v) for u,v in Subsets( g.vertices(), 2 ) ])
4894
True
4895
4896
Just to make sure, we can check that the same is true for two vertices
4897
in a random graph::
4898
4899
sage: g = graphs.RandomGNP(20,.3)
4900
sage: t = g.gomory_hu_tree()
4901
sage: g.flow(0,1) == t.flow(0,1)
4902
True
4903
4904
And also the min cut::
4905
4906
sage: g.edge_connectivity() == min(t.edge_labels())
4907
True
4908
"""
4909
return self._gomory_hu_tree(method=method)
4910
4911
4912
def two_factor_petersen(self):
4913
r"""
4914
Returns a decomposition of the graph into 2-factors.
4915
4916
Petersen's 2-factor decomposition theorem asserts that any
4917
`2r`-regular graph `G` can be decomposed into 2-factors.
4918
Equivalently, it means that the edges of any `2r`-regular
4919
graphs can be partitionned in `r` sets `C_1,\dots,C_r` such
4920
that for all `i`, the set `C_i` is a disjoint union of cycles
4921
( a 2-regular graph ).
4922
4923
As any graph of maximal degree `\Delta` can be completed into
4924
a regular graph of degree `2\lceil\frac\Delta 2\rceil`, this
4925
result also means that the edges of any graph of degree `\Delta`
4926
can be partitionned in `r=2\lceil\frac\Delta 2\rceil` sets
4927
`C_1,\dots,C_r` such that for all `i`, the set `C_i` is a
4928
graph of maximal degree `2` ( a disjoint union of paths
4929
and cycles ).
4930
4931
EXAMPLE:
4932
4933
The Complete Graph on `7` vertices is a `6`-regular graph, so it can
4934
be edge-partitionned into `2`-regular graphs::
4935
4936
sage: g = graphs.CompleteGraph(7)
4937
sage: classes = g.two_factor_petersen()
4938
sage: for c in classes:
4939
... gg = Graph()
4940
... gg.add_edges(c)
4941
... print max(gg.degree())<=2
4942
True
4943
True
4944
True
4945
sage: Set(set(classes[0]) | set(classes[1]) | set(classes[2])).cardinality() == g.size()
4946
True
4947
4948
::
4949
4950
sage: g = graphs.CirculantGraph(24, [7, 11])
4951
sage: cl = g.two_factor_petersen()
4952
sage: g.plot(edge_colors={'black':cl[0], 'red':cl[1]})
4953
4954
"""
4955
4956
d = self.eulerian_orientation()
4957
4958
# This new graph is bipartite, and built the following way :
4959
#
4960
# To each vertex v of the digraph are associated two vertices,
4961
# a sink (-1,v) and a source (1,v)
4962
# Any edge (u,v) in the digraph is then added as ((-1,u),(1,v))
4963
4964
from sage.graphs.graph import Graph
4965
g = Graph()
4966
g.add_edges([((-1,u),(1,v)) for (u,v) in d.edge_iterator(labels=None)])
4967
4968
# This new bipartite graph is now edge_colored
4969
from sage.graphs.graph_coloring import edge_coloring
4970
classes = edge_coloring(g)
4971
4972
# The edges in the classes are of the form ((-1,u),(1,v))
4973
# and have to be translated back to (u,v)
4974
classes_b = []
4975
for c in classes:
4976
classes_b.append([(u,v) for ((uu,u),(vv,v)) in c])
4977
4978
return classes_b
4979
4980
def compare_edges(x, y):
4981
"""
4982
Compare edge x to edge y, return -1 if x y, 1 if x y, else 0.
4983
4984
EXAMPLES::
4985
4986
sage: G = graphs.PetersenGraph()
4987
sage: E = G.edges()
4988
sage: from sage.graphs.graph import compare_edges
4989
sage: compare_edges(E[0], E[2])
4990
-1
4991
sage: compare_edges(E[0], E[1])
4992
-1
4993
sage: compare_edges(E[0], E[0])
4994
0
4995
sage: compare_edges(E[1], E[0])
4996
1
4997
"""
4998
if x[1] < y[1]:
4999
return -1
5000
elif x[1] > y[1]:
5001
return 1
5002
elif x[1] == y[1]:
5003
if x[0] < y[0]:
5004
return -1
5005
if x[0] > y[0]:
5006
return 1
5007
else:
5008
return 0
5009
5010
5011