koichi12 commited on
Commit
b6231d9
·
verified ·
1 Parent(s): c4cbcf2

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/__init__.py +11 -0
  2. .venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/__pycache__/__init__.cpython-311.pyc +0 -0
  3. .venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/__pycache__/connectivity.cpython-311.pyc +0 -0
  4. .venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/__pycache__/cuts.cpython-311.pyc +0 -0
  5. .venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/__pycache__/disjoint_paths.cpython-311.pyc +0 -0
  6. .venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/__pycache__/edge_augmentation.cpython-311.pyc +0 -0
  7. .venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/__pycache__/edge_kcomponents.cpython-311.pyc +0 -0
  8. .venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/__pycache__/kcomponents.cpython-311.pyc +0 -0
  9. .venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/__pycache__/kcutsets.cpython-311.pyc +0 -0
  10. .venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/__pycache__/stoerwagner.cpython-311.pyc +0 -0
  11. .venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/__pycache__/utils.cpython-311.pyc +0 -0
  12. .venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/connectivity.py +811 -0
  13. .venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/cuts.py +612 -0
  14. .venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/disjoint_paths.py +408 -0
  15. .venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/edge_augmentation.py +1270 -0
  16. .venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/edge_kcomponents.py +592 -0
  17. .venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/kcomponents.py +223 -0
  18. .venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/kcutsets.py +235 -0
  19. .venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/stoerwagner.py +152 -0
  20. .venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/tests/__init__.py +0 -0
  21. .venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/tests/__pycache__/test_connectivity.cpython-311.pyc +0 -0
  22. .venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/tests/__pycache__/test_cuts.cpython-311.pyc +0 -0
  23. .venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/tests/__pycache__/test_disjoint_paths.cpython-311.pyc +0 -0
  24. .venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/tests/__pycache__/test_edge_augmentation.cpython-311.pyc +0 -0
  25. .venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/tests/__pycache__/test_edge_kcomponents.cpython-311.pyc +0 -0
  26. .venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/tests/__pycache__/test_kcutsets.cpython-311.pyc +0 -0
  27. .venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/tests/test_connectivity.py +421 -0
  28. .venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/tests/test_cuts.py +309 -0
  29. .venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/tests/test_disjoint_paths.py +249 -0
  30. .venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/tests/test_edge_augmentation.py +502 -0
  31. .venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/tests/test_edge_kcomponents.py +488 -0
  32. .venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/tests/test_kcomponents.py +296 -0
  33. .venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/tests/test_kcutsets.py +273 -0
  34. .venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/tests/test_stoer_wagner.py +102 -0
  35. .venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/utils.py +88 -0
  36. .venv/lib/python3.11/site-packages/networkx/algorithms/tests/__pycache__/__init__.cpython-311.pyc +0 -0
  37. .venv/lib/python3.11/site-packages/networkx/algorithms/tests/__pycache__/test_boundary.cpython-311.pyc +0 -0
  38. .venv/lib/python3.11/site-packages/networkx/algorithms/tests/__pycache__/test_bridges.cpython-311.pyc +0 -0
  39. .venv/lib/python3.11/site-packages/networkx/algorithms/tests/__pycache__/test_broadcasting.cpython-311.pyc +0 -0
  40. .venv/lib/python3.11/site-packages/networkx/algorithms/tests/__pycache__/test_chains.cpython-311.pyc +0 -0
  41. .venv/lib/python3.11/site-packages/networkx/algorithms/tests/__pycache__/test_clique.cpython-311.pyc +0 -0
  42. .venv/lib/python3.11/site-packages/networkx/algorithms/tests/__pycache__/test_cluster.cpython-311.pyc +0 -0
  43. .venv/lib/python3.11/site-packages/networkx/algorithms/tests/__pycache__/test_communicability.cpython-311.pyc +0 -0
  44. .venv/lib/python3.11/site-packages/networkx/algorithms/tests/__pycache__/test_core.cpython-311.pyc +0 -0
  45. .venv/lib/python3.11/site-packages/networkx/algorithms/tests/__pycache__/test_cycles.cpython-311.pyc +0 -0
  46. .venv/lib/python3.11/site-packages/networkx/algorithms/tests/__pycache__/test_d_separation.cpython-311.pyc +0 -0
  47. .venv/lib/python3.11/site-packages/networkx/algorithms/tests/__pycache__/test_dag.cpython-311.pyc +0 -0
  48. .venv/lib/python3.11/site-packages/networkx/algorithms/tests/__pycache__/test_distance_measures.cpython-311.pyc +0 -0
  49. .venv/lib/python3.11/site-packages/networkx/algorithms/tests/__pycache__/test_distance_regular.cpython-311.pyc +0 -0
  50. .venv/lib/python3.11/site-packages/networkx/algorithms/tests/__pycache__/test_dominance.cpython-311.pyc +0 -0
.venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/__init__.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Connectivity and cut algorithms"""
2
+
3
+ from .connectivity import *
4
+ from .cuts import *
5
+ from .edge_augmentation import *
6
+ from .edge_kcomponents import *
7
+ from .disjoint_paths import *
8
+ from .kcomponents import *
9
+ from .kcutsets import *
10
+ from .stoerwagner import *
11
+ from .utils import *
.venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (556 Bytes). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/__pycache__/connectivity.cpython-311.pyc ADDED
Binary file (31.2 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/__pycache__/cuts.cpython-311.pyc ADDED
Binary file (24.5 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/__pycache__/disjoint_paths.cpython-311.pyc ADDED
Binary file (15.8 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/__pycache__/edge_augmentation.cpython-311.pyc ADDED
Binary file (52.9 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/__pycache__/edge_kcomponents.cpython-311.pyc ADDED
Binary file (24.3 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/__pycache__/kcomponents.cpython-311.pyc ADDED
Binary file (12.1 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/__pycache__/kcutsets.cpython-311.pyc ADDED
Binary file (10.4 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/__pycache__/stoerwagner.cpython-311.pyc ADDED
Binary file (6.81 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/__pycache__/utils.cpython-311.pyc ADDED
Binary file (4.6 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/connectivity.py ADDED
@@ -0,0 +1,811 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Flow based connectivity algorithms
3
+ """
4
+
5
+ import itertools
6
+ from operator import itemgetter
7
+
8
+ import networkx as nx
9
+
10
+ # Define the default maximum flow function to use in all flow based
11
+ # connectivity algorithms.
12
+ from networkx.algorithms.flow import (
13
+ boykov_kolmogorov,
14
+ build_residual_network,
15
+ dinitz,
16
+ edmonds_karp,
17
+ preflow_push,
18
+ shortest_augmenting_path,
19
+ )
20
+
21
+ default_flow_func = edmonds_karp
22
+
23
+ from .utils import build_auxiliary_edge_connectivity, build_auxiliary_node_connectivity
24
+
25
+ __all__ = [
26
+ "average_node_connectivity",
27
+ "local_node_connectivity",
28
+ "node_connectivity",
29
+ "local_edge_connectivity",
30
+ "edge_connectivity",
31
+ "all_pairs_node_connectivity",
32
+ ]
33
+
34
+
35
+ @nx._dispatchable(graphs={"G": 0, "auxiliary?": 4}, preserve_graph_attrs={"auxiliary"})
36
+ def local_node_connectivity(
37
+ G, s, t, flow_func=None, auxiliary=None, residual=None, cutoff=None
38
+ ):
39
+ r"""Computes local node connectivity for nodes s and t.
40
+
41
+ Local node connectivity for two non adjacent nodes s and t is the
42
+ minimum number of nodes that must be removed (along with their incident
43
+ edges) to disconnect them.
44
+
45
+ This is a flow based implementation of node connectivity. We compute the
46
+ maximum flow on an auxiliary digraph build from the original input
47
+ graph (see below for details).
48
+
49
+ Parameters
50
+ ----------
51
+ G : NetworkX graph
52
+ Undirected graph
53
+
54
+ s : node
55
+ Source node
56
+
57
+ t : node
58
+ Target node
59
+
60
+ flow_func : function
61
+ A function for computing the maximum flow among a pair of nodes.
62
+ The function has to accept at least three parameters: a Digraph,
63
+ a source node, and a target node. And return a residual network
64
+ that follows NetworkX conventions (see :meth:`maximum_flow` for
65
+ details). If flow_func is None, the default maximum flow function
66
+ (:meth:`edmonds_karp`) is used. See below for details. The choice
67
+ of the default function may change from version to version and
68
+ should not be relied on. Default value: None.
69
+
70
+ auxiliary : NetworkX DiGraph
71
+ Auxiliary digraph to compute flow based node connectivity. It has
72
+ to have a graph attribute called mapping with a dictionary mapping
73
+ node names in G and in the auxiliary digraph. If provided
74
+ it will be reused instead of recreated. Default value: None.
75
+
76
+ residual : NetworkX DiGraph
77
+ Residual network to compute maximum flow. If provided it will be
78
+ reused instead of recreated. Default value: None.
79
+
80
+ cutoff : integer, float, or None (default: None)
81
+ If specified, the maximum flow algorithm will terminate when the
82
+ flow value reaches or exceeds the cutoff. This only works for flows
83
+ that support the cutoff parameter (most do) and is ignored otherwise.
84
+
85
+ Returns
86
+ -------
87
+ K : integer
88
+ local node connectivity for nodes s and t
89
+
90
+ Examples
91
+ --------
92
+ This function is not imported in the base NetworkX namespace, so you
93
+ have to explicitly import it from the connectivity package:
94
+
95
+ >>> from networkx.algorithms.connectivity import local_node_connectivity
96
+
97
+ We use in this example the platonic icosahedral graph, which has node
98
+ connectivity 5.
99
+
100
+ >>> G = nx.icosahedral_graph()
101
+ >>> local_node_connectivity(G, 0, 6)
102
+ 5
103
+
104
+ If you need to compute local connectivity on several pairs of
105
+ nodes in the same graph, it is recommended that you reuse the
106
+ data structures that NetworkX uses in the computation: the
107
+ auxiliary digraph for node connectivity, and the residual
108
+ network for the underlying maximum flow computation.
109
+
110
+ Example of how to compute local node connectivity among
111
+ all pairs of nodes of the platonic icosahedral graph reusing
112
+ the data structures.
113
+
114
+ >>> import itertools
115
+ >>> # You also have to explicitly import the function for
116
+ >>> # building the auxiliary digraph from the connectivity package
117
+ >>> from networkx.algorithms.connectivity import build_auxiliary_node_connectivity
118
+ >>> H = build_auxiliary_node_connectivity(G)
119
+ >>> # And the function for building the residual network from the
120
+ >>> # flow package
121
+ >>> from networkx.algorithms.flow import build_residual_network
122
+ >>> # Note that the auxiliary digraph has an edge attribute named capacity
123
+ >>> R = build_residual_network(H, "capacity")
124
+ >>> result = dict.fromkeys(G, dict())
125
+ >>> # Reuse the auxiliary digraph and the residual network by passing them
126
+ >>> # as parameters
127
+ >>> for u, v in itertools.combinations(G, 2):
128
+ ... k = local_node_connectivity(G, u, v, auxiliary=H, residual=R)
129
+ ... result[u][v] = k
130
+ >>> all(result[u][v] == 5 for u, v in itertools.combinations(G, 2))
131
+ True
132
+
133
+ You can also use alternative flow algorithms for computing node
134
+ connectivity. For instance, in dense networks the algorithm
135
+ :meth:`shortest_augmenting_path` will usually perform better than
136
+ the default :meth:`edmonds_karp` which is faster for sparse
137
+ networks with highly skewed degree distributions. Alternative flow
138
+ functions have to be explicitly imported from the flow package.
139
+
140
+ >>> from networkx.algorithms.flow import shortest_augmenting_path
141
+ >>> local_node_connectivity(G, 0, 6, flow_func=shortest_augmenting_path)
142
+ 5
143
+
144
+ Notes
145
+ -----
146
+ This is a flow based implementation of node connectivity. We compute the
147
+ maximum flow using, by default, the :meth:`edmonds_karp` algorithm (see:
148
+ :meth:`maximum_flow`) on an auxiliary digraph build from the original
149
+ input graph:
150
+
151
+ For an undirected graph G having `n` nodes and `m` edges we derive a
152
+ directed graph H with `2n` nodes and `2m+n` arcs by replacing each
153
+ original node `v` with two nodes `v_A`, `v_B` linked by an (internal)
154
+ arc in H. Then for each edge (`u`, `v`) in G we add two arcs
155
+ (`u_B`, `v_A`) and (`v_B`, `u_A`) in H. Finally we set the attribute
156
+ capacity = 1 for each arc in H [1]_ .
157
+
158
+ For a directed graph G having `n` nodes and `m` arcs we derive a
159
+ directed graph H with `2n` nodes and `m+n` arcs by replacing each
160
+ original node `v` with two nodes `v_A`, `v_B` linked by an (internal)
161
+ arc (`v_A`, `v_B`) in H. Then for each arc (`u`, `v`) in G we add one arc
162
+ (`u_B`, `v_A`) in H. Finally we set the attribute capacity = 1 for
163
+ each arc in H.
164
+
165
+ This is equal to the local node connectivity because the value of
166
+ a maximum s-t-flow is equal to the capacity of a minimum s-t-cut.
167
+
168
+ See also
169
+ --------
170
+ :meth:`local_edge_connectivity`
171
+ :meth:`node_connectivity`
172
+ :meth:`minimum_node_cut`
173
+ :meth:`maximum_flow`
174
+ :meth:`edmonds_karp`
175
+ :meth:`preflow_push`
176
+ :meth:`shortest_augmenting_path`
177
+
178
+ References
179
+ ----------
180
+ .. [1] Kammer, Frank and Hanjo Taubig. Graph Connectivity. in Brandes and
181
+ Erlebach, 'Network Analysis: Methodological Foundations', Lecture
182
+ Notes in Computer Science, Volume 3418, Springer-Verlag, 2005.
183
+ http://www.informatik.uni-augsburg.de/thi/personen/kammer/Graph_Connectivity.pdf
184
+
185
+ """
186
+ if flow_func is None:
187
+ flow_func = default_flow_func
188
+
189
+ if auxiliary is None:
190
+ H = build_auxiliary_node_connectivity(G)
191
+ else:
192
+ H = auxiliary
193
+
194
+ mapping = H.graph.get("mapping", None)
195
+ if mapping is None:
196
+ raise nx.NetworkXError("Invalid auxiliary digraph.")
197
+
198
+ kwargs = {"flow_func": flow_func, "residual": residual}
199
+
200
+ if flow_func is not preflow_push:
201
+ kwargs["cutoff"] = cutoff
202
+
203
+ if flow_func is shortest_augmenting_path:
204
+ kwargs["two_phase"] = True
205
+
206
+ return nx.maximum_flow_value(H, f"{mapping[s]}B", f"{mapping[t]}A", **kwargs)
207
+
208
+
209
+ @nx._dispatchable
210
+ def node_connectivity(G, s=None, t=None, flow_func=None):
211
+ r"""Returns node connectivity for a graph or digraph G.
212
+
213
+ Node connectivity is equal to the minimum number of nodes that
214
+ must be removed to disconnect G or render it trivial. If source
215
+ and target nodes are provided, this function returns the local node
216
+ connectivity: the minimum number of nodes that must be removed to break
217
+ all paths from source to target in G.
218
+
219
+ Parameters
220
+ ----------
221
+ G : NetworkX graph
222
+ Undirected graph
223
+
224
+ s : node
225
+ Source node. Optional. Default value: None.
226
+
227
+ t : node
228
+ Target node. Optional. Default value: None.
229
+
230
+ flow_func : function
231
+ A function for computing the maximum flow among a pair of nodes.
232
+ The function has to accept at least three parameters: a Digraph,
233
+ a source node, and a target node. And return a residual network
234
+ that follows NetworkX conventions (see :meth:`maximum_flow` for
235
+ details). If flow_func is None, the default maximum flow function
236
+ (:meth:`edmonds_karp`) is used. See below for details. The
237
+ choice of the default function may change from version
238
+ to version and should not be relied on. Default value: None.
239
+
240
+ Returns
241
+ -------
242
+ K : integer
243
+ Node connectivity of G, or local node connectivity if source
244
+ and target are provided.
245
+
246
+ Examples
247
+ --------
248
+ >>> # Platonic icosahedral graph is 5-node-connected
249
+ >>> G = nx.icosahedral_graph()
250
+ >>> nx.node_connectivity(G)
251
+ 5
252
+
253
+ You can use alternative flow algorithms for the underlying maximum
254
+ flow computation. In dense networks the algorithm
255
+ :meth:`shortest_augmenting_path` will usually perform better
256
+ than the default :meth:`edmonds_karp`, which is faster for
257
+ sparse networks with highly skewed degree distributions. Alternative
258
+ flow functions have to be explicitly imported from the flow package.
259
+
260
+ >>> from networkx.algorithms.flow import shortest_augmenting_path
261
+ >>> nx.node_connectivity(G, flow_func=shortest_augmenting_path)
262
+ 5
263
+
264
+ If you specify a pair of nodes (source and target) as parameters,
265
+ this function returns the value of local node connectivity.
266
+
267
+ >>> nx.node_connectivity(G, 3, 7)
268
+ 5
269
+
270
+ If you need to perform several local computations among different
271
+ pairs of nodes on the same graph, it is recommended that you reuse
272
+ the data structures used in the maximum flow computations. See
273
+ :meth:`local_node_connectivity` for details.
274
+
275
+ Notes
276
+ -----
277
+ This is a flow based implementation of node connectivity. The
278
+ algorithm works by solving $O((n-\delta-1+\delta(\delta-1)/2))$
279
+ maximum flow problems on an auxiliary digraph. Where $\delta$
280
+ is the minimum degree of G. For details about the auxiliary
281
+ digraph and the computation of local node connectivity see
282
+ :meth:`local_node_connectivity`. This implementation is based
283
+ on algorithm 11 in [1]_.
284
+
285
+ See also
286
+ --------
287
+ :meth:`local_node_connectivity`
288
+ :meth:`edge_connectivity`
289
+ :meth:`maximum_flow`
290
+ :meth:`edmonds_karp`
291
+ :meth:`preflow_push`
292
+ :meth:`shortest_augmenting_path`
293
+
294
+ References
295
+ ----------
296
+ .. [1] Abdol-Hossein Esfahanian. Connectivity Algorithms.
297
+ http://www.cse.msu.edu/~cse835/Papers/Graph_connectivity_revised.pdf
298
+
299
+ """
300
+ if (s is not None and t is None) or (s is None and t is not None):
301
+ raise nx.NetworkXError("Both source and target must be specified.")
302
+
303
+ # Local node connectivity
304
+ if s is not None and t is not None:
305
+ if s not in G:
306
+ raise nx.NetworkXError(f"node {s} not in graph")
307
+ if t not in G:
308
+ raise nx.NetworkXError(f"node {t} not in graph")
309
+ return local_node_connectivity(G, s, t, flow_func=flow_func)
310
+
311
+ # Global node connectivity
312
+ if G.is_directed():
313
+ if not nx.is_weakly_connected(G):
314
+ return 0
315
+ iter_func = itertools.permutations
316
+ # It is necessary to consider both predecessors
317
+ # and successors for directed graphs
318
+
319
+ def neighbors(v):
320
+ return itertools.chain.from_iterable([G.predecessors(v), G.successors(v)])
321
+
322
+ else:
323
+ if not nx.is_connected(G):
324
+ return 0
325
+ iter_func = itertools.combinations
326
+ neighbors = G.neighbors
327
+
328
+ # Reuse the auxiliary digraph and the residual network
329
+ H = build_auxiliary_node_connectivity(G)
330
+ R = build_residual_network(H, "capacity")
331
+ kwargs = {"flow_func": flow_func, "auxiliary": H, "residual": R}
332
+
333
+ # Pick a node with minimum degree
334
+ # Node connectivity is bounded by degree.
335
+ v, K = min(G.degree(), key=itemgetter(1))
336
+ # compute local node connectivity with all its non-neighbors nodes
337
+ for w in set(G) - set(neighbors(v)) - {v}:
338
+ kwargs["cutoff"] = K
339
+ K = min(K, local_node_connectivity(G, v, w, **kwargs))
340
+ # Also for non adjacent pairs of neighbors of v
341
+ for x, y in iter_func(neighbors(v), 2):
342
+ if y in G[x]:
343
+ continue
344
+ kwargs["cutoff"] = K
345
+ K = min(K, local_node_connectivity(G, x, y, **kwargs))
346
+
347
+ return K
348
+
349
+
350
+ @nx._dispatchable
351
+ def average_node_connectivity(G, flow_func=None):
352
+ r"""Returns the average connectivity of a graph G.
353
+
354
+ The average connectivity `\bar{\kappa}` of a graph G is the average
355
+ of local node connectivity over all pairs of nodes of G [1]_ .
356
+
357
+ .. math::
358
+
359
+ \bar{\kappa}(G) = \frac{\sum_{u,v} \kappa_{G}(u,v)}{{n \choose 2}}
360
+
361
+ Parameters
362
+ ----------
363
+
364
+ G : NetworkX graph
365
+ Undirected graph
366
+
367
+ flow_func : function
368
+ A function for computing the maximum flow among a pair of nodes.
369
+ The function has to accept at least three parameters: a Digraph,
370
+ a source node, and a target node. And return a residual network
371
+ that follows NetworkX conventions (see :meth:`maximum_flow` for
372
+ details). If flow_func is None, the default maximum flow function
373
+ (:meth:`edmonds_karp`) is used. See :meth:`local_node_connectivity`
374
+ for details. The choice of the default function may change from
375
+ version to version and should not be relied on. Default value: None.
376
+
377
+ Returns
378
+ -------
379
+ K : float
380
+ Average node connectivity
381
+
382
+ See also
383
+ --------
384
+ :meth:`local_node_connectivity`
385
+ :meth:`node_connectivity`
386
+ :meth:`edge_connectivity`
387
+ :meth:`maximum_flow`
388
+ :meth:`edmonds_karp`
389
+ :meth:`preflow_push`
390
+ :meth:`shortest_augmenting_path`
391
+
392
+ References
393
+ ----------
394
+ .. [1] Beineke, L., O. Oellermann, and R. Pippert (2002). The average
395
+ connectivity of a graph. Discrete mathematics 252(1-3), 31-45.
396
+ http://www.sciencedirect.com/science/article/pii/S0012365X01001807
397
+
398
+ """
399
+ if G.is_directed():
400
+ iter_func = itertools.permutations
401
+ else:
402
+ iter_func = itertools.combinations
403
+
404
+ # Reuse the auxiliary digraph and the residual network
405
+ H = build_auxiliary_node_connectivity(G)
406
+ R = build_residual_network(H, "capacity")
407
+ kwargs = {"flow_func": flow_func, "auxiliary": H, "residual": R}
408
+
409
+ num, den = 0, 0
410
+ for u, v in iter_func(G, 2):
411
+ num += local_node_connectivity(G, u, v, **kwargs)
412
+ den += 1
413
+
414
+ if den == 0: # Null Graph
415
+ return 0
416
+ return num / den
417
+
418
+
419
+ @nx._dispatchable
420
+ def all_pairs_node_connectivity(G, nbunch=None, flow_func=None):
421
+ """Compute node connectivity between all pairs of nodes of G.
422
+
423
+ Parameters
424
+ ----------
425
+ G : NetworkX graph
426
+ Undirected graph
427
+
428
+ nbunch: container
429
+ Container of nodes. If provided node connectivity will be computed
430
+ only over pairs of nodes in nbunch.
431
+
432
+ flow_func : function
433
+ A function for computing the maximum flow among a pair of nodes.
434
+ The function has to accept at least three parameters: a Digraph,
435
+ a source node, and a target node. And return a residual network
436
+ that follows NetworkX conventions (see :meth:`maximum_flow` for
437
+ details). If flow_func is None, the default maximum flow function
438
+ (:meth:`edmonds_karp`) is used. See below for details. The
439
+ choice of the default function may change from version
440
+ to version and should not be relied on. Default value: None.
441
+
442
+ Returns
443
+ -------
444
+ all_pairs : dict
445
+ A dictionary with node connectivity between all pairs of nodes
446
+ in G, or in nbunch if provided.
447
+
448
+ See also
449
+ --------
450
+ :meth:`local_node_connectivity`
451
+ :meth:`edge_connectivity`
452
+ :meth:`local_edge_connectivity`
453
+ :meth:`maximum_flow`
454
+ :meth:`edmonds_karp`
455
+ :meth:`preflow_push`
456
+ :meth:`shortest_augmenting_path`
457
+
458
+ """
459
+ if nbunch is None:
460
+ nbunch = G
461
+ else:
462
+ nbunch = set(nbunch)
463
+
464
+ directed = G.is_directed()
465
+ if directed:
466
+ iter_func = itertools.permutations
467
+ else:
468
+ iter_func = itertools.combinations
469
+
470
+ all_pairs = {n: {} for n in nbunch}
471
+
472
+ # Reuse auxiliary digraph and residual network
473
+ H = build_auxiliary_node_connectivity(G)
474
+ mapping = H.graph["mapping"]
475
+ R = build_residual_network(H, "capacity")
476
+ kwargs = {"flow_func": flow_func, "auxiliary": H, "residual": R}
477
+
478
+ for u, v in iter_func(nbunch, 2):
479
+ K = local_node_connectivity(G, u, v, **kwargs)
480
+ all_pairs[u][v] = K
481
+ if not directed:
482
+ all_pairs[v][u] = K
483
+
484
+ return all_pairs
485
+
486
+
487
+ @nx._dispatchable(graphs={"G": 0, "auxiliary?": 4})
488
+ def local_edge_connectivity(
489
+ G, s, t, flow_func=None, auxiliary=None, residual=None, cutoff=None
490
+ ):
491
+ r"""Returns local edge connectivity for nodes s and t in G.
492
+
493
+ Local edge connectivity for two nodes s and t is the minimum number
494
+ of edges that must be removed to disconnect them.
495
+
496
+ This is a flow based implementation of edge connectivity. We compute the
497
+ maximum flow on an auxiliary digraph build from the original
498
+ network (see below for details). This is equal to the local edge
499
+ connectivity because the value of a maximum s-t-flow is equal to the
500
+ capacity of a minimum s-t-cut (Ford and Fulkerson theorem) [1]_ .
501
+
502
+ Parameters
503
+ ----------
504
+ G : NetworkX graph
505
+ Undirected or directed graph
506
+
507
+ s : node
508
+ Source node
509
+
510
+ t : node
511
+ Target node
512
+
513
+ flow_func : function
514
+ A function for computing the maximum flow among a pair of nodes.
515
+ The function has to accept at least three parameters: a Digraph,
516
+ a source node, and a target node. And return a residual network
517
+ that follows NetworkX conventions (see :meth:`maximum_flow` for
518
+ details). If flow_func is None, the default maximum flow function
519
+ (:meth:`edmonds_karp`) is used. See below for details. The
520
+ choice of the default function may change from version
521
+ to version and should not be relied on. Default value: None.
522
+
523
+ auxiliary : NetworkX DiGraph
524
+ Auxiliary digraph for computing flow based edge connectivity. If
525
+ provided it will be reused instead of recreated. Default value: None.
526
+
527
+ residual : NetworkX DiGraph
528
+ Residual network to compute maximum flow. If provided it will be
529
+ reused instead of recreated. Default value: None.
530
+
531
+ cutoff : integer, float, or None (default: None)
532
+ If specified, the maximum flow algorithm will terminate when the
533
+ flow value reaches or exceeds the cutoff. This only works for flows
534
+ that support the cutoff parameter (most do) and is ignored otherwise.
535
+
536
+ Returns
537
+ -------
538
+ K : integer
539
+ local edge connectivity for nodes s and t.
540
+
541
+ Examples
542
+ --------
543
+ This function is not imported in the base NetworkX namespace, so you
544
+ have to explicitly import it from the connectivity package:
545
+
546
+ >>> from networkx.algorithms.connectivity import local_edge_connectivity
547
+
548
+ We use in this example the platonic icosahedral graph, which has edge
549
+ connectivity 5.
550
+
551
+ >>> G = nx.icosahedral_graph()
552
+ >>> local_edge_connectivity(G, 0, 6)
553
+ 5
554
+
555
+ If you need to compute local connectivity on several pairs of
556
+ nodes in the same graph, it is recommended that you reuse the
557
+ data structures that NetworkX uses in the computation: the
558
+ auxiliary digraph for edge connectivity, and the residual
559
+ network for the underlying maximum flow computation.
560
+
561
+ Example of how to compute local edge connectivity among
562
+ all pairs of nodes of the platonic icosahedral graph reusing
563
+ the data structures.
564
+
565
+ >>> import itertools
566
+ >>> # You also have to explicitly import the function for
567
+ >>> # building the auxiliary digraph from the connectivity package
568
+ >>> from networkx.algorithms.connectivity import build_auxiliary_edge_connectivity
569
+ >>> H = build_auxiliary_edge_connectivity(G)
570
+ >>> # And the function for building the residual network from the
571
+ >>> # flow package
572
+ >>> from networkx.algorithms.flow import build_residual_network
573
+ >>> # Note that the auxiliary digraph has an edge attribute named capacity
574
+ >>> R = build_residual_network(H, "capacity")
575
+ >>> result = dict.fromkeys(G, dict())
576
+ >>> # Reuse the auxiliary digraph and the residual network by passing them
577
+ >>> # as parameters
578
+ >>> for u, v in itertools.combinations(G, 2):
579
+ ... k = local_edge_connectivity(G, u, v, auxiliary=H, residual=R)
580
+ ... result[u][v] = k
581
+ >>> all(result[u][v] == 5 for u, v in itertools.combinations(G, 2))
582
+ True
583
+
584
+ You can also use alternative flow algorithms for computing edge
585
+ connectivity. For instance, in dense networks the algorithm
586
+ :meth:`shortest_augmenting_path` will usually perform better than
587
+ the default :meth:`edmonds_karp` which is faster for sparse
588
+ networks with highly skewed degree distributions. Alternative flow
589
+ functions have to be explicitly imported from the flow package.
590
+
591
+ >>> from networkx.algorithms.flow import shortest_augmenting_path
592
+ >>> local_edge_connectivity(G, 0, 6, flow_func=shortest_augmenting_path)
593
+ 5
594
+
595
+ Notes
596
+ -----
597
+ This is a flow based implementation of edge connectivity. We compute the
598
+ maximum flow using, by default, the :meth:`edmonds_karp` algorithm on an
599
+ auxiliary digraph build from the original input graph:
600
+
601
+ If the input graph is undirected, we replace each edge (`u`,`v`) with
602
+ two reciprocal arcs (`u`, `v`) and (`v`, `u`) and then we set the attribute
603
+ 'capacity' for each arc to 1. If the input graph is directed we simply
604
+ add the 'capacity' attribute. This is an implementation of algorithm 1
605
+ in [1]_.
606
+
607
+ The maximum flow in the auxiliary network is equal to the local edge
608
+ connectivity because the value of a maximum s-t-flow is equal to the
609
+ capacity of a minimum s-t-cut (Ford and Fulkerson theorem).
610
+
611
+ See also
612
+ --------
613
+ :meth:`edge_connectivity`
614
+ :meth:`local_node_connectivity`
615
+ :meth:`node_connectivity`
616
+ :meth:`maximum_flow`
617
+ :meth:`edmonds_karp`
618
+ :meth:`preflow_push`
619
+ :meth:`shortest_augmenting_path`
620
+
621
+ References
622
+ ----------
623
+ .. [1] Abdol-Hossein Esfahanian. Connectivity Algorithms.
624
+ http://www.cse.msu.edu/~cse835/Papers/Graph_connectivity_revised.pdf
625
+
626
+ """
627
+ if flow_func is None:
628
+ flow_func = default_flow_func
629
+
630
+ if auxiliary is None:
631
+ H = build_auxiliary_edge_connectivity(G)
632
+ else:
633
+ H = auxiliary
634
+
635
+ kwargs = {"flow_func": flow_func, "residual": residual}
636
+
637
+ if flow_func is not preflow_push:
638
+ kwargs["cutoff"] = cutoff
639
+
640
+ if flow_func is shortest_augmenting_path:
641
+ kwargs["two_phase"] = True
642
+
643
+ return nx.maximum_flow_value(H, s, t, **kwargs)
644
+
645
+
646
+ @nx._dispatchable
647
+ def edge_connectivity(G, s=None, t=None, flow_func=None, cutoff=None):
648
+ r"""Returns the edge connectivity of the graph or digraph G.
649
+
650
+ The edge connectivity is equal to the minimum number of edges that
651
+ must be removed to disconnect G or render it trivial. If source
652
+ and target nodes are provided, this function returns the local edge
653
+ connectivity: the minimum number of edges that must be removed to
654
+ break all paths from source to target in G.
655
+
656
+ Parameters
657
+ ----------
658
+ G : NetworkX graph
659
+ Undirected or directed graph
660
+
661
+ s : node
662
+ Source node. Optional. Default value: None.
663
+
664
+ t : node
665
+ Target node. Optional. Default value: None.
666
+
667
+ flow_func : function
668
+ A function for computing the maximum flow among a pair of nodes.
669
+ The function has to accept at least three parameters: a Digraph,
670
+ a source node, and a target node. And return a residual network
671
+ that follows NetworkX conventions (see :meth:`maximum_flow` for
672
+ details). If flow_func is None, the default maximum flow function
673
+ (:meth:`edmonds_karp`) is used. See below for details. The
674
+ choice of the default function may change from version
675
+ to version and should not be relied on. Default value: None.
676
+
677
+ cutoff : integer, float, or None (default: None)
678
+ If specified, the maximum flow algorithm will terminate when the
679
+ flow value reaches or exceeds the cutoff. This only works for flows
680
+ that support the cutoff parameter (most do) and is ignored otherwise.
681
+
682
+ Returns
683
+ -------
684
+ K : integer
685
+ Edge connectivity for G, or local edge connectivity if source
686
+ and target were provided
687
+
688
+ Examples
689
+ --------
690
+ >>> # Platonic icosahedral graph is 5-edge-connected
691
+ >>> G = nx.icosahedral_graph()
692
+ >>> nx.edge_connectivity(G)
693
+ 5
694
+
695
+ You can use alternative flow algorithms for the underlying
696
+ maximum flow computation. In dense networks the algorithm
697
+ :meth:`shortest_augmenting_path` will usually perform better
698
+ than the default :meth:`edmonds_karp`, which is faster for
699
+ sparse networks with highly skewed degree distributions.
700
+ Alternative flow functions have to be explicitly imported
701
+ from the flow package.
702
+
703
+ >>> from networkx.algorithms.flow import shortest_augmenting_path
704
+ >>> nx.edge_connectivity(G, flow_func=shortest_augmenting_path)
705
+ 5
706
+
707
+ If you specify a pair of nodes (source and target) as parameters,
708
+ this function returns the value of local edge connectivity.
709
+
710
+ >>> nx.edge_connectivity(G, 3, 7)
711
+ 5
712
+
713
+ If you need to perform several local computations among different
714
+ pairs of nodes on the same graph, it is recommended that you reuse
715
+ the data structures used in the maximum flow computations. See
716
+ :meth:`local_edge_connectivity` for details.
717
+
718
+ Notes
719
+ -----
720
+ This is a flow based implementation of global edge connectivity.
721
+ For undirected graphs the algorithm works by finding a 'small'
722
+ dominating set of nodes of G (see algorithm 7 in [1]_ ) and
723
+ computing local maximum flow (see :meth:`local_edge_connectivity`)
724
+ between an arbitrary node in the dominating set and the rest of
725
+ nodes in it. This is an implementation of algorithm 6 in [1]_ .
726
+ For directed graphs, the algorithm does n calls to the maximum
727
+ flow function. This is an implementation of algorithm 8 in [1]_ .
728
+
729
+ See also
730
+ --------
731
+ :meth:`local_edge_connectivity`
732
+ :meth:`local_node_connectivity`
733
+ :meth:`node_connectivity`
734
+ :meth:`maximum_flow`
735
+ :meth:`edmonds_karp`
736
+ :meth:`preflow_push`
737
+ :meth:`shortest_augmenting_path`
738
+ :meth:`k_edge_components`
739
+ :meth:`k_edge_subgraphs`
740
+
741
+ References
742
+ ----------
743
+ .. [1] Abdol-Hossein Esfahanian. Connectivity Algorithms.
744
+ http://www.cse.msu.edu/~cse835/Papers/Graph_connectivity_revised.pdf
745
+
746
+ """
747
+ if (s is not None and t is None) or (s is None and t is not None):
748
+ raise nx.NetworkXError("Both source and target must be specified.")
749
+
750
+ # Local edge connectivity
751
+ if s is not None and t is not None:
752
+ if s not in G:
753
+ raise nx.NetworkXError(f"node {s} not in graph")
754
+ if t not in G:
755
+ raise nx.NetworkXError(f"node {t} not in graph")
756
+ return local_edge_connectivity(G, s, t, flow_func=flow_func, cutoff=cutoff)
757
+
758
+ # Global edge connectivity
759
+ # reuse auxiliary digraph and residual network
760
+ H = build_auxiliary_edge_connectivity(G)
761
+ R = build_residual_network(H, "capacity")
762
+ kwargs = {"flow_func": flow_func, "auxiliary": H, "residual": R}
763
+
764
+ if G.is_directed():
765
+ # Algorithm 8 in [1]
766
+ if not nx.is_weakly_connected(G):
767
+ return 0
768
+
769
+ # initial value for \lambda is minimum degree
770
+ L = min(d for n, d in G.degree())
771
+ nodes = list(G)
772
+ n = len(nodes)
773
+
774
+ if cutoff is not None:
775
+ L = min(cutoff, L)
776
+
777
+ for i in range(n):
778
+ kwargs["cutoff"] = L
779
+ try:
780
+ L = min(L, local_edge_connectivity(G, nodes[i], nodes[i + 1], **kwargs))
781
+ except IndexError: # last node!
782
+ L = min(L, local_edge_connectivity(G, nodes[i], nodes[0], **kwargs))
783
+ return L
784
+ else: # undirected
785
+ # Algorithm 6 in [1]
786
+ if not nx.is_connected(G):
787
+ return 0
788
+
789
+ # initial value for \lambda is minimum degree
790
+ L = min(d for n, d in G.degree())
791
+
792
+ if cutoff is not None:
793
+ L = min(cutoff, L)
794
+
795
+ # A dominating set is \lambda-covering
796
+ # We need a dominating set with at least two nodes
797
+ for node in G:
798
+ D = nx.dominating_set(G, start_with=node)
799
+ v = D.pop()
800
+ if D:
801
+ break
802
+ else:
803
+ # in complete graphs the dominating sets will always be of one node
804
+ # thus we return min degree
805
+ return L
806
+
807
+ for w in D:
808
+ kwargs["cutoff"] = L
809
+ L = min(L, local_edge_connectivity(G, v, w, **kwargs))
810
+
811
+ return L
.venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/cuts.py ADDED
@@ -0,0 +1,612 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Flow based cut algorithms
3
+ """
4
+
5
+ import itertools
6
+
7
+ import networkx as nx
8
+
9
+ # Define the default maximum flow function to use in all flow based
10
+ # cut algorithms.
11
+ from networkx.algorithms.flow import build_residual_network, edmonds_karp
12
+
13
+ default_flow_func = edmonds_karp
14
+
15
+ from .utils import build_auxiliary_edge_connectivity, build_auxiliary_node_connectivity
16
+
17
+ __all__ = [
18
+ "minimum_st_node_cut",
19
+ "minimum_node_cut",
20
+ "minimum_st_edge_cut",
21
+ "minimum_edge_cut",
22
+ ]
23
+
24
+
25
+ @nx._dispatchable(
26
+ graphs={"G": 0, "auxiliary?": 4},
27
+ preserve_edge_attrs={"auxiliary": {"capacity": float("inf")}},
28
+ preserve_graph_attrs={"auxiliary"},
29
+ )
30
+ def minimum_st_edge_cut(G, s, t, flow_func=None, auxiliary=None, residual=None):
31
+ """Returns the edges of the cut-set of a minimum (s, t)-cut.
32
+
33
+ This function returns the set of edges of minimum cardinality that,
34
+ if removed, would destroy all paths among source and target in G.
35
+ Edge weights are not considered. See :meth:`minimum_cut` for
36
+ computing minimum cuts considering edge weights.
37
+
38
+ Parameters
39
+ ----------
40
+ G : NetworkX graph
41
+
42
+ s : node
43
+ Source node for the flow.
44
+
45
+ t : node
46
+ Sink node for the flow.
47
+
48
+ auxiliary : NetworkX DiGraph
49
+ Auxiliary digraph to compute flow based node connectivity. It has
50
+ to have a graph attribute called mapping with a dictionary mapping
51
+ node names in G and in the auxiliary digraph. If provided
52
+ it will be reused instead of recreated. Default value: None.
53
+
54
+ flow_func : function
55
+ A function for computing the maximum flow among a pair of nodes.
56
+ The function has to accept at least three parameters: a Digraph,
57
+ a source node, and a target node. And return a residual network
58
+ that follows NetworkX conventions (see :meth:`maximum_flow` for
59
+ details). If flow_func is None, the default maximum flow function
60
+ (:meth:`edmonds_karp`) is used. See :meth:`node_connectivity` for
61
+ details. The choice of the default function may change from version
62
+ to version and should not be relied on. Default value: None.
63
+
64
+ residual : NetworkX DiGraph
65
+ Residual network to compute maximum flow. If provided it will be
66
+ reused instead of recreated. Default value: None.
67
+
68
+ Returns
69
+ -------
70
+ cutset : set
71
+ Set of edges that, if removed from the graph, will disconnect it.
72
+
73
+ See also
74
+ --------
75
+ :meth:`minimum_cut`
76
+ :meth:`minimum_node_cut`
77
+ :meth:`minimum_edge_cut`
78
+ :meth:`stoer_wagner`
79
+ :meth:`node_connectivity`
80
+ :meth:`edge_connectivity`
81
+ :meth:`maximum_flow`
82
+ :meth:`edmonds_karp`
83
+ :meth:`preflow_push`
84
+ :meth:`shortest_augmenting_path`
85
+
86
+ Examples
87
+ --------
88
+ This function is not imported in the base NetworkX namespace, so you
89
+ have to explicitly import it from the connectivity package:
90
+
91
+ >>> from networkx.algorithms.connectivity import minimum_st_edge_cut
92
+
93
+ We use in this example the platonic icosahedral graph, which has edge
94
+ connectivity 5.
95
+
96
+ >>> G = nx.icosahedral_graph()
97
+ >>> len(minimum_st_edge_cut(G, 0, 6))
98
+ 5
99
+
100
+ If you need to compute local edge cuts on several pairs of
101
+ nodes in the same graph, it is recommended that you reuse the
102
+ data structures that NetworkX uses in the computation: the
103
+ auxiliary digraph for edge connectivity, and the residual
104
+ network for the underlying maximum flow computation.
105
+
106
+ Example of how to compute local edge cuts among all pairs of
107
+ nodes of the platonic icosahedral graph reusing the data
108
+ structures.
109
+
110
+ >>> import itertools
111
+ >>> # You also have to explicitly import the function for
112
+ >>> # building the auxiliary digraph from the connectivity package
113
+ >>> from networkx.algorithms.connectivity import build_auxiliary_edge_connectivity
114
+ >>> H = build_auxiliary_edge_connectivity(G)
115
+ >>> # And the function for building the residual network from the
116
+ >>> # flow package
117
+ >>> from networkx.algorithms.flow import build_residual_network
118
+ >>> # Note that the auxiliary digraph has an edge attribute named capacity
119
+ >>> R = build_residual_network(H, "capacity")
120
+ >>> result = dict.fromkeys(G, dict())
121
+ >>> # Reuse the auxiliary digraph and the residual network by passing them
122
+ >>> # as parameters
123
+ >>> for u, v in itertools.combinations(G, 2):
124
+ ... k = len(minimum_st_edge_cut(G, u, v, auxiliary=H, residual=R))
125
+ ... result[u][v] = k
126
+ >>> all(result[u][v] == 5 for u, v in itertools.combinations(G, 2))
127
+ True
128
+
129
+ You can also use alternative flow algorithms for computing edge
130
+ cuts. For instance, in dense networks the algorithm
131
+ :meth:`shortest_augmenting_path` will usually perform better than
132
+ the default :meth:`edmonds_karp` which is faster for sparse
133
+ networks with highly skewed degree distributions. Alternative flow
134
+ functions have to be explicitly imported from the flow package.
135
+
136
+ >>> from networkx.algorithms.flow import shortest_augmenting_path
137
+ >>> len(minimum_st_edge_cut(G, 0, 6, flow_func=shortest_augmenting_path))
138
+ 5
139
+
140
+ """
141
+ if flow_func is None:
142
+ flow_func = default_flow_func
143
+
144
+ if auxiliary is None:
145
+ H = build_auxiliary_edge_connectivity(G)
146
+ else:
147
+ H = auxiliary
148
+
149
+ kwargs = {"capacity": "capacity", "flow_func": flow_func, "residual": residual}
150
+
151
+ cut_value, partition = nx.minimum_cut(H, s, t, **kwargs)
152
+ reachable, non_reachable = partition
153
+ # Any edge in the original graph linking the two sets in the
154
+ # partition is part of the edge cutset
155
+ cutset = set()
156
+ for u, nbrs in ((n, G[n]) for n in reachable):
157
+ cutset.update((u, v) for v in nbrs if v in non_reachable)
158
+
159
+ return cutset
160
+
161
+
162
+ @nx._dispatchable(
163
+ graphs={"G": 0, "auxiliary?": 4},
164
+ preserve_node_attrs={"auxiliary": {"id": None}},
165
+ preserve_graph_attrs={"auxiliary"},
166
+ )
167
+ def minimum_st_node_cut(G, s, t, flow_func=None, auxiliary=None, residual=None):
168
+ r"""Returns a set of nodes of minimum cardinality that disconnect source
169
+ from target in G.
170
+
171
+ This function returns the set of nodes of minimum cardinality that,
172
+ if removed, would destroy all paths among source and target in G.
173
+
174
+ Parameters
175
+ ----------
176
+ G : NetworkX graph
177
+
178
+ s : node
179
+ Source node.
180
+
181
+ t : node
182
+ Target node.
183
+
184
+ flow_func : function
185
+ A function for computing the maximum flow among a pair of nodes.
186
+ The function has to accept at least three parameters: a Digraph,
187
+ a source node, and a target node. And return a residual network
188
+ that follows NetworkX conventions (see :meth:`maximum_flow` for
189
+ details). If flow_func is None, the default maximum flow function
190
+ (:meth:`edmonds_karp`) is used. See below for details. The choice
191
+ of the default function may change from version to version and
192
+ should not be relied on. Default value: None.
193
+
194
+ auxiliary : NetworkX DiGraph
195
+ Auxiliary digraph to compute flow based node connectivity. It has
196
+ to have a graph attribute called mapping with a dictionary mapping
197
+ node names in G and in the auxiliary digraph. If provided
198
+ it will be reused instead of recreated. Default value: None.
199
+
200
+ residual : NetworkX DiGraph
201
+ Residual network to compute maximum flow. If provided it will be
202
+ reused instead of recreated. Default value: None.
203
+
204
+ Returns
205
+ -------
206
+ cutset : set
207
+ Set of nodes that, if removed, would destroy all paths between
208
+ source and target in G.
209
+
210
+ Examples
211
+ --------
212
+ This function is not imported in the base NetworkX namespace, so you
213
+ have to explicitly import it from the connectivity package:
214
+
215
+ >>> from networkx.algorithms.connectivity import minimum_st_node_cut
216
+
217
+ We use in this example the platonic icosahedral graph, which has node
218
+ connectivity 5.
219
+
220
+ >>> G = nx.icosahedral_graph()
221
+ >>> len(minimum_st_node_cut(G, 0, 6))
222
+ 5
223
+
224
+ If you need to compute local st cuts between several pairs of
225
+ nodes in the same graph, it is recommended that you reuse the
226
+ data structures that NetworkX uses in the computation: the
227
+ auxiliary digraph for node connectivity and node cuts, and the
228
+ residual network for the underlying maximum flow computation.
229
+
230
+ Example of how to compute local st node cuts reusing the data
231
+ structures:
232
+
233
+ >>> # You also have to explicitly import the function for
234
+ >>> # building the auxiliary digraph from the connectivity package
235
+ >>> from networkx.algorithms.connectivity import build_auxiliary_node_connectivity
236
+ >>> H = build_auxiliary_node_connectivity(G)
237
+ >>> # And the function for building the residual network from the
238
+ >>> # flow package
239
+ >>> from networkx.algorithms.flow import build_residual_network
240
+ >>> # Note that the auxiliary digraph has an edge attribute named capacity
241
+ >>> R = build_residual_network(H, "capacity")
242
+ >>> # Reuse the auxiliary digraph and the residual network by passing them
243
+ >>> # as parameters
244
+ >>> len(minimum_st_node_cut(G, 0, 6, auxiliary=H, residual=R))
245
+ 5
246
+
247
+ You can also use alternative flow algorithms for computing minimum st
248
+ node cuts. For instance, in dense networks the algorithm
249
+ :meth:`shortest_augmenting_path` will usually perform better than
250
+ the default :meth:`edmonds_karp` which is faster for sparse
251
+ networks with highly skewed degree distributions. Alternative flow
252
+ functions have to be explicitly imported from the flow package.
253
+
254
+ >>> from networkx.algorithms.flow import shortest_augmenting_path
255
+ >>> len(minimum_st_node_cut(G, 0, 6, flow_func=shortest_augmenting_path))
256
+ 5
257
+
258
+ Notes
259
+ -----
260
+ This is a flow based implementation of minimum node cut. The algorithm
261
+ is based in solving a number of maximum flow computations to determine
262
+ the capacity of the minimum cut on an auxiliary directed network that
263
+ corresponds to the minimum node cut of G. It handles both directed
264
+ and undirected graphs. This implementation is based on algorithm 11
265
+ in [1]_.
266
+
267
+ See also
268
+ --------
269
+ :meth:`minimum_node_cut`
270
+ :meth:`minimum_edge_cut`
271
+ :meth:`stoer_wagner`
272
+ :meth:`node_connectivity`
273
+ :meth:`edge_connectivity`
274
+ :meth:`maximum_flow`
275
+ :meth:`edmonds_karp`
276
+ :meth:`preflow_push`
277
+ :meth:`shortest_augmenting_path`
278
+
279
+ References
280
+ ----------
281
+ .. [1] Abdol-Hossein Esfahanian. Connectivity Algorithms.
282
+ http://www.cse.msu.edu/~cse835/Papers/Graph_connectivity_revised.pdf
283
+
284
+ """
285
+ if auxiliary is None:
286
+ H = build_auxiliary_node_connectivity(G)
287
+ else:
288
+ H = auxiliary
289
+
290
+ mapping = H.graph.get("mapping", None)
291
+ if mapping is None:
292
+ raise nx.NetworkXError("Invalid auxiliary digraph.")
293
+ if G.has_edge(s, t) or G.has_edge(t, s):
294
+ return {}
295
+ kwargs = {"flow_func": flow_func, "residual": residual, "auxiliary": H}
296
+
297
+ # The edge cut in the auxiliary digraph corresponds to the node cut in the
298
+ # original graph.
299
+ edge_cut = minimum_st_edge_cut(H, f"{mapping[s]}B", f"{mapping[t]}A", **kwargs)
300
+ # Each node in the original graph maps to two nodes of the auxiliary graph
301
+ node_cut = {H.nodes[node]["id"] for edge in edge_cut for node in edge}
302
+ return node_cut - {s, t}
303
+
304
+
305
+ @nx._dispatchable
306
+ def minimum_node_cut(G, s=None, t=None, flow_func=None):
307
+ r"""Returns a set of nodes of minimum cardinality that disconnects G.
308
+
309
+ If source and target nodes are provided, this function returns the
310
+ set of nodes of minimum cardinality that, if removed, would destroy
311
+ all paths among source and target in G. If not, it returns a set
312
+ of nodes of minimum cardinality that disconnects G.
313
+
314
+ Parameters
315
+ ----------
316
+ G : NetworkX graph
317
+
318
+ s : node
319
+ Source node. Optional. Default value: None.
320
+
321
+ t : node
322
+ Target node. Optional. Default value: None.
323
+
324
+ flow_func : function
325
+ A function for computing the maximum flow among a pair of nodes.
326
+ The function has to accept at least three parameters: a Digraph,
327
+ a source node, and a target node. And return a residual network
328
+ that follows NetworkX conventions (see :meth:`maximum_flow` for
329
+ details). If flow_func is None, the default maximum flow function
330
+ (:meth:`edmonds_karp`) is used. See below for details. The
331
+ choice of the default function may change from version
332
+ to version and should not be relied on. Default value: None.
333
+
334
+ Returns
335
+ -------
336
+ cutset : set
337
+ Set of nodes that, if removed, would disconnect G. If source
338
+ and target nodes are provided, the set contains the nodes that
339
+ if removed, would destroy all paths between source and target.
340
+
341
+ Examples
342
+ --------
343
+ >>> # Platonic icosahedral graph has node connectivity 5
344
+ >>> G = nx.icosahedral_graph()
345
+ >>> node_cut = nx.minimum_node_cut(G)
346
+ >>> len(node_cut)
347
+ 5
348
+
349
+ You can use alternative flow algorithms for the underlying maximum
350
+ flow computation. In dense networks the algorithm
351
+ :meth:`shortest_augmenting_path` will usually perform better
352
+ than the default :meth:`edmonds_karp`, which is faster for
353
+ sparse networks with highly skewed degree distributions. Alternative
354
+ flow functions have to be explicitly imported from the flow package.
355
+
356
+ >>> from networkx.algorithms.flow import shortest_augmenting_path
357
+ >>> node_cut == nx.minimum_node_cut(G, flow_func=shortest_augmenting_path)
358
+ True
359
+
360
+ If you specify a pair of nodes (source and target) as parameters,
361
+ this function returns a local st node cut.
362
+
363
+ >>> len(nx.minimum_node_cut(G, 3, 7))
364
+ 5
365
+
366
+ If you need to perform several local st cuts among different
367
+ pairs of nodes on the same graph, it is recommended that you reuse
368
+ the data structures used in the maximum flow computations. See
369
+ :meth:`minimum_st_node_cut` for details.
370
+
371
+ Notes
372
+ -----
373
+ This is a flow based implementation of minimum node cut. The algorithm
374
+ is based in solving a number of maximum flow computations to determine
375
+ the capacity of the minimum cut on an auxiliary directed network that
376
+ corresponds to the minimum node cut of G. It handles both directed
377
+ and undirected graphs. This implementation is based on algorithm 11
378
+ in [1]_.
379
+
380
+ See also
381
+ --------
382
+ :meth:`minimum_st_node_cut`
383
+ :meth:`minimum_cut`
384
+ :meth:`minimum_edge_cut`
385
+ :meth:`stoer_wagner`
386
+ :meth:`node_connectivity`
387
+ :meth:`edge_connectivity`
388
+ :meth:`maximum_flow`
389
+ :meth:`edmonds_karp`
390
+ :meth:`preflow_push`
391
+ :meth:`shortest_augmenting_path`
392
+
393
+ References
394
+ ----------
395
+ .. [1] Abdol-Hossein Esfahanian. Connectivity Algorithms.
396
+ http://www.cse.msu.edu/~cse835/Papers/Graph_connectivity_revised.pdf
397
+
398
+ """
399
+ if (s is not None and t is None) or (s is None and t is not None):
400
+ raise nx.NetworkXError("Both source and target must be specified.")
401
+
402
+ # Local minimum node cut.
403
+ if s is not None and t is not None:
404
+ if s not in G:
405
+ raise nx.NetworkXError(f"node {s} not in graph")
406
+ if t not in G:
407
+ raise nx.NetworkXError(f"node {t} not in graph")
408
+ return minimum_st_node_cut(G, s, t, flow_func=flow_func)
409
+
410
+ # Global minimum node cut.
411
+ # Analog to the algorithm 11 for global node connectivity in [1].
412
+ if G.is_directed():
413
+ if not nx.is_weakly_connected(G):
414
+ raise nx.NetworkXError("Input graph is not connected")
415
+ iter_func = itertools.permutations
416
+
417
+ def neighbors(v):
418
+ return itertools.chain.from_iterable([G.predecessors(v), G.successors(v)])
419
+
420
+ else:
421
+ if not nx.is_connected(G):
422
+ raise nx.NetworkXError("Input graph is not connected")
423
+ iter_func = itertools.combinations
424
+ neighbors = G.neighbors
425
+
426
+ # Reuse the auxiliary digraph and the residual network.
427
+ H = build_auxiliary_node_connectivity(G)
428
+ R = build_residual_network(H, "capacity")
429
+ kwargs = {"flow_func": flow_func, "auxiliary": H, "residual": R}
430
+
431
+ # Choose a node with minimum degree.
432
+ v = min(G, key=G.degree)
433
+ # Initial node cutset is all neighbors of the node with minimum degree.
434
+ min_cut = set(G[v])
435
+ # Compute st node cuts between v and all its non-neighbors nodes in G.
436
+ for w in set(G) - set(neighbors(v)) - {v}:
437
+ this_cut = minimum_st_node_cut(G, v, w, **kwargs)
438
+ if len(min_cut) >= len(this_cut):
439
+ min_cut = this_cut
440
+ # Also for non adjacent pairs of neighbors of v.
441
+ for x, y in iter_func(neighbors(v), 2):
442
+ if y in G[x]:
443
+ continue
444
+ this_cut = minimum_st_node_cut(G, x, y, **kwargs)
445
+ if len(min_cut) >= len(this_cut):
446
+ min_cut = this_cut
447
+
448
+ return min_cut
449
+
450
+
451
+ @nx._dispatchable
452
+ def minimum_edge_cut(G, s=None, t=None, flow_func=None):
453
+ r"""Returns a set of edges of minimum cardinality that disconnects G.
454
+
455
+ If source and target nodes are provided, this function returns the
456
+ set of edges of minimum cardinality that, if removed, would break
457
+ all paths among source and target in G. If not, it returns a set of
458
+ edges of minimum cardinality that disconnects G.
459
+
460
+ Parameters
461
+ ----------
462
+ G : NetworkX graph
463
+
464
+ s : node
465
+ Source node. Optional. Default value: None.
466
+
467
+ t : node
468
+ Target node. Optional. Default value: None.
469
+
470
+ flow_func : function
471
+ A function for computing the maximum flow among a pair of nodes.
472
+ The function has to accept at least three parameters: a Digraph,
473
+ a source node, and a target node. And return a residual network
474
+ that follows NetworkX conventions (see :meth:`maximum_flow` for
475
+ details). If flow_func is None, the default maximum flow function
476
+ (:meth:`edmonds_karp`) is used. See below for details. The
477
+ choice of the default function may change from version
478
+ to version and should not be relied on. Default value: None.
479
+
480
+ Returns
481
+ -------
482
+ cutset : set
483
+ Set of edges that, if removed, would disconnect G. If source
484
+ and target nodes are provided, the set contains the edges that
485
+ if removed, would destroy all paths between source and target.
486
+
487
+ Examples
488
+ --------
489
+ >>> # Platonic icosahedral graph has edge connectivity 5
490
+ >>> G = nx.icosahedral_graph()
491
+ >>> len(nx.minimum_edge_cut(G))
492
+ 5
493
+
494
+ You can use alternative flow algorithms for the underlying
495
+ maximum flow computation. In dense networks the algorithm
496
+ :meth:`shortest_augmenting_path` will usually perform better
497
+ than the default :meth:`edmonds_karp`, which is faster for
498
+ sparse networks with highly skewed degree distributions.
499
+ Alternative flow functions have to be explicitly imported
500
+ from the flow package.
501
+
502
+ >>> from networkx.algorithms.flow import shortest_augmenting_path
503
+ >>> len(nx.minimum_edge_cut(G, flow_func=shortest_augmenting_path))
504
+ 5
505
+
506
+ If you specify a pair of nodes (source and target) as parameters,
507
+ this function returns the value of local edge connectivity.
508
+
509
+ >>> nx.edge_connectivity(G, 3, 7)
510
+ 5
511
+
512
+ If you need to perform several local computations among different
513
+ pairs of nodes on the same graph, it is recommended that you reuse
514
+ the data structures used in the maximum flow computations. See
515
+ :meth:`local_edge_connectivity` for details.
516
+
517
+ Notes
518
+ -----
519
+ This is a flow based implementation of minimum edge cut. For
520
+ undirected graphs the algorithm works by finding a 'small' dominating
521
+ set of nodes of G (see algorithm 7 in [1]_) and computing the maximum
522
+ flow between an arbitrary node in the dominating set and the rest of
523
+ nodes in it. This is an implementation of algorithm 6 in [1]_. For
524
+ directed graphs, the algorithm does n calls to the max flow function.
525
+ The function raises an error if the directed graph is not weakly
526
+ connected and returns an empty set if it is weakly connected.
527
+ It is an implementation of algorithm 8 in [1]_.
528
+
529
+ See also
530
+ --------
531
+ :meth:`minimum_st_edge_cut`
532
+ :meth:`minimum_node_cut`
533
+ :meth:`stoer_wagner`
534
+ :meth:`node_connectivity`
535
+ :meth:`edge_connectivity`
536
+ :meth:`maximum_flow`
537
+ :meth:`edmonds_karp`
538
+ :meth:`preflow_push`
539
+ :meth:`shortest_augmenting_path`
540
+
541
+ References
542
+ ----------
543
+ .. [1] Abdol-Hossein Esfahanian. Connectivity Algorithms.
544
+ http://www.cse.msu.edu/~cse835/Papers/Graph_connectivity_revised.pdf
545
+
546
+ """
547
+ if (s is not None and t is None) or (s is None and t is not None):
548
+ raise nx.NetworkXError("Both source and target must be specified.")
549
+
550
+ # reuse auxiliary digraph and residual network
551
+ H = build_auxiliary_edge_connectivity(G)
552
+ R = build_residual_network(H, "capacity")
553
+ kwargs = {"flow_func": flow_func, "residual": R, "auxiliary": H}
554
+
555
+ # Local minimum edge cut if s and t are not None
556
+ if s is not None and t is not None:
557
+ if s not in G:
558
+ raise nx.NetworkXError(f"node {s} not in graph")
559
+ if t not in G:
560
+ raise nx.NetworkXError(f"node {t} not in graph")
561
+ return minimum_st_edge_cut(H, s, t, **kwargs)
562
+
563
+ # Global minimum edge cut
564
+ # Analog to the algorithm for global edge connectivity
565
+ if G.is_directed():
566
+ # Based on algorithm 8 in [1]
567
+ if not nx.is_weakly_connected(G):
568
+ raise nx.NetworkXError("Input graph is not connected")
569
+
570
+ # Initial cutset is all edges of a node with minimum degree
571
+ node = min(G, key=G.degree)
572
+ min_cut = set(G.edges(node))
573
+ nodes = list(G)
574
+ n = len(nodes)
575
+ for i in range(n):
576
+ try:
577
+ this_cut = minimum_st_edge_cut(H, nodes[i], nodes[i + 1], **kwargs)
578
+ if len(this_cut) <= len(min_cut):
579
+ min_cut = this_cut
580
+ except IndexError: # Last node!
581
+ this_cut = minimum_st_edge_cut(H, nodes[i], nodes[0], **kwargs)
582
+ if len(this_cut) <= len(min_cut):
583
+ min_cut = this_cut
584
+
585
+ return min_cut
586
+
587
+ else: # undirected
588
+ # Based on algorithm 6 in [1]
589
+ if not nx.is_connected(G):
590
+ raise nx.NetworkXError("Input graph is not connected")
591
+
592
+ # Initial cutset is all edges of a node with minimum degree
593
+ node = min(G, key=G.degree)
594
+ min_cut = set(G.edges(node))
595
+ # A dominating set is \lambda-covering
596
+ # We need a dominating set with at least two nodes
597
+ for node in G:
598
+ D = nx.dominating_set(G, start_with=node)
599
+ v = D.pop()
600
+ if D:
601
+ break
602
+ else:
603
+ # in complete graphs the dominating set will always be of one node
604
+ # thus we return min_cut, which now contains the edges of a node
605
+ # with minimum degree
606
+ return min_cut
607
+ for w in D:
608
+ this_cut = minimum_st_edge_cut(H, v, w, **kwargs)
609
+ if len(this_cut) <= len(min_cut):
610
+ min_cut = this_cut
611
+
612
+ return min_cut
.venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/disjoint_paths.py ADDED
@@ -0,0 +1,408 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Flow based node and edge disjoint paths."""
2
+
3
+ import networkx as nx
4
+
5
+ # Define the default maximum flow function to use for the underlying
6
+ # maximum flow computations
7
+ from networkx.algorithms.flow import (
8
+ edmonds_karp,
9
+ preflow_push,
10
+ shortest_augmenting_path,
11
+ )
12
+ from networkx.exception import NetworkXNoPath
13
+
14
+ default_flow_func = edmonds_karp
15
+ from itertools import filterfalse as _filterfalse
16
+
17
+ # Functions to build auxiliary data structures.
18
+ from .utils import build_auxiliary_edge_connectivity, build_auxiliary_node_connectivity
19
+
20
+ __all__ = ["edge_disjoint_paths", "node_disjoint_paths"]
21
+
22
+
23
+ @nx._dispatchable(
24
+ graphs={"G": 0, "auxiliary?": 5},
25
+ preserve_edge_attrs={"auxiliary": {"capacity": float("inf")}},
26
+ )
27
+ def edge_disjoint_paths(
28
+ G, s, t, flow_func=None, cutoff=None, auxiliary=None, residual=None
29
+ ):
30
+ """Returns the edges disjoint paths between source and target.
31
+
32
+ Edge disjoint paths are paths that do not share any edge. The
33
+ number of edge disjoint paths between source and target is equal
34
+ to their edge connectivity.
35
+
36
+ Parameters
37
+ ----------
38
+ G : NetworkX graph
39
+
40
+ s : node
41
+ Source node for the flow.
42
+
43
+ t : node
44
+ Sink node for the flow.
45
+
46
+ flow_func : function
47
+ A function for computing the maximum flow among a pair of nodes.
48
+ The function has to accept at least three parameters: a Digraph,
49
+ a source node, and a target node. And return a residual network
50
+ that follows NetworkX conventions (see :meth:`maximum_flow` for
51
+ details). If flow_func is None, the default maximum flow function
52
+ (:meth:`edmonds_karp`) is used. The choice of the default function
53
+ may change from version to version and should not be relied on.
54
+ Default value: None.
55
+
56
+ cutoff : integer or None (default: None)
57
+ Maximum number of paths to yield. If specified, the maximum flow
58
+ algorithm will terminate when the flow value reaches or exceeds the
59
+ cutoff. This only works for flows that support the cutoff parameter
60
+ (most do) and is ignored otherwise.
61
+
62
+ auxiliary : NetworkX DiGraph
63
+ Auxiliary digraph to compute flow based edge connectivity. It has
64
+ to have a graph attribute called mapping with a dictionary mapping
65
+ node names in G and in the auxiliary digraph. If provided
66
+ it will be reused instead of recreated. Default value: None.
67
+
68
+ residual : NetworkX DiGraph
69
+ Residual network to compute maximum flow. If provided it will be
70
+ reused instead of recreated. Default value: None.
71
+
72
+ Returns
73
+ -------
74
+ paths : generator
75
+ A generator of edge independent paths.
76
+
77
+ Raises
78
+ ------
79
+ NetworkXNoPath
80
+ If there is no path between source and target.
81
+
82
+ NetworkXError
83
+ If source or target are not in the graph G.
84
+
85
+ See also
86
+ --------
87
+ :meth:`node_disjoint_paths`
88
+ :meth:`edge_connectivity`
89
+ :meth:`maximum_flow`
90
+ :meth:`edmonds_karp`
91
+ :meth:`preflow_push`
92
+ :meth:`shortest_augmenting_path`
93
+
94
+ Examples
95
+ --------
96
+ We use in this example the platonic icosahedral graph, which has node
97
+ edge connectivity 5, thus there are 5 edge disjoint paths between any
98
+ pair of nodes.
99
+
100
+ >>> G = nx.icosahedral_graph()
101
+ >>> len(list(nx.edge_disjoint_paths(G, 0, 6)))
102
+ 5
103
+
104
+
105
+ If you need to compute edge disjoint paths on several pairs of
106
+ nodes in the same graph, it is recommended that you reuse the
107
+ data structures that NetworkX uses in the computation: the
108
+ auxiliary digraph for edge connectivity, and the residual
109
+ network for the underlying maximum flow computation.
110
+
111
+ Example of how to compute edge disjoint paths among all pairs of
112
+ nodes of the platonic icosahedral graph reusing the data
113
+ structures.
114
+
115
+ >>> import itertools
116
+ >>> # You also have to explicitly import the function for
117
+ >>> # building the auxiliary digraph from the connectivity package
118
+ >>> from networkx.algorithms.connectivity import build_auxiliary_edge_connectivity
119
+ >>> H = build_auxiliary_edge_connectivity(G)
120
+ >>> # And the function for building the residual network from the
121
+ >>> # flow package
122
+ >>> from networkx.algorithms.flow import build_residual_network
123
+ >>> # Note that the auxiliary digraph has an edge attribute named capacity
124
+ >>> R = build_residual_network(H, "capacity")
125
+ >>> result = {n: {} for n in G}
126
+ >>> # Reuse the auxiliary digraph and the residual network by passing them
127
+ >>> # as arguments
128
+ >>> for u, v in itertools.combinations(G, 2):
129
+ ... k = len(list(nx.edge_disjoint_paths(G, u, v, auxiliary=H, residual=R)))
130
+ ... result[u][v] = k
131
+ >>> all(result[u][v] == 5 for u, v in itertools.combinations(G, 2))
132
+ True
133
+
134
+ You can also use alternative flow algorithms for computing edge disjoint
135
+ paths. For instance, in dense networks the algorithm
136
+ :meth:`shortest_augmenting_path` will usually perform better than
137
+ the default :meth:`edmonds_karp` which is faster for sparse
138
+ networks with highly skewed degree distributions. Alternative flow
139
+ functions have to be explicitly imported from the flow package.
140
+
141
+ >>> from networkx.algorithms.flow import shortest_augmenting_path
142
+ >>> len(list(nx.edge_disjoint_paths(G, 0, 6, flow_func=shortest_augmenting_path)))
143
+ 5
144
+
145
+ Notes
146
+ -----
147
+ This is a flow based implementation of edge disjoint paths. We compute
148
+ the maximum flow between source and target on an auxiliary directed
149
+ network. The saturated edges in the residual network after running the
150
+ maximum flow algorithm correspond to edge disjoint paths between source
151
+ and target in the original network. This function handles both directed
152
+ and undirected graphs, and can use all flow algorithms from NetworkX flow
153
+ package.
154
+
155
+ """
156
+ if s not in G:
157
+ raise nx.NetworkXError(f"node {s} not in graph")
158
+ if t not in G:
159
+ raise nx.NetworkXError(f"node {t} not in graph")
160
+
161
+ if flow_func is None:
162
+ flow_func = default_flow_func
163
+
164
+ if auxiliary is None:
165
+ H = build_auxiliary_edge_connectivity(G)
166
+ else:
167
+ H = auxiliary
168
+
169
+ # Maximum possible edge disjoint paths
170
+ possible = min(H.out_degree(s), H.in_degree(t))
171
+ if not possible:
172
+ raise NetworkXNoPath
173
+
174
+ if cutoff is None:
175
+ cutoff = possible
176
+ else:
177
+ cutoff = min(cutoff, possible)
178
+
179
+ # Compute maximum flow between source and target. Flow functions in
180
+ # NetworkX return a residual network.
181
+ kwargs = {
182
+ "capacity": "capacity",
183
+ "residual": residual,
184
+ "cutoff": cutoff,
185
+ "value_only": True,
186
+ }
187
+ if flow_func is preflow_push:
188
+ del kwargs["cutoff"]
189
+ if flow_func is shortest_augmenting_path:
190
+ kwargs["two_phase"] = True
191
+ R = flow_func(H, s, t, **kwargs)
192
+
193
+ if R.graph["flow_value"] == 0:
194
+ raise NetworkXNoPath
195
+
196
+ # Saturated edges in the residual network form the edge disjoint paths
197
+ # between source and target
198
+ cutset = [
199
+ (u, v)
200
+ for u, v, d in R.edges(data=True)
201
+ if d["capacity"] == d["flow"] and d["flow"] > 0
202
+ ]
203
+ # This is equivalent of what flow.utils.build_flow_dict returns, but
204
+ # only for the nodes with saturated edges and without reporting 0 flows.
205
+ flow_dict = {n: {} for edge in cutset for n in edge}
206
+ for u, v in cutset:
207
+ flow_dict[u][v] = 1
208
+
209
+ # Rebuild the edge disjoint paths from the flow dictionary.
210
+ paths_found = 0
211
+ for v in list(flow_dict[s]):
212
+ if paths_found >= cutoff:
213
+ # preflow_push does not support cutoff: we have to
214
+ # keep track of the paths founds and stop at cutoff.
215
+ break
216
+ path = [s]
217
+ if v == t:
218
+ path.append(v)
219
+ yield path
220
+ continue
221
+ u = v
222
+ while u != t:
223
+ path.append(u)
224
+ try:
225
+ u, _ = flow_dict[u].popitem()
226
+ except KeyError:
227
+ break
228
+ else:
229
+ path.append(t)
230
+ yield path
231
+ paths_found += 1
232
+
233
+
234
+ @nx._dispatchable(
235
+ graphs={"G": 0, "auxiliary?": 5},
236
+ preserve_node_attrs={"auxiliary": {"id": None}},
237
+ preserve_graph_attrs={"auxiliary"},
238
+ )
239
+ def node_disjoint_paths(
240
+ G, s, t, flow_func=None, cutoff=None, auxiliary=None, residual=None
241
+ ):
242
+ r"""Computes node disjoint paths between source and target.
243
+
244
+ Node disjoint paths are paths that only share their first and last
245
+ nodes. The number of node independent paths between two nodes is
246
+ equal to their local node connectivity.
247
+
248
+ Parameters
249
+ ----------
250
+ G : NetworkX graph
251
+
252
+ s : node
253
+ Source node.
254
+
255
+ t : node
256
+ Target node.
257
+
258
+ flow_func : function
259
+ A function for computing the maximum flow among a pair of nodes.
260
+ The function has to accept at least three parameters: a Digraph,
261
+ a source node, and a target node. And return a residual network
262
+ that follows NetworkX conventions (see :meth:`maximum_flow` for
263
+ details). If flow_func is None, the default maximum flow function
264
+ (:meth:`edmonds_karp`) is used. See below for details. The choice
265
+ of the default function may change from version to version and
266
+ should not be relied on. Default value: None.
267
+
268
+ cutoff : integer or None (default: None)
269
+ Maximum number of paths to yield. If specified, the maximum flow
270
+ algorithm will terminate when the flow value reaches or exceeds the
271
+ cutoff. This only works for flows that support the cutoff parameter
272
+ (most do) and is ignored otherwise.
273
+
274
+ auxiliary : NetworkX DiGraph
275
+ Auxiliary digraph to compute flow based node connectivity. It has
276
+ to have a graph attribute called mapping with a dictionary mapping
277
+ node names in G and in the auxiliary digraph. If provided
278
+ it will be reused instead of recreated. Default value: None.
279
+
280
+ residual : NetworkX DiGraph
281
+ Residual network to compute maximum flow. If provided it will be
282
+ reused instead of recreated. Default value: None.
283
+
284
+ Returns
285
+ -------
286
+ paths : generator
287
+ Generator of node disjoint paths.
288
+
289
+ Raises
290
+ ------
291
+ NetworkXNoPath
292
+ If there is no path between source and target.
293
+
294
+ NetworkXError
295
+ If source or target are not in the graph G.
296
+
297
+ Examples
298
+ --------
299
+ We use in this example the platonic icosahedral graph, which has node
300
+ connectivity 5, thus there are 5 node disjoint paths between any pair
301
+ of non neighbor nodes.
302
+
303
+ >>> G = nx.icosahedral_graph()
304
+ >>> len(list(nx.node_disjoint_paths(G, 0, 6)))
305
+ 5
306
+
307
+ If you need to compute node disjoint paths between several pairs of
308
+ nodes in the same graph, it is recommended that you reuse the
309
+ data structures that NetworkX uses in the computation: the
310
+ auxiliary digraph for node connectivity and node cuts, and the
311
+ residual network for the underlying maximum flow computation.
312
+
313
+ Example of how to compute node disjoint paths reusing the data
314
+ structures:
315
+
316
+ >>> # You also have to explicitly import the function for
317
+ >>> # building the auxiliary digraph from the connectivity package
318
+ >>> from networkx.algorithms.connectivity import build_auxiliary_node_connectivity
319
+ >>> H = build_auxiliary_node_connectivity(G)
320
+ >>> # And the function for building the residual network from the
321
+ >>> # flow package
322
+ >>> from networkx.algorithms.flow import build_residual_network
323
+ >>> # Note that the auxiliary digraph has an edge attribute named capacity
324
+ >>> R = build_residual_network(H, "capacity")
325
+ >>> # Reuse the auxiliary digraph and the residual network by passing them
326
+ >>> # as arguments
327
+ >>> len(list(nx.node_disjoint_paths(G, 0, 6, auxiliary=H, residual=R)))
328
+ 5
329
+
330
+ You can also use alternative flow algorithms for computing node disjoint
331
+ paths. For instance, in dense networks the algorithm
332
+ :meth:`shortest_augmenting_path` will usually perform better than
333
+ the default :meth:`edmonds_karp` which is faster for sparse
334
+ networks with highly skewed degree distributions. Alternative flow
335
+ functions have to be explicitly imported from the flow package.
336
+
337
+ >>> from networkx.algorithms.flow import shortest_augmenting_path
338
+ >>> len(list(nx.node_disjoint_paths(G, 0, 6, flow_func=shortest_augmenting_path)))
339
+ 5
340
+
341
+ Notes
342
+ -----
343
+ This is a flow based implementation of node disjoint paths. We compute
344
+ the maximum flow between source and target on an auxiliary directed
345
+ network. The saturated edges in the residual network after running the
346
+ maximum flow algorithm correspond to node disjoint paths between source
347
+ and target in the original network. This function handles both directed
348
+ and undirected graphs, and can use all flow algorithms from NetworkX flow
349
+ package.
350
+
351
+ See also
352
+ --------
353
+ :meth:`edge_disjoint_paths`
354
+ :meth:`node_connectivity`
355
+ :meth:`maximum_flow`
356
+ :meth:`edmonds_karp`
357
+ :meth:`preflow_push`
358
+ :meth:`shortest_augmenting_path`
359
+
360
+ """
361
+ if s not in G:
362
+ raise nx.NetworkXError(f"node {s} not in graph")
363
+ if t not in G:
364
+ raise nx.NetworkXError(f"node {t} not in graph")
365
+
366
+ if auxiliary is None:
367
+ H = build_auxiliary_node_connectivity(G)
368
+ else:
369
+ H = auxiliary
370
+
371
+ mapping = H.graph.get("mapping", None)
372
+ if mapping is None:
373
+ raise nx.NetworkXError("Invalid auxiliary digraph.")
374
+
375
+ # Maximum possible edge disjoint paths
376
+ possible = min(H.out_degree(f"{mapping[s]}B"), H.in_degree(f"{mapping[t]}A"))
377
+ if not possible:
378
+ raise NetworkXNoPath
379
+
380
+ if cutoff is None:
381
+ cutoff = possible
382
+ else:
383
+ cutoff = min(cutoff, possible)
384
+
385
+ kwargs = {
386
+ "flow_func": flow_func,
387
+ "residual": residual,
388
+ "auxiliary": H,
389
+ "cutoff": cutoff,
390
+ }
391
+
392
+ # The edge disjoint paths in the auxiliary digraph correspond to the node
393
+ # disjoint paths in the original graph.
394
+ paths_edges = edge_disjoint_paths(H, f"{mapping[s]}B", f"{mapping[t]}A", **kwargs)
395
+ for path in paths_edges:
396
+ # Each node in the original graph maps to two nodes in auxiliary graph
397
+ yield list(_unique_everseen(H.nodes[node]["id"] for node in path))
398
+
399
+
400
+ def _unique_everseen(iterable):
401
+ # Adapted from https://docs.python.org/3/library/itertools.html examples
402
+ "List unique elements, preserving order. Remember all elements ever seen."
403
+ # unique_everseen('AAAABBBCCDAABBB') --> A B C D
404
+ seen = set()
405
+ seen_add = seen.add
406
+ for element in _filterfalse(seen.__contains__, iterable):
407
+ seen_add(element)
408
+ yield element
.venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/edge_augmentation.py ADDED
@@ -0,0 +1,1270 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Algorithms for finding k-edge-augmentations
3
+
4
+ A k-edge-augmentation is a set of edges, that once added to a graph, ensures
5
+ that the graph is k-edge-connected; i.e. the graph cannot be disconnected
6
+ unless k or more edges are removed. Typically, the goal is to find the
7
+ augmentation with minimum weight. In general, it is not guaranteed that a
8
+ k-edge-augmentation exists.
9
+
10
+ See Also
11
+ --------
12
+ :mod:`edge_kcomponents` : algorithms for finding k-edge-connected components
13
+ :mod:`connectivity` : algorithms for determining edge connectivity.
14
+ """
15
+
16
+ import itertools as it
17
+ import math
18
+ from collections import defaultdict, namedtuple
19
+
20
+ import networkx as nx
21
+ from networkx.utils import not_implemented_for, py_random_state
22
+
23
+ __all__ = ["k_edge_augmentation", "is_k_edge_connected", "is_locally_k_edge_connected"]
24
+
25
+
26
+ @not_implemented_for("directed")
27
+ @not_implemented_for("multigraph")
28
+ @nx._dispatchable
29
+ def is_k_edge_connected(G, k):
30
+ """Tests to see if a graph is k-edge-connected.
31
+
32
+ Is it impossible to disconnect the graph by removing fewer than k edges?
33
+ If so, then G is k-edge-connected.
34
+
35
+ Parameters
36
+ ----------
37
+ G : NetworkX graph
38
+ An undirected graph.
39
+
40
+ k : integer
41
+ edge connectivity to test for
42
+
43
+ Returns
44
+ -------
45
+ boolean
46
+ True if G is k-edge-connected.
47
+
48
+ See Also
49
+ --------
50
+ :func:`is_locally_k_edge_connected`
51
+
52
+ Examples
53
+ --------
54
+ >>> G = nx.barbell_graph(10, 0)
55
+ >>> nx.is_k_edge_connected(G, k=1)
56
+ True
57
+ >>> nx.is_k_edge_connected(G, k=2)
58
+ False
59
+ """
60
+ if k < 1:
61
+ raise ValueError(f"k must be positive, not {k}")
62
+ # First try to quickly determine if G is not k-edge-connected
63
+ if G.number_of_nodes() < k + 1:
64
+ return False
65
+ elif any(d < k for n, d in G.degree()):
66
+ return False
67
+ else:
68
+ # Otherwise perform the full check
69
+ if k == 1:
70
+ return nx.is_connected(G)
71
+ elif k == 2:
72
+ return nx.is_connected(G) and not nx.has_bridges(G)
73
+ else:
74
+ return nx.edge_connectivity(G, cutoff=k) >= k
75
+
76
+
77
+ @not_implemented_for("directed")
78
+ @not_implemented_for("multigraph")
79
+ @nx._dispatchable
80
+ def is_locally_k_edge_connected(G, s, t, k):
81
+ """Tests to see if an edge in a graph is locally k-edge-connected.
82
+
83
+ Is it impossible to disconnect s and t by removing fewer than k edges?
84
+ If so, then s and t are locally k-edge-connected in G.
85
+
86
+ Parameters
87
+ ----------
88
+ G : NetworkX graph
89
+ An undirected graph.
90
+
91
+ s : node
92
+ Source node
93
+
94
+ t : node
95
+ Target node
96
+
97
+ k : integer
98
+ local edge connectivity for nodes s and t
99
+
100
+ Returns
101
+ -------
102
+ boolean
103
+ True if s and t are locally k-edge-connected in G.
104
+
105
+ See Also
106
+ --------
107
+ :func:`is_k_edge_connected`
108
+
109
+ Examples
110
+ --------
111
+ >>> from networkx.algorithms.connectivity import is_locally_k_edge_connected
112
+ >>> G = nx.barbell_graph(10, 0)
113
+ >>> is_locally_k_edge_connected(G, 5, 15, k=1)
114
+ True
115
+ >>> is_locally_k_edge_connected(G, 5, 15, k=2)
116
+ False
117
+ >>> is_locally_k_edge_connected(G, 1, 5, k=2)
118
+ True
119
+ """
120
+ if k < 1:
121
+ raise ValueError(f"k must be positive, not {k}")
122
+
123
+ # First try to quickly determine s, t is not k-locally-edge-connected in G
124
+ if G.degree(s) < k or G.degree(t) < k:
125
+ return False
126
+ else:
127
+ # Otherwise perform the full check
128
+ if k == 1:
129
+ return nx.has_path(G, s, t)
130
+ else:
131
+ localk = nx.connectivity.local_edge_connectivity(G, s, t, cutoff=k)
132
+ return localk >= k
133
+
134
+
135
+ @not_implemented_for("directed")
136
+ @not_implemented_for("multigraph")
137
+ @nx._dispatchable
138
+ def k_edge_augmentation(G, k, avail=None, weight=None, partial=False):
139
+ """Finds set of edges to k-edge-connect G.
140
+
141
+ Adding edges from the augmentation to G make it impossible to disconnect G
142
+ unless k or more edges are removed. This function uses the most efficient
143
+ function available (depending on the value of k and if the problem is
144
+ weighted or unweighted) to search for a minimum weight subset of available
145
+ edges that k-edge-connects G. In general, finding a k-edge-augmentation is
146
+ NP-hard, so solutions are not guaranteed to be minimal. Furthermore, a
147
+ k-edge-augmentation may not exist.
148
+
149
+ Parameters
150
+ ----------
151
+ G : NetworkX graph
152
+ An undirected graph.
153
+
154
+ k : integer
155
+ Desired edge connectivity
156
+
157
+ avail : dict or a set of 2 or 3 tuples
158
+ The available edges that can be used in the augmentation.
159
+
160
+ If unspecified, then all edges in the complement of G are available.
161
+ Otherwise, each item is an available edge (with an optional weight).
162
+
163
+ In the unweighted case, each item is an edge ``(u, v)``.
164
+
165
+ In the weighted case, each item is a 3-tuple ``(u, v, d)`` or a dict
166
+ with items ``(u, v): d``. The third item, ``d``, can be a dictionary
167
+ or a real number. If ``d`` is a dictionary ``d[weight]``
168
+ correspondings to the weight.
169
+
170
+ weight : string
171
+ key to use to find weights if ``avail`` is a set of 3-tuples where the
172
+ third item in each tuple is a dictionary.
173
+
174
+ partial : boolean
175
+ If partial is True and no feasible k-edge-augmentation exists, then all
176
+ a partial k-edge-augmentation is generated. Adding the edges in a
177
+ partial augmentation to G, minimizes the number of k-edge-connected
178
+ components and maximizes the edge connectivity between those
179
+ components. For details, see :func:`partial_k_edge_augmentation`.
180
+
181
+ Yields
182
+ ------
183
+ edge : tuple
184
+ Edges that, once added to G, would cause G to become k-edge-connected.
185
+ If partial is False, an error is raised if this is not possible.
186
+ Otherwise, generated edges form a partial augmentation, which
187
+ k-edge-connects any part of G where it is possible, and maximally
188
+ connects the remaining parts.
189
+
190
+ Raises
191
+ ------
192
+ NetworkXUnfeasible
193
+ If partial is False and no k-edge-augmentation exists.
194
+
195
+ NetworkXNotImplemented
196
+ If the input graph is directed or a multigraph.
197
+
198
+ ValueError:
199
+ If k is less than 1
200
+
201
+ Notes
202
+ -----
203
+ When k=1 this returns an optimal solution.
204
+
205
+ When k=2 and ``avail`` is None, this returns an optimal solution.
206
+ Otherwise when k=2, this returns a 2-approximation of the optimal solution.
207
+
208
+ For k>3, this problem is NP-hard and this uses a randomized algorithm that
209
+ produces a feasible solution, but provides no guarantees on the
210
+ solution weight.
211
+
212
+ Examples
213
+ --------
214
+ >>> # Unweighted cases
215
+ >>> G = nx.path_graph((1, 2, 3, 4))
216
+ >>> G.add_node(5)
217
+ >>> sorted(nx.k_edge_augmentation(G, k=1))
218
+ [(1, 5)]
219
+ >>> sorted(nx.k_edge_augmentation(G, k=2))
220
+ [(1, 5), (5, 4)]
221
+ >>> sorted(nx.k_edge_augmentation(G, k=3))
222
+ [(1, 4), (1, 5), (2, 5), (3, 5), (4, 5)]
223
+ >>> complement = list(nx.k_edge_augmentation(G, k=5, partial=True))
224
+ >>> G.add_edges_from(complement)
225
+ >>> nx.edge_connectivity(G)
226
+ 4
227
+
228
+ >>> # Weighted cases
229
+ >>> G = nx.path_graph((1, 2, 3, 4))
230
+ >>> G.add_node(5)
231
+ >>> # avail can be a tuple with a dict
232
+ >>> avail = [(1, 5, {"weight": 11}), (2, 5, {"weight": 10})]
233
+ >>> sorted(nx.k_edge_augmentation(G, k=1, avail=avail, weight="weight"))
234
+ [(2, 5)]
235
+ >>> # or avail can be a 3-tuple with a real number
236
+ >>> avail = [(1, 5, 11), (2, 5, 10), (4, 3, 1), (4, 5, 51)]
237
+ >>> sorted(nx.k_edge_augmentation(G, k=2, avail=avail))
238
+ [(1, 5), (2, 5), (4, 5)]
239
+ >>> # or avail can be a dict
240
+ >>> avail = {(1, 5): 11, (2, 5): 10, (4, 3): 1, (4, 5): 51}
241
+ >>> sorted(nx.k_edge_augmentation(G, k=2, avail=avail))
242
+ [(1, 5), (2, 5), (4, 5)]
243
+ >>> # If augmentation is infeasible, then a partial solution can be found
244
+ >>> avail = {(1, 5): 11}
245
+ >>> sorted(nx.k_edge_augmentation(G, k=2, avail=avail, partial=True))
246
+ [(1, 5)]
247
+ """
248
+ try:
249
+ if k <= 0:
250
+ raise ValueError(f"k must be a positive integer, not {k}")
251
+ elif G.number_of_nodes() < k + 1:
252
+ msg = f"impossible to {k} connect in graph with less than {k + 1} nodes"
253
+ raise nx.NetworkXUnfeasible(msg)
254
+ elif avail is not None and len(avail) == 0:
255
+ if not nx.is_k_edge_connected(G, k):
256
+ raise nx.NetworkXUnfeasible("no available edges")
257
+ aug_edges = []
258
+ elif k == 1:
259
+ aug_edges = one_edge_augmentation(
260
+ G, avail=avail, weight=weight, partial=partial
261
+ )
262
+ elif k == 2:
263
+ aug_edges = bridge_augmentation(G, avail=avail, weight=weight)
264
+ else:
265
+ # raise NotImplementedError(f'not implemented for k>2. k={k}')
266
+ aug_edges = greedy_k_edge_augmentation(
267
+ G, k=k, avail=avail, weight=weight, seed=0
268
+ )
269
+ # Do eager evaluation so we can catch any exceptions
270
+ # Before executing partial code.
271
+ yield from list(aug_edges)
272
+ except nx.NetworkXUnfeasible:
273
+ if partial:
274
+ # Return all available edges
275
+ if avail is None:
276
+ aug_edges = complement_edges(G)
277
+ else:
278
+ # If we can't k-edge-connect the entire graph, try to
279
+ # k-edge-connect as much as possible
280
+ aug_edges = partial_k_edge_augmentation(
281
+ G, k=k, avail=avail, weight=weight
282
+ )
283
+ yield from aug_edges
284
+ else:
285
+ raise
286
+
287
+
288
+ @nx._dispatchable
289
+ def partial_k_edge_augmentation(G, k, avail, weight=None):
290
+ """Finds augmentation that k-edge-connects as much of the graph as possible.
291
+
292
+ When a k-edge-augmentation is not possible, we can still try to find a
293
+ small set of edges that partially k-edge-connects as much of the graph as
294
+ possible. All possible edges are generated between remaining parts.
295
+ This minimizes the number of k-edge-connected subgraphs in the resulting
296
+ graph and maximizes the edge connectivity between those subgraphs.
297
+
298
+ Parameters
299
+ ----------
300
+ G : NetworkX graph
301
+ An undirected graph.
302
+
303
+ k : integer
304
+ Desired edge connectivity
305
+
306
+ avail : dict or a set of 2 or 3 tuples
307
+ For more details, see :func:`k_edge_augmentation`.
308
+
309
+ weight : string
310
+ key to use to find weights if ``avail`` is a set of 3-tuples.
311
+ For more details, see :func:`k_edge_augmentation`.
312
+
313
+ Yields
314
+ ------
315
+ edge : tuple
316
+ Edges in the partial augmentation of G. These edges k-edge-connect any
317
+ part of G where it is possible, and maximally connects the remaining
318
+ parts. In other words, all edges from avail are generated except for
319
+ those within subgraphs that have already become k-edge-connected.
320
+
321
+ Notes
322
+ -----
323
+ Construct H that augments G with all edges in avail.
324
+ Find the k-edge-subgraphs of H.
325
+ For each k-edge-subgraph, if the number of nodes is more than k, then find
326
+ the k-edge-augmentation of that graph and add it to the solution. Then add
327
+ all edges in avail between k-edge subgraphs to the solution.
328
+
329
+ See Also
330
+ --------
331
+ :func:`k_edge_augmentation`
332
+
333
+ Examples
334
+ --------
335
+ >>> G = nx.path_graph((1, 2, 3, 4, 5, 6, 7))
336
+ >>> G.add_node(8)
337
+ >>> avail = [(1, 3), (1, 4), (1, 5), (2, 4), (2, 5), (3, 5), (1, 8)]
338
+ >>> sorted(partial_k_edge_augmentation(G, k=2, avail=avail))
339
+ [(1, 5), (1, 8)]
340
+ """
341
+
342
+ def _edges_between_disjoint(H, only1, only2):
343
+ """finds edges between disjoint nodes"""
344
+ only1_adj = {u: set(H.adj[u]) for u in only1}
345
+ for u, neighbs in only1_adj.items():
346
+ # Find the neighbors of u in only1 that are also in only2
347
+ neighbs12 = neighbs.intersection(only2)
348
+ for v in neighbs12:
349
+ yield (u, v)
350
+
351
+ avail_uv, avail_w = _unpack_available_edges(avail, weight=weight, G=G)
352
+
353
+ # Find which parts of the graph can be k-edge-connected
354
+ H = G.copy()
355
+ H.add_edges_from(
356
+ (
357
+ (u, v, {"weight": w, "generator": (u, v)})
358
+ for (u, v), w in zip(avail, avail_w)
359
+ )
360
+ )
361
+ k_edge_subgraphs = list(nx.k_edge_subgraphs(H, k=k))
362
+
363
+ # Generate edges to k-edge-connect internal subgraphs
364
+ for nodes in k_edge_subgraphs:
365
+ if len(nodes) > 1:
366
+ # Get the k-edge-connected subgraph
367
+ C = H.subgraph(nodes).copy()
368
+ # Find the internal edges that were available
369
+ sub_avail = {
370
+ d["generator"]: d["weight"]
371
+ for (u, v, d) in C.edges(data=True)
372
+ if "generator" in d
373
+ }
374
+ # Remove potential augmenting edges
375
+ C.remove_edges_from(sub_avail.keys())
376
+ # Find a subset of these edges that makes the component
377
+ # k-edge-connected and ignore the rest
378
+ yield from nx.k_edge_augmentation(C, k=k, avail=sub_avail)
379
+
380
+ # Generate all edges between CCs that could not be k-edge-connected
381
+ for cc1, cc2 in it.combinations(k_edge_subgraphs, 2):
382
+ for u, v in _edges_between_disjoint(H, cc1, cc2):
383
+ d = H.get_edge_data(u, v)
384
+ edge = d.get("generator", None)
385
+ if edge is not None:
386
+ yield edge
387
+
388
+
389
+ @not_implemented_for("multigraph")
390
+ @not_implemented_for("directed")
391
+ @nx._dispatchable
392
+ def one_edge_augmentation(G, avail=None, weight=None, partial=False):
393
+ """Finds minimum weight set of edges to connect G.
394
+
395
+ Equivalent to :func:`k_edge_augmentation` when k=1. Adding the resulting
396
+ edges to G will make it 1-edge-connected. The solution is optimal for both
397
+ weighted and non-weighted variants.
398
+
399
+ Parameters
400
+ ----------
401
+ G : NetworkX graph
402
+ An undirected graph.
403
+
404
+ avail : dict or a set of 2 or 3 tuples
405
+ For more details, see :func:`k_edge_augmentation`.
406
+
407
+ weight : string
408
+ key to use to find weights if ``avail`` is a set of 3-tuples.
409
+ For more details, see :func:`k_edge_augmentation`.
410
+
411
+ partial : boolean
412
+ If partial is True and no feasible k-edge-augmentation exists, then the
413
+ augmenting edges minimize the number of connected components.
414
+
415
+ Yields
416
+ ------
417
+ edge : tuple
418
+ Edges in the one-augmentation of G
419
+
420
+ Raises
421
+ ------
422
+ NetworkXUnfeasible
423
+ If partial is False and no one-edge-augmentation exists.
424
+
425
+ Notes
426
+ -----
427
+ Uses either :func:`unconstrained_one_edge_augmentation` or
428
+ :func:`weighted_one_edge_augmentation` depending on whether ``avail`` is
429
+ specified. Both algorithms are based on finding a minimum spanning tree.
430
+ As such both algorithms find optimal solutions and run in linear time.
431
+
432
+ See Also
433
+ --------
434
+ :func:`k_edge_augmentation`
435
+ """
436
+ if avail is None:
437
+ return unconstrained_one_edge_augmentation(G)
438
+ else:
439
+ return weighted_one_edge_augmentation(
440
+ G, avail=avail, weight=weight, partial=partial
441
+ )
442
+
443
+
444
+ @not_implemented_for("multigraph")
445
+ @not_implemented_for("directed")
446
+ @nx._dispatchable
447
+ def bridge_augmentation(G, avail=None, weight=None):
448
+ """Finds the a set of edges that bridge connects G.
449
+
450
+ Equivalent to :func:`k_edge_augmentation` when k=2, and partial=False.
451
+ Adding the resulting edges to G will make it 2-edge-connected. If no
452
+ constraints are specified the returned set of edges is minimum an optimal,
453
+ otherwise the solution is approximated.
454
+
455
+ Parameters
456
+ ----------
457
+ G : NetworkX graph
458
+ An undirected graph.
459
+
460
+ avail : dict or a set of 2 or 3 tuples
461
+ For more details, see :func:`k_edge_augmentation`.
462
+
463
+ weight : string
464
+ key to use to find weights if ``avail`` is a set of 3-tuples.
465
+ For more details, see :func:`k_edge_augmentation`.
466
+
467
+ Yields
468
+ ------
469
+ edge : tuple
470
+ Edges in the bridge-augmentation of G
471
+
472
+ Raises
473
+ ------
474
+ NetworkXUnfeasible
475
+ If no bridge-augmentation exists.
476
+
477
+ Notes
478
+ -----
479
+ If there are no constraints the solution can be computed in linear time
480
+ using :func:`unconstrained_bridge_augmentation`. Otherwise, the problem
481
+ becomes NP-hard and is the solution is approximated by
482
+ :func:`weighted_bridge_augmentation`.
483
+
484
+ See Also
485
+ --------
486
+ :func:`k_edge_augmentation`
487
+ """
488
+ if G.number_of_nodes() < 3:
489
+ raise nx.NetworkXUnfeasible("impossible to bridge connect less than 3 nodes")
490
+ if avail is None:
491
+ return unconstrained_bridge_augmentation(G)
492
+ else:
493
+ return weighted_bridge_augmentation(G, avail, weight=weight)
494
+
495
+
496
+ # --- Algorithms and Helpers ---
497
+
498
+
499
+ def _ordered(u, v):
500
+ """Returns the nodes in an undirected edge in lower-triangular order"""
501
+ return (u, v) if u < v else (v, u)
502
+
503
+
504
+ def _unpack_available_edges(avail, weight=None, G=None):
505
+ """Helper to separate avail into edges and corresponding weights"""
506
+ if weight is None:
507
+ weight = "weight"
508
+ if isinstance(avail, dict):
509
+ avail_uv = list(avail.keys())
510
+ avail_w = list(avail.values())
511
+ else:
512
+
513
+ def _try_getitem(d):
514
+ try:
515
+ return d[weight]
516
+ except TypeError:
517
+ return d
518
+
519
+ avail_uv = [tup[0:2] for tup in avail]
520
+ avail_w = [1 if len(tup) == 2 else _try_getitem(tup[-1]) for tup in avail]
521
+
522
+ if G is not None:
523
+ # Edges already in the graph are filtered
524
+ flags = [not G.has_edge(u, v) for u, v in avail_uv]
525
+ avail_uv = list(it.compress(avail_uv, flags))
526
+ avail_w = list(it.compress(avail_w, flags))
527
+ return avail_uv, avail_w
528
+
529
+
530
+ MetaEdge = namedtuple("MetaEdge", ("meta_uv", "uv", "w"))
531
+
532
+
533
+ def _lightest_meta_edges(mapping, avail_uv, avail_w):
534
+ """Maps available edges in the original graph to edges in the metagraph.
535
+
536
+ Parameters
537
+ ----------
538
+ mapping : dict
539
+ mapping produced by :func:`collapse`, that maps each node in the
540
+ original graph to a node in the meta graph
541
+
542
+ avail_uv : list
543
+ list of edges
544
+
545
+ avail_w : list
546
+ list of edge weights
547
+
548
+ Notes
549
+ -----
550
+ Each node in the metagraph is a k-edge-connected component in the original
551
+ graph. We don't care about any edge within the same k-edge-connected
552
+ component, so we ignore self edges. We also are only interested in the
553
+ minimum weight edge bridging each k-edge-connected component so, we group
554
+ the edges by meta-edge and take the lightest in each group.
555
+
556
+ Examples
557
+ --------
558
+ >>> # Each group represents a meta-node
559
+ >>> groups = ([1, 2, 3], [4, 5], [6])
560
+ >>> mapping = {n: meta_n for meta_n, ns in enumerate(groups) for n in ns}
561
+ >>> avail_uv = [(1, 2), (3, 6), (1, 4), (5, 2), (6, 1), (2, 6), (3, 1)]
562
+ >>> avail_w = [20, 99, 20, 15, 50, 99, 20]
563
+ >>> sorted(_lightest_meta_edges(mapping, avail_uv, avail_w))
564
+ [MetaEdge(meta_uv=(0, 1), uv=(5, 2), w=15), MetaEdge(meta_uv=(0, 2), uv=(6, 1), w=50)]
565
+ """
566
+ grouped_wuv = defaultdict(list)
567
+ for w, (u, v) in zip(avail_w, avail_uv):
568
+ # Order the meta-edge so it can be used as a dict key
569
+ meta_uv = _ordered(mapping[u], mapping[v])
570
+ # Group each available edge using the meta-edge as a key
571
+ grouped_wuv[meta_uv].append((w, u, v))
572
+
573
+ # Now that all available edges are grouped, choose one per group
574
+ for (mu, mv), choices_wuv in grouped_wuv.items():
575
+ # Ignore available edges within the same meta-node
576
+ if mu != mv:
577
+ # Choose the lightest available edge belonging to each meta-edge
578
+ w, u, v = min(choices_wuv)
579
+ yield MetaEdge((mu, mv), (u, v), w)
580
+
581
+
582
+ @nx._dispatchable
583
+ def unconstrained_one_edge_augmentation(G):
584
+ """Finds the smallest set of edges to connect G.
585
+
586
+ This is a variant of the unweighted MST problem.
587
+ If G is not empty, a feasible solution always exists.
588
+
589
+ Parameters
590
+ ----------
591
+ G : NetworkX graph
592
+ An undirected graph.
593
+
594
+ Yields
595
+ ------
596
+ edge : tuple
597
+ Edges in the one-edge-augmentation of G
598
+
599
+ See Also
600
+ --------
601
+ :func:`one_edge_augmentation`
602
+ :func:`k_edge_augmentation`
603
+
604
+ Examples
605
+ --------
606
+ >>> G = nx.Graph([(1, 2), (2, 3), (4, 5)])
607
+ >>> G.add_nodes_from([6, 7, 8])
608
+ >>> sorted(unconstrained_one_edge_augmentation(G))
609
+ [(1, 4), (4, 6), (6, 7), (7, 8)]
610
+ """
611
+ ccs1 = list(nx.connected_components(G))
612
+ C = collapse(G, ccs1)
613
+ # When we are not constrained, we can just make a meta graph tree.
614
+ meta_nodes = list(C.nodes())
615
+ # build a path in the metagraph
616
+ meta_aug = list(zip(meta_nodes, meta_nodes[1:]))
617
+ # map that path to the original graph
618
+ inverse = defaultdict(list)
619
+ for k, v in C.graph["mapping"].items():
620
+ inverse[v].append(k)
621
+ for mu, mv in meta_aug:
622
+ yield (inverse[mu][0], inverse[mv][0])
623
+
624
+
625
+ @nx._dispatchable
626
+ def weighted_one_edge_augmentation(G, avail, weight=None, partial=False):
627
+ """Finds the minimum weight set of edges to connect G if one exists.
628
+
629
+ This is a variant of the weighted MST problem.
630
+
631
+ Parameters
632
+ ----------
633
+ G : NetworkX graph
634
+ An undirected graph.
635
+
636
+ avail : dict or a set of 2 or 3 tuples
637
+ For more details, see :func:`k_edge_augmentation`.
638
+
639
+ weight : string
640
+ key to use to find weights if ``avail`` is a set of 3-tuples.
641
+ For more details, see :func:`k_edge_augmentation`.
642
+
643
+ partial : boolean
644
+ If partial is True and no feasible k-edge-augmentation exists, then the
645
+ augmenting edges minimize the number of connected components.
646
+
647
+ Yields
648
+ ------
649
+ edge : tuple
650
+ Edges in the subset of avail chosen to connect G.
651
+
652
+ See Also
653
+ --------
654
+ :func:`one_edge_augmentation`
655
+ :func:`k_edge_augmentation`
656
+
657
+ Examples
658
+ --------
659
+ >>> G = nx.Graph([(1, 2), (2, 3), (4, 5)])
660
+ >>> G.add_nodes_from([6, 7, 8])
661
+ >>> # any edge not in avail has an implicit weight of infinity
662
+ >>> avail = [(1, 3), (1, 5), (4, 7), (4, 8), (6, 1), (8, 1), (8, 2)]
663
+ >>> sorted(weighted_one_edge_augmentation(G, avail))
664
+ [(1, 5), (4, 7), (6, 1), (8, 1)]
665
+ >>> # find another solution by giving large weights to edges in the
666
+ >>> # previous solution (note some of the old edges must be used)
667
+ >>> avail = [(1, 3), (1, 5, 99), (4, 7, 9), (6, 1, 99), (8, 1, 99), (8, 2)]
668
+ >>> sorted(weighted_one_edge_augmentation(G, avail))
669
+ [(1, 5), (4, 7), (6, 1), (8, 2)]
670
+ """
671
+ avail_uv, avail_w = _unpack_available_edges(avail, weight=weight, G=G)
672
+ # Collapse CCs in the original graph into nodes in a metagraph
673
+ # Then find an MST of the metagraph instead of the original graph
674
+ C = collapse(G, nx.connected_components(G))
675
+ mapping = C.graph["mapping"]
676
+ # Assign each available edge to an edge in the metagraph
677
+ candidate_mapping = _lightest_meta_edges(mapping, avail_uv, avail_w)
678
+ # nx.set_edge_attributes(C, name='weight', values=0)
679
+ C.add_edges_from(
680
+ (mu, mv, {"weight": w, "generator": uv})
681
+ for (mu, mv), uv, w in candidate_mapping
682
+ )
683
+ # Find MST of the meta graph
684
+ meta_mst = nx.minimum_spanning_tree(C)
685
+ if not partial and not nx.is_connected(meta_mst):
686
+ raise nx.NetworkXUnfeasible("Not possible to connect G with available edges")
687
+ # Yield the edge that generated the meta-edge
688
+ for mu, mv, d in meta_mst.edges(data=True):
689
+ if "generator" in d:
690
+ edge = d["generator"]
691
+ yield edge
692
+
693
+
694
+ @nx._dispatchable
695
+ def unconstrained_bridge_augmentation(G):
696
+ """Finds an optimal 2-edge-augmentation of G using the fewest edges.
697
+
698
+ This is an implementation of the algorithm detailed in [1]_.
699
+ The basic idea is to construct a meta-graph of bridge-ccs, connect leaf
700
+ nodes of the trees to connect the entire graph, and finally connect the
701
+ leafs of the tree in dfs-preorder to bridge connect the entire graph.
702
+
703
+ Parameters
704
+ ----------
705
+ G : NetworkX graph
706
+ An undirected graph.
707
+
708
+ Yields
709
+ ------
710
+ edge : tuple
711
+ Edges in the bridge augmentation of G
712
+
713
+ Notes
714
+ -----
715
+ Input: a graph G.
716
+ First find the bridge components of G and collapse each bridge-cc into a
717
+ node of a metagraph graph C, which is guaranteed to be a forest of trees.
718
+
719
+ C contains p "leafs" --- nodes with exactly one incident edge.
720
+ C contains q "isolated nodes" --- nodes with no incident edges.
721
+
722
+ Theorem: If p + q > 1, then at least :math:`ceil(p / 2) + q` edges are
723
+ needed to bridge connect C. This algorithm achieves this min number.
724
+
725
+ The method first adds enough edges to make G into a tree and then pairs
726
+ leafs in a simple fashion.
727
+
728
+ Let n be the number of trees in C. Let v(i) be an isolated vertex in the
729
+ i-th tree if one exists, otherwise it is a pair of distinct leafs nodes
730
+ in the i-th tree. Alternating edges from these sets (i.e. adding edges
731
+ A1 = [(v(i)[0], v(i + 1)[1]), v(i + 1)[0], v(i + 2)[1])...]) connects C
732
+ into a tree T. This tree has p' = p + 2q - 2(n -1) leafs and no isolated
733
+ vertices. A1 has n - 1 edges. The next step finds ceil(p' / 2) edges to
734
+ biconnect any tree with p' leafs.
735
+
736
+ Convert T into an arborescence T' by picking an arbitrary root node with
737
+ degree >= 2 and directing all edges away from the root. Note the
738
+ implementation implicitly constructs T'.
739
+
740
+ The leafs of T are the nodes with no existing edges in T'.
741
+ Order the leafs of T' by DFS preorder. Then break this list in half
742
+ and add the zipped pairs to A2.
743
+
744
+ The set A = A1 + A2 is the minimum augmentation in the metagraph.
745
+
746
+ To convert this to edges in the original graph
747
+
748
+ References
749
+ ----------
750
+ .. [1] Eswaran, Kapali P., and R. Endre Tarjan. (1975) Augmentation problems.
751
+ http://epubs.siam.org/doi/abs/10.1137/0205044
752
+
753
+ See Also
754
+ --------
755
+ :func:`bridge_augmentation`
756
+ :func:`k_edge_augmentation`
757
+
758
+ Examples
759
+ --------
760
+ >>> G = nx.path_graph((1, 2, 3, 4, 5, 6, 7))
761
+ >>> sorted(unconstrained_bridge_augmentation(G))
762
+ [(1, 7)]
763
+ >>> G = nx.path_graph((1, 2, 3, 2, 4, 5, 6, 7))
764
+ >>> sorted(unconstrained_bridge_augmentation(G))
765
+ [(1, 3), (3, 7)]
766
+ >>> G = nx.Graph([(0, 1), (0, 2), (1, 2)])
767
+ >>> G.add_node(4)
768
+ >>> sorted(unconstrained_bridge_augmentation(G))
769
+ [(1, 4), (4, 0)]
770
+ """
771
+ # -----
772
+ # Mapping of terms from (Eswaran and Tarjan):
773
+ # G = G_0 - the input graph
774
+ # C = G_0' - the bridge condensation of G. (This is a forest of trees)
775
+ # A1 = A_1 - the edges to connect the forest into a tree
776
+ # leaf = pendant - a node with degree of 1
777
+
778
+ # alpha(v) = maps the node v in G to its meta-node in C
779
+ # beta(x) = maps the meta-node x in C to any node in the bridge
780
+ # component of G corresponding to x.
781
+
782
+ # find the 2-edge-connected components of G
783
+ bridge_ccs = list(nx.connectivity.bridge_components(G))
784
+ # condense G into an forest C
785
+ C = collapse(G, bridge_ccs)
786
+
787
+ # Choose pairs of distinct leaf nodes in each tree. If this is not
788
+ # possible then make a pair using the single isolated node in the tree.
789
+ vset1 = [
790
+ tuple(cc) * 2 # case1: an isolated node
791
+ if len(cc) == 1
792
+ else sorted(cc, key=C.degree)[0:2] # case2: pair of leaf nodes
793
+ for cc in nx.connected_components(C)
794
+ ]
795
+ if len(vset1) > 1:
796
+ # Use this set to construct edges that connect C into a tree.
797
+ nodes1 = [vs[0] for vs in vset1]
798
+ nodes2 = [vs[1] for vs in vset1]
799
+ A1 = list(zip(nodes1[1:], nodes2))
800
+ else:
801
+ A1 = []
802
+ # Connect each tree in the forest to construct an arborescence
803
+ T = C.copy()
804
+ T.add_edges_from(A1)
805
+
806
+ # If there are only two leaf nodes, we simply connect them.
807
+ leafs = [n for n, d in T.degree() if d == 1]
808
+ if len(leafs) == 1:
809
+ A2 = []
810
+ if len(leafs) == 2:
811
+ A2 = [tuple(leafs)]
812
+ else:
813
+ # Choose an arbitrary non-leaf root
814
+ try:
815
+ root = next(n for n, d in T.degree() if d > 1)
816
+ except StopIteration: # no nodes found with degree > 1
817
+ return
818
+ # order the leaves of C by (induced directed) preorder
819
+ v2 = [n for n in nx.dfs_preorder_nodes(T, root) if T.degree(n) == 1]
820
+ # connecting first half of the leafs in pre-order to the second
821
+ # half will bridge connect the tree with the fewest edges.
822
+ half = math.ceil(len(v2) / 2)
823
+ A2 = list(zip(v2[:half], v2[-half:]))
824
+
825
+ # collect the edges used to augment the original forest
826
+ aug_tree_edges = A1 + A2
827
+
828
+ # Construct the mapping (beta) from meta-nodes to regular nodes
829
+ inverse = defaultdict(list)
830
+ for k, v in C.graph["mapping"].items():
831
+ inverse[v].append(k)
832
+ # sort so we choose minimum degree nodes first
833
+ inverse = {
834
+ mu: sorted(mapped, key=lambda u: (G.degree(u), u))
835
+ for mu, mapped in inverse.items()
836
+ }
837
+
838
+ # For each meta-edge, map back to an arbitrary pair in the original graph
839
+ G2 = G.copy()
840
+ for mu, mv in aug_tree_edges:
841
+ # Find the first available edge that doesn't exist and return it
842
+ for u, v in it.product(inverse[mu], inverse[mv]):
843
+ if not G2.has_edge(u, v):
844
+ G2.add_edge(u, v)
845
+ yield u, v
846
+ break
847
+
848
+
849
+ @nx._dispatchable
850
+ def weighted_bridge_augmentation(G, avail, weight=None):
851
+ """Finds an approximate min-weight 2-edge-augmentation of G.
852
+
853
+ This is an implementation of the approximation algorithm detailed in [1]_.
854
+ It chooses a set of edges from avail to add to G that renders it
855
+ 2-edge-connected if such a subset exists. This is done by finding a
856
+ minimum spanning arborescence of a specially constructed metagraph.
857
+
858
+ Parameters
859
+ ----------
860
+ G : NetworkX graph
861
+ An undirected graph.
862
+
863
+ avail : set of 2 or 3 tuples.
864
+ candidate edges (with optional weights) to choose from
865
+
866
+ weight : string
867
+ key to use to find weights if avail is a set of 3-tuples where the
868
+ third item in each tuple is a dictionary.
869
+
870
+ Yields
871
+ ------
872
+ edge : tuple
873
+ Edges in the subset of avail chosen to bridge augment G.
874
+
875
+ Notes
876
+ -----
877
+ Finding a weighted 2-edge-augmentation is NP-hard.
878
+ Any edge not in ``avail`` is considered to have a weight of infinity.
879
+ The approximation factor is 2 if ``G`` is connected and 3 if it is not.
880
+ Runs in :math:`O(m + n log(n))` time
881
+
882
+ References
883
+ ----------
884
+ .. [1] Khuller, Samir, and Ramakrishna Thurimella. (1993) Approximation
885
+ algorithms for graph augmentation.
886
+ http://www.sciencedirect.com/science/article/pii/S0196677483710102
887
+
888
+ See Also
889
+ --------
890
+ :func:`bridge_augmentation`
891
+ :func:`k_edge_augmentation`
892
+
893
+ Examples
894
+ --------
895
+ >>> G = nx.path_graph((1, 2, 3, 4))
896
+ >>> # When the weights are equal, (1, 4) is the best
897
+ >>> avail = [(1, 4, 1), (1, 3, 1), (2, 4, 1)]
898
+ >>> sorted(weighted_bridge_augmentation(G, avail))
899
+ [(1, 4)]
900
+ >>> # Giving (1, 4) a high weight makes the two edge solution the best.
901
+ >>> avail = [(1, 4, 1000), (1, 3, 1), (2, 4, 1)]
902
+ >>> sorted(weighted_bridge_augmentation(G, avail))
903
+ [(1, 3), (2, 4)]
904
+ >>> # ------
905
+ >>> G = nx.path_graph((1, 2, 3, 4))
906
+ >>> G.add_node(5)
907
+ >>> avail = [(1, 5, 11), (2, 5, 10), (4, 3, 1), (4, 5, 1)]
908
+ >>> sorted(weighted_bridge_augmentation(G, avail=avail))
909
+ [(1, 5), (4, 5)]
910
+ >>> avail = [(1, 5, 11), (2, 5, 10), (4, 3, 1), (4, 5, 51)]
911
+ >>> sorted(weighted_bridge_augmentation(G, avail=avail))
912
+ [(1, 5), (2, 5), (4, 5)]
913
+ """
914
+
915
+ if weight is None:
916
+ weight = "weight"
917
+
918
+ # If input G is not connected the approximation factor increases to 3
919
+ if not nx.is_connected(G):
920
+ H = G.copy()
921
+ connectors = list(one_edge_augmentation(H, avail=avail, weight=weight))
922
+ H.add_edges_from(connectors)
923
+
924
+ yield from connectors
925
+ else:
926
+ connectors = []
927
+ H = G
928
+
929
+ if len(avail) == 0:
930
+ if nx.has_bridges(H):
931
+ raise nx.NetworkXUnfeasible("no augmentation possible")
932
+
933
+ avail_uv, avail_w = _unpack_available_edges(avail, weight=weight, G=H)
934
+
935
+ # Collapse input into a metagraph. Meta nodes are bridge-ccs
936
+ bridge_ccs = nx.connectivity.bridge_components(H)
937
+ C = collapse(H, bridge_ccs)
938
+
939
+ # Use the meta graph to shrink avail to a small feasible subset
940
+ mapping = C.graph["mapping"]
941
+ # Choose the minimum weight feasible edge in each group
942
+ meta_to_wuv = {
943
+ (mu, mv): (w, uv)
944
+ for (mu, mv), uv, w in _lightest_meta_edges(mapping, avail_uv, avail_w)
945
+ }
946
+
947
+ # Mapping of terms from (Khuller and Thurimella):
948
+ # C : G_0 = (V, E^0)
949
+ # This is the metagraph where each node is a 2-edge-cc in G.
950
+ # The edges in C represent bridges in the original graph.
951
+ # (mu, mv) : E - E^0 # they group both avail and given edges in E
952
+ # T : \Gamma
953
+ # D : G^D = (V, E_D)
954
+
955
+ # The paper uses ancestor because children point to parents, which is
956
+ # contrary to networkx standards. So, we actually need to run
957
+ # nx.least_common_ancestor on the reversed Tree.
958
+
959
+ # Pick an arbitrary leaf from C as the root
960
+ try:
961
+ root = next(n for n, d in C.degree() if d == 1)
962
+ except StopIteration: # no nodes found with degree == 1
963
+ return
964
+ # Root C into a tree TR by directing all edges away from the root
965
+ # Note in their paper T directs edges towards the root
966
+ TR = nx.dfs_tree(C, root)
967
+
968
+ # Add to D the directed edges of T and set their weight to zero
969
+ # This indicates that it costs nothing to use edges that were given.
970
+ D = nx.reverse(TR).copy()
971
+
972
+ nx.set_edge_attributes(D, name="weight", values=0)
973
+
974
+ # The LCA of mu and mv in T is the shared ancestor of mu and mv that is
975
+ # located farthest from the root.
976
+ lca_gen = nx.tree_all_pairs_lowest_common_ancestor(
977
+ TR, root=root, pairs=meta_to_wuv.keys()
978
+ )
979
+
980
+ for (mu, mv), lca in lca_gen:
981
+ w, uv = meta_to_wuv[(mu, mv)]
982
+ if lca == mu:
983
+ # If u is an ancestor of v in TR, then add edge u->v to D
984
+ D.add_edge(lca, mv, weight=w, generator=uv)
985
+ elif lca == mv:
986
+ # If v is an ancestor of u in TR, then add edge v->u to D
987
+ D.add_edge(lca, mu, weight=w, generator=uv)
988
+ else:
989
+ # If neither u nor v is a ancestor of the other in TR
990
+ # let t = lca(TR, u, v) and add edges t->u and t->v
991
+ # Track the original edge that GENERATED these edges.
992
+ D.add_edge(lca, mu, weight=w, generator=uv)
993
+ D.add_edge(lca, mv, weight=w, generator=uv)
994
+
995
+ # Then compute a minimum rooted branching
996
+ try:
997
+ # Note the original edges must be directed towards to root for the
998
+ # branching to give us a bridge-augmentation.
999
+ A = _minimum_rooted_branching(D, root)
1000
+ except nx.NetworkXException as err:
1001
+ # If there is no branching then augmentation is not possible
1002
+ raise nx.NetworkXUnfeasible("no 2-edge-augmentation possible") from err
1003
+
1004
+ # For each edge e, in the branching that did not belong to the directed
1005
+ # tree T, add the corresponding edge that **GENERATED** it (this is not
1006
+ # necessarily e itself!)
1007
+
1008
+ # ensure the third case does not generate edges twice
1009
+ bridge_connectors = set()
1010
+ for mu, mv in A.edges():
1011
+ data = D.get_edge_data(mu, mv)
1012
+ if "generator" in data:
1013
+ # Add the avail edge that generated the branching edge.
1014
+ edge = data["generator"]
1015
+ bridge_connectors.add(edge)
1016
+
1017
+ yield from bridge_connectors
1018
+
1019
+
1020
+ def _minimum_rooted_branching(D, root):
1021
+ """Helper function to compute a minimum rooted branching (aka rooted
1022
+ arborescence)
1023
+
1024
+ Before the branching can be computed, the directed graph must be rooted by
1025
+ removing the predecessors of root.
1026
+
1027
+ A branching / arborescence of rooted graph G is a subgraph that contains a
1028
+ directed path from the root to every other vertex. It is the directed
1029
+ analog of the minimum spanning tree problem.
1030
+
1031
+ References
1032
+ ----------
1033
+ [1] Khuller, Samir (2002) Advanced Algorithms Lecture 24 Notes.
1034
+ https://web.archive.org/web/20121030033722/https://www.cs.umd.edu/class/spring2011/cmsc651/lec07.pdf
1035
+ """
1036
+ rooted = D.copy()
1037
+ # root the graph by removing all predecessors to `root`.
1038
+ rooted.remove_edges_from([(u, root) for u in D.predecessors(root)])
1039
+ # Then compute the branching / arborescence.
1040
+ A = nx.minimum_spanning_arborescence(rooted)
1041
+ return A
1042
+
1043
+
1044
+ @nx._dispatchable(returns_graph=True)
1045
+ def collapse(G, grouped_nodes):
1046
+ """Collapses each group of nodes into a single node.
1047
+
1048
+ This is similar to condensation, but works on undirected graphs.
1049
+
1050
+ Parameters
1051
+ ----------
1052
+ G : NetworkX Graph
1053
+
1054
+ grouped_nodes: list or generator
1055
+ Grouping of nodes to collapse. The grouping must be disjoint.
1056
+ If grouped_nodes are strongly_connected_components then this is
1057
+ equivalent to :func:`condensation`.
1058
+
1059
+ Returns
1060
+ -------
1061
+ C : NetworkX Graph
1062
+ The collapsed graph C of G with respect to the node grouping. The node
1063
+ labels are integers corresponding to the index of the component in the
1064
+ list of grouped_nodes. C has a graph attribute named 'mapping' with a
1065
+ dictionary mapping the original nodes to the nodes in C to which they
1066
+ belong. Each node in C also has a node attribute 'members' with the set
1067
+ of original nodes in G that form the group that the node in C
1068
+ represents.
1069
+
1070
+ Examples
1071
+ --------
1072
+ >>> # Collapses a graph using disjoint groups, but not necessarily connected
1073
+ >>> G = nx.Graph([(1, 0), (2, 3), (3, 1), (3, 4), (4, 5), (5, 6), (5, 7)])
1074
+ >>> G.add_node("A")
1075
+ >>> grouped_nodes = [{0, 1, 2, 3}, {5, 6, 7}]
1076
+ >>> C = collapse(G, grouped_nodes)
1077
+ >>> members = nx.get_node_attributes(C, "members")
1078
+ >>> sorted(members.keys())
1079
+ [0, 1, 2, 3]
1080
+ >>> member_values = set(map(frozenset, members.values()))
1081
+ >>> assert {0, 1, 2, 3} in member_values
1082
+ >>> assert {4} in member_values
1083
+ >>> assert {5, 6, 7} in member_values
1084
+ >>> assert {"A"} in member_values
1085
+ """
1086
+ mapping = {}
1087
+ members = {}
1088
+ C = G.__class__()
1089
+ i = 0 # required if G is empty
1090
+ remaining = set(G.nodes())
1091
+ for i, group in enumerate(grouped_nodes):
1092
+ group = set(group)
1093
+ assert remaining.issuperset(
1094
+ group
1095
+ ), "grouped nodes must exist in G and be disjoint"
1096
+ remaining.difference_update(group)
1097
+ members[i] = group
1098
+ mapping.update((n, i) for n in group)
1099
+ # remaining nodes are in their own group
1100
+ for i, node in enumerate(remaining, start=i + 1):
1101
+ group = {node}
1102
+ members[i] = group
1103
+ mapping.update((n, i) for n in group)
1104
+ number_of_groups = i + 1
1105
+ C.add_nodes_from(range(number_of_groups))
1106
+ C.add_edges_from(
1107
+ (mapping[u], mapping[v]) for u, v in G.edges() if mapping[u] != mapping[v]
1108
+ )
1109
+ # Add a list of members (ie original nodes) to each node (ie scc) in C.
1110
+ nx.set_node_attributes(C, name="members", values=members)
1111
+ # Add mapping dict as graph attribute
1112
+ C.graph["mapping"] = mapping
1113
+ return C
1114
+
1115
+
1116
+ @nx._dispatchable
1117
+ def complement_edges(G):
1118
+ """Returns only the edges in the complement of G
1119
+
1120
+ Parameters
1121
+ ----------
1122
+ G : NetworkX Graph
1123
+
1124
+ Yields
1125
+ ------
1126
+ edge : tuple
1127
+ Edges in the complement of G
1128
+
1129
+ Examples
1130
+ --------
1131
+ >>> G = nx.path_graph((1, 2, 3, 4))
1132
+ >>> sorted(complement_edges(G))
1133
+ [(1, 3), (1, 4), (2, 4)]
1134
+ >>> G = nx.path_graph((1, 2, 3, 4), nx.DiGraph())
1135
+ >>> sorted(complement_edges(G))
1136
+ [(1, 3), (1, 4), (2, 1), (2, 4), (3, 1), (3, 2), (4, 1), (4, 2), (4, 3)]
1137
+ >>> G = nx.complete_graph(1000)
1138
+ >>> sorted(complement_edges(G))
1139
+ []
1140
+ """
1141
+ G_adj = G._adj # Store as a variable to eliminate attribute lookup
1142
+ if G.is_directed():
1143
+ for u, v in it.combinations(G.nodes(), 2):
1144
+ if v not in G_adj[u]:
1145
+ yield (u, v)
1146
+ if u not in G_adj[v]:
1147
+ yield (v, u)
1148
+ else:
1149
+ for u, v in it.combinations(G.nodes(), 2):
1150
+ if v not in G_adj[u]:
1151
+ yield (u, v)
1152
+
1153
+
1154
+ def _compat_shuffle(rng, input):
1155
+ """wrapper around rng.shuffle for python 2 compatibility reasons"""
1156
+ rng.shuffle(input)
1157
+
1158
+
1159
+ @not_implemented_for("multigraph")
1160
+ @not_implemented_for("directed")
1161
+ @py_random_state(4)
1162
+ @nx._dispatchable
1163
+ def greedy_k_edge_augmentation(G, k, avail=None, weight=None, seed=None):
1164
+ """Greedy algorithm for finding a k-edge-augmentation
1165
+
1166
+ Parameters
1167
+ ----------
1168
+ G : NetworkX graph
1169
+ An undirected graph.
1170
+
1171
+ k : integer
1172
+ Desired edge connectivity
1173
+
1174
+ avail : dict or a set of 2 or 3 tuples
1175
+ For more details, see :func:`k_edge_augmentation`.
1176
+
1177
+ weight : string
1178
+ key to use to find weights if ``avail`` is a set of 3-tuples.
1179
+ For more details, see :func:`k_edge_augmentation`.
1180
+
1181
+ seed : integer, random_state, or None (default)
1182
+ Indicator of random number generation state.
1183
+ See :ref:`Randomness<randomness>`.
1184
+
1185
+ Yields
1186
+ ------
1187
+ edge : tuple
1188
+ Edges in the greedy augmentation of G
1189
+
1190
+ Notes
1191
+ -----
1192
+ The algorithm is simple. Edges are incrementally added between parts of the
1193
+ graph that are not yet locally k-edge-connected. Then edges are from the
1194
+ augmenting set are pruned as long as local-edge-connectivity is not broken.
1195
+
1196
+ This algorithm is greedy and does not provide optimality guarantees. It
1197
+ exists only to provide :func:`k_edge_augmentation` with the ability to
1198
+ generate a feasible solution for arbitrary k.
1199
+
1200
+ See Also
1201
+ --------
1202
+ :func:`k_edge_augmentation`
1203
+
1204
+ Examples
1205
+ --------
1206
+ >>> G = nx.path_graph((1, 2, 3, 4, 5, 6, 7))
1207
+ >>> sorted(greedy_k_edge_augmentation(G, k=2))
1208
+ [(1, 7)]
1209
+ >>> sorted(greedy_k_edge_augmentation(G, k=1, avail=[]))
1210
+ []
1211
+ >>> G = nx.path_graph((1, 2, 3, 4, 5, 6, 7))
1212
+ >>> avail = {(u, v): 1 for (u, v) in complement_edges(G)}
1213
+ >>> # randomized pruning process can produce different solutions
1214
+ >>> sorted(greedy_k_edge_augmentation(G, k=4, avail=avail, seed=2))
1215
+ [(1, 3), (1, 4), (1, 5), (1, 6), (1, 7), (2, 4), (2, 6), (3, 7), (5, 7)]
1216
+ >>> sorted(greedy_k_edge_augmentation(G, k=4, avail=avail, seed=3))
1217
+ [(1, 3), (1, 5), (1, 6), (2, 4), (2, 6), (3, 7), (4, 7), (5, 7)]
1218
+ """
1219
+ # Result set
1220
+ aug_edges = []
1221
+
1222
+ done = is_k_edge_connected(G, k)
1223
+ if done:
1224
+ return
1225
+ if avail is None:
1226
+ # all edges are available
1227
+ avail_uv = list(complement_edges(G))
1228
+ avail_w = [1] * len(avail_uv)
1229
+ else:
1230
+ # Get the unique set of unweighted edges
1231
+ avail_uv, avail_w = _unpack_available_edges(avail, weight=weight, G=G)
1232
+
1233
+ # Greedy: order lightest edges. Use degree sum to tie-break
1234
+ tiebreaker = [sum(map(G.degree, uv)) for uv in avail_uv]
1235
+ avail_wduv = sorted(zip(avail_w, tiebreaker, avail_uv))
1236
+ avail_uv = [uv for w, d, uv in avail_wduv]
1237
+
1238
+ # Incrementally add edges in until we are k-connected
1239
+ H = G.copy()
1240
+ for u, v in avail_uv:
1241
+ done = False
1242
+ if not is_locally_k_edge_connected(H, u, v, k=k):
1243
+ # Only add edges in parts that are not yet locally k-edge-connected
1244
+ aug_edges.append((u, v))
1245
+ H.add_edge(u, v)
1246
+ # Did adding this edge help?
1247
+ if H.degree(u) >= k and H.degree(v) >= k:
1248
+ done = is_k_edge_connected(H, k)
1249
+ if done:
1250
+ break
1251
+
1252
+ # Check for feasibility
1253
+ if not done:
1254
+ raise nx.NetworkXUnfeasible("not able to k-edge-connect with available edges")
1255
+
1256
+ # Randomized attempt to reduce the size of the solution
1257
+ _compat_shuffle(seed, aug_edges)
1258
+ for u, v in list(aug_edges):
1259
+ # Don't remove if we know it would break connectivity
1260
+ if H.degree(u) <= k or H.degree(v) <= k:
1261
+ continue
1262
+ H.remove_edge(u, v)
1263
+ aug_edges.remove((u, v))
1264
+ if not is_k_edge_connected(H, k=k):
1265
+ # If removing this edge breaks feasibility, undo
1266
+ H.add_edge(u, v)
1267
+ aug_edges.append((u, v))
1268
+
1269
+ # Generate results
1270
+ yield from aug_edges
.venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/edge_kcomponents.py ADDED
@@ -0,0 +1,592 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Algorithms for finding k-edge-connected components and subgraphs.
3
+
4
+ A k-edge-connected component (k-edge-cc) is a maximal set of nodes in G, such
5
+ that all pairs of node have an edge-connectivity of at least k.
6
+
7
+ A k-edge-connected subgraph (k-edge-subgraph) is a maximal set of nodes in G,
8
+ such that the subgraph of G defined by the nodes has an edge-connectivity at
9
+ least k.
10
+ """
11
+
12
+ import itertools as it
13
+ from functools import partial
14
+
15
+ import networkx as nx
16
+ from networkx.utils import arbitrary_element, not_implemented_for
17
+
18
+ __all__ = [
19
+ "k_edge_components",
20
+ "k_edge_subgraphs",
21
+ "bridge_components",
22
+ "EdgeComponentAuxGraph",
23
+ ]
24
+
25
+
26
+ @not_implemented_for("multigraph")
27
+ @nx._dispatchable
28
+ def k_edge_components(G, k):
29
+ """Generates nodes in each maximal k-edge-connected component in G.
30
+
31
+ Parameters
32
+ ----------
33
+ G : NetworkX graph
34
+
35
+ k : Integer
36
+ Desired edge connectivity
37
+
38
+ Returns
39
+ -------
40
+ k_edge_components : a generator of k-edge-ccs. Each set of returned nodes
41
+ will have k-edge-connectivity in the graph G.
42
+
43
+ See Also
44
+ --------
45
+ :func:`local_edge_connectivity`
46
+ :func:`k_edge_subgraphs` : similar to this function, but the subgraph
47
+ defined by the nodes must also have k-edge-connectivity.
48
+ :func:`k_components` : similar to this function, but uses node-connectivity
49
+ instead of edge-connectivity
50
+
51
+ Raises
52
+ ------
53
+ NetworkXNotImplemented
54
+ If the input graph is a multigraph.
55
+
56
+ ValueError:
57
+ If k is less than 1
58
+
59
+ Notes
60
+ -----
61
+ Attempts to use the most efficient implementation available based on k.
62
+ If k=1, this is simply connected components for directed graphs and
63
+ connected components for undirected graphs.
64
+ If k=2 on an efficient bridge connected component algorithm from _[1] is
65
+ run based on the chain decomposition.
66
+ Otherwise, the algorithm from _[2] is used.
67
+
68
+ Examples
69
+ --------
70
+ >>> import itertools as it
71
+ >>> from networkx.utils import pairwise
72
+ >>> paths = [
73
+ ... (1, 2, 4, 3, 1, 4),
74
+ ... (5, 6, 7, 8, 5, 7, 8, 6),
75
+ ... ]
76
+ >>> G = nx.Graph()
77
+ >>> G.add_nodes_from(it.chain(*paths))
78
+ >>> G.add_edges_from(it.chain(*[pairwise(path) for path in paths]))
79
+ >>> # note this returns {1, 4} unlike k_edge_subgraphs
80
+ >>> sorted(map(sorted, nx.k_edge_components(G, k=3)))
81
+ [[1, 4], [2], [3], [5, 6, 7, 8]]
82
+
83
+ References
84
+ ----------
85
+ .. [1] https://en.wikipedia.org/wiki/Bridge_%28graph_theory%29
86
+ .. [2] Wang, Tianhao, et al. (2015) A simple algorithm for finding all
87
+ k-edge-connected components.
88
+ http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0136264
89
+ """
90
+ # Compute k-edge-ccs using the most efficient algorithms available.
91
+ if k < 1:
92
+ raise ValueError("k cannot be less than 1")
93
+ if G.is_directed():
94
+ if k == 1:
95
+ return nx.strongly_connected_components(G)
96
+ else:
97
+ # TODO: investigate https://arxiv.org/abs/1412.6466 for k=2
98
+ aux_graph = EdgeComponentAuxGraph.construct(G)
99
+ return aux_graph.k_edge_components(k)
100
+ else:
101
+ if k == 1:
102
+ return nx.connected_components(G)
103
+ elif k == 2:
104
+ return bridge_components(G)
105
+ else:
106
+ aux_graph = EdgeComponentAuxGraph.construct(G)
107
+ return aux_graph.k_edge_components(k)
108
+
109
+
110
+ @not_implemented_for("multigraph")
111
+ @nx._dispatchable
112
+ def k_edge_subgraphs(G, k):
113
+ """Generates nodes in each maximal k-edge-connected subgraph in G.
114
+
115
+ Parameters
116
+ ----------
117
+ G : NetworkX graph
118
+
119
+ k : Integer
120
+ Desired edge connectivity
121
+
122
+ Returns
123
+ -------
124
+ k_edge_subgraphs : a generator of k-edge-subgraphs
125
+ Each k-edge-subgraph is a maximal set of nodes that defines a subgraph
126
+ of G that is k-edge-connected.
127
+
128
+ See Also
129
+ --------
130
+ :func:`edge_connectivity`
131
+ :func:`k_edge_components` : similar to this function, but nodes only
132
+ need to have k-edge-connectivity within the graph G and the subgraphs
133
+ might not be k-edge-connected.
134
+
135
+ Raises
136
+ ------
137
+ NetworkXNotImplemented
138
+ If the input graph is a multigraph.
139
+
140
+ ValueError:
141
+ If k is less than 1
142
+
143
+ Notes
144
+ -----
145
+ Attempts to use the most efficient implementation available based on k.
146
+ If k=1, or k=2 and the graph is undirected, then this simply calls
147
+ `k_edge_components`. Otherwise the algorithm from _[1] is used.
148
+
149
+ Examples
150
+ --------
151
+ >>> import itertools as it
152
+ >>> from networkx.utils import pairwise
153
+ >>> paths = [
154
+ ... (1, 2, 4, 3, 1, 4),
155
+ ... (5, 6, 7, 8, 5, 7, 8, 6),
156
+ ... ]
157
+ >>> G = nx.Graph()
158
+ >>> G.add_nodes_from(it.chain(*paths))
159
+ >>> G.add_edges_from(it.chain(*[pairwise(path) for path in paths]))
160
+ >>> # note this does not return {1, 4} unlike k_edge_components
161
+ >>> sorted(map(sorted, nx.k_edge_subgraphs(G, k=3)))
162
+ [[1], [2], [3], [4], [5, 6, 7, 8]]
163
+
164
+ References
165
+ ----------
166
+ .. [1] Zhou, Liu, et al. (2012) Finding maximal k-edge-connected subgraphs
167
+ from a large graph. ACM International Conference on Extending Database
168
+ Technology 2012 480-–491.
169
+ https://openproceedings.org/2012/conf/edbt/ZhouLYLCL12.pdf
170
+ """
171
+ if k < 1:
172
+ raise ValueError("k cannot be less than 1")
173
+ if G.is_directed():
174
+ if k <= 1:
175
+ # For directed graphs ,
176
+ # When k == 1, k-edge-ccs and k-edge-subgraphs are the same
177
+ return k_edge_components(G, k)
178
+ else:
179
+ return _k_edge_subgraphs_nodes(G, k)
180
+ else:
181
+ if k <= 2:
182
+ # For undirected graphs,
183
+ # when k <= 2, k-edge-ccs and k-edge-subgraphs are the same
184
+ return k_edge_components(G, k)
185
+ else:
186
+ return _k_edge_subgraphs_nodes(G, k)
187
+
188
+
189
+ def _k_edge_subgraphs_nodes(G, k):
190
+ """Helper to get the nodes from the subgraphs.
191
+
192
+ This allows k_edge_subgraphs to return a generator.
193
+ """
194
+ for C in general_k_edge_subgraphs(G, k):
195
+ yield set(C.nodes())
196
+
197
+
198
+ @not_implemented_for("directed")
199
+ @not_implemented_for("multigraph")
200
+ @nx._dispatchable
201
+ def bridge_components(G):
202
+ """Finds all bridge-connected components G.
203
+
204
+ Parameters
205
+ ----------
206
+ G : NetworkX undirected graph
207
+
208
+ Returns
209
+ -------
210
+ bridge_components : a generator of 2-edge-connected components
211
+
212
+
213
+ See Also
214
+ --------
215
+ :func:`k_edge_subgraphs` : this function is a special case for an
216
+ undirected graph where k=2.
217
+ :func:`biconnected_components` : similar to this function, but is defined
218
+ using 2-node-connectivity instead of 2-edge-connectivity.
219
+
220
+ Raises
221
+ ------
222
+ NetworkXNotImplemented
223
+ If the input graph is directed or a multigraph.
224
+
225
+ Notes
226
+ -----
227
+ Bridge-connected components are also known as 2-edge-connected components.
228
+
229
+ Examples
230
+ --------
231
+ >>> # The barbell graph with parameter zero has a single bridge
232
+ >>> G = nx.barbell_graph(5, 0)
233
+ >>> from networkx.algorithms.connectivity.edge_kcomponents import bridge_components
234
+ >>> sorted(map(sorted, bridge_components(G)))
235
+ [[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]
236
+ """
237
+ H = G.copy()
238
+ H.remove_edges_from(nx.bridges(G))
239
+ yield from nx.connected_components(H)
240
+
241
+
242
+ class EdgeComponentAuxGraph:
243
+ r"""A simple algorithm to find all k-edge-connected components in a graph.
244
+
245
+ Constructing the auxiliary graph (which may take some time) allows for the
246
+ k-edge-ccs to be found in linear time for arbitrary k.
247
+
248
+ Notes
249
+ -----
250
+ This implementation is based on [1]_. The idea is to construct an auxiliary
251
+ graph from which the k-edge-ccs can be extracted in linear time. The
252
+ auxiliary graph is constructed in $O(|V|\cdot F)$ operations, where F is the
253
+ complexity of max flow. Querying the components takes an additional $O(|V|)$
254
+ operations. This algorithm can be slow for large graphs, but it handles an
255
+ arbitrary k and works for both directed and undirected inputs.
256
+
257
+ The undirected case for k=1 is exactly connected components.
258
+ The undirected case for k=2 is exactly bridge connected components.
259
+ The directed case for k=1 is exactly strongly connected components.
260
+
261
+ References
262
+ ----------
263
+ .. [1] Wang, Tianhao, et al. (2015) A simple algorithm for finding all
264
+ k-edge-connected components.
265
+ http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0136264
266
+
267
+ Examples
268
+ --------
269
+ >>> import itertools as it
270
+ >>> from networkx.utils import pairwise
271
+ >>> from networkx.algorithms.connectivity import EdgeComponentAuxGraph
272
+ >>> # Build an interesting graph with multiple levels of k-edge-ccs
273
+ >>> paths = [
274
+ ... (1, 2, 3, 4, 1, 3, 4, 2), # a 3-edge-cc (a 4 clique)
275
+ ... (5, 6, 7, 5), # a 2-edge-cc (a 3 clique)
276
+ ... (1, 5), # combine first two ccs into a 1-edge-cc
277
+ ... (0,), # add an additional disconnected 1-edge-cc
278
+ ... ]
279
+ >>> G = nx.Graph()
280
+ >>> G.add_nodes_from(it.chain(*paths))
281
+ >>> G.add_edges_from(it.chain(*[pairwise(path) for path in paths]))
282
+ >>> # Constructing the AuxGraph takes about O(n ** 4)
283
+ >>> aux_graph = EdgeComponentAuxGraph.construct(G)
284
+ >>> # Once constructed, querying takes O(n)
285
+ >>> sorted(map(sorted, aux_graph.k_edge_components(k=1)))
286
+ [[0], [1, 2, 3, 4, 5, 6, 7]]
287
+ >>> sorted(map(sorted, aux_graph.k_edge_components(k=2)))
288
+ [[0], [1, 2, 3, 4], [5, 6, 7]]
289
+ >>> sorted(map(sorted, aux_graph.k_edge_components(k=3)))
290
+ [[0], [1, 2, 3, 4], [5], [6], [7]]
291
+ >>> sorted(map(sorted, aux_graph.k_edge_components(k=4)))
292
+ [[0], [1], [2], [3], [4], [5], [6], [7]]
293
+
294
+ The auxiliary graph is primarily used for k-edge-ccs but it
295
+ can also speed up the queries of k-edge-subgraphs by refining the
296
+ search space.
297
+
298
+ >>> import itertools as it
299
+ >>> from networkx.utils import pairwise
300
+ >>> from networkx.algorithms.connectivity import EdgeComponentAuxGraph
301
+ >>> paths = [
302
+ ... (1, 2, 4, 3, 1, 4),
303
+ ... ]
304
+ >>> G = nx.Graph()
305
+ >>> G.add_nodes_from(it.chain(*paths))
306
+ >>> G.add_edges_from(it.chain(*[pairwise(path) for path in paths]))
307
+ >>> aux_graph = EdgeComponentAuxGraph.construct(G)
308
+ >>> sorted(map(sorted, aux_graph.k_edge_subgraphs(k=3)))
309
+ [[1], [2], [3], [4]]
310
+ >>> sorted(map(sorted, aux_graph.k_edge_components(k=3)))
311
+ [[1, 4], [2], [3]]
312
+ """
313
+
314
+ # @not_implemented_for('multigraph') # TODO: fix decor for classmethods
315
+ @classmethod
316
+ def construct(EdgeComponentAuxGraph, G):
317
+ """Builds an auxiliary graph encoding edge-connectivity between nodes.
318
+
319
+ Notes
320
+ -----
321
+ Given G=(V, E), initialize an empty auxiliary graph A.
322
+ Choose an arbitrary source node s. Initialize a set N of available
323
+ nodes (that can be used as the sink). The algorithm picks an
324
+ arbitrary node t from N - {s}, and then computes the minimum st-cut
325
+ (S, T) with value w. If G is directed the minimum of the st-cut or
326
+ the ts-cut is used instead. Then, the edge (s, t) is added to the
327
+ auxiliary graph with weight w. The algorithm is called recursively
328
+ first using S as the available nodes and s as the source, and then
329
+ using T and t. Recursion stops when the source is the only available
330
+ node.
331
+
332
+ Parameters
333
+ ----------
334
+ G : NetworkX graph
335
+ """
336
+ # workaround for classmethod decorator
337
+ not_implemented_for("multigraph")(lambda G: G)(G)
338
+
339
+ def _recursive_build(H, A, source, avail):
340
+ # Terminate once the flow has been compute to every node.
341
+ if {source} == avail:
342
+ return
343
+ # pick an arbitrary node as the sink
344
+ sink = arbitrary_element(avail - {source})
345
+ # find the minimum cut and its weight
346
+ value, (S, T) = nx.minimum_cut(H, source, sink)
347
+ if H.is_directed():
348
+ # check if the reverse direction has a smaller cut
349
+ value_, (T_, S_) = nx.minimum_cut(H, sink, source)
350
+ if value_ < value:
351
+ value, S, T = value_, S_, T_
352
+ # add edge with weight of cut to the aux graph
353
+ A.add_edge(source, sink, weight=value)
354
+ # recursively call until all but one node is used
355
+ _recursive_build(H, A, source, avail.intersection(S))
356
+ _recursive_build(H, A, sink, avail.intersection(T))
357
+
358
+ # Copy input to ensure all edges have unit capacity
359
+ H = G.__class__()
360
+ H.add_nodes_from(G.nodes())
361
+ H.add_edges_from(G.edges(), capacity=1)
362
+
363
+ # A is the auxiliary graph to be constructed
364
+ # It is a weighted undirected tree
365
+ A = nx.Graph()
366
+
367
+ # Pick an arbitrary node as the source
368
+ if H.number_of_nodes() > 0:
369
+ source = arbitrary_element(H.nodes())
370
+ # Initialize a set of elements that can be chosen as the sink
371
+ avail = set(H.nodes())
372
+
373
+ # This constructs A
374
+ _recursive_build(H, A, source, avail)
375
+
376
+ # This class is a container the holds the auxiliary graph A and
377
+ # provides access the k_edge_components function.
378
+ self = EdgeComponentAuxGraph()
379
+ self.A = A
380
+ self.H = H
381
+ return self
382
+
383
+ def k_edge_components(self, k):
384
+ """Queries the auxiliary graph for k-edge-connected components.
385
+
386
+ Parameters
387
+ ----------
388
+ k : Integer
389
+ Desired edge connectivity
390
+
391
+ Returns
392
+ -------
393
+ k_edge_components : a generator of k-edge-ccs
394
+
395
+ Notes
396
+ -----
397
+ Given the auxiliary graph, the k-edge-connected components can be
398
+ determined in linear time by removing all edges with weights less than
399
+ k from the auxiliary graph. The resulting connected components are the
400
+ k-edge-ccs in the original graph.
401
+ """
402
+ if k < 1:
403
+ raise ValueError("k cannot be less than 1")
404
+ A = self.A
405
+ # "traverse the auxiliary graph A and delete all edges with weights less
406
+ # than k"
407
+ aux_weights = nx.get_edge_attributes(A, "weight")
408
+ # Create a relevant graph with the auxiliary edges with weights >= k
409
+ R = nx.Graph()
410
+ R.add_nodes_from(A.nodes())
411
+ R.add_edges_from(e for e, w in aux_weights.items() if w >= k)
412
+
413
+ # Return the nodes that are k-edge-connected in the original graph
414
+ yield from nx.connected_components(R)
415
+
416
+ def k_edge_subgraphs(self, k):
417
+ """Queries the auxiliary graph for k-edge-connected subgraphs.
418
+
419
+ Parameters
420
+ ----------
421
+ k : Integer
422
+ Desired edge connectivity
423
+
424
+ Returns
425
+ -------
426
+ k_edge_subgraphs : a generator of k-edge-subgraphs
427
+
428
+ Notes
429
+ -----
430
+ Refines the k-edge-ccs into k-edge-subgraphs. The running time is more
431
+ than $O(|V|)$.
432
+
433
+ For single values of k it is faster to use `nx.k_edge_subgraphs`.
434
+ But for multiple values of k, it can be faster to build AuxGraph and
435
+ then use this method.
436
+ """
437
+ if k < 1:
438
+ raise ValueError("k cannot be less than 1")
439
+ H = self.H
440
+ A = self.A
441
+ # "traverse the auxiliary graph A and delete all edges with weights less
442
+ # than k"
443
+ aux_weights = nx.get_edge_attributes(A, "weight")
444
+ # Create a relevant graph with the auxiliary edges with weights >= k
445
+ R = nx.Graph()
446
+ R.add_nodes_from(A.nodes())
447
+ R.add_edges_from(e for e, w in aux_weights.items() if w >= k)
448
+
449
+ # Return the components whose subgraphs are k-edge-connected
450
+ for cc in nx.connected_components(R):
451
+ if len(cc) < k:
452
+ # Early return optimization
453
+ for node in cc:
454
+ yield {node}
455
+ else:
456
+ # Call subgraph solution to refine the results
457
+ C = H.subgraph(cc)
458
+ yield from k_edge_subgraphs(C, k)
459
+
460
+
461
+ def _low_degree_nodes(G, k, nbunch=None):
462
+ """Helper for finding nodes with degree less than k."""
463
+ # Nodes with degree less than k cannot be k-edge-connected.
464
+ if G.is_directed():
465
+ # Consider both in and out degree in the directed case
466
+ seen = set()
467
+ for node, degree in G.out_degree(nbunch):
468
+ if degree < k:
469
+ seen.add(node)
470
+ yield node
471
+ for node, degree in G.in_degree(nbunch):
472
+ if node not in seen and degree < k:
473
+ seen.add(node)
474
+ yield node
475
+ else:
476
+ # Only the degree matters in the undirected case
477
+ for node, degree in G.degree(nbunch):
478
+ if degree < k:
479
+ yield node
480
+
481
+
482
+ def _high_degree_components(G, k):
483
+ """Helper for filtering components that can't be k-edge-connected.
484
+
485
+ Removes and generates each node with degree less than k. Then generates
486
+ remaining components where all nodes have degree at least k.
487
+ """
488
+ # Iteratively remove parts of the graph that are not k-edge-connected
489
+ H = G.copy()
490
+ singletons = set(_low_degree_nodes(H, k))
491
+ while singletons:
492
+ # Only search neighbors of removed nodes
493
+ nbunch = set(it.chain.from_iterable(map(H.neighbors, singletons)))
494
+ nbunch.difference_update(singletons)
495
+ H.remove_nodes_from(singletons)
496
+ for node in singletons:
497
+ yield {node}
498
+ singletons = set(_low_degree_nodes(H, k, nbunch))
499
+
500
+ # Note: remaining connected components may not be k-edge-connected
501
+ if G.is_directed():
502
+ yield from nx.strongly_connected_components(H)
503
+ else:
504
+ yield from nx.connected_components(H)
505
+
506
+
507
+ @nx._dispatchable(returns_graph=True)
508
+ def general_k_edge_subgraphs(G, k):
509
+ """General algorithm to find all maximal k-edge-connected subgraphs in `G`.
510
+
511
+ Parameters
512
+ ----------
513
+ G : nx.Graph
514
+ Graph in which all maximal k-edge-connected subgraphs will be found.
515
+
516
+ k : int
517
+
518
+ Yields
519
+ ------
520
+ k_edge_subgraphs : Graph instances that are k-edge-subgraphs
521
+ Each k-edge-subgraph contains a maximal set of nodes that defines a
522
+ subgraph of `G` that is k-edge-connected.
523
+
524
+ Notes
525
+ -----
526
+ Implementation of the basic algorithm from [1]_. The basic idea is to find
527
+ a global minimum cut of the graph. If the cut value is at least k, then the
528
+ graph is a k-edge-connected subgraph and can be added to the results.
529
+ Otherwise, the cut is used to split the graph in two and the procedure is
530
+ applied recursively. If the graph is just a single node, then it is also
531
+ added to the results. At the end, each result is either guaranteed to be
532
+ a single node or a subgraph of G that is k-edge-connected.
533
+
534
+ This implementation contains optimizations for reducing the number of calls
535
+ to max-flow, but there are other optimizations in [1]_ that could be
536
+ implemented.
537
+
538
+ References
539
+ ----------
540
+ .. [1] Zhou, Liu, et al. (2012) Finding maximal k-edge-connected subgraphs
541
+ from a large graph. ACM International Conference on Extending Database
542
+ Technology 2012 480-–491.
543
+ https://openproceedings.org/2012/conf/edbt/ZhouLYLCL12.pdf
544
+
545
+ Examples
546
+ --------
547
+ >>> from networkx.utils import pairwise
548
+ >>> paths = [
549
+ ... (11, 12, 13, 14, 11, 13, 14, 12), # a 4-clique
550
+ ... (21, 22, 23, 24, 21, 23, 24, 22), # another 4-clique
551
+ ... # connect the cliques with high degree but low connectivity
552
+ ... (50, 13),
553
+ ... (12, 50, 22),
554
+ ... (13, 102, 23),
555
+ ... (14, 101, 24),
556
+ ... ]
557
+ >>> G = nx.Graph(it.chain(*[pairwise(path) for path in paths]))
558
+ >>> sorted(len(k_sg) for k_sg in k_edge_subgraphs(G, k=3))
559
+ [1, 1, 1, 4, 4]
560
+ """
561
+ if k < 1:
562
+ raise ValueError("k cannot be less than 1")
563
+
564
+ # Node pruning optimization (incorporates early return)
565
+ # find_ccs is either connected_components/strongly_connected_components
566
+ find_ccs = partial(_high_degree_components, k=k)
567
+
568
+ # Quick return optimization
569
+ if G.number_of_nodes() < k:
570
+ for node in G.nodes():
571
+ yield G.subgraph([node]).copy()
572
+ return
573
+
574
+ # Intermediate results
575
+ R0 = {G.subgraph(cc).copy() for cc in find_ccs(G)}
576
+ # Subdivide CCs in the intermediate results until they are k-conn
577
+ while R0:
578
+ G1 = R0.pop()
579
+ if G1.number_of_nodes() == 1:
580
+ yield G1
581
+ else:
582
+ # Find a global minimum cut
583
+ cut_edges = nx.minimum_edge_cut(G1)
584
+ cut_value = len(cut_edges)
585
+ if cut_value < k:
586
+ # G1 is not k-edge-connected, so subdivide it
587
+ G1.remove_edges_from(cut_edges)
588
+ for cc in find_ccs(G1):
589
+ R0.add(G1.subgraph(cc).copy())
590
+ else:
591
+ # Otherwise we found a k-edge-connected subgraph
592
+ yield G1
.venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/kcomponents.py ADDED
@@ -0,0 +1,223 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Moody and White algorithm for k-components
3
+ """
4
+
5
+ from collections import defaultdict
6
+ from itertools import combinations
7
+ from operator import itemgetter
8
+
9
+ import networkx as nx
10
+
11
+ # Define the default maximum flow function.
12
+ from networkx.algorithms.flow import edmonds_karp
13
+ from networkx.utils import not_implemented_for
14
+
15
+ default_flow_func = edmonds_karp
16
+
17
+ __all__ = ["k_components"]
18
+
19
+
20
+ @not_implemented_for("directed")
21
+ @nx._dispatchable
22
+ def k_components(G, flow_func=None):
23
+ r"""Returns the k-component structure of a graph G.
24
+
25
+ A `k`-component is a maximal subgraph of a graph G that has, at least,
26
+ node connectivity `k`: we need to remove at least `k` nodes to break it
27
+ into more components. `k`-components have an inherent hierarchical
28
+ structure because they are nested in terms of connectivity: a connected
29
+ graph can contain several 2-components, each of which can contain
30
+ one or more 3-components, and so forth.
31
+
32
+ Parameters
33
+ ----------
34
+ G : NetworkX graph
35
+
36
+ flow_func : function
37
+ Function to perform the underlying flow computations. Default value
38
+ :meth:`edmonds_karp`. This function performs better in sparse graphs with
39
+ right tailed degree distributions. :meth:`shortest_augmenting_path` will
40
+ perform better in denser graphs.
41
+
42
+ Returns
43
+ -------
44
+ k_components : dict
45
+ Dictionary with all connectivity levels `k` in the input Graph as keys
46
+ and a list of sets of nodes that form a k-component of level `k` as
47
+ values.
48
+
49
+ Raises
50
+ ------
51
+ NetworkXNotImplemented
52
+ If the input graph is directed.
53
+
54
+ Examples
55
+ --------
56
+ >>> # Petersen graph has 10 nodes and it is triconnected, thus all
57
+ >>> # nodes are in a single component on all three connectivity levels
58
+ >>> G = nx.petersen_graph()
59
+ >>> k_components = nx.k_components(G)
60
+
61
+ Notes
62
+ -----
63
+ Moody and White [1]_ (appendix A) provide an algorithm for identifying
64
+ k-components in a graph, which is based on Kanevsky's algorithm [2]_
65
+ for finding all minimum-size node cut-sets of a graph (implemented in
66
+ :meth:`all_node_cuts` function):
67
+
68
+ 1. Compute node connectivity, k, of the input graph G.
69
+
70
+ 2. Identify all k-cutsets at the current level of connectivity using
71
+ Kanevsky's algorithm.
72
+
73
+ 3. Generate new graph components based on the removal of
74
+ these cutsets. Nodes in a cutset belong to both sides
75
+ of the induced cut.
76
+
77
+ 4. If the graph is neither complete nor trivial, return to 1;
78
+ else end.
79
+
80
+ This implementation also uses some heuristics (see [3]_ for details)
81
+ to speed up the computation.
82
+
83
+ See also
84
+ --------
85
+ node_connectivity
86
+ all_node_cuts
87
+ biconnected_components : special case of this function when k=2
88
+ k_edge_components : similar to this function, but uses edge-connectivity
89
+ instead of node-connectivity
90
+
91
+ References
92
+ ----------
93
+ .. [1] Moody, J. and D. White (2003). Social cohesion and embeddedness:
94
+ A hierarchical conception of social groups.
95
+ American Sociological Review 68(1), 103--28.
96
+ http://www2.asanet.org/journals/ASRFeb03MoodyWhite.pdf
97
+
98
+ .. [2] Kanevsky, A. (1993). Finding all minimum-size separating vertex
99
+ sets in a graph. Networks 23(6), 533--541.
100
+ http://onlinelibrary.wiley.com/doi/10.1002/net.3230230604/abstract
101
+
102
+ .. [3] Torrents, J. and F. Ferraro (2015). Structural Cohesion:
103
+ Visualization and Heuristics for Fast Computation.
104
+ https://arxiv.org/pdf/1503.04476v1
105
+
106
+ """
107
+ # Dictionary with connectivity level (k) as keys and a list of
108
+ # sets of nodes that form a k-component as values. Note that
109
+ # k-components can overlap (but only k - 1 nodes).
110
+ k_components = defaultdict(list)
111
+ # Define default flow function
112
+ if flow_func is None:
113
+ flow_func = default_flow_func
114
+ # Bicomponents as a base to check for higher order k-components
115
+ for component in nx.connected_components(G):
116
+ # isolated nodes have connectivity 0
117
+ comp = set(component)
118
+ if len(comp) > 1:
119
+ k_components[1].append(comp)
120
+ bicomponents = [G.subgraph(c) for c in nx.biconnected_components(G)]
121
+ for bicomponent in bicomponents:
122
+ bicomp = set(bicomponent)
123
+ # avoid considering dyads as bicomponents
124
+ if len(bicomp) > 2:
125
+ k_components[2].append(bicomp)
126
+ for B in bicomponents:
127
+ if len(B) <= 2:
128
+ continue
129
+ k = nx.node_connectivity(B, flow_func=flow_func)
130
+ if k > 2:
131
+ k_components[k].append(set(B))
132
+ # Perform cuts in a DFS like order.
133
+ cuts = list(nx.all_node_cuts(B, k=k, flow_func=flow_func))
134
+ stack = [(k, _generate_partition(B, cuts, k))]
135
+ while stack:
136
+ (parent_k, partition) = stack[-1]
137
+ try:
138
+ nodes = next(partition)
139
+ C = B.subgraph(nodes)
140
+ this_k = nx.node_connectivity(C, flow_func=flow_func)
141
+ if this_k > parent_k and this_k > 2:
142
+ k_components[this_k].append(set(C))
143
+ cuts = list(nx.all_node_cuts(C, k=this_k, flow_func=flow_func))
144
+ if cuts:
145
+ stack.append((this_k, _generate_partition(C, cuts, this_k)))
146
+ except StopIteration:
147
+ stack.pop()
148
+
149
+ # This is necessary because k-components may only be reported at their
150
+ # maximum k level. But we want to return a dictionary in which keys are
151
+ # connectivity levels and values list of sets of components, without
152
+ # skipping any connectivity level. Also, it's possible that subsets of
153
+ # an already detected k-component appear at a level k. Checking for this
154
+ # in the while loop above penalizes the common case. Thus we also have to
155
+ # _consolidate all connectivity levels in _reconstruct_k_components.
156
+ return _reconstruct_k_components(k_components)
157
+
158
+
159
+ def _consolidate(sets, k):
160
+ """Merge sets that share k or more elements.
161
+
162
+ See: http://rosettacode.org/wiki/Set_consolidation
163
+
164
+ The iterative python implementation posted there is
165
+ faster than this because of the overhead of building a
166
+ Graph and calling nx.connected_components, but it's not
167
+ clear for us if we can use it in NetworkX because there
168
+ is no licence for the code.
169
+
170
+ """
171
+ G = nx.Graph()
172
+ nodes = dict(enumerate(sets))
173
+ G.add_nodes_from(nodes)
174
+ G.add_edges_from(
175
+ (u, v) for u, v in combinations(nodes, 2) if len(nodes[u] & nodes[v]) >= k
176
+ )
177
+ for component in nx.connected_components(G):
178
+ yield set.union(*[nodes[n] for n in component])
179
+
180
+
181
+ def _generate_partition(G, cuts, k):
182
+ def has_nbrs_in_partition(G, node, partition):
183
+ return any(n in partition for n in G[node])
184
+
185
+ components = []
186
+ nodes = {n for n, d in G.degree() if d > k} - {n for cut in cuts for n in cut}
187
+ H = G.subgraph(nodes)
188
+ for cc in nx.connected_components(H):
189
+ component = set(cc)
190
+ for cut in cuts:
191
+ for node in cut:
192
+ if has_nbrs_in_partition(G, node, cc):
193
+ component.add(node)
194
+ if len(component) < G.order():
195
+ components.append(component)
196
+ yield from _consolidate(components, k + 1)
197
+
198
+
199
+ def _reconstruct_k_components(k_comps):
200
+ result = {}
201
+ max_k = max(k_comps)
202
+ for k in reversed(range(1, max_k + 1)):
203
+ if k == max_k:
204
+ result[k] = list(_consolidate(k_comps[k], k))
205
+ elif k not in k_comps:
206
+ result[k] = list(_consolidate(result[k + 1], k))
207
+ else:
208
+ nodes_at_k = set.union(*k_comps[k])
209
+ to_add = [c for c in result[k + 1] if any(n not in nodes_at_k for n in c)]
210
+ if to_add:
211
+ result[k] = list(_consolidate(k_comps[k] + to_add, k))
212
+ else:
213
+ result[k] = list(_consolidate(k_comps[k], k))
214
+ return result
215
+
216
+
217
+ def build_k_number_dict(kcomps):
218
+ result = {}
219
+ for k, comps in sorted(kcomps.items(), key=itemgetter(0)):
220
+ for comp in comps:
221
+ for node in comp:
222
+ result[node] = k
223
+ return result
.venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/kcutsets.py ADDED
@@ -0,0 +1,235 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Kanevsky all minimum node k cutsets algorithm.
3
+ """
4
+
5
+ import copy
6
+ from collections import defaultdict
7
+ from itertools import combinations
8
+ from operator import itemgetter
9
+
10
+ import networkx as nx
11
+ from networkx.algorithms.flow import (
12
+ build_residual_network,
13
+ edmonds_karp,
14
+ shortest_augmenting_path,
15
+ )
16
+
17
+ from .utils import build_auxiliary_node_connectivity
18
+
19
+ default_flow_func = edmonds_karp
20
+
21
+
22
+ __all__ = ["all_node_cuts"]
23
+
24
+
25
+ @nx._dispatchable
26
+ def all_node_cuts(G, k=None, flow_func=None):
27
+ r"""Returns all minimum k cutsets of an undirected graph G.
28
+
29
+ This implementation is based on Kanevsky's algorithm [1]_ for finding all
30
+ minimum-size node cut-sets of an undirected graph G; ie the set (or sets)
31
+ of nodes of cardinality equal to the node connectivity of G. Thus if
32
+ removed, would break G into two or more connected components.
33
+
34
+ Parameters
35
+ ----------
36
+ G : NetworkX graph
37
+ Undirected graph
38
+
39
+ k : Integer
40
+ Node connectivity of the input graph. If k is None, then it is
41
+ computed. Default value: None.
42
+
43
+ flow_func : function
44
+ Function to perform the underlying flow computations. Default value is
45
+ :func:`~networkx.algorithms.flow.edmonds_karp`. This function performs
46
+ better in sparse graphs with right tailed degree distributions.
47
+ :func:`~networkx.algorithms.flow.shortest_augmenting_path` will
48
+ perform better in denser graphs.
49
+
50
+
51
+ Returns
52
+ -------
53
+ cuts : a generator of node cutsets
54
+ Each node cutset has cardinality equal to the node connectivity of
55
+ the input graph.
56
+
57
+ Examples
58
+ --------
59
+ >>> # A two-dimensional grid graph has 4 cutsets of cardinality 2
60
+ >>> G = nx.grid_2d_graph(5, 5)
61
+ >>> cutsets = list(nx.all_node_cuts(G))
62
+ >>> len(cutsets)
63
+ 4
64
+ >>> all(2 == len(cutset) for cutset in cutsets)
65
+ True
66
+ >>> nx.node_connectivity(G)
67
+ 2
68
+
69
+ Notes
70
+ -----
71
+ This implementation is based on the sequential algorithm for finding all
72
+ minimum-size separating vertex sets in a graph [1]_. The main idea is to
73
+ compute minimum cuts using local maximum flow computations among a set
74
+ of nodes of highest degree and all other non-adjacent nodes in the Graph.
75
+ Once we find a minimum cut, we add an edge between the high degree
76
+ node and the target node of the local maximum flow computation to make
77
+ sure that we will not find that minimum cut again.
78
+
79
+ See also
80
+ --------
81
+ node_connectivity
82
+ edmonds_karp
83
+ shortest_augmenting_path
84
+
85
+ References
86
+ ----------
87
+ .. [1] Kanevsky, A. (1993). Finding all minimum-size separating vertex
88
+ sets in a graph. Networks 23(6), 533--541.
89
+ http://onlinelibrary.wiley.com/doi/10.1002/net.3230230604/abstract
90
+
91
+ """
92
+ if not nx.is_connected(G):
93
+ raise nx.NetworkXError("Input graph is disconnected.")
94
+
95
+ # Address some corner cases first.
96
+ # For complete Graphs
97
+
98
+ if nx.density(G) == 1:
99
+ yield from ()
100
+ return
101
+
102
+ # Initialize data structures.
103
+ # Keep track of the cuts already computed so we do not repeat them.
104
+ seen = []
105
+ # Even-Tarjan reduction is what we call auxiliary digraph
106
+ # for node connectivity.
107
+ H = build_auxiliary_node_connectivity(G)
108
+ H_nodes = H.nodes # for speed
109
+ mapping = H.graph["mapping"]
110
+ # Keep a copy of original predecessors, H will be modified later.
111
+ # Shallow copy is enough.
112
+ original_H_pred = copy.copy(H._pred)
113
+ R = build_residual_network(H, "capacity")
114
+ kwargs = {"capacity": "capacity", "residual": R}
115
+ # Define default flow function
116
+ if flow_func is None:
117
+ flow_func = default_flow_func
118
+ if flow_func is shortest_augmenting_path:
119
+ kwargs["two_phase"] = True
120
+ # Begin the actual algorithm
121
+ # step 1: Find node connectivity k of G
122
+ if k is None:
123
+ k = nx.node_connectivity(G, flow_func=flow_func)
124
+ # step 2:
125
+ # Find k nodes with top degree, call it X:
126
+ X = {n for n, d in sorted(G.degree(), key=itemgetter(1), reverse=True)[:k]}
127
+ # Check if X is a k-node-cutset
128
+ if _is_separating_set(G, X):
129
+ seen.append(X)
130
+ yield X
131
+
132
+ for x in X:
133
+ # step 3: Compute local connectivity flow of x with all other
134
+ # non adjacent nodes in G
135
+ non_adjacent = set(G) - {x} - set(G[x])
136
+ for v in non_adjacent:
137
+ # step 4: compute maximum flow in an Even-Tarjan reduction H of G
138
+ # and step 5: build the associated residual network R
139
+ R = flow_func(H, f"{mapping[x]}B", f"{mapping[v]}A", **kwargs)
140
+ flow_value = R.graph["flow_value"]
141
+
142
+ if flow_value == k:
143
+ # Find the nodes incident to the flow.
144
+ E1 = flowed_edges = [
145
+ (u, w) for (u, w, d) in R.edges(data=True) if d["flow"] != 0
146
+ ]
147
+ VE1 = incident_nodes = {n for edge in E1 for n in edge}
148
+ # Remove saturated edges form the residual network.
149
+ # Note that reversed edges are introduced with capacity 0
150
+ # in the residual graph and they need to be removed too.
151
+ saturated_edges = [
152
+ (u, w, d)
153
+ for (u, w, d) in R.edges(data=True)
154
+ if d["capacity"] == d["flow"] or d["capacity"] == 0
155
+ ]
156
+ R.remove_edges_from(saturated_edges)
157
+ R_closure = nx.transitive_closure(R)
158
+ # step 6: shrink the strongly connected components of
159
+ # residual flow network R and call it L.
160
+ L = nx.condensation(R)
161
+ cmap = L.graph["mapping"]
162
+ inv_cmap = defaultdict(list)
163
+ for n, scc in cmap.items():
164
+ inv_cmap[scc].append(n)
165
+ # Find the incident nodes in the condensed graph.
166
+ VE1 = {cmap[n] for n in VE1}
167
+ # step 7: Compute all antichains of L;
168
+ # they map to closed sets in H.
169
+ # Any edge in H that links a closed set is part of a cutset.
170
+ for antichain in nx.antichains(L):
171
+ # Only antichains that are subsets of incident nodes counts.
172
+ # Lemma 8 in reference.
173
+ if not set(antichain).issubset(VE1):
174
+ continue
175
+ # Nodes in an antichain of the condensation graph of
176
+ # the residual network map to a closed set of nodes that
177
+ # define a node partition of the auxiliary digraph H
178
+ # through taking all of antichain's predecessors in the
179
+ # transitive closure.
180
+ S = set()
181
+ for scc in antichain:
182
+ S.update(inv_cmap[scc])
183
+ S_ancestors = set()
184
+ for n in S:
185
+ S_ancestors.update(R_closure._pred[n])
186
+ S.update(S_ancestors)
187
+ if f"{mapping[x]}B" not in S or f"{mapping[v]}A" in S:
188
+ continue
189
+ # Find the cutset that links the node partition (S,~S) in H
190
+ cutset = set()
191
+ for u in S:
192
+ cutset.update((u, w) for w in original_H_pred[u] if w not in S)
193
+ # The edges in H that form the cutset are internal edges
194
+ # (ie edges that represent a node of the original graph G)
195
+ if any(H_nodes[u]["id"] != H_nodes[w]["id"] for u, w in cutset):
196
+ continue
197
+ node_cut = {H_nodes[u]["id"] for u, _ in cutset}
198
+
199
+ if len(node_cut) == k:
200
+ # The cut is invalid if it includes internal edges of
201
+ # end nodes. The other half of Lemma 8 in ref.
202
+ if x in node_cut or v in node_cut:
203
+ continue
204
+ if node_cut not in seen:
205
+ yield node_cut
206
+ seen.append(node_cut)
207
+
208
+ # Add an edge (x, v) to make sure that we do not
209
+ # find this cutset again. This is equivalent
210
+ # of adding the edge in the input graph
211
+ # G.add_edge(x, v) and then regenerate H and R:
212
+ # Add edges to the auxiliary digraph.
213
+ # See build_residual_network for convention we used
214
+ # in residual graphs.
215
+ H.add_edge(f"{mapping[x]}B", f"{mapping[v]}A", capacity=1)
216
+ H.add_edge(f"{mapping[v]}B", f"{mapping[x]}A", capacity=1)
217
+ # Add edges to the residual network.
218
+ R.add_edge(f"{mapping[x]}B", f"{mapping[v]}A", capacity=1)
219
+ R.add_edge(f"{mapping[v]}A", f"{mapping[x]}B", capacity=0)
220
+ R.add_edge(f"{mapping[v]}B", f"{mapping[x]}A", capacity=1)
221
+ R.add_edge(f"{mapping[x]}A", f"{mapping[v]}B", capacity=0)
222
+
223
+ # Add again the saturated edges to reuse the residual network
224
+ R.add_edges_from(saturated_edges)
225
+
226
+
227
+ def _is_separating_set(G, cut):
228
+ """Assumes that the input graph is connected"""
229
+ if len(cut) == len(G) - 1:
230
+ return True
231
+
232
+ H = nx.restricted_view(G, cut, [])
233
+ if nx.is_connected(H):
234
+ return False
235
+ return True
.venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/stoerwagner.py ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Stoer-Wagner minimum cut algorithm.
3
+ """
4
+
5
+ from itertools import islice
6
+
7
+ import networkx as nx
8
+
9
+ from ...utils import BinaryHeap, arbitrary_element, not_implemented_for
10
+
11
+ __all__ = ["stoer_wagner"]
12
+
13
+
14
+ @not_implemented_for("directed")
15
+ @not_implemented_for("multigraph")
16
+ @nx._dispatchable(edge_attrs="weight")
17
+ def stoer_wagner(G, weight="weight", heap=BinaryHeap):
18
+ r"""Returns the weighted minimum edge cut using the Stoer-Wagner algorithm.
19
+
20
+ Determine the minimum edge cut of a connected graph using the
21
+ Stoer-Wagner algorithm. In weighted cases, all weights must be
22
+ nonnegative.
23
+
24
+ The running time of the algorithm depends on the type of heaps used:
25
+
26
+ ============== =============================================
27
+ Type of heap Running time
28
+ ============== =============================================
29
+ Binary heap $O(n (m + n) \log n)$
30
+ Fibonacci heap $O(nm + n^2 \log n)$
31
+ Pairing heap $O(2^{2 \sqrt{\log \log n}} nm + n^2 \log n)$
32
+ ============== =============================================
33
+
34
+ Parameters
35
+ ----------
36
+ G : NetworkX graph
37
+ Edges of the graph are expected to have an attribute named by the
38
+ weight parameter below. If this attribute is not present, the edge is
39
+ considered to have unit weight.
40
+
41
+ weight : string
42
+ Name of the weight attribute of the edges. If the attribute is not
43
+ present, unit weight is assumed. Default value: 'weight'.
44
+
45
+ heap : class
46
+ Type of heap to be used in the algorithm. It should be a subclass of
47
+ :class:`MinHeap` or implement a compatible interface.
48
+
49
+ If a stock heap implementation is to be used, :class:`BinaryHeap` is
50
+ recommended over :class:`PairingHeap` for Python implementations without
51
+ optimized attribute accesses (e.g., CPython) despite a slower
52
+ asymptotic running time. For Python implementations with optimized
53
+ attribute accesses (e.g., PyPy), :class:`PairingHeap` provides better
54
+ performance. Default value: :class:`BinaryHeap`.
55
+
56
+ Returns
57
+ -------
58
+ cut_value : integer or float
59
+ The sum of weights of edges in a minimum cut.
60
+
61
+ partition : pair of node lists
62
+ A partitioning of the nodes that defines a minimum cut.
63
+
64
+ Raises
65
+ ------
66
+ NetworkXNotImplemented
67
+ If the graph is directed or a multigraph.
68
+
69
+ NetworkXError
70
+ If the graph has less than two nodes, is not connected or has a
71
+ negative-weighted edge.
72
+
73
+ Examples
74
+ --------
75
+ >>> G = nx.Graph()
76
+ >>> G.add_edge("x", "a", weight=3)
77
+ >>> G.add_edge("x", "b", weight=1)
78
+ >>> G.add_edge("a", "c", weight=3)
79
+ >>> G.add_edge("b", "c", weight=5)
80
+ >>> G.add_edge("b", "d", weight=4)
81
+ >>> G.add_edge("d", "e", weight=2)
82
+ >>> G.add_edge("c", "y", weight=2)
83
+ >>> G.add_edge("e", "y", weight=3)
84
+ >>> cut_value, partition = nx.stoer_wagner(G)
85
+ >>> cut_value
86
+ 4
87
+ """
88
+ n = len(G)
89
+ if n < 2:
90
+ raise nx.NetworkXError("graph has less than two nodes.")
91
+ if not nx.is_connected(G):
92
+ raise nx.NetworkXError("graph is not connected.")
93
+
94
+ # Make a copy of the graph for internal use.
95
+ G = nx.Graph(
96
+ (u, v, {"weight": e.get(weight, 1)}) for u, v, e in G.edges(data=True) if u != v
97
+ )
98
+ G.__networkx_cache__ = None # Disable caching
99
+
100
+ for u, v, e in G.edges(data=True):
101
+ if e["weight"] < 0:
102
+ raise nx.NetworkXError("graph has a negative-weighted edge.")
103
+
104
+ cut_value = float("inf")
105
+ nodes = set(G)
106
+ contractions = [] # contracted node pairs
107
+
108
+ # Repeatedly pick a pair of nodes to contract until only one node is left.
109
+ for i in range(n - 1):
110
+ # Pick an arbitrary node u and create a set A = {u}.
111
+ u = arbitrary_element(G)
112
+ A = {u}
113
+ # Repeatedly pick the node "most tightly connected" to A and add it to
114
+ # A. The tightness of connectivity of a node not in A is defined by the
115
+ # of edges connecting it to nodes in A.
116
+ h = heap() # min-heap emulating a max-heap
117
+ for v, e in G[u].items():
118
+ h.insert(v, -e["weight"])
119
+ # Repeat until all but one node has been added to A.
120
+ for j in range(n - i - 2):
121
+ u = h.pop()[0]
122
+ A.add(u)
123
+ for v, e in G[u].items():
124
+ if v not in A:
125
+ h.insert(v, h.get(v, 0) - e["weight"])
126
+ # A and the remaining node v define a "cut of the phase". There is a
127
+ # minimum cut of the original graph that is also a cut of the phase.
128
+ # Due to contractions in earlier phases, v may in fact represent
129
+ # multiple nodes in the original graph.
130
+ v, w = h.min()
131
+ w = -w
132
+ if w < cut_value:
133
+ cut_value = w
134
+ best_phase = i
135
+ # Contract v and the last node added to A.
136
+ contractions.append((u, v))
137
+ for w, e in G[v].items():
138
+ if w != u:
139
+ if w not in G[u]:
140
+ G.add_edge(u, w, weight=e["weight"])
141
+ else:
142
+ G[u][w]["weight"] += e["weight"]
143
+ G.remove_node(v)
144
+
145
+ # Recover the optimal partitioning from the contractions.
146
+ G = nx.Graph(islice(contractions, best_phase))
147
+ v = contractions[best_phase][1]
148
+ G.add_node(v)
149
+ reachable = set(nx.single_source_shortest_path_length(G, v))
150
+ partition = (list(reachable), list(nodes - reachable))
151
+
152
+ return cut_value, partition
.venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/tests/__init__.py ADDED
File without changes
.venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/tests/__pycache__/test_connectivity.cpython-311.pyc ADDED
Binary file (29.7 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/tests/__pycache__/test_cuts.cpython-311.pyc ADDED
Binary file (18.1 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/tests/__pycache__/test_disjoint_paths.cpython-311.pyc ADDED
Binary file (16.7 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/tests/__pycache__/test_edge_augmentation.cpython-311.pyc ADDED
Binary file (25.5 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/tests/__pycache__/test_edge_kcomponents.cpython-311.pyc ADDED
Binary file (26 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/tests/__pycache__/test_kcutsets.cpython-311.pyc ADDED
Binary file (15.8 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/tests/test_connectivity.py ADDED
@@ -0,0 +1,421 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import itertools
2
+
3
+ import pytest
4
+
5
+ import networkx as nx
6
+ from networkx.algorithms import flow
7
+ from networkx.algorithms.connectivity import (
8
+ local_edge_connectivity,
9
+ local_node_connectivity,
10
+ )
11
+
12
+ flow_funcs = [
13
+ flow.boykov_kolmogorov,
14
+ flow.dinitz,
15
+ flow.edmonds_karp,
16
+ flow.preflow_push,
17
+ flow.shortest_augmenting_path,
18
+ ]
19
+
20
+
21
+ # helper functions for tests
22
+
23
+
24
+ def _generate_no_biconnected(max_attempts=50):
25
+ attempts = 0
26
+ while True:
27
+ G = nx.fast_gnp_random_graph(100, 0.0575, seed=42)
28
+ if nx.is_connected(G) and not nx.is_biconnected(G):
29
+ attempts = 0
30
+ yield G
31
+ else:
32
+ if attempts >= max_attempts:
33
+ msg = f"Tried {max_attempts} times: no suitable Graph."
34
+ raise Exception(msg)
35
+ else:
36
+ attempts += 1
37
+
38
+
39
+ def test_average_connectivity():
40
+ # figure 1 from:
41
+ # Beineke, L., O. Oellermann, and R. Pippert (2002). The average
42
+ # connectivity of a graph. Discrete mathematics 252(1-3), 31-45
43
+ # http://www.sciencedirect.com/science/article/pii/S0012365X01001807
44
+ G1 = nx.path_graph(3)
45
+ G1.add_edges_from([(1, 3), (1, 4)])
46
+ G2 = nx.path_graph(3)
47
+ G2.add_edges_from([(1, 3), (1, 4), (0, 3), (0, 4), (3, 4)])
48
+ G3 = nx.Graph()
49
+ for flow_func in flow_funcs:
50
+ kwargs = {"flow_func": flow_func}
51
+ errmsg = f"Assertion failed in function: {flow_func.__name__}"
52
+ assert nx.average_node_connectivity(G1, **kwargs) == 1, errmsg
53
+ assert nx.average_node_connectivity(G2, **kwargs) == 2.2, errmsg
54
+ assert nx.average_node_connectivity(G3, **kwargs) == 0, errmsg
55
+
56
+
57
+ def test_average_connectivity_directed():
58
+ G = nx.DiGraph([(1, 3), (1, 4), (1, 5)])
59
+ for flow_func in flow_funcs:
60
+ errmsg = f"Assertion failed in function: {flow_func.__name__}"
61
+ assert nx.average_node_connectivity(G) == 0.25, errmsg
62
+
63
+
64
+ def test_articulation_points():
65
+ Ggen = _generate_no_biconnected()
66
+ for flow_func in flow_funcs:
67
+ for i in range(3):
68
+ G = next(Ggen)
69
+ errmsg = f"Assertion failed in function: {flow_func.__name__}"
70
+ assert nx.node_connectivity(G, flow_func=flow_func) == 1, errmsg
71
+
72
+
73
+ def test_brandes_erlebach():
74
+ # Figure 1 chapter 7: Connectivity
75
+ # http://www.informatik.uni-augsburg.de/thi/personen/kammer/Graph_Connectivity.pdf
76
+ G = nx.Graph()
77
+ G.add_edges_from(
78
+ [
79
+ (1, 2),
80
+ (1, 3),
81
+ (1, 4),
82
+ (1, 5),
83
+ (2, 3),
84
+ (2, 6),
85
+ (3, 4),
86
+ (3, 6),
87
+ (4, 6),
88
+ (4, 7),
89
+ (5, 7),
90
+ (6, 8),
91
+ (6, 9),
92
+ (7, 8),
93
+ (7, 10),
94
+ (8, 11),
95
+ (9, 10),
96
+ (9, 11),
97
+ (10, 11),
98
+ ]
99
+ )
100
+ for flow_func in flow_funcs:
101
+ kwargs = {"flow_func": flow_func}
102
+ errmsg = f"Assertion failed in function: {flow_func.__name__}"
103
+ assert 3 == local_edge_connectivity(G, 1, 11, **kwargs), errmsg
104
+ assert 3 == nx.edge_connectivity(G, 1, 11, **kwargs), errmsg
105
+ assert 2 == local_node_connectivity(G, 1, 11, **kwargs), errmsg
106
+ assert 2 == nx.node_connectivity(G, 1, 11, **kwargs), errmsg
107
+ assert 2 == nx.edge_connectivity(G, **kwargs), errmsg
108
+ assert 2 == nx.node_connectivity(G, **kwargs), errmsg
109
+ if flow_func is flow.preflow_push:
110
+ assert 3 == nx.edge_connectivity(G, 1, 11, cutoff=2, **kwargs), errmsg
111
+ else:
112
+ assert 2 == nx.edge_connectivity(G, 1, 11, cutoff=2, **kwargs), errmsg
113
+
114
+
115
+ def test_white_harary_1():
116
+ # Figure 1b white and harary (2001)
117
+ # https://doi.org/10.1111/0081-1750.00098
118
+ # A graph with high adhesion (edge connectivity) and low cohesion
119
+ # (vertex connectivity)
120
+ G = nx.disjoint_union(nx.complete_graph(4), nx.complete_graph(4))
121
+ G.remove_node(7)
122
+ for i in range(4, 7):
123
+ G.add_edge(0, i)
124
+ G = nx.disjoint_union(G, nx.complete_graph(4))
125
+ G.remove_node(G.order() - 1)
126
+ for i in range(7, 10):
127
+ G.add_edge(0, i)
128
+ for flow_func in flow_funcs:
129
+ errmsg = f"Assertion failed in function: {flow_func.__name__}"
130
+ assert 1 == nx.node_connectivity(G, flow_func=flow_func), errmsg
131
+ assert 3 == nx.edge_connectivity(G, flow_func=flow_func), errmsg
132
+
133
+
134
+ def test_white_harary_2():
135
+ # Figure 8 white and harary (2001)
136
+ # https://doi.org/10.1111/0081-1750.00098
137
+ G = nx.disjoint_union(nx.complete_graph(4), nx.complete_graph(4))
138
+ G.add_edge(0, 4)
139
+ # kappa <= lambda <= delta
140
+ assert 3 == min(nx.core_number(G).values())
141
+ for flow_func in flow_funcs:
142
+ errmsg = f"Assertion failed in function: {flow_func.__name__}"
143
+ assert 1 == nx.node_connectivity(G, flow_func=flow_func), errmsg
144
+ assert 1 == nx.edge_connectivity(G, flow_func=flow_func), errmsg
145
+
146
+
147
+ def test_complete_graphs():
148
+ for n in range(5, 20, 5):
149
+ for flow_func in flow_funcs:
150
+ G = nx.complete_graph(n)
151
+ errmsg = f"Assertion failed in function: {flow_func.__name__}"
152
+ assert n - 1 == nx.node_connectivity(G, flow_func=flow_func), errmsg
153
+ assert n - 1 == nx.node_connectivity(
154
+ G.to_directed(), flow_func=flow_func
155
+ ), errmsg
156
+ assert n - 1 == nx.edge_connectivity(G, flow_func=flow_func), errmsg
157
+ assert n - 1 == nx.edge_connectivity(
158
+ G.to_directed(), flow_func=flow_func
159
+ ), errmsg
160
+
161
+
162
+ def test_empty_graphs():
163
+ for k in range(5, 25, 5):
164
+ G = nx.empty_graph(k)
165
+ for flow_func in flow_funcs:
166
+ errmsg = f"Assertion failed in function: {flow_func.__name__}"
167
+ assert 0 == nx.node_connectivity(G, flow_func=flow_func), errmsg
168
+ assert 0 == nx.edge_connectivity(G, flow_func=flow_func), errmsg
169
+
170
+
171
+ def test_petersen():
172
+ G = nx.petersen_graph()
173
+ for flow_func in flow_funcs:
174
+ errmsg = f"Assertion failed in function: {flow_func.__name__}"
175
+ assert 3 == nx.node_connectivity(G, flow_func=flow_func), errmsg
176
+ assert 3 == nx.edge_connectivity(G, flow_func=flow_func), errmsg
177
+
178
+
179
+ def test_tutte():
180
+ G = nx.tutte_graph()
181
+ for flow_func in flow_funcs:
182
+ errmsg = f"Assertion failed in function: {flow_func.__name__}"
183
+ assert 3 == nx.node_connectivity(G, flow_func=flow_func), errmsg
184
+ assert 3 == nx.edge_connectivity(G, flow_func=flow_func), errmsg
185
+
186
+
187
+ def test_dodecahedral():
188
+ G = nx.dodecahedral_graph()
189
+ for flow_func in flow_funcs:
190
+ errmsg = f"Assertion failed in function: {flow_func.__name__}"
191
+ assert 3 == nx.node_connectivity(G, flow_func=flow_func), errmsg
192
+ assert 3 == nx.edge_connectivity(G, flow_func=flow_func), errmsg
193
+
194
+
195
+ def test_octahedral():
196
+ G = nx.octahedral_graph()
197
+ for flow_func in flow_funcs:
198
+ errmsg = f"Assertion failed in function: {flow_func.__name__}"
199
+ assert 4 == nx.node_connectivity(G, flow_func=flow_func), errmsg
200
+ assert 4 == nx.edge_connectivity(G, flow_func=flow_func), errmsg
201
+
202
+
203
+ def test_icosahedral():
204
+ G = nx.icosahedral_graph()
205
+ for flow_func in flow_funcs:
206
+ errmsg = f"Assertion failed in function: {flow_func.__name__}"
207
+ assert 5 == nx.node_connectivity(G, flow_func=flow_func), errmsg
208
+ assert 5 == nx.edge_connectivity(G, flow_func=flow_func), errmsg
209
+
210
+
211
+ def test_missing_source():
212
+ G = nx.path_graph(4)
213
+ for flow_func in flow_funcs:
214
+ pytest.raises(
215
+ nx.NetworkXError, nx.node_connectivity, G, 10, 1, flow_func=flow_func
216
+ )
217
+
218
+
219
+ def test_missing_target():
220
+ G = nx.path_graph(4)
221
+ for flow_func in flow_funcs:
222
+ pytest.raises(
223
+ nx.NetworkXError, nx.node_connectivity, G, 1, 10, flow_func=flow_func
224
+ )
225
+
226
+
227
+ def test_edge_missing_source():
228
+ G = nx.path_graph(4)
229
+ for flow_func in flow_funcs:
230
+ pytest.raises(
231
+ nx.NetworkXError, nx.edge_connectivity, G, 10, 1, flow_func=flow_func
232
+ )
233
+
234
+
235
+ def test_edge_missing_target():
236
+ G = nx.path_graph(4)
237
+ for flow_func in flow_funcs:
238
+ pytest.raises(
239
+ nx.NetworkXError, nx.edge_connectivity, G, 1, 10, flow_func=flow_func
240
+ )
241
+
242
+
243
+ def test_not_weakly_connected():
244
+ G = nx.DiGraph()
245
+ nx.add_path(G, [1, 2, 3])
246
+ nx.add_path(G, [4, 5])
247
+ for flow_func in flow_funcs:
248
+ errmsg = f"Assertion failed in function: {flow_func.__name__}"
249
+ assert nx.node_connectivity(G) == 0, errmsg
250
+ assert nx.edge_connectivity(G) == 0, errmsg
251
+
252
+
253
+ def test_not_connected():
254
+ G = nx.Graph()
255
+ nx.add_path(G, [1, 2, 3])
256
+ nx.add_path(G, [4, 5])
257
+ for flow_func in flow_funcs:
258
+ errmsg = f"Assertion failed in function: {flow_func.__name__}"
259
+ assert nx.node_connectivity(G) == 0, errmsg
260
+ assert nx.edge_connectivity(G) == 0, errmsg
261
+
262
+
263
+ def test_directed_edge_connectivity():
264
+ G = nx.cycle_graph(10, create_using=nx.DiGraph()) # only one direction
265
+ D = nx.cycle_graph(10).to_directed() # 2 reciprocal edges
266
+ for flow_func in flow_funcs:
267
+ errmsg = f"Assertion failed in function: {flow_func.__name__}"
268
+ assert 1 == nx.edge_connectivity(G, flow_func=flow_func), errmsg
269
+ assert 1 == local_edge_connectivity(G, 1, 4, flow_func=flow_func), errmsg
270
+ assert 1 == nx.edge_connectivity(G, 1, 4, flow_func=flow_func), errmsg
271
+ assert 2 == nx.edge_connectivity(D, flow_func=flow_func), errmsg
272
+ assert 2 == local_edge_connectivity(D, 1, 4, flow_func=flow_func), errmsg
273
+ assert 2 == nx.edge_connectivity(D, 1, 4, flow_func=flow_func), errmsg
274
+
275
+
276
+ def test_cutoff():
277
+ G = nx.complete_graph(5)
278
+ for local_func in [local_edge_connectivity, local_node_connectivity]:
279
+ for flow_func in flow_funcs:
280
+ if flow_func is flow.preflow_push:
281
+ # cutoff is not supported by preflow_push
282
+ continue
283
+ for cutoff in [3, 2, 1]:
284
+ result = local_func(G, 0, 4, flow_func=flow_func, cutoff=cutoff)
285
+ assert cutoff == result, f"cutoff error in {flow_func.__name__}"
286
+
287
+
288
+ def test_invalid_auxiliary():
289
+ G = nx.complete_graph(5)
290
+ pytest.raises(nx.NetworkXError, local_node_connectivity, G, 0, 3, auxiliary=G)
291
+
292
+
293
+ def test_interface_only_source():
294
+ G = nx.complete_graph(5)
295
+ for interface_func in [nx.node_connectivity, nx.edge_connectivity]:
296
+ pytest.raises(nx.NetworkXError, interface_func, G, s=0)
297
+
298
+
299
+ def test_interface_only_target():
300
+ G = nx.complete_graph(5)
301
+ for interface_func in [nx.node_connectivity, nx.edge_connectivity]:
302
+ pytest.raises(nx.NetworkXError, interface_func, G, t=3)
303
+
304
+
305
+ def test_edge_connectivity_flow_vs_stoer_wagner():
306
+ graph_funcs = [nx.icosahedral_graph, nx.octahedral_graph, nx.dodecahedral_graph]
307
+ for graph_func in graph_funcs:
308
+ G = graph_func()
309
+ assert nx.stoer_wagner(G)[0] == nx.edge_connectivity(G)
310
+
311
+
312
+ class TestAllPairsNodeConnectivity:
313
+ @classmethod
314
+ def setup_class(cls):
315
+ cls.path = nx.path_graph(7)
316
+ cls.directed_path = nx.path_graph(7, create_using=nx.DiGraph())
317
+ cls.cycle = nx.cycle_graph(7)
318
+ cls.directed_cycle = nx.cycle_graph(7, create_using=nx.DiGraph())
319
+ cls.gnp = nx.gnp_random_graph(30, 0.1, seed=42)
320
+ cls.directed_gnp = nx.gnp_random_graph(30, 0.1, directed=True, seed=42)
321
+ cls.K20 = nx.complete_graph(20)
322
+ cls.K10 = nx.complete_graph(10)
323
+ cls.K5 = nx.complete_graph(5)
324
+ cls.G_list = [
325
+ cls.path,
326
+ cls.directed_path,
327
+ cls.cycle,
328
+ cls.directed_cycle,
329
+ cls.gnp,
330
+ cls.directed_gnp,
331
+ cls.K10,
332
+ cls.K5,
333
+ cls.K20,
334
+ ]
335
+
336
+ def test_cycles(self):
337
+ K_undir = nx.all_pairs_node_connectivity(self.cycle)
338
+ for source in K_undir:
339
+ for target, k in K_undir[source].items():
340
+ assert k == 2
341
+ K_dir = nx.all_pairs_node_connectivity(self.directed_cycle)
342
+ for source in K_dir:
343
+ for target, k in K_dir[source].items():
344
+ assert k == 1
345
+
346
+ def test_complete(self):
347
+ for G in [self.K10, self.K5, self.K20]:
348
+ K = nx.all_pairs_node_connectivity(G)
349
+ for source in K:
350
+ for target, k in K[source].items():
351
+ assert k == len(G) - 1
352
+
353
+ def test_paths(self):
354
+ K_undir = nx.all_pairs_node_connectivity(self.path)
355
+ for source in K_undir:
356
+ for target, k in K_undir[source].items():
357
+ assert k == 1
358
+ K_dir = nx.all_pairs_node_connectivity(self.directed_path)
359
+ for source in K_dir:
360
+ for target, k in K_dir[source].items():
361
+ if source < target:
362
+ assert k == 1
363
+ else:
364
+ assert k == 0
365
+
366
+ def test_all_pairs_connectivity_nbunch(self):
367
+ G = nx.complete_graph(5)
368
+ nbunch = [0, 2, 3]
369
+ C = nx.all_pairs_node_connectivity(G, nbunch=nbunch)
370
+ assert len(C) == len(nbunch)
371
+
372
+ def test_all_pairs_connectivity_icosahedral(self):
373
+ G = nx.icosahedral_graph()
374
+ C = nx.all_pairs_node_connectivity(G)
375
+ assert all(5 == C[u][v] for u, v in itertools.combinations(G, 2))
376
+
377
+ def test_all_pairs_connectivity(self):
378
+ G = nx.Graph()
379
+ nodes = [0, 1, 2, 3]
380
+ nx.add_path(G, nodes)
381
+ A = {n: {} for n in G}
382
+ for u, v in itertools.combinations(nodes, 2):
383
+ A[u][v] = A[v][u] = nx.node_connectivity(G, u, v)
384
+ C = nx.all_pairs_node_connectivity(G)
385
+ assert sorted((k, sorted(v)) for k, v in A.items()) == sorted(
386
+ (k, sorted(v)) for k, v in C.items()
387
+ )
388
+
389
+ def test_all_pairs_connectivity_directed(self):
390
+ G = nx.DiGraph()
391
+ nodes = [0, 1, 2, 3]
392
+ nx.add_path(G, nodes)
393
+ A = {n: {} for n in G}
394
+ for u, v in itertools.permutations(nodes, 2):
395
+ A[u][v] = nx.node_connectivity(G, u, v)
396
+ C = nx.all_pairs_node_connectivity(G)
397
+ assert sorted((k, sorted(v)) for k, v in A.items()) == sorted(
398
+ (k, sorted(v)) for k, v in C.items()
399
+ )
400
+
401
+ def test_all_pairs_connectivity_nbunch_combinations(self):
402
+ G = nx.complete_graph(5)
403
+ nbunch = [0, 2, 3]
404
+ A = {n: {} for n in nbunch}
405
+ for u, v in itertools.combinations(nbunch, 2):
406
+ A[u][v] = A[v][u] = nx.node_connectivity(G, u, v)
407
+ C = nx.all_pairs_node_connectivity(G, nbunch=nbunch)
408
+ assert sorted((k, sorted(v)) for k, v in A.items()) == sorted(
409
+ (k, sorted(v)) for k, v in C.items()
410
+ )
411
+
412
+ def test_all_pairs_connectivity_nbunch_iter(self):
413
+ G = nx.complete_graph(5)
414
+ nbunch = [0, 2, 3]
415
+ A = {n: {} for n in nbunch}
416
+ for u, v in itertools.combinations(nbunch, 2):
417
+ A[u][v] = A[v][u] = nx.node_connectivity(G, u, v)
418
+ C = nx.all_pairs_node_connectivity(G, nbunch=iter(nbunch))
419
+ assert sorted((k, sorted(v)) for k, v in A.items()) == sorted(
420
+ (k, sorted(v)) for k, v in C.items()
421
+ )
.venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/tests/test_cuts.py ADDED
@@ -0,0 +1,309 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+
3
+ import networkx as nx
4
+ from networkx.algorithms import flow
5
+ from networkx.algorithms.connectivity import minimum_st_edge_cut, minimum_st_node_cut
6
+ from networkx.utils import arbitrary_element
7
+
8
+ flow_funcs = [
9
+ flow.boykov_kolmogorov,
10
+ flow.dinitz,
11
+ flow.edmonds_karp,
12
+ flow.preflow_push,
13
+ flow.shortest_augmenting_path,
14
+ ]
15
+
16
+ # Tests for node and edge cutsets
17
+
18
+
19
+ def _generate_no_biconnected(max_attempts=50):
20
+ attempts = 0
21
+ while True:
22
+ G = nx.fast_gnp_random_graph(100, 0.0575, seed=42)
23
+ if nx.is_connected(G) and not nx.is_biconnected(G):
24
+ attempts = 0
25
+ yield G
26
+ else:
27
+ if attempts >= max_attempts:
28
+ msg = f"Tried {attempts} times: no suitable Graph."
29
+ raise Exception(msg)
30
+ else:
31
+ attempts += 1
32
+
33
+
34
+ def test_articulation_points():
35
+ Ggen = _generate_no_biconnected()
36
+ for flow_func in flow_funcs:
37
+ errmsg = f"Assertion failed in function: {flow_func.__name__}"
38
+ for i in range(1): # change 1 to 3 or more for more realizations.
39
+ G = next(Ggen)
40
+ cut = nx.minimum_node_cut(G, flow_func=flow_func)
41
+ assert len(cut) == 1, errmsg
42
+ assert cut.pop() in set(nx.articulation_points(G)), errmsg
43
+
44
+
45
+ def test_brandes_erlebach_book():
46
+ # Figure 1 chapter 7: Connectivity
47
+ # http://www.informatik.uni-augsburg.de/thi/personen/kammer/Graph_Connectivity.pdf
48
+ G = nx.Graph()
49
+ G.add_edges_from(
50
+ [
51
+ (1, 2),
52
+ (1, 3),
53
+ (1, 4),
54
+ (1, 5),
55
+ (2, 3),
56
+ (2, 6),
57
+ (3, 4),
58
+ (3, 6),
59
+ (4, 6),
60
+ (4, 7),
61
+ (5, 7),
62
+ (6, 8),
63
+ (6, 9),
64
+ (7, 8),
65
+ (7, 10),
66
+ (8, 11),
67
+ (9, 10),
68
+ (9, 11),
69
+ (10, 11),
70
+ ]
71
+ )
72
+ for flow_func in flow_funcs:
73
+ kwargs = {"flow_func": flow_func}
74
+ errmsg = f"Assertion failed in function: {flow_func.__name__}"
75
+ # edge cutsets
76
+ assert 3 == len(nx.minimum_edge_cut(G, 1, 11, **kwargs)), errmsg
77
+ edge_cut = nx.minimum_edge_cut(G, **kwargs)
78
+ # Node 5 has only two edges
79
+ assert 2 == len(edge_cut), errmsg
80
+ H = G.copy()
81
+ H.remove_edges_from(edge_cut)
82
+ assert not nx.is_connected(H), errmsg
83
+ # node cuts
84
+ assert {6, 7} == minimum_st_node_cut(G, 1, 11, **kwargs), errmsg
85
+ assert {6, 7} == nx.minimum_node_cut(G, 1, 11, **kwargs), errmsg
86
+ node_cut = nx.minimum_node_cut(G, **kwargs)
87
+ assert 2 == len(node_cut), errmsg
88
+ H = G.copy()
89
+ H.remove_nodes_from(node_cut)
90
+ assert not nx.is_connected(H), errmsg
91
+
92
+
93
+ def test_white_harary_paper():
94
+ # Figure 1b white and harary (2001)
95
+ # https://doi.org/10.1111/0081-1750.00098
96
+ # A graph with high adhesion (edge connectivity) and low cohesion
97
+ # (node connectivity)
98
+ G = nx.disjoint_union(nx.complete_graph(4), nx.complete_graph(4))
99
+ G.remove_node(7)
100
+ for i in range(4, 7):
101
+ G.add_edge(0, i)
102
+ G = nx.disjoint_union(G, nx.complete_graph(4))
103
+ G.remove_node(G.order() - 1)
104
+ for i in range(7, 10):
105
+ G.add_edge(0, i)
106
+ for flow_func in flow_funcs:
107
+ kwargs = {"flow_func": flow_func}
108
+ errmsg = f"Assertion failed in function: {flow_func.__name__}"
109
+ # edge cuts
110
+ edge_cut = nx.minimum_edge_cut(G, **kwargs)
111
+ assert 3 == len(edge_cut), errmsg
112
+ H = G.copy()
113
+ H.remove_edges_from(edge_cut)
114
+ assert not nx.is_connected(H), errmsg
115
+ # node cuts
116
+ node_cut = nx.minimum_node_cut(G, **kwargs)
117
+ assert {0} == node_cut, errmsg
118
+ H = G.copy()
119
+ H.remove_nodes_from(node_cut)
120
+ assert not nx.is_connected(H), errmsg
121
+
122
+
123
+ def test_petersen_cutset():
124
+ G = nx.petersen_graph()
125
+ for flow_func in flow_funcs:
126
+ kwargs = {"flow_func": flow_func}
127
+ errmsg = f"Assertion failed in function: {flow_func.__name__}"
128
+ # edge cuts
129
+ edge_cut = nx.minimum_edge_cut(G, **kwargs)
130
+ assert 3 == len(edge_cut), errmsg
131
+ H = G.copy()
132
+ H.remove_edges_from(edge_cut)
133
+ assert not nx.is_connected(H), errmsg
134
+ # node cuts
135
+ node_cut = nx.minimum_node_cut(G, **kwargs)
136
+ assert 3 == len(node_cut), errmsg
137
+ H = G.copy()
138
+ H.remove_nodes_from(node_cut)
139
+ assert not nx.is_connected(H), errmsg
140
+
141
+
142
+ def test_octahedral_cutset():
143
+ G = nx.octahedral_graph()
144
+ for flow_func in flow_funcs:
145
+ kwargs = {"flow_func": flow_func}
146
+ errmsg = f"Assertion failed in function: {flow_func.__name__}"
147
+ # edge cuts
148
+ edge_cut = nx.minimum_edge_cut(G, **kwargs)
149
+ assert 4 == len(edge_cut), errmsg
150
+ H = G.copy()
151
+ H.remove_edges_from(edge_cut)
152
+ assert not nx.is_connected(H), errmsg
153
+ # node cuts
154
+ node_cut = nx.minimum_node_cut(G, **kwargs)
155
+ assert 4 == len(node_cut), errmsg
156
+ H = G.copy()
157
+ H.remove_nodes_from(node_cut)
158
+ assert not nx.is_connected(H), errmsg
159
+
160
+
161
+ def test_icosahedral_cutset():
162
+ G = nx.icosahedral_graph()
163
+ for flow_func in flow_funcs:
164
+ kwargs = {"flow_func": flow_func}
165
+ errmsg = f"Assertion failed in function: {flow_func.__name__}"
166
+ # edge cuts
167
+ edge_cut = nx.minimum_edge_cut(G, **kwargs)
168
+ assert 5 == len(edge_cut), errmsg
169
+ H = G.copy()
170
+ H.remove_edges_from(edge_cut)
171
+ assert not nx.is_connected(H), errmsg
172
+ # node cuts
173
+ node_cut = nx.minimum_node_cut(G, **kwargs)
174
+ assert 5 == len(node_cut), errmsg
175
+ H = G.copy()
176
+ H.remove_nodes_from(node_cut)
177
+ assert not nx.is_connected(H), errmsg
178
+
179
+
180
+ def test_node_cutset_exception():
181
+ G = nx.Graph()
182
+ G.add_edges_from([(1, 2), (3, 4)])
183
+ for flow_func in flow_funcs:
184
+ pytest.raises(nx.NetworkXError, nx.minimum_node_cut, G, flow_func=flow_func)
185
+
186
+
187
+ def test_node_cutset_random_graphs():
188
+ for flow_func in flow_funcs:
189
+ errmsg = f"Assertion failed in function: {flow_func.__name__}"
190
+ for i in range(3):
191
+ G = nx.fast_gnp_random_graph(50, 0.25, seed=42)
192
+ if not nx.is_connected(G):
193
+ ccs = iter(nx.connected_components(G))
194
+ start = arbitrary_element(next(ccs))
195
+ G.add_edges_from((start, arbitrary_element(c)) for c in ccs)
196
+ cutset = nx.minimum_node_cut(G, flow_func=flow_func)
197
+ assert nx.node_connectivity(G) == len(cutset), errmsg
198
+ G.remove_nodes_from(cutset)
199
+ assert not nx.is_connected(G), errmsg
200
+
201
+
202
+ def test_edge_cutset_random_graphs():
203
+ for flow_func in flow_funcs:
204
+ errmsg = f"Assertion failed in function: {flow_func.__name__}"
205
+ for i in range(3):
206
+ G = nx.fast_gnp_random_graph(50, 0.25, seed=42)
207
+ if not nx.is_connected(G):
208
+ ccs = iter(nx.connected_components(G))
209
+ start = arbitrary_element(next(ccs))
210
+ G.add_edges_from((start, arbitrary_element(c)) for c in ccs)
211
+ cutset = nx.minimum_edge_cut(G, flow_func=flow_func)
212
+ assert nx.edge_connectivity(G) == len(cutset), errmsg
213
+ G.remove_edges_from(cutset)
214
+ assert not nx.is_connected(G), errmsg
215
+
216
+
217
+ def test_empty_graphs():
218
+ G = nx.Graph()
219
+ D = nx.DiGraph()
220
+ for interface_func in [nx.minimum_node_cut, nx.minimum_edge_cut]:
221
+ for flow_func in flow_funcs:
222
+ pytest.raises(
223
+ nx.NetworkXPointlessConcept, interface_func, G, flow_func=flow_func
224
+ )
225
+ pytest.raises(
226
+ nx.NetworkXPointlessConcept, interface_func, D, flow_func=flow_func
227
+ )
228
+
229
+
230
+ def test_unbounded():
231
+ G = nx.complete_graph(5)
232
+ for flow_func in flow_funcs:
233
+ assert 4 == len(minimum_st_edge_cut(G, 1, 4, flow_func=flow_func))
234
+
235
+
236
+ def test_missing_source():
237
+ G = nx.path_graph(4)
238
+ for interface_func in [nx.minimum_edge_cut, nx.minimum_node_cut]:
239
+ for flow_func in flow_funcs:
240
+ pytest.raises(
241
+ nx.NetworkXError, interface_func, G, 10, 1, flow_func=flow_func
242
+ )
243
+
244
+
245
+ def test_missing_target():
246
+ G = nx.path_graph(4)
247
+ for interface_func in [nx.minimum_edge_cut, nx.minimum_node_cut]:
248
+ for flow_func in flow_funcs:
249
+ pytest.raises(
250
+ nx.NetworkXError, interface_func, G, 1, 10, flow_func=flow_func
251
+ )
252
+
253
+
254
+ def test_not_weakly_connected():
255
+ G = nx.DiGraph()
256
+ nx.add_path(G, [1, 2, 3])
257
+ nx.add_path(G, [4, 5])
258
+ for interface_func in [nx.minimum_edge_cut, nx.minimum_node_cut]:
259
+ for flow_func in flow_funcs:
260
+ pytest.raises(nx.NetworkXError, interface_func, G, flow_func=flow_func)
261
+
262
+
263
+ def test_not_connected():
264
+ G = nx.Graph()
265
+ nx.add_path(G, [1, 2, 3])
266
+ nx.add_path(G, [4, 5])
267
+ for interface_func in [nx.minimum_edge_cut, nx.minimum_node_cut]:
268
+ for flow_func in flow_funcs:
269
+ pytest.raises(nx.NetworkXError, interface_func, G, flow_func=flow_func)
270
+
271
+
272
+ def tests_min_cut_complete():
273
+ G = nx.complete_graph(5)
274
+ for interface_func in [nx.minimum_edge_cut, nx.minimum_node_cut]:
275
+ for flow_func in flow_funcs:
276
+ assert 4 == len(interface_func(G, flow_func=flow_func))
277
+
278
+
279
+ def tests_min_cut_complete_directed():
280
+ G = nx.complete_graph(5)
281
+ G = G.to_directed()
282
+ for interface_func in [nx.minimum_edge_cut, nx.minimum_node_cut]:
283
+ for flow_func in flow_funcs:
284
+ assert 4 == len(interface_func(G, flow_func=flow_func))
285
+
286
+
287
+ def tests_minimum_st_node_cut():
288
+ G = nx.Graph()
289
+ G.add_nodes_from([0, 1, 2, 3, 7, 8, 11, 12])
290
+ G.add_edges_from([(7, 11), (1, 11), (1, 12), (12, 8), (0, 1)])
291
+ nodelist = minimum_st_node_cut(G, 7, 11)
292
+ assert nodelist == {}
293
+
294
+
295
+ def test_invalid_auxiliary():
296
+ G = nx.complete_graph(5)
297
+ pytest.raises(nx.NetworkXError, minimum_st_node_cut, G, 0, 3, auxiliary=G)
298
+
299
+
300
+ def test_interface_only_source():
301
+ G = nx.complete_graph(5)
302
+ for interface_func in [nx.minimum_node_cut, nx.minimum_edge_cut]:
303
+ pytest.raises(nx.NetworkXError, interface_func, G, s=0)
304
+
305
+
306
+ def test_interface_only_target():
307
+ G = nx.complete_graph(5)
308
+ for interface_func in [nx.minimum_node_cut, nx.minimum_edge_cut]:
309
+ pytest.raises(nx.NetworkXError, interface_func, G, t=3)
.venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/tests/test_disjoint_paths.py ADDED
@@ -0,0 +1,249 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+
3
+ import networkx as nx
4
+ from networkx.algorithms import flow
5
+ from networkx.utils import pairwise
6
+
7
+ flow_funcs = [
8
+ flow.boykov_kolmogorov,
9
+ flow.edmonds_karp,
10
+ flow.dinitz,
11
+ flow.preflow_push,
12
+ flow.shortest_augmenting_path,
13
+ ]
14
+
15
+
16
+ def is_path(G, path):
17
+ return all(v in G[u] for u, v in pairwise(path))
18
+
19
+
20
+ def are_edge_disjoint_paths(G, paths):
21
+ if not paths:
22
+ return False
23
+ for path in paths:
24
+ assert is_path(G, path)
25
+ paths_edges = [list(pairwise(p)) for p in paths]
26
+ num_of_edges = sum(len(e) for e in paths_edges)
27
+ num_unique_edges = len(set.union(*[set(es) for es in paths_edges]))
28
+ if num_of_edges == num_unique_edges:
29
+ return True
30
+ return False
31
+
32
+
33
+ def are_node_disjoint_paths(G, paths):
34
+ if not paths:
35
+ return False
36
+ for path in paths:
37
+ assert is_path(G, path)
38
+ # first and last nodes are source and target
39
+ st = {paths[0][0], paths[0][-1]}
40
+ num_of_nodes = len([n for path in paths for n in path if n not in st])
41
+ num_unique_nodes = len({n for path in paths for n in path if n not in st})
42
+ if num_of_nodes == num_unique_nodes:
43
+ return True
44
+ return False
45
+
46
+
47
+ def test_graph_from_pr_2053():
48
+ G = nx.Graph()
49
+ G.add_edges_from(
50
+ [
51
+ ("A", "B"),
52
+ ("A", "D"),
53
+ ("A", "F"),
54
+ ("A", "G"),
55
+ ("B", "C"),
56
+ ("B", "D"),
57
+ ("B", "G"),
58
+ ("C", "D"),
59
+ ("C", "E"),
60
+ ("C", "Z"),
61
+ ("D", "E"),
62
+ ("D", "F"),
63
+ ("E", "F"),
64
+ ("E", "Z"),
65
+ ("F", "Z"),
66
+ ("G", "Z"),
67
+ ]
68
+ )
69
+ for flow_func in flow_funcs:
70
+ kwargs = {"flow_func": flow_func}
71
+ errmsg = f"Assertion failed in function: {flow_func.__name__}"
72
+ # edge disjoint paths
73
+ edge_paths = list(nx.edge_disjoint_paths(G, "A", "Z", **kwargs))
74
+ assert are_edge_disjoint_paths(G, edge_paths), errmsg
75
+ assert nx.edge_connectivity(G, "A", "Z") == len(edge_paths), errmsg
76
+ # node disjoint paths
77
+ node_paths = list(nx.node_disjoint_paths(G, "A", "Z", **kwargs))
78
+ assert are_node_disjoint_paths(G, node_paths), errmsg
79
+ assert nx.node_connectivity(G, "A", "Z") == len(node_paths), errmsg
80
+
81
+
82
+ def test_florentine_families():
83
+ G = nx.florentine_families_graph()
84
+ for flow_func in flow_funcs:
85
+ kwargs = {"flow_func": flow_func}
86
+ errmsg = f"Assertion failed in function: {flow_func.__name__}"
87
+ # edge disjoint paths
88
+ edge_dpaths = list(nx.edge_disjoint_paths(G, "Medici", "Strozzi", **kwargs))
89
+ assert are_edge_disjoint_paths(G, edge_dpaths), errmsg
90
+ assert nx.edge_connectivity(G, "Medici", "Strozzi") == len(edge_dpaths), errmsg
91
+ # node disjoint paths
92
+ node_dpaths = list(nx.node_disjoint_paths(G, "Medici", "Strozzi", **kwargs))
93
+ assert are_node_disjoint_paths(G, node_dpaths), errmsg
94
+ assert nx.node_connectivity(G, "Medici", "Strozzi") == len(node_dpaths), errmsg
95
+
96
+
97
+ def test_karate():
98
+ G = nx.karate_club_graph()
99
+ for flow_func in flow_funcs:
100
+ kwargs = {"flow_func": flow_func}
101
+ errmsg = f"Assertion failed in function: {flow_func.__name__}"
102
+ # edge disjoint paths
103
+ edge_dpaths = list(nx.edge_disjoint_paths(G, 0, 33, **kwargs))
104
+ assert are_edge_disjoint_paths(G, edge_dpaths), errmsg
105
+ assert nx.edge_connectivity(G, 0, 33) == len(edge_dpaths), errmsg
106
+ # node disjoint paths
107
+ node_dpaths = list(nx.node_disjoint_paths(G, 0, 33, **kwargs))
108
+ assert are_node_disjoint_paths(G, node_dpaths), errmsg
109
+ assert nx.node_connectivity(G, 0, 33) == len(node_dpaths), errmsg
110
+
111
+
112
+ def test_petersen_disjoint_paths():
113
+ G = nx.petersen_graph()
114
+ for flow_func in flow_funcs:
115
+ kwargs = {"flow_func": flow_func}
116
+ errmsg = f"Assertion failed in function: {flow_func.__name__}"
117
+ # edge disjoint paths
118
+ edge_dpaths = list(nx.edge_disjoint_paths(G, 0, 6, **kwargs))
119
+ assert are_edge_disjoint_paths(G, edge_dpaths), errmsg
120
+ assert 3 == len(edge_dpaths), errmsg
121
+ # node disjoint paths
122
+ node_dpaths = list(nx.node_disjoint_paths(G, 0, 6, **kwargs))
123
+ assert are_node_disjoint_paths(G, node_dpaths), errmsg
124
+ assert 3 == len(node_dpaths), errmsg
125
+
126
+
127
+ def test_octahedral_disjoint_paths():
128
+ G = nx.octahedral_graph()
129
+ for flow_func in flow_funcs:
130
+ kwargs = {"flow_func": flow_func}
131
+ errmsg = f"Assertion failed in function: {flow_func.__name__}"
132
+ # edge disjoint paths
133
+ edge_dpaths = list(nx.edge_disjoint_paths(G, 0, 5, **kwargs))
134
+ assert are_edge_disjoint_paths(G, edge_dpaths), errmsg
135
+ assert 4 == len(edge_dpaths), errmsg
136
+ # node disjoint paths
137
+ node_dpaths = list(nx.node_disjoint_paths(G, 0, 5, **kwargs))
138
+ assert are_node_disjoint_paths(G, node_dpaths), errmsg
139
+ assert 4 == len(node_dpaths), errmsg
140
+
141
+
142
+ def test_icosahedral_disjoint_paths():
143
+ G = nx.icosahedral_graph()
144
+ for flow_func in flow_funcs:
145
+ kwargs = {"flow_func": flow_func}
146
+ errmsg = f"Assertion failed in function: {flow_func.__name__}"
147
+ # edge disjoint paths
148
+ edge_dpaths = list(nx.edge_disjoint_paths(G, 0, 6, **kwargs))
149
+ assert are_edge_disjoint_paths(G, edge_dpaths), errmsg
150
+ assert 5 == len(edge_dpaths), errmsg
151
+ # node disjoint paths
152
+ node_dpaths = list(nx.node_disjoint_paths(G, 0, 6, **kwargs))
153
+ assert are_node_disjoint_paths(G, node_dpaths), errmsg
154
+ assert 5 == len(node_dpaths), errmsg
155
+
156
+
157
+ def test_cutoff_disjoint_paths():
158
+ G = nx.icosahedral_graph()
159
+ for flow_func in flow_funcs:
160
+ kwargs = {"flow_func": flow_func}
161
+ errmsg = f"Assertion failed in function: {flow_func.__name__}"
162
+ for cutoff in [2, 4]:
163
+ kwargs["cutoff"] = cutoff
164
+ # edge disjoint paths
165
+ edge_dpaths = list(nx.edge_disjoint_paths(G, 0, 6, **kwargs))
166
+ assert are_edge_disjoint_paths(G, edge_dpaths), errmsg
167
+ assert cutoff == len(edge_dpaths), errmsg
168
+ # node disjoint paths
169
+ node_dpaths = list(nx.node_disjoint_paths(G, 0, 6, **kwargs))
170
+ assert are_node_disjoint_paths(G, node_dpaths), errmsg
171
+ assert cutoff == len(node_dpaths), errmsg
172
+
173
+
174
+ def test_missing_source_edge_paths():
175
+ with pytest.raises(nx.NetworkXError):
176
+ G = nx.path_graph(4)
177
+ list(nx.edge_disjoint_paths(G, 10, 1))
178
+
179
+
180
+ def test_missing_source_node_paths():
181
+ with pytest.raises(nx.NetworkXError):
182
+ G = nx.path_graph(4)
183
+ list(nx.node_disjoint_paths(G, 10, 1))
184
+
185
+
186
+ def test_missing_target_edge_paths():
187
+ with pytest.raises(nx.NetworkXError):
188
+ G = nx.path_graph(4)
189
+ list(nx.edge_disjoint_paths(G, 1, 10))
190
+
191
+
192
+ def test_missing_target_node_paths():
193
+ with pytest.raises(nx.NetworkXError):
194
+ G = nx.path_graph(4)
195
+ list(nx.node_disjoint_paths(G, 1, 10))
196
+
197
+
198
+ def test_not_weakly_connected_edges():
199
+ with pytest.raises(nx.NetworkXNoPath):
200
+ G = nx.DiGraph()
201
+ nx.add_path(G, [1, 2, 3])
202
+ nx.add_path(G, [4, 5])
203
+ list(nx.edge_disjoint_paths(G, 1, 5))
204
+
205
+
206
+ def test_not_weakly_connected_nodes():
207
+ with pytest.raises(nx.NetworkXNoPath):
208
+ G = nx.DiGraph()
209
+ nx.add_path(G, [1, 2, 3])
210
+ nx.add_path(G, [4, 5])
211
+ list(nx.node_disjoint_paths(G, 1, 5))
212
+
213
+
214
+ def test_not_connected_edges():
215
+ with pytest.raises(nx.NetworkXNoPath):
216
+ G = nx.Graph()
217
+ nx.add_path(G, [1, 2, 3])
218
+ nx.add_path(G, [4, 5])
219
+ list(nx.edge_disjoint_paths(G, 1, 5))
220
+
221
+
222
+ def test_not_connected_nodes():
223
+ with pytest.raises(nx.NetworkXNoPath):
224
+ G = nx.Graph()
225
+ nx.add_path(G, [1, 2, 3])
226
+ nx.add_path(G, [4, 5])
227
+ list(nx.node_disjoint_paths(G, 1, 5))
228
+
229
+
230
+ def test_isolated_edges():
231
+ with pytest.raises(nx.NetworkXNoPath):
232
+ G = nx.Graph()
233
+ G.add_node(1)
234
+ nx.add_path(G, [4, 5])
235
+ list(nx.edge_disjoint_paths(G, 1, 5))
236
+
237
+
238
+ def test_isolated_nodes():
239
+ with pytest.raises(nx.NetworkXNoPath):
240
+ G = nx.Graph()
241
+ G.add_node(1)
242
+ nx.add_path(G, [4, 5])
243
+ list(nx.node_disjoint_paths(G, 1, 5))
244
+
245
+
246
+ def test_invalid_auxiliary():
247
+ with pytest.raises(nx.NetworkXError):
248
+ G = nx.complete_graph(5)
249
+ list(nx.node_disjoint_paths(G, 0, 3, auxiliary=G))
.venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/tests/test_edge_augmentation.py ADDED
@@ -0,0 +1,502 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import itertools as it
2
+ import random
3
+
4
+ import pytest
5
+
6
+ import networkx as nx
7
+ from networkx.algorithms.connectivity import k_edge_augmentation
8
+ from networkx.algorithms.connectivity.edge_augmentation import (
9
+ _unpack_available_edges,
10
+ collapse,
11
+ complement_edges,
12
+ is_k_edge_connected,
13
+ is_locally_k_edge_connected,
14
+ )
15
+ from networkx.utils import pairwise
16
+
17
+ # This should be set to the largest k for which an efficient algorithm is
18
+ # explicitly defined.
19
+ MAX_EFFICIENT_K = 2
20
+
21
+
22
+ def tarjan_bridge_graph():
23
+ # graph from tarjan paper
24
+ # RE Tarjan - "A note on finding the bridges of a graph"
25
+ # Information Processing Letters, 1974 - Elsevier
26
+ # doi:10.1016/0020-0190(74)90003-9.
27
+ # define 2-connected components and bridges
28
+ ccs = [
29
+ (1, 2, 4, 3, 1, 4),
30
+ (5, 6, 7, 5),
31
+ (8, 9, 10, 8),
32
+ (17, 18, 16, 15, 17),
33
+ (11, 12, 14, 13, 11, 14),
34
+ ]
35
+ bridges = [(4, 8), (3, 5), (3, 17)]
36
+ G = nx.Graph(it.chain(*(pairwise(path) for path in ccs + bridges)))
37
+ return G
38
+
39
+
40
+ def test_weight_key():
41
+ G = nx.Graph()
42
+ G.add_nodes_from([1, 2, 3, 4, 5, 6, 7, 8, 9])
43
+ G.add_edges_from([(3, 8), (1, 2), (2, 3)])
44
+ impossible = {(3, 6), (3, 9)}
45
+ rng = random.Random(0)
46
+ avail_uv = list(set(complement_edges(G)) - impossible)
47
+ avail = [(u, v, {"cost": rng.random()}) for u, v in avail_uv]
48
+
49
+ _augment_and_check(G, k=1)
50
+ _augment_and_check(G, k=1, avail=avail_uv)
51
+ _augment_and_check(G, k=1, avail=avail, weight="cost")
52
+
53
+ _check_augmentations(G, avail, weight="cost")
54
+
55
+
56
+ def test_is_locally_k_edge_connected_exceptions():
57
+ pytest.raises(nx.NetworkXNotImplemented, is_k_edge_connected, nx.DiGraph(), k=0)
58
+ pytest.raises(nx.NetworkXNotImplemented, is_k_edge_connected, nx.MultiGraph(), k=0)
59
+ pytest.raises(ValueError, is_k_edge_connected, nx.Graph(), k=0)
60
+
61
+
62
+ def test_is_k_edge_connected():
63
+ G = nx.barbell_graph(10, 0)
64
+ assert is_k_edge_connected(G, k=1)
65
+ assert not is_k_edge_connected(G, k=2)
66
+
67
+ G = nx.Graph()
68
+ G.add_nodes_from([5, 15])
69
+ assert not is_k_edge_connected(G, k=1)
70
+ assert not is_k_edge_connected(G, k=2)
71
+
72
+ G = nx.complete_graph(5)
73
+ assert is_k_edge_connected(G, k=1)
74
+ assert is_k_edge_connected(G, k=2)
75
+ assert is_k_edge_connected(G, k=3)
76
+ assert is_k_edge_connected(G, k=4)
77
+
78
+ G = nx.compose(nx.complete_graph([0, 1, 2]), nx.complete_graph([3, 4, 5]))
79
+ assert not is_k_edge_connected(G, k=1)
80
+ assert not is_k_edge_connected(G, k=2)
81
+ assert not is_k_edge_connected(G, k=3)
82
+
83
+
84
+ def test_is_k_edge_connected_exceptions():
85
+ pytest.raises(
86
+ nx.NetworkXNotImplemented, is_locally_k_edge_connected, nx.DiGraph(), 1, 2, k=0
87
+ )
88
+ pytest.raises(
89
+ nx.NetworkXNotImplemented,
90
+ is_locally_k_edge_connected,
91
+ nx.MultiGraph(),
92
+ 1,
93
+ 2,
94
+ k=0,
95
+ )
96
+ pytest.raises(ValueError, is_locally_k_edge_connected, nx.Graph(), 1, 2, k=0)
97
+
98
+
99
+ def test_is_locally_k_edge_connected():
100
+ G = nx.barbell_graph(10, 0)
101
+ assert is_locally_k_edge_connected(G, 5, 15, k=1)
102
+ assert not is_locally_k_edge_connected(G, 5, 15, k=2)
103
+
104
+ G = nx.Graph()
105
+ G.add_nodes_from([5, 15])
106
+ assert not is_locally_k_edge_connected(G, 5, 15, k=2)
107
+
108
+
109
+ def test_null_graph():
110
+ G = nx.Graph()
111
+ _check_augmentations(G, max_k=MAX_EFFICIENT_K + 2)
112
+
113
+
114
+ def test_cliques():
115
+ for n in range(1, 10):
116
+ G = nx.complete_graph(n)
117
+ _check_augmentations(G, max_k=MAX_EFFICIENT_K + 2)
118
+
119
+
120
+ def test_clique_and_node():
121
+ for n in range(1, 10):
122
+ G = nx.complete_graph(n)
123
+ G.add_node(n + 1)
124
+ _check_augmentations(G, max_k=MAX_EFFICIENT_K + 2)
125
+
126
+
127
+ def test_point_graph():
128
+ G = nx.Graph()
129
+ G.add_node(1)
130
+ _check_augmentations(G, max_k=MAX_EFFICIENT_K + 2)
131
+
132
+
133
+ def test_edgeless_graph():
134
+ G = nx.Graph()
135
+ G.add_nodes_from([1, 2, 3, 4])
136
+ _check_augmentations(G)
137
+
138
+
139
+ def test_invalid_k():
140
+ G = nx.Graph()
141
+ pytest.raises(ValueError, list, k_edge_augmentation(G, k=-1))
142
+ pytest.raises(ValueError, list, k_edge_augmentation(G, k=0))
143
+
144
+
145
+ def test_unfeasible():
146
+ G = tarjan_bridge_graph()
147
+ pytest.raises(nx.NetworkXUnfeasible, list, k_edge_augmentation(G, k=1, avail=[]))
148
+
149
+ pytest.raises(nx.NetworkXUnfeasible, list, k_edge_augmentation(G, k=2, avail=[]))
150
+
151
+ pytest.raises(
152
+ nx.NetworkXUnfeasible, list, k_edge_augmentation(G, k=2, avail=[(7, 9)])
153
+ )
154
+
155
+ # partial solutions should not error if real solutions are infeasible
156
+ aug_edges = list(k_edge_augmentation(G, k=2, avail=[(7, 9)], partial=True))
157
+ assert aug_edges == [(7, 9)]
158
+
159
+ _check_augmentations(G, avail=[], max_k=MAX_EFFICIENT_K + 2)
160
+
161
+ _check_augmentations(G, avail=[(7, 9)], max_k=MAX_EFFICIENT_K + 2)
162
+
163
+
164
+ def test_tarjan():
165
+ G = tarjan_bridge_graph()
166
+
167
+ aug_edges = set(_augment_and_check(G, k=2)[0])
168
+ print(f"aug_edges = {aug_edges!r}")
169
+ # can't assert edge exactly equality due to non-determinant edge order
170
+ # but we do know the size of the solution must be 3
171
+ assert len(aug_edges) == 3
172
+
173
+ avail = [
174
+ (9, 7),
175
+ (8, 5),
176
+ (2, 10),
177
+ (6, 13),
178
+ (11, 18),
179
+ (1, 17),
180
+ (2, 3),
181
+ (16, 17),
182
+ (18, 14),
183
+ (15, 14),
184
+ ]
185
+ aug_edges = set(_augment_and_check(G, avail=avail, k=2)[0])
186
+
187
+ # Can't assert exact length since approximation depends on the order of a
188
+ # dict traversal.
189
+ assert len(aug_edges) <= 3 * 2
190
+
191
+ _check_augmentations(G, avail)
192
+
193
+
194
+ def test_configuration():
195
+ # seeds = [2718183590, 2470619828, 1694705158, 3001036531, 2401251497]
196
+ seeds = [1001, 1002, 1003, 1004]
197
+ for seed in seeds:
198
+ deg_seq = nx.random_powerlaw_tree_sequence(20, seed=seed, tries=5000)
199
+ G = nx.Graph(nx.configuration_model(deg_seq, seed=seed))
200
+ G.remove_edges_from(nx.selfloop_edges(G))
201
+ _check_augmentations(G)
202
+
203
+
204
+ def test_shell():
205
+ # seeds = [2057382236, 3331169846, 1840105863, 476020778, 2247498425]
206
+ seeds = [18]
207
+ for seed in seeds:
208
+ constructor = [(12, 70, 0.8), (15, 40, 0.6)]
209
+ G = nx.random_shell_graph(constructor, seed=seed)
210
+ _check_augmentations(G)
211
+
212
+
213
+ def test_karate():
214
+ G = nx.karate_club_graph()
215
+ _check_augmentations(G)
216
+
217
+
218
+ def test_star():
219
+ G = nx.star_graph(3)
220
+ _check_augmentations(G)
221
+
222
+ G = nx.star_graph(5)
223
+ _check_augmentations(G)
224
+
225
+ G = nx.star_graph(10)
226
+ _check_augmentations(G)
227
+
228
+
229
+ def test_barbell():
230
+ G = nx.barbell_graph(5, 0)
231
+ _check_augmentations(G)
232
+
233
+ G = nx.barbell_graph(5, 2)
234
+ _check_augmentations(G)
235
+
236
+ G = nx.barbell_graph(5, 3)
237
+ _check_augmentations(G)
238
+
239
+ G = nx.barbell_graph(5, 4)
240
+ _check_augmentations(G)
241
+
242
+
243
+ def test_bridge():
244
+ G = nx.Graph([(2393, 2257), (2393, 2685), (2685, 2257), (1758, 2257)])
245
+ _check_augmentations(G)
246
+
247
+
248
+ def test_gnp_augmentation():
249
+ rng = random.Random(0)
250
+ G = nx.gnp_random_graph(30, 0.005, seed=0)
251
+ # Randomly make edges available
252
+ avail = {
253
+ (u, v): 1 + rng.random() for u, v in complement_edges(G) if rng.random() < 0.25
254
+ }
255
+ _check_augmentations(G, avail)
256
+
257
+
258
+ def _assert_solution_properties(G, aug_edges, avail_dict=None):
259
+ """Checks that aug_edges are consistently formatted"""
260
+ if avail_dict is not None:
261
+ assert all(
262
+ e in avail_dict for e in aug_edges
263
+ ), "when avail is specified aug-edges should be in avail"
264
+
265
+ unique_aug = set(map(tuple, map(sorted, aug_edges)))
266
+ unique_aug = list(map(tuple, map(sorted, aug_edges)))
267
+ assert len(aug_edges) == len(unique_aug), "edges should be unique"
268
+
269
+ assert not any(u == v for u, v in unique_aug), "should be no self-edges"
270
+
271
+ assert not any(
272
+ G.has_edge(u, v) for u, v in unique_aug
273
+ ), "aug edges and G.edges should be disjoint"
274
+
275
+
276
+ def _augment_and_check(
277
+ G, k, avail=None, weight=None, verbose=False, orig_k=None, max_aug_k=None
278
+ ):
279
+ """
280
+ Does one specific augmentation and checks for properties of the result
281
+ """
282
+ if orig_k is None:
283
+ try:
284
+ orig_k = nx.edge_connectivity(G)
285
+ except nx.NetworkXPointlessConcept:
286
+ orig_k = 0
287
+ info = {}
288
+ try:
289
+ if avail is not None:
290
+ # ensure avail is in dict form
291
+ avail_dict = dict(zip(*_unpack_available_edges(avail, weight=weight)))
292
+ else:
293
+ avail_dict = None
294
+ try:
295
+ # Find the augmentation if possible
296
+ generator = nx.k_edge_augmentation(G, k=k, weight=weight, avail=avail)
297
+ assert not isinstance(generator, list), "should always return an iter"
298
+ aug_edges = []
299
+ for edge in generator:
300
+ aug_edges.append(edge)
301
+ except nx.NetworkXUnfeasible:
302
+ infeasible = True
303
+ info["infeasible"] = True
304
+ assert len(aug_edges) == 0, "should not generate anything if unfeasible"
305
+
306
+ if avail is None:
307
+ n_nodes = G.number_of_nodes()
308
+ assert n_nodes <= k, (
309
+ "unconstrained cases are only unfeasible if |V| <= k. "
310
+ f"Got |V|={n_nodes} and k={k}"
311
+ )
312
+ else:
313
+ if max_aug_k is None:
314
+ G_aug_all = G.copy()
315
+ G_aug_all.add_edges_from(avail_dict.keys())
316
+ try:
317
+ max_aug_k = nx.edge_connectivity(G_aug_all)
318
+ except nx.NetworkXPointlessConcept:
319
+ max_aug_k = 0
320
+
321
+ assert max_aug_k < k, (
322
+ "avail should only be unfeasible if using all edges "
323
+ "does not achieve k-edge-connectivity"
324
+ )
325
+
326
+ # Test for a partial solution
327
+ partial_edges = list(
328
+ nx.k_edge_augmentation(G, k=k, weight=weight, partial=True, avail=avail)
329
+ )
330
+
331
+ info["n_partial_edges"] = len(partial_edges)
332
+
333
+ if avail_dict is None:
334
+ assert set(partial_edges) == set(
335
+ complement_edges(G)
336
+ ), "unweighted partial solutions should be the complement"
337
+ elif len(avail_dict) > 0:
338
+ H = G.copy()
339
+
340
+ # Find the partial / full augmented connectivity
341
+ H.add_edges_from(partial_edges)
342
+ partial_conn = nx.edge_connectivity(H)
343
+
344
+ H.add_edges_from(set(avail_dict.keys()))
345
+ full_conn = nx.edge_connectivity(H)
346
+
347
+ # Full connectivity should be no better than our partial
348
+ # solution.
349
+ assert (
350
+ partial_conn == full_conn
351
+ ), "adding more edges should not increase k-conn"
352
+
353
+ # Find the new edge-connectivity after adding the augmenting edges
354
+ aug_edges = partial_edges
355
+ else:
356
+ infeasible = False
357
+
358
+ # Find the weight of the augmentation
359
+ num_edges = len(aug_edges)
360
+ if avail is not None:
361
+ total_weight = sum(avail_dict[e] for e in aug_edges)
362
+ else:
363
+ total_weight = num_edges
364
+
365
+ info["total_weight"] = total_weight
366
+ info["num_edges"] = num_edges
367
+
368
+ # Find the new edge-connectivity after adding the augmenting edges
369
+ G_aug = G.copy()
370
+ G_aug.add_edges_from(aug_edges)
371
+ try:
372
+ aug_k = nx.edge_connectivity(G_aug)
373
+ except nx.NetworkXPointlessConcept:
374
+ aug_k = 0
375
+ info["aug_k"] = aug_k
376
+
377
+ # Do checks
378
+ if not infeasible and orig_k < k:
379
+ assert info["aug_k"] >= k, f"connectivity should increase to k={k} or more"
380
+
381
+ assert info["aug_k"] >= orig_k, "augmenting should never reduce connectivity"
382
+
383
+ _assert_solution_properties(G, aug_edges, avail_dict)
384
+
385
+ except Exception:
386
+ info["failed"] = True
387
+ print(f"edges = {list(G.edges())}")
388
+ print(f"nodes = {list(G.nodes())}")
389
+ print(f"aug_edges = {list(aug_edges)}")
390
+ print(f"info = {info}")
391
+ raise
392
+ else:
393
+ if verbose:
394
+ print(f"info = {info}")
395
+
396
+ if infeasible:
397
+ aug_edges = None
398
+ return aug_edges, info
399
+
400
+
401
+ def _check_augmentations(G, avail=None, max_k=None, weight=None, verbose=False):
402
+ """Helper to check weighted/unweighted cases with multiple values of k"""
403
+ # Using all available edges, find the maximum edge-connectivity
404
+ try:
405
+ orig_k = nx.edge_connectivity(G)
406
+ except nx.NetworkXPointlessConcept:
407
+ orig_k = 0
408
+
409
+ if avail is not None:
410
+ all_aug_edges = _unpack_available_edges(avail, weight=weight)[0]
411
+ G_aug_all = G.copy()
412
+ G_aug_all.add_edges_from(all_aug_edges)
413
+ try:
414
+ max_aug_k = nx.edge_connectivity(G_aug_all)
415
+ except nx.NetworkXPointlessConcept:
416
+ max_aug_k = 0
417
+ else:
418
+ max_aug_k = G.number_of_nodes() - 1
419
+
420
+ if max_k is None:
421
+ max_k = min(4, max_aug_k)
422
+
423
+ avail_uniform = {e: 1 for e in complement_edges(G)}
424
+
425
+ if verbose:
426
+ print("\n=== CHECK_AUGMENTATION ===")
427
+ print(f"G.number_of_nodes = {G.number_of_nodes()!r}")
428
+ print(f"G.number_of_edges = {G.number_of_edges()!r}")
429
+ print(f"max_k = {max_k!r}")
430
+ print(f"max_aug_k = {max_aug_k!r}")
431
+ print(f"orig_k = {orig_k!r}")
432
+
433
+ # check augmentation for multiple values of k
434
+ for k in range(1, max_k + 1):
435
+ if verbose:
436
+ print("---------------")
437
+ print(f"Checking k = {k}")
438
+
439
+ # Check the unweighted version
440
+ if verbose:
441
+ print("unweighted case")
442
+ aug_edges1, info1 = _augment_and_check(G, k=k, verbose=verbose, orig_k=orig_k)
443
+
444
+ # Check that the weighted version with all available edges and uniform
445
+ # weights gives a similar solution to the unweighted case.
446
+ if verbose:
447
+ print("weighted uniform case")
448
+ aug_edges2, info2 = _augment_and_check(
449
+ G,
450
+ k=k,
451
+ avail=avail_uniform,
452
+ verbose=verbose,
453
+ orig_k=orig_k,
454
+ max_aug_k=G.number_of_nodes() - 1,
455
+ )
456
+
457
+ # Check the weighted version
458
+ if avail is not None:
459
+ if verbose:
460
+ print("weighted case")
461
+ aug_edges3, info3 = _augment_and_check(
462
+ G,
463
+ k=k,
464
+ avail=avail,
465
+ weight=weight,
466
+ verbose=verbose,
467
+ max_aug_k=max_aug_k,
468
+ orig_k=orig_k,
469
+ )
470
+
471
+ if aug_edges1 is not None:
472
+ # Check approximation ratios
473
+ if k == 1:
474
+ # when k=1, both solutions should be optimal
475
+ assert info2["total_weight"] == info1["total_weight"]
476
+ if k == 2:
477
+ # when k=2, the weighted version is an approximation
478
+ if orig_k == 0:
479
+ # the approximation ratio is 3 if G is not connected
480
+ assert info2["total_weight"] <= info1["total_weight"] * 3
481
+ else:
482
+ # the approximation ratio is 2 if G is was connected
483
+ assert info2["total_weight"] <= info1["total_weight"] * 2
484
+ _check_unconstrained_bridge_property(G, info1)
485
+
486
+
487
+ def _check_unconstrained_bridge_property(G, info1):
488
+ # Check Theorem 5 from Eswaran and Tarjan. (1975) Augmentation problems
489
+ import math
490
+
491
+ bridge_ccs = list(nx.connectivity.bridge_components(G))
492
+ # condense G into an forest C
493
+ C = collapse(G, bridge_ccs)
494
+
495
+ p = len([n for n, d in C.degree() if d == 1]) # leafs
496
+ q = len([n for n, d in C.degree() if d == 0]) # isolated
497
+ if p + q > 1:
498
+ size_target = math.ceil(p / 2) + q
499
+ size_aug = info1["num_edges"]
500
+ assert (
501
+ size_aug == size_target
502
+ ), "augmentation size is different from what theory predicts"
.venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/tests/test_edge_kcomponents.py ADDED
@@ -0,0 +1,488 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import itertools as it
2
+
3
+ import pytest
4
+
5
+ import networkx as nx
6
+ from networkx.algorithms.connectivity import EdgeComponentAuxGraph, bridge_components
7
+ from networkx.algorithms.connectivity.edge_kcomponents import general_k_edge_subgraphs
8
+ from networkx.utils import pairwise
9
+
10
+ # ----------------
11
+ # Helper functions
12
+ # ----------------
13
+
14
+
15
+ def fset(list_of_sets):
16
+ """allows == to be used for list of sets"""
17
+ return set(map(frozenset, list_of_sets))
18
+
19
+
20
+ def _assert_subgraph_edge_connectivity(G, ccs_subgraph, k):
21
+ """
22
+ tests properties of k-edge-connected subgraphs
23
+
24
+ the actual edge connectivity should be no less than k unless the cc is a
25
+ single node.
26
+ """
27
+ for cc in ccs_subgraph:
28
+ C = G.subgraph(cc)
29
+ if len(cc) > 1:
30
+ connectivity = nx.edge_connectivity(C)
31
+ assert connectivity >= k
32
+
33
+
34
+ def _memo_connectivity(G, u, v, memo):
35
+ edge = (u, v)
36
+ if edge in memo:
37
+ return memo[edge]
38
+ if not G.is_directed():
39
+ redge = (v, u)
40
+ if redge in memo:
41
+ return memo[redge]
42
+ memo[edge] = nx.edge_connectivity(G, *edge)
43
+ return memo[edge]
44
+
45
+
46
+ def _all_pairs_connectivity(G, cc, k, memo):
47
+ # Brute force check
48
+ for u, v in it.combinations(cc, 2):
49
+ # Use a memoization dict to save on computation
50
+ connectivity = _memo_connectivity(G, u, v, memo)
51
+ if G.is_directed():
52
+ connectivity = min(connectivity, _memo_connectivity(G, v, u, memo))
53
+ assert connectivity >= k
54
+
55
+
56
+ def _assert_local_cc_edge_connectivity(G, ccs_local, k, memo):
57
+ """
58
+ tests properties of k-edge-connected components
59
+
60
+ the local edge connectivity between each pair of nodes in the original
61
+ graph should be no less than k unless the cc is a single node.
62
+ """
63
+ for cc in ccs_local:
64
+ if len(cc) > 1:
65
+ # Strategy for testing a bit faster: If the subgraph has high edge
66
+ # connectivity then it must have local connectivity
67
+ C = G.subgraph(cc)
68
+ connectivity = nx.edge_connectivity(C)
69
+ if connectivity < k:
70
+ # Otherwise do the brute force (with memoization) check
71
+ _all_pairs_connectivity(G, cc, k, memo)
72
+
73
+
74
+ # Helper function
75
+ def _check_edge_connectivity(G):
76
+ """
77
+ Helper - generates all k-edge-components using the aux graph. Checks the
78
+ both local and subgraph edge connectivity of each cc. Also checks that
79
+ alternate methods of computing the k-edge-ccs generate the same result.
80
+ """
81
+ # Construct the auxiliary graph that can be used to make each k-cc or k-sub
82
+ aux_graph = EdgeComponentAuxGraph.construct(G)
83
+
84
+ # memoize the local connectivity in this graph
85
+ memo = {}
86
+
87
+ for k in it.count(1):
88
+ # Test "local" k-edge-components and k-edge-subgraphs
89
+ ccs_local = fset(aux_graph.k_edge_components(k))
90
+ ccs_subgraph = fset(aux_graph.k_edge_subgraphs(k))
91
+
92
+ # Check connectivity properties that should be guaranteed by the
93
+ # algorithms.
94
+ _assert_local_cc_edge_connectivity(G, ccs_local, k, memo)
95
+ _assert_subgraph_edge_connectivity(G, ccs_subgraph, k)
96
+
97
+ if k == 1 or k == 2 and not G.is_directed():
98
+ assert (
99
+ ccs_local == ccs_subgraph
100
+ ), "Subgraphs and components should be the same when k == 1 or (k == 2 and not G.directed())"
101
+
102
+ if G.is_directed():
103
+ # Test special case methods are the same as the aux graph
104
+ if k == 1:
105
+ alt_sccs = fset(nx.strongly_connected_components(G))
106
+ assert alt_sccs == ccs_local, "k=1 failed alt"
107
+ assert alt_sccs == ccs_subgraph, "k=1 failed alt"
108
+ else:
109
+ # Test special case methods are the same as the aux graph
110
+ if k == 1:
111
+ alt_ccs = fset(nx.connected_components(G))
112
+ assert alt_ccs == ccs_local, "k=1 failed alt"
113
+ assert alt_ccs == ccs_subgraph, "k=1 failed alt"
114
+ elif k == 2:
115
+ alt_bridge_ccs = fset(bridge_components(G))
116
+ assert alt_bridge_ccs == ccs_local, "k=2 failed alt"
117
+ assert alt_bridge_ccs == ccs_subgraph, "k=2 failed alt"
118
+ # if new methods for k == 3 or k == 4 are implemented add them here
119
+
120
+ # Check the general subgraph method works by itself
121
+ alt_subgraph_ccs = fset(
122
+ [set(C.nodes()) for C in general_k_edge_subgraphs(G, k=k)]
123
+ )
124
+ assert alt_subgraph_ccs == ccs_subgraph, "alt subgraph method failed"
125
+
126
+ # Stop once k is larger than all special case methods
127
+ # and we cannot break down ccs any further.
128
+ if k > 2 and all(len(cc) == 1 for cc in ccs_local):
129
+ break
130
+
131
+
132
+ # ----------------
133
+ # Misc tests
134
+ # ----------------
135
+
136
+
137
+ def test_zero_k_exception():
138
+ G = nx.Graph()
139
+ # functions that return generators error immediately
140
+ pytest.raises(ValueError, nx.k_edge_components, G, k=0)
141
+ pytest.raises(ValueError, nx.k_edge_subgraphs, G, k=0)
142
+
143
+ # actual generators only error when you get the first item
144
+ aux_graph = EdgeComponentAuxGraph.construct(G)
145
+ pytest.raises(ValueError, list, aux_graph.k_edge_components(k=0))
146
+ pytest.raises(ValueError, list, aux_graph.k_edge_subgraphs(k=0))
147
+
148
+ pytest.raises(ValueError, list, general_k_edge_subgraphs(G, k=0))
149
+
150
+
151
+ def test_empty_input():
152
+ G = nx.Graph()
153
+ assert [] == list(nx.k_edge_components(G, k=5))
154
+ assert [] == list(nx.k_edge_subgraphs(G, k=5))
155
+
156
+ G = nx.DiGraph()
157
+ assert [] == list(nx.k_edge_components(G, k=5))
158
+ assert [] == list(nx.k_edge_subgraphs(G, k=5))
159
+
160
+
161
+ def test_not_implemented():
162
+ G = nx.MultiGraph()
163
+ pytest.raises(nx.NetworkXNotImplemented, EdgeComponentAuxGraph.construct, G)
164
+ pytest.raises(nx.NetworkXNotImplemented, nx.k_edge_components, G, k=2)
165
+ pytest.raises(nx.NetworkXNotImplemented, nx.k_edge_subgraphs, G, k=2)
166
+ with pytest.raises(nx.NetworkXNotImplemented):
167
+ next(bridge_components(G))
168
+ with pytest.raises(nx.NetworkXNotImplemented):
169
+ next(bridge_components(nx.DiGraph()))
170
+
171
+
172
+ def test_general_k_edge_subgraph_quick_return():
173
+ # tests quick return optimization
174
+ G = nx.Graph()
175
+ G.add_node(0)
176
+ subgraphs = list(general_k_edge_subgraphs(G, k=1))
177
+ assert len(subgraphs) == 1
178
+ for subgraph in subgraphs:
179
+ assert subgraph.number_of_nodes() == 1
180
+
181
+ G.add_node(1)
182
+ subgraphs = list(general_k_edge_subgraphs(G, k=1))
183
+ assert len(subgraphs) == 2
184
+ for subgraph in subgraphs:
185
+ assert subgraph.number_of_nodes() == 1
186
+
187
+
188
+ # ----------------
189
+ # Undirected tests
190
+ # ----------------
191
+
192
+
193
+ def test_random_gnp():
194
+ # seeds = [1550709854, 1309423156, 4208992358, 2785630813, 1915069929]
195
+ seeds = [12, 13]
196
+
197
+ for seed in seeds:
198
+ G = nx.gnp_random_graph(20, 0.2, seed=seed)
199
+ _check_edge_connectivity(G)
200
+
201
+
202
+ def test_configuration():
203
+ # seeds = [2718183590, 2470619828, 1694705158, 3001036531, 2401251497]
204
+ seeds = [14, 15]
205
+ for seed in seeds:
206
+ deg_seq = nx.random_powerlaw_tree_sequence(20, seed=seed, tries=5000)
207
+ G = nx.Graph(nx.configuration_model(deg_seq, seed=seed))
208
+ G.remove_edges_from(nx.selfloop_edges(G))
209
+ _check_edge_connectivity(G)
210
+
211
+
212
+ def test_shell():
213
+ # seeds = [2057382236, 3331169846, 1840105863, 476020778, 2247498425]
214
+ seeds = [20]
215
+ for seed in seeds:
216
+ constructor = [(12, 70, 0.8), (15, 40, 0.6)]
217
+ G = nx.random_shell_graph(constructor, seed=seed)
218
+ _check_edge_connectivity(G)
219
+
220
+
221
+ def test_karate():
222
+ G = nx.karate_club_graph()
223
+ _check_edge_connectivity(G)
224
+
225
+
226
+ def test_tarjan_bridge():
227
+ # graph from tarjan paper
228
+ # RE Tarjan - "A note on finding the bridges of a graph"
229
+ # Information Processing Letters, 1974 - Elsevier
230
+ # doi:10.1016/0020-0190(74)90003-9.
231
+ # define 2-connected components and bridges
232
+ ccs = [
233
+ (1, 2, 4, 3, 1, 4),
234
+ (5, 6, 7, 5),
235
+ (8, 9, 10, 8),
236
+ (17, 18, 16, 15, 17),
237
+ (11, 12, 14, 13, 11, 14),
238
+ ]
239
+ bridges = [(4, 8), (3, 5), (3, 17)]
240
+ G = nx.Graph(it.chain(*(pairwise(path) for path in ccs + bridges)))
241
+ _check_edge_connectivity(G)
242
+
243
+
244
+ def test_bridge_cc():
245
+ # define 2-connected components and bridges
246
+ cc2 = [(1, 2, 4, 3, 1, 4), (8, 9, 10, 8), (11, 12, 13, 11)]
247
+ bridges = [(4, 8), (3, 5), (20, 21), (22, 23, 24)]
248
+ G = nx.Graph(it.chain(*(pairwise(path) for path in cc2 + bridges)))
249
+ bridge_ccs = fset(bridge_components(G))
250
+ target_ccs = fset(
251
+ [{1, 2, 3, 4}, {5}, {8, 9, 10}, {11, 12, 13}, {20}, {21}, {22}, {23}, {24}]
252
+ )
253
+ assert bridge_ccs == target_ccs
254
+ _check_edge_connectivity(G)
255
+
256
+
257
+ def test_undirected_aux_graph():
258
+ # Graph similar to the one in
259
+ # http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0136264
260
+ a, b, c, d, e, f, g, h, i = "abcdefghi"
261
+ paths = [
262
+ (a, d, b, f, c),
263
+ (a, e, b),
264
+ (a, e, b, c, g, b, a),
265
+ (c, b),
266
+ (f, g, f),
267
+ (h, i),
268
+ ]
269
+ G = nx.Graph(it.chain(*[pairwise(path) for path in paths]))
270
+ aux_graph = EdgeComponentAuxGraph.construct(G)
271
+
272
+ components_1 = fset(aux_graph.k_edge_subgraphs(k=1))
273
+ target_1 = fset([{a, b, c, d, e, f, g}, {h, i}])
274
+ assert target_1 == components_1
275
+
276
+ # Check that the undirected case for k=1 agrees with CCs
277
+ alt_1 = fset(nx.k_edge_subgraphs(G, k=1))
278
+ assert alt_1 == components_1
279
+
280
+ components_2 = fset(aux_graph.k_edge_subgraphs(k=2))
281
+ target_2 = fset([{a, b, c, d, e, f, g}, {h}, {i}])
282
+ assert target_2 == components_2
283
+
284
+ # Check that the undirected case for k=2 agrees with bridge components
285
+ alt_2 = fset(nx.k_edge_subgraphs(G, k=2))
286
+ assert alt_2 == components_2
287
+
288
+ components_3 = fset(aux_graph.k_edge_subgraphs(k=3))
289
+ target_3 = fset([{a}, {b, c, f, g}, {d}, {e}, {h}, {i}])
290
+ assert target_3 == components_3
291
+
292
+ components_4 = fset(aux_graph.k_edge_subgraphs(k=4))
293
+ target_4 = fset([{a}, {b}, {c}, {d}, {e}, {f}, {g}, {h}, {i}])
294
+ assert target_4 == components_4
295
+
296
+ _check_edge_connectivity(G)
297
+
298
+
299
+ def test_local_subgraph_difference():
300
+ paths = [
301
+ (11, 12, 13, 14, 11, 13, 14, 12), # first 4-clique
302
+ (21, 22, 23, 24, 21, 23, 24, 22), # second 4-clique
303
+ # paths connecting each node of the 4 cliques
304
+ (11, 101, 21),
305
+ (12, 102, 22),
306
+ (13, 103, 23),
307
+ (14, 104, 24),
308
+ ]
309
+ G = nx.Graph(it.chain(*[pairwise(path) for path in paths]))
310
+ aux_graph = EdgeComponentAuxGraph.construct(G)
311
+
312
+ # Each clique is returned separately in k-edge-subgraphs
313
+ subgraph_ccs = fset(aux_graph.k_edge_subgraphs(3))
314
+ subgraph_target = fset(
315
+ [{101}, {102}, {103}, {104}, {21, 22, 23, 24}, {11, 12, 13, 14}]
316
+ )
317
+ assert subgraph_ccs == subgraph_target
318
+
319
+ # But in k-edge-ccs they are returned together
320
+ # because they are locally 3-edge-connected
321
+ local_ccs = fset(aux_graph.k_edge_components(3))
322
+ local_target = fset([{101}, {102}, {103}, {104}, {11, 12, 13, 14, 21, 22, 23, 24}])
323
+ assert local_ccs == local_target
324
+
325
+
326
+ def test_local_subgraph_difference_directed():
327
+ dipaths = [(1, 2, 3, 4, 1), (1, 3, 1)]
328
+ G = nx.DiGraph(it.chain(*[pairwise(path) for path in dipaths]))
329
+
330
+ assert fset(nx.k_edge_components(G, k=1)) == fset(nx.k_edge_subgraphs(G, k=1))
331
+
332
+ # Unlike undirected graphs, when k=2, for directed graphs there is a case
333
+ # where the k-edge-ccs are not the same as the k-edge-subgraphs.
334
+ # (in directed graphs ccs and subgraphs are the same when k=2)
335
+ assert fset(nx.k_edge_components(G, k=2)) != fset(nx.k_edge_subgraphs(G, k=2))
336
+
337
+ assert fset(nx.k_edge_components(G, k=3)) == fset(nx.k_edge_subgraphs(G, k=3))
338
+
339
+ _check_edge_connectivity(G)
340
+
341
+
342
+ def test_triangles():
343
+ paths = [
344
+ (11, 12, 13, 11), # first 3-clique
345
+ (21, 22, 23, 21), # second 3-clique
346
+ (11, 21), # connected by an edge
347
+ ]
348
+ G = nx.Graph(it.chain(*[pairwise(path) for path in paths]))
349
+
350
+ # subgraph and ccs are the same in all cases here
351
+ assert fset(nx.k_edge_components(G, k=1)) == fset(nx.k_edge_subgraphs(G, k=1))
352
+
353
+ assert fset(nx.k_edge_components(G, k=2)) == fset(nx.k_edge_subgraphs(G, k=2))
354
+
355
+ assert fset(nx.k_edge_components(G, k=3)) == fset(nx.k_edge_subgraphs(G, k=3))
356
+
357
+ _check_edge_connectivity(G)
358
+
359
+
360
+ def test_four_clique():
361
+ paths = [
362
+ (11, 12, 13, 14, 11, 13, 14, 12), # first 4-clique
363
+ (21, 22, 23, 24, 21, 23, 24, 22), # second 4-clique
364
+ # paths connecting the 4 cliques such that they are
365
+ # 3-connected in G, but not in the subgraph.
366
+ # Case where the nodes bridging them do not have degree less than 3.
367
+ (100, 13),
368
+ (12, 100, 22),
369
+ (13, 200, 23),
370
+ (14, 300, 24),
371
+ ]
372
+ G = nx.Graph(it.chain(*[pairwise(path) for path in paths]))
373
+
374
+ # The subgraphs and ccs are different for k=3
375
+ local_ccs = fset(nx.k_edge_components(G, k=3))
376
+ subgraphs = fset(nx.k_edge_subgraphs(G, k=3))
377
+ assert local_ccs != subgraphs
378
+
379
+ # The cliques ares in the same cc
380
+ clique1 = frozenset(paths[0])
381
+ clique2 = frozenset(paths[1])
382
+ assert clique1.union(clique2).union({100}) in local_ccs
383
+
384
+ # but different subgraphs
385
+ assert clique1 in subgraphs
386
+ assert clique2 in subgraphs
387
+
388
+ assert G.degree(100) == 3
389
+
390
+ _check_edge_connectivity(G)
391
+
392
+
393
+ def test_five_clique():
394
+ # Make a graph that can be disconnected less than 4 edges, but no node has
395
+ # degree less than 4.
396
+ G = nx.disjoint_union(nx.complete_graph(5), nx.complete_graph(5))
397
+ paths = [
398
+ # add aux-connections
399
+ (1, 100, 6),
400
+ (2, 100, 7),
401
+ (3, 200, 8),
402
+ (4, 200, 100),
403
+ ]
404
+ G.add_edges_from(it.chain(*[pairwise(path) for path in paths]))
405
+ assert min(dict(nx.degree(G)).values()) == 4
406
+
407
+ # For k=3 they are the same
408
+ assert fset(nx.k_edge_components(G, k=3)) == fset(nx.k_edge_subgraphs(G, k=3))
409
+
410
+ # For k=4 they are the different
411
+ # the aux nodes are in the same CC as clique 1 but no the same subgraph
412
+ assert fset(nx.k_edge_components(G, k=4)) != fset(nx.k_edge_subgraphs(G, k=4))
413
+
414
+ # For k=5 they are not the same
415
+ assert fset(nx.k_edge_components(G, k=5)) != fset(nx.k_edge_subgraphs(G, k=5))
416
+
417
+ # For k=6 they are the same
418
+ assert fset(nx.k_edge_components(G, k=6)) == fset(nx.k_edge_subgraphs(G, k=6))
419
+ _check_edge_connectivity(G)
420
+
421
+
422
+ # ----------------
423
+ # Undirected tests
424
+ # ----------------
425
+
426
+
427
+ def test_directed_aux_graph():
428
+ # Graph similar to the one in
429
+ # http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0136264
430
+ a, b, c, d, e, f, g, h, i = "abcdefghi"
431
+ dipaths = [
432
+ (a, d, b, f, c),
433
+ (a, e, b),
434
+ (a, e, b, c, g, b, a),
435
+ (c, b),
436
+ (f, g, f),
437
+ (h, i),
438
+ ]
439
+ G = nx.DiGraph(it.chain(*[pairwise(path) for path in dipaths]))
440
+ aux_graph = EdgeComponentAuxGraph.construct(G)
441
+
442
+ components_1 = fset(aux_graph.k_edge_subgraphs(k=1))
443
+ target_1 = fset([{a, b, c, d, e, f, g}, {h}, {i}])
444
+ assert target_1 == components_1
445
+
446
+ # Check that the directed case for k=1 agrees with SCCs
447
+ alt_1 = fset(nx.strongly_connected_components(G))
448
+ assert alt_1 == components_1
449
+
450
+ components_2 = fset(aux_graph.k_edge_subgraphs(k=2))
451
+ target_2 = fset([{i}, {e}, {d}, {b, c, f, g}, {h}, {a}])
452
+ assert target_2 == components_2
453
+
454
+ components_3 = fset(aux_graph.k_edge_subgraphs(k=3))
455
+ target_3 = fset([{a}, {b}, {c}, {d}, {e}, {f}, {g}, {h}, {i}])
456
+ assert target_3 == components_3
457
+
458
+
459
+ def test_random_gnp_directed():
460
+ # seeds = [3894723670, 500186844, 267231174, 2181982262, 1116750056]
461
+ seeds = [21]
462
+ for seed in seeds:
463
+ G = nx.gnp_random_graph(20, 0.2, directed=True, seed=seed)
464
+ _check_edge_connectivity(G)
465
+
466
+
467
+ def test_configuration_directed():
468
+ # seeds = [671221681, 2403749451, 124433910, 672335939, 1193127215]
469
+ seeds = [67]
470
+ for seed in seeds:
471
+ deg_seq = nx.random_powerlaw_tree_sequence(20, seed=seed, tries=5000)
472
+ G = nx.DiGraph(nx.configuration_model(deg_seq, seed=seed))
473
+ G.remove_edges_from(nx.selfloop_edges(G))
474
+ _check_edge_connectivity(G)
475
+
476
+
477
+ def test_shell_directed():
478
+ # seeds = [3134027055, 4079264063, 1350769518, 1405643020, 530038094]
479
+ seeds = [31]
480
+ for seed in seeds:
481
+ constructor = [(12, 70, 0.8), (15, 40, 0.6)]
482
+ G = nx.random_shell_graph(constructor, seed=seed).to_directed()
483
+ _check_edge_connectivity(G)
484
+
485
+
486
+ def test_karate_directed():
487
+ G = nx.karate_club_graph().to_directed()
488
+ _check_edge_connectivity(G)
.venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/tests/test_kcomponents.py ADDED
@@ -0,0 +1,296 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Test for Moody and White k-components algorithm
2
+ import pytest
3
+
4
+ import networkx as nx
5
+ from networkx.algorithms.connectivity.kcomponents import (
6
+ _consolidate,
7
+ build_k_number_dict,
8
+ )
9
+
10
+ ##
11
+ # A nice synthetic graph
12
+ ##
13
+
14
+
15
+ def torrents_and_ferraro_graph():
16
+ # Graph from https://arxiv.org/pdf/1503.04476v1 p.26
17
+ G = nx.convert_node_labels_to_integers(
18
+ nx.grid_graph([5, 5]), label_attribute="labels"
19
+ )
20
+ rlabels = nx.get_node_attributes(G, "labels")
21
+ labels = {v: k for k, v in rlabels.items()}
22
+
23
+ for nodes in [(labels[(0, 4)], labels[(1, 4)]), (labels[(3, 4)], labels[(4, 4)])]:
24
+ new_node = G.order() + 1
25
+ # Petersen graph is triconnected
26
+ P = nx.petersen_graph()
27
+ G = nx.disjoint_union(G, P)
28
+ # Add two edges between the grid and P
29
+ G.add_edge(new_node + 1, nodes[0])
30
+ G.add_edge(new_node, nodes[1])
31
+ # K5 is 4-connected
32
+ K = nx.complete_graph(5)
33
+ G = nx.disjoint_union(G, K)
34
+ # Add three edges between P and K5
35
+ G.add_edge(new_node + 2, new_node + 11)
36
+ G.add_edge(new_node + 3, new_node + 12)
37
+ G.add_edge(new_node + 4, new_node + 13)
38
+ # Add another K5 sharing a node
39
+ G = nx.disjoint_union(G, K)
40
+ nbrs = G[new_node + 10]
41
+ G.remove_node(new_node + 10)
42
+ for nbr in nbrs:
43
+ G.add_edge(new_node + 17, nbr)
44
+ # This edge makes the graph biconnected; it's
45
+ # needed because K5s share only one node.
46
+ G.add_edge(new_node + 16, new_node + 8)
47
+
48
+ for nodes in [(labels[(0, 0)], labels[(1, 0)]), (labels[(3, 0)], labels[(4, 0)])]:
49
+ new_node = G.order() + 1
50
+ # Petersen graph is triconnected
51
+ P = nx.petersen_graph()
52
+ G = nx.disjoint_union(G, P)
53
+ # Add two edges between the grid and P
54
+ G.add_edge(new_node + 1, nodes[0])
55
+ G.add_edge(new_node, nodes[1])
56
+ # K5 is 4-connected
57
+ K = nx.complete_graph(5)
58
+ G = nx.disjoint_union(G, K)
59
+ # Add three edges between P and K5
60
+ G.add_edge(new_node + 2, new_node + 11)
61
+ G.add_edge(new_node + 3, new_node + 12)
62
+ G.add_edge(new_node + 4, new_node + 13)
63
+ # Add another K5 sharing two nodes
64
+ G = nx.disjoint_union(G, K)
65
+ nbrs = G[new_node + 10]
66
+ G.remove_node(new_node + 10)
67
+ for nbr in nbrs:
68
+ G.add_edge(new_node + 17, nbr)
69
+ nbrs2 = G[new_node + 9]
70
+ G.remove_node(new_node + 9)
71
+ for nbr in nbrs2:
72
+ G.add_edge(new_node + 18, nbr)
73
+ return G
74
+
75
+
76
+ def test_directed():
77
+ with pytest.raises(nx.NetworkXNotImplemented):
78
+ G = nx.gnp_random_graph(10, 0.2, directed=True, seed=42)
79
+ nx.k_components(G)
80
+
81
+
82
+ # Helper function
83
+ def _check_connectivity(G, k_components):
84
+ for k, components in k_components.items():
85
+ if k < 3:
86
+ continue
87
+ # check that k-components have node connectivity >= k.
88
+ for component in components:
89
+ C = G.subgraph(component)
90
+ K = nx.node_connectivity(C)
91
+ assert K >= k
92
+
93
+
94
+ @pytest.mark.slow
95
+ def test_torrents_and_ferraro_graph():
96
+ G = torrents_and_ferraro_graph()
97
+ result = nx.k_components(G)
98
+ _check_connectivity(G, result)
99
+
100
+ # In this example graph there are 8 3-components, 4 with 15 nodes
101
+ # and 4 with 5 nodes.
102
+ assert len(result[3]) == 8
103
+ assert len([c for c in result[3] if len(c) == 15]) == 4
104
+ assert len([c for c in result[3] if len(c) == 5]) == 4
105
+ # There are also 8 4-components all with 5 nodes.
106
+ assert len(result[4]) == 8
107
+ assert all(len(c) == 5 for c in result[4])
108
+
109
+
110
+ @pytest.mark.slow
111
+ def test_random_gnp():
112
+ G = nx.gnp_random_graph(50, 0.2, seed=42)
113
+ result = nx.k_components(G)
114
+ _check_connectivity(G, result)
115
+
116
+
117
+ @pytest.mark.slow
118
+ def test_shell():
119
+ constructor = [(20, 80, 0.8), (80, 180, 0.6)]
120
+ G = nx.random_shell_graph(constructor, seed=42)
121
+ result = nx.k_components(G)
122
+ _check_connectivity(G, result)
123
+
124
+
125
+ def test_configuration():
126
+ deg_seq = nx.random_powerlaw_tree_sequence(100, tries=5, seed=72)
127
+ G = nx.Graph(nx.configuration_model(deg_seq))
128
+ G.remove_edges_from(nx.selfloop_edges(G))
129
+ result = nx.k_components(G)
130
+ _check_connectivity(G, result)
131
+
132
+
133
+ def test_karate():
134
+ G = nx.karate_club_graph()
135
+ result = nx.k_components(G)
136
+ _check_connectivity(G, result)
137
+
138
+
139
+ def test_karate_component_number():
140
+ karate_k_num = {
141
+ 0: 4,
142
+ 1: 4,
143
+ 2: 4,
144
+ 3: 4,
145
+ 4: 3,
146
+ 5: 3,
147
+ 6: 3,
148
+ 7: 4,
149
+ 8: 4,
150
+ 9: 2,
151
+ 10: 3,
152
+ 11: 1,
153
+ 12: 2,
154
+ 13: 4,
155
+ 14: 2,
156
+ 15: 2,
157
+ 16: 2,
158
+ 17: 2,
159
+ 18: 2,
160
+ 19: 3,
161
+ 20: 2,
162
+ 21: 2,
163
+ 22: 2,
164
+ 23: 3,
165
+ 24: 3,
166
+ 25: 3,
167
+ 26: 2,
168
+ 27: 3,
169
+ 28: 3,
170
+ 29: 3,
171
+ 30: 4,
172
+ 31: 3,
173
+ 32: 4,
174
+ 33: 4,
175
+ }
176
+ G = nx.karate_club_graph()
177
+ k_components = nx.k_components(G)
178
+ k_num = build_k_number_dict(k_components)
179
+ assert karate_k_num == k_num
180
+
181
+
182
+ def test_davis_southern_women():
183
+ G = nx.davis_southern_women_graph()
184
+ result = nx.k_components(G)
185
+ _check_connectivity(G, result)
186
+
187
+
188
+ def test_davis_southern_women_detail_3_and_4():
189
+ solution = {
190
+ 3: [
191
+ {
192
+ "Nora Fayette",
193
+ "E10",
194
+ "Myra Liddel",
195
+ "E12",
196
+ "E14",
197
+ "Frances Anderson",
198
+ "Evelyn Jefferson",
199
+ "Ruth DeSand",
200
+ "Helen Lloyd",
201
+ "Eleanor Nye",
202
+ "E9",
203
+ "E8",
204
+ "E5",
205
+ "E4",
206
+ "E7",
207
+ "E6",
208
+ "E1",
209
+ "Verne Sanderson",
210
+ "E3",
211
+ "E2",
212
+ "Theresa Anderson",
213
+ "Pearl Oglethorpe",
214
+ "Katherina Rogers",
215
+ "Brenda Rogers",
216
+ "E13",
217
+ "Charlotte McDowd",
218
+ "Sylvia Avondale",
219
+ "Laura Mandeville",
220
+ }
221
+ ],
222
+ 4: [
223
+ {
224
+ "Nora Fayette",
225
+ "E10",
226
+ "Verne Sanderson",
227
+ "E12",
228
+ "Frances Anderson",
229
+ "Evelyn Jefferson",
230
+ "Ruth DeSand",
231
+ "Helen Lloyd",
232
+ "Eleanor Nye",
233
+ "E9",
234
+ "E8",
235
+ "E5",
236
+ "E4",
237
+ "E7",
238
+ "E6",
239
+ "Myra Liddel",
240
+ "E3",
241
+ "Theresa Anderson",
242
+ "Katherina Rogers",
243
+ "Brenda Rogers",
244
+ "Charlotte McDowd",
245
+ "Sylvia Avondale",
246
+ "Laura Mandeville",
247
+ }
248
+ ],
249
+ }
250
+ G = nx.davis_southern_women_graph()
251
+ result = nx.k_components(G)
252
+ for k, components in result.items():
253
+ if k < 3:
254
+ continue
255
+ assert len(components) == len(solution[k])
256
+ for component in components:
257
+ assert component in solution[k]
258
+
259
+
260
+ def test_set_consolidation_rosettacode():
261
+ # Tests from http://rosettacode.org/wiki/Set_consolidation
262
+ def list_of_sets_equal(result, solution):
263
+ assert {frozenset(s) for s in result} == {frozenset(s) for s in solution}
264
+
265
+ question = [{"A", "B"}, {"C", "D"}]
266
+ solution = [{"A", "B"}, {"C", "D"}]
267
+ list_of_sets_equal(_consolidate(question, 1), solution)
268
+ question = [{"A", "B"}, {"B", "C"}]
269
+ solution = [{"A", "B", "C"}]
270
+ list_of_sets_equal(_consolidate(question, 1), solution)
271
+ question = [{"A", "B"}, {"C", "D"}, {"D", "B"}]
272
+ solution = [{"A", "C", "B", "D"}]
273
+ list_of_sets_equal(_consolidate(question, 1), solution)
274
+ question = [{"H", "I", "K"}, {"A", "B"}, {"C", "D"}, {"D", "B"}, {"F", "G", "H"}]
275
+ solution = [{"A", "C", "B", "D"}, {"G", "F", "I", "H", "K"}]
276
+ list_of_sets_equal(_consolidate(question, 1), solution)
277
+ question = [
278
+ {"A", "H"},
279
+ {"H", "I", "K"},
280
+ {"A", "B"},
281
+ {"C", "D"},
282
+ {"D", "B"},
283
+ {"F", "G", "H"},
284
+ ]
285
+ solution = [{"A", "C", "B", "D", "G", "F", "I", "H", "K"}]
286
+ list_of_sets_equal(_consolidate(question, 1), solution)
287
+ question = [
288
+ {"H", "I", "K"},
289
+ {"A", "B"},
290
+ {"C", "D"},
291
+ {"D", "B"},
292
+ {"F", "G", "H"},
293
+ {"A", "H"},
294
+ ]
295
+ solution = [{"A", "C", "B", "D", "G", "F", "I", "H", "K"}]
296
+ list_of_sets_equal(_consolidate(question, 1), solution)
.venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/tests/test_kcutsets.py ADDED
@@ -0,0 +1,273 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Jordi Torrents
2
+ # Test for k-cutsets
3
+ import itertools
4
+
5
+ import pytest
6
+
7
+ import networkx as nx
8
+ from networkx.algorithms import flow
9
+ from networkx.algorithms.connectivity.kcutsets import _is_separating_set
10
+
11
+ MAX_CUTSETS_TO_TEST = 4 # originally 100. cut to decrease testing time
12
+
13
+ flow_funcs = [
14
+ flow.boykov_kolmogorov,
15
+ flow.dinitz,
16
+ flow.edmonds_karp,
17
+ flow.preflow_push,
18
+ flow.shortest_augmenting_path,
19
+ ]
20
+
21
+
22
+ ##
23
+ # Some nice synthetic graphs
24
+ ##
25
+ def graph_example_1():
26
+ G = nx.convert_node_labels_to_integers(
27
+ nx.grid_graph([5, 5]), label_attribute="labels"
28
+ )
29
+ rlabels = nx.get_node_attributes(G, "labels")
30
+ labels = {v: k for k, v in rlabels.items()}
31
+
32
+ for nodes in [
33
+ (labels[(0, 0)], labels[(1, 0)]),
34
+ (labels[(0, 4)], labels[(1, 4)]),
35
+ (labels[(3, 0)], labels[(4, 0)]),
36
+ (labels[(3, 4)], labels[(4, 4)]),
37
+ ]:
38
+ new_node = G.order() + 1
39
+ # Petersen graph is triconnected
40
+ P = nx.petersen_graph()
41
+ G = nx.disjoint_union(G, P)
42
+ # Add two edges between the grid and P
43
+ G.add_edge(new_node + 1, nodes[0])
44
+ G.add_edge(new_node, nodes[1])
45
+ # K5 is 4-connected
46
+ K = nx.complete_graph(5)
47
+ G = nx.disjoint_union(G, K)
48
+ # Add three edges between P and K5
49
+ G.add_edge(new_node + 2, new_node + 11)
50
+ G.add_edge(new_node + 3, new_node + 12)
51
+ G.add_edge(new_node + 4, new_node + 13)
52
+ # Add another K5 sharing a node
53
+ G = nx.disjoint_union(G, K)
54
+ nbrs = G[new_node + 10]
55
+ G.remove_node(new_node + 10)
56
+ for nbr in nbrs:
57
+ G.add_edge(new_node + 17, nbr)
58
+ G.add_edge(new_node + 16, new_node + 5)
59
+ return G
60
+
61
+
62
+ def torrents_and_ferraro_graph():
63
+ G = nx.convert_node_labels_to_integers(
64
+ nx.grid_graph([5, 5]), label_attribute="labels"
65
+ )
66
+ rlabels = nx.get_node_attributes(G, "labels")
67
+ labels = {v: k for k, v in rlabels.items()}
68
+
69
+ for nodes in [(labels[(0, 4)], labels[(1, 4)]), (labels[(3, 4)], labels[(4, 4)])]:
70
+ new_node = G.order() + 1
71
+ # Petersen graph is triconnected
72
+ P = nx.petersen_graph()
73
+ G = nx.disjoint_union(G, P)
74
+ # Add two edges between the grid and P
75
+ G.add_edge(new_node + 1, nodes[0])
76
+ G.add_edge(new_node, nodes[1])
77
+ # K5 is 4-connected
78
+ K = nx.complete_graph(5)
79
+ G = nx.disjoint_union(G, K)
80
+ # Add three edges between P and K5
81
+ G.add_edge(new_node + 2, new_node + 11)
82
+ G.add_edge(new_node + 3, new_node + 12)
83
+ G.add_edge(new_node + 4, new_node + 13)
84
+ # Add another K5 sharing a node
85
+ G = nx.disjoint_union(G, K)
86
+ nbrs = G[new_node + 10]
87
+ G.remove_node(new_node + 10)
88
+ for nbr in nbrs:
89
+ G.add_edge(new_node + 17, nbr)
90
+ # Commenting this makes the graph not biconnected !!
91
+ # This stupid mistake make one reviewer very angry :P
92
+ G.add_edge(new_node + 16, new_node + 8)
93
+
94
+ for nodes in [(labels[(0, 0)], labels[(1, 0)]), (labels[(3, 0)], labels[(4, 0)])]:
95
+ new_node = G.order() + 1
96
+ # Petersen graph is triconnected
97
+ P = nx.petersen_graph()
98
+ G = nx.disjoint_union(G, P)
99
+ # Add two edges between the grid and P
100
+ G.add_edge(new_node + 1, nodes[0])
101
+ G.add_edge(new_node, nodes[1])
102
+ # K5 is 4-connected
103
+ K = nx.complete_graph(5)
104
+ G = nx.disjoint_union(G, K)
105
+ # Add three edges between P and K5
106
+ G.add_edge(new_node + 2, new_node + 11)
107
+ G.add_edge(new_node + 3, new_node + 12)
108
+ G.add_edge(new_node + 4, new_node + 13)
109
+ # Add another K5 sharing two nodes
110
+ G = nx.disjoint_union(G, K)
111
+ nbrs = G[new_node + 10]
112
+ G.remove_node(new_node + 10)
113
+ for nbr in nbrs:
114
+ G.add_edge(new_node + 17, nbr)
115
+ nbrs2 = G[new_node + 9]
116
+ G.remove_node(new_node + 9)
117
+ for nbr in nbrs2:
118
+ G.add_edge(new_node + 18, nbr)
119
+ return G
120
+
121
+
122
+ # Helper function
123
+ def _check_separating_sets(G):
124
+ for cc in nx.connected_components(G):
125
+ if len(cc) < 3:
126
+ continue
127
+ Gc = G.subgraph(cc)
128
+ node_conn = nx.node_connectivity(Gc)
129
+ all_cuts = nx.all_node_cuts(Gc)
130
+ # Only test a limited number of cut sets to reduce test time.
131
+ for cut in itertools.islice(all_cuts, MAX_CUTSETS_TO_TEST):
132
+ assert node_conn == len(cut)
133
+ assert not nx.is_connected(nx.restricted_view(G, cut, []))
134
+
135
+
136
+ @pytest.mark.slow
137
+ def test_torrents_and_ferraro_graph():
138
+ G = torrents_and_ferraro_graph()
139
+ _check_separating_sets(G)
140
+
141
+
142
+ def test_example_1():
143
+ G = graph_example_1()
144
+ _check_separating_sets(G)
145
+
146
+
147
+ def test_random_gnp():
148
+ G = nx.gnp_random_graph(100, 0.1, seed=42)
149
+ _check_separating_sets(G)
150
+
151
+
152
+ def test_shell():
153
+ constructor = [(20, 80, 0.8), (80, 180, 0.6)]
154
+ G = nx.random_shell_graph(constructor, seed=42)
155
+ _check_separating_sets(G)
156
+
157
+
158
+ def test_configuration():
159
+ deg_seq = nx.random_powerlaw_tree_sequence(100, tries=5, seed=72)
160
+ G = nx.Graph(nx.configuration_model(deg_seq))
161
+ G.remove_edges_from(nx.selfloop_edges(G))
162
+ _check_separating_sets(G)
163
+
164
+
165
+ def test_karate():
166
+ G = nx.karate_club_graph()
167
+ _check_separating_sets(G)
168
+
169
+
170
+ def _generate_no_biconnected(max_attempts=50):
171
+ attempts = 0
172
+ while True:
173
+ G = nx.fast_gnp_random_graph(100, 0.0575, seed=42)
174
+ if nx.is_connected(G) and not nx.is_biconnected(G):
175
+ attempts = 0
176
+ yield G
177
+ else:
178
+ if attempts >= max_attempts:
179
+ msg = f"Tried {attempts} times: no suitable Graph."
180
+ raise Exception(msg)
181
+ else:
182
+ attempts += 1
183
+
184
+
185
+ def test_articulation_points():
186
+ Ggen = _generate_no_biconnected()
187
+ for i in range(1): # change 1 to 3 or more for more realizations.
188
+ G = next(Ggen)
189
+ articulation_points = [{a} for a in nx.articulation_points(G)]
190
+ for cut in nx.all_node_cuts(G):
191
+ assert cut in articulation_points
192
+
193
+
194
+ def test_grid_2d_graph():
195
+ # All minimum node cuts of a 2d grid
196
+ # are the four pairs of nodes that are
197
+ # neighbors of the four corner nodes.
198
+ G = nx.grid_2d_graph(5, 5)
199
+ solution = [{(0, 1), (1, 0)}, {(3, 0), (4, 1)}, {(3, 4), (4, 3)}, {(0, 3), (1, 4)}]
200
+ for cut in nx.all_node_cuts(G):
201
+ assert cut in solution
202
+
203
+
204
+ def test_disconnected_graph():
205
+ G = nx.fast_gnp_random_graph(100, 0.01, seed=42)
206
+ cuts = nx.all_node_cuts(G)
207
+ pytest.raises(nx.NetworkXError, next, cuts)
208
+
209
+
210
+ @pytest.mark.slow
211
+ def test_alternative_flow_functions():
212
+ graphs = [nx.grid_2d_graph(4, 4), nx.cycle_graph(5)]
213
+ for G in graphs:
214
+ node_conn = nx.node_connectivity(G)
215
+ for flow_func in flow_funcs:
216
+ all_cuts = nx.all_node_cuts(G, flow_func=flow_func)
217
+ # Only test a limited number of cut sets to reduce test time.
218
+ for cut in itertools.islice(all_cuts, MAX_CUTSETS_TO_TEST):
219
+ assert node_conn == len(cut)
220
+ assert not nx.is_connected(nx.restricted_view(G, cut, []))
221
+
222
+
223
+ def test_is_separating_set_complete_graph():
224
+ G = nx.complete_graph(5)
225
+ assert _is_separating_set(G, {0, 1, 2, 3})
226
+
227
+
228
+ def test_is_separating_set():
229
+ for i in [5, 10, 15]:
230
+ G = nx.star_graph(i)
231
+ max_degree_node = max(G, key=G.degree)
232
+ assert _is_separating_set(G, {max_degree_node})
233
+
234
+
235
+ def test_non_repeated_cuts():
236
+ # The algorithm was repeating the cut {0, 1} for the giant biconnected
237
+ # component of the Karate club graph.
238
+ K = nx.karate_club_graph()
239
+ bcc = max(list(nx.biconnected_components(K)), key=len)
240
+ G = K.subgraph(bcc)
241
+ solution = [{32, 33}, {2, 33}, {0, 3}, {0, 1}, {29, 33}]
242
+ cuts = list(nx.all_node_cuts(G))
243
+ if len(solution) != len(cuts):
244
+ print(f"Solution: {solution}")
245
+ print(f"Result: {cuts}")
246
+ assert len(solution) == len(cuts)
247
+ for cut in cuts:
248
+ assert cut in solution
249
+
250
+
251
+ def test_cycle_graph():
252
+ G = nx.cycle_graph(5)
253
+ solution = [{0, 2}, {0, 3}, {1, 3}, {1, 4}, {2, 4}]
254
+ cuts = list(nx.all_node_cuts(G))
255
+ assert len(solution) == len(cuts)
256
+ for cut in cuts:
257
+ assert cut in solution
258
+
259
+
260
+ def test_complete_graph():
261
+ G = nx.complete_graph(5)
262
+ assert nx.node_connectivity(G) == 4
263
+ assert list(nx.all_node_cuts(G)) == []
264
+
265
+
266
+ def test_all_node_cuts_simple_case():
267
+ G = nx.complete_graph(5)
268
+ G.remove_edges_from([(0, 1), (3, 4)])
269
+ expected = [{0, 1, 2}, {2, 3, 4}]
270
+ actual = list(nx.all_node_cuts(G))
271
+ assert len(actual) == len(expected)
272
+ for cut in actual:
273
+ assert cut in expected
.venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/tests/test_stoer_wagner.py ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from itertools import chain
2
+
3
+ import pytest
4
+
5
+ import networkx as nx
6
+
7
+
8
+ def _check_partition(G, cut_value, partition, weight):
9
+ assert isinstance(partition, tuple)
10
+ assert len(partition) == 2
11
+ assert isinstance(partition[0], list)
12
+ assert isinstance(partition[1], list)
13
+ assert len(partition[0]) > 0
14
+ assert len(partition[1]) > 0
15
+ assert sum(map(len, partition)) == len(G)
16
+ assert set(chain.from_iterable(partition)) == set(G)
17
+ partition = tuple(map(set, partition))
18
+ w = 0
19
+ for u, v, e in G.edges(data=True):
20
+ if (u in partition[0]) == (v in partition[1]):
21
+ w += e.get(weight, 1)
22
+ assert w == cut_value
23
+
24
+
25
+ def _test_stoer_wagner(G, answer, weight="weight"):
26
+ cut_value, partition = nx.stoer_wagner(G, weight, heap=nx.utils.PairingHeap)
27
+ assert cut_value == answer
28
+ _check_partition(G, cut_value, partition, weight)
29
+ cut_value, partition = nx.stoer_wagner(G, weight, heap=nx.utils.BinaryHeap)
30
+ assert cut_value == answer
31
+ _check_partition(G, cut_value, partition, weight)
32
+
33
+
34
+ def test_graph1():
35
+ G = nx.Graph()
36
+ G.add_edge("x", "a", weight=3)
37
+ G.add_edge("x", "b", weight=1)
38
+ G.add_edge("a", "c", weight=3)
39
+ G.add_edge("b", "c", weight=5)
40
+ G.add_edge("b", "d", weight=4)
41
+ G.add_edge("d", "e", weight=2)
42
+ G.add_edge("c", "y", weight=2)
43
+ G.add_edge("e", "y", weight=3)
44
+ _test_stoer_wagner(G, 4)
45
+
46
+
47
+ def test_graph2():
48
+ G = nx.Graph()
49
+ G.add_edge("x", "a")
50
+ G.add_edge("x", "b")
51
+ G.add_edge("a", "c")
52
+ G.add_edge("b", "c")
53
+ G.add_edge("b", "d")
54
+ G.add_edge("d", "e")
55
+ G.add_edge("c", "y")
56
+ G.add_edge("e", "y")
57
+ _test_stoer_wagner(G, 2)
58
+
59
+
60
+ def test_graph3():
61
+ # Source:
62
+ # Stoer, M. and Wagner, F. (1997). "A simple min-cut algorithm". Journal of
63
+ # the ACM 44 (4), 585-591.
64
+ G = nx.Graph()
65
+ G.add_edge(1, 2, weight=2)
66
+ G.add_edge(1, 5, weight=3)
67
+ G.add_edge(2, 3, weight=3)
68
+ G.add_edge(2, 5, weight=2)
69
+ G.add_edge(2, 6, weight=2)
70
+ G.add_edge(3, 4, weight=4)
71
+ G.add_edge(3, 7, weight=2)
72
+ G.add_edge(4, 7, weight=2)
73
+ G.add_edge(4, 8, weight=2)
74
+ G.add_edge(5, 6, weight=3)
75
+ G.add_edge(6, 7, weight=1)
76
+ G.add_edge(7, 8, weight=3)
77
+ _test_stoer_wagner(G, 4)
78
+
79
+
80
+ def test_weight_name():
81
+ G = nx.Graph()
82
+ G.add_edge(1, 2, weight=1, cost=8)
83
+ G.add_edge(1, 3, cost=2)
84
+ G.add_edge(2, 3, cost=4)
85
+ _test_stoer_wagner(G, 6, weight="cost")
86
+
87
+
88
+ def test_exceptions():
89
+ G = nx.Graph()
90
+ pytest.raises(nx.NetworkXError, nx.stoer_wagner, G)
91
+ G.add_node(1)
92
+ pytest.raises(nx.NetworkXError, nx.stoer_wagner, G)
93
+ G.add_node(2)
94
+ pytest.raises(nx.NetworkXError, nx.stoer_wagner, G)
95
+ G.add_edge(1, 2, weight=-2)
96
+ pytest.raises(nx.NetworkXError, nx.stoer_wagner, G)
97
+ G = nx.DiGraph()
98
+ pytest.raises(nx.NetworkXNotImplemented, nx.stoer_wagner, G)
99
+ G = nx.MultiGraph()
100
+ pytest.raises(nx.NetworkXNotImplemented, nx.stoer_wagner, G)
101
+ G = nx.MultiDiGraph()
102
+ pytest.raises(nx.NetworkXNotImplemented, nx.stoer_wagner, G)
.venv/lib/python3.11/site-packages/networkx/algorithms/connectivity/utils.py ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Utilities for connectivity package
3
+ """
4
+
5
+ import networkx as nx
6
+
7
+ __all__ = ["build_auxiliary_node_connectivity", "build_auxiliary_edge_connectivity"]
8
+
9
+
10
+ @nx._dispatchable(returns_graph=True)
11
+ def build_auxiliary_node_connectivity(G):
12
+ r"""Creates a directed graph D from an undirected graph G to compute flow
13
+ based node connectivity.
14
+
15
+ For an undirected graph G having `n` nodes and `m` edges we derive a
16
+ directed graph D with `2n` nodes and `2m+n` arcs by replacing each
17
+ original node `v` with two nodes `vA`, `vB` linked by an (internal)
18
+ arc in D. Then for each edge (`u`, `v`) in G we add two arcs (`uB`, `vA`)
19
+ and (`vB`, `uA`) in D. Finally we set the attribute capacity = 1 for each
20
+ arc in D [1]_.
21
+
22
+ For a directed graph having `n` nodes and `m` arcs we derive a
23
+ directed graph D with `2n` nodes and `m+n` arcs by replacing each
24
+ original node `v` with two nodes `vA`, `vB` linked by an (internal)
25
+ arc (`vA`, `vB`) in D. Then for each arc (`u`, `v`) in G we add one
26
+ arc (`uB`, `vA`) in D. Finally we set the attribute capacity = 1 for
27
+ each arc in D.
28
+
29
+ A dictionary with a mapping between nodes in the original graph and the
30
+ auxiliary digraph is stored as a graph attribute: D.graph['mapping'].
31
+
32
+ References
33
+ ----------
34
+ .. [1] Kammer, Frank and Hanjo Taubig. Graph Connectivity. in Brandes and
35
+ Erlebach, 'Network Analysis: Methodological Foundations', Lecture
36
+ Notes in Computer Science, Volume 3418, Springer-Verlag, 2005.
37
+ https://doi.org/10.1007/978-3-540-31955-9_7
38
+
39
+ """
40
+ directed = G.is_directed()
41
+
42
+ mapping = {}
43
+ H = nx.DiGraph()
44
+
45
+ for i, node in enumerate(G):
46
+ mapping[node] = i
47
+ H.add_node(f"{i}A", id=node)
48
+ H.add_node(f"{i}B", id=node)
49
+ H.add_edge(f"{i}A", f"{i}B", capacity=1)
50
+
51
+ edges = []
52
+ for source, target in G.edges():
53
+ edges.append((f"{mapping[source]}B", f"{mapping[target]}A"))
54
+ if not directed:
55
+ edges.append((f"{mapping[target]}B", f"{mapping[source]}A"))
56
+ H.add_edges_from(edges, capacity=1)
57
+
58
+ # Store mapping as graph attribute
59
+ H.graph["mapping"] = mapping
60
+ return H
61
+
62
+
63
+ @nx._dispatchable(returns_graph=True)
64
+ def build_auxiliary_edge_connectivity(G):
65
+ """Auxiliary digraph for computing flow based edge connectivity
66
+
67
+ If the input graph is undirected, we replace each edge (`u`,`v`) with
68
+ two reciprocal arcs (`u`, `v`) and (`v`, `u`) and then we set the attribute
69
+ 'capacity' for each arc to 1. If the input graph is directed we simply
70
+ add the 'capacity' attribute. Part of algorithm 1 in [1]_ .
71
+
72
+ References
73
+ ----------
74
+ .. [1] Abdol-Hossein Esfahanian. Connectivity Algorithms. (this is a
75
+ chapter, look for the reference of the book).
76
+ http://www.cse.msu.edu/~cse835/Papers/Graph_connectivity_revised.pdf
77
+ """
78
+ if G.is_directed():
79
+ H = nx.DiGraph()
80
+ H.add_nodes_from(G.nodes())
81
+ H.add_edges_from(G.edges(), capacity=1)
82
+ return H
83
+ else:
84
+ H = nx.DiGraph()
85
+ H.add_nodes_from(G.nodes())
86
+ for source, target in G.edges():
87
+ H.add_edges_from([(source, target), (target, source)], capacity=1)
88
+ return H
.venv/lib/python3.11/site-packages/networkx/algorithms/tests/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (198 Bytes). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/tests/__pycache__/test_boundary.cpython-311.pyc ADDED
Binary file (13.7 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/tests/__pycache__/test_bridges.cpython-311.pyc ADDED
Binary file (8.31 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/tests/__pycache__/test_broadcasting.cpython-311.pyc ADDED
Binary file (3.9 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/tests/__pycache__/test_chains.cpython-311.pyc ADDED
Binary file (7.46 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/tests/__pycache__/test_clique.cpython-311.pyc ADDED
Binary file (18 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/tests/__pycache__/test_cluster.cpython-311.pyc ADDED
Binary file (33.5 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/tests/__pycache__/test_communicability.cpython-311.pyc ADDED
Binary file (3.6 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/tests/__pycache__/test_core.cpython-311.pyc ADDED
Binary file (21.4 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/tests/__pycache__/test_cycles.cpython-311.pyc ADDED
Binary file (72.6 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/tests/__pycache__/test_d_separation.cpython-311.pyc ADDED
Binary file (20.5 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/tests/__pycache__/test_dag.cpython-311.pyc ADDED
Binary file (60.3 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/tests/__pycache__/test_distance_measures.cpython-311.pyc ADDED
Binary file (57.6 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/tests/__pycache__/test_distance_regular.cpython-311.pyc ADDED
Binary file (7.1 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/tests/__pycache__/test_dominance.cpython-311.pyc ADDED
Binary file (16.2 kB). View file