Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/graphs/digraph.py
8815 views
1
r"""
2
Directed graphs
3
4
This module implements functions and operations involving directed
5
graphs. Here is what they can do
6
7
**Graph basic operations:**
8
9
.. csv-table::
10
:class: contentstable
11
:widths: 30, 70
12
:delim: |
13
14
:meth:`~DiGraph.layout_acyclic_dummy` | Computes a (dummy) ranked layout so that all edges point upward.
15
:meth:`~DiGraph.layout_acyclic` | Computes a ranked layout so that all edges point upward.
16
:meth:`~DiGraph.reverse` | Returns a copy of digraph with edges reversed in direction.
17
:meth:`~DiGraph.reverse_edge` | Reverses an edge.
18
:meth:`~DiGraph.reverse_edges` | Reverses a list of edges.
19
:meth:`~DiGraph.out_degree_sequence` | Return the outdegree sequence.
20
:meth:`~DiGraph.out_degree_iterator` | Same as degree_iterator, but for out degree.
21
:meth:`~DiGraph.out_degree` | Same as degree, but for out degree.
22
:meth:`~DiGraph.in_degree_sequence` | Return the indegree sequence of this digraph.
23
:meth:`~DiGraph.in_degree_iterator` | Same as degree_iterator, but for in degree.
24
:meth:`~DiGraph.in_degree` | Same as degree, but for in-degree.
25
:meth:`~DiGraph.neighbors_out` | Returns the list of the out-neighbors of a given vertex.
26
:meth:`~DiGraph.neighbor_out_iterator` | Returns an iterator over the out-neighbors of a given vertex.
27
:meth:`~DiGraph.neighbors_in` | Returns the list of the in-neighbors of a given vertex.
28
:meth:`~DiGraph.neighbor_in_iterator` | Returns an iterator over the in-neighbors of vertex.
29
:meth:`~DiGraph.outgoing_edges` | Returns a list of edges departing from vertices.
30
:meth:`~DiGraph.outgoing_edge_iterator` | Return an iterator over all departing edges from vertices
31
:meth:`~DiGraph.incoming_edges` | Returns a list of edges arriving at vertices.
32
:meth:`~DiGraph.incoming_edge_iterator` | Return an iterator over all arriving edges from vertices
33
:meth:`~DiGraph.to_undirected` | Returns an undirected version of the graph.
34
:meth:`~DiGraph.to_directed` | Since the graph is already directed, simply returns a copy of itself.
35
:meth:`~DiGraph.is_directed` | Since digraph is directed, returns True.
36
:meth:`~DiGraph.dig6_string` | Returns the dig6 representation of the digraph as an ASCII string.
37
38
39
**Paths and cycles:**
40
41
.. csv-table::
42
:class: contentstable
43
:widths: 30, 70
44
:delim: |
45
46
:meth:`~DiGraph.all_paths_iterator` | Returns an iterator over the paths of self. The paths are
47
:meth:`~DiGraph.all_simple_paths` | Returns a list of all the simple paths of self starting
48
:meth:`~DiGraph.all_cycles_iterator` | Returns an iterator over all the cycles of self starting
49
:meth:`~DiGraph.all_simple_cycles` | Returns a list of all simple cycles of self.
50
51
52
**Connectivity:**
53
54
.. csv-table::
55
:class: contentstable
56
:widths: 30, 70
57
:delim: |
58
59
:meth:`~DiGraph.is_strongly_connected` | Returns whether the current ``DiGraph`` is strongly connected.
60
:meth:`~DiGraph.strongly_connected_components_digraph` | Returns the digraph of the strongly connected components
61
:meth:`~DiGraph.strongly_connected_components_subgraphs` | Returns the strongly connected components as a list of subgraphs.
62
:meth:`~DiGraph.strongly_connected_component_containing_vertex` | Returns the strongly connected component containing a given vertex
63
:meth:`~DiGraph.strongly_connected_components` | Returns the list of strongly connected components.
64
65
66
**Acyclicity:**
67
68
.. csv-table::
69
:class: contentstable
70
:widths: 30, 70
71
:delim: |
72
73
:meth:`~DiGraph.is_directed_acyclic` | Returns whether the digraph is acyclic or not.
74
:meth:`~DiGraph.is_transitive` | Returns whether the digraph is transitive or not.
75
:meth:`~DiGraph.level_sets` | Returns the level set decomposition of the digraph.
76
:meth:`~DiGraph.topological_sort_generator` | Returns a list of all topological sorts of the digraph if it is acyclic
77
:meth:`~DiGraph.topological_sort` | Returns a topological sort of the digraph if it is acyclic
78
79
80
**Hard stuff:**
81
82
.. csv-table::
83
:class: contentstable
84
:widths: 30, 70
85
:delim: |
86
87
:meth:`~DiGraph.feedback_edge_set` | Computes the minimum feedback edge (arc) set of a digraph
88
89
Methods
90
-------
91
"""
92
93
from sage.rings.integer import Integer
94
from sage.misc.superseded import deprecated_function_alias
95
from sage.misc.superseded import deprecation
96
import sage.graphs.generic_graph_pyx as generic_graph_pyx
97
from sage.graphs.generic_graph import GenericGraph
98
from sage.graphs.dot2tex_utils import have_dot2tex
99
100
class DiGraph(GenericGraph):
101
"""
102
Directed graph.
103
104
A digraph or directed graph is a set of vertices connected by oriented
105
edges. For more information, see the
106
`Wikipedia article on digraphs
107
<http://en.wikipedia.org/wiki/Digraph_%28mathematics%29>`_.
108
109
One can very easily create a directed graph in Sage by typing::
110
111
sage: g = DiGraph()
112
113
By typing the name of the digraph, one can get some basic information
114
about it::
115
116
sage: g
117
Digraph on 0 vertices
118
119
This digraph is not very interesting as it is by default the empty
120
graph. But Sage contains several pre-defined digraph classes that can
121
be listed this way:
122
123
* Within a Sage sessions, type ``digraphs.``
124
(Do not press "Enter", and do not forget the final period "." )
125
* Hit "tab".
126
127
You will see a list of methods which will construct named digraphs. For
128
example::
129
130
sage: g = digraphs.ButterflyGraph(3)
131
sage: g.plot()
132
133
You can also use the collection of pre-defined graphs, then create a
134
digraph from them. ::
135
136
sage: g = DiGraph(graphs.PetersenGraph())
137
sage: g.plot()
138
139
Calling ``Digraph`` on a graph returns the original graph in which every
140
edge is replaced by two different edges going toward opposite directions.
141
142
In order to obtain more information about these digraph constructors,
143
access the documentation by typing ``digraphs.RandomDirectedGNP?``.
144
145
Once you have defined the digraph you want, you can begin to work on it
146
by using the almost 200 functions on graphs and digraphs in the Sage
147
library! If your digraph is named ``g``, you can list these functions as
148
previously this way
149
150
* Within a Sage session, type ``g.``
151
(Do not press "Enter", and do not forget the final period "." )
152
* Hit "tab".
153
154
As usual, you can get some information about what these functions do by
155
typing (e.g. if you want to know about the ``diameter()`` method)
156
``g.diameter?``.
157
158
If you have defined a digraph ``g`` having several connected components
159
( i.e. ``g.is_connected()`` returns False ), you can print each one of its
160
connected components with only two lines::
161
162
sage: for component in g.connected_components():
163
... g.subgraph(component).plot()
164
165
The same methods works for strongly connected components ::
166
167
sage: for component in g.strongly_connected_components():
168
... g.subgraph(component).plot()
169
170
171
INPUT:
172
173
- ``data`` - can be any of the following (see the ``format`` keyword):
174
175
#. A dictionary of dictionaries
176
177
#. A dictionary of lists
178
179
#. A numpy matrix or ndarray
180
181
#. A Sage adjacency matrix or incidence matrix
182
183
#. A pygraphviz graph
184
185
#. A SciPy sparse matrix
186
187
#. A NetworkX digraph
188
189
- ``pos`` - a positioning dictionary: for example, the
190
spring layout from NetworkX for the 5-cycle is::
191
192
{0: [-0.91679746, 0.88169588],
193
1: [ 0.47294849, 1.125 ],
194
2: [ 1.125 ,-0.12867615],
195
3: [ 0.12743933,-1.125 ],
196
4: [-1.125 ,-0.50118505]}
197
198
- ``name`` - (must be an explicitly named parameter,
199
i.e., name="complete") gives the graph a name
200
201
- ``loops`` - boolean, whether to allow loops (ignored
202
if data is an instance of the DiGraph class)
203
204
- ``multiedges`` - boolean, whether to allow multiple
205
edges (ignored if data is an instance of the DiGraph class)
206
207
- ``weighted`` - whether digraph thinks of itself as
208
weighted or not. See self.weighted()
209
210
- ``format`` - if None, DiGraph tries to guess- can be
211
several values, including:
212
213
- ``'adjacency_matrix'`` - a square Sage matrix M,
214
with M[i,j] equal to the number of edges {i,j}
215
216
- ``'incidence_matrix'`` - a Sage matrix, with one
217
column C for each edge, where if C represents {i, j}, C[i] is -1
218
and C[j] is 1
219
220
- ``'weighted_adjacency_matrix'`` - a square Sage
221
matrix M, with M[i,j] equal to the weight of the single edge {i,j}.
222
Given this format, weighted is ignored (assumed True).
223
224
- ``NX`` - data must be a NetworkX DiGraph.
225
226
.. NOTE::
227
228
As Sage's default edge labels is ``None`` while NetworkX uses
229
``{}``, the ``{}`` labels of a NetworkX digraph are automatically
230
set to ``None`` when it is converted to a Sage graph. This
231
behaviour can be overruled by setting the keyword
232
``convert_empty_dict_labels_to_None`` to ``False`` (it is
233
``True`` by default).
234
235
- ``boundary`` - a list of boundary vertices, if
236
empty, digraph is considered as a 'graph without boundary'
237
238
- ``implementation`` - what to use as a backend for
239
the graph. Currently, the options are either 'networkx' or
240
'c_graph'
241
242
- ``sparse`` (boolean) -- ``sparse=True`` is an alias for
243
``data_structure="sparse"``, and ``sparse=False`` is an alias for
244
``data_structure="dense"``.
245
246
- ``data_structure`` -- one of the following
247
248
* ``"dense"`` -- selects the :mod:`~sage.graphs.base.dense_graph`
249
backend.
250
251
* ``"sparse"`` -- selects the :mod:`~sage.graphs.base.sparse_graph`
252
backend.
253
254
* ``"static_sparse"`` -- selects the
255
:mod:`~sage.graphs.base.static_sparse_backend` (this backend is faster
256
than the sparse backend and smaller in memory, and it is immutable, so
257
that the resulting graphs can be used as dictionary keys).
258
259
*Only available when* ``implementation == 'c_graph'``
260
261
- ``immutable`` (boolean) -- whether to create a immutable digraph. Note
262
that ``immutable=True`` is actually a shortcut for
263
``data_structure='static_sparse'``. Set to ``False`` by default, only
264
available when ``implementation='c_graph'``
265
266
- ``vertex_labels`` - only for implementation == 'c_graph'.
267
Whether to allow any object as a vertex (slower), or
268
only the integers 0, ..., n-1, where n is the number of vertices.
269
270
- ``convert_empty_dict_labels_to_None`` - this arguments sets
271
the default edge labels used by NetworkX (empty dictionaries)
272
to be replaced by None, the default Sage edge label. It is
273
set to ``True`` iff a NetworkX graph is on the input.
274
275
EXAMPLES:
276
277
#. A dictionary of dictionaries::
278
279
sage: g = DiGraph({0:{1:'x',2:'z',3:'a'}, 2:{5:'out'}}); g
280
Digraph on 5 vertices
281
282
The labels ('x', 'z', 'a', 'out') are labels for edges. For
283
example, 'out' is the label for the edge from 2 to 5. Labels can be
284
used as weights, if all the labels share some common parent.
285
286
#. A dictionary of lists (or iterables)::
287
288
sage: g = DiGraph({0:[1,2,3], 2:[4]}); g
289
Digraph on 5 vertices
290
sage: g = DiGraph({0:(1,2,3), 2:(4,)}); g
291
Digraph on 5 vertices
292
293
#. A list of vertices and a function describing adjacencies. Note
294
that the list of vertices and the function must be enclosed in a
295
list (i.e., [list of vertices, function]).
296
297
We construct a graph on the integers 1 through 12 such that there
298
is a directed edge from i to j if and only if i divides j.
299
300
::
301
302
sage: g=DiGraph([[1..12],lambda i,j: i!=j and i.divides(j)])
303
sage: g.vertices()
304
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
305
sage: g.adjacency_matrix()
306
[0 1 1 1 1 1 1 1 1 1 1 1]
307
[0 0 0 1 0 1 0 1 0 1 0 1]
308
[0 0 0 0 0 1 0 0 1 0 0 1]
309
[0 0 0 0 0 0 0 1 0 0 0 1]
310
[0 0 0 0 0 0 0 0 0 1 0 0]
311
[0 0 0 0 0 0 0 0 0 0 0 1]
312
[0 0 0 0 0 0 0 0 0 0 0 0]
313
[0 0 0 0 0 0 0 0 0 0 0 0]
314
[0 0 0 0 0 0 0 0 0 0 0 0]
315
[0 0 0 0 0 0 0 0 0 0 0 0]
316
[0 0 0 0 0 0 0 0 0 0 0 0]
317
[0 0 0 0 0 0 0 0 0 0 0 0]
318
319
#. A numpy matrix or ndarray::
320
321
sage: import numpy
322
sage: A = numpy.array([[0,1,0],[1,0,0],[1,1,0]])
323
sage: DiGraph(A)
324
Digraph on 3 vertices
325
326
#. A Sage matrix: Note: If format is not specified, then Sage
327
assumes a square matrix is an adjacency matrix, and a nonsquare
328
matrix is an incidence matrix.
329
330
- an adjacency matrix::
331
332
sage: M = Matrix([[0, 1, 1, 1, 0],[0, 0, 0, 0, 0],[0, 0, 0, 0, 1],[0, 0, 0, 0, 0],[0, 0, 0, 0, 0]]); M
333
[0 1 1 1 0]
334
[0 0 0 0 0]
335
[0 0 0 0 1]
336
[0 0 0 0 0]
337
[0 0 0 0 0]
338
sage: DiGraph(M)
339
Digraph on 5 vertices
340
341
- an incidence matrix::
342
343
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
344
[-1 0 0 0 1]
345
[ 1 -1 0 0 0]
346
[ 0 1 -1 0 0]
347
[ 0 0 1 -1 0]
348
[ 0 0 0 1 -1]
349
[ 0 0 0 0 0]
350
sage: DiGraph(M)
351
Digraph on 6 vertices
352
353
#. A c_graph implemented DiGraph can be constructed from a
354
networkx implemented DiGraph::
355
356
sage: D = DiGraph({0:[1],1:[2],2:[0]}, implementation="networkx")
357
sage: E = DiGraph(D,implementation="c_graph")
358
sage: D == E
359
True
360
361
#. A dig6 string: Sage automatically recognizes whether a string is
362
in dig6 format, which is a directed version of graph6::
363
364
sage: D = DiGraph('IRAaDCIIOWEOKcPWAo')
365
sage: D
366
Digraph on 10 vertices
367
368
sage: D = DiGraph('IRAaDCIIOEOKcPWAo')
369
Traceback (most recent call last):
370
...
371
RuntimeError: The string (IRAaDCIIOEOKcPWAo) seems corrupt: for n = 10, the string is too short.
372
373
sage: D = DiGraph("IRAaDCI'OWEOKcPWAo")
374
Traceback (most recent call last):
375
...
376
RuntimeError: The string seems corrupt: valid characters are
377
?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
378
379
#. A NetworkX XDiGraph::
380
381
sage: import networkx
382
sage: g = networkx.MultiDiGraph({0:[1,2,3], 2:[4]})
383
sage: DiGraph(g)
384
Digraph on 5 vertices
385
386
387
#. A NetworkX digraph::
388
389
sage: import networkx
390
sage: g = networkx.DiGraph({0:[1,2,3], 2:[4]})
391
sage: DiGraph(g)
392
Digraph on 5 vertices
393
394
Note that in all cases, we copy the NetworkX structure.
395
396
::
397
398
sage: import networkx
399
sage: g = networkx.DiGraph({0:[1,2,3], 2:[4]})
400
sage: G = DiGraph(g, implementation='networkx')
401
sage: H = DiGraph(g, implementation='networkx')
402
sage: G._backend._nxg is H._backend._nxg
403
False
404
405
TESTS::
406
407
sage: DiGraph({0:[1,2,3], 2:[4]}).edges()
408
[(0, 1, None), (0, 2, None), (0, 3, None), (2, 4, None)]
409
sage: DiGraph({0:(1,2,3), 2:(4,)}).edges()
410
[(0, 1, None), (0, 2, None), (0, 3, None), (2, 4, None)]
411
sage: DiGraph({0:Set([1,2,3]), 2:Set([4])}).edges()
412
[(0, 1, None), (0, 2, None), (0, 3, None), (2, 4, None)]
413
414
Get rid of mutable default argument for `boundary` (:trac:`14794`)::
415
416
sage: D = DiGraph(boundary=None)
417
sage: D._boundary
418
[]
419
420
Demonstrate that digraphs using the static backend are equal to mutable
421
graphs but can be used as dictionary keys::
422
423
sage: import networkx
424
sage: g = networkx.DiGraph({0:[1,2,3], 2:[4]})
425
sage: G = DiGraph(g, implementation='networkx')
426
sage: G_imm = DiGraph(G, data_structure="static_sparse")
427
sage: H_imm = DiGraph(G, data_structure="static_sparse")
428
sage: H_imm is G_imm
429
False
430
sage: H_imm == G_imm == G
431
True
432
sage: {G_imm:1}[H_imm]
433
1
434
sage: {G_imm:1}[G]
435
Traceback (most recent call last):
436
...
437
TypeError: This graph is mutable, and thus not hashable. Create an
438
immutable copy by `g.copy(immutable=True)`
439
440
The error message states that one can also create immutable graphs by
441
specifying the ``immutable`` optional argument (not only by
442
``data_structure='static_sparse'`` as above)::
443
444
sage: J_imm = DiGraph(G, immutable=True)
445
sage: J_imm == G_imm
446
True
447
sage: type(J_imm._backend) == type(G_imm._backend)
448
True
449
450
"""
451
_directed = True
452
453
def __init__(self, data=None, pos=None, loops=None, format=None,
454
boundary=None, weighted=None, implementation='c_graph',
455
data_structure="sparse", vertex_labels=True, name=None,
456
multiedges=None, convert_empty_dict_labels_to_None=None,
457
sparse=True, immutable=False):
458
"""
459
TESTS::
460
461
sage: D = DiGraph()
462
sage: loads(dumps(D)) == D
463
True
464
465
sage: a = matrix(2,2,[1,2,0,1])
466
sage: DiGraph(a,sparse=True).adjacency_matrix() == a
467
True
468
469
sage: a = matrix(2,2,[3,2,0,1])
470
sage: DiGraph(a,sparse=True).adjacency_matrix() == a
471
True
472
473
The positions are copied when the DiGraph is built from
474
another DiGraph or from a Graph ::
475
476
sage: g = DiGraph(graphs.PetersenGraph())
477
sage: h = DiGraph(g)
478
sage: g.get_pos() == h.get_pos()
479
True
480
sage: g.get_pos() == graphs.PetersenGraph().get_pos()
481
True
482
483
Invalid sequence of edges given as an input (they do not all
484
have the same length)::
485
486
sage: g = DiGraph([(1,2),(2,3),(2,3,4)])
487
Traceback (most recent call last):
488
...
489
ValueError: Edges input must all follow the same format.
490
491
492
Two different labels given for the same edge in a graph
493
without multiple edges::
494
495
sage: g = Graph([(1,2,3),(1,2,4)], multiedges = False)
496
Traceback (most recent call last):
497
...
498
ValueError: Two different labels given for the same edge in a graph without multiple edges.
499
500
Detection of multiple edges::
501
502
sage: DiGraph([(1, 2, 0), (1,2,1)])
503
Multi-digraph on 2 vertices
504
sage: DiGraph([(1, 2, 0)])
505
Digraph on 2 vertices
506
507
An empty list or dictionary defines a simple graph (trac #10441 and #12910)::
508
509
sage: DiGraph([])
510
Digraph on 0 vertices
511
sage: DiGraph({})
512
Digraph on 0 vertices
513
sage: # not "Multi-digraph on 0 vertices"
514
515
Problem with weighted adjacency matrix (:trac:`13919`)::
516
517
sage: B = {0:{1:2,2:5,3:4},1:{2:2,4:7},2:{3:1,4:4,5:3},3:{5:4},4:{5:1,6:5},5:{4:1,6:7,5:1}}
518
sage: grafo3 = DiGraph(B,weighted=True)
519
sage: matad = grafo3.weighted_adjacency_matrix()
520
sage: grafo4 = DiGraph(matad,format = "adjacency_matrix", weighted=True)
521
sage: grafo4.shortest_path(0,6,by_weight=True)
522
[0, 1, 2, 5, 4, 6]
523
524
Building a DiGraph with ``immutable=False`` returns a mutable graph::
525
526
sage: g = graphs.PetersenGraph()
527
sage: g = DiGraph(g.edges(),immutable=False)
528
sage: g.add_edge("Hey", "Heyyyyyyy")
529
sage: {g:1}[g]
530
Traceback (most recent call last):
531
...
532
TypeError: This graph is mutable, and thus not hashable. Create an immutable copy by `g.copy(immutable=True)`
533
sage: copy(g) is g
534
False
535
sage: {g.copy(immutable=True):1}[g.copy(immutable=True)]
536
1
537
538
But building it with ``immutable=True`` returns an immutable graph::
539
540
sage: g = DiGraph(graphs.PetersenGraph(), immutable=True)
541
sage: g.add_edge("Hey", "Heyyyyyyy")
542
Traceback (most recent call last):
543
...
544
NotImplementedError
545
sage: {g:1}[g]
546
1
547
sage: copy(g) is g
548
True
549
550
"""
551
msg = ''
552
GenericGraph.__init__(self)
553
from sage.structure.element import is_Matrix
554
from sage.misc.misc import uniq
555
556
if sparse == False:
557
if data_structure != "sparse":
558
raise ValueError("The 'sparse' argument is an alias for "
559
"'data_structure'. Please do not define both.")
560
data_structure = "dense"
561
562
if format is None and isinstance(data, str):
563
format = 'dig6'
564
if data[:8] == ">>dig6<<":
565
data = data[8:]
566
if format is None and is_Matrix(data):
567
if data.is_square():
568
format = 'adjacency_matrix'
569
else:
570
format = 'incidence_matrix'
571
msg += "Non-symmetric or non-square matrix assumed to be an incidence matrix: "
572
if format is None and isinstance(data, DiGraph):
573
format = 'DiGraph'
574
from sage.graphs.all import Graph
575
if format is None and isinstance(data, Graph):
576
data = data.to_directed()
577
format = 'DiGraph'
578
if format is None and isinstance(data,list) and \
579
len(data)>=2 and callable(data[1]):
580
format = 'rule'
581
if format is None and isinstance(data,dict):
582
keys = data.keys()
583
if len(keys) == 0: format = 'dict_of_dicts'
584
else:
585
if isinstance(data[keys[0]], dict):
586
format = 'dict_of_dicts'
587
else:
588
format = 'dict_of_lists'
589
if format is None and hasattr(data, 'adj'):
590
import networkx
591
if isinstance(data, (networkx.Graph, networkx.MultiGraph)):
592
data = data.to_directed()
593
format = 'NX'
594
elif isinstance(data, (networkx.DiGraph, networkx.MultiDiGraph)):
595
format = 'NX'
596
if format is None and isinstance(data, (int, Integer)):
597
format = 'int'
598
if format is None and data is None:
599
format = 'int'
600
data = 0
601
602
# Input is a list of edges
603
if format is None and isinstance(data, list):
604
605
# If we are given a list (we assume it is a list of
606
# edges), we convert the problem to a dict_of_dicts or a
607
# dict_of_lists
608
609
edges = data
610
611
# Along the process, we are assuming all edges have the
612
# same length. If it is not true, a ValueError will occur
613
try:
614
615
# The list is empty
616
if not data:
617
data = {}
618
format = 'dict_of_dicts'
619
620
# The edges are not labelled
621
elif len(data[0]) == 2:
622
data = {}
623
for u,v in edges:
624
if not u in data:
625
data[u] = []
626
627
data[u].append(v)
628
629
format = 'dict_of_lists'
630
631
# The edges are labelled
632
elif len(data[0]) == 3:
633
data = {}
634
for u,v,l in edges:
635
636
if not u in data:
637
data[u] = {}
638
639
# Now the key exists, and is a dictionary ...
640
641
# If we notice for the first time that there
642
# are multiple edges, we update the whole
643
# dictionary so that data[u][v] is a list
644
645
if (multiedges is None and
646
(v in data[u])):
647
multiedges = True
648
for uu, dd in data.iteritems():
649
for vv, ddd in dd.iteritems():
650
dd[vv] = [ddd]
651
652
# If multiedges is set to False while the same
653
# edge is present in the list with different
654
# values of its label
655
elif (multiedges is False and
656
(v in data[u] and data[u][v] != l)):
657
raise ValueError("MULTIEDGE")
658
659
# Now we are behaving as if multiedges == None
660
# means multiedges = False. If something bad
661
# happens later, the whole dictionary will be
662
# updated anyway
663
664
if multiedges is True:
665
if v not in data[u]:
666
data[u][v] = []
667
668
data[u][v].append(l)
669
670
else:
671
data[u][v] = l
672
673
format = 'dict_of_dicts'
674
675
except ValueError as ve:
676
if str(ve) == "MULTIEDGE":
677
raise ValueError("Two different labels given for the same edge in a graph without multiple edges.")
678
else:
679
raise ValueError("Edges input must all follow the same format.")
680
681
if format is None:
682
import networkx
683
data = networkx.MultiDiGraph(data)
684
format = 'NX'
685
686
# At this point, format has been set.
687
688
# adjust for empty dicts instead of None in NetworkX default edge labels
689
if convert_empty_dict_labels_to_None is None:
690
convert_empty_dict_labels_to_None = (format == 'NX')
691
692
verts = None
693
694
if format == 'dig6':
695
if loops is None: loops = False
696
if weighted is None: weighted = False
697
if multiedges is None: multiedges = False
698
if not isinstance(data, str):
699
raise ValueError('If input format is dig6, then data must be a string.')
700
n = data.find('\n')
701
if n == -1:
702
n = len(data)
703
ss = data[:n]
704
n, s = generic_graph_pyx.N_inverse(ss)
705
m = generic_graph_pyx.D_inverse(s, n)
706
expected = n**2
707
if len(m) > expected:
708
raise RuntimeError("The string (%s) seems corrupt: for n = %d, the string is too long."%(ss,n))
709
elif len(m) < expected:
710
raise RuntimeError("The string (%s) seems corrupt: for n = %d, the string is too short."%(ss,n))
711
num_verts = n
712
elif format in ['adjacency_matrix', 'incidence_matrix']:
713
assert is_Matrix(data)
714
if format == 'weighted_adjacency_matrix':
715
if weighted is False:
716
raise ValueError("Format was weighted_adjacency_matrix but weighted was False.")
717
if weighted is None: weighted = True
718
if multiedges is None: multiedges = False
719
format = 'adjacency_matrix'
720
if format == 'adjacency_matrix':
721
entries = uniq(data.list())
722
for e in entries:
723
try:
724
e = int(e)
725
assert e >= 0
726
except Exception:
727
if weighted is False:
728
raise ValueError("Non-weighted digraph's"+
729
" adjacency matrix must have only nonnegative"+
730
" integer entries")
731
weighted = True
732
if multiedges is None: multiedges = False
733
break
734
735
if weighted is None:
736
weighted = False
737
738
if multiedges is None:
739
multiedges = ((not weighted) and sorted(entries) != [0,1])
740
741
for i in xrange(data.nrows()):
742
if data[i,i] != 0:
743
if loops is None: loops = True
744
elif not loops:
745
raise ValueError("Non-looped digraph's adjacency"+
746
" matrix must have zeroes on the diagonal.")
747
break
748
num_verts = data.nrows()
749
elif format == 'incidence_matrix':
750
try:
751
positions = []
752
for c in data.columns():
753
NZ = c.nonzero_positions()
754
if len(NZ) != 2:
755
msg += "There must be two nonzero entries (-1 & 1) per column."
756
assert False
757
L = uniq(c.list())
758
L.sort()
759
if L != [-1,0,1]:
760
msg += "Each column represents an edge: -1 goes to 1."
761
assert False
762
if c[NZ[0]] == -1:
763
positions.append(tuple(NZ))
764
else:
765
positions.append((NZ[1],NZ[0]))
766
if loops is None: loops = False
767
if weighted is None: weighted = False
768
if multiedges is None:
769
total = len(positions)
770
multiedges = ( len(uniq(positions)) < total )
771
except AssertionError:
772
raise ValueError(msg)
773
num_verts = data.nrows()
774
elif format == 'DiGraph':
775
if loops is None: loops = data.allows_loops()
776
elif not loops and data.has_loops():
777
raise ValueError("No loops but input digraph has loops.")
778
if multiedges is None: multiedges = data.allows_multiple_edges()
779
elif not multiedges:
780
e = data.edges(labels=False)
781
if len(e) != len(uniq(e)):
782
raise ValueError("No multiple edges but input digraph"+
783
" has multiple edges.")
784
if weighted is None: weighted = data.weighted()
785
num_verts = data.num_verts()
786
verts = data.vertex_iterator()
787
if data.get_pos() is not None:
788
pos = data.get_pos().copy()
789
elif format == 'rule':
790
f = data[1]
791
if loops is None: loops = any(f(v,v) for v in data[0])
792
if multiedges is None: multiedges = False
793
if weighted is None: weighted = False
794
num_verts = len(data[0])
795
verts = data[0]
796
elif format == 'dict_of_dicts':
797
if not all(isinstance(data[u], dict) for u in data):
798
raise ValueError("Input dict must be a consistent format.")
799
verts = set(data.keys())
800
if loops is None or loops is False:
801
for u in data:
802
if u in data[u]:
803
if loops is None: loops = True
804
elif loops is False:
805
raise ValueError("No loops but dict has loops.")
806
break
807
if loops is None: loops = False
808
if weighted is None: weighted = False
809
for u in data:
810
for v in data[u]:
811
if v not in verts: verts.add(v)
812
if multiedges is not False and not isinstance(data[u][v], list):
813
if multiedges is None: multiedges = False
814
if multiedges:
815
raise ValueError("Dict of dicts for multidigraph must be in the format {v : {u : list}}")
816
if multiedges is None and len(data) > 0:
817
multiedges = True
818
num_verts = len(verts)
819
elif format == 'dict_of_lists':
820
# convert to a dict of lists if not already one
821
if not all(isinstance(data[u], list) for u in data):
822
data = dict([u, list(data[u])] for u in data)
823
verts = set(data.keys())
824
if loops is None or loops is False:
825
for u in data:
826
if u in data[u]:
827
if loops is None: loops = True
828
elif loops is False:
829
raise ValueError("No loops but dict has loops.")
830
break
831
if loops is None: loops = False
832
if weighted is None: weighted = False
833
for u in data:
834
verts = verts.union([v for v in data[u] if v not in verts])
835
if len(uniq(data[u])) != len(data[u]):
836
if multiedges is False:
837
raise ValueError("Non-multidigraph input dict has multiple edges (%s,%s)"%(u, choice([v for v in data[u] if data[u].count(v) > 1])))
838
if multiedges is None: multiedges = True
839
if multiedges is None: multiedges = False
840
num_verts = len(verts)
841
elif format == 'NX':
842
if weighted is None:
843
if isinstance(data, networkx.DiGraph):
844
weighted = False
845
if multiedges is None:
846
multiedges = False
847
if loops is None:
848
loops = False
849
else:
850
weighted = True
851
if multiedges is None:
852
multiedges = data.multiedges
853
if loops is None:
854
loops = data.selfloops
855
num_verts = data.order()
856
verts = data.nodes()
857
data = data.adj
858
format = 'dict_of_dicts'
859
elif format == 'int':
860
if weighted is None: weighted = False
861
if multiedges is None: multiedges = False
862
if loops is None: loops = False
863
num_verts = data
864
865
# weighted, multiedges, loops, verts and num_verts should now be set
866
867
if implementation == 'networkx':
868
import networkx
869
from sage.graphs.base.graph_backends import NetworkXGraphBackend
870
if format == 'DiGraph':
871
self._backend = NetworkXGraphBackend(data.networkx_graph())
872
self._weighted = weighted
873
self.allow_loops(loops)
874
self.allow_multiple_edges(multiedges)
875
else:
876
self._backend = NetworkXGraphBackend(networkx.MultiDiGraph())
877
self._weighted = weighted
878
self.allow_loops(loops)
879
self.allow_multiple_edges(multiedges)
880
if verts is not None:
881
self.add_vertices(verts)
882
else:
883
self.add_vertices(range(num_verts))
884
elif implementation == 'c_graph':
885
if multiedges or weighted:
886
if data_structure == "dense":
887
raise RuntimeError("Multiedge and weighted c_graphs must be sparse.")
888
889
if immutable:
890
data_structure = 'static_sparse'
891
892
# If the data structure is static_sparse, we first build a graph
893
# using the sparse data structure, then reencode the resulting graph
894
# as a static sparse graph.
895
from sage.graphs.base.sparse_graph import SparseGraphBackend
896
from sage.graphs.base.dense_graph import DenseGraphBackend
897
if data_structure in ["sparse", "static_sparse"]:
898
CGB = SparseGraphBackend
899
elif data_structure == "dense":
900
CGB = DenseGraphBackend
901
else:
902
raise ValueError("data_structure must be equal to 'sparse', "
903
"'static_sparse' or 'dense'")
904
if format == 'DiGraph':
905
self._backend = CGB(0, directed=True)
906
self.add_vertices(verts)
907
self._weighted = weighted
908
self.allow_loops(loops, check=False)
909
self.allow_multiple_edges(multiedges, check=False)
910
for u,v,l in data.edge_iterator():
911
self._backend.add_edge(u,v,l,True)
912
else:
913
self._backend = CGB(0, directed=True)
914
if verts is not None:
915
self._backend = CGB(0, directed=True)
916
self.add_vertices(verts)
917
else:
918
self._backend = CGB(num_verts, directed=True)
919
self._weighted = weighted
920
self.allow_loops(loops, check=False)
921
self.allow_multiple_edges(multiedges, check=False)
922
self._backend.directed = True
923
else:
924
raise NotImplementedError("Supported implementations: networkx, c_graph.")
925
926
if format == 'dig6':
927
k = 0
928
for i in xrange(n):
929
for j in xrange(n):
930
if m[k] == '1':
931
self.add_edge(i, j)
932
k += 1
933
elif format == 'adjacency_matrix':
934
e = []
935
if weighted:
936
for i,j in data.nonzero_positions():
937
e.append((i,j,data[i][j]))
938
elif multiedges:
939
for i,j in data.nonzero_positions():
940
e += [(i,j)]*int(data[i][j])
941
else:
942
for i,j in data.nonzero_positions():
943
e.append((i,j))
944
self.add_edges(e)
945
elif format == 'incidence_matrix':
946
self.add_edges(positions)
947
elif format == 'DiGraph':
948
self.name(data.name())
949
elif format == 'rule':
950
verts = list(verts)
951
for u in xrange(num_verts):
952
for v in xrange(num_verts):
953
uu,vv = verts[u], verts[v]
954
if f(uu,vv):
955
self.add_edge(uu,vv)
956
elif format == 'dict_of_dicts':
957
if convert_empty_dict_labels_to_None:
958
for u in data:
959
for v in data[u]:
960
if multiedges:
961
self.add_edges([(u,v,l) for l in data[u][v]])
962
else:
963
self.add_edge((u,v,data[u][v] if data[u][v] != {} else None))
964
else:
965
for u in data:
966
for v in data[u]:
967
if multiedges:
968
self.add_edges([(u,v,l) for l in data[u][v]])
969
else:
970
self.add_edge((u,v,data[u][v]))
971
elif format == 'dict_of_lists':
972
for u in data:
973
for v in data[u]:
974
self.add_edge(u,v)
975
else:
976
assert format == 'int'
977
self._pos = pos
978
self._boundary = boundary if boundary is not None else []
979
if format != 'DiGraph' or name is not None:
980
self.name(name)
981
982
if data_structure == "static_sparse":
983
from sage.graphs.base.static_sparse_backend import StaticSparseBackend
984
ib = StaticSparseBackend(self, loops = loops, multiedges = multiedges)
985
self._backend = ib
986
self._immutable = True
987
988
### Formats
989
def dig6_string(self):
990
"""
991
Returns the dig6 representation of the digraph as an ASCII string.
992
Valid for single (no multiple edges) digraphs on 0 to 262143
993
vertices.
994
995
EXAMPLES::
996
997
sage: D = DiGraph()
998
sage: D.dig6_string()
999
'?'
1000
sage: D.add_edge(0,1)
1001
sage: D.dig6_string()
1002
'AO'
1003
"""
1004
n = self.order()
1005
if n > 262143:
1006
raise ValueError('dig6 format supports graphs on 0 to 262143 vertices only.')
1007
elif self.has_multiple_edges():
1008
raise ValueError('dig6 format does not support multiple edges.')
1009
else:
1010
return generic_graph_pyx.N(n) + generic_graph_pyx.R(self._bit_vector())
1011
1012
### Attributes
1013
1014
def is_directed(self):
1015
"""
1016
Since digraph is directed, returns True.
1017
1018
EXAMPLES::
1019
1020
sage: DiGraph().is_directed()
1021
True
1022
"""
1023
return True
1024
1025
### Properties
1026
1027
def is_directed_acyclic(self, certificate = False):
1028
"""
1029
Returns whether the digraph is acyclic or not.
1030
1031
A directed graph is acyclic if for any vertex `v`, there is no directed
1032
path that starts and ends at `v`. Every directed acyclic graph (DAG)
1033
corresponds to a partial ordering of its vertices, however multiple dags
1034
may lead to the same partial ordering.
1035
1036
INPUT:
1037
1038
- ``certificate`` -- whether to return a certificate (``False`` by
1039
default).
1040
1041
OUTPUT:
1042
1043
* When ``certificate=False``, returns a boolean value.
1044
1045
* When ``certificate=True`` :
1046
1047
* If the graph is acyclic, returns a pair ``(True, ordering)``
1048
where ``ordering`` is a list of the vertices such that ``u``
1049
appears before ``v`` in ``ordering`` if ``u, v`` is an edge.
1050
1051
* Else, returns a pair ``(False, cycle)`` where ``cycle`` is a
1052
list of vertices representing a circuit in the graph.
1053
1054
EXAMPLES:
1055
1056
At first, the following graph is acyclic::
1057
1058
sage: D = DiGraph({ 0:[1,2,3], 4:[2,5], 1:[8], 2:[7], 3:[7], 5:[6,7], 7:[8], 6:[9], 8:[10], 9:[10] })
1059
sage: D.plot(layout='circular').show()
1060
sage: D.is_directed_acyclic()
1061
True
1062
1063
Adding an edge from `9` to `7` does not change it::
1064
1065
sage: D.add_edge(9,7)
1066
sage: D.is_directed_acyclic()
1067
True
1068
1069
We can obtain as a proof an ordering of the vertices such that `u`
1070
appears before `v` if `uv` is an edge of the graph::
1071
1072
sage: D.is_directed_acyclic(certificate = True)
1073
(True, [4, 5, 6, 9, 0, 1, 2, 3, 7, 8, 10])
1074
1075
Adding an edge from 7 to 4, though, makes a difference::
1076
1077
sage: D.add_edge(7,4)
1078
sage: D.is_directed_acyclic()
1079
False
1080
1081
Indeed, it creates a circuit `7, 4, 5`::
1082
1083
sage: D.is_directed_acyclic(certificate = True)
1084
(False, [7, 4, 5])
1085
1086
Checking acyclic graphs are indeed acyclic ::
1087
1088
sage: def random_acyclic(n, p):
1089
... g = graphs.RandomGNP(n, p)
1090
... h = DiGraph()
1091
... h.add_edges([ ((u,v) if u<v else (v,u)) for u,v,_ in g.edges() ])
1092
... return h
1093
...
1094
sage: all( random_acyclic(100, .2).is_directed_acyclic() # long time
1095
... for i in range(50)) # long time
1096
True
1097
1098
TESTS:
1099
1100
What about loops?::
1101
1102
sage: g = digraphs.ButterflyGraph(3)
1103
sage: g.allow_loops(True)
1104
sage: g.is_directed_acyclic()
1105
True
1106
sage: g.add_edge(0,0)
1107
sage: g.is_directed_acyclic()
1108
False
1109
"""
1110
return self._backend.is_directed_acyclic(certificate = certificate)
1111
1112
def to_directed(self):
1113
"""
1114
Since the graph is already directed, simply returns a copy of
1115
itself.
1116
1117
EXAMPLES::
1118
1119
sage: DiGraph({0:[1,2,3],4:[5,1]}).to_directed()
1120
Digraph on 6 vertices
1121
"""
1122
from copy import copy
1123
return copy(self)
1124
1125
def to_undirected(self, implementation='c_graph', data_structure=None,
1126
sparse=None):
1127
"""
1128
Returns an undirected version of the graph. Every directed edge
1129
becomes an edge.
1130
1131
INPUT:
1132
1133
- ``implementation`` - string (default: 'networkx') the
1134
implementation goes here. Current options are only
1135
'networkx' or 'c_graph'.
1136
1137
- ``data_structure`` -- one of ``"sparse"``, ``"static_sparse"``, or
1138
``"dense"``. See the documentation of :class:`Graph` or
1139
:class:`DiGraph`.
1140
1141
- ``sparse`` (boolean) -- ``sparse=True`` is an alias for
1142
``data_structure="sparse"``, and ``sparse=False`` is an alias for
1143
``data_structure="dense"``.
1144
1145
EXAMPLES::
1146
1147
sage: D = DiGraph({0:[1,2],1:[0]})
1148
sage: G = D.to_undirected()
1149
sage: D.edges(labels=False)
1150
[(0, 1), (0, 2), (1, 0)]
1151
sage: G.edges(labels=False)
1152
[(0, 1), (0, 2)]
1153
"""
1154
if sparse != None:
1155
deprecation(14806,"The 'sparse' keyword has been deprecated, and "
1156
"is now replaced by 'data_structure' which has a different "
1157
"meaning. Please consult the documentation.")
1158
data_structure = "sparse" if sparse else "dense"
1159
1160
if data_structure is None:
1161
from sage.graphs.base.dense_graph import DenseGraphBackend
1162
from sage.graphs.base.sparse_graph import SparseGraphBackend
1163
if isinstance(self._backend, DenseGraphBackend):
1164
data_structure = "dense"
1165
elif isinstance(self._backend, SparseGraphBackend):
1166
data_structure = "sparse"
1167
else:
1168
data_structure = "static_sparse"
1169
from sage.graphs.all import Graph
1170
G = Graph(name=self.name(), pos=self._pos, boundary=self._boundary,
1171
multiedges=self.allows_multiple_edges(), loops=self.allows_loops(),
1172
implementation=implementation, data_structure=data_structure)
1173
G.name(self.name())
1174
G.add_vertices(self.vertex_iterator())
1175
G.add_edges(self.edge_iterator())
1176
if hasattr(self, '_embedding'):
1177
from copy import copy
1178
G._embedding = copy(self._embedding)
1179
G._weighted = self._weighted
1180
return G
1181
1182
### Edge Handlers
1183
1184
def incoming_edge_iterator(self, vertices, labels=True):
1185
"""
1186
Return an iterator over all arriving edges from vertices.
1187
1188
INPUT:
1189
1190
- ``vertices`` -- a vertex or a list of vertices
1191
1192
- ``labels`` (boolean) -- whether to return edges as pairs of vertices,
1193
or as triples containing the labels.
1194
1195
EXAMPLES::
1196
1197
sage: D = DiGraph( { 0: [1,2,3], 1: [0,2], 2: [3], 3: [4], 4: [0,5], 5: [1] } )
1198
sage: for a in D.incoming_edge_iterator([0]):
1199
... print a
1200
(1, 0, None)
1201
(4, 0, None)
1202
"""
1203
if vertices is None:
1204
vertices = self
1205
elif vertices in self:
1206
vertices = [vertices]
1207
else:
1208
vertices = [v for v in vertices if v in self]
1209
return self._backend.iterator_in_edges(vertices, labels)
1210
1211
def incoming_edges(self, vertices, labels=True):
1212
"""
1213
Returns a list of edges arriving at vertices.
1214
1215
INPUT:
1216
1217
- ``vertices`` -- a vertex or a list of vertices
1218
1219
- ``labels`` (boolean) -- whether to return edges as pairs of vertices,
1220
or as triples containing the labels.
1221
1222
EXAMPLES::
1223
1224
sage: D = DiGraph( { 0: [1,2,3], 1: [0,2], 2: [3], 3: [4], 4: [0,5], 5: [1] } )
1225
sage: D.incoming_edges([0])
1226
[(1, 0, None), (4, 0, None)]
1227
"""
1228
return list(self.incoming_edge_iterator(vertices, labels=labels))
1229
1230
def outgoing_edge_iterator(self, vertices, labels=True):
1231
"""
1232
Return an iterator over all departing edges from vertices.
1233
1234
INPUT:
1235
1236
- ``vertices`` -- a vertex or a list of vertices
1237
1238
- ``labels`` (boolean) -- whether to return edges as pairs of vertices,
1239
or as triples containing the labels.
1240
1241
EXAMPLES::
1242
1243
sage: D = DiGraph( { 0: [1,2,3], 1: [0,2], 2: [3], 3: [4], 4: [0,5], 5: [1] } )
1244
sage: for a in D.outgoing_edge_iterator([0]):
1245
... print a
1246
(0, 1, None)
1247
(0, 2, None)
1248
(0, 3, None)
1249
"""
1250
if vertices is None:
1251
vertices = self
1252
elif vertices in self:
1253
vertices = [vertices]
1254
else:
1255
vertices = [v for v in vertices if v in self]
1256
return self._backend.iterator_out_edges(vertices, labels)
1257
1258
def outgoing_edges(self, vertices, labels=True):
1259
"""
1260
Returns a list of edges departing from vertices.
1261
1262
INPUT:
1263
1264
- ``vertices`` -- a vertex or a list of vertices
1265
1266
- ``labels`` (boolean) -- whether to return edges as pairs of vertices,
1267
or as triples containing the labels.
1268
1269
EXAMPLES::
1270
1271
sage: D = DiGraph( { 0: [1,2,3], 1: [0,2], 2: [3], 3: [4], 4: [0,5], 5: [1] } )
1272
sage: D.outgoing_edges([0])
1273
[(0, 1, None), (0, 2, None), (0, 3, None)]
1274
"""
1275
return list(self.outgoing_edge_iterator(vertices, labels=labels))
1276
1277
def neighbor_in_iterator(self, vertex):
1278
"""
1279
Returns an iterator over the in-neighbors of vertex.
1280
1281
An vertex `u` is an in-neighbor of a vertex `v` if `uv` in an edge.
1282
1283
EXAMPLES::
1284
1285
sage: D = DiGraph( { 0: [1,2,3], 1: [0,2], 2: [3], 3: [4], 4: [0,5], 5: [1] } )
1286
sage: for a in D.neighbor_in_iterator(0):
1287
... print a
1288
1
1289
4
1290
"""
1291
return iter(set(self._backend.iterator_in_nbrs(vertex)))
1292
1293
predecessor_iterator = deprecated_function_alias(7634, neighbor_in_iterator)
1294
1295
def neighbors_in(self, vertex):
1296
"""
1297
Returns the list of the in-neighbors of a given vertex.
1298
1299
An vertex `u` is an in-neighbor of a vertex `v` if `uv` in an edge.
1300
1301
EXAMPLES::
1302
1303
sage: D = DiGraph( { 0: [1,2,3], 1: [0,2], 2: [3], 3: [4], 4: [0,5], 5: [1] } )
1304
sage: D.neighbors_in(0)
1305
[1, 4]
1306
"""
1307
return list(self.neighbor_in_iterator(vertex))
1308
1309
predecessors = deprecated_function_alias(7634, neighbors_in)
1310
1311
def neighbor_out_iterator(self, vertex):
1312
"""
1313
Returns an iterator over the out-neighbors of a given vertex.
1314
1315
An vertex `u` is an out-neighbor of a vertex `v` if `vu` in an edge.
1316
1317
EXAMPLES::
1318
1319
sage: D = DiGraph( { 0: [1,2,3], 1: [0,2], 2: [3], 3: [4], 4: [0,5], 5: [1] } )
1320
sage: for a in D.neighbor_out_iterator(0):
1321
... print a
1322
1
1323
2
1324
3
1325
"""
1326
return iter(set(self._backend.iterator_out_nbrs(vertex)))
1327
1328
successor_iterator = deprecated_function_alias(7634, neighbor_out_iterator)
1329
1330
def neighbors_out(self, vertex):
1331
"""
1332
Returns the list of the out-neighbors of a given vertex.
1333
1334
An vertex `u` is an out-neighbor of a vertex `v` if `vu` in an edge.
1335
1336
EXAMPLES::
1337
1338
sage: D = DiGraph( { 0: [1,2,3], 1: [0,2], 2: [3], 3: [4], 4: [0,5], 5: [1] } )
1339
sage: D.neighbors_out(0)
1340
[1, 2, 3]
1341
"""
1342
return list(self.neighbor_out_iterator(vertex))
1343
1344
successors = deprecated_function_alias(7634, neighbors_out)
1345
1346
### Degree functions
1347
1348
def in_degree(self, vertices=None, labels=False):
1349
"""
1350
Same as degree, but for in degree.
1351
1352
EXAMPLES::
1353
1354
sage: D = DiGraph( { 0: [1,2,3], 1: [0,2], 2: [3], 3: [4], 4: [0,5], 5: [1] } )
1355
sage: D.in_degree(vertices = [0,1,2], labels=True)
1356
{0: 2, 1: 2, 2: 2}
1357
sage: D.in_degree()
1358
[2, 2, 2, 2, 1, 1]
1359
sage: G = graphs.PetersenGraph().to_directed()
1360
sage: G.in_degree(0)
1361
3
1362
"""
1363
if vertices in self:
1364
for d in self.in_degree_iterator(vertices):
1365
return d # (weird, but works: only happens once!)
1366
elif labels:
1367
di = {}
1368
for v, d in self.in_degree_iterator(vertices, labels=labels):
1369
di[v] = d
1370
return di
1371
else:
1372
return list(self.in_degree_iterator(vertices, labels=labels))
1373
1374
def in_degree_iterator(self, vertices=None, labels=False):
1375
"""
1376
Same as degree_iterator, but for in degree.
1377
1378
EXAMPLES::
1379
1380
sage: D = graphs.Grid2dGraph(2,4).to_directed()
1381
sage: for i in D.in_degree_iterator():
1382
... print i
1383
3
1384
3
1385
2
1386
3
1387
2
1388
2
1389
2
1390
3
1391
sage: for i in D.in_degree_iterator(labels=True):
1392
... print i
1393
((0, 1), 3)
1394
((1, 2), 3)
1395
((0, 0), 2)
1396
((0, 2), 3)
1397
((1, 3), 2)
1398
((1, 0), 2)
1399
((0, 3), 2)
1400
((1, 1), 3)
1401
"""
1402
if vertices is None:
1403
vertices = self.vertex_iterator()
1404
elif vertices in self:
1405
d = 0
1406
for e in self.incoming_edge_iterator(vertices):
1407
d += 1
1408
yield d
1409
return
1410
if labels:
1411
for v in vertices:
1412
d = 0
1413
for e in self.incoming_edge_iterator(v):
1414
d += 1
1415
yield (v, d)
1416
else:
1417
for v in vertices:
1418
d = 0
1419
for e in self.incoming_edge_iterator(v):
1420
d += 1
1421
yield d
1422
1423
def in_degree_sequence(self):
1424
r"""
1425
Return the indegree sequence.
1426
1427
EXAMPLES:
1428
1429
The indegree sequences of two digraphs::
1430
1431
sage: g = DiGraph({1: [2, 5, 6], 2: [3, 6], 3: [4, 6], 4: [6], 5: [4, 6]})
1432
sage: g.in_degree_sequence()
1433
[5, 2, 1, 1, 1, 0]
1434
1435
::
1436
1437
sage: V = [2, 3, 5, 7, 8, 9, 10, 11]
1438
sage: E = [[], [8, 10], [11], [8, 11], [9], [], [], [2, 9, 10]]
1439
sage: g = DiGraph(dict(zip(V, E)))
1440
sage: g.in_degree_sequence()
1441
[2, 2, 2, 2, 1, 0, 0, 0]
1442
"""
1443
return sorted(self.in_degree_iterator(), reverse=True)
1444
1445
def out_degree(self, vertices=None, labels=False):
1446
"""
1447
Same as degree, but for out degree.
1448
1449
EXAMPLES::
1450
1451
sage: D = DiGraph( { 0: [1,2,3], 1: [0,2], 2: [3], 3: [4], 4: [0,5], 5: [1] } )
1452
sage: D.out_degree(vertices = [0,1,2], labels=True)
1453
{0: 3, 1: 2, 2: 1}
1454
sage: D.out_degree()
1455
[3, 2, 1, 1, 2, 1]
1456
sage: D.out_degree(2)
1457
1
1458
"""
1459
if vertices in self:
1460
1461
try:
1462
return self._backend.out_degree(vertices)
1463
1464
except AttributeError:
1465
for d in self.out_degree_iterator(vertices):
1466
return d # (weird, but works: only happens once!)
1467
1468
elif labels:
1469
di = {}
1470
for v, d in self.out_degree_iterator(vertices, labels=labels):
1471
di[v] = d
1472
return di
1473
else:
1474
return list(self.out_degree_iterator(vertices, labels=labels))
1475
1476
def out_degree_iterator(self, vertices=None, labels=False):
1477
"""
1478
Same as degree_iterator, but for out degree.
1479
1480
EXAMPLES::
1481
1482
sage: D = graphs.Grid2dGraph(2,4).to_directed()
1483
sage: for i in D.out_degree_iterator():
1484
... print i
1485
3
1486
3
1487
2
1488
3
1489
2
1490
2
1491
2
1492
3
1493
sage: for i in D.out_degree_iterator(labels=True):
1494
... print i
1495
((0, 1), 3)
1496
((1, 2), 3)
1497
((0, 0), 2)
1498
((0, 2), 3)
1499
((1, 3), 2)
1500
((1, 0), 2)
1501
((0, 3), 2)
1502
((1, 1), 3)
1503
"""
1504
if vertices is None:
1505
vertices = self.vertex_iterator()
1506
elif vertices in self:
1507
d = 0
1508
for e in self.outgoing_edge_iterator(vertices):
1509
d += 1
1510
yield d
1511
return
1512
if labels:
1513
for v in vertices:
1514
d = 0
1515
for e in self.outgoing_edge_iterator(v):
1516
d += 1
1517
yield (v, d)
1518
else:
1519
for v in vertices:
1520
d = 0
1521
for e in self.outgoing_edge_iterator(v):
1522
d += 1
1523
yield d
1524
1525
def out_degree_sequence(self):
1526
r"""
1527
Return the outdegree sequence of this digraph.
1528
1529
EXAMPLES:
1530
1531
The outdegree sequences of two digraphs::
1532
1533
sage: g = DiGraph({1: [2, 5, 6], 2: [3, 6], 3: [4, 6], 4: [6], 5: [4, 6]})
1534
sage: g.out_degree_sequence()
1535
[3, 2, 2, 2, 1, 0]
1536
1537
::
1538
1539
sage: V = [2, 3, 5, 7, 8, 9, 10, 11]
1540
sage: E = [[], [8, 10], [11], [8, 11], [9], [], [], [2, 9, 10]]
1541
sage: g = DiGraph(dict(zip(V, E)))
1542
sage: g.out_degree_sequence()
1543
[3, 2, 2, 1, 1, 0, 0, 0]
1544
"""
1545
return sorted(self.out_degree_iterator(), reverse=True)
1546
1547
1548
def feedback_edge_set(self, constraint_generation= True, value_only=False, solver=None, verbose=0):
1549
r"""
1550
Computes the minimum feedback edge set of a digraph (also called
1551
feedback arc set).
1552
1553
The minimum feedback edge set of a digraph is a set of edges that
1554
intersect all the circuits of the digraph. Equivalently, a minimum
1555
feedback arc set of a DiGraph is a set `S` of arcs such that the digraph
1556
`G-S` is acyclic. For more information, see the `Wikipedia article on
1557
feedback arc sets <http://en.wikipedia.org/wiki/Feedback_arc_set>`_.
1558
1559
INPUT:
1560
1561
- ``value_only`` -- boolean (default: ``False``)
1562
1563
- When set to ``True``, only the minimum cardinal of a minimum edge
1564
set is returned.
1565
1566
- When set to ``False``, the ``Set`` of edges of a minimal edge set is
1567
returned.
1568
1569
- ``constraint_generation`` (boolean) -- whether to use constraint
1570
generation when solving the Mixed Integer Linear Program (default:
1571
``True``).
1572
1573
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
1574
solver to be used. If set to ``None``, the default one is used. For
1575
more information on LP solvers and which default solver is used, see
1576
the method
1577
:meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>`
1578
of the class
1579
:class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`.
1580
1581
- ``verbose`` -- integer (default: ``0``). Sets the level of
1582
verbosity. Set to 0 by default, which means quiet.
1583
1584
ALGORITHM:
1585
1586
This problem is solved using Linear Programming, in two different
1587
ways. The first one is to solve the following formulation:
1588
1589
.. MATH::
1590
1591
\mbox{Minimize : }&\sum_{(u,v)\in G} b_{(u,v)}\\
1592
\mbox{Such that : }&\\
1593
&\forall (u,v)\in G, d_u-d_v+ n \cdot b_{(u,v)}\geq 0\\
1594
&\forall u\in G, 0\leq d_u\leq |G|\\
1595
1596
An explanation:
1597
1598
An acyclic digraph can be seen as a poset, and every poset has a linear
1599
extension. This means that in any acyclic digraph the vertices can be
1600
ordered with a total order `<` in such a way that if `(u,v)\in G`, then
1601
`u<v`.
1602
1603
Thus, this linear program is built in order to assign to each vertex `v`
1604
a number `d_v\in [0,\dots,n-1]` such that if there exists an edge
1605
`(u,v)\in G` such that `d_v<d_u`, then the edge `(u,v)` is removed.
1606
1607
The number of edges removed is then minimized, which is the objective.
1608
1609
(Constraint Generation)
1610
1611
If the parameter ``constraint_generation`` is enabled, a more efficient
1612
formulation is used :
1613
1614
.. MATH::
1615
1616
\mbox{Minimize : }&\sum_{(u,v)\in G} b_{(u,v)}\\
1617
\mbox{Such that : }&\\
1618
&\forall C\text{ circuits }\subseteq G, \sum_{uv\in C}b_{(u,v)}\geq 1\\
1619
1620
As the number of circuits contained in a graph is exponential, this LP
1621
is solved through constraint generation. This means that the solver is
1622
sequentially asked to solved the problem, knowing only a portion of the
1623
circuits contained in `G`, each time adding to the list of its
1624
constraints the circuit which its last answer had left intact.
1625
1626
EXAMPLES:
1627
1628
If the digraph is created from a graph, and hence is symmetric (if `uv`
1629
is an edge, then `vu` is an edge too), then obviously the cardinality of
1630
its feedback arc set is the number of edges in the first graph::
1631
1632
sage: cycle=graphs.CycleGraph(5)
1633
sage: dcycle=DiGraph(cycle)
1634
sage: cycle.size()
1635
5
1636
sage: dcycle.feedback_edge_set(value_only=True)
1637
5
1638
1639
And in this situation, for any edge `uv` of the first graph, `uv` of
1640
`vu` is in the returned feedback arc set::
1641
1642
sage: g = graphs.RandomGNP(5,.3)
1643
sage: dg = DiGraph(g)
1644
sage: feedback = dg.feedback_edge_set()
1645
sage: (u,v,l) = g.edge_iterator().next()
1646
sage: (u,v) in feedback or (v,u) in feedback
1647
True
1648
1649
TESTS:
1650
1651
Comparing with/without constraint generation. Also double-checks ticket :trac:`12833`::
1652
1653
sage: for i in range(20):
1654
... g = digraphs.RandomDirectedGNP(10,.3)
1655
... x = g.feedback_edge_set(value_only = True)
1656
... y = g.feedback_edge_set(value_only = True,
1657
... constraint_generation = False)
1658
... if x != y:
1659
... print "Oh my, oh my !"
1660
... break
1661
"""
1662
# It would be a pity to start a LP if the digraph is already acyclic
1663
if self.is_directed_acyclic():
1664
return 0 if value_only else []
1665
1666
from sage.numerical.mip import MixedIntegerLinearProgram
1667
1668
########################################
1669
# Constraint Generation Implementation #
1670
########################################
1671
if constraint_generation:
1672
1673
p = MixedIntegerLinearProgram(constraint_generation = True,
1674
maximization = False)
1675
1676
# An variable for each edge
1677
b = p.new_variable(binary = True, dim = 2)
1678
1679
# Variables are binary, and their coefficient in the objective is 1
1680
1681
p.set_objective( p.sum( b[u][v]
1682
for u,v in self.edges(labels = False)))
1683
1684
p.solve(log = verbose)
1685
1686
# For as long as we do not break because the digraph is
1687
# acyclic....
1688
while True:
1689
1690
# Building the graph without the edges removed by the LP
1691
h = DiGraph()
1692
for u,v in self.edges(labels = False):
1693
if p.get_values(b[u][v]) < .5:
1694
h.add_edge(u,v)
1695
1696
# Is the digraph acyclic ?
1697
isok, certificate = h.is_directed_acyclic(certificate = True)
1698
1699
# If so, we are done !
1700
if isok:
1701
break
1702
1703
if verbose:
1704
print "Adding a constraint on circuit : ",certificate
1705
1706
# There is a circuit left. Let's add the corresponding
1707
# constraint !
1708
1709
p.add_constraint(
1710
p.sum( b[u][v] for u,v in
1711
zip(certificate, certificate[1:] + [certificate[0]])),
1712
min = 1)
1713
1714
obj = p.solve(log = verbose)
1715
1716
if value_only:
1717
return Integer(round(obj))
1718
1719
else:
1720
1721
# listing the edges contained in the MFAS
1722
return [(u,v) for u,v in self.edges(labels = False)
1723
if p.get_values(b[u][v]) > .5]
1724
1725
######################################
1726
# Ordering-based MILP Implementation #
1727
######################################
1728
else:
1729
p=MixedIntegerLinearProgram(maximization=False, solver=solver)
1730
1731
b=p.new_variable(binary = True)
1732
d=p.new_variable(integer = True)
1733
1734
n=self.order()
1735
1736
for (u,v) in self.edges(labels=None):
1737
p.add_constraint(d[u]-d[v]+n*(b[(u,v)]),min=1)
1738
1739
for v in self:
1740
p.add_constraint(d[v] <= n)
1741
1742
p.set_objective(p.sum([b[(u,v)] for (u,v) in self.edges(labels=None)]))
1743
1744
if value_only:
1745
return Integer(round(p.solve(objective_only=True, log=verbose)))
1746
else:
1747
p.solve(log=verbose)
1748
1749
b_sol=p.get_values(b)
1750
1751
return [(u,v) for (u,v) in self.edges(labels=None) if b_sol[(u,v)]==1]
1752
1753
### Construction
1754
1755
def reverse(self):
1756
"""
1757
Returns a copy of digraph with edges reversed in direction.
1758
1759
EXAMPLES::
1760
1761
sage: D = DiGraph({ 0: [1,2,3], 1: [0,2], 2: [3], 3: [4], 4: [0,5], 5: [1] })
1762
sage: D.reverse()
1763
Reverse of (): Digraph on 6 vertices
1764
"""
1765
H = DiGraph(multiedges=self.allows_multiple_edges(), loops=self.allows_loops())
1766
H.add_vertices(self)
1767
H.add_edges( [ (v,u,d) for (u,v,d) in self.edge_iterator() ] )
1768
name = self.name()
1769
if name is None:
1770
name = ''
1771
H.name("Reverse of (%s)"%name)
1772
return H
1773
1774
def reverse_edge(self, u, v=None, label=None, inplace=True, multiedges=None):
1775
"""
1776
Reverses the edge from u to v.
1777
1778
INPUT:
1779
1780
- ``inplace`` -- (default: True) if ``False``, a new digraph is created
1781
and returned as output, otherwise ``self`` is modified.
1782
1783
- ``multiedges`` -- (default: None) how to decide what should be done in
1784
case of doubt (for instance when edge `(1,2)` is to be reversed in a
1785
graph while `(2,1)` already exists).
1786
1787
- If set to ``True``, input graph will be forced to allow parallel
1788
edges if necessary and edge `(1,2)` will appear twice in the graph.
1789
1790
- If set to ``False``, only one edge `(1,2)` will remain in the graph
1791
after `(2,1)` is reversed. Besides, the label of edge `(1,2)` will
1792
be overwritten with the label of edge `(2,1)`.
1793
1794
The default behaviour (``multiedges = None``) will raise an exception
1795
each time a subjective decision (setting ``multiedges`` to ``True``
1796
or ``False``) is necessary to perform the operation.
1797
1798
The following forms are all accepted:
1799
1800
- D.reverse_edge( 1, 2 )
1801
- D.reverse_edge( (1, 2) )
1802
- D.reverse_edge( [1, 2] )
1803
- D.reverse_edge( 1, 2, 'label' )
1804
- D.reverse_edge( ( 1, 2, 'label') )
1805
- D.reverse_edge( [1, 2, 'label'] )
1806
- D.reverse_edge( ( 1, 2), label='label') )
1807
1808
EXAMPLES:
1809
1810
If ``inplace`` is ``True`` (default value), ``self`` is modified::
1811
1812
sage: D = DiGraph([(0,1,2)])
1813
sage: D.reverse_edge(0,1)
1814
sage: D.edges()
1815
[(1, 0, 2)]
1816
1817
If ``inplace`` is ``False``, ``self`` is not modified
1818
and a new digraph is returned::
1819
1820
sage: D = DiGraph([(0,1,2)])
1821
sage: re = D.reverse_edge(0,1, inplace=False)
1822
sage: re.edges()
1823
[(1, 0, 2)]
1824
sage: D.edges()
1825
[(0, 1, 2)]
1826
1827
If ``multiedges`` is ``True``, ``self`` will be forced to allow parallel
1828
edges when and only when it is necessary::
1829
1830
sage: D = DiGraph( [(1, 2, 'A'), (2, 1, 'A'), (2, 3, None)] )
1831
sage: D.reverse_edge(1,2, multiedges=True)
1832
sage: D.edges()
1833
[(2, 1, 'A'), (2, 1, 'A'), (2, 3, None)]
1834
sage: D.allows_multiple_edges()
1835
True
1836
1837
Even if ``multiedges`` is ``True``, ``self`` will not be forced to allow
1838
parallel edges when it is not necessary::
1839
1840
sage: D = DiGraph( [(1,2,'A'), (2,1,'A'), (2, 3, None)] )
1841
sage: D.reverse_edge(2,3, multiedges=True)
1842
sage: D.edges()
1843
[(1, 2, 'A'), (2, 1, 'A'), (3, 2, None)]
1844
sage: D.allows_multiple_edges()
1845
False
1846
1847
If user specifies ``multiedges = False``, ``self`` will not be forced to
1848
allow parallel edges and a parallel edge will get deleted::
1849
1850
sage: D = DiGraph( [(1, 2, 'A'), (2, 1,'A'), (2, 3, None)] )
1851
sage: D.edges()
1852
[(1, 2, 'A'), (2, 1, 'A'), (2, 3, None)]
1853
sage: D.reverse_edge(1,2, multiedges=False)
1854
sage: D.edges()
1855
[(2, 1, 'A'), (2, 3, None)]
1856
1857
Note that in the following graph, specifying ``multiedges = False`` will
1858
result in overwriting the label of `(1,2)` with the label of `(2,1)`::
1859
1860
sage: D = DiGraph( [(1, 2, 'B'), (2, 1,'A'), (2, 3, None)] )
1861
sage: D.edges()
1862
[(1, 2, 'B'), (2, 1, 'A'), (2, 3, None)]
1863
sage: D.reverse_edge(2,1, multiedges=False)
1864
sage: D.edges()
1865
[(1, 2, 'A'), (2, 3, None)]
1866
1867
If input edge in digraph has weight/label, then the weight/label should
1868
be preserved in the output digraph. User does not need to specify the
1869
weight/label when calling function::
1870
1871
sage: D = DiGraph([[0,1,2],[1,2,1]], weighted=True)
1872
sage: D.reverse_edge(0,1)
1873
sage: D.edges()
1874
[(1, 0, 2), (1, 2, 1)]
1875
sage: re = D.reverse_edge([1,2],inplace=False)
1876
sage: re.edges()
1877
[(1, 0, 2), (2, 1, 1)]
1878
1879
If ``self`` has multiple copies (parallel edges) of the input edge, only
1880
1 of the parallel edges is reversed::
1881
1882
sage: D = DiGraph([(0,1,'01'),(0,1,'01'),(0,1,'cat'),(1,2,'12')], weighted = True, multiedges = true)
1883
sage: re = D.reverse_edge([0,1,'01'],inplace=False)
1884
sage: re.edges()
1885
[(0, 1, '01'), (0, 1, 'cat'), (1, 0, '01'), (1, 2, '12')]
1886
1887
If ``self`` has multiple copies (parallel edges) of the input edge but
1888
with distinct labels and no input label is specified, only 1 of the
1889
parallel edges is reversed (the edge that is labeled by the first label
1890
on the list returned by :meth:`.edge_label`)::
1891
1892
sage: D = DiGraph([(0,1,'A'),(0,1,'B'),(0,1,'mouse'),(0,1,'cat')], multiedges = true)
1893
sage: D.edge_label(0,1)
1894
['cat', 'mouse', 'B', 'A']
1895
sage: D.reverse_edge(0,1)
1896
sage: D.edges()
1897
[(0, 1, 'A'), (0, 1, 'B'), (0, 1, 'mouse'), (1, 0, 'cat')]
1898
1899
Finally, an exception is raised when Sage does not know how to chose
1900
between allowing multiple edges and losing some data::
1901
1902
sage: D = DiGraph([(0,1,'A'),(1,0,'B')])
1903
sage: D.reverse_edge(0,1)
1904
Traceback (most recent call last):
1905
...
1906
ValueError: Reversing the given edge is about to create two parallel
1907
edges but input digraph doesn't allow them - User needs to specify
1908
multiedges is True or False.
1909
1910
The following syntax is supported, but note that you must use
1911
the ``label`` keyword::
1912
1913
sage: D = DiGraph()
1914
sage: D.add_edge((1,2), label='label')
1915
sage: D.edges()
1916
[(1, 2, 'label')]
1917
sage: D.reverse_edge((1,2),label ='label')
1918
sage: D.edges()
1919
[(2, 1, 'label')]
1920
sage: D.add_edge((1,2),'label')
1921
sage: D.edges()
1922
[(2, 1, 'label'), ((1, 2), 'label', None)]
1923
sage: D.reverse_edge((1,2), 'label')
1924
sage: D.edges()
1925
[(2, 1, 'label'), ('label', (1, 2), None)]
1926
1927
TESTS::
1928
1929
sage: D = DiGraph([(0,1,None)])
1930
sage: D.reverse_edge(0,1,'mylabel')
1931
Traceback (most recent call last):
1932
...
1933
ValueError: Input edge must exist in the digraph.
1934
"""
1935
# Assigns the expected values to u,v, and label depending on the input.
1936
if label is None:
1937
if v is None:
1938
try:
1939
u, v, label = u
1940
except Exception:
1941
try:
1942
u, v = u
1943
except Exception:
1944
pass
1945
else:
1946
if v is None:
1947
try:
1948
u, v = u
1949
except Exception:
1950
pass
1951
1952
if not self.has_edge(u,v,label):
1953
raise ValueError, "Input edge must exist in the digraph."
1954
1955
tempG = self if inplace else self.copy()
1956
1957
if label == None:
1958
if not tempG.allows_multiple_edges():
1959
label = tempG.edge_label(u,v)
1960
else:
1961
# If digraph has parallel edges for input edge, pick the first
1962
# from the labels on the list
1963
label = tempG.edge_label(u,v)[0]
1964
1965
if ((not tempG.allows_multiple_edges()) and (tempG.has_edge(v,u))):
1966
# If user wants to force digraph to allow parallel edges
1967
if multiedges == True:
1968
tempG.allow_multiple_edges(True)
1969
tempG.delete_edge(u,v,label)
1970
tempG.add_edge(v,u,label)
1971
1972
# If user does not want to force digraph to allow parallel
1973
# edges, we delete edge u to v and overwrite v,u with the
1974
# label of u,v
1975
elif multiedges == False:
1976
tempG.delete_edge(u,v,label)
1977
tempG.set_edge_label(v,u,label)
1978
1979
# User is supposed to specify multiedges True or None
1980
else:
1981
raise ValueError("Reversing the given edge is about to "
1982
"create two parallel edges but input digraph "
1983
"doesn't allow them - User needs to specify "
1984
"multiedges is True or False.")
1985
else:
1986
tempG.delete_edge(u,v,label)
1987
tempG.add_edge(v,u,label)
1988
1989
if not inplace:
1990
return tempG
1991
1992
def reverse_edges(self, edges, inplace=True, multiedges=None):
1993
"""
1994
Reverses a list of edges.
1995
1996
INPUT:
1997
1998
- ``edges`` -- a list of edges in the DiGraph.
1999
2000
- ``inplace`` -- (default: True) if ``False``, a new digraph is created
2001
and returned as output, otherwise ``self`` is modified.
2002
2003
- ``multiedges`` -- (default: None) if ``True``, input graph will be
2004
forced to allow parallel edges when necessary (for more information
2005
see the documentation of :meth:`~DiGraph.reverse_edge`)
2006
2007
.. SEEALSO::
2008
2009
:meth:`~DiGraph.reverse_edge` - Reverses a single edge.
2010
2011
EXAMPLES:
2012
2013
If ``inplace`` is ``True`` (default value), ``self`` is modified::
2014
2015
sage: D = DiGraph({ 0: [1,1,3], 2: [3,3], 4: [1,5]}, multiedges = true)
2016
sage: D.reverse_edges( [ [0,1], [0,3] ])
2017
sage: D.reverse_edges( [ (2,3),(4,5) ])
2018
sage: D.edges()
2019
[(0, 1, None), (1, 0, None), (2, 3, None), (3, 0, None),
2020
(3, 2, None), (4, 1, None), (5, 4, None)]
2021
2022
If ``inplace`` is ``False``, ``self`` is not modified and a new digraph
2023
is returned::
2024
2025
sage: D = DiGraph ([(0,1,'A'),(1,0,'B'),(1,2,'C')])
2026
sage: re = D.reverse_edges( [ (0,1), (1,2) ],
2027
... inplace = False,
2028
... multiedges = True)
2029
sage: re.edges()
2030
[(1, 0, 'A'), (1, 0, 'B'), (2, 1, 'C')]
2031
sage: D.edges()
2032
[(0, 1, 'A'), (1, 0, 'B'), (1, 2, 'C')]
2033
sage: D.allows_multiple_edges()
2034
False
2035
sage: re.allows_multiple_edges()
2036
True
2037
2038
If ``multiedges`` is ``True``, ``self`` will be forced to allow parallel
2039
edges when and only when it is necessary::
2040
2041
sage: D = DiGraph( [(1, 2, 'A'), (2, 1, 'A'), (2, 3, None)] )
2042
sage: D.reverse_edges([(1,2),(2,3)], multiedges=True)
2043
sage: D.edges()
2044
[(2, 1, 'A'), (2, 1, 'A'), (3, 2, None)]
2045
sage: D.allows_multiple_edges()
2046
True
2047
2048
Even if ``multiedges`` is ``True``, ``self`` will not be forced to allow
2049
parallel edges when it is not necessary::
2050
2051
sage: D = DiGraph( [(1, 2, 'A'), (2, 1, 'A'), (2,3, None)] )
2052
sage: D.reverse_edges([(2,3)], multiedges=True)
2053
sage: D.edges()
2054
[(1, 2, 'A'), (2, 1, 'A'), (3, 2, None)]
2055
sage: D.allows_multiple_edges()
2056
False
2057
2058
If ``multiedges`` is ``False``, ``self`` will not be forced to allow
2059
parallel edges and an edge will get deleted::
2060
2061
sage: D = DiGraph( [(1,2), (2,1)] )
2062
sage: D.edges()
2063
[(1, 2, None), (2, 1, None)]
2064
sage: D.reverse_edges([(1,2)], multiedges=False)
2065
sage: D.edges()
2066
[(2, 1, None)]
2067
2068
If input edge in digraph has weight/label, then the weight/label should
2069
be preserved in the output digraph. User does not need to specify the
2070
weight/label when calling function::
2071
2072
sage: D = DiGraph([(0,1,'01'),(1,2,1),(2,3,'23')], weighted = True)
2073
sage: D.reverse_edges([(0,1,'01'),(1,2),(2,3)])
2074
sage: D.edges()
2075
[(1, 0, '01'), (2, 1, 1), (3, 2, '23')]
2076
2077
TESTS::
2078
2079
sage: D = digraphs.Circuit(6)
2080
sage: D.reverse_edges(D.edges(),inplace=False).edges()
2081
[(0, 5, None), (1, 0, None), (2, 1, None),
2082
(3, 2, None), (4, 3, None), (5, 4, None)]
2083
2084
sage: D = digraphs.Kautz(2,3)
2085
sage: Dr = D.reverse_edges(D.edges(),inplace=False,multiedges=True)
2086
sage: Dr.edges() == D.reverse().edges()
2087
True
2088
"""
2089
tempG = self if inplace else self.copy()
2090
for e in edges:
2091
tempG.reverse_edge(e,inplace=True,multiedges=multiedges)
2092
if not inplace:
2093
return tempG
2094
2095
### Paths and cycles iterators
2096
2097
def _all_paths_iterator(self, vertex, ending_vertices=None,
2098
simple=False, max_length=None, trivial=False):
2099
r"""
2100
Returns an iterator over the paths of self starting with the
2101
given vertex.
2102
2103
INPUT:
2104
2105
- ``vertex`` - the starting vertex of the paths.
2106
- ``ending_vertices`` - iterable (default: None) on the allowed
2107
ending vertices of the paths. If None, then all vertices are
2108
allowed.
2109
- ``simple`` - boolean (default: False). If set to True, then
2110
only simple paths are considered. Simple paths are paths in
2111
which no two arcs share a head or share a tail, i.e. every
2112
vertex in the path is entered at most once and exited at most
2113
once.
2114
- ``max_length`` - non negative integer (default: None). The
2115
maximum length of the enumerated paths. If set to None, then
2116
all lengths are allowed.
2117
- ``trivial`` - boolean (default: False). If set to True, then
2118
the empty paths are also enumerated.
2119
2120
OUTPUT:
2121
2122
iterator
2123
2124
EXAMPLES::
2125
2126
sage: g = DiGraph({'a' : ['a', 'b'], 'b' : ['c'], 'c' : ['d'], 'd' : ['c']}, loops=True)
2127
sage: pi = g._all_paths_iterator('a')
2128
sage: for _ in range(5): print pi.next()
2129
['a', 'a']
2130
['a', 'b']
2131
['a', 'a', 'a']
2132
['a', 'a', 'b']
2133
['a', 'b', 'c']
2134
2135
::
2136
2137
sage: pi = g._all_paths_iterator('b')
2138
sage: for _ in range(5): print pi.next()
2139
['b', 'c']
2140
['b', 'c', 'd']
2141
['b', 'c', 'd', 'c']
2142
['b', 'c', 'd', 'c', 'd']
2143
['b', 'c', 'd', 'c', 'd', 'c']
2144
2145
One may wish to enumerate simple paths, which are paths in which
2146
no two arcs share a head or share a tail, i.e. every vertex in
2147
the path is entered at most once and exited at most once. The
2148
result is always finite but may take a long time to compute::
2149
2150
sage: pi = g._all_paths_iterator('a', simple=True)
2151
sage: list(pi)
2152
[['a', 'a'], ['a', 'b'], ['a', 'b', 'c'], ['a', 'b', 'c', 'd']]
2153
sage: pi = g._all_paths_iterator('d', simple=True)
2154
sage: list(pi)
2155
[['d', 'c'], ['d', 'c', 'd']]
2156
2157
It is possible to specify the allowed ending vertices::
2158
2159
sage: pi = g._all_paths_iterator('a', ending_vertices=['c'])
2160
sage: for _ in range(5): print pi.next()
2161
['a', 'b', 'c']
2162
['a', 'a', 'b', 'c']
2163
['a', 'a', 'a', 'b', 'c']
2164
['a', 'b', 'c', 'd', 'c']
2165
['a', 'a', 'a', 'a', 'b', 'c']
2166
sage: pi = g._all_paths_iterator('a', ending_vertices=['a', 'b'])
2167
sage: for _ in range(5): print pi.next()
2168
['a', 'a']
2169
['a', 'b']
2170
['a', 'a', 'a']
2171
['a', 'a', 'b']
2172
['a', 'a', 'a', 'a']
2173
2174
One can bound the length of the paths::
2175
2176
sage: pi = g._all_paths_iterator('d', max_length=3)
2177
sage: list(pi)
2178
[['d', 'c'], ['d', 'c', 'd'], ['d', 'c', 'd', 'c']]
2179
2180
Or include the trivial empty path::
2181
2182
sage: pi = g._all_paths_iterator('a', max_length=3, trivial=True)
2183
sage: list(pi)
2184
[['a'], ['a', 'a'], ['a', 'b'], ['a', 'a', 'a'], ['a', 'a', 'b'],
2185
['a', 'b', 'c'], ['a', 'a', 'a', 'a'], ['a', 'a', 'a', 'b'],
2186
['a', 'a', 'b', 'c'], ['a', 'b', 'c', 'd']]
2187
"""
2188
if ending_vertices is None:
2189
ending_vertices = self
2190
if max_length is None:
2191
from sage.rings.infinity import Infinity
2192
max_length = Infinity
2193
if max_length < 1:
2194
return
2195
2196
# Start with the empty path; we will try all extensions of it
2197
queue = []
2198
path = [vertex]
2199
2200
if trivial and vertex in ending_vertices:
2201
yield path
2202
while True:
2203
# Build next generation of paths, one arc longer; max_length refers
2204
# to edges and not vertices, hence <= and not <
2205
if len(path) <= max_length:
2206
2207
# We try all possible extensions
2208
if simple:
2209
# We only keep simple extensions. An extension is simple
2210
# iff the new vertex being entered has not previously
2211
# occurred in the path, or has occurred but only been
2212
# exited (i.e. is the first vertex in the path). In this
2213
# latter case we must not exit the new vertex again, so we
2214
# do not consider it for further extension, but just yield
2215
# it immediately. See trac #12385.
2216
for neighbor in self.neighbor_out_iterator(path[-1]):
2217
if neighbor not in path:
2218
queue.append(path + [neighbor])
2219
elif ( neighbor == path[0] and
2220
neighbor in ending_vertices ):
2221
yield path + [neighbor]
2222
2223
else:
2224
# Non-simple paths requested: we add all of them
2225
for neighbor in self.neighbor_out_iterator(path[-1]):
2226
queue.append(path + [neighbor])
2227
2228
if not queue:
2229
break
2230
path = queue.pop(0) # get the next path
2231
2232
if path[-1] in ending_vertices:
2233
yield path # yield good path
2234
2235
2236
def all_paths_iterator(self, starting_vertices=None, ending_vertices=None,
2237
simple=False, max_length=None, trivial=False):
2238
r"""
2239
Returns an iterator over the paths of self. The paths are
2240
enumerated in increasing length order.
2241
2242
INPUT:
2243
2244
- ``starting_vertices`` - iterable (default: None) on the
2245
vertices from which the paths must start. If None, then all
2246
vertices of the graph can be starting points.
2247
- ``ending_vertices`` - iterable (default: None) on
2248
the allowed ending vertices of the paths. If None,
2249
then all vertices are allowed.
2250
- ``simple`` - boolean (default: False). If set to True,
2251
then only simple paths are considered. These are paths in
2252
which no two arcs share a head or share a tail, i.e. every
2253
vertex in the path is entered at most once and exited at most
2254
once.
2255
- ``max_length`` - non negative integer (default: None).
2256
The maximum length of the enumerated paths. If set to None,
2257
then all lengths are allowed.
2258
- ``trivial`` - boolean (default: False). If set to True,
2259
then the empty paths are also enumerated.
2260
2261
OUTPUT:
2262
2263
iterator
2264
2265
AUTHOR:
2266
2267
Alexandre Blondin Masse
2268
2269
EXAMPLES::
2270
2271
sage: g = DiGraph({'a' : ['a', 'b'], 'b' : ['c'], 'c' : ['d'], 'd' : ['c']}, loops=True)
2272
sage: pi = g.all_paths_iterator()
2273
sage: for _ in range(7): print pi.next()
2274
['a', 'a']
2275
['a', 'b']
2276
['b', 'c']
2277
['c', 'd']
2278
['d', 'c']
2279
['a', 'a', 'a']
2280
['a', 'a', 'b']
2281
2282
It is possible to precise the allowed starting and/or ending vertices::
2283
2284
sage: pi = g.all_paths_iterator(starting_vertices=['a'])
2285
sage: for _ in range(5): print pi.next()
2286
['a', 'a']
2287
['a', 'b']
2288
['a', 'a', 'a']
2289
['a', 'a', 'b']
2290
['a', 'b', 'c']
2291
sage: pi = g.all_paths_iterator(starting_vertices=['a'], ending_vertices=['b'])
2292
sage: for _ in range(5): print pi.next()
2293
['a', 'b']
2294
['a', 'a', 'b']
2295
['a', 'a', 'a', 'b']
2296
['a', 'a', 'a', 'a', 'b']
2297
['a', 'a', 'a', 'a', 'a', 'b']
2298
2299
One may prefer to enumerate only simple paths (see
2300
:meth:`all_simple_paths`)::
2301
2302
sage: pi = g.all_paths_iterator(simple=True)
2303
sage: list(pi)
2304
[['a', 'a'], ['a', 'b'], ['b', 'c'], ['c', 'd'], ['d', 'c'],
2305
['a', 'b', 'c'], ['b', 'c', 'd'], ['c', 'd', 'c'],
2306
['d', 'c', 'd'], ['a', 'b', 'c', 'd']]
2307
2308
Or simply bound the length of the enumerated paths::
2309
2310
sage: pi = g.all_paths_iterator(starting_vertices=['a'], ending_vertices=['b', 'c'], max_length=6)
2311
sage: list(pi)
2312
[['a', 'b'], ['a', 'a', 'b'], ['a', 'b', 'c'],
2313
['a', 'a', 'a', 'b'], ['a', 'a', 'b', 'c'],
2314
['a', 'a', 'a', 'a', 'b'], ['a', 'a', 'a', 'b', 'c'],
2315
['a', 'b', 'c', 'd', 'c'], ['a', 'a', 'a', 'a', 'a', 'b'],
2316
['a', 'a', 'a', 'a', 'b', 'c'], ['a', 'a', 'b', 'c', 'd', 'c'],
2317
['a', 'a', 'a', 'a', 'a', 'a', 'b'],
2318
['a', 'a', 'a', 'a', 'a', 'b', 'c'],
2319
['a', 'a', 'a', 'b', 'c', 'd', 'c'],
2320
['a', 'b', 'c', 'd', 'c', 'd', 'c']]
2321
2322
By default, empty paths are not enumerated, but it may be
2323
parametrized::
2324
2325
sage: pi = g.all_paths_iterator(simple=True, trivial=True)
2326
sage: list(pi)
2327
[['a'], ['b'], ['c'], ['d'], ['a', 'a'], ['a', 'b'], ['b', 'c'],
2328
['c', 'd'], ['d', 'c'], ['a', 'b', 'c'], ['b', 'c', 'd'],
2329
['c', 'd', 'c'], ['d', 'c', 'd'], ['a', 'b', 'c', 'd']]
2330
sage: pi = g.all_paths_iterator(simple=True, trivial=False)
2331
sage: list(pi)
2332
[['a', 'a'], ['a', 'b'], ['b', 'c'], ['c', 'd'], ['d', 'c'],
2333
['a', 'b', 'c'], ['b', 'c', 'd'], ['c', 'd', 'c'],
2334
['d', 'c', 'd'], ['a', 'b', 'c', 'd']]
2335
"""
2336
if starting_vertices is None:
2337
starting_vertices = self
2338
# We create one paths iterator per vertex
2339
# This is necessary if we want to iterate over paths
2340
# with increasing length
2341
vertex_iterators = dict([(v, self._all_paths_iterator(v, ending_vertices=ending_vertices, simple=simple, max_length=max_length, trivial=trivial)) for v in starting_vertices])
2342
paths = []
2343
for vi in vertex_iterators.values():
2344
try:
2345
path = vi.next()
2346
paths.append((len(path), path))
2347
except(StopIteration):
2348
pass
2349
# Since we always extract a shortest path, using a heap
2350
# can speed up the algorithm
2351
from heapq import heapify, heappop, heappush
2352
heapify(paths)
2353
while paths:
2354
# We choose the shortest available path
2355
_, shortest_path = heappop(paths)
2356
yield shortest_path
2357
# We update the path iterator to its next available path if it exists
2358
try:
2359
path = vertex_iterators[shortest_path[0]].next()
2360
heappush(paths, (len(path), path))
2361
except(StopIteration):
2362
pass
2363
2364
def all_simple_paths(self, starting_vertices=None, ending_vertices=None,
2365
max_length=None, trivial=False):
2366
r"""
2367
Returns a list of all the simple paths of self starting
2368
with one of the given vertices. Simple paths are paths in which
2369
no two arcs share a head or share a tail, i.e. every vertex in
2370
the path is entered at most once and exited at most once.
2371
2372
INPUT:
2373
2374
- ``starting_vertices`` - list (default: None) of vertices
2375
from which the paths must start. If None, then all
2376
vertices of the graph can be starting points.
2377
- ``ending_vertices`` - iterable (default: None) on
2378
the allowed ending vertices of the paths. If None,
2379
then all vertices are allowed.
2380
- ``max_length`` - non negative integer (default: None).
2381
The maximum length of the enumerated paths. If set to None,
2382
then all lengths are allowed.
2383
- ``trivial`` - boolean (default: False). If set to True,
2384
then the empty paths are also enumerated.
2385
2386
OUTPUT:
2387
2388
list
2389
2390
.. NOTE::
2391
2392
Although the number of simple paths of a finite graph
2393
is always finite, computing all its paths may take a very
2394
long time.
2395
2396
EXAMPLES::
2397
2398
sage: g = DiGraph({'a' : ['a', 'b'], 'b' : ['c'], 'c' : ['d'], 'd' : ['c']}, loops=True)
2399
sage: g.all_simple_paths()
2400
[['a', 'a'], ['a', 'b'], ['b', 'c'], ['c', 'd'], ['d', 'c'],
2401
['a', 'b', 'c'], ['b', 'c', 'd'], ['c', 'd', 'c'],
2402
['d', 'c', 'd'], ['a', 'b', 'c', 'd']]
2403
2404
One may compute all paths having specific starting and/or
2405
ending vertices::
2406
2407
sage: g.all_simple_paths(starting_vertices=['a'])
2408
[['a', 'a'], ['a', 'b'], ['a', 'b', 'c'], ['a', 'b', 'c', 'd']]
2409
sage: g.all_simple_paths(starting_vertices=['a'], ending_vertices=['c'])
2410
[['a', 'b', 'c']]
2411
sage: g.all_simple_paths(starting_vertices=['a'], ending_vertices=['b', 'c'])
2412
[['a', 'b'], ['a', 'b', 'c']]
2413
2414
It is also possible to bound the length of the paths::
2415
2416
sage: g.all_simple_paths(max_length=2)
2417
[['a', 'a'], ['a', 'b'], ['b', 'c'], ['c', 'd'], ['d', 'c'],
2418
['a', 'b', 'c'], ['b', 'c', 'd'], ['c', 'd', 'c'],
2419
['d', 'c', 'd']]
2420
2421
By default, empty paths are not enumerated, but this can
2422
be parametrized::
2423
2424
sage: g.all_simple_paths(starting_vertices=['a'], trivial=True)
2425
[['a'], ['a', 'a'], ['a', 'b'], ['a', 'b', 'c'],
2426
['a', 'b', 'c', 'd']]
2427
sage: g.all_simple_paths(starting_vertices=['a'], trivial=False)
2428
[['a', 'a'], ['a', 'b'], ['a', 'b', 'c'], ['a', 'b', 'c', 'd']]
2429
"""
2430
return list(self.all_paths_iterator(starting_vertices=starting_vertices, ending_vertices=ending_vertices, simple=True, max_length=max_length, trivial=trivial))
2431
2432
def _all_cycles_iterator_vertex(self, vertex, starting_vertices=None, simple=False,
2433
rooted=False, max_length=None, trivial=False,
2434
remove_acyclic_edges=True):
2435
r"""
2436
Returns an iterator over the cycles of self starting with the
2437
given vertex.
2438
2439
INPUT:
2440
2441
- ``vertex`` - the starting vertex of the cycle.
2442
- ``starting_vertices`` - iterable (default: None) on
2443
vertices from which the cycles must start. If None,
2444
then all vertices of the graph can be starting points.
2445
This argument is necessary if ``rooted`` is set to True.
2446
- ``simple`` - boolean (default: False). If set to True,
2447
then only simple cycles are considered. A cycle is simple
2448
if the only vertex occuring twice in it is the starting
2449
and ending one.
2450
- ``rooted`` - boolean (default: False). If set to False,
2451
then cycles differing only by their starting vertex are
2452
considered the same (e.g. ``['a', 'b', 'c', 'a']`` and
2453
``['b', 'c', 'a', 'b']``). Otherwise, all cycles are enumerated.
2454
- ``max_length`` - non negative integer (default: None).
2455
The maximum length of the enumerated cycles. If set to None,
2456
then all lengths are allowed.
2457
- ``trivial`` - boolean (default: False). If set to True,
2458
then the empty cycles are also enumerated.
2459
- ``remove_acyclic_edges`` - boolean (default: True) which
2460
precises if the acyclic edges must be removed from the graph.
2461
Used to avoid recomputing it for each vertex.
2462
2463
OUTPUT:
2464
2465
iterator
2466
2467
EXAMPLES::
2468
2469
sage: g = DiGraph({'a' : ['a', 'b'], 'b' : ['c'], 'c' : ['d'], 'd' : ['c']}, loops=True)
2470
sage: it = g._all_cycles_iterator_vertex('a', simple=False, max_length=None)
2471
sage: for i in range(5): print it.next()
2472
['a', 'a']
2473
['a', 'a', 'a']
2474
['a', 'a', 'a', 'a']
2475
['a', 'a', 'a', 'a', 'a']
2476
['a', 'a', 'a', 'a', 'a', 'a']
2477
sage: it = g._all_cycles_iterator_vertex('c', simple=False, max_length=None)
2478
sage: for i in range(5): print it.next()
2479
['c', 'd', 'c']
2480
['c', 'd', 'c', 'd', 'c']
2481
['c', 'd', 'c', 'd', 'c', 'd', 'c']
2482
['c', 'd', 'c', 'd', 'c', 'd', 'c', 'd', 'c']
2483
['c', 'd', 'c', 'd', 'c', 'd', 'c', 'd', 'c', 'd', 'c']
2484
2485
sage: it = g._all_cycles_iterator_vertex('d', simple=False, max_length=None)
2486
sage: for i in range(5): print it.next()
2487
['d', 'c', 'd']
2488
['d', 'c', 'd', 'c', 'd']
2489
['d', 'c', 'd', 'c', 'd', 'c', 'd']
2490
['d', 'c', 'd', 'c', 'd', 'c', 'd', 'c', 'd']
2491
['d', 'c', 'd', 'c', 'd', 'c', 'd', 'c', 'd', 'c', 'd']
2492
2493
It is possible to set a maximum length so that the number of cycles is
2494
finite::
2495
2496
sage: it = g._all_cycles_iterator_vertex('d', simple=False, max_length=6)
2497
sage: list(it)
2498
[['d', 'c', 'd'], ['d', 'c', 'd', 'c', 'd'], ['d', 'c', 'd', 'c', 'd', 'c', 'd']]
2499
2500
When ``simple`` is set to True, the number of cycles is finite since no vertex
2501
but the first one can occur more than once::
2502
2503
sage: it = g._all_cycles_iterator_vertex('d', simple=True, max_length=None)
2504
sage: list(it)
2505
[['d', 'c', 'd']]
2506
2507
By default, the empty cycle is not enumerated::
2508
2509
sage: it = g._all_cycles_iterator_vertex('d', simple=True, trivial=True)
2510
sage: list(it)
2511
[['d'], ['d', 'c', 'd']]
2512
"""
2513
if starting_vertices is None:
2514
starting_vertices = [vertex]
2515
# First enumerate the empty cycle
2516
if trivial:
2517
yield [vertex]
2518
# First we remove vertices and edges that are not part of any cycle
2519
if remove_acyclic_edges:
2520
sccs = self.strongly_connected_components()
2521
d = {}
2522
for id, component in enumerate(sccs):
2523
for v in component:
2524
d[v] = id
2525
h = self.copy()
2526
h.delete_edges([(u,v) for (u,v) in h.edge_iterator(labels=False) if d[u] != d[v]])
2527
else:
2528
h = self
2529
queue = [[vertex]]
2530
if max_length is None:
2531
from sage.rings.infinity import Infinity
2532
max_length = Infinity
2533
while queue:
2534
path = queue.pop(0)
2535
# Checks if a cycle has been found
2536
if len(path) > 1 and path[0] == path[-1]:
2537
yield path
2538
# Makes sure that the current cycle is not too long
2539
# Also if a cycle has been encountered and only simple cycles are allowed,
2540
# Then it discards the current path
2541
if len(path) <= max_length and (not simple or path.count(path[-1]) == 1):
2542
for neighbor in h.neighbor_out_iterator(path[-1]):
2543
# If cycles are not rooted, makes sure to keep only the minimum
2544
# cycle according to the lexicographic order
2545
if rooted or neighbor not in starting_vertices or path[0] <= neighbor:
2546
queue.append(path + [neighbor])
2547
2548
def all_cycles_iterator(self, starting_vertices=None, simple=False,
2549
rooted=False, max_length=None, trivial=False):
2550
r"""
2551
Returns an iterator over all the cycles of self starting
2552
with one of the given vertices. The cycles are enumerated
2553
in increasing length order.
2554
2555
INPUT:
2556
2557
- ``starting_vertices`` - iterable (default: None) on vertices
2558
from which the cycles must start. If None, then all
2559
vertices of the graph can be starting points.
2560
- ``simple`` - boolean (default: False). If set to True,
2561
then only simple cycles are considered. A cycle is simple
2562
if the only vertex occuring twice in it is the starting
2563
and ending one.
2564
- ``rooted`` - boolean (default: False). If set to False,
2565
then cycles differing only by their starting vertex are
2566
considered the same (e.g. ``['a', 'b', 'c', 'a']`` and
2567
``['b', 'c', 'a', 'b']``). Otherwise, all cycles are enumerated.
2568
- ``max_length`` - non negative integer (default: None).
2569
The maximum length of the enumerated cycles. If set to None,
2570
then all lengths are allowed.
2571
- ``trivial`` - boolean (default: False). If set to True,
2572
then the empty cycles are also enumerated.
2573
2574
OUTPUT:
2575
2576
iterator
2577
2578
.. NOTE::
2579
2580
See also :meth:`all_simple_cycles`.
2581
2582
AUTHOR:
2583
2584
Alexandre Blondin Masse
2585
2586
EXAMPLES::
2587
2588
sage: g = DiGraph({'a' : ['a', 'b'], 'b' : ['c'], 'c' : ['d'], 'd' : ['c']}, loops=True)
2589
sage: it = g.all_cycles_iterator()
2590
sage: for _ in range(7): print it.next()
2591
['a', 'a']
2592
['a', 'a', 'a']
2593
['c', 'd', 'c']
2594
['a', 'a', 'a', 'a']
2595
['a', 'a', 'a', 'a', 'a']
2596
['c', 'd', 'c', 'd', 'c']
2597
['a', 'a', 'a', 'a', 'a', 'a']
2598
2599
There are no cycles in the empty graph and in acyclic graphs::
2600
2601
sage: g = DiGraph()
2602
sage: it = g.all_cycles_iterator()
2603
sage: list(it)
2604
[]
2605
sage: g = DiGraph({0:[1]})
2606
sage: it = g.all_cycles_iterator()
2607
sage: list(it)
2608
[]
2609
2610
It is possible to restrict the starting vertices of the cycles::
2611
2612
sage: g = DiGraph({'a' : ['a', 'b'], 'b' : ['c'], 'c' : ['d'], 'd' : ['c']}, loops=True)
2613
sage: it = g.all_cycles_iterator(starting_vertices=['b', 'c'])
2614
sage: for _ in range(3): print it.next()
2615
['c', 'd', 'c']
2616
['c', 'd', 'c', 'd', 'c']
2617
['c', 'd', 'c', 'd', 'c', 'd', 'c']
2618
2619
Also, one can bound the length of the cycles::
2620
2621
sage: it = g.all_cycles_iterator(max_length=3)
2622
sage: list(it)
2623
[['a', 'a'], ['a', 'a', 'a'], ['c', 'd', 'c'],
2624
['a', 'a', 'a', 'a']]
2625
2626
By default, cycles differing only by their starting point are not all
2627
enumerated, but this may be parametrized::
2628
2629
sage: it = g.all_cycles_iterator(max_length=3, rooted=False)
2630
sage: list(it)
2631
[['a', 'a'], ['a', 'a', 'a'], ['c', 'd', 'c'],
2632
['a', 'a', 'a', 'a']]
2633
sage: it = g.all_cycles_iterator(max_length=3, rooted=True)
2634
sage: list(it)
2635
[['a', 'a'], ['a', 'a', 'a'], ['c', 'd', 'c'], ['d', 'c', 'd'],
2636
['a', 'a', 'a', 'a']]
2637
2638
One may prefer to enumerate simple cycles, i.e. cycles such that the only
2639
vertex occuring twice in it is the starting and ending one (see also
2640
:meth:`all_simple_cycles`)::
2641
2642
sage: it = g.all_cycles_iterator(simple=True)
2643
sage: list(it)
2644
[['a', 'a'], ['c', 'd', 'c']]
2645
sage: g = digraphs.Circuit(4)
2646
sage: list(g.all_cycles_iterator(simple=True))
2647
[[0, 1, 2, 3, 0]]
2648
"""
2649
if starting_vertices is None:
2650
starting_vertices = self
2651
# Since a cycle is always included in a given strongly connected
2652
# component, we may remove edges from the graph
2653
sccs = self.strongly_connected_components()
2654
d = {}
2655
for id, component in enumerate(sccs):
2656
for v in component:
2657
d[v] = id
2658
h = self.copy()
2659
h.delete_edges([ (u,v) for (u,v) in h.edge_iterator(labels=False)
2660
if d[u] != d[v] ])
2661
# We create one cycles iterator per vertex. This is necessary if we
2662
# want to iterate over cycles with increasing length.
2663
vertex_iterators = dict([(v, h._all_cycles_iterator_vertex( v
2664
, starting_vertices=starting_vertices
2665
, simple=simple
2666
, rooted=rooted
2667
, max_length=max_length
2668
, trivial=trivial
2669
, remove_acyclic_edges=False
2670
)) for v in starting_vertices])
2671
cycles = []
2672
for vi in vertex_iterators.values():
2673
try:
2674
cycle = vi.next()
2675
cycles.append((len(cycle), cycle))
2676
except(StopIteration):
2677
pass
2678
# Since we always extract a shortest path, using a heap
2679
# can speed up the algorithm
2680
from heapq import heapify, heappop, heappush
2681
heapify(cycles)
2682
while cycles:
2683
# We choose the shortest available cycle
2684
_, shortest_cycle = heappop(cycles)
2685
yield shortest_cycle
2686
# We update the cycle iterator to its next available cycle if it
2687
# exists
2688
try:
2689
cycle = vertex_iterators[shortest_cycle[0]].next()
2690
heappush(cycles, (len(cycle), cycle))
2691
except(StopIteration):
2692
pass
2693
2694
def all_simple_cycles(self, starting_vertices=None, rooted=False,
2695
max_length=None, trivial=False):
2696
r"""
2697
Returns a list of all simple cycles of self.
2698
2699
INPUT:
2700
2701
- ``starting_vertices`` - iterable (default: None) on vertices
2702
from which the cycles must start. If None, then all
2703
vertices of the graph can be starting points.
2704
- ``rooted`` - boolean (default: False). If set to False,
2705
then equivalent cycles are merged into one single cycle
2706
(the one starting with minimum vertex).
2707
Two cycles are called equivalent if they differ only from
2708
their starting vertex (e.g. ``['a', 'b', 'c', 'a']`` and
2709
``['b', 'c', 'a', 'b']``). Otherwise, all cycles are enumerated.
2710
- ``max_length`` - non negative integer (default: None).
2711
The maximum length of the enumerated cycles. If set to None,
2712
then all lengths are allowed.
2713
- ``trivial`` - boolean (default: False). If set to True,
2714
then the empty cycles are also enumerated.
2715
2716
OUTPUT:
2717
2718
list
2719
2720
.. NOTE::
2721
2722
Although the number of simple cycles of a finite graph is
2723
always finite, computing all its cycles may take a very long
2724
time.
2725
2726
EXAMPLES::
2727
2728
sage: g = DiGraph({'a' : ['a', 'b'], 'b' : ['c'], 'c' : ['d'], 'd' : ['c']}, loops=True)
2729
sage: g.all_simple_cycles()
2730
[['a', 'a'], ['c', 'd', 'c']]
2731
2732
The directed version of the Petersen graph::
2733
2734
sage: g = graphs.PetersenGraph().to_directed()
2735
sage: g.all_simple_cycles(max_length=4)
2736
[[0, 1, 0], [0, 4, 0], [0, 5, 0], [1, 2, 1], [1, 6, 1], [2, 3, 2],
2737
[2, 7, 2], [3, 8, 3], [3, 4, 3], [4, 9, 4], [5, 8, 5], [5, 7, 5],
2738
[6, 8, 6], [6, 9, 6], [7, 9, 7]]
2739
sage: g.all_simple_cycles(max_length=6)
2740
[[0, 1, 0], [0, 4, 0], [0, 5, 0], [1, 2, 1], [1, 6, 1], [2, 3, 2],
2741
[2, 7, 2], [3, 8, 3], [3, 4, 3], [4, 9, 4], [5, 8, 5], [5, 7, 5],
2742
[6, 8, 6], [6, 9, 6], [7, 9, 7], [0, 1, 2, 3, 4, 0],
2743
[0, 1, 2, 7, 5, 0], [0, 1, 6, 8, 5, 0], [0, 1, 6, 9, 4, 0],
2744
[0, 4, 9, 6, 1, 0], [0, 4, 9, 7, 5, 0], [0, 4, 3, 8, 5, 0],
2745
[0, 4, 3, 2, 1, 0], [0, 5, 8, 3, 4, 0], [0, 5, 8, 6, 1, 0],
2746
[0, 5, 7, 9, 4, 0], [0, 5, 7, 2, 1, 0], [1, 2, 3, 8, 6, 1],
2747
[1, 2, 7, 9, 6, 1], [1, 6, 8, 3, 2, 1], [1, 6, 9, 7, 2, 1],
2748
[2, 3, 8, 5, 7, 2], [2, 3, 4, 9, 7, 2], [2, 7, 9, 4, 3, 2],
2749
[2, 7, 5, 8, 3, 2], [3, 8, 6, 9, 4, 3], [3, 4, 9, 6, 8, 3],
2750
[5, 8, 6, 9, 7, 5], [5, 7, 9, 6, 8, 5], [0, 1, 2, 3, 8, 5, 0],
2751
[0, 1, 2, 7, 9, 4, 0], [0, 1, 6, 8, 3, 4, 0],
2752
[0, 1, 6, 9, 7, 5, 0], [0, 4, 9, 6, 8, 5, 0],
2753
[0, 4, 9, 7, 2, 1, 0], [0, 4, 3, 8, 6, 1, 0],
2754
[0, 4, 3, 2, 7, 5, 0], [0, 5, 8, 3, 2, 1, 0],
2755
[0, 5, 8, 6, 9, 4, 0], [0, 5, 7, 9, 6, 1, 0],
2756
[0, 5, 7, 2, 3, 4, 0], [1, 2, 3, 4, 9, 6, 1],
2757
[1, 2, 7, 5, 8, 6, 1], [1, 6, 8, 5, 7, 2, 1],
2758
[1, 6, 9, 4, 3, 2, 1], [2, 3, 8, 6, 9, 7, 2],
2759
[2, 7, 9, 6, 8, 3, 2], [3, 8, 5, 7, 9, 4, 3],
2760
[3, 4, 9, 7, 5, 8, 3]]
2761
2762
The complete graph (without loops) on `4` vertices::
2763
2764
sage: g = graphs.CompleteGraph(4).to_directed()
2765
sage: g.all_simple_cycles()
2766
[[0, 1, 0], [0, 2, 0], [0, 3, 0], [1, 2, 1], [1, 3, 1], [2, 3, 2],
2767
[0, 1, 2, 0], [0, 1, 3, 0], [0, 2, 1, 0], [0, 2, 3, 0],
2768
[0, 3, 1, 0], [0, 3, 2, 0], [1, 2, 3, 1], [1, 3, 2, 1],
2769
[0, 1, 2, 3, 0], [0, 1, 3, 2, 0], [0, 2, 1, 3, 0],
2770
[0, 2, 3, 1, 0], [0, 3, 1, 2, 0], [0, 3, 2, 1, 0]]
2771
2772
If the graph contains a large number of cycles, one can bound
2773
the length of the cycles, or simply restrict the possible
2774
starting vertices of the cycles::
2775
2776
sage: g = graphs.CompleteGraph(20).to_directed()
2777
sage: g.all_simple_cycles(max_length=2)
2778
[[0, 1, 0], [0, 2, 0], [0, 3, 0], [0, 4, 0], [0, 5, 0], [0, 6, 0],
2779
[0, 7, 0], [0, 8, 0], [0, 9, 0], [0, 10, 0], [0, 11, 0],
2780
[0, 12, 0], [0, 13, 0], [0, 14, 0], [0, 15, 0], [0, 16, 0],
2781
[0, 17, 0], [0, 18, 0], [0, 19, 0], [1, 2, 1], [1, 3, 1],
2782
[1, 4, 1], [1, 5, 1], [1, 6, 1], [1, 7, 1], [1, 8, 1], [1, 9, 1],
2783
[1, 10, 1], [1, 11, 1], [1, 12, 1], [1, 13, 1], [1, 14, 1],
2784
[1, 15, 1], [1, 16, 1], [1, 17, 1], [1, 18, 1], [1, 19, 1],
2785
[2, 3, 2], [2, 4, 2], [2, 5, 2], [2, 6, 2], [2, 7, 2], [2, 8, 2],
2786
[2, 9, 2], [2, 10, 2], [2, 11, 2], [2, 12, 2], [2, 13, 2],
2787
[2, 14, 2], [2, 15, 2], [2, 16, 2], [2, 17, 2], [2, 18, 2],
2788
[2, 19, 2], [3, 4, 3], [3, 5, 3], [3, 6, 3], [3, 7, 3], [3, 8, 3],
2789
[3, 9, 3], [3, 10, 3], [3, 11, 3], [3, 12, 3], [3, 13, 3],
2790
[3, 14, 3], [3, 15, 3], [3, 16, 3], [3, 17, 3], [3, 18, 3],
2791
[3, 19, 3], [4, 5, 4], [4, 6, 4], [4, 7, 4], [4, 8, 4], [4, 9, 4],
2792
[4, 10, 4], [4, 11, 4], [4, 12, 4], [4, 13, 4], [4, 14, 4],
2793
[4, 15, 4], [4, 16, 4], [4, 17, 4], [4, 18, 4], [4, 19, 4],
2794
[5, 6, 5], [5, 7, 5], [5, 8, 5], [5, 9, 5], [5, 10, 5],
2795
[5, 11, 5], [5, 12, 5], [5, 13, 5], [5, 14, 5], [5, 15, 5],
2796
[5, 16, 5], [5, 17, 5], [5, 18, 5], [5, 19, 5], [6, 7, 6],
2797
[6, 8, 6], [6, 9, 6], [6, 10, 6], [6, 11, 6], [6, 12, 6],
2798
[6, 13, 6], [6, 14, 6], [6, 15, 6], [6, 16, 6], [6, 17, 6],
2799
[6, 18, 6], [6, 19, 6], [7, 8, 7], [7, 9, 7], [7, 10, 7],
2800
[7, 11, 7], [7, 12, 7], [7, 13, 7], [7, 14, 7], [7, 15, 7],
2801
[7, 16, 7], [7, 17, 7], [7, 18, 7], [7, 19, 7], [8, 9, 8],
2802
[8, 10, 8], [8, 11, 8], [8, 12, 8], [8, 13, 8], [8, 14, 8],
2803
[8, 15, 8], [8, 16, 8], [8, 17, 8], [8, 18, 8], [8, 19, 8],
2804
[9, 10, 9], [9, 11, 9], [9, 12, 9], [9, 13, 9], [9, 14, 9],
2805
[9, 15, 9], [9, 16, 9], [9, 17, 9], [9, 18, 9], [9, 19, 9],
2806
[10, 11, 10], [10, 12, 10], [10, 13, 10], [10, 14, 10],
2807
[10, 15, 10], [10, 16, 10], [10, 17, 10], [10, 18, 10],
2808
[10, 19, 10], [11, 12, 11], [11, 13, 11], [11, 14, 11],
2809
[11, 15, 11], [11, 16, 11], [11, 17, 11], [11, 18, 11],
2810
[11, 19, 11], [12, 13, 12], [12, 14, 12], [12, 15, 12],
2811
[12, 16, 12], [12, 17, 12], [12, 18, 12], [12, 19, 12],
2812
[13, 14, 13], [13, 15, 13], [13, 16, 13], [13, 17, 13],
2813
[13, 18, 13], [13, 19, 13], [14, 15, 14], [14, 16, 14],
2814
[14, 17, 14], [14, 18, 14], [14, 19, 14], [15, 16, 15],
2815
[15, 17, 15], [15, 18, 15], [15, 19, 15], [16, 17, 16],
2816
[16, 18, 16], [16, 19, 16], [17, 18, 17], [17, 19, 17],
2817
[18, 19, 18]]
2818
sage: g = graphs.CompleteGraph(20).to_directed()
2819
sage: g.all_simple_cycles(max_length=2, starting_vertices=[0])
2820
[[0, 1, 0], [0, 2, 0], [0, 3, 0], [0, 4, 0], [0, 5, 0], [0, 6, 0],
2821
[0, 7, 0], [0, 8, 0], [0, 9, 0], [0, 10, 0], [0, 11, 0],
2822
[0, 12, 0], [0, 13, 0], [0, 14, 0], [0, 15, 0], [0, 16, 0],
2823
[0, 17, 0], [0, 18, 0], [0, 19, 0]]
2824
2825
One may prefer to distinguish equivalent cycles having distinct
2826
starting vertices (compare the following examples)::
2827
2828
sage: g = graphs.CompleteGraph(4).to_directed()
2829
sage: g.all_simple_cycles(max_length=2, rooted=False)
2830
[[0, 1, 0], [0, 2, 0], [0, 3, 0], [1, 2, 1], [1, 3, 1], [2, 3, 2]]
2831
sage: g.all_simple_cycles(max_length=2, rooted=True)
2832
[[0, 1, 0], [0, 2, 0], [0, 3, 0], [1, 0, 1], [1, 2, 1], [1, 3, 1],
2833
[2, 0, 2], [2, 1, 2], [2, 3, 2], [3, 0, 3], [3, 1, 3], [3, 2, 3]]
2834
"""
2835
return list(self.all_cycles_iterator(starting_vertices=starting_vertices, simple=True, rooted=rooted, max_length=max_length, trivial=trivial))
2836
2837
### Directed Acyclic Graphs (DAGs)
2838
2839
def topological_sort(self, implementation = "default"):
2840
"""
2841
Returns a topological sort of the digraph if it is acyclic, and
2842
raises a TypeError if the digraph contains a directed cycle. As
2843
topological sorts are not necessarily unique, different
2844
implementations may yield different results.
2845
2846
A topological sort is an ordering of the vertices of the digraph
2847
such that each vertex comes before all of its successors. That
2848
is, if `u` comes before `v` in the sort, then there may be
2849
a directed path from `u` to `v`, but there will be no directed
2850
path from `v` to `u`.
2851
2852
INPUT:
2853
2854
- ``implementation`` -- Use the default Cython implementation
2855
(``implementation = default``), the default NetworkX library
2856
(``implementation = "NetworkX"``) or the recursive NetworkX
2857
implementation (``implementation = "recursive"``)
2858
2859
.. SEEALSO::
2860
2861
- :meth:`is_directed_acyclic` -- Tests whether a directed
2862
graph is acyclic (can also join a certificate --
2863
a topological sort or a circuit in the graph1).
2864
2865
EXAMPLES::
2866
2867
sage: D = DiGraph({ 0:[1,2,3], 4:[2,5], 1:[8], 2:[7], 3:[7],
2868
... 5:[6,7], 7:[8], 6:[9], 8:[10], 9:[10] })
2869
sage: D.plot(layout='circular').show()
2870
sage: D.topological_sort()
2871
[4, 5, 6, 9, 0, 1, 2, 3, 7, 8, 10]
2872
2873
::
2874
2875
sage: D.add_edge(9,7)
2876
sage: D.topological_sort()
2877
[4, 5, 6, 9, 0, 1, 2, 3, 7, 8, 10]
2878
2879
Using the NetworkX implementation ::
2880
2881
sage: D.topological_sort(implementation = "NetworkX")
2882
[4, 5, 6, 9, 0, 1, 2, 3, 7, 8, 10]
2883
2884
Using the NetworkX recursive implementation ::
2885
2886
sage: D.topological_sort(implementation = "recursive")
2887
[4, 5, 6, 9, 0, 3, 2, 7, 1, 8, 10]
2888
2889
::
2890
2891
sage: D.add_edge(7,4)
2892
sage: D.topological_sort()
2893
Traceback (most recent call last):
2894
...
2895
TypeError: Digraph is not acyclic-- there is no topological
2896
sort.
2897
2898
.. note::
2899
2900
There is a recursive version of this in NetworkX, it used to
2901
have problems in earlier versions but they have since been
2902
fixed::
2903
2904
sage: import networkx
2905
sage: D = DiGraph({ 0:[1,2,3], 4:[2,5], 1:[8], 2:[7], 3:[7],
2906
... 5:[6,7], 7:[8], 6:[9], 8:[10], 9:[10] })
2907
sage: N = D.networkx_graph()
2908
sage: networkx.topological_sort(N)
2909
[4, 5, 6, 9, 0, 1, 2, 3, 7, 8, 10]
2910
sage: networkx.topological_sort_recursive(N)
2911
[4, 5, 6, 9, 0, 3, 2, 7, 1, 8, 10]
2912
2913
TESTS:
2914
2915
A wrong value for the ``implementation`` keyword::
2916
2917
sage: D.topological_sort(implementation = "cloud-reading")
2918
Traceback (most recent call last):
2919
...
2920
ValueError: implementation must be set to one of "default"
2921
or "NetworkX"
2922
"""
2923
2924
if implementation == "default":
2925
b, ordering = self._backend.is_directed_acyclic(certificate = True)
2926
if b:
2927
return ordering
2928
else:
2929
raise TypeError('Digraph is not acyclic-- there is no topological sort.')
2930
2931
elif implementation == "NetworkX" or implementation == "recursive":
2932
import networkx
2933
if implementation == "NetworkX":
2934
S = networkx.topological_sort(self.networkx_graph(copy=False))
2935
else:
2936
S = networkx.topological_sort_recursive(self.networkx_graph(copy=False))
2937
if S is None:
2938
raise TypeError('Digraph is not acyclic-- there is no topological sort.')
2939
else:
2940
return S
2941
2942
else:
2943
raise ValueError("implementation must be set to one of \"default\" or \"NetworkX\"")
2944
2945
def topological_sort_generator(self):
2946
"""
2947
Returns a list of all topological sorts of the digraph if it is
2948
acyclic, and raises a TypeError if the digraph contains a directed
2949
cycle.
2950
2951
A topological sort is an ordering of the vertices of the digraph
2952
such that each vertex comes before all of its successors. That is,
2953
if u comes before v in the sort, then there may be a directed path
2954
from u to v, but there will be no directed path from v to u. See
2955
also Graph.topological_sort().
2956
2957
AUTHORS:
2958
2959
- Mike Hansen - original implementation
2960
2961
- Robert L. Miller: wrapping, documentation
2962
2963
REFERENCE:
2964
2965
- [1] Pruesse, Gara and Ruskey, Frank. Generating Linear
2966
Extensions Fast. SIAM J. Comput., Vol. 23 (1994), no. 2, pp.
2967
373-386.
2968
2969
EXAMPLES::
2970
2971
sage: D = DiGraph({ 0:[1,2], 1:[3], 2:[3,4] })
2972
sage: D.plot(layout='circular').show()
2973
sage: D.topological_sort_generator()
2974
[[0, 1, 2, 3, 4], [0, 1, 2, 4, 3], [0, 2, 1, 3, 4], [0, 2, 1, 4, 3], [0, 2, 4, 1, 3]]
2975
2976
::
2977
2978
sage: for sort in D.topological_sort_generator():
2979
... for edge in D.edge_iterator():
2980
... u,v,l = edge
2981
... if sort.index(u) > sort.index(v):
2982
... print "This should never happen."
2983
"""
2984
from sage.graphs.linearextensions import LinearExtensions
2985
try:
2986
return LinearExtensions(self).list()
2987
except TypeError:
2988
raise TypeError('Digraph is not acyclic-- there is no topological sort (or there was an error in sage/graphs/linearextensions.py).')
2989
2990
### Visualization
2991
2992
def layout_acyclic(self, **options):
2993
"""
2994
Computes a ranked layout so that all edges point upward.
2995
2996
To this end, the heights of the vertices are set according to the level
2997
set decomposition of the graph (see :meth:`.level_sets`).
2998
2999
This is achieved by calling ``graphviz`` and ``dot2tex`` if available
3000
(see :meth:`.layout_graphviz`), and using a random horizontal
3001
placement of the vertices otherwise (see :meth:`.layout_acyclic_dummy`).
3002
3003
Non acyclic graphs are partially supported by ``graphviz``, which then
3004
chooses some edges to point down.
3005
3006
EXAMPLES::
3007
3008
sage: H = DiGraph({0:[1,2],1:[3],2:[3],3:[],5:[1,6],6:[2,3]})
3009
3010
The actual layout computed will depend on whether dot2tex and
3011
graphviz are installed, so we don't test it.
3012
3013
sage: H.layout_acyclic()
3014
{0: [..., ...], 1: [..., ...], 2: [..., ...], 3: [..., ...], 5: [..., ...], 6: [..., ...]}
3015
3016
"""
3017
if have_dot2tex():
3018
return self.layout_graphviz(**options)
3019
else:
3020
return self.layout_acyclic_dummy(**options)
3021
3022
def layout_acyclic_dummy(self, heights = None, **options):
3023
"""
3024
Computes a (dummy) ranked layout of an acyclic graph so that
3025
all edges point upward. To this end, the heights of the
3026
vertices are set according to the level set decomposition of
3027
the graph (see :meth:`.level_sets`).
3028
3029
EXAMPLES::
3030
3031
sage: H = DiGraph({0:[1,2],1:[3],2:[3],3:[],5:[1,6],6:[2,3]})
3032
sage: H.layout_acyclic_dummy()
3033
{0: [1.00..., 0], 1: [1.00..., 1], 2: [1.51..., 2], 3: [1.50..., 3], 5: [2.01..., 0], 6: [2.00..., 1]}
3034
3035
sage: H = DiGraph({0:[1,2],1:[3],2:[3],3:[1],5:[1,6],6:[2,3]})
3036
sage: H.layout_acyclic_dummy()
3037
Traceback (most recent call last):
3038
...
3039
AssertionError: `self` should be an acyclic graph
3040
"""
3041
if heights is None:
3042
assert self.is_directed_acyclic(), "`self` should be an acyclic graph"
3043
levels = self.level_sets()
3044
levels = [sorted(z) for z in levels]
3045
heights = dict([[i, levels[i]] for i in range(len(levels))])
3046
return self.layout_ranked(heights = heights, **options)
3047
3048
def level_sets(self):
3049
"""
3050
Returns the level set decomposition of the digraph.
3051
3052
OUTPUT:
3053
3054
- a list of non empty lists of vertices of this graph
3055
3056
The level set decomposition of the digraph is a list `l` such that the
3057
level `l[i]` contains all the vertices having all their predecessors in
3058
the levels `l[j]` for `j<i`, and at least one in level `l[i-1]` (unless
3059
`i=0`).
3060
3061
The level decomposition contains exactly the vertices not occuring in
3062
any cycle of the graph. In particular, the graph is acyclic if and only
3063
if the decomposition forms a set partition of its vertices, and we
3064
recover the usual level set decomposition of the corresponding poset.
3065
3066
EXAMPLES::
3067
3068
sage: H = DiGraph({0:[1,2],1:[3],2:[3],3:[],5:[1,6],6:[2,3]})
3069
sage: H.level_sets()
3070
[[0, 5], [1, 6], [2], [3]]
3071
3072
sage: H = DiGraph({0:[1,2],1:[3],2:[3],3:[1],5:[1,6],6:[2,3]})
3073
sage: H.level_sets()
3074
[[0, 5], [6], [2]]
3075
3076
This routine is mostly used for Hasse diagrams of posets::
3077
3078
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
3079
sage: H = HasseDiagram({0:[1,2],1:[3],2:[3],3:[]})
3080
sage: [len(x) for x in H.level_sets()]
3081
[1, 2, 1]
3082
3083
::
3084
3085
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
3086
sage: H = HasseDiagram({0:[1,2], 1:[3], 2:[4], 3:[4]})
3087
sage: [len(x) for x in H.level_sets()]
3088
[1, 2, 1, 1]
3089
3090
Complexity: `O(n+m)` in time and `O(n)` in memory (besides the
3091
storage of the graph itself), where `n` and `m` are
3092
respectively the number of vertices and edges (assuming that
3093
appending to a list is constant time, which it is not quite).
3094
"""
3095
in_degrees = self.in_degree(labels=True)
3096
level = [x for x in in_degrees if in_degrees[x]==0]
3097
Levels = []
3098
while len(level) != 0:
3099
Levels.append(level)
3100
new_level = []
3101
for x in level:
3102
for y in self.neighbors_out(x):
3103
in_degrees[y] -= 1
3104
if in_degrees[y] == 0:
3105
new_level.append(y)
3106
level = new_level
3107
return Levels
3108
3109
def strongly_connected_components(self):
3110
"""
3111
Returns the list of strongly connected components.
3112
3113
EXAMPLES::
3114
3115
sage: D = DiGraph( { 0 : [1, 3], 1 : [2], 2 : [3], 4 : [5, 6], 5 : [6] } )
3116
sage: D.connected_components()
3117
[[0, 1, 2, 3], [4, 5, 6]]
3118
sage: D = DiGraph( { 0 : [1, 3], 1 : [2], 2 : [3], 4 : [5, 6], 5 : [6] } )
3119
sage: D.strongly_connected_components()
3120
[[0], [1], [2], [3], [4], [5], [6]]
3121
sage: D.add_edge([2,0])
3122
sage: D.strongly_connected_components()
3123
[[0, 1, 2], [3], [4], [5], [6]]
3124
3125
TESTS:
3126
3127
Checking against NetworkX, and another of Sage's implementations::
3128
3129
sage: from sage.graphs.base.static_sparse_graph import strongly_connected_components
3130
sage: import networkx
3131
sage: for i in range(100): # long
3132
... g = digraphs.RandomDirectedGNP(100,.05) # long
3133
... h = g.networkx_graph() # long
3134
... scc1 = g.strongly_connected_components() # long
3135
... scc2 = networkx.strongly_connected_components(h) # long
3136
... scc3 = strongly_connected_components(g) # long
3137
... s1 = Set(map(Set,scc1)) # long
3138
... s2 = Set(map(Set,scc2)) # long
3139
... s3 = Set(map(Set,scc3)) # long
3140
... if s1 != s2: # long
3141
... print "Ooch !" # long
3142
... if s1 != s3: # long
3143
... print "Oooooch !" # long
3144
3145
"""
3146
3147
try:
3148
vertices = set(self.vertices())
3149
scc = []
3150
while vertices:
3151
tmp = self.strongly_connected_component_containing_vertex(vertices.__iter__().next())
3152
vertices.difference_update(set(tmp))
3153
scc.append(tmp)
3154
return scc
3155
3156
except AttributeError:
3157
import networkx
3158
return networkx.strongly_connected_components(self.networkx_graph(copy=False))
3159
3160
def strongly_connected_component_containing_vertex(self, v):
3161
"""
3162
Returns the strongly connected component containing a given vertex
3163
3164
INPUT:
3165
3166
- ``v`` -- a vertex
3167
3168
EXAMPLE:
3169
3170
In the symmetric digraph of a graph, the strongly connected components are the connected
3171
components::
3172
3173
sage: g = graphs.PetersenGraph()
3174
sage: d = DiGraph(g)
3175
sage: d.strongly_connected_component_containing_vertex(0)
3176
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
3177
"""
3178
3179
if self.order()==1:
3180
return [v]
3181
3182
try:
3183
return self._backend.strongly_connected_component_containing_vertex(v)
3184
3185
except AttributeError:
3186
raise AttributeError("This function is only defined for C graphs.")
3187
3188
def strongly_connected_components_subgraphs(self):
3189
r"""
3190
Returns the strongly connected components as a list of subgraphs.
3191
3192
EXAMPLE:
3193
3194
In the symmetric digraph of a graph, the strongly connected components are the connected
3195
components::
3196
3197
sage: g = graphs.PetersenGraph()
3198
sage: d = DiGraph(g)
3199
sage: d.strongly_connected_components_subgraphs()
3200
[Subgraph of (Petersen graph): Digraph on 10 vertices]
3201
3202
"""
3203
return map(self.subgraph, self.strongly_connected_components())
3204
3205
def strongly_connected_components_digraph(self, keep_labels = False):
3206
r"""
3207
Returns the digraph of the strongly connected components
3208
3209
INPUT:
3210
3211
- ``keep_labels`` -- boolean (default: False)
3212
3213
The digraph of the strongly connected components of a graph `G` has
3214
a vertex per strongly connected component included in `G`. There
3215
is an edge from a component `C_1` to a component `C_2` if there is
3216
an edge from one to the other in `G`.
3217
3218
EXAMPLE:
3219
3220
Such a digraph is always acyclic ::
3221
3222
sage: g = digraphs.RandomDirectedGNP(15,.1)
3223
sage: scc_digraph = g.strongly_connected_components_digraph()
3224
sage: scc_digraph.is_directed_acyclic()
3225
True
3226
3227
The vertices of the digraph of strongly connected components are
3228
exactly the strongly connected components::
3229
3230
sage: g = digraphs.ButterflyGraph(2)
3231
sage: scc_digraph = g.strongly_connected_components_digraph()
3232
sage: g.is_directed_acyclic()
3233
True
3234
sage: all([ Set(scc) in scc_digraph.vertices() for scc in g.strongly_connected_components()])
3235
True
3236
3237
The following digraph has three strongly connected components,
3238
and the digraph of those is a chain::
3239
3240
sage: g = DiGraph({0:{1:"01", 2: "02", 3: 03}, 1: {2: "12"}, 2:{1: "21", 3: "23"}})
3241
sage: scc_digraph = g.strongly_connected_components_digraph()
3242
sage: scc_digraph.vertices()
3243
[{0}, {3}, {1, 2}]
3244
sage: scc_digraph.edges()
3245
[({0}, {3}, None), ({0}, {1, 2}, None), ({1, 2}, {3}, None)]
3246
3247
By default, the labels are discarded, and the result has no
3248
loops nor multiple edges. If ``keep_labels`` is ``True``, then
3249
the labels are kept, and the result is a multi digraph,
3250
possibly with multiple edges and loops. However, edges in the
3251
result with same source, target, and label are not duplicated
3252
(see the edges from 0 to the strongly connected component
3253
`\{1,2\}` below)::
3254
3255
sage: g = DiGraph({0:{1:"0-12", 2: "0-12", 3: "0-3"}, 1: {2: "1-2", 3: "1-3"}, 2:{1: "2-1", 3: "2-3"}})
3256
sage: scc_digraph = g.strongly_connected_components_digraph(keep_labels = True)
3257
sage: scc_digraph.vertices()
3258
[{0}, {3}, {1, 2}]
3259
sage: scc_digraph.edges()
3260
[({0}, {3}, '0-3'), ({0}, {1, 2}, '0-12'),
3261
({1, 2}, {3}, '1-3'), ({1, 2}, {3}, '2-3'),
3262
({1, 2}, {1, 2}, '1-2'), ({1, 2}, {1, 2}, '2-1')]
3263
3264
"""
3265
3266
from sage.sets.set import Set
3267
3268
scc = self.strongly_connected_components()
3269
scc_set = map(Set,scc)
3270
3271
d = {}
3272
for i,c in enumerate(scc):
3273
for v in c:
3274
d[v] = i
3275
3276
if keep_labels:
3277
g = DiGraph(multiedges=True, loops=True)
3278
g.add_vertices(range(len(scc)))
3279
3280
g.add_edges( set((d[u], d[v], label) for (u,v,label) in self.edges() ) )
3281
g.relabel(scc_set, inplace = True)
3282
3283
else:
3284
g = DiGraph(multiedges=False, loops=False)
3285
g.add_vertices(range(len(scc)))
3286
3287
for u,v in self.edges(labels=False):
3288
g.add_edge(d[u], d[v])
3289
3290
g.relabel(scc_set, inplace = True)
3291
3292
return g
3293
3294
def is_strongly_connected(self):
3295
r"""
3296
Returns whether the current ``DiGraph`` is strongly connected.
3297
3298
EXAMPLE:
3299
3300
The circuit is obviously strongly connected ::
3301
3302
sage: g = digraphs.Circuit(5)
3303
sage: g.is_strongly_connected()
3304
True
3305
3306
But a transitive triangle is not::
3307
3308
sage: g = DiGraph({ 0 : [1,2], 1 : [2]})
3309
sage: g.is_strongly_connected()
3310
False
3311
"""
3312
if self.order()==1:
3313
return True
3314
3315
try:
3316
return self._backend.is_strongly_connected()
3317
3318
except AttributeError:
3319
return len(self.strongly_connected_components()) == 1
3320
3321
import types
3322
3323
import sage.graphs.comparability
3324
DiGraph.is_transitive = types.MethodType(sage.graphs.comparability.is_transitive, None, DiGraph)
3325
3326