Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/__pycache__/steinertree.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_clique.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_maxcut.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_treewidth.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/boundary.py +167 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/community/lukes.py +226 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/community/quality.py +346 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/test_kernighan_lin.py +91 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/dominance.py +135 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/flow/__pycache__/networksimplex.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/flow/__pycache__/utils.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/flow/dinitz_alg.py +217 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/flow/tests/__init__.py +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/flow/tests/__pycache__/test_gomory_hu.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/flow/tests/test_maxflow_large_graph.py +157 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/link_analysis/__init__.py +2 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/link_analysis/__pycache__/pagerank_alg.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/link_analysis/hits_alg.py +334 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/link_analysis/pagerank_alg.py +499 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/link_analysis/tests/__pycache__/__init__.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/link_analysis/tests/__pycache__/test_hits.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/link_analysis/tests/test_pagerank.py +217 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/minors/__init__.py +27 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/non_randomness.py +96 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/operators/__init__.py +4 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/operators/all.py +319 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/operators/product.py +534 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/operators/tests/__pycache__/test_all.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/swap.py +405 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/__init__.py +13 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/__pycache__/digraph.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/__pycache__/graphviews.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/__pycache__/multidigraph.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/__pycache__/multigraph.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/reportviews.py +1431 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/__init__.py +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/__pycache__/dispatch_interface.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/__pycache__/historical_tests.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/__pycache__/test_digraph.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/__pycache__/test_digraph_historical.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/__pycache__/test_filters.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/__pycache__/test_function.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/__pycache__/test_graph.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/__pycache__/test_subgraphviews.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/dispatch_interface.py +194 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/test_backends.py +76 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/test_coreviews.py +362 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/test_digraph.py +331 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/test_function.py +782 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/test_graph.py +920 -0
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/__pycache__/steinertree.cpython-311.pyc
ADDED
|
Binary file (11.7 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_clique.cpython-311.pyc
ADDED
|
Binary file (7.28 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_maxcut.cpython-311.pyc
ADDED
|
Binary file (5.27 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_treewidth.cpython-311.pyc
ADDED
|
Binary file (15.1 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/boundary.py
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Routines to find the boundary of a set of nodes.
|
| 2 |
+
|
| 3 |
+
An edge boundary is a set of edges, each of which has exactly one
|
| 4 |
+
endpoint in a given set of nodes (or, in the case of directed graphs,
|
| 5 |
+
the set of edges whose source node is in the set).
|
| 6 |
+
|
| 7 |
+
A node boundary of a set *S* of nodes is the set of (out-)neighbors of
|
| 8 |
+
nodes in *S* that are outside *S*.
|
| 9 |
+
|
| 10 |
+
"""
|
| 11 |
+
from itertools import chain
|
| 12 |
+
|
| 13 |
+
import networkx as nx
|
| 14 |
+
|
| 15 |
+
__all__ = ["edge_boundary", "node_boundary"]
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
@nx._dispatch(edge_attrs={"data": "default"}, preserve_edge_attrs="data")
|
| 19 |
+
def edge_boundary(G, nbunch1, nbunch2=None, data=False, keys=False, default=None):
|
| 20 |
+
"""Returns the edge boundary of `nbunch1`.
|
| 21 |
+
|
| 22 |
+
The *edge boundary* of a set *S* with respect to a set *T* is the
|
| 23 |
+
set of edges (*u*, *v*) such that *u* is in *S* and *v* is in *T*.
|
| 24 |
+
If *T* is not specified, it is assumed to be the set of all nodes
|
| 25 |
+
not in *S*.
|
| 26 |
+
|
| 27 |
+
Parameters
|
| 28 |
+
----------
|
| 29 |
+
G : NetworkX graph
|
| 30 |
+
|
| 31 |
+
nbunch1 : iterable
|
| 32 |
+
Iterable of nodes in the graph representing the set of nodes
|
| 33 |
+
whose edge boundary will be returned. (This is the set *S* from
|
| 34 |
+
the definition above.)
|
| 35 |
+
|
| 36 |
+
nbunch2 : iterable
|
| 37 |
+
Iterable of nodes representing the target (or "exterior") set of
|
| 38 |
+
nodes. (This is the set *T* from the definition above.) If not
|
| 39 |
+
specified, this is assumed to be the set of all nodes in `G`
|
| 40 |
+
not in `nbunch1`.
|
| 41 |
+
|
| 42 |
+
keys : bool
|
| 43 |
+
This parameter has the same meaning as in
|
| 44 |
+
:meth:`MultiGraph.edges`.
|
| 45 |
+
|
| 46 |
+
data : bool or object
|
| 47 |
+
This parameter has the same meaning as in
|
| 48 |
+
:meth:`MultiGraph.edges`.
|
| 49 |
+
|
| 50 |
+
default : object
|
| 51 |
+
This parameter has the same meaning as in
|
| 52 |
+
:meth:`MultiGraph.edges`.
|
| 53 |
+
|
| 54 |
+
Returns
|
| 55 |
+
-------
|
| 56 |
+
iterator
|
| 57 |
+
An iterator over the edges in the boundary of `nbunch1` with
|
| 58 |
+
respect to `nbunch2`. If `keys`, `data`, or `default`
|
| 59 |
+
are specified and `G` is a multigraph, then edges are returned
|
| 60 |
+
with keys and/or data, as in :meth:`MultiGraph.edges`.
|
| 61 |
+
|
| 62 |
+
Examples
|
| 63 |
+
--------
|
| 64 |
+
>>> G = nx.wheel_graph(6)
|
| 65 |
+
|
| 66 |
+
When nbunch2=None:
|
| 67 |
+
|
| 68 |
+
>>> list(nx.edge_boundary(G, (1, 3)))
|
| 69 |
+
[(1, 0), (1, 2), (1, 5), (3, 0), (3, 2), (3, 4)]
|
| 70 |
+
|
| 71 |
+
When nbunch2 is given:
|
| 72 |
+
|
| 73 |
+
>>> list(nx.edge_boundary(G, (1, 3), (2, 0)))
|
| 74 |
+
[(1, 0), (1, 2), (3, 0), (3, 2)]
|
| 75 |
+
|
| 76 |
+
Notes
|
| 77 |
+
-----
|
| 78 |
+
Any element of `nbunch` that is not in the graph `G` will be
|
| 79 |
+
ignored.
|
| 80 |
+
|
| 81 |
+
`nbunch1` and `nbunch2` are usually meant to be disjoint, but in
|
| 82 |
+
the interest of speed and generality, that is not required here.
|
| 83 |
+
|
| 84 |
+
"""
|
| 85 |
+
nset1 = {n for n in nbunch1 if n in G}
|
| 86 |
+
# Here we create an iterator over edges incident to nodes in the set
|
| 87 |
+
# `nset1`. The `Graph.edges()` method does not provide a guarantee
|
| 88 |
+
# on the orientation of the edges, so our algorithm below must
|
| 89 |
+
# handle the case in which exactly one orientation, either (u, v) or
|
| 90 |
+
# (v, u), appears in this iterable.
|
| 91 |
+
if G.is_multigraph():
|
| 92 |
+
edges = G.edges(nset1, data=data, keys=keys, default=default)
|
| 93 |
+
else:
|
| 94 |
+
edges = G.edges(nset1, data=data, default=default)
|
| 95 |
+
# If `nbunch2` is not provided, then it is assumed to be the set
|
| 96 |
+
# complement of `nbunch1`. For the sake of efficiency, this is
|
| 97 |
+
# implemented by using the `not in` operator, instead of by creating
|
| 98 |
+
# an additional set and using the `in` operator.
|
| 99 |
+
if nbunch2 is None:
|
| 100 |
+
return (e for e in edges if (e[0] in nset1) ^ (e[1] in nset1))
|
| 101 |
+
nset2 = set(nbunch2)
|
| 102 |
+
return (
|
| 103 |
+
e
|
| 104 |
+
for e in edges
|
| 105 |
+
if (e[0] in nset1 and e[1] in nset2) or (e[1] in nset1 and e[0] in nset2)
|
| 106 |
+
)
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
@nx._dispatch
|
| 110 |
+
def node_boundary(G, nbunch1, nbunch2=None):
|
| 111 |
+
"""Returns the node boundary of `nbunch1`.
|
| 112 |
+
|
| 113 |
+
The *node boundary* of a set *S* with respect to a set *T* is the
|
| 114 |
+
set of nodes *v* in *T* such that for some *u* in *S*, there is an
|
| 115 |
+
edge joining *u* to *v*. If *T* is not specified, it is assumed to
|
| 116 |
+
be the set of all nodes not in *S*.
|
| 117 |
+
|
| 118 |
+
Parameters
|
| 119 |
+
----------
|
| 120 |
+
G : NetworkX graph
|
| 121 |
+
|
| 122 |
+
nbunch1 : iterable
|
| 123 |
+
Iterable of nodes in the graph representing the set of nodes
|
| 124 |
+
whose node boundary will be returned. (This is the set *S* from
|
| 125 |
+
the definition above.)
|
| 126 |
+
|
| 127 |
+
nbunch2 : iterable
|
| 128 |
+
Iterable of nodes representing the target (or "exterior") set of
|
| 129 |
+
nodes. (This is the set *T* from the definition above.) If not
|
| 130 |
+
specified, this is assumed to be the set of all nodes in `G`
|
| 131 |
+
not in `nbunch1`.
|
| 132 |
+
|
| 133 |
+
Returns
|
| 134 |
+
-------
|
| 135 |
+
set
|
| 136 |
+
The node boundary of `nbunch1` with respect to `nbunch2`.
|
| 137 |
+
|
| 138 |
+
Examples
|
| 139 |
+
--------
|
| 140 |
+
>>> G = nx.wheel_graph(6)
|
| 141 |
+
|
| 142 |
+
When nbunch2=None:
|
| 143 |
+
|
| 144 |
+
>>> list(nx.node_boundary(G, (3, 4)))
|
| 145 |
+
[0, 2, 5]
|
| 146 |
+
|
| 147 |
+
When nbunch2 is given:
|
| 148 |
+
|
| 149 |
+
>>> list(nx.node_boundary(G, (3, 4), (0, 1, 5)))
|
| 150 |
+
[0, 5]
|
| 151 |
+
|
| 152 |
+
Notes
|
| 153 |
+
-----
|
| 154 |
+
Any element of `nbunch` that is not in the graph `G` will be
|
| 155 |
+
ignored.
|
| 156 |
+
|
| 157 |
+
`nbunch1` and `nbunch2` are usually meant to be disjoint, but in
|
| 158 |
+
the interest of speed and generality, that is not required here.
|
| 159 |
+
|
| 160 |
+
"""
|
| 161 |
+
nset1 = {n for n in nbunch1 if n in G}
|
| 162 |
+
bdy = set(chain.from_iterable(G[v] for v in nset1)) - nset1
|
| 163 |
+
# If `nbunch2` is not specified, it is assumed to be the set
|
| 164 |
+
# complement of `nbunch1`.
|
| 165 |
+
if nbunch2 is not None:
|
| 166 |
+
bdy &= set(nbunch2)
|
| 167 |
+
return bdy
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/community/lukes.py
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Lukes Algorithm for exact optimal weighted tree partitioning."""
|
| 2 |
+
|
| 3 |
+
from copy import deepcopy
|
| 4 |
+
from functools import lru_cache
|
| 5 |
+
from random import choice
|
| 6 |
+
|
| 7 |
+
import networkx as nx
|
| 8 |
+
from networkx.utils import not_implemented_for
|
| 9 |
+
|
| 10 |
+
__all__ = ["lukes_partitioning"]
|
| 11 |
+
|
| 12 |
+
D_EDGE_W = "weight"
|
| 13 |
+
D_EDGE_VALUE = 1.0
|
| 14 |
+
D_NODE_W = "weight"
|
| 15 |
+
D_NODE_VALUE = 1
|
| 16 |
+
PKEY = "partitions"
|
| 17 |
+
CLUSTER_EVAL_CACHE_SIZE = 2048
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def _split_n_from(n, min_size_of_first_part):
|
| 21 |
+
# splits j in two parts of which the first is at least
|
| 22 |
+
# the second argument
|
| 23 |
+
assert n >= min_size_of_first_part
|
| 24 |
+
for p1 in range(min_size_of_first_part, n + 1):
|
| 25 |
+
yield p1, n - p1
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
@nx._dispatch(node_attrs="node_weight", edge_attrs="edge_weight")
|
| 29 |
+
def lukes_partitioning(G, max_size, node_weight=None, edge_weight=None):
|
| 30 |
+
"""Optimal partitioning of a weighted tree using the Lukes algorithm.
|
| 31 |
+
|
| 32 |
+
This algorithm partitions a connected, acyclic graph featuring integer
|
| 33 |
+
node weights and float edge weights. The resulting clusters are such
|
| 34 |
+
that the total weight of the nodes in each cluster does not exceed
|
| 35 |
+
max_size and that the weight of the edges that are cut by the partition
|
| 36 |
+
is minimum. The algorithm is based on [1]_.
|
| 37 |
+
|
| 38 |
+
Parameters
|
| 39 |
+
----------
|
| 40 |
+
G : NetworkX graph
|
| 41 |
+
|
| 42 |
+
max_size : int
|
| 43 |
+
Maximum weight a partition can have in terms of sum of
|
| 44 |
+
node_weight for all nodes in the partition
|
| 45 |
+
|
| 46 |
+
edge_weight : key
|
| 47 |
+
Edge data key to use as weight. If None, the weights are all
|
| 48 |
+
set to one.
|
| 49 |
+
|
| 50 |
+
node_weight : key
|
| 51 |
+
Node data key to use as weight. If None, the weights are all
|
| 52 |
+
set to one. The data must be int.
|
| 53 |
+
|
| 54 |
+
Returns
|
| 55 |
+
-------
|
| 56 |
+
partition : list
|
| 57 |
+
A list of sets of nodes representing the clusters of the
|
| 58 |
+
partition.
|
| 59 |
+
|
| 60 |
+
Raises
|
| 61 |
+
------
|
| 62 |
+
NotATree
|
| 63 |
+
If G is not a tree.
|
| 64 |
+
TypeError
|
| 65 |
+
If any of the values of node_weight is not int.
|
| 66 |
+
|
| 67 |
+
References
|
| 68 |
+
----------
|
| 69 |
+
.. [1] Lukes, J. A. (1974).
|
| 70 |
+
"Efficient Algorithm for the Partitioning of Trees."
|
| 71 |
+
IBM Journal of Research and Development, 18(3), 217–224.
|
| 72 |
+
|
| 73 |
+
"""
|
| 74 |
+
# First sanity check and tree preparation
|
| 75 |
+
if not nx.is_tree(G):
|
| 76 |
+
raise nx.NotATree("lukes_partitioning works only on trees")
|
| 77 |
+
else:
|
| 78 |
+
if nx.is_directed(G):
|
| 79 |
+
root = [n for n, d in G.in_degree() if d == 0]
|
| 80 |
+
assert len(root) == 1
|
| 81 |
+
root = root[0]
|
| 82 |
+
t_G = deepcopy(G)
|
| 83 |
+
else:
|
| 84 |
+
root = choice(list(G.nodes))
|
| 85 |
+
# this has the desirable side effect of not inheriting attributes
|
| 86 |
+
t_G = nx.dfs_tree(G, root)
|
| 87 |
+
|
| 88 |
+
# Since we do not want to screw up the original graph,
|
| 89 |
+
# if we have a blank attribute, we make a deepcopy
|
| 90 |
+
if edge_weight is None or node_weight is None:
|
| 91 |
+
safe_G = deepcopy(G)
|
| 92 |
+
if edge_weight is None:
|
| 93 |
+
nx.set_edge_attributes(safe_G, D_EDGE_VALUE, D_EDGE_W)
|
| 94 |
+
edge_weight = D_EDGE_W
|
| 95 |
+
if node_weight is None:
|
| 96 |
+
nx.set_node_attributes(safe_G, D_NODE_VALUE, D_NODE_W)
|
| 97 |
+
node_weight = D_NODE_W
|
| 98 |
+
else:
|
| 99 |
+
safe_G = G
|
| 100 |
+
|
| 101 |
+
# Second sanity check
|
| 102 |
+
# The values of node_weight MUST BE int.
|
| 103 |
+
# I cannot see any room for duck typing without incurring serious
|
| 104 |
+
# danger of subtle bugs.
|
| 105 |
+
all_n_attr = nx.get_node_attributes(safe_G, node_weight).values()
|
| 106 |
+
for x in all_n_attr:
|
| 107 |
+
if not isinstance(x, int):
|
| 108 |
+
raise TypeError(
|
| 109 |
+
"lukes_partitioning needs integer "
|
| 110 |
+
f"values for node_weight ({node_weight})"
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
# SUBROUTINES -----------------------
|
| 114 |
+
# these functions are defined here for two reasons:
|
| 115 |
+
# - brevity: we can leverage global "safe_G"
|
| 116 |
+
# - caching: signatures are hashable
|
| 117 |
+
|
| 118 |
+
@not_implemented_for("undirected")
|
| 119 |
+
# this is intended to be called only on t_G
|
| 120 |
+
def _leaves(gr):
|
| 121 |
+
for x in gr.nodes:
|
| 122 |
+
if not nx.descendants(gr, x):
|
| 123 |
+
yield x
|
| 124 |
+
|
| 125 |
+
@not_implemented_for("undirected")
|
| 126 |
+
def _a_parent_of_leaves_only(gr):
|
| 127 |
+
tleaves = set(_leaves(gr))
|
| 128 |
+
for n in set(gr.nodes) - tleaves:
|
| 129 |
+
if all(x in tleaves for x in nx.descendants(gr, n)):
|
| 130 |
+
return n
|
| 131 |
+
|
| 132 |
+
@lru_cache(CLUSTER_EVAL_CACHE_SIZE)
|
| 133 |
+
def _value_of_cluster(cluster):
|
| 134 |
+
valid_edges = [e for e in safe_G.edges if e[0] in cluster and e[1] in cluster]
|
| 135 |
+
return sum(safe_G.edges[e][edge_weight] for e in valid_edges)
|
| 136 |
+
|
| 137 |
+
def _value_of_partition(partition):
|
| 138 |
+
return sum(_value_of_cluster(frozenset(c)) for c in partition)
|
| 139 |
+
|
| 140 |
+
@lru_cache(CLUSTER_EVAL_CACHE_SIZE)
|
| 141 |
+
def _weight_of_cluster(cluster):
|
| 142 |
+
return sum(safe_G.nodes[n][node_weight] for n in cluster)
|
| 143 |
+
|
| 144 |
+
def _pivot(partition, node):
|
| 145 |
+
ccx = [c for c in partition if node in c]
|
| 146 |
+
assert len(ccx) == 1
|
| 147 |
+
return ccx[0]
|
| 148 |
+
|
| 149 |
+
def _concatenate_or_merge(partition_1, partition_2, x, i, ref_weight):
|
| 150 |
+
ccx = _pivot(partition_1, x)
|
| 151 |
+
cci = _pivot(partition_2, i)
|
| 152 |
+
merged_xi = ccx.union(cci)
|
| 153 |
+
|
| 154 |
+
# We first check if we can do the merge.
|
| 155 |
+
# If so, we do the actual calculations, otherwise we concatenate
|
| 156 |
+
if _weight_of_cluster(frozenset(merged_xi)) <= ref_weight:
|
| 157 |
+
cp1 = list(filter(lambda x: x != ccx, partition_1))
|
| 158 |
+
cp2 = list(filter(lambda x: x != cci, partition_2))
|
| 159 |
+
|
| 160 |
+
option_2 = [merged_xi] + cp1 + cp2
|
| 161 |
+
return option_2, _value_of_partition(option_2)
|
| 162 |
+
else:
|
| 163 |
+
option_1 = partition_1 + partition_2
|
| 164 |
+
return option_1, _value_of_partition(option_1)
|
| 165 |
+
|
| 166 |
+
# INITIALIZATION -----------------------
|
| 167 |
+
leaves = set(_leaves(t_G))
|
| 168 |
+
for lv in leaves:
|
| 169 |
+
t_G.nodes[lv][PKEY] = {}
|
| 170 |
+
slot = safe_G.nodes[lv][node_weight]
|
| 171 |
+
t_G.nodes[lv][PKEY][slot] = [{lv}]
|
| 172 |
+
t_G.nodes[lv][PKEY][0] = [{lv}]
|
| 173 |
+
|
| 174 |
+
for inner in [x for x in t_G.nodes if x not in leaves]:
|
| 175 |
+
t_G.nodes[inner][PKEY] = {}
|
| 176 |
+
slot = safe_G.nodes[inner][node_weight]
|
| 177 |
+
t_G.nodes[inner][PKEY][slot] = [{inner}]
|
| 178 |
+
|
| 179 |
+
# CORE ALGORITHM -----------------------
|
| 180 |
+
while True:
|
| 181 |
+
x_node = _a_parent_of_leaves_only(t_G)
|
| 182 |
+
weight_of_x = safe_G.nodes[x_node][node_weight]
|
| 183 |
+
best_value = 0
|
| 184 |
+
best_partition = None
|
| 185 |
+
bp_buffer = {}
|
| 186 |
+
x_descendants = nx.descendants(t_G, x_node)
|
| 187 |
+
for i_node in x_descendants:
|
| 188 |
+
for j in range(weight_of_x, max_size + 1):
|
| 189 |
+
for a, b in _split_n_from(j, weight_of_x):
|
| 190 |
+
if (
|
| 191 |
+
a not in t_G.nodes[x_node][PKEY]
|
| 192 |
+
or b not in t_G.nodes[i_node][PKEY]
|
| 193 |
+
):
|
| 194 |
+
# it's not possible to form this particular weight sum
|
| 195 |
+
continue
|
| 196 |
+
|
| 197 |
+
part1 = t_G.nodes[x_node][PKEY][a]
|
| 198 |
+
part2 = t_G.nodes[i_node][PKEY][b]
|
| 199 |
+
part, value = _concatenate_or_merge(part1, part2, x_node, i_node, j)
|
| 200 |
+
|
| 201 |
+
if j not in bp_buffer or bp_buffer[j][1] < value:
|
| 202 |
+
# we annotate in the buffer the best partition for j
|
| 203 |
+
bp_buffer[j] = part, value
|
| 204 |
+
|
| 205 |
+
# we also keep track of the overall best partition
|
| 206 |
+
if best_value <= value:
|
| 207 |
+
best_value = value
|
| 208 |
+
best_partition = part
|
| 209 |
+
|
| 210 |
+
# as illustrated in Lukes, once we finished a child, we can
|
| 211 |
+
# discharge the partitions we found into the graph
|
| 212 |
+
# (the key phrase is make all x == x')
|
| 213 |
+
# so that they are used by the subsequent children
|
| 214 |
+
for w, (best_part_for_vl, vl) in bp_buffer.items():
|
| 215 |
+
t_G.nodes[x_node][PKEY][w] = best_part_for_vl
|
| 216 |
+
bp_buffer.clear()
|
| 217 |
+
|
| 218 |
+
# the absolute best partition for this node
|
| 219 |
+
# across all weights has to be stored at 0
|
| 220 |
+
t_G.nodes[x_node][PKEY][0] = best_partition
|
| 221 |
+
t_G.remove_nodes_from(x_descendants)
|
| 222 |
+
|
| 223 |
+
if x_node == root:
|
| 224 |
+
# the 0-labeled partition of root
|
| 225 |
+
# is the optimal one for the whole tree
|
| 226 |
+
return t_G.nodes[root][PKEY][0]
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/community/quality.py
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Functions for measuring the quality of a partition (into
|
| 2 |
+
communities).
|
| 3 |
+
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from itertools import combinations
|
| 7 |
+
|
| 8 |
+
import networkx as nx
|
| 9 |
+
from networkx import NetworkXError
|
| 10 |
+
from networkx.algorithms.community.community_utils import is_partition
|
| 11 |
+
from networkx.utils.decorators import argmap
|
| 12 |
+
|
| 13 |
+
__all__ = ["modularity", "partition_quality"]
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class NotAPartition(NetworkXError):
|
| 17 |
+
"""Raised if a given collection is not a partition."""
|
| 18 |
+
|
| 19 |
+
def __init__(self, G, collection):
|
| 20 |
+
msg = f"{collection} is not a valid partition of the graph {G}"
|
| 21 |
+
super().__init__(msg)
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def _require_partition(G, partition):
|
| 25 |
+
"""Decorator to check that a valid partition is input to a function
|
| 26 |
+
|
| 27 |
+
Raises :exc:`networkx.NetworkXError` if the partition is not valid.
|
| 28 |
+
|
| 29 |
+
This decorator should be used on functions whose first two arguments
|
| 30 |
+
are a graph and a partition of the nodes of that graph (in that
|
| 31 |
+
order)::
|
| 32 |
+
|
| 33 |
+
>>> @require_partition
|
| 34 |
+
... def foo(G, partition):
|
| 35 |
+
... print("partition is valid!")
|
| 36 |
+
...
|
| 37 |
+
>>> G = nx.complete_graph(5)
|
| 38 |
+
>>> partition = [{0, 1}, {2, 3}, {4}]
|
| 39 |
+
>>> foo(G, partition)
|
| 40 |
+
partition is valid!
|
| 41 |
+
>>> partition = [{0}, {2, 3}, {4}]
|
| 42 |
+
>>> foo(G, partition)
|
| 43 |
+
Traceback (most recent call last):
|
| 44 |
+
...
|
| 45 |
+
networkx.exception.NetworkXError: `partition` is not a valid partition of the nodes of G
|
| 46 |
+
>>> partition = [{0, 1}, {1, 2, 3}, {4}]
|
| 47 |
+
>>> foo(G, partition)
|
| 48 |
+
Traceback (most recent call last):
|
| 49 |
+
...
|
| 50 |
+
networkx.exception.NetworkXError: `partition` is not a valid partition of the nodes of G
|
| 51 |
+
|
| 52 |
+
"""
|
| 53 |
+
if is_partition(G, partition):
|
| 54 |
+
return G, partition
|
| 55 |
+
raise nx.NetworkXError("`partition` is not a valid partition of the nodes of G")
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
require_partition = argmap(_require_partition, (0, 1))
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
@nx._dispatch
|
| 62 |
+
def intra_community_edges(G, partition):
|
| 63 |
+
"""Returns the number of intra-community edges for a partition of `G`.
|
| 64 |
+
|
| 65 |
+
Parameters
|
| 66 |
+
----------
|
| 67 |
+
G : NetworkX graph.
|
| 68 |
+
|
| 69 |
+
partition : iterable of sets of nodes
|
| 70 |
+
This must be a partition of the nodes of `G`.
|
| 71 |
+
|
| 72 |
+
The "intra-community edges" are those edges joining a pair of nodes
|
| 73 |
+
in the same block of the partition.
|
| 74 |
+
|
| 75 |
+
"""
|
| 76 |
+
return sum(G.subgraph(block).size() for block in partition)
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
@nx._dispatch
|
| 80 |
+
def inter_community_edges(G, partition):
|
| 81 |
+
"""Returns the number of inter-community edges for a partition of `G`.
|
| 82 |
+
according to the given
|
| 83 |
+
partition of the nodes of `G`.
|
| 84 |
+
|
| 85 |
+
Parameters
|
| 86 |
+
----------
|
| 87 |
+
G : NetworkX graph.
|
| 88 |
+
|
| 89 |
+
partition : iterable of sets of nodes
|
| 90 |
+
This must be a partition of the nodes of `G`.
|
| 91 |
+
|
| 92 |
+
The *inter-community edges* are those edges joining a pair of nodes
|
| 93 |
+
in different blocks of the partition.
|
| 94 |
+
|
| 95 |
+
Implementation note: this function creates an intermediate graph
|
| 96 |
+
that may require the same amount of memory as that of `G`.
|
| 97 |
+
|
| 98 |
+
"""
|
| 99 |
+
# Alternate implementation that does not require constructing a new
|
| 100 |
+
# graph object (but does require constructing an affiliation
|
| 101 |
+
# dictionary):
|
| 102 |
+
#
|
| 103 |
+
# aff = dict(chain.from_iterable(((v, block) for v in block)
|
| 104 |
+
# for block in partition))
|
| 105 |
+
# return sum(1 for u, v in G.edges() if aff[u] != aff[v])
|
| 106 |
+
#
|
| 107 |
+
MG = nx.MultiDiGraph if G.is_directed() else nx.MultiGraph
|
| 108 |
+
return nx.quotient_graph(G, partition, create_using=MG).size()
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
@nx._dispatch
|
| 112 |
+
def inter_community_non_edges(G, partition):
|
| 113 |
+
"""Returns the number of inter-community non-edges according to the
|
| 114 |
+
given partition of the nodes of `G`.
|
| 115 |
+
|
| 116 |
+
Parameters
|
| 117 |
+
----------
|
| 118 |
+
G : NetworkX graph.
|
| 119 |
+
|
| 120 |
+
partition : iterable of sets of nodes
|
| 121 |
+
This must be a partition of the nodes of `G`.
|
| 122 |
+
|
| 123 |
+
A *non-edge* is a pair of nodes (undirected if `G` is undirected)
|
| 124 |
+
that are not adjacent in `G`. The *inter-community non-edges* are
|
| 125 |
+
those non-edges on a pair of nodes in different blocks of the
|
| 126 |
+
partition.
|
| 127 |
+
|
| 128 |
+
Implementation note: this function creates two intermediate graphs,
|
| 129 |
+
which may require up to twice the amount of memory as required to
|
| 130 |
+
store `G`.
|
| 131 |
+
|
| 132 |
+
"""
|
| 133 |
+
# Alternate implementation that does not require constructing two
|
| 134 |
+
# new graph objects (but does require constructing an affiliation
|
| 135 |
+
# dictionary):
|
| 136 |
+
#
|
| 137 |
+
# aff = dict(chain.from_iterable(((v, block) for v in block)
|
| 138 |
+
# for block in partition))
|
| 139 |
+
# return sum(1 for u, v in nx.non_edges(G) if aff[u] != aff[v])
|
| 140 |
+
#
|
| 141 |
+
return inter_community_edges(nx.complement(G), partition)
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
@nx._dispatch(edge_attrs="weight")
|
| 145 |
+
def modularity(G, communities, weight="weight", resolution=1):
|
| 146 |
+
r"""Returns the modularity of the given partition of the graph.
|
| 147 |
+
|
| 148 |
+
Modularity is defined in [1]_ as
|
| 149 |
+
|
| 150 |
+
.. math::
|
| 151 |
+
Q = \frac{1}{2m} \sum_{ij} \left( A_{ij} - \gamma\frac{k_ik_j}{2m}\right)
|
| 152 |
+
\delta(c_i,c_j)
|
| 153 |
+
|
| 154 |
+
where $m$ is the number of edges (or sum of all edge weights as in [5]_),
|
| 155 |
+
$A$ is the adjacency matrix of `G`, $k_i$ is the (weighted) degree of $i$,
|
| 156 |
+
$\gamma$ is the resolution parameter, and $\delta(c_i, c_j)$ is 1 if $i$ and
|
| 157 |
+
$j$ are in the same community else 0.
|
| 158 |
+
|
| 159 |
+
According to [2]_ (and verified by some algebra) this can be reduced to
|
| 160 |
+
|
| 161 |
+
.. math::
|
| 162 |
+
Q = \sum_{c=1}^{n}
|
| 163 |
+
\left[ \frac{L_c}{m} - \gamma\left( \frac{k_c}{2m} \right) ^2 \right]
|
| 164 |
+
|
| 165 |
+
where the sum iterates over all communities $c$, $m$ is the number of edges,
|
| 166 |
+
$L_c$ is the number of intra-community links for community $c$,
|
| 167 |
+
$k_c$ is the sum of degrees of the nodes in community $c$,
|
| 168 |
+
and $\gamma$ is the resolution parameter.
|
| 169 |
+
|
| 170 |
+
The resolution parameter sets an arbitrary tradeoff between intra-group
|
| 171 |
+
edges and inter-group edges. More complex grouping patterns can be
|
| 172 |
+
discovered by analyzing the same network with multiple values of gamma
|
| 173 |
+
and then combining the results [3]_. That said, it is very common to
|
| 174 |
+
simply use gamma=1. More on the choice of gamma is in [4]_.
|
| 175 |
+
|
| 176 |
+
The second formula is the one actually used in calculation of the modularity.
|
| 177 |
+
For directed graphs the second formula replaces $k_c$ with $k^{in}_c k^{out}_c$.
|
| 178 |
+
|
| 179 |
+
Parameters
|
| 180 |
+
----------
|
| 181 |
+
G : NetworkX Graph
|
| 182 |
+
|
| 183 |
+
communities : list or iterable of set of nodes
|
| 184 |
+
These node sets must represent a partition of G's nodes.
|
| 185 |
+
|
| 186 |
+
weight : string or None, optional (default="weight")
|
| 187 |
+
The edge attribute that holds the numerical value used
|
| 188 |
+
as a weight. If None or an edge does not have that attribute,
|
| 189 |
+
then that edge has weight 1.
|
| 190 |
+
|
| 191 |
+
resolution : float (default=1)
|
| 192 |
+
If resolution is less than 1, modularity favors larger communities.
|
| 193 |
+
Greater than 1 favors smaller communities.
|
| 194 |
+
|
| 195 |
+
Returns
|
| 196 |
+
-------
|
| 197 |
+
Q : float
|
| 198 |
+
The modularity of the partition.
|
| 199 |
+
|
| 200 |
+
Raises
|
| 201 |
+
------
|
| 202 |
+
NotAPartition
|
| 203 |
+
If `communities` is not a partition of the nodes of `G`.
|
| 204 |
+
|
| 205 |
+
Examples
|
| 206 |
+
--------
|
| 207 |
+
>>> G = nx.barbell_graph(3, 0)
|
| 208 |
+
>>> nx.community.modularity(G, [{0, 1, 2}, {3, 4, 5}])
|
| 209 |
+
0.35714285714285715
|
| 210 |
+
>>> nx.community.modularity(G, nx.community.label_propagation_communities(G))
|
| 211 |
+
0.35714285714285715
|
| 212 |
+
|
| 213 |
+
References
|
| 214 |
+
----------
|
| 215 |
+
.. [1] M. E. J. Newman "Networks: An Introduction", page 224.
|
| 216 |
+
Oxford University Press, 2011.
|
| 217 |
+
.. [2] Clauset, Aaron, Mark EJ Newman, and Cristopher Moore.
|
| 218 |
+
"Finding community structure in very large networks."
|
| 219 |
+
Phys. Rev. E 70.6 (2004). <https://arxiv.org/abs/cond-mat/0408187>
|
| 220 |
+
.. [3] Reichardt and Bornholdt "Statistical Mechanics of Community Detection"
|
| 221 |
+
Phys. Rev. E 74, 016110, 2006. https://doi.org/10.1103/PhysRevE.74.016110
|
| 222 |
+
.. [4] M. E. J. Newman, "Equivalence between modularity optimization and
|
| 223 |
+
maximum likelihood methods for community detection"
|
| 224 |
+
Phys. Rev. E 94, 052315, 2016. https://doi.org/10.1103/PhysRevE.94.052315
|
| 225 |
+
.. [5] Blondel, V.D. et al. "Fast unfolding of communities in large
|
| 226 |
+
networks" J. Stat. Mech 10008, 1-12 (2008).
|
| 227 |
+
https://doi.org/10.1088/1742-5468/2008/10/P10008
|
| 228 |
+
"""
|
| 229 |
+
if not isinstance(communities, list):
|
| 230 |
+
communities = list(communities)
|
| 231 |
+
if not is_partition(G, communities):
|
| 232 |
+
raise NotAPartition(G, communities)
|
| 233 |
+
|
| 234 |
+
directed = G.is_directed()
|
| 235 |
+
if directed:
|
| 236 |
+
out_degree = dict(G.out_degree(weight=weight))
|
| 237 |
+
in_degree = dict(G.in_degree(weight=weight))
|
| 238 |
+
m = sum(out_degree.values())
|
| 239 |
+
norm = 1 / m**2
|
| 240 |
+
else:
|
| 241 |
+
out_degree = in_degree = dict(G.degree(weight=weight))
|
| 242 |
+
deg_sum = sum(out_degree.values())
|
| 243 |
+
m = deg_sum / 2
|
| 244 |
+
norm = 1 / deg_sum**2
|
| 245 |
+
|
| 246 |
+
def community_contribution(community):
|
| 247 |
+
comm = set(community)
|
| 248 |
+
L_c = sum(wt for u, v, wt in G.edges(comm, data=weight, default=1) if v in comm)
|
| 249 |
+
|
| 250 |
+
out_degree_sum = sum(out_degree[u] for u in comm)
|
| 251 |
+
in_degree_sum = sum(in_degree[u] for u in comm) if directed else out_degree_sum
|
| 252 |
+
|
| 253 |
+
return L_c / m - resolution * out_degree_sum * in_degree_sum * norm
|
| 254 |
+
|
| 255 |
+
return sum(map(community_contribution, communities))
|
| 256 |
+
|
| 257 |
+
|
| 258 |
+
@require_partition
|
| 259 |
+
@nx._dispatch
|
| 260 |
+
def partition_quality(G, partition):
|
| 261 |
+
"""Returns the coverage and performance of a partition of G.
|
| 262 |
+
|
| 263 |
+
The *coverage* of a partition is the ratio of the number of
|
| 264 |
+
intra-community edges to the total number of edges in the graph.
|
| 265 |
+
|
| 266 |
+
The *performance* of a partition is the number of
|
| 267 |
+
intra-community edges plus inter-community non-edges divided by the total
|
| 268 |
+
number of potential edges.
|
| 269 |
+
|
| 270 |
+
This algorithm has complexity $O(C^2 + L)$ where C is the number of communities and L is the number of links.
|
| 271 |
+
|
| 272 |
+
Parameters
|
| 273 |
+
----------
|
| 274 |
+
G : NetworkX graph
|
| 275 |
+
|
| 276 |
+
partition : sequence
|
| 277 |
+
Partition of the nodes of `G`, represented as a sequence of
|
| 278 |
+
sets of nodes (blocks). Each block of the partition represents a
|
| 279 |
+
community.
|
| 280 |
+
|
| 281 |
+
Returns
|
| 282 |
+
-------
|
| 283 |
+
(float, float)
|
| 284 |
+
The (coverage, performance) tuple of the partition, as defined above.
|
| 285 |
+
|
| 286 |
+
Raises
|
| 287 |
+
------
|
| 288 |
+
NetworkXError
|
| 289 |
+
If `partition` is not a valid partition of the nodes of `G`.
|
| 290 |
+
|
| 291 |
+
Notes
|
| 292 |
+
-----
|
| 293 |
+
If `G` is a multigraph;
|
| 294 |
+
- for coverage, the multiplicity of edges is counted
|
| 295 |
+
- for performance, the result is -1 (total number of possible edges is not defined)
|
| 296 |
+
|
| 297 |
+
References
|
| 298 |
+
----------
|
| 299 |
+
.. [1] Santo Fortunato.
|
| 300 |
+
"Community Detection in Graphs".
|
| 301 |
+
*Physical Reports*, Volume 486, Issue 3--5 pp. 75--174
|
| 302 |
+
<https://arxiv.org/abs/0906.0612>
|
| 303 |
+
"""
|
| 304 |
+
|
| 305 |
+
node_community = {}
|
| 306 |
+
for i, community in enumerate(partition):
|
| 307 |
+
for node in community:
|
| 308 |
+
node_community[node] = i
|
| 309 |
+
|
| 310 |
+
# `performance` is not defined for multigraphs
|
| 311 |
+
if not G.is_multigraph():
|
| 312 |
+
# Iterate over the communities, quadratic, to calculate `possible_inter_community_edges`
|
| 313 |
+
possible_inter_community_edges = sum(
|
| 314 |
+
len(p1) * len(p2) for p1, p2 in combinations(partition, 2)
|
| 315 |
+
)
|
| 316 |
+
|
| 317 |
+
if G.is_directed():
|
| 318 |
+
possible_inter_community_edges *= 2
|
| 319 |
+
else:
|
| 320 |
+
possible_inter_community_edges = 0
|
| 321 |
+
|
| 322 |
+
# Compute the number of edges in the complete graph -- `n` nodes,
|
| 323 |
+
# directed or undirected, depending on `G`
|
| 324 |
+
n = len(G)
|
| 325 |
+
total_pairs = n * (n - 1)
|
| 326 |
+
if not G.is_directed():
|
| 327 |
+
total_pairs //= 2
|
| 328 |
+
|
| 329 |
+
intra_community_edges = 0
|
| 330 |
+
inter_community_non_edges = possible_inter_community_edges
|
| 331 |
+
|
| 332 |
+
# Iterate over the links to count `intra_community_edges` and `inter_community_non_edges`
|
| 333 |
+
for e in G.edges():
|
| 334 |
+
if node_community[e[0]] == node_community[e[1]]:
|
| 335 |
+
intra_community_edges += 1
|
| 336 |
+
else:
|
| 337 |
+
inter_community_non_edges -= 1
|
| 338 |
+
|
| 339 |
+
coverage = intra_community_edges / len(G.edges)
|
| 340 |
+
|
| 341 |
+
if G.is_multigraph():
|
| 342 |
+
performance = -1.0
|
| 343 |
+
else:
|
| 344 |
+
performance = (intra_community_edges + inter_community_non_edges) / total_pairs
|
| 345 |
+
|
| 346 |
+
return coverage, performance
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/test_kernighan_lin.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Unit tests for the :mod:`networkx.algorithms.community.kernighan_lin`
|
| 2 |
+
module.
|
| 3 |
+
"""
|
| 4 |
+
from itertools import permutations
|
| 5 |
+
|
| 6 |
+
import pytest
|
| 7 |
+
|
| 8 |
+
import networkx as nx
|
| 9 |
+
from networkx.algorithms.community import kernighan_lin_bisection
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def assert_partition_equal(x, y):
|
| 13 |
+
assert set(map(frozenset, x)) == set(map(frozenset, y))
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def test_partition():
|
| 17 |
+
G = nx.barbell_graph(3, 0)
|
| 18 |
+
C = kernighan_lin_bisection(G)
|
| 19 |
+
assert_partition_equal(C, [{0, 1, 2}, {3, 4, 5}])
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def test_partition_argument():
|
| 23 |
+
G = nx.barbell_graph(3, 0)
|
| 24 |
+
partition = [{0, 1, 2}, {3, 4, 5}]
|
| 25 |
+
C = kernighan_lin_bisection(G, partition)
|
| 26 |
+
assert_partition_equal(C, partition)
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def test_partition_argument_non_integer_nodes():
|
| 30 |
+
G = nx.Graph([("A", "B"), ("A", "C"), ("B", "C"), ("C", "D")])
|
| 31 |
+
partition = ({"A", "B"}, {"C", "D"})
|
| 32 |
+
C = kernighan_lin_bisection(G, partition)
|
| 33 |
+
assert_partition_equal(C, partition)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def test_seed_argument():
|
| 37 |
+
G = nx.barbell_graph(3, 0)
|
| 38 |
+
C = kernighan_lin_bisection(G, seed=1)
|
| 39 |
+
assert_partition_equal(C, [{0, 1, 2}, {3, 4, 5}])
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def test_non_disjoint_partition():
|
| 43 |
+
with pytest.raises(nx.NetworkXError):
|
| 44 |
+
G = nx.barbell_graph(3, 0)
|
| 45 |
+
partition = ({0, 1, 2}, {2, 3, 4, 5})
|
| 46 |
+
kernighan_lin_bisection(G, partition)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def test_too_many_blocks():
|
| 50 |
+
with pytest.raises(nx.NetworkXError):
|
| 51 |
+
G = nx.barbell_graph(3, 0)
|
| 52 |
+
partition = ({0, 1}, {2}, {3, 4, 5})
|
| 53 |
+
kernighan_lin_bisection(G, partition)
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def test_multigraph():
|
| 57 |
+
G = nx.cycle_graph(4)
|
| 58 |
+
M = nx.MultiGraph(G.edges())
|
| 59 |
+
M.add_edges_from(G.edges())
|
| 60 |
+
M.remove_edge(1, 2)
|
| 61 |
+
for labels in permutations(range(4)):
|
| 62 |
+
mapping = dict(zip(M, labels))
|
| 63 |
+
A, B = kernighan_lin_bisection(nx.relabel_nodes(M, mapping), seed=0)
|
| 64 |
+
assert_partition_equal(
|
| 65 |
+
[A, B], [{mapping[0], mapping[1]}, {mapping[2], mapping[3]}]
|
| 66 |
+
)
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def test_max_iter_argument():
|
| 70 |
+
G = nx.Graph(
|
| 71 |
+
[
|
| 72 |
+
("A", "B", {"weight": 1}),
|
| 73 |
+
("A", "C", {"weight": 2}),
|
| 74 |
+
("A", "D", {"weight": 3}),
|
| 75 |
+
("A", "E", {"weight": 2}),
|
| 76 |
+
("A", "F", {"weight": 4}),
|
| 77 |
+
("B", "C", {"weight": 1}),
|
| 78 |
+
("B", "D", {"weight": 4}),
|
| 79 |
+
("B", "E", {"weight": 2}),
|
| 80 |
+
("B", "F", {"weight": 1}),
|
| 81 |
+
("C", "D", {"weight": 3}),
|
| 82 |
+
("C", "E", {"weight": 2}),
|
| 83 |
+
("C", "F", {"weight": 1}),
|
| 84 |
+
("D", "E", {"weight": 4}),
|
| 85 |
+
("D", "F", {"weight": 3}),
|
| 86 |
+
("E", "F", {"weight": 2}),
|
| 87 |
+
]
|
| 88 |
+
)
|
| 89 |
+
partition = ({"A", "B", "C"}, {"D", "E", "F"})
|
| 90 |
+
C = kernighan_lin_bisection(G, partition, max_iter=1)
|
| 91 |
+
assert_partition_equal(C, ({"A", "F", "C"}, {"D", "E", "B"}))
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/dominance.py
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Dominance algorithms.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
from functools import reduce
|
| 6 |
+
|
| 7 |
+
import networkx as nx
|
| 8 |
+
from networkx.utils import not_implemented_for
|
| 9 |
+
|
| 10 |
+
__all__ = ["immediate_dominators", "dominance_frontiers"]
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
@not_implemented_for("undirected")
|
| 14 |
+
@nx._dispatch
|
| 15 |
+
def immediate_dominators(G, start):
|
| 16 |
+
"""Returns the immediate dominators of all nodes of a directed graph.
|
| 17 |
+
|
| 18 |
+
Parameters
|
| 19 |
+
----------
|
| 20 |
+
G : a DiGraph or MultiDiGraph
|
| 21 |
+
The graph where dominance is to be computed.
|
| 22 |
+
|
| 23 |
+
start : node
|
| 24 |
+
The start node of dominance computation.
|
| 25 |
+
|
| 26 |
+
Returns
|
| 27 |
+
-------
|
| 28 |
+
idom : dict keyed by nodes
|
| 29 |
+
A dict containing the immediate dominators of each node reachable from
|
| 30 |
+
`start`.
|
| 31 |
+
|
| 32 |
+
Raises
|
| 33 |
+
------
|
| 34 |
+
NetworkXNotImplemented
|
| 35 |
+
If `G` is undirected.
|
| 36 |
+
|
| 37 |
+
NetworkXError
|
| 38 |
+
If `start` is not in `G`.
|
| 39 |
+
|
| 40 |
+
Notes
|
| 41 |
+
-----
|
| 42 |
+
Except for `start`, the immediate dominators are the parents of their
|
| 43 |
+
corresponding nodes in the dominator tree.
|
| 44 |
+
|
| 45 |
+
Examples
|
| 46 |
+
--------
|
| 47 |
+
>>> G = nx.DiGraph([(1, 2), (1, 3), (2, 5), (3, 4), (4, 5)])
|
| 48 |
+
>>> sorted(nx.immediate_dominators(G, 1).items())
|
| 49 |
+
[(1, 1), (2, 1), (3, 1), (4, 3), (5, 1)]
|
| 50 |
+
|
| 51 |
+
References
|
| 52 |
+
----------
|
| 53 |
+
.. [1] K. D. Cooper, T. J. Harvey, and K. Kennedy.
|
| 54 |
+
A simple, fast dominance algorithm.
|
| 55 |
+
Software Practice & Experience, 4:110, 2001.
|
| 56 |
+
"""
|
| 57 |
+
if start not in G:
|
| 58 |
+
raise nx.NetworkXError("start is not in G")
|
| 59 |
+
|
| 60 |
+
idom = {start: start}
|
| 61 |
+
|
| 62 |
+
order = list(nx.dfs_postorder_nodes(G, start))
|
| 63 |
+
dfn = {u: i for i, u in enumerate(order)}
|
| 64 |
+
order.pop()
|
| 65 |
+
order.reverse()
|
| 66 |
+
|
| 67 |
+
def intersect(u, v):
|
| 68 |
+
while u != v:
|
| 69 |
+
while dfn[u] < dfn[v]:
|
| 70 |
+
u = idom[u]
|
| 71 |
+
while dfn[u] > dfn[v]:
|
| 72 |
+
v = idom[v]
|
| 73 |
+
return u
|
| 74 |
+
|
| 75 |
+
changed = True
|
| 76 |
+
while changed:
|
| 77 |
+
changed = False
|
| 78 |
+
for u in order:
|
| 79 |
+
new_idom = reduce(intersect, (v for v in G.pred[u] if v in idom))
|
| 80 |
+
if u not in idom or idom[u] != new_idom:
|
| 81 |
+
idom[u] = new_idom
|
| 82 |
+
changed = True
|
| 83 |
+
|
| 84 |
+
return idom
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
@nx._dispatch
|
| 88 |
+
def dominance_frontiers(G, start):
|
| 89 |
+
"""Returns the dominance frontiers of all nodes of a directed graph.
|
| 90 |
+
|
| 91 |
+
Parameters
|
| 92 |
+
----------
|
| 93 |
+
G : a DiGraph or MultiDiGraph
|
| 94 |
+
The graph where dominance is to be computed.
|
| 95 |
+
|
| 96 |
+
start : node
|
| 97 |
+
The start node of dominance computation.
|
| 98 |
+
|
| 99 |
+
Returns
|
| 100 |
+
-------
|
| 101 |
+
df : dict keyed by nodes
|
| 102 |
+
A dict containing the dominance frontiers of each node reachable from
|
| 103 |
+
`start` as lists.
|
| 104 |
+
|
| 105 |
+
Raises
|
| 106 |
+
------
|
| 107 |
+
NetworkXNotImplemented
|
| 108 |
+
If `G` is undirected.
|
| 109 |
+
|
| 110 |
+
NetworkXError
|
| 111 |
+
If `start` is not in `G`.
|
| 112 |
+
|
| 113 |
+
Examples
|
| 114 |
+
--------
|
| 115 |
+
>>> G = nx.DiGraph([(1, 2), (1, 3), (2, 5), (3, 4), (4, 5)])
|
| 116 |
+
>>> sorted((u, sorted(df)) for u, df in nx.dominance_frontiers(G, 1).items())
|
| 117 |
+
[(1, []), (2, [5]), (3, [5]), (4, [5]), (5, [])]
|
| 118 |
+
|
| 119 |
+
References
|
| 120 |
+
----------
|
| 121 |
+
.. [1] K. D. Cooper, T. J. Harvey, and K. Kennedy.
|
| 122 |
+
A simple, fast dominance algorithm.
|
| 123 |
+
Software Practice & Experience, 4:110, 2001.
|
| 124 |
+
"""
|
| 125 |
+
idom = nx.immediate_dominators(G, start)
|
| 126 |
+
|
| 127 |
+
df = {u: set() for u in idom}
|
| 128 |
+
for u in idom:
|
| 129 |
+
if len(G.pred[u]) >= 2:
|
| 130 |
+
for v in G.pred[u]:
|
| 131 |
+
if v in idom:
|
| 132 |
+
while v != idom[u]:
|
| 133 |
+
df[v].add(u)
|
| 134 |
+
v = idom[v]
|
| 135 |
+
return df
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/flow/__pycache__/networksimplex.cpython-311.pyc
ADDED
|
Binary file (32.3 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/flow/__pycache__/utils.cpython-311.pyc
ADDED
|
Binary file (9.74 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/flow/dinitz_alg.py
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Dinitz' algorithm for maximum flow problems.
|
| 3 |
+
"""
|
| 4 |
+
from collections import deque
|
| 5 |
+
|
| 6 |
+
import networkx as nx
|
| 7 |
+
from networkx.algorithms.flow.utils import build_residual_network
|
| 8 |
+
from networkx.utils import pairwise
|
| 9 |
+
|
| 10 |
+
__all__ = ["dinitz"]
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
@nx._dispatch(
|
| 14 |
+
graphs={"G": 0, "residual?": 4},
|
| 15 |
+
edge_attrs={"capacity": float("inf")},
|
| 16 |
+
preserve_edge_attrs={"residual": {"capacity": float("inf")}},
|
| 17 |
+
preserve_graph_attrs={"residual"},
|
| 18 |
+
)
|
| 19 |
+
def dinitz(G, s, t, capacity="capacity", residual=None, value_only=False, cutoff=None):
|
| 20 |
+
"""Find a maximum single-commodity flow using Dinitz' algorithm.
|
| 21 |
+
|
| 22 |
+
This function returns the residual network resulting after computing
|
| 23 |
+
the maximum flow. See below for details about the conventions
|
| 24 |
+
NetworkX uses for defining residual networks.
|
| 25 |
+
|
| 26 |
+
This algorithm has a running time of $O(n^2 m)$ for $n$ nodes and $m$
|
| 27 |
+
edges [1]_.
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
Parameters
|
| 31 |
+
----------
|
| 32 |
+
G : NetworkX graph
|
| 33 |
+
Edges of the graph are expected to have an attribute called
|
| 34 |
+
'capacity'. If this attribute is not present, the edge is
|
| 35 |
+
considered to have infinite capacity.
|
| 36 |
+
|
| 37 |
+
s : node
|
| 38 |
+
Source node for the flow.
|
| 39 |
+
|
| 40 |
+
t : node
|
| 41 |
+
Sink node for the flow.
|
| 42 |
+
|
| 43 |
+
capacity : string
|
| 44 |
+
Edges of the graph G are expected to have an attribute capacity
|
| 45 |
+
that indicates how much flow the edge can support. If this
|
| 46 |
+
attribute is not present, the edge is considered to have
|
| 47 |
+
infinite capacity. Default value: 'capacity'.
|
| 48 |
+
|
| 49 |
+
residual : NetworkX graph
|
| 50 |
+
Residual network on which the algorithm is to be executed. If None, a
|
| 51 |
+
new residual network is created. Default value: None.
|
| 52 |
+
|
| 53 |
+
value_only : bool
|
| 54 |
+
If True compute only the value of the maximum flow. This parameter
|
| 55 |
+
will be ignored by this algorithm because it is not applicable.
|
| 56 |
+
|
| 57 |
+
cutoff : integer, float
|
| 58 |
+
If specified, the algorithm will terminate when the flow value reaches
|
| 59 |
+
or exceeds the cutoff. In this case, it may be unable to immediately
|
| 60 |
+
determine a minimum cut. Default value: None.
|
| 61 |
+
|
| 62 |
+
Returns
|
| 63 |
+
-------
|
| 64 |
+
R : NetworkX DiGraph
|
| 65 |
+
Residual network after computing the maximum flow.
|
| 66 |
+
|
| 67 |
+
Raises
|
| 68 |
+
------
|
| 69 |
+
NetworkXError
|
| 70 |
+
The algorithm does not support MultiGraph and MultiDiGraph. If
|
| 71 |
+
the input graph is an instance of one of these two classes, a
|
| 72 |
+
NetworkXError is raised.
|
| 73 |
+
|
| 74 |
+
NetworkXUnbounded
|
| 75 |
+
If the graph has a path of infinite capacity, the value of a
|
| 76 |
+
feasible flow on the graph is unbounded above and the function
|
| 77 |
+
raises a NetworkXUnbounded.
|
| 78 |
+
|
| 79 |
+
See also
|
| 80 |
+
--------
|
| 81 |
+
:meth:`maximum_flow`
|
| 82 |
+
:meth:`minimum_cut`
|
| 83 |
+
:meth:`preflow_push`
|
| 84 |
+
:meth:`shortest_augmenting_path`
|
| 85 |
+
|
| 86 |
+
Notes
|
| 87 |
+
-----
|
| 88 |
+
The residual network :samp:`R` from an input graph :samp:`G` has the
|
| 89 |
+
same nodes as :samp:`G`. :samp:`R` is a DiGraph that contains a pair
|
| 90 |
+
of edges :samp:`(u, v)` and :samp:`(v, u)` iff :samp:`(u, v)` is not a
|
| 91 |
+
self-loop, and at least one of :samp:`(u, v)` and :samp:`(v, u)` exists
|
| 92 |
+
in :samp:`G`.
|
| 93 |
+
|
| 94 |
+
For each edge :samp:`(u, v)` in :samp:`R`, :samp:`R[u][v]['capacity']`
|
| 95 |
+
is equal to the capacity of :samp:`(u, v)` in :samp:`G` if it exists
|
| 96 |
+
in :samp:`G` or zero otherwise. If the capacity is infinite,
|
| 97 |
+
:samp:`R[u][v]['capacity']` will have a high arbitrary finite value
|
| 98 |
+
that does not affect the solution of the problem. This value is stored in
|
| 99 |
+
:samp:`R.graph['inf']`. For each edge :samp:`(u, v)` in :samp:`R`,
|
| 100 |
+
:samp:`R[u][v]['flow']` represents the flow function of :samp:`(u, v)` and
|
| 101 |
+
satisfies :samp:`R[u][v]['flow'] == -R[v][u]['flow']`.
|
| 102 |
+
|
| 103 |
+
The flow value, defined as the total flow into :samp:`t`, the sink, is
|
| 104 |
+
stored in :samp:`R.graph['flow_value']`. If :samp:`cutoff` is not
|
| 105 |
+
specified, reachability to :samp:`t` using only edges :samp:`(u, v)` such
|
| 106 |
+
that :samp:`R[u][v]['flow'] < R[u][v]['capacity']` induces a minimum
|
| 107 |
+
:samp:`s`-:samp:`t` cut.
|
| 108 |
+
|
| 109 |
+
Examples
|
| 110 |
+
--------
|
| 111 |
+
>>> from networkx.algorithms.flow import dinitz
|
| 112 |
+
|
| 113 |
+
The functions that implement flow algorithms and output a residual
|
| 114 |
+
network, such as this one, are not imported to the base NetworkX
|
| 115 |
+
namespace, so you have to explicitly import them from the flow package.
|
| 116 |
+
|
| 117 |
+
>>> G = nx.DiGraph()
|
| 118 |
+
>>> G.add_edge("x", "a", capacity=3.0)
|
| 119 |
+
>>> G.add_edge("x", "b", capacity=1.0)
|
| 120 |
+
>>> G.add_edge("a", "c", capacity=3.0)
|
| 121 |
+
>>> G.add_edge("b", "c", capacity=5.0)
|
| 122 |
+
>>> G.add_edge("b", "d", capacity=4.0)
|
| 123 |
+
>>> G.add_edge("d", "e", capacity=2.0)
|
| 124 |
+
>>> G.add_edge("c", "y", capacity=2.0)
|
| 125 |
+
>>> G.add_edge("e", "y", capacity=3.0)
|
| 126 |
+
>>> R = dinitz(G, "x", "y")
|
| 127 |
+
>>> flow_value = nx.maximum_flow_value(G, "x", "y")
|
| 128 |
+
>>> flow_value
|
| 129 |
+
3.0
|
| 130 |
+
>>> flow_value == R.graph["flow_value"]
|
| 131 |
+
True
|
| 132 |
+
|
| 133 |
+
References
|
| 134 |
+
----------
|
| 135 |
+
.. [1] Dinitz' Algorithm: The Original Version and Even's Version.
|
| 136 |
+
2006. Yefim Dinitz. In Theoretical Computer Science. Lecture
|
| 137 |
+
Notes in Computer Science. Volume 3895. pp 218-240.
|
| 138 |
+
https://doi.org/10.1007/11685654_10
|
| 139 |
+
|
| 140 |
+
"""
|
| 141 |
+
R = dinitz_impl(G, s, t, capacity, residual, cutoff)
|
| 142 |
+
R.graph["algorithm"] = "dinitz"
|
| 143 |
+
return R
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
def dinitz_impl(G, s, t, capacity, residual, cutoff):
|
| 147 |
+
if s not in G:
|
| 148 |
+
raise nx.NetworkXError(f"node {str(s)} not in graph")
|
| 149 |
+
if t not in G:
|
| 150 |
+
raise nx.NetworkXError(f"node {str(t)} not in graph")
|
| 151 |
+
if s == t:
|
| 152 |
+
raise nx.NetworkXError("source and sink are the same node")
|
| 153 |
+
|
| 154 |
+
if residual is None:
|
| 155 |
+
R = build_residual_network(G, capacity)
|
| 156 |
+
else:
|
| 157 |
+
R = residual
|
| 158 |
+
|
| 159 |
+
# Initialize/reset the residual network.
|
| 160 |
+
for u in R:
|
| 161 |
+
for e in R[u].values():
|
| 162 |
+
e["flow"] = 0
|
| 163 |
+
|
| 164 |
+
# Use an arbitrary high value as infinite. It is computed
|
| 165 |
+
# when building the residual network.
|
| 166 |
+
INF = R.graph["inf"]
|
| 167 |
+
|
| 168 |
+
if cutoff is None:
|
| 169 |
+
cutoff = INF
|
| 170 |
+
|
| 171 |
+
R_succ = R.succ
|
| 172 |
+
R_pred = R.pred
|
| 173 |
+
|
| 174 |
+
def breath_first_search():
|
| 175 |
+
parents = {}
|
| 176 |
+
queue = deque([s])
|
| 177 |
+
while queue:
|
| 178 |
+
if t in parents:
|
| 179 |
+
break
|
| 180 |
+
u = queue.popleft()
|
| 181 |
+
for v in R_succ[u]:
|
| 182 |
+
attr = R_succ[u][v]
|
| 183 |
+
if v not in parents and attr["capacity"] - attr["flow"] > 0:
|
| 184 |
+
parents[v] = u
|
| 185 |
+
queue.append(v)
|
| 186 |
+
return parents
|
| 187 |
+
|
| 188 |
+
def depth_first_search(parents):
|
| 189 |
+
"""Build a path using DFS starting from the sink"""
|
| 190 |
+
path = []
|
| 191 |
+
u = t
|
| 192 |
+
flow = INF
|
| 193 |
+
while u != s:
|
| 194 |
+
path.append(u)
|
| 195 |
+
v = parents[u]
|
| 196 |
+
flow = min(flow, R_pred[u][v]["capacity"] - R_pred[u][v]["flow"])
|
| 197 |
+
u = v
|
| 198 |
+
path.append(s)
|
| 199 |
+
# Augment the flow along the path found
|
| 200 |
+
if flow > 0:
|
| 201 |
+
for u, v in pairwise(path):
|
| 202 |
+
R_pred[u][v]["flow"] += flow
|
| 203 |
+
R_pred[v][u]["flow"] -= flow
|
| 204 |
+
return flow
|
| 205 |
+
|
| 206 |
+
flow_value = 0
|
| 207 |
+
while flow_value < cutoff:
|
| 208 |
+
parents = breath_first_search()
|
| 209 |
+
if t not in parents:
|
| 210 |
+
break
|
| 211 |
+
this_flow = depth_first_search(parents)
|
| 212 |
+
if this_flow * 2 > INF:
|
| 213 |
+
raise nx.NetworkXUnbounded("Infinite capacity path, flow unbounded above.")
|
| 214 |
+
flow_value += this_flow
|
| 215 |
+
|
| 216 |
+
R.graph["flow_value"] = flow_value
|
| 217 |
+
return R
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/flow/tests/__init__.py
ADDED
|
File without changes
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/flow/tests/__pycache__/test_gomory_hu.cpython-311.pyc
ADDED
|
Binary file (9.78 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/flow/tests/test_maxflow_large_graph.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Maximum flow algorithms test suite on large graphs.
|
| 2 |
+
"""
|
| 3 |
+
|
| 4 |
+
import bz2
|
| 5 |
+
import importlib.resources
|
| 6 |
+
import os
|
| 7 |
+
import pickle
|
| 8 |
+
|
| 9 |
+
import pytest
|
| 10 |
+
|
| 11 |
+
import networkx as nx
|
| 12 |
+
from networkx.algorithms.flow import (
|
| 13 |
+
boykov_kolmogorov,
|
| 14 |
+
build_flow_dict,
|
| 15 |
+
build_residual_network,
|
| 16 |
+
dinitz,
|
| 17 |
+
edmonds_karp,
|
| 18 |
+
preflow_push,
|
| 19 |
+
shortest_augmenting_path,
|
| 20 |
+
)
|
| 21 |
+
|
| 22 |
+
flow_funcs = [
|
| 23 |
+
boykov_kolmogorov,
|
| 24 |
+
dinitz,
|
| 25 |
+
edmonds_karp,
|
| 26 |
+
preflow_push,
|
| 27 |
+
shortest_augmenting_path,
|
| 28 |
+
]
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def gen_pyramid(N):
|
| 32 |
+
# This graph admits a flow of value 1 for which every arc is at
|
| 33 |
+
# capacity (except the arcs incident to the sink which have
|
| 34 |
+
# infinite capacity).
|
| 35 |
+
G = nx.DiGraph()
|
| 36 |
+
|
| 37 |
+
for i in range(N - 1):
|
| 38 |
+
cap = 1.0 / (i + 2)
|
| 39 |
+
for j in range(i + 1):
|
| 40 |
+
G.add_edge((i, j), (i + 1, j), capacity=cap)
|
| 41 |
+
cap = 1.0 / (i + 1) - cap
|
| 42 |
+
G.add_edge((i, j), (i + 1, j + 1), capacity=cap)
|
| 43 |
+
cap = 1.0 / (i + 2) - cap
|
| 44 |
+
|
| 45 |
+
for j in range(N):
|
| 46 |
+
G.add_edge((N - 1, j), "t")
|
| 47 |
+
|
| 48 |
+
return G
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def read_graph(name):
|
| 52 |
+
fname = (
|
| 53 |
+
importlib.resources.files("networkx.algorithms.flow.tests")
|
| 54 |
+
/ f"{name}.gpickle.bz2"
|
| 55 |
+
)
|
| 56 |
+
|
| 57 |
+
with bz2.BZ2File(fname, "rb") as f:
|
| 58 |
+
G = pickle.load(f)
|
| 59 |
+
return G
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def validate_flows(G, s, t, soln_value, R, flow_func):
|
| 63 |
+
flow_value = R.graph["flow_value"]
|
| 64 |
+
flow_dict = build_flow_dict(G, R)
|
| 65 |
+
errmsg = f"Assertion failed in function: {flow_func.__name__}"
|
| 66 |
+
assert soln_value == flow_value, errmsg
|
| 67 |
+
assert set(G) == set(flow_dict), errmsg
|
| 68 |
+
for u in G:
|
| 69 |
+
assert set(G[u]) == set(flow_dict[u]), errmsg
|
| 70 |
+
excess = {u: 0 for u in flow_dict}
|
| 71 |
+
for u in flow_dict:
|
| 72 |
+
for v, flow in flow_dict[u].items():
|
| 73 |
+
assert flow <= G[u][v].get("capacity", float("inf")), errmsg
|
| 74 |
+
assert flow >= 0, errmsg
|
| 75 |
+
excess[u] -= flow
|
| 76 |
+
excess[v] += flow
|
| 77 |
+
for u, exc in excess.items():
|
| 78 |
+
if u == s:
|
| 79 |
+
assert exc == -soln_value, errmsg
|
| 80 |
+
elif u == t:
|
| 81 |
+
assert exc == soln_value, errmsg
|
| 82 |
+
else:
|
| 83 |
+
assert exc == 0, errmsg
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
class TestMaxflowLargeGraph:
|
| 87 |
+
def test_complete_graph(self):
|
| 88 |
+
N = 50
|
| 89 |
+
G = nx.complete_graph(N)
|
| 90 |
+
nx.set_edge_attributes(G, 5, "capacity")
|
| 91 |
+
R = build_residual_network(G, "capacity")
|
| 92 |
+
kwargs = {"residual": R}
|
| 93 |
+
|
| 94 |
+
for flow_func in flow_funcs:
|
| 95 |
+
kwargs["flow_func"] = flow_func
|
| 96 |
+
errmsg = f"Assertion failed in function: {flow_func.__name__}"
|
| 97 |
+
flow_value = nx.maximum_flow_value(G, 1, 2, **kwargs)
|
| 98 |
+
assert flow_value == 5 * (N - 1), errmsg
|
| 99 |
+
|
| 100 |
+
def test_pyramid(self):
|
| 101 |
+
N = 10
|
| 102 |
+
# N = 100 # this gives a graph with 5051 nodes
|
| 103 |
+
G = gen_pyramid(N)
|
| 104 |
+
R = build_residual_network(G, "capacity")
|
| 105 |
+
kwargs = {"residual": R}
|
| 106 |
+
|
| 107 |
+
for flow_func in flow_funcs:
|
| 108 |
+
kwargs["flow_func"] = flow_func
|
| 109 |
+
errmsg = f"Assertion failed in function: {flow_func.__name__}"
|
| 110 |
+
flow_value = nx.maximum_flow_value(G, (0, 0), "t", **kwargs)
|
| 111 |
+
assert flow_value == pytest.approx(1.0, abs=1e-7)
|
| 112 |
+
|
| 113 |
+
def test_gl1(self):
|
| 114 |
+
G = read_graph("gl1")
|
| 115 |
+
s = 1
|
| 116 |
+
t = len(G)
|
| 117 |
+
R = build_residual_network(G, "capacity")
|
| 118 |
+
kwargs = {"residual": R}
|
| 119 |
+
|
| 120 |
+
# do one flow_func to save time
|
| 121 |
+
flow_func = flow_funcs[0]
|
| 122 |
+
validate_flows(G, s, t, 156545, flow_func(G, s, t, **kwargs), flow_func)
|
| 123 |
+
|
| 124 |
+
# for flow_func in flow_funcs:
|
| 125 |
+
# validate_flows(G, s, t, 156545, flow_func(G, s, t, **kwargs),
|
| 126 |
+
# flow_func)
|
| 127 |
+
|
| 128 |
+
@pytest.mark.slow
|
| 129 |
+
def test_gw1(self):
|
| 130 |
+
G = read_graph("gw1")
|
| 131 |
+
s = 1
|
| 132 |
+
t = len(G)
|
| 133 |
+
R = build_residual_network(G, "capacity")
|
| 134 |
+
kwargs = {"residual": R}
|
| 135 |
+
|
| 136 |
+
for flow_func in flow_funcs:
|
| 137 |
+
validate_flows(G, s, t, 1202018, flow_func(G, s, t, **kwargs), flow_func)
|
| 138 |
+
|
| 139 |
+
def test_wlm3(self):
|
| 140 |
+
G = read_graph("wlm3")
|
| 141 |
+
s = 1
|
| 142 |
+
t = len(G)
|
| 143 |
+
R = build_residual_network(G, "capacity")
|
| 144 |
+
kwargs = {"residual": R}
|
| 145 |
+
|
| 146 |
+
# do one flow_func to save time
|
| 147 |
+
flow_func = flow_funcs[0]
|
| 148 |
+
validate_flows(G, s, t, 11875108, flow_func(G, s, t, **kwargs), flow_func)
|
| 149 |
+
|
| 150 |
+
# for flow_func in flow_funcs:
|
| 151 |
+
# validate_flows(G, s, t, 11875108, flow_func(G, s, t, **kwargs),
|
| 152 |
+
# flow_func)
|
| 153 |
+
|
| 154 |
+
def test_preflow_push_global_relabel(self):
|
| 155 |
+
G = read_graph("gw1")
|
| 156 |
+
R = preflow_push(G, 1, len(G), global_relabel_freq=50)
|
| 157 |
+
assert R.graph["flow_value"] == 1202018
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/link_analysis/__init__.py
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from networkx.algorithms.link_analysis.hits_alg import *
|
| 2 |
+
from networkx.algorithms.link_analysis.pagerank_alg import *
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/link_analysis/__pycache__/pagerank_alg.cpython-311.pyc
ADDED
|
Binary file (22.2 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/link_analysis/hits_alg.py
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Hubs and authorities analysis of graph structure.
|
| 2 |
+
"""
|
| 3 |
+
import networkx as nx
|
| 4 |
+
|
| 5 |
+
__all__ = ["hits"]
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
@nx._dispatch
|
| 9 |
+
def hits(G, max_iter=100, tol=1.0e-8, nstart=None, normalized=True):
|
| 10 |
+
"""Returns HITS hubs and authorities values for nodes.
|
| 11 |
+
|
| 12 |
+
The HITS algorithm computes two numbers for a node.
|
| 13 |
+
Authorities estimates the node value based on the incoming links.
|
| 14 |
+
Hubs estimates the node value based on outgoing links.
|
| 15 |
+
|
| 16 |
+
Parameters
|
| 17 |
+
----------
|
| 18 |
+
G : graph
|
| 19 |
+
A NetworkX graph
|
| 20 |
+
|
| 21 |
+
max_iter : integer, optional
|
| 22 |
+
Maximum number of iterations in power method.
|
| 23 |
+
|
| 24 |
+
tol : float, optional
|
| 25 |
+
Error tolerance used to check convergence in power method iteration.
|
| 26 |
+
|
| 27 |
+
nstart : dictionary, optional
|
| 28 |
+
Starting value of each node for power method iteration.
|
| 29 |
+
|
| 30 |
+
normalized : bool (default=True)
|
| 31 |
+
Normalize results by the sum of all of the values.
|
| 32 |
+
|
| 33 |
+
Returns
|
| 34 |
+
-------
|
| 35 |
+
(hubs,authorities) : two-tuple of dictionaries
|
| 36 |
+
Two dictionaries keyed by node containing the hub and authority
|
| 37 |
+
values.
|
| 38 |
+
|
| 39 |
+
Raises
|
| 40 |
+
------
|
| 41 |
+
PowerIterationFailedConvergence
|
| 42 |
+
If the algorithm fails to converge to the specified tolerance
|
| 43 |
+
within the specified number of iterations of the power iteration
|
| 44 |
+
method.
|
| 45 |
+
|
| 46 |
+
Examples
|
| 47 |
+
--------
|
| 48 |
+
>>> G = nx.path_graph(4)
|
| 49 |
+
>>> h, a = nx.hits(G)
|
| 50 |
+
|
| 51 |
+
Notes
|
| 52 |
+
-----
|
| 53 |
+
The eigenvector calculation is done by the power iteration method
|
| 54 |
+
and has no guarantee of convergence. The iteration will stop
|
| 55 |
+
after max_iter iterations or an error tolerance of
|
| 56 |
+
number_of_nodes(G)*tol has been reached.
|
| 57 |
+
|
| 58 |
+
The HITS algorithm was designed for directed graphs but this
|
| 59 |
+
algorithm does not check if the input graph is directed and will
|
| 60 |
+
execute on undirected graphs.
|
| 61 |
+
|
| 62 |
+
References
|
| 63 |
+
----------
|
| 64 |
+
.. [1] A. Langville and C. Meyer,
|
| 65 |
+
"A survey of eigenvector methods of web information retrieval."
|
| 66 |
+
http://citeseer.ist.psu.edu/713792.html
|
| 67 |
+
.. [2] Jon Kleinberg,
|
| 68 |
+
Authoritative sources in a hyperlinked environment
|
| 69 |
+
Journal of the ACM 46 (5): 604-32, 1999.
|
| 70 |
+
doi:10.1145/324133.324140.
|
| 71 |
+
http://www.cs.cornell.edu/home/kleinber/auth.pdf.
|
| 72 |
+
"""
|
| 73 |
+
import numpy as np
|
| 74 |
+
import scipy as sp
|
| 75 |
+
|
| 76 |
+
if len(G) == 0:
|
| 77 |
+
return {}, {}
|
| 78 |
+
A = nx.adjacency_matrix(G, nodelist=list(G), dtype=float)
|
| 79 |
+
|
| 80 |
+
if nstart is None:
|
| 81 |
+
_, _, vt = sp.sparse.linalg.svds(A, k=1, maxiter=max_iter, tol=tol)
|
| 82 |
+
else:
|
| 83 |
+
nstart = np.array(list(nstart.values()))
|
| 84 |
+
_, _, vt = sp.sparse.linalg.svds(A, k=1, v0=nstart, maxiter=max_iter, tol=tol)
|
| 85 |
+
|
| 86 |
+
a = vt.flatten().real
|
| 87 |
+
h = A @ a
|
| 88 |
+
if normalized:
|
| 89 |
+
h /= h.sum()
|
| 90 |
+
a /= a.sum()
|
| 91 |
+
hubs = dict(zip(G, map(float, h)))
|
| 92 |
+
authorities = dict(zip(G, map(float, a)))
|
| 93 |
+
return hubs, authorities
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def _hits_python(G, max_iter=100, tol=1.0e-8, nstart=None, normalized=True):
|
| 97 |
+
if isinstance(G, (nx.MultiGraph, nx.MultiDiGraph)):
|
| 98 |
+
raise Exception("hits() not defined for graphs with multiedges.")
|
| 99 |
+
if len(G) == 0:
|
| 100 |
+
return {}, {}
|
| 101 |
+
# choose fixed starting vector if not given
|
| 102 |
+
if nstart is None:
|
| 103 |
+
h = dict.fromkeys(G, 1.0 / G.number_of_nodes())
|
| 104 |
+
else:
|
| 105 |
+
h = nstart
|
| 106 |
+
# normalize starting vector
|
| 107 |
+
s = 1.0 / sum(h.values())
|
| 108 |
+
for k in h:
|
| 109 |
+
h[k] *= s
|
| 110 |
+
for _ in range(max_iter): # power iteration: make up to max_iter iterations
|
| 111 |
+
hlast = h
|
| 112 |
+
h = dict.fromkeys(hlast.keys(), 0)
|
| 113 |
+
a = dict.fromkeys(hlast.keys(), 0)
|
| 114 |
+
# this "matrix multiply" looks odd because it is
|
| 115 |
+
# doing a left multiply a^T=hlast^T*G
|
| 116 |
+
for n in h:
|
| 117 |
+
for nbr in G[n]:
|
| 118 |
+
a[nbr] += hlast[n] * G[n][nbr].get("weight", 1)
|
| 119 |
+
# now multiply h=Ga
|
| 120 |
+
for n in h:
|
| 121 |
+
for nbr in G[n]:
|
| 122 |
+
h[n] += a[nbr] * G[n][nbr].get("weight", 1)
|
| 123 |
+
# normalize vector
|
| 124 |
+
s = 1.0 / max(h.values())
|
| 125 |
+
for n in h:
|
| 126 |
+
h[n] *= s
|
| 127 |
+
# normalize vector
|
| 128 |
+
s = 1.0 / max(a.values())
|
| 129 |
+
for n in a:
|
| 130 |
+
a[n] *= s
|
| 131 |
+
# check convergence, l1 norm
|
| 132 |
+
err = sum(abs(h[n] - hlast[n]) for n in h)
|
| 133 |
+
if err < tol:
|
| 134 |
+
break
|
| 135 |
+
else:
|
| 136 |
+
raise nx.PowerIterationFailedConvergence(max_iter)
|
| 137 |
+
if normalized:
|
| 138 |
+
s = 1.0 / sum(a.values())
|
| 139 |
+
for n in a:
|
| 140 |
+
a[n] *= s
|
| 141 |
+
s = 1.0 / sum(h.values())
|
| 142 |
+
for n in h:
|
| 143 |
+
h[n] *= s
|
| 144 |
+
return h, a
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
def _hits_numpy(G, normalized=True):
|
| 148 |
+
"""Returns HITS hubs and authorities values for nodes.
|
| 149 |
+
|
| 150 |
+
The HITS algorithm computes two numbers for a node.
|
| 151 |
+
Authorities estimates the node value based on the incoming links.
|
| 152 |
+
Hubs estimates the node value based on outgoing links.
|
| 153 |
+
|
| 154 |
+
Parameters
|
| 155 |
+
----------
|
| 156 |
+
G : graph
|
| 157 |
+
A NetworkX graph
|
| 158 |
+
|
| 159 |
+
normalized : bool (default=True)
|
| 160 |
+
Normalize results by the sum of all of the values.
|
| 161 |
+
|
| 162 |
+
Returns
|
| 163 |
+
-------
|
| 164 |
+
(hubs,authorities) : two-tuple of dictionaries
|
| 165 |
+
Two dictionaries keyed by node containing the hub and authority
|
| 166 |
+
values.
|
| 167 |
+
|
| 168 |
+
Examples
|
| 169 |
+
--------
|
| 170 |
+
>>> G = nx.path_graph(4)
|
| 171 |
+
|
| 172 |
+
The `hubs` and `authorities` are given by the eigenvectors corresponding to the
|
| 173 |
+
maximum eigenvalues of the hubs_matrix and the authority_matrix, respectively.
|
| 174 |
+
|
| 175 |
+
The ``hubs`` and ``authority`` matrices are computed from the adjacency
|
| 176 |
+
matrix:
|
| 177 |
+
|
| 178 |
+
>>> adj_ary = nx.to_numpy_array(G)
|
| 179 |
+
>>> hubs_matrix = adj_ary @ adj_ary.T
|
| 180 |
+
>>> authority_matrix = adj_ary.T @ adj_ary
|
| 181 |
+
|
| 182 |
+
`_hits_numpy` maps the eigenvector corresponding to the maximum eigenvalue
|
| 183 |
+
of the respective matrices to the nodes in `G`:
|
| 184 |
+
|
| 185 |
+
>>> from networkx.algorithms.link_analysis.hits_alg import _hits_numpy
|
| 186 |
+
>>> hubs, authority = _hits_numpy(G)
|
| 187 |
+
|
| 188 |
+
Notes
|
| 189 |
+
-----
|
| 190 |
+
The eigenvector calculation uses NumPy's interface to LAPACK.
|
| 191 |
+
|
| 192 |
+
The HITS algorithm was designed for directed graphs but this
|
| 193 |
+
algorithm does not check if the input graph is directed and will
|
| 194 |
+
execute on undirected graphs.
|
| 195 |
+
|
| 196 |
+
References
|
| 197 |
+
----------
|
| 198 |
+
.. [1] A. Langville and C. Meyer,
|
| 199 |
+
"A survey of eigenvector methods of web information retrieval."
|
| 200 |
+
http://citeseer.ist.psu.edu/713792.html
|
| 201 |
+
.. [2] Jon Kleinberg,
|
| 202 |
+
Authoritative sources in a hyperlinked environment
|
| 203 |
+
Journal of the ACM 46 (5): 604-32, 1999.
|
| 204 |
+
doi:10.1145/324133.324140.
|
| 205 |
+
http://www.cs.cornell.edu/home/kleinber/auth.pdf.
|
| 206 |
+
"""
|
| 207 |
+
import numpy as np
|
| 208 |
+
|
| 209 |
+
if len(G) == 0:
|
| 210 |
+
return {}, {}
|
| 211 |
+
adj_ary = nx.to_numpy_array(G)
|
| 212 |
+
# Hub matrix
|
| 213 |
+
H = adj_ary @ adj_ary.T
|
| 214 |
+
e, ev = np.linalg.eig(H)
|
| 215 |
+
h = ev[:, np.argmax(e)] # eigenvector corresponding to the maximum eigenvalue
|
| 216 |
+
# Authority matrix
|
| 217 |
+
A = adj_ary.T @ adj_ary
|
| 218 |
+
e, ev = np.linalg.eig(A)
|
| 219 |
+
a = ev[:, np.argmax(e)] # eigenvector corresponding to the maximum eigenvalue
|
| 220 |
+
if normalized:
|
| 221 |
+
h /= h.sum()
|
| 222 |
+
a /= a.sum()
|
| 223 |
+
else:
|
| 224 |
+
h /= h.max()
|
| 225 |
+
a /= a.max()
|
| 226 |
+
hubs = dict(zip(G, map(float, h)))
|
| 227 |
+
authorities = dict(zip(G, map(float, a)))
|
| 228 |
+
return hubs, authorities
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
def _hits_scipy(G, max_iter=100, tol=1.0e-6, nstart=None, normalized=True):
|
| 232 |
+
"""Returns HITS hubs and authorities values for nodes.
|
| 233 |
+
|
| 234 |
+
|
| 235 |
+
The HITS algorithm computes two numbers for a node.
|
| 236 |
+
Authorities estimates the node value based on the incoming links.
|
| 237 |
+
Hubs estimates the node value based on outgoing links.
|
| 238 |
+
|
| 239 |
+
Parameters
|
| 240 |
+
----------
|
| 241 |
+
G : graph
|
| 242 |
+
A NetworkX graph
|
| 243 |
+
|
| 244 |
+
max_iter : integer, optional
|
| 245 |
+
Maximum number of iterations in power method.
|
| 246 |
+
|
| 247 |
+
tol : float, optional
|
| 248 |
+
Error tolerance used to check convergence in power method iteration.
|
| 249 |
+
|
| 250 |
+
nstart : dictionary, optional
|
| 251 |
+
Starting value of each node for power method iteration.
|
| 252 |
+
|
| 253 |
+
normalized : bool (default=True)
|
| 254 |
+
Normalize results by the sum of all of the values.
|
| 255 |
+
|
| 256 |
+
Returns
|
| 257 |
+
-------
|
| 258 |
+
(hubs,authorities) : two-tuple of dictionaries
|
| 259 |
+
Two dictionaries keyed by node containing the hub and authority
|
| 260 |
+
values.
|
| 261 |
+
|
| 262 |
+
Examples
|
| 263 |
+
--------
|
| 264 |
+
>>> from networkx.algorithms.link_analysis.hits_alg import _hits_scipy
|
| 265 |
+
>>> G = nx.path_graph(4)
|
| 266 |
+
>>> h, a = _hits_scipy(G)
|
| 267 |
+
|
| 268 |
+
Notes
|
| 269 |
+
-----
|
| 270 |
+
This implementation uses SciPy sparse matrices.
|
| 271 |
+
|
| 272 |
+
The eigenvector calculation is done by the power iteration method
|
| 273 |
+
and has no guarantee of convergence. The iteration will stop
|
| 274 |
+
after max_iter iterations or an error tolerance of
|
| 275 |
+
number_of_nodes(G)*tol has been reached.
|
| 276 |
+
|
| 277 |
+
The HITS algorithm was designed for directed graphs but this
|
| 278 |
+
algorithm does not check if the input graph is directed and will
|
| 279 |
+
execute on undirected graphs.
|
| 280 |
+
|
| 281 |
+
Raises
|
| 282 |
+
------
|
| 283 |
+
PowerIterationFailedConvergence
|
| 284 |
+
If the algorithm fails to converge to the specified tolerance
|
| 285 |
+
within the specified number of iterations of the power iteration
|
| 286 |
+
method.
|
| 287 |
+
|
| 288 |
+
References
|
| 289 |
+
----------
|
| 290 |
+
.. [1] A. Langville and C. Meyer,
|
| 291 |
+
"A survey of eigenvector methods of web information retrieval."
|
| 292 |
+
http://citeseer.ist.psu.edu/713792.html
|
| 293 |
+
.. [2] Jon Kleinberg,
|
| 294 |
+
Authoritative sources in a hyperlinked environment
|
| 295 |
+
Journal of the ACM 46 (5): 604-632, 1999.
|
| 296 |
+
doi:10.1145/324133.324140.
|
| 297 |
+
http://www.cs.cornell.edu/home/kleinber/auth.pdf.
|
| 298 |
+
"""
|
| 299 |
+
import numpy as np
|
| 300 |
+
|
| 301 |
+
if len(G) == 0:
|
| 302 |
+
return {}, {}
|
| 303 |
+
A = nx.to_scipy_sparse_array(G, nodelist=list(G))
|
| 304 |
+
(n, _) = A.shape # should be square
|
| 305 |
+
ATA = A.T @ A # authority matrix
|
| 306 |
+
# choose fixed starting vector if not given
|
| 307 |
+
if nstart is None:
|
| 308 |
+
x = np.ones((n, 1)) / n
|
| 309 |
+
else:
|
| 310 |
+
x = np.array([nstart.get(n, 0) for n in list(G)], dtype=float)
|
| 311 |
+
x /= x.sum()
|
| 312 |
+
|
| 313 |
+
# power iteration on authority matrix
|
| 314 |
+
i = 0
|
| 315 |
+
while True:
|
| 316 |
+
xlast = x
|
| 317 |
+
x = ATA @ x
|
| 318 |
+
x /= x.max()
|
| 319 |
+
# check convergence, l1 norm
|
| 320 |
+
err = np.absolute(x - xlast).sum()
|
| 321 |
+
if err < tol:
|
| 322 |
+
break
|
| 323 |
+
if i > max_iter:
|
| 324 |
+
raise nx.PowerIterationFailedConvergence(max_iter)
|
| 325 |
+
i += 1
|
| 326 |
+
|
| 327 |
+
a = x.flatten()
|
| 328 |
+
h = A @ a
|
| 329 |
+
if normalized:
|
| 330 |
+
h /= h.sum()
|
| 331 |
+
a /= a.sum()
|
| 332 |
+
hubs = dict(zip(G, map(float, h)))
|
| 333 |
+
authorities = dict(zip(G, map(float, a)))
|
| 334 |
+
return hubs, authorities
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/link_analysis/pagerank_alg.py
ADDED
|
@@ -0,0 +1,499 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""PageRank analysis of graph structure. """
|
| 2 |
+
from warnings import warn
|
| 3 |
+
|
| 4 |
+
import networkx as nx
|
| 5 |
+
|
| 6 |
+
__all__ = ["pagerank", "google_matrix"]
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
@nx._dispatch(edge_attrs="weight")
|
| 10 |
+
def pagerank(
|
| 11 |
+
G,
|
| 12 |
+
alpha=0.85,
|
| 13 |
+
personalization=None,
|
| 14 |
+
max_iter=100,
|
| 15 |
+
tol=1.0e-6,
|
| 16 |
+
nstart=None,
|
| 17 |
+
weight="weight",
|
| 18 |
+
dangling=None,
|
| 19 |
+
):
|
| 20 |
+
"""Returns the PageRank of the nodes in the graph.
|
| 21 |
+
|
| 22 |
+
PageRank computes a ranking of the nodes in the graph G based on
|
| 23 |
+
the structure of the incoming links. It was originally designed as
|
| 24 |
+
an algorithm to rank web pages.
|
| 25 |
+
|
| 26 |
+
Parameters
|
| 27 |
+
----------
|
| 28 |
+
G : graph
|
| 29 |
+
A NetworkX graph. Undirected graphs will be converted to a directed
|
| 30 |
+
graph with two directed edges for each undirected edge.
|
| 31 |
+
|
| 32 |
+
alpha : float, optional
|
| 33 |
+
Damping parameter for PageRank, default=0.85.
|
| 34 |
+
|
| 35 |
+
personalization: dict, optional
|
| 36 |
+
The "personalization vector" consisting of a dictionary with a
|
| 37 |
+
key some subset of graph nodes and personalization value each of those.
|
| 38 |
+
At least one personalization value must be non-zero.
|
| 39 |
+
If not specified, a nodes personalization value will be zero.
|
| 40 |
+
By default, a uniform distribution is used.
|
| 41 |
+
|
| 42 |
+
max_iter : integer, optional
|
| 43 |
+
Maximum number of iterations in power method eigenvalue solver.
|
| 44 |
+
|
| 45 |
+
tol : float, optional
|
| 46 |
+
Error tolerance used to check convergence in power method solver.
|
| 47 |
+
The iteration will stop after a tolerance of ``len(G) * tol`` is reached.
|
| 48 |
+
|
| 49 |
+
nstart : dictionary, optional
|
| 50 |
+
Starting value of PageRank iteration for each node.
|
| 51 |
+
|
| 52 |
+
weight : key, optional
|
| 53 |
+
Edge data key to use as weight. If None weights are set to 1.
|
| 54 |
+
|
| 55 |
+
dangling: dict, optional
|
| 56 |
+
The outedges to be assigned to any "dangling" nodes, i.e., nodes without
|
| 57 |
+
any outedges. The dict key is the node the outedge points to and the dict
|
| 58 |
+
value is the weight of that outedge. By default, dangling nodes are given
|
| 59 |
+
outedges according to the personalization vector (uniform if not
|
| 60 |
+
specified). This must be selected to result in an irreducible transition
|
| 61 |
+
matrix (see notes under google_matrix). It may be common to have the
|
| 62 |
+
dangling dict to be the same as the personalization dict.
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
Returns
|
| 66 |
+
-------
|
| 67 |
+
pagerank : dictionary
|
| 68 |
+
Dictionary of nodes with PageRank as value
|
| 69 |
+
|
| 70 |
+
Examples
|
| 71 |
+
--------
|
| 72 |
+
>>> G = nx.DiGraph(nx.path_graph(4))
|
| 73 |
+
>>> pr = nx.pagerank(G, alpha=0.9)
|
| 74 |
+
|
| 75 |
+
Notes
|
| 76 |
+
-----
|
| 77 |
+
The eigenvector calculation is done by the power iteration method
|
| 78 |
+
and has no guarantee of convergence. The iteration will stop after
|
| 79 |
+
an error tolerance of ``len(G) * tol`` has been reached. If the
|
| 80 |
+
number of iterations exceed `max_iter`, a
|
| 81 |
+
:exc:`networkx.exception.PowerIterationFailedConvergence` exception
|
| 82 |
+
is raised.
|
| 83 |
+
|
| 84 |
+
The PageRank algorithm was designed for directed graphs but this
|
| 85 |
+
algorithm does not check if the input graph is directed and will
|
| 86 |
+
execute on undirected graphs by converting each edge in the
|
| 87 |
+
directed graph to two edges.
|
| 88 |
+
|
| 89 |
+
See Also
|
| 90 |
+
--------
|
| 91 |
+
google_matrix
|
| 92 |
+
|
| 93 |
+
Raises
|
| 94 |
+
------
|
| 95 |
+
PowerIterationFailedConvergence
|
| 96 |
+
If the algorithm fails to converge to the specified tolerance
|
| 97 |
+
within the specified number of iterations of the power iteration
|
| 98 |
+
method.
|
| 99 |
+
|
| 100 |
+
References
|
| 101 |
+
----------
|
| 102 |
+
.. [1] A. Langville and C. Meyer,
|
| 103 |
+
"A survey of eigenvector methods of web information retrieval."
|
| 104 |
+
http://citeseer.ist.psu.edu/713792.html
|
| 105 |
+
.. [2] Page, Lawrence; Brin, Sergey; Motwani, Rajeev and Winograd, Terry,
|
| 106 |
+
The PageRank citation ranking: Bringing order to the Web. 1999
|
| 107 |
+
http://dbpubs.stanford.edu:8090/pub/showDoc.Fulltext?lang=en&doc=1999-66&format=pdf
|
| 108 |
+
|
| 109 |
+
"""
|
| 110 |
+
return _pagerank_scipy(
|
| 111 |
+
G, alpha, personalization, max_iter, tol, nstart, weight, dangling
|
| 112 |
+
)
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
def _pagerank_python(
|
| 116 |
+
G,
|
| 117 |
+
alpha=0.85,
|
| 118 |
+
personalization=None,
|
| 119 |
+
max_iter=100,
|
| 120 |
+
tol=1.0e-6,
|
| 121 |
+
nstart=None,
|
| 122 |
+
weight="weight",
|
| 123 |
+
dangling=None,
|
| 124 |
+
):
|
| 125 |
+
if len(G) == 0:
|
| 126 |
+
return {}
|
| 127 |
+
|
| 128 |
+
D = G.to_directed()
|
| 129 |
+
|
| 130 |
+
# Create a copy in (right) stochastic form
|
| 131 |
+
W = nx.stochastic_graph(D, weight=weight)
|
| 132 |
+
N = W.number_of_nodes()
|
| 133 |
+
|
| 134 |
+
# Choose fixed starting vector if not given
|
| 135 |
+
if nstart is None:
|
| 136 |
+
x = dict.fromkeys(W, 1.0 / N)
|
| 137 |
+
else:
|
| 138 |
+
# Normalized nstart vector
|
| 139 |
+
s = sum(nstart.values())
|
| 140 |
+
x = {k: v / s for k, v in nstart.items()}
|
| 141 |
+
|
| 142 |
+
if personalization is None:
|
| 143 |
+
# Assign uniform personalization vector if not given
|
| 144 |
+
p = dict.fromkeys(W, 1.0 / N)
|
| 145 |
+
else:
|
| 146 |
+
s = sum(personalization.values())
|
| 147 |
+
p = {k: v / s for k, v in personalization.items()}
|
| 148 |
+
|
| 149 |
+
if dangling is None:
|
| 150 |
+
# Use personalization vector if dangling vector not specified
|
| 151 |
+
dangling_weights = p
|
| 152 |
+
else:
|
| 153 |
+
s = sum(dangling.values())
|
| 154 |
+
dangling_weights = {k: v / s for k, v in dangling.items()}
|
| 155 |
+
dangling_nodes = [n for n in W if W.out_degree(n, weight=weight) == 0.0]
|
| 156 |
+
|
| 157 |
+
# power iteration: make up to max_iter iterations
|
| 158 |
+
for _ in range(max_iter):
|
| 159 |
+
xlast = x
|
| 160 |
+
x = dict.fromkeys(xlast.keys(), 0)
|
| 161 |
+
danglesum = alpha * sum(xlast[n] for n in dangling_nodes)
|
| 162 |
+
for n in x:
|
| 163 |
+
# this matrix multiply looks odd because it is
|
| 164 |
+
# doing a left multiply x^T=xlast^T*W
|
| 165 |
+
for _, nbr, wt in W.edges(n, data=weight):
|
| 166 |
+
x[nbr] += alpha * xlast[n] * wt
|
| 167 |
+
x[n] += danglesum * dangling_weights.get(n, 0) + (1.0 - alpha) * p.get(n, 0)
|
| 168 |
+
# check convergence, l1 norm
|
| 169 |
+
err = sum(abs(x[n] - xlast[n]) for n in x)
|
| 170 |
+
if err < N * tol:
|
| 171 |
+
return x
|
| 172 |
+
raise nx.PowerIterationFailedConvergence(max_iter)
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
@nx._dispatch(edge_attrs="weight")
|
| 176 |
+
def google_matrix(
|
| 177 |
+
G, alpha=0.85, personalization=None, nodelist=None, weight="weight", dangling=None
|
| 178 |
+
):
|
| 179 |
+
"""Returns the Google matrix of the graph.
|
| 180 |
+
|
| 181 |
+
Parameters
|
| 182 |
+
----------
|
| 183 |
+
G : graph
|
| 184 |
+
A NetworkX graph. Undirected graphs will be converted to a directed
|
| 185 |
+
graph with two directed edges for each undirected edge.
|
| 186 |
+
|
| 187 |
+
alpha : float
|
| 188 |
+
The damping factor.
|
| 189 |
+
|
| 190 |
+
personalization: dict, optional
|
| 191 |
+
The "personalization vector" consisting of a dictionary with a
|
| 192 |
+
key some subset of graph nodes and personalization value each of those.
|
| 193 |
+
At least one personalization value must be non-zero.
|
| 194 |
+
If not specified, a nodes personalization value will be zero.
|
| 195 |
+
By default, a uniform distribution is used.
|
| 196 |
+
|
| 197 |
+
nodelist : list, optional
|
| 198 |
+
The rows and columns are ordered according to the nodes in nodelist.
|
| 199 |
+
If nodelist is None, then the ordering is produced by G.nodes().
|
| 200 |
+
|
| 201 |
+
weight : key, optional
|
| 202 |
+
Edge data key to use as weight. If None weights are set to 1.
|
| 203 |
+
|
| 204 |
+
dangling: dict, optional
|
| 205 |
+
The outedges to be assigned to any "dangling" nodes, i.e., nodes without
|
| 206 |
+
any outedges. The dict key is the node the outedge points to and the dict
|
| 207 |
+
value is the weight of that outedge. By default, dangling nodes are given
|
| 208 |
+
outedges according to the personalization vector (uniform if not
|
| 209 |
+
specified) This must be selected to result in an irreducible transition
|
| 210 |
+
matrix (see notes below). It may be common to have the dangling dict to
|
| 211 |
+
be the same as the personalization dict.
|
| 212 |
+
|
| 213 |
+
Returns
|
| 214 |
+
-------
|
| 215 |
+
A : 2D NumPy ndarray
|
| 216 |
+
Google matrix of the graph
|
| 217 |
+
|
| 218 |
+
Notes
|
| 219 |
+
-----
|
| 220 |
+
The array returned represents the transition matrix that describes the
|
| 221 |
+
Markov chain used in PageRank. For PageRank to converge to a unique
|
| 222 |
+
solution (i.e., a unique stationary distribution in a Markov chain), the
|
| 223 |
+
transition matrix must be irreducible. In other words, it must be that
|
| 224 |
+
there exists a path between every pair of nodes in the graph, or else there
|
| 225 |
+
is the potential of "rank sinks."
|
| 226 |
+
|
| 227 |
+
This implementation works with Multi(Di)Graphs. For multigraphs the
|
| 228 |
+
weight between two nodes is set to be the sum of all edge weights
|
| 229 |
+
between those nodes.
|
| 230 |
+
|
| 231 |
+
See Also
|
| 232 |
+
--------
|
| 233 |
+
pagerank
|
| 234 |
+
"""
|
| 235 |
+
import numpy as np
|
| 236 |
+
|
| 237 |
+
if nodelist is None:
|
| 238 |
+
nodelist = list(G)
|
| 239 |
+
|
| 240 |
+
A = nx.to_numpy_array(G, nodelist=nodelist, weight=weight)
|
| 241 |
+
N = len(G)
|
| 242 |
+
if N == 0:
|
| 243 |
+
return A
|
| 244 |
+
|
| 245 |
+
# Personalization vector
|
| 246 |
+
if personalization is None:
|
| 247 |
+
p = np.repeat(1.0 / N, N)
|
| 248 |
+
else:
|
| 249 |
+
p = np.array([personalization.get(n, 0) for n in nodelist], dtype=float)
|
| 250 |
+
if p.sum() == 0:
|
| 251 |
+
raise ZeroDivisionError
|
| 252 |
+
p /= p.sum()
|
| 253 |
+
|
| 254 |
+
# Dangling nodes
|
| 255 |
+
if dangling is None:
|
| 256 |
+
dangling_weights = p
|
| 257 |
+
else:
|
| 258 |
+
# Convert the dangling dictionary into an array in nodelist order
|
| 259 |
+
dangling_weights = np.array([dangling.get(n, 0) for n in nodelist], dtype=float)
|
| 260 |
+
dangling_weights /= dangling_weights.sum()
|
| 261 |
+
dangling_nodes = np.where(A.sum(axis=1) == 0)[0]
|
| 262 |
+
|
| 263 |
+
# Assign dangling_weights to any dangling nodes (nodes with no out links)
|
| 264 |
+
A[dangling_nodes] = dangling_weights
|
| 265 |
+
|
| 266 |
+
A /= A.sum(axis=1)[:, np.newaxis] # Normalize rows to sum to 1
|
| 267 |
+
|
| 268 |
+
return alpha * A + (1 - alpha) * p
|
| 269 |
+
|
| 270 |
+
|
| 271 |
+
def _pagerank_numpy(
|
| 272 |
+
G, alpha=0.85, personalization=None, weight="weight", dangling=None
|
| 273 |
+
):
|
| 274 |
+
"""Returns the PageRank of the nodes in the graph.
|
| 275 |
+
|
| 276 |
+
PageRank computes a ranking of the nodes in the graph G based on
|
| 277 |
+
the structure of the incoming links. It was originally designed as
|
| 278 |
+
an algorithm to rank web pages.
|
| 279 |
+
|
| 280 |
+
Parameters
|
| 281 |
+
----------
|
| 282 |
+
G : graph
|
| 283 |
+
A NetworkX graph. Undirected graphs will be converted to a directed
|
| 284 |
+
graph with two directed edges for each undirected edge.
|
| 285 |
+
|
| 286 |
+
alpha : float, optional
|
| 287 |
+
Damping parameter for PageRank, default=0.85.
|
| 288 |
+
|
| 289 |
+
personalization: dict, optional
|
| 290 |
+
The "personalization vector" consisting of a dictionary with a
|
| 291 |
+
key some subset of graph nodes and personalization value each of those.
|
| 292 |
+
At least one personalization value must be non-zero.
|
| 293 |
+
If not specified, a nodes personalization value will be zero.
|
| 294 |
+
By default, a uniform distribution is used.
|
| 295 |
+
|
| 296 |
+
weight : key, optional
|
| 297 |
+
Edge data key to use as weight. If None weights are set to 1.
|
| 298 |
+
|
| 299 |
+
dangling: dict, optional
|
| 300 |
+
The outedges to be assigned to any "dangling" nodes, i.e., nodes without
|
| 301 |
+
any outedges. The dict key is the node the outedge points to and the dict
|
| 302 |
+
value is the weight of that outedge. By default, dangling nodes are given
|
| 303 |
+
outedges according to the personalization vector (uniform if not
|
| 304 |
+
specified) This must be selected to result in an irreducible transition
|
| 305 |
+
matrix (see notes under google_matrix). It may be common to have the
|
| 306 |
+
dangling dict to be the same as the personalization dict.
|
| 307 |
+
|
| 308 |
+
Returns
|
| 309 |
+
-------
|
| 310 |
+
pagerank : dictionary
|
| 311 |
+
Dictionary of nodes with PageRank as value.
|
| 312 |
+
|
| 313 |
+
Examples
|
| 314 |
+
--------
|
| 315 |
+
>>> from networkx.algorithms.link_analysis.pagerank_alg import _pagerank_numpy
|
| 316 |
+
>>> G = nx.DiGraph(nx.path_graph(4))
|
| 317 |
+
>>> pr = _pagerank_numpy(G, alpha=0.9)
|
| 318 |
+
|
| 319 |
+
Notes
|
| 320 |
+
-----
|
| 321 |
+
The eigenvector calculation uses NumPy's interface to the LAPACK
|
| 322 |
+
eigenvalue solvers. This will be the fastest and most accurate
|
| 323 |
+
for small graphs.
|
| 324 |
+
|
| 325 |
+
This implementation works with Multi(Di)Graphs. For multigraphs the
|
| 326 |
+
weight between two nodes is set to be the sum of all edge weights
|
| 327 |
+
between those nodes.
|
| 328 |
+
|
| 329 |
+
See Also
|
| 330 |
+
--------
|
| 331 |
+
pagerank, google_matrix
|
| 332 |
+
|
| 333 |
+
References
|
| 334 |
+
----------
|
| 335 |
+
.. [1] A. Langville and C. Meyer,
|
| 336 |
+
"A survey of eigenvector methods of web information retrieval."
|
| 337 |
+
http://citeseer.ist.psu.edu/713792.html
|
| 338 |
+
.. [2] Page, Lawrence; Brin, Sergey; Motwani, Rajeev and Winograd, Terry,
|
| 339 |
+
The PageRank citation ranking: Bringing order to the Web. 1999
|
| 340 |
+
http://dbpubs.stanford.edu:8090/pub/showDoc.Fulltext?lang=en&doc=1999-66&format=pdf
|
| 341 |
+
"""
|
| 342 |
+
import numpy as np
|
| 343 |
+
|
| 344 |
+
if len(G) == 0:
|
| 345 |
+
return {}
|
| 346 |
+
M = google_matrix(
|
| 347 |
+
G, alpha, personalization=personalization, weight=weight, dangling=dangling
|
| 348 |
+
)
|
| 349 |
+
# use numpy LAPACK solver
|
| 350 |
+
eigenvalues, eigenvectors = np.linalg.eig(M.T)
|
| 351 |
+
ind = np.argmax(eigenvalues)
|
| 352 |
+
# eigenvector of largest eigenvalue is at ind, normalized
|
| 353 |
+
largest = np.array(eigenvectors[:, ind]).flatten().real
|
| 354 |
+
norm = largest.sum()
|
| 355 |
+
return dict(zip(G, map(float, largest / norm)))
|
| 356 |
+
|
| 357 |
+
|
| 358 |
+
def _pagerank_scipy(
|
| 359 |
+
G,
|
| 360 |
+
alpha=0.85,
|
| 361 |
+
personalization=None,
|
| 362 |
+
max_iter=100,
|
| 363 |
+
tol=1.0e-6,
|
| 364 |
+
nstart=None,
|
| 365 |
+
weight="weight",
|
| 366 |
+
dangling=None,
|
| 367 |
+
):
|
| 368 |
+
"""Returns the PageRank of the nodes in the graph.
|
| 369 |
+
|
| 370 |
+
PageRank computes a ranking of the nodes in the graph G based on
|
| 371 |
+
the structure of the incoming links. It was originally designed as
|
| 372 |
+
an algorithm to rank web pages.
|
| 373 |
+
|
| 374 |
+
Parameters
|
| 375 |
+
----------
|
| 376 |
+
G : graph
|
| 377 |
+
A NetworkX graph. Undirected graphs will be converted to a directed
|
| 378 |
+
graph with two directed edges for each undirected edge.
|
| 379 |
+
|
| 380 |
+
alpha : float, optional
|
| 381 |
+
Damping parameter for PageRank, default=0.85.
|
| 382 |
+
|
| 383 |
+
personalization: dict, optional
|
| 384 |
+
The "personalization vector" consisting of a dictionary with a
|
| 385 |
+
key some subset of graph nodes and personalization value each of those.
|
| 386 |
+
At least one personalization value must be non-zero.
|
| 387 |
+
If not specified, a nodes personalization value will be zero.
|
| 388 |
+
By default, a uniform distribution is used.
|
| 389 |
+
|
| 390 |
+
max_iter : integer, optional
|
| 391 |
+
Maximum number of iterations in power method eigenvalue solver.
|
| 392 |
+
|
| 393 |
+
tol : float, optional
|
| 394 |
+
Error tolerance used to check convergence in power method solver.
|
| 395 |
+
The iteration will stop after a tolerance of ``len(G) * tol`` is reached.
|
| 396 |
+
|
| 397 |
+
nstart : dictionary, optional
|
| 398 |
+
Starting value of PageRank iteration for each node.
|
| 399 |
+
|
| 400 |
+
weight : key, optional
|
| 401 |
+
Edge data key to use as weight. If None weights are set to 1.
|
| 402 |
+
|
| 403 |
+
dangling: dict, optional
|
| 404 |
+
The outedges to be assigned to any "dangling" nodes, i.e., nodes without
|
| 405 |
+
any outedges. The dict key is the node the outedge points to and the dict
|
| 406 |
+
value is the weight of that outedge. By default, dangling nodes are given
|
| 407 |
+
outedges according to the personalization vector (uniform if not
|
| 408 |
+
specified) This must be selected to result in an irreducible transition
|
| 409 |
+
matrix (see notes under google_matrix). It may be common to have the
|
| 410 |
+
dangling dict to be the same as the personalization dict.
|
| 411 |
+
|
| 412 |
+
Returns
|
| 413 |
+
-------
|
| 414 |
+
pagerank : dictionary
|
| 415 |
+
Dictionary of nodes with PageRank as value
|
| 416 |
+
|
| 417 |
+
Examples
|
| 418 |
+
--------
|
| 419 |
+
>>> from networkx.algorithms.link_analysis.pagerank_alg import _pagerank_scipy
|
| 420 |
+
>>> G = nx.DiGraph(nx.path_graph(4))
|
| 421 |
+
>>> pr = _pagerank_scipy(G, alpha=0.9)
|
| 422 |
+
|
| 423 |
+
Notes
|
| 424 |
+
-----
|
| 425 |
+
The eigenvector calculation uses power iteration with a SciPy
|
| 426 |
+
sparse matrix representation.
|
| 427 |
+
|
| 428 |
+
This implementation works with Multi(Di)Graphs. For multigraphs the
|
| 429 |
+
weight between two nodes is set to be the sum of all edge weights
|
| 430 |
+
between those nodes.
|
| 431 |
+
|
| 432 |
+
See Also
|
| 433 |
+
--------
|
| 434 |
+
pagerank
|
| 435 |
+
|
| 436 |
+
Raises
|
| 437 |
+
------
|
| 438 |
+
PowerIterationFailedConvergence
|
| 439 |
+
If the algorithm fails to converge to the specified tolerance
|
| 440 |
+
within the specified number of iterations of the power iteration
|
| 441 |
+
method.
|
| 442 |
+
|
| 443 |
+
References
|
| 444 |
+
----------
|
| 445 |
+
.. [1] A. Langville and C. Meyer,
|
| 446 |
+
"A survey of eigenvector methods of web information retrieval."
|
| 447 |
+
http://citeseer.ist.psu.edu/713792.html
|
| 448 |
+
.. [2] Page, Lawrence; Brin, Sergey; Motwani, Rajeev and Winograd, Terry,
|
| 449 |
+
The PageRank citation ranking: Bringing order to the Web. 1999
|
| 450 |
+
http://dbpubs.stanford.edu:8090/pub/showDoc.Fulltext?lang=en&doc=1999-66&format=pdf
|
| 451 |
+
"""
|
| 452 |
+
import numpy as np
|
| 453 |
+
import scipy as sp
|
| 454 |
+
|
| 455 |
+
N = len(G)
|
| 456 |
+
if N == 0:
|
| 457 |
+
return {}
|
| 458 |
+
|
| 459 |
+
nodelist = list(G)
|
| 460 |
+
A = nx.to_scipy_sparse_array(G, nodelist=nodelist, weight=weight, dtype=float)
|
| 461 |
+
S = A.sum(axis=1)
|
| 462 |
+
S[S != 0] = 1.0 / S[S != 0]
|
| 463 |
+
# TODO: csr_array
|
| 464 |
+
Q = sp.sparse.csr_array(sp.sparse.spdiags(S.T, 0, *A.shape))
|
| 465 |
+
A = Q @ A
|
| 466 |
+
|
| 467 |
+
# initial vector
|
| 468 |
+
if nstart is None:
|
| 469 |
+
x = np.repeat(1.0 / N, N)
|
| 470 |
+
else:
|
| 471 |
+
x = np.array([nstart.get(n, 0) for n in nodelist], dtype=float)
|
| 472 |
+
x /= x.sum()
|
| 473 |
+
|
| 474 |
+
# Personalization vector
|
| 475 |
+
if personalization is None:
|
| 476 |
+
p = np.repeat(1.0 / N, N)
|
| 477 |
+
else:
|
| 478 |
+
p = np.array([personalization.get(n, 0) for n in nodelist], dtype=float)
|
| 479 |
+
if p.sum() == 0:
|
| 480 |
+
raise ZeroDivisionError
|
| 481 |
+
p /= p.sum()
|
| 482 |
+
# Dangling nodes
|
| 483 |
+
if dangling is None:
|
| 484 |
+
dangling_weights = p
|
| 485 |
+
else:
|
| 486 |
+
# Convert the dangling dictionary into an array in nodelist order
|
| 487 |
+
dangling_weights = np.array([dangling.get(n, 0) for n in nodelist], dtype=float)
|
| 488 |
+
dangling_weights /= dangling_weights.sum()
|
| 489 |
+
is_dangling = np.where(S == 0)[0]
|
| 490 |
+
|
| 491 |
+
# power iteration: make up to max_iter iterations
|
| 492 |
+
for _ in range(max_iter):
|
| 493 |
+
xlast = x
|
| 494 |
+
x = alpha * (x @ A + sum(x[is_dangling]) * dangling_weights) + (1 - alpha) * p
|
| 495 |
+
# check convergence, l1 norm
|
| 496 |
+
err = np.absolute(x - xlast).sum()
|
| 497 |
+
if err < N * tol:
|
| 498 |
+
return dict(zip(nodelist, map(float, x)))
|
| 499 |
+
raise nx.PowerIterationFailedConvergence(max_iter)
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/link_analysis/tests/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (240 Bytes). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/link_analysis/tests/__pycache__/test_hits.cpython-311.pyc
ADDED
|
Binary file (6.4 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/link_analysis/tests/test_pagerank.py
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
|
| 3 |
+
import pytest
|
| 4 |
+
|
| 5 |
+
import networkx as nx
|
| 6 |
+
from networkx.classes.tests import dispatch_interface
|
| 7 |
+
|
| 8 |
+
np = pytest.importorskip("numpy")
|
| 9 |
+
pytest.importorskip("scipy")
|
| 10 |
+
|
| 11 |
+
from networkx.algorithms.link_analysis.pagerank_alg import (
|
| 12 |
+
_pagerank_numpy,
|
| 13 |
+
_pagerank_python,
|
| 14 |
+
_pagerank_scipy,
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
# Example from
|
| 18 |
+
# A. Langville and C. Meyer, "A survey of eigenvector methods of web
|
| 19 |
+
# information retrieval." http://citeseer.ist.psu.edu/713792.html
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class TestPageRank:
|
| 23 |
+
@classmethod
|
| 24 |
+
def setup_class(cls):
|
| 25 |
+
G = nx.DiGraph()
|
| 26 |
+
edges = [
|
| 27 |
+
(1, 2),
|
| 28 |
+
(1, 3),
|
| 29 |
+
# 2 is a dangling node
|
| 30 |
+
(3, 1),
|
| 31 |
+
(3, 2),
|
| 32 |
+
(3, 5),
|
| 33 |
+
(4, 5),
|
| 34 |
+
(4, 6),
|
| 35 |
+
(5, 4),
|
| 36 |
+
(5, 6),
|
| 37 |
+
(6, 4),
|
| 38 |
+
]
|
| 39 |
+
G.add_edges_from(edges)
|
| 40 |
+
cls.G = G
|
| 41 |
+
cls.G.pagerank = dict(
|
| 42 |
+
zip(
|
| 43 |
+
sorted(G),
|
| 44 |
+
[
|
| 45 |
+
0.03721197,
|
| 46 |
+
0.05395735,
|
| 47 |
+
0.04150565,
|
| 48 |
+
0.37508082,
|
| 49 |
+
0.20599833,
|
| 50 |
+
0.28624589,
|
| 51 |
+
],
|
| 52 |
+
)
|
| 53 |
+
)
|
| 54 |
+
cls.dangling_node_index = 1
|
| 55 |
+
cls.dangling_edges = {1: 2, 2: 3, 3: 0, 4: 0, 5: 0, 6: 0}
|
| 56 |
+
cls.G.dangling_pagerank = dict(
|
| 57 |
+
zip(
|
| 58 |
+
sorted(G),
|
| 59 |
+
[0.10844518, 0.18618601, 0.0710892, 0.2683668, 0.15919783, 0.20671497],
|
| 60 |
+
)
|
| 61 |
+
)
|
| 62 |
+
|
| 63 |
+
@pytest.mark.parametrize("alg", (nx.pagerank, _pagerank_python))
|
| 64 |
+
def test_pagerank(self, alg):
|
| 65 |
+
G = self.G
|
| 66 |
+
p = alg(G, alpha=0.9, tol=1.0e-08)
|
| 67 |
+
for n in G:
|
| 68 |
+
assert p[n] == pytest.approx(G.pagerank[n], abs=1e-4)
|
| 69 |
+
|
| 70 |
+
nstart = {n: random.random() for n in G}
|
| 71 |
+
p = alg(G, alpha=0.9, tol=1.0e-08, nstart=nstart)
|
| 72 |
+
for n in G:
|
| 73 |
+
assert p[n] == pytest.approx(G.pagerank[n], abs=1e-4)
|
| 74 |
+
|
| 75 |
+
@pytest.mark.parametrize("alg", (nx.pagerank, _pagerank_python))
|
| 76 |
+
def test_pagerank_max_iter(self, alg):
|
| 77 |
+
with pytest.raises(nx.PowerIterationFailedConvergence):
|
| 78 |
+
alg(self.G, max_iter=0)
|
| 79 |
+
|
| 80 |
+
def test_numpy_pagerank(self):
|
| 81 |
+
G = self.G
|
| 82 |
+
p = _pagerank_numpy(G, alpha=0.9)
|
| 83 |
+
for n in G:
|
| 84 |
+
assert p[n] == pytest.approx(G.pagerank[n], abs=1e-4)
|
| 85 |
+
|
| 86 |
+
# This additionally tests the @nx._dispatch mechanism, treating
|
| 87 |
+
# nx.google_matrix as if it were a re-implementation from another package
|
| 88 |
+
@pytest.mark.parametrize("wrapper", [lambda x: x, dispatch_interface.convert])
|
| 89 |
+
def test_google_matrix(self, wrapper):
|
| 90 |
+
G = wrapper(self.G)
|
| 91 |
+
M = nx.google_matrix(G, alpha=0.9, nodelist=sorted(G))
|
| 92 |
+
_, ev = np.linalg.eig(M.T)
|
| 93 |
+
p = ev[:, 0] / ev[:, 0].sum()
|
| 94 |
+
for a, b in zip(p, self.G.pagerank.values()):
|
| 95 |
+
assert a == pytest.approx(b, abs=1e-7)
|
| 96 |
+
|
| 97 |
+
@pytest.mark.parametrize("alg", (nx.pagerank, _pagerank_python, _pagerank_numpy))
|
| 98 |
+
def test_personalization(self, alg):
|
| 99 |
+
G = nx.complete_graph(4)
|
| 100 |
+
personalize = {0: 1, 1: 1, 2: 4, 3: 4}
|
| 101 |
+
answer = {
|
| 102 |
+
0: 0.23246732615667579,
|
| 103 |
+
1: 0.23246732615667579,
|
| 104 |
+
2: 0.267532673843324,
|
| 105 |
+
3: 0.2675326738433241,
|
| 106 |
+
}
|
| 107 |
+
p = alg(G, alpha=0.85, personalization=personalize)
|
| 108 |
+
for n in G:
|
| 109 |
+
assert p[n] == pytest.approx(answer[n], abs=1e-4)
|
| 110 |
+
|
| 111 |
+
@pytest.mark.parametrize("alg", (nx.pagerank, _pagerank_python, nx.google_matrix))
|
| 112 |
+
def test_zero_personalization_vector(self, alg):
|
| 113 |
+
G = nx.complete_graph(4)
|
| 114 |
+
personalize = {0: 0, 1: 0, 2: 0, 3: 0}
|
| 115 |
+
pytest.raises(ZeroDivisionError, alg, G, personalization=personalize)
|
| 116 |
+
|
| 117 |
+
@pytest.mark.parametrize("alg", (nx.pagerank, _pagerank_python))
|
| 118 |
+
def test_one_nonzero_personalization_value(self, alg):
|
| 119 |
+
G = nx.complete_graph(4)
|
| 120 |
+
personalize = {0: 0, 1: 0, 2: 0, 3: 1}
|
| 121 |
+
answer = {
|
| 122 |
+
0: 0.22077931820379187,
|
| 123 |
+
1: 0.22077931820379187,
|
| 124 |
+
2: 0.22077931820379187,
|
| 125 |
+
3: 0.3376620453886241,
|
| 126 |
+
}
|
| 127 |
+
p = alg(G, alpha=0.85, personalization=personalize)
|
| 128 |
+
for n in G:
|
| 129 |
+
assert p[n] == pytest.approx(answer[n], abs=1e-4)
|
| 130 |
+
|
| 131 |
+
@pytest.mark.parametrize("alg", (nx.pagerank, _pagerank_python))
|
| 132 |
+
def test_incomplete_personalization(self, alg):
|
| 133 |
+
G = nx.complete_graph(4)
|
| 134 |
+
personalize = {3: 1}
|
| 135 |
+
answer = {
|
| 136 |
+
0: 0.22077931820379187,
|
| 137 |
+
1: 0.22077931820379187,
|
| 138 |
+
2: 0.22077931820379187,
|
| 139 |
+
3: 0.3376620453886241,
|
| 140 |
+
}
|
| 141 |
+
p = alg(G, alpha=0.85, personalization=personalize)
|
| 142 |
+
for n in G:
|
| 143 |
+
assert p[n] == pytest.approx(answer[n], abs=1e-4)
|
| 144 |
+
|
| 145 |
+
def test_dangling_matrix(self):
|
| 146 |
+
"""
|
| 147 |
+
Tests that the google_matrix doesn't change except for the dangling
|
| 148 |
+
nodes.
|
| 149 |
+
"""
|
| 150 |
+
G = self.G
|
| 151 |
+
dangling = self.dangling_edges
|
| 152 |
+
dangling_sum = sum(dangling.values())
|
| 153 |
+
M1 = nx.google_matrix(G, personalization=dangling)
|
| 154 |
+
M2 = nx.google_matrix(G, personalization=dangling, dangling=dangling)
|
| 155 |
+
for i in range(len(G)):
|
| 156 |
+
for j in range(len(G)):
|
| 157 |
+
if i == self.dangling_node_index and (j + 1) in dangling:
|
| 158 |
+
assert M2[i, j] == pytest.approx(
|
| 159 |
+
dangling[j + 1] / dangling_sum, abs=1e-4
|
| 160 |
+
)
|
| 161 |
+
else:
|
| 162 |
+
assert M2[i, j] == pytest.approx(M1[i, j], abs=1e-4)
|
| 163 |
+
|
| 164 |
+
@pytest.mark.parametrize("alg", (nx.pagerank, _pagerank_python, _pagerank_numpy))
|
| 165 |
+
def test_dangling_pagerank(self, alg):
|
| 166 |
+
pr = alg(self.G, dangling=self.dangling_edges)
|
| 167 |
+
for n in self.G:
|
| 168 |
+
assert pr[n] == pytest.approx(self.G.dangling_pagerank[n], abs=1e-4)
|
| 169 |
+
|
| 170 |
+
def test_empty(self):
|
| 171 |
+
G = nx.Graph()
|
| 172 |
+
assert nx.pagerank(G) == {}
|
| 173 |
+
assert _pagerank_python(G) == {}
|
| 174 |
+
assert _pagerank_numpy(G) == {}
|
| 175 |
+
assert nx.google_matrix(G).shape == (0, 0)
|
| 176 |
+
|
| 177 |
+
@pytest.mark.parametrize("alg", (nx.pagerank, _pagerank_python))
|
| 178 |
+
def test_multigraph(self, alg):
|
| 179 |
+
G = nx.MultiGraph()
|
| 180 |
+
G.add_edges_from([(1, 2), (1, 2), (1, 2), (2, 3), (2, 3), ("3", 3), ("3", 3)])
|
| 181 |
+
answer = {
|
| 182 |
+
1: 0.21066048614468322,
|
| 183 |
+
2: 0.3395308825985378,
|
| 184 |
+
3: 0.28933951385531687,
|
| 185 |
+
"3": 0.16046911740146227,
|
| 186 |
+
}
|
| 187 |
+
p = alg(G)
|
| 188 |
+
for n in G:
|
| 189 |
+
assert p[n] == pytest.approx(answer[n], abs=1e-4)
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
class TestPageRankScipy(TestPageRank):
|
| 193 |
+
def test_scipy_pagerank(self):
|
| 194 |
+
G = self.G
|
| 195 |
+
p = _pagerank_scipy(G, alpha=0.9, tol=1.0e-08)
|
| 196 |
+
for n in G:
|
| 197 |
+
assert p[n] == pytest.approx(G.pagerank[n], abs=1e-4)
|
| 198 |
+
personalize = {n: random.random() for n in G}
|
| 199 |
+
p = _pagerank_scipy(G, alpha=0.9, tol=1.0e-08, personalization=personalize)
|
| 200 |
+
|
| 201 |
+
nstart = {n: random.random() for n in G}
|
| 202 |
+
p = _pagerank_scipy(G, alpha=0.9, tol=1.0e-08, nstart=nstart)
|
| 203 |
+
for n in G:
|
| 204 |
+
assert p[n] == pytest.approx(G.pagerank[n], abs=1e-4)
|
| 205 |
+
|
| 206 |
+
def test_scipy_pagerank_max_iter(self):
|
| 207 |
+
with pytest.raises(nx.PowerIterationFailedConvergence):
|
| 208 |
+
_pagerank_scipy(self.G, max_iter=0)
|
| 209 |
+
|
| 210 |
+
def test_dangling_scipy_pagerank(self):
|
| 211 |
+
pr = _pagerank_scipy(self.G, dangling=self.dangling_edges)
|
| 212 |
+
for n in self.G:
|
| 213 |
+
assert pr[n] == pytest.approx(self.G.dangling_pagerank[n], abs=1e-4)
|
| 214 |
+
|
| 215 |
+
def test_empty_scipy(self):
|
| 216 |
+
G = nx.Graph()
|
| 217 |
+
assert _pagerank_scipy(G) == {}
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/minors/__init__.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Subpackages related to graph-minor problems.
|
| 3 |
+
|
| 4 |
+
In graph theory, an undirected graph H is called a minor of the graph G if H
|
| 5 |
+
can be formed from G by deleting edges and vertices and by contracting edges
|
| 6 |
+
[1]_.
|
| 7 |
+
|
| 8 |
+
References
|
| 9 |
+
----------
|
| 10 |
+
.. [1] https://en.wikipedia.org/wiki/Graph_minor
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
from networkx.algorithms.minors.contraction import (
|
| 14 |
+
contracted_edge,
|
| 15 |
+
contracted_nodes,
|
| 16 |
+
equivalence_classes,
|
| 17 |
+
identified_nodes,
|
| 18 |
+
quotient_graph,
|
| 19 |
+
)
|
| 20 |
+
|
| 21 |
+
__all__ = [
|
| 22 |
+
"contracted_edge",
|
| 23 |
+
"contracted_nodes",
|
| 24 |
+
"equivalence_classes",
|
| 25 |
+
"identified_nodes",
|
| 26 |
+
"quotient_graph",
|
| 27 |
+
]
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/non_randomness.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
r""" Computation of graph non-randomness
|
| 2 |
+
"""
|
| 3 |
+
|
| 4 |
+
import math
|
| 5 |
+
|
| 6 |
+
import networkx as nx
|
| 7 |
+
from networkx.utils import not_implemented_for
|
| 8 |
+
|
| 9 |
+
__all__ = ["non_randomness"]
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
@not_implemented_for("directed")
|
| 13 |
+
@not_implemented_for("multigraph")
|
| 14 |
+
@nx._dispatch(edge_attrs="weight")
|
| 15 |
+
def non_randomness(G, k=None, weight="weight"):
|
| 16 |
+
"""Compute the non-randomness of graph G.
|
| 17 |
+
|
| 18 |
+
The first returned value nr is the sum of non-randomness values of all
|
| 19 |
+
edges within the graph (where the non-randomness of an edge tends to be
|
| 20 |
+
small when the two nodes linked by that edge are from two different
|
| 21 |
+
communities).
|
| 22 |
+
|
| 23 |
+
The second computed value nr_rd is a relative measure that indicates
|
| 24 |
+
to what extent graph G is different from random graphs in terms
|
| 25 |
+
of probability. When it is close to 0, the graph tends to be more
|
| 26 |
+
likely generated by an Erdos Renyi model.
|
| 27 |
+
|
| 28 |
+
Parameters
|
| 29 |
+
----------
|
| 30 |
+
G : NetworkX graph
|
| 31 |
+
Graph must be symmetric, connected, and without self-loops.
|
| 32 |
+
|
| 33 |
+
k : int
|
| 34 |
+
The number of communities in G.
|
| 35 |
+
If k is not set, the function will use a default community
|
| 36 |
+
detection algorithm to set it.
|
| 37 |
+
|
| 38 |
+
weight : string or None, optional (default=None)
|
| 39 |
+
The name of an edge attribute that holds the numerical value used
|
| 40 |
+
as a weight. If None, then each edge has weight 1, i.e., the graph is
|
| 41 |
+
binary.
|
| 42 |
+
|
| 43 |
+
Returns
|
| 44 |
+
-------
|
| 45 |
+
non-randomness : (float, float) tuple
|
| 46 |
+
Non-randomness, Relative non-randomness w.r.t.
|
| 47 |
+
Erdos Renyi random graphs.
|
| 48 |
+
|
| 49 |
+
Raises
|
| 50 |
+
------
|
| 51 |
+
NetworkXException
|
| 52 |
+
if the input graph is not connected.
|
| 53 |
+
NetworkXError
|
| 54 |
+
if the input graph contains self-loops.
|
| 55 |
+
|
| 56 |
+
Examples
|
| 57 |
+
--------
|
| 58 |
+
>>> G = nx.karate_club_graph()
|
| 59 |
+
>>> nr, nr_rd = nx.non_randomness(G, 2)
|
| 60 |
+
>>> nr, nr_rd = nx.non_randomness(G, 2, 'weight')
|
| 61 |
+
|
| 62 |
+
Notes
|
| 63 |
+
-----
|
| 64 |
+
This computes Eq. (4.4) and (4.5) in Ref. [1]_.
|
| 65 |
+
|
| 66 |
+
If a weight field is passed, this algorithm will use the eigenvalues
|
| 67 |
+
of the weighted adjacency matrix to compute Eq. (4.4) and (4.5).
|
| 68 |
+
|
| 69 |
+
References
|
| 70 |
+
----------
|
| 71 |
+
.. [1] Xiaowei Ying and Xintao Wu,
|
| 72 |
+
On Randomness Measures for Social Networks,
|
| 73 |
+
SIAM International Conference on Data Mining. 2009
|
| 74 |
+
"""
|
| 75 |
+
import numpy as np
|
| 76 |
+
|
| 77 |
+
if not nx.is_connected(G):
|
| 78 |
+
raise nx.NetworkXException("Non connected graph.")
|
| 79 |
+
if len(list(nx.selfloop_edges(G))) > 0:
|
| 80 |
+
raise nx.NetworkXError("Graph must not contain self-loops")
|
| 81 |
+
|
| 82 |
+
if k is None:
|
| 83 |
+
k = len(tuple(nx.community.label_propagation_communities(G)))
|
| 84 |
+
|
| 85 |
+
# eq. 4.4
|
| 86 |
+
eigenvalues = np.linalg.eigvals(nx.to_numpy_array(G, weight=weight))
|
| 87 |
+
nr = np.real(np.sum(eigenvalues[:k]))
|
| 88 |
+
|
| 89 |
+
n = G.number_of_nodes()
|
| 90 |
+
m = G.number_of_edges()
|
| 91 |
+
p = (2 * k * m) / (n * (n - k))
|
| 92 |
+
|
| 93 |
+
# eq. 4.5
|
| 94 |
+
nr_rd = (nr - ((n - 2 * k) * p + k)) / math.sqrt(2 * k * p * (1 - p))
|
| 95 |
+
|
| 96 |
+
return nr, nr_rd
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/operators/__init__.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from networkx.algorithms.operators.all import *
|
| 2 |
+
from networkx.algorithms.operators.binary import *
|
| 3 |
+
from networkx.algorithms.operators.product import *
|
| 4 |
+
from networkx.algorithms.operators.unary import *
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/operators/all.py
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Operations on many graphs.
|
| 2 |
+
"""
|
| 3 |
+
from itertools import chain, repeat
|
| 4 |
+
|
| 5 |
+
import networkx as nx
|
| 6 |
+
|
| 7 |
+
__all__ = ["union_all", "compose_all", "disjoint_union_all", "intersection_all"]
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
@nx._dispatch(graphs="[graphs]", preserve_all_attrs=True)
|
| 11 |
+
def union_all(graphs, rename=()):
|
| 12 |
+
"""Returns the union of all graphs.
|
| 13 |
+
|
| 14 |
+
The graphs must be disjoint, otherwise an exception is raised.
|
| 15 |
+
|
| 16 |
+
Parameters
|
| 17 |
+
----------
|
| 18 |
+
graphs : iterable
|
| 19 |
+
Iterable of NetworkX graphs
|
| 20 |
+
|
| 21 |
+
rename : iterable , optional
|
| 22 |
+
Node names of graphs can be changed by specifying the tuple
|
| 23 |
+
rename=('G-','H-') (for example). Node "u" in G is then renamed
|
| 24 |
+
"G-u" and "v" in H is renamed "H-v". Infinite generators (like itertools.count)
|
| 25 |
+
are also supported.
|
| 26 |
+
|
| 27 |
+
Returns
|
| 28 |
+
-------
|
| 29 |
+
U : a graph with the same type as the first graph in list
|
| 30 |
+
|
| 31 |
+
Raises
|
| 32 |
+
------
|
| 33 |
+
ValueError
|
| 34 |
+
If `graphs` is an empty list.
|
| 35 |
+
|
| 36 |
+
NetworkXError
|
| 37 |
+
In case of mixed type graphs, like MultiGraph and Graph, or directed and undirected graphs.
|
| 38 |
+
|
| 39 |
+
Notes
|
| 40 |
+
-----
|
| 41 |
+
For operating on mixed type graphs, they should be converted to the same type.
|
| 42 |
+
>>> G = nx.Graph()
|
| 43 |
+
>>> H = nx.DiGraph()
|
| 44 |
+
>>> GH = union_all([nx.DiGraph(G), H])
|
| 45 |
+
|
| 46 |
+
To force a disjoint union with node relabeling, use
|
| 47 |
+
disjoint_union_all(G,H) or convert_node_labels_to integers().
|
| 48 |
+
|
| 49 |
+
Graph, edge, and node attributes are propagated to the union graph.
|
| 50 |
+
If a graph attribute is present in multiple graphs, then the value
|
| 51 |
+
from the last graph in the list with that attribute is used.
|
| 52 |
+
|
| 53 |
+
Examples
|
| 54 |
+
--------
|
| 55 |
+
>>> G1 = nx.Graph([(1, 2), (2, 3)])
|
| 56 |
+
>>> G2 = nx.Graph([(4, 5), (5, 6)])
|
| 57 |
+
>>> result_graph = nx.union_all([G1, G2])
|
| 58 |
+
>>> result_graph.nodes()
|
| 59 |
+
NodeView((1, 2, 3, 4, 5, 6))
|
| 60 |
+
>>> result_graph.edges()
|
| 61 |
+
EdgeView([(1, 2), (2, 3), (4, 5), (5, 6)])
|
| 62 |
+
|
| 63 |
+
See Also
|
| 64 |
+
--------
|
| 65 |
+
union
|
| 66 |
+
disjoint_union_all
|
| 67 |
+
"""
|
| 68 |
+
R = None
|
| 69 |
+
seen_nodes = set()
|
| 70 |
+
|
| 71 |
+
# rename graph to obtain disjoint node labels
|
| 72 |
+
def add_prefix(graph, prefix):
|
| 73 |
+
if prefix is None:
|
| 74 |
+
return graph
|
| 75 |
+
|
| 76 |
+
def label(x):
|
| 77 |
+
return f"{prefix}{x}"
|
| 78 |
+
|
| 79 |
+
return nx.relabel_nodes(graph, label)
|
| 80 |
+
|
| 81 |
+
rename = chain(rename, repeat(None))
|
| 82 |
+
graphs = (add_prefix(G, name) for G, name in zip(graphs, rename))
|
| 83 |
+
|
| 84 |
+
for i, G in enumerate(graphs):
|
| 85 |
+
G_nodes_set = set(G.nodes)
|
| 86 |
+
if i == 0:
|
| 87 |
+
# Union is the same type as first graph
|
| 88 |
+
R = G.__class__()
|
| 89 |
+
elif G.is_directed() != R.is_directed():
|
| 90 |
+
raise nx.NetworkXError("All graphs must be directed or undirected.")
|
| 91 |
+
elif G.is_multigraph() != R.is_multigraph():
|
| 92 |
+
raise nx.NetworkXError("All graphs must be graphs or multigraphs.")
|
| 93 |
+
elif not seen_nodes.isdisjoint(G_nodes_set):
|
| 94 |
+
raise nx.NetworkXError(
|
| 95 |
+
"The node sets of the graphs are not disjoint.",
|
| 96 |
+
"Use appropriate rename"
|
| 97 |
+
"=(G1prefix,G2prefix,...,GNprefix)"
|
| 98 |
+
"or use disjoint_union(G1,G2,...,GN).",
|
| 99 |
+
)
|
| 100 |
+
|
| 101 |
+
seen_nodes |= G_nodes_set
|
| 102 |
+
R.graph.update(G.graph)
|
| 103 |
+
R.add_nodes_from(G.nodes(data=True))
|
| 104 |
+
R.add_edges_from(
|
| 105 |
+
G.edges(keys=True, data=True) if G.is_multigraph() else G.edges(data=True)
|
| 106 |
+
)
|
| 107 |
+
|
| 108 |
+
if R is None:
|
| 109 |
+
raise ValueError("cannot apply union_all to an empty list")
|
| 110 |
+
|
| 111 |
+
return R
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
@nx._dispatch(graphs="[graphs]", preserve_all_attrs=True)
|
| 115 |
+
def disjoint_union_all(graphs):
|
| 116 |
+
"""Returns the disjoint union of all graphs.
|
| 117 |
+
|
| 118 |
+
This operation forces distinct integer node labels starting with 0
|
| 119 |
+
for the first graph in the list and numbering consecutively.
|
| 120 |
+
|
| 121 |
+
Parameters
|
| 122 |
+
----------
|
| 123 |
+
graphs : iterable
|
| 124 |
+
Iterable of NetworkX graphs
|
| 125 |
+
|
| 126 |
+
Returns
|
| 127 |
+
-------
|
| 128 |
+
U : A graph with the same type as the first graph in list
|
| 129 |
+
|
| 130 |
+
Raises
|
| 131 |
+
------
|
| 132 |
+
ValueError
|
| 133 |
+
If `graphs` is an empty list.
|
| 134 |
+
|
| 135 |
+
NetworkXError
|
| 136 |
+
In case of mixed type graphs, like MultiGraph and Graph, or directed and undirected graphs.
|
| 137 |
+
|
| 138 |
+
Examples
|
| 139 |
+
--------
|
| 140 |
+
>>> G1 = nx.Graph([(1, 2), (2, 3)])
|
| 141 |
+
>>> G2 = nx.Graph([(4, 5), (5, 6)])
|
| 142 |
+
>>> U = nx.disjoint_union_all([G1, G2])
|
| 143 |
+
>>> list(U.nodes())
|
| 144 |
+
[0, 1, 2, 3, 4, 5]
|
| 145 |
+
>>> list(U.edges())
|
| 146 |
+
[(0, 1), (1, 2), (3, 4), (4, 5)]
|
| 147 |
+
|
| 148 |
+
Notes
|
| 149 |
+
-----
|
| 150 |
+
For operating on mixed type graphs, they should be converted to the same type.
|
| 151 |
+
|
| 152 |
+
Graph, edge, and node attributes are propagated to the union graph.
|
| 153 |
+
If a graph attribute is present in multiple graphs, then the value
|
| 154 |
+
from the last graph in the list with that attribute is used.
|
| 155 |
+
"""
|
| 156 |
+
|
| 157 |
+
def yield_relabeled(graphs):
|
| 158 |
+
first_label = 0
|
| 159 |
+
for G in graphs:
|
| 160 |
+
yield nx.convert_node_labels_to_integers(G, first_label=first_label)
|
| 161 |
+
first_label += len(G)
|
| 162 |
+
|
| 163 |
+
R = union_all(yield_relabeled(graphs))
|
| 164 |
+
|
| 165 |
+
return R
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
@nx._dispatch(graphs="[graphs]", preserve_all_attrs=True)
|
| 169 |
+
def compose_all(graphs):
|
| 170 |
+
"""Returns the composition of all graphs.
|
| 171 |
+
|
| 172 |
+
Composition is the simple union of the node sets and edge sets.
|
| 173 |
+
The node sets of the supplied graphs need not be disjoint.
|
| 174 |
+
|
| 175 |
+
Parameters
|
| 176 |
+
----------
|
| 177 |
+
graphs : iterable
|
| 178 |
+
Iterable of NetworkX graphs
|
| 179 |
+
|
| 180 |
+
Returns
|
| 181 |
+
-------
|
| 182 |
+
C : A graph with the same type as the first graph in list
|
| 183 |
+
|
| 184 |
+
Raises
|
| 185 |
+
------
|
| 186 |
+
ValueError
|
| 187 |
+
If `graphs` is an empty list.
|
| 188 |
+
|
| 189 |
+
NetworkXError
|
| 190 |
+
In case of mixed type graphs, like MultiGraph and Graph, or directed and undirected graphs.
|
| 191 |
+
|
| 192 |
+
Examples
|
| 193 |
+
--------
|
| 194 |
+
>>> G1 = nx.Graph([(1, 2), (2, 3)])
|
| 195 |
+
>>> G2 = nx.Graph([(3, 4), (5, 6)])
|
| 196 |
+
>>> C = nx.compose_all([G1, G2])
|
| 197 |
+
>>> list(C.nodes())
|
| 198 |
+
[1, 2, 3, 4, 5, 6]
|
| 199 |
+
>>> list(C.edges())
|
| 200 |
+
[(1, 2), (2, 3), (3, 4), (5, 6)]
|
| 201 |
+
|
| 202 |
+
Notes
|
| 203 |
+
-----
|
| 204 |
+
For operating on mixed type graphs, they should be converted to the same type.
|
| 205 |
+
|
| 206 |
+
Graph, edge, and node attributes are propagated to the union graph.
|
| 207 |
+
If a graph attribute is present in multiple graphs, then the value
|
| 208 |
+
from the last graph in the list with that attribute is used.
|
| 209 |
+
"""
|
| 210 |
+
R = None
|
| 211 |
+
|
| 212 |
+
# add graph attributes, H attributes take precedent over G attributes
|
| 213 |
+
for i, G in enumerate(graphs):
|
| 214 |
+
if i == 0:
|
| 215 |
+
# create new graph
|
| 216 |
+
R = G.__class__()
|
| 217 |
+
elif G.is_directed() != R.is_directed():
|
| 218 |
+
raise nx.NetworkXError("All graphs must be directed or undirected.")
|
| 219 |
+
elif G.is_multigraph() != R.is_multigraph():
|
| 220 |
+
raise nx.NetworkXError("All graphs must be graphs or multigraphs.")
|
| 221 |
+
|
| 222 |
+
R.graph.update(G.graph)
|
| 223 |
+
R.add_nodes_from(G.nodes(data=True))
|
| 224 |
+
R.add_edges_from(
|
| 225 |
+
G.edges(keys=True, data=True) if G.is_multigraph() else G.edges(data=True)
|
| 226 |
+
)
|
| 227 |
+
|
| 228 |
+
if R is None:
|
| 229 |
+
raise ValueError("cannot apply compose_all to an empty list")
|
| 230 |
+
|
| 231 |
+
return R
|
| 232 |
+
|
| 233 |
+
|
| 234 |
+
@nx._dispatch(graphs="[graphs]")
|
| 235 |
+
def intersection_all(graphs):
|
| 236 |
+
"""Returns a new graph that contains only the nodes and the edges that exist in
|
| 237 |
+
all graphs.
|
| 238 |
+
|
| 239 |
+
Parameters
|
| 240 |
+
----------
|
| 241 |
+
graphs : iterable
|
| 242 |
+
Iterable of NetworkX graphs
|
| 243 |
+
|
| 244 |
+
Returns
|
| 245 |
+
-------
|
| 246 |
+
R : A new graph with the same type as the first graph in list
|
| 247 |
+
|
| 248 |
+
Raises
|
| 249 |
+
------
|
| 250 |
+
ValueError
|
| 251 |
+
If `graphs` is an empty list.
|
| 252 |
+
|
| 253 |
+
NetworkXError
|
| 254 |
+
In case of mixed type graphs, like MultiGraph and Graph, or directed and undirected graphs.
|
| 255 |
+
|
| 256 |
+
Notes
|
| 257 |
+
-----
|
| 258 |
+
For operating on mixed type graphs, they should be converted to the same type.
|
| 259 |
+
|
| 260 |
+
Attributes from the graph, nodes, and edges are not copied to the new
|
| 261 |
+
graph.
|
| 262 |
+
|
| 263 |
+
The resulting graph can be updated with attributes if desired. For example, code which adds the minimum attribute for each node across all graphs could work.
|
| 264 |
+
>>> g = nx.Graph()
|
| 265 |
+
>>> g.add_node(0, capacity=4)
|
| 266 |
+
>>> g.add_node(1, capacity=3)
|
| 267 |
+
>>> g.add_edge(0, 1)
|
| 268 |
+
|
| 269 |
+
>>> h = g.copy()
|
| 270 |
+
>>> h.nodes[0]["capacity"] = 2
|
| 271 |
+
|
| 272 |
+
>>> gh = nx.intersection_all([g, h])
|
| 273 |
+
|
| 274 |
+
>>> new_node_attr = {n: min(*(anyG.nodes[n].get('capacity', float('inf')) for anyG in [g, h])) for n in gh}
|
| 275 |
+
>>> nx.set_node_attributes(gh, new_node_attr, 'new_capacity')
|
| 276 |
+
>>> gh.nodes(data=True)
|
| 277 |
+
NodeDataView({0: {'new_capacity': 2}, 1: {'new_capacity': 3}})
|
| 278 |
+
|
| 279 |
+
Examples
|
| 280 |
+
--------
|
| 281 |
+
>>> G1 = nx.Graph([(1, 2), (2, 3)])
|
| 282 |
+
>>> G2 = nx.Graph([(2, 3), (3, 4)])
|
| 283 |
+
>>> R = nx.intersection_all([G1, G2])
|
| 284 |
+
>>> list(R.nodes())
|
| 285 |
+
[2, 3]
|
| 286 |
+
>>> list(R.edges())
|
| 287 |
+
[(2, 3)]
|
| 288 |
+
|
| 289 |
+
"""
|
| 290 |
+
R = None
|
| 291 |
+
|
| 292 |
+
for i, G in enumerate(graphs):
|
| 293 |
+
G_nodes_set = set(G.nodes)
|
| 294 |
+
G_edges_set = set(G.edges)
|
| 295 |
+
if not G.is_directed():
|
| 296 |
+
if G.is_multigraph():
|
| 297 |
+
G_edges_set.update((v, u, k) for u, v, k in list(G_edges_set))
|
| 298 |
+
else:
|
| 299 |
+
G_edges_set.update((v, u) for u, v in list(G_edges_set))
|
| 300 |
+
if i == 0:
|
| 301 |
+
# create new graph
|
| 302 |
+
R = G.__class__()
|
| 303 |
+
node_intersection = G_nodes_set
|
| 304 |
+
edge_intersection = G_edges_set
|
| 305 |
+
elif G.is_directed() != R.is_directed():
|
| 306 |
+
raise nx.NetworkXError("All graphs must be directed or undirected.")
|
| 307 |
+
elif G.is_multigraph() != R.is_multigraph():
|
| 308 |
+
raise nx.NetworkXError("All graphs must be graphs or multigraphs.")
|
| 309 |
+
else:
|
| 310 |
+
node_intersection &= G_nodes_set
|
| 311 |
+
edge_intersection &= G_edges_set
|
| 312 |
+
|
| 313 |
+
if R is None:
|
| 314 |
+
raise ValueError("cannot apply intersection_all to an empty list")
|
| 315 |
+
|
| 316 |
+
R.add_nodes_from(node_intersection)
|
| 317 |
+
R.add_edges_from(edge_intersection)
|
| 318 |
+
|
| 319 |
+
return R
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/operators/product.py
ADDED
|
@@ -0,0 +1,534 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Graph products.
|
| 3 |
+
"""
|
| 4 |
+
from itertools import product
|
| 5 |
+
|
| 6 |
+
import networkx as nx
|
| 7 |
+
from networkx.utils import not_implemented_for
|
| 8 |
+
|
| 9 |
+
__all__ = [
|
| 10 |
+
"tensor_product",
|
| 11 |
+
"cartesian_product",
|
| 12 |
+
"lexicographic_product",
|
| 13 |
+
"strong_product",
|
| 14 |
+
"power",
|
| 15 |
+
"rooted_product",
|
| 16 |
+
"corona_product",
|
| 17 |
+
]
|
| 18 |
+
_G_H = {"G": 0, "H": 1}
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def _dict_product(d1, d2):
|
| 22 |
+
return {k: (d1.get(k), d2.get(k)) for k in set(d1) | set(d2)}
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
# Generators for producing graph products
|
| 26 |
+
def _node_product(G, H):
|
| 27 |
+
for u, v in product(G, H):
|
| 28 |
+
yield ((u, v), _dict_product(G.nodes[u], H.nodes[v]))
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def _directed_edges_cross_edges(G, H):
|
| 32 |
+
if not G.is_multigraph() and not H.is_multigraph():
|
| 33 |
+
for u, v, c in G.edges(data=True):
|
| 34 |
+
for x, y, d in H.edges(data=True):
|
| 35 |
+
yield (u, x), (v, y), _dict_product(c, d)
|
| 36 |
+
if not G.is_multigraph() and H.is_multigraph():
|
| 37 |
+
for u, v, c in G.edges(data=True):
|
| 38 |
+
for x, y, k, d in H.edges(data=True, keys=True):
|
| 39 |
+
yield (u, x), (v, y), k, _dict_product(c, d)
|
| 40 |
+
if G.is_multigraph() and not H.is_multigraph():
|
| 41 |
+
for u, v, k, c in G.edges(data=True, keys=True):
|
| 42 |
+
for x, y, d in H.edges(data=True):
|
| 43 |
+
yield (u, x), (v, y), k, _dict_product(c, d)
|
| 44 |
+
if G.is_multigraph() and H.is_multigraph():
|
| 45 |
+
for u, v, j, c in G.edges(data=True, keys=True):
|
| 46 |
+
for x, y, k, d in H.edges(data=True, keys=True):
|
| 47 |
+
yield (u, x), (v, y), (j, k), _dict_product(c, d)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def _undirected_edges_cross_edges(G, H):
|
| 51 |
+
if not G.is_multigraph() and not H.is_multigraph():
|
| 52 |
+
for u, v, c in G.edges(data=True):
|
| 53 |
+
for x, y, d in H.edges(data=True):
|
| 54 |
+
yield (v, x), (u, y), _dict_product(c, d)
|
| 55 |
+
if not G.is_multigraph() and H.is_multigraph():
|
| 56 |
+
for u, v, c in G.edges(data=True):
|
| 57 |
+
for x, y, k, d in H.edges(data=True, keys=True):
|
| 58 |
+
yield (v, x), (u, y), k, _dict_product(c, d)
|
| 59 |
+
if G.is_multigraph() and not H.is_multigraph():
|
| 60 |
+
for u, v, k, c in G.edges(data=True, keys=True):
|
| 61 |
+
for x, y, d in H.edges(data=True):
|
| 62 |
+
yield (v, x), (u, y), k, _dict_product(c, d)
|
| 63 |
+
if G.is_multigraph() and H.is_multigraph():
|
| 64 |
+
for u, v, j, c in G.edges(data=True, keys=True):
|
| 65 |
+
for x, y, k, d in H.edges(data=True, keys=True):
|
| 66 |
+
yield (v, x), (u, y), (j, k), _dict_product(c, d)
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def _edges_cross_nodes(G, H):
|
| 70 |
+
if G.is_multigraph():
|
| 71 |
+
for u, v, k, d in G.edges(data=True, keys=True):
|
| 72 |
+
for x in H:
|
| 73 |
+
yield (u, x), (v, x), k, d
|
| 74 |
+
else:
|
| 75 |
+
for u, v, d in G.edges(data=True):
|
| 76 |
+
for x in H:
|
| 77 |
+
if H.is_multigraph():
|
| 78 |
+
yield (u, x), (v, x), None, d
|
| 79 |
+
else:
|
| 80 |
+
yield (u, x), (v, x), d
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def _nodes_cross_edges(G, H):
|
| 84 |
+
if H.is_multigraph():
|
| 85 |
+
for x in G:
|
| 86 |
+
for u, v, k, d in H.edges(data=True, keys=True):
|
| 87 |
+
yield (x, u), (x, v), k, d
|
| 88 |
+
else:
|
| 89 |
+
for x in G:
|
| 90 |
+
for u, v, d in H.edges(data=True):
|
| 91 |
+
if G.is_multigraph():
|
| 92 |
+
yield (x, u), (x, v), None, d
|
| 93 |
+
else:
|
| 94 |
+
yield (x, u), (x, v), d
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
def _edges_cross_nodes_and_nodes(G, H):
|
| 98 |
+
if G.is_multigraph():
|
| 99 |
+
for u, v, k, d in G.edges(data=True, keys=True):
|
| 100 |
+
for x in H:
|
| 101 |
+
for y in H:
|
| 102 |
+
yield (u, x), (v, y), k, d
|
| 103 |
+
else:
|
| 104 |
+
for u, v, d in G.edges(data=True):
|
| 105 |
+
for x in H:
|
| 106 |
+
for y in H:
|
| 107 |
+
if H.is_multigraph():
|
| 108 |
+
yield (u, x), (v, y), None, d
|
| 109 |
+
else:
|
| 110 |
+
yield (u, x), (v, y), d
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def _init_product_graph(G, H):
|
| 114 |
+
if G.is_directed() != H.is_directed():
|
| 115 |
+
msg = "G and H must be both directed or both undirected"
|
| 116 |
+
raise nx.NetworkXError(msg)
|
| 117 |
+
if G.is_multigraph() or H.is_multigraph():
|
| 118 |
+
GH = nx.MultiGraph()
|
| 119 |
+
else:
|
| 120 |
+
GH = nx.Graph()
|
| 121 |
+
if G.is_directed():
|
| 122 |
+
GH = GH.to_directed()
|
| 123 |
+
return GH
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
@nx._dispatch(graphs=_G_H)
|
| 127 |
+
def tensor_product(G, H):
|
| 128 |
+
r"""Returns the tensor product of G and H.
|
| 129 |
+
|
| 130 |
+
The tensor product $P$ of the graphs $G$ and $H$ has a node set that
|
| 131 |
+
is the tensor product of the node sets, $V(P)=V(G) \times V(H)$.
|
| 132 |
+
$P$ has an edge $((u,v), (x,y))$ if and only if $(u,x)$ is an edge in $G$
|
| 133 |
+
and $(v,y)$ is an edge in $H$.
|
| 134 |
+
|
| 135 |
+
Tensor product is sometimes also referred to as the categorical product,
|
| 136 |
+
direct product, cardinal product or conjunction.
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
Parameters
|
| 140 |
+
----------
|
| 141 |
+
G, H: graphs
|
| 142 |
+
Networkx graphs.
|
| 143 |
+
|
| 144 |
+
Returns
|
| 145 |
+
-------
|
| 146 |
+
P: NetworkX graph
|
| 147 |
+
The tensor product of G and H. P will be a multi-graph if either G
|
| 148 |
+
or H is a multi-graph, will be a directed if G and H are directed,
|
| 149 |
+
and undirected if G and H are undirected.
|
| 150 |
+
|
| 151 |
+
Raises
|
| 152 |
+
------
|
| 153 |
+
NetworkXError
|
| 154 |
+
If G and H are not both directed or both undirected.
|
| 155 |
+
|
| 156 |
+
Notes
|
| 157 |
+
-----
|
| 158 |
+
Node attributes in P are two-tuple of the G and H node attributes.
|
| 159 |
+
Missing attributes are assigned None.
|
| 160 |
+
|
| 161 |
+
Examples
|
| 162 |
+
--------
|
| 163 |
+
>>> G = nx.Graph()
|
| 164 |
+
>>> H = nx.Graph()
|
| 165 |
+
>>> G.add_node(0, a1=True)
|
| 166 |
+
>>> H.add_node("a", a2="Spam")
|
| 167 |
+
>>> P = nx.tensor_product(G, H)
|
| 168 |
+
>>> list(P)
|
| 169 |
+
[(0, 'a')]
|
| 170 |
+
|
| 171 |
+
Edge attributes and edge keys (for multigraphs) are also copied to the
|
| 172 |
+
new product graph
|
| 173 |
+
"""
|
| 174 |
+
GH = _init_product_graph(G, H)
|
| 175 |
+
GH.add_nodes_from(_node_product(G, H))
|
| 176 |
+
GH.add_edges_from(_directed_edges_cross_edges(G, H))
|
| 177 |
+
if not GH.is_directed():
|
| 178 |
+
GH.add_edges_from(_undirected_edges_cross_edges(G, H))
|
| 179 |
+
return GH
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
@nx._dispatch(graphs=_G_H)
|
| 183 |
+
def cartesian_product(G, H):
|
| 184 |
+
r"""Returns the Cartesian product of G and H.
|
| 185 |
+
|
| 186 |
+
The Cartesian product $P$ of the graphs $G$ and $H$ has a node set that
|
| 187 |
+
is the Cartesian product of the node sets, $V(P)=V(G) \times V(H)$.
|
| 188 |
+
$P$ has an edge $((u,v),(x,y))$ if and only if either $u$ is equal to $x$
|
| 189 |
+
and both $v$ and $y$ are adjacent in $H$ or if $v$ is equal to $y$ and
|
| 190 |
+
both $u$ and $x$ are adjacent in $G$.
|
| 191 |
+
|
| 192 |
+
Parameters
|
| 193 |
+
----------
|
| 194 |
+
G, H: graphs
|
| 195 |
+
Networkx graphs.
|
| 196 |
+
|
| 197 |
+
Returns
|
| 198 |
+
-------
|
| 199 |
+
P: NetworkX graph
|
| 200 |
+
The Cartesian product of G and H. P will be a multi-graph if either G
|
| 201 |
+
or H is a multi-graph. Will be a directed if G and H are directed,
|
| 202 |
+
and undirected if G and H are undirected.
|
| 203 |
+
|
| 204 |
+
Raises
|
| 205 |
+
------
|
| 206 |
+
NetworkXError
|
| 207 |
+
If G and H are not both directed or both undirected.
|
| 208 |
+
|
| 209 |
+
Notes
|
| 210 |
+
-----
|
| 211 |
+
Node attributes in P are two-tuple of the G and H node attributes.
|
| 212 |
+
Missing attributes are assigned None.
|
| 213 |
+
|
| 214 |
+
Examples
|
| 215 |
+
--------
|
| 216 |
+
>>> G = nx.Graph()
|
| 217 |
+
>>> H = nx.Graph()
|
| 218 |
+
>>> G.add_node(0, a1=True)
|
| 219 |
+
>>> H.add_node("a", a2="Spam")
|
| 220 |
+
>>> P = nx.cartesian_product(G, H)
|
| 221 |
+
>>> list(P)
|
| 222 |
+
[(0, 'a')]
|
| 223 |
+
|
| 224 |
+
Edge attributes and edge keys (for multigraphs) are also copied to the
|
| 225 |
+
new product graph
|
| 226 |
+
"""
|
| 227 |
+
GH = _init_product_graph(G, H)
|
| 228 |
+
GH.add_nodes_from(_node_product(G, H))
|
| 229 |
+
GH.add_edges_from(_edges_cross_nodes(G, H))
|
| 230 |
+
GH.add_edges_from(_nodes_cross_edges(G, H))
|
| 231 |
+
return GH
|
| 232 |
+
|
| 233 |
+
|
| 234 |
+
@nx._dispatch(graphs=_G_H)
|
| 235 |
+
def lexicographic_product(G, H):
|
| 236 |
+
r"""Returns the lexicographic product of G and H.
|
| 237 |
+
|
| 238 |
+
The lexicographical product $P$ of the graphs $G$ and $H$ has a node set
|
| 239 |
+
that is the Cartesian product of the node sets, $V(P)=V(G) \times V(H)$.
|
| 240 |
+
$P$ has an edge $((u,v), (x,y))$ if and only if $(u,v)$ is an edge in $G$
|
| 241 |
+
or $u==v$ and $(x,y)$ is an edge in $H$.
|
| 242 |
+
|
| 243 |
+
Parameters
|
| 244 |
+
----------
|
| 245 |
+
G, H: graphs
|
| 246 |
+
Networkx graphs.
|
| 247 |
+
|
| 248 |
+
Returns
|
| 249 |
+
-------
|
| 250 |
+
P: NetworkX graph
|
| 251 |
+
The Cartesian product of G and H. P will be a multi-graph if either G
|
| 252 |
+
or H is a multi-graph. Will be a directed if G and H are directed,
|
| 253 |
+
and undirected if G and H are undirected.
|
| 254 |
+
|
| 255 |
+
Raises
|
| 256 |
+
------
|
| 257 |
+
NetworkXError
|
| 258 |
+
If G and H are not both directed or both undirected.
|
| 259 |
+
|
| 260 |
+
Notes
|
| 261 |
+
-----
|
| 262 |
+
Node attributes in P are two-tuple of the G and H node attributes.
|
| 263 |
+
Missing attributes are assigned None.
|
| 264 |
+
|
| 265 |
+
Examples
|
| 266 |
+
--------
|
| 267 |
+
>>> G = nx.Graph()
|
| 268 |
+
>>> H = nx.Graph()
|
| 269 |
+
>>> G.add_node(0, a1=True)
|
| 270 |
+
>>> H.add_node("a", a2="Spam")
|
| 271 |
+
>>> P = nx.lexicographic_product(G, H)
|
| 272 |
+
>>> list(P)
|
| 273 |
+
[(0, 'a')]
|
| 274 |
+
|
| 275 |
+
Edge attributes and edge keys (for multigraphs) are also copied to the
|
| 276 |
+
new product graph
|
| 277 |
+
"""
|
| 278 |
+
GH = _init_product_graph(G, H)
|
| 279 |
+
GH.add_nodes_from(_node_product(G, H))
|
| 280 |
+
# Edges in G regardless of H designation
|
| 281 |
+
GH.add_edges_from(_edges_cross_nodes_and_nodes(G, H))
|
| 282 |
+
# For each x in G, only if there is an edge in H
|
| 283 |
+
GH.add_edges_from(_nodes_cross_edges(G, H))
|
| 284 |
+
return GH
|
| 285 |
+
|
| 286 |
+
|
| 287 |
+
@nx._dispatch(graphs=_G_H)
|
| 288 |
+
def strong_product(G, H):
|
| 289 |
+
r"""Returns the strong product of G and H.
|
| 290 |
+
|
| 291 |
+
The strong product $P$ of the graphs $G$ and $H$ has a node set that
|
| 292 |
+
is the Cartesian product of the node sets, $V(P)=V(G) \times V(H)$.
|
| 293 |
+
$P$ has an edge $((u,v), (x,y))$ if and only if
|
| 294 |
+
$u==v$ and $(x,y)$ is an edge in $H$, or
|
| 295 |
+
$x==y$ and $(u,v)$ is an edge in $G$, or
|
| 296 |
+
$(u,v)$ is an edge in $G$ and $(x,y)$ is an edge in $H$.
|
| 297 |
+
|
| 298 |
+
Parameters
|
| 299 |
+
----------
|
| 300 |
+
G, H: graphs
|
| 301 |
+
Networkx graphs.
|
| 302 |
+
|
| 303 |
+
Returns
|
| 304 |
+
-------
|
| 305 |
+
P: NetworkX graph
|
| 306 |
+
The Cartesian product of G and H. P will be a multi-graph if either G
|
| 307 |
+
or H is a multi-graph. Will be a directed if G and H are directed,
|
| 308 |
+
and undirected if G and H are undirected.
|
| 309 |
+
|
| 310 |
+
Raises
|
| 311 |
+
------
|
| 312 |
+
NetworkXError
|
| 313 |
+
If G and H are not both directed or both undirected.
|
| 314 |
+
|
| 315 |
+
Notes
|
| 316 |
+
-----
|
| 317 |
+
Node attributes in P are two-tuple of the G and H node attributes.
|
| 318 |
+
Missing attributes are assigned None.
|
| 319 |
+
|
| 320 |
+
Examples
|
| 321 |
+
--------
|
| 322 |
+
>>> G = nx.Graph()
|
| 323 |
+
>>> H = nx.Graph()
|
| 324 |
+
>>> G.add_node(0, a1=True)
|
| 325 |
+
>>> H.add_node("a", a2="Spam")
|
| 326 |
+
>>> P = nx.strong_product(G, H)
|
| 327 |
+
>>> list(P)
|
| 328 |
+
[(0, 'a')]
|
| 329 |
+
|
| 330 |
+
Edge attributes and edge keys (for multigraphs) are also copied to the
|
| 331 |
+
new product graph
|
| 332 |
+
"""
|
| 333 |
+
GH = _init_product_graph(G, H)
|
| 334 |
+
GH.add_nodes_from(_node_product(G, H))
|
| 335 |
+
GH.add_edges_from(_nodes_cross_edges(G, H))
|
| 336 |
+
GH.add_edges_from(_edges_cross_nodes(G, H))
|
| 337 |
+
GH.add_edges_from(_directed_edges_cross_edges(G, H))
|
| 338 |
+
if not GH.is_directed():
|
| 339 |
+
GH.add_edges_from(_undirected_edges_cross_edges(G, H))
|
| 340 |
+
return GH
|
| 341 |
+
|
| 342 |
+
|
| 343 |
+
@not_implemented_for("directed")
|
| 344 |
+
@not_implemented_for("multigraph")
|
| 345 |
+
@nx._dispatch
|
| 346 |
+
def power(G, k):
|
| 347 |
+
"""Returns the specified power of a graph.
|
| 348 |
+
|
| 349 |
+
The $k$th power of a simple graph $G$, denoted $G^k$, is a
|
| 350 |
+
graph on the same set of nodes in which two distinct nodes $u$ and
|
| 351 |
+
$v$ are adjacent in $G^k$ if and only if the shortest path
|
| 352 |
+
distance between $u$ and $v$ in $G$ is at most $k$.
|
| 353 |
+
|
| 354 |
+
Parameters
|
| 355 |
+
----------
|
| 356 |
+
G : graph
|
| 357 |
+
A NetworkX simple graph object.
|
| 358 |
+
|
| 359 |
+
k : positive integer
|
| 360 |
+
The power to which to raise the graph `G`.
|
| 361 |
+
|
| 362 |
+
Returns
|
| 363 |
+
-------
|
| 364 |
+
NetworkX simple graph
|
| 365 |
+
`G` to the power `k`.
|
| 366 |
+
|
| 367 |
+
Raises
|
| 368 |
+
------
|
| 369 |
+
ValueError
|
| 370 |
+
If the exponent `k` is not positive.
|
| 371 |
+
|
| 372 |
+
NetworkXNotImplemented
|
| 373 |
+
If `G` is not a simple graph.
|
| 374 |
+
|
| 375 |
+
Examples
|
| 376 |
+
--------
|
| 377 |
+
The number of edges will never decrease when taking successive
|
| 378 |
+
powers:
|
| 379 |
+
|
| 380 |
+
>>> G = nx.path_graph(4)
|
| 381 |
+
>>> list(nx.power(G, 2).edges)
|
| 382 |
+
[(0, 1), (0, 2), (1, 2), (1, 3), (2, 3)]
|
| 383 |
+
>>> list(nx.power(G, 3).edges)
|
| 384 |
+
[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]
|
| 385 |
+
|
| 386 |
+
The `k` th power of a cycle graph on *n* nodes is the complete graph
|
| 387 |
+
on *n* nodes, if `k` is at least ``n // 2``:
|
| 388 |
+
|
| 389 |
+
>>> G = nx.cycle_graph(5)
|
| 390 |
+
>>> H = nx.complete_graph(5)
|
| 391 |
+
>>> nx.is_isomorphic(nx.power(G, 2), H)
|
| 392 |
+
True
|
| 393 |
+
>>> G = nx.cycle_graph(8)
|
| 394 |
+
>>> H = nx.complete_graph(8)
|
| 395 |
+
>>> nx.is_isomorphic(nx.power(G, 4), H)
|
| 396 |
+
True
|
| 397 |
+
|
| 398 |
+
References
|
| 399 |
+
----------
|
| 400 |
+
.. [1] J. A. Bondy, U. S. R. Murty, *Graph Theory*. Springer, 2008.
|
| 401 |
+
|
| 402 |
+
Notes
|
| 403 |
+
-----
|
| 404 |
+
This definition of "power graph" comes from Exercise 3.1.6 of
|
| 405 |
+
*Graph Theory* by Bondy and Murty [1]_.
|
| 406 |
+
|
| 407 |
+
"""
|
| 408 |
+
if k <= 0:
|
| 409 |
+
raise ValueError("k must be a positive integer")
|
| 410 |
+
H = nx.Graph()
|
| 411 |
+
H.add_nodes_from(G)
|
| 412 |
+
# update BFS code to ignore self loops.
|
| 413 |
+
for n in G:
|
| 414 |
+
seen = {} # level (number of hops) when seen in BFS
|
| 415 |
+
level = 1 # the current level
|
| 416 |
+
nextlevel = G[n]
|
| 417 |
+
while nextlevel:
|
| 418 |
+
thislevel = nextlevel # advance to next level
|
| 419 |
+
nextlevel = {} # and start a new list (fringe)
|
| 420 |
+
for v in thislevel:
|
| 421 |
+
if v == n: # avoid self loop
|
| 422 |
+
continue
|
| 423 |
+
if v not in seen:
|
| 424 |
+
seen[v] = level # set the level of vertex v
|
| 425 |
+
nextlevel.update(G[v]) # add neighbors of v
|
| 426 |
+
if k <= level:
|
| 427 |
+
break
|
| 428 |
+
level += 1
|
| 429 |
+
H.add_edges_from((n, nbr) for nbr in seen)
|
| 430 |
+
return H
|
| 431 |
+
|
| 432 |
+
|
| 433 |
+
@not_implemented_for("multigraph")
|
| 434 |
+
@nx._dispatch(graphs=_G_H)
|
| 435 |
+
def rooted_product(G, H, root):
|
| 436 |
+
"""Return the rooted product of graphs G and H rooted at root in H.
|
| 437 |
+
|
| 438 |
+
A new graph is constructed representing the rooted product of
|
| 439 |
+
the inputted graphs, G and H, with a root in H.
|
| 440 |
+
A rooted product duplicates H for each nodes in G with the root
|
| 441 |
+
of H corresponding to the node in G. Nodes are renamed as the direct
|
| 442 |
+
product of G and H. The result is a subgraph of the cartesian product.
|
| 443 |
+
|
| 444 |
+
Parameters
|
| 445 |
+
----------
|
| 446 |
+
G,H : graph
|
| 447 |
+
A NetworkX graph
|
| 448 |
+
root : node
|
| 449 |
+
A node in H
|
| 450 |
+
|
| 451 |
+
Returns
|
| 452 |
+
-------
|
| 453 |
+
R : The rooted product of G and H with a specified root in H
|
| 454 |
+
|
| 455 |
+
Notes
|
| 456 |
+
-----
|
| 457 |
+
The nodes of R are the Cartesian Product of the nodes of G and H.
|
| 458 |
+
The nodes of G and H are not relabeled.
|
| 459 |
+
"""
|
| 460 |
+
if root not in H:
|
| 461 |
+
raise nx.NetworkXError("root must be a vertex in H")
|
| 462 |
+
|
| 463 |
+
R = nx.Graph()
|
| 464 |
+
R.add_nodes_from(product(G, H))
|
| 465 |
+
|
| 466 |
+
R.add_edges_from(((e[0], root), (e[1], root)) for e in G.edges())
|
| 467 |
+
R.add_edges_from(((g, e[0]), (g, e[1])) for g in G for e in H.edges())
|
| 468 |
+
|
| 469 |
+
return R
|
| 470 |
+
|
| 471 |
+
|
| 472 |
+
@not_implemented_for("directed")
|
| 473 |
+
@not_implemented_for("multigraph")
|
| 474 |
+
@nx._dispatch(graphs=_G_H)
|
| 475 |
+
def corona_product(G, H):
|
| 476 |
+
r"""Returns the Corona product of G and H.
|
| 477 |
+
|
| 478 |
+
The corona product of $G$ and $H$ is the graph $C = G \circ H$ obtained by
|
| 479 |
+
taking one copy of $G$, called the center graph, $|V(G)|$ copies of $H$,
|
| 480 |
+
called the outer graph, and making the $i$-th vertex of $G$ adjacent to
|
| 481 |
+
every vertex of the $i$-th copy of $H$, where $1 ≤ i ≤ |V(G)|$.
|
| 482 |
+
|
| 483 |
+
Parameters
|
| 484 |
+
----------
|
| 485 |
+
G, H: NetworkX graphs
|
| 486 |
+
The graphs to take the carona product of.
|
| 487 |
+
`G` is the center graph and `H` is the outer graph
|
| 488 |
+
|
| 489 |
+
Returns
|
| 490 |
+
-------
|
| 491 |
+
C: NetworkX graph
|
| 492 |
+
The Corona product of G and H.
|
| 493 |
+
|
| 494 |
+
Raises
|
| 495 |
+
------
|
| 496 |
+
NetworkXError
|
| 497 |
+
If G and H are not both directed or both undirected.
|
| 498 |
+
|
| 499 |
+
Examples
|
| 500 |
+
--------
|
| 501 |
+
>>> G = nx.cycle_graph(4)
|
| 502 |
+
>>> H = nx.path_graph(2)
|
| 503 |
+
>>> C = nx.corona_product(G, H)
|
| 504 |
+
>>> list(C)
|
| 505 |
+
[0, 1, 2, 3, (0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1), (3, 0), (3, 1)]
|
| 506 |
+
>>> print(C)
|
| 507 |
+
Graph with 12 nodes and 16 edges
|
| 508 |
+
|
| 509 |
+
References
|
| 510 |
+
----------
|
| 511 |
+
[1] M. Tavakoli, F. Rahbarnia, and A. R. Ashrafi,
|
| 512 |
+
"Studying the corona product of graphs under some graph invariants,"
|
| 513 |
+
Transactions on Combinatorics, vol. 3, no. 3, pp. 43–49, Sep. 2014,
|
| 514 |
+
doi: 10.22108/toc.2014.5542.
|
| 515 |
+
[2] A. Faraji, "Corona Product in Graph Theory," Ali Faraji, May 11, 2021.
|
| 516 |
+
https://blog.alifaraji.ir/math/graph-theory/corona-product.html (accessed Dec. 07, 2021).
|
| 517 |
+
"""
|
| 518 |
+
GH = _init_product_graph(G, H)
|
| 519 |
+
GH.add_nodes_from(G)
|
| 520 |
+
GH.add_edges_from(G.edges)
|
| 521 |
+
|
| 522 |
+
for G_node in G:
|
| 523 |
+
# copy nodes of H in GH, call it H_i
|
| 524 |
+
GH.add_nodes_from((G_node, v) for v in H)
|
| 525 |
+
|
| 526 |
+
# copy edges of H_i based on H
|
| 527 |
+
GH.add_edges_from(
|
| 528 |
+
((G_node, e0), (G_node, e1), d) for e0, e1, d in H.edges.data()
|
| 529 |
+
)
|
| 530 |
+
|
| 531 |
+
# creating new edges between H_i and a G's node
|
| 532 |
+
GH.add_edges_from((G_node, (G_node, H_node)) for H_node in H)
|
| 533 |
+
|
| 534 |
+
return GH
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/operators/tests/__pycache__/test_all.cpython-311.pyc
ADDED
|
Binary file (22.5 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/swap.py
ADDED
|
@@ -0,0 +1,405 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Swap edges in a graph.
|
| 2 |
+
"""
|
| 3 |
+
|
| 4 |
+
import math
|
| 5 |
+
|
| 6 |
+
import networkx as nx
|
| 7 |
+
from networkx.utils import py_random_state
|
| 8 |
+
|
| 9 |
+
__all__ = ["double_edge_swap", "connected_double_edge_swap", "directed_edge_swap"]
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
@nx.utils.not_implemented_for("undirected")
|
| 13 |
+
@py_random_state(3)
|
| 14 |
+
@nx._dispatch
|
| 15 |
+
def directed_edge_swap(G, *, nswap=1, max_tries=100, seed=None):
|
| 16 |
+
"""Swap three edges in a directed graph while keeping the node degrees fixed.
|
| 17 |
+
|
| 18 |
+
A directed edge swap swaps three edges such that a -> b -> c -> d becomes
|
| 19 |
+
a -> c -> b -> d. This pattern of swapping allows all possible states with the
|
| 20 |
+
same in- and out-degree distribution in a directed graph to be reached.
|
| 21 |
+
|
| 22 |
+
If the swap would create parallel edges (e.g. if a -> c already existed in the
|
| 23 |
+
previous example), another attempt is made to find a suitable trio of edges.
|
| 24 |
+
|
| 25 |
+
Parameters
|
| 26 |
+
----------
|
| 27 |
+
G : DiGraph
|
| 28 |
+
A directed graph
|
| 29 |
+
|
| 30 |
+
nswap : integer (optional, default=1)
|
| 31 |
+
Number of three-edge (directed) swaps to perform
|
| 32 |
+
|
| 33 |
+
max_tries : integer (optional, default=100)
|
| 34 |
+
Maximum number of attempts to swap edges
|
| 35 |
+
|
| 36 |
+
seed : integer, random_state, or None (default)
|
| 37 |
+
Indicator of random number generation state.
|
| 38 |
+
See :ref:`Randomness<randomness>`.
|
| 39 |
+
|
| 40 |
+
Returns
|
| 41 |
+
-------
|
| 42 |
+
G : DiGraph
|
| 43 |
+
The graph after the edges are swapped.
|
| 44 |
+
|
| 45 |
+
Raises
|
| 46 |
+
------
|
| 47 |
+
NetworkXError
|
| 48 |
+
If `G` is not directed, or
|
| 49 |
+
If nswap > max_tries, or
|
| 50 |
+
If there are fewer than 4 nodes or 3 edges in `G`.
|
| 51 |
+
NetworkXAlgorithmError
|
| 52 |
+
If the number of swap attempts exceeds `max_tries` before `nswap` swaps are made
|
| 53 |
+
|
| 54 |
+
Notes
|
| 55 |
+
-----
|
| 56 |
+
Does not enforce any connectivity constraints.
|
| 57 |
+
|
| 58 |
+
The graph G is modified in place.
|
| 59 |
+
|
| 60 |
+
References
|
| 61 |
+
----------
|
| 62 |
+
.. [1] Erdős, Péter L., et al. “A Simple Havel-Hakimi Type Algorithm to Realize
|
| 63 |
+
Graphical Degree Sequences of Directed Graphs.” ArXiv:0905.4913 [Math],
|
| 64 |
+
Jan. 2010. https://doi.org/10.48550/arXiv.0905.4913.
|
| 65 |
+
Published 2010 in Elec. J. Combinatorics (17(1)). R66.
|
| 66 |
+
http://www.combinatorics.org/Volume_17/PDF/v17i1r66.pdf
|
| 67 |
+
.. [2] “Combinatorics - Reaching All Possible Simple Directed Graphs with a given
|
| 68 |
+
Degree Sequence with 2-Edge Swaps.” Mathematics Stack Exchange,
|
| 69 |
+
https://math.stackexchange.com/questions/22272/. Accessed 30 May 2022.
|
| 70 |
+
"""
|
| 71 |
+
if nswap > max_tries:
|
| 72 |
+
raise nx.NetworkXError("Number of swaps > number of tries allowed.")
|
| 73 |
+
if len(G) < 4:
|
| 74 |
+
raise nx.NetworkXError("DiGraph has fewer than four nodes.")
|
| 75 |
+
if len(G.edges) < 3:
|
| 76 |
+
raise nx.NetworkXError("DiGraph has fewer than 3 edges")
|
| 77 |
+
|
| 78 |
+
# Instead of choosing uniformly at random from a generated edge list,
|
| 79 |
+
# this algorithm chooses nonuniformly from the set of nodes with
|
| 80 |
+
# probability weighted by degree.
|
| 81 |
+
tries = 0
|
| 82 |
+
swapcount = 0
|
| 83 |
+
keys, degrees = zip(*G.degree()) # keys, degree
|
| 84 |
+
cdf = nx.utils.cumulative_distribution(degrees) # cdf of degree
|
| 85 |
+
discrete_sequence = nx.utils.discrete_sequence
|
| 86 |
+
|
| 87 |
+
while swapcount < nswap:
|
| 88 |
+
# choose source node index from discrete distribution
|
| 89 |
+
start_index = discrete_sequence(1, cdistribution=cdf, seed=seed)[0]
|
| 90 |
+
start = keys[start_index]
|
| 91 |
+
tries += 1
|
| 92 |
+
|
| 93 |
+
if tries > max_tries:
|
| 94 |
+
msg = f"Maximum number of swap attempts ({tries}) exceeded before desired swaps achieved ({nswap})."
|
| 95 |
+
raise nx.NetworkXAlgorithmError(msg)
|
| 96 |
+
|
| 97 |
+
# If the given node doesn't have any out edges, then there isn't anything to swap
|
| 98 |
+
if G.out_degree(start) == 0:
|
| 99 |
+
continue
|
| 100 |
+
second = seed.choice(list(G.succ[start]))
|
| 101 |
+
if start == second:
|
| 102 |
+
continue
|
| 103 |
+
|
| 104 |
+
if G.out_degree(second) == 0:
|
| 105 |
+
continue
|
| 106 |
+
third = seed.choice(list(G.succ[second]))
|
| 107 |
+
if second == third:
|
| 108 |
+
continue
|
| 109 |
+
|
| 110 |
+
if G.out_degree(third) == 0:
|
| 111 |
+
continue
|
| 112 |
+
fourth = seed.choice(list(G.succ[third]))
|
| 113 |
+
if third == fourth:
|
| 114 |
+
continue
|
| 115 |
+
|
| 116 |
+
if (
|
| 117 |
+
third not in G.succ[start]
|
| 118 |
+
and fourth not in G.succ[second]
|
| 119 |
+
and second not in G.succ[third]
|
| 120 |
+
):
|
| 121 |
+
# Swap nodes
|
| 122 |
+
G.add_edge(start, third)
|
| 123 |
+
G.add_edge(third, second)
|
| 124 |
+
G.add_edge(second, fourth)
|
| 125 |
+
G.remove_edge(start, second)
|
| 126 |
+
G.remove_edge(second, third)
|
| 127 |
+
G.remove_edge(third, fourth)
|
| 128 |
+
swapcount += 1
|
| 129 |
+
|
| 130 |
+
return G
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
@py_random_state(3)
|
| 134 |
+
@nx._dispatch
|
| 135 |
+
def double_edge_swap(G, nswap=1, max_tries=100, seed=None):
|
| 136 |
+
"""Swap two edges in the graph while keeping the node degrees fixed.
|
| 137 |
+
|
| 138 |
+
A double-edge swap removes two randomly chosen edges u-v and x-y
|
| 139 |
+
and creates the new edges u-x and v-y::
|
| 140 |
+
|
| 141 |
+
u--v u v
|
| 142 |
+
becomes | |
|
| 143 |
+
x--y x y
|
| 144 |
+
|
| 145 |
+
If either the edge u-x or v-y already exist no swap is performed
|
| 146 |
+
and another attempt is made to find a suitable edge pair.
|
| 147 |
+
|
| 148 |
+
Parameters
|
| 149 |
+
----------
|
| 150 |
+
G : graph
|
| 151 |
+
An undirected graph
|
| 152 |
+
|
| 153 |
+
nswap : integer (optional, default=1)
|
| 154 |
+
Number of double-edge swaps to perform
|
| 155 |
+
|
| 156 |
+
max_tries : integer (optional)
|
| 157 |
+
Maximum number of attempts to swap edges
|
| 158 |
+
|
| 159 |
+
seed : integer, random_state, or None (default)
|
| 160 |
+
Indicator of random number generation state.
|
| 161 |
+
See :ref:`Randomness<randomness>`.
|
| 162 |
+
|
| 163 |
+
Returns
|
| 164 |
+
-------
|
| 165 |
+
G : graph
|
| 166 |
+
The graph after double edge swaps.
|
| 167 |
+
|
| 168 |
+
Raises
|
| 169 |
+
------
|
| 170 |
+
NetworkXError
|
| 171 |
+
If `G` is directed, or
|
| 172 |
+
If `nswap` > `max_tries`, or
|
| 173 |
+
If there are fewer than 4 nodes or 2 edges in `G`.
|
| 174 |
+
NetworkXAlgorithmError
|
| 175 |
+
If the number of swap attempts exceeds `max_tries` before `nswap` swaps are made
|
| 176 |
+
|
| 177 |
+
Notes
|
| 178 |
+
-----
|
| 179 |
+
Does not enforce any connectivity constraints.
|
| 180 |
+
|
| 181 |
+
The graph G is modified in place.
|
| 182 |
+
"""
|
| 183 |
+
if G.is_directed():
|
| 184 |
+
raise nx.NetworkXError(
|
| 185 |
+
"double_edge_swap() not defined for directed graphs. Use directed_edge_swap instead."
|
| 186 |
+
)
|
| 187 |
+
if nswap > max_tries:
|
| 188 |
+
raise nx.NetworkXError("Number of swaps > number of tries allowed.")
|
| 189 |
+
if len(G) < 4:
|
| 190 |
+
raise nx.NetworkXError("Graph has fewer than four nodes.")
|
| 191 |
+
if len(G.edges) < 2:
|
| 192 |
+
raise nx.NetworkXError("Graph has fewer than 2 edges")
|
| 193 |
+
# Instead of choosing uniformly at random from a generated edge list,
|
| 194 |
+
# this algorithm chooses nonuniformly from the set of nodes with
|
| 195 |
+
# probability weighted by degree.
|
| 196 |
+
n = 0
|
| 197 |
+
swapcount = 0
|
| 198 |
+
keys, degrees = zip(*G.degree()) # keys, degree
|
| 199 |
+
cdf = nx.utils.cumulative_distribution(degrees) # cdf of degree
|
| 200 |
+
discrete_sequence = nx.utils.discrete_sequence
|
| 201 |
+
while swapcount < nswap:
|
| 202 |
+
# if random.random() < 0.5: continue # trick to avoid periodicities?
|
| 203 |
+
# pick two random edges without creating edge list
|
| 204 |
+
# choose source node indices from discrete distribution
|
| 205 |
+
(ui, xi) = discrete_sequence(2, cdistribution=cdf, seed=seed)
|
| 206 |
+
if ui == xi:
|
| 207 |
+
continue # same source, skip
|
| 208 |
+
u = keys[ui] # convert index to label
|
| 209 |
+
x = keys[xi]
|
| 210 |
+
# choose target uniformly from neighbors
|
| 211 |
+
v = seed.choice(list(G[u]))
|
| 212 |
+
y = seed.choice(list(G[x]))
|
| 213 |
+
if v == y:
|
| 214 |
+
continue # same target, skip
|
| 215 |
+
if (x not in G[u]) and (y not in G[v]): # don't create parallel edges
|
| 216 |
+
G.add_edge(u, x)
|
| 217 |
+
G.add_edge(v, y)
|
| 218 |
+
G.remove_edge(u, v)
|
| 219 |
+
G.remove_edge(x, y)
|
| 220 |
+
swapcount += 1
|
| 221 |
+
if n >= max_tries:
|
| 222 |
+
e = (
|
| 223 |
+
f"Maximum number of swap attempts ({n}) exceeded "
|
| 224 |
+
f"before desired swaps achieved ({nswap})."
|
| 225 |
+
)
|
| 226 |
+
raise nx.NetworkXAlgorithmError(e)
|
| 227 |
+
n += 1
|
| 228 |
+
return G
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
@py_random_state(3)
|
| 232 |
+
@nx._dispatch
|
| 233 |
+
def connected_double_edge_swap(G, nswap=1, _window_threshold=3, seed=None):
|
| 234 |
+
"""Attempts the specified number of double-edge swaps in the graph `G`.
|
| 235 |
+
|
| 236 |
+
A double-edge swap removes two randomly chosen edges `(u, v)` and `(x,
|
| 237 |
+
y)` and creates the new edges `(u, x)` and `(v, y)`::
|
| 238 |
+
|
| 239 |
+
u--v u v
|
| 240 |
+
becomes | |
|
| 241 |
+
x--y x y
|
| 242 |
+
|
| 243 |
+
If either `(u, x)` or `(v, y)` already exist, then no swap is performed
|
| 244 |
+
so the actual number of swapped edges is always *at most* `nswap`.
|
| 245 |
+
|
| 246 |
+
Parameters
|
| 247 |
+
----------
|
| 248 |
+
G : graph
|
| 249 |
+
An undirected graph
|
| 250 |
+
|
| 251 |
+
nswap : integer (optional, default=1)
|
| 252 |
+
Number of double-edge swaps to perform
|
| 253 |
+
|
| 254 |
+
_window_threshold : integer
|
| 255 |
+
|
| 256 |
+
The window size below which connectedness of the graph will be checked
|
| 257 |
+
after each swap.
|
| 258 |
+
|
| 259 |
+
The "window" in this function is a dynamically updated integer that
|
| 260 |
+
represents the number of swap attempts to make before checking if the
|
| 261 |
+
graph remains connected. It is an optimization used to decrease the
|
| 262 |
+
running time of the algorithm in exchange for increased complexity of
|
| 263 |
+
implementation.
|
| 264 |
+
|
| 265 |
+
If the window size is below this threshold, then the algorithm checks
|
| 266 |
+
after each swap if the graph remains connected by checking if there is a
|
| 267 |
+
path joining the two nodes whose edge was just removed. If the window
|
| 268 |
+
size is above this threshold, then the algorithm performs do all the
|
| 269 |
+
swaps in the window and only then check if the graph is still connected.
|
| 270 |
+
|
| 271 |
+
seed : integer, random_state, or None (default)
|
| 272 |
+
Indicator of random number generation state.
|
| 273 |
+
See :ref:`Randomness<randomness>`.
|
| 274 |
+
|
| 275 |
+
Returns
|
| 276 |
+
-------
|
| 277 |
+
int
|
| 278 |
+
The number of successful swaps
|
| 279 |
+
|
| 280 |
+
Raises
|
| 281 |
+
------
|
| 282 |
+
|
| 283 |
+
NetworkXError
|
| 284 |
+
|
| 285 |
+
If the input graph is not connected, or if the graph has fewer than four
|
| 286 |
+
nodes.
|
| 287 |
+
|
| 288 |
+
Notes
|
| 289 |
+
-----
|
| 290 |
+
|
| 291 |
+
The initial graph `G` must be connected, and the resulting graph is
|
| 292 |
+
connected. The graph `G` is modified in place.
|
| 293 |
+
|
| 294 |
+
References
|
| 295 |
+
----------
|
| 296 |
+
.. [1] C. Gkantsidis and M. Mihail and E. Zegura,
|
| 297 |
+
The Markov chain simulation method for generating connected
|
| 298 |
+
power law random graphs, 2003.
|
| 299 |
+
http://citeseer.ist.psu.edu/gkantsidis03markov.html
|
| 300 |
+
"""
|
| 301 |
+
if not nx.is_connected(G):
|
| 302 |
+
raise nx.NetworkXError("Graph not connected")
|
| 303 |
+
if len(G) < 4:
|
| 304 |
+
raise nx.NetworkXError("Graph has fewer than four nodes.")
|
| 305 |
+
n = 0
|
| 306 |
+
swapcount = 0
|
| 307 |
+
deg = G.degree()
|
| 308 |
+
# Label key for nodes
|
| 309 |
+
dk = [n for n, d in G.degree()]
|
| 310 |
+
cdf = nx.utils.cumulative_distribution([d for n, d in G.degree()])
|
| 311 |
+
discrete_sequence = nx.utils.discrete_sequence
|
| 312 |
+
window = 1
|
| 313 |
+
while n < nswap:
|
| 314 |
+
wcount = 0
|
| 315 |
+
swapped = []
|
| 316 |
+
# If the window is small, we just check each time whether the graph is
|
| 317 |
+
# connected by checking if the nodes that were just separated are still
|
| 318 |
+
# connected.
|
| 319 |
+
if window < _window_threshold:
|
| 320 |
+
# This Boolean keeps track of whether there was a failure or not.
|
| 321 |
+
fail = False
|
| 322 |
+
while wcount < window and n < nswap:
|
| 323 |
+
# Pick two random edges without creating the edge list. Choose
|
| 324 |
+
# source nodes from the discrete degree distribution.
|
| 325 |
+
(ui, xi) = discrete_sequence(2, cdistribution=cdf, seed=seed)
|
| 326 |
+
# If the source nodes are the same, skip this pair.
|
| 327 |
+
if ui == xi:
|
| 328 |
+
continue
|
| 329 |
+
# Convert an index to a node label.
|
| 330 |
+
u = dk[ui]
|
| 331 |
+
x = dk[xi]
|
| 332 |
+
# Choose targets uniformly from neighbors.
|
| 333 |
+
v = seed.choice(list(G.neighbors(u)))
|
| 334 |
+
y = seed.choice(list(G.neighbors(x)))
|
| 335 |
+
# If the target nodes are the same, skip this pair.
|
| 336 |
+
if v == y:
|
| 337 |
+
continue
|
| 338 |
+
if x not in G[u] and y not in G[v]:
|
| 339 |
+
G.remove_edge(u, v)
|
| 340 |
+
G.remove_edge(x, y)
|
| 341 |
+
G.add_edge(u, x)
|
| 342 |
+
G.add_edge(v, y)
|
| 343 |
+
swapped.append((u, v, x, y))
|
| 344 |
+
swapcount += 1
|
| 345 |
+
n += 1
|
| 346 |
+
# If G remains connected...
|
| 347 |
+
if nx.has_path(G, u, v):
|
| 348 |
+
wcount += 1
|
| 349 |
+
# Otherwise, undo the changes.
|
| 350 |
+
else:
|
| 351 |
+
G.add_edge(u, v)
|
| 352 |
+
G.add_edge(x, y)
|
| 353 |
+
G.remove_edge(u, x)
|
| 354 |
+
G.remove_edge(v, y)
|
| 355 |
+
swapcount -= 1
|
| 356 |
+
fail = True
|
| 357 |
+
# If one of the swaps failed, reduce the window size.
|
| 358 |
+
if fail:
|
| 359 |
+
window = math.ceil(window / 2)
|
| 360 |
+
else:
|
| 361 |
+
window += 1
|
| 362 |
+
# If the window is large, then there is a good chance that a bunch of
|
| 363 |
+
# swaps will work. It's quicker to do all those swaps first and then
|
| 364 |
+
# check if the graph remains connected.
|
| 365 |
+
else:
|
| 366 |
+
while wcount < window and n < nswap:
|
| 367 |
+
# Pick two random edges without creating the edge list. Choose
|
| 368 |
+
# source nodes from the discrete degree distribution.
|
| 369 |
+
(ui, xi) = discrete_sequence(2, cdistribution=cdf, seed=seed)
|
| 370 |
+
# If the source nodes are the same, skip this pair.
|
| 371 |
+
if ui == xi:
|
| 372 |
+
continue
|
| 373 |
+
# Convert an index to a node label.
|
| 374 |
+
u = dk[ui]
|
| 375 |
+
x = dk[xi]
|
| 376 |
+
# Choose targets uniformly from neighbors.
|
| 377 |
+
v = seed.choice(list(G.neighbors(u)))
|
| 378 |
+
y = seed.choice(list(G.neighbors(x)))
|
| 379 |
+
# If the target nodes are the same, skip this pair.
|
| 380 |
+
if v == y:
|
| 381 |
+
continue
|
| 382 |
+
if x not in G[u] and y not in G[v]:
|
| 383 |
+
G.remove_edge(u, v)
|
| 384 |
+
G.remove_edge(x, y)
|
| 385 |
+
G.add_edge(u, x)
|
| 386 |
+
G.add_edge(v, y)
|
| 387 |
+
swapped.append((u, v, x, y))
|
| 388 |
+
swapcount += 1
|
| 389 |
+
n += 1
|
| 390 |
+
wcount += 1
|
| 391 |
+
# If the graph remains connected, increase the window size.
|
| 392 |
+
if nx.is_connected(G):
|
| 393 |
+
window += 1
|
| 394 |
+
# Otherwise, undo the changes from the previous window and decrease
|
| 395 |
+
# the window size.
|
| 396 |
+
else:
|
| 397 |
+
while swapped:
|
| 398 |
+
(u, v, x, y) = swapped.pop()
|
| 399 |
+
G.add_edge(u, v)
|
| 400 |
+
G.add_edge(x, y)
|
| 401 |
+
G.remove_edge(u, x)
|
| 402 |
+
G.remove_edge(v, y)
|
| 403 |
+
swapcount -= 1
|
| 404 |
+
window = math.ceil(window / 2)
|
| 405 |
+
return swapcount
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .graph import Graph
|
| 2 |
+
from .digraph import DiGraph
|
| 3 |
+
from .multigraph import MultiGraph
|
| 4 |
+
from .multidigraph import MultiDiGraph
|
| 5 |
+
|
| 6 |
+
from .function import *
|
| 7 |
+
from .graphviews import subgraph_view, reverse_view
|
| 8 |
+
|
| 9 |
+
from networkx.classes import filters
|
| 10 |
+
|
| 11 |
+
from networkx.classes import coreviews
|
| 12 |
+
from networkx.classes import graphviews
|
| 13 |
+
from networkx.classes import reportviews
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/__pycache__/digraph.cpython-311.pyc
ADDED
|
Binary file (54.4 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/__pycache__/graphviews.cpython-311.pyc
ADDED
|
Binary file (10 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/__pycache__/multidigraph.cpython-311.pyc
ADDED
|
Binary file (41.2 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/__pycache__/multigraph.cpython-311.pyc
ADDED
|
Binary file (52.8 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/reportviews.py
ADDED
|
@@ -0,0 +1,1431 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
View Classes provide node, edge and degree "views" of a graph.
|
| 3 |
+
|
| 4 |
+
Views for nodes, edges and degree are provided for all base graph classes.
|
| 5 |
+
A view means a read-only object that is quick to create, automatically
|
| 6 |
+
updated when the graph changes, and provides basic access like `n in V`,
|
| 7 |
+
`for n in V`, `V[n]` and sometimes set operations.
|
| 8 |
+
|
| 9 |
+
The views are read-only iterable containers that are updated as the
|
| 10 |
+
graph is updated. As with dicts, the graph should not be updated
|
| 11 |
+
while iterating through the view. Views can be iterated multiple times.
|
| 12 |
+
|
| 13 |
+
Edge and Node views also allow data attribute lookup.
|
| 14 |
+
The resulting attribute dict is writable as `G.edges[3, 4]['color']='red'`
|
| 15 |
+
Degree views allow lookup of degree values for single nodes.
|
| 16 |
+
Weighted degree is supported with the `weight` argument.
|
| 17 |
+
|
| 18 |
+
NodeView
|
| 19 |
+
========
|
| 20 |
+
|
| 21 |
+
`V = G.nodes` (or `V = G.nodes()`) allows `len(V)`, `n in V`, set
|
| 22 |
+
operations e.g. "G.nodes & H.nodes", and `dd = G.nodes[n]`, where
|
| 23 |
+
`dd` is the node data dict. Iteration is over the nodes by default.
|
| 24 |
+
|
| 25 |
+
NodeDataView
|
| 26 |
+
============
|
| 27 |
+
|
| 28 |
+
To iterate over (node, data) pairs, use arguments to `G.nodes()`
|
| 29 |
+
to create a DataView e.g. `DV = G.nodes(data='color', default='red')`.
|
| 30 |
+
The DataView iterates as `for n, color in DV` and allows
|
| 31 |
+
`(n, 'red') in DV`. Using `DV = G.nodes(data=True)`, the DataViews
|
| 32 |
+
use the full datadict in writeable form also allowing contain testing as
|
| 33 |
+
`(n, {'color': 'red'}) in VD`. DataViews allow set operations when
|
| 34 |
+
data attributes are hashable.
|
| 35 |
+
|
| 36 |
+
DegreeView
|
| 37 |
+
==========
|
| 38 |
+
|
| 39 |
+
`V = G.degree` allows iteration over (node, degree) pairs as well
|
| 40 |
+
as lookup: `deg=V[n]`. There are many flavors of DegreeView
|
| 41 |
+
for In/Out/Directed/Multi. For Directed Graphs, `G.degree`
|
| 42 |
+
counts both in and out going edges. `G.out_degree` and
|
| 43 |
+
`G.in_degree` count only specific directions.
|
| 44 |
+
Weighted degree using edge data attributes is provide via
|
| 45 |
+
`V = G.degree(weight='attr_name')` where any string with the
|
| 46 |
+
attribute name can be used. `weight=None` is the default.
|
| 47 |
+
No set operations are implemented for degrees, use NodeView.
|
| 48 |
+
|
| 49 |
+
The argument `nbunch` restricts iteration to nodes in nbunch.
|
| 50 |
+
The DegreeView can still lookup any node even if nbunch is specified.
|
| 51 |
+
|
| 52 |
+
EdgeView
|
| 53 |
+
========
|
| 54 |
+
|
| 55 |
+
`V = G.edges` or `V = G.edges()` allows iteration over edges as well as
|
| 56 |
+
`e in V`, set operations and edge data lookup `dd = G.edges[2, 3]`.
|
| 57 |
+
Iteration is over 2-tuples `(u, v)` for Graph/DiGraph. For multigraphs
|
| 58 |
+
edges 3-tuples `(u, v, key)` are the default but 2-tuples can be obtained
|
| 59 |
+
via `V = G.edges(keys=False)`.
|
| 60 |
+
|
| 61 |
+
Set operations for directed graphs treat the edges as a set of 2-tuples.
|
| 62 |
+
For undirected graphs, 2-tuples are not a unique representation of edges.
|
| 63 |
+
So long as the set being compared to contains unique representations
|
| 64 |
+
of its edges, the set operations will act as expected. If the other
|
| 65 |
+
set contains both `(0, 1)` and `(1, 0)` however, the result of set
|
| 66 |
+
operations may contain both representations of the same edge.
|
| 67 |
+
|
| 68 |
+
EdgeDataView
|
| 69 |
+
============
|
| 70 |
+
|
| 71 |
+
Edge data can be reported using an EdgeDataView typically created
|
| 72 |
+
by calling an EdgeView: `DV = G.edges(data='weight', default=1)`.
|
| 73 |
+
The EdgeDataView allows iteration over edge tuples, membership checking
|
| 74 |
+
but no set operations.
|
| 75 |
+
|
| 76 |
+
Iteration depends on `data` and `default` and for multigraph `keys`
|
| 77 |
+
If `data is False` (the default) then iterate over 2-tuples `(u, v)`.
|
| 78 |
+
If `data is True` iterate over 3-tuples `(u, v, datadict)`.
|
| 79 |
+
Otherwise iterate over `(u, v, datadict.get(data, default))`.
|
| 80 |
+
For Multigraphs, if `keys is True`, replace `u, v` with `u, v, key`
|
| 81 |
+
to create 3-tuples and 4-tuples.
|
| 82 |
+
|
| 83 |
+
The argument `nbunch` restricts edges to those incident to nodes in nbunch.
|
| 84 |
+
"""
|
| 85 |
+
from collections.abc import Mapping, Set
|
| 86 |
+
|
| 87 |
+
import networkx as nx
|
| 88 |
+
|
| 89 |
+
__all__ = [
|
| 90 |
+
"NodeView",
|
| 91 |
+
"NodeDataView",
|
| 92 |
+
"EdgeView",
|
| 93 |
+
"OutEdgeView",
|
| 94 |
+
"InEdgeView",
|
| 95 |
+
"EdgeDataView",
|
| 96 |
+
"OutEdgeDataView",
|
| 97 |
+
"InEdgeDataView",
|
| 98 |
+
"MultiEdgeView",
|
| 99 |
+
"OutMultiEdgeView",
|
| 100 |
+
"InMultiEdgeView",
|
| 101 |
+
"MultiEdgeDataView",
|
| 102 |
+
"OutMultiEdgeDataView",
|
| 103 |
+
"InMultiEdgeDataView",
|
| 104 |
+
"DegreeView",
|
| 105 |
+
"DiDegreeView",
|
| 106 |
+
"InDegreeView",
|
| 107 |
+
"OutDegreeView",
|
| 108 |
+
"MultiDegreeView",
|
| 109 |
+
"DiMultiDegreeView",
|
| 110 |
+
"InMultiDegreeView",
|
| 111 |
+
"OutMultiDegreeView",
|
| 112 |
+
]
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
# NodeViews
|
| 116 |
+
class NodeView(Mapping, Set):
|
| 117 |
+
"""A NodeView class to act as G.nodes for a NetworkX Graph
|
| 118 |
+
|
| 119 |
+
Set operations act on the nodes without considering data.
|
| 120 |
+
Iteration is over nodes. Node data can be looked up like a dict.
|
| 121 |
+
Use NodeDataView to iterate over node data or to specify a data
|
| 122 |
+
attribute for lookup. NodeDataView is created by calling the NodeView.
|
| 123 |
+
|
| 124 |
+
Parameters
|
| 125 |
+
----------
|
| 126 |
+
graph : NetworkX graph-like class
|
| 127 |
+
|
| 128 |
+
Examples
|
| 129 |
+
--------
|
| 130 |
+
>>> G = nx.path_graph(3)
|
| 131 |
+
>>> NV = G.nodes()
|
| 132 |
+
>>> 2 in NV
|
| 133 |
+
True
|
| 134 |
+
>>> for n in NV:
|
| 135 |
+
... print(n)
|
| 136 |
+
0
|
| 137 |
+
1
|
| 138 |
+
2
|
| 139 |
+
>>> assert NV & {1, 2, 3} == {1, 2}
|
| 140 |
+
|
| 141 |
+
>>> G.add_node(2, color="blue")
|
| 142 |
+
>>> NV[2]
|
| 143 |
+
{'color': 'blue'}
|
| 144 |
+
>>> G.add_node(8, color="red")
|
| 145 |
+
>>> NDV = G.nodes(data=True)
|
| 146 |
+
>>> (2, NV[2]) in NDV
|
| 147 |
+
True
|
| 148 |
+
>>> for n, dd in NDV:
|
| 149 |
+
... print((n, dd.get("color", "aqua")))
|
| 150 |
+
(0, 'aqua')
|
| 151 |
+
(1, 'aqua')
|
| 152 |
+
(2, 'blue')
|
| 153 |
+
(8, 'red')
|
| 154 |
+
>>> NDV[2] == NV[2]
|
| 155 |
+
True
|
| 156 |
+
|
| 157 |
+
>>> NVdata = G.nodes(data="color", default="aqua")
|
| 158 |
+
>>> (2, NVdata[2]) in NVdata
|
| 159 |
+
True
|
| 160 |
+
>>> for n, dd in NVdata:
|
| 161 |
+
... print((n, dd))
|
| 162 |
+
(0, 'aqua')
|
| 163 |
+
(1, 'aqua')
|
| 164 |
+
(2, 'blue')
|
| 165 |
+
(8, 'red')
|
| 166 |
+
>>> NVdata[2] == NV[2] # NVdata gets 'color', NV gets datadict
|
| 167 |
+
False
|
| 168 |
+
"""
|
| 169 |
+
|
| 170 |
+
__slots__ = ("_nodes",)
|
| 171 |
+
|
| 172 |
+
def __getstate__(self):
|
| 173 |
+
return {"_nodes": self._nodes}
|
| 174 |
+
|
| 175 |
+
def __setstate__(self, state):
|
| 176 |
+
self._nodes = state["_nodes"]
|
| 177 |
+
|
| 178 |
+
def __init__(self, graph):
|
| 179 |
+
self._nodes = graph._node
|
| 180 |
+
|
| 181 |
+
# Mapping methods
|
| 182 |
+
def __len__(self):
|
| 183 |
+
return len(self._nodes)
|
| 184 |
+
|
| 185 |
+
def __iter__(self):
|
| 186 |
+
return iter(self._nodes)
|
| 187 |
+
|
| 188 |
+
def __getitem__(self, n):
|
| 189 |
+
if isinstance(n, slice):
|
| 190 |
+
raise nx.NetworkXError(
|
| 191 |
+
f"{type(self).__name__} does not support slicing, "
|
| 192 |
+
f"try list(G.nodes)[{n.start}:{n.stop}:{n.step}]"
|
| 193 |
+
)
|
| 194 |
+
return self._nodes[n]
|
| 195 |
+
|
| 196 |
+
# Set methods
|
| 197 |
+
def __contains__(self, n):
|
| 198 |
+
return n in self._nodes
|
| 199 |
+
|
| 200 |
+
@classmethod
|
| 201 |
+
def _from_iterable(cls, it):
|
| 202 |
+
return set(it)
|
| 203 |
+
|
| 204 |
+
# DataView method
|
| 205 |
+
def __call__(self, data=False, default=None):
|
| 206 |
+
if data is False:
|
| 207 |
+
return self
|
| 208 |
+
return NodeDataView(self._nodes, data, default)
|
| 209 |
+
|
| 210 |
+
def data(self, data=True, default=None):
|
| 211 |
+
"""
|
| 212 |
+
Return a read-only view of node data.
|
| 213 |
+
|
| 214 |
+
Parameters
|
| 215 |
+
----------
|
| 216 |
+
data : bool or node data key, default=True
|
| 217 |
+
If ``data=True`` (the default), return a `NodeDataView` object that
|
| 218 |
+
maps each node to *all* of its attributes. `data` may also be an
|
| 219 |
+
arbitrary key, in which case the `NodeDataView` maps each node to
|
| 220 |
+
the value for the keyed attribute. In this case, if a node does
|
| 221 |
+
not have the `data` attribute, the `default` value is used.
|
| 222 |
+
default : object, default=None
|
| 223 |
+
The value used when a node does not have a specific attribute.
|
| 224 |
+
|
| 225 |
+
Returns
|
| 226 |
+
-------
|
| 227 |
+
NodeDataView
|
| 228 |
+
The layout of the returned NodeDataView depends on the value of the
|
| 229 |
+
`data` parameter.
|
| 230 |
+
|
| 231 |
+
Notes
|
| 232 |
+
-----
|
| 233 |
+
If ``data=False``, returns a `NodeView` object without data.
|
| 234 |
+
|
| 235 |
+
See Also
|
| 236 |
+
--------
|
| 237 |
+
NodeDataView
|
| 238 |
+
|
| 239 |
+
Examples
|
| 240 |
+
--------
|
| 241 |
+
>>> G = nx.Graph()
|
| 242 |
+
>>> G.add_nodes_from([
|
| 243 |
+
... (0, {"color": "red", "weight": 10}),
|
| 244 |
+
... (1, {"color": "blue"}),
|
| 245 |
+
... (2, {"color": "yellow", "weight": 2})
|
| 246 |
+
... ])
|
| 247 |
+
|
| 248 |
+
Accessing node data with ``data=True`` (the default) returns a
|
| 249 |
+
NodeDataView mapping each node to all of its attributes:
|
| 250 |
+
|
| 251 |
+
>>> G.nodes.data()
|
| 252 |
+
NodeDataView({0: {'color': 'red', 'weight': 10}, 1: {'color': 'blue'}, 2: {'color': 'yellow', 'weight': 2}})
|
| 253 |
+
|
| 254 |
+
If `data` represents a key in the node attribute dict, a NodeDataView mapping
|
| 255 |
+
the nodes to the value for that specific key is returned:
|
| 256 |
+
|
| 257 |
+
>>> G.nodes.data("color")
|
| 258 |
+
NodeDataView({0: 'red', 1: 'blue', 2: 'yellow'}, data='color')
|
| 259 |
+
|
| 260 |
+
If a specific key is not found in an attribute dict, the value specified
|
| 261 |
+
by `default` is returned:
|
| 262 |
+
|
| 263 |
+
>>> G.nodes.data("weight", default=-999)
|
| 264 |
+
NodeDataView({0: 10, 1: -999, 2: 2}, data='weight')
|
| 265 |
+
|
| 266 |
+
Note that there is no check that the `data` key is in any of the
|
| 267 |
+
node attribute dictionaries:
|
| 268 |
+
|
| 269 |
+
>>> G.nodes.data("height")
|
| 270 |
+
NodeDataView({0: None, 1: None, 2: None}, data='height')
|
| 271 |
+
"""
|
| 272 |
+
if data is False:
|
| 273 |
+
return self
|
| 274 |
+
return NodeDataView(self._nodes, data, default)
|
| 275 |
+
|
| 276 |
+
def __str__(self):
|
| 277 |
+
return str(list(self))
|
| 278 |
+
|
| 279 |
+
def __repr__(self):
|
| 280 |
+
return f"{self.__class__.__name__}({tuple(self)})"
|
| 281 |
+
|
| 282 |
+
|
| 283 |
+
class NodeDataView(Set):
|
| 284 |
+
"""A DataView class for nodes of a NetworkX Graph
|
| 285 |
+
|
| 286 |
+
The main use for this class is to iterate through node-data pairs.
|
| 287 |
+
The data can be the entire data-dictionary for each node, or it
|
| 288 |
+
can be a specific attribute (with default) for each node.
|
| 289 |
+
Set operations are enabled with NodeDataView, but don't work in
|
| 290 |
+
cases where the data is not hashable. Use with caution.
|
| 291 |
+
Typically, set operations on nodes use NodeView, not NodeDataView.
|
| 292 |
+
That is, they use `G.nodes` instead of `G.nodes(data='foo')`.
|
| 293 |
+
|
| 294 |
+
Parameters
|
| 295 |
+
==========
|
| 296 |
+
graph : NetworkX graph-like class
|
| 297 |
+
data : bool or string (default=False)
|
| 298 |
+
default : object (default=None)
|
| 299 |
+
"""
|
| 300 |
+
|
| 301 |
+
__slots__ = ("_nodes", "_data", "_default")
|
| 302 |
+
|
| 303 |
+
def __getstate__(self):
|
| 304 |
+
return {"_nodes": self._nodes, "_data": self._data, "_default": self._default}
|
| 305 |
+
|
| 306 |
+
def __setstate__(self, state):
|
| 307 |
+
self._nodes = state["_nodes"]
|
| 308 |
+
self._data = state["_data"]
|
| 309 |
+
self._default = state["_default"]
|
| 310 |
+
|
| 311 |
+
def __init__(self, nodedict, data=False, default=None):
|
| 312 |
+
self._nodes = nodedict
|
| 313 |
+
self._data = data
|
| 314 |
+
self._default = default
|
| 315 |
+
|
| 316 |
+
@classmethod
|
| 317 |
+
def _from_iterable(cls, it):
|
| 318 |
+
try:
|
| 319 |
+
return set(it)
|
| 320 |
+
except TypeError as err:
|
| 321 |
+
if "unhashable" in str(err):
|
| 322 |
+
msg = " : Could be b/c data=True or your values are unhashable"
|
| 323 |
+
raise TypeError(str(err) + msg) from err
|
| 324 |
+
raise
|
| 325 |
+
|
| 326 |
+
def __len__(self):
|
| 327 |
+
return len(self._nodes)
|
| 328 |
+
|
| 329 |
+
def __iter__(self):
|
| 330 |
+
data = self._data
|
| 331 |
+
if data is False:
|
| 332 |
+
return iter(self._nodes)
|
| 333 |
+
if data is True:
|
| 334 |
+
return iter(self._nodes.items())
|
| 335 |
+
return (
|
| 336 |
+
(n, dd[data] if data in dd else self._default)
|
| 337 |
+
for n, dd in self._nodes.items()
|
| 338 |
+
)
|
| 339 |
+
|
| 340 |
+
def __contains__(self, n):
|
| 341 |
+
try:
|
| 342 |
+
node_in = n in self._nodes
|
| 343 |
+
except TypeError:
|
| 344 |
+
n, d = n
|
| 345 |
+
return n in self._nodes and self[n] == d
|
| 346 |
+
if node_in is True:
|
| 347 |
+
return node_in
|
| 348 |
+
try:
|
| 349 |
+
n, d = n
|
| 350 |
+
except (TypeError, ValueError):
|
| 351 |
+
return False
|
| 352 |
+
return n in self._nodes and self[n] == d
|
| 353 |
+
|
| 354 |
+
def __getitem__(self, n):
|
| 355 |
+
if isinstance(n, slice):
|
| 356 |
+
raise nx.NetworkXError(
|
| 357 |
+
f"{type(self).__name__} does not support slicing, "
|
| 358 |
+
f"try list(G.nodes.data())[{n.start}:{n.stop}:{n.step}]"
|
| 359 |
+
)
|
| 360 |
+
ddict = self._nodes[n]
|
| 361 |
+
data = self._data
|
| 362 |
+
if data is False or data is True:
|
| 363 |
+
return ddict
|
| 364 |
+
return ddict[data] if data in ddict else self._default
|
| 365 |
+
|
| 366 |
+
def __str__(self):
|
| 367 |
+
return str(list(self))
|
| 368 |
+
|
| 369 |
+
def __repr__(self):
|
| 370 |
+
name = self.__class__.__name__
|
| 371 |
+
if self._data is False:
|
| 372 |
+
return f"{name}({tuple(self)})"
|
| 373 |
+
if self._data is True:
|
| 374 |
+
return f"{name}({dict(self)})"
|
| 375 |
+
return f"{name}({dict(self)}, data={self._data!r})"
|
| 376 |
+
|
| 377 |
+
|
| 378 |
+
# DegreeViews
|
| 379 |
+
class DiDegreeView:
|
| 380 |
+
"""A View class for degree of nodes in a NetworkX Graph
|
| 381 |
+
|
| 382 |
+
The functionality is like dict.items() with (node, degree) pairs.
|
| 383 |
+
Additional functionality includes read-only lookup of node degree,
|
| 384 |
+
and calling with optional features nbunch (for only a subset of nodes)
|
| 385 |
+
and weight (use edge weights to compute degree).
|
| 386 |
+
|
| 387 |
+
Parameters
|
| 388 |
+
==========
|
| 389 |
+
graph : NetworkX graph-like class
|
| 390 |
+
nbunch : node, container of nodes, or None meaning all nodes (default=None)
|
| 391 |
+
weight : bool or string (default=None)
|
| 392 |
+
|
| 393 |
+
Notes
|
| 394 |
+
-----
|
| 395 |
+
DegreeView can still lookup any node even if nbunch is specified.
|
| 396 |
+
|
| 397 |
+
Examples
|
| 398 |
+
--------
|
| 399 |
+
>>> G = nx.path_graph(3)
|
| 400 |
+
>>> DV = G.degree()
|
| 401 |
+
>>> assert DV[2] == 1
|
| 402 |
+
>>> assert sum(deg for n, deg in DV) == 4
|
| 403 |
+
|
| 404 |
+
>>> DVweight = G.degree(weight="span")
|
| 405 |
+
>>> G.add_edge(1, 2, span=34)
|
| 406 |
+
>>> DVweight[2]
|
| 407 |
+
34
|
| 408 |
+
>>> DVweight[0] # default edge weight is 1
|
| 409 |
+
1
|
| 410 |
+
>>> sum(span for n, span in DVweight) # sum weighted degrees
|
| 411 |
+
70
|
| 412 |
+
|
| 413 |
+
>>> DVnbunch = G.degree(nbunch=(1, 2))
|
| 414 |
+
>>> assert len(list(DVnbunch)) == 2 # iteration over nbunch only
|
| 415 |
+
"""
|
| 416 |
+
|
| 417 |
+
def __init__(self, G, nbunch=None, weight=None):
|
| 418 |
+
self._graph = G
|
| 419 |
+
self._succ = G._succ if hasattr(G, "_succ") else G._adj
|
| 420 |
+
self._pred = G._pred if hasattr(G, "_pred") else G._adj
|
| 421 |
+
self._nodes = self._succ if nbunch is None else list(G.nbunch_iter(nbunch))
|
| 422 |
+
self._weight = weight
|
| 423 |
+
|
| 424 |
+
def __call__(self, nbunch=None, weight=None):
|
| 425 |
+
if nbunch is None:
|
| 426 |
+
if weight == self._weight:
|
| 427 |
+
return self
|
| 428 |
+
return self.__class__(self._graph, None, weight)
|
| 429 |
+
try:
|
| 430 |
+
if nbunch in self._nodes:
|
| 431 |
+
if weight == self._weight:
|
| 432 |
+
return self[nbunch]
|
| 433 |
+
return self.__class__(self._graph, None, weight)[nbunch]
|
| 434 |
+
except TypeError:
|
| 435 |
+
pass
|
| 436 |
+
return self.__class__(self._graph, nbunch, weight)
|
| 437 |
+
|
| 438 |
+
def __getitem__(self, n):
|
| 439 |
+
weight = self._weight
|
| 440 |
+
succs = self._succ[n]
|
| 441 |
+
preds = self._pred[n]
|
| 442 |
+
if weight is None:
|
| 443 |
+
return len(succs) + len(preds)
|
| 444 |
+
return sum(dd.get(weight, 1) for dd in succs.values()) + sum(
|
| 445 |
+
dd.get(weight, 1) for dd in preds.values()
|
| 446 |
+
)
|
| 447 |
+
|
| 448 |
+
def __iter__(self):
|
| 449 |
+
weight = self._weight
|
| 450 |
+
if weight is None:
|
| 451 |
+
for n in self._nodes:
|
| 452 |
+
succs = self._succ[n]
|
| 453 |
+
preds = self._pred[n]
|
| 454 |
+
yield (n, len(succs) + len(preds))
|
| 455 |
+
else:
|
| 456 |
+
for n in self._nodes:
|
| 457 |
+
succs = self._succ[n]
|
| 458 |
+
preds = self._pred[n]
|
| 459 |
+
deg = sum(dd.get(weight, 1) for dd in succs.values()) + sum(
|
| 460 |
+
dd.get(weight, 1) for dd in preds.values()
|
| 461 |
+
)
|
| 462 |
+
yield (n, deg)
|
| 463 |
+
|
| 464 |
+
def __len__(self):
|
| 465 |
+
return len(self._nodes)
|
| 466 |
+
|
| 467 |
+
def __str__(self):
|
| 468 |
+
return str(list(self))
|
| 469 |
+
|
| 470 |
+
def __repr__(self):
|
| 471 |
+
return f"{self.__class__.__name__}({dict(self)})"
|
| 472 |
+
|
| 473 |
+
|
| 474 |
+
class DegreeView(DiDegreeView):
|
| 475 |
+
"""A DegreeView class to act as G.degree for a NetworkX Graph
|
| 476 |
+
|
| 477 |
+
Typical usage focuses on iteration over `(node, degree)` pairs.
|
| 478 |
+
The degree is by default the number of edges incident to the node.
|
| 479 |
+
Optional argument `weight` enables weighted degree using the edge
|
| 480 |
+
attribute named in the `weight` argument. Reporting and iteration
|
| 481 |
+
can also be restricted to a subset of nodes using `nbunch`.
|
| 482 |
+
|
| 483 |
+
Additional functionality include node lookup so that `G.degree[n]`
|
| 484 |
+
reported the (possibly weighted) degree of node `n`. Calling the
|
| 485 |
+
view creates a view with different arguments `nbunch` or `weight`.
|
| 486 |
+
|
| 487 |
+
Parameters
|
| 488 |
+
==========
|
| 489 |
+
graph : NetworkX graph-like class
|
| 490 |
+
nbunch : node, container of nodes, or None meaning all nodes (default=None)
|
| 491 |
+
weight : string or None (default=None)
|
| 492 |
+
|
| 493 |
+
Notes
|
| 494 |
+
-----
|
| 495 |
+
DegreeView can still lookup any node even if nbunch is specified.
|
| 496 |
+
|
| 497 |
+
Examples
|
| 498 |
+
--------
|
| 499 |
+
>>> G = nx.path_graph(3)
|
| 500 |
+
>>> DV = G.degree()
|
| 501 |
+
>>> assert DV[2] == 1
|
| 502 |
+
>>> assert G.degree[2] == 1
|
| 503 |
+
>>> assert sum(deg for n, deg in DV) == 4
|
| 504 |
+
|
| 505 |
+
>>> DVweight = G.degree(weight="span")
|
| 506 |
+
>>> G.add_edge(1, 2, span=34)
|
| 507 |
+
>>> DVweight[2]
|
| 508 |
+
34
|
| 509 |
+
>>> DVweight[0] # default edge weight is 1
|
| 510 |
+
1
|
| 511 |
+
>>> sum(span for n, span in DVweight) # sum weighted degrees
|
| 512 |
+
70
|
| 513 |
+
|
| 514 |
+
>>> DVnbunch = G.degree(nbunch=(1, 2))
|
| 515 |
+
>>> assert len(list(DVnbunch)) == 2 # iteration over nbunch only
|
| 516 |
+
"""
|
| 517 |
+
|
| 518 |
+
def __getitem__(self, n):
|
| 519 |
+
weight = self._weight
|
| 520 |
+
nbrs = self._succ[n]
|
| 521 |
+
if weight is None:
|
| 522 |
+
return len(nbrs) + (n in nbrs)
|
| 523 |
+
return sum(dd.get(weight, 1) for dd in nbrs.values()) + (
|
| 524 |
+
n in nbrs and nbrs[n].get(weight, 1)
|
| 525 |
+
)
|
| 526 |
+
|
| 527 |
+
def __iter__(self):
|
| 528 |
+
weight = self._weight
|
| 529 |
+
if weight is None:
|
| 530 |
+
for n in self._nodes:
|
| 531 |
+
nbrs = self._succ[n]
|
| 532 |
+
yield (n, len(nbrs) + (n in nbrs))
|
| 533 |
+
else:
|
| 534 |
+
for n in self._nodes:
|
| 535 |
+
nbrs = self._succ[n]
|
| 536 |
+
deg = sum(dd.get(weight, 1) for dd in nbrs.values()) + (
|
| 537 |
+
n in nbrs and nbrs[n].get(weight, 1)
|
| 538 |
+
)
|
| 539 |
+
yield (n, deg)
|
| 540 |
+
|
| 541 |
+
|
| 542 |
+
class OutDegreeView(DiDegreeView):
|
| 543 |
+
"""A DegreeView class to report out_degree for a DiGraph; See DegreeView"""
|
| 544 |
+
|
| 545 |
+
def __getitem__(self, n):
|
| 546 |
+
weight = self._weight
|
| 547 |
+
nbrs = self._succ[n]
|
| 548 |
+
if self._weight is None:
|
| 549 |
+
return len(nbrs)
|
| 550 |
+
return sum(dd.get(self._weight, 1) for dd in nbrs.values())
|
| 551 |
+
|
| 552 |
+
def __iter__(self):
|
| 553 |
+
weight = self._weight
|
| 554 |
+
if weight is None:
|
| 555 |
+
for n in self._nodes:
|
| 556 |
+
succs = self._succ[n]
|
| 557 |
+
yield (n, len(succs))
|
| 558 |
+
else:
|
| 559 |
+
for n in self._nodes:
|
| 560 |
+
succs = self._succ[n]
|
| 561 |
+
deg = sum(dd.get(weight, 1) for dd in succs.values())
|
| 562 |
+
yield (n, deg)
|
| 563 |
+
|
| 564 |
+
|
| 565 |
+
class InDegreeView(DiDegreeView):
|
| 566 |
+
"""A DegreeView class to report in_degree for a DiGraph; See DegreeView"""
|
| 567 |
+
|
| 568 |
+
def __getitem__(self, n):
|
| 569 |
+
weight = self._weight
|
| 570 |
+
nbrs = self._pred[n]
|
| 571 |
+
if weight is None:
|
| 572 |
+
return len(nbrs)
|
| 573 |
+
return sum(dd.get(weight, 1) for dd in nbrs.values())
|
| 574 |
+
|
| 575 |
+
def __iter__(self):
|
| 576 |
+
weight = self._weight
|
| 577 |
+
if weight is None:
|
| 578 |
+
for n in self._nodes:
|
| 579 |
+
preds = self._pred[n]
|
| 580 |
+
yield (n, len(preds))
|
| 581 |
+
else:
|
| 582 |
+
for n in self._nodes:
|
| 583 |
+
preds = self._pred[n]
|
| 584 |
+
deg = sum(dd.get(weight, 1) for dd in preds.values())
|
| 585 |
+
yield (n, deg)
|
| 586 |
+
|
| 587 |
+
|
| 588 |
+
class MultiDegreeView(DiDegreeView):
|
| 589 |
+
"""A DegreeView class for undirected multigraphs; See DegreeView"""
|
| 590 |
+
|
| 591 |
+
def __getitem__(self, n):
|
| 592 |
+
weight = self._weight
|
| 593 |
+
nbrs = self._succ[n]
|
| 594 |
+
if weight is None:
|
| 595 |
+
return sum(len(keys) for keys in nbrs.values()) + (
|
| 596 |
+
n in nbrs and len(nbrs[n])
|
| 597 |
+
)
|
| 598 |
+
# edge weighted graph - degree is sum of nbr edge weights
|
| 599 |
+
deg = sum(
|
| 600 |
+
d.get(weight, 1) for key_dict in nbrs.values() for d in key_dict.values()
|
| 601 |
+
)
|
| 602 |
+
if n in nbrs:
|
| 603 |
+
deg += sum(d.get(weight, 1) for d in nbrs[n].values())
|
| 604 |
+
return deg
|
| 605 |
+
|
| 606 |
+
def __iter__(self):
|
| 607 |
+
weight = self._weight
|
| 608 |
+
if weight is None:
|
| 609 |
+
for n in self._nodes:
|
| 610 |
+
nbrs = self._succ[n]
|
| 611 |
+
deg = sum(len(keys) for keys in nbrs.values()) + (
|
| 612 |
+
n in nbrs and len(nbrs[n])
|
| 613 |
+
)
|
| 614 |
+
yield (n, deg)
|
| 615 |
+
else:
|
| 616 |
+
for n in self._nodes:
|
| 617 |
+
nbrs = self._succ[n]
|
| 618 |
+
deg = sum(
|
| 619 |
+
d.get(weight, 1)
|
| 620 |
+
for key_dict in nbrs.values()
|
| 621 |
+
for d in key_dict.values()
|
| 622 |
+
)
|
| 623 |
+
if n in nbrs:
|
| 624 |
+
deg += sum(d.get(weight, 1) for d in nbrs[n].values())
|
| 625 |
+
yield (n, deg)
|
| 626 |
+
|
| 627 |
+
|
| 628 |
+
class DiMultiDegreeView(DiDegreeView):
|
| 629 |
+
"""A DegreeView class for MultiDiGraph; See DegreeView"""
|
| 630 |
+
|
| 631 |
+
def __getitem__(self, n):
|
| 632 |
+
weight = self._weight
|
| 633 |
+
succs = self._succ[n]
|
| 634 |
+
preds = self._pred[n]
|
| 635 |
+
if weight is None:
|
| 636 |
+
return sum(len(keys) for keys in succs.values()) + sum(
|
| 637 |
+
len(keys) for keys in preds.values()
|
| 638 |
+
)
|
| 639 |
+
# edge weighted graph - degree is sum of nbr edge weights
|
| 640 |
+
deg = sum(
|
| 641 |
+
d.get(weight, 1) for key_dict in succs.values() for d in key_dict.values()
|
| 642 |
+
) + sum(
|
| 643 |
+
d.get(weight, 1) for key_dict in preds.values() for d in key_dict.values()
|
| 644 |
+
)
|
| 645 |
+
return deg
|
| 646 |
+
|
| 647 |
+
def __iter__(self):
|
| 648 |
+
weight = self._weight
|
| 649 |
+
if weight is None:
|
| 650 |
+
for n in self._nodes:
|
| 651 |
+
succs = self._succ[n]
|
| 652 |
+
preds = self._pred[n]
|
| 653 |
+
deg = sum(len(keys) for keys in succs.values()) + sum(
|
| 654 |
+
len(keys) for keys in preds.values()
|
| 655 |
+
)
|
| 656 |
+
yield (n, deg)
|
| 657 |
+
else:
|
| 658 |
+
for n in self._nodes:
|
| 659 |
+
succs = self._succ[n]
|
| 660 |
+
preds = self._pred[n]
|
| 661 |
+
deg = sum(
|
| 662 |
+
d.get(weight, 1)
|
| 663 |
+
for key_dict in succs.values()
|
| 664 |
+
for d in key_dict.values()
|
| 665 |
+
) + sum(
|
| 666 |
+
d.get(weight, 1)
|
| 667 |
+
for key_dict in preds.values()
|
| 668 |
+
for d in key_dict.values()
|
| 669 |
+
)
|
| 670 |
+
yield (n, deg)
|
| 671 |
+
|
| 672 |
+
|
| 673 |
+
class InMultiDegreeView(DiDegreeView):
|
| 674 |
+
"""A DegreeView class for inward degree of MultiDiGraph; See DegreeView"""
|
| 675 |
+
|
| 676 |
+
def __getitem__(self, n):
|
| 677 |
+
weight = self._weight
|
| 678 |
+
nbrs = self._pred[n]
|
| 679 |
+
if weight is None:
|
| 680 |
+
return sum(len(data) for data in nbrs.values())
|
| 681 |
+
# edge weighted graph - degree is sum of nbr edge weights
|
| 682 |
+
return sum(
|
| 683 |
+
d.get(weight, 1) for key_dict in nbrs.values() for d in key_dict.values()
|
| 684 |
+
)
|
| 685 |
+
|
| 686 |
+
def __iter__(self):
|
| 687 |
+
weight = self._weight
|
| 688 |
+
if weight is None:
|
| 689 |
+
for n in self._nodes:
|
| 690 |
+
nbrs = self._pred[n]
|
| 691 |
+
deg = sum(len(data) for data in nbrs.values())
|
| 692 |
+
yield (n, deg)
|
| 693 |
+
else:
|
| 694 |
+
for n in self._nodes:
|
| 695 |
+
nbrs = self._pred[n]
|
| 696 |
+
deg = sum(
|
| 697 |
+
d.get(weight, 1)
|
| 698 |
+
for key_dict in nbrs.values()
|
| 699 |
+
for d in key_dict.values()
|
| 700 |
+
)
|
| 701 |
+
yield (n, deg)
|
| 702 |
+
|
| 703 |
+
|
| 704 |
+
class OutMultiDegreeView(DiDegreeView):
|
| 705 |
+
"""A DegreeView class for outward degree of MultiDiGraph; See DegreeView"""
|
| 706 |
+
|
| 707 |
+
def __getitem__(self, n):
|
| 708 |
+
weight = self._weight
|
| 709 |
+
nbrs = self._succ[n]
|
| 710 |
+
if weight is None:
|
| 711 |
+
return sum(len(data) for data in nbrs.values())
|
| 712 |
+
# edge weighted graph - degree is sum of nbr edge weights
|
| 713 |
+
return sum(
|
| 714 |
+
d.get(weight, 1) for key_dict in nbrs.values() for d in key_dict.values()
|
| 715 |
+
)
|
| 716 |
+
|
| 717 |
+
def __iter__(self):
|
| 718 |
+
weight = self._weight
|
| 719 |
+
if weight is None:
|
| 720 |
+
for n in self._nodes:
|
| 721 |
+
nbrs = self._succ[n]
|
| 722 |
+
deg = sum(len(data) for data in nbrs.values())
|
| 723 |
+
yield (n, deg)
|
| 724 |
+
else:
|
| 725 |
+
for n in self._nodes:
|
| 726 |
+
nbrs = self._succ[n]
|
| 727 |
+
deg = sum(
|
| 728 |
+
d.get(weight, 1)
|
| 729 |
+
for key_dict in nbrs.values()
|
| 730 |
+
for d in key_dict.values()
|
| 731 |
+
)
|
| 732 |
+
yield (n, deg)
|
| 733 |
+
|
| 734 |
+
|
| 735 |
+
# EdgeDataViews
|
| 736 |
+
class OutEdgeDataView:
|
| 737 |
+
"""EdgeDataView for outward edges of DiGraph; See EdgeDataView"""
|
| 738 |
+
|
| 739 |
+
__slots__ = (
|
| 740 |
+
"_viewer",
|
| 741 |
+
"_nbunch",
|
| 742 |
+
"_data",
|
| 743 |
+
"_default",
|
| 744 |
+
"_adjdict",
|
| 745 |
+
"_nodes_nbrs",
|
| 746 |
+
"_report",
|
| 747 |
+
)
|
| 748 |
+
|
| 749 |
+
def __getstate__(self):
|
| 750 |
+
return {
|
| 751 |
+
"viewer": self._viewer,
|
| 752 |
+
"nbunch": self._nbunch,
|
| 753 |
+
"data": self._data,
|
| 754 |
+
"default": self._default,
|
| 755 |
+
}
|
| 756 |
+
|
| 757 |
+
def __setstate__(self, state):
|
| 758 |
+
self.__init__(**state)
|
| 759 |
+
|
| 760 |
+
def __init__(self, viewer, nbunch=None, data=False, *, default=None):
|
| 761 |
+
self._viewer = viewer
|
| 762 |
+
adjdict = self._adjdict = viewer._adjdict
|
| 763 |
+
if nbunch is None:
|
| 764 |
+
self._nodes_nbrs = adjdict.items
|
| 765 |
+
else:
|
| 766 |
+
# dict retains order of nodes but acts like a set
|
| 767 |
+
nbunch = dict.fromkeys(viewer._graph.nbunch_iter(nbunch))
|
| 768 |
+
self._nodes_nbrs = lambda: [(n, adjdict[n]) for n in nbunch]
|
| 769 |
+
self._nbunch = nbunch
|
| 770 |
+
self._data = data
|
| 771 |
+
self._default = default
|
| 772 |
+
# Set _report based on data and default
|
| 773 |
+
if data is True:
|
| 774 |
+
self._report = lambda n, nbr, dd: (n, nbr, dd)
|
| 775 |
+
elif data is False:
|
| 776 |
+
self._report = lambda n, nbr, dd: (n, nbr)
|
| 777 |
+
else: # data is attribute name
|
| 778 |
+
self._report = (
|
| 779 |
+
lambda n, nbr, dd: (n, nbr, dd[data])
|
| 780 |
+
if data in dd
|
| 781 |
+
else (n, nbr, default)
|
| 782 |
+
)
|
| 783 |
+
|
| 784 |
+
def __len__(self):
|
| 785 |
+
return sum(len(nbrs) for n, nbrs in self._nodes_nbrs())
|
| 786 |
+
|
| 787 |
+
def __iter__(self):
|
| 788 |
+
return (
|
| 789 |
+
self._report(n, nbr, dd)
|
| 790 |
+
for n, nbrs in self._nodes_nbrs()
|
| 791 |
+
for nbr, dd in nbrs.items()
|
| 792 |
+
)
|
| 793 |
+
|
| 794 |
+
def __contains__(self, e):
|
| 795 |
+
u, v = e[:2]
|
| 796 |
+
if self._nbunch is not None and u not in self._nbunch:
|
| 797 |
+
return False # this edge doesn't start in nbunch
|
| 798 |
+
try:
|
| 799 |
+
ddict = self._adjdict[u][v]
|
| 800 |
+
except KeyError:
|
| 801 |
+
return False
|
| 802 |
+
return e == self._report(u, v, ddict)
|
| 803 |
+
|
| 804 |
+
def __str__(self):
|
| 805 |
+
return str(list(self))
|
| 806 |
+
|
| 807 |
+
def __repr__(self):
|
| 808 |
+
return f"{self.__class__.__name__}({list(self)})"
|
| 809 |
+
|
| 810 |
+
|
| 811 |
+
class EdgeDataView(OutEdgeDataView):
|
| 812 |
+
"""A EdgeDataView class for edges of Graph
|
| 813 |
+
|
| 814 |
+
This view is primarily used to iterate over the edges reporting
|
| 815 |
+
edges as node-tuples with edge data optionally reported. The
|
| 816 |
+
argument `nbunch` allows restriction to edges incident to nodes
|
| 817 |
+
in that container/singleton. The default (nbunch=None)
|
| 818 |
+
reports all edges. The arguments `data` and `default` control
|
| 819 |
+
what edge data is reported. The default `data is False` reports
|
| 820 |
+
only node-tuples for each edge. If `data is True` the entire edge
|
| 821 |
+
data dict is returned. Otherwise `data` is assumed to hold the name
|
| 822 |
+
of the edge attribute to report with default `default` if that
|
| 823 |
+
edge attribute is not present.
|
| 824 |
+
|
| 825 |
+
Parameters
|
| 826 |
+
----------
|
| 827 |
+
nbunch : container of nodes, node or None (default None)
|
| 828 |
+
data : False, True or string (default False)
|
| 829 |
+
default : default value (default None)
|
| 830 |
+
|
| 831 |
+
Examples
|
| 832 |
+
--------
|
| 833 |
+
>>> G = nx.path_graph(3)
|
| 834 |
+
>>> G.add_edge(1, 2, foo="bar")
|
| 835 |
+
>>> list(G.edges(data="foo", default="biz"))
|
| 836 |
+
[(0, 1, 'biz'), (1, 2, 'bar')]
|
| 837 |
+
>>> assert (0, 1, "biz") in G.edges(data="foo", default="biz")
|
| 838 |
+
"""
|
| 839 |
+
|
| 840 |
+
__slots__ = ()
|
| 841 |
+
|
| 842 |
+
def __len__(self):
|
| 843 |
+
return sum(1 for e in self)
|
| 844 |
+
|
| 845 |
+
def __iter__(self):
|
| 846 |
+
seen = {}
|
| 847 |
+
for n, nbrs in self._nodes_nbrs():
|
| 848 |
+
for nbr, dd in nbrs.items():
|
| 849 |
+
if nbr not in seen:
|
| 850 |
+
yield self._report(n, nbr, dd)
|
| 851 |
+
seen[n] = 1
|
| 852 |
+
del seen
|
| 853 |
+
|
| 854 |
+
def __contains__(self, e):
|
| 855 |
+
u, v = e[:2]
|
| 856 |
+
if self._nbunch is not None and u not in self._nbunch and v not in self._nbunch:
|
| 857 |
+
return False # this edge doesn't start and it doesn't end in nbunch
|
| 858 |
+
try:
|
| 859 |
+
ddict = self._adjdict[u][v]
|
| 860 |
+
except KeyError:
|
| 861 |
+
return False
|
| 862 |
+
return e == self._report(u, v, ddict)
|
| 863 |
+
|
| 864 |
+
|
| 865 |
+
class InEdgeDataView(OutEdgeDataView):
|
| 866 |
+
"""An EdgeDataView class for outward edges of DiGraph; See EdgeDataView"""
|
| 867 |
+
|
| 868 |
+
__slots__ = ()
|
| 869 |
+
|
| 870 |
+
def __iter__(self):
|
| 871 |
+
return (
|
| 872 |
+
self._report(nbr, n, dd)
|
| 873 |
+
for n, nbrs in self._nodes_nbrs()
|
| 874 |
+
for nbr, dd in nbrs.items()
|
| 875 |
+
)
|
| 876 |
+
|
| 877 |
+
def __contains__(self, e):
|
| 878 |
+
u, v = e[:2]
|
| 879 |
+
if self._nbunch is not None and v not in self._nbunch:
|
| 880 |
+
return False # this edge doesn't end in nbunch
|
| 881 |
+
try:
|
| 882 |
+
ddict = self._adjdict[v][u]
|
| 883 |
+
except KeyError:
|
| 884 |
+
return False
|
| 885 |
+
return e == self._report(u, v, ddict)
|
| 886 |
+
|
| 887 |
+
|
| 888 |
+
class OutMultiEdgeDataView(OutEdgeDataView):
|
| 889 |
+
"""An EdgeDataView for outward edges of MultiDiGraph; See EdgeDataView"""
|
| 890 |
+
|
| 891 |
+
__slots__ = ("keys",)
|
| 892 |
+
|
| 893 |
+
def __getstate__(self):
|
| 894 |
+
return {
|
| 895 |
+
"viewer": self._viewer,
|
| 896 |
+
"nbunch": self._nbunch,
|
| 897 |
+
"keys": self.keys,
|
| 898 |
+
"data": self._data,
|
| 899 |
+
"default": self._default,
|
| 900 |
+
}
|
| 901 |
+
|
| 902 |
+
def __setstate__(self, state):
|
| 903 |
+
self.__init__(**state)
|
| 904 |
+
|
| 905 |
+
def __init__(self, viewer, nbunch=None, data=False, *, default=None, keys=False):
|
| 906 |
+
self._viewer = viewer
|
| 907 |
+
adjdict = self._adjdict = viewer._adjdict
|
| 908 |
+
self.keys = keys
|
| 909 |
+
if nbunch is None:
|
| 910 |
+
self._nodes_nbrs = adjdict.items
|
| 911 |
+
else:
|
| 912 |
+
# dict retains order of nodes but acts like a set
|
| 913 |
+
nbunch = dict.fromkeys(viewer._graph.nbunch_iter(nbunch))
|
| 914 |
+
self._nodes_nbrs = lambda: [(n, adjdict[n]) for n in nbunch]
|
| 915 |
+
self._nbunch = nbunch
|
| 916 |
+
self._data = data
|
| 917 |
+
self._default = default
|
| 918 |
+
# Set _report based on data and default
|
| 919 |
+
if data is True:
|
| 920 |
+
if keys is True:
|
| 921 |
+
self._report = lambda n, nbr, k, dd: (n, nbr, k, dd)
|
| 922 |
+
else:
|
| 923 |
+
self._report = lambda n, nbr, k, dd: (n, nbr, dd)
|
| 924 |
+
elif data is False:
|
| 925 |
+
if keys is True:
|
| 926 |
+
self._report = lambda n, nbr, k, dd: (n, nbr, k)
|
| 927 |
+
else:
|
| 928 |
+
self._report = lambda n, nbr, k, dd: (n, nbr)
|
| 929 |
+
else: # data is attribute name
|
| 930 |
+
if keys is True:
|
| 931 |
+
self._report = (
|
| 932 |
+
lambda n, nbr, k, dd: (n, nbr, k, dd[data])
|
| 933 |
+
if data in dd
|
| 934 |
+
else (n, nbr, k, default)
|
| 935 |
+
)
|
| 936 |
+
else:
|
| 937 |
+
self._report = (
|
| 938 |
+
lambda n, nbr, k, dd: (n, nbr, dd[data])
|
| 939 |
+
if data in dd
|
| 940 |
+
else (n, nbr, default)
|
| 941 |
+
)
|
| 942 |
+
|
| 943 |
+
def __len__(self):
|
| 944 |
+
return sum(1 for e in self)
|
| 945 |
+
|
| 946 |
+
def __iter__(self):
|
| 947 |
+
return (
|
| 948 |
+
self._report(n, nbr, k, dd)
|
| 949 |
+
for n, nbrs in self._nodes_nbrs()
|
| 950 |
+
for nbr, kd in nbrs.items()
|
| 951 |
+
for k, dd in kd.items()
|
| 952 |
+
)
|
| 953 |
+
|
| 954 |
+
def __contains__(self, e):
|
| 955 |
+
u, v = e[:2]
|
| 956 |
+
if self._nbunch is not None and u not in self._nbunch:
|
| 957 |
+
return False # this edge doesn't start in nbunch
|
| 958 |
+
try:
|
| 959 |
+
kdict = self._adjdict[u][v]
|
| 960 |
+
except KeyError:
|
| 961 |
+
return False
|
| 962 |
+
if self.keys is True:
|
| 963 |
+
k = e[2]
|
| 964 |
+
try:
|
| 965 |
+
dd = kdict[k]
|
| 966 |
+
except KeyError:
|
| 967 |
+
return False
|
| 968 |
+
return e == self._report(u, v, k, dd)
|
| 969 |
+
return any(e == self._report(u, v, k, dd) for k, dd in kdict.items())
|
| 970 |
+
|
| 971 |
+
|
| 972 |
+
class MultiEdgeDataView(OutMultiEdgeDataView):
|
| 973 |
+
"""An EdgeDataView class for edges of MultiGraph; See EdgeDataView"""
|
| 974 |
+
|
| 975 |
+
__slots__ = ()
|
| 976 |
+
|
| 977 |
+
def __iter__(self):
|
| 978 |
+
seen = {}
|
| 979 |
+
for n, nbrs in self._nodes_nbrs():
|
| 980 |
+
for nbr, kd in nbrs.items():
|
| 981 |
+
if nbr not in seen:
|
| 982 |
+
for k, dd in kd.items():
|
| 983 |
+
yield self._report(n, nbr, k, dd)
|
| 984 |
+
seen[n] = 1
|
| 985 |
+
del seen
|
| 986 |
+
|
| 987 |
+
def __contains__(self, e):
|
| 988 |
+
u, v = e[:2]
|
| 989 |
+
if self._nbunch is not None and u not in self._nbunch and v not in self._nbunch:
|
| 990 |
+
return False # this edge doesn't start and doesn't end in nbunch
|
| 991 |
+
try:
|
| 992 |
+
kdict = self._adjdict[u][v]
|
| 993 |
+
except KeyError:
|
| 994 |
+
try:
|
| 995 |
+
kdict = self._adjdict[v][u]
|
| 996 |
+
except KeyError:
|
| 997 |
+
return False
|
| 998 |
+
if self.keys is True:
|
| 999 |
+
k = e[2]
|
| 1000 |
+
try:
|
| 1001 |
+
dd = kdict[k]
|
| 1002 |
+
except KeyError:
|
| 1003 |
+
return False
|
| 1004 |
+
return e == self._report(u, v, k, dd)
|
| 1005 |
+
return any(e == self._report(u, v, k, dd) for k, dd in kdict.items())
|
| 1006 |
+
|
| 1007 |
+
|
| 1008 |
+
class InMultiEdgeDataView(OutMultiEdgeDataView):
|
| 1009 |
+
"""An EdgeDataView for inward edges of MultiDiGraph; See EdgeDataView"""
|
| 1010 |
+
|
| 1011 |
+
__slots__ = ()
|
| 1012 |
+
|
| 1013 |
+
def __iter__(self):
|
| 1014 |
+
return (
|
| 1015 |
+
self._report(nbr, n, k, dd)
|
| 1016 |
+
for n, nbrs in self._nodes_nbrs()
|
| 1017 |
+
for nbr, kd in nbrs.items()
|
| 1018 |
+
for k, dd in kd.items()
|
| 1019 |
+
)
|
| 1020 |
+
|
| 1021 |
+
def __contains__(self, e):
|
| 1022 |
+
u, v = e[:2]
|
| 1023 |
+
if self._nbunch is not None and v not in self._nbunch:
|
| 1024 |
+
return False # this edge doesn't end in nbunch
|
| 1025 |
+
try:
|
| 1026 |
+
kdict = self._adjdict[v][u]
|
| 1027 |
+
except KeyError:
|
| 1028 |
+
return False
|
| 1029 |
+
if self.keys is True:
|
| 1030 |
+
k = e[2]
|
| 1031 |
+
dd = kdict[k]
|
| 1032 |
+
return e == self._report(u, v, k, dd)
|
| 1033 |
+
return any(e == self._report(u, v, k, dd) for k, dd in kdict.items())
|
| 1034 |
+
|
| 1035 |
+
|
| 1036 |
+
# EdgeViews have set operations and no data reported
|
| 1037 |
+
class OutEdgeView(Set, Mapping):
|
| 1038 |
+
"""A EdgeView class for outward edges of a DiGraph"""
|
| 1039 |
+
|
| 1040 |
+
__slots__ = ("_adjdict", "_graph", "_nodes_nbrs")
|
| 1041 |
+
|
| 1042 |
+
def __getstate__(self):
|
| 1043 |
+
return {"_graph": self._graph, "_adjdict": self._adjdict}
|
| 1044 |
+
|
| 1045 |
+
def __setstate__(self, state):
|
| 1046 |
+
self._graph = state["_graph"]
|
| 1047 |
+
self._adjdict = state["_adjdict"]
|
| 1048 |
+
self._nodes_nbrs = self._adjdict.items
|
| 1049 |
+
|
| 1050 |
+
@classmethod
|
| 1051 |
+
def _from_iterable(cls, it):
|
| 1052 |
+
return set(it)
|
| 1053 |
+
|
| 1054 |
+
dataview = OutEdgeDataView
|
| 1055 |
+
|
| 1056 |
+
def __init__(self, G):
|
| 1057 |
+
self._graph = G
|
| 1058 |
+
self._adjdict = G._succ if hasattr(G, "succ") else G._adj
|
| 1059 |
+
self._nodes_nbrs = self._adjdict.items
|
| 1060 |
+
|
| 1061 |
+
# Set methods
|
| 1062 |
+
def __len__(self):
|
| 1063 |
+
return sum(len(nbrs) for n, nbrs in self._nodes_nbrs())
|
| 1064 |
+
|
| 1065 |
+
def __iter__(self):
|
| 1066 |
+
for n, nbrs in self._nodes_nbrs():
|
| 1067 |
+
for nbr in nbrs:
|
| 1068 |
+
yield (n, nbr)
|
| 1069 |
+
|
| 1070 |
+
def __contains__(self, e):
|
| 1071 |
+
try:
|
| 1072 |
+
u, v = e
|
| 1073 |
+
return v in self._adjdict[u]
|
| 1074 |
+
except KeyError:
|
| 1075 |
+
return False
|
| 1076 |
+
|
| 1077 |
+
# Mapping Methods
|
| 1078 |
+
def __getitem__(self, e):
|
| 1079 |
+
if isinstance(e, slice):
|
| 1080 |
+
raise nx.NetworkXError(
|
| 1081 |
+
f"{type(self).__name__} does not support slicing, "
|
| 1082 |
+
f"try list(G.edges)[{e.start}:{e.stop}:{e.step}]"
|
| 1083 |
+
)
|
| 1084 |
+
u, v = e
|
| 1085 |
+
return self._adjdict[u][v]
|
| 1086 |
+
|
| 1087 |
+
# EdgeDataView methods
|
| 1088 |
+
def __call__(self, nbunch=None, data=False, *, default=None):
|
| 1089 |
+
if nbunch is None and data is False:
|
| 1090 |
+
return self
|
| 1091 |
+
return self.dataview(self, nbunch, data, default=default)
|
| 1092 |
+
|
| 1093 |
+
def data(self, data=True, default=None, nbunch=None):
|
| 1094 |
+
"""
|
| 1095 |
+
Return a read-only view of edge data.
|
| 1096 |
+
|
| 1097 |
+
Parameters
|
| 1098 |
+
----------
|
| 1099 |
+
data : bool or edge attribute key
|
| 1100 |
+
If ``data=True``, then the data view maps each edge to a dictionary
|
| 1101 |
+
containing all of its attributes. If `data` is a key in the edge
|
| 1102 |
+
dictionary, then the data view maps each edge to its value for
|
| 1103 |
+
the keyed attribute. In this case, if the edge doesn't have the
|
| 1104 |
+
attribute, the `default` value is returned.
|
| 1105 |
+
default : object, default=None
|
| 1106 |
+
The value used when an edge does not have a specific attribute
|
| 1107 |
+
nbunch : container of nodes, optional (default=None)
|
| 1108 |
+
Allows restriction to edges only involving certain nodes. All edges
|
| 1109 |
+
are considered by default.
|
| 1110 |
+
|
| 1111 |
+
Returns
|
| 1112 |
+
-------
|
| 1113 |
+
dataview
|
| 1114 |
+
Returns an `EdgeDataView` for undirected Graphs, `OutEdgeDataView`
|
| 1115 |
+
for DiGraphs, `MultiEdgeDataView` for MultiGraphs and
|
| 1116 |
+
`OutMultiEdgeDataView` for MultiDiGraphs.
|
| 1117 |
+
|
| 1118 |
+
Notes
|
| 1119 |
+
-----
|
| 1120 |
+
If ``data=False``, returns an `EdgeView` without any edge data.
|
| 1121 |
+
|
| 1122 |
+
See Also
|
| 1123 |
+
--------
|
| 1124 |
+
EdgeDataView
|
| 1125 |
+
OutEdgeDataView
|
| 1126 |
+
MultiEdgeDataView
|
| 1127 |
+
OutMultiEdgeDataView
|
| 1128 |
+
|
| 1129 |
+
Examples
|
| 1130 |
+
--------
|
| 1131 |
+
>>> G = nx.Graph()
|
| 1132 |
+
>>> G.add_edges_from([
|
| 1133 |
+
... (0, 1, {"dist": 3, "capacity": 20}),
|
| 1134 |
+
... (1, 2, {"dist": 4}),
|
| 1135 |
+
... (2, 0, {"dist": 5})
|
| 1136 |
+
... ])
|
| 1137 |
+
|
| 1138 |
+
Accessing edge data with ``data=True`` (the default) returns an
|
| 1139 |
+
edge data view object listing each edge with all of its attributes:
|
| 1140 |
+
|
| 1141 |
+
>>> G.edges.data()
|
| 1142 |
+
EdgeDataView([(0, 1, {'dist': 3, 'capacity': 20}), (0, 2, {'dist': 5}), (1, 2, {'dist': 4})])
|
| 1143 |
+
|
| 1144 |
+
If `data` represents a key in the edge attribute dict, a dataview listing
|
| 1145 |
+
each edge with its value for that specific key is returned:
|
| 1146 |
+
|
| 1147 |
+
>>> G.edges.data("dist")
|
| 1148 |
+
EdgeDataView([(0, 1, 3), (0, 2, 5), (1, 2, 4)])
|
| 1149 |
+
|
| 1150 |
+
`nbunch` can be used to limit the edges:
|
| 1151 |
+
|
| 1152 |
+
>>> G.edges.data("dist", nbunch=[0])
|
| 1153 |
+
EdgeDataView([(0, 1, 3), (0, 2, 5)])
|
| 1154 |
+
|
| 1155 |
+
If a specific key is not found in an edge attribute dict, the value
|
| 1156 |
+
specified by `default` is used:
|
| 1157 |
+
|
| 1158 |
+
>>> G.edges.data("capacity")
|
| 1159 |
+
EdgeDataView([(0, 1, 20), (0, 2, None), (1, 2, None)])
|
| 1160 |
+
|
| 1161 |
+
Note that there is no check that the `data` key is present in any of
|
| 1162 |
+
the edge attribute dictionaries:
|
| 1163 |
+
|
| 1164 |
+
>>> G.edges.data("speed")
|
| 1165 |
+
EdgeDataView([(0, 1, None), (0, 2, None), (1, 2, None)])
|
| 1166 |
+
"""
|
| 1167 |
+
if nbunch is None and data is False:
|
| 1168 |
+
return self
|
| 1169 |
+
return self.dataview(self, nbunch, data, default=default)
|
| 1170 |
+
|
| 1171 |
+
# String Methods
|
| 1172 |
+
def __str__(self):
|
| 1173 |
+
return str(list(self))
|
| 1174 |
+
|
| 1175 |
+
def __repr__(self):
|
| 1176 |
+
return f"{self.__class__.__name__}({list(self)})"
|
| 1177 |
+
|
| 1178 |
+
|
| 1179 |
+
class EdgeView(OutEdgeView):
|
| 1180 |
+
"""A EdgeView class for edges of a Graph
|
| 1181 |
+
|
| 1182 |
+
This densely packed View allows iteration over edges, data lookup
|
| 1183 |
+
like a dict and set operations on edges represented by node-tuples.
|
| 1184 |
+
In addition, edge data can be controlled by calling this object
|
| 1185 |
+
possibly creating an EdgeDataView. Typically edges are iterated over
|
| 1186 |
+
and reported as `(u, v)` node tuples or `(u, v, key)` node/key tuples
|
| 1187 |
+
for multigraphs. Those edge representations can also be using to
|
| 1188 |
+
lookup the data dict for any edge. Set operations also are available
|
| 1189 |
+
where those tuples are the elements of the set.
|
| 1190 |
+
Calling this object with optional arguments `data`, `default` and `keys`
|
| 1191 |
+
controls the form of the tuple (see EdgeDataView). Optional argument
|
| 1192 |
+
`nbunch` allows restriction to edges only involving certain nodes.
|
| 1193 |
+
|
| 1194 |
+
If `data is False` (the default) then iterate over 2-tuples `(u, v)`.
|
| 1195 |
+
If `data is True` iterate over 3-tuples `(u, v, datadict)`.
|
| 1196 |
+
Otherwise iterate over `(u, v, datadict.get(data, default))`.
|
| 1197 |
+
For Multigraphs, if `keys is True`, replace `u, v` with `u, v, key` above.
|
| 1198 |
+
|
| 1199 |
+
Parameters
|
| 1200 |
+
==========
|
| 1201 |
+
graph : NetworkX graph-like class
|
| 1202 |
+
nbunch : (default= all nodes in graph) only report edges with these nodes
|
| 1203 |
+
keys : (only for MultiGraph. default=False) report edge key in tuple
|
| 1204 |
+
data : bool or string (default=False) see above
|
| 1205 |
+
default : object (default=None)
|
| 1206 |
+
|
| 1207 |
+
Examples
|
| 1208 |
+
========
|
| 1209 |
+
>>> G = nx.path_graph(4)
|
| 1210 |
+
>>> EV = G.edges()
|
| 1211 |
+
>>> (2, 3) in EV
|
| 1212 |
+
True
|
| 1213 |
+
>>> for u, v in EV:
|
| 1214 |
+
... print((u, v))
|
| 1215 |
+
(0, 1)
|
| 1216 |
+
(1, 2)
|
| 1217 |
+
(2, 3)
|
| 1218 |
+
>>> assert EV & {(1, 2), (3, 4)} == {(1, 2)}
|
| 1219 |
+
|
| 1220 |
+
>>> EVdata = G.edges(data="color", default="aqua")
|
| 1221 |
+
>>> G.add_edge(2, 3, color="blue")
|
| 1222 |
+
>>> assert (2, 3, "blue") in EVdata
|
| 1223 |
+
>>> for u, v, c in EVdata:
|
| 1224 |
+
... print(f"({u}, {v}) has color: {c}")
|
| 1225 |
+
(0, 1) has color: aqua
|
| 1226 |
+
(1, 2) has color: aqua
|
| 1227 |
+
(2, 3) has color: blue
|
| 1228 |
+
|
| 1229 |
+
>>> EVnbunch = G.edges(nbunch=2)
|
| 1230 |
+
>>> assert (2, 3) in EVnbunch
|
| 1231 |
+
>>> assert (0, 1) not in EVnbunch
|
| 1232 |
+
>>> for u, v in EVnbunch:
|
| 1233 |
+
... assert u == 2 or v == 2
|
| 1234 |
+
|
| 1235 |
+
>>> MG = nx.path_graph(4, create_using=nx.MultiGraph)
|
| 1236 |
+
>>> EVmulti = MG.edges(keys=True)
|
| 1237 |
+
>>> (2, 3, 0) in EVmulti
|
| 1238 |
+
True
|
| 1239 |
+
>>> (2, 3) in EVmulti # 2-tuples work even when keys is True
|
| 1240 |
+
True
|
| 1241 |
+
>>> key = MG.add_edge(2, 3)
|
| 1242 |
+
>>> for u, v, k in EVmulti:
|
| 1243 |
+
... print((u, v, k))
|
| 1244 |
+
(0, 1, 0)
|
| 1245 |
+
(1, 2, 0)
|
| 1246 |
+
(2, 3, 0)
|
| 1247 |
+
(2, 3, 1)
|
| 1248 |
+
"""
|
| 1249 |
+
|
| 1250 |
+
__slots__ = ()
|
| 1251 |
+
|
| 1252 |
+
dataview = EdgeDataView
|
| 1253 |
+
|
| 1254 |
+
def __len__(self):
|
| 1255 |
+
num_nbrs = (len(nbrs) + (n in nbrs) for n, nbrs in self._nodes_nbrs())
|
| 1256 |
+
return sum(num_nbrs) // 2
|
| 1257 |
+
|
| 1258 |
+
def __iter__(self):
|
| 1259 |
+
seen = {}
|
| 1260 |
+
for n, nbrs in self._nodes_nbrs():
|
| 1261 |
+
for nbr in list(nbrs):
|
| 1262 |
+
if nbr not in seen:
|
| 1263 |
+
yield (n, nbr)
|
| 1264 |
+
seen[n] = 1
|
| 1265 |
+
del seen
|
| 1266 |
+
|
| 1267 |
+
def __contains__(self, e):
|
| 1268 |
+
try:
|
| 1269 |
+
u, v = e[:2]
|
| 1270 |
+
return v in self._adjdict[u] or u in self._adjdict[v]
|
| 1271 |
+
except (KeyError, ValueError):
|
| 1272 |
+
return False
|
| 1273 |
+
|
| 1274 |
+
|
| 1275 |
+
class InEdgeView(OutEdgeView):
|
| 1276 |
+
"""A EdgeView class for inward edges of a DiGraph"""
|
| 1277 |
+
|
| 1278 |
+
__slots__ = ()
|
| 1279 |
+
|
| 1280 |
+
def __setstate__(self, state):
|
| 1281 |
+
self._graph = state["_graph"]
|
| 1282 |
+
self._adjdict = state["_adjdict"]
|
| 1283 |
+
self._nodes_nbrs = self._adjdict.items
|
| 1284 |
+
|
| 1285 |
+
dataview = InEdgeDataView
|
| 1286 |
+
|
| 1287 |
+
def __init__(self, G):
|
| 1288 |
+
self._graph = G
|
| 1289 |
+
self._adjdict = G._pred if hasattr(G, "pred") else G._adj
|
| 1290 |
+
self._nodes_nbrs = self._adjdict.items
|
| 1291 |
+
|
| 1292 |
+
def __iter__(self):
|
| 1293 |
+
for n, nbrs in self._nodes_nbrs():
|
| 1294 |
+
for nbr in nbrs:
|
| 1295 |
+
yield (nbr, n)
|
| 1296 |
+
|
| 1297 |
+
def __contains__(self, e):
|
| 1298 |
+
try:
|
| 1299 |
+
u, v = e
|
| 1300 |
+
return u in self._adjdict[v]
|
| 1301 |
+
except KeyError:
|
| 1302 |
+
return False
|
| 1303 |
+
|
| 1304 |
+
def __getitem__(self, e):
|
| 1305 |
+
if isinstance(e, slice):
|
| 1306 |
+
raise nx.NetworkXError(
|
| 1307 |
+
f"{type(self).__name__} does not support slicing, "
|
| 1308 |
+
f"try list(G.in_edges)[{e.start}:{e.stop}:{e.step}]"
|
| 1309 |
+
)
|
| 1310 |
+
u, v = e
|
| 1311 |
+
return self._adjdict[v][u]
|
| 1312 |
+
|
| 1313 |
+
|
| 1314 |
+
class OutMultiEdgeView(OutEdgeView):
|
| 1315 |
+
"""A EdgeView class for outward edges of a MultiDiGraph"""
|
| 1316 |
+
|
| 1317 |
+
__slots__ = ()
|
| 1318 |
+
|
| 1319 |
+
dataview = OutMultiEdgeDataView
|
| 1320 |
+
|
| 1321 |
+
def __len__(self):
|
| 1322 |
+
return sum(
|
| 1323 |
+
len(kdict) for n, nbrs in self._nodes_nbrs() for nbr, kdict in nbrs.items()
|
| 1324 |
+
)
|
| 1325 |
+
|
| 1326 |
+
def __iter__(self):
|
| 1327 |
+
for n, nbrs in self._nodes_nbrs():
|
| 1328 |
+
for nbr, kdict in nbrs.items():
|
| 1329 |
+
for key in kdict:
|
| 1330 |
+
yield (n, nbr, key)
|
| 1331 |
+
|
| 1332 |
+
def __contains__(self, e):
|
| 1333 |
+
N = len(e)
|
| 1334 |
+
if N == 3:
|
| 1335 |
+
u, v, k = e
|
| 1336 |
+
elif N == 2:
|
| 1337 |
+
u, v = e
|
| 1338 |
+
k = 0
|
| 1339 |
+
else:
|
| 1340 |
+
raise ValueError("MultiEdge must have length 2 or 3")
|
| 1341 |
+
try:
|
| 1342 |
+
return k in self._adjdict[u][v]
|
| 1343 |
+
except KeyError:
|
| 1344 |
+
return False
|
| 1345 |
+
|
| 1346 |
+
def __getitem__(self, e):
|
| 1347 |
+
if isinstance(e, slice):
|
| 1348 |
+
raise nx.NetworkXError(
|
| 1349 |
+
f"{type(self).__name__} does not support slicing, "
|
| 1350 |
+
f"try list(G.edges)[{e.start}:{e.stop}:{e.step}]"
|
| 1351 |
+
)
|
| 1352 |
+
u, v, k = e
|
| 1353 |
+
return self._adjdict[u][v][k]
|
| 1354 |
+
|
| 1355 |
+
def __call__(self, nbunch=None, data=False, *, default=None, keys=False):
|
| 1356 |
+
if nbunch is None and data is False and keys is True:
|
| 1357 |
+
return self
|
| 1358 |
+
return self.dataview(self, nbunch, data, default=default, keys=keys)
|
| 1359 |
+
|
| 1360 |
+
def data(self, data=True, default=None, nbunch=None, keys=False):
|
| 1361 |
+
if nbunch is None and data is False and keys is True:
|
| 1362 |
+
return self
|
| 1363 |
+
return self.dataview(self, nbunch, data, default=default, keys=keys)
|
| 1364 |
+
|
| 1365 |
+
|
| 1366 |
+
class MultiEdgeView(OutMultiEdgeView):
|
| 1367 |
+
"""A EdgeView class for edges of a MultiGraph"""
|
| 1368 |
+
|
| 1369 |
+
__slots__ = ()
|
| 1370 |
+
|
| 1371 |
+
dataview = MultiEdgeDataView
|
| 1372 |
+
|
| 1373 |
+
def __len__(self):
|
| 1374 |
+
return sum(1 for e in self)
|
| 1375 |
+
|
| 1376 |
+
def __iter__(self):
|
| 1377 |
+
seen = {}
|
| 1378 |
+
for n, nbrs in self._nodes_nbrs():
|
| 1379 |
+
for nbr, kd in nbrs.items():
|
| 1380 |
+
if nbr not in seen:
|
| 1381 |
+
for k, dd in kd.items():
|
| 1382 |
+
yield (n, nbr, k)
|
| 1383 |
+
seen[n] = 1
|
| 1384 |
+
del seen
|
| 1385 |
+
|
| 1386 |
+
|
| 1387 |
+
class InMultiEdgeView(OutMultiEdgeView):
|
| 1388 |
+
"""A EdgeView class for inward edges of a MultiDiGraph"""
|
| 1389 |
+
|
| 1390 |
+
__slots__ = ()
|
| 1391 |
+
|
| 1392 |
+
def __setstate__(self, state):
|
| 1393 |
+
self._graph = state["_graph"]
|
| 1394 |
+
self._adjdict = state["_adjdict"]
|
| 1395 |
+
self._nodes_nbrs = self._adjdict.items
|
| 1396 |
+
|
| 1397 |
+
dataview = InMultiEdgeDataView
|
| 1398 |
+
|
| 1399 |
+
def __init__(self, G):
|
| 1400 |
+
self._graph = G
|
| 1401 |
+
self._adjdict = G._pred if hasattr(G, "pred") else G._adj
|
| 1402 |
+
self._nodes_nbrs = self._adjdict.items
|
| 1403 |
+
|
| 1404 |
+
def __iter__(self):
|
| 1405 |
+
for n, nbrs in self._nodes_nbrs():
|
| 1406 |
+
for nbr, kdict in nbrs.items():
|
| 1407 |
+
for key in kdict:
|
| 1408 |
+
yield (nbr, n, key)
|
| 1409 |
+
|
| 1410 |
+
def __contains__(self, e):
|
| 1411 |
+
N = len(e)
|
| 1412 |
+
if N == 3:
|
| 1413 |
+
u, v, k = e
|
| 1414 |
+
elif N == 2:
|
| 1415 |
+
u, v = e
|
| 1416 |
+
k = 0
|
| 1417 |
+
else:
|
| 1418 |
+
raise ValueError("MultiEdge must have length 2 or 3")
|
| 1419 |
+
try:
|
| 1420 |
+
return k in self._adjdict[v][u]
|
| 1421 |
+
except KeyError:
|
| 1422 |
+
return False
|
| 1423 |
+
|
| 1424 |
+
def __getitem__(self, e):
|
| 1425 |
+
if isinstance(e, slice):
|
| 1426 |
+
raise nx.NetworkXError(
|
| 1427 |
+
f"{type(self).__name__} does not support slicing, "
|
| 1428 |
+
f"try list(G.in_edges)[{e.start}:{e.stop}:{e.step}]"
|
| 1429 |
+
)
|
| 1430 |
+
u, v, k = e
|
| 1431 |
+
return self._adjdict[v][u][k]
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/__init__.py
ADDED
|
File without changes
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/__pycache__/dispatch_interface.cpython-311.pyc
ADDED
|
Binary file (10.2 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/__pycache__/historical_tests.cpython-311.pyc
ADDED
|
Binary file (31.9 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/__pycache__/test_digraph.cpython-311.pyc
ADDED
|
Binary file (29.9 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/__pycache__/test_digraph_historical.cpython-311.pyc
ADDED
|
Binary file (10.2 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/__pycache__/test_filters.cpython-311.pyc
ADDED
|
Binary file (12.1 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/__pycache__/test_function.cpython-311.pyc
ADDED
|
Binary file (52.6 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/__pycache__/test_graph.cpython-311.pyc
ADDED
|
Binary file (68.3 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/__pycache__/test_subgraphviews.cpython-311.pyc
ADDED
|
Binary file (26.4 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/dispatch_interface.py
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This file contains utilities for testing the dispatching feature
|
| 2 |
+
|
| 3 |
+
# A full test of all dispatchable algorithms is performed by
|
| 4 |
+
# modifying the pytest invocation and setting an environment variable
|
| 5 |
+
# NETWORKX_TEST_BACKEND=nx-loopback pytest
|
| 6 |
+
# This is comprehensive, but only tests the `test_override_dispatch`
|
| 7 |
+
# function in networkx.classes.backends.
|
| 8 |
+
|
| 9 |
+
# To test the `_dispatch` function directly, several tests scattered throughout
|
| 10 |
+
# NetworkX have been augmented to test normal and dispatch mode.
|
| 11 |
+
# Searching for `dispatch_interface` should locate the specific tests.
|
| 12 |
+
|
| 13 |
+
import networkx as nx
|
| 14 |
+
from networkx import DiGraph, Graph, MultiDiGraph, MultiGraph, PlanarEmbedding
|
| 15 |
+
from networkx.classes.reportviews import NodeView
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class LoopbackGraph(Graph):
|
| 19 |
+
__networkx_backend__ = "nx-loopback"
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class LoopbackDiGraph(DiGraph):
|
| 23 |
+
__networkx_backend__ = "nx-loopback"
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
class LoopbackMultiGraph(MultiGraph):
|
| 27 |
+
__networkx_backend__ = "nx-loopback"
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
class LoopbackMultiDiGraph(MultiDiGraph):
|
| 31 |
+
__networkx_backend__ = "nx-loopback"
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class LoopbackPlanarEmbedding(PlanarEmbedding):
|
| 35 |
+
__networkx_backend__ = "nx-loopback"
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def convert(graph):
|
| 39 |
+
if isinstance(graph, PlanarEmbedding):
|
| 40 |
+
return LoopbackPlanarEmbedding(graph)
|
| 41 |
+
if isinstance(graph, MultiDiGraph):
|
| 42 |
+
return LoopbackMultiDiGraph(graph)
|
| 43 |
+
if isinstance(graph, MultiGraph):
|
| 44 |
+
return LoopbackMultiGraph(graph)
|
| 45 |
+
if isinstance(graph, DiGraph):
|
| 46 |
+
return LoopbackDiGraph(graph)
|
| 47 |
+
if isinstance(graph, Graph):
|
| 48 |
+
return LoopbackGraph(graph)
|
| 49 |
+
raise TypeError(f"Unsupported type of graph: {type(graph)}")
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
class LoopbackDispatcher:
|
| 53 |
+
def __getattr__(self, item):
|
| 54 |
+
try:
|
| 55 |
+
return nx.utils.backends._registered_algorithms[item].orig_func
|
| 56 |
+
except KeyError:
|
| 57 |
+
raise AttributeError(item) from None
|
| 58 |
+
|
| 59 |
+
@staticmethod
|
| 60 |
+
def convert_from_nx(
|
| 61 |
+
graph,
|
| 62 |
+
*,
|
| 63 |
+
edge_attrs=None,
|
| 64 |
+
node_attrs=None,
|
| 65 |
+
preserve_edge_attrs=None,
|
| 66 |
+
preserve_node_attrs=None,
|
| 67 |
+
preserve_graph_attrs=None,
|
| 68 |
+
name=None,
|
| 69 |
+
graph_name=None,
|
| 70 |
+
):
|
| 71 |
+
if name in {
|
| 72 |
+
# Raise if input graph changes
|
| 73 |
+
"lexicographical_topological_sort",
|
| 74 |
+
"topological_generations",
|
| 75 |
+
"topological_sort",
|
| 76 |
+
# Sensitive tests (iteration order matters)
|
| 77 |
+
"dfs_labeled_edges",
|
| 78 |
+
}:
|
| 79 |
+
return graph
|
| 80 |
+
if isinstance(graph, NodeView):
|
| 81 |
+
# Convert to a Graph with only nodes (no edges)
|
| 82 |
+
new_graph = Graph()
|
| 83 |
+
new_graph.add_nodes_from(graph.items())
|
| 84 |
+
graph = new_graph
|
| 85 |
+
G = LoopbackGraph()
|
| 86 |
+
elif not isinstance(graph, Graph):
|
| 87 |
+
raise TypeError(
|
| 88 |
+
f"Bad type for graph argument {graph_name} in {name}: {type(graph)}"
|
| 89 |
+
)
|
| 90 |
+
elif graph.__class__ in {Graph, LoopbackGraph}:
|
| 91 |
+
G = LoopbackGraph()
|
| 92 |
+
elif graph.__class__ in {DiGraph, LoopbackDiGraph}:
|
| 93 |
+
G = LoopbackDiGraph()
|
| 94 |
+
elif graph.__class__ in {MultiGraph, LoopbackMultiGraph}:
|
| 95 |
+
G = LoopbackMultiGraph()
|
| 96 |
+
elif graph.__class__ in {MultiDiGraph, LoopbackMultiDiGraph}:
|
| 97 |
+
G = LoopbackMultiDiGraph()
|
| 98 |
+
elif graph.__class__ in {PlanarEmbedding, LoopbackPlanarEmbedding}:
|
| 99 |
+
G = LoopbackDiGraph() # or LoopbackPlanarEmbedding
|
| 100 |
+
else:
|
| 101 |
+
# It would be nice to be able to convert _AntiGraph to a regular Graph
|
| 102 |
+
# nx.algorithms.approximation.kcomponents._AntiGraph
|
| 103 |
+
# nx.algorithms.tree.branchings.MultiDiGraph_EdgeKey
|
| 104 |
+
# nx.classes.tests.test_multidigraph.MultiDiGraphSubClass
|
| 105 |
+
# nx.classes.tests.test_multigraph.MultiGraphSubClass
|
| 106 |
+
G = graph.__class__()
|
| 107 |
+
|
| 108 |
+
if preserve_graph_attrs:
|
| 109 |
+
G.graph.update(graph.graph)
|
| 110 |
+
|
| 111 |
+
if preserve_node_attrs:
|
| 112 |
+
G.add_nodes_from(graph.nodes(data=True))
|
| 113 |
+
elif node_attrs:
|
| 114 |
+
G.add_nodes_from(
|
| 115 |
+
(
|
| 116 |
+
node,
|
| 117 |
+
{
|
| 118 |
+
k: datadict.get(k, default)
|
| 119 |
+
for k, default in node_attrs.items()
|
| 120 |
+
if default is not None or k in datadict
|
| 121 |
+
},
|
| 122 |
+
)
|
| 123 |
+
for node, datadict in graph.nodes(data=True)
|
| 124 |
+
)
|
| 125 |
+
else:
|
| 126 |
+
G.add_nodes_from(graph)
|
| 127 |
+
|
| 128 |
+
if graph.is_multigraph():
|
| 129 |
+
if preserve_edge_attrs:
|
| 130 |
+
G.add_edges_from(
|
| 131 |
+
(u, v, key, datadict)
|
| 132 |
+
for u, nbrs in graph._adj.items()
|
| 133 |
+
for v, keydict in nbrs.items()
|
| 134 |
+
for key, datadict in keydict.items()
|
| 135 |
+
)
|
| 136 |
+
elif edge_attrs:
|
| 137 |
+
G.add_edges_from(
|
| 138 |
+
(
|
| 139 |
+
u,
|
| 140 |
+
v,
|
| 141 |
+
key,
|
| 142 |
+
{
|
| 143 |
+
k: datadict.get(k, default)
|
| 144 |
+
for k, default in edge_attrs.items()
|
| 145 |
+
if default is not None or k in datadict
|
| 146 |
+
},
|
| 147 |
+
)
|
| 148 |
+
for u, nbrs in graph._adj.items()
|
| 149 |
+
for v, keydict in nbrs.items()
|
| 150 |
+
for key, datadict in keydict.items()
|
| 151 |
+
)
|
| 152 |
+
else:
|
| 153 |
+
G.add_edges_from(
|
| 154 |
+
(u, v, key, {})
|
| 155 |
+
for u, nbrs in graph._adj.items()
|
| 156 |
+
for v, keydict in nbrs.items()
|
| 157 |
+
for key, datadict in keydict.items()
|
| 158 |
+
)
|
| 159 |
+
elif preserve_edge_attrs:
|
| 160 |
+
G.add_edges_from(graph.edges(data=True))
|
| 161 |
+
elif edge_attrs:
|
| 162 |
+
G.add_edges_from(
|
| 163 |
+
(
|
| 164 |
+
u,
|
| 165 |
+
v,
|
| 166 |
+
{
|
| 167 |
+
k: datadict.get(k, default)
|
| 168 |
+
for k, default in edge_attrs.items()
|
| 169 |
+
if default is not None or k in datadict
|
| 170 |
+
},
|
| 171 |
+
)
|
| 172 |
+
for u, v, datadict in graph.edges(data=True)
|
| 173 |
+
)
|
| 174 |
+
else:
|
| 175 |
+
G.add_edges_from(graph.edges)
|
| 176 |
+
return G
|
| 177 |
+
|
| 178 |
+
@staticmethod
|
| 179 |
+
def convert_to_nx(obj, *, name=None):
|
| 180 |
+
return obj
|
| 181 |
+
|
| 182 |
+
@staticmethod
|
| 183 |
+
def on_start_tests(items):
|
| 184 |
+
# Verify that items can be xfailed
|
| 185 |
+
for item in items:
|
| 186 |
+
assert hasattr(item, "add_marker")
|
| 187 |
+
|
| 188 |
+
def can_run(self, name, args, kwargs):
|
| 189 |
+
# It is unnecessary to define this function if algorithms are fully supported.
|
| 190 |
+
# We include it for illustration purposes.
|
| 191 |
+
return hasattr(self, name)
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
dispatcher = LoopbackDispatcher()
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/test_backends.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pickle
|
| 2 |
+
|
| 3 |
+
import pytest
|
| 4 |
+
|
| 5 |
+
import networkx as nx
|
| 6 |
+
|
| 7 |
+
sp = pytest.importorskip("scipy")
|
| 8 |
+
pytest.importorskip("numpy")
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def test_dispatch_kwds_vs_args():
|
| 12 |
+
G = nx.path_graph(4)
|
| 13 |
+
nx.pagerank(G)
|
| 14 |
+
nx.pagerank(G=G)
|
| 15 |
+
with pytest.raises(TypeError):
|
| 16 |
+
nx.pagerank()
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def test_pickle():
|
| 20 |
+
for name, func in nx.utils.backends._registered_algorithms.items():
|
| 21 |
+
assert pickle.loads(pickle.dumps(func)) is func
|
| 22 |
+
assert pickle.loads(pickle.dumps(nx.inverse_line_graph)) is nx.inverse_line_graph
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
@pytest.mark.skipif(
|
| 26 |
+
"not nx._dispatch._automatic_backends "
|
| 27 |
+
"or nx._dispatch._automatic_backends[0] != 'nx-loopback'"
|
| 28 |
+
)
|
| 29 |
+
def test_graph_converter_needs_backend():
|
| 30 |
+
# When testing, `nx.from_scipy_sparse_array` will *always* call the backend
|
| 31 |
+
# implementation if it's implemented. If `backend=` isn't given, then the result
|
| 32 |
+
# will be converted back to NetworkX via `convert_to_nx`.
|
| 33 |
+
# If not testing, then calling `nx.from_scipy_sparse_array` w/o `backend=` will
|
| 34 |
+
# always call the original version. `backend=` is *required* to call the backend.
|
| 35 |
+
from networkx.classes.tests.dispatch_interface import (
|
| 36 |
+
LoopbackDispatcher,
|
| 37 |
+
LoopbackGraph,
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
+
A = sp.sparse.coo_array([[0, 3, 2], [3, 0, 1], [2, 1, 0]])
|
| 41 |
+
|
| 42 |
+
side_effects = []
|
| 43 |
+
|
| 44 |
+
def from_scipy_sparse_array(self, *args, **kwargs):
|
| 45 |
+
side_effects.append(1) # Just to prove this was called
|
| 46 |
+
return self.convert_from_nx(
|
| 47 |
+
self.__getattr__("from_scipy_sparse_array")(*args, **kwargs),
|
| 48 |
+
preserve_edge_attrs=None,
|
| 49 |
+
preserve_node_attrs=None,
|
| 50 |
+
preserve_graph_attrs=None,
|
| 51 |
+
)
|
| 52 |
+
|
| 53 |
+
@staticmethod
|
| 54 |
+
def convert_to_nx(obj, *, name=None):
|
| 55 |
+
if type(obj) is nx.Graph:
|
| 56 |
+
return obj
|
| 57 |
+
return nx.Graph(obj)
|
| 58 |
+
|
| 59 |
+
# *This mutates LoopbackDispatcher!*
|
| 60 |
+
orig_convert_to_nx = LoopbackDispatcher.convert_to_nx
|
| 61 |
+
LoopbackDispatcher.convert_to_nx = convert_to_nx
|
| 62 |
+
LoopbackDispatcher.from_scipy_sparse_array = from_scipy_sparse_array
|
| 63 |
+
|
| 64 |
+
try:
|
| 65 |
+
assert side_effects == []
|
| 66 |
+
assert type(nx.from_scipy_sparse_array(A)) is nx.Graph
|
| 67 |
+
assert side_effects == [1]
|
| 68 |
+
assert (
|
| 69 |
+
type(nx.from_scipy_sparse_array(A, backend="nx-loopback")) is LoopbackGraph
|
| 70 |
+
)
|
| 71 |
+
assert side_effects == [1, 1]
|
| 72 |
+
finally:
|
| 73 |
+
LoopbackDispatcher.convert_to_nx = staticmethod(orig_convert_to_nx)
|
| 74 |
+
del LoopbackDispatcher.from_scipy_sparse_array
|
| 75 |
+
with pytest.raises(ImportError, match="Unable to load"):
|
| 76 |
+
nx.from_scipy_sparse_array(A, backend="bad-backend-name")
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/test_coreviews.py
ADDED
|
@@ -0,0 +1,362 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pickle
|
| 2 |
+
|
| 3 |
+
import pytest
|
| 4 |
+
|
| 5 |
+
import networkx as nx
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class TestAtlasView:
|
| 9 |
+
# node->data
|
| 10 |
+
def setup_method(self):
|
| 11 |
+
self.d = {0: {"color": "blue", "weight": 1.2}, 1: {}, 2: {"color": 1}}
|
| 12 |
+
self.av = nx.classes.coreviews.AtlasView(self.d)
|
| 13 |
+
|
| 14 |
+
def test_pickle(self):
|
| 15 |
+
view = self.av
|
| 16 |
+
pview = pickle.loads(pickle.dumps(view, -1))
|
| 17 |
+
assert view == pview
|
| 18 |
+
assert view.__slots__ == pview.__slots__
|
| 19 |
+
pview = pickle.loads(pickle.dumps(view))
|
| 20 |
+
assert view == pview
|
| 21 |
+
assert view.__slots__ == pview.__slots__
|
| 22 |
+
|
| 23 |
+
def test_len(self):
|
| 24 |
+
assert len(self.av) == len(self.d)
|
| 25 |
+
|
| 26 |
+
def test_iter(self):
|
| 27 |
+
assert list(self.av) == list(self.d)
|
| 28 |
+
|
| 29 |
+
def test_getitem(self):
|
| 30 |
+
assert self.av[1] is self.d[1]
|
| 31 |
+
assert self.av[2]["color"] == 1
|
| 32 |
+
pytest.raises(KeyError, self.av.__getitem__, 3)
|
| 33 |
+
|
| 34 |
+
def test_copy(self):
|
| 35 |
+
avcopy = self.av.copy()
|
| 36 |
+
assert avcopy[0] == self.av[0]
|
| 37 |
+
assert avcopy == self.av
|
| 38 |
+
assert avcopy[0] is not self.av[0]
|
| 39 |
+
assert avcopy is not self.av
|
| 40 |
+
avcopy[5] = {}
|
| 41 |
+
assert avcopy != self.av
|
| 42 |
+
|
| 43 |
+
avcopy[0]["ht"] = 4
|
| 44 |
+
assert avcopy[0] != self.av[0]
|
| 45 |
+
self.av[0]["ht"] = 4
|
| 46 |
+
assert avcopy[0] == self.av[0]
|
| 47 |
+
del self.av[0]["ht"]
|
| 48 |
+
|
| 49 |
+
assert not hasattr(self.av, "__setitem__")
|
| 50 |
+
|
| 51 |
+
def test_items(self):
|
| 52 |
+
assert sorted(self.av.items()) == sorted(self.d.items())
|
| 53 |
+
|
| 54 |
+
def test_str(self):
|
| 55 |
+
out = str(self.d)
|
| 56 |
+
assert str(self.av) == out
|
| 57 |
+
|
| 58 |
+
def test_repr(self):
|
| 59 |
+
out = "AtlasView(" + str(self.d) + ")"
|
| 60 |
+
assert repr(self.av) == out
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
class TestAdjacencyView:
|
| 64 |
+
# node->nbr->data
|
| 65 |
+
def setup_method(self):
|
| 66 |
+
dd = {"color": "blue", "weight": 1.2}
|
| 67 |
+
self.nd = {0: dd, 1: {}, 2: {"color": 1}}
|
| 68 |
+
self.adj = {3: self.nd, 0: {3: dd}, 1: {}, 2: {3: {"color": 1}}}
|
| 69 |
+
self.adjview = nx.classes.coreviews.AdjacencyView(self.adj)
|
| 70 |
+
|
| 71 |
+
def test_pickle(self):
|
| 72 |
+
view = self.adjview
|
| 73 |
+
pview = pickle.loads(pickle.dumps(view, -1))
|
| 74 |
+
assert view == pview
|
| 75 |
+
assert view.__slots__ == pview.__slots__
|
| 76 |
+
|
| 77 |
+
def test_len(self):
|
| 78 |
+
assert len(self.adjview) == len(self.adj)
|
| 79 |
+
|
| 80 |
+
def test_iter(self):
|
| 81 |
+
assert list(self.adjview) == list(self.adj)
|
| 82 |
+
|
| 83 |
+
def test_getitem(self):
|
| 84 |
+
assert self.adjview[1] is not self.adj[1]
|
| 85 |
+
assert self.adjview[3][0] is self.adjview[0][3]
|
| 86 |
+
assert self.adjview[2][3]["color"] == 1
|
| 87 |
+
pytest.raises(KeyError, self.adjview.__getitem__, 4)
|
| 88 |
+
|
| 89 |
+
def test_copy(self):
|
| 90 |
+
avcopy = self.adjview.copy()
|
| 91 |
+
assert avcopy[0] == self.adjview[0]
|
| 92 |
+
assert avcopy[0] is not self.adjview[0]
|
| 93 |
+
|
| 94 |
+
avcopy[2][3]["ht"] = 4
|
| 95 |
+
assert avcopy[2] != self.adjview[2]
|
| 96 |
+
self.adjview[2][3]["ht"] = 4
|
| 97 |
+
assert avcopy[2] == self.adjview[2]
|
| 98 |
+
del self.adjview[2][3]["ht"]
|
| 99 |
+
|
| 100 |
+
assert not hasattr(self.adjview, "__setitem__")
|
| 101 |
+
|
| 102 |
+
def test_items(self):
|
| 103 |
+
view_items = sorted((n, dict(d)) for n, d in self.adjview.items())
|
| 104 |
+
assert view_items == sorted(self.adj.items())
|
| 105 |
+
|
| 106 |
+
def test_str(self):
|
| 107 |
+
out = str(dict(self.adj))
|
| 108 |
+
assert str(self.adjview) == out
|
| 109 |
+
|
| 110 |
+
def test_repr(self):
|
| 111 |
+
out = self.adjview.__class__.__name__ + "(" + str(self.adj) + ")"
|
| 112 |
+
assert repr(self.adjview) == out
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
class TestMultiAdjacencyView(TestAdjacencyView):
|
| 116 |
+
# node->nbr->key->data
|
| 117 |
+
def setup_method(self):
|
| 118 |
+
dd = {"color": "blue", "weight": 1.2}
|
| 119 |
+
self.kd = {0: dd, 1: {}, 2: {"color": 1}}
|
| 120 |
+
self.nd = {3: self.kd, 0: {3: dd}, 1: {0: {}}, 2: {3: {"color": 1}}}
|
| 121 |
+
self.adj = {3: self.nd, 0: {3: {3: dd}}, 1: {}, 2: {3: {8: {}}}}
|
| 122 |
+
self.adjview = nx.classes.coreviews.MultiAdjacencyView(self.adj)
|
| 123 |
+
|
| 124 |
+
def test_getitem(self):
|
| 125 |
+
assert self.adjview[1] is not self.adj[1]
|
| 126 |
+
assert self.adjview[3][0][3] is self.adjview[0][3][3]
|
| 127 |
+
assert self.adjview[3][2][3]["color"] == 1
|
| 128 |
+
pytest.raises(KeyError, self.adjview.__getitem__, 4)
|
| 129 |
+
|
| 130 |
+
def test_copy(self):
|
| 131 |
+
avcopy = self.adjview.copy()
|
| 132 |
+
assert avcopy[0] == self.adjview[0]
|
| 133 |
+
assert avcopy[0] is not self.adjview[0]
|
| 134 |
+
|
| 135 |
+
avcopy[2][3][8]["ht"] = 4
|
| 136 |
+
assert avcopy[2] != self.adjview[2]
|
| 137 |
+
self.adjview[2][3][8]["ht"] = 4
|
| 138 |
+
assert avcopy[2] == self.adjview[2]
|
| 139 |
+
del self.adjview[2][3][8]["ht"]
|
| 140 |
+
|
| 141 |
+
assert not hasattr(self.adjview, "__setitem__")
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
class TestUnionAtlas:
|
| 145 |
+
# node->data
|
| 146 |
+
def setup_method(self):
|
| 147 |
+
self.s = {0: {"color": "blue", "weight": 1.2}, 1: {}, 2: {"color": 1}}
|
| 148 |
+
self.p = {3: {"color": "blue", "weight": 1.2}, 4: {}, 2: {"watch": 2}}
|
| 149 |
+
self.av = nx.classes.coreviews.UnionAtlas(self.s, self.p)
|
| 150 |
+
|
| 151 |
+
def test_pickle(self):
|
| 152 |
+
view = self.av
|
| 153 |
+
pview = pickle.loads(pickle.dumps(view, -1))
|
| 154 |
+
assert view == pview
|
| 155 |
+
assert view.__slots__ == pview.__slots__
|
| 156 |
+
|
| 157 |
+
def test_len(self):
|
| 158 |
+
assert len(self.av) == len(self.s.keys() | self.p.keys()) == 5
|
| 159 |
+
|
| 160 |
+
def test_iter(self):
|
| 161 |
+
assert set(self.av) == set(self.s) | set(self.p)
|
| 162 |
+
|
| 163 |
+
def test_getitem(self):
|
| 164 |
+
assert self.av[0] is self.s[0]
|
| 165 |
+
assert self.av[4] is self.p[4]
|
| 166 |
+
assert self.av[2]["color"] == 1
|
| 167 |
+
pytest.raises(KeyError, self.av[2].__getitem__, "watch")
|
| 168 |
+
pytest.raises(KeyError, self.av.__getitem__, 8)
|
| 169 |
+
|
| 170 |
+
def test_copy(self):
|
| 171 |
+
avcopy = self.av.copy()
|
| 172 |
+
assert avcopy[0] == self.av[0]
|
| 173 |
+
assert avcopy[0] is not self.av[0]
|
| 174 |
+
assert avcopy is not self.av
|
| 175 |
+
avcopy[5] = {}
|
| 176 |
+
assert avcopy != self.av
|
| 177 |
+
|
| 178 |
+
avcopy[0]["ht"] = 4
|
| 179 |
+
assert avcopy[0] != self.av[0]
|
| 180 |
+
self.av[0]["ht"] = 4
|
| 181 |
+
assert avcopy[0] == self.av[0]
|
| 182 |
+
del self.av[0]["ht"]
|
| 183 |
+
|
| 184 |
+
assert not hasattr(self.av, "__setitem__")
|
| 185 |
+
|
| 186 |
+
def test_items(self):
|
| 187 |
+
expected = dict(self.p.items())
|
| 188 |
+
expected.update(self.s)
|
| 189 |
+
assert sorted(self.av.items()) == sorted(expected.items())
|
| 190 |
+
|
| 191 |
+
def test_str(self):
|
| 192 |
+
out = str(dict(self.av))
|
| 193 |
+
assert str(self.av) == out
|
| 194 |
+
|
| 195 |
+
def test_repr(self):
|
| 196 |
+
out = f"{self.av.__class__.__name__}({self.s}, {self.p})"
|
| 197 |
+
assert repr(self.av) == out
|
| 198 |
+
|
| 199 |
+
|
| 200 |
+
class TestUnionAdjacency:
|
| 201 |
+
# node->nbr->data
|
| 202 |
+
def setup_method(self):
|
| 203 |
+
dd = {"color": "blue", "weight": 1.2}
|
| 204 |
+
self.nd = {0: dd, 1: {}, 2: {"color": 1}}
|
| 205 |
+
self.s = {3: self.nd, 0: {}, 1: {}, 2: {3: {"color": 1}}}
|
| 206 |
+
self.p = {3: {}, 0: {3: dd}, 1: {0: {}}, 2: {1: {"color": 1}}}
|
| 207 |
+
self.adjview = nx.classes.coreviews.UnionAdjacency(self.s, self.p)
|
| 208 |
+
|
| 209 |
+
def test_pickle(self):
|
| 210 |
+
view = self.adjview
|
| 211 |
+
pview = pickle.loads(pickle.dumps(view, -1))
|
| 212 |
+
assert view == pview
|
| 213 |
+
assert view.__slots__ == pview.__slots__
|
| 214 |
+
|
| 215 |
+
def test_len(self):
|
| 216 |
+
assert len(self.adjview) == len(self.s)
|
| 217 |
+
|
| 218 |
+
def test_iter(self):
|
| 219 |
+
assert sorted(self.adjview) == sorted(self.s)
|
| 220 |
+
|
| 221 |
+
def test_getitem(self):
|
| 222 |
+
assert self.adjview[1] is not self.s[1]
|
| 223 |
+
assert self.adjview[3][0] is self.adjview[0][3]
|
| 224 |
+
assert self.adjview[2][3]["color"] == 1
|
| 225 |
+
pytest.raises(KeyError, self.adjview.__getitem__, 4)
|
| 226 |
+
|
| 227 |
+
def test_copy(self):
|
| 228 |
+
avcopy = self.adjview.copy()
|
| 229 |
+
assert avcopy[0] == self.adjview[0]
|
| 230 |
+
assert avcopy[0] is not self.adjview[0]
|
| 231 |
+
|
| 232 |
+
avcopy[2][3]["ht"] = 4
|
| 233 |
+
assert avcopy[2] != self.adjview[2]
|
| 234 |
+
self.adjview[2][3]["ht"] = 4
|
| 235 |
+
assert avcopy[2] == self.adjview[2]
|
| 236 |
+
del self.adjview[2][3]["ht"]
|
| 237 |
+
|
| 238 |
+
assert not hasattr(self.adjview, "__setitem__")
|
| 239 |
+
|
| 240 |
+
def test_str(self):
|
| 241 |
+
out = str(dict(self.adjview))
|
| 242 |
+
assert str(self.adjview) == out
|
| 243 |
+
|
| 244 |
+
def test_repr(self):
|
| 245 |
+
clsname = self.adjview.__class__.__name__
|
| 246 |
+
out = f"{clsname}({self.s}, {self.p})"
|
| 247 |
+
assert repr(self.adjview) == out
|
| 248 |
+
|
| 249 |
+
|
| 250 |
+
class TestUnionMultiInner(TestUnionAdjacency):
|
| 251 |
+
# nbr->key->data
|
| 252 |
+
def setup_method(self):
|
| 253 |
+
dd = {"color": "blue", "weight": 1.2}
|
| 254 |
+
self.kd = {7: {}, "ekey": {}, 9: {"color": 1}}
|
| 255 |
+
self.s = {3: self.kd, 0: {7: dd}, 1: {}, 2: {"key": {"color": 1}}}
|
| 256 |
+
self.p = {3: {}, 0: {3: dd}, 1: {}, 2: {1: {"span": 2}}}
|
| 257 |
+
self.adjview = nx.classes.coreviews.UnionMultiInner(self.s, self.p)
|
| 258 |
+
|
| 259 |
+
def test_len(self):
|
| 260 |
+
assert len(self.adjview) == len(self.s.keys() | self.p.keys()) == 4
|
| 261 |
+
|
| 262 |
+
def test_getitem(self):
|
| 263 |
+
assert self.adjview[1] is not self.s[1]
|
| 264 |
+
assert self.adjview[0][7] is self.adjview[0][3]
|
| 265 |
+
assert self.adjview[2]["key"]["color"] == 1
|
| 266 |
+
assert self.adjview[2][1]["span"] == 2
|
| 267 |
+
pytest.raises(KeyError, self.adjview.__getitem__, 4)
|
| 268 |
+
pytest.raises(KeyError, self.adjview[1].__getitem__, "key")
|
| 269 |
+
|
| 270 |
+
def test_copy(self):
|
| 271 |
+
avcopy = self.adjview.copy()
|
| 272 |
+
assert avcopy[0] == self.adjview[0]
|
| 273 |
+
assert avcopy[0] is not self.adjview[0]
|
| 274 |
+
|
| 275 |
+
avcopy[2][1]["width"] = 8
|
| 276 |
+
assert avcopy[2] != self.adjview[2]
|
| 277 |
+
self.adjview[2][1]["width"] = 8
|
| 278 |
+
assert avcopy[2] == self.adjview[2]
|
| 279 |
+
del self.adjview[2][1]["width"]
|
| 280 |
+
|
| 281 |
+
assert not hasattr(self.adjview, "__setitem__")
|
| 282 |
+
assert hasattr(avcopy, "__setitem__")
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
class TestUnionMultiAdjacency(TestUnionAdjacency):
|
| 286 |
+
# node->nbr->key->data
|
| 287 |
+
def setup_method(self):
|
| 288 |
+
dd = {"color": "blue", "weight": 1.2}
|
| 289 |
+
self.kd = {7: {}, 8: {}, 9: {"color": 1}}
|
| 290 |
+
self.nd = {3: self.kd, 0: {9: dd}, 1: {8: {}}, 2: {9: {"color": 1}}}
|
| 291 |
+
self.s = {3: self.nd, 0: {3: {7: dd}}, 1: {}, 2: {3: {8: {}}}}
|
| 292 |
+
self.p = {3: {}, 0: {3: {9: dd}}, 1: {}, 2: {1: {8: {}}}}
|
| 293 |
+
self.adjview = nx.classes.coreviews.UnionMultiAdjacency(self.s, self.p)
|
| 294 |
+
|
| 295 |
+
def test_getitem(self):
|
| 296 |
+
assert self.adjview[1] is not self.s[1]
|
| 297 |
+
assert self.adjview[3][0][9] is self.adjview[0][3][9]
|
| 298 |
+
assert self.adjview[3][2][9]["color"] == 1
|
| 299 |
+
pytest.raises(KeyError, self.adjview.__getitem__, 4)
|
| 300 |
+
|
| 301 |
+
def test_copy(self):
|
| 302 |
+
avcopy = self.adjview.copy()
|
| 303 |
+
assert avcopy[0] == self.adjview[0]
|
| 304 |
+
assert avcopy[0] is not self.adjview[0]
|
| 305 |
+
|
| 306 |
+
avcopy[2][3][8]["ht"] = 4
|
| 307 |
+
assert avcopy[2] != self.adjview[2]
|
| 308 |
+
self.adjview[2][3][8]["ht"] = 4
|
| 309 |
+
assert avcopy[2] == self.adjview[2]
|
| 310 |
+
del self.adjview[2][3][8]["ht"]
|
| 311 |
+
|
| 312 |
+
assert not hasattr(self.adjview, "__setitem__")
|
| 313 |
+
assert hasattr(avcopy, "__setitem__")
|
| 314 |
+
|
| 315 |
+
|
| 316 |
+
class TestFilteredGraphs:
|
| 317 |
+
def setup_method(self):
|
| 318 |
+
self.Graphs = [nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph]
|
| 319 |
+
|
| 320 |
+
def test_hide_show_nodes(self):
|
| 321 |
+
SubGraph = nx.subgraph_view
|
| 322 |
+
for Graph in self.Graphs:
|
| 323 |
+
G = nx.path_graph(4, Graph)
|
| 324 |
+
SG = G.subgraph([2, 3])
|
| 325 |
+
RG = SubGraph(G, filter_node=nx.filters.hide_nodes([0, 1]))
|
| 326 |
+
assert SG.nodes == RG.nodes
|
| 327 |
+
assert SG.edges == RG.edges
|
| 328 |
+
SGC = SG.copy()
|
| 329 |
+
RGC = RG.copy()
|
| 330 |
+
assert SGC.nodes == RGC.nodes
|
| 331 |
+
assert SGC.edges == RGC.edges
|
| 332 |
+
|
| 333 |
+
def test_str_repr(self):
|
| 334 |
+
SubGraph = nx.subgraph_view
|
| 335 |
+
for Graph in self.Graphs:
|
| 336 |
+
G = nx.path_graph(4, Graph)
|
| 337 |
+
SG = G.subgraph([2, 3])
|
| 338 |
+
RG = SubGraph(G, filter_node=nx.filters.hide_nodes([0, 1]))
|
| 339 |
+
str(SG.adj)
|
| 340 |
+
str(RG.adj)
|
| 341 |
+
repr(SG.adj)
|
| 342 |
+
repr(RG.adj)
|
| 343 |
+
str(SG.adj[2])
|
| 344 |
+
str(RG.adj[2])
|
| 345 |
+
repr(SG.adj[2])
|
| 346 |
+
repr(RG.adj[2])
|
| 347 |
+
|
| 348 |
+
def test_copy(self):
|
| 349 |
+
SubGraph = nx.subgraph_view
|
| 350 |
+
for Graph in self.Graphs:
|
| 351 |
+
G = nx.path_graph(4, Graph)
|
| 352 |
+
SG = G.subgraph([2, 3])
|
| 353 |
+
RG = SubGraph(G, filter_node=nx.filters.hide_nodes([0, 1]))
|
| 354 |
+
RsG = SubGraph(G, filter_node=nx.filters.show_nodes([2, 3]))
|
| 355 |
+
assert G.adj.copy() == G.adj
|
| 356 |
+
assert G.adj[2].copy() == G.adj[2]
|
| 357 |
+
assert SG.adj.copy() == SG.adj
|
| 358 |
+
assert SG.adj[2].copy() == SG.adj[2]
|
| 359 |
+
assert RG.adj.copy() == RG.adj
|
| 360 |
+
assert RG.adj[2].copy() == RG.adj[2]
|
| 361 |
+
assert RsG.adj.copy() == RsG.adj
|
| 362 |
+
assert RsG.adj[2].copy() == RsG.adj[2]
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/test_digraph.py
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pytest
|
| 2 |
+
|
| 3 |
+
import networkx as nx
|
| 4 |
+
from networkx.utils import nodes_equal
|
| 5 |
+
|
| 6 |
+
from .test_graph import BaseAttrGraphTester, BaseGraphTester
|
| 7 |
+
from .test_graph import TestEdgeSubgraph as _TestGraphEdgeSubgraph
|
| 8 |
+
from .test_graph import TestGraph as _TestGraph
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class BaseDiGraphTester(BaseGraphTester):
|
| 12 |
+
def test_has_successor(self):
|
| 13 |
+
G = self.K3
|
| 14 |
+
assert G.has_successor(0, 1)
|
| 15 |
+
assert not G.has_successor(0, -1)
|
| 16 |
+
|
| 17 |
+
def test_successors(self):
|
| 18 |
+
G = self.K3
|
| 19 |
+
assert sorted(G.successors(0)) == [1, 2]
|
| 20 |
+
with pytest.raises(nx.NetworkXError):
|
| 21 |
+
G.successors(-1)
|
| 22 |
+
|
| 23 |
+
def test_has_predecessor(self):
|
| 24 |
+
G = self.K3
|
| 25 |
+
assert G.has_predecessor(0, 1)
|
| 26 |
+
assert not G.has_predecessor(0, -1)
|
| 27 |
+
|
| 28 |
+
def test_predecessors(self):
|
| 29 |
+
G = self.K3
|
| 30 |
+
assert sorted(G.predecessors(0)) == [1, 2]
|
| 31 |
+
with pytest.raises(nx.NetworkXError):
|
| 32 |
+
G.predecessors(-1)
|
| 33 |
+
|
| 34 |
+
def test_edges(self):
|
| 35 |
+
G = self.K3
|
| 36 |
+
assert sorted(G.edges()) == [(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)]
|
| 37 |
+
assert sorted(G.edges(0)) == [(0, 1), (0, 2)]
|
| 38 |
+
assert sorted(G.edges([0, 1])) == [(0, 1), (0, 2), (1, 0), (1, 2)]
|
| 39 |
+
with pytest.raises(nx.NetworkXError):
|
| 40 |
+
G.edges(-1)
|
| 41 |
+
|
| 42 |
+
def test_out_edges(self):
|
| 43 |
+
G = self.K3
|
| 44 |
+
assert sorted(G.out_edges()) == [(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)]
|
| 45 |
+
assert sorted(G.out_edges(0)) == [(0, 1), (0, 2)]
|
| 46 |
+
with pytest.raises(nx.NetworkXError):
|
| 47 |
+
G.out_edges(-1)
|
| 48 |
+
|
| 49 |
+
def test_out_edges_dir(self):
|
| 50 |
+
G = self.P3
|
| 51 |
+
assert sorted(G.out_edges()) == [(0, 1), (1, 2)]
|
| 52 |
+
assert sorted(G.out_edges(0)) == [(0, 1)]
|
| 53 |
+
assert sorted(G.out_edges(2)) == []
|
| 54 |
+
|
| 55 |
+
def test_out_edges_data(self):
|
| 56 |
+
G = nx.DiGraph([(0, 1, {"data": 0}), (1, 0, {})])
|
| 57 |
+
assert sorted(G.out_edges(data=True)) == [(0, 1, {"data": 0}), (1, 0, {})]
|
| 58 |
+
assert sorted(G.out_edges(0, data=True)) == [(0, 1, {"data": 0})]
|
| 59 |
+
assert sorted(G.out_edges(data="data")) == [(0, 1, 0), (1, 0, None)]
|
| 60 |
+
assert sorted(G.out_edges(0, data="data")) == [(0, 1, 0)]
|
| 61 |
+
|
| 62 |
+
def test_in_edges_dir(self):
|
| 63 |
+
G = self.P3
|
| 64 |
+
assert sorted(G.in_edges()) == [(0, 1), (1, 2)]
|
| 65 |
+
assert sorted(G.in_edges(0)) == []
|
| 66 |
+
assert sorted(G.in_edges(2)) == [(1, 2)]
|
| 67 |
+
|
| 68 |
+
def test_in_edges_data(self):
|
| 69 |
+
G = nx.DiGraph([(0, 1, {"data": 0}), (1, 0, {})])
|
| 70 |
+
assert sorted(G.in_edges(data=True)) == [(0, 1, {"data": 0}), (1, 0, {})]
|
| 71 |
+
assert sorted(G.in_edges(1, data=True)) == [(0, 1, {"data": 0})]
|
| 72 |
+
assert sorted(G.in_edges(data="data")) == [(0, 1, 0), (1, 0, None)]
|
| 73 |
+
assert sorted(G.in_edges(1, data="data")) == [(0, 1, 0)]
|
| 74 |
+
|
| 75 |
+
def test_degree(self):
|
| 76 |
+
G = self.K3
|
| 77 |
+
assert sorted(G.degree()) == [(0, 4), (1, 4), (2, 4)]
|
| 78 |
+
assert dict(G.degree()) == {0: 4, 1: 4, 2: 4}
|
| 79 |
+
assert G.degree(0) == 4
|
| 80 |
+
assert list(G.degree(iter([0]))) == [(0, 4)] # run through iterator
|
| 81 |
+
|
| 82 |
+
def test_in_degree(self):
|
| 83 |
+
G = self.K3
|
| 84 |
+
assert sorted(G.in_degree()) == [(0, 2), (1, 2), (2, 2)]
|
| 85 |
+
assert dict(G.in_degree()) == {0: 2, 1: 2, 2: 2}
|
| 86 |
+
assert G.in_degree(0) == 2
|
| 87 |
+
assert list(G.in_degree(iter([0]))) == [(0, 2)] # run through iterator
|
| 88 |
+
|
| 89 |
+
def test_out_degree(self):
|
| 90 |
+
G = self.K3
|
| 91 |
+
assert sorted(G.out_degree()) == [(0, 2), (1, 2), (2, 2)]
|
| 92 |
+
assert dict(G.out_degree()) == {0: 2, 1: 2, 2: 2}
|
| 93 |
+
assert G.out_degree(0) == 2
|
| 94 |
+
assert list(G.out_degree(iter([0]))) == [(0, 2)]
|
| 95 |
+
|
| 96 |
+
def test_size(self):
|
| 97 |
+
G = self.K3
|
| 98 |
+
assert G.size() == 6
|
| 99 |
+
assert G.number_of_edges() == 6
|
| 100 |
+
|
| 101 |
+
def test_to_undirected_reciprocal(self):
|
| 102 |
+
G = self.Graph()
|
| 103 |
+
G.add_edge(1, 2)
|
| 104 |
+
assert G.to_undirected().has_edge(1, 2)
|
| 105 |
+
assert not G.to_undirected(reciprocal=True).has_edge(1, 2)
|
| 106 |
+
G.add_edge(2, 1)
|
| 107 |
+
assert G.to_undirected(reciprocal=True).has_edge(1, 2)
|
| 108 |
+
|
| 109 |
+
def test_reverse_copy(self):
|
| 110 |
+
G = nx.DiGraph([(0, 1), (1, 2)])
|
| 111 |
+
R = G.reverse()
|
| 112 |
+
assert sorted(R.edges()) == [(1, 0), (2, 1)]
|
| 113 |
+
R.remove_edge(1, 0)
|
| 114 |
+
assert sorted(R.edges()) == [(2, 1)]
|
| 115 |
+
assert sorted(G.edges()) == [(0, 1), (1, 2)]
|
| 116 |
+
|
| 117 |
+
def test_reverse_nocopy(self):
|
| 118 |
+
G = nx.DiGraph([(0, 1), (1, 2)])
|
| 119 |
+
R = G.reverse(copy=False)
|
| 120 |
+
assert sorted(R.edges()) == [(1, 0), (2, 1)]
|
| 121 |
+
with pytest.raises(nx.NetworkXError):
|
| 122 |
+
R.remove_edge(1, 0)
|
| 123 |
+
|
| 124 |
+
def test_reverse_hashable(self):
|
| 125 |
+
class Foo:
|
| 126 |
+
pass
|
| 127 |
+
|
| 128 |
+
x = Foo()
|
| 129 |
+
y = Foo()
|
| 130 |
+
G = nx.DiGraph()
|
| 131 |
+
G.add_edge(x, y)
|
| 132 |
+
assert nodes_equal(G.nodes(), G.reverse().nodes())
|
| 133 |
+
assert [(y, x)] == list(G.reverse().edges())
|
| 134 |
+
|
| 135 |
+
def test_di_cache_reset(self):
|
| 136 |
+
G = self.K3.copy()
|
| 137 |
+
old_succ = G.succ
|
| 138 |
+
assert id(G.succ) == id(old_succ)
|
| 139 |
+
old_adj = G.adj
|
| 140 |
+
assert id(G.adj) == id(old_adj)
|
| 141 |
+
|
| 142 |
+
G._succ = {}
|
| 143 |
+
assert id(G.succ) != id(old_succ)
|
| 144 |
+
assert id(G.adj) != id(old_adj)
|
| 145 |
+
|
| 146 |
+
old_pred = G.pred
|
| 147 |
+
assert id(G.pred) == id(old_pred)
|
| 148 |
+
G._pred = {}
|
| 149 |
+
assert id(G.pred) != id(old_pred)
|
| 150 |
+
|
| 151 |
+
def test_di_attributes_cached(self):
|
| 152 |
+
G = self.K3.copy()
|
| 153 |
+
assert id(G.in_edges) == id(G.in_edges)
|
| 154 |
+
assert id(G.out_edges) == id(G.out_edges)
|
| 155 |
+
assert id(G.in_degree) == id(G.in_degree)
|
| 156 |
+
assert id(G.out_degree) == id(G.out_degree)
|
| 157 |
+
assert id(G.succ) == id(G.succ)
|
| 158 |
+
assert id(G.pred) == id(G.pred)
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
class BaseAttrDiGraphTester(BaseDiGraphTester, BaseAttrGraphTester):
|
| 162 |
+
def test_edges_data(self):
|
| 163 |
+
G = self.K3
|
| 164 |
+
all_edges = [
|
| 165 |
+
(0, 1, {}),
|
| 166 |
+
(0, 2, {}),
|
| 167 |
+
(1, 0, {}),
|
| 168 |
+
(1, 2, {}),
|
| 169 |
+
(2, 0, {}),
|
| 170 |
+
(2, 1, {}),
|
| 171 |
+
]
|
| 172 |
+
assert sorted(G.edges(data=True)) == all_edges
|
| 173 |
+
assert sorted(G.edges(0, data=True)) == all_edges[:2]
|
| 174 |
+
assert sorted(G.edges([0, 1], data=True)) == all_edges[:4]
|
| 175 |
+
with pytest.raises(nx.NetworkXError):
|
| 176 |
+
G.edges(-1, True)
|
| 177 |
+
|
| 178 |
+
def test_in_degree_weighted(self):
|
| 179 |
+
G = self.K3.copy()
|
| 180 |
+
G.add_edge(0, 1, weight=0.3, other=1.2)
|
| 181 |
+
assert sorted(G.in_degree(weight="weight")) == [(0, 2), (1, 1.3), (2, 2)]
|
| 182 |
+
assert dict(G.in_degree(weight="weight")) == {0: 2, 1: 1.3, 2: 2}
|
| 183 |
+
assert G.in_degree(1, weight="weight") == 1.3
|
| 184 |
+
assert sorted(G.in_degree(weight="other")) == [(0, 2), (1, 2.2), (2, 2)]
|
| 185 |
+
assert dict(G.in_degree(weight="other")) == {0: 2, 1: 2.2, 2: 2}
|
| 186 |
+
assert G.in_degree(1, weight="other") == 2.2
|
| 187 |
+
assert list(G.in_degree(iter([1]), weight="other")) == [(1, 2.2)]
|
| 188 |
+
|
| 189 |
+
def test_out_degree_weighted(self):
|
| 190 |
+
G = self.K3.copy()
|
| 191 |
+
G.add_edge(0, 1, weight=0.3, other=1.2)
|
| 192 |
+
assert sorted(G.out_degree(weight="weight")) == [(0, 1.3), (1, 2), (2, 2)]
|
| 193 |
+
assert dict(G.out_degree(weight="weight")) == {0: 1.3, 1: 2, 2: 2}
|
| 194 |
+
assert G.out_degree(0, weight="weight") == 1.3
|
| 195 |
+
assert sorted(G.out_degree(weight="other")) == [(0, 2.2), (1, 2), (2, 2)]
|
| 196 |
+
assert dict(G.out_degree(weight="other")) == {0: 2.2, 1: 2, 2: 2}
|
| 197 |
+
assert G.out_degree(0, weight="other") == 2.2
|
| 198 |
+
assert list(G.out_degree(iter([0]), weight="other")) == [(0, 2.2)]
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
class TestDiGraph(BaseAttrDiGraphTester, _TestGraph):
|
| 202 |
+
"""Tests specific to dict-of-dict-of-dict digraph data structure"""
|
| 203 |
+
|
| 204 |
+
def setup_method(self):
|
| 205 |
+
self.Graph = nx.DiGraph
|
| 206 |
+
# build dict-of-dict-of-dict K3
|
| 207 |
+
ed1, ed2, ed3, ed4, ed5, ed6 = ({}, {}, {}, {}, {}, {})
|
| 208 |
+
self.k3adj = {0: {1: ed1, 2: ed2}, 1: {0: ed3, 2: ed4}, 2: {0: ed5, 1: ed6}}
|
| 209 |
+
self.k3edges = [(0, 1), (0, 2), (1, 2)]
|
| 210 |
+
self.k3nodes = [0, 1, 2]
|
| 211 |
+
self.K3 = self.Graph()
|
| 212 |
+
self.K3._succ = self.k3adj # K3._adj is synced with K3._succ
|
| 213 |
+
self.K3._pred = {0: {1: ed3, 2: ed5}, 1: {0: ed1, 2: ed6}, 2: {0: ed2, 1: ed4}}
|
| 214 |
+
self.K3._node = {}
|
| 215 |
+
self.K3._node[0] = {}
|
| 216 |
+
self.K3._node[1] = {}
|
| 217 |
+
self.K3._node[2] = {}
|
| 218 |
+
|
| 219 |
+
ed1, ed2 = ({}, {})
|
| 220 |
+
self.P3 = self.Graph()
|
| 221 |
+
self.P3._succ = {0: {1: ed1}, 1: {2: ed2}, 2: {}}
|
| 222 |
+
self.P3._pred = {0: {}, 1: {0: ed1}, 2: {1: ed2}}
|
| 223 |
+
# P3._adj is synced with P3._succ
|
| 224 |
+
self.P3._node = {}
|
| 225 |
+
self.P3._node[0] = {}
|
| 226 |
+
self.P3._node[1] = {}
|
| 227 |
+
self.P3._node[2] = {}
|
| 228 |
+
|
| 229 |
+
def test_data_input(self):
|
| 230 |
+
G = self.Graph({1: [2], 2: [1]}, name="test")
|
| 231 |
+
assert G.name == "test"
|
| 232 |
+
assert sorted(G.adj.items()) == [(1, {2: {}}), (2, {1: {}})]
|
| 233 |
+
assert sorted(G.succ.items()) == [(1, {2: {}}), (2, {1: {}})]
|
| 234 |
+
assert sorted(G.pred.items()) == [(1, {2: {}}), (2, {1: {}})]
|
| 235 |
+
|
| 236 |
+
def test_add_edge(self):
|
| 237 |
+
G = self.Graph()
|
| 238 |
+
G.add_edge(0, 1)
|
| 239 |
+
assert G.adj == {0: {1: {}}, 1: {}}
|
| 240 |
+
assert G.succ == {0: {1: {}}, 1: {}}
|
| 241 |
+
assert G.pred == {0: {}, 1: {0: {}}}
|
| 242 |
+
G = self.Graph()
|
| 243 |
+
G.add_edge(*(0, 1))
|
| 244 |
+
assert G.adj == {0: {1: {}}, 1: {}}
|
| 245 |
+
assert G.succ == {0: {1: {}}, 1: {}}
|
| 246 |
+
assert G.pred == {0: {}, 1: {0: {}}}
|
| 247 |
+
with pytest.raises(ValueError, match="None cannot be a node"):
|
| 248 |
+
G.add_edge(None, 3)
|
| 249 |
+
|
| 250 |
+
def test_add_edges_from(self):
|
| 251 |
+
G = self.Graph()
|
| 252 |
+
G.add_edges_from([(0, 1), (0, 2, {"data": 3})], data=2)
|
| 253 |
+
assert G.adj == {0: {1: {"data": 2}, 2: {"data": 3}}, 1: {}, 2: {}}
|
| 254 |
+
assert G.succ == {0: {1: {"data": 2}, 2: {"data": 3}}, 1: {}, 2: {}}
|
| 255 |
+
assert G.pred == {0: {}, 1: {0: {"data": 2}}, 2: {0: {"data": 3}}}
|
| 256 |
+
|
| 257 |
+
with pytest.raises(nx.NetworkXError):
|
| 258 |
+
G.add_edges_from([(0,)]) # too few in tuple
|
| 259 |
+
with pytest.raises(nx.NetworkXError):
|
| 260 |
+
G.add_edges_from([(0, 1, 2, 3)]) # too many in tuple
|
| 261 |
+
with pytest.raises(TypeError):
|
| 262 |
+
G.add_edges_from([0]) # not a tuple
|
| 263 |
+
with pytest.raises(ValueError, match="None cannot be a node"):
|
| 264 |
+
G.add_edges_from([(None, 3), (3, 2)])
|
| 265 |
+
|
| 266 |
+
def test_remove_edge(self):
|
| 267 |
+
G = self.K3.copy()
|
| 268 |
+
G.remove_edge(0, 1)
|
| 269 |
+
assert G.succ == {0: {2: {}}, 1: {0: {}, 2: {}}, 2: {0: {}, 1: {}}}
|
| 270 |
+
assert G.pred == {0: {1: {}, 2: {}}, 1: {2: {}}, 2: {0: {}, 1: {}}}
|
| 271 |
+
with pytest.raises(nx.NetworkXError):
|
| 272 |
+
G.remove_edge(-1, 0)
|
| 273 |
+
|
| 274 |
+
def test_remove_edges_from(self):
|
| 275 |
+
G = self.K3.copy()
|
| 276 |
+
G.remove_edges_from([(0, 1)])
|
| 277 |
+
assert G.succ == {0: {2: {}}, 1: {0: {}, 2: {}}, 2: {0: {}, 1: {}}}
|
| 278 |
+
assert G.pred == {0: {1: {}, 2: {}}, 1: {2: {}}, 2: {0: {}, 1: {}}}
|
| 279 |
+
G.remove_edges_from([(0, 0)]) # silent fail
|
| 280 |
+
|
| 281 |
+
def test_clear(self):
|
| 282 |
+
G = self.K3
|
| 283 |
+
G.graph["name"] = "K3"
|
| 284 |
+
G.clear()
|
| 285 |
+
assert list(G.nodes) == []
|
| 286 |
+
assert G.succ == {}
|
| 287 |
+
assert G.pred == {}
|
| 288 |
+
assert G.graph == {}
|
| 289 |
+
|
| 290 |
+
def test_clear_edges(self):
|
| 291 |
+
G = self.K3
|
| 292 |
+
G.graph["name"] = "K3"
|
| 293 |
+
nodes = list(G.nodes)
|
| 294 |
+
G.clear_edges()
|
| 295 |
+
assert list(G.nodes) == nodes
|
| 296 |
+
expected = {0: {}, 1: {}, 2: {}}
|
| 297 |
+
assert G.succ == expected
|
| 298 |
+
assert G.pred == expected
|
| 299 |
+
assert list(G.edges) == []
|
| 300 |
+
assert G.graph["name"] == "K3"
|
| 301 |
+
|
| 302 |
+
|
| 303 |
+
class TestEdgeSubgraph(_TestGraphEdgeSubgraph):
|
| 304 |
+
"""Unit tests for the :meth:`DiGraph.edge_subgraph` method."""
|
| 305 |
+
|
| 306 |
+
def setup_method(self):
|
| 307 |
+
# Create a doubly-linked path graph on five nodes.
|
| 308 |
+
G = nx.DiGraph(nx.path_graph(5))
|
| 309 |
+
# Add some node, edge, and graph attributes.
|
| 310 |
+
for i in range(5):
|
| 311 |
+
G.nodes[i]["name"] = f"node{i}"
|
| 312 |
+
G.edges[0, 1]["name"] = "edge01"
|
| 313 |
+
G.edges[3, 4]["name"] = "edge34"
|
| 314 |
+
G.graph["name"] = "graph"
|
| 315 |
+
# Get the subgraph induced by the first and last edges.
|
| 316 |
+
self.G = G
|
| 317 |
+
self.H = G.edge_subgraph([(0, 1), (3, 4)])
|
| 318 |
+
|
| 319 |
+
def test_pred_succ(self):
|
| 320 |
+
"""Test that nodes are added to predecessors and successors.
|
| 321 |
+
|
| 322 |
+
For more information, see GitHub issue #2370.
|
| 323 |
+
|
| 324 |
+
"""
|
| 325 |
+
G = nx.DiGraph()
|
| 326 |
+
G.add_edge(0, 1)
|
| 327 |
+
H = G.edge_subgraph([(0, 1)])
|
| 328 |
+
assert list(H.predecessors(0)) == []
|
| 329 |
+
assert list(H.successors(0)) == [1]
|
| 330 |
+
assert list(H.predecessors(1)) == [0]
|
| 331 |
+
assert list(H.successors(1)) == []
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/test_function.py
ADDED
|
@@ -0,0 +1,782 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
|
| 3 |
+
import pytest
|
| 4 |
+
|
| 5 |
+
import networkx as nx
|
| 6 |
+
from networkx.utils import edges_equal, nodes_equal
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class TestFunction:
|
| 10 |
+
def setup_method(self):
|
| 11 |
+
self.G = nx.Graph({0: [1, 2, 3], 1: [1, 2, 0], 4: []}, name="Test")
|
| 12 |
+
self.Gdegree = {0: 3, 1: 2, 2: 2, 3: 1, 4: 0}
|
| 13 |
+
self.Gnodes = list(range(5))
|
| 14 |
+
self.Gedges = [(0, 1), (0, 2), (0, 3), (1, 0), (1, 1), (1, 2)]
|
| 15 |
+
self.DG = nx.DiGraph({0: [1, 2, 3], 1: [1, 2, 0], 4: []})
|
| 16 |
+
self.DGin_degree = {0: 1, 1: 2, 2: 2, 3: 1, 4: 0}
|
| 17 |
+
self.DGout_degree = {0: 3, 1: 3, 2: 0, 3: 0, 4: 0}
|
| 18 |
+
self.DGnodes = list(range(5))
|
| 19 |
+
self.DGedges = [(0, 1), (0, 2), (0, 3), (1, 0), (1, 1), (1, 2)]
|
| 20 |
+
|
| 21 |
+
def test_nodes(self):
|
| 22 |
+
assert nodes_equal(self.G.nodes(), list(nx.nodes(self.G)))
|
| 23 |
+
assert nodes_equal(self.DG.nodes(), list(nx.nodes(self.DG)))
|
| 24 |
+
|
| 25 |
+
def test_edges(self):
|
| 26 |
+
assert edges_equal(self.G.edges(), list(nx.edges(self.G)))
|
| 27 |
+
assert sorted(self.DG.edges()) == sorted(nx.edges(self.DG))
|
| 28 |
+
assert edges_equal(
|
| 29 |
+
self.G.edges(nbunch=[0, 1, 3]), list(nx.edges(self.G, nbunch=[0, 1, 3]))
|
| 30 |
+
)
|
| 31 |
+
assert sorted(self.DG.edges(nbunch=[0, 1, 3])) == sorted(
|
| 32 |
+
nx.edges(self.DG, nbunch=[0, 1, 3])
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
def test_degree(self):
|
| 36 |
+
assert edges_equal(self.G.degree(), list(nx.degree(self.G)))
|
| 37 |
+
assert sorted(self.DG.degree()) == sorted(nx.degree(self.DG))
|
| 38 |
+
assert edges_equal(
|
| 39 |
+
self.G.degree(nbunch=[0, 1]), list(nx.degree(self.G, nbunch=[0, 1]))
|
| 40 |
+
)
|
| 41 |
+
assert sorted(self.DG.degree(nbunch=[0, 1])) == sorted(
|
| 42 |
+
nx.degree(self.DG, nbunch=[0, 1])
|
| 43 |
+
)
|
| 44 |
+
assert edges_equal(
|
| 45 |
+
self.G.degree(weight="weight"), list(nx.degree(self.G, weight="weight"))
|
| 46 |
+
)
|
| 47 |
+
assert sorted(self.DG.degree(weight="weight")) == sorted(
|
| 48 |
+
nx.degree(self.DG, weight="weight")
|
| 49 |
+
)
|
| 50 |
+
|
| 51 |
+
def test_neighbors(self):
|
| 52 |
+
assert list(self.G.neighbors(1)) == list(nx.neighbors(self.G, 1))
|
| 53 |
+
assert list(self.DG.neighbors(1)) == list(nx.neighbors(self.DG, 1))
|
| 54 |
+
|
| 55 |
+
def test_number_of_nodes(self):
|
| 56 |
+
assert self.G.number_of_nodes() == nx.number_of_nodes(self.G)
|
| 57 |
+
assert self.DG.number_of_nodes() == nx.number_of_nodes(self.DG)
|
| 58 |
+
|
| 59 |
+
def test_number_of_edges(self):
|
| 60 |
+
assert self.G.number_of_edges() == nx.number_of_edges(self.G)
|
| 61 |
+
assert self.DG.number_of_edges() == nx.number_of_edges(self.DG)
|
| 62 |
+
|
| 63 |
+
def test_is_directed(self):
|
| 64 |
+
assert self.G.is_directed() == nx.is_directed(self.G)
|
| 65 |
+
assert self.DG.is_directed() == nx.is_directed(self.DG)
|
| 66 |
+
|
| 67 |
+
def test_add_star(self):
|
| 68 |
+
G = self.G.copy()
|
| 69 |
+
nlist = [12, 13, 14, 15]
|
| 70 |
+
nx.add_star(G, nlist)
|
| 71 |
+
assert edges_equal(G.edges(nlist), [(12, 13), (12, 14), (12, 15)])
|
| 72 |
+
|
| 73 |
+
G = self.G.copy()
|
| 74 |
+
nx.add_star(G, nlist, weight=2.0)
|
| 75 |
+
assert edges_equal(
|
| 76 |
+
G.edges(nlist, data=True),
|
| 77 |
+
[
|
| 78 |
+
(12, 13, {"weight": 2.0}),
|
| 79 |
+
(12, 14, {"weight": 2.0}),
|
| 80 |
+
(12, 15, {"weight": 2.0}),
|
| 81 |
+
],
|
| 82 |
+
)
|
| 83 |
+
|
| 84 |
+
G = self.G.copy()
|
| 85 |
+
nlist = [12]
|
| 86 |
+
nx.add_star(G, nlist)
|
| 87 |
+
assert nodes_equal(G, list(self.G) + nlist)
|
| 88 |
+
|
| 89 |
+
G = self.G.copy()
|
| 90 |
+
nlist = []
|
| 91 |
+
nx.add_star(G, nlist)
|
| 92 |
+
assert nodes_equal(G.nodes, self.Gnodes)
|
| 93 |
+
assert edges_equal(G.edges, self.G.edges)
|
| 94 |
+
|
| 95 |
+
def test_add_path(self):
|
| 96 |
+
G = self.G.copy()
|
| 97 |
+
nlist = [12, 13, 14, 15]
|
| 98 |
+
nx.add_path(G, nlist)
|
| 99 |
+
assert edges_equal(G.edges(nlist), [(12, 13), (13, 14), (14, 15)])
|
| 100 |
+
G = self.G.copy()
|
| 101 |
+
nx.add_path(G, nlist, weight=2.0)
|
| 102 |
+
assert edges_equal(
|
| 103 |
+
G.edges(nlist, data=True),
|
| 104 |
+
[
|
| 105 |
+
(12, 13, {"weight": 2.0}),
|
| 106 |
+
(13, 14, {"weight": 2.0}),
|
| 107 |
+
(14, 15, {"weight": 2.0}),
|
| 108 |
+
],
|
| 109 |
+
)
|
| 110 |
+
|
| 111 |
+
G = self.G.copy()
|
| 112 |
+
nlist = ["node"]
|
| 113 |
+
nx.add_path(G, nlist)
|
| 114 |
+
assert edges_equal(G.edges(nlist), [])
|
| 115 |
+
assert nodes_equal(G, list(self.G) + ["node"])
|
| 116 |
+
|
| 117 |
+
G = self.G.copy()
|
| 118 |
+
nlist = iter(["node"])
|
| 119 |
+
nx.add_path(G, nlist)
|
| 120 |
+
assert edges_equal(G.edges(["node"]), [])
|
| 121 |
+
assert nodes_equal(G, list(self.G) + ["node"])
|
| 122 |
+
|
| 123 |
+
G = self.G.copy()
|
| 124 |
+
nlist = [12]
|
| 125 |
+
nx.add_path(G, nlist)
|
| 126 |
+
assert edges_equal(G.edges(nlist), [])
|
| 127 |
+
assert nodes_equal(G, list(self.G) + [12])
|
| 128 |
+
|
| 129 |
+
G = self.G.copy()
|
| 130 |
+
nlist = iter([12])
|
| 131 |
+
nx.add_path(G, nlist)
|
| 132 |
+
assert edges_equal(G.edges([12]), [])
|
| 133 |
+
assert nodes_equal(G, list(self.G) + [12])
|
| 134 |
+
|
| 135 |
+
G = self.G.copy()
|
| 136 |
+
nlist = []
|
| 137 |
+
nx.add_path(G, nlist)
|
| 138 |
+
assert edges_equal(G.edges, self.G.edges)
|
| 139 |
+
assert nodes_equal(G, list(self.G))
|
| 140 |
+
|
| 141 |
+
G = self.G.copy()
|
| 142 |
+
nlist = iter([])
|
| 143 |
+
nx.add_path(G, nlist)
|
| 144 |
+
assert edges_equal(G.edges, self.G.edges)
|
| 145 |
+
assert nodes_equal(G, list(self.G))
|
| 146 |
+
|
| 147 |
+
def test_add_cycle(self):
|
| 148 |
+
G = self.G.copy()
|
| 149 |
+
nlist = [12, 13, 14, 15]
|
| 150 |
+
oklists = [
|
| 151 |
+
[(12, 13), (12, 15), (13, 14), (14, 15)],
|
| 152 |
+
[(12, 13), (13, 14), (14, 15), (15, 12)],
|
| 153 |
+
]
|
| 154 |
+
nx.add_cycle(G, nlist)
|
| 155 |
+
assert sorted(G.edges(nlist)) in oklists
|
| 156 |
+
G = self.G.copy()
|
| 157 |
+
oklists = [
|
| 158 |
+
[
|
| 159 |
+
(12, 13, {"weight": 1.0}),
|
| 160 |
+
(12, 15, {"weight": 1.0}),
|
| 161 |
+
(13, 14, {"weight": 1.0}),
|
| 162 |
+
(14, 15, {"weight": 1.0}),
|
| 163 |
+
],
|
| 164 |
+
[
|
| 165 |
+
(12, 13, {"weight": 1.0}),
|
| 166 |
+
(13, 14, {"weight": 1.0}),
|
| 167 |
+
(14, 15, {"weight": 1.0}),
|
| 168 |
+
(15, 12, {"weight": 1.0}),
|
| 169 |
+
],
|
| 170 |
+
]
|
| 171 |
+
nx.add_cycle(G, nlist, weight=1.0)
|
| 172 |
+
assert sorted(G.edges(nlist, data=True)) in oklists
|
| 173 |
+
|
| 174 |
+
G = self.G.copy()
|
| 175 |
+
nlist = [12]
|
| 176 |
+
nx.add_cycle(G, nlist)
|
| 177 |
+
assert nodes_equal(G, list(self.G) + nlist)
|
| 178 |
+
|
| 179 |
+
G = self.G.copy()
|
| 180 |
+
nlist = []
|
| 181 |
+
nx.add_cycle(G, nlist)
|
| 182 |
+
assert nodes_equal(G.nodes, self.Gnodes)
|
| 183 |
+
assert edges_equal(G.edges, self.G.edges)
|
| 184 |
+
|
| 185 |
+
def test_subgraph(self):
|
| 186 |
+
assert (
|
| 187 |
+
self.G.subgraph([0, 1, 2, 4]).adj == nx.subgraph(self.G, [0, 1, 2, 4]).adj
|
| 188 |
+
)
|
| 189 |
+
assert (
|
| 190 |
+
self.DG.subgraph([0, 1, 2, 4]).adj == nx.subgraph(self.DG, [0, 1, 2, 4]).adj
|
| 191 |
+
)
|
| 192 |
+
assert (
|
| 193 |
+
self.G.subgraph([0, 1, 2, 4]).adj
|
| 194 |
+
== nx.induced_subgraph(self.G, [0, 1, 2, 4]).adj
|
| 195 |
+
)
|
| 196 |
+
assert (
|
| 197 |
+
self.DG.subgraph([0, 1, 2, 4]).adj
|
| 198 |
+
== nx.induced_subgraph(self.DG, [0, 1, 2, 4]).adj
|
| 199 |
+
)
|
| 200 |
+
# subgraph-subgraph chain is allowed in function interface
|
| 201 |
+
H = nx.induced_subgraph(self.G.subgraph([0, 1, 2, 4]), [0, 1, 4])
|
| 202 |
+
assert H._graph is not self.G
|
| 203 |
+
assert H.adj == self.G.subgraph([0, 1, 4]).adj
|
| 204 |
+
|
| 205 |
+
def test_edge_subgraph(self):
|
| 206 |
+
assert (
|
| 207 |
+
self.G.edge_subgraph([(1, 2), (0, 3)]).adj
|
| 208 |
+
== nx.edge_subgraph(self.G, [(1, 2), (0, 3)]).adj
|
| 209 |
+
)
|
| 210 |
+
assert (
|
| 211 |
+
self.DG.edge_subgraph([(1, 2), (0, 3)]).adj
|
| 212 |
+
== nx.edge_subgraph(self.DG, [(1, 2), (0, 3)]).adj
|
| 213 |
+
)
|
| 214 |
+
|
| 215 |
+
def test_create_empty_copy(self):
|
| 216 |
+
G = nx.create_empty_copy(self.G, with_data=False)
|
| 217 |
+
assert nodes_equal(G, list(self.G))
|
| 218 |
+
assert G.graph == {}
|
| 219 |
+
assert G._node == {}.fromkeys(self.G.nodes(), {})
|
| 220 |
+
assert G._adj == {}.fromkeys(self.G.nodes(), {})
|
| 221 |
+
G = nx.create_empty_copy(self.G)
|
| 222 |
+
assert nodes_equal(G, list(self.G))
|
| 223 |
+
assert G.graph == self.G.graph
|
| 224 |
+
assert G._node == self.G._node
|
| 225 |
+
assert G._adj == {}.fromkeys(self.G.nodes(), {})
|
| 226 |
+
|
| 227 |
+
def test_degree_histogram(self):
|
| 228 |
+
assert nx.degree_histogram(self.G) == [1, 1, 1, 1, 1]
|
| 229 |
+
|
| 230 |
+
def test_density(self):
|
| 231 |
+
assert nx.density(self.G) == 0.5
|
| 232 |
+
assert nx.density(self.DG) == 0.3
|
| 233 |
+
G = nx.Graph()
|
| 234 |
+
G.add_node(1)
|
| 235 |
+
assert nx.density(G) == 0.0
|
| 236 |
+
|
| 237 |
+
def test_density_selfloop(self):
|
| 238 |
+
G = nx.Graph()
|
| 239 |
+
G.add_edge(1, 1)
|
| 240 |
+
assert nx.density(G) == 0.0
|
| 241 |
+
G.add_edge(1, 2)
|
| 242 |
+
assert nx.density(G) == 2.0
|
| 243 |
+
|
| 244 |
+
def test_freeze(self):
|
| 245 |
+
G = nx.freeze(self.G)
|
| 246 |
+
assert G.frozen
|
| 247 |
+
pytest.raises(nx.NetworkXError, G.add_node, 1)
|
| 248 |
+
pytest.raises(nx.NetworkXError, G.add_nodes_from, [1])
|
| 249 |
+
pytest.raises(nx.NetworkXError, G.remove_node, 1)
|
| 250 |
+
pytest.raises(nx.NetworkXError, G.remove_nodes_from, [1])
|
| 251 |
+
pytest.raises(nx.NetworkXError, G.add_edge, 1, 2)
|
| 252 |
+
pytest.raises(nx.NetworkXError, G.add_edges_from, [(1, 2)])
|
| 253 |
+
pytest.raises(nx.NetworkXError, G.remove_edge, 1, 2)
|
| 254 |
+
pytest.raises(nx.NetworkXError, G.remove_edges_from, [(1, 2)])
|
| 255 |
+
pytest.raises(nx.NetworkXError, G.clear_edges)
|
| 256 |
+
pytest.raises(nx.NetworkXError, G.clear)
|
| 257 |
+
|
| 258 |
+
def test_is_frozen(self):
|
| 259 |
+
assert not nx.is_frozen(self.G)
|
| 260 |
+
G = nx.freeze(self.G)
|
| 261 |
+
assert G.frozen == nx.is_frozen(self.G)
|
| 262 |
+
assert G.frozen
|
| 263 |
+
|
| 264 |
+
def test_node_attributes_are_still_mutable_on_frozen_graph(self):
|
| 265 |
+
G = nx.freeze(nx.path_graph(3))
|
| 266 |
+
node = G.nodes[0]
|
| 267 |
+
node["node_attribute"] = True
|
| 268 |
+
assert node["node_attribute"] == True
|
| 269 |
+
|
| 270 |
+
def test_edge_attributes_are_still_mutable_on_frozen_graph(self):
|
| 271 |
+
G = nx.freeze(nx.path_graph(3))
|
| 272 |
+
edge = G.edges[(0, 1)]
|
| 273 |
+
edge["edge_attribute"] = True
|
| 274 |
+
assert edge["edge_attribute"] == True
|
| 275 |
+
|
| 276 |
+
def test_neighbors_complete_graph(self):
|
| 277 |
+
graph = nx.complete_graph(100)
|
| 278 |
+
pop = random.sample(list(graph), 1)
|
| 279 |
+
nbors = list(nx.neighbors(graph, pop[0]))
|
| 280 |
+
# should be all the other vertices in the graph
|
| 281 |
+
assert len(nbors) == len(graph) - 1
|
| 282 |
+
|
| 283 |
+
graph = nx.path_graph(100)
|
| 284 |
+
node = random.sample(list(graph), 1)[0]
|
| 285 |
+
nbors = list(nx.neighbors(graph, node))
|
| 286 |
+
# should be all the other vertices in the graph
|
| 287 |
+
if node != 0 and node != 99:
|
| 288 |
+
assert len(nbors) == 2
|
| 289 |
+
else:
|
| 290 |
+
assert len(nbors) == 1
|
| 291 |
+
|
| 292 |
+
# create a star graph with 99 outer nodes
|
| 293 |
+
graph = nx.star_graph(99)
|
| 294 |
+
nbors = list(nx.neighbors(graph, 0))
|
| 295 |
+
assert len(nbors) == 99
|
| 296 |
+
|
| 297 |
+
def test_non_neighbors(self):
|
| 298 |
+
graph = nx.complete_graph(100)
|
| 299 |
+
pop = random.sample(list(graph), 1)
|
| 300 |
+
nbors = list(nx.non_neighbors(graph, pop[0]))
|
| 301 |
+
# should be all the other vertices in the graph
|
| 302 |
+
assert len(nbors) == 0
|
| 303 |
+
|
| 304 |
+
graph = nx.path_graph(100)
|
| 305 |
+
node = random.sample(list(graph), 1)[0]
|
| 306 |
+
nbors = list(nx.non_neighbors(graph, node))
|
| 307 |
+
# should be all the other vertices in the graph
|
| 308 |
+
if node != 0 and node != 99:
|
| 309 |
+
assert len(nbors) == 97
|
| 310 |
+
else:
|
| 311 |
+
assert len(nbors) == 98
|
| 312 |
+
|
| 313 |
+
# create a star graph with 99 outer nodes
|
| 314 |
+
graph = nx.star_graph(99)
|
| 315 |
+
nbors = list(nx.non_neighbors(graph, 0))
|
| 316 |
+
assert len(nbors) == 0
|
| 317 |
+
|
| 318 |
+
# disconnected graph
|
| 319 |
+
graph = nx.Graph()
|
| 320 |
+
graph.add_nodes_from(range(10))
|
| 321 |
+
nbors = list(nx.non_neighbors(graph, 0))
|
| 322 |
+
assert len(nbors) == 9
|
| 323 |
+
|
| 324 |
+
def test_non_edges(self):
|
| 325 |
+
# All possible edges exist
|
| 326 |
+
graph = nx.complete_graph(5)
|
| 327 |
+
nedges = list(nx.non_edges(graph))
|
| 328 |
+
assert len(nedges) == 0
|
| 329 |
+
|
| 330 |
+
graph = nx.path_graph(4)
|
| 331 |
+
expected = [(0, 2), (0, 3), (1, 3)]
|
| 332 |
+
nedges = list(nx.non_edges(graph))
|
| 333 |
+
for u, v in expected:
|
| 334 |
+
assert (u, v) in nedges or (v, u) in nedges
|
| 335 |
+
|
| 336 |
+
graph = nx.star_graph(4)
|
| 337 |
+
expected = [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
|
| 338 |
+
nedges = list(nx.non_edges(graph))
|
| 339 |
+
for u, v in expected:
|
| 340 |
+
assert (u, v) in nedges or (v, u) in nedges
|
| 341 |
+
|
| 342 |
+
# Directed graphs
|
| 343 |
+
graph = nx.DiGraph()
|
| 344 |
+
graph.add_edges_from([(0, 2), (2, 0), (2, 1)])
|
| 345 |
+
expected = [(0, 1), (1, 0), (1, 2)]
|
| 346 |
+
nedges = list(nx.non_edges(graph))
|
| 347 |
+
for e in expected:
|
| 348 |
+
assert e in nedges
|
| 349 |
+
|
| 350 |
+
def test_is_weighted(self):
|
| 351 |
+
G = nx.Graph()
|
| 352 |
+
assert not nx.is_weighted(G)
|
| 353 |
+
|
| 354 |
+
G = nx.path_graph(4)
|
| 355 |
+
assert not nx.is_weighted(G)
|
| 356 |
+
assert not nx.is_weighted(G, (2, 3))
|
| 357 |
+
|
| 358 |
+
G.add_node(4)
|
| 359 |
+
G.add_edge(3, 4, weight=4)
|
| 360 |
+
assert not nx.is_weighted(G)
|
| 361 |
+
assert nx.is_weighted(G, (3, 4))
|
| 362 |
+
|
| 363 |
+
G = nx.DiGraph()
|
| 364 |
+
G.add_weighted_edges_from(
|
| 365 |
+
[
|
| 366 |
+
("0", "3", 3),
|
| 367 |
+
("0", "1", -5),
|
| 368 |
+
("1", "0", -5),
|
| 369 |
+
("0", "2", 2),
|
| 370 |
+
("1", "2", 4),
|
| 371 |
+
("2", "3", 1),
|
| 372 |
+
]
|
| 373 |
+
)
|
| 374 |
+
assert nx.is_weighted(G)
|
| 375 |
+
assert nx.is_weighted(G, ("1", "0"))
|
| 376 |
+
|
| 377 |
+
G = G.to_undirected()
|
| 378 |
+
assert nx.is_weighted(G)
|
| 379 |
+
assert nx.is_weighted(G, ("1", "0"))
|
| 380 |
+
|
| 381 |
+
pytest.raises(nx.NetworkXError, nx.is_weighted, G, (1, 2))
|
| 382 |
+
|
| 383 |
+
def test_is_negatively_weighted(self):
|
| 384 |
+
G = nx.Graph()
|
| 385 |
+
assert not nx.is_negatively_weighted(G)
|
| 386 |
+
|
| 387 |
+
G.add_node(1)
|
| 388 |
+
G.add_nodes_from([2, 3, 4, 5])
|
| 389 |
+
assert not nx.is_negatively_weighted(G)
|
| 390 |
+
|
| 391 |
+
G.add_edge(1, 2, weight=4)
|
| 392 |
+
assert not nx.is_negatively_weighted(G, (1, 2))
|
| 393 |
+
|
| 394 |
+
G.add_edges_from([(1, 3), (2, 4), (2, 6)])
|
| 395 |
+
G[1][3]["color"] = "blue"
|
| 396 |
+
assert not nx.is_negatively_weighted(G)
|
| 397 |
+
assert not nx.is_negatively_weighted(G, (1, 3))
|
| 398 |
+
|
| 399 |
+
G[2][4]["weight"] = -2
|
| 400 |
+
assert nx.is_negatively_weighted(G, (2, 4))
|
| 401 |
+
assert nx.is_negatively_weighted(G)
|
| 402 |
+
|
| 403 |
+
G = nx.DiGraph()
|
| 404 |
+
G.add_weighted_edges_from(
|
| 405 |
+
[
|
| 406 |
+
("0", "3", 3),
|
| 407 |
+
("0", "1", -5),
|
| 408 |
+
("1", "0", -2),
|
| 409 |
+
("0", "2", 2),
|
| 410 |
+
("1", "2", -3),
|
| 411 |
+
("2", "3", 1),
|
| 412 |
+
]
|
| 413 |
+
)
|
| 414 |
+
assert nx.is_negatively_weighted(G)
|
| 415 |
+
assert not nx.is_negatively_weighted(G, ("0", "3"))
|
| 416 |
+
assert nx.is_negatively_weighted(G, ("1", "0"))
|
| 417 |
+
|
| 418 |
+
pytest.raises(nx.NetworkXError, nx.is_negatively_weighted, G, (1, 4))
|
| 419 |
+
|
| 420 |
+
|
| 421 |
+
class TestCommonNeighbors:
|
| 422 |
+
@classmethod
|
| 423 |
+
def setup_class(cls):
|
| 424 |
+
cls.func = staticmethod(nx.common_neighbors)
|
| 425 |
+
|
| 426 |
+
def test_func(G, u, v, expected):
|
| 427 |
+
result = sorted(cls.func(G, u, v))
|
| 428 |
+
assert result == expected
|
| 429 |
+
|
| 430 |
+
cls.test = staticmethod(test_func)
|
| 431 |
+
|
| 432 |
+
def test_K5(self):
|
| 433 |
+
G = nx.complete_graph(5)
|
| 434 |
+
self.test(G, 0, 1, [2, 3, 4])
|
| 435 |
+
|
| 436 |
+
def test_P3(self):
|
| 437 |
+
G = nx.path_graph(3)
|
| 438 |
+
self.test(G, 0, 2, [1])
|
| 439 |
+
|
| 440 |
+
def test_S4(self):
|
| 441 |
+
G = nx.star_graph(4)
|
| 442 |
+
self.test(G, 1, 2, [0])
|
| 443 |
+
|
| 444 |
+
def test_digraph(self):
|
| 445 |
+
with pytest.raises(nx.NetworkXNotImplemented):
|
| 446 |
+
G = nx.DiGraph()
|
| 447 |
+
G.add_edges_from([(0, 1), (1, 2)])
|
| 448 |
+
self.func(G, 0, 2)
|
| 449 |
+
|
| 450 |
+
def test_nonexistent_nodes(self):
|
| 451 |
+
G = nx.complete_graph(5)
|
| 452 |
+
pytest.raises(nx.NetworkXError, nx.common_neighbors, G, 5, 4)
|
| 453 |
+
pytest.raises(nx.NetworkXError, nx.common_neighbors, G, 4, 5)
|
| 454 |
+
pytest.raises(nx.NetworkXError, nx.common_neighbors, G, 5, 6)
|
| 455 |
+
|
| 456 |
+
def test_custom1(self):
|
| 457 |
+
"""Case of no common neighbors."""
|
| 458 |
+
G = nx.Graph()
|
| 459 |
+
G.add_nodes_from([0, 1])
|
| 460 |
+
self.test(G, 0, 1, [])
|
| 461 |
+
|
| 462 |
+
def test_custom2(self):
|
| 463 |
+
"""Case of equal nodes."""
|
| 464 |
+
G = nx.complete_graph(4)
|
| 465 |
+
self.test(G, 0, 0, [1, 2, 3])
|
| 466 |
+
|
| 467 |
+
|
| 468 |
+
@pytest.mark.parametrize(
|
| 469 |
+
"graph_type", (nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph)
|
| 470 |
+
)
|
| 471 |
+
def test_set_node_attributes(graph_type):
|
| 472 |
+
# Test single value
|
| 473 |
+
G = nx.path_graph(3, create_using=graph_type)
|
| 474 |
+
vals = 100
|
| 475 |
+
attr = "hello"
|
| 476 |
+
nx.set_node_attributes(G, vals, attr)
|
| 477 |
+
assert G.nodes[0][attr] == vals
|
| 478 |
+
assert G.nodes[1][attr] == vals
|
| 479 |
+
assert G.nodes[2][attr] == vals
|
| 480 |
+
|
| 481 |
+
# Test dictionary
|
| 482 |
+
G = nx.path_graph(3, create_using=graph_type)
|
| 483 |
+
vals = dict(zip(sorted(G.nodes()), range(len(G))))
|
| 484 |
+
attr = "hi"
|
| 485 |
+
nx.set_node_attributes(G, vals, attr)
|
| 486 |
+
assert G.nodes[0][attr] == 0
|
| 487 |
+
assert G.nodes[1][attr] == 1
|
| 488 |
+
assert G.nodes[2][attr] == 2
|
| 489 |
+
|
| 490 |
+
# Test dictionary of dictionaries
|
| 491 |
+
G = nx.path_graph(3, create_using=graph_type)
|
| 492 |
+
d = {"hi": 0, "hello": 200}
|
| 493 |
+
vals = dict.fromkeys(G.nodes(), d)
|
| 494 |
+
vals.pop(0)
|
| 495 |
+
nx.set_node_attributes(G, vals)
|
| 496 |
+
assert G.nodes[0] == {}
|
| 497 |
+
assert G.nodes[1]["hi"] == 0
|
| 498 |
+
assert G.nodes[2]["hello"] == 200
|
| 499 |
+
|
| 500 |
+
|
| 501 |
+
@pytest.mark.parametrize(
|
| 502 |
+
("values", "name"),
|
| 503 |
+
(
|
| 504 |
+
({0: "red", 1: "blue"}, "color"), # values dictionary
|
| 505 |
+
({0: {"color": "red"}, 1: {"color": "blue"}}, None), # dict-of-dict
|
| 506 |
+
),
|
| 507 |
+
)
|
| 508 |
+
def test_set_node_attributes_ignores_extra_nodes(values, name):
|
| 509 |
+
"""
|
| 510 |
+
When `values` is a dict or dict-of-dict keyed by nodes, ensure that keys
|
| 511 |
+
that correspond to nodes not in G are ignored.
|
| 512 |
+
"""
|
| 513 |
+
G = nx.Graph()
|
| 514 |
+
G.add_node(0)
|
| 515 |
+
nx.set_node_attributes(G, values, name)
|
| 516 |
+
assert G.nodes[0]["color"] == "red"
|
| 517 |
+
assert 1 not in G.nodes
|
| 518 |
+
|
| 519 |
+
|
| 520 |
+
@pytest.mark.parametrize("graph_type", (nx.Graph, nx.DiGraph))
|
| 521 |
+
def test_set_edge_attributes(graph_type):
|
| 522 |
+
# Test single value
|
| 523 |
+
G = nx.path_graph(3, create_using=graph_type)
|
| 524 |
+
attr = "hello"
|
| 525 |
+
vals = 3
|
| 526 |
+
nx.set_edge_attributes(G, vals, attr)
|
| 527 |
+
assert G[0][1][attr] == vals
|
| 528 |
+
assert G[1][2][attr] == vals
|
| 529 |
+
|
| 530 |
+
# Test multiple values
|
| 531 |
+
G = nx.path_graph(3, create_using=graph_type)
|
| 532 |
+
attr = "hi"
|
| 533 |
+
edges = [(0, 1), (1, 2)]
|
| 534 |
+
vals = dict(zip(edges, range(len(edges))))
|
| 535 |
+
nx.set_edge_attributes(G, vals, attr)
|
| 536 |
+
assert G[0][1][attr] == 0
|
| 537 |
+
assert G[1][2][attr] == 1
|
| 538 |
+
|
| 539 |
+
# Test dictionary of dictionaries
|
| 540 |
+
G = nx.path_graph(3, create_using=graph_type)
|
| 541 |
+
d = {"hi": 0, "hello": 200}
|
| 542 |
+
edges = [(0, 1)]
|
| 543 |
+
vals = dict.fromkeys(edges, d)
|
| 544 |
+
nx.set_edge_attributes(G, vals)
|
| 545 |
+
assert G[0][1]["hi"] == 0
|
| 546 |
+
assert G[0][1]["hello"] == 200
|
| 547 |
+
assert G[1][2] == {}
|
| 548 |
+
|
| 549 |
+
|
| 550 |
+
@pytest.mark.parametrize(
|
| 551 |
+
("values", "name"),
|
| 552 |
+
(
|
| 553 |
+
({(0, 1): 1.0, (0, 2): 2.0}, "weight"), # values dict
|
| 554 |
+
({(0, 1): {"weight": 1.0}, (0, 2): {"weight": 2.0}}, None), # values dod
|
| 555 |
+
),
|
| 556 |
+
)
|
| 557 |
+
def test_set_edge_attributes_ignores_extra_edges(values, name):
|
| 558 |
+
"""If `values` is a dict or dict-of-dicts containing edges that are not in
|
| 559 |
+
G, data associate with these edges should be ignored.
|
| 560 |
+
"""
|
| 561 |
+
G = nx.Graph([(0, 1)])
|
| 562 |
+
nx.set_edge_attributes(G, values, name)
|
| 563 |
+
assert G[0][1]["weight"] == 1.0
|
| 564 |
+
assert (0, 2) not in G.edges
|
| 565 |
+
|
| 566 |
+
|
| 567 |
+
@pytest.mark.parametrize("graph_type", (nx.MultiGraph, nx.MultiDiGraph))
|
| 568 |
+
def test_set_edge_attributes_multi(graph_type):
|
| 569 |
+
# Test single value
|
| 570 |
+
G = nx.path_graph(3, create_using=graph_type)
|
| 571 |
+
attr = "hello"
|
| 572 |
+
vals = 3
|
| 573 |
+
nx.set_edge_attributes(G, vals, attr)
|
| 574 |
+
assert G[0][1][0][attr] == vals
|
| 575 |
+
assert G[1][2][0][attr] == vals
|
| 576 |
+
|
| 577 |
+
# Test multiple values
|
| 578 |
+
G = nx.path_graph(3, create_using=graph_type)
|
| 579 |
+
attr = "hi"
|
| 580 |
+
edges = [(0, 1, 0), (1, 2, 0)]
|
| 581 |
+
vals = dict(zip(edges, range(len(edges))))
|
| 582 |
+
nx.set_edge_attributes(G, vals, attr)
|
| 583 |
+
assert G[0][1][0][attr] == 0
|
| 584 |
+
assert G[1][2][0][attr] == 1
|
| 585 |
+
|
| 586 |
+
# Test dictionary of dictionaries
|
| 587 |
+
G = nx.path_graph(3, create_using=graph_type)
|
| 588 |
+
d = {"hi": 0, "hello": 200}
|
| 589 |
+
edges = [(0, 1, 0)]
|
| 590 |
+
vals = dict.fromkeys(edges, d)
|
| 591 |
+
nx.set_edge_attributes(G, vals)
|
| 592 |
+
assert G[0][1][0]["hi"] == 0
|
| 593 |
+
assert G[0][1][0]["hello"] == 200
|
| 594 |
+
assert G[1][2][0] == {}
|
| 595 |
+
|
| 596 |
+
|
| 597 |
+
@pytest.mark.parametrize(
|
| 598 |
+
("values", "name"),
|
| 599 |
+
(
|
| 600 |
+
({(0, 1, 0): 1.0, (0, 2, 0): 2.0}, "weight"), # values dict
|
| 601 |
+
({(0, 1, 0): {"weight": 1.0}, (0, 2, 0): {"weight": 2.0}}, None), # values dod
|
| 602 |
+
),
|
| 603 |
+
)
|
| 604 |
+
def test_set_edge_attributes_multi_ignores_extra_edges(values, name):
|
| 605 |
+
"""If `values` is a dict or dict-of-dicts containing edges that are not in
|
| 606 |
+
G, data associate with these edges should be ignored.
|
| 607 |
+
"""
|
| 608 |
+
G = nx.MultiGraph([(0, 1, 0), (0, 1, 1)])
|
| 609 |
+
nx.set_edge_attributes(G, values, name)
|
| 610 |
+
assert G[0][1][0]["weight"] == 1.0
|
| 611 |
+
assert G[0][1][1] == {}
|
| 612 |
+
assert (0, 2) not in G.edges()
|
| 613 |
+
|
| 614 |
+
|
| 615 |
+
def test_get_node_attributes():
|
| 616 |
+
graphs = [nx.Graph(), nx.DiGraph(), nx.MultiGraph(), nx.MultiDiGraph()]
|
| 617 |
+
for G in graphs:
|
| 618 |
+
G = nx.path_graph(3, create_using=G)
|
| 619 |
+
attr = "hello"
|
| 620 |
+
vals = 100
|
| 621 |
+
nx.set_node_attributes(G, vals, attr)
|
| 622 |
+
attrs = nx.get_node_attributes(G, attr)
|
| 623 |
+
assert attrs[0] == vals
|
| 624 |
+
assert attrs[1] == vals
|
| 625 |
+
assert attrs[2] == vals
|
| 626 |
+
default_val = 1
|
| 627 |
+
G.add_node(4)
|
| 628 |
+
attrs = nx.get_node_attributes(G, attr, default=default_val)
|
| 629 |
+
assert attrs[4] == default_val
|
| 630 |
+
|
| 631 |
+
|
| 632 |
+
def test_get_edge_attributes():
|
| 633 |
+
graphs = [nx.Graph(), nx.DiGraph(), nx.MultiGraph(), nx.MultiDiGraph()]
|
| 634 |
+
for G in graphs:
|
| 635 |
+
G = nx.path_graph(3, create_using=G)
|
| 636 |
+
attr = "hello"
|
| 637 |
+
vals = 100
|
| 638 |
+
nx.set_edge_attributes(G, vals, attr)
|
| 639 |
+
attrs = nx.get_edge_attributes(G, attr)
|
| 640 |
+
assert len(attrs) == 2
|
| 641 |
+
|
| 642 |
+
for edge in G.edges:
|
| 643 |
+
assert attrs[edge] == vals
|
| 644 |
+
|
| 645 |
+
default_val = vals
|
| 646 |
+
G.add_edge(4, 5)
|
| 647 |
+
deafult_attrs = nx.get_edge_attributes(G, attr, default=default_val)
|
| 648 |
+
assert len(deafult_attrs) == 3
|
| 649 |
+
|
| 650 |
+
for edge in G.edges:
|
| 651 |
+
assert deafult_attrs[edge] == vals
|
| 652 |
+
|
| 653 |
+
|
| 654 |
+
def test_is_empty():
|
| 655 |
+
graphs = [nx.Graph(), nx.DiGraph(), nx.MultiGraph(), nx.MultiDiGraph()]
|
| 656 |
+
for G in graphs:
|
| 657 |
+
assert nx.is_empty(G)
|
| 658 |
+
G.add_nodes_from(range(5))
|
| 659 |
+
assert nx.is_empty(G)
|
| 660 |
+
G.add_edges_from([(1, 2), (3, 4)])
|
| 661 |
+
assert not nx.is_empty(G)
|
| 662 |
+
|
| 663 |
+
|
| 664 |
+
@pytest.mark.parametrize(
|
| 665 |
+
"graph_type", [nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph]
|
| 666 |
+
)
|
| 667 |
+
def test_selfloops(graph_type):
|
| 668 |
+
G = nx.complete_graph(3, create_using=graph_type)
|
| 669 |
+
G.add_edge(0, 0)
|
| 670 |
+
assert nodes_equal(nx.nodes_with_selfloops(G), [0])
|
| 671 |
+
assert edges_equal(nx.selfloop_edges(G), [(0, 0)])
|
| 672 |
+
assert edges_equal(nx.selfloop_edges(G, data=True), [(0, 0, {})])
|
| 673 |
+
assert nx.number_of_selfloops(G) == 1
|
| 674 |
+
|
| 675 |
+
|
| 676 |
+
@pytest.mark.parametrize(
|
| 677 |
+
"graph_type", [nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph]
|
| 678 |
+
)
|
| 679 |
+
def test_selfloop_edges_attr(graph_type):
|
| 680 |
+
G = nx.complete_graph(3, create_using=graph_type)
|
| 681 |
+
G.add_edge(0, 0)
|
| 682 |
+
G.add_edge(1, 1, weight=2)
|
| 683 |
+
assert edges_equal(
|
| 684 |
+
nx.selfloop_edges(G, data=True), [(0, 0, {}), (1, 1, {"weight": 2})]
|
| 685 |
+
)
|
| 686 |
+
assert edges_equal(nx.selfloop_edges(G, data="weight"), [(0, 0, None), (1, 1, 2)])
|
| 687 |
+
|
| 688 |
+
|
| 689 |
+
def test_selfloop_edges_multi_with_data_and_keys():
|
| 690 |
+
G = nx.complete_graph(3, create_using=nx.MultiGraph)
|
| 691 |
+
G.add_edge(0, 0, weight=10)
|
| 692 |
+
G.add_edge(0, 0, weight=100)
|
| 693 |
+
assert edges_equal(
|
| 694 |
+
nx.selfloop_edges(G, data="weight", keys=True), [(0, 0, 0, 10), (0, 0, 1, 100)]
|
| 695 |
+
)
|
| 696 |
+
|
| 697 |
+
|
| 698 |
+
@pytest.mark.parametrize("graph_type", [nx.Graph, nx.DiGraph])
|
| 699 |
+
def test_selfloops_removal(graph_type):
|
| 700 |
+
G = nx.complete_graph(3, create_using=graph_type)
|
| 701 |
+
G.add_edge(0, 0)
|
| 702 |
+
G.remove_edges_from(nx.selfloop_edges(G, keys=True))
|
| 703 |
+
G.add_edge(0, 0)
|
| 704 |
+
G.remove_edges_from(nx.selfloop_edges(G, data=True))
|
| 705 |
+
G.add_edge(0, 0)
|
| 706 |
+
G.remove_edges_from(nx.selfloop_edges(G, keys=True, data=True))
|
| 707 |
+
|
| 708 |
+
|
| 709 |
+
@pytest.mark.parametrize("graph_type", [nx.MultiGraph, nx.MultiDiGraph])
|
| 710 |
+
def test_selfloops_removal_multi(graph_type):
|
| 711 |
+
"""test removing selfloops behavior vis-a-vis altering a dict while iterating.
|
| 712 |
+
cf. gh-4068"""
|
| 713 |
+
G = nx.complete_graph(3, create_using=graph_type)
|
| 714 |
+
# Defaults - see gh-4080
|
| 715 |
+
G.add_edge(0, 0)
|
| 716 |
+
G.add_edge(0, 0)
|
| 717 |
+
G.remove_edges_from(nx.selfloop_edges(G))
|
| 718 |
+
assert (0, 0) not in G.edges()
|
| 719 |
+
# With keys
|
| 720 |
+
G.add_edge(0, 0)
|
| 721 |
+
G.add_edge(0, 0)
|
| 722 |
+
with pytest.raises(RuntimeError):
|
| 723 |
+
G.remove_edges_from(nx.selfloop_edges(G, keys=True))
|
| 724 |
+
# With data
|
| 725 |
+
G.add_edge(0, 0)
|
| 726 |
+
G.add_edge(0, 0)
|
| 727 |
+
with pytest.raises(TypeError):
|
| 728 |
+
G.remove_edges_from(nx.selfloop_edges(G, data=True))
|
| 729 |
+
# With keys and data
|
| 730 |
+
G.add_edge(0, 0)
|
| 731 |
+
G.add_edge(0, 0)
|
| 732 |
+
with pytest.raises(RuntimeError):
|
| 733 |
+
G.remove_edges_from(nx.selfloop_edges(G, data=True, keys=True))
|
| 734 |
+
|
| 735 |
+
|
| 736 |
+
def test_pathweight():
|
| 737 |
+
valid_path = [1, 2, 3]
|
| 738 |
+
invalid_path = [1, 3, 2]
|
| 739 |
+
graphs = [nx.Graph(), nx.DiGraph(), nx.MultiGraph(), nx.MultiDiGraph()]
|
| 740 |
+
edges = [
|
| 741 |
+
(1, 2, {"cost": 5, "dist": 6}),
|
| 742 |
+
(2, 3, {"cost": 3, "dist": 4}),
|
| 743 |
+
(1, 2, {"cost": 1, "dist": 2}),
|
| 744 |
+
]
|
| 745 |
+
for graph in graphs:
|
| 746 |
+
graph.add_edges_from(edges)
|
| 747 |
+
assert nx.path_weight(graph, valid_path, "cost") == 4
|
| 748 |
+
assert nx.path_weight(graph, valid_path, "dist") == 6
|
| 749 |
+
pytest.raises(nx.NetworkXNoPath, nx.path_weight, graph, invalid_path, "cost")
|
| 750 |
+
|
| 751 |
+
|
| 752 |
+
@pytest.mark.parametrize(
|
| 753 |
+
"G", (nx.Graph(), nx.DiGraph(), nx.MultiGraph(), nx.MultiDiGraph())
|
| 754 |
+
)
|
| 755 |
+
def test_ispath(G):
|
| 756 |
+
G.add_edges_from([(1, 2), (2, 3), (1, 2), (3, 4)])
|
| 757 |
+
valid_path = [1, 2, 3, 4]
|
| 758 |
+
invalid_path = [1, 2, 4, 3] # wrong node order
|
| 759 |
+
another_invalid_path = [1, 2, 3, 4, 5] # contains node not in G
|
| 760 |
+
assert nx.is_path(G, valid_path)
|
| 761 |
+
assert not nx.is_path(G, invalid_path)
|
| 762 |
+
assert not nx.is_path(G, another_invalid_path)
|
| 763 |
+
|
| 764 |
+
|
| 765 |
+
@pytest.mark.parametrize("G", (nx.Graph(), nx.DiGraph()))
|
| 766 |
+
def test_restricted_view(G):
|
| 767 |
+
G.add_edges_from([(0, 1), (0, 2), (0, 3), (1, 0), (1, 1), (1, 2)])
|
| 768 |
+
G.add_node(4)
|
| 769 |
+
H = nx.restricted_view(G, [0, 2, 5], [(1, 2), (3, 4)])
|
| 770 |
+
assert set(H.nodes()) == {1, 3, 4}
|
| 771 |
+
assert set(H.edges()) == {(1, 1)}
|
| 772 |
+
|
| 773 |
+
|
| 774 |
+
@pytest.mark.parametrize("G", (nx.MultiGraph(), nx.MultiDiGraph()))
|
| 775 |
+
def test_restricted_view_multi(G):
|
| 776 |
+
G.add_edges_from(
|
| 777 |
+
[(0, 1, 0), (0, 2, 0), (0, 3, 0), (0, 1, 1), (1, 0, 0), (1, 1, 0), (1, 2, 0)]
|
| 778 |
+
)
|
| 779 |
+
G.add_node(4)
|
| 780 |
+
H = nx.restricted_view(G, [0, 2, 5], [(1, 2, 0), (3, 4, 0)])
|
| 781 |
+
assert set(H.nodes()) == {1, 3, 4}
|
| 782 |
+
assert set(H.edges()) == {(1, 1)}
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/classes/tests/test_graph.py
ADDED
|
@@ -0,0 +1,920 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gc
|
| 2 |
+
import pickle
|
| 3 |
+
import platform
|
| 4 |
+
import weakref
|
| 5 |
+
|
| 6 |
+
import pytest
|
| 7 |
+
|
| 8 |
+
import networkx as nx
|
| 9 |
+
from networkx.utils import edges_equal, graphs_equal, nodes_equal
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class BaseGraphTester:
|
| 13 |
+
"""Tests for data-structure independent graph class features."""
|
| 14 |
+
|
| 15 |
+
def test_contains(self):
|
| 16 |
+
G = self.K3
|
| 17 |
+
assert 1 in G
|
| 18 |
+
assert 4 not in G
|
| 19 |
+
assert "b" not in G
|
| 20 |
+
assert [] not in G # no exception for nonhashable
|
| 21 |
+
assert {1: 1} not in G # no exception for nonhashable
|
| 22 |
+
|
| 23 |
+
def test_order(self):
|
| 24 |
+
G = self.K3
|
| 25 |
+
assert len(G) == 3
|
| 26 |
+
assert G.order() == 3
|
| 27 |
+
assert G.number_of_nodes() == 3
|
| 28 |
+
|
| 29 |
+
def test_nodes(self):
|
| 30 |
+
G = self.K3
|
| 31 |
+
assert isinstance(G._node, G.node_dict_factory)
|
| 32 |
+
assert isinstance(G._adj, G.adjlist_outer_dict_factory)
|
| 33 |
+
assert all(
|
| 34 |
+
isinstance(adj, G.adjlist_inner_dict_factory) for adj in G._adj.values()
|
| 35 |
+
)
|
| 36 |
+
assert sorted(G.nodes()) == self.k3nodes
|
| 37 |
+
assert sorted(G.nodes(data=True)) == [(0, {}), (1, {}), (2, {})]
|
| 38 |
+
|
| 39 |
+
def test_none_node(self):
|
| 40 |
+
G = self.Graph()
|
| 41 |
+
with pytest.raises(ValueError):
|
| 42 |
+
G.add_node(None)
|
| 43 |
+
with pytest.raises(ValueError):
|
| 44 |
+
G.add_nodes_from([None])
|
| 45 |
+
with pytest.raises(ValueError):
|
| 46 |
+
G.add_edge(0, None)
|
| 47 |
+
with pytest.raises(ValueError):
|
| 48 |
+
G.add_edges_from([(0, None)])
|
| 49 |
+
|
| 50 |
+
def test_has_node(self):
|
| 51 |
+
G = self.K3
|
| 52 |
+
assert G.has_node(1)
|
| 53 |
+
assert not G.has_node(4)
|
| 54 |
+
assert not G.has_node([]) # no exception for nonhashable
|
| 55 |
+
assert not G.has_node({1: 1}) # no exception for nonhashable
|
| 56 |
+
|
| 57 |
+
def test_has_edge(self):
|
| 58 |
+
G = self.K3
|
| 59 |
+
assert G.has_edge(0, 1)
|
| 60 |
+
assert not G.has_edge(0, -1)
|
| 61 |
+
|
| 62 |
+
def test_neighbors(self):
|
| 63 |
+
G = self.K3
|
| 64 |
+
assert sorted(G.neighbors(0)) == [1, 2]
|
| 65 |
+
with pytest.raises(nx.NetworkXError):
|
| 66 |
+
G.neighbors(-1)
|
| 67 |
+
|
| 68 |
+
@pytest.mark.skipif(
|
| 69 |
+
platform.python_implementation() == "PyPy", reason="PyPy gc is different"
|
| 70 |
+
)
|
| 71 |
+
def test_memory_leak(self):
|
| 72 |
+
G = self.Graph()
|
| 73 |
+
|
| 74 |
+
def count_objects_of_type(_type):
|
| 75 |
+
# Iterating over all objects tracked by gc can include weak references
|
| 76 |
+
# whose weakly-referenced objects may no longer exist. Calling `isinstance`
|
| 77 |
+
# on such a weak reference will raise ReferenceError. There are at least
|
| 78 |
+
# three workarounds for this: one is to compare type names instead of using
|
| 79 |
+
# `isinstance` such as `type(obj).__name__ == typename`, another is to use
|
| 80 |
+
# `type(obj) == _type`, and the last is to ignore ProxyTypes as we do below.
|
| 81 |
+
# NOTE: even if this safeguard is deemed unnecessary to pass NetworkX tests,
|
| 82 |
+
# we should still keep it for maximum safety for other NetworkX backends.
|
| 83 |
+
return sum(
|
| 84 |
+
1
|
| 85 |
+
for obj in gc.get_objects()
|
| 86 |
+
if not isinstance(obj, weakref.ProxyTypes) and isinstance(obj, _type)
|
| 87 |
+
)
|
| 88 |
+
|
| 89 |
+
gc.collect()
|
| 90 |
+
before = count_objects_of_type(self.Graph)
|
| 91 |
+
G.copy()
|
| 92 |
+
gc.collect()
|
| 93 |
+
after = count_objects_of_type(self.Graph)
|
| 94 |
+
assert before == after
|
| 95 |
+
|
| 96 |
+
# test a subgraph of the base class
|
| 97 |
+
class MyGraph(self.Graph):
|
| 98 |
+
pass
|
| 99 |
+
|
| 100 |
+
gc.collect()
|
| 101 |
+
G = MyGraph()
|
| 102 |
+
before = count_objects_of_type(MyGraph)
|
| 103 |
+
G.copy()
|
| 104 |
+
gc.collect()
|
| 105 |
+
after = count_objects_of_type(MyGraph)
|
| 106 |
+
assert before == after
|
| 107 |
+
|
| 108 |
+
def test_edges(self):
|
| 109 |
+
G = self.K3
|
| 110 |
+
assert isinstance(G._adj, G.adjlist_outer_dict_factory)
|
| 111 |
+
assert edges_equal(G.edges(), [(0, 1), (0, 2), (1, 2)])
|
| 112 |
+
assert edges_equal(G.edges(0), [(0, 1), (0, 2)])
|
| 113 |
+
assert edges_equal(G.edges([0, 1]), [(0, 1), (0, 2), (1, 2)])
|
| 114 |
+
with pytest.raises(nx.NetworkXError):
|
| 115 |
+
G.edges(-1)
|
| 116 |
+
|
| 117 |
+
def test_degree(self):
|
| 118 |
+
G = self.K3
|
| 119 |
+
assert sorted(G.degree()) == [(0, 2), (1, 2), (2, 2)]
|
| 120 |
+
assert dict(G.degree()) == {0: 2, 1: 2, 2: 2}
|
| 121 |
+
assert G.degree(0) == 2
|
| 122 |
+
with pytest.raises(nx.NetworkXError):
|
| 123 |
+
G.degree(-1) # node not in graph
|
| 124 |
+
|
| 125 |
+
def test_size(self):
|
| 126 |
+
G = self.K3
|
| 127 |
+
assert G.size() == 3
|
| 128 |
+
assert G.number_of_edges() == 3
|
| 129 |
+
|
| 130 |
+
def test_nbunch_iter(self):
|
| 131 |
+
G = self.K3
|
| 132 |
+
assert nodes_equal(G.nbunch_iter(), self.k3nodes) # all nodes
|
| 133 |
+
assert nodes_equal(G.nbunch_iter(0), [0]) # single node
|
| 134 |
+
assert nodes_equal(G.nbunch_iter([0, 1]), [0, 1]) # sequence
|
| 135 |
+
# sequence with none in graph
|
| 136 |
+
assert nodes_equal(G.nbunch_iter([-1]), [])
|
| 137 |
+
# string sequence with none in graph
|
| 138 |
+
assert nodes_equal(G.nbunch_iter("foo"), [])
|
| 139 |
+
# node not in graph doesn't get caught upon creation of iterator
|
| 140 |
+
bunch = G.nbunch_iter(-1)
|
| 141 |
+
# but gets caught when iterator used
|
| 142 |
+
with pytest.raises(nx.NetworkXError, match="is not a node or a sequence"):
|
| 143 |
+
list(bunch)
|
| 144 |
+
# unhashable doesn't get caught upon creation of iterator
|
| 145 |
+
bunch = G.nbunch_iter([0, 1, 2, {}])
|
| 146 |
+
# but gets caught when iterator hits the unhashable
|
| 147 |
+
with pytest.raises(
|
| 148 |
+
nx.NetworkXError, match="in sequence nbunch is not a valid node"
|
| 149 |
+
):
|
| 150 |
+
list(bunch)
|
| 151 |
+
|
| 152 |
+
def test_nbunch_iter_node_format_raise(self):
|
| 153 |
+
# Tests that a node that would have failed string formatting
|
| 154 |
+
# doesn't cause an error when attempting to raise a
|
| 155 |
+
# :exc:`nx.NetworkXError`.
|
| 156 |
+
|
| 157 |
+
# For more information, see pull request #1813.
|
| 158 |
+
G = self.Graph()
|
| 159 |
+
nbunch = [("x", set())]
|
| 160 |
+
with pytest.raises(nx.NetworkXError):
|
| 161 |
+
list(G.nbunch_iter(nbunch))
|
| 162 |
+
|
| 163 |
+
def test_selfloop_degree(self):
|
| 164 |
+
G = self.Graph()
|
| 165 |
+
G.add_edge(1, 1)
|
| 166 |
+
assert sorted(G.degree()) == [(1, 2)]
|
| 167 |
+
assert dict(G.degree()) == {1: 2}
|
| 168 |
+
assert G.degree(1) == 2
|
| 169 |
+
assert sorted(G.degree([1])) == [(1, 2)]
|
| 170 |
+
assert G.degree(1, weight="weight") == 2
|
| 171 |
+
|
| 172 |
+
def test_selfloops(self):
|
| 173 |
+
G = self.K3.copy()
|
| 174 |
+
G.add_edge(0, 0)
|
| 175 |
+
assert nodes_equal(nx.nodes_with_selfloops(G), [0])
|
| 176 |
+
assert edges_equal(nx.selfloop_edges(G), [(0, 0)])
|
| 177 |
+
assert nx.number_of_selfloops(G) == 1
|
| 178 |
+
G.remove_edge(0, 0)
|
| 179 |
+
G.add_edge(0, 0)
|
| 180 |
+
G.remove_edges_from([(0, 0)])
|
| 181 |
+
G.add_edge(1, 1)
|
| 182 |
+
G.remove_node(1)
|
| 183 |
+
G.add_edge(0, 0)
|
| 184 |
+
G.add_edge(1, 1)
|
| 185 |
+
G.remove_nodes_from([0, 1])
|
| 186 |
+
|
| 187 |
+
def test_cache_reset(self):
|
| 188 |
+
G = self.K3.copy()
|
| 189 |
+
old_adj = G.adj
|
| 190 |
+
assert id(G.adj) == id(old_adj)
|
| 191 |
+
G._adj = {}
|
| 192 |
+
assert id(G.adj) != id(old_adj)
|
| 193 |
+
|
| 194 |
+
old_nodes = G.nodes
|
| 195 |
+
assert id(G.nodes) == id(old_nodes)
|
| 196 |
+
G._node = {}
|
| 197 |
+
assert id(G.nodes) != id(old_nodes)
|
| 198 |
+
|
| 199 |
+
def test_attributes_cached(self):
|
| 200 |
+
G = self.K3.copy()
|
| 201 |
+
assert id(G.nodes) == id(G.nodes)
|
| 202 |
+
assert id(G.edges) == id(G.edges)
|
| 203 |
+
assert id(G.degree) == id(G.degree)
|
| 204 |
+
assert id(G.adj) == id(G.adj)
|
| 205 |
+
|
| 206 |
+
|
| 207 |
+
class BaseAttrGraphTester(BaseGraphTester):
|
| 208 |
+
"""Tests of graph class attribute features."""
|
| 209 |
+
|
| 210 |
+
def test_weighted_degree(self):
|
| 211 |
+
G = self.Graph()
|
| 212 |
+
G.add_edge(1, 2, weight=2, other=3)
|
| 213 |
+
G.add_edge(2, 3, weight=3, other=4)
|
| 214 |
+
assert sorted(d for n, d in G.degree(weight="weight")) == [2, 3, 5]
|
| 215 |
+
assert dict(G.degree(weight="weight")) == {1: 2, 2: 5, 3: 3}
|
| 216 |
+
assert G.degree(1, weight="weight") == 2
|
| 217 |
+
assert nodes_equal((G.degree([1], weight="weight")), [(1, 2)])
|
| 218 |
+
|
| 219 |
+
assert nodes_equal((d for n, d in G.degree(weight="other")), [3, 7, 4])
|
| 220 |
+
assert dict(G.degree(weight="other")) == {1: 3, 2: 7, 3: 4}
|
| 221 |
+
assert G.degree(1, weight="other") == 3
|
| 222 |
+
assert edges_equal((G.degree([1], weight="other")), [(1, 3)])
|
| 223 |
+
|
| 224 |
+
def add_attributes(self, G):
|
| 225 |
+
G.graph["foo"] = []
|
| 226 |
+
G.nodes[0]["foo"] = []
|
| 227 |
+
G.remove_edge(1, 2)
|
| 228 |
+
ll = []
|
| 229 |
+
G.add_edge(1, 2, foo=ll)
|
| 230 |
+
G.add_edge(2, 1, foo=ll)
|
| 231 |
+
|
| 232 |
+
def test_name(self):
|
| 233 |
+
G = self.Graph(name="")
|
| 234 |
+
assert G.name == ""
|
| 235 |
+
G = self.Graph(name="test")
|
| 236 |
+
assert G.name == "test"
|
| 237 |
+
|
| 238 |
+
def test_str_unnamed(self):
|
| 239 |
+
G = self.Graph()
|
| 240 |
+
G.add_edges_from([(1, 2), (2, 3)])
|
| 241 |
+
assert str(G) == f"{type(G).__name__} with 3 nodes and 2 edges"
|
| 242 |
+
|
| 243 |
+
def test_str_named(self):
|
| 244 |
+
G = self.Graph(name="foo")
|
| 245 |
+
G.add_edges_from([(1, 2), (2, 3)])
|
| 246 |
+
assert str(G) == f"{type(G).__name__} named 'foo' with 3 nodes and 2 edges"
|
| 247 |
+
|
| 248 |
+
def test_graph_chain(self):
|
| 249 |
+
G = self.Graph([(0, 1), (1, 2)])
|
| 250 |
+
DG = G.to_directed(as_view=True)
|
| 251 |
+
SDG = DG.subgraph([0, 1])
|
| 252 |
+
RSDG = SDG.reverse(copy=False)
|
| 253 |
+
assert G is DG._graph
|
| 254 |
+
assert DG is SDG._graph
|
| 255 |
+
assert SDG is RSDG._graph
|
| 256 |
+
|
| 257 |
+
def test_copy(self):
|
| 258 |
+
G = self.Graph()
|
| 259 |
+
G.add_node(0)
|
| 260 |
+
G.add_edge(1, 2)
|
| 261 |
+
self.add_attributes(G)
|
| 262 |
+
# copy edge datadict but any container attr are same
|
| 263 |
+
H = G.copy()
|
| 264 |
+
self.graphs_equal(H, G)
|
| 265 |
+
self.different_attrdict(H, G)
|
| 266 |
+
self.shallow_copy_attrdict(H, G)
|
| 267 |
+
|
| 268 |
+
def test_class_copy(self):
|
| 269 |
+
G = self.Graph()
|
| 270 |
+
G.add_node(0)
|
| 271 |
+
G.add_edge(1, 2)
|
| 272 |
+
self.add_attributes(G)
|
| 273 |
+
# copy edge datadict but any container attr are same
|
| 274 |
+
H = G.__class__(G)
|
| 275 |
+
self.graphs_equal(H, G)
|
| 276 |
+
self.different_attrdict(H, G)
|
| 277 |
+
self.shallow_copy_attrdict(H, G)
|
| 278 |
+
|
| 279 |
+
def test_fresh_copy(self):
|
| 280 |
+
G = self.Graph()
|
| 281 |
+
G.add_node(0)
|
| 282 |
+
G.add_edge(1, 2)
|
| 283 |
+
self.add_attributes(G)
|
| 284 |
+
# copy graph structure but use fresh datadict
|
| 285 |
+
H = G.__class__()
|
| 286 |
+
H.add_nodes_from(G)
|
| 287 |
+
H.add_edges_from(G.edges())
|
| 288 |
+
assert len(G.nodes[0]) == 1
|
| 289 |
+
ddict = G.adj[1][2][0] if G.is_multigraph() else G.adj[1][2]
|
| 290 |
+
assert len(ddict) == 1
|
| 291 |
+
assert len(H.nodes[0]) == 0
|
| 292 |
+
ddict = H.adj[1][2][0] if H.is_multigraph() else H.adj[1][2]
|
| 293 |
+
assert len(ddict) == 0
|
| 294 |
+
|
| 295 |
+
def is_deepcopy(self, H, G):
|
| 296 |
+
self.graphs_equal(H, G)
|
| 297 |
+
self.different_attrdict(H, G)
|
| 298 |
+
self.deep_copy_attrdict(H, G)
|
| 299 |
+
|
| 300 |
+
def deep_copy_attrdict(self, H, G):
|
| 301 |
+
self.deepcopy_graph_attr(H, G)
|
| 302 |
+
self.deepcopy_node_attr(H, G)
|
| 303 |
+
self.deepcopy_edge_attr(H, G)
|
| 304 |
+
|
| 305 |
+
def deepcopy_graph_attr(self, H, G):
|
| 306 |
+
assert G.graph["foo"] == H.graph["foo"]
|
| 307 |
+
G.graph["foo"].append(1)
|
| 308 |
+
assert G.graph["foo"] != H.graph["foo"]
|
| 309 |
+
|
| 310 |
+
def deepcopy_node_attr(self, H, G):
|
| 311 |
+
assert G.nodes[0]["foo"] == H.nodes[0]["foo"]
|
| 312 |
+
G.nodes[0]["foo"].append(1)
|
| 313 |
+
assert G.nodes[0]["foo"] != H.nodes[0]["foo"]
|
| 314 |
+
|
| 315 |
+
def deepcopy_edge_attr(self, H, G):
|
| 316 |
+
assert G[1][2]["foo"] == H[1][2]["foo"]
|
| 317 |
+
G[1][2]["foo"].append(1)
|
| 318 |
+
assert G[1][2]["foo"] != H[1][2]["foo"]
|
| 319 |
+
|
| 320 |
+
def is_shallow_copy(self, H, G):
|
| 321 |
+
self.graphs_equal(H, G)
|
| 322 |
+
self.shallow_copy_attrdict(H, G)
|
| 323 |
+
|
| 324 |
+
def shallow_copy_attrdict(self, H, G):
|
| 325 |
+
self.shallow_copy_graph_attr(H, G)
|
| 326 |
+
self.shallow_copy_node_attr(H, G)
|
| 327 |
+
self.shallow_copy_edge_attr(H, G)
|
| 328 |
+
|
| 329 |
+
def shallow_copy_graph_attr(self, H, G):
|
| 330 |
+
assert G.graph["foo"] == H.graph["foo"]
|
| 331 |
+
G.graph["foo"].append(1)
|
| 332 |
+
assert G.graph["foo"] == H.graph["foo"]
|
| 333 |
+
|
| 334 |
+
def shallow_copy_node_attr(self, H, G):
|
| 335 |
+
assert G.nodes[0]["foo"] == H.nodes[0]["foo"]
|
| 336 |
+
G.nodes[0]["foo"].append(1)
|
| 337 |
+
assert G.nodes[0]["foo"] == H.nodes[0]["foo"]
|
| 338 |
+
|
| 339 |
+
def shallow_copy_edge_attr(self, H, G):
|
| 340 |
+
assert G[1][2]["foo"] == H[1][2]["foo"]
|
| 341 |
+
G[1][2]["foo"].append(1)
|
| 342 |
+
assert G[1][2]["foo"] == H[1][2]["foo"]
|
| 343 |
+
|
| 344 |
+
def same_attrdict(self, H, G):
|
| 345 |
+
old_foo = H[1][2]["foo"]
|
| 346 |
+
H.adj[1][2]["foo"] = "baz"
|
| 347 |
+
assert G.edges == H.edges
|
| 348 |
+
H.adj[1][2]["foo"] = old_foo
|
| 349 |
+
assert G.edges == H.edges
|
| 350 |
+
|
| 351 |
+
old_foo = H.nodes[0]["foo"]
|
| 352 |
+
H.nodes[0]["foo"] = "baz"
|
| 353 |
+
assert G.nodes == H.nodes
|
| 354 |
+
H.nodes[0]["foo"] = old_foo
|
| 355 |
+
assert G.nodes == H.nodes
|
| 356 |
+
|
| 357 |
+
def different_attrdict(self, H, G):
|
| 358 |
+
old_foo = H[1][2]["foo"]
|
| 359 |
+
H.adj[1][2]["foo"] = "baz"
|
| 360 |
+
assert G._adj != H._adj
|
| 361 |
+
H.adj[1][2]["foo"] = old_foo
|
| 362 |
+
assert G._adj == H._adj
|
| 363 |
+
|
| 364 |
+
old_foo = H.nodes[0]["foo"]
|
| 365 |
+
H.nodes[0]["foo"] = "baz"
|
| 366 |
+
assert G._node != H._node
|
| 367 |
+
H.nodes[0]["foo"] = old_foo
|
| 368 |
+
assert G._node == H._node
|
| 369 |
+
|
| 370 |
+
def graphs_equal(self, H, G):
|
| 371 |
+
assert G._adj == H._adj
|
| 372 |
+
assert G._node == H._node
|
| 373 |
+
assert G.graph == H.graph
|
| 374 |
+
assert G.name == H.name
|
| 375 |
+
if not G.is_directed() and not H.is_directed():
|
| 376 |
+
assert H._adj[1][2] is H._adj[2][1]
|
| 377 |
+
assert G._adj[1][2] is G._adj[2][1]
|
| 378 |
+
else: # at least one is directed
|
| 379 |
+
if not G.is_directed():
|
| 380 |
+
G._pred = G._adj
|
| 381 |
+
G._succ = G._adj
|
| 382 |
+
if not H.is_directed():
|
| 383 |
+
H._pred = H._adj
|
| 384 |
+
H._succ = H._adj
|
| 385 |
+
assert G._pred == H._pred
|
| 386 |
+
assert G._succ == H._succ
|
| 387 |
+
assert H._succ[1][2] is H._pred[2][1]
|
| 388 |
+
assert G._succ[1][2] is G._pred[2][1]
|
| 389 |
+
|
| 390 |
+
def test_graph_attr(self):
|
| 391 |
+
G = self.K3.copy()
|
| 392 |
+
G.graph["foo"] = "bar"
|
| 393 |
+
assert isinstance(G.graph, G.graph_attr_dict_factory)
|
| 394 |
+
assert G.graph["foo"] == "bar"
|
| 395 |
+
del G.graph["foo"]
|
| 396 |
+
assert G.graph == {}
|
| 397 |
+
H = self.Graph(foo="bar")
|
| 398 |
+
assert H.graph["foo"] == "bar"
|
| 399 |
+
|
| 400 |
+
def test_node_attr(self):
|
| 401 |
+
G = self.K3.copy()
|
| 402 |
+
G.add_node(1, foo="bar")
|
| 403 |
+
assert all(
|
| 404 |
+
isinstance(d, G.node_attr_dict_factory) for u, d in G.nodes(data=True)
|
| 405 |
+
)
|
| 406 |
+
assert nodes_equal(G.nodes(), [0, 1, 2])
|
| 407 |
+
assert nodes_equal(G.nodes(data=True), [(0, {}), (1, {"foo": "bar"}), (2, {})])
|
| 408 |
+
G.nodes[1]["foo"] = "baz"
|
| 409 |
+
assert nodes_equal(G.nodes(data=True), [(0, {}), (1, {"foo": "baz"}), (2, {})])
|
| 410 |
+
assert nodes_equal(G.nodes(data="foo"), [(0, None), (1, "baz"), (2, None)])
|
| 411 |
+
assert nodes_equal(
|
| 412 |
+
G.nodes(data="foo", default="bar"), [(0, "bar"), (1, "baz"), (2, "bar")]
|
| 413 |
+
)
|
| 414 |
+
|
| 415 |
+
def test_node_attr2(self):
|
| 416 |
+
G = self.K3.copy()
|
| 417 |
+
a = {"foo": "bar"}
|
| 418 |
+
G.add_node(3, **a)
|
| 419 |
+
assert nodes_equal(G.nodes(), [0, 1, 2, 3])
|
| 420 |
+
assert nodes_equal(
|
| 421 |
+
G.nodes(data=True), [(0, {}), (1, {}), (2, {}), (3, {"foo": "bar"})]
|
| 422 |
+
)
|
| 423 |
+
|
| 424 |
+
def test_edge_lookup(self):
|
| 425 |
+
G = self.Graph()
|
| 426 |
+
G.add_edge(1, 2, foo="bar")
|
| 427 |
+
assert edges_equal(G.edges[1, 2], {"foo": "bar"})
|
| 428 |
+
|
| 429 |
+
def test_edge_attr(self):
|
| 430 |
+
G = self.Graph()
|
| 431 |
+
G.add_edge(1, 2, foo="bar")
|
| 432 |
+
assert all(
|
| 433 |
+
isinstance(d, G.edge_attr_dict_factory) for u, v, d in G.edges(data=True)
|
| 434 |
+
)
|
| 435 |
+
assert edges_equal(G.edges(data=True), [(1, 2, {"foo": "bar"})])
|
| 436 |
+
assert edges_equal(G.edges(data="foo"), [(1, 2, "bar")])
|
| 437 |
+
|
| 438 |
+
def test_edge_attr2(self):
|
| 439 |
+
G = self.Graph()
|
| 440 |
+
G.add_edges_from([(1, 2), (3, 4)], foo="foo")
|
| 441 |
+
assert edges_equal(
|
| 442 |
+
G.edges(data=True), [(1, 2, {"foo": "foo"}), (3, 4, {"foo": "foo"})]
|
| 443 |
+
)
|
| 444 |
+
assert edges_equal(G.edges(data="foo"), [(1, 2, "foo"), (3, 4, "foo")])
|
| 445 |
+
|
| 446 |
+
def test_edge_attr3(self):
|
| 447 |
+
G = self.Graph()
|
| 448 |
+
G.add_edges_from([(1, 2, {"weight": 32}), (3, 4, {"weight": 64})], foo="foo")
|
| 449 |
+
assert edges_equal(
|
| 450 |
+
G.edges(data=True),
|
| 451 |
+
[
|
| 452 |
+
(1, 2, {"foo": "foo", "weight": 32}),
|
| 453 |
+
(3, 4, {"foo": "foo", "weight": 64}),
|
| 454 |
+
],
|
| 455 |
+
)
|
| 456 |
+
|
| 457 |
+
G.remove_edges_from([(1, 2), (3, 4)])
|
| 458 |
+
G.add_edge(1, 2, data=7, spam="bar", bar="foo")
|
| 459 |
+
assert edges_equal(
|
| 460 |
+
G.edges(data=True), [(1, 2, {"data": 7, "spam": "bar", "bar": "foo"})]
|
| 461 |
+
)
|
| 462 |
+
|
| 463 |
+
def test_edge_attr4(self):
|
| 464 |
+
G = self.Graph()
|
| 465 |
+
G.add_edge(1, 2, data=7, spam="bar", bar="foo")
|
| 466 |
+
assert edges_equal(
|
| 467 |
+
G.edges(data=True), [(1, 2, {"data": 7, "spam": "bar", "bar": "foo"})]
|
| 468 |
+
)
|
| 469 |
+
G[1][2]["data"] = 10 # OK to set data like this
|
| 470 |
+
assert edges_equal(
|
| 471 |
+
G.edges(data=True), [(1, 2, {"data": 10, "spam": "bar", "bar": "foo"})]
|
| 472 |
+
)
|
| 473 |
+
|
| 474 |
+
G.adj[1][2]["data"] = 20
|
| 475 |
+
assert edges_equal(
|
| 476 |
+
G.edges(data=True), [(1, 2, {"data": 20, "spam": "bar", "bar": "foo"})]
|
| 477 |
+
)
|
| 478 |
+
G.edges[1, 2]["data"] = 21 # another spelling, "edge"
|
| 479 |
+
assert edges_equal(
|
| 480 |
+
G.edges(data=True), [(1, 2, {"data": 21, "spam": "bar", "bar": "foo"})]
|
| 481 |
+
)
|
| 482 |
+
G.adj[1][2]["listdata"] = [20, 200]
|
| 483 |
+
G.adj[1][2]["weight"] = 20
|
| 484 |
+
dd = {
|
| 485 |
+
"data": 21,
|
| 486 |
+
"spam": "bar",
|
| 487 |
+
"bar": "foo",
|
| 488 |
+
"listdata": [20, 200],
|
| 489 |
+
"weight": 20,
|
| 490 |
+
}
|
| 491 |
+
assert edges_equal(G.edges(data=True), [(1, 2, dd)])
|
| 492 |
+
|
| 493 |
+
def test_to_undirected(self):
|
| 494 |
+
G = self.K3
|
| 495 |
+
self.add_attributes(G)
|
| 496 |
+
H = nx.Graph(G)
|
| 497 |
+
self.is_shallow_copy(H, G)
|
| 498 |
+
self.different_attrdict(H, G)
|
| 499 |
+
H = G.to_undirected()
|
| 500 |
+
self.is_deepcopy(H, G)
|
| 501 |
+
|
| 502 |
+
def test_to_directed_as_view(self):
|
| 503 |
+
H = nx.path_graph(2, create_using=self.Graph)
|
| 504 |
+
H2 = H.to_directed(as_view=True)
|
| 505 |
+
assert H is H2._graph
|
| 506 |
+
assert H2.has_edge(0, 1)
|
| 507 |
+
assert H2.has_edge(1, 0) or H.is_directed()
|
| 508 |
+
pytest.raises(nx.NetworkXError, H2.add_node, -1)
|
| 509 |
+
pytest.raises(nx.NetworkXError, H2.add_edge, 1, 2)
|
| 510 |
+
H.add_edge(1, 2)
|
| 511 |
+
assert H2.has_edge(1, 2)
|
| 512 |
+
assert H2.has_edge(2, 1) or H.is_directed()
|
| 513 |
+
|
| 514 |
+
def test_to_undirected_as_view(self):
|
| 515 |
+
H = nx.path_graph(2, create_using=self.Graph)
|
| 516 |
+
H2 = H.to_undirected(as_view=True)
|
| 517 |
+
assert H is H2._graph
|
| 518 |
+
assert H2.has_edge(0, 1)
|
| 519 |
+
assert H2.has_edge(1, 0)
|
| 520 |
+
pytest.raises(nx.NetworkXError, H2.add_node, -1)
|
| 521 |
+
pytest.raises(nx.NetworkXError, H2.add_edge, 1, 2)
|
| 522 |
+
H.add_edge(1, 2)
|
| 523 |
+
assert H2.has_edge(1, 2)
|
| 524 |
+
assert H2.has_edge(2, 1)
|
| 525 |
+
|
| 526 |
+
def test_directed_class(self):
|
| 527 |
+
G = self.Graph()
|
| 528 |
+
|
| 529 |
+
class newGraph(G.to_undirected_class()):
|
| 530 |
+
def to_directed_class(self):
|
| 531 |
+
return newDiGraph
|
| 532 |
+
|
| 533 |
+
def to_undirected_class(self):
|
| 534 |
+
return newGraph
|
| 535 |
+
|
| 536 |
+
class newDiGraph(G.to_directed_class()):
|
| 537 |
+
def to_directed_class(self):
|
| 538 |
+
return newDiGraph
|
| 539 |
+
|
| 540 |
+
def to_undirected_class(self):
|
| 541 |
+
return newGraph
|
| 542 |
+
|
| 543 |
+
G = newDiGraph() if G.is_directed() else newGraph()
|
| 544 |
+
H = G.to_directed()
|
| 545 |
+
assert isinstance(H, newDiGraph)
|
| 546 |
+
H = G.to_undirected()
|
| 547 |
+
assert isinstance(H, newGraph)
|
| 548 |
+
|
| 549 |
+
def test_to_directed(self):
|
| 550 |
+
G = self.K3
|
| 551 |
+
self.add_attributes(G)
|
| 552 |
+
H = nx.DiGraph(G)
|
| 553 |
+
self.is_shallow_copy(H, G)
|
| 554 |
+
self.different_attrdict(H, G)
|
| 555 |
+
H = G.to_directed()
|
| 556 |
+
self.is_deepcopy(H, G)
|
| 557 |
+
|
| 558 |
+
def test_subgraph(self):
|
| 559 |
+
G = self.K3
|
| 560 |
+
self.add_attributes(G)
|
| 561 |
+
H = G.subgraph([0, 1, 2, 5])
|
| 562 |
+
self.graphs_equal(H, G)
|
| 563 |
+
self.same_attrdict(H, G)
|
| 564 |
+
self.shallow_copy_attrdict(H, G)
|
| 565 |
+
|
| 566 |
+
H = G.subgraph(0)
|
| 567 |
+
assert H.adj == {0: {}}
|
| 568 |
+
H = G.subgraph([])
|
| 569 |
+
assert H.adj == {}
|
| 570 |
+
assert G.adj != {}
|
| 571 |
+
|
| 572 |
+
def test_selfloops_attr(self):
|
| 573 |
+
G = self.K3.copy()
|
| 574 |
+
G.add_edge(0, 0)
|
| 575 |
+
G.add_edge(1, 1, weight=2)
|
| 576 |
+
assert edges_equal(
|
| 577 |
+
nx.selfloop_edges(G, data=True), [(0, 0, {}), (1, 1, {"weight": 2})]
|
| 578 |
+
)
|
| 579 |
+
assert edges_equal(
|
| 580 |
+
nx.selfloop_edges(G, data="weight"), [(0, 0, None), (1, 1, 2)]
|
| 581 |
+
)
|
| 582 |
+
|
| 583 |
+
|
| 584 |
+
class TestGraph(BaseAttrGraphTester):
|
| 585 |
+
"""Tests specific to dict-of-dict-of-dict graph data structure"""
|
| 586 |
+
|
| 587 |
+
def setup_method(self):
|
| 588 |
+
self.Graph = nx.Graph
|
| 589 |
+
# build dict-of-dict-of-dict K3
|
| 590 |
+
ed1, ed2, ed3 = ({}, {}, {})
|
| 591 |
+
self.k3adj = {0: {1: ed1, 2: ed2}, 1: {0: ed1, 2: ed3}, 2: {0: ed2, 1: ed3}}
|
| 592 |
+
self.k3edges = [(0, 1), (0, 2), (1, 2)]
|
| 593 |
+
self.k3nodes = [0, 1, 2]
|
| 594 |
+
self.K3 = self.Graph()
|
| 595 |
+
self.K3._adj = self.k3adj
|
| 596 |
+
self.K3._node = {}
|
| 597 |
+
self.K3._node[0] = {}
|
| 598 |
+
self.K3._node[1] = {}
|
| 599 |
+
self.K3._node[2] = {}
|
| 600 |
+
|
| 601 |
+
def test_pickle(self):
|
| 602 |
+
G = self.K3
|
| 603 |
+
pg = pickle.loads(pickle.dumps(G, -1))
|
| 604 |
+
self.graphs_equal(pg, G)
|
| 605 |
+
pg = pickle.loads(pickle.dumps(G))
|
| 606 |
+
self.graphs_equal(pg, G)
|
| 607 |
+
|
| 608 |
+
def test_data_input(self):
|
| 609 |
+
G = self.Graph({1: [2], 2: [1]}, name="test")
|
| 610 |
+
assert G.name == "test"
|
| 611 |
+
assert sorted(G.adj.items()) == [(1, {2: {}}), (2, {1: {}})]
|
| 612 |
+
|
| 613 |
+
def test_adjacency(self):
|
| 614 |
+
G = self.K3
|
| 615 |
+
assert dict(G.adjacency()) == {
|
| 616 |
+
0: {1: {}, 2: {}},
|
| 617 |
+
1: {0: {}, 2: {}},
|
| 618 |
+
2: {0: {}, 1: {}},
|
| 619 |
+
}
|
| 620 |
+
|
| 621 |
+
def test_getitem(self):
|
| 622 |
+
G = self.K3
|
| 623 |
+
assert G.adj[0] == {1: {}, 2: {}}
|
| 624 |
+
assert G[0] == {1: {}, 2: {}}
|
| 625 |
+
with pytest.raises(KeyError):
|
| 626 |
+
G.__getitem__("j")
|
| 627 |
+
with pytest.raises(TypeError):
|
| 628 |
+
G.__getitem__(["A"])
|
| 629 |
+
|
| 630 |
+
def test_add_node(self):
|
| 631 |
+
G = self.Graph()
|
| 632 |
+
G.add_node(0)
|
| 633 |
+
assert G.adj == {0: {}}
|
| 634 |
+
# test add attributes
|
| 635 |
+
G.add_node(1, c="red")
|
| 636 |
+
G.add_node(2, c="blue")
|
| 637 |
+
G.add_node(3, c="red")
|
| 638 |
+
assert G.nodes[1]["c"] == "red"
|
| 639 |
+
assert G.nodes[2]["c"] == "blue"
|
| 640 |
+
assert G.nodes[3]["c"] == "red"
|
| 641 |
+
# test updating attributes
|
| 642 |
+
G.add_node(1, c="blue")
|
| 643 |
+
G.add_node(2, c="red")
|
| 644 |
+
G.add_node(3, c="blue")
|
| 645 |
+
assert G.nodes[1]["c"] == "blue"
|
| 646 |
+
assert G.nodes[2]["c"] == "red"
|
| 647 |
+
assert G.nodes[3]["c"] == "blue"
|
| 648 |
+
|
| 649 |
+
def test_add_nodes_from(self):
|
| 650 |
+
G = self.Graph()
|
| 651 |
+
G.add_nodes_from([0, 1, 2])
|
| 652 |
+
assert G.adj == {0: {}, 1: {}, 2: {}}
|
| 653 |
+
# test add attributes
|
| 654 |
+
G.add_nodes_from([0, 1, 2], c="red")
|
| 655 |
+
assert G.nodes[0]["c"] == "red"
|
| 656 |
+
assert G.nodes[2]["c"] == "red"
|
| 657 |
+
# test that attribute dicts are not the same
|
| 658 |
+
assert G.nodes[0] is not G.nodes[1]
|
| 659 |
+
# test updating attributes
|
| 660 |
+
G.add_nodes_from([0, 1, 2], c="blue")
|
| 661 |
+
assert G.nodes[0]["c"] == "blue"
|
| 662 |
+
assert G.nodes[2]["c"] == "blue"
|
| 663 |
+
assert G.nodes[0] is not G.nodes[1]
|
| 664 |
+
# test tuple input
|
| 665 |
+
H = self.Graph()
|
| 666 |
+
H.add_nodes_from(G.nodes(data=True))
|
| 667 |
+
assert H.nodes[0]["c"] == "blue"
|
| 668 |
+
assert H.nodes[2]["c"] == "blue"
|
| 669 |
+
assert H.nodes[0] is not H.nodes[1]
|
| 670 |
+
# specific overrides general
|
| 671 |
+
H.add_nodes_from([0, (1, {"c": "green"}), (3, {"c": "cyan"})], c="red")
|
| 672 |
+
assert H.nodes[0]["c"] == "red"
|
| 673 |
+
assert H.nodes[1]["c"] == "green"
|
| 674 |
+
assert H.nodes[2]["c"] == "blue"
|
| 675 |
+
assert H.nodes[3]["c"] == "cyan"
|
| 676 |
+
|
| 677 |
+
def test_remove_node(self):
|
| 678 |
+
G = self.K3.copy()
|
| 679 |
+
G.remove_node(0)
|
| 680 |
+
assert G.adj == {1: {2: {}}, 2: {1: {}}}
|
| 681 |
+
with pytest.raises(nx.NetworkXError):
|
| 682 |
+
G.remove_node(-1)
|
| 683 |
+
|
| 684 |
+
# generator here to implement list,set,string...
|
| 685 |
+
|
| 686 |
+
def test_remove_nodes_from(self):
|
| 687 |
+
G = self.K3.copy()
|
| 688 |
+
G.remove_nodes_from([0, 1])
|
| 689 |
+
assert G.adj == {2: {}}
|
| 690 |
+
G.remove_nodes_from([-1]) # silent fail
|
| 691 |
+
|
| 692 |
+
def test_add_edge(self):
|
| 693 |
+
G = self.Graph()
|
| 694 |
+
G.add_edge(0, 1)
|
| 695 |
+
assert G.adj == {0: {1: {}}, 1: {0: {}}}
|
| 696 |
+
G = self.Graph()
|
| 697 |
+
G.add_edge(*(0, 1))
|
| 698 |
+
assert G.adj == {0: {1: {}}, 1: {0: {}}}
|
| 699 |
+
G = self.Graph()
|
| 700 |
+
with pytest.raises(ValueError):
|
| 701 |
+
G.add_edge(None, "anything")
|
| 702 |
+
|
| 703 |
+
def test_add_edges_from(self):
|
| 704 |
+
G = self.Graph()
|
| 705 |
+
G.add_edges_from([(0, 1), (0, 2, {"weight": 3})])
|
| 706 |
+
assert G.adj == {
|
| 707 |
+
0: {1: {}, 2: {"weight": 3}},
|
| 708 |
+
1: {0: {}},
|
| 709 |
+
2: {0: {"weight": 3}},
|
| 710 |
+
}
|
| 711 |
+
G = self.Graph()
|
| 712 |
+
G.add_edges_from([(0, 1), (0, 2, {"weight": 3}), (1, 2, {"data": 4})], data=2)
|
| 713 |
+
assert G.adj == {
|
| 714 |
+
0: {1: {"data": 2}, 2: {"weight": 3, "data": 2}},
|
| 715 |
+
1: {0: {"data": 2}, 2: {"data": 4}},
|
| 716 |
+
2: {0: {"weight": 3, "data": 2}, 1: {"data": 4}},
|
| 717 |
+
}
|
| 718 |
+
|
| 719 |
+
with pytest.raises(nx.NetworkXError):
|
| 720 |
+
G.add_edges_from([(0,)]) # too few in tuple
|
| 721 |
+
with pytest.raises(nx.NetworkXError):
|
| 722 |
+
G.add_edges_from([(0, 1, 2, 3)]) # too many in tuple
|
| 723 |
+
with pytest.raises(TypeError):
|
| 724 |
+
G.add_edges_from([0]) # not a tuple
|
| 725 |
+
with pytest.raises(ValueError):
|
| 726 |
+
G.add_edges_from([(None, 3), (3, 2)]) # None cannot be a node
|
| 727 |
+
|
| 728 |
+
def test_remove_edge(self):
|
| 729 |
+
G = self.K3.copy()
|
| 730 |
+
G.remove_edge(0, 1)
|
| 731 |
+
assert G.adj == {0: {2: {}}, 1: {2: {}}, 2: {0: {}, 1: {}}}
|
| 732 |
+
with pytest.raises(nx.NetworkXError):
|
| 733 |
+
G.remove_edge(-1, 0)
|
| 734 |
+
|
| 735 |
+
def test_remove_edges_from(self):
|
| 736 |
+
G = self.K3.copy()
|
| 737 |
+
G.remove_edges_from([(0, 1)])
|
| 738 |
+
assert G.adj == {0: {2: {}}, 1: {2: {}}, 2: {0: {}, 1: {}}}
|
| 739 |
+
G.remove_edges_from([(0, 0)]) # silent fail
|
| 740 |
+
|
| 741 |
+
def test_clear(self):
|
| 742 |
+
G = self.K3.copy()
|
| 743 |
+
G.graph["name"] = "K3"
|
| 744 |
+
G.clear()
|
| 745 |
+
assert list(G.nodes) == []
|
| 746 |
+
assert G.adj == {}
|
| 747 |
+
assert G.graph == {}
|
| 748 |
+
|
| 749 |
+
def test_clear_edges(self):
|
| 750 |
+
G = self.K3.copy()
|
| 751 |
+
G.graph["name"] = "K3"
|
| 752 |
+
nodes = list(G.nodes)
|
| 753 |
+
G.clear_edges()
|
| 754 |
+
assert list(G.nodes) == nodes
|
| 755 |
+
assert G.adj == {0: {}, 1: {}, 2: {}}
|
| 756 |
+
assert list(G.edges) == []
|
| 757 |
+
assert G.graph["name"] == "K3"
|
| 758 |
+
|
| 759 |
+
def test_edges_data(self):
|
| 760 |
+
G = self.K3
|
| 761 |
+
all_edges = [(0, 1, {}), (0, 2, {}), (1, 2, {})]
|
| 762 |
+
assert edges_equal(G.edges(data=True), all_edges)
|
| 763 |
+
assert edges_equal(G.edges(0, data=True), [(0, 1, {}), (0, 2, {})])
|
| 764 |
+
assert edges_equal(G.edges([0, 1], data=True), all_edges)
|
| 765 |
+
with pytest.raises(nx.NetworkXError):
|
| 766 |
+
G.edges(-1, True)
|
| 767 |
+
|
| 768 |
+
def test_get_edge_data(self):
|
| 769 |
+
G = self.K3.copy()
|
| 770 |
+
assert G.get_edge_data(0, 1) == {}
|
| 771 |
+
assert G[0][1] == {}
|
| 772 |
+
assert G.get_edge_data(10, 20) is None
|
| 773 |
+
assert G.get_edge_data(-1, 0) is None
|
| 774 |
+
assert G.get_edge_data(-1, 0, default=1) == 1
|
| 775 |
+
|
| 776 |
+
def test_update(self):
|
| 777 |
+
# specify both edges and nodes
|
| 778 |
+
G = self.K3.copy()
|
| 779 |
+
G.update(nodes=[3, (4, {"size": 2})], edges=[(4, 5), (6, 7, {"weight": 2})])
|
| 780 |
+
nlist = [
|
| 781 |
+
(0, {}),
|
| 782 |
+
(1, {}),
|
| 783 |
+
(2, {}),
|
| 784 |
+
(3, {}),
|
| 785 |
+
(4, {"size": 2}),
|
| 786 |
+
(5, {}),
|
| 787 |
+
(6, {}),
|
| 788 |
+
(7, {}),
|
| 789 |
+
]
|
| 790 |
+
assert sorted(G.nodes.data()) == nlist
|
| 791 |
+
if G.is_directed():
|
| 792 |
+
elist = [
|
| 793 |
+
(0, 1, {}),
|
| 794 |
+
(0, 2, {}),
|
| 795 |
+
(1, 0, {}),
|
| 796 |
+
(1, 2, {}),
|
| 797 |
+
(2, 0, {}),
|
| 798 |
+
(2, 1, {}),
|
| 799 |
+
(4, 5, {}),
|
| 800 |
+
(6, 7, {"weight": 2}),
|
| 801 |
+
]
|
| 802 |
+
else:
|
| 803 |
+
elist = [
|
| 804 |
+
(0, 1, {}),
|
| 805 |
+
(0, 2, {}),
|
| 806 |
+
(1, 2, {}),
|
| 807 |
+
(4, 5, {}),
|
| 808 |
+
(6, 7, {"weight": 2}),
|
| 809 |
+
]
|
| 810 |
+
assert sorted(G.edges.data()) == elist
|
| 811 |
+
assert G.graph == {}
|
| 812 |
+
|
| 813 |
+
# no keywords -- order is edges, nodes
|
| 814 |
+
G = self.K3.copy()
|
| 815 |
+
G.update([(4, 5), (6, 7, {"weight": 2})], [3, (4, {"size": 2})])
|
| 816 |
+
assert sorted(G.nodes.data()) == nlist
|
| 817 |
+
assert sorted(G.edges.data()) == elist
|
| 818 |
+
assert G.graph == {}
|
| 819 |
+
|
| 820 |
+
# update using only a graph
|
| 821 |
+
G = self.Graph()
|
| 822 |
+
G.graph["foo"] = "bar"
|
| 823 |
+
G.add_node(2, data=4)
|
| 824 |
+
G.add_edge(0, 1, weight=0.5)
|
| 825 |
+
GG = G.copy()
|
| 826 |
+
H = self.Graph()
|
| 827 |
+
GG.update(H)
|
| 828 |
+
assert graphs_equal(G, GG)
|
| 829 |
+
H.update(G)
|
| 830 |
+
assert graphs_equal(H, G)
|
| 831 |
+
|
| 832 |
+
# update nodes only
|
| 833 |
+
H = self.Graph()
|
| 834 |
+
H.update(nodes=[3, 4])
|
| 835 |
+
assert H.nodes ^ {3, 4} == set()
|
| 836 |
+
assert H.size() == 0
|
| 837 |
+
|
| 838 |
+
# update edges only
|
| 839 |
+
H = self.Graph()
|
| 840 |
+
H.update(edges=[(3, 4)])
|
| 841 |
+
assert sorted(H.edges.data()) == [(3, 4, {})]
|
| 842 |
+
assert H.size() == 1
|
| 843 |
+
|
| 844 |
+
# No inputs -> exception
|
| 845 |
+
with pytest.raises(nx.NetworkXError):
|
| 846 |
+
nx.Graph().update()
|
| 847 |
+
|
| 848 |
+
|
| 849 |
+
class TestEdgeSubgraph:
|
| 850 |
+
"""Unit tests for the :meth:`Graph.edge_subgraph` method."""
|
| 851 |
+
|
| 852 |
+
def setup_method(self):
|
| 853 |
+
# Create a path graph on five nodes.
|
| 854 |
+
G = nx.path_graph(5)
|
| 855 |
+
# Add some node, edge, and graph attributes.
|
| 856 |
+
for i in range(5):
|
| 857 |
+
G.nodes[i]["name"] = f"node{i}"
|
| 858 |
+
G.edges[0, 1]["name"] = "edge01"
|
| 859 |
+
G.edges[3, 4]["name"] = "edge34"
|
| 860 |
+
G.graph["name"] = "graph"
|
| 861 |
+
# Get the subgraph induced by the first and last edges.
|
| 862 |
+
self.G = G
|
| 863 |
+
self.H = G.edge_subgraph([(0, 1), (3, 4)])
|
| 864 |
+
|
| 865 |
+
def test_correct_nodes(self):
|
| 866 |
+
"""Tests that the subgraph has the correct nodes."""
|
| 867 |
+
assert [0, 1, 3, 4] == sorted(self.H.nodes())
|
| 868 |
+
|
| 869 |
+
def test_correct_edges(self):
|
| 870 |
+
"""Tests that the subgraph has the correct edges."""
|
| 871 |
+
assert [(0, 1, "edge01"), (3, 4, "edge34")] == sorted(self.H.edges(data="name"))
|
| 872 |
+
|
| 873 |
+
def test_add_node(self):
|
| 874 |
+
"""Tests that adding a node to the original graph does not
|
| 875 |
+
affect the nodes of the subgraph.
|
| 876 |
+
|
| 877 |
+
"""
|
| 878 |
+
self.G.add_node(5)
|
| 879 |
+
assert [0, 1, 3, 4] == sorted(self.H.nodes())
|
| 880 |
+
|
| 881 |
+
def test_remove_node(self):
|
| 882 |
+
"""Tests that removing a node in the original graph does
|
| 883 |
+
affect the nodes of the subgraph.
|
| 884 |
+
|
| 885 |
+
"""
|
| 886 |
+
self.G.remove_node(0)
|
| 887 |
+
assert [1, 3, 4] == sorted(self.H.nodes())
|
| 888 |
+
|
| 889 |
+
def test_node_attr_dict(self):
|
| 890 |
+
"""Tests that the node attribute dictionary of the two graphs is
|
| 891 |
+
the same object.
|
| 892 |
+
|
| 893 |
+
"""
|
| 894 |
+
for v in self.H:
|
| 895 |
+
assert self.G.nodes[v] == self.H.nodes[v]
|
| 896 |
+
# Making a change to G should make a change in H and vice versa.
|
| 897 |
+
self.G.nodes[0]["name"] = "foo"
|
| 898 |
+
assert self.G.nodes[0] == self.H.nodes[0]
|
| 899 |
+
self.H.nodes[1]["name"] = "bar"
|
| 900 |
+
assert self.G.nodes[1] == self.H.nodes[1]
|
| 901 |
+
|
| 902 |
+
def test_edge_attr_dict(self):
|
| 903 |
+
"""Tests that the edge attribute dictionary of the two graphs is
|
| 904 |
+
the same object.
|
| 905 |
+
|
| 906 |
+
"""
|
| 907 |
+
for u, v in self.H.edges():
|
| 908 |
+
assert self.G.edges[u, v] == self.H.edges[u, v]
|
| 909 |
+
# Making a change to G should make a change in H and vice versa.
|
| 910 |
+
self.G.edges[0, 1]["name"] = "foo"
|
| 911 |
+
assert self.G.edges[0, 1]["name"] == self.H.edges[0, 1]["name"]
|
| 912 |
+
self.H.edges[3, 4]["name"] = "bar"
|
| 913 |
+
assert self.G.edges[3, 4]["name"] == self.H.edges[3, 4]["name"]
|
| 914 |
+
|
| 915 |
+
def test_graph_attr_dict(self):
|
| 916 |
+
"""Tests that the graph attribute dictionary of the two graphs
|
| 917 |
+
is the same object.
|
| 918 |
+
|
| 919 |
+
"""
|
| 920 |
+
assert self.G.graph is self.H.graph
|