koichi12 commited on
Commit
738db08
·
verified ·
1 Parent(s): 799d677

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. .env +1 -0
  2. .python-version +1 -0
  3. hoge.txt +1 -0
  4. settings.yaml +7 -0
  5. tuning-competition-baseline/.flake8 +2 -0
  6. tuning-competition-baseline/.isort.cfg +2 -0
  7. tuning-competition-baseline/.venv/bin/isympy +8 -0
  8. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/chains.py +172 -0
  9. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/clique.py +753 -0
  10. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/communicability_alg.py +162 -0
  11. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/covering.py +142 -0
  12. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/cuts.py +400 -0
  13. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/hybrid.py +195 -0
  14. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/link_prediction.py +604 -0
  15. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/simple_paths.py +978 -0
  16. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/voronoi.py +85 -0
  17. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/__pycache__/__init__.cpython-311.pyc +0 -0
  18. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/__pycache__/coreviews.cpython-311.pyc +0 -0
  19. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/__pycache__/filters.cpython-311.pyc +0 -0
  20. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/__pycache__/graph.cpython-311.pyc +0 -0
  21. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/coreviews.py +367 -0
  22. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/digraph.py +1323 -0
  23. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/filters.py +75 -0
  24. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/function.py +1313 -0
  25. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/graph.py +2030 -0
  26. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/graphviews.py +267 -0
  27. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/multidigraph.py +963 -0
  28. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/__pycache__/test_reportviews.cpython-311.pyc +0 -0
  29. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/test_graph_historical.py +12 -0
  30. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/test_reportviews.py +1423 -0
  31. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/test_subgraphviews.py +362 -0
  32. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/convert.py +496 -0
  33. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/convert_matrix.py +1200 -0
  34. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/exception.py +125 -0
  35. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/linalg/__pycache__/attrmatrix.cpython-311.pyc +0 -0
  36. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/linalg/__pycache__/spectrum.cpython-311.pyc +0 -0
  37. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/linalg/algebraicconnectivity.py +656 -0
  38. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/linalg/bethehessianmatrix.py +78 -0
  39. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/linalg/tests/__pycache__/test_bethehessian.cpython-311.pyc +0 -0
  40. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/readwrite/__pycache__/edgelist.cpython-311.pyc +0 -0
  41. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/readwrite/__pycache__/gexf.cpython-311.pyc +0 -0
  42. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/readwrite/__pycache__/gml.cpython-311.pyc +0 -0
  43. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/readwrite/__pycache__/graphml.cpython-311.pyc +0 -0
  44. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/readwrite/__pycache__/multiline_adjlist.cpython-311.pyc +0 -0
  45. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/readwrite/gexf.py +1065 -0
  46. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/readwrite/json_graph/__pycache__/tree.cpython-311.pyc +0 -0
  47. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/readwrite/json_graph/cytoscape.py +174 -0
  48. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/readwrite/json_graph/tests/__init__.py +0 -0
  49. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/readwrite/json_graph/tests/__pycache__/__init__.cpython-311.pyc +0 -0
  50. tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/readwrite/multiline_adjlist.py +393 -0
.env ADDED
@@ -0,0 +1 @@
 
 
1
+ GOOGLE_API_KEY=AIzaSyBqBRvefAw-B9DdKuD3ueKfA4rqvFdr9Zw
.python-version ADDED
@@ -0,0 +1 @@
 
 
1
+ 3.11.10
hoge.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ Hello World
settings.yaml ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ client_config_file: client_secrets.json
2
+
3
+ save_credentials: True
4
+ save_credentials_backend: file
5
+ save_credentials_file: saved_credentials.json
6
+
7
+ get_refresh_token: True
tuning-competition-baseline/.flake8 ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ [flake8]
2
+ extend-ignore = E203,E501
tuning-competition-baseline/.isort.cfg ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ [settings]
2
+ profile=black
tuning-competition-baseline/.venv/bin/isympy ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ #!/home/koiwa/work/tuning-competition-baseline/.venv/bin/python3.11
2
+ # -*- coding: utf-8 -*-
3
+ import re
4
+ import sys
5
+ from isympy import main
6
+ if __name__ == '__main__':
7
+ sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
8
+ sys.exit(main())
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/chains.py ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Functions for finding chains in a graph."""
2
+
3
+ import networkx as nx
4
+ from networkx.utils import not_implemented_for
5
+
6
+ __all__ = ["chain_decomposition"]
7
+
8
+
9
+ @not_implemented_for("directed")
10
+ @not_implemented_for("multigraph")
11
+ @nx._dispatch
12
+ def chain_decomposition(G, root=None):
13
+ """Returns the chain decomposition of a graph.
14
+
15
+ The *chain decomposition* of a graph with respect a depth-first
16
+ search tree is a set of cycles or paths derived from the set of
17
+ fundamental cycles of the tree in the following manner. Consider
18
+ each fundamental cycle with respect to the given tree, represented
19
+ as a list of edges beginning with the nontree edge oriented away
20
+ from the root of the tree. For each fundamental cycle, if it
21
+ overlaps with any previous fundamental cycle, just take the initial
22
+ non-overlapping segment, which is a path instead of a cycle. Each
23
+ cycle or path is called a *chain*. For more information, see [1]_.
24
+
25
+ Parameters
26
+ ----------
27
+ G : undirected graph
28
+
29
+ root : node (optional)
30
+ A node in the graph `G`. If specified, only the chain
31
+ decomposition for the connected component containing this node
32
+ will be returned. This node indicates the root of the depth-first
33
+ search tree.
34
+
35
+ Yields
36
+ ------
37
+ chain : list
38
+ A list of edges representing a chain. There is no guarantee on
39
+ the orientation of the edges in each chain (for example, if a
40
+ chain includes the edge joining nodes 1 and 2, the chain may
41
+ include either (1, 2) or (2, 1)).
42
+
43
+ Raises
44
+ ------
45
+ NodeNotFound
46
+ If `root` is not in the graph `G`.
47
+
48
+ Examples
49
+ --------
50
+ >>> G = nx.Graph([(0, 1), (1, 4), (3, 4), (3, 5), (4, 5)])
51
+ >>> list(nx.chain_decomposition(G))
52
+ [[(4, 5), (5, 3), (3, 4)]]
53
+
54
+ Notes
55
+ -----
56
+ The worst-case running time of this implementation is linear in the
57
+ number of nodes and number of edges [1]_.
58
+
59
+ References
60
+ ----------
61
+ .. [1] Jens M. Schmidt (2013). "A simple test on 2-vertex-
62
+ and 2-edge-connectivity." *Information Processing Letters*,
63
+ 113, 241–244. Elsevier. <https://doi.org/10.1016/j.ipl.2013.01.016>
64
+
65
+ """
66
+
67
+ def _dfs_cycle_forest(G, root=None):
68
+ """Builds a directed graph composed of cycles from the given graph.
69
+
70
+ `G` is an undirected simple graph. `root` is a node in the graph
71
+ from which the depth-first search is started.
72
+
73
+ This function returns both the depth-first search cycle graph
74
+ (as a :class:`~networkx.DiGraph`) and the list of nodes in
75
+ depth-first preorder. The depth-first search cycle graph is a
76
+ directed graph whose edges are the edges of `G` oriented toward
77
+ the root if the edge is a tree edge and away from the root if
78
+ the edge is a non-tree edge. If `root` is not specified, this
79
+ performs a depth-first search on each connected component of `G`
80
+ and returns a directed forest instead.
81
+
82
+ If `root` is not in the graph, this raises :exc:`KeyError`.
83
+
84
+ """
85
+ # Create a directed graph from the depth-first search tree with
86
+ # root node `root` in which tree edges are directed toward the
87
+ # root and nontree edges are directed away from the root. For
88
+ # each node with an incident nontree edge, this creates a
89
+ # directed cycle starting with the nontree edge and returning to
90
+ # that node.
91
+ #
92
+ # The `parent` node attribute stores the parent of each node in
93
+ # the DFS tree. The `nontree` edge attribute indicates whether
94
+ # the edge is a tree edge or a nontree edge.
95
+ #
96
+ # We also store the order of the nodes found in the depth-first
97
+ # search in the `nodes` list.
98
+ H = nx.DiGraph()
99
+ nodes = []
100
+ for u, v, d in nx.dfs_labeled_edges(G, source=root):
101
+ if d == "forward":
102
+ # `dfs_labeled_edges()` yields (root, root, 'forward')
103
+ # if it is beginning the search on a new connected
104
+ # component.
105
+ if u == v:
106
+ H.add_node(v, parent=None)
107
+ nodes.append(v)
108
+ else:
109
+ H.add_node(v, parent=u)
110
+ H.add_edge(v, u, nontree=False)
111
+ nodes.append(v)
112
+ # `dfs_labeled_edges` considers nontree edges in both
113
+ # orientations, so we need to not add the edge if it its
114
+ # other orientation has been added.
115
+ elif d == "nontree" and v not in H[u]:
116
+ H.add_edge(v, u, nontree=True)
117
+ else:
118
+ # Do nothing on 'reverse' edges; we only care about
119
+ # forward and nontree edges.
120
+ pass
121
+ return H, nodes
122
+
123
+ def _build_chain(G, u, v, visited):
124
+ """Generate the chain starting from the given nontree edge.
125
+
126
+ `G` is a DFS cycle graph as constructed by
127
+ :func:`_dfs_cycle_graph`. The edge (`u`, `v`) is a nontree edge
128
+ that begins a chain. `visited` is a set representing the nodes
129
+ in `G` that have already been visited.
130
+
131
+ This function yields the edges in an initial segment of the
132
+ fundamental cycle of `G` starting with the nontree edge (`u`,
133
+ `v`) that includes all the edges up until the first node that
134
+ appears in `visited`. The tree edges are given by the 'parent'
135
+ node attribute. The `visited` set is updated to add each node in
136
+ an edge yielded by this function.
137
+
138
+ """
139
+ while v not in visited:
140
+ yield u, v
141
+ visited.add(v)
142
+ u, v = v, G.nodes[v]["parent"]
143
+ yield u, v
144
+
145
+ # Check if the root is in the graph G. If not, raise NodeNotFound
146
+ if root is not None and root not in G:
147
+ raise nx.NodeNotFound(f"Root node {root} is not in graph")
148
+
149
+ # Create a directed version of H that has the DFS edges directed
150
+ # toward the root and the nontree edges directed away from the root
151
+ # (in each connected component).
152
+ H, nodes = _dfs_cycle_forest(G, root)
153
+
154
+ # Visit the nodes again in DFS order. For each node, and for each
155
+ # nontree edge leaving that node, compute the fundamental cycle for
156
+ # that nontree edge starting with that edge. If the fundamental
157
+ # cycle overlaps with any visited nodes, just take the prefix of the
158
+ # cycle up to the point of visited nodes.
159
+ #
160
+ # We repeat this process for each connected component (implicitly,
161
+ # since `nodes` already has a list of the nodes grouped by connected
162
+ # component).
163
+ visited = set()
164
+ for u in nodes:
165
+ visited.add(u)
166
+ # For each nontree edge going out of node u...
167
+ edges = ((u, v) for u, v, d in H.out_edges(u, data="nontree") if d)
168
+ for u, v in edges:
169
+ # Create the cycle or cycle prefix starting with the
170
+ # nontree edge.
171
+ chain = list(_build_chain(H, u, v, visited))
172
+ yield chain
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/clique.py ADDED
@@ -0,0 +1,753 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Functions for finding and manipulating cliques.
2
+
3
+ Finding the largest clique in a graph is NP-complete problem, so most of
4
+ these algorithms have an exponential running time; for more information,
5
+ see the Wikipedia article on the clique problem [1]_.
6
+
7
+ .. [1] clique problem:: https://en.wikipedia.org/wiki/Clique_problem
8
+
9
+ """
10
+ from collections import defaultdict, deque
11
+ from itertools import chain, combinations, islice
12
+
13
+ import networkx as nx
14
+ from networkx.utils import not_implemented_for
15
+
16
+ __all__ = [
17
+ "find_cliques",
18
+ "find_cliques_recursive",
19
+ "make_max_clique_graph",
20
+ "make_clique_bipartite",
21
+ "node_clique_number",
22
+ "number_of_cliques",
23
+ "enumerate_all_cliques",
24
+ "max_weight_clique",
25
+ ]
26
+
27
+
28
+ @not_implemented_for("directed")
29
+ @nx._dispatch
30
+ def enumerate_all_cliques(G):
31
+ """Returns all cliques in an undirected graph.
32
+
33
+ This function returns an iterator over cliques, each of which is a
34
+ list of nodes. The iteration is ordered by cardinality of the
35
+ cliques: first all cliques of size one, then all cliques of size
36
+ two, etc.
37
+
38
+ Parameters
39
+ ----------
40
+ G : NetworkX graph
41
+ An undirected graph.
42
+
43
+ Returns
44
+ -------
45
+ iterator
46
+ An iterator over cliques, each of which is a list of nodes in
47
+ `G`. The cliques are ordered according to size.
48
+
49
+ Notes
50
+ -----
51
+ To obtain a list of all cliques, use
52
+ `list(enumerate_all_cliques(G))`. However, be aware that in the
53
+ worst-case, the length of this list can be exponential in the number
54
+ of nodes in the graph (for example, when the graph is the complete
55
+ graph). This function avoids storing all cliques in memory by only
56
+ keeping current candidate node lists in memory during its search.
57
+
58
+ The implementation is adapted from the algorithm by Zhang, et
59
+ al. (2005) [1]_ to output all cliques discovered.
60
+
61
+ This algorithm ignores self-loops and parallel edges, since cliques
62
+ are not conventionally defined with such edges.
63
+
64
+ References
65
+ ----------
66
+ .. [1] Yun Zhang, Abu-Khzam, F.N., Baldwin, N.E., Chesler, E.J.,
67
+ Langston, M.A., Samatova, N.F.,
68
+ "Genome-Scale Computational Approaches to Memory-Intensive
69
+ Applications in Systems Biology".
70
+ *Supercomputing*, 2005. Proceedings of the ACM/IEEE SC 2005
71
+ Conference, pp. 12, 12--18 Nov. 2005.
72
+ <https://doi.org/10.1109/SC.2005.29>.
73
+
74
+ """
75
+ index = {}
76
+ nbrs = {}
77
+ for u in G:
78
+ index[u] = len(index)
79
+ # Neighbors of u that appear after u in the iteration order of G.
80
+ nbrs[u] = {v for v in G[u] if v not in index}
81
+
82
+ queue = deque(([u], sorted(nbrs[u], key=index.__getitem__)) for u in G)
83
+ # Loop invariants:
84
+ # 1. len(base) is nondecreasing.
85
+ # 2. (base + cnbrs) is sorted with respect to the iteration order of G.
86
+ # 3. cnbrs is a set of common neighbors of nodes in base.
87
+ while queue:
88
+ base, cnbrs = map(list, queue.popleft())
89
+ yield base
90
+ for i, u in enumerate(cnbrs):
91
+ # Use generators to reduce memory consumption.
92
+ queue.append(
93
+ (
94
+ chain(base, [u]),
95
+ filter(nbrs[u].__contains__, islice(cnbrs, i + 1, None)),
96
+ )
97
+ )
98
+
99
+
100
+ @not_implemented_for("directed")
101
+ @nx._dispatch
102
+ def find_cliques(G, nodes=None):
103
+ """Returns all maximal cliques in an undirected graph.
104
+
105
+ For each node *n*, a *maximal clique for n* is a largest complete
106
+ subgraph containing *n*. The largest maximal clique is sometimes
107
+ called the *maximum clique*.
108
+
109
+ This function returns an iterator over cliques, each of which is a
110
+ list of nodes. It is an iterative implementation, so should not
111
+ suffer from recursion depth issues.
112
+
113
+ This function accepts a list of `nodes` and only the maximal cliques
114
+ containing all of these `nodes` are returned. It can considerably speed up
115
+ the running time if some specific cliques are desired.
116
+
117
+ Parameters
118
+ ----------
119
+ G : NetworkX graph
120
+ An undirected graph.
121
+
122
+ nodes : list, optional (default=None)
123
+ If provided, only yield *maximal cliques* containing all nodes in `nodes`.
124
+ If `nodes` isn't a clique itself, a ValueError is raised.
125
+
126
+ Returns
127
+ -------
128
+ iterator
129
+ An iterator over maximal cliques, each of which is a list of
130
+ nodes in `G`. If `nodes` is provided, only the maximal cliques
131
+ containing all the nodes in `nodes` are returned. The order of
132
+ cliques is arbitrary.
133
+
134
+ Raises
135
+ ------
136
+ ValueError
137
+ If `nodes` is not a clique.
138
+
139
+ Examples
140
+ --------
141
+ >>> from pprint import pprint # For nice dict formatting
142
+ >>> G = nx.karate_club_graph()
143
+ >>> sum(1 for c in nx.find_cliques(G)) # The number of maximal cliques in G
144
+ 36
145
+ >>> max(nx.find_cliques(G), key=len) # The largest maximal clique in G
146
+ [0, 1, 2, 3, 13]
147
+
148
+ The size of the largest maximal clique is known as the *clique number* of
149
+ the graph, which can be found directly with:
150
+
151
+ >>> max(len(c) for c in nx.find_cliques(G))
152
+ 5
153
+
154
+ One can also compute the number of maximal cliques in `G` that contain a given
155
+ node. The following produces a dictionary keyed by node whose
156
+ values are the number of maximal cliques in `G` that contain the node:
157
+
158
+ >>> pprint({n: sum(1 for c in nx.find_cliques(G) if n in c) for n in G})
159
+ {0: 13,
160
+ 1: 6,
161
+ 2: 7,
162
+ 3: 3,
163
+ 4: 2,
164
+ 5: 3,
165
+ 6: 3,
166
+ 7: 1,
167
+ 8: 3,
168
+ 9: 2,
169
+ 10: 2,
170
+ 11: 1,
171
+ 12: 1,
172
+ 13: 2,
173
+ 14: 1,
174
+ 15: 1,
175
+ 16: 1,
176
+ 17: 1,
177
+ 18: 1,
178
+ 19: 2,
179
+ 20: 1,
180
+ 21: 1,
181
+ 22: 1,
182
+ 23: 3,
183
+ 24: 2,
184
+ 25: 2,
185
+ 26: 1,
186
+ 27: 3,
187
+ 28: 2,
188
+ 29: 2,
189
+ 30: 2,
190
+ 31: 4,
191
+ 32: 9,
192
+ 33: 14}
193
+
194
+ Or, similarly, the maximal cliques in `G` that contain a given node.
195
+ For example, the 4 maximal cliques that contain node 31:
196
+
197
+ >>> [c for c in nx.find_cliques(G) if 31 in c]
198
+ [[0, 31], [33, 32, 31], [33, 28, 31], [24, 25, 31]]
199
+
200
+ See Also
201
+ --------
202
+ find_cliques_recursive
203
+ A recursive version of the same algorithm.
204
+
205
+ Notes
206
+ -----
207
+ To obtain a list of all maximal cliques, use
208
+ `list(find_cliques(G))`. However, be aware that in the worst-case,
209
+ the length of this list can be exponential in the number of nodes in
210
+ the graph. This function avoids storing all cliques in memory by
211
+ only keeping current candidate node lists in memory during its search.
212
+
213
+ This implementation is based on the algorithm published by Bron and
214
+ Kerbosch (1973) [1]_, as adapted by Tomita, Tanaka and Takahashi
215
+ (2006) [2]_ and discussed in Cazals and Karande (2008) [3]_. It
216
+ essentially unrolls the recursion used in the references to avoid
217
+ issues of recursion stack depth (for a recursive implementation, see
218
+ :func:`find_cliques_recursive`).
219
+
220
+ This algorithm ignores self-loops and parallel edges, since cliques
221
+ are not conventionally defined with such edges.
222
+
223
+ References
224
+ ----------
225
+ .. [1] Bron, C. and Kerbosch, J.
226
+ "Algorithm 457: finding all cliques of an undirected graph".
227
+ *Communications of the ACM* 16, 9 (Sep. 1973), 575--577.
228
+ <http://portal.acm.org/citation.cfm?doid=362342.362367>
229
+
230
+ .. [2] Etsuji Tomita, Akira Tanaka, Haruhisa Takahashi,
231
+ "The worst-case time complexity for generating all maximal
232
+ cliques and computational experiments",
233
+ *Theoretical Computer Science*, Volume 363, Issue 1,
234
+ Computing and Combinatorics,
235
+ 10th Annual International Conference on
236
+ Computing and Combinatorics (COCOON 2004), 25 October 2006, Pages 28--42
237
+ <https://doi.org/10.1016/j.tcs.2006.06.015>
238
+
239
+ .. [3] F. Cazals, C. Karande,
240
+ "A note on the problem of reporting maximal cliques",
241
+ *Theoretical Computer Science*,
242
+ Volume 407, Issues 1--3, 6 November 2008, Pages 564--568,
243
+ <https://doi.org/10.1016/j.tcs.2008.05.010>
244
+
245
+ """
246
+ if len(G) == 0:
247
+ return
248
+
249
+ adj = {u: {v for v in G[u] if v != u} for u in G}
250
+
251
+ # Initialize Q with the given nodes and subg, cand with their nbrs
252
+ Q = nodes[:] if nodes is not None else []
253
+ cand = set(G)
254
+ for node in Q:
255
+ if node not in cand:
256
+ raise ValueError(f"The given `nodes` {nodes} do not form a clique")
257
+ cand &= adj[node]
258
+
259
+ if not cand:
260
+ yield Q[:]
261
+ return
262
+
263
+ subg = cand.copy()
264
+ stack = []
265
+ Q.append(None)
266
+
267
+ u = max(subg, key=lambda u: len(cand & adj[u]))
268
+ ext_u = cand - adj[u]
269
+
270
+ try:
271
+ while True:
272
+ if ext_u:
273
+ q = ext_u.pop()
274
+ cand.remove(q)
275
+ Q[-1] = q
276
+ adj_q = adj[q]
277
+ subg_q = subg & adj_q
278
+ if not subg_q:
279
+ yield Q[:]
280
+ else:
281
+ cand_q = cand & adj_q
282
+ if cand_q:
283
+ stack.append((subg, cand, ext_u))
284
+ Q.append(None)
285
+ subg = subg_q
286
+ cand = cand_q
287
+ u = max(subg, key=lambda u: len(cand & adj[u]))
288
+ ext_u = cand - adj[u]
289
+ else:
290
+ Q.pop()
291
+ subg, cand, ext_u = stack.pop()
292
+ except IndexError:
293
+ pass
294
+
295
+
296
+ # TODO Should this also be not implemented for directed graphs?
297
+ @nx._dispatch
298
+ def find_cliques_recursive(G, nodes=None):
299
+ """Returns all maximal cliques in a graph.
300
+
301
+ For each node *v*, a *maximal clique for v* is a largest complete
302
+ subgraph containing *v*. The largest maximal clique is sometimes
303
+ called the *maximum clique*.
304
+
305
+ This function returns an iterator over cliques, each of which is a
306
+ list of nodes. It is a recursive implementation, so may suffer from
307
+ recursion depth issues, but is included for pedagogical reasons.
308
+ For a non-recursive implementation, see :func:`find_cliques`.
309
+
310
+ This function accepts a list of `nodes` and only the maximal cliques
311
+ containing all of these `nodes` are returned. It can considerably speed up
312
+ the running time if some specific cliques are desired.
313
+
314
+ Parameters
315
+ ----------
316
+ G : NetworkX graph
317
+
318
+ nodes : list, optional (default=None)
319
+ If provided, only yield *maximal cliques* containing all nodes in `nodes`.
320
+ If `nodes` isn't a clique itself, a ValueError is raised.
321
+
322
+ Returns
323
+ -------
324
+ iterator
325
+ An iterator over maximal cliques, each of which is a list of
326
+ nodes in `G`. If `nodes` is provided, only the maximal cliques
327
+ containing all the nodes in `nodes` are yielded. The order of
328
+ cliques is arbitrary.
329
+
330
+ Raises
331
+ ------
332
+ ValueError
333
+ If `nodes` is not a clique.
334
+
335
+ See Also
336
+ --------
337
+ find_cliques
338
+ An iterative version of the same algorithm. See docstring for examples.
339
+
340
+ Notes
341
+ -----
342
+ To obtain a list of all maximal cliques, use
343
+ `list(find_cliques_recursive(G))`. However, be aware that in the
344
+ worst-case, the length of this list can be exponential in the number
345
+ of nodes in the graph. This function avoids storing all cliques in memory
346
+ by only keeping current candidate node lists in memory during its search.
347
+
348
+ This implementation is based on the algorithm published by Bron and
349
+ Kerbosch (1973) [1]_, as adapted by Tomita, Tanaka and Takahashi
350
+ (2006) [2]_ and discussed in Cazals and Karande (2008) [3]_. For a
351
+ non-recursive implementation, see :func:`find_cliques`.
352
+
353
+ This algorithm ignores self-loops and parallel edges, since cliques
354
+ are not conventionally defined with such edges.
355
+
356
+ References
357
+ ----------
358
+ .. [1] Bron, C. and Kerbosch, J.
359
+ "Algorithm 457: finding all cliques of an undirected graph".
360
+ *Communications of the ACM* 16, 9 (Sep. 1973), 575--577.
361
+ <http://portal.acm.org/citation.cfm?doid=362342.362367>
362
+
363
+ .. [2] Etsuji Tomita, Akira Tanaka, Haruhisa Takahashi,
364
+ "The worst-case time complexity for generating all maximal
365
+ cliques and computational experiments",
366
+ *Theoretical Computer Science*, Volume 363, Issue 1,
367
+ Computing and Combinatorics,
368
+ 10th Annual International Conference on
369
+ Computing and Combinatorics (COCOON 2004), 25 October 2006, Pages 28--42
370
+ <https://doi.org/10.1016/j.tcs.2006.06.015>
371
+
372
+ .. [3] F. Cazals, C. Karande,
373
+ "A note on the problem of reporting maximal cliques",
374
+ *Theoretical Computer Science*,
375
+ Volume 407, Issues 1--3, 6 November 2008, Pages 564--568,
376
+ <https://doi.org/10.1016/j.tcs.2008.05.010>
377
+
378
+ """
379
+ if len(G) == 0:
380
+ return iter([])
381
+
382
+ adj = {u: {v for v in G[u] if v != u} for u in G}
383
+
384
+ # Initialize Q with the given nodes and subg, cand with their nbrs
385
+ Q = nodes[:] if nodes is not None else []
386
+ cand_init = set(G)
387
+ for node in Q:
388
+ if node not in cand_init:
389
+ raise ValueError(f"The given `nodes` {nodes} do not form a clique")
390
+ cand_init &= adj[node]
391
+
392
+ if not cand_init:
393
+ return iter([Q])
394
+
395
+ subg_init = cand_init.copy()
396
+
397
+ def expand(subg, cand):
398
+ u = max(subg, key=lambda u: len(cand & adj[u]))
399
+ for q in cand - adj[u]:
400
+ cand.remove(q)
401
+ Q.append(q)
402
+ adj_q = adj[q]
403
+ subg_q = subg & adj_q
404
+ if not subg_q:
405
+ yield Q[:]
406
+ else:
407
+ cand_q = cand & adj_q
408
+ if cand_q:
409
+ yield from expand(subg_q, cand_q)
410
+ Q.pop()
411
+
412
+ return expand(subg_init, cand_init)
413
+
414
+
415
+ @nx._dispatch
416
+ def make_max_clique_graph(G, create_using=None):
417
+ """Returns the maximal clique graph of the given graph.
418
+
419
+ The nodes of the maximal clique graph of `G` are the cliques of
420
+ `G` and an edge joins two cliques if the cliques are not disjoint.
421
+
422
+ Parameters
423
+ ----------
424
+ G : NetworkX graph
425
+
426
+ create_using : NetworkX graph constructor, optional (default=nx.Graph)
427
+ Graph type to create. If graph instance, then cleared before populated.
428
+
429
+ Returns
430
+ -------
431
+ NetworkX graph
432
+ A graph whose nodes are the cliques of `G` and whose edges
433
+ join two cliques if they are not disjoint.
434
+
435
+ Notes
436
+ -----
437
+ This function behaves like the following code::
438
+
439
+ import networkx as nx
440
+ G = nx.make_clique_bipartite(G)
441
+ cliques = [v for v in G.nodes() if G.nodes[v]['bipartite'] == 0]
442
+ G = nx.bipartite.projected_graph(G, cliques)
443
+ G = nx.relabel_nodes(G, {-v: v - 1 for v in G})
444
+
445
+ It should be faster, though, since it skips all the intermediate
446
+ steps.
447
+
448
+ """
449
+ if create_using is None:
450
+ B = G.__class__()
451
+ else:
452
+ B = nx.empty_graph(0, create_using)
453
+ cliques = list(enumerate(set(c) for c in find_cliques(G)))
454
+ # Add a numbered node for each clique.
455
+ B.add_nodes_from(i for i, c in cliques)
456
+ # Join cliques by an edge if they share a node.
457
+ clique_pairs = combinations(cliques, 2)
458
+ B.add_edges_from((i, j) for (i, c1), (j, c2) in clique_pairs if c1 & c2)
459
+ return B
460
+
461
+
462
+ @nx._dispatch
463
+ def make_clique_bipartite(G, fpos=None, create_using=None, name=None):
464
+ """Returns the bipartite clique graph corresponding to `G`.
465
+
466
+ In the returned bipartite graph, the "bottom" nodes are the nodes of
467
+ `G` and the "top" nodes represent the maximal cliques of `G`.
468
+ There is an edge from node *v* to clique *C* in the returned graph
469
+ if and only if *v* is an element of *C*.
470
+
471
+ Parameters
472
+ ----------
473
+ G : NetworkX graph
474
+ An undirected graph.
475
+
476
+ fpos : bool
477
+ If True or not None, the returned graph will have an
478
+ additional attribute, `pos`, a dictionary mapping node to
479
+ position in the Euclidean plane.
480
+
481
+ create_using : NetworkX graph constructor, optional (default=nx.Graph)
482
+ Graph type to create. If graph instance, then cleared before populated.
483
+
484
+ Returns
485
+ -------
486
+ NetworkX graph
487
+ A bipartite graph whose "bottom" set is the nodes of the graph
488
+ `G`, whose "top" set is the cliques of `G`, and whose edges
489
+ join nodes of `G` to the cliques that contain them.
490
+
491
+ The nodes of the graph `G` have the node attribute
492
+ 'bipartite' set to 1 and the nodes representing cliques
493
+ have the node attribute 'bipartite' set to 0, as is the
494
+ convention for bipartite graphs in NetworkX.
495
+
496
+ """
497
+ B = nx.empty_graph(0, create_using)
498
+ B.clear()
499
+ # The "bottom" nodes in the bipartite graph are the nodes of the
500
+ # original graph, G.
501
+ B.add_nodes_from(G, bipartite=1)
502
+ for i, cl in enumerate(find_cliques(G)):
503
+ # The "top" nodes in the bipartite graph are the cliques. These
504
+ # nodes get negative numbers as labels.
505
+ name = -i - 1
506
+ B.add_node(name, bipartite=0)
507
+ B.add_edges_from((v, name) for v in cl)
508
+ return B
509
+
510
+
511
+ @nx._dispatch
512
+ def node_clique_number(G, nodes=None, cliques=None, separate_nodes=False):
513
+ """Returns the size of the largest maximal clique containing each given node.
514
+
515
+ Returns a single or list depending on input nodes.
516
+ An optional list of cliques can be input if already computed.
517
+
518
+ Parameters
519
+ ----------
520
+ G : NetworkX graph
521
+ An undirected graph.
522
+
523
+ cliques : list, optional (default=None)
524
+ A list of cliques, each of which is itself a list of nodes.
525
+ If not specified, the list of all cliques will be computed
526
+ using :func:`find_cliques`.
527
+
528
+ Returns
529
+ -------
530
+ int or dict
531
+ If `nodes` is a single node, returns the size of the
532
+ largest maximal clique in `G` containing that node.
533
+ Otherwise return a dict keyed by node to the size
534
+ of the largest maximal clique containing that node.
535
+
536
+ See Also
537
+ --------
538
+ find_cliques
539
+ find_cliques yields the maximal cliques of G.
540
+ It accepts a `nodes` argument which restricts consideration to
541
+ maximal cliques containing all the given `nodes`.
542
+ The search for the cliques is optimized for `nodes`.
543
+ """
544
+ if cliques is None:
545
+ if nodes is not None:
546
+ # Use ego_graph to decrease size of graph
547
+ # check for single node
548
+ if nodes in G:
549
+ return max(len(c) for c in find_cliques(nx.ego_graph(G, nodes)))
550
+ # handle multiple nodes
551
+ return {
552
+ n: max(len(c) for c in find_cliques(nx.ego_graph(G, n))) for n in nodes
553
+ }
554
+
555
+ # nodes is None--find all cliques
556
+ cliques = list(find_cliques(G))
557
+
558
+ # single node requested
559
+ if nodes in G:
560
+ return max(len(c) for c in cliques if nodes in c)
561
+
562
+ # multiple nodes requested
563
+ # preprocess all nodes (faster than one at a time for even 2 nodes)
564
+ size_for_n = defaultdict(int)
565
+ for c in cliques:
566
+ size_of_c = len(c)
567
+ for n in c:
568
+ if size_for_n[n] < size_of_c:
569
+ size_for_n[n] = size_of_c
570
+ if nodes is None:
571
+ return size_for_n
572
+ return {n: size_for_n[n] for n in nodes}
573
+
574
+
575
+ def number_of_cliques(G, nodes=None, cliques=None):
576
+ """Returns the number of maximal cliques for each node.
577
+
578
+ Returns a single or list depending on input nodes.
579
+ Optional list of cliques can be input if already computed.
580
+ """
581
+ if cliques is None:
582
+ cliques = list(find_cliques(G))
583
+
584
+ if nodes is None:
585
+ nodes = list(G.nodes()) # none, get entire graph
586
+
587
+ if not isinstance(nodes, list): # check for a list
588
+ v = nodes
589
+ # assume it is a single value
590
+ numcliq = len([1 for c in cliques if v in c])
591
+ else:
592
+ numcliq = {}
593
+ for v in nodes:
594
+ numcliq[v] = len([1 for c in cliques if v in c])
595
+ return numcliq
596
+
597
+
598
+ class MaxWeightClique:
599
+ """A class for the maximum weight clique algorithm.
600
+
601
+ This class is a helper for the `max_weight_clique` function. The class
602
+ should not normally be used directly.
603
+
604
+ Parameters
605
+ ----------
606
+ G : NetworkX graph
607
+ The undirected graph for which a maximum weight clique is sought
608
+ weight : string or None, optional (default='weight')
609
+ The node attribute that holds the integer value used as a weight.
610
+ If None, then each node has weight 1.
611
+
612
+ Attributes
613
+ ----------
614
+ G : NetworkX graph
615
+ The undirected graph for which a maximum weight clique is sought
616
+ node_weights: dict
617
+ The weight of each node
618
+ incumbent_nodes : list
619
+ The nodes of the incumbent clique (the best clique found so far)
620
+ incumbent_weight: int
621
+ The weight of the incumbent clique
622
+ """
623
+
624
+ def __init__(self, G, weight):
625
+ self.G = G
626
+ self.incumbent_nodes = []
627
+ self.incumbent_weight = 0
628
+
629
+ if weight is None:
630
+ self.node_weights = {v: 1 for v in G.nodes()}
631
+ else:
632
+ for v in G.nodes():
633
+ if weight not in G.nodes[v]:
634
+ errmsg = f"Node {v!r} does not have the requested weight field."
635
+ raise KeyError(errmsg)
636
+ if not isinstance(G.nodes[v][weight], int):
637
+ errmsg = f"The {weight!r} field of node {v!r} is not an integer."
638
+ raise ValueError(errmsg)
639
+ self.node_weights = {v: G.nodes[v][weight] for v in G.nodes()}
640
+
641
+ def update_incumbent_if_improved(self, C, C_weight):
642
+ """Update the incumbent if the node set C has greater weight.
643
+
644
+ C is assumed to be a clique.
645
+ """
646
+ if C_weight > self.incumbent_weight:
647
+ self.incumbent_nodes = C[:]
648
+ self.incumbent_weight = C_weight
649
+
650
+ def greedily_find_independent_set(self, P):
651
+ """Greedily find an independent set of nodes from a set of
652
+ nodes P."""
653
+ independent_set = []
654
+ P = P[:]
655
+ while P:
656
+ v = P[0]
657
+ independent_set.append(v)
658
+ P = [w for w in P if v != w and not self.G.has_edge(v, w)]
659
+ return independent_set
660
+
661
+ def find_branching_nodes(self, P, target):
662
+ """Find a set of nodes to branch on."""
663
+ residual_wt = {v: self.node_weights[v] for v in P}
664
+ total_wt = 0
665
+ P = P[:]
666
+ while P:
667
+ independent_set = self.greedily_find_independent_set(P)
668
+ min_wt_in_class = min(residual_wt[v] for v in independent_set)
669
+ total_wt += min_wt_in_class
670
+ if total_wt > target:
671
+ break
672
+ for v in independent_set:
673
+ residual_wt[v] -= min_wt_in_class
674
+ P = [v for v in P if residual_wt[v] != 0]
675
+ return P
676
+
677
+ def expand(self, C, C_weight, P):
678
+ """Look for the best clique that contains all the nodes in C and zero or
679
+ more of the nodes in P, backtracking if it can be shown that no such
680
+ clique has greater weight than the incumbent.
681
+ """
682
+ self.update_incumbent_if_improved(C, C_weight)
683
+ branching_nodes = self.find_branching_nodes(P, self.incumbent_weight - C_weight)
684
+ while branching_nodes:
685
+ v = branching_nodes.pop()
686
+ P.remove(v)
687
+ new_C = C + [v]
688
+ new_C_weight = C_weight + self.node_weights[v]
689
+ new_P = [w for w in P if self.G.has_edge(v, w)]
690
+ self.expand(new_C, new_C_weight, new_P)
691
+
692
+ def find_max_weight_clique(self):
693
+ """Find a maximum weight clique."""
694
+ # Sort nodes in reverse order of degree for speed
695
+ nodes = sorted(self.G.nodes(), key=lambda v: self.G.degree(v), reverse=True)
696
+ nodes = [v for v in nodes if self.node_weights[v] > 0]
697
+ self.expand([], 0, nodes)
698
+
699
+
700
+ @not_implemented_for("directed")
701
+ @nx._dispatch(node_attrs="weight")
702
+ def max_weight_clique(G, weight="weight"):
703
+ """Find a maximum weight clique in G.
704
+
705
+ A *clique* in a graph is a set of nodes such that every two distinct nodes
706
+ are adjacent. The *weight* of a clique is the sum of the weights of its
707
+ nodes. A *maximum weight clique* of graph G is a clique C in G such that
708
+ no clique in G has weight greater than the weight of C.
709
+
710
+ Parameters
711
+ ----------
712
+ G : NetworkX graph
713
+ Undirected graph
714
+ weight : string or None, optional (default='weight')
715
+ The node attribute that holds the integer value used as a weight.
716
+ If None, then each node has weight 1.
717
+
718
+ Returns
719
+ -------
720
+ clique : list
721
+ the nodes of a maximum weight clique
722
+ weight : int
723
+ the weight of a maximum weight clique
724
+
725
+ Notes
726
+ -----
727
+ The implementation is recursive, and therefore it may run into recursion
728
+ depth issues if G contains a clique whose number of nodes is close to the
729
+ recursion depth limit.
730
+
731
+ At each search node, the algorithm greedily constructs a weighted
732
+ independent set cover of part of the graph in order to find a small set of
733
+ nodes on which to branch. The algorithm is very similar to the algorithm
734
+ of Tavares et al. [1]_, other than the fact that the NetworkX version does
735
+ not use bitsets. This style of algorithm for maximum weight clique (and
736
+ maximum weight independent set, which is the same problem but on the
737
+ complement graph) has a decades-long history. See Algorithm B of Warren
738
+ and Hicks [2]_ and the references in that paper.
739
+
740
+ References
741
+ ----------
742
+ .. [1] Tavares, W.A., Neto, M.B.C., Rodrigues, C.D., Michelon, P.: Um
743
+ algoritmo de branch and bound para o problema da clique máxima
744
+ ponderada. Proceedings of XLVII SBPO 1 (2015).
745
+
746
+ .. [2] Warren, Jeffrey S, Hicks, Illya V.: Combinatorial Branch-and-Bound
747
+ for the Maximum Weight Independent Set Problem. Technical Report,
748
+ Texas A&M University (2016).
749
+ """
750
+
751
+ mwc = MaxWeightClique(G, weight)
752
+ mwc.find_max_weight_clique()
753
+ return mwc.incumbent_nodes, mwc.incumbent_weight
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/communicability_alg.py ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Communicability.
3
+ """
4
+ import networkx as nx
5
+ from networkx.utils import not_implemented_for
6
+
7
+ __all__ = ["communicability", "communicability_exp"]
8
+
9
+
10
+ @not_implemented_for("directed")
11
+ @not_implemented_for("multigraph")
12
+ @nx._dispatch
13
+ def communicability(G):
14
+ r"""Returns communicability between all pairs of nodes in G.
15
+
16
+ The communicability between pairs of nodes in G is the sum of
17
+ walks of different lengths starting at node u and ending at node v.
18
+
19
+ Parameters
20
+ ----------
21
+ G: graph
22
+
23
+ Returns
24
+ -------
25
+ comm: dictionary of dictionaries
26
+ Dictionary of dictionaries keyed by nodes with communicability
27
+ as the value.
28
+
29
+ Raises
30
+ ------
31
+ NetworkXError
32
+ If the graph is not undirected and simple.
33
+
34
+ See Also
35
+ --------
36
+ communicability_exp:
37
+ Communicability between all pairs of nodes in G using spectral
38
+ decomposition.
39
+ communicability_betweenness_centrality:
40
+ Communicability betweenness centrality for each node in G.
41
+
42
+ Notes
43
+ -----
44
+ This algorithm uses a spectral decomposition of the adjacency matrix.
45
+ Let G=(V,E) be a simple undirected graph. Using the connection between
46
+ the powers of the adjacency matrix and the number of walks in the graph,
47
+ the communicability between nodes `u` and `v` based on the graph spectrum
48
+ is [1]_
49
+
50
+ .. math::
51
+ C(u,v)=\sum_{j=1}^{n}\phi_{j}(u)\phi_{j}(v)e^{\lambda_{j}},
52
+
53
+ where `\phi_{j}(u)` is the `u\rm{th}` element of the `j\rm{th}` orthonormal
54
+ eigenvector of the adjacency matrix associated with the eigenvalue
55
+ `\lambda_{j}`.
56
+
57
+ References
58
+ ----------
59
+ .. [1] Ernesto Estrada, Naomichi Hatano,
60
+ "Communicability in complex networks",
61
+ Phys. Rev. E 77, 036111 (2008).
62
+ https://arxiv.org/abs/0707.0756
63
+
64
+ Examples
65
+ --------
66
+ >>> G = nx.Graph([(0, 1), (1, 2), (1, 5), (5, 4), (2, 4), (2, 3), (4, 3), (3, 6)])
67
+ >>> c = nx.communicability(G)
68
+ """
69
+ import numpy as np
70
+
71
+ nodelist = list(G) # ordering of nodes in matrix
72
+ A = nx.to_numpy_array(G, nodelist)
73
+ # convert to 0-1 matrix
74
+ A[A != 0.0] = 1
75
+ w, vec = np.linalg.eigh(A)
76
+ expw = np.exp(w)
77
+ mapping = dict(zip(nodelist, range(len(nodelist))))
78
+ c = {}
79
+ # computing communicabilities
80
+ for u in G:
81
+ c[u] = {}
82
+ for v in G:
83
+ s = 0
84
+ p = mapping[u]
85
+ q = mapping[v]
86
+ for j in range(len(nodelist)):
87
+ s += vec[:, j][p] * vec[:, j][q] * expw[j]
88
+ c[u][v] = float(s)
89
+ return c
90
+
91
+
92
+ @not_implemented_for("directed")
93
+ @not_implemented_for("multigraph")
94
+ @nx._dispatch
95
+ def communicability_exp(G):
96
+ r"""Returns communicability between all pairs of nodes in G.
97
+
98
+ Communicability between pair of node (u,v) of node in G is the sum of
99
+ walks of different lengths starting at node u and ending at node v.
100
+
101
+ Parameters
102
+ ----------
103
+ G: graph
104
+
105
+ Returns
106
+ -------
107
+ comm: dictionary of dictionaries
108
+ Dictionary of dictionaries keyed by nodes with communicability
109
+ as the value.
110
+
111
+ Raises
112
+ ------
113
+ NetworkXError
114
+ If the graph is not undirected and simple.
115
+
116
+ See Also
117
+ --------
118
+ communicability:
119
+ Communicability between pairs of nodes in G.
120
+ communicability_betweenness_centrality:
121
+ Communicability betweenness centrality for each node in G.
122
+
123
+ Notes
124
+ -----
125
+ This algorithm uses matrix exponentiation of the adjacency matrix.
126
+
127
+ Let G=(V,E) be a simple undirected graph. Using the connection between
128
+ the powers of the adjacency matrix and the number of walks in the graph,
129
+ the communicability between nodes u and v is [1]_,
130
+
131
+ .. math::
132
+ C(u,v) = (e^A)_{uv},
133
+
134
+ where `A` is the adjacency matrix of G.
135
+
136
+ References
137
+ ----------
138
+ .. [1] Ernesto Estrada, Naomichi Hatano,
139
+ "Communicability in complex networks",
140
+ Phys. Rev. E 77, 036111 (2008).
141
+ https://arxiv.org/abs/0707.0756
142
+
143
+ Examples
144
+ --------
145
+ >>> G = nx.Graph([(0, 1), (1, 2), (1, 5), (5, 4), (2, 4), (2, 3), (4, 3), (3, 6)])
146
+ >>> c = nx.communicability_exp(G)
147
+ """
148
+ import scipy as sp
149
+
150
+ nodelist = list(G) # ordering of nodes in matrix
151
+ A = nx.to_numpy_array(G, nodelist)
152
+ # convert to 0-1 matrix
153
+ A[A != 0.0] = 1
154
+ # communicability matrix
155
+ expA = sp.linalg.expm(A)
156
+ mapping = dict(zip(nodelist, range(len(nodelist))))
157
+ c = {}
158
+ for u in G:
159
+ c[u] = {}
160
+ for v in G:
161
+ c[u][v] = float(expA[mapping[u], mapping[v]])
162
+ return c
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/covering.py ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """ Functions related to graph covers."""
2
+
3
+ from functools import partial
4
+ from itertools import chain
5
+
6
+ import networkx as nx
7
+ from networkx.utils import arbitrary_element, not_implemented_for
8
+
9
+ __all__ = ["min_edge_cover", "is_edge_cover"]
10
+
11
+
12
+ @not_implemented_for("directed")
13
+ @not_implemented_for("multigraph")
14
+ @nx._dispatch
15
+ def min_edge_cover(G, matching_algorithm=None):
16
+ """Returns the min cardinality edge cover of the graph as a set of edges.
17
+
18
+ A smallest edge cover can be found in polynomial time by finding
19
+ a maximum matching and extending it greedily so that all nodes
20
+ are covered. This function follows that process. A maximum matching
21
+ algorithm can be specified for the first step of the algorithm.
22
+ The resulting set may return a set with one 2-tuple for each edge,
23
+ (the usual case) or with both 2-tuples `(u, v)` and `(v, u)` for
24
+ each edge. The latter is only done when a bipartite matching algorithm
25
+ is specified as `matching_algorithm`.
26
+
27
+ Parameters
28
+ ----------
29
+ G : NetworkX graph
30
+ An undirected graph.
31
+
32
+ matching_algorithm : function
33
+ A function that returns a maximum cardinality matching for `G`.
34
+ The function must take one input, the graph `G`, and return
35
+ either a set of edges (with only one direction for the pair of nodes)
36
+ or a dictionary mapping each node to its mate. If not specified,
37
+ :func:`~networkx.algorithms.matching.max_weight_matching` is used.
38
+ Common bipartite matching functions include
39
+ :func:`~networkx.algorithms.bipartite.matching.hopcroft_karp_matching`
40
+ or
41
+ :func:`~networkx.algorithms.bipartite.matching.eppstein_matching`.
42
+
43
+ Returns
44
+ -------
45
+ min_cover : set
46
+
47
+ A set of the edges in a minimum edge cover in the form of tuples.
48
+ It contains only one of the equivalent 2-tuples `(u, v)` and `(v, u)`
49
+ for each edge. If a bipartite method is used to compute the matching,
50
+ the returned set contains both the 2-tuples `(u, v)` and `(v, u)`
51
+ for each edge of a minimum edge cover.
52
+
53
+ Examples
54
+ --------
55
+ >>> G = nx.Graph([(0, 1), (0, 2), (0, 3), (1, 2), (1, 3)])
56
+ >>> sorted(nx.min_edge_cover(G))
57
+ [(2, 1), (3, 0)]
58
+
59
+ Notes
60
+ -----
61
+ An edge cover of a graph is a set of edges such that every node of
62
+ the graph is incident to at least one edge of the set.
63
+ The minimum edge cover is an edge covering of smallest cardinality.
64
+
65
+ Due to its implementation, the worst-case running time of this algorithm
66
+ is bounded by the worst-case running time of the function
67
+ ``matching_algorithm``.
68
+
69
+ Minimum edge cover for `G` can also be found using the `min_edge_covering`
70
+ function in :mod:`networkx.algorithms.bipartite.covering` which is
71
+ simply this function with a default matching algorithm of
72
+ :func:`~networkx.algorithms.bipartite.matching.hopcraft_karp_matching`
73
+ """
74
+ if len(G) == 0:
75
+ return set()
76
+ if nx.number_of_isolates(G) > 0:
77
+ # ``min_cover`` does not exist as there is an isolated node
78
+ raise nx.NetworkXException(
79
+ "Graph has a node with no edge incident on it, " "so no edge cover exists."
80
+ )
81
+ if matching_algorithm is None:
82
+ matching_algorithm = partial(nx.max_weight_matching, maxcardinality=True)
83
+ maximum_matching = matching_algorithm(G)
84
+ # ``min_cover`` is superset of ``maximum_matching``
85
+ try:
86
+ # bipartite matching algs return dict so convert if needed
87
+ min_cover = set(maximum_matching.items())
88
+ bipartite_cover = True
89
+ except AttributeError:
90
+ min_cover = maximum_matching
91
+ bipartite_cover = False
92
+ # iterate for uncovered nodes
93
+ uncovered_nodes = set(G) - {v for u, v in min_cover} - {u for u, v in min_cover}
94
+ for v in uncovered_nodes:
95
+ # Since `v` is uncovered, each edge incident to `v` will join it
96
+ # with a covered node (otherwise, if there were an edge joining
97
+ # uncovered nodes `u` and `v`, the maximum matching algorithm
98
+ # would have found it), so we can choose an arbitrary edge
99
+ # incident to `v`. (This applies only in a simple graph, not a
100
+ # multigraph.)
101
+ u = arbitrary_element(G[v])
102
+ min_cover.add((u, v))
103
+ if bipartite_cover:
104
+ min_cover.add((v, u))
105
+ return min_cover
106
+
107
+
108
+ @not_implemented_for("directed")
109
+ @nx._dispatch
110
+ def is_edge_cover(G, cover):
111
+ """Decides whether a set of edges is a valid edge cover of the graph.
112
+
113
+ Given a set of edges, whether it is an edge covering can
114
+ be decided if we just check whether all nodes of the graph
115
+ has an edge from the set, incident on it.
116
+
117
+ Parameters
118
+ ----------
119
+ G : NetworkX graph
120
+ An undirected bipartite graph.
121
+
122
+ cover : set
123
+ Set of edges to be checked.
124
+
125
+ Returns
126
+ -------
127
+ bool
128
+ Whether the set of edges is a valid edge cover of the graph.
129
+
130
+ Examples
131
+ --------
132
+ >>> G = nx.Graph([(0, 1), (0, 2), (0, 3), (1, 2), (1, 3)])
133
+ >>> cover = {(2, 1), (3, 0)}
134
+ >>> nx.is_edge_cover(G, cover)
135
+ True
136
+
137
+ Notes
138
+ -----
139
+ An edge cover of a graph is a set of edges such that every node of
140
+ the graph is incident to at least one edge of the set.
141
+ """
142
+ return set(G) <= set(chain.from_iterable(cover))
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/cuts.py ADDED
@@ -0,0 +1,400 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Functions for finding and evaluating cuts in a graph.
2
+
3
+ """
4
+
5
+ from itertools import chain
6
+
7
+ import networkx as nx
8
+
9
+ __all__ = [
10
+ "boundary_expansion",
11
+ "conductance",
12
+ "cut_size",
13
+ "edge_expansion",
14
+ "mixing_expansion",
15
+ "node_expansion",
16
+ "normalized_cut_size",
17
+ "volume",
18
+ ]
19
+
20
+
21
+ # TODO STILL NEED TO UPDATE ALL THE DOCUMENTATION!
22
+
23
+
24
+ @nx._dispatch(edge_attrs="weight")
25
+ def cut_size(G, S, T=None, weight=None):
26
+ """Returns the size of the cut between two sets of nodes.
27
+
28
+ A *cut* is a partition of the nodes of a graph into two sets. The
29
+ *cut size* is the sum of the weights of the edges "between" the two
30
+ sets of nodes.
31
+
32
+ Parameters
33
+ ----------
34
+ G : NetworkX graph
35
+
36
+ S : collection
37
+ A collection of nodes in `G`.
38
+
39
+ T : collection
40
+ A collection of nodes in `G`. If not specified, this is taken to
41
+ be the set complement of `S`.
42
+
43
+ weight : object
44
+ Edge attribute key to use as weight. If not specified, edges
45
+ have weight one.
46
+
47
+ Returns
48
+ -------
49
+ number
50
+ Total weight of all edges from nodes in set `S` to nodes in
51
+ set `T` (and, in the case of directed graphs, all edges from
52
+ nodes in `T` to nodes in `S`).
53
+
54
+ Examples
55
+ --------
56
+ In the graph with two cliques joined by a single edges, the natural
57
+ bipartition of the graph into two blocks, one for each clique,
58
+ yields a cut of weight one::
59
+
60
+ >>> G = nx.barbell_graph(3, 0)
61
+ >>> S = {0, 1, 2}
62
+ >>> T = {3, 4, 5}
63
+ >>> nx.cut_size(G, S, T)
64
+ 1
65
+
66
+ Each parallel edge in a multigraph is counted when determining the
67
+ cut size::
68
+
69
+ >>> G = nx.MultiGraph(["ab", "ab"])
70
+ >>> S = {"a"}
71
+ >>> T = {"b"}
72
+ >>> nx.cut_size(G, S, T)
73
+ 2
74
+
75
+ Notes
76
+ -----
77
+ In a multigraph, the cut size is the total weight of edges including
78
+ multiplicity.
79
+
80
+ """
81
+ edges = nx.edge_boundary(G, S, T, data=weight, default=1)
82
+ if G.is_directed():
83
+ edges = chain(edges, nx.edge_boundary(G, T, S, data=weight, default=1))
84
+ return sum(weight for u, v, weight in edges)
85
+
86
+
87
+ @nx._dispatch(edge_attrs="weight")
88
+ def volume(G, S, weight=None):
89
+ """Returns the volume of a set of nodes.
90
+
91
+ The *volume* of a set *S* is the sum of the (out-)degrees of nodes
92
+ in *S* (taking into account parallel edges in multigraphs). [1]
93
+
94
+ Parameters
95
+ ----------
96
+ G : NetworkX graph
97
+
98
+ S : collection
99
+ A collection of nodes in `G`.
100
+
101
+ weight : object
102
+ Edge attribute key to use as weight. If not specified, edges
103
+ have weight one.
104
+
105
+ Returns
106
+ -------
107
+ number
108
+ The volume of the set of nodes represented by `S` in the graph
109
+ `G`.
110
+
111
+ See also
112
+ --------
113
+ conductance
114
+ cut_size
115
+ edge_expansion
116
+ edge_boundary
117
+ normalized_cut_size
118
+
119
+ References
120
+ ----------
121
+ .. [1] David Gleich.
122
+ *Hierarchical Directed Spectral Graph Partitioning*.
123
+ <https://www.cs.purdue.edu/homes/dgleich/publications/Gleich%202005%20-%20hierarchical%20directed%20spectral.pdf>
124
+
125
+ """
126
+ degree = G.out_degree if G.is_directed() else G.degree
127
+ return sum(d for v, d in degree(S, weight=weight))
128
+
129
+
130
+ @nx._dispatch(edge_attrs="weight")
131
+ def normalized_cut_size(G, S, T=None, weight=None):
132
+ """Returns the normalized size of the cut between two sets of nodes.
133
+
134
+ The *normalized cut size* is the cut size times the sum of the
135
+ reciprocal sizes of the volumes of the two sets. [1]
136
+
137
+ Parameters
138
+ ----------
139
+ G : NetworkX graph
140
+
141
+ S : collection
142
+ A collection of nodes in `G`.
143
+
144
+ T : collection
145
+ A collection of nodes in `G`.
146
+
147
+ weight : object
148
+ Edge attribute key to use as weight. If not specified, edges
149
+ have weight one.
150
+
151
+ Returns
152
+ -------
153
+ number
154
+ The normalized cut size between the two sets `S` and `T`.
155
+
156
+ Notes
157
+ -----
158
+ In a multigraph, the cut size is the total weight of edges including
159
+ multiplicity.
160
+
161
+ See also
162
+ --------
163
+ conductance
164
+ cut_size
165
+ edge_expansion
166
+ volume
167
+
168
+ References
169
+ ----------
170
+ .. [1] David Gleich.
171
+ *Hierarchical Directed Spectral Graph Partitioning*.
172
+ <https://www.cs.purdue.edu/homes/dgleich/publications/Gleich%202005%20-%20hierarchical%20directed%20spectral.pdf>
173
+
174
+ """
175
+ if T is None:
176
+ T = set(G) - set(S)
177
+ num_cut_edges = cut_size(G, S, T=T, weight=weight)
178
+ volume_S = volume(G, S, weight=weight)
179
+ volume_T = volume(G, T, weight=weight)
180
+ return num_cut_edges * ((1 / volume_S) + (1 / volume_T))
181
+
182
+
183
+ @nx._dispatch(edge_attrs="weight")
184
+ def conductance(G, S, T=None, weight=None):
185
+ """Returns the conductance of two sets of nodes.
186
+
187
+ The *conductance* is the quotient of the cut size and the smaller of
188
+ the volumes of the two sets. [1]
189
+
190
+ Parameters
191
+ ----------
192
+ G : NetworkX graph
193
+
194
+ S : collection
195
+ A collection of nodes in `G`.
196
+
197
+ T : collection
198
+ A collection of nodes in `G`.
199
+
200
+ weight : object
201
+ Edge attribute key to use as weight. If not specified, edges
202
+ have weight one.
203
+
204
+ Returns
205
+ -------
206
+ number
207
+ The conductance between the two sets `S` and `T`.
208
+
209
+ See also
210
+ --------
211
+ cut_size
212
+ edge_expansion
213
+ normalized_cut_size
214
+ volume
215
+
216
+ References
217
+ ----------
218
+ .. [1] David Gleich.
219
+ *Hierarchical Directed Spectral Graph Partitioning*.
220
+ <https://www.cs.purdue.edu/homes/dgleich/publications/Gleich%202005%20-%20hierarchical%20directed%20spectral.pdf>
221
+
222
+ """
223
+ if T is None:
224
+ T = set(G) - set(S)
225
+ num_cut_edges = cut_size(G, S, T, weight=weight)
226
+ volume_S = volume(G, S, weight=weight)
227
+ volume_T = volume(G, T, weight=weight)
228
+ return num_cut_edges / min(volume_S, volume_T)
229
+
230
+
231
+ @nx._dispatch(edge_attrs="weight")
232
+ def edge_expansion(G, S, T=None, weight=None):
233
+ """Returns the edge expansion between two node sets.
234
+
235
+ The *edge expansion* is the quotient of the cut size and the smaller
236
+ of the cardinalities of the two sets. [1]
237
+
238
+ Parameters
239
+ ----------
240
+ G : NetworkX graph
241
+
242
+ S : collection
243
+ A collection of nodes in `G`.
244
+
245
+ T : collection
246
+ A collection of nodes in `G`.
247
+
248
+ weight : object
249
+ Edge attribute key to use as weight. If not specified, edges
250
+ have weight one.
251
+
252
+ Returns
253
+ -------
254
+ number
255
+ The edge expansion between the two sets `S` and `T`.
256
+
257
+ See also
258
+ --------
259
+ boundary_expansion
260
+ mixing_expansion
261
+ node_expansion
262
+
263
+ References
264
+ ----------
265
+ .. [1] Fan Chung.
266
+ *Spectral Graph Theory*.
267
+ (CBMS Regional Conference Series in Mathematics, No. 92),
268
+ American Mathematical Society, 1997, ISBN 0-8218-0315-8
269
+ <http://www.math.ucsd.edu/~fan/research/revised.html>
270
+
271
+ """
272
+ if T is None:
273
+ T = set(G) - set(S)
274
+ num_cut_edges = cut_size(G, S, T=T, weight=weight)
275
+ return num_cut_edges / min(len(S), len(T))
276
+
277
+
278
+ @nx._dispatch(edge_attrs="weight")
279
+ def mixing_expansion(G, S, T=None, weight=None):
280
+ """Returns the mixing expansion between two node sets.
281
+
282
+ The *mixing expansion* is the quotient of the cut size and twice the
283
+ number of edges in the graph. [1]
284
+
285
+ Parameters
286
+ ----------
287
+ G : NetworkX graph
288
+
289
+ S : collection
290
+ A collection of nodes in `G`.
291
+
292
+ T : collection
293
+ A collection of nodes in `G`.
294
+
295
+ weight : object
296
+ Edge attribute key to use as weight. If not specified, edges
297
+ have weight one.
298
+
299
+ Returns
300
+ -------
301
+ number
302
+ The mixing expansion between the two sets `S` and `T`.
303
+
304
+ See also
305
+ --------
306
+ boundary_expansion
307
+ edge_expansion
308
+ node_expansion
309
+
310
+ References
311
+ ----------
312
+ .. [1] Vadhan, Salil P.
313
+ "Pseudorandomness."
314
+ *Foundations and Trends
315
+ in Theoretical Computer Science* 7.1–3 (2011): 1–336.
316
+ <https://doi.org/10.1561/0400000010>
317
+
318
+ """
319
+ num_cut_edges = cut_size(G, S, T=T, weight=weight)
320
+ num_total_edges = G.number_of_edges()
321
+ return num_cut_edges / (2 * num_total_edges)
322
+
323
+
324
+ # TODO What is the generalization to two arguments, S and T? Does the
325
+ # denominator become `min(len(S), len(T))`?
326
+ @nx._dispatch
327
+ def node_expansion(G, S):
328
+ """Returns the node expansion of the set `S`.
329
+
330
+ The *node expansion* is the quotient of the size of the node
331
+ boundary of *S* and the cardinality of *S*. [1]
332
+
333
+ Parameters
334
+ ----------
335
+ G : NetworkX graph
336
+
337
+ S : collection
338
+ A collection of nodes in `G`.
339
+
340
+ Returns
341
+ -------
342
+ number
343
+ The node expansion of the set `S`.
344
+
345
+ See also
346
+ --------
347
+ boundary_expansion
348
+ edge_expansion
349
+ mixing_expansion
350
+
351
+ References
352
+ ----------
353
+ .. [1] Vadhan, Salil P.
354
+ "Pseudorandomness."
355
+ *Foundations and Trends
356
+ in Theoretical Computer Science* 7.1–3 (2011): 1–336.
357
+ <https://doi.org/10.1561/0400000010>
358
+
359
+ """
360
+ neighborhood = set(chain.from_iterable(G.neighbors(v) for v in S))
361
+ return len(neighborhood) / len(S)
362
+
363
+
364
+ # TODO What is the generalization to two arguments, S and T? Does the
365
+ # denominator become `min(len(S), len(T))`?
366
+ @nx._dispatch
367
+ def boundary_expansion(G, S):
368
+ """Returns the boundary expansion of the set `S`.
369
+
370
+ The *boundary expansion* is the quotient of the size
371
+ of the node boundary and the cardinality of *S*. [1]
372
+
373
+ Parameters
374
+ ----------
375
+ G : NetworkX graph
376
+
377
+ S : collection
378
+ A collection of nodes in `G`.
379
+
380
+ Returns
381
+ -------
382
+ number
383
+ The boundary expansion of the set `S`.
384
+
385
+ See also
386
+ --------
387
+ edge_expansion
388
+ mixing_expansion
389
+ node_expansion
390
+
391
+ References
392
+ ----------
393
+ .. [1] Vadhan, Salil P.
394
+ "Pseudorandomness."
395
+ *Foundations and Trends in Theoretical Computer Science*
396
+ 7.1–3 (2011): 1–336.
397
+ <https://doi.org/10.1561/0400000010>
398
+
399
+ """
400
+ return len(nx.node_boundary(G, S)) / len(S)
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/hybrid.py ADDED
@@ -0,0 +1,195 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Provides functions for finding and testing for locally `(k, l)`-connected
3
+ graphs.
4
+
5
+ """
6
+ import copy
7
+
8
+ import networkx as nx
9
+
10
+ __all__ = ["kl_connected_subgraph", "is_kl_connected"]
11
+
12
+
13
+ @nx._dispatch
14
+ def kl_connected_subgraph(G, k, l, low_memory=False, same_as_graph=False):
15
+ """Returns the maximum locally `(k, l)`-connected subgraph of `G`.
16
+
17
+ A graph is locally `(k, l)`-connected if for each edge `(u, v)` in the
18
+ graph there are at least `l` edge-disjoint paths of length at most `k`
19
+ joining `u` to `v`.
20
+
21
+ Parameters
22
+ ----------
23
+ G : NetworkX graph
24
+ The graph in which to find a maximum locally `(k, l)`-connected
25
+ subgraph.
26
+
27
+ k : integer
28
+ The maximum length of paths to consider. A higher number means a looser
29
+ connectivity requirement.
30
+
31
+ l : integer
32
+ The number of edge-disjoint paths. A higher number means a stricter
33
+ connectivity requirement.
34
+
35
+ low_memory : bool
36
+ If this is True, this function uses an algorithm that uses slightly
37
+ more time but less memory.
38
+
39
+ same_as_graph : bool
40
+ If True then return a tuple of the form `(H, is_same)`,
41
+ where `H` is the maximum locally `(k, l)`-connected subgraph and
42
+ `is_same` is a Boolean representing whether `G` is locally `(k,
43
+ l)`-connected (and hence, whether `H` is simply a copy of the input
44
+ graph `G`).
45
+
46
+ Returns
47
+ -------
48
+ NetworkX graph or two-tuple
49
+ If `same_as_graph` is True, then this function returns a
50
+ two-tuple as described above. Otherwise, it returns only the maximum
51
+ locally `(k, l)`-connected subgraph.
52
+
53
+ See also
54
+ --------
55
+ is_kl_connected
56
+
57
+ References
58
+ ----------
59
+ .. [1] Chung, Fan and Linyuan Lu. "The Small World Phenomenon in Hybrid
60
+ Power Law Graphs." *Complex Networks*. Springer Berlin Heidelberg,
61
+ 2004. 89--104.
62
+
63
+ """
64
+ H = copy.deepcopy(G) # subgraph we construct by removing from G
65
+
66
+ graphOK = True
67
+ deleted_some = True # hack to start off the while loop
68
+ while deleted_some:
69
+ deleted_some = False
70
+ # We use `for edge in list(H.edges()):` instead of
71
+ # `for edge in H.edges():` because we edit the graph `H` in
72
+ # the loop. Hence using an iterator will result in
73
+ # `RuntimeError: dictionary changed size during iteration`
74
+ for edge in list(H.edges()):
75
+ (u, v) = edge
76
+ # Get copy of graph needed for this search
77
+ if low_memory:
78
+ verts = {u, v}
79
+ for i in range(k):
80
+ for w in verts.copy():
81
+ verts.update(G[w])
82
+ G2 = G.subgraph(verts).copy()
83
+ else:
84
+ G2 = copy.deepcopy(G)
85
+ ###
86
+ path = [u, v]
87
+ cnt = 0
88
+ accept = 0
89
+ while path:
90
+ cnt += 1 # Found a path
91
+ if cnt >= l:
92
+ accept = 1
93
+ break
94
+ # record edges along this graph
95
+ prev = u
96
+ for w in path:
97
+ if prev != w:
98
+ G2.remove_edge(prev, w)
99
+ prev = w
100
+ # path = shortest_path(G2, u, v, k) # ??? should "Cutoff" be k+1?
101
+ try:
102
+ path = nx.shortest_path(G2, u, v) # ??? should "Cutoff" be k+1?
103
+ except nx.NetworkXNoPath:
104
+ path = False
105
+ # No Other Paths
106
+ if accept == 0:
107
+ H.remove_edge(u, v)
108
+ deleted_some = True
109
+ if graphOK:
110
+ graphOK = False
111
+ # We looked through all edges and removed none of them.
112
+ # So, H is the maximal (k,l)-connected subgraph of G
113
+ if same_as_graph:
114
+ return (H, graphOK)
115
+ return H
116
+
117
+
118
+ @nx._dispatch
119
+ def is_kl_connected(G, k, l, low_memory=False):
120
+ """Returns True if and only if `G` is locally `(k, l)`-connected.
121
+
122
+ A graph is locally `(k, l)`-connected if for each edge `(u, v)` in the
123
+ graph there are at least `l` edge-disjoint paths of length at most `k`
124
+ joining `u` to `v`.
125
+
126
+ Parameters
127
+ ----------
128
+ G : NetworkX graph
129
+ The graph to test for local `(k, l)`-connectedness.
130
+
131
+ k : integer
132
+ The maximum length of paths to consider. A higher number means a looser
133
+ connectivity requirement.
134
+
135
+ l : integer
136
+ The number of edge-disjoint paths. A higher number means a stricter
137
+ connectivity requirement.
138
+
139
+ low_memory : bool
140
+ If this is True, this function uses an algorithm that uses slightly
141
+ more time but less memory.
142
+
143
+ Returns
144
+ -------
145
+ bool
146
+ Whether the graph is locally `(k, l)`-connected subgraph.
147
+
148
+ See also
149
+ --------
150
+ kl_connected_subgraph
151
+
152
+ References
153
+ ----------
154
+ .. [1] Chung, Fan and Linyuan Lu. "The Small World Phenomenon in Hybrid
155
+ Power Law Graphs." *Complex Networks*. Springer Berlin Heidelberg,
156
+ 2004. 89--104.
157
+
158
+ """
159
+ graphOK = True
160
+ for edge in G.edges():
161
+ (u, v) = edge
162
+ # Get copy of graph needed for this search
163
+ if low_memory:
164
+ verts = {u, v}
165
+ for i in range(k):
166
+ [verts.update(G.neighbors(w)) for w in verts.copy()]
167
+ G2 = G.subgraph(verts)
168
+ else:
169
+ G2 = copy.deepcopy(G)
170
+ ###
171
+ path = [u, v]
172
+ cnt = 0
173
+ accept = 0
174
+ while path:
175
+ cnt += 1 # Found a path
176
+ if cnt >= l:
177
+ accept = 1
178
+ break
179
+ # record edges along this graph
180
+ prev = u
181
+ for w in path:
182
+ if w != prev:
183
+ G2.remove_edge(prev, w)
184
+ prev = w
185
+ # path = shortest_path(G2, u, v, k) # ??? should "Cutoff" be k+1?
186
+ try:
187
+ path = nx.shortest_path(G2, u, v) # ??? should "Cutoff" be k+1?
188
+ except nx.NetworkXNoPath:
189
+ path = False
190
+ # No Other Paths
191
+ if accept == 0:
192
+ graphOK = False
193
+ break
194
+ # return status
195
+ return graphOK
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/link_prediction.py ADDED
@@ -0,0 +1,604 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Link prediction algorithms.
3
+ """
4
+
5
+
6
+ from math import log
7
+
8
+ import networkx as nx
9
+ from networkx.utils import not_implemented_for
10
+
11
+ __all__ = [
12
+ "resource_allocation_index",
13
+ "jaccard_coefficient",
14
+ "adamic_adar_index",
15
+ "preferential_attachment",
16
+ "cn_soundarajan_hopcroft",
17
+ "ra_index_soundarajan_hopcroft",
18
+ "within_inter_cluster",
19
+ "common_neighbor_centrality",
20
+ ]
21
+
22
+
23
+ def _apply_prediction(G, func, ebunch=None):
24
+ """Applies the given function to each edge in the specified iterable
25
+ of edges.
26
+
27
+ `G` is an instance of :class:`networkx.Graph`.
28
+
29
+ `func` is a function on two inputs, each of which is a node in the
30
+ graph. The function can return anything, but it should return a
31
+ value representing a prediction of the likelihood of a "link"
32
+ joining the two nodes.
33
+
34
+ `ebunch` is an iterable of pairs of nodes. If not specified, all
35
+ non-edges in the graph `G` will be used.
36
+
37
+ """
38
+ if ebunch is None:
39
+ ebunch = nx.non_edges(G)
40
+ return ((u, v, func(u, v)) for u, v in ebunch)
41
+
42
+
43
+ @not_implemented_for("directed")
44
+ @not_implemented_for("multigraph")
45
+ @nx._dispatch
46
+ def resource_allocation_index(G, ebunch=None):
47
+ r"""Compute the resource allocation index of all node pairs in ebunch.
48
+
49
+ Resource allocation index of `u` and `v` is defined as
50
+
51
+ .. math::
52
+
53
+ \sum_{w \in \Gamma(u) \cap \Gamma(v)} \frac{1}{|\Gamma(w)|}
54
+
55
+ where $\Gamma(u)$ denotes the set of neighbors of $u$.
56
+
57
+ Parameters
58
+ ----------
59
+ G : graph
60
+ A NetworkX undirected graph.
61
+
62
+ ebunch : iterable of node pairs, optional (default = None)
63
+ Resource allocation index will be computed for each pair of
64
+ nodes given in the iterable. The pairs must be given as
65
+ 2-tuples (u, v) where u and v are nodes in the graph. If ebunch
66
+ is None then all nonexistent edges in the graph will be used.
67
+ Default value: None.
68
+
69
+ Returns
70
+ -------
71
+ piter : iterator
72
+ An iterator of 3-tuples in the form (u, v, p) where (u, v) is a
73
+ pair of nodes and p is their resource allocation index.
74
+
75
+ Examples
76
+ --------
77
+ >>> G = nx.complete_graph(5)
78
+ >>> preds = nx.resource_allocation_index(G, [(0, 1), (2, 3)])
79
+ >>> for u, v, p in preds:
80
+ ... print(f"({u}, {v}) -> {p:.8f}")
81
+ (0, 1) -> 0.75000000
82
+ (2, 3) -> 0.75000000
83
+
84
+ References
85
+ ----------
86
+ .. [1] T. Zhou, L. Lu, Y.-C. Zhang.
87
+ Predicting missing links via local information.
88
+ Eur. Phys. J. B 71 (2009) 623.
89
+ https://arxiv.org/pdf/0901.0553.pdf
90
+ """
91
+
92
+ def predict(u, v):
93
+ return sum(1 / G.degree(w) for w in nx.common_neighbors(G, u, v))
94
+
95
+ return _apply_prediction(G, predict, ebunch)
96
+
97
+
98
+ @not_implemented_for("directed")
99
+ @not_implemented_for("multigraph")
100
+ @nx._dispatch
101
+ def jaccard_coefficient(G, ebunch=None):
102
+ r"""Compute the Jaccard coefficient of all node pairs in ebunch.
103
+
104
+ Jaccard coefficient of nodes `u` and `v` is defined as
105
+
106
+ .. math::
107
+
108
+ \frac{|\Gamma(u) \cap \Gamma(v)|}{|\Gamma(u) \cup \Gamma(v)|}
109
+
110
+ where $\Gamma(u)$ denotes the set of neighbors of $u$.
111
+
112
+ Parameters
113
+ ----------
114
+ G : graph
115
+ A NetworkX undirected graph.
116
+
117
+ ebunch : iterable of node pairs, optional (default = None)
118
+ Jaccard coefficient will be computed for each pair of nodes
119
+ given in the iterable. The pairs must be given as 2-tuples
120
+ (u, v) where u and v are nodes in the graph. If ebunch is None
121
+ then all nonexistent edges in the graph will be used.
122
+ Default value: None.
123
+
124
+ Returns
125
+ -------
126
+ piter : iterator
127
+ An iterator of 3-tuples in the form (u, v, p) where (u, v) is a
128
+ pair of nodes and p is their Jaccard coefficient.
129
+
130
+ Examples
131
+ --------
132
+ >>> G = nx.complete_graph(5)
133
+ >>> preds = nx.jaccard_coefficient(G, [(0, 1), (2, 3)])
134
+ >>> for u, v, p in preds:
135
+ ... print(f"({u}, {v}) -> {p:.8f}")
136
+ (0, 1) -> 0.60000000
137
+ (2, 3) -> 0.60000000
138
+
139
+ References
140
+ ----------
141
+ .. [1] D. Liben-Nowell, J. Kleinberg.
142
+ The Link Prediction Problem for Social Networks (2004).
143
+ http://www.cs.cornell.edu/home/kleinber/link-pred.pdf
144
+ """
145
+
146
+ def predict(u, v):
147
+ union_size = len(set(G[u]) | set(G[v]))
148
+ if union_size == 0:
149
+ return 0
150
+ return len(list(nx.common_neighbors(G, u, v))) / union_size
151
+
152
+ return _apply_prediction(G, predict, ebunch)
153
+
154
+
155
+ @not_implemented_for("directed")
156
+ @not_implemented_for("multigraph")
157
+ @nx._dispatch
158
+ def adamic_adar_index(G, ebunch=None):
159
+ r"""Compute the Adamic-Adar index of all node pairs in ebunch.
160
+
161
+ Adamic-Adar index of `u` and `v` is defined as
162
+
163
+ .. math::
164
+
165
+ \sum_{w \in \Gamma(u) \cap \Gamma(v)} \frac{1}{\log |\Gamma(w)|}
166
+
167
+ where $\Gamma(u)$ denotes the set of neighbors of $u$.
168
+ This index leads to zero-division for nodes only connected via self-loops.
169
+ It is intended to be used when no self-loops are present.
170
+
171
+ Parameters
172
+ ----------
173
+ G : graph
174
+ NetworkX undirected graph.
175
+
176
+ ebunch : iterable of node pairs, optional (default = None)
177
+ Adamic-Adar index will be computed for each pair of nodes given
178
+ in the iterable. The pairs must be given as 2-tuples (u, v)
179
+ where u and v are nodes in the graph. If ebunch is None then all
180
+ nonexistent edges in the graph will be used.
181
+ Default value: None.
182
+
183
+ Returns
184
+ -------
185
+ piter : iterator
186
+ An iterator of 3-tuples in the form (u, v, p) where (u, v) is a
187
+ pair of nodes and p is their Adamic-Adar index.
188
+
189
+ Examples
190
+ --------
191
+ >>> G = nx.complete_graph(5)
192
+ >>> preds = nx.adamic_adar_index(G, [(0, 1), (2, 3)])
193
+ >>> for u, v, p in preds:
194
+ ... print(f"({u}, {v}) -> {p:.8f}")
195
+ (0, 1) -> 2.16404256
196
+ (2, 3) -> 2.16404256
197
+
198
+ References
199
+ ----------
200
+ .. [1] D. Liben-Nowell, J. Kleinberg.
201
+ The Link Prediction Problem for Social Networks (2004).
202
+ http://www.cs.cornell.edu/home/kleinber/link-pred.pdf
203
+ """
204
+
205
+ def predict(u, v):
206
+ return sum(1 / log(G.degree(w)) for w in nx.common_neighbors(G, u, v))
207
+
208
+ return _apply_prediction(G, predict, ebunch)
209
+
210
+
211
+ @not_implemented_for("directed")
212
+ @not_implemented_for("multigraph")
213
+ @nx._dispatch
214
+ def common_neighbor_centrality(G, ebunch=None, alpha=0.8):
215
+ r"""Return the CCPA score for each pair of nodes.
216
+
217
+ Compute the Common Neighbor and Centrality based Parameterized Algorithm(CCPA)
218
+ score of all node pairs in ebunch.
219
+
220
+ CCPA score of `u` and `v` is defined as
221
+
222
+ .. math::
223
+
224
+ \alpha \cdot (|\Gamma (u){\cap }^{}\Gamma (v)|)+(1-\alpha )\cdot \frac{N}{{d}_{uv}}
225
+
226
+ where $\Gamma(u)$ denotes the set of neighbors of $u$, $\Gamma(v)$ denotes the
227
+ set of neighbors of $v$, $\alpha$ is parameter varies between [0,1], $N$ denotes
228
+ total number of nodes in the Graph and ${d}_{uv}$ denotes shortest distance
229
+ between $u$ and $v$.
230
+
231
+ This algorithm is based on two vital properties of nodes, namely the number
232
+ of common neighbors and their centrality. Common neighbor refers to the common
233
+ nodes between two nodes. Centrality refers to the prestige that a node enjoys
234
+ in a network.
235
+
236
+ .. seealso::
237
+
238
+ :func:`common_neighbors`
239
+
240
+ Parameters
241
+ ----------
242
+ G : graph
243
+ NetworkX undirected graph.
244
+
245
+ ebunch : iterable of node pairs, optional (default = None)
246
+ Preferential attachment score will be computed for each pair of
247
+ nodes given in the iterable. The pairs must be given as
248
+ 2-tuples (u, v) where u and v are nodes in the graph. If ebunch
249
+ is None then all nonexistent edges in the graph will be used.
250
+ Default value: None.
251
+
252
+ alpha : Parameter defined for participation of Common Neighbor
253
+ and Centrality Algorithm share. Values for alpha should
254
+ normally be between 0 and 1. Default value set to 0.8
255
+ because author found better performance at 0.8 for all the
256
+ dataset.
257
+ Default value: 0.8
258
+
259
+
260
+ Returns
261
+ -------
262
+ piter : iterator
263
+ An iterator of 3-tuples in the form (u, v, p) where (u, v) is a
264
+ pair of nodes and p is their Common Neighbor and Centrality based
265
+ Parameterized Algorithm(CCPA) score.
266
+
267
+ Examples
268
+ --------
269
+ >>> G = nx.complete_graph(5)
270
+ >>> preds = nx.common_neighbor_centrality(G, [(0, 1), (2, 3)])
271
+ >>> for u, v, p in preds:
272
+ ... print(f"({u}, {v}) -> {p}")
273
+ (0, 1) -> 3.4000000000000004
274
+ (2, 3) -> 3.4000000000000004
275
+
276
+ References
277
+ ----------
278
+ .. [1] Ahmad, I., Akhtar, M.U., Noor, S. et al.
279
+ Missing Link Prediction using Common Neighbor and Centrality based Parameterized Algorithm.
280
+ Sci Rep 10, 364 (2020).
281
+ https://doi.org/10.1038/s41598-019-57304-y
282
+ """
283
+
284
+ # When alpha == 1, the CCPA score simplifies to the number of common neighbors.
285
+ if alpha == 1:
286
+
287
+ def predict(u, v):
288
+ if u == v:
289
+ raise nx.NetworkXAlgorithmError("Self links are not supported")
290
+
291
+ return sum(1 for _ in nx.common_neighbors(G, u, v))
292
+
293
+ else:
294
+ spl = dict(nx.shortest_path_length(G))
295
+ inf = float("inf")
296
+
297
+ def predict(u, v):
298
+ if u == v:
299
+ raise nx.NetworkXAlgorithmError("Self links are not supported")
300
+ path_len = spl[u].get(v, inf)
301
+
302
+ return alpha * sum(1 for _ in nx.common_neighbors(G, u, v)) + (
303
+ 1 - alpha
304
+ ) * (G.number_of_nodes() / path_len)
305
+
306
+ return _apply_prediction(G, predict, ebunch)
307
+
308
+
309
+ @not_implemented_for("directed")
310
+ @not_implemented_for("multigraph")
311
+ @nx._dispatch
312
+ def preferential_attachment(G, ebunch=None):
313
+ r"""Compute the preferential attachment score of all node pairs in ebunch.
314
+
315
+ Preferential attachment score of `u` and `v` is defined as
316
+
317
+ .. math::
318
+
319
+ |\Gamma(u)| |\Gamma(v)|
320
+
321
+ where $\Gamma(u)$ denotes the set of neighbors of $u$.
322
+
323
+ Parameters
324
+ ----------
325
+ G : graph
326
+ NetworkX undirected graph.
327
+
328
+ ebunch : iterable of node pairs, optional (default = None)
329
+ Preferential attachment score will be computed for each pair of
330
+ nodes given in the iterable. The pairs must be given as
331
+ 2-tuples (u, v) where u and v are nodes in the graph. If ebunch
332
+ is None then all nonexistent edges in the graph will be used.
333
+ Default value: None.
334
+
335
+ Returns
336
+ -------
337
+ piter : iterator
338
+ An iterator of 3-tuples in the form (u, v, p) where (u, v) is a
339
+ pair of nodes and p is their preferential attachment score.
340
+
341
+ Examples
342
+ --------
343
+ >>> G = nx.complete_graph(5)
344
+ >>> preds = nx.preferential_attachment(G, [(0, 1), (2, 3)])
345
+ >>> for u, v, p in preds:
346
+ ... print(f"({u}, {v}) -> {p}")
347
+ (0, 1) -> 16
348
+ (2, 3) -> 16
349
+
350
+ References
351
+ ----------
352
+ .. [1] D. Liben-Nowell, J. Kleinberg.
353
+ The Link Prediction Problem for Social Networks (2004).
354
+ http://www.cs.cornell.edu/home/kleinber/link-pred.pdf
355
+ """
356
+
357
+ def predict(u, v):
358
+ return G.degree(u) * G.degree(v)
359
+
360
+ return _apply_prediction(G, predict, ebunch)
361
+
362
+
363
+ @not_implemented_for("directed")
364
+ @not_implemented_for("multigraph")
365
+ @nx._dispatch(node_attrs="community")
366
+ def cn_soundarajan_hopcroft(G, ebunch=None, community="community"):
367
+ r"""Count the number of common neighbors of all node pairs in ebunch
368
+ using community information.
369
+
370
+ For two nodes $u$ and $v$, this function computes the number of
371
+ common neighbors and bonus one for each common neighbor belonging to
372
+ the same community as $u$ and $v$. Mathematically,
373
+
374
+ .. math::
375
+
376
+ |\Gamma(u) \cap \Gamma(v)| + \sum_{w \in \Gamma(u) \cap \Gamma(v)} f(w)
377
+
378
+ where $f(w)$ equals 1 if $w$ belongs to the same community as $u$
379
+ and $v$ or 0 otherwise and $\Gamma(u)$ denotes the set of
380
+ neighbors of $u$.
381
+
382
+ Parameters
383
+ ----------
384
+ G : graph
385
+ A NetworkX undirected graph.
386
+
387
+ ebunch : iterable of node pairs, optional (default = None)
388
+ The score will be computed for each pair of nodes given in the
389
+ iterable. The pairs must be given as 2-tuples (u, v) where u
390
+ and v are nodes in the graph. If ebunch is None then all
391
+ nonexistent edges in the graph will be used.
392
+ Default value: None.
393
+
394
+ community : string, optional (default = 'community')
395
+ Nodes attribute name containing the community information.
396
+ G[u][community] identifies which community u belongs to. Each
397
+ node belongs to at most one community. Default value: 'community'.
398
+
399
+ Returns
400
+ -------
401
+ piter : iterator
402
+ An iterator of 3-tuples in the form (u, v, p) where (u, v) is a
403
+ pair of nodes and p is their score.
404
+
405
+ Examples
406
+ --------
407
+ >>> G = nx.path_graph(3)
408
+ >>> G.nodes[0]["community"] = 0
409
+ >>> G.nodes[1]["community"] = 0
410
+ >>> G.nodes[2]["community"] = 0
411
+ >>> preds = nx.cn_soundarajan_hopcroft(G, [(0, 2)])
412
+ >>> for u, v, p in preds:
413
+ ... print(f"({u}, {v}) -> {p}")
414
+ (0, 2) -> 2
415
+
416
+ References
417
+ ----------
418
+ .. [1] Sucheta Soundarajan and John Hopcroft.
419
+ Using community information to improve the precision of link
420
+ prediction methods.
421
+ In Proceedings of the 21st international conference companion on
422
+ World Wide Web (WWW '12 Companion). ACM, New York, NY, USA, 607-608.
423
+ http://doi.acm.org/10.1145/2187980.2188150
424
+ """
425
+
426
+ def predict(u, v):
427
+ Cu = _community(G, u, community)
428
+ Cv = _community(G, v, community)
429
+ cnbors = list(nx.common_neighbors(G, u, v))
430
+ neighbors = (
431
+ sum(_community(G, w, community) == Cu for w in cnbors) if Cu == Cv else 0
432
+ )
433
+ return len(cnbors) + neighbors
434
+
435
+ return _apply_prediction(G, predict, ebunch)
436
+
437
+
438
+ @not_implemented_for("directed")
439
+ @not_implemented_for("multigraph")
440
+ @nx._dispatch(node_attrs="community")
441
+ def ra_index_soundarajan_hopcroft(G, ebunch=None, community="community"):
442
+ r"""Compute the resource allocation index of all node pairs in
443
+ ebunch using community information.
444
+
445
+ For two nodes $u$ and $v$, this function computes the resource
446
+ allocation index considering only common neighbors belonging to the
447
+ same community as $u$ and $v$. Mathematically,
448
+
449
+ .. math::
450
+
451
+ \sum_{w \in \Gamma(u) \cap \Gamma(v)} \frac{f(w)}{|\Gamma(w)|}
452
+
453
+ where $f(w)$ equals 1 if $w$ belongs to the same community as $u$
454
+ and $v$ or 0 otherwise and $\Gamma(u)$ denotes the set of
455
+ neighbors of $u$.
456
+
457
+ Parameters
458
+ ----------
459
+ G : graph
460
+ A NetworkX undirected graph.
461
+
462
+ ebunch : iterable of node pairs, optional (default = None)
463
+ The score will be computed for each pair of nodes given in the
464
+ iterable. The pairs must be given as 2-tuples (u, v) where u
465
+ and v are nodes in the graph. If ebunch is None then all
466
+ nonexistent edges in the graph will be used.
467
+ Default value: None.
468
+
469
+ community : string, optional (default = 'community')
470
+ Nodes attribute name containing the community information.
471
+ G[u][community] identifies which community u belongs to. Each
472
+ node belongs to at most one community. Default value: 'community'.
473
+
474
+ Returns
475
+ -------
476
+ piter : iterator
477
+ An iterator of 3-tuples in the form (u, v, p) where (u, v) is a
478
+ pair of nodes and p is their score.
479
+
480
+ Examples
481
+ --------
482
+ >>> G = nx.Graph()
483
+ >>> G.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 3)])
484
+ >>> G.nodes[0]["community"] = 0
485
+ >>> G.nodes[1]["community"] = 0
486
+ >>> G.nodes[2]["community"] = 1
487
+ >>> G.nodes[3]["community"] = 0
488
+ >>> preds = nx.ra_index_soundarajan_hopcroft(G, [(0, 3)])
489
+ >>> for u, v, p in preds:
490
+ ... print(f"({u}, {v}) -> {p:.8f}")
491
+ (0, 3) -> 0.50000000
492
+
493
+ References
494
+ ----------
495
+ .. [1] Sucheta Soundarajan and John Hopcroft.
496
+ Using community information to improve the precision of link
497
+ prediction methods.
498
+ In Proceedings of the 21st international conference companion on
499
+ World Wide Web (WWW '12 Companion). ACM, New York, NY, USA, 607-608.
500
+ http://doi.acm.org/10.1145/2187980.2188150
501
+ """
502
+
503
+ def predict(u, v):
504
+ Cu = _community(G, u, community)
505
+ Cv = _community(G, v, community)
506
+ if Cu != Cv:
507
+ return 0
508
+ cnbors = nx.common_neighbors(G, u, v)
509
+ return sum(1 / G.degree(w) for w in cnbors if _community(G, w, community) == Cu)
510
+
511
+ return _apply_prediction(G, predict, ebunch)
512
+
513
+
514
+ @not_implemented_for("directed")
515
+ @not_implemented_for("multigraph")
516
+ @nx._dispatch(node_attrs="community")
517
+ def within_inter_cluster(G, ebunch=None, delta=0.001, community="community"):
518
+ """Compute the ratio of within- and inter-cluster common neighbors
519
+ of all node pairs in ebunch.
520
+
521
+ For two nodes `u` and `v`, if a common neighbor `w` belongs to the
522
+ same community as them, `w` is considered as within-cluster common
523
+ neighbor of `u` and `v`. Otherwise, it is considered as
524
+ inter-cluster common neighbor of `u` and `v`. The ratio between the
525
+ size of the set of within- and inter-cluster common neighbors is
526
+ defined as the WIC measure. [1]_
527
+
528
+ Parameters
529
+ ----------
530
+ G : graph
531
+ A NetworkX undirected graph.
532
+
533
+ ebunch : iterable of node pairs, optional (default = None)
534
+ The WIC measure will be computed for each pair of nodes given in
535
+ the iterable. The pairs must be given as 2-tuples (u, v) where
536
+ u and v are nodes in the graph. If ebunch is None then all
537
+ nonexistent edges in the graph will be used.
538
+ Default value: None.
539
+
540
+ delta : float, optional (default = 0.001)
541
+ Value to prevent division by zero in case there is no
542
+ inter-cluster common neighbor between two nodes. See [1]_ for
543
+ details. Default value: 0.001.
544
+
545
+ community : string, optional (default = 'community')
546
+ Nodes attribute name containing the community information.
547
+ G[u][community] identifies which community u belongs to. Each
548
+ node belongs to at most one community. Default value: 'community'.
549
+
550
+ Returns
551
+ -------
552
+ piter : iterator
553
+ An iterator of 3-tuples in the form (u, v, p) where (u, v) is a
554
+ pair of nodes and p is their WIC measure.
555
+
556
+ Examples
557
+ --------
558
+ >>> G = nx.Graph()
559
+ >>> G.add_edges_from([(0, 1), (0, 2), (0, 3), (1, 4), (2, 4), (3, 4)])
560
+ >>> G.nodes[0]["community"] = 0
561
+ >>> G.nodes[1]["community"] = 1
562
+ >>> G.nodes[2]["community"] = 0
563
+ >>> G.nodes[3]["community"] = 0
564
+ >>> G.nodes[4]["community"] = 0
565
+ >>> preds = nx.within_inter_cluster(G, [(0, 4)])
566
+ >>> for u, v, p in preds:
567
+ ... print(f"({u}, {v}) -> {p:.8f}")
568
+ (0, 4) -> 1.99800200
569
+ >>> preds = nx.within_inter_cluster(G, [(0, 4)], delta=0.5)
570
+ >>> for u, v, p in preds:
571
+ ... print(f"({u}, {v}) -> {p:.8f}")
572
+ (0, 4) -> 1.33333333
573
+
574
+ References
575
+ ----------
576
+ .. [1] Jorge Carlos Valverde-Rebaza and Alneu de Andrade Lopes.
577
+ Link prediction in complex networks based on cluster information.
578
+ In Proceedings of the 21st Brazilian conference on Advances in
579
+ Artificial Intelligence (SBIA'12)
580
+ https://doi.org/10.1007/978-3-642-34459-6_10
581
+ """
582
+ if delta <= 0:
583
+ raise nx.NetworkXAlgorithmError("Delta must be greater than zero")
584
+
585
+ def predict(u, v):
586
+ Cu = _community(G, u, community)
587
+ Cv = _community(G, v, community)
588
+ if Cu != Cv:
589
+ return 0
590
+ cnbors = set(nx.common_neighbors(G, u, v))
591
+ within = {w for w in cnbors if _community(G, w, community) == Cu}
592
+ inter = cnbors - within
593
+ return len(within) / (len(inter) + delta)
594
+
595
+ return _apply_prediction(G, predict, ebunch)
596
+
597
+
598
+ def _community(G, u, community):
599
+ """Get the community of the given node."""
600
+ node_u = G.nodes[u]
601
+ try:
602
+ return node_u[community]
603
+ except KeyError as err:
604
+ raise nx.NetworkXAlgorithmError("No community information") from err
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/simple_paths.py ADDED
@@ -0,0 +1,978 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from heapq import heappop, heappush
2
+ from itertools import count
3
+
4
+ import networkx as nx
5
+ from networkx.algorithms.shortest_paths.weighted import _weight_function
6
+ from networkx.utils import not_implemented_for, pairwise
7
+
8
+ __all__ = [
9
+ "all_simple_paths",
10
+ "is_simple_path",
11
+ "shortest_simple_paths",
12
+ "all_simple_edge_paths",
13
+ ]
14
+
15
+
16
+ @nx._dispatch
17
+ def is_simple_path(G, nodes):
18
+ """Returns True if and only if `nodes` form a simple path in `G`.
19
+
20
+ A *simple path* in a graph is a nonempty sequence of nodes in which
21
+ no node appears more than once in the sequence, and each adjacent
22
+ pair of nodes in the sequence is adjacent in the graph.
23
+
24
+ Parameters
25
+ ----------
26
+ G : graph
27
+ A NetworkX graph.
28
+ nodes : list
29
+ A list of one or more nodes in the graph `G`.
30
+
31
+ Returns
32
+ -------
33
+ bool
34
+ Whether the given list of nodes represents a simple path in `G`.
35
+
36
+ Notes
37
+ -----
38
+ An empty list of nodes is not a path but a list of one node is a
39
+ path. Here's an explanation why.
40
+
41
+ This function operates on *node paths*. One could also consider
42
+ *edge paths*. There is a bijection between node paths and edge
43
+ paths.
44
+
45
+ The *length of a path* is the number of edges in the path, so a list
46
+ of nodes of length *n* corresponds to a path of length *n* - 1.
47
+ Thus the smallest edge path would be a list of zero edges, the empty
48
+ path. This corresponds to a list of one node.
49
+
50
+ To convert between a node path and an edge path, you can use code
51
+ like the following::
52
+
53
+ >>> from networkx.utils import pairwise
54
+ >>> nodes = [0, 1, 2, 3]
55
+ >>> edges = list(pairwise(nodes))
56
+ >>> edges
57
+ [(0, 1), (1, 2), (2, 3)]
58
+ >>> nodes = [edges[0][0]] + [v for u, v in edges]
59
+ >>> nodes
60
+ [0, 1, 2, 3]
61
+
62
+ Examples
63
+ --------
64
+ >>> G = nx.cycle_graph(4)
65
+ >>> nx.is_simple_path(G, [2, 3, 0])
66
+ True
67
+ >>> nx.is_simple_path(G, [0, 2])
68
+ False
69
+
70
+ """
71
+ # The empty list is not a valid path. Could also return
72
+ # NetworkXPointlessConcept here.
73
+ if len(nodes) == 0:
74
+ return False
75
+
76
+ # If the list is a single node, just check that the node is actually
77
+ # in the graph.
78
+ if len(nodes) == 1:
79
+ return nodes[0] in G
80
+
81
+ # check that all nodes in the list are in the graph, if at least one
82
+ # is not in the graph, then this is not a simple path
83
+ if not all(n in G for n in nodes):
84
+ return False
85
+
86
+ # If the list contains repeated nodes, then it's not a simple path
87
+ if len(set(nodes)) != len(nodes):
88
+ return False
89
+
90
+ # Test that each adjacent pair of nodes is adjacent.
91
+ return all(v in G[u] for u, v in pairwise(nodes))
92
+
93
+
94
+ @nx._dispatch
95
+ def all_simple_paths(G, source, target, cutoff=None):
96
+ """Generate all simple paths in the graph G from source to target.
97
+
98
+ A simple path is a path with no repeated nodes.
99
+
100
+ Parameters
101
+ ----------
102
+ G : NetworkX graph
103
+
104
+ source : node
105
+ Starting node for path
106
+
107
+ target : nodes
108
+ Single node or iterable of nodes at which to end path
109
+
110
+ cutoff : integer, optional
111
+ Depth to stop the search. Only paths of length <= cutoff are returned.
112
+
113
+ Returns
114
+ -------
115
+ path_generator: generator
116
+ A generator that produces lists of simple paths. If there are no paths
117
+ between the source and target within the given cutoff the generator
118
+ produces no output. If it is possible to traverse the same sequence of
119
+ nodes in multiple ways, namely through parallel edges, then it will be
120
+ returned multiple times (once for each viable edge combination).
121
+
122
+ Examples
123
+ --------
124
+ This iterator generates lists of nodes::
125
+
126
+ >>> G = nx.complete_graph(4)
127
+ >>> for path in nx.all_simple_paths(G, source=0, target=3):
128
+ ... print(path)
129
+ ...
130
+ [0, 1, 2, 3]
131
+ [0, 1, 3]
132
+ [0, 2, 1, 3]
133
+ [0, 2, 3]
134
+ [0, 3]
135
+
136
+ You can generate only those paths that are shorter than a certain
137
+ length by using the `cutoff` keyword argument::
138
+
139
+ >>> paths = nx.all_simple_paths(G, source=0, target=3, cutoff=2)
140
+ >>> print(list(paths))
141
+ [[0, 1, 3], [0, 2, 3], [0, 3]]
142
+
143
+ To get each path as the corresponding list of edges, you can use the
144
+ :func:`networkx.utils.pairwise` helper function::
145
+
146
+ >>> paths = nx.all_simple_paths(G, source=0, target=3)
147
+ >>> for path in map(nx.utils.pairwise, paths):
148
+ ... print(list(path))
149
+ [(0, 1), (1, 2), (2, 3)]
150
+ [(0, 1), (1, 3)]
151
+ [(0, 2), (2, 1), (1, 3)]
152
+ [(0, 2), (2, 3)]
153
+ [(0, 3)]
154
+
155
+ Pass an iterable of nodes as target to generate all paths ending in any of several nodes::
156
+
157
+ >>> G = nx.complete_graph(4)
158
+ >>> for path in nx.all_simple_paths(G, source=0, target=[3, 2]):
159
+ ... print(path)
160
+ ...
161
+ [0, 1, 2]
162
+ [0, 1, 2, 3]
163
+ [0, 1, 3]
164
+ [0, 1, 3, 2]
165
+ [0, 2]
166
+ [0, 2, 1, 3]
167
+ [0, 2, 3]
168
+ [0, 3]
169
+ [0, 3, 1, 2]
170
+ [0, 3, 2]
171
+
172
+ Iterate over each path from the root nodes to the leaf nodes in a
173
+ directed acyclic graph using a functional programming approach::
174
+
175
+ >>> from itertools import chain
176
+ >>> from itertools import product
177
+ >>> from itertools import starmap
178
+ >>> from functools import partial
179
+ >>>
180
+ >>> chaini = chain.from_iterable
181
+ >>>
182
+ >>> G = nx.DiGraph([(0, 1), (1, 2), (0, 3), (3, 2)])
183
+ >>> roots = (v for v, d in G.in_degree() if d == 0)
184
+ >>> leaves = (v for v, d in G.out_degree() if d == 0)
185
+ >>> all_paths = partial(nx.all_simple_paths, G)
186
+ >>> list(chaini(starmap(all_paths, product(roots, leaves))))
187
+ [[0, 1, 2], [0, 3, 2]]
188
+
189
+ The same list computed using an iterative approach::
190
+
191
+ >>> G = nx.DiGraph([(0, 1), (1, 2), (0, 3), (3, 2)])
192
+ >>> roots = (v for v, d in G.in_degree() if d == 0)
193
+ >>> leaves = (v for v, d in G.out_degree() if d == 0)
194
+ >>> all_paths = []
195
+ >>> for root in roots:
196
+ ... for leaf in leaves:
197
+ ... paths = nx.all_simple_paths(G, root, leaf)
198
+ ... all_paths.extend(paths)
199
+ >>> all_paths
200
+ [[0, 1, 2], [0, 3, 2]]
201
+
202
+ Iterate over each path from the root nodes to the leaf nodes in a
203
+ directed acyclic graph passing all leaves together to avoid unnecessary
204
+ compute::
205
+
206
+ >>> G = nx.DiGraph([(0, 1), (2, 1), (1, 3), (1, 4)])
207
+ >>> roots = (v for v, d in G.in_degree() if d == 0)
208
+ >>> leaves = [v for v, d in G.out_degree() if d == 0]
209
+ >>> all_paths = []
210
+ >>> for root in roots:
211
+ ... paths = nx.all_simple_paths(G, root, leaves)
212
+ ... all_paths.extend(paths)
213
+ >>> all_paths
214
+ [[0, 1, 3], [0, 1, 4], [2, 1, 3], [2, 1, 4]]
215
+
216
+ If parallel edges offer multiple ways to traverse a given sequence of
217
+ nodes, this sequence of nodes will be returned multiple times:
218
+
219
+ >>> G = nx.MultiDiGraph([(0, 1), (0, 1), (1, 2)])
220
+ >>> list(nx.all_simple_paths(G, 0, 2))
221
+ [[0, 1, 2], [0, 1, 2]]
222
+
223
+ Notes
224
+ -----
225
+ This algorithm uses a modified depth-first search to generate the
226
+ paths [1]_. A single path can be found in $O(V+E)$ time but the
227
+ number of simple paths in a graph can be very large, e.g. $O(n!)$ in
228
+ the complete graph of order $n$.
229
+
230
+ This function does not check that a path exists between `source` and
231
+ `target`. For large graphs, this may result in very long runtimes.
232
+ Consider using `has_path` to check that a path exists between `source` and
233
+ `target` before calling this function on large graphs.
234
+
235
+ References
236
+ ----------
237
+ .. [1] R. Sedgewick, "Algorithms in C, Part 5: Graph Algorithms",
238
+ Addison Wesley Professional, 3rd ed., 2001.
239
+
240
+ See Also
241
+ --------
242
+ all_shortest_paths, shortest_path, has_path
243
+
244
+ """
245
+ if source not in G:
246
+ raise nx.NodeNotFound(f"source node {source} not in graph")
247
+ if target in G:
248
+ targets = {target}
249
+ else:
250
+ try:
251
+ targets = set(target)
252
+ except TypeError as err:
253
+ raise nx.NodeNotFound(f"target node {target} not in graph") from err
254
+ if source in targets:
255
+ return _empty_generator()
256
+ if cutoff is None:
257
+ cutoff = len(G) - 1
258
+ if cutoff < 1:
259
+ return _empty_generator()
260
+ if G.is_multigraph():
261
+ return _all_simple_paths_multigraph(G, source, targets, cutoff)
262
+ else:
263
+ return _all_simple_paths_graph(G, source, targets, cutoff)
264
+
265
+
266
+ def _empty_generator():
267
+ yield from ()
268
+
269
+
270
+ def _all_simple_paths_graph(G, source, targets, cutoff):
271
+ visited = {source: True}
272
+ stack = [iter(G[source])]
273
+ while stack:
274
+ children = stack[-1]
275
+ child = next(children, None)
276
+ if child is None:
277
+ stack.pop()
278
+ visited.popitem()
279
+ elif len(visited) < cutoff:
280
+ if child in visited:
281
+ continue
282
+ if child in targets:
283
+ yield list(visited) + [child]
284
+ visited[child] = True
285
+ if targets - set(visited.keys()): # expand stack until find all targets
286
+ stack.append(iter(G[child]))
287
+ else:
288
+ visited.popitem() # maybe other ways to child
289
+ else: # len(visited) == cutoff:
290
+ for target in (targets & (set(children) | {child})) - set(visited.keys()):
291
+ yield list(visited) + [target]
292
+ stack.pop()
293
+ visited.popitem()
294
+
295
+
296
+ def _all_simple_paths_multigraph(G, source, targets, cutoff):
297
+ visited = {source: True}
298
+ stack = [(v for u, v in G.edges(source))]
299
+ while stack:
300
+ children = stack[-1]
301
+ child = next(children, None)
302
+ if child is None:
303
+ stack.pop()
304
+ visited.popitem()
305
+ elif len(visited) < cutoff:
306
+ if child in visited:
307
+ continue
308
+ if child in targets:
309
+ yield list(visited) + [child]
310
+ visited[child] = True
311
+ if targets - set(visited.keys()):
312
+ stack.append((v for u, v in G.edges(child)))
313
+ else:
314
+ visited.popitem()
315
+ else: # len(visited) == cutoff:
316
+ for target in targets - set(visited.keys()):
317
+ count = ([child] + list(children)).count(target)
318
+ for i in range(count):
319
+ yield list(visited) + [target]
320
+ stack.pop()
321
+ visited.popitem()
322
+
323
+
324
+ @nx._dispatch
325
+ def all_simple_edge_paths(G, source, target, cutoff=None):
326
+ """Generate lists of edges for all simple paths in G from source to target.
327
+
328
+ A simple path is a path with no repeated nodes.
329
+
330
+ Parameters
331
+ ----------
332
+ G : NetworkX graph
333
+
334
+ source : node
335
+ Starting node for path
336
+
337
+ target : nodes
338
+ Single node or iterable of nodes at which to end path
339
+
340
+ cutoff : integer, optional
341
+ Depth to stop the search. Only paths of length <= cutoff are returned.
342
+
343
+ Returns
344
+ -------
345
+ path_generator: generator
346
+ A generator that produces lists of simple paths. If there are no paths
347
+ between the source and target within the given cutoff the generator
348
+ produces no output.
349
+ For multigraphs, the list of edges have elements of the form `(u,v,k)`.
350
+ Where `k` corresponds to the edge key.
351
+
352
+ Examples
353
+ --------
354
+
355
+ Print the simple path edges of a Graph::
356
+
357
+ >>> g = nx.Graph([(1, 2), (2, 4), (1, 3), (3, 4)])
358
+ >>> for path in sorted(nx.all_simple_edge_paths(g, 1, 4)):
359
+ ... print(path)
360
+ [(1, 2), (2, 4)]
361
+ [(1, 3), (3, 4)]
362
+
363
+ Print the simple path edges of a MultiGraph. Returned edges come with
364
+ their associated keys::
365
+
366
+ >>> mg = nx.MultiGraph()
367
+ >>> mg.add_edge(1, 2, key="k0")
368
+ 'k0'
369
+ >>> mg.add_edge(1, 2, key="k1")
370
+ 'k1'
371
+ >>> mg.add_edge(2, 3, key="k0")
372
+ 'k0'
373
+ >>> for path in sorted(nx.all_simple_edge_paths(mg, 1, 3)):
374
+ ... print(path)
375
+ [(1, 2, 'k0'), (2, 3, 'k0')]
376
+ [(1, 2, 'k1'), (2, 3, 'k0')]
377
+
378
+
379
+ Notes
380
+ -----
381
+ This algorithm uses a modified depth-first search to generate the
382
+ paths [1]_. A single path can be found in $O(V+E)$ time but the
383
+ number of simple paths in a graph can be very large, e.g. $O(n!)$ in
384
+ the complete graph of order $n$.
385
+
386
+ References
387
+ ----------
388
+ .. [1] R. Sedgewick, "Algorithms in C, Part 5: Graph Algorithms",
389
+ Addison Wesley Professional, 3rd ed., 2001.
390
+
391
+ See Also
392
+ --------
393
+ all_shortest_paths, shortest_path, all_simple_paths
394
+
395
+ """
396
+ if source not in G:
397
+ raise nx.NodeNotFound("source node %s not in graph" % source)
398
+ if target in G:
399
+ targets = {target}
400
+ else:
401
+ try:
402
+ targets = set(target)
403
+ except TypeError:
404
+ raise nx.NodeNotFound("target node %s not in graph" % target)
405
+ if source in targets:
406
+ return []
407
+ if cutoff is None:
408
+ cutoff = len(G) - 1
409
+ if cutoff < 1:
410
+ return []
411
+ if G.is_multigraph():
412
+ for simp_path in _all_simple_edge_paths_multigraph(G, source, targets, cutoff):
413
+ yield simp_path
414
+ else:
415
+ for simp_path in _all_simple_paths_graph(G, source, targets, cutoff):
416
+ yield list(zip(simp_path[:-1], simp_path[1:]))
417
+
418
+
419
+ def _all_simple_edge_paths_multigraph(G, source, targets, cutoff):
420
+ if not cutoff or cutoff < 1:
421
+ return []
422
+ visited = [source]
423
+ stack = [iter(G.edges(source, keys=True))]
424
+
425
+ while stack:
426
+ children = stack[-1]
427
+ child = next(children, None)
428
+ if child is None:
429
+ stack.pop()
430
+ visited.pop()
431
+ elif len(visited) < cutoff:
432
+ if child[1] in targets:
433
+ yield visited[1:] + [child]
434
+ elif child[1] not in [v[0] for v in visited[1:]]:
435
+ visited.append(child)
436
+ stack.append(iter(G.edges(child[1], keys=True)))
437
+ else: # len(visited) == cutoff:
438
+ for u, v, k in [child] + list(children):
439
+ if v in targets:
440
+ yield visited[1:] + [(u, v, k)]
441
+ stack.pop()
442
+ visited.pop()
443
+
444
+
445
+ @not_implemented_for("multigraph")
446
+ @nx._dispatch(edge_attrs="weight")
447
+ def shortest_simple_paths(G, source, target, weight=None):
448
+ """Generate all simple paths in the graph G from source to target,
449
+ starting from shortest ones.
450
+
451
+ A simple path is a path with no repeated nodes.
452
+
453
+ If a weighted shortest path search is to be used, no negative weights
454
+ are allowed.
455
+
456
+ Parameters
457
+ ----------
458
+ G : NetworkX graph
459
+
460
+ source : node
461
+ Starting node for path
462
+
463
+ target : node
464
+ Ending node for path
465
+
466
+ weight : string or function
467
+ If it is a string, it is the name of the edge attribute to be
468
+ used as a weight.
469
+
470
+ If it is a function, the weight of an edge is the value returned
471
+ by the function. The function must accept exactly three positional
472
+ arguments: the two endpoints of an edge and the dictionary of edge
473
+ attributes for that edge. The function must return a number.
474
+
475
+ If None all edges are considered to have unit weight. Default
476
+ value None.
477
+
478
+ Returns
479
+ -------
480
+ path_generator: generator
481
+ A generator that produces lists of simple paths, in order from
482
+ shortest to longest.
483
+
484
+ Raises
485
+ ------
486
+ NetworkXNoPath
487
+ If no path exists between source and target.
488
+
489
+ NetworkXError
490
+ If source or target nodes are not in the input graph.
491
+
492
+ NetworkXNotImplemented
493
+ If the input graph is a Multi[Di]Graph.
494
+
495
+ Examples
496
+ --------
497
+
498
+ >>> G = nx.cycle_graph(7)
499
+ >>> paths = list(nx.shortest_simple_paths(G, 0, 3))
500
+ >>> print(paths)
501
+ [[0, 1, 2, 3], [0, 6, 5, 4, 3]]
502
+
503
+ You can use this function to efficiently compute the k shortest/best
504
+ paths between two nodes.
505
+
506
+ >>> from itertools import islice
507
+ >>> def k_shortest_paths(G, source, target, k, weight=None):
508
+ ... return list(
509
+ ... islice(nx.shortest_simple_paths(G, source, target, weight=weight), k)
510
+ ... )
511
+ >>> for path in k_shortest_paths(G, 0, 3, 2):
512
+ ... print(path)
513
+ [0, 1, 2, 3]
514
+ [0, 6, 5, 4, 3]
515
+
516
+ Notes
517
+ -----
518
+ This procedure is based on algorithm by Jin Y. Yen [1]_. Finding
519
+ the first $K$ paths requires $O(KN^3)$ operations.
520
+
521
+ See Also
522
+ --------
523
+ all_shortest_paths
524
+ shortest_path
525
+ all_simple_paths
526
+
527
+ References
528
+ ----------
529
+ .. [1] Jin Y. Yen, "Finding the K Shortest Loopless Paths in a
530
+ Network", Management Science, Vol. 17, No. 11, Theory Series
531
+ (Jul., 1971), pp. 712-716.
532
+
533
+ """
534
+ if source not in G:
535
+ raise nx.NodeNotFound(f"source node {source} not in graph")
536
+
537
+ if target not in G:
538
+ raise nx.NodeNotFound(f"target node {target} not in graph")
539
+
540
+ if weight is None:
541
+ length_func = len
542
+ shortest_path_func = _bidirectional_shortest_path
543
+ else:
544
+ wt = _weight_function(G, weight)
545
+
546
+ def length_func(path):
547
+ return sum(
548
+ wt(u, v, G.get_edge_data(u, v)) for (u, v) in zip(path, path[1:])
549
+ )
550
+
551
+ shortest_path_func = _bidirectional_dijkstra
552
+
553
+ listA = []
554
+ listB = PathBuffer()
555
+ prev_path = None
556
+ while True:
557
+ if not prev_path:
558
+ length, path = shortest_path_func(G, source, target, weight=weight)
559
+ listB.push(length, path)
560
+ else:
561
+ ignore_nodes = set()
562
+ ignore_edges = set()
563
+ for i in range(1, len(prev_path)):
564
+ root = prev_path[:i]
565
+ root_length = length_func(root)
566
+ for path in listA:
567
+ if path[:i] == root:
568
+ ignore_edges.add((path[i - 1], path[i]))
569
+ try:
570
+ length, spur = shortest_path_func(
571
+ G,
572
+ root[-1],
573
+ target,
574
+ ignore_nodes=ignore_nodes,
575
+ ignore_edges=ignore_edges,
576
+ weight=weight,
577
+ )
578
+ path = root[:-1] + spur
579
+ listB.push(root_length + length, path)
580
+ except nx.NetworkXNoPath:
581
+ pass
582
+ ignore_nodes.add(root[-1])
583
+
584
+ if listB:
585
+ path = listB.pop()
586
+ yield path
587
+ listA.append(path)
588
+ prev_path = path
589
+ else:
590
+ break
591
+
592
+
593
+ class PathBuffer:
594
+ def __init__(self):
595
+ self.paths = set()
596
+ self.sortedpaths = []
597
+ self.counter = count()
598
+
599
+ def __len__(self):
600
+ return len(self.sortedpaths)
601
+
602
+ def push(self, cost, path):
603
+ hashable_path = tuple(path)
604
+ if hashable_path not in self.paths:
605
+ heappush(self.sortedpaths, (cost, next(self.counter), path))
606
+ self.paths.add(hashable_path)
607
+
608
+ def pop(self):
609
+ (cost, num, path) = heappop(self.sortedpaths)
610
+ hashable_path = tuple(path)
611
+ self.paths.remove(hashable_path)
612
+ return path
613
+
614
+
615
+ def _bidirectional_shortest_path(
616
+ G, source, target, ignore_nodes=None, ignore_edges=None, weight=None
617
+ ):
618
+ """Returns the shortest path between source and target ignoring
619
+ nodes and edges in the containers ignore_nodes and ignore_edges.
620
+
621
+ This is a custom modification of the standard bidirectional shortest
622
+ path implementation at networkx.algorithms.unweighted
623
+
624
+ Parameters
625
+ ----------
626
+ G : NetworkX graph
627
+
628
+ source : node
629
+ starting node for path
630
+
631
+ target : node
632
+ ending node for path
633
+
634
+ ignore_nodes : container of nodes
635
+ nodes to ignore, optional
636
+
637
+ ignore_edges : container of edges
638
+ edges to ignore, optional
639
+
640
+ weight : None
641
+ This function accepts a weight argument for convenience of
642
+ shortest_simple_paths function. It will be ignored.
643
+
644
+ Returns
645
+ -------
646
+ path: list
647
+ List of nodes in a path from source to target.
648
+
649
+ Raises
650
+ ------
651
+ NetworkXNoPath
652
+ If no path exists between source and target.
653
+
654
+ See Also
655
+ --------
656
+ shortest_path
657
+
658
+ """
659
+ # call helper to do the real work
660
+ results = _bidirectional_pred_succ(G, source, target, ignore_nodes, ignore_edges)
661
+ pred, succ, w = results
662
+
663
+ # build path from pred+w+succ
664
+ path = []
665
+ # from w to target
666
+ while w is not None:
667
+ path.append(w)
668
+ w = succ[w]
669
+ # from source to w
670
+ w = pred[path[0]]
671
+ while w is not None:
672
+ path.insert(0, w)
673
+ w = pred[w]
674
+
675
+ return len(path), path
676
+
677
+
678
+ def _bidirectional_pred_succ(G, source, target, ignore_nodes=None, ignore_edges=None):
679
+ """Bidirectional shortest path helper.
680
+ Returns (pred,succ,w) where
681
+ pred is a dictionary of predecessors from w to the source, and
682
+ succ is a dictionary of successors from w to the target.
683
+ """
684
+ # does BFS from both source and target and meets in the middle
685
+ if ignore_nodes and (source in ignore_nodes or target in ignore_nodes):
686
+ raise nx.NetworkXNoPath(f"No path between {source} and {target}.")
687
+ if target == source:
688
+ return ({target: None}, {source: None}, source)
689
+
690
+ # handle either directed or undirected
691
+ if G.is_directed():
692
+ Gpred = G.predecessors
693
+ Gsucc = G.successors
694
+ else:
695
+ Gpred = G.neighbors
696
+ Gsucc = G.neighbors
697
+
698
+ # support optional nodes filter
699
+ if ignore_nodes:
700
+
701
+ def filter_iter(nodes):
702
+ def iterate(v):
703
+ for w in nodes(v):
704
+ if w not in ignore_nodes:
705
+ yield w
706
+
707
+ return iterate
708
+
709
+ Gpred = filter_iter(Gpred)
710
+ Gsucc = filter_iter(Gsucc)
711
+
712
+ # support optional edges filter
713
+ if ignore_edges:
714
+ if G.is_directed():
715
+
716
+ def filter_pred_iter(pred_iter):
717
+ def iterate(v):
718
+ for w in pred_iter(v):
719
+ if (w, v) not in ignore_edges:
720
+ yield w
721
+
722
+ return iterate
723
+
724
+ def filter_succ_iter(succ_iter):
725
+ def iterate(v):
726
+ for w in succ_iter(v):
727
+ if (v, w) not in ignore_edges:
728
+ yield w
729
+
730
+ return iterate
731
+
732
+ Gpred = filter_pred_iter(Gpred)
733
+ Gsucc = filter_succ_iter(Gsucc)
734
+
735
+ else:
736
+
737
+ def filter_iter(nodes):
738
+ def iterate(v):
739
+ for w in nodes(v):
740
+ if (v, w) not in ignore_edges and (w, v) not in ignore_edges:
741
+ yield w
742
+
743
+ return iterate
744
+
745
+ Gpred = filter_iter(Gpred)
746
+ Gsucc = filter_iter(Gsucc)
747
+
748
+ # predecessor and successors in search
749
+ pred = {source: None}
750
+ succ = {target: None}
751
+
752
+ # initialize fringes, start with forward
753
+ forward_fringe = [source]
754
+ reverse_fringe = [target]
755
+
756
+ while forward_fringe and reverse_fringe:
757
+ if len(forward_fringe) <= len(reverse_fringe):
758
+ this_level = forward_fringe
759
+ forward_fringe = []
760
+ for v in this_level:
761
+ for w in Gsucc(v):
762
+ if w not in pred:
763
+ forward_fringe.append(w)
764
+ pred[w] = v
765
+ if w in succ:
766
+ # found path
767
+ return pred, succ, w
768
+ else:
769
+ this_level = reverse_fringe
770
+ reverse_fringe = []
771
+ for v in this_level:
772
+ for w in Gpred(v):
773
+ if w not in succ:
774
+ succ[w] = v
775
+ reverse_fringe.append(w)
776
+ if w in pred:
777
+ # found path
778
+ return pred, succ, w
779
+
780
+ raise nx.NetworkXNoPath(f"No path between {source} and {target}.")
781
+
782
+
783
+ def _bidirectional_dijkstra(
784
+ G, source, target, weight="weight", ignore_nodes=None, ignore_edges=None
785
+ ):
786
+ """Dijkstra's algorithm for shortest paths using bidirectional search.
787
+
788
+ This function returns the shortest path between source and target
789
+ ignoring nodes and edges in the containers ignore_nodes and
790
+ ignore_edges.
791
+
792
+ This is a custom modification of the standard Dijkstra bidirectional
793
+ shortest path implementation at networkx.algorithms.weighted
794
+
795
+ Parameters
796
+ ----------
797
+ G : NetworkX graph
798
+
799
+ source : node
800
+ Starting node.
801
+
802
+ target : node
803
+ Ending node.
804
+
805
+ weight: string, function, optional (default='weight')
806
+ Edge data key or weight function corresponding to the edge weight
807
+
808
+ ignore_nodes : container of nodes
809
+ nodes to ignore, optional
810
+
811
+ ignore_edges : container of edges
812
+ edges to ignore, optional
813
+
814
+ Returns
815
+ -------
816
+ length : number
817
+ Shortest path length.
818
+
819
+ Returns a tuple of two dictionaries keyed by node.
820
+ The first dictionary stores distance from the source.
821
+ The second stores the path from the source to that node.
822
+
823
+ Raises
824
+ ------
825
+ NetworkXNoPath
826
+ If no path exists between source and target.
827
+
828
+ Notes
829
+ -----
830
+ Edge weight attributes must be numerical.
831
+ Distances are calculated as sums of weighted edges traversed.
832
+
833
+ In practice bidirectional Dijkstra is much more than twice as fast as
834
+ ordinary Dijkstra.
835
+
836
+ Ordinary Dijkstra expands nodes in a sphere-like manner from the
837
+ source. The radius of this sphere will eventually be the length
838
+ of the shortest path. Bidirectional Dijkstra will expand nodes
839
+ from both the source and the target, making two spheres of half
840
+ this radius. Volume of the first sphere is pi*r*r while the
841
+ others are 2*pi*r/2*r/2, making up half the volume.
842
+
843
+ This algorithm is not guaranteed to work if edge weights
844
+ are negative or are floating point numbers
845
+ (overflows and roundoff errors can cause problems).
846
+
847
+ See Also
848
+ --------
849
+ shortest_path
850
+ shortest_path_length
851
+ """
852
+ if ignore_nodes and (source in ignore_nodes or target in ignore_nodes):
853
+ raise nx.NetworkXNoPath(f"No path between {source} and {target}.")
854
+ if source == target:
855
+ if source not in G:
856
+ raise nx.NodeNotFound(f"Node {source} not in graph")
857
+ return (0, [source])
858
+
859
+ # handle either directed or undirected
860
+ if G.is_directed():
861
+ Gpred = G.predecessors
862
+ Gsucc = G.successors
863
+ else:
864
+ Gpred = G.neighbors
865
+ Gsucc = G.neighbors
866
+
867
+ # support optional nodes filter
868
+ if ignore_nodes:
869
+
870
+ def filter_iter(nodes):
871
+ def iterate(v):
872
+ for w in nodes(v):
873
+ if w not in ignore_nodes:
874
+ yield w
875
+
876
+ return iterate
877
+
878
+ Gpred = filter_iter(Gpred)
879
+ Gsucc = filter_iter(Gsucc)
880
+
881
+ # support optional edges filter
882
+ if ignore_edges:
883
+ if G.is_directed():
884
+
885
+ def filter_pred_iter(pred_iter):
886
+ def iterate(v):
887
+ for w in pred_iter(v):
888
+ if (w, v) not in ignore_edges:
889
+ yield w
890
+
891
+ return iterate
892
+
893
+ def filter_succ_iter(succ_iter):
894
+ def iterate(v):
895
+ for w in succ_iter(v):
896
+ if (v, w) not in ignore_edges:
897
+ yield w
898
+
899
+ return iterate
900
+
901
+ Gpred = filter_pred_iter(Gpred)
902
+ Gsucc = filter_succ_iter(Gsucc)
903
+
904
+ else:
905
+
906
+ def filter_iter(nodes):
907
+ def iterate(v):
908
+ for w in nodes(v):
909
+ if (v, w) not in ignore_edges and (w, v) not in ignore_edges:
910
+ yield w
911
+
912
+ return iterate
913
+
914
+ Gpred = filter_iter(Gpred)
915
+ Gsucc = filter_iter(Gsucc)
916
+
917
+ push = heappush
918
+ pop = heappop
919
+ # Init: Forward Backward
920
+ dists = [{}, {}] # dictionary of final distances
921
+ paths = [{source: [source]}, {target: [target]}] # dictionary of paths
922
+ fringe = [[], []] # heap of (distance, node) tuples for
923
+ # extracting next node to expand
924
+ seen = [{source: 0}, {target: 0}] # dictionary of distances to
925
+ # nodes seen
926
+ c = count()
927
+ # initialize fringe heap
928
+ push(fringe[0], (0, next(c), source))
929
+ push(fringe[1], (0, next(c), target))
930
+ # neighs for extracting correct neighbor information
931
+ neighs = [Gsucc, Gpred]
932
+ # variables to hold shortest discovered path
933
+ # finaldist = 1e30000
934
+ finalpath = []
935
+ dir = 1
936
+ while fringe[0] and fringe[1]:
937
+ # choose direction
938
+ # dir == 0 is forward direction and dir == 1 is back
939
+ dir = 1 - dir
940
+ # extract closest to expand
941
+ (dist, _, v) = pop(fringe[dir])
942
+ if v in dists[dir]:
943
+ # Shortest path to v has already been found
944
+ continue
945
+ # update distance
946
+ dists[dir][v] = dist # equal to seen[dir][v]
947
+ if v in dists[1 - dir]:
948
+ # if we have scanned v in both directions we are done
949
+ # we have now discovered the shortest path
950
+ return (finaldist, finalpath)
951
+
952
+ wt = _weight_function(G, weight)
953
+ for w in neighs[dir](v):
954
+ if dir == 0: # forward
955
+ minweight = wt(v, w, G.get_edge_data(v, w))
956
+ vwLength = dists[dir][v] + minweight
957
+ else: # back, must remember to change v,w->w,v
958
+ minweight = wt(w, v, G.get_edge_data(w, v))
959
+ vwLength = dists[dir][v] + minweight
960
+
961
+ if w in dists[dir]:
962
+ if vwLength < dists[dir][w]:
963
+ raise ValueError("Contradictory paths found: negative weights?")
964
+ elif w not in seen[dir] or vwLength < seen[dir][w]:
965
+ # relaxing
966
+ seen[dir][w] = vwLength
967
+ push(fringe[dir], (vwLength, next(c), w))
968
+ paths[dir][w] = paths[dir][v] + [w]
969
+ if w in seen[0] and w in seen[1]:
970
+ # see if this path is better than the already
971
+ # discovered shortest path
972
+ totaldist = seen[0][w] + seen[1][w]
973
+ if finalpath == [] or finaldist > totaldist:
974
+ finaldist = totaldist
975
+ revpath = paths[1][w][:]
976
+ revpath.reverse()
977
+ finalpath = paths[0][w] + revpath[1:]
978
+ raise nx.NetworkXNoPath(f"No path between {source} and {target}.")
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/voronoi.py ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Functions for computing the Voronoi cells of a graph."""
2
+ import networkx as nx
3
+ from networkx.utils import groups
4
+
5
+ __all__ = ["voronoi_cells"]
6
+
7
+
8
+ @nx._dispatch(edge_attrs="weight")
9
+ def voronoi_cells(G, center_nodes, weight="weight"):
10
+ """Returns the Voronoi cells centered at `center_nodes` with respect
11
+ to the shortest-path distance metric.
12
+
13
+ If $C$ is a set of nodes in the graph and $c$ is an element of $C$,
14
+ the *Voronoi cell* centered at a node $c$ is the set of all nodes
15
+ $v$ that are closer to $c$ than to any other center node in $C$ with
16
+ respect to the shortest-path distance metric. [1]_
17
+
18
+ For directed graphs, this will compute the "outward" Voronoi cells,
19
+ as defined in [1]_, in which distance is measured from the center
20
+ nodes to the target node. For the "inward" Voronoi cells, use the
21
+ :meth:`DiGraph.reverse` method to reverse the orientation of the
22
+ edges before invoking this function on the directed graph.
23
+
24
+ Parameters
25
+ ----------
26
+ G : NetworkX graph
27
+
28
+ center_nodes : set
29
+ A nonempty set of nodes in the graph `G` that represent the
30
+ center of the Voronoi cells.
31
+
32
+ weight : string or function
33
+ The edge attribute (or an arbitrary function) representing the
34
+ weight of an edge. This keyword argument is as described in the
35
+ documentation for :func:`~networkx.multi_source_dijkstra_path`,
36
+ for example.
37
+
38
+ Returns
39
+ -------
40
+ dictionary
41
+ A mapping from center node to set of all nodes in the graph
42
+ closer to that center node than to any other center node. The
43
+ keys of the dictionary are the element of `center_nodes`, and
44
+ the values of the dictionary form a partition of the nodes of
45
+ `G`.
46
+
47
+ Examples
48
+ --------
49
+ To get only the partition of the graph induced by the Voronoi cells,
50
+ take the collection of all values in the returned dictionary::
51
+
52
+ >>> G = nx.path_graph(6)
53
+ >>> center_nodes = {0, 3}
54
+ >>> cells = nx.voronoi_cells(G, center_nodes)
55
+ >>> partition = set(map(frozenset, cells.values()))
56
+ >>> sorted(map(sorted, partition))
57
+ [[0, 1], [2, 3, 4, 5]]
58
+
59
+ Raises
60
+ ------
61
+ ValueError
62
+ If `center_nodes` is empty.
63
+
64
+ References
65
+ ----------
66
+ .. [1] Erwig, Martin. (2000),"The graph Voronoi diagram with applications."
67
+ *Networks*, 36: 156--163.
68
+ https://doi.org/10.1002/1097-0037(200010)36:3<156::AID-NET2>3.0.CO;2-L
69
+
70
+ """
71
+ # Determine the shortest paths from any one of the center nodes to
72
+ # every node in the graph.
73
+ #
74
+ # This raises `ValueError` if `center_nodes` is an empty set.
75
+ paths = nx.multi_source_dijkstra_path(G, center_nodes, weight=weight)
76
+ # Determine the center node from which the shortest path originates.
77
+ nearest = {v: p[0] for v, p in paths.items()}
78
+ # Get the mapping from center node to all nodes closer to it than to
79
+ # any other center node.
80
+ cells = groups(nearest)
81
+ # We collect all unreachable nodes under a special key, if there are any.
82
+ unreachable = set(G) - set(nearest)
83
+ if unreachable:
84
+ cells["unreachable"] = unreachable
85
+ return cells
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (779 Bytes). View file
 
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/__pycache__/coreviews.cpython-311.pyc ADDED
Binary file (23.6 kB). View file
 
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/__pycache__/filters.cpython-311.pyc ADDED
Binary file (5.63 kB). View file
 
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/__pycache__/graph.cpython-311.pyc ADDED
Binary file (80 kB). View file
 
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/coreviews.py ADDED
@@ -0,0 +1,367 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Views of core data structures such as nested Mappings (e.g. dict-of-dicts).
2
+ These ``Views`` often restrict element access, with either the entire view or
3
+ layers of nested mappings being read-only.
4
+ """
5
+ from collections.abc import Mapping
6
+
7
+ __all__ = [
8
+ "AtlasView",
9
+ "AdjacencyView",
10
+ "MultiAdjacencyView",
11
+ "UnionAtlas",
12
+ "UnionAdjacency",
13
+ "UnionMultiInner",
14
+ "UnionMultiAdjacency",
15
+ "FilterAtlas",
16
+ "FilterAdjacency",
17
+ "FilterMultiInner",
18
+ "FilterMultiAdjacency",
19
+ ]
20
+
21
+
22
+ class AtlasView(Mapping):
23
+ """An AtlasView is a Read-only Mapping of Mappings.
24
+
25
+ It is a View into a dict-of-dict data structure.
26
+ The inner level of dict is read-write. But the
27
+ outer level is read-only.
28
+
29
+ See Also
30
+ ========
31
+ AdjacencyView: View into dict-of-dict-of-dict
32
+ MultiAdjacencyView: View into dict-of-dict-of-dict-of-dict
33
+ """
34
+
35
+ __slots__ = ("_atlas",)
36
+
37
+ def __getstate__(self):
38
+ return {"_atlas": self._atlas}
39
+
40
+ def __setstate__(self, state):
41
+ self._atlas = state["_atlas"]
42
+
43
+ def __init__(self, d):
44
+ self._atlas = d
45
+
46
+ def __len__(self):
47
+ return len(self._atlas)
48
+
49
+ def __iter__(self):
50
+ return iter(self._atlas)
51
+
52
+ def __getitem__(self, key):
53
+ return self._atlas[key]
54
+
55
+ def copy(self):
56
+ return {n: self[n].copy() for n in self._atlas}
57
+
58
+ def __str__(self):
59
+ return str(self._atlas) # {nbr: self[nbr] for nbr in self})
60
+
61
+ def __repr__(self):
62
+ return f"{self.__class__.__name__}({self._atlas!r})"
63
+
64
+
65
+ class AdjacencyView(AtlasView):
66
+ """An AdjacencyView is a Read-only Map of Maps of Maps.
67
+
68
+ It is a View into a dict-of-dict-of-dict data structure.
69
+ The inner level of dict is read-write. But the
70
+ outer levels are read-only.
71
+
72
+ See Also
73
+ ========
74
+ AtlasView: View into dict-of-dict
75
+ MultiAdjacencyView: View into dict-of-dict-of-dict-of-dict
76
+ """
77
+
78
+ __slots__ = () # Still uses AtlasView slots names _atlas
79
+
80
+ def __getitem__(self, name):
81
+ return AtlasView(self._atlas[name])
82
+
83
+ def copy(self):
84
+ return {n: self[n].copy() for n in self._atlas}
85
+
86
+
87
+ class MultiAdjacencyView(AdjacencyView):
88
+ """An MultiAdjacencyView is a Read-only Map of Maps of Maps of Maps.
89
+
90
+ It is a View into a dict-of-dict-of-dict-of-dict data structure.
91
+ The inner level of dict is read-write. But the
92
+ outer levels are read-only.
93
+
94
+ See Also
95
+ ========
96
+ AtlasView: View into dict-of-dict
97
+ AdjacencyView: View into dict-of-dict-of-dict
98
+ """
99
+
100
+ __slots__ = () # Still uses AtlasView slots names _atlas
101
+
102
+ def __getitem__(self, name):
103
+ return AdjacencyView(self._atlas[name])
104
+
105
+ def copy(self):
106
+ return {n: self[n].copy() for n in self._atlas}
107
+
108
+
109
+ class UnionAtlas(Mapping):
110
+ """A read-only union of two atlases (dict-of-dict).
111
+
112
+ The two dict-of-dicts represent the inner dict of
113
+ an Adjacency: `G.succ[node]` and `G.pred[node]`.
114
+ The inner level of dict of both hold attribute key:value
115
+ pairs and is read-write. But the outer level is read-only.
116
+
117
+ See Also
118
+ ========
119
+ UnionAdjacency: View into dict-of-dict-of-dict
120
+ UnionMultiAdjacency: View into dict-of-dict-of-dict-of-dict
121
+ """
122
+
123
+ __slots__ = ("_succ", "_pred")
124
+
125
+ def __getstate__(self):
126
+ return {"_succ": self._succ, "_pred": self._pred}
127
+
128
+ def __setstate__(self, state):
129
+ self._succ = state["_succ"]
130
+ self._pred = state["_pred"]
131
+
132
+ def __init__(self, succ, pred):
133
+ self._succ = succ
134
+ self._pred = pred
135
+
136
+ def __len__(self):
137
+ return len(self._succ.keys() | self._pred.keys())
138
+
139
+ def __iter__(self):
140
+ return iter(set(self._succ.keys()) | set(self._pred.keys()))
141
+
142
+ def __getitem__(self, key):
143
+ try:
144
+ return self._succ[key]
145
+ except KeyError:
146
+ return self._pred[key]
147
+
148
+ def copy(self):
149
+ result = {nbr: dd.copy() for nbr, dd in self._succ.items()}
150
+ for nbr, dd in self._pred.items():
151
+ if nbr in result:
152
+ result[nbr].update(dd)
153
+ else:
154
+ result[nbr] = dd.copy()
155
+ return result
156
+
157
+ def __str__(self):
158
+ return str({nbr: self[nbr] for nbr in self})
159
+
160
+ def __repr__(self):
161
+ return f"{self.__class__.__name__}({self._succ!r}, {self._pred!r})"
162
+
163
+
164
+ class UnionAdjacency(Mapping):
165
+ """A read-only union of dict Adjacencies as a Map of Maps of Maps.
166
+
167
+ The two input dict-of-dict-of-dicts represent the union of
168
+ `G.succ` and `G.pred`. Return values are UnionAtlas
169
+ The inner level of dict is read-write. But the
170
+ middle and outer levels are read-only.
171
+
172
+ succ : a dict-of-dict-of-dict {node: nbrdict}
173
+ pred : a dict-of-dict-of-dict {node: nbrdict}
174
+ The keys for the two dicts should be the same
175
+
176
+ See Also
177
+ ========
178
+ UnionAtlas: View into dict-of-dict
179
+ UnionMultiAdjacency: View into dict-of-dict-of-dict-of-dict
180
+ """
181
+
182
+ __slots__ = ("_succ", "_pred")
183
+
184
+ def __getstate__(self):
185
+ return {"_succ": self._succ, "_pred": self._pred}
186
+
187
+ def __setstate__(self, state):
188
+ self._succ = state["_succ"]
189
+ self._pred = state["_pred"]
190
+
191
+ def __init__(self, succ, pred):
192
+ # keys must be the same for two input dicts
193
+ assert len(set(succ.keys()) ^ set(pred.keys())) == 0
194
+ self._succ = succ
195
+ self._pred = pred
196
+
197
+ def __len__(self):
198
+ return len(self._succ) # length of each dict should be the same
199
+
200
+ def __iter__(self):
201
+ return iter(self._succ)
202
+
203
+ def __getitem__(self, nbr):
204
+ return UnionAtlas(self._succ[nbr], self._pred[nbr])
205
+
206
+ def copy(self):
207
+ return {n: self[n].copy() for n in self._succ}
208
+
209
+ def __str__(self):
210
+ return str({nbr: self[nbr] for nbr in self})
211
+
212
+ def __repr__(self):
213
+ return f"{self.__class__.__name__}({self._succ!r}, {self._pred!r})"
214
+
215
+
216
+ class UnionMultiInner(UnionAtlas):
217
+ """A read-only union of two inner dicts of MultiAdjacencies.
218
+
219
+ The two input dict-of-dict-of-dicts represent the union of
220
+ `G.succ[node]` and `G.pred[node]` for MultiDiGraphs.
221
+ Return values are UnionAtlas.
222
+ The inner level of dict is read-write. But the outer levels are read-only.
223
+
224
+ See Also
225
+ ========
226
+ UnionAtlas: View into dict-of-dict
227
+ UnionAdjacency: View into dict-of-dict-of-dict
228
+ UnionMultiAdjacency: View into dict-of-dict-of-dict-of-dict
229
+ """
230
+
231
+ __slots__ = () # Still uses UnionAtlas slots names _succ, _pred
232
+
233
+ def __getitem__(self, node):
234
+ in_succ = node in self._succ
235
+ in_pred = node in self._pred
236
+ if in_succ:
237
+ if in_pred:
238
+ return UnionAtlas(self._succ[node], self._pred[node])
239
+ return UnionAtlas(self._succ[node], {})
240
+ return UnionAtlas({}, self._pred[node])
241
+
242
+ def copy(self):
243
+ nodes = set(self._succ.keys()) | set(self._pred.keys())
244
+ return {n: self[n].copy() for n in nodes}
245
+
246
+
247
+ class UnionMultiAdjacency(UnionAdjacency):
248
+ """A read-only union of two dict MultiAdjacencies.
249
+
250
+ The two input dict-of-dict-of-dict-of-dicts represent the union of
251
+ `G.succ` and `G.pred` for MultiDiGraphs. Return values are UnionAdjacency.
252
+ The inner level of dict is read-write. But the outer levels are read-only.
253
+
254
+ See Also
255
+ ========
256
+ UnionAtlas: View into dict-of-dict
257
+ UnionMultiInner: View into dict-of-dict-of-dict
258
+ """
259
+
260
+ __slots__ = () # Still uses UnionAdjacency slots names _succ, _pred
261
+
262
+ def __getitem__(self, node):
263
+ return UnionMultiInner(self._succ[node], self._pred[node])
264
+
265
+
266
+ class FilterAtlas(Mapping): # nodedict, nbrdict, keydict
267
+ def __init__(self, d, NODE_OK):
268
+ self._atlas = d
269
+ self.NODE_OK = NODE_OK
270
+
271
+ def __len__(self):
272
+ return sum(1 for n in self)
273
+
274
+ def __iter__(self):
275
+ try: # check that NODE_OK has attr 'nodes'
276
+ node_ok_shorter = 2 * len(self.NODE_OK.nodes) < len(self._atlas)
277
+ except AttributeError:
278
+ node_ok_shorter = False
279
+ if node_ok_shorter:
280
+ return (n for n in self.NODE_OK.nodes if n in self._atlas)
281
+ return (n for n in self._atlas if self.NODE_OK(n))
282
+
283
+ def __getitem__(self, key):
284
+ if key in self._atlas and self.NODE_OK(key):
285
+ return self._atlas[key]
286
+ raise KeyError(f"Key {key} not found")
287
+
288
+ def __str__(self):
289
+ return str({nbr: self[nbr] for nbr in self})
290
+
291
+ def __repr__(self):
292
+ return f"{self.__class__.__name__}({self._atlas!r}, {self.NODE_OK!r})"
293
+
294
+
295
+ class FilterAdjacency(Mapping): # edgedict
296
+ def __init__(self, d, NODE_OK, EDGE_OK):
297
+ self._atlas = d
298
+ self.NODE_OK = NODE_OK
299
+ self.EDGE_OK = EDGE_OK
300
+
301
+ def __len__(self):
302
+ return sum(1 for n in self)
303
+
304
+ def __iter__(self):
305
+ try: # check that NODE_OK has attr 'nodes'
306
+ node_ok_shorter = 2 * len(self.NODE_OK.nodes) < len(self._atlas)
307
+ except AttributeError:
308
+ node_ok_shorter = False
309
+ if node_ok_shorter:
310
+ return (n for n in self.NODE_OK.nodes if n in self._atlas)
311
+ return (n for n in self._atlas if self.NODE_OK(n))
312
+
313
+ def __getitem__(self, node):
314
+ if node in self._atlas and self.NODE_OK(node):
315
+
316
+ def new_node_ok(nbr):
317
+ return self.NODE_OK(nbr) and self.EDGE_OK(node, nbr)
318
+
319
+ return FilterAtlas(self._atlas[node], new_node_ok)
320
+ raise KeyError(f"Key {node} not found")
321
+
322
+ def __str__(self):
323
+ return str({nbr: self[nbr] for nbr in self})
324
+
325
+ def __repr__(self):
326
+ name = self.__class__.__name__
327
+ return f"{name}({self._atlas!r}, {self.NODE_OK!r}, {self.EDGE_OK!r})"
328
+
329
+
330
+ class FilterMultiInner(FilterAdjacency): # muliedge_seconddict
331
+ def __iter__(self):
332
+ try: # check that NODE_OK has attr 'nodes'
333
+ node_ok_shorter = 2 * len(self.NODE_OK.nodes) < len(self._atlas)
334
+ except AttributeError:
335
+ node_ok_shorter = False
336
+ if node_ok_shorter:
337
+ my_nodes = (n for n in self.NODE_OK.nodes if n in self._atlas)
338
+ else:
339
+ my_nodes = (n for n in self._atlas if self.NODE_OK(n))
340
+ for n in my_nodes:
341
+ some_keys_ok = False
342
+ for key in self._atlas[n]:
343
+ if self.EDGE_OK(n, key):
344
+ some_keys_ok = True
345
+ break
346
+ if some_keys_ok is True:
347
+ yield n
348
+
349
+ def __getitem__(self, nbr):
350
+ if nbr in self._atlas and self.NODE_OK(nbr):
351
+
352
+ def new_node_ok(key):
353
+ return self.EDGE_OK(nbr, key)
354
+
355
+ return FilterAtlas(self._atlas[nbr], new_node_ok)
356
+ raise KeyError(f"Key {nbr} not found")
357
+
358
+
359
+ class FilterMultiAdjacency(FilterAdjacency): # multiedgedict
360
+ def __getitem__(self, node):
361
+ if node in self._atlas and self.NODE_OK(node):
362
+
363
+ def edge_ok(nbr, key):
364
+ return self.NODE_OK(nbr) and self.EDGE_OK(node, nbr, key)
365
+
366
+ return FilterMultiInner(self._atlas[node], self.NODE_OK, edge_ok)
367
+ raise KeyError(f"Key {node} not found")
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/digraph.py ADDED
@@ -0,0 +1,1323 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Base class for directed graphs."""
2
+ from copy import deepcopy
3
+ from functools import cached_property
4
+
5
+ import networkx as nx
6
+ from networkx import convert
7
+ from networkx.classes.coreviews import AdjacencyView
8
+ from networkx.classes.graph import Graph
9
+ from networkx.classes.reportviews import (
10
+ DiDegreeView,
11
+ InDegreeView,
12
+ InEdgeView,
13
+ OutDegreeView,
14
+ OutEdgeView,
15
+ )
16
+ from networkx.exception import NetworkXError
17
+
18
+ __all__ = ["DiGraph"]
19
+
20
+
21
+ class _CachedPropertyResetterAdjAndSucc:
22
+ """Data Descriptor class that syncs and resets cached properties adj and succ
23
+
24
+ The cached properties `adj` and `succ` are reset whenever `_adj` or `_succ`
25
+ are set to new objects. In addition, the attributes `_succ` and `_adj`
26
+ are synced so these two names point to the same object.
27
+
28
+ This object sits on a class and ensures that any instance of that
29
+ class clears its cached properties "succ" and "adj" whenever the
30
+ underlying instance attributes "_succ" or "_adj" are set to a new object.
31
+ It only affects the set process of the obj._adj and obj._succ attribute.
32
+ All get/del operations act as they normally would.
33
+
34
+ For info on Data Descriptors see: https://docs.python.org/3/howto/descriptor.html
35
+ """
36
+
37
+ def __set__(self, obj, value):
38
+ od = obj.__dict__
39
+ od["_adj"] = value
40
+ od["_succ"] = value
41
+ # reset cached properties
42
+ if "adj" in od:
43
+ del od["adj"]
44
+ if "succ" in od:
45
+ del od["succ"]
46
+
47
+
48
+ class _CachedPropertyResetterPred:
49
+ """Data Descriptor class for _pred that resets ``pred`` cached_property when needed
50
+
51
+ This assumes that the ``cached_property`` ``G.pred`` should be reset whenever
52
+ ``G._pred`` is set to a new value.
53
+
54
+ This object sits on a class and ensures that any instance of that
55
+ class clears its cached property "pred" whenever the underlying
56
+ instance attribute "_pred" is set to a new object. It only affects
57
+ the set process of the obj._pred attribute. All get/del operations
58
+ act as they normally would.
59
+
60
+ For info on Data Descriptors see: https://docs.python.org/3/howto/descriptor.html
61
+ """
62
+
63
+ def __set__(self, obj, value):
64
+ od = obj.__dict__
65
+ od["_pred"] = value
66
+ if "pred" in od:
67
+ del od["pred"]
68
+
69
+
70
+ class DiGraph(Graph):
71
+ """
72
+ Base class for directed graphs.
73
+
74
+ A DiGraph stores nodes and edges with optional data, or attributes.
75
+
76
+ DiGraphs hold directed edges. Self loops are allowed but multiple
77
+ (parallel) edges are not.
78
+
79
+ Nodes can be arbitrary (hashable) Python objects with optional
80
+ key/value attributes. By convention `None` is not used as a node.
81
+
82
+ Edges are represented as links between nodes with optional
83
+ key/value attributes.
84
+
85
+ Parameters
86
+ ----------
87
+ incoming_graph_data : input graph (optional, default: None)
88
+ Data to initialize graph. If None (default) an empty
89
+ graph is created. The data can be any format that is supported
90
+ by the to_networkx_graph() function, currently including edge list,
91
+ dict of dicts, dict of lists, NetworkX graph, 2D NumPy array, SciPy
92
+ sparse matrix, or PyGraphviz graph.
93
+
94
+ attr : keyword arguments, optional (default= no attributes)
95
+ Attributes to add to graph as key=value pairs.
96
+
97
+ See Also
98
+ --------
99
+ Graph
100
+ MultiGraph
101
+ MultiDiGraph
102
+
103
+ Examples
104
+ --------
105
+ Create an empty graph structure (a "null graph") with no nodes and
106
+ no edges.
107
+
108
+ >>> G = nx.DiGraph()
109
+
110
+ G can be grown in several ways.
111
+
112
+ **Nodes:**
113
+
114
+ Add one node at a time:
115
+
116
+ >>> G.add_node(1)
117
+
118
+ Add the nodes from any container (a list, dict, set or
119
+ even the lines from a file or the nodes from another graph).
120
+
121
+ >>> G.add_nodes_from([2, 3])
122
+ >>> G.add_nodes_from(range(100, 110))
123
+ >>> H = nx.path_graph(10)
124
+ >>> G.add_nodes_from(H)
125
+
126
+ In addition to strings and integers any hashable Python object
127
+ (except None) can represent a node, e.g. a customized node object,
128
+ or even another Graph.
129
+
130
+ >>> G.add_node(H)
131
+
132
+ **Edges:**
133
+
134
+ G can also be grown by adding edges.
135
+
136
+ Add one edge,
137
+
138
+ >>> G.add_edge(1, 2)
139
+
140
+ a list of edges,
141
+
142
+ >>> G.add_edges_from([(1, 2), (1, 3)])
143
+
144
+ or a collection of edges,
145
+
146
+ >>> G.add_edges_from(H.edges)
147
+
148
+ If some edges connect nodes not yet in the graph, the nodes
149
+ are added automatically. There are no errors when adding
150
+ nodes or edges that already exist.
151
+
152
+ **Attributes:**
153
+
154
+ Each graph, node, and edge can hold key/value attribute pairs
155
+ in an associated attribute dictionary (the keys must be hashable).
156
+ By default these are empty, but can be added or changed using
157
+ add_edge, add_node or direct manipulation of the attribute
158
+ dictionaries named graph, node and edge respectively.
159
+
160
+ >>> G = nx.DiGraph(day="Friday")
161
+ >>> G.graph
162
+ {'day': 'Friday'}
163
+
164
+ Add node attributes using add_node(), add_nodes_from() or G.nodes
165
+
166
+ >>> G.add_node(1, time="5pm")
167
+ >>> G.add_nodes_from([3], time="2pm")
168
+ >>> G.nodes[1]
169
+ {'time': '5pm'}
170
+ >>> G.nodes[1]["room"] = 714
171
+ >>> del G.nodes[1]["room"] # remove attribute
172
+ >>> list(G.nodes(data=True))
173
+ [(1, {'time': '5pm'}), (3, {'time': '2pm'})]
174
+
175
+ Add edge attributes using add_edge(), add_edges_from(), subscript
176
+ notation, or G.edges.
177
+
178
+ >>> G.add_edge(1, 2, weight=4.7)
179
+ >>> G.add_edges_from([(3, 4), (4, 5)], color="red")
180
+ >>> G.add_edges_from([(1, 2, {"color": "blue"}), (2, 3, {"weight": 8})])
181
+ >>> G[1][2]["weight"] = 4.7
182
+ >>> G.edges[1, 2]["weight"] = 4
183
+
184
+ Warning: we protect the graph data structure by making `G.edges[1, 2]` a
185
+ read-only dict-like structure. However, you can assign to attributes
186
+ in e.g. `G.edges[1, 2]`. Thus, use 2 sets of brackets to add/change
187
+ data attributes: `G.edges[1, 2]['weight'] = 4`
188
+ (For multigraphs: `MG.edges[u, v, key][name] = value`).
189
+
190
+ **Shortcuts:**
191
+
192
+ Many common graph features allow python syntax to speed reporting.
193
+
194
+ >>> 1 in G # check if node in graph
195
+ True
196
+ >>> [n for n in G if n < 3] # iterate through nodes
197
+ [1, 2]
198
+ >>> len(G) # number of nodes in graph
199
+ 5
200
+
201
+ Often the best way to traverse all edges of a graph is via the neighbors.
202
+ The neighbors are reported as an adjacency-dict `G.adj` or `G.adjacency()`
203
+
204
+ >>> for n, nbrsdict in G.adjacency():
205
+ ... for nbr, eattr in nbrsdict.items():
206
+ ... if "weight" in eattr:
207
+ ... # Do something useful with the edges
208
+ ... pass
209
+
210
+ But the edges reporting object is often more convenient:
211
+
212
+ >>> for u, v, weight in G.edges(data="weight"):
213
+ ... if weight is not None:
214
+ ... # Do something useful with the edges
215
+ ... pass
216
+
217
+ **Reporting:**
218
+
219
+ Simple graph information is obtained using object-attributes and methods.
220
+ Reporting usually provides views instead of containers to reduce memory
221
+ usage. The views update as the graph is updated similarly to dict-views.
222
+ The objects `nodes`, `edges` and `adj` provide access to data attributes
223
+ via lookup (e.g. `nodes[n]`, `edges[u, v]`, `adj[u][v]`) and iteration
224
+ (e.g. `nodes.items()`, `nodes.data('color')`,
225
+ `nodes.data('color', default='blue')` and similarly for `edges`)
226
+ Views exist for `nodes`, `edges`, `neighbors()`/`adj` and `degree`.
227
+
228
+ For details on these and other miscellaneous methods, see below.
229
+
230
+ **Subclasses (Advanced):**
231
+
232
+ The Graph class uses a dict-of-dict-of-dict data structure.
233
+ The outer dict (node_dict) holds adjacency information keyed by node.
234
+ The next dict (adjlist_dict) represents the adjacency information and holds
235
+ edge data keyed by neighbor. The inner dict (edge_attr_dict) represents
236
+ the edge data and holds edge attribute values keyed by attribute names.
237
+
238
+ Each of these three dicts can be replaced in a subclass by a user defined
239
+ dict-like object. In general, the dict-like features should be
240
+ maintained but extra features can be added. To replace one of the
241
+ dicts create a new graph class by changing the class(!) variable
242
+ holding the factory for that dict-like structure. The variable names are
243
+ node_dict_factory, node_attr_dict_factory, adjlist_inner_dict_factory,
244
+ adjlist_outer_dict_factory, edge_attr_dict_factory and graph_attr_dict_factory.
245
+
246
+ node_dict_factory : function, (default: dict)
247
+ Factory function to be used to create the dict containing node
248
+ attributes, keyed by node id.
249
+ It should require no arguments and return a dict-like object
250
+
251
+ node_attr_dict_factory: function, (default: dict)
252
+ Factory function to be used to create the node attribute
253
+ dict which holds attribute values keyed by attribute name.
254
+ It should require no arguments and return a dict-like object
255
+
256
+ adjlist_outer_dict_factory : function, (default: dict)
257
+ Factory function to be used to create the outer-most dict
258
+ in the data structure that holds adjacency info keyed by node.
259
+ It should require no arguments and return a dict-like object.
260
+
261
+ adjlist_inner_dict_factory : function, optional (default: dict)
262
+ Factory function to be used to create the adjacency list
263
+ dict which holds edge data keyed by neighbor.
264
+ It should require no arguments and return a dict-like object
265
+
266
+ edge_attr_dict_factory : function, optional (default: dict)
267
+ Factory function to be used to create the edge attribute
268
+ dict which holds attribute values keyed by attribute name.
269
+ It should require no arguments and return a dict-like object.
270
+
271
+ graph_attr_dict_factory : function, (default: dict)
272
+ Factory function to be used to create the graph attribute
273
+ dict which holds attribute values keyed by attribute name.
274
+ It should require no arguments and return a dict-like object.
275
+
276
+ Typically, if your extension doesn't impact the data structure all
277
+ methods will inherited without issue except: `to_directed/to_undirected`.
278
+ By default these methods create a DiGraph/Graph class and you probably
279
+ want them to create your extension of a DiGraph/Graph. To facilitate
280
+ this we define two class variables that you can set in your subclass.
281
+
282
+ to_directed_class : callable, (default: DiGraph or MultiDiGraph)
283
+ Class to create a new graph structure in the `to_directed` method.
284
+ If `None`, a NetworkX class (DiGraph or MultiDiGraph) is used.
285
+
286
+ to_undirected_class : callable, (default: Graph or MultiGraph)
287
+ Class to create a new graph structure in the `to_undirected` method.
288
+ If `None`, a NetworkX class (Graph or MultiGraph) is used.
289
+
290
+ **Subclassing Example**
291
+
292
+ Create a low memory graph class that effectively disallows edge
293
+ attributes by using a single attribute dict for all edges.
294
+ This reduces the memory used, but you lose edge attributes.
295
+
296
+ >>> class ThinGraph(nx.Graph):
297
+ ... all_edge_dict = {"weight": 1}
298
+ ...
299
+ ... def single_edge_dict(self):
300
+ ... return self.all_edge_dict
301
+ ...
302
+ ... edge_attr_dict_factory = single_edge_dict
303
+ >>> G = ThinGraph()
304
+ >>> G.add_edge(2, 1)
305
+ >>> G[2][1]
306
+ {'weight': 1}
307
+ >>> G.add_edge(2, 2)
308
+ >>> G[2][1] is G[2][2]
309
+ True
310
+ """
311
+
312
+ _adj = _CachedPropertyResetterAdjAndSucc() # type: ignore[assignment]
313
+ _succ = _adj # type: ignore[has-type]
314
+ _pred = _CachedPropertyResetterPred()
315
+
316
+ def __init__(self, incoming_graph_data=None, **attr):
317
+ """Initialize a graph with edges, name, or graph attributes.
318
+
319
+ Parameters
320
+ ----------
321
+ incoming_graph_data : input graph (optional, default: None)
322
+ Data to initialize graph. If None (default) an empty
323
+ graph is created. The data can be an edge list, or any
324
+ NetworkX graph object. If the corresponding optional Python
325
+ packages are installed the data can also be a 2D NumPy array, a
326
+ SciPy sparse array, or a PyGraphviz graph.
327
+
328
+ attr : keyword arguments, optional (default= no attributes)
329
+ Attributes to add to graph as key=value pairs.
330
+
331
+ See Also
332
+ --------
333
+ convert
334
+
335
+ Examples
336
+ --------
337
+ >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
338
+ >>> G = nx.Graph(name="my graph")
339
+ >>> e = [(1, 2), (2, 3), (3, 4)] # list of edges
340
+ >>> G = nx.Graph(e)
341
+
342
+ Arbitrary graph attribute pairs (key=value) may be assigned
343
+
344
+ >>> G = nx.Graph(e, day="Friday")
345
+ >>> G.graph
346
+ {'day': 'Friday'}
347
+
348
+ """
349
+ self.graph = self.graph_attr_dict_factory() # dictionary for graph attributes
350
+ self._node = self.node_dict_factory() # dictionary for node attr
351
+ # We store two adjacency lists:
352
+ # the predecessors of node n are stored in the dict self._pred
353
+ # the successors of node n are stored in the dict self._succ=self._adj
354
+ self._adj = self.adjlist_outer_dict_factory() # empty adjacency dict successor
355
+ self._pred = self.adjlist_outer_dict_factory() # predecessor
356
+ # Note: self._succ = self._adj # successor
357
+
358
+ # attempt to load graph with data
359
+ if incoming_graph_data is not None:
360
+ convert.to_networkx_graph(incoming_graph_data, create_using=self)
361
+ # load graph attributes (must be after convert)
362
+ self.graph.update(attr)
363
+
364
+ @cached_property
365
+ def adj(self):
366
+ """Graph adjacency object holding the neighbors of each node.
367
+
368
+ This object is a read-only dict-like structure with node keys
369
+ and neighbor-dict values. The neighbor-dict is keyed by neighbor
370
+ to the edge-data-dict. So `G.adj[3][2]['color'] = 'blue'` sets
371
+ the color of the edge `(3, 2)` to `"blue"`.
372
+
373
+ Iterating over G.adj behaves like a dict. Useful idioms include
374
+ `for nbr, datadict in G.adj[n].items():`.
375
+
376
+ The neighbor information is also provided by subscripting the graph.
377
+ So `for nbr, foovalue in G[node].data('foo', default=1):` works.
378
+
379
+ For directed graphs, `G.adj` holds outgoing (successor) info.
380
+ """
381
+ return AdjacencyView(self._succ)
382
+
383
+ @cached_property
384
+ def succ(self):
385
+ """Graph adjacency object holding the successors of each node.
386
+
387
+ This object is a read-only dict-like structure with node keys
388
+ and neighbor-dict values. The neighbor-dict is keyed by neighbor
389
+ to the edge-data-dict. So `G.succ[3][2]['color'] = 'blue'` sets
390
+ the color of the edge `(3, 2)` to `"blue"`.
391
+
392
+ Iterating over G.succ behaves like a dict. Useful idioms include
393
+ `for nbr, datadict in G.succ[n].items():`. A data-view not provided
394
+ by dicts also exists: `for nbr, foovalue in G.succ[node].data('foo'):`
395
+ and a default can be set via a `default` argument to the `data` method.
396
+
397
+ The neighbor information is also provided by subscripting the graph.
398
+ So `for nbr, foovalue in G[node].data('foo', default=1):` works.
399
+
400
+ For directed graphs, `G.adj` is identical to `G.succ`.
401
+ """
402
+ return AdjacencyView(self._succ)
403
+
404
+ @cached_property
405
+ def pred(self):
406
+ """Graph adjacency object holding the predecessors of each node.
407
+
408
+ This object is a read-only dict-like structure with node keys
409
+ and neighbor-dict values. The neighbor-dict is keyed by neighbor
410
+ to the edge-data-dict. So `G.pred[2][3]['color'] = 'blue'` sets
411
+ the color of the edge `(3, 2)` to `"blue"`.
412
+
413
+ Iterating over G.pred behaves like a dict. Useful idioms include
414
+ `for nbr, datadict in G.pred[n].items():`. A data-view not provided
415
+ by dicts also exists: `for nbr, foovalue in G.pred[node].data('foo'):`
416
+ A default can be set via a `default` argument to the `data` method.
417
+ """
418
+ return AdjacencyView(self._pred)
419
+
420
+ def add_node(self, node_for_adding, **attr):
421
+ """Add a single node `node_for_adding` and update node attributes.
422
+
423
+ Parameters
424
+ ----------
425
+ node_for_adding : node
426
+ A node can be any hashable Python object except None.
427
+ attr : keyword arguments, optional
428
+ Set or change node attributes using key=value.
429
+
430
+ See Also
431
+ --------
432
+ add_nodes_from
433
+
434
+ Examples
435
+ --------
436
+ >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
437
+ >>> G.add_node(1)
438
+ >>> G.add_node("Hello")
439
+ >>> K3 = nx.Graph([(0, 1), (1, 2), (2, 0)])
440
+ >>> G.add_node(K3)
441
+ >>> G.number_of_nodes()
442
+ 3
443
+
444
+ Use keywords set/change node attributes:
445
+
446
+ >>> G.add_node(1, size=10)
447
+ >>> G.add_node(3, weight=0.4, UTM=("13S", 382871, 3972649))
448
+
449
+ Notes
450
+ -----
451
+ A hashable object is one that can be used as a key in a Python
452
+ dictionary. This includes strings, numbers, tuples of strings
453
+ and numbers, etc.
454
+
455
+ On many platforms hashable items also include mutables such as
456
+ NetworkX Graphs, though one should be careful that the hash
457
+ doesn't change on mutables.
458
+ """
459
+ if node_for_adding not in self._succ:
460
+ if node_for_adding is None:
461
+ raise ValueError("None cannot be a node")
462
+ self._succ[node_for_adding] = self.adjlist_inner_dict_factory()
463
+ self._pred[node_for_adding] = self.adjlist_inner_dict_factory()
464
+ attr_dict = self._node[node_for_adding] = self.node_attr_dict_factory()
465
+ attr_dict.update(attr)
466
+ else: # update attr even if node already exists
467
+ self._node[node_for_adding].update(attr)
468
+
469
+ def add_nodes_from(self, nodes_for_adding, **attr):
470
+ """Add multiple nodes.
471
+
472
+ Parameters
473
+ ----------
474
+ nodes_for_adding : iterable container
475
+ A container of nodes (list, dict, set, etc.).
476
+ OR
477
+ A container of (node, attribute dict) tuples.
478
+ Node attributes are updated using the attribute dict.
479
+ attr : keyword arguments, optional (default= no attributes)
480
+ Update attributes for all nodes in nodes.
481
+ Node attributes specified in nodes as a tuple take
482
+ precedence over attributes specified via keyword arguments.
483
+
484
+ See Also
485
+ --------
486
+ add_node
487
+
488
+ Notes
489
+ -----
490
+ When adding nodes from an iterator over the graph you are changing,
491
+ a `RuntimeError` can be raised with message:
492
+ `RuntimeError: dictionary changed size during iteration`. This
493
+ happens when the graph's underlying dictionary is modified during
494
+ iteration. To avoid this error, evaluate the iterator into a separate
495
+ object, e.g. by using `list(iterator_of_nodes)`, and pass this
496
+ object to `G.add_nodes_from`.
497
+
498
+ Examples
499
+ --------
500
+ >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
501
+ >>> G.add_nodes_from("Hello")
502
+ >>> K3 = nx.Graph([(0, 1), (1, 2), (2, 0)])
503
+ >>> G.add_nodes_from(K3)
504
+ >>> sorted(G.nodes(), key=str)
505
+ [0, 1, 2, 'H', 'e', 'l', 'o']
506
+
507
+ Use keywords to update specific node attributes for every node.
508
+
509
+ >>> G.add_nodes_from([1, 2], size=10)
510
+ >>> G.add_nodes_from([3, 4], weight=0.4)
511
+
512
+ Use (node, attrdict) tuples to update attributes for specific nodes.
513
+
514
+ >>> G.add_nodes_from([(1, dict(size=11)), (2, {"color": "blue"})])
515
+ >>> G.nodes[1]["size"]
516
+ 11
517
+ >>> H = nx.Graph()
518
+ >>> H.add_nodes_from(G.nodes(data=True))
519
+ >>> H.nodes[1]["size"]
520
+ 11
521
+
522
+ Evaluate an iterator over a graph if using it to modify the same graph
523
+
524
+ >>> G = nx.DiGraph([(0, 1), (1, 2), (3, 4)])
525
+ >>> # wrong way - will raise RuntimeError
526
+ >>> # G.add_nodes_from(n + 1 for n in G.nodes)
527
+ >>> # correct way
528
+ >>> G.add_nodes_from(list(n + 1 for n in G.nodes))
529
+ """
530
+ for n in nodes_for_adding:
531
+ try:
532
+ newnode = n not in self._node
533
+ newdict = attr
534
+ except TypeError:
535
+ n, ndict = n
536
+ newnode = n not in self._node
537
+ newdict = attr.copy()
538
+ newdict.update(ndict)
539
+ if newnode:
540
+ if n is None:
541
+ raise ValueError("None cannot be a node")
542
+ self._succ[n] = self.adjlist_inner_dict_factory()
543
+ self._pred[n] = self.adjlist_inner_dict_factory()
544
+ self._node[n] = self.node_attr_dict_factory()
545
+ self._node[n].update(newdict)
546
+
547
+ def remove_node(self, n):
548
+ """Remove node n.
549
+
550
+ Removes the node n and all adjacent edges.
551
+ Attempting to remove a nonexistent node will raise an exception.
552
+
553
+ Parameters
554
+ ----------
555
+ n : node
556
+ A node in the graph
557
+
558
+ Raises
559
+ ------
560
+ NetworkXError
561
+ If n is not in the graph.
562
+
563
+ See Also
564
+ --------
565
+ remove_nodes_from
566
+
567
+ Examples
568
+ --------
569
+ >>> G = nx.path_graph(3) # or DiGraph, MultiGraph, MultiDiGraph, etc
570
+ >>> list(G.edges)
571
+ [(0, 1), (1, 2)]
572
+ >>> G.remove_node(1)
573
+ >>> list(G.edges)
574
+ []
575
+
576
+ """
577
+ try:
578
+ nbrs = self._succ[n]
579
+ del self._node[n]
580
+ except KeyError as err: # NetworkXError if n not in self
581
+ raise NetworkXError(f"The node {n} is not in the digraph.") from err
582
+ for u in nbrs:
583
+ del self._pred[u][n] # remove all edges n-u in digraph
584
+ del self._succ[n] # remove node from succ
585
+ for u in self._pred[n]:
586
+ del self._succ[u][n] # remove all edges n-u in digraph
587
+ del self._pred[n] # remove node from pred
588
+
589
+ def remove_nodes_from(self, nodes):
590
+ """Remove multiple nodes.
591
+
592
+ Parameters
593
+ ----------
594
+ nodes : iterable container
595
+ A container of nodes (list, dict, set, etc.). If a node
596
+ in the container is not in the graph it is silently ignored.
597
+
598
+ See Also
599
+ --------
600
+ remove_node
601
+
602
+ Notes
603
+ -----
604
+ When removing nodes from an iterator over the graph you are changing,
605
+ a `RuntimeError` will be raised with message:
606
+ `RuntimeError: dictionary changed size during iteration`. This
607
+ happens when the graph's underlying dictionary is modified during
608
+ iteration. To avoid this error, evaluate the iterator into a separate
609
+ object, e.g. by using `list(iterator_of_nodes)`, and pass this
610
+ object to `G.remove_nodes_from`.
611
+
612
+ Examples
613
+ --------
614
+ >>> G = nx.path_graph(3) # or DiGraph, MultiGraph, MultiDiGraph, etc
615
+ >>> e = list(G.nodes)
616
+ >>> e
617
+ [0, 1, 2]
618
+ >>> G.remove_nodes_from(e)
619
+ >>> list(G.nodes)
620
+ []
621
+
622
+ Evaluate an iterator over a graph if using it to modify the same graph
623
+
624
+ >>> G = nx.DiGraph([(0, 1), (1, 2), (3, 4)])
625
+ >>> # this command will fail, as the graph's dict is modified during iteration
626
+ >>> # G.remove_nodes_from(n for n in G.nodes if n < 2)
627
+ >>> # this command will work, since the dictionary underlying graph is not modified
628
+ >>> G.remove_nodes_from(list(n for n in G.nodes if n < 2))
629
+ """
630
+ for n in nodes:
631
+ try:
632
+ succs = self._succ[n]
633
+ del self._node[n]
634
+ for u in succs:
635
+ del self._pred[u][n] # remove all edges n-u in digraph
636
+ del self._succ[n] # now remove node
637
+ for u in self._pred[n]:
638
+ del self._succ[u][n] # remove all edges n-u in digraph
639
+ del self._pred[n] # now remove node
640
+ except KeyError:
641
+ pass # silent failure on remove
642
+
643
+ def add_edge(self, u_of_edge, v_of_edge, **attr):
644
+ """Add an edge between u and v.
645
+
646
+ The nodes u and v will be automatically added if they are
647
+ not already in the graph.
648
+
649
+ Edge attributes can be specified with keywords or by directly
650
+ accessing the edge's attribute dictionary. See examples below.
651
+
652
+ Parameters
653
+ ----------
654
+ u_of_edge, v_of_edge : nodes
655
+ Nodes can be, for example, strings or numbers.
656
+ Nodes must be hashable (and not None) Python objects.
657
+ attr : keyword arguments, optional
658
+ Edge data (or labels or objects) can be assigned using
659
+ keyword arguments.
660
+
661
+ See Also
662
+ --------
663
+ add_edges_from : add a collection of edges
664
+
665
+ Notes
666
+ -----
667
+ Adding an edge that already exists updates the edge data.
668
+
669
+ Many NetworkX algorithms designed for weighted graphs use
670
+ an edge attribute (by default `weight`) to hold a numerical value.
671
+
672
+ Examples
673
+ --------
674
+ The following all add the edge e=(1, 2) to graph G:
675
+
676
+ >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
677
+ >>> e = (1, 2)
678
+ >>> G.add_edge(1, 2) # explicit two-node form
679
+ >>> G.add_edge(*e) # single edge as tuple of two nodes
680
+ >>> G.add_edges_from([(1, 2)]) # add edges from iterable container
681
+
682
+ Associate data to edges using keywords:
683
+
684
+ >>> G.add_edge(1, 2, weight=3)
685
+ >>> G.add_edge(1, 3, weight=7, capacity=15, length=342.7)
686
+
687
+ For non-string attribute keys, use subscript notation.
688
+
689
+ >>> G.add_edge(1, 2)
690
+ >>> G[1][2].update({0: 5})
691
+ >>> G.edges[1, 2].update({0: 5})
692
+ """
693
+ u, v = u_of_edge, v_of_edge
694
+ # add nodes
695
+ if u not in self._succ:
696
+ if u is None:
697
+ raise ValueError("None cannot be a node")
698
+ self._succ[u] = self.adjlist_inner_dict_factory()
699
+ self._pred[u] = self.adjlist_inner_dict_factory()
700
+ self._node[u] = self.node_attr_dict_factory()
701
+ if v not in self._succ:
702
+ if v is None:
703
+ raise ValueError("None cannot be a node")
704
+ self._succ[v] = self.adjlist_inner_dict_factory()
705
+ self._pred[v] = self.adjlist_inner_dict_factory()
706
+ self._node[v] = self.node_attr_dict_factory()
707
+ # add the edge
708
+ datadict = self._adj[u].get(v, self.edge_attr_dict_factory())
709
+ datadict.update(attr)
710
+ self._succ[u][v] = datadict
711
+ self._pred[v][u] = datadict
712
+
713
+ def add_edges_from(self, ebunch_to_add, **attr):
714
+ """Add all the edges in ebunch_to_add.
715
+
716
+ Parameters
717
+ ----------
718
+ ebunch_to_add : container of edges
719
+ Each edge given in the container will be added to the
720
+ graph. The edges must be given as 2-tuples (u, v) or
721
+ 3-tuples (u, v, d) where d is a dictionary containing edge data.
722
+ attr : keyword arguments, optional
723
+ Edge data (or labels or objects) can be assigned using
724
+ keyword arguments.
725
+
726
+ See Also
727
+ --------
728
+ add_edge : add a single edge
729
+ add_weighted_edges_from : convenient way to add weighted edges
730
+
731
+ Notes
732
+ -----
733
+ Adding the same edge twice has no effect but any edge data
734
+ will be updated when each duplicate edge is added.
735
+
736
+ Edge attributes specified in an ebunch take precedence over
737
+ attributes specified via keyword arguments.
738
+
739
+ When adding edges from an iterator over the graph you are changing,
740
+ a `RuntimeError` can be raised with message:
741
+ `RuntimeError: dictionary changed size during iteration`. This
742
+ happens when the graph's underlying dictionary is modified during
743
+ iteration. To avoid this error, evaluate the iterator into a separate
744
+ object, e.g. by using `list(iterator_of_edges)`, and pass this
745
+ object to `G.add_edges_from`.
746
+
747
+ Examples
748
+ --------
749
+ >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
750
+ >>> G.add_edges_from([(0, 1), (1, 2)]) # using a list of edge tuples
751
+ >>> e = zip(range(0, 3), range(1, 4))
752
+ >>> G.add_edges_from(e) # Add the path graph 0-1-2-3
753
+
754
+ Associate data to edges
755
+
756
+ >>> G.add_edges_from([(1, 2), (2, 3)], weight=3)
757
+ >>> G.add_edges_from([(3, 4), (1, 4)], label="WN2898")
758
+
759
+ Evaluate an iterator over a graph if using it to modify the same graph
760
+
761
+ >>> G = nx.DiGraph([(1, 2), (2, 3), (3, 4)])
762
+ >>> # Grow graph by one new node, adding edges to all existing nodes.
763
+ >>> # wrong way - will raise RuntimeError
764
+ >>> # G.add_edges_from(((5, n) for n in G.nodes))
765
+ >>> # right way - note that there will be no self-edge for node 5
766
+ >>> G.add_edges_from(list((5, n) for n in G.nodes))
767
+ """
768
+ for e in ebunch_to_add:
769
+ ne = len(e)
770
+ if ne == 3:
771
+ u, v, dd = e
772
+ elif ne == 2:
773
+ u, v = e
774
+ dd = {}
775
+ else:
776
+ raise NetworkXError(f"Edge tuple {e} must be a 2-tuple or 3-tuple.")
777
+ if u not in self._succ:
778
+ if u is None:
779
+ raise ValueError("None cannot be a node")
780
+ self._succ[u] = self.adjlist_inner_dict_factory()
781
+ self._pred[u] = self.adjlist_inner_dict_factory()
782
+ self._node[u] = self.node_attr_dict_factory()
783
+ if v not in self._succ:
784
+ if v is None:
785
+ raise ValueError("None cannot be a node")
786
+ self._succ[v] = self.adjlist_inner_dict_factory()
787
+ self._pred[v] = self.adjlist_inner_dict_factory()
788
+ self._node[v] = self.node_attr_dict_factory()
789
+ datadict = self._adj[u].get(v, self.edge_attr_dict_factory())
790
+ datadict.update(attr)
791
+ datadict.update(dd)
792
+ self._succ[u][v] = datadict
793
+ self._pred[v][u] = datadict
794
+
795
+ def remove_edge(self, u, v):
796
+ """Remove the edge between u and v.
797
+
798
+ Parameters
799
+ ----------
800
+ u, v : nodes
801
+ Remove the edge between nodes u and v.
802
+
803
+ Raises
804
+ ------
805
+ NetworkXError
806
+ If there is not an edge between u and v.
807
+
808
+ See Also
809
+ --------
810
+ remove_edges_from : remove a collection of edges
811
+
812
+ Examples
813
+ --------
814
+ >>> G = nx.Graph() # or DiGraph, etc
815
+ >>> nx.add_path(G, [0, 1, 2, 3])
816
+ >>> G.remove_edge(0, 1)
817
+ >>> e = (1, 2)
818
+ >>> G.remove_edge(*e) # unpacks e from an edge tuple
819
+ >>> e = (2, 3, {"weight": 7}) # an edge with attribute data
820
+ >>> G.remove_edge(*e[:2]) # select first part of edge tuple
821
+ """
822
+ try:
823
+ del self._succ[u][v]
824
+ del self._pred[v][u]
825
+ except KeyError as err:
826
+ raise NetworkXError(f"The edge {u}-{v} not in graph.") from err
827
+
828
+ def remove_edges_from(self, ebunch):
829
+ """Remove all edges specified in ebunch.
830
+
831
+ Parameters
832
+ ----------
833
+ ebunch: list or container of edge tuples
834
+ Each edge given in the list or container will be removed
835
+ from the graph. The edges can be:
836
+
837
+ - 2-tuples (u, v) edge between u and v.
838
+ - 3-tuples (u, v, k) where k is ignored.
839
+
840
+ See Also
841
+ --------
842
+ remove_edge : remove a single edge
843
+
844
+ Notes
845
+ -----
846
+ Will fail silently if an edge in ebunch is not in the graph.
847
+
848
+ Examples
849
+ --------
850
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
851
+ >>> ebunch = [(1, 2), (2, 3)]
852
+ >>> G.remove_edges_from(ebunch)
853
+ """
854
+ for e in ebunch:
855
+ u, v = e[:2] # ignore edge data
856
+ if u in self._succ and v in self._succ[u]:
857
+ del self._succ[u][v]
858
+ del self._pred[v][u]
859
+
860
+ def has_successor(self, u, v):
861
+ """Returns True if node u has successor v.
862
+
863
+ This is true if graph has the edge u->v.
864
+ """
865
+ return u in self._succ and v in self._succ[u]
866
+
867
+ def has_predecessor(self, u, v):
868
+ """Returns True if node u has predecessor v.
869
+
870
+ This is true if graph has the edge u<-v.
871
+ """
872
+ return u in self._pred and v in self._pred[u]
873
+
874
+ def successors(self, n):
875
+ """Returns an iterator over successor nodes of n.
876
+
877
+ A successor of n is a node m such that there exists a directed
878
+ edge from n to m.
879
+
880
+ Parameters
881
+ ----------
882
+ n : node
883
+ A node in the graph
884
+
885
+ Raises
886
+ ------
887
+ NetworkXError
888
+ If n is not in the graph.
889
+
890
+ See Also
891
+ --------
892
+ predecessors
893
+
894
+ Notes
895
+ -----
896
+ neighbors() and successors() are the same.
897
+ """
898
+ try:
899
+ return iter(self._succ[n])
900
+ except KeyError as err:
901
+ raise NetworkXError(f"The node {n} is not in the digraph.") from err
902
+
903
+ # digraph definitions
904
+ neighbors = successors
905
+
906
+ def predecessors(self, n):
907
+ """Returns an iterator over predecessor nodes of n.
908
+
909
+ A predecessor of n is a node m such that there exists a directed
910
+ edge from m to n.
911
+
912
+ Parameters
913
+ ----------
914
+ n : node
915
+ A node in the graph
916
+
917
+ Raises
918
+ ------
919
+ NetworkXError
920
+ If n is not in the graph.
921
+
922
+ See Also
923
+ --------
924
+ successors
925
+ """
926
+ try:
927
+ return iter(self._pred[n])
928
+ except KeyError as err:
929
+ raise NetworkXError(f"The node {n} is not in the digraph.") from err
930
+
931
+ @cached_property
932
+ def edges(self):
933
+ """An OutEdgeView of the DiGraph as G.edges or G.edges().
934
+
935
+ edges(self, nbunch=None, data=False, default=None)
936
+
937
+ The OutEdgeView provides set-like operations on the edge-tuples
938
+ as well as edge attribute lookup. When called, it also provides
939
+ an EdgeDataView object which allows control of access to edge
940
+ attributes (but does not provide set-like operations).
941
+ Hence, `G.edges[u, v]['color']` provides the value of the color
942
+ attribute for edge `(u, v)` while
943
+ `for (u, v, c) in G.edges.data('color', default='red'):`
944
+ iterates through all the edges yielding the color attribute
945
+ with default `'red'` if no color attribute exists.
946
+
947
+ Parameters
948
+ ----------
949
+ nbunch : single node, container, or all nodes (default= all nodes)
950
+ The view will only report edges from these nodes.
951
+ data : string or bool, optional (default=False)
952
+ The edge attribute returned in 3-tuple (u, v, ddict[data]).
953
+ If True, return edge attribute dict in 3-tuple (u, v, ddict).
954
+ If False, return 2-tuple (u, v).
955
+ default : value, optional (default=None)
956
+ Value used for edges that don't have the requested attribute.
957
+ Only relevant if data is not True or False.
958
+
959
+ Returns
960
+ -------
961
+ edges : OutEdgeView
962
+ A view of edge attributes, usually it iterates over (u, v)
963
+ or (u, v, d) tuples of edges, but can also be used for
964
+ attribute lookup as `edges[u, v]['foo']`.
965
+
966
+ See Also
967
+ --------
968
+ in_edges, out_edges
969
+
970
+ Notes
971
+ -----
972
+ Nodes in nbunch that are not in the graph will be (quietly) ignored.
973
+ For directed graphs this returns the out-edges.
974
+
975
+ Examples
976
+ --------
977
+ >>> G = nx.DiGraph() # or MultiDiGraph, etc
978
+ >>> nx.add_path(G, [0, 1, 2])
979
+ >>> G.add_edge(2, 3, weight=5)
980
+ >>> [e for e in G.edges]
981
+ [(0, 1), (1, 2), (2, 3)]
982
+ >>> G.edges.data() # default data is {} (empty dict)
983
+ OutEdgeDataView([(0, 1, {}), (1, 2, {}), (2, 3, {'weight': 5})])
984
+ >>> G.edges.data("weight", default=1)
985
+ OutEdgeDataView([(0, 1, 1), (1, 2, 1), (2, 3, 5)])
986
+ >>> G.edges([0, 2]) # only edges originating from these nodes
987
+ OutEdgeDataView([(0, 1), (2, 3)])
988
+ >>> G.edges(0) # only edges from node 0
989
+ OutEdgeDataView([(0, 1)])
990
+
991
+ """
992
+ return OutEdgeView(self)
993
+
994
+ # alias out_edges to edges
995
+ @cached_property
996
+ def out_edges(self):
997
+ return OutEdgeView(self)
998
+
999
+ out_edges.__doc__ = edges.__doc__
1000
+
1001
+ @cached_property
1002
+ def in_edges(self):
1003
+ """A view of the in edges of the graph as G.in_edges or G.in_edges().
1004
+
1005
+ in_edges(self, nbunch=None, data=False, default=None):
1006
+
1007
+ Parameters
1008
+ ----------
1009
+ nbunch : single node, container, or all nodes (default= all nodes)
1010
+ The view will only report edges incident to these nodes.
1011
+ data : string or bool, optional (default=False)
1012
+ The edge attribute returned in 3-tuple (u, v, ddict[data]).
1013
+ If True, return edge attribute dict in 3-tuple (u, v, ddict).
1014
+ If False, return 2-tuple (u, v).
1015
+ default : value, optional (default=None)
1016
+ Value used for edges that don't have the requested attribute.
1017
+ Only relevant if data is not True or False.
1018
+
1019
+ Returns
1020
+ -------
1021
+ in_edges : InEdgeView or InEdgeDataView
1022
+ A view of edge attributes, usually it iterates over (u, v)
1023
+ or (u, v, d) tuples of edges, but can also be used for
1024
+ attribute lookup as `edges[u, v]['foo']`.
1025
+
1026
+ Examples
1027
+ --------
1028
+ >>> G = nx.DiGraph()
1029
+ >>> G.add_edge(1, 2, color='blue')
1030
+ >>> G.in_edges()
1031
+ InEdgeView([(1, 2)])
1032
+ >>> G.in_edges(nbunch=2)
1033
+ InEdgeDataView([(1, 2)])
1034
+
1035
+ See Also
1036
+ --------
1037
+ edges
1038
+ """
1039
+ return InEdgeView(self)
1040
+
1041
+ @cached_property
1042
+ def degree(self):
1043
+ """A DegreeView for the Graph as G.degree or G.degree().
1044
+
1045
+ The node degree is the number of edges adjacent to the node.
1046
+ The weighted node degree is the sum of the edge weights for
1047
+ edges incident to that node.
1048
+
1049
+ This object provides an iterator for (node, degree) as well as
1050
+ lookup for the degree for a single node.
1051
+
1052
+ Parameters
1053
+ ----------
1054
+ nbunch : single node, container, or all nodes (default= all nodes)
1055
+ The view will only report edges incident to these nodes.
1056
+
1057
+ weight : string or None, optional (default=None)
1058
+ The name of an edge attribute that holds the numerical value used
1059
+ as a weight. If None, then each edge has weight 1.
1060
+ The degree is the sum of the edge weights adjacent to the node.
1061
+
1062
+ Returns
1063
+ -------
1064
+ DiDegreeView or int
1065
+ If multiple nodes are requested (the default), returns a `DiDegreeView`
1066
+ mapping nodes to their degree.
1067
+ If a single node is requested, returns the degree of the node as an integer.
1068
+
1069
+ See Also
1070
+ --------
1071
+ in_degree, out_degree
1072
+
1073
+ Examples
1074
+ --------
1075
+ >>> G = nx.DiGraph() # or MultiDiGraph
1076
+ >>> nx.add_path(G, [0, 1, 2, 3])
1077
+ >>> G.degree(0) # node 0 with degree 1
1078
+ 1
1079
+ >>> list(G.degree([0, 1, 2]))
1080
+ [(0, 1), (1, 2), (2, 2)]
1081
+
1082
+ """
1083
+ return DiDegreeView(self)
1084
+
1085
+ @cached_property
1086
+ def in_degree(self):
1087
+ """An InDegreeView for (node, in_degree) or in_degree for single node.
1088
+
1089
+ The node in_degree is the number of edges pointing to the node.
1090
+ The weighted node degree is the sum of the edge weights for
1091
+ edges incident to that node.
1092
+
1093
+ This object provides an iteration over (node, in_degree) as well as
1094
+ lookup for the degree for a single node.
1095
+
1096
+ Parameters
1097
+ ----------
1098
+ nbunch : single node, container, or all nodes (default= all nodes)
1099
+ The view will only report edges incident to these nodes.
1100
+
1101
+ weight : string or None, optional (default=None)
1102
+ The name of an edge attribute that holds the numerical value used
1103
+ as a weight. If None, then each edge has weight 1.
1104
+ The degree is the sum of the edge weights adjacent to the node.
1105
+
1106
+ Returns
1107
+ -------
1108
+ If a single node is requested
1109
+ deg : int
1110
+ In-degree of the node
1111
+
1112
+ OR if multiple nodes are requested
1113
+ nd_iter : iterator
1114
+ The iterator returns two-tuples of (node, in-degree).
1115
+
1116
+ See Also
1117
+ --------
1118
+ degree, out_degree
1119
+
1120
+ Examples
1121
+ --------
1122
+ >>> G = nx.DiGraph()
1123
+ >>> nx.add_path(G, [0, 1, 2, 3])
1124
+ >>> G.in_degree(0) # node 0 with degree 0
1125
+ 0
1126
+ >>> list(G.in_degree([0, 1, 2]))
1127
+ [(0, 0), (1, 1), (2, 1)]
1128
+
1129
+ """
1130
+ return InDegreeView(self)
1131
+
1132
+ @cached_property
1133
+ def out_degree(self):
1134
+ """An OutDegreeView for (node, out_degree)
1135
+
1136
+ The node out_degree is the number of edges pointing out of the node.
1137
+ The weighted node degree is the sum of the edge weights for
1138
+ edges incident to that node.
1139
+
1140
+ This object provides an iterator over (node, out_degree) as well as
1141
+ lookup for the degree for a single node.
1142
+
1143
+ Parameters
1144
+ ----------
1145
+ nbunch : single node, container, or all nodes (default= all nodes)
1146
+ The view will only report edges incident to these nodes.
1147
+
1148
+ weight : string or None, optional (default=None)
1149
+ The name of an edge attribute that holds the numerical value used
1150
+ as a weight. If None, then each edge has weight 1.
1151
+ The degree is the sum of the edge weights adjacent to the node.
1152
+
1153
+ Returns
1154
+ -------
1155
+ If a single node is requested
1156
+ deg : int
1157
+ Out-degree of the node
1158
+
1159
+ OR if multiple nodes are requested
1160
+ nd_iter : iterator
1161
+ The iterator returns two-tuples of (node, out-degree).
1162
+
1163
+ See Also
1164
+ --------
1165
+ degree, in_degree
1166
+
1167
+ Examples
1168
+ --------
1169
+ >>> G = nx.DiGraph()
1170
+ >>> nx.add_path(G, [0, 1, 2, 3])
1171
+ >>> G.out_degree(0) # node 0 with degree 1
1172
+ 1
1173
+ >>> list(G.out_degree([0, 1, 2]))
1174
+ [(0, 1), (1, 1), (2, 1)]
1175
+
1176
+ """
1177
+ return OutDegreeView(self)
1178
+
1179
+ def clear(self):
1180
+ """Remove all nodes and edges from the graph.
1181
+
1182
+ This also removes the name, and all graph, node, and edge attributes.
1183
+
1184
+ Examples
1185
+ --------
1186
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1187
+ >>> G.clear()
1188
+ >>> list(G.nodes)
1189
+ []
1190
+ >>> list(G.edges)
1191
+ []
1192
+
1193
+ """
1194
+ self._succ.clear()
1195
+ self._pred.clear()
1196
+ self._node.clear()
1197
+ self.graph.clear()
1198
+
1199
+ def clear_edges(self):
1200
+ """Remove all edges from the graph without altering nodes.
1201
+
1202
+ Examples
1203
+ --------
1204
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1205
+ >>> G.clear_edges()
1206
+ >>> list(G.nodes)
1207
+ [0, 1, 2, 3]
1208
+ >>> list(G.edges)
1209
+ []
1210
+
1211
+ """
1212
+ for predecessor_dict in self._pred.values():
1213
+ predecessor_dict.clear()
1214
+ for successor_dict in self._succ.values():
1215
+ successor_dict.clear()
1216
+
1217
+ def is_multigraph(self):
1218
+ """Returns True if graph is a multigraph, False otherwise."""
1219
+ return False
1220
+
1221
+ def is_directed(self):
1222
+ """Returns True if graph is directed, False otherwise."""
1223
+ return True
1224
+
1225
+ def to_undirected(self, reciprocal=False, as_view=False):
1226
+ """Returns an undirected representation of the digraph.
1227
+
1228
+ Parameters
1229
+ ----------
1230
+ reciprocal : bool (optional)
1231
+ If True only keep edges that appear in both directions
1232
+ in the original digraph.
1233
+ as_view : bool (optional, default=False)
1234
+ If True return an undirected view of the original directed graph.
1235
+
1236
+ Returns
1237
+ -------
1238
+ G : Graph
1239
+ An undirected graph with the same name and nodes and
1240
+ with edge (u, v, data) if either (u, v, data) or (v, u, data)
1241
+ is in the digraph. If both edges exist in digraph and
1242
+ their edge data is different, only one edge is created
1243
+ with an arbitrary choice of which edge data to use.
1244
+ You must check and correct for this manually if desired.
1245
+
1246
+ See Also
1247
+ --------
1248
+ Graph, copy, add_edge, add_edges_from
1249
+
1250
+ Notes
1251
+ -----
1252
+ If edges in both directions (u, v) and (v, u) exist in the
1253
+ graph, attributes for the new undirected edge will be a combination of
1254
+ the attributes of the directed edges. The edge data is updated
1255
+ in the (arbitrary) order that the edges are encountered. For
1256
+ more customized control of the edge attributes use add_edge().
1257
+
1258
+ This returns a "deepcopy" of the edge, node, and
1259
+ graph attributes which attempts to completely copy
1260
+ all of the data and references.
1261
+
1262
+ This is in contrast to the similar G=DiGraph(D) which returns a
1263
+ shallow copy of the data.
1264
+
1265
+ See the Python copy module for more information on shallow
1266
+ and deep copies, https://docs.python.org/3/library/copy.html.
1267
+
1268
+ Warning: If you have subclassed DiGraph to use dict-like objects
1269
+ in the data structure, those changes do not transfer to the
1270
+ Graph created by this method.
1271
+
1272
+ Examples
1273
+ --------
1274
+ >>> G = nx.path_graph(2) # or MultiGraph, etc
1275
+ >>> H = G.to_directed()
1276
+ >>> list(H.edges)
1277
+ [(0, 1), (1, 0)]
1278
+ >>> G2 = H.to_undirected()
1279
+ >>> list(G2.edges)
1280
+ [(0, 1)]
1281
+ """
1282
+ graph_class = self.to_undirected_class()
1283
+ if as_view is True:
1284
+ return nx.graphviews.generic_graph_view(self, graph_class)
1285
+ # deepcopy when not a view
1286
+ G = graph_class()
1287
+ G.graph.update(deepcopy(self.graph))
1288
+ G.add_nodes_from((n, deepcopy(d)) for n, d in self._node.items())
1289
+ if reciprocal is True:
1290
+ G.add_edges_from(
1291
+ (u, v, deepcopy(d))
1292
+ for u, nbrs in self._adj.items()
1293
+ for v, d in nbrs.items()
1294
+ if v in self._pred[u]
1295
+ )
1296
+ else:
1297
+ G.add_edges_from(
1298
+ (u, v, deepcopy(d))
1299
+ for u, nbrs in self._adj.items()
1300
+ for v, d in nbrs.items()
1301
+ )
1302
+ return G
1303
+
1304
+ def reverse(self, copy=True):
1305
+ """Returns the reverse of the graph.
1306
+
1307
+ The reverse is a graph with the same nodes and edges
1308
+ but with the directions of the edges reversed.
1309
+
1310
+ Parameters
1311
+ ----------
1312
+ copy : bool optional (default=True)
1313
+ If True, return a new DiGraph holding the reversed edges.
1314
+ If False, the reverse graph is created using a view of
1315
+ the original graph.
1316
+ """
1317
+ if copy:
1318
+ H = self.__class__()
1319
+ H.graph.update(deepcopy(self.graph))
1320
+ H.add_nodes_from((n, deepcopy(d)) for n, d in self.nodes.items())
1321
+ H.add_edges_from((v, u, deepcopy(d)) for u, v, d in self.edges(data=True))
1322
+ return H
1323
+ return nx.reverse_view(self)
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/filters.py ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Filter factories to hide or show sets of nodes and edges.
2
+
3
+ These filters return the function used when creating `SubGraph`.
4
+ """
5
+ __all__ = [
6
+ "no_filter",
7
+ "hide_nodes",
8
+ "hide_edges",
9
+ "hide_multiedges",
10
+ "hide_diedges",
11
+ "hide_multidiedges",
12
+ "show_nodes",
13
+ "show_edges",
14
+ "show_multiedges",
15
+ "show_diedges",
16
+ "show_multidiedges",
17
+ ]
18
+
19
+
20
+ def no_filter(*items):
21
+ return True
22
+
23
+
24
+ def hide_nodes(nodes):
25
+ nodes = set(nodes)
26
+ return lambda node: node not in nodes
27
+
28
+
29
+ def hide_diedges(edges):
30
+ edges = {(u, v) for u, v in edges}
31
+ return lambda u, v: (u, v) not in edges
32
+
33
+
34
+ def hide_edges(edges):
35
+ alledges = set(edges) | {(v, u) for (u, v) in edges}
36
+ return lambda u, v: (u, v) not in alledges
37
+
38
+
39
+ def hide_multidiedges(edges):
40
+ edges = {(u, v, k) for u, v, k in edges}
41
+ return lambda u, v, k: (u, v, k) not in edges
42
+
43
+
44
+ def hide_multiedges(edges):
45
+ alledges = set(edges) | {(v, u, k) for (u, v, k) in edges}
46
+ return lambda u, v, k: (u, v, k) not in alledges
47
+
48
+
49
+ # write show_nodes as a class to make SubGraph pickleable
50
+ class show_nodes:
51
+ def __init__(self, nodes):
52
+ self.nodes = set(nodes)
53
+
54
+ def __call__(self, node):
55
+ return node in self.nodes
56
+
57
+
58
+ def show_diedges(edges):
59
+ edges = {(u, v) for u, v in edges}
60
+ return lambda u, v: (u, v) in edges
61
+
62
+
63
+ def show_edges(edges):
64
+ alledges = set(edges) | {(v, u) for (u, v) in edges}
65
+ return lambda u, v: (u, v) in alledges
66
+
67
+
68
+ def show_multidiedges(edges):
69
+ edges = {(u, v, k) for u, v, k in edges}
70
+ return lambda u, v, k: (u, v, k) in edges
71
+
72
+
73
+ def show_multiedges(edges):
74
+ alledges = set(edges) | {(v, u, k) for (u, v, k) in edges}
75
+ return lambda u, v, k: (u, v, k) in alledges
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/function.py ADDED
@@ -0,0 +1,1313 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Functional interface to graph methods and assorted utilities.
2
+ """
3
+
4
+ from collections import Counter
5
+ from itertools import chain
6
+
7
+ import networkx as nx
8
+ from networkx.utils import not_implemented_for, pairwise
9
+
10
+ __all__ = [
11
+ "nodes",
12
+ "edges",
13
+ "degree",
14
+ "degree_histogram",
15
+ "neighbors",
16
+ "number_of_nodes",
17
+ "number_of_edges",
18
+ "density",
19
+ "is_directed",
20
+ "freeze",
21
+ "is_frozen",
22
+ "subgraph",
23
+ "induced_subgraph",
24
+ "edge_subgraph",
25
+ "restricted_view",
26
+ "to_directed",
27
+ "to_undirected",
28
+ "add_star",
29
+ "add_path",
30
+ "add_cycle",
31
+ "create_empty_copy",
32
+ "set_node_attributes",
33
+ "get_node_attributes",
34
+ "set_edge_attributes",
35
+ "get_edge_attributes",
36
+ "all_neighbors",
37
+ "non_neighbors",
38
+ "non_edges",
39
+ "common_neighbors",
40
+ "is_weighted",
41
+ "is_negatively_weighted",
42
+ "is_empty",
43
+ "selfloop_edges",
44
+ "nodes_with_selfloops",
45
+ "number_of_selfloops",
46
+ "path_weight",
47
+ "is_path",
48
+ ]
49
+
50
+
51
+ def nodes(G):
52
+ """Returns an iterator over the graph nodes."""
53
+ return G.nodes()
54
+
55
+
56
+ def edges(G, nbunch=None):
57
+ """Returns an edge view of edges incident to nodes in nbunch.
58
+
59
+ Return all edges if nbunch is unspecified or nbunch=None.
60
+
61
+ For digraphs, edges=out_edges
62
+ """
63
+ return G.edges(nbunch)
64
+
65
+
66
+ def degree(G, nbunch=None, weight=None):
67
+ """Returns a degree view of single node or of nbunch of nodes.
68
+ If nbunch is omitted, then return degrees of *all* nodes.
69
+ """
70
+ return G.degree(nbunch, weight)
71
+
72
+
73
+ def neighbors(G, n):
74
+ """Returns a list of nodes connected to node n."""
75
+ return G.neighbors(n)
76
+
77
+
78
+ def number_of_nodes(G):
79
+ """Returns the number of nodes in the graph."""
80
+ return G.number_of_nodes()
81
+
82
+
83
+ def number_of_edges(G):
84
+ """Returns the number of edges in the graph."""
85
+ return G.number_of_edges()
86
+
87
+
88
+ def density(G):
89
+ r"""Returns the density of a graph.
90
+
91
+ The density for undirected graphs is
92
+
93
+ .. math::
94
+
95
+ d = \frac{2m}{n(n-1)},
96
+
97
+ and for directed graphs is
98
+
99
+ .. math::
100
+
101
+ d = \frac{m}{n(n-1)},
102
+
103
+ where `n` is the number of nodes and `m` is the number of edges in `G`.
104
+
105
+ Notes
106
+ -----
107
+ The density is 0 for a graph without edges and 1 for a complete graph.
108
+ The density of multigraphs can be higher than 1.
109
+
110
+ Self loops are counted in the total number of edges so graphs with self
111
+ loops can have density higher than 1.
112
+ """
113
+ n = number_of_nodes(G)
114
+ m = number_of_edges(G)
115
+ if m == 0 or n <= 1:
116
+ return 0
117
+ d = m / (n * (n - 1))
118
+ if not G.is_directed():
119
+ d *= 2
120
+ return d
121
+
122
+
123
+ def degree_histogram(G):
124
+ """Returns a list of the frequency of each degree value.
125
+
126
+ Parameters
127
+ ----------
128
+ G : Networkx graph
129
+ A graph
130
+
131
+ Returns
132
+ -------
133
+ hist : list
134
+ A list of frequencies of degrees.
135
+ The degree values are the index in the list.
136
+
137
+ Notes
138
+ -----
139
+ Note: the bins are width one, hence len(list) can be large
140
+ (Order(number_of_edges))
141
+ """
142
+ counts = Counter(d for n, d in G.degree())
143
+ return [counts.get(i, 0) for i in range(max(counts) + 1)]
144
+
145
+
146
+ def is_directed(G):
147
+ """Return True if graph is directed."""
148
+ return G.is_directed()
149
+
150
+
151
+ def frozen(*args, **kwargs):
152
+ """Dummy method for raising errors when trying to modify frozen graphs"""
153
+ raise nx.NetworkXError("Frozen graph can't be modified")
154
+
155
+
156
+ def freeze(G):
157
+ """Modify graph to prevent further change by adding or removing
158
+ nodes or edges.
159
+
160
+ Node and edge data can still be modified.
161
+
162
+ Parameters
163
+ ----------
164
+ G : graph
165
+ A NetworkX graph
166
+
167
+ Examples
168
+ --------
169
+ >>> G = nx.path_graph(4)
170
+ >>> G = nx.freeze(G)
171
+ >>> try:
172
+ ... G.add_edge(4, 5)
173
+ ... except nx.NetworkXError as err:
174
+ ... print(str(err))
175
+ Frozen graph can't be modified
176
+
177
+ Notes
178
+ -----
179
+ To "unfreeze" a graph you must make a copy by creating a new graph object:
180
+
181
+ >>> graph = nx.path_graph(4)
182
+ >>> frozen_graph = nx.freeze(graph)
183
+ >>> unfrozen_graph = nx.Graph(frozen_graph)
184
+ >>> nx.is_frozen(unfrozen_graph)
185
+ False
186
+
187
+ See Also
188
+ --------
189
+ is_frozen
190
+ """
191
+ G.add_node = frozen
192
+ G.add_nodes_from = frozen
193
+ G.remove_node = frozen
194
+ G.remove_nodes_from = frozen
195
+ G.add_edge = frozen
196
+ G.add_edges_from = frozen
197
+ G.add_weighted_edges_from = frozen
198
+ G.remove_edge = frozen
199
+ G.remove_edges_from = frozen
200
+ G.clear = frozen
201
+ G.clear_edges = frozen
202
+ G.frozen = True
203
+ return G
204
+
205
+
206
+ def is_frozen(G):
207
+ """Returns True if graph is frozen.
208
+
209
+ Parameters
210
+ ----------
211
+ G : graph
212
+ A NetworkX graph
213
+
214
+ See Also
215
+ --------
216
+ freeze
217
+ """
218
+ try:
219
+ return G.frozen
220
+ except AttributeError:
221
+ return False
222
+
223
+
224
+ def add_star(G_to_add_to, nodes_for_star, **attr):
225
+ """Add a star to Graph G_to_add_to.
226
+
227
+ The first node in `nodes_for_star` is the middle of the star.
228
+ It is connected to all other nodes.
229
+
230
+ Parameters
231
+ ----------
232
+ G_to_add_to : graph
233
+ A NetworkX graph
234
+ nodes_for_star : iterable container
235
+ A container of nodes.
236
+ attr : keyword arguments, optional (default= no attributes)
237
+ Attributes to add to every edge in star.
238
+
239
+ See Also
240
+ --------
241
+ add_path, add_cycle
242
+
243
+ Examples
244
+ --------
245
+ >>> G = nx.Graph()
246
+ >>> nx.add_star(G, [0, 1, 2, 3])
247
+ >>> nx.add_star(G, [10, 11, 12], weight=2)
248
+ """
249
+ nlist = iter(nodes_for_star)
250
+ try:
251
+ v = next(nlist)
252
+ except StopIteration:
253
+ return
254
+ G_to_add_to.add_node(v)
255
+ edges = ((v, n) for n in nlist)
256
+ G_to_add_to.add_edges_from(edges, **attr)
257
+
258
+
259
+ def add_path(G_to_add_to, nodes_for_path, **attr):
260
+ """Add a path to the Graph G_to_add_to.
261
+
262
+ Parameters
263
+ ----------
264
+ G_to_add_to : graph
265
+ A NetworkX graph
266
+ nodes_for_path : iterable container
267
+ A container of nodes. A path will be constructed from
268
+ the nodes (in order) and added to the graph.
269
+ attr : keyword arguments, optional (default= no attributes)
270
+ Attributes to add to every edge in path.
271
+
272
+ See Also
273
+ --------
274
+ add_star, add_cycle
275
+
276
+ Examples
277
+ --------
278
+ >>> G = nx.Graph()
279
+ >>> nx.add_path(G, [0, 1, 2, 3])
280
+ >>> nx.add_path(G, [10, 11, 12], weight=7)
281
+ """
282
+ nlist = iter(nodes_for_path)
283
+ try:
284
+ first_node = next(nlist)
285
+ except StopIteration:
286
+ return
287
+ G_to_add_to.add_node(first_node)
288
+ G_to_add_to.add_edges_from(pairwise(chain((first_node,), nlist)), **attr)
289
+
290
+
291
+ def add_cycle(G_to_add_to, nodes_for_cycle, **attr):
292
+ """Add a cycle to the Graph G_to_add_to.
293
+
294
+ Parameters
295
+ ----------
296
+ G_to_add_to : graph
297
+ A NetworkX graph
298
+ nodes_for_cycle: iterable container
299
+ A container of nodes. A cycle will be constructed from
300
+ the nodes (in order) and added to the graph.
301
+ attr : keyword arguments, optional (default= no attributes)
302
+ Attributes to add to every edge in cycle.
303
+
304
+ See Also
305
+ --------
306
+ add_path, add_star
307
+
308
+ Examples
309
+ --------
310
+ >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
311
+ >>> nx.add_cycle(G, [0, 1, 2, 3])
312
+ >>> nx.add_cycle(G, [10, 11, 12], weight=7)
313
+ """
314
+ nlist = iter(nodes_for_cycle)
315
+ try:
316
+ first_node = next(nlist)
317
+ except StopIteration:
318
+ return
319
+ G_to_add_to.add_node(first_node)
320
+ G_to_add_to.add_edges_from(
321
+ pairwise(chain((first_node,), nlist), cyclic=True), **attr
322
+ )
323
+
324
+
325
+ def subgraph(G, nbunch):
326
+ """Returns the subgraph induced on nodes in nbunch.
327
+
328
+ Parameters
329
+ ----------
330
+ G : graph
331
+ A NetworkX graph
332
+
333
+ nbunch : list, iterable
334
+ A container of nodes that will be iterated through once (thus
335
+ it should be an iterator or be iterable). Each element of the
336
+ container should be a valid node type: any hashable type except
337
+ None. If nbunch is None, return all edges data in the graph.
338
+ Nodes in nbunch that are not in the graph will be (quietly)
339
+ ignored.
340
+
341
+ Notes
342
+ -----
343
+ subgraph(G) calls G.subgraph()
344
+ """
345
+ return G.subgraph(nbunch)
346
+
347
+
348
+ def induced_subgraph(G, nbunch):
349
+ """Returns a SubGraph view of `G` showing only nodes in nbunch.
350
+
351
+ The induced subgraph of a graph on a set of nodes N is the
352
+ graph with nodes N and edges from G which have both ends in N.
353
+
354
+ Parameters
355
+ ----------
356
+ G : NetworkX Graph
357
+ nbunch : node, container of nodes or None (for all nodes)
358
+
359
+ Returns
360
+ -------
361
+ subgraph : SubGraph View
362
+ A read-only view of the subgraph in `G` induced by the nodes.
363
+ Changes to the graph `G` will be reflected in the view.
364
+
365
+ Notes
366
+ -----
367
+ To create a mutable subgraph with its own copies of nodes
368
+ edges and attributes use `subgraph.copy()` or `Graph(subgraph)`
369
+
370
+ For an inplace reduction of a graph to a subgraph you can remove nodes:
371
+ `G.remove_nodes_from(n in G if n not in set(nbunch))`
372
+
373
+ If you are going to compute subgraphs of your subgraphs you could
374
+ end up with a chain of views that can be very slow once the chain
375
+ has about 15 views in it. If they are all induced subgraphs, you
376
+ can short-cut the chain by making them all subgraphs of the original
377
+ graph. The graph class method `G.subgraph` does this when `G` is
378
+ a subgraph. In contrast, this function allows you to choose to build
379
+ chains or not, as you wish. The returned subgraph is a view on `G`.
380
+
381
+ Examples
382
+ --------
383
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
384
+ >>> H = nx.induced_subgraph(G, [0, 1, 3])
385
+ >>> list(H.edges)
386
+ [(0, 1)]
387
+ >>> list(H.nodes)
388
+ [0, 1, 3]
389
+ """
390
+ induced_nodes = nx.filters.show_nodes(G.nbunch_iter(nbunch))
391
+ return nx.subgraph_view(G, filter_node=induced_nodes)
392
+
393
+
394
+ def edge_subgraph(G, edges):
395
+ """Returns a view of the subgraph induced by the specified edges.
396
+
397
+ The induced subgraph contains each edge in `edges` and each
398
+ node incident to any of those edges.
399
+
400
+ Parameters
401
+ ----------
402
+ G : NetworkX Graph
403
+ edges : iterable
404
+ An iterable of edges. Edges not present in `G` are ignored.
405
+
406
+ Returns
407
+ -------
408
+ subgraph : SubGraph View
409
+ A read-only edge-induced subgraph of `G`.
410
+ Changes to `G` are reflected in the view.
411
+
412
+ Notes
413
+ -----
414
+ To create a mutable subgraph with its own copies of nodes
415
+ edges and attributes use `subgraph.copy()` or `Graph(subgraph)`
416
+
417
+ If you create a subgraph of a subgraph recursively you can end up
418
+ with a chain of subgraphs that becomes very slow with about 15
419
+ nested subgraph views. Luckily the edge_subgraph filter nests
420
+ nicely so you can use the original graph as G in this function
421
+ to avoid chains. We do not rule out chains programmatically so
422
+ that odd cases like an `edge_subgraph` of a `restricted_view`
423
+ can be created.
424
+
425
+ Examples
426
+ --------
427
+ >>> G = nx.path_graph(5)
428
+ >>> H = G.edge_subgraph([(0, 1), (3, 4)])
429
+ >>> list(H.nodes)
430
+ [0, 1, 3, 4]
431
+ >>> list(H.edges)
432
+ [(0, 1), (3, 4)]
433
+ """
434
+ nxf = nx.filters
435
+ edges = set(edges)
436
+ nodes = set()
437
+ for e in edges:
438
+ nodes.update(e[:2])
439
+ induced_nodes = nxf.show_nodes(nodes)
440
+ if G.is_multigraph():
441
+ if G.is_directed():
442
+ induced_edges = nxf.show_multidiedges(edges)
443
+ else:
444
+ induced_edges = nxf.show_multiedges(edges)
445
+ else:
446
+ if G.is_directed():
447
+ induced_edges = nxf.show_diedges(edges)
448
+ else:
449
+ induced_edges = nxf.show_edges(edges)
450
+ return nx.subgraph_view(G, filter_node=induced_nodes, filter_edge=induced_edges)
451
+
452
+
453
+ def restricted_view(G, nodes, edges):
454
+ """Returns a view of `G` with hidden nodes and edges.
455
+
456
+ The resulting subgraph filters out node `nodes` and edges `edges`.
457
+ Filtered out nodes also filter out any of their edges.
458
+
459
+ Parameters
460
+ ----------
461
+ G : NetworkX Graph
462
+ nodes : iterable
463
+ An iterable of nodes. Nodes not present in `G` are ignored.
464
+ edges : iterable
465
+ An iterable of edges. Edges not present in `G` are ignored.
466
+
467
+ Returns
468
+ -------
469
+ subgraph : SubGraph View
470
+ A read-only restricted view of `G` filtering out nodes and edges.
471
+ Changes to `G` are reflected in the view.
472
+
473
+ Notes
474
+ -----
475
+ To create a mutable subgraph with its own copies of nodes
476
+ edges and attributes use `subgraph.copy()` or `Graph(subgraph)`
477
+
478
+ If you create a subgraph of a subgraph recursively you may end up
479
+ with a chain of subgraph views. Such chains can get quite slow
480
+ for lengths near 15. To avoid long chains, try to make your subgraph
481
+ based on the original graph. We do not rule out chains programmatically
482
+ so that odd cases like an `edge_subgraph` of a `restricted_view`
483
+ can be created.
484
+
485
+ Examples
486
+ --------
487
+ >>> G = nx.path_graph(5)
488
+ >>> H = nx.restricted_view(G, [0], [(1, 2), (3, 4)])
489
+ >>> list(H.nodes)
490
+ [1, 2, 3, 4]
491
+ >>> list(H.edges)
492
+ [(2, 3)]
493
+ """
494
+ nxf = nx.filters
495
+ hide_nodes = nxf.hide_nodes(nodes)
496
+ if G.is_multigraph():
497
+ if G.is_directed():
498
+ hide_edges = nxf.hide_multidiedges(edges)
499
+ else:
500
+ hide_edges = nxf.hide_multiedges(edges)
501
+ else:
502
+ if G.is_directed():
503
+ hide_edges = nxf.hide_diedges(edges)
504
+ else:
505
+ hide_edges = nxf.hide_edges(edges)
506
+ return nx.subgraph_view(G, filter_node=hide_nodes, filter_edge=hide_edges)
507
+
508
+
509
+ def to_directed(graph):
510
+ """Returns a directed view of the graph `graph`.
511
+
512
+ Identical to graph.to_directed(as_view=True)
513
+ Note that graph.to_directed defaults to `as_view=False`
514
+ while this function always provides a view.
515
+ """
516
+ return graph.to_directed(as_view=True)
517
+
518
+
519
+ def to_undirected(graph):
520
+ """Returns an undirected view of the graph `graph`.
521
+
522
+ Identical to graph.to_undirected(as_view=True)
523
+ Note that graph.to_undirected defaults to `as_view=False`
524
+ while this function always provides a view.
525
+ """
526
+ return graph.to_undirected(as_view=True)
527
+
528
+
529
+ def create_empty_copy(G, with_data=True):
530
+ """Returns a copy of the graph G with all of the edges removed.
531
+
532
+ Parameters
533
+ ----------
534
+ G : graph
535
+ A NetworkX graph
536
+
537
+ with_data : bool (default=True)
538
+ Propagate Graph and Nodes data to the new graph.
539
+
540
+ See Also
541
+ --------
542
+ empty_graph
543
+
544
+ """
545
+ H = G.__class__()
546
+ H.add_nodes_from(G.nodes(data=with_data))
547
+ if with_data:
548
+ H.graph.update(G.graph)
549
+ return H
550
+
551
+
552
+ def set_node_attributes(G, values, name=None):
553
+ """Sets node attributes from a given value or dictionary of values.
554
+
555
+ .. Warning:: The call order of arguments `values` and `name`
556
+ switched between v1.x & v2.x.
557
+
558
+ Parameters
559
+ ----------
560
+ G : NetworkX Graph
561
+
562
+ values : scalar value, dict-like
563
+ What the node attribute should be set to. If `values` is
564
+ not a dictionary, then it is treated as a single attribute value
565
+ that is then applied to every node in `G`. This means that if
566
+ you provide a mutable object, like a list, updates to that object
567
+ will be reflected in the node attribute for every node.
568
+ The attribute name will be `name`.
569
+
570
+ If `values` is a dict or a dict of dict, it should be keyed
571
+ by node to either an attribute value or a dict of attribute key/value
572
+ pairs used to update the node's attributes.
573
+
574
+ name : string (optional, default=None)
575
+ Name of the node attribute to set if values is a scalar.
576
+
577
+ Examples
578
+ --------
579
+ After computing some property of the nodes of a graph, you may want
580
+ to assign a node attribute to store the value of that property for
581
+ each node::
582
+
583
+ >>> G = nx.path_graph(3)
584
+ >>> bb = nx.betweenness_centrality(G)
585
+ >>> isinstance(bb, dict)
586
+ True
587
+ >>> nx.set_node_attributes(G, bb, "betweenness")
588
+ >>> G.nodes[1]["betweenness"]
589
+ 1.0
590
+
591
+ If you provide a list as the second argument, updates to the list
592
+ will be reflected in the node attribute for each node::
593
+
594
+ >>> G = nx.path_graph(3)
595
+ >>> labels = []
596
+ >>> nx.set_node_attributes(G, labels, "labels")
597
+ >>> labels.append("foo")
598
+ >>> G.nodes[0]["labels"]
599
+ ['foo']
600
+ >>> G.nodes[1]["labels"]
601
+ ['foo']
602
+ >>> G.nodes[2]["labels"]
603
+ ['foo']
604
+
605
+ If you provide a dictionary of dictionaries as the second argument,
606
+ the outer dictionary is assumed to be keyed by node to an inner
607
+ dictionary of node attributes for that node::
608
+
609
+ >>> G = nx.path_graph(3)
610
+ >>> attrs = {0: {"attr1": 20, "attr2": "nothing"}, 1: {"attr2": 3}}
611
+ >>> nx.set_node_attributes(G, attrs)
612
+ >>> G.nodes[0]["attr1"]
613
+ 20
614
+ >>> G.nodes[0]["attr2"]
615
+ 'nothing'
616
+ >>> G.nodes[1]["attr2"]
617
+ 3
618
+ >>> G.nodes[2]
619
+ {}
620
+
621
+ Note that if the dictionary contains nodes that are not in `G`, the
622
+ values are silently ignored::
623
+
624
+ >>> G = nx.Graph()
625
+ >>> G.add_node(0)
626
+ >>> nx.set_node_attributes(G, {0: "red", 1: "blue"}, name="color")
627
+ >>> G.nodes[0]["color"]
628
+ 'red'
629
+ >>> 1 in G.nodes
630
+ False
631
+
632
+ """
633
+ # Set node attributes based on type of `values`
634
+ if name is not None: # `values` must not be a dict of dict
635
+ try: # `values` is a dict
636
+ for n, v in values.items():
637
+ try:
638
+ G.nodes[n][name] = values[n]
639
+ except KeyError:
640
+ pass
641
+ except AttributeError: # `values` is a constant
642
+ for n in G:
643
+ G.nodes[n][name] = values
644
+ else: # `values` must be dict of dict
645
+ for n, d in values.items():
646
+ try:
647
+ G.nodes[n].update(d)
648
+ except KeyError:
649
+ pass
650
+
651
+
652
+ def get_node_attributes(G, name, default=None):
653
+ """Get node attributes from graph
654
+
655
+ Parameters
656
+ ----------
657
+ G : NetworkX Graph
658
+
659
+ name : string
660
+ Attribute name
661
+
662
+ default: object (default=None)
663
+ Default value of the node attribute if there is no value set for that
664
+ node in graph. If `None` then nodes without this attribute are not
665
+ included in the returned dict.
666
+
667
+ Returns
668
+ -------
669
+ Dictionary of attributes keyed by node.
670
+
671
+ Examples
672
+ --------
673
+ >>> G = nx.Graph()
674
+ >>> G.add_nodes_from([1, 2, 3], color="red")
675
+ >>> color = nx.get_node_attributes(G, "color")
676
+ >>> color[1]
677
+ 'red'
678
+ >>> G.add_node(4)
679
+ >>> color = nx.get_node_attributes(G, "color", default="yellow")
680
+ >>> color[4]
681
+ 'yellow'
682
+ """
683
+ if default is not None:
684
+ return {n: d.get(name, default) for n, d in G.nodes.items()}
685
+ return {n: d[name] for n, d in G.nodes.items() if name in d}
686
+
687
+
688
+ def set_edge_attributes(G, values, name=None):
689
+ """Sets edge attributes from a given value or dictionary of values.
690
+
691
+ .. Warning:: The call order of arguments `values` and `name`
692
+ switched between v1.x & v2.x.
693
+
694
+ Parameters
695
+ ----------
696
+ G : NetworkX Graph
697
+
698
+ values : scalar value, dict-like
699
+ What the edge attribute should be set to. If `values` is
700
+ not a dictionary, then it is treated as a single attribute value
701
+ that is then applied to every edge in `G`. This means that if
702
+ you provide a mutable object, like a list, updates to that object
703
+ will be reflected in the edge attribute for each edge. The attribute
704
+ name will be `name`.
705
+
706
+ If `values` is a dict or a dict of dict, it should be keyed
707
+ by edge tuple to either an attribute value or a dict of attribute
708
+ key/value pairs used to update the edge's attributes.
709
+ For multigraphs, the edge tuples must be of the form ``(u, v, key)``,
710
+ where `u` and `v` are nodes and `key` is the edge key.
711
+ For non-multigraphs, the keys must be tuples of the form ``(u, v)``.
712
+
713
+ name : string (optional, default=None)
714
+ Name of the edge attribute to set if values is a scalar.
715
+
716
+ Examples
717
+ --------
718
+ After computing some property of the edges of a graph, you may want
719
+ to assign a edge attribute to store the value of that property for
720
+ each edge::
721
+
722
+ >>> G = nx.path_graph(3)
723
+ >>> bb = nx.edge_betweenness_centrality(G, normalized=False)
724
+ >>> nx.set_edge_attributes(G, bb, "betweenness")
725
+ >>> G.edges[1, 2]["betweenness"]
726
+ 2.0
727
+
728
+ If you provide a list as the second argument, updates to the list
729
+ will be reflected in the edge attribute for each edge::
730
+
731
+ >>> labels = []
732
+ >>> nx.set_edge_attributes(G, labels, "labels")
733
+ >>> labels.append("foo")
734
+ >>> G.edges[0, 1]["labels"]
735
+ ['foo']
736
+ >>> G.edges[1, 2]["labels"]
737
+ ['foo']
738
+
739
+ If you provide a dictionary of dictionaries as the second argument,
740
+ the entire dictionary will be used to update edge attributes::
741
+
742
+ >>> G = nx.path_graph(3)
743
+ >>> attrs = {(0, 1): {"attr1": 20, "attr2": "nothing"}, (1, 2): {"attr2": 3}}
744
+ >>> nx.set_edge_attributes(G, attrs)
745
+ >>> G[0][1]["attr1"]
746
+ 20
747
+ >>> G[0][1]["attr2"]
748
+ 'nothing'
749
+ >>> G[1][2]["attr2"]
750
+ 3
751
+
752
+ The attributes of one Graph can be used to set those of another.
753
+
754
+ >>> H = nx.path_graph(3)
755
+ >>> nx.set_edge_attributes(H, G.edges)
756
+
757
+ Note that if the dict contains edges that are not in `G`, they are
758
+ silently ignored::
759
+
760
+ >>> G = nx.Graph([(0, 1)])
761
+ >>> nx.set_edge_attributes(G, {(1, 2): {"weight": 2.0}})
762
+ >>> (1, 2) in G.edges()
763
+ False
764
+
765
+ For multigraphs, the `values` dict is expected to be keyed by 3-tuples
766
+ including the edge key::
767
+
768
+ >>> MG = nx.MultiGraph()
769
+ >>> edges = [(0, 1), (0, 1)]
770
+ >>> MG.add_edges_from(edges) # Returns list of edge keys
771
+ [0, 1]
772
+ >>> attributes = {(0, 1, 0): {"cost": 21}, (0, 1, 1): {"cost": 7}}
773
+ >>> nx.set_edge_attributes(MG, attributes)
774
+ >>> MG[0][1][0]["cost"]
775
+ 21
776
+ >>> MG[0][1][1]["cost"]
777
+ 7
778
+
779
+ If MultiGraph attributes are desired for a Graph, you must convert the 3-tuple
780
+ multiedge to a 2-tuple edge and the last multiedge's attribute value will
781
+ overwrite the previous values. Continuing from the previous case we get::
782
+
783
+ >>> H = nx.path_graph([0, 1, 2])
784
+ >>> nx.set_edge_attributes(H, {(u, v): ed for u, v, ed in MG.edges.data()})
785
+ >>> nx.get_edge_attributes(H, "cost")
786
+ {(0, 1): 7}
787
+
788
+ """
789
+ if name is not None:
790
+ # `values` does not contain attribute names
791
+ try:
792
+ # if `values` is a dict using `.items()` => {edge: value}
793
+ if G.is_multigraph():
794
+ for (u, v, key), value in values.items():
795
+ try:
796
+ G[u][v][key][name] = value
797
+ except KeyError:
798
+ pass
799
+ else:
800
+ for (u, v), value in values.items():
801
+ try:
802
+ G[u][v][name] = value
803
+ except KeyError:
804
+ pass
805
+ except AttributeError:
806
+ # treat `values` as a constant
807
+ for u, v, data in G.edges(data=True):
808
+ data[name] = values
809
+ else:
810
+ # `values` consists of doct-of-dict {edge: {attr: value}} shape
811
+ if G.is_multigraph():
812
+ for (u, v, key), d in values.items():
813
+ try:
814
+ G[u][v][key].update(d)
815
+ except KeyError:
816
+ pass
817
+ else:
818
+ for (u, v), d in values.items():
819
+ try:
820
+ G[u][v].update(d)
821
+ except KeyError:
822
+ pass
823
+
824
+
825
+ def get_edge_attributes(G, name, default=None):
826
+ """Get edge attributes from graph
827
+
828
+ Parameters
829
+ ----------
830
+ G : NetworkX Graph
831
+
832
+ name : string
833
+ Attribute name
834
+
835
+ default: object (default=None)
836
+ Default value of the edge attribute if there is no value set for that
837
+ edge in graph. If `None` then edges without this attribute are not
838
+ included in the returned dict.
839
+
840
+ Returns
841
+ -------
842
+ Dictionary of attributes keyed by edge. For (di)graphs, the keys are
843
+ 2-tuples of the form: (u, v). For multi(di)graphs, the keys are 3-tuples of
844
+ the form: (u, v, key).
845
+
846
+ Examples
847
+ --------
848
+ >>> G = nx.Graph()
849
+ >>> nx.add_path(G, [1, 2, 3], color="red")
850
+ >>> color = nx.get_edge_attributes(G, "color")
851
+ >>> color[(1, 2)]
852
+ 'red'
853
+ >>> G.add_edge(3, 4)
854
+ >>> color = nx.get_edge_attributes(G, "color", default="yellow")
855
+ >>> color[(3, 4)]
856
+ 'yellow'
857
+ """
858
+ if G.is_multigraph():
859
+ edges = G.edges(keys=True, data=True)
860
+ else:
861
+ edges = G.edges(data=True)
862
+ if default is not None:
863
+ return {x[:-1]: x[-1].get(name, default) for x in edges}
864
+ return {x[:-1]: x[-1][name] for x in edges if name in x[-1]}
865
+
866
+
867
+ def all_neighbors(graph, node):
868
+ """Returns all of the neighbors of a node in the graph.
869
+
870
+ If the graph is directed returns predecessors as well as successors.
871
+
872
+ Parameters
873
+ ----------
874
+ graph : NetworkX graph
875
+ Graph to find neighbors.
876
+
877
+ node : node
878
+ The node whose neighbors will be returned.
879
+
880
+ Returns
881
+ -------
882
+ neighbors : iterator
883
+ Iterator of neighbors
884
+ """
885
+ if graph.is_directed():
886
+ values = chain(graph.predecessors(node), graph.successors(node))
887
+ else:
888
+ values = graph.neighbors(node)
889
+ return values
890
+
891
+
892
+ def non_neighbors(graph, node):
893
+ """Returns the non-neighbors of the node in the graph.
894
+
895
+ Parameters
896
+ ----------
897
+ graph : NetworkX graph
898
+ Graph to find neighbors.
899
+
900
+ node : node
901
+ The node whose neighbors will be returned.
902
+
903
+ Returns
904
+ -------
905
+ non_neighbors : iterator
906
+ Iterator of nodes in the graph that are not neighbors of the node.
907
+ """
908
+ nbors = set(neighbors(graph, node)) | {node}
909
+ return (nnode for nnode in graph if nnode not in nbors)
910
+
911
+
912
+ def non_edges(graph):
913
+ """Returns the nonexistent edges in the graph.
914
+
915
+ Parameters
916
+ ----------
917
+ graph : NetworkX graph.
918
+ Graph to find nonexistent edges.
919
+
920
+ Returns
921
+ -------
922
+ non_edges : iterator
923
+ Iterator of edges that are not in the graph.
924
+ """
925
+ if graph.is_directed():
926
+ for u in graph:
927
+ for v in non_neighbors(graph, u):
928
+ yield (u, v)
929
+ else:
930
+ nodes = set(graph)
931
+ while nodes:
932
+ u = nodes.pop()
933
+ for v in nodes - set(graph[u]):
934
+ yield (u, v)
935
+
936
+
937
+ @not_implemented_for("directed")
938
+ def common_neighbors(G, u, v):
939
+ """Returns the common neighbors of two nodes in a graph.
940
+
941
+ Parameters
942
+ ----------
943
+ G : graph
944
+ A NetworkX undirected graph.
945
+
946
+ u, v : nodes
947
+ Nodes in the graph.
948
+
949
+ Returns
950
+ -------
951
+ cnbors : iterator
952
+ Iterator of common neighbors of u and v in the graph.
953
+
954
+ Raises
955
+ ------
956
+ NetworkXError
957
+ If u or v is not a node in the graph.
958
+
959
+ Examples
960
+ --------
961
+ >>> G = nx.complete_graph(5)
962
+ >>> sorted(nx.common_neighbors(G, 0, 1))
963
+ [2, 3, 4]
964
+ """
965
+ if u not in G:
966
+ raise nx.NetworkXError("u is not in the graph.")
967
+ if v not in G:
968
+ raise nx.NetworkXError("v is not in the graph.")
969
+
970
+ # Return a generator explicitly instead of yielding so that the above
971
+ # checks are executed eagerly.
972
+ return (w for w in G[u] if w in G[v] and w not in (u, v))
973
+
974
+
975
+ def is_weighted(G, edge=None, weight="weight"):
976
+ """Returns True if `G` has weighted edges.
977
+
978
+ Parameters
979
+ ----------
980
+ G : graph
981
+ A NetworkX graph.
982
+
983
+ edge : tuple, optional
984
+ A 2-tuple specifying the only edge in `G` that will be tested. If
985
+ None, then every edge in `G` is tested.
986
+
987
+ weight: string, optional
988
+ The attribute name used to query for edge weights.
989
+
990
+ Returns
991
+ -------
992
+ bool
993
+ A boolean signifying if `G`, or the specified edge, is weighted.
994
+
995
+ Raises
996
+ ------
997
+ NetworkXError
998
+ If the specified edge does not exist.
999
+
1000
+ Examples
1001
+ --------
1002
+ >>> G = nx.path_graph(4)
1003
+ >>> nx.is_weighted(G)
1004
+ False
1005
+ >>> nx.is_weighted(G, (2, 3))
1006
+ False
1007
+
1008
+ >>> G = nx.DiGraph()
1009
+ >>> G.add_edge(1, 2, weight=1)
1010
+ >>> nx.is_weighted(G)
1011
+ True
1012
+
1013
+ """
1014
+ if edge is not None:
1015
+ data = G.get_edge_data(*edge)
1016
+ if data is None:
1017
+ msg = f"Edge {edge!r} does not exist."
1018
+ raise nx.NetworkXError(msg)
1019
+ return weight in data
1020
+
1021
+ if is_empty(G):
1022
+ # Special handling required since: all([]) == True
1023
+ return False
1024
+
1025
+ return all(weight in data for u, v, data in G.edges(data=True))
1026
+
1027
+
1028
+ def is_negatively_weighted(G, edge=None, weight="weight"):
1029
+ """Returns True if `G` has negatively weighted edges.
1030
+
1031
+ Parameters
1032
+ ----------
1033
+ G : graph
1034
+ A NetworkX graph.
1035
+
1036
+ edge : tuple, optional
1037
+ A 2-tuple specifying the only edge in `G` that will be tested. If
1038
+ None, then every edge in `G` is tested.
1039
+
1040
+ weight: string, optional
1041
+ The attribute name used to query for edge weights.
1042
+
1043
+ Returns
1044
+ -------
1045
+ bool
1046
+ A boolean signifying if `G`, or the specified edge, is negatively
1047
+ weighted.
1048
+
1049
+ Raises
1050
+ ------
1051
+ NetworkXError
1052
+ If the specified edge does not exist.
1053
+
1054
+ Examples
1055
+ --------
1056
+ >>> G = nx.Graph()
1057
+ >>> G.add_edges_from([(1, 3), (2, 4), (2, 6)])
1058
+ >>> G.add_edge(1, 2, weight=4)
1059
+ >>> nx.is_negatively_weighted(G, (1, 2))
1060
+ False
1061
+ >>> G[2][4]["weight"] = -2
1062
+ >>> nx.is_negatively_weighted(G)
1063
+ True
1064
+ >>> G = nx.DiGraph()
1065
+ >>> edges = [("0", "3", 3), ("0", "1", -5), ("1", "0", -2)]
1066
+ >>> G.add_weighted_edges_from(edges)
1067
+ >>> nx.is_negatively_weighted(G)
1068
+ True
1069
+
1070
+ """
1071
+ if edge is not None:
1072
+ data = G.get_edge_data(*edge)
1073
+ if data is None:
1074
+ msg = f"Edge {edge!r} does not exist."
1075
+ raise nx.NetworkXError(msg)
1076
+ return weight in data and data[weight] < 0
1077
+
1078
+ return any(weight in data and data[weight] < 0 for u, v, data in G.edges(data=True))
1079
+
1080
+
1081
+ def is_empty(G):
1082
+ """Returns True if `G` has no edges.
1083
+
1084
+ Parameters
1085
+ ----------
1086
+ G : graph
1087
+ A NetworkX graph.
1088
+
1089
+ Returns
1090
+ -------
1091
+ bool
1092
+ True if `G` has no edges, and False otherwise.
1093
+
1094
+ Notes
1095
+ -----
1096
+ An empty graph can have nodes but not edges. The empty graph with zero
1097
+ nodes is known as the null graph. This is an $O(n)$ operation where n
1098
+ is the number of nodes in the graph.
1099
+
1100
+ """
1101
+ return not any(G.adj.values())
1102
+
1103
+
1104
+ def nodes_with_selfloops(G):
1105
+ """Returns an iterator over nodes with self loops.
1106
+
1107
+ A node with a self loop has an edge with both ends adjacent
1108
+ to that node.
1109
+
1110
+ Returns
1111
+ -------
1112
+ nodelist : iterator
1113
+ A iterator over nodes with self loops.
1114
+
1115
+ See Also
1116
+ --------
1117
+ selfloop_edges, number_of_selfloops
1118
+
1119
+ Examples
1120
+ --------
1121
+ >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
1122
+ >>> G.add_edge(1, 1)
1123
+ >>> G.add_edge(1, 2)
1124
+ >>> list(nx.nodes_with_selfloops(G))
1125
+ [1]
1126
+
1127
+ """
1128
+ return (n for n, nbrs in G.adj.items() if n in nbrs)
1129
+
1130
+
1131
+ def selfloop_edges(G, data=False, keys=False, default=None):
1132
+ """Returns an iterator over selfloop edges.
1133
+
1134
+ A selfloop edge has the same node at both ends.
1135
+
1136
+ Parameters
1137
+ ----------
1138
+ G : graph
1139
+ A NetworkX graph.
1140
+ data : string or bool, optional (default=False)
1141
+ Return selfloop edges as two tuples (u, v) (data=False)
1142
+ or three-tuples (u, v, datadict) (data=True)
1143
+ or three-tuples (u, v, datavalue) (data='attrname')
1144
+ keys : bool, optional (default=False)
1145
+ If True, return edge keys with each edge.
1146
+ default : value, optional (default=None)
1147
+ Value used for edges that don't have the requested attribute.
1148
+ Only relevant if data is not True or False.
1149
+
1150
+ Returns
1151
+ -------
1152
+ edgeiter : iterator over edge tuples
1153
+ An iterator over all selfloop edges.
1154
+
1155
+ See Also
1156
+ --------
1157
+ nodes_with_selfloops, number_of_selfloops
1158
+
1159
+ Examples
1160
+ --------
1161
+ >>> G = nx.MultiGraph() # or Graph, DiGraph, MultiDiGraph, etc
1162
+ >>> ekey = G.add_edge(1, 1)
1163
+ >>> ekey = G.add_edge(1, 2)
1164
+ >>> list(nx.selfloop_edges(G))
1165
+ [(1, 1)]
1166
+ >>> list(nx.selfloop_edges(G, data=True))
1167
+ [(1, 1, {})]
1168
+ >>> list(nx.selfloop_edges(G, keys=True))
1169
+ [(1, 1, 0)]
1170
+ >>> list(nx.selfloop_edges(G, keys=True, data=True))
1171
+ [(1, 1, 0, {})]
1172
+ """
1173
+ if data is True:
1174
+ if G.is_multigraph():
1175
+ if keys is True:
1176
+ return (
1177
+ (n, n, k, d)
1178
+ for n, nbrs in G.adj.items()
1179
+ if n in nbrs
1180
+ for k, d in nbrs[n].items()
1181
+ )
1182
+ else:
1183
+ return (
1184
+ (n, n, d)
1185
+ for n, nbrs in G.adj.items()
1186
+ if n in nbrs
1187
+ for d in nbrs[n].values()
1188
+ )
1189
+ else:
1190
+ return ((n, n, nbrs[n]) for n, nbrs in G.adj.items() if n in nbrs)
1191
+ elif data is not False:
1192
+ if G.is_multigraph():
1193
+ if keys is True:
1194
+ return (
1195
+ (n, n, k, d.get(data, default))
1196
+ for n, nbrs in G.adj.items()
1197
+ if n in nbrs
1198
+ for k, d in nbrs[n].items()
1199
+ )
1200
+ else:
1201
+ return (
1202
+ (n, n, d.get(data, default))
1203
+ for n, nbrs in G.adj.items()
1204
+ if n in nbrs
1205
+ for d in nbrs[n].values()
1206
+ )
1207
+ else:
1208
+ return (
1209
+ (n, n, nbrs[n].get(data, default))
1210
+ for n, nbrs in G.adj.items()
1211
+ if n in nbrs
1212
+ )
1213
+ else:
1214
+ if G.is_multigraph():
1215
+ if keys is True:
1216
+ return (
1217
+ (n, n, k) for n, nbrs in G.adj.items() if n in nbrs for k in nbrs[n]
1218
+ )
1219
+ else:
1220
+ return (
1221
+ (n, n)
1222
+ for n, nbrs in G.adj.items()
1223
+ if n in nbrs
1224
+ for i in range(len(nbrs[n])) # for easy edge removal (#4068)
1225
+ )
1226
+ else:
1227
+ return ((n, n) for n, nbrs in G.adj.items() if n in nbrs)
1228
+
1229
+
1230
+ def number_of_selfloops(G):
1231
+ """Returns the number of selfloop edges.
1232
+
1233
+ A selfloop edge has the same node at both ends.
1234
+
1235
+ Returns
1236
+ -------
1237
+ nloops : int
1238
+ The number of selfloops.
1239
+
1240
+ See Also
1241
+ --------
1242
+ nodes_with_selfloops, selfloop_edges
1243
+
1244
+ Examples
1245
+ --------
1246
+ >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
1247
+ >>> G.add_edge(1, 1)
1248
+ >>> G.add_edge(1, 2)
1249
+ >>> nx.number_of_selfloops(G)
1250
+ 1
1251
+ """
1252
+ return sum(1 for _ in nx.selfloop_edges(G))
1253
+
1254
+
1255
+ def is_path(G, path):
1256
+ """Returns whether or not the specified path exists.
1257
+
1258
+ For it to return True, every node on the path must exist and
1259
+ each consecutive pair must be connected via one or more edges.
1260
+
1261
+ Parameters
1262
+ ----------
1263
+ G : graph
1264
+ A NetworkX graph.
1265
+
1266
+ path : list
1267
+ A list of nodes which defines the path to traverse
1268
+
1269
+ Returns
1270
+ -------
1271
+ bool
1272
+ True if `path` is a valid path in `G`
1273
+
1274
+ """
1275
+ return all((node in G and nbr in G[node]) for node, nbr in nx.utils.pairwise(path))
1276
+
1277
+
1278
+ def path_weight(G, path, weight):
1279
+ """Returns total cost associated with specified path and weight
1280
+
1281
+ Parameters
1282
+ ----------
1283
+ G : graph
1284
+ A NetworkX graph.
1285
+
1286
+ path: list
1287
+ A list of node labels which defines the path to traverse
1288
+
1289
+ weight: string
1290
+ A string indicating which edge attribute to use for path cost
1291
+
1292
+ Returns
1293
+ -------
1294
+ cost: int or float
1295
+ An integer or a float representing the total cost with respect to the
1296
+ specified weight of the specified path
1297
+
1298
+ Raises
1299
+ ------
1300
+ NetworkXNoPath
1301
+ If the specified edge does not exist.
1302
+ """
1303
+ multigraph = G.is_multigraph()
1304
+ cost = 0
1305
+
1306
+ if not nx.is_path(G, path):
1307
+ raise nx.NetworkXNoPath("path does not exist")
1308
+ for node, nbr in nx.utils.pairwise(path):
1309
+ if multigraph:
1310
+ cost += min(v[weight] for v in G[node][nbr].values())
1311
+ else:
1312
+ cost += G[node][nbr][weight]
1313
+ return cost
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/graph.py ADDED
@@ -0,0 +1,2030 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Base class for undirected graphs.
2
+
3
+ The Graph class allows any hashable object as a node
4
+ and can associate key/value attribute pairs with each undirected edge.
5
+
6
+ Self-loops are allowed but multiple edges are not (see MultiGraph).
7
+
8
+ For directed graphs see DiGraph and MultiDiGraph.
9
+ """
10
+ from copy import deepcopy
11
+ from functools import cached_property
12
+
13
+ import networkx as nx
14
+ from networkx import convert
15
+ from networkx.classes.coreviews import AdjacencyView
16
+ from networkx.classes.reportviews import DegreeView, EdgeView, NodeView
17
+ from networkx.exception import NetworkXError
18
+
19
+ __all__ = ["Graph"]
20
+
21
+
22
+ class _CachedPropertyResetterAdj:
23
+ """Data Descriptor class for _adj that resets ``adj`` cached_property when needed
24
+
25
+ This assumes that the ``cached_property`` ``G.adj`` should be reset whenever
26
+ ``G._adj`` is set to a new value.
27
+
28
+ This object sits on a class and ensures that any instance of that
29
+ class clears its cached property "adj" whenever the underlying
30
+ instance attribute "_adj" is set to a new object. It only affects
31
+ the set process of the obj._adj attribute. All get/del operations
32
+ act as they normally would.
33
+
34
+ For info on Data Descriptors see: https://docs.python.org/3/howto/descriptor.html
35
+ """
36
+
37
+ def __set__(self, obj, value):
38
+ od = obj.__dict__
39
+ od["_adj"] = value
40
+ if "adj" in od:
41
+ del od["adj"]
42
+
43
+
44
+ class _CachedPropertyResetterNode:
45
+ """Data Descriptor class for _node that resets ``nodes`` cached_property when needed
46
+
47
+ This assumes that the ``cached_property`` ``G.node`` should be reset whenever
48
+ ``G._node`` is set to a new value.
49
+
50
+ This object sits on a class and ensures that any instance of that
51
+ class clears its cached property "nodes" whenever the underlying
52
+ instance attribute "_node" is set to a new object. It only affects
53
+ the set process of the obj._adj attribute. All get/del operations
54
+ act as they normally would.
55
+
56
+ For info on Data Descriptors see: https://docs.python.org/3/howto/descriptor.html
57
+ """
58
+
59
+ def __set__(self, obj, value):
60
+ od = obj.__dict__
61
+ od["_node"] = value
62
+ if "nodes" in od:
63
+ del od["nodes"]
64
+
65
+
66
+ class Graph:
67
+ """
68
+ Base class for undirected graphs.
69
+
70
+ A Graph stores nodes and edges with optional data, or attributes.
71
+
72
+ Graphs hold undirected edges. Self loops are allowed but multiple
73
+ (parallel) edges are not.
74
+
75
+ Nodes can be arbitrary (hashable) Python objects with optional
76
+ key/value attributes, except that `None` is not allowed as a node.
77
+
78
+ Edges are represented as links between nodes with optional
79
+ key/value attributes.
80
+
81
+ Parameters
82
+ ----------
83
+ incoming_graph_data : input graph (optional, default: None)
84
+ Data to initialize graph. If None (default) an empty
85
+ graph is created. The data can be any format that is supported
86
+ by the to_networkx_graph() function, currently including edge list,
87
+ dict of dicts, dict of lists, NetworkX graph, 2D NumPy array, SciPy
88
+ sparse matrix, or PyGraphviz graph.
89
+
90
+ attr : keyword arguments, optional (default= no attributes)
91
+ Attributes to add to graph as key=value pairs.
92
+
93
+ See Also
94
+ --------
95
+ DiGraph
96
+ MultiGraph
97
+ MultiDiGraph
98
+
99
+ Examples
100
+ --------
101
+ Create an empty graph structure (a "null graph") with no nodes and
102
+ no edges.
103
+
104
+ >>> G = nx.Graph()
105
+
106
+ G can be grown in several ways.
107
+
108
+ **Nodes:**
109
+
110
+ Add one node at a time:
111
+
112
+ >>> G.add_node(1)
113
+
114
+ Add the nodes from any container (a list, dict, set or
115
+ even the lines from a file or the nodes from another graph).
116
+
117
+ >>> G.add_nodes_from([2, 3])
118
+ >>> G.add_nodes_from(range(100, 110))
119
+ >>> H = nx.path_graph(10)
120
+ >>> G.add_nodes_from(H)
121
+
122
+ In addition to strings and integers any hashable Python object
123
+ (except None) can represent a node, e.g. a customized node object,
124
+ or even another Graph.
125
+
126
+ >>> G.add_node(H)
127
+
128
+ **Edges:**
129
+
130
+ G can also be grown by adding edges.
131
+
132
+ Add one edge,
133
+
134
+ >>> G.add_edge(1, 2)
135
+
136
+ a list of edges,
137
+
138
+ >>> G.add_edges_from([(1, 2), (1, 3)])
139
+
140
+ or a collection of edges,
141
+
142
+ >>> G.add_edges_from(H.edges)
143
+
144
+ If some edges connect nodes not yet in the graph, the nodes
145
+ are added automatically. There are no errors when adding
146
+ nodes or edges that already exist.
147
+
148
+ **Attributes:**
149
+
150
+ Each graph, node, and edge can hold key/value attribute pairs
151
+ in an associated attribute dictionary (the keys must be hashable).
152
+ By default these are empty, but can be added or changed using
153
+ add_edge, add_node or direct manipulation of the attribute
154
+ dictionaries named graph, node and edge respectively.
155
+
156
+ >>> G = nx.Graph(day="Friday")
157
+ >>> G.graph
158
+ {'day': 'Friday'}
159
+
160
+ Add node attributes using add_node(), add_nodes_from() or G.nodes
161
+
162
+ >>> G.add_node(1, time="5pm")
163
+ >>> G.add_nodes_from([3], time="2pm")
164
+ >>> G.nodes[1]
165
+ {'time': '5pm'}
166
+ >>> G.nodes[1]["room"] = 714 # node must exist already to use G.nodes
167
+ >>> del G.nodes[1]["room"] # remove attribute
168
+ >>> list(G.nodes(data=True))
169
+ [(1, {'time': '5pm'}), (3, {'time': '2pm'})]
170
+
171
+ Add edge attributes using add_edge(), add_edges_from(), subscript
172
+ notation, or G.edges.
173
+
174
+ >>> G.add_edge(1, 2, weight=4.7)
175
+ >>> G.add_edges_from([(3, 4), (4, 5)], color="red")
176
+ >>> G.add_edges_from([(1, 2, {"color": "blue"}), (2, 3, {"weight": 8})])
177
+ >>> G[1][2]["weight"] = 4.7
178
+ >>> G.edges[1, 2]["weight"] = 4
179
+
180
+ Warning: we protect the graph data structure by making `G.edges` a
181
+ read-only dict-like structure. However, you can assign to attributes
182
+ in e.g. `G.edges[1, 2]`. Thus, use 2 sets of brackets to add/change
183
+ data attributes: `G.edges[1, 2]['weight'] = 4`
184
+ (For multigraphs: `MG.edges[u, v, key][name] = value`).
185
+
186
+ **Shortcuts:**
187
+
188
+ Many common graph features allow python syntax to speed reporting.
189
+
190
+ >>> 1 in G # check if node in graph
191
+ True
192
+ >>> [n for n in G if n < 3] # iterate through nodes
193
+ [1, 2]
194
+ >>> len(G) # number of nodes in graph
195
+ 5
196
+
197
+ Often the best way to traverse all edges of a graph is via the neighbors.
198
+ The neighbors are reported as an adjacency-dict `G.adj` or `G.adjacency()`
199
+
200
+ >>> for n, nbrsdict in G.adjacency():
201
+ ... for nbr, eattr in nbrsdict.items():
202
+ ... if "weight" in eattr:
203
+ ... # Do something useful with the edges
204
+ ... pass
205
+
206
+ But the edges() method is often more convenient:
207
+
208
+ >>> for u, v, weight in G.edges.data("weight"):
209
+ ... if weight is not None:
210
+ ... # Do something useful with the edges
211
+ ... pass
212
+
213
+ **Reporting:**
214
+
215
+ Simple graph information is obtained using object-attributes and methods.
216
+ Reporting typically provides views instead of containers to reduce memory
217
+ usage. The views update as the graph is updated similarly to dict-views.
218
+ The objects `nodes`, `edges` and `adj` provide access to data attributes
219
+ via lookup (e.g. `nodes[n]`, `edges[u, v]`, `adj[u][v]`) and iteration
220
+ (e.g. `nodes.items()`, `nodes.data('color')`,
221
+ `nodes.data('color', default='blue')` and similarly for `edges`)
222
+ Views exist for `nodes`, `edges`, `neighbors()`/`adj` and `degree`.
223
+
224
+ For details on these and other miscellaneous methods, see below.
225
+
226
+ **Subclasses (Advanced):**
227
+
228
+ The Graph class uses a dict-of-dict-of-dict data structure.
229
+ The outer dict (node_dict) holds adjacency information keyed by node.
230
+ The next dict (adjlist_dict) represents the adjacency information and holds
231
+ edge data keyed by neighbor. The inner dict (edge_attr_dict) represents
232
+ the edge data and holds edge attribute values keyed by attribute names.
233
+
234
+ Each of these three dicts can be replaced in a subclass by a user defined
235
+ dict-like object. In general, the dict-like features should be
236
+ maintained but extra features can be added. To replace one of the
237
+ dicts create a new graph class by changing the class(!) variable
238
+ holding the factory for that dict-like structure.
239
+
240
+ node_dict_factory : function, (default: dict)
241
+ Factory function to be used to create the dict containing node
242
+ attributes, keyed by node id.
243
+ It should require no arguments and return a dict-like object
244
+
245
+ node_attr_dict_factory: function, (default: dict)
246
+ Factory function to be used to create the node attribute
247
+ dict which holds attribute values keyed by attribute name.
248
+ It should require no arguments and return a dict-like object
249
+
250
+ adjlist_outer_dict_factory : function, (default: dict)
251
+ Factory function to be used to create the outer-most dict
252
+ in the data structure that holds adjacency info keyed by node.
253
+ It should require no arguments and return a dict-like object.
254
+
255
+ adjlist_inner_dict_factory : function, (default: dict)
256
+ Factory function to be used to create the adjacency list
257
+ dict which holds edge data keyed by neighbor.
258
+ It should require no arguments and return a dict-like object
259
+
260
+ edge_attr_dict_factory : function, (default: dict)
261
+ Factory function to be used to create the edge attribute
262
+ dict which holds attribute values keyed by attribute name.
263
+ It should require no arguments and return a dict-like object.
264
+
265
+ graph_attr_dict_factory : function, (default: dict)
266
+ Factory function to be used to create the graph attribute
267
+ dict which holds attribute values keyed by attribute name.
268
+ It should require no arguments and return a dict-like object.
269
+
270
+ Typically, if your extension doesn't impact the data structure all
271
+ methods will inherit without issue except: `to_directed/to_undirected`.
272
+ By default these methods create a DiGraph/Graph class and you probably
273
+ want them to create your extension of a DiGraph/Graph. To facilitate
274
+ this we define two class variables that you can set in your subclass.
275
+
276
+ to_directed_class : callable, (default: DiGraph or MultiDiGraph)
277
+ Class to create a new graph structure in the `to_directed` method.
278
+ If `None`, a NetworkX class (DiGraph or MultiDiGraph) is used.
279
+
280
+ to_undirected_class : callable, (default: Graph or MultiGraph)
281
+ Class to create a new graph structure in the `to_undirected` method.
282
+ If `None`, a NetworkX class (Graph or MultiGraph) is used.
283
+
284
+ **Subclassing Example**
285
+
286
+ Create a low memory graph class that effectively disallows edge
287
+ attributes by using a single attribute dict for all edges.
288
+ This reduces the memory used, but you lose edge attributes.
289
+
290
+ >>> class ThinGraph(nx.Graph):
291
+ ... all_edge_dict = {"weight": 1}
292
+ ...
293
+ ... def single_edge_dict(self):
294
+ ... return self.all_edge_dict
295
+ ...
296
+ ... edge_attr_dict_factory = single_edge_dict
297
+ >>> G = ThinGraph()
298
+ >>> G.add_edge(2, 1)
299
+ >>> G[2][1]
300
+ {'weight': 1}
301
+ >>> G.add_edge(2, 2)
302
+ >>> G[2][1] is G[2][2]
303
+ True
304
+ """
305
+
306
+ _adj = _CachedPropertyResetterAdj()
307
+ _node = _CachedPropertyResetterNode()
308
+
309
+ node_dict_factory = dict
310
+ node_attr_dict_factory = dict
311
+ adjlist_outer_dict_factory = dict
312
+ adjlist_inner_dict_factory = dict
313
+ edge_attr_dict_factory = dict
314
+ graph_attr_dict_factory = dict
315
+
316
+ def to_directed_class(self):
317
+ """Returns the class to use for empty directed copies.
318
+
319
+ If you subclass the base classes, use this to designate
320
+ what directed class to use for `to_directed()` copies.
321
+ """
322
+ return nx.DiGraph
323
+
324
+ def to_undirected_class(self):
325
+ """Returns the class to use for empty undirected copies.
326
+
327
+ If you subclass the base classes, use this to designate
328
+ what directed class to use for `to_directed()` copies.
329
+ """
330
+ return Graph
331
+
332
+ def __init__(self, incoming_graph_data=None, **attr):
333
+ """Initialize a graph with edges, name, or graph attributes.
334
+
335
+ Parameters
336
+ ----------
337
+ incoming_graph_data : input graph (optional, default: None)
338
+ Data to initialize graph. If None (default) an empty
339
+ graph is created. The data can be an edge list, or any
340
+ NetworkX graph object. If the corresponding optional Python
341
+ packages are installed the data can also be a 2D NumPy array, a
342
+ SciPy sparse array, or a PyGraphviz graph.
343
+
344
+ attr : keyword arguments, optional (default= no attributes)
345
+ Attributes to add to graph as key=value pairs.
346
+
347
+ See Also
348
+ --------
349
+ convert
350
+
351
+ Examples
352
+ --------
353
+ >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
354
+ >>> G = nx.Graph(name="my graph")
355
+ >>> e = [(1, 2), (2, 3), (3, 4)] # list of edges
356
+ >>> G = nx.Graph(e)
357
+
358
+ Arbitrary graph attribute pairs (key=value) may be assigned
359
+
360
+ >>> G = nx.Graph(e, day="Friday")
361
+ >>> G.graph
362
+ {'day': 'Friday'}
363
+
364
+ """
365
+ self.graph = self.graph_attr_dict_factory() # dictionary for graph attributes
366
+ self._node = self.node_dict_factory() # empty node attribute dict
367
+ self._adj = self.adjlist_outer_dict_factory() # empty adjacency dict
368
+ # attempt to load graph with data
369
+ if incoming_graph_data is not None:
370
+ convert.to_networkx_graph(incoming_graph_data, create_using=self)
371
+ # load graph attributes (must be after convert)
372
+ self.graph.update(attr)
373
+
374
+ @cached_property
375
+ def adj(self):
376
+ """Graph adjacency object holding the neighbors of each node.
377
+
378
+ This object is a read-only dict-like structure with node keys
379
+ and neighbor-dict values. The neighbor-dict is keyed by neighbor
380
+ to the edge-data-dict. So `G.adj[3][2]['color'] = 'blue'` sets
381
+ the color of the edge `(3, 2)` to `"blue"`.
382
+
383
+ Iterating over G.adj behaves like a dict. Useful idioms include
384
+ `for nbr, datadict in G.adj[n].items():`.
385
+
386
+ The neighbor information is also provided by subscripting the graph.
387
+ So `for nbr, foovalue in G[node].data('foo', default=1):` works.
388
+
389
+ For directed graphs, `G.adj` holds outgoing (successor) info.
390
+ """
391
+ return AdjacencyView(self._adj)
392
+
393
+ @property
394
+ def name(self):
395
+ """String identifier of the graph.
396
+
397
+ This graph attribute appears in the attribute dict G.graph
398
+ keyed by the string `"name"`. as well as an attribute (technically
399
+ a property) `G.name`. This is entirely user controlled.
400
+ """
401
+ return self.graph.get("name", "")
402
+
403
+ @name.setter
404
+ def name(self, s):
405
+ self.graph["name"] = s
406
+
407
+ def __str__(self):
408
+ """Returns a short summary of the graph.
409
+
410
+ Returns
411
+ -------
412
+ info : string
413
+ Graph information including the graph name (if any), graph type, and the
414
+ number of nodes and edges.
415
+
416
+ Examples
417
+ --------
418
+ >>> G = nx.Graph(name="foo")
419
+ >>> str(G)
420
+ "Graph named 'foo' with 0 nodes and 0 edges"
421
+
422
+ >>> G = nx.path_graph(3)
423
+ >>> str(G)
424
+ 'Graph with 3 nodes and 2 edges'
425
+
426
+ """
427
+ return "".join(
428
+ [
429
+ type(self).__name__,
430
+ f" named {self.name!r}" if self.name else "",
431
+ f" with {self.number_of_nodes()} nodes and {self.number_of_edges()} edges",
432
+ ]
433
+ )
434
+
435
+ def __iter__(self):
436
+ """Iterate over the nodes. Use: 'for n in G'.
437
+
438
+ Returns
439
+ -------
440
+ niter : iterator
441
+ An iterator over all nodes in the graph.
442
+
443
+ Examples
444
+ --------
445
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
446
+ >>> [n for n in G]
447
+ [0, 1, 2, 3]
448
+ >>> list(G)
449
+ [0, 1, 2, 3]
450
+ """
451
+ return iter(self._node)
452
+
453
+ def __contains__(self, n):
454
+ """Returns True if n is a node, False otherwise. Use: 'n in G'.
455
+
456
+ Examples
457
+ --------
458
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
459
+ >>> 1 in G
460
+ True
461
+ """
462
+ try:
463
+ return n in self._node
464
+ except TypeError:
465
+ return False
466
+
467
+ def __len__(self):
468
+ """Returns the number of nodes in the graph. Use: 'len(G)'.
469
+
470
+ Returns
471
+ -------
472
+ nnodes : int
473
+ The number of nodes in the graph.
474
+
475
+ See Also
476
+ --------
477
+ number_of_nodes: identical method
478
+ order: identical method
479
+
480
+ Examples
481
+ --------
482
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
483
+ >>> len(G)
484
+ 4
485
+
486
+ """
487
+ return len(self._node)
488
+
489
+ def __getitem__(self, n):
490
+ """Returns a dict of neighbors of node n. Use: 'G[n]'.
491
+
492
+ Parameters
493
+ ----------
494
+ n : node
495
+ A node in the graph.
496
+
497
+ Returns
498
+ -------
499
+ adj_dict : dictionary
500
+ The adjacency dictionary for nodes connected to n.
501
+
502
+ Notes
503
+ -----
504
+ G[n] is the same as G.adj[n] and similar to G.neighbors(n)
505
+ (which is an iterator over G.adj[n])
506
+
507
+ Examples
508
+ --------
509
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
510
+ >>> G[0]
511
+ AtlasView({1: {}})
512
+ """
513
+ return self.adj[n]
514
+
515
+ def add_node(self, node_for_adding, **attr):
516
+ """Add a single node `node_for_adding` and update node attributes.
517
+
518
+ Parameters
519
+ ----------
520
+ node_for_adding : node
521
+ A node can be any hashable Python object except None.
522
+ attr : keyword arguments, optional
523
+ Set or change node attributes using key=value.
524
+
525
+ See Also
526
+ --------
527
+ add_nodes_from
528
+
529
+ Examples
530
+ --------
531
+ >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
532
+ >>> G.add_node(1)
533
+ >>> G.add_node("Hello")
534
+ >>> K3 = nx.Graph([(0, 1), (1, 2), (2, 0)])
535
+ >>> G.add_node(K3)
536
+ >>> G.number_of_nodes()
537
+ 3
538
+
539
+ Use keywords set/change node attributes:
540
+
541
+ >>> G.add_node(1, size=10)
542
+ >>> G.add_node(3, weight=0.4, UTM=("13S", 382871, 3972649))
543
+
544
+ Notes
545
+ -----
546
+ A hashable object is one that can be used as a key in a Python
547
+ dictionary. This includes strings, numbers, tuples of strings
548
+ and numbers, etc.
549
+
550
+ On many platforms hashable items also include mutables such as
551
+ NetworkX Graphs, though one should be careful that the hash
552
+ doesn't change on mutables.
553
+ """
554
+ if node_for_adding not in self._node:
555
+ if node_for_adding is None:
556
+ raise ValueError("None cannot be a node")
557
+ self._adj[node_for_adding] = self.adjlist_inner_dict_factory()
558
+ attr_dict = self._node[node_for_adding] = self.node_attr_dict_factory()
559
+ attr_dict.update(attr)
560
+ else: # update attr even if node already exists
561
+ self._node[node_for_adding].update(attr)
562
+
563
+ def add_nodes_from(self, nodes_for_adding, **attr):
564
+ """Add multiple nodes.
565
+
566
+ Parameters
567
+ ----------
568
+ nodes_for_adding : iterable container
569
+ A container of nodes (list, dict, set, etc.).
570
+ OR
571
+ A container of (node, attribute dict) tuples.
572
+ Node attributes are updated using the attribute dict.
573
+ attr : keyword arguments, optional (default= no attributes)
574
+ Update attributes for all nodes in nodes.
575
+ Node attributes specified in nodes as a tuple take
576
+ precedence over attributes specified via keyword arguments.
577
+
578
+ See Also
579
+ --------
580
+ add_node
581
+
582
+ Notes
583
+ -----
584
+ When adding nodes from an iterator over the graph you are changing,
585
+ a `RuntimeError` can be raised with message:
586
+ `RuntimeError: dictionary changed size during iteration`. This
587
+ happens when the graph's underlying dictionary is modified during
588
+ iteration. To avoid this error, evaluate the iterator into a separate
589
+ object, e.g. by using `list(iterator_of_nodes)`, and pass this
590
+ object to `G.add_nodes_from`.
591
+
592
+ Examples
593
+ --------
594
+ >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
595
+ >>> G.add_nodes_from("Hello")
596
+ >>> K3 = nx.Graph([(0, 1), (1, 2), (2, 0)])
597
+ >>> G.add_nodes_from(K3)
598
+ >>> sorted(G.nodes(), key=str)
599
+ [0, 1, 2, 'H', 'e', 'l', 'o']
600
+
601
+ Use keywords to update specific node attributes for every node.
602
+
603
+ >>> G.add_nodes_from([1, 2], size=10)
604
+ >>> G.add_nodes_from([3, 4], weight=0.4)
605
+
606
+ Use (node, attrdict) tuples to update attributes for specific nodes.
607
+
608
+ >>> G.add_nodes_from([(1, dict(size=11)), (2, {"color": "blue"})])
609
+ >>> G.nodes[1]["size"]
610
+ 11
611
+ >>> H = nx.Graph()
612
+ >>> H.add_nodes_from(G.nodes(data=True))
613
+ >>> H.nodes[1]["size"]
614
+ 11
615
+
616
+ Evaluate an iterator over a graph if using it to modify the same graph
617
+
618
+ >>> G = nx.Graph([(0, 1), (1, 2), (3, 4)])
619
+ >>> # wrong way - will raise RuntimeError
620
+ >>> # G.add_nodes_from(n + 1 for n in G.nodes)
621
+ >>> # correct way
622
+ >>> G.add_nodes_from(list(n + 1 for n in G.nodes))
623
+ """
624
+ for n in nodes_for_adding:
625
+ try:
626
+ newnode = n not in self._node
627
+ newdict = attr
628
+ except TypeError:
629
+ n, ndict = n
630
+ newnode = n not in self._node
631
+ newdict = attr.copy()
632
+ newdict.update(ndict)
633
+ if newnode:
634
+ if n is None:
635
+ raise ValueError("None cannot be a node")
636
+ self._adj[n] = self.adjlist_inner_dict_factory()
637
+ self._node[n] = self.node_attr_dict_factory()
638
+ self._node[n].update(newdict)
639
+
640
+ def remove_node(self, n):
641
+ """Remove node n.
642
+
643
+ Removes the node n and all adjacent edges.
644
+ Attempting to remove a nonexistent node will raise an exception.
645
+
646
+ Parameters
647
+ ----------
648
+ n : node
649
+ A node in the graph
650
+
651
+ Raises
652
+ ------
653
+ NetworkXError
654
+ If n is not in the graph.
655
+
656
+ See Also
657
+ --------
658
+ remove_nodes_from
659
+
660
+ Examples
661
+ --------
662
+ >>> G = nx.path_graph(3) # or DiGraph, MultiGraph, MultiDiGraph, etc
663
+ >>> list(G.edges)
664
+ [(0, 1), (1, 2)]
665
+ >>> G.remove_node(1)
666
+ >>> list(G.edges)
667
+ []
668
+
669
+ """
670
+ adj = self._adj
671
+ try:
672
+ nbrs = list(adj[n]) # list handles self-loops (allows mutation)
673
+ del self._node[n]
674
+ except KeyError as err: # NetworkXError if n not in self
675
+ raise NetworkXError(f"The node {n} is not in the graph.") from err
676
+ for u in nbrs:
677
+ del adj[u][n] # remove all edges n-u in graph
678
+ del adj[n] # now remove node
679
+
680
+ def remove_nodes_from(self, nodes):
681
+ """Remove multiple nodes.
682
+
683
+ Parameters
684
+ ----------
685
+ nodes : iterable container
686
+ A container of nodes (list, dict, set, etc.). If a node
687
+ in the container is not in the graph it is silently
688
+ ignored.
689
+
690
+ See Also
691
+ --------
692
+ remove_node
693
+
694
+ Notes
695
+ -----
696
+ When removing nodes from an iterator over the graph you are changing,
697
+ a `RuntimeError` will be raised with message:
698
+ `RuntimeError: dictionary changed size during iteration`. This
699
+ happens when the graph's underlying dictionary is modified during
700
+ iteration. To avoid this error, evaluate the iterator into a separate
701
+ object, e.g. by using `list(iterator_of_nodes)`, and pass this
702
+ object to `G.remove_nodes_from`.
703
+
704
+ Examples
705
+ --------
706
+ >>> G = nx.path_graph(3) # or DiGraph, MultiGraph, MultiDiGraph, etc
707
+ >>> e = list(G.nodes)
708
+ >>> e
709
+ [0, 1, 2]
710
+ >>> G.remove_nodes_from(e)
711
+ >>> list(G.nodes)
712
+ []
713
+
714
+ Evaluate an iterator over a graph if using it to modify the same graph
715
+
716
+ >>> G = nx.Graph([(0, 1), (1, 2), (3, 4)])
717
+ >>> # this command will fail, as the graph's dict is modified during iteration
718
+ >>> # G.remove_nodes_from(n for n in G.nodes if n < 2)
719
+ >>> # this command will work, since the dictionary underlying graph is not modified
720
+ >>> G.remove_nodes_from(list(n for n in G.nodes if n < 2))
721
+ """
722
+ adj = self._adj
723
+ for n in nodes:
724
+ try:
725
+ del self._node[n]
726
+ for u in list(adj[n]): # list handles self-loops
727
+ del adj[u][n] # (allows mutation of dict in loop)
728
+ del adj[n]
729
+ except KeyError:
730
+ pass
731
+
732
+ @cached_property
733
+ def nodes(self):
734
+ """A NodeView of the Graph as G.nodes or G.nodes().
735
+
736
+ Can be used as `G.nodes` for data lookup and for set-like operations.
737
+ Can also be used as `G.nodes(data='color', default=None)` to return a
738
+ NodeDataView which reports specific node data but no set operations.
739
+ It presents a dict-like interface as well with `G.nodes.items()`
740
+ iterating over `(node, nodedata)` 2-tuples and `G.nodes[3]['foo']`
741
+ providing the value of the `foo` attribute for node `3`. In addition,
742
+ a view `G.nodes.data('foo')` provides a dict-like interface to the
743
+ `foo` attribute of each node. `G.nodes.data('foo', default=1)`
744
+ provides a default for nodes that do not have attribute `foo`.
745
+
746
+ Parameters
747
+ ----------
748
+ data : string or bool, optional (default=False)
749
+ The node attribute returned in 2-tuple (n, ddict[data]).
750
+ If True, return entire node attribute dict as (n, ddict).
751
+ If False, return just the nodes n.
752
+
753
+ default : value, optional (default=None)
754
+ Value used for nodes that don't have the requested attribute.
755
+ Only relevant if data is not True or False.
756
+
757
+ Returns
758
+ -------
759
+ NodeView
760
+ Allows set-like operations over the nodes as well as node
761
+ attribute dict lookup and calling to get a NodeDataView.
762
+ A NodeDataView iterates over `(n, data)` and has no set operations.
763
+ A NodeView iterates over `n` and includes set operations.
764
+
765
+ When called, if data is False, an iterator over nodes.
766
+ Otherwise an iterator of 2-tuples (node, attribute value)
767
+ where the attribute is specified in `data`.
768
+ If data is True then the attribute becomes the
769
+ entire data dictionary.
770
+
771
+ Notes
772
+ -----
773
+ If your node data is not needed, it is simpler and equivalent
774
+ to use the expression ``for n in G``, or ``list(G)``.
775
+
776
+ Examples
777
+ --------
778
+ There are two simple ways of getting a list of all nodes in the graph:
779
+
780
+ >>> G = nx.path_graph(3)
781
+ >>> list(G.nodes)
782
+ [0, 1, 2]
783
+ >>> list(G)
784
+ [0, 1, 2]
785
+
786
+ To get the node data along with the nodes:
787
+
788
+ >>> G.add_node(1, time="5pm")
789
+ >>> G.nodes[0]["foo"] = "bar"
790
+ >>> list(G.nodes(data=True))
791
+ [(0, {'foo': 'bar'}), (1, {'time': '5pm'}), (2, {})]
792
+ >>> list(G.nodes.data())
793
+ [(0, {'foo': 'bar'}), (1, {'time': '5pm'}), (2, {})]
794
+
795
+ >>> list(G.nodes(data="foo"))
796
+ [(0, 'bar'), (1, None), (2, None)]
797
+ >>> list(G.nodes.data("foo"))
798
+ [(0, 'bar'), (1, None), (2, None)]
799
+
800
+ >>> list(G.nodes(data="time"))
801
+ [(0, None), (1, '5pm'), (2, None)]
802
+ >>> list(G.nodes.data("time"))
803
+ [(0, None), (1, '5pm'), (2, None)]
804
+
805
+ >>> list(G.nodes(data="time", default="Not Available"))
806
+ [(0, 'Not Available'), (1, '5pm'), (2, 'Not Available')]
807
+ >>> list(G.nodes.data("time", default="Not Available"))
808
+ [(0, 'Not Available'), (1, '5pm'), (2, 'Not Available')]
809
+
810
+ If some of your nodes have an attribute and the rest are assumed
811
+ to have a default attribute value you can create a dictionary
812
+ from node/attribute pairs using the `default` keyword argument
813
+ to guarantee the value is never None::
814
+
815
+ >>> G = nx.Graph()
816
+ >>> G.add_node(0)
817
+ >>> G.add_node(1, weight=2)
818
+ >>> G.add_node(2, weight=3)
819
+ >>> dict(G.nodes(data="weight", default=1))
820
+ {0: 1, 1: 2, 2: 3}
821
+
822
+ """
823
+ return NodeView(self)
824
+
825
+ def number_of_nodes(self):
826
+ """Returns the number of nodes in the graph.
827
+
828
+ Returns
829
+ -------
830
+ nnodes : int
831
+ The number of nodes in the graph.
832
+
833
+ See Also
834
+ --------
835
+ order: identical method
836
+ __len__: identical method
837
+
838
+ Examples
839
+ --------
840
+ >>> G = nx.path_graph(3) # or DiGraph, MultiGraph, MultiDiGraph, etc
841
+ >>> G.number_of_nodes()
842
+ 3
843
+ """
844
+ return len(self._node)
845
+
846
+ def order(self):
847
+ """Returns the number of nodes in the graph.
848
+
849
+ Returns
850
+ -------
851
+ nnodes : int
852
+ The number of nodes in the graph.
853
+
854
+ See Also
855
+ --------
856
+ number_of_nodes: identical method
857
+ __len__: identical method
858
+
859
+ Examples
860
+ --------
861
+ >>> G = nx.path_graph(3) # or DiGraph, MultiGraph, MultiDiGraph, etc
862
+ >>> G.order()
863
+ 3
864
+ """
865
+ return len(self._node)
866
+
867
+ def has_node(self, n):
868
+ """Returns True if the graph contains the node n.
869
+
870
+ Identical to `n in G`
871
+
872
+ Parameters
873
+ ----------
874
+ n : node
875
+
876
+ Examples
877
+ --------
878
+ >>> G = nx.path_graph(3) # or DiGraph, MultiGraph, MultiDiGraph, etc
879
+ >>> G.has_node(0)
880
+ True
881
+
882
+ It is more readable and simpler to use
883
+
884
+ >>> 0 in G
885
+ True
886
+
887
+ """
888
+ try:
889
+ return n in self._node
890
+ except TypeError:
891
+ return False
892
+
893
+ def add_edge(self, u_of_edge, v_of_edge, **attr):
894
+ """Add an edge between u and v.
895
+
896
+ The nodes u and v will be automatically added if they are
897
+ not already in the graph.
898
+
899
+ Edge attributes can be specified with keywords or by directly
900
+ accessing the edge's attribute dictionary. See examples below.
901
+
902
+ Parameters
903
+ ----------
904
+ u_of_edge, v_of_edge : nodes
905
+ Nodes can be, for example, strings or numbers.
906
+ Nodes must be hashable (and not None) Python objects.
907
+ attr : keyword arguments, optional
908
+ Edge data (or labels or objects) can be assigned using
909
+ keyword arguments.
910
+
911
+ See Also
912
+ --------
913
+ add_edges_from : add a collection of edges
914
+
915
+ Notes
916
+ -----
917
+ Adding an edge that already exists updates the edge data.
918
+
919
+ Many NetworkX algorithms designed for weighted graphs use
920
+ an edge attribute (by default `weight`) to hold a numerical value.
921
+
922
+ Examples
923
+ --------
924
+ The following all add the edge e=(1, 2) to graph G:
925
+
926
+ >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
927
+ >>> e = (1, 2)
928
+ >>> G.add_edge(1, 2) # explicit two-node form
929
+ >>> G.add_edge(*e) # single edge as tuple of two nodes
930
+ >>> G.add_edges_from([(1, 2)]) # add edges from iterable container
931
+
932
+ Associate data to edges using keywords:
933
+
934
+ >>> G.add_edge(1, 2, weight=3)
935
+ >>> G.add_edge(1, 3, weight=7, capacity=15, length=342.7)
936
+
937
+ For non-string attribute keys, use subscript notation.
938
+
939
+ >>> G.add_edge(1, 2)
940
+ >>> G[1][2].update({0: 5})
941
+ >>> G.edges[1, 2].update({0: 5})
942
+ """
943
+ u, v = u_of_edge, v_of_edge
944
+ # add nodes
945
+ if u not in self._node:
946
+ if u is None:
947
+ raise ValueError("None cannot be a node")
948
+ self._adj[u] = self.adjlist_inner_dict_factory()
949
+ self._node[u] = self.node_attr_dict_factory()
950
+ if v not in self._node:
951
+ if v is None:
952
+ raise ValueError("None cannot be a node")
953
+ self._adj[v] = self.adjlist_inner_dict_factory()
954
+ self._node[v] = self.node_attr_dict_factory()
955
+ # add the edge
956
+ datadict = self._adj[u].get(v, self.edge_attr_dict_factory())
957
+ datadict.update(attr)
958
+ self._adj[u][v] = datadict
959
+ self._adj[v][u] = datadict
960
+
961
+ def add_edges_from(self, ebunch_to_add, **attr):
962
+ """Add all the edges in ebunch_to_add.
963
+
964
+ Parameters
965
+ ----------
966
+ ebunch_to_add : container of edges
967
+ Each edge given in the container will be added to the
968
+ graph. The edges must be given as 2-tuples (u, v) or
969
+ 3-tuples (u, v, d) where d is a dictionary containing edge data.
970
+ attr : keyword arguments, optional
971
+ Edge data (or labels or objects) can be assigned using
972
+ keyword arguments.
973
+
974
+ See Also
975
+ --------
976
+ add_edge : add a single edge
977
+ add_weighted_edges_from : convenient way to add weighted edges
978
+
979
+ Notes
980
+ -----
981
+ Adding the same edge twice has no effect but any edge data
982
+ will be updated when each duplicate edge is added.
983
+
984
+ Edge attributes specified in an ebunch take precedence over
985
+ attributes specified via keyword arguments.
986
+
987
+ When adding edges from an iterator over the graph you are changing,
988
+ a `RuntimeError` can be raised with message:
989
+ `RuntimeError: dictionary changed size during iteration`. This
990
+ happens when the graph's underlying dictionary is modified during
991
+ iteration. To avoid this error, evaluate the iterator into a separate
992
+ object, e.g. by using `list(iterator_of_edges)`, and pass this
993
+ object to `G.add_edges_from`.
994
+
995
+ Examples
996
+ --------
997
+ >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
998
+ >>> G.add_edges_from([(0, 1), (1, 2)]) # using a list of edge tuples
999
+ >>> e = zip(range(0, 3), range(1, 4))
1000
+ >>> G.add_edges_from(e) # Add the path graph 0-1-2-3
1001
+
1002
+ Associate data to edges
1003
+
1004
+ >>> G.add_edges_from([(1, 2), (2, 3)], weight=3)
1005
+ >>> G.add_edges_from([(3, 4), (1, 4)], label="WN2898")
1006
+
1007
+ Evaluate an iterator over a graph if using it to modify the same graph
1008
+
1009
+ >>> G = nx.Graph([(1, 2), (2, 3), (3, 4)])
1010
+ >>> # Grow graph by one new node, adding edges to all existing nodes.
1011
+ >>> # wrong way - will raise RuntimeError
1012
+ >>> # G.add_edges_from(((5, n) for n in G.nodes))
1013
+ >>> # correct way - note that there will be no self-edge for node 5
1014
+ >>> G.add_edges_from(list((5, n) for n in G.nodes))
1015
+ """
1016
+ for e in ebunch_to_add:
1017
+ ne = len(e)
1018
+ if ne == 3:
1019
+ u, v, dd = e
1020
+ elif ne == 2:
1021
+ u, v = e
1022
+ dd = {} # doesn't need edge_attr_dict_factory
1023
+ else:
1024
+ raise NetworkXError(f"Edge tuple {e} must be a 2-tuple or 3-tuple.")
1025
+ if u not in self._node:
1026
+ if u is None:
1027
+ raise ValueError("None cannot be a node")
1028
+ self._adj[u] = self.adjlist_inner_dict_factory()
1029
+ self._node[u] = self.node_attr_dict_factory()
1030
+ if v not in self._node:
1031
+ if v is None:
1032
+ raise ValueError("None cannot be a node")
1033
+ self._adj[v] = self.adjlist_inner_dict_factory()
1034
+ self._node[v] = self.node_attr_dict_factory()
1035
+ datadict = self._adj[u].get(v, self.edge_attr_dict_factory())
1036
+ datadict.update(attr)
1037
+ datadict.update(dd)
1038
+ self._adj[u][v] = datadict
1039
+ self._adj[v][u] = datadict
1040
+
1041
+ def add_weighted_edges_from(self, ebunch_to_add, weight="weight", **attr):
1042
+ """Add weighted edges in `ebunch_to_add` with specified weight attr
1043
+
1044
+ Parameters
1045
+ ----------
1046
+ ebunch_to_add : container of edges
1047
+ Each edge given in the list or container will be added
1048
+ to the graph. The edges must be given as 3-tuples (u, v, w)
1049
+ where w is a number.
1050
+ weight : string, optional (default= 'weight')
1051
+ The attribute name for the edge weights to be added.
1052
+ attr : keyword arguments, optional (default= no attributes)
1053
+ Edge attributes to add/update for all edges.
1054
+
1055
+ See Also
1056
+ --------
1057
+ add_edge : add a single edge
1058
+ add_edges_from : add multiple edges
1059
+
1060
+ Notes
1061
+ -----
1062
+ Adding the same edge twice for Graph/DiGraph simply updates
1063
+ the edge data. For MultiGraph/MultiDiGraph, duplicate edges
1064
+ are stored.
1065
+
1066
+ When adding edges from an iterator over the graph you are changing,
1067
+ a `RuntimeError` can be raised with message:
1068
+ `RuntimeError: dictionary changed size during iteration`. This
1069
+ happens when the graph's underlying dictionary is modified during
1070
+ iteration. To avoid this error, evaluate the iterator into a separate
1071
+ object, e.g. by using `list(iterator_of_edges)`, and pass this
1072
+ object to `G.add_weighted_edges_from`.
1073
+
1074
+ Examples
1075
+ --------
1076
+ >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
1077
+ >>> G.add_weighted_edges_from([(0, 1, 3.0), (1, 2, 7.5)])
1078
+
1079
+ Evaluate an iterator over edges before passing it
1080
+
1081
+ >>> G = nx.Graph([(1, 2), (2, 3), (3, 4)])
1082
+ >>> weight = 0.1
1083
+ >>> # Grow graph by one new node, adding edges to all existing nodes.
1084
+ >>> # wrong way - will raise RuntimeError
1085
+ >>> # G.add_weighted_edges_from(((5, n, weight) for n in G.nodes))
1086
+ >>> # correct way - note that there will be no self-edge for node 5
1087
+ >>> G.add_weighted_edges_from(list((5, n, weight) for n in G.nodes))
1088
+ """
1089
+ self.add_edges_from(((u, v, {weight: d}) for u, v, d in ebunch_to_add), **attr)
1090
+
1091
+ def remove_edge(self, u, v):
1092
+ """Remove the edge between u and v.
1093
+
1094
+ Parameters
1095
+ ----------
1096
+ u, v : nodes
1097
+ Remove the edge between nodes u and v.
1098
+
1099
+ Raises
1100
+ ------
1101
+ NetworkXError
1102
+ If there is not an edge between u and v.
1103
+
1104
+ See Also
1105
+ --------
1106
+ remove_edges_from : remove a collection of edges
1107
+
1108
+ Examples
1109
+ --------
1110
+ >>> G = nx.path_graph(4) # or DiGraph, etc
1111
+ >>> G.remove_edge(0, 1)
1112
+ >>> e = (1, 2)
1113
+ >>> G.remove_edge(*e) # unpacks e from an edge tuple
1114
+ >>> e = (2, 3, {"weight": 7}) # an edge with attribute data
1115
+ >>> G.remove_edge(*e[:2]) # select first part of edge tuple
1116
+ """
1117
+ try:
1118
+ del self._adj[u][v]
1119
+ if u != v: # self-loop needs only one entry removed
1120
+ del self._adj[v][u]
1121
+ except KeyError as err:
1122
+ raise NetworkXError(f"The edge {u}-{v} is not in the graph") from err
1123
+
1124
+ def remove_edges_from(self, ebunch):
1125
+ """Remove all edges specified in ebunch.
1126
+
1127
+ Parameters
1128
+ ----------
1129
+ ebunch: list or container of edge tuples
1130
+ Each edge given in the list or container will be removed
1131
+ from the graph. The edges can be:
1132
+
1133
+ - 2-tuples (u, v) edge between u and v.
1134
+ - 3-tuples (u, v, k) where k is ignored.
1135
+
1136
+ See Also
1137
+ --------
1138
+ remove_edge : remove a single edge
1139
+
1140
+ Notes
1141
+ -----
1142
+ Will fail silently if an edge in ebunch is not in the graph.
1143
+
1144
+ Examples
1145
+ --------
1146
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1147
+ >>> ebunch = [(1, 2), (2, 3)]
1148
+ >>> G.remove_edges_from(ebunch)
1149
+ """
1150
+ adj = self._adj
1151
+ for e in ebunch:
1152
+ u, v = e[:2] # ignore edge data if present
1153
+ if u in adj and v in adj[u]:
1154
+ del adj[u][v]
1155
+ if u != v: # self loop needs only one entry removed
1156
+ del adj[v][u]
1157
+
1158
+ def update(self, edges=None, nodes=None):
1159
+ """Update the graph using nodes/edges/graphs as input.
1160
+
1161
+ Like dict.update, this method takes a graph as input, adding the
1162
+ graph's nodes and edges to this graph. It can also take two inputs:
1163
+ edges and nodes. Finally it can take either edges or nodes.
1164
+ To specify only nodes the keyword `nodes` must be used.
1165
+
1166
+ The collections of edges and nodes are treated similarly to
1167
+ the add_edges_from/add_nodes_from methods. When iterated, they
1168
+ should yield 2-tuples (u, v) or 3-tuples (u, v, datadict).
1169
+
1170
+ Parameters
1171
+ ----------
1172
+ edges : Graph object, collection of edges, or None
1173
+ The first parameter can be a graph or some edges. If it has
1174
+ attributes `nodes` and `edges`, then it is taken to be a
1175
+ Graph-like object and those attributes are used as collections
1176
+ of nodes and edges to be added to the graph.
1177
+ If the first parameter does not have those attributes, it is
1178
+ treated as a collection of edges and added to the graph.
1179
+ If the first argument is None, no edges are added.
1180
+ nodes : collection of nodes, or None
1181
+ The second parameter is treated as a collection of nodes
1182
+ to be added to the graph unless it is None.
1183
+ If `edges is None` and `nodes is None` an exception is raised.
1184
+ If the first parameter is a Graph, then `nodes` is ignored.
1185
+
1186
+ Examples
1187
+ --------
1188
+ >>> G = nx.path_graph(5)
1189
+ >>> G.update(nx.complete_graph(range(4, 10)))
1190
+ >>> from itertools import combinations
1191
+ >>> edges = (
1192
+ ... (u, v, {"power": u * v})
1193
+ ... for u, v in combinations(range(10, 20), 2)
1194
+ ... if u * v < 225
1195
+ ... )
1196
+ >>> nodes = [1000] # for singleton, use a container
1197
+ >>> G.update(edges, nodes)
1198
+
1199
+ Notes
1200
+ -----
1201
+ It you want to update the graph using an adjacency structure
1202
+ it is straightforward to obtain the edges/nodes from adjacency.
1203
+ The following examples provide common cases, your adjacency may
1204
+ be slightly different and require tweaks of these examples::
1205
+
1206
+ >>> # dict-of-set/list/tuple
1207
+ >>> adj = {1: {2, 3}, 2: {1, 3}, 3: {1, 2}}
1208
+ >>> e = [(u, v) for u, nbrs in adj.items() for v in nbrs]
1209
+ >>> G.update(edges=e, nodes=adj)
1210
+
1211
+ >>> DG = nx.DiGraph()
1212
+ >>> # dict-of-dict-of-attribute
1213
+ >>> adj = {1: {2: 1.3, 3: 0.7}, 2: {1: 1.4}, 3: {1: 0.7}}
1214
+ >>> e = [
1215
+ ... (u, v, {"weight": d})
1216
+ ... for u, nbrs in adj.items()
1217
+ ... for v, d in nbrs.items()
1218
+ ... ]
1219
+ >>> DG.update(edges=e, nodes=adj)
1220
+
1221
+ >>> # dict-of-dict-of-dict
1222
+ >>> adj = {1: {2: {"weight": 1.3}, 3: {"color": 0.7, "weight": 1.2}}}
1223
+ >>> e = [
1224
+ ... (u, v, {"weight": d})
1225
+ ... for u, nbrs in adj.items()
1226
+ ... for v, d in nbrs.items()
1227
+ ... ]
1228
+ >>> DG.update(edges=e, nodes=adj)
1229
+
1230
+ >>> # predecessor adjacency (dict-of-set)
1231
+ >>> pred = {1: {2, 3}, 2: {3}, 3: {3}}
1232
+ >>> e = [(v, u) for u, nbrs in pred.items() for v in nbrs]
1233
+
1234
+ >>> # MultiGraph dict-of-dict-of-dict-of-attribute
1235
+ >>> MDG = nx.MultiDiGraph()
1236
+ >>> adj = {
1237
+ ... 1: {2: {0: {"weight": 1.3}, 1: {"weight": 1.2}}},
1238
+ ... 3: {2: {0: {"weight": 0.7}}},
1239
+ ... }
1240
+ >>> e = [
1241
+ ... (u, v, ekey, d)
1242
+ ... for u, nbrs in adj.items()
1243
+ ... for v, keydict in nbrs.items()
1244
+ ... for ekey, d in keydict.items()
1245
+ ... ]
1246
+ >>> MDG.update(edges=e)
1247
+
1248
+ See Also
1249
+ --------
1250
+ add_edges_from: add multiple edges to a graph
1251
+ add_nodes_from: add multiple nodes to a graph
1252
+ """
1253
+ if edges is not None:
1254
+ if nodes is not None:
1255
+ self.add_nodes_from(nodes)
1256
+ self.add_edges_from(edges)
1257
+ else:
1258
+ # check if edges is a Graph object
1259
+ try:
1260
+ graph_nodes = edges.nodes
1261
+ graph_edges = edges.edges
1262
+ except AttributeError:
1263
+ # edge not Graph-like
1264
+ self.add_edges_from(edges)
1265
+ else: # edges is Graph-like
1266
+ self.add_nodes_from(graph_nodes.data())
1267
+ self.add_edges_from(graph_edges.data())
1268
+ self.graph.update(edges.graph)
1269
+ elif nodes is not None:
1270
+ self.add_nodes_from(nodes)
1271
+ else:
1272
+ raise NetworkXError("update needs nodes or edges input")
1273
+
1274
+ def has_edge(self, u, v):
1275
+ """Returns True if the edge (u, v) is in the graph.
1276
+
1277
+ This is the same as `v in G[u]` without KeyError exceptions.
1278
+
1279
+ Parameters
1280
+ ----------
1281
+ u, v : nodes
1282
+ Nodes can be, for example, strings or numbers.
1283
+ Nodes must be hashable (and not None) Python objects.
1284
+
1285
+ Returns
1286
+ -------
1287
+ edge_ind : bool
1288
+ True if edge is in the graph, False otherwise.
1289
+
1290
+ Examples
1291
+ --------
1292
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1293
+ >>> G.has_edge(0, 1) # using two nodes
1294
+ True
1295
+ >>> e = (0, 1)
1296
+ >>> G.has_edge(*e) # e is a 2-tuple (u, v)
1297
+ True
1298
+ >>> e = (0, 1, {"weight": 7})
1299
+ >>> G.has_edge(*e[:2]) # e is a 3-tuple (u, v, data_dictionary)
1300
+ True
1301
+
1302
+ The following syntax are equivalent:
1303
+
1304
+ >>> G.has_edge(0, 1)
1305
+ True
1306
+ >>> 1 in G[0] # though this gives KeyError if 0 not in G
1307
+ True
1308
+
1309
+ """
1310
+ try:
1311
+ return v in self._adj[u]
1312
+ except KeyError:
1313
+ return False
1314
+
1315
+ def neighbors(self, n):
1316
+ """Returns an iterator over all neighbors of node n.
1317
+
1318
+ This is identical to `iter(G[n])`
1319
+
1320
+ Parameters
1321
+ ----------
1322
+ n : node
1323
+ A node in the graph
1324
+
1325
+ Returns
1326
+ -------
1327
+ neighbors : iterator
1328
+ An iterator over all neighbors of node n
1329
+
1330
+ Raises
1331
+ ------
1332
+ NetworkXError
1333
+ If the node n is not in the graph.
1334
+
1335
+ Examples
1336
+ --------
1337
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1338
+ >>> [n for n in G.neighbors(0)]
1339
+ [1]
1340
+
1341
+ Notes
1342
+ -----
1343
+ Alternate ways to access the neighbors are ``G.adj[n]`` or ``G[n]``:
1344
+
1345
+ >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
1346
+ >>> G.add_edge("a", "b", weight=7)
1347
+ >>> G["a"]
1348
+ AtlasView({'b': {'weight': 7}})
1349
+ >>> G = nx.path_graph(4)
1350
+ >>> [n for n in G[0]]
1351
+ [1]
1352
+ """
1353
+ try:
1354
+ return iter(self._adj[n])
1355
+ except KeyError as err:
1356
+ raise NetworkXError(f"The node {n} is not in the graph.") from err
1357
+
1358
+ @cached_property
1359
+ def edges(self):
1360
+ """An EdgeView of the Graph as G.edges or G.edges().
1361
+
1362
+ edges(self, nbunch=None, data=False, default=None)
1363
+
1364
+ The EdgeView provides set-like operations on the edge-tuples
1365
+ as well as edge attribute lookup. When called, it also provides
1366
+ an EdgeDataView object which allows control of access to edge
1367
+ attributes (but does not provide set-like operations).
1368
+ Hence, `G.edges[u, v]['color']` provides the value of the color
1369
+ attribute for edge `(u, v)` while
1370
+ `for (u, v, c) in G.edges.data('color', default='red'):`
1371
+ iterates through all the edges yielding the color attribute
1372
+ with default `'red'` if no color attribute exists.
1373
+
1374
+ Parameters
1375
+ ----------
1376
+ nbunch : single node, container, or all nodes (default= all nodes)
1377
+ The view will only report edges from these nodes.
1378
+ data : string or bool, optional (default=False)
1379
+ The edge attribute returned in 3-tuple (u, v, ddict[data]).
1380
+ If True, return edge attribute dict in 3-tuple (u, v, ddict).
1381
+ If False, return 2-tuple (u, v).
1382
+ default : value, optional (default=None)
1383
+ Value used for edges that don't have the requested attribute.
1384
+ Only relevant if data is not True or False.
1385
+
1386
+ Returns
1387
+ -------
1388
+ edges : EdgeView
1389
+ A view of edge attributes, usually it iterates over (u, v)
1390
+ or (u, v, d) tuples of edges, but can also be used for
1391
+ attribute lookup as `edges[u, v]['foo']`.
1392
+
1393
+ Notes
1394
+ -----
1395
+ Nodes in nbunch that are not in the graph will be (quietly) ignored.
1396
+ For directed graphs this returns the out-edges.
1397
+
1398
+ Examples
1399
+ --------
1400
+ >>> G = nx.path_graph(3) # or MultiGraph, etc
1401
+ >>> G.add_edge(2, 3, weight=5)
1402
+ >>> [e for e in G.edges]
1403
+ [(0, 1), (1, 2), (2, 3)]
1404
+ >>> G.edges.data() # default data is {} (empty dict)
1405
+ EdgeDataView([(0, 1, {}), (1, 2, {}), (2, 3, {'weight': 5})])
1406
+ >>> G.edges.data("weight", default=1)
1407
+ EdgeDataView([(0, 1, 1), (1, 2, 1), (2, 3, 5)])
1408
+ >>> G.edges([0, 3]) # only edges from these nodes
1409
+ EdgeDataView([(0, 1), (3, 2)])
1410
+ >>> G.edges(0) # only edges from node 0
1411
+ EdgeDataView([(0, 1)])
1412
+ """
1413
+ return EdgeView(self)
1414
+
1415
+ def get_edge_data(self, u, v, default=None):
1416
+ """Returns the attribute dictionary associated with edge (u, v).
1417
+
1418
+ This is identical to `G[u][v]` except the default is returned
1419
+ instead of an exception if the edge doesn't exist.
1420
+
1421
+ Parameters
1422
+ ----------
1423
+ u, v : nodes
1424
+ default: any Python object (default=None)
1425
+ Value to return if the edge (u, v) is not found.
1426
+
1427
+ Returns
1428
+ -------
1429
+ edge_dict : dictionary
1430
+ The edge attribute dictionary.
1431
+
1432
+ Examples
1433
+ --------
1434
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1435
+ >>> G[0][1]
1436
+ {}
1437
+
1438
+ Warning: Assigning to `G[u][v]` is not permitted.
1439
+ But it is safe to assign attributes `G[u][v]['foo']`
1440
+
1441
+ >>> G[0][1]["weight"] = 7
1442
+ >>> G[0][1]["weight"]
1443
+ 7
1444
+ >>> G[1][0]["weight"]
1445
+ 7
1446
+
1447
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1448
+ >>> G.get_edge_data(0, 1) # default edge data is {}
1449
+ {}
1450
+ >>> e = (0, 1)
1451
+ >>> G.get_edge_data(*e) # tuple form
1452
+ {}
1453
+ >>> G.get_edge_data("a", "b", default=0) # edge not in graph, return 0
1454
+ 0
1455
+ """
1456
+ try:
1457
+ return self._adj[u][v]
1458
+ except KeyError:
1459
+ return default
1460
+
1461
+ def adjacency(self):
1462
+ """Returns an iterator over (node, adjacency dict) tuples for all nodes.
1463
+
1464
+ For directed graphs, only outgoing neighbors/adjacencies are included.
1465
+
1466
+ Returns
1467
+ -------
1468
+ adj_iter : iterator
1469
+ An iterator over (node, adjacency dictionary) for all nodes in
1470
+ the graph.
1471
+
1472
+ Examples
1473
+ --------
1474
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1475
+ >>> [(n, nbrdict) for n, nbrdict in G.adjacency()]
1476
+ [(0, {1: {}}), (1, {0: {}, 2: {}}), (2, {1: {}, 3: {}}), (3, {2: {}})]
1477
+
1478
+ """
1479
+ return iter(self._adj.items())
1480
+
1481
+ @cached_property
1482
+ def degree(self):
1483
+ """A DegreeView for the Graph as G.degree or G.degree().
1484
+
1485
+ The node degree is the number of edges adjacent to the node.
1486
+ The weighted node degree is the sum of the edge weights for
1487
+ edges incident to that node.
1488
+
1489
+ This object provides an iterator for (node, degree) as well as
1490
+ lookup for the degree for a single node.
1491
+
1492
+ Parameters
1493
+ ----------
1494
+ nbunch : single node, container, or all nodes (default= all nodes)
1495
+ The view will only report edges incident to these nodes.
1496
+
1497
+ weight : string or None, optional (default=None)
1498
+ The name of an edge attribute that holds the numerical value used
1499
+ as a weight. If None, then each edge has weight 1.
1500
+ The degree is the sum of the edge weights adjacent to the node.
1501
+
1502
+ Returns
1503
+ -------
1504
+ DegreeView or int
1505
+ If multiple nodes are requested (the default), returns a `DegreeView`
1506
+ mapping nodes to their degree.
1507
+ If a single node is requested, returns the degree of the node as an integer.
1508
+
1509
+ Examples
1510
+ --------
1511
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1512
+ >>> G.degree[0] # node 0 has degree 1
1513
+ 1
1514
+ >>> list(G.degree([0, 1, 2]))
1515
+ [(0, 1), (1, 2), (2, 2)]
1516
+ """
1517
+ return DegreeView(self)
1518
+
1519
+ def clear(self):
1520
+ """Remove all nodes and edges from the graph.
1521
+
1522
+ This also removes the name, and all graph, node, and edge attributes.
1523
+
1524
+ Examples
1525
+ --------
1526
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1527
+ >>> G.clear()
1528
+ >>> list(G.nodes)
1529
+ []
1530
+ >>> list(G.edges)
1531
+ []
1532
+
1533
+ """
1534
+ self._adj.clear()
1535
+ self._node.clear()
1536
+ self.graph.clear()
1537
+
1538
+ def clear_edges(self):
1539
+ """Remove all edges from the graph without altering nodes.
1540
+
1541
+ Examples
1542
+ --------
1543
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1544
+ >>> G.clear_edges()
1545
+ >>> list(G.nodes)
1546
+ [0, 1, 2, 3]
1547
+ >>> list(G.edges)
1548
+ []
1549
+ """
1550
+ for neighbours_dict in self._adj.values():
1551
+ neighbours_dict.clear()
1552
+
1553
+ def is_multigraph(self):
1554
+ """Returns True if graph is a multigraph, False otherwise."""
1555
+ return False
1556
+
1557
+ def is_directed(self):
1558
+ """Returns True if graph is directed, False otherwise."""
1559
+ return False
1560
+
1561
+ def copy(self, as_view=False):
1562
+ """Returns a copy of the graph.
1563
+
1564
+ The copy method by default returns an independent shallow copy
1565
+ of the graph and attributes. That is, if an attribute is a
1566
+ container, that container is shared by the original an the copy.
1567
+ Use Python's `copy.deepcopy` for new containers.
1568
+
1569
+ If `as_view` is True then a view is returned instead of a copy.
1570
+
1571
+ Notes
1572
+ -----
1573
+ All copies reproduce the graph structure, but data attributes
1574
+ may be handled in different ways. There are four types of copies
1575
+ of a graph that people might want.
1576
+
1577
+ Deepcopy -- A "deepcopy" copies the graph structure as well as
1578
+ all data attributes and any objects they might contain.
1579
+ The entire graph object is new so that changes in the copy
1580
+ do not affect the original object. (see Python's copy.deepcopy)
1581
+
1582
+ Data Reference (Shallow) -- For a shallow copy the graph structure
1583
+ is copied but the edge, node and graph attribute dicts are
1584
+ references to those in the original graph. This saves
1585
+ time and memory but could cause confusion if you change an attribute
1586
+ in one graph and it changes the attribute in the other.
1587
+ NetworkX does not provide this level of shallow copy.
1588
+
1589
+ Independent Shallow -- This copy creates new independent attribute
1590
+ dicts and then does a shallow copy of the attributes. That is, any
1591
+ attributes that are containers are shared between the new graph
1592
+ and the original. This is exactly what `dict.copy()` provides.
1593
+ You can obtain this style copy using:
1594
+
1595
+ >>> G = nx.path_graph(5)
1596
+ >>> H = G.copy()
1597
+ >>> H = G.copy(as_view=False)
1598
+ >>> H = nx.Graph(G)
1599
+ >>> H = G.__class__(G)
1600
+
1601
+ Fresh Data -- For fresh data, the graph structure is copied while
1602
+ new empty data attribute dicts are created. The resulting graph
1603
+ is independent of the original and it has no edge, node or graph
1604
+ attributes. Fresh copies are not enabled. Instead use:
1605
+
1606
+ >>> H = G.__class__()
1607
+ >>> H.add_nodes_from(G)
1608
+ >>> H.add_edges_from(G.edges)
1609
+
1610
+ View -- Inspired by dict-views, graph-views act like read-only
1611
+ versions of the original graph, providing a copy of the original
1612
+ structure without requiring any memory for copying the information.
1613
+
1614
+ See the Python copy module for more information on shallow
1615
+ and deep copies, https://docs.python.org/3/library/copy.html.
1616
+
1617
+ Parameters
1618
+ ----------
1619
+ as_view : bool, optional (default=False)
1620
+ If True, the returned graph-view provides a read-only view
1621
+ of the original graph without actually copying any data.
1622
+
1623
+ Returns
1624
+ -------
1625
+ G : Graph
1626
+ A copy of the graph.
1627
+
1628
+ See Also
1629
+ --------
1630
+ to_directed: return a directed copy of the graph.
1631
+
1632
+ Examples
1633
+ --------
1634
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1635
+ >>> H = G.copy()
1636
+
1637
+ """
1638
+ if as_view is True:
1639
+ return nx.graphviews.generic_graph_view(self)
1640
+ G = self.__class__()
1641
+ G.graph.update(self.graph)
1642
+ G.add_nodes_from((n, d.copy()) for n, d in self._node.items())
1643
+ G.add_edges_from(
1644
+ (u, v, datadict.copy())
1645
+ for u, nbrs in self._adj.items()
1646
+ for v, datadict in nbrs.items()
1647
+ )
1648
+ return G
1649
+
1650
+ def to_directed(self, as_view=False):
1651
+ """Returns a directed representation of the graph.
1652
+
1653
+ Returns
1654
+ -------
1655
+ G : DiGraph
1656
+ A directed graph with the same name, same nodes, and with
1657
+ each edge (u, v, data) replaced by two directed edges
1658
+ (u, v, data) and (v, u, data).
1659
+
1660
+ Notes
1661
+ -----
1662
+ This returns a "deepcopy" of the edge, node, and
1663
+ graph attributes which attempts to completely copy
1664
+ all of the data and references.
1665
+
1666
+ This is in contrast to the similar D=DiGraph(G) which returns a
1667
+ shallow copy of the data.
1668
+
1669
+ See the Python copy module for more information on shallow
1670
+ and deep copies, https://docs.python.org/3/library/copy.html.
1671
+
1672
+ Warning: If you have subclassed Graph to use dict-like objects
1673
+ in the data structure, those changes do not transfer to the
1674
+ DiGraph created by this method.
1675
+
1676
+ Examples
1677
+ --------
1678
+ >>> G = nx.Graph() # or MultiGraph, etc
1679
+ >>> G.add_edge(0, 1)
1680
+ >>> H = G.to_directed()
1681
+ >>> list(H.edges)
1682
+ [(0, 1), (1, 0)]
1683
+
1684
+ If already directed, return a (deep) copy
1685
+
1686
+ >>> G = nx.DiGraph() # or MultiDiGraph, etc
1687
+ >>> G.add_edge(0, 1)
1688
+ >>> H = G.to_directed()
1689
+ >>> list(H.edges)
1690
+ [(0, 1)]
1691
+ """
1692
+ graph_class = self.to_directed_class()
1693
+ if as_view is True:
1694
+ return nx.graphviews.generic_graph_view(self, graph_class)
1695
+ # deepcopy when not a view
1696
+ G = graph_class()
1697
+ G.graph.update(deepcopy(self.graph))
1698
+ G.add_nodes_from((n, deepcopy(d)) for n, d in self._node.items())
1699
+ G.add_edges_from(
1700
+ (u, v, deepcopy(data))
1701
+ for u, nbrs in self._adj.items()
1702
+ for v, data in nbrs.items()
1703
+ )
1704
+ return G
1705
+
1706
+ def to_undirected(self, as_view=False):
1707
+ """Returns an undirected copy of the graph.
1708
+
1709
+ Parameters
1710
+ ----------
1711
+ as_view : bool (optional, default=False)
1712
+ If True return a view of the original undirected graph.
1713
+
1714
+ Returns
1715
+ -------
1716
+ G : Graph/MultiGraph
1717
+ A deepcopy of the graph.
1718
+
1719
+ See Also
1720
+ --------
1721
+ Graph, copy, add_edge, add_edges_from
1722
+
1723
+ Notes
1724
+ -----
1725
+ This returns a "deepcopy" of the edge, node, and
1726
+ graph attributes which attempts to completely copy
1727
+ all of the data and references.
1728
+
1729
+ This is in contrast to the similar `G = nx.DiGraph(D)` which returns a
1730
+ shallow copy of the data.
1731
+
1732
+ See the Python copy module for more information on shallow
1733
+ and deep copies, https://docs.python.org/3/library/copy.html.
1734
+
1735
+ Warning: If you have subclassed DiGraph to use dict-like objects
1736
+ in the data structure, those changes do not transfer to the
1737
+ Graph created by this method.
1738
+
1739
+ Examples
1740
+ --------
1741
+ >>> G = nx.path_graph(2) # or MultiGraph, etc
1742
+ >>> H = G.to_directed()
1743
+ >>> list(H.edges)
1744
+ [(0, 1), (1, 0)]
1745
+ >>> G2 = H.to_undirected()
1746
+ >>> list(G2.edges)
1747
+ [(0, 1)]
1748
+ """
1749
+ graph_class = self.to_undirected_class()
1750
+ if as_view is True:
1751
+ return nx.graphviews.generic_graph_view(self, graph_class)
1752
+ # deepcopy when not a view
1753
+ G = graph_class()
1754
+ G.graph.update(deepcopy(self.graph))
1755
+ G.add_nodes_from((n, deepcopy(d)) for n, d in self._node.items())
1756
+ G.add_edges_from(
1757
+ (u, v, deepcopy(d))
1758
+ for u, nbrs in self._adj.items()
1759
+ for v, d in nbrs.items()
1760
+ )
1761
+ return G
1762
+
1763
+ def subgraph(self, nodes):
1764
+ """Returns a SubGraph view of the subgraph induced on `nodes`.
1765
+
1766
+ The induced subgraph of the graph contains the nodes in `nodes`
1767
+ and the edges between those nodes.
1768
+
1769
+ Parameters
1770
+ ----------
1771
+ nodes : list, iterable
1772
+ A container of nodes which will be iterated through once.
1773
+
1774
+ Returns
1775
+ -------
1776
+ G : SubGraph View
1777
+ A subgraph view of the graph. The graph structure cannot be
1778
+ changed but node/edge attributes can and are shared with the
1779
+ original graph.
1780
+
1781
+ Notes
1782
+ -----
1783
+ The graph, edge and node attributes are shared with the original graph.
1784
+ Changes to the graph structure is ruled out by the view, but changes
1785
+ to attributes are reflected in the original graph.
1786
+
1787
+ To create a subgraph with its own copy of the edge/node attributes use:
1788
+ G.subgraph(nodes).copy()
1789
+
1790
+ For an inplace reduction of a graph to a subgraph you can remove nodes:
1791
+ G.remove_nodes_from([n for n in G if n not in set(nodes)])
1792
+
1793
+ Subgraph views are sometimes NOT what you want. In most cases where
1794
+ you want to do more than simply look at the induced edges, it makes
1795
+ more sense to just create the subgraph as its own graph with code like:
1796
+
1797
+ ::
1798
+
1799
+ # Create a subgraph SG based on a (possibly multigraph) G
1800
+ SG = G.__class__()
1801
+ SG.add_nodes_from((n, G.nodes[n]) for n in largest_wcc)
1802
+ if SG.is_multigraph():
1803
+ SG.add_edges_from((n, nbr, key, d)
1804
+ for n, nbrs in G.adj.items() if n in largest_wcc
1805
+ for nbr, keydict in nbrs.items() if nbr in largest_wcc
1806
+ for key, d in keydict.items())
1807
+ else:
1808
+ SG.add_edges_from((n, nbr, d)
1809
+ for n, nbrs in G.adj.items() if n in largest_wcc
1810
+ for nbr, d in nbrs.items() if nbr in largest_wcc)
1811
+ SG.graph.update(G.graph)
1812
+
1813
+ Examples
1814
+ --------
1815
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1816
+ >>> H = G.subgraph([0, 1, 2])
1817
+ >>> list(H.edges)
1818
+ [(0, 1), (1, 2)]
1819
+ """
1820
+ induced_nodes = nx.filters.show_nodes(self.nbunch_iter(nodes))
1821
+ # if already a subgraph, don't make a chain
1822
+ subgraph = nx.subgraph_view
1823
+ if hasattr(self, "_NODE_OK"):
1824
+ return subgraph(
1825
+ self._graph, filter_node=induced_nodes, filter_edge=self._EDGE_OK
1826
+ )
1827
+ return subgraph(self, filter_node=induced_nodes)
1828
+
1829
+ def edge_subgraph(self, edges):
1830
+ """Returns the subgraph induced by the specified edges.
1831
+
1832
+ The induced subgraph contains each edge in `edges` and each
1833
+ node incident to any one of those edges.
1834
+
1835
+ Parameters
1836
+ ----------
1837
+ edges : iterable
1838
+ An iterable of edges in this graph.
1839
+
1840
+ Returns
1841
+ -------
1842
+ G : Graph
1843
+ An edge-induced subgraph of this graph with the same edge
1844
+ attributes.
1845
+
1846
+ Notes
1847
+ -----
1848
+ The graph, edge, and node attributes in the returned subgraph
1849
+ view are references to the corresponding attributes in the original
1850
+ graph. The view is read-only.
1851
+
1852
+ To create a full graph version of the subgraph with its own copy
1853
+ of the edge or node attributes, use::
1854
+
1855
+ G.edge_subgraph(edges).copy()
1856
+
1857
+ Examples
1858
+ --------
1859
+ >>> G = nx.path_graph(5)
1860
+ >>> H = G.edge_subgraph([(0, 1), (3, 4)])
1861
+ >>> list(H.nodes)
1862
+ [0, 1, 3, 4]
1863
+ >>> list(H.edges)
1864
+ [(0, 1), (3, 4)]
1865
+
1866
+ """
1867
+ return nx.edge_subgraph(self, edges)
1868
+
1869
+ def size(self, weight=None):
1870
+ """Returns the number of edges or total of all edge weights.
1871
+
1872
+ Parameters
1873
+ ----------
1874
+ weight : string or None, optional (default=None)
1875
+ The edge attribute that holds the numerical value used
1876
+ as a weight. If None, then each edge has weight 1.
1877
+
1878
+ Returns
1879
+ -------
1880
+ size : numeric
1881
+ The number of edges or
1882
+ (if weight keyword is provided) the total weight sum.
1883
+
1884
+ If weight is None, returns an int. Otherwise a float
1885
+ (or more general numeric if the weights are more general).
1886
+
1887
+ See Also
1888
+ --------
1889
+ number_of_edges
1890
+
1891
+ Examples
1892
+ --------
1893
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1894
+ >>> G.size()
1895
+ 3
1896
+
1897
+ >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
1898
+ >>> G.add_edge("a", "b", weight=2)
1899
+ >>> G.add_edge("b", "c", weight=4)
1900
+ >>> G.size()
1901
+ 2
1902
+ >>> G.size(weight="weight")
1903
+ 6.0
1904
+ """
1905
+ s = sum(d for v, d in self.degree(weight=weight))
1906
+ # If `weight` is None, the sum of the degrees is guaranteed to be
1907
+ # even, so we can perform integer division and hence return an
1908
+ # integer. Otherwise, the sum of the weighted degrees is not
1909
+ # guaranteed to be an integer, so we perform "real" division.
1910
+ return s // 2 if weight is None else s / 2
1911
+
1912
+ def number_of_edges(self, u=None, v=None):
1913
+ """Returns the number of edges between two nodes.
1914
+
1915
+ Parameters
1916
+ ----------
1917
+ u, v : nodes, optional (default=all edges)
1918
+ If u and v are specified, return the number of edges between
1919
+ u and v. Otherwise return the total number of all edges.
1920
+
1921
+ Returns
1922
+ -------
1923
+ nedges : int
1924
+ The number of edges in the graph. If nodes `u` and `v` are
1925
+ specified return the number of edges between those nodes. If
1926
+ the graph is directed, this only returns the number of edges
1927
+ from `u` to `v`.
1928
+
1929
+ See Also
1930
+ --------
1931
+ size
1932
+
1933
+ Examples
1934
+ --------
1935
+ For undirected graphs, this method counts the total number of
1936
+ edges in the graph:
1937
+
1938
+ >>> G = nx.path_graph(4)
1939
+ >>> G.number_of_edges()
1940
+ 3
1941
+
1942
+ If you specify two nodes, this counts the total number of edges
1943
+ joining the two nodes:
1944
+
1945
+ >>> G.number_of_edges(0, 1)
1946
+ 1
1947
+
1948
+ For directed graphs, this method can count the total number of
1949
+ directed edges from `u` to `v`:
1950
+
1951
+ >>> G = nx.DiGraph()
1952
+ >>> G.add_edge(0, 1)
1953
+ >>> G.add_edge(1, 0)
1954
+ >>> G.number_of_edges(0, 1)
1955
+ 1
1956
+
1957
+ """
1958
+ if u is None:
1959
+ return int(self.size())
1960
+ if v in self._adj[u]:
1961
+ return 1
1962
+ return 0
1963
+
1964
+ def nbunch_iter(self, nbunch=None):
1965
+ """Returns an iterator over nodes contained in nbunch that are
1966
+ also in the graph.
1967
+
1968
+ The nodes in nbunch are checked for membership in the graph
1969
+ and if not are silently ignored.
1970
+
1971
+ Parameters
1972
+ ----------
1973
+ nbunch : single node, container, or all nodes (default= all nodes)
1974
+ The view will only report edges incident to these nodes.
1975
+
1976
+ Returns
1977
+ -------
1978
+ niter : iterator
1979
+ An iterator over nodes in nbunch that are also in the graph.
1980
+ If nbunch is None, iterate over all nodes in the graph.
1981
+
1982
+ Raises
1983
+ ------
1984
+ NetworkXError
1985
+ If nbunch is not a node or sequence of nodes.
1986
+ If a node in nbunch is not hashable.
1987
+
1988
+ See Also
1989
+ --------
1990
+ Graph.__iter__
1991
+
1992
+ Notes
1993
+ -----
1994
+ When nbunch is an iterator, the returned iterator yields values
1995
+ directly from nbunch, becoming exhausted when nbunch is exhausted.
1996
+
1997
+ To test whether nbunch is a single node, one can use
1998
+ "if nbunch in self:", even after processing with this routine.
1999
+
2000
+ If nbunch is not a node or a (possibly empty) sequence/iterator
2001
+ or None, a :exc:`NetworkXError` is raised. Also, if any object in
2002
+ nbunch is not hashable, a :exc:`NetworkXError` is raised.
2003
+ """
2004
+ if nbunch is None: # include all nodes via iterator
2005
+ bunch = iter(self._adj)
2006
+ elif nbunch in self: # if nbunch is a single node
2007
+ bunch = iter([nbunch])
2008
+ else: # if nbunch is a sequence of nodes
2009
+
2010
+ def bunch_iter(nlist, adj):
2011
+ try:
2012
+ for n in nlist:
2013
+ if n in adj:
2014
+ yield n
2015
+ except TypeError as err:
2016
+ exc, message = err, err.args[0]
2017
+ # capture error for non-sequence/iterator nbunch.
2018
+ if "iter" in message:
2019
+ exc = NetworkXError(
2020
+ "nbunch is not a node or a sequence of nodes."
2021
+ )
2022
+ # capture error for unhashable node.
2023
+ if "hashable" in message:
2024
+ exc = NetworkXError(
2025
+ f"Node {n} in sequence nbunch is not a valid node."
2026
+ )
2027
+ raise exc
2028
+
2029
+ bunch = bunch_iter(nbunch, self._adj)
2030
+ return bunch
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/graphviews.py ADDED
@@ -0,0 +1,267 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """View of Graphs as SubGraph, Reverse, Directed, Undirected.
2
+
3
+ In some algorithms it is convenient to temporarily morph
4
+ a graph to exclude some nodes or edges. It should be better
5
+ to do that via a view than to remove and then re-add.
6
+ In other algorithms it is convenient to temporarily morph
7
+ a graph to reverse directed edges, or treat a directed graph
8
+ as undirected, etc. This module provides those graph views.
9
+
10
+ The resulting views are essentially read-only graphs that
11
+ report data from the original graph object. We provide an
12
+ attribute G._graph which points to the underlying graph object.
13
+
14
+ Note: Since graphviews look like graphs, one can end up with
15
+ view-of-view-of-view chains. Be careful with chains because
16
+ they become very slow with about 15 nested views.
17
+ For the common simple case of node induced subgraphs created
18
+ from the graph class, we short-cut the chain by returning a
19
+ subgraph of the original graph directly rather than a subgraph
20
+ of a subgraph. We are careful not to disrupt any edge filter in
21
+ the middle subgraph. In general, determining how to short-cut
22
+ the chain is tricky and much harder with restricted_views than
23
+ with induced subgraphs.
24
+ Often it is easiest to use .copy() to avoid chains.
25
+ """
26
+ import networkx as nx
27
+ from networkx.classes.coreviews import (
28
+ FilterAdjacency,
29
+ FilterAtlas,
30
+ FilterMultiAdjacency,
31
+ UnionAdjacency,
32
+ UnionMultiAdjacency,
33
+ )
34
+ from networkx.classes.filters import no_filter
35
+ from networkx.exception import NetworkXError
36
+ from networkx.utils import deprecate_positional_args, not_implemented_for
37
+
38
+ __all__ = ["generic_graph_view", "subgraph_view", "reverse_view"]
39
+
40
+
41
+ def generic_graph_view(G, create_using=None):
42
+ """Returns a read-only view of `G`.
43
+
44
+ The graph `G` and its attributes are not copied but viewed through the new graph object
45
+ of the same class as `G` (or of the class specified in `create_using`).
46
+
47
+ Parameters
48
+ ----------
49
+ G : graph
50
+ A directed/undirected graph/multigraph.
51
+
52
+ create_using : NetworkX graph constructor, optional (default=None)
53
+ Graph type to create. If graph instance, then cleared before populated.
54
+ If `None`, then the appropriate Graph type is inferred from `G`.
55
+
56
+ Returns
57
+ -------
58
+ newG : graph
59
+ A view of the input graph `G` and its attributes as viewed through
60
+ the `create_using` class.
61
+
62
+ Raises
63
+ ------
64
+ NetworkXError
65
+ If `G` is a multigraph (or multidigraph) but `create_using` is not, or vice versa.
66
+
67
+ Notes
68
+ -----
69
+ The returned graph view is read-only (cannot modify the graph).
70
+ Yet the view reflects any changes in `G`. The intent is to mimic dict views.
71
+
72
+ Examples
73
+ --------
74
+ >>> G = nx.Graph()
75
+ >>> G.add_edge(1, 2, weight=0.3)
76
+ >>> G.add_edge(2, 3, weight=0.5)
77
+ >>> G.edges(data=True)
78
+ EdgeDataView([(1, 2, {'weight': 0.3}), (2, 3, {'weight': 0.5})])
79
+
80
+ The view exposes the attributes from the original graph.
81
+
82
+ >>> viewG = nx.graphviews.generic_graph_view(G)
83
+ >>> viewG.edges(data=True)
84
+ EdgeDataView([(1, 2, {'weight': 0.3}), (2, 3, {'weight': 0.5})])
85
+
86
+ Changes to `G` are reflected in `viewG`.
87
+
88
+ >>> G.remove_edge(2, 3)
89
+ >>> G.edges(data=True)
90
+ EdgeDataView([(1, 2, {'weight': 0.3})])
91
+
92
+ >>> viewG.edges(data=True)
93
+ EdgeDataView([(1, 2, {'weight': 0.3})])
94
+
95
+ We can change the graph type with the `create_using` parameter.
96
+
97
+ >>> type(G)
98
+ <class 'networkx.classes.graph.Graph'>
99
+ >>> viewDG = nx.graphviews.generic_graph_view(G, create_using=nx.DiGraph)
100
+ >>> type(viewDG)
101
+ <class 'networkx.classes.digraph.DiGraph'>
102
+ """
103
+ if create_using is None:
104
+ newG = G.__class__()
105
+ else:
106
+ newG = nx.empty_graph(0, create_using)
107
+ if G.is_multigraph() != newG.is_multigraph():
108
+ raise NetworkXError("Multigraph for G must agree with create_using")
109
+ newG = nx.freeze(newG)
110
+
111
+ # create view by assigning attributes from G
112
+ newG._graph = G
113
+ newG.graph = G.graph
114
+
115
+ newG._node = G._node
116
+ if newG.is_directed():
117
+ if G.is_directed():
118
+ newG._succ = G._succ
119
+ newG._pred = G._pred
120
+ # newG._adj is synced with _succ
121
+ else:
122
+ newG._succ = G._adj
123
+ newG._pred = G._adj
124
+ # newG._adj is synced with _succ
125
+ elif G.is_directed():
126
+ if G.is_multigraph():
127
+ newG._adj = UnionMultiAdjacency(G._succ, G._pred)
128
+ else:
129
+ newG._adj = UnionAdjacency(G._succ, G._pred)
130
+ else:
131
+ newG._adj = G._adj
132
+ return newG
133
+
134
+
135
+ @deprecate_positional_args(version="3.4")
136
+ def subgraph_view(G, *, filter_node=no_filter, filter_edge=no_filter):
137
+ """View of `G` applying a filter on nodes and edges.
138
+
139
+ `subgraph_view` provides a read-only view of the input graph that excludes
140
+ nodes and edges based on the outcome of two filter functions `filter_node`
141
+ and `filter_edge`.
142
+
143
+ The `filter_node` function takes one argument --- the node --- and returns
144
+ `True` if the node should be included in the subgraph, and `False` if it
145
+ should not be included.
146
+
147
+ The `filter_edge` function takes two (or three arguments if `G` is a
148
+ multi-graph) --- the nodes describing an edge, plus the edge-key if
149
+ parallel edges are possible --- and returns `True` if the edge should be
150
+ included in the subgraph, and `False` if it should not be included.
151
+
152
+ Both node and edge filter functions are called on graph elements as they
153
+ are queried, meaning there is no up-front cost to creating the view.
154
+
155
+ Parameters
156
+ ----------
157
+ G : networkx.Graph
158
+ A directed/undirected graph/multigraph
159
+
160
+ filter_node : callable, optional
161
+ A function taking a node as input, which returns `True` if the node
162
+ should appear in the view.
163
+
164
+ filter_edge : callable, optional
165
+ A function taking as input the two nodes describing an edge (plus the
166
+ edge-key if `G` is a multi-graph), which returns `True` if the edge
167
+ should appear in the view.
168
+
169
+ Returns
170
+ -------
171
+ graph : networkx.Graph
172
+ A read-only graph view of the input graph.
173
+
174
+ Examples
175
+ --------
176
+ >>> G = nx.path_graph(6)
177
+
178
+ Filter functions operate on the node, and return `True` if the node should
179
+ appear in the view:
180
+
181
+ >>> def filter_node(n1):
182
+ ... return n1 != 5
183
+ ...
184
+ >>> view = nx.subgraph_view(G, filter_node=filter_node)
185
+ >>> view.nodes()
186
+ NodeView((0, 1, 2, 3, 4))
187
+
188
+ We can use a closure pattern to filter graph elements based on additional
189
+ data --- for example, filtering on edge data attached to the graph:
190
+
191
+ >>> G[3][4]["cross_me"] = False
192
+ >>> def filter_edge(n1, n2):
193
+ ... return G[n1][n2].get("cross_me", True)
194
+ ...
195
+ >>> view = nx.subgraph_view(G, filter_edge=filter_edge)
196
+ >>> view.edges()
197
+ EdgeView([(0, 1), (1, 2), (2, 3), (4, 5)])
198
+
199
+ >>> view = nx.subgraph_view(G, filter_node=filter_node, filter_edge=filter_edge,)
200
+ >>> view.nodes()
201
+ NodeView((0, 1, 2, 3, 4))
202
+ >>> view.edges()
203
+ EdgeView([(0, 1), (1, 2), (2, 3)])
204
+ """
205
+ newG = nx.freeze(G.__class__())
206
+ newG._NODE_OK = filter_node
207
+ newG._EDGE_OK = filter_edge
208
+
209
+ # create view by assigning attributes from G
210
+ newG._graph = G
211
+ newG.graph = G.graph
212
+
213
+ newG._node = FilterAtlas(G._node, filter_node)
214
+ if G.is_multigraph():
215
+ Adj = FilterMultiAdjacency
216
+
217
+ def reverse_edge(u, v, k=None):
218
+ return filter_edge(v, u, k)
219
+
220
+ else:
221
+ Adj = FilterAdjacency
222
+
223
+ def reverse_edge(u, v, k=None):
224
+ return filter_edge(v, u)
225
+
226
+ if G.is_directed():
227
+ newG._succ = Adj(G._succ, filter_node, filter_edge)
228
+ newG._pred = Adj(G._pred, filter_node, reverse_edge)
229
+ # newG._adj is synced with _succ
230
+ else:
231
+ newG._adj = Adj(G._adj, filter_node, filter_edge)
232
+ return newG
233
+
234
+
235
+ @not_implemented_for("undirected")
236
+ def reverse_view(G):
237
+ """View of `G` with edge directions reversed
238
+
239
+ `reverse_view` returns a read-only view of the input graph where
240
+ edge directions are reversed.
241
+
242
+ Identical to digraph.reverse(copy=False)
243
+
244
+ Parameters
245
+ ----------
246
+ G : networkx.DiGraph
247
+
248
+ Returns
249
+ -------
250
+ graph : networkx.DiGraph
251
+
252
+ Examples
253
+ --------
254
+ >>> G = nx.DiGraph()
255
+ >>> G.add_edge(1, 2)
256
+ >>> G.add_edge(2, 3)
257
+ >>> G.edges()
258
+ OutEdgeView([(1, 2), (2, 3)])
259
+
260
+ >>> view = nx.reverse_view(G)
261
+ >>> view.edges()
262
+ OutEdgeView([(2, 1), (3, 2)])
263
+ """
264
+ newG = generic_graph_view(G)
265
+ newG._succ, newG._pred = G._pred, G._succ
266
+ # newG._adj is synced with _succ
267
+ return newG
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/multidigraph.py ADDED
@@ -0,0 +1,963 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Base class for MultiDiGraph."""
2
+ from copy import deepcopy
3
+ from functools import cached_property
4
+
5
+ import networkx as nx
6
+ from networkx import convert
7
+ from networkx.classes.coreviews import MultiAdjacencyView
8
+ from networkx.classes.digraph import DiGraph
9
+ from networkx.classes.multigraph import MultiGraph
10
+ from networkx.classes.reportviews import (
11
+ DiMultiDegreeView,
12
+ InMultiDegreeView,
13
+ InMultiEdgeView,
14
+ OutMultiDegreeView,
15
+ OutMultiEdgeView,
16
+ )
17
+ from networkx.exception import NetworkXError
18
+
19
+ __all__ = ["MultiDiGraph"]
20
+
21
+
22
+ class MultiDiGraph(MultiGraph, DiGraph):
23
+ """A directed graph class that can store multiedges.
24
+
25
+ Multiedges are multiple edges between two nodes. Each edge
26
+ can hold optional data or attributes.
27
+
28
+ A MultiDiGraph holds directed edges. Self loops are allowed.
29
+
30
+ Nodes can be arbitrary (hashable) Python objects with optional
31
+ key/value attributes. By convention `None` is not used as a node.
32
+
33
+ Edges are represented as links between nodes with optional
34
+ key/value attributes.
35
+
36
+ Parameters
37
+ ----------
38
+ incoming_graph_data : input graph (optional, default: None)
39
+ Data to initialize graph. If None (default) an empty
40
+ graph is created. The data can be any format that is supported
41
+ by the to_networkx_graph() function, currently including edge list,
42
+ dict of dicts, dict of lists, NetworkX graph, 2D NumPy array, SciPy
43
+ sparse matrix, or PyGraphviz graph.
44
+
45
+ multigraph_input : bool or None (default None)
46
+ Note: Only used when `incoming_graph_data` is a dict.
47
+ If True, `incoming_graph_data` is assumed to be a
48
+ dict-of-dict-of-dict-of-dict structure keyed by
49
+ node to neighbor to edge keys to edge data for multi-edges.
50
+ A NetworkXError is raised if this is not the case.
51
+ If False, :func:`to_networkx_graph` is used to try to determine
52
+ the dict's graph data structure as either a dict-of-dict-of-dict
53
+ keyed by node to neighbor to edge data, or a dict-of-iterable
54
+ keyed by node to neighbors.
55
+ If None, the treatment for True is tried, but if it fails,
56
+ the treatment for False is tried.
57
+
58
+ attr : keyword arguments, optional (default= no attributes)
59
+ Attributes to add to graph as key=value pairs.
60
+
61
+ See Also
62
+ --------
63
+ Graph
64
+ DiGraph
65
+ MultiGraph
66
+
67
+ Examples
68
+ --------
69
+ Create an empty graph structure (a "null graph") with no nodes and
70
+ no edges.
71
+
72
+ >>> G = nx.MultiDiGraph()
73
+
74
+ G can be grown in several ways.
75
+
76
+ **Nodes:**
77
+
78
+ Add one node at a time:
79
+
80
+ >>> G.add_node(1)
81
+
82
+ Add the nodes from any container (a list, dict, set or
83
+ even the lines from a file or the nodes from another graph).
84
+
85
+ >>> G.add_nodes_from([2, 3])
86
+ >>> G.add_nodes_from(range(100, 110))
87
+ >>> H = nx.path_graph(10)
88
+ >>> G.add_nodes_from(H)
89
+
90
+ In addition to strings and integers any hashable Python object
91
+ (except None) can represent a node, e.g. a customized node object,
92
+ or even another Graph.
93
+
94
+ >>> G.add_node(H)
95
+
96
+ **Edges:**
97
+
98
+ G can also be grown by adding edges.
99
+
100
+ Add one edge,
101
+
102
+ >>> key = G.add_edge(1, 2)
103
+
104
+ a list of edges,
105
+
106
+ >>> keys = G.add_edges_from([(1, 2), (1, 3)])
107
+
108
+ or a collection of edges,
109
+
110
+ >>> keys = G.add_edges_from(H.edges)
111
+
112
+ If some edges connect nodes not yet in the graph, the nodes
113
+ are added automatically. If an edge already exists, an additional
114
+ edge is created and stored using a key to identify the edge.
115
+ By default the key is the lowest unused integer.
116
+
117
+ >>> keys = G.add_edges_from([(4, 5, dict(route=282)), (4, 5, dict(route=37))])
118
+ >>> G[4]
119
+ AdjacencyView({5: {0: {}, 1: {'route': 282}, 2: {'route': 37}}})
120
+
121
+ **Attributes:**
122
+
123
+ Each graph, node, and edge can hold key/value attribute pairs
124
+ in an associated attribute dictionary (the keys must be hashable).
125
+ By default these are empty, but can be added or changed using
126
+ add_edge, add_node or direct manipulation of the attribute
127
+ dictionaries named graph, node and edge respectively.
128
+
129
+ >>> G = nx.MultiDiGraph(day="Friday")
130
+ >>> G.graph
131
+ {'day': 'Friday'}
132
+
133
+ Add node attributes using add_node(), add_nodes_from() or G.nodes
134
+
135
+ >>> G.add_node(1, time="5pm")
136
+ >>> G.add_nodes_from([3], time="2pm")
137
+ >>> G.nodes[1]
138
+ {'time': '5pm'}
139
+ >>> G.nodes[1]["room"] = 714
140
+ >>> del G.nodes[1]["room"] # remove attribute
141
+ >>> list(G.nodes(data=True))
142
+ [(1, {'time': '5pm'}), (3, {'time': '2pm'})]
143
+
144
+ Add edge attributes using add_edge(), add_edges_from(), subscript
145
+ notation, or G.edges.
146
+
147
+ >>> key = G.add_edge(1, 2, weight=4.7)
148
+ >>> keys = G.add_edges_from([(3, 4), (4, 5)], color="red")
149
+ >>> keys = G.add_edges_from([(1, 2, {"color": "blue"}), (2, 3, {"weight": 8})])
150
+ >>> G[1][2][0]["weight"] = 4.7
151
+ >>> G.edges[1, 2, 0]["weight"] = 4
152
+
153
+ Warning: we protect the graph data structure by making `G.edges[1,
154
+ 2, 0]` a read-only dict-like structure. However, you can assign to
155
+ attributes in e.g. `G.edges[1, 2, 0]`. Thus, use 2 sets of brackets
156
+ to add/change data attributes: `G.edges[1, 2, 0]['weight'] = 4`
157
+ (for multigraphs the edge key is required: `MG.edges[u, v,
158
+ key][name] = value`).
159
+
160
+ **Shortcuts:**
161
+
162
+ Many common graph features allow python syntax to speed reporting.
163
+
164
+ >>> 1 in G # check if node in graph
165
+ True
166
+ >>> [n for n in G if n < 3] # iterate through nodes
167
+ [1, 2]
168
+ >>> len(G) # number of nodes in graph
169
+ 5
170
+ >>> G[1] # adjacency dict-like view mapping neighbor -> edge key -> edge attributes
171
+ AdjacencyView({2: {0: {'weight': 4}, 1: {'color': 'blue'}}})
172
+
173
+ Often the best way to traverse all edges of a graph is via the neighbors.
174
+ The neighbors are available as an adjacency-view `G.adj` object or via
175
+ the method `G.adjacency()`.
176
+
177
+ >>> for n, nbrsdict in G.adjacency():
178
+ ... for nbr, keydict in nbrsdict.items():
179
+ ... for key, eattr in keydict.items():
180
+ ... if "weight" in eattr:
181
+ ... # Do something useful with the edges
182
+ ... pass
183
+
184
+ But the edges() method is often more convenient:
185
+
186
+ >>> for u, v, keys, weight in G.edges(data="weight", keys=True):
187
+ ... if weight is not None:
188
+ ... # Do something useful with the edges
189
+ ... pass
190
+
191
+ **Reporting:**
192
+
193
+ Simple graph information is obtained using methods and object-attributes.
194
+ Reporting usually provides views instead of containers to reduce memory
195
+ usage. The views update as the graph is updated similarly to dict-views.
196
+ The objects `nodes`, `edges` and `adj` provide access to data attributes
197
+ via lookup (e.g. `nodes[n]`, `edges[u, v, k]`, `adj[u][v]`) and iteration
198
+ (e.g. `nodes.items()`, `nodes.data('color')`,
199
+ `nodes.data('color', default='blue')` and similarly for `edges`)
200
+ Views exist for `nodes`, `edges`, `neighbors()`/`adj` and `degree`.
201
+
202
+ For details on these and other miscellaneous methods, see below.
203
+
204
+ **Subclasses (Advanced):**
205
+
206
+ The MultiDiGraph class uses a dict-of-dict-of-dict-of-dict structure.
207
+ The outer dict (node_dict) holds adjacency information keyed by node.
208
+ The next dict (adjlist_dict) represents the adjacency information
209
+ and holds edge_key dicts keyed by neighbor. The edge_key dict holds
210
+ each edge_attr dict keyed by edge key. The inner dict
211
+ (edge_attr_dict) represents the edge data and holds edge attribute
212
+ values keyed by attribute names.
213
+
214
+ Each of these four dicts in the dict-of-dict-of-dict-of-dict
215
+ structure can be replaced by a user defined dict-like object.
216
+ In general, the dict-like features should be maintained but
217
+ extra features can be added. To replace one of the dicts create
218
+ a new graph class by changing the class(!) variable holding the
219
+ factory for that dict-like structure. The variable names are
220
+ node_dict_factory, node_attr_dict_factory, adjlist_inner_dict_factory,
221
+ adjlist_outer_dict_factory, edge_key_dict_factory, edge_attr_dict_factory
222
+ and graph_attr_dict_factory.
223
+
224
+ node_dict_factory : function, (default: dict)
225
+ Factory function to be used to create the dict containing node
226
+ attributes, keyed by node id.
227
+ It should require no arguments and return a dict-like object
228
+
229
+ node_attr_dict_factory: function, (default: dict)
230
+ Factory function to be used to create the node attribute
231
+ dict which holds attribute values keyed by attribute name.
232
+ It should require no arguments and return a dict-like object
233
+
234
+ adjlist_outer_dict_factory : function, (default: dict)
235
+ Factory function to be used to create the outer-most dict
236
+ in the data structure that holds adjacency info keyed by node.
237
+ It should require no arguments and return a dict-like object.
238
+
239
+ adjlist_inner_dict_factory : function, (default: dict)
240
+ Factory function to be used to create the adjacency list
241
+ dict which holds multiedge key dicts keyed by neighbor.
242
+ It should require no arguments and return a dict-like object.
243
+
244
+ edge_key_dict_factory : function, (default: dict)
245
+ Factory function to be used to create the edge key dict
246
+ which holds edge data keyed by edge key.
247
+ It should require no arguments and return a dict-like object.
248
+
249
+ edge_attr_dict_factory : function, (default: dict)
250
+ Factory function to be used to create the edge attribute
251
+ dict which holds attribute values keyed by attribute name.
252
+ It should require no arguments and return a dict-like object.
253
+
254
+ graph_attr_dict_factory : function, (default: dict)
255
+ Factory function to be used to create the graph attribute
256
+ dict which holds attribute values keyed by attribute name.
257
+ It should require no arguments and return a dict-like object.
258
+
259
+ Typically, if your extension doesn't impact the data structure all
260
+ methods will inherited without issue except: `to_directed/to_undirected`.
261
+ By default these methods create a DiGraph/Graph class and you probably
262
+ want them to create your extension of a DiGraph/Graph. To facilitate
263
+ this we define two class variables that you can set in your subclass.
264
+
265
+ to_directed_class : callable, (default: DiGraph or MultiDiGraph)
266
+ Class to create a new graph structure in the `to_directed` method.
267
+ If `None`, a NetworkX class (DiGraph or MultiDiGraph) is used.
268
+
269
+ to_undirected_class : callable, (default: Graph or MultiGraph)
270
+ Class to create a new graph structure in the `to_undirected` method.
271
+ If `None`, a NetworkX class (Graph or MultiGraph) is used.
272
+
273
+ **Subclassing Example**
274
+
275
+ Create a low memory graph class that effectively disallows edge
276
+ attributes by using a single attribute dict for all edges.
277
+ This reduces the memory used, but you lose edge attributes.
278
+
279
+ >>> class ThinGraph(nx.Graph):
280
+ ... all_edge_dict = {"weight": 1}
281
+ ...
282
+ ... def single_edge_dict(self):
283
+ ... return self.all_edge_dict
284
+ ...
285
+ ... edge_attr_dict_factory = single_edge_dict
286
+ >>> G = ThinGraph()
287
+ >>> G.add_edge(2, 1)
288
+ >>> G[2][1]
289
+ {'weight': 1}
290
+ >>> G.add_edge(2, 2)
291
+ >>> G[2][1] is G[2][2]
292
+ True
293
+ """
294
+
295
+ # node_dict_factory = dict # already assigned in Graph
296
+ # adjlist_outer_dict_factory = dict
297
+ # adjlist_inner_dict_factory = dict
298
+ edge_key_dict_factory = dict
299
+ # edge_attr_dict_factory = dict
300
+
301
+ def __init__(self, incoming_graph_data=None, multigraph_input=None, **attr):
302
+ """Initialize a graph with edges, name, or graph attributes.
303
+
304
+ Parameters
305
+ ----------
306
+ incoming_graph_data : input graph
307
+ Data to initialize graph. If incoming_graph_data=None (default)
308
+ an empty graph is created. The data can be an edge list, or any
309
+ NetworkX graph object. If the corresponding optional Python
310
+ packages are installed the data can also be a 2D NumPy array, a
311
+ SciPy sparse array, or a PyGraphviz graph.
312
+
313
+ multigraph_input : bool or None (default None)
314
+ Note: Only used when `incoming_graph_data` is a dict.
315
+ If True, `incoming_graph_data` is assumed to be a
316
+ dict-of-dict-of-dict-of-dict structure keyed by
317
+ node to neighbor to edge keys to edge data for multi-edges.
318
+ A NetworkXError is raised if this is not the case.
319
+ If False, :func:`to_networkx_graph` is used to try to determine
320
+ the dict's graph data structure as either a dict-of-dict-of-dict
321
+ keyed by node to neighbor to edge data, or a dict-of-iterable
322
+ keyed by node to neighbors.
323
+ If None, the treatment for True is tried, but if it fails,
324
+ the treatment for False is tried.
325
+
326
+ attr : keyword arguments, optional (default= no attributes)
327
+ Attributes to add to graph as key=value pairs.
328
+
329
+ See Also
330
+ --------
331
+ convert
332
+
333
+ Examples
334
+ --------
335
+ >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
336
+ >>> G = nx.Graph(name="my graph")
337
+ >>> e = [(1, 2), (2, 3), (3, 4)] # list of edges
338
+ >>> G = nx.Graph(e)
339
+
340
+ Arbitrary graph attribute pairs (key=value) may be assigned
341
+
342
+ >>> G = nx.Graph(e, day="Friday")
343
+ >>> G.graph
344
+ {'day': 'Friday'}
345
+
346
+ """
347
+ # multigraph_input can be None/True/False. So check "is not False"
348
+ if isinstance(incoming_graph_data, dict) and multigraph_input is not False:
349
+ DiGraph.__init__(self)
350
+ try:
351
+ convert.from_dict_of_dicts(
352
+ incoming_graph_data, create_using=self, multigraph_input=True
353
+ )
354
+ self.graph.update(attr)
355
+ except Exception as err:
356
+ if multigraph_input is True:
357
+ raise nx.NetworkXError(
358
+ f"converting multigraph_input raised:\n{type(err)}: {err}"
359
+ )
360
+ DiGraph.__init__(self, incoming_graph_data, **attr)
361
+ else:
362
+ DiGraph.__init__(self, incoming_graph_data, **attr)
363
+
364
+ @cached_property
365
+ def adj(self):
366
+ """Graph adjacency object holding the neighbors of each node.
367
+
368
+ This object is a read-only dict-like structure with node keys
369
+ and neighbor-dict values. The neighbor-dict is keyed by neighbor
370
+ to the edgekey-dict. So `G.adj[3][2][0]['color'] = 'blue'` sets
371
+ the color of the edge `(3, 2, 0)` to `"blue"`.
372
+
373
+ Iterating over G.adj behaves like a dict. Useful idioms include
374
+ `for nbr, datadict in G.adj[n].items():`.
375
+
376
+ The neighbor information is also provided by subscripting the graph.
377
+ So `for nbr, foovalue in G[node].data('foo', default=1):` works.
378
+
379
+ For directed graphs, `G.adj` holds outgoing (successor) info.
380
+ """
381
+ return MultiAdjacencyView(self._succ)
382
+
383
+ @cached_property
384
+ def succ(self):
385
+ """Graph adjacency object holding the successors of each node.
386
+
387
+ This object is a read-only dict-like structure with node keys
388
+ and neighbor-dict values. The neighbor-dict is keyed by neighbor
389
+ to the edgekey-dict. So `G.adj[3][2][0]['color'] = 'blue'` sets
390
+ the color of the edge `(3, 2, 0)` to `"blue"`.
391
+
392
+ Iterating over G.adj behaves like a dict. Useful idioms include
393
+ `for nbr, datadict in G.adj[n].items():`.
394
+
395
+ The neighbor information is also provided by subscripting the graph.
396
+ So `for nbr, foovalue in G[node].data('foo', default=1):` works.
397
+
398
+ For directed graphs, `G.succ` is identical to `G.adj`.
399
+ """
400
+ return MultiAdjacencyView(self._succ)
401
+
402
+ @cached_property
403
+ def pred(self):
404
+ """Graph adjacency object holding the predecessors of each node.
405
+
406
+ This object is a read-only dict-like structure with node keys
407
+ and neighbor-dict values. The neighbor-dict is keyed by neighbor
408
+ to the edgekey-dict. So `G.adj[3][2][0]['color'] = 'blue'` sets
409
+ the color of the edge `(3, 2, 0)` to `"blue"`.
410
+
411
+ Iterating over G.adj behaves like a dict. Useful idioms include
412
+ `for nbr, datadict in G.adj[n].items():`.
413
+ """
414
+ return MultiAdjacencyView(self._pred)
415
+
416
+ def add_edge(self, u_for_edge, v_for_edge, key=None, **attr):
417
+ """Add an edge between u and v.
418
+
419
+ The nodes u and v will be automatically added if they are
420
+ not already in the graph.
421
+
422
+ Edge attributes can be specified with keywords or by directly
423
+ accessing the edge's attribute dictionary. See examples below.
424
+
425
+ Parameters
426
+ ----------
427
+ u_for_edge, v_for_edge : nodes
428
+ Nodes can be, for example, strings or numbers.
429
+ Nodes must be hashable (and not None) Python objects.
430
+ key : hashable identifier, optional (default=lowest unused integer)
431
+ Used to distinguish multiedges between a pair of nodes.
432
+ attr : keyword arguments, optional
433
+ Edge data (or labels or objects) can be assigned using
434
+ keyword arguments.
435
+
436
+ Returns
437
+ -------
438
+ The edge key assigned to the edge.
439
+
440
+ See Also
441
+ --------
442
+ add_edges_from : add a collection of edges
443
+
444
+ Notes
445
+ -----
446
+ To replace/update edge data, use the optional key argument
447
+ to identify a unique edge. Otherwise a new edge will be created.
448
+
449
+ NetworkX algorithms designed for weighted graphs cannot use
450
+ multigraphs directly because it is not clear how to handle
451
+ multiedge weights. Convert to Graph using edge attribute
452
+ 'weight' to enable weighted graph algorithms.
453
+
454
+ Default keys are generated using the method `new_edge_key()`.
455
+ This method can be overridden by subclassing the base class and
456
+ providing a custom `new_edge_key()` method.
457
+
458
+ Examples
459
+ --------
460
+ The following all add the edge e=(1, 2) to graph G:
461
+
462
+ >>> G = nx.MultiDiGraph()
463
+ >>> e = (1, 2)
464
+ >>> key = G.add_edge(1, 2) # explicit two-node form
465
+ >>> G.add_edge(*e) # single edge as tuple of two nodes
466
+ 1
467
+ >>> G.add_edges_from([(1, 2)]) # add edges from iterable container
468
+ [2]
469
+
470
+ Associate data to edges using keywords:
471
+
472
+ >>> key = G.add_edge(1, 2, weight=3)
473
+ >>> key = G.add_edge(1, 2, key=0, weight=4) # update data for key=0
474
+ >>> key = G.add_edge(1, 3, weight=7, capacity=15, length=342.7)
475
+
476
+ For non-string attribute keys, use subscript notation.
477
+
478
+ >>> ekey = G.add_edge(1, 2)
479
+ >>> G[1][2][0].update({0: 5})
480
+ >>> G.edges[1, 2, 0].update({0: 5})
481
+ """
482
+ u, v = u_for_edge, v_for_edge
483
+ # add nodes
484
+ if u not in self._succ:
485
+ if u is None:
486
+ raise ValueError("None cannot be a node")
487
+ self._succ[u] = self.adjlist_inner_dict_factory()
488
+ self._pred[u] = self.adjlist_inner_dict_factory()
489
+ self._node[u] = self.node_attr_dict_factory()
490
+ if v not in self._succ:
491
+ if v is None:
492
+ raise ValueError("None cannot be a node")
493
+ self._succ[v] = self.adjlist_inner_dict_factory()
494
+ self._pred[v] = self.adjlist_inner_dict_factory()
495
+ self._node[v] = self.node_attr_dict_factory()
496
+ if key is None:
497
+ key = self.new_edge_key(u, v)
498
+ if v in self._succ[u]:
499
+ keydict = self._adj[u][v]
500
+ datadict = keydict.get(key, self.edge_attr_dict_factory())
501
+ datadict.update(attr)
502
+ keydict[key] = datadict
503
+ else:
504
+ # selfloops work this way without special treatment
505
+ datadict = self.edge_attr_dict_factory()
506
+ datadict.update(attr)
507
+ keydict = self.edge_key_dict_factory()
508
+ keydict[key] = datadict
509
+ self._succ[u][v] = keydict
510
+ self._pred[v][u] = keydict
511
+ return key
512
+
513
+ def remove_edge(self, u, v, key=None):
514
+ """Remove an edge between u and v.
515
+
516
+ Parameters
517
+ ----------
518
+ u, v : nodes
519
+ Remove an edge between nodes u and v.
520
+ key : hashable identifier, optional (default=None)
521
+ Used to distinguish multiple edges between a pair of nodes.
522
+ If None, remove a single edge between u and v. If there are
523
+ multiple edges, removes the last edge added in terms of
524
+ insertion order.
525
+
526
+ Raises
527
+ ------
528
+ NetworkXError
529
+ If there is not an edge between u and v, or
530
+ if there is no edge with the specified key.
531
+
532
+ See Also
533
+ --------
534
+ remove_edges_from : remove a collection of edges
535
+
536
+ Examples
537
+ --------
538
+ >>> G = nx.MultiDiGraph()
539
+ >>> nx.add_path(G, [0, 1, 2, 3])
540
+ >>> G.remove_edge(0, 1)
541
+ >>> e = (1, 2)
542
+ >>> G.remove_edge(*e) # unpacks e from an edge tuple
543
+
544
+ For multiple edges
545
+
546
+ >>> G = nx.MultiDiGraph()
547
+ >>> G.add_edges_from([(1, 2), (1, 2), (1, 2)]) # key_list returned
548
+ [0, 1, 2]
549
+
550
+ When ``key=None`` (the default), edges are removed in the opposite
551
+ order that they were added:
552
+
553
+ >>> G.remove_edge(1, 2)
554
+ >>> G.edges(keys=True)
555
+ OutMultiEdgeView([(1, 2, 0), (1, 2, 1)])
556
+
557
+ For edges with keys
558
+
559
+ >>> G = nx.MultiDiGraph()
560
+ >>> G.add_edge(1, 2, key="first")
561
+ 'first'
562
+ >>> G.add_edge(1, 2, key="second")
563
+ 'second'
564
+ >>> G.remove_edge(1, 2, key="first")
565
+ >>> G.edges(keys=True)
566
+ OutMultiEdgeView([(1, 2, 'second')])
567
+
568
+ """
569
+ try:
570
+ d = self._adj[u][v]
571
+ except KeyError as err:
572
+ raise NetworkXError(f"The edge {u}-{v} is not in the graph.") from err
573
+ # remove the edge with specified data
574
+ if key is None:
575
+ d.popitem()
576
+ else:
577
+ try:
578
+ del d[key]
579
+ except KeyError as err:
580
+ msg = f"The edge {u}-{v} with key {key} is not in the graph."
581
+ raise NetworkXError(msg) from err
582
+ if len(d) == 0:
583
+ # remove the key entries if last edge
584
+ del self._succ[u][v]
585
+ del self._pred[v][u]
586
+
587
+ @cached_property
588
+ def edges(self):
589
+ """An OutMultiEdgeView of the Graph as G.edges or G.edges().
590
+
591
+ edges(self, nbunch=None, data=False, keys=False, default=None)
592
+
593
+ The OutMultiEdgeView provides set-like operations on the edge-tuples
594
+ as well as edge attribute lookup. When called, it also provides
595
+ an EdgeDataView object which allows control of access to edge
596
+ attributes (but does not provide set-like operations).
597
+ Hence, ``G.edges[u, v, k]['color']`` provides the value of the color
598
+ attribute for the edge from ``u`` to ``v`` with key ``k`` while
599
+ ``for (u, v, k, c) in G.edges(data='color', default='red', keys=True):``
600
+ iterates through all the edges yielding the color attribute with
601
+ default `'red'` if no color attribute exists.
602
+
603
+ Edges are returned as tuples with optional data and keys
604
+ in the order (node, neighbor, key, data). If ``keys=True`` is not
605
+ provided, the tuples will just be (node, neighbor, data), but
606
+ multiple tuples with the same node and neighbor will be
607
+ generated when multiple edges between two nodes exist.
608
+
609
+ Parameters
610
+ ----------
611
+ nbunch : single node, container, or all nodes (default= all nodes)
612
+ The view will only report edges from these nodes.
613
+ data : string or bool, optional (default=False)
614
+ The edge attribute returned in 3-tuple (u, v, ddict[data]).
615
+ If True, return edge attribute dict in 3-tuple (u, v, ddict).
616
+ If False, return 2-tuple (u, v).
617
+ keys : bool, optional (default=False)
618
+ If True, return edge keys with each edge, creating (u, v, k,
619
+ d) tuples when data is also requested (the default) and (u,
620
+ v, k) tuples when data is not requested.
621
+ default : value, optional (default=None)
622
+ Value used for edges that don't have the requested attribute.
623
+ Only relevant if data is not True or False.
624
+
625
+ Returns
626
+ -------
627
+ edges : OutMultiEdgeView
628
+ A view of edge attributes, usually it iterates over (u, v)
629
+ (u, v, k) or (u, v, k, d) tuples of edges, but can also be
630
+ used for attribute lookup as ``edges[u, v, k]['foo']``.
631
+
632
+ Notes
633
+ -----
634
+ Nodes in nbunch that are not in the graph will be (quietly) ignored.
635
+ For directed graphs this returns the out-edges.
636
+
637
+ Examples
638
+ --------
639
+ >>> G = nx.MultiDiGraph()
640
+ >>> nx.add_path(G, [0, 1, 2])
641
+ >>> key = G.add_edge(2, 3, weight=5)
642
+ >>> key2 = G.add_edge(1, 2) # second edge between these nodes
643
+ >>> [e for e in G.edges()]
644
+ [(0, 1), (1, 2), (1, 2), (2, 3)]
645
+ >>> list(G.edges(data=True)) # default data is {} (empty dict)
646
+ [(0, 1, {}), (1, 2, {}), (1, 2, {}), (2, 3, {'weight': 5})]
647
+ >>> list(G.edges(data="weight", default=1))
648
+ [(0, 1, 1), (1, 2, 1), (1, 2, 1), (2, 3, 5)]
649
+ >>> list(G.edges(keys=True)) # default keys are integers
650
+ [(0, 1, 0), (1, 2, 0), (1, 2, 1), (2, 3, 0)]
651
+ >>> list(G.edges(data=True, keys=True))
652
+ [(0, 1, 0, {}), (1, 2, 0, {}), (1, 2, 1, {}), (2, 3, 0, {'weight': 5})]
653
+ >>> list(G.edges(data="weight", default=1, keys=True))
654
+ [(0, 1, 0, 1), (1, 2, 0, 1), (1, 2, 1, 1), (2, 3, 0, 5)]
655
+ >>> list(G.edges([0, 2]))
656
+ [(0, 1), (2, 3)]
657
+ >>> list(G.edges(0))
658
+ [(0, 1)]
659
+ >>> list(G.edges(1))
660
+ [(1, 2), (1, 2)]
661
+
662
+ See Also
663
+ --------
664
+ in_edges, out_edges
665
+ """
666
+ return OutMultiEdgeView(self)
667
+
668
+ # alias out_edges to edges
669
+ @cached_property
670
+ def out_edges(self):
671
+ return OutMultiEdgeView(self)
672
+
673
+ out_edges.__doc__ = edges.__doc__
674
+
675
+ @cached_property
676
+ def in_edges(self):
677
+ """A view of the in edges of the graph as G.in_edges or G.in_edges().
678
+
679
+ in_edges(self, nbunch=None, data=False, keys=False, default=None)
680
+
681
+ Parameters
682
+ ----------
683
+ nbunch : single node, container, or all nodes (default= all nodes)
684
+ The view will only report edges incident to these nodes.
685
+ data : string or bool, optional (default=False)
686
+ The edge attribute returned in 3-tuple (u, v, ddict[data]).
687
+ If True, return edge attribute dict in 3-tuple (u, v, ddict).
688
+ If False, return 2-tuple (u, v).
689
+ keys : bool, optional (default=False)
690
+ If True, return edge keys with each edge, creating 3-tuples
691
+ (u, v, k) or with data, 4-tuples (u, v, k, d).
692
+ default : value, optional (default=None)
693
+ Value used for edges that don't have the requested attribute.
694
+ Only relevant if data is not True or False.
695
+
696
+ Returns
697
+ -------
698
+ in_edges : InMultiEdgeView or InMultiEdgeDataView
699
+ A view of edge attributes, usually it iterates over (u, v)
700
+ or (u, v, k) or (u, v, k, d) tuples of edges, but can also be
701
+ used for attribute lookup as `edges[u, v, k]['foo']`.
702
+
703
+ See Also
704
+ --------
705
+ edges
706
+ """
707
+ return InMultiEdgeView(self)
708
+
709
+ @cached_property
710
+ def degree(self):
711
+ """A DegreeView for the Graph as G.degree or G.degree().
712
+
713
+ The node degree is the number of edges adjacent to the node.
714
+ The weighted node degree is the sum of the edge weights for
715
+ edges incident to that node.
716
+
717
+ This object provides an iterator for (node, degree) as well as
718
+ lookup for the degree for a single node.
719
+
720
+ Parameters
721
+ ----------
722
+ nbunch : single node, container, or all nodes (default= all nodes)
723
+ The view will only report edges incident to these nodes.
724
+
725
+ weight : string or None, optional (default=None)
726
+ The name of an edge attribute that holds the numerical value used
727
+ as a weight. If None, then each edge has weight 1.
728
+ The degree is the sum of the edge weights adjacent to the node.
729
+
730
+ Returns
731
+ -------
732
+ DiMultiDegreeView or int
733
+ If multiple nodes are requested (the default), returns a `DiMultiDegreeView`
734
+ mapping nodes to their degree.
735
+ If a single node is requested, returns the degree of the node as an integer.
736
+
737
+ See Also
738
+ --------
739
+ out_degree, in_degree
740
+
741
+ Examples
742
+ --------
743
+ >>> G = nx.MultiDiGraph()
744
+ >>> nx.add_path(G, [0, 1, 2, 3])
745
+ >>> G.degree(0) # node 0 with degree 1
746
+ 1
747
+ >>> list(G.degree([0, 1, 2]))
748
+ [(0, 1), (1, 2), (2, 2)]
749
+ >>> G.add_edge(0, 1) # parallel edge
750
+ 1
751
+ >>> list(G.degree([0, 1, 2])) # parallel edges are counted
752
+ [(0, 2), (1, 3), (2, 2)]
753
+
754
+ """
755
+ return DiMultiDegreeView(self)
756
+
757
+ @cached_property
758
+ def in_degree(self):
759
+ """A DegreeView for (node, in_degree) or in_degree for single node.
760
+
761
+ The node in-degree is the number of edges pointing into the node.
762
+ The weighted node degree is the sum of the edge weights for
763
+ edges incident to that node.
764
+
765
+ This object provides an iterator for (node, degree) as well as
766
+ lookup for the degree for a single node.
767
+
768
+ Parameters
769
+ ----------
770
+ nbunch : single node, container, or all nodes (default= all nodes)
771
+ The view will only report edges incident to these nodes.
772
+
773
+ weight : string or None, optional (default=None)
774
+ The edge attribute that holds the numerical value used
775
+ as a weight. If None, then each edge has weight 1.
776
+ The degree is the sum of the edge weights adjacent to the node.
777
+
778
+ Returns
779
+ -------
780
+ If a single node is requested
781
+ deg : int
782
+ Degree of the node
783
+
784
+ OR if multiple nodes are requested
785
+ nd_iter : iterator
786
+ The iterator returns two-tuples of (node, in-degree).
787
+
788
+ See Also
789
+ --------
790
+ degree, out_degree
791
+
792
+ Examples
793
+ --------
794
+ >>> G = nx.MultiDiGraph()
795
+ >>> nx.add_path(G, [0, 1, 2, 3])
796
+ >>> G.in_degree(0) # node 0 with degree 0
797
+ 0
798
+ >>> list(G.in_degree([0, 1, 2]))
799
+ [(0, 0), (1, 1), (2, 1)]
800
+ >>> G.add_edge(0, 1) # parallel edge
801
+ 1
802
+ >>> list(G.in_degree([0, 1, 2])) # parallel edges counted
803
+ [(0, 0), (1, 2), (2, 1)]
804
+
805
+ """
806
+ return InMultiDegreeView(self)
807
+
808
+ @cached_property
809
+ def out_degree(self):
810
+ """Returns an iterator for (node, out-degree) or out-degree for single node.
811
+
812
+ out_degree(self, nbunch=None, weight=None)
813
+
814
+ The node out-degree is the number of edges pointing out of the node.
815
+ This function returns the out-degree for a single node or an iterator
816
+ for a bunch of nodes or if nothing is passed as argument.
817
+
818
+ Parameters
819
+ ----------
820
+ nbunch : single node, container, or all nodes (default= all nodes)
821
+ The view will only report edges incident to these nodes.
822
+
823
+ weight : string or None, optional (default=None)
824
+ The edge attribute that holds the numerical value used
825
+ as a weight. If None, then each edge has weight 1.
826
+ The degree is the sum of the edge weights.
827
+
828
+ Returns
829
+ -------
830
+ If a single node is requested
831
+ deg : int
832
+ Degree of the node
833
+
834
+ OR if multiple nodes are requested
835
+ nd_iter : iterator
836
+ The iterator returns two-tuples of (node, out-degree).
837
+
838
+ See Also
839
+ --------
840
+ degree, in_degree
841
+
842
+ Examples
843
+ --------
844
+ >>> G = nx.MultiDiGraph()
845
+ >>> nx.add_path(G, [0, 1, 2, 3])
846
+ >>> G.out_degree(0) # node 0 with degree 1
847
+ 1
848
+ >>> list(G.out_degree([0, 1, 2]))
849
+ [(0, 1), (1, 1), (2, 1)]
850
+ >>> G.add_edge(0, 1) # parallel edge
851
+ 1
852
+ >>> list(G.out_degree([0, 1, 2])) # counts parallel edges
853
+ [(0, 2), (1, 1), (2, 1)]
854
+
855
+ """
856
+ return OutMultiDegreeView(self)
857
+
858
+ def is_multigraph(self):
859
+ """Returns True if graph is a multigraph, False otherwise."""
860
+ return True
861
+
862
+ def is_directed(self):
863
+ """Returns True if graph is directed, False otherwise."""
864
+ return True
865
+
866
+ def to_undirected(self, reciprocal=False, as_view=False):
867
+ """Returns an undirected representation of the digraph.
868
+
869
+ Parameters
870
+ ----------
871
+ reciprocal : bool (optional)
872
+ If True only keep edges that appear in both directions
873
+ in the original digraph.
874
+ as_view : bool (optional, default=False)
875
+ If True return an undirected view of the original directed graph.
876
+
877
+ Returns
878
+ -------
879
+ G : MultiGraph
880
+ An undirected graph with the same name and nodes and
881
+ with edge (u, v, data) if either (u, v, data) or (v, u, data)
882
+ is in the digraph. If both edges exist in digraph and
883
+ their edge data is different, only one edge is created
884
+ with an arbitrary choice of which edge data to use.
885
+ You must check and correct for this manually if desired.
886
+
887
+ See Also
888
+ --------
889
+ MultiGraph, copy, add_edge, add_edges_from
890
+
891
+ Notes
892
+ -----
893
+ This returns a "deepcopy" of the edge, node, and
894
+ graph attributes which attempts to completely copy
895
+ all of the data and references.
896
+
897
+ This is in contrast to the similar D=MultiDiGraph(G) which
898
+ returns a shallow copy of the data.
899
+
900
+ See the Python copy module for more information on shallow
901
+ and deep copies, https://docs.python.org/3/library/copy.html.
902
+
903
+ Warning: If you have subclassed MultiDiGraph to use dict-like
904
+ objects in the data structure, those changes do not transfer
905
+ to the MultiGraph created by this method.
906
+
907
+ Examples
908
+ --------
909
+ >>> G = nx.path_graph(2) # or MultiGraph, etc
910
+ >>> H = G.to_directed()
911
+ >>> list(H.edges)
912
+ [(0, 1), (1, 0)]
913
+ >>> G2 = H.to_undirected()
914
+ >>> list(G2.edges)
915
+ [(0, 1)]
916
+ """
917
+ graph_class = self.to_undirected_class()
918
+ if as_view is True:
919
+ return nx.graphviews.generic_graph_view(self, graph_class)
920
+ # deepcopy when not a view
921
+ G = graph_class()
922
+ G.graph.update(deepcopy(self.graph))
923
+ G.add_nodes_from((n, deepcopy(d)) for n, d in self._node.items())
924
+ if reciprocal is True:
925
+ G.add_edges_from(
926
+ (u, v, key, deepcopy(data))
927
+ for u, nbrs in self._adj.items()
928
+ for v, keydict in nbrs.items()
929
+ for key, data in keydict.items()
930
+ if v in self._pred[u] and key in self._pred[u][v]
931
+ )
932
+ else:
933
+ G.add_edges_from(
934
+ (u, v, key, deepcopy(data))
935
+ for u, nbrs in self._adj.items()
936
+ for v, keydict in nbrs.items()
937
+ for key, data in keydict.items()
938
+ )
939
+ return G
940
+
941
+ def reverse(self, copy=True):
942
+ """Returns the reverse of the graph.
943
+
944
+ The reverse is a graph with the same nodes and edges
945
+ but with the directions of the edges reversed.
946
+
947
+ Parameters
948
+ ----------
949
+ copy : bool optional (default=True)
950
+ If True, return a new DiGraph holding the reversed edges.
951
+ If False, the reverse graph is created using a view of
952
+ the original graph.
953
+ """
954
+ if copy:
955
+ H = self.__class__()
956
+ H.graph.update(deepcopy(self.graph))
957
+ H.add_nodes_from((n, deepcopy(d)) for n, d in self._node.items())
958
+ H.add_edges_from(
959
+ (v, u, k, deepcopy(d))
960
+ for u, v, k, d in self.edges(keys=True, data=True)
961
+ )
962
+ return H
963
+ return nx.reverse_view(self)
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/__pycache__/test_reportviews.cpython-311.pyc ADDED
Binary file (83.7 kB). View file
 
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/test_graph_historical.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Original NetworkX graph tests"""
2
+ import networkx
3
+ import networkx as nx
4
+
5
+ from .historical_tests import HistoricalTests
6
+
7
+
8
+ class TestGraphHistorical(HistoricalTests):
9
+ @classmethod
10
+ def setup_class(cls):
11
+ HistoricalTests.setup_class()
12
+ cls.G = nx.Graph
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/test_reportviews.py ADDED
@@ -0,0 +1,1423 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pickle
2
+ from copy import deepcopy
3
+
4
+ import pytest
5
+
6
+ import networkx as nx
7
+ from networkx.classes import reportviews as rv
8
+ from networkx.classes.reportviews import NodeDataView
9
+
10
+
11
+ # Nodes
12
+ class TestNodeView:
13
+ @classmethod
14
+ def setup_class(cls):
15
+ cls.G = nx.path_graph(9)
16
+ cls.nv = cls.G.nodes # NodeView(G)
17
+
18
+ def test_pickle(self):
19
+ import pickle
20
+
21
+ nv = self.nv
22
+ pnv = pickle.loads(pickle.dumps(nv, -1))
23
+ assert nv == pnv
24
+ assert nv.__slots__ == pnv.__slots__
25
+
26
+ def test_str(self):
27
+ assert str(self.nv) == "[0, 1, 2, 3, 4, 5, 6, 7, 8]"
28
+
29
+ def test_repr(self):
30
+ assert repr(self.nv) == "NodeView((0, 1, 2, 3, 4, 5, 6, 7, 8))"
31
+
32
+ def test_contains(self):
33
+ G = self.G.copy()
34
+ nv = G.nodes
35
+ assert 7 in nv
36
+ assert 9 not in nv
37
+ G.remove_node(7)
38
+ G.add_node(9)
39
+ assert 7 not in nv
40
+ assert 9 in nv
41
+
42
+ def test_getitem(self):
43
+ G = self.G.copy()
44
+ nv = G.nodes
45
+ G.nodes[3]["foo"] = "bar"
46
+ assert nv[7] == {}
47
+ assert nv[3] == {"foo": "bar"}
48
+ # slicing
49
+ with pytest.raises(nx.NetworkXError):
50
+ G.nodes[0:5]
51
+
52
+ def test_iter(self):
53
+ nv = self.nv
54
+ for i, n in enumerate(nv):
55
+ assert i == n
56
+ inv = iter(nv)
57
+ assert next(inv) == 0
58
+ assert iter(nv) != nv
59
+ assert iter(inv) == inv
60
+ inv2 = iter(nv)
61
+ next(inv2)
62
+ assert list(inv) == list(inv2)
63
+ # odd case where NodeView calls NodeDataView with data=False
64
+ nnv = nv(data=False)
65
+ for i, n in enumerate(nnv):
66
+ assert i == n
67
+
68
+ def test_call(self):
69
+ nodes = self.nv
70
+ assert nodes is nodes()
71
+ assert nodes is not nodes(data=True)
72
+ assert nodes is not nodes(data="weight")
73
+
74
+
75
+ class TestNodeDataView:
76
+ @classmethod
77
+ def setup_class(cls):
78
+ cls.G = nx.path_graph(9)
79
+ cls.nv = NodeDataView(cls.G)
80
+ cls.ndv = cls.G.nodes.data(True)
81
+ cls.nwv = cls.G.nodes.data("foo")
82
+
83
+ def test_viewtype(self):
84
+ nv = self.G.nodes
85
+ ndvfalse = nv.data(False)
86
+ assert nv is ndvfalse
87
+ assert nv is not self.ndv
88
+
89
+ def test_pickle(self):
90
+ import pickle
91
+
92
+ nv = self.nv
93
+ pnv = pickle.loads(pickle.dumps(nv, -1))
94
+ assert nv == pnv
95
+ assert nv.__slots__ == pnv.__slots__
96
+
97
+ def test_str(self):
98
+ msg = str([(n, {}) for n in range(9)])
99
+ assert str(self.ndv) == msg
100
+
101
+ def test_repr(self):
102
+ expected = "NodeDataView((0, 1, 2, 3, 4, 5, 6, 7, 8))"
103
+ assert repr(self.nv) == expected
104
+ expected = (
105
+ "NodeDataView({0: {}, 1: {}, 2: {}, 3: {}, "
106
+ + "4: {}, 5: {}, 6: {}, 7: {}, 8: {}})"
107
+ )
108
+ assert repr(self.ndv) == expected
109
+ expected = (
110
+ "NodeDataView({0: None, 1: None, 2: None, 3: None, 4: None, "
111
+ + "5: None, 6: None, 7: None, 8: None}, data='foo')"
112
+ )
113
+ assert repr(self.nwv) == expected
114
+
115
+ def test_contains(self):
116
+ G = self.G.copy()
117
+ nv = G.nodes.data()
118
+ nwv = G.nodes.data("foo")
119
+ G.nodes[3]["foo"] = "bar"
120
+ assert (7, {}) in nv
121
+ assert (3, {"foo": "bar"}) in nv
122
+ assert (3, "bar") in nwv
123
+ assert (7, None) in nwv
124
+ # default
125
+ nwv_def = G.nodes(data="foo", default="biz")
126
+ assert (7, "biz") in nwv_def
127
+ assert (3, "bar") in nwv_def
128
+
129
+ def test_getitem(self):
130
+ G = self.G.copy()
131
+ nv = G.nodes
132
+ G.nodes[3]["foo"] = "bar"
133
+ assert nv[3] == {"foo": "bar"}
134
+ # default
135
+ nwv_def = G.nodes(data="foo", default="biz")
136
+ assert nwv_def[7], "biz"
137
+ assert nwv_def[3] == "bar"
138
+ # slicing
139
+ with pytest.raises(nx.NetworkXError):
140
+ G.nodes.data()[0:5]
141
+
142
+ def test_iter(self):
143
+ G = self.G.copy()
144
+ nv = G.nodes.data()
145
+ ndv = G.nodes.data(True)
146
+ nwv = G.nodes.data("foo")
147
+ for i, (n, d) in enumerate(nv):
148
+ assert i == n
149
+ assert d == {}
150
+ inv = iter(nv)
151
+ assert next(inv) == (0, {})
152
+ G.nodes[3]["foo"] = "bar"
153
+ # default
154
+ for n, d in nv:
155
+ if n == 3:
156
+ assert d == {"foo": "bar"}
157
+ else:
158
+ assert d == {}
159
+ # data=True
160
+ for n, d in ndv:
161
+ if n == 3:
162
+ assert d == {"foo": "bar"}
163
+ else:
164
+ assert d == {}
165
+ # data='foo'
166
+ for n, d in nwv:
167
+ if n == 3:
168
+ assert d == "bar"
169
+ else:
170
+ assert d is None
171
+ # data='foo', default=1
172
+ for n, d in G.nodes.data("foo", default=1):
173
+ if n == 3:
174
+ assert d == "bar"
175
+ else:
176
+ assert d == 1
177
+
178
+
179
+ def test_nodedataview_unhashable():
180
+ G = nx.path_graph(9)
181
+ G.nodes[3]["foo"] = "bar"
182
+ nvs = [G.nodes.data()]
183
+ nvs.append(G.nodes.data(True))
184
+ H = G.copy()
185
+ H.nodes[4]["foo"] = {1, 2, 3}
186
+ nvs.append(H.nodes.data(True))
187
+ # raise unhashable
188
+ for nv in nvs:
189
+ pytest.raises(TypeError, set, nv)
190
+ pytest.raises(TypeError, eval, "nv | nv", locals())
191
+ # no raise... hashable
192
+ Gn = G.nodes.data(False)
193
+ set(Gn)
194
+ Gn | Gn
195
+ Gn = G.nodes.data("foo")
196
+ set(Gn)
197
+ Gn | Gn
198
+
199
+
200
+ class TestNodeViewSetOps:
201
+ @classmethod
202
+ def setup_class(cls):
203
+ cls.G = nx.path_graph(9)
204
+ cls.G.nodes[3]["foo"] = "bar"
205
+ cls.nv = cls.G.nodes
206
+
207
+ def n_its(self, nodes):
208
+ return set(nodes)
209
+
210
+ def test_len(self):
211
+ G = self.G.copy()
212
+ nv = G.nodes
213
+ assert len(nv) == 9
214
+ G.remove_node(7)
215
+ assert len(nv) == 8
216
+ G.add_node(9)
217
+ assert len(nv) == 9
218
+
219
+ def test_and(self):
220
+ # print("G & H nodes:", gnv & hnv)
221
+ nv = self.nv
222
+ some_nodes = self.n_its(range(5, 12))
223
+ assert nv & some_nodes == self.n_its(range(5, 9))
224
+ assert some_nodes & nv == self.n_its(range(5, 9))
225
+
226
+ def test_or(self):
227
+ # print("G | H nodes:", gnv | hnv)
228
+ nv = self.nv
229
+ some_nodes = self.n_its(range(5, 12))
230
+ assert nv | some_nodes == self.n_its(range(12))
231
+ assert some_nodes | nv == self.n_its(range(12))
232
+
233
+ def test_xor(self):
234
+ # print("G ^ H nodes:", gnv ^ hnv)
235
+ nv = self.nv
236
+ some_nodes = self.n_its(range(5, 12))
237
+ nodes = {0, 1, 2, 3, 4, 9, 10, 11}
238
+ assert nv ^ some_nodes == self.n_its(nodes)
239
+ assert some_nodes ^ nv == self.n_its(nodes)
240
+
241
+ def test_sub(self):
242
+ # print("G - H nodes:", gnv - hnv)
243
+ nv = self.nv
244
+ some_nodes = self.n_its(range(5, 12))
245
+ assert nv - some_nodes == self.n_its(range(5))
246
+ assert some_nodes - nv == self.n_its(range(9, 12))
247
+
248
+
249
+ class TestNodeDataViewSetOps(TestNodeViewSetOps):
250
+ @classmethod
251
+ def setup_class(cls):
252
+ cls.G = nx.path_graph(9)
253
+ cls.G.nodes[3]["foo"] = "bar"
254
+ cls.nv = cls.G.nodes.data("foo")
255
+
256
+ def n_its(self, nodes):
257
+ return {(node, "bar" if node == 3 else None) for node in nodes}
258
+
259
+
260
+ class TestNodeDataViewDefaultSetOps(TestNodeDataViewSetOps):
261
+ @classmethod
262
+ def setup_class(cls):
263
+ cls.G = nx.path_graph(9)
264
+ cls.G.nodes[3]["foo"] = "bar"
265
+ cls.nv = cls.G.nodes.data("foo", default=1)
266
+
267
+ def n_its(self, nodes):
268
+ return {(node, "bar" if node == 3 else 1) for node in nodes}
269
+
270
+
271
+ # Edges Data View
272
+ class TestEdgeDataView:
273
+ @classmethod
274
+ def setup_class(cls):
275
+ cls.G = nx.path_graph(9)
276
+ cls.eview = nx.reportviews.EdgeView
277
+
278
+ def test_pickle(self):
279
+ import pickle
280
+
281
+ ev = self.eview(self.G)(data=True)
282
+ pev = pickle.loads(pickle.dumps(ev, -1))
283
+ assert list(ev) == list(pev)
284
+ assert ev.__slots__ == pev.__slots__
285
+
286
+ def modify_edge(self, G, e, **kwds):
287
+ G._adj[e[0]][e[1]].update(kwds)
288
+
289
+ def test_str(self):
290
+ ev = self.eview(self.G)(data=True)
291
+ rep = str([(n, n + 1, {}) for n in range(8)])
292
+ assert str(ev) == rep
293
+
294
+ def test_repr(self):
295
+ ev = self.eview(self.G)(data=True)
296
+ rep = (
297
+ "EdgeDataView([(0, 1, {}), (1, 2, {}), "
298
+ + "(2, 3, {}), (3, 4, {}), "
299
+ + "(4, 5, {}), (5, 6, {}), "
300
+ + "(6, 7, {}), (7, 8, {})])"
301
+ )
302
+ assert repr(ev) == rep
303
+
304
+ def test_iterdata(self):
305
+ G = self.G.copy()
306
+ evr = self.eview(G)
307
+ ev = evr(data=True)
308
+ ev_def = evr(data="foo", default=1)
309
+
310
+ for u, v, d in ev:
311
+ pass
312
+ assert d == {}
313
+
314
+ for u, v, wt in ev_def:
315
+ pass
316
+ assert wt == 1
317
+
318
+ self.modify_edge(G, (2, 3), foo="bar")
319
+ for e in ev:
320
+ assert len(e) == 3
321
+ if set(e[:2]) == {2, 3}:
322
+ assert e[2] == {"foo": "bar"}
323
+ checked = True
324
+ else:
325
+ assert e[2] == {}
326
+ assert checked
327
+
328
+ for e in ev_def:
329
+ assert len(e) == 3
330
+ if set(e[:2]) == {2, 3}:
331
+ assert e[2] == "bar"
332
+ checked_wt = True
333
+ else:
334
+ assert e[2] == 1
335
+ assert checked_wt
336
+
337
+ def test_iter(self):
338
+ evr = self.eview(self.G)
339
+ ev = evr()
340
+ for u, v in ev:
341
+ pass
342
+ iev = iter(ev)
343
+ assert next(iev) == (0, 1)
344
+ assert iter(ev) != ev
345
+ assert iter(iev) == iev
346
+
347
+ def test_contains(self):
348
+ evr = self.eview(self.G)
349
+ ev = evr()
350
+ if self.G.is_directed():
351
+ assert (1, 2) in ev and (2, 1) not in ev
352
+ else:
353
+ assert (1, 2) in ev and (2, 1) in ev
354
+ assert (1, 4) not in ev
355
+ assert (1, 90) not in ev
356
+ assert (90, 1) not in ev
357
+
358
+ def test_contains_with_nbunch(self):
359
+ evr = self.eview(self.G)
360
+ ev = evr(nbunch=[0, 2])
361
+ if self.G.is_directed():
362
+ assert (0, 1) in ev
363
+ assert (1, 2) not in ev
364
+ assert (2, 3) in ev
365
+ else:
366
+ assert (0, 1) in ev
367
+ assert (1, 2) in ev
368
+ assert (2, 3) in ev
369
+ assert (3, 4) not in ev
370
+ assert (4, 5) not in ev
371
+ assert (5, 6) not in ev
372
+ assert (7, 8) not in ev
373
+ assert (8, 9) not in ev
374
+
375
+ def test_len(self):
376
+ evr = self.eview(self.G)
377
+ ev = evr(data="foo")
378
+ assert len(ev) == 8
379
+ assert len(evr(1)) == 2
380
+ assert len(evr([1, 2, 3])) == 4
381
+
382
+ assert len(self.G.edges(1)) == 2
383
+ assert len(self.G.edges()) == 8
384
+ assert len(self.G.edges) == 8
385
+
386
+ H = self.G.copy()
387
+ H.add_edge(1, 1)
388
+ assert len(H.edges(1)) == 3
389
+ assert len(H.edges()) == 9
390
+ assert len(H.edges) == 9
391
+
392
+
393
+ class TestOutEdgeDataView(TestEdgeDataView):
394
+ @classmethod
395
+ def setup_class(cls):
396
+ cls.G = nx.path_graph(9, create_using=nx.DiGraph())
397
+ cls.eview = nx.reportviews.OutEdgeView
398
+
399
+ def test_repr(self):
400
+ ev = self.eview(self.G)(data=True)
401
+ rep = (
402
+ "OutEdgeDataView([(0, 1, {}), (1, 2, {}), "
403
+ + "(2, 3, {}), (3, 4, {}), "
404
+ + "(4, 5, {}), (5, 6, {}), "
405
+ + "(6, 7, {}), (7, 8, {})])"
406
+ )
407
+ assert repr(ev) == rep
408
+
409
+ def test_len(self):
410
+ evr = self.eview(self.G)
411
+ ev = evr(data="foo")
412
+ assert len(ev) == 8
413
+ assert len(evr(1)) == 1
414
+ assert len(evr([1, 2, 3])) == 3
415
+
416
+ assert len(self.G.edges(1)) == 1
417
+ assert len(self.G.edges()) == 8
418
+ assert len(self.G.edges) == 8
419
+
420
+ H = self.G.copy()
421
+ H.add_edge(1, 1)
422
+ assert len(H.edges(1)) == 2
423
+ assert len(H.edges()) == 9
424
+ assert len(H.edges) == 9
425
+
426
+ def test_contains_with_nbunch(self):
427
+ evr = self.eview(self.G)
428
+ ev = evr(nbunch=[0, 2])
429
+ assert (0, 1) in ev
430
+ assert (1, 2) not in ev
431
+ assert (2, 3) in ev
432
+ assert (3, 4) not in ev
433
+ assert (4, 5) not in ev
434
+ assert (5, 6) not in ev
435
+ assert (7, 8) not in ev
436
+ assert (8, 9) not in ev
437
+
438
+
439
+ class TestInEdgeDataView(TestOutEdgeDataView):
440
+ @classmethod
441
+ def setup_class(cls):
442
+ cls.G = nx.path_graph(9, create_using=nx.DiGraph())
443
+ cls.eview = nx.reportviews.InEdgeView
444
+
445
+ def test_repr(self):
446
+ ev = self.eview(self.G)(data=True)
447
+ rep = (
448
+ "InEdgeDataView([(0, 1, {}), (1, 2, {}), "
449
+ + "(2, 3, {}), (3, 4, {}), "
450
+ + "(4, 5, {}), (5, 6, {}), "
451
+ + "(6, 7, {}), (7, 8, {})])"
452
+ )
453
+ assert repr(ev) == rep
454
+
455
+ def test_contains_with_nbunch(self):
456
+ evr = self.eview(self.G)
457
+ ev = evr(nbunch=[0, 2])
458
+ assert (0, 1) not in ev
459
+ assert (1, 2) in ev
460
+ assert (2, 3) not in ev
461
+ assert (3, 4) not in ev
462
+ assert (4, 5) not in ev
463
+ assert (5, 6) not in ev
464
+ assert (7, 8) not in ev
465
+ assert (8, 9) not in ev
466
+
467
+
468
+ class TestMultiEdgeDataView(TestEdgeDataView):
469
+ @classmethod
470
+ def setup_class(cls):
471
+ cls.G = nx.path_graph(9, create_using=nx.MultiGraph())
472
+ cls.eview = nx.reportviews.MultiEdgeView
473
+
474
+ def modify_edge(self, G, e, **kwds):
475
+ G._adj[e[0]][e[1]][0].update(kwds)
476
+
477
+ def test_repr(self):
478
+ ev = self.eview(self.G)(data=True)
479
+ rep = (
480
+ "MultiEdgeDataView([(0, 1, {}), (1, 2, {}), "
481
+ + "(2, 3, {}), (3, 4, {}), "
482
+ + "(4, 5, {}), (5, 6, {}), "
483
+ + "(6, 7, {}), (7, 8, {})])"
484
+ )
485
+ assert repr(ev) == rep
486
+
487
+ def test_contains_with_nbunch(self):
488
+ evr = self.eview(self.G)
489
+ ev = evr(nbunch=[0, 2])
490
+ assert (0, 1) in ev
491
+ assert (1, 2) in ev
492
+ assert (2, 3) in ev
493
+ assert (3, 4) not in ev
494
+ assert (4, 5) not in ev
495
+ assert (5, 6) not in ev
496
+ assert (7, 8) not in ev
497
+ assert (8, 9) not in ev
498
+
499
+
500
+ class TestOutMultiEdgeDataView(TestOutEdgeDataView):
501
+ @classmethod
502
+ def setup_class(cls):
503
+ cls.G = nx.path_graph(9, create_using=nx.MultiDiGraph())
504
+ cls.eview = nx.reportviews.OutMultiEdgeView
505
+
506
+ def modify_edge(self, G, e, **kwds):
507
+ G._adj[e[0]][e[1]][0].update(kwds)
508
+
509
+ def test_repr(self):
510
+ ev = self.eview(self.G)(data=True)
511
+ rep = (
512
+ "OutMultiEdgeDataView([(0, 1, {}), (1, 2, {}), "
513
+ + "(2, 3, {}), (3, 4, {}), "
514
+ + "(4, 5, {}), (5, 6, {}), "
515
+ + "(6, 7, {}), (7, 8, {})])"
516
+ )
517
+ assert repr(ev) == rep
518
+
519
+ def test_contains_with_nbunch(self):
520
+ evr = self.eview(self.G)
521
+ ev = evr(nbunch=[0, 2])
522
+ assert (0, 1) in ev
523
+ assert (1, 2) not in ev
524
+ assert (2, 3) in ev
525
+ assert (3, 4) not in ev
526
+ assert (4, 5) not in ev
527
+ assert (5, 6) not in ev
528
+ assert (7, 8) not in ev
529
+ assert (8, 9) not in ev
530
+
531
+
532
+ class TestInMultiEdgeDataView(TestOutMultiEdgeDataView):
533
+ @classmethod
534
+ def setup_class(cls):
535
+ cls.G = nx.path_graph(9, create_using=nx.MultiDiGraph())
536
+ cls.eview = nx.reportviews.InMultiEdgeView
537
+
538
+ def test_repr(self):
539
+ ev = self.eview(self.G)(data=True)
540
+ rep = (
541
+ "InMultiEdgeDataView([(0, 1, {}), (1, 2, {}), "
542
+ + "(2, 3, {}), (3, 4, {}), "
543
+ + "(4, 5, {}), (5, 6, {}), "
544
+ + "(6, 7, {}), (7, 8, {})])"
545
+ )
546
+ assert repr(ev) == rep
547
+
548
+ def test_contains_with_nbunch(self):
549
+ evr = self.eview(self.G)
550
+ ev = evr(nbunch=[0, 2])
551
+ assert (0, 1) not in ev
552
+ assert (1, 2) in ev
553
+ assert (2, 3) not in ev
554
+ assert (3, 4) not in ev
555
+ assert (4, 5) not in ev
556
+ assert (5, 6) not in ev
557
+ assert (7, 8) not in ev
558
+ assert (8, 9) not in ev
559
+
560
+
561
+ # Edge Views
562
+ class TestEdgeView:
563
+ @classmethod
564
+ def setup_class(cls):
565
+ cls.G = nx.path_graph(9)
566
+ cls.eview = nx.reportviews.EdgeView
567
+
568
+ def test_pickle(self):
569
+ import pickle
570
+
571
+ ev = self.eview(self.G)
572
+ pev = pickle.loads(pickle.dumps(ev, -1))
573
+ assert ev == pev
574
+ assert ev.__slots__ == pev.__slots__
575
+
576
+ def modify_edge(self, G, e, **kwds):
577
+ G._adj[e[0]][e[1]].update(kwds)
578
+
579
+ def test_str(self):
580
+ ev = self.eview(self.G)
581
+ rep = str([(n, n + 1) for n in range(8)])
582
+ assert str(ev) == rep
583
+
584
+ def test_repr(self):
585
+ ev = self.eview(self.G)
586
+ rep = (
587
+ "EdgeView([(0, 1), (1, 2), (2, 3), (3, 4), "
588
+ + "(4, 5), (5, 6), (6, 7), (7, 8)])"
589
+ )
590
+ assert repr(ev) == rep
591
+
592
+ def test_getitem(self):
593
+ G = self.G.copy()
594
+ ev = G.edges
595
+ G.edges[0, 1]["foo"] = "bar"
596
+ assert ev[0, 1] == {"foo": "bar"}
597
+
598
+ # slicing
599
+ with pytest.raises(nx.NetworkXError):
600
+ G.edges[0:5]
601
+
602
+ def test_call(self):
603
+ ev = self.eview(self.G)
604
+ assert id(ev) == id(ev())
605
+ assert id(ev) == id(ev(data=False))
606
+ assert id(ev) != id(ev(data=True))
607
+ assert id(ev) != id(ev(nbunch=1))
608
+
609
+ def test_data(self):
610
+ ev = self.eview(self.G)
611
+ assert id(ev) != id(ev.data())
612
+ assert id(ev) == id(ev.data(data=False))
613
+ assert id(ev) != id(ev.data(data=True))
614
+ assert id(ev) != id(ev.data(nbunch=1))
615
+
616
+ def test_iter(self):
617
+ ev = self.eview(self.G)
618
+ for u, v in ev:
619
+ pass
620
+ iev = iter(ev)
621
+ assert next(iev) == (0, 1)
622
+ assert iter(ev) != ev
623
+ assert iter(iev) == iev
624
+
625
+ def test_contains(self):
626
+ ev = self.eview(self.G)
627
+ edv = ev()
628
+ if self.G.is_directed():
629
+ assert (1, 2) in ev and (2, 1) not in ev
630
+ assert (1, 2) in edv and (2, 1) not in edv
631
+ else:
632
+ assert (1, 2) in ev and (2, 1) in ev
633
+ assert (1, 2) in edv and (2, 1) in edv
634
+ assert (1, 4) not in ev
635
+ assert (1, 4) not in edv
636
+ # edge not in graph
637
+ assert (1, 90) not in ev
638
+ assert (90, 1) not in ev
639
+ assert (1, 90) not in edv
640
+ assert (90, 1) not in edv
641
+
642
+ def test_contains_with_nbunch(self):
643
+ ev = self.eview(self.G)
644
+ evn = ev(nbunch=[0, 2])
645
+ assert (0, 1) in evn
646
+ assert (1, 2) in evn
647
+ assert (2, 3) in evn
648
+ assert (3, 4) not in evn
649
+ assert (4, 5) not in evn
650
+ assert (5, 6) not in evn
651
+ assert (7, 8) not in evn
652
+ assert (8, 9) not in evn
653
+
654
+ def test_len(self):
655
+ ev = self.eview(self.G)
656
+ num_ed = 9 if self.G.is_multigraph() else 8
657
+ assert len(ev) == num_ed
658
+
659
+ H = self.G.copy()
660
+ H.add_edge(1, 1)
661
+ assert len(H.edges(1)) == 3 + H.is_multigraph() - H.is_directed()
662
+ assert len(H.edges()) == num_ed + 1
663
+ assert len(H.edges) == num_ed + 1
664
+
665
+ def test_and(self):
666
+ # print("G & H edges:", gnv & hnv)
667
+ ev = self.eview(self.G)
668
+ some_edges = {(0, 1), (1, 0), (0, 2)}
669
+ if self.G.is_directed():
670
+ assert some_edges & ev, {(0, 1)}
671
+ assert ev & some_edges, {(0, 1)}
672
+ else:
673
+ assert ev & some_edges == {(0, 1), (1, 0)}
674
+ assert some_edges & ev == {(0, 1), (1, 0)}
675
+ return
676
+
677
+ def test_or(self):
678
+ # print("G | H edges:", gnv | hnv)
679
+ ev = self.eview(self.G)
680
+ some_edges = {(0, 1), (1, 0), (0, 2)}
681
+ result1 = {(n, n + 1) for n in range(8)}
682
+ result1.update(some_edges)
683
+ result2 = {(n + 1, n) for n in range(8)}
684
+ result2.update(some_edges)
685
+ assert (ev | some_edges) in (result1, result2)
686
+ assert (some_edges | ev) in (result1, result2)
687
+
688
+ def test_xor(self):
689
+ # print("G ^ H edges:", gnv ^ hnv)
690
+ ev = self.eview(self.G)
691
+ some_edges = {(0, 1), (1, 0), (0, 2)}
692
+ if self.G.is_directed():
693
+ result = {(n, n + 1) for n in range(1, 8)}
694
+ result.update({(1, 0), (0, 2)})
695
+ assert ev ^ some_edges == result
696
+ else:
697
+ result = {(n, n + 1) for n in range(1, 8)}
698
+ result.update({(0, 2)})
699
+ assert ev ^ some_edges == result
700
+ return
701
+
702
+ def test_sub(self):
703
+ # print("G - H edges:", gnv - hnv)
704
+ ev = self.eview(self.G)
705
+ some_edges = {(0, 1), (1, 0), (0, 2)}
706
+ result = {(n, n + 1) for n in range(8)}
707
+ result.remove((0, 1))
708
+ assert ev - some_edges, result
709
+
710
+
711
+ class TestOutEdgeView(TestEdgeView):
712
+ @classmethod
713
+ def setup_class(cls):
714
+ cls.G = nx.path_graph(9, nx.DiGraph())
715
+ cls.eview = nx.reportviews.OutEdgeView
716
+
717
+ def test_repr(self):
718
+ ev = self.eview(self.G)
719
+ rep = (
720
+ "OutEdgeView([(0, 1), (1, 2), (2, 3), (3, 4), "
721
+ + "(4, 5), (5, 6), (6, 7), (7, 8)])"
722
+ )
723
+ assert repr(ev) == rep
724
+
725
+ def test_contains_with_nbunch(self):
726
+ ev = self.eview(self.G)
727
+ evn = ev(nbunch=[0, 2])
728
+ assert (0, 1) in evn
729
+ assert (1, 2) not in evn
730
+ assert (2, 3) in evn
731
+ assert (3, 4) not in evn
732
+ assert (4, 5) not in evn
733
+ assert (5, 6) not in evn
734
+ assert (7, 8) not in evn
735
+ assert (8, 9) not in evn
736
+
737
+
738
+ class TestInEdgeView(TestEdgeView):
739
+ @classmethod
740
+ def setup_class(cls):
741
+ cls.G = nx.path_graph(9, nx.DiGraph())
742
+ cls.eview = nx.reportviews.InEdgeView
743
+
744
+ def test_repr(self):
745
+ ev = self.eview(self.G)
746
+ rep = (
747
+ "InEdgeView([(0, 1), (1, 2), (2, 3), (3, 4), "
748
+ + "(4, 5), (5, 6), (6, 7), (7, 8)])"
749
+ )
750
+ assert repr(ev) == rep
751
+
752
+ def test_contains_with_nbunch(self):
753
+ ev = self.eview(self.G)
754
+ evn = ev(nbunch=[0, 2])
755
+ assert (0, 1) not in evn
756
+ assert (1, 2) in evn
757
+ assert (2, 3) not in evn
758
+ assert (3, 4) not in evn
759
+ assert (4, 5) not in evn
760
+ assert (5, 6) not in evn
761
+ assert (7, 8) not in evn
762
+ assert (8, 9) not in evn
763
+
764
+
765
+ class TestMultiEdgeView(TestEdgeView):
766
+ @classmethod
767
+ def setup_class(cls):
768
+ cls.G = nx.path_graph(9, nx.MultiGraph())
769
+ cls.G.add_edge(1, 2, key=3, foo="bar")
770
+ cls.eview = nx.reportviews.MultiEdgeView
771
+
772
+ def modify_edge(self, G, e, **kwds):
773
+ if len(e) == 2:
774
+ e = e + (0,)
775
+ G._adj[e[0]][e[1]][e[2]].update(kwds)
776
+
777
+ def test_str(self):
778
+ ev = self.eview(self.G)
779
+ replist = [(n, n + 1, 0) for n in range(8)]
780
+ replist.insert(2, (1, 2, 3))
781
+ rep = str(replist)
782
+ assert str(ev) == rep
783
+
784
+ def test_getitem(self):
785
+ G = self.G.copy()
786
+ ev = G.edges
787
+ G.edges[0, 1, 0]["foo"] = "bar"
788
+ assert ev[0, 1, 0] == {"foo": "bar"}
789
+
790
+ # slicing
791
+ with pytest.raises(nx.NetworkXError):
792
+ G.edges[0:5]
793
+
794
+ def test_repr(self):
795
+ ev = self.eview(self.G)
796
+ rep = (
797
+ "MultiEdgeView([(0, 1, 0), (1, 2, 0), (1, 2, 3), (2, 3, 0), "
798
+ + "(3, 4, 0), (4, 5, 0), (5, 6, 0), (6, 7, 0), (7, 8, 0)])"
799
+ )
800
+ assert repr(ev) == rep
801
+
802
+ def test_call(self):
803
+ ev = self.eview(self.G)
804
+ assert id(ev) == id(ev(keys=True))
805
+ assert id(ev) == id(ev(data=False, keys=True))
806
+ assert id(ev) != id(ev(keys=False))
807
+ assert id(ev) != id(ev(data=True))
808
+ assert id(ev) != id(ev(nbunch=1))
809
+
810
+ def test_data(self):
811
+ ev = self.eview(self.G)
812
+ assert id(ev) != id(ev.data())
813
+ assert id(ev) == id(ev.data(data=False, keys=True))
814
+ assert id(ev) != id(ev.data(keys=False))
815
+ assert id(ev) != id(ev.data(data=True))
816
+ assert id(ev) != id(ev.data(nbunch=1))
817
+
818
+ def test_iter(self):
819
+ ev = self.eview(self.G)
820
+ for u, v, k in ev:
821
+ pass
822
+ iev = iter(ev)
823
+ assert next(iev) == (0, 1, 0)
824
+ assert iter(ev) != ev
825
+ assert iter(iev) == iev
826
+
827
+ def test_iterkeys(self):
828
+ G = self.G
829
+ evr = self.eview(G)
830
+ ev = evr(keys=True)
831
+ for u, v, k in ev:
832
+ pass
833
+ assert k == 0
834
+ ev = evr(keys=True, data="foo", default=1)
835
+ for u, v, k, wt in ev:
836
+ pass
837
+ assert wt == 1
838
+
839
+ self.modify_edge(G, (2, 3, 0), foo="bar")
840
+ ev = evr(keys=True, data=True)
841
+ for e in ev:
842
+ assert len(e) == 4
843
+ print("edge:", e)
844
+ if set(e[:2]) == {2, 3}:
845
+ print(self.G._adj[2][3])
846
+ assert e[2] == 0
847
+ assert e[3] == {"foo": "bar"}
848
+ checked = True
849
+ elif set(e[:3]) == {1, 2, 3}:
850
+ assert e[2] == 3
851
+ assert e[3] == {"foo": "bar"}
852
+ checked_multi = True
853
+ else:
854
+ assert e[2] == 0
855
+ assert e[3] == {}
856
+ assert checked
857
+ assert checked_multi
858
+ ev = evr(keys=True, data="foo", default=1)
859
+ for e in ev:
860
+ if set(e[:2]) == {1, 2} and e[2] == 3:
861
+ assert e[3] == "bar"
862
+ if set(e[:2]) == {1, 2} and e[2] == 0:
863
+ assert e[3] == 1
864
+ if set(e[:2]) == {2, 3}:
865
+ assert e[2] == 0
866
+ assert e[3] == "bar"
867
+ assert len(e) == 4
868
+ checked_wt = True
869
+ assert checked_wt
870
+ ev = evr(keys=True)
871
+ for e in ev:
872
+ assert len(e) == 3
873
+ elist = sorted([(i, i + 1, 0) for i in range(8)] + [(1, 2, 3)])
874
+ assert sorted(ev) == elist
875
+ # test that the keyword arguments are passed correctly
876
+ ev = evr((1, 2), "foo", keys=True, default=1)
877
+ with pytest.raises(TypeError):
878
+ evr((1, 2), "foo", True, 1)
879
+ with pytest.raises(TypeError):
880
+ evr((1, 2), "foo", True, default=1)
881
+ for e in ev:
882
+ if set(e[:2]) == {1, 2}:
883
+ assert e[2] in {0, 3}
884
+ if e[2] == 3:
885
+ assert e[3] == "bar"
886
+ else: # e[2] == 0
887
+ assert e[3] == 1
888
+ if G.is_directed():
889
+ assert len(list(ev)) == 3
890
+ else:
891
+ assert len(list(ev)) == 4
892
+
893
+ def test_or(self):
894
+ # print("G | H edges:", gnv | hnv)
895
+ ev = self.eview(self.G)
896
+ some_edges = {(0, 1, 0), (1, 0, 0), (0, 2, 0)}
897
+ result = {(n, n + 1, 0) for n in range(8)}
898
+ result.update(some_edges)
899
+ result.update({(1, 2, 3)})
900
+ assert ev | some_edges == result
901
+ assert some_edges | ev == result
902
+
903
+ def test_sub(self):
904
+ # print("G - H edges:", gnv - hnv)
905
+ ev = self.eview(self.G)
906
+ some_edges = {(0, 1, 0), (1, 0, 0), (0, 2, 0)}
907
+ result = {(n, n + 1, 0) for n in range(8)}
908
+ result.remove((0, 1, 0))
909
+ result.update({(1, 2, 3)})
910
+ assert ev - some_edges, result
911
+ assert some_edges - ev, result
912
+
913
+ def test_xor(self):
914
+ # print("G ^ H edges:", gnv ^ hnv)
915
+ ev = self.eview(self.G)
916
+ some_edges = {(0, 1, 0), (1, 0, 0), (0, 2, 0)}
917
+ if self.G.is_directed():
918
+ result = {(n, n + 1, 0) for n in range(1, 8)}
919
+ result.update({(1, 0, 0), (0, 2, 0), (1, 2, 3)})
920
+ assert ev ^ some_edges == result
921
+ assert some_edges ^ ev == result
922
+ else:
923
+ result = {(n, n + 1, 0) for n in range(1, 8)}
924
+ result.update({(0, 2, 0), (1, 2, 3)})
925
+ assert ev ^ some_edges == result
926
+ assert some_edges ^ ev == result
927
+
928
+ def test_and(self):
929
+ # print("G & H edges:", gnv & hnv)
930
+ ev = self.eview(self.G)
931
+ some_edges = {(0, 1, 0), (1, 0, 0), (0, 2, 0)}
932
+ if self.G.is_directed():
933
+ assert ev & some_edges == {(0, 1, 0)}
934
+ assert some_edges & ev == {(0, 1, 0)}
935
+ else:
936
+ assert ev & some_edges == {(0, 1, 0), (1, 0, 0)}
937
+ assert some_edges & ev == {(0, 1, 0), (1, 0, 0)}
938
+
939
+ def test_contains_with_nbunch(self):
940
+ ev = self.eview(self.G)
941
+ evn = ev(nbunch=[0, 2])
942
+ assert (0, 1) in evn
943
+ assert (1, 2) in evn
944
+ assert (2, 3) in evn
945
+ assert (3, 4) not in evn
946
+ assert (4, 5) not in evn
947
+ assert (5, 6) not in evn
948
+ assert (7, 8) not in evn
949
+ assert (8, 9) not in evn
950
+
951
+
952
+ class TestOutMultiEdgeView(TestMultiEdgeView):
953
+ @classmethod
954
+ def setup_class(cls):
955
+ cls.G = nx.path_graph(9, nx.MultiDiGraph())
956
+ cls.G.add_edge(1, 2, key=3, foo="bar")
957
+ cls.eview = nx.reportviews.OutMultiEdgeView
958
+
959
+ def modify_edge(self, G, e, **kwds):
960
+ if len(e) == 2:
961
+ e = e + (0,)
962
+ G._adj[e[0]][e[1]][e[2]].update(kwds)
963
+
964
+ def test_repr(self):
965
+ ev = self.eview(self.G)
966
+ rep = (
967
+ "OutMultiEdgeView([(0, 1, 0), (1, 2, 0), (1, 2, 3), (2, 3, 0),"
968
+ + " (3, 4, 0), (4, 5, 0), (5, 6, 0), (6, 7, 0), (7, 8, 0)])"
969
+ )
970
+ assert repr(ev) == rep
971
+
972
+ def test_contains_with_nbunch(self):
973
+ ev = self.eview(self.G)
974
+ evn = ev(nbunch=[0, 2])
975
+ assert (0, 1) in evn
976
+ assert (1, 2) not in evn
977
+ assert (2, 3) in evn
978
+ assert (3, 4) not in evn
979
+ assert (4, 5) not in evn
980
+ assert (5, 6) not in evn
981
+ assert (7, 8) not in evn
982
+ assert (8, 9) not in evn
983
+
984
+
985
+ class TestInMultiEdgeView(TestMultiEdgeView):
986
+ @classmethod
987
+ def setup_class(cls):
988
+ cls.G = nx.path_graph(9, nx.MultiDiGraph())
989
+ cls.G.add_edge(1, 2, key=3, foo="bar")
990
+ cls.eview = nx.reportviews.InMultiEdgeView
991
+
992
+ def modify_edge(self, G, e, **kwds):
993
+ if len(e) == 2:
994
+ e = e + (0,)
995
+ G._adj[e[0]][e[1]][e[2]].update(kwds)
996
+
997
+ def test_repr(self):
998
+ ev = self.eview(self.G)
999
+ rep = (
1000
+ "InMultiEdgeView([(0, 1, 0), (1, 2, 0), (1, 2, 3), (2, 3, 0), "
1001
+ + "(3, 4, 0), (4, 5, 0), (5, 6, 0), (6, 7, 0), (7, 8, 0)])"
1002
+ )
1003
+ assert repr(ev) == rep
1004
+
1005
+ def test_contains_with_nbunch(self):
1006
+ ev = self.eview(self.G)
1007
+ evn = ev(nbunch=[0, 2])
1008
+ assert (0, 1) not in evn
1009
+ assert (1, 2) in evn
1010
+ assert (2, 3) not in evn
1011
+ assert (3, 4) not in evn
1012
+ assert (4, 5) not in evn
1013
+ assert (5, 6) not in evn
1014
+ assert (7, 8) not in evn
1015
+ assert (8, 9) not in evn
1016
+
1017
+
1018
+ # Degrees
1019
+ class TestDegreeView:
1020
+ GRAPH = nx.Graph
1021
+ dview = nx.reportviews.DegreeView
1022
+
1023
+ @classmethod
1024
+ def setup_class(cls):
1025
+ cls.G = nx.path_graph(6, cls.GRAPH())
1026
+ cls.G.add_edge(1, 3, foo=2)
1027
+ cls.G.add_edge(1, 3, foo=3)
1028
+
1029
+ def test_pickle(self):
1030
+ import pickle
1031
+
1032
+ deg = self.G.degree
1033
+ pdeg = pickle.loads(pickle.dumps(deg, -1))
1034
+ assert dict(deg) == dict(pdeg)
1035
+
1036
+ def test_str(self):
1037
+ dv = self.dview(self.G)
1038
+ rep = str([(0, 1), (1, 3), (2, 2), (3, 3), (4, 2), (5, 1)])
1039
+ assert str(dv) == rep
1040
+ dv = self.G.degree()
1041
+ assert str(dv) == rep
1042
+
1043
+ def test_repr(self):
1044
+ dv = self.dview(self.G)
1045
+ rep = "DegreeView({0: 1, 1: 3, 2: 2, 3: 3, 4: 2, 5: 1})"
1046
+ assert repr(dv) == rep
1047
+
1048
+ def test_iter(self):
1049
+ dv = self.dview(self.G)
1050
+ for n, d in dv:
1051
+ pass
1052
+ idv = iter(dv)
1053
+ assert iter(dv) != dv
1054
+ assert iter(idv) == idv
1055
+ assert next(idv) == (0, dv[0])
1056
+ assert next(idv) == (1, dv[1])
1057
+ # weighted
1058
+ dv = self.dview(self.G, weight="foo")
1059
+ for n, d in dv:
1060
+ pass
1061
+ idv = iter(dv)
1062
+ assert iter(dv) != dv
1063
+ assert iter(idv) == idv
1064
+ assert next(idv) == (0, dv[0])
1065
+ assert next(idv) == (1, dv[1])
1066
+
1067
+ def test_nbunch(self):
1068
+ dv = self.dview(self.G)
1069
+ dvn = dv(0)
1070
+ assert dvn == 1
1071
+ dvn = dv([2, 3])
1072
+ assert sorted(dvn) == [(2, 2), (3, 3)]
1073
+
1074
+ def test_getitem(self):
1075
+ dv = self.dview(self.G)
1076
+ assert dv[0] == 1
1077
+ assert dv[1] == 3
1078
+ assert dv[2] == 2
1079
+ assert dv[3] == 3
1080
+ dv = self.dview(self.G, weight="foo")
1081
+ assert dv[0] == 1
1082
+ assert dv[1] == 5
1083
+ assert dv[2] == 2
1084
+ assert dv[3] == 5
1085
+
1086
+ def test_weight(self):
1087
+ dv = self.dview(self.G)
1088
+ dvw = dv(0, weight="foo")
1089
+ assert dvw == 1
1090
+ dvw = dv(1, weight="foo")
1091
+ assert dvw == 5
1092
+ dvw = dv([2, 3], weight="foo")
1093
+ assert sorted(dvw) == [(2, 2), (3, 5)]
1094
+ dvd = dict(dv(weight="foo"))
1095
+ assert dvd[0] == 1
1096
+ assert dvd[1] == 5
1097
+ assert dvd[2] == 2
1098
+ assert dvd[3] == 5
1099
+
1100
+ def test_len(self):
1101
+ dv = self.dview(self.G)
1102
+ assert len(dv) == 6
1103
+
1104
+
1105
+ class TestDiDegreeView(TestDegreeView):
1106
+ GRAPH = nx.DiGraph
1107
+ dview = nx.reportviews.DiDegreeView
1108
+
1109
+ def test_repr(self):
1110
+ dv = self.G.degree()
1111
+ rep = "DiDegreeView({0: 1, 1: 3, 2: 2, 3: 3, 4: 2, 5: 1})"
1112
+ assert repr(dv) == rep
1113
+
1114
+
1115
+ class TestOutDegreeView(TestDegreeView):
1116
+ GRAPH = nx.DiGraph
1117
+ dview = nx.reportviews.OutDegreeView
1118
+
1119
+ def test_str(self):
1120
+ dv = self.dview(self.G)
1121
+ rep = str([(0, 1), (1, 2), (2, 1), (3, 1), (4, 1), (5, 0)])
1122
+ assert str(dv) == rep
1123
+ dv = self.G.out_degree()
1124
+ assert str(dv) == rep
1125
+
1126
+ def test_repr(self):
1127
+ dv = self.G.out_degree()
1128
+ rep = "OutDegreeView({0: 1, 1: 2, 2: 1, 3: 1, 4: 1, 5: 0})"
1129
+ assert repr(dv) == rep
1130
+
1131
+ def test_nbunch(self):
1132
+ dv = self.dview(self.G)
1133
+ dvn = dv(0)
1134
+ assert dvn == 1
1135
+ dvn = dv([2, 3])
1136
+ assert sorted(dvn) == [(2, 1), (3, 1)]
1137
+
1138
+ def test_getitem(self):
1139
+ dv = self.dview(self.G)
1140
+ assert dv[0] == 1
1141
+ assert dv[1] == 2
1142
+ assert dv[2] == 1
1143
+ assert dv[3] == 1
1144
+ dv = self.dview(self.G, weight="foo")
1145
+ assert dv[0] == 1
1146
+ assert dv[1] == 4
1147
+ assert dv[2] == 1
1148
+ assert dv[3] == 1
1149
+
1150
+ def test_weight(self):
1151
+ dv = self.dview(self.G)
1152
+ dvw = dv(0, weight="foo")
1153
+ assert dvw == 1
1154
+ dvw = dv(1, weight="foo")
1155
+ assert dvw == 4
1156
+ dvw = dv([2, 3], weight="foo")
1157
+ assert sorted(dvw) == [(2, 1), (3, 1)]
1158
+ dvd = dict(dv(weight="foo"))
1159
+ assert dvd[0] == 1
1160
+ assert dvd[1] == 4
1161
+ assert dvd[2] == 1
1162
+ assert dvd[3] == 1
1163
+
1164
+
1165
+ class TestInDegreeView(TestDegreeView):
1166
+ GRAPH = nx.DiGraph
1167
+ dview = nx.reportviews.InDegreeView
1168
+
1169
+ def test_str(self):
1170
+ dv = self.dview(self.G)
1171
+ rep = str([(0, 0), (1, 1), (2, 1), (3, 2), (4, 1), (5, 1)])
1172
+ assert str(dv) == rep
1173
+ dv = self.G.in_degree()
1174
+ assert str(dv) == rep
1175
+
1176
+ def test_repr(self):
1177
+ dv = self.G.in_degree()
1178
+ rep = "InDegreeView({0: 0, 1: 1, 2: 1, 3: 2, 4: 1, 5: 1})"
1179
+ assert repr(dv) == rep
1180
+
1181
+ def test_nbunch(self):
1182
+ dv = self.dview(self.G)
1183
+ dvn = dv(0)
1184
+ assert dvn == 0
1185
+ dvn = dv([2, 3])
1186
+ assert sorted(dvn) == [(2, 1), (3, 2)]
1187
+
1188
+ def test_getitem(self):
1189
+ dv = self.dview(self.G)
1190
+ assert dv[0] == 0
1191
+ assert dv[1] == 1
1192
+ assert dv[2] == 1
1193
+ assert dv[3] == 2
1194
+ dv = self.dview(self.G, weight="foo")
1195
+ assert dv[0] == 0
1196
+ assert dv[1] == 1
1197
+ assert dv[2] == 1
1198
+ assert dv[3] == 4
1199
+
1200
+ def test_weight(self):
1201
+ dv = self.dview(self.G)
1202
+ dvw = dv(0, weight="foo")
1203
+ assert dvw == 0
1204
+ dvw = dv(1, weight="foo")
1205
+ assert dvw == 1
1206
+ dvw = dv([2, 3], weight="foo")
1207
+ assert sorted(dvw) == [(2, 1), (3, 4)]
1208
+ dvd = dict(dv(weight="foo"))
1209
+ assert dvd[0] == 0
1210
+ assert dvd[1] == 1
1211
+ assert dvd[2] == 1
1212
+ assert dvd[3] == 4
1213
+
1214
+
1215
+ class TestMultiDegreeView(TestDegreeView):
1216
+ GRAPH = nx.MultiGraph
1217
+ dview = nx.reportviews.MultiDegreeView
1218
+
1219
+ def test_str(self):
1220
+ dv = self.dview(self.G)
1221
+ rep = str([(0, 1), (1, 4), (2, 2), (3, 4), (4, 2), (5, 1)])
1222
+ assert str(dv) == rep
1223
+ dv = self.G.degree()
1224
+ assert str(dv) == rep
1225
+
1226
+ def test_repr(self):
1227
+ dv = self.G.degree()
1228
+ rep = "MultiDegreeView({0: 1, 1: 4, 2: 2, 3: 4, 4: 2, 5: 1})"
1229
+ assert repr(dv) == rep
1230
+
1231
+ def test_nbunch(self):
1232
+ dv = self.dview(self.G)
1233
+ dvn = dv(0)
1234
+ assert dvn == 1
1235
+ dvn = dv([2, 3])
1236
+ assert sorted(dvn) == [(2, 2), (3, 4)]
1237
+
1238
+ def test_getitem(self):
1239
+ dv = self.dview(self.G)
1240
+ assert dv[0] == 1
1241
+ assert dv[1] == 4
1242
+ assert dv[2] == 2
1243
+ assert dv[3] == 4
1244
+ dv = self.dview(self.G, weight="foo")
1245
+ assert dv[0] == 1
1246
+ assert dv[1] == 7
1247
+ assert dv[2] == 2
1248
+ assert dv[3] == 7
1249
+
1250
+ def test_weight(self):
1251
+ dv = self.dview(self.G)
1252
+ dvw = dv(0, weight="foo")
1253
+ assert dvw == 1
1254
+ dvw = dv(1, weight="foo")
1255
+ assert dvw == 7
1256
+ dvw = dv([2, 3], weight="foo")
1257
+ assert sorted(dvw) == [(2, 2), (3, 7)]
1258
+ dvd = dict(dv(weight="foo"))
1259
+ assert dvd[0] == 1
1260
+ assert dvd[1] == 7
1261
+ assert dvd[2] == 2
1262
+ assert dvd[3] == 7
1263
+
1264
+
1265
+ class TestDiMultiDegreeView(TestMultiDegreeView):
1266
+ GRAPH = nx.MultiDiGraph
1267
+ dview = nx.reportviews.DiMultiDegreeView
1268
+
1269
+ def test_repr(self):
1270
+ dv = self.G.degree()
1271
+ rep = "DiMultiDegreeView({0: 1, 1: 4, 2: 2, 3: 4, 4: 2, 5: 1})"
1272
+ assert repr(dv) == rep
1273
+
1274
+
1275
+ class TestOutMultiDegreeView(TestDegreeView):
1276
+ GRAPH = nx.MultiDiGraph
1277
+ dview = nx.reportviews.OutMultiDegreeView
1278
+
1279
+ def test_str(self):
1280
+ dv = self.dview(self.G)
1281
+ rep = str([(0, 1), (1, 3), (2, 1), (3, 1), (4, 1), (5, 0)])
1282
+ assert str(dv) == rep
1283
+ dv = self.G.out_degree()
1284
+ assert str(dv) == rep
1285
+
1286
+ def test_repr(self):
1287
+ dv = self.G.out_degree()
1288
+ rep = "OutMultiDegreeView({0: 1, 1: 3, 2: 1, 3: 1, 4: 1, 5: 0})"
1289
+ assert repr(dv) == rep
1290
+
1291
+ def test_nbunch(self):
1292
+ dv = self.dview(self.G)
1293
+ dvn = dv(0)
1294
+ assert dvn == 1
1295
+ dvn = dv([2, 3])
1296
+ assert sorted(dvn) == [(2, 1), (3, 1)]
1297
+
1298
+ def test_getitem(self):
1299
+ dv = self.dview(self.G)
1300
+ assert dv[0] == 1
1301
+ assert dv[1] == 3
1302
+ assert dv[2] == 1
1303
+ assert dv[3] == 1
1304
+ dv = self.dview(self.G, weight="foo")
1305
+ assert dv[0] == 1
1306
+ assert dv[1] == 6
1307
+ assert dv[2] == 1
1308
+ assert dv[3] == 1
1309
+
1310
+ def test_weight(self):
1311
+ dv = self.dview(self.G)
1312
+ dvw = dv(0, weight="foo")
1313
+ assert dvw == 1
1314
+ dvw = dv(1, weight="foo")
1315
+ assert dvw == 6
1316
+ dvw = dv([2, 3], weight="foo")
1317
+ assert sorted(dvw) == [(2, 1), (3, 1)]
1318
+ dvd = dict(dv(weight="foo"))
1319
+ assert dvd[0] == 1
1320
+ assert dvd[1] == 6
1321
+ assert dvd[2] == 1
1322
+ assert dvd[3] == 1
1323
+
1324
+
1325
+ class TestInMultiDegreeView(TestDegreeView):
1326
+ GRAPH = nx.MultiDiGraph
1327
+ dview = nx.reportviews.InMultiDegreeView
1328
+
1329
+ def test_str(self):
1330
+ dv = self.dview(self.G)
1331
+ rep = str([(0, 0), (1, 1), (2, 1), (3, 3), (4, 1), (5, 1)])
1332
+ assert str(dv) == rep
1333
+ dv = self.G.in_degree()
1334
+ assert str(dv) == rep
1335
+
1336
+ def test_repr(self):
1337
+ dv = self.G.in_degree()
1338
+ rep = "InMultiDegreeView({0: 0, 1: 1, 2: 1, 3: 3, 4: 1, 5: 1})"
1339
+ assert repr(dv) == rep
1340
+
1341
+ def test_nbunch(self):
1342
+ dv = self.dview(self.G)
1343
+ dvn = dv(0)
1344
+ assert dvn == 0
1345
+ dvn = dv([2, 3])
1346
+ assert sorted(dvn) == [(2, 1), (3, 3)]
1347
+
1348
+ def test_getitem(self):
1349
+ dv = self.dview(self.G)
1350
+ assert dv[0] == 0
1351
+ assert dv[1] == 1
1352
+ assert dv[2] == 1
1353
+ assert dv[3] == 3
1354
+ dv = self.dview(self.G, weight="foo")
1355
+ assert dv[0] == 0
1356
+ assert dv[1] == 1
1357
+ assert dv[2] == 1
1358
+ assert dv[3] == 6
1359
+
1360
+ def test_weight(self):
1361
+ dv = self.dview(self.G)
1362
+ dvw = dv(0, weight="foo")
1363
+ assert dvw == 0
1364
+ dvw = dv(1, weight="foo")
1365
+ assert dvw == 1
1366
+ dvw = dv([2, 3], weight="foo")
1367
+ assert sorted(dvw) == [(2, 1), (3, 6)]
1368
+ dvd = dict(dv(weight="foo"))
1369
+ assert dvd[0] == 0
1370
+ assert dvd[1] == 1
1371
+ assert dvd[2] == 1
1372
+ assert dvd[3] == 6
1373
+
1374
+
1375
+ @pytest.mark.parametrize(
1376
+ ("reportview", "err_msg_terms"),
1377
+ (
1378
+ (rv.NodeView, "list(G.nodes"),
1379
+ (rv.NodeDataView, "list(G.nodes.data"),
1380
+ (rv.EdgeView, "list(G.edges"),
1381
+ # Directed EdgeViews
1382
+ (rv.InEdgeView, "list(G.in_edges"),
1383
+ (rv.OutEdgeView, "list(G.edges"),
1384
+ # Multi EdgeViews
1385
+ (rv.MultiEdgeView, "list(G.edges"),
1386
+ (rv.InMultiEdgeView, "list(G.in_edges"),
1387
+ (rv.OutMultiEdgeView, "list(G.edges"),
1388
+ ),
1389
+ )
1390
+ def test_slicing_reportviews(reportview, err_msg_terms):
1391
+ G = nx.complete_graph(3)
1392
+ view = reportview(G)
1393
+ with pytest.raises(nx.NetworkXError) as exc:
1394
+ view[0:2]
1395
+ errmsg = str(exc.value)
1396
+ assert type(view).__name__ in errmsg
1397
+ assert err_msg_terms in errmsg
1398
+
1399
+
1400
+ @pytest.mark.parametrize(
1401
+ "graph", [nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph]
1402
+ )
1403
+ def test_cache_dict_get_set_state(graph):
1404
+ G = nx.path_graph(5, graph())
1405
+ G.nodes, G.edges, G.adj, G.degree
1406
+ if G.is_directed():
1407
+ G.pred, G.succ, G.in_edges, G.out_edges, G.in_degree, G.out_degree
1408
+ cached_dict = G.__dict__
1409
+ assert "nodes" in cached_dict
1410
+ assert "edges" in cached_dict
1411
+ assert "adj" in cached_dict
1412
+ assert "degree" in cached_dict
1413
+ if G.is_directed():
1414
+ assert "pred" in cached_dict
1415
+ assert "succ" in cached_dict
1416
+ assert "in_edges" in cached_dict
1417
+ assert "out_edges" in cached_dict
1418
+ assert "in_degree" in cached_dict
1419
+ assert "out_degree" in cached_dict
1420
+
1421
+ # Raises error if the cached properties and views do not work
1422
+ pickle.loads(pickle.dumps(G, -1))
1423
+ deepcopy(G)
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/test_subgraphviews.py ADDED
@@ -0,0 +1,362 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+
3
+ import networkx as nx
4
+ from networkx.utils import edges_equal
5
+
6
+
7
+ class TestSubGraphView:
8
+ gview = staticmethod(nx.subgraph_view)
9
+ graph = nx.Graph
10
+ hide_edges_filter = staticmethod(nx.filters.hide_edges)
11
+ show_edges_filter = staticmethod(nx.filters.show_edges)
12
+
13
+ @classmethod
14
+ def setup_class(cls):
15
+ cls.G = nx.path_graph(9, create_using=cls.graph())
16
+ cls.hide_edges_w_hide_nodes = {(3, 4), (4, 5), (5, 6)}
17
+
18
+ def test_hidden_nodes(self):
19
+ hide_nodes = [4, 5, 111]
20
+ nodes_gone = nx.filters.hide_nodes(hide_nodes)
21
+ gview = self.gview
22
+ G = gview(self.G, filter_node=nodes_gone)
23
+ assert self.G.nodes - G.nodes == {4, 5}
24
+ assert self.G.edges - G.edges == self.hide_edges_w_hide_nodes
25
+ if G.is_directed():
26
+ assert list(G[3]) == []
27
+ assert list(G[2]) == [3]
28
+ else:
29
+ assert list(G[3]) == [2]
30
+ assert set(G[2]) == {1, 3}
31
+ pytest.raises(KeyError, G.__getitem__, 4)
32
+ pytest.raises(KeyError, G.__getitem__, 112)
33
+ pytest.raises(KeyError, G.__getitem__, 111)
34
+ assert G.degree(3) == (3 if G.is_multigraph() else 1)
35
+ assert G.size() == (7 if G.is_multigraph() else 5)
36
+
37
+ def test_hidden_edges(self):
38
+ hide_edges = [(2, 3), (8, 7), (222, 223)]
39
+ edges_gone = self.hide_edges_filter(hide_edges)
40
+ gview = self.gview
41
+ G = gview(self.G, filter_edge=edges_gone)
42
+ assert self.G.nodes == G.nodes
43
+ if G.is_directed():
44
+ assert self.G.edges - G.edges == {(2, 3)}
45
+ assert list(G[2]) == []
46
+ assert list(G.pred[3]) == []
47
+ assert list(G.pred[2]) == [1]
48
+ assert G.size() == 7
49
+ else:
50
+ assert self.G.edges - G.edges == {(2, 3), (7, 8)}
51
+ assert list(G[2]) == [1]
52
+ assert G.size() == 6
53
+ assert list(G[3]) == [4]
54
+ pytest.raises(KeyError, G.__getitem__, 221)
55
+ pytest.raises(KeyError, G.__getitem__, 222)
56
+ assert G.degree(3) == 1
57
+
58
+ def test_shown_node(self):
59
+ induced_subgraph = nx.filters.show_nodes([2, 3, 111])
60
+ gview = self.gview
61
+ G = gview(self.G, filter_node=induced_subgraph)
62
+ assert set(G.nodes) == {2, 3}
63
+ if G.is_directed():
64
+ assert list(G[3]) == []
65
+ else:
66
+ assert list(G[3]) == [2]
67
+ assert list(G[2]) == [3]
68
+ pytest.raises(KeyError, G.__getitem__, 4)
69
+ pytest.raises(KeyError, G.__getitem__, 112)
70
+ pytest.raises(KeyError, G.__getitem__, 111)
71
+ assert G.degree(3) == (3 if G.is_multigraph() else 1)
72
+ assert G.size() == (3 if G.is_multigraph() else 1)
73
+
74
+ def test_shown_edges(self):
75
+ show_edges = [(2, 3), (8, 7), (222, 223)]
76
+ edge_subgraph = self.show_edges_filter(show_edges)
77
+ G = self.gview(self.G, filter_edge=edge_subgraph)
78
+ assert self.G.nodes == G.nodes
79
+ if G.is_directed():
80
+ assert G.edges == {(2, 3)}
81
+ assert list(G[3]) == []
82
+ assert list(G[2]) == [3]
83
+ assert list(G.pred[3]) == [2]
84
+ assert list(G.pred[2]) == []
85
+ assert G.size() == 1
86
+ else:
87
+ assert G.edges == {(2, 3), (7, 8)}
88
+ assert list(G[3]) == [2]
89
+ assert list(G[2]) == [3]
90
+ assert G.size() == 2
91
+ pytest.raises(KeyError, G.__getitem__, 221)
92
+ pytest.raises(KeyError, G.__getitem__, 222)
93
+ assert G.degree(3) == 1
94
+
95
+
96
+ class TestSubDiGraphView(TestSubGraphView):
97
+ gview = staticmethod(nx.subgraph_view)
98
+ graph = nx.DiGraph
99
+ hide_edges_filter = staticmethod(nx.filters.hide_diedges)
100
+ show_edges_filter = staticmethod(nx.filters.show_diedges)
101
+ hide_edges = [(2, 3), (8, 7), (222, 223)]
102
+ excluded = {(2, 3), (3, 4), (4, 5), (5, 6)}
103
+
104
+ def test_inoutedges(self):
105
+ edges_gone = self.hide_edges_filter(self.hide_edges)
106
+ hide_nodes = [4, 5, 111]
107
+ nodes_gone = nx.filters.hide_nodes(hide_nodes)
108
+ G = self.gview(self.G, filter_node=nodes_gone, filter_edge=edges_gone)
109
+
110
+ assert self.G.in_edges - G.in_edges == self.excluded
111
+ assert self.G.out_edges - G.out_edges == self.excluded
112
+
113
+ def test_pred(self):
114
+ edges_gone = self.hide_edges_filter(self.hide_edges)
115
+ hide_nodes = [4, 5, 111]
116
+ nodes_gone = nx.filters.hide_nodes(hide_nodes)
117
+ G = self.gview(self.G, filter_node=nodes_gone, filter_edge=edges_gone)
118
+
119
+ assert list(G.pred[2]) == [1]
120
+ assert list(G.pred[6]) == []
121
+
122
+ def test_inout_degree(self):
123
+ edges_gone = self.hide_edges_filter(self.hide_edges)
124
+ hide_nodes = [4, 5, 111]
125
+ nodes_gone = nx.filters.hide_nodes(hide_nodes)
126
+ G = self.gview(self.G, filter_node=nodes_gone, filter_edge=edges_gone)
127
+
128
+ assert G.degree(2) == 1
129
+ assert G.out_degree(2) == 0
130
+ assert G.in_degree(2) == 1
131
+ assert G.size() == 4
132
+
133
+
134
+ # multigraph
135
+ class TestMultiGraphView(TestSubGraphView):
136
+ gview = staticmethod(nx.subgraph_view)
137
+ graph = nx.MultiGraph
138
+ hide_edges_filter = staticmethod(nx.filters.hide_multiedges)
139
+ show_edges_filter = staticmethod(nx.filters.show_multiedges)
140
+
141
+ @classmethod
142
+ def setup_class(cls):
143
+ cls.G = nx.path_graph(9, create_using=cls.graph())
144
+ multiedges = {(2, 3, 4), (2, 3, 5)}
145
+ cls.G.add_edges_from(multiedges)
146
+ cls.hide_edges_w_hide_nodes = {(3, 4, 0), (4, 5, 0), (5, 6, 0)}
147
+
148
+ def test_hidden_edges(self):
149
+ hide_edges = [(2, 3, 4), (2, 3, 3), (8, 7, 0), (222, 223, 0)]
150
+ edges_gone = self.hide_edges_filter(hide_edges)
151
+ G = self.gview(self.G, filter_edge=edges_gone)
152
+ assert self.G.nodes == G.nodes
153
+ if G.is_directed():
154
+ assert self.G.edges - G.edges == {(2, 3, 4)}
155
+ assert list(G[3]) == [4]
156
+ assert list(G[2]) == [3]
157
+ assert list(G.pred[3]) == [2] # only one 2 but two edges
158
+ assert list(G.pred[2]) == [1]
159
+ assert G.size() == 9
160
+ else:
161
+ assert self.G.edges - G.edges == {(2, 3, 4), (7, 8, 0)}
162
+ assert list(G[3]) == [2, 4]
163
+ assert list(G[2]) == [1, 3]
164
+ assert G.size() == 8
165
+ assert G.degree(3) == 3
166
+ pytest.raises(KeyError, G.__getitem__, 221)
167
+ pytest.raises(KeyError, G.__getitem__, 222)
168
+
169
+ def test_shown_edges(self):
170
+ show_edges = [(2, 3, 4), (2, 3, 3), (8, 7, 0), (222, 223, 0)]
171
+ edge_subgraph = self.show_edges_filter(show_edges)
172
+ G = self.gview(self.G, filter_edge=edge_subgraph)
173
+ assert self.G.nodes == G.nodes
174
+ if G.is_directed():
175
+ assert G.edges == {(2, 3, 4)}
176
+ assert list(G[3]) == []
177
+ assert list(G.pred[3]) == [2]
178
+ assert list(G.pred[2]) == []
179
+ assert G.size() == 1
180
+ else:
181
+ assert G.edges == {(2, 3, 4), (7, 8, 0)}
182
+ assert G.size() == 2
183
+ assert list(G[3]) == [2]
184
+ assert G.degree(3) == 1
185
+ assert list(G[2]) == [3]
186
+ pytest.raises(KeyError, G.__getitem__, 221)
187
+ pytest.raises(KeyError, G.__getitem__, 222)
188
+
189
+
190
+ # multidigraph
191
+ class TestMultiDiGraphView(TestMultiGraphView, TestSubDiGraphView):
192
+ gview = staticmethod(nx.subgraph_view)
193
+ graph = nx.MultiDiGraph
194
+ hide_edges_filter = staticmethod(nx.filters.hide_multidiedges)
195
+ show_edges_filter = staticmethod(nx.filters.show_multidiedges)
196
+ hide_edges = [(2, 3, 0), (8, 7, 0), (222, 223, 0)]
197
+ excluded = {(2, 3, 0), (3, 4, 0), (4, 5, 0), (5, 6, 0)}
198
+
199
+ def test_inout_degree(self):
200
+ edges_gone = self.hide_edges_filter(self.hide_edges)
201
+ hide_nodes = [4, 5, 111]
202
+ nodes_gone = nx.filters.hide_nodes(hide_nodes)
203
+ G = self.gview(self.G, filter_node=nodes_gone, filter_edge=edges_gone)
204
+
205
+ assert G.degree(2) == 3
206
+ assert G.out_degree(2) == 2
207
+ assert G.in_degree(2) == 1
208
+ assert G.size() == 6
209
+
210
+
211
+ # induced_subgraph
212
+ class TestInducedSubGraph:
213
+ @classmethod
214
+ def setup_class(cls):
215
+ cls.K3 = G = nx.complete_graph(3)
216
+ G.graph["foo"] = []
217
+ G.nodes[0]["foo"] = []
218
+ G.remove_edge(1, 2)
219
+ ll = []
220
+ G.add_edge(1, 2, foo=ll)
221
+ G.add_edge(2, 1, foo=ll)
222
+
223
+ def test_full_graph(self):
224
+ G = self.K3
225
+ H = nx.induced_subgraph(G, [0, 1, 2, 5])
226
+ assert H.name == G.name
227
+ self.graphs_equal(H, G)
228
+ self.same_attrdict(H, G)
229
+
230
+ def test_partial_subgraph(self):
231
+ G = self.K3
232
+ H = nx.induced_subgraph(G, 0)
233
+ assert dict(H.adj) == {0: {}}
234
+ assert dict(G.adj) != {0: {}}
235
+
236
+ H = nx.induced_subgraph(G, [0, 1])
237
+ assert dict(H.adj) == {0: {1: {}}, 1: {0: {}}}
238
+
239
+ def same_attrdict(self, H, G):
240
+ old_foo = H[1][2]["foo"]
241
+ H.edges[1, 2]["foo"] = "baz"
242
+ assert G.edges == H.edges
243
+ H.edges[1, 2]["foo"] = old_foo
244
+ assert G.edges == H.edges
245
+ old_foo = H.nodes[0]["foo"]
246
+ H.nodes[0]["foo"] = "baz"
247
+ assert G.nodes == H.nodes
248
+ H.nodes[0]["foo"] = old_foo
249
+ assert G.nodes == H.nodes
250
+
251
+ def graphs_equal(self, H, G):
252
+ assert G._adj == H._adj
253
+ assert G._node == H._node
254
+ assert G.graph == H.graph
255
+ assert G.name == H.name
256
+ if not G.is_directed() and not H.is_directed():
257
+ assert H._adj[1][2] is H._adj[2][1]
258
+ assert G._adj[1][2] is G._adj[2][1]
259
+ else: # at least one is directed
260
+ if not G.is_directed():
261
+ G._pred = G._adj
262
+ G._succ = G._adj
263
+ if not H.is_directed():
264
+ H._pred = H._adj
265
+ H._succ = H._adj
266
+ assert G._pred == H._pred
267
+ assert G._succ == H._succ
268
+ assert H._succ[1][2] is H._pred[2][1]
269
+ assert G._succ[1][2] is G._pred[2][1]
270
+
271
+
272
+ # edge_subgraph
273
+ class TestEdgeSubGraph:
274
+ @classmethod
275
+ def setup_class(cls):
276
+ # Create a path graph on five nodes.
277
+ cls.G = G = nx.path_graph(5)
278
+ # Add some node, edge, and graph attributes.
279
+ for i in range(5):
280
+ G.nodes[i]["name"] = f"node{i}"
281
+ G.edges[0, 1]["name"] = "edge01"
282
+ G.edges[3, 4]["name"] = "edge34"
283
+ G.graph["name"] = "graph"
284
+ # Get the subgraph induced by the first and last edges.
285
+ cls.H = nx.edge_subgraph(G, [(0, 1), (3, 4)])
286
+
287
+ def test_correct_nodes(self):
288
+ """Tests that the subgraph has the correct nodes."""
289
+ assert [(0, "node0"), (1, "node1"), (3, "node3"), (4, "node4")] == sorted(
290
+ self.H.nodes.data("name")
291
+ )
292
+
293
+ def test_correct_edges(self):
294
+ """Tests that the subgraph has the correct edges."""
295
+ assert edges_equal(
296
+ [(0, 1, "edge01"), (3, 4, "edge34")], self.H.edges.data("name")
297
+ )
298
+
299
+ def test_add_node(self):
300
+ """Tests that adding a node to the original graph does not
301
+ affect the nodes of the subgraph.
302
+
303
+ """
304
+ self.G.add_node(5)
305
+ assert [0, 1, 3, 4] == sorted(self.H.nodes)
306
+ self.G.remove_node(5)
307
+
308
+ def test_remove_node(self):
309
+ """Tests that removing a node in the original graph
310
+ removes the nodes of the subgraph.
311
+
312
+ """
313
+ self.G.remove_node(0)
314
+ assert [1, 3, 4] == sorted(self.H.nodes)
315
+ self.G.add_node(0, name="node0")
316
+ self.G.add_edge(0, 1, name="edge01")
317
+
318
+ def test_node_attr_dict(self):
319
+ """Tests that the node attribute dictionary of the two graphs is
320
+ the same object.
321
+
322
+ """
323
+ for v in self.H:
324
+ assert self.G.nodes[v] == self.H.nodes[v]
325
+ # Making a change to G should make a change in H and vice versa.
326
+ self.G.nodes[0]["name"] = "foo"
327
+ assert self.G.nodes[0] == self.H.nodes[0]
328
+ self.H.nodes[1]["name"] = "bar"
329
+ assert self.G.nodes[1] == self.H.nodes[1]
330
+ # Revert the change, so tests pass with pytest-randomly
331
+ self.G.nodes[0]["name"] = "node0"
332
+ self.H.nodes[1]["name"] = "node1"
333
+
334
+ def test_edge_attr_dict(self):
335
+ """Tests that the edge attribute dictionary of the two graphs is
336
+ the same object.
337
+
338
+ """
339
+ for u, v in self.H.edges():
340
+ assert self.G.edges[u, v] == self.H.edges[u, v]
341
+ # Making a change to G should make a change in H and vice versa.
342
+ self.G.edges[0, 1]["name"] = "foo"
343
+ assert self.G.edges[0, 1]["name"] == self.H.edges[0, 1]["name"]
344
+ self.H.edges[3, 4]["name"] = "bar"
345
+ assert self.G.edges[3, 4]["name"] == self.H.edges[3, 4]["name"]
346
+ # Revert the change, so tests pass with pytest-randomly
347
+ self.G.edges[0, 1]["name"] = "edge01"
348
+ self.H.edges[3, 4]["name"] = "edge34"
349
+
350
+ def test_graph_attr_dict(self):
351
+ """Tests that the graph attribute dictionary of the two graphs
352
+ is the same object.
353
+
354
+ """
355
+ assert self.G.graph is self.H.graph
356
+
357
+ def test_readonly(self):
358
+ """Tests that the subgraph cannot change the graph structure"""
359
+ pytest.raises(nx.NetworkXError, self.H.add_node, 5)
360
+ pytest.raises(nx.NetworkXError, self.H.remove_node, 0)
361
+ pytest.raises(nx.NetworkXError, self.H.add_edge, 5, 6)
362
+ pytest.raises(nx.NetworkXError, self.H.remove_edge, 0, 1)
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/convert.py ADDED
@@ -0,0 +1,496 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Functions to convert NetworkX graphs to and from other formats.
2
+
3
+ The preferred way of converting data to a NetworkX graph is through the
4
+ graph constructor. The constructor calls the to_networkx_graph() function
5
+ which attempts to guess the input type and convert it automatically.
6
+
7
+ Examples
8
+ --------
9
+ Create a graph with a single edge from a dictionary of dictionaries
10
+
11
+ >>> d = {0: {1: 1}} # dict-of-dicts single edge (0,1)
12
+ >>> G = nx.Graph(d)
13
+
14
+ See Also
15
+ --------
16
+ nx_agraph, nx_pydot
17
+ """
18
+ import warnings
19
+ from collections.abc import Collection, Generator, Iterator
20
+
21
+ import networkx as nx
22
+
23
+ __all__ = [
24
+ "to_networkx_graph",
25
+ "from_dict_of_dicts",
26
+ "to_dict_of_dicts",
27
+ "from_dict_of_lists",
28
+ "to_dict_of_lists",
29
+ "from_edgelist",
30
+ "to_edgelist",
31
+ ]
32
+
33
+
34
+ def to_networkx_graph(data, create_using=None, multigraph_input=False):
35
+ """Make a NetworkX graph from a known data structure.
36
+
37
+ The preferred way to call this is automatically
38
+ from the class constructor
39
+
40
+ >>> d = {0: {1: {"weight": 1}}} # dict-of-dicts single edge (0,1)
41
+ >>> G = nx.Graph(d)
42
+
43
+ instead of the equivalent
44
+
45
+ >>> G = nx.from_dict_of_dicts(d)
46
+
47
+ Parameters
48
+ ----------
49
+ data : object to be converted
50
+
51
+ Current known types are:
52
+ any NetworkX graph
53
+ dict-of-dicts
54
+ dict-of-lists
55
+ container (e.g. set, list, tuple) of edges
56
+ iterator (e.g. itertools.chain) that produces edges
57
+ generator of edges
58
+ Pandas DataFrame (row per edge)
59
+ 2D numpy array
60
+ scipy sparse array
61
+ pygraphviz agraph
62
+
63
+ create_using : NetworkX graph constructor, optional (default=nx.Graph)
64
+ Graph type to create. If graph instance, then cleared before populated.
65
+
66
+ multigraph_input : bool (default False)
67
+ If True and data is a dict_of_dicts,
68
+ try to create a multigraph assuming dict_of_dict_of_lists.
69
+ If data and create_using are both multigraphs then create
70
+ a multigraph from a multigraph.
71
+
72
+ """
73
+ # NX graph
74
+ if hasattr(data, "adj"):
75
+ try:
76
+ result = from_dict_of_dicts(
77
+ data.adj,
78
+ create_using=create_using,
79
+ multigraph_input=data.is_multigraph(),
80
+ )
81
+ # data.graph should be dict-like
82
+ result.graph.update(data.graph)
83
+ # data.nodes should be dict-like
84
+ # result.add_node_from(data.nodes.items()) possible but
85
+ # for custom node_attr_dict_factory which may be hashable
86
+ # will be unexpected behavior
87
+ for n, dd in data.nodes.items():
88
+ result._node[n].update(dd)
89
+ return result
90
+ except Exception as err:
91
+ raise nx.NetworkXError("Input is not a correct NetworkX graph.") from err
92
+
93
+ # pygraphviz agraph
94
+ if hasattr(data, "is_strict"):
95
+ try:
96
+ return nx.nx_agraph.from_agraph(data, create_using=create_using)
97
+ except Exception as err:
98
+ raise nx.NetworkXError("Input is not a correct pygraphviz graph.") from err
99
+
100
+ # dict of dicts/lists
101
+ if isinstance(data, dict):
102
+ try:
103
+ return from_dict_of_dicts(
104
+ data, create_using=create_using, multigraph_input=multigraph_input
105
+ )
106
+ except Exception as err1:
107
+ if multigraph_input is True:
108
+ raise nx.NetworkXError(
109
+ f"converting multigraph_input raised:\n{type(err1)}: {err1}"
110
+ )
111
+ try:
112
+ return from_dict_of_lists(data, create_using=create_using)
113
+ except Exception as err2:
114
+ raise TypeError("Input is not known type.") from err2
115
+
116
+ # Pandas DataFrame
117
+ try:
118
+ import pandas as pd
119
+
120
+ if isinstance(data, pd.DataFrame):
121
+ if data.shape[0] == data.shape[1]:
122
+ try:
123
+ return nx.from_pandas_adjacency(data, create_using=create_using)
124
+ except Exception as err:
125
+ msg = "Input is not a correct Pandas DataFrame adjacency matrix."
126
+ raise nx.NetworkXError(msg) from err
127
+ else:
128
+ try:
129
+ return nx.from_pandas_edgelist(
130
+ data, edge_attr=True, create_using=create_using
131
+ )
132
+ except Exception as err:
133
+ msg = "Input is not a correct Pandas DataFrame edge-list."
134
+ raise nx.NetworkXError(msg) from err
135
+ except ImportError:
136
+ warnings.warn("pandas not found, skipping conversion test.", ImportWarning)
137
+
138
+ # numpy array
139
+ try:
140
+ import numpy as np
141
+
142
+ if isinstance(data, np.ndarray):
143
+ try:
144
+ return nx.from_numpy_array(data, create_using=create_using)
145
+ except Exception as err:
146
+ raise nx.NetworkXError(
147
+ f"Failed to interpret array as an adjacency matrix."
148
+ ) from err
149
+ except ImportError:
150
+ warnings.warn("numpy not found, skipping conversion test.", ImportWarning)
151
+
152
+ # scipy sparse array - any format
153
+ try:
154
+ import scipy
155
+
156
+ if hasattr(data, "format"):
157
+ try:
158
+ return nx.from_scipy_sparse_array(data, create_using=create_using)
159
+ except Exception as err:
160
+ raise nx.NetworkXError(
161
+ "Input is not a correct scipy sparse array type."
162
+ ) from err
163
+ except ImportError:
164
+ warnings.warn("scipy not found, skipping conversion test.", ImportWarning)
165
+
166
+ # Note: most general check - should remain last in order of execution
167
+ # Includes containers (e.g. list, set, dict, etc.), generators, and
168
+ # iterators (e.g. itertools.chain) of edges
169
+
170
+ if isinstance(data, (Collection, Generator, Iterator)):
171
+ try:
172
+ return from_edgelist(data, create_using=create_using)
173
+ except Exception as err:
174
+ raise nx.NetworkXError("Input is not a valid edge list") from err
175
+
176
+ raise nx.NetworkXError("Input is not a known data type for conversion.")
177
+
178
+
179
+ @nx._dispatch
180
+ def to_dict_of_lists(G, nodelist=None):
181
+ """Returns adjacency representation of graph as a dictionary of lists.
182
+
183
+ Parameters
184
+ ----------
185
+ G : graph
186
+ A NetworkX graph
187
+
188
+ nodelist : list
189
+ Use only nodes specified in nodelist
190
+
191
+ Notes
192
+ -----
193
+ Completely ignores edge data for MultiGraph and MultiDiGraph.
194
+
195
+ """
196
+ if nodelist is None:
197
+ nodelist = G
198
+
199
+ d = {}
200
+ for n in nodelist:
201
+ d[n] = [nbr for nbr in G.neighbors(n) if nbr in nodelist]
202
+ return d
203
+
204
+
205
+ @nx._dispatch(graphs=None)
206
+ def from_dict_of_lists(d, create_using=None):
207
+ """Returns a graph from a dictionary of lists.
208
+
209
+ Parameters
210
+ ----------
211
+ d : dictionary of lists
212
+ A dictionary of lists adjacency representation.
213
+
214
+ create_using : NetworkX graph constructor, optional (default=nx.Graph)
215
+ Graph type to create. If graph instance, then cleared before populated.
216
+
217
+ Examples
218
+ --------
219
+ >>> dol = {0: [1]} # single edge (0,1)
220
+ >>> G = nx.from_dict_of_lists(dol)
221
+
222
+ or
223
+
224
+ >>> G = nx.Graph(dol) # use Graph constructor
225
+
226
+ """
227
+ G = nx.empty_graph(0, create_using)
228
+ G.add_nodes_from(d)
229
+ if G.is_multigraph() and not G.is_directed():
230
+ # a dict_of_lists can't show multiedges. BUT for undirected graphs,
231
+ # each edge shows up twice in the dict_of_lists.
232
+ # So we need to treat this case separately.
233
+ seen = {}
234
+ for node, nbrlist in d.items():
235
+ for nbr in nbrlist:
236
+ if nbr not in seen:
237
+ G.add_edge(node, nbr)
238
+ seen[node] = 1 # don't allow reverse edge to show up
239
+ else:
240
+ G.add_edges_from(
241
+ ((node, nbr) for node, nbrlist in d.items() for nbr in nbrlist)
242
+ )
243
+ return G
244
+
245
+
246
+ def to_dict_of_dicts(G, nodelist=None, edge_data=None):
247
+ """Returns adjacency representation of graph as a dictionary of dictionaries.
248
+
249
+ Parameters
250
+ ----------
251
+ G : graph
252
+ A NetworkX graph
253
+
254
+ nodelist : list
255
+ Use only nodes specified in nodelist
256
+
257
+ edge_data : scalar, optional
258
+ If provided, the value of the dictionary will be set to `edge_data` for
259
+ all edges. Usual values could be `1` or `True`. If `edge_data` is
260
+ `None` (the default), the edgedata in `G` is used, resulting in a
261
+ dict-of-dict-of-dicts. If `G` is a MultiGraph, the result will be a
262
+ dict-of-dict-of-dict-of-dicts. See Notes for an approach to customize
263
+ handling edge data. `edge_data` should *not* be a container.
264
+
265
+ Returns
266
+ -------
267
+ dod : dict
268
+ A nested dictionary representation of `G`. Note that the level of
269
+ nesting depends on the type of `G` and the value of `edge_data`
270
+ (see Examples).
271
+
272
+ See Also
273
+ --------
274
+ from_dict_of_dicts, to_dict_of_lists
275
+
276
+ Notes
277
+ -----
278
+ For a more custom approach to handling edge data, try::
279
+
280
+ dod = {
281
+ n: {
282
+ nbr: custom(n, nbr, dd) for nbr, dd in nbrdict.items()
283
+ }
284
+ for n, nbrdict in G.adj.items()
285
+ }
286
+
287
+ where `custom` returns the desired edge data for each edge between `n` and
288
+ `nbr`, given existing edge data `dd`.
289
+
290
+ Examples
291
+ --------
292
+ >>> G = nx.path_graph(3)
293
+ >>> nx.to_dict_of_dicts(G)
294
+ {0: {1: {}}, 1: {0: {}, 2: {}}, 2: {1: {}}}
295
+
296
+ Edge data is preserved by default (``edge_data=None``), resulting
297
+ in dict-of-dict-of-dicts where the innermost dictionary contains the
298
+ edge data:
299
+
300
+ >>> G = nx.Graph()
301
+ >>> G.add_edges_from(
302
+ ... [
303
+ ... (0, 1, {'weight': 1.0}),
304
+ ... (1, 2, {'weight': 2.0}),
305
+ ... (2, 0, {'weight': 1.0}),
306
+ ... ]
307
+ ... )
308
+ >>> d = nx.to_dict_of_dicts(G)
309
+ >>> d # doctest: +SKIP
310
+ {0: {1: {'weight': 1.0}, 2: {'weight': 1.0}},
311
+ 1: {0: {'weight': 1.0}, 2: {'weight': 2.0}},
312
+ 2: {1: {'weight': 2.0}, 0: {'weight': 1.0}}}
313
+ >>> d[1][2]['weight']
314
+ 2.0
315
+
316
+ If `edge_data` is not `None`, edge data in the original graph (if any) is
317
+ replaced:
318
+
319
+ >>> d = nx.to_dict_of_dicts(G, edge_data=1)
320
+ >>> d
321
+ {0: {1: 1, 2: 1}, 1: {0: 1, 2: 1}, 2: {1: 1, 0: 1}}
322
+ >>> d[1][2]
323
+ 1
324
+
325
+ This also applies to MultiGraphs: edge data is preserved by default:
326
+
327
+ >>> G = nx.MultiGraph()
328
+ >>> G.add_edge(0, 1, key='a', weight=1.0)
329
+ 'a'
330
+ >>> G.add_edge(0, 1, key='b', weight=5.0)
331
+ 'b'
332
+ >>> d = nx.to_dict_of_dicts(G)
333
+ >>> d # doctest: +SKIP
334
+ {0: {1: {'a': {'weight': 1.0}, 'b': {'weight': 5.0}}},
335
+ 1: {0: {'a': {'weight': 1.0}, 'b': {'weight': 5.0}}}}
336
+ >>> d[0][1]['b']['weight']
337
+ 5.0
338
+
339
+ But multi edge data is lost if `edge_data` is not `None`:
340
+
341
+ >>> d = nx.to_dict_of_dicts(G, edge_data=10)
342
+ >>> d
343
+ {0: {1: 10}, 1: {0: 10}}
344
+ """
345
+ dod = {}
346
+ if nodelist is None:
347
+ if edge_data is None:
348
+ for u, nbrdict in G.adjacency():
349
+ dod[u] = nbrdict.copy()
350
+ else: # edge_data is not None
351
+ for u, nbrdict in G.adjacency():
352
+ dod[u] = dod.fromkeys(nbrdict, edge_data)
353
+ else: # nodelist is not None
354
+ if edge_data is None:
355
+ for u in nodelist:
356
+ dod[u] = {}
357
+ for v, data in ((v, data) for v, data in G[u].items() if v in nodelist):
358
+ dod[u][v] = data
359
+ else: # nodelist and edge_data are not None
360
+ for u in nodelist:
361
+ dod[u] = {}
362
+ for v in (v for v in G[u] if v in nodelist):
363
+ dod[u][v] = edge_data
364
+ return dod
365
+
366
+
367
+ @nx._dispatch(graphs=None)
368
+ def from_dict_of_dicts(d, create_using=None, multigraph_input=False):
369
+ """Returns a graph from a dictionary of dictionaries.
370
+
371
+ Parameters
372
+ ----------
373
+ d : dictionary of dictionaries
374
+ A dictionary of dictionaries adjacency representation.
375
+
376
+ create_using : NetworkX graph constructor, optional (default=nx.Graph)
377
+ Graph type to create. If graph instance, then cleared before populated.
378
+
379
+ multigraph_input : bool (default False)
380
+ When True, the dict `d` is assumed
381
+ to be a dict-of-dict-of-dict-of-dict structure keyed by
382
+ node to neighbor to edge keys to edge data for multi-edges.
383
+ Otherwise this routine assumes dict-of-dict-of-dict keyed by
384
+ node to neighbor to edge data.
385
+
386
+ Examples
387
+ --------
388
+ >>> dod = {0: {1: {"weight": 1}}} # single edge (0,1)
389
+ >>> G = nx.from_dict_of_dicts(dod)
390
+
391
+ or
392
+
393
+ >>> G = nx.Graph(dod) # use Graph constructor
394
+
395
+ """
396
+ G = nx.empty_graph(0, create_using)
397
+ G.add_nodes_from(d)
398
+ # does dict d represent a MultiGraph or MultiDiGraph?
399
+ if multigraph_input:
400
+ if G.is_directed():
401
+ if G.is_multigraph():
402
+ G.add_edges_from(
403
+ (u, v, key, data)
404
+ for u, nbrs in d.items()
405
+ for v, datadict in nbrs.items()
406
+ for key, data in datadict.items()
407
+ )
408
+ else:
409
+ G.add_edges_from(
410
+ (u, v, data)
411
+ for u, nbrs in d.items()
412
+ for v, datadict in nbrs.items()
413
+ for key, data in datadict.items()
414
+ )
415
+ else: # Undirected
416
+ if G.is_multigraph():
417
+ seen = set() # don't add both directions of undirected graph
418
+ for u, nbrs in d.items():
419
+ for v, datadict in nbrs.items():
420
+ if (u, v) not in seen:
421
+ G.add_edges_from(
422
+ (u, v, key, data) for key, data in datadict.items()
423
+ )
424
+ seen.add((v, u))
425
+ else:
426
+ seen = set() # don't add both directions of undirected graph
427
+ for u, nbrs in d.items():
428
+ for v, datadict in nbrs.items():
429
+ if (u, v) not in seen:
430
+ G.add_edges_from(
431
+ (u, v, data) for key, data in datadict.items()
432
+ )
433
+ seen.add((v, u))
434
+
435
+ else: # not a multigraph to multigraph transfer
436
+ if G.is_multigraph() and not G.is_directed():
437
+ # d can have both representations u-v, v-u in dict. Only add one.
438
+ # We don't need this check for digraphs since we add both directions,
439
+ # or for Graph() since it is done implicitly (parallel edges not allowed)
440
+ seen = set()
441
+ for u, nbrs in d.items():
442
+ for v, data in nbrs.items():
443
+ if (u, v) not in seen:
444
+ G.add_edge(u, v, key=0)
445
+ G[u][v][0].update(data)
446
+ seen.add((v, u))
447
+ else:
448
+ G.add_edges_from(
449
+ ((u, v, data) for u, nbrs in d.items() for v, data in nbrs.items())
450
+ )
451
+ return G
452
+
453
+
454
+ @nx._dispatch(preserve_edge_attrs=True)
455
+ def to_edgelist(G, nodelist=None):
456
+ """Returns a list of edges in the graph.
457
+
458
+ Parameters
459
+ ----------
460
+ G : graph
461
+ A NetworkX graph
462
+
463
+ nodelist : list
464
+ Use only nodes specified in nodelist
465
+
466
+ """
467
+ if nodelist is None:
468
+ return G.edges(data=True)
469
+ return G.edges(nodelist, data=True)
470
+
471
+
472
+ @nx._dispatch(graphs=None)
473
+ def from_edgelist(edgelist, create_using=None):
474
+ """Returns a graph from a list of edges.
475
+
476
+ Parameters
477
+ ----------
478
+ edgelist : list or iterator
479
+ Edge tuples
480
+
481
+ create_using : NetworkX graph constructor, optional (default=nx.Graph)
482
+ Graph type to create. If graph instance, then cleared before populated.
483
+
484
+ Examples
485
+ --------
486
+ >>> edgelist = [(0, 1)] # single edge (0,1)
487
+ >>> G = nx.from_edgelist(edgelist)
488
+
489
+ or
490
+
491
+ >>> G = nx.Graph(edgelist) # use Graph constructor
492
+
493
+ """
494
+ G = nx.empty_graph(0, create_using)
495
+ G.add_edges_from(edgelist)
496
+ return G
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/convert_matrix.py ADDED
@@ -0,0 +1,1200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Functions to convert NetworkX graphs to and from common data containers
2
+ like numpy arrays, scipy sparse arrays, and pandas DataFrames.
3
+
4
+ The preferred way of converting data to a NetworkX graph is through the
5
+ graph constructor. The constructor calls the `~networkx.convert.to_networkx_graph`
6
+ function which attempts to guess the input type and convert it automatically.
7
+
8
+ Examples
9
+ --------
10
+ Create a 10 node random graph from a numpy array
11
+
12
+ >>> import numpy as np
13
+ >>> rng = np.random.default_rng()
14
+ >>> a = rng.integers(low=0, high=2, size=(10, 10))
15
+ >>> DG = nx.from_numpy_array(a, create_using=nx.DiGraph)
16
+
17
+ or equivalently:
18
+
19
+ >>> DG = nx.DiGraph(a)
20
+
21
+ which calls `from_numpy_array` internally based on the type of ``a``.
22
+
23
+ See Also
24
+ --------
25
+ nx_agraph, nx_pydot
26
+ """
27
+
28
+ import itertools
29
+ from collections import defaultdict
30
+
31
+ import networkx as nx
32
+ from networkx.utils import not_implemented_for
33
+
34
+ __all__ = [
35
+ "from_pandas_adjacency",
36
+ "to_pandas_adjacency",
37
+ "from_pandas_edgelist",
38
+ "to_pandas_edgelist",
39
+ "from_scipy_sparse_array",
40
+ "to_scipy_sparse_array",
41
+ "from_numpy_array",
42
+ "to_numpy_array",
43
+ ]
44
+
45
+
46
+ @nx._dispatch(edge_attrs="weight")
47
+ def to_pandas_adjacency(
48
+ G,
49
+ nodelist=None,
50
+ dtype=None,
51
+ order=None,
52
+ multigraph_weight=sum,
53
+ weight="weight",
54
+ nonedge=0.0,
55
+ ):
56
+ """Returns the graph adjacency matrix as a Pandas DataFrame.
57
+
58
+ Parameters
59
+ ----------
60
+ G : graph
61
+ The NetworkX graph used to construct the Pandas DataFrame.
62
+
63
+ nodelist : list, optional
64
+ The rows and columns are ordered according to the nodes in `nodelist`.
65
+ If `nodelist` is None, then the ordering is produced by G.nodes().
66
+
67
+ multigraph_weight : {sum, min, max}, optional
68
+ An operator that determines how weights in multigraphs are handled.
69
+ The default is to sum the weights of the multiple edges.
70
+
71
+ weight : string or None, optional
72
+ The edge attribute that holds the numerical value used for
73
+ the edge weight. If an edge does not have that attribute, then the
74
+ value 1 is used instead.
75
+
76
+ nonedge : float, optional
77
+ The matrix values corresponding to nonedges are typically set to zero.
78
+ However, this could be undesirable if there are matrix values
79
+ corresponding to actual edges that also have the value zero. If so,
80
+ one might prefer nonedges to have some other value, such as nan.
81
+
82
+ Returns
83
+ -------
84
+ df : Pandas DataFrame
85
+ Graph adjacency matrix
86
+
87
+ Notes
88
+ -----
89
+ For directed graphs, entry i,j corresponds to an edge from i to j.
90
+
91
+ The DataFrame entries are assigned to the weight edge attribute. When
92
+ an edge does not have a weight attribute, the value of the entry is set to
93
+ the number 1. For multiple (parallel) edges, the values of the entries
94
+ are determined by the 'multigraph_weight' parameter. The default is to
95
+ sum the weight attributes for each of the parallel edges.
96
+
97
+ When `nodelist` does not contain every node in `G`, the matrix is built
98
+ from the subgraph of `G` that is induced by the nodes in `nodelist`.
99
+
100
+ The convention used for self-loop edges in graphs is to assign the
101
+ diagonal matrix entry value to the weight attribute of the edge
102
+ (or the number 1 if the edge has no weight attribute). If the
103
+ alternate convention of doubling the edge weight is desired the
104
+ resulting Pandas DataFrame can be modified as follows:
105
+
106
+ >>> import pandas as pd
107
+ >>> pd.options.display.max_columns = 20
108
+ >>> import numpy as np
109
+ >>> G = nx.Graph([(1, 1)])
110
+ >>> df = nx.to_pandas_adjacency(G, dtype=int)
111
+ >>> df
112
+ 1
113
+ 1 1
114
+ >>> df.values[np.diag_indices_from(df)] *= 2
115
+ >>> df
116
+ 1
117
+ 1 2
118
+
119
+ Examples
120
+ --------
121
+ >>> G = nx.MultiDiGraph()
122
+ >>> G.add_edge(0, 1, weight=2)
123
+ 0
124
+ >>> G.add_edge(1, 0)
125
+ 0
126
+ >>> G.add_edge(2, 2, weight=3)
127
+ 0
128
+ >>> G.add_edge(2, 2)
129
+ 1
130
+ >>> nx.to_pandas_adjacency(G, nodelist=[0, 1, 2], dtype=int)
131
+ 0 1 2
132
+ 0 0 2 0
133
+ 1 1 0 0
134
+ 2 0 0 4
135
+
136
+ """
137
+ import pandas as pd
138
+
139
+ M = to_numpy_array(
140
+ G,
141
+ nodelist=nodelist,
142
+ dtype=dtype,
143
+ order=order,
144
+ multigraph_weight=multigraph_weight,
145
+ weight=weight,
146
+ nonedge=nonedge,
147
+ )
148
+ if nodelist is None:
149
+ nodelist = list(G)
150
+ return pd.DataFrame(data=M, index=nodelist, columns=nodelist)
151
+
152
+
153
+ @nx._dispatch(graphs=None)
154
+ def from_pandas_adjacency(df, create_using=None):
155
+ r"""Returns a graph from Pandas DataFrame.
156
+
157
+ The Pandas DataFrame is interpreted as an adjacency matrix for the graph.
158
+
159
+ Parameters
160
+ ----------
161
+ df : Pandas DataFrame
162
+ An adjacency matrix representation of a graph
163
+
164
+ create_using : NetworkX graph constructor, optional (default=nx.Graph)
165
+ Graph type to create. If graph instance, then cleared before populated.
166
+
167
+ Notes
168
+ -----
169
+ For directed graphs, explicitly mention create_using=nx.DiGraph,
170
+ and entry i,j of df corresponds to an edge from i to j.
171
+
172
+ If `df` has a single data type for each entry it will be converted to an
173
+ appropriate Python data type.
174
+
175
+ If you have node attributes stored in a separate dataframe `df_nodes`,
176
+ you can load those attributes to the graph `G` using the following code:
177
+
178
+ ```
179
+ df_nodes = pd.DataFrame({"node_id": [1, 2, 3], "attribute1": ["A", "B", "C"]})
180
+ G.add_nodes_from((n, dict(d)) for n, d in df_nodes.iterrows())
181
+ ```
182
+
183
+ If `df` has a user-specified compound data type the names
184
+ of the data fields will be used as attribute keys in the resulting
185
+ NetworkX graph.
186
+
187
+ See Also
188
+ --------
189
+ to_pandas_adjacency
190
+
191
+ Examples
192
+ --------
193
+ Simple integer weights on edges:
194
+
195
+ >>> import pandas as pd
196
+ >>> pd.options.display.max_columns = 20
197
+ >>> df = pd.DataFrame([[1, 1], [2, 1]])
198
+ >>> df
199
+ 0 1
200
+ 0 1 1
201
+ 1 2 1
202
+ >>> G = nx.from_pandas_adjacency(df)
203
+ >>> G.name = "Graph from pandas adjacency matrix"
204
+ >>> print(G)
205
+ Graph named 'Graph from pandas adjacency matrix' with 2 nodes and 3 edges
206
+ """
207
+
208
+ try:
209
+ df = df[df.index]
210
+ except Exception as err:
211
+ missing = list(set(df.index).difference(set(df.columns)))
212
+ msg = f"{missing} not in columns"
213
+ raise nx.NetworkXError("Columns must match Indices.", msg) from err
214
+
215
+ A = df.values
216
+ G = from_numpy_array(A, create_using=create_using)
217
+
218
+ nx.relabel.relabel_nodes(G, dict(enumerate(df.columns)), copy=False)
219
+ return G
220
+
221
+
222
+ @nx._dispatch(preserve_edge_attrs=True)
223
+ def to_pandas_edgelist(
224
+ G,
225
+ source="source",
226
+ target="target",
227
+ nodelist=None,
228
+ dtype=None,
229
+ edge_key=None,
230
+ ):
231
+ """Returns the graph edge list as a Pandas DataFrame.
232
+
233
+ Parameters
234
+ ----------
235
+ G : graph
236
+ The NetworkX graph used to construct the Pandas DataFrame.
237
+
238
+ source : str or int, optional
239
+ A valid column name (string or integer) for the source nodes (for the
240
+ directed case).
241
+
242
+ target : str or int, optional
243
+ A valid column name (string or integer) for the target nodes (for the
244
+ directed case).
245
+
246
+ nodelist : list, optional
247
+ Use only nodes specified in nodelist
248
+
249
+ dtype : dtype, default None
250
+ Use to create the DataFrame. Data type to force.
251
+ Only a single dtype is allowed. If None, infer.
252
+
253
+ edge_key : str or int or None, optional (default=None)
254
+ A valid column name (string or integer) for the edge keys (for the
255
+ multigraph case). If None, edge keys are not stored in the DataFrame.
256
+
257
+ Returns
258
+ -------
259
+ df : Pandas DataFrame
260
+ Graph edge list
261
+
262
+ Examples
263
+ --------
264
+ >>> G = nx.Graph(
265
+ ... [
266
+ ... ("A", "B", {"cost": 1, "weight": 7}),
267
+ ... ("C", "E", {"cost": 9, "weight": 10}),
268
+ ... ]
269
+ ... )
270
+ >>> df = nx.to_pandas_edgelist(G, nodelist=["A", "C"])
271
+ >>> df[["source", "target", "cost", "weight"]]
272
+ source target cost weight
273
+ 0 A B 1 7
274
+ 1 C E 9 10
275
+
276
+ >>> G = nx.MultiGraph([('A', 'B', {'cost': 1}), ('A', 'B', {'cost': 9})])
277
+ >>> df = nx.to_pandas_edgelist(G, nodelist=['A', 'C'], edge_key='ekey')
278
+ >>> df[['source', 'target', 'cost', 'ekey']]
279
+ source target cost ekey
280
+ 0 A B 1 0
281
+ 1 A B 9 1
282
+
283
+ """
284
+ import pandas as pd
285
+
286
+ if nodelist is None:
287
+ edgelist = G.edges(data=True)
288
+ else:
289
+ edgelist = G.edges(nodelist, data=True)
290
+ source_nodes = [s for s, _, _ in edgelist]
291
+ target_nodes = [t for _, t, _ in edgelist]
292
+
293
+ all_attrs = set().union(*(d.keys() for _, _, d in edgelist))
294
+ if source in all_attrs:
295
+ raise nx.NetworkXError(f"Source name {source!r} is an edge attr name")
296
+ if target in all_attrs:
297
+ raise nx.NetworkXError(f"Target name {target!r} is an edge attr name")
298
+
299
+ nan = float("nan")
300
+ edge_attr = {k: [d.get(k, nan) for _, _, d in edgelist] for k in all_attrs}
301
+
302
+ if G.is_multigraph() and edge_key is not None:
303
+ if edge_key in all_attrs:
304
+ raise nx.NetworkXError(f"Edge key name {edge_key!r} is an edge attr name")
305
+ edge_keys = [k for _, _, k in G.edges(keys=True)]
306
+ edgelistdict = {source: source_nodes, target: target_nodes, edge_key: edge_keys}
307
+ else:
308
+ edgelistdict = {source: source_nodes, target: target_nodes}
309
+
310
+ edgelistdict.update(edge_attr)
311
+ return pd.DataFrame(edgelistdict, dtype=dtype)
312
+
313
+
314
+ @nx._dispatch(graphs=None)
315
+ def from_pandas_edgelist(
316
+ df,
317
+ source="source",
318
+ target="target",
319
+ edge_attr=None,
320
+ create_using=None,
321
+ edge_key=None,
322
+ ):
323
+ """Returns a graph from Pandas DataFrame containing an edge list.
324
+
325
+ The Pandas DataFrame should contain at least two columns of node names and
326
+ zero or more columns of edge attributes. Each row will be processed as one
327
+ edge instance.
328
+
329
+ Note: This function iterates over DataFrame.values, which is not
330
+ guaranteed to retain the data type across columns in the row. This is only
331
+ a problem if your row is entirely numeric and a mix of ints and floats. In
332
+ that case, all values will be returned as floats. See the
333
+ DataFrame.iterrows documentation for an example.
334
+
335
+ Parameters
336
+ ----------
337
+ df : Pandas DataFrame
338
+ An edge list representation of a graph
339
+
340
+ source : str or int
341
+ A valid column name (string or integer) for the source nodes (for the
342
+ directed case).
343
+
344
+ target : str or int
345
+ A valid column name (string or integer) for the target nodes (for the
346
+ directed case).
347
+
348
+ edge_attr : str or int, iterable, True, or None
349
+ A valid column name (str or int) or iterable of column names that are
350
+ used to retrieve items and add them to the graph as edge attributes.
351
+ If `True`, all of the remaining columns will be added.
352
+ If `None`, no edge attributes are added to the graph.
353
+
354
+ create_using : NetworkX graph constructor, optional (default=nx.Graph)
355
+ Graph type to create. If graph instance, then cleared before populated.
356
+
357
+ edge_key : str or None, optional (default=None)
358
+ A valid column name for the edge keys (for a MultiGraph). The values in
359
+ this column are used for the edge keys when adding edges if create_using
360
+ is a multigraph.
361
+
362
+ If you have node attributes stored in a separate dataframe `df_nodes`,
363
+ you can load those attributes to the graph `G` using the following code:
364
+
365
+ ```
366
+ df_nodes = pd.DataFrame({"node_id": [1, 2, 3], "attribute1": ["A", "B", "C"]})
367
+ G.add_nodes_from((n, dict(d)) for n, d in df_nodes.iterrows())
368
+ ```
369
+
370
+ See Also
371
+ --------
372
+ to_pandas_edgelist
373
+
374
+ Examples
375
+ --------
376
+ Simple integer weights on edges:
377
+
378
+ >>> import pandas as pd
379
+ >>> pd.options.display.max_columns = 20
380
+ >>> import numpy as np
381
+ >>> rng = np.random.RandomState(seed=5)
382
+ >>> ints = rng.randint(1, 11, size=(3, 2))
383
+ >>> a = ["A", "B", "C"]
384
+ >>> b = ["D", "A", "E"]
385
+ >>> df = pd.DataFrame(ints, columns=["weight", "cost"])
386
+ >>> df[0] = a
387
+ >>> df["b"] = b
388
+ >>> df[["weight", "cost", 0, "b"]]
389
+ weight cost 0 b
390
+ 0 4 7 A D
391
+ 1 7 1 B A
392
+ 2 10 9 C E
393
+ >>> G = nx.from_pandas_edgelist(df, 0, "b", ["weight", "cost"])
394
+ >>> G["E"]["C"]["weight"]
395
+ 10
396
+ >>> G["E"]["C"]["cost"]
397
+ 9
398
+ >>> edges = pd.DataFrame(
399
+ ... {
400
+ ... "source": [0, 1, 2],
401
+ ... "target": [2, 2, 3],
402
+ ... "weight": [3, 4, 5],
403
+ ... "color": ["red", "blue", "blue"],
404
+ ... }
405
+ ... )
406
+ >>> G = nx.from_pandas_edgelist(edges, edge_attr=True)
407
+ >>> G[0][2]["color"]
408
+ 'red'
409
+
410
+ Build multigraph with custom keys:
411
+
412
+ >>> edges = pd.DataFrame(
413
+ ... {
414
+ ... "source": [0, 1, 2, 0],
415
+ ... "target": [2, 2, 3, 2],
416
+ ... "my_edge_key": ["A", "B", "C", "D"],
417
+ ... "weight": [3, 4, 5, 6],
418
+ ... "color": ["red", "blue", "blue", "blue"],
419
+ ... }
420
+ ... )
421
+ >>> G = nx.from_pandas_edgelist(
422
+ ... edges,
423
+ ... edge_key="my_edge_key",
424
+ ... edge_attr=["weight", "color"],
425
+ ... create_using=nx.MultiGraph(),
426
+ ... )
427
+ >>> G[0][2]
428
+ AtlasView({'A': {'weight': 3, 'color': 'red'}, 'D': {'weight': 6, 'color': 'blue'}})
429
+
430
+
431
+ """
432
+ g = nx.empty_graph(0, create_using)
433
+
434
+ if edge_attr is None:
435
+ g.add_edges_from(zip(df[source], df[target]))
436
+ return g
437
+
438
+ reserved_columns = [source, target]
439
+
440
+ # Additional columns requested
441
+ attr_col_headings = []
442
+ attribute_data = []
443
+ if edge_attr is True:
444
+ attr_col_headings = [c for c in df.columns if c not in reserved_columns]
445
+ elif isinstance(edge_attr, (list, tuple)):
446
+ attr_col_headings = edge_attr
447
+ else:
448
+ attr_col_headings = [edge_attr]
449
+ if len(attr_col_headings) == 0:
450
+ raise nx.NetworkXError(
451
+ f"Invalid edge_attr argument: No columns found with name: {attr_col_headings}"
452
+ )
453
+
454
+ try:
455
+ attribute_data = zip(*[df[col] for col in attr_col_headings])
456
+ except (KeyError, TypeError) as err:
457
+ msg = f"Invalid edge_attr argument: {edge_attr}"
458
+ raise nx.NetworkXError(msg) from err
459
+
460
+ if g.is_multigraph():
461
+ # => append the edge keys from the df to the bundled data
462
+ if edge_key is not None:
463
+ try:
464
+ multigraph_edge_keys = df[edge_key]
465
+ attribute_data = zip(attribute_data, multigraph_edge_keys)
466
+ except (KeyError, TypeError) as err:
467
+ msg = f"Invalid edge_key argument: {edge_key}"
468
+ raise nx.NetworkXError(msg) from err
469
+
470
+ for s, t, attrs in zip(df[source], df[target], attribute_data):
471
+ if edge_key is not None:
472
+ attrs, multigraph_edge_key = attrs
473
+ key = g.add_edge(s, t, key=multigraph_edge_key)
474
+ else:
475
+ key = g.add_edge(s, t)
476
+
477
+ g[s][t][key].update(zip(attr_col_headings, attrs))
478
+ else:
479
+ for s, t, attrs in zip(df[source], df[target], attribute_data):
480
+ g.add_edge(s, t)
481
+ g[s][t].update(zip(attr_col_headings, attrs))
482
+
483
+ return g
484
+
485
+
486
+ @nx._dispatch(edge_attrs="weight")
487
+ def to_scipy_sparse_array(G, nodelist=None, dtype=None, weight="weight", format="csr"):
488
+ """Returns the graph adjacency matrix as a SciPy sparse array.
489
+
490
+ Parameters
491
+ ----------
492
+ G : graph
493
+ The NetworkX graph used to construct the sparse matrix.
494
+
495
+ nodelist : list, optional
496
+ The rows and columns are ordered according to the nodes in `nodelist`.
497
+ If `nodelist` is None, then the ordering is produced by G.nodes().
498
+
499
+ dtype : NumPy data-type, optional
500
+ A valid NumPy dtype used to initialize the array. If None, then the
501
+ NumPy default is used.
502
+
503
+ weight : string or None optional (default='weight')
504
+ The edge attribute that holds the numerical value used for
505
+ the edge weight. If None then all edge weights are 1.
506
+
507
+ format : str in {'bsr', 'csr', 'csc', 'coo', 'lil', 'dia', 'dok'}
508
+ The type of the matrix to be returned (default 'csr'). For
509
+ some algorithms different implementations of sparse matrices
510
+ can perform better. See [1]_ for details.
511
+
512
+ Returns
513
+ -------
514
+ A : SciPy sparse array
515
+ Graph adjacency matrix.
516
+
517
+ Notes
518
+ -----
519
+ For directed graphs, matrix entry i,j corresponds to an edge from i to j.
520
+
521
+ The matrix entries are populated using the edge attribute held in
522
+ parameter weight. When an edge does not have that attribute, the
523
+ value of the entry is 1.
524
+
525
+ For multiple edges the matrix values are the sums of the edge weights.
526
+
527
+ When `nodelist` does not contain every node in `G`, the adjacency matrix
528
+ is built from the subgraph of `G` that is induced by the nodes in
529
+ `nodelist`.
530
+
531
+ The convention used for self-loop edges in graphs is to assign the
532
+ diagonal matrix entry value to the weight attribute of the edge
533
+ (or the number 1 if the edge has no weight attribute). If the
534
+ alternate convention of doubling the edge weight is desired the
535
+ resulting SciPy sparse array can be modified as follows:
536
+
537
+ >>> G = nx.Graph([(1, 1)])
538
+ >>> A = nx.to_scipy_sparse_array(G)
539
+ >>> print(A.todense())
540
+ [[1]]
541
+ >>> A.setdiag(A.diagonal() * 2)
542
+ >>> print(A.toarray())
543
+ [[2]]
544
+
545
+ Examples
546
+ --------
547
+ >>> G = nx.MultiDiGraph()
548
+ >>> G.add_edge(0, 1, weight=2)
549
+ 0
550
+ >>> G.add_edge(1, 0)
551
+ 0
552
+ >>> G.add_edge(2, 2, weight=3)
553
+ 0
554
+ >>> G.add_edge(2, 2)
555
+ 1
556
+ >>> S = nx.to_scipy_sparse_array(G, nodelist=[0, 1, 2])
557
+ >>> print(S.toarray())
558
+ [[0 2 0]
559
+ [1 0 0]
560
+ [0 0 4]]
561
+
562
+ References
563
+ ----------
564
+ .. [1] Scipy Dev. References, "Sparse Matrices",
565
+ https://docs.scipy.org/doc/scipy/reference/sparse.html
566
+ """
567
+ import scipy as sp
568
+
569
+ if len(G) == 0:
570
+ raise nx.NetworkXError("Graph has no nodes or edges")
571
+
572
+ if nodelist is None:
573
+ nodelist = list(G)
574
+ nlen = len(G)
575
+ else:
576
+ nlen = len(nodelist)
577
+ if nlen == 0:
578
+ raise nx.NetworkXError("nodelist has no nodes")
579
+ nodeset = set(G.nbunch_iter(nodelist))
580
+ if nlen != len(nodeset):
581
+ for n in nodelist:
582
+ if n not in G:
583
+ raise nx.NetworkXError(f"Node {n} in nodelist is not in G")
584
+ raise nx.NetworkXError("nodelist contains duplicates.")
585
+ if nlen < len(G):
586
+ G = G.subgraph(nodelist)
587
+
588
+ index = dict(zip(nodelist, range(nlen)))
589
+ coefficients = zip(
590
+ *((index[u], index[v], wt) for u, v, wt in G.edges(data=weight, default=1))
591
+ )
592
+ try:
593
+ row, col, data = coefficients
594
+ except ValueError:
595
+ # there is no edge in the subgraph
596
+ row, col, data = [], [], []
597
+
598
+ if G.is_directed():
599
+ A = sp.sparse.coo_array((data, (row, col)), shape=(nlen, nlen), dtype=dtype)
600
+ else:
601
+ # symmetrize matrix
602
+ d = data + data
603
+ r = row + col
604
+ c = col + row
605
+ # selfloop entries get double counted when symmetrizing
606
+ # so we subtract the data on the diagonal
607
+ selfloops = list(nx.selfloop_edges(G, data=weight, default=1))
608
+ if selfloops:
609
+ diag_index, diag_data = zip(*((index[u], -wt) for u, v, wt in selfloops))
610
+ d += diag_data
611
+ r += diag_index
612
+ c += diag_index
613
+ A = sp.sparse.coo_array((d, (r, c)), shape=(nlen, nlen), dtype=dtype)
614
+ try:
615
+ return A.asformat(format)
616
+ except ValueError as err:
617
+ raise nx.NetworkXError(f"Unknown sparse matrix format: {format}") from err
618
+
619
+
620
+ def _csr_gen_triples(A):
621
+ """Converts a SciPy sparse array in **Compressed Sparse Row** format to
622
+ an iterable of weighted edge triples.
623
+
624
+ """
625
+ nrows = A.shape[0]
626
+ data, indices, indptr = A.data, A.indices, A.indptr
627
+ for i in range(nrows):
628
+ for j in range(indptr[i], indptr[i + 1]):
629
+ yield i, int(indices[j]), data[j]
630
+
631
+
632
+ def _csc_gen_triples(A):
633
+ """Converts a SciPy sparse array in **Compressed Sparse Column** format to
634
+ an iterable of weighted edge triples.
635
+
636
+ """
637
+ ncols = A.shape[1]
638
+ data, indices, indptr = A.data, A.indices, A.indptr
639
+ for i in range(ncols):
640
+ for j in range(indptr[i], indptr[i + 1]):
641
+ yield int(indices[j]), i, data[j]
642
+
643
+
644
+ def _coo_gen_triples(A):
645
+ """Converts a SciPy sparse array in **Coordinate** format to an iterable
646
+ of weighted edge triples.
647
+
648
+ """
649
+ return ((int(i), int(j), d) for i, j, d in zip(A.row, A.col, A.data))
650
+
651
+
652
+ def _dok_gen_triples(A):
653
+ """Converts a SciPy sparse array in **Dictionary of Keys** format to an
654
+ iterable of weighted edge triples.
655
+
656
+ """
657
+ for (r, c), v in A.items():
658
+ yield r, c, v
659
+
660
+
661
+ def _generate_weighted_edges(A):
662
+ """Returns an iterable over (u, v, w) triples, where u and v are adjacent
663
+ vertices and w is the weight of the edge joining u and v.
664
+
665
+ `A` is a SciPy sparse array (in any format).
666
+
667
+ """
668
+ if A.format == "csr":
669
+ return _csr_gen_triples(A)
670
+ if A.format == "csc":
671
+ return _csc_gen_triples(A)
672
+ if A.format == "dok":
673
+ return _dok_gen_triples(A)
674
+ # If A is in any other format (including COO), convert it to COO format.
675
+ return _coo_gen_triples(A.tocoo())
676
+
677
+
678
+ @nx._dispatch(graphs=None)
679
+ def from_scipy_sparse_array(
680
+ A, parallel_edges=False, create_using=None, edge_attribute="weight"
681
+ ):
682
+ """Creates a new graph from an adjacency matrix given as a SciPy sparse
683
+ array.
684
+
685
+ Parameters
686
+ ----------
687
+ A: scipy.sparse array
688
+ An adjacency matrix representation of a graph
689
+
690
+ parallel_edges : Boolean
691
+ If this is True, `create_using` is a multigraph, and `A` is an
692
+ integer matrix, then entry *(i, j)* in the matrix is interpreted as the
693
+ number of parallel edges joining vertices *i* and *j* in the graph.
694
+ If it is False, then the entries in the matrix are interpreted as
695
+ the weight of a single edge joining the vertices.
696
+
697
+ create_using : NetworkX graph constructor, optional (default=nx.Graph)
698
+ Graph type to create. If graph instance, then cleared before populated.
699
+
700
+ edge_attribute: string
701
+ Name of edge attribute to store matrix numeric value. The data will
702
+ have the same type as the matrix entry (int, float, (real,imag)).
703
+
704
+ Notes
705
+ -----
706
+ For directed graphs, explicitly mention create_using=nx.DiGraph,
707
+ and entry i,j of A corresponds to an edge from i to j.
708
+
709
+ If `create_using` is :class:`networkx.MultiGraph` or
710
+ :class:`networkx.MultiDiGraph`, `parallel_edges` is True, and the
711
+ entries of `A` are of type :class:`int`, then this function returns a
712
+ multigraph (constructed from `create_using`) with parallel edges.
713
+ In this case, `edge_attribute` will be ignored.
714
+
715
+ If `create_using` indicates an undirected multigraph, then only the edges
716
+ indicated by the upper triangle of the matrix `A` will be added to the
717
+ graph.
718
+
719
+ Examples
720
+ --------
721
+ >>> import scipy as sp
722
+ >>> A = sp.sparse.eye(2, 2, 1)
723
+ >>> G = nx.from_scipy_sparse_array(A)
724
+
725
+ If `create_using` indicates a multigraph and the matrix has only integer
726
+ entries and `parallel_edges` is False, then the entries will be treated
727
+ as weights for edges joining the nodes (without creating parallel edges):
728
+
729
+ >>> A = sp.sparse.csr_array([[1, 1], [1, 2]])
730
+ >>> G = nx.from_scipy_sparse_array(A, create_using=nx.MultiGraph)
731
+ >>> G[1][1]
732
+ AtlasView({0: {'weight': 2}})
733
+
734
+ If `create_using` indicates a multigraph and the matrix has only integer
735
+ entries and `parallel_edges` is True, then the entries will be treated
736
+ as the number of parallel edges joining those two vertices:
737
+
738
+ >>> A = sp.sparse.csr_array([[1, 1], [1, 2]])
739
+ >>> G = nx.from_scipy_sparse_array(
740
+ ... A, parallel_edges=True, create_using=nx.MultiGraph
741
+ ... )
742
+ >>> G[1][1]
743
+ AtlasView({0: {'weight': 1}, 1: {'weight': 1}})
744
+
745
+ """
746
+ G = nx.empty_graph(0, create_using)
747
+ n, m = A.shape
748
+ if n != m:
749
+ raise nx.NetworkXError(f"Adjacency matrix not square: nx,ny={A.shape}")
750
+ # Make sure we get even the isolated nodes of the graph.
751
+ G.add_nodes_from(range(n))
752
+ # Create an iterable over (u, v, w) triples and for each triple, add an
753
+ # edge from u to v with weight w.
754
+ triples = _generate_weighted_edges(A)
755
+ # If the entries in the adjacency matrix are integers, the graph is a
756
+ # multigraph, and parallel_edges is True, then create parallel edges, each
757
+ # with weight 1, for each entry in the adjacency matrix. Otherwise, create
758
+ # one edge for each positive entry in the adjacency matrix and set the
759
+ # weight of that edge to be the entry in the matrix.
760
+ if A.dtype.kind in ("i", "u") and G.is_multigraph() and parallel_edges:
761
+ chain = itertools.chain.from_iterable
762
+ # The following line is equivalent to:
763
+ #
764
+ # for (u, v) in edges:
765
+ # for d in range(A[u, v]):
766
+ # G.add_edge(u, v, weight=1)
767
+ #
768
+ triples = chain(((u, v, 1) for d in range(w)) for (u, v, w) in triples)
769
+ # If we are creating an undirected multigraph, only add the edges from the
770
+ # upper triangle of the matrix. Otherwise, add all the edges. This relies
771
+ # on the fact that the vertices created in the
772
+ # `_generated_weighted_edges()` function are actually the row/column
773
+ # indices for the matrix `A`.
774
+ #
775
+ # Without this check, we run into a problem where each edge is added twice
776
+ # when `G.add_weighted_edges_from()` is invoked below.
777
+ if G.is_multigraph() and not G.is_directed():
778
+ triples = ((u, v, d) for u, v, d in triples if u <= v)
779
+ G.add_weighted_edges_from(triples, weight=edge_attribute)
780
+ return G
781
+
782
+
783
+ @nx._dispatch(edge_attrs="weight") # edge attrs may also be obtained from `dtype`
784
+ def to_numpy_array(
785
+ G,
786
+ nodelist=None,
787
+ dtype=None,
788
+ order=None,
789
+ multigraph_weight=sum,
790
+ weight="weight",
791
+ nonedge=0.0,
792
+ ):
793
+ """Returns the graph adjacency matrix as a NumPy array.
794
+
795
+ Parameters
796
+ ----------
797
+ G : graph
798
+ The NetworkX graph used to construct the NumPy array.
799
+
800
+ nodelist : list, optional
801
+ The rows and columns are ordered according to the nodes in `nodelist`.
802
+ If `nodelist` is ``None``, then the ordering is produced by ``G.nodes()``.
803
+
804
+ dtype : NumPy data type, optional
805
+ A NumPy data type used to initialize the array. If None, then the NumPy
806
+ default is used. The dtype can be structured if `weight=None`, in which
807
+ case the dtype field names are used to look up edge attributes. The
808
+ result is a structured array where each named field in the dtype
809
+ corresponds to the adjacency for that edge attribute. See examples for
810
+ details.
811
+
812
+ order : {'C', 'F'}, optional
813
+ Whether to store multidimensional data in C- or Fortran-contiguous
814
+ (row- or column-wise) order in memory. If None, then the NumPy default
815
+ is used.
816
+
817
+ multigraph_weight : callable, optional
818
+ An function that determines how weights in multigraphs are handled.
819
+ The function should accept a sequence of weights and return a single
820
+ value. The default is to sum the weights of the multiple edges.
821
+
822
+ weight : string or None optional (default = 'weight')
823
+ The edge attribute that holds the numerical value used for
824
+ the edge weight. If an edge does not have that attribute, then the
825
+ value 1 is used instead. `weight` must be ``None`` if a structured
826
+ dtype is used.
827
+
828
+ nonedge : array_like (default = 0.0)
829
+ The value used to represent non-edges in the adjacency matrix.
830
+ The array values corresponding to nonedges are typically set to zero.
831
+ However, this could be undesirable if there are array values
832
+ corresponding to actual edges that also have the value zero. If so,
833
+ one might prefer nonedges to have some other value, such as ``nan``.
834
+
835
+ Returns
836
+ -------
837
+ A : NumPy ndarray
838
+ Graph adjacency matrix
839
+
840
+ Raises
841
+ ------
842
+ NetworkXError
843
+ If `dtype` is a structured dtype and `G` is a multigraph
844
+ ValueError
845
+ If `dtype` is a structured dtype and `weight` is not `None`
846
+
847
+ See Also
848
+ --------
849
+ from_numpy_array
850
+
851
+ Notes
852
+ -----
853
+ For directed graphs, entry ``i, j`` corresponds to an edge from ``i`` to ``j``.
854
+
855
+ Entries in the adjacency matrix are given by the `weight` edge attribute.
856
+ When an edge does not have a weight attribute, the value of the entry is
857
+ set to the number 1. For multiple (parallel) edges, the values of the
858
+ entries are determined by the `multigraph_weight` parameter. The default is
859
+ to sum the weight attributes for each of the parallel edges.
860
+
861
+ When `nodelist` does not contain every node in `G`, the adjacency matrix is
862
+ built from the subgraph of `G` that is induced by the nodes in `nodelist`.
863
+
864
+ The convention used for self-loop edges in graphs is to assign the
865
+ diagonal array entry value to the weight attribute of the edge
866
+ (or the number 1 if the edge has no weight attribute). If the
867
+ alternate convention of doubling the edge weight is desired the
868
+ resulting NumPy array can be modified as follows:
869
+
870
+ >>> import numpy as np
871
+ >>> G = nx.Graph([(1, 1)])
872
+ >>> A = nx.to_numpy_array(G)
873
+ >>> A
874
+ array([[1.]])
875
+ >>> A[np.diag_indices_from(A)] *= 2
876
+ >>> A
877
+ array([[2.]])
878
+
879
+ Examples
880
+ --------
881
+ >>> G = nx.MultiDiGraph()
882
+ >>> G.add_edge(0, 1, weight=2)
883
+ 0
884
+ >>> G.add_edge(1, 0)
885
+ 0
886
+ >>> G.add_edge(2, 2, weight=3)
887
+ 0
888
+ >>> G.add_edge(2, 2)
889
+ 1
890
+ >>> nx.to_numpy_array(G, nodelist=[0, 1, 2])
891
+ array([[0., 2., 0.],
892
+ [1., 0., 0.],
893
+ [0., 0., 4.]])
894
+
895
+ When `nodelist` argument is used, nodes of `G` which do not appear in the `nodelist`
896
+ and their edges are not included in the adjacency matrix. Here is an example:
897
+
898
+ >>> G = nx.Graph()
899
+ >>> G.add_edge(3, 1)
900
+ >>> G.add_edge(2, 0)
901
+ >>> G.add_edge(2, 1)
902
+ >>> G.add_edge(3, 0)
903
+ >>> nx.to_numpy_array(G, nodelist=[1, 2, 3])
904
+ array([[0., 1., 1.],
905
+ [1., 0., 0.],
906
+ [1., 0., 0.]])
907
+
908
+ This function can also be used to create adjacency matrices for multiple
909
+ edge attributes with structured dtypes:
910
+
911
+ >>> G = nx.Graph()
912
+ >>> G.add_edge(0, 1, weight=10)
913
+ >>> G.add_edge(1, 2, cost=5)
914
+ >>> G.add_edge(2, 3, weight=3, cost=-4.0)
915
+ >>> dtype = np.dtype([("weight", int), ("cost", float)])
916
+ >>> A = nx.to_numpy_array(G, dtype=dtype, weight=None)
917
+ >>> A["weight"]
918
+ array([[ 0, 10, 0, 0],
919
+ [10, 0, 1, 0],
920
+ [ 0, 1, 0, 3],
921
+ [ 0, 0, 3, 0]])
922
+ >>> A["cost"]
923
+ array([[ 0., 1., 0., 0.],
924
+ [ 1., 0., 5., 0.],
925
+ [ 0., 5., 0., -4.],
926
+ [ 0., 0., -4., 0.]])
927
+
928
+ As stated above, the argument "nonedge" is useful especially when there are
929
+ actually edges with weight 0 in the graph. Setting a nonedge value different than 0,
930
+ makes it much clearer to differentiate such 0-weighted edges and actual nonedge values.
931
+
932
+ >>> G = nx.Graph()
933
+ >>> G.add_edge(3, 1, weight=2)
934
+ >>> G.add_edge(2, 0, weight=0)
935
+ >>> G.add_edge(2, 1, weight=0)
936
+ >>> G.add_edge(3, 0, weight=1)
937
+ >>> nx.to_numpy_array(G, nonedge=-1.)
938
+ array([[-1., 2., -1., 1.],
939
+ [ 2., -1., 0., -1.],
940
+ [-1., 0., -1., 0.],
941
+ [ 1., -1., 0., -1.]])
942
+ """
943
+ import numpy as np
944
+
945
+ if nodelist is None:
946
+ nodelist = list(G)
947
+ nlen = len(nodelist)
948
+
949
+ # Input validation
950
+ nodeset = set(nodelist)
951
+ if nodeset - set(G):
952
+ raise nx.NetworkXError(f"Nodes {nodeset - set(G)} in nodelist is not in G")
953
+ if len(nodeset) < nlen:
954
+ raise nx.NetworkXError("nodelist contains duplicates.")
955
+
956
+ A = np.full((nlen, nlen), fill_value=nonedge, dtype=dtype, order=order)
957
+
958
+ # Corner cases: empty nodelist or graph without any edges
959
+ if nlen == 0 or G.number_of_edges() == 0:
960
+ return A
961
+
962
+ # If dtype is structured and weight is None, use dtype field names as
963
+ # edge attributes
964
+ edge_attrs = None # Only single edge attribute by default
965
+ if A.dtype.names:
966
+ if weight is None:
967
+ edge_attrs = dtype.names
968
+ else:
969
+ raise ValueError(
970
+ "Specifying `weight` not supported for structured dtypes\n."
971
+ "To create adjacency matrices from structured dtypes, use `weight=None`."
972
+ )
973
+
974
+ # Map nodes to row/col in matrix
975
+ idx = dict(zip(nodelist, range(nlen)))
976
+ if len(nodelist) < len(G):
977
+ G = G.subgraph(nodelist).copy()
978
+
979
+ # Collect all edge weights and reduce with `multigraph_weights`
980
+ if G.is_multigraph():
981
+ if edge_attrs:
982
+ raise nx.NetworkXError(
983
+ "Structured arrays are not supported for MultiGraphs"
984
+ )
985
+ d = defaultdict(list)
986
+ for u, v, wt in G.edges(data=weight, default=1.0):
987
+ d[(idx[u], idx[v])].append(wt)
988
+ i, j = np.array(list(d.keys())).T # indices
989
+ wts = [multigraph_weight(ws) for ws in d.values()] # reduced weights
990
+ else:
991
+ i, j, wts = [], [], []
992
+
993
+ # Special branch: multi-attr adjacency from structured dtypes
994
+ if edge_attrs:
995
+ # Extract edges with all data
996
+ for u, v, data in G.edges(data=True):
997
+ i.append(idx[u])
998
+ j.append(idx[v])
999
+ wts.append(data)
1000
+ # Map each attribute to the appropriate named field in the
1001
+ # structured dtype
1002
+ for attr in edge_attrs:
1003
+ attr_data = [wt.get(attr, 1.0) for wt in wts]
1004
+ A[attr][i, j] = attr_data
1005
+ if not G.is_directed():
1006
+ A[attr][j, i] = attr_data
1007
+ return A
1008
+
1009
+ for u, v, wt in G.edges(data=weight, default=1.0):
1010
+ i.append(idx[u])
1011
+ j.append(idx[v])
1012
+ wts.append(wt)
1013
+
1014
+ # Set array values with advanced indexing
1015
+ A[i, j] = wts
1016
+ if not G.is_directed():
1017
+ A[j, i] = wts
1018
+
1019
+ return A
1020
+
1021
+
1022
+ @nx._dispatch(graphs=None)
1023
+ def from_numpy_array(A, parallel_edges=False, create_using=None, edge_attr="weight"):
1024
+ """Returns a graph from a 2D NumPy array.
1025
+
1026
+ The 2D NumPy array is interpreted as an adjacency matrix for the graph.
1027
+
1028
+ Parameters
1029
+ ----------
1030
+ A : a 2D numpy.ndarray
1031
+ An adjacency matrix representation of a graph
1032
+
1033
+ parallel_edges : Boolean
1034
+ If this is True, `create_using` is a multigraph, and `A` is an
1035
+ integer array, then entry *(i, j)* in the array is interpreted as the
1036
+ number of parallel edges joining vertices *i* and *j* in the graph.
1037
+ If it is False, then the entries in the array are interpreted as
1038
+ the weight of a single edge joining the vertices.
1039
+
1040
+ create_using : NetworkX graph constructor, optional (default=nx.Graph)
1041
+ Graph type to create. If graph instance, then cleared before populated.
1042
+
1043
+ edge_attr : String, optional (default="weight")
1044
+ The attribute to which the array values are assigned on each edge. If
1045
+ it is None, edge attributes will not be assigned.
1046
+
1047
+ Notes
1048
+ -----
1049
+ For directed graphs, explicitly mention create_using=nx.DiGraph,
1050
+ and entry i,j of A corresponds to an edge from i to j.
1051
+
1052
+ If `create_using` is :class:`networkx.MultiGraph` or
1053
+ :class:`networkx.MultiDiGraph`, `parallel_edges` is True, and the
1054
+ entries of `A` are of type :class:`int`, then this function returns a
1055
+ multigraph (of the same type as `create_using`) with parallel edges.
1056
+
1057
+ If `create_using` indicates an undirected multigraph, then only the edges
1058
+ indicated by the upper triangle of the array `A` will be added to the
1059
+ graph.
1060
+
1061
+ If `edge_attr` is Falsy (False or None), edge attributes will not be
1062
+ assigned, and the array data will be treated like a binary mask of
1063
+ edge presence or absence. Otherwise, the attributes will be assigned
1064
+ as follows:
1065
+
1066
+ If the NumPy array has a single data type for each array entry it
1067
+ will be converted to an appropriate Python data type.
1068
+
1069
+ If the NumPy array has a user-specified compound data type the names
1070
+ of the data fields will be used as attribute keys in the resulting
1071
+ NetworkX graph.
1072
+
1073
+ See Also
1074
+ --------
1075
+ to_numpy_array
1076
+
1077
+ Examples
1078
+ --------
1079
+ Simple integer weights on edges:
1080
+
1081
+ >>> import numpy as np
1082
+ >>> A = np.array([[1, 1], [2, 1]])
1083
+ >>> G = nx.from_numpy_array(A)
1084
+ >>> G.edges(data=True)
1085
+ EdgeDataView([(0, 0, {'weight': 1}), (0, 1, {'weight': 2}), (1, 1, {'weight': 1})])
1086
+
1087
+ If `create_using` indicates a multigraph and the array has only integer
1088
+ entries and `parallel_edges` is False, then the entries will be treated
1089
+ as weights for edges joining the nodes (without creating parallel edges):
1090
+
1091
+ >>> A = np.array([[1, 1], [1, 2]])
1092
+ >>> G = nx.from_numpy_array(A, create_using=nx.MultiGraph)
1093
+ >>> G[1][1]
1094
+ AtlasView({0: {'weight': 2}})
1095
+
1096
+ If `create_using` indicates a multigraph and the array has only integer
1097
+ entries and `parallel_edges` is True, then the entries will be treated
1098
+ as the number of parallel edges joining those two vertices:
1099
+
1100
+ >>> A = np.array([[1, 1], [1, 2]])
1101
+ >>> temp = nx.MultiGraph()
1102
+ >>> G = nx.from_numpy_array(A, parallel_edges=True, create_using=temp)
1103
+ >>> G[1][1]
1104
+ AtlasView({0: {'weight': 1}, 1: {'weight': 1}})
1105
+
1106
+ User defined compound data type on edges:
1107
+
1108
+ >>> dt = [("weight", float), ("cost", int)]
1109
+ >>> A = np.array([[(1.0, 2)]], dtype=dt)
1110
+ >>> G = nx.from_numpy_array(A)
1111
+ >>> G.edges()
1112
+ EdgeView([(0, 0)])
1113
+ >>> G[0][0]["cost"]
1114
+ 2
1115
+ >>> G[0][0]["weight"]
1116
+ 1.0
1117
+
1118
+ """
1119
+ kind_to_python_type = {
1120
+ "f": float,
1121
+ "i": int,
1122
+ "u": int,
1123
+ "b": bool,
1124
+ "c": complex,
1125
+ "S": str,
1126
+ "U": str,
1127
+ "V": "void",
1128
+ }
1129
+ G = nx.empty_graph(0, create_using)
1130
+ if A.ndim != 2:
1131
+ raise nx.NetworkXError(f"Input array must be 2D, not {A.ndim}")
1132
+ n, m = A.shape
1133
+ if n != m:
1134
+ raise nx.NetworkXError(f"Adjacency matrix not square: nx,ny={A.shape}")
1135
+ dt = A.dtype
1136
+ try:
1137
+ python_type = kind_to_python_type[dt.kind]
1138
+ except Exception as err:
1139
+ raise TypeError(f"Unknown numpy data type: {dt}") from err
1140
+
1141
+ # Make sure we get even the isolated nodes of the graph.
1142
+ G.add_nodes_from(range(n))
1143
+ # Get a list of all the entries in the array with nonzero entries. These
1144
+ # coordinates become edges in the graph. (convert to int from np.int64)
1145
+ edges = ((int(e[0]), int(e[1])) for e in zip(*A.nonzero()))
1146
+ # handle numpy constructed data type
1147
+ if python_type == "void":
1148
+ # Sort the fields by their offset, then by dtype, then by name.
1149
+ fields = sorted(
1150
+ (offset, dtype, name) for name, (dtype, offset) in A.dtype.fields.items()
1151
+ )
1152
+ triples = (
1153
+ (
1154
+ u,
1155
+ v,
1156
+ {}
1157
+ if edge_attr in [False, None]
1158
+ else {
1159
+ name: kind_to_python_type[dtype.kind](val)
1160
+ for (_, dtype, name), val in zip(fields, A[u, v])
1161
+ },
1162
+ )
1163
+ for u, v in edges
1164
+ )
1165
+ # If the entries in the adjacency matrix are integers, the graph is a
1166
+ # multigraph, and parallel_edges is True, then create parallel edges, each
1167
+ # with weight 1, for each entry in the adjacency matrix. Otherwise, create
1168
+ # one edge for each positive entry in the adjacency matrix and set the
1169
+ # weight of that edge to be the entry in the matrix.
1170
+ elif python_type is int and G.is_multigraph() and parallel_edges:
1171
+ chain = itertools.chain.from_iterable
1172
+ # The following line is equivalent to:
1173
+ #
1174
+ # for (u, v) in edges:
1175
+ # for d in range(A[u, v]):
1176
+ # G.add_edge(u, v, weight=1)
1177
+ #
1178
+ if edge_attr in [False, None]:
1179
+ triples = chain(((u, v, {}) for d in range(A[u, v])) for (u, v) in edges)
1180
+ else:
1181
+ triples = chain(
1182
+ ((u, v, {edge_attr: 1}) for d in range(A[u, v])) for (u, v) in edges
1183
+ )
1184
+ else: # basic data type
1185
+ if edge_attr in [False, None]:
1186
+ triples = ((u, v, {}) for u, v in edges)
1187
+ else:
1188
+ triples = ((u, v, {edge_attr: python_type(A[u, v])}) for u, v in edges)
1189
+ # If we are creating an undirected multigraph, only add the edges from the
1190
+ # upper triangle of the matrix. Otherwise, add all the edges. This relies
1191
+ # on the fact that the vertices created in the
1192
+ # `_generated_weighted_edges()` function are actually the row/column
1193
+ # indices for the matrix `A`.
1194
+ #
1195
+ # Without this check, we run into a problem where each edge is added twice
1196
+ # when `G.add_edges_from()` is invoked below.
1197
+ if G.is_multigraph() and not G.is_directed():
1198
+ triples = ((u, v, d) for u, v, d in triples if u <= v)
1199
+ G.add_edges_from(triples)
1200
+ return G
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/exception.py ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ **********
3
+ Exceptions
4
+ **********
5
+
6
+ Base exceptions and errors for NetworkX.
7
+ """
8
+
9
+ __all__ = [
10
+ "HasACycle",
11
+ "NodeNotFound",
12
+ "PowerIterationFailedConvergence",
13
+ "ExceededMaxIterations",
14
+ "AmbiguousSolution",
15
+ "NetworkXAlgorithmError",
16
+ "NetworkXException",
17
+ "NetworkXError",
18
+ "NetworkXNoCycle",
19
+ "NetworkXNoPath",
20
+ "NetworkXNotImplemented",
21
+ "NetworkXPointlessConcept",
22
+ "NetworkXUnbounded",
23
+ "NetworkXUnfeasible",
24
+ ]
25
+
26
+
27
+ class NetworkXException(Exception):
28
+ """Base class for exceptions in NetworkX."""
29
+
30
+
31
+ class NetworkXError(NetworkXException):
32
+ """Exception for a serious error in NetworkX"""
33
+
34
+
35
+ class NetworkXPointlessConcept(NetworkXException):
36
+ """Raised when a null graph is provided as input to an algorithm
37
+ that cannot use it.
38
+
39
+ The null graph is sometimes considered a pointless concept [1]_,
40
+ thus the name of the exception.
41
+
42
+ References
43
+ ----------
44
+ .. [1] Harary, F. and Read, R. "Is the Null Graph a Pointless
45
+ Concept?" In Graphs and Combinatorics Conference, George
46
+ Washington University. New York: Springer-Verlag, 1973.
47
+
48
+ """
49
+
50
+
51
+ class NetworkXAlgorithmError(NetworkXException):
52
+ """Exception for unexpected termination of algorithms."""
53
+
54
+
55
+ class NetworkXUnfeasible(NetworkXAlgorithmError):
56
+ """Exception raised by algorithms trying to solve a problem
57
+ instance that has no feasible solution."""
58
+
59
+
60
+ class NetworkXNoPath(NetworkXUnfeasible):
61
+ """Exception for algorithms that should return a path when running
62
+ on graphs where such a path does not exist."""
63
+
64
+
65
+ class NetworkXNoCycle(NetworkXUnfeasible):
66
+ """Exception for algorithms that should return a cycle when running
67
+ on graphs where such a cycle does not exist."""
68
+
69
+
70
+ class HasACycle(NetworkXException):
71
+ """Raised if a graph has a cycle when an algorithm expects that it
72
+ will have no cycles.
73
+
74
+ """
75
+
76
+
77
+ class NetworkXUnbounded(NetworkXAlgorithmError):
78
+ """Exception raised by algorithms trying to solve a maximization
79
+ or a minimization problem instance that is unbounded."""
80
+
81
+
82
+ class NetworkXNotImplemented(NetworkXException):
83
+ """Exception raised by algorithms not implemented for a type of graph."""
84
+
85
+
86
+ class NodeNotFound(NetworkXException):
87
+ """Exception raised if requested node is not present in the graph"""
88
+
89
+
90
+ class AmbiguousSolution(NetworkXException):
91
+ """Raised if more than one valid solution exists for an intermediary step
92
+ of an algorithm.
93
+
94
+ In the face of ambiguity, refuse the temptation to guess.
95
+ This may occur, for example, when trying to determine the
96
+ bipartite node sets in a disconnected bipartite graph when
97
+ computing bipartite matchings.
98
+
99
+ """
100
+
101
+
102
+ class ExceededMaxIterations(NetworkXException):
103
+ """Raised if a loop iterates too many times without breaking.
104
+
105
+ This may occur, for example, in an algorithm that computes
106
+ progressively better approximations to a value but exceeds an
107
+ iteration bound specified by the user.
108
+
109
+ """
110
+
111
+
112
+ class PowerIterationFailedConvergence(ExceededMaxIterations):
113
+ """Raised when the power iteration method fails to converge within a
114
+ specified iteration limit.
115
+
116
+ `num_iterations` is the number of iterations that have been
117
+ completed when this exception was raised.
118
+
119
+ """
120
+
121
+ def __init__(self, num_iterations, *args, **kw):
122
+ msg = f"power iteration failed to converge within {num_iterations} iterations"
123
+ exception_message = msg
124
+ superinit = super().__init__
125
+ superinit(self, exception_message, *args, **kw)
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/linalg/__pycache__/attrmatrix.cpython-311.pyc ADDED
Binary file (18.2 kB). View file
 
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/linalg/__pycache__/spectrum.cpython-311.pyc ADDED
Binary file (5.82 kB). View file
 
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/linalg/algebraicconnectivity.py ADDED
@@ -0,0 +1,656 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Algebraic connectivity and Fiedler vectors of undirected graphs.
3
+ """
4
+ from functools import partial
5
+
6
+ import networkx as nx
7
+ from networkx.utils import (
8
+ not_implemented_for,
9
+ np_random_state,
10
+ reverse_cuthill_mckee_ordering,
11
+ )
12
+
13
+ __all__ = [
14
+ "algebraic_connectivity",
15
+ "fiedler_vector",
16
+ "spectral_ordering",
17
+ "spectral_bisection",
18
+ ]
19
+
20
+
21
+ class _PCGSolver:
22
+ """Preconditioned conjugate gradient method.
23
+
24
+ To solve Ax = b:
25
+ M = A.diagonal() # or some other preconditioner
26
+ solver = _PCGSolver(lambda x: A * x, lambda x: M * x)
27
+ x = solver.solve(b)
28
+
29
+ The inputs A and M are functions which compute
30
+ matrix multiplication on the argument.
31
+ A - multiply by the matrix A in Ax=b
32
+ M - multiply by M, the preconditioner surrogate for A
33
+
34
+ Warning: There is no limit on number of iterations.
35
+ """
36
+
37
+ def __init__(self, A, M):
38
+ self._A = A
39
+ self._M = M
40
+
41
+ def solve(self, B, tol):
42
+ import numpy as np
43
+
44
+ # Densifying step - can this be kept sparse?
45
+ B = np.asarray(B)
46
+ X = np.ndarray(B.shape, order="F")
47
+ for j in range(B.shape[1]):
48
+ X[:, j] = self._solve(B[:, j], tol)
49
+ return X
50
+
51
+ def _solve(self, b, tol):
52
+ import numpy as np
53
+ import scipy as sp
54
+
55
+ A = self._A
56
+ M = self._M
57
+ tol *= sp.linalg.blas.dasum(b)
58
+ # Initialize.
59
+ x = np.zeros(b.shape)
60
+ r = b.copy()
61
+ z = M(r)
62
+ rz = sp.linalg.blas.ddot(r, z)
63
+ p = z.copy()
64
+ # Iterate.
65
+ while True:
66
+ Ap = A(p)
67
+ alpha = rz / sp.linalg.blas.ddot(p, Ap)
68
+ x = sp.linalg.blas.daxpy(p, x, a=alpha)
69
+ r = sp.linalg.blas.daxpy(Ap, r, a=-alpha)
70
+ if sp.linalg.blas.dasum(r) < tol:
71
+ return x
72
+ z = M(r)
73
+ beta = sp.linalg.blas.ddot(r, z)
74
+ beta, rz = beta / rz, beta
75
+ p = sp.linalg.blas.daxpy(p, z, a=beta)
76
+
77
+
78
+ class _LUSolver:
79
+ """LU factorization.
80
+
81
+ To solve Ax = b:
82
+ solver = _LUSolver(A)
83
+ x = solver.solve(b)
84
+
85
+ optional argument `tol` on solve method is ignored but included
86
+ to match _PCGsolver API.
87
+ """
88
+
89
+ def __init__(self, A):
90
+ import scipy as sp
91
+
92
+ self._LU = sp.sparse.linalg.splu(
93
+ A,
94
+ permc_spec="MMD_AT_PLUS_A",
95
+ diag_pivot_thresh=0.0,
96
+ options={"Equil": True, "SymmetricMode": True},
97
+ )
98
+
99
+ def solve(self, B, tol=None):
100
+ import numpy as np
101
+
102
+ B = np.asarray(B)
103
+ X = np.ndarray(B.shape, order="F")
104
+ for j in range(B.shape[1]):
105
+ X[:, j] = self._LU.solve(B[:, j])
106
+ return X
107
+
108
+
109
+ def _preprocess_graph(G, weight):
110
+ """Compute edge weights and eliminate zero-weight edges."""
111
+ if G.is_directed():
112
+ H = nx.MultiGraph()
113
+ H.add_nodes_from(G)
114
+ H.add_weighted_edges_from(
115
+ ((u, v, e.get(weight, 1.0)) for u, v, e in G.edges(data=True) if u != v),
116
+ weight=weight,
117
+ )
118
+ G = H
119
+ if not G.is_multigraph():
120
+ edges = (
121
+ (u, v, abs(e.get(weight, 1.0))) for u, v, e in G.edges(data=True) if u != v
122
+ )
123
+ else:
124
+ edges = (
125
+ (u, v, sum(abs(e.get(weight, 1.0)) for e in G[u][v].values()))
126
+ for u, v in G.edges()
127
+ if u != v
128
+ )
129
+ H = nx.Graph()
130
+ H.add_nodes_from(G)
131
+ H.add_weighted_edges_from((u, v, e) for u, v, e in edges if e != 0)
132
+ return H
133
+
134
+
135
+ def _rcm_estimate(G, nodelist):
136
+ """Estimate the Fiedler vector using the reverse Cuthill-McKee ordering."""
137
+ import numpy as np
138
+
139
+ G = G.subgraph(nodelist)
140
+ order = reverse_cuthill_mckee_ordering(G)
141
+ n = len(nodelist)
142
+ index = dict(zip(nodelist, range(n)))
143
+ x = np.ndarray(n, dtype=float)
144
+ for i, u in enumerate(order):
145
+ x[index[u]] = i
146
+ x -= (n - 1) / 2.0
147
+ return x
148
+
149
+
150
+ def _tracemin_fiedler(L, X, normalized, tol, method):
151
+ """Compute the Fiedler vector of L using the TraceMIN-Fiedler algorithm.
152
+
153
+ The Fiedler vector of a connected undirected graph is the eigenvector
154
+ corresponding to the second smallest eigenvalue of the Laplacian matrix
155
+ of the graph. This function starts with the Laplacian L, not the Graph.
156
+
157
+ Parameters
158
+ ----------
159
+ L : Laplacian of a possibly weighted or normalized, but undirected graph
160
+
161
+ X : Initial guess for a solution. Usually a matrix of random numbers.
162
+ This function allows more than one column in X to identify more than
163
+ one eigenvector if desired.
164
+
165
+ normalized : bool
166
+ Whether the normalized Laplacian matrix is used.
167
+
168
+ tol : float
169
+ Tolerance of relative residual in eigenvalue computation.
170
+ Warning: There is no limit on number of iterations.
171
+
172
+ method : string
173
+ Should be 'tracemin_pcg' or 'tracemin_lu'.
174
+ Otherwise exception is raised.
175
+
176
+ Returns
177
+ -------
178
+ sigma, X : Two NumPy arrays of floats.
179
+ The lowest eigenvalues and corresponding eigenvectors of L.
180
+ The size of input X determines the size of these outputs.
181
+ As this is for Fiedler vectors, the zero eigenvalue (and
182
+ constant eigenvector) are avoided.
183
+ """
184
+ import numpy as np
185
+ import scipy as sp
186
+
187
+ n = X.shape[0]
188
+
189
+ if normalized:
190
+ # Form the normalized Laplacian matrix and determine the eigenvector of
191
+ # its nullspace.
192
+ e = np.sqrt(L.diagonal())
193
+ # TODO: rm csr_array wrapper when spdiags array creation becomes available
194
+ D = sp.sparse.csr_array(sp.sparse.spdiags(1 / e, 0, n, n, format="csr"))
195
+ L = D @ L @ D
196
+ e *= 1.0 / np.linalg.norm(e, 2)
197
+
198
+ if normalized:
199
+
200
+ def project(X):
201
+ """Make X orthogonal to the nullspace of L."""
202
+ X = np.asarray(X)
203
+ for j in range(X.shape[1]):
204
+ X[:, j] -= (X[:, j] @ e) * e
205
+
206
+ else:
207
+
208
+ def project(X):
209
+ """Make X orthogonal to the nullspace of L."""
210
+ X = np.asarray(X)
211
+ for j in range(X.shape[1]):
212
+ X[:, j] -= X[:, j].sum() / n
213
+
214
+ if method == "tracemin_pcg":
215
+ D = L.diagonal().astype(float)
216
+ solver = _PCGSolver(lambda x: L @ x, lambda x: D * x)
217
+ elif method == "tracemin_lu":
218
+ # Convert A to CSC to suppress SparseEfficiencyWarning.
219
+ A = sp.sparse.csc_array(L, dtype=float, copy=True)
220
+ # Force A to be nonsingular. Since A is the Laplacian matrix of a
221
+ # connected graph, its rank deficiency is one, and thus one diagonal
222
+ # element needs to modified. Changing to infinity forces a zero in the
223
+ # corresponding element in the solution.
224
+ i = (A.indptr[1:] - A.indptr[:-1]).argmax()
225
+ A[i, i] = float("inf")
226
+ solver = _LUSolver(A)
227
+ else:
228
+ raise nx.NetworkXError(f"Unknown linear system solver: {method}")
229
+
230
+ # Initialize.
231
+ Lnorm = abs(L).sum(axis=1).flatten().max()
232
+ project(X)
233
+ W = np.ndarray(X.shape, order="F")
234
+
235
+ while True:
236
+ # Orthonormalize X.
237
+ X = np.linalg.qr(X)[0]
238
+ # Compute iteration matrix H.
239
+ W[:, :] = L @ X
240
+ H = X.T @ W
241
+ sigma, Y = sp.linalg.eigh(H, overwrite_a=True)
242
+ # Compute the Ritz vectors.
243
+ X = X @ Y
244
+ # Test for convergence exploiting the fact that L * X == W * Y.
245
+ res = sp.linalg.blas.dasum(W @ Y[:, 0] - sigma[0] * X[:, 0]) / Lnorm
246
+ if res < tol:
247
+ break
248
+ # Compute X = L \ X / (X' * (L \ X)).
249
+ # L \ X can have an arbitrary projection on the nullspace of L,
250
+ # which will be eliminated.
251
+ W[:, :] = solver.solve(X, tol)
252
+ X = (sp.linalg.inv(W.T @ X) @ W.T).T # Preserves Fortran storage order.
253
+ project(X)
254
+
255
+ return sigma, np.asarray(X)
256
+
257
+
258
+ def _get_fiedler_func(method):
259
+ """Returns a function that solves the Fiedler eigenvalue problem."""
260
+ import numpy as np
261
+
262
+ if method == "tracemin": # old style keyword <v2.1
263
+ method = "tracemin_pcg"
264
+ if method in ("tracemin_pcg", "tracemin_lu"):
265
+
266
+ def find_fiedler(L, x, normalized, tol, seed):
267
+ q = 1 if method == "tracemin_pcg" else min(4, L.shape[0] - 1)
268
+ X = np.asarray(seed.normal(size=(q, L.shape[0]))).T
269
+ sigma, X = _tracemin_fiedler(L, X, normalized, tol, method)
270
+ return sigma[0], X[:, 0]
271
+
272
+ elif method == "lanczos" or method == "lobpcg":
273
+
274
+ def find_fiedler(L, x, normalized, tol, seed):
275
+ import scipy as sp
276
+
277
+ L = sp.sparse.csc_array(L, dtype=float)
278
+ n = L.shape[0]
279
+ if normalized:
280
+ # TODO: rm csc_array wrapping when spdiags array becomes available
281
+ D = sp.sparse.csc_array(
282
+ sp.sparse.spdiags(
283
+ 1.0 / np.sqrt(L.diagonal()), [0], n, n, format="csc"
284
+ )
285
+ )
286
+ L = D @ L @ D
287
+ if method == "lanczos" or n < 10:
288
+ # Avoid LOBPCG when n < 10 due to
289
+ # https://github.com/scipy/scipy/issues/3592
290
+ # https://github.com/scipy/scipy/pull/3594
291
+ sigma, X = sp.sparse.linalg.eigsh(
292
+ L, 2, which="SM", tol=tol, return_eigenvectors=True
293
+ )
294
+ return sigma[1], X[:, 1]
295
+ else:
296
+ X = np.asarray(np.atleast_2d(x).T)
297
+ # TODO: rm csr_array wrapping when spdiags array becomes available
298
+ M = sp.sparse.csr_array(sp.sparse.spdiags(1.0 / L.diagonal(), 0, n, n))
299
+ Y = np.ones(n)
300
+ if normalized:
301
+ Y /= D.diagonal()
302
+ sigma, X = sp.sparse.linalg.lobpcg(
303
+ L, X, M=M, Y=np.atleast_2d(Y).T, tol=tol, maxiter=n, largest=False
304
+ )
305
+ return sigma[0], X[:, 0]
306
+
307
+ else:
308
+ raise nx.NetworkXError(f"unknown method {method!r}.")
309
+
310
+ return find_fiedler
311
+
312
+
313
+ @not_implemented_for("directed")
314
+ @np_random_state(5)
315
+ @nx._dispatch(edge_attrs="weight")
316
+ def algebraic_connectivity(
317
+ G, weight="weight", normalized=False, tol=1e-8, method="tracemin_pcg", seed=None
318
+ ):
319
+ r"""Returns the algebraic connectivity of an undirected graph.
320
+
321
+ The algebraic connectivity of a connected undirected graph is the second
322
+ smallest eigenvalue of its Laplacian matrix.
323
+
324
+ Parameters
325
+ ----------
326
+ G : NetworkX graph
327
+ An undirected graph.
328
+
329
+ weight : object, optional (default: None)
330
+ The data key used to determine the weight of each edge. If None, then
331
+ each edge has unit weight.
332
+
333
+ normalized : bool, optional (default: False)
334
+ Whether the normalized Laplacian matrix is used.
335
+
336
+ tol : float, optional (default: 1e-8)
337
+ Tolerance of relative residual in eigenvalue computation.
338
+
339
+ method : string, optional (default: 'tracemin_pcg')
340
+ Method of eigenvalue computation. It must be one of the tracemin
341
+ options shown below (TraceMIN), 'lanczos' (Lanczos iteration)
342
+ or 'lobpcg' (LOBPCG).
343
+
344
+ The TraceMIN algorithm uses a linear system solver. The following
345
+ values allow specifying the solver to be used.
346
+
347
+ =============== ========================================
348
+ Value Solver
349
+ =============== ========================================
350
+ 'tracemin_pcg' Preconditioned conjugate gradient method
351
+ 'tracemin_lu' LU factorization
352
+ =============== ========================================
353
+
354
+ seed : integer, random_state, or None (default)
355
+ Indicator of random number generation state.
356
+ See :ref:`Randomness<randomness>`.
357
+
358
+ Returns
359
+ -------
360
+ algebraic_connectivity : float
361
+ Algebraic connectivity.
362
+
363
+ Raises
364
+ ------
365
+ NetworkXNotImplemented
366
+ If G is directed.
367
+
368
+ NetworkXError
369
+ If G has less than two nodes.
370
+
371
+ Notes
372
+ -----
373
+ Edge weights are interpreted by their absolute values. For MultiGraph's,
374
+ weights of parallel edges are summed. Zero-weighted edges are ignored.
375
+
376
+ See Also
377
+ --------
378
+ laplacian_matrix
379
+
380
+ Examples
381
+ --------
382
+ For undirected graphs algebraic connectivity can tell us if a graph is connected or not
383
+ `G` is connected iff ``algebraic_connectivity(G) > 0``:
384
+
385
+ >>> G = nx.complete_graph(5)
386
+ >>> nx.algebraic_connectivity(G) > 0
387
+ True
388
+ >>> G.add_node(10) # G is no longer connected
389
+ >>> nx.algebraic_connectivity(G) > 0
390
+ False
391
+
392
+ """
393
+ if len(G) < 2:
394
+ raise nx.NetworkXError("graph has less than two nodes.")
395
+ G = _preprocess_graph(G, weight)
396
+ if not nx.is_connected(G):
397
+ return 0.0
398
+
399
+ L = nx.laplacian_matrix(G)
400
+ if L.shape[0] == 2:
401
+ return 2.0 * L[0, 0] if not normalized else 2.0
402
+
403
+ find_fiedler = _get_fiedler_func(method)
404
+ x = None if method != "lobpcg" else _rcm_estimate(G, G)
405
+ sigma, fiedler = find_fiedler(L, x, normalized, tol, seed)
406
+ return sigma
407
+
408
+
409
+ @not_implemented_for("directed")
410
+ @np_random_state(5)
411
+ @nx._dispatch(edge_attrs="weight")
412
+ def fiedler_vector(
413
+ G, weight="weight", normalized=False, tol=1e-8, method="tracemin_pcg", seed=None
414
+ ):
415
+ """Returns the Fiedler vector of a connected undirected graph.
416
+
417
+ The Fiedler vector of a connected undirected graph is the eigenvector
418
+ corresponding to the second smallest eigenvalue of the Laplacian matrix
419
+ of the graph.
420
+
421
+ Parameters
422
+ ----------
423
+ G : NetworkX graph
424
+ An undirected graph.
425
+
426
+ weight : object, optional (default: None)
427
+ The data key used to determine the weight of each edge. If None, then
428
+ each edge has unit weight.
429
+
430
+ normalized : bool, optional (default: False)
431
+ Whether the normalized Laplacian matrix is used.
432
+
433
+ tol : float, optional (default: 1e-8)
434
+ Tolerance of relative residual in eigenvalue computation.
435
+
436
+ method : string, optional (default: 'tracemin_pcg')
437
+ Method of eigenvalue computation. It must be one of the tracemin
438
+ options shown below (TraceMIN), 'lanczos' (Lanczos iteration)
439
+ or 'lobpcg' (LOBPCG).
440
+
441
+ The TraceMIN algorithm uses a linear system solver. The following
442
+ values allow specifying the solver to be used.
443
+
444
+ =============== ========================================
445
+ Value Solver
446
+ =============== ========================================
447
+ 'tracemin_pcg' Preconditioned conjugate gradient method
448
+ 'tracemin_lu' LU factorization
449
+ =============== ========================================
450
+
451
+ seed : integer, random_state, or None (default)
452
+ Indicator of random number generation state.
453
+ See :ref:`Randomness<randomness>`.
454
+
455
+ Returns
456
+ -------
457
+ fiedler_vector : NumPy array of floats.
458
+ Fiedler vector.
459
+
460
+ Raises
461
+ ------
462
+ NetworkXNotImplemented
463
+ If G is directed.
464
+
465
+ NetworkXError
466
+ If G has less than two nodes or is not connected.
467
+
468
+ Notes
469
+ -----
470
+ Edge weights are interpreted by their absolute values. For MultiGraph's,
471
+ weights of parallel edges are summed. Zero-weighted edges are ignored.
472
+
473
+ See Also
474
+ --------
475
+ laplacian_matrix
476
+
477
+ Examples
478
+ --------
479
+ Given a connected graph the signs of the values in the Fiedler vector can be
480
+ used to partition the graph into two components.
481
+
482
+ >>> G = nx.barbell_graph(5, 0)
483
+ >>> nx.fiedler_vector(G, normalized=True, seed=1)
484
+ array([-0.32864129, -0.32864129, -0.32864129, -0.32864129, -0.26072899,
485
+ 0.26072899, 0.32864129, 0.32864129, 0.32864129, 0.32864129])
486
+
487
+ The connected components are the two 5-node cliques of the barbell graph.
488
+ """
489
+ import numpy as np
490
+
491
+ if len(G) < 2:
492
+ raise nx.NetworkXError("graph has less than two nodes.")
493
+ G = _preprocess_graph(G, weight)
494
+ if not nx.is_connected(G):
495
+ raise nx.NetworkXError("graph is not connected.")
496
+
497
+ if len(G) == 2:
498
+ return np.array([1.0, -1.0])
499
+
500
+ find_fiedler = _get_fiedler_func(method)
501
+ L = nx.laplacian_matrix(G)
502
+ x = None if method != "lobpcg" else _rcm_estimate(G, G)
503
+ sigma, fiedler = find_fiedler(L, x, normalized, tol, seed)
504
+ return fiedler
505
+
506
+
507
+ @np_random_state(5)
508
+ @nx._dispatch(edge_attrs="weight")
509
+ def spectral_ordering(
510
+ G, weight="weight", normalized=False, tol=1e-8, method="tracemin_pcg", seed=None
511
+ ):
512
+ """Compute the spectral_ordering of a graph.
513
+
514
+ The spectral ordering of a graph is an ordering of its nodes where nodes
515
+ in the same weakly connected components appear contiguous and ordered by
516
+ their corresponding elements in the Fiedler vector of the component.
517
+
518
+ Parameters
519
+ ----------
520
+ G : NetworkX graph
521
+ A graph.
522
+
523
+ weight : object, optional (default: None)
524
+ The data key used to determine the weight of each edge. If None, then
525
+ each edge has unit weight.
526
+
527
+ normalized : bool, optional (default: False)
528
+ Whether the normalized Laplacian matrix is used.
529
+
530
+ tol : float, optional (default: 1e-8)
531
+ Tolerance of relative residual in eigenvalue computation.
532
+
533
+ method : string, optional (default: 'tracemin_pcg')
534
+ Method of eigenvalue computation. It must be one of the tracemin
535
+ options shown below (TraceMIN), 'lanczos' (Lanczos iteration)
536
+ or 'lobpcg' (LOBPCG).
537
+
538
+ The TraceMIN algorithm uses a linear system solver. The following
539
+ values allow specifying the solver to be used.
540
+
541
+ =============== ========================================
542
+ Value Solver
543
+ =============== ========================================
544
+ 'tracemin_pcg' Preconditioned conjugate gradient method
545
+ 'tracemin_lu' LU factorization
546
+ =============== ========================================
547
+
548
+ seed : integer, random_state, or None (default)
549
+ Indicator of random number generation state.
550
+ See :ref:`Randomness<randomness>`.
551
+
552
+ Returns
553
+ -------
554
+ spectral_ordering : NumPy array of floats.
555
+ Spectral ordering of nodes.
556
+
557
+ Raises
558
+ ------
559
+ NetworkXError
560
+ If G is empty.
561
+
562
+ Notes
563
+ -----
564
+ Edge weights are interpreted by their absolute values. For MultiGraph's,
565
+ weights of parallel edges are summed. Zero-weighted edges are ignored.
566
+
567
+ See Also
568
+ --------
569
+ laplacian_matrix
570
+ """
571
+ if len(G) == 0:
572
+ raise nx.NetworkXError("graph is empty.")
573
+ G = _preprocess_graph(G, weight)
574
+
575
+ find_fiedler = _get_fiedler_func(method)
576
+ order = []
577
+ for component in nx.connected_components(G):
578
+ size = len(component)
579
+ if size > 2:
580
+ L = nx.laplacian_matrix(G, component)
581
+ x = None if method != "lobpcg" else _rcm_estimate(G, component)
582
+ sigma, fiedler = find_fiedler(L, x, normalized, tol, seed)
583
+ sort_info = zip(fiedler, range(size), component)
584
+ order.extend(u for x, c, u in sorted(sort_info))
585
+ else:
586
+ order.extend(component)
587
+
588
+ return order
589
+
590
+
591
+ @nx._dispatch(edge_attrs="weight")
592
+ def spectral_bisection(
593
+ G, weight="weight", normalized=False, tol=1e-8, method="tracemin_pcg", seed=None
594
+ ):
595
+ """Bisect the graph using the Fiedler vector.
596
+
597
+ This method uses the Fiedler vector to bisect a graph.
598
+ The partition is defined by the nodes which are associated with
599
+ either positive or negative values in the vector.
600
+
601
+ Parameters
602
+ ----------
603
+ G : NetworkX Graph
604
+
605
+ weight : str, optional (default: weight)
606
+ The data key used to determine the weight of each edge. If None, then
607
+ each edge has unit weight.
608
+
609
+ normalized : bool, optional (default: False)
610
+ Whether the normalized Laplacian matrix is used.
611
+
612
+ tol : float, optional (default: 1e-8)
613
+ Tolerance of relative residual in eigenvalue computation.
614
+
615
+ method : string, optional (default: 'tracemin_pcg')
616
+ Method of eigenvalue computation. It must be one of the tracemin
617
+ options shown below (TraceMIN), 'lanczos' (Lanczos iteration)
618
+ or 'lobpcg' (LOBPCG).
619
+
620
+ The TraceMIN algorithm uses a linear system solver. The following
621
+ values allow specifying the solver to be used.
622
+
623
+ =============== ========================================
624
+ Value Solver
625
+ =============== ========================================
626
+ 'tracemin_pcg' Preconditioned conjugate gradient method
627
+ 'tracemin_lu' LU factorization
628
+ =============== ========================================
629
+
630
+ seed : integer, random_state, or None (default)
631
+ Indicator of random number generation state.
632
+ See :ref:`Randomness<randomness>`.
633
+
634
+ Returns
635
+ -------
636
+ bisection : tuple of sets
637
+ Sets with the bisection of nodes
638
+
639
+ Examples
640
+ --------
641
+ >>> G = nx.barbell_graph(3, 0)
642
+ >>> nx.spectral_bisection(G)
643
+ ({0, 1, 2}, {3, 4, 5})
644
+
645
+ References
646
+ ----------
647
+ .. [1] M. E. J Newman 'Networks: An Introduction', pages 364-370
648
+ Oxford University Press 2011.
649
+ """
650
+ import numpy as np
651
+
652
+ v = nx.fiedler_vector(G, weight, normalized, tol, method, seed)
653
+ nodes = np.array(list(G))
654
+ pos_vals = v >= 0
655
+
656
+ return set(nodes[~pos_vals]), set(nodes[pos_vals])
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/linalg/bethehessianmatrix.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Bethe Hessian or deformed Laplacian matrix of graphs."""
2
+ import networkx as nx
3
+ from networkx.utils import not_implemented_for
4
+
5
+ __all__ = ["bethe_hessian_matrix"]
6
+
7
+
8
+ @not_implemented_for("directed")
9
+ @not_implemented_for("multigraph")
10
+ @nx._dispatch
11
+ def bethe_hessian_matrix(G, r=None, nodelist=None):
12
+ r"""Returns the Bethe Hessian matrix of G.
13
+
14
+ The Bethe Hessian is a family of matrices parametrized by r, defined as
15
+ H(r) = (r^2 - 1) I - r A + D where A is the adjacency matrix, D is the
16
+ diagonal matrix of node degrees, and I is the identify matrix. It is equal
17
+ to the graph laplacian when the regularizer r = 1.
18
+
19
+ The default choice of regularizer should be the ratio [2]_
20
+
21
+ .. math::
22
+ r_m = \left(\sum k_i \right)^{-1}\left(\sum k_i^2 \right) - 1
23
+
24
+ Parameters
25
+ ----------
26
+ G : Graph
27
+ A NetworkX graph
28
+ r : float
29
+ Regularizer parameter
30
+ nodelist : list, optional
31
+ The rows and columns are ordered according to the nodes in nodelist.
32
+ If nodelist is None, then the ordering is produced by ``G.nodes()``.
33
+
34
+ Returns
35
+ -------
36
+ H : scipy.sparse.csr_array
37
+ The Bethe Hessian matrix of `G`, with parameter `r`.
38
+
39
+ Examples
40
+ --------
41
+ >>> k = [3, 2, 2, 1, 0]
42
+ >>> G = nx.havel_hakimi_graph(k)
43
+ >>> H = nx.bethe_hessian_matrix(G)
44
+ >>> H.toarray()
45
+ array([[ 3.5625, -1.25 , -1.25 , -1.25 , 0. ],
46
+ [-1.25 , 2.5625, -1.25 , 0. , 0. ],
47
+ [-1.25 , -1.25 , 2.5625, 0. , 0. ],
48
+ [-1.25 , 0. , 0. , 1.5625, 0. ],
49
+ [ 0. , 0. , 0. , 0. , 0.5625]])
50
+
51
+ See Also
52
+ --------
53
+ bethe_hessian_spectrum
54
+ adjacency_matrix
55
+ laplacian_matrix
56
+
57
+ References
58
+ ----------
59
+ .. [1] A. Saade, F. Krzakala and L. Zdeborová
60
+ "Spectral Clustering of Graphs with the Bethe Hessian",
61
+ Advances in Neural Information Processing Systems, 2014.
62
+ .. [2] C. M. Le, E. Levina
63
+ "Estimating the number of communities in networks by spectral methods"
64
+ arXiv:1507.00827, 2015.
65
+ """
66
+ import scipy as sp
67
+
68
+ if nodelist is None:
69
+ nodelist = list(G)
70
+ if r is None:
71
+ r = sum(d**2 for v, d in nx.degree(G)) / sum(d for v, d in nx.degree(G)) - 1
72
+ A = nx.to_scipy_sparse_array(G, nodelist=nodelist, format="csr")
73
+ n, m = A.shape
74
+ # TODO: Rm csr_array wrapper when spdiags array creation becomes available
75
+ D = sp.sparse.csr_array(sp.sparse.spdiags(A.sum(axis=1), 0, m, n, format="csr"))
76
+ # TODO: Rm csr_array wrapper when eye array creation becomes available
77
+ I = sp.sparse.csr_array(sp.sparse.eye(m, n, format="csr"))
78
+ return (r**2 - 1) * I - r * A + D
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/linalg/tests/__pycache__/test_bethehessian.cpython-311.pyc ADDED
Binary file (2.83 kB). View file
 
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/readwrite/__pycache__/edgelist.cpython-311.pyc ADDED
Binary file (16.4 kB). View file
 
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/readwrite/__pycache__/gexf.cpython-311.pyc ADDED
Binary file (49.5 kB). View file
 
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/readwrite/__pycache__/gml.cpython-311.pyc ADDED
Binary file (39.1 kB). View file
 
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/readwrite/__pycache__/graphml.cpython-311.pyc ADDED
Binary file (47.5 kB). View file
 
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/readwrite/__pycache__/multiline_adjlist.cpython-311.pyc ADDED
Binary file (14.4 kB). View file
 
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/readwrite/gexf.py ADDED
@@ -0,0 +1,1065 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Read and write graphs in GEXF format.
2
+
3
+ .. warning::
4
+ This parser uses the standard xml library present in Python, which is
5
+ insecure - see :external+python:mod:`xml` for additional information.
6
+ Only parse GEFX files you trust.
7
+
8
+ GEXF (Graph Exchange XML Format) is a language for describing complex
9
+ network structures, their associated data and dynamics.
10
+
11
+ This implementation does not support mixed graphs (directed and
12
+ undirected edges together).
13
+
14
+ Format
15
+ ------
16
+ GEXF is an XML format. See http://gexf.net/schema.html for the
17
+ specification and http://gexf.net/basic.html for examples.
18
+ """
19
+ import itertools
20
+ import time
21
+ from xml.etree.ElementTree import (
22
+ Element,
23
+ ElementTree,
24
+ SubElement,
25
+ register_namespace,
26
+ tostring,
27
+ )
28
+
29
+ import networkx as nx
30
+ from networkx.utils import open_file
31
+
32
+ __all__ = ["write_gexf", "read_gexf", "relabel_gexf_graph", "generate_gexf"]
33
+
34
+
35
+ @open_file(1, mode="wb")
36
+ def write_gexf(G, path, encoding="utf-8", prettyprint=True, version="1.2draft"):
37
+ """Write G in GEXF format to path.
38
+
39
+ "GEXF (Graph Exchange XML Format) is a language for describing
40
+ complex networks structures, their associated data and dynamics" [1]_.
41
+
42
+ Node attributes are checked according to the version of the GEXF
43
+ schemas used for parameters which are not user defined,
44
+ e.g. visualization 'viz' [2]_. See example for usage.
45
+
46
+ Parameters
47
+ ----------
48
+ G : graph
49
+ A NetworkX graph
50
+ path : file or string
51
+ File or file name to write.
52
+ File names ending in .gz or .bz2 will be compressed.
53
+ encoding : string (optional, default: 'utf-8')
54
+ Encoding for text data.
55
+ prettyprint : bool (optional, default: True)
56
+ If True use line breaks and indenting in output XML.
57
+ version: string (optional, default: '1.2draft')
58
+ The version of GEXF to be used for nodes attributes checking
59
+
60
+ Examples
61
+ --------
62
+ >>> G = nx.path_graph(4)
63
+ >>> nx.write_gexf(G, "test.gexf")
64
+
65
+ # visualization data
66
+ >>> G.nodes[0]["viz"] = {"size": 54}
67
+ >>> G.nodes[0]["viz"]["position"] = {"x": 0, "y": 1}
68
+ >>> G.nodes[0]["viz"]["color"] = {"r": 0, "g": 0, "b": 256}
69
+
70
+
71
+ Notes
72
+ -----
73
+ This implementation does not support mixed graphs (directed and undirected
74
+ edges together).
75
+
76
+ The node id attribute is set to be the string of the node label.
77
+ If you want to specify an id use set it as node data, e.g.
78
+ node['a']['id']=1 to set the id of node 'a' to 1.
79
+
80
+ References
81
+ ----------
82
+ .. [1] GEXF File Format, http://gexf.net/
83
+ .. [2] GEXF schema, http://gexf.net/schema.html
84
+ """
85
+ writer = GEXFWriter(encoding=encoding, prettyprint=prettyprint, version=version)
86
+ writer.add_graph(G)
87
+ writer.write(path)
88
+
89
+
90
+ def generate_gexf(G, encoding="utf-8", prettyprint=True, version="1.2draft"):
91
+ """Generate lines of GEXF format representation of G.
92
+
93
+ "GEXF (Graph Exchange XML Format) is a language for describing
94
+ complex networks structures, their associated data and dynamics" [1]_.
95
+
96
+ Parameters
97
+ ----------
98
+ G : graph
99
+ A NetworkX graph
100
+ encoding : string (optional, default: 'utf-8')
101
+ Encoding for text data.
102
+ prettyprint : bool (optional, default: True)
103
+ If True use line breaks and indenting in output XML.
104
+ version : string (default: 1.2draft)
105
+ Version of GEFX File Format (see http://gexf.net/schema.html)
106
+ Supported values: "1.1draft", "1.2draft"
107
+
108
+
109
+ Examples
110
+ --------
111
+ >>> G = nx.path_graph(4)
112
+ >>> linefeed = chr(10) # linefeed=\n
113
+ >>> s = linefeed.join(nx.generate_gexf(G))
114
+ >>> for line in nx.generate_gexf(G): # doctest: +SKIP
115
+ ... print(line)
116
+
117
+ Notes
118
+ -----
119
+ This implementation does not support mixed graphs (directed and undirected
120
+ edges together).
121
+
122
+ The node id attribute is set to be the string of the node label.
123
+ If you want to specify an id use set it as node data, e.g.
124
+ node['a']['id']=1 to set the id of node 'a' to 1.
125
+
126
+ References
127
+ ----------
128
+ .. [1] GEXF File Format, https://gephi.org/gexf/format/
129
+ """
130
+ writer = GEXFWriter(encoding=encoding, prettyprint=prettyprint, version=version)
131
+ writer.add_graph(G)
132
+ yield from str(writer).splitlines()
133
+
134
+
135
+ @open_file(0, mode="rb")
136
+ @nx._dispatch(graphs=None)
137
+ def read_gexf(path, node_type=None, relabel=False, version="1.2draft"):
138
+ """Read graph in GEXF format from path.
139
+
140
+ "GEXF (Graph Exchange XML Format) is a language for describing
141
+ complex networks structures, their associated data and dynamics" [1]_.
142
+
143
+ Parameters
144
+ ----------
145
+ path : file or string
146
+ File or file name to read.
147
+ File names ending in .gz or .bz2 will be decompressed.
148
+ node_type: Python type (default: None)
149
+ Convert node ids to this type if not None.
150
+ relabel : bool (default: False)
151
+ If True relabel the nodes to use the GEXF node "label" attribute
152
+ instead of the node "id" attribute as the NetworkX node label.
153
+ version : string (default: 1.2draft)
154
+ Version of GEFX File Format (see http://gexf.net/schema.html)
155
+ Supported values: "1.1draft", "1.2draft"
156
+
157
+ Returns
158
+ -------
159
+ graph: NetworkX graph
160
+ If no parallel edges are found a Graph or DiGraph is returned.
161
+ Otherwise a MultiGraph or MultiDiGraph is returned.
162
+
163
+ Notes
164
+ -----
165
+ This implementation does not support mixed graphs (directed and undirected
166
+ edges together).
167
+
168
+ References
169
+ ----------
170
+ .. [1] GEXF File Format, http://gexf.net/
171
+ """
172
+ reader = GEXFReader(node_type=node_type, version=version)
173
+ if relabel:
174
+ G = relabel_gexf_graph(reader(path))
175
+ else:
176
+ G = reader(path)
177
+ return G
178
+
179
+
180
+ class GEXF:
181
+ versions = {
182
+ "1.1draft": {
183
+ "NS_GEXF": "http://www.gexf.net/1.1draft",
184
+ "NS_VIZ": "http://www.gexf.net/1.1draft/viz",
185
+ "NS_XSI": "http://www.w3.org/2001/XMLSchema-instance",
186
+ "SCHEMALOCATION": " ".join(
187
+ [
188
+ "http://www.gexf.net/1.1draft",
189
+ "http://www.gexf.net/1.1draft/gexf.xsd",
190
+ ]
191
+ ),
192
+ "VERSION": "1.1",
193
+ },
194
+ "1.2draft": {
195
+ "NS_GEXF": "http://www.gexf.net/1.2draft",
196
+ "NS_VIZ": "http://www.gexf.net/1.2draft/viz",
197
+ "NS_XSI": "http://www.w3.org/2001/XMLSchema-instance",
198
+ "SCHEMALOCATION": " ".join(
199
+ [
200
+ "http://www.gexf.net/1.2draft",
201
+ "http://www.gexf.net/1.2draft/gexf.xsd",
202
+ ]
203
+ ),
204
+ "VERSION": "1.2",
205
+ },
206
+ }
207
+
208
+ def construct_types(self):
209
+ types = [
210
+ (int, "integer"),
211
+ (float, "float"),
212
+ (float, "double"),
213
+ (bool, "boolean"),
214
+ (list, "string"),
215
+ (dict, "string"),
216
+ (int, "long"),
217
+ (str, "liststring"),
218
+ (str, "anyURI"),
219
+ (str, "string"),
220
+ ]
221
+
222
+ # These additions to types allow writing numpy types
223
+ try:
224
+ import numpy as np
225
+ except ImportError:
226
+ pass
227
+ else:
228
+ # prepend so that python types are created upon read (last entry wins)
229
+ types = [
230
+ (np.float64, "float"),
231
+ (np.float32, "float"),
232
+ (np.float16, "float"),
233
+ (np.int_, "int"),
234
+ (np.int8, "int"),
235
+ (np.int16, "int"),
236
+ (np.int32, "int"),
237
+ (np.int64, "int"),
238
+ (np.uint8, "int"),
239
+ (np.uint16, "int"),
240
+ (np.uint32, "int"),
241
+ (np.uint64, "int"),
242
+ (np.int_, "int"),
243
+ (np.intc, "int"),
244
+ (np.intp, "int"),
245
+ ] + types
246
+
247
+ self.xml_type = dict(types)
248
+ self.python_type = dict(reversed(a) for a in types)
249
+
250
+ # http://www.w3.org/TR/xmlschema-2/#boolean
251
+ convert_bool = {
252
+ "true": True,
253
+ "false": False,
254
+ "True": True,
255
+ "False": False,
256
+ "0": False,
257
+ 0: False,
258
+ "1": True,
259
+ 1: True,
260
+ }
261
+
262
+ def set_version(self, version):
263
+ d = self.versions.get(version)
264
+ if d is None:
265
+ raise nx.NetworkXError(f"Unknown GEXF version {version}.")
266
+ self.NS_GEXF = d["NS_GEXF"]
267
+ self.NS_VIZ = d["NS_VIZ"]
268
+ self.NS_XSI = d["NS_XSI"]
269
+ self.SCHEMALOCATION = d["SCHEMALOCATION"]
270
+ self.VERSION = d["VERSION"]
271
+ self.version = version
272
+
273
+
274
+ class GEXFWriter(GEXF):
275
+ # class for writing GEXF format files
276
+ # use write_gexf() function
277
+ def __init__(
278
+ self, graph=None, encoding="utf-8", prettyprint=True, version="1.2draft"
279
+ ):
280
+ self.construct_types()
281
+ self.prettyprint = prettyprint
282
+ self.encoding = encoding
283
+ self.set_version(version)
284
+ self.xml = Element(
285
+ "gexf",
286
+ {
287
+ "xmlns": self.NS_GEXF,
288
+ "xmlns:xsi": self.NS_XSI,
289
+ "xsi:schemaLocation": self.SCHEMALOCATION,
290
+ "version": self.VERSION,
291
+ },
292
+ )
293
+
294
+ # Make meta element a non-graph element
295
+ # Also add lastmodifieddate as attribute, not tag
296
+ meta_element = Element("meta")
297
+ subelement_text = f"NetworkX {nx.__version__}"
298
+ SubElement(meta_element, "creator").text = subelement_text
299
+ meta_element.set("lastmodifieddate", time.strftime("%Y-%m-%d"))
300
+ self.xml.append(meta_element)
301
+
302
+ register_namespace("viz", self.NS_VIZ)
303
+
304
+ # counters for edge and attribute identifiers
305
+ self.edge_id = itertools.count()
306
+ self.attr_id = itertools.count()
307
+ self.all_edge_ids = set()
308
+ # default attributes are stored in dictionaries
309
+ self.attr = {}
310
+ self.attr["node"] = {}
311
+ self.attr["edge"] = {}
312
+ self.attr["node"]["dynamic"] = {}
313
+ self.attr["node"]["static"] = {}
314
+ self.attr["edge"]["dynamic"] = {}
315
+ self.attr["edge"]["static"] = {}
316
+
317
+ if graph is not None:
318
+ self.add_graph(graph)
319
+
320
+ def __str__(self):
321
+ if self.prettyprint:
322
+ self.indent(self.xml)
323
+ s = tostring(self.xml).decode(self.encoding)
324
+ return s
325
+
326
+ def add_graph(self, G):
327
+ # first pass through G collecting edge ids
328
+ for u, v, dd in G.edges(data=True):
329
+ eid = dd.get("id")
330
+ if eid is not None:
331
+ self.all_edge_ids.add(str(eid))
332
+ # set graph attributes
333
+ if G.graph.get("mode") == "dynamic":
334
+ mode = "dynamic"
335
+ else:
336
+ mode = "static"
337
+ # Add a graph element to the XML
338
+ if G.is_directed():
339
+ default = "directed"
340
+ else:
341
+ default = "undirected"
342
+ name = G.graph.get("name", "")
343
+ graph_element = Element("graph", defaultedgetype=default, mode=mode, name=name)
344
+ self.graph_element = graph_element
345
+ self.add_nodes(G, graph_element)
346
+ self.add_edges(G, graph_element)
347
+ self.xml.append(graph_element)
348
+
349
+ def add_nodes(self, G, graph_element):
350
+ nodes_element = Element("nodes")
351
+ for node, data in G.nodes(data=True):
352
+ node_data = data.copy()
353
+ node_id = str(node_data.pop("id", node))
354
+ kw = {"id": node_id}
355
+ label = str(node_data.pop("label", node))
356
+ kw["label"] = label
357
+ try:
358
+ pid = node_data.pop("pid")
359
+ kw["pid"] = str(pid)
360
+ except KeyError:
361
+ pass
362
+ try:
363
+ start = node_data.pop("start")
364
+ kw["start"] = str(start)
365
+ self.alter_graph_mode_timeformat(start)
366
+ except KeyError:
367
+ pass
368
+ try:
369
+ end = node_data.pop("end")
370
+ kw["end"] = str(end)
371
+ self.alter_graph_mode_timeformat(end)
372
+ except KeyError:
373
+ pass
374
+ # add node element with attributes
375
+ node_element = Element("node", **kw)
376
+ # add node element and attr subelements
377
+ default = G.graph.get("node_default", {})
378
+ node_data = self.add_parents(node_element, node_data)
379
+ if self.VERSION == "1.1":
380
+ node_data = self.add_slices(node_element, node_data)
381
+ else:
382
+ node_data = self.add_spells(node_element, node_data)
383
+ node_data = self.add_viz(node_element, node_data)
384
+ node_data = self.add_attributes("node", node_element, node_data, default)
385
+ nodes_element.append(node_element)
386
+ graph_element.append(nodes_element)
387
+
388
+ def add_edges(self, G, graph_element):
389
+ def edge_key_data(G):
390
+ # helper function to unify multigraph and graph edge iterator
391
+ if G.is_multigraph():
392
+ for u, v, key, data in G.edges(data=True, keys=True):
393
+ edge_data = data.copy()
394
+ edge_data.update(key=key)
395
+ edge_id = edge_data.pop("id", None)
396
+ if edge_id is None:
397
+ edge_id = next(self.edge_id)
398
+ while str(edge_id) in self.all_edge_ids:
399
+ edge_id = next(self.edge_id)
400
+ self.all_edge_ids.add(str(edge_id))
401
+ yield u, v, edge_id, edge_data
402
+ else:
403
+ for u, v, data in G.edges(data=True):
404
+ edge_data = data.copy()
405
+ edge_id = edge_data.pop("id", None)
406
+ if edge_id is None:
407
+ edge_id = next(self.edge_id)
408
+ while str(edge_id) in self.all_edge_ids:
409
+ edge_id = next(self.edge_id)
410
+ self.all_edge_ids.add(str(edge_id))
411
+ yield u, v, edge_id, edge_data
412
+
413
+ edges_element = Element("edges")
414
+ for u, v, key, edge_data in edge_key_data(G):
415
+ kw = {"id": str(key)}
416
+ try:
417
+ edge_label = edge_data.pop("label")
418
+ kw["label"] = str(edge_label)
419
+ except KeyError:
420
+ pass
421
+ try:
422
+ edge_weight = edge_data.pop("weight")
423
+ kw["weight"] = str(edge_weight)
424
+ except KeyError:
425
+ pass
426
+ try:
427
+ edge_type = edge_data.pop("type")
428
+ kw["type"] = str(edge_type)
429
+ except KeyError:
430
+ pass
431
+ try:
432
+ start = edge_data.pop("start")
433
+ kw["start"] = str(start)
434
+ self.alter_graph_mode_timeformat(start)
435
+ except KeyError:
436
+ pass
437
+ try:
438
+ end = edge_data.pop("end")
439
+ kw["end"] = str(end)
440
+ self.alter_graph_mode_timeformat(end)
441
+ except KeyError:
442
+ pass
443
+ source_id = str(G.nodes[u].get("id", u))
444
+ target_id = str(G.nodes[v].get("id", v))
445
+ edge_element = Element("edge", source=source_id, target=target_id, **kw)
446
+ default = G.graph.get("edge_default", {})
447
+ if self.VERSION == "1.1":
448
+ edge_data = self.add_slices(edge_element, edge_data)
449
+ else:
450
+ edge_data = self.add_spells(edge_element, edge_data)
451
+ edge_data = self.add_viz(edge_element, edge_data)
452
+ edge_data = self.add_attributes("edge", edge_element, edge_data, default)
453
+ edges_element.append(edge_element)
454
+ graph_element.append(edges_element)
455
+
456
+ def add_attributes(self, node_or_edge, xml_obj, data, default):
457
+ # Add attrvalues to node or edge
458
+ attvalues = Element("attvalues")
459
+ if len(data) == 0:
460
+ return data
461
+ mode = "static"
462
+ for k, v in data.items():
463
+ # rename generic multigraph key to avoid any name conflict
464
+ if k == "key":
465
+ k = "networkx_key"
466
+ val_type = type(v)
467
+ if val_type not in self.xml_type:
468
+ raise TypeError(f"attribute value type is not allowed: {val_type}")
469
+ if isinstance(v, list):
470
+ # dynamic data
471
+ for val, start, end in v:
472
+ val_type = type(val)
473
+ if start is not None or end is not None:
474
+ mode = "dynamic"
475
+ self.alter_graph_mode_timeformat(start)
476
+ self.alter_graph_mode_timeformat(end)
477
+ break
478
+ attr_id = self.get_attr_id(
479
+ str(k), self.xml_type[val_type], node_or_edge, default, mode
480
+ )
481
+ for val, start, end in v:
482
+ e = Element("attvalue")
483
+ e.attrib["for"] = attr_id
484
+ e.attrib["value"] = str(val)
485
+ # Handle nan, inf, -inf differently
486
+ if val_type == float:
487
+ if e.attrib["value"] == "inf":
488
+ e.attrib["value"] = "INF"
489
+ elif e.attrib["value"] == "nan":
490
+ e.attrib["value"] = "NaN"
491
+ elif e.attrib["value"] == "-inf":
492
+ e.attrib["value"] = "-INF"
493
+ if start is not None:
494
+ e.attrib["start"] = str(start)
495
+ if end is not None:
496
+ e.attrib["end"] = str(end)
497
+ attvalues.append(e)
498
+ else:
499
+ # static data
500
+ mode = "static"
501
+ attr_id = self.get_attr_id(
502
+ str(k), self.xml_type[val_type], node_or_edge, default, mode
503
+ )
504
+ e = Element("attvalue")
505
+ e.attrib["for"] = attr_id
506
+ if isinstance(v, bool):
507
+ e.attrib["value"] = str(v).lower()
508
+ else:
509
+ e.attrib["value"] = str(v)
510
+ # Handle float nan, inf, -inf differently
511
+ if val_type == float:
512
+ if e.attrib["value"] == "inf":
513
+ e.attrib["value"] = "INF"
514
+ elif e.attrib["value"] == "nan":
515
+ e.attrib["value"] = "NaN"
516
+ elif e.attrib["value"] == "-inf":
517
+ e.attrib["value"] = "-INF"
518
+ attvalues.append(e)
519
+ xml_obj.append(attvalues)
520
+ return data
521
+
522
+ def get_attr_id(self, title, attr_type, edge_or_node, default, mode):
523
+ # find the id of the attribute or generate a new id
524
+ try:
525
+ return self.attr[edge_or_node][mode][title]
526
+ except KeyError:
527
+ # generate new id
528
+ new_id = str(next(self.attr_id))
529
+ self.attr[edge_or_node][mode][title] = new_id
530
+ attr_kwargs = {"id": new_id, "title": title, "type": attr_type}
531
+ attribute = Element("attribute", **attr_kwargs)
532
+ # add subelement for data default value if present
533
+ default_title = default.get(title)
534
+ if default_title is not None:
535
+ default_element = Element("default")
536
+ default_element.text = str(default_title)
537
+ attribute.append(default_element)
538
+ # new insert it into the XML
539
+ attributes_element = None
540
+ for a in self.graph_element.findall("attributes"):
541
+ # find existing attributes element by class and mode
542
+ a_class = a.get("class")
543
+ a_mode = a.get("mode", "static")
544
+ if a_class == edge_or_node and a_mode == mode:
545
+ attributes_element = a
546
+ if attributes_element is None:
547
+ # create new attributes element
548
+ attr_kwargs = {"mode": mode, "class": edge_or_node}
549
+ attributes_element = Element("attributes", **attr_kwargs)
550
+ self.graph_element.insert(0, attributes_element)
551
+ attributes_element.append(attribute)
552
+ return new_id
553
+
554
+ def add_viz(self, element, node_data):
555
+ viz = node_data.pop("viz", False)
556
+ if viz:
557
+ color = viz.get("color")
558
+ if color is not None:
559
+ if self.VERSION == "1.1":
560
+ e = Element(
561
+ f"{{{self.NS_VIZ}}}color",
562
+ r=str(color.get("r")),
563
+ g=str(color.get("g")),
564
+ b=str(color.get("b")),
565
+ )
566
+ else:
567
+ e = Element(
568
+ f"{{{self.NS_VIZ}}}color",
569
+ r=str(color.get("r")),
570
+ g=str(color.get("g")),
571
+ b=str(color.get("b")),
572
+ a=str(color.get("a", 1.0)),
573
+ )
574
+ element.append(e)
575
+
576
+ size = viz.get("size")
577
+ if size is not None:
578
+ e = Element(f"{{{self.NS_VIZ}}}size", value=str(size))
579
+ element.append(e)
580
+
581
+ thickness = viz.get("thickness")
582
+ if thickness is not None:
583
+ e = Element(f"{{{self.NS_VIZ}}}thickness", value=str(thickness))
584
+ element.append(e)
585
+
586
+ shape = viz.get("shape")
587
+ if shape is not None:
588
+ if shape.startswith("http"):
589
+ e = Element(
590
+ f"{{{self.NS_VIZ}}}shape", value="image", uri=str(shape)
591
+ )
592
+ else:
593
+ e = Element(f"{{{self.NS_VIZ}}}shape", value=str(shape))
594
+ element.append(e)
595
+
596
+ position = viz.get("position")
597
+ if position is not None:
598
+ e = Element(
599
+ f"{{{self.NS_VIZ}}}position",
600
+ x=str(position.get("x")),
601
+ y=str(position.get("y")),
602
+ z=str(position.get("z")),
603
+ )
604
+ element.append(e)
605
+ return node_data
606
+
607
+ def add_parents(self, node_element, node_data):
608
+ parents = node_data.pop("parents", False)
609
+ if parents:
610
+ parents_element = Element("parents")
611
+ for p in parents:
612
+ e = Element("parent")
613
+ e.attrib["for"] = str(p)
614
+ parents_element.append(e)
615
+ node_element.append(parents_element)
616
+ return node_data
617
+
618
+ def add_slices(self, node_or_edge_element, node_or_edge_data):
619
+ slices = node_or_edge_data.pop("slices", False)
620
+ if slices:
621
+ slices_element = Element("slices")
622
+ for start, end in slices:
623
+ e = Element("slice", start=str(start), end=str(end))
624
+ slices_element.append(e)
625
+ node_or_edge_element.append(slices_element)
626
+ return node_or_edge_data
627
+
628
+ def add_spells(self, node_or_edge_element, node_or_edge_data):
629
+ spells = node_or_edge_data.pop("spells", False)
630
+ if spells:
631
+ spells_element = Element("spells")
632
+ for start, end in spells:
633
+ e = Element("spell")
634
+ if start is not None:
635
+ e.attrib["start"] = str(start)
636
+ self.alter_graph_mode_timeformat(start)
637
+ if end is not None:
638
+ e.attrib["end"] = str(end)
639
+ self.alter_graph_mode_timeformat(end)
640
+ spells_element.append(e)
641
+ node_or_edge_element.append(spells_element)
642
+ return node_or_edge_data
643
+
644
+ def alter_graph_mode_timeformat(self, start_or_end):
645
+ # If 'start' or 'end' appears, alter Graph mode to dynamic and
646
+ # set timeformat
647
+ if self.graph_element.get("mode") == "static":
648
+ if start_or_end is not None:
649
+ if isinstance(start_or_end, str):
650
+ timeformat = "date"
651
+ elif isinstance(start_or_end, float):
652
+ timeformat = "double"
653
+ elif isinstance(start_or_end, int):
654
+ timeformat = "long"
655
+ else:
656
+ raise nx.NetworkXError(
657
+ "timeformat should be of the type int, float or str"
658
+ )
659
+ self.graph_element.set("timeformat", timeformat)
660
+ self.graph_element.set("mode", "dynamic")
661
+
662
+ def write(self, fh):
663
+ # Serialize graph G in GEXF to the open fh
664
+ if self.prettyprint:
665
+ self.indent(self.xml)
666
+ document = ElementTree(self.xml)
667
+ document.write(fh, encoding=self.encoding, xml_declaration=True)
668
+
669
+ def indent(self, elem, level=0):
670
+ # in-place prettyprint formatter
671
+ i = "\n" + " " * level
672
+ if len(elem):
673
+ if not elem.text or not elem.text.strip():
674
+ elem.text = i + " "
675
+ if not elem.tail or not elem.tail.strip():
676
+ elem.tail = i
677
+ for elem in elem:
678
+ self.indent(elem, level + 1)
679
+ if not elem.tail or not elem.tail.strip():
680
+ elem.tail = i
681
+ else:
682
+ if level and (not elem.tail or not elem.tail.strip()):
683
+ elem.tail = i
684
+
685
+
686
+ class GEXFReader(GEXF):
687
+ # Class to read GEXF format files
688
+ # use read_gexf() function
689
+ def __init__(self, node_type=None, version="1.2draft"):
690
+ self.construct_types()
691
+ self.node_type = node_type
692
+ # assume simple graph and test for multigraph on read
693
+ self.simple_graph = True
694
+ self.set_version(version)
695
+
696
+ def __call__(self, stream):
697
+ self.xml = ElementTree(file=stream)
698
+ g = self.xml.find(f"{{{self.NS_GEXF}}}graph")
699
+ if g is not None:
700
+ return self.make_graph(g)
701
+ # try all the versions
702
+ for version in self.versions:
703
+ self.set_version(version)
704
+ g = self.xml.find(f"{{{self.NS_GEXF}}}graph")
705
+ if g is not None:
706
+ return self.make_graph(g)
707
+ raise nx.NetworkXError("No <graph> element in GEXF file.")
708
+
709
+ def make_graph(self, graph_xml):
710
+ # start with empty DiGraph or MultiDiGraph
711
+ edgedefault = graph_xml.get("defaultedgetype", None)
712
+ if edgedefault == "directed":
713
+ G = nx.MultiDiGraph()
714
+ else:
715
+ G = nx.MultiGraph()
716
+
717
+ # graph attributes
718
+ graph_name = graph_xml.get("name", "")
719
+ if graph_name != "":
720
+ G.graph["name"] = graph_name
721
+ graph_start = graph_xml.get("start")
722
+ if graph_start is not None:
723
+ G.graph["start"] = graph_start
724
+ graph_end = graph_xml.get("end")
725
+ if graph_end is not None:
726
+ G.graph["end"] = graph_end
727
+ graph_mode = graph_xml.get("mode", "")
728
+ if graph_mode == "dynamic":
729
+ G.graph["mode"] = "dynamic"
730
+ else:
731
+ G.graph["mode"] = "static"
732
+
733
+ # timeformat
734
+ self.timeformat = graph_xml.get("timeformat")
735
+ if self.timeformat == "date":
736
+ self.timeformat = "string"
737
+
738
+ # node and edge attributes
739
+ attributes_elements = graph_xml.findall(f"{{{self.NS_GEXF}}}attributes")
740
+ # dictionaries to hold attributes and attribute defaults
741
+ node_attr = {}
742
+ node_default = {}
743
+ edge_attr = {}
744
+ edge_default = {}
745
+ for a in attributes_elements:
746
+ attr_class = a.get("class")
747
+ if attr_class == "node":
748
+ na, nd = self.find_gexf_attributes(a)
749
+ node_attr.update(na)
750
+ node_default.update(nd)
751
+ G.graph["node_default"] = node_default
752
+ elif attr_class == "edge":
753
+ ea, ed = self.find_gexf_attributes(a)
754
+ edge_attr.update(ea)
755
+ edge_default.update(ed)
756
+ G.graph["edge_default"] = edge_default
757
+ else:
758
+ raise # unknown attribute class
759
+
760
+ # Hack to handle Gephi0.7beta bug
761
+ # add weight attribute
762
+ ea = {"weight": {"type": "double", "mode": "static", "title": "weight"}}
763
+ ed = {}
764
+ edge_attr.update(ea)
765
+ edge_default.update(ed)
766
+ G.graph["edge_default"] = edge_default
767
+
768
+ # add nodes
769
+ nodes_element = graph_xml.find(f"{{{self.NS_GEXF}}}nodes")
770
+ if nodes_element is not None:
771
+ for node_xml in nodes_element.findall(f"{{{self.NS_GEXF}}}node"):
772
+ self.add_node(G, node_xml, node_attr)
773
+
774
+ # add edges
775
+ edges_element = graph_xml.find(f"{{{self.NS_GEXF}}}edges")
776
+ if edges_element is not None:
777
+ for edge_xml in edges_element.findall(f"{{{self.NS_GEXF}}}edge"):
778
+ self.add_edge(G, edge_xml, edge_attr)
779
+
780
+ # switch to Graph or DiGraph if no parallel edges were found.
781
+ if self.simple_graph:
782
+ if G.is_directed():
783
+ G = nx.DiGraph(G)
784
+ else:
785
+ G = nx.Graph(G)
786
+ return G
787
+
788
+ def add_node(self, G, node_xml, node_attr, node_pid=None):
789
+ # add a single node with attributes to the graph
790
+
791
+ # get attributes and subattributues for node
792
+ data = self.decode_attr_elements(node_attr, node_xml)
793
+ data = self.add_parents(data, node_xml) # add any parents
794
+ if self.VERSION == "1.1":
795
+ data = self.add_slices(data, node_xml) # add slices
796
+ else:
797
+ data = self.add_spells(data, node_xml) # add spells
798
+ data = self.add_viz(data, node_xml) # add viz
799
+ data = self.add_start_end(data, node_xml) # add start/end
800
+
801
+ # find the node id and cast it to the appropriate type
802
+ node_id = node_xml.get("id")
803
+ if self.node_type is not None:
804
+ node_id = self.node_type(node_id)
805
+
806
+ # every node should have a label
807
+ node_label = node_xml.get("label")
808
+ data["label"] = node_label
809
+
810
+ # parent node id
811
+ node_pid = node_xml.get("pid", node_pid)
812
+ if node_pid is not None:
813
+ data["pid"] = node_pid
814
+
815
+ # check for subnodes, recursive
816
+ subnodes = node_xml.find(f"{{{self.NS_GEXF}}}nodes")
817
+ if subnodes is not None:
818
+ for node_xml in subnodes.findall(f"{{{self.NS_GEXF}}}node"):
819
+ self.add_node(G, node_xml, node_attr, node_pid=node_id)
820
+
821
+ G.add_node(node_id, **data)
822
+
823
+ def add_start_end(self, data, xml):
824
+ # start and end times
825
+ ttype = self.timeformat
826
+ node_start = xml.get("start")
827
+ if node_start is not None:
828
+ data["start"] = self.python_type[ttype](node_start)
829
+ node_end = xml.get("end")
830
+ if node_end is not None:
831
+ data["end"] = self.python_type[ttype](node_end)
832
+ return data
833
+
834
+ def add_viz(self, data, node_xml):
835
+ # add viz element for node
836
+ viz = {}
837
+ color = node_xml.find(f"{{{self.NS_VIZ}}}color")
838
+ if color is not None:
839
+ if self.VERSION == "1.1":
840
+ viz["color"] = {
841
+ "r": int(color.get("r")),
842
+ "g": int(color.get("g")),
843
+ "b": int(color.get("b")),
844
+ }
845
+ else:
846
+ viz["color"] = {
847
+ "r": int(color.get("r")),
848
+ "g": int(color.get("g")),
849
+ "b": int(color.get("b")),
850
+ "a": float(color.get("a", 1)),
851
+ }
852
+
853
+ size = node_xml.find(f"{{{self.NS_VIZ}}}size")
854
+ if size is not None:
855
+ viz["size"] = float(size.get("value"))
856
+
857
+ thickness = node_xml.find(f"{{{self.NS_VIZ}}}thickness")
858
+ if thickness is not None:
859
+ viz["thickness"] = float(thickness.get("value"))
860
+
861
+ shape = node_xml.find(f"{{{self.NS_VIZ}}}shape")
862
+ if shape is not None:
863
+ viz["shape"] = shape.get("shape")
864
+ if viz["shape"] == "image":
865
+ viz["shape"] = shape.get("uri")
866
+
867
+ position = node_xml.find(f"{{{self.NS_VIZ}}}position")
868
+ if position is not None:
869
+ viz["position"] = {
870
+ "x": float(position.get("x", 0)),
871
+ "y": float(position.get("y", 0)),
872
+ "z": float(position.get("z", 0)),
873
+ }
874
+
875
+ if len(viz) > 0:
876
+ data["viz"] = viz
877
+ return data
878
+
879
+ def add_parents(self, data, node_xml):
880
+ parents_element = node_xml.find(f"{{{self.NS_GEXF}}}parents")
881
+ if parents_element is not None:
882
+ data["parents"] = []
883
+ for p in parents_element.findall(f"{{{self.NS_GEXF}}}parent"):
884
+ parent = p.get("for")
885
+ data["parents"].append(parent)
886
+ return data
887
+
888
+ def add_slices(self, data, node_or_edge_xml):
889
+ slices_element = node_or_edge_xml.find(f"{{{self.NS_GEXF}}}slices")
890
+ if slices_element is not None:
891
+ data["slices"] = []
892
+ for s in slices_element.findall(f"{{{self.NS_GEXF}}}slice"):
893
+ start = s.get("start")
894
+ end = s.get("end")
895
+ data["slices"].append((start, end))
896
+ return data
897
+
898
+ def add_spells(self, data, node_or_edge_xml):
899
+ spells_element = node_or_edge_xml.find(f"{{{self.NS_GEXF}}}spells")
900
+ if spells_element is not None:
901
+ data["spells"] = []
902
+ ttype = self.timeformat
903
+ for s in spells_element.findall(f"{{{self.NS_GEXF}}}spell"):
904
+ start = self.python_type[ttype](s.get("start"))
905
+ end = self.python_type[ttype](s.get("end"))
906
+ data["spells"].append((start, end))
907
+ return data
908
+
909
+ def add_edge(self, G, edge_element, edge_attr):
910
+ # add an edge to the graph
911
+
912
+ # raise error if we find mixed directed and undirected edges
913
+ edge_direction = edge_element.get("type")
914
+ if G.is_directed() and edge_direction == "undirected":
915
+ raise nx.NetworkXError("Undirected edge found in directed graph.")
916
+ if (not G.is_directed()) and edge_direction == "directed":
917
+ raise nx.NetworkXError("Directed edge found in undirected graph.")
918
+
919
+ # Get source and target and recast type if required
920
+ source = edge_element.get("source")
921
+ target = edge_element.get("target")
922
+ if self.node_type is not None:
923
+ source = self.node_type(source)
924
+ target = self.node_type(target)
925
+
926
+ data = self.decode_attr_elements(edge_attr, edge_element)
927
+ data = self.add_start_end(data, edge_element)
928
+
929
+ if self.VERSION == "1.1":
930
+ data = self.add_slices(data, edge_element) # add slices
931
+ else:
932
+ data = self.add_spells(data, edge_element) # add spells
933
+
934
+ # GEXF stores edge ids as an attribute
935
+ # NetworkX uses them as keys in multigraphs
936
+ # if networkx_key is not specified as an attribute
937
+ edge_id = edge_element.get("id")
938
+ if edge_id is not None:
939
+ data["id"] = edge_id
940
+
941
+ # check if there is a 'multigraph_key' and use that as edge_id
942
+ multigraph_key = data.pop("networkx_key", None)
943
+ if multigraph_key is not None:
944
+ edge_id = multigraph_key
945
+
946
+ weight = edge_element.get("weight")
947
+ if weight is not None:
948
+ data["weight"] = float(weight)
949
+
950
+ edge_label = edge_element.get("label")
951
+ if edge_label is not None:
952
+ data["label"] = edge_label
953
+
954
+ if G.has_edge(source, target):
955
+ # seen this edge before - this is a multigraph
956
+ self.simple_graph = False
957
+ G.add_edge(source, target, key=edge_id, **data)
958
+ if edge_direction == "mutual":
959
+ G.add_edge(target, source, key=edge_id, **data)
960
+
961
+ def decode_attr_elements(self, gexf_keys, obj_xml):
962
+ # Use the key information to decode the attr XML
963
+ attr = {}
964
+ # look for outer '<attvalues>' element
965
+ attr_element = obj_xml.find(f"{{{self.NS_GEXF}}}attvalues")
966
+ if attr_element is not None:
967
+ # loop over <attvalue> elements
968
+ for a in attr_element.findall(f"{{{self.NS_GEXF}}}attvalue"):
969
+ key = a.get("for") # for is required
970
+ try: # should be in our gexf_keys dictionary
971
+ title = gexf_keys[key]["title"]
972
+ except KeyError as err:
973
+ raise nx.NetworkXError(f"No attribute defined for={key}.") from err
974
+ atype = gexf_keys[key]["type"]
975
+ value = a.get("value")
976
+ if atype == "boolean":
977
+ value = self.convert_bool[value]
978
+ else:
979
+ value = self.python_type[atype](value)
980
+ if gexf_keys[key]["mode"] == "dynamic":
981
+ # for dynamic graphs use list of three-tuples
982
+ # [(value1,start1,end1), (value2,start2,end2), etc]
983
+ ttype = self.timeformat
984
+ start = self.python_type[ttype](a.get("start"))
985
+ end = self.python_type[ttype](a.get("end"))
986
+ if title in attr:
987
+ attr[title].append((value, start, end))
988
+ else:
989
+ attr[title] = [(value, start, end)]
990
+ else:
991
+ # for static graphs just assign the value
992
+ attr[title] = value
993
+ return attr
994
+
995
+ def find_gexf_attributes(self, attributes_element):
996
+ # Extract all the attributes and defaults
997
+ attrs = {}
998
+ defaults = {}
999
+ mode = attributes_element.get("mode")
1000
+ for k in attributes_element.findall(f"{{{self.NS_GEXF}}}attribute"):
1001
+ attr_id = k.get("id")
1002
+ title = k.get("title")
1003
+ atype = k.get("type")
1004
+ attrs[attr_id] = {"title": title, "type": atype, "mode": mode}
1005
+ # check for the 'default' subelement of key element and add
1006
+ default = k.find(f"{{{self.NS_GEXF}}}default")
1007
+ if default is not None:
1008
+ if atype == "boolean":
1009
+ value = self.convert_bool[default.text]
1010
+ else:
1011
+ value = self.python_type[atype](default.text)
1012
+ defaults[title] = value
1013
+ return attrs, defaults
1014
+
1015
+
1016
+ def relabel_gexf_graph(G):
1017
+ """Relabel graph using "label" node keyword for node label.
1018
+
1019
+ Parameters
1020
+ ----------
1021
+ G : graph
1022
+ A NetworkX graph read from GEXF data
1023
+
1024
+ Returns
1025
+ -------
1026
+ H : graph
1027
+ A NetworkX graph with relabeled nodes
1028
+
1029
+ Raises
1030
+ ------
1031
+ NetworkXError
1032
+ If node labels are missing or not unique while relabel=True.
1033
+
1034
+ Notes
1035
+ -----
1036
+ This function relabels the nodes in a NetworkX graph with the
1037
+ "label" attribute. It also handles relabeling the specific GEXF
1038
+ node attributes "parents", and "pid".
1039
+ """
1040
+ # build mapping of node labels, do some error checking
1041
+ try:
1042
+ mapping = [(u, G.nodes[u]["label"]) for u in G]
1043
+ except KeyError as err:
1044
+ raise nx.NetworkXError(
1045
+ "Failed to relabel nodes: missing node labels found. Use relabel=False."
1046
+ ) from err
1047
+ x, y = zip(*mapping)
1048
+ if len(set(y)) != len(G):
1049
+ raise nx.NetworkXError(
1050
+ "Failed to relabel nodes: "
1051
+ "duplicate node labels found. "
1052
+ "Use relabel=False."
1053
+ )
1054
+ mapping = dict(mapping)
1055
+ H = nx.relabel_nodes(G, mapping)
1056
+ # relabel attributes
1057
+ for n in G:
1058
+ m = mapping[n]
1059
+ H.nodes[m]["id"] = n
1060
+ H.nodes[m].pop("label")
1061
+ if "pid" in H.nodes[m]:
1062
+ H.nodes[m]["pid"] = mapping[G.nodes[n]["pid"]]
1063
+ if "parents" in H.nodes[m]:
1064
+ H.nodes[m]["parents"] = [mapping[p] for p in G.nodes[n]["parents"]]
1065
+ return H
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/readwrite/json_graph/__pycache__/tree.cpython-311.pyc ADDED
Binary file (5.86 kB). View file
 
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/readwrite/json_graph/cytoscape.py ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import networkx as nx
2
+
3
+ __all__ = ["cytoscape_data", "cytoscape_graph"]
4
+
5
+
6
+ def cytoscape_data(G, name="name", ident="id"):
7
+ """Returns data in Cytoscape JSON format (cyjs).
8
+
9
+ Parameters
10
+ ----------
11
+ G : NetworkX Graph
12
+ The graph to convert to cytoscape format
13
+ name : string
14
+ A string which is mapped to the 'name' node element in cyjs format.
15
+ Must not have the same value as `ident`.
16
+ ident : string
17
+ A string which is mapped to the 'id' node element in cyjs format.
18
+ Must not have the same value as `name`.
19
+
20
+ Returns
21
+ -------
22
+ data: dict
23
+ A dictionary with cyjs formatted data.
24
+
25
+ Raises
26
+ ------
27
+ NetworkXError
28
+ If the values for `name` and `ident` are identical.
29
+
30
+ See Also
31
+ --------
32
+ cytoscape_graph: convert a dictionary in cyjs format to a graph
33
+
34
+ References
35
+ ----------
36
+ .. [1] Cytoscape user's manual:
37
+ http://manual.cytoscape.org/en/stable/index.html
38
+
39
+ Examples
40
+ --------
41
+ >>> G = nx.path_graph(2)
42
+ >>> nx.cytoscape_data(G) # doctest: +SKIP
43
+ {'data': [],
44
+ 'directed': False,
45
+ 'multigraph': False,
46
+ 'elements': {'nodes': [{'data': {'id': '0', 'value': 0, 'name': '0'}},
47
+ {'data': {'id': '1', 'value': 1, 'name': '1'}}],
48
+ 'edges': [{'data': {'source': 0, 'target': 1}}]}}
49
+ """
50
+ if name == ident:
51
+ raise nx.NetworkXError("name and ident must be different.")
52
+
53
+ jsondata = {"data": list(G.graph.items())}
54
+ jsondata["directed"] = G.is_directed()
55
+ jsondata["multigraph"] = G.is_multigraph()
56
+ jsondata["elements"] = {"nodes": [], "edges": []}
57
+ nodes = jsondata["elements"]["nodes"]
58
+ edges = jsondata["elements"]["edges"]
59
+
60
+ for i, j in G.nodes.items():
61
+ n = {"data": j.copy()}
62
+ n["data"]["id"] = j.get(ident) or str(i)
63
+ n["data"]["value"] = i
64
+ n["data"]["name"] = j.get(name) or str(i)
65
+ nodes.append(n)
66
+
67
+ if G.is_multigraph():
68
+ for e in G.edges(keys=True):
69
+ n = {"data": G.adj[e[0]][e[1]][e[2]].copy()}
70
+ n["data"]["source"] = e[0]
71
+ n["data"]["target"] = e[1]
72
+ n["data"]["key"] = e[2]
73
+ edges.append(n)
74
+ else:
75
+ for e in G.edges():
76
+ n = {"data": G.adj[e[0]][e[1]].copy()}
77
+ n["data"]["source"] = e[0]
78
+ n["data"]["target"] = e[1]
79
+ edges.append(n)
80
+ return jsondata
81
+
82
+
83
+ @nx._dispatch(graphs=None)
84
+ def cytoscape_graph(data, name="name", ident="id"):
85
+ """
86
+ Create a NetworkX graph from a dictionary in cytoscape JSON format.
87
+
88
+ Parameters
89
+ ----------
90
+ data : dict
91
+ A dictionary of data conforming to cytoscape JSON format.
92
+ name : string
93
+ A string which is mapped to the 'name' node element in cyjs format.
94
+ Must not have the same value as `ident`.
95
+ ident : string
96
+ A string which is mapped to the 'id' node element in cyjs format.
97
+ Must not have the same value as `name`.
98
+
99
+ Returns
100
+ -------
101
+ graph : a NetworkX graph instance
102
+ The `graph` can be an instance of `Graph`, `DiGraph`, `MultiGraph`, or
103
+ `MultiDiGraph` depending on the input data.
104
+
105
+ Raises
106
+ ------
107
+ NetworkXError
108
+ If the `name` and `ident` attributes are identical.
109
+
110
+ See Also
111
+ --------
112
+ cytoscape_data: convert a NetworkX graph to a dict in cyjs format
113
+
114
+ References
115
+ ----------
116
+ .. [1] Cytoscape user's manual:
117
+ http://manual.cytoscape.org/en/stable/index.html
118
+
119
+ Examples
120
+ --------
121
+ >>> data_dict = {
122
+ ... 'data': [],
123
+ ... 'directed': False,
124
+ ... 'multigraph': False,
125
+ ... 'elements': {'nodes': [{'data': {'id': '0', 'value': 0, 'name': '0'}},
126
+ ... {'data': {'id': '1', 'value': 1, 'name': '1'}}],
127
+ ... 'edges': [{'data': {'source': 0, 'target': 1}}]}
128
+ ... }
129
+ >>> G = nx.cytoscape_graph(data_dict)
130
+ >>> G.name
131
+ ''
132
+ >>> G.nodes()
133
+ NodeView((0, 1))
134
+ >>> G.nodes(data=True)[0]
135
+ {'id': '0', 'value': 0, 'name': '0'}
136
+ >>> G.edges(data=True)
137
+ EdgeDataView([(0, 1, {'source': 0, 'target': 1})])
138
+ """
139
+ if name == ident:
140
+ raise nx.NetworkXError("name and ident must be different.")
141
+
142
+ multigraph = data.get("multigraph")
143
+ directed = data.get("directed")
144
+ if multigraph:
145
+ graph = nx.MultiGraph()
146
+ else:
147
+ graph = nx.Graph()
148
+ if directed:
149
+ graph = graph.to_directed()
150
+ graph.graph = dict(data.get("data"))
151
+ for d in data["elements"]["nodes"]:
152
+ node_data = d["data"].copy()
153
+ node = d["data"]["value"]
154
+
155
+ if d["data"].get(name):
156
+ node_data[name] = d["data"].get(name)
157
+ if d["data"].get(ident):
158
+ node_data[ident] = d["data"].get(ident)
159
+
160
+ graph.add_node(node)
161
+ graph.nodes[node].update(node_data)
162
+
163
+ for d in data["elements"]["edges"]:
164
+ edge_data = d["data"].copy()
165
+ sour = d["data"]["source"]
166
+ targ = d["data"]["target"]
167
+ if multigraph:
168
+ key = d["data"].get("key", 0)
169
+ graph.add_edge(sour, targ, key=key)
170
+ graph.edges[sour, targ, key].update(edge_data)
171
+ else:
172
+ graph.add_edge(sour, targ)
173
+ graph.edges[sour, targ].update(edge_data)
174
+ return graph
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/readwrite/json_graph/tests/__init__.py ADDED
File without changes
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/readwrite/json_graph/tests/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (236 Bytes). View file
 
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/readwrite/multiline_adjlist.py ADDED
@@ -0,0 +1,393 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ *************************
3
+ Multi-line Adjacency List
4
+ *************************
5
+ Read and write NetworkX graphs as multi-line adjacency lists.
6
+
7
+ The multi-line adjacency list format is useful for graphs with
8
+ nodes that can be meaningfully represented as strings. With this format
9
+ simple edge data can be stored but node or graph data is not.
10
+
11
+ Format
12
+ ------
13
+ The first label in a line is the source node label followed by the node degree
14
+ d. The next d lines are target node labels and optional edge data.
15
+ That pattern repeats for all nodes in the graph.
16
+
17
+ The graph with edges a-b, a-c, d-e can be represented as the following
18
+ adjacency list (anything following the # in a line is a comment)::
19
+
20
+ # example.multiline-adjlist
21
+ a 2
22
+ b
23
+ c
24
+ d 1
25
+ e
26
+ """
27
+
28
+ __all__ = [
29
+ "generate_multiline_adjlist",
30
+ "write_multiline_adjlist",
31
+ "parse_multiline_adjlist",
32
+ "read_multiline_adjlist",
33
+ ]
34
+
35
+ import networkx as nx
36
+ from networkx.utils import open_file
37
+
38
+
39
+ def generate_multiline_adjlist(G, delimiter=" "):
40
+ """Generate a single line of the graph G in multiline adjacency list format.
41
+
42
+ Parameters
43
+ ----------
44
+ G : NetworkX graph
45
+
46
+ delimiter : string, optional
47
+ Separator for node labels
48
+
49
+ Returns
50
+ -------
51
+ lines : string
52
+ Lines of data in multiline adjlist format.
53
+
54
+ Examples
55
+ --------
56
+ >>> G = nx.lollipop_graph(4, 3)
57
+ >>> for line in nx.generate_multiline_adjlist(G):
58
+ ... print(line)
59
+ 0 3
60
+ 1 {}
61
+ 2 {}
62
+ 3 {}
63
+ 1 2
64
+ 2 {}
65
+ 3 {}
66
+ 2 1
67
+ 3 {}
68
+ 3 1
69
+ 4 {}
70
+ 4 1
71
+ 5 {}
72
+ 5 1
73
+ 6 {}
74
+ 6 0
75
+
76
+ See Also
77
+ --------
78
+ write_multiline_adjlist, read_multiline_adjlist
79
+ """
80
+ if G.is_directed():
81
+ if G.is_multigraph():
82
+ for s, nbrs in G.adjacency():
83
+ nbr_edges = [
84
+ (u, data)
85
+ for u, datadict in nbrs.items()
86
+ for key, data in datadict.items()
87
+ ]
88
+ deg = len(nbr_edges)
89
+ yield str(s) + delimiter + str(deg)
90
+ for u, d in nbr_edges:
91
+ if d is None:
92
+ yield str(u)
93
+ else:
94
+ yield str(u) + delimiter + str(d)
95
+ else: # directed single edges
96
+ for s, nbrs in G.adjacency():
97
+ deg = len(nbrs)
98
+ yield str(s) + delimiter + str(deg)
99
+ for u, d in nbrs.items():
100
+ if d is None:
101
+ yield str(u)
102
+ else:
103
+ yield str(u) + delimiter + str(d)
104
+ else: # undirected
105
+ if G.is_multigraph():
106
+ seen = set() # helper dict used to avoid duplicate edges
107
+ for s, nbrs in G.adjacency():
108
+ nbr_edges = [
109
+ (u, data)
110
+ for u, datadict in nbrs.items()
111
+ if u not in seen
112
+ for key, data in datadict.items()
113
+ ]
114
+ deg = len(nbr_edges)
115
+ yield str(s) + delimiter + str(deg)
116
+ for u, d in nbr_edges:
117
+ if d is None:
118
+ yield str(u)
119
+ else:
120
+ yield str(u) + delimiter + str(d)
121
+ seen.add(s)
122
+ else: # undirected single edges
123
+ seen = set() # helper dict used to avoid duplicate edges
124
+ for s, nbrs in G.adjacency():
125
+ nbr_edges = [(u, d) for u, d in nbrs.items() if u not in seen]
126
+ deg = len(nbr_edges)
127
+ yield str(s) + delimiter + str(deg)
128
+ for u, d in nbr_edges:
129
+ if d is None:
130
+ yield str(u)
131
+ else:
132
+ yield str(u) + delimiter + str(d)
133
+ seen.add(s)
134
+
135
+
136
+ @open_file(1, mode="wb")
137
+ def write_multiline_adjlist(G, path, delimiter=" ", comments="#", encoding="utf-8"):
138
+ """Write the graph G in multiline adjacency list format to path
139
+
140
+ Parameters
141
+ ----------
142
+ G : NetworkX graph
143
+
144
+ path : string or file
145
+ Filename or file handle to write to.
146
+ Filenames ending in .gz or .bz2 will be compressed.
147
+
148
+ comments : string, optional
149
+ Marker for comment lines
150
+
151
+ delimiter : string, optional
152
+ Separator for node labels
153
+
154
+ encoding : string, optional
155
+ Text encoding.
156
+
157
+ Examples
158
+ --------
159
+ >>> G = nx.path_graph(4)
160
+ >>> nx.write_multiline_adjlist(G, "test.adjlist")
161
+
162
+ The path can be a file handle or a string with the name of the file. If a
163
+ file handle is provided, it has to be opened in 'wb' mode.
164
+
165
+ >>> fh = open("test.adjlist", "wb")
166
+ >>> nx.write_multiline_adjlist(G, fh)
167
+
168
+ Filenames ending in .gz or .bz2 will be compressed.
169
+
170
+ >>> nx.write_multiline_adjlist(G, "test.adjlist.gz")
171
+
172
+ See Also
173
+ --------
174
+ read_multiline_adjlist
175
+ """
176
+ import sys
177
+ import time
178
+
179
+ pargs = comments + " ".join(sys.argv)
180
+ header = (
181
+ f"{pargs}\n"
182
+ + comments
183
+ + f" GMT {time.asctime(time.gmtime())}\n"
184
+ + comments
185
+ + f" {G.name}\n"
186
+ )
187
+ path.write(header.encode(encoding))
188
+
189
+ for multiline in generate_multiline_adjlist(G, delimiter):
190
+ multiline += "\n"
191
+ path.write(multiline.encode(encoding))
192
+
193
+
194
+ @nx._dispatch(graphs=None)
195
+ def parse_multiline_adjlist(
196
+ lines, comments="#", delimiter=None, create_using=None, nodetype=None, edgetype=None
197
+ ):
198
+ """Parse lines of a multiline adjacency list representation of a graph.
199
+
200
+ Parameters
201
+ ----------
202
+ lines : list or iterator of strings
203
+ Input data in multiline adjlist format
204
+
205
+ create_using : NetworkX graph constructor, optional (default=nx.Graph)
206
+ Graph type to create. If graph instance, then cleared before populated.
207
+
208
+ nodetype : Python type, optional
209
+ Convert nodes to this type.
210
+
211
+ edgetype : Python type, optional
212
+ Convert edges to this type.
213
+
214
+ comments : string, optional
215
+ Marker for comment lines
216
+
217
+ delimiter : string, optional
218
+ Separator for node labels. The default is whitespace.
219
+
220
+ Returns
221
+ -------
222
+ G: NetworkX graph
223
+ The graph corresponding to the lines in multiline adjacency list format.
224
+
225
+ Examples
226
+ --------
227
+ >>> lines = [
228
+ ... "1 2",
229
+ ... "2 {'weight':3, 'name': 'Frodo'}",
230
+ ... "3 {}",
231
+ ... "2 1",
232
+ ... "5 {'weight':6, 'name': 'Saruman'}",
233
+ ... ]
234
+ >>> G = nx.parse_multiline_adjlist(iter(lines), nodetype=int)
235
+ >>> list(G)
236
+ [1, 2, 3, 5]
237
+
238
+ """
239
+ from ast import literal_eval
240
+
241
+ G = nx.empty_graph(0, create_using)
242
+ for line in lines:
243
+ p = line.find(comments)
244
+ if p >= 0:
245
+ line = line[:p]
246
+ if not line:
247
+ continue
248
+ try:
249
+ (u, deg) = line.strip().split(delimiter)
250
+ deg = int(deg)
251
+ except BaseException as err:
252
+ raise TypeError(f"Failed to read node and degree on line ({line})") from err
253
+ if nodetype is not None:
254
+ try:
255
+ u = nodetype(u)
256
+ except BaseException as err:
257
+ raise TypeError(
258
+ f"Failed to convert node ({u}) to " f"type {nodetype}"
259
+ ) from err
260
+ G.add_node(u)
261
+ for i in range(deg):
262
+ while True:
263
+ try:
264
+ line = next(lines)
265
+ except StopIteration as err:
266
+ msg = f"Failed to find neighbor for node ({u})"
267
+ raise TypeError(msg) from err
268
+ p = line.find(comments)
269
+ if p >= 0:
270
+ line = line[:p]
271
+ if line:
272
+ break
273
+ vlist = line.strip().split(delimiter)
274
+ numb = len(vlist)
275
+ if numb < 1:
276
+ continue # isolated node
277
+ v = vlist.pop(0)
278
+ data = "".join(vlist)
279
+ if nodetype is not None:
280
+ try:
281
+ v = nodetype(v)
282
+ except BaseException as err:
283
+ raise TypeError(
284
+ f"Failed to convert node ({v}) " f"to type {nodetype}"
285
+ ) from err
286
+ if edgetype is not None:
287
+ try:
288
+ edgedata = {"weight": edgetype(data)}
289
+ except BaseException as err:
290
+ raise TypeError(
291
+ f"Failed to convert edge data ({data}) " f"to type {edgetype}"
292
+ ) from err
293
+ else:
294
+ try: # try to evaluate
295
+ edgedata = literal_eval(data)
296
+ except:
297
+ edgedata = {}
298
+ G.add_edge(u, v, **edgedata)
299
+
300
+ return G
301
+
302
+
303
+ @open_file(0, mode="rb")
304
+ @nx._dispatch(graphs=None)
305
+ def read_multiline_adjlist(
306
+ path,
307
+ comments="#",
308
+ delimiter=None,
309
+ create_using=None,
310
+ nodetype=None,
311
+ edgetype=None,
312
+ encoding="utf-8",
313
+ ):
314
+ """Read graph in multi-line adjacency list format from path.
315
+
316
+ Parameters
317
+ ----------
318
+ path : string or file
319
+ Filename or file handle to read.
320
+ Filenames ending in .gz or .bz2 will be uncompressed.
321
+
322
+ create_using : NetworkX graph constructor, optional (default=nx.Graph)
323
+ Graph type to create. If graph instance, then cleared before populated.
324
+
325
+ nodetype : Python type, optional
326
+ Convert nodes to this type.
327
+
328
+ edgetype : Python type, optional
329
+ Convert edge data to this type.
330
+
331
+ comments : string, optional
332
+ Marker for comment lines
333
+
334
+ delimiter : string, optional
335
+ Separator for node labels. The default is whitespace.
336
+
337
+ Returns
338
+ -------
339
+ G: NetworkX graph
340
+
341
+ Examples
342
+ --------
343
+ >>> G = nx.path_graph(4)
344
+ >>> nx.write_multiline_adjlist(G, "test.adjlist")
345
+ >>> G = nx.read_multiline_adjlist("test.adjlist")
346
+
347
+ The path can be a file or a string with the name of the file. If a
348
+ file s provided, it has to be opened in 'rb' mode.
349
+
350
+ >>> fh = open("test.adjlist", "rb")
351
+ >>> G = nx.read_multiline_adjlist(fh)
352
+
353
+ Filenames ending in .gz or .bz2 will be compressed.
354
+
355
+ >>> nx.write_multiline_adjlist(G, "test.adjlist.gz")
356
+ >>> G = nx.read_multiline_adjlist("test.adjlist.gz")
357
+
358
+ The optional nodetype is a function to convert node strings to nodetype.
359
+
360
+ For example
361
+
362
+ >>> G = nx.read_multiline_adjlist("test.adjlist", nodetype=int)
363
+
364
+ will attempt to convert all nodes to integer type.
365
+
366
+ The optional edgetype is a function to convert edge data strings to
367
+ edgetype.
368
+
369
+ >>> G = nx.read_multiline_adjlist("test.adjlist")
370
+
371
+ The optional create_using parameter is a NetworkX graph container.
372
+ The default is Graph(), an undirected graph. To read the data as
373
+ a directed graph use
374
+
375
+ >>> G = nx.read_multiline_adjlist("test.adjlist", create_using=nx.DiGraph)
376
+
377
+ Notes
378
+ -----
379
+ This format does not store graph, node, or edge data.
380
+
381
+ See Also
382
+ --------
383
+ write_multiline_adjlist
384
+ """
385
+ lines = (line.decode(encoding) for line in path)
386
+ return parse_multiline_adjlist(
387
+ lines,
388
+ comments=comments,
389
+ delimiter=delimiter,
390
+ create_using=create_using,
391
+ nodetype=nodetype,
392
+ edgetype=edgetype,
393
+ )