koichi12 commited on
Commit
1f8330b
·
verified ·
1 Parent(s): 8a4dc42

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__init__.py +87 -0
  2. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/basic.cpython-311.pyc +0 -0
  3. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/cluster.cpython-311.pyc +0 -0
  4. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/covering.cpython-311.pyc +0 -0
  5. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/edgelist.cpython-311.pyc +0 -0
  6. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/generators.cpython-311.pyc +0 -0
  7. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/matching.cpython-311.pyc +0 -0
  8. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/matrix.cpython-311.pyc +0 -0
  9. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/projection.cpython-311.pyc +0 -0
  10. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/spectral.cpython-311.pyc +0 -0
  11. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/basic.py +322 -0
  12. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/centrality.py +290 -0
  13. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/cluster.py +278 -0
  14. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/covering.py +57 -0
  15. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/edgelist.py +360 -0
  16. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/extendability.py +105 -0
  17. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/generators.py +604 -0
  18. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/matching.py +590 -0
  19. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/matrix.py +168 -0
  20. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/projection.py +526 -0
  21. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/redundancy.py +112 -0
  22. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/spectral.py +69 -0
  23. .venv/lib/python3.11/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_betweenness_centrality.cpython-311.pyc +0 -0
  24. .venv/lib/python3.11/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_current_flow_betweenness_centrality.cpython-311.pyc +0 -0
  25. .venv/lib/python3.11/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_current_flow_betweenness_centrality_subset.cpython-311.pyc +0 -0
  26. .venv/lib/python3.11/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_current_flow_closeness.cpython-311.pyc +0 -0
  27. .venv/lib/python3.11/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_group.cpython-311.pyc +0 -0
  28. .venv/lib/python3.11/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_load_centrality.cpython-311.pyc +0 -0
  29. .venv/lib/python3.11/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_voterank.cpython-311.pyc +0 -0
  30. .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/__init__.py +5 -0
  31. .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/__pycache__/__init__.cpython-311.pyc +0 -0
  32. .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/__pycache__/beamsearch.cpython-311.pyc +0 -0
  33. .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/__pycache__/breadth_first_search.cpython-311.pyc +0 -0
  34. .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/__pycache__/depth_first_search.cpython-311.pyc +0 -0
  35. .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/__pycache__/edgebfs.cpython-311.pyc +0 -0
  36. .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/__pycache__/edgedfs.cpython-311.pyc +0 -0
  37. .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/beamsearch.py +90 -0
  38. .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/breadth_first_search.py +575 -0
  39. .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/depth_first_search.py +529 -0
  40. .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/edgebfs.py +178 -0
  41. .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/edgedfs.py +176 -0
  42. .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/tests/__init__.py +0 -0
  43. .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/tests/__pycache__/__init__.cpython-311.pyc +0 -0
  44. .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/tests/__pycache__/test_beamsearch.cpython-311.pyc +0 -0
  45. .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/tests/__pycache__/test_bfs.cpython-311.pyc +0 -0
  46. .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/tests/__pycache__/test_dfs.cpython-311.pyc +0 -0
  47. .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/tests/__pycache__/test_edgebfs.cpython-311.pyc +0 -0
  48. .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/tests/__pycache__/test_edgedfs.cpython-311.pyc +0 -0
  49. .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/tests/test_beamsearch.py +25 -0
  50. .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/tests/test_bfs.py +203 -0
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__init__.py ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ r"""This module provides functions and operations for bipartite
2
+ graphs. Bipartite graphs `B = (U, V, E)` have two node sets `U,V` and edges in
3
+ `E` that only connect nodes from opposite sets. It is common in the literature
4
+ to use an spatial analogy referring to the two node sets as top and bottom nodes.
5
+
6
+ The bipartite algorithms are not imported into the networkx namespace
7
+ at the top level so the easiest way to use them is with:
8
+
9
+ >>> from networkx.algorithms import bipartite
10
+
11
+ NetworkX does not have a custom bipartite graph class but the Graph()
12
+ or DiGraph() classes can be used to represent bipartite graphs. However,
13
+ you have to keep track of which set each node belongs to, and make
14
+ sure that there is no edge between nodes of the same set. The convention used
15
+ in NetworkX is to use a node attribute named `bipartite` with values 0 or 1 to
16
+ identify the sets each node belongs to. This convention is not enforced in
17
+ the source code of bipartite functions, it's only a recommendation.
18
+
19
+ For example:
20
+
21
+ >>> B = nx.Graph()
22
+ >>> # Add nodes with the node attribute "bipartite"
23
+ >>> B.add_nodes_from([1, 2, 3, 4], bipartite=0)
24
+ >>> B.add_nodes_from(["a", "b", "c"], bipartite=1)
25
+ >>> # Add edges only between nodes of opposite node sets
26
+ >>> B.add_edges_from([(1, "a"), (1, "b"), (2, "b"), (2, "c"), (3, "c"), (4, "a")])
27
+
28
+ Many algorithms of the bipartite module of NetworkX require, as an argument, a
29
+ container with all the nodes that belong to one set, in addition to the bipartite
30
+ graph `B`. The functions in the bipartite package do not check that the node set
31
+ is actually correct nor that the input graph is actually bipartite.
32
+ If `B` is connected, you can find the two node sets using a two-coloring
33
+ algorithm:
34
+
35
+ >>> nx.is_connected(B)
36
+ True
37
+ >>> bottom_nodes, top_nodes = bipartite.sets(B)
38
+
39
+ However, if the input graph is not connected, there are more than one possible
40
+ colorations. This is the reason why we require the user to pass a container
41
+ with all nodes of one bipartite node set as an argument to most bipartite
42
+ functions. In the face of ambiguity, we refuse the temptation to guess and
43
+ raise an :exc:`AmbiguousSolution <networkx.AmbiguousSolution>`
44
+ Exception if the input graph for
45
+ :func:`bipartite.sets <networkx.algorithms.bipartite.basic.sets>`
46
+ is disconnected.
47
+
48
+ Using the `bipartite` node attribute, you can easily get the two node sets:
49
+
50
+ >>> top_nodes = {n for n, d in B.nodes(data=True) if d["bipartite"] == 0}
51
+ >>> bottom_nodes = set(B) - top_nodes
52
+
53
+ So you can easily use the bipartite algorithms that require, as an argument, a
54
+ container with all nodes that belong to one node set:
55
+
56
+ >>> print(round(bipartite.density(B, bottom_nodes), 2))
57
+ 0.5
58
+ >>> G = bipartite.projected_graph(B, top_nodes)
59
+
60
+ All bipartite graph generators in NetworkX build bipartite graphs with the
61
+ `bipartite` node attribute. Thus, you can use the same approach:
62
+
63
+ >>> RB = bipartite.random_graph(5, 7, 0.2)
64
+ >>> RB_top = {n for n, d in RB.nodes(data=True) if d["bipartite"] == 0}
65
+ >>> RB_bottom = set(RB) - RB_top
66
+ >>> list(RB_top)
67
+ [0, 1, 2, 3, 4]
68
+ >>> list(RB_bottom)
69
+ [5, 6, 7, 8, 9, 10, 11]
70
+
71
+ For other bipartite graph generators see
72
+ :mod:`Generators <networkx.algorithms.bipartite.generators>`.
73
+
74
+ """
75
+
76
+ from networkx.algorithms.bipartite.basic import *
77
+ from networkx.algorithms.bipartite.centrality import *
78
+ from networkx.algorithms.bipartite.cluster import *
79
+ from networkx.algorithms.bipartite.covering import *
80
+ from networkx.algorithms.bipartite.edgelist import *
81
+ from networkx.algorithms.bipartite.matching import *
82
+ from networkx.algorithms.bipartite.matrix import *
83
+ from networkx.algorithms.bipartite.projection import *
84
+ from networkx.algorithms.bipartite.redundancy import *
85
+ from networkx.algorithms.bipartite.spectral import *
86
+ from networkx.algorithms.bipartite.generators import *
87
+ from networkx.algorithms.bipartite.extendability import *
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/basic.cpython-311.pyc ADDED
Binary file (11.3 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/cluster.cpython-311.pyc ADDED
Binary file (9.63 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/covering.cpython-311.pyc ADDED
Binary file (2.67 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/edgelist.cpython-311.pyc ADDED
Binary file (13.9 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/generators.cpython-311.pyc ADDED
Binary file (29.3 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/matching.cpython-311.pyc ADDED
Binary file (21.7 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/matrix.cpython-311.pyc ADDED
Binary file (8.48 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/projection.cpython-311.pyc ADDED
Binary file (23.5 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/spectral.cpython-311.pyc ADDED
Binary file (2.77 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/basic.py ADDED
@@ -0,0 +1,322 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ ==========================
3
+ Bipartite Graph Algorithms
4
+ ==========================
5
+ """
6
+
7
+ import networkx as nx
8
+ from networkx.algorithms.components import connected_components
9
+ from networkx.exception import AmbiguousSolution
10
+
11
+ __all__ = [
12
+ "is_bipartite",
13
+ "is_bipartite_node_set",
14
+ "color",
15
+ "sets",
16
+ "density",
17
+ "degrees",
18
+ ]
19
+
20
+
21
+ @nx._dispatchable
22
+ def color(G):
23
+ """Returns a two-coloring of the graph.
24
+
25
+ Raises an exception if the graph is not bipartite.
26
+
27
+ Parameters
28
+ ----------
29
+ G : NetworkX graph
30
+
31
+ Returns
32
+ -------
33
+ color : dictionary
34
+ A dictionary keyed by node with a 1 or 0 as data for each node color.
35
+
36
+ Raises
37
+ ------
38
+ NetworkXError
39
+ If the graph is not two-colorable.
40
+
41
+ Examples
42
+ --------
43
+ >>> from networkx.algorithms import bipartite
44
+ >>> G = nx.path_graph(4)
45
+ >>> c = bipartite.color(G)
46
+ >>> print(c)
47
+ {0: 1, 1: 0, 2: 1, 3: 0}
48
+
49
+ You can use this to set a node attribute indicating the bipartite set:
50
+
51
+ >>> nx.set_node_attributes(G, c, "bipartite")
52
+ >>> print(G.nodes[0]["bipartite"])
53
+ 1
54
+ >>> print(G.nodes[1]["bipartite"])
55
+ 0
56
+ """
57
+ if G.is_directed():
58
+ import itertools
59
+
60
+ def neighbors(v):
61
+ return itertools.chain.from_iterable([G.predecessors(v), G.successors(v)])
62
+
63
+ else:
64
+ neighbors = G.neighbors
65
+
66
+ color = {}
67
+ for n in G: # handle disconnected graphs
68
+ if n in color or len(G[n]) == 0: # skip isolates
69
+ continue
70
+ queue = [n]
71
+ color[n] = 1 # nodes seen with color (1 or 0)
72
+ while queue:
73
+ v = queue.pop()
74
+ c = 1 - color[v] # opposite color of node v
75
+ for w in neighbors(v):
76
+ if w in color:
77
+ if color[w] == color[v]:
78
+ raise nx.NetworkXError("Graph is not bipartite.")
79
+ else:
80
+ color[w] = c
81
+ queue.append(w)
82
+ # color isolates with 0
83
+ color.update(dict.fromkeys(nx.isolates(G), 0))
84
+ return color
85
+
86
+
87
+ @nx._dispatchable
88
+ def is_bipartite(G):
89
+ """Returns True if graph G is bipartite, False if not.
90
+
91
+ Parameters
92
+ ----------
93
+ G : NetworkX graph
94
+
95
+ Examples
96
+ --------
97
+ >>> from networkx.algorithms import bipartite
98
+ >>> G = nx.path_graph(4)
99
+ >>> print(bipartite.is_bipartite(G))
100
+ True
101
+
102
+ See Also
103
+ --------
104
+ color, is_bipartite_node_set
105
+ """
106
+ try:
107
+ color(G)
108
+ return True
109
+ except nx.NetworkXError:
110
+ return False
111
+
112
+
113
+ @nx._dispatchable
114
+ def is_bipartite_node_set(G, nodes):
115
+ """Returns True if nodes and G/nodes are a bipartition of G.
116
+
117
+ Parameters
118
+ ----------
119
+ G : NetworkX graph
120
+
121
+ nodes: list or container
122
+ Check if nodes are a one of a bipartite set.
123
+
124
+ Examples
125
+ --------
126
+ >>> from networkx.algorithms import bipartite
127
+ >>> G = nx.path_graph(4)
128
+ >>> X = set([1, 3])
129
+ >>> bipartite.is_bipartite_node_set(G, X)
130
+ True
131
+
132
+ Notes
133
+ -----
134
+ An exception is raised if the input nodes are not distinct, because in this
135
+ case some bipartite algorithms will yield incorrect results.
136
+ For connected graphs the bipartite sets are unique. This function handles
137
+ disconnected graphs.
138
+ """
139
+ S = set(nodes)
140
+
141
+ if len(S) < len(nodes):
142
+ # this should maybe just return False?
143
+ raise AmbiguousSolution(
144
+ "The input node set contains duplicates.\n"
145
+ "This may lead to incorrect results when using it in bipartite algorithms.\n"
146
+ "Consider using set(nodes) as the input"
147
+ )
148
+
149
+ for CC in (G.subgraph(c).copy() for c in connected_components(G)):
150
+ X, Y = sets(CC)
151
+ if not (
152
+ (X.issubset(S) and Y.isdisjoint(S)) or (Y.issubset(S) and X.isdisjoint(S))
153
+ ):
154
+ return False
155
+ return True
156
+
157
+
158
+ @nx._dispatchable
159
+ def sets(G, top_nodes=None):
160
+ """Returns bipartite node sets of graph G.
161
+
162
+ Raises an exception if the graph is not bipartite or if the input
163
+ graph is disconnected and thus more than one valid solution exists.
164
+ See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
165
+ for further details on how bipartite graphs are handled in NetworkX.
166
+
167
+ Parameters
168
+ ----------
169
+ G : NetworkX graph
170
+
171
+ top_nodes : container, optional
172
+ Container with all nodes in one bipartite node set. If not supplied
173
+ it will be computed. But if more than one solution exists an exception
174
+ will be raised.
175
+
176
+ Returns
177
+ -------
178
+ X : set
179
+ Nodes from one side of the bipartite graph.
180
+ Y : set
181
+ Nodes from the other side.
182
+
183
+ Raises
184
+ ------
185
+ AmbiguousSolution
186
+ Raised if the input bipartite graph is disconnected and no container
187
+ with all nodes in one bipartite set is provided. When determining
188
+ the nodes in each bipartite set more than one valid solution is
189
+ possible if the input graph is disconnected.
190
+ NetworkXError
191
+ Raised if the input graph is not bipartite.
192
+
193
+ Examples
194
+ --------
195
+ >>> from networkx.algorithms import bipartite
196
+ >>> G = nx.path_graph(4)
197
+ >>> X, Y = bipartite.sets(G)
198
+ >>> list(X)
199
+ [0, 2]
200
+ >>> list(Y)
201
+ [1, 3]
202
+
203
+ See Also
204
+ --------
205
+ color
206
+
207
+ """
208
+ if G.is_directed():
209
+ is_connected = nx.is_weakly_connected
210
+ else:
211
+ is_connected = nx.is_connected
212
+ if top_nodes is not None:
213
+ X = set(top_nodes)
214
+ Y = set(G) - X
215
+ else:
216
+ if not is_connected(G):
217
+ msg = "Disconnected graph: Ambiguous solution for bipartite sets."
218
+ raise nx.AmbiguousSolution(msg)
219
+ c = color(G)
220
+ X = {n for n, is_top in c.items() if is_top}
221
+ Y = {n for n, is_top in c.items() if not is_top}
222
+ return (X, Y)
223
+
224
+
225
+ @nx._dispatchable(graphs="B")
226
+ def density(B, nodes):
227
+ """Returns density of bipartite graph B.
228
+
229
+ Parameters
230
+ ----------
231
+ B : NetworkX graph
232
+
233
+ nodes: list or container
234
+ Nodes in one node set of the bipartite graph.
235
+
236
+ Returns
237
+ -------
238
+ d : float
239
+ The bipartite density
240
+
241
+ Examples
242
+ --------
243
+ >>> from networkx.algorithms import bipartite
244
+ >>> G = nx.complete_bipartite_graph(3, 2)
245
+ >>> X = set([0, 1, 2])
246
+ >>> bipartite.density(G, X)
247
+ 1.0
248
+ >>> Y = set([3, 4])
249
+ >>> bipartite.density(G, Y)
250
+ 1.0
251
+
252
+ Notes
253
+ -----
254
+ The container of nodes passed as argument must contain all nodes
255
+ in one of the two bipartite node sets to avoid ambiguity in the
256
+ case of disconnected graphs.
257
+ See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
258
+ for further details on how bipartite graphs are handled in NetworkX.
259
+
260
+ See Also
261
+ --------
262
+ color
263
+ """
264
+ n = len(B)
265
+ m = nx.number_of_edges(B)
266
+ nb = len(nodes)
267
+ nt = n - nb
268
+ if m == 0: # includes cases n==0 and n==1
269
+ d = 0.0
270
+ else:
271
+ if B.is_directed():
272
+ d = m / (2 * nb * nt)
273
+ else:
274
+ d = m / (nb * nt)
275
+ return d
276
+
277
+
278
+ @nx._dispatchable(graphs="B", edge_attrs="weight")
279
+ def degrees(B, nodes, weight=None):
280
+ """Returns the degrees of the two node sets in the bipartite graph B.
281
+
282
+ Parameters
283
+ ----------
284
+ B : NetworkX graph
285
+
286
+ nodes: list or container
287
+ Nodes in one node set of the bipartite graph.
288
+
289
+ weight : string or None, optional (default=None)
290
+ The edge attribute that holds the numerical value used as a weight.
291
+ If None, then each edge has weight 1.
292
+ The degree is the sum of the edge weights adjacent to the node.
293
+
294
+ Returns
295
+ -------
296
+ (degX,degY) : tuple of dictionaries
297
+ The degrees of the two bipartite sets as dictionaries keyed by node.
298
+
299
+ Examples
300
+ --------
301
+ >>> from networkx.algorithms import bipartite
302
+ >>> G = nx.complete_bipartite_graph(3, 2)
303
+ >>> Y = set([3, 4])
304
+ >>> degX, degY = bipartite.degrees(G, Y)
305
+ >>> dict(degX)
306
+ {0: 2, 1: 2, 2: 2}
307
+
308
+ Notes
309
+ -----
310
+ The container of nodes passed as argument must contain all nodes
311
+ in one of the two bipartite node sets to avoid ambiguity in the
312
+ case of disconnected graphs.
313
+ See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
314
+ for further details on how bipartite graphs are handled in NetworkX.
315
+
316
+ See Also
317
+ --------
318
+ color, density
319
+ """
320
+ bottom = set(nodes)
321
+ top = set(B) - bottom
322
+ return (B.degree(top, weight), B.degree(bottom, weight))
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/centrality.py ADDED
@@ -0,0 +1,290 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import networkx as nx
2
+
3
+ __all__ = ["degree_centrality", "betweenness_centrality", "closeness_centrality"]
4
+
5
+
6
+ @nx._dispatchable(name="bipartite_degree_centrality")
7
+ def degree_centrality(G, nodes):
8
+ r"""Compute the degree centrality for nodes in a bipartite network.
9
+
10
+ The degree centrality for a node `v` is the fraction of nodes
11
+ connected to it.
12
+
13
+ Parameters
14
+ ----------
15
+ G : graph
16
+ A bipartite network
17
+
18
+ nodes : list or container
19
+ Container with all nodes in one bipartite node set.
20
+
21
+ Returns
22
+ -------
23
+ centrality : dictionary
24
+ Dictionary keyed by node with bipartite degree centrality as the value.
25
+
26
+ Examples
27
+ --------
28
+ >>> G = nx.wheel_graph(5)
29
+ >>> top_nodes = {0, 1, 2}
30
+ >>> nx.bipartite.degree_centrality(G, nodes=top_nodes)
31
+ {0: 2.0, 1: 1.5, 2: 1.5, 3: 1.0, 4: 1.0}
32
+
33
+ See Also
34
+ --------
35
+ betweenness_centrality
36
+ closeness_centrality
37
+ :func:`~networkx.algorithms.bipartite.basic.sets`
38
+ :func:`~networkx.algorithms.bipartite.basic.is_bipartite`
39
+
40
+ Notes
41
+ -----
42
+ The nodes input parameter must contain all nodes in one bipartite node set,
43
+ but the dictionary returned contains all nodes from both bipartite node
44
+ sets. See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
45
+ for further details on how bipartite graphs are handled in NetworkX.
46
+
47
+ For unipartite networks, the degree centrality values are
48
+ normalized by dividing by the maximum possible degree (which is
49
+ `n-1` where `n` is the number of nodes in G).
50
+
51
+ In the bipartite case, the maximum possible degree of a node in a
52
+ bipartite node set is the number of nodes in the opposite node set
53
+ [1]_. The degree centrality for a node `v` in the bipartite
54
+ sets `U` with `n` nodes and `V` with `m` nodes is
55
+
56
+ .. math::
57
+
58
+ d_{v} = \frac{deg(v)}{m}, \mbox{for} v \in U ,
59
+
60
+ d_{v} = \frac{deg(v)}{n}, \mbox{for} v \in V ,
61
+
62
+
63
+ where `deg(v)` is the degree of node `v`.
64
+
65
+ References
66
+ ----------
67
+ .. [1] Borgatti, S.P. and Halgin, D. In press. "Analyzing Affiliation
68
+ Networks". In Carrington, P. and Scott, J. (eds) The Sage Handbook
69
+ of Social Network Analysis. Sage Publications.
70
+ https://dx.doi.org/10.4135/9781446294413.n28
71
+ """
72
+ top = set(nodes)
73
+ bottom = set(G) - top
74
+ s = 1.0 / len(bottom)
75
+ centrality = {n: d * s for n, d in G.degree(top)}
76
+ s = 1.0 / len(top)
77
+ centrality.update({n: d * s for n, d in G.degree(bottom)})
78
+ return centrality
79
+
80
+
81
+ @nx._dispatchable(name="bipartite_betweenness_centrality")
82
+ def betweenness_centrality(G, nodes):
83
+ r"""Compute betweenness centrality for nodes in a bipartite network.
84
+
85
+ Betweenness centrality of a node `v` is the sum of the
86
+ fraction of all-pairs shortest paths that pass through `v`.
87
+
88
+ Values of betweenness are normalized by the maximum possible
89
+ value which for bipartite graphs is limited by the relative size
90
+ of the two node sets [1]_.
91
+
92
+ Let `n` be the number of nodes in the node set `U` and
93
+ `m` be the number of nodes in the node set `V`, then
94
+ nodes in `U` are normalized by dividing by
95
+
96
+ .. math::
97
+
98
+ \frac{1}{2} [m^2 (s + 1)^2 + m (s + 1)(2t - s - 1) - t (2s - t + 3)] ,
99
+
100
+ where
101
+
102
+ .. math::
103
+
104
+ s = (n - 1) \div m , t = (n - 1) \mod m ,
105
+
106
+ and nodes in `V` are normalized by dividing by
107
+
108
+ .. math::
109
+
110
+ \frac{1}{2} [n^2 (p + 1)^2 + n (p + 1)(2r - p - 1) - r (2p - r + 3)] ,
111
+
112
+ where,
113
+
114
+ .. math::
115
+
116
+ p = (m - 1) \div n , r = (m - 1) \mod n .
117
+
118
+ Parameters
119
+ ----------
120
+ G : graph
121
+ A bipartite graph
122
+
123
+ nodes : list or container
124
+ Container with all nodes in one bipartite node set.
125
+
126
+ Returns
127
+ -------
128
+ betweenness : dictionary
129
+ Dictionary keyed by node with bipartite betweenness centrality
130
+ as the value.
131
+
132
+ Examples
133
+ --------
134
+ >>> G = nx.cycle_graph(4)
135
+ >>> top_nodes = {1, 2}
136
+ >>> nx.bipartite.betweenness_centrality(G, nodes=top_nodes)
137
+ {0: 0.25, 1: 0.25, 2: 0.25, 3: 0.25}
138
+
139
+ See Also
140
+ --------
141
+ degree_centrality
142
+ closeness_centrality
143
+ :func:`~networkx.algorithms.bipartite.basic.sets`
144
+ :func:`~networkx.algorithms.bipartite.basic.is_bipartite`
145
+
146
+ Notes
147
+ -----
148
+ The nodes input parameter must contain all nodes in one bipartite node set,
149
+ but the dictionary returned contains all nodes from both node sets.
150
+ See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
151
+ for further details on how bipartite graphs are handled in NetworkX.
152
+
153
+
154
+ References
155
+ ----------
156
+ .. [1] Borgatti, S.P. and Halgin, D. In press. "Analyzing Affiliation
157
+ Networks". In Carrington, P. and Scott, J. (eds) The Sage Handbook
158
+ of Social Network Analysis. Sage Publications.
159
+ https://dx.doi.org/10.4135/9781446294413.n28
160
+ """
161
+ top = set(nodes)
162
+ bottom = set(G) - top
163
+ n = len(top)
164
+ m = len(bottom)
165
+ s, t = divmod(n - 1, m)
166
+ bet_max_top = (
167
+ ((m**2) * ((s + 1) ** 2))
168
+ + (m * (s + 1) * (2 * t - s - 1))
169
+ - (t * ((2 * s) - t + 3))
170
+ ) / 2.0
171
+ p, r = divmod(m - 1, n)
172
+ bet_max_bot = (
173
+ ((n**2) * ((p + 1) ** 2))
174
+ + (n * (p + 1) * (2 * r - p - 1))
175
+ - (r * ((2 * p) - r + 3))
176
+ ) / 2.0
177
+ betweenness = nx.betweenness_centrality(G, normalized=False, weight=None)
178
+ for node in top:
179
+ betweenness[node] /= bet_max_top
180
+ for node in bottom:
181
+ betweenness[node] /= bet_max_bot
182
+ return betweenness
183
+
184
+
185
+ @nx._dispatchable(name="bipartite_closeness_centrality")
186
+ def closeness_centrality(G, nodes, normalized=True):
187
+ r"""Compute the closeness centrality for nodes in a bipartite network.
188
+
189
+ The closeness of a node is the distance to all other nodes in the
190
+ graph or in the case that the graph is not connected to all other nodes
191
+ in the connected component containing that node.
192
+
193
+ Parameters
194
+ ----------
195
+ G : graph
196
+ A bipartite network
197
+
198
+ nodes : list or container
199
+ Container with all nodes in one bipartite node set.
200
+
201
+ normalized : bool, optional
202
+ If True (default) normalize by connected component size.
203
+
204
+ Returns
205
+ -------
206
+ closeness : dictionary
207
+ Dictionary keyed by node with bipartite closeness centrality
208
+ as the value.
209
+
210
+ Examples
211
+ --------
212
+ >>> G = nx.wheel_graph(5)
213
+ >>> top_nodes = {0, 1, 2}
214
+ >>> nx.bipartite.closeness_centrality(G, nodes=top_nodes)
215
+ {0: 1.5, 1: 1.2, 2: 1.2, 3: 1.0, 4: 1.0}
216
+
217
+ See Also
218
+ --------
219
+ betweenness_centrality
220
+ degree_centrality
221
+ :func:`~networkx.algorithms.bipartite.basic.sets`
222
+ :func:`~networkx.algorithms.bipartite.basic.is_bipartite`
223
+
224
+ Notes
225
+ -----
226
+ The nodes input parameter must contain all nodes in one bipartite node set,
227
+ but the dictionary returned contains all nodes from both node sets.
228
+ See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
229
+ for further details on how bipartite graphs are handled in NetworkX.
230
+
231
+
232
+ Closeness centrality is normalized by the minimum distance possible.
233
+ In the bipartite case the minimum distance for a node in one bipartite
234
+ node set is 1 from all nodes in the other node set and 2 from all
235
+ other nodes in its own set [1]_. Thus the closeness centrality
236
+ for node `v` in the two bipartite sets `U` with
237
+ `n` nodes and `V` with `m` nodes is
238
+
239
+ .. math::
240
+
241
+ c_{v} = \frac{m + 2(n - 1)}{d}, \mbox{for} v \in U,
242
+
243
+ c_{v} = \frac{n + 2(m - 1)}{d}, \mbox{for} v \in V,
244
+
245
+ where `d` is the sum of the distances from `v` to all
246
+ other nodes.
247
+
248
+ Higher values of closeness indicate higher centrality.
249
+
250
+ As in the unipartite case, setting normalized=True causes the
251
+ values to normalized further to n-1 / size(G)-1 where n is the
252
+ number of nodes in the connected part of graph containing the
253
+ node. If the graph is not completely connected, this algorithm
254
+ computes the closeness centrality for each connected part
255
+ separately.
256
+
257
+ References
258
+ ----------
259
+ .. [1] Borgatti, S.P. and Halgin, D. In press. "Analyzing Affiliation
260
+ Networks". In Carrington, P. and Scott, J. (eds) The Sage Handbook
261
+ of Social Network Analysis. Sage Publications.
262
+ https://dx.doi.org/10.4135/9781446294413.n28
263
+ """
264
+ closeness = {}
265
+ path_length = nx.single_source_shortest_path_length
266
+ top = set(nodes)
267
+ bottom = set(G) - top
268
+ n = len(top)
269
+ m = len(bottom)
270
+ for node in top:
271
+ sp = dict(path_length(G, node))
272
+ totsp = sum(sp.values())
273
+ if totsp > 0.0 and len(G) > 1:
274
+ closeness[node] = (m + 2 * (n - 1)) / totsp
275
+ if normalized:
276
+ s = (len(sp) - 1) / (len(G) - 1)
277
+ closeness[node] *= s
278
+ else:
279
+ closeness[node] = 0.0
280
+ for node in bottom:
281
+ sp = dict(path_length(G, node))
282
+ totsp = sum(sp.values())
283
+ if totsp > 0.0 and len(G) > 1:
284
+ closeness[node] = (n + 2 * (m - 1)) / totsp
285
+ if normalized:
286
+ s = (len(sp) - 1) / (len(G) - 1)
287
+ closeness[node] *= s
288
+ else:
289
+ closeness[node] = 0.0
290
+ return closeness
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/cluster.py ADDED
@@ -0,0 +1,278 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Functions for computing clustering of pairs"""
2
+
3
+ import itertools
4
+
5
+ import networkx as nx
6
+
7
+ __all__ = [
8
+ "clustering",
9
+ "average_clustering",
10
+ "latapy_clustering",
11
+ "robins_alexander_clustering",
12
+ ]
13
+
14
+
15
+ def cc_dot(nu, nv):
16
+ return len(nu & nv) / len(nu | nv)
17
+
18
+
19
+ def cc_max(nu, nv):
20
+ return len(nu & nv) / max(len(nu), len(nv))
21
+
22
+
23
+ def cc_min(nu, nv):
24
+ return len(nu & nv) / min(len(nu), len(nv))
25
+
26
+
27
+ modes = {"dot": cc_dot, "min": cc_min, "max": cc_max}
28
+
29
+
30
+ @nx._dispatchable
31
+ def latapy_clustering(G, nodes=None, mode="dot"):
32
+ r"""Compute a bipartite clustering coefficient for nodes.
33
+
34
+ The bipartite clustering coefficient is a measure of local density
35
+ of connections defined as [1]_:
36
+
37
+ .. math::
38
+
39
+ c_u = \frac{\sum_{v \in N(N(u))} c_{uv} }{|N(N(u))|}
40
+
41
+ where `N(N(u))` are the second order neighbors of `u` in `G` excluding `u`,
42
+ and `c_{uv}` is the pairwise clustering coefficient between nodes
43
+ `u` and `v`.
44
+
45
+ The mode selects the function for `c_{uv}` which can be:
46
+
47
+ `dot`:
48
+
49
+ .. math::
50
+
51
+ c_{uv}=\frac{|N(u)\cap N(v)|}{|N(u) \cup N(v)|}
52
+
53
+ `min`:
54
+
55
+ .. math::
56
+
57
+ c_{uv}=\frac{|N(u)\cap N(v)|}{min(|N(u)|,|N(v)|)}
58
+
59
+ `max`:
60
+
61
+ .. math::
62
+
63
+ c_{uv}=\frac{|N(u)\cap N(v)|}{max(|N(u)|,|N(v)|)}
64
+
65
+
66
+ Parameters
67
+ ----------
68
+ G : graph
69
+ A bipartite graph
70
+
71
+ nodes : list or iterable (optional)
72
+ Compute bipartite clustering for these nodes. The default
73
+ is all nodes in G.
74
+
75
+ mode : string
76
+ The pairwise bipartite clustering method to be used in the computation.
77
+ It must be "dot", "max", or "min".
78
+
79
+ Returns
80
+ -------
81
+ clustering : dictionary
82
+ A dictionary keyed by node with the clustering coefficient value.
83
+
84
+
85
+ Examples
86
+ --------
87
+ >>> from networkx.algorithms import bipartite
88
+ >>> G = nx.path_graph(4) # path graphs are bipartite
89
+ >>> c = bipartite.clustering(G)
90
+ >>> c[0]
91
+ 0.5
92
+ >>> c = bipartite.clustering(G, mode="min")
93
+ >>> c[0]
94
+ 1.0
95
+
96
+ See Also
97
+ --------
98
+ robins_alexander_clustering
99
+ average_clustering
100
+ networkx.algorithms.cluster.square_clustering
101
+
102
+ References
103
+ ----------
104
+ .. [1] Latapy, Matthieu, Clémence Magnien, and Nathalie Del Vecchio (2008).
105
+ Basic notions for the analysis of large two-mode networks.
106
+ Social Networks 30(1), 31--48.
107
+ """
108
+ if not nx.algorithms.bipartite.is_bipartite(G):
109
+ raise nx.NetworkXError("Graph is not bipartite")
110
+
111
+ try:
112
+ cc_func = modes[mode]
113
+ except KeyError as err:
114
+ raise nx.NetworkXError(
115
+ "Mode for bipartite clustering must be: dot, min or max"
116
+ ) from err
117
+
118
+ if nodes is None:
119
+ nodes = G
120
+ ccs = {}
121
+ for v in nodes:
122
+ cc = 0.0
123
+ nbrs2 = {u for nbr in G[v] for u in G[nbr]} - {v}
124
+ for u in nbrs2:
125
+ cc += cc_func(set(G[u]), set(G[v]))
126
+ if cc > 0.0: # len(nbrs2)>0
127
+ cc /= len(nbrs2)
128
+ ccs[v] = cc
129
+ return ccs
130
+
131
+
132
+ clustering = latapy_clustering
133
+
134
+
135
+ @nx._dispatchable(name="bipartite_average_clustering")
136
+ def average_clustering(G, nodes=None, mode="dot"):
137
+ r"""Compute the average bipartite clustering coefficient.
138
+
139
+ A clustering coefficient for the whole graph is the average,
140
+
141
+ .. math::
142
+
143
+ C = \frac{1}{n}\sum_{v \in G} c_v,
144
+
145
+ where `n` is the number of nodes in `G`.
146
+
147
+ Similar measures for the two bipartite sets can be defined [1]_
148
+
149
+ .. math::
150
+
151
+ C_X = \frac{1}{|X|}\sum_{v \in X} c_v,
152
+
153
+ where `X` is a bipartite set of `G`.
154
+
155
+ Parameters
156
+ ----------
157
+ G : graph
158
+ a bipartite graph
159
+
160
+ nodes : list or iterable, optional
161
+ A container of nodes to use in computing the average.
162
+ The nodes should be either the entire graph (the default) or one of the
163
+ bipartite sets.
164
+
165
+ mode : string
166
+ The pairwise bipartite clustering method.
167
+ It must be "dot", "max", or "min"
168
+
169
+ Returns
170
+ -------
171
+ clustering : float
172
+ The average bipartite clustering for the given set of nodes or the
173
+ entire graph if no nodes are specified.
174
+
175
+ Examples
176
+ --------
177
+ >>> from networkx.algorithms import bipartite
178
+ >>> G = nx.star_graph(3) # star graphs are bipartite
179
+ >>> bipartite.average_clustering(G)
180
+ 0.75
181
+ >>> X, Y = bipartite.sets(G)
182
+ >>> bipartite.average_clustering(G, X)
183
+ 0.0
184
+ >>> bipartite.average_clustering(G, Y)
185
+ 1.0
186
+
187
+ See Also
188
+ --------
189
+ clustering
190
+
191
+ Notes
192
+ -----
193
+ The container of nodes passed to this function must contain all of the nodes
194
+ in one of the bipartite sets ("top" or "bottom") in order to compute
195
+ the correct average bipartite clustering coefficients.
196
+ See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
197
+ for further details on how bipartite graphs are handled in NetworkX.
198
+
199
+
200
+ References
201
+ ----------
202
+ .. [1] Latapy, Matthieu, Clémence Magnien, and Nathalie Del Vecchio (2008).
203
+ Basic notions for the analysis of large two-mode networks.
204
+ Social Networks 30(1), 31--48.
205
+ """
206
+ if nodes is None:
207
+ nodes = G
208
+ ccs = latapy_clustering(G, nodes=nodes, mode=mode)
209
+ return sum(ccs[v] for v in nodes) / len(nodes)
210
+
211
+
212
+ @nx._dispatchable
213
+ def robins_alexander_clustering(G):
214
+ r"""Compute the bipartite clustering of G.
215
+
216
+ Robins and Alexander [1]_ defined bipartite clustering coefficient as
217
+ four times the number of four cycles `C_4` divided by the number of
218
+ three paths `L_3` in a bipartite graph:
219
+
220
+ .. math::
221
+
222
+ CC_4 = \frac{4 * C_4}{L_3}
223
+
224
+ Parameters
225
+ ----------
226
+ G : graph
227
+ a bipartite graph
228
+
229
+ Returns
230
+ -------
231
+ clustering : float
232
+ The Robins and Alexander bipartite clustering for the input graph.
233
+
234
+ Examples
235
+ --------
236
+ >>> from networkx.algorithms import bipartite
237
+ >>> G = nx.davis_southern_women_graph()
238
+ >>> print(round(bipartite.robins_alexander_clustering(G), 3))
239
+ 0.468
240
+
241
+ See Also
242
+ --------
243
+ latapy_clustering
244
+ networkx.algorithms.cluster.square_clustering
245
+
246
+ References
247
+ ----------
248
+ .. [1] Robins, G. and M. Alexander (2004). Small worlds among interlocking
249
+ directors: Network structure and distance in bipartite graphs.
250
+ Computational & Mathematical Organization Theory 10(1), 69–94.
251
+
252
+ """
253
+ if G.order() < 4 or G.size() < 3:
254
+ return 0
255
+ L_3 = _threepaths(G)
256
+ if L_3 == 0:
257
+ return 0
258
+ C_4 = _four_cycles(G)
259
+ return (4.0 * C_4) / L_3
260
+
261
+
262
+ def _four_cycles(G):
263
+ cycles = 0
264
+ for v in G:
265
+ for u, w in itertools.combinations(G[v], 2):
266
+ cycles += len((set(G[u]) & set(G[w])) - {v})
267
+ return cycles / 4
268
+
269
+
270
+ def _threepaths(G):
271
+ paths = 0
272
+ for v in G:
273
+ for u in G[v]:
274
+ for w in set(G[u]) - {v}:
275
+ paths += len(set(G[w]) - {v, u})
276
+ # Divide by two because we count each three path twice
277
+ # one for each possible starting point
278
+ return paths / 2
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/covering.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Functions related to graph covers."""
2
+
3
+ import networkx as nx
4
+ from networkx.algorithms.bipartite.matching import hopcroft_karp_matching
5
+ from networkx.algorithms.covering import min_edge_cover as _min_edge_cover
6
+ from networkx.utils import not_implemented_for
7
+
8
+ __all__ = ["min_edge_cover"]
9
+
10
+
11
+ @not_implemented_for("directed")
12
+ @not_implemented_for("multigraph")
13
+ @nx._dispatchable(name="bipartite_min_edge_cover")
14
+ def min_edge_cover(G, matching_algorithm=None):
15
+ """Returns a set of edges which constitutes
16
+ the minimum edge cover of the graph.
17
+
18
+ The smallest edge cover can be found in polynomial time by finding
19
+ a maximum matching and extending it greedily so that all nodes
20
+ are covered.
21
+
22
+ Parameters
23
+ ----------
24
+ G : NetworkX graph
25
+ An undirected bipartite graph.
26
+
27
+ matching_algorithm : function
28
+ A function that returns a maximum cardinality matching in a
29
+ given bipartite graph. The function must take one input, the
30
+ graph ``G``, and return a dictionary mapping each node to its
31
+ mate. If not specified,
32
+ :func:`~networkx.algorithms.bipartite.matching.hopcroft_karp_matching`
33
+ will be used. Other possibilities include
34
+ :func:`~networkx.algorithms.bipartite.matching.eppstein_matching`,
35
+
36
+ Returns
37
+ -------
38
+ set
39
+ A set of the edges in a minimum edge cover of the graph, given as
40
+ pairs of nodes. It contains both the edges `(u, v)` and `(v, u)`
41
+ for given nodes `u` and `v` among the edges of minimum edge cover.
42
+
43
+ Notes
44
+ -----
45
+ An edge cover of a graph is a set of edges such that every node of
46
+ the graph is incident to at least one edge of the set.
47
+ A minimum edge cover is an edge covering of smallest cardinality.
48
+
49
+ Due to its implementation, the worst-case running time of this algorithm
50
+ is bounded by the worst-case running time of the function
51
+ ``matching_algorithm``.
52
+ """
53
+ if G.order() == 0: # Special case for the empty graph
54
+ return set()
55
+ if matching_algorithm is None:
56
+ matching_algorithm = hopcroft_karp_matching
57
+ return _min_edge_cover(G, matching_algorithm=matching_algorithm)
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/edgelist.py ADDED
@@ -0,0 +1,360 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ ********************
3
+ Bipartite Edge Lists
4
+ ********************
5
+ Read and write NetworkX graphs as bipartite edge lists.
6
+
7
+ Format
8
+ ------
9
+ You can read or write three formats of edge lists with these functions.
10
+
11
+ Node pairs with no data::
12
+
13
+ 1 2
14
+
15
+ Python dictionary as data::
16
+
17
+ 1 2 {'weight':7, 'color':'green'}
18
+
19
+ Arbitrary data::
20
+
21
+ 1 2 7 green
22
+
23
+ For each edge (u, v) the node u is assigned to part 0 and the node v to part 1.
24
+ """
25
+
26
+ __all__ = ["generate_edgelist", "write_edgelist", "parse_edgelist", "read_edgelist"]
27
+
28
+ import networkx as nx
29
+ from networkx.utils import not_implemented_for, open_file
30
+
31
+
32
+ @open_file(1, mode="wb")
33
+ def write_edgelist(G, path, comments="#", delimiter=" ", data=True, encoding="utf-8"):
34
+ """Write a bipartite graph as a list of edges.
35
+
36
+ Parameters
37
+ ----------
38
+ G : Graph
39
+ A NetworkX bipartite graph
40
+ path : file or string
41
+ File or filename to write. If a file is provided, it must be
42
+ opened in 'wb' mode. Filenames ending in .gz or .bz2 will be compressed.
43
+ comments : string, optional
44
+ The character used to indicate the start of a comment
45
+ delimiter : string, optional
46
+ The string used to separate values. The default is whitespace.
47
+ data : bool or list, optional
48
+ If False write no edge data.
49
+ If True write a string representation of the edge data dictionary..
50
+ If a list (or other iterable) is provided, write the keys specified
51
+ in the list.
52
+ encoding: string, optional
53
+ Specify which encoding to use when writing file.
54
+
55
+ Examples
56
+ --------
57
+ >>> G = nx.path_graph(4)
58
+ >>> G.add_nodes_from([0, 2], bipartite=0)
59
+ >>> G.add_nodes_from([1, 3], bipartite=1)
60
+ >>> nx.write_edgelist(G, "test.edgelist")
61
+ >>> fh = open("test.edgelist", "wb")
62
+ >>> nx.write_edgelist(G, fh)
63
+ >>> nx.write_edgelist(G, "test.edgelist.gz")
64
+ >>> nx.write_edgelist(G, "test.edgelist.gz", data=False)
65
+
66
+ >>> G = nx.Graph()
67
+ >>> G.add_edge(1, 2, weight=7, color="red")
68
+ >>> nx.write_edgelist(G, "test.edgelist", data=False)
69
+ >>> nx.write_edgelist(G, "test.edgelist", data=["color"])
70
+ >>> nx.write_edgelist(G, "test.edgelist", data=["color", "weight"])
71
+
72
+ See Also
73
+ --------
74
+ write_edgelist
75
+ generate_edgelist
76
+ """
77
+ for line in generate_edgelist(G, delimiter, data):
78
+ line += "\n"
79
+ path.write(line.encode(encoding))
80
+
81
+
82
+ @not_implemented_for("directed")
83
+ def generate_edgelist(G, delimiter=" ", data=True):
84
+ """Generate a single line of the bipartite graph G in edge list format.
85
+
86
+ Parameters
87
+ ----------
88
+ G : NetworkX graph
89
+ The graph is assumed to have node attribute `part` set to 0,1 representing
90
+ the two graph parts
91
+
92
+ delimiter : string, optional
93
+ Separator for node labels
94
+
95
+ data : bool or list of keys
96
+ If False generate no edge data. If True use a dictionary
97
+ representation of edge data. If a list of keys use a list of data
98
+ values corresponding to the keys.
99
+
100
+ Returns
101
+ -------
102
+ lines : string
103
+ Lines of data in adjlist format.
104
+
105
+ Examples
106
+ --------
107
+ >>> from networkx.algorithms import bipartite
108
+ >>> G = nx.path_graph(4)
109
+ >>> G.add_nodes_from([0, 2], bipartite=0)
110
+ >>> G.add_nodes_from([1, 3], bipartite=1)
111
+ >>> G[1][2]["weight"] = 3
112
+ >>> G[2][3]["capacity"] = 12
113
+ >>> for line in bipartite.generate_edgelist(G, data=False):
114
+ ... print(line)
115
+ 0 1
116
+ 2 1
117
+ 2 3
118
+
119
+ >>> for line in bipartite.generate_edgelist(G):
120
+ ... print(line)
121
+ 0 1 {}
122
+ 2 1 {'weight': 3}
123
+ 2 3 {'capacity': 12}
124
+
125
+ >>> for line in bipartite.generate_edgelist(G, data=["weight"]):
126
+ ... print(line)
127
+ 0 1
128
+ 2 1 3
129
+ 2 3
130
+ """
131
+ try:
132
+ part0 = [n for n, d in G.nodes.items() if d["bipartite"] == 0]
133
+ except BaseException as err:
134
+ raise AttributeError("Missing node attribute `bipartite`") from err
135
+ if data is True or data is False:
136
+ for n in part0:
137
+ for edge in G.edges(n, data=data):
138
+ yield delimiter.join(map(str, edge))
139
+ else:
140
+ for n in part0:
141
+ for u, v, d in G.edges(n, data=True):
142
+ edge = [u, v]
143
+ try:
144
+ edge.extend(d[k] for k in data)
145
+ except KeyError:
146
+ pass # missing data for this edge, should warn?
147
+ yield delimiter.join(map(str, edge))
148
+
149
+
150
+ @nx._dispatchable(name="bipartite_parse_edgelist", graphs=None, returns_graph=True)
151
+ def parse_edgelist(
152
+ lines, comments="#", delimiter=None, create_using=None, nodetype=None, data=True
153
+ ):
154
+ """Parse lines of an edge list representation of a bipartite graph.
155
+
156
+ Parameters
157
+ ----------
158
+ lines : list or iterator of strings
159
+ Input data in edgelist format
160
+ comments : string, optional
161
+ Marker for comment lines
162
+ delimiter : string, optional
163
+ Separator for node labels
164
+ create_using: NetworkX graph container, optional
165
+ Use given NetworkX graph for holding nodes or edges.
166
+ nodetype : Python type, optional
167
+ Convert nodes to this type.
168
+ data : bool or list of (label,type) tuples
169
+ If False generate no edge data or if True use a dictionary
170
+ representation of edge data or a list tuples specifying dictionary
171
+ key names and types for edge data.
172
+
173
+ Returns
174
+ -------
175
+ G: NetworkX Graph
176
+ The bipartite graph corresponding to lines
177
+
178
+ Examples
179
+ --------
180
+ Edgelist with no data:
181
+
182
+ >>> from networkx.algorithms import bipartite
183
+ >>> lines = ["1 2", "2 3", "3 4"]
184
+ >>> G = bipartite.parse_edgelist(lines, nodetype=int)
185
+ >>> sorted(G.nodes())
186
+ [1, 2, 3, 4]
187
+ >>> sorted(G.nodes(data=True))
188
+ [(1, {'bipartite': 0}), (2, {'bipartite': 0}), (3, {'bipartite': 0}), (4, {'bipartite': 1})]
189
+ >>> sorted(G.edges())
190
+ [(1, 2), (2, 3), (3, 4)]
191
+
192
+ Edgelist with data in Python dictionary representation:
193
+
194
+ >>> lines = ["1 2 {'weight':3}", "2 3 {'weight':27}", "3 4 {'weight':3.0}"]
195
+ >>> G = bipartite.parse_edgelist(lines, nodetype=int)
196
+ >>> sorted(G.nodes())
197
+ [1, 2, 3, 4]
198
+ >>> sorted(G.edges(data=True))
199
+ [(1, 2, {'weight': 3}), (2, 3, {'weight': 27}), (3, 4, {'weight': 3.0})]
200
+
201
+ Edgelist with data in a list:
202
+
203
+ >>> lines = ["1 2 3", "2 3 27", "3 4 3.0"]
204
+ >>> G = bipartite.parse_edgelist(lines, nodetype=int, data=(("weight", float),))
205
+ >>> sorted(G.nodes())
206
+ [1, 2, 3, 4]
207
+ >>> sorted(G.edges(data=True))
208
+ [(1, 2, {'weight': 3.0}), (2, 3, {'weight': 27.0}), (3, 4, {'weight': 3.0})]
209
+
210
+ See Also
211
+ --------
212
+ """
213
+ from ast import literal_eval
214
+
215
+ G = nx.empty_graph(0, create_using)
216
+ for line in lines:
217
+ p = line.find(comments)
218
+ if p >= 0:
219
+ line = line[:p]
220
+ if not len(line):
221
+ continue
222
+ # split line, should have 2 or more
223
+ s = line.rstrip("\n").split(delimiter)
224
+ if len(s) < 2:
225
+ continue
226
+ u = s.pop(0)
227
+ v = s.pop(0)
228
+ d = s
229
+ if nodetype is not None:
230
+ try:
231
+ u = nodetype(u)
232
+ v = nodetype(v)
233
+ except BaseException as err:
234
+ raise TypeError(
235
+ f"Failed to convert nodes {u},{v} to type {nodetype}."
236
+ ) from err
237
+
238
+ if len(d) == 0 or data is False:
239
+ # no data or data type specified
240
+ edgedata = {}
241
+ elif data is True:
242
+ # no edge types specified
243
+ try: # try to evaluate as dictionary
244
+ edgedata = dict(literal_eval(" ".join(d)))
245
+ except BaseException as err:
246
+ raise TypeError(
247
+ f"Failed to convert edge data ({d}) to dictionary."
248
+ ) from err
249
+ else:
250
+ # convert edge data to dictionary with specified keys and type
251
+ if len(d) != len(data):
252
+ raise IndexError(
253
+ f"Edge data {d} and data_keys {data} are not the same length"
254
+ )
255
+ edgedata = {}
256
+ for (edge_key, edge_type), edge_value in zip(data, d):
257
+ try:
258
+ edge_value = edge_type(edge_value)
259
+ except BaseException as err:
260
+ raise TypeError(
261
+ f"Failed to convert {edge_key} data "
262
+ f"{edge_value} to type {edge_type}."
263
+ ) from err
264
+ edgedata.update({edge_key: edge_value})
265
+ G.add_node(u, bipartite=0)
266
+ G.add_node(v, bipartite=1)
267
+ G.add_edge(u, v, **edgedata)
268
+ return G
269
+
270
+
271
+ @open_file(0, mode="rb")
272
+ @nx._dispatchable(name="bipartite_read_edgelist", graphs=None, returns_graph=True)
273
+ def read_edgelist(
274
+ path,
275
+ comments="#",
276
+ delimiter=None,
277
+ create_using=None,
278
+ nodetype=None,
279
+ data=True,
280
+ edgetype=None,
281
+ encoding="utf-8",
282
+ ):
283
+ """Read a bipartite graph from a list of edges.
284
+
285
+ Parameters
286
+ ----------
287
+ path : file or string
288
+ File or filename to read. If a file is provided, it must be
289
+ opened in 'rb' mode.
290
+ Filenames ending in .gz or .bz2 will be uncompressed.
291
+ comments : string, optional
292
+ The character used to indicate the start of a comment.
293
+ delimiter : string, optional
294
+ The string used to separate values. The default is whitespace.
295
+ create_using : Graph container, optional,
296
+ Use specified container to build graph. The default is networkx.Graph,
297
+ an undirected graph.
298
+ nodetype : int, float, str, Python type, optional
299
+ Convert node data from strings to specified type
300
+ data : bool or list of (label,type) tuples
301
+ Tuples specifying dictionary key names and types for edge data
302
+ edgetype : int, float, str, Python type, optional OBSOLETE
303
+ Convert edge data from strings to specified type and use as 'weight'
304
+ encoding: string, optional
305
+ Specify which encoding to use when reading file.
306
+
307
+ Returns
308
+ -------
309
+ G : graph
310
+ A networkx Graph or other type specified with create_using
311
+
312
+ Examples
313
+ --------
314
+ >>> from networkx.algorithms import bipartite
315
+ >>> G = nx.path_graph(4)
316
+ >>> G.add_nodes_from([0, 2], bipartite=0)
317
+ >>> G.add_nodes_from([1, 3], bipartite=1)
318
+ >>> bipartite.write_edgelist(G, "test.edgelist")
319
+ >>> G = bipartite.read_edgelist("test.edgelist")
320
+
321
+ >>> fh = open("test.edgelist", "rb")
322
+ >>> G = bipartite.read_edgelist(fh)
323
+ >>> fh.close()
324
+
325
+ >>> G = bipartite.read_edgelist("test.edgelist", nodetype=int)
326
+
327
+ Edgelist with data in a list:
328
+
329
+ >>> textline = "1 2 3"
330
+ >>> fh = open("test.edgelist", "w")
331
+ >>> d = fh.write(textline)
332
+ >>> fh.close()
333
+ >>> G = bipartite.read_edgelist(
334
+ ... "test.edgelist", nodetype=int, data=(("weight", float),)
335
+ ... )
336
+ >>> list(G)
337
+ [1, 2]
338
+ >>> list(G.edges(data=True))
339
+ [(1, 2, {'weight': 3.0})]
340
+
341
+ See parse_edgelist() for more examples of formatting.
342
+
343
+ See Also
344
+ --------
345
+ parse_edgelist
346
+
347
+ Notes
348
+ -----
349
+ Since nodes must be hashable, the function nodetype must return hashable
350
+ types (e.g. int, float, str, frozenset - or tuples of those, etc.)
351
+ """
352
+ lines = (line.decode(encoding) for line in path)
353
+ return parse_edgelist(
354
+ lines,
355
+ comments=comments,
356
+ delimiter=delimiter,
357
+ create_using=create_using,
358
+ nodetype=nodetype,
359
+ data=data,
360
+ )
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/extendability.py ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Provides a function for computing the extendability of a graph which is
2
+ undirected, simple, connected and bipartite and contains at least one perfect matching."""
3
+
4
+ import networkx as nx
5
+ from networkx.utils import not_implemented_for
6
+
7
+ __all__ = ["maximal_extendability"]
8
+
9
+
10
+ @not_implemented_for("directed")
11
+ @not_implemented_for("multigraph")
12
+ @nx._dispatchable
13
+ def maximal_extendability(G):
14
+ """Computes the extendability of a graph.
15
+
16
+ The extendability of a graph is defined as the maximum $k$ for which `G`
17
+ is $k$-extendable. Graph `G` is $k$-extendable if and only if `G` has a
18
+ perfect matching and every set of $k$ independent edges can be extended
19
+ to a perfect matching in `G`.
20
+
21
+ Parameters
22
+ ----------
23
+ G : NetworkX Graph
24
+ A fully-connected bipartite graph without self-loops
25
+
26
+ Returns
27
+ -------
28
+ extendability : int
29
+
30
+ Raises
31
+ ------
32
+ NetworkXError
33
+ If the graph `G` is disconnected.
34
+ If the graph `G` is not bipartite.
35
+ If the graph `G` does not contain a perfect matching.
36
+ If the residual graph of `G` is not strongly connected.
37
+
38
+ Notes
39
+ -----
40
+ Definition:
41
+ Let `G` be a simple, connected, undirected and bipartite graph with a perfect
42
+ matching M and bipartition (U,V). The residual graph of `G`, denoted by $G_M$,
43
+ is the graph obtained from G by directing the edges of M from V to U and the
44
+ edges that do not belong to M from U to V.
45
+
46
+ Lemma [1]_ :
47
+ Let M be a perfect matching of `G`. `G` is $k$-extendable if and only if its residual
48
+ graph $G_M$ is strongly connected and there are $k$ vertex-disjoint directed
49
+ paths between every vertex of U and every vertex of V.
50
+
51
+ Assuming that input graph `G` is undirected, simple, connected, bipartite and contains
52
+ a perfect matching M, this function constructs the residual graph $G_M$ of G and
53
+ returns the minimum value among the maximum vertex-disjoint directed paths between
54
+ every vertex of U and every vertex of V in $G_M$. By combining the definitions
55
+ and the lemma, this value represents the extendability of the graph `G`.
56
+
57
+ Time complexity O($n^3$ $m^2$)) where $n$ is the number of vertices
58
+ and $m$ is the number of edges.
59
+
60
+ References
61
+ ----------
62
+ .. [1] "A polynomial algorithm for the extendability problem in bipartite graphs",
63
+ J. Lakhal, L. Litzler, Information Processing Letters, 1998.
64
+ .. [2] "On n-extendible graphs", M. D. Plummer, Discrete Mathematics, 31:201–210, 1980
65
+ https://doi.org/10.1016/0012-365X(80)90037-0
66
+
67
+ """
68
+ if not nx.is_connected(G):
69
+ raise nx.NetworkXError("Graph G is not connected")
70
+
71
+ if not nx.bipartite.is_bipartite(G):
72
+ raise nx.NetworkXError("Graph G is not bipartite")
73
+
74
+ U, V = nx.bipartite.sets(G)
75
+
76
+ maximum_matching = nx.bipartite.hopcroft_karp_matching(G)
77
+
78
+ if not nx.is_perfect_matching(G, maximum_matching):
79
+ raise nx.NetworkXError("Graph G does not contain a perfect matching")
80
+
81
+ # list of edges in perfect matching, directed from V to U
82
+ pm = [(node, maximum_matching[node]) for node in V & maximum_matching.keys()]
83
+
84
+ # Direct all the edges of G, from V to U if in matching, else from U to V
85
+ directed_edges = [
86
+ (x, y) if (x in V and (x, y) in pm) or (x in U and (y, x) not in pm) else (y, x)
87
+ for x, y in G.edges
88
+ ]
89
+
90
+ # Construct the residual graph of G
91
+ residual_G = nx.DiGraph()
92
+ residual_G.add_nodes_from(G)
93
+ residual_G.add_edges_from(directed_edges)
94
+
95
+ if not nx.is_strongly_connected(residual_G):
96
+ raise nx.NetworkXError("The residual graph of G is not strongly connected")
97
+
98
+ # For node-pairs between V & U, keep min of max number of node-disjoint paths
99
+ # Variable $k$ stands for the extendability of graph G
100
+ k = float("inf")
101
+ for u in U:
102
+ for v in V:
103
+ num_paths = sum(1 for _ in nx.node_disjoint_paths(residual_G, u, v))
104
+ k = k if k < num_paths else num_paths
105
+ return k
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/generators.py ADDED
@@ -0,0 +1,604 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Generators and functions for bipartite graphs.
3
+ """
4
+
5
+ import math
6
+ import numbers
7
+ from functools import reduce
8
+
9
+ import networkx as nx
10
+ from networkx.utils import nodes_or_number, py_random_state
11
+
12
+ __all__ = [
13
+ "configuration_model",
14
+ "havel_hakimi_graph",
15
+ "reverse_havel_hakimi_graph",
16
+ "alternating_havel_hakimi_graph",
17
+ "preferential_attachment_graph",
18
+ "random_graph",
19
+ "gnmk_random_graph",
20
+ "complete_bipartite_graph",
21
+ ]
22
+
23
+
24
+ @nx._dispatchable(graphs=None, returns_graph=True)
25
+ @nodes_or_number([0, 1])
26
+ def complete_bipartite_graph(n1, n2, create_using=None):
27
+ """Returns the complete bipartite graph `K_{n_1,n_2}`.
28
+
29
+ The graph is composed of two partitions with nodes 0 to (n1 - 1)
30
+ in the first and nodes n1 to (n1 + n2 - 1) in the second.
31
+ Each node in the first is connected to each node in the second.
32
+
33
+ Parameters
34
+ ----------
35
+ n1, n2 : integer or iterable container of nodes
36
+ If integers, nodes are from `range(n1)` and `range(n1, n1 + n2)`.
37
+ If a container, the elements are the nodes.
38
+ create_using : NetworkX graph instance, (default: nx.Graph)
39
+ Return graph of this type.
40
+
41
+ Notes
42
+ -----
43
+ Nodes are the integers 0 to `n1 + n2 - 1` unless either n1 or n2 are
44
+ containers of nodes. If only one of n1 or n2 are integers, that
45
+ integer is replaced by `range` of that integer.
46
+
47
+ The nodes are assigned the attribute 'bipartite' with the value 0 or 1
48
+ to indicate which bipartite set the node belongs to.
49
+
50
+ This function is not imported in the main namespace.
51
+ To use it use nx.bipartite.complete_bipartite_graph
52
+ """
53
+ G = nx.empty_graph(0, create_using)
54
+ if G.is_directed():
55
+ raise nx.NetworkXError("Directed Graph not supported")
56
+
57
+ n1, top = n1
58
+ n2, bottom = n2
59
+ if isinstance(n1, numbers.Integral) and isinstance(n2, numbers.Integral):
60
+ bottom = [n1 + i for i in bottom]
61
+ G.add_nodes_from(top, bipartite=0)
62
+ G.add_nodes_from(bottom, bipartite=1)
63
+ if len(G) != len(top) + len(bottom):
64
+ raise nx.NetworkXError("Inputs n1 and n2 must contain distinct nodes")
65
+ G.add_edges_from((u, v) for u in top for v in bottom)
66
+ G.graph["name"] = f"complete_bipartite_graph({len(top)}, {len(bottom)})"
67
+ return G
68
+
69
+
70
+ @py_random_state(3)
71
+ @nx._dispatchable(name="bipartite_configuration_model", graphs=None, returns_graph=True)
72
+ def configuration_model(aseq, bseq, create_using=None, seed=None):
73
+ """Returns a random bipartite graph from two given degree sequences.
74
+
75
+ Parameters
76
+ ----------
77
+ aseq : list
78
+ Degree sequence for node set A.
79
+ bseq : list
80
+ Degree sequence for node set B.
81
+ create_using : NetworkX graph instance, optional
82
+ Return graph of this type.
83
+ seed : integer, random_state, or None (default)
84
+ Indicator of random number generation state.
85
+ See :ref:`Randomness<randomness>`.
86
+
87
+ The graph is composed of two partitions. Set A has nodes 0 to
88
+ (len(aseq) - 1) and set B has nodes len(aseq) to (len(bseq) - 1).
89
+ Nodes from set A are connected to nodes in set B by choosing
90
+ randomly from the possible free stubs, one in A and one in B.
91
+
92
+ Notes
93
+ -----
94
+ The sum of the two sequences must be equal: sum(aseq)=sum(bseq)
95
+ If no graph type is specified use MultiGraph with parallel edges.
96
+ If you want a graph with no parallel edges use create_using=Graph()
97
+ but then the resulting degree sequences might not be exact.
98
+
99
+ The nodes are assigned the attribute 'bipartite' with the value 0 or 1
100
+ to indicate which bipartite set the node belongs to.
101
+
102
+ This function is not imported in the main namespace.
103
+ To use it use nx.bipartite.configuration_model
104
+ """
105
+ G = nx.empty_graph(0, create_using, default=nx.MultiGraph)
106
+ if G.is_directed():
107
+ raise nx.NetworkXError("Directed Graph not supported")
108
+
109
+ # length and sum of each sequence
110
+ lena = len(aseq)
111
+ lenb = len(bseq)
112
+ suma = sum(aseq)
113
+ sumb = sum(bseq)
114
+
115
+ if not suma == sumb:
116
+ raise nx.NetworkXError(
117
+ f"invalid degree sequences, sum(aseq)!=sum(bseq),{suma},{sumb}"
118
+ )
119
+
120
+ G = _add_nodes_with_bipartite_label(G, lena, lenb)
121
+
122
+ if len(aseq) == 0 or max(aseq) == 0:
123
+ return G # done if no edges
124
+
125
+ # build lists of degree-repeated vertex numbers
126
+ stubs = [[v] * aseq[v] for v in range(lena)]
127
+ astubs = [x for subseq in stubs for x in subseq]
128
+
129
+ stubs = [[v] * bseq[v - lena] for v in range(lena, lena + lenb)]
130
+ bstubs = [x for subseq in stubs for x in subseq]
131
+
132
+ # shuffle lists
133
+ seed.shuffle(astubs)
134
+ seed.shuffle(bstubs)
135
+
136
+ G.add_edges_from([astubs[i], bstubs[i]] for i in range(suma))
137
+
138
+ G.name = "bipartite_configuration_model"
139
+ return G
140
+
141
+
142
+ @nx._dispatchable(name="bipartite_havel_hakimi_graph", graphs=None, returns_graph=True)
143
+ def havel_hakimi_graph(aseq, bseq, create_using=None):
144
+ """Returns a bipartite graph from two given degree sequences using a
145
+ Havel-Hakimi style construction.
146
+
147
+ The graph is composed of two partitions. Set A has nodes 0 to
148
+ (len(aseq) - 1) and set B has nodes len(aseq) to (len(bseq) - 1).
149
+ Nodes from the set A are connected to nodes in the set B by
150
+ connecting the highest degree nodes in set A to the highest degree
151
+ nodes in set B until all stubs are connected.
152
+
153
+ Parameters
154
+ ----------
155
+ aseq : list
156
+ Degree sequence for node set A.
157
+ bseq : list
158
+ Degree sequence for node set B.
159
+ create_using : NetworkX graph instance, optional
160
+ Return graph of this type.
161
+
162
+ Notes
163
+ -----
164
+ The sum of the two sequences must be equal: sum(aseq)=sum(bseq)
165
+ If no graph type is specified use MultiGraph with parallel edges.
166
+ If you want a graph with no parallel edges use create_using=Graph()
167
+ but then the resulting degree sequences might not be exact.
168
+
169
+ The nodes are assigned the attribute 'bipartite' with the value 0 or 1
170
+ to indicate which bipartite set the node belongs to.
171
+
172
+ This function is not imported in the main namespace.
173
+ To use it use nx.bipartite.havel_hakimi_graph
174
+ """
175
+ G = nx.empty_graph(0, create_using, default=nx.MultiGraph)
176
+ if G.is_directed():
177
+ raise nx.NetworkXError("Directed Graph not supported")
178
+
179
+ # length of the each sequence
180
+ naseq = len(aseq)
181
+ nbseq = len(bseq)
182
+
183
+ suma = sum(aseq)
184
+ sumb = sum(bseq)
185
+
186
+ if not suma == sumb:
187
+ raise nx.NetworkXError(
188
+ f"invalid degree sequences, sum(aseq)!=sum(bseq),{suma},{sumb}"
189
+ )
190
+
191
+ G = _add_nodes_with_bipartite_label(G, naseq, nbseq)
192
+
193
+ if len(aseq) == 0 or max(aseq) == 0:
194
+ return G # done if no edges
195
+
196
+ # build list of degree-repeated vertex numbers
197
+ astubs = [[aseq[v], v] for v in range(naseq)]
198
+ bstubs = [[bseq[v - naseq], v] for v in range(naseq, naseq + nbseq)]
199
+ astubs.sort()
200
+ while astubs:
201
+ (degree, u) = astubs.pop() # take of largest degree node in the a set
202
+ if degree == 0:
203
+ break # done, all are zero
204
+ # connect the source to largest degree nodes in the b set
205
+ bstubs.sort()
206
+ for target in bstubs[-degree:]:
207
+ v = target[1]
208
+ G.add_edge(u, v)
209
+ target[0] -= 1 # note this updates bstubs too.
210
+ if target[0] == 0:
211
+ bstubs.remove(target)
212
+
213
+ G.name = "bipartite_havel_hakimi_graph"
214
+ return G
215
+
216
+
217
+ @nx._dispatchable(graphs=None, returns_graph=True)
218
+ def reverse_havel_hakimi_graph(aseq, bseq, create_using=None):
219
+ """Returns a bipartite graph from two given degree sequences using a
220
+ Havel-Hakimi style construction.
221
+
222
+ The graph is composed of two partitions. Set A has nodes 0 to
223
+ (len(aseq) - 1) and set B has nodes len(aseq) to (len(bseq) - 1).
224
+ Nodes from set A are connected to nodes in the set B by connecting
225
+ the highest degree nodes in set A to the lowest degree nodes in
226
+ set B until all stubs are connected.
227
+
228
+ Parameters
229
+ ----------
230
+ aseq : list
231
+ Degree sequence for node set A.
232
+ bseq : list
233
+ Degree sequence for node set B.
234
+ create_using : NetworkX graph instance, optional
235
+ Return graph of this type.
236
+
237
+ Notes
238
+ -----
239
+ The sum of the two sequences must be equal: sum(aseq)=sum(bseq)
240
+ If no graph type is specified use MultiGraph with parallel edges.
241
+ If you want a graph with no parallel edges use create_using=Graph()
242
+ but then the resulting degree sequences might not be exact.
243
+
244
+ The nodes are assigned the attribute 'bipartite' with the value 0 or 1
245
+ to indicate which bipartite set the node belongs to.
246
+
247
+ This function is not imported in the main namespace.
248
+ To use it use nx.bipartite.reverse_havel_hakimi_graph
249
+ """
250
+ G = nx.empty_graph(0, create_using, default=nx.MultiGraph)
251
+ if G.is_directed():
252
+ raise nx.NetworkXError("Directed Graph not supported")
253
+
254
+ # length of the each sequence
255
+ lena = len(aseq)
256
+ lenb = len(bseq)
257
+ suma = sum(aseq)
258
+ sumb = sum(bseq)
259
+
260
+ if not suma == sumb:
261
+ raise nx.NetworkXError(
262
+ f"invalid degree sequences, sum(aseq)!=sum(bseq),{suma},{sumb}"
263
+ )
264
+
265
+ G = _add_nodes_with_bipartite_label(G, lena, lenb)
266
+
267
+ if len(aseq) == 0 or max(aseq) == 0:
268
+ return G # done if no edges
269
+
270
+ # build list of degree-repeated vertex numbers
271
+ astubs = [[aseq[v], v] for v in range(lena)]
272
+ bstubs = [[bseq[v - lena], v] for v in range(lena, lena + lenb)]
273
+ astubs.sort()
274
+ bstubs.sort()
275
+ while astubs:
276
+ (degree, u) = astubs.pop() # take of largest degree node in the a set
277
+ if degree == 0:
278
+ break # done, all are zero
279
+ # connect the source to the smallest degree nodes in the b set
280
+ for target in bstubs[0:degree]:
281
+ v = target[1]
282
+ G.add_edge(u, v)
283
+ target[0] -= 1 # note this updates bstubs too.
284
+ if target[0] == 0:
285
+ bstubs.remove(target)
286
+
287
+ G.name = "bipartite_reverse_havel_hakimi_graph"
288
+ return G
289
+
290
+
291
+ @nx._dispatchable(graphs=None, returns_graph=True)
292
+ def alternating_havel_hakimi_graph(aseq, bseq, create_using=None):
293
+ """Returns a bipartite graph from two given degree sequences using
294
+ an alternating Havel-Hakimi style construction.
295
+
296
+ The graph is composed of two partitions. Set A has nodes 0 to
297
+ (len(aseq) - 1) and set B has nodes len(aseq) to (len(bseq) - 1).
298
+ Nodes from the set A are connected to nodes in the set B by
299
+ connecting the highest degree nodes in set A to alternatively the
300
+ highest and the lowest degree nodes in set B until all stubs are
301
+ connected.
302
+
303
+ Parameters
304
+ ----------
305
+ aseq : list
306
+ Degree sequence for node set A.
307
+ bseq : list
308
+ Degree sequence for node set B.
309
+ create_using : NetworkX graph instance, optional
310
+ Return graph of this type.
311
+
312
+ Notes
313
+ -----
314
+ The sum of the two sequences must be equal: sum(aseq)=sum(bseq)
315
+ If no graph type is specified use MultiGraph with parallel edges.
316
+ If you want a graph with no parallel edges use create_using=Graph()
317
+ but then the resulting degree sequences might not be exact.
318
+
319
+ The nodes are assigned the attribute 'bipartite' with the value 0 or 1
320
+ to indicate which bipartite set the node belongs to.
321
+
322
+ This function is not imported in the main namespace.
323
+ To use it use nx.bipartite.alternating_havel_hakimi_graph
324
+ """
325
+ G = nx.empty_graph(0, create_using, default=nx.MultiGraph)
326
+ if G.is_directed():
327
+ raise nx.NetworkXError("Directed Graph not supported")
328
+
329
+ # length of the each sequence
330
+ naseq = len(aseq)
331
+ nbseq = len(bseq)
332
+ suma = sum(aseq)
333
+ sumb = sum(bseq)
334
+
335
+ if not suma == sumb:
336
+ raise nx.NetworkXError(
337
+ f"invalid degree sequences, sum(aseq)!=sum(bseq),{suma},{sumb}"
338
+ )
339
+
340
+ G = _add_nodes_with_bipartite_label(G, naseq, nbseq)
341
+
342
+ if len(aseq) == 0 or max(aseq) == 0:
343
+ return G # done if no edges
344
+ # build list of degree-repeated vertex numbers
345
+ astubs = [[aseq[v], v] for v in range(naseq)]
346
+ bstubs = [[bseq[v - naseq], v] for v in range(naseq, naseq + nbseq)]
347
+ while astubs:
348
+ astubs.sort()
349
+ (degree, u) = astubs.pop() # take of largest degree node in the a set
350
+ if degree == 0:
351
+ break # done, all are zero
352
+ bstubs.sort()
353
+ small = bstubs[0 : degree // 2] # add these low degree targets
354
+ large = bstubs[(-degree + degree // 2) :] # now high degree targets
355
+ stubs = [x for z in zip(large, small) for x in z] # combine, sorry
356
+ if len(stubs) < len(small) + len(large): # check for zip truncation
357
+ stubs.append(large.pop())
358
+ for target in stubs:
359
+ v = target[1]
360
+ G.add_edge(u, v)
361
+ target[0] -= 1 # note this updates bstubs too.
362
+ if target[0] == 0:
363
+ bstubs.remove(target)
364
+
365
+ G.name = "bipartite_alternating_havel_hakimi_graph"
366
+ return G
367
+
368
+
369
+ @py_random_state(3)
370
+ @nx._dispatchable(graphs=None, returns_graph=True)
371
+ def preferential_attachment_graph(aseq, p, create_using=None, seed=None):
372
+ """Create a bipartite graph with a preferential attachment model from
373
+ a given single degree sequence.
374
+
375
+ The graph is composed of two partitions. Set A has nodes 0 to
376
+ (len(aseq) - 1) and set B has nodes starting with node len(aseq).
377
+ The number of nodes in set B is random.
378
+
379
+ Parameters
380
+ ----------
381
+ aseq : list
382
+ Degree sequence for node set A.
383
+ p : float
384
+ Probability that a new bottom node is added.
385
+ create_using : NetworkX graph instance, optional
386
+ Return graph of this type.
387
+ seed : integer, random_state, or None (default)
388
+ Indicator of random number generation state.
389
+ See :ref:`Randomness<randomness>`.
390
+
391
+ References
392
+ ----------
393
+ .. [1] Guillaume, J.L. and Latapy, M.,
394
+ Bipartite graphs as models of complex networks.
395
+ Physica A: Statistical Mechanics and its Applications,
396
+ 2006, 371(2), pp.795-813.
397
+ .. [2] Jean-Loup Guillaume and Matthieu Latapy,
398
+ Bipartite structure of all complex networks,
399
+ Inf. Process. Lett. 90, 2004, pg. 215-221
400
+ https://doi.org/10.1016/j.ipl.2004.03.007
401
+
402
+ Notes
403
+ -----
404
+ The nodes are assigned the attribute 'bipartite' with the value 0 or 1
405
+ to indicate which bipartite set the node belongs to.
406
+
407
+ This function is not imported in the main namespace.
408
+ To use it use nx.bipartite.preferential_attachment_graph
409
+ """
410
+ G = nx.empty_graph(0, create_using, default=nx.MultiGraph)
411
+ if G.is_directed():
412
+ raise nx.NetworkXError("Directed Graph not supported")
413
+
414
+ if p > 1:
415
+ raise nx.NetworkXError(f"probability {p} > 1")
416
+
417
+ naseq = len(aseq)
418
+ G = _add_nodes_with_bipartite_label(G, naseq, 0)
419
+ vv = [[v] * aseq[v] for v in range(naseq)]
420
+ while vv:
421
+ while vv[0]:
422
+ source = vv[0][0]
423
+ vv[0].remove(source)
424
+ if seed.random() < p or len(G) == naseq:
425
+ target = len(G)
426
+ G.add_node(target, bipartite=1)
427
+ G.add_edge(source, target)
428
+ else:
429
+ bb = [[b] * G.degree(b) for b in range(naseq, len(G))]
430
+ # flatten the list of lists into a list.
431
+ bbstubs = reduce(lambda x, y: x + y, bb)
432
+ # choose preferentially a bottom node.
433
+ target = seed.choice(bbstubs)
434
+ G.add_node(target, bipartite=1)
435
+ G.add_edge(source, target)
436
+ vv.remove(vv[0])
437
+ G.name = "bipartite_preferential_attachment_model"
438
+ return G
439
+
440
+
441
+ @py_random_state(3)
442
+ @nx._dispatchable(graphs=None, returns_graph=True)
443
+ def random_graph(n, m, p, seed=None, directed=False):
444
+ """Returns a bipartite random graph.
445
+
446
+ This is a bipartite version of the binomial (Erdős-Rényi) graph.
447
+ The graph is composed of two partitions. Set A has nodes 0 to
448
+ (n - 1) and set B has nodes n to (n + m - 1).
449
+
450
+ Parameters
451
+ ----------
452
+ n : int
453
+ The number of nodes in the first bipartite set.
454
+ m : int
455
+ The number of nodes in the second bipartite set.
456
+ p : float
457
+ Probability for edge creation.
458
+ seed : integer, random_state, or None (default)
459
+ Indicator of random number generation state.
460
+ See :ref:`Randomness<randomness>`.
461
+ directed : bool, optional (default=False)
462
+ If True return a directed graph
463
+
464
+ Notes
465
+ -----
466
+ The bipartite random graph algorithm chooses each of the n*m (undirected)
467
+ or 2*nm (directed) possible edges with probability p.
468
+
469
+ This algorithm is $O(n+m)$ where $m$ is the expected number of edges.
470
+
471
+ The nodes are assigned the attribute 'bipartite' with the value 0 or 1
472
+ to indicate which bipartite set the node belongs to.
473
+
474
+ This function is not imported in the main namespace.
475
+ To use it use nx.bipartite.random_graph
476
+
477
+ See Also
478
+ --------
479
+ gnp_random_graph, configuration_model
480
+
481
+ References
482
+ ----------
483
+ .. [1] Vladimir Batagelj and Ulrik Brandes,
484
+ "Efficient generation of large random networks",
485
+ Phys. Rev. E, 71, 036113, 2005.
486
+ """
487
+ G = nx.Graph()
488
+ G = _add_nodes_with_bipartite_label(G, n, m)
489
+ if directed:
490
+ G = nx.DiGraph(G)
491
+ G.name = f"fast_gnp_random_graph({n},{m},{p})"
492
+
493
+ if p <= 0:
494
+ return G
495
+ if p >= 1:
496
+ return nx.complete_bipartite_graph(n, m)
497
+
498
+ lp = math.log(1.0 - p)
499
+
500
+ v = 0
501
+ w = -1
502
+ while v < n:
503
+ lr = math.log(1.0 - seed.random())
504
+ w = w + 1 + int(lr / lp)
505
+ while w >= m and v < n:
506
+ w = w - m
507
+ v = v + 1
508
+ if v < n:
509
+ G.add_edge(v, n + w)
510
+
511
+ if directed:
512
+ # use the same algorithm to
513
+ # add edges from the "m" to "n" set
514
+ v = 0
515
+ w = -1
516
+ while v < n:
517
+ lr = math.log(1.0 - seed.random())
518
+ w = w + 1 + int(lr / lp)
519
+ while w >= m and v < n:
520
+ w = w - m
521
+ v = v + 1
522
+ if v < n:
523
+ G.add_edge(n + w, v)
524
+
525
+ return G
526
+
527
+
528
+ @py_random_state(3)
529
+ @nx._dispatchable(graphs=None, returns_graph=True)
530
+ def gnmk_random_graph(n, m, k, seed=None, directed=False):
531
+ """Returns a random bipartite graph G_{n,m,k}.
532
+
533
+ Produces a bipartite graph chosen randomly out of the set of all graphs
534
+ with n top nodes, m bottom nodes, and k edges.
535
+ The graph is composed of two sets of nodes.
536
+ Set A has nodes 0 to (n - 1) and set B has nodes n to (n + m - 1).
537
+
538
+ Parameters
539
+ ----------
540
+ n : int
541
+ The number of nodes in the first bipartite set.
542
+ m : int
543
+ The number of nodes in the second bipartite set.
544
+ k : int
545
+ The number of edges
546
+ seed : integer, random_state, or None (default)
547
+ Indicator of random number generation state.
548
+ See :ref:`Randomness<randomness>`.
549
+ directed : bool, optional (default=False)
550
+ If True return a directed graph
551
+
552
+ Examples
553
+ --------
554
+ from nx.algorithms import bipartite
555
+ G = bipartite.gnmk_random_graph(10,20,50)
556
+
557
+ See Also
558
+ --------
559
+ gnm_random_graph
560
+
561
+ Notes
562
+ -----
563
+ If k > m * n then a complete bipartite graph is returned.
564
+
565
+ This graph is a bipartite version of the `G_{nm}` random graph model.
566
+
567
+ The nodes are assigned the attribute 'bipartite' with the value 0 or 1
568
+ to indicate which bipartite set the node belongs to.
569
+
570
+ This function is not imported in the main namespace.
571
+ To use it use nx.bipartite.gnmk_random_graph
572
+ """
573
+ G = nx.Graph()
574
+ G = _add_nodes_with_bipartite_label(G, n, m)
575
+ if directed:
576
+ G = nx.DiGraph(G)
577
+ G.name = f"bipartite_gnm_random_graph({n},{m},{k})"
578
+ if n == 1 or m == 1:
579
+ return G
580
+ max_edges = n * m # max_edges for bipartite networks
581
+ if k >= max_edges: # Maybe we should raise an exception here
582
+ return nx.complete_bipartite_graph(n, m, create_using=G)
583
+
584
+ top = [n for n, d in G.nodes(data=True) if d["bipartite"] == 0]
585
+ bottom = list(set(G) - set(top))
586
+ edge_count = 0
587
+ while edge_count < k:
588
+ # generate random edge,u,v
589
+ u = seed.choice(top)
590
+ v = seed.choice(bottom)
591
+ if v in G[u]:
592
+ continue
593
+ else:
594
+ G.add_edge(u, v)
595
+ edge_count += 1
596
+ return G
597
+
598
+
599
+ def _add_nodes_with_bipartite_label(G, lena, lenb):
600
+ G.add_nodes_from(range(lena + lenb))
601
+ b = dict(zip(range(lena), [0] * lena))
602
+ b.update(dict(zip(range(lena, lena + lenb), [1] * lenb)))
603
+ nx.set_node_attributes(G, b, "bipartite")
604
+ return G
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/matching.py ADDED
@@ -0,0 +1,590 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This module uses material from the Wikipedia article Hopcroft--Karp algorithm
2
+ # <https://en.wikipedia.org/wiki/Hopcroft%E2%80%93Karp_algorithm>, accessed on
3
+ # January 3, 2015, which is released under the Creative Commons
4
+ # Attribution-Share-Alike License 3.0
5
+ # <http://creativecommons.org/licenses/by-sa/3.0/>. That article includes
6
+ # pseudocode, which has been translated into the corresponding Python code.
7
+ #
8
+ # Portions of this module use code from David Eppstein's Python Algorithms and
9
+ # Data Structures (PADS) library, which is dedicated to the public domain (for
10
+ # proof, see <http://www.ics.uci.edu/~eppstein/PADS/ABOUT-PADS.txt>).
11
+ """Provides functions for computing maximum cardinality matchings and minimum
12
+ weight full matchings in a bipartite graph.
13
+
14
+ If you don't care about the particular implementation of the maximum matching
15
+ algorithm, simply use the :func:`maximum_matching`. If you do care, you can
16
+ import one of the named maximum matching algorithms directly.
17
+
18
+ For example, to find a maximum matching in the complete bipartite graph with
19
+ two vertices on the left and three vertices on the right:
20
+
21
+ >>> G = nx.complete_bipartite_graph(2, 3)
22
+ >>> left, right = nx.bipartite.sets(G)
23
+ >>> list(left)
24
+ [0, 1]
25
+ >>> list(right)
26
+ [2, 3, 4]
27
+ >>> nx.bipartite.maximum_matching(G)
28
+ {0: 2, 1: 3, 2: 0, 3: 1}
29
+
30
+ The dictionary returned by :func:`maximum_matching` includes a mapping for
31
+ vertices in both the left and right vertex sets.
32
+
33
+ Similarly, :func:`minimum_weight_full_matching` produces, for a complete
34
+ weighted bipartite graph, a matching whose cardinality is the cardinality of
35
+ the smaller of the two partitions, and for which the sum of the weights of the
36
+ edges included in the matching is minimal.
37
+
38
+ """
39
+
40
+ import collections
41
+ import itertools
42
+
43
+ import networkx as nx
44
+ from networkx.algorithms.bipartite import sets as bipartite_sets
45
+ from networkx.algorithms.bipartite.matrix import biadjacency_matrix
46
+
47
+ __all__ = [
48
+ "maximum_matching",
49
+ "hopcroft_karp_matching",
50
+ "eppstein_matching",
51
+ "to_vertex_cover",
52
+ "minimum_weight_full_matching",
53
+ ]
54
+
55
+ INFINITY = float("inf")
56
+
57
+
58
+ @nx._dispatchable
59
+ def hopcroft_karp_matching(G, top_nodes=None):
60
+ """Returns the maximum cardinality matching of the bipartite graph `G`.
61
+
62
+ A matching is a set of edges that do not share any nodes. A maximum
63
+ cardinality matching is a matching with the most edges possible. It
64
+ is not always unique. Finding a matching in a bipartite graph can be
65
+ treated as a networkx flow problem.
66
+
67
+ The functions ``hopcroft_karp_matching`` and ``maximum_matching``
68
+ are aliases of the same function.
69
+
70
+ Parameters
71
+ ----------
72
+ G : NetworkX graph
73
+
74
+ Undirected bipartite graph
75
+
76
+ top_nodes : container of nodes
77
+
78
+ Container with all nodes in one bipartite node set. If not supplied
79
+ it will be computed. But if more than one solution exists an exception
80
+ will be raised.
81
+
82
+ Returns
83
+ -------
84
+ matches : dictionary
85
+
86
+ The matching is returned as a dictionary, `matches`, such that
87
+ ``matches[v] == w`` if node `v` is matched to node `w`. Unmatched
88
+ nodes do not occur as a key in `matches`.
89
+
90
+ Raises
91
+ ------
92
+ AmbiguousSolution
93
+ Raised if the input bipartite graph is disconnected and no container
94
+ with all nodes in one bipartite set is provided. When determining
95
+ the nodes in each bipartite set more than one valid solution is
96
+ possible if the input graph is disconnected.
97
+
98
+ Notes
99
+ -----
100
+ This function is implemented with the `Hopcroft--Karp matching algorithm
101
+ <https://en.wikipedia.org/wiki/Hopcroft%E2%80%93Karp_algorithm>`_ for
102
+ bipartite graphs.
103
+
104
+ See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
105
+ for further details on how bipartite graphs are handled in NetworkX.
106
+
107
+ See Also
108
+ --------
109
+ maximum_matching
110
+ hopcroft_karp_matching
111
+ eppstein_matching
112
+
113
+ References
114
+ ----------
115
+ .. [1] John E. Hopcroft and Richard M. Karp. "An n^{5 / 2} Algorithm for
116
+ Maximum Matchings in Bipartite Graphs" In: **SIAM Journal of Computing**
117
+ 2.4 (1973), pp. 225--231. <https://doi.org/10.1137/0202019>.
118
+
119
+ """
120
+
121
+ # First we define some auxiliary search functions.
122
+ #
123
+ # If you are a human reading these auxiliary search functions, the "global"
124
+ # variables `leftmatches`, `rightmatches`, `distances`, etc. are defined
125
+ # below the functions, so that they are initialized close to the initial
126
+ # invocation of the search functions.
127
+ def breadth_first_search():
128
+ for v in left:
129
+ if leftmatches[v] is None:
130
+ distances[v] = 0
131
+ queue.append(v)
132
+ else:
133
+ distances[v] = INFINITY
134
+ distances[None] = INFINITY
135
+ while queue:
136
+ v = queue.popleft()
137
+ if distances[v] < distances[None]:
138
+ for u in G[v]:
139
+ if distances[rightmatches[u]] is INFINITY:
140
+ distances[rightmatches[u]] = distances[v] + 1
141
+ queue.append(rightmatches[u])
142
+ return distances[None] is not INFINITY
143
+
144
+ def depth_first_search(v):
145
+ if v is not None:
146
+ for u in G[v]:
147
+ if distances[rightmatches[u]] == distances[v] + 1:
148
+ if depth_first_search(rightmatches[u]):
149
+ rightmatches[u] = v
150
+ leftmatches[v] = u
151
+ return True
152
+ distances[v] = INFINITY
153
+ return False
154
+ return True
155
+
156
+ # Initialize the "global" variables that maintain state during the search.
157
+ left, right = bipartite_sets(G, top_nodes)
158
+ leftmatches = {v: None for v in left}
159
+ rightmatches = {v: None for v in right}
160
+ distances = {}
161
+ queue = collections.deque()
162
+
163
+ # Implementation note: this counter is incremented as pairs are matched but
164
+ # it is currently not used elsewhere in the computation.
165
+ num_matched_pairs = 0
166
+ while breadth_first_search():
167
+ for v in left:
168
+ if leftmatches[v] is None:
169
+ if depth_first_search(v):
170
+ num_matched_pairs += 1
171
+
172
+ # Strip the entries matched to `None`.
173
+ leftmatches = {k: v for k, v in leftmatches.items() if v is not None}
174
+ rightmatches = {k: v for k, v in rightmatches.items() if v is not None}
175
+
176
+ # At this point, the left matches and the right matches are inverses of one
177
+ # another. In other words,
178
+ #
179
+ # leftmatches == {v, k for k, v in rightmatches.items()}
180
+ #
181
+ # Finally, we combine both the left matches and right matches.
182
+ return dict(itertools.chain(leftmatches.items(), rightmatches.items()))
183
+
184
+
185
+ @nx._dispatchable
186
+ def eppstein_matching(G, top_nodes=None):
187
+ """Returns the maximum cardinality matching of the bipartite graph `G`.
188
+
189
+ Parameters
190
+ ----------
191
+ G : NetworkX graph
192
+
193
+ Undirected bipartite graph
194
+
195
+ top_nodes : container
196
+
197
+ Container with all nodes in one bipartite node set. If not supplied
198
+ it will be computed. But if more than one solution exists an exception
199
+ will be raised.
200
+
201
+ Returns
202
+ -------
203
+ matches : dictionary
204
+
205
+ The matching is returned as a dictionary, `matching`, such that
206
+ ``matching[v] == w`` if node `v` is matched to node `w`. Unmatched
207
+ nodes do not occur as a key in `matching`.
208
+
209
+ Raises
210
+ ------
211
+ AmbiguousSolution
212
+ Raised if the input bipartite graph is disconnected and no container
213
+ with all nodes in one bipartite set is provided. When determining
214
+ the nodes in each bipartite set more than one valid solution is
215
+ possible if the input graph is disconnected.
216
+
217
+ Notes
218
+ -----
219
+ This function is implemented with David Eppstein's version of the algorithm
220
+ Hopcroft--Karp algorithm (see :func:`hopcroft_karp_matching`), which
221
+ originally appeared in the `Python Algorithms and Data Structures library
222
+ (PADS) <http://www.ics.uci.edu/~eppstein/PADS/ABOUT-PADS.txt>`_.
223
+
224
+ See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
225
+ for further details on how bipartite graphs are handled in NetworkX.
226
+
227
+ See Also
228
+ --------
229
+
230
+ hopcroft_karp_matching
231
+
232
+ """
233
+ # Due to its original implementation, a directed graph is needed
234
+ # so that the two sets of bipartite nodes can be distinguished
235
+ left, right = bipartite_sets(G, top_nodes)
236
+ G = nx.DiGraph(G.edges(left))
237
+ # initialize greedy matching (redundant, but faster than full search)
238
+ matching = {}
239
+ for u in G:
240
+ for v in G[u]:
241
+ if v not in matching:
242
+ matching[v] = u
243
+ break
244
+ while True:
245
+ # structure residual graph into layers
246
+ # pred[u] gives the neighbor in the previous layer for u in U
247
+ # preds[v] gives a list of neighbors in the previous layer for v in V
248
+ # unmatched gives a list of unmatched vertices in final layer of V,
249
+ # and is also used as a flag value for pred[u] when u is in the first
250
+ # layer
251
+ preds = {}
252
+ unmatched = []
253
+ pred = {u: unmatched for u in G}
254
+ for v in matching:
255
+ del pred[matching[v]]
256
+ layer = list(pred)
257
+
258
+ # repeatedly extend layering structure by another pair of layers
259
+ while layer and not unmatched:
260
+ newLayer = {}
261
+ for u in layer:
262
+ for v in G[u]:
263
+ if v not in preds:
264
+ newLayer.setdefault(v, []).append(u)
265
+ layer = []
266
+ for v in newLayer:
267
+ preds[v] = newLayer[v]
268
+ if v in matching:
269
+ layer.append(matching[v])
270
+ pred[matching[v]] = v
271
+ else:
272
+ unmatched.append(v)
273
+
274
+ # did we finish layering without finding any alternating paths?
275
+ if not unmatched:
276
+ # TODO - The lines between --- were unused and were thus commented
277
+ # out. This whole commented chunk should be reviewed to determine
278
+ # whether it should be built upon or completely removed.
279
+ # ---
280
+ # unlayered = {}
281
+ # for u in G:
282
+ # # TODO Why is extra inner loop necessary?
283
+ # for v in G[u]:
284
+ # if v not in preds:
285
+ # unlayered[v] = None
286
+ # ---
287
+ # TODO Originally, this function returned a three-tuple:
288
+ #
289
+ # return (matching, list(pred), list(unlayered))
290
+ #
291
+ # For some reason, the documentation for this function
292
+ # indicated that the second and third elements of the returned
293
+ # three-tuple would be the vertices in the left and right vertex
294
+ # sets, respectively, that are also in the maximum independent set.
295
+ # However, what I think the author meant was that the second
296
+ # element is the list of vertices that were unmatched and the third
297
+ # element was the list of vertices that were matched. Since that
298
+ # seems to be the case, they don't really need to be returned,
299
+ # since that information can be inferred from the matching
300
+ # dictionary.
301
+
302
+ # All the matched nodes must be a key in the dictionary
303
+ for key in matching.copy():
304
+ matching[matching[key]] = key
305
+ return matching
306
+
307
+ # recursively search backward through layers to find alternating paths
308
+ # recursion returns true if found path, false otherwise
309
+ def recurse(v):
310
+ if v in preds:
311
+ L = preds.pop(v)
312
+ for u in L:
313
+ if u in pred:
314
+ pu = pred.pop(u)
315
+ if pu is unmatched or recurse(pu):
316
+ matching[v] = u
317
+ return True
318
+ return False
319
+
320
+ for v in unmatched:
321
+ recurse(v)
322
+
323
+
324
+ def _is_connected_by_alternating_path(G, v, matched_edges, unmatched_edges, targets):
325
+ """Returns True if and only if the vertex `v` is connected to one of
326
+ the target vertices by an alternating path in `G`.
327
+
328
+ An *alternating path* is a path in which every other edge is in the
329
+ specified maximum matching (and the remaining edges in the path are not in
330
+ the matching). An alternating path may have matched edges in the even
331
+ positions or in the odd positions, as long as the edges alternate between
332
+ 'matched' and 'unmatched'.
333
+
334
+ `G` is an undirected bipartite NetworkX graph.
335
+
336
+ `v` is a vertex in `G`.
337
+
338
+ `matched_edges` is a set of edges present in a maximum matching in `G`.
339
+
340
+ `unmatched_edges` is a set of edges not present in a maximum
341
+ matching in `G`.
342
+
343
+ `targets` is a set of vertices.
344
+
345
+ """
346
+
347
+ def _alternating_dfs(u, along_matched=True):
348
+ """Returns True if and only if `u` is connected to one of the
349
+ targets by an alternating path.
350
+
351
+ `u` is a vertex in the graph `G`.
352
+
353
+ If `along_matched` is True, this step of the depth-first search
354
+ will continue only through edges in the given matching. Otherwise, it
355
+ will continue only through edges *not* in the given matching.
356
+
357
+ """
358
+ visited = set()
359
+ # Follow matched edges when depth is even,
360
+ # and follow unmatched edges when depth is odd.
361
+ initial_depth = 0 if along_matched else 1
362
+ stack = [(u, iter(G[u]), initial_depth)]
363
+ while stack:
364
+ parent, children, depth = stack[-1]
365
+ valid_edges = matched_edges if depth % 2 else unmatched_edges
366
+ try:
367
+ child = next(children)
368
+ if child not in visited:
369
+ if (parent, child) in valid_edges or (child, parent) in valid_edges:
370
+ if child in targets:
371
+ return True
372
+ visited.add(child)
373
+ stack.append((child, iter(G[child]), depth + 1))
374
+ except StopIteration:
375
+ stack.pop()
376
+ return False
377
+
378
+ # Check for alternating paths starting with edges in the matching, then
379
+ # check for alternating paths starting with edges not in the
380
+ # matching.
381
+ return _alternating_dfs(v, along_matched=True) or _alternating_dfs(
382
+ v, along_matched=False
383
+ )
384
+
385
+
386
+ def _connected_by_alternating_paths(G, matching, targets):
387
+ """Returns the set of vertices that are connected to one of the target
388
+ vertices by an alternating path in `G` or are themselves a target.
389
+
390
+ An *alternating path* is a path in which every other edge is in the
391
+ specified maximum matching (and the remaining edges in the path are not in
392
+ the matching). An alternating path may have matched edges in the even
393
+ positions or in the odd positions, as long as the edges alternate between
394
+ 'matched' and 'unmatched'.
395
+
396
+ `G` is an undirected bipartite NetworkX graph.
397
+
398
+ `matching` is a dictionary representing a maximum matching in `G`, as
399
+ returned by, for example, :func:`maximum_matching`.
400
+
401
+ `targets` is a set of vertices.
402
+
403
+ """
404
+ # Get the set of matched edges and the set of unmatched edges. Only include
405
+ # one version of each undirected edge (for example, include edge (1, 2) but
406
+ # not edge (2, 1)). Using frozensets as an intermediary step we do not
407
+ # require nodes to be orderable.
408
+ edge_sets = {frozenset((u, v)) for u, v in matching.items()}
409
+ matched_edges = {tuple(edge) for edge in edge_sets}
410
+ unmatched_edges = {
411
+ (u, v) for (u, v) in G.edges() if frozenset((u, v)) not in edge_sets
412
+ }
413
+
414
+ return {
415
+ v
416
+ for v in G
417
+ if v in targets
418
+ or _is_connected_by_alternating_path(
419
+ G, v, matched_edges, unmatched_edges, targets
420
+ )
421
+ }
422
+
423
+
424
+ @nx._dispatchable
425
+ def to_vertex_cover(G, matching, top_nodes=None):
426
+ """Returns the minimum vertex cover corresponding to the given maximum
427
+ matching of the bipartite graph `G`.
428
+
429
+ Parameters
430
+ ----------
431
+ G : NetworkX graph
432
+
433
+ Undirected bipartite graph
434
+
435
+ matching : dictionary
436
+
437
+ A dictionary whose keys are vertices in `G` and whose values are the
438
+ distinct neighbors comprising the maximum matching for `G`, as returned
439
+ by, for example, :func:`maximum_matching`. The dictionary *must*
440
+ represent the maximum matching.
441
+
442
+ top_nodes : container
443
+
444
+ Container with all nodes in one bipartite node set. If not supplied
445
+ it will be computed. But if more than one solution exists an exception
446
+ will be raised.
447
+
448
+ Returns
449
+ -------
450
+ vertex_cover : :class:`set`
451
+
452
+ The minimum vertex cover in `G`.
453
+
454
+ Raises
455
+ ------
456
+ AmbiguousSolution
457
+ Raised if the input bipartite graph is disconnected and no container
458
+ with all nodes in one bipartite set is provided. When determining
459
+ the nodes in each bipartite set more than one valid solution is
460
+ possible if the input graph is disconnected.
461
+
462
+ Notes
463
+ -----
464
+ This function is implemented using the procedure guaranteed by `Konig's
465
+ theorem
466
+ <https://en.wikipedia.org/wiki/K%C3%B6nig%27s_theorem_%28graph_theory%29>`_,
467
+ which proves an equivalence between a maximum matching and a minimum vertex
468
+ cover in bipartite graphs.
469
+
470
+ Since a minimum vertex cover is the complement of a maximum independent set
471
+ for any graph, one can compute the maximum independent set of a bipartite
472
+ graph this way:
473
+
474
+ >>> G = nx.complete_bipartite_graph(2, 3)
475
+ >>> matching = nx.bipartite.maximum_matching(G)
476
+ >>> vertex_cover = nx.bipartite.to_vertex_cover(G, matching)
477
+ >>> independent_set = set(G) - vertex_cover
478
+ >>> print(list(independent_set))
479
+ [2, 3, 4]
480
+
481
+ See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
482
+ for further details on how bipartite graphs are handled in NetworkX.
483
+
484
+ """
485
+ # This is a Python implementation of the algorithm described at
486
+ # <https://en.wikipedia.org/wiki/K%C3%B6nig%27s_theorem_%28graph_theory%29#Proof>.
487
+ L, R = bipartite_sets(G, top_nodes)
488
+ # Let U be the set of unmatched vertices in the left vertex set.
489
+ unmatched_vertices = set(G) - set(matching)
490
+ U = unmatched_vertices & L
491
+ # Let Z be the set of vertices that are either in U or are connected to U
492
+ # by alternating paths.
493
+ Z = _connected_by_alternating_paths(G, matching, U)
494
+ # At this point, every edge either has a right endpoint in Z or a left
495
+ # endpoint not in Z. This gives us the vertex cover.
496
+ return (L - Z) | (R & Z)
497
+
498
+
499
+ #: Returns the maximum cardinality matching in the given bipartite graph.
500
+ #:
501
+ #: This function is simply an alias for :func:`hopcroft_karp_matching`.
502
+ maximum_matching = hopcroft_karp_matching
503
+
504
+
505
+ @nx._dispatchable(edge_attrs="weight")
506
+ def minimum_weight_full_matching(G, top_nodes=None, weight="weight"):
507
+ r"""Returns a minimum weight full matching of the bipartite graph `G`.
508
+
509
+ Let :math:`G = ((U, V), E)` be a weighted bipartite graph with real weights
510
+ :math:`w : E \to \mathbb{R}`. This function then produces a matching
511
+ :math:`M \subseteq E` with cardinality
512
+
513
+ .. math::
514
+ \lvert M \rvert = \min(\lvert U \rvert, \lvert V \rvert),
515
+
516
+ which minimizes the sum of the weights of the edges included in the
517
+ matching, :math:`\sum_{e \in M} w(e)`, or raises an error if no such
518
+ matching exists.
519
+
520
+ When :math:`\lvert U \rvert = \lvert V \rvert`, this is commonly
521
+ referred to as a perfect matching; here, since we allow
522
+ :math:`\lvert U \rvert` and :math:`\lvert V \rvert` to differ, we
523
+ follow Karp [1]_ and refer to the matching as *full*.
524
+
525
+ Parameters
526
+ ----------
527
+ G : NetworkX graph
528
+
529
+ Undirected bipartite graph
530
+
531
+ top_nodes : container
532
+
533
+ Container with all nodes in one bipartite node set. If not supplied
534
+ it will be computed.
535
+
536
+ weight : string, optional (default='weight')
537
+
538
+ The edge data key used to provide each value in the matrix.
539
+ If None, then each edge has weight 1.
540
+
541
+ Returns
542
+ -------
543
+ matches : dictionary
544
+
545
+ The matching is returned as a dictionary, `matches`, such that
546
+ ``matches[v] == w`` if node `v` is matched to node `w`. Unmatched
547
+ nodes do not occur as a key in `matches`.
548
+
549
+ Raises
550
+ ------
551
+ ValueError
552
+ Raised if no full matching exists.
553
+
554
+ ImportError
555
+ Raised if SciPy is not available.
556
+
557
+ Notes
558
+ -----
559
+ The problem of determining a minimum weight full matching is also known as
560
+ the rectangular linear assignment problem. This implementation defers the
561
+ calculation of the assignment to SciPy.
562
+
563
+ References
564
+ ----------
565
+ .. [1] Richard Manning Karp:
566
+ An algorithm to Solve the m x n Assignment Problem in Expected Time
567
+ O(mn log n).
568
+ Networks, 10(2):143–152, 1980.
569
+
570
+ """
571
+ import numpy as np
572
+ import scipy as sp
573
+
574
+ left, right = nx.bipartite.sets(G, top_nodes)
575
+ U = list(left)
576
+ V = list(right)
577
+ # We explicitly create the biadjacency matrix having infinities
578
+ # where edges are missing (as opposed to zeros, which is what one would
579
+ # get by using toarray on the sparse matrix).
580
+ weights_sparse = biadjacency_matrix(
581
+ G, row_order=U, column_order=V, weight=weight, format="coo"
582
+ )
583
+ weights = np.full(weights_sparse.shape, np.inf)
584
+ weights[weights_sparse.row, weights_sparse.col] = weights_sparse.data
585
+ left_matches = sp.optimize.linear_sum_assignment(weights)
586
+ d = {U[u]: V[v] for u, v in zip(*left_matches)}
587
+ # d will contain the matching from edges in left to right; we need to
588
+ # add the ones from right to left as well.
589
+ d.update({v: u for u, v in d.items()})
590
+ return d
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/matrix.py ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ ====================
3
+ Biadjacency matrices
4
+ ====================
5
+ """
6
+
7
+ import itertools
8
+
9
+ import networkx as nx
10
+ from networkx.convert_matrix import _generate_weighted_edges
11
+
12
+ __all__ = ["biadjacency_matrix", "from_biadjacency_matrix"]
13
+
14
+
15
+ @nx._dispatchable(edge_attrs="weight")
16
+ def biadjacency_matrix(
17
+ G, row_order, column_order=None, dtype=None, weight="weight", format="csr"
18
+ ):
19
+ r"""Returns the biadjacency matrix of the bipartite graph G.
20
+
21
+ Let `G = (U, V, E)` be a bipartite graph with node sets
22
+ `U = u_{1},...,u_{r}` and `V = v_{1},...,v_{s}`. The biadjacency
23
+ matrix [1]_ is the `r` x `s` matrix `B` in which `b_{i,j} = 1`
24
+ if, and only if, `(u_i, v_j) \in E`. If the parameter `weight` is
25
+ not `None` and matches the name of an edge attribute, its value is
26
+ used instead of 1.
27
+
28
+ Parameters
29
+ ----------
30
+ G : graph
31
+ A NetworkX graph
32
+
33
+ row_order : list of nodes
34
+ The rows of the matrix are ordered according to the list of nodes.
35
+
36
+ column_order : list, optional
37
+ The columns of the matrix are ordered according to the list of nodes.
38
+ If column_order is None, then the ordering of columns is arbitrary.
39
+
40
+ dtype : NumPy data-type, optional
41
+ A valid NumPy dtype used to initialize the array. If None, then the
42
+ NumPy default is used.
43
+
44
+ weight : string or None, optional (default='weight')
45
+ The edge data key used to provide each value in the matrix.
46
+ If None, then each edge has weight 1.
47
+
48
+ format : str in {'bsr', 'csr', 'csc', 'coo', 'lil', 'dia', 'dok'}
49
+ The type of the matrix to be returned (default 'csr'). For
50
+ some algorithms different implementations of sparse matrices
51
+ can perform better. See [2]_ for details.
52
+
53
+ Returns
54
+ -------
55
+ M : SciPy sparse array
56
+ Biadjacency matrix representation of the bipartite graph G.
57
+
58
+ Notes
59
+ -----
60
+ No attempt is made to check that the input graph is bipartite.
61
+
62
+ For directed bipartite graphs only successors are considered as neighbors.
63
+ To obtain an adjacency matrix with ones (or weight values) for both
64
+ predecessors and successors you have to generate two biadjacency matrices
65
+ where the rows of one of them are the columns of the other, and then add
66
+ one to the transpose of the other.
67
+
68
+ See Also
69
+ --------
70
+ adjacency_matrix
71
+ from_biadjacency_matrix
72
+
73
+ References
74
+ ----------
75
+ .. [1] https://en.wikipedia.org/wiki/Adjacency_matrix#Adjacency_matrix_of_a_bipartite_graph
76
+ .. [2] Scipy Dev. References, "Sparse Matrices",
77
+ https://docs.scipy.org/doc/scipy/reference/sparse.html
78
+ """
79
+ import scipy as sp
80
+
81
+ nlen = len(row_order)
82
+ if nlen == 0:
83
+ raise nx.NetworkXError("row_order is empty list")
84
+ if len(row_order) != len(set(row_order)):
85
+ msg = "Ambiguous ordering: `row_order` contained duplicates."
86
+ raise nx.NetworkXError(msg)
87
+ if column_order is None:
88
+ column_order = list(set(G) - set(row_order))
89
+ mlen = len(column_order)
90
+ if len(column_order) != len(set(column_order)):
91
+ msg = "Ambiguous ordering: `column_order` contained duplicates."
92
+ raise nx.NetworkXError(msg)
93
+
94
+ row_index = dict(zip(row_order, itertools.count()))
95
+ col_index = dict(zip(column_order, itertools.count()))
96
+
97
+ if G.number_of_edges() == 0:
98
+ row, col, data = [], [], []
99
+ else:
100
+ row, col, data = zip(
101
+ *(
102
+ (row_index[u], col_index[v], d.get(weight, 1))
103
+ for u, v, d in G.edges(row_order, data=True)
104
+ if u in row_index and v in col_index
105
+ )
106
+ )
107
+ A = sp.sparse.coo_array((data, (row, col)), shape=(nlen, mlen), dtype=dtype)
108
+ try:
109
+ return A.asformat(format)
110
+ except ValueError as err:
111
+ raise nx.NetworkXError(f"Unknown sparse array format: {format}") from err
112
+
113
+
114
+ @nx._dispatchable(graphs=None, returns_graph=True)
115
+ def from_biadjacency_matrix(A, create_using=None, edge_attribute="weight"):
116
+ r"""Creates a new bipartite graph from a biadjacency matrix given as a
117
+ SciPy sparse array.
118
+
119
+ Parameters
120
+ ----------
121
+ A: scipy sparse array
122
+ A biadjacency matrix representation of a graph
123
+
124
+ create_using: NetworkX graph
125
+ Use specified graph for result. The default is Graph()
126
+
127
+ edge_attribute: string
128
+ Name of edge attribute to store matrix numeric value. The data will
129
+ have the same type as the matrix entry (int, float, (real,imag)).
130
+
131
+ Notes
132
+ -----
133
+ The nodes are labeled with the attribute `bipartite` set to an integer
134
+ 0 or 1 representing membership in part 0 or part 1 of the bipartite graph.
135
+
136
+ If `create_using` is an instance of :class:`networkx.MultiGraph` or
137
+ :class:`networkx.MultiDiGraph` and the entries of `A` are of
138
+ type :class:`int`, then this function returns a multigraph (of the same
139
+ type as `create_using`) with parallel edges. In this case, `edge_attribute`
140
+ will be ignored.
141
+
142
+ See Also
143
+ --------
144
+ biadjacency_matrix
145
+ from_numpy_array
146
+
147
+ References
148
+ ----------
149
+ [1] https://en.wikipedia.org/wiki/Adjacency_matrix#Adjacency_matrix_of_a_bipartite_graph
150
+ """
151
+ G = nx.empty_graph(0, create_using)
152
+ n, m = A.shape
153
+ # Make sure we get even the isolated nodes of the graph.
154
+ G.add_nodes_from(range(n), bipartite=0)
155
+ G.add_nodes_from(range(n, n + m), bipartite=1)
156
+ # Create an iterable over (u, v, w) triples and for each triple, add an
157
+ # edge from u to v with weight w.
158
+ triples = ((u, n + v, d) for (u, v, d) in _generate_weighted_edges(A))
159
+ # If the entries in the adjacency matrix are integers and the graph is a
160
+ # multigraph, then create parallel edges, each with weight 1, for each
161
+ # entry in the adjacency matrix. Otherwise, create one edge for each
162
+ # positive entry in the adjacency matrix and set the weight of that edge to
163
+ # be the entry in the matrix.
164
+ if A.dtype.kind in ("i", "u") and G.is_multigraph():
165
+ chain = itertools.chain.from_iterable
166
+ triples = chain(((u, v, 1) for d in range(w)) for (u, v, w) in triples)
167
+ G.add_weighted_edges_from(triples, weight=edge_attribute)
168
+ return G
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/projection.py ADDED
@@ -0,0 +1,526 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """One-mode (unipartite) projections of bipartite graphs."""
2
+
3
+ import networkx as nx
4
+ from networkx.exception import NetworkXAlgorithmError
5
+ from networkx.utils import not_implemented_for
6
+
7
+ __all__ = [
8
+ "projected_graph",
9
+ "weighted_projected_graph",
10
+ "collaboration_weighted_projected_graph",
11
+ "overlap_weighted_projected_graph",
12
+ "generic_weighted_projected_graph",
13
+ ]
14
+
15
+
16
+ @nx._dispatchable(
17
+ graphs="B", preserve_node_attrs=True, preserve_graph_attrs=True, returns_graph=True
18
+ )
19
+ def projected_graph(B, nodes, multigraph=False):
20
+ r"""Returns the projection of B onto one of its node sets.
21
+
22
+ Returns the graph G that is the projection of the bipartite graph B
23
+ onto the specified nodes. They retain their attributes and are connected
24
+ in G if they have a common neighbor in B.
25
+
26
+ Parameters
27
+ ----------
28
+ B : NetworkX graph
29
+ The input graph should be bipartite.
30
+
31
+ nodes : list or iterable
32
+ Nodes to project onto (the "bottom" nodes).
33
+
34
+ multigraph: bool (default=False)
35
+ If True return a multigraph where the multiple edges represent multiple
36
+ shared neighbors. They edge key in the multigraph is assigned to the
37
+ label of the neighbor.
38
+
39
+ Returns
40
+ -------
41
+ Graph : NetworkX graph or multigraph
42
+ A graph that is the projection onto the given nodes.
43
+
44
+ Examples
45
+ --------
46
+ >>> from networkx.algorithms import bipartite
47
+ >>> B = nx.path_graph(4)
48
+ >>> G = bipartite.projected_graph(B, [1, 3])
49
+ >>> list(G)
50
+ [1, 3]
51
+ >>> list(G.edges())
52
+ [(1, 3)]
53
+
54
+ If nodes `a`, and `b` are connected through both nodes 1 and 2 then
55
+ building a multigraph results in two edges in the projection onto
56
+ [`a`, `b`]:
57
+
58
+ >>> B = nx.Graph()
59
+ >>> B.add_edges_from([("a", 1), ("b", 1), ("a", 2), ("b", 2)])
60
+ >>> G = bipartite.projected_graph(B, ["a", "b"], multigraph=True)
61
+ >>> print([sorted((u, v)) for u, v in G.edges()])
62
+ [['a', 'b'], ['a', 'b']]
63
+
64
+ Notes
65
+ -----
66
+ No attempt is made to verify that the input graph B is bipartite.
67
+ Returns a simple graph that is the projection of the bipartite graph B
68
+ onto the set of nodes given in list nodes. If multigraph=True then
69
+ a multigraph is returned with an edge for every shared neighbor.
70
+
71
+ Directed graphs are allowed as input. The output will also then
72
+ be a directed graph with edges if there is a directed path between
73
+ the nodes.
74
+
75
+ The graph and node properties are (shallow) copied to the projected graph.
76
+
77
+ See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
78
+ for further details on how bipartite graphs are handled in NetworkX.
79
+
80
+ See Also
81
+ --------
82
+ is_bipartite,
83
+ is_bipartite_node_set,
84
+ sets,
85
+ weighted_projected_graph,
86
+ collaboration_weighted_projected_graph,
87
+ overlap_weighted_projected_graph,
88
+ generic_weighted_projected_graph
89
+ """
90
+ if B.is_multigraph():
91
+ raise nx.NetworkXError("not defined for multigraphs")
92
+ if B.is_directed():
93
+ directed = True
94
+ if multigraph:
95
+ G = nx.MultiDiGraph()
96
+ else:
97
+ G = nx.DiGraph()
98
+ else:
99
+ directed = False
100
+ if multigraph:
101
+ G = nx.MultiGraph()
102
+ else:
103
+ G = nx.Graph()
104
+ G.graph.update(B.graph)
105
+ G.add_nodes_from((n, B.nodes[n]) for n in nodes)
106
+ for u in nodes:
107
+ nbrs2 = {v for nbr in B[u] for v in B[nbr] if v != u}
108
+ if multigraph:
109
+ for n in nbrs2:
110
+ if directed:
111
+ links = set(B[u]) & set(B.pred[n])
112
+ else:
113
+ links = set(B[u]) & set(B[n])
114
+ for l in links:
115
+ if not G.has_edge(u, n, l):
116
+ G.add_edge(u, n, key=l)
117
+ else:
118
+ G.add_edges_from((u, n) for n in nbrs2)
119
+ return G
120
+
121
+
122
+ @not_implemented_for("multigraph")
123
+ @nx._dispatchable(graphs="B", returns_graph=True)
124
+ def weighted_projected_graph(B, nodes, ratio=False):
125
+ r"""Returns a weighted projection of B onto one of its node sets.
126
+
127
+ The weighted projected graph is the projection of the bipartite
128
+ network B onto the specified nodes with weights representing the
129
+ number of shared neighbors or the ratio between actual shared
130
+ neighbors and possible shared neighbors if ``ratio is True`` [1]_.
131
+ The nodes retain their attributes and are connected in the resulting
132
+ graph if they have an edge to a common node in the original graph.
133
+
134
+ Parameters
135
+ ----------
136
+ B : NetworkX graph
137
+ The input graph should be bipartite.
138
+
139
+ nodes : list or iterable
140
+ Distinct nodes to project onto (the "bottom" nodes).
141
+
142
+ ratio: Bool (default=False)
143
+ If True, edge weight is the ratio between actual shared neighbors
144
+ and maximum possible shared neighbors (i.e., the size of the other
145
+ node set). If False, edges weight is the number of shared neighbors.
146
+
147
+ Returns
148
+ -------
149
+ Graph : NetworkX graph
150
+ A graph that is the projection onto the given nodes.
151
+
152
+ Examples
153
+ --------
154
+ >>> from networkx.algorithms import bipartite
155
+ >>> B = nx.path_graph(4)
156
+ >>> G = bipartite.weighted_projected_graph(B, [1, 3])
157
+ >>> list(G)
158
+ [1, 3]
159
+ >>> list(G.edges(data=True))
160
+ [(1, 3, {'weight': 1})]
161
+ >>> G = bipartite.weighted_projected_graph(B, [1, 3], ratio=True)
162
+ >>> list(G.edges(data=True))
163
+ [(1, 3, {'weight': 0.5})]
164
+
165
+ Notes
166
+ -----
167
+ No attempt is made to verify that the input graph B is bipartite, or that
168
+ the input nodes are distinct. However, if the length of the input nodes is
169
+ greater than or equal to the nodes in the graph B, an exception is raised.
170
+ If the nodes are not distinct but don't raise this error, the output weights
171
+ will be incorrect.
172
+ The graph and node properties are (shallow) copied to the projected graph.
173
+
174
+ See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
175
+ for further details on how bipartite graphs are handled in NetworkX.
176
+
177
+ See Also
178
+ --------
179
+ is_bipartite,
180
+ is_bipartite_node_set,
181
+ sets,
182
+ collaboration_weighted_projected_graph,
183
+ overlap_weighted_projected_graph,
184
+ generic_weighted_projected_graph
185
+ projected_graph
186
+
187
+ References
188
+ ----------
189
+ .. [1] Borgatti, S.P. and Halgin, D. In press. "Analyzing Affiliation
190
+ Networks". In Carrington, P. and Scott, J. (eds) The Sage Handbook
191
+ of Social Network Analysis. Sage Publications.
192
+ """
193
+ if B.is_directed():
194
+ pred = B.pred
195
+ G = nx.DiGraph()
196
+ else:
197
+ pred = B.adj
198
+ G = nx.Graph()
199
+ G.graph.update(B.graph)
200
+ G.add_nodes_from((n, B.nodes[n]) for n in nodes)
201
+ n_top = len(B) - len(nodes)
202
+
203
+ if n_top < 1:
204
+ raise NetworkXAlgorithmError(
205
+ f"the size of the nodes to project onto ({len(nodes)}) is >= the graph size ({len(B)}).\n"
206
+ "They are either not a valid bipartite partition or contain duplicates"
207
+ )
208
+
209
+ for u in nodes:
210
+ unbrs = set(B[u])
211
+ nbrs2 = {n for nbr in unbrs for n in B[nbr]} - {u}
212
+ for v in nbrs2:
213
+ vnbrs = set(pred[v])
214
+ common = unbrs & vnbrs
215
+ if not ratio:
216
+ weight = len(common)
217
+ else:
218
+ weight = len(common) / n_top
219
+ G.add_edge(u, v, weight=weight)
220
+ return G
221
+
222
+
223
+ @not_implemented_for("multigraph")
224
+ @nx._dispatchable(graphs="B", returns_graph=True)
225
+ def collaboration_weighted_projected_graph(B, nodes):
226
+ r"""Newman's weighted projection of B onto one of its node sets.
227
+
228
+ The collaboration weighted projection is the projection of the
229
+ bipartite network B onto the specified nodes with weights assigned
230
+ using Newman's collaboration model [1]_:
231
+
232
+ .. math::
233
+
234
+ w_{u, v} = \sum_k \frac{\delta_{u}^{k} \delta_{v}^{k}}{d_k - 1}
235
+
236
+ where `u` and `v` are nodes from the bottom bipartite node set,
237
+ and `k` is a node of the top node set.
238
+ The value `d_k` is the degree of node `k` in the bipartite
239
+ network and `\delta_{u}^{k}` is 1 if node `u` is
240
+ linked to node `k` in the original bipartite graph or 0 otherwise.
241
+
242
+ The nodes retain their attributes and are connected in the resulting
243
+ graph if have an edge to a common node in the original bipartite
244
+ graph.
245
+
246
+ Parameters
247
+ ----------
248
+ B : NetworkX graph
249
+ The input graph should be bipartite.
250
+
251
+ nodes : list or iterable
252
+ Nodes to project onto (the "bottom" nodes).
253
+
254
+ Returns
255
+ -------
256
+ Graph : NetworkX graph
257
+ A graph that is the projection onto the given nodes.
258
+
259
+ Examples
260
+ --------
261
+ >>> from networkx.algorithms import bipartite
262
+ >>> B = nx.path_graph(5)
263
+ >>> B.add_edge(1, 5)
264
+ >>> G = bipartite.collaboration_weighted_projected_graph(B, [0, 2, 4, 5])
265
+ >>> list(G)
266
+ [0, 2, 4, 5]
267
+ >>> for edge in sorted(G.edges(data=True)):
268
+ ... print(edge)
269
+ (0, 2, {'weight': 0.5})
270
+ (0, 5, {'weight': 0.5})
271
+ (2, 4, {'weight': 1.0})
272
+ (2, 5, {'weight': 0.5})
273
+
274
+ Notes
275
+ -----
276
+ No attempt is made to verify that the input graph B is bipartite.
277
+ The graph and node properties are (shallow) copied to the projected graph.
278
+
279
+ See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
280
+ for further details on how bipartite graphs are handled in NetworkX.
281
+
282
+ See Also
283
+ --------
284
+ is_bipartite,
285
+ is_bipartite_node_set,
286
+ sets,
287
+ weighted_projected_graph,
288
+ overlap_weighted_projected_graph,
289
+ generic_weighted_projected_graph,
290
+ projected_graph
291
+
292
+ References
293
+ ----------
294
+ .. [1] Scientific collaboration networks: II.
295
+ Shortest paths, weighted networks, and centrality,
296
+ M. E. J. Newman, Phys. Rev. E 64, 016132 (2001).
297
+ """
298
+ if B.is_directed():
299
+ pred = B.pred
300
+ G = nx.DiGraph()
301
+ else:
302
+ pred = B.adj
303
+ G = nx.Graph()
304
+ G.graph.update(B.graph)
305
+ G.add_nodes_from((n, B.nodes[n]) for n in nodes)
306
+ for u in nodes:
307
+ unbrs = set(B[u])
308
+ nbrs2 = {n for nbr in unbrs for n in B[nbr] if n != u}
309
+ for v in nbrs2:
310
+ vnbrs = set(pred[v])
311
+ common_degree = (len(B[n]) for n in unbrs & vnbrs)
312
+ weight = sum(1.0 / (deg - 1) for deg in common_degree if deg > 1)
313
+ G.add_edge(u, v, weight=weight)
314
+ return G
315
+
316
+
317
+ @not_implemented_for("multigraph")
318
+ @nx._dispatchable(graphs="B", returns_graph=True)
319
+ def overlap_weighted_projected_graph(B, nodes, jaccard=True):
320
+ r"""Overlap weighted projection of B onto one of its node sets.
321
+
322
+ The overlap weighted projection is the projection of the bipartite
323
+ network B onto the specified nodes with weights representing
324
+ the Jaccard index between the neighborhoods of the two nodes in the
325
+ original bipartite network [1]_:
326
+
327
+ .. math::
328
+
329
+ w_{v, u} = \frac{|N(u) \cap N(v)|}{|N(u) \cup N(v)|}
330
+
331
+ or if the parameter 'jaccard' is False, the fraction of common
332
+ neighbors by minimum of both nodes degree in the original
333
+ bipartite graph [1]_:
334
+
335
+ .. math::
336
+
337
+ w_{v, u} = \frac{|N(u) \cap N(v)|}{min(|N(u)|, |N(v)|)}
338
+
339
+ The nodes retain their attributes and are connected in the resulting
340
+ graph if have an edge to a common node in the original bipartite graph.
341
+
342
+ Parameters
343
+ ----------
344
+ B : NetworkX graph
345
+ The input graph should be bipartite.
346
+
347
+ nodes : list or iterable
348
+ Nodes to project onto (the "bottom" nodes).
349
+
350
+ jaccard: Bool (default=True)
351
+
352
+ Returns
353
+ -------
354
+ Graph : NetworkX graph
355
+ A graph that is the projection onto the given nodes.
356
+
357
+ Examples
358
+ --------
359
+ >>> from networkx.algorithms import bipartite
360
+ >>> B = nx.path_graph(5)
361
+ >>> nodes = [0, 2, 4]
362
+ >>> G = bipartite.overlap_weighted_projected_graph(B, nodes)
363
+ >>> list(G)
364
+ [0, 2, 4]
365
+ >>> list(G.edges(data=True))
366
+ [(0, 2, {'weight': 0.5}), (2, 4, {'weight': 0.5})]
367
+ >>> G = bipartite.overlap_weighted_projected_graph(B, nodes, jaccard=False)
368
+ >>> list(G.edges(data=True))
369
+ [(0, 2, {'weight': 1.0}), (2, 4, {'weight': 1.0})]
370
+
371
+ Notes
372
+ -----
373
+ No attempt is made to verify that the input graph B is bipartite.
374
+ The graph and node properties are (shallow) copied to the projected graph.
375
+
376
+ See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
377
+ for further details on how bipartite graphs are handled in NetworkX.
378
+
379
+ See Also
380
+ --------
381
+ is_bipartite,
382
+ is_bipartite_node_set,
383
+ sets,
384
+ weighted_projected_graph,
385
+ collaboration_weighted_projected_graph,
386
+ generic_weighted_projected_graph,
387
+ projected_graph
388
+
389
+ References
390
+ ----------
391
+ .. [1] Borgatti, S.P. and Halgin, D. In press. Analyzing Affiliation
392
+ Networks. In Carrington, P. and Scott, J. (eds) The Sage Handbook
393
+ of Social Network Analysis. Sage Publications.
394
+
395
+ """
396
+ if B.is_directed():
397
+ pred = B.pred
398
+ G = nx.DiGraph()
399
+ else:
400
+ pred = B.adj
401
+ G = nx.Graph()
402
+ G.graph.update(B.graph)
403
+ G.add_nodes_from((n, B.nodes[n]) for n in nodes)
404
+ for u in nodes:
405
+ unbrs = set(B[u])
406
+ nbrs2 = {n for nbr in unbrs for n in B[nbr]} - {u}
407
+ for v in nbrs2:
408
+ vnbrs = set(pred[v])
409
+ if jaccard:
410
+ wt = len(unbrs & vnbrs) / len(unbrs | vnbrs)
411
+ else:
412
+ wt = len(unbrs & vnbrs) / min(len(unbrs), len(vnbrs))
413
+ G.add_edge(u, v, weight=wt)
414
+ return G
415
+
416
+
417
+ @not_implemented_for("multigraph")
418
+ @nx._dispatchable(graphs="B", preserve_all_attrs=True, returns_graph=True)
419
+ def generic_weighted_projected_graph(B, nodes, weight_function=None):
420
+ r"""Weighted projection of B with a user-specified weight function.
421
+
422
+ The bipartite network B is projected on to the specified nodes
423
+ with weights computed by a user-specified function. This function
424
+ must accept as a parameter the neighborhood sets of two nodes and
425
+ return an integer or a float.
426
+
427
+ The nodes retain their attributes and are connected in the resulting graph
428
+ if they have an edge to a common node in the original graph.
429
+
430
+ Parameters
431
+ ----------
432
+ B : NetworkX graph
433
+ The input graph should be bipartite.
434
+
435
+ nodes : list or iterable
436
+ Nodes to project onto (the "bottom" nodes).
437
+
438
+ weight_function : function
439
+ This function must accept as parameters the same input graph
440
+ that this function, and two nodes; and return an integer or a float.
441
+ The default function computes the number of shared neighbors.
442
+
443
+ Returns
444
+ -------
445
+ Graph : NetworkX graph
446
+ A graph that is the projection onto the given nodes.
447
+
448
+ Examples
449
+ --------
450
+ >>> from networkx.algorithms import bipartite
451
+ >>> # Define some custom weight functions
452
+ >>> def jaccard(G, u, v):
453
+ ... unbrs = set(G[u])
454
+ ... vnbrs = set(G[v])
455
+ ... return float(len(unbrs & vnbrs)) / len(unbrs | vnbrs)
456
+ >>> def my_weight(G, u, v, weight="weight"):
457
+ ... w = 0
458
+ ... for nbr in set(G[u]) & set(G[v]):
459
+ ... w += G[u][nbr].get(weight, 1) + G[v][nbr].get(weight, 1)
460
+ ... return w
461
+ >>> # A complete bipartite graph with 4 nodes and 4 edges
462
+ >>> B = nx.complete_bipartite_graph(2, 2)
463
+ >>> # Add some arbitrary weight to the edges
464
+ >>> for i, (u, v) in enumerate(B.edges()):
465
+ ... B.edges[u, v]["weight"] = i + 1
466
+ >>> for edge in B.edges(data=True):
467
+ ... print(edge)
468
+ (0, 2, {'weight': 1})
469
+ (0, 3, {'weight': 2})
470
+ (1, 2, {'weight': 3})
471
+ (1, 3, {'weight': 4})
472
+ >>> # By default, the weight is the number of shared neighbors
473
+ >>> G = bipartite.generic_weighted_projected_graph(B, [0, 1])
474
+ >>> print(list(G.edges(data=True)))
475
+ [(0, 1, {'weight': 2})]
476
+ >>> # To specify a custom weight function use the weight_function parameter
477
+ >>> G = bipartite.generic_weighted_projected_graph(
478
+ ... B, [0, 1], weight_function=jaccard
479
+ ... )
480
+ >>> print(list(G.edges(data=True)))
481
+ [(0, 1, {'weight': 1.0})]
482
+ >>> G = bipartite.generic_weighted_projected_graph(
483
+ ... B, [0, 1], weight_function=my_weight
484
+ ... )
485
+ >>> print(list(G.edges(data=True)))
486
+ [(0, 1, {'weight': 10})]
487
+
488
+ Notes
489
+ -----
490
+ No attempt is made to verify that the input graph B is bipartite.
491
+ The graph and node properties are (shallow) copied to the projected graph.
492
+
493
+ See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
494
+ for further details on how bipartite graphs are handled in NetworkX.
495
+
496
+ See Also
497
+ --------
498
+ is_bipartite,
499
+ is_bipartite_node_set,
500
+ sets,
501
+ weighted_projected_graph,
502
+ collaboration_weighted_projected_graph,
503
+ overlap_weighted_projected_graph,
504
+ projected_graph
505
+
506
+ """
507
+ if B.is_directed():
508
+ pred = B.pred
509
+ G = nx.DiGraph()
510
+ else:
511
+ pred = B.adj
512
+ G = nx.Graph()
513
+ if weight_function is None:
514
+
515
+ def weight_function(G, u, v):
516
+ # Notice that we use set(pred[v]) for handling the directed case.
517
+ return len(set(G[u]) & set(pred[v]))
518
+
519
+ G.graph.update(B.graph)
520
+ G.add_nodes_from((n, B.nodes[n]) for n in nodes)
521
+ for u in nodes:
522
+ nbrs2 = {n for nbr in set(B[u]) for n in B[nbr]} - {u}
523
+ for v in nbrs2:
524
+ weight = weight_function(B, u, v)
525
+ G.add_edge(u, v, weight=weight)
526
+ return G
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/redundancy.py ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Node redundancy for bipartite graphs."""
2
+
3
+ from itertools import combinations
4
+
5
+ import networkx as nx
6
+ from networkx import NetworkXError
7
+
8
+ __all__ = ["node_redundancy"]
9
+
10
+
11
+ @nx._dispatchable
12
+ def node_redundancy(G, nodes=None):
13
+ r"""Computes the node redundancy coefficients for the nodes in the bipartite
14
+ graph `G`.
15
+
16
+ The redundancy coefficient of a node `v` is the fraction of pairs of
17
+ neighbors of `v` that are both linked to other nodes. In a one-mode
18
+ projection these nodes would be linked together even if `v` were
19
+ not there.
20
+
21
+ More formally, for any vertex `v`, the *redundancy coefficient of `v`* is
22
+ defined by
23
+
24
+ .. math::
25
+
26
+ rc(v) = \frac{|\{\{u, w\} \subseteq N(v),
27
+ \: \exists v' \neq v,\: (v',u) \in E\:
28
+ \mathrm{and}\: (v',w) \in E\}|}{ \frac{|N(v)|(|N(v)|-1)}{2}},
29
+
30
+ where `N(v)` is the set of neighbors of `v` in `G`.
31
+
32
+ Parameters
33
+ ----------
34
+ G : graph
35
+ A bipartite graph
36
+
37
+ nodes : list or iterable (optional)
38
+ Compute redundancy for these nodes. The default is all nodes in G.
39
+
40
+ Returns
41
+ -------
42
+ redundancy : dictionary
43
+ A dictionary keyed by node with the node redundancy value.
44
+
45
+ Examples
46
+ --------
47
+ Compute the redundancy coefficient of each node in a graph::
48
+
49
+ >>> from networkx.algorithms import bipartite
50
+ >>> G = nx.cycle_graph(4)
51
+ >>> rc = bipartite.node_redundancy(G)
52
+ >>> rc[0]
53
+ 1.0
54
+
55
+ Compute the average redundancy for the graph::
56
+
57
+ >>> from networkx.algorithms import bipartite
58
+ >>> G = nx.cycle_graph(4)
59
+ >>> rc = bipartite.node_redundancy(G)
60
+ >>> sum(rc.values()) / len(G)
61
+ 1.0
62
+
63
+ Compute the average redundancy for a set of nodes::
64
+
65
+ >>> from networkx.algorithms import bipartite
66
+ >>> G = nx.cycle_graph(4)
67
+ >>> rc = bipartite.node_redundancy(G)
68
+ >>> nodes = [0, 2]
69
+ >>> sum(rc[n] for n in nodes) / len(nodes)
70
+ 1.0
71
+
72
+ Raises
73
+ ------
74
+ NetworkXError
75
+ If any of the nodes in the graph (or in `nodes`, if specified) has
76
+ (out-)degree less than two (which would result in division by zero,
77
+ according to the definition of the redundancy coefficient).
78
+
79
+ References
80
+ ----------
81
+ .. [1] Latapy, Matthieu, Clémence Magnien, and Nathalie Del Vecchio (2008).
82
+ Basic notions for the analysis of large two-mode networks.
83
+ Social Networks 30(1), 31--48.
84
+
85
+ """
86
+ if nodes is None:
87
+ nodes = G
88
+ if any(len(G[v]) < 2 for v in nodes):
89
+ raise NetworkXError(
90
+ "Cannot compute redundancy coefficient for a node"
91
+ " that has fewer than two neighbors."
92
+ )
93
+ # TODO This can be trivially parallelized.
94
+ return {v: _node_redundancy(G, v) for v in nodes}
95
+
96
+
97
+ def _node_redundancy(G, v):
98
+ """Returns the redundancy of the node `v` in the bipartite graph `G`.
99
+
100
+ If `G` is a graph with `n` nodes, the redundancy of a node is the ratio
101
+ of the "overlap" of `v` to the maximum possible overlap of `v`
102
+ according to its degree. The overlap of `v` is the number of pairs of
103
+ neighbors that have mutual neighbors themselves, other than `v`.
104
+
105
+ `v` must have at least two neighbors in `G`.
106
+
107
+ """
108
+ n = len(G[v])
109
+ overlap = sum(
110
+ 1 for (u, w) in combinations(G[v], 2) if (set(G[u]) & set(G[w])) - {v}
111
+ )
112
+ return (2 * overlap) / (n * (n - 1))
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/spectral.py ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Spectral bipartivity measure.
3
+ """
4
+
5
+ import networkx as nx
6
+
7
+ __all__ = ["spectral_bipartivity"]
8
+
9
+
10
+ @nx._dispatchable(edge_attrs="weight")
11
+ def spectral_bipartivity(G, nodes=None, weight="weight"):
12
+ """Returns the spectral bipartivity.
13
+
14
+ Parameters
15
+ ----------
16
+ G : NetworkX graph
17
+
18
+ nodes : list or container optional(default is all nodes)
19
+ Nodes to return value of spectral bipartivity contribution.
20
+
21
+ weight : string or None optional (default = 'weight')
22
+ Edge data key to use for edge weights. If None, weights set to 1.
23
+
24
+ Returns
25
+ -------
26
+ sb : float or dict
27
+ A single number if the keyword nodes is not specified, or
28
+ a dictionary keyed by node with the spectral bipartivity contribution
29
+ of that node as the value.
30
+
31
+ Examples
32
+ --------
33
+ >>> from networkx.algorithms import bipartite
34
+ >>> G = nx.path_graph(4)
35
+ >>> bipartite.spectral_bipartivity(G)
36
+ 1.0
37
+
38
+ Notes
39
+ -----
40
+ This implementation uses Numpy (dense) matrices which are not efficient
41
+ for storing large sparse graphs.
42
+
43
+ See Also
44
+ --------
45
+ color
46
+
47
+ References
48
+ ----------
49
+ .. [1] E. Estrada and J. A. Rodríguez-Velázquez, "Spectral measures of
50
+ bipartivity in complex networks", PhysRev E 72, 046105 (2005)
51
+ """
52
+ import scipy as sp
53
+
54
+ nodelist = list(G) # ordering of nodes in matrix
55
+ A = nx.to_numpy_array(G, nodelist, weight=weight)
56
+ expA = sp.linalg.expm(A)
57
+ expmA = sp.linalg.expm(-A)
58
+ coshA = 0.5 * (expA + expmA)
59
+ if nodes is None:
60
+ # return single number for entire graph
61
+ return float(coshA.diagonal().sum() / expA.diagonal().sum())
62
+ else:
63
+ # contribution for individual nodes
64
+ index = dict(zip(nodelist, range(len(nodelist))))
65
+ sb = {}
66
+ for n in nodes:
67
+ i = index[n]
68
+ sb[n] = coshA.item(i, i) / expA.item(i, i)
69
+ return sb
.venv/lib/python3.11/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_betweenness_centrality.cpython-311.pyc ADDED
Binary file (38.5 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_current_flow_betweenness_centrality.cpython-311.pyc ADDED
Binary file (15.3 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_current_flow_betweenness_centrality_subset.cpython-311.pyc ADDED
Binary file (10.7 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_current_flow_closeness.cpython-311.pyc ADDED
Binary file (3.54 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_group.cpython-311.pyc ADDED
Binary file (16.4 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_load_centrality.cpython-311.pyc ADDED
Binary file (16.5 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_voterank.cpython-311.pyc ADDED
Binary file (3.33 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/__init__.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ from .beamsearch import *
2
+ from .breadth_first_search import *
3
+ from .depth_first_search import *
4
+ from .edgedfs import *
5
+ from .edgebfs import *
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (377 Bytes). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/__pycache__/beamsearch.cpython-311.pyc ADDED
Binary file (3.54 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/__pycache__/breadth_first_search.cpython-311.pyc ADDED
Binary file (20.1 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/__pycache__/depth_first_search.cpython-311.pyc ADDED
Binary file (18.5 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/__pycache__/edgebfs.cpython-311.pyc ADDED
Binary file (8.31 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/__pycache__/edgedfs.cpython-311.pyc ADDED
Binary file (7.45 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/beamsearch.py ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Basic algorithms for breadth-first searching the nodes of a graph."""
2
+
3
+ import networkx as nx
4
+
5
+ __all__ = ["bfs_beam_edges"]
6
+
7
+
8
+ @nx._dispatchable
9
+ def bfs_beam_edges(G, source, value, width=None):
10
+ """Iterates over edges in a beam search.
11
+
12
+ The beam search is a generalized breadth-first search in which only
13
+ the "best" *w* neighbors of the current node are enqueued, where *w*
14
+ is the beam width and "best" is an application-specific
15
+ heuristic. In general, a beam search with a small beam width might
16
+ not visit each node in the graph.
17
+
18
+ .. note::
19
+
20
+ With the default value of ``width=None`` or `width` greater than the
21
+ maximum degree of the graph, this function equates to a slower
22
+ version of `~networkx.algorithms.traversal.breadth_first_search.bfs_edges`.
23
+ All nodes will be visited, though the order of the reported edges may
24
+ vary. In such cases, `value` has no effect - consider using `bfs_edges`
25
+ directly instead.
26
+
27
+ Parameters
28
+ ----------
29
+ G : NetworkX graph
30
+
31
+ source : node
32
+ Starting node for the breadth-first search; this function
33
+ iterates over only those edges in the component reachable from
34
+ this node.
35
+
36
+ value : function
37
+ A function that takes a node of the graph as input and returns a
38
+ real number indicating how "good" it is. A higher value means it
39
+ is more likely to be visited sooner during the search. When
40
+ visiting a new node, only the `width` neighbors with the highest
41
+ `value` are enqueued (in decreasing order of `value`).
42
+
43
+ width : int (default = None)
44
+ The beam width for the search. This is the number of neighbors
45
+ (ordered by `value`) to enqueue when visiting each new node.
46
+
47
+ Yields
48
+ ------
49
+ edge
50
+ Edges in the beam search starting from `source`, given as a pair
51
+ of nodes.
52
+
53
+ Examples
54
+ --------
55
+ To give nodes with, for example, a higher centrality precedence
56
+ during the search, set the `value` function to return the centrality
57
+ value of the node:
58
+
59
+ >>> G = nx.karate_club_graph()
60
+ >>> centrality = nx.eigenvector_centrality(G)
61
+ >>> list(nx.bfs_beam_edges(G, source=0, value=centrality.get, width=3))
62
+ [(0, 2), (0, 1), (0, 8), (2, 32), (1, 13), (8, 33)]
63
+ """
64
+
65
+ if width is None:
66
+ width = len(G)
67
+
68
+ def successors(v):
69
+ """Returns a list of the best neighbors of a node.
70
+
71
+ `v` is a node in the graph `G`.
72
+
73
+ The "best" neighbors are chosen according to the `value`
74
+ function (higher is better). Only the `width` best neighbors of
75
+ `v` are returned.
76
+ """
77
+ # TODO The Python documentation states that for small values, it
78
+ # is better to use `heapq.nlargest`. We should determine the
79
+ # threshold at which its better to use `heapq.nlargest()`
80
+ # instead of `sorted()[:]` and apply that optimization here.
81
+ #
82
+ # If `width` is greater than the number of neighbors of `v`, all
83
+ # neighbors are returned by the semantics of slicing in
84
+ # Python. This occurs in the special case that the user did not
85
+ # specify a `width`: in this case all neighbors are always
86
+ # returned, so this is just a (slower) implementation of
87
+ # `bfs_edges(G, source)` but with a sorted enqueue step.
88
+ return iter(sorted(G.neighbors(v), key=value, reverse=True)[:width])
89
+
90
+ yield from nx.generic_bfs_edges(G, source, successors)
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/breadth_first_search.py ADDED
@@ -0,0 +1,575 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Basic algorithms for breadth-first searching the nodes of a graph."""
2
+
3
+ from collections import deque
4
+
5
+ import networkx as nx
6
+
7
+ __all__ = [
8
+ "bfs_edges",
9
+ "bfs_tree",
10
+ "bfs_predecessors",
11
+ "bfs_successors",
12
+ "descendants_at_distance",
13
+ "bfs_layers",
14
+ "bfs_labeled_edges",
15
+ "generic_bfs_edges",
16
+ ]
17
+
18
+
19
+ @nx._dispatchable
20
+ def generic_bfs_edges(G, source, neighbors=None, depth_limit=None):
21
+ """Iterate over edges in a breadth-first search.
22
+
23
+ The breadth-first search begins at `source` and enqueues the
24
+ neighbors of newly visited nodes specified by the `neighbors`
25
+ function.
26
+
27
+ Parameters
28
+ ----------
29
+ G : NetworkX graph
30
+
31
+ source : node
32
+ Starting node for the breadth-first search; this function
33
+ iterates over only those edges in the component reachable from
34
+ this node.
35
+
36
+ neighbors : function
37
+ A function that takes a newly visited node of the graph as input
38
+ and returns an *iterator* (not just a list) of nodes that are
39
+ neighbors of that node with custom ordering. If not specified, this is
40
+ just the ``G.neighbors`` method, but in general it can be any function
41
+ that returns an iterator over some or all of the neighbors of a
42
+ given node, in any order.
43
+
44
+ depth_limit : int, optional(default=len(G))
45
+ Specify the maximum search depth.
46
+
47
+ Yields
48
+ ------
49
+ edge
50
+ Edges in the breadth-first search starting from `source`.
51
+
52
+ Examples
53
+ --------
54
+ >>> G = nx.path_graph(7)
55
+ >>> list(nx.generic_bfs_edges(G, source=0))
56
+ [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)]
57
+ >>> list(nx.generic_bfs_edges(G, source=2))
58
+ [(2, 1), (2, 3), (1, 0), (3, 4), (4, 5), (5, 6)]
59
+ >>> list(nx.generic_bfs_edges(G, source=2, depth_limit=2))
60
+ [(2, 1), (2, 3), (1, 0), (3, 4)]
61
+
62
+ The `neighbors` param can be used to specify the visitation order of each
63
+ node's neighbors generically. In the following example, we modify the default
64
+ neighbor to return *odd* nodes first:
65
+
66
+ >>> def odd_first(n):
67
+ ... return sorted(G.neighbors(n), key=lambda x: x % 2, reverse=True)
68
+
69
+ >>> G = nx.star_graph(5)
70
+ >>> list(nx.generic_bfs_edges(G, source=0)) # Default neighbor ordering
71
+ [(0, 1), (0, 2), (0, 3), (0, 4), (0, 5)]
72
+ >>> list(nx.generic_bfs_edges(G, source=0, neighbors=odd_first))
73
+ [(0, 1), (0, 3), (0, 5), (0, 2), (0, 4)]
74
+
75
+ Notes
76
+ -----
77
+ This implementation is from `PADS`_, which was in the public domain
78
+ when it was first accessed in July, 2004. The modifications
79
+ to allow depth limits are based on the Wikipedia article
80
+ "`Depth-limited-search`_".
81
+
82
+ .. _PADS: http://www.ics.uci.edu/~eppstein/PADS/BFS.py
83
+ .. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search
84
+ """
85
+ if neighbors is None:
86
+ neighbors = G.neighbors
87
+ if depth_limit is None:
88
+ depth_limit = len(G)
89
+
90
+ seen = {source}
91
+ n = len(G)
92
+ depth = 0
93
+ next_parents_children = [(source, neighbors(source))]
94
+ while next_parents_children and depth < depth_limit:
95
+ this_parents_children = next_parents_children
96
+ next_parents_children = []
97
+ for parent, children in this_parents_children:
98
+ for child in children:
99
+ if child not in seen:
100
+ seen.add(child)
101
+ next_parents_children.append((child, neighbors(child)))
102
+ yield parent, child
103
+ if len(seen) == n:
104
+ return
105
+ depth += 1
106
+
107
+
108
+ @nx._dispatchable
109
+ def bfs_edges(G, source, reverse=False, depth_limit=None, sort_neighbors=None):
110
+ """Iterate over edges in a breadth-first-search starting at source.
111
+
112
+ Parameters
113
+ ----------
114
+ G : NetworkX graph
115
+
116
+ source : node
117
+ Specify starting node for breadth-first search; this function
118
+ iterates over only those edges in the component reachable from
119
+ this node.
120
+
121
+ reverse : bool, optional
122
+ If True traverse a directed graph in the reverse direction
123
+
124
+ depth_limit : int, optional(default=len(G))
125
+ Specify the maximum search depth
126
+
127
+ sort_neighbors : function (default=None)
128
+ A function that takes an iterator over nodes as the input, and
129
+ returns an iterable of the same nodes with a custom ordering.
130
+ For example, `sorted` will sort the nodes in increasing order.
131
+
132
+ Yields
133
+ ------
134
+ edge: 2-tuple of nodes
135
+ Yields edges resulting from the breadth-first search.
136
+
137
+ Examples
138
+ --------
139
+ To get the edges in a breadth-first search::
140
+
141
+ >>> G = nx.path_graph(3)
142
+ >>> list(nx.bfs_edges(G, 0))
143
+ [(0, 1), (1, 2)]
144
+ >>> list(nx.bfs_edges(G, source=0, depth_limit=1))
145
+ [(0, 1)]
146
+
147
+ To get the nodes in a breadth-first search order::
148
+
149
+ >>> G = nx.path_graph(3)
150
+ >>> root = 2
151
+ >>> edges = nx.bfs_edges(G, root)
152
+ >>> nodes = [root] + [v for u, v in edges]
153
+ >>> nodes
154
+ [2, 1, 0]
155
+
156
+ Notes
157
+ -----
158
+ The naming of this function is very similar to
159
+ :func:`~networkx.algorithms.traversal.edgebfs.edge_bfs`. The difference
160
+ is that ``edge_bfs`` yields edges even if they extend back to an already
161
+ explored node while this generator yields the edges of the tree that results
162
+ from a breadth-first-search (BFS) so no edges are reported if they extend
163
+ to already explored nodes. That means ``edge_bfs`` reports all edges while
164
+ ``bfs_edges`` only reports those traversed by a node-based BFS. Yet another
165
+ description is that ``bfs_edges`` reports the edges traversed during BFS
166
+ while ``edge_bfs`` reports all edges in the order they are explored.
167
+
168
+ Based on the breadth-first search implementation in PADS [1]_
169
+ by D. Eppstein, July 2004; with modifications to allow depth limits
170
+ as described in [2]_.
171
+
172
+ References
173
+ ----------
174
+ .. [1] http://www.ics.uci.edu/~eppstein/PADS/BFS.py.
175
+ .. [2] https://en.wikipedia.org/wiki/Depth-limited_search
176
+
177
+ See Also
178
+ --------
179
+ bfs_tree
180
+ :func:`~networkx.algorithms.traversal.depth_first_search.dfs_edges`
181
+ :func:`~networkx.algorithms.traversal.edgebfs.edge_bfs`
182
+
183
+ """
184
+ if reverse and G.is_directed():
185
+ successors = G.predecessors
186
+ else:
187
+ successors = G.neighbors
188
+
189
+ if sort_neighbors is not None:
190
+ yield from generic_bfs_edges(
191
+ G, source, lambda node: iter(sort_neighbors(successors(node))), depth_limit
192
+ )
193
+ else:
194
+ yield from generic_bfs_edges(G, source, successors, depth_limit)
195
+
196
+
197
+ @nx._dispatchable(returns_graph=True)
198
+ def bfs_tree(G, source, reverse=False, depth_limit=None, sort_neighbors=None):
199
+ """Returns an oriented tree constructed from of a breadth-first-search
200
+ starting at source.
201
+
202
+ Parameters
203
+ ----------
204
+ G : NetworkX graph
205
+
206
+ source : node
207
+ Specify starting node for breadth-first search
208
+
209
+ reverse : bool, optional
210
+ If True traverse a directed graph in the reverse direction
211
+
212
+ depth_limit : int, optional(default=len(G))
213
+ Specify the maximum search depth
214
+
215
+ sort_neighbors : function (default=None)
216
+ A function that takes an iterator over nodes as the input, and
217
+ returns an iterable of the same nodes with a custom ordering.
218
+ For example, `sorted` will sort the nodes in increasing order.
219
+
220
+ Returns
221
+ -------
222
+ T: NetworkX DiGraph
223
+ An oriented tree
224
+
225
+ Examples
226
+ --------
227
+ >>> G = nx.path_graph(3)
228
+ >>> list(nx.bfs_tree(G, 1).edges())
229
+ [(1, 0), (1, 2)]
230
+ >>> H = nx.Graph()
231
+ >>> nx.add_path(H, [0, 1, 2, 3, 4, 5, 6])
232
+ >>> nx.add_path(H, [2, 7, 8, 9, 10])
233
+ >>> sorted(list(nx.bfs_tree(H, source=3, depth_limit=3).edges()))
234
+ [(1, 0), (2, 1), (2, 7), (3, 2), (3, 4), (4, 5), (5, 6), (7, 8)]
235
+
236
+
237
+ Notes
238
+ -----
239
+ Based on http://www.ics.uci.edu/~eppstein/PADS/BFS.py
240
+ by D. Eppstein, July 2004. The modifications
241
+ to allow depth limits based on the Wikipedia article
242
+ "`Depth-limited-search`_".
243
+
244
+ .. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search
245
+
246
+ See Also
247
+ --------
248
+ dfs_tree
249
+ bfs_edges
250
+ edge_bfs
251
+ """
252
+ T = nx.DiGraph()
253
+ T.add_node(source)
254
+ edges_gen = bfs_edges(
255
+ G,
256
+ source,
257
+ reverse=reverse,
258
+ depth_limit=depth_limit,
259
+ sort_neighbors=sort_neighbors,
260
+ )
261
+ T.add_edges_from(edges_gen)
262
+ return T
263
+
264
+
265
+ @nx._dispatchable
266
+ def bfs_predecessors(G, source, depth_limit=None, sort_neighbors=None):
267
+ """Returns an iterator of predecessors in breadth-first-search from source.
268
+
269
+ Parameters
270
+ ----------
271
+ G : NetworkX graph
272
+
273
+ source : node
274
+ Specify starting node for breadth-first search
275
+
276
+ depth_limit : int, optional(default=len(G))
277
+ Specify the maximum search depth
278
+
279
+ sort_neighbors : function (default=None)
280
+ A function that takes an iterator over nodes as the input, and
281
+ returns an iterable of the same nodes with a custom ordering.
282
+ For example, `sorted` will sort the nodes in increasing order.
283
+
284
+ Returns
285
+ -------
286
+ pred: iterator
287
+ (node, predecessor) iterator where `predecessor` is the predecessor of
288
+ `node` in a breadth first search starting from `source`.
289
+
290
+ Examples
291
+ --------
292
+ >>> G = nx.path_graph(3)
293
+ >>> dict(nx.bfs_predecessors(G, 0))
294
+ {1: 0, 2: 1}
295
+ >>> H = nx.Graph()
296
+ >>> H.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)])
297
+ >>> dict(nx.bfs_predecessors(H, 0))
298
+ {1: 0, 2: 0, 3: 1, 4: 1, 5: 2, 6: 2}
299
+ >>> M = nx.Graph()
300
+ >>> nx.add_path(M, [0, 1, 2, 3, 4, 5, 6])
301
+ >>> nx.add_path(M, [2, 7, 8, 9, 10])
302
+ >>> sorted(nx.bfs_predecessors(M, source=1, depth_limit=3))
303
+ [(0, 1), (2, 1), (3, 2), (4, 3), (7, 2), (8, 7)]
304
+ >>> N = nx.DiGraph()
305
+ >>> nx.add_path(N, [0, 1, 2, 3, 4, 7])
306
+ >>> nx.add_path(N, [3, 5, 6, 7])
307
+ >>> sorted(nx.bfs_predecessors(N, source=2))
308
+ [(3, 2), (4, 3), (5, 3), (6, 5), (7, 4)]
309
+
310
+ Notes
311
+ -----
312
+ Based on http://www.ics.uci.edu/~eppstein/PADS/BFS.py
313
+ by D. Eppstein, July 2004. The modifications
314
+ to allow depth limits based on the Wikipedia article
315
+ "`Depth-limited-search`_".
316
+
317
+ .. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search
318
+
319
+ See Also
320
+ --------
321
+ bfs_tree
322
+ bfs_edges
323
+ edge_bfs
324
+ """
325
+ for s, t in bfs_edges(
326
+ G, source, depth_limit=depth_limit, sort_neighbors=sort_neighbors
327
+ ):
328
+ yield (t, s)
329
+
330
+
331
+ @nx._dispatchable
332
+ def bfs_successors(G, source, depth_limit=None, sort_neighbors=None):
333
+ """Returns an iterator of successors in breadth-first-search from source.
334
+
335
+ Parameters
336
+ ----------
337
+ G : NetworkX graph
338
+
339
+ source : node
340
+ Specify starting node for breadth-first search
341
+
342
+ depth_limit : int, optional(default=len(G))
343
+ Specify the maximum search depth
344
+
345
+ sort_neighbors : function (default=None)
346
+ A function that takes an iterator over nodes as the input, and
347
+ returns an iterable of the same nodes with a custom ordering.
348
+ For example, `sorted` will sort the nodes in increasing order.
349
+
350
+ Returns
351
+ -------
352
+ succ: iterator
353
+ (node, successors) iterator where `successors` is the non-empty list of
354
+ successors of `node` in a breadth first search from `source`.
355
+ To appear in the iterator, `node` must have successors.
356
+
357
+ Examples
358
+ --------
359
+ >>> G = nx.path_graph(3)
360
+ >>> dict(nx.bfs_successors(G, 0))
361
+ {0: [1], 1: [2]}
362
+ >>> H = nx.Graph()
363
+ >>> H.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)])
364
+ >>> dict(nx.bfs_successors(H, 0))
365
+ {0: [1, 2], 1: [3, 4], 2: [5, 6]}
366
+ >>> G = nx.Graph()
367
+ >>> nx.add_path(G, [0, 1, 2, 3, 4, 5, 6])
368
+ >>> nx.add_path(G, [2, 7, 8, 9, 10])
369
+ >>> dict(nx.bfs_successors(G, source=1, depth_limit=3))
370
+ {1: [0, 2], 2: [3, 7], 3: [4], 7: [8]}
371
+ >>> G = nx.DiGraph()
372
+ >>> nx.add_path(G, [0, 1, 2, 3, 4, 5])
373
+ >>> dict(nx.bfs_successors(G, source=3))
374
+ {3: [4], 4: [5]}
375
+
376
+ Notes
377
+ -----
378
+ Based on http://www.ics.uci.edu/~eppstein/PADS/BFS.py
379
+ by D. Eppstein, July 2004.The modifications
380
+ to allow depth limits based on the Wikipedia article
381
+ "`Depth-limited-search`_".
382
+
383
+ .. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search
384
+
385
+ See Also
386
+ --------
387
+ bfs_tree
388
+ bfs_edges
389
+ edge_bfs
390
+ """
391
+ parent = source
392
+ children = []
393
+ for p, c in bfs_edges(
394
+ G, source, depth_limit=depth_limit, sort_neighbors=sort_neighbors
395
+ ):
396
+ if p == parent:
397
+ children.append(c)
398
+ continue
399
+ yield (parent, children)
400
+ children = [c]
401
+ parent = p
402
+ yield (parent, children)
403
+
404
+
405
+ @nx._dispatchable
406
+ def bfs_layers(G, sources):
407
+ """Returns an iterator of all the layers in breadth-first search traversal.
408
+
409
+ Parameters
410
+ ----------
411
+ G : NetworkX graph
412
+ A graph over which to find the layers using breadth-first search.
413
+
414
+ sources : node in `G` or list of nodes in `G`
415
+ Specify starting nodes for single source or multiple sources breadth-first search
416
+
417
+ Yields
418
+ ------
419
+ layer: list of nodes
420
+ Yields list of nodes at the same distance from sources
421
+
422
+ Examples
423
+ --------
424
+ >>> G = nx.path_graph(5)
425
+ >>> dict(enumerate(nx.bfs_layers(G, [0, 4])))
426
+ {0: [0, 4], 1: [1, 3], 2: [2]}
427
+ >>> H = nx.Graph()
428
+ >>> H.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)])
429
+ >>> dict(enumerate(nx.bfs_layers(H, [1])))
430
+ {0: [1], 1: [0, 3, 4], 2: [2], 3: [5, 6]}
431
+ >>> dict(enumerate(nx.bfs_layers(H, [1, 6])))
432
+ {0: [1, 6], 1: [0, 3, 4, 2], 2: [5]}
433
+ """
434
+ if sources in G:
435
+ sources = [sources]
436
+
437
+ current_layer = list(sources)
438
+ visited = set(sources)
439
+
440
+ for source in current_layer:
441
+ if source not in G:
442
+ raise nx.NetworkXError(f"The node {source} is not in the graph.")
443
+
444
+ # this is basically BFS, except that the current layer only stores the nodes at
445
+ # same distance from sources at each iteration
446
+ while current_layer:
447
+ yield current_layer
448
+ next_layer = []
449
+ for node in current_layer:
450
+ for child in G[node]:
451
+ if child not in visited:
452
+ visited.add(child)
453
+ next_layer.append(child)
454
+ current_layer = next_layer
455
+
456
+
457
+ REVERSE_EDGE = "reverse"
458
+ TREE_EDGE = "tree"
459
+ FORWARD_EDGE = "forward"
460
+ LEVEL_EDGE = "level"
461
+
462
+
463
+ @nx._dispatchable
464
+ def bfs_labeled_edges(G, sources):
465
+ """Iterate over edges in a breadth-first search (BFS) labeled by type.
466
+
467
+ We generate triple of the form (*u*, *v*, *d*), where (*u*, *v*) is the
468
+ edge being explored in the breadth-first search and *d* is one of the
469
+ strings 'tree', 'forward', 'level', or 'reverse'. A 'tree' edge is one in
470
+ which *v* is first discovered and placed into the layer below *u*. A
471
+ 'forward' edge is one in which *u* is on the layer above *v* and *v* has
472
+ already been discovered. A 'level' edge is one in which both *u* and *v*
473
+ occur on the same layer. A 'reverse' edge is one in which *u* is on a layer
474
+ below *v*.
475
+
476
+ We emit each edge exactly once. In an undirected graph, 'reverse' edges do
477
+ not occur, because each is discovered either as a 'tree' or 'forward' edge.
478
+
479
+ Parameters
480
+ ----------
481
+ G : NetworkX graph
482
+ A graph over which to find the layers using breadth-first search.
483
+
484
+ sources : node in `G` or list of nodes in `G`
485
+ Starting nodes for single source or multiple sources breadth-first search
486
+
487
+ Yields
488
+ ------
489
+ edges: generator
490
+ A generator of triples (*u*, *v*, *d*) where (*u*, *v*) is the edge being
491
+ explored and *d* is described above.
492
+
493
+ Examples
494
+ --------
495
+ >>> G = nx.cycle_graph(4, create_using=nx.DiGraph)
496
+ >>> list(nx.bfs_labeled_edges(G, 0))
497
+ [(0, 1, 'tree'), (1, 2, 'tree'), (2, 3, 'tree'), (3, 0, 'reverse')]
498
+ >>> G = nx.complete_graph(3)
499
+ >>> list(nx.bfs_labeled_edges(G, 0))
500
+ [(0, 1, 'tree'), (0, 2, 'tree'), (1, 2, 'level')]
501
+ >>> list(nx.bfs_labeled_edges(G, [0, 1]))
502
+ [(0, 1, 'level'), (0, 2, 'tree'), (1, 2, 'forward')]
503
+ """
504
+ if sources in G:
505
+ sources = [sources]
506
+
507
+ neighbors = G._adj
508
+ directed = G.is_directed()
509
+ visited = set()
510
+ visit = visited.discard if directed else visited.add
511
+ # We use visited in a negative sense, so the visited set stays empty for the
512
+ # directed case and level edges are reported on their first occurrence in
513
+ # the undirected case. Note our use of visited.discard -- this is built-in
514
+ # thus somewhat faster than a python-defined def nop(x): pass
515
+ depth = {s: 0 for s in sources}
516
+ queue = deque(depth.items())
517
+ push = queue.append
518
+ pop = queue.popleft
519
+ while queue:
520
+ u, du = pop()
521
+ for v in neighbors[u]:
522
+ if v not in depth:
523
+ depth[v] = dv = du + 1
524
+ push((v, dv))
525
+ yield u, v, TREE_EDGE
526
+ else:
527
+ dv = depth[v]
528
+ if du == dv:
529
+ if v not in visited:
530
+ yield u, v, LEVEL_EDGE
531
+ elif du < dv:
532
+ yield u, v, FORWARD_EDGE
533
+ elif directed:
534
+ yield u, v, REVERSE_EDGE
535
+ visit(u)
536
+
537
+
538
+ @nx._dispatchable
539
+ def descendants_at_distance(G, source, distance):
540
+ """Returns all nodes at a fixed `distance` from `source` in `G`.
541
+
542
+ Parameters
543
+ ----------
544
+ G : NetworkX graph
545
+ A graph
546
+ source : node in `G`
547
+ distance : the distance of the wanted nodes from `source`
548
+
549
+ Returns
550
+ -------
551
+ set()
552
+ The descendants of `source` in `G` at the given `distance` from `source`
553
+
554
+ Examples
555
+ --------
556
+ >>> G = nx.path_graph(5)
557
+ >>> nx.descendants_at_distance(G, 2, 2)
558
+ {0, 4}
559
+ >>> H = nx.DiGraph()
560
+ >>> H.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)])
561
+ >>> nx.descendants_at_distance(H, 0, 2)
562
+ {3, 4, 5, 6}
563
+ >>> nx.descendants_at_distance(H, 5, 0)
564
+ {5}
565
+ >>> nx.descendants_at_distance(H, 5, 1)
566
+ set()
567
+ """
568
+ if source not in G:
569
+ raise nx.NetworkXError(f"The node {source} is not in the graph.")
570
+
571
+ bfs_generator = nx.bfs_layers(G, source)
572
+ for i, layer in enumerate(bfs_generator):
573
+ if i == distance:
574
+ return set(layer)
575
+ return set()
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/depth_first_search.py ADDED
@@ -0,0 +1,529 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Basic algorithms for depth-first searching the nodes of a graph."""
2
+
3
+ from collections import defaultdict
4
+
5
+ import networkx as nx
6
+
7
+ __all__ = [
8
+ "dfs_edges",
9
+ "dfs_tree",
10
+ "dfs_predecessors",
11
+ "dfs_successors",
12
+ "dfs_preorder_nodes",
13
+ "dfs_postorder_nodes",
14
+ "dfs_labeled_edges",
15
+ ]
16
+
17
+
18
+ @nx._dispatchable
19
+ def dfs_edges(G, source=None, depth_limit=None, *, sort_neighbors=None):
20
+ """Iterate over edges in a depth-first-search (DFS).
21
+
22
+ Perform a depth-first-search over the nodes of `G` and yield
23
+ the edges in order. This may not generate all edges in `G`
24
+ (see `~networkx.algorithms.traversal.edgedfs.edge_dfs`).
25
+
26
+ Parameters
27
+ ----------
28
+ G : NetworkX graph
29
+
30
+ source : node, optional
31
+ Specify starting node for depth-first search and yield edges in
32
+ the component reachable from source.
33
+
34
+ depth_limit : int, optional (default=len(G))
35
+ Specify the maximum search depth.
36
+
37
+ sort_neighbors : function (default=None)
38
+ A function that takes an iterator over nodes as the input, and
39
+ returns an iterable of the same nodes with a custom ordering.
40
+ For example, `sorted` will sort the nodes in increasing order.
41
+
42
+ Yields
43
+ ------
44
+ edge: 2-tuple of nodes
45
+ Yields edges resulting from the depth-first-search.
46
+
47
+ Examples
48
+ --------
49
+ >>> G = nx.path_graph(5)
50
+ >>> list(nx.dfs_edges(G, source=0))
51
+ [(0, 1), (1, 2), (2, 3), (3, 4)]
52
+ >>> list(nx.dfs_edges(G, source=0, depth_limit=2))
53
+ [(0, 1), (1, 2)]
54
+
55
+ Notes
56
+ -----
57
+ If a source is not specified then a source is chosen arbitrarily and
58
+ repeatedly until all components in the graph are searched.
59
+
60
+ The implementation of this function is adapted from David Eppstein's
61
+ depth-first search function in PADS [1]_, with modifications
62
+ to allow depth limits based on the Wikipedia article
63
+ "Depth-limited search" [2]_.
64
+
65
+ See Also
66
+ --------
67
+ dfs_preorder_nodes
68
+ dfs_postorder_nodes
69
+ dfs_labeled_edges
70
+ :func:`~networkx.algorithms.traversal.edgedfs.edge_dfs`
71
+ :func:`~networkx.algorithms.traversal.breadth_first_search.bfs_edges`
72
+
73
+ References
74
+ ----------
75
+ .. [1] http://www.ics.uci.edu/~eppstein/PADS
76
+ .. [2] https://en.wikipedia.org/wiki/Depth-limited_search
77
+ """
78
+ if source is None:
79
+ # edges for all components
80
+ nodes = G
81
+ else:
82
+ # edges for components with source
83
+ nodes = [source]
84
+ if depth_limit is None:
85
+ depth_limit = len(G)
86
+
87
+ get_children = (
88
+ G.neighbors
89
+ if sort_neighbors is None
90
+ else lambda n: iter(sort_neighbors(G.neighbors(n)))
91
+ )
92
+
93
+ visited = set()
94
+ for start in nodes:
95
+ if start in visited:
96
+ continue
97
+ visited.add(start)
98
+ stack = [(start, get_children(start))]
99
+ depth_now = 1
100
+ while stack:
101
+ parent, children = stack[-1]
102
+ for child in children:
103
+ if child not in visited:
104
+ yield parent, child
105
+ visited.add(child)
106
+ if depth_now < depth_limit:
107
+ stack.append((child, get_children(child)))
108
+ depth_now += 1
109
+ break
110
+ else:
111
+ stack.pop()
112
+ depth_now -= 1
113
+
114
+
115
+ @nx._dispatchable(returns_graph=True)
116
+ def dfs_tree(G, source=None, depth_limit=None, *, sort_neighbors=None):
117
+ """Returns oriented tree constructed from a depth-first-search from source.
118
+
119
+ Parameters
120
+ ----------
121
+ G : NetworkX graph
122
+
123
+ source : node, optional
124
+ Specify starting node for depth-first search.
125
+
126
+ depth_limit : int, optional (default=len(G))
127
+ Specify the maximum search depth.
128
+
129
+ sort_neighbors : function (default=None)
130
+ A function that takes an iterator over nodes as the input, and
131
+ returns an iterable of the same nodes with a custom ordering.
132
+ For example, `sorted` will sort the nodes in increasing order.
133
+
134
+ Returns
135
+ -------
136
+ T : NetworkX DiGraph
137
+ An oriented tree
138
+
139
+ Examples
140
+ --------
141
+ >>> G = nx.path_graph(5)
142
+ >>> T = nx.dfs_tree(G, source=0, depth_limit=2)
143
+ >>> list(T.edges())
144
+ [(0, 1), (1, 2)]
145
+ >>> T = nx.dfs_tree(G, source=0)
146
+ >>> list(T.edges())
147
+ [(0, 1), (1, 2), (2, 3), (3, 4)]
148
+
149
+ See Also
150
+ --------
151
+ dfs_preorder_nodes
152
+ dfs_postorder_nodes
153
+ dfs_labeled_edges
154
+ :func:`~networkx.algorithms.traversal.edgedfs.edge_dfs`
155
+ :func:`~networkx.algorithms.traversal.breadth_first_search.bfs_tree`
156
+ """
157
+ T = nx.DiGraph()
158
+ if source is None:
159
+ T.add_nodes_from(G)
160
+ else:
161
+ T.add_node(source)
162
+ T.add_edges_from(dfs_edges(G, source, depth_limit, sort_neighbors=sort_neighbors))
163
+ return T
164
+
165
+
166
+ @nx._dispatchable
167
+ def dfs_predecessors(G, source=None, depth_limit=None, *, sort_neighbors=None):
168
+ """Returns dictionary of predecessors in depth-first-search from source.
169
+
170
+ Parameters
171
+ ----------
172
+ G : NetworkX graph
173
+
174
+ source : node, optional
175
+ Specify starting node for depth-first search.
176
+ Note that you will get predecessors for all nodes in the
177
+ component containing `source`. This input only specifies
178
+ where the DFS starts.
179
+
180
+ depth_limit : int, optional (default=len(G))
181
+ Specify the maximum search depth.
182
+
183
+ sort_neighbors : function (default=None)
184
+ A function that takes an iterator over nodes as the input, and
185
+ returns an iterable of the same nodes with a custom ordering.
186
+ For example, `sorted` will sort the nodes in increasing order.
187
+
188
+ Returns
189
+ -------
190
+ pred: dict
191
+ A dictionary with nodes as keys and predecessor nodes as values.
192
+
193
+ Examples
194
+ --------
195
+ >>> G = nx.path_graph(4)
196
+ >>> nx.dfs_predecessors(G, source=0)
197
+ {1: 0, 2: 1, 3: 2}
198
+ >>> nx.dfs_predecessors(G, source=0, depth_limit=2)
199
+ {1: 0, 2: 1}
200
+
201
+ Notes
202
+ -----
203
+ If a source is not specified then a source is chosen arbitrarily and
204
+ repeatedly until all components in the graph are searched.
205
+
206
+ The implementation of this function is adapted from David Eppstein's
207
+ depth-first search function in `PADS`_, with modifications
208
+ to allow depth limits based on the Wikipedia article
209
+ "`Depth-limited search`_".
210
+
211
+ .. _PADS: http://www.ics.uci.edu/~eppstein/PADS
212
+ .. _Depth-limited search: https://en.wikipedia.org/wiki/Depth-limited_search
213
+
214
+ See Also
215
+ --------
216
+ dfs_preorder_nodes
217
+ dfs_postorder_nodes
218
+ dfs_labeled_edges
219
+ :func:`~networkx.algorithms.traversal.edgedfs.edge_dfs`
220
+ :func:`~networkx.algorithms.traversal.breadth_first_search.bfs_tree`
221
+ """
222
+ return {
223
+ t: s
224
+ for s, t in dfs_edges(G, source, depth_limit, sort_neighbors=sort_neighbors)
225
+ }
226
+
227
+
228
+ @nx._dispatchable
229
+ def dfs_successors(G, source=None, depth_limit=None, *, sort_neighbors=None):
230
+ """Returns dictionary of successors in depth-first-search from source.
231
+
232
+ Parameters
233
+ ----------
234
+ G : NetworkX graph
235
+
236
+ source : node, optional
237
+ Specify starting node for depth-first search.
238
+ Note that you will get successors for all nodes in the
239
+ component containing `source`. This input only specifies
240
+ where the DFS starts.
241
+
242
+ depth_limit : int, optional (default=len(G))
243
+ Specify the maximum search depth.
244
+
245
+ sort_neighbors : function (default=None)
246
+ A function that takes an iterator over nodes as the input, and
247
+ returns an iterable of the same nodes with a custom ordering.
248
+ For example, `sorted` will sort the nodes in increasing order.
249
+
250
+ Returns
251
+ -------
252
+ succ: dict
253
+ A dictionary with nodes as keys and list of successor nodes as values.
254
+
255
+ Examples
256
+ --------
257
+ >>> G = nx.path_graph(5)
258
+ >>> nx.dfs_successors(G, source=0)
259
+ {0: [1], 1: [2], 2: [3], 3: [4]}
260
+ >>> nx.dfs_successors(G, source=0, depth_limit=2)
261
+ {0: [1], 1: [2]}
262
+
263
+ Notes
264
+ -----
265
+ If a source is not specified then a source is chosen arbitrarily and
266
+ repeatedly until all components in the graph are searched.
267
+
268
+ The implementation of this function is adapted from David Eppstein's
269
+ depth-first search function in `PADS`_, with modifications
270
+ to allow depth limits based on the Wikipedia article
271
+ "`Depth-limited search`_".
272
+
273
+ .. _PADS: http://www.ics.uci.edu/~eppstein/PADS
274
+ .. _Depth-limited search: https://en.wikipedia.org/wiki/Depth-limited_search
275
+
276
+ See Also
277
+ --------
278
+ dfs_preorder_nodes
279
+ dfs_postorder_nodes
280
+ dfs_labeled_edges
281
+ :func:`~networkx.algorithms.traversal.edgedfs.edge_dfs`
282
+ :func:`~networkx.algorithms.traversal.breadth_first_search.bfs_tree`
283
+ """
284
+ d = defaultdict(list)
285
+ for s, t in dfs_edges(
286
+ G,
287
+ source=source,
288
+ depth_limit=depth_limit,
289
+ sort_neighbors=sort_neighbors,
290
+ ):
291
+ d[s].append(t)
292
+ return dict(d)
293
+
294
+
295
+ @nx._dispatchable
296
+ def dfs_postorder_nodes(G, source=None, depth_limit=None, *, sort_neighbors=None):
297
+ """Generate nodes in a depth-first-search post-ordering starting at source.
298
+
299
+ Parameters
300
+ ----------
301
+ G : NetworkX graph
302
+
303
+ source : node, optional
304
+ Specify starting node for depth-first search.
305
+
306
+ depth_limit : int, optional (default=len(G))
307
+ Specify the maximum search depth.
308
+
309
+ sort_neighbors : function (default=None)
310
+ A function that takes an iterator over nodes as the input, and
311
+ returns an iterable of the same nodes with a custom ordering.
312
+ For example, `sorted` will sort the nodes in increasing order.
313
+
314
+ Returns
315
+ -------
316
+ nodes: generator
317
+ A generator of nodes in a depth-first-search post-ordering.
318
+
319
+ Examples
320
+ --------
321
+ >>> G = nx.path_graph(5)
322
+ >>> list(nx.dfs_postorder_nodes(G, source=0))
323
+ [4, 3, 2, 1, 0]
324
+ >>> list(nx.dfs_postorder_nodes(G, source=0, depth_limit=2))
325
+ [1, 0]
326
+
327
+ Notes
328
+ -----
329
+ If a source is not specified then a source is chosen arbitrarily and
330
+ repeatedly until all components in the graph are searched.
331
+
332
+ The implementation of this function is adapted from David Eppstein's
333
+ depth-first search function in `PADS`_, with modifications
334
+ to allow depth limits based on the Wikipedia article
335
+ "`Depth-limited search`_".
336
+
337
+ .. _PADS: http://www.ics.uci.edu/~eppstein/PADS
338
+ .. _Depth-limited search: https://en.wikipedia.org/wiki/Depth-limited_search
339
+
340
+ See Also
341
+ --------
342
+ dfs_edges
343
+ dfs_preorder_nodes
344
+ dfs_labeled_edges
345
+ :func:`~networkx.algorithms.traversal.edgedfs.edge_dfs`
346
+ :func:`~networkx.algorithms.traversal.breadth_first_search.bfs_tree`
347
+ """
348
+ edges = nx.dfs_labeled_edges(
349
+ G, source=source, depth_limit=depth_limit, sort_neighbors=sort_neighbors
350
+ )
351
+ return (v for u, v, d in edges if d == "reverse")
352
+
353
+
354
+ @nx._dispatchable
355
+ def dfs_preorder_nodes(G, source=None, depth_limit=None, *, sort_neighbors=None):
356
+ """Generate nodes in a depth-first-search pre-ordering starting at source.
357
+
358
+ Parameters
359
+ ----------
360
+ G : NetworkX graph
361
+
362
+ source : node, optional
363
+ Specify starting node for depth-first search and return nodes in
364
+ the component reachable from source.
365
+
366
+ depth_limit : int, optional (default=len(G))
367
+ Specify the maximum search depth.
368
+
369
+ sort_neighbors : function (default=None)
370
+ A function that takes an iterator over nodes as the input, and
371
+ returns an iterable of the same nodes with a custom ordering.
372
+ For example, `sorted` will sort the nodes in increasing order.
373
+
374
+ Returns
375
+ -------
376
+ nodes: generator
377
+ A generator of nodes in a depth-first-search pre-ordering.
378
+
379
+ Examples
380
+ --------
381
+ >>> G = nx.path_graph(5)
382
+ >>> list(nx.dfs_preorder_nodes(G, source=0))
383
+ [0, 1, 2, 3, 4]
384
+ >>> list(nx.dfs_preorder_nodes(G, source=0, depth_limit=2))
385
+ [0, 1, 2]
386
+
387
+ Notes
388
+ -----
389
+ If a source is not specified then a source is chosen arbitrarily and
390
+ repeatedly until all components in the graph are searched.
391
+
392
+ The implementation of this function is adapted from David Eppstein's
393
+ depth-first search function in `PADS`_, with modifications
394
+ to allow depth limits based on the Wikipedia article
395
+ "`Depth-limited search`_".
396
+
397
+ .. _PADS: http://www.ics.uci.edu/~eppstein/PADS
398
+ .. _Depth-limited search: https://en.wikipedia.org/wiki/Depth-limited_search
399
+
400
+ See Also
401
+ --------
402
+ dfs_edges
403
+ dfs_postorder_nodes
404
+ dfs_labeled_edges
405
+ :func:`~networkx.algorithms.traversal.breadth_first_search.bfs_edges`
406
+ """
407
+ edges = nx.dfs_labeled_edges(
408
+ G, source=source, depth_limit=depth_limit, sort_neighbors=sort_neighbors
409
+ )
410
+ return (v for u, v, d in edges if d == "forward")
411
+
412
+
413
+ @nx._dispatchable
414
+ def dfs_labeled_edges(G, source=None, depth_limit=None, *, sort_neighbors=None):
415
+ """Iterate over edges in a depth-first-search (DFS) labeled by type.
416
+
417
+ Parameters
418
+ ----------
419
+ G : NetworkX graph
420
+
421
+ source : node, optional
422
+ Specify starting node for depth-first search and return edges in
423
+ the component reachable from source.
424
+
425
+ depth_limit : int, optional (default=len(G))
426
+ Specify the maximum search depth.
427
+
428
+ sort_neighbors : function (default=None)
429
+ A function that takes an iterator over nodes as the input, and
430
+ returns an iterable of the same nodes with a custom ordering.
431
+ For example, `sorted` will sort the nodes in increasing order.
432
+
433
+ Returns
434
+ -------
435
+ edges: generator
436
+ A generator of triples of the form (*u*, *v*, *d*), where (*u*,
437
+ *v*) is the edge being explored in the depth-first search and *d*
438
+ is one of the strings 'forward', 'nontree', 'reverse', or 'reverse-depth_limit'.
439
+ A 'forward' edge is one in which *u* has been visited but *v* has
440
+ not. A 'nontree' edge is one in which both *u* and *v* have been
441
+ visited but the edge is not in the DFS tree. A 'reverse' edge is
442
+ one in which both *u* and *v* have been visited and the edge is in
443
+ the DFS tree. When the `depth_limit` is reached via a 'forward' edge,
444
+ a 'reverse' edge is immediately generated rather than the subtree
445
+ being explored. To indicate this flavor of 'reverse' edge, the string
446
+ yielded is 'reverse-depth_limit'.
447
+
448
+ Examples
449
+ --------
450
+
451
+ The labels reveal the complete transcript of the depth-first search
452
+ algorithm in more detail than, for example, :func:`dfs_edges`::
453
+
454
+ >>> from pprint import pprint
455
+ >>>
456
+ >>> G = nx.DiGraph([(0, 1), (1, 2), (2, 1)])
457
+ >>> pprint(list(nx.dfs_labeled_edges(G, source=0)))
458
+ [(0, 0, 'forward'),
459
+ (0, 1, 'forward'),
460
+ (1, 2, 'forward'),
461
+ (2, 1, 'nontree'),
462
+ (1, 2, 'reverse'),
463
+ (0, 1, 'reverse'),
464
+ (0, 0, 'reverse')]
465
+
466
+ Notes
467
+ -----
468
+ If a source is not specified then a source is chosen arbitrarily and
469
+ repeatedly until all components in the graph are searched.
470
+
471
+ The implementation of this function is adapted from David Eppstein's
472
+ depth-first search function in `PADS`_, with modifications
473
+ to allow depth limits based on the Wikipedia article
474
+ "`Depth-limited search`_".
475
+
476
+ .. _PADS: http://www.ics.uci.edu/~eppstein/PADS
477
+ .. _Depth-limited search: https://en.wikipedia.org/wiki/Depth-limited_search
478
+
479
+ See Also
480
+ --------
481
+ dfs_edges
482
+ dfs_preorder_nodes
483
+ dfs_postorder_nodes
484
+ """
485
+ # Based on http://www.ics.uci.edu/~eppstein/PADS/DFS.py
486
+ # by D. Eppstein, July 2004.
487
+ if source is None:
488
+ # edges for all components
489
+ nodes = G
490
+ else:
491
+ # edges for components with source
492
+ nodes = [source]
493
+ if depth_limit is None:
494
+ depth_limit = len(G)
495
+
496
+ get_children = (
497
+ G.neighbors
498
+ if sort_neighbors is None
499
+ else lambda n: iter(sort_neighbors(G.neighbors(n)))
500
+ )
501
+
502
+ visited = set()
503
+ for start in nodes:
504
+ if start in visited:
505
+ continue
506
+ yield start, start, "forward"
507
+ visited.add(start)
508
+ stack = [(start, get_children(start))]
509
+ depth_now = 1
510
+ while stack:
511
+ parent, children = stack[-1]
512
+ for child in children:
513
+ if child in visited:
514
+ yield parent, child, "nontree"
515
+ else:
516
+ yield parent, child, "forward"
517
+ visited.add(child)
518
+ if depth_now < depth_limit:
519
+ stack.append((child, iter(get_children(child))))
520
+ depth_now += 1
521
+ break
522
+ else:
523
+ yield parent, child, "reverse-depth_limit"
524
+ else:
525
+ stack.pop()
526
+ depth_now -= 1
527
+ if stack:
528
+ yield stack[-1][0], parent, "reverse"
529
+ yield start, start, "reverse"
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/edgebfs.py ADDED
@@ -0,0 +1,178 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ =============================
3
+ Breadth First Search on Edges
4
+ =============================
5
+
6
+ Algorithms for a breadth-first traversal of edges in a graph.
7
+
8
+ """
9
+
10
+ from collections import deque
11
+
12
+ import networkx as nx
13
+
14
+ FORWARD = "forward"
15
+ REVERSE = "reverse"
16
+
17
+ __all__ = ["edge_bfs"]
18
+
19
+
20
+ @nx._dispatchable
21
+ def edge_bfs(G, source=None, orientation=None):
22
+ """A directed, breadth-first-search of edges in `G`, beginning at `source`.
23
+
24
+ Yield the edges of G in a breadth-first-search order continuing until
25
+ all edges are generated.
26
+
27
+ Parameters
28
+ ----------
29
+ G : graph
30
+ A directed/undirected graph/multigraph.
31
+
32
+ source : node, list of nodes
33
+ The node from which the traversal begins. If None, then a source
34
+ is chosen arbitrarily and repeatedly until all edges from each node in
35
+ the graph are searched.
36
+
37
+ orientation : None | 'original' | 'reverse' | 'ignore' (default: None)
38
+ For directed graphs and directed multigraphs, edge traversals need not
39
+ respect the original orientation of the edges.
40
+ When set to 'reverse' every edge is traversed in the reverse direction.
41
+ When set to 'ignore', every edge is treated as undirected.
42
+ When set to 'original', every edge is treated as directed.
43
+ In all three cases, the yielded edge tuples add a last entry to
44
+ indicate the direction in which that edge was traversed.
45
+ If orientation is None, the yielded edge has no direction indicated.
46
+ The direction is respected, but not reported.
47
+
48
+ Yields
49
+ ------
50
+ edge : directed edge
51
+ A directed edge indicating the path taken by the breadth-first-search.
52
+ For graphs, `edge` is of the form `(u, v)` where `u` and `v`
53
+ are the tail and head of the edge as determined by the traversal.
54
+ For multigraphs, `edge` is of the form `(u, v, key)`, where `key` is
55
+ the key of the edge. When the graph is directed, then `u` and `v`
56
+ are always in the order of the actual directed edge.
57
+ If orientation is not None then the edge tuple is extended to include
58
+ the direction of traversal ('forward' or 'reverse') on that edge.
59
+
60
+ Examples
61
+ --------
62
+ >>> nodes = [0, 1, 2, 3]
63
+ >>> edges = [(0, 1), (1, 0), (1, 0), (2, 0), (2, 1), (3, 1)]
64
+
65
+ >>> list(nx.edge_bfs(nx.Graph(edges), nodes))
66
+ [(0, 1), (0, 2), (1, 2), (1, 3)]
67
+
68
+ >>> list(nx.edge_bfs(nx.DiGraph(edges), nodes))
69
+ [(0, 1), (1, 0), (2, 0), (2, 1), (3, 1)]
70
+
71
+ >>> list(nx.edge_bfs(nx.MultiGraph(edges), nodes))
72
+ [(0, 1, 0), (0, 1, 1), (0, 1, 2), (0, 2, 0), (1, 2, 0), (1, 3, 0)]
73
+
74
+ >>> list(nx.edge_bfs(nx.MultiDiGraph(edges), nodes))
75
+ [(0, 1, 0), (1, 0, 0), (1, 0, 1), (2, 0, 0), (2, 1, 0), (3, 1, 0)]
76
+
77
+ >>> list(nx.edge_bfs(nx.DiGraph(edges), nodes, orientation="ignore"))
78
+ [(0, 1, 'forward'), (1, 0, 'reverse'), (2, 0, 'reverse'), (2, 1, 'reverse'), (3, 1, 'reverse')]
79
+
80
+ >>> list(nx.edge_bfs(nx.MultiDiGraph(edges), nodes, orientation="ignore"))
81
+ [(0, 1, 0, 'forward'), (1, 0, 0, 'reverse'), (1, 0, 1, 'reverse'), (2, 0, 0, 'reverse'), (2, 1, 0, 'reverse'), (3, 1, 0, 'reverse')]
82
+
83
+ Notes
84
+ -----
85
+ The goal of this function is to visit edges. It differs from the more
86
+ familiar breadth-first-search of nodes, as provided by
87
+ :func:`networkx.algorithms.traversal.breadth_first_search.bfs_edges`, in
88
+ that it does not stop once every node has been visited. In a directed graph
89
+ with edges [(0, 1), (1, 2), (2, 1)], the edge (2, 1) would not be visited
90
+ if not for the functionality provided by this function.
91
+
92
+ The naming of this function is very similar to bfs_edges. The difference
93
+ is that 'edge_bfs' yields edges even if they extend back to an already
94
+ explored node while 'bfs_edges' yields the edges of the tree that results
95
+ from a breadth-first-search (BFS) so no edges are reported if they extend
96
+ to already explored nodes. That means 'edge_bfs' reports all edges while
97
+ 'bfs_edges' only report those traversed by a node-based BFS. Yet another
98
+ description is that 'bfs_edges' reports the edges traversed during BFS
99
+ while 'edge_bfs' reports all edges in the order they are explored.
100
+
101
+ See Also
102
+ --------
103
+ bfs_edges
104
+ bfs_tree
105
+ edge_dfs
106
+
107
+ """
108
+ nodes = list(G.nbunch_iter(source))
109
+ if not nodes:
110
+ return
111
+
112
+ directed = G.is_directed()
113
+ kwds = {"data": False}
114
+ if G.is_multigraph() is True:
115
+ kwds["keys"] = True
116
+
117
+ # set up edge lookup
118
+ if orientation is None:
119
+
120
+ def edges_from(node):
121
+ return iter(G.edges(node, **kwds))
122
+
123
+ elif not directed or orientation == "original":
124
+
125
+ def edges_from(node):
126
+ for e in G.edges(node, **kwds):
127
+ yield e + (FORWARD,)
128
+
129
+ elif orientation == "reverse":
130
+
131
+ def edges_from(node):
132
+ for e in G.in_edges(node, **kwds):
133
+ yield e + (REVERSE,)
134
+
135
+ elif orientation == "ignore":
136
+
137
+ def edges_from(node):
138
+ for e in G.edges(node, **kwds):
139
+ yield e + (FORWARD,)
140
+ for e in G.in_edges(node, **kwds):
141
+ yield e + (REVERSE,)
142
+
143
+ else:
144
+ raise nx.NetworkXError("invalid orientation argument.")
145
+
146
+ if directed:
147
+ neighbors = G.successors
148
+
149
+ def edge_id(edge):
150
+ # remove direction indicator
151
+ return edge[:-1] if orientation is not None else edge
152
+
153
+ else:
154
+ neighbors = G.neighbors
155
+
156
+ def edge_id(edge):
157
+ return (frozenset(edge[:2]),) + edge[2:]
158
+
159
+ check_reverse = directed and orientation in ("reverse", "ignore")
160
+
161
+ # start BFS
162
+ visited_nodes = set(nodes)
163
+ visited_edges = set()
164
+ queue = deque([(n, edges_from(n)) for n in nodes])
165
+ while queue:
166
+ parent, children_edges = queue.popleft()
167
+ for edge in children_edges:
168
+ if check_reverse and edge[-1] == REVERSE:
169
+ child = edge[0]
170
+ else:
171
+ child = edge[1]
172
+ if child not in visited_nodes:
173
+ visited_nodes.add(child)
174
+ queue.append((child, edges_from(child)))
175
+ edgeid = edge_id(edge)
176
+ if edgeid not in visited_edges:
177
+ visited_edges.add(edgeid)
178
+ yield edge
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/edgedfs.py ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ ===========================
3
+ Depth First Search on Edges
4
+ ===========================
5
+
6
+ Algorithms for a depth-first traversal of edges in a graph.
7
+
8
+ """
9
+
10
+ import networkx as nx
11
+
12
+ FORWARD = "forward"
13
+ REVERSE = "reverse"
14
+
15
+ __all__ = ["edge_dfs"]
16
+
17
+
18
+ @nx._dispatchable
19
+ def edge_dfs(G, source=None, orientation=None):
20
+ """A directed, depth-first-search of edges in `G`, beginning at `source`.
21
+
22
+ Yield the edges of G in a depth-first-search order continuing until
23
+ all edges are generated.
24
+
25
+ Parameters
26
+ ----------
27
+ G : graph
28
+ A directed/undirected graph/multigraph.
29
+
30
+ source : node, list of nodes
31
+ The node from which the traversal begins. If None, then a source
32
+ is chosen arbitrarily and repeatedly until all edges from each node in
33
+ the graph are searched.
34
+
35
+ orientation : None | 'original' | 'reverse' | 'ignore' (default: None)
36
+ For directed graphs and directed multigraphs, edge traversals need not
37
+ respect the original orientation of the edges.
38
+ When set to 'reverse' every edge is traversed in the reverse direction.
39
+ When set to 'ignore', every edge is treated as undirected.
40
+ When set to 'original', every edge is treated as directed.
41
+ In all three cases, the yielded edge tuples add a last entry to
42
+ indicate the direction in which that edge was traversed.
43
+ If orientation is None, the yielded edge has no direction indicated.
44
+ The direction is respected, but not reported.
45
+
46
+ Yields
47
+ ------
48
+ edge : directed edge
49
+ A directed edge indicating the path taken by the depth-first traversal.
50
+ For graphs, `edge` is of the form `(u, v)` where `u` and `v`
51
+ are the tail and head of the edge as determined by the traversal.
52
+ For multigraphs, `edge` is of the form `(u, v, key)`, where `key` is
53
+ the key of the edge. When the graph is directed, then `u` and `v`
54
+ are always in the order of the actual directed edge.
55
+ If orientation is not None then the edge tuple is extended to include
56
+ the direction of traversal ('forward' or 'reverse') on that edge.
57
+
58
+ Examples
59
+ --------
60
+ >>> nodes = [0, 1, 2, 3]
61
+ >>> edges = [(0, 1), (1, 0), (1, 0), (2, 1), (3, 1)]
62
+
63
+ >>> list(nx.edge_dfs(nx.Graph(edges), nodes))
64
+ [(0, 1), (1, 2), (1, 3)]
65
+
66
+ >>> list(nx.edge_dfs(nx.DiGraph(edges), nodes))
67
+ [(0, 1), (1, 0), (2, 1), (3, 1)]
68
+
69
+ >>> list(nx.edge_dfs(nx.MultiGraph(edges), nodes))
70
+ [(0, 1, 0), (1, 0, 1), (0, 1, 2), (1, 2, 0), (1, 3, 0)]
71
+
72
+ >>> list(nx.edge_dfs(nx.MultiDiGraph(edges), nodes))
73
+ [(0, 1, 0), (1, 0, 0), (1, 0, 1), (2, 1, 0), (3, 1, 0)]
74
+
75
+ >>> list(nx.edge_dfs(nx.DiGraph(edges), nodes, orientation="ignore"))
76
+ [(0, 1, 'forward'), (1, 0, 'forward'), (2, 1, 'reverse'), (3, 1, 'reverse')]
77
+
78
+ >>> list(nx.edge_dfs(nx.MultiDiGraph(edges), nodes, orientation="ignore"))
79
+ [(0, 1, 0, 'forward'), (1, 0, 0, 'forward'), (1, 0, 1, 'reverse'), (2, 1, 0, 'reverse'), (3, 1, 0, 'reverse')]
80
+
81
+ Notes
82
+ -----
83
+ The goal of this function is to visit edges. It differs from the more
84
+ familiar depth-first traversal of nodes, as provided by
85
+ :func:`~networkx.algorithms.traversal.depth_first_search.dfs_edges`, in
86
+ that it does not stop once every node has been visited. In a directed graph
87
+ with edges [(0, 1), (1, 2), (2, 1)], the edge (2, 1) would not be visited
88
+ if not for the functionality provided by this function.
89
+
90
+ See Also
91
+ --------
92
+ :func:`~networkx.algorithms.traversal.depth_first_search.dfs_edges`
93
+
94
+ """
95
+ nodes = list(G.nbunch_iter(source))
96
+ if not nodes:
97
+ return
98
+
99
+ directed = G.is_directed()
100
+ kwds = {"data": False}
101
+ if G.is_multigraph() is True:
102
+ kwds["keys"] = True
103
+
104
+ # set up edge lookup
105
+ if orientation is None:
106
+
107
+ def edges_from(node):
108
+ return iter(G.edges(node, **kwds))
109
+
110
+ elif not directed or orientation == "original":
111
+
112
+ def edges_from(node):
113
+ for e in G.edges(node, **kwds):
114
+ yield e + (FORWARD,)
115
+
116
+ elif orientation == "reverse":
117
+
118
+ def edges_from(node):
119
+ for e in G.in_edges(node, **kwds):
120
+ yield e + (REVERSE,)
121
+
122
+ elif orientation == "ignore":
123
+
124
+ def edges_from(node):
125
+ for e in G.edges(node, **kwds):
126
+ yield e + (FORWARD,)
127
+ for e in G.in_edges(node, **kwds):
128
+ yield e + (REVERSE,)
129
+
130
+ else:
131
+ raise nx.NetworkXError("invalid orientation argument.")
132
+
133
+ # set up formation of edge_id to easily look up if edge already returned
134
+ if directed:
135
+
136
+ def edge_id(edge):
137
+ # remove direction indicator
138
+ return edge[:-1] if orientation is not None else edge
139
+
140
+ else:
141
+
142
+ def edge_id(edge):
143
+ # single id for undirected requires frozenset on nodes
144
+ return (frozenset(edge[:2]),) + edge[2:]
145
+
146
+ # Basic setup
147
+ check_reverse = directed and orientation in ("reverse", "ignore")
148
+
149
+ visited_edges = set()
150
+ visited_nodes = set()
151
+ edges = {}
152
+
153
+ # start DFS
154
+ for start_node in nodes:
155
+ stack = [start_node]
156
+ while stack:
157
+ current_node = stack[-1]
158
+ if current_node not in visited_nodes:
159
+ edges[current_node] = edges_from(current_node)
160
+ visited_nodes.add(current_node)
161
+
162
+ try:
163
+ edge = next(edges[current_node])
164
+ except StopIteration:
165
+ # No more edges from the current node.
166
+ stack.pop()
167
+ else:
168
+ edgeid = edge_id(edge)
169
+ if edgeid not in visited_edges:
170
+ visited_edges.add(edgeid)
171
+ # Mark the traversed "to" node as to-be-explored.
172
+ if check_reverse and edge[-1] == REVERSE:
173
+ stack.append(edge[0])
174
+ else:
175
+ stack.append(edge[1])
176
+ yield edge
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/tests/__init__.py ADDED
File without changes
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/tests/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (208 Bytes). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/tests/__pycache__/test_beamsearch.cpython-311.pyc ADDED
Binary file (1.64 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/tests/__pycache__/test_bfs.cpython-311.pyc ADDED
Binary file (14.2 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/tests/__pycache__/test_dfs.cpython-311.pyc ADDED
Binary file (17.4 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/tests/__pycache__/test_edgebfs.cpython-311.pyc ADDED
Binary file (9.32 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/tests/__pycache__/test_edgedfs.cpython-311.pyc ADDED
Binary file (8.46 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/tests/test_beamsearch.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Unit tests for the beam search functions."""
2
+
3
+ import pytest
4
+
5
+ import networkx as nx
6
+
7
+
8
+ def test_narrow():
9
+ """Tests that a narrow beam width may cause an incomplete search."""
10
+ # In this search, we enqueue only the neighbor 3 at the first
11
+ # step, then only the neighbor 2 at the second step. Once at
12
+ # node 2, the search chooses node 3, since it has a higher value
13
+ # than node 1, but node 3 has already been visited, so the
14
+ # search terminates.
15
+ G = nx.cycle_graph(4)
16
+ edges = nx.bfs_beam_edges(G, source=0, value=lambda n: n, width=1)
17
+ assert list(edges) == [(0, 3), (3, 2)]
18
+
19
+
20
+ @pytest.mark.parametrize("width", (2, None))
21
+ def test_wide(width):
22
+ """All nodes are searched when `width` is None or >= max degree"""
23
+ G = nx.cycle_graph(4)
24
+ edges = nx.bfs_beam_edges(G, source=0, value=lambda n: n, width=width)
25
+ assert list(edges) == [(0, 3), (0, 1), (3, 2)]
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/tests/test_bfs.py ADDED
@@ -0,0 +1,203 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from functools import partial
2
+
3
+ import pytest
4
+
5
+ import networkx as nx
6
+
7
+
8
+ class TestBFS:
9
+ @classmethod
10
+ def setup_class(cls):
11
+ # simple graph
12
+ G = nx.Graph()
13
+ G.add_edges_from([(0, 1), (1, 2), (1, 3), (2, 4), (3, 4)])
14
+ cls.G = G
15
+
16
+ def test_successor(self):
17
+ assert dict(nx.bfs_successors(self.G, source=0)) == {0: [1], 1: [2, 3], 2: [4]}
18
+
19
+ def test_predecessor(self):
20
+ assert dict(nx.bfs_predecessors(self.G, source=0)) == {1: 0, 2: 1, 3: 1, 4: 2}
21
+
22
+ def test_bfs_tree(self):
23
+ T = nx.bfs_tree(self.G, source=0)
24
+ assert sorted(T.nodes()) == sorted(self.G.nodes())
25
+ assert sorted(T.edges()) == [(0, 1), (1, 2), (1, 3), (2, 4)]
26
+
27
+ def test_bfs_edges(self):
28
+ edges = nx.bfs_edges(self.G, source=0)
29
+ assert list(edges) == [(0, 1), (1, 2), (1, 3), (2, 4)]
30
+
31
+ def test_bfs_edges_reverse(self):
32
+ D = nx.DiGraph()
33
+ D.add_edges_from([(0, 1), (1, 2), (1, 3), (2, 4), (3, 4)])
34
+ edges = nx.bfs_edges(D, source=4, reverse=True)
35
+ assert list(edges) == [(4, 2), (4, 3), (2, 1), (1, 0)]
36
+
37
+ def test_bfs_edges_sorting(self):
38
+ D = nx.DiGraph()
39
+ D.add_edges_from([(0, 1), (0, 2), (1, 4), (1, 3), (2, 5)])
40
+ sort_desc = partial(sorted, reverse=True)
41
+ edges_asc = nx.bfs_edges(D, source=0, sort_neighbors=sorted)
42
+ edges_desc = nx.bfs_edges(D, source=0, sort_neighbors=sort_desc)
43
+ assert list(edges_asc) == [(0, 1), (0, 2), (1, 3), (1, 4), (2, 5)]
44
+ assert list(edges_desc) == [(0, 2), (0, 1), (2, 5), (1, 4), (1, 3)]
45
+
46
+ def test_bfs_tree_isolates(self):
47
+ G = nx.Graph()
48
+ G.add_node(1)
49
+ G.add_node(2)
50
+ T = nx.bfs_tree(G, source=1)
51
+ assert sorted(T.nodes()) == [1]
52
+ assert sorted(T.edges()) == []
53
+
54
+ def test_bfs_layers(self):
55
+ expected = {
56
+ 0: [0],
57
+ 1: [1],
58
+ 2: [2, 3],
59
+ 3: [4],
60
+ }
61
+ assert dict(enumerate(nx.bfs_layers(self.G, sources=[0]))) == expected
62
+ assert dict(enumerate(nx.bfs_layers(self.G, sources=0))) == expected
63
+
64
+ def test_bfs_layers_missing_source(self):
65
+ with pytest.raises(nx.NetworkXError):
66
+ next(nx.bfs_layers(self.G, sources="abc"))
67
+ with pytest.raises(nx.NetworkXError):
68
+ next(nx.bfs_layers(self.G, sources=["abc"]))
69
+
70
+ def test_descendants_at_distance(self):
71
+ for distance, descendants in enumerate([{0}, {1}, {2, 3}, {4}]):
72
+ assert nx.descendants_at_distance(self.G, 0, distance) == descendants
73
+
74
+ def test_descendants_at_distance_missing_source(self):
75
+ with pytest.raises(nx.NetworkXError):
76
+ nx.descendants_at_distance(self.G, "abc", 0)
77
+
78
+ def test_bfs_labeled_edges_directed(self):
79
+ D = nx.cycle_graph(5, create_using=nx.DiGraph)
80
+ expected = [
81
+ (0, 1, "tree"),
82
+ (1, 2, "tree"),
83
+ (2, 3, "tree"),
84
+ (3, 4, "tree"),
85
+ (4, 0, "reverse"),
86
+ ]
87
+ answer = list(nx.bfs_labeled_edges(D, 0))
88
+ assert expected == answer
89
+
90
+ D.add_edge(4, 4)
91
+ expected.append((4, 4, "level"))
92
+ answer = list(nx.bfs_labeled_edges(D, 0))
93
+ assert expected == answer
94
+
95
+ D.add_edge(0, 2)
96
+ D.add_edge(1, 5)
97
+ D.add_edge(2, 5)
98
+ D.remove_edge(4, 4)
99
+ expected = [
100
+ (0, 1, "tree"),
101
+ (0, 2, "tree"),
102
+ (1, 2, "level"),
103
+ (1, 5, "tree"),
104
+ (2, 3, "tree"),
105
+ (2, 5, "forward"),
106
+ (3, 4, "tree"),
107
+ (4, 0, "reverse"),
108
+ ]
109
+ answer = list(nx.bfs_labeled_edges(D, 0))
110
+ assert expected == answer
111
+
112
+ G = D.to_undirected()
113
+ G.add_edge(4, 4)
114
+ expected = [
115
+ (0, 1, "tree"),
116
+ (0, 2, "tree"),
117
+ (0, 4, "tree"),
118
+ (1, 2, "level"),
119
+ (1, 5, "tree"),
120
+ (2, 3, "tree"),
121
+ (2, 5, "forward"),
122
+ (4, 3, "forward"),
123
+ (4, 4, "level"),
124
+ ]
125
+ answer = list(nx.bfs_labeled_edges(G, 0))
126
+ assert expected == answer
127
+
128
+
129
+ class TestBreadthLimitedSearch:
130
+ @classmethod
131
+ def setup_class(cls):
132
+ # a tree
133
+ G = nx.Graph()
134
+ nx.add_path(G, [0, 1, 2, 3, 4, 5, 6])
135
+ nx.add_path(G, [2, 7, 8, 9, 10])
136
+ cls.G = G
137
+ # a disconnected graph
138
+ D = nx.Graph()
139
+ D.add_edges_from([(0, 1), (2, 3)])
140
+ nx.add_path(D, [2, 7, 8, 9, 10])
141
+ cls.D = D
142
+
143
+ def test_limited_bfs_successor(self):
144
+ assert dict(nx.bfs_successors(self.G, source=1, depth_limit=3)) == {
145
+ 1: [0, 2],
146
+ 2: [3, 7],
147
+ 3: [4],
148
+ 7: [8],
149
+ }
150
+ result = {
151
+ n: sorted(s) for n, s in nx.bfs_successors(self.D, source=7, depth_limit=2)
152
+ }
153
+ assert result == {8: [9], 2: [3], 7: [2, 8]}
154
+
155
+ def test_limited_bfs_predecessor(self):
156
+ assert dict(nx.bfs_predecessors(self.G, source=1, depth_limit=3)) == {
157
+ 0: 1,
158
+ 2: 1,
159
+ 3: 2,
160
+ 4: 3,
161
+ 7: 2,
162
+ 8: 7,
163
+ }
164
+ assert dict(nx.bfs_predecessors(self.D, source=7, depth_limit=2)) == {
165
+ 2: 7,
166
+ 3: 2,
167
+ 8: 7,
168
+ 9: 8,
169
+ }
170
+
171
+ def test_limited_bfs_tree(self):
172
+ T = nx.bfs_tree(self.G, source=3, depth_limit=1)
173
+ assert sorted(T.edges()) == [(3, 2), (3, 4)]
174
+
175
+ def test_limited_bfs_edges(self):
176
+ edges = nx.bfs_edges(self.G, source=9, depth_limit=4)
177
+ assert list(edges) == [(9, 8), (9, 10), (8, 7), (7, 2), (2, 1), (2, 3)]
178
+
179
+ def test_limited_bfs_layers(self):
180
+ assert dict(enumerate(nx.bfs_layers(self.G, sources=[0]))) == {
181
+ 0: [0],
182
+ 1: [1],
183
+ 2: [2],
184
+ 3: [3, 7],
185
+ 4: [4, 8],
186
+ 5: [5, 9],
187
+ 6: [6, 10],
188
+ }
189
+ assert dict(enumerate(nx.bfs_layers(self.D, sources=2))) == {
190
+ 0: [2],
191
+ 1: [3, 7],
192
+ 2: [8],
193
+ 3: [9],
194
+ 4: [10],
195
+ }
196
+
197
+ def test_limited_descendants_at_distance(self):
198
+ for distance, descendants in enumerate(
199
+ [{0}, {1}, {2}, {3, 7}, {4, 8}, {5, 9}, {6, 10}]
200
+ ):
201
+ assert nx.descendants_at_distance(self.G, 0, distance) == descendants
202
+ for distance, descendants in enumerate([{2}, {3, 7}, {8}, {9}, {10}]):
203
+ assert nx.descendants_at_distance(self.D, 2, distance) == descendants