koichi12 commited on
Commit
033aa1d
·
verified ·
1 Parent(s): 53f3a8e

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. .gitattributes +4 -0
  2. .venv/lib/python3.11/site-packages/PIL/__pycache__/Image.cpython-311.pyc +3 -0
  3. .venv/lib/python3.11/site-packages/PIL/_imaging.cpython-311-x86_64-linux-gnu.so +3 -0
  4. .venv/lib/python3.11/site-packages/PIL/_imagingft.cpython-311-x86_64-linux-gnu.so +3 -0
  5. .venv/lib/python3.11/site-packages/PIL/_webp.cpython-311-x86_64-linux-gnu.so +3 -0
  6. .venv/lib/python3.11/site-packages/networkx/algorithms/__init__.py +133 -0
  7. .venv/lib/python3.11/site-packages/networkx/algorithms/approximation/clique.py +259 -0
  8. .venv/lib/python3.11/site-packages/networkx/algorithms/approximation/clustering_coefficient.py +71 -0
  9. .venv/lib/python3.11/site-packages/networkx/algorithms/approximation/connectivity.py +412 -0
  10. .venv/lib/python3.11/site-packages/networkx/algorithms/approximation/distance_measures.py +150 -0
  11. .venv/lib/python3.11/site-packages/networkx/algorithms/approximation/kcomponents.py +369 -0
  12. .venv/lib/python3.11/site-packages/networkx/algorithms/approximation/matching.py +44 -0
  13. .venv/lib/python3.11/site-packages/networkx/algorithms/approximation/maxcut.py +143 -0
  14. .venv/lib/python3.11/site-packages/networkx/algorithms/approximation/ramsey.py +53 -0
  15. .venv/lib/python3.11/site-packages/networkx/algorithms/approximation/steinertree.py +231 -0
  16. .venv/lib/python3.11/site-packages/networkx/algorithms/approximation/traveling_salesman.py +1501 -0
  17. .venv/lib/python3.11/site-packages/networkx/algorithms/broadcasting.py +155 -0
  18. .venv/lib/python3.11/site-packages/networkx/algorithms/dominating.py +95 -0
  19. .venv/lib/python3.11/site-packages/networkx/algorithms/flow/__pycache__/__init__.cpython-311.pyc +0 -0
  20. .venv/lib/python3.11/site-packages/networkx/algorithms/flow/__pycache__/boykovkolmogorov.cpython-311.pyc +0 -0
  21. .venv/lib/python3.11/site-packages/networkx/algorithms/flow/__pycache__/capacityscaling.cpython-311.pyc +0 -0
  22. .venv/lib/python3.11/site-packages/networkx/algorithms/flow/__pycache__/dinitz_alg.cpython-311.pyc +0 -0
  23. .venv/lib/python3.11/site-packages/networkx/algorithms/flow/__pycache__/edmondskarp.cpython-311.pyc +0 -0
  24. .venv/lib/python3.11/site-packages/networkx/algorithms/flow/__pycache__/gomory_hu.cpython-311.pyc +0 -0
  25. .venv/lib/python3.11/site-packages/networkx/algorithms/flow/__pycache__/maxflow.cpython-311.pyc +0 -0
  26. .venv/lib/python3.11/site-packages/networkx/algorithms/flow/__pycache__/mincost.cpython-311.pyc +0 -0
  27. .venv/lib/python3.11/site-packages/networkx/algorithms/flow/__pycache__/networksimplex.cpython-311.pyc +0 -0
  28. .venv/lib/python3.11/site-packages/networkx/algorithms/flow/__pycache__/preflowpush.cpython-311.pyc +0 -0
  29. .venv/lib/python3.11/site-packages/networkx/algorithms/flow/__pycache__/shortestaugmentingpath.cpython-311.pyc +0 -0
  30. .venv/lib/python3.11/site-packages/networkx/algorithms/flow/__pycache__/utils.cpython-311.pyc +0 -0
  31. .venv/lib/python3.11/site-packages/networkx/algorithms/flow/edmondskarp.py +241 -0
  32. .venv/lib/python3.11/site-packages/networkx/algorithms/flow/mincost.py +356 -0
  33. .venv/lib/python3.11/site-packages/networkx/algorithms/flow/shortestaugmentingpath.py +300 -0
  34. .venv/lib/python3.11/site-packages/networkx/algorithms/flow/tests/__init__.py +0 -0
  35. .venv/lib/python3.11/site-packages/networkx/algorithms/flow/tests/__pycache__/__init__.cpython-311.pyc +0 -0
  36. .venv/lib/python3.11/site-packages/networkx/algorithms/flow/tests/__pycache__/test_gomory_hu.cpython-311.pyc +0 -0
  37. .venv/lib/python3.11/site-packages/networkx/algorithms/flow/tests/__pycache__/test_maxflow.cpython-311.pyc +0 -0
  38. .venv/lib/python3.11/site-packages/networkx/algorithms/flow/tests/__pycache__/test_maxflow_large_graph.cpython-311.pyc +0 -0
  39. .venv/lib/python3.11/site-packages/networkx/algorithms/flow/tests/__pycache__/test_mincost.cpython-311.pyc +0 -0
  40. .venv/lib/python3.11/site-packages/networkx/algorithms/flow/tests/__pycache__/test_networksimplex.cpython-311.pyc +0 -0
  41. .venv/lib/python3.11/site-packages/networkx/algorithms/flow/tests/test_gomory_hu.py +128 -0
  42. .venv/lib/python3.11/site-packages/networkx/algorithms/flow/tests/test_maxflow.py +573 -0
  43. .venv/lib/python3.11/site-packages/networkx/algorithms/flow/tests/test_maxflow_large_graph.py +156 -0
  44. .venv/lib/python3.11/site-packages/networkx/algorithms/flow/tests/test_mincost.py +476 -0
  45. .venv/lib/python3.11/site-packages/networkx/algorithms/flow/tests/test_networksimplex.py +387 -0
  46. .venv/lib/python3.11/site-packages/networkx/algorithms/flow/utils.py +189 -0
  47. .venv/lib/python3.11/site-packages/networkx/algorithms/link_analysis/__init__.py +2 -0
  48. .venv/lib/python3.11/site-packages/networkx/algorithms/link_analysis/__pycache__/__init__.cpython-311.pyc +0 -0
  49. .venv/lib/python3.11/site-packages/networkx/algorithms/link_analysis/__pycache__/hits_alg.cpython-311.pyc +0 -0
  50. .venv/lib/python3.11/site-packages/networkx/algorithms/link_analysis/__pycache__/pagerank_alg.cpython-311.pyc +0 -0
.gitattributes CHANGED
@@ -305,3 +305,7 @@ tuning-competition-baseline/.venv/lib/python3.11/site-packages/nvidia/cudnn/lib/
305
  .venv/lib/python3.11/site-packages/torchaudio/transforms/__pycache__/_transforms.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text
306
  .venv/lib/python3.11/site-packages/PIL/_imagingmath.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
307
  .venv/lib/python3.11/site-packages/PIL/_imagingcms.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
305
  .venv/lib/python3.11/site-packages/torchaudio/transforms/__pycache__/_transforms.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text
306
  .venv/lib/python3.11/site-packages/PIL/_imagingmath.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
307
  .venv/lib/python3.11/site-packages/PIL/_imagingcms.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
308
+ .venv/lib/python3.11/site-packages/PIL/_webp.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
309
+ .venv/lib/python3.11/site-packages/PIL/_imagingft.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
310
+ .venv/lib/python3.11/site-packages/PIL/__pycache__/Image.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text
311
+ .venv/lib/python3.11/site-packages/PIL/_imaging.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
.venv/lib/python3.11/site-packages/PIL/__pycache__/Image.cpython-311.pyc ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:79b49a60b6452dacc1cccac7143b705dece7b1e8c1eba21aa226a47fe3ea12f5
3
+ size 179907
.venv/lib/python3.11/site-packages/PIL/_imaging.cpython-311-x86_64-linux-gnu.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:28a22e8cdfe2f7cd15a98551cbe737198b69d22266e19e98dd796d0e6515278f
3
+ size 3062073
.venv/lib/python3.11/site-packages/PIL/_imagingft.cpython-311-x86_64-linux-gnu.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:569172d64c6773374562af7f28905a5d65dc08c2bf530fcd81c119ac3c4e3418
3
+ size 290065
.venv/lib/python3.11/site-packages/PIL/_webp.cpython-311-x86_64-linux-gnu.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4d81e0eeb3333d6eaa4bd5ebfc2a59932b8e19dbf6d9cea095be9fe1126085a1
3
+ size 100857
.venv/lib/python3.11/site-packages/networkx/algorithms/__init__.py ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from networkx.algorithms.assortativity import *
2
+ from networkx.algorithms.asteroidal import *
3
+ from networkx.algorithms.boundary import *
4
+ from networkx.algorithms.broadcasting import *
5
+ from networkx.algorithms.bridges import *
6
+ from networkx.algorithms.chains import *
7
+ from networkx.algorithms.centrality import *
8
+ from networkx.algorithms.chordal import *
9
+ from networkx.algorithms.cluster import *
10
+ from networkx.algorithms.clique import *
11
+ from networkx.algorithms.communicability_alg import *
12
+ from networkx.algorithms.components import *
13
+ from networkx.algorithms.coloring import *
14
+ from networkx.algorithms.core import *
15
+ from networkx.algorithms.covering import *
16
+ from networkx.algorithms.cycles import *
17
+ from networkx.algorithms.cuts import *
18
+ from networkx.algorithms.d_separation import *
19
+ from networkx.algorithms.dag import *
20
+ from networkx.algorithms.distance_measures import *
21
+ from networkx.algorithms.distance_regular import *
22
+ from networkx.algorithms.dominance import *
23
+ from networkx.algorithms.dominating import *
24
+ from networkx.algorithms.efficiency_measures import *
25
+ from networkx.algorithms.euler import *
26
+ from networkx.algorithms.graphical import *
27
+ from networkx.algorithms.hierarchy import *
28
+ from networkx.algorithms.hybrid import *
29
+ from networkx.algorithms.link_analysis import *
30
+ from networkx.algorithms.link_prediction import *
31
+ from networkx.algorithms.lowest_common_ancestors import *
32
+ from networkx.algorithms.isolate import *
33
+ from networkx.algorithms.matching import *
34
+ from networkx.algorithms.minors import *
35
+ from networkx.algorithms.mis import *
36
+ from networkx.algorithms.moral import *
37
+ from networkx.algorithms.non_randomness import *
38
+ from networkx.algorithms.operators import *
39
+ from networkx.algorithms.planarity import *
40
+ from networkx.algorithms.planar_drawing import *
41
+ from networkx.algorithms.polynomials import *
42
+ from networkx.algorithms.reciprocity import *
43
+ from networkx.algorithms.regular import *
44
+ from networkx.algorithms.richclub import *
45
+ from networkx.algorithms.shortest_paths import *
46
+ from networkx.algorithms.similarity import *
47
+ from networkx.algorithms.graph_hashing import *
48
+ from networkx.algorithms.simple_paths import *
49
+ from networkx.algorithms.smallworld import *
50
+ from networkx.algorithms.smetric import *
51
+ from networkx.algorithms.structuralholes import *
52
+ from networkx.algorithms.sparsifiers import *
53
+ from networkx.algorithms.summarization import *
54
+ from networkx.algorithms.swap import *
55
+ from networkx.algorithms.time_dependent import *
56
+ from networkx.algorithms.traversal import *
57
+ from networkx.algorithms.triads import *
58
+ from networkx.algorithms.vitality import *
59
+ from networkx.algorithms.voronoi import *
60
+ from networkx.algorithms.walks import *
61
+ from networkx.algorithms.wiener import *
62
+
63
+ # Make certain subpackages available to the user as direct imports from
64
+ # the `networkx` namespace.
65
+ from networkx.algorithms import approximation
66
+ from networkx.algorithms import assortativity
67
+ from networkx.algorithms import bipartite
68
+ from networkx.algorithms import node_classification
69
+ from networkx.algorithms import centrality
70
+ from networkx.algorithms import chordal
71
+ from networkx.algorithms import cluster
72
+ from networkx.algorithms import clique
73
+ from networkx.algorithms import components
74
+ from networkx.algorithms import connectivity
75
+ from networkx.algorithms import community
76
+ from networkx.algorithms import coloring
77
+ from networkx.algorithms import flow
78
+ from networkx.algorithms import isomorphism
79
+ from networkx.algorithms import link_analysis
80
+ from networkx.algorithms import lowest_common_ancestors
81
+ from networkx.algorithms import operators
82
+ from networkx.algorithms import shortest_paths
83
+ from networkx.algorithms import tournament
84
+ from networkx.algorithms import traversal
85
+ from networkx.algorithms import tree
86
+
87
+ # Make certain functions from some of the previous subpackages available
88
+ # to the user as direct imports from the `networkx` namespace.
89
+ from networkx.algorithms.bipartite import complete_bipartite_graph
90
+ from networkx.algorithms.bipartite import is_bipartite
91
+ from networkx.algorithms.bipartite import projected_graph
92
+ from networkx.algorithms.connectivity import all_pairs_node_connectivity
93
+ from networkx.algorithms.connectivity import all_node_cuts
94
+ from networkx.algorithms.connectivity import average_node_connectivity
95
+ from networkx.algorithms.connectivity import edge_connectivity
96
+ from networkx.algorithms.connectivity import edge_disjoint_paths
97
+ from networkx.algorithms.connectivity import k_components
98
+ from networkx.algorithms.connectivity import k_edge_components
99
+ from networkx.algorithms.connectivity import k_edge_subgraphs
100
+ from networkx.algorithms.connectivity import k_edge_augmentation
101
+ from networkx.algorithms.connectivity import is_k_edge_connected
102
+ from networkx.algorithms.connectivity import minimum_edge_cut
103
+ from networkx.algorithms.connectivity import minimum_node_cut
104
+ from networkx.algorithms.connectivity import node_connectivity
105
+ from networkx.algorithms.connectivity import node_disjoint_paths
106
+ from networkx.algorithms.connectivity import stoer_wagner
107
+ from networkx.algorithms.flow import capacity_scaling
108
+ from networkx.algorithms.flow import cost_of_flow
109
+ from networkx.algorithms.flow import gomory_hu_tree
110
+ from networkx.algorithms.flow import max_flow_min_cost
111
+ from networkx.algorithms.flow import maximum_flow
112
+ from networkx.algorithms.flow import maximum_flow_value
113
+ from networkx.algorithms.flow import min_cost_flow
114
+ from networkx.algorithms.flow import min_cost_flow_cost
115
+ from networkx.algorithms.flow import minimum_cut
116
+ from networkx.algorithms.flow import minimum_cut_value
117
+ from networkx.algorithms.flow import network_simplex
118
+ from networkx.algorithms.isomorphism import could_be_isomorphic
119
+ from networkx.algorithms.isomorphism import fast_could_be_isomorphic
120
+ from networkx.algorithms.isomorphism import faster_could_be_isomorphic
121
+ from networkx.algorithms.isomorphism import is_isomorphic
122
+ from networkx.algorithms.isomorphism.vf2pp import *
123
+ from networkx.algorithms.tree.branchings import maximum_branching
124
+ from networkx.algorithms.tree.branchings import maximum_spanning_arborescence
125
+ from networkx.algorithms.tree.branchings import minimum_branching
126
+ from networkx.algorithms.tree.branchings import minimum_spanning_arborescence
127
+ from networkx.algorithms.tree.branchings import ArborescenceIterator
128
+ from networkx.algorithms.tree.coding import *
129
+ from networkx.algorithms.tree.decomposition import *
130
+ from networkx.algorithms.tree.mst import *
131
+ from networkx.algorithms.tree.operations import *
132
+ from networkx.algorithms.tree.recognition import *
133
+ from networkx.algorithms.tournament import is_tournament
.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/clique.py ADDED
@@ -0,0 +1,259 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Functions for computing large cliques and maximum independent sets."""
2
+
3
+ import networkx as nx
4
+ from networkx.algorithms.approximation import ramsey
5
+ from networkx.utils import not_implemented_for
6
+
7
+ __all__ = [
8
+ "clique_removal",
9
+ "max_clique",
10
+ "large_clique_size",
11
+ "maximum_independent_set",
12
+ ]
13
+
14
+
15
+ @not_implemented_for("directed")
16
+ @not_implemented_for("multigraph")
17
+ @nx._dispatchable
18
+ def maximum_independent_set(G):
19
+ """Returns an approximate maximum independent set.
20
+
21
+ Independent set or stable set is a set of vertices in a graph, no two of
22
+ which are adjacent. That is, it is a set I of vertices such that for every
23
+ two vertices in I, there is no edge connecting the two. Equivalently, each
24
+ edge in the graph has at most one endpoint in I. The size of an independent
25
+ set is the number of vertices it contains [1]_.
26
+
27
+ A maximum independent set is a largest independent set for a given graph G
28
+ and its size is denoted $\\alpha(G)$. The problem of finding such a set is called
29
+ the maximum independent set problem and is an NP-hard optimization problem.
30
+ As such, it is unlikely that there exists an efficient algorithm for finding
31
+ a maximum independent set of a graph.
32
+
33
+ The Independent Set algorithm is based on [2]_.
34
+
35
+ Parameters
36
+ ----------
37
+ G : NetworkX graph
38
+ Undirected graph
39
+
40
+ Returns
41
+ -------
42
+ iset : Set
43
+ The apx-maximum independent set
44
+
45
+ Examples
46
+ --------
47
+ >>> G = nx.path_graph(10)
48
+ >>> nx.approximation.maximum_independent_set(G)
49
+ {0, 2, 4, 6, 9}
50
+
51
+ Raises
52
+ ------
53
+ NetworkXNotImplemented
54
+ If the graph is directed or is a multigraph.
55
+
56
+ Notes
57
+ -----
58
+ Finds the $O(|V|/(log|V|)^2)$ apx of independent set in the worst case.
59
+
60
+ References
61
+ ----------
62
+ .. [1] `Wikipedia: Independent set
63
+ <https://en.wikipedia.org/wiki/Independent_set_(graph_theory)>`_
64
+ .. [2] Boppana, R., & Halldórsson, M. M. (1992).
65
+ Approximating maximum independent sets by excluding subgraphs.
66
+ BIT Numerical Mathematics, 32(2), 180–196. Springer.
67
+ """
68
+ iset, _ = clique_removal(G)
69
+ return iset
70
+
71
+
72
+ @not_implemented_for("directed")
73
+ @not_implemented_for("multigraph")
74
+ @nx._dispatchable
75
+ def max_clique(G):
76
+ r"""Find the Maximum Clique
77
+
78
+ Finds the $O(|V|/(log|V|)^2)$ apx of maximum clique/independent set
79
+ in the worst case.
80
+
81
+ Parameters
82
+ ----------
83
+ G : NetworkX graph
84
+ Undirected graph
85
+
86
+ Returns
87
+ -------
88
+ clique : set
89
+ The apx-maximum clique of the graph
90
+
91
+ Examples
92
+ --------
93
+ >>> G = nx.path_graph(10)
94
+ >>> nx.approximation.max_clique(G)
95
+ {8, 9}
96
+
97
+ Raises
98
+ ------
99
+ NetworkXNotImplemented
100
+ If the graph is directed or is a multigraph.
101
+
102
+ Notes
103
+ -----
104
+ A clique in an undirected graph G = (V, E) is a subset of the vertex set
105
+ `C \subseteq V` such that for every two vertices in C there exists an edge
106
+ connecting the two. This is equivalent to saying that the subgraph
107
+ induced by C is complete (in some cases, the term clique may also refer
108
+ to the subgraph).
109
+
110
+ A maximum clique is a clique of the largest possible size in a given graph.
111
+ The clique number `\omega(G)` of a graph G is the number of
112
+ vertices in a maximum clique in G. The intersection number of
113
+ G is the smallest number of cliques that together cover all edges of G.
114
+
115
+ https://en.wikipedia.org/wiki/Maximum_clique
116
+
117
+ References
118
+ ----------
119
+ .. [1] Boppana, R., & Halldórsson, M. M. (1992).
120
+ Approximating maximum independent sets by excluding subgraphs.
121
+ BIT Numerical Mathematics, 32(2), 180–196. Springer.
122
+ doi:10.1007/BF01994876
123
+ """
124
+ # finding the maximum clique in a graph is equivalent to finding
125
+ # the independent set in the complementary graph
126
+ cgraph = nx.complement(G)
127
+ iset, _ = clique_removal(cgraph)
128
+ return iset
129
+
130
+
131
+ @not_implemented_for("directed")
132
+ @not_implemented_for("multigraph")
133
+ @nx._dispatchable
134
+ def clique_removal(G):
135
+ r"""Repeatedly remove cliques from the graph.
136
+
137
+ Results in a $O(|V|/(\log |V|)^2)$ approximation of maximum clique
138
+ and independent set. Returns the largest independent set found, along
139
+ with found maximal cliques.
140
+
141
+ Parameters
142
+ ----------
143
+ G : NetworkX graph
144
+ Undirected graph
145
+
146
+ Returns
147
+ -------
148
+ max_ind_cliques : (set, list) tuple
149
+ 2-tuple of Maximal Independent Set and list of maximal cliques (sets).
150
+
151
+ Examples
152
+ --------
153
+ >>> G = nx.path_graph(10)
154
+ >>> nx.approximation.clique_removal(G)
155
+ ({0, 2, 4, 6, 9}, [{0, 1}, {2, 3}, {4, 5}, {6, 7}, {8, 9}])
156
+
157
+ Raises
158
+ ------
159
+ NetworkXNotImplemented
160
+ If the graph is directed or is a multigraph.
161
+
162
+ References
163
+ ----------
164
+ .. [1] Boppana, R., & Halldórsson, M. M. (1992).
165
+ Approximating maximum independent sets by excluding subgraphs.
166
+ BIT Numerical Mathematics, 32(2), 180–196. Springer.
167
+ """
168
+ graph = G.copy()
169
+ c_i, i_i = ramsey.ramsey_R2(graph)
170
+ cliques = [c_i]
171
+ isets = [i_i]
172
+ while graph:
173
+ graph.remove_nodes_from(c_i)
174
+ c_i, i_i = ramsey.ramsey_R2(graph)
175
+ if c_i:
176
+ cliques.append(c_i)
177
+ if i_i:
178
+ isets.append(i_i)
179
+ # Determine the largest independent set as measured by cardinality.
180
+ maxiset = max(isets, key=len)
181
+ return maxiset, cliques
182
+
183
+
184
+ @not_implemented_for("directed")
185
+ @not_implemented_for("multigraph")
186
+ @nx._dispatchable
187
+ def large_clique_size(G):
188
+ """Find the size of a large clique in a graph.
189
+
190
+ A *clique* is a subset of nodes in which each pair of nodes is
191
+ adjacent. This function is a heuristic for finding the size of a
192
+ large clique in the graph.
193
+
194
+ Parameters
195
+ ----------
196
+ G : NetworkX graph
197
+
198
+ Returns
199
+ -------
200
+ k: integer
201
+ The size of a large clique in the graph.
202
+
203
+ Examples
204
+ --------
205
+ >>> G = nx.path_graph(10)
206
+ >>> nx.approximation.large_clique_size(G)
207
+ 2
208
+
209
+ Raises
210
+ ------
211
+ NetworkXNotImplemented
212
+ If the graph is directed or is a multigraph.
213
+
214
+ Notes
215
+ -----
216
+ This implementation is from [1]_. Its worst case time complexity is
217
+ :math:`O(n d^2)`, where *n* is the number of nodes in the graph and
218
+ *d* is the maximum degree.
219
+
220
+ This function is a heuristic, which means it may work well in
221
+ practice, but there is no rigorous mathematical guarantee on the
222
+ ratio between the returned number and the actual largest clique size
223
+ in the graph.
224
+
225
+ References
226
+ ----------
227
+ .. [1] Pattabiraman, Bharath, et al.
228
+ "Fast Algorithms for the Maximum Clique Problem on Massive Graphs
229
+ with Applications to Overlapping Community Detection."
230
+ *Internet Mathematics* 11.4-5 (2015): 421--448.
231
+ <https://doi.org/10.1080/15427951.2014.986778>
232
+
233
+ See also
234
+ --------
235
+
236
+ :func:`networkx.algorithms.approximation.clique.max_clique`
237
+ A function that returns an approximate maximum clique with a
238
+ guarantee on the approximation ratio.
239
+
240
+ :mod:`networkx.algorithms.clique`
241
+ Functions for finding the exact maximum clique in a graph.
242
+
243
+ """
244
+ degrees = G.degree
245
+
246
+ def _clique_heuristic(G, U, size, best_size):
247
+ if not U:
248
+ return max(best_size, size)
249
+ u = max(U, key=degrees)
250
+ U.remove(u)
251
+ N_prime = {v for v in G[u] if degrees[v] >= best_size}
252
+ return _clique_heuristic(G, U & N_prime, size + 1, best_size)
253
+
254
+ best_size = 0
255
+ nodes = (u for u in G if degrees[u] >= best_size)
256
+ for u in nodes:
257
+ neighbors = {v for v in G[u] if degrees[v] >= best_size}
258
+ best_size = _clique_heuristic(G, neighbors, 1, best_size)
259
+ return best_size
.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/clustering_coefficient.py ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import networkx as nx
2
+ from networkx.utils import not_implemented_for, py_random_state
3
+
4
+ __all__ = ["average_clustering"]
5
+
6
+
7
+ @not_implemented_for("directed")
8
+ @py_random_state(2)
9
+ @nx._dispatchable(name="approximate_average_clustering")
10
+ def average_clustering(G, trials=1000, seed=None):
11
+ r"""Estimates the average clustering coefficient of G.
12
+
13
+ The local clustering of each node in `G` is the fraction of triangles
14
+ that actually exist over all possible triangles in its neighborhood.
15
+ The average clustering coefficient of a graph `G` is the mean of
16
+ local clusterings.
17
+
18
+ This function finds an approximate average clustering coefficient
19
+ for G by repeating `n` times (defined in `trials`) the following
20
+ experiment: choose a node at random, choose two of its neighbors
21
+ at random, and check if they are connected. The approximate
22
+ coefficient is the fraction of triangles found over the number
23
+ of trials [1]_.
24
+
25
+ Parameters
26
+ ----------
27
+ G : NetworkX graph
28
+
29
+ trials : integer
30
+ Number of trials to perform (default 1000).
31
+
32
+ seed : integer, random_state, or None (default)
33
+ Indicator of random number generation state.
34
+ See :ref:`Randomness<randomness>`.
35
+
36
+ Returns
37
+ -------
38
+ c : float
39
+ Approximated average clustering coefficient.
40
+
41
+ Examples
42
+ --------
43
+ >>> from networkx.algorithms import approximation
44
+ >>> G = nx.erdos_renyi_graph(10, 0.2, seed=10)
45
+ >>> approximation.average_clustering(G, trials=1000, seed=10)
46
+ 0.214
47
+
48
+ Raises
49
+ ------
50
+ NetworkXNotImplemented
51
+ If G is directed.
52
+
53
+ References
54
+ ----------
55
+ .. [1] Schank, Thomas, and Dorothea Wagner. Approximating clustering
56
+ coefficient and transitivity. Universität Karlsruhe, Fakultät für
57
+ Informatik, 2004.
58
+ https://doi.org/10.5445/IR/1000001239
59
+
60
+ """
61
+ n = len(G)
62
+ triangles = 0
63
+ nodes = list(G)
64
+ for i in [int(seed.random() * n) for i in range(trials)]:
65
+ nbrs = list(G[nodes[i]])
66
+ if len(nbrs) < 2:
67
+ continue
68
+ u, v = seed.sample(nbrs, 2)
69
+ if u in G[v]:
70
+ triangles += 1
71
+ return triangles / trials
.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/connectivity.py ADDED
@@ -0,0 +1,412 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Fast approximation for node connectivity"""
2
+
3
+ import itertools
4
+ from operator import itemgetter
5
+
6
+ import networkx as nx
7
+
8
+ __all__ = [
9
+ "local_node_connectivity",
10
+ "node_connectivity",
11
+ "all_pairs_node_connectivity",
12
+ ]
13
+
14
+
15
+ @nx._dispatchable(name="approximate_local_node_connectivity")
16
+ def local_node_connectivity(G, source, target, cutoff=None):
17
+ """Compute node connectivity between source and target.
18
+
19
+ Pairwise or local node connectivity between two distinct and nonadjacent
20
+ nodes is the minimum number of nodes that must be removed (minimum
21
+ separating cutset) to disconnect them. By Menger's theorem, this is equal
22
+ to the number of node independent paths (paths that share no nodes other
23
+ than source and target). Which is what we compute in this function.
24
+
25
+ This algorithm is a fast approximation that gives an strict lower
26
+ bound on the actual number of node independent paths between two nodes [1]_.
27
+ It works for both directed and undirected graphs.
28
+
29
+ Parameters
30
+ ----------
31
+
32
+ G : NetworkX graph
33
+
34
+ source : node
35
+ Starting node for node connectivity
36
+
37
+ target : node
38
+ Ending node for node connectivity
39
+
40
+ cutoff : integer
41
+ Maximum node connectivity to consider. If None, the minimum degree
42
+ of source or target is used as a cutoff. Default value None.
43
+
44
+ Returns
45
+ -------
46
+ k: integer
47
+ pairwise node connectivity
48
+
49
+ Examples
50
+ --------
51
+ >>> # Platonic octahedral graph has node connectivity 4
52
+ >>> # for each non adjacent node pair
53
+ >>> from networkx.algorithms import approximation as approx
54
+ >>> G = nx.octahedral_graph()
55
+ >>> approx.local_node_connectivity(G, 0, 5)
56
+ 4
57
+
58
+ Notes
59
+ -----
60
+ This algorithm [1]_ finds node independents paths between two nodes by
61
+ computing their shortest path using BFS, marking the nodes of the path
62
+ found as 'used' and then searching other shortest paths excluding the
63
+ nodes marked as used until no more paths exist. It is not exact because
64
+ a shortest path could use nodes that, if the path were longer, may belong
65
+ to two different node independent paths. Thus it only guarantees an
66
+ strict lower bound on node connectivity.
67
+
68
+ Note that the authors propose a further refinement, losing accuracy and
69
+ gaining speed, which is not implemented yet.
70
+
71
+ See also
72
+ --------
73
+ all_pairs_node_connectivity
74
+ node_connectivity
75
+
76
+ References
77
+ ----------
78
+ .. [1] White, Douglas R., and Mark Newman. 2001 A Fast Algorithm for
79
+ Node-Independent Paths. Santa Fe Institute Working Paper #01-07-035
80
+ http://eclectic.ss.uci.edu/~drwhite/working.pdf
81
+
82
+ """
83
+ if target == source:
84
+ raise nx.NetworkXError("source and target have to be different nodes.")
85
+
86
+ # Maximum possible node independent paths
87
+ if G.is_directed():
88
+ possible = min(G.out_degree(source), G.in_degree(target))
89
+ else:
90
+ possible = min(G.degree(source), G.degree(target))
91
+
92
+ K = 0
93
+ if not possible:
94
+ return K
95
+
96
+ if cutoff is None:
97
+ cutoff = float("inf")
98
+
99
+ exclude = set()
100
+ for i in range(min(possible, cutoff)):
101
+ try:
102
+ path = _bidirectional_shortest_path(G, source, target, exclude)
103
+ exclude.update(set(path))
104
+ K += 1
105
+ except nx.NetworkXNoPath:
106
+ break
107
+
108
+ return K
109
+
110
+
111
+ @nx._dispatchable(name="approximate_node_connectivity")
112
+ def node_connectivity(G, s=None, t=None):
113
+ r"""Returns an approximation for node connectivity for a graph or digraph G.
114
+
115
+ Node connectivity is equal to the minimum number of nodes that
116
+ must be removed to disconnect G or render it trivial. By Menger's theorem,
117
+ this is equal to the number of node independent paths (paths that
118
+ share no nodes other than source and target).
119
+
120
+ If source and target nodes are provided, this function returns the
121
+ local node connectivity: the minimum number of nodes that must be
122
+ removed to break all paths from source to target in G.
123
+
124
+ This algorithm is based on a fast approximation that gives an strict lower
125
+ bound on the actual number of node independent paths between two nodes [1]_.
126
+ It works for both directed and undirected graphs.
127
+
128
+ Parameters
129
+ ----------
130
+ G : NetworkX graph
131
+ Undirected graph
132
+
133
+ s : node
134
+ Source node. Optional. Default value: None.
135
+
136
+ t : node
137
+ Target node. Optional. Default value: None.
138
+
139
+ Returns
140
+ -------
141
+ K : integer
142
+ Node connectivity of G, or local node connectivity if source
143
+ and target are provided.
144
+
145
+ Examples
146
+ --------
147
+ >>> # Platonic octahedral graph is 4-node-connected
148
+ >>> from networkx.algorithms import approximation as approx
149
+ >>> G = nx.octahedral_graph()
150
+ >>> approx.node_connectivity(G)
151
+ 4
152
+
153
+ Notes
154
+ -----
155
+ This algorithm [1]_ finds node independents paths between two nodes by
156
+ computing their shortest path using BFS, marking the nodes of the path
157
+ found as 'used' and then searching other shortest paths excluding the
158
+ nodes marked as used until no more paths exist. It is not exact because
159
+ a shortest path could use nodes that, if the path were longer, may belong
160
+ to two different node independent paths. Thus it only guarantees an
161
+ strict lower bound on node connectivity.
162
+
163
+ See also
164
+ --------
165
+ all_pairs_node_connectivity
166
+ local_node_connectivity
167
+
168
+ References
169
+ ----------
170
+ .. [1] White, Douglas R., and Mark Newman. 2001 A Fast Algorithm for
171
+ Node-Independent Paths. Santa Fe Institute Working Paper #01-07-035
172
+ http://eclectic.ss.uci.edu/~drwhite/working.pdf
173
+
174
+ """
175
+ if (s is not None and t is None) or (s is None and t is not None):
176
+ raise nx.NetworkXError("Both source and target must be specified.")
177
+
178
+ # Local node connectivity
179
+ if s is not None and t is not None:
180
+ if s not in G:
181
+ raise nx.NetworkXError(f"node {s} not in graph")
182
+ if t not in G:
183
+ raise nx.NetworkXError(f"node {t} not in graph")
184
+ return local_node_connectivity(G, s, t)
185
+
186
+ # Global node connectivity
187
+ if G.is_directed():
188
+ connected_func = nx.is_weakly_connected
189
+ iter_func = itertools.permutations
190
+
191
+ def neighbors(v):
192
+ return itertools.chain(G.predecessors(v), G.successors(v))
193
+
194
+ else:
195
+ connected_func = nx.is_connected
196
+ iter_func = itertools.combinations
197
+ neighbors = G.neighbors
198
+
199
+ if not connected_func(G):
200
+ return 0
201
+
202
+ # Choose a node with minimum degree
203
+ v, minimum_degree = min(G.degree(), key=itemgetter(1))
204
+ # Node connectivity is bounded by minimum degree
205
+ K = minimum_degree
206
+ # compute local node connectivity with all non-neighbors nodes
207
+ # and store the minimum
208
+ for w in set(G) - set(neighbors(v)) - {v}:
209
+ K = min(K, local_node_connectivity(G, v, w, cutoff=K))
210
+ # Same for non adjacent pairs of neighbors of v
211
+ for x, y in iter_func(neighbors(v), 2):
212
+ if y not in G[x] and x != y:
213
+ K = min(K, local_node_connectivity(G, x, y, cutoff=K))
214
+ return K
215
+
216
+
217
+ @nx._dispatchable(name="approximate_all_pairs_node_connectivity")
218
+ def all_pairs_node_connectivity(G, nbunch=None, cutoff=None):
219
+ """Compute node connectivity between all pairs of nodes.
220
+
221
+ Pairwise or local node connectivity between two distinct and nonadjacent
222
+ nodes is the minimum number of nodes that must be removed (minimum
223
+ separating cutset) to disconnect them. By Menger's theorem, this is equal
224
+ to the number of node independent paths (paths that share no nodes other
225
+ than source and target). Which is what we compute in this function.
226
+
227
+ This algorithm is a fast approximation that gives an strict lower
228
+ bound on the actual number of node independent paths between two nodes [1]_.
229
+ It works for both directed and undirected graphs.
230
+
231
+
232
+ Parameters
233
+ ----------
234
+ G : NetworkX graph
235
+
236
+ nbunch: container
237
+ Container of nodes. If provided node connectivity will be computed
238
+ only over pairs of nodes in nbunch.
239
+
240
+ cutoff : integer
241
+ Maximum node connectivity to consider. If None, the minimum degree
242
+ of source or target is used as a cutoff in each pair of nodes.
243
+ Default value None.
244
+
245
+ Returns
246
+ -------
247
+ K : dictionary
248
+ Dictionary, keyed by source and target, of pairwise node connectivity
249
+
250
+ Examples
251
+ --------
252
+ A 3 node cycle with one extra node attached has connectivity 2 between all
253
+ nodes in the cycle and connectivity 1 between the extra node and the rest:
254
+
255
+ >>> G = nx.cycle_graph(3)
256
+ >>> G.add_edge(2, 3)
257
+ >>> import pprint # for nice dictionary formatting
258
+ >>> pprint.pprint(nx.all_pairs_node_connectivity(G))
259
+ {0: {1: 2, 2: 2, 3: 1},
260
+ 1: {0: 2, 2: 2, 3: 1},
261
+ 2: {0: 2, 1: 2, 3: 1},
262
+ 3: {0: 1, 1: 1, 2: 1}}
263
+
264
+ See Also
265
+ --------
266
+ local_node_connectivity
267
+ node_connectivity
268
+
269
+ References
270
+ ----------
271
+ .. [1] White, Douglas R., and Mark Newman. 2001 A Fast Algorithm for
272
+ Node-Independent Paths. Santa Fe Institute Working Paper #01-07-035
273
+ http://eclectic.ss.uci.edu/~drwhite/working.pdf
274
+ """
275
+ if nbunch is None:
276
+ nbunch = G
277
+ else:
278
+ nbunch = set(nbunch)
279
+
280
+ directed = G.is_directed()
281
+ if directed:
282
+ iter_func = itertools.permutations
283
+ else:
284
+ iter_func = itertools.combinations
285
+
286
+ all_pairs = {n: {} for n in nbunch}
287
+
288
+ for u, v in iter_func(nbunch, 2):
289
+ k = local_node_connectivity(G, u, v, cutoff=cutoff)
290
+ all_pairs[u][v] = k
291
+ if not directed:
292
+ all_pairs[v][u] = k
293
+
294
+ return all_pairs
295
+
296
+
297
+ def _bidirectional_shortest_path(G, source, target, exclude):
298
+ """Returns shortest path between source and target ignoring nodes in the
299
+ container 'exclude'.
300
+
301
+ Parameters
302
+ ----------
303
+
304
+ G : NetworkX graph
305
+
306
+ source : node
307
+ Starting node for path
308
+
309
+ target : node
310
+ Ending node for path
311
+
312
+ exclude: container
313
+ Container for nodes to exclude from the search for shortest paths
314
+
315
+ Returns
316
+ -------
317
+ path: list
318
+ Shortest path between source and target ignoring nodes in 'exclude'
319
+
320
+ Raises
321
+ ------
322
+ NetworkXNoPath
323
+ If there is no path or if nodes are adjacent and have only one path
324
+ between them
325
+
326
+ Notes
327
+ -----
328
+ This function and its helper are originally from
329
+ networkx.algorithms.shortest_paths.unweighted and are modified to
330
+ accept the extra parameter 'exclude', which is a container for nodes
331
+ already used in other paths that should be ignored.
332
+
333
+ References
334
+ ----------
335
+ .. [1] White, Douglas R., and Mark Newman. 2001 A Fast Algorithm for
336
+ Node-Independent Paths. Santa Fe Institute Working Paper #01-07-035
337
+ http://eclectic.ss.uci.edu/~drwhite/working.pdf
338
+
339
+ """
340
+ # call helper to do the real work
341
+ results = _bidirectional_pred_succ(G, source, target, exclude)
342
+ pred, succ, w = results
343
+
344
+ # build path from pred+w+succ
345
+ path = []
346
+ # from source to w
347
+ while w is not None:
348
+ path.append(w)
349
+ w = pred[w]
350
+ path.reverse()
351
+ # from w to target
352
+ w = succ[path[-1]]
353
+ while w is not None:
354
+ path.append(w)
355
+ w = succ[w]
356
+
357
+ return path
358
+
359
+
360
+ def _bidirectional_pred_succ(G, source, target, exclude):
361
+ # does BFS from both source and target and meets in the middle
362
+ # excludes nodes in the container "exclude" from the search
363
+
364
+ # handle either directed or undirected
365
+ if G.is_directed():
366
+ Gpred = G.predecessors
367
+ Gsucc = G.successors
368
+ else:
369
+ Gpred = G.neighbors
370
+ Gsucc = G.neighbors
371
+
372
+ # predecessor and successors in search
373
+ pred = {source: None}
374
+ succ = {target: None}
375
+
376
+ # initialize fringes, start with forward
377
+ forward_fringe = [source]
378
+ reverse_fringe = [target]
379
+
380
+ level = 0
381
+
382
+ while forward_fringe and reverse_fringe:
383
+ # Make sure that we iterate one step forward and one step backwards
384
+ # thus source and target will only trigger "found path" when they are
385
+ # adjacent and then they can be safely included in the container 'exclude'
386
+ level += 1
387
+ if level % 2 != 0:
388
+ this_level = forward_fringe
389
+ forward_fringe = []
390
+ for v in this_level:
391
+ for w in Gsucc(v):
392
+ if w in exclude:
393
+ continue
394
+ if w not in pred:
395
+ forward_fringe.append(w)
396
+ pred[w] = v
397
+ if w in succ:
398
+ return pred, succ, w # found path
399
+ else:
400
+ this_level = reverse_fringe
401
+ reverse_fringe = []
402
+ for v in this_level:
403
+ for w in Gpred(v):
404
+ if w in exclude:
405
+ continue
406
+ if w not in succ:
407
+ succ[w] = v
408
+ reverse_fringe.append(w)
409
+ if w in pred:
410
+ return pred, succ, w # found path
411
+
412
+ raise nx.NetworkXNoPath(f"No path between {source} and {target}.")
.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/distance_measures.py ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Distance measures approximated metrics."""
2
+
3
+ import networkx as nx
4
+ from networkx.utils.decorators import py_random_state
5
+
6
+ __all__ = ["diameter"]
7
+
8
+
9
+ @py_random_state(1)
10
+ @nx._dispatchable(name="approximate_diameter")
11
+ def diameter(G, seed=None):
12
+ """Returns a lower bound on the diameter of the graph G.
13
+
14
+ The function computes a lower bound on the diameter (i.e., the maximum eccentricity)
15
+ of a directed or undirected graph G. The procedure used varies depending on the graph
16
+ being directed or not.
17
+
18
+ If G is an `undirected` graph, then the function uses the `2-sweep` algorithm [1]_.
19
+ The main idea is to pick the farthest node from a random node and return its eccentricity.
20
+
21
+ Otherwise, if G is a `directed` graph, the function uses the `2-dSweep` algorithm [2]_,
22
+ The procedure starts by selecting a random source node $s$ from which it performs a
23
+ forward and a backward BFS. Let $a_1$ and $a_2$ be the farthest nodes in the forward and
24
+ backward cases, respectively. Then, it computes the backward eccentricity of $a_1$ using
25
+ a backward BFS and the forward eccentricity of $a_2$ using a forward BFS.
26
+ Finally, it returns the best lower bound between the two.
27
+
28
+ In both cases, the time complexity is linear with respect to the size of G.
29
+
30
+ Parameters
31
+ ----------
32
+ G : NetworkX graph
33
+
34
+ seed : integer, random_state, or None (default)
35
+ Indicator of random number generation state.
36
+ See :ref:`Randomness<randomness>`.
37
+
38
+ Returns
39
+ -------
40
+ d : integer
41
+ Lower Bound on the Diameter of G
42
+
43
+ Examples
44
+ --------
45
+ >>> G = nx.path_graph(10) # undirected graph
46
+ >>> nx.diameter(G)
47
+ 9
48
+ >>> G = nx.cycle_graph(3, create_using=nx.DiGraph) # directed graph
49
+ >>> nx.diameter(G)
50
+ 2
51
+
52
+ Raises
53
+ ------
54
+ NetworkXError
55
+ If the graph is empty or
56
+ If the graph is undirected and not connected or
57
+ If the graph is directed and not strongly connected.
58
+
59
+ See Also
60
+ --------
61
+ networkx.algorithms.distance_measures.diameter
62
+
63
+ References
64
+ ----------
65
+ .. [1] Magnien, Clémence, Matthieu Latapy, and Michel Habib.
66
+ *Fast computation of empirically tight bounds for the diameter of massive graphs.*
67
+ Journal of Experimental Algorithmics (JEA), 2009.
68
+ https://arxiv.org/pdf/0904.2728.pdf
69
+ .. [2] Crescenzi, Pierluigi, Roberto Grossi, Leonardo Lanzi, and Andrea Marino.
70
+ *On computing the diameter of real-world directed (weighted) graphs.*
71
+ International Symposium on Experimental Algorithms. Springer, Berlin, Heidelberg, 2012.
72
+ https://courses.cs.ut.ee/MTAT.03.238/2014_fall/uploads/Main/diameter.pdf
73
+ """
74
+ # if G is empty
75
+ if not G:
76
+ raise nx.NetworkXError("Expected non-empty NetworkX graph!")
77
+ # if there's only a node
78
+ if G.number_of_nodes() == 1:
79
+ return 0
80
+ # if G is directed
81
+ if G.is_directed():
82
+ return _two_sweep_directed(G, seed)
83
+ # else if G is undirected
84
+ return _two_sweep_undirected(G, seed)
85
+
86
+
87
+ def _two_sweep_undirected(G, seed):
88
+ """Helper function for finding a lower bound on the diameter
89
+ for undirected Graphs.
90
+
91
+ The idea is to pick the farthest node from a random node
92
+ and return its eccentricity.
93
+
94
+ ``G`` is a NetworkX undirected graph.
95
+
96
+ .. note::
97
+
98
+ ``seed`` is a random.Random or numpy.random.RandomState instance
99
+ """
100
+ # select a random source node
101
+ source = seed.choice(list(G))
102
+ # get the distances to the other nodes
103
+ distances = nx.shortest_path_length(G, source)
104
+ # if some nodes have not been visited, then the graph is not connected
105
+ if len(distances) != len(G):
106
+ raise nx.NetworkXError("Graph not connected.")
107
+ # take a node that is (one of) the farthest nodes from the source
108
+ *_, node = distances
109
+ # return the eccentricity of the node
110
+ return nx.eccentricity(G, node)
111
+
112
+
113
+ def _two_sweep_directed(G, seed):
114
+ """Helper function for finding a lower bound on the diameter
115
+ for directed Graphs.
116
+
117
+ It implements 2-dSweep, the directed version of the 2-sweep algorithm.
118
+ The algorithm follows the following steps.
119
+ 1. Select a source node $s$ at random.
120
+ 2. Perform a forward BFS from $s$ to select a node $a_1$ at the maximum
121
+ distance from the source, and compute $LB_1$, the backward eccentricity of $a_1$.
122
+ 3. Perform a backward BFS from $s$ to select a node $a_2$ at the maximum
123
+ distance from the source, and compute $LB_2$, the forward eccentricity of $a_2$.
124
+ 4. Return the maximum between $LB_1$ and $LB_2$.
125
+
126
+ ``G`` is a NetworkX directed graph.
127
+
128
+ .. note::
129
+
130
+ ``seed`` is a random.Random or numpy.random.RandomState instance
131
+ """
132
+ # get a new digraph G' with the edges reversed in the opposite direction
133
+ G_reversed = G.reverse()
134
+ # select a random source node
135
+ source = seed.choice(list(G))
136
+ # compute forward distances from source
137
+ forward_distances = nx.shortest_path_length(G, source)
138
+ # compute backward distances from source
139
+ backward_distances = nx.shortest_path_length(G_reversed, source)
140
+ # if either the source can't reach every node or not every node
141
+ # can reach the source, then the graph is not strongly connected
142
+ n = len(G)
143
+ if len(forward_distances) != n or len(backward_distances) != n:
144
+ raise nx.NetworkXError("DiGraph not strongly connected.")
145
+ # take a node a_1 at the maximum distance from the source in G
146
+ *_, a_1 = forward_distances
147
+ # take a node a_2 at the maximum distance from the source in G_reversed
148
+ *_, a_2 = backward_distances
149
+ # return the max between the backward eccentricity of a_1 and the forward eccentricity of a_2
150
+ return max(nx.eccentricity(G_reversed, a_1), nx.eccentricity(G, a_2))
.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/kcomponents.py ADDED
@@ -0,0 +1,369 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Fast approximation for k-component structure"""
2
+
3
+ import itertools
4
+ from collections import defaultdict
5
+ from collections.abc import Mapping
6
+ from functools import cached_property
7
+
8
+ import networkx as nx
9
+ from networkx.algorithms.approximation import local_node_connectivity
10
+ from networkx.exception import NetworkXError
11
+ from networkx.utils import not_implemented_for
12
+
13
+ __all__ = ["k_components"]
14
+
15
+
16
+ @not_implemented_for("directed")
17
+ @nx._dispatchable(name="approximate_k_components")
18
+ def k_components(G, min_density=0.95):
19
+ r"""Returns the approximate k-component structure of a graph G.
20
+
21
+ A `k`-component is a maximal subgraph of a graph G that has, at least,
22
+ node connectivity `k`: we need to remove at least `k` nodes to break it
23
+ into more components. `k`-components have an inherent hierarchical
24
+ structure because they are nested in terms of connectivity: a connected
25
+ graph can contain several 2-components, each of which can contain
26
+ one or more 3-components, and so forth.
27
+
28
+ This implementation is based on the fast heuristics to approximate
29
+ the `k`-component structure of a graph [1]_. Which, in turn, it is based on
30
+ a fast approximation algorithm for finding good lower bounds of the number
31
+ of node independent paths between two nodes [2]_.
32
+
33
+ Parameters
34
+ ----------
35
+ G : NetworkX graph
36
+ Undirected graph
37
+
38
+ min_density : Float
39
+ Density relaxation threshold. Default value 0.95
40
+
41
+ Returns
42
+ -------
43
+ k_components : dict
44
+ Dictionary with connectivity level `k` as key and a list of
45
+ sets of nodes that form a k-component of level `k` as values.
46
+
47
+ Raises
48
+ ------
49
+ NetworkXNotImplemented
50
+ If G is directed.
51
+
52
+ Examples
53
+ --------
54
+ >>> # Petersen graph has 10 nodes and it is triconnected, thus all
55
+ >>> # nodes are in a single component on all three connectivity levels
56
+ >>> from networkx.algorithms import approximation as apxa
57
+ >>> G = nx.petersen_graph()
58
+ >>> k_components = apxa.k_components(G)
59
+
60
+ Notes
61
+ -----
62
+ The logic of the approximation algorithm for computing the `k`-component
63
+ structure [1]_ is based on repeatedly applying simple and fast algorithms
64
+ for `k`-cores and biconnected components in order to narrow down the
65
+ number of pairs of nodes over which we have to compute White and Newman's
66
+ approximation algorithm for finding node independent paths [2]_. More
67
+ formally, this algorithm is based on Whitney's theorem, which states
68
+ an inclusion relation among node connectivity, edge connectivity, and
69
+ minimum degree for any graph G. This theorem implies that every
70
+ `k`-component is nested inside a `k`-edge-component, which in turn,
71
+ is contained in a `k`-core. Thus, this algorithm computes node independent
72
+ paths among pairs of nodes in each biconnected part of each `k`-core,
73
+ and repeats this procedure for each `k` from 3 to the maximal core number
74
+ of a node in the input graph.
75
+
76
+ Because, in practice, many nodes of the core of level `k` inside a
77
+ bicomponent actually are part of a component of level k, the auxiliary
78
+ graph needed for the algorithm is likely to be very dense. Thus, we use
79
+ a complement graph data structure (see `AntiGraph`) to save memory.
80
+ AntiGraph only stores information of the edges that are *not* present
81
+ in the actual auxiliary graph. When applying algorithms to this
82
+ complement graph data structure, it behaves as if it were the dense
83
+ version.
84
+
85
+ See also
86
+ --------
87
+ k_components
88
+
89
+ References
90
+ ----------
91
+ .. [1] Torrents, J. and F. Ferraro (2015) Structural Cohesion:
92
+ Visualization and Heuristics for Fast Computation.
93
+ https://arxiv.org/pdf/1503.04476v1
94
+
95
+ .. [2] White, Douglas R., and Mark Newman (2001) A Fast Algorithm for
96
+ Node-Independent Paths. Santa Fe Institute Working Paper #01-07-035
97
+ https://www.santafe.edu/research/results/working-papers/fast-approximation-algorithms-for-finding-node-ind
98
+
99
+ .. [3] Moody, J. and D. White (2003). Social cohesion and embeddedness:
100
+ A hierarchical conception of social groups.
101
+ American Sociological Review 68(1), 103--28.
102
+ https://doi.org/10.2307/3088904
103
+
104
+ """
105
+ # Dictionary with connectivity level (k) as keys and a list of
106
+ # sets of nodes that form a k-component as values
107
+ k_components = defaultdict(list)
108
+ # make a few functions local for speed
109
+ node_connectivity = local_node_connectivity
110
+ k_core = nx.k_core
111
+ core_number = nx.core_number
112
+ biconnected_components = nx.biconnected_components
113
+ combinations = itertools.combinations
114
+ # Exact solution for k = {1,2}
115
+ # There is a linear time algorithm for triconnectivity, if we had an
116
+ # implementation available we could start from k = 4.
117
+ for component in nx.connected_components(G):
118
+ # isolated nodes have connectivity 0
119
+ comp = set(component)
120
+ if len(comp) > 1:
121
+ k_components[1].append(comp)
122
+ for bicomponent in nx.biconnected_components(G):
123
+ # avoid considering dyads as bicomponents
124
+ bicomp = set(bicomponent)
125
+ if len(bicomp) > 2:
126
+ k_components[2].append(bicomp)
127
+ # There is no k-component of k > maximum core number
128
+ # \kappa(G) <= \lambda(G) <= \delta(G)
129
+ g_cnumber = core_number(G)
130
+ max_core = max(g_cnumber.values())
131
+ for k in range(3, max_core + 1):
132
+ C = k_core(G, k, core_number=g_cnumber)
133
+ for nodes in biconnected_components(C):
134
+ # Build a subgraph SG induced by the nodes that are part of
135
+ # each biconnected component of the k-core subgraph C.
136
+ if len(nodes) < k:
137
+ continue
138
+ SG = G.subgraph(nodes)
139
+ # Build auxiliary graph
140
+ H = _AntiGraph()
141
+ H.add_nodes_from(SG.nodes())
142
+ for u, v in combinations(SG, 2):
143
+ K = node_connectivity(SG, u, v, cutoff=k)
144
+ if k > K:
145
+ H.add_edge(u, v)
146
+ for h_nodes in biconnected_components(H):
147
+ if len(h_nodes) <= k:
148
+ continue
149
+ SH = H.subgraph(h_nodes)
150
+ for Gc in _cliques_heuristic(SG, SH, k, min_density):
151
+ for k_nodes in biconnected_components(Gc):
152
+ Gk = nx.k_core(SG.subgraph(k_nodes), k)
153
+ if len(Gk) <= k:
154
+ continue
155
+ k_components[k].append(set(Gk))
156
+ return k_components
157
+
158
+
159
+ def _cliques_heuristic(G, H, k, min_density):
160
+ h_cnumber = nx.core_number(H)
161
+ for i, c_value in enumerate(sorted(set(h_cnumber.values()), reverse=True)):
162
+ cands = {n for n, c in h_cnumber.items() if c == c_value}
163
+ # Skip checking for overlap for the highest core value
164
+ if i == 0:
165
+ overlap = False
166
+ else:
167
+ overlap = set.intersection(
168
+ *[{x for x in H[n] if x not in cands} for n in cands]
169
+ )
170
+ if overlap and len(overlap) < k:
171
+ SH = H.subgraph(cands | overlap)
172
+ else:
173
+ SH = H.subgraph(cands)
174
+ sh_cnumber = nx.core_number(SH)
175
+ SG = nx.k_core(G.subgraph(SH), k)
176
+ while not (_same(sh_cnumber) and nx.density(SH) >= min_density):
177
+ # This subgraph must be writable => .copy()
178
+ SH = H.subgraph(SG).copy()
179
+ if len(SH) <= k:
180
+ break
181
+ sh_cnumber = nx.core_number(SH)
182
+ sh_deg = dict(SH.degree())
183
+ min_deg = min(sh_deg.values())
184
+ SH.remove_nodes_from(n for n, d in sh_deg.items() if d == min_deg)
185
+ SG = nx.k_core(G.subgraph(SH), k)
186
+ else:
187
+ yield SG
188
+
189
+
190
+ def _same(measure, tol=0):
191
+ vals = set(measure.values())
192
+ if (max(vals) - min(vals)) <= tol:
193
+ return True
194
+ return False
195
+
196
+
197
+ class _AntiGraph(nx.Graph):
198
+ """
199
+ Class for complement graphs.
200
+
201
+ The main goal is to be able to work with big and dense graphs with
202
+ a low memory footprint.
203
+
204
+ In this class you add the edges that *do not exist* in the dense graph,
205
+ the report methods of the class return the neighbors, the edges and
206
+ the degree as if it was the dense graph. Thus it's possible to use
207
+ an instance of this class with some of NetworkX functions. In this
208
+ case we only use k-core, connected_components, and biconnected_components.
209
+ """
210
+
211
+ all_edge_dict = {"weight": 1}
212
+
213
+ def single_edge_dict(self):
214
+ return self.all_edge_dict
215
+
216
+ edge_attr_dict_factory = single_edge_dict # type: ignore[assignment]
217
+
218
+ def __getitem__(self, n):
219
+ """Returns a dict of neighbors of node n in the dense graph.
220
+
221
+ Parameters
222
+ ----------
223
+ n : node
224
+ A node in the graph.
225
+
226
+ Returns
227
+ -------
228
+ adj_dict : dictionary
229
+ The adjacency dictionary for nodes connected to n.
230
+
231
+ """
232
+ all_edge_dict = self.all_edge_dict
233
+ return {
234
+ node: all_edge_dict for node in set(self._adj) - set(self._adj[n]) - {n}
235
+ }
236
+
237
+ def neighbors(self, n):
238
+ """Returns an iterator over all neighbors of node n in the
239
+ dense graph.
240
+ """
241
+ try:
242
+ return iter(set(self._adj) - set(self._adj[n]) - {n})
243
+ except KeyError as err:
244
+ raise NetworkXError(f"The node {n} is not in the graph.") from err
245
+
246
+ class AntiAtlasView(Mapping):
247
+ """An adjacency inner dict for AntiGraph"""
248
+
249
+ def __init__(self, graph, node):
250
+ self._graph = graph
251
+ self._atlas = graph._adj[node]
252
+ self._node = node
253
+
254
+ def __len__(self):
255
+ return len(self._graph) - len(self._atlas) - 1
256
+
257
+ def __iter__(self):
258
+ return (n for n in self._graph if n not in self._atlas and n != self._node)
259
+
260
+ def __getitem__(self, nbr):
261
+ nbrs = set(self._graph._adj) - set(self._atlas) - {self._node}
262
+ if nbr in nbrs:
263
+ return self._graph.all_edge_dict
264
+ raise KeyError(nbr)
265
+
266
+ class AntiAdjacencyView(AntiAtlasView):
267
+ """An adjacency outer dict for AntiGraph"""
268
+
269
+ def __init__(self, graph):
270
+ self._graph = graph
271
+ self._atlas = graph._adj
272
+
273
+ def __len__(self):
274
+ return len(self._atlas)
275
+
276
+ def __iter__(self):
277
+ return iter(self._graph)
278
+
279
+ def __getitem__(self, node):
280
+ if node not in self._graph:
281
+ raise KeyError(node)
282
+ return self._graph.AntiAtlasView(self._graph, node)
283
+
284
+ @cached_property
285
+ def adj(self):
286
+ return self.AntiAdjacencyView(self)
287
+
288
+ def subgraph(self, nodes):
289
+ """This subgraph method returns a full AntiGraph. Not a View"""
290
+ nodes = set(nodes)
291
+ G = _AntiGraph()
292
+ G.add_nodes_from(nodes)
293
+ for n in G:
294
+ Gnbrs = G.adjlist_inner_dict_factory()
295
+ G._adj[n] = Gnbrs
296
+ for nbr, d in self._adj[n].items():
297
+ if nbr in G._adj:
298
+ Gnbrs[nbr] = d
299
+ G._adj[nbr][n] = d
300
+ G.graph = self.graph
301
+ return G
302
+
303
+ class AntiDegreeView(nx.reportviews.DegreeView):
304
+ def __iter__(self):
305
+ all_nodes = set(self._succ)
306
+ for n in self._nodes:
307
+ nbrs = all_nodes - set(self._succ[n]) - {n}
308
+ yield (n, len(nbrs))
309
+
310
+ def __getitem__(self, n):
311
+ nbrs = set(self._succ) - set(self._succ[n]) - {n}
312
+ # AntiGraph is a ThinGraph so all edges have weight 1
313
+ return len(nbrs) + (n in nbrs)
314
+
315
+ @cached_property
316
+ def degree(self):
317
+ """Returns an iterator for (node, degree) and degree for single node.
318
+
319
+ The node degree is the number of edges adjacent to the node.
320
+
321
+ Parameters
322
+ ----------
323
+ nbunch : iterable container, optional (default=all nodes)
324
+ A container of nodes. The container will be iterated
325
+ through once.
326
+
327
+ weight : string or None, optional (default=None)
328
+ The edge attribute that holds the numerical value used
329
+ as a weight. If None, then each edge has weight 1.
330
+ The degree is the sum of the edge weights adjacent to the node.
331
+
332
+ Returns
333
+ -------
334
+ deg:
335
+ Degree of the node, if a single node is passed as argument.
336
+ nd_iter : an iterator
337
+ The iterator returns two-tuples of (node, degree).
338
+
339
+ See Also
340
+ --------
341
+ degree
342
+
343
+ Examples
344
+ --------
345
+ >>> G = nx.path_graph(4)
346
+ >>> G.degree(0) # node 0 with degree 1
347
+ 1
348
+ >>> list(G.degree([0, 1]))
349
+ [(0, 1), (1, 2)]
350
+
351
+ """
352
+ return self.AntiDegreeView(self)
353
+
354
+ def adjacency(self):
355
+ """Returns an iterator of (node, adjacency set) tuples for all nodes
356
+ in the dense graph.
357
+
358
+ This is the fastest way to look at every edge.
359
+ For directed graphs, only outgoing adjacencies are included.
360
+
361
+ Returns
362
+ -------
363
+ adj_iter : iterator
364
+ An iterator of (node, adjacency set) for all nodes in
365
+ the graph.
366
+
367
+ """
368
+ for n in self._adj:
369
+ yield (n, set(self._adj) - set(self._adj[n]) - {n})
.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/matching.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ **************
3
+ Graph Matching
4
+ **************
5
+
6
+ Given a graph G = (V,E), a matching M in G is a set of pairwise non-adjacent
7
+ edges; that is, no two edges share a common vertex.
8
+
9
+ `Wikipedia: Matching <https://en.wikipedia.org/wiki/Matching_(graph_theory)>`_
10
+ """
11
+
12
+ import networkx as nx
13
+
14
+ __all__ = ["min_maximal_matching"]
15
+
16
+
17
+ @nx._dispatchable
18
+ def min_maximal_matching(G):
19
+ r"""Returns the minimum maximal matching of G. That is, out of all maximal
20
+ matchings of the graph G, the smallest is returned.
21
+
22
+ Parameters
23
+ ----------
24
+ G : NetworkX graph
25
+ Undirected graph
26
+
27
+ Returns
28
+ -------
29
+ min_maximal_matching : set
30
+ Returns a set of edges such that no two edges share a common endpoint
31
+ and every edge not in the set shares some common endpoint in the set.
32
+ Cardinality will be 2*OPT in the worst case.
33
+
34
+ Notes
35
+ -----
36
+ The algorithm computes an approximate solution for the minimum maximal
37
+ cardinality matching problem. The solution is no more than 2 * OPT in size.
38
+ Runtime is $O(|E|)$.
39
+
40
+ References
41
+ ----------
42
+ .. [1] Vazirani, Vijay Approximation Algorithms (2001)
43
+ """
44
+ return nx.maximal_matching(G)
.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/maxcut.py ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import networkx as nx
2
+ from networkx.utils.decorators import not_implemented_for, py_random_state
3
+
4
+ __all__ = ["randomized_partitioning", "one_exchange"]
5
+
6
+
7
+ @not_implemented_for("directed")
8
+ @not_implemented_for("multigraph")
9
+ @py_random_state(1)
10
+ @nx._dispatchable(edge_attrs="weight")
11
+ def randomized_partitioning(G, seed=None, p=0.5, weight=None):
12
+ """Compute a random partitioning of the graph nodes and its cut value.
13
+
14
+ A partitioning is calculated by observing each node
15
+ and deciding to add it to the partition with probability `p`,
16
+ returning a random cut and its corresponding value (the
17
+ sum of weights of edges connecting different partitions).
18
+
19
+ Parameters
20
+ ----------
21
+ G : NetworkX graph
22
+
23
+ seed : integer, random_state, or None (default)
24
+ Indicator of random number generation state.
25
+ See :ref:`Randomness<randomness>`.
26
+
27
+ p : scalar
28
+ Probability for each node to be part of the first partition.
29
+ Should be in [0,1]
30
+
31
+ weight : object
32
+ Edge attribute key to use as weight. If not specified, edges
33
+ have weight one.
34
+
35
+ Returns
36
+ -------
37
+ cut_size : scalar
38
+ Value of the minimum cut.
39
+
40
+ partition : pair of node sets
41
+ A partitioning of the nodes that defines a minimum cut.
42
+
43
+ Examples
44
+ --------
45
+ >>> G = nx.complete_graph(5)
46
+ >>> cut_size, partition = nx.approximation.randomized_partitioning(G, seed=1)
47
+ >>> cut_size
48
+ 6
49
+ >>> partition
50
+ ({0, 3, 4}, {1, 2})
51
+
52
+ Raises
53
+ ------
54
+ NetworkXNotImplemented
55
+ If the graph is directed or is a multigraph.
56
+ """
57
+ cut = {node for node in G.nodes() if seed.random() < p}
58
+ cut_size = nx.algorithms.cut_size(G, cut, weight=weight)
59
+ partition = (cut, G.nodes - cut)
60
+ return cut_size, partition
61
+
62
+
63
+ def _swap_node_partition(cut, node):
64
+ return cut - {node} if node in cut else cut.union({node})
65
+
66
+
67
+ @not_implemented_for("directed")
68
+ @not_implemented_for("multigraph")
69
+ @py_random_state(2)
70
+ @nx._dispatchable(edge_attrs="weight")
71
+ def one_exchange(G, initial_cut=None, seed=None, weight=None):
72
+ """Compute a partitioning of the graphs nodes and the corresponding cut value.
73
+
74
+ Use a greedy one exchange strategy to find a locally maximal cut
75
+ and its value, it works by finding the best node (one that gives
76
+ the highest gain to the cut value) to add to the current cut
77
+ and repeats this process until no improvement can be made.
78
+
79
+ Parameters
80
+ ----------
81
+ G : networkx Graph
82
+ Graph to find a maximum cut for.
83
+
84
+ initial_cut : set
85
+ Cut to use as a starting point. If not supplied the algorithm
86
+ starts with an empty cut.
87
+
88
+ seed : integer, random_state, or None (default)
89
+ Indicator of random number generation state.
90
+ See :ref:`Randomness<randomness>`.
91
+
92
+ weight : object
93
+ Edge attribute key to use as weight. If not specified, edges
94
+ have weight one.
95
+
96
+ Returns
97
+ -------
98
+ cut_value : scalar
99
+ Value of the maximum cut.
100
+
101
+ partition : pair of node sets
102
+ A partitioning of the nodes that defines a maximum cut.
103
+
104
+ Examples
105
+ --------
106
+ >>> G = nx.complete_graph(5)
107
+ >>> curr_cut_size, partition = nx.approximation.one_exchange(G, seed=1)
108
+ >>> curr_cut_size
109
+ 6
110
+ >>> partition
111
+ ({0, 2}, {1, 3, 4})
112
+
113
+ Raises
114
+ ------
115
+ NetworkXNotImplemented
116
+ If the graph is directed or is a multigraph.
117
+ """
118
+ if initial_cut is None:
119
+ initial_cut = set()
120
+ cut = set(initial_cut)
121
+ current_cut_size = nx.algorithms.cut_size(G, cut, weight=weight)
122
+ while True:
123
+ nodes = list(G.nodes())
124
+ # Shuffling the nodes ensures random tie-breaks in the following call to max
125
+ seed.shuffle(nodes)
126
+ best_node_to_swap = max(
127
+ nodes,
128
+ key=lambda v: nx.algorithms.cut_size(
129
+ G, _swap_node_partition(cut, v), weight=weight
130
+ ),
131
+ default=None,
132
+ )
133
+ potential_cut = _swap_node_partition(cut, best_node_to_swap)
134
+ potential_cut_size = nx.algorithms.cut_size(G, potential_cut, weight=weight)
135
+
136
+ if potential_cut_size > current_cut_size:
137
+ cut = potential_cut
138
+ current_cut_size = potential_cut_size
139
+ else:
140
+ break
141
+
142
+ partition = (cut, G.nodes - cut)
143
+ return current_cut_size, partition
.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/ramsey.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Ramsey numbers.
3
+ """
4
+
5
+ import networkx as nx
6
+ from networkx.utils import not_implemented_for
7
+
8
+ from ...utils import arbitrary_element
9
+
10
+ __all__ = ["ramsey_R2"]
11
+
12
+
13
+ @not_implemented_for("directed")
14
+ @not_implemented_for("multigraph")
15
+ @nx._dispatchable
16
+ def ramsey_R2(G):
17
+ r"""Compute the largest clique and largest independent set in `G`.
18
+
19
+ This can be used to estimate bounds for the 2-color
20
+ Ramsey number `R(2;s,t)` for `G`.
21
+
22
+ This is a recursive implementation which could run into trouble
23
+ for large recursions. Note that self-loop edges are ignored.
24
+
25
+ Parameters
26
+ ----------
27
+ G : NetworkX graph
28
+ Undirected graph
29
+
30
+ Returns
31
+ -------
32
+ max_pair : (set, set) tuple
33
+ Maximum clique, Maximum independent set.
34
+
35
+ Raises
36
+ ------
37
+ NetworkXNotImplemented
38
+ If the graph is directed or is a multigraph.
39
+ """
40
+ if not G:
41
+ return set(), set()
42
+
43
+ node = arbitrary_element(G)
44
+ nbrs = (nbr for nbr in nx.all_neighbors(G, node) if nbr != node)
45
+ nnbrs = nx.non_neighbors(G, node)
46
+ c_1, i_1 = ramsey_R2(G.subgraph(nbrs).copy())
47
+ c_2, i_2 = ramsey_R2(G.subgraph(nnbrs).copy())
48
+
49
+ c_1.add(node)
50
+ i_2.add(node)
51
+ # Choose the larger of the two cliques and the larger of the two
52
+ # independent sets, according to cardinality.
53
+ return max(c_1, c_2, key=len), max(i_1, i_2, key=len)
.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/steinertree.py ADDED
@@ -0,0 +1,231 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from itertools import chain
2
+
3
+ import networkx as nx
4
+ from networkx.utils import not_implemented_for, pairwise
5
+
6
+ __all__ = ["metric_closure", "steiner_tree"]
7
+
8
+
9
+ @not_implemented_for("directed")
10
+ @nx._dispatchable(edge_attrs="weight", returns_graph=True)
11
+ def metric_closure(G, weight="weight"):
12
+ """Return the metric closure of a graph.
13
+
14
+ The metric closure of a graph *G* is the complete graph in which each edge
15
+ is weighted by the shortest path distance between the nodes in *G* .
16
+
17
+ Parameters
18
+ ----------
19
+ G : NetworkX graph
20
+
21
+ Returns
22
+ -------
23
+ NetworkX graph
24
+ Metric closure of the graph `G`.
25
+
26
+ """
27
+ M = nx.Graph()
28
+
29
+ Gnodes = set(G)
30
+
31
+ # check for connected graph while processing first node
32
+ all_paths_iter = nx.all_pairs_dijkstra(G, weight=weight)
33
+ u, (distance, path) = next(all_paths_iter)
34
+ if Gnodes - set(distance):
35
+ msg = "G is not a connected graph. metric_closure is not defined."
36
+ raise nx.NetworkXError(msg)
37
+ Gnodes.remove(u)
38
+ for v in Gnodes:
39
+ M.add_edge(u, v, distance=distance[v], path=path[v])
40
+
41
+ # first node done -- now process the rest
42
+ for u, (distance, path) in all_paths_iter:
43
+ Gnodes.remove(u)
44
+ for v in Gnodes:
45
+ M.add_edge(u, v, distance=distance[v], path=path[v])
46
+
47
+ return M
48
+
49
+
50
+ def _mehlhorn_steiner_tree(G, terminal_nodes, weight):
51
+ paths = nx.multi_source_dijkstra_path(G, terminal_nodes)
52
+
53
+ d_1 = {}
54
+ s = {}
55
+ for v in G.nodes():
56
+ s[v] = paths[v][0]
57
+ d_1[(v, s[v])] = len(paths[v]) - 1
58
+
59
+ # G1-G4 names match those from the Mehlhorn 1988 paper.
60
+ G_1_prime = nx.Graph()
61
+ for u, v, data in G.edges(data=True):
62
+ su, sv = s[u], s[v]
63
+ weight_here = d_1[(u, su)] + data.get(weight, 1) + d_1[(v, sv)]
64
+ if not G_1_prime.has_edge(su, sv):
65
+ G_1_prime.add_edge(su, sv, weight=weight_here)
66
+ else:
67
+ new_weight = min(weight_here, G_1_prime[su][sv]["weight"])
68
+ G_1_prime.add_edge(su, sv, weight=new_weight)
69
+
70
+ G_2 = nx.minimum_spanning_edges(G_1_prime, data=True)
71
+
72
+ G_3 = nx.Graph()
73
+ for u, v, d in G_2:
74
+ path = nx.shortest_path(G, u, v, weight)
75
+ for n1, n2 in pairwise(path):
76
+ G_3.add_edge(n1, n2)
77
+
78
+ G_3_mst = list(nx.minimum_spanning_edges(G_3, data=False))
79
+ if G.is_multigraph():
80
+ G_3_mst = (
81
+ (u, v, min(G[u][v], key=lambda k: G[u][v][k][weight])) for u, v in G_3_mst
82
+ )
83
+ G_4 = G.edge_subgraph(G_3_mst).copy()
84
+ _remove_nonterminal_leaves(G_4, terminal_nodes)
85
+ return G_4.edges()
86
+
87
+
88
+ def _kou_steiner_tree(G, terminal_nodes, weight):
89
+ # H is the subgraph induced by terminal_nodes in the metric closure M of G.
90
+ M = metric_closure(G, weight=weight)
91
+ H = M.subgraph(terminal_nodes)
92
+
93
+ # Use the 'distance' attribute of each edge provided by M.
94
+ mst_edges = nx.minimum_spanning_edges(H, weight="distance", data=True)
95
+
96
+ # Create an iterator over each edge in each shortest path; repeats are okay
97
+ mst_all_edges = chain.from_iterable(pairwise(d["path"]) for u, v, d in mst_edges)
98
+ if G.is_multigraph():
99
+ mst_all_edges = (
100
+ (u, v, min(G[u][v], key=lambda k: G[u][v][k][weight]))
101
+ for u, v in mst_all_edges
102
+ )
103
+
104
+ # Find the MST again, over this new set of edges
105
+ G_S = G.edge_subgraph(mst_all_edges)
106
+ T_S = nx.minimum_spanning_edges(G_S, weight="weight", data=False)
107
+
108
+ # Leaf nodes that are not terminal might still remain; remove them here
109
+ T_H = G.edge_subgraph(T_S).copy()
110
+ _remove_nonterminal_leaves(T_H, terminal_nodes)
111
+
112
+ return T_H.edges()
113
+
114
+
115
+ def _remove_nonterminal_leaves(G, terminals):
116
+ terminal_set = set(terminals)
117
+ leaves = {n for n in G if len(set(G[n]) - {n}) == 1}
118
+ nonterminal_leaves = leaves - terminal_set
119
+
120
+ while nonterminal_leaves:
121
+ # Removing a node may create new non-terminal leaves, so we limit
122
+ # search for candidate non-terminal nodes to neighbors of current
123
+ # non-terminal nodes
124
+ candidate_leaves = set.union(*(set(G[n]) for n in nonterminal_leaves))
125
+ candidate_leaves -= nonterminal_leaves | terminal_set
126
+ # Remove current set of non-terminal nodes
127
+ G.remove_nodes_from(nonterminal_leaves)
128
+ # Find any new non-terminal nodes from the set of candidates
129
+ leaves = {n for n in candidate_leaves if len(set(G[n]) - {n}) == 1}
130
+ nonterminal_leaves = leaves - terminal_set
131
+
132
+
133
+ ALGORITHMS = {
134
+ "kou": _kou_steiner_tree,
135
+ "mehlhorn": _mehlhorn_steiner_tree,
136
+ }
137
+
138
+
139
+ @not_implemented_for("directed")
140
+ @nx._dispatchable(preserve_all_attrs=True, returns_graph=True)
141
+ def steiner_tree(G, terminal_nodes, weight="weight", method=None):
142
+ r"""Return an approximation to the minimum Steiner tree of a graph.
143
+
144
+ The minimum Steiner tree of `G` w.r.t a set of `terminal_nodes` (also *S*)
145
+ is a tree within `G` that spans those nodes and has minimum size (sum of
146
+ edge weights) among all such trees.
147
+
148
+ The approximation algorithm is specified with the `method` keyword
149
+ argument. All three available algorithms produce a tree whose weight is
150
+ within a ``(2 - (2 / l))`` factor of the weight of the optimal Steiner tree,
151
+ where ``l`` is the minimum number of leaf nodes across all possible Steiner
152
+ trees.
153
+
154
+ * ``"kou"`` [2]_ (runtime $O(|S| |V|^2)$) computes the minimum spanning tree of
155
+ the subgraph of the metric closure of *G* induced by the terminal nodes,
156
+ where the metric closure of *G* is the complete graph in which each edge is
157
+ weighted by the shortest path distance between the nodes in *G*.
158
+
159
+ * ``"mehlhorn"`` [3]_ (runtime $O(|E|+|V|\log|V|)$) modifies Kou et al.'s
160
+ algorithm, beginning by finding the closest terminal node for each
161
+ non-terminal. This data is used to create a complete graph containing only
162
+ the terminal nodes, in which edge is weighted with the shortest path
163
+ distance between them. The algorithm then proceeds in the same way as Kou
164
+ et al..
165
+
166
+ Parameters
167
+ ----------
168
+ G : NetworkX graph
169
+
170
+ terminal_nodes : list
171
+ A list of terminal nodes for which minimum steiner tree is
172
+ to be found.
173
+
174
+ weight : string (default = 'weight')
175
+ Use the edge attribute specified by this string as the edge weight.
176
+ Any edge attribute not present defaults to 1.
177
+
178
+ method : string, optional (default = 'mehlhorn')
179
+ The algorithm to use to approximate the Steiner tree.
180
+ Supported options: 'kou', 'mehlhorn'.
181
+ Other inputs produce a ValueError.
182
+
183
+ Returns
184
+ -------
185
+ NetworkX graph
186
+ Approximation to the minimum steiner tree of `G` induced by
187
+ `terminal_nodes` .
188
+
189
+ Raises
190
+ ------
191
+ NetworkXNotImplemented
192
+ If `G` is directed.
193
+
194
+ ValueError
195
+ If the specified `method` is not supported.
196
+
197
+ Notes
198
+ -----
199
+ For multigraphs, the edge between two nodes with minimum weight is the
200
+ edge put into the Steiner tree.
201
+
202
+
203
+ References
204
+ ----------
205
+ .. [1] Steiner_tree_problem on Wikipedia.
206
+ https://en.wikipedia.org/wiki/Steiner_tree_problem
207
+ .. [2] Kou, L., G. Markowsky, and L. Berman. 1981.
208
+ ‘A Fast Algorithm for Steiner Trees’.
209
+ Acta Informatica 15 (2): 141–45.
210
+ https://doi.org/10.1007/BF00288961.
211
+ .. [3] Mehlhorn, Kurt. 1988.
212
+ ‘A Faster Approximation Algorithm for the Steiner Problem in Graphs’.
213
+ Information Processing Letters 27 (3): 125–28.
214
+ https://doi.org/10.1016/0020-0190(88)90066-X.
215
+ """
216
+ if method is None:
217
+ method = "mehlhorn"
218
+
219
+ try:
220
+ algo = ALGORITHMS[method]
221
+ except KeyError as e:
222
+ raise ValueError(f"{method} is not a valid choice for an algorithm.") from e
223
+
224
+ edges = algo(G, terminal_nodes, weight)
225
+ # For multigraph we should add the minimal weight edge keys
226
+ if G.is_multigraph():
227
+ edges = (
228
+ (u, v, min(G[u][v], key=lambda k: G[u][v][k][weight])) for u, v in edges
229
+ )
230
+ T = G.edge_subgraph(edges)
231
+ return T
.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/traveling_salesman.py ADDED
@@ -0,0 +1,1501 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ =================================
3
+ Travelling Salesman Problem (TSP)
4
+ =================================
5
+
6
+ Implementation of approximate algorithms
7
+ for solving and approximating the TSP problem.
8
+
9
+ Categories of algorithms which are implemented:
10
+
11
+ - Christofides (provides a 3/2-approximation of TSP)
12
+ - Greedy
13
+ - Simulated Annealing (SA)
14
+ - Threshold Accepting (TA)
15
+ - Asadpour Asymmetric Traveling Salesman Algorithm
16
+
17
+ The Travelling Salesman Problem tries to find, given the weight
18
+ (distance) between all points where a salesman has to visit, the
19
+ route so that:
20
+
21
+ - The total distance (cost) which the salesman travels is minimized.
22
+ - The salesman returns to the starting point.
23
+ - Note that for a complete graph, the salesman visits each point once.
24
+
25
+ The function `travelling_salesman_problem` allows for incomplete
26
+ graphs by finding all-pairs shortest paths, effectively converting
27
+ the problem to a complete graph problem. It calls one of the
28
+ approximate methods on that problem and then converts the result
29
+ back to the original graph using the previously found shortest paths.
30
+
31
+ TSP is an NP-hard problem in combinatorial optimization,
32
+ important in operations research and theoretical computer science.
33
+
34
+ http://en.wikipedia.org/wiki/Travelling_salesman_problem
35
+ """
36
+
37
+ import math
38
+
39
+ import networkx as nx
40
+ from networkx.algorithms.tree.mst import random_spanning_tree
41
+ from networkx.utils import not_implemented_for, pairwise, py_random_state
42
+
43
+ __all__ = [
44
+ "traveling_salesman_problem",
45
+ "christofides",
46
+ "asadpour_atsp",
47
+ "greedy_tsp",
48
+ "simulated_annealing_tsp",
49
+ "threshold_accepting_tsp",
50
+ ]
51
+
52
+
53
+ def swap_two_nodes(soln, seed):
54
+ """Swap two nodes in `soln` to give a neighbor solution.
55
+
56
+ Parameters
57
+ ----------
58
+ soln : list of nodes
59
+ Current cycle of nodes
60
+
61
+ seed : integer, random_state, or None (default)
62
+ Indicator of random number generation state.
63
+ See :ref:`Randomness<randomness>`.
64
+
65
+ Returns
66
+ -------
67
+ list
68
+ The solution after move is applied. (A neighbor solution.)
69
+
70
+ Notes
71
+ -----
72
+ This function assumes that the incoming list `soln` is a cycle
73
+ (that the first and last element are the same) and also that
74
+ we don't want any move to change the first node in the list
75
+ (and thus not the last node either).
76
+
77
+ The input list is changed as well as returned. Make a copy if needed.
78
+
79
+ See Also
80
+ --------
81
+ move_one_node
82
+ """
83
+ a, b = seed.sample(range(1, len(soln) - 1), k=2)
84
+ soln[a], soln[b] = soln[b], soln[a]
85
+ return soln
86
+
87
+
88
+ def move_one_node(soln, seed):
89
+ """Move one node to another position to give a neighbor solution.
90
+
91
+ The node to move and the position to move to are chosen randomly.
92
+ The first and last nodes are left untouched as soln must be a cycle
93
+ starting at that node.
94
+
95
+ Parameters
96
+ ----------
97
+ soln : list of nodes
98
+ Current cycle of nodes
99
+
100
+ seed : integer, random_state, or None (default)
101
+ Indicator of random number generation state.
102
+ See :ref:`Randomness<randomness>`.
103
+
104
+ Returns
105
+ -------
106
+ list
107
+ The solution after move is applied. (A neighbor solution.)
108
+
109
+ Notes
110
+ -----
111
+ This function assumes that the incoming list `soln` is a cycle
112
+ (that the first and last element are the same) and also that
113
+ we don't want any move to change the first node in the list
114
+ (and thus not the last node either).
115
+
116
+ The input list is changed as well as returned. Make a copy if needed.
117
+
118
+ See Also
119
+ --------
120
+ swap_two_nodes
121
+ """
122
+ a, b = seed.sample(range(1, len(soln) - 1), k=2)
123
+ soln.insert(b, soln.pop(a))
124
+ return soln
125
+
126
+
127
+ @not_implemented_for("directed")
128
+ @nx._dispatchable(edge_attrs="weight")
129
+ def christofides(G, weight="weight", tree=None):
130
+ """Approximate a solution of the traveling salesman problem
131
+
132
+ Compute a 3/2-approximation of the traveling salesman problem
133
+ in a complete undirected graph using Christofides [1]_ algorithm.
134
+
135
+ Parameters
136
+ ----------
137
+ G : Graph
138
+ `G` should be a complete weighted undirected graph.
139
+ The distance between all pairs of nodes should be included.
140
+
141
+ weight : string, optional (default="weight")
142
+ Edge data key corresponding to the edge weight.
143
+ If any edge does not have this attribute the weight is set to 1.
144
+
145
+ tree : NetworkX graph or None (default: None)
146
+ A minimum spanning tree of G. Or, if None, the minimum spanning
147
+ tree is computed using :func:`networkx.minimum_spanning_tree`
148
+
149
+ Returns
150
+ -------
151
+ list
152
+ List of nodes in `G` along a cycle with a 3/2-approximation of
153
+ the minimal Hamiltonian cycle.
154
+
155
+ References
156
+ ----------
157
+ .. [1] Christofides, Nicos. "Worst-case analysis of a new heuristic for
158
+ the travelling salesman problem." No. RR-388. Carnegie-Mellon Univ
159
+ Pittsburgh Pa Management Sciences Research Group, 1976.
160
+ """
161
+ # Remove selfloops if necessary
162
+ loop_nodes = nx.nodes_with_selfloops(G)
163
+ try:
164
+ node = next(loop_nodes)
165
+ except StopIteration:
166
+ pass
167
+ else:
168
+ G = G.copy()
169
+ G.remove_edge(node, node)
170
+ G.remove_edges_from((n, n) for n in loop_nodes)
171
+ # Check that G is a complete graph
172
+ N = len(G) - 1
173
+ # This check ignores selfloops which is what we want here.
174
+ if any(len(nbrdict) != N for n, nbrdict in G.adj.items()):
175
+ raise nx.NetworkXError("G must be a complete graph.")
176
+
177
+ if tree is None:
178
+ tree = nx.minimum_spanning_tree(G, weight=weight)
179
+ L = G.copy()
180
+ L.remove_nodes_from([v for v, degree in tree.degree if not (degree % 2)])
181
+ MG = nx.MultiGraph()
182
+ MG.add_edges_from(tree.edges)
183
+ edges = nx.min_weight_matching(L, weight=weight)
184
+ MG.add_edges_from(edges)
185
+ return _shortcutting(nx.eulerian_circuit(MG))
186
+
187
+
188
+ def _shortcutting(circuit):
189
+ """Remove duplicate nodes in the path"""
190
+ nodes = []
191
+ for u, v in circuit:
192
+ if v in nodes:
193
+ continue
194
+ if not nodes:
195
+ nodes.append(u)
196
+ nodes.append(v)
197
+ nodes.append(nodes[0])
198
+ return nodes
199
+
200
+
201
+ @nx._dispatchable(edge_attrs="weight")
202
+ def traveling_salesman_problem(
203
+ G, weight="weight", nodes=None, cycle=True, method=None, **kwargs
204
+ ):
205
+ """Find the shortest path in `G` connecting specified nodes
206
+
207
+ This function allows approximate solution to the traveling salesman
208
+ problem on networks that are not complete graphs and/or where the
209
+ salesman does not need to visit all nodes.
210
+
211
+ This function proceeds in two steps. First, it creates a complete
212
+ graph using the all-pairs shortest_paths between nodes in `nodes`.
213
+ Edge weights in the new graph are the lengths of the paths
214
+ between each pair of nodes in the original graph.
215
+ Second, an algorithm (default: `christofides` for undirected and
216
+ `asadpour_atsp` for directed) is used to approximate the minimal Hamiltonian
217
+ cycle on this new graph. The available algorithms are:
218
+
219
+ - christofides
220
+ - greedy_tsp
221
+ - simulated_annealing_tsp
222
+ - threshold_accepting_tsp
223
+ - asadpour_atsp
224
+
225
+ Once the Hamiltonian Cycle is found, this function post-processes to
226
+ accommodate the structure of the original graph. If `cycle` is ``False``,
227
+ the biggest weight edge is removed to make a Hamiltonian path.
228
+ Then each edge on the new complete graph used for that analysis is
229
+ replaced by the shortest_path between those nodes on the original graph.
230
+ If the input graph `G` includes edges with weights that do not adhere to
231
+ the triangle inequality, such as when `G` is not a complete graph (i.e
232
+ length of non-existent edges is infinity), then the returned path may
233
+ contain some repeating nodes (other than the starting node).
234
+
235
+ Parameters
236
+ ----------
237
+ G : NetworkX graph
238
+ A possibly weighted graph
239
+
240
+ nodes : collection of nodes (default=G.nodes)
241
+ collection (list, set, etc.) of nodes to visit
242
+
243
+ weight : string, optional (default="weight")
244
+ Edge data key corresponding to the edge weight.
245
+ If any edge does not have this attribute the weight is set to 1.
246
+
247
+ cycle : bool (default: True)
248
+ Indicates whether a cycle should be returned, or a path.
249
+ Note: the cycle is the approximate minimal cycle.
250
+ The path simply removes the biggest edge in that cycle.
251
+
252
+ method : function (default: None)
253
+ A function that returns a cycle on all nodes and approximates
254
+ the solution to the traveling salesman problem on a complete
255
+ graph. The returned cycle is then used to find a corresponding
256
+ solution on `G`. `method` should be callable; take inputs
257
+ `G`, and `weight`; and return a list of nodes along the cycle.
258
+
259
+ Provided options include :func:`christofides`, :func:`greedy_tsp`,
260
+ :func:`simulated_annealing_tsp` and :func:`threshold_accepting_tsp`.
261
+
262
+ If `method is None`: use :func:`christofides` for undirected `G` and
263
+ :func:`asadpour_atsp` for directed `G`.
264
+
265
+ **kwargs : dict
266
+ Other keyword arguments to be passed to the `method` function passed in.
267
+
268
+ Returns
269
+ -------
270
+ list
271
+ List of nodes in `G` along a path with an approximation of the minimal
272
+ path through `nodes`.
273
+
274
+ Raises
275
+ ------
276
+ NetworkXError
277
+ If `G` is a directed graph it has to be strongly connected or the
278
+ complete version cannot be generated.
279
+
280
+ Examples
281
+ --------
282
+ >>> tsp = nx.approximation.traveling_salesman_problem
283
+ >>> G = nx.cycle_graph(9)
284
+ >>> G[4][5]["weight"] = 5 # all other weights are 1
285
+ >>> tsp(G, nodes=[3, 6])
286
+ [3, 2, 1, 0, 8, 7, 6, 7, 8, 0, 1, 2, 3]
287
+ >>> path = tsp(G, cycle=False)
288
+ >>> path in ([4, 3, 2, 1, 0, 8, 7, 6, 5], [5, 6, 7, 8, 0, 1, 2, 3, 4])
289
+ True
290
+
291
+ While no longer required, you can still build (curry) your own function
292
+ to provide parameter values to the methods.
293
+
294
+ >>> SA_tsp = nx.approximation.simulated_annealing_tsp
295
+ >>> method = lambda G, weight: SA_tsp(G, "greedy", weight=weight, temp=500)
296
+ >>> path = tsp(G, cycle=False, method=method)
297
+ >>> path in ([4, 3, 2, 1, 0, 8, 7, 6, 5], [5, 6, 7, 8, 0, 1, 2, 3, 4])
298
+ True
299
+
300
+ Otherwise, pass other keyword arguments directly into the tsp function.
301
+
302
+ >>> path = tsp(
303
+ ... G,
304
+ ... cycle=False,
305
+ ... method=nx.approximation.simulated_annealing_tsp,
306
+ ... init_cycle="greedy",
307
+ ... temp=500,
308
+ ... )
309
+ >>> path in ([4, 3, 2, 1, 0, 8, 7, 6, 5], [5, 6, 7, 8, 0, 1, 2, 3, 4])
310
+ True
311
+ """
312
+ if method is None:
313
+ if G.is_directed():
314
+ method = asadpour_atsp
315
+ else:
316
+ method = christofides
317
+ if nodes is None:
318
+ nodes = list(G.nodes)
319
+
320
+ dist = {}
321
+ path = {}
322
+ for n, (d, p) in nx.all_pairs_dijkstra(G, weight=weight):
323
+ dist[n] = d
324
+ path[n] = p
325
+
326
+ if G.is_directed():
327
+ # If the graph is not strongly connected, raise an exception
328
+ if not nx.is_strongly_connected(G):
329
+ raise nx.NetworkXError("G is not strongly connected")
330
+ GG = nx.DiGraph()
331
+ else:
332
+ GG = nx.Graph()
333
+ for u in nodes:
334
+ for v in nodes:
335
+ if u == v:
336
+ continue
337
+ GG.add_edge(u, v, weight=dist[u][v])
338
+
339
+ best_GG = method(GG, weight=weight, **kwargs)
340
+
341
+ if not cycle:
342
+ # find and remove the biggest edge
343
+ (u, v) = max(pairwise(best_GG), key=lambda x: dist[x[0]][x[1]])
344
+ pos = best_GG.index(u) + 1
345
+ while best_GG[pos] != v:
346
+ pos = best_GG[pos:].index(u) + 1
347
+ best_GG = best_GG[pos:-1] + best_GG[:pos]
348
+
349
+ best_path = []
350
+ for u, v in pairwise(best_GG):
351
+ best_path.extend(path[u][v][:-1])
352
+ best_path.append(v)
353
+ return best_path
354
+
355
+
356
+ @not_implemented_for("undirected")
357
+ @py_random_state(2)
358
+ @nx._dispatchable(edge_attrs="weight", mutates_input=True)
359
+ def asadpour_atsp(G, weight="weight", seed=None, source=None):
360
+ """
361
+ Returns an approximate solution to the traveling salesman problem.
362
+
363
+ This approximate solution is one of the best known approximations for the
364
+ asymmetric traveling salesman problem developed by Asadpour et al,
365
+ [1]_. The algorithm first solves the Held-Karp relaxation to find a lower
366
+ bound for the weight of the cycle. Next, it constructs an exponential
367
+ distribution of undirected spanning trees where the probability of an
368
+ edge being in the tree corresponds to the weight of that edge using a
369
+ maximum entropy rounding scheme. Next we sample that distribution
370
+ $2 \\lceil \\ln n \\rceil$ times and save the minimum sampled tree once the
371
+ direction of the arcs is added back to the edges. Finally, we augment
372
+ then short circuit that graph to find the approximate tour for the
373
+ salesman.
374
+
375
+ Parameters
376
+ ----------
377
+ G : nx.DiGraph
378
+ The graph should be a complete weighted directed graph. The
379
+ distance between all paris of nodes should be included and the triangle
380
+ inequality should hold. That is, the direct edge between any two nodes
381
+ should be the path of least cost.
382
+
383
+ weight : string, optional (default="weight")
384
+ Edge data key corresponding to the edge weight.
385
+ If any edge does not have this attribute the weight is set to 1.
386
+
387
+ seed : integer, random_state, or None (default)
388
+ Indicator of random number generation state.
389
+ See :ref:`Randomness<randomness>`.
390
+
391
+ source : node label (default=`None`)
392
+ If given, return the cycle starting and ending at the given node.
393
+
394
+ Returns
395
+ -------
396
+ cycle : list of nodes
397
+ Returns the cycle (list of nodes) that a salesman can follow to minimize
398
+ the total weight of the trip.
399
+
400
+ Raises
401
+ ------
402
+ NetworkXError
403
+ If `G` is not complete or has less than two nodes, the algorithm raises
404
+ an exception.
405
+
406
+ NetworkXError
407
+ If `source` is not `None` and is not a node in `G`, the algorithm raises
408
+ an exception.
409
+
410
+ NetworkXNotImplemented
411
+ If `G` is an undirected graph.
412
+
413
+ References
414
+ ----------
415
+ .. [1] A. Asadpour, M. X. Goemans, A. Madry, S. O. Gharan, and A. Saberi,
416
+ An o(log n/log log n)-approximation algorithm for the asymmetric
417
+ traveling salesman problem, Operations research, 65 (2017),
418
+ pp. 1043–1061
419
+
420
+ Examples
421
+ --------
422
+ >>> import networkx as nx
423
+ >>> import networkx.algorithms.approximation as approx
424
+ >>> G = nx.complete_graph(3, create_using=nx.DiGraph)
425
+ >>> nx.set_edge_attributes(
426
+ ... G,
427
+ ... {(0, 1): 2, (1, 2): 2, (2, 0): 2, (0, 2): 1, (2, 1): 1, (1, 0): 1},
428
+ ... "weight",
429
+ ... )
430
+ >>> tour = approx.asadpour_atsp(G, source=0)
431
+ >>> tour
432
+ [0, 2, 1, 0]
433
+ """
434
+ from math import ceil, exp
435
+ from math import log as ln
436
+
437
+ # Check that G is a complete graph
438
+ N = len(G) - 1
439
+ if N < 2:
440
+ raise nx.NetworkXError("G must have at least two nodes")
441
+ # This check ignores selfloops which is what we want here.
442
+ if any(len(nbrdict) - (n in nbrdict) != N for n, nbrdict in G.adj.items()):
443
+ raise nx.NetworkXError("G is not a complete DiGraph")
444
+ # Check that the source vertex, if given, is in the graph
445
+ if source is not None and source not in G.nodes:
446
+ raise nx.NetworkXError("Given source node not in G.")
447
+
448
+ opt_hk, z_star = held_karp_ascent(G, weight)
449
+
450
+ # Test to see if the ascent method found an integer solution or a fractional
451
+ # solution. If it is integral then z_star is a nx.Graph, otherwise it is
452
+ # a dict
453
+ if not isinstance(z_star, dict):
454
+ # Here we are using the shortcutting method to go from the list of edges
455
+ # returned from eulerian_circuit to a list of nodes
456
+ return _shortcutting(nx.eulerian_circuit(z_star, source=source))
457
+
458
+ # Create the undirected support of z_star
459
+ z_support = nx.MultiGraph()
460
+ for u, v in z_star:
461
+ if (u, v) not in z_support.edges:
462
+ edge_weight = min(G[u][v][weight], G[v][u][weight])
463
+ z_support.add_edge(u, v, **{weight: edge_weight})
464
+
465
+ # Create the exponential distribution of spanning trees
466
+ gamma = spanning_tree_distribution(z_support, z_star)
467
+
468
+ # Write the lambda values to the edges of z_support
469
+ z_support = nx.Graph(z_support)
470
+ lambda_dict = {(u, v): exp(gamma[(u, v)]) for u, v in z_support.edges()}
471
+ nx.set_edge_attributes(z_support, lambda_dict, "weight")
472
+ del gamma, lambda_dict
473
+
474
+ # Sample 2 * ceil( ln(n) ) spanning trees and record the minimum one
475
+ minimum_sampled_tree = None
476
+ minimum_sampled_tree_weight = math.inf
477
+ for _ in range(2 * ceil(ln(G.number_of_nodes()))):
478
+ sampled_tree = random_spanning_tree(z_support, "weight", seed=seed)
479
+ sampled_tree_weight = sampled_tree.size(weight)
480
+ if sampled_tree_weight < minimum_sampled_tree_weight:
481
+ minimum_sampled_tree = sampled_tree.copy()
482
+ minimum_sampled_tree_weight = sampled_tree_weight
483
+
484
+ # Orient the edges in that tree to keep the cost of the tree the same.
485
+ t_star = nx.MultiDiGraph()
486
+ for u, v, d in minimum_sampled_tree.edges(data=weight):
487
+ if d == G[u][v][weight]:
488
+ t_star.add_edge(u, v, **{weight: d})
489
+ else:
490
+ t_star.add_edge(v, u, **{weight: d})
491
+
492
+ # Find the node demands needed to neutralize the flow of t_star in G
493
+ node_demands = {n: t_star.out_degree(n) - t_star.in_degree(n) for n in t_star}
494
+ nx.set_node_attributes(G, node_demands, "demand")
495
+
496
+ # Find the min_cost_flow
497
+ flow_dict = nx.min_cost_flow(G, "demand")
498
+
499
+ # Build the flow into t_star
500
+ for source, values in flow_dict.items():
501
+ for target in values:
502
+ if (source, target) not in t_star.edges and values[target] > 0:
503
+ # IF values[target] > 0 we have to add that many edges
504
+ for _ in range(values[target]):
505
+ t_star.add_edge(source, target)
506
+
507
+ # Return the shortcut eulerian circuit
508
+ circuit = nx.eulerian_circuit(t_star, source=source)
509
+ return _shortcutting(circuit)
510
+
511
+
512
+ @nx._dispatchable(edge_attrs="weight", mutates_input=True, returns_graph=True)
513
+ def held_karp_ascent(G, weight="weight"):
514
+ """
515
+ Minimizes the Held-Karp relaxation of the TSP for `G`
516
+
517
+ Solves the Held-Karp relaxation of the input complete digraph and scales
518
+ the output solution for use in the Asadpour [1]_ ASTP algorithm.
519
+
520
+ The Held-Karp relaxation defines the lower bound for solutions to the
521
+ ATSP, although it does return a fractional solution. This is used in the
522
+ Asadpour algorithm as an initial solution which is later rounded to a
523
+ integral tree within the spanning tree polytopes. This function solves
524
+ the relaxation with the branch and bound method in [2]_.
525
+
526
+ Parameters
527
+ ----------
528
+ G : nx.DiGraph
529
+ The graph should be a complete weighted directed graph.
530
+ The distance between all paris of nodes should be included.
531
+
532
+ weight : string, optional (default="weight")
533
+ Edge data key corresponding to the edge weight.
534
+ If any edge does not have this attribute the weight is set to 1.
535
+
536
+ Returns
537
+ -------
538
+ OPT : float
539
+ The cost for the optimal solution to the Held-Karp relaxation
540
+ z : dict or nx.Graph
541
+ A symmetrized and scaled version of the optimal solution to the
542
+ Held-Karp relaxation for use in the Asadpour algorithm.
543
+
544
+ If an integral solution is found, then that is an optimal solution for
545
+ the ATSP problem and that is returned instead.
546
+
547
+ References
548
+ ----------
549
+ .. [1] A. Asadpour, M. X. Goemans, A. Madry, S. O. Gharan, and A. Saberi,
550
+ An o(log n/log log n)-approximation algorithm for the asymmetric
551
+ traveling salesman problem, Operations research, 65 (2017),
552
+ pp. 1043–1061
553
+
554
+ .. [2] M. Held, R. M. Karp, The traveling-salesman problem and minimum
555
+ spanning trees, Operations Research, 1970-11-01, Vol. 18 (6),
556
+ pp.1138-1162
557
+ """
558
+ import numpy as np
559
+ from scipy import optimize
560
+
561
+ def k_pi():
562
+ """
563
+ Find the set of minimum 1-Arborescences for G at point pi.
564
+
565
+ Returns
566
+ -------
567
+ Set
568
+ The set of minimum 1-Arborescences
569
+ """
570
+ # Create a copy of G without vertex 1.
571
+ G_1 = G.copy()
572
+ minimum_1_arborescences = set()
573
+ minimum_1_arborescence_weight = math.inf
574
+
575
+ # node is node '1' in the Held and Karp paper
576
+ n = next(G.__iter__())
577
+ G_1.remove_node(n)
578
+
579
+ # Iterate over the spanning arborescences of the graph until we know
580
+ # that we have found the minimum 1-arborescences. My proposed strategy
581
+ # is to find the most extensive root to connect to from 'node 1' and
582
+ # the least expensive one. We then iterate over arborescences until
583
+ # the cost of the basic arborescence is the cost of the minimum one
584
+ # plus the difference between the most and least expensive roots,
585
+ # that way the cost of connecting 'node 1' will by definition not by
586
+ # minimum
587
+ min_root = {"node": None, weight: math.inf}
588
+ max_root = {"node": None, weight: -math.inf}
589
+ for u, v, d in G.edges(n, data=True):
590
+ if d[weight] < min_root[weight]:
591
+ min_root = {"node": v, weight: d[weight]}
592
+ if d[weight] > max_root[weight]:
593
+ max_root = {"node": v, weight: d[weight]}
594
+
595
+ min_in_edge = min(G.in_edges(n, data=True), key=lambda x: x[2][weight])
596
+ min_root[weight] = min_root[weight] + min_in_edge[2][weight]
597
+ max_root[weight] = max_root[weight] + min_in_edge[2][weight]
598
+
599
+ min_arb_weight = math.inf
600
+ for arb in nx.ArborescenceIterator(G_1):
601
+ arb_weight = arb.size(weight)
602
+ if min_arb_weight == math.inf:
603
+ min_arb_weight = arb_weight
604
+ elif arb_weight > min_arb_weight + max_root[weight] - min_root[weight]:
605
+ break
606
+ # We have to pick the root node of the arborescence for the out
607
+ # edge of the first vertex as that is the only node without an
608
+ # edge directed into it.
609
+ for N, deg in arb.in_degree:
610
+ if deg == 0:
611
+ # root found
612
+ arb.add_edge(n, N, **{weight: G[n][N][weight]})
613
+ arb_weight += G[n][N][weight]
614
+ break
615
+
616
+ # We can pick the minimum weight in-edge for the vertex with
617
+ # a cycle. If there are multiple edges with the same, minimum
618
+ # weight, We need to add all of them.
619
+ #
620
+ # Delete the edge (N, v) so that we cannot pick it.
621
+ edge_data = G[N][n]
622
+ G.remove_edge(N, n)
623
+ min_weight = min(G.in_edges(n, data=weight), key=lambda x: x[2])[2]
624
+ min_edges = [
625
+ (u, v, d) for u, v, d in G.in_edges(n, data=weight) if d == min_weight
626
+ ]
627
+ for u, v, d in min_edges:
628
+ new_arb = arb.copy()
629
+ new_arb.add_edge(u, v, **{weight: d})
630
+ new_arb_weight = arb_weight + d
631
+ # Check to see the weight of the arborescence, if it is a
632
+ # new minimum, clear all of the old potential minimum
633
+ # 1-arborescences and add this is the only one. If its
634
+ # weight is above the known minimum, do not add it.
635
+ if new_arb_weight < minimum_1_arborescence_weight:
636
+ minimum_1_arborescences.clear()
637
+ minimum_1_arborescence_weight = new_arb_weight
638
+ # We have a 1-arborescence, add it to the set
639
+ if new_arb_weight == minimum_1_arborescence_weight:
640
+ minimum_1_arborescences.add(new_arb)
641
+ G.add_edge(N, n, **edge_data)
642
+
643
+ return minimum_1_arborescences
644
+
645
+ def direction_of_ascent():
646
+ """
647
+ Find the direction of ascent at point pi.
648
+
649
+ See [1]_ for more information.
650
+
651
+ Returns
652
+ -------
653
+ dict
654
+ A mapping from the nodes of the graph which represents the direction
655
+ of ascent.
656
+
657
+ References
658
+ ----------
659
+ .. [1] M. Held, R. M. Karp, The traveling-salesman problem and minimum
660
+ spanning trees, Operations Research, 1970-11-01, Vol. 18 (6),
661
+ pp.1138-1162
662
+ """
663
+ # 1. Set d equal to the zero n-vector.
664
+ d = {}
665
+ for n in G:
666
+ d[n] = 0
667
+ del n
668
+ # 2. Find a 1-Arborescence T^k such that k is in K(pi, d).
669
+ minimum_1_arborescences = k_pi()
670
+ while True:
671
+ # Reduce K(pi) to K(pi, d)
672
+ # Find the arborescence in K(pi) which increases the lest in
673
+ # direction d
674
+ min_k_d_weight = math.inf
675
+ min_k_d = None
676
+ for arborescence in minimum_1_arborescences:
677
+ weighted_cost = 0
678
+ for n, deg in arborescence.degree:
679
+ weighted_cost += d[n] * (deg - 2)
680
+ if weighted_cost < min_k_d_weight:
681
+ min_k_d_weight = weighted_cost
682
+ min_k_d = arborescence
683
+
684
+ # 3. If sum of d_i * v_{i, k} is greater than zero, terminate
685
+ if min_k_d_weight > 0:
686
+ return d, min_k_d
687
+ # 4. d_i = d_i + v_{i, k}
688
+ for n, deg in min_k_d.degree:
689
+ d[n] += deg - 2
690
+ # Check that we do not need to terminate because the direction
691
+ # of ascent does not exist. This is done with linear
692
+ # programming.
693
+ c = np.full(len(minimum_1_arborescences), -1, dtype=int)
694
+ a_eq = np.empty((len(G) + 1, len(minimum_1_arborescences)), dtype=int)
695
+ b_eq = np.zeros(len(G) + 1, dtype=int)
696
+ b_eq[len(G)] = 1
697
+ for arb_count, arborescence in enumerate(minimum_1_arborescences):
698
+ n_count = len(G) - 1
699
+ for n, deg in arborescence.degree:
700
+ a_eq[n_count][arb_count] = deg - 2
701
+ n_count -= 1
702
+ a_eq[len(G)][arb_count] = 1
703
+ program_result = optimize.linprog(
704
+ c, A_eq=a_eq, b_eq=b_eq, method="highs-ipm"
705
+ )
706
+ # If the constants exist, then the direction of ascent doesn't
707
+ if program_result.success:
708
+ # There is no direction of ascent
709
+ return None, minimum_1_arborescences
710
+
711
+ # 5. GO TO 2
712
+
713
+ def find_epsilon(k, d):
714
+ """
715
+ Given the direction of ascent at pi, find the maximum distance we can go
716
+ in that direction.
717
+
718
+ Parameters
719
+ ----------
720
+ k_xy : set
721
+ The set of 1-arborescences which have the minimum rate of increase
722
+ in the direction of ascent
723
+
724
+ d : dict
725
+ The direction of ascent
726
+
727
+ Returns
728
+ -------
729
+ float
730
+ The distance we can travel in direction `d`
731
+ """
732
+ min_epsilon = math.inf
733
+ for e_u, e_v, e_w in G.edges(data=weight):
734
+ if (e_u, e_v) in k.edges:
735
+ continue
736
+ # Now, I have found a condition which MUST be true for the edges to
737
+ # be a valid substitute. The edge in the graph which is the
738
+ # substitute is the one with the same terminated end. This can be
739
+ # checked rather simply.
740
+ #
741
+ # Find the edge within k which is the substitute. Because k is a
742
+ # 1-arborescence, we know that they is only one such edges
743
+ # leading into every vertex.
744
+ if len(k.in_edges(e_v, data=weight)) > 1:
745
+ raise Exception
746
+ sub_u, sub_v, sub_w = next(k.in_edges(e_v, data=weight).__iter__())
747
+ k.add_edge(e_u, e_v, **{weight: e_w})
748
+ k.remove_edge(sub_u, sub_v)
749
+ if (
750
+ max(d for n, d in k.in_degree()) <= 1
751
+ and len(G) == k.number_of_edges()
752
+ and nx.is_weakly_connected(k)
753
+ ):
754
+ # Ascent method calculation
755
+ if d[sub_u] == d[e_u] or sub_w == e_w:
756
+ # Revert to the original graph
757
+ k.remove_edge(e_u, e_v)
758
+ k.add_edge(sub_u, sub_v, **{weight: sub_w})
759
+ continue
760
+ epsilon = (sub_w - e_w) / (d[e_u] - d[sub_u])
761
+ if 0 < epsilon < min_epsilon:
762
+ min_epsilon = epsilon
763
+ # Revert to the original graph
764
+ k.remove_edge(e_u, e_v)
765
+ k.add_edge(sub_u, sub_v, **{weight: sub_w})
766
+
767
+ return min_epsilon
768
+
769
+ # I have to know that the elements in pi correspond to the correct elements
770
+ # in the direction of ascent, even if the node labels are not integers.
771
+ # Thus, I will use dictionaries to made that mapping.
772
+ pi_dict = {}
773
+ for n in G:
774
+ pi_dict[n] = 0
775
+ del n
776
+ original_edge_weights = {}
777
+ for u, v, d in G.edges(data=True):
778
+ original_edge_weights[(u, v)] = d[weight]
779
+ dir_ascent, k_d = direction_of_ascent()
780
+ while dir_ascent is not None:
781
+ max_distance = find_epsilon(k_d, dir_ascent)
782
+ for n, v in dir_ascent.items():
783
+ pi_dict[n] += max_distance * v
784
+ for u, v, d in G.edges(data=True):
785
+ d[weight] = original_edge_weights[(u, v)] + pi_dict[u]
786
+ dir_ascent, k_d = direction_of_ascent()
787
+ nx._clear_cache(G)
788
+ # k_d is no longer an individual 1-arborescence but rather a set of
789
+ # minimal 1-arborescences at the maximum point of the polytope and should
790
+ # be reflected as such
791
+ k_max = k_d
792
+
793
+ # Search for a cycle within k_max. If a cycle exists, return it as the
794
+ # solution
795
+ for k in k_max:
796
+ if len([n for n in k if k.degree(n) == 2]) == G.order():
797
+ # Tour found
798
+ # TODO: this branch does not restore original_edge_weights of G!
799
+ return k.size(weight), k
800
+
801
+ # Write the original edge weights back to G and every member of k_max at
802
+ # the maximum point. Also average the number of times that edge appears in
803
+ # the set of minimal 1-arborescences.
804
+ x_star = {}
805
+ size_k_max = len(k_max)
806
+ for u, v, d in G.edges(data=True):
807
+ edge_count = 0
808
+ d[weight] = original_edge_weights[(u, v)]
809
+ for k in k_max:
810
+ if (u, v) in k.edges():
811
+ edge_count += 1
812
+ k[u][v][weight] = original_edge_weights[(u, v)]
813
+ x_star[(u, v)] = edge_count / size_k_max
814
+ # Now symmetrize the edges in x_star and scale them according to (5) in
815
+ # reference [1]
816
+ z_star = {}
817
+ scale_factor = (G.order() - 1) / G.order()
818
+ for u, v in x_star:
819
+ frequency = x_star[(u, v)] + x_star[(v, u)]
820
+ if frequency > 0:
821
+ z_star[(u, v)] = scale_factor * frequency
822
+ del x_star
823
+ # Return the optimal weight and the z dict
824
+ return next(k_max.__iter__()).size(weight), z_star
825
+
826
+
827
+ @nx._dispatchable
828
+ def spanning_tree_distribution(G, z):
829
+ """
830
+ Find the asadpour exponential distribution of spanning trees.
831
+
832
+ Solves the Maximum Entropy Convex Program in the Asadpour algorithm [1]_
833
+ using the approach in section 7 to build an exponential distribution of
834
+ undirected spanning trees.
835
+
836
+ This algorithm ensures that the probability of any edge in a spanning
837
+ tree is proportional to the sum of the probabilities of the tress
838
+ containing that edge over the sum of the probabilities of all spanning
839
+ trees of the graph.
840
+
841
+ Parameters
842
+ ----------
843
+ G : nx.MultiGraph
844
+ The undirected support graph for the Held Karp relaxation
845
+
846
+ z : dict
847
+ The output of `held_karp_ascent()`, a scaled version of the Held-Karp
848
+ solution.
849
+
850
+ Returns
851
+ -------
852
+ gamma : dict
853
+ The probability distribution which approximately preserves the marginal
854
+ probabilities of `z`.
855
+ """
856
+ from math import exp
857
+ from math import log as ln
858
+
859
+ def q(e):
860
+ """
861
+ The value of q(e) is described in the Asadpour paper is "the
862
+ probability that edge e will be included in a spanning tree T that is
863
+ chosen with probability proportional to exp(gamma(T))" which
864
+ basically means that it is the total probability of the edge appearing
865
+ across the whole distribution.
866
+
867
+ Parameters
868
+ ----------
869
+ e : tuple
870
+ The `(u, v)` tuple describing the edge we are interested in
871
+
872
+ Returns
873
+ -------
874
+ float
875
+ The probability that a spanning tree chosen according to the
876
+ current values of gamma will include edge `e`.
877
+ """
878
+ # Create the laplacian matrices
879
+ for u, v, d in G.edges(data=True):
880
+ d[lambda_key] = exp(gamma[(u, v)])
881
+ G_Kirchhoff = nx.total_spanning_tree_weight(G, lambda_key)
882
+ G_e = nx.contracted_edge(G, e, self_loops=False)
883
+ G_e_Kirchhoff = nx.total_spanning_tree_weight(G_e, lambda_key)
884
+
885
+ # Multiply by the weight of the contracted edge since it is not included
886
+ # in the total weight of the contracted graph.
887
+ return exp(gamma[(e[0], e[1])]) * G_e_Kirchhoff / G_Kirchhoff
888
+
889
+ # initialize gamma to the zero dict
890
+ gamma = {}
891
+ for u, v, _ in G.edges:
892
+ gamma[(u, v)] = 0
893
+
894
+ # set epsilon
895
+ EPSILON = 0.2
896
+
897
+ # pick an edge attribute name that is unlikely to be in the graph
898
+ lambda_key = "spanning_tree_distribution's secret attribute name for lambda"
899
+
900
+ while True:
901
+ # We need to know that know that no values of q_e are greater than
902
+ # (1 + epsilon) * z_e, however changing one gamma value can increase the
903
+ # value of a different q_e, so we have to complete the for loop without
904
+ # changing anything for the condition to be meet
905
+ in_range_count = 0
906
+ # Search for an edge with q_e > (1 + epsilon) * z_e
907
+ for u, v in gamma:
908
+ e = (u, v)
909
+ q_e = q(e)
910
+ z_e = z[e]
911
+ if q_e > (1 + EPSILON) * z_e:
912
+ delta = ln(
913
+ (q_e * (1 - (1 + EPSILON / 2) * z_e))
914
+ / ((1 - q_e) * (1 + EPSILON / 2) * z_e)
915
+ )
916
+ gamma[e] -= delta
917
+ # Check that delta had the desired effect
918
+ new_q_e = q(e)
919
+ desired_q_e = (1 + EPSILON / 2) * z_e
920
+ if round(new_q_e, 8) != round(desired_q_e, 8):
921
+ raise nx.NetworkXError(
922
+ f"Unable to modify probability for edge ({u}, {v})"
923
+ )
924
+ else:
925
+ in_range_count += 1
926
+ # Check if the for loop terminated without changing any gamma
927
+ if in_range_count == len(gamma):
928
+ break
929
+
930
+ # Remove the new edge attributes
931
+ for _, _, d in G.edges(data=True):
932
+ if lambda_key in d:
933
+ del d[lambda_key]
934
+
935
+ return gamma
936
+
937
+
938
+ @nx._dispatchable(edge_attrs="weight")
939
+ def greedy_tsp(G, weight="weight", source=None):
940
+ """Return a low cost cycle starting at `source` and its cost.
941
+
942
+ This approximates a solution to the traveling salesman problem.
943
+ It finds a cycle of all the nodes that a salesman can visit in order
944
+ to visit many nodes while minimizing total distance.
945
+ It uses a simple greedy algorithm.
946
+ In essence, this function returns a large cycle given a source point
947
+ for which the total cost of the cycle is minimized.
948
+
949
+ Parameters
950
+ ----------
951
+ G : Graph
952
+ The Graph should be a complete weighted undirected graph.
953
+ The distance between all pairs of nodes should be included.
954
+
955
+ weight : string, optional (default="weight")
956
+ Edge data key corresponding to the edge weight.
957
+ If any edge does not have this attribute the weight is set to 1.
958
+
959
+ source : node, optional (default: first node in list(G))
960
+ Starting node. If None, defaults to ``next(iter(G))``
961
+
962
+ Returns
963
+ -------
964
+ cycle : list of nodes
965
+ Returns the cycle (list of nodes) that a salesman
966
+ can follow to minimize total weight of the trip.
967
+
968
+ Raises
969
+ ------
970
+ NetworkXError
971
+ If `G` is not complete, the algorithm raises an exception.
972
+
973
+ Examples
974
+ --------
975
+ >>> from networkx.algorithms import approximation as approx
976
+ >>> G = nx.DiGraph()
977
+ >>> G.add_weighted_edges_from(
978
+ ... {
979
+ ... ("A", "B", 3),
980
+ ... ("A", "C", 17),
981
+ ... ("A", "D", 14),
982
+ ... ("B", "A", 3),
983
+ ... ("B", "C", 12),
984
+ ... ("B", "D", 16),
985
+ ... ("C", "A", 13),
986
+ ... ("C", "B", 12),
987
+ ... ("C", "D", 4),
988
+ ... ("D", "A", 14),
989
+ ... ("D", "B", 15),
990
+ ... ("D", "C", 2),
991
+ ... }
992
+ ... )
993
+ >>> cycle = approx.greedy_tsp(G, source="D")
994
+ >>> cost = sum(G[n][nbr]["weight"] for n, nbr in nx.utils.pairwise(cycle))
995
+ >>> cycle
996
+ ['D', 'C', 'B', 'A', 'D']
997
+ >>> cost
998
+ 31
999
+
1000
+ Notes
1001
+ -----
1002
+ This implementation of a greedy algorithm is based on the following:
1003
+
1004
+ - The algorithm adds a node to the solution at every iteration.
1005
+ - The algorithm selects a node not already in the cycle whose connection
1006
+ to the previous node adds the least cost to the cycle.
1007
+
1008
+ A greedy algorithm does not always give the best solution.
1009
+ However, it can construct a first feasible solution which can
1010
+ be passed as a parameter to an iterative improvement algorithm such
1011
+ as Simulated Annealing, or Threshold Accepting.
1012
+
1013
+ Time complexity: It has a running time $O(|V|^2)$
1014
+ """
1015
+ # Check that G is a complete graph
1016
+ N = len(G) - 1
1017
+ # This check ignores selfloops which is what we want here.
1018
+ if any(len(nbrdict) - (n in nbrdict) != N for n, nbrdict in G.adj.items()):
1019
+ raise nx.NetworkXError("G must be a complete graph.")
1020
+
1021
+ if source is None:
1022
+ source = nx.utils.arbitrary_element(G)
1023
+
1024
+ if G.number_of_nodes() == 2:
1025
+ neighbor = next(G.neighbors(source))
1026
+ return [source, neighbor, source]
1027
+
1028
+ nodeset = set(G)
1029
+ nodeset.remove(source)
1030
+ cycle = [source]
1031
+ next_node = source
1032
+ while nodeset:
1033
+ nbrdict = G[next_node]
1034
+ next_node = min(nodeset, key=lambda n: nbrdict[n].get(weight, 1))
1035
+ cycle.append(next_node)
1036
+ nodeset.remove(next_node)
1037
+ cycle.append(cycle[0])
1038
+ return cycle
1039
+
1040
+
1041
+ @py_random_state(9)
1042
+ @nx._dispatchable(edge_attrs="weight")
1043
+ def simulated_annealing_tsp(
1044
+ G,
1045
+ init_cycle,
1046
+ weight="weight",
1047
+ source=None,
1048
+ temp=100,
1049
+ move="1-1",
1050
+ max_iterations=10,
1051
+ N_inner=100,
1052
+ alpha=0.01,
1053
+ seed=None,
1054
+ ):
1055
+ """Returns an approximate solution to the traveling salesman problem.
1056
+
1057
+ This function uses simulated annealing to approximate the minimal cost
1058
+ cycle through the nodes. Starting from a suboptimal solution, simulated
1059
+ annealing perturbs that solution, occasionally accepting changes that make
1060
+ the solution worse to escape from a locally optimal solution. The chance
1061
+ of accepting such changes decreases over the iterations to encourage
1062
+ an optimal result. In summary, the function returns a cycle starting
1063
+ at `source` for which the total cost is minimized. It also returns the cost.
1064
+
1065
+ The chance of accepting a proposed change is related to a parameter called
1066
+ the temperature (annealing has a physical analogue of steel hardening
1067
+ as it cools). As the temperature is reduced, the chance of moves that
1068
+ increase cost goes down.
1069
+
1070
+ Parameters
1071
+ ----------
1072
+ G : Graph
1073
+ `G` should be a complete weighted graph.
1074
+ The distance between all pairs of nodes should be included.
1075
+
1076
+ init_cycle : list of all nodes or "greedy"
1077
+ The initial solution (a cycle through all nodes returning to the start).
1078
+ This argument has no default to make you think about it.
1079
+ If "greedy", use `greedy_tsp(G, weight)`.
1080
+ Other common starting cycles are `list(G) + [next(iter(G))]` or the final
1081
+ result of `simulated_annealing_tsp` when doing `threshold_accepting_tsp`.
1082
+
1083
+ weight : string, optional (default="weight")
1084
+ Edge data key corresponding to the edge weight.
1085
+ If any edge does not have this attribute the weight is set to 1.
1086
+
1087
+ source : node, optional (default: first node in list(G))
1088
+ Starting node. If None, defaults to ``next(iter(G))``
1089
+
1090
+ temp : int, optional (default=100)
1091
+ The algorithm's temperature parameter. It represents the initial
1092
+ value of temperature
1093
+
1094
+ move : "1-1" or "1-0" or function, optional (default="1-1")
1095
+ Indicator of what move to use when finding new trial solutions.
1096
+ Strings indicate two special built-in moves:
1097
+
1098
+ - "1-1": 1-1 exchange which transposes the position
1099
+ of two elements of the current solution.
1100
+ The function called is :func:`swap_two_nodes`.
1101
+ For example if we apply 1-1 exchange in the solution
1102
+ ``A = [3, 2, 1, 4, 3]``
1103
+ we can get the following by the transposition of 1 and 4 elements:
1104
+ ``A' = [3, 2, 4, 1, 3]``
1105
+ - "1-0": 1-0 exchange which moves an node in the solution
1106
+ to a new position.
1107
+ The function called is :func:`move_one_node`.
1108
+ For example if we apply 1-0 exchange in the solution
1109
+ ``A = [3, 2, 1, 4, 3]``
1110
+ we can transfer the fourth element to the second position:
1111
+ ``A' = [3, 4, 2, 1, 3]``
1112
+
1113
+ You may provide your own functions to enact a move from
1114
+ one solution to a neighbor solution. The function must take
1115
+ the solution as input along with a `seed` input to control
1116
+ random number generation (see the `seed` input here).
1117
+ Your function should maintain the solution as a cycle with
1118
+ equal first and last node and all others appearing once.
1119
+ Your function should return the new solution.
1120
+
1121
+ max_iterations : int, optional (default=10)
1122
+ Declared done when this number of consecutive iterations of
1123
+ the outer loop occurs without any change in the best cost solution.
1124
+
1125
+ N_inner : int, optional (default=100)
1126
+ The number of iterations of the inner loop.
1127
+
1128
+ alpha : float between (0, 1), optional (default=0.01)
1129
+ Percentage of temperature decrease in each iteration
1130
+ of outer loop
1131
+
1132
+ seed : integer, random_state, or None (default)
1133
+ Indicator of random number generation state.
1134
+ See :ref:`Randomness<randomness>`.
1135
+
1136
+ Returns
1137
+ -------
1138
+ cycle : list of nodes
1139
+ Returns the cycle (list of nodes) that a salesman
1140
+ can follow to minimize total weight of the trip.
1141
+
1142
+ Raises
1143
+ ------
1144
+ NetworkXError
1145
+ If `G` is not complete the algorithm raises an exception.
1146
+
1147
+ Examples
1148
+ --------
1149
+ >>> from networkx.algorithms import approximation as approx
1150
+ >>> G = nx.DiGraph()
1151
+ >>> G.add_weighted_edges_from(
1152
+ ... {
1153
+ ... ("A", "B", 3),
1154
+ ... ("A", "C", 17),
1155
+ ... ("A", "D", 14),
1156
+ ... ("B", "A", 3),
1157
+ ... ("B", "C", 12),
1158
+ ... ("B", "D", 16),
1159
+ ... ("C", "A", 13),
1160
+ ... ("C", "B", 12),
1161
+ ... ("C", "D", 4),
1162
+ ... ("D", "A", 14),
1163
+ ... ("D", "B", 15),
1164
+ ... ("D", "C", 2),
1165
+ ... }
1166
+ ... )
1167
+ >>> cycle = approx.simulated_annealing_tsp(G, "greedy", source="D")
1168
+ >>> cost = sum(G[n][nbr]["weight"] for n, nbr in nx.utils.pairwise(cycle))
1169
+ >>> cycle
1170
+ ['D', 'C', 'B', 'A', 'D']
1171
+ >>> cost
1172
+ 31
1173
+ >>> incycle = ["D", "B", "A", "C", "D"]
1174
+ >>> cycle = approx.simulated_annealing_tsp(G, incycle, source="D")
1175
+ >>> cost = sum(G[n][nbr]["weight"] for n, nbr in nx.utils.pairwise(cycle))
1176
+ >>> cycle
1177
+ ['D', 'C', 'B', 'A', 'D']
1178
+ >>> cost
1179
+ 31
1180
+
1181
+ Notes
1182
+ -----
1183
+ Simulated Annealing is a metaheuristic local search algorithm.
1184
+ The main characteristic of this algorithm is that it accepts
1185
+ even solutions which lead to the increase of the cost in order
1186
+ to escape from low quality local optimal solutions.
1187
+
1188
+ This algorithm needs an initial solution. If not provided, it is
1189
+ constructed by a simple greedy algorithm. At every iteration, the
1190
+ algorithm selects thoughtfully a neighbor solution.
1191
+ Consider $c(x)$ cost of current solution and $c(x')$ cost of a
1192
+ neighbor solution.
1193
+ If $c(x') - c(x) <= 0$ then the neighbor solution becomes the current
1194
+ solution for the next iteration. Otherwise, the algorithm accepts
1195
+ the neighbor solution with probability $p = exp - ([c(x') - c(x)] / temp)$.
1196
+ Otherwise the current solution is retained.
1197
+
1198
+ `temp` is a parameter of the algorithm and represents temperature.
1199
+
1200
+ Time complexity:
1201
+ For $N_i$ iterations of the inner loop and $N_o$ iterations of the
1202
+ outer loop, this algorithm has running time $O(N_i * N_o * |V|)$.
1203
+
1204
+ For more information and how the algorithm is inspired see:
1205
+ http://en.wikipedia.org/wiki/Simulated_annealing
1206
+ """
1207
+ if move == "1-1":
1208
+ move = swap_two_nodes
1209
+ elif move == "1-0":
1210
+ move = move_one_node
1211
+ if init_cycle == "greedy":
1212
+ # Construct an initial solution using a greedy algorithm.
1213
+ cycle = greedy_tsp(G, weight=weight, source=source)
1214
+ if G.number_of_nodes() == 2:
1215
+ return cycle
1216
+
1217
+ else:
1218
+ cycle = list(init_cycle)
1219
+ if source is None:
1220
+ source = cycle[0]
1221
+ elif source != cycle[0]:
1222
+ raise nx.NetworkXError("source must be first node in init_cycle")
1223
+ if cycle[0] != cycle[-1]:
1224
+ raise nx.NetworkXError("init_cycle must be a cycle. (return to start)")
1225
+
1226
+ if len(cycle) - 1 != len(G) or len(set(G.nbunch_iter(cycle))) != len(G):
1227
+ raise nx.NetworkXError("init_cycle should be a cycle over all nodes in G.")
1228
+
1229
+ # Check that G is a complete graph
1230
+ N = len(G) - 1
1231
+ # This check ignores selfloops which is what we want here.
1232
+ if any(len(nbrdict) - (n in nbrdict) != N for n, nbrdict in G.adj.items()):
1233
+ raise nx.NetworkXError("G must be a complete graph.")
1234
+
1235
+ if G.number_of_nodes() == 2:
1236
+ neighbor = next(G.neighbors(source))
1237
+ return [source, neighbor, source]
1238
+
1239
+ # Find the cost of initial solution
1240
+ cost = sum(G[u][v].get(weight, 1) for u, v in pairwise(cycle))
1241
+
1242
+ count = 0
1243
+ best_cycle = cycle.copy()
1244
+ best_cost = cost
1245
+ while count <= max_iterations and temp > 0:
1246
+ count += 1
1247
+ for i in range(N_inner):
1248
+ adj_sol = move(cycle, seed)
1249
+ adj_cost = sum(G[u][v].get(weight, 1) for u, v in pairwise(adj_sol))
1250
+ delta = adj_cost - cost
1251
+ if delta <= 0:
1252
+ # Set current solution the adjacent solution.
1253
+ cycle = adj_sol
1254
+ cost = adj_cost
1255
+
1256
+ if cost < best_cost:
1257
+ count = 0
1258
+ best_cycle = cycle.copy()
1259
+ best_cost = cost
1260
+ else:
1261
+ # Accept even a worse solution with probability p.
1262
+ p = math.exp(-delta / temp)
1263
+ if p >= seed.random():
1264
+ cycle = adj_sol
1265
+ cost = adj_cost
1266
+ temp -= temp * alpha
1267
+
1268
+ return best_cycle
1269
+
1270
+
1271
+ @py_random_state(9)
1272
+ @nx._dispatchable(edge_attrs="weight")
1273
+ def threshold_accepting_tsp(
1274
+ G,
1275
+ init_cycle,
1276
+ weight="weight",
1277
+ source=None,
1278
+ threshold=1,
1279
+ move="1-1",
1280
+ max_iterations=10,
1281
+ N_inner=100,
1282
+ alpha=0.1,
1283
+ seed=None,
1284
+ ):
1285
+ """Returns an approximate solution to the traveling salesman problem.
1286
+
1287
+ This function uses threshold accepting methods to approximate the minimal cost
1288
+ cycle through the nodes. Starting from a suboptimal solution, threshold
1289
+ accepting methods perturb that solution, accepting any changes that make
1290
+ the solution no worse than increasing by a threshold amount. Improvements
1291
+ in cost are accepted, but so are changes leading to small increases in cost.
1292
+ This allows the solution to leave suboptimal local minima in solution space.
1293
+ The threshold is decreased slowly as iterations proceed helping to ensure
1294
+ an optimum. In summary, the function returns a cycle starting at `source`
1295
+ for which the total cost is minimized.
1296
+
1297
+ Parameters
1298
+ ----------
1299
+ G : Graph
1300
+ `G` should be a complete weighted graph.
1301
+ The distance between all pairs of nodes should be included.
1302
+
1303
+ init_cycle : list or "greedy"
1304
+ The initial solution (a cycle through all nodes returning to the start).
1305
+ This argument has no default to make you think about it.
1306
+ If "greedy", use `greedy_tsp(G, weight)`.
1307
+ Other common starting cycles are `list(G) + [next(iter(G))]` or the final
1308
+ result of `simulated_annealing_tsp` when doing `threshold_accepting_tsp`.
1309
+
1310
+ weight : string, optional (default="weight")
1311
+ Edge data key corresponding to the edge weight.
1312
+ If any edge does not have this attribute the weight is set to 1.
1313
+
1314
+ source : node, optional (default: first node in list(G))
1315
+ Starting node. If None, defaults to ``next(iter(G))``
1316
+
1317
+ threshold : int, optional (default=1)
1318
+ The algorithm's threshold parameter. It represents the initial
1319
+ threshold's value
1320
+
1321
+ move : "1-1" or "1-0" or function, optional (default="1-1")
1322
+ Indicator of what move to use when finding new trial solutions.
1323
+ Strings indicate two special built-in moves:
1324
+
1325
+ - "1-1": 1-1 exchange which transposes the position
1326
+ of two elements of the current solution.
1327
+ The function called is :func:`swap_two_nodes`.
1328
+ For example if we apply 1-1 exchange in the solution
1329
+ ``A = [3, 2, 1, 4, 3]``
1330
+ we can get the following by the transposition of 1 and 4 elements:
1331
+ ``A' = [3, 2, 4, 1, 3]``
1332
+ - "1-0": 1-0 exchange which moves an node in the solution
1333
+ to a new position.
1334
+ The function called is :func:`move_one_node`.
1335
+ For example if we apply 1-0 exchange in the solution
1336
+ ``A = [3, 2, 1, 4, 3]``
1337
+ we can transfer the fourth element to the second position:
1338
+ ``A' = [3, 4, 2, 1, 3]``
1339
+
1340
+ You may provide your own functions to enact a move from
1341
+ one solution to a neighbor solution. The function must take
1342
+ the solution as input along with a `seed` input to control
1343
+ random number generation (see the `seed` input here).
1344
+ Your function should maintain the solution as a cycle with
1345
+ equal first and last node and all others appearing once.
1346
+ Your function should return the new solution.
1347
+
1348
+ max_iterations : int, optional (default=10)
1349
+ Declared done when this number of consecutive iterations of
1350
+ the outer loop occurs without any change in the best cost solution.
1351
+
1352
+ N_inner : int, optional (default=100)
1353
+ The number of iterations of the inner loop.
1354
+
1355
+ alpha : float between (0, 1), optional (default=0.1)
1356
+ Percentage of threshold decrease when there is at
1357
+ least one acceptance of a neighbor solution.
1358
+ If no inner loop moves are accepted the threshold remains unchanged.
1359
+
1360
+ seed : integer, random_state, or None (default)
1361
+ Indicator of random number generation state.
1362
+ See :ref:`Randomness<randomness>`.
1363
+
1364
+ Returns
1365
+ -------
1366
+ cycle : list of nodes
1367
+ Returns the cycle (list of nodes) that a salesman
1368
+ can follow to minimize total weight of the trip.
1369
+
1370
+ Raises
1371
+ ------
1372
+ NetworkXError
1373
+ If `G` is not complete the algorithm raises an exception.
1374
+
1375
+ Examples
1376
+ --------
1377
+ >>> from networkx.algorithms import approximation as approx
1378
+ >>> G = nx.DiGraph()
1379
+ >>> G.add_weighted_edges_from(
1380
+ ... {
1381
+ ... ("A", "B", 3),
1382
+ ... ("A", "C", 17),
1383
+ ... ("A", "D", 14),
1384
+ ... ("B", "A", 3),
1385
+ ... ("B", "C", 12),
1386
+ ... ("B", "D", 16),
1387
+ ... ("C", "A", 13),
1388
+ ... ("C", "B", 12),
1389
+ ... ("C", "D", 4),
1390
+ ... ("D", "A", 14),
1391
+ ... ("D", "B", 15),
1392
+ ... ("D", "C", 2),
1393
+ ... }
1394
+ ... )
1395
+ >>> cycle = approx.threshold_accepting_tsp(G, "greedy", source="D")
1396
+ >>> cost = sum(G[n][nbr]["weight"] for n, nbr in nx.utils.pairwise(cycle))
1397
+ >>> cycle
1398
+ ['D', 'C', 'B', 'A', 'D']
1399
+ >>> cost
1400
+ 31
1401
+ >>> incycle = ["D", "B", "A", "C", "D"]
1402
+ >>> cycle = approx.threshold_accepting_tsp(G, incycle, source="D")
1403
+ >>> cost = sum(G[n][nbr]["weight"] for n, nbr in nx.utils.pairwise(cycle))
1404
+ >>> cycle
1405
+ ['D', 'C', 'B', 'A', 'D']
1406
+ >>> cost
1407
+ 31
1408
+
1409
+ Notes
1410
+ -----
1411
+ Threshold Accepting is a metaheuristic local search algorithm.
1412
+ The main characteristic of this algorithm is that it accepts
1413
+ even solutions which lead to the increase of the cost in order
1414
+ to escape from low quality local optimal solutions.
1415
+
1416
+ This algorithm needs an initial solution. This solution can be
1417
+ constructed by a simple greedy algorithm. At every iteration, it
1418
+ selects thoughtfully a neighbor solution.
1419
+ Consider $c(x)$ cost of current solution and $c(x')$ cost of
1420
+ neighbor solution.
1421
+ If $c(x') - c(x) <= threshold$ then the neighbor solution becomes the current
1422
+ solution for the next iteration, where the threshold is named threshold.
1423
+
1424
+ In comparison to the Simulated Annealing algorithm, the Threshold
1425
+ Accepting algorithm does not accept very low quality solutions
1426
+ (due to the presence of the threshold value). In the case of
1427
+ Simulated Annealing, even a very low quality solution can
1428
+ be accepted with probability $p$.
1429
+
1430
+ Time complexity:
1431
+ It has a running time $O(m * n * |V|)$ where $m$ and $n$ are the number
1432
+ of times the outer and inner loop run respectively.
1433
+
1434
+ For more information and how algorithm is inspired see:
1435
+ https://doi.org/10.1016/0021-9991(90)90201-B
1436
+
1437
+ See Also
1438
+ --------
1439
+ simulated_annealing_tsp
1440
+
1441
+ """
1442
+ if move == "1-1":
1443
+ move = swap_two_nodes
1444
+ elif move == "1-0":
1445
+ move = move_one_node
1446
+ if init_cycle == "greedy":
1447
+ # Construct an initial solution using a greedy algorithm.
1448
+ cycle = greedy_tsp(G, weight=weight, source=source)
1449
+ if G.number_of_nodes() == 2:
1450
+ return cycle
1451
+
1452
+ else:
1453
+ cycle = list(init_cycle)
1454
+ if source is None:
1455
+ source = cycle[0]
1456
+ elif source != cycle[0]:
1457
+ raise nx.NetworkXError("source must be first node in init_cycle")
1458
+ if cycle[0] != cycle[-1]:
1459
+ raise nx.NetworkXError("init_cycle must be a cycle. (return to start)")
1460
+
1461
+ if len(cycle) - 1 != len(G) or len(set(G.nbunch_iter(cycle))) != len(G):
1462
+ raise nx.NetworkXError("init_cycle is not all and only nodes.")
1463
+
1464
+ # Check that G is a complete graph
1465
+ N = len(G) - 1
1466
+ # This check ignores selfloops which is what we want here.
1467
+ if any(len(nbrdict) - (n in nbrdict) != N for n, nbrdict in G.adj.items()):
1468
+ raise nx.NetworkXError("G must be a complete graph.")
1469
+
1470
+ if G.number_of_nodes() == 2:
1471
+ neighbor = list(G.neighbors(source))[0]
1472
+ return [source, neighbor, source]
1473
+
1474
+ # Find the cost of initial solution
1475
+ cost = sum(G[u][v].get(weight, 1) for u, v in pairwise(cycle))
1476
+
1477
+ count = 0
1478
+ best_cycle = cycle.copy()
1479
+ best_cost = cost
1480
+ while count <= max_iterations:
1481
+ count += 1
1482
+ accepted = False
1483
+ for i in range(N_inner):
1484
+ adj_sol = move(cycle, seed)
1485
+ adj_cost = sum(G[u][v].get(weight, 1) for u, v in pairwise(adj_sol))
1486
+ delta = adj_cost - cost
1487
+ if delta <= threshold:
1488
+ accepted = True
1489
+
1490
+ # Set current solution the adjacent solution.
1491
+ cycle = adj_sol
1492
+ cost = adj_cost
1493
+
1494
+ if cost < best_cost:
1495
+ count = 0
1496
+ best_cycle = cycle.copy()
1497
+ best_cost = cost
1498
+ if accepted:
1499
+ threshold -= threshold * alpha
1500
+
1501
+ return best_cycle
.venv/lib/python3.11/site-packages/networkx/algorithms/broadcasting.py ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Routines to calculate the broadcast time of certain graphs.
2
+
3
+ Broadcasting is an information dissemination problem in which a node in a graph,
4
+ called the originator, must distribute a message to all other nodes by placing
5
+ a series of calls along the edges of the graph. Once informed, other nodes aid
6
+ the originator in distributing the message.
7
+
8
+ The broadcasting must be completed as quickly as possible subject to the
9
+ following constraints:
10
+ - Each call requires one unit of time.
11
+ - A node can only participate in one call per unit of time.
12
+ - Each call only involves two adjacent nodes: a sender and a receiver.
13
+ """
14
+
15
+ import networkx as nx
16
+ from networkx import NetworkXError
17
+ from networkx.utils import not_implemented_for
18
+
19
+ __all__ = [
20
+ "tree_broadcast_center",
21
+ "tree_broadcast_time",
22
+ ]
23
+
24
+
25
+ def _get_max_broadcast_value(G, U, v, values):
26
+ adj = sorted(set(G.neighbors(v)) & U, key=values.get, reverse=True)
27
+ return max(values[u] + i for i, u in enumerate(adj, start=1))
28
+
29
+
30
+ def _get_broadcast_centers(G, v, values, target):
31
+ adj = sorted(G.neighbors(v), key=values.get, reverse=True)
32
+ j = next(i for i, u in enumerate(adj, start=1) if values[u] + i == target)
33
+ return set([v] + adj[:j])
34
+
35
+
36
+ @not_implemented_for("directed")
37
+ @not_implemented_for("multigraph")
38
+ @nx._dispatchable
39
+ def tree_broadcast_center(G):
40
+ """Return the Broadcast Center of the tree `G`.
41
+
42
+ The broadcast center of a graph G denotes the set of nodes having
43
+ minimum broadcast time [1]_. This is a linear algorithm for determining
44
+ the broadcast center of a tree with ``N`` nodes, as a by-product it also
45
+ determines the broadcast time from the broadcast center.
46
+
47
+ Parameters
48
+ ----------
49
+ G : undirected graph
50
+ The graph should be an undirected tree
51
+
52
+ Returns
53
+ -------
54
+ BC : (int, set) tuple
55
+ minimum broadcast number of the tree, set of broadcast centers
56
+
57
+ Raises
58
+ ------
59
+ NetworkXNotImplemented
60
+ If the graph is directed or is a multigraph.
61
+
62
+ References
63
+ ----------
64
+ .. [1] Slater, P.J., Cockayne, E.J., Hedetniemi, S.T,
65
+ Information dissemination in trees. SIAM J.Comput. 10(4), 692–701 (1981)
66
+ """
67
+ # Assert that the graph G is a tree
68
+ if not nx.is_tree(G):
69
+ NetworkXError("Input graph is not a tree")
70
+ # step 0
71
+ if G.number_of_nodes() == 2:
72
+ return 1, set(G.nodes())
73
+ if G.number_of_nodes() == 1:
74
+ return 0, set(G.nodes())
75
+
76
+ # step 1
77
+ U = {node for node, deg in G.degree if deg == 1}
78
+ values = {n: 0 for n in U}
79
+ T = G.copy()
80
+ T.remove_nodes_from(U)
81
+
82
+ # step 2
83
+ W = {node for node, deg in T.degree if deg == 1}
84
+ values.update((w, G.degree[w] - 1) for w in W)
85
+
86
+ # step 3
87
+ while T.number_of_nodes() >= 2:
88
+ # step 4
89
+ w = min(W, key=lambda n: values[n])
90
+ v = next(T.neighbors(w))
91
+
92
+ # step 5
93
+ U.add(w)
94
+ W.remove(w)
95
+ T.remove_node(w)
96
+
97
+ # step 6
98
+ if T.degree(v) == 1:
99
+ # update t(v)
100
+ values.update({v: _get_max_broadcast_value(G, U, v, values)})
101
+ W.add(v)
102
+
103
+ # step 7
104
+ v = nx.utils.arbitrary_element(T)
105
+ b_T = _get_max_broadcast_value(G, U, v, values)
106
+ return b_T, _get_broadcast_centers(G, v, values, b_T)
107
+
108
+
109
+ @not_implemented_for("directed")
110
+ @not_implemented_for("multigraph")
111
+ @nx._dispatchable
112
+ def tree_broadcast_time(G, node=None):
113
+ """Return the Broadcast Time of the tree `G`.
114
+
115
+ The minimum broadcast time of a node is defined as the minimum amount
116
+ of time required to complete broadcasting starting from the
117
+ originator. The broadcast time of a graph is the maximum over
118
+ all nodes of the minimum broadcast time from that node [1]_.
119
+ This function returns the minimum broadcast time of `node`.
120
+ If `node` is None the broadcast time for the graph is returned.
121
+
122
+ Parameters
123
+ ----------
124
+ G : undirected graph
125
+ The graph should be an undirected tree
126
+ node: int, optional
127
+ index of starting node. If `None`, the algorithm returns the broadcast
128
+ time of the tree.
129
+
130
+ Returns
131
+ -------
132
+ BT : int
133
+ Broadcast Time of a node in a tree
134
+
135
+ Raises
136
+ ------
137
+ NetworkXNotImplemented
138
+ If the graph is directed or is a multigraph.
139
+
140
+ References
141
+ ----------
142
+ .. [1] Harutyunyan, H. A. and Li, Z.
143
+ "A Simple Construction of Broadcast Graphs."
144
+ In Computing and Combinatorics. COCOON 2019
145
+ (Ed. D. Z. Du and C. Tian.) Springer, pp. 240-253, 2019.
146
+ """
147
+ b_T, b_C = tree_broadcast_center(G)
148
+ if node is not None:
149
+ return b_T + min(nx.shortest_path_length(G, node, u) for u in b_C)
150
+ dist_from_center = dict.fromkeys(G, len(G))
151
+ for u in b_C:
152
+ for v, dist in nx.shortest_path_length(G, u).items():
153
+ if dist < dist_from_center[v]:
154
+ dist_from_center[v] = dist
155
+ return b_T + max(dist_from_center.values())
.venv/lib/python3.11/site-packages/networkx/algorithms/dominating.py ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Functions for computing dominating sets in a graph."""
2
+
3
+ from itertools import chain
4
+
5
+ import networkx as nx
6
+ from networkx.utils import arbitrary_element
7
+
8
+ __all__ = ["dominating_set", "is_dominating_set"]
9
+
10
+
11
+ @nx._dispatchable
12
+ def dominating_set(G, start_with=None):
13
+ r"""Finds a dominating set for the graph G.
14
+
15
+ A *dominating set* for a graph with node set *V* is a subset *D* of
16
+ *V* such that every node not in *D* is adjacent to at least one
17
+ member of *D* [1]_.
18
+
19
+ Parameters
20
+ ----------
21
+ G : NetworkX graph
22
+
23
+ start_with : node (default=None)
24
+ Node to use as a starting point for the algorithm.
25
+
26
+ Returns
27
+ -------
28
+ D : set
29
+ A dominating set for G.
30
+
31
+ Notes
32
+ -----
33
+ This function is an implementation of algorithm 7 in [2]_ which
34
+ finds some dominating set, not necessarily the smallest one.
35
+
36
+ See also
37
+ --------
38
+ is_dominating_set
39
+
40
+ References
41
+ ----------
42
+ .. [1] https://en.wikipedia.org/wiki/Dominating_set
43
+
44
+ .. [2] Abdol-Hossein Esfahanian. Connectivity Algorithms.
45
+ http://www.cse.msu.edu/~cse835/Papers/Graph_connectivity_revised.pdf
46
+
47
+ """
48
+ all_nodes = set(G)
49
+ if start_with is None:
50
+ start_with = arbitrary_element(all_nodes)
51
+ if start_with not in G:
52
+ raise nx.NetworkXError(f"node {start_with} is not in G")
53
+ dominating_set = {start_with}
54
+ dominated_nodes = set(G[start_with])
55
+ remaining_nodes = all_nodes - dominated_nodes - dominating_set
56
+ while remaining_nodes:
57
+ # Choose an arbitrary node and determine its undominated neighbors.
58
+ v = remaining_nodes.pop()
59
+ undominated_nbrs = set(G[v]) - dominating_set
60
+ # Add the node to the dominating set and the neighbors to the
61
+ # dominated set. Finally, remove all of those nodes from the set
62
+ # of remaining nodes.
63
+ dominating_set.add(v)
64
+ dominated_nodes |= undominated_nbrs
65
+ remaining_nodes -= undominated_nbrs
66
+ return dominating_set
67
+
68
+
69
+ @nx._dispatchable
70
+ def is_dominating_set(G, nbunch):
71
+ """Checks if `nbunch` is a dominating set for `G`.
72
+
73
+ A *dominating set* for a graph with node set *V* is a subset *D* of
74
+ *V* such that every node not in *D* is adjacent to at least one
75
+ member of *D* [1]_.
76
+
77
+ Parameters
78
+ ----------
79
+ G : NetworkX graph
80
+
81
+ nbunch : iterable
82
+ An iterable of nodes in the graph `G`.
83
+
84
+ See also
85
+ --------
86
+ dominating_set
87
+
88
+ References
89
+ ----------
90
+ .. [1] https://en.wikipedia.org/wiki/Dominating_set
91
+
92
+ """
93
+ testset = {n for n in nbunch if n in G}
94
+ nbrs = set(chain.from_iterable(G[n] for n in testset))
95
+ return len(set(G) - testset - nbrs) == 0
.venv/lib/python3.11/site-packages/networkx/algorithms/flow/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (642 Bytes). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/flow/__pycache__/boykovkolmogorov.cpython-311.pyc ADDED
Binary file (15.9 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/flow/__pycache__/capacityscaling.cpython-311.pyc ADDED
Binary file (19.8 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/flow/__pycache__/dinitz_alg.cpython-311.pyc ADDED
Binary file (9.78 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/flow/__pycache__/edmondskarp.cpython-311.pyc ADDED
Binary file (9.5 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/flow/__pycache__/gomory_hu.cpython-311.pyc ADDED
Binary file (7.09 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/flow/__pycache__/maxflow.cpython-311.pyc ADDED
Binary file (23.7 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/flow/__pycache__/mincost.cpython-311.pyc ADDED
Binary file (14.2 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/flow/__pycache__/networksimplex.cpython-311.pyc ADDED
Binary file (32.3 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/flow/__pycache__/preflowpush.cpython-311.pyc ADDED
Binary file (17.5 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/flow/__pycache__/shortestaugmentingpath.cpython-311.pyc ADDED
Binary file (11.2 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/flow/__pycache__/utils.cpython-311.pyc ADDED
Binary file (9.78 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/flow/edmondskarp.py ADDED
@@ -0,0 +1,241 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Edmonds-Karp algorithm for maximum flow problems.
3
+ """
4
+
5
+ import networkx as nx
6
+ from networkx.algorithms.flow.utils import build_residual_network
7
+
8
+ __all__ = ["edmonds_karp"]
9
+
10
+
11
+ def edmonds_karp_core(R, s, t, cutoff):
12
+ """Implementation of the Edmonds-Karp algorithm."""
13
+ R_nodes = R.nodes
14
+ R_pred = R.pred
15
+ R_succ = R.succ
16
+
17
+ inf = R.graph["inf"]
18
+
19
+ def augment(path):
20
+ """Augment flow along a path from s to t."""
21
+ # Determine the path residual capacity.
22
+ flow = inf
23
+ it = iter(path)
24
+ u = next(it)
25
+ for v in it:
26
+ attr = R_succ[u][v]
27
+ flow = min(flow, attr["capacity"] - attr["flow"])
28
+ u = v
29
+ if flow * 2 > inf:
30
+ raise nx.NetworkXUnbounded("Infinite capacity path, flow unbounded above.")
31
+ # Augment flow along the path.
32
+ it = iter(path)
33
+ u = next(it)
34
+ for v in it:
35
+ R_succ[u][v]["flow"] += flow
36
+ R_succ[v][u]["flow"] -= flow
37
+ u = v
38
+ return flow
39
+
40
+ def bidirectional_bfs():
41
+ """Bidirectional breadth-first search for an augmenting path."""
42
+ pred = {s: None}
43
+ q_s = [s]
44
+ succ = {t: None}
45
+ q_t = [t]
46
+ while True:
47
+ q = []
48
+ if len(q_s) <= len(q_t):
49
+ for u in q_s:
50
+ for v, attr in R_succ[u].items():
51
+ if v not in pred and attr["flow"] < attr["capacity"]:
52
+ pred[v] = u
53
+ if v in succ:
54
+ return v, pred, succ
55
+ q.append(v)
56
+ if not q:
57
+ return None, None, None
58
+ q_s = q
59
+ else:
60
+ for u in q_t:
61
+ for v, attr in R_pred[u].items():
62
+ if v not in succ and attr["flow"] < attr["capacity"]:
63
+ succ[v] = u
64
+ if v in pred:
65
+ return v, pred, succ
66
+ q.append(v)
67
+ if not q:
68
+ return None, None, None
69
+ q_t = q
70
+
71
+ # Look for shortest augmenting paths using breadth-first search.
72
+ flow_value = 0
73
+ while flow_value < cutoff:
74
+ v, pred, succ = bidirectional_bfs()
75
+ if pred is None:
76
+ break
77
+ path = [v]
78
+ # Trace a path from s to v.
79
+ u = v
80
+ while u != s:
81
+ u = pred[u]
82
+ path.append(u)
83
+ path.reverse()
84
+ # Trace a path from v to t.
85
+ u = v
86
+ while u != t:
87
+ u = succ[u]
88
+ path.append(u)
89
+ flow_value += augment(path)
90
+
91
+ return flow_value
92
+
93
+
94
+ def edmonds_karp_impl(G, s, t, capacity, residual, cutoff):
95
+ """Implementation of the Edmonds-Karp algorithm."""
96
+ if s not in G:
97
+ raise nx.NetworkXError(f"node {str(s)} not in graph")
98
+ if t not in G:
99
+ raise nx.NetworkXError(f"node {str(t)} not in graph")
100
+ if s == t:
101
+ raise nx.NetworkXError("source and sink are the same node")
102
+
103
+ if residual is None:
104
+ R = build_residual_network(G, capacity)
105
+ else:
106
+ R = residual
107
+
108
+ # Initialize/reset the residual network.
109
+ for u in R:
110
+ for e in R[u].values():
111
+ e["flow"] = 0
112
+
113
+ if cutoff is None:
114
+ cutoff = float("inf")
115
+ R.graph["flow_value"] = edmonds_karp_core(R, s, t, cutoff)
116
+
117
+ return R
118
+
119
+
120
+ @nx._dispatchable(edge_attrs={"capacity": float("inf")}, returns_graph=True)
121
+ def edmonds_karp(
122
+ G, s, t, capacity="capacity", residual=None, value_only=False, cutoff=None
123
+ ):
124
+ """Find a maximum single-commodity flow using the Edmonds-Karp algorithm.
125
+
126
+ This function returns the residual network resulting after computing
127
+ the maximum flow. See below for details about the conventions
128
+ NetworkX uses for defining residual networks.
129
+
130
+ This algorithm has a running time of $O(n m^2)$ for $n$ nodes and $m$
131
+ edges.
132
+
133
+
134
+ Parameters
135
+ ----------
136
+ G : NetworkX graph
137
+ Edges of the graph are expected to have an attribute called
138
+ 'capacity'. If this attribute is not present, the edge is
139
+ considered to have infinite capacity.
140
+
141
+ s : node
142
+ Source node for the flow.
143
+
144
+ t : node
145
+ Sink node for the flow.
146
+
147
+ capacity : string
148
+ Edges of the graph G are expected to have an attribute capacity
149
+ that indicates how much flow the edge can support. If this
150
+ attribute is not present, the edge is considered to have
151
+ infinite capacity. Default value: 'capacity'.
152
+
153
+ residual : NetworkX graph
154
+ Residual network on which the algorithm is to be executed. If None, a
155
+ new residual network is created. Default value: None.
156
+
157
+ value_only : bool
158
+ If True compute only the value of the maximum flow. This parameter
159
+ will be ignored by this algorithm because it is not applicable.
160
+
161
+ cutoff : integer, float
162
+ If specified, the algorithm will terminate when the flow value reaches
163
+ or exceeds the cutoff. In this case, it may be unable to immediately
164
+ determine a minimum cut. Default value: None.
165
+
166
+ Returns
167
+ -------
168
+ R : NetworkX DiGraph
169
+ Residual network after computing the maximum flow.
170
+
171
+ Raises
172
+ ------
173
+ NetworkXError
174
+ The algorithm does not support MultiGraph and MultiDiGraph. If
175
+ the input graph is an instance of one of these two classes, a
176
+ NetworkXError is raised.
177
+
178
+ NetworkXUnbounded
179
+ If the graph has a path of infinite capacity, the value of a
180
+ feasible flow on the graph is unbounded above and the function
181
+ raises a NetworkXUnbounded.
182
+
183
+ See also
184
+ --------
185
+ :meth:`maximum_flow`
186
+ :meth:`minimum_cut`
187
+ :meth:`preflow_push`
188
+ :meth:`shortest_augmenting_path`
189
+
190
+ Notes
191
+ -----
192
+ The residual network :samp:`R` from an input graph :samp:`G` has the
193
+ same nodes as :samp:`G`. :samp:`R` is a DiGraph that contains a pair
194
+ of edges :samp:`(u, v)` and :samp:`(v, u)` iff :samp:`(u, v)` is not a
195
+ self-loop, and at least one of :samp:`(u, v)` and :samp:`(v, u)` exists
196
+ in :samp:`G`.
197
+
198
+ For each edge :samp:`(u, v)` in :samp:`R`, :samp:`R[u][v]['capacity']`
199
+ is equal to the capacity of :samp:`(u, v)` in :samp:`G` if it exists
200
+ in :samp:`G` or zero otherwise. If the capacity is infinite,
201
+ :samp:`R[u][v]['capacity']` will have a high arbitrary finite value
202
+ that does not affect the solution of the problem. This value is stored in
203
+ :samp:`R.graph['inf']`. For each edge :samp:`(u, v)` in :samp:`R`,
204
+ :samp:`R[u][v]['flow']` represents the flow function of :samp:`(u, v)` and
205
+ satisfies :samp:`R[u][v]['flow'] == -R[v][u]['flow']`.
206
+
207
+ The flow value, defined as the total flow into :samp:`t`, the sink, is
208
+ stored in :samp:`R.graph['flow_value']`. If :samp:`cutoff` is not
209
+ specified, reachability to :samp:`t` using only edges :samp:`(u, v)` such
210
+ that :samp:`R[u][v]['flow'] < R[u][v]['capacity']` induces a minimum
211
+ :samp:`s`-:samp:`t` cut.
212
+
213
+ Examples
214
+ --------
215
+ >>> from networkx.algorithms.flow import edmonds_karp
216
+
217
+ The functions that implement flow algorithms and output a residual
218
+ network, such as this one, are not imported to the base NetworkX
219
+ namespace, so you have to explicitly import them from the flow package.
220
+
221
+ >>> G = nx.DiGraph()
222
+ >>> G.add_edge("x", "a", capacity=3.0)
223
+ >>> G.add_edge("x", "b", capacity=1.0)
224
+ >>> G.add_edge("a", "c", capacity=3.0)
225
+ >>> G.add_edge("b", "c", capacity=5.0)
226
+ >>> G.add_edge("b", "d", capacity=4.0)
227
+ >>> G.add_edge("d", "e", capacity=2.0)
228
+ >>> G.add_edge("c", "y", capacity=2.0)
229
+ >>> G.add_edge("e", "y", capacity=3.0)
230
+ >>> R = edmonds_karp(G, "x", "y")
231
+ >>> flow_value = nx.maximum_flow_value(G, "x", "y")
232
+ >>> flow_value
233
+ 3.0
234
+ >>> flow_value == R.graph["flow_value"]
235
+ True
236
+
237
+ """
238
+ R = edmonds_karp_impl(G, s, t, capacity, residual, cutoff)
239
+ R.graph["algorithm"] = "edmonds_karp"
240
+ nx._clear_cache(R)
241
+ return R
.venv/lib/python3.11/site-packages/networkx/algorithms/flow/mincost.py ADDED
@@ -0,0 +1,356 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Minimum cost flow algorithms on directed connected graphs.
3
+ """
4
+
5
+ __all__ = ["min_cost_flow_cost", "min_cost_flow", "cost_of_flow", "max_flow_min_cost"]
6
+
7
+ import networkx as nx
8
+
9
+
10
+ @nx._dispatchable(
11
+ node_attrs="demand", edge_attrs={"capacity": float("inf"), "weight": 0}
12
+ )
13
+ def min_cost_flow_cost(G, demand="demand", capacity="capacity", weight="weight"):
14
+ r"""Find the cost of a minimum cost flow satisfying all demands in digraph G.
15
+
16
+ G is a digraph with edge costs and capacities and in which nodes
17
+ have demand, i.e., they want to send or receive some amount of
18
+ flow. A negative demand means that the node wants to send flow, a
19
+ positive demand means that the node want to receive flow. A flow on
20
+ the digraph G satisfies all demand if the net flow into each node
21
+ is equal to the demand of that node.
22
+
23
+ Parameters
24
+ ----------
25
+ G : NetworkX graph
26
+ DiGraph on which a minimum cost flow satisfying all demands is
27
+ to be found.
28
+
29
+ demand : string
30
+ Nodes of the graph G are expected to have an attribute demand
31
+ that indicates how much flow a node wants to send (negative
32
+ demand) or receive (positive demand). Note that the sum of the
33
+ demands should be 0 otherwise the problem in not feasible. If
34
+ this attribute is not present, a node is considered to have 0
35
+ demand. Default value: 'demand'.
36
+
37
+ capacity : string
38
+ Edges of the graph G are expected to have an attribute capacity
39
+ that indicates how much flow the edge can support. If this
40
+ attribute is not present, the edge is considered to have
41
+ infinite capacity. Default value: 'capacity'.
42
+
43
+ weight : string
44
+ Edges of the graph G are expected to have an attribute weight
45
+ that indicates the cost incurred by sending one unit of flow on
46
+ that edge. If not present, the weight is considered to be 0.
47
+ Default value: 'weight'.
48
+
49
+ Returns
50
+ -------
51
+ flowCost : integer, float
52
+ Cost of a minimum cost flow satisfying all demands.
53
+
54
+ Raises
55
+ ------
56
+ NetworkXError
57
+ This exception is raised if the input graph is not directed or
58
+ not connected.
59
+
60
+ NetworkXUnfeasible
61
+ This exception is raised in the following situations:
62
+
63
+ * The sum of the demands is not zero. Then, there is no
64
+ flow satisfying all demands.
65
+ * There is no flow satisfying all demand.
66
+
67
+ NetworkXUnbounded
68
+ This exception is raised if the digraph G has a cycle of
69
+ negative cost and infinite capacity. Then, the cost of a flow
70
+ satisfying all demands is unbounded below.
71
+
72
+ See also
73
+ --------
74
+ cost_of_flow, max_flow_min_cost, min_cost_flow, network_simplex
75
+
76
+ Notes
77
+ -----
78
+ This algorithm is not guaranteed to work if edge weights or demands
79
+ are floating point numbers (overflows and roundoff errors can
80
+ cause problems). As a workaround you can use integer numbers by
81
+ multiplying the relevant edge attributes by a convenient
82
+ constant factor (eg 100).
83
+
84
+ Examples
85
+ --------
86
+ A simple example of a min cost flow problem.
87
+
88
+ >>> G = nx.DiGraph()
89
+ >>> G.add_node("a", demand=-5)
90
+ >>> G.add_node("d", demand=5)
91
+ >>> G.add_edge("a", "b", weight=3, capacity=4)
92
+ >>> G.add_edge("a", "c", weight=6, capacity=10)
93
+ >>> G.add_edge("b", "d", weight=1, capacity=9)
94
+ >>> G.add_edge("c", "d", weight=2, capacity=5)
95
+ >>> flowCost = nx.min_cost_flow_cost(G)
96
+ >>> flowCost
97
+ 24
98
+ """
99
+ return nx.network_simplex(G, demand=demand, capacity=capacity, weight=weight)[0]
100
+
101
+
102
+ @nx._dispatchable(
103
+ node_attrs="demand", edge_attrs={"capacity": float("inf"), "weight": 0}
104
+ )
105
+ def min_cost_flow(G, demand="demand", capacity="capacity", weight="weight"):
106
+ r"""Returns a minimum cost flow satisfying all demands in digraph G.
107
+
108
+ G is a digraph with edge costs and capacities and in which nodes
109
+ have demand, i.e., they want to send or receive some amount of
110
+ flow. A negative demand means that the node wants to send flow, a
111
+ positive demand means that the node want to receive flow. A flow on
112
+ the digraph G satisfies all demand if the net flow into each node
113
+ is equal to the demand of that node.
114
+
115
+ Parameters
116
+ ----------
117
+ G : NetworkX graph
118
+ DiGraph on which a minimum cost flow satisfying all demands is
119
+ to be found.
120
+
121
+ demand : string
122
+ Nodes of the graph G are expected to have an attribute demand
123
+ that indicates how much flow a node wants to send (negative
124
+ demand) or receive (positive demand). Note that the sum of the
125
+ demands should be 0 otherwise the problem in not feasible. If
126
+ this attribute is not present, a node is considered to have 0
127
+ demand. Default value: 'demand'.
128
+
129
+ capacity : string
130
+ Edges of the graph G are expected to have an attribute capacity
131
+ that indicates how much flow the edge can support. If this
132
+ attribute is not present, the edge is considered to have
133
+ infinite capacity. Default value: 'capacity'.
134
+
135
+ weight : string
136
+ Edges of the graph G are expected to have an attribute weight
137
+ that indicates the cost incurred by sending one unit of flow on
138
+ that edge. If not present, the weight is considered to be 0.
139
+ Default value: 'weight'.
140
+
141
+ Returns
142
+ -------
143
+ flowDict : dictionary
144
+ Dictionary of dictionaries keyed by nodes such that
145
+ flowDict[u][v] is the flow edge (u, v).
146
+
147
+ Raises
148
+ ------
149
+ NetworkXError
150
+ This exception is raised if the input graph is not directed or
151
+ not connected.
152
+
153
+ NetworkXUnfeasible
154
+ This exception is raised in the following situations:
155
+
156
+ * The sum of the demands is not zero. Then, there is no
157
+ flow satisfying all demands.
158
+ * There is no flow satisfying all demand.
159
+
160
+ NetworkXUnbounded
161
+ This exception is raised if the digraph G has a cycle of
162
+ negative cost and infinite capacity. Then, the cost of a flow
163
+ satisfying all demands is unbounded below.
164
+
165
+ See also
166
+ --------
167
+ cost_of_flow, max_flow_min_cost, min_cost_flow_cost, network_simplex
168
+
169
+ Notes
170
+ -----
171
+ This algorithm is not guaranteed to work if edge weights or demands
172
+ are floating point numbers (overflows and roundoff errors can
173
+ cause problems). As a workaround you can use integer numbers by
174
+ multiplying the relevant edge attributes by a convenient
175
+ constant factor (eg 100).
176
+
177
+ Examples
178
+ --------
179
+ A simple example of a min cost flow problem.
180
+
181
+ >>> G = nx.DiGraph()
182
+ >>> G.add_node("a", demand=-5)
183
+ >>> G.add_node("d", demand=5)
184
+ >>> G.add_edge("a", "b", weight=3, capacity=4)
185
+ >>> G.add_edge("a", "c", weight=6, capacity=10)
186
+ >>> G.add_edge("b", "d", weight=1, capacity=9)
187
+ >>> G.add_edge("c", "d", weight=2, capacity=5)
188
+ >>> flowDict = nx.min_cost_flow(G)
189
+ >>> flowDict
190
+ {'a': {'b': 4, 'c': 1}, 'd': {}, 'b': {'d': 4}, 'c': {'d': 1}}
191
+ """
192
+ return nx.network_simplex(G, demand=demand, capacity=capacity, weight=weight)[1]
193
+
194
+
195
+ @nx._dispatchable(edge_attrs={"weight": 0})
196
+ def cost_of_flow(G, flowDict, weight="weight"):
197
+ """Compute the cost of the flow given by flowDict on graph G.
198
+
199
+ Note that this function does not check for the validity of the
200
+ flow flowDict. This function will fail if the graph G and the
201
+ flow don't have the same edge set.
202
+
203
+ Parameters
204
+ ----------
205
+ G : NetworkX graph
206
+ DiGraph on which a minimum cost flow satisfying all demands is
207
+ to be found.
208
+
209
+ weight : string
210
+ Edges of the graph G are expected to have an attribute weight
211
+ that indicates the cost incurred by sending one unit of flow on
212
+ that edge. If not present, the weight is considered to be 0.
213
+ Default value: 'weight'.
214
+
215
+ flowDict : dictionary
216
+ Dictionary of dictionaries keyed by nodes such that
217
+ flowDict[u][v] is the flow edge (u, v).
218
+
219
+ Returns
220
+ -------
221
+ cost : Integer, float
222
+ The total cost of the flow. This is given by the sum over all
223
+ edges of the product of the edge's flow and the edge's weight.
224
+
225
+ See also
226
+ --------
227
+ max_flow_min_cost, min_cost_flow, min_cost_flow_cost, network_simplex
228
+
229
+ Notes
230
+ -----
231
+ This algorithm is not guaranteed to work if edge weights or demands
232
+ are floating point numbers (overflows and roundoff errors can
233
+ cause problems). As a workaround you can use integer numbers by
234
+ multiplying the relevant edge attributes by a convenient
235
+ constant factor (eg 100).
236
+
237
+ Examples
238
+ --------
239
+ >>> G = nx.DiGraph()
240
+ >>> G.add_node("a", demand=-5)
241
+ >>> G.add_node("d", demand=5)
242
+ >>> G.add_edge("a", "b", weight=3, capacity=4)
243
+ >>> G.add_edge("a", "c", weight=6, capacity=10)
244
+ >>> G.add_edge("b", "d", weight=1, capacity=9)
245
+ >>> G.add_edge("c", "d", weight=2, capacity=5)
246
+ >>> flowDict = nx.min_cost_flow(G)
247
+ >>> flowDict
248
+ {'a': {'b': 4, 'c': 1}, 'd': {}, 'b': {'d': 4}, 'c': {'d': 1}}
249
+ >>> nx.cost_of_flow(G, flowDict)
250
+ 24
251
+ """
252
+ return sum((flowDict[u][v] * d.get(weight, 0) for u, v, d in G.edges(data=True)))
253
+
254
+
255
+ @nx._dispatchable(edge_attrs={"capacity": float("inf"), "weight": 0})
256
+ def max_flow_min_cost(G, s, t, capacity="capacity", weight="weight"):
257
+ """Returns a maximum (s, t)-flow of minimum cost.
258
+
259
+ G is a digraph with edge costs and capacities. There is a source
260
+ node s and a sink node t. This function finds a maximum flow from
261
+ s to t whose total cost is minimized.
262
+
263
+ Parameters
264
+ ----------
265
+ G : NetworkX graph
266
+ DiGraph on which a minimum cost flow satisfying all demands is
267
+ to be found.
268
+
269
+ s: node label
270
+ Source of the flow.
271
+
272
+ t: node label
273
+ Destination of the flow.
274
+
275
+ capacity: string
276
+ Edges of the graph G are expected to have an attribute capacity
277
+ that indicates how much flow the edge can support. If this
278
+ attribute is not present, the edge is considered to have
279
+ infinite capacity. Default value: 'capacity'.
280
+
281
+ weight: string
282
+ Edges of the graph G are expected to have an attribute weight
283
+ that indicates the cost incurred by sending one unit of flow on
284
+ that edge. If not present, the weight is considered to be 0.
285
+ Default value: 'weight'.
286
+
287
+ Returns
288
+ -------
289
+ flowDict: dictionary
290
+ Dictionary of dictionaries keyed by nodes such that
291
+ flowDict[u][v] is the flow edge (u, v).
292
+
293
+ Raises
294
+ ------
295
+ NetworkXError
296
+ This exception is raised if the input graph is not directed or
297
+ not connected.
298
+
299
+ NetworkXUnbounded
300
+ This exception is raised if there is an infinite capacity path
301
+ from s to t in G. In this case there is no maximum flow. This
302
+ exception is also raised if the digraph G has a cycle of
303
+ negative cost and infinite capacity. Then, the cost of a flow
304
+ is unbounded below.
305
+
306
+ See also
307
+ --------
308
+ cost_of_flow, min_cost_flow, min_cost_flow_cost, network_simplex
309
+
310
+ Notes
311
+ -----
312
+ This algorithm is not guaranteed to work if edge weights or demands
313
+ are floating point numbers (overflows and roundoff errors can
314
+ cause problems). As a workaround you can use integer numbers by
315
+ multiplying the relevant edge attributes by a convenient
316
+ constant factor (eg 100).
317
+
318
+ Examples
319
+ --------
320
+ >>> G = nx.DiGraph()
321
+ >>> G.add_edges_from(
322
+ ... [
323
+ ... (1, 2, {"capacity": 12, "weight": 4}),
324
+ ... (1, 3, {"capacity": 20, "weight": 6}),
325
+ ... (2, 3, {"capacity": 6, "weight": -3}),
326
+ ... (2, 6, {"capacity": 14, "weight": 1}),
327
+ ... (3, 4, {"weight": 9}),
328
+ ... (3, 5, {"capacity": 10, "weight": 5}),
329
+ ... (4, 2, {"capacity": 19, "weight": 13}),
330
+ ... (4, 5, {"capacity": 4, "weight": 0}),
331
+ ... (5, 7, {"capacity": 28, "weight": 2}),
332
+ ... (6, 5, {"capacity": 11, "weight": 1}),
333
+ ... (6, 7, {"weight": 8}),
334
+ ... (7, 4, {"capacity": 6, "weight": 6}),
335
+ ... ]
336
+ ... )
337
+ >>> mincostFlow = nx.max_flow_min_cost(G, 1, 7)
338
+ >>> mincost = nx.cost_of_flow(G, mincostFlow)
339
+ >>> mincost
340
+ 373
341
+ >>> from networkx.algorithms.flow import maximum_flow
342
+ >>> maxFlow = maximum_flow(G, 1, 7)[1]
343
+ >>> nx.cost_of_flow(G, maxFlow) >= mincost
344
+ True
345
+ >>> mincostFlowValue = sum((mincostFlow[u][7] for u in G.predecessors(7))) - sum(
346
+ ... (mincostFlow[7][v] for v in G.successors(7))
347
+ ... )
348
+ >>> mincostFlowValue == nx.maximum_flow_value(G, 1, 7)
349
+ True
350
+
351
+ """
352
+ maxFlow = nx.maximum_flow_value(G, s, t, capacity=capacity)
353
+ H = nx.DiGraph(G)
354
+ H.add_node(s, demand=-maxFlow)
355
+ H.add_node(t, demand=maxFlow)
356
+ return min_cost_flow(H, capacity=capacity, weight=weight)
.venv/lib/python3.11/site-packages/networkx/algorithms/flow/shortestaugmentingpath.py ADDED
@@ -0,0 +1,300 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Shortest augmenting path algorithm for maximum flow problems.
3
+ """
4
+
5
+ from collections import deque
6
+
7
+ import networkx as nx
8
+
9
+ from .edmondskarp import edmonds_karp_core
10
+ from .utils import CurrentEdge, build_residual_network
11
+
12
+ __all__ = ["shortest_augmenting_path"]
13
+
14
+
15
+ def shortest_augmenting_path_impl(G, s, t, capacity, residual, two_phase, cutoff):
16
+ """Implementation of the shortest augmenting path algorithm."""
17
+ if s not in G:
18
+ raise nx.NetworkXError(f"node {str(s)} not in graph")
19
+ if t not in G:
20
+ raise nx.NetworkXError(f"node {str(t)} not in graph")
21
+ if s == t:
22
+ raise nx.NetworkXError("source and sink are the same node")
23
+
24
+ if residual is None:
25
+ R = build_residual_network(G, capacity)
26
+ else:
27
+ R = residual
28
+
29
+ R_nodes = R.nodes
30
+ R_pred = R.pred
31
+ R_succ = R.succ
32
+
33
+ # Initialize/reset the residual network.
34
+ for u in R:
35
+ for e in R_succ[u].values():
36
+ e["flow"] = 0
37
+
38
+ # Initialize heights of the nodes.
39
+ heights = {t: 0}
40
+ q = deque([(t, 0)])
41
+ while q:
42
+ u, height = q.popleft()
43
+ height += 1
44
+ for v, attr in R_pred[u].items():
45
+ if v not in heights and attr["flow"] < attr["capacity"]:
46
+ heights[v] = height
47
+ q.append((v, height))
48
+
49
+ if s not in heights:
50
+ # t is not reachable from s in the residual network. The maximum flow
51
+ # must be zero.
52
+ R.graph["flow_value"] = 0
53
+ return R
54
+
55
+ n = len(G)
56
+ m = R.size() / 2
57
+
58
+ # Initialize heights and 'current edge' data structures of the nodes.
59
+ for u in R:
60
+ R_nodes[u]["height"] = heights[u] if u in heights else n
61
+ R_nodes[u]["curr_edge"] = CurrentEdge(R_succ[u])
62
+
63
+ # Initialize counts of nodes in each level.
64
+ counts = [0] * (2 * n - 1)
65
+ for u in R:
66
+ counts[R_nodes[u]["height"]] += 1
67
+
68
+ inf = R.graph["inf"]
69
+
70
+ def augment(path):
71
+ """Augment flow along a path from s to t."""
72
+ # Determine the path residual capacity.
73
+ flow = inf
74
+ it = iter(path)
75
+ u = next(it)
76
+ for v in it:
77
+ attr = R_succ[u][v]
78
+ flow = min(flow, attr["capacity"] - attr["flow"])
79
+ u = v
80
+ if flow * 2 > inf:
81
+ raise nx.NetworkXUnbounded("Infinite capacity path, flow unbounded above.")
82
+ # Augment flow along the path.
83
+ it = iter(path)
84
+ u = next(it)
85
+ for v in it:
86
+ R_succ[u][v]["flow"] += flow
87
+ R_succ[v][u]["flow"] -= flow
88
+ u = v
89
+ return flow
90
+
91
+ def relabel(u):
92
+ """Relabel a node to create an admissible edge."""
93
+ height = n - 1
94
+ for v, attr in R_succ[u].items():
95
+ if attr["flow"] < attr["capacity"]:
96
+ height = min(height, R_nodes[v]["height"])
97
+ return height + 1
98
+
99
+ if cutoff is None:
100
+ cutoff = float("inf")
101
+
102
+ # Phase 1: Look for shortest augmenting paths using depth-first search.
103
+
104
+ flow_value = 0
105
+ path = [s]
106
+ u = s
107
+ d = n if not two_phase else int(min(m**0.5, 2 * n ** (2.0 / 3)))
108
+ done = R_nodes[s]["height"] >= d
109
+ while not done:
110
+ height = R_nodes[u]["height"]
111
+ curr_edge = R_nodes[u]["curr_edge"]
112
+ # Depth-first search for the next node on the path to t.
113
+ while True:
114
+ v, attr = curr_edge.get()
115
+ if height == R_nodes[v]["height"] + 1 and attr["flow"] < attr["capacity"]:
116
+ # Advance to the next node following an admissible edge.
117
+ path.append(v)
118
+ u = v
119
+ break
120
+ try:
121
+ curr_edge.move_to_next()
122
+ except StopIteration:
123
+ counts[height] -= 1
124
+ if counts[height] == 0:
125
+ # Gap heuristic: If relabeling causes a level to become
126
+ # empty, a minimum cut has been identified. The algorithm
127
+ # can now be terminated.
128
+ R.graph["flow_value"] = flow_value
129
+ return R
130
+ height = relabel(u)
131
+ if u == s and height >= d:
132
+ if not two_phase:
133
+ # t is disconnected from s in the residual network. No
134
+ # more augmenting paths exist.
135
+ R.graph["flow_value"] = flow_value
136
+ return R
137
+ else:
138
+ # t is at least d steps away from s. End of phase 1.
139
+ done = True
140
+ break
141
+ counts[height] += 1
142
+ R_nodes[u]["height"] = height
143
+ if u != s:
144
+ # After relabeling, the last edge on the path is no longer
145
+ # admissible. Retreat one step to look for an alternative.
146
+ path.pop()
147
+ u = path[-1]
148
+ break
149
+ if u == t:
150
+ # t is reached. Augment flow along the path and reset it for a new
151
+ # depth-first search.
152
+ flow_value += augment(path)
153
+ if flow_value >= cutoff:
154
+ R.graph["flow_value"] = flow_value
155
+ return R
156
+ path = [s]
157
+ u = s
158
+
159
+ # Phase 2: Look for shortest augmenting paths using breadth-first search.
160
+ flow_value += edmonds_karp_core(R, s, t, cutoff - flow_value)
161
+
162
+ R.graph["flow_value"] = flow_value
163
+ return R
164
+
165
+
166
+ @nx._dispatchable(edge_attrs={"capacity": float("inf")}, returns_graph=True)
167
+ def shortest_augmenting_path(
168
+ G,
169
+ s,
170
+ t,
171
+ capacity="capacity",
172
+ residual=None,
173
+ value_only=False,
174
+ two_phase=False,
175
+ cutoff=None,
176
+ ):
177
+ r"""Find a maximum single-commodity flow using the shortest augmenting path
178
+ algorithm.
179
+
180
+ This function returns the residual network resulting after computing
181
+ the maximum flow. See below for details about the conventions
182
+ NetworkX uses for defining residual networks.
183
+
184
+ This algorithm has a running time of $O(n^2 m)$ for $n$ nodes and $m$
185
+ edges.
186
+
187
+
188
+ Parameters
189
+ ----------
190
+ G : NetworkX graph
191
+ Edges of the graph are expected to have an attribute called
192
+ 'capacity'. If this attribute is not present, the edge is
193
+ considered to have infinite capacity.
194
+
195
+ s : node
196
+ Source node for the flow.
197
+
198
+ t : node
199
+ Sink node for the flow.
200
+
201
+ capacity : string
202
+ Edges of the graph G are expected to have an attribute capacity
203
+ that indicates how much flow the edge can support. If this
204
+ attribute is not present, the edge is considered to have
205
+ infinite capacity. Default value: 'capacity'.
206
+
207
+ residual : NetworkX graph
208
+ Residual network on which the algorithm is to be executed. If None, a
209
+ new residual network is created. Default value: None.
210
+
211
+ value_only : bool
212
+ If True compute only the value of the maximum flow. This parameter
213
+ will be ignored by this algorithm because it is not applicable.
214
+
215
+ two_phase : bool
216
+ If True, a two-phase variant is used. The two-phase variant improves
217
+ the running time on unit-capacity networks from $O(nm)$ to
218
+ $O(\min(n^{2/3}, m^{1/2}) m)$. Default value: False.
219
+
220
+ cutoff : integer, float
221
+ If specified, the algorithm will terminate when the flow value reaches
222
+ or exceeds the cutoff. In this case, it may be unable to immediately
223
+ determine a minimum cut. Default value: None.
224
+
225
+ Returns
226
+ -------
227
+ R : NetworkX DiGraph
228
+ Residual network after computing the maximum flow.
229
+
230
+ Raises
231
+ ------
232
+ NetworkXError
233
+ The algorithm does not support MultiGraph and MultiDiGraph. If
234
+ the input graph is an instance of one of these two classes, a
235
+ NetworkXError is raised.
236
+
237
+ NetworkXUnbounded
238
+ If the graph has a path of infinite capacity, the value of a
239
+ feasible flow on the graph is unbounded above and the function
240
+ raises a NetworkXUnbounded.
241
+
242
+ See also
243
+ --------
244
+ :meth:`maximum_flow`
245
+ :meth:`minimum_cut`
246
+ :meth:`edmonds_karp`
247
+ :meth:`preflow_push`
248
+
249
+ Notes
250
+ -----
251
+ The residual network :samp:`R` from an input graph :samp:`G` has the
252
+ same nodes as :samp:`G`. :samp:`R` is a DiGraph that contains a pair
253
+ of edges :samp:`(u, v)` and :samp:`(v, u)` iff :samp:`(u, v)` is not a
254
+ self-loop, and at least one of :samp:`(u, v)` and :samp:`(v, u)` exists
255
+ in :samp:`G`.
256
+
257
+ For each edge :samp:`(u, v)` in :samp:`R`, :samp:`R[u][v]['capacity']`
258
+ is equal to the capacity of :samp:`(u, v)` in :samp:`G` if it exists
259
+ in :samp:`G` or zero otherwise. If the capacity is infinite,
260
+ :samp:`R[u][v]['capacity']` will have a high arbitrary finite value
261
+ that does not affect the solution of the problem. This value is stored in
262
+ :samp:`R.graph['inf']`. For each edge :samp:`(u, v)` in :samp:`R`,
263
+ :samp:`R[u][v]['flow']` represents the flow function of :samp:`(u, v)` and
264
+ satisfies :samp:`R[u][v]['flow'] == -R[v][u]['flow']`.
265
+
266
+ The flow value, defined as the total flow into :samp:`t`, the sink, is
267
+ stored in :samp:`R.graph['flow_value']`. If :samp:`cutoff` is not
268
+ specified, reachability to :samp:`t` using only edges :samp:`(u, v)` such
269
+ that :samp:`R[u][v]['flow'] < R[u][v]['capacity']` induces a minimum
270
+ :samp:`s`-:samp:`t` cut.
271
+
272
+ Examples
273
+ --------
274
+ >>> from networkx.algorithms.flow import shortest_augmenting_path
275
+
276
+ The functions that implement flow algorithms and output a residual
277
+ network, such as this one, are not imported to the base NetworkX
278
+ namespace, so you have to explicitly import them from the flow package.
279
+
280
+ >>> G = nx.DiGraph()
281
+ >>> G.add_edge("x", "a", capacity=3.0)
282
+ >>> G.add_edge("x", "b", capacity=1.0)
283
+ >>> G.add_edge("a", "c", capacity=3.0)
284
+ >>> G.add_edge("b", "c", capacity=5.0)
285
+ >>> G.add_edge("b", "d", capacity=4.0)
286
+ >>> G.add_edge("d", "e", capacity=2.0)
287
+ >>> G.add_edge("c", "y", capacity=2.0)
288
+ >>> G.add_edge("e", "y", capacity=3.0)
289
+ >>> R = shortest_augmenting_path(G, "x", "y")
290
+ >>> flow_value = nx.maximum_flow_value(G, "x", "y")
291
+ >>> flow_value
292
+ 3.0
293
+ >>> flow_value == R.graph["flow_value"]
294
+ True
295
+
296
+ """
297
+ R = shortest_augmenting_path_impl(G, s, t, capacity, residual, two_phase, cutoff)
298
+ R.graph["algorithm"] = "shortest_augmenting_path"
299
+ nx._clear_cache(R)
300
+ return R
.venv/lib/python3.11/site-packages/networkx/algorithms/flow/tests/__init__.py ADDED
File without changes
.venv/lib/python3.11/site-packages/networkx/algorithms/flow/tests/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (203 Bytes). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/flow/tests/__pycache__/test_gomory_hu.cpython-311.pyc ADDED
Binary file (9.75 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/flow/tests/__pycache__/test_maxflow.cpython-311.pyc ADDED
Binary file (30.4 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/flow/tests/__pycache__/test_maxflow_large_graph.cpython-311.pyc ADDED
Binary file (7.79 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/flow/tests/__pycache__/test_mincost.cpython-311.pyc ADDED
Binary file (30.4 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/flow/tests/__pycache__/test_networksimplex.cpython-311.pyc ADDED
Binary file (21.6 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/flow/tests/test_gomory_hu.py ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from itertools import combinations
2
+
3
+ import pytest
4
+
5
+ import networkx as nx
6
+ from networkx.algorithms.flow import (
7
+ boykov_kolmogorov,
8
+ dinitz,
9
+ edmonds_karp,
10
+ preflow_push,
11
+ shortest_augmenting_path,
12
+ )
13
+
14
+ flow_funcs = [
15
+ boykov_kolmogorov,
16
+ dinitz,
17
+ edmonds_karp,
18
+ preflow_push,
19
+ shortest_augmenting_path,
20
+ ]
21
+
22
+
23
+ class TestGomoryHuTree:
24
+ def minimum_edge_weight(self, T, u, v):
25
+ path = nx.shortest_path(T, u, v, weight="weight")
26
+ return min((T[u][v]["weight"], (u, v)) for (u, v) in zip(path, path[1:]))
27
+
28
+ def compute_cutset(self, G, T_orig, edge):
29
+ T = T_orig.copy()
30
+ T.remove_edge(*edge)
31
+ U, V = list(nx.connected_components(T))
32
+ cutset = set()
33
+ for x, nbrs in ((n, G[n]) for n in U):
34
+ cutset.update((x, y) for y in nbrs if y in V)
35
+ return cutset
36
+
37
+ def test_default_flow_function_karate_club_graph(self):
38
+ G = nx.karate_club_graph()
39
+ nx.set_edge_attributes(G, 1, "capacity")
40
+ T = nx.gomory_hu_tree(G)
41
+ assert nx.is_tree(T)
42
+ for u, v in combinations(G, 2):
43
+ cut_value, edge = self.minimum_edge_weight(T, u, v)
44
+ assert nx.minimum_cut_value(G, u, v) == cut_value
45
+
46
+ def test_karate_club_graph(self):
47
+ G = nx.karate_club_graph()
48
+ nx.set_edge_attributes(G, 1, "capacity")
49
+ for flow_func in flow_funcs:
50
+ T = nx.gomory_hu_tree(G, flow_func=flow_func)
51
+ assert nx.is_tree(T)
52
+ for u, v in combinations(G, 2):
53
+ cut_value, edge = self.minimum_edge_weight(T, u, v)
54
+ assert nx.minimum_cut_value(G, u, v) == cut_value
55
+
56
+ def test_davis_southern_women_graph(self):
57
+ G = nx.davis_southern_women_graph()
58
+ nx.set_edge_attributes(G, 1, "capacity")
59
+ for flow_func in flow_funcs:
60
+ T = nx.gomory_hu_tree(G, flow_func=flow_func)
61
+ assert nx.is_tree(T)
62
+ for u, v in combinations(G, 2):
63
+ cut_value, edge = self.minimum_edge_weight(T, u, v)
64
+ assert nx.minimum_cut_value(G, u, v) == cut_value
65
+
66
+ def test_florentine_families_graph(self):
67
+ G = nx.florentine_families_graph()
68
+ nx.set_edge_attributes(G, 1, "capacity")
69
+ for flow_func in flow_funcs:
70
+ T = nx.gomory_hu_tree(G, flow_func=flow_func)
71
+ assert nx.is_tree(T)
72
+ for u, v in combinations(G, 2):
73
+ cut_value, edge = self.minimum_edge_weight(T, u, v)
74
+ assert nx.minimum_cut_value(G, u, v) == cut_value
75
+
76
+ @pytest.mark.slow
77
+ def test_les_miserables_graph_cutset(self):
78
+ G = nx.les_miserables_graph()
79
+ nx.set_edge_attributes(G, 1, "capacity")
80
+ for flow_func in flow_funcs:
81
+ T = nx.gomory_hu_tree(G, flow_func=flow_func)
82
+ assert nx.is_tree(T)
83
+ for u, v in combinations(G, 2):
84
+ cut_value, edge = self.minimum_edge_weight(T, u, v)
85
+ assert nx.minimum_cut_value(G, u, v) == cut_value
86
+
87
+ def test_karate_club_graph_cutset(self):
88
+ G = nx.karate_club_graph()
89
+ nx.set_edge_attributes(G, 1, "capacity")
90
+ T = nx.gomory_hu_tree(G)
91
+ assert nx.is_tree(T)
92
+ u, v = 0, 33
93
+ cut_value, edge = self.minimum_edge_weight(T, u, v)
94
+ cutset = self.compute_cutset(G, T, edge)
95
+ assert cut_value == len(cutset)
96
+
97
+ def test_wikipedia_example(self):
98
+ # Example from https://en.wikipedia.org/wiki/Gomory%E2%80%93Hu_tree
99
+ G = nx.Graph()
100
+ G.add_weighted_edges_from(
101
+ (
102
+ (0, 1, 1),
103
+ (0, 2, 7),
104
+ (1, 2, 1),
105
+ (1, 3, 3),
106
+ (1, 4, 2),
107
+ (2, 4, 4),
108
+ (3, 4, 1),
109
+ (3, 5, 6),
110
+ (4, 5, 2),
111
+ )
112
+ )
113
+ for flow_func in flow_funcs:
114
+ T = nx.gomory_hu_tree(G, capacity="weight", flow_func=flow_func)
115
+ assert nx.is_tree(T)
116
+ for u, v in combinations(G, 2):
117
+ cut_value, edge = self.minimum_edge_weight(T, u, v)
118
+ assert nx.minimum_cut_value(G, u, v, capacity="weight") == cut_value
119
+
120
+ def test_directed_raises(self):
121
+ with pytest.raises(nx.NetworkXNotImplemented):
122
+ G = nx.DiGraph()
123
+ T = nx.gomory_hu_tree(G)
124
+
125
+ def test_empty_raises(self):
126
+ with pytest.raises(nx.NetworkXError):
127
+ G = nx.empty_graph()
128
+ T = nx.gomory_hu_tree(G)
.venv/lib/python3.11/site-packages/networkx/algorithms/flow/tests/test_maxflow.py ADDED
@@ -0,0 +1,573 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Maximum flow algorithms test suite."""
2
+
3
+ import pytest
4
+
5
+ import networkx as nx
6
+ from networkx.algorithms.flow import (
7
+ boykov_kolmogorov,
8
+ build_flow_dict,
9
+ build_residual_network,
10
+ dinitz,
11
+ edmonds_karp,
12
+ preflow_push,
13
+ shortest_augmenting_path,
14
+ )
15
+
16
+ flow_funcs = {
17
+ boykov_kolmogorov,
18
+ dinitz,
19
+ edmonds_karp,
20
+ preflow_push,
21
+ shortest_augmenting_path,
22
+ }
23
+
24
+ max_min_funcs = {nx.maximum_flow, nx.minimum_cut}
25
+ flow_value_funcs = {nx.maximum_flow_value, nx.minimum_cut_value}
26
+ interface_funcs = max_min_funcs | flow_value_funcs
27
+ all_funcs = flow_funcs | interface_funcs
28
+
29
+
30
+ def compute_cutset(G, partition):
31
+ reachable, non_reachable = partition
32
+ cutset = set()
33
+ for u, nbrs in ((n, G[n]) for n in reachable):
34
+ cutset.update((u, v) for v in nbrs if v in non_reachable)
35
+ return cutset
36
+
37
+
38
+ def validate_flows(G, s, t, flowDict, solnValue, capacity, flow_func):
39
+ errmsg = f"Assertion failed in function: {flow_func.__name__}"
40
+ assert set(G) == set(flowDict), errmsg
41
+ for u in G:
42
+ assert set(G[u]) == set(flowDict[u]), errmsg
43
+ excess = {u: 0 for u in flowDict}
44
+ for u in flowDict:
45
+ for v, flow in flowDict[u].items():
46
+ if capacity in G[u][v]:
47
+ assert flow <= G[u][v][capacity]
48
+ assert flow >= 0, errmsg
49
+ excess[u] -= flow
50
+ excess[v] += flow
51
+ for u, exc in excess.items():
52
+ if u == s:
53
+ assert exc == -solnValue, errmsg
54
+ elif u == t:
55
+ assert exc == solnValue, errmsg
56
+ else:
57
+ assert exc == 0, errmsg
58
+
59
+
60
+ def validate_cuts(G, s, t, solnValue, partition, capacity, flow_func):
61
+ errmsg = f"Assertion failed in function: {flow_func.__name__}"
62
+ assert all(n in G for n in partition[0]), errmsg
63
+ assert all(n in G for n in partition[1]), errmsg
64
+ cutset = compute_cutset(G, partition)
65
+ assert all(G.has_edge(u, v) for (u, v) in cutset), errmsg
66
+ assert solnValue == sum(G[u][v][capacity] for (u, v) in cutset), errmsg
67
+ H = G.copy()
68
+ H.remove_edges_from(cutset)
69
+ if not G.is_directed():
70
+ assert not nx.is_connected(H), errmsg
71
+ else:
72
+ assert not nx.is_strongly_connected(H), errmsg
73
+
74
+
75
+ def compare_flows_and_cuts(G, s, t, solnValue, capacity="capacity"):
76
+ for flow_func in flow_funcs:
77
+ errmsg = f"Assertion failed in function: {flow_func.__name__}"
78
+ R = flow_func(G, s, t, capacity)
79
+ # Test both legacy and new implementations.
80
+ flow_value = R.graph["flow_value"]
81
+ flow_dict = build_flow_dict(G, R)
82
+ assert flow_value == solnValue, errmsg
83
+ validate_flows(G, s, t, flow_dict, solnValue, capacity, flow_func)
84
+ # Minimum cut
85
+ cut_value, partition = nx.minimum_cut(
86
+ G, s, t, capacity=capacity, flow_func=flow_func
87
+ )
88
+ validate_cuts(G, s, t, solnValue, partition, capacity, flow_func)
89
+
90
+
91
+ class TestMaxflowMinCutCommon:
92
+ def test_graph1(self):
93
+ # Trivial undirected graph
94
+ G = nx.Graph()
95
+ G.add_edge(1, 2, capacity=1.0)
96
+
97
+ # solution flows
98
+ # {1: {2: 1.0}, 2: {1: 1.0}}
99
+
100
+ compare_flows_and_cuts(G, 1, 2, 1.0)
101
+
102
+ def test_graph2(self):
103
+ # A more complex undirected graph
104
+ # adapted from https://web.archive.org/web/20220815055650/https://www.topcoder.com/thrive/articles/Maximum%20Flow:%20Part%20One
105
+ G = nx.Graph()
106
+ G.add_edge("x", "a", capacity=3.0)
107
+ G.add_edge("x", "b", capacity=1.0)
108
+ G.add_edge("a", "c", capacity=3.0)
109
+ G.add_edge("b", "c", capacity=5.0)
110
+ G.add_edge("b", "d", capacity=4.0)
111
+ G.add_edge("d", "e", capacity=2.0)
112
+ G.add_edge("c", "y", capacity=2.0)
113
+ G.add_edge("e", "y", capacity=3.0)
114
+
115
+ # H
116
+ # {
117
+ # "x": {"a": 3, "b": 1},
118
+ # "a": {"c": 3, "x": 3},
119
+ # "b": {"c": 1, "d": 2, "x": 1},
120
+ # "c": {"a": 3, "b": 1, "y": 2},
121
+ # "d": {"b": 2, "e": 2},
122
+ # "e": {"d": 2, "y": 2},
123
+ # "y": {"c": 2, "e": 2},
124
+ # }
125
+
126
+ compare_flows_and_cuts(G, "x", "y", 4.0)
127
+
128
+ def test_digraph1(self):
129
+ # The classic directed graph example
130
+ G = nx.DiGraph()
131
+ G.add_edge("a", "b", capacity=1000.0)
132
+ G.add_edge("a", "c", capacity=1000.0)
133
+ G.add_edge("b", "c", capacity=1.0)
134
+ G.add_edge("b", "d", capacity=1000.0)
135
+ G.add_edge("c", "d", capacity=1000.0)
136
+
137
+ # H
138
+ # {
139
+ # "a": {"b": 1000.0, "c": 1000.0},
140
+ # "b": {"c": 0, "d": 1000.0},
141
+ # "c": {"d": 1000.0},
142
+ # "d": {},
143
+ # }
144
+
145
+ compare_flows_and_cuts(G, "a", "d", 2000.0)
146
+
147
+ def test_digraph2(self):
148
+ # An example in which some edges end up with zero flow.
149
+ G = nx.DiGraph()
150
+ G.add_edge("s", "b", capacity=2)
151
+ G.add_edge("s", "c", capacity=1)
152
+ G.add_edge("c", "d", capacity=1)
153
+ G.add_edge("d", "a", capacity=1)
154
+ G.add_edge("b", "a", capacity=2)
155
+ G.add_edge("a", "t", capacity=2)
156
+
157
+ # H
158
+ # {
159
+ # "s": {"b": 2, "c": 0},
160
+ # "c": {"d": 0},
161
+ # "d": {"a": 0},
162
+ # "b": {"a": 2},
163
+ # "a": {"t": 2},
164
+ # "t": {},
165
+ # }
166
+
167
+ compare_flows_and_cuts(G, "s", "t", 2)
168
+
169
+ def test_digraph3(self):
170
+ # A directed graph example from Cormen et al.
171
+ G = nx.DiGraph()
172
+ G.add_edge("s", "v1", capacity=16.0)
173
+ G.add_edge("s", "v2", capacity=13.0)
174
+ G.add_edge("v1", "v2", capacity=10.0)
175
+ G.add_edge("v2", "v1", capacity=4.0)
176
+ G.add_edge("v1", "v3", capacity=12.0)
177
+ G.add_edge("v3", "v2", capacity=9.0)
178
+ G.add_edge("v2", "v4", capacity=14.0)
179
+ G.add_edge("v4", "v3", capacity=7.0)
180
+ G.add_edge("v3", "t", capacity=20.0)
181
+ G.add_edge("v4", "t", capacity=4.0)
182
+
183
+ # H
184
+ # {
185
+ # "s": {"v1": 12.0, "v2": 11.0},
186
+ # "v2": {"v1": 0, "v4": 11.0},
187
+ # "v1": {"v2": 0, "v3": 12.0},
188
+ # "v3": {"v2": 0, "t": 19.0},
189
+ # "v4": {"v3": 7.0, "t": 4.0},
190
+ # "t": {},
191
+ # }
192
+
193
+ compare_flows_and_cuts(G, "s", "t", 23.0)
194
+
195
+ def test_digraph4(self):
196
+ # A more complex directed graph
197
+ # from https://web.archive.org/web/20220815055650/https://www.topcoder.com/thrive/articles/Maximum%20Flow:%20Part%20One
198
+ G = nx.DiGraph()
199
+ G.add_edge("x", "a", capacity=3.0)
200
+ G.add_edge("x", "b", capacity=1.0)
201
+ G.add_edge("a", "c", capacity=3.0)
202
+ G.add_edge("b", "c", capacity=5.0)
203
+ G.add_edge("b", "d", capacity=4.0)
204
+ G.add_edge("d", "e", capacity=2.0)
205
+ G.add_edge("c", "y", capacity=2.0)
206
+ G.add_edge("e", "y", capacity=3.0)
207
+
208
+ # H
209
+ # {
210
+ # "x": {"a": 2.0, "b": 1.0},
211
+ # "a": {"c": 2.0},
212
+ # "b": {"c": 0, "d": 1.0},
213
+ # "c": {"y": 2.0},
214
+ # "d": {"e": 1.0},
215
+ # "e": {"y": 1.0},
216
+ # "y": {},
217
+ # }
218
+
219
+ compare_flows_and_cuts(G, "x", "y", 3.0)
220
+
221
+ def test_wikipedia_dinitz_example(self):
222
+ # Nice example from https://en.wikipedia.org/wiki/Dinic's_algorithm
223
+ G = nx.DiGraph()
224
+ G.add_edge("s", 1, capacity=10)
225
+ G.add_edge("s", 2, capacity=10)
226
+ G.add_edge(1, 3, capacity=4)
227
+ G.add_edge(1, 4, capacity=8)
228
+ G.add_edge(1, 2, capacity=2)
229
+ G.add_edge(2, 4, capacity=9)
230
+ G.add_edge(3, "t", capacity=10)
231
+ G.add_edge(4, 3, capacity=6)
232
+ G.add_edge(4, "t", capacity=10)
233
+
234
+ # solution flows
235
+ # {
236
+ # 1: {2: 0, 3: 4, 4: 6},
237
+ # 2: {4: 9},
238
+ # 3: {"t": 9},
239
+ # 4: {3: 5, "t": 10},
240
+ # "s": {1: 10, 2: 9},
241
+ # "t": {},
242
+ # }
243
+
244
+ compare_flows_and_cuts(G, "s", "t", 19)
245
+
246
+ def test_optional_capacity(self):
247
+ # Test optional capacity parameter.
248
+ G = nx.DiGraph()
249
+ G.add_edge("x", "a", spam=3.0)
250
+ G.add_edge("x", "b", spam=1.0)
251
+ G.add_edge("a", "c", spam=3.0)
252
+ G.add_edge("b", "c", spam=5.0)
253
+ G.add_edge("b", "d", spam=4.0)
254
+ G.add_edge("d", "e", spam=2.0)
255
+ G.add_edge("c", "y", spam=2.0)
256
+ G.add_edge("e", "y", spam=3.0)
257
+
258
+ # solution flows
259
+ # {
260
+ # "x": {"a": 2.0, "b": 1.0},
261
+ # "a": {"c": 2.0},
262
+ # "b": {"c": 0, "d": 1.0},
263
+ # "c": {"y": 2.0},
264
+ # "d": {"e": 1.0},
265
+ # "e": {"y": 1.0},
266
+ # "y": {},
267
+ # }
268
+ solnValue = 3.0
269
+ s = "x"
270
+ t = "y"
271
+
272
+ compare_flows_and_cuts(G, s, t, solnValue, capacity="spam")
273
+
274
+ def test_digraph_infcap_edges(self):
275
+ # DiGraph with infinite capacity edges
276
+ G = nx.DiGraph()
277
+ G.add_edge("s", "a")
278
+ G.add_edge("s", "b", capacity=30)
279
+ G.add_edge("a", "c", capacity=25)
280
+ G.add_edge("b", "c", capacity=12)
281
+ G.add_edge("a", "t", capacity=60)
282
+ G.add_edge("c", "t")
283
+
284
+ # H
285
+ # {
286
+ # "s": {"a": 85, "b": 12},
287
+ # "a": {"c": 25, "t": 60},
288
+ # "b": {"c": 12},
289
+ # "c": {"t": 37},
290
+ # "t": {},
291
+ # }
292
+
293
+ compare_flows_and_cuts(G, "s", "t", 97)
294
+
295
+ # DiGraph with infinite capacity digon
296
+ G = nx.DiGraph()
297
+ G.add_edge("s", "a", capacity=85)
298
+ G.add_edge("s", "b", capacity=30)
299
+ G.add_edge("a", "c")
300
+ G.add_edge("c", "a")
301
+ G.add_edge("b", "c", capacity=12)
302
+ G.add_edge("a", "t", capacity=60)
303
+ G.add_edge("c", "t", capacity=37)
304
+
305
+ # H
306
+ # {
307
+ # "s": {"a": 85, "b": 12},
308
+ # "a": {"c": 25, "t": 60},
309
+ # "c": {"a": 0, "t": 37},
310
+ # "b": {"c": 12},
311
+ # "t": {},
312
+ # }
313
+
314
+ compare_flows_and_cuts(G, "s", "t", 97)
315
+
316
+ def test_digraph_infcap_path(self):
317
+ # Graph with infinite capacity (s, t)-path
318
+ G = nx.DiGraph()
319
+ G.add_edge("s", "a")
320
+ G.add_edge("s", "b", capacity=30)
321
+ G.add_edge("a", "c")
322
+ G.add_edge("b", "c", capacity=12)
323
+ G.add_edge("a", "t", capacity=60)
324
+ G.add_edge("c", "t")
325
+
326
+ for flow_func in all_funcs:
327
+ pytest.raises(nx.NetworkXUnbounded, flow_func, G, "s", "t")
328
+
329
+ def test_graph_infcap_edges(self):
330
+ # Undirected graph with infinite capacity edges
331
+ G = nx.Graph()
332
+ G.add_edge("s", "a")
333
+ G.add_edge("s", "b", capacity=30)
334
+ G.add_edge("a", "c", capacity=25)
335
+ G.add_edge("b", "c", capacity=12)
336
+ G.add_edge("a", "t", capacity=60)
337
+ G.add_edge("c", "t")
338
+
339
+ # H
340
+ # {
341
+ # "s": {"a": 85, "b": 12},
342
+ # "a": {"c": 25, "s": 85, "t": 60},
343
+ # "b": {"c": 12, "s": 12},
344
+ # "c": {"a": 25, "b": 12, "t": 37},
345
+ # "t": {"a": 60, "c": 37},
346
+ # }
347
+
348
+ compare_flows_and_cuts(G, "s", "t", 97)
349
+
350
+ def test_digraph5(self):
351
+ # From ticket #429 by mfrasca.
352
+ G = nx.DiGraph()
353
+ G.add_edge("s", "a", capacity=2)
354
+ G.add_edge("s", "b", capacity=2)
355
+ G.add_edge("a", "b", capacity=5)
356
+ G.add_edge("a", "t", capacity=1)
357
+ G.add_edge("b", "a", capacity=1)
358
+ G.add_edge("b", "t", capacity=3)
359
+ # flow solution
360
+ # {
361
+ # "a": {"b": 1, "t": 1},
362
+ # "b": {"a": 0, "t": 3},
363
+ # "s": {"a": 2, "b": 2},
364
+ # "t": {},
365
+ # }
366
+ compare_flows_and_cuts(G, "s", "t", 4)
367
+
368
+ def test_disconnected(self):
369
+ G = nx.Graph()
370
+ G.add_weighted_edges_from([(0, 1, 1), (1, 2, 1), (2, 3, 1)], weight="capacity")
371
+ G.remove_node(1)
372
+ assert nx.maximum_flow_value(G, 0, 3) == 0
373
+ # flow solution
374
+ # {0: {}, 2: {3: 0}, 3: {2: 0}}
375
+ compare_flows_and_cuts(G, 0, 3, 0)
376
+
377
+ def test_source_target_not_in_graph(self):
378
+ G = nx.Graph()
379
+ G.add_weighted_edges_from([(0, 1, 1), (1, 2, 1), (2, 3, 1)], weight="capacity")
380
+ G.remove_node(0)
381
+ for flow_func in all_funcs:
382
+ pytest.raises(nx.NetworkXError, flow_func, G, 0, 3)
383
+ G.add_weighted_edges_from([(0, 1, 1), (1, 2, 1), (2, 3, 1)], weight="capacity")
384
+ G.remove_node(3)
385
+ for flow_func in all_funcs:
386
+ pytest.raises(nx.NetworkXError, flow_func, G, 0, 3)
387
+
388
+ def test_source_target_coincide(self):
389
+ G = nx.Graph()
390
+ G.add_node(0)
391
+ for flow_func in all_funcs:
392
+ pytest.raises(nx.NetworkXError, flow_func, G, 0, 0)
393
+
394
+ def test_multigraphs_raise(self):
395
+ G = nx.MultiGraph()
396
+ M = nx.MultiDiGraph()
397
+ G.add_edges_from([(0, 1), (1, 0)], capacity=True)
398
+ for flow_func in all_funcs:
399
+ pytest.raises(nx.NetworkXError, flow_func, G, 0, 0)
400
+
401
+
402
+ class TestMaxFlowMinCutInterface:
403
+ def setup_method(self):
404
+ G = nx.DiGraph()
405
+ G.add_edge("x", "a", capacity=3.0)
406
+ G.add_edge("x", "b", capacity=1.0)
407
+ G.add_edge("a", "c", capacity=3.0)
408
+ G.add_edge("b", "c", capacity=5.0)
409
+ G.add_edge("b", "d", capacity=4.0)
410
+ G.add_edge("d", "e", capacity=2.0)
411
+ G.add_edge("c", "y", capacity=2.0)
412
+ G.add_edge("e", "y", capacity=3.0)
413
+ self.G = G
414
+ H = nx.DiGraph()
415
+ H.add_edge(0, 1, capacity=1.0)
416
+ H.add_edge(1, 2, capacity=1.0)
417
+ self.H = H
418
+
419
+ def test_flow_func_not_callable(self):
420
+ elements = ["this_should_be_callable", 10, {1, 2, 3}]
421
+ G = nx.Graph()
422
+ G.add_weighted_edges_from([(0, 1, 1), (1, 2, 1), (2, 3, 1)], weight="capacity")
423
+ for flow_func in interface_funcs:
424
+ for element in elements:
425
+ pytest.raises(nx.NetworkXError, flow_func, G, 0, 1, flow_func=element)
426
+ pytest.raises(nx.NetworkXError, flow_func, G, 0, 1, flow_func=element)
427
+
428
+ def test_flow_func_parameters(self):
429
+ G = self.G
430
+ fv = 3.0
431
+ for interface_func in interface_funcs:
432
+ for flow_func in flow_funcs:
433
+ errmsg = (
434
+ f"Assertion failed in function: {flow_func.__name__} "
435
+ f"in interface {interface_func.__name__}"
436
+ )
437
+ result = interface_func(G, "x", "y", flow_func=flow_func)
438
+ if interface_func in max_min_funcs:
439
+ result = result[0]
440
+ assert fv == result, errmsg
441
+
442
+ def test_minimum_cut_no_cutoff(self):
443
+ G = self.G
444
+ pytest.raises(
445
+ nx.NetworkXError,
446
+ nx.minimum_cut,
447
+ G,
448
+ "x",
449
+ "y",
450
+ flow_func=preflow_push,
451
+ cutoff=1.0,
452
+ )
453
+ pytest.raises(
454
+ nx.NetworkXError,
455
+ nx.minimum_cut_value,
456
+ G,
457
+ "x",
458
+ "y",
459
+ flow_func=preflow_push,
460
+ cutoff=1.0,
461
+ )
462
+
463
+ def test_kwargs(self):
464
+ G = self.H
465
+ fv = 1.0
466
+ to_test = (
467
+ (shortest_augmenting_path, {"two_phase": True}),
468
+ (preflow_push, {"global_relabel_freq": 5}),
469
+ )
470
+ for interface_func in interface_funcs:
471
+ for flow_func, kwargs in to_test:
472
+ errmsg = (
473
+ f"Assertion failed in function: {flow_func.__name__} "
474
+ f"in interface {interface_func.__name__}"
475
+ )
476
+ result = interface_func(G, 0, 2, flow_func=flow_func, **kwargs)
477
+ if interface_func in max_min_funcs:
478
+ result = result[0]
479
+ assert fv == result, errmsg
480
+
481
+ def test_kwargs_default_flow_func(self):
482
+ G = self.H
483
+ for interface_func in interface_funcs:
484
+ pytest.raises(
485
+ nx.NetworkXError, interface_func, G, 0, 1, global_relabel_freq=2
486
+ )
487
+
488
+ def test_reusing_residual(self):
489
+ G = self.G
490
+ fv = 3.0
491
+ s, t = "x", "y"
492
+ R = build_residual_network(G, "capacity")
493
+ for interface_func in interface_funcs:
494
+ for flow_func in flow_funcs:
495
+ errmsg = (
496
+ f"Assertion failed in function: {flow_func.__name__} "
497
+ f"in interface {interface_func.__name__}"
498
+ )
499
+ for i in range(3):
500
+ result = interface_func(
501
+ G, "x", "y", flow_func=flow_func, residual=R
502
+ )
503
+ if interface_func in max_min_funcs:
504
+ result = result[0]
505
+ assert fv == result, errmsg
506
+
507
+
508
+ # Tests specific to one algorithm
509
+ def test_preflow_push_global_relabel_freq():
510
+ G = nx.DiGraph()
511
+ G.add_edge(1, 2, capacity=1)
512
+ R = preflow_push(G, 1, 2, global_relabel_freq=None)
513
+ assert R.graph["flow_value"] == 1
514
+ pytest.raises(nx.NetworkXError, preflow_push, G, 1, 2, global_relabel_freq=-1)
515
+
516
+
517
+ def test_preflow_push_makes_enough_space():
518
+ # From ticket #1542
519
+ G = nx.DiGraph()
520
+ nx.add_path(G, [0, 1, 3], capacity=1)
521
+ nx.add_path(G, [1, 2, 3], capacity=1)
522
+ R = preflow_push(G, 0, 3, value_only=False)
523
+ assert R.graph["flow_value"] == 1
524
+
525
+
526
+ def test_shortest_augmenting_path_two_phase():
527
+ k = 5
528
+ p = 1000
529
+ G = nx.DiGraph()
530
+ for i in range(k):
531
+ G.add_edge("s", (i, 0), capacity=1)
532
+ nx.add_path(G, ((i, j) for j in range(p)), capacity=1)
533
+ G.add_edge((i, p - 1), "t", capacity=1)
534
+ R = shortest_augmenting_path(G, "s", "t", two_phase=True)
535
+ assert R.graph["flow_value"] == k
536
+ R = shortest_augmenting_path(G, "s", "t", two_phase=False)
537
+ assert R.graph["flow_value"] == k
538
+
539
+
540
+ class TestCutoff:
541
+ def test_cutoff(self):
542
+ k = 5
543
+ p = 1000
544
+ G = nx.DiGraph()
545
+ for i in range(k):
546
+ G.add_edge("s", (i, 0), capacity=2)
547
+ nx.add_path(G, ((i, j) for j in range(p)), capacity=2)
548
+ G.add_edge((i, p - 1), "t", capacity=2)
549
+ R = shortest_augmenting_path(G, "s", "t", two_phase=True, cutoff=k)
550
+ assert k <= R.graph["flow_value"] <= (2 * k)
551
+ R = shortest_augmenting_path(G, "s", "t", two_phase=False, cutoff=k)
552
+ assert k <= R.graph["flow_value"] <= (2 * k)
553
+ R = edmonds_karp(G, "s", "t", cutoff=k)
554
+ assert k <= R.graph["flow_value"] <= (2 * k)
555
+ R = dinitz(G, "s", "t", cutoff=k)
556
+ assert k <= R.graph["flow_value"] <= (2 * k)
557
+ R = boykov_kolmogorov(G, "s", "t", cutoff=k)
558
+ assert k <= R.graph["flow_value"] <= (2 * k)
559
+
560
+ def test_complete_graph_cutoff(self):
561
+ G = nx.complete_graph(5)
562
+ nx.set_edge_attributes(G, {(u, v): 1 for u, v in G.edges()}, "capacity")
563
+ for flow_func in [
564
+ shortest_augmenting_path,
565
+ edmonds_karp,
566
+ dinitz,
567
+ boykov_kolmogorov,
568
+ ]:
569
+ for cutoff in [3, 2, 1]:
570
+ result = nx.maximum_flow_value(
571
+ G, 0, 4, flow_func=flow_func, cutoff=cutoff
572
+ )
573
+ assert cutoff == result, f"cutoff error in {flow_func.__name__}"
.venv/lib/python3.11/site-packages/networkx/algorithms/flow/tests/test_maxflow_large_graph.py ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Maximum flow algorithms test suite on large graphs."""
2
+
3
+ import bz2
4
+ import importlib.resources
5
+ import os
6
+ import pickle
7
+
8
+ import pytest
9
+
10
+ import networkx as nx
11
+ from networkx.algorithms.flow import (
12
+ boykov_kolmogorov,
13
+ build_flow_dict,
14
+ build_residual_network,
15
+ dinitz,
16
+ edmonds_karp,
17
+ preflow_push,
18
+ shortest_augmenting_path,
19
+ )
20
+
21
+ flow_funcs = [
22
+ boykov_kolmogorov,
23
+ dinitz,
24
+ edmonds_karp,
25
+ preflow_push,
26
+ shortest_augmenting_path,
27
+ ]
28
+
29
+
30
+ def gen_pyramid(N):
31
+ # This graph admits a flow of value 1 for which every arc is at
32
+ # capacity (except the arcs incident to the sink which have
33
+ # infinite capacity).
34
+ G = nx.DiGraph()
35
+
36
+ for i in range(N - 1):
37
+ cap = 1.0 / (i + 2)
38
+ for j in range(i + 1):
39
+ G.add_edge((i, j), (i + 1, j), capacity=cap)
40
+ cap = 1.0 / (i + 1) - cap
41
+ G.add_edge((i, j), (i + 1, j + 1), capacity=cap)
42
+ cap = 1.0 / (i + 2) - cap
43
+
44
+ for j in range(N):
45
+ G.add_edge((N - 1, j), "t")
46
+
47
+ return G
48
+
49
+
50
+ def read_graph(name):
51
+ fname = (
52
+ importlib.resources.files("networkx.algorithms.flow.tests")
53
+ / f"{name}.gpickle.bz2"
54
+ )
55
+
56
+ with bz2.BZ2File(fname, "rb") as f:
57
+ G = pickle.load(f)
58
+ return G
59
+
60
+
61
+ def validate_flows(G, s, t, soln_value, R, flow_func):
62
+ flow_value = R.graph["flow_value"]
63
+ flow_dict = build_flow_dict(G, R)
64
+ errmsg = f"Assertion failed in function: {flow_func.__name__}"
65
+ assert soln_value == flow_value, errmsg
66
+ assert set(G) == set(flow_dict), errmsg
67
+ for u in G:
68
+ assert set(G[u]) == set(flow_dict[u]), errmsg
69
+ excess = {u: 0 for u in flow_dict}
70
+ for u in flow_dict:
71
+ for v, flow in flow_dict[u].items():
72
+ assert flow <= G[u][v].get("capacity", float("inf")), errmsg
73
+ assert flow >= 0, errmsg
74
+ excess[u] -= flow
75
+ excess[v] += flow
76
+ for u, exc in excess.items():
77
+ if u == s:
78
+ assert exc == -soln_value, errmsg
79
+ elif u == t:
80
+ assert exc == soln_value, errmsg
81
+ else:
82
+ assert exc == 0, errmsg
83
+
84
+
85
+ class TestMaxflowLargeGraph:
86
+ def test_complete_graph(self):
87
+ N = 50
88
+ G = nx.complete_graph(N)
89
+ nx.set_edge_attributes(G, 5, "capacity")
90
+ R = build_residual_network(G, "capacity")
91
+ kwargs = {"residual": R}
92
+
93
+ for flow_func in flow_funcs:
94
+ kwargs["flow_func"] = flow_func
95
+ errmsg = f"Assertion failed in function: {flow_func.__name__}"
96
+ flow_value = nx.maximum_flow_value(G, 1, 2, **kwargs)
97
+ assert flow_value == 5 * (N - 1), errmsg
98
+
99
+ def test_pyramid(self):
100
+ N = 10
101
+ # N = 100 # this gives a graph with 5051 nodes
102
+ G = gen_pyramid(N)
103
+ R = build_residual_network(G, "capacity")
104
+ kwargs = {"residual": R}
105
+
106
+ for flow_func in flow_funcs:
107
+ kwargs["flow_func"] = flow_func
108
+ errmsg = f"Assertion failed in function: {flow_func.__name__}"
109
+ flow_value = nx.maximum_flow_value(G, (0, 0), "t", **kwargs)
110
+ assert flow_value == pytest.approx(1.0, abs=1e-7)
111
+
112
+ def test_gl1(self):
113
+ G = read_graph("gl1")
114
+ s = 1
115
+ t = len(G)
116
+ R = build_residual_network(G, "capacity")
117
+ kwargs = {"residual": R}
118
+
119
+ # do one flow_func to save time
120
+ flow_func = flow_funcs[0]
121
+ validate_flows(G, s, t, 156545, flow_func(G, s, t, **kwargs), flow_func)
122
+
123
+ # for flow_func in flow_funcs:
124
+ # validate_flows(G, s, t, 156545, flow_func(G, s, t, **kwargs),
125
+ # flow_func)
126
+
127
+ @pytest.mark.slow
128
+ def test_gw1(self):
129
+ G = read_graph("gw1")
130
+ s = 1
131
+ t = len(G)
132
+ R = build_residual_network(G, "capacity")
133
+ kwargs = {"residual": R}
134
+
135
+ for flow_func in flow_funcs:
136
+ validate_flows(G, s, t, 1202018, flow_func(G, s, t, **kwargs), flow_func)
137
+
138
+ def test_wlm3(self):
139
+ G = read_graph("wlm3")
140
+ s = 1
141
+ t = len(G)
142
+ R = build_residual_network(G, "capacity")
143
+ kwargs = {"residual": R}
144
+
145
+ # do one flow_func to save time
146
+ flow_func = flow_funcs[0]
147
+ validate_flows(G, s, t, 11875108, flow_func(G, s, t, **kwargs), flow_func)
148
+
149
+ # for flow_func in flow_funcs:
150
+ # validate_flows(G, s, t, 11875108, flow_func(G, s, t, **kwargs),
151
+ # flow_func)
152
+
153
+ def test_preflow_push_global_relabel(self):
154
+ G = read_graph("gw1")
155
+ R = preflow_push(G, 1, len(G), global_relabel_freq=50)
156
+ assert R.graph["flow_value"] == 1202018
.venv/lib/python3.11/site-packages/networkx/algorithms/flow/tests/test_mincost.py ADDED
@@ -0,0 +1,476 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import bz2
2
+ import importlib.resources
3
+ import os
4
+ import pickle
5
+
6
+ import pytest
7
+
8
+ import networkx as nx
9
+
10
+
11
+ class TestMinCostFlow:
12
+ def test_simple_digraph(self):
13
+ G = nx.DiGraph()
14
+ G.add_node("a", demand=-5)
15
+ G.add_node("d", demand=5)
16
+ G.add_edge("a", "b", weight=3, capacity=4)
17
+ G.add_edge("a", "c", weight=6, capacity=10)
18
+ G.add_edge("b", "d", weight=1, capacity=9)
19
+ G.add_edge("c", "d", weight=2, capacity=5)
20
+ flowCost, H = nx.network_simplex(G)
21
+ soln = {"a": {"b": 4, "c": 1}, "b": {"d": 4}, "c": {"d": 1}, "d": {}}
22
+ assert flowCost == 24
23
+ assert nx.min_cost_flow_cost(G) == 24
24
+ assert H == soln
25
+ assert nx.min_cost_flow(G) == soln
26
+ assert nx.cost_of_flow(G, H) == 24
27
+
28
+ flowCost, H = nx.capacity_scaling(G)
29
+ assert flowCost == 24
30
+ assert nx.cost_of_flow(G, H) == 24
31
+ assert H == soln
32
+
33
+ def test_negcycle_infcap(self):
34
+ G = nx.DiGraph()
35
+ G.add_node("s", demand=-5)
36
+ G.add_node("t", demand=5)
37
+ G.add_edge("s", "a", weight=1, capacity=3)
38
+ G.add_edge("a", "b", weight=3)
39
+ G.add_edge("c", "a", weight=-6)
40
+ G.add_edge("b", "d", weight=1)
41
+ G.add_edge("d", "c", weight=-2)
42
+ G.add_edge("d", "t", weight=1, capacity=3)
43
+ pytest.raises(nx.NetworkXUnfeasible, nx.network_simplex, G)
44
+ pytest.raises(nx.NetworkXUnbounded, nx.capacity_scaling, G)
45
+
46
+ def test_sum_demands_not_zero(self):
47
+ G = nx.DiGraph()
48
+ G.add_node("s", demand=-5)
49
+ G.add_node("t", demand=4)
50
+ G.add_edge("s", "a", weight=1, capacity=3)
51
+ G.add_edge("a", "b", weight=3)
52
+ G.add_edge("a", "c", weight=-6)
53
+ G.add_edge("b", "d", weight=1)
54
+ G.add_edge("c", "d", weight=-2)
55
+ G.add_edge("d", "t", weight=1, capacity=3)
56
+ pytest.raises(nx.NetworkXUnfeasible, nx.network_simplex, G)
57
+ pytest.raises(nx.NetworkXUnfeasible, nx.capacity_scaling, G)
58
+
59
+ def test_no_flow_satisfying_demands(self):
60
+ G = nx.DiGraph()
61
+ G.add_node("s", demand=-5)
62
+ G.add_node("t", demand=5)
63
+ G.add_edge("s", "a", weight=1, capacity=3)
64
+ G.add_edge("a", "b", weight=3)
65
+ G.add_edge("a", "c", weight=-6)
66
+ G.add_edge("b", "d", weight=1)
67
+ G.add_edge("c", "d", weight=-2)
68
+ G.add_edge("d", "t", weight=1, capacity=3)
69
+ pytest.raises(nx.NetworkXUnfeasible, nx.network_simplex, G)
70
+ pytest.raises(nx.NetworkXUnfeasible, nx.capacity_scaling, G)
71
+
72
+ def test_transshipment(self):
73
+ G = nx.DiGraph()
74
+ G.add_node("a", demand=1)
75
+ G.add_node("b", demand=-2)
76
+ G.add_node("c", demand=-2)
77
+ G.add_node("d", demand=3)
78
+ G.add_node("e", demand=-4)
79
+ G.add_node("f", demand=-4)
80
+ G.add_node("g", demand=3)
81
+ G.add_node("h", demand=2)
82
+ G.add_node("r", demand=3)
83
+ G.add_edge("a", "c", weight=3)
84
+ G.add_edge("r", "a", weight=2)
85
+ G.add_edge("b", "a", weight=9)
86
+ G.add_edge("r", "c", weight=0)
87
+ G.add_edge("b", "r", weight=-6)
88
+ G.add_edge("c", "d", weight=5)
89
+ G.add_edge("e", "r", weight=4)
90
+ G.add_edge("e", "f", weight=3)
91
+ G.add_edge("h", "b", weight=4)
92
+ G.add_edge("f", "d", weight=7)
93
+ G.add_edge("f", "h", weight=12)
94
+ G.add_edge("g", "d", weight=12)
95
+ G.add_edge("f", "g", weight=-1)
96
+ G.add_edge("h", "g", weight=-10)
97
+ flowCost, H = nx.network_simplex(G)
98
+ soln = {
99
+ "a": {"c": 0},
100
+ "b": {"a": 0, "r": 2},
101
+ "c": {"d": 3},
102
+ "d": {},
103
+ "e": {"r": 3, "f": 1},
104
+ "f": {"d": 0, "g": 3, "h": 2},
105
+ "g": {"d": 0},
106
+ "h": {"b": 0, "g": 0},
107
+ "r": {"a": 1, "c": 1},
108
+ }
109
+ assert flowCost == 41
110
+ assert nx.min_cost_flow_cost(G) == 41
111
+ assert H == soln
112
+ assert nx.min_cost_flow(G) == soln
113
+ assert nx.cost_of_flow(G, H) == 41
114
+
115
+ flowCost, H = nx.capacity_scaling(G)
116
+ assert flowCost == 41
117
+ assert nx.cost_of_flow(G, H) == 41
118
+ assert H == soln
119
+
120
+ def test_max_flow_min_cost(self):
121
+ G = nx.DiGraph()
122
+ G.add_edge("s", "a", bandwidth=6)
123
+ G.add_edge("s", "c", bandwidth=10, cost=10)
124
+ G.add_edge("a", "b", cost=6)
125
+ G.add_edge("b", "d", bandwidth=8, cost=7)
126
+ G.add_edge("c", "d", cost=10)
127
+ G.add_edge("d", "t", bandwidth=5, cost=5)
128
+ soln = {
129
+ "s": {"a": 5, "c": 0},
130
+ "a": {"b": 5},
131
+ "b": {"d": 5},
132
+ "c": {"d": 0},
133
+ "d": {"t": 5},
134
+ "t": {},
135
+ }
136
+ flow = nx.max_flow_min_cost(G, "s", "t", capacity="bandwidth", weight="cost")
137
+ assert flow == soln
138
+ assert nx.cost_of_flow(G, flow, weight="cost") == 90
139
+
140
+ G.add_edge("t", "s", cost=-100)
141
+ flowCost, flow = nx.capacity_scaling(G, capacity="bandwidth", weight="cost")
142
+ G.remove_edge("t", "s")
143
+ assert flowCost == -410
144
+ assert flow["t"]["s"] == 5
145
+ del flow["t"]["s"]
146
+ assert flow == soln
147
+ assert nx.cost_of_flow(G, flow, weight="cost") == 90
148
+
149
+ def test_digraph1(self):
150
+ # From Bradley, S. P., Hax, A. C. and Magnanti, T. L. Applied
151
+ # Mathematical Programming. Addison-Wesley, 1977.
152
+ G = nx.DiGraph()
153
+ G.add_node(1, demand=-20)
154
+ G.add_node(4, demand=5)
155
+ G.add_node(5, demand=15)
156
+ G.add_edges_from(
157
+ [
158
+ (1, 2, {"capacity": 15, "weight": 4}),
159
+ (1, 3, {"capacity": 8, "weight": 4}),
160
+ (2, 3, {"weight": 2}),
161
+ (2, 4, {"capacity": 4, "weight": 2}),
162
+ (2, 5, {"capacity": 10, "weight": 6}),
163
+ (3, 4, {"capacity": 15, "weight": 1}),
164
+ (3, 5, {"capacity": 5, "weight": 3}),
165
+ (4, 5, {"weight": 2}),
166
+ (5, 3, {"capacity": 4, "weight": 1}),
167
+ ]
168
+ )
169
+ flowCost, H = nx.network_simplex(G)
170
+ soln = {
171
+ 1: {2: 12, 3: 8},
172
+ 2: {3: 8, 4: 4, 5: 0},
173
+ 3: {4: 11, 5: 5},
174
+ 4: {5: 10},
175
+ 5: {3: 0},
176
+ }
177
+ assert flowCost == 150
178
+ assert nx.min_cost_flow_cost(G) == 150
179
+ assert H == soln
180
+ assert nx.min_cost_flow(G) == soln
181
+ assert nx.cost_of_flow(G, H) == 150
182
+
183
+ flowCost, H = nx.capacity_scaling(G)
184
+ assert flowCost == 150
185
+ assert H == soln
186
+ assert nx.cost_of_flow(G, H) == 150
187
+
188
+ def test_digraph2(self):
189
+ # Example from ticket #430 from mfrasca. Original source:
190
+ # http://www.cs.princeton.edu/courses/archive/spr03/cs226/lectures/mincost.4up.pdf, slide 11.
191
+ G = nx.DiGraph()
192
+ G.add_edge("s", 1, capacity=12)
193
+ G.add_edge("s", 2, capacity=6)
194
+ G.add_edge("s", 3, capacity=14)
195
+ G.add_edge(1, 2, capacity=11, weight=4)
196
+ G.add_edge(2, 3, capacity=9, weight=6)
197
+ G.add_edge(1, 4, capacity=5, weight=5)
198
+ G.add_edge(1, 5, capacity=2, weight=12)
199
+ G.add_edge(2, 5, capacity=4, weight=4)
200
+ G.add_edge(2, 6, capacity=2, weight=6)
201
+ G.add_edge(3, 6, capacity=31, weight=3)
202
+ G.add_edge(4, 5, capacity=18, weight=4)
203
+ G.add_edge(5, 6, capacity=9, weight=5)
204
+ G.add_edge(4, "t", capacity=3)
205
+ G.add_edge(5, "t", capacity=7)
206
+ G.add_edge(6, "t", capacity=22)
207
+ flow = nx.max_flow_min_cost(G, "s", "t")
208
+ soln = {
209
+ 1: {2: 6, 4: 5, 5: 1},
210
+ 2: {3: 6, 5: 4, 6: 2},
211
+ 3: {6: 20},
212
+ 4: {5: 2, "t": 3},
213
+ 5: {6: 0, "t": 7},
214
+ 6: {"t": 22},
215
+ "s": {1: 12, 2: 6, 3: 14},
216
+ "t": {},
217
+ }
218
+ assert flow == soln
219
+
220
+ G.add_edge("t", "s", weight=-100)
221
+ flowCost, flow = nx.capacity_scaling(G)
222
+ G.remove_edge("t", "s")
223
+ assert flow["t"]["s"] == 32
224
+ assert flowCost == -3007
225
+ del flow["t"]["s"]
226
+ assert flow == soln
227
+ assert nx.cost_of_flow(G, flow) == 193
228
+
229
+ def test_digraph3(self):
230
+ """Combinatorial Optimization: Algorithms and Complexity,
231
+ Papadimitriou Steiglitz at page 140 has an example, 7.1, but that
232
+ admits multiple solutions, so I alter it a bit. From ticket #430
233
+ by mfrasca."""
234
+
235
+ G = nx.DiGraph()
236
+ G.add_edge("s", "a")
237
+ G["s"]["a"].update({0: 2, 1: 4})
238
+ G.add_edge("s", "b")
239
+ G["s"]["b"].update({0: 2, 1: 1})
240
+ G.add_edge("a", "b")
241
+ G["a"]["b"].update({0: 5, 1: 2})
242
+ G.add_edge("a", "t")
243
+ G["a"]["t"].update({0: 1, 1: 5})
244
+ G.add_edge("b", "a")
245
+ G["b"]["a"].update({0: 1, 1: 3})
246
+ G.add_edge("b", "t")
247
+ G["b"]["t"].update({0: 3, 1: 2})
248
+
249
+ "PS.ex.7.1: testing main function"
250
+ sol = nx.max_flow_min_cost(G, "s", "t", capacity=0, weight=1)
251
+ flow = sum(v for v in sol["s"].values())
252
+ assert 4 == flow
253
+ assert 23 == nx.cost_of_flow(G, sol, weight=1)
254
+ assert sol["s"] == {"a": 2, "b": 2}
255
+ assert sol["a"] == {"b": 1, "t": 1}
256
+ assert sol["b"] == {"a": 0, "t": 3}
257
+ assert sol["t"] == {}
258
+
259
+ G.add_edge("t", "s")
260
+ G["t"]["s"].update({1: -100})
261
+ flowCost, sol = nx.capacity_scaling(G, capacity=0, weight=1)
262
+ G.remove_edge("t", "s")
263
+ flow = sum(v for v in sol["s"].values())
264
+ assert 4 == flow
265
+ assert sol["t"]["s"] == 4
266
+ assert flowCost == -377
267
+ del sol["t"]["s"]
268
+ assert sol["s"] == {"a": 2, "b": 2}
269
+ assert sol["a"] == {"b": 1, "t": 1}
270
+ assert sol["b"] == {"a": 0, "t": 3}
271
+ assert sol["t"] == {}
272
+ assert nx.cost_of_flow(G, sol, weight=1) == 23
273
+
274
+ def test_zero_capacity_edges(self):
275
+ """Address issue raised in ticket #617 by arv."""
276
+ G = nx.DiGraph()
277
+ G.add_edges_from(
278
+ [
279
+ (1, 2, {"capacity": 1, "weight": 1}),
280
+ (1, 5, {"capacity": 1, "weight": 1}),
281
+ (2, 3, {"capacity": 0, "weight": 1}),
282
+ (2, 5, {"capacity": 1, "weight": 1}),
283
+ (5, 3, {"capacity": 2, "weight": 1}),
284
+ (5, 4, {"capacity": 0, "weight": 1}),
285
+ (3, 4, {"capacity": 2, "weight": 1}),
286
+ ]
287
+ )
288
+ G.nodes[1]["demand"] = -1
289
+ G.nodes[2]["demand"] = -1
290
+ G.nodes[4]["demand"] = 2
291
+
292
+ flowCost, H = nx.network_simplex(G)
293
+ soln = {1: {2: 0, 5: 1}, 2: {3: 0, 5: 1}, 3: {4: 2}, 4: {}, 5: {3: 2, 4: 0}}
294
+ assert flowCost == 6
295
+ assert nx.min_cost_flow_cost(G) == 6
296
+ assert H == soln
297
+ assert nx.min_cost_flow(G) == soln
298
+ assert nx.cost_of_flow(G, H) == 6
299
+
300
+ flowCost, H = nx.capacity_scaling(G)
301
+ assert flowCost == 6
302
+ assert H == soln
303
+ assert nx.cost_of_flow(G, H) == 6
304
+
305
+ def test_digon(self):
306
+ """Check if digons are handled properly. Taken from ticket
307
+ #618 by arv."""
308
+ nodes = [(1, {}), (2, {"demand": -4}), (3, {"demand": 4})]
309
+ edges = [
310
+ (1, 2, {"capacity": 3, "weight": 600000}),
311
+ (2, 1, {"capacity": 2, "weight": 0}),
312
+ (2, 3, {"capacity": 5, "weight": 714285}),
313
+ (3, 2, {"capacity": 2, "weight": 0}),
314
+ ]
315
+ G = nx.DiGraph(edges)
316
+ G.add_nodes_from(nodes)
317
+ flowCost, H = nx.network_simplex(G)
318
+ soln = {1: {2: 0}, 2: {1: 0, 3: 4}, 3: {2: 0}}
319
+ assert flowCost == 2857140
320
+ assert nx.min_cost_flow_cost(G) == 2857140
321
+ assert H == soln
322
+ assert nx.min_cost_flow(G) == soln
323
+ assert nx.cost_of_flow(G, H) == 2857140
324
+
325
+ flowCost, H = nx.capacity_scaling(G)
326
+ assert flowCost == 2857140
327
+ assert H == soln
328
+ assert nx.cost_of_flow(G, H) == 2857140
329
+
330
+ def test_deadend(self):
331
+ """Check if one-node cycles are handled properly. Taken from ticket
332
+ #2906 from @sshraven."""
333
+ G = nx.DiGraph()
334
+
335
+ G.add_nodes_from(range(5), demand=0)
336
+ G.nodes[4]["demand"] = -13
337
+ G.nodes[3]["demand"] = 13
338
+
339
+ G.add_edges_from([(0, 2), (0, 3), (2, 1)], capacity=20, weight=0.1)
340
+ pytest.raises(nx.NetworkXUnfeasible, nx.min_cost_flow, G)
341
+
342
+ def test_infinite_capacity_neg_digon(self):
343
+ """An infinite capacity negative cost digon results in an unbounded
344
+ instance."""
345
+ nodes = [(1, {}), (2, {"demand": -4}), (3, {"demand": 4})]
346
+ edges = [
347
+ (1, 2, {"weight": -600}),
348
+ (2, 1, {"weight": 0}),
349
+ (2, 3, {"capacity": 5, "weight": 714285}),
350
+ (3, 2, {"capacity": 2, "weight": 0}),
351
+ ]
352
+ G = nx.DiGraph(edges)
353
+ G.add_nodes_from(nodes)
354
+ pytest.raises(nx.NetworkXUnbounded, nx.network_simplex, G)
355
+ pytest.raises(nx.NetworkXUnbounded, nx.capacity_scaling, G)
356
+
357
+ def test_finite_capacity_neg_digon(self):
358
+ """The digon should receive the maximum amount of flow it can handle.
359
+ Taken from ticket #749 by @chuongdo."""
360
+ G = nx.DiGraph()
361
+ G.add_edge("a", "b", capacity=1, weight=-1)
362
+ G.add_edge("b", "a", capacity=1, weight=-1)
363
+ min_cost = -2
364
+ assert nx.min_cost_flow_cost(G) == min_cost
365
+
366
+ flowCost, H = nx.capacity_scaling(G)
367
+ assert flowCost == -2
368
+ assert H == {"a": {"b": 1}, "b": {"a": 1}}
369
+ assert nx.cost_of_flow(G, H) == -2
370
+
371
+ def test_multidigraph(self):
372
+ """Multidigraphs are acceptable."""
373
+ G = nx.MultiDiGraph()
374
+ G.add_weighted_edges_from([(1, 2, 1), (2, 3, 2)], weight="capacity")
375
+ flowCost, H = nx.network_simplex(G)
376
+ assert flowCost == 0
377
+ assert H == {1: {2: {0: 0}}, 2: {3: {0: 0}}, 3: {}}
378
+
379
+ flowCost, H = nx.capacity_scaling(G)
380
+ assert flowCost == 0
381
+ assert H == {1: {2: {0: 0}}, 2: {3: {0: 0}}, 3: {}}
382
+
383
+ def test_negative_selfloops(self):
384
+ """Negative selfloops should cause an exception if uncapacitated and
385
+ always be saturated otherwise.
386
+ """
387
+ G = nx.DiGraph()
388
+ G.add_edge(1, 1, weight=-1)
389
+ pytest.raises(nx.NetworkXUnbounded, nx.network_simplex, G)
390
+ pytest.raises(nx.NetworkXUnbounded, nx.capacity_scaling, G)
391
+ G[1][1]["capacity"] = 2
392
+ flowCost, H = nx.network_simplex(G)
393
+ assert flowCost == -2
394
+ assert H == {1: {1: 2}}
395
+ flowCost, H = nx.capacity_scaling(G)
396
+ assert flowCost == -2
397
+ assert H == {1: {1: 2}}
398
+
399
+ G = nx.MultiDiGraph()
400
+ G.add_edge(1, 1, "x", weight=-1)
401
+ G.add_edge(1, 1, "y", weight=1)
402
+ pytest.raises(nx.NetworkXUnbounded, nx.network_simplex, G)
403
+ pytest.raises(nx.NetworkXUnbounded, nx.capacity_scaling, G)
404
+ G[1][1]["x"]["capacity"] = 2
405
+ flowCost, H = nx.network_simplex(G)
406
+ assert flowCost == -2
407
+ assert H == {1: {1: {"x": 2, "y": 0}}}
408
+ flowCost, H = nx.capacity_scaling(G)
409
+ assert flowCost == -2
410
+ assert H == {1: {1: {"x": 2, "y": 0}}}
411
+
412
+ def test_bone_shaped(self):
413
+ # From #1283
414
+ G = nx.DiGraph()
415
+ G.add_node(0, demand=-4)
416
+ G.add_node(1, demand=2)
417
+ G.add_node(2, demand=2)
418
+ G.add_node(3, demand=4)
419
+ G.add_node(4, demand=-2)
420
+ G.add_node(5, demand=-2)
421
+ G.add_edge(0, 1, capacity=4)
422
+ G.add_edge(0, 2, capacity=4)
423
+ G.add_edge(4, 3, capacity=4)
424
+ G.add_edge(5, 3, capacity=4)
425
+ G.add_edge(0, 3, capacity=0)
426
+ flowCost, H = nx.network_simplex(G)
427
+ assert flowCost == 0
428
+ assert H == {0: {1: 2, 2: 2, 3: 0}, 1: {}, 2: {}, 3: {}, 4: {3: 2}, 5: {3: 2}}
429
+ flowCost, H = nx.capacity_scaling(G)
430
+ assert flowCost == 0
431
+ assert H == {0: {1: 2, 2: 2, 3: 0}, 1: {}, 2: {}, 3: {}, 4: {3: 2}, 5: {3: 2}}
432
+
433
+ def test_exceptions(self):
434
+ G = nx.Graph()
435
+ pytest.raises(nx.NetworkXNotImplemented, nx.network_simplex, G)
436
+ pytest.raises(nx.NetworkXNotImplemented, nx.capacity_scaling, G)
437
+ G = nx.MultiGraph()
438
+ pytest.raises(nx.NetworkXNotImplemented, nx.network_simplex, G)
439
+ pytest.raises(nx.NetworkXNotImplemented, nx.capacity_scaling, G)
440
+ G = nx.DiGraph()
441
+ pytest.raises(nx.NetworkXError, nx.network_simplex, G)
442
+ # pytest.raises(nx.NetworkXError, nx.capacity_scaling, G)
443
+ G.add_node(0, demand=float("inf"))
444
+ pytest.raises(nx.NetworkXError, nx.network_simplex, G)
445
+ pytest.raises(nx.NetworkXUnfeasible, nx.capacity_scaling, G)
446
+ G.nodes[0]["demand"] = 0
447
+ G.add_node(1, demand=0)
448
+ G.add_edge(0, 1, weight=-float("inf"))
449
+ pytest.raises(nx.NetworkXError, nx.network_simplex, G)
450
+ pytest.raises(nx.NetworkXUnfeasible, nx.capacity_scaling, G)
451
+ G[0][1]["weight"] = 0
452
+ G.add_edge(0, 0, weight=float("inf"))
453
+ pytest.raises(nx.NetworkXError, nx.network_simplex, G)
454
+ # pytest.raises(nx.NetworkXError, nx.capacity_scaling, G)
455
+ G[0][0]["weight"] = 0
456
+ G[0][1]["capacity"] = -1
457
+ pytest.raises(nx.NetworkXUnfeasible, nx.network_simplex, G)
458
+ # pytest.raises(nx.NetworkXUnfeasible, nx.capacity_scaling, G)
459
+ G[0][1]["capacity"] = 0
460
+ G[0][0]["capacity"] = -1
461
+ pytest.raises(nx.NetworkXUnfeasible, nx.network_simplex, G)
462
+ # pytest.raises(nx.NetworkXUnfeasible, nx.capacity_scaling, G)
463
+
464
+ def test_large(self):
465
+ fname = (
466
+ importlib.resources.files("networkx.algorithms.flow.tests")
467
+ / "netgen-2.gpickle.bz2"
468
+ )
469
+ with bz2.BZ2File(fname, "rb") as f:
470
+ G = pickle.load(f)
471
+ flowCost, flowDict = nx.network_simplex(G)
472
+ assert 6749969302 == flowCost
473
+ assert 6749969302 == nx.cost_of_flow(G, flowDict)
474
+ flowCost, flowDict = nx.capacity_scaling(G)
475
+ assert 6749969302 == flowCost
476
+ assert 6749969302 == nx.cost_of_flow(G, flowDict)
.venv/lib/python3.11/site-packages/networkx/algorithms/flow/tests/test_networksimplex.py ADDED
@@ -0,0 +1,387 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import bz2
2
+ import importlib.resources
3
+ import os
4
+ import pickle
5
+
6
+ import pytest
7
+
8
+ import networkx as nx
9
+
10
+
11
+ @pytest.fixture
12
+ def simple_flow_graph():
13
+ G = nx.DiGraph()
14
+ G.add_node("a", demand=0)
15
+ G.add_node("b", demand=-5)
16
+ G.add_node("c", demand=50000000)
17
+ G.add_node("d", demand=-49999995)
18
+ G.add_edge("a", "b", weight=3, capacity=4)
19
+ G.add_edge("a", "c", weight=6, capacity=10)
20
+ G.add_edge("b", "d", weight=1, capacity=9)
21
+ G.add_edge("c", "d", weight=2, capacity=5)
22
+ return G
23
+
24
+
25
+ @pytest.fixture
26
+ def simple_no_flow_graph():
27
+ G = nx.DiGraph()
28
+ G.add_node("s", demand=-5)
29
+ G.add_node("t", demand=5)
30
+ G.add_edge("s", "a", weight=1, capacity=3)
31
+ G.add_edge("a", "b", weight=3)
32
+ G.add_edge("a", "c", weight=-6)
33
+ G.add_edge("b", "d", weight=1)
34
+ G.add_edge("c", "d", weight=-2)
35
+ G.add_edge("d", "t", weight=1, capacity=3)
36
+ return G
37
+
38
+
39
+ def get_flowcost_from_flowdict(G, flowDict):
40
+ """Returns flow cost calculated from flow dictionary"""
41
+ flowCost = 0
42
+ for u in flowDict:
43
+ for v in flowDict[u]:
44
+ flowCost += flowDict[u][v] * G[u][v]["weight"]
45
+ return flowCost
46
+
47
+
48
+ def test_infinite_demand_raise(simple_flow_graph):
49
+ G = simple_flow_graph
50
+ inf = float("inf")
51
+ nx.set_node_attributes(G, {"a": {"demand": inf}})
52
+ pytest.raises(nx.NetworkXError, nx.network_simplex, G)
53
+
54
+
55
+ def test_neg_infinite_demand_raise(simple_flow_graph):
56
+ G = simple_flow_graph
57
+ inf = float("inf")
58
+ nx.set_node_attributes(G, {"a": {"demand": -inf}})
59
+ pytest.raises(nx.NetworkXError, nx.network_simplex, G)
60
+
61
+
62
+ def test_infinite_weight_raise(simple_flow_graph):
63
+ G = simple_flow_graph
64
+ inf = float("inf")
65
+ nx.set_edge_attributes(
66
+ G, {("a", "b"): {"weight": inf}, ("b", "d"): {"weight": inf}}
67
+ )
68
+ pytest.raises(nx.NetworkXError, nx.network_simplex, G)
69
+
70
+
71
+ def test_nonzero_net_demand_raise(simple_flow_graph):
72
+ G = simple_flow_graph
73
+ nx.set_node_attributes(G, {"b": {"demand": -4}})
74
+ pytest.raises(nx.NetworkXUnfeasible, nx.network_simplex, G)
75
+
76
+
77
+ def test_negative_capacity_raise(simple_flow_graph):
78
+ G = simple_flow_graph
79
+ nx.set_edge_attributes(G, {("a", "b"): {"weight": 1}, ("b", "d"): {"capacity": -9}})
80
+ pytest.raises(nx.NetworkXUnfeasible, nx.network_simplex, G)
81
+
82
+
83
+ def test_no_flow_satisfying_demands(simple_no_flow_graph):
84
+ G = simple_no_flow_graph
85
+ pytest.raises(nx.NetworkXUnfeasible, nx.network_simplex, G)
86
+
87
+
88
+ def test_sum_demands_not_zero(simple_no_flow_graph):
89
+ G = simple_no_flow_graph
90
+ nx.set_node_attributes(G, {"t": {"demand": 4}})
91
+ pytest.raises(nx.NetworkXUnfeasible, nx.network_simplex, G)
92
+
93
+
94
+ def test_google_or_tools_example():
95
+ """
96
+ https://developers.google.com/optimization/flow/mincostflow
97
+ """
98
+ G = nx.DiGraph()
99
+ start_nodes = [0, 0, 1, 1, 1, 2, 2, 3, 4]
100
+ end_nodes = [1, 2, 2, 3, 4, 3, 4, 4, 2]
101
+ capacities = [15, 8, 20, 4, 10, 15, 4, 20, 5]
102
+ unit_costs = [4, 4, 2, 2, 6, 1, 3, 2, 3]
103
+ supplies = [20, 0, 0, -5, -15]
104
+ answer = 150
105
+
106
+ for i in range(len(supplies)):
107
+ G.add_node(i, demand=(-1) * supplies[i]) # supplies are negative of demand
108
+
109
+ for i in range(len(start_nodes)):
110
+ G.add_edge(
111
+ start_nodes[i], end_nodes[i], weight=unit_costs[i], capacity=capacities[i]
112
+ )
113
+
114
+ flowCost, flowDict = nx.network_simplex(G)
115
+ assert flowCost == answer
116
+ assert flowCost == get_flowcost_from_flowdict(G, flowDict)
117
+
118
+
119
+ def test_google_or_tools_example2():
120
+ """
121
+ https://developers.google.com/optimization/flow/mincostflow
122
+ """
123
+ G = nx.DiGraph()
124
+ start_nodes = [0, 0, 1, 1, 1, 2, 2, 3, 4, 3]
125
+ end_nodes = [1, 2, 2, 3, 4, 3, 4, 4, 2, 5]
126
+ capacities = [15, 8, 20, 4, 10, 15, 4, 20, 5, 10]
127
+ unit_costs = [4, 4, 2, 2, 6, 1, 3, 2, 3, 4]
128
+ supplies = [23, 0, 0, -5, -15, -3]
129
+ answer = 183
130
+
131
+ for i in range(len(supplies)):
132
+ G.add_node(i, demand=(-1) * supplies[i]) # supplies are negative of demand
133
+
134
+ for i in range(len(start_nodes)):
135
+ G.add_edge(
136
+ start_nodes[i], end_nodes[i], weight=unit_costs[i], capacity=capacities[i]
137
+ )
138
+
139
+ flowCost, flowDict = nx.network_simplex(G)
140
+ assert flowCost == answer
141
+ assert flowCost == get_flowcost_from_flowdict(G, flowDict)
142
+
143
+
144
+ def test_large():
145
+ fname = (
146
+ importlib.resources.files("networkx.algorithms.flow.tests")
147
+ / "netgen-2.gpickle.bz2"
148
+ )
149
+
150
+ with bz2.BZ2File(fname, "rb") as f:
151
+ G = pickle.load(f)
152
+ flowCost, flowDict = nx.network_simplex(G)
153
+ assert 6749969302 == flowCost
154
+ assert 6749969302 == nx.cost_of_flow(G, flowDict)
155
+
156
+
157
+ def test_simple_digraph():
158
+ G = nx.DiGraph()
159
+ G.add_node("a", demand=-5)
160
+ G.add_node("d", demand=5)
161
+ G.add_edge("a", "b", weight=3, capacity=4)
162
+ G.add_edge("a", "c", weight=6, capacity=10)
163
+ G.add_edge("b", "d", weight=1, capacity=9)
164
+ G.add_edge("c", "d", weight=2, capacity=5)
165
+ flowCost, H = nx.network_simplex(G)
166
+ soln = {"a": {"b": 4, "c": 1}, "b": {"d": 4}, "c": {"d": 1}, "d": {}}
167
+ assert flowCost == 24
168
+ assert nx.min_cost_flow_cost(G) == 24
169
+ assert H == soln
170
+
171
+
172
+ def test_negcycle_infcap():
173
+ G = nx.DiGraph()
174
+ G.add_node("s", demand=-5)
175
+ G.add_node("t", demand=5)
176
+ G.add_edge("s", "a", weight=1, capacity=3)
177
+ G.add_edge("a", "b", weight=3)
178
+ G.add_edge("c", "a", weight=-6)
179
+ G.add_edge("b", "d", weight=1)
180
+ G.add_edge("d", "c", weight=-2)
181
+ G.add_edge("d", "t", weight=1, capacity=3)
182
+ pytest.raises(nx.NetworkXUnfeasible, nx.network_simplex, G)
183
+
184
+
185
+ def test_transshipment():
186
+ G = nx.DiGraph()
187
+ G.add_node("a", demand=1)
188
+ G.add_node("b", demand=-2)
189
+ G.add_node("c", demand=-2)
190
+ G.add_node("d", demand=3)
191
+ G.add_node("e", demand=-4)
192
+ G.add_node("f", demand=-4)
193
+ G.add_node("g", demand=3)
194
+ G.add_node("h", demand=2)
195
+ G.add_node("r", demand=3)
196
+ G.add_edge("a", "c", weight=3)
197
+ G.add_edge("r", "a", weight=2)
198
+ G.add_edge("b", "a", weight=9)
199
+ G.add_edge("r", "c", weight=0)
200
+ G.add_edge("b", "r", weight=-6)
201
+ G.add_edge("c", "d", weight=5)
202
+ G.add_edge("e", "r", weight=4)
203
+ G.add_edge("e", "f", weight=3)
204
+ G.add_edge("h", "b", weight=4)
205
+ G.add_edge("f", "d", weight=7)
206
+ G.add_edge("f", "h", weight=12)
207
+ G.add_edge("g", "d", weight=12)
208
+ G.add_edge("f", "g", weight=-1)
209
+ G.add_edge("h", "g", weight=-10)
210
+ flowCost, H = nx.network_simplex(G)
211
+ soln = {
212
+ "a": {"c": 0},
213
+ "b": {"a": 0, "r": 2},
214
+ "c": {"d": 3},
215
+ "d": {},
216
+ "e": {"r": 3, "f": 1},
217
+ "f": {"d": 0, "g": 3, "h": 2},
218
+ "g": {"d": 0},
219
+ "h": {"b": 0, "g": 0},
220
+ "r": {"a": 1, "c": 1},
221
+ }
222
+ assert flowCost == 41
223
+ assert H == soln
224
+
225
+
226
+ def test_digraph1():
227
+ # From Bradley, S. P., Hax, A. C. and Magnanti, T. L. Applied
228
+ # Mathematical Programming. Addison-Wesley, 1977.
229
+ G = nx.DiGraph()
230
+ G.add_node(1, demand=-20)
231
+ G.add_node(4, demand=5)
232
+ G.add_node(5, demand=15)
233
+ G.add_edges_from(
234
+ [
235
+ (1, 2, {"capacity": 15, "weight": 4}),
236
+ (1, 3, {"capacity": 8, "weight": 4}),
237
+ (2, 3, {"weight": 2}),
238
+ (2, 4, {"capacity": 4, "weight": 2}),
239
+ (2, 5, {"capacity": 10, "weight": 6}),
240
+ (3, 4, {"capacity": 15, "weight": 1}),
241
+ (3, 5, {"capacity": 5, "weight": 3}),
242
+ (4, 5, {"weight": 2}),
243
+ (5, 3, {"capacity": 4, "weight": 1}),
244
+ ]
245
+ )
246
+ flowCost, H = nx.network_simplex(G)
247
+ soln = {
248
+ 1: {2: 12, 3: 8},
249
+ 2: {3: 8, 4: 4, 5: 0},
250
+ 3: {4: 11, 5: 5},
251
+ 4: {5: 10},
252
+ 5: {3: 0},
253
+ }
254
+ assert flowCost == 150
255
+ assert nx.min_cost_flow_cost(G) == 150
256
+ assert H == soln
257
+
258
+
259
+ def test_zero_capacity_edges():
260
+ """Address issue raised in ticket #617 by arv."""
261
+ G = nx.DiGraph()
262
+ G.add_edges_from(
263
+ [
264
+ (1, 2, {"capacity": 1, "weight": 1}),
265
+ (1, 5, {"capacity": 1, "weight": 1}),
266
+ (2, 3, {"capacity": 0, "weight": 1}),
267
+ (2, 5, {"capacity": 1, "weight": 1}),
268
+ (5, 3, {"capacity": 2, "weight": 1}),
269
+ (5, 4, {"capacity": 0, "weight": 1}),
270
+ (3, 4, {"capacity": 2, "weight": 1}),
271
+ ]
272
+ )
273
+ G.nodes[1]["demand"] = -1
274
+ G.nodes[2]["demand"] = -1
275
+ G.nodes[4]["demand"] = 2
276
+
277
+ flowCost, H = nx.network_simplex(G)
278
+ soln = {1: {2: 0, 5: 1}, 2: {3: 0, 5: 1}, 3: {4: 2}, 4: {}, 5: {3: 2, 4: 0}}
279
+ assert flowCost == 6
280
+ assert nx.min_cost_flow_cost(G) == 6
281
+ assert H == soln
282
+
283
+
284
+ def test_digon():
285
+ """Check if digons are handled properly. Taken from ticket
286
+ #618 by arv."""
287
+ nodes = [(1, {}), (2, {"demand": -4}), (3, {"demand": 4})]
288
+ edges = [
289
+ (1, 2, {"capacity": 3, "weight": 600000}),
290
+ (2, 1, {"capacity": 2, "weight": 0}),
291
+ (2, 3, {"capacity": 5, "weight": 714285}),
292
+ (3, 2, {"capacity": 2, "weight": 0}),
293
+ ]
294
+ G = nx.DiGraph(edges)
295
+ G.add_nodes_from(nodes)
296
+ flowCost, H = nx.network_simplex(G)
297
+ soln = {1: {2: 0}, 2: {1: 0, 3: 4}, 3: {2: 0}}
298
+ assert flowCost == 2857140
299
+
300
+
301
+ def test_deadend():
302
+ """Check if one-node cycles are handled properly. Taken from ticket
303
+ #2906 from @sshraven."""
304
+ G = nx.DiGraph()
305
+
306
+ G.add_nodes_from(range(5), demand=0)
307
+ G.nodes[4]["demand"] = -13
308
+ G.nodes[3]["demand"] = 13
309
+
310
+ G.add_edges_from([(0, 2), (0, 3), (2, 1)], capacity=20, weight=0.1)
311
+ pytest.raises(nx.NetworkXUnfeasible, nx.network_simplex, G)
312
+
313
+
314
+ def test_infinite_capacity_neg_digon():
315
+ """An infinite capacity negative cost digon results in an unbounded
316
+ instance."""
317
+ nodes = [(1, {}), (2, {"demand": -4}), (3, {"demand": 4})]
318
+ edges = [
319
+ (1, 2, {"weight": -600}),
320
+ (2, 1, {"weight": 0}),
321
+ (2, 3, {"capacity": 5, "weight": 714285}),
322
+ (3, 2, {"capacity": 2, "weight": 0}),
323
+ ]
324
+ G = nx.DiGraph(edges)
325
+ G.add_nodes_from(nodes)
326
+ pytest.raises(nx.NetworkXUnbounded, nx.network_simplex, G)
327
+
328
+
329
+ def test_multidigraph():
330
+ """Multidigraphs are acceptable."""
331
+ G = nx.MultiDiGraph()
332
+ G.add_weighted_edges_from([(1, 2, 1), (2, 3, 2)], weight="capacity")
333
+ flowCost, H = nx.network_simplex(G)
334
+ assert flowCost == 0
335
+ assert H == {1: {2: {0: 0}}, 2: {3: {0: 0}}, 3: {}}
336
+
337
+
338
+ def test_negative_selfloops():
339
+ """Negative selfloops should cause an exception if uncapacitated and
340
+ always be saturated otherwise.
341
+ """
342
+ G = nx.DiGraph()
343
+ G.add_edge(1, 1, weight=-1)
344
+ pytest.raises(nx.NetworkXUnbounded, nx.network_simplex, G)
345
+
346
+ G[1][1]["capacity"] = 2
347
+ flowCost, H = nx.network_simplex(G)
348
+ assert flowCost == -2
349
+ assert H == {1: {1: 2}}
350
+
351
+ G = nx.MultiDiGraph()
352
+ G.add_edge(1, 1, "x", weight=-1)
353
+ G.add_edge(1, 1, "y", weight=1)
354
+ pytest.raises(nx.NetworkXUnbounded, nx.network_simplex, G)
355
+
356
+ G[1][1]["x"]["capacity"] = 2
357
+ flowCost, H = nx.network_simplex(G)
358
+ assert flowCost == -2
359
+ assert H == {1: {1: {"x": 2, "y": 0}}}
360
+
361
+
362
+ def test_bone_shaped():
363
+ # From #1283
364
+ G = nx.DiGraph()
365
+ G.add_node(0, demand=-4)
366
+ G.add_node(1, demand=2)
367
+ G.add_node(2, demand=2)
368
+ G.add_node(3, demand=4)
369
+ G.add_node(4, demand=-2)
370
+ G.add_node(5, demand=-2)
371
+ G.add_edge(0, 1, capacity=4)
372
+ G.add_edge(0, 2, capacity=4)
373
+ G.add_edge(4, 3, capacity=4)
374
+ G.add_edge(5, 3, capacity=4)
375
+ G.add_edge(0, 3, capacity=0)
376
+ flowCost, H = nx.network_simplex(G)
377
+ assert flowCost == 0
378
+ assert H == {0: {1: 2, 2: 2, 3: 0}, 1: {}, 2: {}, 3: {}, 4: {3: 2}, 5: {3: 2}}
379
+
380
+
381
+ def test_graphs_type_exceptions():
382
+ G = nx.Graph()
383
+ pytest.raises(nx.NetworkXNotImplemented, nx.network_simplex, G)
384
+ G = nx.MultiGraph()
385
+ pytest.raises(nx.NetworkXNotImplemented, nx.network_simplex, G)
386
+ G = nx.DiGraph()
387
+ pytest.raises(nx.NetworkXError, nx.network_simplex, G)
.venv/lib/python3.11/site-packages/networkx/algorithms/flow/utils.py ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Utility classes and functions for network flow algorithms.
3
+ """
4
+
5
+ from collections import deque
6
+
7
+ import networkx as nx
8
+
9
+ __all__ = [
10
+ "CurrentEdge",
11
+ "Level",
12
+ "GlobalRelabelThreshold",
13
+ "build_residual_network",
14
+ "detect_unboundedness",
15
+ "build_flow_dict",
16
+ ]
17
+
18
+
19
+ class CurrentEdge:
20
+ """Mechanism for iterating over out-edges incident to a node in a circular
21
+ manner. StopIteration exception is raised when wraparound occurs.
22
+ """
23
+
24
+ __slots__ = ("_edges", "_it", "_curr")
25
+
26
+ def __init__(self, edges):
27
+ self._edges = edges
28
+ if self._edges:
29
+ self._rewind()
30
+
31
+ def get(self):
32
+ return self._curr
33
+
34
+ def move_to_next(self):
35
+ try:
36
+ self._curr = next(self._it)
37
+ except StopIteration:
38
+ self._rewind()
39
+ raise
40
+
41
+ def _rewind(self):
42
+ self._it = iter(self._edges.items())
43
+ self._curr = next(self._it)
44
+
45
+
46
+ class Level:
47
+ """Active and inactive nodes in a level."""
48
+
49
+ __slots__ = ("active", "inactive")
50
+
51
+ def __init__(self):
52
+ self.active = set()
53
+ self.inactive = set()
54
+
55
+
56
+ class GlobalRelabelThreshold:
57
+ """Measurement of work before the global relabeling heuristic should be
58
+ applied.
59
+ """
60
+
61
+ def __init__(self, n, m, freq):
62
+ self._threshold = (n + m) / freq if freq else float("inf")
63
+ self._work = 0
64
+
65
+ def add_work(self, work):
66
+ self._work += work
67
+
68
+ def is_reached(self):
69
+ return self._work >= self._threshold
70
+
71
+ def clear_work(self):
72
+ self._work = 0
73
+
74
+
75
+ @nx._dispatchable(edge_attrs={"capacity": float("inf")}, returns_graph=True)
76
+ def build_residual_network(G, capacity):
77
+ """Build a residual network and initialize a zero flow.
78
+
79
+ The residual network :samp:`R` from an input graph :samp:`G` has the
80
+ same nodes as :samp:`G`. :samp:`R` is a DiGraph that contains a pair
81
+ of edges :samp:`(u, v)` and :samp:`(v, u)` iff :samp:`(u, v)` is not a
82
+ self-loop, and at least one of :samp:`(u, v)` and :samp:`(v, u)` exists
83
+ in :samp:`G`.
84
+
85
+ For each edge :samp:`(u, v)` in :samp:`R`, :samp:`R[u][v]['capacity']`
86
+ is equal to the capacity of :samp:`(u, v)` in :samp:`G` if it exists
87
+ in :samp:`G` or zero otherwise. If the capacity is infinite,
88
+ :samp:`R[u][v]['capacity']` will have a high arbitrary finite value
89
+ that does not affect the solution of the problem. This value is stored in
90
+ :samp:`R.graph['inf']`. For each edge :samp:`(u, v)` in :samp:`R`,
91
+ :samp:`R[u][v]['flow']` represents the flow function of :samp:`(u, v)` and
92
+ satisfies :samp:`R[u][v]['flow'] == -R[v][u]['flow']`.
93
+
94
+ The flow value, defined as the total flow into :samp:`t`, the sink, is
95
+ stored in :samp:`R.graph['flow_value']`. If :samp:`cutoff` is not
96
+ specified, reachability to :samp:`t` using only edges :samp:`(u, v)` such
97
+ that :samp:`R[u][v]['flow'] < R[u][v]['capacity']` induces a minimum
98
+ :samp:`s`-:samp:`t` cut.
99
+
100
+ """
101
+ if G.is_multigraph():
102
+ raise nx.NetworkXError("MultiGraph and MultiDiGraph not supported (yet).")
103
+
104
+ R = nx.DiGraph()
105
+ R.__networkx_cache__ = None # Disable caching
106
+ R.add_nodes_from(G)
107
+
108
+ inf = float("inf")
109
+ # Extract edges with positive capacities. Self loops excluded.
110
+ edge_list = [
111
+ (u, v, attr)
112
+ for u, v, attr in G.edges(data=True)
113
+ if u != v and attr.get(capacity, inf) > 0
114
+ ]
115
+ # Simulate infinity with three times the sum of the finite edge capacities
116
+ # or any positive value if the sum is zero. This allows the
117
+ # infinite-capacity edges to be distinguished for unboundedness detection
118
+ # and directly participate in residual capacity calculation. If the maximum
119
+ # flow is finite, these edges cannot appear in the minimum cut and thus
120
+ # guarantee correctness. Since the residual capacity of an
121
+ # infinite-capacity edge is always at least 2/3 of inf, while that of an
122
+ # finite-capacity edge is at most 1/3 of inf, if an operation moves more
123
+ # than 1/3 of inf units of flow to t, there must be an infinite-capacity
124
+ # s-t path in G.
125
+ inf = (
126
+ 3
127
+ * sum(
128
+ attr[capacity]
129
+ for u, v, attr in edge_list
130
+ if capacity in attr and attr[capacity] != inf
131
+ )
132
+ or 1
133
+ )
134
+ if G.is_directed():
135
+ for u, v, attr in edge_list:
136
+ r = min(attr.get(capacity, inf), inf)
137
+ if not R.has_edge(u, v):
138
+ # Both (u, v) and (v, u) must be present in the residual
139
+ # network.
140
+ R.add_edge(u, v, capacity=r)
141
+ R.add_edge(v, u, capacity=0)
142
+ else:
143
+ # The edge (u, v) was added when (v, u) was visited.
144
+ R[u][v]["capacity"] = r
145
+ else:
146
+ for u, v, attr in edge_list:
147
+ # Add a pair of edges with equal residual capacities.
148
+ r = min(attr.get(capacity, inf), inf)
149
+ R.add_edge(u, v, capacity=r)
150
+ R.add_edge(v, u, capacity=r)
151
+
152
+ # Record the value simulating infinity.
153
+ R.graph["inf"] = inf
154
+
155
+ return R
156
+
157
+
158
+ @nx._dispatchable(
159
+ graphs="R",
160
+ preserve_edge_attrs={"R": {"capacity": float("inf")}},
161
+ preserve_graph_attrs=True,
162
+ )
163
+ def detect_unboundedness(R, s, t):
164
+ """Detect an infinite-capacity s-t path in R."""
165
+ q = deque([s])
166
+ seen = {s}
167
+ inf = R.graph["inf"]
168
+ while q:
169
+ u = q.popleft()
170
+ for v, attr in R[u].items():
171
+ if attr["capacity"] == inf and v not in seen:
172
+ if v == t:
173
+ raise nx.NetworkXUnbounded(
174
+ "Infinite capacity path, flow unbounded above."
175
+ )
176
+ seen.add(v)
177
+ q.append(v)
178
+
179
+
180
+ @nx._dispatchable(graphs={"G": 0, "R": 1}, preserve_edge_attrs={"R": {"flow": None}})
181
+ def build_flow_dict(G, R):
182
+ """Build a flow dictionary from a residual network."""
183
+ flow_dict = {}
184
+ for u in G:
185
+ flow_dict[u] = {v: 0 for v in G[u]}
186
+ flow_dict[u].update(
187
+ (v, attr["flow"]) for v, attr in R[u].items() if attr["flow"] > 0
188
+ )
189
+ return flow_dict
.venv/lib/python3.11/site-packages/networkx/algorithms/link_analysis/__init__.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ from networkx.algorithms.link_analysis.hits_alg import *
2
+ from networkx.algorithms.link_analysis.pagerank_alg import *
.venv/lib/python3.11/site-packages/networkx/algorithms/link_analysis/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (341 Bytes). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/link_analysis/__pycache__/hits_alg.cpython-311.pyc ADDED
Binary file (14.6 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/algorithms/link_analysis/__pycache__/pagerank_alg.cpython-311.pyc ADDED
Binary file (22.2 kB). View file