xiaoanyu123 commited on
Commit
8358296
·
verified ·
1 Parent(s): 902e892

Add files using upload-large-folder tool

Browse files
Files changed (20) hide show
  1. pythonProject/.venv/Lib/site-packages/networkx/algorithms/assortativity/__pycache__/connectivity.cpython-310.pyc +0 -0
  2. pythonProject/.venv/Lib/site-packages/networkx/algorithms/assortativity/__pycache__/correlation.cpython-310.pyc +0 -0
  3. pythonProject/.venv/Lib/site-packages/networkx/algorithms/assortativity/__pycache__/mixing.cpython-310.pyc +0 -0
  4. pythonProject/.venv/Lib/site-packages/networkx/algorithms/assortativity/__pycache__/neighbor_degree.cpython-310.pyc +0 -0
  5. pythonProject/.venv/Lib/site-packages/networkx/algorithms/assortativity/tests/__pycache__/__init__.cpython-310.pyc +0 -0
  6. pythonProject/.venv/Lib/site-packages/networkx/algorithms/assortativity/tests/__pycache__/test_neighbor_degree.cpython-310.pyc +0 -0
  7. pythonProject/.venv/Lib/site-packages/networkx/algorithms/assortativity/tests/__pycache__/test_pairs.cpython-310.pyc +0 -0
  8. pythonProject/.venv/Lib/site-packages/networkx/algorithms/efficiency_measures.py +168 -0
  9. pythonProject/.venv/Lib/site-packages/networkx/algorithms/euler.py +469 -0
  10. pythonProject/.venv/Lib/site-packages/networkx/algorithms/graph_hashing.py +322 -0
  11. pythonProject/.venv/Lib/site-packages/networkx/algorithms/graphical.py +483 -0
  12. pythonProject/.venv/Lib/site-packages/networkx/algorithms/hierarchy.py +48 -0
  13. pythonProject/.venv/Lib/site-packages/networkx/algorithms/hybrid.py +195 -0
  14. pythonProject/.venv/Lib/site-packages/networkx/algorithms/isolate.py +107 -0
  15. pythonProject/.venv/Lib/site-packages/networkx/algorithms/link_prediction.py +688 -0
  16. pythonProject/.venv/Lib/site-packages/networkx/algorithms/lowest_common_ancestors.py +268 -0
  17. pythonProject/.venv/Lib/site-packages/networkx/algorithms/matching.py +1151 -0
  18. pythonProject/.venv/Lib/site-packages/networkx/algorithms/mis.py +77 -0
  19. pythonProject/.venv/Lib/site-packages/networkx/algorithms/moral.py +59 -0
  20. pythonProject/.venv/Lib/site-packages/networkx/algorithms/node_classification.py +218 -0
pythonProject/.venv/Lib/site-packages/networkx/algorithms/assortativity/__pycache__/connectivity.cpython-310.pyc ADDED
Binary file (4.02 kB). View file
 
pythonProject/.venv/Lib/site-packages/networkx/algorithms/assortativity/__pycache__/correlation.cpython-310.pyc ADDED
Binary file (9.27 kB). View file
 
pythonProject/.venv/Lib/site-packages/networkx/algorithms/assortativity/__pycache__/mixing.cpython-310.pyc ADDED
Binary file (7.6 kB). View file
 
pythonProject/.venv/Lib/site-packages/networkx/algorithms/assortativity/__pycache__/neighbor_degree.cpython-310.pyc ADDED
Binary file (4.7 kB). View file
 
pythonProject/.venv/Lib/site-packages/networkx/algorithms/assortativity/tests/__pycache__/__init__.cpython-310.pyc ADDED
Binary file (196 Bytes). View file
 
pythonProject/.venv/Lib/site-packages/networkx/algorithms/assortativity/tests/__pycache__/test_neighbor_degree.cpython-310.pyc ADDED
Binary file (3.49 kB). View file
 
pythonProject/.venv/Lib/site-packages/networkx/algorithms/assortativity/tests/__pycache__/test_pairs.cpython-310.pyc ADDED
Binary file (3.68 kB). View file
 
pythonProject/.venv/Lib/site-packages/networkx/algorithms/efficiency_measures.py ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Provides functions for computing the efficiency of nodes and graphs."""
2
+
3
+ import networkx as nx
4
+ from networkx.exception import NetworkXNoPath
5
+
6
+ from ..utils import not_implemented_for
7
+
8
+ __all__ = ["efficiency", "local_efficiency", "global_efficiency"]
9
+
10
+
11
+ @not_implemented_for("directed")
12
+ @nx._dispatchable
13
+ def efficiency(G, u, v):
14
+ """Returns the efficiency of a pair of nodes in a graph.
15
+
16
+ The *efficiency* of a pair of nodes is the multiplicative inverse of the
17
+ shortest path distance between the nodes [1]_. Returns 0 if no path
18
+ between nodes.
19
+
20
+ Parameters
21
+ ----------
22
+ G : :class:`networkx.Graph`
23
+ An undirected graph for which to compute the average local efficiency.
24
+ u, v : node
25
+ Nodes in the graph ``G``.
26
+
27
+ Returns
28
+ -------
29
+ float
30
+ Multiplicative inverse of the shortest path distance between the nodes.
31
+
32
+ Examples
33
+ --------
34
+ >>> G = nx.Graph([(0, 1), (0, 2), (0, 3), (1, 2), (1, 3)])
35
+ >>> nx.efficiency(G, 2, 3) # this gives efficiency for node 2 and 3
36
+ 0.5
37
+
38
+ Notes
39
+ -----
40
+ Edge weights are ignored when computing the shortest path distances.
41
+
42
+ See also
43
+ --------
44
+ local_efficiency
45
+ global_efficiency
46
+
47
+ References
48
+ ----------
49
+ .. [1] Latora, Vito, and Massimo Marchiori.
50
+ "Efficient behavior of small-world networks."
51
+ *Physical Review Letters* 87.19 (2001): 198701.
52
+ <https://doi.org/10.1103/PhysRevLett.87.198701>
53
+
54
+ """
55
+ try:
56
+ eff = 1 / nx.shortest_path_length(G, u, v)
57
+ except NetworkXNoPath:
58
+ eff = 0
59
+ return eff
60
+
61
+
62
+ @not_implemented_for("directed")
63
+ @nx._dispatchable
64
+ def global_efficiency(G):
65
+ """Returns the average global efficiency of the graph.
66
+
67
+ The *efficiency* of a pair of nodes in a graph is the multiplicative
68
+ inverse of the shortest path distance between the nodes. The *average
69
+ global efficiency* of a graph is the average efficiency of all pairs of
70
+ nodes [1]_.
71
+
72
+ Parameters
73
+ ----------
74
+ G : :class:`networkx.Graph`
75
+ An undirected graph for which to compute the average global efficiency.
76
+
77
+ Returns
78
+ -------
79
+ float
80
+ The average global efficiency of the graph.
81
+
82
+ Examples
83
+ --------
84
+ >>> G = nx.Graph([(0, 1), (0, 2), (0, 3), (1, 2), (1, 3)])
85
+ >>> round(nx.global_efficiency(G), 12)
86
+ 0.916666666667
87
+
88
+ Notes
89
+ -----
90
+ Edge weights are ignored when computing the shortest path distances.
91
+
92
+ See also
93
+ --------
94
+ local_efficiency
95
+
96
+ References
97
+ ----------
98
+ .. [1] Latora, Vito, and Massimo Marchiori.
99
+ "Efficient behavior of small-world networks."
100
+ *Physical Review Letters* 87.19 (2001): 198701.
101
+ <https://doi.org/10.1103/PhysRevLett.87.198701>
102
+
103
+ """
104
+ n = len(G)
105
+ denom = n * (n - 1)
106
+ if denom != 0:
107
+ lengths = nx.all_pairs_shortest_path_length(G)
108
+ g_eff = 0
109
+ for source, targets in lengths:
110
+ for target, distance in targets.items():
111
+ if distance > 0:
112
+ g_eff += 1 / distance
113
+ g_eff /= denom
114
+ # g_eff = sum(1 / d for s, tgts in lengths
115
+ # for t, d in tgts.items() if d > 0) / denom
116
+ else:
117
+ g_eff = 0
118
+ # TODO This can be made more efficient by computing all pairs shortest
119
+ # path lengths in parallel.
120
+ return g_eff
121
+
122
+
123
+ @not_implemented_for("directed")
124
+ @nx._dispatchable
125
+ def local_efficiency(G):
126
+ """Returns the average local efficiency of the graph.
127
+
128
+ The *efficiency* of a pair of nodes in a graph is the multiplicative
129
+ inverse of the shortest path distance between the nodes. The *local
130
+ efficiency* of a node in the graph is the average global efficiency of the
131
+ subgraph induced by the neighbors of the node. The *average local
132
+ efficiency* is the average of the local efficiencies of each node [1]_.
133
+
134
+ Parameters
135
+ ----------
136
+ G : :class:`networkx.Graph`
137
+ An undirected graph for which to compute the average local efficiency.
138
+
139
+ Returns
140
+ -------
141
+ float
142
+ The average local efficiency of the graph.
143
+
144
+ Examples
145
+ --------
146
+ >>> G = nx.Graph([(0, 1), (0, 2), (0, 3), (1, 2), (1, 3)])
147
+ >>> nx.local_efficiency(G)
148
+ 0.9166666666666667
149
+
150
+ Notes
151
+ -----
152
+ Edge weights are ignored when computing the shortest path distances.
153
+
154
+ See also
155
+ --------
156
+ global_efficiency
157
+
158
+ References
159
+ ----------
160
+ .. [1] Latora, Vito, and Massimo Marchiori.
161
+ "Efficient behavior of small-world networks."
162
+ *Physical Review Letters* 87.19 (2001): 198701.
163
+ <https://doi.org/10.1103/PhysRevLett.87.198701>
164
+
165
+ """
166
+ # TODO This summation can be trivially parallelized.
167
+ efficiency_list = (global_efficiency(G.subgraph(G[v])) for v in G)
168
+ return sum(efficiency_list) / len(G)
pythonProject/.venv/Lib/site-packages/networkx/algorithms/euler.py ADDED
@@ -0,0 +1,469 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Eulerian circuits and graphs.
3
+ """
4
+ from itertools import combinations
5
+
6
+ import networkx as nx
7
+
8
+ from ..utils import arbitrary_element, not_implemented_for
9
+
10
+ __all__ = [
11
+ "is_eulerian",
12
+ "eulerian_circuit",
13
+ "eulerize",
14
+ "is_semieulerian",
15
+ "has_eulerian_path",
16
+ "eulerian_path",
17
+ ]
18
+
19
+
20
+ @nx._dispatchable
21
+ def is_eulerian(G):
22
+ """Returns True if and only if `G` is Eulerian.
23
+
24
+ A graph is *Eulerian* if it has an Eulerian circuit. An *Eulerian
25
+ circuit* is a closed walk that includes each edge of a graph exactly
26
+ once.
27
+
28
+ Graphs with isolated vertices (i.e. vertices with zero degree) are not
29
+ considered to have Eulerian circuits. Therefore, if the graph is not
30
+ connected (or not strongly connected, for directed graphs), this function
31
+ returns False.
32
+
33
+ Parameters
34
+ ----------
35
+ G : NetworkX graph
36
+ A graph, either directed or undirected.
37
+
38
+ Examples
39
+ --------
40
+ >>> nx.is_eulerian(nx.DiGraph({0: [3], 1: [2], 2: [3], 3: [0, 1]}))
41
+ True
42
+ >>> nx.is_eulerian(nx.complete_graph(5))
43
+ True
44
+ >>> nx.is_eulerian(nx.petersen_graph())
45
+ False
46
+
47
+ If you prefer to allow graphs with isolated vertices to have Eulerian circuits,
48
+ you can first remove such vertices and then call `is_eulerian` as below example shows.
49
+
50
+ >>> G = nx.Graph([(0, 1), (1, 2), (0, 2)])
51
+ >>> G.add_node(3)
52
+ >>> nx.is_eulerian(G)
53
+ False
54
+
55
+ >>> G.remove_nodes_from(list(nx.isolates(G)))
56
+ >>> nx.is_eulerian(G)
57
+ True
58
+
59
+
60
+ """
61
+ if G.is_directed():
62
+ # Every node must have equal in degree and out degree and the
63
+ # graph must be strongly connected
64
+ return all(
65
+ G.in_degree(n) == G.out_degree(n) for n in G
66
+ ) and nx.is_strongly_connected(G)
67
+ # An undirected Eulerian graph has no vertices of odd degree and
68
+ # must be connected.
69
+ return all(d % 2 == 0 for v, d in G.degree()) and nx.is_connected(G)
70
+
71
+
72
+ @nx._dispatchable
73
+ def is_semieulerian(G):
74
+ """Return True iff `G` is semi-Eulerian.
75
+
76
+ G is semi-Eulerian if it has an Eulerian path but no Eulerian circuit.
77
+
78
+ See Also
79
+ --------
80
+ has_eulerian_path
81
+ is_eulerian
82
+ """
83
+ return has_eulerian_path(G) and not is_eulerian(G)
84
+
85
+
86
+ def _find_path_start(G):
87
+ """Return a suitable starting vertex for an Eulerian path.
88
+
89
+ If no path exists, return None.
90
+ """
91
+ if not has_eulerian_path(G):
92
+ return None
93
+
94
+ if is_eulerian(G):
95
+ return arbitrary_element(G)
96
+
97
+ if G.is_directed():
98
+ v1, v2 = (v for v in G if G.in_degree(v) != G.out_degree(v))
99
+ # Determines which is the 'start' node (as opposed to the 'end')
100
+ if G.out_degree(v1) > G.in_degree(v1):
101
+ return v1
102
+ else:
103
+ return v2
104
+
105
+ else:
106
+ # In an undirected graph randomly choose one of the possibilities
107
+ start = [v for v in G if G.degree(v) % 2 != 0][0]
108
+ return start
109
+
110
+
111
+ def _simplegraph_eulerian_circuit(G, source):
112
+ if G.is_directed():
113
+ degree = G.out_degree
114
+ edges = G.out_edges
115
+ else:
116
+ degree = G.degree
117
+ edges = G.edges
118
+ vertex_stack = [source]
119
+ last_vertex = None
120
+ while vertex_stack:
121
+ current_vertex = vertex_stack[-1]
122
+ if degree(current_vertex) == 0:
123
+ if last_vertex is not None:
124
+ yield (last_vertex, current_vertex)
125
+ last_vertex = current_vertex
126
+ vertex_stack.pop()
127
+ else:
128
+ _, next_vertex = arbitrary_element(edges(current_vertex))
129
+ vertex_stack.append(next_vertex)
130
+ G.remove_edge(current_vertex, next_vertex)
131
+
132
+
133
+ def _multigraph_eulerian_circuit(G, source):
134
+ if G.is_directed():
135
+ degree = G.out_degree
136
+ edges = G.out_edges
137
+ else:
138
+ degree = G.degree
139
+ edges = G.edges
140
+ vertex_stack = [(source, None)]
141
+ last_vertex = None
142
+ last_key = None
143
+ while vertex_stack:
144
+ current_vertex, current_key = vertex_stack[-1]
145
+ if degree(current_vertex) == 0:
146
+ if last_vertex is not None:
147
+ yield (last_vertex, current_vertex, last_key)
148
+ last_vertex, last_key = current_vertex, current_key
149
+ vertex_stack.pop()
150
+ else:
151
+ triple = arbitrary_element(edges(current_vertex, keys=True))
152
+ _, next_vertex, next_key = triple
153
+ vertex_stack.append((next_vertex, next_key))
154
+ G.remove_edge(current_vertex, next_vertex, next_key)
155
+
156
+
157
+ @nx._dispatchable
158
+ def eulerian_circuit(G, source=None, keys=False):
159
+ """Returns an iterator over the edges of an Eulerian circuit in `G`.
160
+
161
+ An *Eulerian circuit* is a closed walk that includes each edge of a
162
+ graph exactly once.
163
+
164
+ Parameters
165
+ ----------
166
+ G : NetworkX graph
167
+ A graph, either directed or undirected.
168
+
169
+ source : node, optional
170
+ Starting node for circuit.
171
+
172
+ keys : bool
173
+ If False, edges generated by this function will be of the form
174
+ ``(u, v)``. Otherwise, edges will be of the form ``(u, v, k)``.
175
+ This option is ignored unless `G` is a multigraph.
176
+
177
+ Returns
178
+ -------
179
+ edges : iterator
180
+ An iterator over edges in the Eulerian circuit.
181
+
182
+ Raises
183
+ ------
184
+ NetworkXError
185
+ If the graph is not Eulerian.
186
+
187
+ See Also
188
+ --------
189
+ is_eulerian
190
+
191
+ Notes
192
+ -----
193
+ This is a linear time implementation of an algorithm adapted from [1]_.
194
+
195
+ For general information about Euler tours, see [2]_.
196
+
197
+ References
198
+ ----------
199
+ .. [1] J. Edmonds, E. L. Johnson.
200
+ Matching, Euler tours and the Chinese postman.
201
+ Mathematical programming, Volume 5, Issue 1 (1973), 111-114.
202
+ .. [2] https://en.wikipedia.org/wiki/Eulerian_path
203
+
204
+ Examples
205
+ --------
206
+ To get an Eulerian circuit in an undirected graph::
207
+
208
+ >>> G = nx.complete_graph(3)
209
+ >>> list(nx.eulerian_circuit(G))
210
+ [(0, 2), (2, 1), (1, 0)]
211
+ >>> list(nx.eulerian_circuit(G, source=1))
212
+ [(1, 2), (2, 0), (0, 1)]
213
+
214
+ To get the sequence of vertices in an Eulerian circuit::
215
+
216
+ >>> [u for u, v in nx.eulerian_circuit(G)]
217
+ [0, 2, 1]
218
+
219
+ """
220
+ if not is_eulerian(G):
221
+ raise nx.NetworkXError("G is not Eulerian.")
222
+ if G.is_directed():
223
+ G = G.reverse()
224
+ else:
225
+ G = G.copy()
226
+ if source is None:
227
+ source = arbitrary_element(G)
228
+ if G.is_multigraph():
229
+ for u, v, k in _multigraph_eulerian_circuit(G, source):
230
+ if keys:
231
+ yield u, v, k
232
+ else:
233
+ yield u, v
234
+ else:
235
+ yield from _simplegraph_eulerian_circuit(G, source)
236
+
237
+
238
+ @nx._dispatchable
239
+ def has_eulerian_path(G, source=None):
240
+ """Return True iff `G` has an Eulerian path.
241
+
242
+ An Eulerian path is a path in a graph which uses each edge of a graph
243
+ exactly once. If `source` is specified, then this function checks
244
+ whether an Eulerian path that starts at node `source` exists.
245
+
246
+ A directed graph has an Eulerian path iff:
247
+ - at most one vertex has out_degree - in_degree = 1,
248
+ - at most one vertex has in_degree - out_degree = 1,
249
+ - every other vertex has equal in_degree and out_degree,
250
+ - and all of its vertices belong to a single connected
251
+ component of the underlying undirected graph.
252
+
253
+ If `source` is not None, an Eulerian path starting at `source` exists if no
254
+ other node has out_degree - in_degree = 1. This is equivalent to either
255
+ there exists an Eulerian circuit or `source` has out_degree - in_degree = 1
256
+ and the conditions above hold.
257
+
258
+ An undirected graph has an Eulerian path iff:
259
+ - exactly zero or two vertices have odd degree,
260
+ - and all of its vertices belong to a single connected component.
261
+
262
+ If `source` is not None, an Eulerian path starting at `source` exists if
263
+ either there exists an Eulerian circuit or `source` has an odd degree and the
264
+ conditions above hold.
265
+
266
+ Graphs with isolated vertices (i.e. vertices with zero degree) are not considered
267
+ to have an Eulerian path. Therefore, if the graph is not connected (or not strongly
268
+ connected, for directed graphs), this function returns False.
269
+
270
+ Parameters
271
+ ----------
272
+ G : NetworkX Graph
273
+ The graph to find an euler path in.
274
+
275
+ source : node, optional
276
+ Starting node for path.
277
+
278
+ Returns
279
+ -------
280
+ Bool : True if G has an Eulerian path.
281
+
282
+ Examples
283
+ --------
284
+ If you prefer to allow graphs with isolated vertices to have Eulerian path,
285
+ you can first remove such vertices and then call `has_eulerian_path` as below example shows.
286
+
287
+ >>> G = nx.Graph([(0, 1), (1, 2), (0, 2)])
288
+ >>> G.add_node(3)
289
+ >>> nx.has_eulerian_path(G)
290
+ False
291
+
292
+ >>> G.remove_nodes_from(list(nx.isolates(G)))
293
+ >>> nx.has_eulerian_path(G)
294
+ True
295
+
296
+ See Also
297
+ --------
298
+ is_eulerian
299
+ eulerian_path
300
+ """
301
+ if nx.is_eulerian(G):
302
+ return True
303
+
304
+ if G.is_directed():
305
+ ins = G.in_degree
306
+ outs = G.out_degree
307
+ # Since we know it is not eulerian, outs - ins must be 1 for source
308
+ if source is not None and outs[source] - ins[source] != 1:
309
+ return False
310
+
311
+ unbalanced_ins = 0
312
+ unbalanced_outs = 0
313
+ for v in G:
314
+ if ins[v] - outs[v] == 1:
315
+ unbalanced_ins += 1
316
+ elif outs[v] - ins[v] == 1:
317
+ unbalanced_outs += 1
318
+ elif ins[v] != outs[v]:
319
+ return False
320
+
321
+ return (
322
+ unbalanced_ins <= 1 and unbalanced_outs <= 1 and nx.is_weakly_connected(G)
323
+ )
324
+ else:
325
+ # We know it is not eulerian, so degree of source must be odd.
326
+ if source is not None and G.degree[source] % 2 != 1:
327
+ return False
328
+
329
+ # Sum is 2 since we know it is not eulerian (which implies sum is 0)
330
+ return sum(d % 2 == 1 for v, d in G.degree()) == 2 and nx.is_connected(G)
331
+
332
+
333
+ @nx._dispatchable
334
+ def eulerian_path(G, source=None, keys=False):
335
+ """Return an iterator over the edges of an Eulerian path in `G`.
336
+
337
+ Parameters
338
+ ----------
339
+ G : NetworkX Graph
340
+ The graph in which to look for an eulerian path.
341
+ source : node or None (default: None)
342
+ The node at which to start the search. None means search over all
343
+ starting nodes.
344
+ keys : Bool (default: False)
345
+ Indicates whether to yield edge 3-tuples (u, v, edge_key).
346
+ The default yields edge 2-tuples
347
+
348
+ Yields
349
+ ------
350
+ Edge tuples along the eulerian path.
351
+
352
+ Warning: If `source` provided is not the start node of an Euler path
353
+ will raise error even if an Euler Path exists.
354
+ """
355
+ if not has_eulerian_path(G, source):
356
+ raise nx.NetworkXError("Graph has no Eulerian paths.")
357
+ if G.is_directed():
358
+ G = G.reverse()
359
+ if source is None or nx.is_eulerian(G) is False:
360
+ source = _find_path_start(G)
361
+ if G.is_multigraph():
362
+ for u, v, k in _multigraph_eulerian_circuit(G, source):
363
+ if keys:
364
+ yield u, v, k
365
+ else:
366
+ yield u, v
367
+ else:
368
+ yield from _simplegraph_eulerian_circuit(G, source)
369
+ else:
370
+ G = G.copy()
371
+ if source is None:
372
+ source = _find_path_start(G)
373
+ if G.is_multigraph():
374
+ if keys:
375
+ yield from reversed(
376
+ [(v, u, k) for u, v, k in _multigraph_eulerian_circuit(G, source)]
377
+ )
378
+ else:
379
+ yield from reversed(
380
+ [(v, u) for u, v, k in _multigraph_eulerian_circuit(G, source)]
381
+ )
382
+ else:
383
+ yield from reversed(
384
+ [(v, u) for u, v in _simplegraph_eulerian_circuit(G, source)]
385
+ )
386
+
387
+
388
+ @not_implemented_for("directed")
389
+ @nx._dispatchable(returns_graph=True)
390
+ def eulerize(G):
391
+ """Transforms a graph into an Eulerian graph.
392
+
393
+ If `G` is Eulerian the result is `G` as a MultiGraph, otherwise the result is a smallest
394
+ (in terms of the number of edges) multigraph whose underlying simple graph is `G`.
395
+
396
+ Parameters
397
+ ----------
398
+ G : NetworkX graph
399
+ An undirected graph
400
+
401
+ Returns
402
+ -------
403
+ G : NetworkX multigraph
404
+
405
+ Raises
406
+ ------
407
+ NetworkXError
408
+ If the graph is not connected.
409
+
410
+ See Also
411
+ --------
412
+ is_eulerian
413
+ eulerian_circuit
414
+
415
+ References
416
+ ----------
417
+ .. [1] J. Edmonds, E. L. Johnson.
418
+ Matching, Euler tours and the Chinese postman.
419
+ Mathematical programming, Volume 5, Issue 1 (1973), 111-114.
420
+ .. [2] https://en.wikipedia.org/wiki/Eulerian_path
421
+ .. [3] http://web.math.princeton.edu/math_alive/5/Notes1.pdf
422
+
423
+ Examples
424
+ --------
425
+ >>> G = nx.complete_graph(10)
426
+ >>> H = nx.eulerize(G)
427
+ >>> nx.is_eulerian(H)
428
+ True
429
+
430
+ """
431
+ if G.order() == 0:
432
+ raise nx.NetworkXPointlessConcept("Cannot Eulerize null graph")
433
+ if not nx.is_connected(G):
434
+ raise nx.NetworkXError("G is not connected")
435
+ odd_degree_nodes = [n for n, d in G.degree() if d % 2 == 1]
436
+ G = nx.MultiGraph(G)
437
+ if len(odd_degree_nodes) == 0:
438
+ return G
439
+
440
+ # get all shortest paths between vertices of odd degree
441
+ odd_deg_pairs_paths = [
442
+ (m, {n: nx.shortest_path(G, source=m, target=n)})
443
+ for m, n in combinations(odd_degree_nodes, 2)
444
+ ]
445
+
446
+ # use the number of vertices in a graph + 1 as an upper bound on
447
+ # the maximum length of a path in G
448
+ upper_bound_on_max_path_length = len(G) + 1
449
+
450
+ # use "len(G) + 1 - len(P)",
451
+ # where P is a shortest path between vertices n and m,
452
+ # as edge-weights in a new graph
453
+ # store the paths in the graph for easy indexing later
454
+ Gp = nx.Graph()
455
+ for n, Ps in odd_deg_pairs_paths:
456
+ for m, P in Ps.items():
457
+ if n != m:
458
+ Gp.add_edge(
459
+ m, n, weight=upper_bound_on_max_path_length - len(P), path=P
460
+ )
461
+
462
+ # find the minimum weight matching of edges in the weighted graph
463
+ best_matching = nx.Graph(list(nx.max_weight_matching(Gp)))
464
+
465
+ # duplicate each edge along each path in the set of paths in Gp
466
+ for m, n in best_matching.edges():
467
+ path = Gp[m][n]["path"]
468
+ G.add_edges_from(nx.utils.pairwise(path))
469
+ return G
pythonProject/.venv/Lib/site-packages/networkx/algorithms/graph_hashing.py ADDED
@@ -0,0 +1,322 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Functions for hashing graphs to strings.
3
+ Isomorphic graphs should be assigned identical hashes.
4
+ For now, only Weisfeiler-Lehman hashing is implemented.
5
+ """
6
+
7
+ from collections import Counter, defaultdict
8
+ from hashlib import blake2b
9
+
10
+ import networkx as nx
11
+
12
+ __all__ = ["weisfeiler_lehman_graph_hash", "weisfeiler_lehman_subgraph_hashes"]
13
+
14
+
15
+ def _hash_label(label, digest_size):
16
+ return blake2b(label.encode("ascii"), digest_size=digest_size).hexdigest()
17
+
18
+
19
+ def _init_node_labels(G, edge_attr, node_attr):
20
+ if node_attr:
21
+ return {u: str(dd[node_attr]) for u, dd in G.nodes(data=True)}
22
+ elif edge_attr:
23
+ return {u: "" for u in G}
24
+ else:
25
+ return {u: str(deg) for u, deg in G.degree()}
26
+
27
+
28
+ def _neighborhood_aggregate(G, node, node_labels, edge_attr=None):
29
+ """
30
+ Compute new labels for given node by aggregating
31
+ the labels of each node's neighbors.
32
+ """
33
+ label_list = []
34
+ for nbr in G.neighbors(node):
35
+ prefix = "" if edge_attr is None else str(G[node][nbr][edge_attr])
36
+ label_list.append(prefix + node_labels[nbr])
37
+ return node_labels[node] + "".join(sorted(label_list))
38
+
39
+
40
+ @nx._dispatchable(edge_attrs={"edge_attr": None}, node_attrs="node_attr")
41
+ def weisfeiler_lehman_graph_hash(
42
+ G, edge_attr=None, node_attr=None, iterations=3, digest_size=16
43
+ ):
44
+ """Return Weisfeiler Lehman (WL) graph hash.
45
+
46
+ The function iteratively aggregates and hashes neighborhoods of each node.
47
+ After each node's neighbors are hashed to obtain updated node labels,
48
+ a hashed histogram of resulting labels is returned as the final hash.
49
+
50
+ Hashes are identical for isomorphic graphs and strong guarantees that
51
+ non-isomorphic graphs will get different hashes. See [1]_ for details.
52
+
53
+ If no node or edge attributes are provided, the degree of each node
54
+ is used as its initial label.
55
+ Otherwise, node and/or edge labels are used to compute the hash.
56
+
57
+ Parameters
58
+ ----------
59
+ G : graph
60
+ The graph to be hashed.
61
+ Can have node and/or edge attributes. Can also have no attributes.
62
+ edge_attr : string, optional (default=None)
63
+ The key in edge attribute dictionary to be used for hashing.
64
+ If None, edge labels are ignored.
65
+ node_attr: string, optional (default=None)
66
+ The key in node attribute dictionary to be used for hashing.
67
+ If None, and no edge_attr given, use the degrees of the nodes as labels.
68
+ iterations: int, optional (default=3)
69
+ Number of neighbor aggregations to perform.
70
+ Should be larger for larger graphs.
71
+ digest_size: int, optional (default=16)
72
+ Size (in bits) of blake2b hash digest to use for hashing node labels.
73
+
74
+ Returns
75
+ -------
76
+ h : string
77
+ Hexadecimal string corresponding to hash of the input graph.
78
+
79
+ Examples
80
+ --------
81
+ Two graphs with edge attributes that are isomorphic, except for
82
+ differences in the edge labels.
83
+
84
+ >>> G1 = nx.Graph()
85
+ >>> G1.add_edges_from(
86
+ ... [
87
+ ... (1, 2, {"label": "A"}),
88
+ ... (2, 3, {"label": "A"}),
89
+ ... (3, 1, {"label": "A"}),
90
+ ... (1, 4, {"label": "B"}),
91
+ ... ]
92
+ ... )
93
+ >>> G2 = nx.Graph()
94
+ >>> G2.add_edges_from(
95
+ ... [
96
+ ... (5, 6, {"label": "B"}),
97
+ ... (6, 7, {"label": "A"}),
98
+ ... (7, 5, {"label": "A"}),
99
+ ... (7, 8, {"label": "A"}),
100
+ ... ]
101
+ ... )
102
+
103
+ Omitting the `edge_attr` option, results in identical hashes.
104
+
105
+ >>> nx.weisfeiler_lehman_graph_hash(G1)
106
+ '7bc4dde9a09d0b94c5097b219891d81a'
107
+ >>> nx.weisfeiler_lehman_graph_hash(G2)
108
+ '7bc4dde9a09d0b94c5097b219891d81a'
109
+
110
+ With edge labels, the graphs are no longer assigned
111
+ the same hash digest.
112
+
113
+ >>> nx.weisfeiler_lehman_graph_hash(G1, edge_attr="label")
114
+ 'c653d85538bcf041d88c011f4f905f10'
115
+ >>> nx.weisfeiler_lehman_graph_hash(G2, edge_attr="label")
116
+ '3dcd84af1ca855d0eff3c978d88e7ec7'
117
+
118
+ Notes
119
+ -----
120
+ To return the WL hashes of each subgraph of a graph, use
121
+ `weisfeiler_lehman_subgraph_hashes`
122
+
123
+ Similarity between hashes does not imply similarity between graphs.
124
+
125
+ References
126
+ ----------
127
+ .. [1] Shervashidze, Nino, Pascal Schweitzer, Erik Jan Van Leeuwen,
128
+ Kurt Mehlhorn, and Karsten M. Borgwardt. Weisfeiler Lehman
129
+ Graph Kernels. Journal of Machine Learning Research. 2011.
130
+ http://www.jmlr.org/papers/volume12/shervashidze11a/shervashidze11a.pdf
131
+
132
+ See also
133
+ --------
134
+ weisfeiler_lehman_subgraph_hashes
135
+ """
136
+
137
+ def weisfeiler_lehman_step(G, labels, edge_attr=None):
138
+ """
139
+ Apply neighborhood aggregation to each node
140
+ in the graph.
141
+ Computes a dictionary with labels for each node.
142
+ """
143
+ new_labels = {}
144
+ for node in G.nodes():
145
+ label = _neighborhood_aggregate(G, node, labels, edge_attr=edge_attr)
146
+ new_labels[node] = _hash_label(label, digest_size)
147
+ return new_labels
148
+
149
+ # set initial node labels
150
+ node_labels = _init_node_labels(G, edge_attr, node_attr)
151
+
152
+ subgraph_hash_counts = []
153
+ for _ in range(iterations):
154
+ node_labels = weisfeiler_lehman_step(G, node_labels, edge_attr=edge_attr)
155
+ counter = Counter(node_labels.values())
156
+ # sort the counter, extend total counts
157
+ subgraph_hash_counts.extend(sorted(counter.items(), key=lambda x: x[0]))
158
+
159
+ # hash the final counter
160
+ return _hash_label(str(tuple(subgraph_hash_counts)), digest_size)
161
+
162
+
163
+ @nx._dispatchable(edge_attrs={"edge_attr": None}, node_attrs="node_attr")
164
+ def weisfeiler_lehman_subgraph_hashes(
165
+ G,
166
+ edge_attr=None,
167
+ node_attr=None,
168
+ iterations=3,
169
+ digest_size=16,
170
+ include_initial_labels=False,
171
+ ):
172
+ """
173
+ Return a dictionary of subgraph hashes by node.
174
+
175
+ Dictionary keys are nodes in `G`, and values are a list of hashes.
176
+ Each hash corresponds to a subgraph rooted at a given node u in `G`.
177
+ Lists of subgraph hashes are sorted in increasing order of depth from
178
+ their root node, with the hash at index i corresponding to a subgraph
179
+ of nodes at most i edges distance from u. Thus, each list will contain
180
+ `iterations` elements - a hash for a subgraph at each depth. If
181
+ `include_initial_labels` is set to `True`, each list will additionally
182
+ have contain a hash of the initial node label (or equivalently a
183
+ subgraph of depth 0) prepended, totalling ``iterations + 1`` elements.
184
+
185
+ The function iteratively aggregates and hashes neighborhoods of each node.
186
+ This is achieved for each step by replacing for each node its label from
187
+ the previous iteration with its hashed 1-hop neighborhood aggregate.
188
+ The new node label is then appended to a list of node labels for each
189
+ node.
190
+
191
+ To aggregate neighborhoods for a node $u$ at each step, all labels of
192
+ nodes adjacent to $u$ are concatenated. If the `edge_attr` parameter is set,
193
+ labels for each neighboring node are prefixed with the value of this attribute
194
+ along the connecting edge from this neighbor to node $u$. The resulting string
195
+ is then hashed to compress this information into a fixed digest size.
196
+
197
+ Thus, at the $i$-th iteration, nodes within $i$ hops influence any given
198
+ hashed node label. We can therefore say that at depth $i$ for node $u$
199
+ we have a hash for a subgraph induced by the $i$-hop neighborhood of $u$.
200
+
201
+ The output can be used to to create general Weisfeiler-Lehman graph kernels,
202
+ or generate features for graphs or nodes - for example to generate 'words' in
203
+ a graph as seen in the 'graph2vec' algorithm.
204
+ See [1]_ & [2]_ respectively for details.
205
+
206
+ Hashes are identical for isomorphic subgraphs and there exist strong
207
+ guarantees that non-isomorphic graphs will get different hashes.
208
+ See [1]_ for details.
209
+
210
+ If no node or edge attributes are provided, the degree of each node
211
+ is used as its initial label.
212
+ Otherwise, node and/or edge labels are used to compute the hash.
213
+
214
+ Parameters
215
+ ----------
216
+ G : graph
217
+ The graph to be hashed.
218
+ Can have node and/or edge attributes. Can also have no attributes.
219
+ edge_attr : string, optional (default=None)
220
+ The key in edge attribute dictionary to be used for hashing.
221
+ If None, edge labels are ignored.
222
+ node_attr : string, optional (default=None)
223
+ The key in node attribute dictionary to be used for hashing.
224
+ If None, and no edge_attr given, use the degrees of the nodes as labels.
225
+ If None, and edge_attr is given, each node starts with an identical label.
226
+ iterations : int, optional (default=3)
227
+ Number of neighbor aggregations to perform.
228
+ Should be larger for larger graphs.
229
+ digest_size : int, optional (default=16)
230
+ Size (in bits) of blake2b hash digest to use for hashing node labels.
231
+ The default size is 16 bits.
232
+ include_initial_labels : bool, optional (default=False)
233
+ If True, include the hashed initial node label as the first subgraph
234
+ hash for each node.
235
+
236
+ Returns
237
+ -------
238
+ node_subgraph_hashes : dict
239
+ A dictionary with each key given by a node in G, and each value given
240
+ by the subgraph hashes in order of depth from the key node.
241
+
242
+ Examples
243
+ --------
244
+ Finding similar nodes in different graphs:
245
+
246
+ >>> G1 = nx.Graph()
247
+ >>> G1.add_edges_from([(1, 2), (2, 3), (2, 4), (3, 5), (4, 6), (5, 7), (6, 7)])
248
+ >>> G2 = nx.Graph()
249
+ >>> G2.add_edges_from([(1, 3), (2, 3), (1, 6), (1, 5), (4, 6)])
250
+ >>> g1_hashes = nx.weisfeiler_lehman_subgraph_hashes(G1, iterations=3, digest_size=8)
251
+ >>> g2_hashes = nx.weisfeiler_lehman_subgraph_hashes(G2, iterations=3, digest_size=8)
252
+
253
+ Even though G1 and G2 are not isomorphic (they have different numbers of edges),
254
+ the hash sequence of depth 3 for node 1 in G1 and node 5 in G2 are similar:
255
+
256
+ >>> g1_hashes[1]
257
+ ['a93b64973cfc8897', 'db1b43ae35a1878f', '57872a7d2059c1c0']
258
+ >>> g2_hashes[5]
259
+ ['a93b64973cfc8897', 'db1b43ae35a1878f', '1716d2a4012fa4bc']
260
+
261
+ The first 2 WL subgraph hashes match. From this we can conclude that it's very
262
+ likely the neighborhood of 2 hops around these nodes are isomorphic.
263
+
264
+ However the 3-hop neighborhoods of ``G1`` and ``G2`` are not isomorphic since the
265
+ 3rd hashes in the lists above are not equal.
266
+
267
+ These nodes may be candidates to be classified together since their local topology
268
+ is similar.
269
+
270
+ Notes
271
+ -----
272
+ To hash the full graph when subgraph hashes are not needed, use
273
+ `weisfeiler_lehman_graph_hash` for efficiency.
274
+
275
+ Similarity between hashes does not imply similarity between graphs.
276
+
277
+ References
278
+ ----------
279
+ .. [1] Shervashidze, Nino, Pascal Schweitzer, Erik Jan Van Leeuwen,
280
+ Kurt Mehlhorn, and Karsten M. Borgwardt. Weisfeiler Lehman
281
+ Graph Kernels. Journal of Machine Learning Research. 2011.
282
+ http://www.jmlr.org/papers/volume12/shervashidze11a/shervashidze11a.pdf
283
+ .. [2] Annamalai Narayanan, Mahinthan Chandramohan, Rajasekar Venkatesan,
284
+ Lihui Chen, Yang Liu and Shantanu Jaiswa. graph2vec: Learning
285
+ Distributed Representations of Graphs. arXiv. 2017
286
+ https://arxiv.org/pdf/1707.05005.pdf
287
+
288
+ See also
289
+ --------
290
+ weisfeiler_lehman_graph_hash
291
+ """
292
+
293
+ def weisfeiler_lehman_step(G, labels, node_subgraph_hashes, edge_attr=None):
294
+ """
295
+ Apply neighborhood aggregation to each node
296
+ in the graph.
297
+ Computes a dictionary with labels for each node.
298
+ Appends the new hashed label to the dictionary of subgraph hashes
299
+ originating from and indexed by each node in G
300
+ """
301
+ new_labels = {}
302
+ for node in G.nodes():
303
+ label = _neighborhood_aggregate(G, node, labels, edge_attr=edge_attr)
304
+ hashed_label = _hash_label(label, digest_size)
305
+ new_labels[node] = hashed_label
306
+ node_subgraph_hashes[node].append(hashed_label)
307
+ return new_labels
308
+
309
+ node_labels = _init_node_labels(G, edge_attr, node_attr)
310
+ if include_initial_labels:
311
+ node_subgraph_hashes = {
312
+ k: [_hash_label(v, digest_size)] for k, v in node_labels.items()
313
+ }
314
+ else:
315
+ node_subgraph_hashes = defaultdict(list)
316
+
317
+ for _ in range(iterations):
318
+ node_labels = weisfeiler_lehman_step(
319
+ G, node_labels, node_subgraph_hashes, edge_attr
320
+ )
321
+
322
+ return dict(node_subgraph_hashes)
pythonProject/.venv/Lib/site-packages/networkx/algorithms/graphical.py ADDED
@@ -0,0 +1,483 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Test sequences for graphiness.
2
+ """
3
+ import heapq
4
+
5
+ import networkx as nx
6
+
7
+ __all__ = [
8
+ "is_graphical",
9
+ "is_multigraphical",
10
+ "is_pseudographical",
11
+ "is_digraphical",
12
+ "is_valid_degree_sequence_erdos_gallai",
13
+ "is_valid_degree_sequence_havel_hakimi",
14
+ ]
15
+
16
+
17
+ @nx._dispatchable(graphs=None)
18
+ def is_graphical(sequence, method="eg"):
19
+ """Returns True if sequence is a valid degree sequence.
20
+
21
+ A degree sequence is valid if some graph can realize it.
22
+
23
+ Parameters
24
+ ----------
25
+ sequence : list or iterable container
26
+ A sequence of integer node degrees
27
+
28
+ method : "eg" | "hh" (default: 'eg')
29
+ The method used to validate the degree sequence.
30
+ "eg" corresponds to the Erdős-Gallai algorithm
31
+ [EG1960]_, [choudum1986]_, and
32
+ "hh" to the Havel-Hakimi algorithm
33
+ [havel1955]_, [hakimi1962]_, [CL1996]_.
34
+
35
+ Returns
36
+ -------
37
+ valid : bool
38
+ True if the sequence is a valid degree sequence and False if not.
39
+
40
+ Examples
41
+ --------
42
+ >>> G = nx.path_graph(4)
43
+ >>> sequence = (d for n, d in G.degree())
44
+ >>> nx.is_graphical(sequence)
45
+ True
46
+
47
+ To test a non-graphical sequence:
48
+ >>> sequence_list = [d for n, d in G.degree()]
49
+ >>> sequence_list[-1] += 1
50
+ >>> nx.is_graphical(sequence_list)
51
+ False
52
+
53
+ References
54
+ ----------
55
+ .. [EG1960] Erdős and Gallai, Mat. Lapok 11 264, 1960.
56
+ .. [choudum1986] S.A. Choudum. "A simple proof of the Erdős-Gallai theorem on
57
+ graph sequences." Bulletin of the Australian Mathematical Society, 33,
58
+ pp 67-70, 1986. https://doi.org/10.1017/S0004972700002872
59
+ .. [havel1955] Havel, V. "A Remark on the Existence of Finite Graphs"
60
+ Casopis Pest. Mat. 80, 477-480, 1955.
61
+ .. [hakimi1962] Hakimi, S. "On the Realizability of a Set of Integers as
62
+ Degrees of the Vertices of a Graph." SIAM J. Appl. Math. 10, 496-506, 1962.
63
+ .. [CL1996] G. Chartrand and L. Lesniak, "Graphs and Digraphs",
64
+ Chapman and Hall/CRC, 1996.
65
+ """
66
+ if method == "eg":
67
+ valid = is_valid_degree_sequence_erdos_gallai(list(sequence))
68
+ elif method == "hh":
69
+ valid = is_valid_degree_sequence_havel_hakimi(list(sequence))
70
+ else:
71
+ msg = "`method` must be 'eg' or 'hh'"
72
+ raise nx.NetworkXException(msg)
73
+ return valid
74
+
75
+
76
+ def _basic_graphical_tests(deg_sequence):
77
+ # Sort and perform some simple tests on the sequence
78
+ deg_sequence = nx.utils.make_list_of_ints(deg_sequence)
79
+ p = len(deg_sequence)
80
+ num_degs = [0] * p
81
+ dmax, dmin, dsum, n = 0, p, 0, 0
82
+ for d in deg_sequence:
83
+ # Reject if degree is negative or larger than the sequence length
84
+ if d < 0 or d >= p:
85
+ raise nx.NetworkXUnfeasible
86
+ # Process only the non-zero integers
87
+ elif d > 0:
88
+ dmax, dmin, dsum, n = max(dmax, d), min(dmin, d), dsum + d, n + 1
89
+ num_degs[d] += 1
90
+ # Reject sequence if it has odd sum or is oversaturated
91
+ if dsum % 2 or dsum > n * (n - 1):
92
+ raise nx.NetworkXUnfeasible
93
+ return dmax, dmin, dsum, n, num_degs
94
+
95
+
96
+ @nx._dispatchable(graphs=None)
97
+ def is_valid_degree_sequence_havel_hakimi(deg_sequence):
98
+ r"""Returns True if deg_sequence can be realized by a simple graph.
99
+
100
+ The validation proceeds using the Havel-Hakimi theorem
101
+ [havel1955]_, [hakimi1962]_, [CL1996]_.
102
+ Worst-case run time is $O(s)$ where $s$ is the sum of the sequence.
103
+
104
+ Parameters
105
+ ----------
106
+ deg_sequence : list
107
+ A list of integers where each element specifies the degree of a node
108
+ in a graph.
109
+
110
+ Returns
111
+ -------
112
+ valid : bool
113
+ True if deg_sequence is graphical and False if not.
114
+
115
+ Examples
116
+ --------
117
+ >>> G = nx.Graph([(1, 2), (1, 3), (2, 3), (3, 4), (4, 2), (5, 1), (5, 4)])
118
+ >>> sequence = (d for _, d in G.degree())
119
+ >>> nx.is_valid_degree_sequence_havel_hakimi(sequence)
120
+ True
121
+
122
+ To test a non-valid sequence:
123
+ >>> sequence_list = [d for _, d in G.degree()]
124
+ >>> sequence_list[-1] += 1
125
+ >>> nx.is_valid_degree_sequence_havel_hakimi(sequence_list)
126
+ False
127
+
128
+ Notes
129
+ -----
130
+ The ZZ condition says that for the sequence d if
131
+
132
+ .. math::
133
+ |d| >= \frac{(\max(d) + \min(d) + 1)^2}{4*\min(d)}
134
+
135
+ then d is graphical. This was shown in Theorem 6 in [1]_.
136
+
137
+ References
138
+ ----------
139
+ .. [1] I.E. Zverovich and V.E. Zverovich. "Contributions to the theory
140
+ of graphic sequences", Discrete Mathematics, 105, pp. 292-303 (1992).
141
+ .. [havel1955] Havel, V. "A Remark on the Existence of Finite Graphs"
142
+ Casopis Pest. Mat. 80, 477-480, 1955.
143
+ .. [hakimi1962] Hakimi, S. "On the Realizability of a Set of Integers as
144
+ Degrees of the Vertices of a Graph." SIAM J. Appl. Math. 10, 496-506, 1962.
145
+ .. [CL1996] G. Chartrand and L. Lesniak, "Graphs and Digraphs",
146
+ Chapman and Hall/CRC, 1996.
147
+ """
148
+ try:
149
+ dmax, dmin, dsum, n, num_degs = _basic_graphical_tests(deg_sequence)
150
+ except nx.NetworkXUnfeasible:
151
+ return False
152
+ # Accept if sequence has no non-zero degrees or passes the ZZ condition
153
+ if n == 0 or 4 * dmin * n >= (dmax + dmin + 1) * (dmax + dmin + 1):
154
+ return True
155
+
156
+ modstubs = [0] * (dmax + 1)
157
+ # Successively reduce degree sequence by removing the maximum degree
158
+ while n > 0:
159
+ # Retrieve the maximum degree in the sequence
160
+ while num_degs[dmax] == 0:
161
+ dmax -= 1
162
+ # If there are not enough stubs to connect to, then the sequence is
163
+ # not graphical
164
+ if dmax > n - 1:
165
+ return False
166
+
167
+ # Remove largest stub in list
168
+ num_degs[dmax], n = num_degs[dmax] - 1, n - 1
169
+ # Reduce the next dmax largest stubs
170
+ mslen = 0
171
+ k = dmax
172
+ for i in range(dmax):
173
+ while num_degs[k] == 0:
174
+ k -= 1
175
+ num_degs[k], n = num_degs[k] - 1, n - 1
176
+ if k > 1:
177
+ modstubs[mslen] = k - 1
178
+ mslen += 1
179
+ # Add back to the list any non-zero stubs that were removed
180
+ for i in range(mslen):
181
+ stub = modstubs[i]
182
+ num_degs[stub], n = num_degs[stub] + 1, n + 1
183
+ return True
184
+
185
+
186
+ @nx._dispatchable(graphs=None)
187
+ def is_valid_degree_sequence_erdos_gallai(deg_sequence):
188
+ r"""Returns True if deg_sequence can be realized by a simple graph.
189
+
190
+ The validation is done using the Erdős-Gallai theorem [EG1960]_.
191
+
192
+ Parameters
193
+ ----------
194
+ deg_sequence : list
195
+ A list of integers
196
+
197
+ Returns
198
+ -------
199
+ valid : bool
200
+ True if deg_sequence is graphical and False if not.
201
+
202
+ Examples
203
+ --------
204
+ >>> G = nx.Graph([(1, 2), (1, 3), (2, 3), (3, 4), (4, 2), (5, 1), (5, 4)])
205
+ >>> sequence = (d for _, d in G.degree())
206
+ >>> nx.is_valid_degree_sequence_erdos_gallai(sequence)
207
+ True
208
+
209
+ To test a non-valid sequence:
210
+ >>> sequence_list = [d for _, d in G.degree()]
211
+ >>> sequence_list[-1] += 1
212
+ >>> nx.is_valid_degree_sequence_erdos_gallai(sequence_list)
213
+ False
214
+
215
+ Notes
216
+ -----
217
+
218
+ This implementation uses an equivalent form of the Erdős-Gallai criterion.
219
+ Worst-case run time is $O(n)$ where $n$ is the length of the sequence.
220
+
221
+ Specifically, a sequence d is graphical if and only if the
222
+ sum of the sequence is even and for all strong indices k in the sequence,
223
+
224
+ .. math::
225
+
226
+ \sum_{i=1}^{k} d_i \leq k(k-1) + \sum_{j=k+1}^{n} \min(d_i,k)
227
+ = k(n-1) - ( k \sum_{j=0}^{k-1} n_j - \sum_{j=0}^{k-1} j n_j )
228
+
229
+ A strong index k is any index where d_k >= k and the value n_j is the
230
+ number of occurrences of j in d. The maximal strong index is called the
231
+ Durfee index.
232
+
233
+ This particular rearrangement comes from the proof of Theorem 3 in [2]_.
234
+
235
+ The ZZ condition says that for the sequence d if
236
+
237
+ .. math::
238
+ |d| >= \frac{(\max(d) + \min(d) + 1)^2}{4*\min(d)}
239
+
240
+ then d is graphical. This was shown in Theorem 6 in [2]_.
241
+
242
+ References
243
+ ----------
244
+ .. [1] A. Tripathi and S. Vijay. "A note on a theorem of Erdős & Gallai",
245
+ Discrete Mathematics, 265, pp. 417-420 (2003).
246
+ .. [2] I.E. Zverovich and V.E. Zverovich. "Contributions to the theory
247
+ of graphic sequences", Discrete Mathematics, 105, pp. 292-303 (1992).
248
+ .. [EG1960] Erdős and Gallai, Mat. Lapok 11 264, 1960.
249
+ """
250
+ try:
251
+ dmax, dmin, dsum, n, num_degs = _basic_graphical_tests(deg_sequence)
252
+ except nx.NetworkXUnfeasible:
253
+ return False
254
+ # Accept if sequence has no non-zero degrees or passes the ZZ condition
255
+ if n == 0 or 4 * dmin * n >= (dmax + dmin + 1) * (dmax + dmin + 1):
256
+ return True
257
+
258
+ # Perform the EG checks using the reformulation of Zverovich and Zverovich
259
+ k, sum_deg, sum_nj, sum_jnj = 0, 0, 0, 0
260
+ for dk in range(dmax, dmin - 1, -1):
261
+ if dk < k + 1: # Check if already past Durfee index
262
+ return True
263
+ if num_degs[dk] > 0:
264
+ run_size = num_degs[dk] # Process a run of identical-valued degrees
265
+ if dk < k + run_size: # Check if end of run is past Durfee index
266
+ run_size = dk - k # Adjust back to Durfee index
267
+ sum_deg += run_size * dk
268
+ for v in range(run_size):
269
+ sum_nj += num_degs[k + v]
270
+ sum_jnj += (k + v) * num_degs[k + v]
271
+ k += run_size
272
+ if sum_deg > k * (n - 1) - k * sum_nj + sum_jnj:
273
+ return False
274
+ return True
275
+
276
+
277
+ @nx._dispatchable(graphs=None)
278
+ def is_multigraphical(sequence):
279
+ """Returns True if some multigraph can realize the sequence.
280
+
281
+ Parameters
282
+ ----------
283
+ sequence : list
284
+ A list of integers
285
+
286
+ Returns
287
+ -------
288
+ valid : bool
289
+ True if deg_sequence is a multigraphic degree sequence and False if not.
290
+
291
+ Examples
292
+ --------
293
+ >>> G = nx.MultiGraph([(1, 2), (1, 3), (2, 3), (3, 4), (4, 2), (5, 1), (5, 4)])
294
+ >>> sequence = (d for _, d in G.degree())
295
+ >>> nx.is_multigraphical(sequence)
296
+ True
297
+
298
+ To test a non-multigraphical sequence:
299
+ >>> sequence_list = [d for _, d in G.degree()]
300
+ >>> sequence_list[-1] += 1
301
+ >>> nx.is_multigraphical(sequence_list)
302
+ False
303
+
304
+ Notes
305
+ -----
306
+ The worst-case run time is $O(n)$ where $n$ is the length of the sequence.
307
+
308
+ References
309
+ ----------
310
+ .. [1] S. L. Hakimi. "On the realizability of a set of integers as
311
+ degrees of the vertices of a linear graph", J. SIAM, 10, pp. 496-506
312
+ (1962).
313
+ """
314
+ try:
315
+ deg_sequence = nx.utils.make_list_of_ints(sequence)
316
+ except nx.NetworkXError:
317
+ return False
318
+ dsum, dmax = 0, 0
319
+ for d in deg_sequence:
320
+ if d < 0:
321
+ return False
322
+ dsum, dmax = dsum + d, max(dmax, d)
323
+ if dsum % 2 or dsum < 2 * dmax:
324
+ return False
325
+ return True
326
+
327
+
328
+ @nx._dispatchable(graphs=None)
329
+ def is_pseudographical(sequence):
330
+ """Returns True if some pseudograph can realize the sequence.
331
+
332
+ Every nonnegative integer sequence with an even sum is pseudographical
333
+ (see [1]_).
334
+
335
+ Parameters
336
+ ----------
337
+ sequence : list or iterable container
338
+ A sequence of integer node degrees
339
+
340
+ Returns
341
+ -------
342
+ valid : bool
343
+ True if the sequence is a pseudographic degree sequence and False if not.
344
+
345
+ Examples
346
+ --------
347
+ >>> G = nx.Graph([(1, 2), (1, 3), (2, 3), (3, 4), (4, 2), (5, 1), (5, 4)])
348
+ >>> sequence = (d for _, d in G.degree())
349
+ >>> nx.is_pseudographical(sequence)
350
+ True
351
+
352
+ To test a non-pseudographical sequence:
353
+ >>> sequence_list = [d for _, d in G.degree()]
354
+ >>> sequence_list[-1] += 1
355
+ >>> nx.is_pseudographical(sequence_list)
356
+ False
357
+
358
+ Notes
359
+ -----
360
+ The worst-case run time is $O(n)$ where n is the length of the sequence.
361
+
362
+ References
363
+ ----------
364
+ .. [1] F. Boesch and F. Harary. "Line removal algorithms for graphs
365
+ and their degree lists", IEEE Trans. Circuits and Systems, CAS-23(12),
366
+ pp. 778-782 (1976).
367
+ """
368
+ try:
369
+ deg_sequence = nx.utils.make_list_of_ints(sequence)
370
+ except nx.NetworkXError:
371
+ return False
372
+ return sum(deg_sequence) % 2 == 0 and min(deg_sequence) >= 0
373
+
374
+
375
+ @nx._dispatchable(graphs=None)
376
+ def is_digraphical(in_sequence, out_sequence):
377
+ r"""Returns True if some directed graph can realize the in- and out-degree
378
+ sequences.
379
+
380
+ Parameters
381
+ ----------
382
+ in_sequence : list or iterable container
383
+ A sequence of integer node in-degrees
384
+
385
+ out_sequence : list or iterable container
386
+ A sequence of integer node out-degrees
387
+
388
+ Returns
389
+ -------
390
+ valid : bool
391
+ True if in and out-sequences are digraphic False if not.
392
+
393
+ Examples
394
+ --------
395
+ >>> G = nx.DiGraph([(1, 2), (1, 3), (2, 3), (3, 4), (4, 2), (5, 1), (5, 4)])
396
+ >>> in_seq = (d for n, d in G.in_degree())
397
+ >>> out_seq = (d for n, d in G.out_degree())
398
+ >>> nx.is_digraphical(in_seq, out_seq)
399
+ True
400
+
401
+ To test a non-digraphical scenario:
402
+ >>> in_seq_list = [d for n, d in G.in_degree()]
403
+ >>> in_seq_list[-1] += 1
404
+ >>> nx.is_digraphical(in_seq_list, out_seq)
405
+ False
406
+
407
+ Notes
408
+ -----
409
+ This algorithm is from Kleitman and Wang [1]_.
410
+ The worst case runtime is $O(s \times \log n)$ where $s$ and $n$ are the
411
+ sum and length of the sequences respectively.
412
+
413
+ References
414
+ ----------
415
+ .. [1] D.J. Kleitman and D.L. Wang
416
+ Algorithms for Constructing Graphs and Digraphs with Given Valences
417
+ and Factors, Discrete Mathematics, 6(1), pp. 79-88 (1973)
418
+ """
419
+ try:
420
+ in_deg_sequence = nx.utils.make_list_of_ints(in_sequence)
421
+ out_deg_sequence = nx.utils.make_list_of_ints(out_sequence)
422
+ except nx.NetworkXError:
423
+ return False
424
+ # Process the sequences and form two heaps to store degree pairs with
425
+ # either zero or non-zero out degrees
426
+ sumin, sumout, nin, nout = 0, 0, len(in_deg_sequence), len(out_deg_sequence)
427
+ maxn = max(nin, nout)
428
+ maxin = 0
429
+ if maxn == 0:
430
+ return True
431
+ stubheap, zeroheap = [], []
432
+ for n in range(maxn):
433
+ in_deg, out_deg = 0, 0
434
+ if n < nout:
435
+ out_deg = out_deg_sequence[n]
436
+ if n < nin:
437
+ in_deg = in_deg_sequence[n]
438
+ if in_deg < 0 or out_deg < 0:
439
+ return False
440
+ sumin, sumout, maxin = sumin + in_deg, sumout + out_deg, max(maxin, in_deg)
441
+ if in_deg > 0:
442
+ stubheap.append((-1 * out_deg, -1 * in_deg))
443
+ elif out_deg > 0:
444
+ zeroheap.append(-1 * out_deg)
445
+ if sumin != sumout:
446
+ return False
447
+ heapq.heapify(stubheap)
448
+ heapq.heapify(zeroheap)
449
+
450
+ modstubs = [(0, 0)] * (maxin + 1)
451
+ # Successively reduce degree sequence by removing the maximum out degree
452
+ while stubheap:
453
+ # Take the first value in the sequence with non-zero in degree
454
+ (freeout, freein) = heapq.heappop(stubheap)
455
+ freein *= -1
456
+ if freein > len(stubheap) + len(zeroheap):
457
+ return False
458
+
459
+ # Attach out stubs to the nodes with the most in stubs
460
+ mslen = 0
461
+ for i in range(freein):
462
+ if zeroheap and (not stubheap or stubheap[0][0] > zeroheap[0]):
463
+ stubout = heapq.heappop(zeroheap)
464
+ stubin = 0
465
+ else:
466
+ (stubout, stubin) = heapq.heappop(stubheap)
467
+ if stubout == 0:
468
+ return False
469
+ # Check if target is now totally connected
470
+ if stubout + 1 < 0 or stubin < 0:
471
+ modstubs[mslen] = (stubout + 1, stubin)
472
+ mslen += 1
473
+
474
+ # Add back the nodes to the heap that still have available stubs
475
+ for i in range(mslen):
476
+ stub = modstubs[i]
477
+ if stub[1] < 0:
478
+ heapq.heappush(stubheap, stub)
479
+ else:
480
+ heapq.heappush(zeroheap, stub[0])
481
+ if freeout < 0:
482
+ heapq.heappush(zeroheap, freeout)
483
+ return True
pythonProject/.venv/Lib/site-packages/networkx/algorithms/hierarchy.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Flow Hierarchy.
3
+ """
4
+ import networkx as nx
5
+
6
+ __all__ = ["flow_hierarchy"]
7
+
8
+
9
+ @nx._dispatchable(edge_attrs="weight")
10
+ def flow_hierarchy(G, weight=None):
11
+ """Returns the flow hierarchy of a directed network.
12
+
13
+ Flow hierarchy is defined as the fraction of edges not participating
14
+ in cycles in a directed graph [1]_.
15
+
16
+ Parameters
17
+ ----------
18
+ G : DiGraph or MultiDiGraph
19
+ A directed graph
20
+
21
+ weight : string, optional (default=None)
22
+ Attribute to use for edge weights. If None the weight defaults to 1.
23
+
24
+ Returns
25
+ -------
26
+ h : float
27
+ Flow hierarchy value
28
+
29
+ Notes
30
+ -----
31
+ The algorithm described in [1]_ computes the flow hierarchy through
32
+ exponentiation of the adjacency matrix. This function implements an
33
+ alternative approach that finds strongly connected components.
34
+ An edge is in a cycle if and only if it is in a strongly connected
35
+ component, which can be found in $O(m)$ time using Tarjan's algorithm.
36
+
37
+ References
38
+ ----------
39
+ .. [1] Luo, J.; Magee, C.L. (2011),
40
+ Detecting evolving patterns of self-organizing networks by flow
41
+ hierarchy measurement, Complexity, Volume 16 Issue 6 53-61.
42
+ DOI: 10.1002/cplx.20368
43
+ http://web.mit.edu/~cmagee/www/documents/28-DetectingEvolvingPatterns_FlowHierarchy.pdf
44
+ """
45
+ if not G.is_directed():
46
+ raise nx.NetworkXError("G must be a digraph in flow_hierarchy")
47
+ scc = nx.strongly_connected_components(G)
48
+ return 1 - sum(G.subgraph(c).size(weight) for c in scc) / G.size(weight)
pythonProject/.venv/Lib/site-packages/networkx/algorithms/hybrid.py ADDED
@@ -0,0 +1,195 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Provides functions for finding and testing for locally `(k, l)`-connected
3
+ graphs.
4
+
5
+ """
6
+ import copy
7
+
8
+ import networkx as nx
9
+
10
+ __all__ = ["kl_connected_subgraph", "is_kl_connected"]
11
+
12
+
13
+ @nx._dispatchable(returns_graph=True)
14
+ def kl_connected_subgraph(G, k, l, low_memory=False, same_as_graph=False):
15
+ """Returns the maximum locally `(k, l)`-connected subgraph of `G`.
16
+
17
+ A graph is locally `(k, l)`-connected if for each edge `(u, v)` in the
18
+ graph there are at least `l` edge-disjoint paths of length at most `k`
19
+ joining `u` to `v`.
20
+
21
+ Parameters
22
+ ----------
23
+ G : NetworkX graph
24
+ The graph in which to find a maximum locally `(k, l)`-connected
25
+ subgraph.
26
+
27
+ k : integer
28
+ The maximum length of paths to consider. A higher number means a looser
29
+ connectivity requirement.
30
+
31
+ l : integer
32
+ The number of edge-disjoint paths. A higher number means a stricter
33
+ connectivity requirement.
34
+
35
+ low_memory : bool
36
+ If this is True, this function uses an algorithm that uses slightly
37
+ more time but less memory.
38
+
39
+ same_as_graph : bool
40
+ If True then return a tuple of the form `(H, is_same)`,
41
+ where `H` is the maximum locally `(k, l)`-connected subgraph and
42
+ `is_same` is a Boolean representing whether `G` is locally `(k,
43
+ l)`-connected (and hence, whether `H` is simply a copy of the input
44
+ graph `G`).
45
+
46
+ Returns
47
+ -------
48
+ NetworkX graph or two-tuple
49
+ If `same_as_graph` is True, then this function returns a
50
+ two-tuple as described above. Otherwise, it returns only the maximum
51
+ locally `(k, l)`-connected subgraph.
52
+
53
+ See also
54
+ --------
55
+ is_kl_connected
56
+
57
+ References
58
+ ----------
59
+ .. [1] Chung, Fan and Linyuan Lu. "The Small World Phenomenon in Hybrid
60
+ Power Law Graphs." *Complex Networks*. Springer Berlin Heidelberg,
61
+ 2004. 89--104.
62
+
63
+ """
64
+ H = copy.deepcopy(G) # subgraph we construct by removing from G
65
+
66
+ graphOK = True
67
+ deleted_some = True # hack to start off the while loop
68
+ while deleted_some:
69
+ deleted_some = False
70
+ # We use `for edge in list(H.edges()):` instead of
71
+ # `for edge in H.edges():` because we edit the graph `H` in
72
+ # the loop. Hence using an iterator will result in
73
+ # `RuntimeError: dictionary changed size during iteration`
74
+ for edge in list(H.edges()):
75
+ (u, v) = edge
76
+ # Get copy of graph needed for this search
77
+ if low_memory:
78
+ verts = {u, v}
79
+ for i in range(k):
80
+ for w in verts.copy():
81
+ verts.update(G[w])
82
+ G2 = G.subgraph(verts).copy()
83
+ else:
84
+ G2 = copy.deepcopy(G)
85
+ ###
86
+ path = [u, v]
87
+ cnt = 0
88
+ accept = 0
89
+ while path:
90
+ cnt += 1 # Found a path
91
+ if cnt >= l:
92
+ accept = 1
93
+ break
94
+ # record edges along this graph
95
+ prev = u
96
+ for w in path:
97
+ if prev != w:
98
+ G2.remove_edge(prev, w)
99
+ prev = w
100
+ # path = shortest_path(G2, u, v, k) # ??? should "Cutoff" be k+1?
101
+ try:
102
+ path = nx.shortest_path(G2, u, v) # ??? should "Cutoff" be k+1?
103
+ except nx.NetworkXNoPath:
104
+ path = False
105
+ # No Other Paths
106
+ if accept == 0:
107
+ H.remove_edge(u, v)
108
+ deleted_some = True
109
+ if graphOK:
110
+ graphOK = False
111
+ # We looked through all edges and removed none of them.
112
+ # So, H is the maximal (k,l)-connected subgraph of G
113
+ if same_as_graph:
114
+ return (H, graphOK)
115
+ return H
116
+
117
+
118
+ @nx._dispatchable
119
+ def is_kl_connected(G, k, l, low_memory=False):
120
+ """Returns True if and only if `G` is locally `(k, l)`-connected.
121
+
122
+ A graph is locally `(k, l)`-connected if for each edge `(u, v)` in the
123
+ graph there are at least `l` edge-disjoint paths of length at most `k`
124
+ joining `u` to `v`.
125
+
126
+ Parameters
127
+ ----------
128
+ G : NetworkX graph
129
+ The graph to test for local `(k, l)`-connectedness.
130
+
131
+ k : integer
132
+ The maximum length of paths to consider. A higher number means a looser
133
+ connectivity requirement.
134
+
135
+ l : integer
136
+ The number of edge-disjoint paths. A higher number means a stricter
137
+ connectivity requirement.
138
+
139
+ low_memory : bool
140
+ If this is True, this function uses an algorithm that uses slightly
141
+ more time but less memory.
142
+
143
+ Returns
144
+ -------
145
+ bool
146
+ Whether the graph is locally `(k, l)`-connected subgraph.
147
+
148
+ See also
149
+ --------
150
+ kl_connected_subgraph
151
+
152
+ References
153
+ ----------
154
+ .. [1] Chung, Fan and Linyuan Lu. "The Small World Phenomenon in Hybrid
155
+ Power Law Graphs." *Complex Networks*. Springer Berlin Heidelberg,
156
+ 2004. 89--104.
157
+
158
+ """
159
+ graphOK = True
160
+ for edge in G.edges():
161
+ (u, v) = edge
162
+ # Get copy of graph needed for this search
163
+ if low_memory:
164
+ verts = {u, v}
165
+ for i in range(k):
166
+ [verts.update(G.neighbors(w)) for w in verts.copy()]
167
+ G2 = G.subgraph(verts)
168
+ else:
169
+ G2 = copy.deepcopy(G)
170
+ ###
171
+ path = [u, v]
172
+ cnt = 0
173
+ accept = 0
174
+ while path:
175
+ cnt += 1 # Found a path
176
+ if cnt >= l:
177
+ accept = 1
178
+ break
179
+ # record edges along this graph
180
+ prev = u
181
+ for w in path:
182
+ if w != prev:
183
+ G2.remove_edge(prev, w)
184
+ prev = w
185
+ # path = shortest_path(G2, u, v, k) # ??? should "Cutoff" be k+1?
186
+ try:
187
+ path = nx.shortest_path(G2, u, v) # ??? should "Cutoff" be k+1?
188
+ except nx.NetworkXNoPath:
189
+ path = False
190
+ # No Other Paths
191
+ if accept == 0:
192
+ graphOK = False
193
+ break
194
+ # return status
195
+ return graphOK
pythonProject/.venv/Lib/site-packages/networkx/algorithms/isolate.py ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Functions for identifying isolate (degree zero) nodes.
3
+ """
4
+ import networkx as nx
5
+
6
+ __all__ = ["is_isolate", "isolates", "number_of_isolates"]
7
+
8
+
9
+ @nx._dispatchable
10
+ def is_isolate(G, n):
11
+ """Determines whether a node is an isolate.
12
+
13
+ An *isolate* is a node with no neighbors (that is, with degree
14
+ zero). For directed graphs, this means no in-neighbors and no
15
+ out-neighbors.
16
+
17
+ Parameters
18
+ ----------
19
+ G : NetworkX graph
20
+
21
+ n : node
22
+ A node in `G`.
23
+
24
+ Returns
25
+ -------
26
+ is_isolate : bool
27
+ True if and only if `n` has no neighbors.
28
+
29
+ Examples
30
+ --------
31
+ >>> G = nx.Graph()
32
+ >>> G.add_edge(1, 2)
33
+ >>> G.add_node(3)
34
+ >>> nx.is_isolate(G, 2)
35
+ False
36
+ >>> nx.is_isolate(G, 3)
37
+ True
38
+ """
39
+ return G.degree(n) == 0
40
+
41
+
42
+ @nx._dispatchable
43
+ def isolates(G):
44
+ """Iterator over isolates in the graph.
45
+
46
+ An *isolate* is a node with no neighbors (that is, with degree
47
+ zero). For directed graphs, this means no in-neighbors and no
48
+ out-neighbors.
49
+
50
+ Parameters
51
+ ----------
52
+ G : NetworkX graph
53
+
54
+ Returns
55
+ -------
56
+ iterator
57
+ An iterator over the isolates of `G`.
58
+
59
+ Examples
60
+ --------
61
+ To get a list of all isolates of a graph, use the :class:`list`
62
+ constructor::
63
+
64
+ >>> G = nx.Graph()
65
+ >>> G.add_edge(1, 2)
66
+ >>> G.add_node(3)
67
+ >>> list(nx.isolates(G))
68
+ [3]
69
+
70
+ To remove all isolates in the graph, first create a list of the
71
+ isolates, then use :meth:`Graph.remove_nodes_from`::
72
+
73
+ >>> G.remove_nodes_from(list(nx.isolates(G)))
74
+ >>> list(G)
75
+ [1, 2]
76
+
77
+ For digraphs, isolates have zero in-degree and zero out_degre::
78
+
79
+ >>> G = nx.DiGraph([(0, 1), (1, 2)])
80
+ >>> G.add_node(3)
81
+ >>> list(nx.isolates(G))
82
+ [3]
83
+
84
+ """
85
+ return (n for n, d in G.degree() if d == 0)
86
+
87
+
88
+ @nx._dispatchable
89
+ def number_of_isolates(G):
90
+ """Returns the number of isolates in the graph.
91
+
92
+ An *isolate* is a node with no neighbors (that is, with degree
93
+ zero). For directed graphs, this means no in-neighbors and no
94
+ out-neighbors.
95
+
96
+ Parameters
97
+ ----------
98
+ G : NetworkX graph
99
+
100
+ Returns
101
+ -------
102
+ int
103
+ The number of degree zero nodes in the graph `G`.
104
+
105
+ """
106
+ # TODO This can be parallelized.
107
+ return sum(1 for v in isolates(G))
pythonProject/.venv/Lib/site-packages/networkx/algorithms/link_prediction.py ADDED
@@ -0,0 +1,688 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Link prediction algorithms.
3
+ """
4
+
5
+
6
+ from math import log
7
+
8
+ import networkx as nx
9
+ from networkx.utils import not_implemented_for
10
+
11
+ __all__ = [
12
+ "resource_allocation_index",
13
+ "jaccard_coefficient",
14
+ "adamic_adar_index",
15
+ "preferential_attachment",
16
+ "cn_soundarajan_hopcroft",
17
+ "ra_index_soundarajan_hopcroft",
18
+ "within_inter_cluster",
19
+ "common_neighbor_centrality",
20
+ ]
21
+
22
+
23
+ def _apply_prediction(G, func, ebunch=None):
24
+ """Applies the given function to each edge in the specified iterable
25
+ of edges.
26
+
27
+ `G` is an instance of :class:`networkx.Graph`.
28
+
29
+ `func` is a function on two inputs, each of which is a node in the
30
+ graph. The function can return anything, but it should return a
31
+ value representing a prediction of the likelihood of a "link"
32
+ joining the two nodes.
33
+
34
+ `ebunch` is an iterable of pairs of nodes. If not specified, all
35
+ non-edges in the graph `G` will be used.
36
+
37
+ """
38
+ if ebunch is None:
39
+ ebunch = nx.non_edges(G)
40
+ else:
41
+ for u, v in ebunch:
42
+ if u not in G:
43
+ raise nx.NodeNotFound(f"Node {u} not in G.")
44
+ if v not in G:
45
+ raise nx.NodeNotFound(f"Node {v} not in G.")
46
+ return ((u, v, func(u, v)) for u, v in ebunch)
47
+
48
+
49
+ @not_implemented_for("directed")
50
+ @not_implemented_for("multigraph")
51
+ @nx._dispatchable
52
+ def resource_allocation_index(G, ebunch=None):
53
+ r"""Compute the resource allocation index of all node pairs in ebunch.
54
+
55
+ Resource allocation index of `u` and `v` is defined as
56
+
57
+ .. math::
58
+
59
+ \sum_{w \in \Gamma(u) \cap \Gamma(v)} \frac{1}{|\Gamma(w)|}
60
+
61
+ where $\Gamma(u)$ denotes the set of neighbors of $u$.
62
+
63
+ Parameters
64
+ ----------
65
+ G : graph
66
+ A NetworkX undirected graph.
67
+
68
+ ebunch : iterable of node pairs, optional (default = None)
69
+ Resource allocation index will be computed for each pair of
70
+ nodes given in the iterable. The pairs must be given as
71
+ 2-tuples (u, v) where u and v are nodes in the graph. If ebunch
72
+ is None then all nonexistent edges in the graph will be used.
73
+ Default value: None.
74
+
75
+ Returns
76
+ -------
77
+ piter : iterator
78
+ An iterator of 3-tuples in the form (u, v, p) where (u, v) is a
79
+ pair of nodes and p is their resource allocation index.
80
+
81
+ Raises
82
+ ------
83
+ NetworkXNotImplemented
84
+ If `G` is a `DiGraph`, a `Multigraph` or a `MultiDiGraph`.
85
+
86
+ NodeNotFound
87
+ If `ebunch` has a node that is not in `G`.
88
+
89
+ Examples
90
+ --------
91
+ >>> G = nx.complete_graph(5)
92
+ >>> preds = nx.resource_allocation_index(G, [(0, 1), (2, 3)])
93
+ >>> for u, v, p in preds:
94
+ ... print(f"({u}, {v}) -> {p:.8f}")
95
+ (0, 1) -> 0.75000000
96
+ (2, 3) -> 0.75000000
97
+
98
+ References
99
+ ----------
100
+ .. [1] T. Zhou, L. Lu, Y.-C. Zhang.
101
+ Predicting missing links via local information.
102
+ Eur. Phys. J. B 71 (2009) 623.
103
+ https://arxiv.org/pdf/0901.0553.pdf
104
+ """
105
+
106
+ def predict(u, v):
107
+ return sum(1 / G.degree(w) for w in nx.common_neighbors(G, u, v))
108
+
109
+ return _apply_prediction(G, predict, ebunch)
110
+
111
+
112
+ @not_implemented_for("directed")
113
+ @not_implemented_for("multigraph")
114
+ @nx._dispatchable
115
+ def jaccard_coefficient(G, ebunch=None):
116
+ r"""Compute the Jaccard coefficient of all node pairs in ebunch.
117
+
118
+ Jaccard coefficient of nodes `u` and `v` is defined as
119
+
120
+ .. math::
121
+
122
+ \frac{|\Gamma(u) \cap \Gamma(v)|}{|\Gamma(u) \cup \Gamma(v)|}
123
+
124
+ where $\Gamma(u)$ denotes the set of neighbors of $u$.
125
+
126
+ Parameters
127
+ ----------
128
+ G : graph
129
+ A NetworkX undirected graph.
130
+
131
+ ebunch : iterable of node pairs, optional (default = None)
132
+ Jaccard coefficient will be computed for each pair of nodes
133
+ given in the iterable. The pairs must be given as 2-tuples
134
+ (u, v) where u and v are nodes in the graph. If ebunch is None
135
+ then all nonexistent edges in the graph will be used.
136
+ Default value: None.
137
+
138
+ Returns
139
+ -------
140
+ piter : iterator
141
+ An iterator of 3-tuples in the form (u, v, p) where (u, v) is a
142
+ pair of nodes and p is their Jaccard coefficient.
143
+
144
+ Raises
145
+ ------
146
+ NetworkXNotImplemented
147
+ If `G` is a `DiGraph`, a `Multigraph` or a `MultiDiGraph`.
148
+
149
+ NodeNotFound
150
+ If `ebunch` has a node that is not in `G`.
151
+
152
+ Examples
153
+ --------
154
+ >>> G = nx.complete_graph(5)
155
+ >>> preds = nx.jaccard_coefficient(G, [(0, 1), (2, 3)])
156
+ >>> for u, v, p in preds:
157
+ ... print(f"({u}, {v}) -> {p:.8f}")
158
+ (0, 1) -> 0.60000000
159
+ (2, 3) -> 0.60000000
160
+
161
+ References
162
+ ----------
163
+ .. [1] D. Liben-Nowell, J. Kleinberg.
164
+ The Link Prediction Problem for Social Networks (2004).
165
+ http://www.cs.cornell.edu/home/kleinber/link-pred.pdf
166
+ """
167
+
168
+ def predict(u, v):
169
+ union_size = len(set(G[u]) | set(G[v]))
170
+ if union_size == 0:
171
+ return 0
172
+ return len(nx.common_neighbors(G, u, v)) / union_size
173
+
174
+ return _apply_prediction(G, predict, ebunch)
175
+
176
+
177
+ @not_implemented_for("directed")
178
+ @not_implemented_for("multigraph")
179
+ @nx._dispatchable
180
+ def adamic_adar_index(G, ebunch=None):
181
+ r"""Compute the Adamic-Adar index of all node pairs in ebunch.
182
+
183
+ Adamic-Adar index of `u` and `v` is defined as
184
+
185
+ .. math::
186
+
187
+ \sum_{w \in \Gamma(u) \cap \Gamma(v)} \frac{1}{\log |\Gamma(w)|}
188
+
189
+ where $\Gamma(u)$ denotes the set of neighbors of $u$.
190
+ This index leads to zero-division for nodes only connected via self-loops.
191
+ It is intended to be used when no self-loops are present.
192
+
193
+ Parameters
194
+ ----------
195
+ G : graph
196
+ NetworkX undirected graph.
197
+
198
+ ebunch : iterable of node pairs, optional (default = None)
199
+ Adamic-Adar index will be computed for each pair of nodes given
200
+ in the iterable. The pairs must be given as 2-tuples (u, v)
201
+ where u and v are nodes in the graph. If ebunch is None then all
202
+ nonexistent edges in the graph will be used.
203
+ Default value: None.
204
+
205
+ Returns
206
+ -------
207
+ piter : iterator
208
+ An iterator of 3-tuples in the form (u, v, p) where (u, v) is a
209
+ pair of nodes and p is their Adamic-Adar index.
210
+
211
+ Raises
212
+ ------
213
+ NetworkXNotImplemented
214
+ If `G` is a `DiGraph`, a `Multigraph` or a `MultiDiGraph`.
215
+
216
+ NodeNotFound
217
+ If `ebunch` has a node that is not in `G`.
218
+
219
+ Examples
220
+ --------
221
+ >>> G = nx.complete_graph(5)
222
+ >>> preds = nx.adamic_adar_index(G, [(0, 1), (2, 3)])
223
+ >>> for u, v, p in preds:
224
+ ... print(f"({u}, {v}) -> {p:.8f}")
225
+ (0, 1) -> 2.16404256
226
+ (2, 3) -> 2.16404256
227
+
228
+ References
229
+ ----------
230
+ .. [1] D. Liben-Nowell, J. Kleinberg.
231
+ The Link Prediction Problem for Social Networks (2004).
232
+ http://www.cs.cornell.edu/home/kleinber/link-pred.pdf
233
+ """
234
+
235
+ def predict(u, v):
236
+ return sum(1 / log(G.degree(w)) for w in nx.common_neighbors(G, u, v))
237
+
238
+ return _apply_prediction(G, predict, ebunch)
239
+
240
+
241
+ @not_implemented_for("directed")
242
+ @not_implemented_for("multigraph")
243
+ @nx._dispatchable
244
+ def common_neighbor_centrality(G, ebunch=None, alpha=0.8):
245
+ r"""Return the CCPA score for each pair of nodes.
246
+
247
+ Compute the Common Neighbor and Centrality based Parameterized Algorithm(CCPA)
248
+ score of all node pairs in ebunch.
249
+
250
+ CCPA score of `u` and `v` is defined as
251
+
252
+ .. math::
253
+
254
+ \alpha \cdot (|\Gamma (u){\cap }^{}\Gamma (v)|)+(1-\alpha )\cdot \frac{N}{{d}_{uv}}
255
+
256
+ where $\Gamma(u)$ denotes the set of neighbors of $u$, $\Gamma(v)$ denotes the
257
+ set of neighbors of $v$, $\alpha$ is parameter varies between [0,1], $N$ denotes
258
+ total number of nodes in the Graph and ${d}_{uv}$ denotes shortest distance
259
+ between $u$ and $v$.
260
+
261
+ This algorithm is based on two vital properties of nodes, namely the number
262
+ of common neighbors and their centrality. Common neighbor refers to the common
263
+ nodes between two nodes. Centrality refers to the prestige that a node enjoys
264
+ in a network.
265
+
266
+ .. seealso::
267
+
268
+ :func:`common_neighbors`
269
+
270
+ Parameters
271
+ ----------
272
+ G : graph
273
+ NetworkX undirected graph.
274
+
275
+ ebunch : iterable of node pairs, optional (default = None)
276
+ Preferential attachment score will be computed for each pair of
277
+ nodes given in the iterable. The pairs must be given as
278
+ 2-tuples (u, v) where u and v are nodes in the graph. If ebunch
279
+ is None then all nonexistent edges in the graph will be used.
280
+ Default value: None.
281
+
282
+ alpha : Parameter defined for participation of Common Neighbor
283
+ and Centrality Algorithm share. Values for alpha should
284
+ normally be between 0 and 1. Default value set to 0.8
285
+ because author found better performance at 0.8 for all the
286
+ dataset.
287
+ Default value: 0.8
288
+
289
+
290
+ Returns
291
+ -------
292
+ piter : iterator
293
+ An iterator of 3-tuples in the form (u, v, p) where (u, v) is a
294
+ pair of nodes and p is their Common Neighbor and Centrality based
295
+ Parameterized Algorithm(CCPA) score.
296
+
297
+ Raises
298
+ ------
299
+ NetworkXNotImplemented
300
+ If `G` is a `DiGraph`, a `Multigraph` or a `MultiDiGraph`.
301
+
302
+ NetworkXAlgorithmError
303
+ If self loops exsists in `ebunch` or in `G` (if `ebunch` is `None`).
304
+
305
+ NodeNotFound
306
+ If `ebunch` has a node that is not in `G`.
307
+
308
+ Examples
309
+ --------
310
+ >>> G = nx.complete_graph(5)
311
+ >>> preds = nx.common_neighbor_centrality(G, [(0, 1), (2, 3)])
312
+ >>> for u, v, p in preds:
313
+ ... print(f"({u}, {v}) -> {p}")
314
+ (0, 1) -> 3.4000000000000004
315
+ (2, 3) -> 3.4000000000000004
316
+
317
+ References
318
+ ----------
319
+ .. [1] Ahmad, I., Akhtar, M.U., Noor, S. et al.
320
+ Missing Link Prediction using Common Neighbor and Centrality based Parameterized Algorithm.
321
+ Sci Rep 10, 364 (2020).
322
+ https://doi.org/10.1038/s41598-019-57304-y
323
+ """
324
+
325
+ # When alpha == 1, the CCPA score simplifies to the number of common neighbors.
326
+ if alpha == 1:
327
+
328
+ def predict(u, v):
329
+ if u == v:
330
+ raise nx.NetworkXAlgorithmError("Self loops are not supported")
331
+
332
+ return len(nx.common_neighbors(G, u, v))
333
+
334
+ else:
335
+ spl = dict(nx.shortest_path_length(G))
336
+ inf = float("inf")
337
+
338
+ def predict(u, v):
339
+ if u == v:
340
+ raise nx.NetworkXAlgorithmError("Self loops are not supported")
341
+ path_len = spl[u].get(v, inf)
342
+
343
+ n_nbrs = len(nx.common_neighbors(G, u, v))
344
+ return alpha * n_nbrs + (1 - alpha) * len(G) / path_len
345
+
346
+ return _apply_prediction(G, predict, ebunch)
347
+
348
+
349
+ @not_implemented_for("directed")
350
+ @not_implemented_for("multigraph")
351
+ @nx._dispatchable
352
+ def preferential_attachment(G, ebunch=None):
353
+ r"""Compute the preferential attachment score of all node pairs in ebunch.
354
+
355
+ Preferential attachment score of `u` and `v` is defined as
356
+
357
+ .. math::
358
+
359
+ |\Gamma(u)| |\Gamma(v)|
360
+
361
+ where $\Gamma(u)$ denotes the set of neighbors of $u$.
362
+
363
+ Parameters
364
+ ----------
365
+ G : graph
366
+ NetworkX undirected graph.
367
+
368
+ ebunch : iterable of node pairs, optional (default = None)
369
+ Preferential attachment score will be computed for each pair of
370
+ nodes given in the iterable. The pairs must be given as
371
+ 2-tuples (u, v) where u and v are nodes in the graph. If ebunch
372
+ is None then all nonexistent edges in the graph will be used.
373
+ Default value: None.
374
+
375
+ Returns
376
+ -------
377
+ piter : iterator
378
+ An iterator of 3-tuples in the form (u, v, p) where (u, v) is a
379
+ pair of nodes and p is their preferential attachment score.
380
+
381
+ Raises
382
+ ------
383
+ NetworkXNotImplemented
384
+ If `G` is a `DiGraph`, a `Multigraph` or a `MultiDiGraph`.
385
+
386
+ NodeNotFound
387
+ If `ebunch` has a node that is not in `G`.
388
+
389
+ Examples
390
+ --------
391
+ >>> G = nx.complete_graph(5)
392
+ >>> preds = nx.preferential_attachment(G, [(0, 1), (2, 3)])
393
+ >>> for u, v, p in preds:
394
+ ... print(f"({u}, {v}) -> {p}")
395
+ (0, 1) -> 16
396
+ (2, 3) -> 16
397
+
398
+ References
399
+ ----------
400
+ .. [1] D. Liben-Nowell, J. Kleinberg.
401
+ The Link Prediction Problem for Social Networks (2004).
402
+ http://www.cs.cornell.edu/home/kleinber/link-pred.pdf
403
+ """
404
+
405
+ def predict(u, v):
406
+ return G.degree(u) * G.degree(v)
407
+
408
+ return _apply_prediction(G, predict, ebunch)
409
+
410
+
411
+ @not_implemented_for("directed")
412
+ @not_implemented_for("multigraph")
413
+ @nx._dispatchable(node_attrs="community")
414
+ def cn_soundarajan_hopcroft(G, ebunch=None, community="community"):
415
+ r"""Count the number of common neighbors of all node pairs in ebunch
416
+ using community information.
417
+
418
+ For two nodes $u$ and $v$, this function computes the number of
419
+ common neighbors and bonus one for each common neighbor belonging to
420
+ the same community as $u$ and $v$. Mathematically,
421
+
422
+ .. math::
423
+
424
+ |\Gamma(u) \cap \Gamma(v)| + \sum_{w \in \Gamma(u) \cap \Gamma(v)} f(w)
425
+
426
+ where $f(w)$ equals 1 if $w$ belongs to the same community as $u$
427
+ and $v$ or 0 otherwise and $\Gamma(u)$ denotes the set of
428
+ neighbors of $u$.
429
+
430
+ Parameters
431
+ ----------
432
+ G : graph
433
+ A NetworkX undirected graph.
434
+
435
+ ebunch : iterable of node pairs, optional (default = None)
436
+ The score will be computed for each pair of nodes given in the
437
+ iterable. The pairs must be given as 2-tuples (u, v) where u
438
+ and v are nodes in the graph. If ebunch is None then all
439
+ nonexistent edges in the graph will be used.
440
+ Default value: None.
441
+
442
+ community : string, optional (default = 'community')
443
+ Nodes attribute name containing the community information.
444
+ G[u][community] identifies which community u belongs to. Each
445
+ node belongs to at most one community. Default value: 'community'.
446
+
447
+ Returns
448
+ -------
449
+ piter : iterator
450
+ An iterator of 3-tuples in the form (u, v, p) where (u, v) is a
451
+ pair of nodes and p is their score.
452
+
453
+ Raises
454
+ ------
455
+ NetworkXNotImplemented
456
+ If `G` is a `DiGraph`, a `Multigraph` or a `MultiDiGraph`.
457
+
458
+ NetworkXAlgorithmError
459
+ If no community information is available for a node in `ebunch` or in `G` (if `ebunch` is `None`).
460
+
461
+ NodeNotFound
462
+ If `ebunch` has a node that is not in `G`.
463
+
464
+ Examples
465
+ --------
466
+ >>> G = nx.path_graph(3)
467
+ >>> G.nodes[0]["community"] = 0
468
+ >>> G.nodes[1]["community"] = 0
469
+ >>> G.nodes[2]["community"] = 0
470
+ >>> preds = nx.cn_soundarajan_hopcroft(G, [(0, 2)])
471
+ >>> for u, v, p in preds:
472
+ ... print(f"({u}, {v}) -> {p}")
473
+ (0, 2) -> 2
474
+
475
+ References
476
+ ----------
477
+ .. [1] Sucheta Soundarajan and John Hopcroft.
478
+ Using community information to improve the precision of link
479
+ prediction methods.
480
+ In Proceedings of the 21st international conference companion on
481
+ World Wide Web (WWW '12 Companion). ACM, New York, NY, USA, 607-608.
482
+ http://doi.acm.org/10.1145/2187980.2188150
483
+ """
484
+
485
+ def predict(u, v):
486
+ Cu = _community(G, u, community)
487
+ Cv = _community(G, v, community)
488
+ cnbors = nx.common_neighbors(G, u, v)
489
+ neighbors = (
490
+ sum(_community(G, w, community) == Cu for w in cnbors) if Cu == Cv else 0
491
+ )
492
+ return len(cnbors) + neighbors
493
+
494
+ return _apply_prediction(G, predict, ebunch)
495
+
496
+
497
+ @not_implemented_for("directed")
498
+ @not_implemented_for("multigraph")
499
+ @nx._dispatchable(node_attrs="community")
500
+ def ra_index_soundarajan_hopcroft(G, ebunch=None, community="community"):
501
+ r"""Compute the resource allocation index of all node pairs in
502
+ ebunch using community information.
503
+
504
+ For two nodes $u$ and $v$, this function computes the resource
505
+ allocation index considering only common neighbors belonging to the
506
+ same community as $u$ and $v$. Mathematically,
507
+
508
+ .. math::
509
+
510
+ \sum_{w \in \Gamma(u) \cap \Gamma(v)} \frac{f(w)}{|\Gamma(w)|}
511
+
512
+ where $f(w)$ equals 1 if $w$ belongs to the same community as $u$
513
+ and $v$ or 0 otherwise and $\Gamma(u)$ denotes the set of
514
+ neighbors of $u$.
515
+
516
+ Parameters
517
+ ----------
518
+ G : graph
519
+ A NetworkX undirected graph.
520
+
521
+ ebunch : iterable of node pairs, optional (default = None)
522
+ The score will be computed for each pair of nodes given in the
523
+ iterable. The pairs must be given as 2-tuples (u, v) where u
524
+ and v are nodes in the graph. If ebunch is None then all
525
+ nonexistent edges in the graph will be used.
526
+ Default value: None.
527
+
528
+ community : string, optional (default = 'community')
529
+ Nodes attribute name containing the community information.
530
+ G[u][community] identifies which community u belongs to. Each
531
+ node belongs to at most one community. Default value: 'community'.
532
+
533
+ Returns
534
+ -------
535
+ piter : iterator
536
+ An iterator of 3-tuples in the form (u, v, p) where (u, v) is a
537
+ pair of nodes and p is their score.
538
+
539
+ Raises
540
+ ------
541
+ NetworkXNotImplemented
542
+ If `G` is a `DiGraph`, a `Multigraph` or a `MultiDiGraph`.
543
+
544
+ NetworkXAlgorithmError
545
+ If no community information is available for a node in `ebunch` or in `G` (if `ebunch` is `None`).
546
+
547
+ NodeNotFound
548
+ If `ebunch` has a node that is not in `G`.
549
+
550
+ Examples
551
+ --------
552
+ >>> G = nx.Graph()
553
+ >>> G.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 3)])
554
+ >>> G.nodes[0]["community"] = 0
555
+ >>> G.nodes[1]["community"] = 0
556
+ >>> G.nodes[2]["community"] = 1
557
+ >>> G.nodes[3]["community"] = 0
558
+ >>> preds = nx.ra_index_soundarajan_hopcroft(G, [(0, 3)])
559
+ >>> for u, v, p in preds:
560
+ ... print(f"({u}, {v}) -> {p:.8f}")
561
+ (0, 3) -> 0.50000000
562
+
563
+ References
564
+ ----------
565
+ .. [1] Sucheta Soundarajan and John Hopcroft.
566
+ Using community information to improve the precision of link
567
+ prediction methods.
568
+ In Proceedings of the 21st international conference companion on
569
+ World Wide Web (WWW '12 Companion). ACM, New York, NY, USA, 607-608.
570
+ http://doi.acm.org/10.1145/2187980.2188150
571
+ """
572
+
573
+ def predict(u, v):
574
+ Cu = _community(G, u, community)
575
+ Cv = _community(G, v, community)
576
+ if Cu != Cv:
577
+ return 0
578
+ cnbors = nx.common_neighbors(G, u, v)
579
+ return sum(1 / G.degree(w) for w in cnbors if _community(G, w, community) == Cu)
580
+
581
+ return _apply_prediction(G, predict, ebunch)
582
+
583
+
584
+ @not_implemented_for("directed")
585
+ @not_implemented_for("multigraph")
586
+ @nx._dispatchable(node_attrs="community")
587
+ def within_inter_cluster(G, ebunch=None, delta=0.001, community="community"):
588
+ """Compute the ratio of within- and inter-cluster common neighbors
589
+ of all node pairs in ebunch.
590
+
591
+ For two nodes `u` and `v`, if a common neighbor `w` belongs to the
592
+ same community as them, `w` is considered as within-cluster common
593
+ neighbor of `u` and `v`. Otherwise, it is considered as
594
+ inter-cluster common neighbor of `u` and `v`. The ratio between the
595
+ size of the set of within- and inter-cluster common neighbors is
596
+ defined as the WIC measure. [1]_
597
+
598
+ Parameters
599
+ ----------
600
+ G : graph
601
+ A NetworkX undirected graph.
602
+
603
+ ebunch : iterable of node pairs, optional (default = None)
604
+ The WIC measure will be computed for each pair of nodes given in
605
+ the iterable. The pairs must be given as 2-tuples (u, v) where
606
+ u and v are nodes in the graph. If ebunch is None then all
607
+ nonexistent edges in the graph will be used.
608
+ Default value: None.
609
+
610
+ delta : float, optional (default = 0.001)
611
+ Value to prevent division by zero in case there is no
612
+ inter-cluster common neighbor between two nodes. See [1]_ for
613
+ details. Default value: 0.001.
614
+
615
+ community : string, optional (default = 'community')
616
+ Nodes attribute name containing the community information.
617
+ G[u][community] identifies which community u belongs to. Each
618
+ node belongs to at most one community. Default value: 'community'.
619
+
620
+ Returns
621
+ -------
622
+ piter : iterator
623
+ An iterator of 3-tuples in the form (u, v, p) where (u, v) is a
624
+ pair of nodes and p is their WIC measure.
625
+
626
+ Raises
627
+ ------
628
+ NetworkXNotImplemented
629
+ If `G` is a `DiGraph`, a `Multigraph` or a `MultiDiGraph`.
630
+
631
+ NetworkXAlgorithmError
632
+ - If `delta` is less than or equal to zero.
633
+ - If no community information is available for a node in `ebunch` or in `G` (if `ebunch` is `None`).
634
+
635
+ NodeNotFound
636
+ If `ebunch` has a node that is not in `G`.
637
+
638
+ Examples
639
+ --------
640
+ >>> G = nx.Graph()
641
+ >>> G.add_edges_from([(0, 1), (0, 2), (0, 3), (1, 4), (2, 4), (3, 4)])
642
+ >>> G.nodes[0]["community"] = 0
643
+ >>> G.nodes[1]["community"] = 1
644
+ >>> G.nodes[2]["community"] = 0
645
+ >>> G.nodes[3]["community"] = 0
646
+ >>> G.nodes[4]["community"] = 0
647
+ >>> preds = nx.within_inter_cluster(G, [(0, 4)])
648
+ >>> for u, v, p in preds:
649
+ ... print(f"({u}, {v}) -> {p:.8f}")
650
+ (0, 4) -> 1.99800200
651
+ >>> preds = nx.within_inter_cluster(G, [(0, 4)], delta=0.5)
652
+ >>> for u, v, p in preds:
653
+ ... print(f"({u}, {v}) -> {p:.8f}")
654
+ (0, 4) -> 1.33333333
655
+
656
+ References
657
+ ----------
658
+ .. [1] Jorge Carlos Valverde-Rebaza and Alneu de Andrade Lopes.
659
+ Link prediction in complex networks based on cluster information.
660
+ In Proceedings of the 21st Brazilian conference on Advances in
661
+ Artificial Intelligence (SBIA'12)
662
+ https://doi.org/10.1007/978-3-642-34459-6_10
663
+ """
664
+ if delta <= 0:
665
+ raise nx.NetworkXAlgorithmError("Delta must be greater than zero")
666
+
667
+ def predict(u, v):
668
+ Cu = _community(G, u, community)
669
+ Cv = _community(G, v, community)
670
+ if Cu != Cv:
671
+ return 0
672
+ cnbors = nx.common_neighbors(G, u, v)
673
+ within = {w for w in cnbors if _community(G, w, community) == Cu}
674
+ inter = cnbors - within
675
+ return len(within) / (len(inter) + delta)
676
+
677
+ return _apply_prediction(G, predict, ebunch)
678
+
679
+
680
+ def _community(G, u, community):
681
+ """Get the community of the given node."""
682
+ node_u = G.nodes[u]
683
+ try:
684
+ return node_u[community]
685
+ except KeyError as err:
686
+ raise nx.NetworkXAlgorithmError(
687
+ f"No community information available for Node {u}"
688
+ ) from err
pythonProject/.venv/Lib/site-packages/networkx/algorithms/lowest_common_ancestors.py ADDED
@@ -0,0 +1,268 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Algorithms for finding the lowest common ancestor of trees and DAGs."""
2
+ from collections import defaultdict
3
+ from collections.abc import Mapping, Set
4
+ from itertools import combinations_with_replacement
5
+
6
+ import networkx as nx
7
+ from networkx.utils import UnionFind, arbitrary_element, not_implemented_for
8
+
9
+ __all__ = [
10
+ "all_pairs_lowest_common_ancestor",
11
+ "tree_all_pairs_lowest_common_ancestor",
12
+ "lowest_common_ancestor",
13
+ ]
14
+
15
+
16
+ @not_implemented_for("undirected")
17
+ @nx._dispatchable
18
+ def all_pairs_lowest_common_ancestor(G, pairs=None):
19
+ """Return the lowest common ancestor of all pairs or the provided pairs
20
+
21
+ Parameters
22
+ ----------
23
+ G : NetworkX directed graph
24
+
25
+ pairs : iterable of pairs of nodes, optional (default: all pairs)
26
+ The pairs of nodes of interest.
27
+ If None, will find the LCA of all pairs of nodes.
28
+
29
+ Yields
30
+ ------
31
+ ((node1, node2), lca) : 2-tuple
32
+ Where lca is least common ancestor of node1 and node2.
33
+ Note that for the default case, the order of the node pair is not considered,
34
+ e.g. you will not get both ``(a, b)`` and ``(b, a)``
35
+
36
+ Raises
37
+ ------
38
+ NetworkXPointlessConcept
39
+ If `G` is null.
40
+ NetworkXError
41
+ If `G` is not a DAG.
42
+
43
+ Examples
44
+ --------
45
+ The default behavior is to yield the lowest common ancestor for all
46
+ possible combinations of nodes in `G`, including self-pairings:
47
+
48
+ >>> G = nx.DiGraph([(0, 1), (0, 3), (1, 2)])
49
+ >>> dict(nx.all_pairs_lowest_common_ancestor(G))
50
+ {(0, 0): 0, (0, 1): 0, (0, 3): 0, (0, 2): 0, (1, 1): 1, (1, 3): 0, (1, 2): 1, (3, 3): 3, (3, 2): 0, (2, 2): 2}
51
+
52
+ The pairs argument can be used to limit the output to only the
53
+ specified node pairings:
54
+
55
+ >>> dict(nx.all_pairs_lowest_common_ancestor(G, pairs=[(1, 2), (2, 3)]))
56
+ {(1, 2): 1, (2, 3): 0}
57
+
58
+ Notes
59
+ -----
60
+ Only defined on non-null directed acyclic graphs.
61
+
62
+ See Also
63
+ --------
64
+ lowest_common_ancestor
65
+ """
66
+ if not nx.is_directed_acyclic_graph(G):
67
+ raise nx.NetworkXError("LCA only defined on directed acyclic graphs.")
68
+ if len(G) == 0:
69
+ raise nx.NetworkXPointlessConcept("LCA meaningless on null graphs.")
70
+
71
+ if pairs is None:
72
+ pairs = combinations_with_replacement(G, 2)
73
+ else:
74
+ # Convert iterator to iterable, if necessary. Trim duplicates.
75
+ pairs = dict.fromkeys(pairs)
76
+ # Verify that each of the nodes in the provided pairs is in G
77
+ nodeset = set(G)
78
+ for pair in pairs:
79
+ if set(pair) - nodeset:
80
+ raise nx.NodeNotFound(
81
+ f"Node(s) {set(pair) - nodeset} from pair {pair} not in G."
82
+ )
83
+
84
+ # Once input validation is done, construct the generator
85
+ def generate_lca_from_pairs(G, pairs):
86
+ ancestor_cache = {}
87
+
88
+ for v, w in pairs:
89
+ if v not in ancestor_cache:
90
+ ancestor_cache[v] = nx.ancestors(G, v)
91
+ ancestor_cache[v].add(v)
92
+ if w not in ancestor_cache:
93
+ ancestor_cache[w] = nx.ancestors(G, w)
94
+ ancestor_cache[w].add(w)
95
+
96
+ common_ancestors = ancestor_cache[v] & ancestor_cache[w]
97
+
98
+ if common_ancestors:
99
+ common_ancestor = next(iter(common_ancestors))
100
+ while True:
101
+ successor = None
102
+ for lower_ancestor in G.successors(common_ancestor):
103
+ if lower_ancestor in common_ancestors:
104
+ successor = lower_ancestor
105
+ break
106
+ if successor is None:
107
+ break
108
+ common_ancestor = successor
109
+ yield ((v, w), common_ancestor)
110
+
111
+ return generate_lca_from_pairs(G, pairs)
112
+
113
+
114
+ @not_implemented_for("undirected")
115
+ @nx._dispatchable
116
+ def lowest_common_ancestor(G, node1, node2, default=None):
117
+ """Compute the lowest common ancestor of the given pair of nodes.
118
+
119
+ Parameters
120
+ ----------
121
+ G : NetworkX directed graph
122
+
123
+ node1, node2 : nodes in the graph.
124
+
125
+ default : object
126
+ Returned if no common ancestor between `node1` and `node2`
127
+
128
+ Returns
129
+ -------
130
+ The lowest common ancestor of node1 and node2,
131
+ or default if they have no common ancestors.
132
+
133
+ Examples
134
+ --------
135
+ >>> G = nx.DiGraph()
136
+ >>> nx.add_path(G, (0, 1, 2, 3))
137
+ >>> nx.add_path(G, (0, 4, 3))
138
+ >>> nx.lowest_common_ancestor(G, 2, 4)
139
+ 0
140
+
141
+ See Also
142
+ --------
143
+ all_pairs_lowest_common_ancestor"""
144
+
145
+ ans = list(all_pairs_lowest_common_ancestor(G, pairs=[(node1, node2)]))
146
+ if ans:
147
+ assert len(ans) == 1
148
+ return ans[0][1]
149
+ return default
150
+
151
+
152
+ @not_implemented_for("undirected")
153
+ @nx._dispatchable
154
+ def tree_all_pairs_lowest_common_ancestor(G, root=None, pairs=None):
155
+ r"""Yield the lowest common ancestor for sets of pairs in a tree.
156
+
157
+ Parameters
158
+ ----------
159
+ G : NetworkX directed graph (must be a tree)
160
+
161
+ root : node, optional (default: None)
162
+ The root of the subtree to operate on.
163
+ If None, assume the entire graph has exactly one source and use that.
164
+
165
+ pairs : iterable or iterator of pairs of nodes, optional (default: None)
166
+ The pairs of interest. If None, Defaults to all pairs of nodes
167
+ under `root` that have a lowest common ancestor.
168
+
169
+ Returns
170
+ -------
171
+ lcas : generator of tuples `((u, v), lca)` where `u` and `v` are nodes
172
+ in `pairs` and `lca` is their lowest common ancestor.
173
+
174
+ Examples
175
+ --------
176
+ >>> import pprint
177
+ >>> G = nx.DiGraph([(1, 3), (2, 4), (1, 2)])
178
+ >>> pprint.pprint(dict(nx.tree_all_pairs_lowest_common_ancestor(G)))
179
+ {(1, 1): 1,
180
+ (2, 1): 1,
181
+ (2, 2): 2,
182
+ (3, 1): 1,
183
+ (3, 2): 1,
184
+ (3, 3): 3,
185
+ (3, 4): 1,
186
+ (4, 1): 1,
187
+ (4, 2): 2,
188
+ (4, 4): 4}
189
+
190
+ We can also use `pairs` argument to specify the pairs of nodes for which we
191
+ want to compute lowest common ancestors. Here is an example:
192
+
193
+ >>> dict(nx.tree_all_pairs_lowest_common_ancestor(G, pairs=[(1, 4), (2, 3)]))
194
+ {(2, 3): 1, (1, 4): 1}
195
+
196
+ Notes
197
+ -----
198
+ Only defined on non-null trees represented with directed edges from
199
+ parents to children. Uses Tarjan's off-line lowest-common-ancestors
200
+ algorithm. Runs in time $O(4 \times (V + E + P))$ time, where 4 is the largest
201
+ value of the inverse Ackermann function likely to ever come up in actual
202
+ use, and $P$ is the number of pairs requested (or $V^2$ if all are needed).
203
+
204
+ Tarjan, R. E. (1979), "Applications of path compression on balanced trees",
205
+ Journal of the ACM 26 (4): 690-715, doi:10.1145/322154.322161.
206
+
207
+ See Also
208
+ --------
209
+ all_pairs_lowest_common_ancestor: similar routine for general DAGs
210
+ lowest_common_ancestor: just a single pair for general DAGs
211
+ """
212
+ if len(G) == 0:
213
+ raise nx.NetworkXPointlessConcept("LCA meaningless on null graphs.")
214
+
215
+ # Index pairs of interest for efficient lookup from either side.
216
+ if pairs is not None:
217
+ pair_dict = defaultdict(set)
218
+ # See note on all_pairs_lowest_common_ancestor.
219
+ if not isinstance(pairs, Mapping | Set):
220
+ pairs = set(pairs)
221
+ for u, v in pairs:
222
+ for n in (u, v):
223
+ if n not in G:
224
+ msg = f"The node {str(n)} is not in the digraph."
225
+ raise nx.NodeNotFound(msg)
226
+ pair_dict[u].add(v)
227
+ pair_dict[v].add(u)
228
+
229
+ # If root is not specified, find the exactly one node with in degree 0 and
230
+ # use it. Raise an error if none are found, or more than one is. Also check
231
+ # for any nodes with in degree larger than 1, which would imply G is not a
232
+ # tree.
233
+ if root is None:
234
+ for n, deg in G.in_degree:
235
+ if deg == 0:
236
+ if root is not None:
237
+ msg = "No root specified and tree has multiple sources."
238
+ raise nx.NetworkXError(msg)
239
+ root = n
240
+ # checking deg>1 is not sufficient for MultiDiGraphs
241
+ elif deg > 1 and len(G.pred[n]) > 1:
242
+ msg = "Tree LCA only defined on trees; use DAG routine."
243
+ raise nx.NetworkXError(msg)
244
+ if root is None:
245
+ raise nx.NetworkXError("Graph contains a cycle.")
246
+
247
+ # Iterative implementation of Tarjan's offline lca algorithm
248
+ # as described in CLRS on page 521 (2nd edition)/page 584 (3rd edition)
249
+ uf = UnionFind()
250
+ ancestors = {}
251
+ for node in G:
252
+ ancestors[node] = uf[node]
253
+
254
+ colors = defaultdict(bool)
255
+ for node in nx.dfs_postorder_nodes(G, root):
256
+ colors[node] = True
257
+ for v in pair_dict[node] if pairs is not None else G:
258
+ if colors[v]:
259
+ # If the user requested both directions of a pair, give it.
260
+ # Otherwise, just give one.
261
+ if pairs is not None and (node, v) in pairs:
262
+ yield (node, v), ancestors[uf[v]]
263
+ if pairs is None or (v, node) in pairs:
264
+ yield (v, node), ancestors[uf[v]]
265
+ if node != root:
266
+ parent = arbitrary_element(G.pred[node])
267
+ uf.union(parent, node)
268
+ ancestors[uf[parent]] = parent
pythonProject/.venv/Lib/site-packages/networkx/algorithms/matching.py ADDED
@@ -0,0 +1,1151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Functions for computing and verifying matchings in a graph."""
2
+ from collections import Counter
3
+ from itertools import combinations, repeat
4
+
5
+ import networkx as nx
6
+ from networkx.utils import not_implemented_for
7
+
8
+ __all__ = [
9
+ "is_matching",
10
+ "is_maximal_matching",
11
+ "is_perfect_matching",
12
+ "max_weight_matching",
13
+ "min_weight_matching",
14
+ "maximal_matching",
15
+ ]
16
+
17
+
18
+ @not_implemented_for("multigraph")
19
+ @not_implemented_for("directed")
20
+ @nx._dispatchable
21
+ def maximal_matching(G):
22
+ r"""Find a maximal matching in the graph.
23
+
24
+ A matching is a subset of edges in which no node occurs more than once.
25
+ A maximal matching cannot add more edges and still be a matching.
26
+
27
+ Parameters
28
+ ----------
29
+ G : NetworkX graph
30
+ Undirected graph
31
+
32
+ Returns
33
+ -------
34
+ matching : set
35
+ A maximal matching of the graph.
36
+
37
+ Examples
38
+ --------
39
+ >>> G = nx.Graph([(1, 2), (1, 3), (2, 3), (2, 4), (3, 5), (4, 5)])
40
+ >>> sorted(nx.maximal_matching(G))
41
+ [(1, 2), (3, 5)]
42
+
43
+ Notes
44
+ -----
45
+ The algorithm greedily selects a maximal matching M of the graph G
46
+ (i.e. no superset of M exists). It runs in $O(|E|)$ time.
47
+ """
48
+ matching = set()
49
+ nodes = set()
50
+ for edge in G.edges():
51
+ # If the edge isn't covered, add it to the matching
52
+ # then remove neighborhood of u and v from consideration.
53
+ u, v = edge
54
+ if u not in nodes and v not in nodes and u != v:
55
+ matching.add(edge)
56
+ nodes.update(edge)
57
+ return matching
58
+
59
+
60
+ def matching_dict_to_set(matching):
61
+ """Converts matching dict format to matching set format
62
+
63
+ Converts a dictionary representing a matching (as returned by
64
+ :func:`max_weight_matching`) to a set representing a matching (as
65
+ returned by :func:`maximal_matching`).
66
+
67
+ In the definition of maximal matching adopted by NetworkX,
68
+ self-loops are not allowed, so the provided dictionary is expected
69
+ to never have any mapping from a key to itself. However, the
70
+ dictionary is expected to have mirrored key/value pairs, for
71
+ example, key ``u`` with value ``v`` and key ``v`` with value ``u``.
72
+
73
+ """
74
+ edges = set()
75
+ for edge in matching.items():
76
+ u, v = edge
77
+ if (v, u) in edges or edge in edges:
78
+ continue
79
+ if u == v:
80
+ raise nx.NetworkXError(f"Selfloops cannot appear in matchings {edge}")
81
+ edges.add(edge)
82
+ return edges
83
+
84
+
85
+ @nx._dispatchable
86
+ def is_matching(G, matching):
87
+ """Return True if ``matching`` is a valid matching of ``G``
88
+
89
+ A *matching* in a graph is a set of edges in which no two distinct
90
+ edges share a common endpoint. Each node is incident to at most one
91
+ edge in the matching. The edges are said to be independent.
92
+
93
+ Parameters
94
+ ----------
95
+ G : NetworkX graph
96
+
97
+ matching : dict or set
98
+ A dictionary or set representing a matching. If a dictionary, it
99
+ must have ``matching[u] == v`` and ``matching[v] == u`` for each
100
+ edge ``(u, v)`` in the matching. If a set, it must have elements
101
+ of the form ``(u, v)``, where ``(u, v)`` is an edge in the
102
+ matching.
103
+
104
+ Returns
105
+ -------
106
+ bool
107
+ Whether the given set or dictionary represents a valid matching
108
+ in the graph.
109
+
110
+ Raises
111
+ ------
112
+ NetworkXError
113
+ If the proposed matching has an edge to a node not in G.
114
+ Or if the matching is not a collection of 2-tuple edges.
115
+
116
+ Examples
117
+ --------
118
+ >>> G = nx.Graph([(1, 2), (1, 3), (2, 3), (2, 4), (3, 5), (4, 5)])
119
+ >>> nx.is_maximal_matching(G, {1: 3, 2: 4}) # using dict to represent matching
120
+ True
121
+
122
+ >>> nx.is_matching(G, {(1, 3), (2, 4)}) # using set to represent matching
123
+ True
124
+
125
+ """
126
+ if isinstance(matching, dict):
127
+ matching = matching_dict_to_set(matching)
128
+
129
+ nodes = set()
130
+ for edge in matching:
131
+ if len(edge) != 2:
132
+ raise nx.NetworkXError(f"matching has non-2-tuple edge {edge}")
133
+ u, v = edge
134
+ if u not in G or v not in G:
135
+ raise nx.NetworkXError(f"matching contains edge {edge} with node not in G")
136
+ if u == v:
137
+ return False
138
+ if not G.has_edge(u, v):
139
+ return False
140
+ if u in nodes or v in nodes:
141
+ return False
142
+ nodes.update(edge)
143
+ return True
144
+
145
+
146
+ @nx._dispatchable
147
+ def is_maximal_matching(G, matching):
148
+ """Return True if ``matching`` is a maximal matching of ``G``
149
+
150
+ A *maximal matching* in a graph is a matching in which adding any
151
+ edge would cause the set to no longer be a valid matching.
152
+
153
+ Parameters
154
+ ----------
155
+ G : NetworkX graph
156
+
157
+ matching : dict or set
158
+ A dictionary or set representing a matching. If a dictionary, it
159
+ must have ``matching[u] == v`` and ``matching[v] == u`` for each
160
+ edge ``(u, v)`` in the matching. If a set, it must have elements
161
+ of the form ``(u, v)``, where ``(u, v)`` is an edge in the
162
+ matching.
163
+
164
+ Returns
165
+ -------
166
+ bool
167
+ Whether the given set or dictionary represents a valid maximal
168
+ matching in the graph.
169
+
170
+ Examples
171
+ --------
172
+ >>> G = nx.Graph([(1, 2), (1, 3), (2, 3), (3, 4), (3, 5)])
173
+ >>> nx.is_maximal_matching(G, {(1, 2), (3, 4)})
174
+ True
175
+
176
+ """
177
+ if isinstance(matching, dict):
178
+ matching = matching_dict_to_set(matching)
179
+ # If the given set is not a matching, then it is not a maximal matching.
180
+ edges = set()
181
+ nodes = set()
182
+ for edge in matching:
183
+ if len(edge) != 2:
184
+ raise nx.NetworkXError(f"matching has non-2-tuple edge {edge}")
185
+ u, v = edge
186
+ if u not in G or v not in G:
187
+ raise nx.NetworkXError(f"matching contains edge {edge} with node not in G")
188
+ if u == v:
189
+ return False
190
+ if not G.has_edge(u, v):
191
+ return False
192
+ if u in nodes or v in nodes:
193
+ return False
194
+ nodes.update(edge)
195
+ edges.add(edge)
196
+ edges.add((v, u))
197
+ # A matching is maximal if adding any new edge from G to it
198
+ # causes the resulting set to match some node twice.
199
+ # Be careful to check for adding selfloops
200
+ for u, v in G.edges:
201
+ if (u, v) not in edges:
202
+ # could add edge (u, v) to edges and have a bigger matching
203
+ if u not in nodes and v not in nodes and u != v:
204
+ return False
205
+ return True
206
+
207
+
208
+ @nx._dispatchable
209
+ def is_perfect_matching(G, matching):
210
+ """Return True if ``matching`` is a perfect matching for ``G``
211
+
212
+ A *perfect matching* in a graph is a matching in which exactly one edge
213
+ is incident upon each vertex.
214
+
215
+ Parameters
216
+ ----------
217
+ G : NetworkX graph
218
+
219
+ matching : dict or set
220
+ A dictionary or set representing a matching. If a dictionary, it
221
+ must have ``matching[u] == v`` and ``matching[v] == u`` for each
222
+ edge ``(u, v)`` in the matching. If a set, it must have elements
223
+ of the form ``(u, v)``, where ``(u, v)`` is an edge in the
224
+ matching.
225
+
226
+ Returns
227
+ -------
228
+ bool
229
+ Whether the given set or dictionary represents a valid perfect
230
+ matching in the graph.
231
+
232
+ Examples
233
+ --------
234
+ >>> G = nx.Graph([(1, 2), (1, 3), (2, 3), (2, 4), (3, 5), (4, 5), (4, 6)])
235
+ >>> my_match = {1: 2, 3: 5, 4: 6}
236
+ >>> nx.is_perfect_matching(G, my_match)
237
+ True
238
+
239
+ """
240
+ if isinstance(matching, dict):
241
+ matching = matching_dict_to_set(matching)
242
+
243
+ nodes = set()
244
+ for edge in matching:
245
+ if len(edge) != 2:
246
+ raise nx.NetworkXError(f"matching has non-2-tuple edge {edge}")
247
+ u, v = edge
248
+ if u not in G or v not in G:
249
+ raise nx.NetworkXError(f"matching contains edge {edge} with node not in G")
250
+ if u == v:
251
+ return False
252
+ if not G.has_edge(u, v):
253
+ return False
254
+ if u in nodes or v in nodes:
255
+ return False
256
+ nodes.update(edge)
257
+ return len(nodes) == len(G)
258
+
259
+
260
+ @not_implemented_for("multigraph")
261
+ @not_implemented_for("directed")
262
+ @nx._dispatchable(edge_attrs="weight")
263
+ def min_weight_matching(G, weight="weight"):
264
+ """Computing a minimum-weight maximal matching of G.
265
+
266
+ Use the maximum-weight algorithm with edge weights subtracted
267
+ from the maximum weight of all edges.
268
+
269
+ A matching is a subset of edges in which no node occurs more than once.
270
+ The weight of a matching is the sum of the weights of its edges.
271
+ A maximal matching cannot add more edges and still be a matching.
272
+ The cardinality of a matching is the number of matched edges.
273
+
274
+ This method replaces the edge weights with 1 plus the maximum edge weight
275
+ minus the original edge weight.
276
+
277
+ new_weight = (max_weight + 1) - edge_weight
278
+
279
+ then runs :func:`max_weight_matching` with the new weights.
280
+ The max weight matching with these new weights corresponds
281
+ to the min weight matching using the original weights.
282
+ Adding 1 to the max edge weight keeps all edge weights positive
283
+ and as integers if they started as integers.
284
+
285
+ You might worry that adding 1 to each weight would make the algorithm
286
+ favor matchings with more edges. But we use the parameter
287
+ `maxcardinality=True` in `max_weight_matching` to ensure that the
288
+ number of edges in the competing matchings are the same and thus
289
+ the optimum does not change due to changes in the number of edges.
290
+
291
+ Read the documentation of `max_weight_matching` for more information.
292
+
293
+ Parameters
294
+ ----------
295
+ G : NetworkX graph
296
+ Undirected graph
297
+
298
+ weight: string, optional (default='weight')
299
+ Edge data key corresponding to the edge weight.
300
+ If key not found, uses 1 as weight.
301
+
302
+ Returns
303
+ -------
304
+ matching : set
305
+ A minimal weight matching of the graph.
306
+
307
+ See Also
308
+ --------
309
+ max_weight_matching
310
+ """
311
+ if len(G.edges) == 0:
312
+ return max_weight_matching(G, maxcardinality=True, weight=weight)
313
+ G_edges = G.edges(data=weight, default=1)
314
+ max_weight = 1 + max(w for _, _, w in G_edges)
315
+ InvG = nx.Graph()
316
+ edges = ((u, v, max_weight - w) for u, v, w in G_edges)
317
+ InvG.add_weighted_edges_from(edges, weight=weight)
318
+ return max_weight_matching(InvG, maxcardinality=True, weight=weight)
319
+
320
+
321
+ @not_implemented_for("multigraph")
322
+ @not_implemented_for("directed")
323
+ @nx._dispatchable(edge_attrs="weight")
324
+ def max_weight_matching(G, maxcardinality=False, weight="weight"):
325
+ """Compute a maximum-weighted matching of G.
326
+
327
+ A matching is a subset of edges in which no node occurs more than once.
328
+ The weight of a matching is the sum of the weights of its edges.
329
+ A maximal matching cannot add more edges and still be a matching.
330
+ The cardinality of a matching is the number of matched edges.
331
+
332
+ Parameters
333
+ ----------
334
+ G : NetworkX graph
335
+ Undirected graph
336
+
337
+ maxcardinality: bool, optional (default=False)
338
+ If maxcardinality is True, compute the maximum-cardinality matching
339
+ with maximum weight among all maximum-cardinality matchings.
340
+
341
+ weight: string, optional (default='weight')
342
+ Edge data key corresponding to the edge weight.
343
+ If key not found, uses 1 as weight.
344
+
345
+
346
+ Returns
347
+ -------
348
+ matching : set
349
+ A maximal matching of the graph.
350
+
351
+ Examples
352
+ --------
353
+ >>> G = nx.Graph()
354
+ >>> edges = [(1, 2, 6), (1, 3, 2), (2, 3, 1), (2, 4, 7), (3, 5, 9), (4, 5, 3)]
355
+ >>> G.add_weighted_edges_from(edges)
356
+ >>> sorted(nx.max_weight_matching(G))
357
+ [(2, 4), (5, 3)]
358
+
359
+ Notes
360
+ -----
361
+ If G has edges with weight attributes the edge data are used as
362
+ weight values else the weights are assumed to be 1.
363
+
364
+ This function takes time O(number_of_nodes ** 3).
365
+
366
+ If all edge weights are integers, the algorithm uses only integer
367
+ computations. If floating point weights are used, the algorithm
368
+ could return a slightly suboptimal matching due to numeric
369
+ precision errors.
370
+
371
+ This method is based on the "blossom" method for finding augmenting
372
+ paths and the "primal-dual" method for finding a matching of maximum
373
+ weight, both methods invented by Jack Edmonds [1]_.
374
+
375
+ Bipartite graphs can also be matched using the functions present in
376
+ :mod:`networkx.algorithms.bipartite.matching`.
377
+
378
+ References
379
+ ----------
380
+ .. [1] "Efficient Algorithms for Finding Maximum Matching in Graphs",
381
+ Zvi Galil, ACM Computing Surveys, 1986.
382
+ """
383
+ #
384
+ # The algorithm is taken from "Efficient Algorithms for Finding Maximum
385
+ # Matching in Graphs" by Zvi Galil, ACM Computing Surveys, 1986.
386
+ # It is based on the "blossom" method for finding augmenting paths and
387
+ # the "primal-dual" method for finding a matching of maximum weight, both
388
+ # methods invented by Jack Edmonds.
389
+ #
390
+ # A C program for maximum weight matching by Ed Rothberg was used
391
+ # extensively to validate this new code.
392
+ #
393
+ # Many terms used in the code comments are explained in the paper
394
+ # by Galil. You will probably need the paper to make sense of this code.
395
+ #
396
+
397
+ class NoNode:
398
+ """Dummy value which is different from any node."""
399
+
400
+ class Blossom:
401
+ """Representation of a non-trivial blossom or sub-blossom."""
402
+
403
+ __slots__ = ["childs", "edges", "mybestedges"]
404
+
405
+ # b.childs is an ordered list of b's sub-blossoms, starting with
406
+ # the base and going round the blossom.
407
+
408
+ # b.edges is the list of b's connecting edges, such that
409
+ # b.edges[i] = (v, w) where v is a vertex in b.childs[i]
410
+ # and w is a vertex in b.childs[wrap(i+1)].
411
+
412
+ # If b is a top-level S-blossom,
413
+ # b.mybestedges is a list of least-slack edges to neighboring
414
+ # S-blossoms, or None if no such list has been computed yet.
415
+ # This is used for efficient computation of delta3.
416
+
417
+ # Generate the blossom's leaf vertices.
418
+ def leaves(self):
419
+ stack = [*self.childs]
420
+ while stack:
421
+ t = stack.pop()
422
+ if isinstance(t, Blossom):
423
+ stack.extend(t.childs)
424
+ else:
425
+ yield t
426
+
427
+ # Get a list of vertices.
428
+ gnodes = list(G)
429
+ if not gnodes:
430
+ return set() # don't bother with empty graphs
431
+
432
+ # Find the maximum edge weight.
433
+ maxweight = 0
434
+ allinteger = True
435
+ for i, j, d in G.edges(data=True):
436
+ wt = d.get(weight, 1)
437
+ if i != j and wt > maxweight:
438
+ maxweight = wt
439
+ allinteger = allinteger and (str(type(wt)).split("'")[1] in ("int", "long"))
440
+
441
+ # If v is a matched vertex, mate[v] is its partner vertex.
442
+ # If v is a single vertex, v does not occur as a key in mate.
443
+ # Initially all vertices are single; updated during augmentation.
444
+ mate = {}
445
+
446
+ # If b is a top-level blossom,
447
+ # label.get(b) is None if b is unlabeled (free),
448
+ # 1 if b is an S-blossom,
449
+ # 2 if b is a T-blossom.
450
+ # The label of a vertex is found by looking at the label of its top-level
451
+ # containing blossom.
452
+ # If v is a vertex inside a T-blossom, label[v] is 2 iff v is reachable
453
+ # from an S-vertex outside the blossom.
454
+ # Labels are assigned during a stage and reset after each augmentation.
455
+ label = {}
456
+
457
+ # If b is a labeled top-level blossom,
458
+ # labeledge[b] = (v, w) is the edge through which b obtained its label
459
+ # such that w is a vertex in b, or None if b's base vertex is single.
460
+ # If w is a vertex inside a T-blossom and label[w] == 2,
461
+ # labeledge[w] = (v, w) is an edge through which w is reachable from
462
+ # outside the blossom.
463
+ labeledge = {}
464
+
465
+ # If v is a vertex, inblossom[v] is the top-level blossom to which v
466
+ # belongs.
467
+ # If v is a top-level vertex, inblossom[v] == v since v is itself
468
+ # a (trivial) top-level blossom.
469
+ # Initially all vertices are top-level trivial blossoms.
470
+ inblossom = dict(zip(gnodes, gnodes))
471
+
472
+ # If b is a sub-blossom,
473
+ # blossomparent[b] is its immediate parent (sub-)blossom.
474
+ # If b is a top-level blossom, blossomparent[b] is None.
475
+ blossomparent = dict(zip(gnodes, repeat(None)))
476
+
477
+ # If b is a (sub-)blossom,
478
+ # blossombase[b] is its base VERTEX (i.e. recursive sub-blossom).
479
+ blossombase = dict(zip(gnodes, gnodes))
480
+
481
+ # If w is a free vertex (or an unreached vertex inside a T-blossom),
482
+ # bestedge[w] = (v, w) is the least-slack edge from an S-vertex,
483
+ # or None if there is no such edge.
484
+ # If b is a (possibly trivial) top-level S-blossom,
485
+ # bestedge[b] = (v, w) is the least-slack edge to a different S-blossom
486
+ # (v inside b), or None if there is no such edge.
487
+ # This is used for efficient computation of delta2 and delta3.
488
+ bestedge = {}
489
+
490
+ # If v is a vertex,
491
+ # dualvar[v] = 2 * u(v) where u(v) is the v's variable in the dual
492
+ # optimization problem (if all edge weights are integers, multiplication
493
+ # by two ensures that all values remain integers throughout the algorithm).
494
+ # Initially, u(v) = maxweight / 2.
495
+ dualvar = dict(zip(gnodes, repeat(maxweight)))
496
+
497
+ # If b is a non-trivial blossom,
498
+ # blossomdual[b] = z(b) where z(b) is b's variable in the dual
499
+ # optimization problem.
500
+ blossomdual = {}
501
+
502
+ # If (v, w) in allowedge or (w, v) in allowedg, then the edge
503
+ # (v, w) is known to have zero slack in the optimization problem;
504
+ # otherwise the edge may or may not have zero slack.
505
+ allowedge = {}
506
+
507
+ # Queue of newly discovered S-vertices.
508
+ queue = []
509
+
510
+ # Return 2 * slack of edge (v, w) (does not work inside blossoms).
511
+ def slack(v, w):
512
+ return dualvar[v] + dualvar[w] - 2 * G[v][w].get(weight, 1)
513
+
514
+ # Assign label t to the top-level blossom containing vertex w,
515
+ # coming through an edge from vertex v.
516
+ def assignLabel(w, t, v):
517
+ b = inblossom[w]
518
+ assert label.get(w) is None and label.get(b) is None
519
+ label[w] = label[b] = t
520
+ if v is not None:
521
+ labeledge[w] = labeledge[b] = (v, w)
522
+ else:
523
+ labeledge[w] = labeledge[b] = None
524
+ bestedge[w] = bestedge[b] = None
525
+ if t == 1:
526
+ # b became an S-vertex/blossom; add it(s vertices) to the queue.
527
+ if isinstance(b, Blossom):
528
+ queue.extend(b.leaves())
529
+ else:
530
+ queue.append(b)
531
+ elif t == 2:
532
+ # b became a T-vertex/blossom; assign label S to its mate.
533
+ # (If b is a non-trivial blossom, its base is the only vertex
534
+ # with an external mate.)
535
+ base = blossombase[b]
536
+ assignLabel(mate[base], 1, base)
537
+
538
+ # Trace back from vertices v and w to discover either a new blossom
539
+ # or an augmenting path. Return the base vertex of the new blossom,
540
+ # or NoNode if an augmenting path was found.
541
+ def scanBlossom(v, w):
542
+ # Trace back from v and w, placing breadcrumbs as we go.
543
+ path = []
544
+ base = NoNode
545
+ while v is not NoNode:
546
+ # Look for a breadcrumb in v's blossom or put a new breadcrumb.
547
+ b = inblossom[v]
548
+ if label[b] & 4:
549
+ base = blossombase[b]
550
+ break
551
+ assert label[b] == 1
552
+ path.append(b)
553
+ label[b] = 5
554
+ # Trace one step back.
555
+ if labeledge[b] is None:
556
+ # The base of blossom b is single; stop tracing this path.
557
+ assert blossombase[b] not in mate
558
+ v = NoNode
559
+ else:
560
+ assert labeledge[b][0] == mate[blossombase[b]]
561
+ v = labeledge[b][0]
562
+ b = inblossom[v]
563
+ assert label[b] == 2
564
+ # b is a T-blossom; trace one more step back.
565
+ v = labeledge[b][0]
566
+ # Swap v and w so that we alternate between both paths.
567
+ if w is not NoNode:
568
+ v, w = w, v
569
+ # Remove breadcrumbs.
570
+ for b in path:
571
+ label[b] = 1
572
+ # Return base vertex, if we found one.
573
+ return base
574
+
575
+ # Construct a new blossom with given base, through S-vertices v and w.
576
+ # Label the new blossom as S; set its dual variable to zero;
577
+ # relabel its T-vertices to S and add them to the queue.
578
+ def addBlossom(base, v, w):
579
+ bb = inblossom[base]
580
+ bv = inblossom[v]
581
+ bw = inblossom[w]
582
+ # Create blossom.
583
+ b = Blossom()
584
+ blossombase[b] = base
585
+ blossomparent[b] = None
586
+ blossomparent[bb] = b
587
+ # Make list of sub-blossoms and their interconnecting edge endpoints.
588
+ b.childs = path = []
589
+ b.edges = edgs = [(v, w)]
590
+ # Trace back from v to base.
591
+ while bv != bb:
592
+ # Add bv to the new blossom.
593
+ blossomparent[bv] = b
594
+ path.append(bv)
595
+ edgs.append(labeledge[bv])
596
+ assert label[bv] == 2 or (
597
+ label[bv] == 1 and labeledge[bv][0] == mate[blossombase[bv]]
598
+ )
599
+ # Trace one step back.
600
+ v = labeledge[bv][0]
601
+ bv = inblossom[v]
602
+ # Add base sub-blossom; reverse lists.
603
+ path.append(bb)
604
+ path.reverse()
605
+ edgs.reverse()
606
+ # Trace back from w to base.
607
+ while bw != bb:
608
+ # Add bw to the new blossom.
609
+ blossomparent[bw] = b
610
+ path.append(bw)
611
+ edgs.append((labeledge[bw][1], labeledge[bw][0]))
612
+ assert label[bw] == 2 or (
613
+ label[bw] == 1 and labeledge[bw][0] == mate[blossombase[bw]]
614
+ )
615
+ # Trace one step back.
616
+ w = labeledge[bw][0]
617
+ bw = inblossom[w]
618
+ # Set label to S.
619
+ assert label[bb] == 1
620
+ label[b] = 1
621
+ labeledge[b] = labeledge[bb]
622
+ # Set dual variable to zero.
623
+ blossomdual[b] = 0
624
+ # Relabel vertices.
625
+ for v in b.leaves():
626
+ if label[inblossom[v]] == 2:
627
+ # This T-vertex now turns into an S-vertex because it becomes
628
+ # part of an S-blossom; add it to the queue.
629
+ queue.append(v)
630
+ inblossom[v] = b
631
+ # Compute b.mybestedges.
632
+ bestedgeto = {}
633
+ for bv in path:
634
+ if isinstance(bv, Blossom):
635
+ if bv.mybestedges is not None:
636
+ # Walk this subblossom's least-slack edges.
637
+ nblist = bv.mybestedges
638
+ # The sub-blossom won't need this data again.
639
+ bv.mybestedges = None
640
+ else:
641
+ # This subblossom does not have a list of least-slack
642
+ # edges; get the information from the vertices.
643
+ nblist = [
644
+ (v, w) for v in bv.leaves() for w in G.neighbors(v) if v != w
645
+ ]
646
+ else:
647
+ nblist = [(bv, w) for w in G.neighbors(bv) if bv != w]
648
+ for k in nblist:
649
+ (i, j) = k
650
+ if inblossom[j] == b:
651
+ i, j = j, i
652
+ bj = inblossom[j]
653
+ if (
654
+ bj != b
655
+ and label.get(bj) == 1
656
+ and ((bj not in bestedgeto) or slack(i, j) < slack(*bestedgeto[bj]))
657
+ ):
658
+ bestedgeto[bj] = k
659
+ # Forget about least-slack edge of the subblossom.
660
+ bestedge[bv] = None
661
+ b.mybestedges = list(bestedgeto.values())
662
+ # Select bestedge[b].
663
+ mybestedge = None
664
+ bestedge[b] = None
665
+ for k in b.mybestedges:
666
+ kslack = slack(*k)
667
+ if mybestedge is None or kslack < mybestslack:
668
+ mybestedge = k
669
+ mybestslack = kslack
670
+ bestedge[b] = mybestedge
671
+
672
+ # Expand the given top-level blossom.
673
+ def expandBlossom(b, endstage):
674
+ # This is an obnoxiously complicated recursive function for the sake of
675
+ # a stack-transformation. So, we hack around the complexity by using
676
+ # a trampoline pattern. By yielding the arguments to each recursive
677
+ # call, we keep the actual callstack flat.
678
+
679
+ def _recurse(b, endstage):
680
+ # Convert sub-blossoms into top-level blossoms.
681
+ for s in b.childs:
682
+ blossomparent[s] = None
683
+ if isinstance(s, Blossom):
684
+ if endstage and blossomdual[s] == 0:
685
+ # Recursively expand this sub-blossom.
686
+ yield s
687
+ else:
688
+ for v in s.leaves():
689
+ inblossom[v] = s
690
+ else:
691
+ inblossom[s] = s
692
+ # If we expand a T-blossom during a stage, its sub-blossoms must be
693
+ # relabeled.
694
+ if (not endstage) and label.get(b) == 2:
695
+ # Start at the sub-blossom through which the expanding
696
+ # blossom obtained its label, and relabel sub-blossoms untili
697
+ # we reach the base.
698
+ # Figure out through which sub-blossom the expanding blossom
699
+ # obtained its label initially.
700
+ entrychild = inblossom[labeledge[b][1]]
701
+ # Decide in which direction we will go round the blossom.
702
+ j = b.childs.index(entrychild)
703
+ if j & 1:
704
+ # Start index is odd; go forward and wrap.
705
+ j -= len(b.childs)
706
+ jstep = 1
707
+ else:
708
+ # Start index is even; go backward.
709
+ jstep = -1
710
+ # Move along the blossom until we get to the base.
711
+ v, w = labeledge[b]
712
+ while j != 0:
713
+ # Relabel the T-sub-blossom.
714
+ if jstep == 1:
715
+ p, q = b.edges[j]
716
+ else:
717
+ q, p = b.edges[j - 1]
718
+ label[w] = None
719
+ label[q] = None
720
+ assignLabel(w, 2, v)
721
+ # Step to the next S-sub-blossom and note its forward edge.
722
+ allowedge[(p, q)] = allowedge[(q, p)] = True
723
+ j += jstep
724
+ if jstep == 1:
725
+ v, w = b.edges[j]
726
+ else:
727
+ w, v = b.edges[j - 1]
728
+ # Step to the next T-sub-blossom.
729
+ allowedge[(v, w)] = allowedge[(w, v)] = True
730
+ j += jstep
731
+ # Relabel the base T-sub-blossom WITHOUT stepping through to
732
+ # its mate (so don't call assignLabel).
733
+ bw = b.childs[j]
734
+ label[w] = label[bw] = 2
735
+ labeledge[w] = labeledge[bw] = (v, w)
736
+ bestedge[bw] = None
737
+ # Continue along the blossom until we get back to entrychild.
738
+ j += jstep
739
+ while b.childs[j] != entrychild:
740
+ # Examine the vertices of the sub-blossom to see whether
741
+ # it is reachable from a neighboring S-vertex outside the
742
+ # expanding blossom.
743
+ bv = b.childs[j]
744
+ if label.get(bv) == 1:
745
+ # This sub-blossom just got label S through one of its
746
+ # neighbors; leave it be.
747
+ j += jstep
748
+ continue
749
+ if isinstance(bv, Blossom):
750
+ for v in bv.leaves():
751
+ if label.get(v):
752
+ break
753
+ else:
754
+ v = bv
755
+ # If the sub-blossom contains a reachable vertex, assign
756
+ # label T to the sub-blossom.
757
+ if label.get(v):
758
+ assert label[v] == 2
759
+ assert inblossom[v] == bv
760
+ label[v] = None
761
+ label[mate[blossombase[bv]]] = None
762
+ assignLabel(v, 2, labeledge[v][0])
763
+ j += jstep
764
+ # Remove the expanded blossom entirely.
765
+ label.pop(b, None)
766
+ labeledge.pop(b, None)
767
+ bestedge.pop(b, None)
768
+ del blossomparent[b]
769
+ del blossombase[b]
770
+ del blossomdual[b]
771
+
772
+ # Now, we apply the trampoline pattern. We simulate a recursive
773
+ # callstack by maintaining a stack of generators, each yielding a
774
+ # sequence of function arguments. We grow the stack by appending a call
775
+ # to _recurse on each argument tuple, and shrink the stack whenever a
776
+ # generator is exhausted.
777
+ stack = [_recurse(b, endstage)]
778
+ while stack:
779
+ top = stack[-1]
780
+ for s in top:
781
+ stack.append(_recurse(s, endstage))
782
+ break
783
+ else:
784
+ stack.pop()
785
+
786
+ # Swap matched/unmatched edges over an alternating path through blossom b
787
+ # between vertex v and the base vertex. Keep blossom bookkeeping
788
+ # consistent.
789
+ def augmentBlossom(b, v):
790
+ # This is an obnoxiously complicated recursive function for the sake of
791
+ # a stack-transformation. So, we hack around the complexity by using
792
+ # a trampoline pattern. By yielding the arguments to each recursive
793
+ # call, we keep the actual callstack flat.
794
+
795
+ def _recurse(b, v):
796
+ # Bubble up through the blossom tree from vertex v to an immediate
797
+ # sub-blossom of b.
798
+ t = v
799
+ while blossomparent[t] != b:
800
+ t = blossomparent[t]
801
+ # Recursively deal with the first sub-blossom.
802
+ if isinstance(t, Blossom):
803
+ yield (t, v)
804
+ # Decide in which direction we will go round the blossom.
805
+ i = j = b.childs.index(t)
806
+ if i & 1:
807
+ # Start index is odd; go forward and wrap.
808
+ j -= len(b.childs)
809
+ jstep = 1
810
+ else:
811
+ # Start index is even; go backward.
812
+ jstep = -1
813
+ # Move along the blossom until we get to the base.
814
+ while j != 0:
815
+ # Step to the next sub-blossom and augment it recursively.
816
+ j += jstep
817
+ t = b.childs[j]
818
+ if jstep == 1:
819
+ w, x = b.edges[j]
820
+ else:
821
+ x, w = b.edges[j - 1]
822
+ if isinstance(t, Blossom):
823
+ yield (t, w)
824
+ # Step to the next sub-blossom and augment it recursively.
825
+ j += jstep
826
+ t = b.childs[j]
827
+ if isinstance(t, Blossom):
828
+ yield (t, x)
829
+ # Match the edge connecting those sub-blossoms.
830
+ mate[w] = x
831
+ mate[x] = w
832
+ # Rotate the list of sub-blossoms to put the new base at the front.
833
+ b.childs = b.childs[i:] + b.childs[:i]
834
+ b.edges = b.edges[i:] + b.edges[:i]
835
+ blossombase[b] = blossombase[b.childs[0]]
836
+ assert blossombase[b] == v
837
+
838
+ # Now, we apply the trampoline pattern. We simulate a recursive
839
+ # callstack by maintaining a stack of generators, each yielding a
840
+ # sequence of function arguments. We grow the stack by appending a call
841
+ # to _recurse on each argument tuple, and shrink the stack whenever a
842
+ # generator is exhausted.
843
+ stack = [_recurse(b, v)]
844
+ while stack:
845
+ top = stack[-1]
846
+ for args in top:
847
+ stack.append(_recurse(*args))
848
+ break
849
+ else:
850
+ stack.pop()
851
+
852
+ # Swap matched/unmatched edges over an alternating path between two
853
+ # single vertices. The augmenting path runs through S-vertices v and w.
854
+ def augmentMatching(v, w):
855
+ for s, j in ((v, w), (w, v)):
856
+ # Match vertex s to vertex j. Then trace back from s
857
+ # until we find a single vertex, swapping matched and unmatched
858
+ # edges as we go.
859
+ while 1:
860
+ bs = inblossom[s]
861
+ assert label[bs] == 1
862
+ assert (labeledge[bs] is None and blossombase[bs] not in mate) or (
863
+ labeledge[bs][0] == mate[blossombase[bs]]
864
+ )
865
+ # Augment through the S-blossom from s to base.
866
+ if isinstance(bs, Blossom):
867
+ augmentBlossom(bs, s)
868
+ # Update mate[s]
869
+ mate[s] = j
870
+ # Trace one step back.
871
+ if labeledge[bs] is None:
872
+ # Reached single vertex; stop.
873
+ break
874
+ t = labeledge[bs][0]
875
+ bt = inblossom[t]
876
+ assert label[bt] == 2
877
+ # Trace one more step back.
878
+ s, j = labeledge[bt]
879
+ # Augment through the T-blossom from j to base.
880
+ assert blossombase[bt] == t
881
+ if isinstance(bt, Blossom):
882
+ augmentBlossom(bt, j)
883
+ # Update mate[j]
884
+ mate[j] = s
885
+
886
+ # Verify that the optimum solution has been reached.
887
+ def verifyOptimum():
888
+ if maxcardinality:
889
+ # Vertices may have negative dual;
890
+ # find a constant non-negative number to add to all vertex duals.
891
+ vdualoffset = max(0, -min(dualvar.values()))
892
+ else:
893
+ vdualoffset = 0
894
+ # 0. all dual variables are non-negative
895
+ assert min(dualvar.values()) + vdualoffset >= 0
896
+ assert len(blossomdual) == 0 or min(blossomdual.values()) >= 0
897
+ # 0. all edges have non-negative slack and
898
+ # 1. all matched edges have zero slack;
899
+ for i, j, d in G.edges(data=True):
900
+ wt = d.get(weight, 1)
901
+ if i == j:
902
+ continue # ignore self-loops
903
+ s = dualvar[i] + dualvar[j] - 2 * wt
904
+ iblossoms = [i]
905
+ jblossoms = [j]
906
+ while blossomparent[iblossoms[-1]] is not None:
907
+ iblossoms.append(blossomparent[iblossoms[-1]])
908
+ while blossomparent[jblossoms[-1]] is not None:
909
+ jblossoms.append(blossomparent[jblossoms[-1]])
910
+ iblossoms.reverse()
911
+ jblossoms.reverse()
912
+ for bi, bj in zip(iblossoms, jblossoms):
913
+ if bi != bj:
914
+ break
915
+ s += 2 * blossomdual[bi]
916
+ assert s >= 0
917
+ if mate.get(i) == j or mate.get(j) == i:
918
+ assert mate[i] == j and mate[j] == i
919
+ assert s == 0
920
+ # 2. all single vertices have zero dual value;
921
+ for v in gnodes:
922
+ assert (v in mate) or dualvar[v] + vdualoffset == 0
923
+ # 3. all blossoms with positive dual value are full.
924
+ for b in blossomdual:
925
+ if blossomdual[b] > 0:
926
+ assert len(b.edges) % 2 == 1
927
+ for i, j in b.edges[1::2]:
928
+ assert mate[i] == j and mate[j] == i
929
+ # Ok.
930
+
931
+ # Main loop: continue until no further improvement is possible.
932
+ while 1:
933
+ # Each iteration of this loop is a "stage".
934
+ # A stage finds an augmenting path and uses that to improve
935
+ # the matching.
936
+
937
+ # Remove labels from top-level blossoms/vertices.
938
+ label.clear()
939
+ labeledge.clear()
940
+
941
+ # Forget all about least-slack edges.
942
+ bestedge.clear()
943
+ for b in blossomdual:
944
+ b.mybestedges = None
945
+
946
+ # Loss of labeling means that we can not be sure that currently
947
+ # allowable edges remain allowable throughout this stage.
948
+ allowedge.clear()
949
+
950
+ # Make queue empty.
951
+ queue[:] = []
952
+
953
+ # Label single blossoms/vertices with S and put them in the queue.
954
+ for v in gnodes:
955
+ if (v not in mate) and label.get(inblossom[v]) is None:
956
+ assignLabel(v, 1, None)
957
+
958
+ # Loop until we succeed in augmenting the matching.
959
+ augmented = 0
960
+ while 1:
961
+ # Each iteration of this loop is a "substage".
962
+ # A substage tries to find an augmenting path;
963
+ # if found, the path is used to improve the matching and
964
+ # the stage ends. If there is no augmenting path, the
965
+ # primal-dual method is used to pump some slack out of
966
+ # the dual variables.
967
+
968
+ # Continue labeling until all vertices which are reachable
969
+ # through an alternating path have got a label.
970
+ while queue and not augmented:
971
+ # Take an S vertex from the queue.
972
+ v = queue.pop()
973
+ assert label[inblossom[v]] == 1
974
+
975
+ # Scan its neighbors:
976
+ for w in G.neighbors(v):
977
+ if w == v:
978
+ continue # ignore self-loops
979
+ # w is a neighbor to v
980
+ bv = inblossom[v]
981
+ bw = inblossom[w]
982
+ if bv == bw:
983
+ # this edge is internal to a blossom; ignore it
984
+ continue
985
+ if (v, w) not in allowedge:
986
+ kslack = slack(v, w)
987
+ if kslack <= 0:
988
+ # edge k has zero slack => it is allowable
989
+ allowedge[(v, w)] = allowedge[(w, v)] = True
990
+ if (v, w) in allowedge:
991
+ if label.get(bw) is None:
992
+ # (C1) w is a free vertex;
993
+ # label w with T and label its mate with S (R12).
994
+ assignLabel(w, 2, v)
995
+ elif label.get(bw) == 1:
996
+ # (C2) w is an S-vertex (not in the same blossom);
997
+ # follow back-links to discover either an
998
+ # augmenting path or a new blossom.
999
+ base = scanBlossom(v, w)
1000
+ if base is not NoNode:
1001
+ # Found a new blossom; add it to the blossom
1002
+ # bookkeeping and turn it into an S-blossom.
1003
+ addBlossom(base, v, w)
1004
+ else:
1005
+ # Found an augmenting path; augment the
1006
+ # matching and end this stage.
1007
+ augmentMatching(v, w)
1008
+ augmented = 1
1009
+ break
1010
+ elif label.get(w) is None:
1011
+ # w is inside a T-blossom, but w itself has not
1012
+ # yet been reached from outside the blossom;
1013
+ # mark it as reached (we need this to relabel
1014
+ # during T-blossom expansion).
1015
+ assert label[bw] == 2
1016
+ label[w] = 2
1017
+ labeledge[w] = (v, w)
1018
+ elif label.get(bw) == 1:
1019
+ # keep track of the least-slack non-allowable edge to
1020
+ # a different S-blossom.
1021
+ if bestedge.get(bv) is None or kslack < slack(*bestedge[bv]):
1022
+ bestedge[bv] = (v, w)
1023
+ elif label.get(w) is None:
1024
+ # w is a free vertex (or an unreached vertex inside
1025
+ # a T-blossom) but we can not reach it yet;
1026
+ # keep track of the least-slack edge that reaches w.
1027
+ if bestedge.get(w) is None or kslack < slack(*bestedge[w]):
1028
+ bestedge[w] = (v, w)
1029
+
1030
+ if augmented:
1031
+ break
1032
+
1033
+ # There is no augmenting path under these constraints;
1034
+ # compute delta and reduce slack in the optimization problem.
1035
+ # (Note that our vertex dual variables, edge slacks and delta's
1036
+ # are pre-multiplied by two.)
1037
+ deltatype = -1
1038
+ delta = deltaedge = deltablossom = None
1039
+
1040
+ # Compute delta1: the minimum value of any vertex dual.
1041
+ if not maxcardinality:
1042
+ deltatype = 1
1043
+ delta = min(dualvar.values())
1044
+
1045
+ # Compute delta2: the minimum slack on any edge between
1046
+ # an S-vertex and a free vertex.
1047
+ for v in G.nodes():
1048
+ if label.get(inblossom[v]) is None and bestedge.get(v) is not None:
1049
+ d = slack(*bestedge[v])
1050
+ if deltatype == -1 or d < delta:
1051
+ delta = d
1052
+ deltatype = 2
1053
+ deltaedge = bestedge[v]
1054
+
1055
+ # Compute delta3: half the minimum slack on any edge between
1056
+ # a pair of S-blossoms.
1057
+ for b in blossomparent:
1058
+ if (
1059
+ blossomparent[b] is None
1060
+ and label.get(b) == 1
1061
+ and bestedge.get(b) is not None
1062
+ ):
1063
+ kslack = slack(*bestedge[b])
1064
+ if allinteger:
1065
+ assert (kslack % 2) == 0
1066
+ d = kslack // 2
1067
+ else:
1068
+ d = kslack / 2.0
1069
+ if deltatype == -1 or d < delta:
1070
+ delta = d
1071
+ deltatype = 3
1072
+ deltaedge = bestedge[b]
1073
+
1074
+ # Compute delta4: minimum z variable of any T-blossom.
1075
+ for b in blossomdual:
1076
+ if (
1077
+ blossomparent[b] is None
1078
+ and label.get(b) == 2
1079
+ and (deltatype == -1 or blossomdual[b] < delta)
1080
+ ):
1081
+ delta = blossomdual[b]
1082
+ deltatype = 4
1083
+ deltablossom = b
1084
+
1085
+ if deltatype == -1:
1086
+ # No further improvement possible; max-cardinality optimum
1087
+ # reached. Do a final delta update to make the optimum
1088
+ # verifiable.
1089
+ assert maxcardinality
1090
+ deltatype = 1
1091
+ delta = max(0, min(dualvar.values()))
1092
+
1093
+ # Update dual variables according to delta.
1094
+ for v in gnodes:
1095
+ if label.get(inblossom[v]) == 1:
1096
+ # S-vertex: 2*u = 2*u - 2*delta
1097
+ dualvar[v] -= delta
1098
+ elif label.get(inblossom[v]) == 2:
1099
+ # T-vertex: 2*u = 2*u + 2*delta
1100
+ dualvar[v] += delta
1101
+ for b in blossomdual:
1102
+ if blossomparent[b] is None:
1103
+ if label.get(b) == 1:
1104
+ # top-level S-blossom: z = z + 2*delta
1105
+ blossomdual[b] += delta
1106
+ elif label.get(b) == 2:
1107
+ # top-level T-blossom: z = z - 2*delta
1108
+ blossomdual[b] -= delta
1109
+
1110
+ # Take action at the point where minimum delta occurred.
1111
+ if deltatype == 1:
1112
+ # No further improvement possible; optimum reached.
1113
+ break
1114
+ elif deltatype == 2:
1115
+ # Use the least-slack edge to continue the search.
1116
+ (v, w) = deltaedge
1117
+ assert label[inblossom[v]] == 1
1118
+ allowedge[(v, w)] = allowedge[(w, v)] = True
1119
+ queue.append(v)
1120
+ elif deltatype == 3:
1121
+ # Use the least-slack edge to continue the search.
1122
+ (v, w) = deltaedge
1123
+ allowedge[(v, w)] = allowedge[(w, v)] = True
1124
+ assert label[inblossom[v]] == 1
1125
+ queue.append(v)
1126
+ elif deltatype == 4:
1127
+ # Expand the least-z blossom.
1128
+ expandBlossom(deltablossom, False)
1129
+
1130
+ # End of a this substage.
1131
+
1132
+ # Paranoia check that the matching is symmetric.
1133
+ for v in mate:
1134
+ assert mate[mate[v]] == v
1135
+
1136
+ # Stop when no more augmenting path can be found.
1137
+ if not augmented:
1138
+ break
1139
+
1140
+ # End of a stage; expand all S-blossoms which have zero dual.
1141
+ for b in list(blossomdual.keys()):
1142
+ if b not in blossomdual:
1143
+ continue # already expanded
1144
+ if blossomparent[b] is None and label.get(b) == 1 and blossomdual[b] == 0:
1145
+ expandBlossom(b, True)
1146
+
1147
+ # Verify that we reached the optimum solution (only for integer weights).
1148
+ if allinteger:
1149
+ verifyOptimum()
1150
+
1151
+ return matching_dict_to_set(mate)
pythonProject/.venv/Lib/site-packages/networkx/algorithms/mis.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Algorithm to find a maximal (not maximum) independent set.
3
+
4
+ """
5
+ import networkx as nx
6
+ from networkx.utils import not_implemented_for, py_random_state
7
+
8
+ __all__ = ["maximal_independent_set"]
9
+
10
+
11
+ @not_implemented_for("directed")
12
+ @py_random_state(2)
13
+ @nx._dispatchable
14
+ def maximal_independent_set(G, nodes=None, seed=None):
15
+ """Returns a random maximal independent set guaranteed to contain
16
+ a given set of nodes.
17
+
18
+ An independent set is a set of nodes such that the subgraph
19
+ of G induced by these nodes contains no edges. A maximal
20
+ independent set is an independent set such that it is not possible
21
+ to add a new node and still get an independent set.
22
+
23
+ Parameters
24
+ ----------
25
+ G : NetworkX graph
26
+
27
+ nodes : list or iterable
28
+ Nodes that must be part of the independent set. This set of nodes
29
+ must be independent.
30
+
31
+ seed : integer, random_state, or None (default)
32
+ Indicator of random number generation state.
33
+ See :ref:`Randomness<randomness>`.
34
+
35
+ Returns
36
+ -------
37
+ indep_nodes : list
38
+ List of nodes that are part of a maximal independent set.
39
+
40
+ Raises
41
+ ------
42
+ NetworkXUnfeasible
43
+ If the nodes in the provided list are not part of the graph or
44
+ do not form an independent set, an exception is raised.
45
+
46
+ NetworkXNotImplemented
47
+ If `G` is directed.
48
+
49
+ Examples
50
+ --------
51
+ >>> G = nx.path_graph(5)
52
+ >>> nx.maximal_independent_set(G) # doctest: +SKIP
53
+ [4, 0, 2]
54
+ >>> nx.maximal_independent_set(G, [1]) # doctest: +SKIP
55
+ [1, 3]
56
+
57
+ Notes
58
+ -----
59
+ This algorithm does not solve the maximum independent set problem.
60
+
61
+ """
62
+ if not nodes:
63
+ nodes = {seed.choice(list(G))}
64
+ else:
65
+ nodes = set(nodes)
66
+ if not nodes.issubset(G):
67
+ raise nx.NetworkXUnfeasible(f"{nodes} is not a subset of the nodes of G")
68
+ neighbors = set.union(*[set(G.adj[v]) for v in nodes])
69
+ if set.intersection(neighbors, nodes):
70
+ raise nx.NetworkXUnfeasible(f"{nodes} is not an independent set of G")
71
+ indep_nodes = list(nodes)
72
+ available_nodes = set(G.nodes()).difference(neighbors.union(nodes))
73
+ while available_nodes:
74
+ node = seed.choice(list(available_nodes))
75
+ indep_nodes.append(node)
76
+ available_nodes.difference_update(list(G.adj[node]) + [node])
77
+ return indep_nodes
pythonProject/.venv/Lib/site-packages/networkx/algorithms/moral.py ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ r"""Function for computing the moral graph of a directed graph."""
2
+
3
+ import itertools
4
+
5
+ import networkx as nx
6
+ from networkx.utils import not_implemented_for
7
+
8
+ __all__ = ["moral_graph"]
9
+
10
+
11
+ @not_implemented_for("undirected")
12
+ @nx._dispatchable(returns_graph=True)
13
+ def moral_graph(G):
14
+ r"""Return the Moral Graph
15
+
16
+ Returns the moralized graph of a given directed graph.
17
+
18
+ Parameters
19
+ ----------
20
+ G : NetworkX graph
21
+ Directed graph
22
+
23
+ Returns
24
+ -------
25
+ H : NetworkX graph
26
+ The undirected moralized graph of G
27
+
28
+ Raises
29
+ ------
30
+ NetworkXNotImplemented
31
+ If `G` is undirected.
32
+
33
+ Examples
34
+ --------
35
+ >>> G = nx.DiGraph([(1, 2), (2, 3), (2, 5), (3, 4), (4, 3)])
36
+ >>> G_moral = nx.moral_graph(G)
37
+ >>> G_moral.edges()
38
+ EdgeView([(1, 2), (2, 3), (2, 5), (2, 4), (3, 4)])
39
+
40
+ Notes
41
+ -----
42
+ A moral graph is an undirected graph H = (V, E) generated from a
43
+ directed Graph, where if a node has more than one parent node, edges
44
+ between these parent nodes are inserted and all directed edges become
45
+ undirected.
46
+
47
+ https://en.wikipedia.org/wiki/Moral_graph
48
+
49
+ References
50
+ ----------
51
+ .. [1] Wray L. Buntine. 1995. Chain graphs for learning.
52
+ In Proceedings of the Eleventh conference on Uncertainty
53
+ in artificial intelligence (UAI'95)
54
+ """
55
+ H = G.to_undirected()
56
+ for preds in G.pred.values():
57
+ predecessors_combinations = itertools.combinations(preds, r=2)
58
+ H.add_edges_from(predecessors_combinations)
59
+ return H
pythonProject/.venv/Lib/site-packages/networkx/algorithms/node_classification.py ADDED
@@ -0,0 +1,218 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """ This module provides the functions for node classification problem.
2
+
3
+ The functions in this module are not imported
4
+ into the top level `networkx` namespace.
5
+ You can access these functions by importing
6
+ the `networkx.algorithms.node_classification` modules,
7
+ then accessing the functions as attributes of `node_classification`.
8
+ For example:
9
+
10
+ >>> from networkx.algorithms import node_classification
11
+ >>> G = nx.path_graph(4)
12
+ >>> G.edges()
13
+ EdgeView([(0, 1), (1, 2), (2, 3)])
14
+ >>> G.nodes[0]["label"] = "A"
15
+ >>> G.nodes[3]["label"] = "B"
16
+ >>> node_classification.harmonic_function(G)
17
+ ['A', 'A', 'B', 'B']
18
+
19
+ References
20
+ ----------
21
+ Zhu, X., Ghahramani, Z., & Lafferty, J. (2003, August).
22
+ Semi-supervised learning using gaussian fields and harmonic functions.
23
+ In ICML (Vol. 3, pp. 912-919).
24
+ """
25
+ import networkx as nx
26
+
27
+ __all__ = ["harmonic_function", "local_and_global_consistency"]
28
+
29
+
30
+ @nx.utils.not_implemented_for("directed")
31
+ @nx._dispatchable(node_attrs="label_name")
32
+ def harmonic_function(G, max_iter=30, label_name="label"):
33
+ """Node classification by Harmonic function
34
+
35
+ Function for computing Harmonic function algorithm by Zhu et al.
36
+
37
+ Parameters
38
+ ----------
39
+ G : NetworkX Graph
40
+ max_iter : int
41
+ maximum number of iterations allowed
42
+ label_name : string
43
+ name of target labels to predict
44
+
45
+ Returns
46
+ -------
47
+ predicted : list
48
+ List of length ``len(G)`` with the predicted labels for each node.
49
+
50
+ Raises
51
+ ------
52
+ NetworkXError
53
+ If no nodes in `G` have attribute `label_name`.
54
+
55
+ Examples
56
+ --------
57
+ >>> from networkx.algorithms import node_classification
58
+ >>> G = nx.path_graph(4)
59
+ >>> G.nodes[0]["label"] = "A"
60
+ >>> G.nodes[3]["label"] = "B"
61
+ >>> G.nodes(data=True)
62
+ NodeDataView({0: {'label': 'A'}, 1: {}, 2: {}, 3: {'label': 'B'}})
63
+ >>> G.edges()
64
+ EdgeView([(0, 1), (1, 2), (2, 3)])
65
+ >>> predicted = node_classification.harmonic_function(G)
66
+ >>> predicted
67
+ ['A', 'A', 'B', 'B']
68
+
69
+ References
70
+ ----------
71
+ Zhu, X., Ghahramani, Z., & Lafferty, J. (2003, August).
72
+ Semi-supervised learning using gaussian fields and harmonic functions.
73
+ In ICML (Vol. 3, pp. 912-919).
74
+ """
75
+ import numpy as np
76
+ import scipy as sp
77
+
78
+ X = nx.to_scipy_sparse_array(G) # adjacency matrix
79
+ labels, label_dict = _get_label_info(G, label_name)
80
+
81
+ if labels.shape[0] == 0:
82
+ raise nx.NetworkXError(
83
+ f"No node on the input graph is labeled by '{label_name}'."
84
+ )
85
+
86
+ n_samples = X.shape[0]
87
+ n_classes = label_dict.shape[0]
88
+ F = np.zeros((n_samples, n_classes))
89
+
90
+ # Build propagation matrix
91
+ degrees = X.sum(axis=0)
92
+ degrees[degrees == 0] = 1 # Avoid division by 0
93
+ # TODO: csr_array
94
+ D = sp.sparse.csr_array(sp.sparse.diags((1.0 / degrees), offsets=0))
95
+ P = (D @ X).tolil()
96
+ P[labels[:, 0]] = 0 # labels[:, 0] indicates IDs of labeled nodes
97
+ # Build base matrix
98
+ B = np.zeros((n_samples, n_classes))
99
+ B[labels[:, 0], labels[:, 1]] = 1
100
+
101
+ for _ in range(max_iter):
102
+ F = (P @ F) + B
103
+
104
+ return label_dict[np.argmax(F, axis=1)].tolist()
105
+
106
+
107
+ @nx.utils.not_implemented_for("directed")
108
+ @nx._dispatchable(node_attrs="label_name")
109
+ def local_and_global_consistency(G, alpha=0.99, max_iter=30, label_name="label"):
110
+ """Node classification by Local and Global Consistency
111
+
112
+ Function for computing Local and global consistency algorithm by Zhou et al.
113
+
114
+ Parameters
115
+ ----------
116
+ G : NetworkX Graph
117
+ alpha : float
118
+ Clamping factor
119
+ max_iter : int
120
+ Maximum number of iterations allowed
121
+ label_name : string
122
+ Name of target labels to predict
123
+
124
+ Returns
125
+ -------
126
+ predicted : list
127
+ List of length ``len(G)`` with the predicted labels for each node.
128
+
129
+ Raises
130
+ ------
131
+ NetworkXError
132
+ If no nodes in `G` have attribute `label_name`.
133
+
134
+ Examples
135
+ --------
136
+ >>> from networkx.algorithms import node_classification
137
+ >>> G = nx.path_graph(4)
138
+ >>> G.nodes[0]["label"] = "A"
139
+ >>> G.nodes[3]["label"] = "B"
140
+ >>> G.nodes(data=True)
141
+ NodeDataView({0: {'label': 'A'}, 1: {}, 2: {}, 3: {'label': 'B'}})
142
+ >>> G.edges()
143
+ EdgeView([(0, 1), (1, 2), (2, 3)])
144
+ >>> predicted = node_classification.local_and_global_consistency(G)
145
+ >>> predicted
146
+ ['A', 'A', 'B', 'B']
147
+
148
+ References
149
+ ----------
150
+ Zhou, D., Bousquet, O., Lal, T. N., Weston, J., & Schölkopf, B. (2004).
151
+ Learning with local and global consistency.
152
+ Advances in neural information processing systems, 16(16), 321-328.
153
+ """
154
+ import numpy as np
155
+ import scipy as sp
156
+
157
+ X = nx.to_scipy_sparse_array(G) # adjacency matrix
158
+ labels, label_dict = _get_label_info(G, label_name)
159
+
160
+ if labels.shape[0] == 0:
161
+ raise nx.NetworkXError(
162
+ f"No node on the input graph is labeled by '{label_name}'."
163
+ )
164
+
165
+ n_samples = X.shape[0]
166
+ n_classes = label_dict.shape[0]
167
+ F = np.zeros((n_samples, n_classes))
168
+
169
+ # Build propagation matrix
170
+ degrees = X.sum(axis=0)
171
+ degrees[degrees == 0] = 1 # Avoid division by 0
172
+ # TODO: csr_array
173
+ D2 = np.sqrt(sp.sparse.csr_array(sp.sparse.diags((1.0 / degrees), offsets=0)))
174
+ P = alpha * ((D2 @ X) @ D2)
175
+ # Build base matrix
176
+ B = np.zeros((n_samples, n_classes))
177
+ B[labels[:, 0], labels[:, 1]] = 1 - alpha
178
+
179
+ for _ in range(max_iter):
180
+ F = (P @ F) + B
181
+
182
+ return label_dict[np.argmax(F, axis=1)].tolist()
183
+
184
+
185
+ def _get_label_info(G, label_name):
186
+ """Get and return information of labels from the input graph
187
+
188
+ Parameters
189
+ ----------
190
+ G : Network X graph
191
+ label_name : string
192
+ Name of the target label
193
+
194
+ Returns
195
+ -------
196
+ labels : numpy array, shape = [n_labeled_samples, 2]
197
+ Array of pairs of labeled node ID and label ID
198
+ label_dict : numpy array, shape = [n_classes]
199
+ Array of labels
200
+ i-th element contains the label corresponding label ID `i`
201
+ """
202
+ import numpy as np
203
+
204
+ labels = []
205
+ label_to_id = {}
206
+ lid = 0
207
+ for i, n in enumerate(G.nodes(data=True)):
208
+ if label_name in n[1]:
209
+ label = n[1][label_name]
210
+ if label not in label_to_id:
211
+ label_to_id[label] = lid
212
+ lid += 1
213
+ labels.append([i, label_to_id[label]])
214
+ labels = np.array(labels)
215
+ label_dict = np.array(
216
+ [label for label, _ in sorted(label_to_id.items(), key=lambda x: x[1])]
217
+ )
218
+ return (labels, label_dict)