comparison planemo/lib/python3.7/site-packages/networkx/algorithms/euler.py @ 1:56ad4e20f292 draft

"planemo upload commit 6eee67778febed82ddd413c3ca40b3183a3898f1"
author guerler
date Fri, 31 Jul 2020 00:32:28 -0400
parents
children
comparison
equal deleted inserted replaced
0:d30785e31577 1:56ad4e20f292
1 # -*- coding: utf-8 -*-
2 #
3 # Copyright (C) 2010 by
4 # Aric Hagberg <hagberg@lanl.gov>
5 # Dan Schult <dschult@colgate.edu>
6 # Pieter Swart <swart@lanl.gov>
7 # All rights reserved.
8 # BSD license.
9 #
10 # Authors:
11 # Nima Mohammadi <nima.irt@gmail.com>
12 # Aric Hagberg <hagberg@lanl.gov>
13 # Mike Trenfield <william.trenfield@utsouthwestern.edu>
14 """
15 Eulerian circuits and graphs.
16 """
17 from itertools import combinations
18
19 import networkx as nx
20 from ..utils import arbitrary_element, not_implemented_for
21
22 __all__ = ['is_eulerian', 'eulerian_circuit', 'eulerize',
23 'is_semieulerian', 'has_eulerian_path', 'eulerian_path',
24 ]
25
26
27 def is_eulerian(G):
28 """Returns True if and only if `G` is Eulerian.
29
30 A graph is *Eulerian* if it has an Eulerian circuit. An *Eulerian
31 circuit* is a closed walk that includes each edge of a graph exactly
32 once.
33
34 Parameters
35 ----------
36 G : NetworkX graph
37 A graph, either directed or undirected.
38
39 Examples
40 --------
41 >>> nx.is_eulerian(nx.DiGraph({0: [3], 1: [2], 2: [3], 3: [0, 1]}))
42 True
43 >>> nx.is_eulerian(nx.complete_graph(5))
44 True
45 >>> nx.is_eulerian(nx.petersen_graph())
46 False
47
48 Notes
49 -----
50 If the graph is not connected (or not strongly connected, for
51 directed graphs), this function returns False.
52
53 """
54 if G.is_directed():
55 # Every node must have equal in degree and out degree and the
56 # graph must be strongly connected
57 return (all(G.in_degree(n) == G.out_degree(n) for n in G) and
58 nx.is_strongly_connected(G))
59 # An undirected Eulerian graph has no vertices of odd degree and
60 # must be connected.
61 return all(d % 2 == 0 for v, d in G.degree()) and nx.is_connected(G)
62
63
64 def is_semieulerian(G):
65 """Return True iff `G` is semi-Eulerian.
66
67 G is semi-Eulerian if it has an Eulerian path but no Eulerian circuit.
68 """
69 return has_eulerian_path(G) and not is_eulerian(G)
70
71
72 def _find_path_start(G):
73 """Return a suitable starting vertex for an Eulerian path.
74
75 If no path exists, return None.
76 """
77 if not has_eulerian_path(G):
78 return None
79
80 if is_eulerian(G):
81 return arbitrary_element(G)
82
83 if G.is_directed():
84 v1, v2 = [v for v in G if G.in_degree(v) != G.out_degree(v)]
85 # Determines which is the 'start' node (as opposed to the 'end')
86 if G.out_degree(v1) > G.in_degree(v1):
87 return v1
88 else:
89 return v2
90
91 else:
92 # In an undirected graph randomly choose one of the possibilities
93 start = [v for v in G if G.degree(v) % 2 != 0][0]
94 return start
95
96
97 def _simplegraph_eulerian_circuit(G, source):
98 if G.is_directed():
99 degree = G.out_degree
100 edges = G.out_edges
101 else:
102 degree = G.degree
103 edges = G.edges
104 vertex_stack = [source]
105 last_vertex = None
106 while vertex_stack:
107 current_vertex = vertex_stack[-1]
108 if degree(current_vertex) == 0:
109 if last_vertex is not None:
110 yield (last_vertex, current_vertex)
111 last_vertex = current_vertex
112 vertex_stack.pop()
113 else:
114 _, next_vertex = arbitrary_element(edges(current_vertex))
115 vertex_stack.append(next_vertex)
116 G.remove_edge(current_vertex, next_vertex)
117
118
119 def _multigraph_eulerian_circuit(G, source):
120 if G.is_directed():
121 degree = G.out_degree
122 edges = G.out_edges
123 else:
124 degree = G.degree
125 edges = G.edges
126 vertex_stack = [(source, None)]
127 last_vertex = None
128 last_key = None
129 while vertex_stack:
130 current_vertex, current_key = vertex_stack[-1]
131 if degree(current_vertex) == 0:
132 if last_vertex is not None:
133 yield (last_vertex, current_vertex, last_key)
134 last_vertex, last_key = current_vertex, current_key
135 vertex_stack.pop()
136 else:
137 triple = arbitrary_element(edges(current_vertex, keys=True))
138 _, next_vertex, next_key = triple
139 vertex_stack.append((next_vertex, next_key))
140 G.remove_edge(current_vertex, next_vertex, next_key)
141
142
143 def eulerian_circuit(G, source=None, keys=False):
144 """Returns an iterator over the edges of an Eulerian circuit in `G`.
145
146 An *Eulerian circuit* is a closed walk that includes each edge of a
147 graph exactly once.
148
149 Parameters
150 ----------
151 G : NetworkX graph
152 A graph, either directed or undirected.
153
154 source : node, optional
155 Starting node for circuit.
156
157 keys : bool
158 If False, edges generated by this function will be of the form
159 ``(u, v)``. Otherwise, edges will be of the form ``(u, v, k)``.
160 This option is ignored unless `G` is a multigraph.
161
162 Returns
163 -------
164 edges : iterator
165 An iterator over edges in the Eulerian circuit.
166
167 Raises
168 ------
169 NetworkXError
170 If the graph is not Eulerian.
171
172 See Also
173 --------
174 is_eulerian
175
176 Notes
177 -----
178 This is a linear time implementation of an algorithm adapted from [1]_.
179
180 For general information about Euler tours, see [2]_.
181
182 References
183 ----------
184 .. [1] J. Edmonds, E. L. Johnson.
185 Matching, Euler tours and the Chinese postman.
186 Mathematical programming, Volume 5, Issue 1 (1973), 111-114.
187 .. [2] https://en.wikipedia.org/wiki/Eulerian_path
188
189 Examples
190 --------
191 To get an Eulerian circuit in an undirected graph::
192
193 >>> G = nx.complete_graph(3)
194 >>> list(nx.eulerian_circuit(G))
195 [(0, 2), (2, 1), (1, 0)]
196 >>> list(nx.eulerian_circuit(G, source=1))
197 [(1, 2), (2, 0), (0, 1)]
198
199 To get the sequence of vertices in an Eulerian circuit::
200
201 >>> [u for u, v in nx.eulerian_circuit(G)]
202 [0, 2, 1]
203
204 """
205 if not is_eulerian(G):
206 raise nx.NetworkXError("G is not Eulerian.")
207 if G.is_directed():
208 G = G.reverse()
209 else:
210 G = G.copy()
211 if source is None:
212 source = arbitrary_element(G)
213 if G.is_multigraph():
214 for u, v, k in _multigraph_eulerian_circuit(G, source):
215 if keys:
216 yield u, v, k
217 else:
218 yield u, v
219 else:
220 for u, v in _simplegraph_eulerian_circuit(G, source):
221 yield u, v
222
223
224 def has_eulerian_path(G):
225 """Return True iff `G` has an Eulerian path.
226
227 An Eulerian path is a path in a graph which uses each edge of a graph
228 exactly once.
229
230 A directed graph has an Eulerian path iff:
231 - at most one vertex has out_degree - in_degree = 1,
232 - at most one vertex has in_degree - out_degree = 1,
233 - every other vertex has equal in_degree and out_degree,
234 - and all of its vertices with nonzero degree belong to a
235 - single connected component of the underlying undirected graph.
236
237 An undirected graph has an Eulerian path iff:
238 - exactly zero or two vertices have odd degree,
239 - and all of its vertices with nonzero degree belong to a
240 - single connected component.
241
242 Parameters
243 ----------
244 G : NetworkX Graph
245 The graph to find an euler path in.
246
247 Returns
248 -------
249 Bool : True if G has an eulerian path.
250
251 See Also
252 --------
253 is_eulerian
254 eulerian_path
255 """
256 if G.is_directed():
257 ins = G.in_degree
258 outs = G.out_degree
259 semibalanced_ins = sum(ins(v) - outs(v) == 1 for v in G)
260 semibalanced_outs = sum(outs(v) - ins(v) == 1 for v in G)
261 return (semibalanced_ins <= 1 and
262 semibalanced_outs <= 1 and
263 sum(G.in_degree(v) != G.out_degree(v) for v in G) <= 2 and
264 nx.is_weakly_connected(G))
265 else:
266 return (sum(d % 2 == 1 for v, d in G.degree()) in (0, 2)
267 and nx.is_connected(G))
268
269
270 def eulerian_path(G, source=None, keys=False):
271 """Return an iterator over the edges of an Eulerian path in `G`.
272
273 Parameters
274 ----------
275 G : NetworkX Graph
276 The graph in which to look for an eulerian path.
277 source : node or None (default: None)
278 The node at which to start the search. None means search over all
279 starting nodes.
280 keys : Bool (default: False)
281 Indicates whether to yield edge 3-tuples (u, v, edge_key).
282 The default yields edge 2-tuples
283
284 Yields
285 ------
286 Edge tuples along the eulerian path.
287
288 Warning: If `source` provided is not the start node of an Euler path
289 will raise error even if an Euler Path exists.
290 """
291 if not has_eulerian_path(G):
292 raise nx.NetworkXError("Graph has no Eulerian paths.")
293 if G.is_directed():
294 G = G.reverse()
295 else:
296 G = G.copy()
297 if source is None:
298 source = _find_path_start(G)
299 if G.is_multigraph():
300 for u, v, k in _multigraph_eulerian_circuit(G, source):
301 if keys:
302 yield u, v, k
303 else:
304 yield u, v
305 else:
306 for u, v in _simplegraph_eulerian_circuit(G, source):
307 yield u, v
308
309
310 @not_implemented_for('directed')
311 def eulerize(G):
312 """Transforms a graph into an Eulerian graph
313
314 Parameters
315 ----------
316 G : NetworkX graph
317 An undirected graph
318
319 Returns
320 -------
321 G : NetworkX multigraph
322
323 Raises
324 ------
325 NetworkXError
326 If the graph is not connected.
327
328 See Also
329 --------
330 is_eulerian
331 eulerian_circuit
332
333 References
334 ----------
335 .. [1] J. Edmonds, E. L. Johnson.
336 Matching, Euler tours and the Chinese postman.
337 Mathematical programming, Volume 5, Issue 1 (1973), 111-114.
338 [2] https://en.wikipedia.org/wiki/Eulerian_path
339 .. [3] http://web.math.princeton.edu/math_alive/5/Notes1.pdf
340
341 Examples
342 --------
343 >>> G = nx.complete_graph(10)
344 >>> H = nx.eulerize(G)
345 >>> nx.is_eulerian(H)
346 True
347
348 """
349 if G.order() == 0:
350 raise nx.NetworkXPointlessConcept("Cannot Eulerize null graph")
351 if not nx.is_connected(G):
352 raise nx.NetworkXError("G is not connected")
353 odd_degree_nodes = [n for n, d in G.degree() if d % 2 == 1]
354 G = nx.MultiGraph(G)
355 if len(odd_degree_nodes) == 0:
356 return G
357
358 # get all shortest paths between vertices of odd degree
359 odd_deg_pairs_paths = [(m,
360 {n: nx.shortest_path(G, source=m, target=n)}
361 )
362 for m, n in combinations(odd_degree_nodes, 2)]
363
364 # use inverse path lengths as edge-weights in a new graph
365 # store the paths in the graph for easy indexing later
366 Gp = nx.Graph()
367 for n, Ps in odd_deg_pairs_paths:
368 for m, P in Ps.items():
369 if n != m:
370 Gp.add_edge(m, n, weight=1/len(P), path=P)
371
372 # find the minimum weight matching of edges in the weighted graph
373 best_matching = nx.Graph(list(nx.max_weight_matching(Gp)))
374
375 # duplicate each edge along each path in the set of paths in Gp
376 for m, n in best_matching.edges():
377 path = Gp[m][n]["path"]
378 G.add_edges_from(nx.utils.pairwise(path))
379 return G