comparison planemo/lib/python3.7/site-packages/networkx/generators/line.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 # Copyright (C) 2013-2019 by
2 # Aric Hagberg <hagberg@lanl.gov>
3 # Dan Schult <dschult@colgate.edu>
4 # Pieter Swart <swart@lanl.gov>
5 # All rights reserved.
6 # BSD license.
7 #
8 # Authors: James Clough <james.clough91@gmail.com>
9 # Aric Hagberg <hagberg@lanl.gov>
10 # Pieter Swart <swart@lanl.gov>
11 # Dan Schult <dschult@colgate.edu>
12 # chebee7i <chebee7i@gmail.com>
13 """Functions for generating line graphs."""
14 from itertools import combinations
15 from collections import defaultdict
16
17 import networkx as nx
18 from networkx.utils import arbitrary_element, generate_unique_node
19 from networkx.utils.decorators import *
20
21 __all__ = ['line_graph', 'inverse_line_graph']
22
23
24 def line_graph(G, create_using=None):
25 r"""Returns the line graph of the graph or digraph `G`.
26
27 The line graph of a graph `G` has a node for each edge in `G` and an
28 edge joining those nodes if the two edges in `G` share a common node. For
29 directed graphs, nodes are adjacent exactly when the edges they represent
30 form a directed path of length two.
31
32 The nodes of the line graph are 2-tuples of nodes in the original graph (or
33 3-tuples for multigraphs, with the key of the edge as the third element).
34
35 For information about self-loops and more discussion, see the **Notes**
36 section below.
37
38 Parameters
39 ----------
40 G : graph
41 A NetworkX Graph, DiGraph, MultiGraph, or MultiDigraph.
42 create_using : NetworkX graph constructor, optional (default=nx.Graph)
43 Graph type to create. If graph instance, then cleared before populated.
44
45 Returns
46 -------
47 L : graph
48 The line graph of G.
49
50 Examples
51 --------
52 >>> import networkx as nx
53 >>> G = nx.star_graph(3)
54 >>> L = nx.line_graph(G)
55 >>> print(sorted(map(sorted, L.edges()))) # makes a 3-clique, K3
56 [[(0, 1), (0, 2)], [(0, 1), (0, 3)], [(0, 2), (0, 3)]]
57
58 Notes
59 -----
60 Graph, node, and edge data are not propagated to the new graph. For
61 undirected graphs, the nodes in G must be sortable, otherwise the
62 constructed line graph may not be correct.
63
64 *Self-loops in undirected graphs*
65
66 For an undirected graph `G` without multiple edges, each edge can be
67 written as a set `\{u, v\}`. Its line graph `L` has the edges of `G` as
68 its nodes. If `x` and `y` are two nodes in `L`, then `\{x, y\}` is an edge
69 in `L` if and only if the intersection of `x` and `y` is nonempty. Thus,
70 the set of all edges is determined by the set of all pairwise intersections
71 of edges in `G`.
72
73 Trivially, every edge in G would have a nonzero intersection with itself,
74 and so every node in `L` should have a self-loop. This is not so
75 interesting, and the original context of line graphs was with simple
76 graphs, which had no self-loops or multiple edges. The line graph was also
77 meant to be a simple graph and thus, self-loops in `L` are not part of the
78 standard definition of a line graph. In a pairwise intersection matrix,
79 this is analogous to excluding the diagonal entries from the line graph
80 definition.
81
82 Self-loops and multiple edges in `G` add nodes to `L` in a natural way, and
83 do not require any fundamental changes to the definition. It might be
84 argued that the self-loops we excluded before should now be included.
85 However, the self-loops are still "trivial" in some sense and thus, are
86 usually excluded.
87
88 *Self-loops in directed graphs*
89
90 For a directed graph `G` without multiple edges, each edge can be written
91 as a tuple `(u, v)`. Its line graph `L` has the edges of `G` as its
92 nodes. If `x` and `y` are two nodes in `L`, then `(x, y)` is an edge in `L`
93 if and only if the tail of `x` matches the head of `y`, for example, if `x
94 = (a, b)` and `y = (b, c)` for some vertices `a`, `b`, and `c` in `G`.
95
96 Due to the directed nature of the edges, it is no longer the case that
97 every edge in `G` should have a self-loop in `L`. Now, the only time
98 self-loops arise is if a node in `G` itself has a self-loop. So such
99 self-loops are no longer "trivial" but instead, represent essential
100 features of the topology of `G`. For this reason, the historical
101 development of line digraphs is such that self-loops are included. When the
102 graph `G` has multiple edges, once again only superficial changes are
103 required to the definition.
104
105 References
106 ----------
107 * Harary, Frank, and Norman, Robert Z., "Some properties of line digraphs",
108 Rend. Circ. Mat. Palermo, II. Ser. 9 (1960), 161--168.
109 * Hemminger, R. L.; Beineke, L. W. (1978), "Line graphs and line digraphs",
110 in Beineke, L. W.; Wilson, R. J., Selected Topics in Graph Theory,
111 Academic Press Inc., pp. 271--305.
112
113 """
114 if G.is_directed():
115 L = _lg_directed(G, create_using=create_using)
116 else:
117 L = _lg_undirected(G, selfloops=False, create_using=create_using)
118 return L
119
120
121 def _node_func(G):
122 """Returns a function which returns a sorted node for line graphs.
123
124 When constructing a line graph for undirected graphs, we must normalize
125 the ordering of nodes as they appear in the edge.
126
127 """
128 if G.is_multigraph():
129 def sorted_node(u, v, key):
130 return (u, v, key) if u <= v else (v, u, key)
131 else:
132 def sorted_node(u, v):
133 return (u, v) if u <= v else (v, u)
134 return sorted_node
135
136
137 def _edge_func(G):
138 """Returns the edges from G, handling keys for multigraphs as necessary.
139
140 """
141 if G.is_multigraph():
142 def get_edges(nbunch=None):
143 return G.edges(nbunch, keys=True)
144 else:
145 def get_edges(nbunch=None):
146 return G.edges(nbunch)
147 return get_edges
148
149
150 def _sorted_edge(u, v):
151 """Returns a sorted edge.
152
153 During the construction of a line graph for undirected graphs, the data
154 structure can be a multigraph even though the line graph will never have
155 multiple edges between its nodes. For this reason, we must make sure not
156 to add any edge more than once. This requires that we build up a list of
157 edges to add and then remove all duplicates. And so, we must normalize
158 the representation of the edges.
159
160 """
161 return (u, v) if u <= v else (v, u)
162
163
164 def _lg_directed(G, create_using=None):
165 """Returns the line graph L of the (multi)digraph G.
166
167 Edges in G appear as nodes in L, represented as tuples of the form (u,v)
168 or (u,v,key) if G is a multidigraph. A node in L corresponding to the edge
169 (u,v) is connected to every node corresponding to an edge (v,w).
170
171 Parameters
172 ----------
173 G : digraph
174 A directed graph or directed multigraph.
175 create_using : NetworkX graph constructor, optional
176 Graph type to create. If graph instance, then cleared before populated.
177 Default is to use the same graph class as `G`.
178
179 """
180 L = nx.empty_graph(0, create_using, default=G.__class__)
181
182 # Create a graph specific edge function.
183 get_edges = _edge_func(G)
184
185 for from_node in get_edges():
186 # from_node is: (u,v) or (u,v,key)
187 L.add_node(from_node)
188 for to_node in get_edges(from_node[1]):
189 L.add_edge(from_node, to_node)
190
191 return L
192
193
194 def _lg_undirected(G, selfloops=False, create_using=None):
195 """Returns the line graph L of the (multi)graph G.
196
197 Edges in G appear as nodes in L, represented as sorted tuples of the form
198 (u,v), or (u,v,key) if G is a multigraph. A node in L corresponding to
199 the edge {u,v} is connected to every node corresponding to an edge that
200 involves u or v.
201
202 Parameters
203 ----------
204 G : graph
205 An undirected graph or multigraph.
206 selfloops : bool
207 If `True`, then self-loops are included in the line graph. If `False`,
208 they are excluded.
209 create_using : NetworkX graph constructor, optional (default=nx.Graph)
210 Graph type to create. If graph instance, then cleared before populated.
211
212 Notes
213 -----
214 The standard algorithm for line graphs of undirected graphs does not
215 produce self-loops.
216
217 """
218 L = nx.empty_graph(0, create_using, default=G.__class__)
219
220 # Graph specific functions for edges and sorted nodes.
221 get_edges = _edge_func(G)
222 sorted_node = _node_func(G)
223
224 # Determine if we include self-loops or not.
225 shift = 0 if selfloops else 1
226
227 edges = set([])
228 for u in G:
229 # Label nodes as a sorted tuple of nodes in original graph.
230 nodes = [sorted_node(*x) for x in get_edges(u)]
231
232 if len(nodes) == 1:
233 # Then the edge will be an isolated node in L.
234 L.add_node(nodes[0])
235
236 # Add a clique of `nodes` to graph. To prevent double adding edges,
237 # especially important for multigraphs, we store the edges in
238 # canonical form in a set.
239 for i, a in enumerate(nodes):
240 edges.update([_sorted_edge(a, b) for b in nodes[i + shift:]])
241
242 L.add_edges_from(edges)
243 return L
244
245
246 @not_implemented_for('directed')
247 @not_implemented_for('multigraph')
248 def inverse_line_graph(G):
249 """ Returns the inverse line graph of graph G.
250
251 If H is a graph, and G is the line graph of H, such that H = L(G).
252 Then H is the inverse line graph of G.
253
254 Not all graphs are line graphs and these do not have an inverse line graph.
255 In these cases this generator returns a NetworkXError.
256
257 Parameters
258 ----------
259 G : graph
260 A NetworkX Graph
261
262 Returns
263 -------
264 H : graph
265 The inverse line graph of G.
266
267 Raises
268 ------
269 NetworkXNotImplemented
270 If G is directed or a multigraph
271
272 NetworkXError
273 If G is not a line graph
274
275 Notes
276 -----
277 This is an implementation of the Roussopoulos algorithm.
278
279 If G consists of multiple components, then the algorithm doesn't work.
280 You should invert every component seperately:
281
282 >>> K5 = nx.complete_graph(5)
283 >>> P4 = nx.Graph([('a', 'b'), ('b', 'c'), ('c', 'd')])
284 >>> G = nx.union(K5, P4)
285 >>> root_graphs = []
286 >>> for comp in nx.connected_components(G):
287 ... root_graphs.append(nx.inverse_line_graph(G.subgraph(comp)))
288 >>> len(root_graphs)
289 2
290
291 References
292 ----------
293 * Roussopolous, N, "A max {m, n} algorithm for determining the graph H from
294 its line graph G", Information Processing Letters 2, (1973), 108--112.
295
296 """
297 if G.number_of_nodes() == 0:
298 a = generate_unique_node()
299 H = nx.Graph()
300 H.add_node(a)
301 return H
302 elif G.number_of_nodes() == 1:
303 v = list(G)[0]
304 a = (v, 0)
305 b = (v, 1)
306 H = nx.Graph([(a,b)])
307 return H
308 elif G.number_of_nodes() > 1 and G.number_of_edges() == 0:
309 msg = (
310 "inverse_line_graph() doesn't work on an edgeless graph. "
311 "Please use this function on each component seperately."
312 )
313 raise nx.NetworkXError(msg)
314
315 starting_cell = _select_starting_cell(G)
316 P = _find_partition(G, starting_cell)
317 # count how many times each vertex appears in the partition set
318 P_count = {u: 0 for u in G.nodes()}
319 for p in P:
320 for u in p:
321 P_count[u] += 1
322
323 if max(P_count.values()) > 2:
324 msg = "G is not a line graph (vertex found in more " \
325 "than two partition cells)"
326 raise nx.NetworkXError(msg)
327 W = tuple([(u,) for u in P_count if P_count[u] == 1])
328 H = nx.Graph()
329 H.add_nodes_from(P)
330 H.add_nodes_from(W)
331 for a, b in combinations(H.nodes(), 2):
332 if len(set(a).intersection(set(b))) > 0:
333 H.add_edge(a, b)
334 return H
335
336
337 def _triangles(G, e):
338 """ Return list of all triangles containing edge e"""
339 u, v = e
340 if u not in G:
341 raise nx.NetworkXError("Vertex %s not in graph" % u)
342 if v not in G[u]:
343 raise nx.NetworkXError("Edge (%s, %s) not in graph" % (u, v))
344 triangle_list = []
345 for x in G[u]:
346 if x in G[v]:
347 triangle_list.append((u, v, x))
348 return triangle_list
349
350
351 def _odd_triangle(G, T):
352 """ Test whether T is an odd triangle in G
353
354 Parameters
355 ----------
356 G : NetworkX Graph
357 T : 3-tuple of vertices forming triangle in G
358
359 Returns
360 -------
361 True is T is an odd triangle
362 False otherwise
363
364 Raises
365 ------
366 NetworkXError
367 T is not a triangle in G
368
369 Notes
370 -----
371 An odd triangle is one in which there exists another vertex in G which is
372 adjacent to either exactly one or exactly all three of the vertices in the
373 triangle.
374
375 """
376 for u in T:
377 if u not in G.nodes():
378 raise nx.NetworkXError("Vertex %s not in graph" % u)
379 for e in list(combinations(T, 2)):
380 if e[0] not in G[e[1]]:
381 raise nx.NetworkXError("Edge (%s, %s) not in graph" % (e[0], e[1]))
382
383 T_neighbors = defaultdict(int)
384 for t in T:
385 for v in G[t]:
386 if v not in T:
387 T_neighbors[v] += 1
388 for v in T_neighbors:
389 if T_neighbors[v] in [1, 3]:
390 return True
391 return False
392
393
394 def _find_partition(G, starting_cell):
395 """ Find a partition of the vertices of G into cells of complete graphs
396
397 Parameters
398 ----------
399 G : NetworkX Graph
400 starting_cell : tuple of vertices in G which form a cell
401
402 Returns
403 -------
404 List of tuples of vertices of G
405
406 Raises
407 ------
408 NetworkXError
409 If a cell is not a complete subgraph then G is not a line graph
410 """
411 G_partition = G.copy()
412 P = [starting_cell] # partition set
413 G_partition.remove_edges_from(list(combinations(starting_cell, 2)))
414 # keep list of partitioned nodes which might have an edge in G_partition
415 partitioned_vertices = list(starting_cell)
416 while G_partition.number_of_edges() > 0:
417 # there are still edges left and so more cells to be made
418 u = partitioned_vertices[-1]
419 deg_u = len(G_partition[u])
420 if deg_u == 0:
421 # if u has no edges left in G_partition then we have found
422 # all of its cells so we do not need to keep looking
423 partitioned_vertices.pop()
424 else:
425 # if u still has edges then we need to find its other cell
426 # this other cell must be a complete subgraph or else G is
427 # not a line graph
428 new_cell = [u] + list(G_partition[u])
429 for u in new_cell:
430 for v in new_cell:
431 if (u != v) and (v not in G_partition[u]):
432 msg = "G is not a line graph" \
433 "(partition cell not a complete subgraph)"
434 raise nx.NetworkXError(msg)
435 P.append(tuple(new_cell))
436 G_partition.remove_edges_from(list(combinations(new_cell, 2)))
437 partitioned_vertices += new_cell
438 return P
439
440
441 def _select_starting_cell(G, starting_edge=None):
442 """ Select a cell to initiate _find_partition
443
444 Parameters
445 ----------
446 G : NetworkX Graph
447 starting_edge: an edge to build the starting cell from
448
449 Returns
450 -------
451 Tuple of vertices in G
452
453 Raises
454 ------
455 NetworkXError
456 If it is determined that G is not a line graph
457
458 Notes
459 -----
460 If starting edge not specified then pick an arbitrary edge - doesn't
461 matter which. However, this function may call itself requiring a
462 specific starting edge. Note that the r, s notation for counting
463 triangles is the same as in the Roussopoulos paper cited above.
464 """
465 if starting_edge is None:
466 e = arbitrary_element(list(G.edges()))
467 else:
468 e = starting_edge
469 if e[0] not in G[e[1]]:
470 msg = 'starting_edge (%s, %s) is not in the Graph'
471 raise nx.NetworkXError(msg % e)
472 e_triangles = _triangles(G, e)
473 r = len(e_triangles)
474 if r == 0:
475 # there are no triangles containing e, so the starting cell is just e
476 starting_cell = e
477 elif r == 1:
478 # there is exactly one triangle, T, containing e. If other 2 edges
479 # of T belong only to this triangle then T is starting cell
480 T = e_triangles[0]
481 a, b, c = T
482 # ab was original edge so check the other 2 edges
483 ac_edges = [x for x in _triangles(G, (a, c))]
484 bc_edges = [x for x in _triangles(G, (b, c))]
485 if len(ac_edges) == 1:
486 if len(bc_edges) == 1:
487 starting_cell = T
488 else:
489 return _select_starting_cell(G, starting_edge=(b, c))
490 else:
491 return _select_starting_cell(G, starting_edge=(a, c))
492 else:
493 # r >= 2 so we need to count the number of odd triangles, s
494 s = 0
495 odd_triangles = []
496 for T in e_triangles:
497 if _odd_triangle(G, T):
498 s += 1
499 odd_triangles.append(T)
500 if r == 2 and s == 0:
501 # in this case either triangle works, so just use T
502 starting_cell = T
503 elif r - 1 <= s <= r:
504 # check if odd triangles containing e form complete subgraph
505 # there must be exactly s+2 of them
506 # and they must all be connected
507 triangle_nodes = set([])
508 for T in odd_triangles:
509 for x in T:
510 triangle_nodes.add(x)
511 if len(triangle_nodes) == s + 2:
512 for u in triangle_nodes:
513 for v in triangle_nodes:
514 if u != v and (v not in G[u]):
515 msg = "G is not a line graph (odd triangles " \
516 "do not form complete subgraph)"
517 raise nx.NetworkXError(msg)
518 # otherwise then we can use this as the starting cell
519 starting_cell = tuple(triangle_nodes)
520 else:
521 msg = "G is not a line graph (odd triangles " \
522 "do not form complete subgraph)"
523 raise nx.NetworkXError(msg)
524 else:
525 msg = "G is not a line graph (incorrect number of " \
526 "odd triangles around starting edge)"
527 raise nx.NetworkXError(msg)
528 return starting_cell