Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .env +1 -0
- .python-version +1 -0
- hoge.txt +1 -0
- settings.yaml +7 -0
- tuning-competition-baseline/.flake8 +2 -0
- tuning-competition-baseline/.isort.cfg +2 -0
- tuning-competition-baseline/.venv/bin/isympy +8 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/chains.py +172 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/clique.py +753 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/communicability_alg.py +162 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/covering.py +142 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/cuts.py +400 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/hybrid.py +195 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/link_prediction.py +604 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/simple_paths.py +978 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/voronoi.py +85 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/__pycache__/__init__.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/__pycache__/coreviews.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/__pycache__/filters.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/__pycache__/graph.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/coreviews.py +367 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/digraph.py +1323 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/filters.py +75 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/function.py +1313 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/graph.py +2030 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/graphviews.py +267 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/multidigraph.py +963 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/__pycache__/test_reportviews.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/test_graph_historical.py +12 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/test_reportviews.py +1423 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/test_subgraphviews.py +362 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/convert.py +496 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/convert_matrix.py +1200 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/exception.py +125 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/linalg/__pycache__/attrmatrix.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/linalg/__pycache__/spectrum.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/linalg/algebraicconnectivity.py +656 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/linalg/bethehessianmatrix.py +78 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/linalg/tests/__pycache__/test_bethehessian.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/readwrite/__pycache__/edgelist.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/readwrite/__pycache__/gexf.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/readwrite/__pycache__/gml.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/readwrite/__pycache__/graphml.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/readwrite/__pycache__/multiline_adjlist.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/readwrite/gexf.py +1065 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/readwrite/json_graph/__pycache__/tree.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/readwrite/json_graph/cytoscape.py +174 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/readwrite/json_graph/tests/__init__.py +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/readwrite/json_graph/tests/__pycache__/__init__.cpython-311.pyc +0 -0
- 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 |
+
)
|