Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .gitattributes +3 -0
- .venv/lib/python3.11/site-packages/networkx/classes/__init__.py +13 -0
- .venv/lib/python3.11/site-packages/networkx/classes/__pycache__/__init__.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/classes/__pycache__/coreviews.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/classes/__pycache__/digraph.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/classes/__pycache__/filters.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/classes/__pycache__/function.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/classes/__pycache__/graph.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/classes/__pycache__/graphviews.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/classes/__pycache__/multidigraph.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/classes/__pycache__/multigraph.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/classes/__pycache__/reportviews.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/classes/coreviews.py +431 -0
- .venv/lib/python3.11/site-packages/networkx/classes/digraph.py +1352 -0
- .venv/lib/python3.11/site-packages/networkx/classes/filters.py +95 -0
- .venv/lib/python3.11/site-packages/networkx/classes/function.py +1407 -0
- .venv/lib/python3.11/site-packages/networkx/classes/graph.py +2058 -0
- .venv/lib/python3.11/site-packages/networkx/classes/graphviews.py +269 -0
- .venv/lib/python3.11/site-packages/networkx/classes/multidigraph.py +966 -0
- .venv/lib/python3.11/site-packages/networkx/classes/multigraph.py +1283 -0
- .venv/lib/python3.11/site-packages/networkx/classes/reportviews.py +1447 -0
- .venv/lib/python3.11/site-packages/networkx/classes/tests/dispatch_interface.py +185 -0
- .venv/lib/python3.11/site-packages/networkx/classes/tests/historical_tests.py +475 -0
- .venv/lib/python3.11/site-packages/networkx/classes/tests/test_coreviews.py +362 -0
- .venv/lib/python3.11/site-packages/networkx/classes/tests/test_digraph.py +331 -0
- .venv/lib/python3.11/site-packages/networkx/classes/tests/test_digraph_historical.py +111 -0
- .venv/lib/python3.11/site-packages/networkx/classes/tests/test_filters.py +177 -0
- .venv/lib/python3.11/site-packages/networkx/classes/tests/test_function.py +1035 -0
- .venv/lib/python3.11/site-packages/networkx/classes/tests/test_graph.py +920 -0
- .venv/lib/python3.11/site-packages/networkx/classes/tests/test_graph_historical.py +13 -0
- .venv/lib/python3.11/site-packages/networkx/classes/tests/test_graphviews.py +350 -0
- .venv/lib/python3.11/site-packages/networkx/classes/tests/test_multidigraph.py +459 -0
- .venv/lib/python3.11/site-packages/networkx/classes/tests/test_multigraph.py +528 -0
- .venv/lib/python3.11/site-packages/networkx/classes/tests/test_reportviews.py +1435 -0
- .venv/lib/python3.11/site-packages/networkx/classes/tests/test_special.py +131 -0
- .venv/lib/python3.11/site-packages/networkx/classes/tests/test_subgraphviews.py +362 -0
- .venv/lib/python3.11/site-packages/networkx/readwrite/__init__.py +17 -0
- .venv/lib/python3.11/site-packages/networkx/readwrite/__pycache__/__init__.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/readwrite/__pycache__/gexf.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/readwrite/__pycache__/gml.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/readwrite/__pycache__/graph6.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/readwrite/__pycache__/multiline_adjlist.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/readwrite/__pycache__/text.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/readwrite/gexf.py +1066 -0
- .venv/lib/python3.11/site-packages/networkx/readwrite/graphml.py +1053 -0
- .venv/lib/python3.11/site-packages/networkx/readwrite/json_graph/cytoscape.py +178 -0
- .venv/lib/python3.11/site-packages/networkx/readwrite/json_graph/node_link.py +330 -0
- .venv/lib/python3.11/site-packages/networkx/readwrite/json_graph/tree.py +137 -0
- .venv/lib/python3.11/site-packages/networkx/readwrite/leda.py +108 -0
- .venv/lib/python3.11/site-packages/networkx/readwrite/pajek.py +286 -0
.gitattributes
CHANGED
|
@@ -298,3 +298,6 @@ tuning-competition-baseline/.venv/lib/python3.11/site-packages/nvidia/cudnn/lib/
|
|
| 298 |
.venv/lib/python3.11/site-packages/torchaudio/functional/__pycache__/functional.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text
|
| 299 |
.venv/lib/python3.11/site-packages/torchaudio/lib/_torchaudio.so filter=lfs diff=lfs merge=lfs -text
|
| 300 |
.venv/lib/python3.11/site-packages/torchaudio/lib/libtorchaudio.so filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
| 298 |
.venv/lib/python3.11/site-packages/torchaudio/functional/__pycache__/functional.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text
|
| 299 |
.venv/lib/python3.11/site-packages/torchaudio/lib/_torchaudio.so filter=lfs diff=lfs merge=lfs -text
|
| 300 |
.venv/lib/python3.11/site-packages/torchaudio/lib/libtorchaudio.so filter=lfs diff=lfs merge=lfs -text
|
| 301 |
+
.venv/lib/python3.11/site-packages/torchaudio/lib/pybind11_prefixctc.so filter=lfs diff=lfs merge=lfs -text
|
| 302 |
+
.venv/lib/python3.11/site-packages/torchaudio/lib/libctc_prefix_decoder.so filter=lfs diff=lfs merge=lfs -text
|
| 303 |
+
.venv/lib/python3.11/site-packages/torchaudio/lib/libtorchaudio_sox.so filter=lfs diff=lfs merge=lfs -text
|
.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
|
.venv/lib/python3.11/site-packages/networkx/classes/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (751 Bytes). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/classes/__pycache__/coreviews.cpython-311.pyc
ADDED
|
Binary file (25.7 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/classes/__pycache__/digraph.cpython-311.pyc
ADDED
|
Binary file (55.5 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/classes/__pycache__/filters.cpython-311.pyc
ADDED
|
Binary file (6.62 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/classes/__pycache__/function.cpython-311.pyc
ADDED
|
Binary file (52.4 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/classes/__pycache__/graph.cpython-311.pyc
ADDED
|
Binary file (81 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/classes/__pycache__/graphviews.cpython-311.pyc
ADDED
|
Binary file (9.93 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/classes/__pycache__/multidigraph.cpython-311.pyc
ADDED
|
Binary file (41.3 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/classes/__pycache__/multigraph.cpython-311.pyc
ADDED
|
Binary file (53.1 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/classes/__pycache__/reportviews.cpython-311.pyc
ADDED
|
Binary file (78 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/classes/coreviews.py
ADDED
|
@@ -0,0 +1,431 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Views of core data structures such as nested Mappings (e.g. dict-of-dicts).
|
| 2 |
+
These ``Views`` often restrict element access, with either the entire view or
|
| 3 |
+
layers of nested mappings being read-only.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from collections.abc import Mapping
|
| 7 |
+
|
| 8 |
+
__all__ = [
|
| 9 |
+
"AtlasView",
|
| 10 |
+
"AdjacencyView",
|
| 11 |
+
"MultiAdjacencyView",
|
| 12 |
+
"UnionAtlas",
|
| 13 |
+
"UnionAdjacency",
|
| 14 |
+
"UnionMultiInner",
|
| 15 |
+
"UnionMultiAdjacency",
|
| 16 |
+
"FilterAtlas",
|
| 17 |
+
"FilterAdjacency",
|
| 18 |
+
"FilterMultiInner",
|
| 19 |
+
"FilterMultiAdjacency",
|
| 20 |
+
]
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
class AtlasView(Mapping):
|
| 24 |
+
"""An AtlasView is a Read-only Mapping of Mappings.
|
| 25 |
+
|
| 26 |
+
It is a View into a dict-of-dict data structure.
|
| 27 |
+
The inner level of dict is read-write. But the
|
| 28 |
+
outer level is read-only.
|
| 29 |
+
|
| 30 |
+
See Also
|
| 31 |
+
========
|
| 32 |
+
AdjacencyView: View into dict-of-dict-of-dict
|
| 33 |
+
MultiAdjacencyView: View into dict-of-dict-of-dict-of-dict
|
| 34 |
+
"""
|
| 35 |
+
|
| 36 |
+
__slots__ = ("_atlas",)
|
| 37 |
+
|
| 38 |
+
def __getstate__(self):
|
| 39 |
+
return {"_atlas": self._atlas}
|
| 40 |
+
|
| 41 |
+
def __setstate__(self, state):
|
| 42 |
+
self._atlas = state["_atlas"]
|
| 43 |
+
|
| 44 |
+
def __init__(self, d):
|
| 45 |
+
self._atlas = d
|
| 46 |
+
|
| 47 |
+
def __len__(self):
|
| 48 |
+
return len(self._atlas)
|
| 49 |
+
|
| 50 |
+
def __iter__(self):
|
| 51 |
+
return iter(self._atlas)
|
| 52 |
+
|
| 53 |
+
def __getitem__(self, key):
|
| 54 |
+
return self._atlas[key]
|
| 55 |
+
|
| 56 |
+
def copy(self):
|
| 57 |
+
return {n: self[n].copy() for n in self._atlas}
|
| 58 |
+
|
| 59 |
+
def __str__(self):
|
| 60 |
+
return str(self._atlas) # {nbr: self[nbr] for nbr in self})
|
| 61 |
+
|
| 62 |
+
def __repr__(self):
|
| 63 |
+
return f"{self.__class__.__name__}({self._atlas!r})"
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
class AdjacencyView(AtlasView):
|
| 67 |
+
"""An AdjacencyView is a Read-only Map of Maps of Maps.
|
| 68 |
+
|
| 69 |
+
It is a View into a dict-of-dict-of-dict data structure.
|
| 70 |
+
The inner level of dict is read-write. But the
|
| 71 |
+
outer levels are read-only.
|
| 72 |
+
|
| 73 |
+
See Also
|
| 74 |
+
========
|
| 75 |
+
AtlasView: View into dict-of-dict
|
| 76 |
+
MultiAdjacencyView: View into dict-of-dict-of-dict-of-dict
|
| 77 |
+
"""
|
| 78 |
+
|
| 79 |
+
__slots__ = () # Still uses AtlasView slots names _atlas
|
| 80 |
+
|
| 81 |
+
def __getitem__(self, name):
|
| 82 |
+
return AtlasView(self._atlas[name])
|
| 83 |
+
|
| 84 |
+
def copy(self):
|
| 85 |
+
return {n: self[n].copy() for n in self._atlas}
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
class MultiAdjacencyView(AdjacencyView):
|
| 89 |
+
"""An MultiAdjacencyView is a Read-only Map of Maps of Maps of Maps.
|
| 90 |
+
|
| 91 |
+
It is a View into a dict-of-dict-of-dict-of-dict data structure.
|
| 92 |
+
The inner level of dict is read-write. But the
|
| 93 |
+
outer levels are read-only.
|
| 94 |
+
|
| 95 |
+
See Also
|
| 96 |
+
========
|
| 97 |
+
AtlasView: View into dict-of-dict
|
| 98 |
+
AdjacencyView: View into dict-of-dict-of-dict
|
| 99 |
+
"""
|
| 100 |
+
|
| 101 |
+
__slots__ = () # Still uses AtlasView slots names _atlas
|
| 102 |
+
|
| 103 |
+
def __getitem__(self, name):
|
| 104 |
+
return AdjacencyView(self._atlas[name])
|
| 105 |
+
|
| 106 |
+
def copy(self):
|
| 107 |
+
return {n: self[n].copy() for n in self._atlas}
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
class UnionAtlas(Mapping):
|
| 111 |
+
"""A read-only union of two atlases (dict-of-dict).
|
| 112 |
+
|
| 113 |
+
The two dict-of-dicts represent the inner dict of
|
| 114 |
+
an Adjacency: `G.succ[node]` and `G.pred[node]`.
|
| 115 |
+
The inner level of dict of both hold attribute key:value
|
| 116 |
+
pairs and is read-write. But the outer level is read-only.
|
| 117 |
+
|
| 118 |
+
See Also
|
| 119 |
+
========
|
| 120 |
+
UnionAdjacency: View into dict-of-dict-of-dict
|
| 121 |
+
UnionMultiAdjacency: View into dict-of-dict-of-dict-of-dict
|
| 122 |
+
"""
|
| 123 |
+
|
| 124 |
+
__slots__ = ("_succ", "_pred")
|
| 125 |
+
|
| 126 |
+
def __getstate__(self):
|
| 127 |
+
return {"_succ": self._succ, "_pred": self._pred}
|
| 128 |
+
|
| 129 |
+
def __setstate__(self, state):
|
| 130 |
+
self._succ = state["_succ"]
|
| 131 |
+
self._pred = state["_pred"]
|
| 132 |
+
|
| 133 |
+
def __init__(self, succ, pred):
|
| 134 |
+
self._succ = succ
|
| 135 |
+
self._pred = pred
|
| 136 |
+
|
| 137 |
+
def __len__(self):
|
| 138 |
+
return len(self._succ.keys() | self._pred.keys())
|
| 139 |
+
|
| 140 |
+
def __iter__(self):
|
| 141 |
+
return iter(set(self._succ.keys()) | set(self._pred.keys()))
|
| 142 |
+
|
| 143 |
+
def __getitem__(self, key):
|
| 144 |
+
try:
|
| 145 |
+
return self._succ[key]
|
| 146 |
+
except KeyError:
|
| 147 |
+
return self._pred[key]
|
| 148 |
+
|
| 149 |
+
def copy(self):
|
| 150 |
+
result = {nbr: dd.copy() for nbr, dd in self._succ.items()}
|
| 151 |
+
for nbr, dd in self._pred.items():
|
| 152 |
+
if nbr in result:
|
| 153 |
+
result[nbr].update(dd)
|
| 154 |
+
else:
|
| 155 |
+
result[nbr] = dd.copy()
|
| 156 |
+
return result
|
| 157 |
+
|
| 158 |
+
def __str__(self):
|
| 159 |
+
return str({nbr: self[nbr] for nbr in self})
|
| 160 |
+
|
| 161 |
+
def __repr__(self):
|
| 162 |
+
return f"{self.__class__.__name__}({self._succ!r}, {self._pred!r})"
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
class UnionAdjacency(Mapping):
|
| 166 |
+
"""A read-only union of dict Adjacencies as a Map of Maps of Maps.
|
| 167 |
+
|
| 168 |
+
The two input dict-of-dict-of-dicts represent the union of
|
| 169 |
+
`G.succ` and `G.pred`. Return values are UnionAtlas
|
| 170 |
+
The inner level of dict is read-write. But the
|
| 171 |
+
middle and outer levels are read-only.
|
| 172 |
+
|
| 173 |
+
succ : a dict-of-dict-of-dict {node: nbrdict}
|
| 174 |
+
pred : a dict-of-dict-of-dict {node: nbrdict}
|
| 175 |
+
The keys for the two dicts should be the same
|
| 176 |
+
|
| 177 |
+
See Also
|
| 178 |
+
========
|
| 179 |
+
UnionAtlas: View into dict-of-dict
|
| 180 |
+
UnionMultiAdjacency: View into dict-of-dict-of-dict-of-dict
|
| 181 |
+
"""
|
| 182 |
+
|
| 183 |
+
__slots__ = ("_succ", "_pred")
|
| 184 |
+
|
| 185 |
+
def __getstate__(self):
|
| 186 |
+
return {"_succ": self._succ, "_pred": self._pred}
|
| 187 |
+
|
| 188 |
+
def __setstate__(self, state):
|
| 189 |
+
self._succ = state["_succ"]
|
| 190 |
+
self._pred = state["_pred"]
|
| 191 |
+
|
| 192 |
+
def __init__(self, succ, pred):
|
| 193 |
+
# keys must be the same for two input dicts
|
| 194 |
+
assert len(set(succ.keys()) ^ set(pred.keys())) == 0
|
| 195 |
+
self._succ = succ
|
| 196 |
+
self._pred = pred
|
| 197 |
+
|
| 198 |
+
def __len__(self):
|
| 199 |
+
return len(self._succ) # length of each dict should be the same
|
| 200 |
+
|
| 201 |
+
def __iter__(self):
|
| 202 |
+
return iter(self._succ)
|
| 203 |
+
|
| 204 |
+
def __getitem__(self, nbr):
|
| 205 |
+
return UnionAtlas(self._succ[nbr], self._pred[nbr])
|
| 206 |
+
|
| 207 |
+
def copy(self):
|
| 208 |
+
return {n: self[n].copy() for n in self._succ}
|
| 209 |
+
|
| 210 |
+
def __str__(self):
|
| 211 |
+
return str({nbr: self[nbr] for nbr in self})
|
| 212 |
+
|
| 213 |
+
def __repr__(self):
|
| 214 |
+
return f"{self.__class__.__name__}({self._succ!r}, {self._pred!r})"
|
| 215 |
+
|
| 216 |
+
|
| 217 |
+
class UnionMultiInner(UnionAtlas):
|
| 218 |
+
"""A read-only union of two inner dicts of MultiAdjacencies.
|
| 219 |
+
|
| 220 |
+
The two input dict-of-dict-of-dicts represent the union of
|
| 221 |
+
`G.succ[node]` and `G.pred[node]` for MultiDiGraphs.
|
| 222 |
+
Return values are UnionAtlas.
|
| 223 |
+
The inner level of dict is read-write. But the outer levels are read-only.
|
| 224 |
+
|
| 225 |
+
See Also
|
| 226 |
+
========
|
| 227 |
+
UnionAtlas: View into dict-of-dict
|
| 228 |
+
UnionAdjacency: View into dict-of-dict-of-dict
|
| 229 |
+
UnionMultiAdjacency: View into dict-of-dict-of-dict-of-dict
|
| 230 |
+
"""
|
| 231 |
+
|
| 232 |
+
__slots__ = () # Still uses UnionAtlas slots names _succ, _pred
|
| 233 |
+
|
| 234 |
+
def __getitem__(self, node):
|
| 235 |
+
in_succ = node in self._succ
|
| 236 |
+
in_pred = node in self._pred
|
| 237 |
+
if in_succ:
|
| 238 |
+
if in_pred:
|
| 239 |
+
return UnionAtlas(self._succ[node], self._pred[node])
|
| 240 |
+
return UnionAtlas(self._succ[node], {})
|
| 241 |
+
return UnionAtlas({}, self._pred[node])
|
| 242 |
+
|
| 243 |
+
def copy(self):
|
| 244 |
+
nodes = set(self._succ.keys()) | set(self._pred.keys())
|
| 245 |
+
return {n: self[n].copy() for n in nodes}
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
class UnionMultiAdjacency(UnionAdjacency):
|
| 249 |
+
"""A read-only union of two dict MultiAdjacencies.
|
| 250 |
+
|
| 251 |
+
The two input dict-of-dict-of-dict-of-dicts represent the union of
|
| 252 |
+
`G.succ` and `G.pred` for MultiDiGraphs. Return values are UnionAdjacency.
|
| 253 |
+
The inner level of dict is read-write. But the outer levels are read-only.
|
| 254 |
+
|
| 255 |
+
See Also
|
| 256 |
+
========
|
| 257 |
+
UnionAtlas: View into dict-of-dict
|
| 258 |
+
UnionMultiInner: View into dict-of-dict-of-dict
|
| 259 |
+
"""
|
| 260 |
+
|
| 261 |
+
__slots__ = () # Still uses UnionAdjacency slots names _succ, _pred
|
| 262 |
+
|
| 263 |
+
def __getitem__(self, node):
|
| 264 |
+
return UnionMultiInner(self._succ[node], self._pred[node])
|
| 265 |
+
|
| 266 |
+
|
| 267 |
+
class FilterAtlas(Mapping): # nodedict, nbrdict, keydict
|
| 268 |
+
"""A read-only Mapping of Mappings with filtering criteria for nodes.
|
| 269 |
+
|
| 270 |
+
It is a view into a dict-of-dict data structure, and it selects only
|
| 271 |
+
nodes that meet the criteria defined by ``NODE_OK``.
|
| 272 |
+
|
| 273 |
+
See Also
|
| 274 |
+
========
|
| 275 |
+
FilterAdjacency
|
| 276 |
+
FilterMultiInner
|
| 277 |
+
FilterMultiAdjacency
|
| 278 |
+
"""
|
| 279 |
+
|
| 280 |
+
def __init__(self, d, NODE_OK):
|
| 281 |
+
self._atlas = d
|
| 282 |
+
self.NODE_OK = NODE_OK
|
| 283 |
+
|
| 284 |
+
def __len__(self):
|
| 285 |
+
# check whether NODE_OK stores the number of nodes as `length`
|
| 286 |
+
# or the nodes themselves as a set `nodes`. If not, count the nodes.
|
| 287 |
+
if hasattr(self.NODE_OK, "length"):
|
| 288 |
+
return self.NODE_OK.length
|
| 289 |
+
if hasattr(self.NODE_OK, "nodes"):
|
| 290 |
+
return len(self.NODE_OK.nodes & self._atlas.keys())
|
| 291 |
+
return sum(1 for n in self._atlas if self.NODE_OK(n))
|
| 292 |
+
|
| 293 |
+
def __iter__(self):
|
| 294 |
+
try: # check that NODE_OK has attr 'nodes'
|
| 295 |
+
node_ok_shorter = 2 * len(self.NODE_OK.nodes) < len(self._atlas)
|
| 296 |
+
except AttributeError:
|
| 297 |
+
node_ok_shorter = False
|
| 298 |
+
if node_ok_shorter:
|
| 299 |
+
return (n for n in self.NODE_OK.nodes if n in self._atlas)
|
| 300 |
+
return (n for n in self._atlas if self.NODE_OK(n))
|
| 301 |
+
|
| 302 |
+
def __getitem__(self, key):
|
| 303 |
+
if key in self._atlas and self.NODE_OK(key):
|
| 304 |
+
return self._atlas[key]
|
| 305 |
+
raise KeyError(f"Key {key} not found")
|
| 306 |
+
|
| 307 |
+
def __str__(self):
|
| 308 |
+
return str({nbr: self[nbr] for nbr in self})
|
| 309 |
+
|
| 310 |
+
def __repr__(self):
|
| 311 |
+
return f"{self.__class__.__name__}({self._atlas!r}, {self.NODE_OK!r})"
|
| 312 |
+
|
| 313 |
+
|
| 314 |
+
class FilterAdjacency(Mapping): # edgedict
|
| 315 |
+
"""A read-only Mapping of Mappings with filtering criteria for nodes and edges.
|
| 316 |
+
|
| 317 |
+
It is a view into a dict-of-dict-of-dict data structure, and it selects nodes
|
| 318 |
+
and edges that satisfy specific criteria defined by ``NODE_OK`` and ``EDGE_OK``,
|
| 319 |
+
respectively.
|
| 320 |
+
|
| 321 |
+
See Also
|
| 322 |
+
========
|
| 323 |
+
FilterAtlas
|
| 324 |
+
FilterMultiInner
|
| 325 |
+
FilterMultiAdjacency
|
| 326 |
+
"""
|
| 327 |
+
|
| 328 |
+
def __init__(self, d, NODE_OK, EDGE_OK):
|
| 329 |
+
self._atlas = d
|
| 330 |
+
self.NODE_OK = NODE_OK
|
| 331 |
+
self.EDGE_OK = EDGE_OK
|
| 332 |
+
|
| 333 |
+
def __len__(self):
|
| 334 |
+
# check whether NODE_OK stores the number of nodes as `length`
|
| 335 |
+
# or the nodes themselves as a set `nodes`. If not, count the nodes.
|
| 336 |
+
if hasattr(self.NODE_OK, "length"):
|
| 337 |
+
return self.NODE_OK.length
|
| 338 |
+
if hasattr(self.NODE_OK, "nodes"):
|
| 339 |
+
return len(self.NODE_OK.nodes & self._atlas.keys())
|
| 340 |
+
return sum(1 for n in self._atlas if self.NODE_OK(n))
|
| 341 |
+
|
| 342 |
+
def __iter__(self):
|
| 343 |
+
try: # check that NODE_OK has attr 'nodes'
|
| 344 |
+
node_ok_shorter = 2 * len(self.NODE_OK.nodes) < len(self._atlas)
|
| 345 |
+
except AttributeError:
|
| 346 |
+
node_ok_shorter = False
|
| 347 |
+
if node_ok_shorter:
|
| 348 |
+
return (n for n in self.NODE_OK.nodes if n in self._atlas)
|
| 349 |
+
return (n for n in self._atlas if self.NODE_OK(n))
|
| 350 |
+
|
| 351 |
+
def __getitem__(self, node):
|
| 352 |
+
if node in self._atlas and self.NODE_OK(node):
|
| 353 |
+
|
| 354 |
+
def new_node_ok(nbr):
|
| 355 |
+
return self.NODE_OK(nbr) and self.EDGE_OK(node, nbr)
|
| 356 |
+
|
| 357 |
+
return FilterAtlas(self._atlas[node], new_node_ok)
|
| 358 |
+
raise KeyError(f"Key {node} not found")
|
| 359 |
+
|
| 360 |
+
def __str__(self):
|
| 361 |
+
return str({nbr: self[nbr] for nbr in self})
|
| 362 |
+
|
| 363 |
+
def __repr__(self):
|
| 364 |
+
name = self.__class__.__name__
|
| 365 |
+
return f"{name}({self._atlas!r}, {self.NODE_OK!r}, {self.EDGE_OK!r})"
|
| 366 |
+
|
| 367 |
+
|
| 368 |
+
class FilterMultiInner(FilterAdjacency): # muliedge_seconddict
|
| 369 |
+
"""A read-only Mapping of Mappings with filtering criteria for nodes and edges.
|
| 370 |
+
|
| 371 |
+
It is a view into a dict-of-dict-of-dict-of-dict data structure, and it selects nodes
|
| 372 |
+
and edges that meet specific criteria defined by ``NODE_OK`` and ``EDGE_OK``.
|
| 373 |
+
|
| 374 |
+
See Also
|
| 375 |
+
========
|
| 376 |
+
FilterAtlas
|
| 377 |
+
FilterAdjacency
|
| 378 |
+
FilterMultiAdjacency
|
| 379 |
+
"""
|
| 380 |
+
|
| 381 |
+
def __iter__(self):
|
| 382 |
+
try: # check that NODE_OK has attr 'nodes'
|
| 383 |
+
node_ok_shorter = 2 * len(self.NODE_OK.nodes) < len(self._atlas)
|
| 384 |
+
except AttributeError:
|
| 385 |
+
node_ok_shorter = False
|
| 386 |
+
if node_ok_shorter:
|
| 387 |
+
my_nodes = (n for n in self.NODE_OK.nodes if n in self._atlas)
|
| 388 |
+
else:
|
| 389 |
+
my_nodes = (n for n in self._atlas if self.NODE_OK(n))
|
| 390 |
+
for n in my_nodes:
|
| 391 |
+
some_keys_ok = False
|
| 392 |
+
for key in self._atlas[n]:
|
| 393 |
+
if self.EDGE_OK(n, key):
|
| 394 |
+
some_keys_ok = True
|
| 395 |
+
break
|
| 396 |
+
if some_keys_ok is True:
|
| 397 |
+
yield n
|
| 398 |
+
|
| 399 |
+
def __getitem__(self, nbr):
|
| 400 |
+
if nbr in self._atlas and self.NODE_OK(nbr):
|
| 401 |
+
|
| 402 |
+
def new_node_ok(key):
|
| 403 |
+
return self.EDGE_OK(nbr, key)
|
| 404 |
+
|
| 405 |
+
return FilterAtlas(self._atlas[nbr], new_node_ok)
|
| 406 |
+
raise KeyError(f"Key {nbr} not found")
|
| 407 |
+
|
| 408 |
+
|
| 409 |
+
class FilterMultiAdjacency(FilterAdjacency): # multiedgedict
|
| 410 |
+
"""A read-only Mapping of Mappings with filtering criteria
|
| 411 |
+
for nodes and edges.
|
| 412 |
+
|
| 413 |
+
It is a view into a dict-of-dict-of-dict-of-dict data structure,
|
| 414 |
+
and it selects nodes and edges that satisfy specific criteria
|
| 415 |
+
defined by ``NODE_OK`` and ``EDGE_OK``, respectively.
|
| 416 |
+
|
| 417 |
+
See Also
|
| 418 |
+
========
|
| 419 |
+
FilterAtlas
|
| 420 |
+
FilterAdjacency
|
| 421 |
+
FilterMultiInner
|
| 422 |
+
"""
|
| 423 |
+
|
| 424 |
+
def __getitem__(self, node):
|
| 425 |
+
if node in self._atlas and self.NODE_OK(node):
|
| 426 |
+
|
| 427 |
+
def edge_ok(nbr, key):
|
| 428 |
+
return self.NODE_OK(nbr) and self.EDGE_OK(node, nbr, key)
|
| 429 |
+
|
| 430 |
+
return FilterMultiInner(self._atlas[node], self.NODE_OK, edge_ok)
|
| 431 |
+
raise KeyError(f"Key {node} not found")
|
.venv/lib/python3.11/site-packages/networkx/classes/digraph.py
ADDED
|
@@ -0,0 +1,1352 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Base class for directed graphs."""
|
| 2 |
+
|
| 3 |
+
from copy import deepcopy
|
| 4 |
+
from functools import cached_property
|
| 5 |
+
|
| 6 |
+
import networkx as nx
|
| 7 |
+
from networkx import convert
|
| 8 |
+
from networkx.classes.coreviews import AdjacencyView
|
| 9 |
+
from networkx.classes.graph import Graph
|
| 10 |
+
from networkx.classes.reportviews import (
|
| 11 |
+
DiDegreeView,
|
| 12 |
+
InDegreeView,
|
| 13 |
+
InEdgeView,
|
| 14 |
+
OutDegreeView,
|
| 15 |
+
OutEdgeView,
|
| 16 |
+
)
|
| 17 |
+
from networkx.exception import NetworkXError
|
| 18 |
+
|
| 19 |
+
__all__ = ["DiGraph"]
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class _CachedPropertyResetterAdjAndSucc:
|
| 23 |
+
"""Data Descriptor class that syncs and resets cached properties adj and succ
|
| 24 |
+
|
| 25 |
+
The cached properties `adj` and `succ` are reset whenever `_adj` or `_succ`
|
| 26 |
+
are set to new objects. In addition, the attributes `_succ` and `_adj`
|
| 27 |
+
are synced so these two names point to the same object.
|
| 28 |
+
|
| 29 |
+
Warning: most of the time, when ``G._adj`` is set, ``G._pred`` should also
|
| 30 |
+
be set to maintain a valid data structure. They share datadicts.
|
| 31 |
+
|
| 32 |
+
This object sits on a class and ensures that any instance of that
|
| 33 |
+
class clears its cached properties "succ" and "adj" whenever the
|
| 34 |
+
underlying instance attributes "_succ" or "_adj" are set to a new object.
|
| 35 |
+
It only affects the set process of the obj._adj and obj._succ attribute.
|
| 36 |
+
All get/del operations act as they normally would.
|
| 37 |
+
|
| 38 |
+
For info on Data Descriptors see: https://docs.python.org/3/howto/descriptor.html
|
| 39 |
+
"""
|
| 40 |
+
|
| 41 |
+
def __set__(self, obj, value):
|
| 42 |
+
od = obj.__dict__
|
| 43 |
+
od["_adj"] = value
|
| 44 |
+
od["_succ"] = value
|
| 45 |
+
# reset cached properties
|
| 46 |
+
props = [
|
| 47 |
+
"adj",
|
| 48 |
+
"succ",
|
| 49 |
+
"edges",
|
| 50 |
+
"out_edges",
|
| 51 |
+
"degree",
|
| 52 |
+
"out_degree",
|
| 53 |
+
"in_degree",
|
| 54 |
+
]
|
| 55 |
+
for prop in props:
|
| 56 |
+
if prop in od:
|
| 57 |
+
del od[prop]
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
class _CachedPropertyResetterPred:
|
| 61 |
+
"""Data Descriptor class for _pred that resets ``pred`` cached_property when needed
|
| 62 |
+
|
| 63 |
+
This assumes that the ``cached_property`` ``G.pred`` should be reset whenever
|
| 64 |
+
``G._pred`` is set to a new value.
|
| 65 |
+
|
| 66 |
+
Warning: most of the time, when ``G._pred`` is set, ``G._adj`` should also
|
| 67 |
+
be set to maintain a valid data structure. They share datadicts.
|
| 68 |
+
|
| 69 |
+
This object sits on a class and ensures that any instance of that
|
| 70 |
+
class clears its cached property "pred" whenever the underlying
|
| 71 |
+
instance attribute "_pred" is set to a new object. It only affects
|
| 72 |
+
the set process of the obj._pred attribute. All get/del operations
|
| 73 |
+
act as they normally would.
|
| 74 |
+
|
| 75 |
+
For info on Data Descriptors see: https://docs.python.org/3/howto/descriptor.html
|
| 76 |
+
"""
|
| 77 |
+
|
| 78 |
+
def __set__(self, obj, value):
|
| 79 |
+
od = obj.__dict__
|
| 80 |
+
od["_pred"] = value
|
| 81 |
+
# reset cached properties
|
| 82 |
+
props = ["pred", "in_edges", "degree", "out_degree", "in_degree"]
|
| 83 |
+
for prop in props:
|
| 84 |
+
if prop in od:
|
| 85 |
+
del od[prop]
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
class DiGraph(Graph):
|
| 89 |
+
"""
|
| 90 |
+
Base class for directed graphs.
|
| 91 |
+
|
| 92 |
+
A DiGraph stores nodes and edges with optional data, or attributes.
|
| 93 |
+
|
| 94 |
+
DiGraphs hold directed edges. Self loops are allowed but multiple
|
| 95 |
+
(parallel) edges are not.
|
| 96 |
+
|
| 97 |
+
Nodes can be arbitrary (hashable) Python objects with optional
|
| 98 |
+
key/value attributes. By convention `None` is not used as a node.
|
| 99 |
+
|
| 100 |
+
Edges are represented as links between nodes with optional
|
| 101 |
+
key/value attributes.
|
| 102 |
+
|
| 103 |
+
Parameters
|
| 104 |
+
----------
|
| 105 |
+
incoming_graph_data : input graph (optional, default: None)
|
| 106 |
+
Data to initialize graph. If None (default) an empty
|
| 107 |
+
graph is created. The data can be any format that is supported
|
| 108 |
+
by the to_networkx_graph() function, currently including edge list,
|
| 109 |
+
dict of dicts, dict of lists, NetworkX graph, 2D NumPy array, SciPy
|
| 110 |
+
sparse matrix, or PyGraphviz graph.
|
| 111 |
+
|
| 112 |
+
attr : keyword arguments, optional (default= no attributes)
|
| 113 |
+
Attributes to add to graph as key=value pairs.
|
| 114 |
+
|
| 115 |
+
See Also
|
| 116 |
+
--------
|
| 117 |
+
Graph
|
| 118 |
+
MultiGraph
|
| 119 |
+
MultiDiGraph
|
| 120 |
+
|
| 121 |
+
Examples
|
| 122 |
+
--------
|
| 123 |
+
Create an empty graph structure (a "null graph") with no nodes and
|
| 124 |
+
no edges.
|
| 125 |
+
|
| 126 |
+
>>> G = nx.DiGraph()
|
| 127 |
+
|
| 128 |
+
G can be grown in several ways.
|
| 129 |
+
|
| 130 |
+
**Nodes:**
|
| 131 |
+
|
| 132 |
+
Add one node at a time:
|
| 133 |
+
|
| 134 |
+
>>> G.add_node(1)
|
| 135 |
+
|
| 136 |
+
Add the nodes from any container (a list, dict, set or
|
| 137 |
+
even the lines from a file or the nodes from another graph).
|
| 138 |
+
|
| 139 |
+
>>> G.add_nodes_from([2, 3])
|
| 140 |
+
>>> G.add_nodes_from(range(100, 110))
|
| 141 |
+
>>> H = nx.path_graph(10)
|
| 142 |
+
>>> G.add_nodes_from(H)
|
| 143 |
+
|
| 144 |
+
In addition to strings and integers any hashable Python object
|
| 145 |
+
(except None) can represent a node, e.g. a customized node object,
|
| 146 |
+
or even another Graph.
|
| 147 |
+
|
| 148 |
+
>>> G.add_node(H)
|
| 149 |
+
|
| 150 |
+
**Edges:**
|
| 151 |
+
|
| 152 |
+
G can also be grown by adding edges.
|
| 153 |
+
|
| 154 |
+
Add one edge,
|
| 155 |
+
|
| 156 |
+
>>> G.add_edge(1, 2)
|
| 157 |
+
|
| 158 |
+
a list of edges,
|
| 159 |
+
|
| 160 |
+
>>> G.add_edges_from([(1, 2), (1, 3)])
|
| 161 |
+
|
| 162 |
+
or a collection of edges,
|
| 163 |
+
|
| 164 |
+
>>> G.add_edges_from(H.edges)
|
| 165 |
+
|
| 166 |
+
If some edges connect nodes not yet in the graph, the nodes
|
| 167 |
+
are added automatically. There are no errors when adding
|
| 168 |
+
nodes or edges that already exist.
|
| 169 |
+
|
| 170 |
+
**Attributes:**
|
| 171 |
+
|
| 172 |
+
Each graph, node, and edge can hold key/value attribute pairs
|
| 173 |
+
in an associated attribute dictionary (the keys must be hashable).
|
| 174 |
+
By default these are empty, but can be added or changed using
|
| 175 |
+
add_edge, add_node or direct manipulation of the attribute
|
| 176 |
+
dictionaries named graph, node and edge respectively.
|
| 177 |
+
|
| 178 |
+
>>> G = nx.DiGraph(day="Friday")
|
| 179 |
+
>>> G.graph
|
| 180 |
+
{'day': 'Friday'}
|
| 181 |
+
|
| 182 |
+
Add node attributes using add_node(), add_nodes_from() or G.nodes
|
| 183 |
+
|
| 184 |
+
>>> G.add_node(1, time="5pm")
|
| 185 |
+
>>> G.add_nodes_from([3], time="2pm")
|
| 186 |
+
>>> G.nodes[1]
|
| 187 |
+
{'time': '5pm'}
|
| 188 |
+
>>> G.nodes[1]["room"] = 714
|
| 189 |
+
>>> del G.nodes[1]["room"] # remove attribute
|
| 190 |
+
>>> list(G.nodes(data=True))
|
| 191 |
+
[(1, {'time': '5pm'}), (3, {'time': '2pm'})]
|
| 192 |
+
|
| 193 |
+
Add edge attributes using add_edge(), add_edges_from(), subscript
|
| 194 |
+
notation, or G.edges.
|
| 195 |
+
|
| 196 |
+
>>> G.add_edge(1, 2, weight=4.7)
|
| 197 |
+
>>> G.add_edges_from([(3, 4), (4, 5)], color="red")
|
| 198 |
+
>>> G.add_edges_from([(1, 2, {"color": "blue"}), (2, 3, {"weight": 8})])
|
| 199 |
+
>>> G[1][2]["weight"] = 4.7
|
| 200 |
+
>>> G.edges[1, 2]["weight"] = 4
|
| 201 |
+
|
| 202 |
+
Warning: we protect the graph data structure by making `G.edges[1, 2]` a
|
| 203 |
+
read-only dict-like structure. However, you can assign to attributes
|
| 204 |
+
in e.g. `G.edges[1, 2]`. Thus, use 2 sets of brackets to add/change
|
| 205 |
+
data attributes: `G.edges[1, 2]['weight'] = 4`
|
| 206 |
+
(For multigraphs: `MG.edges[u, v, key][name] = value`).
|
| 207 |
+
|
| 208 |
+
**Shortcuts:**
|
| 209 |
+
|
| 210 |
+
Many common graph features allow python syntax to speed reporting.
|
| 211 |
+
|
| 212 |
+
>>> 1 in G # check if node in graph
|
| 213 |
+
True
|
| 214 |
+
>>> [n for n in G if n < 3] # iterate through nodes
|
| 215 |
+
[1, 2]
|
| 216 |
+
>>> len(G) # number of nodes in graph
|
| 217 |
+
5
|
| 218 |
+
|
| 219 |
+
Often the best way to traverse all edges of a graph is via the neighbors.
|
| 220 |
+
The neighbors are reported as an adjacency-dict `G.adj` or `G.adjacency()`
|
| 221 |
+
|
| 222 |
+
>>> for n, nbrsdict in G.adjacency():
|
| 223 |
+
... for nbr, eattr in nbrsdict.items():
|
| 224 |
+
... if "weight" in eattr:
|
| 225 |
+
... # Do something useful with the edges
|
| 226 |
+
... pass
|
| 227 |
+
|
| 228 |
+
But the edges reporting object is often more convenient:
|
| 229 |
+
|
| 230 |
+
>>> for u, v, weight in G.edges(data="weight"):
|
| 231 |
+
... if weight is not None:
|
| 232 |
+
... # Do something useful with the edges
|
| 233 |
+
... pass
|
| 234 |
+
|
| 235 |
+
**Reporting:**
|
| 236 |
+
|
| 237 |
+
Simple graph information is obtained using object-attributes and methods.
|
| 238 |
+
Reporting usually provides views instead of containers to reduce memory
|
| 239 |
+
usage. The views update as the graph is updated similarly to dict-views.
|
| 240 |
+
The objects `nodes`, `edges` and `adj` provide access to data attributes
|
| 241 |
+
via lookup (e.g. `nodes[n]`, `edges[u, v]`, `adj[u][v]`) and iteration
|
| 242 |
+
(e.g. `nodes.items()`, `nodes.data('color')`,
|
| 243 |
+
`nodes.data('color', default='blue')` and similarly for `edges`)
|
| 244 |
+
Views exist for `nodes`, `edges`, `neighbors()`/`adj` and `degree`.
|
| 245 |
+
|
| 246 |
+
For details on these and other miscellaneous methods, see below.
|
| 247 |
+
|
| 248 |
+
**Subclasses (Advanced):**
|
| 249 |
+
|
| 250 |
+
The Graph class uses a dict-of-dict-of-dict data structure.
|
| 251 |
+
The outer dict (node_dict) holds adjacency information keyed by node.
|
| 252 |
+
The next dict (adjlist_dict) represents the adjacency information and holds
|
| 253 |
+
edge data keyed by neighbor. The inner dict (edge_attr_dict) represents
|
| 254 |
+
the edge data and holds edge attribute values keyed by attribute names.
|
| 255 |
+
|
| 256 |
+
Each of these three dicts can be replaced in a subclass by a user defined
|
| 257 |
+
dict-like object. In general, the dict-like features should be
|
| 258 |
+
maintained but extra features can be added. To replace one of the
|
| 259 |
+
dicts create a new graph class by changing the class(!) variable
|
| 260 |
+
holding the factory for that dict-like structure. The variable names are
|
| 261 |
+
node_dict_factory, node_attr_dict_factory, adjlist_inner_dict_factory,
|
| 262 |
+
adjlist_outer_dict_factory, edge_attr_dict_factory and graph_attr_dict_factory.
|
| 263 |
+
|
| 264 |
+
node_dict_factory : function, (default: dict)
|
| 265 |
+
Factory function to be used to create the dict containing node
|
| 266 |
+
attributes, keyed by node id.
|
| 267 |
+
It should require no arguments and return a dict-like object
|
| 268 |
+
|
| 269 |
+
node_attr_dict_factory: function, (default: dict)
|
| 270 |
+
Factory function to be used to create the node attribute
|
| 271 |
+
dict which holds attribute values keyed by attribute name.
|
| 272 |
+
It should require no arguments and return a dict-like object
|
| 273 |
+
|
| 274 |
+
adjlist_outer_dict_factory : function, (default: dict)
|
| 275 |
+
Factory function to be used to create the outer-most dict
|
| 276 |
+
in the data structure that holds adjacency info keyed by node.
|
| 277 |
+
It should require no arguments and return a dict-like object.
|
| 278 |
+
|
| 279 |
+
adjlist_inner_dict_factory : function, optional (default: dict)
|
| 280 |
+
Factory function to be used to create the adjacency list
|
| 281 |
+
dict which holds edge data keyed by neighbor.
|
| 282 |
+
It should require no arguments and return a dict-like object
|
| 283 |
+
|
| 284 |
+
edge_attr_dict_factory : function, optional (default: dict)
|
| 285 |
+
Factory function to be used to create the edge attribute
|
| 286 |
+
dict which holds attribute values keyed by attribute name.
|
| 287 |
+
It should require no arguments and return a dict-like object.
|
| 288 |
+
|
| 289 |
+
graph_attr_dict_factory : function, (default: dict)
|
| 290 |
+
Factory function to be used to create the graph attribute
|
| 291 |
+
dict which holds attribute values keyed by attribute name.
|
| 292 |
+
It should require no arguments and return a dict-like object.
|
| 293 |
+
|
| 294 |
+
Typically, if your extension doesn't impact the data structure all
|
| 295 |
+
methods will inherited without issue except: `to_directed/to_undirected`.
|
| 296 |
+
By default these methods create a DiGraph/Graph class and you probably
|
| 297 |
+
want them to create your extension of a DiGraph/Graph. To facilitate
|
| 298 |
+
this we define two class variables that you can set in your subclass.
|
| 299 |
+
|
| 300 |
+
to_directed_class : callable, (default: DiGraph or MultiDiGraph)
|
| 301 |
+
Class to create a new graph structure in the `to_directed` method.
|
| 302 |
+
If `None`, a NetworkX class (DiGraph or MultiDiGraph) is used.
|
| 303 |
+
|
| 304 |
+
to_undirected_class : callable, (default: Graph or MultiGraph)
|
| 305 |
+
Class to create a new graph structure in the `to_undirected` method.
|
| 306 |
+
If `None`, a NetworkX class (Graph or MultiGraph) is used.
|
| 307 |
+
|
| 308 |
+
**Subclassing Example**
|
| 309 |
+
|
| 310 |
+
Create a low memory graph class that effectively disallows edge
|
| 311 |
+
attributes by using a single attribute dict for all edges.
|
| 312 |
+
This reduces the memory used, but you lose edge attributes.
|
| 313 |
+
|
| 314 |
+
>>> class ThinGraph(nx.Graph):
|
| 315 |
+
... all_edge_dict = {"weight": 1}
|
| 316 |
+
...
|
| 317 |
+
... def single_edge_dict(self):
|
| 318 |
+
... return self.all_edge_dict
|
| 319 |
+
...
|
| 320 |
+
... edge_attr_dict_factory = single_edge_dict
|
| 321 |
+
>>> G = ThinGraph()
|
| 322 |
+
>>> G.add_edge(2, 1)
|
| 323 |
+
>>> G[2][1]
|
| 324 |
+
{'weight': 1}
|
| 325 |
+
>>> G.add_edge(2, 2)
|
| 326 |
+
>>> G[2][1] is G[2][2]
|
| 327 |
+
True
|
| 328 |
+
"""
|
| 329 |
+
|
| 330 |
+
_adj = _CachedPropertyResetterAdjAndSucc() # type: ignore[assignment]
|
| 331 |
+
_succ = _adj # type: ignore[has-type]
|
| 332 |
+
_pred = _CachedPropertyResetterPred()
|
| 333 |
+
|
| 334 |
+
def __init__(self, incoming_graph_data=None, **attr):
|
| 335 |
+
"""Initialize a graph with edges, name, or graph attributes.
|
| 336 |
+
|
| 337 |
+
Parameters
|
| 338 |
+
----------
|
| 339 |
+
incoming_graph_data : input graph (optional, default: None)
|
| 340 |
+
Data to initialize graph. If None (default) an empty
|
| 341 |
+
graph is created. The data can be an edge list, or any
|
| 342 |
+
NetworkX graph object. If the corresponding optional Python
|
| 343 |
+
packages are installed the data can also be a 2D NumPy array, a
|
| 344 |
+
SciPy sparse array, or a PyGraphviz graph.
|
| 345 |
+
|
| 346 |
+
attr : keyword arguments, optional (default= no attributes)
|
| 347 |
+
Attributes to add to graph as key=value pairs.
|
| 348 |
+
|
| 349 |
+
See Also
|
| 350 |
+
--------
|
| 351 |
+
convert
|
| 352 |
+
|
| 353 |
+
Examples
|
| 354 |
+
--------
|
| 355 |
+
>>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 356 |
+
>>> G = nx.Graph(name="my graph")
|
| 357 |
+
>>> e = [(1, 2), (2, 3), (3, 4)] # list of edges
|
| 358 |
+
>>> G = nx.Graph(e)
|
| 359 |
+
|
| 360 |
+
Arbitrary graph attribute pairs (key=value) may be assigned
|
| 361 |
+
|
| 362 |
+
>>> G = nx.Graph(e, day="Friday")
|
| 363 |
+
>>> G.graph
|
| 364 |
+
{'day': 'Friday'}
|
| 365 |
+
|
| 366 |
+
"""
|
| 367 |
+
self.graph = self.graph_attr_dict_factory() # dictionary for graph attributes
|
| 368 |
+
self._node = self.node_dict_factory() # dictionary for node attr
|
| 369 |
+
# We store two adjacency lists:
|
| 370 |
+
# the predecessors of node n are stored in the dict self._pred
|
| 371 |
+
# the successors of node n are stored in the dict self._succ=self._adj
|
| 372 |
+
self._adj = self.adjlist_outer_dict_factory() # empty adjacency dict successor
|
| 373 |
+
self._pred = self.adjlist_outer_dict_factory() # predecessor
|
| 374 |
+
# Note: self._succ = self._adj # successor
|
| 375 |
+
|
| 376 |
+
self.__networkx_cache__ = {}
|
| 377 |
+
# attempt to load graph with data
|
| 378 |
+
if incoming_graph_data is not None:
|
| 379 |
+
convert.to_networkx_graph(incoming_graph_data, create_using=self)
|
| 380 |
+
# load graph attributes (must be after convert)
|
| 381 |
+
self.graph.update(attr)
|
| 382 |
+
|
| 383 |
+
@cached_property
|
| 384 |
+
def adj(self):
|
| 385 |
+
"""Graph adjacency object holding the neighbors of each node.
|
| 386 |
+
|
| 387 |
+
This object is a read-only dict-like structure with node keys
|
| 388 |
+
and neighbor-dict values. The neighbor-dict is keyed by neighbor
|
| 389 |
+
to the edge-data-dict. So `G.adj[3][2]['color'] = 'blue'` sets
|
| 390 |
+
the color of the edge `(3, 2)` to `"blue"`.
|
| 391 |
+
|
| 392 |
+
Iterating over G.adj behaves like a dict. Useful idioms include
|
| 393 |
+
`for nbr, datadict in G.adj[n].items():`.
|
| 394 |
+
|
| 395 |
+
The neighbor information is also provided by subscripting the graph.
|
| 396 |
+
So `for nbr, foovalue in G[node].data('foo', default=1):` works.
|
| 397 |
+
|
| 398 |
+
For directed graphs, `G.adj` holds outgoing (successor) info.
|
| 399 |
+
"""
|
| 400 |
+
return AdjacencyView(self._succ)
|
| 401 |
+
|
| 402 |
+
@cached_property
|
| 403 |
+
def succ(self):
|
| 404 |
+
"""Graph adjacency object holding the successors of each node.
|
| 405 |
+
|
| 406 |
+
This object is a read-only dict-like structure with node keys
|
| 407 |
+
and neighbor-dict values. The neighbor-dict is keyed by neighbor
|
| 408 |
+
to the edge-data-dict. So `G.succ[3][2]['color'] = 'blue'` sets
|
| 409 |
+
the color of the edge `(3, 2)` to `"blue"`.
|
| 410 |
+
|
| 411 |
+
Iterating over G.succ behaves like a dict. Useful idioms include
|
| 412 |
+
`for nbr, datadict in G.succ[n].items():`. A data-view not provided
|
| 413 |
+
by dicts also exists: `for nbr, foovalue in G.succ[node].data('foo'):`
|
| 414 |
+
and a default can be set via a `default` argument to the `data` method.
|
| 415 |
+
|
| 416 |
+
The neighbor information is also provided by subscripting the graph.
|
| 417 |
+
So `for nbr, foovalue in G[node].data('foo', default=1):` works.
|
| 418 |
+
|
| 419 |
+
For directed graphs, `G.adj` is identical to `G.succ`.
|
| 420 |
+
"""
|
| 421 |
+
return AdjacencyView(self._succ)
|
| 422 |
+
|
| 423 |
+
@cached_property
|
| 424 |
+
def pred(self):
|
| 425 |
+
"""Graph adjacency object holding the predecessors of each node.
|
| 426 |
+
|
| 427 |
+
This object is a read-only dict-like structure with node keys
|
| 428 |
+
and neighbor-dict values. The neighbor-dict is keyed by neighbor
|
| 429 |
+
to the edge-data-dict. So `G.pred[2][3]['color'] = 'blue'` sets
|
| 430 |
+
the color of the edge `(3, 2)` to `"blue"`.
|
| 431 |
+
|
| 432 |
+
Iterating over G.pred behaves like a dict. Useful idioms include
|
| 433 |
+
`for nbr, datadict in G.pred[n].items():`. A data-view not provided
|
| 434 |
+
by dicts also exists: `for nbr, foovalue in G.pred[node].data('foo'):`
|
| 435 |
+
A default can be set via a `default` argument to the `data` method.
|
| 436 |
+
"""
|
| 437 |
+
return AdjacencyView(self._pred)
|
| 438 |
+
|
| 439 |
+
def add_node(self, node_for_adding, **attr):
|
| 440 |
+
"""Add a single node `node_for_adding` and update node attributes.
|
| 441 |
+
|
| 442 |
+
Parameters
|
| 443 |
+
----------
|
| 444 |
+
node_for_adding : node
|
| 445 |
+
A node can be any hashable Python object except None.
|
| 446 |
+
attr : keyword arguments, optional
|
| 447 |
+
Set or change node attributes using key=value.
|
| 448 |
+
|
| 449 |
+
See Also
|
| 450 |
+
--------
|
| 451 |
+
add_nodes_from
|
| 452 |
+
|
| 453 |
+
Examples
|
| 454 |
+
--------
|
| 455 |
+
>>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 456 |
+
>>> G.add_node(1)
|
| 457 |
+
>>> G.add_node("Hello")
|
| 458 |
+
>>> K3 = nx.Graph([(0, 1), (1, 2), (2, 0)])
|
| 459 |
+
>>> G.add_node(K3)
|
| 460 |
+
>>> G.number_of_nodes()
|
| 461 |
+
3
|
| 462 |
+
|
| 463 |
+
Use keywords set/change node attributes:
|
| 464 |
+
|
| 465 |
+
>>> G.add_node(1, size=10)
|
| 466 |
+
>>> G.add_node(3, weight=0.4, UTM=("13S", 382871, 3972649))
|
| 467 |
+
|
| 468 |
+
Notes
|
| 469 |
+
-----
|
| 470 |
+
A hashable object is one that can be used as a key in a Python
|
| 471 |
+
dictionary. This includes strings, numbers, tuples of strings
|
| 472 |
+
and numbers, etc.
|
| 473 |
+
|
| 474 |
+
On many platforms hashable items also include mutables such as
|
| 475 |
+
NetworkX Graphs, though one should be careful that the hash
|
| 476 |
+
doesn't change on mutables.
|
| 477 |
+
"""
|
| 478 |
+
if node_for_adding not in self._succ:
|
| 479 |
+
if node_for_adding is None:
|
| 480 |
+
raise ValueError("None cannot be a node")
|
| 481 |
+
self._succ[node_for_adding] = self.adjlist_inner_dict_factory()
|
| 482 |
+
self._pred[node_for_adding] = self.adjlist_inner_dict_factory()
|
| 483 |
+
attr_dict = self._node[node_for_adding] = self.node_attr_dict_factory()
|
| 484 |
+
attr_dict.update(attr)
|
| 485 |
+
else: # update attr even if node already exists
|
| 486 |
+
self._node[node_for_adding].update(attr)
|
| 487 |
+
nx._clear_cache(self)
|
| 488 |
+
|
| 489 |
+
def add_nodes_from(self, nodes_for_adding, **attr):
|
| 490 |
+
"""Add multiple nodes.
|
| 491 |
+
|
| 492 |
+
Parameters
|
| 493 |
+
----------
|
| 494 |
+
nodes_for_adding : iterable container
|
| 495 |
+
A container of nodes (list, dict, set, etc.).
|
| 496 |
+
OR
|
| 497 |
+
A container of (node, attribute dict) tuples.
|
| 498 |
+
Node attributes are updated using the attribute dict.
|
| 499 |
+
attr : keyword arguments, optional (default= no attributes)
|
| 500 |
+
Update attributes for all nodes in nodes.
|
| 501 |
+
Node attributes specified in nodes as a tuple take
|
| 502 |
+
precedence over attributes specified via keyword arguments.
|
| 503 |
+
|
| 504 |
+
See Also
|
| 505 |
+
--------
|
| 506 |
+
add_node
|
| 507 |
+
|
| 508 |
+
Notes
|
| 509 |
+
-----
|
| 510 |
+
When adding nodes from an iterator over the graph you are changing,
|
| 511 |
+
a `RuntimeError` can be raised with message:
|
| 512 |
+
`RuntimeError: dictionary changed size during iteration`. This
|
| 513 |
+
happens when the graph's underlying dictionary is modified during
|
| 514 |
+
iteration. To avoid this error, evaluate the iterator into a separate
|
| 515 |
+
object, e.g. by using `list(iterator_of_nodes)`, and pass this
|
| 516 |
+
object to `G.add_nodes_from`.
|
| 517 |
+
|
| 518 |
+
Examples
|
| 519 |
+
--------
|
| 520 |
+
>>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 521 |
+
>>> G.add_nodes_from("Hello")
|
| 522 |
+
>>> K3 = nx.Graph([(0, 1), (1, 2), (2, 0)])
|
| 523 |
+
>>> G.add_nodes_from(K3)
|
| 524 |
+
>>> sorted(G.nodes(), key=str)
|
| 525 |
+
[0, 1, 2, 'H', 'e', 'l', 'o']
|
| 526 |
+
|
| 527 |
+
Use keywords to update specific node attributes for every node.
|
| 528 |
+
|
| 529 |
+
>>> G.add_nodes_from([1, 2], size=10)
|
| 530 |
+
>>> G.add_nodes_from([3, 4], weight=0.4)
|
| 531 |
+
|
| 532 |
+
Use (node, attrdict) tuples to update attributes for specific nodes.
|
| 533 |
+
|
| 534 |
+
>>> G.add_nodes_from([(1, dict(size=11)), (2, {"color": "blue"})])
|
| 535 |
+
>>> G.nodes[1]["size"]
|
| 536 |
+
11
|
| 537 |
+
>>> H = nx.Graph()
|
| 538 |
+
>>> H.add_nodes_from(G.nodes(data=True))
|
| 539 |
+
>>> H.nodes[1]["size"]
|
| 540 |
+
11
|
| 541 |
+
|
| 542 |
+
Evaluate an iterator over a graph if using it to modify the same graph
|
| 543 |
+
|
| 544 |
+
>>> G = nx.DiGraph([(0, 1), (1, 2), (3, 4)])
|
| 545 |
+
>>> # wrong way - will raise RuntimeError
|
| 546 |
+
>>> # G.add_nodes_from(n + 1 for n in G.nodes)
|
| 547 |
+
>>> # correct way
|
| 548 |
+
>>> G.add_nodes_from(list(n + 1 for n in G.nodes))
|
| 549 |
+
"""
|
| 550 |
+
for n in nodes_for_adding:
|
| 551 |
+
try:
|
| 552 |
+
newnode = n not in self._node
|
| 553 |
+
newdict = attr
|
| 554 |
+
except TypeError:
|
| 555 |
+
n, ndict = n
|
| 556 |
+
newnode = n not in self._node
|
| 557 |
+
newdict = attr.copy()
|
| 558 |
+
newdict.update(ndict)
|
| 559 |
+
if newnode:
|
| 560 |
+
if n is None:
|
| 561 |
+
raise ValueError("None cannot be a node")
|
| 562 |
+
self._succ[n] = self.adjlist_inner_dict_factory()
|
| 563 |
+
self._pred[n] = self.adjlist_inner_dict_factory()
|
| 564 |
+
self._node[n] = self.node_attr_dict_factory()
|
| 565 |
+
self._node[n].update(newdict)
|
| 566 |
+
nx._clear_cache(self)
|
| 567 |
+
|
| 568 |
+
def remove_node(self, n):
|
| 569 |
+
"""Remove node n.
|
| 570 |
+
|
| 571 |
+
Removes the node n and all adjacent edges.
|
| 572 |
+
Attempting to remove a nonexistent node will raise an exception.
|
| 573 |
+
|
| 574 |
+
Parameters
|
| 575 |
+
----------
|
| 576 |
+
n : node
|
| 577 |
+
A node in the graph
|
| 578 |
+
|
| 579 |
+
Raises
|
| 580 |
+
------
|
| 581 |
+
NetworkXError
|
| 582 |
+
If n is not in the graph.
|
| 583 |
+
|
| 584 |
+
See Also
|
| 585 |
+
--------
|
| 586 |
+
remove_nodes_from
|
| 587 |
+
|
| 588 |
+
Examples
|
| 589 |
+
--------
|
| 590 |
+
>>> G = nx.path_graph(3) # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 591 |
+
>>> list(G.edges)
|
| 592 |
+
[(0, 1), (1, 2)]
|
| 593 |
+
>>> G.remove_node(1)
|
| 594 |
+
>>> list(G.edges)
|
| 595 |
+
[]
|
| 596 |
+
|
| 597 |
+
"""
|
| 598 |
+
try:
|
| 599 |
+
nbrs = self._succ[n]
|
| 600 |
+
del self._node[n]
|
| 601 |
+
except KeyError as err: # NetworkXError if n not in self
|
| 602 |
+
raise NetworkXError(f"The node {n} is not in the digraph.") from err
|
| 603 |
+
for u in nbrs:
|
| 604 |
+
del self._pred[u][n] # remove all edges n-u in digraph
|
| 605 |
+
del self._succ[n] # remove node from succ
|
| 606 |
+
for u in self._pred[n]:
|
| 607 |
+
del self._succ[u][n] # remove all edges n-u in digraph
|
| 608 |
+
del self._pred[n] # remove node from pred
|
| 609 |
+
nx._clear_cache(self)
|
| 610 |
+
|
| 611 |
+
def remove_nodes_from(self, nodes):
|
| 612 |
+
"""Remove multiple nodes.
|
| 613 |
+
|
| 614 |
+
Parameters
|
| 615 |
+
----------
|
| 616 |
+
nodes : iterable container
|
| 617 |
+
A container of nodes (list, dict, set, etc.). If a node
|
| 618 |
+
in the container is not in the graph it is silently ignored.
|
| 619 |
+
|
| 620 |
+
See Also
|
| 621 |
+
--------
|
| 622 |
+
remove_node
|
| 623 |
+
|
| 624 |
+
Notes
|
| 625 |
+
-----
|
| 626 |
+
When removing nodes from an iterator over the graph you are changing,
|
| 627 |
+
a `RuntimeError` will be raised with message:
|
| 628 |
+
`RuntimeError: dictionary changed size during iteration`. This
|
| 629 |
+
happens when the graph's underlying dictionary is modified during
|
| 630 |
+
iteration. To avoid this error, evaluate the iterator into a separate
|
| 631 |
+
object, e.g. by using `list(iterator_of_nodes)`, and pass this
|
| 632 |
+
object to `G.remove_nodes_from`.
|
| 633 |
+
|
| 634 |
+
Examples
|
| 635 |
+
--------
|
| 636 |
+
>>> G = nx.path_graph(3) # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 637 |
+
>>> e = list(G.nodes)
|
| 638 |
+
>>> e
|
| 639 |
+
[0, 1, 2]
|
| 640 |
+
>>> G.remove_nodes_from(e)
|
| 641 |
+
>>> list(G.nodes)
|
| 642 |
+
[]
|
| 643 |
+
|
| 644 |
+
Evaluate an iterator over a graph if using it to modify the same graph
|
| 645 |
+
|
| 646 |
+
>>> G = nx.DiGraph([(0, 1), (1, 2), (3, 4)])
|
| 647 |
+
>>> # this command will fail, as the graph's dict is modified during iteration
|
| 648 |
+
>>> # G.remove_nodes_from(n for n in G.nodes if n < 2)
|
| 649 |
+
>>> # this command will work, since the dictionary underlying graph is not modified
|
| 650 |
+
>>> G.remove_nodes_from(list(n for n in G.nodes if n < 2))
|
| 651 |
+
"""
|
| 652 |
+
for n in nodes:
|
| 653 |
+
try:
|
| 654 |
+
succs = self._succ[n]
|
| 655 |
+
del self._node[n]
|
| 656 |
+
for u in succs:
|
| 657 |
+
del self._pred[u][n] # remove all edges n-u in digraph
|
| 658 |
+
del self._succ[n] # now remove node
|
| 659 |
+
for u in self._pred[n]:
|
| 660 |
+
del self._succ[u][n] # remove all edges n-u in digraph
|
| 661 |
+
del self._pred[n] # now remove node
|
| 662 |
+
except KeyError:
|
| 663 |
+
pass # silent failure on remove
|
| 664 |
+
nx._clear_cache(self)
|
| 665 |
+
|
| 666 |
+
def add_edge(self, u_of_edge, v_of_edge, **attr):
|
| 667 |
+
"""Add an edge between u and v.
|
| 668 |
+
|
| 669 |
+
The nodes u and v will be automatically added if they are
|
| 670 |
+
not already in the graph.
|
| 671 |
+
|
| 672 |
+
Edge attributes can be specified with keywords or by directly
|
| 673 |
+
accessing the edge's attribute dictionary. See examples below.
|
| 674 |
+
|
| 675 |
+
Parameters
|
| 676 |
+
----------
|
| 677 |
+
u_of_edge, v_of_edge : nodes
|
| 678 |
+
Nodes can be, for example, strings or numbers.
|
| 679 |
+
Nodes must be hashable (and not None) Python objects.
|
| 680 |
+
attr : keyword arguments, optional
|
| 681 |
+
Edge data (or labels or objects) can be assigned using
|
| 682 |
+
keyword arguments.
|
| 683 |
+
|
| 684 |
+
See Also
|
| 685 |
+
--------
|
| 686 |
+
add_edges_from : add a collection of edges
|
| 687 |
+
|
| 688 |
+
Notes
|
| 689 |
+
-----
|
| 690 |
+
Adding an edge that already exists updates the edge data.
|
| 691 |
+
|
| 692 |
+
Many NetworkX algorithms designed for weighted graphs use
|
| 693 |
+
an edge attribute (by default `weight`) to hold a numerical value.
|
| 694 |
+
|
| 695 |
+
Examples
|
| 696 |
+
--------
|
| 697 |
+
The following all add the edge e=(1, 2) to graph G:
|
| 698 |
+
|
| 699 |
+
>>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 700 |
+
>>> e = (1, 2)
|
| 701 |
+
>>> G.add_edge(1, 2) # explicit two-node form
|
| 702 |
+
>>> G.add_edge(*e) # single edge as tuple of two nodes
|
| 703 |
+
>>> G.add_edges_from([(1, 2)]) # add edges from iterable container
|
| 704 |
+
|
| 705 |
+
Associate data to edges using keywords:
|
| 706 |
+
|
| 707 |
+
>>> G.add_edge(1, 2, weight=3)
|
| 708 |
+
>>> G.add_edge(1, 3, weight=7, capacity=15, length=342.7)
|
| 709 |
+
|
| 710 |
+
For non-string attribute keys, use subscript notation.
|
| 711 |
+
|
| 712 |
+
>>> G.add_edge(1, 2)
|
| 713 |
+
>>> G[1][2].update({0: 5})
|
| 714 |
+
>>> G.edges[1, 2].update({0: 5})
|
| 715 |
+
"""
|
| 716 |
+
u, v = u_of_edge, v_of_edge
|
| 717 |
+
# add nodes
|
| 718 |
+
if u not in self._succ:
|
| 719 |
+
if u is None:
|
| 720 |
+
raise ValueError("None cannot be a node")
|
| 721 |
+
self._succ[u] = self.adjlist_inner_dict_factory()
|
| 722 |
+
self._pred[u] = self.adjlist_inner_dict_factory()
|
| 723 |
+
self._node[u] = self.node_attr_dict_factory()
|
| 724 |
+
if v not in self._succ:
|
| 725 |
+
if v is None:
|
| 726 |
+
raise ValueError("None cannot be a node")
|
| 727 |
+
self._succ[v] = self.adjlist_inner_dict_factory()
|
| 728 |
+
self._pred[v] = self.adjlist_inner_dict_factory()
|
| 729 |
+
self._node[v] = self.node_attr_dict_factory()
|
| 730 |
+
# add the edge
|
| 731 |
+
datadict = self._adj[u].get(v, self.edge_attr_dict_factory())
|
| 732 |
+
datadict.update(attr)
|
| 733 |
+
self._succ[u][v] = datadict
|
| 734 |
+
self._pred[v][u] = datadict
|
| 735 |
+
nx._clear_cache(self)
|
| 736 |
+
|
| 737 |
+
def add_edges_from(self, ebunch_to_add, **attr):
|
| 738 |
+
"""Add all the edges in ebunch_to_add.
|
| 739 |
+
|
| 740 |
+
Parameters
|
| 741 |
+
----------
|
| 742 |
+
ebunch_to_add : container of edges
|
| 743 |
+
Each edge given in the container will be added to the
|
| 744 |
+
graph. The edges must be given as 2-tuples (u, v) or
|
| 745 |
+
3-tuples (u, v, d) where d is a dictionary containing edge data.
|
| 746 |
+
attr : keyword arguments, optional
|
| 747 |
+
Edge data (or labels or objects) can be assigned using
|
| 748 |
+
keyword arguments.
|
| 749 |
+
|
| 750 |
+
See Also
|
| 751 |
+
--------
|
| 752 |
+
add_edge : add a single edge
|
| 753 |
+
add_weighted_edges_from : convenient way to add weighted edges
|
| 754 |
+
|
| 755 |
+
Notes
|
| 756 |
+
-----
|
| 757 |
+
Adding the same edge twice has no effect but any edge data
|
| 758 |
+
will be updated when each duplicate edge is added.
|
| 759 |
+
|
| 760 |
+
Edge attributes specified in an ebunch take precedence over
|
| 761 |
+
attributes specified via keyword arguments.
|
| 762 |
+
|
| 763 |
+
When adding edges from an iterator over the graph you are changing,
|
| 764 |
+
a `RuntimeError` can be raised with message:
|
| 765 |
+
`RuntimeError: dictionary changed size during iteration`. This
|
| 766 |
+
happens when the graph's underlying dictionary is modified during
|
| 767 |
+
iteration. To avoid this error, evaluate the iterator into a separate
|
| 768 |
+
object, e.g. by using `list(iterator_of_edges)`, and pass this
|
| 769 |
+
object to `G.add_edges_from`.
|
| 770 |
+
|
| 771 |
+
Examples
|
| 772 |
+
--------
|
| 773 |
+
>>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 774 |
+
>>> G.add_edges_from([(0, 1), (1, 2)]) # using a list of edge tuples
|
| 775 |
+
>>> e = zip(range(0, 3), range(1, 4))
|
| 776 |
+
>>> G.add_edges_from(e) # Add the path graph 0-1-2-3
|
| 777 |
+
|
| 778 |
+
Associate data to edges
|
| 779 |
+
|
| 780 |
+
>>> G.add_edges_from([(1, 2), (2, 3)], weight=3)
|
| 781 |
+
>>> G.add_edges_from([(3, 4), (1, 4)], label="WN2898")
|
| 782 |
+
|
| 783 |
+
Evaluate an iterator over a graph if using it to modify the same graph
|
| 784 |
+
|
| 785 |
+
>>> G = nx.DiGraph([(1, 2), (2, 3), (3, 4)])
|
| 786 |
+
>>> # Grow graph by one new node, adding edges to all existing nodes.
|
| 787 |
+
>>> # wrong way - will raise RuntimeError
|
| 788 |
+
>>> # G.add_edges_from(((5, n) for n in G.nodes))
|
| 789 |
+
>>> # right way - note that there will be no self-edge for node 5
|
| 790 |
+
>>> G.add_edges_from(list((5, n) for n in G.nodes))
|
| 791 |
+
"""
|
| 792 |
+
for e in ebunch_to_add:
|
| 793 |
+
ne = len(e)
|
| 794 |
+
if ne == 3:
|
| 795 |
+
u, v, dd = e
|
| 796 |
+
elif ne == 2:
|
| 797 |
+
u, v = e
|
| 798 |
+
dd = {}
|
| 799 |
+
else:
|
| 800 |
+
raise NetworkXError(f"Edge tuple {e} must be a 2-tuple or 3-tuple.")
|
| 801 |
+
if u not in self._succ:
|
| 802 |
+
if u is None:
|
| 803 |
+
raise ValueError("None cannot be a node")
|
| 804 |
+
self._succ[u] = self.adjlist_inner_dict_factory()
|
| 805 |
+
self._pred[u] = self.adjlist_inner_dict_factory()
|
| 806 |
+
self._node[u] = self.node_attr_dict_factory()
|
| 807 |
+
if v not in self._succ:
|
| 808 |
+
if v is None:
|
| 809 |
+
raise ValueError("None cannot be a node")
|
| 810 |
+
self._succ[v] = self.adjlist_inner_dict_factory()
|
| 811 |
+
self._pred[v] = self.adjlist_inner_dict_factory()
|
| 812 |
+
self._node[v] = self.node_attr_dict_factory()
|
| 813 |
+
datadict = self._adj[u].get(v, self.edge_attr_dict_factory())
|
| 814 |
+
datadict.update(attr)
|
| 815 |
+
datadict.update(dd)
|
| 816 |
+
self._succ[u][v] = datadict
|
| 817 |
+
self._pred[v][u] = datadict
|
| 818 |
+
nx._clear_cache(self)
|
| 819 |
+
|
| 820 |
+
def remove_edge(self, u, v):
|
| 821 |
+
"""Remove the edge between u and v.
|
| 822 |
+
|
| 823 |
+
Parameters
|
| 824 |
+
----------
|
| 825 |
+
u, v : nodes
|
| 826 |
+
Remove the edge between nodes u and v.
|
| 827 |
+
|
| 828 |
+
Raises
|
| 829 |
+
------
|
| 830 |
+
NetworkXError
|
| 831 |
+
If there is not an edge between u and v.
|
| 832 |
+
|
| 833 |
+
See Also
|
| 834 |
+
--------
|
| 835 |
+
remove_edges_from : remove a collection of edges
|
| 836 |
+
|
| 837 |
+
Examples
|
| 838 |
+
--------
|
| 839 |
+
>>> G = nx.Graph() # or DiGraph, etc
|
| 840 |
+
>>> nx.add_path(G, [0, 1, 2, 3])
|
| 841 |
+
>>> G.remove_edge(0, 1)
|
| 842 |
+
>>> e = (1, 2)
|
| 843 |
+
>>> G.remove_edge(*e) # unpacks e from an edge tuple
|
| 844 |
+
>>> e = (2, 3, {"weight": 7}) # an edge with attribute data
|
| 845 |
+
>>> G.remove_edge(*e[:2]) # select first part of edge tuple
|
| 846 |
+
"""
|
| 847 |
+
try:
|
| 848 |
+
del self._succ[u][v]
|
| 849 |
+
del self._pred[v][u]
|
| 850 |
+
except KeyError as err:
|
| 851 |
+
raise NetworkXError(f"The edge {u}-{v} not in graph.") from err
|
| 852 |
+
nx._clear_cache(self)
|
| 853 |
+
|
| 854 |
+
def remove_edges_from(self, ebunch):
|
| 855 |
+
"""Remove all edges specified in ebunch.
|
| 856 |
+
|
| 857 |
+
Parameters
|
| 858 |
+
----------
|
| 859 |
+
ebunch: list or container of edge tuples
|
| 860 |
+
Each edge given in the list or container will be removed
|
| 861 |
+
from the graph. The edges can be:
|
| 862 |
+
|
| 863 |
+
- 2-tuples (u, v) edge between u and v.
|
| 864 |
+
- 3-tuples (u, v, k) where k is ignored.
|
| 865 |
+
|
| 866 |
+
See Also
|
| 867 |
+
--------
|
| 868 |
+
remove_edge : remove a single edge
|
| 869 |
+
|
| 870 |
+
Notes
|
| 871 |
+
-----
|
| 872 |
+
Will fail silently if an edge in ebunch is not in the graph.
|
| 873 |
+
|
| 874 |
+
Examples
|
| 875 |
+
--------
|
| 876 |
+
>>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 877 |
+
>>> ebunch = [(1, 2), (2, 3)]
|
| 878 |
+
>>> G.remove_edges_from(ebunch)
|
| 879 |
+
"""
|
| 880 |
+
for e in ebunch:
|
| 881 |
+
u, v = e[:2] # ignore edge data
|
| 882 |
+
if u in self._succ and v in self._succ[u]:
|
| 883 |
+
del self._succ[u][v]
|
| 884 |
+
del self._pred[v][u]
|
| 885 |
+
nx._clear_cache(self)
|
| 886 |
+
|
| 887 |
+
def has_successor(self, u, v):
|
| 888 |
+
"""Returns True if node u has successor v.
|
| 889 |
+
|
| 890 |
+
This is true if graph has the edge u->v.
|
| 891 |
+
"""
|
| 892 |
+
return u in self._succ and v in self._succ[u]
|
| 893 |
+
|
| 894 |
+
def has_predecessor(self, u, v):
|
| 895 |
+
"""Returns True if node u has predecessor v.
|
| 896 |
+
|
| 897 |
+
This is true if graph has the edge u<-v.
|
| 898 |
+
"""
|
| 899 |
+
return u in self._pred and v in self._pred[u]
|
| 900 |
+
|
| 901 |
+
def successors(self, n):
|
| 902 |
+
"""Returns an iterator over successor nodes of n.
|
| 903 |
+
|
| 904 |
+
A successor of n is a node m such that there exists a directed
|
| 905 |
+
edge from n to m.
|
| 906 |
+
|
| 907 |
+
Parameters
|
| 908 |
+
----------
|
| 909 |
+
n : node
|
| 910 |
+
A node in the graph
|
| 911 |
+
|
| 912 |
+
Raises
|
| 913 |
+
------
|
| 914 |
+
NetworkXError
|
| 915 |
+
If n is not in the graph.
|
| 916 |
+
|
| 917 |
+
See Also
|
| 918 |
+
--------
|
| 919 |
+
predecessors
|
| 920 |
+
|
| 921 |
+
Notes
|
| 922 |
+
-----
|
| 923 |
+
neighbors() and successors() are the same.
|
| 924 |
+
"""
|
| 925 |
+
try:
|
| 926 |
+
return iter(self._succ[n])
|
| 927 |
+
except KeyError as err:
|
| 928 |
+
raise NetworkXError(f"The node {n} is not in the digraph.") from err
|
| 929 |
+
|
| 930 |
+
# digraph definitions
|
| 931 |
+
neighbors = successors
|
| 932 |
+
|
| 933 |
+
def predecessors(self, n):
|
| 934 |
+
"""Returns an iterator over predecessor nodes of n.
|
| 935 |
+
|
| 936 |
+
A predecessor of n is a node m such that there exists a directed
|
| 937 |
+
edge from m to n.
|
| 938 |
+
|
| 939 |
+
Parameters
|
| 940 |
+
----------
|
| 941 |
+
n : node
|
| 942 |
+
A node in the graph
|
| 943 |
+
|
| 944 |
+
Raises
|
| 945 |
+
------
|
| 946 |
+
NetworkXError
|
| 947 |
+
If n is not in the graph.
|
| 948 |
+
|
| 949 |
+
See Also
|
| 950 |
+
--------
|
| 951 |
+
successors
|
| 952 |
+
"""
|
| 953 |
+
try:
|
| 954 |
+
return iter(self._pred[n])
|
| 955 |
+
except KeyError as err:
|
| 956 |
+
raise NetworkXError(f"The node {n} is not in the digraph.") from err
|
| 957 |
+
|
| 958 |
+
@cached_property
|
| 959 |
+
def edges(self):
|
| 960 |
+
"""An OutEdgeView of the DiGraph as G.edges or G.edges().
|
| 961 |
+
|
| 962 |
+
edges(self, nbunch=None, data=False, default=None)
|
| 963 |
+
|
| 964 |
+
The OutEdgeView provides set-like operations on the edge-tuples
|
| 965 |
+
as well as edge attribute lookup. When called, it also provides
|
| 966 |
+
an EdgeDataView object which allows control of access to edge
|
| 967 |
+
attributes (but does not provide set-like operations).
|
| 968 |
+
Hence, `G.edges[u, v]['color']` provides the value of the color
|
| 969 |
+
attribute for edge `(u, v)` while
|
| 970 |
+
`for (u, v, c) in G.edges.data('color', default='red'):`
|
| 971 |
+
iterates through all the edges yielding the color attribute
|
| 972 |
+
with default `'red'` if no color attribute exists.
|
| 973 |
+
|
| 974 |
+
Parameters
|
| 975 |
+
----------
|
| 976 |
+
nbunch : single node, container, or all nodes (default= all nodes)
|
| 977 |
+
The view will only report edges from these nodes.
|
| 978 |
+
data : string or bool, optional (default=False)
|
| 979 |
+
The edge attribute returned in 3-tuple (u, v, ddict[data]).
|
| 980 |
+
If True, return edge attribute dict in 3-tuple (u, v, ddict).
|
| 981 |
+
If False, return 2-tuple (u, v).
|
| 982 |
+
default : value, optional (default=None)
|
| 983 |
+
Value used for edges that don't have the requested attribute.
|
| 984 |
+
Only relevant if data is not True or False.
|
| 985 |
+
|
| 986 |
+
Returns
|
| 987 |
+
-------
|
| 988 |
+
edges : OutEdgeView
|
| 989 |
+
A view of edge attributes, usually it iterates over (u, v)
|
| 990 |
+
or (u, v, d) tuples of edges, but can also be used for
|
| 991 |
+
attribute lookup as `edges[u, v]['foo']`.
|
| 992 |
+
|
| 993 |
+
See Also
|
| 994 |
+
--------
|
| 995 |
+
in_edges, out_edges
|
| 996 |
+
|
| 997 |
+
Notes
|
| 998 |
+
-----
|
| 999 |
+
Nodes in nbunch that are not in the graph will be (quietly) ignored.
|
| 1000 |
+
For directed graphs this returns the out-edges.
|
| 1001 |
+
|
| 1002 |
+
Examples
|
| 1003 |
+
--------
|
| 1004 |
+
>>> G = nx.DiGraph() # or MultiDiGraph, etc
|
| 1005 |
+
>>> nx.add_path(G, [0, 1, 2])
|
| 1006 |
+
>>> G.add_edge(2, 3, weight=5)
|
| 1007 |
+
>>> [e for e in G.edges]
|
| 1008 |
+
[(0, 1), (1, 2), (2, 3)]
|
| 1009 |
+
>>> G.edges.data() # default data is {} (empty dict)
|
| 1010 |
+
OutEdgeDataView([(0, 1, {}), (1, 2, {}), (2, 3, {'weight': 5})])
|
| 1011 |
+
>>> G.edges.data("weight", default=1)
|
| 1012 |
+
OutEdgeDataView([(0, 1, 1), (1, 2, 1), (2, 3, 5)])
|
| 1013 |
+
>>> G.edges([0, 2]) # only edges originating from these nodes
|
| 1014 |
+
OutEdgeDataView([(0, 1), (2, 3)])
|
| 1015 |
+
>>> G.edges(0) # only edges from node 0
|
| 1016 |
+
OutEdgeDataView([(0, 1)])
|
| 1017 |
+
|
| 1018 |
+
"""
|
| 1019 |
+
return OutEdgeView(self)
|
| 1020 |
+
|
| 1021 |
+
# alias out_edges to edges
|
| 1022 |
+
@cached_property
|
| 1023 |
+
def out_edges(self):
|
| 1024 |
+
return OutEdgeView(self)
|
| 1025 |
+
|
| 1026 |
+
out_edges.__doc__ = edges.__doc__
|
| 1027 |
+
|
| 1028 |
+
@cached_property
|
| 1029 |
+
def in_edges(self):
|
| 1030 |
+
"""A view of the in edges of the graph as G.in_edges or G.in_edges().
|
| 1031 |
+
|
| 1032 |
+
in_edges(self, nbunch=None, data=False, default=None):
|
| 1033 |
+
|
| 1034 |
+
Parameters
|
| 1035 |
+
----------
|
| 1036 |
+
nbunch : single node, container, or all nodes (default= all nodes)
|
| 1037 |
+
The view will only report edges incident to these nodes.
|
| 1038 |
+
data : string or bool, optional (default=False)
|
| 1039 |
+
The edge attribute returned in 3-tuple (u, v, ddict[data]).
|
| 1040 |
+
If True, return edge attribute dict in 3-tuple (u, v, ddict).
|
| 1041 |
+
If False, return 2-tuple (u, v).
|
| 1042 |
+
default : value, optional (default=None)
|
| 1043 |
+
Value used for edges that don't have the requested attribute.
|
| 1044 |
+
Only relevant if data is not True or False.
|
| 1045 |
+
|
| 1046 |
+
Returns
|
| 1047 |
+
-------
|
| 1048 |
+
in_edges : InEdgeView or InEdgeDataView
|
| 1049 |
+
A view of edge attributes, usually it iterates over (u, v)
|
| 1050 |
+
or (u, v, d) tuples of edges, but can also be used for
|
| 1051 |
+
attribute lookup as `edges[u, v]['foo']`.
|
| 1052 |
+
|
| 1053 |
+
Examples
|
| 1054 |
+
--------
|
| 1055 |
+
>>> G = nx.DiGraph()
|
| 1056 |
+
>>> G.add_edge(1, 2, color="blue")
|
| 1057 |
+
>>> G.in_edges()
|
| 1058 |
+
InEdgeView([(1, 2)])
|
| 1059 |
+
>>> G.in_edges(nbunch=2)
|
| 1060 |
+
InEdgeDataView([(1, 2)])
|
| 1061 |
+
|
| 1062 |
+
See Also
|
| 1063 |
+
--------
|
| 1064 |
+
edges
|
| 1065 |
+
"""
|
| 1066 |
+
return InEdgeView(self)
|
| 1067 |
+
|
| 1068 |
+
@cached_property
|
| 1069 |
+
def degree(self):
|
| 1070 |
+
"""A DegreeView for the Graph as G.degree or G.degree().
|
| 1071 |
+
|
| 1072 |
+
The node degree is the number of edges adjacent to the node.
|
| 1073 |
+
The weighted node degree is the sum of the edge weights for
|
| 1074 |
+
edges incident to that node.
|
| 1075 |
+
|
| 1076 |
+
This object provides an iterator for (node, degree) as well as
|
| 1077 |
+
lookup for the degree for a single node.
|
| 1078 |
+
|
| 1079 |
+
Parameters
|
| 1080 |
+
----------
|
| 1081 |
+
nbunch : single node, container, or all nodes (default= all nodes)
|
| 1082 |
+
The view will only report edges incident to these nodes.
|
| 1083 |
+
|
| 1084 |
+
weight : string or None, optional (default=None)
|
| 1085 |
+
The name of an edge attribute that holds the numerical value used
|
| 1086 |
+
as a weight. If None, then each edge has weight 1.
|
| 1087 |
+
The degree is the sum of the edge weights adjacent to the node.
|
| 1088 |
+
|
| 1089 |
+
Returns
|
| 1090 |
+
-------
|
| 1091 |
+
DiDegreeView or int
|
| 1092 |
+
If multiple nodes are requested (the default), returns a `DiDegreeView`
|
| 1093 |
+
mapping nodes to their degree.
|
| 1094 |
+
If a single node is requested, returns the degree of the node as an integer.
|
| 1095 |
+
|
| 1096 |
+
See Also
|
| 1097 |
+
--------
|
| 1098 |
+
in_degree, out_degree
|
| 1099 |
+
|
| 1100 |
+
Examples
|
| 1101 |
+
--------
|
| 1102 |
+
>>> G = nx.DiGraph() # or MultiDiGraph
|
| 1103 |
+
>>> nx.add_path(G, [0, 1, 2, 3])
|
| 1104 |
+
>>> G.degree(0) # node 0 with degree 1
|
| 1105 |
+
1
|
| 1106 |
+
>>> list(G.degree([0, 1, 2]))
|
| 1107 |
+
[(0, 1), (1, 2), (2, 2)]
|
| 1108 |
+
|
| 1109 |
+
"""
|
| 1110 |
+
return DiDegreeView(self)
|
| 1111 |
+
|
| 1112 |
+
@cached_property
|
| 1113 |
+
def in_degree(self):
|
| 1114 |
+
"""An InDegreeView for (node, in_degree) or in_degree for single node.
|
| 1115 |
+
|
| 1116 |
+
The node in_degree is the number of edges pointing to the node.
|
| 1117 |
+
The weighted node degree is the sum of the edge weights for
|
| 1118 |
+
edges incident to that node.
|
| 1119 |
+
|
| 1120 |
+
This object provides an iteration over (node, in_degree) as well as
|
| 1121 |
+
lookup for the degree for a single node.
|
| 1122 |
+
|
| 1123 |
+
Parameters
|
| 1124 |
+
----------
|
| 1125 |
+
nbunch : single node, container, or all nodes (default= all nodes)
|
| 1126 |
+
The view will only report edges incident to these nodes.
|
| 1127 |
+
|
| 1128 |
+
weight : string or None, optional (default=None)
|
| 1129 |
+
The name of an edge attribute that holds the numerical value used
|
| 1130 |
+
as a weight. If None, then each edge has weight 1.
|
| 1131 |
+
The degree is the sum of the edge weights adjacent to the node.
|
| 1132 |
+
|
| 1133 |
+
Returns
|
| 1134 |
+
-------
|
| 1135 |
+
If a single node is requested
|
| 1136 |
+
deg : int
|
| 1137 |
+
In-degree of the node
|
| 1138 |
+
|
| 1139 |
+
OR if multiple nodes are requested
|
| 1140 |
+
nd_iter : iterator
|
| 1141 |
+
The iterator returns two-tuples of (node, in-degree).
|
| 1142 |
+
|
| 1143 |
+
See Also
|
| 1144 |
+
--------
|
| 1145 |
+
degree, out_degree
|
| 1146 |
+
|
| 1147 |
+
Examples
|
| 1148 |
+
--------
|
| 1149 |
+
>>> G = nx.DiGraph()
|
| 1150 |
+
>>> nx.add_path(G, [0, 1, 2, 3])
|
| 1151 |
+
>>> G.in_degree(0) # node 0 with degree 0
|
| 1152 |
+
0
|
| 1153 |
+
>>> list(G.in_degree([0, 1, 2]))
|
| 1154 |
+
[(0, 0), (1, 1), (2, 1)]
|
| 1155 |
+
|
| 1156 |
+
"""
|
| 1157 |
+
return InDegreeView(self)
|
| 1158 |
+
|
| 1159 |
+
@cached_property
|
| 1160 |
+
def out_degree(self):
|
| 1161 |
+
"""An OutDegreeView for (node, out_degree)
|
| 1162 |
+
|
| 1163 |
+
The node out_degree is the number of edges pointing out of the node.
|
| 1164 |
+
The weighted node degree is the sum of the edge weights for
|
| 1165 |
+
edges incident to that node.
|
| 1166 |
+
|
| 1167 |
+
This object provides an iterator over (node, out_degree) as well as
|
| 1168 |
+
lookup for the degree for a single node.
|
| 1169 |
+
|
| 1170 |
+
Parameters
|
| 1171 |
+
----------
|
| 1172 |
+
nbunch : single node, container, or all nodes (default= all nodes)
|
| 1173 |
+
The view will only report edges incident to these nodes.
|
| 1174 |
+
|
| 1175 |
+
weight : string or None, optional (default=None)
|
| 1176 |
+
The name of an edge attribute that holds the numerical value used
|
| 1177 |
+
as a weight. If None, then each edge has weight 1.
|
| 1178 |
+
The degree is the sum of the edge weights adjacent to the node.
|
| 1179 |
+
|
| 1180 |
+
Returns
|
| 1181 |
+
-------
|
| 1182 |
+
If a single node is requested
|
| 1183 |
+
deg : int
|
| 1184 |
+
Out-degree of the node
|
| 1185 |
+
|
| 1186 |
+
OR if multiple nodes are requested
|
| 1187 |
+
nd_iter : iterator
|
| 1188 |
+
The iterator returns two-tuples of (node, out-degree).
|
| 1189 |
+
|
| 1190 |
+
See Also
|
| 1191 |
+
--------
|
| 1192 |
+
degree, in_degree
|
| 1193 |
+
|
| 1194 |
+
Examples
|
| 1195 |
+
--------
|
| 1196 |
+
>>> G = nx.DiGraph()
|
| 1197 |
+
>>> nx.add_path(G, [0, 1, 2, 3])
|
| 1198 |
+
>>> G.out_degree(0) # node 0 with degree 1
|
| 1199 |
+
1
|
| 1200 |
+
>>> list(G.out_degree([0, 1, 2]))
|
| 1201 |
+
[(0, 1), (1, 1), (2, 1)]
|
| 1202 |
+
|
| 1203 |
+
"""
|
| 1204 |
+
return OutDegreeView(self)
|
| 1205 |
+
|
| 1206 |
+
def clear(self):
|
| 1207 |
+
"""Remove all nodes and edges from the graph.
|
| 1208 |
+
|
| 1209 |
+
This also removes the name, and all graph, node, and edge attributes.
|
| 1210 |
+
|
| 1211 |
+
Examples
|
| 1212 |
+
--------
|
| 1213 |
+
>>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 1214 |
+
>>> G.clear()
|
| 1215 |
+
>>> list(G.nodes)
|
| 1216 |
+
[]
|
| 1217 |
+
>>> list(G.edges)
|
| 1218 |
+
[]
|
| 1219 |
+
|
| 1220 |
+
"""
|
| 1221 |
+
self._succ.clear()
|
| 1222 |
+
self._pred.clear()
|
| 1223 |
+
self._node.clear()
|
| 1224 |
+
self.graph.clear()
|
| 1225 |
+
nx._clear_cache(self)
|
| 1226 |
+
|
| 1227 |
+
def clear_edges(self):
|
| 1228 |
+
"""Remove all edges from the graph without altering nodes.
|
| 1229 |
+
|
| 1230 |
+
Examples
|
| 1231 |
+
--------
|
| 1232 |
+
>>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 1233 |
+
>>> G.clear_edges()
|
| 1234 |
+
>>> list(G.nodes)
|
| 1235 |
+
[0, 1, 2, 3]
|
| 1236 |
+
>>> list(G.edges)
|
| 1237 |
+
[]
|
| 1238 |
+
|
| 1239 |
+
"""
|
| 1240 |
+
for predecessor_dict in self._pred.values():
|
| 1241 |
+
predecessor_dict.clear()
|
| 1242 |
+
for successor_dict in self._succ.values():
|
| 1243 |
+
successor_dict.clear()
|
| 1244 |
+
nx._clear_cache(self)
|
| 1245 |
+
|
| 1246 |
+
def is_multigraph(self):
|
| 1247 |
+
"""Returns True if graph is a multigraph, False otherwise."""
|
| 1248 |
+
return False
|
| 1249 |
+
|
| 1250 |
+
def is_directed(self):
|
| 1251 |
+
"""Returns True if graph is directed, False otherwise."""
|
| 1252 |
+
return True
|
| 1253 |
+
|
| 1254 |
+
def to_undirected(self, reciprocal=False, as_view=False):
|
| 1255 |
+
"""Returns an undirected representation of the digraph.
|
| 1256 |
+
|
| 1257 |
+
Parameters
|
| 1258 |
+
----------
|
| 1259 |
+
reciprocal : bool (optional)
|
| 1260 |
+
If True only keep edges that appear in both directions
|
| 1261 |
+
in the original digraph.
|
| 1262 |
+
as_view : bool (optional, default=False)
|
| 1263 |
+
If True return an undirected view of the original directed graph.
|
| 1264 |
+
|
| 1265 |
+
Returns
|
| 1266 |
+
-------
|
| 1267 |
+
G : Graph
|
| 1268 |
+
An undirected graph with the same name and nodes and
|
| 1269 |
+
with edge (u, v, data) if either (u, v, data) or (v, u, data)
|
| 1270 |
+
is in the digraph. If both edges exist in digraph and
|
| 1271 |
+
their edge data is different, only one edge is created
|
| 1272 |
+
with an arbitrary choice of which edge data to use.
|
| 1273 |
+
You must check and correct for this manually if desired.
|
| 1274 |
+
|
| 1275 |
+
See Also
|
| 1276 |
+
--------
|
| 1277 |
+
Graph, copy, add_edge, add_edges_from
|
| 1278 |
+
|
| 1279 |
+
Notes
|
| 1280 |
+
-----
|
| 1281 |
+
If edges in both directions (u, v) and (v, u) exist in the
|
| 1282 |
+
graph, attributes for the new undirected edge will be a combination of
|
| 1283 |
+
the attributes of the directed edges. The edge data is updated
|
| 1284 |
+
in the (arbitrary) order that the edges are encountered. For
|
| 1285 |
+
more customized control of the edge attributes use add_edge().
|
| 1286 |
+
|
| 1287 |
+
This returns a "deepcopy" of the edge, node, and
|
| 1288 |
+
graph attributes which attempts to completely copy
|
| 1289 |
+
all of the data and references.
|
| 1290 |
+
|
| 1291 |
+
This is in contrast to the similar G=DiGraph(D) which returns a
|
| 1292 |
+
shallow copy of the data.
|
| 1293 |
+
|
| 1294 |
+
See the Python copy module for more information on shallow
|
| 1295 |
+
and deep copies, https://docs.python.org/3/library/copy.html.
|
| 1296 |
+
|
| 1297 |
+
Warning: If you have subclassed DiGraph to use dict-like objects
|
| 1298 |
+
in the data structure, those changes do not transfer to the
|
| 1299 |
+
Graph created by this method.
|
| 1300 |
+
|
| 1301 |
+
Examples
|
| 1302 |
+
--------
|
| 1303 |
+
>>> G = nx.path_graph(2) # or MultiGraph, etc
|
| 1304 |
+
>>> H = G.to_directed()
|
| 1305 |
+
>>> list(H.edges)
|
| 1306 |
+
[(0, 1), (1, 0)]
|
| 1307 |
+
>>> G2 = H.to_undirected()
|
| 1308 |
+
>>> list(G2.edges)
|
| 1309 |
+
[(0, 1)]
|
| 1310 |
+
"""
|
| 1311 |
+
graph_class = self.to_undirected_class()
|
| 1312 |
+
if as_view is True:
|
| 1313 |
+
return nx.graphviews.generic_graph_view(self, graph_class)
|
| 1314 |
+
# deepcopy when not a view
|
| 1315 |
+
G = graph_class()
|
| 1316 |
+
G.graph.update(deepcopy(self.graph))
|
| 1317 |
+
G.add_nodes_from((n, deepcopy(d)) for n, d in self._node.items())
|
| 1318 |
+
if reciprocal is True:
|
| 1319 |
+
G.add_edges_from(
|
| 1320 |
+
(u, v, deepcopy(d))
|
| 1321 |
+
for u, nbrs in self._adj.items()
|
| 1322 |
+
for v, d in nbrs.items()
|
| 1323 |
+
if v in self._pred[u]
|
| 1324 |
+
)
|
| 1325 |
+
else:
|
| 1326 |
+
G.add_edges_from(
|
| 1327 |
+
(u, v, deepcopy(d))
|
| 1328 |
+
for u, nbrs in self._adj.items()
|
| 1329 |
+
for v, d in nbrs.items()
|
| 1330 |
+
)
|
| 1331 |
+
return G
|
| 1332 |
+
|
| 1333 |
+
def reverse(self, copy=True):
|
| 1334 |
+
"""Returns the reverse of the graph.
|
| 1335 |
+
|
| 1336 |
+
The reverse is a graph with the same nodes and edges
|
| 1337 |
+
but with the directions of the edges reversed.
|
| 1338 |
+
|
| 1339 |
+
Parameters
|
| 1340 |
+
----------
|
| 1341 |
+
copy : bool optional (default=True)
|
| 1342 |
+
If True, return a new DiGraph holding the reversed edges.
|
| 1343 |
+
If False, the reverse graph is created using a view of
|
| 1344 |
+
the original graph.
|
| 1345 |
+
"""
|
| 1346 |
+
if copy:
|
| 1347 |
+
H = self.__class__()
|
| 1348 |
+
H.graph.update(deepcopy(self.graph))
|
| 1349 |
+
H.add_nodes_from((n, deepcopy(d)) for n, d in self.nodes.items())
|
| 1350 |
+
H.add_edges_from((v, u, deepcopy(d)) for u, v, d in self.edges(data=True))
|
| 1351 |
+
return H
|
| 1352 |
+
return nx.reverse_view(self)
|
.venv/lib/python3.11/site-packages/networkx/classes/filters.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Filter factories to hide or show sets of nodes and edges.
|
| 2 |
+
|
| 3 |
+
These filters return the function used when creating `SubGraph`.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
__all__ = [
|
| 7 |
+
"no_filter",
|
| 8 |
+
"hide_nodes",
|
| 9 |
+
"hide_edges",
|
| 10 |
+
"hide_multiedges",
|
| 11 |
+
"hide_diedges",
|
| 12 |
+
"hide_multidiedges",
|
| 13 |
+
"show_nodes",
|
| 14 |
+
"show_edges",
|
| 15 |
+
"show_multiedges",
|
| 16 |
+
"show_diedges",
|
| 17 |
+
"show_multidiedges",
|
| 18 |
+
]
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def no_filter(*items):
|
| 22 |
+
"""Returns a filter function that always evaluates to True."""
|
| 23 |
+
return True
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def hide_nodes(nodes):
|
| 27 |
+
"""Returns a filter function that hides specific nodes."""
|
| 28 |
+
nodes = set(nodes)
|
| 29 |
+
return lambda node: node not in nodes
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def hide_diedges(edges):
|
| 33 |
+
"""Returns a filter function that hides specific directed edges."""
|
| 34 |
+
edges = {(u, v) for u, v in edges}
|
| 35 |
+
return lambda u, v: (u, v) not in edges
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def hide_edges(edges):
|
| 39 |
+
"""Returns a filter function that hides specific undirected edges."""
|
| 40 |
+
alledges = set(edges) | {(v, u) for (u, v) in edges}
|
| 41 |
+
return lambda u, v: (u, v) not in alledges
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def hide_multidiedges(edges):
|
| 45 |
+
"""Returns a filter function that hides specific multi-directed edges."""
|
| 46 |
+
edges = {(u, v, k) for u, v, k in edges}
|
| 47 |
+
return lambda u, v, k: (u, v, k) not in edges
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def hide_multiedges(edges):
|
| 51 |
+
"""Returns a filter function that hides specific multi-undirected edges."""
|
| 52 |
+
alledges = set(edges) | {(v, u, k) for (u, v, k) in edges}
|
| 53 |
+
return lambda u, v, k: (u, v, k) not in alledges
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
# write show_nodes as a class to make SubGraph pickleable
|
| 57 |
+
class show_nodes:
|
| 58 |
+
"""Filter class to show specific nodes.
|
| 59 |
+
|
| 60 |
+
Attach the set of nodes as an attribute to speed up this commonly used filter
|
| 61 |
+
|
| 62 |
+
Note that another allowed attribute for filters is to store the number of nodes
|
| 63 |
+
on the filter as attribute `length` (used in `__len__`). It is a user
|
| 64 |
+
responsibility to ensure this attribute is accurate if present.
|
| 65 |
+
"""
|
| 66 |
+
|
| 67 |
+
def __init__(self, nodes):
|
| 68 |
+
self.nodes = set(nodes)
|
| 69 |
+
|
| 70 |
+
def __call__(self, node):
|
| 71 |
+
return node in self.nodes
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def show_diedges(edges):
|
| 75 |
+
"""Returns a filter function that shows specific directed edges."""
|
| 76 |
+
edges = {(u, v) for u, v in edges}
|
| 77 |
+
return lambda u, v: (u, v) in edges
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def show_edges(edges):
|
| 81 |
+
"""Returns a filter function that shows specific undirected edges."""
|
| 82 |
+
alledges = set(edges) | {(v, u) for (u, v) in edges}
|
| 83 |
+
return lambda u, v: (u, v) in alledges
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
def show_multidiedges(edges):
|
| 87 |
+
"""Returns a filter function that shows specific multi-directed edges."""
|
| 88 |
+
edges = {(u, v, k) for u, v, k in edges}
|
| 89 |
+
return lambda u, v, k: (u, v, k) in edges
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def show_multiedges(edges):
|
| 93 |
+
"""Returns a filter function that shows specific multi-undirected edges."""
|
| 94 |
+
alledges = set(edges) | {(v, u, k) for (u, v, k) in edges}
|
| 95 |
+
return lambda u, v, k: (u, v, k) in alledges
|
.venv/lib/python3.11/site-packages/networkx/classes/function.py
ADDED
|
@@ -0,0 +1,1407 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Functional interface to graph methods and assorted utilities."""
|
| 2 |
+
|
| 3 |
+
from collections import Counter
|
| 4 |
+
from itertools import chain
|
| 5 |
+
|
| 6 |
+
import networkx as nx
|
| 7 |
+
from networkx.utils import not_implemented_for, pairwise
|
| 8 |
+
|
| 9 |
+
__all__ = [
|
| 10 |
+
"nodes",
|
| 11 |
+
"edges",
|
| 12 |
+
"degree",
|
| 13 |
+
"degree_histogram",
|
| 14 |
+
"neighbors",
|
| 15 |
+
"number_of_nodes",
|
| 16 |
+
"number_of_edges",
|
| 17 |
+
"density",
|
| 18 |
+
"is_directed",
|
| 19 |
+
"freeze",
|
| 20 |
+
"is_frozen",
|
| 21 |
+
"subgraph",
|
| 22 |
+
"induced_subgraph",
|
| 23 |
+
"edge_subgraph",
|
| 24 |
+
"restricted_view",
|
| 25 |
+
"to_directed",
|
| 26 |
+
"to_undirected",
|
| 27 |
+
"add_star",
|
| 28 |
+
"add_path",
|
| 29 |
+
"add_cycle",
|
| 30 |
+
"create_empty_copy",
|
| 31 |
+
"set_node_attributes",
|
| 32 |
+
"get_node_attributes",
|
| 33 |
+
"remove_node_attributes",
|
| 34 |
+
"set_edge_attributes",
|
| 35 |
+
"get_edge_attributes",
|
| 36 |
+
"remove_edge_attributes",
|
| 37 |
+
"all_neighbors",
|
| 38 |
+
"non_neighbors",
|
| 39 |
+
"non_edges",
|
| 40 |
+
"common_neighbors",
|
| 41 |
+
"is_weighted",
|
| 42 |
+
"is_negatively_weighted",
|
| 43 |
+
"is_empty",
|
| 44 |
+
"selfloop_edges",
|
| 45 |
+
"nodes_with_selfloops",
|
| 46 |
+
"number_of_selfloops",
|
| 47 |
+
"path_weight",
|
| 48 |
+
"is_path",
|
| 49 |
+
]
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def nodes(G):
|
| 53 |
+
"""Returns a NodeView over the graph nodes.
|
| 54 |
+
|
| 55 |
+
This function wraps the :func:`G.nodes <networkx.Graph.nodes>` property.
|
| 56 |
+
"""
|
| 57 |
+
return G.nodes()
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def edges(G, nbunch=None):
|
| 61 |
+
"""Returns an edge view of edges incident to nodes in nbunch.
|
| 62 |
+
|
| 63 |
+
Return all edges if nbunch is unspecified or nbunch=None.
|
| 64 |
+
|
| 65 |
+
For digraphs, edges=out_edges
|
| 66 |
+
|
| 67 |
+
This function wraps the :func:`G.edges <networkx.Graph.edges>` property.
|
| 68 |
+
"""
|
| 69 |
+
return G.edges(nbunch)
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def degree(G, nbunch=None, weight=None):
|
| 73 |
+
"""Returns a degree view of single node or of nbunch of nodes.
|
| 74 |
+
If nbunch is omitted, then return degrees of *all* nodes.
|
| 75 |
+
|
| 76 |
+
This function wraps the :func:`G.degree <networkx.Graph.degree>` property.
|
| 77 |
+
"""
|
| 78 |
+
return G.degree(nbunch, weight)
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def neighbors(G, n):
|
| 82 |
+
"""Returns an iterator over all neighbors of node n.
|
| 83 |
+
|
| 84 |
+
This function wraps the :func:`G.neighbors <networkx.Graph.neighbors>` function.
|
| 85 |
+
"""
|
| 86 |
+
return G.neighbors(n)
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def number_of_nodes(G):
|
| 90 |
+
"""Returns the number of nodes in the graph.
|
| 91 |
+
|
| 92 |
+
This function wraps the :func:`G.number_of_nodes <networkx.Graph.number_of_nodes>` function.
|
| 93 |
+
"""
|
| 94 |
+
return G.number_of_nodes()
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
def number_of_edges(G):
|
| 98 |
+
"""Returns the number of edges in the graph.
|
| 99 |
+
|
| 100 |
+
This function wraps the :func:`G.number_of_edges <networkx.Graph.number_of_edges>` function.
|
| 101 |
+
"""
|
| 102 |
+
return G.number_of_edges()
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
def density(G):
|
| 106 |
+
r"""Returns the density of a graph.
|
| 107 |
+
|
| 108 |
+
The density for undirected graphs is
|
| 109 |
+
|
| 110 |
+
.. math::
|
| 111 |
+
|
| 112 |
+
d = \frac{2m}{n(n-1)},
|
| 113 |
+
|
| 114 |
+
and for directed graphs is
|
| 115 |
+
|
| 116 |
+
.. math::
|
| 117 |
+
|
| 118 |
+
d = \frac{m}{n(n-1)},
|
| 119 |
+
|
| 120 |
+
where `n` is the number of nodes and `m` is the number of edges in `G`.
|
| 121 |
+
|
| 122 |
+
Notes
|
| 123 |
+
-----
|
| 124 |
+
The density is 0 for a graph without edges and 1 for a complete graph.
|
| 125 |
+
The density of multigraphs can be higher than 1.
|
| 126 |
+
|
| 127 |
+
Self loops are counted in the total number of edges so graphs with self
|
| 128 |
+
loops can have density higher than 1.
|
| 129 |
+
"""
|
| 130 |
+
n = number_of_nodes(G)
|
| 131 |
+
m = number_of_edges(G)
|
| 132 |
+
if m == 0 or n <= 1:
|
| 133 |
+
return 0
|
| 134 |
+
d = m / (n * (n - 1))
|
| 135 |
+
if not G.is_directed():
|
| 136 |
+
d *= 2
|
| 137 |
+
return d
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
def degree_histogram(G):
|
| 141 |
+
"""Returns a list of the frequency of each degree value.
|
| 142 |
+
|
| 143 |
+
Parameters
|
| 144 |
+
----------
|
| 145 |
+
G : Networkx graph
|
| 146 |
+
A graph
|
| 147 |
+
|
| 148 |
+
Returns
|
| 149 |
+
-------
|
| 150 |
+
hist : list
|
| 151 |
+
A list of frequencies of degrees.
|
| 152 |
+
The degree values are the index in the list.
|
| 153 |
+
|
| 154 |
+
Notes
|
| 155 |
+
-----
|
| 156 |
+
Note: the bins are width one, hence len(list) can be large
|
| 157 |
+
(Order(number_of_edges))
|
| 158 |
+
"""
|
| 159 |
+
counts = Counter(d for n, d in G.degree())
|
| 160 |
+
return [counts.get(i, 0) for i in range(max(counts) + 1 if counts else 0)]
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
def is_directed(G):
|
| 164 |
+
"""Return True if graph is directed."""
|
| 165 |
+
return G.is_directed()
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
def frozen(*args, **kwargs):
|
| 169 |
+
"""Dummy method for raising errors when trying to modify frozen graphs"""
|
| 170 |
+
raise nx.NetworkXError("Frozen graph can't be modified")
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
def freeze(G):
|
| 174 |
+
"""Modify graph to prevent further change by adding or removing
|
| 175 |
+
nodes or edges.
|
| 176 |
+
|
| 177 |
+
Node and edge data can still be modified.
|
| 178 |
+
|
| 179 |
+
Parameters
|
| 180 |
+
----------
|
| 181 |
+
G : graph
|
| 182 |
+
A NetworkX graph
|
| 183 |
+
|
| 184 |
+
Examples
|
| 185 |
+
--------
|
| 186 |
+
>>> G = nx.path_graph(4)
|
| 187 |
+
>>> G = nx.freeze(G)
|
| 188 |
+
>>> try:
|
| 189 |
+
... G.add_edge(4, 5)
|
| 190 |
+
... except nx.NetworkXError as err:
|
| 191 |
+
... print(str(err))
|
| 192 |
+
Frozen graph can't be modified
|
| 193 |
+
|
| 194 |
+
Notes
|
| 195 |
+
-----
|
| 196 |
+
To "unfreeze" a graph you must make a copy by creating a new graph object:
|
| 197 |
+
|
| 198 |
+
>>> graph = nx.path_graph(4)
|
| 199 |
+
>>> frozen_graph = nx.freeze(graph)
|
| 200 |
+
>>> unfrozen_graph = nx.Graph(frozen_graph)
|
| 201 |
+
>>> nx.is_frozen(unfrozen_graph)
|
| 202 |
+
False
|
| 203 |
+
|
| 204 |
+
See Also
|
| 205 |
+
--------
|
| 206 |
+
is_frozen
|
| 207 |
+
"""
|
| 208 |
+
G.add_node = frozen
|
| 209 |
+
G.add_nodes_from = frozen
|
| 210 |
+
G.remove_node = frozen
|
| 211 |
+
G.remove_nodes_from = frozen
|
| 212 |
+
G.add_edge = frozen
|
| 213 |
+
G.add_edges_from = frozen
|
| 214 |
+
G.add_weighted_edges_from = frozen
|
| 215 |
+
G.remove_edge = frozen
|
| 216 |
+
G.remove_edges_from = frozen
|
| 217 |
+
G.clear = frozen
|
| 218 |
+
G.clear_edges = frozen
|
| 219 |
+
G.frozen = True
|
| 220 |
+
return G
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
def is_frozen(G):
|
| 224 |
+
"""Returns True if graph is frozen.
|
| 225 |
+
|
| 226 |
+
Parameters
|
| 227 |
+
----------
|
| 228 |
+
G : graph
|
| 229 |
+
A NetworkX graph
|
| 230 |
+
|
| 231 |
+
See Also
|
| 232 |
+
--------
|
| 233 |
+
freeze
|
| 234 |
+
"""
|
| 235 |
+
try:
|
| 236 |
+
return G.frozen
|
| 237 |
+
except AttributeError:
|
| 238 |
+
return False
|
| 239 |
+
|
| 240 |
+
|
| 241 |
+
def add_star(G_to_add_to, nodes_for_star, **attr):
|
| 242 |
+
"""Add a star to Graph G_to_add_to.
|
| 243 |
+
|
| 244 |
+
The first node in `nodes_for_star` is the middle of the star.
|
| 245 |
+
It is connected to all other nodes.
|
| 246 |
+
|
| 247 |
+
Parameters
|
| 248 |
+
----------
|
| 249 |
+
G_to_add_to : graph
|
| 250 |
+
A NetworkX graph
|
| 251 |
+
nodes_for_star : iterable container
|
| 252 |
+
A container of nodes.
|
| 253 |
+
attr : keyword arguments, optional (default= no attributes)
|
| 254 |
+
Attributes to add to every edge in star.
|
| 255 |
+
|
| 256 |
+
See Also
|
| 257 |
+
--------
|
| 258 |
+
add_path, add_cycle
|
| 259 |
+
|
| 260 |
+
Examples
|
| 261 |
+
--------
|
| 262 |
+
>>> G = nx.Graph()
|
| 263 |
+
>>> nx.add_star(G, [0, 1, 2, 3])
|
| 264 |
+
>>> nx.add_star(G, [10, 11, 12], weight=2)
|
| 265 |
+
"""
|
| 266 |
+
nlist = iter(nodes_for_star)
|
| 267 |
+
try:
|
| 268 |
+
v = next(nlist)
|
| 269 |
+
except StopIteration:
|
| 270 |
+
return
|
| 271 |
+
G_to_add_to.add_node(v)
|
| 272 |
+
edges = ((v, n) for n in nlist)
|
| 273 |
+
G_to_add_to.add_edges_from(edges, **attr)
|
| 274 |
+
|
| 275 |
+
|
| 276 |
+
def add_path(G_to_add_to, nodes_for_path, **attr):
|
| 277 |
+
"""Add a path to the Graph G_to_add_to.
|
| 278 |
+
|
| 279 |
+
Parameters
|
| 280 |
+
----------
|
| 281 |
+
G_to_add_to : graph
|
| 282 |
+
A NetworkX graph
|
| 283 |
+
nodes_for_path : iterable container
|
| 284 |
+
A container of nodes. A path will be constructed from
|
| 285 |
+
the nodes (in order) and added to the graph.
|
| 286 |
+
attr : keyword arguments, optional (default= no attributes)
|
| 287 |
+
Attributes to add to every edge in path.
|
| 288 |
+
|
| 289 |
+
See Also
|
| 290 |
+
--------
|
| 291 |
+
add_star, add_cycle
|
| 292 |
+
|
| 293 |
+
Examples
|
| 294 |
+
--------
|
| 295 |
+
>>> G = nx.Graph()
|
| 296 |
+
>>> nx.add_path(G, [0, 1, 2, 3])
|
| 297 |
+
>>> nx.add_path(G, [10, 11, 12], weight=7)
|
| 298 |
+
"""
|
| 299 |
+
nlist = iter(nodes_for_path)
|
| 300 |
+
try:
|
| 301 |
+
first_node = next(nlist)
|
| 302 |
+
except StopIteration:
|
| 303 |
+
return
|
| 304 |
+
G_to_add_to.add_node(first_node)
|
| 305 |
+
G_to_add_to.add_edges_from(pairwise(chain((first_node,), nlist)), **attr)
|
| 306 |
+
|
| 307 |
+
|
| 308 |
+
def add_cycle(G_to_add_to, nodes_for_cycle, **attr):
|
| 309 |
+
"""Add a cycle to the Graph G_to_add_to.
|
| 310 |
+
|
| 311 |
+
Parameters
|
| 312 |
+
----------
|
| 313 |
+
G_to_add_to : graph
|
| 314 |
+
A NetworkX graph
|
| 315 |
+
nodes_for_cycle: iterable container
|
| 316 |
+
A container of nodes. A cycle will be constructed from
|
| 317 |
+
the nodes (in order) and added to the graph.
|
| 318 |
+
attr : keyword arguments, optional (default= no attributes)
|
| 319 |
+
Attributes to add to every edge in cycle.
|
| 320 |
+
|
| 321 |
+
See Also
|
| 322 |
+
--------
|
| 323 |
+
add_path, add_star
|
| 324 |
+
|
| 325 |
+
Examples
|
| 326 |
+
--------
|
| 327 |
+
>>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 328 |
+
>>> nx.add_cycle(G, [0, 1, 2, 3])
|
| 329 |
+
>>> nx.add_cycle(G, [10, 11, 12], weight=7)
|
| 330 |
+
"""
|
| 331 |
+
nlist = iter(nodes_for_cycle)
|
| 332 |
+
try:
|
| 333 |
+
first_node = next(nlist)
|
| 334 |
+
except StopIteration:
|
| 335 |
+
return
|
| 336 |
+
G_to_add_to.add_node(first_node)
|
| 337 |
+
G_to_add_to.add_edges_from(
|
| 338 |
+
pairwise(chain((first_node,), nlist), cyclic=True), **attr
|
| 339 |
+
)
|
| 340 |
+
|
| 341 |
+
|
| 342 |
+
def subgraph(G, nbunch):
|
| 343 |
+
"""Returns the subgraph induced on nodes in nbunch.
|
| 344 |
+
|
| 345 |
+
Parameters
|
| 346 |
+
----------
|
| 347 |
+
G : graph
|
| 348 |
+
A NetworkX graph
|
| 349 |
+
|
| 350 |
+
nbunch : list, iterable
|
| 351 |
+
A container of nodes that will be iterated through once (thus
|
| 352 |
+
it should be an iterator or be iterable). Each element of the
|
| 353 |
+
container should be a valid node type: any hashable type except
|
| 354 |
+
None. If nbunch is None, return all edges data in the graph.
|
| 355 |
+
Nodes in nbunch that are not in the graph will be (quietly)
|
| 356 |
+
ignored.
|
| 357 |
+
|
| 358 |
+
Notes
|
| 359 |
+
-----
|
| 360 |
+
subgraph(G) calls G.subgraph()
|
| 361 |
+
"""
|
| 362 |
+
return G.subgraph(nbunch)
|
| 363 |
+
|
| 364 |
+
|
| 365 |
+
def induced_subgraph(G, nbunch):
|
| 366 |
+
"""Returns a SubGraph view of `G` showing only nodes in nbunch.
|
| 367 |
+
|
| 368 |
+
The induced subgraph of a graph on a set of nodes N is the
|
| 369 |
+
graph with nodes N and edges from G which have both ends in N.
|
| 370 |
+
|
| 371 |
+
Parameters
|
| 372 |
+
----------
|
| 373 |
+
G : NetworkX Graph
|
| 374 |
+
nbunch : node, container of nodes or None (for all nodes)
|
| 375 |
+
|
| 376 |
+
Returns
|
| 377 |
+
-------
|
| 378 |
+
subgraph : SubGraph View
|
| 379 |
+
A read-only view of the subgraph in `G` induced by the nodes.
|
| 380 |
+
Changes to the graph `G` will be reflected in the view.
|
| 381 |
+
|
| 382 |
+
Notes
|
| 383 |
+
-----
|
| 384 |
+
To create a mutable subgraph with its own copies of nodes
|
| 385 |
+
edges and attributes use `subgraph.copy()` or `Graph(subgraph)`
|
| 386 |
+
|
| 387 |
+
For an inplace reduction of a graph to a subgraph you can remove nodes:
|
| 388 |
+
`G.remove_nodes_from(n in G if n not in set(nbunch))`
|
| 389 |
+
|
| 390 |
+
If you are going to compute subgraphs of your subgraphs you could
|
| 391 |
+
end up with a chain of views that can be very slow once the chain
|
| 392 |
+
has about 15 views in it. If they are all induced subgraphs, you
|
| 393 |
+
can short-cut the chain by making them all subgraphs of the original
|
| 394 |
+
graph. The graph class method `G.subgraph` does this when `G` is
|
| 395 |
+
a subgraph. In contrast, this function allows you to choose to build
|
| 396 |
+
chains or not, as you wish. The returned subgraph is a view on `G`.
|
| 397 |
+
|
| 398 |
+
Examples
|
| 399 |
+
--------
|
| 400 |
+
>>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 401 |
+
>>> H = nx.induced_subgraph(G, [0, 1, 3])
|
| 402 |
+
>>> list(H.edges)
|
| 403 |
+
[(0, 1)]
|
| 404 |
+
>>> list(H.nodes)
|
| 405 |
+
[0, 1, 3]
|
| 406 |
+
"""
|
| 407 |
+
induced_nodes = nx.filters.show_nodes(G.nbunch_iter(nbunch))
|
| 408 |
+
return nx.subgraph_view(G, filter_node=induced_nodes)
|
| 409 |
+
|
| 410 |
+
|
| 411 |
+
def edge_subgraph(G, edges):
|
| 412 |
+
"""Returns a view of the subgraph induced by the specified edges.
|
| 413 |
+
|
| 414 |
+
The induced subgraph contains each edge in `edges` and each
|
| 415 |
+
node incident to any of those edges.
|
| 416 |
+
|
| 417 |
+
Parameters
|
| 418 |
+
----------
|
| 419 |
+
G : NetworkX Graph
|
| 420 |
+
edges : iterable
|
| 421 |
+
An iterable of edges. Edges not present in `G` are ignored.
|
| 422 |
+
|
| 423 |
+
Returns
|
| 424 |
+
-------
|
| 425 |
+
subgraph : SubGraph View
|
| 426 |
+
A read-only edge-induced subgraph of `G`.
|
| 427 |
+
Changes to `G` are reflected in the view.
|
| 428 |
+
|
| 429 |
+
Notes
|
| 430 |
+
-----
|
| 431 |
+
To create a mutable subgraph with its own copies of nodes
|
| 432 |
+
edges and attributes use `subgraph.copy()` or `Graph(subgraph)`
|
| 433 |
+
|
| 434 |
+
If you create a subgraph of a subgraph recursively you can end up
|
| 435 |
+
with a chain of subgraphs that becomes very slow with about 15
|
| 436 |
+
nested subgraph views. Luckily the edge_subgraph filter nests
|
| 437 |
+
nicely so you can use the original graph as G in this function
|
| 438 |
+
to avoid chains. We do not rule out chains programmatically so
|
| 439 |
+
that odd cases like an `edge_subgraph` of a `restricted_view`
|
| 440 |
+
can be created.
|
| 441 |
+
|
| 442 |
+
Examples
|
| 443 |
+
--------
|
| 444 |
+
>>> G = nx.path_graph(5)
|
| 445 |
+
>>> H = G.edge_subgraph([(0, 1), (3, 4)])
|
| 446 |
+
>>> list(H.nodes)
|
| 447 |
+
[0, 1, 3, 4]
|
| 448 |
+
>>> list(H.edges)
|
| 449 |
+
[(0, 1), (3, 4)]
|
| 450 |
+
"""
|
| 451 |
+
nxf = nx.filters
|
| 452 |
+
edges = set(edges)
|
| 453 |
+
nodes = set()
|
| 454 |
+
for e in edges:
|
| 455 |
+
nodes.update(e[:2])
|
| 456 |
+
induced_nodes = nxf.show_nodes(nodes)
|
| 457 |
+
if G.is_multigraph():
|
| 458 |
+
if G.is_directed():
|
| 459 |
+
induced_edges = nxf.show_multidiedges(edges)
|
| 460 |
+
else:
|
| 461 |
+
induced_edges = nxf.show_multiedges(edges)
|
| 462 |
+
else:
|
| 463 |
+
if G.is_directed():
|
| 464 |
+
induced_edges = nxf.show_diedges(edges)
|
| 465 |
+
else:
|
| 466 |
+
induced_edges = nxf.show_edges(edges)
|
| 467 |
+
return nx.subgraph_view(G, filter_node=induced_nodes, filter_edge=induced_edges)
|
| 468 |
+
|
| 469 |
+
|
| 470 |
+
def restricted_view(G, nodes, edges):
|
| 471 |
+
"""Returns a view of `G` with hidden nodes and edges.
|
| 472 |
+
|
| 473 |
+
The resulting subgraph filters out node `nodes` and edges `edges`.
|
| 474 |
+
Filtered out nodes also filter out any of their edges.
|
| 475 |
+
|
| 476 |
+
Parameters
|
| 477 |
+
----------
|
| 478 |
+
G : NetworkX Graph
|
| 479 |
+
nodes : iterable
|
| 480 |
+
An iterable of nodes. Nodes not present in `G` are ignored.
|
| 481 |
+
edges : iterable
|
| 482 |
+
An iterable of edges. Edges not present in `G` are ignored.
|
| 483 |
+
|
| 484 |
+
Returns
|
| 485 |
+
-------
|
| 486 |
+
subgraph : SubGraph View
|
| 487 |
+
A read-only restricted view of `G` filtering out nodes and edges.
|
| 488 |
+
Changes to `G` are reflected in the view.
|
| 489 |
+
|
| 490 |
+
Notes
|
| 491 |
+
-----
|
| 492 |
+
To create a mutable subgraph with its own copies of nodes
|
| 493 |
+
edges and attributes use `subgraph.copy()` or `Graph(subgraph)`
|
| 494 |
+
|
| 495 |
+
If you create a subgraph of a subgraph recursively you may end up
|
| 496 |
+
with a chain of subgraph views. Such chains can get quite slow
|
| 497 |
+
for lengths near 15. To avoid long chains, try to make your subgraph
|
| 498 |
+
based on the original graph. We do not rule out chains programmatically
|
| 499 |
+
so that odd cases like an `edge_subgraph` of a `restricted_view`
|
| 500 |
+
can be created.
|
| 501 |
+
|
| 502 |
+
Examples
|
| 503 |
+
--------
|
| 504 |
+
>>> G = nx.path_graph(5)
|
| 505 |
+
>>> H = nx.restricted_view(G, [0], [(1, 2), (3, 4)])
|
| 506 |
+
>>> list(H.nodes)
|
| 507 |
+
[1, 2, 3, 4]
|
| 508 |
+
>>> list(H.edges)
|
| 509 |
+
[(2, 3)]
|
| 510 |
+
"""
|
| 511 |
+
nxf = nx.filters
|
| 512 |
+
hide_nodes = nxf.hide_nodes(nodes)
|
| 513 |
+
if G.is_multigraph():
|
| 514 |
+
if G.is_directed():
|
| 515 |
+
hide_edges = nxf.hide_multidiedges(edges)
|
| 516 |
+
else:
|
| 517 |
+
hide_edges = nxf.hide_multiedges(edges)
|
| 518 |
+
else:
|
| 519 |
+
if G.is_directed():
|
| 520 |
+
hide_edges = nxf.hide_diedges(edges)
|
| 521 |
+
else:
|
| 522 |
+
hide_edges = nxf.hide_edges(edges)
|
| 523 |
+
return nx.subgraph_view(G, filter_node=hide_nodes, filter_edge=hide_edges)
|
| 524 |
+
|
| 525 |
+
|
| 526 |
+
def to_directed(graph):
|
| 527 |
+
"""Returns a directed view of the graph `graph`.
|
| 528 |
+
|
| 529 |
+
Identical to graph.to_directed(as_view=True)
|
| 530 |
+
Note that graph.to_directed defaults to `as_view=False`
|
| 531 |
+
while this function always provides a view.
|
| 532 |
+
"""
|
| 533 |
+
return graph.to_directed(as_view=True)
|
| 534 |
+
|
| 535 |
+
|
| 536 |
+
def to_undirected(graph):
|
| 537 |
+
"""Returns an undirected view of the graph `graph`.
|
| 538 |
+
|
| 539 |
+
Identical to graph.to_undirected(as_view=True)
|
| 540 |
+
Note that graph.to_undirected defaults to `as_view=False`
|
| 541 |
+
while this function always provides a view.
|
| 542 |
+
"""
|
| 543 |
+
return graph.to_undirected(as_view=True)
|
| 544 |
+
|
| 545 |
+
|
| 546 |
+
def create_empty_copy(G, with_data=True):
|
| 547 |
+
"""Returns a copy of the graph G with all of the edges removed.
|
| 548 |
+
|
| 549 |
+
Parameters
|
| 550 |
+
----------
|
| 551 |
+
G : graph
|
| 552 |
+
A NetworkX graph
|
| 553 |
+
|
| 554 |
+
with_data : bool (default=True)
|
| 555 |
+
Propagate Graph and Nodes data to the new graph.
|
| 556 |
+
|
| 557 |
+
See Also
|
| 558 |
+
--------
|
| 559 |
+
empty_graph
|
| 560 |
+
|
| 561 |
+
"""
|
| 562 |
+
H = G.__class__()
|
| 563 |
+
H.add_nodes_from(G.nodes(data=with_data))
|
| 564 |
+
if with_data:
|
| 565 |
+
H.graph.update(G.graph)
|
| 566 |
+
return H
|
| 567 |
+
|
| 568 |
+
|
| 569 |
+
def set_node_attributes(G, values, name=None):
|
| 570 |
+
"""Sets node attributes from a given value or dictionary of values.
|
| 571 |
+
|
| 572 |
+
.. Warning:: The call order of arguments `values` and `name`
|
| 573 |
+
switched between v1.x & v2.x.
|
| 574 |
+
|
| 575 |
+
Parameters
|
| 576 |
+
----------
|
| 577 |
+
G : NetworkX Graph
|
| 578 |
+
|
| 579 |
+
values : scalar value, dict-like
|
| 580 |
+
What the node attribute should be set to. If `values` is
|
| 581 |
+
not a dictionary, then it is treated as a single attribute value
|
| 582 |
+
that is then applied to every node in `G`. This means that if
|
| 583 |
+
you provide a mutable object, like a list, updates to that object
|
| 584 |
+
will be reflected in the node attribute for every node.
|
| 585 |
+
The attribute name will be `name`.
|
| 586 |
+
|
| 587 |
+
If `values` is a dict or a dict of dict, it should be keyed
|
| 588 |
+
by node to either an attribute value or a dict of attribute key/value
|
| 589 |
+
pairs used to update the node's attributes.
|
| 590 |
+
|
| 591 |
+
name : string (optional, default=None)
|
| 592 |
+
Name of the node attribute to set if values is a scalar.
|
| 593 |
+
|
| 594 |
+
Examples
|
| 595 |
+
--------
|
| 596 |
+
After computing some property of the nodes of a graph, you may want
|
| 597 |
+
to assign a node attribute to store the value of that property for
|
| 598 |
+
each node::
|
| 599 |
+
|
| 600 |
+
>>> G = nx.path_graph(3)
|
| 601 |
+
>>> bb = nx.betweenness_centrality(G)
|
| 602 |
+
>>> isinstance(bb, dict)
|
| 603 |
+
True
|
| 604 |
+
>>> nx.set_node_attributes(G, bb, "betweenness")
|
| 605 |
+
>>> G.nodes[1]["betweenness"]
|
| 606 |
+
1.0
|
| 607 |
+
|
| 608 |
+
If you provide a list as the second argument, updates to the list
|
| 609 |
+
will be reflected in the node attribute for each node::
|
| 610 |
+
|
| 611 |
+
>>> G = nx.path_graph(3)
|
| 612 |
+
>>> labels = []
|
| 613 |
+
>>> nx.set_node_attributes(G, labels, "labels")
|
| 614 |
+
>>> labels.append("foo")
|
| 615 |
+
>>> G.nodes[0]["labels"]
|
| 616 |
+
['foo']
|
| 617 |
+
>>> G.nodes[1]["labels"]
|
| 618 |
+
['foo']
|
| 619 |
+
>>> G.nodes[2]["labels"]
|
| 620 |
+
['foo']
|
| 621 |
+
|
| 622 |
+
If you provide a dictionary of dictionaries as the second argument,
|
| 623 |
+
the outer dictionary is assumed to be keyed by node to an inner
|
| 624 |
+
dictionary of node attributes for that node::
|
| 625 |
+
|
| 626 |
+
>>> G = nx.path_graph(3)
|
| 627 |
+
>>> attrs = {0: {"attr1": 20, "attr2": "nothing"}, 1: {"attr2": 3}}
|
| 628 |
+
>>> nx.set_node_attributes(G, attrs)
|
| 629 |
+
>>> G.nodes[0]["attr1"]
|
| 630 |
+
20
|
| 631 |
+
>>> G.nodes[0]["attr2"]
|
| 632 |
+
'nothing'
|
| 633 |
+
>>> G.nodes[1]["attr2"]
|
| 634 |
+
3
|
| 635 |
+
>>> G.nodes[2]
|
| 636 |
+
{}
|
| 637 |
+
|
| 638 |
+
Note that if the dictionary contains nodes that are not in `G`, the
|
| 639 |
+
values are silently ignored::
|
| 640 |
+
|
| 641 |
+
>>> G = nx.Graph()
|
| 642 |
+
>>> G.add_node(0)
|
| 643 |
+
>>> nx.set_node_attributes(G, {0: "red", 1: "blue"}, name="color")
|
| 644 |
+
>>> G.nodes[0]["color"]
|
| 645 |
+
'red'
|
| 646 |
+
>>> 1 in G.nodes
|
| 647 |
+
False
|
| 648 |
+
|
| 649 |
+
"""
|
| 650 |
+
# Set node attributes based on type of `values`
|
| 651 |
+
if name is not None: # `values` must not be a dict of dict
|
| 652 |
+
try: # `values` is a dict
|
| 653 |
+
for n, v in values.items():
|
| 654 |
+
try:
|
| 655 |
+
G.nodes[n][name] = values[n]
|
| 656 |
+
except KeyError:
|
| 657 |
+
pass
|
| 658 |
+
except AttributeError: # `values` is a constant
|
| 659 |
+
for n in G:
|
| 660 |
+
G.nodes[n][name] = values
|
| 661 |
+
else: # `values` must be dict of dict
|
| 662 |
+
for n, d in values.items():
|
| 663 |
+
try:
|
| 664 |
+
G.nodes[n].update(d)
|
| 665 |
+
except KeyError:
|
| 666 |
+
pass
|
| 667 |
+
nx._clear_cache(G)
|
| 668 |
+
|
| 669 |
+
|
| 670 |
+
def get_node_attributes(G, name, default=None):
|
| 671 |
+
"""Get node attributes from graph
|
| 672 |
+
|
| 673 |
+
Parameters
|
| 674 |
+
----------
|
| 675 |
+
G : NetworkX Graph
|
| 676 |
+
|
| 677 |
+
name : string
|
| 678 |
+
Attribute name
|
| 679 |
+
|
| 680 |
+
default: object (default=None)
|
| 681 |
+
Default value of the node attribute if there is no value set for that
|
| 682 |
+
node in graph. If `None` then nodes without this attribute are not
|
| 683 |
+
included in the returned dict.
|
| 684 |
+
|
| 685 |
+
Returns
|
| 686 |
+
-------
|
| 687 |
+
Dictionary of attributes keyed by node.
|
| 688 |
+
|
| 689 |
+
Examples
|
| 690 |
+
--------
|
| 691 |
+
>>> G = nx.Graph()
|
| 692 |
+
>>> G.add_nodes_from([1, 2, 3], color="red")
|
| 693 |
+
>>> color = nx.get_node_attributes(G, "color")
|
| 694 |
+
>>> color[1]
|
| 695 |
+
'red'
|
| 696 |
+
>>> G.add_node(4)
|
| 697 |
+
>>> color = nx.get_node_attributes(G, "color", default="yellow")
|
| 698 |
+
>>> color[4]
|
| 699 |
+
'yellow'
|
| 700 |
+
"""
|
| 701 |
+
if default is not None:
|
| 702 |
+
return {n: d.get(name, default) for n, d in G.nodes.items()}
|
| 703 |
+
return {n: d[name] for n, d in G.nodes.items() if name in d}
|
| 704 |
+
|
| 705 |
+
|
| 706 |
+
def remove_node_attributes(G, *attr_names, nbunch=None):
|
| 707 |
+
"""Remove node attributes from all nodes in the graph.
|
| 708 |
+
|
| 709 |
+
Parameters
|
| 710 |
+
----------
|
| 711 |
+
G : NetworkX Graph
|
| 712 |
+
|
| 713 |
+
*attr_names : List of Strings
|
| 714 |
+
The attribute names to remove from the graph.
|
| 715 |
+
|
| 716 |
+
nbunch : List of Nodes
|
| 717 |
+
Remove the node attributes only from the nodes in this list.
|
| 718 |
+
|
| 719 |
+
Examples
|
| 720 |
+
--------
|
| 721 |
+
>>> G = nx.Graph()
|
| 722 |
+
>>> G.add_nodes_from([1, 2, 3], color="blue")
|
| 723 |
+
>>> nx.get_node_attributes(G, "color")
|
| 724 |
+
{1: 'blue', 2: 'blue', 3: 'blue'}
|
| 725 |
+
>>> nx.remove_node_attributes(G, "color")
|
| 726 |
+
>>> nx.get_node_attributes(G, "color")
|
| 727 |
+
{}
|
| 728 |
+
"""
|
| 729 |
+
|
| 730 |
+
if nbunch is None:
|
| 731 |
+
nbunch = G.nodes()
|
| 732 |
+
|
| 733 |
+
for attr in attr_names:
|
| 734 |
+
for n, d in G.nodes(data=True):
|
| 735 |
+
if n in nbunch:
|
| 736 |
+
try:
|
| 737 |
+
del d[attr]
|
| 738 |
+
except KeyError:
|
| 739 |
+
pass
|
| 740 |
+
|
| 741 |
+
|
| 742 |
+
def set_edge_attributes(G, values, name=None):
|
| 743 |
+
"""Sets edge attributes from a given value or dictionary of values.
|
| 744 |
+
|
| 745 |
+
.. Warning:: The call order of arguments `values` and `name`
|
| 746 |
+
switched between v1.x & v2.x.
|
| 747 |
+
|
| 748 |
+
Parameters
|
| 749 |
+
----------
|
| 750 |
+
G : NetworkX Graph
|
| 751 |
+
|
| 752 |
+
values : scalar value, dict-like
|
| 753 |
+
What the edge attribute should be set to. If `values` is
|
| 754 |
+
not a dictionary, then it is treated as a single attribute value
|
| 755 |
+
that is then applied to every edge in `G`. This means that if
|
| 756 |
+
you provide a mutable object, like a list, updates to that object
|
| 757 |
+
will be reflected in the edge attribute for each edge. The attribute
|
| 758 |
+
name will be `name`.
|
| 759 |
+
|
| 760 |
+
If `values` is a dict or a dict of dict, it should be keyed
|
| 761 |
+
by edge tuple to either an attribute value or a dict of attribute
|
| 762 |
+
key/value pairs used to update the edge's attributes.
|
| 763 |
+
For multigraphs, the edge tuples must be of the form ``(u, v, key)``,
|
| 764 |
+
where `u` and `v` are nodes and `key` is the edge key.
|
| 765 |
+
For non-multigraphs, the keys must be tuples of the form ``(u, v)``.
|
| 766 |
+
|
| 767 |
+
name : string (optional, default=None)
|
| 768 |
+
Name of the edge attribute to set if values is a scalar.
|
| 769 |
+
|
| 770 |
+
Examples
|
| 771 |
+
--------
|
| 772 |
+
After computing some property of the edges of a graph, you may want
|
| 773 |
+
to assign a edge attribute to store the value of that property for
|
| 774 |
+
each edge::
|
| 775 |
+
|
| 776 |
+
>>> G = nx.path_graph(3)
|
| 777 |
+
>>> bb = nx.edge_betweenness_centrality(G, normalized=False)
|
| 778 |
+
>>> nx.set_edge_attributes(G, bb, "betweenness")
|
| 779 |
+
>>> G.edges[1, 2]["betweenness"]
|
| 780 |
+
2.0
|
| 781 |
+
|
| 782 |
+
If you provide a list as the second argument, updates to the list
|
| 783 |
+
will be reflected in the edge attribute for each edge::
|
| 784 |
+
|
| 785 |
+
>>> labels = []
|
| 786 |
+
>>> nx.set_edge_attributes(G, labels, "labels")
|
| 787 |
+
>>> labels.append("foo")
|
| 788 |
+
>>> G.edges[0, 1]["labels"]
|
| 789 |
+
['foo']
|
| 790 |
+
>>> G.edges[1, 2]["labels"]
|
| 791 |
+
['foo']
|
| 792 |
+
|
| 793 |
+
If you provide a dictionary of dictionaries as the second argument,
|
| 794 |
+
the entire dictionary will be used to update edge attributes::
|
| 795 |
+
|
| 796 |
+
>>> G = nx.path_graph(3)
|
| 797 |
+
>>> attrs = {(0, 1): {"attr1": 20, "attr2": "nothing"}, (1, 2): {"attr2": 3}}
|
| 798 |
+
>>> nx.set_edge_attributes(G, attrs)
|
| 799 |
+
>>> G[0][1]["attr1"]
|
| 800 |
+
20
|
| 801 |
+
>>> G[0][1]["attr2"]
|
| 802 |
+
'nothing'
|
| 803 |
+
>>> G[1][2]["attr2"]
|
| 804 |
+
3
|
| 805 |
+
|
| 806 |
+
The attributes of one Graph can be used to set those of another.
|
| 807 |
+
|
| 808 |
+
>>> H = nx.path_graph(3)
|
| 809 |
+
>>> nx.set_edge_attributes(H, G.edges)
|
| 810 |
+
|
| 811 |
+
Note that if the dict contains edges that are not in `G`, they are
|
| 812 |
+
silently ignored::
|
| 813 |
+
|
| 814 |
+
>>> G = nx.Graph([(0, 1)])
|
| 815 |
+
>>> nx.set_edge_attributes(G, {(1, 2): {"weight": 2.0}})
|
| 816 |
+
>>> (1, 2) in G.edges()
|
| 817 |
+
False
|
| 818 |
+
|
| 819 |
+
For multigraphs, the `values` dict is expected to be keyed by 3-tuples
|
| 820 |
+
including the edge key::
|
| 821 |
+
|
| 822 |
+
>>> MG = nx.MultiGraph()
|
| 823 |
+
>>> edges = [(0, 1), (0, 1)]
|
| 824 |
+
>>> MG.add_edges_from(edges) # Returns list of edge keys
|
| 825 |
+
[0, 1]
|
| 826 |
+
>>> attributes = {(0, 1, 0): {"cost": 21}, (0, 1, 1): {"cost": 7}}
|
| 827 |
+
>>> nx.set_edge_attributes(MG, attributes)
|
| 828 |
+
>>> MG[0][1][0]["cost"]
|
| 829 |
+
21
|
| 830 |
+
>>> MG[0][1][1]["cost"]
|
| 831 |
+
7
|
| 832 |
+
|
| 833 |
+
If MultiGraph attributes are desired for a Graph, you must convert the 3-tuple
|
| 834 |
+
multiedge to a 2-tuple edge and the last multiedge's attribute value will
|
| 835 |
+
overwrite the previous values. Continuing from the previous case we get::
|
| 836 |
+
|
| 837 |
+
>>> H = nx.path_graph([0, 1, 2])
|
| 838 |
+
>>> nx.set_edge_attributes(H, {(u, v): ed for u, v, ed in MG.edges.data()})
|
| 839 |
+
>>> nx.get_edge_attributes(H, "cost")
|
| 840 |
+
{(0, 1): 7}
|
| 841 |
+
|
| 842 |
+
"""
|
| 843 |
+
if name is not None:
|
| 844 |
+
# `values` does not contain attribute names
|
| 845 |
+
try:
|
| 846 |
+
# if `values` is a dict using `.items()` => {edge: value}
|
| 847 |
+
if G.is_multigraph():
|
| 848 |
+
for (u, v, key), value in values.items():
|
| 849 |
+
try:
|
| 850 |
+
G._adj[u][v][key][name] = value
|
| 851 |
+
except KeyError:
|
| 852 |
+
pass
|
| 853 |
+
else:
|
| 854 |
+
for (u, v), value in values.items():
|
| 855 |
+
try:
|
| 856 |
+
G._adj[u][v][name] = value
|
| 857 |
+
except KeyError:
|
| 858 |
+
pass
|
| 859 |
+
except AttributeError:
|
| 860 |
+
# treat `values` as a constant
|
| 861 |
+
for u, v, data in G.edges(data=True):
|
| 862 |
+
data[name] = values
|
| 863 |
+
else:
|
| 864 |
+
# `values` consists of doct-of-dict {edge: {attr: value}} shape
|
| 865 |
+
if G.is_multigraph():
|
| 866 |
+
for (u, v, key), d in values.items():
|
| 867 |
+
try:
|
| 868 |
+
G._adj[u][v][key].update(d)
|
| 869 |
+
except KeyError:
|
| 870 |
+
pass
|
| 871 |
+
else:
|
| 872 |
+
for (u, v), d in values.items():
|
| 873 |
+
try:
|
| 874 |
+
G._adj[u][v].update(d)
|
| 875 |
+
except KeyError:
|
| 876 |
+
pass
|
| 877 |
+
nx._clear_cache(G)
|
| 878 |
+
|
| 879 |
+
|
| 880 |
+
def get_edge_attributes(G, name, default=None):
|
| 881 |
+
"""Get edge attributes from graph
|
| 882 |
+
|
| 883 |
+
Parameters
|
| 884 |
+
----------
|
| 885 |
+
G : NetworkX Graph
|
| 886 |
+
|
| 887 |
+
name : string
|
| 888 |
+
Attribute name
|
| 889 |
+
|
| 890 |
+
default: object (default=None)
|
| 891 |
+
Default value of the edge attribute if there is no value set for that
|
| 892 |
+
edge in graph. If `None` then edges without this attribute are not
|
| 893 |
+
included in the returned dict.
|
| 894 |
+
|
| 895 |
+
Returns
|
| 896 |
+
-------
|
| 897 |
+
Dictionary of attributes keyed by edge. For (di)graphs, the keys are
|
| 898 |
+
2-tuples of the form: (u, v). For multi(di)graphs, the keys are 3-tuples of
|
| 899 |
+
the form: (u, v, key).
|
| 900 |
+
|
| 901 |
+
Examples
|
| 902 |
+
--------
|
| 903 |
+
>>> G = nx.Graph()
|
| 904 |
+
>>> nx.add_path(G, [1, 2, 3], color="red")
|
| 905 |
+
>>> color = nx.get_edge_attributes(G, "color")
|
| 906 |
+
>>> color[(1, 2)]
|
| 907 |
+
'red'
|
| 908 |
+
>>> G.add_edge(3, 4)
|
| 909 |
+
>>> color = nx.get_edge_attributes(G, "color", default="yellow")
|
| 910 |
+
>>> color[(3, 4)]
|
| 911 |
+
'yellow'
|
| 912 |
+
"""
|
| 913 |
+
if G.is_multigraph():
|
| 914 |
+
edges = G.edges(keys=True, data=True)
|
| 915 |
+
else:
|
| 916 |
+
edges = G.edges(data=True)
|
| 917 |
+
if default is not None:
|
| 918 |
+
return {x[:-1]: x[-1].get(name, default) for x in edges}
|
| 919 |
+
return {x[:-1]: x[-1][name] for x in edges if name in x[-1]}
|
| 920 |
+
|
| 921 |
+
|
| 922 |
+
def remove_edge_attributes(G, *attr_names, ebunch=None):
|
| 923 |
+
"""Remove edge attributes from all edges in the graph.
|
| 924 |
+
|
| 925 |
+
Parameters
|
| 926 |
+
----------
|
| 927 |
+
G : NetworkX Graph
|
| 928 |
+
|
| 929 |
+
*attr_names : List of Strings
|
| 930 |
+
The attribute names to remove from the graph.
|
| 931 |
+
|
| 932 |
+
Examples
|
| 933 |
+
--------
|
| 934 |
+
>>> G = nx.path_graph(3)
|
| 935 |
+
>>> nx.set_edge_attributes(G, {(u, v): u + v for u, v in G.edges()}, name="weight")
|
| 936 |
+
>>> nx.get_edge_attributes(G, "weight")
|
| 937 |
+
{(0, 1): 1, (1, 2): 3}
|
| 938 |
+
>>> remove_edge_attributes(G, "weight")
|
| 939 |
+
>>> nx.get_edge_attributes(G, "weight")
|
| 940 |
+
{}
|
| 941 |
+
"""
|
| 942 |
+
if ebunch is None:
|
| 943 |
+
ebunch = G.edges(keys=True) if G.is_multigraph() else G.edges()
|
| 944 |
+
|
| 945 |
+
for attr in attr_names:
|
| 946 |
+
edges = (
|
| 947 |
+
G.edges(keys=True, data=True) if G.is_multigraph() else G.edges(data=True)
|
| 948 |
+
)
|
| 949 |
+
for *e, d in edges:
|
| 950 |
+
if tuple(e) in ebunch:
|
| 951 |
+
try:
|
| 952 |
+
del d[attr]
|
| 953 |
+
except KeyError:
|
| 954 |
+
pass
|
| 955 |
+
|
| 956 |
+
|
| 957 |
+
def all_neighbors(graph, node):
|
| 958 |
+
"""Returns all of the neighbors of a node in the graph.
|
| 959 |
+
|
| 960 |
+
If the graph is directed returns predecessors as well as successors.
|
| 961 |
+
|
| 962 |
+
Parameters
|
| 963 |
+
----------
|
| 964 |
+
graph : NetworkX graph
|
| 965 |
+
Graph to find neighbors.
|
| 966 |
+
|
| 967 |
+
node : node
|
| 968 |
+
The node whose neighbors will be returned.
|
| 969 |
+
|
| 970 |
+
Returns
|
| 971 |
+
-------
|
| 972 |
+
neighbors : iterator
|
| 973 |
+
Iterator of neighbors
|
| 974 |
+
"""
|
| 975 |
+
if graph.is_directed():
|
| 976 |
+
values = chain(graph.predecessors(node), graph.successors(node))
|
| 977 |
+
else:
|
| 978 |
+
values = graph.neighbors(node)
|
| 979 |
+
return values
|
| 980 |
+
|
| 981 |
+
|
| 982 |
+
def non_neighbors(graph, node):
|
| 983 |
+
"""Returns the non-neighbors of the node in the graph.
|
| 984 |
+
|
| 985 |
+
Parameters
|
| 986 |
+
----------
|
| 987 |
+
graph : NetworkX graph
|
| 988 |
+
Graph to find neighbors.
|
| 989 |
+
|
| 990 |
+
node : node
|
| 991 |
+
The node whose neighbors will be returned.
|
| 992 |
+
|
| 993 |
+
Returns
|
| 994 |
+
-------
|
| 995 |
+
non_neighbors : set
|
| 996 |
+
Set of nodes in the graph that are not neighbors of the node.
|
| 997 |
+
"""
|
| 998 |
+
return graph._adj.keys() - graph._adj[node].keys() - {node}
|
| 999 |
+
|
| 1000 |
+
|
| 1001 |
+
def non_edges(graph):
|
| 1002 |
+
"""Returns the nonexistent edges in the graph.
|
| 1003 |
+
|
| 1004 |
+
Parameters
|
| 1005 |
+
----------
|
| 1006 |
+
graph : NetworkX graph.
|
| 1007 |
+
Graph to find nonexistent edges.
|
| 1008 |
+
|
| 1009 |
+
Returns
|
| 1010 |
+
-------
|
| 1011 |
+
non_edges : iterator
|
| 1012 |
+
Iterator of edges that are not in the graph.
|
| 1013 |
+
"""
|
| 1014 |
+
if graph.is_directed():
|
| 1015 |
+
for u in graph:
|
| 1016 |
+
for v in non_neighbors(graph, u):
|
| 1017 |
+
yield (u, v)
|
| 1018 |
+
else:
|
| 1019 |
+
nodes = set(graph)
|
| 1020 |
+
while nodes:
|
| 1021 |
+
u = nodes.pop()
|
| 1022 |
+
for v in nodes - set(graph[u]):
|
| 1023 |
+
yield (u, v)
|
| 1024 |
+
|
| 1025 |
+
|
| 1026 |
+
@not_implemented_for("directed")
|
| 1027 |
+
def common_neighbors(G, u, v):
|
| 1028 |
+
"""Returns the common neighbors of two nodes in a graph.
|
| 1029 |
+
|
| 1030 |
+
Parameters
|
| 1031 |
+
----------
|
| 1032 |
+
G : graph
|
| 1033 |
+
A NetworkX undirected graph.
|
| 1034 |
+
|
| 1035 |
+
u, v : nodes
|
| 1036 |
+
Nodes in the graph.
|
| 1037 |
+
|
| 1038 |
+
Returns
|
| 1039 |
+
-------
|
| 1040 |
+
cnbors : set
|
| 1041 |
+
Set of common neighbors of u and v in the graph.
|
| 1042 |
+
|
| 1043 |
+
Raises
|
| 1044 |
+
------
|
| 1045 |
+
NetworkXError
|
| 1046 |
+
If u or v is not a node in the graph.
|
| 1047 |
+
|
| 1048 |
+
Examples
|
| 1049 |
+
--------
|
| 1050 |
+
>>> G = nx.complete_graph(5)
|
| 1051 |
+
>>> sorted(nx.common_neighbors(G, 0, 1))
|
| 1052 |
+
[2, 3, 4]
|
| 1053 |
+
"""
|
| 1054 |
+
if u not in G:
|
| 1055 |
+
raise nx.NetworkXError("u is not in the graph.")
|
| 1056 |
+
if v not in G:
|
| 1057 |
+
raise nx.NetworkXError("v is not in the graph.")
|
| 1058 |
+
|
| 1059 |
+
return G._adj[u].keys() & G._adj[v].keys() - {u, v}
|
| 1060 |
+
|
| 1061 |
+
|
| 1062 |
+
def is_weighted(G, edge=None, weight="weight"):
|
| 1063 |
+
"""Returns True if `G` has weighted edges.
|
| 1064 |
+
|
| 1065 |
+
Parameters
|
| 1066 |
+
----------
|
| 1067 |
+
G : graph
|
| 1068 |
+
A NetworkX graph.
|
| 1069 |
+
|
| 1070 |
+
edge : tuple, optional
|
| 1071 |
+
A 2-tuple specifying the only edge in `G` that will be tested. If
|
| 1072 |
+
None, then every edge in `G` is tested.
|
| 1073 |
+
|
| 1074 |
+
weight: string, optional
|
| 1075 |
+
The attribute name used to query for edge weights.
|
| 1076 |
+
|
| 1077 |
+
Returns
|
| 1078 |
+
-------
|
| 1079 |
+
bool
|
| 1080 |
+
A boolean signifying if `G`, or the specified edge, is weighted.
|
| 1081 |
+
|
| 1082 |
+
Raises
|
| 1083 |
+
------
|
| 1084 |
+
NetworkXError
|
| 1085 |
+
If the specified edge does not exist.
|
| 1086 |
+
|
| 1087 |
+
Examples
|
| 1088 |
+
--------
|
| 1089 |
+
>>> G = nx.path_graph(4)
|
| 1090 |
+
>>> nx.is_weighted(G)
|
| 1091 |
+
False
|
| 1092 |
+
>>> nx.is_weighted(G, (2, 3))
|
| 1093 |
+
False
|
| 1094 |
+
|
| 1095 |
+
>>> G = nx.DiGraph()
|
| 1096 |
+
>>> G.add_edge(1, 2, weight=1)
|
| 1097 |
+
>>> nx.is_weighted(G)
|
| 1098 |
+
True
|
| 1099 |
+
|
| 1100 |
+
"""
|
| 1101 |
+
if edge is not None:
|
| 1102 |
+
data = G.get_edge_data(*edge)
|
| 1103 |
+
if data is None:
|
| 1104 |
+
msg = f"Edge {edge!r} does not exist."
|
| 1105 |
+
raise nx.NetworkXError(msg)
|
| 1106 |
+
return weight in data
|
| 1107 |
+
|
| 1108 |
+
if is_empty(G):
|
| 1109 |
+
# Special handling required since: all([]) == True
|
| 1110 |
+
return False
|
| 1111 |
+
|
| 1112 |
+
return all(weight in data for u, v, data in G.edges(data=True))
|
| 1113 |
+
|
| 1114 |
+
|
| 1115 |
+
@nx._dispatchable(edge_attrs="weight")
|
| 1116 |
+
def is_negatively_weighted(G, edge=None, weight="weight"):
|
| 1117 |
+
"""Returns True if `G` has negatively weighted edges.
|
| 1118 |
+
|
| 1119 |
+
Parameters
|
| 1120 |
+
----------
|
| 1121 |
+
G : graph
|
| 1122 |
+
A NetworkX graph.
|
| 1123 |
+
|
| 1124 |
+
edge : tuple, optional
|
| 1125 |
+
A 2-tuple specifying the only edge in `G` that will be tested. If
|
| 1126 |
+
None, then every edge in `G` is tested.
|
| 1127 |
+
|
| 1128 |
+
weight: string, optional
|
| 1129 |
+
The attribute name used to query for edge weights.
|
| 1130 |
+
|
| 1131 |
+
Returns
|
| 1132 |
+
-------
|
| 1133 |
+
bool
|
| 1134 |
+
A boolean signifying if `G`, or the specified edge, is negatively
|
| 1135 |
+
weighted.
|
| 1136 |
+
|
| 1137 |
+
Raises
|
| 1138 |
+
------
|
| 1139 |
+
NetworkXError
|
| 1140 |
+
If the specified edge does not exist.
|
| 1141 |
+
|
| 1142 |
+
Examples
|
| 1143 |
+
--------
|
| 1144 |
+
>>> G = nx.Graph()
|
| 1145 |
+
>>> G.add_edges_from([(1, 3), (2, 4), (2, 6)])
|
| 1146 |
+
>>> G.add_edge(1, 2, weight=4)
|
| 1147 |
+
>>> nx.is_negatively_weighted(G, (1, 2))
|
| 1148 |
+
False
|
| 1149 |
+
>>> G[2][4]["weight"] = -2
|
| 1150 |
+
>>> nx.is_negatively_weighted(G)
|
| 1151 |
+
True
|
| 1152 |
+
>>> G = nx.DiGraph()
|
| 1153 |
+
>>> edges = [("0", "3", 3), ("0", "1", -5), ("1", "0", -2)]
|
| 1154 |
+
>>> G.add_weighted_edges_from(edges)
|
| 1155 |
+
>>> nx.is_negatively_weighted(G)
|
| 1156 |
+
True
|
| 1157 |
+
|
| 1158 |
+
"""
|
| 1159 |
+
if edge is not None:
|
| 1160 |
+
data = G.get_edge_data(*edge)
|
| 1161 |
+
if data is None:
|
| 1162 |
+
msg = f"Edge {edge!r} does not exist."
|
| 1163 |
+
raise nx.NetworkXError(msg)
|
| 1164 |
+
return weight in data and data[weight] < 0
|
| 1165 |
+
|
| 1166 |
+
return any(weight in data and data[weight] < 0 for u, v, data in G.edges(data=True))
|
| 1167 |
+
|
| 1168 |
+
|
| 1169 |
+
def is_empty(G):
|
| 1170 |
+
"""Returns True if `G` has no edges.
|
| 1171 |
+
|
| 1172 |
+
Parameters
|
| 1173 |
+
----------
|
| 1174 |
+
G : graph
|
| 1175 |
+
A NetworkX graph.
|
| 1176 |
+
|
| 1177 |
+
Returns
|
| 1178 |
+
-------
|
| 1179 |
+
bool
|
| 1180 |
+
True if `G` has no edges, and False otherwise.
|
| 1181 |
+
|
| 1182 |
+
Notes
|
| 1183 |
+
-----
|
| 1184 |
+
An empty graph can have nodes but not edges. The empty graph with zero
|
| 1185 |
+
nodes is known as the null graph. This is an $O(n)$ operation where n
|
| 1186 |
+
is the number of nodes in the graph.
|
| 1187 |
+
|
| 1188 |
+
"""
|
| 1189 |
+
return not any(G._adj.values())
|
| 1190 |
+
|
| 1191 |
+
|
| 1192 |
+
def nodes_with_selfloops(G):
|
| 1193 |
+
"""Returns an iterator over nodes with self loops.
|
| 1194 |
+
|
| 1195 |
+
A node with a self loop has an edge with both ends adjacent
|
| 1196 |
+
to that node.
|
| 1197 |
+
|
| 1198 |
+
Returns
|
| 1199 |
+
-------
|
| 1200 |
+
nodelist : iterator
|
| 1201 |
+
A iterator over nodes with self loops.
|
| 1202 |
+
|
| 1203 |
+
See Also
|
| 1204 |
+
--------
|
| 1205 |
+
selfloop_edges, number_of_selfloops
|
| 1206 |
+
|
| 1207 |
+
Examples
|
| 1208 |
+
--------
|
| 1209 |
+
>>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 1210 |
+
>>> G.add_edge(1, 1)
|
| 1211 |
+
>>> G.add_edge(1, 2)
|
| 1212 |
+
>>> list(nx.nodes_with_selfloops(G))
|
| 1213 |
+
[1]
|
| 1214 |
+
|
| 1215 |
+
"""
|
| 1216 |
+
return (n for n, nbrs in G._adj.items() if n in nbrs)
|
| 1217 |
+
|
| 1218 |
+
|
| 1219 |
+
def selfloop_edges(G, data=False, keys=False, default=None):
|
| 1220 |
+
"""Returns an iterator over selfloop edges.
|
| 1221 |
+
|
| 1222 |
+
A selfloop edge has the same node at both ends.
|
| 1223 |
+
|
| 1224 |
+
Parameters
|
| 1225 |
+
----------
|
| 1226 |
+
G : graph
|
| 1227 |
+
A NetworkX graph.
|
| 1228 |
+
data : string or bool, optional (default=False)
|
| 1229 |
+
Return selfloop edges as two tuples (u, v) (data=False)
|
| 1230 |
+
or three-tuples (u, v, datadict) (data=True)
|
| 1231 |
+
or three-tuples (u, v, datavalue) (data='attrname')
|
| 1232 |
+
keys : bool, optional (default=False)
|
| 1233 |
+
If True, return edge keys with each edge.
|
| 1234 |
+
default : value, optional (default=None)
|
| 1235 |
+
Value used for edges that don't have the requested attribute.
|
| 1236 |
+
Only relevant if data is not True or False.
|
| 1237 |
+
|
| 1238 |
+
Returns
|
| 1239 |
+
-------
|
| 1240 |
+
edgeiter : iterator over edge tuples
|
| 1241 |
+
An iterator over all selfloop edges.
|
| 1242 |
+
|
| 1243 |
+
See Also
|
| 1244 |
+
--------
|
| 1245 |
+
nodes_with_selfloops, number_of_selfloops
|
| 1246 |
+
|
| 1247 |
+
Examples
|
| 1248 |
+
--------
|
| 1249 |
+
>>> G = nx.MultiGraph() # or Graph, DiGraph, MultiDiGraph, etc
|
| 1250 |
+
>>> ekey = G.add_edge(1, 1)
|
| 1251 |
+
>>> ekey = G.add_edge(1, 2)
|
| 1252 |
+
>>> list(nx.selfloop_edges(G))
|
| 1253 |
+
[(1, 1)]
|
| 1254 |
+
>>> list(nx.selfloop_edges(G, data=True))
|
| 1255 |
+
[(1, 1, {})]
|
| 1256 |
+
>>> list(nx.selfloop_edges(G, keys=True))
|
| 1257 |
+
[(1, 1, 0)]
|
| 1258 |
+
>>> list(nx.selfloop_edges(G, keys=True, data=True))
|
| 1259 |
+
[(1, 1, 0, {})]
|
| 1260 |
+
"""
|
| 1261 |
+
if data is True:
|
| 1262 |
+
if G.is_multigraph():
|
| 1263 |
+
if keys is True:
|
| 1264 |
+
return (
|
| 1265 |
+
(n, n, k, d)
|
| 1266 |
+
for n, nbrs in G._adj.items()
|
| 1267 |
+
if n in nbrs
|
| 1268 |
+
for k, d in nbrs[n].items()
|
| 1269 |
+
)
|
| 1270 |
+
else:
|
| 1271 |
+
return (
|
| 1272 |
+
(n, n, d)
|
| 1273 |
+
for n, nbrs in G._adj.items()
|
| 1274 |
+
if n in nbrs
|
| 1275 |
+
for d in nbrs[n].values()
|
| 1276 |
+
)
|
| 1277 |
+
else:
|
| 1278 |
+
return ((n, n, nbrs[n]) for n, nbrs in G._adj.items() if n in nbrs)
|
| 1279 |
+
elif data is not False:
|
| 1280 |
+
if G.is_multigraph():
|
| 1281 |
+
if keys is True:
|
| 1282 |
+
return (
|
| 1283 |
+
(n, n, k, d.get(data, default))
|
| 1284 |
+
for n, nbrs in G._adj.items()
|
| 1285 |
+
if n in nbrs
|
| 1286 |
+
for k, d in nbrs[n].items()
|
| 1287 |
+
)
|
| 1288 |
+
else:
|
| 1289 |
+
return (
|
| 1290 |
+
(n, n, d.get(data, default))
|
| 1291 |
+
for n, nbrs in G._adj.items()
|
| 1292 |
+
if n in nbrs
|
| 1293 |
+
for d in nbrs[n].values()
|
| 1294 |
+
)
|
| 1295 |
+
else:
|
| 1296 |
+
return (
|
| 1297 |
+
(n, n, nbrs[n].get(data, default))
|
| 1298 |
+
for n, nbrs in G._adj.items()
|
| 1299 |
+
if n in nbrs
|
| 1300 |
+
)
|
| 1301 |
+
else:
|
| 1302 |
+
if G.is_multigraph():
|
| 1303 |
+
if keys is True:
|
| 1304 |
+
return (
|
| 1305 |
+
(n, n, k)
|
| 1306 |
+
for n, nbrs in G._adj.items()
|
| 1307 |
+
if n in nbrs
|
| 1308 |
+
for k in nbrs[n]
|
| 1309 |
+
)
|
| 1310 |
+
else:
|
| 1311 |
+
return (
|
| 1312 |
+
(n, n)
|
| 1313 |
+
for n, nbrs in G._adj.items()
|
| 1314 |
+
if n in nbrs
|
| 1315 |
+
for i in range(len(nbrs[n])) # for easy edge removal (#4068)
|
| 1316 |
+
)
|
| 1317 |
+
else:
|
| 1318 |
+
return ((n, n) for n, nbrs in G._adj.items() if n in nbrs)
|
| 1319 |
+
|
| 1320 |
+
|
| 1321 |
+
def number_of_selfloops(G):
|
| 1322 |
+
"""Returns the number of selfloop edges.
|
| 1323 |
+
|
| 1324 |
+
A selfloop edge has the same node at both ends.
|
| 1325 |
+
|
| 1326 |
+
Returns
|
| 1327 |
+
-------
|
| 1328 |
+
nloops : int
|
| 1329 |
+
The number of selfloops.
|
| 1330 |
+
|
| 1331 |
+
See Also
|
| 1332 |
+
--------
|
| 1333 |
+
nodes_with_selfloops, selfloop_edges
|
| 1334 |
+
|
| 1335 |
+
Examples
|
| 1336 |
+
--------
|
| 1337 |
+
>>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 1338 |
+
>>> G.add_edge(1, 1)
|
| 1339 |
+
>>> G.add_edge(1, 2)
|
| 1340 |
+
>>> nx.number_of_selfloops(G)
|
| 1341 |
+
1
|
| 1342 |
+
"""
|
| 1343 |
+
return sum(1 for _ in nx.selfloop_edges(G))
|
| 1344 |
+
|
| 1345 |
+
|
| 1346 |
+
def is_path(G, path):
|
| 1347 |
+
"""Returns whether or not the specified path exists.
|
| 1348 |
+
|
| 1349 |
+
For it to return True, every node on the path must exist and
|
| 1350 |
+
each consecutive pair must be connected via one or more edges.
|
| 1351 |
+
|
| 1352 |
+
Parameters
|
| 1353 |
+
----------
|
| 1354 |
+
G : graph
|
| 1355 |
+
A NetworkX graph.
|
| 1356 |
+
|
| 1357 |
+
path : list
|
| 1358 |
+
A list of nodes which defines the path to traverse
|
| 1359 |
+
|
| 1360 |
+
Returns
|
| 1361 |
+
-------
|
| 1362 |
+
bool
|
| 1363 |
+
True if `path` is a valid path in `G`
|
| 1364 |
+
|
| 1365 |
+
"""
|
| 1366 |
+
try:
|
| 1367 |
+
return all(nbr in G._adj[node] for node, nbr in nx.utils.pairwise(path))
|
| 1368 |
+
except (KeyError, TypeError):
|
| 1369 |
+
return False
|
| 1370 |
+
|
| 1371 |
+
|
| 1372 |
+
def path_weight(G, path, weight):
|
| 1373 |
+
"""Returns total cost associated with specified path and weight
|
| 1374 |
+
|
| 1375 |
+
Parameters
|
| 1376 |
+
----------
|
| 1377 |
+
G : graph
|
| 1378 |
+
A NetworkX graph.
|
| 1379 |
+
|
| 1380 |
+
path: list
|
| 1381 |
+
A list of node labels which defines the path to traverse
|
| 1382 |
+
|
| 1383 |
+
weight: string
|
| 1384 |
+
A string indicating which edge attribute to use for path cost
|
| 1385 |
+
|
| 1386 |
+
Returns
|
| 1387 |
+
-------
|
| 1388 |
+
cost: int or float
|
| 1389 |
+
An integer or a float representing the total cost with respect to the
|
| 1390 |
+
specified weight of the specified path
|
| 1391 |
+
|
| 1392 |
+
Raises
|
| 1393 |
+
------
|
| 1394 |
+
NetworkXNoPath
|
| 1395 |
+
If the specified edge does not exist.
|
| 1396 |
+
"""
|
| 1397 |
+
multigraph = G.is_multigraph()
|
| 1398 |
+
cost = 0
|
| 1399 |
+
|
| 1400 |
+
if not nx.is_path(G, path):
|
| 1401 |
+
raise nx.NetworkXNoPath("path does not exist")
|
| 1402 |
+
for node, nbr in nx.utils.pairwise(path):
|
| 1403 |
+
if multigraph:
|
| 1404 |
+
cost += min(v[weight] for v in G._adj[node][nbr].values())
|
| 1405 |
+
else:
|
| 1406 |
+
cost += G._adj[node][nbr][weight]
|
| 1407 |
+
return cost
|
.venv/lib/python3.11/site-packages/networkx/classes/graph.py
ADDED
|
@@ -0,0 +1,2058 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Base class for undirected graphs.
|
| 2 |
+
|
| 3 |
+
The Graph class allows any hashable object as a node
|
| 4 |
+
and can associate key/value attribute pairs with each undirected edge.
|
| 5 |
+
|
| 6 |
+
Self-loops are allowed but multiple edges are not (see MultiGraph).
|
| 7 |
+
|
| 8 |
+
For directed graphs see DiGraph and MultiDiGraph.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
from copy import deepcopy
|
| 12 |
+
from functools import cached_property
|
| 13 |
+
|
| 14 |
+
import networkx as nx
|
| 15 |
+
from networkx import convert
|
| 16 |
+
from networkx.classes.coreviews import AdjacencyView
|
| 17 |
+
from networkx.classes.reportviews import DegreeView, EdgeView, NodeView
|
| 18 |
+
from networkx.exception import NetworkXError
|
| 19 |
+
|
| 20 |
+
__all__ = ["Graph"]
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
class _CachedPropertyResetterAdj:
|
| 24 |
+
"""Data Descriptor class for _adj that resets ``adj`` cached_property when needed
|
| 25 |
+
|
| 26 |
+
This assumes that the ``cached_property`` ``G.adj`` should be reset whenever
|
| 27 |
+
``G._adj`` is set to a new value.
|
| 28 |
+
|
| 29 |
+
This object sits on a class and ensures that any instance of that
|
| 30 |
+
class clears its cached property "adj" whenever the underlying
|
| 31 |
+
instance attribute "_adj" is set to a new object. It only affects
|
| 32 |
+
the set process of the obj._adj attribute. All get/del operations
|
| 33 |
+
act as they normally would.
|
| 34 |
+
|
| 35 |
+
For info on Data Descriptors see: https://docs.python.org/3/howto/descriptor.html
|
| 36 |
+
"""
|
| 37 |
+
|
| 38 |
+
def __set__(self, obj, value):
|
| 39 |
+
od = obj.__dict__
|
| 40 |
+
od["_adj"] = value
|
| 41 |
+
# reset cached properties
|
| 42 |
+
props = ["adj", "edges", "degree"]
|
| 43 |
+
for prop in props:
|
| 44 |
+
if prop in od:
|
| 45 |
+
del od[prop]
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
class _CachedPropertyResetterNode:
|
| 49 |
+
"""Data Descriptor class for _node that resets ``nodes`` cached_property when needed
|
| 50 |
+
|
| 51 |
+
This assumes that the ``cached_property`` ``G.node`` should be reset whenever
|
| 52 |
+
``G._node`` is set to a new value.
|
| 53 |
+
|
| 54 |
+
This object sits on a class and ensures that any instance of that
|
| 55 |
+
class clears its cached property "nodes" whenever the underlying
|
| 56 |
+
instance attribute "_node" is set to a new object. It only affects
|
| 57 |
+
the set process of the obj._adj attribute. All get/del operations
|
| 58 |
+
act as they normally would.
|
| 59 |
+
|
| 60 |
+
For info on Data Descriptors see: https://docs.python.org/3/howto/descriptor.html
|
| 61 |
+
"""
|
| 62 |
+
|
| 63 |
+
def __set__(self, obj, value):
|
| 64 |
+
od = obj.__dict__
|
| 65 |
+
od["_node"] = value
|
| 66 |
+
# reset cached properties
|
| 67 |
+
if "nodes" in od:
|
| 68 |
+
del od["nodes"]
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
class Graph:
|
| 72 |
+
"""
|
| 73 |
+
Base class for undirected graphs.
|
| 74 |
+
|
| 75 |
+
A Graph stores nodes and edges with optional data, or attributes.
|
| 76 |
+
|
| 77 |
+
Graphs hold undirected edges. Self loops are allowed but multiple
|
| 78 |
+
(parallel) edges are not.
|
| 79 |
+
|
| 80 |
+
Nodes can be arbitrary (hashable) Python objects with optional
|
| 81 |
+
key/value attributes, except that `None` is not allowed as a node.
|
| 82 |
+
|
| 83 |
+
Edges are represented as links between nodes with optional
|
| 84 |
+
key/value attributes.
|
| 85 |
+
|
| 86 |
+
Parameters
|
| 87 |
+
----------
|
| 88 |
+
incoming_graph_data : input graph (optional, default: None)
|
| 89 |
+
Data to initialize graph. If None (default) an empty
|
| 90 |
+
graph is created. The data can be any format that is supported
|
| 91 |
+
by the to_networkx_graph() function, currently including edge list,
|
| 92 |
+
dict of dicts, dict of lists, NetworkX graph, 2D NumPy array, SciPy
|
| 93 |
+
sparse matrix, or PyGraphviz graph.
|
| 94 |
+
|
| 95 |
+
attr : keyword arguments, optional (default= no attributes)
|
| 96 |
+
Attributes to add to graph as key=value pairs.
|
| 97 |
+
|
| 98 |
+
See Also
|
| 99 |
+
--------
|
| 100 |
+
DiGraph
|
| 101 |
+
MultiGraph
|
| 102 |
+
MultiDiGraph
|
| 103 |
+
|
| 104 |
+
Examples
|
| 105 |
+
--------
|
| 106 |
+
Create an empty graph structure (a "null graph") with no nodes and
|
| 107 |
+
no edges.
|
| 108 |
+
|
| 109 |
+
>>> G = nx.Graph()
|
| 110 |
+
|
| 111 |
+
G can be grown in several ways.
|
| 112 |
+
|
| 113 |
+
**Nodes:**
|
| 114 |
+
|
| 115 |
+
Add one node at a time:
|
| 116 |
+
|
| 117 |
+
>>> G.add_node(1)
|
| 118 |
+
|
| 119 |
+
Add the nodes from any container (a list, dict, set or
|
| 120 |
+
even the lines from a file or the nodes from another graph).
|
| 121 |
+
|
| 122 |
+
>>> G.add_nodes_from([2, 3])
|
| 123 |
+
>>> G.add_nodes_from(range(100, 110))
|
| 124 |
+
>>> H = nx.path_graph(10)
|
| 125 |
+
>>> G.add_nodes_from(H)
|
| 126 |
+
|
| 127 |
+
In addition to strings and integers any hashable Python object
|
| 128 |
+
(except None) can represent a node, e.g. a customized node object,
|
| 129 |
+
or even another Graph.
|
| 130 |
+
|
| 131 |
+
>>> G.add_node(H)
|
| 132 |
+
|
| 133 |
+
**Edges:**
|
| 134 |
+
|
| 135 |
+
G can also be grown by adding edges.
|
| 136 |
+
|
| 137 |
+
Add one edge,
|
| 138 |
+
|
| 139 |
+
>>> G.add_edge(1, 2)
|
| 140 |
+
|
| 141 |
+
a list of edges,
|
| 142 |
+
|
| 143 |
+
>>> G.add_edges_from([(1, 2), (1, 3)])
|
| 144 |
+
|
| 145 |
+
or a collection of edges,
|
| 146 |
+
|
| 147 |
+
>>> G.add_edges_from(H.edges)
|
| 148 |
+
|
| 149 |
+
If some edges connect nodes not yet in the graph, the nodes
|
| 150 |
+
are added automatically. There are no errors when adding
|
| 151 |
+
nodes or edges that already exist.
|
| 152 |
+
|
| 153 |
+
**Attributes:**
|
| 154 |
+
|
| 155 |
+
Each graph, node, and edge can hold key/value attribute pairs
|
| 156 |
+
in an associated attribute dictionary (the keys must be hashable).
|
| 157 |
+
By default these are empty, but can be added or changed using
|
| 158 |
+
add_edge, add_node or direct manipulation of the attribute
|
| 159 |
+
dictionaries named graph, node and edge respectively.
|
| 160 |
+
|
| 161 |
+
>>> G = nx.Graph(day="Friday")
|
| 162 |
+
>>> G.graph
|
| 163 |
+
{'day': 'Friday'}
|
| 164 |
+
|
| 165 |
+
Add node attributes using add_node(), add_nodes_from() or G.nodes
|
| 166 |
+
|
| 167 |
+
>>> G.add_node(1, time="5pm")
|
| 168 |
+
>>> G.add_nodes_from([3], time="2pm")
|
| 169 |
+
>>> G.nodes[1]
|
| 170 |
+
{'time': '5pm'}
|
| 171 |
+
>>> G.nodes[1]["room"] = 714 # node must exist already to use G.nodes
|
| 172 |
+
>>> del G.nodes[1]["room"] # remove attribute
|
| 173 |
+
>>> list(G.nodes(data=True))
|
| 174 |
+
[(1, {'time': '5pm'}), (3, {'time': '2pm'})]
|
| 175 |
+
|
| 176 |
+
Add edge attributes using add_edge(), add_edges_from(), subscript
|
| 177 |
+
notation, or G.edges.
|
| 178 |
+
|
| 179 |
+
>>> G.add_edge(1, 2, weight=4.7)
|
| 180 |
+
>>> G.add_edges_from([(3, 4), (4, 5)], color="red")
|
| 181 |
+
>>> G.add_edges_from([(1, 2, {"color": "blue"}), (2, 3, {"weight": 8})])
|
| 182 |
+
>>> G[1][2]["weight"] = 4.7
|
| 183 |
+
>>> G.edges[1, 2]["weight"] = 4
|
| 184 |
+
|
| 185 |
+
Warning: we protect the graph data structure by making `G.edges` a
|
| 186 |
+
read-only dict-like structure. However, you can assign to attributes
|
| 187 |
+
in e.g. `G.edges[1, 2]`. Thus, use 2 sets of brackets to add/change
|
| 188 |
+
data attributes: `G.edges[1, 2]['weight'] = 4`
|
| 189 |
+
(For multigraphs: `MG.edges[u, v, key][name] = value`).
|
| 190 |
+
|
| 191 |
+
**Shortcuts:**
|
| 192 |
+
|
| 193 |
+
Many common graph features allow python syntax to speed reporting.
|
| 194 |
+
|
| 195 |
+
>>> 1 in G # check if node in graph
|
| 196 |
+
True
|
| 197 |
+
>>> [n for n in G if n < 3] # iterate through nodes
|
| 198 |
+
[1, 2]
|
| 199 |
+
>>> len(G) # number of nodes in graph
|
| 200 |
+
5
|
| 201 |
+
|
| 202 |
+
Often the best way to traverse all edges of a graph is via the neighbors.
|
| 203 |
+
The neighbors are reported as an adjacency-dict `G.adj` or `G.adjacency()`
|
| 204 |
+
|
| 205 |
+
>>> for n, nbrsdict in G.adjacency():
|
| 206 |
+
... for nbr, eattr in nbrsdict.items():
|
| 207 |
+
... if "weight" in eattr:
|
| 208 |
+
... # Do something useful with the edges
|
| 209 |
+
... pass
|
| 210 |
+
|
| 211 |
+
But the edges() method is often more convenient:
|
| 212 |
+
|
| 213 |
+
>>> for u, v, weight in G.edges.data("weight"):
|
| 214 |
+
... if weight is not None:
|
| 215 |
+
... # Do something useful with the edges
|
| 216 |
+
... pass
|
| 217 |
+
|
| 218 |
+
**Reporting:**
|
| 219 |
+
|
| 220 |
+
Simple graph information is obtained using object-attributes and methods.
|
| 221 |
+
Reporting typically provides views instead of containers to reduce memory
|
| 222 |
+
usage. The views update as the graph is updated similarly to dict-views.
|
| 223 |
+
The objects `nodes`, `edges` and `adj` provide access to data attributes
|
| 224 |
+
via lookup (e.g. `nodes[n]`, `edges[u, v]`, `adj[u][v]`) and iteration
|
| 225 |
+
(e.g. `nodes.items()`, `nodes.data('color')`,
|
| 226 |
+
`nodes.data('color', default='blue')` and similarly for `edges`)
|
| 227 |
+
Views exist for `nodes`, `edges`, `neighbors()`/`adj` and `degree`.
|
| 228 |
+
|
| 229 |
+
For details on these and other miscellaneous methods, see below.
|
| 230 |
+
|
| 231 |
+
**Subclasses (Advanced):**
|
| 232 |
+
|
| 233 |
+
The Graph class uses a dict-of-dict-of-dict data structure.
|
| 234 |
+
The outer dict (node_dict) holds adjacency information keyed by node.
|
| 235 |
+
The next dict (adjlist_dict) represents the adjacency information and holds
|
| 236 |
+
edge data keyed by neighbor. The inner dict (edge_attr_dict) represents
|
| 237 |
+
the edge data and holds edge attribute values keyed by attribute names.
|
| 238 |
+
|
| 239 |
+
Each of these three dicts can be replaced in a subclass by a user defined
|
| 240 |
+
dict-like object. In general, the dict-like features should be
|
| 241 |
+
maintained but extra features can be added. To replace one of the
|
| 242 |
+
dicts create a new graph class by changing the class(!) variable
|
| 243 |
+
holding the factory for that dict-like structure.
|
| 244 |
+
|
| 245 |
+
node_dict_factory : function, (default: dict)
|
| 246 |
+
Factory function to be used to create the dict containing node
|
| 247 |
+
attributes, keyed by node id.
|
| 248 |
+
It should require no arguments and return a dict-like object
|
| 249 |
+
|
| 250 |
+
node_attr_dict_factory: function, (default: dict)
|
| 251 |
+
Factory function to be used to create the node attribute
|
| 252 |
+
dict which holds attribute values keyed by attribute name.
|
| 253 |
+
It should require no arguments and return a dict-like object
|
| 254 |
+
|
| 255 |
+
adjlist_outer_dict_factory : function, (default: dict)
|
| 256 |
+
Factory function to be used to create the outer-most dict
|
| 257 |
+
in the data structure that holds adjacency info keyed by node.
|
| 258 |
+
It should require no arguments and return a dict-like object.
|
| 259 |
+
|
| 260 |
+
adjlist_inner_dict_factory : function, (default: dict)
|
| 261 |
+
Factory function to be used to create the adjacency list
|
| 262 |
+
dict which holds edge data keyed by neighbor.
|
| 263 |
+
It should require no arguments and return a dict-like object
|
| 264 |
+
|
| 265 |
+
edge_attr_dict_factory : function, (default: dict)
|
| 266 |
+
Factory function to be used to create the edge attribute
|
| 267 |
+
dict which holds attribute values keyed by attribute name.
|
| 268 |
+
It should require no arguments and return a dict-like object.
|
| 269 |
+
|
| 270 |
+
graph_attr_dict_factory : function, (default: dict)
|
| 271 |
+
Factory function to be used to create the graph attribute
|
| 272 |
+
dict which holds attribute values keyed by attribute name.
|
| 273 |
+
It should require no arguments and return a dict-like object.
|
| 274 |
+
|
| 275 |
+
Typically, if your extension doesn't impact the data structure all
|
| 276 |
+
methods will inherit without issue except: `to_directed/to_undirected`.
|
| 277 |
+
By default these methods create a DiGraph/Graph class and you probably
|
| 278 |
+
want them to create your extension of a DiGraph/Graph. To facilitate
|
| 279 |
+
this we define two class variables that you can set in your subclass.
|
| 280 |
+
|
| 281 |
+
to_directed_class : callable, (default: DiGraph or MultiDiGraph)
|
| 282 |
+
Class to create a new graph structure in the `to_directed` method.
|
| 283 |
+
If `None`, a NetworkX class (DiGraph or MultiDiGraph) is used.
|
| 284 |
+
|
| 285 |
+
to_undirected_class : callable, (default: Graph or MultiGraph)
|
| 286 |
+
Class to create a new graph structure in the `to_undirected` method.
|
| 287 |
+
If `None`, a NetworkX class (Graph or MultiGraph) is used.
|
| 288 |
+
|
| 289 |
+
**Subclassing Example**
|
| 290 |
+
|
| 291 |
+
Create a low memory graph class that effectively disallows edge
|
| 292 |
+
attributes by using a single attribute dict for all edges.
|
| 293 |
+
This reduces the memory used, but you lose edge attributes.
|
| 294 |
+
|
| 295 |
+
>>> class ThinGraph(nx.Graph):
|
| 296 |
+
... all_edge_dict = {"weight": 1}
|
| 297 |
+
...
|
| 298 |
+
... def single_edge_dict(self):
|
| 299 |
+
... return self.all_edge_dict
|
| 300 |
+
...
|
| 301 |
+
... edge_attr_dict_factory = single_edge_dict
|
| 302 |
+
>>> G = ThinGraph()
|
| 303 |
+
>>> G.add_edge(2, 1)
|
| 304 |
+
>>> G[2][1]
|
| 305 |
+
{'weight': 1}
|
| 306 |
+
>>> G.add_edge(2, 2)
|
| 307 |
+
>>> G[2][1] is G[2][2]
|
| 308 |
+
True
|
| 309 |
+
"""
|
| 310 |
+
|
| 311 |
+
__networkx_backend__ = "networkx"
|
| 312 |
+
|
| 313 |
+
_adj = _CachedPropertyResetterAdj()
|
| 314 |
+
_node = _CachedPropertyResetterNode()
|
| 315 |
+
|
| 316 |
+
node_dict_factory = dict
|
| 317 |
+
node_attr_dict_factory = dict
|
| 318 |
+
adjlist_outer_dict_factory = dict
|
| 319 |
+
adjlist_inner_dict_factory = dict
|
| 320 |
+
edge_attr_dict_factory = dict
|
| 321 |
+
graph_attr_dict_factory = dict
|
| 322 |
+
|
| 323 |
+
def to_directed_class(self):
|
| 324 |
+
"""Returns the class to use for empty directed copies.
|
| 325 |
+
|
| 326 |
+
If you subclass the base classes, use this to designate
|
| 327 |
+
what directed class to use for `to_directed()` copies.
|
| 328 |
+
"""
|
| 329 |
+
return nx.DiGraph
|
| 330 |
+
|
| 331 |
+
def to_undirected_class(self):
|
| 332 |
+
"""Returns the class to use for empty undirected copies.
|
| 333 |
+
|
| 334 |
+
If you subclass the base classes, use this to designate
|
| 335 |
+
what directed class to use for `to_directed()` copies.
|
| 336 |
+
"""
|
| 337 |
+
return Graph
|
| 338 |
+
|
| 339 |
+
def __init__(self, incoming_graph_data=None, **attr):
|
| 340 |
+
"""Initialize a graph with edges, name, or graph attributes.
|
| 341 |
+
|
| 342 |
+
Parameters
|
| 343 |
+
----------
|
| 344 |
+
incoming_graph_data : input graph (optional, default: None)
|
| 345 |
+
Data to initialize graph. If None (default) an empty
|
| 346 |
+
graph is created. The data can be an edge list, or any
|
| 347 |
+
NetworkX graph object. If the corresponding optional Python
|
| 348 |
+
packages are installed the data can also be a 2D NumPy array, a
|
| 349 |
+
SciPy sparse array, or a PyGraphviz graph.
|
| 350 |
+
|
| 351 |
+
attr : keyword arguments, optional (default= no attributes)
|
| 352 |
+
Attributes to add to graph as key=value pairs.
|
| 353 |
+
|
| 354 |
+
See Also
|
| 355 |
+
--------
|
| 356 |
+
convert
|
| 357 |
+
|
| 358 |
+
Examples
|
| 359 |
+
--------
|
| 360 |
+
>>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 361 |
+
>>> G = nx.Graph(name="my graph")
|
| 362 |
+
>>> e = [(1, 2), (2, 3), (3, 4)] # list of edges
|
| 363 |
+
>>> G = nx.Graph(e)
|
| 364 |
+
|
| 365 |
+
Arbitrary graph attribute pairs (key=value) may be assigned
|
| 366 |
+
|
| 367 |
+
>>> G = nx.Graph(e, day="Friday")
|
| 368 |
+
>>> G.graph
|
| 369 |
+
{'day': 'Friday'}
|
| 370 |
+
|
| 371 |
+
"""
|
| 372 |
+
self.graph = self.graph_attr_dict_factory() # dictionary for graph attributes
|
| 373 |
+
self._node = self.node_dict_factory() # empty node attribute dict
|
| 374 |
+
self._adj = self.adjlist_outer_dict_factory() # empty adjacency dict
|
| 375 |
+
self.__networkx_cache__ = {}
|
| 376 |
+
# attempt to load graph with data
|
| 377 |
+
if incoming_graph_data is not None:
|
| 378 |
+
convert.to_networkx_graph(incoming_graph_data, create_using=self)
|
| 379 |
+
# load graph attributes (must be after convert)
|
| 380 |
+
self.graph.update(attr)
|
| 381 |
+
|
| 382 |
+
@cached_property
|
| 383 |
+
def adj(self):
|
| 384 |
+
"""Graph adjacency object holding the neighbors of each node.
|
| 385 |
+
|
| 386 |
+
This object is a read-only dict-like structure with node keys
|
| 387 |
+
and neighbor-dict values. The neighbor-dict is keyed by neighbor
|
| 388 |
+
to the edge-data-dict. So `G.adj[3][2]['color'] = 'blue'` sets
|
| 389 |
+
the color of the edge `(3, 2)` to `"blue"`.
|
| 390 |
+
|
| 391 |
+
Iterating over G.adj behaves like a dict. Useful idioms include
|
| 392 |
+
`for nbr, datadict in G.adj[n].items():`.
|
| 393 |
+
|
| 394 |
+
The neighbor information is also provided by subscripting the graph.
|
| 395 |
+
So `for nbr, foovalue in G[node].data('foo', default=1):` works.
|
| 396 |
+
|
| 397 |
+
For directed graphs, `G.adj` holds outgoing (successor) info.
|
| 398 |
+
"""
|
| 399 |
+
return AdjacencyView(self._adj)
|
| 400 |
+
|
| 401 |
+
@property
|
| 402 |
+
def name(self):
|
| 403 |
+
"""String identifier of the graph.
|
| 404 |
+
|
| 405 |
+
This graph attribute appears in the attribute dict G.graph
|
| 406 |
+
keyed by the string `"name"`. as well as an attribute (technically
|
| 407 |
+
a property) `G.name`. This is entirely user controlled.
|
| 408 |
+
"""
|
| 409 |
+
return self.graph.get("name", "")
|
| 410 |
+
|
| 411 |
+
@name.setter
|
| 412 |
+
def name(self, s):
|
| 413 |
+
self.graph["name"] = s
|
| 414 |
+
nx._clear_cache(self)
|
| 415 |
+
|
| 416 |
+
def __str__(self):
|
| 417 |
+
"""Returns a short summary of the graph.
|
| 418 |
+
|
| 419 |
+
Returns
|
| 420 |
+
-------
|
| 421 |
+
info : string
|
| 422 |
+
Graph information including the graph name (if any), graph type, and the
|
| 423 |
+
number of nodes and edges.
|
| 424 |
+
|
| 425 |
+
Examples
|
| 426 |
+
--------
|
| 427 |
+
>>> G = nx.Graph(name="foo")
|
| 428 |
+
>>> str(G)
|
| 429 |
+
"Graph named 'foo' with 0 nodes and 0 edges"
|
| 430 |
+
|
| 431 |
+
>>> G = nx.path_graph(3)
|
| 432 |
+
>>> str(G)
|
| 433 |
+
'Graph with 3 nodes and 2 edges'
|
| 434 |
+
|
| 435 |
+
"""
|
| 436 |
+
return "".join(
|
| 437 |
+
[
|
| 438 |
+
type(self).__name__,
|
| 439 |
+
f" named {self.name!r}" if self.name else "",
|
| 440 |
+
f" with {self.number_of_nodes()} nodes and {self.number_of_edges()} edges",
|
| 441 |
+
]
|
| 442 |
+
)
|
| 443 |
+
|
| 444 |
+
def __iter__(self):
|
| 445 |
+
"""Iterate over the nodes. Use: 'for n in G'.
|
| 446 |
+
|
| 447 |
+
Returns
|
| 448 |
+
-------
|
| 449 |
+
niter : iterator
|
| 450 |
+
An iterator over all nodes in the graph.
|
| 451 |
+
|
| 452 |
+
Examples
|
| 453 |
+
--------
|
| 454 |
+
>>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 455 |
+
>>> [n for n in G]
|
| 456 |
+
[0, 1, 2, 3]
|
| 457 |
+
>>> list(G)
|
| 458 |
+
[0, 1, 2, 3]
|
| 459 |
+
"""
|
| 460 |
+
return iter(self._node)
|
| 461 |
+
|
| 462 |
+
def __contains__(self, n):
|
| 463 |
+
"""Returns True if n is a node, False otherwise. Use: 'n in G'.
|
| 464 |
+
|
| 465 |
+
Examples
|
| 466 |
+
--------
|
| 467 |
+
>>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 468 |
+
>>> 1 in G
|
| 469 |
+
True
|
| 470 |
+
"""
|
| 471 |
+
try:
|
| 472 |
+
return n in self._node
|
| 473 |
+
except TypeError:
|
| 474 |
+
return False
|
| 475 |
+
|
| 476 |
+
def __len__(self):
|
| 477 |
+
"""Returns the number of nodes in the graph. Use: 'len(G)'.
|
| 478 |
+
|
| 479 |
+
Returns
|
| 480 |
+
-------
|
| 481 |
+
nnodes : int
|
| 482 |
+
The number of nodes in the graph.
|
| 483 |
+
|
| 484 |
+
See Also
|
| 485 |
+
--------
|
| 486 |
+
number_of_nodes: identical method
|
| 487 |
+
order: identical method
|
| 488 |
+
|
| 489 |
+
Examples
|
| 490 |
+
--------
|
| 491 |
+
>>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 492 |
+
>>> len(G)
|
| 493 |
+
4
|
| 494 |
+
|
| 495 |
+
"""
|
| 496 |
+
return len(self._node)
|
| 497 |
+
|
| 498 |
+
def __getitem__(self, n):
|
| 499 |
+
"""Returns a dict of neighbors of node n. Use: 'G[n]'.
|
| 500 |
+
|
| 501 |
+
Parameters
|
| 502 |
+
----------
|
| 503 |
+
n : node
|
| 504 |
+
A node in the graph.
|
| 505 |
+
|
| 506 |
+
Returns
|
| 507 |
+
-------
|
| 508 |
+
adj_dict : dictionary
|
| 509 |
+
The adjacency dictionary for nodes connected to n.
|
| 510 |
+
|
| 511 |
+
Notes
|
| 512 |
+
-----
|
| 513 |
+
G[n] is the same as G.adj[n] and similar to G.neighbors(n)
|
| 514 |
+
(which is an iterator over G.adj[n])
|
| 515 |
+
|
| 516 |
+
Examples
|
| 517 |
+
--------
|
| 518 |
+
>>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 519 |
+
>>> G[0]
|
| 520 |
+
AtlasView({1: {}})
|
| 521 |
+
"""
|
| 522 |
+
return self.adj[n]
|
| 523 |
+
|
| 524 |
+
def add_node(self, node_for_adding, **attr):
|
| 525 |
+
"""Add a single node `node_for_adding` and update node attributes.
|
| 526 |
+
|
| 527 |
+
Parameters
|
| 528 |
+
----------
|
| 529 |
+
node_for_adding : node
|
| 530 |
+
A node can be any hashable Python object except None.
|
| 531 |
+
attr : keyword arguments, optional
|
| 532 |
+
Set or change node attributes using key=value.
|
| 533 |
+
|
| 534 |
+
See Also
|
| 535 |
+
--------
|
| 536 |
+
add_nodes_from
|
| 537 |
+
|
| 538 |
+
Examples
|
| 539 |
+
--------
|
| 540 |
+
>>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 541 |
+
>>> G.add_node(1)
|
| 542 |
+
>>> G.add_node("Hello")
|
| 543 |
+
>>> K3 = nx.Graph([(0, 1), (1, 2), (2, 0)])
|
| 544 |
+
>>> G.add_node(K3)
|
| 545 |
+
>>> G.number_of_nodes()
|
| 546 |
+
3
|
| 547 |
+
|
| 548 |
+
Use keywords set/change node attributes:
|
| 549 |
+
|
| 550 |
+
>>> G.add_node(1, size=10)
|
| 551 |
+
>>> G.add_node(3, weight=0.4, UTM=("13S", 382871, 3972649))
|
| 552 |
+
|
| 553 |
+
Notes
|
| 554 |
+
-----
|
| 555 |
+
A hashable object is one that can be used as a key in a Python
|
| 556 |
+
dictionary. This includes strings, numbers, tuples of strings
|
| 557 |
+
and numbers, etc.
|
| 558 |
+
|
| 559 |
+
On many platforms hashable items also include mutables such as
|
| 560 |
+
NetworkX Graphs, though one should be careful that the hash
|
| 561 |
+
doesn't change on mutables.
|
| 562 |
+
"""
|
| 563 |
+
if node_for_adding not in self._node:
|
| 564 |
+
if node_for_adding is None:
|
| 565 |
+
raise ValueError("None cannot be a node")
|
| 566 |
+
self._adj[node_for_adding] = self.adjlist_inner_dict_factory()
|
| 567 |
+
attr_dict = self._node[node_for_adding] = self.node_attr_dict_factory()
|
| 568 |
+
attr_dict.update(attr)
|
| 569 |
+
else: # update attr even if node already exists
|
| 570 |
+
self._node[node_for_adding].update(attr)
|
| 571 |
+
nx._clear_cache(self)
|
| 572 |
+
|
| 573 |
+
def add_nodes_from(self, nodes_for_adding, **attr):
|
| 574 |
+
"""Add multiple nodes.
|
| 575 |
+
|
| 576 |
+
Parameters
|
| 577 |
+
----------
|
| 578 |
+
nodes_for_adding : iterable container
|
| 579 |
+
A container of nodes (list, dict, set, etc.).
|
| 580 |
+
OR
|
| 581 |
+
A container of (node, attribute dict) tuples.
|
| 582 |
+
Node attributes are updated using the attribute dict.
|
| 583 |
+
attr : keyword arguments, optional (default= no attributes)
|
| 584 |
+
Update attributes for all nodes in nodes.
|
| 585 |
+
Node attributes specified in nodes as a tuple take
|
| 586 |
+
precedence over attributes specified via keyword arguments.
|
| 587 |
+
|
| 588 |
+
See Also
|
| 589 |
+
--------
|
| 590 |
+
add_node
|
| 591 |
+
|
| 592 |
+
Notes
|
| 593 |
+
-----
|
| 594 |
+
When adding nodes from an iterator over the graph you are changing,
|
| 595 |
+
a `RuntimeError` can be raised with message:
|
| 596 |
+
`RuntimeError: dictionary changed size during iteration`. This
|
| 597 |
+
happens when the graph's underlying dictionary is modified during
|
| 598 |
+
iteration. To avoid this error, evaluate the iterator into a separate
|
| 599 |
+
object, e.g. by using `list(iterator_of_nodes)`, and pass this
|
| 600 |
+
object to `G.add_nodes_from`.
|
| 601 |
+
|
| 602 |
+
Examples
|
| 603 |
+
--------
|
| 604 |
+
>>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 605 |
+
>>> G.add_nodes_from("Hello")
|
| 606 |
+
>>> K3 = nx.Graph([(0, 1), (1, 2), (2, 0)])
|
| 607 |
+
>>> G.add_nodes_from(K3)
|
| 608 |
+
>>> sorted(G.nodes(), key=str)
|
| 609 |
+
[0, 1, 2, 'H', 'e', 'l', 'o']
|
| 610 |
+
|
| 611 |
+
Use keywords to update specific node attributes for every node.
|
| 612 |
+
|
| 613 |
+
>>> G.add_nodes_from([1, 2], size=10)
|
| 614 |
+
>>> G.add_nodes_from([3, 4], weight=0.4)
|
| 615 |
+
|
| 616 |
+
Use (node, attrdict) tuples to update attributes for specific nodes.
|
| 617 |
+
|
| 618 |
+
>>> G.add_nodes_from([(1, dict(size=11)), (2, {"color": "blue"})])
|
| 619 |
+
>>> G.nodes[1]["size"]
|
| 620 |
+
11
|
| 621 |
+
>>> H = nx.Graph()
|
| 622 |
+
>>> H.add_nodes_from(G.nodes(data=True))
|
| 623 |
+
>>> H.nodes[1]["size"]
|
| 624 |
+
11
|
| 625 |
+
|
| 626 |
+
Evaluate an iterator over a graph if using it to modify the same graph
|
| 627 |
+
|
| 628 |
+
>>> G = nx.Graph([(0, 1), (1, 2), (3, 4)])
|
| 629 |
+
>>> # wrong way - will raise RuntimeError
|
| 630 |
+
>>> # G.add_nodes_from(n + 1 for n in G.nodes)
|
| 631 |
+
>>> # correct way
|
| 632 |
+
>>> G.add_nodes_from(list(n + 1 for n in G.nodes))
|
| 633 |
+
"""
|
| 634 |
+
for n in nodes_for_adding:
|
| 635 |
+
try:
|
| 636 |
+
newnode = n not in self._node
|
| 637 |
+
newdict = attr
|
| 638 |
+
except TypeError:
|
| 639 |
+
n, ndict = n
|
| 640 |
+
newnode = n not in self._node
|
| 641 |
+
newdict = attr.copy()
|
| 642 |
+
newdict.update(ndict)
|
| 643 |
+
if newnode:
|
| 644 |
+
if n is None:
|
| 645 |
+
raise ValueError("None cannot be a node")
|
| 646 |
+
self._adj[n] = self.adjlist_inner_dict_factory()
|
| 647 |
+
self._node[n] = self.node_attr_dict_factory()
|
| 648 |
+
self._node[n].update(newdict)
|
| 649 |
+
nx._clear_cache(self)
|
| 650 |
+
|
| 651 |
+
def remove_node(self, n):
|
| 652 |
+
"""Remove node n.
|
| 653 |
+
|
| 654 |
+
Removes the node n and all adjacent edges.
|
| 655 |
+
Attempting to remove a nonexistent node will raise an exception.
|
| 656 |
+
|
| 657 |
+
Parameters
|
| 658 |
+
----------
|
| 659 |
+
n : node
|
| 660 |
+
A node in the graph
|
| 661 |
+
|
| 662 |
+
Raises
|
| 663 |
+
------
|
| 664 |
+
NetworkXError
|
| 665 |
+
If n is not in the graph.
|
| 666 |
+
|
| 667 |
+
See Also
|
| 668 |
+
--------
|
| 669 |
+
remove_nodes_from
|
| 670 |
+
|
| 671 |
+
Examples
|
| 672 |
+
--------
|
| 673 |
+
>>> G = nx.path_graph(3) # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 674 |
+
>>> list(G.edges)
|
| 675 |
+
[(0, 1), (1, 2)]
|
| 676 |
+
>>> G.remove_node(1)
|
| 677 |
+
>>> list(G.edges)
|
| 678 |
+
[]
|
| 679 |
+
|
| 680 |
+
"""
|
| 681 |
+
adj = self._adj
|
| 682 |
+
try:
|
| 683 |
+
nbrs = list(adj[n]) # list handles self-loops (allows mutation)
|
| 684 |
+
del self._node[n]
|
| 685 |
+
except KeyError as err: # NetworkXError if n not in self
|
| 686 |
+
raise NetworkXError(f"The node {n} is not in the graph.") from err
|
| 687 |
+
for u in nbrs:
|
| 688 |
+
del adj[u][n] # remove all edges n-u in graph
|
| 689 |
+
del adj[n] # now remove node
|
| 690 |
+
nx._clear_cache(self)
|
| 691 |
+
|
| 692 |
+
def remove_nodes_from(self, nodes):
|
| 693 |
+
"""Remove multiple nodes.
|
| 694 |
+
|
| 695 |
+
Parameters
|
| 696 |
+
----------
|
| 697 |
+
nodes : iterable container
|
| 698 |
+
A container of nodes (list, dict, set, etc.). If a node
|
| 699 |
+
in the container is not in the graph it is silently
|
| 700 |
+
ignored.
|
| 701 |
+
|
| 702 |
+
See Also
|
| 703 |
+
--------
|
| 704 |
+
remove_node
|
| 705 |
+
|
| 706 |
+
Notes
|
| 707 |
+
-----
|
| 708 |
+
When removing nodes from an iterator over the graph you are changing,
|
| 709 |
+
a `RuntimeError` will be raised with message:
|
| 710 |
+
`RuntimeError: dictionary changed size during iteration`. This
|
| 711 |
+
happens when the graph's underlying dictionary is modified during
|
| 712 |
+
iteration. To avoid this error, evaluate the iterator into a separate
|
| 713 |
+
object, e.g. by using `list(iterator_of_nodes)`, and pass this
|
| 714 |
+
object to `G.remove_nodes_from`.
|
| 715 |
+
|
| 716 |
+
Examples
|
| 717 |
+
--------
|
| 718 |
+
>>> G = nx.path_graph(3) # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 719 |
+
>>> e = list(G.nodes)
|
| 720 |
+
>>> e
|
| 721 |
+
[0, 1, 2]
|
| 722 |
+
>>> G.remove_nodes_from(e)
|
| 723 |
+
>>> list(G.nodes)
|
| 724 |
+
[]
|
| 725 |
+
|
| 726 |
+
Evaluate an iterator over a graph if using it to modify the same graph
|
| 727 |
+
|
| 728 |
+
>>> G = nx.Graph([(0, 1), (1, 2), (3, 4)])
|
| 729 |
+
>>> # this command will fail, as the graph's dict is modified during iteration
|
| 730 |
+
>>> # G.remove_nodes_from(n for n in G.nodes if n < 2)
|
| 731 |
+
>>> # this command will work, since the dictionary underlying graph is not modified
|
| 732 |
+
>>> G.remove_nodes_from(list(n for n in G.nodes if n < 2))
|
| 733 |
+
"""
|
| 734 |
+
adj = self._adj
|
| 735 |
+
for n in nodes:
|
| 736 |
+
try:
|
| 737 |
+
del self._node[n]
|
| 738 |
+
for u in list(adj[n]): # list handles self-loops
|
| 739 |
+
del adj[u][n] # (allows mutation of dict in loop)
|
| 740 |
+
del adj[n]
|
| 741 |
+
except KeyError:
|
| 742 |
+
pass
|
| 743 |
+
nx._clear_cache(self)
|
| 744 |
+
|
| 745 |
+
@cached_property
|
| 746 |
+
def nodes(self):
|
| 747 |
+
"""A NodeView of the Graph as G.nodes or G.nodes().
|
| 748 |
+
|
| 749 |
+
Can be used as `G.nodes` for data lookup and for set-like operations.
|
| 750 |
+
Can also be used as `G.nodes(data='color', default=None)` to return a
|
| 751 |
+
NodeDataView which reports specific node data but no set operations.
|
| 752 |
+
It presents a dict-like interface as well with `G.nodes.items()`
|
| 753 |
+
iterating over `(node, nodedata)` 2-tuples and `G.nodes[3]['foo']`
|
| 754 |
+
providing the value of the `foo` attribute for node `3`. In addition,
|
| 755 |
+
a view `G.nodes.data('foo')` provides a dict-like interface to the
|
| 756 |
+
`foo` attribute of each node. `G.nodes.data('foo', default=1)`
|
| 757 |
+
provides a default for nodes that do not have attribute `foo`.
|
| 758 |
+
|
| 759 |
+
Parameters
|
| 760 |
+
----------
|
| 761 |
+
data : string or bool, optional (default=False)
|
| 762 |
+
The node attribute returned in 2-tuple (n, ddict[data]).
|
| 763 |
+
If True, return entire node attribute dict as (n, ddict).
|
| 764 |
+
If False, return just the nodes n.
|
| 765 |
+
|
| 766 |
+
default : value, optional (default=None)
|
| 767 |
+
Value used for nodes that don't have the requested attribute.
|
| 768 |
+
Only relevant if data is not True or False.
|
| 769 |
+
|
| 770 |
+
Returns
|
| 771 |
+
-------
|
| 772 |
+
NodeView
|
| 773 |
+
Allows set-like operations over the nodes as well as node
|
| 774 |
+
attribute dict lookup and calling to get a NodeDataView.
|
| 775 |
+
A NodeDataView iterates over `(n, data)` and has no set operations.
|
| 776 |
+
A NodeView iterates over `n` and includes set operations.
|
| 777 |
+
|
| 778 |
+
When called, if data is False, an iterator over nodes.
|
| 779 |
+
Otherwise an iterator of 2-tuples (node, attribute value)
|
| 780 |
+
where the attribute is specified in `data`.
|
| 781 |
+
If data is True then the attribute becomes the
|
| 782 |
+
entire data dictionary.
|
| 783 |
+
|
| 784 |
+
Notes
|
| 785 |
+
-----
|
| 786 |
+
If your node data is not needed, it is simpler and equivalent
|
| 787 |
+
to use the expression ``for n in G``, or ``list(G)``.
|
| 788 |
+
|
| 789 |
+
Examples
|
| 790 |
+
--------
|
| 791 |
+
There are two simple ways of getting a list of all nodes in the graph:
|
| 792 |
+
|
| 793 |
+
>>> G = nx.path_graph(3)
|
| 794 |
+
>>> list(G.nodes)
|
| 795 |
+
[0, 1, 2]
|
| 796 |
+
>>> list(G)
|
| 797 |
+
[0, 1, 2]
|
| 798 |
+
|
| 799 |
+
To get the node data along with the nodes:
|
| 800 |
+
|
| 801 |
+
>>> G.add_node(1, time="5pm")
|
| 802 |
+
>>> G.nodes[0]["foo"] = "bar"
|
| 803 |
+
>>> list(G.nodes(data=True))
|
| 804 |
+
[(0, {'foo': 'bar'}), (1, {'time': '5pm'}), (2, {})]
|
| 805 |
+
>>> list(G.nodes.data())
|
| 806 |
+
[(0, {'foo': 'bar'}), (1, {'time': '5pm'}), (2, {})]
|
| 807 |
+
|
| 808 |
+
>>> list(G.nodes(data="foo"))
|
| 809 |
+
[(0, 'bar'), (1, None), (2, None)]
|
| 810 |
+
>>> list(G.nodes.data("foo"))
|
| 811 |
+
[(0, 'bar'), (1, None), (2, None)]
|
| 812 |
+
|
| 813 |
+
>>> list(G.nodes(data="time"))
|
| 814 |
+
[(0, None), (1, '5pm'), (2, None)]
|
| 815 |
+
>>> list(G.nodes.data("time"))
|
| 816 |
+
[(0, None), (1, '5pm'), (2, None)]
|
| 817 |
+
|
| 818 |
+
>>> list(G.nodes(data="time", default="Not Available"))
|
| 819 |
+
[(0, 'Not Available'), (1, '5pm'), (2, 'Not Available')]
|
| 820 |
+
>>> list(G.nodes.data("time", default="Not Available"))
|
| 821 |
+
[(0, 'Not Available'), (1, '5pm'), (2, 'Not Available')]
|
| 822 |
+
|
| 823 |
+
If some of your nodes have an attribute and the rest are assumed
|
| 824 |
+
to have a default attribute value you can create a dictionary
|
| 825 |
+
from node/attribute pairs using the `default` keyword argument
|
| 826 |
+
to guarantee the value is never None::
|
| 827 |
+
|
| 828 |
+
>>> G = nx.Graph()
|
| 829 |
+
>>> G.add_node(0)
|
| 830 |
+
>>> G.add_node(1, weight=2)
|
| 831 |
+
>>> G.add_node(2, weight=3)
|
| 832 |
+
>>> dict(G.nodes(data="weight", default=1))
|
| 833 |
+
{0: 1, 1: 2, 2: 3}
|
| 834 |
+
|
| 835 |
+
"""
|
| 836 |
+
return NodeView(self)
|
| 837 |
+
|
| 838 |
+
def number_of_nodes(self):
|
| 839 |
+
"""Returns the number of nodes in the graph.
|
| 840 |
+
|
| 841 |
+
Returns
|
| 842 |
+
-------
|
| 843 |
+
nnodes : int
|
| 844 |
+
The number of nodes in the graph.
|
| 845 |
+
|
| 846 |
+
See Also
|
| 847 |
+
--------
|
| 848 |
+
order: identical method
|
| 849 |
+
__len__: identical method
|
| 850 |
+
|
| 851 |
+
Examples
|
| 852 |
+
--------
|
| 853 |
+
>>> G = nx.path_graph(3) # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 854 |
+
>>> G.number_of_nodes()
|
| 855 |
+
3
|
| 856 |
+
"""
|
| 857 |
+
return len(self._node)
|
| 858 |
+
|
| 859 |
+
def order(self):
|
| 860 |
+
"""Returns the number of nodes in the graph.
|
| 861 |
+
|
| 862 |
+
Returns
|
| 863 |
+
-------
|
| 864 |
+
nnodes : int
|
| 865 |
+
The number of nodes in the graph.
|
| 866 |
+
|
| 867 |
+
See Also
|
| 868 |
+
--------
|
| 869 |
+
number_of_nodes: identical method
|
| 870 |
+
__len__: identical method
|
| 871 |
+
|
| 872 |
+
Examples
|
| 873 |
+
--------
|
| 874 |
+
>>> G = nx.path_graph(3) # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 875 |
+
>>> G.order()
|
| 876 |
+
3
|
| 877 |
+
"""
|
| 878 |
+
return len(self._node)
|
| 879 |
+
|
| 880 |
+
def has_node(self, n):
|
| 881 |
+
"""Returns True if the graph contains the node n.
|
| 882 |
+
|
| 883 |
+
Identical to `n in G`
|
| 884 |
+
|
| 885 |
+
Parameters
|
| 886 |
+
----------
|
| 887 |
+
n : node
|
| 888 |
+
|
| 889 |
+
Examples
|
| 890 |
+
--------
|
| 891 |
+
>>> G = nx.path_graph(3) # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 892 |
+
>>> G.has_node(0)
|
| 893 |
+
True
|
| 894 |
+
|
| 895 |
+
It is more readable and simpler to use
|
| 896 |
+
|
| 897 |
+
>>> 0 in G
|
| 898 |
+
True
|
| 899 |
+
|
| 900 |
+
"""
|
| 901 |
+
try:
|
| 902 |
+
return n in self._node
|
| 903 |
+
except TypeError:
|
| 904 |
+
return False
|
| 905 |
+
|
| 906 |
+
def add_edge(self, u_of_edge, v_of_edge, **attr):
|
| 907 |
+
"""Add an edge between u and v.
|
| 908 |
+
|
| 909 |
+
The nodes u and v will be automatically added if they are
|
| 910 |
+
not already in the graph.
|
| 911 |
+
|
| 912 |
+
Edge attributes can be specified with keywords or by directly
|
| 913 |
+
accessing the edge's attribute dictionary. See examples below.
|
| 914 |
+
|
| 915 |
+
Parameters
|
| 916 |
+
----------
|
| 917 |
+
u_of_edge, v_of_edge : nodes
|
| 918 |
+
Nodes can be, for example, strings or numbers.
|
| 919 |
+
Nodes must be hashable (and not None) Python objects.
|
| 920 |
+
attr : keyword arguments, optional
|
| 921 |
+
Edge data (or labels or objects) can be assigned using
|
| 922 |
+
keyword arguments.
|
| 923 |
+
|
| 924 |
+
See Also
|
| 925 |
+
--------
|
| 926 |
+
add_edges_from : add a collection of edges
|
| 927 |
+
|
| 928 |
+
Notes
|
| 929 |
+
-----
|
| 930 |
+
Adding an edge that already exists updates the edge data.
|
| 931 |
+
|
| 932 |
+
Many NetworkX algorithms designed for weighted graphs use
|
| 933 |
+
an edge attribute (by default `weight`) to hold a numerical value.
|
| 934 |
+
|
| 935 |
+
Examples
|
| 936 |
+
--------
|
| 937 |
+
The following all add the edge e=(1, 2) to graph G:
|
| 938 |
+
|
| 939 |
+
>>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 940 |
+
>>> e = (1, 2)
|
| 941 |
+
>>> G.add_edge(1, 2) # explicit two-node form
|
| 942 |
+
>>> G.add_edge(*e) # single edge as tuple of two nodes
|
| 943 |
+
>>> G.add_edges_from([(1, 2)]) # add edges from iterable container
|
| 944 |
+
|
| 945 |
+
Associate data to edges using keywords:
|
| 946 |
+
|
| 947 |
+
>>> G.add_edge(1, 2, weight=3)
|
| 948 |
+
>>> G.add_edge(1, 3, weight=7, capacity=15, length=342.7)
|
| 949 |
+
|
| 950 |
+
For non-string attribute keys, use subscript notation.
|
| 951 |
+
|
| 952 |
+
>>> G.add_edge(1, 2)
|
| 953 |
+
>>> G[1][2].update({0: 5})
|
| 954 |
+
>>> G.edges[1, 2].update({0: 5})
|
| 955 |
+
"""
|
| 956 |
+
u, v = u_of_edge, v_of_edge
|
| 957 |
+
# add nodes
|
| 958 |
+
if u not in self._node:
|
| 959 |
+
if u is None:
|
| 960 |
+
raise ValueError("None cannot be a node")
|
| 961 |
+
self._adj[u] = self.adjlist_inner_dict_factory()
|
| 962 |
+
self._node[u] = self.node_attr_dict_factory()
|
| 963 |
+
if v not in self._node:
|
| 964 |
+
if v is None:
|
| 965 |
+
raise ValueError("None cannot be a node")
|
| 966 |
+
self._adj[v] = self.adjlist_inner_dict_factory()
|
| 967 |
+
self._node[v] = self.node_attr_dict_factory()
|
| 968 |
+
# add the edge
|
| 969 |
+
datadict = self._adj[u].get(v, self.edge_attr_dict_factory())
|
| 970 |
+
datadict.update(attr)
|
| 971 |
+
self._adj[u][v] = datadict
|
| 972 |
+
self._adj[v][u] = datadict
|
| 973 |
+
nx._clear_cache(self)
|
| 974 |
+
|
| 975 |
+
def add_edges_from(self, ebunch_to_add, **attr):
|
| 976 |
+
"""Add all the edges in ebunch_to_add.
|
| 977 |
+
|
| 978 |
+
Parameters
|
| 979 |
+
----------
|
| 980 |
+
ebunch_to_add : container of edges
|
| 981 |
+
Each edge given in the container will be added to the
|
| 982 |
+
graph. The edges must be given as 2-tuples (u, v) or
|
| 983 |
+
3-tuples (u, v, d) where d is a dictionary containing edge data.
|
| 984 |
+
attr : keyword arguments, optional
|
| 985 |
+
Edge data (or labels or objects) can be assigned using
|
| 986 |
+
keyword arguments.
|
| 987 |
+
|
| 988 |
+
See Also
|
| 989 |
+
--------
|
| 990 |
+
add_edge : add a single edge
|
| 991 |
+
add_weighted_edges_from : convenient way to add weighted edges
|
| 992 |
+
|
| 993 |
+
Notes
|
| 994 |
+
-----
|
| 995 |
+
Adding the same edge twice has no effect but any edge data
|
| 996 |
+
will be updated when each duplicate edge is added.
|
| 997 |
+
|
| 998 |
+
Edge attributes specified in an ebunch take precedence over
|
| 999 |
+
attributes specified via keyword arguments.
|
| 1000 |
+
|
| 1001 |
+
When adding edges from an iterator over the graph you are changing,
|
| 1002 |
+
a `RuntimeError` can be raised with message:
|
| 1003 |
+
`RuntimeError: dictionary changed size during iteration`. This
|
| 1004 |
+
happens when the graph's underlying dictionary is modified during
|
| 1005 |
+
iteration. To avoid this error, evaluate the iterator into a separate
|
| 1006 |
+
object, e.g. by using `list(iterator_of_edges)`, and pass this
|
| 1007 |
+
object to `G.add_edges_from`.
|
| 1008 |
+
|
| 1009 |
+
Examples
|
| 1010 |
+
--------
|
| 1011 |
+
>>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 1012 |
+
>>> G.add_edges_from([(0, 1), (1, 2)]) # using a list of edge tuples
|
| 1013 |
+
>>> e = zip(range(0, 3), range(1, 4))
|
| 1014 |
+
>>> G.add_edges_from(e) # Add the path graph 0-1-2-3
|
| 1015 |
+
|
| 1016 |
+
Associate data to edges
|
| 1017 |
+
|
| 1018 |
+
>>> G.add_edges_from([(1, 2), (2, 3)], weight=3)
|
| 1019 |
+
>>> G.add_edges_from([(3, 4), (1, 4)], label="WN2898")
|
| 1020 |
+
|
| 1021 |
+
Evaluate an iterator over a graph if using it to modify the same graph
|
| 1022 |
+
|
| 1023 |
+
>>> G = nx.Graph([(1, 2), (2, 3), (3, 4)])
|
| 1024 |
+
>>> # Grow graph by one new node, adding edges to all existing nodes.
|
| 1025 |
+
>>> # wrong way - will raise RuntimeError
|
| 1026 |
+
>>> # G.add_edges_from(((5, n) for n in G.nodes))
|
| 1027 |
+
>>> # correct way - note that there will be no self-edge for node 5
|
| 1028 |
+
>>> G.add_edges_from(list((5, n) for n in G.nodes))
|
| 1029 |
+
"""
|
| 1030 |
+
for e in ebunch_to_add:
|
| 1031 |
+
ne = len(e)
|
| 1032 |
+
if ne == 3:
|
| 1033 |
+
u, v, dd = e
|
| 1034 |
+
elif ne == 2:
|
| 1035 |
+
u, v = e
|
| 1036 |
+
dd = {} # doesn't need edge_attr_dict_factory
|
| 1037 |
+
else:
|
| 1038 |
+
raise NetworkXError(f"Edge tuple {e} must be a 2-tuple or 3-tuple.")
|
| 1039 |
+
if u not in self._node:
|
| 1040 |
+
if u is None:
|
| 1041 |
+
raise ValueError("None cannot be a node")
|
| 1042 |
+
self._adj[u] = self.adjlist_inner_dict_factory()
|
| 1043 |
+
self._node[u] = self.node_attr_dict_factory()
|
| 1044 |
+
if v not in self._node:
|
| 1045 |
+
if v is None:
|
| 1046 |
+
raise ValueError("None cannot be a node")
|
| 1047 |
+
self._adj[v] = self.adjlist_inner_dict_factory()
|
| 1048 |
+
self._node[v] = self.node_attr_dict_factory()
|
| 1049 |
+
datadict = self._adj[u].get(v, self.edge_attr_dict_factory())
|
| 1050 |
+
datadict.update(attr)
|
| 1051 |
+
datadict.update(dd)
|
| 1052 |
+
self._adj[u][v] = datadict
|
| 1053 |
+
self._adj[v][u] = datadict
|
| 1054 |
+
nx._clear_cache(self)
|
| 1055 |
+
|
| 1056 |
+
def add_weighted_edges_from(self, ebunch_to_add, weight="weight", **attr):
|
| 1057 |
+
"""Add weighted edges in `ebunch_to_add` with specified weight attr
|
| 1058 |
+
|
| 1059 |
+
Parameters
|
| 1060 |
+
----------
|
| 1061 |
+
ebunch_to_add : container of edges
|
| 1062 |
+
Each edge given in the list or container will be added
|
| 1063 |
+
to the graph. The edges must be given as 3-tuples (u, v, w)
|
| 1064 |
+
where w is a number.
|
| 1065 |
+
weight : string, optional (default= 'weight')
|
| 1066 |
+
The attribute name for the edge weights to be added.
|
| 1067 |
+
attr : keyword arguments, optional (default= no attributes)
|
| 1068 |
+
Edge attributes to add/update for all edges.
|
| 1069 |
+
|
| 1070 |
+
See Also
|
| 1071 |
+
--------
|
| 1072 |
+
add_edge : add a single edge
|
| 1073 |
+
add_edges_from : add multiple edges
|
| 1074 |
+
|
| 1075 |
+
Notes
|
| 1076 |
+
-----
|
| 1077 |
+
Adding the same edge twice for Graph/DiGraph simply updates
|
| 1078 |
+
the edge data. For MultiGraph/MultiDiGraph, duplicate edges
|
| 1079 |
+
are stored.
|
| 1080 |
+
|
| 1081 |
+
When adding edges from an iterator over the graph you are changing,
|
| 1082 |
+
a `RuntimeError` can be raised with message:
|
| 1083 |
+
`RuntimeError: dictionary changed size during iteration`. This
|
| 1084 |
+
happens when the graph's underlying dictionary is modified during
|
| 1085 |
+
iteration. To avoid this error, evaluate the iterator into a separate
|
| 1086 |
+
object, e.g. by using `list(iterator_of_edges)`, and pass this
|
| 1087 |
+
object to `G.add_weighted_edges_from`.
|
| 1088 |
+
|
| 1089 |
+
Examples
|
| 1090 |
+
--------
|
| 1091 |
+
>>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 1092 |
+
>>> G.add_weighted_edges_from([(0, 1, 3.0), (1, 2, 7.5)])
|
| 1093 |
+
|
| 1094 |
+
Evaluate an iterator over edges before passing it
|
| 1095 |
+
|
| 1096 |
+
>>> G = nx.Graph([(1, 2), (2, 3), (3, 4)])
|
| 1097 |
+
>>> weight = 0.1
|
| 1098 |
+
>>> # Grow graph by one new node, adding edges to all existing nodes.
|
| 1099 |
+
>>> # wrong way - will raise RuntimeError
|
| 1100 |
+
>>> # G.add_weighted_edges_from(((5, n, weight) for n in G.nodes))
|
| 1101 |
+
>>> # correct way - note that there will be no self-edge for node 5
|
| 1102 |
+
>>> G.add_weighted_edges_from(list((5, n, weight) for n in G.nodes))
|
| 1103 |
+
"""
|
| 1104 |
+
self.add_edges_from(((u, v, {weight: d}) for u, v, d in ebunch_to_add), **attr)
|
| 1105 |
+
nx._clear_cache(self)
|
| 1106 |
+
|
| 1107 |
+
def remove_edge(self, u, v):
|
| 1108 |
+
"""Remove the edge between u and v.
|
| 1109 |
+
|
| 1110 |
+
Parameters
|
| 1111 |
+
----------
|
| 1112 |
+
u, v : nodes
|
| 1113 |
+
Remove the edge between nodes u and v.
|
| 1114 |
+
|
| 1115 |
+
Raises
|
| 1116 |
+
------
|
| 1117 |
+
NetworkXError
|
| 1118 |
+
If there is not an edge between u and v.
|
| 1119 |
+
|
| 1120 |
+
See Also
|
| 1121 |
+
--------
|
| 1122 |
+
remove_edges_from : remove a collection of edges
|
| 1123 |
+
|
| 1124 |
+
Examples
|
| 1125 |
+
--------
|
| 1126 |
+
>>> G = nx.path_graph(4) # or DiGraph, etc
|
| 1127 |
+
>>> G.remove_edge(0, 1)
|
| 1128 |
+
>>> e = (1, 2)
|
| 1129 |
+
>>> G.remove_edge(*e) # unpacks e from an edge tuple
|
| 1130 |
+
>>> e = (2, 3, {"weight": 7}) # an edge with attribute data
|
| 1131 |
+
>>> G.remove_edge(*e[:2]) # select first part of edge tuple
|
| 1132 |
+
"""
|
| 1133 |
+
try:
|
| 1134 |
+
del self._adj[u][v]
|
| 1135 |
+
if u != v: # self-loop needs only one entry removed
|
| 1136 |
+
del self._adj[v][u]
|
| 1137 |
+
except KeyError as err:
|
| 1138 |
+
raise NetworkXError(f"The edge {u}-{v} is not in the graph") from err
|
| 1139 |
+
nx._clear_cache(self)
|
| 1140 |
+
|
| 1141 |
+
def remove_edges_from(self, ebunch):
|
| 1142 |
+
"""Remove all edges specified in ebunch.
|
| 1143 |
+
|
| 1144 |
+
Parameters
|
| 1145 |
+
----------
|
| 1146 |
+
ebunch: list or container of edge tuples
|
| 1147 |
+
Each edge given in the list or container will be removed
|
| 1148 |
+
from the graph. The edges can be:
|
| 1149 |
+
|
| 1150 |
+
- 2-tuples (u, v) edge between u and v.
|
| 1151 |
+
- 3-tuples (u, v, k) where k is ignored.
|
| 1152 |
+
|
| 1153 |
+
See Also
|
| 1154 |
+
--------
|
| 1155 |
+
remove_edge : remove a single edge
|
| 1156 |
+
|
| 1157 |
+
Notes
|
| 1158 |
+
-----
|
| 1159 |
+
Will fail silently if an edge in ebunch is not in the graph.
|
| 1160 |
+
|
| 1161 |
+
Examples
|
| 1162 |
+
--------
|
| 1163 |
+
>>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 1164 |
+
>>> ebunch = [(1, 2), (2, 3)]
|
| 1165 |
+
>>> G.remove_edges_from(ebunch)
|
| 1166 |
+
"""
|
| 1167 |
+
adj = self._adj
|
| 1168 |
+
for e in ebunch:
|
| 1169 |
+
u, v = e[:2] # ignore edge data if present
|
| 1170 |
+
if u in adj and v in adj[u]:
|
| 1171 |
+
del adj[u][v]
|
| 1172 |
+
if u != v: # self loop needs only one entry removed
|
| 1173 |
+
del adj[v][u]
|
| 1174 |
+
nx._clear_cache(self)
|
| 1175 |
+
|
| 1176 |
+
def update(self, edges=None, nodes=None):
|
| 1177 |
+
"""Update the graph using nodes/edges/graphs as input.
|
| 1178 |
+
|
| 1179 |
+
Like dict.update, this method takes a graph as input, adding the
|
| 1180 |
+
graph's nodes and edges to this graph. It can also take two inputs:
|
| 1181 |
+
edges and nodes. Finally it can take either edges or nodes.
|
| 1182 |
+
To specify only nodes the keyword `nodes` must be used.
|
| 1183 |
+
|
| 1184 |
+
The collections of edges and nodes are treated similarly to
|
| 1185 |
+
the add_edges_from/add_nodes_from methods. When iterated, they
|
| 1186 |
+
should yield 2-tuples (u, v) or 3-tuples (u, v, datadict).
|
| 1187 |
+
|
| 1188 |
+
Parameters
|
| 1189 |
+
----------
|
| 1190 |
+
edges : Graph object, collection of edges, or None
|
| 1191 |
+
The first parameter can be a graph or some edges. If it has
|
| 1192 |
+
attributes `nodes` and `edges`, then it is taken to be a
|
| 1193 |
+
Graph-like object and those attributes are used as collections
|
| 1194 |
+
of nodes and edges to be added to the graph.
|
| 1195 |
+
If the first parameter does not have those attributes, it is
|
| 1196 |
+
treated as a collection of edges and added to the graph.
|
| 1197 |
+
If the first argument is None, no edges are added.
|
| 1198 |
+
nodes : collection of nodes, or None
|
| 1199 |
+
The second parameter is treated as a collection of nodes
|
| 1200 |
+
to be added to the graph unless it is None.
|
| 1201 |
+
If `edges is None` and `nodes is None` an exception is raised.
|
| 1202 |
+
If the first parameter is a Graph, then `nodes` is ignored.
|
| 1203 |
+
|
| 1204 |
+
Examples
|
| 1205 |
+
--------
|
| 1206 |
+
>>> G = nx.path_graph(5)
|
| 1207 |
+
>>> G.update(nx.complete_graph(range(4, 10)))
|
| 1208 |
+
>>> from itertools import combinations
|
| 1209 |
+
>>> edges = (
|
| 1210 |
+
... (u, v, {"power": u * v})
|
| 1211 |
+
... for u, v in combinations(range(10, 20), 2)
|
| 1212 |
+
... if u * v < 225
|
| 1213 |
+
... )
|
| 1214 |
+
>>> nodes = [1000] # for singleton, use a container
|
| 1215 |
+
>>> G.update(edges, nodes)
|
| 1216 |
+
|
| 1217 |
+
Notes
|
| 1218 |
+
-----
|
| 1219 |
+
It you want to update the graph using an adjacency structure
|
| 1220 |
+
it is straightforward to obtain the edges/nodes from adjacency.
|
| 1221 |
+
The following examples provide common cases, your adjacency may
|
| 1222 |
+
be slightly different and require tweaks of these examples::
|
| 1223 |
+
|
| 1224 |
+
>>> # dict-of-set/list/tuple
|
| 1225 |
+
>>> adj = {1: {2, 3}, 2: {1, 3}, 3: {1, 2}}
|
| 1226 |
+
>>> e = [(u, v) for u, nbrs in adj.items() for v in nbrs]
|
| 1227 |
+
>>> G.update(edges=e, nodes=adj)
|
| 1228 |
+
|
| 1229 |
+
>>> DG = nx.DiGraph()
|
| 1230 |
+
>>> # dict-of-dict-of-attribute
|
| 1231 |
+
>>> adj = {1: {2: 1.3, 3: 0.7}, 2: {1: 1.4}, 3: {1: 0.7}}
|
| 1232 |
+
>>> e = [
|
| 1233 |
+
... (u, v, {"weight": d})
|
| 1234 |
+
... for u, nbrs in adj.items()
|
| 1235 |
+
... for v, d in nbrs.items()
|
| 1236 |
+
... ]
|
| 1237 |
+
>>> DG.update(edges=e, nodes=adj)
|
| 1238 |
+
|
| 1239 |
+
>>> # dict-of-dict-of-dict
|
| 1240 |
+
>>> adj = {1: {2: {"weight": 1.3}, 3: {"color": 0.7, "weight": 1.2}}}
|
| 1241 |
+
>>> e = [
|
| 1242 |
+
... (u, v, {"weight": d})
|
| 1243 |
+
... for u, nbrs in adj.items()
|
| 1244 |
+
... for v, d in nbrs.items()
|
| 1245 |
+
... ]
|
| 1246 |
+
>>> DG.update(edges=e, nodes=adj)
|
| 1247 |
+
|
| 1248 |
+
>>> # predecessor adjacency (dict-of-set)
|
| 1249 |
+
>>> pred = {1: {2, 3}, 2: {3}, 3: {3}}
|
| 1250 |
+
>>> e = [(v, u) for u, nbrs in pred.items() for v in nbrs]
|
| 1251 |
+
|
| 1252 |
+
>>> # MultiGraph dict-of-dict-of-dict-of-attribute
|
| 1253 |
+
>>> MDG = nx.MultiDiGraph()
|
| 1254 |
+
>>> adj = {
|
| 1255 |
+
... 1: {2: {0: {"weight": 1.3}, 1: {"weight": 1.2}}},
|
| 1256 |
+
... 3: {2: {0: {"weight": 0.7}}},
|
| 1257 |
+
... }
|
| 1258 |
+
>>> e = [
|
| 1259 |
+
... (u, v, ekey, d)
|
| 1260 |
+
... for u, nbrs in adj.items()
|
| 1261 |
+
... for v, keydict in nbrs.items()
|
| 1262 |
+
... for ekey, d in keydict.items()
|
| 1263 |
+
... ]
|
| 1264 |
+
>>> MDG.update(edges=e)
|
| 1265 |
+
|
| 1266 |
+
See Also
|
| 1267 |
+
--------
|
| 1268 |
+
add_edges_from: add multiple edges to a graph
|
| 1269 |
+
add_nodes_from: add multiple nodes to a graph
|
| 1270 |
+
"""
|
| 1271 |
+
if edges is not None:
|
| 1272 |
+
if nodes is not None:
|
| 1273 |
+
self.add_nodes_from(nodes)
|
| 1274 |
+
self.add_edges_from(edges)
|
| 1275 |
+
else:
|
| 1276 |
+
# check if edges is a Graph object
|
| 1277 |
+
try:
|
| 1278 |
+
graph_nodes = edges.nodes
|
| 1279 |
+
graph_edges = edges.edges
|
| 1280 |
+
except AttributeError:
|
| 1281 |
+
# edge not Graph-like
|
| 1282 |
+
self.add_edges_from(edges)
|
| 1283 |
+
else: # edges is Graph-like
|
| 1284 |
+
self.add_nodes_from(graph_nodes.data())
|
| 1285 |
+
self.add_edges_from(graph_edges.data())
|
| 1286 |
+
self.graph.update(edges.graph)
|
| 1287 |
+
elif nodes is not None:
|
| 1288 |
+
self.add_nodes_from(nodes)
|
| 1289 |
+
else:
|
| 1290 |
+
raise NetworkXError("update needs nodes or edges input")
|
| 1291 |
+
|
| 1292 |
+
def has_edge(self, u, v):
|
| 1293 |
+
"""Returns True if the edge (u, v) is in the graph.
|
| 1294 |
+
|
| 1295 |
+
This is the same as `v in G[u]` without KeyError exceptions.
|
| 1296 |
+
|
| 1297 |
+
Parameters
|
| 1298 |
+
----------
|
| 1299 |
+
u, v : nodes
|
| 1300 |
+
Nodes can be, for example, strings or numbers.
|
| 1301 |
+
Nodes must be hashable (and not None) Python objects.
|
| 1302 |
+
|
| 1303 |
+
Returns
|
| 1304 |
+
-------
|
| 1305 |
+
edge_ind : bool
|
| 1306 |
+
True if edge is in the graph, False otherwise.
|
| 1307 |
+
|
| 1308 |
+
Examples
|
| 1309 |
+
--------
|
| 1310 |
+
>>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 1311 |
+
>>> G.has_edge(0, 1) # using two nodes
|
| 1312 |
+
True
|
| 1313 |
+
>>> e = (0, 1)
|
| 1314 |
+
>>> G.has_edge(*e) # e is a 2-tuple (u, v)
|
| 1315 |
+
True
|
| 1316 |
+
>>> e = (0, 1, {"weight": 7})
|
| 1317 |
+
>>> G.has_edge(*e[:2]) # e is a 3-tuple (u, v, data_dictionary)
|
| 1318 |
+
True
|
| 1319 |
+
|
| 1320 |
+
The following syntax are equivalent:
|
| 1321 |
+
|
| 1322 |
+
>>> G.has_edge(0, 1)
|
| 1323 |
+
True
|
| 1324 |
+
>>> 1 in G[0] # though this gives KeyError if 0 not in G
|
| 1325 |
+
True
|
| 1326 |
+
|
| 1327 |
+
"""
|
| 1328 |
+
try:
|
| 1329 |
+
return v in self._adj[u]
|
| 1330 |
+
except KeyError:
|
| 1331 |
+
return False
|
| 1332 |
+
|
| 1333 |
+
def neighbors(self, n):
|
| 1334 |
+
"""Returns an iterator over all neighbors of node n.
|
| 1335 |
+
|
| 1336 |
+
This is identical to `iter(G[n])`
|
| 1337 |
+
|
| 1338 |
+
Parameters
|
| 1339 |
+
----------
|
| 1340 |
+
n : node
|
| 1341 |
+
A node in the graph
|
| 1342 |
+
|
| 1343 |
+
Returns
|
| 1344 |
+
-------
|
| 1345 |
+
neighbors : iterator
|
| 1346 |
+
An iterator over all neighbors of node n
|
| 1347 |
+
|
| 1348 |
+
Raises
|
| 1349 |
+
------
|
| 1350 |
+
NetworkXError
|
| 1351 |
+
If the node n is not in the graph.
|
| 1352 |
+
|
| 1353 |
+
Examples
|
| 1354 |
+
--------
|
| 1355 |
+
>>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 1356 |
+
>>> [n for n in G.neighbors(0)]
|
| 1357 |
+
[1]
|
| 1358 |
+
|
| 1359 |
+
Notes
|
| 1360 |
+
-----
|
| 1361 |
+
Alternate ways to access the neighbors are ``G.adj[n]`` or ``G[n]``:
|
| 1362 |
+
|
| 1363 |
+
>>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 1364 |
+
>>> G.add_edge("a", "b", weight=7)
|
| 1365 |
+
>>> G["a"]
|
| 1366 |
+
AtlasView({'b': {'weight': 7}})
|
| 1367 |
+
>>> G = nx.path_graph(4)
|
| 1368 |
+
>>> [n for n in G[0]]
|
| 1369 |
+
[1]
|
| 1370 |
+
"""
|
| 1371 |
+
try:
|
| 1372 |
+
return iter(self._adj[n])
|
| 1373 |
+
except KeyError as err:
|
| 1374 |
+
raise NetworkXError(f"The node {n} is not in the graph.") from err
|
| 1375 |
+
|
| 1376 |
+
@cached_property
|
| 1377 |
+
def edges(self):
|
| 1378 |
+
"""An EdgeView of the Graph as G.edges or G.edges().
|
| 1379 |
+
|
| 1380 |
+
edges(self, nbunch=None, data=False, default=None)
|
| 1381 |
+
|
| 1382 |
+
The EdgeView provides set-like operations on the edge-tuples
|
| 1383 |
+
as well as edge attribute lookup. When called, it also provides
|
| 1384 |
+
an EdgeDataView object which allows control of access to edge
|
| 1385 |
+
attributes (but does not provide set-like operations).
|
| 1386 |
+
Hence, `G.edges[u, v]['color']` provides the value of the color
|
| 1387 |
+
attribute for edge `(u, v)` while
|
| 1388 |
+
`for (u, v, c) in G.edges.data('color', default='red'):`
|
| 1389 |
+
iterates through all the edges yielding the color attribute
|
| 1390 |
+
with default `'red'` if no color attribute exists.
|
| 1391 |
+
|
| 1392 |
+
Parameters
|
| 1393 |
+
----------
|
| 1394 |
+
nbunch : single node, container, or all nodes (default= all nodes)
|
| 1395 |
+
The view will only report edges from these nodes.
|
| 1396 |
+
data : string or bool, optional (default=False)
|
| 1397 |
+
The edge attribute returned in 3-tuple (u, v, ddict[data]).
|
| 1398 |
+
If True, return edge attribute dict in 3-tuple (u, v, ddict).
|
| 1399 |
+
If False, return 2-tuple (u, v).
|
| 1400 |
+
default : value, optional (default=None)
|
| 1401 |
+
Value used for edges that don't have the requested attribute.
|
| 1402 |
+
Only relevant if data is not True or False.
|
| 1403 |
+
|
| 1404 |
+
Returns
|
| 1405 |
+
-------
|
| 1406 |
+
edges : EdgeView
|
| 1407 |
+
A view of edge attributes, usually it iterates over (u, v)
|
| 1408 |
+
or (u, v, d) tuples of edges, but can also be used for
|
| 1409 |
+
attribute lookup as `edges[u, v]['foo']`.
|
| 1410 |
+
|
| 1411 |
+
Notes
|
| 1412 |
+
-----
|
| 1413 |
+
Nodes in nbunch that are not in the graph will be (quietly) ignored.
|
| 1414 |
+
For directed graphs this returns the out-edges.
|
| 1415 |
+
|
| 1416 |
+
Examples
|
| 1417 |
+
--------
|
| 1418 |
+
>>> G = nx.path_graph(3) # or MultiGraph, etc
|
| 1419 |
+
>>> G.add_edge(2, 3, weight=5)
|
| 1420 |
+
>>> [e for e in G.edges]
|
| 1421 |
+
[(0, 1), (1, 2), (2, 3)]
|
| 1422 |
+
>>> G.edges.data() # default data is {} (empty dict)
|
| 1423 |
+
EdgeDataView([(0, 1, {}), (1, 2, {}), (2, 3, {'weight': 5})])
|
| 1424 |
+
>>> G.edges.data("weight", default=1)
|
| 1425 |
+
EdgeDataView([(0, 1, 1), (1, 2, 1), (2, 3, 5)])
|
| 1426 |
+
>>> G.edges([0, 3]) # only edges from these nodes
|
| 1427 |
+
EdgeDataView([(0, 1), (3, 2)])
|
| 1428 |
+
>>> G.edges(0) # only edges from node 0
|
| 1429 |
+
EdgeDataView([(0, 1)])
|
| 1430 |
+
"""
|
| 1431 |
+
return EdgeView(self)
|
| 1432 |
+
|
| 1433 |
+
def get_edge_data(self, u, v, default=None):
|
| 1434 |
+
"""Returns the attribute dictionary associated with edge (u, v).
|
| 1435 |
+
|
| 1436 |
+
This is identical to `G[u][v]` except the default is returned
|
| 1437 |
+
instead of an exception if the edge doesn't exist.
|
| 1438 |
+
|
| 1439 |
+
Parameters
|
| 1440 |
+
----------
|
| 1441 |
+
u, v : nodes
|
| 1442 |
+
default: any Python object (default=None)
|
| 1443 |
+
Value to return if the edge (u, v) is not found.
|
| 1444 |
+
|
| 1445 |
+
Returns
|
| 1446 |
+
-------
|
| 1447 |
+
edge_dict : dictionary
|
| 1448 |
+
The edge attribute dictionary.
|
| 1449 |
+
|
| 1450 |
+
Examples
|
| 1451 |
+
--------
|
| 1452 |
+
>>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 1453 |
+
>>> G[0][1]
|
| 1454 |
+
{}
|
| 1455 |
+
|
| 1456 |
+
Warning: Assigning to `G[u][v]` is not permitted.
|
| 1457 |
+
But it is safe to assign attributes `G[u][v]['foo']`
|
| 1458 |
+
|
| 1459 |
+
>>> G[0][1]["weight"] = 7
|
| 1460 |
+
>>> G[0][1]["weight"]
|
| 1461 |
+
7
|
| 1462 |
+
>>> G[1][0]["weight"]
|
| 1463 |
+
7
|
| 1464 |
+
|
| 1465 |
+
>>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 1466 |
+
>>> G.get_edge_data(0, 1) # default edge data is {}
|
| 1467 |
+
{}
|
| 1468 |
+
>>> e = (0, 1)
|
| 1469 |
+
>>> G.get_edge_data(*e) # tuple form
|
| 1470 |
+
{}
|
| 1471 |
+
>>> G.get_edge_data("a", "b", default=0) # edge not in graph, return 0
|
| 1472 |
+
0
|
| 1473 |
+
"""
|
| 1474 |
+
try:
|
| 1475 |
+
return self._adj[u][v]
|
| 1476 |
+
except KeyError:
|
| 1477 |
+
return default
|
| 1478 |
+
|
| 1479 |
+
def adjacency(self):
|
| 1480 |
+
"""Returns an iterator over (node, adjacency dict) tuples for all nodes.
|
| 1481 |
+
|
| 1482 |
+
For directed graphs, only outgoing neighbors/adjacencies are included.
|
| 1483 |
+
|
| 1484 |
+
Returns
|
| 1485 |
+
-------
|
| 1486 |
+
adj_iter : iterator
|
| 1487 |
+
An iterator over (node, adjacency dictionary) for all nodes in
|
| 1488 |
+
the graph.
|
| 1489 |
+
|
| 1490 |
+
Examples
|
| 1491 |
+
--------
|
| 1492 |
+
>>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 1493 |
+
>>> [(n, nbrdict) for n, nbrdict in G.adjacency()]
|
| 1494 |
+
[(0, {1: {}}), (1, {0: {}, 2: {}}), (2, {1: {}, 3: {}}), (3, {2: {}})]
|
| 1495 |
+
|
| 1496 |
+
"""
|
| 1497 |
+
return iter(self._adj.items())
|
| 1498 |
+
|
| 1499 |
+
@cached_property
|
| 1500 |
+
def degree(self):
|
| 1501 |
+
"""A DegreeView for the Graph as G.degree or G.degree().
|
| 1502 |
+
|
| 1503 |
+
The node degree is the number of edges adjacent to the node.
|
| 1504 |
+
The weighted node degree is the sum of the edge weights for
|
| 1505 |
+
edges incident to that node.
|
| 1506 |
+
|
| 1507 |
+
This object provides an iterator for (node, degree) as well as
|
| 1508 |
+
lookup for the degree for a single node.
|
| 1509 |
+
|
| 1510 |
+
Parameters
|
| 1511 |
+
----------
|
| 1512 |
+
nbunch : single node, container, or all nodes (default= all nodes)
|
| 1513 |
+
The view will only report edges incident to these nodes.
|
| 1514 |
+
|
| 1515 |
+
weight : string or None, optional (default=None)
|
| 1516 |
+
The name of an edge attribute that holds the numerical value used
|
| 1517 |
+
as a weight. If None, then each edge has weight 1.
|
| 1518 |
+
The degree is the sum of the edge weights adjacent to the node.
|
| 1519 |
+
|
| 1520 |
+
Returns
|
| 1521 |
+
-------
|
| 1522 |
+
DegreeView or int
|
| 1523 |
+
If multiple nodes are requested (the default), returns a `DegreeView`
|
| 1524 |
+
mapping nodes to their degree.
|
| 1525 |
+
If a single node is requested, returns the degree of the node as an integer.
|
| 1526 |
+
|
| 1527 |
+
Examples
|
| 1528 |
+
--------
|
| 1529 |
+
>>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 1530 |
+
>>> G.degree[0] # node 0 has degree 1
|
| 1531 |
+
1
|
| 1532 |
+
>>> list(G.degree([0, 1, 2]))
|
| 1533 |
+
[(0, 1), (1, 2), (2, 2)]
|
| 1534 |
+
"""
|
| 1535 |
+
return DegreeView(self)
|
| 1536 |
+
|
| 1537 |
+
def clear(self):
|
| 1538 |
+
"""Remove all nodes and edges from the graph.
|
| 1539 |
+
|
| 1540 |
+
This also removes the name, and all graph, node, and edge attributes.
|
| 1541 |
+
|
| 1542 |
+
Examples
|
| 1543 |
+
--------
|
| 1544 |
+
>>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 1545 |
+
>>> G.clear()
|
| 1546 |
+
>>> list(G.nodes)
|
| 1547 |
+
[]
|
| 1548 |
+
>>> list(G.edges)
|
| 1549 |
+
[]
|
| 1550 |
+
|
| 1551 |
+
"""
|
| 1552 |
+
self._adj.clear()
|
| 1553 |
+
self._node.clear()
|
| 1554 |
+
self.graph.clear()
|
| 1555 |
+
nx._clear_cache(self)
|
| 1556 |
+
|
| 1557 |
+
def clear_edges(self):
|
| 1558 |
+
"""Remove all edges from the graph without altering nodes.
|
| 1559 |
+
|
| 1560 |
+
Examples
|
| 1561 |
+
--------
|
| 1562 |
+
>>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 1563 |
+
>>> G.clear_edges()
|
| 1564 |
+
>>> list(G.nodes)
|
| 1565 |
+
[0, 1, 2, 3]
|
| 1566 |
+
>>> list(G.edges)
|
| 1567 |
+
[]
|
| 1568 |
+
"""
|
| 1569 |
+
for nbr_dict in self._adj.values():
|
| 1570 |
+
nbr_dict.clear()
|
| 1571 |
+
nx._clear_cache(self)
|
| 1572 |
+
|
| 1573 |
+
def is_multigraph(self):
|
| 1574 |
+
"""Returns True if graph is a multigraph, False otherwise."""
|
| 1575 |
+
return False
|
| 1576 |
+
|
| 1577 |
+
def is_directed(self):
|
| 1578 |
+
"""Returns True if graph is directed, False otherwise."""
|
| 1579 |
+
return False
|
| 1580 |
+
|
| 1581 |
+
def copy(self, as_view=False):
|
| 1582 |
+
"""Returns a copy of the graph.
|
| 1583 |
+
|
| 1584 |
+
The copy method by default returns an independent shallow copy
|
| 1585 |
+
of the graph and attributes. That is, if an attribute is a
|
| 1586 |
+
container, that container is shared by the original an the copy.
|
| 1587 |
+
Use Python's `copy.deepcopy` for new containers.
|
| 1588 |
+
|
| 1589 |
+
If `as_view` is True then a view is returned instead of a copy.
|
| 1590 |
+
|
| 1591 |
+
Notes
|
| 1592 |
+
-----
|
| 1593 |
+
All copies reproduce the graph structure, but data attributes
|
| 1594 |
+
may be handled in different ways. There are four types of copies
|
| 1595 |
+
of a graph that people might want.
|
| 1596 |
+
|
| 1597 |
+
Deepcopy -- A "deepcopy" copies the graph structure as well as
|
| 1598 |
+
all data attributes and any objects they might contain.
|
| 1599 |
+
The entire graph object is new so that changes in the copy
|
| 1600 |
+
do not affect the original object. (see Python's copy.deepcopy)
|
| 1601 |
+
|
| 1602 |
+
Data Reference (Shallow) -- For a shallow copy the graph structure
|
| 1603 |
+
is copied but the edge, node and graph attribute dicts are
|
| 1604 |
+
references to those in the original graph. This saves
|
| 1605 |
+
time and memory but could cause confusion if you change an attribute
|
| 1606 |
+
in one graph and it changes the attribute in the other.
|
| 1607 |
+
NetworkX does not provide this level of shallow copy.
|
| 1608 |
+
|
| 1609 |
+
Independent Shallow -- This copy creates new independent attribute
|
| 1610 |
+
dicts and then does a shallow copy of the attributes. That is, any
|
| 1611 |
+
attributes that are containers are shared between the new graph
|
| 1612 |
+
and the original. This is exactly what `dict.copy()` provides.
|
| 1613 |
+
You can obtain this style copy using:
|
| 1614 |
+
|
| 1615 |
+
>>> G = nx.path_graph(5)
|
| 1616 |
+
>>> H = G.copy()
|
| 1617 |
+
>>> H = G.copy(as_view=False)
|
| 1618 |
+
>>> H = nx.Graph(G)
|
| 1619 |
+
>>> H = G.__class__(G)
|
| 1620 |
+
|
| 1621 |
+
Fresh Data -- For fresh data, the graph structure is copied while
|
| 1622 |
+
new empty data attribute dicts are created. The resulting graph
|
| 1623 |
+
is independent of the original and it has no edge, node or graph
|
| 1624 |
+
attributes. Fresh copies are not enabled. Instead use:
|
| 1625 |
+
|
| 1626 |
+
>>> H = G.__class__()
|
| 1627 |
+
>>> H.add_nodes_from(G)
|
| 1628 |
+
>>> H.add_edges_from(G.edges)
|
| 1629 |
+
|
| 1630 |
+
View -- Inspired by dict-views, graph-views act like read-only
|
| 1631 |
+
versions of the original graph, providing a copy of the original
|
| 1632 |
+
structure without requiring any memory for copying the information.
|
| 1633 |
+
|
| 1634 |
+
See the Python copy module for more information on shallow
|
| 1635 |
+
and deep copies, https://docs.python.org/3/library/copy.html.
|
| 1636 |
+
|
| 1637 |
+
Parameters
|
| 1638 |
+
----------
|
| 1639 |
+
as_view : bool, optional (default=False)
|
| 1640 |
+
If True, the returned graph-view provides a read-only view
|
| 1641 |
+
of the original graph without actually copying any data.
|
| 1642 |
+
|
| 1643 |
+
Returns
|
| 1644 |
+
-------
|
| 1645 |
+
G : Graph
|
| 1646 |
+
A copy of the graph.
|
| 1647 |
+
|
| 1648 |
+
See Also
|
| 1649 |
+
--------
|
| 1650 |
+
to_directed: return a directed copy of the graph.
|
| 1651 |
+
|
| 1652 |
+
Examples
|
| 1653 |
+
--------
|
| 1654 |
+
>>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 1655 |
+
>>> H = G.copy()
|
| 1656 |
+
|
| 1657 |
+
"""
|
| 1658 |
+
if as_view is True:
|
| 1659 |
+
return nx.graphviews.generic_graph_view(self)
|
| 1660 |
+
G = self.__class__()
|
| 1661 |
+
G.graph.update(self.graph)
|
| 1662 |
+
G.add_nodes_from((n, d.copy()) for n, d in self._node.items())
|
| 1663 |
+
G.add_edges_from(
|
| 1664 |
+
(u, v, datadict.copy())
|
| 1665 |
+
for u, nbrs in self._adj.items()
|
| 1666 |
+
for v, datadict in nbrs.items()
|
| 1667 |
+
)
|
| 1668 |
+
return G
|
| 1669 |
+
|
| 1670 |
+
def to_directed(self, as_view=False):
|
| 1671 |
+
"""Returns a directed representation of the graph.
|
| 1672 |
+
|
| 1673 |
+
Returns
|
| 1674 |
+
-------
|
| 1675 |
+
G : DiGraph
|
| 1676 |
+
A directed graph with the same name, same nodes, and with
|
| 1677 |
+
each edge (u, v, data) replaced by two directed edges
|
| 1678 |
+
(u, v, data) and (v, u, data).
|
| 1679 |
+
|
| 1680 |
+
Notes
|
| 1681 |
+
-----
|
| 1682 |
+
This returns a "deepcopy" of the edge, node, and
|
| 1683 |
+
graph attributes which attempts to completely copy
|
| 1684 |
+
all of the data and references.
|
| 1685 |
+
|
| 1686 |
+
This is in contrast to the similar D=DiGraph(G) which returns a
|
| 1687 |
+
shallow copy of the data.
|
| 1688 |
+
|
| 1689 |
+
See the Python copy module for more information on shallow
|
| 1690 |
+
and deep copies, https://docs.python.org/3/library/copy.html.
|
| 1691 |
+
|
| 1692 |
+
Warning: If you have subclassed Graph to use dict-like objects
|
| 1693 |
+
in the data structure, those changes do not transfer to the
|
| 1694 |
+
DiGraph created by this method.
|
| 1695 |
+
|
| 1696 |
+
Examples
|
| 1697 |
+
--------
|
| 1698 |
+
>>> G = nx.Graph() # or MultiGraph, etc
|
| 1699 |
+
>>> G.add_edge(0, 1)
|
| 1700 |
+
>>> H = G.to_directed()
|
| 1701 |
+
>>> list(H.edges)
|
| 1702 |
+
[(0, 1), (1, 0)]
|
| 1703 |
+
|
| 1704 |
+
If already directed, return a (deep) copy
|
| 1705 |
+
|
| 1706 |
+
>>> G = nx.DiGraph() # or MultiDiGraph, etc
|
| 1707 |
+
>>> G.add_edge(0, 1)
|
| 1708 |
+
>>> H = G.to_directed()
|
| 1709 |
+
>>> list(H.edges)
|
| 1710 |
+
[(0, 1)]
|
| 1711 |
+
"""
|
| 1712 |
+
graph_class = self.to_directed_class()
|
| 1713 |
+
if as_view is True:
|
| 1714 |
+
return nx.graphviews.generic_graph_view(self, graph_class)
|
| 1715 |
+
# deepcopy when not a view
|
| 1716 |
+
G = graph_class()
|
| 1717 |
+
G.graph.update(deepcopy(self.graph))
|
| 1718 |
+
G.add_nodes_from((n, deepcopy(d)) for n, d in self._node.items())
|
| 1719 |
+
G.add_edges_from(
|
| 1720 |
+
(u, v, deepcopy(data))
|
| 1721 |
+
for u, nbrs in self._adj.items()
|
| 1722 |
+
for v, data in nbrs.items()
|
| 1723 |
+
)
|
| 1724 |
+
return G
|
| 1725 |
+
|
| 1726 |
+
def to_undirected(self, as_view=False):
|
| 1727 |
+
"""Returns an undirected copy of the graph.
|
| 1728 |
+
|
| 1729 |
+
Parameters
|
| 1730 |
+
----------
|
| 1731 |
+
as_view : bool (optional, default=False)
|
| 1732 |
+
If True return a view of the original undirected graph.
|
| 1733 |
+
|
| 1734 |
+
Returns
|
| 1735 |
+
-------
|
| 1736 |
+
G : Graph/MultiGraph
|
| 1737 |
+
A deepcopy of the graph.
|
| 1738 |
+
|
| 1739 |
+
See Also
|
| 1740 |
+
--------
|
| 1741 |
+
Graph, copy, add_edge, add_edges_from
|
| 1742 |
+
|
| 1743 |
+
Notes
|
| 1744 |
+
-----
|
| 1745 |
+
This returns a "deepcopy" of the edge, node, and
|
| 1746 |
+
graph attributes which attempts to completely copy
|
| 1747 |
+
all of the data and references.
|
| 1748 |
+
|
| 1749 |
+
This is in contrast to the similar `G = nx.DiGraph(D)` which returns a
|
| 1750 |
+
shallow copy of the data.
|
| 1751 |
+
|
| 1752 |
+
See the Python copy module for more information on shallow
|
| 1753 |
+
and deep copies, https://docs.python.org/3/library/copy.html.
|
| 1754 |
+
|
| 1755 |
+
Warning: If you have subclassed DiGraph to use dict-like objects
|
| 1756 |
+
in the data structure, those changes do not transfer to the
|
| 1757 |
+
Graph created by this method.
|
| 1758 |
+
|
| 1759 |
+
Examples
|
| 1760 |
+
--------
|
| 1761 |
+
>>> G = nx.path_graph(2) # or MultiGraph, etc
|
| 1762 |
+
>>> H = G.to_directed()
|
| 1763 |
+
>>> list(H.edges)
|
| 1764 |
+
[(0, 1), (1, 0)]
|
| 1765 |
+
>>> G2 = H.to_undirected()
|
| 1766 |
+
>>> list(G2.edges)
|
| 1767 |
+
[(0, 1)]
|
| 1768 |
+
"""
|
| 1769 |
+
graph_class = self.to_undirected_class()
|
| 1770 |
+
if as_view is True:
|
| 1771 |
+
return nx.graphviews.generic_graph_view(self, graph_class)
|
| 1772 |
+
# deepcopy when not a view
|
| 1773 |
+
G = graph_class()
|
| 1774 |
+
G.graph.update(deepcopy(self.graph))
|
| 1775 |
+
G.add_nodes_from((n, deepcopy(d)) for n, d in self._node.items())
|
| 1776 |
+
G.add_edges_from(
|
| 1777 |
+
(u, v, deepcopy(d))
|
| 1778 |
+
for u, nbrs in self._adj.items()
|
| 1779 |
+
for v, d in nbrs.items()
|
| 1780 |
+
)
|
| 1781 |
+
return G
|
| 1782 |
+
|
| 1783 |
+
def subgraph(self, nodes):
|
| 1784 |
+
"""Returns a SubGraph view of the subgraph induced on `nodes`.
|
| 1785 |
+
|
| 1786 |
+
The induced subgraph of the graph contains the nodes in `nodes`
|
| 1787 |
+
and the edges between those nodes.
|
| 1788 |
+
|
| 1789 |
+
Parameters
|
| 1790 |
+
----------
|
| 1791 |
+
nodes : list, iterable
|
| 1792 |
+
A container of nodes which will be iterated through once.
|
| 1793 |
+
|
| 1794 |
+
Returns
|
| 1795 |
+
-------
|
| 1796 |
+
G : SubGraph View
|
| 1797 |
+
A subgraph view of the graph. The graph structure cannot be
|
| 1798 |
+
changed but node/edge attributes can and are shared with the
|
| 1799 |
+
original graph.
|
| 1800 |
+
|
| 1801 |
+
Notes
|
| 1802 |
+
-----
|
| 1803 |
+
The graph, edge and node attributes are shared with the original graph.
|
| 1804 |
+
Changes to the graph structure is ruled out by the view, but changes
|
| 1805 |
+
to attributes are reflected in the original graph.
|
| 1806 |
+
|
| 1807 |
+
To create a subgraph with its own copy of the edge/node attributes use:
|
| 1808 |
+
G.subgraph(nodes).copy()
|
| 1809 |
+
|
| 1810 |
+
For an inplace reduction of a graph to a subgraph you can remove nodes:
|
| 1811 |
+
G.remove_nodes_from([n for n in G if n not in set(nodes)])
|
| 1812 |
+
|
| 1813 |
+
Subgraph views are sometimes NOT what you want. In most cases where
|
| 1814 |
+
you want to do more than simply look at the induced edges, it makes
|
| 1815 |
+
more sense to just create the subgraph as its own graph with code like:
|
| 1816 |
+
|
| 1817 |
+
::
|
| 1818 |
+
|
| 1819 |
+
# Create a subgraph SG based on a (possibly multigraph) G
|
| 1820 |
+
SG = G.__class__()
|
| 1821 |
+
SG.add_nodes_from((n, G.nodes[n]) for n in largest_wcc)
|
| 1822 |
+
if SG.is_multigraph():
|
| 1823 |
+
SG.add_edges_from(
|
| 1824 |
+
(n, nbr, key, d)
|
| 1825 |
+
for n, nbrs in G.adj.items()
|
| 1826 |
+
if n in largest_wcc
|
| 1827 |
+
for nbr, keydict in nbrs.items()
|
| 1828 |
+
if nbr in largest_wcc
|
| 1829 |
+
for key, d in keydict.items()
|
| 1830 |
+
)
|
| 1831 |
+
else:
|
| 1832 |
+
SG.add_edges_from(
|
| 1833 |
+
(n, nbr, d)
|
| 1834 |
+
for n, nbrs in G.adj.items()
|
| 1835 |
+
if n in largest_wcc
|
| 1836 |
+
for nbr, d in nbrs.items()
|
| 1837 |
+
if nbr in largest_wcc
|
| 1838 |
+
)
|
| 1839 |
+
SG.graph.update(G.graph)
|
| 1840 |
+
|
| 1841 |
+
Examples
|
| 1842 |
+
--------
|
| 1843 |
+
>>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 1844 |
+
>>> H = G.subgraph([0, 1, 2])
|
| 1845 |
+
>>> list(H.edges)
|
| 1846 |
+
[(0, 1), (1, 2)]
|
| 1847 |
+
"""
|
| 1848 |
+
induced_nodes = nx.filters.show_nodes(self.nbunch_iter(nodes))
|
| 1849 |
+
# if already a subgraph, don't make a chain
|
| 1850 |
+
subgraph = nx.subgraph_view
|
| 1851 |
+
if hasattr(self, "_NODE_OK"):
|
| 1852 |
+
return subgraph(
|
| 1853 |
+
self._graph, filter_node=induced_nodes, filter_edge=self._EDGE_OK
|
| 1854 |
+
)
|
| 1855 |
+
return subgraph(self, filter_node=induced_nodes)
|
| 1856 |
+
|
| 1857 |
+
def edge_subgraph(self, edges):
|
| 1858 |
+
"""Returns the subgraph induced by the specified edges.
|
| 1859 |
+
|
| 1860 |
+
The induced subgraph contains each edge in `edges` and each
|
| 1861 |
+
node incident to any one of those edges.
|
| 1862 |
+
|
| 1863 |
+
Parameters
|
| 1864 |
+
----------
|
| 1865 |
+
edges : iterable
|
| 1866 |
+
An iterable of edges in this graph.
|
| 1867 |
+
|
| 1868 |
+
Returns
|
| 1869 |
+
-------
|
| 1870 |
+
G : Graph
|
| 1871 |
+
An edge-induced subgraph of this graph with the same edge
|
| 1872 |
+
attributes.
|
| 1873 |
+
|
| 1874 |
+
Notes
|
| 1875 |
+
-----
|
| 1876 |
+
The graph, edge, and node attributes in the returned subgraph
|
| 1877 |
+
view are references to the corresponding attributes in the original
|
| 1878 |
+
graph. The view is read-only.
|
| 1879 |
+
|
| 1880 |
+
To create a full graph version of the subgraph with its own copy
|
| 1881 |
+
of the edge or node attributes, use::
|
| 1882 |
+
|
| 1883 |
+
G.edge_subgraph(edges).copy()
|
| 1884 |
+
|
| 1885 |
+
Examples
|
| 1886 |
+
--------
|
| 1887 |
+
>>> G = nx.path_graph(5)
|
| 1888 |
+
>>> H = G.edge_subgraph([(0, 1), (3, 4)])
|
| 1889 |
+
>>> list(H.nodes)
|
| 1890 |
+
[0, 1, 3, 4]
|
| 1891 |
+
>>> list(H.edges)
|
| 1892 |
+
[(0, 1), (3, 4)]
|
| 1893 |
+
|
| 1894 |
+
"""
|
| 1895 |
+
return nx.edge_subgraph(self, edges)
|
| 1896 |
+
|
| 1897 |
+
def size(self, weight=None):
|
| 1898 |
+
"""Returns the number of edges or total of all edge weights.
|
| 1899 |
+
|
| 1900 |
+
Parameters
|
| 1901 |
+
----------
|
| 1902 |
+
weight : string or None, optional (default=None)
|
| 1903 |
+
The edge attribute that holds the numerical value used
|
| 1904 |
+
as a weight. If None, then each edge has weight 1.
|
| 1905 |
+
|
| 1906 |
+
Returns
|
| 1907 |
+
-------
|
| 1908 |
+
size : numeric
|
| 1909 |
+
The number of edges or
|
| 1910 |
+
(if weight keyword is provided) the total weight sum.
|
| 1911 |
+
|
| 1912 |
+
If weight is None, returns an int. Otherwise a float
|
| 1913 |
+
(or more general numeric if the weights are more general).
|
| 1914 |
+
|
| 1915 |
+
See Also
|
| 1916 |
+
--------
|
| 1917 |
+
number_of_edges
|
| 1918 |
+
|
| 1919 |
+
Examples
|
| 1920 |
+
--------
|
| 1921 |
+
>>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 1922 |
+
>>> G.size()
|
| 1923 |
+
3
|
| 1924 |
+
|
| 1925 |
+
>>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 1926 |
+
>>> G.add_edge("a", "b", weight=2)
|
| 1927 |
+
>>> G.add_edge("b", "c", weight=4)
|
| 1928 |
+
>>> G.size()
|
| 1929 |
+
2
|
| 1930 |
+
>>> G.size(weight="weight")
|
| 1931 |
+
6.0
|
| 1932 |
+
"""
|
| 1933 |
+
s = sum(d for v, d in self.degree(weight=weight))
|
| 1934 |
+
# If `weight` is None, the sum of the degrees is guaranteed to be
|
| 1935 |
+
# even, so we can perform integer division and hence return an
|
| 1936 |
+
# integer. Otherwise, the sum of the weighted degrees is not
|
| 1937 |
+
# guaranteed to be an integer, so we perform "real" division.
|
| 1938 |
+
return s // 2 if weight is None else s / 2
|
| 1939 |
+
|
| 1940 |
+
def number_of_edges(self, u=None, v=None):
|
| 1941 |
+
"""Returns the number of edges between two nodes.
|
| 1942 |
+
|
| 1943 |
+
Parameters
|
| 1944 |
+
----------
|
| 1945 |
+
u, v : nodes, optional (default=all edges)
|
| 1946 |
+
If u and v are specified, return the number of edges between
|
| 1947 |
+
u and v. Otherwise return the total number of all edges.
|
| 1948 |
+
|
| 1949 |
+
Returns
|
| 1950 |
+
-------
|
| 1951 |
+
nedges : int
|
| 1952 |
+
The number of edges in the graph. If nodes `u` and `v` are
|
| 1953 |
+
specified return the number of edges between those nodes. If
|
| 1954 |
+
the graph is directed, this only returns the number of edges
|
| 1955 |
+
from `u` to `v`.
|
| 1956 |
+
|
| 1957 |
+
See Also
|
| 1958 |
+
--------
|
| 1959 |
+
size
|
| 1960 |
+
|
| 1961 |
+
Examples
|
| 1962 |
+
--------
|
| 1963 |
+
For undirected graphs, this method counts the total number of
|
| 1964 |
+
edges in the graph:
|
| 1965 |
+
|
| 1966 |
+
>>> G = nx.path_graph(4)
|
| 1967 |
+
>>> G.number_of_edges()
|
| 1968 |
+
3
|
| 1969 |
+
|
| 1970 |
+
If you specify two nodes, this counts the total number of edges
|
| 1971 |
+
joining the two nodes:
|
| 1972 |
+
|
| 1973 |
+
>>> G.number_of_edges(0, 1)
|
| 1974 |
+
1
|
| 1975 |
+
|
| 1976 |
+
For directed graphs, this method can count the total number of
|
| 1977 |
+
directed edges from `u` to `v`:
|
| 1978 |
+
|
| 1979 |
+
>>> G = nx.DiGraph()
|
| 1980 |
+
>>> G.add_edge(0, 1)
|
| 1981 |
+
>>> G.add_edge(1, 0)
|
| 1982 |
+
>>> G.number_of_edges(0, 1)
|
| 1983 |
+
1
|
| 1984 |
+
|
| 1985 |
+
"""
|
| 1986 |
+
if u is None:
|
| 1987 |
+
return int(self.size())
|
| 1988 |
+
if v in self._adj[u]:
|
| 1989 |
+
return 1
|
| 1990 |
+
return 0
|
| 1991 |
+
|
| 1992 |
+
def nbunch_iter(self, nbunch=None):
|
| 1993 |
+
"""Returns an iterator over nodes contained in nbunch that are
|
| 1994 |
+
also in the graph.
|
| 1995 |
+
|
| 1996 |
+
The nodes in nbunch are checked for membership in the graph
|
| 1997 |
+
and if not are silently ignored.
|
| 1998 |
+
|
| 1999 |
+
Parameters
|
| 2000 |
+
----------
|
| 2001 |
+
nbunch : single node, container, or all nodes (default= all nodes)
|
| 2002 |
+
The view will only report edges incident to these nodes.
|
| 2003 |
+
|
| 2004 |
+
Returns
|
| 2005 |
+
-------
|
| 2006 |
+
niter : iterator
|
| 2007 |
+
An iterator over nodes in nbunch that are also in the graph.
|
| 2008 |
+
If nbunch is None, iterate over all nodes in the graph.
|
| 2009 |
+
|
| 2010 |
+
Raises
|
| 2011 |
+
------
|
| 2012 |
+
NetworkXError
|
| 2013 |
+
If nbunch is not a node or sequence of nodes.
|
| 2014 |
+
If a node in nbunch is not hashable.
|
| 2015 |
+
|
| 2016 |
+
See Also
|
| 2017 |
+
--------
|
| 2018 |
+
Graph.__iter__
|
| 2019 |
+
|
| 2020 |
+
Notes
|
| 2021 |
+
-----
|
| 2022 |
+
When nbunch is an iterator, the returned iterator yields values
|
| 2023 |
+
directly from nbunch, becoming exhausted when nbunch is exhausted.
|
| 2024 |
+
|
| 2025 |
+
To test whether nbunch is a single node, one can use
|
| 2026 |
+
"if nbunch in self:", even after processing with this routine.
|
| 2027 |
+
|
| 2028 |
+
If nbunch is not a node or a (possibly empty) sequence/iterator
|
| 2029 |
+
or None, a :exc:`NetworkXError` is raised. Also, if any object in
|
| 2030 |
+
nbunch is not hashable, a :exc:`NetworkXError` is raised.
|
| 2031 |
+
"""
|
| 2032 |
+
if nbunch is None: # include all nodes via iterator
|
| 2033 |
+
bunch = iter(self._adj)
|
| 2034 |
+
elif nbunch in self: # if nbunch is a single node
|
| 2035 |
+
bunch = iter([nbunch])
|
| 2036 |
+
else: # if nbunch is a sequence of nodes
|
| 2037 |
+
|
| 2038 |
+
def bunch_iter(nlist, adj):
|
| 2039 |
+
try:
|
| 2040 |
+
for n in nlist:
|
| 2041 |
+
if n in adj:
|
| 2042 |
+
yield n
|
| 2043 |
+
except TypeError as err:
|
| 2044 |
+
exc, message = err, err.args[0]
|
| 2045 |
+
# capture error for non-sequence/iterator nbunch.
|
| 2046 |
+
if "iter" in message:
|
| 2047 |
+
exc = NetworkXError(
|
| 2048 |
+
"nbunch is not a node or a sequence of nodes."
|
| 2049 |
+
)
|
| 2050 |
+
# capture error for unhashable node.
|
| 2051 |
+
if "hashable" in message:
|
| 2052 |
+
exc = NetworkXError(
|
| 2053 |
+
f"Node {n} in sequence nbunch is not a valid node."
|
| 2054 |
+
)
|
| 2055 |
+
raise exc
|
| 2056 |
+
|
| 2057 |
+
bunch = bunch_iter(nbunch, self._adj)
|
| 2058 |
+
return bunch
|
.venv/lib/python3.11/site-packages/networkx/classes/graphviews.py
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""View of Graphs as SubGraph, Reverse, Directed, Undirected.
|
| 2 |
+
|
| 3 |
+
In some algorithms it is convenient to temporarily morph
|
| 4 |
+
a graph to exclude some nodes or edges. It should be better
|
| 5 |
+
to do that via a view than to remove and then re-add.
|
| 6 |
+
In other algorithms it is convenient to temporarily morph
|
| 7 |
+
a graph to reverse directed edges, or treat a directed graph
|
| 8 |
+
as undirected, etc. This module provides those graph views.
|
| 9 |
+
|
| 10 |
+
The resulting views are essentially read-only graphs that
|
| 11 |
+
report data from the original graph object. We provide an
|
| 12 |
+
attribute G._graph which points to the underlying graph object.
|
| 13 |
+
|
| 14 |
+
Note: Since graphviews look like graphs, one can end up with
|
| 15 |
+
view-of-view-of-view chains. Be careful with chains because
|
| 16 |
+
they become very slow with about 15 nested views.
|
| 17 |
+
For the common simple case of node induced subgraphs created
|
| 18 |
+
from the graph class, we short-cut the chain by returning a
|
| 19 |
+
subgraph of the original graph directly rather than a subgraph
|
| 20 |
+
of a subgraph. We are careful not to disrupt any edge filter in
|
| 21 |
+
the middle subgraph. In general, determining how to short-cut
|
| 22 |
+
the chain is tricky and much harder with restricted_views than
|
| 23 |
+
with induced subgraphs.
|
| 24 |
+
Often it is easiest to use .copy() to avoid chains.
|
| 25 |
+
"""
|
| 26 |
+
|
| 27 |
+
import networkx as nx
|
| 28 |
+
from networkx.classes.coreviews import (
|
| 29 |
+
FilterAdjacency,
|
| 30 |
+
FilterAtlas,
|
| 31 |
+
FilterMultiAdjacency,
|
| 32 |
+
UnionAdjacency,
|
| 33 |
+
UnionMultiAdjacency,
|
| 34 |
+
)
|
| 35 |
+
from networkx.classes.filters import no_filter
|
| 36 |
+
from networkx.exception import NetworkXError
|
| 37 |
+
from networkx.utils import not_implemented_for
|
| 38 |
+
|
| 39 |
+
__all__ = ["generic_graph_view", "subgraph_view", "reverse_view"]
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def generic_graph_view(G, create_using=None):
|
| 43 |
+
"""Returns a read-only view of `G`.
|
| 44 |
+
|
| 45 |
+
The graph `G` and its attributes are not copied but viewed through the new graph object
|
| 46 |
+
of the same class as `G` (or of the class specified in `create_using`).
|
| 47 |
+
|
| 48 |
+
Parameters
|
| 49 |
+
----------
|
| 50 |
+
G : graph
|
| 51 |
+
A directed/undirected graph/multigraph.
|
| 52 |
+
|
| 53 |
+
create_using : NetworkX graph constructor, optional (default=None)
|
| 54 |
+
Graph type to create. If graph instance, then cleared before populated.
|
| 55 |
+
If `None`, then the appropriate Graph type is inferred from `G`.
|
| 56 |
+
|
| 57 |
+
Returns
|
| 58 |
+
-------
|
| 59 |
+
newG : graph
|
| 60 |
+
A view of the input graph `G` and its attributes as viewed through
|
| 61 |
+
the `create_using` class.
|
| 62 |
+
|
| 63 |
+
Raises
|
| 64 |
+
------
|
| 65 |
+
NetworkXError
|
| 66 |
+
If `G` is a multigraph (or multidigraph) but `create_using` is not, or vice versa.
|
| 67 |
+
|
| 68 |
+
Notes
|
| 69 |
+
-----
|
| 70 |
+
The returned graph view is read-only (cannot modify the graph).
|
| 71 |
+
Yet the view reflects any changes in `G`. The intent is to mimic dict views.
|
| 72 |
+
|
| 73 |
+
Examples
|
| 74 |
+
--------
|
| 75 |
+
>>> G = nx.Graph()
|
| 76 |
+
>>> G.add_edge(1, 2, weight=0.3)
|
| 77 |
+
>>> G.add_edge(2, 3, weight=0.5)
|
| 78 |
+
>>> G.edges(data=True)
|
| 79 |
+
EdgeDataView([(1, 2, {'weight': 0.3}), (2, 3, {'weight': 0.5})])
|
| 80 |
+
|
| 81 |
+
The view exposes the attributes from the original graph.
|
| 82 |
+
|
| 83 |
+
>>> viewG = nx.graphviews.generic_graph_view(G)
|
| 84 |
+
>>> viewG.edges(data=True)
|
| 85 |
+
EdgeDataView([(1, 2, {'weight': 0.3}), (2, 3, {'weight': 0.5})])
|
| 86 |
+
|
| 87 |
+
Changes to `G` are reflected in `viewG`.
|
| 88 |
+
|
| 89 |
+
>>> G.remove_edge(2, 3)
|
| 90 |
+
>>> G.edges(data=True)
|
| 91 |
+
EdgeDataView([(1, 2, {'weight': 0.3})])
|
| 92 |
+
|
| 93 |
+
>>> viewG.edges(data=True)
|
| 94 |
+
EdgeDataView([(1, 2, {'weight': 0.3})])
|
| 95 |
+
|
| 96 |
+
We can change the graph type with the `create_using` parameter.
|
| 97 |
+
|
| 98 |
+
>>> type(G)
|
| 99 |
+
<class 'networkx.classes.graph.Graph'>
|
| 100 |
+
>>> viewDG = nx.graphviews.generic_graph_view(G, create_using=nx.DiGraph)
|
| 101 |
+
>>> type(viewDG)
|
| 102 |
+
<class 'networkx.classes.digraph.DiGraph'>
|
| 103 |
+
"""
|
| 104 |
+
if create_using is None:
|
| 105 |
+
newG = G.__class__()
|
| 106 |
+
else:
|
| 107 |
+
newG = nx.empty_graph(0, create_using)
|
| 108 |
+
if G.is_multigraph() != newG.is_multigraph():
|
| 109 |
+
raise NetworkXError("Multigraph for G must agree with create_using")
|
| 110 |
+
newG = nx.freeze(newG)
|
| 111 |
+
|
| 112 |
+
# create view by assigning attributes from G
|
| 113 |
+
newG._graph = G
|
| 114 |
+
newG.graph = G.graph
|
| 115 |
+
|
| 116 |
+
newG._node = G._node
|
| 117 |
+
if newG.is_directed():
|
| 118 |
+
if G.is_directed():
|
| 119 |
+
newG._succ = G._succ
|
| 120 |
+
newG._pred = G._pred
|
| 121 |
+
# newG._adj is synced with _succ
|
| 122 |
+
else:
|
| 123 |
+
newG._succ = G._adj
|
| 124 |
+
newG._pred = G._adj
|
| 125 |
+
# newG._adj is synced with _succ
|
| 126 |
+
elif G.is_directed():
|
| 127 |
+
if G.is_multigraph():
|
| 128 |
+
newG._adj = UnionMultiAdjacency(G._succ, G._pred)
|
| 129 |
+
else:
|
| 130 |
+
newG._adj = UnionAdjacency(G._succ, G._pred)
|
| 131 |
+
else:
|
| 132 |
+
newG._adj = G._adj
|
| 133 |
+
return newG
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
def subgraph_view(G, *, filter_node=no_filter, filter_edge=no_filter):
|
| 137 |
+
"""View of `G` applying a filter on nodes and edges.
|
| 138 |
+
|
| 139 |
+
`subgraph_view` provides a read-only view of the input graph that excludes
|
| 140 |
+
nodes and edges based on the outcome of two filter functions `filter_node`
|
| 141 |
+
and `filter_edge`.
|
| 142 |
+
|
| 143 |
+
The `filter_node` function takes one argument --- the node --- and returns
|
| 144 |
+
`True` if the node should be included in the subgraph, and `False` if it
|
| 145 |
+
should not be included.
|
| 146 |
+
|
| 147 |
+
The `filter_edge` function takes two (or three arguments if `G` is a
|
| 148 |
+
multi-graph) --- the nodes describing an edge, plus the edge-key if
|
| 149 |
+
parallel edges are possible --- and returns `True` if the edge should be
|
| 150 |
+
included in the subgraph, and `False` if it should not be included.
|
| 151 |
+
|
| 152 |
+
Both node and edge filter functions are called on graph elements as they
|
| 153 |
+
are queried, meaning there is no up-front cost to creating the view.
|
| 154 |
+
|
| 155 |
+
Parameters
|
| 156 |
+
----------
|
| 157 |
+
G : networkx.Graph
|
| 158 |
+
A directed/undirected graph/multigraph
|
| 159 |
+
|
| 160 |
+
filter_node : callable, optional
|
| 161 |
+
A function taking a node as input, which returns `True` if the node
|
| 162 |
+
should appear in the view.
|
| 163 |
+
|
| 164 |
+
filter_edge : callable, optional
|
| 165 |
+
A function taking as input the two nodes describing an edge (plus the
|
| 166 |
+
edge-key if `G` is a multi-graph), which returns `True` if the edge
|
| 167 |
+
should appear in the view.
|
| 168 |
+
|
| 169 |
+
Returns
|
| 170 |
+
-------
|
| 171 |
+
graph : networkx.Graph
|
| 172 |
+
A read-only graph view of the input graph.
|
| 173 |
+
|
| 174 |
+
Examples
|
| 175 |
+
--------
|
| 176 |
+
>>> G = nx.path_graph(6)
|
| 177 |
+
|
| 178 |
+
Filter functions operate on the node, and return `True` if the node should
|
| 179 |
+
appear in the view:
|
| 180 |
+
|
| 181 |
+
>>> def filter_node(n1):
|
| 182 |
+
... return n1 != 5
|
| 183 |
+
>>> view = nx.subgraph_view(G, filter_node=filter_node)
|
| 184 |
+
>>> view.nodes()
|
| 185 |
+
NodeView((0, 1, 2, 3, 4))
|
| 186 |
+
|
| 187 |
+
We can use a closure pattern to filter graph elements based on additional
|
| 188 |
+
data --- for example, filtering on edge data attached to the graph:
|
| 189 |
+
|
| 190 |
+
>>> G[3][4]["cross_me"] = False
|
| 191 |
+
>>> def filter_edge(n1, n2):
|
| 192 |
+
... return G[n1][n2].get("cross_me", True)
|
| 193 |
+
>>> view = nx.subgraph_view(G, filter_edge=filter_edge)
|
| 194 |
+
>>> view.edges()
|
| 195 |
+
EdgeView([(0, 1), (1, 2), (2, 3), (4, 5)])
|
| 196 |
+
|
| 197 |
+
>>> view = nx.subgraph_view(
|
| 198 |
+
... G,
|
| 199 |
+
... filter_node=filter_node,
|
| 200 |
+
... filter_edge=filter_edge,
|
| 201 |
+
... )
|
| 202 |
+
>>> view.nodes()
|
| 203 |
+
NodeView((0, 1, 2, 3, 4))
|
| 204 |
+
>>> view.edges()
|
| 205 |
+
EdgeView([(0, 1), (1, 2), (2, 3)])
|
| 206 |
+
"""
|
| 207 |
+
newG = nx.freeze(G.__class__())
|
| 208 |
+
newG._NODE_OK = filter_node
|
| 209 |
+
newG._EDGE_OK = filter_edge
|
| 210 |
+
|
| 211 |
+
# create view by assigning attributes from G
|
| 212 |
+
newG._graph = G
|
| 213 |
+
newG.graph = G.graph
|
| 214 |
+
|
| 215 |
+
newG._node = FilterAtlas(G._node, filter_node)
|
| 216 |
+
if G.is_multigraph():
|
| 217 |
+
Adj = FilterMultiAdjacency
|
| 218 |
+
|
| 219 |
+
def reverse_edge(u, v, k=None):
|
| 220 |
+
return filter_edge(v, u, k)
|
| 221 |
+
|
| 222 |
+
else:
|
| 223 |
+
Adj = FilterAdjacency
|
| 224 |
+
|
| 225 |
+
def reverse_edge(u, v, k=None):
|
| 226 |
+
return filter_edge(v, u)
|
| 227 |
+
|
| 228 |
+
if G.is_directed():
|
| 229 |
+
newG._succ = Adj(G._succ, filter_node, filter_edge)
|
| 230 |
+
newG._pred = Adj(G._pred, filter_node, reverse_edge)
|
| 231 |
+
# newG._adj is synced with _succ
|
| 232 |
+
else:
|
| 233 |
+
newG._adj = Adj(G._adj, filter_node, filter_edge)
|
| 234 |
+
return newG
|
| 235 |
+
|
| 236 |
+
|
| 237 |
+
@not_implemented_for("undirected")
|
| 238 |
+
def reverse_view(G):
|
| 239 |
+
"""View of `G` with edge directions reversed
|
| 240 |
+
|
| 241 |
+
`reverse_view` returns a read-only view of the input graph where
|
| 242 |
+
edge directions are reversed.
|
| 243 |
+
|
| 244 |
+
Identical to digraph.reverse(copy=False)
|
| 245 |
+
|
| 246 |
+
Parameters
|
| 247 |
+
----------
|
| 248 |
+
G : networkx.DiGraph
|
| 249 |
+
|
| 250 |
+
Returns
|
| 251 |
+
-------
|
| 252 |
+
graph : networkx.DiGraph
|
| 253 |
+
|
| 254 |
+
Examples
|
| 255 |
+
--------
|
| 256 |
+
>>> G = nx.DiGraph()
|
| 257 |
+
>>> G.add_edge(1, 2)
|
| 258 |
+
>>> G.add_edge(2, 3)
|
| 259 |
+
>>> G.edges()
|
| 260 |
+
OutEdgeView([(1, 2), (2, 3)])
|
| 261 |
+
|
| 262 |
+
>>> view = nx.reverse_view(G)
|
| 263 |
+
>>> view.edges()
|
| 264 |
+
OutEdgeView([(2, 1), (3, 2)])
|
| 265 |
+
"""
|
| 266 |
+
newG = generic_graph_view(G)
|
| 267 |
+
newG._succ, newG._pred = G._pred, G._succ
|
| 268 |
+
# newG._adj is synced with _succ
|
| 269 |
+
return newG
|
.venv/lib/python3.11/site-packages/networkx/classes/multidigraph.py
ADDED
|
@@ -0,0 +1,966 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Base class for MultiDiGraph."""
|
| 2 |
+
|
| 3 |
+
from copy import deepcopy
|
| 4 |
+
from functools import cached_property
|
| 5 |
+
|
| 6 |
+
import networkx as nx
|
| 7 |
+
from networkx import convert
|
| 8 |
+
from networkx.classes.coreviews import MultiAdjacencyView
|
| 9 |
+
from networkx.classes.digraph import DiGraph
|
| 10 |
+
from networkx.classes.multigraph import MultiGraph
|
| 11 |
+
from networkx.classes.reportviews import (
|
| 12 |
+
DiMultiDegreeView,
|
| 13 |
+
InMultiDegreeView,
|
| 14 |
+
InMultiEdgeView,
|
| 15 |
+
OutMultiDegreeView,
|
| 16 |
+
OutMultiEdgeView,
|
| 17 |
+
)
|
| 18 |
+
from networkx.exception import NetworkXError
|
| 19 |
+
|
| 20 |
+
__all__ = ["MultiDiGraph"]
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
class MultiDiGraph(MultiGraph, DiGraph):
|
| 24 |
+
"""A directed graph class that can store multiedges.
|
| 25 |
+
|
| 26 |
+
Multiedges are multiple edges between two nodes. Each edge
|
| 27 |
+
can hold optional data or attributes.
|
| 28 |
+
|
| 29 |
+
A MultiDiGraph holds directed edges. Self loops are allowed.
|
| 30 |
+
|
| 31 |
+
Nodes can be arbitrary (hashable) Python objects with optional
|
| 32 |
+
key/value attributes. By convention `None` is not used as a node.
|
| 33 |
+
|
| 34 |
+
Edges are represented as links between nodes with optional
|
| 35 |
+
key/value attributes.
|
| 36 |
+
|
| 37 |
+
Parameters
|
| 38 |
+
----------
|
| 39 |
+
incoming_graph_data : input graph (optional, default: None)
|
| 40 |
+
Data to initialize graph. If None (default) an empty
|
| 41 |
+
graph is created. The data can be any format that is supported
|
| 42 |
+
by the to_networkx_graph() function, currently including edge list,
|
| 43 |
+
dict of dicts, dict of lists, NetworkX graph, 2D NumPy array, SciPy
|
| 44 |
+
sparse matrix, or PyGraphviz graph.
|
| 45 |
+
|
| 46 |
+
multigraph_input : bool or None (default None)
|
| 47 |
+
Note: Only used when `incoming_graph_data` is a dict.
|
| 48 |
+
If True, `incoming_graph_data` is assumed to be a
|
| 49 |
+
dict-of-dict-of-dict-of-dict structure keyed by
|
| 50 |
+
node to neighbor to edge keys to edge data for multi-edges.
|
| 51 |
+
A NetworkXError is raised if this is not the case.
|
| 52 |
+
If False, :func:`to_networkx_graph` is used to try to determine
|
| 53 |
+
the dict's graph data structure as either a dict-of-dict-of-dict
|
| 54 |
+
keyed by node to neighbor to edge data, or a dict-of-iterable
|
| 55 |
+
keyed by node to neighbors.
|
| 56 |
+
If None, the treatment for True is tried, but if it fails,
|
| 57 |
+
the treatment for False is tried.
|
| 58 |
+
|
| 59 |
+
attr : keyword arguments, optional (default= no attributes)
|
| 60 |
+
Attributes to add to graph as key=value pairs.
|
| 61 |
+
|
| 62 |
+
See Also
|
| 63 |
+
--------
|
| 64 |
+
Graph
|
| 65 |
+
DiGraph
|
| 66 |
+
MultiGraph
|
| 67 |
+
|
| 68 |
+
Examples
|
| 69 |
+
--------
|
| 70 |
+
Create an empty graph structure (a "null graph") with no nodes and
|
| 71 |
+
no edges.
|
| 72 |
+
|
| 73 |
+
>>> G = nx.MultiDiGraph()
|
| 74 |
+
|
| 75 |
+
G can be grown in several ways.
|
| 76 |
+
|
| 77 |
+
**Nodes:**
|
| 78 |
+
|
| 79 |
+
Add one node at a time:
|
| 80 |
+
|
| 81 |
+
>>> G.add_node(1)
|
| 82 |
+
|
| 83 |
+
Add the nodes from any container (a list, dict, set or
|
| 84 |
+
even the lines from a file or the nodes from another graph).
|
| 85 |
+
|
| 86 |
+
>>> G.add_nodes_from([2, 3])
|
| 87 |
+
>>> G.add_nodes_from(range(100, 110))
|
| 88 |
+
>>> H = nx.path_graph(10)
|
| 89 |
+
>>> G.add_nodes_from(H)
|
| 90 |
+
|
| 91 |
+
In addition to strings and integers any hashable Python object
|
| 92 |
+
(except None) can represent a node, e.g. a customized node object,
|
| 93 |
+
or even another Graph.
|
| 94 |
+
|
| 95 |
+
>>> G.add_node(H)
|
| 96 |
+
|
| 97 |
+
**Edges:**
|
| 98 |
+
|
| 99 |
+
G can also be grown by adding edges.
|
| 100 |
+
|
| 101 |
+
Add one edge,
|
| 102 |
+
|
| 103 |
+
>>> key = G.add_edge(1, 2)
|
| 104 |
+
|
| 105 |
+
a list of edges,
|
| 106 |
+
|
| 107 |
+
>>> keys = G.add_edges_from([(1, 2), (1, 3)])
|
| 108 |
+
|
| 109 |
+
or a collection of edges,
|
| 110 |
+
|
| 111 |
+
>>> keys = G.add_edges_from(H.edges)
|
| 112 |
+
|
| 113 |
+
If some edges connect nodes not yet in the graph, the nodes
|
| 114 |
+
are added automatically. If an edge already exists, an additional
|
| 115 |
+
edge is created and stored using a key to identify the edge.
|
| 116 |
+
By default the key is the lowest unused integer.
|
| 117 |
+
|
| 118 |
+
>>> keys = G.add_edges_from([(4, 5, dict(route=282)), (4, 5, dict(route=37))])
|
| 119 |
+
>>> G[4]
|
| 120 |
+
AdjacencyView({5: {0: {}, 1: {'route': 282}, 2: {'route': 37}}})
|
| 121 |
+
|
| 122 |
+
**Attributes:**
|
| 123 |
+
|
| 124 |
+
Each graph, node, and edge can hold key/value attribute pairs
|
| 125 |
+
in an associated attribute dictionary (the keys must be hashable).
|
| 126 |
+
By default these are empty, but can be added or changed using
|
| 127 |
+
add_edge, add_node or direct manipulation of the attribute
|
| 128 |
+
dictionaries named graph, node and edge respectively.
|
| 129 |
+
|
| 130 |
+
>>> G = nx.MultiDiGraph(day="Friday")
|
| 131 |
+
>>> G.graph
|
| 132 |
+
{'day': 'Friday'}
|
| 133 |
+
|
| 134 |
+
Add node attributes using add_node(), add_nodes_from() or G.nodes
|
| 135 |
+
|
| 136 |
+
>>> G.add_node(1, time="5pm")
|
| 137 |
+
>>> G.add_nodes_from([3], time="2pm")
|
| 138 |
+
>>> G.nodes[1]
|
| 139 |
+
{'time': '5pm'}
|
| 140 |
+
>>> G.nodes[1]["room"] = 714
|
| 141 |
+
>>> del G.nodes[1]["room"] # remove attribute
|
| 142 |
+
>>> list(G.nodes(data=True))
|
| 143 |
+
[(1, {'time': '5pm'}), (3, {'time': '2pm'})]
|
| 144 |
+
|
| 145 |
+
Add edge attributes using add_edge(), add_edges_from(), subscript
|
| 146 |
+
notation, or G.edges.
|
| 147 |
+
|
| 148 |
+
>>> key = G.add_edge(1, 2, weight=4.7)
|
| 149 |
+
>>> keys = G.add_edges_from([(3, 4), (4, 5)], color="red")
|
| 150 |
+
>>> keys = G.add_edges_from([(1, 2, {"color": "blue"}), (2, 3, {"weight": 8})])
|
| 151 |
+
>>> G[1][2][0]["weight"] = 4.7
|
| 152 |
+
>>> G.edges[1, 2, 0]["weight"] = 4
|
| 153 |
+
|
| 154 |
+
Warning: we protect the graph data structure by making `G.edges[1,
|
| 155 |
+
2, 0]` a read-only dict-like structure. However, you can assign to
|
| 156 |
+
attributes in e.g. `G.edges[1, 2, 0]`. Thus, use 2 sets of brackets
|
| 157 |
+
to add/change data attributes: `G.edges[1, 2, 0]['weight'] = 4`
|
| 158 |
+
(for multigraphs the edge key is required: `MG.edges[u, v,
|
| 159 |
+
key][name] = value`).
|
| 160 |
+
|
| 161 |
+
**Shortcuts:**
|
| 162 |
+
|
| 163 |
+
Many common graph features allow python syntax to speed reporting.
|
| 164 |
+
|
| 165 |
+
>>> 1 in G # check if node in graph
|
| 166 |
+
True
|
| 167 |
+
>>> [n for n in G if n < 3] # iterate through nodes
|
| 168 |
+
[1, 2]
|
| 169 |
+
>>> len(G) # number of nodes in graph
|
| 170 |
+
5
|
| 171 |
+
>>> G[1] # adjacency dict-like view mapping neighbor -> edge key -> edge attributes
|
| 172 |
+
AdjacencyView({2: {0: {'weight': 4}, 1: {'color': 'blue'}}})
|
| 173 |
+
|
| 174 |
+
Often the best way to traverse all edges of a graph is via the neighbors.
|
| 175 |
+
The neighbors are available as an adjacency-view `G.adj` object or via
|
| 176 |
+
the method `G.adjacency()`.
|
| 177 |
+
|
| 178 |
+
>>> for n, nbrsdict in G.adjacency():
|
| 179 |
+
... for nbr, keydict in nbrsdict.items():
|
| 180 |
+
... for key, eattr in keydict.items():
|
| 181 |
+
... if "weight" in eattr:
|
| 182 |
+
... # Do something useful with the edges
|
| 183 |
+
... pass
|
| 184 |
+
|
| 185 |
+
But the edges() method is often more convenient:
|
| 186 |
+
|
| 187 |
+
>>> for u, v, keys, weight in G.edges(data="weight", keys=True):
|
| 188 |
+
... if weight is not None:
|
| 189 |
+
... # Do something useful with the edges
|
| 190 |
+
... pass
|
| 191 |
+
|
| 192 |
+
**Reporting:**
|
| 193 |
+
|
| 194 |
+
Simple graph information is obtained using methods and object-attributes.
|
| 195 |
+
Reporting usually provides views instead of containers to reduce memory
|
| 196 |
+
usage. The views update as the graph is updated similarly to dict-views.
|
| 197 |
+
The objects `nodes`, `edges` and `adj` provide access to data attributes
|
| 198 |
+
via lookup (e.g. `nodes[n]`, `edges[u, v, k]`, `adj[u][v]`) and iteration
|
| 199 |
+
(e.g. `nodes.items()`, `nodes.data('color')`,
|
| 200 |
+
`nodes.data('color', default='blue')` and similarly for `edges`)
|
| 201 |
+
Views exist for `nodes`, `edges`, `neighbors()`/`adj` and `degree`.
|
| 202 |
+
|
| 203 |
+
For details on these and other miscellaneous methods, see below.
|
| 204 |
+
|
| 205 |
+
**Subclasses (Advanced):**
|
| 206 |
+
|
| 207 |
+
The MultiDiGraph class uses a dict-of-dict-of-dict-of-dict structure.
|
| 208 |
+
The outer dict (node_dict) holds adjacency information keyed by node.
|
| 209 |
+
The next dict (adjlist_dict) represents the adjacency information
|
| 210 |
+
and holds edge_key dicts keyed by neighbor. The edge_key dict holds
|
| 211 |
+
each edge_attr dict keyed by edge key. The inner dict
|
| 212 |
+
(edge_attr_dict) represents the edge data and holds edge attribute
|
| 213 |
+
values keyed by attribute names.
|
| 214 |
+
|
| 215 |
+
Each of these four dicts in the dict-of-dict-of-dict-of-dict
|
| 216 |
+
structure can be replaced by a user defined dict-like object.
|
| 217 |
+
In general, the dict-like features should be maintained but
|
| 218 |
+
extra features can be added. To replace one of the dicts create
|
| 219 |
+
a new graph class by changing the class(!) variable holding the
|
| 220 |
+
factory for that dict-like structure. The variable names are
|
| 221 |
+
node_dict_factory, node_attr_dict_factory, adjlist_inner_dict_factory,
|
| 222 |
+
adjlist_outer_dict_factory, edge_key_dict_factory, edge_attr_dict_factory
|
| 223 |
+
and graph_attr_dict_factory.
|
| 224 |
+
|
| 225 |
+
node_dict_factory : function, (default: dict)
|
| 226 |
+
Factory function to be used to create the dict containing node
|
| 227 |
+
attributes, keyed by node id.
|
| 228 |
+
It should require no arguments and return a dict-like object
|
| 229 |
+
|
| 230 |
+
node_attr_dict_factory: function, (default: dict)
|
| 231 |
+
Factory function to be used to create the node attribute
|
| 232 |
+
dict which holds attribute values keyed by attribute name.
|
| 233 |
+
It should require no arguments and return a dict-like object
|
| 234 |
+
|
| 235 |
+
adjlist_outer_dict_factory : function, (default: dict)
|
| 236 |
+
Factory function to be used to create the outer-most dict
|
| 237 |
+
in the data structure that holds adjacency info keyed by node.
|
| 238 |
+
It should require no arguments and return a dict-like object.
|
| 239 |
+
|
| 240 |
+
adjlist_inner_dict_factory : function, (default: dict)
|
| 241 |
+
Factory function to be used to create the adjacency list
|
| 242 |
+
dict which holds multiedge key dicts keyed by neighbor.
|
| 243 |
+
It should require no arguments and return a dict-like object.
|
| 244 |
+
|
| 245 |
+
edge_key_dict_factory : function, (default: dict)
|
| 246 |
+
Factory function to be used to create the edge key dict
|
| 247 |
+
which holds edge data keyed by edge key.
|
| 248 |
+
It should require no arguments and return a dict-like object.
|
| 249 |
+
|
| 250 |
+
edge_attr_dict_factory : function, (default: dict)
|
| 251 |
+
Factory function to be used to create the edge attribute
|
| 252 |
+
dict which holds attribute values keyed by attribute name.
|
| 253 |
+
It should require no arguments and return a dict-like object.
|
| 254 |
+
|
| 255 |
+
graph_attr_dict_factory : function, (default: dict)
|
| 256 |
+
Factory function to be used to create the graph attribute
|
| 257 |
+
dict which holds attribute values keyed by attribute name.
|
| 258 |
+
It should require no arguments and return a dict-like object.
|
| 259 |
+
|
| 260 |
+
Typically, if your extension doesn't impact the data structure all
|
| 261 |
+
methods will inherited without issue except: `to_directed/to_undirected`.
|
| 262 |
+
By default these methods create a DiGraph/Graph class and you probably
|
| 263 |
+
want them to create your extension of a DiGraph/Graph. To facilitate
|
| 264 |
+
this we define two class variables that you can set in your subclass.
|
| 265 |
+
|
| 266 |
+
to_directed_class : callable, (default: DiGraph or MultiDiGraph)
|
| 267 |
+
Class to create a new graph structure in the `to_directed` method.
|
| 268 |
+
If `None`, a NetworkX class (DiGraph or MultiDiGraph) is used.
|
| 269 |
+
|
| 270 |
+
to_undirected_class : callable, (default: Graph or MultiGraph)
|
| 271 |
+
Class to create a new graph structure in the `to_undirected` method.
|
| 272 |
+
If `None`, a NetworkX class (Graph or MultiGraph) is used.
|
| 273 |
+
|
| 274 |
+
**Subclassing Example**
|
| 275 |
+
|
| 276 |
+
Create a low memory graph class that effectively disallows edge
|
| 277 |
+
attributes by using a single attribute dict for all edges.
|
| 278 |
+
This reduces the memory used, but you lose edge attributes.
|
| 279 |
+
|
| 280 |
+
>>> class ThinGraph(nx.Graph):
|
| 281 |
+
... all_edge_dict = {"weight": 1}
|
| 282 |
+
...
|
| 283 |
+
... def single_edge_dict(self):
|
| 284 |
+
... return self.all_edge_dict
|
| 285 |
+
...
|
| 286 |
+
... edge_attr_dict_factory = single_edge_dict
|
| 287 |
+
>>> G = ThinGraph()
|
| 288 |
+
>>> G.add_edge(2, 1)
|
| 289 |
+
>>> G[2][1]
|
| 290 |
+
{'weight': 1}
|
| 291 |
+
>>> G.add_edge(2, 2)
|
| 292 |
+
>>> G[2][1] is G[2][2]
|
| 293 |
+
True
|
| 294 |
+
"""
|
| 295 |
+
|
| 296 |
+
# node_dict_factory = dict # already assigned in Graph
|
| 297 |
+
# adjlist_outer_dict_factory = dict
|
| 298 |
+
# adjlist_inner_dict_factory = dict
|
| 299 |
+
edge_key_dict_factory = dict
|
| 300 |
+
# edge_attr_dict_factory = dict
|
| 301 |
+
|
| 302 |
+
def __init__(self, incoming_graph_data=None, multigraph_input=None, **attr):
|
| 303 |
+
"""Initialize a graph with edges, name, or graph attributes.
|
| 304 |
+
|
| 305 |
+
Parameters
|
| 306 |
+
----------
|
| 307 |
+
incoming_graph_data : input graph
|
| 308 |
+
Data to initialize graph. If incoming_graph_data=None (default)
|
| 309 |
+
an empty graph is created. The data can be an edge list, or any
|
| 310 |
+
NetworkX graph object. If the corresponding optional Python
|
| 311 |
+
packages are installed the data can also be a 2D NumPy array, a
|
| 312 |
+
SciPy sparse array, or a PyGraphviz graph.
|
| 313 |
+
|
| 314 |
+
multigraph_input : bool or None (default None)
|
| 315 |
+
Note: Only used when `incoming_graph_data` is a dict.
|
| 316 |
+
If True, `incoming_graph_data` is assumed to be a
|
| 317 |
+
dict-of-dict-of-dict-of-dict structure keyed by
|
| 318 |
+
node to neighbor to edge keys to edge data for multi-edges.
|
| 319 |
+
A NetworkXError is raised if this is not the case.
|
| 320 |
+
If False, :func:`to_networkx_graph` is used to try to determine
|
| 321 |
+
the dict's graph data structure as either a dict-of-dict-of-dict
|
| 322 |
+
keyed by node to neighbor to edge data, or a dict-of-iterable
|
| 323 |
+
keyed by node to neighbors.
|
| 324 |
+
If None, the treatment for True is tried, but if it fails,
|
| 325 |
+
the treatment for False is tried.
|
| 326 |
+
|
| 327 |
+
attr : keyword arguments, optional (default= no attributes)
|
| 328 |
+
Attributes to add to graph as key=value pairs.
|
| 329 |
+
|
| 330 |
+
See Also
|
| 331 |
+
--------
|
| 332 |
+
convert
|
| 333 |
+
|
| 334 |
+
Examples
|
| 335 |
+
--------
|
| 336 |
+
>>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 337 |
+
>>> G = nx.Graph(name="my graph")
|
| 338 |
+
>>> e = [(1, 2), (2, 3), (3, 4)] # list of edges
|
| 339 |
+
>>> G = nx.Graph(e)
|
| 340 |
+
|
| 341 |
+
Arbitrary graph attribute pairs (key=value) may be assigned
|
| 342 |
+
|
| 343 |
+
>>> G = nx.Graph(e, day="Friday")
|
| 344 |
+
>>> G.graph
|
| 345 |
+
{'day': 'Friday'}
|
| 346 |
+
|
| 347 |
+
"""
|
| 348 |
+
# multigraph_input can be None/True/False. So check "is not False"
|
| 349 |
+
if isinstance(incoming_graph_data, dict) and multigraph_input is not False:
|
| 350 |
+
DiGraph.__init__(self)
|
| 351 |
+
try:
|
| 352 |
+
convert.from_dict_of_dicts(
|
| 353 |
+
incoming_graph_data, create_using=self, multigraph_input=True
|
| 354 |
+
)
|
| 355 |
+
self.graph.update(attr)
|
| 356 |
+
except Exception as err:
|
| 357 |
+
if multigraph_input is True:
|
| 358 |
+
raise nx.NetworkXError(
|
| 359 |
+
f"converting multigraph_input raised:\n{type(err)}: {err}"
|
| 360 |
+
)
|
| 361 |
+
DiGraph.__init__(self, incoming_graph_data, **attr)
|
| 362 |
+
else:
|
| 363 |
+
DiGraph.__init__(self, incoming_graph_data, **attr)
|
| 364 |
+
|
| 365 |
+
@cached_property
|
| 366 |
+
def adj(self):
|
| 367 |
+
"""Graph adjacency object holding the neighbors of each node.
|
| 368 |
+
|
| 369 |
+
This object is a read-only dict-like structure with node keys
|
| 370 |
+
and neighbor-dict values. The neighbor-dict is keyed by neighbor
|
| 371 |
+
to the edgekey-dict. So `G.adj[3][2][0]['color'] = 'blue'` sets
|
| 372 |
+
the color of the edge `(3, 2, 0)` to `"blue"`.
|
| 373 |
+
|
| 374 |
+
Iterating over G.adj behaves like a dict. Useful idioms include
|
| 375 |
+
`for nbr, datadict in G.adj[n].items():`.
|
| 376 |
+
|
| 377 |
+
The neighbor information is also provided by subscripting the graph.
|
| 378 |
+
So `for nbr, foovalue in G[node].data('foo', default=1):` works.
|
| 379 |
+
|
| 380 |
+
For directed graphs, `G.adj` holds outgoing (successor) info.
|
| 381 |
+
"""
|
| 382 |
+
return MultiAdjacencyView(self._succ)
|
| 383 |
+
|
| 384 |
+
@cached_property
|
| 385 |
+
def succ(self):
|
| 386 |
+
"""Graph adjacency object holding the successors of each node.
|
| 387 |
+
|
| 388 |
+
This object is a read-only dict-like structure with node keys
|
| 389 |
+
and neighbor-dict values. The neighbor-dict is keyed by neighbor
|
| 390 |
+
to the edgekey-dict. So `G.adj[3][2][0]['color'] = 'blue'` sets
|
| 391 |
+
the color of the edge `(3, 2, 0)` to `"blue"`.
|
| 392 |
+
|
| 393 |
+
Iterating over G.adj behaves like a dict. Useful idioms include
|
| 394 |
+
`for nbr, datadict in G.adj[n].items():`.
|
| 395 |
+
|
| 396 |
+
The neighbor information is also provided by subscripting the graph.
|
| 397 |
+
So `for nbr, foovalue in G[node].data('foo', default=1):` works.
|
| 398 |
+
|
| 399 |
+
For directed graphs, `G.succ` is identical to `G.adj`.
|
| 400 |
+
"""
|
| 401 |
+
return MultiAdjacencyView(self._succ)
|
| 402 |
+
|
| 403 |
+
@cached_property
|
| 404 |
+
def pred(self):
|
| 405 |
+
"""Graph adjacency object holding the predecessors of each node.
|
| 406 |
+
|
| 407 |
+
This object is a read-only dict-like structure with node keys
|
| 408 |
+
and neighbor-dict values. The neighbor-dict is keyed by neighbor
|
| 409 |
+
to the edgekey-dict. So `G.adj[3][2][0]['color'] = 'blue'` sets
|
| 410 |
+
the color of the edge `(3, 2, 0)` to `"blue"`.
|
| 411 |
+
|
| 412 |
+
Iterating over G.adj behaves like a dict. Useful idioms include
|
| 413 |
+
`for nbr, datadict in G.adj[n].items():`.
|
| 414 |
+
"""
|
| 415 |
+
return MultiAdjacencyView(self._pred)
|
| 416 |
+
|
| 417 |
+
def add_edge(self, u_for_edge, v_for_edge, key=None, **attr):
|
| 418 |
+
"""Add an edge between u and v.
|
| 419 |
+
|
| 420 |
+
The nodes u and v will be automatically added if they are
|
| 421 |
+
not already in the graph.
|
| 422 |
+
|
| 423 |
+
Edge attributes can be specified with keywords or by directly
|
| 424 |
+
accessing the edge's attribute dictionary. See examples below.
|
| 425 |
+
|
| 426 |
+
Parameters
|
| 427 |
+
----------
|
| 428 |
+
u_for_edge, v_for_edge : nodes
|
| 429 |
+
Nodes can be, for example, strings or numbers.
|
| 430 |
+
Nodes must be hashable (and not None) Python objects.
|
| 431 |
+
key : hashable identifier, optional (default=lowest unused integer)
|
| 432 |
+
Used to distinguish multiedges between a pair of nodes.
|
| 433 |
+
attr : keyword arguments, optional
|
| 434 |
+
Edge data (or labels or objects) can be assigned using
|
| 435 |
+
keyword arguments.
|
| 436 |
+
|
| 437 |
+
Returns
|
| 438 |
+
-------
|
| 439 |
+
The edge key assigned to the edge.
|
| 440 |
+
|
| 441 |
+
See Also
|
| 442 |
+
--------
|
| 443 |
+
add_edges_from : add a collection of edges
|
| 444 |
+
|
| 445 |
+
Notes
|
| 446 |
+
-----
|
| 447 |
+
To replace/update edge data, use the optional key argument
|
| 448 |
+
to identify a unique edge. Otherwise a new edge will be created.
|
| 449 |
+
|
| 450 |
+
NetworkX algorithms designed for weighted graphs cannot use
|
| 451 |
+
multigraphs directly because it is not clear how to handle
|
| 452 |
+
multiedge weights. Convert to Graph using edge attribute
|
| 453 |
+
'weight' to enable weighted graph algorithms.
|
| 454 |
+
|
| 455 |
+
Default keys are generated using the method `new_edge_key()`.
|
| 456 |
+
This method can be overridden by subclassing the base class and
|
| 457 |
+
providing a custom `new_edge_key()` method.
|
| 458 |
+
|
| 459 |
+
Examples
|
| 460 |
+
--------
|
| 461 |
+
The following all add the edge e=(1, 2) to graph G:
|
| 462 |
+
|
| 463 |
+
>>> G = nx.MultiDiGraph()
|
| 464 |
+
>>> e = (1, 2)
|
| 465 |
+
>>> key = G.add_edge(1, 2) # explicit two-node form
|
| 466 |
+
>>> G.add_edge(*e) # single edge as tuple of two nodes
|
| 467 |
+
1
|
| 468 |
+
>>> G.add_edges_from([(1, 2)]) # add edges from iterable container
|
| 469 |
+
[2]
|
| 470 |
+
|
| 471 |
+
Associate data to edges using keywords:
|
| 472 |
+
|
| 473 |
+
>>> key = G.add_edge(1, 2, weight=3)
|
| 474 |
+
>>> key = G.add_edge(1, 2, key=0, weight=4) # update data for key=0
|
| 475 |
+
>>> key = G.add_edge(1, 3, weight=7, capacity=15, length=342.7)
|
| 476 |
+
|
| 477 |
+
For non-string attribute keys, use subscript notation.
|
| 478 |
+
|
| 479 |
+
>>> ekey = G.add_edge(1, 2)
|
| 480 |
+
>>> G[1][2][0].update({0: 5})
|
| 481 |
+
>>> G.edges[1, 2, 0].update({0: 5})
|
| 482 |
+
"""
|
| 483 |
+
u, v = u_for_edge, v_for_edge
|
| 484 |
+
# add nodes
|
| 485 |
+
if u not in self._succ:
|
| 486 |
+
if u is None:
|
| 487 |
+
raise ValueError("None cannot be a node")
|
| 488 |
+
self._succ[u] = self.adjlist_inner_dict_factory()
|
| 489 |
+
self._pred[u] = self.adjlist_inner_dict_factory()
|
| 490 |
+
self._node[u] = self.node_attr_dict_factory()
|
| 491 |
+
if v not in self._succ:
|
| 492 |
+
if v is None:
|
| 493 |
+
raise ValueError("None cannot be a node")
|
| 494 |
+
self._succ[v] = self.adjlist_inner_dict_factory()
|
| 495 |
+
self._pred[v] = self.adjlist_inner_dict_factory()
|
| 496 |
+
self._node[v] = self.node_attr_dict_factory()
|
| 497 |
+
if key is None:
|
| 498 |
+
key = self.new_edge_key(u, v)
|
| 499 |
+
if v in self._succ[u]:
|
| 500 |
+
keydict = self._adj[u][v]
|
| 501 |
+
datadict = keydict.get(key, self.edge_attr_dict_factory())
|
| 502 |
+
datadict.update(attr)
|
| 503 |
+
keydict[key] = datadict
|
| 504 |
+
else:
|
| 505 |
+
# selfloops work this way without special treatment
|
| 506 |
+
datadict = self.edge_attr_dict_factory()
|
| 507 |
+
datadict.update(attr)
|
| 508 |
+
keydict = self.edge_key_dict_factory()
|
| 509 |
+
keydict[key] = datadict
|
| 510 |
+
self._succ[u][v] = keydict
|
| 511 |
+
self._pred[v][u] = keydict
|
| 512 |
+
nx._clear_cache(self)
|
| 513 |
+
return key
|
| 514 |
+
|
| 515 |
+
def remove_edge(self, u, v, key=None):
|
| 516 |
+
"""Remove an edge between u and v.
|
| 517 |
+
|
| 518 |
+
Parameters
|
| 519 |
+
----------
|
| 520 |
+
u, v : nodes
|
| 521 |
+
Remove an edge between nodes u and v.
|
| 522 |
+
key : hashable identifier, optional (default=None)
|
| 523 |
+
Used to distinguish multiple edges between a pair of nodes.
|
| 524 |
+
If None, remove a single edge between u and v. If there are
|
| 525 |
+
multiple edges, removes the last edge added in terms of
|
| 526 |
+
insertion order.
|
| 527 |
+
|
| 528 |
+
Raises
|
| 529 |
+
------
|
| 530 |
+
NetworkXError
|
| 531 |
+
If there is not an edge between u and v, or
|
| 532 |
+
if there is no edge with the specified key.
|
| 533 |
+
|
| 534 |
+
See Also
|
| 535 |
+
--------
|
| 536 |
+
remove_edges_from : remove a collection of edges
|
| 537 |
+
|
| 538 |
+
Examples
|
| 539 |
+
--------
|
| 540 |
+
>>> G = nx.MultiDiGraph()
|
| 541 |
+
>>> nx.add_path(G, [0, 1, 2, 3])
|
| 542 |
+
>>> G.remove_edge(0, 1)
|
| 543 |
+
>>> e = (1, 2)
|
| 544 |
+
>>> G.remove_edge(*e) # unpacks e from an edge tuple
|
| 545 |
+
|
| 546 |
+
For multiple edges
|
| 547 |
+
|
| 548 |
+
>>> G = nx.MultiDiGraph()
|
| 549 |
+
>>> G.add_edges_from([(1, 2), (1, 2), (1, 2)]) # key_list returned
|
| 550 |
+
[0, 1, 2]
|
| 551 |
+
|
| 552 |
+
When ``key=None`` (the default), edges are removed in the opposite
|
| 553 |
+
order that they were added:
|
| 554 |
+
|
| 555 |
+
>>> G.remove_edge(1, 2)
|
| 556 |
+
>>> G.edges(keys=True)
|
| 557 |
+
OutMultiEdgeView([(1, 2, 0), (1, 2, 1)])
|
| 558 |
+
|
| 559 |
+
For edges with keys
|
| 560 |
+
|
| 561 |
+
>>> G = nx.MultiDiGraph()
|
| 562 |
+
>>> G.add_edge(1, 2, key="first")
|
| 563 |
+
'first'
|
| 564 |
+
>>> G.add_edge(1, 2, key="second")
|
| 565 |
+
'second'
|
| 566 |
+
>>> G.remove_edge(1, 2, key="first")
|
| 567 |
+
>>> G.edges(keys=True)
|
| 568 |
+
OutMultiEdgeView([(1, 2, 'second')])
|
| 569 |
+
|
| 570 |
+
"""
|
| 571 |
+
try:
|
| 572 |
+
d = self._adj[u][v]
|
| 573 |
+
except KeyError as err:
|
| 574 |
+
raise NetworkXError(f"The edge {u}-{v} is not in the graph.") from err
|
| 575 |
+
# remove the edge with specified data
|
| 576 |
+
if key is None:
|
| 577 |
+
d.popitem()
|
| 578 |
+
else:
|
| 579 |
+
try:
|
| 580 |
+
del d[key]
|
| 581 |
+
except KeyError as err:
|
| 582 |
+
msg = f"The edge {u}-{v} with key {key} is not in the graph."
|
| 583 |
+
raise NetworkXError(msg) from err
|
| 584 |
+
if len(d) == 0:
|
| 585 |
+
# remove the key entries if last edge
|
| 586 |
+
del self._succ[u][v]
|
| 587 |
+
del self._pred[v][u]
|
| 588 |
+
nx._clear_cache(self)
|
| 589 |
+
|
| 590 |
+
@cached_property
|
| 591 |
+
def edges(self):
|
| 592 |
+
"""An OutMultiEdgeView of the Graph as G.edges or G.edges().
|
| 593 |
+
|
| 594 |
+
edges(self, nbunch=None, data=False, keys=False, default=None)
|
| 595 |
+
|
| 596 |
+
The OutMultiEdgeView provides set-like operations on the edge-tuples
|
| 597 |
+
as well as edge attribute lookup. When called, it also provides
|
| 598 |
+
an EdgeDataView object which allows control of access to edge
|
| 599 |
+
attributes (but does not provide set-like operations).
|
| 600 |
+
Hence, ``G.edges[u, v, k]['color']`` provides the value of the color
|
| 601 |
+
attribute for the edge from ``u`` to ``v`` with key ``k`` while
|
| 602 |
+
``for (u, v, k, c) in G.edges(data='color', default='red', keys=True):``
|
| 603 |
+
iterates through all the edges yielding the color attribute with
|
| 604 |
+
default `'red'` if no color attribute exists.
|
| 605 |
+
|
| 606 |
+
Edges are returned as tuples with optional data and keys
|
| 607 |
+
in the order (node, neighbor, key, data). If ``keys=True`` is not
|
| 608 |
+
provided, the tuples will just be (node, neighbor, data), but
|
| 609 |
+
multiple tuples with the same node and neighbor will be
|
| 610 |
+
generated when multiple edges between two nodes exist.
|
| 611 |
+
|
| 612 |
+
Parameters
|
| 613 |
+
----------
|
| 614 |
+
nbunch : single node, container, or all nodes (default= all nodes)
|
| 615 |
+
The view will only report edges from these nodes.
|
| 616 |
+
data : string or bool, optional (default=False)
|
| 617 |
+
The edge attribute returned in 3-tuple (u, v, ddict[data]).
|
| 618 |
+
If True, return edge attribute dict in 3-tuple (u, v, ddict).
|
| 619 |
+
If False, return 2-tuple (u, v).
|
| 620 |
+
keys : bool, optional (default=False)
|
| 621 |
+
If True, return edge keys with each edge, creating (u, v, k,
|
| 622 |
+
d) tuples when data is also requested (the default) and (u,
|
| 623 |
+
v, k) tuples when data is not requested.
|
| 624 |
+
default : value, optional (default=None)
|
| 625 |
+
Value used for edges that don't have the requested attribute.
|
| 626 |
+
Only relevant if data is not True or False.
|
| 627 |
+
|
| 628 |
+
Returns
|
| 629 |
+
-------
|
| 630 |
+
edges : OutMultiEdgeView
|
| 631 |
+
A view of edge attributes, usually it iterates over (u, v)
|
| 632 |
+
(u, v, k) or (u, v, k, d) tuples of edges, but can also be
|
| 633 |
+
used for attribute lookup as ``edges[u, v, k]['foo']``.
|
| 634 |
+
|
| 635 |
+
Notes
|
| 636 |
+
-----
|
| 637 |
+
Nodes in nbunch that are not in the graph will be (quietly) ignored.
|
| 638 |
+
For directed graphs this returns the out-edges.
|
| 639 |
+
|
| 640 |
+
Examples
|
| 641 |
+
--------
|
| 642 |
+
>>> G = nx.MultiDiGraph()
|
| 643 |
+
>>> nx.add_path(G, [0, 1, 2])
|
| 644 |
+
>>> key = G.add_edge(2, 3, weight=5)
|
| 645 |
+
>>> key2 = G.add_edge(1, 2) # second edge between these nodes
|
| 646 |
+
>>> [e for e in G.edges()]
|
| 647 |
+
[(0, 1), (1, 2), (1, 2), (2, 3)]
|
| 648 |
+
>>> list(G.edges(data=True)) # default data is {} (empty dict)
|
| 649 |
+
[(0, 1, {}), (1, 2, {}), (1, 2, {}), (2, 3, {'weight': 5})]
|
| 650 |
+
>>> list(G.edges(data="weight", default=1))
|
| 651 |
+
[(0, 1, 1), (1, 2, 1), (1, 2, 1), (2, 3, 5)]
|
| 652 |
+
>>> list(G.edges(keys=True)) # default keys are integers
|
| 653 |
+
[(0, 1, 0), (1, 2, 0), (1, 2, 1), (2, 3, 0)]
|
| 654 |
+
>>> list(G.edges(data=True, keys=True))
|
| 655 |
+
[(0, 1, 0, {}), (1, 2, 0, {}), (1, 2, 1, {}), (2, 3, 0, {'weight': 5})]
|
| 656 |
+
>>> list(G.edges(data="weight", default=1, keys=True))
|
| 657 |
+
[(0, 1, 0, 1), (1, 2, 0, 1), (1, 2, 1, 1), (2, 3, 0, 5)]
|
| 658 |
+
>>> list(G.edges([0, 2]))
|
| 659 |
+
[(0, 1), (2, 3)]
|
| 660 |
+
>>> list(G.edges(0))
|
| 661 |
+
[(0, 1)]
|
| 662 |
+
>>> list(G.edges(1))
|
| 663 |
+
[(1, 2), (1, 2)]
|
| 664 |
+
|
| 665 |
+
See Also
|
| 666 |
+
--------
|
| 667 |
+
in_edges, out_edges
|
| 668 |
+
"""
|
| 669 |
+
return OutMultiEdgeView(self)
|
| 670 |
+
|
| 671 |
+
# alias out_edges to edges
|
| 672 |
+
@cached_property
|
| 673 |
+
def out_edges(self):
|
| 674 |
+
return OutMultiEdgeView(self)
|
| 675 |
+
|
| 676 |
+
out_edges.__doc__ = edges.__doc__
|
| 677 |
+
|
| 678 |
+
@cached_property
|
| 679 |
+
def in_edges(self):
|
| 680 |
+
"""A view of the in edges of the graph as G.in_edges or G.in_edges().
|
| 681 |
+
|
| 682 |
+
in_edges(self, nbunch=None, data=False, keys=False, default=None)
|
| 683 |
+
|
| 684 |
+
Parameters
|
| 685 |
+
----------
|
| 686 |
+
nbunch : single node, container, or all nodes (default= all nodes)
|
| 687 |
+
The view will only report edges incident to these nodes.
|
| 688 |
+
data : string or bool, optional (default=False)
|
| 689 |
+
The edge attribute returned in 3-tuple (u, v, ddict[data]).
|
| 690 |
+
If True, return edge attribute dict in 3-tuple (u, v, ddict).
|
| 691 |
+
If False, return 2-tuple (u, v).
|
| 692 |
+
keys : bool, optional (default=False)
|
| 693 |
+
If True, return edge keys with each edge, creating 3-tuples
|
| 694 |
+
(u, v, k) or with data, 4-tuples (u, v, k, d).
|
| 695 |
+
default : value, optional (default=None)
|
| 696 |
+
Value used for edges that don't have the requested attribute.
|
| 697 |
+
Only relevant if data is not True or False.
|
| 698 |
+
|
| 699 |
+
Returns
|
| 700 |
+
-------
|
| 701 |
+
in_edges : InMultiEdgeView or InMultiEdgeDataView
|
| 702 |
+
A view of edge attributes, usually it iterates over (u, v)
|
| 703 |
+
or (u, v, k) or (u, v, k, d) tuples of edges, but can also be
|
| 704 |
+
used for attribute lookup as `edges[u, v, k]['foo']`.
|
| 705 |
+
|
| 706 |
+
See Also
|
| 707 |
+
--------
|
| 708 |
+
edges
|
| 709 |
+
"""
|
| 710 |
+
return InMultiEdgeView(self)
|
| 711 |
+
|
| 712 |
+
@cached_property
|
| 713 |
+
def degree(self):
|
| 714 |
+
"""A DegreeView for the Graph as G.degree or G.degree().
|
| 715 |
+
|
| 716 |
+
The node degree is the number of edges adjacent to the node.
|
| 717 |
+
The weighted node degree is the sum of the edge weights for
|
| 718 |
+
edges incident to that node.
|
| 719 |
+
|
| 720 |
+
This object provides an iterator for (node, degree) as well as
|
| 721 |
+
lookup for the degree for a single node.
|
| 722 |
+
|
| 723 |
+
Parameters
|
| 724 |
+
----------
|
| 725 |
+
nbunch : single node, container, or all nodes (default= all nodes)
|
| 726 |
+
The view will only report edges incident to these nodes.
|
| 727 |
+
|
| 728 |
+
weight : string or None, optional (default=None)
|
| 729 |
+
The name of an edge attribute that holds the numerical value used
|
| 730 |
+
as a weight. If None, then each edge has weight 1.
|
| 731 |
+
The degree is the sum of the edge weights adjacent to the node.
|
| 732 |
+
|
| 733 |
+
Returns
|
| 734 |
+
-------
|
| 735 |
+
DiMultiDegreeView or int
|
| 736 |
+
If multiple nodes are requested (the default), returns a `DiMultiDegreeView`
|
| 737 |
+
mapping nodes to their degree.
|
| 738 |
+
If a single node is requested, returns the degree of the node as an integer.
|
| 739 |
+
|
| 740 |
+
See Also
|
| 741 |
+
--------
|
| 742 |
+
out_degree, in_degree
|
| 743 |
+
|
| 744 |
+
Examples
|
| 745 |
+
--------
|
| 746 |
+
>>> G = nx.MultiDiGraph()
|
| 747 |
+
>>> nx.add_path(G, [0, 1, 2, 3])
|
| 748 |
+
>>> G.degree(0) # node 0 with degree 1
|
| 749 |
+
1
|
| 750 |
+
>>> list(G.degree([0, 1, 2]))
|
| 751 |
+
[(0, 1), (1, 2), (2, 2)]
|
| 752 |
+
>>> G.add_edge(0, 1) # parallel edge
|
| 753 |
+
1
|
| 754 |
+
>>> list(G.degree([0, 1, 2])) # parallel edges are counted
|
| 755 |
+
[(0, 2), (1, 3), (2, 2)]
|
| 756 |
+
|
| 757 |
+
"""
|
| 758 |
+
return DiMultiDegreeView(self)
|
| 759 |
+
|
| 760 |
+
@cached_property
|
| 761 |
+
def in_degree(self):
|
| 762 |
+
"""A DegreeView for (node, in_degree) or in_degree for single node.
|
| 763 |
+
|
| 764 |
+
The node in-degree is the number of edges pointing into the node.
|
| 765 |
+
The weighted node degree is the sum of the edge weights for
|
| 766 |
+
edges incident to that node.
|
| 767 |
+
|
| 768 |
+
This object provides an iterator for (node, degree) as well as
|
| 769 |
+
lookup for the degree for a single node.
|
| 770 |
+
|
| 771 |
+
Parameters
|
| 772 |
+
----------
|
| 773 |
+
nbunch : single node, container, or all nodes (default= all nodes)
|
| 774 |
+
The view will only report edges incident to these nodes.
|
| 775 |
+
|
| 776 |
+
weight : string or None, optional (default=None)
|
| 777 |
+
The edge attribute that holds the numerical value used
|
| 778 |
+
as a weight. If None, then each edge has weight 1.
|
| 779 |
+
The degree is the sum of the edge weights adjacent to the node.
|
| 780 |
+
|
| 781 |
+
Returns
|
| 782 |
+
-------
|
| 783 |
+
If a single node is requested
|
| 784 |
+
deg : int
|
| 785 |
+
Degree of the node
|
| 786 |
+
|
| 787 |
+
OR if multiple nodes are requested
|
| 788 |
+
nd_iter : iterator
|
| 789 |
+
The iterator returns two-tuples of (node, in-degree).
|
| 790 |
+
|
| 791 |
+
See Also
|
| 792 |
+
--------
|
| 793 |
+
degree, out_degree
|
| 794 |
+
|
| 795 |
+
Examples
|
| 796 |
+
--------
|
| 797 |
+
>>> G = nx.MultiDiGraph()
|
| 798 |
+
>>> nx.add_path(G, [0, 1, 2, 3])
|
| 799 |
+
>>> G.in_degree(0) # node 0 with degree 0
|
| 800 |
+
0
|
| 801 |
+
>>> list(G.in_degree([0, 1, 2]))
|
| 802 |
+
[(0, 0), (1, 1), (2, 1)]
|
| 803 |
+
>>> G.add_edge(0, 1) # parallel edge
|
| 804 |
+
1
|
| 805 |
+
>>> list(G.in_degree([0, 1, 2])) # parallel edges counted
|
| 806 |
+
[(0, 0), (1, 2), (2, 1)]
|
| 807 |
+
|
| 808 |
+
"""
|
| 809 |
+
return InMultiDegreeView(self)
|
| 810 |
+
|
| 811 |
+
@cached_property
|
| 812 |
+
def out_degree(self):
|
| 813 |
+
"""Returns an iterator for (node, out-degree) or out-degree for single node.
|
| 814 |
+
|
| 815 |
+
out_degree(self, nbunch=None, weight=None)
|
| 816 |
+
|
| 817 |
+
The node out-degree is the number of edges pointing out of the node.
|
| 818 |
+
This function returns the out-degree for a single node or an iterator
|
| 819 |
+
for a bunch of nodes or if nothing is passed as argument.
|
| 820 |
+
|
| 821 |
+
Parameters
|
| 822 |
+
----------
|
| 823 |
+
nbunch : single node, container, or all nodes (default= all nodes)
|
| 824 |
+
The view will only report edges incident to these nodes.
|
| 825 |
+
|
| 826 |
+
weight : string or None, optional (default=None)
|
| 827 |
+
The edge attribute that holds the numerical value used
|
| 828 |
+
as a weight. If None, then each edge has weight 1.
|
| 829 |
+
The degree is the sum of the edge weights.
|
| 830 |
+
|
| 831 |
+
Returns
|
| 832 |
+
-------
|
| 833 |
+
If a single node is requested
|
| 834 |
+
deg : int
|
| 835 |
+
Degree of the node
|
| 836 |
+
|
| 837 |
+
OR if multiple nodes are requested
|
| 838 |
+
nd_iter : iterator
|
| 839 |
+
The iterator returns two-tuples of (node, out-degree).
|
| 840 |
+
|
| 841 |
+
See Also
|
| 842 |
+
--------
|
| 843 |
+
degree, in_degree
|
| 844 |
+
|
| 845 |
+
Examples
|
| 846 |
+
--------
|
| 847 |
+
>>> G = nx.MultiDiGraph()
|
| 848 |
+
>>> nx.add_path(G, [0, 1, 2, 3])
|
| 849 |
+
>>> G.out_degree(0) # node 0 with degree 1
|
| 850 |
+
1
|
| 851 |
+
>>> list(G.out_degree([0, 1, 2]))
|
| 852 |
+
[(0, 1), (1, 1), (2, 1)]
|
| 853 |
+
>>> G.add_edge(0, 1) # parallel edge
|
| 854 |
+
1
|
| 855 |
+
>>> list(G.out_degree([0, 1, 2])) # counts parallel edges
|
| 856 |
+
[(0, 2), (1, 1), (2, 1)]
|
| 857 |
+
|
| 858 |
+
"""
|
| 859 |
+
return OutMultiDegreeView(self)
|
| 860 |
+
|
| 861 |
+
def is_multigraph(self):
|
| 862 |
+
"""Returns True if graph is a multigraph, False otherwise."""
|
| 863 |
+
return True
|
| 864 |
+
|
| 865 |
+
def is_directed(self):
|
| 866 |
+
"""Returns True if graph is directed, False otherwise."""
|
| 867 |
+
return True
|
| 868 |
+
|
| 869 |
+
def to_undirected(self, reciprocal=False, as_view=False):
|
| 870 |
+
"""Returns an undirected representation of the digraph.
|
| 871 |
+
|
| 872 |
+
Parameters
|
| 873 |
+
----------
|
| 874 |
+
reciprocal : bool (optional)
|
| 875 |
+
If True only keep edges that appear in both directions
|
| 876 |
+
in the original digraph.
|
| 877 |
+
as_view : bool (optional, default=False)
|
| 878 |
+
If True return an undirected view of the original directed graph.
|
| 879 |
+
|
| 880 |
+
Returns
|
| 881 |
+
-------
|
| 882 |
+
G : MultiGraph
|
| 883 |
+
An undirected graph with the same name and nodes and
|
| 884 |
+
with edge (u, v, data) if either (u, v, data) or (v, u, data)
|
| 885 |
+
is in the digraph. If both edges exist in digraph and
|
| 886 |
+
their edge data is different, only one edge is created
|
| 887 |
+
with an arbitrary choice of which edge data to use.
|
| 888 |
+
You must check and correct for this manually if desired.
|
| 889 |
+
|
| 890 |
+
See Also
|
| 891 |
+
--------
|
| 892 |
+
MultiGraph, copy, add_edge, add_edges_from
|
| 893 |
+
|
| 894 |
+
Notes
|
| 895 |
+
-----
|
| 896 |
+
This returns a "deepcopy" of the edge, node, and
|
| 897 |
+
graph attributes which attempts to completely copy
|
| 898 |
+
all of the data and references.
|
| 899 |
+
|
| 900 |
+
This is in contrast to the similar D=MultiDiGraph(G) which
|
| 901 |
+
returns a shallow copy of the data.
|
| 902 |
+
|
| 903 |
+
See the Python copy module for more information on shallow
|
| 904 |
+
and deep copies, https://docs.python.org/3/library/copy.html.
|
| 905 |
+
|
| 906 |
+
Warning: If you have subclassed MultiDiGraph to use dict-like
|
| 907 |
+
objects in the data structure, those changes do not transfer
|
| 908 |
+
to the MultiGraph created by this method.
|
| 909 |
+
|
| 910 |
+
Examples
|
| 911 |
+
--------
|
| 912 |
+
>>> G = nx.path_graph(2) # or MultiGraph, etc
|
| 913 |
+
>>> H = G.to_directed()
|
| 914 |
+
>>> list(H.edges)
|
| 915 |
+
[(0, 1), (1, 0)]
|
| 916 |
+
>>> G2 = H.to_undirected()
|
| 917 |
+
>>> list(G2.edges)
|
| 918 |
+
[(0, 1)]
|
| 919 |
+
"""
|
| 920 |
+
graph_class = self.to_undirected_class()
|
| 921 |
+
if as_view is True:
|
| 922 |
+
return nx.graphviews.generic_graph_view(self, graph_class)
|
| 923 |
+
# deepcopy when not a view
|
| 924 |
+
G = graph_class()
|
| 925 |
+
G.graph.update(deepcopy(self.graph))
|
| 926 |
+
G.add_nodes_from((n, deepcopy(d)) for n, d in self._node.items())
|
| 927 |
+
if reciprocal is True:
|
| 928 |
+
G.add_edges_from(
|
| 929 |
+
(u, v, key, deepcopy(data))
|
| 930 |
+
for u, nbrs in self._adj.items()
|
| 931 |
+
for v, keydict in nbrs.items()
|
| 932 |
+
for key, data in keydict.items()
|
| 933 |
+
if v in self._pred[u] and key in self._pred[u][v]
|
| 934 |
+
)
|
| 935 |
+
else:
|
| 936 |
+
G.add_edges_from(
|
| 937 |
+
(u, v, key, deepcopy(data))
|
| 938 |
+
for u, nbrs in self._adj.items()
|
| 939 |
+
for v, keydict in nbrs.items()
|
| 940 |
+
for key, data in keydict.items()
|
| 941 |
+
)
|
| 942 |
+
return G
|
| 943 |
+
|
| 944 |
+
def reverse(self, copy=True):
|
| 945 |
+
"""Returns the reverse of the graph.
|
| 946 |
+
|
| 947 |
+
The reverse is a graph with the same nodes and edges
|
| 948 |
+
but with the directions of the edges reversed.
|
| 949 |
+
|
| 950 |
+
Parameters
|
| 951 |
+
----------
|
| 952 |
+
copy : bool optional (default=True)
|
| 953 |
+
If True, return a new DiGraph holding the reversed edges.
|
| 954 |
+
If False, the reverse graph is created using a view of
|
| 955 |
+
the original graph.
|
| 956 |
+
"""
|
| 957 |
+
if copy:
|
| 958 |
+
H = self.__class__()
|
| 959 |
+
H.graph.update(deepcopy(self.graph))
|
| 960 |
+
H.add_nodes_from((n, deepcopy(d)) for n, d in self._node.items())
|
| 961 |
+
H.add_edges_from(
|
| 962 |
+
(v, u, k, deepcopy(d))
|
| 963 |
+
for u, v, k, d in self.edges(keys=True, data=True)
|
| 964 |
+
)
|
| 965 |
+
return H
|
| 966 |
+
return nx.reverse_view(self)
|
.venv/lib/python3.11/site-packages/networkx/classes/multigraph.py
ADDED
|
@@ -0,0 +1,1283 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Base class for MultiGraph."""
|
| 2 |
+
|
| 3 |
+
from copy import deepcopy
|
| 4 |
+
from functools import cached_property
|
| 5 |
+
|
| 6 |
+
import networkx as nx
|
| 7 |
+
from networkx import NetworkXError, convert
|
| 8 |
+
from networkx.classes.coreviews import MultiAdjacencyView
|
| 9 |
+
from networkx.classes.graph import Graph
|
| 10 |
+
from networkx.classes.reportviews import MultiDegreeView, MultiEdgeView
|
| 11 |
+
|
| 12 |
+
__all__ = ["MultiGraph"]
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class MultiGraph(Graph):
|
| 16 |
+
"""
|
| 17 |
+
An undirected graph class that can store multiedges.
|
| 18 |
+
|
| 19 |
+
Multiedges are multiple edges between two nodes. Each edge
|
| 20 |
+
can hold optional data or attributes.
|
| 21 |
+
|
| 22 |
+
A MultiGraph holds undirected edges. Self loops are allowed.
|
| 23 |
+
|
| 24 |
+
Nodes can be arbitrary (hashable) Python objects with optional
|
| 25 |
+
key/value attributes. By convention `None` is not used as a node.
|
| 26 |
+
|
| 27 |
+
Edges are represented as links between nodes with optional
|
| 28 |
+
key/value attributes, in a MultiGraph each edge has a key to
|
| 29 |
+
distinguish between multiple edges that have the same source and
|
| 30 |
+
destination nodes.
|
| 31 |
+
|
| 32 |
+
Parameters
|
| 33 |
+
----------
|
| 34 |
+
incoming_graph_data : input graph (optional, default: None)
|
| 35 |
+
Data to initialize graph. If None (default) an empty
|
| 36 |
+
graph is created. The data can be any format that is supported
|
| 37 |
+
by the to_networkx_graph() function, currently including edge list,
|
| 38 |
+
dict of dicts, dict of lists, NetworkX graph, 2D NumPy array,
|
| 39 |
+
SciPy sparse array, or PyGraphviz graph.
|
| 40 |
+
|
| 41 |
+
multigraph_input : bool or None (default None)
|
| 42 |
+
Note: Only used when `incoming_graph_data` is a dict.
|
| 43 |
+
If True, `incoming_graph_data` is assumed to be a
|
| 44 |
+
dict-of-dict-of-dict-of-dict structure keyed by
|
| 45 |
+
node to neighbor to edge keys to edge data for multi-edges.
|
| 46 |
+
A NetworkXError is raised if this is not the case.
|
| 47 |
+
If False, :func:`to_networkx_graph` is used to try to determine
|
| 48 |
+
the dict's graph data structure as either a dict-of-dict-of-dict
|
| 49 |
+
keyed by node to neighbor to edge data, or a dict-of-iterable
|
| 50 |
+
keyed by node to neighbors.
|
| 51 |
+
If None, the treatment for True is tried, but if it fails,
|
| 52 |
+
the treatment for False is tried.
|
| 53 |
+
|
| 54 |
+
attr : keyword arguments, optional (default= no attributes)
|
| 55 |
+
Attributes to add to graph as key=value pairs.
|
| 56 |
+
|
| 57 |
+
See Also
|
| 58 |
+
--------
|
| 59 |
+
Graph
|
| 60 |
+
DiGraph
|
| 61 |
+
MultiDiGraph
|
| 62 |
+
|
| 63 |
+
Examples
|
| 64 |
+
--------
|
| 65 |
+
Create an empty graph structure (a "null graph") with no nodes and
|
| 66 |
+
no edges.
|
| 67 |
+
|
| 68 |
+
>>> G = nx.MultiGraph()
|
| 69 |
+
|
| 70 |
+
G can be grown in several ways.
|
| 71 |
+
|
| 72 |
+
**Nodes:**
|
| 73 |
+
|
| 74 |
+
Add one node at a time:
|
| 75 |
+
|
| 76 |
+
>>> G.add_node(1)
|
| 77 |
+
|
| 78 |
+
Add the nodes from any container (a list, dict, set or
|
| 79 |
+
even the lines from a file or the nodes from another graph).
|
| 80 |
+
|
| 81 |
+
>>> G.add_nodes_from([2, 3])
|
| 82 |
+
>>> G.add_nodes_from(range(100, 110))
|
| 83 |
+
>>> H = nx.path_graph(10)
|
| 84 |
+
>>> G.add_nodes_from(H)
|
| 85 |
+
|
| 86 |
+
In addition to strings and integers any hashable Python object
|
| 87 |
+
(except None) can represent a node, e.g. a customized node object,
|
| 88 |
+
or even another Graph.
|
| 89 |
+
|
| 90 |
+
>>> G.add_node(H)
|
| 91 |
+
|
| 92 |
+
**Edges:**
|
| 93 |
+
|
| 94 |
+
G can also be grown by adding edges.
|
| 95 |
+
|
| 96 |
+
Add one edge,
|
| 97 |
+
|
| 98 |
+
>>> key = G.add_edge(1, 2)
|
| 99 |
+
|
| 100 |
+
a list of edges,
|
| 101 |
+
|
| 102 |
+
>>> keys = G.add_edges_from([(1, 2), (1, 3)])
|
| 103 |
+
|
| 104 |
+
or a collection of edges,
|
| 105 |
+
|
| 106 |
+
>>> keys = G.add_edges_from(H.edges)
|
| 107 |
+
|
| 108 |
+
If some edges connect nodes not yet in the graph, the nodes
|
| 109 |
+
are added automatically. If an edge already exists, an additional
|
| 110 |
+
edge is created and stored using a key to identify the edge.
|
| 111 |
+
By default the key is the lowest unused integer.
|
| 112 |
+
|
| 113 |
+
>>> keys = G.add_edges_from([(4, 5, {"route": 28}), (4, 5, {"route": 37})])
|
| 114 |
+
>>> G[4]
|
| 115 |
+
AdjacencyView({3: {0: {}}, 5: {0: {}, 1: {'route': 28}, 2: {'route': 37}}})
|
| 116 |
+
|
| 117 |
+
**Attributes:**
|
| 118 |
+
|
| 119 |
+
Each graph, node, and edge can hold key/value attribute pairs
|
| 120 |
+
in an associated attribute dictionary (the keys must be hashable).
|
| 121 |
+
By default these are empty, but can be added or changed using
|
| 122 |
+
add_edge, add_node or direct manipulation of the attribute
|
| 123 |
+
dictionaries named graph, node and edge respectively.
|
| 124 |
+
|
| 125 |
+
>>> G = nx.MultiGraph(day="Friday")
|
| 126 |
+
>>> G.graph
|
| 127 |
+
{'day': 'Friday'}
|
| 128 |
+
|
| 129 |
+
Add node attributes using add_node(), add_nodes_from() or G.nodes
|
| 130 |
+
|
| 131 |
+
>>> G.add_node(1, time="5pm")
|
| 132 |
+
>>> G.add_nodes_from([3], time="2pm")
|
| 133 |
+
>>> G.nodes[1]
|
| 134 |
+
{'time': '5pm'}
|
| 135 |
+
>>> G.nodes[1]["room"] = 714
|
| 136 |
+
>>> del G.nodes[1]["room"] # remove attribute
|
| 137 |
+
>>> list(G.nodes(data=True))
|
| 138 |
+
[(1, {'time': '5pm'}), (3, {'time': '2pm'})]
|
| 139 |
+
|
| 140 |
+
Add edge attributes using add_edge(), add_edges_from(), subscript
|
| 141 |
+
notation, or G.edges.
|
| 142 |
+
|
| 143 |
+
>>> key = G.add_edge(1, 2, weight=4.7)
|
| 144 |
+
>>> keys = G.add_edges_from([(3, 4), (4, 5)], color="red")
|
| 145 |
+
>>> keys = G.add_edges_from([(1, 2, {"color": "blue"}), (2, 3, {"weight": 8})])
|
| 146 |
+
>>> G[1][2][0]["weight"] = 4.7
|
| 147 |
+
>>> G.edges[1, 2, 0]["weight"] = 4
|
| 148 |
+
|
| 149 |
+
Warning: we protect the graph data structure by making `G.edges[1,
|
| 150 |
+
2, 0]` a read-only dict-like structure. However, you can assign to
|
| 151 |
+
attributes in e.g. `G.edges[1, 2, 0]`. Thus, use 2 sets of brackets
|
| 152 |
+
to add/change data attributes: `G.edges[1, 2, 0]['weight'] = 4`.
|
| 153 |
+
|
| 154 |
+
**Shortcuts:**
|
| 155 |
+
|
| 156 |
+
Many common graph features allow python syntax to speed reporting.
|
| 157 |
+
|
| 158 |
+
>>> 1 in G # check if node in graph
|
| 159 |
+
True
|
| 160 |
+
>>> [n for n in G if n < 3] # iterate through nodes
|
| 161 |
+
[1, 2]
|
| 162 |
+
>>> len(G) # number of nodes in graph
|
| 163 |
+
5
|
| 164 |
+
>>> G[1] # adjacency dict-like view mapping neighbor -> edge key -> edge attributes
|
| 165 |
+
AdjacencyView({2: {0: {'weight': 4}, 1: {'color': 'blue'}}})
|
| 166 |
+
|
| 167 |
+
Often the best way to traverse all edges of a graph is via the neighbors.
|
| 168 |
+
The neighbors are reported as an adjacency-dict `G.adj` or `G.adjacency()`.
|
| 169 |
+
|
| 170 |
+
>>> for n, nbrsdict in G.adjacency():
|
| 171 |
+
... for nbr, keydict in nbrsdict.items():
|
| 172 |
+
... for key, eattr in keydict.items():
|
| 173 |
+
... if "weight" in eattr:
|
| 174 |
+
... # Do something useful with the edges
|
| 175 |
+
... pass
|
| 176 |
+
|
| 177 |
+
But the edges() method is often more convenient:
|
| 178 |
+
|
| 179 |
+
>>> for u, v, keys, weight in G.edges(data="weight", keys=True):
|
| 180 |
+
... if weight is not None:
|
| 181 |
+
... # Do something useful with the edges
|
| 182 |
+
... pass
|
| 183 |
+
|
| 184 |
+
**Reporting:**
|
| 185 |
+
|
| 186 |
+
Simple graph information is obtained using methods and object-attributes.
|
| 187 |
+
Reporting usually provides views instead of containers to reduce memory
|
| 188 |
+
usage. The views update as the graph is updated similarly to dict-views.
|
| 189 |
+
The objects `nodes`, `edges` and `adj` provide access to data attributes
|
| 190 |
+
via lookup (e.g. `nodes[n]`, `edges[u, v, k]`, `adj[u][v]`) and iteration
|
| 191 |
+
(e.g. `nodes.items()`, `nodes.data('color')`,
|
| 192 |
+
`nodes.data('color', default='blue')` and similarly for `edges`)
|
| 193 |
+
Views exist for `nodes`, `edges`, `neighbors()`/`adj` and `degree`.
|
| 194 |
+
|
| 195 |
+
For details on these and other miscellaneous methods, see below.
|
| 196 |
+
|
| 197 |
+
**Subclasses (Advanced):**
|
| 198 |
+
|
| 199 |
+
The MultiGraph class uses a dict-of-dict-of-dict-of-dict data structure.
|
| 200 |
+
The outer dict (node_dict) holds adjacency information keyed by node.
|
| 201 |
+
The next dict (adjlist_dict) represents the adjacency information
|
| 202 |
+
and holds edge_key dicts keyed by neighbor. The edge_key dict holds
|
| 203 |
+
each edge_attr dict keyed by edge key. The inner dict
|
| 204 |
+
(edge_attr_dict) represents the edge data and holds edge attribute
|
| 205 |
+
values keyed by attribute names.
|
| 206 |
+
|
| 207 |
+
Each of these four dicts in the dict-of-dict-of-dict-of-dict
|
| 208 |
+
structure can be replaced by a user defined dict-like object.
|
| 209 |
+
In general, the dict-like features should be maintained but
|
| 210 |
+
extra features can be added. To replace one of the dicts create
|
| 211 |
+
a new graph class by changing the class(!) variable holding the
|
| 212 |
+
factory for that dict-like structure. The variable names are
|
| 213 |
+
node_dict_factory, node_attr_dict_factory, adjlist_inner_dict_factory,
|
| 214 |
+
adjlist_outer_dict_factory, edge_key_dict_factory, edge_attr_dict_factory
|
| 215 |
+
and graph_attr_dict_factory.
|
| 216 |
+
|
| 217 |
+
node_dict_factory : function, (default: dict)
|
| 218 |
+
Factory function to be used to create the dict containing node
|
| 219 |
+
attributes, keyed by node id.
|
| 220 |
+
It should require no arguments and return a dict-like object
|
| 221 |
+
|
| 222 |
+
node_attr_dict_factory: function, (default: dict)
|
| 223 |
+
Factory function to be used to create the node attribute
|
| 224 |
+
dict which holds attribute values keyed by attribute name.
|
| 225 |
+
It should require no arguments and return a dict-like object
|
| 226 |
+
|
| 227 |
+
adjlist_outer_dict_factory : function, (default: dict)
|
| 228 |
+
Factory function to be used to create the outer-most dict
|
| 229 |
+
in the data structure that holds adjacency info keyed by node.
|
| 230 |
+
It should require no arguments and return a dict-like object.
|
| 231 |
+
|
| 232 |
+
adjlist_inner_dict_factory : function, (default: dict)
|
| 233 |
+
Factory function to be used to create the adjacency list
|
| 234 |
+
dict which holds multiedge key dicts keyed by neighbor.
|
| 235 |
+
It should require no arguments and return a dict-like object.
|
| 236 |
+
|
| 237 |
+
edge_key_dict_factory : function, (default: dict)
|
| 238 |
+
Factory function to be used to create the edge key dict
|
| 239 |
+
which holds edge data keyed by edge key.
|
| 240 |
+
It should require no arguments and return a dict-like object.
|
| 241 |
+
|
| 242 |
+
edge_attr_dict_factory : function, (default: dict)
|
| 243 |
+
Factory function to be used to create the edge attribute
|
| 244 |
+
dict which holds attribute values keyed by attribute name.
|
| 245 |
+
It should require no arguments and return a dict-like object.
|
| 246 |
+
|
| 247 |
+
graph_attr_dict_factory : function, (default: dict)
|
| 248 |
+
Factory function to be used to create the graph attribute
|
| 249 |
+
dict which holds attribute values keyed by attribute name.
|
| 250 |
+
It should require no arguments and return a dict-like object.
|
| 251 |
+
|
| 252 |
+
Typically, if your extension doesn't impact the data structure all
|
| 253 |
+
methods will inherited without issue except: `to_directed/to_undirected`.
|
| 254 |
+
By default these methods create a DiGraph/Graph class and you probably
|
| 255 |
+
want them to create your extension of a DiGraph/Graph. To facilitate
|
| 256 |
+
this we define two class variables that you can set in your subclass.
|
| 257 |
+
|
| 258 |
+
to_directed_class : callable, (default: DiGraph or MultiDiGraph)
|
| 259 |
+
Class to create a new graph structure in the `to_directed` method.
|
| 260 |
+
If `None`, a NetworkX class (DiGraph or MultiDiGraph) is used.
|
| 261 |
+
|
| 262 |
+
to_undirected_class : callable, (default: Graph or MultiGraph)
|
| 263 |
+
Class to create a new graph structure in the `to_undirected` method.
|
| 264 |
+
If `None`, a NetworkX class (Graph or MultiGraph) is used.
|
| 265 |
+
|
| 266 |
+
**Subclassing Example**
|
| 267 |
+
|
| 268 |
+
Create a low memory graph class that effectively disallows edge
|
| 269 |
+
attributes by using a single attribute dict for all edges.
|
| 270 |
+
This reduces the memory used, but you lose edge attributes.
|
| 271 |
+
|
| 272 |
+
>>> class ThinGraph(nx.Graph):
|
| 273 |
+
... all_edge_dict = {"weight": 1}
|
| 274 |
+
...
|
| 275 |
+
... def single_edge_dict(self):
|
| 276 |
+
... return self.all_edge_dict
|
| 277 |
+
...
|
| 278 |
+
... edge_attr_dict_factory = single_edge_dict
|
| 279 |
+
>>> G = ThinGraph()
|
| 280 |
+
>>> G.add_edge(2, 1)
|
| 281 |
+
>>> G[2][1]
|
| 282 |
+
{'weight': 1}
|
| 283 |
+
>>> G.add_edge(2, 2)
|
| 284 |
+
>>> G[2][1] is G[2][2]
|
| 285 |
+
True
|
| 286 |
+
"""
|
| 287 |
+
|
| 288 |
+
# node_dict_factory = dict # already assigned in Graph
|
| 289 |
+
# adjlist_outer_dict_factory = dict
|
| 290 |
+
# adjlist_inner_dict_factory = dict
|
| 291 |
+
edge_key_dict_factory = dict
|
| 292 |
+
# edge_attr_dict_factory = dict
|
| 293 |
+
|
| 294 |
+
def to_directed_class(self):
|
| 295 |
+
"""Returns the class to use for empty directed copies.
|
| 296 |
+
|
| 297 |
+
If you subclass the base classes, use this to designate
|
| 298 |
+
what directed class to use for `to_directed()` copies.
|
| 299 |
+
"""
|
| 300 |
+
return nx.MultiDiGraph
|
| 301 |
+
|
| 302 |
+
def to_undirected_class(self):
|
| 303 |
+
"""Returns the class to use for empty undirected copies.
|
| 304 |
+
|
| 305 |
+
If you subclass the base classes, use this to designate
|
| 306 |
+
what directed class to use for `to_directed()` copies.
|
| 307 |
+
"""
|
| 308 |
+
return MultiGraph
|
| 309 |
+
|
| 310 |
+
def __init__(self, incoming_graph_data=None, multigraph_input=None, **attr):
|
| 311 |
+
"""Initialize a graph with edges, name, or graph attributes.
|
| 312 |
+
|
| 313 |
+
Parameters
|
| 314 |
+
----------
|
| 315 |
+
incoming_graph_data : input graph
|
| 316 |
+
Data to initialize graph. If incoming_graph_data=None (default)
|
| 317 |
+
an empty graph is created. The data can be an edge list, or any
|
| 318 |
+
NetworkX graph object. If the corresponding optional Python
|
| 319 |
+
packages are installed the data can also be a 2D NumPy array, a
|
| 320 |
+
SciPy sparse array, or a PyGraphviz graph.
|
| 321 |
+
|
| 322 |
+
multigraph_input : bool or None (default None)
|
| 323 |
+
Note: Only used when `incoming_graph_data` is a dict.
|
| 324 |
+
If True, `incoming_graph_data` is assumed to be a
|
| 325 |
+
dict-of-dict-of-dict-of-dict structure keyed by
|
| 326 |
+
node to neighbor to edge keys to edge data for multi-edges.
|
| 327 |
+
A NetworkXError is raised if this is not the case.
|
| 328 |
+
If False, :func:`to_networkx_graph` is used to try to determine
|
| 329 |
+
the dict's graph data structure as either a dict-of-dict-of-dict
|
| 330 |
+
keyed by node to neighbor to edge data, or a dict-of-iterable
|
| 331 |
+
keyed by node to neighbors.
|
| 332 |
+
If None, the treatment for True is tried, but if it fails,
|
| 333 |
+
the treatment for False is tried.
|
| 334 |
+
|
| 335 |
+
attr : keyword arguments, optional (default= no attributes)
|
| 336 |
+
Attributes to add to graph as key=value pairs.
|
| 337 |
+
|
| 338 |
+
See Also
|
| 339 |
+
--------
|
| 340 |
+
convert
|
| 341 |
+
|
| 342 |
+
Examples
|
| 343 |
+
--------
|
| 344 |
+
>>> G = nx.MultiGraph()
|
| 345 |
+
>>> G = nx.MultiGraph(name="my graph")
|
| 346 |
+
>>> e = [(1, 2), (1, 2), (2, 3), (3, 4)] # list of edges
|
| 347 |
+
>>> G = nx.MultiGraph(e)
|
| 348 |
+
|
| 349 |
+
Arbitrary graph attribute pairs (key=value) may be assigned
|
| 350 |
+
|
| 351 |
+
>>> G = nx.MultiGraph(e, day="Friday")
|
| 352 |
+
>>> G.graph
|
| 353 |
+
{'day': 'Friday'}
|
| 354 |
+
|
| 355 |
+
"""
|
| 356 |
+
# multigraph_input can be None/True/False. So check "is not False"
|
| 357 |
+
if isinstance(incoming_graph_data, dict) and multigraph_input is not False:
|
| 358 |
+
Graph.__init__(self)
|
| 359 |
+
try:
|
| 360 |
+
convert.from_dict_of_dicts(
|
| 361 |
+
incoming_graph_data, create_using=self, multigraph_input=True
|
| 362 |
+
)
|
| 363 |
+
self.graph.update(attr)
|
| 364 |
+
except Exception as err:
|
| 365 |
+
if multigraph_input is True:
|
| 366 |
+
raise nx.NetworkXError(
|
| 367 |
+
f"converting multigraph_input raised:\n{type(err)}: {err}"
|
| 368 |
+
)
|
| 369 |
+
Graph.__init__(self, incoming_graph_data, **attr)
|
| 370 |
+
else:
|
| 371 |
+
Graph.__init__(self, incoming_graph_data, **attr)
|
| 372 |
+
|
| 373 |
+
@cached_property
|
| 374 |
+
def adj(self):
|
| 375 |
+
"""Graph adjacency object holding the neighbors of each node.
|
| 376 |
+
|
| 377 |
+
This object is a read-only dict-like structure with node keys
|
| 378 |
+
and neighbor-dict values. The neighbor-dict is keyed by neighbor
|
| 379 |
+
to the edgekey-data-dict. So `G.adj[3][2][0]['color'] = 'blue'` sets
|
| 380 |
+
the color of the edge `(3, 2, 0)` to `"blue"`.
|
| 381 |
+
|
| 382 |
+
Iterating over G.adj behaves like a dict. Useful idioms include
|
| 383 |
+
`for nbr, edgesdict in G.adj[n].items():`.
|
| 384 |
+
|
| 385 |
+
The neighbor information is also provided by subscripting the graph.
|
| 386 |
+
|
| 387 |
+
Examples
|
| 388 |
+
--------
|
| 389 |
+
>>> e = [(1, 2), (1, 2), (1, 3), (3, 4)] # list of edges
|
| 390 |
+
>>> G = nx.MultiGraph(e)
|
| 391 |
+
>>> G.edges[1, 2, 0]["weight"] = 3
|
| 392 |
+
>>> result = set()
|
| 393 |
+
>>> for edgekey, data in G[1][2].items():
|
| 394 |
+
... result.add(data.get("weight", 1))
|
| 395 |
+
>>> result
|
| 396 |
+
{1, 3}
|
| 397 |
+
|
| 398 |
+
For directed graphs, `G.adj` holds outgoing (successor) info.
|
| 399 |
+
"""
|
| 400 |
+
return MultiAdjacencyView(self._adj)
|
| 401 |
+
|
| 402 |
+
def new_edge_key(self, u, v):
|
| 403 |
+
"""Returns an unused key for edges between nodes `u` and `v`.
|
| 404 |
+
|
| 405 |
+
The nodes `u` and `v` do not need to be already in the graph.
|
| 406 |
+
|
| 407 |
+
Notes
|
| 408 |
+
-----
|
| 409 |
+
In the standard MultiGraph class the new key is the number of existing
|
| 410 |
+
edges between `u` and `v` (increased if necessary to ensure unused).
|
| 411 |
+
The first edge will have key 0, then 1, etc. If an edge is removed
|
| 412 |
+
further new_edge_keys may not be in this order.
|
| 413 |
+
|
| 414 |
+
Parameters
|
| 415 |
+
----------
|
| 416 |
+
u, v : nodes
|
| 417 |
+
|
| 418 |
+
Returns
|
| 419 |
+
-------
|
| 420 |
+
key : int
|
| 421 |
+
"""
|
| 422 |
+
try:
|
| 423 |
+
keydict = self._adj[u][v]
|
| 424 |
+
except KeyError:
|
| 425 |
+
return 0
|
| 426 |
+
key = len(keydict)
|
| 427 |
+
while key in keydict:
|
| 428 |
+
key += 1
|
| 429 |
+
return key
|
| 430 |
+
|
| 431 |
+
def add_edge(self, u_for_edge, v_for_edge, key=None, **attr):
|
| 432 |
+
"""Add an edge between u and v.
|
| 433 |
+
|
| 434 |
+
The nodes u and v will be automatically added if they are
|
| 435 |
+
not already in the graph.
|
| 436 |
+
|
| 437 |
+
Edge attributes can be specified with keywords or by directly
|
| 438 |
+
accessing the edge's attribute dictionary. See examples below.
|
| 439 |
+
|
| 440 |
+
Parameters
|
| 441 |
+
----------
|
| 442 |
+
u_for_edge, v_for_edge : nodes
|
| 443 |
+
Nodes can be, for example, strings or numbers.
|
| 444 |
+
Nodes must be hashable (and not None) Python objects.
|
| 445 |
+
key : hashable identifier, optional (default=lowest unused integer)
|
| 446 |
+
Used to distinguish multiedges between a pair of nodes.
|
| 447 |
+
attr : keyword arguments, optional
|
| 448 |
+
Edge data (or labels or objects) can be assigned using
|
| 449 |
+
keyword arguments.
|
| 450 |
+
|
| 451 |
+
Returns
|
| 452 |
+
-------
|
| 453 |
+
The edge key assigned to the edge.
|
| 454 |
+
|
| 455 |
+
See Also
|
| 456 |
+
--------
|
| 457 |
+
add_edges_from : add a collection of edges
|
| 458 |
+
|
| 459 |
+
Notes
|
| 460 |
+
-----
|
| 461 |
+
To replace/update edge data, use the optional key argument
|
| 462 |
+
to identify a unique edge. Otherwise a new edge will be created.
|
| 463 |
+
|
| 464 |
+
NetworkX algorithms designed for weighted graphs cannot use
|
| 465 |
+
multigraphs directly because it is not clear how to handle
|
| 466 |
+
multiedge weights. Convert to Graph using edge attribute
|
| 467 |
+
'weight' to enable weighted graph algorithms.
|
| 468 |
+
|
| 469 |
+
Default keys are generated using the method `new_edge_key()`.
|
| 470 |
+
This method can be overridden by subclassing the base class and
|
| 471 |
+
providing a custom `new_edge_key()` method.
|
| 472 |
+
|
| 473 |
+
Examples
|
| 474 |
+
--------
|
| 475 |
+
The following each add an additional edge e=(1, 2) to graph G:
|
| 476 |
+
|
| 477 |
+
>>> G = nx.MultiGraph()
|
| 478 |
+
>>> e = (1, 2)
|
| 479 |
+
>>> ekey = G.add_edge(1, 2) # explicit two-node form
|
| 480 |
+
>>> G.add_edge(*e) # single edge as tuple of two nodes
|
| 481 |
+
1
|
| 482 |
+
>>> G.add_edges_from([(1, 2)]) # add edges from iterable container
|
| 483 |
+
[2]
|
| 484 |
+
|
| 485 |
+
Associate data to edges using keywords:
|
| 486 |
+
|
| 487 |
+
>>> ekey = G.add_edge(1, 2, weight=3)
|
| 488 |
+
>>> ekey = G.add_edge(1, 2, key=0, weight=4) # update data for key=0
|
| 489 |
+
>>> ekey = G.add_edge(1, 3, weight=7, capacity=15, length=342.7)
|
| 490 |
+
|
| 491 |
+
For non-string attribute keys, use subscript notation.
|
| 492 |
+
|
| 493 |
+
>>> ekey = G.add_edge(1, 2)
|
| 494 |
+
>>> G[1][2][0].update({0: 5})
|
| 495 |
+
>>> G.edges[1, 2, 0].update({0: 5})
|
| 496 |
+
"""
|
| 497 |
+
u, v = u_for_edge, v_for_edge
|
| 498 |
+
# add nodes
|
| 499 |
+
if u not in self._adj:
|
| 500 |
+
if u is None:
|
| 501 |
+
raise ValueError("None cannot be a node")
|
| 502 |
+
self._adj[u] = self.adjlist_inner_dict_factory()
|
| 503 |
+
self._node[u] = self.node_attr_dict_factory()
|
| 504 |
+
if v not in self._adj:
|
| 505 |
+
if v is None:
|
| 506 |
+
raise ValueError("None cannot be a node")
|
| 507 |
+
self._adj[v] = self.adjlist_inner_dict_factory()
|
| 508 |
+
self._node[v] = self.node_attr_dict_factory()
|
| 509 |
+
if key is None:
|
| 510 |
+
key = self.new_edge_key(u, v)
|
| 511 |
+
if v in self._adj[u]:
|
| 512 |
+
keydict = self._adj[u][v]
|
| 513 |
+
datadict = keydict.get(key, self.edge_attr_dict_factory())
|
| 514 |
+
datadict.update(attr)
|
| 515 |
+
keydict[key] = datadict
|
| 516 |
+
else:
|
| 517 |
+
# selfloops work this way without special treatment
|
| 518 |
+
datadict = self.edge_attr_dict_factory()
|
| 519 |
+
datadict.update(attr)
|
| 520 |
+
keydict = self.edge_key_dict_factory()
|
| 521 |
+
keydict[key] = datadict
|
| 522 |
+
self._adj[u][v] = keydict
|
| 523 |
+
self._adj[v][u] = keydict
|
| 524 |
+
nx._clear_cache(self)
|
| 525 |
+
return key
|
| 526 |
+
|
| 527 |
+
def add_edges_from(self, ebunch_to_add, **attr):
|
| 528 |
+
"""Add all the edges in ebunch_to_add.
|
| 529 |
+
|
| 530 |
+
Parameters
|
| 531 |
+
----------
|
| 532 |
+
ebunch_to_add : container of edges
|
| 533 |
+
Each edge given in the container will be added to the
|
| 534 |
+
graph. The edges can be:
|
| 535 |
+
|
| 536 |
+
- 2-tuples (u, v) or
|
| 537 |
+
- 3-tuples (u, v, d) for an edge data dict d, or
|
| 538 |
+
- 3-tuples (u, v, k) for not iterable key k, or
|
| 539 |
+
- 4-tuples (u, v, k, d) for an edge with data and key k
|
| 540 |
+
|
| 541 |
+
attr : keyword arguments, optional
|
| 542 |
+
Edge data (or labels or objects) can be assigned using
|
| 543 |
+
keyword arguments.
|
| 544 |
+
|
| 545 |
+
Returns
|
| 546 |
+
-------
|
| 547 |
+
A list of edge keys assigned to the edges in `ebunch`.
|
| 548 |
+
|
| 549 |
+
See Also
|
| 550 |
+
--------
|
| 551 |
+
add_edge : add a single edge
|
| 552 |
+
add_weighted_edges_from : convenient way to add weighted edges
|
| 553 |
+
|
| 554 |
+
Notes
|
| 555 |
+
-----
|
| 556 |
+
Adding the same edge twice has no effect but any edge data
|
| 557 |
+
will be updated when each duplicate edge is added.
|
| 558 |
+
|
| 559 |
+
Edge attributes specified in an ebunch take precedence over
|
| 560 |
+
attributes specified via keyword arguments.
|
| 561 |
+
|
| 562 |
+
Default keys are generated using the method ``new_edge_key()``.
|
| 563 |
+
This method can be overridden by subclassing the base class and
|
| 564 |
+
providing a custom ``new_edge_key()`` method.
|
| 565 |
+
|
| 566 |
+
When adding edges from an iterator over the graph you are changing,
|
| 567 |
+
a `RuntimeError` can be raised with message:
|
| 568 |
+
`RuntimeError: dictionary changed size during iteration`. This
|
| 569 |
+
happens when the graph's underlying dictionary is modified during
|
| 570 |
+
iteration. To avoid this error, evaluate the iterator into a separate
|
| 571 |
+
object, e.g. by using `list(iterator_of_edges)`, and pass this
|
| 572 |
+
object to `G.add_edges_from`.
|
| 573 |
+
|
| 574 |
+
Examples
|
| 575 |
+
--------
|
| 576 |
+
>>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 577 |
+
>>> G.add_edges_from([(0, 1), (1, 2)]) # using a list of edge tuples
|
| 578 |
+
>>> e = zip(range(0, 3), range(1, 4))
|
| 579 |
+
>>> G.add_edges_from(e) # Add the path graph 0-1-2-3
|
| 580 |
+
|
| 581 |
+
Associate data to edges
|
| 582 |
+
|
| 583 |
+
>>> G.add_edges_from([(1, 2), (2, 3)], weight=3)
|
| 584 |
+
>>> G.add_edges_from([(3, 4), (1, 4)], label="WN2898")
|
| 585 |
+
|
| 586 |
+
Evaluate an iterator over a graph if using it to modify the same graph
|
| 587 |
+
|
| 588 |
+
>>> G = nx.MultiGraph([(1, 2), (2, 3), (3, 4)])
|
| 589 |
+
>>> # Grow graph by one new node, adding edges to all existing nodes.
|
| 590 |
+
>>> # wrong way - will raise RuntimeError
|
| 591 |
+
>>> # G.add_edges_from(((5, n) for n in G.nodes))
|
| 592 |
+
>>> # right way - note that there will be no self-edge for node 5
|
| 593 |
+
>>> assigned_keys = G.add_edges_from(list((5, n) for n in G.nodes))
|
| 594 |
+
"""
|
| 595 |
+
keylist = []
|
| 596 |
+
for e in ebunch_to_add:
|
| 597 |
+
ne = len(e)
|
| 598 |
+
if ne == 4:
|
| 599 |
+
u, v, key, dd = e
|
| 600 |
+
elif ne == 3:
|
| 601 |
+
u, v, dd = e
|
| 602 |
+
key = None
|
| 603 |
+
elif ne == 2:
|
| 604 |
+
u, v = e
|
| 605 |
+
dd = {}
|
| 606 |
+
key = None
|
| 607 |
+
else:
|
| 608 |
+
msg = f"Edge tuple {e} must be a 2-tuple, 3-tuple or 4-tuple."
|
| 609 |
+
raise NetworkXError(msg)
|
| 610 |
+
ddd = {}
|
| 611 |
+
ddd.update(attr)
|
| 612 |
+
try:
|
| 613 |
+
ddd.update(dd)
|
| 614 |
+
except (TypeError, ValueError):
|
| 615 |
+
if ne != 3:
|
| 616 |
+
raise
|
| 617 |
+
key = dd # ne == 3 with 3rd value not dict, must be a key
|
| 618 |
+
key = self.add_edge(u, v, key)
|
| 619 |
+
self[u][v][key].update(ddd)
|
| 620 |
+
keylist.append(key)
|
| 621 |
+
nx._clear_cache(self)
|
| 622 |
+
return keylist
|
| 623 |
+
|
| 624 |
+
def remove_edge(self, u, v, key=None):
|
| 625 |
+
"""Remove an edge between u and v.
|
| 626 |
+
|
| 627 |
+
Parameters
|
| 628 |
+
----------
|
| 629 |
+
u, v : nodes
|
| 630 |
+
Remove an edge between nodes u and v.
|
| 631 |
+
key : hashable identifier, optional (default=None)
|
| 632 |
+
Used to distinguish multiple edges between a pair of nodes.
|
| 633 |
+
If None, remove a single edge between u and v. If there are
|
| 634 |
+
multiple edges, removes the last edge added in terms of
|
| 635 |
+
insertion order.
|
| 636 |
+
|
| 637 |
+
Raises
|
| 638 |
+
------
|
| 639 |
+
NetworkXError
|
| 640 |
+
If there is not an edge between u and v, or
|
| 641 |
+
if there is no edge with the specified key.
|
| 642 |
+
|
| 643 |
+
See Also
|
| 644 |
+
--------
|
| 645 |
+
remove_edges_from : remove a collection of edges
|
| 646 |
+
|
| 647 |
+
Examples
|
| 648 |
+
--------
|
| 649 |
+
>>> G = nx.MultiGraph()
|
| 650 |
+
>>> nx.add_path(G, [0, 1, 2, 3])
|
| 651 |
+
>>> G.remove_edge(0, 1)
|
| 652 |
+
>>> e = (1, 2)
|
| 653 |
+
>>> G.remove_edge(*e) # unpacks e from an edge tuple
|
| 654 |
+
|
| 655 |
+
For multiple edges
|
| 656 |
+
|
| 657 |
+
>>> G = nx.MultiGraph() # or MultiDiGraph, etc
|
| 658 |
+
>>> G.add_edges_from([(1, 2), (1, 2), (1, 2)]) # key_list returned
|
| 659 |
+
[0, 1, 2]
|
| 660 |
+
|
| 661 |
+
When ``key=None`` (the default), edges are removed in the opposite
|
| 662 |
+
order that they were added:
|
| 663 |
+
|
| 664 |
+
>>> G.remove_edge(1, 2)
|
| 665 |
+
>>> G.edges(keys=True)
|
| 666 |
+
MultiEdgeView([(1, 2, 0), (1, 2, 1)])
|
| 667 |
+
>>> G.remove_edge(2, 1) # edges are not directed
|
| 668 |
+
>>> G.edges(keys=True)
|
| 669 |
+
MultiEdgeView([(1, 2, 0)])
|
| 670 |
+
|
| 671 |
+
For edges with keys
|
| 672 |
+
|
| 673 |
+
>>> G = nx.MultiGraph()
|
| 674 |
+
>>> G.add_edge(1, 2, key="first")
|
| 675 |
+
'first'
|
| 676 |
+
>>> G.add_edge(1, 2, key="second")
|
| 677 |
+
'second'
|
| 678 |
+
>>> G.remove_edge(1, 2, key="first")
|
| 679 |
+
>>> G.edges(keys=True)
|
| 680 |
+
MultiEdgeView([(1, 2, 'second')])
|
| 681 |
+
|
| 682 |
+
"""
|
| 683 |
+
try:
|
| 684 |
+
d = self._adj[u][v]
|
| 685 |
+
except KeyError as err:
|
| 686 |
+
raise NetworkXError(f"The edge {u}-{v} is not in the graph.") from err
|
| 687 |
+
# remove the edge with specified data
|
| 688 |
+
if key is None:
|
| 689 |
+
d.popitem()
|
| 690 |
+
else:
|
| 691 |
+
try:
|
| 692 |
+
del d[key]
|
| 693 |
+
except KeyError as err:
|
| 694 |
+
msg = f"The edge {u}-{v} with key {key} is not in the graph."
|
| 695 |
+
raise NetworkXError(msg) from err
|
| 696 |
+
if len(d) == 0:
|
| 697 |
+
# remove the key entries if last edge
|
| 698 |
+
del self._adj[u][v]
|
| 699 |
+
if u != v: # check for selfloop
|
| 700 |
+
del self._adj[v][u]
|
| 701 |
+
nx._clear_cache(self)
|
| 702 |
+
|
| 703 |
+
def remove_edges_from(self, ebunch):
|
| 704 |
+
"""Remove all edges specified in ebunch.
|
| 705 |
+
|
| 706 |
+
Parameters
|
| 707 |
+
----------
|
| 708 |
+
ebunch: list or container of edge tuples
|
| 709 |
+
Each edge given in the list or container will be removed
|
| 710 |
+
from the graph. The edges can be:
|
| 711 |
+
|
| 712 |
+
- 2-tuples (u, v) A single edge between u and v is removed.
|
| 713 |
+
- 3-tuples (u, v, key) The edge identified by key is removed.
|
| 714 |
+
- 4-tuples (u, v, key, data) where data is ignored.
|
| 715 |
+
|
| 716 |
+
See Also
|
| 717 |
+
--------
|
| 718 |
+
remove_edge : remove a single edge
|
| 719 |
+
|
| 720 |
+
Notes
|
| 721 |
+
-----
|
| 722 |
+
Will fail silently if an edge in ebunch is not in the graph.
|
| 723 |
+
|
| 724 |
+
Examples
|
| 725 |
+
--------
|
| 726 |
+
>>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 727 |
+
>>> ebunch = [(1, 2), (2, 3)]
|
| 728 |
+
>>> G.remove_edges_from(ebunch)
|
| 729 |
+
|
| 730 |
+
Removing multiple copies of edges
|
| 731 |
+
|
| 732 |
+
>>> G = nx.MultiGraph()
|
| 733 |
+
>>> keys = G.add_edges_from([(1, 2), (1, 2), (1, 2)])
|
| 734 |
+
>>> G.remove_edges_from([(1, 2), (2, 1)]) # edges aren't directed
|
| 735 |
+
>>> list(G.edges())
|
| 736 |
+
[(1, 2)]
|
| 737 |
+
>>> G.remove_edges_from([(1, 2), (1, 2)]) # silently ignore extra copy
|
| 738 |
+
>>> list(G.edges) # now empty graph
|
| 739 |
+
[]
|
| 740 |
+
|
| 741 |
+
When the edge is a 2-tuple ``(u, v)`` but there are multiple edges between
|
| 742 |
+
u and v in the graph, the most recent edge (in terms of insertion
|
| 743 |
+
order) is removed.
|
| 744 |
+
|
| 745 |
+
>>> G = nx.MultiGraph()
|
| 746 |
+
>>> for key in ("x", "y", "a"):
|
| 747 |
+
... k = G.add_edge(0, 1, key=key)
|
| 748 |
+
>>> G.edges(keys=True)
|
| 749 |
+
MultiEdgeView([(0, 1, 'x'), (0, 1, 'y'), (0, 1, 'a')])
|
| 750 |
+
>>> G.remove_edges_from([(0, 1)])
|
| 751 |
+
>>> G.edges(keys=True)
|
| 752 |
+
MultiEdgeView([(0, 1, 'x'), (0, 1, 'y')])
|
| 753 |
+
|
| 754 |
+
"""
|
| 755 |
+
for e in ebunch:
|
| 756 |
+
try:
|
| 757 |
+
self.remove_edge(*e[:3])
|
| 758 |
+
except NetworkXError:
|
| 759 |
+
pass
|
| 760 |
+
nx._clear_cache(self)
|
| 761 |
+
|
| 762 |
+
def has_edge(self, u, v, key=None):
|
| 763 |
+
"""Returns True if the graph has an edge between nodes u and v.
|
| 764 |
+
|
| 765 |
+
This is the same as `v in G[u] or key in G[u][v]`
|
| 766 |
+
without KeyError exceptions.
|
| 767 |
+
|
| 768 |
+
Parameters
|
| 769 |
+
----------
|
| 770 |
+
u, v : nodes
|
| 771 |
+
Nodes can be, for example, strings or numbers.
|
| 772 |
+
|
| 773 |
+
key : hashable identifier, optional (default=None)
|
| 774 |
+
If specified return True only if the edge with
|
| 775 |
+
key is found.
|
| 776 |
+
|
| 777 |
+
Returns
|
| 778 |
+
-------
|
| 779 |
+
edge_ind : bool
|
| 780 |
+
True if edge is in the graph, False otherwise.
|
| 781 |
+
|
| 782 |
+
Examples
|
| 783 |
+
--------
|
| 784 |
+
Can be called either using two nodes u, v, an edge tuple (u, v),
|
| 785 |
+
or an edge tuple (u, v, key).
|
| 786 |
+
|
| 787 |
+
>>> G = nx.MultiGraph() # or MultiDiGraph
|
| 788 |
+
>>> nx.add_path(G, [0, 1, 2, 3])
|
| 789 |
+
>>> G.has_edge(0, 1) # using two nodes
|
| 790 |
+
True
|
| 791 |
+
>>> e = (0, 1)
|
| 792 |
+
>>> G.has_edge(*e) # e is a 2-tuple (u, v)
|
| 793 |
+
True
|
| 794 |
+
>>> G.add_edge(0, 1, key="a")
|
| 795 |
+
'a'
|
| 796 |
+
>>> G.has_edge(0, 1, key="a") # specify key
|
| 797 |
+
True
|
| 798 |
+
>>> G.has_edge(1, 0, key="a") # edges aren't directed
|
| 799 |
+
True
|
| 800 |
+
>>> e = (0, 1, "a")
|
| 801 |
+
>>> G.has_edge(*e) # e is a 3-tuple (u, v, 'a')
|
| 802 |
+
True
|
| 803 |
+
|
| 804 |
+
The following syntax are equivalent:
|
| 805 |
+
|
| 806 |
+
>>> G.has_edge(0, 1)
|
| 807 |
+
True
|
| 808 |
+
>>> 1 in G[0] # though this gives :exc:`KeyError` if 0 not in G
|
| 809 |
+
True
|
| 810 |
+
>>> 0 in G[1] # other order; also gives :exc:`KeyError` if 0 not in G
|
| 811 |
+
True
|
| 812 |
+
|
| 813 |
+
"""
|
| 814 |
+
try:
|
| 815 |
+
if key is None:
|
| 816 |
+
return v in self._adj[u]
|
| 817 |
+
else:
|
| 818 |
+
return key in self._adj[u][v]
|
| 819 |
+
except KeyError:
|
| 820 |
+
return False
|
| 821 |
+
|
| 822 |
+
@cached_property
|
| 823 |
+
def edges(self):
|
| 824 |
+
"""Returns an iterator over the edges.
|
| 825 |
+
|
| 826 |
+
edges(self, nbunch=None, data=False, keys=False, default=None)
|
| 827 |
+
|
| 828 |
+
The MultiEdgeView provides set-like operations on the edge-tuples
|
| 829 |
+
as well as edge attribute lookup. When called, it also provides
|
| 830 |
+
an EdgeDataView object which allows control of access to edge
|
| 831 |
+
attributes (but does not provide set-like operations).
|
| 832 |
+
Hence, ``G.edges[u, v, k]['color']`` provides the value of the color
|
| 833 |
+
attribute for the edge from ``u`` to ``v`` with key ``k`` while
|
| 834 |
+
``for (u, v, k, c) in G.edges(data='color', keys=True, default="red"):``
|
| 835 |
+
iterates through all the edges yielding the color attribute with
|
| 836 |
+
default `'red'` if no color attribute exists.
|
| 837 |
+
|
| 838 |
+
Edges are returned as tuples with optional data and keys
|
| 839 |
+
in the order (node, neighbor, key, data). If ``keys=True`` is not
|
| 840 |
+
provided, the tuples will just be (node, neighbor, data), but
|
| 841 |
+
multiple tuples with the same node and neighbor will be generated
|
| 842 |
+
when multiple edges exist between two nodes.
|
| 843 |
+
|
| 844 |
+
Parameters
|
| 845 |
+
----------
|
| 846 |
+
nbunch : single node, container, or all nodes (default= all nodes)
|
| 847 |
+
The view will only report edges from these nodes.
|
| 848 |
+
data : string or bool, optional (default=False)
|
| 849 |
+
The edge attribute returned in 3-tuple (u, v, ddict[data]).
|
| 850 |
+
If True, return edge attribute dict in 3-tuple (u, v, ddict).
|
| 851 |
+
If False, return 2-tuple (u, v).
|
| 852 |
+
keys : bool, optional (default=False)
|
| 853 |
+
If True, return edge keys with each edge, creating (u, v, k)
|
| 854 |
+
tuples or (u, v, k, d) tuples if data is also requested.
|
| 855 |
+
default : value, optional (default=None)
|
| 856 |
+
Value used for edges that don't have the requested attribute.
|
| 857 |
+
Only relevant if data is not True or False.
|
| 858 |
+
|
| 859 |
+
Returns
|
| 860 |
+
-------
|
| 861 |
+
edges : MultiEdgeView
|
| 862 |
+
A view of edge attributes, usually it iterates over (u, v)
|
| 863 |
+
(u, v, k) or (u, v, k, d) tuples of edges, but can also be
|
| 864 |
+
used for attribute lookup as ``edges[u, v, k]['foo']``.
|
| 865 |
+
|
| 866 |
+
Notes
|
| 867 |
+
-----
|
| 868 |
+
Nodes in nbunch that are not in the graph will be (quietly) ignored.
|
| 869 |
+
For directed graphs this returns the out-edges.
|
| 870 |
+
|
| 871 |
+
Examples
|
| 872 |
+
--------
|
| 873 |
+
>>> G = nx.MultiGraph()
|
| 874 |
+
>>> nx.add_path(G, [0, 1, 2])
|
| 875 |
+
>>> key = G.add_edge(2, 3, weight=5)
|
| 876 |
+
>>> key2 = G.add_edge(2, 1, weight=2) # multi-edge
|
| 877 |
+
>>> [e for e in G.edges()]
|
| 878 |
+
[(0, 1), (1, 2), (1, 2), (2, 3)]
|
| 879 |
+
>>> G.edges.data() # default data is {} (empty dict)
|
| 880 |
+
MultiEdgeDataView([(0, 1, {}), (1, 2, {}), (1, 2, {'weight': 2}), (2, 3, {'weight': 5})])
|
| 881 |
+
>>> G.edges.data("weight", default=1)
|
| 882 |
+
MultiEdgeDataView([(0, 1, 1), (1, 2, 1), (1, 2, 2), (2, 3, 5)])
|
| 883 |
+
>>> G.edges(keys=True) # default keys are integers
|
| 884 |
+
MultiEdgeView([(0, 1, 0), (1, 2, 0), (1, 2, 1), (2, 3, 0)])
|
| 885 |
+
>>> G.edges.data(keys=True)
|
| 886 |
+
MultiEdgeDataView([(0, 1, 0, {}), (1, 2, 0, {}), (1, 2, 1, {'weight': 2}), (2, 3, 0, {'weight': 5})])
|
| 887 |
+
>>> G.edges.data("weight", default=1, keys=True)
|
| 888 |
+
MultiEdgeDataView([(0, 1, 0, 1), (1, 2, 0, 1), (1, 2, 1, 2), (2, 3, 0, 5)])
|
| 889 |
+
>>> G.edges([0, 3]) # Note ordering of tuples from listed sources
|
| 890 |
+
MultiEdgeDataView([(0, 1), (3, 2)])
|
| 891 |
+
>>> G.edges([0, 3, 2, 1]) # Note ordering of tuples
|
| 892 |
+
MultiEdgeDataView([(0, 1), (3, 2), (2, 1), (2, 1)])
|
| 893 |
+
>>> G.edges(0)
|
| 894 |
+
MultiEdgeDataView([(0, 1)])
|
| 895 |
+
"""
|
| 896 |
+
return MultiEdgeView(self)
|
| 897 |
+
|
| 898 |
+
def get_edge_data(self, u, v, key=None, default=None):
|
| 899 |
+
"""Returns the attribute dictionary associated with edge (u, v,
|
| 900 |
+
key).
|
| 901 |
+
|
| 902 |
+
If a key is not provided, returns a dictionary mapping edge keys
|
| 903 |
+
to attribute dictionaries for each edge between u and v.
|
| 904 |
+
|
| 905 |
+
This is identical to `G[u][v][key]` except the default is returned
|
| 906 |
+
instead of an exception is the edge doesn't exist.
|
| 907 |
+
|
| 908 |
+
Parameters
|
| 909 |
+
----------
|
| 910 |
+
u, v : nodes
|
| 911 |
+
|
| 912 |
+
default : any Python object (default=None)
|
| 913 |
+
Value to return if the specific edge (u, v, key) is not
|
| 914 |
+
found, OR if there are no edges between u and v and no key
|
| 915 |
+
is specified.
|
| 916 |
+
|
| 917 |
+
key : hashable identifier, optional (default=None)
|
| 918 |
+
Return data only for the edge with specified key, as an
|
| 919 |
+
attribute dictionary (rather than a dictionary mapping keys
|
| 920 |
+
to attribute dictionaries).
|
| 921 |
+
|
| 922 |
+
Returns
|
| 923 |
+
-------
|
| 924 |
+
edge_dict : dictionary
|
| 925 |
+
The edge attribute dictionary, OR a dictionary mapping edge
|
| 926 |
+
keys to attribute dictionaries for each of those edges if no
|
| 927 |
+
specific key is provided (even if there's only one edge
|
| 928 |
+
between u and v).
|
| 929 |
+
|
| 930 |
+
Examples
|
| 931 |
+
--------
|
| 932 |
+
>>> G = nx.MultiGraph() # or MultiDiGraph
|
| 933 |
+
>>> key = G.add_edge(0, 1, key="a", weight=7)
|
| 934 |
+
>>> G[0][1]["a"] # key='a'
|
| 935 |
+
{'weight': 7}
|
| 936 |
+
>>> G.edges[0, 1, "a"] # key='a'
|
| 937 |
+
{'weight': 7}
|
| 938 |
+
|
| 939 |
+
Warning: we protect the graph data structure by making
|
| 940 |
+
`G.edges` and `G[1][2]` read-only dict-like structures.
|
| 941 |
+
However, you can assign values to attributes in e.g.
|
| 942 |
+
`G.edges[1, 2, 'a']` or `G[1][2]['a']` using an additional
|
| 943 |
+
bracket as shown next. You need to specify all edge info
|
| 944 |
+
to assign to the edge data associated with an edge.
|
| 945 |
+
|
| 946 |
+
>>> G[0][1]["a"]["weight"] = 10
|
| 947 |
+
>>> G.edges[0, 1, "a"]["weight"] = 10
|
| 948 |
+
>>> G[0][1]["a"]["weight"]
|
| 949 |
+
10
|
| 950 |
+
>>> G.edges[1, 0, "a"]["weight"]
|
| 951 |
+
10
|
| 952 |
+
|
| 953 |
+
>>> G = nx.MultiGraph() # or MultiDiGraph
|
| 954 |
+
>>> nx.add_path(G, [0, 1, 2, 3])
|
| 955 |
+
>>> G.edges[0, 1, 0]["weight"] = 5
|
| 956 |
+
>>> G.get_edge_data(0, 1)
|
| 957 |
+
{0: {'weight': 5}}
|
| 958 |
+
>>> e = (0, 1)
|
| 959 |
+
>>> G.get_edge_data(*e) # tuple form
|
| 960 |
+
{0: {'weight': 5}}
|
| 961 |
+
>>> G.get_edge_data(3, 0) # edge not in graph, returns None
|
| 962 |
+
>>> G.get_edge_data(3, 0, default=0) # edge not in graph, return default
|
| 963 |
+
0
|
| 964 |
+
>>> G.get_edge_data(1, 0, 0) # specific key gives back
|
| 965 |
+
{'weight': 5}
|
| 966 |
+
"""
|
| 967 |
+
try:
|
| 968 |
+
if key is None:
|
| 969 |
+
return self._adj[u][v]
|
| 970 |
+
else:
|
| 971 |
+
return self._adj[u][v][key]
|
| 972 |
+
except KeyError:
|
| 973 |
+
return default
|
| 974 |
+
|
| 975 |
+
@cached_property
|
| 976 |
+
def degree(self):
|
| 977 |
+
"""A DegreeView for the Graph as G.degree or G.degree().
|
| 978 |
+
|
| 979 |
+
The node degree is the number of edges adjacent to the node.
|
| 980 |
+
The weighted node degree is the sum of the edge weights for
|
| 981 |
+
edges incident to that node.
|
| 982 |
+
|
| 983 |
+
This object provides an iterator for (node, degree) as well as
|
| 984 |
+
lookup for the degree for a single node.
|
| 985 |
+
|
| 986 |
+
Parameters
|
| 987 |
+
----------
|
| 988 |
+
nbunch : single node, container, or all nodes (default= all nodes)
|
| 989 |
+
The view will only report edges incident to these nodes.
|
| 990 |
+
|
| 991 |
+
weight : string or None, optional (default=None)
|
| 992 |
+
The name of an edge attribute that holds the numerical value used
|
| 993 |
+
as a weight. If None, then each edge has weight 1.
|
| 994 |
+
The degree is the sum of the edge weights adjacent to the node.
|
| 995 |
+
|
| 996 |
+
Returns
|
| 997 |
+
-------
|
| 998 |
+
MultiDegreeView or int
|
| 999 |
+
If multiple nodes are requested (the default), returns a `MultiDegreeView`
|
| 1000 |
+
mapping nodes to their degree.
|
| 1001 |
+
If a single node is requested, returns the degree of the node as an integer.
|
| 1002 |
+
|
| 1003 |
+
Examples
|
| 1004 |
+
--------
|
| 1005 |
+
>>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 1006 |
+
>>> nx.add_path(G, [0, 1, 2, 3])
|
| 1007 |
+
>>> G.degree(0) # node 0 with degree 1
|
| 1008 |
+
1
|
| 1009 |
+
>>> list(G.degree([0, 1]))
|
| 1010 |
+
[(0, 1), (1, 2)]
|
| 1011 |
+
|
| 1012 |
+
"""
|
| 1013 |
+
return MultiDegreeView(self)
|
| 1014 |
+
|
| 1015 |
+
def is_multigraph(self):
|
| 1016 |
+
"""Returns True if graph is a multigraph, False otherwise."""
|
| 1017 |
+
return True
|
| 1018 |
+
|
| 1019 |
+
def is_directed(self):
|
| 1020 |
+
"""Returns True if graph is directed, False otherwise."""
|
| 1021 |
+
return False
|
| 1022 |
+
|
| 1023 |
+
def copy(self, as_view=False):
|
| 1024 |
+
"""Returns a copy of the graph.
|
| 1025 |
+
|
| 1026 |
+
The copy method by default returns an independent shallow copy
|
| 1027 |
+
of the graph and attributes. That is, if an attribute is a
|
| 1028 |
+
container, that container is shared by the original an the copy.
|
| 1029 |
+
Use Python's `copy.deepcopy` for new containers.
|
| 1030 |
+
|
| 1031 |
+
If `as_view` is True then a view is returned instead of a copy.
|
| 1032 |
+
|
| 1033 |
+
Notes
|
| 1034 |
+
-----
|
| 1035 |
+
All copies reproduce the graph structure, but data attributes
|
| 1036 |
+
may be handled in different ways. There are four types of copies
|
| 1037 |
+
of a graph that people might want.
|
| 1038 |
+
|
| 1039 |
+
Deepcopy -- A "deepcopy" copies the graph structure as well as
|
| 1040 |
+
all data attributes and any objects they might contain.
|
| 1041 |
+
The entire graph object is new so that changes in the copy
|
| 1042 |
+
do not affect the original object. (see Python's copy.deepcopy)
|
| 1043 |
+
|
| 1044 |
+
Data Reference (Shallow) -- For a shallow copy the graph structure
|
| 1045 |
+
is copied but the edge, node and graph attribute dicts are
|
| 1046 |
+
references to those in the original graph. This saves
|
| 1047 |
+
time and memory but could cause confusion if you change an attribute
|
| 1048 |
+
in one graph and it changes the attribute in the other.
|
| 1049 |
+
NetworkX does not provide this level of shallow copy.
|
| 1050 |
+
|
| 1051 |
+
Independent Shallow -- This copy creates new independent attribute
|
| 1052 |
+
dicts and then does a shallow copy of the attributes. That is, any
|
| 1053 |
+
attributes that are containers are shared between the new graph
|
| 1054 |
+
and the original. This is exactly what `dict.copy()` provides.
|
| 1055 |
+
You can obtain this style copy using:
|
| 1056 |
+
|
| 1057 |
+
>>> G = nx.path_graph(5)
|
| 1058 |
+
>>> H = G.copy()
|
| 1059 |
+
>>> H = G.copy(as_view=False)
|
| 1060 |
+
>>> H = nx.Graph(G)
|
| 1061 |
+
>>> H = G.__class__(G)
|
| 1062 |
+
|
| 1063 |
+
Fresh Data -- For fresh data, the graph structure is copied while
|
| 1064 |
+
new empty data attribute dicts are created. The resulting graph
|
| 1065 |
+
is independent of the original and it has no edge, node or graph
|
| 1066 |
+
attributes. Fresh copies are not enabled. Instead use:
|
| 1067 |
+
|
| 1068 |
+
>>> H = G.__class__()
|
| 1069 |
+
>>> H.add_nodes_from(G)
|
| 1070 |
+
>>> H.add_edges_from(G.edges)
|
| 1071 |
+
|
| 1072 |
+
View -- Inspired by dict-views, graph-views act like read-only
|
| 1073 |
+
versions of the original graph, providing a copy of the original
|
| 1074 |
+
structure without requiring any memory for copying the information.
|
| 1075 |
+
|
| 1076 |
+
See the Python copy module for more information on shallow
|
| 1077 |
+
and deep copies, https://docs.python.org/3/library/copy.html.
|
| 1078 |
+
|
| 1079 |
+
Parameters
|
| 1080 |
+
----------
|
| 1081 |
+
as_view : bool, optional (default=False)
|
| 1082 |
+
If True, the returned graph-view provides a read-only view
|
| 1083 |
+
of the original graph without actually copying any data.
|
| 1084 |
+
|
| 1085 |
+
Returns
|
| 1086 |
+
-------
|
| 1087 |
+
G : Graph
|
| 1088 |
+
A copy of the graph.
|
| 1089 |
+
|
| 1090 |
+
See Also
|
| 1091 |
+
--------
|
| 1092 |
+
to_directed: return a directed copy of the graph.
|
| 1093 |
+
|
| 1094 |
+
Examples
|
| 1095 |
+
--------
|
| 1096 |
+
>>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
|
| 1097 |
+
>>> H = G.copy()
|
| 1098 |
+
|
| 1099 |
+
"""
|
| 1100 |
+
if as_view is True:
|
| 1101 |
+
return nx.graphviews.generic_graph_view(self)
|
| 1102 |
+
G = self.__class__()
|
| 1103 |
+
G.graph.update(self.graph)
|
| 1104 |
+
G.add_nodes_from((n, d.copy()) for n, d in self._node.items())
|
| 1105 |
+
G.add_edges_from(
|
| 1106 |
+
(u, v, key, datadict.copy())
|
| 1107 |
+
for u, nbrs in self._adj.items()
|
| 1108 |
+
for v, keydict in nbrs.items()
|
| 1109 |
+
for key, datadict in keydict.items()
|
| 1110 |
+
)
|
| 1111 |
+
return G
|
| 1112 |
+
|
| 1113 |
+
def to_directed(self, as_view=False):
|
| 1114 |
+
"""Returns a directed representation of the graph.
|
| 1115 |
+
|
| 1116 |
+
Returns
|
| 1117 |
+
-------
|
| 1118 |
+
G : MultiDiGraph
|
| 1119 |
+
A directed graph with the same name, same nodes, and with
|
| 1120 |
+
each edge (u, v, k, data) replaced by two directed edges
|
| 1121 |
+
(u, v, k, data) and (v, u, k, data).
|
| 1122 |
+
|
| 1123 |
+
Notes
|
| 1124 |
+
-----
|
| 1125 |
+
This returns a "deepcopy" of the edge, node, and
|
| 1126 |
+
graph attributes which attempts to completely copy
|
| 1127 |
+
all of the data and references.
|
| 1128 |
+
|
| 1129 |
+
This is in contrast to the similar D=MultiDiGraph(G) which
|
| 1130 |
+
returns a shallow copy of the data.
|
| 1131 |
+
|
| 1132 |
+
See the Python copy module for more information on shallow
|
| 1133 |
+
and deep copies, https://docs.python.org/3/library/copy.html.
|
| 1134 |
+
|
| 1135 |
+
Warning: If you have subclassed MultiGraph to use dict-like objects
|
| 1136 |
+
in the data structure, those changes do not transfer to the
|
| 1137 |
+
MultiDiGraph created by this method.
|
| 1138 |
+
|
| 1139 |
+
Examples
|
| 1140 |
+
--------
|
| 1141 |
+
>>> G = nx.MultiGraph()
|
| 1142 |
+
>>> G.add_edge(0, 1)
|
| 1143 |
+
0
|
| 1144 |
+
>>> G.add_edge(0, 1)
|
| 1145 |
+
1
|
| 1146 |
+
>>> H = G.to_directed()
|
| 1147 |
+
>>> list(H.edges)
|
| 1148 |
+
[(0, 1, 0), (0, 1, 1), (1, 0, 0), (1, 0, 1)]
|
| 1149 |
+
|
| 1150 |
+
If already directed, return a (deep) copy
|
| 1151 |
+
|
| 1152 |
+
>>> G = nx.MultiDiGraph()
|
| 1153 |
+
>>> G.add_edge(0, 1)
|
| 1154 |
+
0
|
| 1155 |
+
>>> H = G.to_directed()
|
| 1156 |
+
>>> list(H.edges)
|
| 1157 |
+
[(0, 1, 0)]
|
| 1158 |
+
"""
|
| 1159 |
+
graph_class = self.to_directed_class()
|
| 1160 |
+
if as_view is True:
|
| 1161 |
+
return nx.graphviews.generic_graph_view(self, graph_class)
|
| 1162 |
+
# deepcopy when not a view
|
| 1163 |
+
G = graph_class()
|
| 1164 |
+
G.graph.update(deepcopy(self.graph))
|
| 1165 |
+
G.add_nodes_from((n, deepcopy(d)) for n, d in self._node.items())
|
| 1166 |
+
G.add_edges_from(
|
| 1167 |
+
(u, v, key, deepcopy(datadict))
|
| 1168 |
+
for u, nbrs in self.adj.items()
|
| 1169 |
+
for v, keydict in nbrs.items()
|
| 1170 |
+
for key, datadict in keydict.items()
|
| 1171 |
+
)
|
| 1172 |
+
return G
|
| 1173 |
+
|
| 1174 |
+
def to_undirected(self, as_view=False):
|
| 1175 |
+
"""Returns an undirected copy of the graph.
|
| 1176 |
+
|
| 1177 |
+
Returns
|
| 1178 |
+
-------
|
| 1179 |
+
G : Graph/MultiGraph
|
| 1180 |
+
A deepcopy of the graph.
|
| 1181 |
+
|
| 1182 |
+
See Also
|
| 1183 |
+
--------
|
| 1184 |
+
copy, add_edge, add_edges_from
|
| 1185 |
+
|
| 1186 |
+
Notes
|
| 1187 |
+
-----
|
| 1188 |
+
This returns a "deepcopy" of the edge, node, and
|
| 1189 |
+
graph attributes which attempts to completely copy
|
| 1190 |
+
all of the data and references.
|
| 1191 |
+
|
| 1192 |
+
This is in contrast to the similar `G = nx.MultiGraph(D)`
|
| 1193 |
+
which returns a shallow copy of the data.
|
| 1194 |
+
|
| 1195 |
+
See the Python copy module for more information on shallow
|
| 1196 |
+
and deep copies, https://docs.python.org/3/library/copy.html.
|
| 1197 |
+
|
| 1198 |
+
Warning: If you have subclassed MultiGraph to use dict-like
|
| 1199 |
+
objects in the data structure, those changes do not transfer
|
| 1200 |
+
to the MultiGraph created by this method.
|
| 1201 |
+
|
| 1202 |
+
Examples
|
| 1203 |
+
--------
|
| 1204 |
+
>>> G = nx.MultiGraph([(0, 1), (0, 1), (1, 2)])
|
| 1205 |
+
>>> H = G.to_directed()
|
| 1206 |
+
>>> list(H.edges)
|
| 1207 |
+
[(0, 1, 0), (0, 1, 1), (1, 0, 0), (1, 0, 1), (1, 2, 0), (2, 1, 0)]
|
| 1208 |
+
>>> G2 = H.to_undirected()
|
| 1209 |
+
>>> list(G2.edges)
|
| 1210 |
+
[(0, 1, 0), (0, 1, 1), (1, 2, 0)]
|
| 1211 |
+
"""
|
| 1212 |
+
graph_class = self.to_undirected_class()
|
| 1213 |
+
if as_view is True:
|
| 1214 |
+
return nx.graphviews.generic_graph_view(self, graph_class)
|
| 1215 |
+
# deepcopy when not a view
|
| 1216 |
+
G = graph_class()
|
| 1217 |
+
G.graph.update(deepcopy(self.graph))
|
| 1218 |
+
G.add_nodes_from((n, deepcopy(d)) for n, d in self._node.items())
|
| 1219 |
+
G.add_edges_from(
|
| 1220 |
+
(u, v, key, deepcopy(datadict))
|
| 1221 |
+
for u, nbrs in self._adj.items()
|
| 1222 |
+
for v, keydict in nbrs.items()
|
| 1223 |
+
for key, datadict in keydict.items()
|
| 1224 |
+
)
|
| 1225 |
+
return G
|
| 1226 |
+
|
| 1227 |
+
def number_of_edges(self, u=None, v=None):
|
| 1228 |
+
"""Returns the number of edges between two nodes.
|
| 1229 |
+
|
| 1230 |
+
Parameters
|
| 1231 |
+
----------
|
| 1232 |
+
u, v : nodes, optional (Default=all edges)
|
| 1233 |
+
If u and v are specified, return the number of edges between
|
| 1234 |
+
u and v. Otherwise return the total number of all edges.
|
| 1235 |
+
|
| 1236 |
+
Returns
|
| 1237 |
+
-------
|
| 1238 |
+
nedges : int
|
| 1239 |
+
The number of edges in the graph. If nodes `u` and `v` are
|
| 1240 |
+
specified return the number of edges between those nodes. If
|
| 1241 |
+
the graph is directed, this only returns the number of edges
|
| 1242 |
+
from `u` to `v`.
|
| 1243 |
+
|
| 1244 |
+
See Also
|
| 1245 |
+
--------
|
| 1246 |
+
size
|
| 1247 |
+
|
| 1248 |
+
Examples
|
| 1249 |
+
--------
|
| 1250 |
+
For undirected multigraphs, this method counts the total number
|
| 1251 |
+
of edges in the graph::
|
| 1252 |
+
|
| 1253 |
+
>>> G = nx.MultiGraph()
|
| 1254 |
+
>>> G.add_edges_from([(0, 1), (0, 1), (1, 2)])
|
| 1255 |
+
[0, 1, 0]
|
| 1256 |
+
>>> G.number_of_edges()
|
| 1257 |
+
3
|
| 1258 |
+
|
| 1259 |
+
If you specify two nodes, this counts the total number of edges
|
| 1260 |
+
joining the two nodes::
|
| 1261 |
+
|
| 1262 |
+
>>> G.number_of_edges(0, 1)
|
| 1263 |
+
2
|
| 1264 |
+
|
| 1265 |
+
For directed multigraphs, this method can count the total number
|
| 1266 |
+
of directed edges from `u` to `v`::
|
| 1267 |
+
|
| 1268 |
+
>>> G = nx.MultiDiGraph()
|
| 1269 |
+
>>> G.add_edges_from([(0, 1), (0, 1), (1, 0)])
|
| 1270 |
+
[0, 1, 0]
|
| 1271 |
+
>>> G.number_of_edges(0, 1)
|
| 1272 |
+
2
|
| 1273 |
+
>>> G.number_of_edges(1, 0)
|
| 1274 |
+
1
|
| 1275 |
+
|
| 1276 |
+
"""
|
| 1277 |
+
if u is None:
|
| 1278 |
+
return self.size()
|
| 1279 |
+
try:
|
| 1280 |
+
edgedata = self._adj[u][v]
|
| 1281 |
+
except KeyError:
|
| 1282 |
+
return 0 # no such edge
|
| 1283 |
+
return len(edgedata)
|
.venv/lib/python3.11/site-packages/networkx/classes/reportviews.py
ADDED
|
@@ -0,0 +1,1447 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
|
| 86 |
+
from abc import ABC
|
| 87 |
+
from collections.abc import Mapping, Set
|
| 88 |
+
|
| 89 |
+
import networkx as nx
|
| 90 |
+
|
| 91 |
+
__all__ = [
|
| 92 |
+
"NodeView",
|
| 93 |
+
"NodeDataView",
|
| 94 |
+
"EdgeView",
|
| 95 |
+
"OutEdgeView",
|
| 96 |
+
"InEdgeView",
|
| 97 |
+
"EdgeDataView",
|
| 98 |
+
"OutEdgeDataView",
|
| 99 |
+
"InEdgeDataView",
|
| 100 |
+
"MultiEdgeView",
|
| 101 |
+
"OutMultiEdgeView",
|
| 102 |
+
"InMultiEdgeView",
|
| 103 |
+
"MultiEdgeDataView",
|
| 104 |
+
"OutMultiEdgeDataView",
|
| 105 |
+
"InMultiEdgeDataView",
|
| 106 |
+
"DegreeView",
|
| 107 |
+
"DiDegreeView",
|
| 108 |
+
"InDegreeView",
|
| 109 |
+
"OutDegreeView",
|
| 110 |
+
"MultiDegreeView",
|
| 111 |
+
"DiMultiDegreeView",
|
| 112 |
+
"InMultiDegreeView",
|
| 113 |
+
"OutMultiDegreeView",
|
| 114 |
+
]
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
# NodeViews
|
| 118 |
+
class NodeView(Mapping, Set):
|
| 119 |
+
"""A NodeView class to act as G.nodes for a NetworkX Graph
|
| 120 |
+
|
| 121 |
+
Set operations act on the nodes without considering data.
|
| 122 |
+
Iteration is over nodes. Node data can be looked up like a dict.
|
| 123 |
+
Use NodeDataView to iterate over node data or to specify a data
|
| 124 |
+
attribute for lookup. NodeDataView is created by calling the NodeView.
|
| 125 |
+
|
| 126 |
+
Parameters
|
| 127 |
+
----------
|
| 128 |
+
graph : NetworkX graph-like class
|
| 129 |
+
|
| 130 |
+
Examples
|
| 131 |
+
--------
|
| 132 |
+
>>> G = nx.path_graph(3)
|
| 133 |
+
>>> NV = G.nodes()
|
| 134 |
+
>>> 2 in NV
|
| 135 |
+
True
|
| 136 |
+
>>> for n in NV:
|
| 137 |
+
... print(n)
|
| 138 |
+
0
|
| 139 |
+
1
|
| 140 |
+
2
|
| 141 |
+
>>> assert NV & {1, 2, 3} == {1, 2}
|
| 142 |
+
|
| 143 |
+
>>> G.add_node(2, color="blue")
|
| 144 |
+
>>> NV[2]
|
| 145 |
+
{'color': 'blue'}
|
| 146 |
+
>>> G.add_node(8, color="red")
|
| 147 |
+
>>> NDV = G.nodes(data=True)
|
| 148 |
+
>>> (2, NV[2]) in NDV
|
| 149 |
+
True
|
| 150 |
+
>>> for n, dd in NDV:
|
| 151 |
+
... print((n, dd.get("color", "aqua")))
|
| 152 |
+
(0, 'aqua')
|
| 153 |
+
(1, 'aqua')
|
| 154 |
+
(2, 'blue')
|
| 155 |
+
(8, 'red')
|
| 156 |
+
>>> NDV[2] == NV[2]
|
| 157 |
+
True
|
| 158 |
+
|
| 159 |
+
>>> NVdata = G.nodes(data="color", default="aqua")
|
| 160 |
+
>>> (2, NVdata[2]) in NVdata
|
| 161 |
+
True
|
| 162 |
+
>>> for n, dd in NVdata:
|
| 163 |
+
... print((n, dd))
|
| 164 |
+
(0, 'aqua')
|
| 165 |
+
(1, 'aqua')
|
| 166 |
+
(2, 'blue')
|
| 167 |
+
(8, 'red')
|
| 168 |
+
>>> NVdata[2] == NV[2] # NVdata gets 'color', NV gets datadict
|
| 169 |
+
False
|
| 170 |
+
"""
|
| 171 |
+
|
| 172 |
+
__slots__ = ("_nodes",)
|
| 173 |
+
|
| 174 |
+
def __getstate__(self):
|
| 175 |
+
return {"_nodes": self._nodes}
|
| 176 |
+
|
| 177 |
+
def __setstate__(self, state):
|
| 178 |
+
self._nodes = state["_nodes"]
|
| 179 |
+
|
| 180 |
+
def __init__(self, graph):
|
| 181 |
+
self._nodes = graph._node
|
| 182 |
+
|
| 183 |
+
# Mapping methods
|
| 184 |
+
def __len__(self):
|
| 185 |
+
return len(self._nodes)
|
| 186 |
+
|
| 187 |
+
def __iter__(self):
|
| 188 |
+
return iter(self._nodes)
|
| 189 |
+
|
| 190 |
+
def __getitem__(self, n):
|
| 191 |
+
if isinstance(n, slice):
|
| 192 |
+
raise nx.NetworkXError(
|
| 193 |
+
f"{type(self).__name__} does not support slicing, "
|
| 194 |
+
f"try list(G.nodes)[{n.start}:{n.stop}:{n.step}]"
|
| 195 |
+
)
|
| 196 |
+
return self._nodes[n]
|
| 197 |
+
|
| 198 |
+
# Set methods
|
| 199 |
+
def __contains__(self, n):
|
| 200 |
+
return n in self._nodes
|
| 201 |
+
|
| 202 |
+
@classmethod
|
| 203 |
+
def _from_iterable(cls, it):
|
| 204 |
+
return set(it)
|
| 205 |
+
|
| 206 |
+
# DataView method
|
| 207 |
+
def __call__(self, data=False, default=None):
|
| 208 |
+
if data is False:
|
| 209 |
+
return self
|
| 210 |
+
return NodeDataView(self._nodes, data, default)
|
| 211 |
+
|
| 212 |
+
def data(self, data=True, default=None):
|
| 213 |
+
"""
|
| 214 |
+
Return a read-only view of node data.
|
| 215 |
+
|
| 216 |
+
Parameters
|
| 217 |
+
----------
|
| 218 |
+
data : bool or node data key, default=True
|
| 219 |
+
If ``data=True`` (the default), return a `NodeDataView` object that
|
| 220 |
+
maps each node to *all* of its attributes. `data` may also be an
|
| 221 |
+
arbitrary key, in which case the `NodeDataView` maps each node to
|
| 222 |
+
the value for the keyed attribute. In this case, if a node does
|
| 223 |
+
not have the `data` attribute, the `default` value is used.
|
| 224 |
+
default : object, default=None
|
| 225 |
+
The value used when a node does not have a specific attribute.
|
| 226 |
+
|
| 227 |
+
Returns
|
| 228 |
+
-------
|
| 229 |
+
NodeDataView
|
| 230 |
+
The layout of the returned NodeDataView depends on the value of the
|
| 231 |
+
`data` parameter.
|
| 232 |
+
|
| 233 |
+
Notes
|
| 234 |
+
-----
|
| 235 |
+
If ``data=False``, returns a `NodeView` object without data.
|
| 236 |
+
|
| 237 |
+
See Also
|
| 238 |
+
--------
|
| 239 |
+
NodeDataView
|
| 240 |
+
|
| 241 |
+
Examples
|
| 242 |
+
--------
|
| 243 |
+
>>> G = nx.Graph()
|
| 244 |
+
>>> G.add_nodes_from(
|
| 245 |
+
... [
|
| 246 |
+
... (0, {"color": "red", "weight": 10}),
|
| 247 |
+
... (1, {"color": "blue"}),
|
| 248 |
+
... (2, {"color": "yellow", "weight": 2}),
|
| 249 |
+
... ]
|
| 250 |
+
... )
|
| 251 |
+
|
| 252 |
+
Accessing node data with ``data=True`` (the default) returns a
|
| 253 |
+
NodeDataView mapping each node to all of its attributes:
|
| 254 |
+
|
| 255 |
+
>>> G.nodes.data()
|
| 256 |
+
NodeDataView({0: {'color': 'red', 'weight': 10}, 1: {'color': 'blue'}, 2: {'color': 'yellow', 'weight': 2}})
|
| 257 |
+
|
| 258 |
+
If `data` represents a key in the node attribute dict, a NodeDataView mapping
|
| 259 |
+
the nodes to the value for that specific key is returned:
|
| 260 |
+
|
| 261 |
+
>>> G.nodes.data("color")
|
| 262 |
+
NodeDataView({0: 'red', 1: 'blue', 2: 'yellow'}, data='color')
|
| 263 |
+
|
| 264 |
+
If a specific key is not found in an attribute dict, the value specified
|
| 265 |
+
by `default` is returned:
|
| 266 |
+
|
| 267 |
+
>>> G.nodes.data("weight", default=-999)
|
| 268 |
+
NodeDataView({0: 10, 1: -999, 2: 2}, data='weight')
|
| 269 |
+
|
| 270 |
+
Note that there is no check that the `data` key is in any of the
|
| 271 |
+
node attribute dictionaries:
|
| 272 |
+
|
| 273 |
+
>>> G.nodes.data("height")
|
| 274 |
+
NodeDataView({0: None, 1: None, 2: None}, data='height')
|
| 275 |
+
"""
|
| 276 |
+
if data is False:
|
| 277 |
+
return self
|
| 278 |
+
return NodeDataView(self._nodes, data, default)
|
| 279 |
+
|
| 280 |
+
def __str__(self):
|
| 281 |
+
return str(list(self))
|
| 282 |
+
|
| 283 |
+
def __repr__(self):
|
| 284 |
+
return f"{self.__class__.__name__}({tuple(self)})"
|
| 285 |
+
|
| 286 |
+
|
| 287 |
+
class NodeDataView(Set):
|
| 288 |
+
"""A DataView class for nodes of a NetworkX Graph
|
| 289 |
+
|
| 290 |
+
The main use for this class is to iterate through node-data pairs.
|
| 291 |
+
The data can be the entire data-dictionary for each node, or it
|
| 292 |
+
can be a specific attribute (with default) for each node.
|
| 293 |
+
Set operations are enabled with NodeDataView, but don't work in
|
| 294 |
+
cases where the data is not hashable. Use with caution.
|
| 295 |
+
Typically, set operations on nodes use NodeView, not NodeDataView.
|
| 296 |
+
That is, they use `G.nodes` instead of `G.nodes(data='foo')`.
|
| 297 |
+
|
| 298 |
+
Parameters
|
| 299 |
+
==========
|
| 300 |
+
graph : NetworkX graph-like class
|
| 301 |
+
data : bool or string (default=False)
|
| 302 |
+
default : object (default=None)
|
| 303 |
+
"""
|
| 304 |
+
|
| 305 |
+
__slots__ = ("_nodes", "_data", "_default")
|
| 306 |
+
|
| 307 |
+
def __getstate__(self):
|
| 308 |
+
return {"_nodes": self._nodes, "_data": self._data, "_default": self._default}
|
| 309 |
+
|
| 310 |
+
def __setstate__(self, state):
|
| 311 |
+
self._nodes = state["_nodes"]
|
| 312 |
+
self._data = state["_data"]
|
| 313 |
+
self._default = state["_default"]
|
| 314 |
+
|
| 315 |
+
def __init__(self, nodedict, data=False, default=None):
|
| 316 |
+
self._nodes = nodedict
|
| 317 |
+
self._data = data
|
| 318 |
+
self._default = default
|
| 319 |
+
|
| 320 |
+
@classmethod
|
| 321 |
+
def _from_iterable(cls, it):
|
| 322 |
+
try:
|
| 323 |
+
return set(it)
|
| 324 |
+
except TypeError as err:
|
| 325 |
+
if "unhashable" in str(err):
|
| 326 |
+
msg = " : Could be b/c data=True or your values are unhashable"
|
| 327 |
+
raise TypeError(str(err) + msg) from err
|
| 328 |
+
raise
|
| 329 |
+
|
| 330 |
+
def __len__(self):
|
| 331 |
+
return len(self._nodes)
|
| 332 |
+
|
| 333 |
+
def __iter__(self):
|
| 334 |
+
data = self._data
|
| 335 |
+
if data is False:
|
| 336 |
+
return iter(self._nodes)
|
| 337 |
+
if data is True:
|
| 338 |
+
return iter(self._nodes.items())
|
| 339 |
+
return (
|
| 340 |
+
(n, dd[data] if data in dd else self._default)
|
| 341 |
+
for n, dd in self._nodes.items()
|
| 342 |
+
)
|
| 343 |
+
|
| 344 |
+
def __contains__(self, n):
|
| 345 |
+
try:
|
| 346 |
+
node_in = n in self._nodes
|
| 347 |
+
except TypeError:
|
| 348 |
+
n, d = n
|
| 349 |
+
return n in self._nodes and self[n] == d
|
| 350 |
+
if node_in is True:
|
| 351 |
+
return node_in
|
| 352 |
+
try:
|
| 353 |
+
n, d = n
|
| 354 |
+
except (TypeError, ValueError):
|
| 355 |
+
return False
|
| 356 |
+
return n in self._nodes and self[n] == d
|
| 357 |
+
|
| 358 |
+
def __getitem__(self, n):
|
| 359 |
+
if isinstance(n, slice):
|
| 360 |
+
raise nx.NetworkXError(
|
| 361 |
+
f"{type(self).__name__} does not support slicing, "
|
| 362 |
+
f"try list(G.nodes.data())[{n.start}:{n.stop}:{n.step}]"
|
| 363 |
+
)
|
| 364 |
+
ddict = self._nodes[n]
|
| 365 |
+
data = self._data
|
| 366 |
+
if data is False or data is True:
|
| 367 |
+
return ddict
|
| 368 |
+
return ddict[data] if data in ddict else self._default
|
| 369 |
+
|
| 370 |
+
def __str__(self):
|
| 371 |
+
return str(list(self))
|
| 372 |
+
|
| 373 |
+
def __repr__(self):
|
| 374 |
+
name = self.__class__.__name__
|
| 375 |
+
if self._data is False:
|
| 376 |
+
return f"{name}({tuple(self)})"
|
| 377 |
+
if self._data is True:
|
| 378 |
+
return f"{name}({dict(self)})"
|
| 379 |
+
return f"{name}({dict(self)}, data={self._data!r})"
|
| 380 |
+
|
| 381 |
+
|
| 382 |
+
# DegreeViews
|
| 383 |
+
class DiDegreeView:
|
| 384 |
+
"""A View class for degree of nodes in a NetworkX Graph
|
| 385 |
+
|
| 386 |
+
The functionality is like dict.items() with (node, degree) pairs.
|
| 387 |
+
Additional functionality includes read-only lookup of node degree,
|
| 388 |
+
and calling with optional features nbunch (for only a subset of nodes)
|
| 389 |
+
and weight (use edge weights to compute degree).
|
| 390 |
+
|
| 391 |
+
Parameters
|
| 392 |
+
==========
|
| 393 |
+
graph : NetworkX graph-like class
|
| 394 |
+
nbunch : node, container of nodes, or None meaning all nodes (default=None)
|
| 395 |
+
weight : bool or string (default=None)
|
| 396 |
+
|
| 397 |
+
Notes
|
| 398 |
+
-----
|
| 399 |
+
DegreeView can still lookup any node even if nbunch is specified.
|
| 400 |
+
|
| 401 |
+
Examples
|
| 402 |
+
--------
|
| 403 |
+
>>> G = nx.path_graph(3)
|
| 404 |
+
>>> DV = G.degree()
|
| 405 |
+
>>> assert DV[2] == 1
|
| 406 |
+
>>> assert sum(deg for n, deg in DV) == 4
|
| 407 |
+
|
| 408 |
+
>>> DVweight = G.degree(weight="span")
|
| 409 |
+
>>> G.add_edge(1, 2, span=34)
|
| 410 |
+
>>> DVweight[2]
|
| 411 |
+
34
|
| 412 |
+
>>> DVweight[0] # default edge weight is 1
|
| 413 |
+
1
|
| 414 |
+
>>> sum(span for n, span in DVweight) # sum weighted degrees
|
| 415 |
+
70
|
| 416 |
+
|
| 417 |
+
>>> DVnbunch = G.degree(nbunch=(1, 2))
|
| 418 |
+
>>> assert len(list(DVnbunch)) == 2 # iteration over nbunch only
|
| 419 |
+
"""
|
| 420 |
+
|
| 421 |
+
def __init__(self, G, nbunch=None, weight=None):
|
| 422 |
+
self._graph = G
|
| 423 |
+
self._succ = G._succ if hasattr(G, "_succ") else G._adj
|
| 424 |
+
self._pred = G._pred if hasattr(G, "_pred") else G._adj
|
| 425 |
+
self._nodes = self._succ if nbunch is None else list(G.nbunch_iter(nbunch))
|
| 426 |
+
self._weight = weight
|
| 427 |
+
|
| 428 |
+
def __call__(self, nbunch=None, weight=None):
|
| 429 |
+
if nbunch is None:
|
| 430 |
+
if weight == self._weight:
|
| 431 |
+
return self
|
| 432 |
+
return self.__class__(self._graph, None, weight)
|
| 433 |
+
try:
|
| 434 |
+
if nbunch in self._nodes:
|
| 435 |
+
if weight == self._weight:
|
| 436 |
+
return self[nbunch]
|
| 437 |
+
return self.__class__(self._graph, None, weight)[nbunch]
|
| 438 |
+
except TypeError:
|
| 439 |
+
pass
|
| 440 |
+
return self.__class__(self._graph, nbunch, weight)
|
| 441 |
+
|
| 442 |
+
def __getitem__(self, n):
|
| 443 |
+
weight = self._weight
|
| 444 |
+
succs = self._succ[n]
|
| 445 |
+
preds = self._pred[n]
|
| 446 |
+
if weight is None:
|
| 447 |
+
return len(succs) + len(preds)
|
| 448 |
+
return sum(dd.get(weight, 1) for dd in succs.values()) + sum(
|
| 449 |
+
dd.get(weight, 1) for dd in preds.values()
|
| 450 |
+
)
|
| 451 |
+
|
| 452 |
+
def __iter__(self):
|
| 453 |
+
weight = self._weight
|
| 454 |
+
if weight is None:
|
| 455 |
+
for n in self._nodes:
|
| 456 |
+
succs = self._succ[n]
|
| 457 |
+
preds = self._pred[n]
|
| 458 |
+
yield (n, len(succs) + len(preds))
|
| 459 |
+
else:
|
| 460 |
+
for n in self._nodes:
|
| 461 |
+
succs = self._succ[n]
|
| 462 |
+
preds = self._pred[n]
|
| 463 |
+
deg = sum(dd.get(weight, 1) for dd in succs.values()) + sum(
|
| 464 |
+
dd.get(weight, 1) for dd in preds.values()
|
| 465 |
+
)
|
| 466 |
+
yield (n, deg)
|
| 467 |
+
|
| 468 |
+
def __len__(self):
|
| 469 |
+
return len(self._nodes)
|
| 470 |
+
|
| 471 |
+
def __str__(self):
|
| 472 |
+
return str(list(self))
|
| 473 |
+
|
| 474 |
+
def __repr__(self):
|
| 475 |
+
return f"{self.__class__.__name__}({dict(self)})"
|
| 476 |
+
|
| 477 |
+
|
| 478 |
+
class DegreeView(DiDegreeView):
|
| 479 |
+
"""A DegreeView class to act as G.degree for a NetworkX Graph
|
| 480 |
+
|
| 481 |
+
Typical usage focuses on iteration over `(node, degree)` pairs.
|
| 482 |
+
The degree is by default the number of edges incident to the node.
|
| 483 |
+
Optional argument `weight` enables weighted degree using the edge
|
| 484 |
+
attribute named in the `weight` argument. Reporting and iteration
|
| 485 |
+
can also be restricted to a subset of nodes using `nbunch`.
|
| 486 |
+
|
| 487 |
+
Additional functionality include node lookup so that `G.degree[n]`
|
| 488 |
+
reported the (possibly weighted) degree of node `n`. Calling the
|
| 489 |
+
view creates a view with different arguments `nbunch` or `weight`.
|
| 490 |
+
|
| 491 |
+
Parameters
|
| 492 |
+
==========
|
| 493 |
+
graph : NetworkX graph-like class
|
| 494 |
+
nbunch : node, container of nodes, or None meaning all nodes (default=None)
|
| 495 |
+
weight : string or None (default=None)
|
| 496 |
+
|
| 497 |
+
Notes
|
| 498 |
+
-----
|
| 499 |
+
DegreeView can still lookup any node even if nbunch is specified.
|
| 500 |
+
|
| 501 |
+
Examples
|
| 502 |
+
--------
|
| 503 |
+
>>> G = nx.path_graph(3)
|
| 504 |
+
>>> DV = G.degree()
|
| 505 |
+
>>> assert DV[2] == 1
|
| 506 |
+
>>> assert G.degree[2] == 1
|
| 507 |
+
>>> assert sum(deg for n, deg in DV) == 4
|
| 508 |
+
|
| 509 |
+
>>> DVweight = G.degree(weight="span")
|
| 510 |
+
>>> G.add_edge(1, 2, span=34)
|
| 511 |
+
>>> DVweight[2]
|
| 512 |
+
34
|
| 513 |
+
>>> DVweight[0] # default edge weight is 1
|
| 514 |
+
1
|
| 515 |
+
>>> sum(span for n, span in DVweight) # sum weighted degrees
|
| 516 |
+
70
|
| 517 |
+
|
| 518 |
+
>>> DVnbunch = G.degree(nbunch=(1, 2))
|
| 519 |
+
>>> assert len(list(DVnbunch)) == 2 # iteration over nbunch only
|
| 520 |
+
"""
|
| 521 |
+
|
| 522 |
+
def __getitem__(self, n):
|
| 523 |
+
weight = self._weight
|
| 524 |
+
nbrs = self._succ[n]
|
| 525 |
+
if weight is None:
|
| 526 |
+
return len(nbrs) + (n in nbrs)
|
| 527 |
+
return sum(dd.get(weight, 1) for dd in nbrs.values()) + (
|
| 528 |
+
n in nbrs and nbrs[n].get(weight, 1)
|
| 529 |
+
)
|
| 530 |
+
|
| 531 |
+
def __iter__(self):
|
| 532 |
+
weight = self._weight
|
| 533 |
+
if weight is None:
|
| 534 |
+
for n in self._nodes:
|
| 535 |
+
nbrs = self._succ[n]
|
| 536 |
+
yield (n, len(nbrs) + (n in nbrs))
|
| 537 |
+
else:
|
| 538 |
+
for n in self._nodes:
|
| 539 |
+
nbrs = self._succ[n]
|
| 540 |
+
deg = sum(dd.get(weight, 1) for dd in nbrs.values()) + (
|
| 541 |
+
n in nbrs and nbrs[n].get(weight, 1)
|
| 542 |
+
)
|
| 543 |
+
yield (n, deg)
|
| 544 |
+
|
| 545 |
+
|
| 546 |
+
class OutDegreeView(DiDegreeView):
|
| 547 |
+
"""A DegreeView class to report out_degree for a DiGraph; See DegreeView"""
|
| 548 |
+
|
| 549 |
+
def __getitem__(self, n):
|
| 550 |
+
weight = self._weight
|
| 551 |
+
nbrs = self._succ[n]
|
| 552 |
+
if self._weight is None:
|
| 553 |
+
return len(nbrs)
|
| 554 |
+
return sum(dd.get(self._weight, 1) for dd in nbrs.values())
|
| 555 |
+
|
| 556 |
+
def __iter__(self):
|
| 557 |
+
weight = self._weight
|
| 558 |
+
if weight is None:
|
| 559 |
+
for n in self._nodes:
|
| 560 |
+
succs = self._succ[n]
|
| 561 |
+
yield (n, len(succs))
|
| 562 |
+
else:
|
| 563 |
+
for n in self._nodes:
|
| 564 |
+
succs = self._succ[n]
|
| 565 |
+
deg = sum(dd.get(weight, 1) for dd in succs.values())
|
| 566 |
+
yield (n, deg)
|
| 567 |
+
|
| 568 |
+
|
| 569 |
+
class InDegreeView(DiDegreeView):
|
| 570 |
+
"""A DegreeView class to report in_degree for a DiGraph; See DegreeView"""
|
| 571 |
+
|
| 572 |
+
def __getitem__(self, n):
|
| 573 |
+
weight = self._weight
|
| 574 |
+
nbrs = self._pred[n]
|
| 575 |
+
if weight is None:
|
| 576 |
+
return len(nbrs)
|
| 577 |
+
return sum(dd.get(weight, 1) for dd in nbrs.values())
|
| 578 |
+
|
| 579 |
+
def __iter__(self):
|
| 580 |
+
weight = self._weight
|
| 581 |
+
if weight is None:
|
| 582 |
+
for n in self._nodes:
|
| 583 |
+
preds = self._pred[n]
|
| 584 |
+
yield (n, len(preds))
|
| 585 |
+
else:
|
| 586 |
+
for n in self._nodes:
|
| 587 |
+
preds = self._pred[n]
|
| 588 |
+
deg = sum(dd.get(weight, 1) for dd in preds.values())
|
| 589 |
+
yield (n, deg)
|
| 590 |
+
|
| 591 |
+
|
| 592 |
+
class MultiDegreeView(DiDegreeView):
|
| 593 |
+
"""A DegreeView class for undirected multigraphs; See DegreeView"""
|
| 594 |
+
|
| 595 |
+
def __getitem__(self, n):
|
| 596 |
+
weight = self._weight
|
| 597 |
+
nbrs = self._succ[n]
|
| 598 |
+
if weight is None:
|
| 599 |
+
return sum(len(keys) for keys in nbrs.values()) + (
|
| 600 |
+
n in nbrs and len(nbrs[n])
|
| 601 |
+
)
|
| 602 |
+
# edge weighted graph - degree is sum of nbr edge weights
|
| 603 |
+
deg = sum(
|
| 604 |
+
d.get(weight, 1) for key_dict in nbrs.values() for d in key_dict.values()
|
| 605 |
+
)
|
| 606 |
+
if n in nbrs:
|
| 607 |
+
deg += sum(d.get(weight, 1) for d in nbrs[n].values())
|
| 608 |
+
return deg
|
| 609 |
+
|
| 610 |
+
def __iter__(self):
|
| 611 |
+
weight = self._weight
|
| 612 |
+
if weight is None:
|
| 613 |
+
for n in self._nodes:
|
| 614 |
+
nbrs = self._succ[n]
|
| 615 |
+
deg = sum(len(keys) for keys in nbrs.values()) + (
|
| 616 |
+
n in nbrs and len(nbrs[n])
|
| 617 |
+
)
|
| 618 |
+
yield (n, deg)
|
| 619 |
+
else:
|
| 620 |
+
for n in self._nodes:
|
| 621 |
+
nbrs = self._succ[n]
|
| 622 |
+
deg = sum(
|
| 623 |
+
d.get(weight, 1)
|
| 624 |
+
for key_dict in nbrs.values()
|
| 625 |
+
for d in key_dict.values()
|
| 626 |
+
)
|
| 627 |
+
if n in nbrs:
|
| 628 |
+
deg += sum(d.get(weight, 1) for d in nbrs[n].values())
|
| 629 |
+
yield (n, deg)
|
| 630 |
+
|
| 631 |
+
|
| 632 |
+
class DiMultiDegreeView(DiDegreeView):
|
| 633 |
+
"""A DegreeView class for MultiDiGraph; See DegreeView"""
|
| 634 |
+
|
| 635 |
+
def __getitem__(self, n):
|
| 636 |
+
weight = self._weight
|
| 637 |
+
succs = self._succ[n]
|
| 638 |
+
preds = self._pred[n]
|
| 639 |
+
if weight is None:
|
| 640 |
+
return sum(len(keys) for keys in succs.values()) + sum(
|
| 641 |
+
len(keys) for keys in preds.values()
|
| 642 |
+
)
|
| 643 |
+
# edge weighted graph - degree is sum of nbr edge weights
|
| 644 |
+
deg = sum(
|
| 645 |
+
d.get(weight, 1) for key_dict in succs.values() for d in key_dict.values()
|
| 646 |
+
) + sum(
|
| 647 |
+
d.get(weight, 1) for key_dict in preds.values() for d in key_dict.values()
|
| 648 |
+
)
|
| 649 |
+
return deg
|
| 650 |
+
|
| 651 |
+
def __iter__(self):
|
| 652 |
+
weight = self._weight
|
| 653 |
+
if weight is None:
|
| 654 |
+
for n in self._nodes:
|
| 655 |
+
succs = self._succ[n]
|
| 656 |
+
preds = self._pred[n]
|
| 657 |
+
deg = sum(len(keys) for keys in succs.values()) + sum(
|
| 658 |
+
len(keys) for keys in preds.values()
|
| 659 |
+
)
|
| 660 |
+
yield (n, deg)
|
| 661 |
+
else:
|
| 662 |
+
for n in self._nodes:
|
| 663 |
+
succs = self._succ[n]
|
| 664 |
+
preds = self._pred[n]
|
| 665 |
+
deg = sum(
|
| 666 |
+
d.get(weight, 1)
|
| 667 |
+
for key_dict in succs.values()
|
| 668 |
+
for d in key_dict.values()
|
| 669 |
+
) + sum(
|
| 670 |
+
d.get(weight, 1)
|
| 671 |
+
for key_dict in preds.values()
|
| 672 |
+
for d in key_dict.values()
|
| 673 |
+
)
|
| 674 |
+
yield (n, deg)
|
| 675 |
+
|
| 676 |
+
|
| 677 |
+
class InMultiDegreeView(DiDegreeView):
|
| 678 |
+
"""A DegreeView class for inward degree of MultiDiGraph; See DegreeView"""
|
| 679 |
+
|
| 680 |
+
def __getitem__(self, n):
|
| 681 |
+
weight = self._weight
|
| 682 |
+
nbrs = self._pred[n]
|
| 683 |
+
if weight is None:
|
| 684 |
+
return sum(len(data) for data in nbrs.values())
|
| 685 |
+
# edge weighted graph - degree is sum of nbr edge weights
|
| 686 |
+
return sum(
|
| 687 |
+
d.get(weight, 1) for key_dict in nbrs.values() for d in key_dict.values()
|
| 688 |
+
)
|
| 689 |
+
|
| 690 |
+
def __iter__(self):
|
| 691 |
+
weight = self._weight
|
| 692 |
+
if weight is None:
|
| 693 |
+
for n in self._nodes:
|
| 694 |
+
nbrs = self._pred[n]
|
| 695 |
+
deg = sum(len(data) for data in nbrs.values())
|
| 696 |
+
yield (n, deg)
|
| 697 |
+
else:
|
| 698 |
+
for n in self._nodes:
|
| 699 |
+
nbrs = self._pred[n]
|
| 700 |
+
deg = sum(
|
| 701 |
+
d.get(weight, 1)
|
| 702 |
+
for key_dict in nbrs.values()
|
| 703 |
+
for d in key_dict.values()
|
| 704 |
+
)
|
| 705 |
+
yield (n, deg)
|
| 706 |
+
|
| 707 |
+
|
| 708 |
+
class OutMultiDegreeView(DiDegreeView):
|
| 709 |
+
"""A DegreeView class for outward degree of MultiDiGraph; See DegreeView"""
|
| 710 |
+
|
| 711 |
+
def __getitem__(self, n):
|
| 712 |
+
weight = self._weight
|
| 713 |
+
nbrs = self._succ[n]
|
| 714 |
+
if weight is None:
|
| 715 |
+
return sum(len(data) for data in nbrs.values())
|
| 716 |
+
# edge weighted graph - degree is sum of nbr edge weights
|
| 717 |
+
return sum(
|
| 718 |
+
d.get(weight, 1) for key_dict in nbrs.values() for d in key_dict.values()
|
| 719 |
+
)
|
| 720 |
+
|
| 721 |
+
def __iter__(self):
|
| 722 |
+
weight = self._weight
|
| 723 |
+
if weight is None:
|
| 724 |
+
for n in self._nodes:
|
| 725 |
+
nbrs = self._succ[n]
|
| 726 |
+
deg = sum(len(data) for data in nbrs.values())
|
| 727 |
+
yield (n, deg)
|
| 728 |
+
else:
|
| 729 |
+
for n in self._nodes:
|
| 730 |
+
nbrs = self._succ[n]
|
| 731 |
+
deg = sum(
|
| 732 |
+
d.get(weight, 1)
|
| 733 |
+
for key_dict in nbrs.values()
|
| 734 |
+
for d in key_dict.values()
|
| 735 |
+
)
|
| 736 |
+
yield (n, deg)
|
| 737 |
+
|
| 738 |
+
|
| 739 |
+
# A base class for all edge views. Ensures all edge view and edge data view
|
| 740 |
+
# objects/classes are captured by `isinstance(obj, EdgeViewABC)` and
|
| 741 |
+
# `issubclass(cls, EdgeViewABC)` respectively
|
| 742 |
+
class EdgeViewABC(ABC):
|
| 743 |
+
pass
|
| 744 |
+
|
| 745 |
+
|
| 746 |
+
# EdgeDataViews
|
| 747 |
+
class OutEdgeDataView(EdgeViewABC):
|
| 748 |
+
"""EdgeDataView for outward edges of DiGraph; See EdgeDataView"""
|
| 749 |
+
|
| 750 |
+
__slots__ = (
|
| 751 |
+
"_viewer",
|
| 752 |
+
"_nbunch",
|
| 753 |
+
"_data",
|
| 754 |
+
"_default",
|
| 755 |
+
"_adjdict",
|
| 756 |
+
"_nodes_nbrs",
|
| 757 |
+
"_report",
|
| 758 |
+
)
|
| 759 |
+
|
| 760 |
+
def __getstate__(self):
|
| 761 |
+
return {
|
| 762 |
+
"viewer": self._viewer,
|
| 763 |
+
"nbunch": self._nbunch,
|
| 764 |
+
"data": self._data,
|
| 765 |
+
"default": self._default,
|
| 766 |
+
}
|
| 767 |
+
|
| 768 |
+
def __setstate__(self, state):
|
| 769 |
+
self.__init__(**state)
|
| 770 |
+
|
| 771 |
+
def __init__(self, viewer, nbunch=None, data=False, *, default=None):
|
| 772 |
+
self._viewer = viewer
|
| 773 |
+
adjdict = self._adjdict = viewer._adjdict
|
| 774 |
+
if nbunch is None:
|
| 775 |
+
self._nodes_nbrs = adjdict.items
|
| 776 |
+
else:
|
| 777 |
+
# dict retains order of nodes but acts like a set
|
| 778 |
+
nbunch = dict.fromkeys(viewer._graph.nbunch_iter(nbunch))
|
| 779 |
+
self._nodes_nbrs = lambda: [(n, adjdict[n]) for n in nbunch]
|
| 780 |
+
self._nbunch = nbunch
|
| 781 |
+
self._data = data
|
| 782 |
+
self._default = default
|
| 783 |
+
# Set _report based on data and default
|
| 784 |
+
if data is True:
|
| 785 |
+
self._report = lambda n, nbr, dd: (n, nbr, dd)
|
| 786 |
+
elif data is False:
|
| 787 |
+
self._report = lambda n, nbr, dd: (n, nbr)
|
| 788 |
+
else: # data is attribute name
|
| 789 |
+
self._report = (
|
| 790 |
+
lambda n, nbr, dd: (n, nbr, dd[data])
|
| 791 |
+
if data in dd
|
| 792 |
+
else (n, nbr, default)
|
| 793 |
+
)
|
| 794 |
+
|
| 795 |
+
def __len__(self):
|
| 796 |
+
return sum(len(nbrs) for n, nbrs in self._nodes_nbrs())
|
| 797 |
+
|
| 798 |
+
def __iter__(self):
|
| 799 |
+
return (
|
| 800 |
+
self._report(n, nbr, dd)
|
| 801 |
+
for n, nbrs in self._nodes_nbrs()
|
| 802 |
+
for nbr, dd in nbrs.items()
|
| 803 |
+
)
|
| 804 |
+
|
| 805 |
+
def __contains__(self, e):
|
| 806 |
+
u, v = e[:2]
|
| 807 |
+
if self._nbunch is not None and u not in self._nbunch:
|
| 808 |
+
return False # this edge doesn't start in nbunch
|
| 809 |
+
try:
|
| 810 |
+
ddict = self._adjdict[u][v]
|
| 811 |
+
except KeyError:
|
| 812 |
+
return False
|
| 813 |
+
return e == self._report(u, v, ddict)
|
| 814 |
+
|
| 815 |
+
def __str__(self):
|
| 816 |
+
return str(list(self))
|
| 817 |
+
|
| 818 |
+
def __repr__(self):
|
| 819 |
+
return f"{self.__class__.__name__}({list(self)})"
|
| 820 |
+
|
| 821 |
+
|
| 822 |
+
class EdgeDataView(OutEdgeDataView):
|
| 823 |
+
"""A EdgeDataView class for edges of Graph
|
| 824 |
+
|
| 825 |
+
This view is primarily used to iterate over the edges reporting
|
| 826 |
+
edges as node-tuples with edge data optionally reported. The
|
| 827 |
+
argument `nbunch` allows restriction to edges incident to nodes
|
| 828 |
+
in that container/singleton. The default (nbunch=None)
|
| 829 |
+
reports all edges. The arguments `data` and `default` control
|
| 830 |
+
what edge data is reported. The default `data is False` reports
|
| 831 |
+
only node-tuples for each edge. If `data is True` the entire edge
|
| 832 |
+
data dict is returned. Otherwise `data` is assumed to hold the name
|
| 833 |
+
of the edge attribute to report with default `default` if that
|
| 834 |
+
edge attribute is not present.
|
| 835 |
+
|
| 836 |
+
Parameters
|
| 837 |
+
----------
|
| 838 |
+
nbunch : container of nodes, node or None (default None)
|
| 839 |
+
data : False, True or string (default False)
|
| 840 |
+
default : default value (default None)
|
| 841 |
+
|
| 842 |
+
Examples
|
| 843 |
+
--------
|
| 844 |
+
>>> G = nx.path_graph(3)
|
| 845 |
+
>>> G.add_edge(1, 2, foo="bar")
|
| 846 |
+
>>> list(G.edges(data="foo", default="biz"))
|
| 847 |
+
[(0, 1, 'biz'), (1, 2, 'bar')]
|
| 848 |
+
>>> assert (0, 1, "biz") in G.edges(data="foo", default="biz")
|
| 849 |
+
"""
|
| 850 |
+
|
| 851 |
+
__slots__ = ()
|
| 852 |
+
|
| 853 |
+
def __len__(self):
|
| 854 |
+
return sum(1 for e in self)
|
| 855 |
+
|
| 856 |
+
def __iter__(self):
|
| 857 |
+
seen = {}
|
| 858 |
+
for n, nbrs in self._nodes_nbrs():
|
| 859 |
+
for nbr, dd in nbrs.items():
|
| 860 |
+
if nbr not in seen:
|
| 861 |
+
yield self._report(n, nbr, dd)
|
| 862 |
+
seen[n] = 1
|
| 863 |
+
del seen
|
| 864 |
+
|
| 865 |
+
def __contains__(self, e):
|
| 866 |
+
u, v = e[:2]
|
| 867 |
+
if self._nbunch is not None and u not in self._nbunch and v not in self._nbunch:
|
| 868 |
+
return False # this edge doesn't start and it doesn't end in nbunch
|
| 869 |
+
try:
|
| 870 |
+
ddict = self._adjdict[u][v]
|
| 871 |
+
except KeyError:
|
| 872 |
+
return False
|
| 873 |
+
return e == self._report(u, v, ddict)
|
| 874 |
+
|
| 875 |
+
|
| 876 |
+
class InEdgeDataView(OutEdgeDataView):
|
| 877 |
+
"""An EdgeDataView class for outward edges of DiGraph; See EdgeDataView"""
|
| 878 |
+
|
| 879 |
+
__slots__ = ()
|
| 880 |
+
|
| 881 |
+
def __iter__(self):
|
| 882 |
+
return (
|
| 883 |
+
self._report(nbr, n, dd)
|
| 884 |
+
for n, nbrs in self._nodes_nbrs()
|
| 885 |
+
for nbr, dd in nbrs.items()
|
| 886 |
+
)
|
| 887 |
+
|
| 888 |
+
def __contains__(self, e):
|
| 889 |
+
u, v = e[:2]
|
| 890 |
+
if self._nbunch is not None and v not in self._nbunch:
|
| 891 |
+
return False # this edge doesn't end in nbunch
|
| 892 |
+
try:
|
| 893 |
+
ddict = self._adjdict[v][u]
|
| 894 |
+
except KeyError:
|
| 895 |
+
return False
|
| 896 |
+
return e == self._report(u, v, ddict)
|
| 897 |
+
|
| 898 |
+
|
| 899 |
+
class OutMultiEdgeDataView(OutEdgeDataView):
|
| 900 |
+
"""An EdgeDataView for outward edges of MultiDiGraph; See EdgeDataView"""
|
| 901 |
+
|
| 902 |
+
__slots__ = ("keys",)
|
| 903 |
+
|
| 904 |
+
def __getstate__(self):
|
| 905 |
+
return {
|
| 906 |
+
"viewer": self._viewer,
|
| 907 |
+
"nbunch": self._nbunch,
|
| 908 |
+
"keys": self.keys,
|
| 909 |
+
"data": self._data,
|
| 910 |
+
"default": self._default,
|
| 911 |
+
}
|
| 912 |
+
|
| 913 |
+
def __setstate__(self, state):
|
| 914 |
+
self.__init__(**state)
|
| 915 |
+
|
| 916 |
+
def __init__(self, viewer, nbunch=None, data=False, *, default=None, keys=False):
|
| 917 |
+
self._viewer = viewer
|
| 918 |
+
adjdict = self._adjdict = viewer._adjdict
|
| 919 |
+
self.keys = keys
|
| 920 |
+
if nbunch is None:
|
| 921 |
+
self._nodes_nbrs = adjdict.items
|
| 922 |
+
else:
|
| 923 |
+
# dict retains order of nodes but acts like a set
|
| 924 |
+
nbunch = dict.fromkeys(viewer._graph.nbunch_iter(nbunch))
|
| 925 |
+
self._nodes_nbrs = lambda: [(n, adjdict[n]) for n in nbunch]
|
| 926 |
+
self._nbunch = nbunch
|
| 927 |
+
self._data = data
|
| 928 |
+
self._default = default
|
| 929 |
+
# Set _report based on data and default
|
| 930 |
+
if data is True:
|
| 931 |
+
if keys is True:
|
| 932 |
+
self._report = lambda n, nbr, k, dd: (n, nbr, k, dd)
|
| 933 |
+
else:
|
| 934 |
+
self._report = lambda n, nbr, k, dd: (n, nbr, dd)
|
| 935 |
+
elif data is False:
|
| 936 |
+
if keys is True:
|
| 937 |
+
self._report = lambda n, nbr, k, dd: (n, nbr, k)
|
| 938 |
+
else:
|
| 939 |
+
self._report = lambda n, nbr, k, dd: (n, nbr)
|
| 940 |
+
else: # data is attribute name
|
| 941 |
+
if keys is True:
|
| 942 |
+
self._report = (
|
| 943 |
+
lambda n, nbr, k, dd: (n, nbr, k, dd[data])
|
| 944 |
+
if data in dd
|
| 945 |
+
else (n, nbr, k, default)
|
| 946 |
+
)
|
| 947 |
+
else:
|
| 948 |
+
self._report = (
|
| 949 |
+
lambda n, nbr, k, dd: (n, nbr, dd[data])
|
| 950 |
+
if data in dd
|
| 951 |
+
else (n, nbr, default)
|
| 952 |
+
)
|
| 953 |
+
|
| 954 |
+
def __len__(self):
|
| 955 |
+
return sum(1 for e in self)
|
| 956 |
+
|
| 957 |
+
def __iter__(self):
|
| 958 |
+
return (
|
| 959 |
+
self._report(n, nbr, k, dd)
|
| 960 |
+
for n, nbrs in self._nodes_nbrs()
|
| 961 |
+
for nbr, kd in nbrs.items()
|
| 962 |
+
for k, dd in kd.items()
|
| 963 |
+
)
|
| 964 |
+
|
| 965 |
+
def __contains__(self, e):
|
| 966 |
+
u, v = e[:2]
|
| 967 |
+
if self._nbunch is not None and u not in self._nbunch:
|
| 968 |
+
return False # this edge doesn't start in nbunch
|
| 969 |
+
try:
|
| 970 |
+
kdict = self._adjdict[u][v]
|
| 971 |
+
except KeyError:
|
| 972 |
+
return False
|
| 973 |
+
if self.keys is True:
|
| 974 |
+
k = e[2]
|
| 975 |
+
try:
|
| 976 |
+
dd = kdict[k]
|
| 977 |
+
except KeyError:
|
| 978 |
+
return False
|
| 979 |
+
return e == self._report(u, v, k, dd)
|
| 980 |
+
return any(e == self._report(u, v, k, dd) for k, dd in kdict.items())
|
| 981 |
+
|
| 982 |
+
|
| 983 |
+
class MultiEdgeDataView(OutMultiEdgeDataView):
|
| 984 |
+
"""An EdgeDataView class for edges of MultiGraph; See EdgeDataView"""
|
| 985 |
+
|
| 986 |
+
__slots__ = ()
|
| 987 |
+
|
| 988 |
+
def __iter__(self):
|
| 989 |
+
seen = {}
|
| 990 |
+
for n, nbrs in self._nodes_nbrs():
|
| 991 |
+
for nbr, kd in nbrs.items():
|
| 992 |
+
if nbr not in seen:
|
| 993 |
+
for k, dd in kd.items():
|
| 994 |
+
yield self._report(n, nbr, k, dd)
|
| 995 |
+
seen[n] = 1
|
| 996 |
+
del seen
|
| 997 |
+
|
| 998 |
+
def __contains__(self, e):
|
| 999 |
+
u, v = e[:2]
|
| 1000 |
+
if self._nbunch is not None and u not in self._nbunch and v not in self._nbunch:
|
| 1001 |
+
return False # this edge doesn't start and doesn't end in nbunch
|
| 1002 |
+
try:
|
| 1003 |
+
kdict = self._adjdict[u][v]
|
| 1004 |
+
except KeyError:
|
| 1005 |
+
try:
|
| 1006 |
+
kdict = self._adjdict[v][u]
|
| 1007 |
+
except KeyError:
|
| 1008 |
+
return False
|
| 1009 |
+
if self.keys is True:
|
| 1010 |
+
k = e[2]
|
| 1011 |
+
try:
|
| 1012 |
+
dd = kdict[k]
|
| 1013 |
+
except KeyError:
|
| 1014 |
+
return False
|
| 1015 |
+
return e == self._report(u, v, k, dd)
|
| 1016 |
+
return any(e == self._report(u, v, k, dd) for k, dd in kdict.items())
|
| 1017 |
+
|
| 1018 |
+
|
| 1019 |
+
class InMultiEdgeDataView(OutMultiEdgeDataView):
|
| 1020 |
+
"""An EdgeDataView for inward edges of MultiDiGraph; See EdgeDataView"""
|
| 1021 |
+
|
| 1022 |
+
__slots__ = ()
|
| 1023 |
+
|
| 1024 |
+
def __iter__(self):
|
| 1025 |
+
return (
|
| 1026 |
+
self._report(nbr, n, k, dd)
|
| 1027 |
+
for n, nbrs in self._nodes_nbrs()
|
| 1028 |
+
for nbr, kd in nbrs.items()
|
| 1029 |
+
for k, dd in kd.items()
|
| 1030 |
+
)
|
| 1031 |
+
|
| 1032 |
+
def __contains__(self, e):
|
| 1033 |
+
u, v = e[:2]
|
| 1034 |
+
if self._nbunch is not None and v not in self._nbunch:
|
| 1035 |
+
return False # this edge doesn't end in nbunch
|
| 1036 |
+
try:
|
| 1037 |
+
kdict = self._adjdict[v][u]
|
| 1038 |
+
except KeyError:
|
| 1039 |
+
return False
|
| 1040 |
+
if self.keys is True:
|
| 1041 |
+
k = e[2]
|
| 1042 |
+
dd = kdict[k]
|
| 1043 |
+
return e == self._report(u, v, k, dd)
|
| 1044 |
+
return any(e == self._report(u, v, k, dd) for k, dd in kdict.items())
|
| 1045 |
+
|
| 1046 |
+
|
| 1047 |
+
# EdgeViews have set operations and no data reported
|
| 1048 |
+
class OutEdgeView(Set, Mapping, EdgeViewABC):
|
| 1049 |
+
"""A EdgeView class for outward edges of a DiGraph"""
|
| 1050 |
+
|
| 1051 |
+
__slots__ = ("_adjdict", "_graph", "_nodes_nbrs")
|
| 1052 |
+
|
| 1053 |
+
def __getstate__(self):
|
| 1054 |
+
return {"_graph": self._graph, "_adjdict": self._adjdict}
|
| 1055 |
+
|
| 1056 |
+
def __setstate__(self, state):
|
| 1057 |
+
self._graph = state["_graph"]
|
| 1058 |
+
self._adjdict = state["_adjdict"]
|
| 1059 |
+
self._nodes_nbrs = self._adjdict.items
|
| 1060 |
+
|
| 1061 |
+
@classmethod
|
| 1062 |
+
def _from_iterable(cls, it):
|
| 1063 |
+
return set(it)
|
| 1064 |
+
|
| 1065 |
+
dataview = OutEdgeDataView
|
| 1066 |
+
|
| 1067 |
+
def __init__(self, G):
|
| 1068 |
+
self._graph = G
|
| 1069 |
+
self._adjdict = G._succ if hasattr(G, "succ") else G._adj
|
| 1070 |
+
self._nodes_nbrs = self._adjdict.items
|
| 1071 |
+
|
| 1072 |
+
# Set methods
|
| 1073 |
+
def __len__(self):
|
| 1074 |
+
return sum(len(nbrs) for n, nbrs in self._nodes_nbrs())
|
| 1075 |
+
|
| 1076 |
+
def __iter__(self):
|
| 1077 |
+
for n, nbrs in self._nodes_nbrs():
|
| 1078 |
+
for nbr in nbrs:
|
| 1079 |
+
yield (n, nbr)
|
| 1080 |
+
|
| 1081 |
+
def __contains__(self, e):
|
| 1082 |
+
try:
|
| 1083 |
+
u, v = e
|
| 1084 |
+
return v in self._adjdict[u]
|
| 1085 |
+
except KeyError:
|
| 1086 |
+
return False
|
| 1087 |
+
|
| 1088 |
+
# Mapping Methods
|
| 1089 |
+
def __getitem__(self, e):
|
| 1090 |
+
if isinstance(e, slice):
|
| 1091 |
+
raise nx.NetworkXError(
|
| 1092 |
+
f"{type(self).__name__} does not support slicing, "
|
| 1093 |
+
f"try list(G.edges)[{e.start}:{e.stop}:{e.step}]"
|
| 1094 |
+
)
|
| 1095 |
+
u, v = e
|
| 1096 |
+
try:
|
| 1097 |
+
return self._adjdict[u][v]
|
| 1098 |
+
except KeyError as ex: # Customize msg to indicate exception origin
|
| 1099 |
+
raise KeyError(f"The edge {e} is not in the graph.")
|
| 1100 |
+
|
| 1101 |
+
# EdgeDataView methods
|
| 1102 |
+
def __call__(self, nbunch=None, data=False, *, default=None):
|
| 1103 |
+
if nbunch is None and data is False:
|
| 1104 |
+
return self
|
| 1105 |
+
return self.dataview(self, nbunch, data, default=default)
|
| 1106 |
+
|
| 1107 |
+
def data(self, data=True, default=None, nbunch=None):
|
| 1108 |
+
"""
|
| 1109 |
+
Return a read-only view of edge data.
|
| 1110 |
+
|
| 1111 |
+
Parameters
|
| 1112 |
+
----------
|
| 1113 |
+
data : bool or edge attribute key
|
| 1114 |
+
If ``data=True``, then the data view maps each edge to a dictionary
|
| 1115 |
+
containing all of its attributes. If `data` is a key in the edge
|
| 1116 |
+
dictionary, then the data view maps each edge to its value for
|
| 1117 |
+
the keyed attribute. In this case, if the edge doesn't have the
|
| 1118 |
+
attribute, the `default` value is returned.
|
| 1119 |
+
default : object, default=None
|
| 1120 |
+
The value used when an edge does not have a specific attribute
|
| 1121 |
+
nbunch : container of nodes, optional (default=None)
|
| 1122 |
+
Allows restriction to edges only involving certain nodes. All edges
|
| 1123 |
+
are considered by default.
|
| 1124 |
+
|
| 1125 |
+
Returns
|
| 1126 |
+
-------
|
| 1127 |
+
dataview
|
| 1128 |
+
Returns an `EdgeDataView` for undirected Graphs, `OutEdgeDataView`
|
| 1129 |
+
for DiGraphs, `MultiEdgeDataView` for MultiGraphs and
|
| 1130 |
+
`OutMultiEdgeDataView` for MultiDiGraphs.
|
| 1131 |
+
|
| 1132 |
+
Notes
|
| 1133 |
+
-----
|
| 1134 |
+
If ``data=False``, returns an `EdgeView` without any edge data.
|
| 1135 |
+
|
| 1136 |
+
See Also
|
| 1137 |
+
--------
|
| 1138 |
+
EdgeDataView
|
| 1139 |
+
OutEdgeDataView
|
| 1140 |
+
MultiEdgeDataView
|
| 1141 |
+
OutMultiEdgeDataView
|
| 1142 |
+
|
| 1143 |
+
Examples
|
| 1144 |
+
--------
|
| 1145 |
+
>>> G = nx.Graph()
|
| 1146 |
+
>>> G.add_edges_from(
|
| 1147 |
+
... [
|
| 1148 |
+
... (0, 1, {"dist": 3, "capacity": 20}),
|
| 1149 |
+
... (1, 2, {"dist": 4}),
|
| 1150 |
+
... (2, 0, {"dist": 5}),
|
| 1151 |
+
... ]
|
| 1152 |
+
... )
|
| 1153 |
+
|
| 1154 |
+
Accessing edge data with ``data=True`` (the default) returns an
|
| 1155 |
+
edge data view object listing each edge with all of its attributes:
|
| 1156 |
+
|
| 1157 |
+
>>> G.edges.data()
|
| 1158 |
+
EdgeDataView([(0, 1, {'dist': 3, 'capacity': 20}), (0, 2, {'dist': 5}), (1, 2, {'dist': 4})])
|
| 1159 |
+
|
| 1160 |
+
If `data` represents a key in the edge attribute dict, a dataview listing
|
| 1161 |
+
each edge with its value for that specific key is returned:
|
| 1162 |
+
|
| 1163 |
+
>>> G.edges.data("dist")
|
| 1164 |
+
EdgeDataView([(0, 1, 3), (0, 2, 5), (1, 2, 4)])
|
| 1165 |
+
|
| 1166 |
+
`nbunch` can be used to limit the edges:
|
| 1167 |
+
|
| 1168 |
+
>>> G.edges.data("dist", nbunch=[0])
|
| 1169 |
+
EdgeDataView([(0, 1, 3), (0, 2, 5)])
|
| 1170 |
+
|
| 1171 |
+
If a specific key is not found in an edge attribute dict, the value
|
| 1172 |
+
specified by `default` is used:
|
| 1173 |
+
|
| 1174 |
+
>>> G.edges.data("capacity")
|
| 1175 |
+
EdgeDataView([(0, 1, 20), (0, 2, None), (1, 2, None)])
|
| 1176 |
+
|
| 1177 |
+
Note that there is no check that the `data` key is present in any of
|
| 1178 |
+
the edge attribute dictionaries:
|
| 1179 |
+
|
| 1180 |
+
>>> G.edges.data("speed")
|
| 1181 |
+
EdgeDataView([(0, 1, None), (0, 2, None), (1, 2, None)])
|
| 1182 |
+
"""
|
| 1183 |
+
if nbunch is None and data is False:
|
| 1184 |
+
return self
|
| 1185 |
+
return self.dataview(self, nbunch, data, default=default)
|
| 1186 |
+
|
| 1187 |
+
# String Methods
|
| 1188 |
+
def __str__(self):
|
| 1189 |
+
return str(list(self))
|
| 1190 |
+
|
| 1191 |
+
def __repr__(self):
|
| 1192 |
+
return f"{self.__class__.__name__}({list(self)})"
|
| 1193 |
+
|
| 1194 |
+
|
| 1195 |
+
class EdgeView(OutEdgeView):
|
| 1196 |
+
"""A EdgeView class for edges of a Graph
|
| 1197 |
+
|
| 1198 |
+
This densely packed View allows iteration over edges, data lookup
|
| 1199 |
+
like a dict and set operations on edges represented by node-tuples.
|
| 1200 |
+
In addition, edge data can be controlled by calling this object
|
| 1201 |
+
possibly creating an EdgeDataView. Typically edges are iterated over
|
| 1202 |
+
and reported as `(u, v)` node tuples or `(u, v, key)` node/key tuples
|
| 1203 |
+
for multigraphs. Those edge representations can also be using to
|
| 1204 |
+
lookup the data dict for any edge. Set operations also are available
|
| 1205 |
+
where those tuples are the elements of the set.
|
| 1206 |
+
Calling this object with optional arguments `data`, `default` and `keys`
|
| 1207 |
+
controls the form of the tuple (see EdgeDataView). Optional argument
|
| 1208 |
+
`nbunch` allows restriction to edges only involving certain nodes.
|
| 1209 |
+
|
| 1210 |
+
If `data is False` (the default) then iterate over 2-tuples `(u, v)`.
|
| 1211 |
+
If `data is True` iterate over 3-tuples `(u, v, datadict)`.
|
| 1212 |
+
Otherwise iterate over `(u, v, datadict.get(data, default))`.
|
| 1213 |
+
For Multigraphs, if `keys is True`, replace `u, v` with `u, v, key` above.
|
| 1214 |
+
|
| 1215 |
+
Parameters
|
| 1216 |
+
==========
|
| 1217 |
+
graph : NetworkX graph-like class
|
| 1218 |
+
nbunch : (default= all nodes in graph) only report edges with these nodes
|
| 1219 |
+
keys : (only for MultiGraph. default=False) report edge key in tuple
|
| 1220 |
+
data : bool or string (default=False) see above
|
| 1221 |
+
default : object (default=None)
|
| 1222 |
+
|
| 1223 |
+
Examples
|
| 1224 |
+
========
|
| 1225 |
+
>>> G = nx.path_graph(4)
|
| 1226 |
+
>>> EV = G.edges()
|
| 1227 |
+
>>> (2, 3) in EV
|
| 1228 |
+
True
|
| 1229 |
+
>>> for u, v in EV:
|
| 1230 |
+
... print((u, v))
|
| 1231 |
+
(0, 1)
|
| 1232 |
+
(1, 2)
|
| 1233 |
+
(2, 3)
|
| 1234 |
+
>>> assert EV & {(1, 2), (3, 4)} == {(1, 2)}
|
| 1235 |
+
|
| 1236 |
+
>>> EVdata = G.edges(data="color", default="aqua")
|
| 1237 |
+
>>> G.add_edge(2, 3, color="blue")
|
| 1238 |
+
>>> assert (2, 3, "blue") in EVdata
|
| 1239 |
+
>>> for u, v, c in EVdata:
|
| 1240 |
+
... print(f"({u}, {v}) has color: {c}")
|
| 1241 |
+
(0, 1) has color: aqua
|
| 1242 |
+
(1, 2) has color: aqua
|
| 1243 |
+
(2, 3) has color: blue
|
| 1244 |
+
|
| 1245 |
+
>>> EVnbunch = G.edges(nbunch=2)
|
| 1246 |
+
>>> assert (2, 3) in EVnbunch
|
| 1247 |
+
>>> assert (0, 1) not in EVnbunch
|
| 1248 |
+
>>> for u, v in EVnbunch:
|
| 1249 |
+
... assert u == 2 or v == 2
|
| 1250 |
+
|
| 1251 |
+
>>> MG = nx.path_graph(4, create_using=nx.MultiGraph)
|
| 1252 |
+
>>> EVmulti = MG.edges(keys=True)
|
| 1253 |
+
>>> (2, 3, 0) in EVmulti
|
| 1254 |
+
True
|
| 1255 |
+
>>> (2, 3) in EVmulti # 2-tuples work even when keys is True
|
| 1256 |
+
True
|
| 1257 |
+
>>> key = MG.add_edge(2, 3)
|
| 1258 |
+
>>> for u, v, k in EVmulti:
|
| 1259 |
+
... print((u, v, k))
|
| 1260 |
+
(0, 1, 0)
|
| 1261 |
+
(1, 2, 0)
|
| 1262 |
+
(2, 3, 0)
|
| 1263 |
+
(2, 3, 1)
|
| 1264 |
+
"""
|
| 1265 |
+
|
| 1266 |
+
__slots__ = ()
|
| 1267 |
+
|
| 1268 |
+
dataview = EdgeDataView
|
| 1269 |
+
|
| 1270 |
+
def __len__(self):
|
| 1271 |
+
num_nbrs = (len(nbrs) + (n in nbrs) for n, nbrs in self._nodes_nbrs())
|
| 1272 |
+
return sum(num_nbrs) // 2
|
| 1273 |
+
|
| 1274 |
+
def __iter__(self):
|
| 1275 |
+
seen = {}
|
| 1276 |
+
for n, nbrs in self._nodes_nbrs():
|
| 1277 |
+
for nbr in list(nbrs):
|
| 1278 |
+
if nbr not in seen:
|
| 1279 |
+
yield (n, nbr)
|
| 1280 |
+
seen[n] = 1
|
| 1281 |
+
del seen
|
| 1282 |
+
|
| 1283 |
+
def __contains__(self, e):
|
| 1284 |
+
try:
|
| 1285 |
+
u, v = e[:2]
|
| 1286 |
+
return v in self._adjdict[u] or u in self._adjdict[v]
|
| 1287 |
+
except (KeyError, ValueError):
|
| 1288 |
+
return False
|
| 1289 |
+
|
| 1290 |
+
|
| 1291 |
+
class InEdgeView(OutEdgeView):
|
| 1292 |
+
"""A EdgeView class for inward edges of a DiGraph"""
|
| 1293 |
+
|
| 1294 |
+
__slots__ = ()
|
| 1295 |
+
|
| 1296 |
+
def __setstate__(self, state):
|
| 1297 |
+
self._graph = state["_graph"]
|
| 1298 |
+
self._adjdict = state["_adjdict"]
|
| 1299 |
+
self._nodes_nbrs = self._adjdict.items
|
| 1300 |
+
|
| 1301 |
+
dataview = InEdgeDataView
|
| 1302 |
+
|
| 1303 |
+
def __init__(self, G):
|
| 1304 |
+
self._graph = G
|
| 1305 |
+
self._adjdict = G._pred if hasattr(G, "pred") else G._adj
|
| 1306 |
+
self._nodes_nbrs = self._adjdict.items
|
| 1307 |
+
|
| 1308 |
+
def __iter__(self):
|
| 1309 |
+
for n, nbrs in self._nodes_nbrs():
|
| 1310 |
+
for nbr in nbrs:
|
| 1311 |
+
yield (nbr, n)
|
| 1312 |
+
|
| 1313 |
+
def __contains__(self, e):
|
| 1314 |
+
try:
|
| 1315 |
+
u, v = e
|
| 1316 |
+
return u in self._adjdict[v]
|
| 1317 |
+
except KeyError:
|
| 1318 |
+
return False
|
| 1319 |
+
|
| 1320 |
+
def __getitem__(self, e):
|
| 1321 |
+
if isinstance(e, slice):
|
| 1322 |
+
raise nx.NetworkXError(
|
| 1323 |
+
f"{type(self).__name__} does not support slicing, "
|
| 1324 |
+
f"try list(G.in_edges)[{e.start}:{e.stop}:{e.step}]"
|
| 1325 |
+
)
|
| 1326 |
+
u, v = e
|
| 1327 |
+
return self._adjdict[v][u]
|
| 1328 |
+
|
| 1329 |
+
|
| 1330 |
+
class OutMultiEdgeView(OutEdgeView):
|
| 1331 |
+
"""A EdgeView class for outward edges of a MultiDiGraph"""
|
| 1332 |
+
|
| 1333 |
+
__slots__ = ()
|
| 1334 |
+
|
| 1335 |
+
dataview = OutMultiEdgeDataView
|
| 1336 |
+
|
| 1337 |
+
def __len__(self):
|
| 1338 |
+
return sum(
|
| 1339 |
+
len(kdict) for n, nbrs in self._nodes_nbrs() for nbr, kdict in nbrs.items()
|
| 1340 |
+
)
|
| 1341 |
+
|
| 1342 |
+
def __iter__(self):
|
| 1343 |
+
for n, nbrs in self._nodes_nbrs():
|
| 1344 |
+
for nbr, kdict in nbrs.items():
|
| 1345 |
+
for key in kdict:
|
| 1346 |
+
yield (n, nbr, key)
|
| 1347 |
+
|
| 1348 |
+
def __contains__(self, e):
|
| 1349 |
+
N = len(e)
|
| 1350 |
+
if N == 3:
|
| 1351 |
+
u, v, k = e
|
| 1352 |
+
elif N == 2:
|
| 1353 |
+
u, v = e
|
| 1354 |
+
k = 0
|
| 1355 |
+
else:
|
| 1356 |
+
raise ValueError("MultiEdge must have length 2 or 3")
|
| 1357 |
+
try:
|
| 1358 |
+
return k in self._adjdict[u][v]
|
| 1359 |
+
except KeyError:
|
| 1360 |
+
return False
|
| 1361 |
+
|
| 1362 |
+
def __getitem__(self, e):
|
| 1363 |
+
if isinstance(e, slice):
|
| 1364 |
+
raise nx.NetworkXError(
|
| 1365 |
+
f"{type(self).__name__} does not support slicing, "
|
| 1366 |
+
f"try list(G.edges)[{e.start}:{e.stop}:{e.step}]"
|
| 1367 |
+
)
|
| 1368 |
+
u, v, k = e
|
| 1369 |
+
return self._adjdict[u][v][k]
|
| 1370 |
+
|
| 1371 |
+
def __call__(self, nbunch=None, data=False, *, default=None, keys=False):
|
| 1372 |
+
if nbunch is None and data is False and keys is True:
|
| 1373 |
+
return self
|
| 1374 |
+
return self.dataview(self, nbunch, data, default=default, keys=keys)
|
| 1375 |
+
|
| 1376 |
+
def data(self, data=True, default=None, nbunch=None, keys=False):
|
| 1377 |
+
if nbunch is None and data is False and keys is True:
|
| 1378 |
+
return self
|
| 1379 |
+
return self.dataview(self, nbunch, data, default=default, keys=keys)
|
| 1380 |
+
|
| 1381 |
+
|
| 1382 |
+
class MultiEdgeView(OutMultiEdgeView):
|
| 1383 |
+
"""A EdgeView class for edges of a MultiGraph"""
|
| 1384 |
+
|
| 1385 |
+
__slots__ = ()
|
| 1386 |
+
|
| 1387 |
+
dataview = MultiEdgeDataView
|
| 1388 |
+
|
| 1389 |
+
def __len__(self):
|
| 1390 |
+
return sum(1 for e in self)
|
| 1391 |
+
|
| 1392 |
+
def __iter__(self):
|
| 1393 |
+
seen = {}
|
| 1394 |
+
for n, nbrs in self._nodes_nbrs():
|
| 1395 |
+
for nbr, kd in nbrs.items():
|
| 1396 |
+
if nbr not in seen:
|
| 1397 |
+
for k, dd in kd.items():
|
| 1398 |
+
yield (n, nbr, k)
|
| 1399 |
+
seen[n] = 1
|
| 1400 |
+
del seen
|
| 1401 |
+
|
| 1402 |
+
|
| 1403 |
+
class InMultiEdgeView(OutMultiEdgeView):
|
| 1404 |
+
"""A EdgeView class for inward edges of a MultiDiGraph"""
|
| 1405 |
+
|
| 1406 |
+
__slots__ = ()
|
| 1407 |
+
|
| 1408 |
+
def __setstate__(self, state):
|
| 1409 |
+
self._graph = state["_graph"]
|
| 1410 |
+
self._adjdict = state["_adjdict"]
|
| 1411 |
+
self._nodes_nbrs = self._adjdict.items
|
| 1412 |
+
|
| 1413 |
+
dataview = InMultiEdgeDataView
|
| 1414 |
+
|
| 1415 |
+
def __init__(self, G):
|
| 1416 |
+
self._graph = G
|
| 1417 |
+
self._adjdict = G._pred if hasattr(G, "pred") else G._adj
|
| 1418 |
+
self._nodes_nbrs = self._adjdict.items
|
| 1419 |
+
|
| 1420 |
+
def __iter__(self):
|
| 1421 |
+
for n, nbrs in self._nodes_nbrs():
|
| 1422 |
+
for nbr, kdict in nbrs.items():
|
| 1423 |
+
for key in kdict:
|
| 1424 |
+
yield (nbr, n, key)
|
| 1425 |
+
|
| 1426 |
+
def __contains__(self, e):
|
| 1427 |
+
N = len(e)
|
| 1428 |
+
if N == 3:
|
| 1429 |
+
u, v, k = e
|
| 1430 |
+
elif N == 2:
|
| 1431 |
+
u, v = e
|
| 1432 |
+
k = 0
|
| 1433 |
+
else:
|
| 1434 |
+
raise ValueError("MultiEdge must have length 2 or 3")
|
| 1435 |
+
try:
|
| 1436 |
+
return k in self._adjdict[v][u]
|
| 1437 |
+
except KeyError:
|
| 1438 |
+
return False
|
| 1439 |
+
|
| 1440 |
+
def __getitem__(self, e):
|
| 1441 |
+
if isinstance(e, slice):
|
| 1442 |
+
raise nx.NetworkXError(
|
| 1443 |
+
f"{type(self).__name__} does not support slicing, "
|
| 1444 |
+
f"try list(G.in_edges)[{e.start}:{e.stop}:{e.step}]"
|
| 1445 |
+
)
|
| 1446 |
+
u, v, k = e
|
| 1447 |
+
return self._adjdict[v][u][k]
|
.venv/lib/python3.11/site-packages/networkx/classes/tests/dispatch_interface.py
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 `_dispatchable` 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 LoopbackBackendInterface:
|
| 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. See test_dag.py::test_topological_sort6
|
| 73 |
+
"lexicographical_topological_sort",
|
| 74 |
+
"topological_generations",
|
| 75 |
+
"topological_sort",
|
| 76 |
+
# Would be nice to some day avoid these cutoffs of full testing
|
| 77 |
+
}:
|
| 78 |
+
return graph
|
| 79 |
+
if isinstance(graph, NodeView):
|
| 80 |
+
# Convert to a Graph with only nodes (no edges)
|
| 81 |
+
new_graph = Graph()
|
| 82 |
+
new_graph.add_nodes_from(graph.items())
|
| 83 |
+
graph = new_graph
|
| 84 |
+
G = LoopbackGraph()
|
| 85 |
+
elif not isinstance(graph, Graph):
|
| 86 |
+
raise TypeError(
|
| 87 |
+
f"Bad type for graph argument {graph_name} in {name}: {type(graph)}"
|
| 88 |
+
)
|
| 89 |
+
elif graph.__class__ in {Graph, LoopbackGraph}:
|
| 90 |
+
G = LoopbackGraph()
|
| 91 |
+
elif graph.__class__ in {DiGraph, LoopbackDiGraph}:
|
| 92 |
+
G = LoopbackDiGraph()
|
| 93 |
+
elif graph.__class__ in {MultiGraph, LoopbackMultiGraph}:
|
| 94 |
+
G = LoopbackMultiGraph()
|
| 95 |
+
elif graph.__class__ in {MultiDiGraph, LoopbackMultiDiGraph}:
|
| 96 |
+
G = LoopbackMultiDiGraph()
|
| 97 |
+
elif graph.__class__ in {PlanarEmbedding, LoopbackPlanarEmbedding}:
|
| 98 |
+
G = LoopbackDiGraph() # or LoopbackPlanarEmbedding
|
| 99 |
+
else:
|
| 100 |
+
# Would be nice to handle these better some day
|
| 101 |
+
# nx.algorithms.approximation.kcomponents._AntiGraph
|
| 102 |
+
# nx.classes.tests.test_multidigraph.MultiDiGraphSubClass
|
| 103 |
+
# nx.classes.tests.test_multigraph.MultiGraphSubClass
|
| 104 |
+
G = graph.__class__()
|
| 105 |
+
|
| 106 |
+
if preserve_graph_attrs:
|
| 107 |
+
G.graph.update(graph.graph)
|
| 108 |
+
|
| 109 |
+
# add nodes
|
| 110 |
+
G.add_nodes_from(graph)
|
| 111 |
+
if preserve_node_attrs:
|
| 112 |
+
for n, dd in G._node.items():
|
| 113 |
+
dd.update(graph.nodes[n])
|
| 114 |
+
elif node_attrs:
|
| 115 |
+
for n, dd in G._node.items():
|
| 116 |
+
dd.update(
|
| 117 |
+
(attr, graph._node[n].get(attr, default))
|
| 118 |
+
for attr, default in node_attrs.items()
|
| 119 |
+
if default is not None or attr in graph._node[n]
|
| 120 |
+
)
|
| 121 |
+
|
| 122 |
+
# tools to build datadict and keydict
|
| 123 |
+
if preserve_edge_attrs:
|
| 124 |
+
|
| 125 |
+
def G_new_datadict(old_dd):
|
| 126 |
+
return G.edge_attr_dict_factory(old_dd)
|
| 127 |
+
elif edge_attrs:
|
| 128 |
+
|
| 129 |
+
def G_new_datadict(old_dd):
|
| 130 |
+
return G.edge_attr_dict_factory(
|
| 131 |
+
(attr, old_dd.get(attr, default))
|
| 132 |
+
for attr, default in edge_attrs.items()
|
| 133 |
+
if default is not None or attr in old_dd
|
| 134 |
+
)
|
| 135 |
+
else:
|
| 136 |
+
|
| 137 |
+
def G_new_datadict(old_dd):
|
| 138 |
+
return G.edge_attr_dict_factory()
|
| 139 |
+
|
| 140 |
+
if G.is_multigraph():
|
| 141 |
+
|
| 142 |
+
def G_new_inner(keydict):
|
| 143 |
+
kd = G.adjlist_inner_dict_factory(
|
| 144 |
+
(k, G_new_datadict(dd)) for k, dd in keydict.items()
|
| 145 |
+
)
|
| 146 |
+
return kd
|
| 147 |
+
else:
|
| 148 |
+
G_new_inner = G_new_datadict
|
| 149 |
+
|
| 150 |
+
# add edges keeping the same order in _adj and _pred
|
| 151 |
+
G_adj = G._adj
|
| 152 |
+
if G.is_directed():
|
| 153 |
+
for n, nbrs in graph._adj.items():
|
| 154 |
+
G_adj[n].update((nbr, G_new_inner(dd)) for nbr, dd in nbrs.items())
|
| 155 |
+
# ensure same datadict for pred and adj; and pred order of graph._pred
|
| 156 |
+
G_pred = G._pred
|
| 157 |
+
for n, nbrs in graph._pred.items():
|
| 158 |
+
G_pred[n].update((nbr, G_adj[nbr][n]) for nbr in nbrs)
|
| 159 |
+
else: # undirected
|
| 160 |
+
for n, nbrs in graph._adj.items():
|
| 161 |
+
# ensure same datadict for both ways; and adj order of graph._adj
|
| 162 |
+
G_adj[n].update(
|
| 163 |
+
(nbr, G_adj[nbr][n] if n in G_adj[nbr] else G_new_inner(dd))
|
| 164 |
+
for nbr, dd in nbrs.items()
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
return G
|
| 168 |
+
|
| 169 |
+
@staticmethod
|
| 170 |
+
def convert_to_nx(obj, *, name=None):
|
| 171 |
+
return obj
|
| 172 |
+
|
| 173 |
+
@staticmethod
|
| 174 |
+
def on_start_tests(items):
|
| 175 |
+
# Verify that items can be xfailed
|
| 176 |
+
for item in items:
|
| 177 |
+
assert hasattr(item, "add_marker")
|
| 178 |
+
|
| 179 |
+
def can_run(self, name, args, kwargs):
|
| 180 |
+
# It is unnecessary to define this function if algorithms are fully supported.
|
| 181 |
+
# We include it for illustration purposes.
|
| 182 |
+
return hasattr(self, name)
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
backend_interface = LoopbackBackendInterface()
|
.venv/lib/python3.11/site-packages/networkx/classes/tests/historical_tests.py
ADDED
|
@@ -0,0 +1,475 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Original NetworkX graph tests"""
|
| 2 |
+
|
| 3 |
+
import pytest
|
| 4 |
+
|
| 5 |
+
import networkx as nx
|
| 6 |
+
from networkx import convert_node_labels_to_integers as cnlti
|
| 7 |
+
from networkx.utils import edges_equal, nodes_equal
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class HistoricalTests:
|
| 11 |
+
@classmethod
|
| 12 |
+
def setup_class(cls):
|
| 13 |
+
cls.null = nx.null_graph()
|
| 14 |
+
cls.P1 = cnlti(nx.path_graph(1), first_label=1)
|
| 15 |
+
cls.P3 = cnlti(nx.path_graph(3), first_label=1)
|
| 16 |
+
cls.P10 = cnlti(nx.path_graph(10), first_label=1)
|
| 17 |
+
cls.K1 = cnlti(nx.complete_graph(1), first_label=1)
|
| 18 |
+
cls.K3 = cnlti(nx.complete_graph(3), first_label=1)
|
| 19 |
+
cls.K4 = cnlti(nx.complete_graph(4), first_label=1)
|
| 20 |
+
cls.K5 = cnlti(nx.complete_graph(5), first_label=1)
|
| 21 |
+
cls.K10 = cnlti(nx.complete_graph(10), first_label=1)
|
| 22 |
+
cls.G = nx.Graph
|
| 23 |
+
|
| 24 |
+
def test_name(self):
|
| 25 |
+
G = self.G(name="test")
|
| 26 |
+
assert G.name == "test"
|
| 27 |
+
H = self.G()
|
| 28 |
+
assert H.name == ""
|
| 29 |
+
|
| 30 |
+
# Nodes
|
| 31 |
+
|
| 32 |
+
def test_add_remove_node(self):
|
| 33 |
+
G = self.G()
|
| 34 |
+
G.add_node("A")
|
| 35 |
+
assert G.has_node("A")
|
| 36 |
+
G.remove_node("A")
|
| 37 |
+
assert not G.has_node("A")
|
| 38 |
+
|
| 39 |
+
def test_nonhashable_node(self):
|
| 40 |
+
# Test if a non-hashable object is in the Graph. A python dict will
|
| 41 |
+
# raise a TypeError, but for a Graph class a simple False should be
|
| 42 |
+
# returned (see Graph __contains__). If it cannot be a node then it is
|
| 43 |
+
# not a node.
|
| 44 |
+
G = self.G()
|
| 45 |
+
assert not G.has_node(["A"])
|
| 46 |
+
assert not G.has_node({"A": 1})
|
| 47 |
+
|
| 48 |
+
def test_add_nodes_from(self):
|
| 49 |
+
G = self.G()
|
| 50 |
+
G.add_nodes_from(list("ABCDEFGHIJKL"))
|
| 51 |
+
assert G.has_node("L")
|
| 52 |
+
G.remove_nodes_from(["H", "I", "J", "K", "L"])
|
| 53 |
+
G.add_nodes_from([1, 2, 3, 4])
|
| 54 |
+
assert sorted(G.nodes(), key=str) == [
|
| 55 |
+
1,
|
| 56 |
+
2,
|
| 57 |
+
3,
|
| 58 |
+
4,
|
| 59 |
+
"A",
|
| 60 |
+
"B",
|
| 61 |
+
"C",
|
| 62 |
+
"D",
|
| 63 |
+
"E",
|
| 64 |
+
"F",
|
| 65 |
+
"G",
|
| 66 |
+
]
|
| 67 |
+
# test __iter__
|
| 68 |
+
assert sorted(G, key=str) == [1, 2, 3, 4, "A", "B", "C", "D", "E", "F", "G"]
|
| 69 |
+
|
| 70 |
+
def test_contains(self):
|
| 71 |
+
G = self.G()
|
| 72 |
+
G.add_node("A")
|
| 73 |
+
assert "A" in G
|
| 74 |
+
assert [] not in G # never raise a Key or TypeError in this test
|
| 75 |
+
assert {1: 1} not in G
|
| 76 |
+
|
| 77 |
+
def test_add_remove(self):
|
| 78 |
+
# Test add_node and remove_node acting for various nbunch
|
| 79 |
+
G = self.G()
|
| 80 |
+
G.add_node("m")
|
| 81 |
+
assert G.has_node("m")
|
| 82 |
+
G.add_node("m") # no complaints
|
| 83 |
+
pytest.raises(nx.NetworkXError, G.remove_node, "j")
|
| 84 |
+
G.remove_node("m")
|
| 85 |
+
assert list(G) == []
|
| 86 |
+
|
| 87 |
+
def test_nbunch_is_list(self):
|
| 88 |
+
G = self.G()
|
| 89 |
+
G.add_nodes_from(list("ABCD"))
|
| 90 |
+
G.add_nodes_from(self.P3) # add nbunch of nodes (nbunch=Graph)
|
| 91 |
+
assert sorted(G.nodes(), key=str) == [1, 2, 3, "A", "B", "C", "D"]
|
| 92 |
+
G.remove_nodes_from(self.P3) # remove nbunch of nodes (nbunch=Graph)
|
| 93 |
+
assert sorted(G.nodes(), key=str) == ["A", "B", "C", "D"]
|
| 94 |
+
|
| 95 |
+
def test_nbunch_is_set(self):
|
| 96 |
+
G = self.G()
|
| 97 |
+
nbunch = set("ABCDEFGHIJKL")
|
| 98 |
+
G.add_nodes_from(nbunch)
|
| 99 |
+
assert G.has_node("L")
|
| 100 |
+
|
| 101 |
+
def test_nbunch_dict(self):
|
| 102 |
+
# nbunch is a dict with nodes as keys
|
| 103 |
+
G = self.G()
|
| 104 |
+
nbunch = set("ABCDEFGHIJKL")
|
| 105 |
+
G.add_nodes_from(nbunch)
|
| 106 |
+
nbunch = {"I": "foo", "J": 2, "K": True, "L": "spam"}
|
| 107 |
+
G.remove_nodes_from(nbunch)
|
| 108 |
+
assert sorted(G.nodes(), key=str), ["A", "B", "C", "D", "E", "F", "G", "H"]
|
| 109 |
+
|
| 110 |
+
def test_nbunch_iterator(self):
|
| 111 |
+
G = self.G()
|
| 112 |
+
G.add_nodes_from(["A", "B", "C", "D", "E", "F", "G", "H"])
|
| 113 |
+
n_iter = self.P3.nodes()
|
| 114 |
+
G.add_nodes_from(n_iter)
|
| 115 |
+
assert sorted(G.nodes(), key=str) == [
|
| 116 |
+
1,
|
| 117 |
+
2,
|
| 118 |
+
3,
|
| 119 |
+
"A",
|
| 120 |
+
"B",
|
| 121 |
+
"C",
|
| 122 |
+
"D",
|
| 123 |
+
"E",
|
| 124 |
+
"F",
|
| 125 |
+
"G",
|
| 126 |
+
"H",
|
| 127 |
+
]
|
| 128 |
+
n_iter = self.P3.nodes() # rebuild same iterator
|
| 129 |
+
G.remove_nodes_from(n_iter) # remove nbunch of nodes (nbunch=iterator)
|
| 130 |
+
assert sorted(G.nodes(), key=str) == ["A", "B", "C", "D", "E", "F", "G", "H"]
|
| 131 |
+
|
| 132 |
+
def test_nbunch_graph(self):
|
| 133 |
+
G = self.G()
|
| 134 |
+
G.add_nodes_from(["A", "B", "C", "D", "E", "F", "G", "H"])
|
| 135 |
+
nbunch = self.K3
|
| 136 |
+
G.add_nodes_from(nbunch)
|
| 137 |
+
assert sorted(G.nodes(), key=str), [
|
| 138 |
+
1,
|
| 139 |
+
2,
|
| 140 |
+
3,
|
| 141 |
+
"A",
|
| 142 |
+
"B",
|
| 143 |
+
"C",
|
| 144 |
+
"D",
|
| 145 |
+
"E",
|
| 146 |
+
"F",
|
| 147 |
+
"G",
|
| 148 |
+
"H",
|
| 149 |
+
]
|
| 150 |
+
|
| 151 |
+
# Edges
|
| 152 |
+
|
| 153 |
+
def test_add_edge(self):
|
| 154 |
+
G = self.G()
|
| 155 |
+
pytest.raises(TypeError, G.add_edge, "A")
|
| 156 |
+
|
| 157 |
+
G.add_edge("A", "B") # testing add_edge()
|
| 158 |
+
G.add_edge("A", "B") # should fail silently
|
| 159 |
+
assert G.has_edge("A", "B")
|
| 160 |
+
assert not G.has_edge("A", "C")
|
| 161 |
+
assert G.has_edge(*("A", "B"))
|
| 162 |
+
if G.is_directed():
|
| 163 |
+
assert not G.has_edge("B", "A")
|
| 164 |
+
else:
|
| 165 |
+
# G is undirected, so B->A is an edge
|
| 166 |
+
assert G.has_edge("B", "A")
|
| 167 |
+
|
| 168 |
+
G.add_edge("A", "C") # test directedness
|
| 169 |
+
G.add_edge("C", "A")
|
| 170 |
+
G.remove_edge("C", "A")
|
| 171 |
+
if G.is_directed():
|
| 172 |
+
assert G.has_edge("A", "C")
|
| 173 |
+
else:
|
| 174 |
+
assert not G.has_edge("A", "C")
|
| 175 |
+
assert not G.has_edge("C", "A")
|
| 176 |
+
|
| 177 |
+
def test_self_loop(self):
|
| 178 |
+
G = self.G()
|
| 179 |
+
G.add_edge("A", "A") # test self loops
|
| 180 |
+
assert G.has_edge("A", "A")
|
| 181 |
+
G.remove_edge("A", "A")
|
| 182 |
+
G.add_edge("X", "X")
|
| 183 |
+
assert G.has_node("X")
|
| 184 |
+
G.remove_node("X")
|
| 185 |
+
G.add_edge("A", "Z") # should add the node silently
|
| 186 |
+
assert G.has_node("Z")
|
| 187 |
+
|
| 188 |
+
def test_add_edges_from(self):
|
| 189 |
+
G = self.G()
|
| 190 |
+
G.add_edges_from([("B", "C")]) # test add_edges_from()
|
| 191 |
+
assert G.has_edge("B", "C")
|
| 192 |
+
if G.is_directed():
|
| 193 |
+
assert not G.has_edge("C", "B")
|
| 194 |
+
else:
|
| 195 |
+
assert G.has_edge("C", "B") # undirected
|
| 196 |
+
|
| 197 |
+
G.add_edges_from([("D", "F"), ("B", "D")])
|
| 198 |
+
assert G.has_edge("D", "F")
|
| 199 |
+
assert G.has_edge("B", "D")
|
| 200 |
+
|
| 201 |
+
if G.is_directed():
|
| 202 |
+
assert not G.has_edge("D", "B")
|
| 203 |
+
else:
|
| 204 |
+
assert G.has_edge("D", "B") # undirected
|
| 205 |
+
|
| 206 |
+
def test_add_edges_from2(self):
|
| 207 |
+
G = self.G()
|
| 208 |
+
# after failing silently, should add 2nd edge
|
| 209 |
+
G.add_edges_from([tuple("IJ"), list("KK"), tuple("JK")])
|
| 210 |
+
assert G.has_edge(*("I", "J"))
|
| 211 |
+
assert G.has_edge(*("K", "K"))
|
| 212 |
+
assert G.has_edge(*("J", "K"))
|
| 213 |
+
if G.is_directed():
|
| 214 |
+
assert not G.has_edge(*("K", "J"))
|
| 215 |
+
else:
|
| 216 |
+
assert G.has_edge(*("K", "J"))
|
| 217 |
+
|
| 218 |
+
def test_add_edges_from3(self):
|
| 219 |
+
G = self.G()
|
| 220 |
+
G.add_edges_from(zip(list("ACD"), list("CDE")))
|
| 221 |
+
assert G.has_edge("D", "E")
|
| 222 |
+
assert not G.has_edge("E", "C")
|
| 223 |
+
|
| 224 |
+
def test_remove_edge(self):
|
| 225 |
+
G = self.G()
|
| 226 |
+
G.add_nodes_from([1, 2, 3, "A", "B", "C", "D", "E", "F", "G", "H"])
|
| 227 |
+
|
| 228 |
+
G.add_edges_from(zip(list("MNOP"), list("NOPM")))
|
| 229 |
+
assert G.has_edge("O", "P")
|
| 230 |
+
assert G.has_edge("P", "M")
|
| 231 |
+
G.remove_node("P") # tests remove_node()'s handling of edges.
|
| 232 |
+
assert not G.has_edge("P", "M")
|
| 233 |
+
pytest.raises(TypeError, G.remove_edge, "M")
|
| 234 |
+
|
| 235 |
+
G.add_edge("N", "M")
|
| 236 |
+
assert G.has_edge("M", "N")
|
| 237 |
+
G.remove_edge("M", "N")
|
| 238 |
+
assert not G.has_edge("M", "N")
|
| 239 |
+
|
| 240 |
+
# self loop fails silently
|
| 241 |
+
G.remove_edges_from([list("HI"), list("DF"), tuple("KK"), tuple("JK")])
|
| 242 |
+
assert not G.has_edge("H", "I")
|
| 243 |
+
assert not G.has_edge("J", "K")
|
| 244 |
+
G.remove_edges_from([list("IJ"), list("KK"), list("JK")])
|
| 245 |
+
assert not G.has_edge("I", "J")
|
| 246 |
+
G.remove_nodes_from(set("ZEFHIMNO"))
|
| 247 |
+
G.add_edge("J", "K")
|
| 248 |
+
|
| 249 |
+
def test_edges_nbunch(self):
|
| 250 |
+
# Test G.edges(nbunch) with various forms of nbunch
|
| 251 |
+
G = self.G()
|
| 252 |
+
G.add_edges_from([("A", "B"), ("A", "C"), ("B", "D"), ("C", "B"), ("C", "D")])
|
| 253 |
+
# node not in nbunch should be quietly ignored
|
| 254 |
+
pytest.raises(nx.NetworkXError, G.edges, 6)
|
| 255 |
+
assert list(G.edges("Z")) == [] # iterable non-node
|
| 256 |
+
# nbunch can be an empty list
|
| 257 |
+
assert list(G.edges([])) == []
|
| 258 |
+
if G.is_directed():
|
| 259 |
+
elist = [("A", "B"), ("A", "C"), ("B", "D")]
|
| 260 |
+
else:
|
| 261 |
+
elist = [("A", "B"), ("A", "C"), ("B", "C"), ("B", "D")]
|
| 262 |
+
# nbunch can be a list
|
| 263 |
+
assert edges_equal(list(G.edges(["A", "B"])), elist)
|
| 264 |
+
# nbunch can be a set
|
| 265 |
+
assert edges_equal(G.edges({"A", "B"}), elist)
|
| 266 |
+
# nbunch can be a graph
|
| 267 |
+
G1 = self.G()
|
| 268 |
+
G1.add_nodes_from("AB")
|
| 269 |
+
assert edges_equal(G.edges(G1), elist)
|
| 270 |
+
# nbunch can be a dict with nodes as keys
|
| 271 |
+
ndict = {"A": "thing1", "B": "thing2"}
|
| 272 |
+
assert edges_equal(G.edges(ndict), elist)
|
| 273 |
+
# nbunch can be a single node
|
| 274 |
+
assert edges_equal(list(G.edges("A")), [("A", "B"), ("A", "C")])
|
| 275 |
+
assert nodes_equal(sorted(G), ["A", "B", "C", "D"])
|
| 276 |
+
|
| 277 |
+
# nbunch can be nothing (whole graph)
|
| 278 |
+
assert edges_equal(
|
| 279 |
+
list(G.edges()),
|
| 280 |
+
[("A", "B"), ("A", "C"), ("B", "D"), ("C", "B"), ("C", "D")],
|
| 281 |
+
)
|
| 282 |
+
|
| 283 |
+
def test_degree(self):
|
| 284 |
+
G = self.G()
|
| 285 |
+
G.add_edges_from([("A", "B"), ("A", "C"), ("B", "D"), ("C", "B"), ("C", "D")])
|
| 286 |
+
assert G.degree("A") == 2
|
| 287 |
+
|
| 288 |
+
# degree of single node in iterable container must return dict
|
| 289 |
+
assert list(G.degree(["A"])) == [("A", 2)]
|
| 290 |
+
assert sorted(d for n, d in G.degree(["A", "B"])) == [2, 3]
|
| 291 |
+
assert sorted(d for n, d in G.degree()) == [2, 2, 3, 3]
|
| 292 |
+
|
| 293 |
+
def test_degree2(self):
|
| 294 |
+
H = self.G()
|
| 295 |
+
H.add_edges_from([(1, 24), (1, 2)])
|
| 296 |
+
assert sorted(d for n, d in H.degree([1, 24])) == [1, 2]
|
| 297 |
+
|
| 298 |
+
def test_degree_graph(self):
|
| 299 |
+
P3 = nx.path_graph(3)
|
| 300 |
+
P5 = nx.path_graph(5)
|
| 301 |
+
# silently ignore nodes not in P3
|
| 302 |
+
assert dict(d for n, d in P3.degree(["A", "B"])) == {}
|
| 303 |
+
# nbunch can be a graph
|
| 304 |
+
assert sorted(d for n, d in P5.degree(P3)) == [1, 2, 2]
|
| 305 |
+
# nbunch can be a graph that's way too big
|
| 306 |
+
assert sorted(d for n, d in P3.degree(P5)) == [1, 1, 2]
|
| 307 |
+
assert list(P5.degree([])) == []
|
| 308 |
+
assert dict(P5.degree([])) == {}
|
| 309 |
+
|
| 310 |
+
def test_null(self):
|
| 311 |
+
null = nx.null_graph()
|
| 312 |
+
assert list(null.degree()) == []
|
| 313 |
+
assert dict(null.degree()) == {}
|
| 314 |
+
|
| 315 |
+
def test_order_size(self):
|
| 316 |
+
G = self.G()
|
| 317 |
+
G.add_edges_from([("A", "B"), ("A", "C"), ("B", "D"), ("C", "B"), ("C", "D")])
|
| 318 |
+
assert G.order() == 4
|
| 319 |
+
assert G.size() == 5
|
| 320 |
+
assert G.number_of_edges() == 5
|
| 321 |
+
assert G.number_of_edges("A", "B") == 1
|
| 322 |
+
assert G.number_of_edges("A", "D") == 0
|
| 323 |
+
|
| 324 |
+
def test_copy(self):
|
| 325 |
+
G = self.G()
|
| 326 |
+
H = G.copy() # copy
|
| 327 |
+
assert H.adj == G.adj
|
| 328 |
+
assert H.name == G.name
|
| 329 |
+
assert H is not G
|
| 330 |
+
|
| 331 |
+
def test_subgraph(self):
|
| 332 |
+
G = self.G()
|
| 333 |
+
G.add_edges_from([("A", "B"), ("A", "C"), ("B", "D"), ("C", "B"), ("C", "D")])
|
| 334 |
+
SG = G.subgraph(["A", "B", "D"])
|
| 335 |
+
assert nodes_equal(list(SG), ["A", "B", "D"])
|
| 336 |
+
assert edges_equal(list(SG.edges()), [("A", "B"), ("B", "D")])
|
| 337 |
+
|
| 338 |
+
def test_to_directed(self):
|
| 339 |
+
G = self.G()
|
| 340 |
+
if not G.is_directed():
|
| 341 |
+
G.add_edges_from(
|
| 342 |
+
[("A", "B"), ("A", "C"), ("B", "D"), ("C", "B"), ("C", "D")]
|
| 343 |
+
)
|
| 344 |
+
|
| 345 |
+
DG = G.to_directed()
|
| 346 |
+
assert DG is not G # directed copy or copy
|
| 347 |
+
|
| 348 |
+
assert DG.is_directed()
|
| 349 |
+
assert DG.name == G.name
|
| 350 |
+
assert DG.adj == G.adj
|
| 351 |
+
assert sorted(DG.out_edges(list("AB"))) == [
|
| 352 |
+
("A", "B"),
|
| 353 |
+
("A", "C"),
|
| 354 |
+
("B", "A"),
|
| 355 |
+
("B", "C"),
|
| 356 |
+
("B", "D"),
|
| 357 |
+
]
|
| 358 |
+
DG.remove_edge("A", "B")
|
| 359 |
+
assert DG.has_edge("B", "A") # this removes B-A but not A-B
|
| 360 |
+
assert not DG.has_edge("A", "B")
|
| 361 |
+
|
| 362 |
+
def test_to_undirected(self):
|
| 363 |
+
G = self.G()
|
| 364 |
+
if G.is_directed():
|
| 365 |
+
G.add_edges_from(
|
| 366 |
+
[("A", "B"), ("A", "C"), ("B", "D"), ("C", "B"), ("C", "D")]
|
| 367 |
+
)
|
| 368 |
+
UG = G.to_undirected() # to_undirected
|
| 369 |
+
assert UG is not G
|
| 370 |
+
assert not UG.is_directed()
|
| 371 |
+
assert G.is_directed()
|
| 372 |
+
assert UG.name == G.name
|
| 373 |
+
assert UG.adj != G.adj
|
| 374 |
+
assert sorted(UG.edges(list("AB"))) == [
|
| 375 |
+
("A", "B"),
|
| 376 |
+
("A", "C"),
|
| 377 |
+
("B", "C"),
|
| 378 |
+
("B", "D"),
|
| 379 |
+
]
|
| 380 |
+
assert sorted(UG.edges(["A", "B"])) == [
|
| 381 |
+
("A", "B"),
|
| 382 |
+
("A", "C"),
|
| 383 |
+
("B", "C"),
|
| 384 |
+
("B", "D"),
|
| 385 |
+
]
|
| 386 |
+
UG.remove_edge("A", "B")
|
| 387 |
+
assert not UG.has_edge("B", "A")
|
| 388 |
+
assert not UG.has_edge("A", "B")
|
| 389 |
+
|
| 390 |
+
def test_neighbors(self):
|
| 391 |
+
G = self.G()
|
| 392 |
+
G.add_edges_from([("A", "B"), ("A", "C"), ("B", "D"), ("C", "B"), ("C", "D")])
|
| 393 |
+
G.add_nodes_from("GJK")
|
| 394 |
+
assert sorted(G["A"]) == ["B", "C"]
|
| 395 |
+
assert sorted(G.neighbors("A")) == ["B", "C"]
|
| 396 |
+
assert sorted(G.neighbors("A")) == ["B", "C"]
|
| 397 |
+
assert sorted(G.neighbors("G")) == []
|
| 398 |
+
pytest.raises(nx.NetworkXError, G.neighbors, "j")
|
| 399 |
+
|
| 400 |
+
def test_iterators(self):
|
| 401 |
+
G = self.G()
|
| 402 |
+
G.add_edges_from([("A", "B"), ("A", "C"), ("B", "D"), ("C", "B"), ("C", "D")])
|
| 403 |
+
G.add_nodes_from("GJK")
|
| 404 |
+
assert sorted(G.nodes()) == ["A", "B", "C", "D", "G", "J", "K"]
|
| 405 |
+
assert edges_equal(
|
| 406 |
+
G.edges(), [("A", "B"), ("A", "C"), ("B", "D"), ("C", "B"), ("C", "D")]
|
| 407 |
+
)
|
| 408 |
+
|
| 409 |
+
assert sorted(v for k, v in G.degree()) == [0, 0, 0, 2, 2, 3, 3]
|
| 410 |
+
assert sorted(G.degree(), key=str) == [
|
| 411 |
+
("A", 2),
|
| 412 |
+
("B", 3),
|
| 413 |
+
("C", 3),
|
| 414 |
+
("D", 2),
|
| 415 |
+
("G", 0),
|
| 416 |
+
("J", 0),
|
| 417 |
+
("K", 0),
|
| 418 |
+
]
|
| 419 |
+
assert sorted(G.neighbors("A")) == ["B", "C"]
|
| 420 |
+
pytest.raises(nx.NetworkXError, G.neighbors, "X")
|
| 421 |
+
G.clear()
|
| 422 |
+
assert nx.number_of_nodes(G) == 0
|
| 423 |
+
assert nx.number_of_edges(G) == 0
|
| 424 |
+
|
| 425 |
+
def test_null_subgraph(self):
|
| 426 |
+
# Subgraph of a null graph is a null graph
|
| 427 |
+
nullgraph = nx.null_graph()
|
| 428 |
+
G = nx.null_graph()
|
| 429 |
+
H = G.subgraph([])
|
| 430 |
+
assert nx.is_isomorphic(H, nullgraph)
|
| 431 |
+
|
| 432 |
+
def test_empty_subgraph(self):
|
| 433 |
+
# Subgraph of an empty graph is an empty graph. test 1
|
| 434 |
+
nullgraph = nx.null_graph()
|
| 435 |
+
E5 = nx.empty_graph(5)
|
| 436 |
+
E10 = nx.empty_graph(10)
|
| 437 |
+
H = E10.subgraph([])
|
| 438 |
+
assert nx.is_isomorphic(H, nullgraph)
|
| 439 |
+
H = E10.subgraph([1, 2, 3, 4, 5])
|
| 440 |
+
assert nx.is_isomorphic(H, E5)
|
| 441 |
+
|
| 442 |
+
def test_complete_subgraph(self):
|
| 443 |
+
# Subgraph of a complete graph is a complete graph
|
| 444 |
+
K1 = nx.complete_graph(1)
|
| 445 |
+
K3 = nx.complete_graph(3)
|
| 446 |
+
K5 = nx.complete_graph(5)
|
| 447 |
+
H = K5.subgraph([1, 2, 3])
|
| 448 |
+
assert nx.is_isomorphic(H, K3)
|
| 449 |
+
|
| 450 |
+
def test_subgraph_nbunch(self):
|
| 451 |
+
nullgraph = nx.null_graph()
|
| 452 |
+
K1 = nx.complete_graph(1)
|
| 453 |
+
K3 = nx.complete_graph(3)
|
| 454 |
+
K5 = nx.complete_graph(5)
|
| 455 |
+
# Test G.subgraph(nbunch), where nbunch is a single node
|
| 456 |
+
H = K5.subgraph(1)
|
| 457 |
+
assert nx.is_isomorphic(H, K1)
|
| 458 |
+
# Test G.subgraph(nbunch), where nbunch is a set
|
| 459 |
+
H = K5.subgraph({1})
|
| 460 |
+
assert nx.is_isomorphic(H, K1)
|
| 461 |
+
# Test G.subgraph(nbunch), where nbunch is an iterator
|
| 462 |
+
H = K5.subgraph(iter(K3))
|
| 463 |
+
assert nx.is_isomorphic(H, K3)
|
| 464 |
+
# Test G.subgraph(nbunch), where nbunch is another graph
|
| 465 |
+
H = K5.subgraph(K3)
|
| 466 |
+
assert nx.is_isomorphic(H, K3)
|
| 467 |
+
H = K5.subgraph([9])
|
| 468 |
+
assert nx.is_isomorphic(H, nullgraph)
|
| 469 |
+
|
| 470 |
+
def test_node_tuple_issue(self):
|
| 471 |
+
H = self.G()
|
| 472 |
+
# Test error handling of tuple as a node
|
| 473 |
+
pytest.raises(nx.NetworkXError, H.remove_node, (1, 2))
|
| 474 |
+
H.remove_nodes_from([(1, 2)]) # no error
|
| 475 |
+
pytest.raises(nx.NetworkXError, H.neighbors, (1, 2))
|
.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]
|
.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)) == []
|
.venv/lib/python3.11/site-packages/networkx/classes/tests/test_digraph_historical.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Original NetworkX graph tests"""
|
| 2 |
+
|
| 3 |
+
import pytest
|
| 4 |
+
|
| 5 |
+
import networkx
|
| 6 |
+
import networkx as nx
|
| 7 |
+
|
| 8 |
+
from .historical_tests import HistoricalTests
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class TestDiGraphHistorical(HistoricalTests):
|
| 12 |
+
@classmethod
|
| 13 |
+
def setup_class(cls):
|
| 14 |
+
HistoricalTests.setup_class()
|
| 15 |
+
cls.G = nx.DiGraph
|
| 16 |
+
|
| 17 |
+
def test_in_degree(self):
|
| 18 |
+
G = self.G()
|
| 19 |
+
G.add_nodes_from("GJK")
|
| 20 |
+
G.add_edges_from([("A", "B"), ("A", "C"), ("B", "D"), ("B", "C"), ("C", "D")])
|
| 21 |
+
|
| 22 |
+
assert sorted(d for n, d in G.in_degree()) == [0, 0, 0, 0, 1, 2, 2]
|
| 23 |
+
assert dict(G.in_degree()) == {
|
| 24 |
+
"A": 0,
|
| 25 |
+
"C": 2,
|
| 26 |
+
"B": 1,
|
| 27 |
+
"D": 2,
|
| 28 |
+
"G": 0,
|
| 29 |
+
"K": 0,
|
| 30 |
+
"J": 0,
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
def test_out_degree(self):
|
| 34 |
+
G = self.G()
|
| 35 |
+
G.add_nodes_from("GJK")
|
| 36 |
+
G.add_edges_from([("A", "B"), ("A", "C"), ("B", "D"), ("B", "C"), ("C", "D")])
|
| 37 |
+
assert sorted(v for k, v in G.in_degree()) == [0, 0, 0, 0, 1, 2, 2]
|
| 38 |
+
assert dict(G.out_degree()) == {
|
| 39 |
+
"A": 2,
|
| 40 |
+
"C": 1,
|
| 41 |
+
"B": 2,
|
| 42 |
+
"D": 0,
|
| 43 |
+
"G": 0,
|
| 44 |
+
"K": 0,
|
| 45 |
+
"J": 0,
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
def test_degree_digraph(self):
|
| 49 |
+
H = nx.DiGraph()
|
| 50 |
+
H.add_edges_from([(1, 24), (1, 2)])
|
| 51 |
+
assert sorted(d for n, d in H.in_degree([1, 24])) == [0, 1]
|
| 52 |
+
assert sorted(d for n, d in H.out_degree([1, 24])) == [0, 2]
|
| 53 |
+
assert sorted(d for n, d in H.degree([1, 24])) == [1, 2]
|
| 54 |
+
|
| 55 |
+
def test_neighbors(self):
|
| 56 |
+
G = self.G()
|
| 57 |
+
G.add_nodes_from("GJK")
|
| 58 |
+
G.add_edges_from([("A", "B"), ("A", "C"), ("B", "D"), ("B", "C"), ("C", "D")])
|
| 59 |
+
|
| 60 |
+
assert sorted(G.neighbors("C")) == ["D"]
|
| 61 |
+
assert sorted(G["C"]) == ["D"]
|
| 62 |
+
assert sorted(G.neighbors("A")) == ["B", "C"]
|
| 63 |
+
pytest.raises(nx.NetworkXError, G.neighbors, "j")
|
| 64 |
+
pytest.raises(nx.NetworkXError, G.neighbors, "j")
|
| 65 |
+
|
| 66 |
+
def test_successors(self):
|
| 67 |
+
G = self.G()
|
| 68 |
+
G.add_nodes_from("GJK")
|
| 69 |
+
G.add_edges_from([("A", "B"), ("A", "C"), ("B", "D"), ("B", "C"), ("C", "D")])
|
| 70 |
+
assert sorted(G.successors("A")) == ["B", "C"]
|
| 71 |
+
assert sorted(G.successors("A")) == ["B", "C"]
|
| 72 |
+
assert sorted(G.successors("G")) == []
|
| 73 |
+
assert sorted(G.successors("D")) == []
|
| 74 |
+
assert sorted(G.successors("G")) == []
|
| 75 |
+
pytest.raises(nx.NetworkXError, G.successors, "j")
|
| 76 |
+
pytest.raises(nx.NetworkXError, G.successors, "j")
|
| 77 |
+
|
| 78 |
+
def test_predecessors(self):
|
| 79 |
+
G = self.G()
|
| 80 |
+
G.add_nodes_from("GJK")
|
| 81 |
+
G.add_edges_from([("A", "B"), ("A", "C"), ("B", "D"), ("B", "C"), ("C", "D")])
|
| 82 |
+
assert sorted(G.predecessors("C")) == ["A", "B"]
|
| 83 |
+
assert sorted(G.predecessors("C")) == ["A", "B"]
|
| 84 |
+
assert sorted(G.predecessors("G")) == []
|
| 85 |
+
assert sorted(G.predecessors("A")) == []
|
| 86 |
+
assert sorted(G.predecessors("G")) == []
|
| 87 |
+
assert sorted(G.predecessors("A")) == []
|
| 88 |
+
assert sorted(G.successors("D")) == []
|
| 89 |
+
|
| 90 |
+
pytest.raises(nx.NetworkXError, G.predecessors, "j")
|
| 91 |
+
pytest.raises(nx.NetworkXError, G.predecessors, "j")
|
| 92 |
+
|
| 93 |
+
def test_reverse(self):
|
| 94 |
+
G = nx.complete_graph(10)
|
| 95 |
+
H = G.to_directed()
|
| 96 |
+
HR = H.reverse()
|
| 97 |
+
assert nx.is_isomorphic(H, HR)
|
| 98 |
+
assert sorted(H.edges()) == sorted(HR.edges())
|
| 99 |
+
|
| 100 |
+
def test_reverse2(self):
|
| 101 |
+
H = nx.DiGraph()
|
| 102 |
+
foo = [H.add_edge(u, u + 1) for u in range(5)]
|
| 103 |
+
HR = H.reverse()
|
| 104 |
+
for u in range(5):
|
| 105 |
+
assert HR.has_edge(u + 1, u)
|
| 106 |
+
|
| 107 |
+
def test_reverse3(self):
|
| 108 |
+
H = nx.DiGraph()
|
| 109 |
+
H.add_nodes_from([1, 2, 3, 4])
|
| 110 |
+
HR = H.reverse()
|
| 111 |
+
assert sorted(HR.nodes()) == [1, 2, 3, 4]
|
.venv/lib/python3.11/site-packages/networkx/classes/tests/test_filters.py
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pytest
|
| 2 |
+
|
| 3 |
+
import networkx as nx
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class TestFilterFactory:
|
| 7 |
+
def test_no_filter(self):
|
| 8 |
+
nf = nx.filters.no_filter
|
| 9 |
+
assert nf()
|
| 10 |
+
assert nf(1)
|
| 11 |
+
assert nf(2, 1)
|
| 12 |
+
|
| 13 |
+
def test_hide_nodes(self):
|
| 14 |
+
f = nx.classes.filters.hide_nodes([1, 2, 3])
|
| 15 |
+
assert not f(1)
|
| 16 |
+
assert not f(2)
|
| 17 |
+
assert not f(3)
|
| 18 |
+
assert f(4)
|
| 19 |
+
assert f(0)
|
| 20 |
+
assert f("a")
|
| 21 |
+
pytest.raises(TypeError, f, 1, 2)
|
| 22 |
+
pytest.raises(TypeError, f)
|
| 23 |
+
|
| 24 |
+
def test_show_nodes(self):
|
| 25 |
+
f = nx.classes.filters.show_nodes([1, 2, 3])
|
| 26 |
+
assert f(1)
|
| 27 |
+
assert f(2)
|
| 28 |
+
assert f(3)
|
| 29 |
+
assert not f(4)
|
| 30 |
+
assert not f(0)
|
| 31 |
+
assert not f("a")
|
| 32 |
+
pytest.raises(TypeError, f, 1, 2)
|
| 33 |
+
pytest.raises(TypeError, f)
|
| 34 |
+
|
| 35 |
+
def test_hide_edges(self):
|
| 36 |
+
factory = nx.classes.filters.hide_edges
|
| 37 |
+
f = factory([(1, 2), (3, 4)])
|
| 38 |
+
assert not f(1, 2)
|
| 39 |
+
assert not f(3, 4)
|
| 40 |
+
assert not f(4, 3)
|
| 41 |
+
assert f(2, 3)
|
| 42 |
+
assert f(0, -1)
|
| 43 |
+
assert f("a", "b")
|
| 44 |
+
pytest.raises(TypeError, f, 1, 2, 3)
|
| 45 |
+
pytest.raises(TypeError, f, 1)
|
| 46 |
+
pytest.raises(TypeError, f)
|
| 47 |
+
pytest.raises(TypeError, factory, [1, 2, 3])
|
| 48 |
+
pytest.raises(ValueError, factory, [(1, 2, 3)])
|
| 49 |
+
|
| 50 |
+
def test_show_edges(self):
|
| 51 |
+
factory = nx.classes.filters.show_edges
|
| 52 |
+
f = factory([(1, 2), (3, 4)])
|
| 53 |
+
assert f(1, 2)
|
| 54 |
+
assert f(3, 4)
|
| 55 |
+
assert f(4, 3)
|
| 56 |
+
assert not f(2, 3)
|
| 57 |
+
assert not f(0, -1)
|
| 58 |
+
assert not f("a", "b")
|
| 59 |
+
pytest.raises(TypeError, f, 1, 2, 3)
|
| 60 |
+
pytest.raises(TypeError, f, 1)
|
| 61 |
+
pytest.raises(TypeError, f)
|
| 62 |
+
pytest.raises(TypeError, factory, [1, 2, 3])
|
| 63 |
+
pytest.raises(ValueError, factory, [(1, 2, 3)])
|
| 64 |
+
|
| 65 |
+
def test_hide_diedges(self):
|
| 66 |
+
factory = nx.classes.filters.hide_diedges
|
| 67 |
+
f = factory([(1, 2), (3, 4)])
|
| 68 |
+
assert not f(1, 2)
|
| 69 |
+
assert not f(3, 4)
|
| 70 |
+
assert f(4, 3)
|
| 71 |
+
assert f(2, 3)
|
| 72 |
+
assert f(0, -1)
|
| 73 |
+
assert f("a", "b")
|
| 74 |
+
pytest.raises(TypeError, f, 1, 2, 3)
|
| 75 |
+
pytest.raises(TypeError, f, 1)
|
| 76 |
+
pytest.raises(TypeError, f)
|
| 77 |
+
pytest.raises(TypeError, factory, [1, 2, 3])
|
| 78 |
+
pytest.raises(ValueError, factory, [(1, 2, 3)])
|
| 79 |
+
|
| 80 |
+
def test_show_diedges(self):
|
| 81 |
+
factory = nx.classes.filters.show_diedges
|
| 82 |
+
f = factory([(1, 2), (3, 4)])
|
| 83 |
+
assert f(1, 2)
|
| 84 |
+
assert f(3, 4)
|
| 85 |
+
assert not f(4, 3)
|
| 86 |
+
assert not f(2, 3)
|
| 87 |
+
assert not f(0, -1)
|
| 88 |
+
assert not f("a", "b")
|
| 89 |
+
pytest.raises(TypeError, f, 1, 2, 3)
|
| 90 |
+
pytest.raises(TypeError, f, 1)
|
| 91 |
+
pytest.raises(TypeError, f)
|
| 92 |
+
pytest.raises(TypeError, factory, [1, 2, 3])
|
| 93 |
+
pytest.raises(ValueError, factory, [(1, 2, 3)])
|
| 94 |
+
|
| 95 |
+
def test_hide_multiedges(self):
|
| 96 |
+
factory = nx.classes.filters.hide_multiedges
|
| 97 |
+
f = factory([(1, 2, 0), (3, 4, 1), (1, 2, 1)])
|
| 98 |
+
assert not f(1, 2, 0)
|
| 99 |
+
assert not f(1, 2, 1)
|
| 100 |
+
assert f(1, 2, 2)
|
| 101 |
+
assert f(3, 4, 0)
|
| 102 |
+
assert not f(3, 4, 1)
|
| 103 |
+
assert not f(4, 3, 1)
|
| 104 |
+
assert f(4, 3, 0)
|
| 105 |
+
assert f(2, 3, 0)
|
| 106 |
+
assert f(0, -1, 0)
|
| 107 |
+
assert f("a", "b", 0)
|
| 108 |
+
pytest.raises(TypeError, f, 1, 2, 3, 4)
|
| 109 |
+
pytest.raises(TypeError, f, 1, 2)
|
| 110 |
+
pytest.raises(TypeError, f, 1)
|
| 111 |
+
pytest.raises(TypeError, f)
|
| 112 |
+
pytest.raises(TypeError, factory, [1, 2, 3])
|
| 113 |
+
pytest.raises(ValueError, factory, [(1, 2)])
|
| 114 |
+
pytest.raises(ValueError, factory, [(1, 2, 3, 4)])
|
| 115 |
+
|
| 116 |
+
def test_show_multiedges(self):
|
| 117 |
+
factory = nx.classes.filters.show_multiedges
|
| 118 |
+
f = factory([(1, 2, 0), (3, 4, 1), (1, 2, 1)])
|
| 119 |
+
assert f(1, 2, 0)
|
| 120 |
+
assert f(1, 2, 1)
|
| 121 |
+
assert not f(1, 2, 2)
|
| 122 |
+
assert not f(3, 4, 0)
|
| 123 |
+
assert f(3, 4, 1)
|
| 124 |
+
assert f(4, 3, 1)
|
| 125 |
+
assert not f(4, 3, 0)
|
| 126 |
+
assert not f(2, 3, 0)
|
| 127 |
+
assert not f(0, -1, 0)
|
| 128 |
+
assert not f("a", "b", 0)
|
| 129 |
+
pytest.raises(TypeError, f, 1, 2, 3, 4)
|
| 130 |
+
pytest.raises(TypeError, f, 1, 2)
|
| 131 |
+
pytest.raises(TypeError, f, 1)
|
| 132 |
+
pytest.raises(TypeError, f)
|
| 133 |
+
pytest.raises(TypeError, factory, [1, 2, 3])
|
| 134 |
+
pytest.raises(ValueError, factory, [(1, 2)])
|
| 135 |
+
pytest.raises(ValueError, factory, [(1, 2, 3, 4)])
|
| 136 |
+
|
| 137 |
+
def test_hide_multidiedges(self):
|
| 138 |
+
factory = nx.classes.filters.hide_multidiedges
|
| 139 |
+
f = factory([(1, 2, 0), (3, 4, 1), (1, 2, 1)])
|
| 140 |
+
assert not f(1, 2, 0)
|
| 141 |
+
assert not f(1, 2, 1)
|
| 142 |
+
assert f(1, 2, 2)
|
| 143 |
+
assert f(3, 4, 0)
|
| 144 |
+
assert not f(3, 4, 1)
|
| 145 |
+
assert f(4, 3, 1)
|
| 146 |
+
assert f(4, 3, 0)
|
| 147 |
+
assert f(2, 3, 0)
|
| 148 |
+
assert f(0, -1, 0)
|
| 149 |
+
assert f("a", "b", 0)
|
| 150 |
+
pytest.raises(TypeError, f, 1, 2, 3, 4)
|
| 151 |
+
pytest.raises(TypeError, f, 1, 2)
|
| 152 |
+
pytest.raises(TypeError, f, 1)
|
| 153 |
+
pytest.raises(TypeError, f)
|
| 154 |
+
pytest.raises(TypeError, factory, [1, 2, 3])
|
| 155 |
+
pytest.raises(ValueError, factory, [(1, 2)])
|
| 156 |
+
pytest.raises(ValueError, factory, [(1, 2, 3, 4)])
|
| 157 |
+
|
| 158 |
+
def test_show_multidiedges(self):
|
| 159 |
+
factory = nx.classes.filters.show_multidiedges
|
| 160 |
+
f = factory([(1, 2, 0), (3, 4, 1), (1, 2, 1)])
|
| 161 |
+
assert f(1, 2, 0)
|
| 162 |
+
assert f(1, 2, 1)
|
| 163 |
+
assert not f(1, 2, 2)
|
| 164 |
+
assert not f(3, 4, 0)
|
| 165 |
+
assert f(3, 4, 1)
|
| 166 |
+
assert not f(4, 3, 1)
|
| 167 |
+
assert not f(4, 3, 0)
|
| 168 |
+
assert not f(2, 3, 0)
|
| 169 |
+
assert not f(0, -1, 0)
|
| 170 |
+
assert not f("a", "b", 0)
|
| 171 |
+
pytest.raises(TypeError, f, 1, 2, 3, 4)
|
| 172 |
+
pytest.raises(TypeError, f, 1, 2)
|
| 173 |
+
pytest.raises(TypeError, f, 1)
|
| 174 |
+
pytest.raises(TypeError, f)
|
| 175 |
+
pytest.raises(TypeError, factory, [1, 2, 3])
|
| 176 |
+
pytest.raises(ValueError, factory, [(1, 2)])
|
| 177 |
+
pytest.raises(ValueError, factory, [(1, 2, 3, 4)])
|
.venv/lib/python3.11/site-packages/networkx/classes/tests/test_function.py
ADDED
|
@@ -0,0 +1,1035 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
def test_degree_histogram_empty():
|
| 10 |
+
G = nx.Graph()
|
| 11 |
+
assert nx.degree_histogram(G) == []
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class TestFunction:
|
| 15 |
+
def setup_method(self):
|
| 16 |
+
self.G = nx.Graph({0: [1, 2, 3], 1: [1, 2, 0], 4: []}, name="Test")
|
| 17 |
+
self.Gdegree = {0: 3, 1: 2, 2: 2, 3: 1, 4: 0}
|
| 18 |
+
self.Gnodes = list(range(5))
|
| 19 |
+
self.Gedges = [(0, 1), (0, 2), (0, 3), (1, 0), (1, 1), (1, 2)]
|
| 20 |
+
self.DG = nx.DiGraph({0: [1, 2, 3], 1: [1, 2, 0], 4: []})
|
| 21 |
+
self.DGin_degree = {0: 1, 1: 2, 2: 2, 3: 1, 4: 0}
|
| 22 |
+
self.DGout_degree = {0: 3, 1: 3, 2: 0, 3: 0, 4: 0}
|
| 23 |
+
self.DGnodes = list(range(5))
|
| 24 |
+
self.DGedges = [(0, 1), (0, 2), (0, 3), (1, 0), (1, 1), (1, 2)]
|
| 25 |
+
|
| 26 |
+
def test_nodes(self):
|
| 27 |
+
assert nodes_equal(self.G.nodes(), list(nx.nodes(self.G)))
|
| 28 |
+
assert nodes_equal(self.DG.nodes(), list(nx.nodes(self.DG)))
|
| 29 |
+
|
| 30 |
+
def test_edges(self):
|
| 31 |
+
assert edges_equal(self.G.edges(), list(nx.edges(self.G)))
|
| 32 |
+
assert sorted(self.DG.edges()) == sorted(nx.edges(self.DG))
|
| 33 |
+
assert edges_equal(
|
| 34 |
+
self.G.edges(nbunch=[0, 1, 3]), list(nx.edges(self.G, nbunch=[0, 1, 3]))
|
| 35 |
+
)
|
| 36 |
+
assert sorted(self.DG.edges(nbunch=[0, 1, 3])) == sorted(
|
| 37 |
+
nx.edges(self.DG, nbunch=[0, 1, 3])
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
+
def test_degree(self):
|
| 41 |
+
assert edges_equal(self.G.degree(), list(nx.degree(self.G)))
|
| 42 |
+
assert sorted(self.DG.degree()) == sorted(nx.degree(self.DG))
|
| 43 |
+
assert edges_equal(
|
| 44 |
+
self.G.degree(nbunch=[0, 1]), list(nx.degree(self.G, nbunch=[0, 1]))
|
| 45 |
+
)
|
| 46 |
+
assert sorted(self.DG.degree(nbunch=[0, 1])) == sorted(
|
| 47 |
+
nx.degree(self.DG, nbunch=[0, 1])
|
| 48 |
+
)
|
| 49 |
+
assert edges_equal(
|
| 50 |
+
self.G.degree(weight="weight"), list(nx.degree(self.G, weight="weight"))
|
| 51 |
+
)
|
| 52 |
+
assert sorted(self.DG.degree(weight="weight")) == sorted(
|
| 53 |
+
nx.degree(self.DG, weight="weight")
|
| 54 |
+
)
|
| 55 |
+
|
| 56 |
+
def test_neighbors(self):
|
| 57 |
+
assert list(self.G.neighbors(1)) == list(nx.neighbors(self.G, 1))
|
| 58 |
+
assert list(self.DG.neighbors(1)) == list(nx.neighbors(self.DG, 1))
|
| 59 |
+
|
| 60 |
+
def test_number_of_nodes(self):
|
| 61 |
+
assert self.G.number_of_nodes() == nx.number_of_nodes(self.G)
|
| 62 |
+
assert self.DG.number_of_nodes() == nx.number_of_nodes(self.DG)
|
| 63 |
+
|
| 64 |
+
def test_number_of_edges(self):
|
| 65 |
+
assert self.G.number_of_edges() == nx.number_of_edges(self.G)
|
| 66 |
+
assert self.DG.number_of_edges() == nx.number_of_edges(self.DG)
|
| 67 |
+
|
| 68 |
+
def test_is_directed(self):
|
| 69 |
+
assert self.G.is_directed() == nx.is_directed(self.G)
|
| 70 |
+
assert self.DG.is_directed() == nx.is_directed(self.DG)
|
| 71 |
+
|
| 72 |
+
def test_add_star(self):
|
| 73 |
+
G = self.G.copy()
|
| 74 |
+
nlist = [12, 13, 14, 15]
|
| 75 |
+
nx.add_star(G, nlist)
|
| 76 |
+
assert edges_equal(G.edges(nlist), [(12, 13), (12, 14), (12, 15)])
|
| 77 |
+
|
| 78 |
+
G = self.G.copy()
|
| 79 |
+
nx.add_star(G, nlist, weight=2.0)
|
| 80 |
+
assert edges_equal(
|
| 81 |
+
G.edges(nlist, data=True),
|
| 82 |
+
[
|
| 83 |
+
(12, 13, {"weight": 2.0}),
|
| 84 |
+
(12, 14, {"weight": 2.0}),
|
| 85 |
+
(12, 15, {"weight": 2.0}),
|
| 86 |
+
],
|
| 87 |
+
)
|
| 88 |
+
|
| 89 |
+
G = self.G.copy()
|
| 90 |
+
nlist = [12]
|
| 91 |
+
nx.add_star(G, nlist)
|
| 92 |
+
assert nodes_equal(G, list(self.G) + nlist)
|
| 93 |
+
|
| 94 |
+
G = self.G.copy()
|
| 95 |
+
nlist = []
|
| 96 |
+
nx.add_star(G, nlist)
|
| 97 |
+
assert nodes_equal(G.nodes, self.Gnodes)
|
| 98 |
+
assert edges_equal(G.edges, self.G.edges)
|
| 99 |
+
|
| 100 |
+
def test_add_path(self):
|
| 101 |
+
G = self.G.copy()
|
| 102 |
+
nlist = [12, 13, 14, 15]
|
| 103 |
+
nx.add_path(G, nlist)
|
| 104 |
+
assert edges_equal(G.edges(nlist), [(12, 13), (13, 14), (14, 15)])
|
| 105 |
+
G = self.G.copy()
|
| 106 |
+
nx.add_path(G, nlist, weight=2.0)
|
| 107 |
+
assert edges_equal(
|
| 108 |
+
G.edges(nlist, data=True),
|
| 109 |
+
[
|
| 110 |
+
(12, 13, {"weight": 2.0}),
|
| 111 |
+
(13, 14, {"weight": 2.0}),
|
| 112 |
+
(14, 15, {"weight": 2.0}),
|
| 113 |
+
],
|
| 114 |
+
)
|
| 115 |
+
|
| 116 |
+
G = self.G.copy()
|
| 117 |
+
nlist = ["node"]
|
| 118 |
+
nx.add_path(G, nlist)
|
| 119 |
+
assert edges_equal(G.edges(nlist), [])
|
| 120 |
+
assert nodes_equal(G, list(self.G) + ["node"])
|
| 121 |
+
|
| 122 |
+
G = self.G.copy()
|
| 123 |
+
nlist = iter(["node"])
|
| 124 |
+
nx.add_path(G, nlist)
|
| 125 |
+
assert edges_equal(G.edges(["node"]), [])
|
| 126 |
+
assert nodes_equal(G, list(self.G) + ["node"])
|
| 127 |
+
|
| 128 |
+
G = self.G.copy()
|
| 129 |
+
nlist = [12]
|
| 130 |
+
nx.add_path(G, nlist)
|
| 131 |
+
assert edges_equal(G.edges(nlist), [])
|
| 132 |
+
assert nodes_equal(G, list(self.G) + [12])
|
| 133 |
+
|
| 134 |
+
G = self.G.copy()
|
| 135 |
+
nlist = iter([12])
|
| 136 |
+
nx.add_path(G, nlist)
|
| 137 |
+
assert edges_equal(G.edges([12]), [])
|
| 138 |
+
assert nodes_equal(G, list(self.G) + [12])
|
| 139 |
+
|
| 140 |
+
G = self.G.copy()
|
| 141 |
+
nlist = []
|
| 142 |
+
nx.add_path(G, nlist)
|
| 143 |
+
assert edges_equal(G.edges, self.G.edges)
|
| 144 |
+
assert nodes_equal(G, list(self.G))
|
| 145 |
+
|
| 146 |
+
G = self.G.copy()
|
| 147 |
+
nlist = iter([])
|
| 148 |
+
nx.add_path(G, nlist)
|
| 149 |
+
assert edges_equal(G.edges, self.G.edges)
|
| 150 |
+
assert nodes_equal(G, list(self.G))
|
| 151 |
+
|
| 152 |
+
def test_add_cycle(self):
|
| 153 |
+
G = self.G.copy()
|
| 154 |
+
nlist = [12, 13, 14, 15]
|
| 155 |
+
oklists = [
|
| 156 |
+
[(12, 13), (12, 15), (13, 14), (14, 15)],
|
| 157 |
+
[(12, 13), (13, 14), (14, 15), (15, 12)],
|
| 158 |
+
]
|
| 159 |
+
nx.add_cycle(G, nlist)
|
| 160 |
+
assert sorted(G.edges(nlist)) in oklists
|
| 161 |
+
G = self.G.copy()
|
| 162 |
+
oklists = [
|
| 163 |
+
[
|
| 164 |
+
(12, 13, {"weight": 1.0}),
|
| 165 |
+
(12, 15, {"weight": 1.0}),
|
| 166 |
+
(13, 14, {"weight": 1.0}),
|
| 167 |
+
(14, 15, {"weight": 1.0}),
|
| 168 |
+
],
|
| 169 |
+
[
|
| 170 |
+
(12, 13, {"weight": 1.0}),
|
| 171 |
+
(13, 14, {"weight": 1.0}),
|
| 172 |
+
(14, 15, {"weight": 1.0}),
|
| 173 |
+
(15, 12, {"weight": 1.0}),
|
| 174 |
+
],
|
| 175 |
+
]
|
| 176 |
+
nx.add_cycle(G, nlist, weight=1.0)
|
| 177 |
+
assert sorted(G.edges(nlist, data=True)) in oklists
|
| 178 |
+
|
| 179 |
+
G = self.G.copy()
|
| 180 |
+
nlist = [12]
|
| 181 |
+
nx.add_cycle(G, nlist)
|
| 182 |
+
assert nodes_equal(G, list(self.G) + nlist)
|
| 183 |
+
|
| 184 |
+
G = self.G.copy()
|
| 185 |
+
nlist = []
|
| 186 |
+
nx.add_cycle(G, nlist)
|
| 187 |
+
assert nodes_equal(G.nodes, self.Gnodes)
|
| 188 |
+
assert edges_equal(G.edges, self.G.edges)
|
| 189 |
+
|
| 190 |
+
def test_subgraph(self):
|
| 191 |
+
assert (
|
| 192 |
+
self.G.subgraph([0, 1, 2, 4]).adj == nx.subgraph(self.G, [0, 1, 2, 4]).adj
|
| 193 |
+
)
|
| 194 |
+
assert (
|
| 195 |
+
self.DG.subgraph([0, 1, 2, 4]).adj == nx.subgraph(self.DG, [0, 1, 2, 4]).adj
|
| 196 |
+
)
|
| 197 |
+
assert (
|
| 198 |
+
self.G.subgraph([0, 1, 2, 4]).adj
|
| 199 |
+
== nx.induced_subgraph(self.G, [0, 1, 2, 4]).adj
|
| 200 |
+
)
|
| 201 |
+
assert (
|
| 202 |
+
self.DG.subgraph([0, 1, 2, 4]).adj
|
| 203 |
+
== nx.induced_subgraph(self.DG, [0, 1, 2, 4]).adj
|
| 204 |
+
)
|
| 205 |
+
# subgraph-subgraph chain is allowed in function interface
|
| 206 |
+
H = nx.induced_subgraph(self.G.subgraph([0, 1, 2, 4]), [0, 1, 4])
|
| 207 |
+
assert H._graph is not self.G
|
| 208 |
+
assert H.adj == self.G.subgraph([0, 1, 4]).adj
|
| 209 |
+
|
| 210 |
+
def test_edge_subgraph(self):
|
| 211 |
+
assert (
|
| 212 |
+
self.G.edge_subgraph([(1, 2), (0, 3)]).adj
|
| 213 |
+
== nx.edge_subgraph(self.G, [(1, 2), (0, 3)]).adj
|
| 214 |
+
)
|
| 215 |
+
assert (
|
| 216 |
+
self.DG.edge_subgraph([(1, 2), (0, 3)]).adj
|
| 217 |
+
== nx.edge_subgraph(self.DG, [(1, 2), (0, 3)]).adj
|
| 218 |
+
)
|
| 219 |
+
|
| 220 |
+
def test_create_empty_copy(self):
|
| 221 |
+
G = nx.create_empty_copy(self.G, with_data=False)
|
| 222 |
+
assert nodes_equal(G, list(self.G))
|
| 223 |
+
assert G.graph == {}
|
| 224 |
+
assert G._node == {}.fromkeys(self.G.nodes(), {})
|
| 225 |
+
assert G._adj == {}.fromkeys(self.G.nodes(), {})
|
| 226 |
+
G = nx.create_empty_copy(self.G)
|
| 227 |
+
assert nodes_equal(G, list(self.G))
|
| 228 |
+
assert G.graph == self.G.graph
|
| 229 |
+
assert G._node == self.G._node
|
| 230 |
+
assert G._adj == {}.fromkeys(self.G.nodes(), {})
|
| 231 |
+
|
| 232 |
+
def test_degree_histogram(self):
|
| 233 |
+
assert nx.degree_histogram(self.G) == [1, 1, 1, 1, 1]
|
| 234 |
+
|
| 235 |
+
def test_density(self):
|
| 236 |
+
assert nx.density(self.G) == 0.5
|
| 237 |
+
assert nx.density(self.DG) == 0.3
|
| 238 |
+
G = nx.Graph()
|
| 239 |
+
G.add_node(1)
|
| 240 |
+
assert nx.density(G) == 0.0
|
| 241 |
+
|
| 242 |
+
def test_density_selfloop(self):
|
| 243 |
+
G = nx.Graph()
|
| 244 |
+
G.add_edge(1, 1)
|
| 245 |
+
assert nx.density(G) == 0.0
|
| 246 |
+
G.add_edge(1, 2)
|
| 247 |
+
assert nx.density(G) == 2.0
|
| 248 |
+
|
| 249 |
+
def test_freeze(self):
|
| 250 |
+
G = nx.freeze(self.G)
|
| 251 |
+
assert G.frozen
|
| 252 |
+
pytest.raises(nx.NetworkXError, G.add_node, 1)
|
| 253 |
+
pytest.raises(nx.NetworkXError, G.add_nodes_from, [1])
|
| 254 |
+
pytest.raises(nx.NetworkXError, G.remove_node, 1)
|
| 255 |
+
pytest.raises(nx.NetworkXError, G.remove_nodes_from, [1])
|
| 256 |
+
pytest.raises(nx.NetworkXError, G.add_edge, 1, 2)
|
| 257 |
+
pytest.raises(nx.NetworkXError, G.add_edges_from, [(1, 2)])
|
| 258 |
+
pytest.raises(nx.NetworkXError, G.remove_edge, 1, 2)
|
| 259 |
+
pytest.raises(nx.NetworkXError, G.remove_edges_from, [(1, 2)])
|
| 260 |
+
pytest.raises(nx.NetworkXError, G.clear_edges)
|
| 261 |
+
pytest.raises(nx.NetworkXError, G.clear)
|
| 262 |
+
|
| 263 |
+
def test_is_frozen(self):
|
| 264 |
+
assert not nx.is_frozen(self.G)
|
| 265 |
+
G = nx.freeze(self.G)
|
| 266 |
+
assert G.frozen == nx.is_frozen(self.G)
|
| 267 |
+
assert G.frozen
|
| 268 |
+
|
| 269 |
+
def test_node_attributes_are_still_mutable_on_frozen_graph(self):
|
| 270 |
+
G = nx.freeze(nx.path_graph(3))
|
| 271 |
+
node = G.nodes[0]
|
| 272 |
+
node["node_attribute"] = True
|
| 273 |
+
assert node["node_attribute"] == True
|
| 274 |
+
|
| 275 |
+
def test_edge_attributes_are_still_mutable_on_frozen_graph(self):
|
| 276 |
+
G = nx.freeze(nx.path_graph(3))
|
| 277 |
+
edge = G.edges[(0, 1)]
|
| 278 |
+
edge["edge_attribute"] = True
|
| 279 |
+
assert edge["edge_attribute"] == True
|
| 280 |
+
|
| 281 |
+
def test_neighbors_complete_graph(self):
|
| 282 |
+
graph = nx.complete_graph(100)
|
| 283 |
+
pop = random.sample(list(graph), 1)
|
| 284 |
+
nbors = list(nx.neighbors(graph, pop[0]))
|
| 285 |
+
# should be all the other vertices in the graph
|
| 286 |
+
assert len(nbors) == len(graph) - 1
|
| 287 |
+
|
| 288 |
+
graph = nx.path_graph(100)
|
| 289 |
+
node = random.sample(list(graph), 1)[0]
|
| 290 |
+
nbors = list(nx.neighbors(graph, node))
|
| 291 |
+
# should be all the other vertices in the graph
|
| 292 |
+
if node != 0 and node != 99:
|
| 293 |
+
assert len(nbors) == 2
|
| 294 |
+
else:
|
| 295 |
+
assert len(nbors) == 1
|
| 296 |
+
|
| 297 |
+
# create a star graph with 99 outer nodes
|
| 298 |
+
graph = nx.star_graph(99)
|
| 299 |
+
nbors = list(nx.neighbors(graph, 0))
|
| 300 |
+
assert len(nbors) == 99
|
| 301 |
+
|
| 302 |
+
def test_non_neighbors(self):
|
| 303 |
+
graph = nx.complete_graph(100)
|
| 304 |
+
pop = random.sample(list(graph), 1)
|
| 305 |
+
nbors = nx.non_neighbors(graph, pop[0])
|
| 306 |
+
# should be all the other vertices in the graph
|
| 307 |
+
assert len(nbors) == 0
|
| 308 |
+
|
| 309 |
+
graph = nx.path_graph(100)
|
| 310 |
+
node = random.sample(list(graph), 1)[0]
|
| 311 |
+
nbors = nx.non_neighbors(graph, node)
|
| 312 |
+
# should be all the other vertices in the graph
|
| 313 |
+
if node != 0 and node != 99:
|
| 314 |
+
assert len(nbors) == 97
|
| 315 |
+
else:
|
| 316 |
+
assert len(nbors) == 98
|
| 317 |
+
|
| 318 |
+
# create a star graph with 99 outer nodes
|
| 319 |
+
graph = nx.star_graph(99)
|
| 320 |
+
nbors = nx.non_neighbors(graph, 0)
|
| 321 |
+
assert len(nbors) == 0
|
| 322 |
+
|
| 323 |
+
# disconnected graph
|
| 324 |
+
graph = nx.Graph()
|
| 325 |
+
graph.add_nodes_from(range(10))
|
| 326 |
+
nbors = nx.non_neighbors(graph, 0)
|
| 327 |
+
assert len(nbors) == 9
|
| 328 |
+
|
| 329 |
+
def test_non_edges(self):
|
| 330 |
+
# All possible edges exist
|
| 331 |
+
graph = nx.complete_graph(5)
|
| 332 |
+
nedges = list(nx.non_edges(graph))
|
| 333 |
+
assert len(nedges) == 0
|
| 334 |
+
|
| 335 |
+
graph = nx.path_graph(4)
|
| 336 |
+
expected = [(0, 2), (0, 3), (1, 3)]
|
| 337 |
+
nedges = list(nx.non_edges(graph))
|
| 338 |
+
for u, v in expected:
|
| 339 |
+
assert (u, v) in nedges or (v, u) in nedges
|
| 340 |
+
|
| 341 |
+
graph = nx.star_graph(4)
|
| 342 |
+
expected = [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
|
| 343 |
+
nedges = list(nx.non_edges(graph))
|
| 344 |
+
for u, v in expected:
|
| 345 |
+
assert (u, v) in nedges or (v, u) in nedges
|
| 346 |
+
|
| 347 |
+
# Directed graphs
|
| 348 |
+
graph = nx.DiGraph()
|
| 349 |
+
graph.add_edges_from([(0, 2), (2, 0), (2, 1)])
|
| 350 |
+
expected = [(0, 1), (1, 0), (1, 2)]
|
| 351 |
+
nedges = list(nx.non_edges(graph))
|
| 352 |
+
for e in expected:
|
| 353 |
+
assert e in nedges
|
| 354 |
+
|
| 355 |
+
def test_is_weighted(self):
|
| 356 |
+
G = nx.Graph()
|
| 357 |
+
assert not nx.is_weighted(G)
|
| 358 |
+
|
| 359 |
+
G = nx.path_graph(4)
|
| 360 |
+
assert not nx.is_weighted(G)
|
| 361 |
+
assert not nx.is_weighted(G, (2, 3))
|
| 362 |
+
|
| 363 |
+
G.add_node(4)
|
| 364 |
+
G.add_edge(3, 4, weight=4)
|
| 365 |
+
assert not nx.is_weighted(G)
|
| 366 |
+
assert nx.is_weighted(G, (3, 4))
|
| 367 |
+
|
| 368 |
+
G = nx.DiGraph()
|
| 369 |
+
G.add_weighted_edges_from(
|
| 370 |
+
[
|
| 371 |
+
("0", "3", 3),
|
| 372 |
+
("0", "1", -5),
|
| 373 |
+
("1", "0", -5),
|
| 374 |
+
("0", "2", 2),
|
| 375 |
+
("1", "2", 4),
|
| 376 |
+
("2", "3", 1),
|
| 377 |
+
]
|
| 378 |
+
)
|
| 379 |
+
assert nx.is_weighted(G)
|
| 380 |
+
assert nx.is_weighted(G, ("1", "0"))
|
| 381 |
+
|
| 382 |
+
G = G.to_undirected()
|
| 383 |
+
assert nx.is_weighted(G)
|
| 384 |
+
assert nx.is_weighted(G, ("1", "0"))
|
| 385 |
+
|
| 386 |
+
pytest.raises(nx.NetworkXError, nx.is_weighted, G, (1, 2))
|
| 387 |
+
|
| 388 |
+
def test_is_negatively_weighted(self):
|
| 389 |
+
G = nx.Graph()
|
| 390 |
+
assert not nx.is_negatively_weighted(G)
|
| 391 |
+
|
| 392 |
+
G.add_node(1)
|
| 393 |
+
G.add_nodes_from([2, 3, 4, 5])
|
| 394 |
+
assert not nx.is_negatively_weighted(G)
|
| 395 |
+
|
| 396 |
+
G.add_edge(1, 2, weight=4)
|
| 397 |
+
assert not nx.is_negatively_weighted(G, (1, 2))
|
| 398 |
+
|
| 399 |
+
G.add_edges_from([(1, 3), (2, 4), (2, 6)])
|
| 400 |
+
G[1][3]["color"] = "blue"
|
| 401 |
+
assert not nx.is_negatively_weighted(G)
|
| 402 |
+
assert not nx.is_negatively_weighted(G, (1, 3))
|
| 403 |
+
|
| 404 |
+
G[2][4]["weight"] = -2
|
| 405 |
+
assert nx.is_negatively_weighted(G, (2, 4))
|
| 406 |
+
assert nx.is_negatively_weighted(G)
|
| 407 |
+
|
| 408 |
+
G = nx.DiGraph()
|
| 409 |
+
G.add_weighted_edges_from(
|
| 410 |
+
[
|
| 411 |
+
("0", "3", 3),
|
| 412 |
+
("0", "1", -5),
|
| 413 |
+
("1", "0", -2),
|
| 414 |
+
("0", "2", 2),
|
| 415 |
+
("1", "2", -3),
|
| 416 |
+
("2", "3", 1),
|
| 417 |
+
]
|
| 418 |
+
)
|
| 419 |
+
assert nx.is_negatively_weighted(G)
|
| 420 |
+
assert not nx.is_negatively_weighted(G, ("0", "3"))
|
| 421 |
+
assert nx.is_negatively_weighted(G, ("1", "0"))
|
| 422 |
+
|
| 423 |
+
pytest.raises(nx.NetworkXError, nx.is_negatively_weighted, G, (1, 4))
|
| 424 |
+
|
| 425 |
+
|
| 426 |
+
class TestCommonNeighbors:
|
| 427 |
+
@classmethod
|
| 428 |
+
def setup_class(cls):
|
| 429 |
+
cls.func = staticmethod(nx.common_neighbors)
|
| 430 |
+
|
| 431 |
+
def test_func(G, u, v, expected):
|
| 432 |
+
result = sorted(cls.func(G, u, v))
|
| 433 |
+
assert result == expected
|
| 434 |
+
|
| 435 |
+
cls.test = staticmethod(test_func)
|
| 436 |
+
|
| 437 |
+
def test_K5(self):
|
| 438 |
+
G = nx.complete_graph(5)
|
| 439 |
+
self.test(G, 0, 1, [2, 3, 4])
|
| 440 |
+
|
| 441 |
+
def test_P3(self):
|
| 442 |
+
G = nx.path_graph(3)
|
| 443 |
+
self.test(G, 0, 2, [1])
|
| 444 |
+
|
| 445 |
+
def test_S4(self):
|
| 446 |
+
G = nx.star_graph(4)
|
| 447 |
+
self.test(G, 1, 2, [0])
|
| 448 |
+
|
| 449 |
+
def test_digraph(self):
|
| 450 |
+
with pytest.raises(nx.NetworkXNotImplemented):
|
| 451 |
+
G = nx.DiGraph()
|
| 452 |
+
G.add_edges_from([(0, 1), (1, 2)])
|
| 453 |
+
self.func(G, 0, 2)
|
| 454 |
+
|
| 455 |
+
def test_nonexistent_nodes(self):
|
| 456 |
+
G = nx.complete_graph(5)
|
| 457 |
+
pytest.raises(nx.NetworkXError, nx.common_neighbors, G, 5, 4)
|
| 458 |
+
pytest.raises(nx.NetworkXError, nx.common_neighbors, G, 4, 5)
|
| 459 |
+
pytest.raises(nx.NetworkXError, nx.common_neighbors, G, 5, 6)
|
| 460 |
+
|
| 461 |
+
def test_custom1(self):
|
| 462 |
+
"""Case of no common neighbors."""
|
| 463 |
+
G = nx.Graph()
|
| 464 |
+
G.add_nodes_from([0, 1])
|
| 465 |
+
self.test(G, 0, 1, [])
|
| 466 |
+
|
| 467 |
+
def test_custom2(self):
|
| 468 |
+
"""Case of equal nodes."""
|
| 469 |
+
G = nx.complete_graph(4)
|
| 470 |
+
self.test(G, 0, 0, [1, 2, 3])
|
| 471 |
+
|
| 472 |
+
|
| 473 |
+
@pytest.mark.parametrize(
|
| 474 |
+
"graph_type", (nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph)
|
| 475 |
+
)
|
| 476 |
+
def test_set_node_attributes(graph_type):
|
| 477 |
+
# Test single value
|
| 478 |
+
G = nx.path_graph(3, create_using=graph_type)
|
| 479 |
+
vals = 100
|
| 480 |
+
attr = "hello"
|
| 481 |
+
nx.set_node_attributes(G, vals, attr)
|
| 482 |
+
assert G.nodes[0][attr] == vals
|
| 483 |
+
assert G.nodes[1][attr] == vals
|
| 484 |
+
assert G.nodes[2][attr] == vals
|
| 485 |
+
|
| 486 |
+
# Test dictionary
|
| 487 |
+
G = nx.path_graph(3, create_using=graph_type)
|
| 488 |
+
vals = dict(zip(sorted(G.nodes()), range(len(G))))
|
| 489 |
+
attr = "hi"
|
| 490 |
+
nx.set_node_attributes(G, vals, attr)
|
| 491 |
+
assert G.nodes[0][attr] == 0
|
| 492 |
+
assert G.nodes[1][attr] == 1
|
| 493 |
+
assert G.nodes[2][attr] == 2
|
| 494 |
+
|
| 495 |
+
# Test dictionary of dictionaries
|
| 496 |
+
G = nx.path_graph(3, create_using=graph_type)
|
| 497 |
+
d = {"hi": 0, "hello": 200}
|
| 498 |
+
vals = dict.fromkeys(G.nodes(), d)
|
| 499 |
+
vals.pop(0)
|
| 500 |
+
nx.set_node_attributes(G, vals)
|
| 501 |
+
assert G.nodes[0] == {}
|
| 502 |
+
assert G.nodes[1]["hi"] == 0
|
| 503 |
+
assert G.nodes[2]["hello"] == 200
|
| 504 |
+
|
| 505 |
+
|
| 506 |
+
@pytest.mark.parametrize(
|
| 507 |
+
("values", "name"),
|
| 508 |
+
(
|
| 509 |
+
({0: "red", 1: "blue"}, "color"), # values dictionary
|
| 510 |
+
({0: {"color": "red"}, 1: {"color": "blue"}}, None), # dict-of-dict
|
| 511 |
+
),
|
| 512 |
+
)
|
| 513 |
+
def test_set_node_attributes_ignores_extra_nodes(values, name):
|
| 514 |
+
"""
|
| 515 |
+
When `values` is a dict or dict-of-dict keyed by nodes, ensure that keys
|
| 516 |
+
that correspond to nodes not in G are ignored.
|
| 517 |
+
"""
|
| 518 |
+
G = nx.Graph()
|
| 519 |
+
G.add_node(0)
|
| 520 |
+
nx.set_node_attributes(G, values, name)
|
| 521 |
+
assert G.nodes[0]["color"] == "red"
|
| 522 |
+
assert 1 not in G.nodes
|
| 523 |
+
|
| 524 |
+
|
| 525 |
+
@pytest.mark.parametrize("graph_type", (nx.Graph, nx.DiGraph))
|
| 526 |
+
def test_set_edge_attributes(graph_type):
|
| 527 |
+
# Test single value
|
| 528 |
+
G = nx.path_graph(3, create_using=graph_type)
|
| 529 |
+
attr = "hello"
|
| 530 |
+
vals = 3
|
| 531 |
+
nx.set_edge_attributes(G, vals, attr)
|
| 532 |
+
assert G[0][1][attr] == vals
|
| 533 |
+
assert G[1][2][attr] == vals
|
| 534 |
+
|
| 535 |
+
# Test multiple values
|
| 536 |
+
G = nx.path_graph(3, create_using=graph_type)
|
| 537 |
+
attr = "hi"
|
| 538 |
+
edges = [(0, 1), (1, 2)]
|
| 539 |
+
vals = dict(zip(edges, range(len(edges))))
|
| 540 |
+
nx.set_edge_attributes(G, vals, attr)
|
| 541 |
+
assert G[0][1][attr] == 0
|
| 542 |
+
assert G[1][2][attr] == 1
|
| 543 |
+
|
| 544 |
+
# Test dictionary of dictionaries
|
| 545 |
+
G = nx.path_graph(3, create_using=graph_type)
|
| 546 |
+
d = {"hi": 0, "hello": 200}
|
| 547 |
+
edges = [(0, 1)]
|
| 548 |
+
vals = dict.fromkeys(edges, d)
|
| 549 |
+
nx.set_edge_attributes(G, vals)
|
| 550 |
+
assert G[0][1]["hi"] == 0
|
| 551 |
+
assert G[0][1]["hello"] == 200
|
| 552 |
+
assert G[1][2] == {}
|
| 553 |
+
|
| 554 |
+
|
| 555 |
+
@pytest.mark.parametrize(
|
| 556 |
+
("values", "name"),
|
| 557 |
+
(
|
| 558 |
+
({(0, 1): 1.0, (0, 2): 2.0}, "weight"), # values dict
|
| 559 |
+
({(0, 1): {"weight": 1.0}, (0, 2): {"weight": 2.0}}, None), # values dod
|
| 560 |
+
),
|
| 561 |
+
)
|
| 562 |
+
def test_set_edge_attributes_ignores_extra_edges(values, name):
|
| 563 |
+
"""If `values` is a dict or dict-of-dicts containing edges that are not in
|
| 564 |
+
G, data associate with these edges should be ignored.
|
| 565 |
+
"""
|
| 566 |
+
G = nx.Graph([(0, 1)])
|
| 567 |
+
nx.set_edge_attributes(G, values, name)
|
| 568 |
+
assert G[0][1]["weight"] == 1.0
|
| 569 |
+
assert (0, 2) not in G.edges
|
| 570 |
+
|
| 571 |
+
|
| 572 |
+
@pytest.mark.parametrize("graph_type", (nx.MultiGraph, nx.MultiDiGraph))
|
| 573 |
+
def test_set_edge_attributes_multi(graph_type):
|
| 574 |
+
# Test single value
|
| 575 |
+
G = nx.path_graph(3, create_using=graph_type)
|
| 576 |
+
attr = "hello"
|
| 577 |
+
vals = 3
|
| 578 |
+
nx.set_edge_attributes(G, vals, attr)
|
| 579 |
+
assert G[0][1][0][attr] == vals
|
| 580 |
+
assert G[1][2][0][attr] == vals
|
| 581 |
+
|
| 582 |
+
# Test multiple values
|
| 583 |
+
G = nx.path_graph(3, create_using=graph_type)
|
| 584 |
+
attr = "hi"
|
| 585 |
+
edges = [(0, 1, 0), (1, 2, 0)]
|
| 586 |
+
vals = dict(zip(edges, range(len(edges))))
|
| 587 |
+
nx.set_edge_attributes(G, vals, attr)
|
| 588 |
+
assert G[0][1][0][attr] == 0
|
| 589 |
+
assert G[1][2][0][attr] == 1
|
| 590 |
+
|
| 591 |
+
# Test dictionary of dictionaries
|
| 592 |
+
G = nx.path_graph(3, create_using=graph_type)
|
| 593 |
+
d = {"hi": 0, "hello": 200}
|
| 594 |
+
edges = [(0, 1, 0)]
|
| 595 |
+
vals = dict.fromkeys(edges, d)
|
| 596 |
+
nx.set_edge_attributes(G, vals)
|
| 597 |
+
assert G[0][1][0]["hi"] == 0
|
| 598 |
+
assert G[0][1][0]["hello"] == 200
|
| 599 |
+
assert G[1][2][0] == {}
|
| 600 |
+
|
| 601 |
+
|
| 602 |
+
@pytest.mark.parametrize(
|
| 603 |
+
("values", "name"),
|
| 604 |
+
(
|
| 605 |
+
({(0, 1, 0): 1.0, (0, 2, 0): 2.0}, "weight"), # values dict
|
| 606 |
+
({(0, 1, 0): {"weight": 1.0}, (0, 2, 0): {"weight": 2.0}}, None), # values dod
|
| 607 |
+
),
|
| 608 |
+
)
|
| 609 |
+
def test_set_edge_attributes_multi_ignores_extra_edges(values, name):
|
| 610 |
+
"""If `values` is a dict or dict-of-dicts containing edges that are not in
|
| 611 |
+
G, data associate with these edges should be ignored.
|
| 612 |
+
"""
|
| 613 |
+
G = nx.MultiGraph([(0, 1, 0), (0, 1, 1)])
|
| 614 |
+
nx.set_edge_attributes(G, values, name)
|
| 615 |
+
assert G[0][1][0]["weight"] == 1.0
|
| 616 |
+
assert G[0][1][1] == {}
|
| 617 |
+
assert (0, 2) not in G.edges()
|
| 618 |
+
|
| 619 |
+
|
| 620 |
+
def test_get_node_attributes():
|
| 621 |
+
graphs = [nx.Graph(), nx.DiGraph(), nx.MultiGraph(), nx.MultiDiGraph()]
|
| 622 |
+
for G in graphs:
|
| 623 |
+
G = nx.path_graph(3, create_using=G)
|
| 624 |
+
attr = "hello"
|
| 625 |
+
vals = 100
|
| 626 |
+
nx.set_node_attributes(G, vals, attr)
|
| 627 |
+
attrs = nx.get_node_attributes(G, attr)
|
| 628 |
+
assert attrs[0] == vals
|
| 629 |
+
assert attrs[1] == vals
|
| 630 |
+
assert attrs[2] == vals
|
| 631 |
+
default_val = 1
|
| 632 |
+
G.add_node(4)
|
| 633 |
+
attrs = nx.get_node_attributes(G, attr, default=default_val)
|
| 634 |
+
assert attrs[4] == default_val
|
| 635 |
+
|
| 636 |
+
|
| 637 |
+
def test_get_edge_attributes():
|
| 638 |
+
graphs = [nx.Graph(), nx.DiGraph(), nx.MultiGraph(), nx.MultiDiGraph()]
|
| 639 |
+
for G in graphs:
|
| 640 |
+
G = nx.path_graph(3, create_using=G)
|
| 641 |
+
attr = "hello"
|
| 642 |
+
vals = 100
|
| 643 |
+
nx.set_edge_attributes(G, vals, attr)
|
| 644 |
+
attrs = nx.get_edge_attributes(G, attr)
|
| 645 |
+
assert len(attrs) == 2
|
| 646 |
+
|
| 647 |
+
for edge in G.edges:
|
| 648 |
+
assert attrs[edge] == vals
|
| 649 |
+
|
| 650 |
+
default_val = vals
|
| 651 |
+
G.add_edge(4, 5)
|
| 652 |
+
deafult_attrs = nx.get_edge_attributes(G, attr, default=default_val)
|
| 653 |
+
assert len(deafult_attrs) == 3
|
| 654 |
+
|
| 655 |
+
for edge in G.edges:
|
| 656 |
+
assert deafult_attrs[edge] == vals
|
| 657 |
+
|
| 658 |
+
|
| 659 |
+
@pytest.mark.parametrize(
|
| 660 |
+
"graph_type", (nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph)
|
| 661 |
+
)
|
| 662 |
+
def test_remove_node_attributes(graph_type):
|
| 663 |
+
# Test removing single attribute
|
| 664 |
+
G = nx.path_graph(3, create_using=graph_type)
|
| 665 |
+
vals = 100
|
| 666 |
+
attr = "hello"
|
| 667 |
+
nx.set_node_attributes(G, vals, attr)
|
| 668 |
+
nx.remove_node_attributes(G, attr)
|
| 669 |
+
assert attr not in G.nodes[0]
|
| 670 |
+
assert attr not in G.nodes[1]
|
| 671 |
+
assert attr not in G.nodes[2]
|
| 672 |
+
|
| 673 |
+
# Test removing single attribute when multiple present
|
| 674 |
+
G = nx.path_graph(3, create_using=graph_type)
|
| 675 |
+
other_vals = 200
|
| 676 |
+
other_attr = "other"
|
| 677 |
+
nx.set_node_attributes(G, vals, attr)
|
| 678 |
+
nx.set_node_attributes(G, other_vals, other_attr)
|
| 679 |
+
nx.remove_node_attributes(G, attr)
|
| 680 |
+
assert attr not in G.nodes[0]
|
| 681 |
+
assert G.nodes[0][other_attr] == other_vals
|
| 682 |
+
assert attr not in G.nodes[1]
|
| 683 |
+
assert G.nodes[1][other_attr] == other_vals
|
| 684 |
+
assert attr not in G.nodes[2]
|
| 685 |
+
assert G.nodes[2][other_attr] == other_vals
|
| 686 |
+
|
| 687 |
+
# Test removing multiple attributes
|
| 688 |
+
G = nx.path_graph(3, create_using=graph_type)
|
| 689 |
+
nx.set_node_attributes(G, vals, attr)
|
| 690 |
+
nx.set_node_attributes(G, other_vals, other_attr)
|
| 691 |
+
nx.remove_node_attributes(G, attr, other_attr)
|
| 692 |
+
assert attr not in G.nodes[0] and other_attr not in G.nodes[0]
|
| 693 |
+
assert attr not in G.nodes[1] and other_attr not in G.nodes[1]
|
| 694 |
+
assert attr not in G.nodes[2] and other_attr not in G.nodes[2]
|
| 695 |
+
|
| 696 |
+
# Test removing multiple (but not all) attributes
|
| 697 |
+
G = nx.path_graph(3, create_using=graph_type)
|
| 698 |
+
third_vals = 300
|
| 699 |
+
third_attr = "three"
|
| 700 |
+
nx.set_node_attributes(
|
| 701 |
+
G,
|
| 702 |
+
{
|
| 703 |
+
n: {attr: vals, other_attr: other_vals, third_attr: third_vals}
|
| 704 |
+
for n in G.nodes()
|
| 705 |
+
},
|
| 706 |
+
)
|
| 707 |
+
nx.remove_node_attributes(G, other_attr, third_attr)
|
| 708 |
+
assert other_attr not in G.nodes[0] and third_attr not in G.nodes[0]
|
| 709 |
+
assert other_attr not in G.nodes[1] and third_attr not in G.nodes[1]
|
| 710 |
+
assert other_attr not in G.nodes[2] and third_attr not in G.nodes[2]
|
| 711 |
+
assert G.nodes[0][attr] == vals
|
| 712 |
+
assert G.nodes[1][attr] == vals
|
| 713 |
+
assert G.nodes[2][attr] == vals
|
| 714 |
+
|
| 715 |
+
# Test incomplete node attributes
|
| 716 |
+
G = nx.path_graph(3, create_using=graph_type)
|
| 717 |
+
nx.set_node_attributes(
|
| 718 |
+
G,
|
| 719 |
+
{
|
| 720 |
+
1: {attr: vals, other_attr: other_vals},
|
| 721 |
+
2: {attr: vals, other_attr: other_vals},
|
| 722 |
+
},
|
| 723 |
+
)
|
| 724 |
+
nx.remove_node_attributes(G, attr)
|
| 725 |
+
assert attr not in G.nodes[0]
|
| 726 |
+
assert attr not in G.nodes[1]
|
| 727 |
+
assert attr not in G.nodes[2]
|
| 728 |
+
assert G.nodes[1][other_attr] == other_vals
|
| 729 |
+
assert G.nodes[2][other_attr] == other_vals
|
| 730 |
+
|
| 731 |
+
# Test removing on a subset of nodes
|
| 732 |
+
G = nx.path_graph(3, create_using=graph_type)
|
| 733 |
+
nx.set_node_attributes(
|
| 734 |
+
G,
|
| 735 |
+
{
|
| 736 |
+
n: {attr: vals, other_attr: other_vals, third_attr: third_vals}
|
| 737 |
+
for n in G.nodes()
|
| 738 |
+
},
|
| 739 |
+
)
|
| 740 |
+
nx.remove_node_attributes(G, attr, other_attr, nbunch=[0, 1])
|
| 741 |
+
assert attr not in G.nodes[0] and other_attr not in G.nodes[0]
|
| 742 |
+
assert attr not in G.nodes[1] and other_attr not in G.nodes[1]
|
| 743 |
+
assert attr in G.nodes[2] and other_attr in G.nodes[2]
|
| 744 |
+
assert third_attr in G.nodes[0] and G.nodes[0][third_attr] == third_vals
|
| 745 |
+
assert third_attr in G.nodes[1] and G.nodes[1][third_attr] == third_vals
|
| 746 |
+
|
| 747 |
+
|
| 748 |
+
@pytest.mark.parametrize("graph_type", (nx.Graph, nx.DiGraph))
|
| 749 |
+
def test_remove_edge_attributes(graph_type):
|
| 750 |
+
# Test removing single attribute
|
| 751 |
+
G = nx.path_graph(3, create_using=graph_type)
|
| 752 |
+
attr = "hello"
|
| 753 |
+
vals = 100
|
| 754 |
+
nx.set_edge_attributes(G, vals, attr)
|
| 755 |
+
nx.remove_edge_attributes(G, attr)
|
| 756 |
+
assert len(nx.get_edge_attributes(G, attr)) == 0
|
| 757 |
+
|
| 758 |
+
# Test removing only some attributes
|
| 759 |
+
G = nx.path_graph(3, create_using=graph_type)
|
| 760 |
+
other_attr = "other"
|
| 761 |
+
other_vals = 200
|
| 762 |
+
nx.set_edge_attributes(G, vals, attr)
|
| 763 |
+
nx.set_edge_attributes(G, other_vals, other_attr)
|
| 764 |
+
nx.remove_edge_attributes(G, attr)
|
| 765 |
+
|
| 766 |
+
assert attr not in G[0][1]
|
| 767 |
+
assert attr not in G[1][2]
|
| 768 |
+
assert G[0][1][other_attr] == 200
|
| 769 |
+
assert G[1][2][other_attr] == 200
|
| 770 |
+
|
| 771 |
+
# Test removing multiple attributes
|
| 772 |
+
G = nx.path_graph(3, create_using=graph_type)
|
| 773 |
+
nx.set_edge_attributes(G, vals, attr)
|
| 774 |
+
nx.set_edge_attributes(G, other_vals, other_attr)
|
| 775 |
+
nx.remove_edge_attributes(G, attr, other_attr)
|
| 776 |
+
assert attr not in G[0][1] and other_attr not in G[0][1]
|
| 777 |
+
assert attr not in G[1][2] and other_attr not in G[1][2]
|
| 778 |
+
|
| 779 |
+
# Test removing multiple (not all) attributes
|
| 780 |
+
G = nx.path_graph(3, create_using=graph_type)
|
| 781 |
+
third_attr = "third"
|
| 782 |
+
third_vals = 300
|
| 783 |
+
nx.set_edge_attributes(
|
| 784 |
+
G,
|
| 785 |
+
{
|
| 786 |
+
(u, v): {attr: vals, other_attr: other_vals, third_attr: third_vals}
|
| 787 |
+
for u, v in G.edges()
|
| 788 |
+
},
|
| 789 |
+
)
|
| 790 |
+
nx.remove_edge_attributes(G, other_attr, third_attr)
|
| 791 |
+
assert other_attr not in G[0][1] and third_attr not in G[0][1]
|
| 792 |
+
assert other_attr not in G[1][2] and third_attr not in G[1][2]
|
| 793 |
+
assert G[0][1][attr] == vals
|
| 794 |
+
assert G[1][2][attr] == vals
|
| 795 |
+
|
| 796 |
+
# Test removing incomplete edge attributes
|
| 797 |
+
G = nx.path_graph(3, create_using=graph_type)
|
| 798 |
+
nx.set_edge_attributes(G, {(0, 1): {attr: vals, other_attr: other_vals}})
|
| 799 |
+
nx.remove_edge_attributes(G, other_attr)
|
| 800 |
+
assert other_attr not in G[0][1] and G[0][1][attr] == vals
|
| 801 |
+
assert other_attr not in G[1][2]
|
| 802 |
+
|
| 803 |
+
# Test removing subset of edge attributes
|
| 804 |
+
G = nx.path_graph(3, create_using=graph_type)
|
| 805 |
+
nx.set_edge_attributes(
|
| 806 |
+
G,
|
| 807 |
+
{
|
| 808 |
+
(u, v): {attr: vals, other_attr: other_vals, third_attr: third_vals}
|
| 809 |
+
for u, v in G.edges()
|
| 810 |
+
},
|
| 811 |
+
)
|
| 812 |
+
nx.remove_edge_attributes(G, other_attr, third_attr, ebunch=[(0, 1)])
|
| 813 |
+
assert other_attr not in G[0][1] and third_attr not in G[0][1]
|
| 814 |
+
assert other_attr in G[1][2] and third_attr in G[1][2]
|
| 815 |
+
|
| 816 |
+
|
| 817 |
+
@pytest.mark.parametrize("graph_type", (nx.MultiGraph, nx.MultiDiGraph))
|
| 818 |
+
def test_remove_multi_edge_attributes(graph_type):
|
| 819 |
+
# Test removing single attribute
|
| 820 |
+
G = nx.path_graph(3, create_using=graph_type)
|
| 821 |
+
G.add_edge(1, 2)
|
| 822 |
+
attr = "hello"
|
| 823 |
+
vals = 100
|
| 824 |
+
nx.set_edge_attributes(G, vals, attr)
|
| 825 |
+
nx.remove_edge_attributes(G, attr)
|
| 826 |
+
assert attr not in G[0][1][0]
|
| 827 |
+
assert attr not in G[1][2][0]
|
| 828 |
+
assert attr not in G[1][2][1]
|
| 829 |
+
|
| 830 |
+
# Test removing only some attributes
|
| 831 |
+
G = nx.path_graph(3, create_using=graph_type)
|
| 832 |
+
G.add_edge(1, 2)
|
| 833 |
+
other_attr = "other"
|
| 834 |
+
other_vals = 200
|
| 835 |
+
nx.set_edge_attributes(G, vals, attr)
|
| 836 |
+
nx.set_edge_attributes(G, other_vals, other_attr)
|
| 837 |
+
nx.remove_edge_attributes(G, attr)
|
| 838 |
+
assert attr not in G[0][1][0]
|
| 839 |
+
assert attr not in G[1][2][0]
|
| 840 |
+
assert attr not in G[1][2][1]
|
| 841 |
+
assert G[0][1][0][other_attr] == other_vals
|
| 842 |
+
assert G[1][2][0][other_attr] == other_vals
|
| 843 |
+
assert G[1][2][1][other_attr] == other_vals
|
| 844 |
+
|
| 845 |
+
# Test removing multiple attributes
|
| 846 |
+
G = nx.path_graph(3, create_using=graph_type)
|
| 847 |
+
G.add_edge(1, 2)
|
| 848 |
+
nx.set_edge_attributes(G, vals, attr)
|
| 849 |
+
nx.set_edge_attributes(G, other_vals, other_attr)
|
| 850 |
+
nx.remove_edge_attributes(G, attr, other_attr)
|
| 851 |
+
assert attr not in G[0][1][0] and other_attr not in G[0][1][0]
|
| 852 |
+
assert attr not in G[1][2][0] and other_attr not in G[1][2][0]
|
| 853 |
+
assert attr not in G[1][2][1] and other_attr not in G[1][2][1]
|
| 854 |
+
|
| 855 |
+
# Test removing multiple (not all) attributes
|
| 856 |
+
G = nx.path_graph(3, create_using=graph_type)
|
| 857 |
+
G.add_edge(1, 2)
|
| 858 |
+
third_attr = "third"
|
| 859 |
+
third_vals = 300
|
| 860 |
+
nx.set_edge_attributes(
|
| 861 |
+
G,
|
| 862 |
+
{
|
| 863 |
+
(u, v, k): {attr: vals, other_attr: other_vals, third_attr: third_vals}
|
| 864 |
+
for u, v, k in G.edges(keys=True)
|
| 865 |
+
},
|
| 866 |
+
)
|
| 867 |
+
nx.remove_edge_attributes(G, other_attr, third_attr)
|
| 868 |
+
assert other_attr not in G[0][1][0] and third_attr not in G[0][1][0]
|
| 869 |
+
assert other_attr not in G[1][2][0] and other_attr not in G[1][2][0]
|
| 870 |
+
assert other_attr not in G[1][2][1] and other_attr not in G[1][2][1]
|
| 871 |
+
assert G[0][1][0][attr] == vals
|
| 872 |
+
assert G[1][2][0][attr] == vals
|
| 873 |
+
assert G[1][2][1][attr] == vals
|
| 874 |
+
|
| 875 |
+
# Test removing incomplete edge attributes
|
| 876 |
+
G = nx.path_graph(3, create_using=graph_type)
|
| 877 |
+
G.add_edge(1, 2)
|
| 878 |
+
nx.set_edge_attributes(
|
| 879 |
+
G,
|
| 880 |
+
{
|
| 881 |
+
(0, 1, 0): {attr: vals, other_attr: other_vals},
|
| 882 |
+
(1, 2, 1): {attr: vals, other_attr: other_vals},
|
| 883 |
+
},
|
| 884 |
+
)
|
| 885 |
+
nx.remove_edge_attributes(G, other_attr)
|
| 886 |
+
assert other_attr not in G[0][1][0] and G[0][1][0][attr] == vals
|
| 887 |
+
assert other_attr not in G[1][2][0]
|
| 888 |
+
assert other_attr not in G[1][2][1]
|
| 889 |
+
|
| 890 |
+
# Test removing subset of edge attributes
|
| 891 |
+
G = nx.path_graph(3, create_using=graph_type)
|
| 892 |
+
G.add_edge(1, 2)
|
| 893 |
+
nx.set_edge_attributes(
|
| 894 |
+
G,
|
| 895 |
+
{
|
| 896 |
+
(0, 1, 0): {attr: vals, other_attr: other_vals},
|
| 897 |
+
(1, 2, 0): {attr: vals, other_attr: other_vals},
|
| 898 |
+
(1, 2, 1): {attr: vals, other_attr: other_vals},
|
| 899 |
+
},
|
| 900 |
+
)
|
| 901 |
+
nx.remove_edge_attributes(G, attr, ebunch=[(0, 1, 0), (1, 2, 0)])
|
| 902 |
+
assert attr not in G[0][1][0] and other_attr in G[0][1][0]
|
| 903 |
+
assert attr not in G[1][2][0] and other_attr in G[1][2][0]
|
| 904 |
+
assert attr in G[1][2][1] and other_attr in G[1][2][1]
|
| 905 |
+
|
| 906 |
+
|
| 907 |
+
def test_is_empty():
|
| 908 |
+
graphs = [nx.Graph(), nx.DiGraph(), nx.MultiGraph(), nx.MultiDiGraph()]
|
| 909 |
+
for G in graphs:
|
| 910 |
+
assert nx.is_empty(G)
|
| 911 |
+
G.add_nodes_from(range(5))
|
| 912 |
+
assert nx.is_empty(G)
|
| 913 |
+
G.add_edges_from([(1, 2), (3, 4)])
|
| 914 |
+
assert not nx.is_empty(G)
|
| 915 |
+
|
| 916 |
+
|
| 917 |
+
@pytest.mark.parametrize(
|
| 918 |
+
"graph_type", [nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph]
|
| 919 |
+
)
|
| 920 |
+
def test_selfloops(graph_type):
|
| 921 |
+
G = nx.complete_graph(3, create_using=graph_type)
|
| 922 |
+
G.add_edge(0, 0)
|
| 923 |
+
assert nodes_equal(nx.nodes_with_selfloops(G), [0])
|
| 924 |
+
assert edges_equal(nx.selfloop_edges(G), [(0, 0)])
|
| 925 |
+
assert edges_equal(nx.selfloop_edges(G, data=True), [(0, 0, {})])
|
| 926 |
+
assert nx.number_of_selfloops(G) == 1
|
| 927 |
+
|
| 928 |
+
|
| 929 |
+
@pytest.mark.parametrize(
|
| 930 |
+
"graph_type", [nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph]
|
| 931 |
+
)
|
| 932 |
+
def test_selfloop_edges_attr(graph_type):
|
| 933 |
+
G = nx.complete_graph(3, create_using=graph_type)
|
| 934 |
+
G.add_edge(0, 0)
|
| 935 |
+
G.add_edge(1, 1, weight=2)
|
| 936 |
+
assert edges_equal(
|
| 937 |
+
nx.selfloop_edges(G, data=True), [(0, 0, {}), (1, 1, {"weight": 2})]
|
| 938 |
+
)
|
| 939 |
+
assert edges_equal(nx.selfloop_edges(G, data="weight"), [(0, 0, None), (1, 1, 2)])
|
| 940 |
+
|
| 941 |
+
|
| 942 |
+
def test_selfloop_edges_multi_with_data_and_keys():
|
| 943 |
+
G = nx.complete_graph(3, create_using=nx.MultiGraph)
|
| 944 |
+
G.add_edge(0, 0, weight=10)
|
| 945 |
+
G.add_edge(0, 0, weight=100)
|
| 946 |
+
assert edges_equal(
|
| 947 |
+
nx.selfloop_edges(G, data="weight", keys=True), [(0, 0, 0, 10), (0, 0, 1, 100)]
|
| 948 |
+
)
|
| 949 |
+
|
| 950 |
+
|
| 951 |
+
@pytest.mark.parametrize("graph_type", [nx.Graph, nx.DiGraph])
|
| 952 |
+
def test_selfloops_removal(graph_type):
|
| 953 |
+
G = nx.complete_graph(3, create_using=graph_type)
|
| 954 |
+
G.add_edge(0, 0)
|
| 955 |
+
G.remove_edges_from(nx.selfloop_edges(G, keys=True))
|
| 956 |
+
G.add_edge(0, 0)
|
| 957 |
+
G.remove_edges_from(nx.selfloop_edges(G, data=True))
|
| 958 |
+
G.add_edge(0, 0)
|
| 959 |
+
G.remove_edges_from(nx.selfloop_edges(G, keys=True, data=True))
|
| 960 |
+
|
| 961 |
+
|
| 962 |
+
@pytest.mark.parametrize("graph_type", [nx.MultiGraph, nx.MultiDiGraph])
|
| 963 |
+
def test_selfloops_removal_multi(graph_type):
|
| 964 |
+
"""test removing selfloops behavior vis-a-vis altering a dict while iterating.
|
| 965 |
+
cf. gh-4068"""
|
| 966 |
+
G = nx.complete_graph(3, create_using=graph_type)
|
| 967 |
+
# Defaults - see gh-4080
|
| 968 |
+
G.add_edge(0, 0)
|
| 969 |
+
G.add_edge(0, 0)
|
| 970 |
+
G.remove_edges_from(nx.selfloop_edges(G))
|
| 971 |
+
assert (0, 0) not in G.edges()
|
| 972 |
+
# With keys
|
| 973 |
+
G.add_edge(0, 0)
|
| 974 |
+
G.add_edge(0, 0)
|
| 975 |
+
with pytest.raises(RuntimeError):
|
| 976 |
+
G.remove_edges_from(nx.selfloop_edges(G, keys=True))
|
| 977 |
+
# With data
|
| 978 |
+
G.add_edge(0, 0)
|
| 979 |
+
G.add_edge(0, 0)
|
| 980 |
+
with pytest.raises(TypeError):
|
| 981 |
+
G.remove_edges_from(nx.selfloop_edges(G, data=True))
|
| 982 |
+
# With keys and data
|
| 983 |
+
G.add_edge(0, 0)
|
| 984 |
+
G.add_edge(0, 0)
|
| 985 |
+
with pytest.raises(RuntimeError):
|
| 986 |
+
G.remove_edges_from(nx.selfloop_edges(G, data=True, keys=True))
|
| 987 |
+
|
| 988 |
+
|
| 989 |
+
def test_pathweight():
|
| 990 |
+
valid_path = [1, 2, 3]
|
| 991 |
+
invalid_path = [1, 3, 2]
|
| 992 |
+
graphs = [nx.Graph(), nx.DiGraph(), nx.MultiGraph(), nx.MultiDiGraph()]
|
| 993 |
+
edges = [
|
| 994 |
+
(1, 2, {"cost": 5, "dist": 6}),
|
| 995 |
+
(2, 3, {"cost": 3, "dist": 4}),
|
| 996 |
+
(1, 2, {"cost": 1, "dist": 2}),
|
| 997 |
+
]
|
| 998 |
+
for graph in graphs:
|
| 999 |
+
graph.add_edges_from(edges)
|
| 1000 |
+
assert nx.path_weight(graph, valid_path, "cost") == 4
|
| 1001 |
+
assert nx.path_weight(graph, valid_path, "dist") == 6
|
| 1002 |
+
pytest.raises(nx.NetworkXNoPath, nx.path_weight, graph, invalid_path, "cost")
|
| 1003 |
+
|
| 1004 |
+
|
| 1005 |
+
@pytest.mark.parametrize(
|
| 1006 |
+
"G", (nx.Graph(), nx.DiGraph(), nx.MultiGraph(), nx.MultiDiGraph())
|
| 1007 |
+
)
|
| 1008 |
+
def test_ispath(G):
|
| 1009 |
+
G.add_edges_from([(1, 2), (2, 3), (1, 2), (3, 4)])
|
| 1010 |
+
valid_path = [1, 2, 3, 4]
|
| 1011 |
+
invalid_path = [1, 2, 4, 3] # wrong node order
|
| 1012 |
+
another_invalid_path = [1, 2, 3, 4, 5] # contains node not in G
|
| 1013 |
+
assert nx.is_path(G, valid_path)
|
| 1014 |
+
assert not nx.is_path(G, invalid_path)
|
| 1015 |
+
assert not nx.is_path(G, another_invalid_path)
|
| 1016 |
+
|
| 1017 |
+
|
| 1018 |
+
@pytest.mark.parametrize("G", (nx.Graph(), nx.DiGraph()))
|
| 1019 |
+
def test_restricted_view(G):
|
| 1020 |
+
G.add_edges_from([(0, 1), (0, 2), (0, 3), (1, 0), (1, 1), (1, 2)])
|
| 1021 |
+
G.add_node(4)
|
| 1022 |
+
H = nx.restricted_view(G, [0, 2, 5], [(1, 2), (3, 4)])
|
| 1023 |
+
assert set(H.nodes()) == {1, 3, 4}
|
| 1024 |
+
assert set(H.edges()) == {(1, 1)}
|
| 1025 |
+
|
| 1026 |
+
|
| 1027 |
+
@pytest.mark.parametrize("G", (nx.MultiGraph(), nx.MultiDiGraph()))
|
| 1028 |
+
def test_restricted_view_multi(G):
|
| 1029 |
+
G.add_edges_from(
|
| 1030 |
+
[(0, 1, 0), (0, 2, 0), (0, 3, 0), (0, 1, 1), (1, 0, 0), (1, 1, 0), (1, 2, 0)]
|
| 1031 |
+
)
|
| 1032 |
+
G.add_node(4)
|
| 1033 |
+
H = nx.restricted_view(G, [0, 2, 5], [(1, 2, 0), (3, 4, 0)])
|
| 1034 |
+
assert set(H.nodes()) == {1, 3, 4}
|
| 1035 |
+
assert set(H.edges()) == {(1, 1)}
|
.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
|
.venv/lib/python3.11/site-packages/networkx/classes/tests/test_graph_historical.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Original NetworkX graph tests"""
|
| 2 |
+
|
| 3 |
+
import networkx
|
| 4 |
+
import networkx as nx
|
| 5 |
+
|
| 6 |
+
from .historical_tests import HistoricalTests
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class TestGraphHistorical(HistoricalTests):
|
| 10 |
+
@classmethod
|
| 11 |
+
def setup_class(cls):
|
| 12 |
+
HistoricalTests.setup_class()
|
| 13 |
+
cls.G = nx.Graph
|
.venv/lib/python3.11/site-packages/networkx/classes/tests/test_graphviews.py
ADDED
|
@@ -0,0 +1,350 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pytest
|
| 2 |
+
|
| 3 |
+
import networkx as nx
|
| 4 |
+
from networkx.utils import edges_equal, nodes_equal
|
| 5 |
+
|
| 6 |
+
# Note: SubGraph views are not tested here. They have their own testing file
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class TestReverseView:
|
| 10 |
+
def setup_method(self):
|
| 11 |
+
self.G = nx.path_graph(9, create_using=nx.DiGraph())
|
| 12 |
+
self.rv = nx.reverse_view(self.G)
|
| 13 |
+
|
| 14 |
+
def test_pickle(self):
|
| 15 |
+
import pickle
|
| 16 |
+
|
| 17 |
+
rv = self.rv
|
| 18 |
+
prv = pickle.loads(pickle.dumps(rv, -1))
|
| 19 |
+
assert rv._node == prv._node
|
| 20 |
+
assert rv._adj == prv._adj
|
| 21 |
+
assert rv.graph == prv.graph
|
| 22 |
+
|
| 23 |
+
def test_contains(self):
|
| 24 |
+
assert (2, 3) in self.G.edges
|
| 25 |
+
assert (3, 2) not in self.G.edges
|
| 26 |
+
assert (2, 3) not in self.rv.edges
|
| 27 |
+
assert (3, 2) in self.rv.edges
|
| 28 |
+
|
| 29 |
+
def test_iter(self):
|
| 30 |
+
expected = sorted(tuple(reversed(e)) for e in self.G.edges)
|
| 31 |
+
assert sorted(self.rv.edges) == expected
|
| 32 |
+
|
| 33 |
+
def test_exceptions(self):
|
| 34 |
+
G = nx.Graph()
|
| 35 |
+
pytest.raises(nx.NetworkXNotImplemented, nx.reverse_view, G)
|
| 36 |
+
|
| 37 |
+
def test_subclass(self):
|
| 38 |
+
class MyGraph(nx.DiGraph):
|
| 39 |
+
def my_method(self):
|
| 40 |
+
return "me"
|
| 41 |
+
|
| 42 |
+
def to_directed_class(self):
|
| 43 |
+
return MyGraph()
|
| 44 |
+
|
| 45 |
+
M = MyGraph()
|
| 46 |
+
M.add_edge(1, 2)
|
| 47 |
+
RM = nx.reverse_view(M)
|
| 48 |
+
print("RM class", RM.__class__)
|
| 49 |
+
RMC = RM.copy()
|
| 50 |
+
print("RMC class", RMC.__class__)
|
| 51 |
+
print(RMC.edges)
|
| 52 |
+
assert RMC.has_edge(2, 1)
|
| 53 |
+
assert RMC.my_method() == "me"
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
class TestMultiReverseView:
|
| 57 |
+
def setup_method(self):
|
| 58 |
+
self.G = nx.path_graph(9, create_using=nx.MultiDiGraph())
|
| 59 |
+
self.G.add_edge(4, 5)
|
| 60 |
+
self.rv = nx.reverse_view(self.G)
|
| 61 |
+
|
| 62 |
+
def test_pickle(self):
|
| 63 |
+
import pickle
|
| 64 |
+
|
| 65 |
+
rv = self.rv
|
| 66 |
+
prv = pickle.loads(pickle.dumps(rv, -1))
|
| 67 |
+
assert rv._node == prv._node
|
| 68 |
+
assert rv._adj == prv._adj
|
| 69 |
+
assert rv.graph == prv.graph
|
| 70 |
+
|
| 71 |
+
def test_contains(self):
|
| 72 |
+
assert (2, 3, 0) in self.G.edges
|
| 73 |
+
assert (3, 2, 0) not in self.G.edges
|
| 74 |
+
assert (2, 3, 0) not in self.rv.edges
|
| 75 |
+
assert (3, 2, 0) in self.rv.edges
|
| 76 |
+
assert (5, 4, 1) in self.rv.edges
|
| 77 |
+
assert (4, 5, 1) not in self.rv.edges
|
| 78 |
+
|
| 79 |
+
def test_iter(self):
|
| 80 |
+
expected = sorted((v, u, k) for u, v, k in self.G.edges)
|
| 81 |
+
assert sorted(self.rv.edges) == expected
|
| 82 |
+
|
| 83 |
+
def test_exceptions(self):
|
| 84 |
+
MG = nx.MultiGraph(self.G)
|
| 85 |
+
pytest.raises(nx.NetworkXNotImplemented, nx.reverse_view, MG)
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def test_generic_multitype():
|
| 89 |
+
nxg = nx.graphviews
|
| 90 |
+
G = nx.DiGraph([(1, 2)])
|
| 91 |
+
with pytest.raises(nx.NetworkXError):
|
| 92 |
+
nxg.generic_graph_view(G, create_using=nx.MultiGraph)
|
| 93 |
+
G = nx.MultiDiGraph([(1, 2)])
|
| 94 |
+
with pytest.raises(nx.NetworkXError):
|
| 95 |
+
nxg.generic_graph_view(G, create_using=nx.DiGraph)
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
class TestToDirected:
|
| 99 |
+
def setup_method(self):
|
| 100 |
+
self.G = nx.path_graph(9)
|
| 101 |
+
self.dv = nx.to_directed(self.G)
|
| 102 |
+
self.MG = nx.path_graph(9, create_using=nx.MultiGraph())
|
| 103 |
+
self.Mdv = nx.to_directed(self.MG)
|
| 104 |
+
|
| 105 |
+
def test_directed(self):
|
| 106 |
+
assert not self.G.is_directed()
|
| 107 |
+
assert self.dv.is_directed()
|
| 108 |
+
|
| 109 |
+
def test_already_directed(self):
|
| 110 |
+
dd = nx.to_directed(self.dv)
|
| 111 |
+
Mdd = nx.to_directed(self.Mdv)
|
| 112 |
+
assert edges_equal(dd.edges, self.dv.edges)
|
| 113 |
+
assert edges_equal(Mdd.edges, self.Mdv.edges)
|
| 114 |
+
|
| 115 |
+
def test_pickle(self):
|
| 116 |
+
import pickle
|
| 117 |
+
|
| 118 |
+
dv = self.dv
|
| 119 |
+
pdv = pickle.loads(pickle.dumps(dv, -1))
|
| 120 |
+
assert dv._node == pdv._node
|
| 121 |
+
assert dv._succ == pdv._succ
|
| 122 |
+
assert dv._pred == pdv._pred
|
| 123 |
+
assert dv.graph == pdv.graph
|
| 124 |
+
|
| 125 |
+
def test_contains(self):
|
| 126 |
+
assert (2, 3) in self.G.edges
|
| 127 |
+
assert (3, 2) in self.G.edges
|
| 128 |
+
assert (2, 3) in self.dv.edges
|
| 129 |
+
assert (3, 2) in self.dv.edges
|
| 130 |
+
|
| 131 |
+
def test_iter(self):
|
| 132 |
+
revd = [tuple(reversed(e)) for e in self.G.edges]
|
| 133 |
+
expected = sorted(list(self.G.edges) + revd)
|
| 134 |
+
assert sorted(self.dv.edges) == expected
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
class TestToUndirected:
|
| 138 |
+
def setup_method(self):
|
| 139 |
+
self.DG = nx.path_graph(9, create_using=nx.DiGraph())
|
| 140 |
+
self.uv = nx.to_undirected(self.DG)
|
| 141 |
+
self.MDG = nx.path_graph(9, create_using=nx.MultiDiGraph())
|
| 142 |
+
self.Muv = nx.to_undirected(self.MDG)
|
| 143 |
+
|
| 144 |
+
def test_directed(self):
|
| 145 |
+
assert self.DG.is_directed()
|
| 146 |
+
assert not self.uv.is_directed()
|
| 147 |
+
|
| 148 |
+
def test_already_directed(self):
|
| 149 |
+
uu = nx.to_undirected(self.uv)
|
| 150 |
+
Muu = nx.to_undirected(self.Muv)
|
| 151 |
+
assert edges_equal(uu.edges, self.uv.edges)
|
| 152 |
+
assert edges_equal(Muu.edges, self.Muv.edges)
|
| 153 |
+
|
| 154 |
+
def test_pickle(self):
|
| 155 |
+
import pickle
|
| 156 |
+
|
| 157 |
+
uv = self.uv
|
| 158 |
+
puv = pickle.loads(pickle.dumps(uv, -1))
|
| 159 |
+
assert uv._node == puv._node
|
| 160 |
+
assert uv._adj == puv._adj
|
| 161 |
+
assert uv.graph == puv.graph
|
| 162 |
+
assert hasattr(uv, "_graph")
|
| 163 |
+
|
| 164 |
+
def test_contains(self):
|
| 165 |
+
assert (2, 3) in self.DG.edges
|
| 166 |
+
assert (3, 2) not in self.DG.edges
|
| 167 |
+
assert (2, 3) in self.uv.edges
|
| 168 |
+
assert (3, 2) in self.uv.edges
|
| 169 |
+
|
| 170 |
+
def test_iter(self):
|
| 171 |
+
expected = sorted(self.DG.edges)
|
| 172 |
+
assert sorted(self.uv.edges) == expected
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
class TestChainsOfViews:
|
| 176 |
+
@classmethod
|
| 177 |
+
def setup_class(cls):
|
| 178 |
+
cls.G = nx.path_graph(9)
|
| 179 |
+
cls.DG = nx.path_graph(9, create_using=nx.DiGraph())
|
| 180 |
+
cls.MG = nx.path_graph(9, create_using=nx.MultiGraph())
|
| 181 |
+
cls.MDG = nx.path_graph(9, create_using=nx.MultiDiGraph())
|
| 182 |
+
cls.Gv = nx.to_undirected(cls.DG)
|
| 183 |
+
cls.DGv = nx.to_directed(cls.G)
|
| 184 |
+
cls.MGv = nx.to_undirected(cls.MDG)
|
| 185 |
+
cls.MDGv = nx.to_directed(cls.MG)
|
| 186 |
+
cls.Rv = cls.DG.reverse()
|
| 187 |
+
cls.MRv = cls.MDG.reverse()
|
| 188 |
+
cls.graphs = [
|
| 189 |
+
cls.G,
|
| 190 |
+
cls.DG,
|
| 191 |
+
cls.MG,
|
| 192 |
+
cls.MDG,
|
| 193 |
+
cls.Gv,
|
| 194 |
+
cls.DGv,
|
| 195 |
+
cls.MGv,
|
| 196 |
+
cls.MDGv,
|
| 197 |
+
cls.Rv,
|
| 198 |
+
cls.MRv,
|
| 199 |
+
]
|
| 200 |
+
for G in cls.graphs:
|
| 201 |
+
G.edges, G.nodes, G.degree
|
| 202 |
+
|
| 203 |
+
def test_pickle(self):
|
| 204 |
+
import pickle
|
| 205 |
+
|
| 206 |
+
for G in self.graphs:
|
| 207 |
+
H = pickle.loads(pickle.dumps(G, -1))
|
| 208 |
+
assert edges_equal(H.edges, G.edges)
|
| 209 |
+
assert nodes_equal(H.nodes, G.nodes)
|
| 210 |
+
|
| 211 |
+
def test_subgraph_of_subgraph(self):
|
| 212 |
+
SGv = nx.subgraph(self.G, range(3, 7))
|
| 213 |
+
SDGv = nx.subgraph(self.DG, range(3, 7))
|
| 214 |
+
SMGv = nx.subgraph(self.MG, range(3, 7))
|
| 215 |
+
SMDGv = nx.subgraph(self.MDG, range(3, 7))
|
| 216 |
+
for G in self.graphs + [SGv, SDGv, SMGv, SMDGv]:
|
| 217 |
+
SG = nx.induced_subgraph(G, [4, 5, 6])
|
| 218 |
+
assert list(SG) == [4, 5, 6]
|
| 219 |
+
SSG = SG.subgraph([6, 7])
|
| 220 |
+
assert list(SSG) == [6]
|
| 221 |
+
# subgraph-subgraph chain is short-cut in base class method
|
| 222 |
+
assert SSG._graph is G
|
| 223 |
+
|
| 224 |
+
def test_restricted_induced_subgraph_chains(self):
|
| 225 |
+
"""Test subgraph chains that both restrict and show nodes/edges.
|
| 226 |
+
|
| 227 |
+
A restricted_view subgraph should allow induced subgraphs using
|
| 228 |
+
G.subgraph that automagically without a chain (meaning the result
|
| 229 |
+
is a subgraph view of the original graph not a subgraph-of-subgraph.
|
| 230 |
+
"""
|
| 231 |
+
hide_nodes = [3, 4, 5]
|
| 232 |
+
hide_edges = [(6, 7)]
|
| 233 |
+
RG = nx.restricted_view(self.G, hide_nodes, hide_edges)
|
| 234 |
+
nodes = [4, 5, 6, 7, 8]
|
| 235 |
+
SG = nx.induced_subgraph(RG, nodes)
|
| 236 |
+
SSG = RG.subgraph(nodes)
|
| 237 |
+
assert RG._graph is self.G
|
| 238 |
+
assert SSG._graph is self.G
|
| 239 |
+
assert SG._graph is RG
|
| 240 |
+
assert edges_equal(SG.edges, SSG.edges)
|
| 241 |
+
# should be same as morphing the graph
|
| 242 |
+
CG = self.G.copy()
|
| 243 |
+
CG.remove_nodes_from(hide_nodes)
|
| 244 |
+
CG.remove_edges_from(hide_edges)
|
| 245 |
+
assert edges_equal(CG.edges(nodes), SSG.edges)
|
| 246 |
+
CG.remove_nodes_from([0, 1, 2, 3])
|
| 247 |
+
assert edges_equal(CG.edges, SSG.edges)
|
| 248 |
+
# switch order: subgraph first, then restricted view
|
| 249 |
+
SSSG = self.G.subgraph(nodes)
|
| 250 |
+
RSG = nx.restricted_view(SSSG, hide_nodes, hide_edges)
|
| 251 |
+
assert RSG._graph is not self.G
|
| 252 |
+
assert edges_equal(RSG.edges, CG.edges)
|
| 253 |
+
|
| 254 |
+
def test_subgraph_copy(self):
|
| 255 |
+
for origG in self.graphs:
|
| 256 |
+
G = nx.Graph(origG)
|
| 257 |
+
SG = G.subgraph([4, 5, 6])
|
| 258 |
+
H = SG.copy()
|
| 259 |
+
assert type(G) == type(H)
|
| 260 |
+
|
| 261 |
+
def test_subgraph_todirected(self):
|
| 262 |
+
SG = nx.induced_subgraph(self.G, [4, 5, 6])
|
| 263 |
+
SSG = SG.to_directed()
|
| 264 |
+
assert sorted(SSG) == [4, 5, 6]
|
| 265 |
+
assert sorted(SSG.edges) == [(4, 5), (5, 4), (5, 6), (6, 5)]
|
| 266 |
+
|
| 267 |
+
def test_subgraph_toundirected(self):
|
| 268 |
+
SG = nx.induced_subgraph(self.G, [4, 5, 6])
|
| 269 |
+
SSG = SG.to_undirected()
|
| 270 |
+
assert list(SSG) == [4, 5, 6]
|
| 271 |
+
assert sorted(SSG.edges) == [(4, 5), (5, 6)]
|
| 272 |
+
|
| 273 |
+
def test_reverse_subgraph_toundirected(self):
|
| 274 |
+
G = self.DG.reverse(copy=False)
|
| 275 |
+
SG = G.subgraph([4, 5, 6])
|
| 276 |
+
SSG = SG.to_undirected()
|
| 277 |
+
assert list(SSG) == [4, 5, 6]
|
| 278 |
+
assert sorted(SSG.edges) == [(4, 5), (5, 6)]
|
| 279 |
+
|
| 280 |
+
def test_reverse_reverse_copy(self):
|
| 281 |
+
G = self.DG.reverse(copy=False)
|
| 282 |
+
H = G.reverse(copy=True)
|
| 283 |
+
assert H.nodes == self.DG.nodes
|
| 284 |
+
assert H.edges == self.DG.edges
|
| 285 |
+
G = self.MDG.reverse(copy=False)
|
| 286 |
+
H = G.reverse(copy=True)
|
| 287 |
+
assert H.nodes == self.MDG.nodes
|
| 288 |
+
assert H.edges == self.MDG.edges
|
| 289 |
+
|
| 290 |
+
def test_subgraph_edgesubgraph_toundirected(self):
|
| 291 |
+
G = self.G.copy()
|
| 292 |
+
SG = G.subgraph([4, 5, 6])
|
| 293 |
+
SSG = SG.edge_subgraph([(4, 5), (5, 4)])
|
| 294 |
+
USSG = SSG.to_undirected()
|
| 295 |
+
assert list(USSG) == [4, 5]
|
| 296 |
+
assert sorted(USSG.edges) == [(4, 5)]
|
| 297 |
+
|
| 298 |
+
def test_copy_subgraph(self):
|
| 299 |
+
G = self.G.copy()
|
| 300 |
+
SG = G.subgraph([4, 5, 6])
|
| 301 |
+
CSG = SG.copy(as_view=True)
|
| 302 |
+
DCSG = SG.copy(as_view=False)
|
| 303 |
+
assert hasattr(CSG, "_graph") # is a view
|
| 304 |
+
assert not hasattr(DCSG, "_graph") # not a view
|
| 305 |
+
|
| 306 |
+
def test_copy_disubgraph(self):
|
| 307 |
+
G = self.DG.copy()
|
| 308 |
+
SG = G.subgraph([4, 5, 6])
|
| 309 |
+
CSG = SG.copy(as_view=True)
|
| 310 |
+
DCSG = SG.copy(as_view=False)
|
| 311 |
+
assert hasattr(CSG, "_graph") # is a view
|
| 312 |
+
assert not hasattr(DCSG, "_graph") # not a view
|
| 313 |
+
|
| 314 |
+
def test_copy_multidisubgraph(self):
|
| 315 |
+
G = self.MDG.copy()
|
| 316 |
+
SG = G.subgraph([4, 5, 6])
|
| 317 |
+
CSG = SG.copy(as_view=True)
|
| 318 |
+
DCSG = SG.copy(as_view=False)
|
| 319 |
+
assert hasattr(CSG, "_graph") # is a view
|
| 320 |
+
assert not hasattr(DCSG, "_graph") # not a view
|
| 321 |
+
|
| 322 |
+
def test_copy_multisubgraph(self):
|
| 323 |
+
G = self.MG.copy()
|
| 324 |
+
SG = G.subgraph([4, 5, 6])
|
| 325 |
+
CSG = SG.copy(as_view=True)
|
| 326 |
+
DCSG = SG.copy(as_view=False)
|
| 327 |
+
assert hasattr(CSG, "_graph") # is a view
|
| 328 |
+
assert not hasattr(DCSG, "_graph") # not a view
|
| 329 |
+
|
| 330 |
+
def test_copy_of_view(self):
|
| 331 |
+
G = nx.MultiGraph(self.MGv)
|
| 332 |
+
assert G.__class__.__name__ == "MultiGraph"
|
| 333 |
+
G = G.copy(as_view=True)
|
| 334 |
+
assert G.__class__.__name__ == "MultiGraph"
|
| 335 |
+
|
| 336 |
+
def test_subclass(self):
|
| 337 |
+
class MyGraph(nx.DiGraph):
|
| 338 |
+
def my_method(self):
|
| 339 |
+
return "me"
|
| 340 |
+
|
| 341 |
+
def to_directed_class(self):
|
| 342 |
+
return MyGraph()
|
| 343 |
+
|
| 344 |
+
for origG in self.graphs:
|
| 345 |
+
G = MyGraph(origG)
|
| 346 |
+
SG = G.subgraph([4, 5, 6])
|
| 347 |
+
H = SG.copy()
|
| 348 |
+
assert SG.my_method() == "me"
|
| 349 |
+
assert H.my_method() == "me"
|
| 350 |
+
assert 3 not in H or 3 in SG
|
.venv/lib/python3.11/site-packages/networkx/classes/tests/test_multidigraph.py
ADDED
|
@@ -0,0 +1,459 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from collections import UserDict
|
| 2 |
+
|
| 3 |
+
import pytest
|
| 4 |
+
|
| 5 |
+
import networkx as nx
|
| 6 |
+
from networkx.utils import edges_equal
|
| 7 |
+
|
| 8 |
+
from .test_multigraph import BaseMultiGraphTester
|
| 9 |
+
from .test_multigraph import TestEdgeSubgraph as _TestMultiGraphEdgeSubgraph
|
| 10 |
+
from .test_multigraph import TestMultiGraph as _TestMultiGraph
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class BaseMultiDiGraphTester(BaseMultiGraphTester):
|
| 14 |
+
def test_edges(self):
|
| 15 |
+
G = self.K3
|
| 16 |
+
edges = [(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)]
|
| 17 |
+
assert sorted(G.edges()) == edges
|
| 18 |
+
assert sorted(G.edges(0)) == [(0, 1), (0, 2)]
|
| 19 |
+
pytest.raises((KeyError, nx.NetworkXError), G.edges, -1)
|
| 20 |
+
|
| 21 |
+
def test_edges_data(self):
|
| 22 |
+
G = self.K3
|
| 23 |
+
edges = [(0, 1, {}), (0, 2, {}), (1, 0, {}), (1, 2, {}), (2, 0, {}), (2, 1, {})]
|
| 24 |
+
assert sorted(G.edges(data=True)) == edges
|
| 25 |
+
assert sorted(G.edges(0, data=True)) == [(0, 1, {}), (0, 2, {})]
|
| 26 |
+
pytest.raises((KeyError, nx.NetworkXError), G.neighbors, -1)
|
| 27 |
+
|
| 28 |
+
def test_edges_multi(self):
|
| 29 |
+
G = self.K3
|
| 30 |
+
assert sorted(G.edges()) == [(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)]
|
| 31 |
+
assert sorted(G.edges(0)) == [(0, 1), (0, 2)]
|
| 32 |
+
G.add_edge(0, 1)
|
| 33 |
+
assert sorted(G.edges()) == [
|
| 34 |
+
(0, 1),
|
| 35 |
+
(0, 1),
|
| 36 |
+
(0, 2),
|
| 37 |
+
(1, 0),
|
| 38 |
+
(1, 2),
|
| 39 |
+
(2, 0),
|
| 40 |
+
(2, 1),
|
| 41 |
+
]
|
| 42 |
+
|
| 43 |
+
def test_out_edges(self):
|
| 44 |
+
G = self.K3
|
| 45 |
+
assert sorted(G.out_edges()) == [(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)]
|
| 46 |
+
assert sorted(G.out_edges(0)) == [(0, 1), (0, 2)]
|
| 47 |
+
pytest.raises((KeyError, nx.NetworkXError), G.out_edges, -1)
|
| 48 |
+
assert sorted(G.out_edges(0, keys=True)) == [(0, 1, 0), (0, 2, 0)]
|
| 49 |
+
|
| 50 |
+
def test_out_edges_multi(self):
|
| 51 |
+
G = self.K3
|
| 52 |
+
assert sorted(G.out_edges()) == [(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)]
|
| 53 |
+
assert sorted(G.out_edges(0)) == [(0, 1), (0, 2)]
|
| 54 |
+
G.add_edge(0, 1, 2)
|
| 55 |
+
assert sorted(G.out_edges()) == [
|
| 56 |
+
(0, 1),
|
| 57 |
+
(0, 1),
|
| 58 |
+
(0, 2),
|
| 59 |
+
(1, 0),
|
| 60 |
+
(1, 2),
|
| 61 |
+
(2, 0),
|
| 62 |
+
(2, 1),
|
| 63 |
+
]
|
| 64 |
+
|
| 65 |
+
def test_out_edges_data(self):
|
| 66 |
+
G = self.K3
|
| 67 |
+
assert sorted(G.edges(0, data=True)) == [(0, 1, {}), (0, 2, {})]
|
| 68 |
+
G.remove_edge(0, 1)
|
| 69 |
+
G.add_edge(0, 1, data=1)
|
| 70 |
+
assert sorted(G.edges(0, data=True)) == [(0, 1, {"data": 1}), (0, 2, {})]
|
| 71 |
+
assert sorted(G.edges(0, data="data")) == [(0, 1, 1), (0, 2, None)]
|
| 72 |
+
assert sorted(G.edges(0, data="data", default=-1)) == [(0, 1, 1), (0, 2, -1)]
|
| 73 |
+
|
| 74 |
+
def test_in_edges(self):
|
| 75 |
+
G = self.K3
|
| 76 |
+
assert sorted(G.in_edges()) == [(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)]
|
| 77 |
+
assert sorted(G.in_edges(0)) == [(1, 0), (2, 0)]
|
| 78 |
+
pytest.raises((KeyError, nx.NetworkXError), G.in_edges, -1)
|
| 79 |
+
G.add_edge(0, 1, 2)
|
| 80 |
+
assert sorted(G.in_edges()) == [
|
| 81 |
+
(0, 1),
|
| 82 |
+
(0, 1),
|
| 83 |
+
(0, 2),
|
| 84 |
+
(1, 0),
|
| 85 |
+
(1, 2),
|
| 86 |
+
(2, 0),
|
| 87 |
+
(2, 1),
|
| 88 |
+
]
|
| 89 |
+
assert sorted(G.in_edges(0, keys=True)) == [(1, 0, 0), (2, 0, 0)]
|
| 90 |
+
|
| 91 |
+
def test_in_edges_no_keys(self):
|
| 92 |
+
G = self.K3
|
| 93 |
+
assert sorted(G.in_edges()) == [(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)]
|
| 94 |
+
assert sorted(G.in_edges(0)) == [(1, 0), (2, 0)]
|
| 95 |
+
G.add_edge(0, 1, 2)
|
| 96 |
+
assert sorted(G.in_edges()) == [
|
| 97 |
+
(0, 1),
|
| 98 |
+
(0, 1),
|
| 99 |
+
(0, 2),
|
| 100 |
+
(1, 0),
|
| 101 |
+
(1, 2),
|
| 102 |
+
(2, 0),
|
| 103 |
+
(2, 1),
|
| 104 |
+
]
|
| 105 |
+
|
| 106 |
+
assert sorted(G.in_edges(data=True, keys=False)) == [
|
| 107 |
+
(0, 1, {}),
|
| 108 |
+
(0, 1, {}),
|
| 109 |
+
(0, 2, {}),
|
| 110 |
+
(1, 0, {}),
|
| 111 |
+
(1, 2, {}),
|
| 112 |
+
(2, 0, {}),
|
| 113 |
+
(2, 1, {}),
|
| 114 |
+
]
|
| 115 |
+
|
| 116 |
+
def test_in_edges_data(self):
|
| 117 |
+
G = self.K3
|
| 118 |
+
assert sorted(G.in_edges(0, data=True)) == [(1, 0, {}), (2, 0, {})]
|
| 119 |
+
G.remove_edge(1, 0)
|
| 120 |
+
G.add_edge(1, 0, data=1)
|
| 121 |
+
assert sorted(G.in_edges(0, data=True)) == [(1, 0, {"data": 1}), (2, 0, {})]
|
| 122 |
+
assert sorted(G.in_edges(0, data="data")) == [(1, 0, 1), (2, 0, None)]
|
| 123 |
+
assert sorted(G.in_edges(0, data="data", default=-1)) == [(1, 0, 1), (2, 0, -1)]
|
| 124 |
+
|
| 125 |
+
def is_shallow(self, H, G):
|
| 126 |
+
# graph
|
| 127 |
+
assert G.graph["foo"] == H.graph["foo"]
|
| 128 |
+
G.graph["foo"].append(1)
|
| 129 |
+
assert G.graph["foo"] == H.graph["foo"]
|
| 130 |
+
# node
|
| 131 |
+
assert G.nodes[0]["foo"] == H.nodes[0]["foo"]
|
| 132 |
+
G.nodes[0]["foo"].append(1)
|
| 133 |
+
assert G.nodes[0]["foo"] == H.nodes[0]["foo"]
|
| 134 |
+
# edge
|
| 135 |
+
assert G[1][2][0]["foo"] == H[1][2][0]["foo"]
|
| 136 |
+
G[1][2][0]["foo"].append(1)
|
| 137 |
+
assert G[1][2][0]["foo"] == H[1][2][0]["foo"]
|
| 138 |
+
|
| 139 |
+
def is_deep(self, H, G):
|
| 140 |
+
# graph
|
| 141 |
+
assert G.graph["foo"] == H.graph["foo"]
|
| 142 |
+
G.graph["foo"].append(1)
|
| 143 |
+
assert G.graph["foo"] != H.graph["foo"]
|
| 144 |
+
# node
|
| 145 |
+
assert G.nodes[0]["foo"] == H.nodes[0]["foo"]
|
| 146 |
+
G.nodes[0]["foo"].append(1)
|
| 147 |
+
assert G.nodes[0]["foo"] != H.nodes[0]["foo"]
|
| 148 |
+
# edge
|
| 149 |
+
assert G[1][2][0]["foo"] == H[1][2][0]["foo"]
|
| 150 |
+
G[1][2][0]["foo"].append(1)
|
| 151 |
+
assert G[1][2][0]["foo"] != H[1][2][0]["foo"]
|
| 152 |
+
|
| 153 |
+
def test_to_undirected(self):
|
| 154 |
+
# MultiDiGraph -> MultiGraph changes number of edges so it is
|
| 155 |
+
# not a copy operation... use is_shallow, not is_shallow_copy
|
| 156 |
+
G = self.K3
|
| 157 |
+
self.add_attributes(G)
|
| 158 |
+
H = nx.MultiGraph(G)
|
| 159 |
+
# self.is_shallow(H,G)
|
| 160 |
+
# the result is traversal order dependent so we
|
| 161 |
+
# can't use the is_shallow() test here.
|
| 162 |
+
try:
|
| 163 |
+
assert edges_equal(H.edges(), [(0, 1), (1, 2), (2, 0)])
|
| 164 |
+
except AssertionError:
|
| 165 |
+
assert edges_equal(H.edges(), [(0, 1), (1, 2), (1, 2), (2, 0)])
|
| 166 |
+
H = G.to_undirected()
|
| 167 |
+
self.is_deep(H, G)
|
| 168 |
+
|
| 169 |
+
def test_has_successor(self):
|
| 170 |
+
G = self.K3
|
| 171 |
+
assert G.has_successor(0, 1)
|
| 172 |
+
assert not G.has_successor(0, -1)
|
| 173 |
+
|
| 174 |
+
def test_successors(self):
|
| 175 |
+
G = self.K3
|
| 176 |
+
assert sorted(G.successors(0)) == [1, 2]
|
| 177 |
+
pytest.raises((KeyError, nx.NetworkXError), G.successors, -1)
|
| 178 |
+
|
| 179 |
+
def test_has_predecessor(self):
|
| 180 |
+
G = self.K3
|
| 181 |
+
assert G.has_predecessor(0, 1)
|
| 182 |
+
assert not G.has_predecessor(0, -1)
|
| 183 |
+
|
| 184 |
+
def test_predecessors(self):
|
| 185 |
+
G = self.K3
|
| 186 |
+
assert sorted(G.predecessors(0)) == [1, 2]
|
| 187 |
+
pytest.raises((KeyError, nx.NetworkXError), G.predecessors, -1)
|
| 188 |
+
|
| 189 |
+
def test_degree(self):
|
| 190 |
+
G = self.K3
|
| 191 |
+
assert sorted(G.degree()) == [(0, 4), (1, 4), (2, 4)]
|
| 192 |
+
assert dict(G.degree()) == {0: 4, 1: 4, 2: 4}
|
| 193 |
+
assert G.degree(0) == 4
|
| 194 |
+
assert list(G.degree(iter([0]))) == [(0, 4)]
|
| 195 |
+
G.add_edge(0, 1, weight=0.3, other=1.2)
|
| 196 |
+
assert sorted(G.degree(weight="weight")) == [(0, 4.3), (1, 4.3), (2, 4)]
|
| 197 |
+
assert sorted(G.degree(weight="other")) == [(0, 5.2), (1, 5.2), (2, 4)]
|
| 198 |
+
|
| 199 |
+
def test_in_degree(self):
|
| 200 |
+
G = self.K3
|
| 201 |
+
assert sorted(G.in_degree()) == [(0, 2), (1, 2), (2, 2)]
|
| 202 |
+
assert dict(G.in_degree()) == {0: 2, 1: 2, 2: 2}
|
| 203 |
+
assert G.in_degree(0) == 2
|
| 204 |
+
assert list(G.in_degree(iter([0]))) == [(0, 2)]
|
| 205 |
+
assert G.in_degree(0, weight="weight") == 2
|
| 206 |
+
|
| 207 |
+
def test_out_degree(self):
|
| 208 |
+
G = self.K3
|
| 209 |
+
assert sorted(G.out_degree()) == [(0, 2), (1, 2), (2, 2)]
|
| 210 |
+
assert dict(G.out_degree()) == {0: 2, 1: 2, 2: 2}
|
| 211 |
+
assert G.out_degree(0) == 2
|
| 212 |
+
assert list(G.out_degree(iter([0]))) == [(0, 2)]
|
| 213 |
+
assert G.out_degree(0, weight="weight") == 2
|
| 214 |
+
|
| 215 |
+
def test_size(self):
|
| 216 |
+
G = self.K3
|
| 217 |
+
assert G.size() == 6
|
| 218 |
+
assert G.number_of_edges() == 6
|
| 219 |
+
G.add_edge(0, 1, weight=0.3, other=1.2)
|
| 220 |
+
assert round(G.size(weight="weight"), 2) == 6.3
|
| 221 |
+
assert round(G.size(weight="other"), 2) == 7.2
|
| 222 |
+
|
| 223 |
+
def test_to_undirected_reciprocal(self):
|
| 224 |
+
G = self.Graph()
|
| 225 |
+
G.add_edge(1, 2)
|
| 226 |
+
assert G.to_undirected().has_edge(1, 2)
|
| 227 |
+
assert not G.to_undirected(reciprocal=True).has_edge(1, 2)
|
| 228 |
+
G.add_edge(2, 1)
|
| 229 |
+
assert G.to_undirected(reciprocal=True).has_edge(1, 2)
|
| 230 |
+
|
| 231 |
+
def test_reverse_copy(self):
|
| 232 |
+
G = nx.MultiDiGraph([(0, 1), (0, 1)])
|
| 233 |
+
R = G.reverse()
|
| 234 |
+
assert sorted(R.edges()) == [(1, 0), (1, 0)]
|
| 235 |
+
R.remove_edge(1, 0)
|
| 236 |
+
assert sorted(R.edges()) == [(1, 0)]
|
| 237 |
+
assert sorted(G.edges()) == [(0, 1), (0, 1)]
|
| 238 |
+
|
| 239 |
+
def test_reverse_nocopy(self):
|
| 240 |
+
G = nx.MultiDiGraph([(0, 1), (0, 1)])
|
| 241 |
+
R = G.reverse(copy=False)
|
| 242 |
+
assert sorted(R.edges()) == [(1, 0), (1, 0)]
|
| 243 |
+
pytest.raises(nx.NetworkXError, R.remove_edge, 1, 0)
|
| 244 |
+
|
| 245 |
+
def test_di_attributes_cached(self):
|
| 246 |
+
G = self.K3.copy()
|
| 247 |
+
assert id(G.in_edges) == id(G.in_edges)
|
| 248 |
+
assert id(G.out_edges) == id(G.out_edges)
|
| 249 |
+
assert id(G.in_degree) == id(G.in_degree)
|
| 250 |
+
assert id(G.out_degree) == id(G.out_degree)
|
| 251 |
+
assert id(G.succ) == id(G.succ)
|
| 252 |
+
assert id(G.pred) == id(G.pred)
|
| 253 |
+
|
| 254 |
+
|
| 255 |
+
class TestMultiDiGraph(BaseMultiDiGraphTester, _TestMultiGraph):
|
| 256 |
+
def setup_method(self):
|
| 257 |
+
self.Graph = nx.MultiDiGraph
|
| 258 |
+
# build K3
|
| 259 |
+
self.k3edges = [(0, 1), (0, 2), (1, 2)]
|
| 260 |
+
self.k3nodes = [0, 1, 2]
|
| 261 |
+
self.K3 = self.Graph()
|
| 262 |
+
self.K3._succ = {0: {}, 1: {}, 2: {}}
|
| 263 |
+
# K3._adj is synced with K3._succ
|
| 264 |
+
self.K3._pred = {0: {}, 1: {}, 2: {}}
|
| 265 |
+
for u in self.k3nodes:
|
| 266 |
+
for v in self.k3nodes:
|
| 267 |
+
if u == v:
|
| 268 |
+
continue
|
| 269 |
+
d = {0: {}}
|
| 270 |
+
self.K3._succ[u][v] = d
|
| 271 |
+
self.K3._pred[v][u] = d
|
| 272 |
+
self.K3._node = {}
|
| 273 |
+
self.K3._node[0] = {}
|
| 274 |
+
self.K3._node[1] = {}
|
| 275 |
+
self.K3._node[2] = {}
|
| 276 |
+
|
| 277 |
+
def test_add_edge(self):
|
| 278 |
+
G = self.Graph()
|
| 279 |
+
G.add_edge(0, 1)
|
| 280 |
+
assert G._adj == {0: {1: {0: {}}}, 1: {}}
|
| 281 |
+
assert G._succ == {0: {1: {0: {}}}, 1: {}}
|
| 282 |
+
assert G._pred == {0: {}, 1: {0: {0: {}}}}
|
| 283 |
+
G = self.Graph()
|
| 284 |
+
G.add_edge(*(0, 1))
|
| 285 |
+
assert G._adj == {0: {1: {0: {}}}, 1: {}}
|
| 286 |
+
assert G._succ == {0: {1: {0: {}}}, 1: {}}
|
| 287 |
+
assert G._pred == {0: {}, 1: {0: {0: {}}}}
|
| 288 |
+
with pytest.raises(ValueError, match="None cannot be a node"):
|
| 289 |
+
G.add_edge(None, 3)
|
| 290 |
+
|
| 291 |
+
def test_add_edges_from(self):
|
| 292 |
+
G = self.Graph()
|
| 293 |
+
G.add_edges_from([(0, 1), (0, 1, {"weight": 3})])
|
| 294 |
+
assert G._adj == {0: {1: {0: {}, 1: {"weight": 3}}}, 1: {}}
|
| 295 |
+
assert G._succ == {0: {1: {0: {}, 1: {"weight": 3}}}, 1: {}}
|
| 296 |
+
assert G._pred == {0: {}, 1: {0: {0: {}, 1: {"weight": 3}}}}
|
| 297 |
+
|
| 298 |
+
G.add_edges_from([(0, 1), (0, 1, {"weight": 3})], weight=2)
|
| 299 |
+
assert G._succ == {
|
| 300 |
+
0: {1: {0: {}, 1: {"weight": 3}, 2: {"weight": 2}, 3: {"weight": 3}}},
|
| 301 |
+
1: {},
|
| 302 |
+
}
|
| 303 |
+
assert G._pred == {
|
| 304 |
+
0: {},
|
| 305 |
+
1: {0: {0: {}, 1: {"weight": 3}, 2: {"weight": 2}, 3: {"weight": 3}}},
|
| 306 |
+
}
|
| 307 |
+
|
| 308 |
+
G = self.Graph()
|
| 309 |
+
edges = [
|
| 310 |
+
(0, 1, {"weight": 3}),
|
| 311 |
+
(0, 1, (("weight", 2),)),
|
| 312 |
+
(0, 1, 5),
|
| 313 |
+
(0, 1, "s"),
|
| 314 |
+
]
|
| 315 |
+
G.add_edges_from(edges)
|
| 316 |
+
keydict = {0: {"weight": 3}, 1: {"weight": 2}, 5: {}, "s": {}}
|
| 317 |
+
assert G._succ == {0: {1: keydict}, 1: {}}
|
| 318 |
+
assert G._pred == {1: {0: keydict}, 0: {}}
|
| 319 |
+
|
| 320 |
+
# too few in tuple
|
| 321 |
+
pytest.raises(nx.NetworkXError, G.add_edges_from, [(0,)])
|
| 322 |
+
# too many in tuple
|
| 323 |
+
pytest.raises(nx.NetworkXError, G.add_edges_from, [(0, 1, 2, 3, 4)])
|
| 324 |
+
# not a tuple
|
| 325 |
+
pytest.raises(TypeError, G.add_edges_from, [0])
|
| 326 |
+
with pytest.raises(ValueError, match="None cannot be a node"):
|
| 327 |
+
G.add_edges_from([(None, 3), (3, 2)])
|
| 328 |
+
|
| 329 |
+
def test_remove_edge(self):
|
| 330 |
+
G = self.K3
|
| 331 |
+
G.remove_edge(0, 1)
|
| 332 |
+
assert G._succ == {
|
| 333 |
+
0: {2: {0: {}}},
|
| 334 |
+
1: {0: {0: {}}, 2: {0: {}}},
|
| 335 |
+
2: {0: {0: {}}, 1: {0: {}}},
|
| 336 |
+
}
|
| 337 |
+
assert G._pred == {
|
| 338 |
+
0: {1: {0: {}}, 2: {0: {}}},
|
| 339 |
+
1: {2: {0: {}}},
|
| 340 |
+
2: {0: {0: {}}, 1: {0: {}}},
|
| 341 |
+
}
|
| 342 |
+
pytest.raises((KeyError, nx.NetworkXError), G.remove_edge, -1, 0)
|
| 343 |
+
pytest.raises((KeyError, nx.NetworkXError), G.remove_edge, 0, 2, key=1)
|
| 344 |
+
|
| 345 |
+
def test_remove_multiedge(self):
|
| 346 |
+
G = self.K3
|
| 347 |
+
G.add_edge(0, 1, key="parallel edge")
|
| 348 |
+
G.remove_edge(0, 1, key="parallel edge")
|
| 349 |
+
assert G._adj == {
|
| 350 |
+
0: {1: {0: {}}, 2: {0: {}}},
|
| 351 |
+
1: {0: {0: {}}, 2: {0: {}}},
|
| 352 |
+
2: {0: {0: {}}, 1: {0: {}}},
|
| 353 |
+
}
|
| 354 |
+
|
| 355 |
+
assert G._succ == {
|
| 356 |
+
0: {1: {0: {}}, 2: {0: {}}},
|
| 357 |
+
1: {0: {0: {}}, 2: {0: {}}},
|
| 358 |
+
2: {0: {0: {}}, 1: {0: {}}},
|
| 359 |
+
}
|
| 360 |
+
|
| 361 |
+
assert G._pred == {
|
| 362 |
+
0: {1: {0: {}}, 2: {0: {}}},
|
| 363 |
+
1: {0: {0: {}}, 2: {0: {}}},
|
| 364 |
+
2: {0: {0: {}}, 1: {0: {}}},
|
| 365 |
+
}
|
| 366 |
+
G.remove_edge(0, 1)
|
| 367 |
+
assert G._succ == {
|
| 368 |
+
0: {2: {0: {}}},
|
| 369 |
+
1: {0: {0: {}}, 2: {0: {}}},
|
| 370 |
+
2: {0: {0: {}}, 1: {0: {}}},
|
| 371 |
+
}
|
| 372 |
+
assert G._pred == {
|
| 373 |
+
0: {1: {0: {}}, 2: {0: {}}},
|
| 374 |
+
1: {2: {0: {}}},
|
| 375 |
+
2: {0: {0: {}}, 1: {0: {}}},
|
| 376 |
+
}
|
| 377 |
+
pytest.raises((KeyError, nx.NetworkXError), G.remove_edge, -1, 0)
|
| 378 |
+
|
| 379 |
+
def test_remove_edges_from(self):
|
| 380 |
+
G = self.K3
|
| 381 |
+
G.remove_edges_from([(0, 1)])
|
| 382 |
+
assert G._succ == {
|
| 383 |
+
0: {2: {0: {}}},
|
| 384 |
+
1: {0: {0: {}}, 2: {0: {}}},
|
| 385 |
+
2: {0: {0: {}}, 1: {0: {}}},
|
| 386 |
+
}
|
| 387 |
+
assert G._pred == {
|
| 388 |
+
0: {1: {0: {}}, 2: {0: {}}},
|
| 389 |
+
1: {2: {0: {}}},
|
| 390 |
+
2: {0: {0: {}}, 1: {0: {}}},
|
| 391 |
+
}
|
| 392 |
+
G.remove_edges_from([(0, 0)]) # silent fail
|
| 393 |
+
|
| 394 |
+
|
| 395 |
+
class TestEdgeSubgraph(_TestMultiGraphEdgeSubgraph):
|
| 396 |
+
"""Unit tests for the :meth:`MultiDiGraph.edge_subgraph` method."""
|
| 397 |
+
|
| 398 |
+
def setup_method(self):
|
| 399 |
+
# Create a quadruply-linked path graph on five nodes.
|
| 400 |
+
G = nx.MultiDiGraph()
|
| 401 |
+
nx.add_path(G, range(5))
|
| 402 |
+
nx.add_path(G, range(5))
|
| 403 |
+
nx.add_path(G, reversed(range(5)))
|
| 404 |
+
nx.add_path(G, reversed(range(5)))
|
| 405 |
+
# Add some node, edge, and graph attributes.
|
| 406 |
+
for i in range(5):
|
| 407 |
+
G.nodes[i]["name"] = f"node{i}"
|
| 408 |
+
G.adj[0][1][0]["name"] = "edge010"
|
| 409 |
+
G.adj[0][1][1]["name"] = "edge011"
|
| 410 |
+
G.adj[3][4][0]["name"] = "edge340"
|
| 411 |
+
G.adj[3][4][1]["name"] = "edge341"
|
| 412 |
+
G.graph["name"] = "graph"
|
| 413 |
+
# Get the subgraph induced by one of the first edges and one of
|
| 414 |
+
# the last edges.
|
| 415 |
+
self.G = G
|
| 416 |
+
self.H = G.edge_subgraph([(0, 1, 0), (3, 4, 1)])
|
| 417 |
+
|
| 418 |
+
|
| 419 |
+
class CustomDictClass(UserDict):
|
| 420 |
+
pass
|
| 421 |
+
|
| 422 |
+
|
| 423 |
+
class MultiDiGraphSubClass(nx.MultiDiGraph):
|
| 424 |
+
node_dict_factory = CustomDictClass # type: ignore[assignment]
|
| 425 |
+
node_attr_dict_factory = CustomDictClass # type: ignore[assignment]
|
| 426 |
+
adjlist_outer_dict_factory = CustomDictClass # type: ignore[assignment]
|
| 427 |
+
adjlist_inner_dict_factory = CustomDictClass # type: ignore[assignment]
|
| 428 |
+
edge_key_dict_factory = CustomDictClass # type: ignore[assignment]
|
| 429 |
+
edge_attr_dict_factory = CustomDictClass # type: ignore[assignment]
|
| 430 |
+
graph_attr_dict_factory = CustomDictClass # type: ignore[assignment]
|
| 431 |
+
|
| 432 |
+
|
| 433 |
+
class TestMultiDiGraphSubclass(TestMultiDiGraph):
|
| 434 |
+
def setup_method(self):
|
| 435 |
+
self.Graph = MultiDiGraphSubClass
|
| 436 |
+
# build K3
|
| 437 |
+
self.k3edges = [(0, 1), (0, 2), (1, 2)]
|
| 438 |
+
self.k3nodes = [0, 1, 2]
|
| 439 |
+
self.K3 = self.Graph()
|
| 440 |
+
self.K3._succ = self.K3.adjlist_outer_dict_factory(
|
| 441 |
+
{
|
| 442 |
+
0: self.K3.adjlist_inner_dict_factory(),
|
| 443 |
+
1: self.K3.adjlist_inner_dict_factory(),
|
| 444 |
+
2: self.K3.adjlist_inner_dict_factory(),
|
| 445 |
+
}
|
| 446 |
+
)
|
| 447 |
+
# K3._adj is synced with K3._succ
|
| 448 |
+
self.K3._pred = {0: {}, 1: {}, 2: {}}
|
| 449 |
+
for u in self.k3nodes:
|
| 450 |
+
for v in self.k3nodes:
|
| 451 |
+
if u == v:
|
| 452 |
+
continue
|
| 453 |
+
d = {0: {}}
|
| 454 |
+
self.K3._succ[u][v] = d
|
| 455 |
+
self.K3._pred[v][u] = d
|
| 456 |
+
self.K3._node = self.K3.node_dict_factory()
|
| 457 |
+
self.K3._node[0] = self.K3.node_attr_dict_factory()
|
| 458 |
+
self.K3._node[1] = self.K3.node_attr_dict_factory()
|
| 459 |
+
self.K3._node[2] = self.K3.node_attr_dict_factory()
|
.venv/lib/python3.11/site-packages/networkx/classes/tests/test_multigraph.py
ADDED
|
@@ -0,0 +1,528 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from collections import UserDict
|
| 2 |
+
|
| 3 |
+
import pytest
|
| 4 |
+
|
| 5 |
+
import networkx as nx
|
| 6 |
+
from networkx.utils import edges_equal
|
| 7 |
+
|
| 8 |
+
from .test_graph import BaseAttrGraphTester
|
| 9 |
+
from .test_graph import TestGraph as _TestGraph
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class BaseMultiGraphTester(BaseAttrGraphTester):
|
| 13 |
+
def test_has_edge(self):
|
| 14 |
+
G = self.K3
|
| 15 |
+
assert G.has_edge(0, 1)
|
| 16 |
+
assert not G.has_edge(0, -1)
|
| 17 |
+
assert G.has_edge(0, 1, 0)
|
| 18 |
+
assert not G.has_edge(0, 1, 1)
|
| 19 |
+
|
| 20 |
+
def test_get_edge_data(self):
|
| 21 |
+
G = self.K3
|
| 22 |
+
assert G.get_edge_data(0, 1) == {0: {}}
|
| 23 |
+
assert G[0][1] == {0: {}}
|
| 24 |
+
assert G[0][1][0] == {}
|
| 25 |
+
assert G.get_edge_data(10, 20) is None
|
| 26 |
+
assert G.get_edge_data(0, 1, 0) == {}
|
| 27 |
+
|
| 28 |
+
def test_adjacency(self):
|
| 29 |
+
G = self.K3
|
| 30 |
+
assert dict(G.adjacency()) == {
|
| 31 |
+
0: {1: {0: {}}, 2: {0: {}}},
|
| 32 |
+
1: {0: {0: {}}, 2: {0: {}}},
|
| 33 |
+
2: {0: {0: {}}, 1: {0: {}}},
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
def deepcopy_edge_attr(self, H, G):
|
| 37 |
+
assert G[1][2][0]["foo"] == H[1][2][0]["foo"]
|
| 38 |
+
G[1][2][0]["foo"].append(1)
|
| 39 |
+
assert G[1][2][0]["foo"] != H[1][2][0]["foo"]
|
| 40 |
+
|
| 41 |
+
def shallow_copy_edge_attr(self, H, G):
|
| 42 |
+
assert G[1][2][0]["foo"] == H[1][2][0]["foo"]
|
| 43 |
+
G[1][2][0]["foo"].append(1)
|
| 44 |
+
assert G[1][2][0]["foo"] == H[1][2][0]["foo"]
|
| 45 |
+
|
| 46 |
+
def graphs_equal(self, H, G):
|
| 47 |
+
assert G._adj == H._adj
|
| 48 |
+
assert G._node == H._node
|
| 49 |
+
assert G.graph == H.graph
|
| 50 |
+
assert G.name == H.name
|
| 51 |
+
if not G.is_directed() and not H.is_directed():
|
| 52 |
+
assert H._adj[1][2][0] is H._adj[2][1][0]
|
| 53 |
+
assert G._adj[1][2][0] is G._adj[2][1][0]
|
| 54 |
+
else: # at least one is directed
|
| 55 |
+
if not G.is_directed():
|
| 56 |
+
G._pred = G._adj
|
| 57 |
+
G._succ = G._adj
|
| 58 |
+
if not H.is_directed():
|
| 59 |
+
H._pred = H._adj
|
| 60 |
+
H._succ = H._adj
|
| 61 |
+
assert G._pred == H._pred
|
| 62 |
+
assert G._succ == H._succ
|
| 63 |
+
assert H._succ[1][2][0] is H._pred[2][1][0]
|
| 64 |
+
assert G._succ[1][2][0] is G._pred[2][1][0]
|
| 65 |
+
|
| 66 |
+
def same_attrdict(self, H, G):
|
| 67 |
+
# same attrdict in the edgedata
|
| 68 |
+
old_foo = H[1][2][0]["foo"]
|
| 69 |
+
H.adj[1][2][0]["foo"] = "baz"
|
| 70 |
+
assert G._adj == H._adj
|
| 71 |
+
H.adj[1][2][0]["foo"] = old_foo
|
| 72 |
+
assert G._adj == H._adj
|
| 73 |
+
|
| 74 |
+
old_foo = H.nodes[0]["foo"]
|
| 75 |
+
H.nodes[0]["foo"] = "baz"
|
| 76 |
+
assert G._node == H._node
|
| 77 |
+
H.nodes[0]["foo"] = old_foo
|
| 78 |
+
assert G._node == H._node
|
| 79 |
+
|
| 80 |
+
def different_attrdict(self, H, G):
|
| 81 |
+
# used by graph_equal_but_different
|
| 82 |
+
old_foo = H[1][2][0]["foo"]
|
| 83 |
+
H.adj[1][2][0]["foo"] = "baz"
|
| 84 |
+
assert G._adj != H._adj
|
| 85 |
+
H.adj[1][2][0]["foo"] = old_foo
|
| 86 |
+
assert G._adj == H._adj
|
| 87 |
+
|
| 88 |
+
old_foo = H.nodes[0]["foo"]
|
| 89 |
+
H.nodes[0]["foo"] = "baz"
|
| 90 |
+
assert G._node != H._node
|
| 91 |
+
H.nodes[0]["foo"] = old_foo
|
| 92 |
+
assert G._node == H._node
|
| 93 |
+
|
| 94 |
+
def test_to_undirected(self):
|
| 95 |
+
G = self.K3
|
| 96 |
+
self.add_attributes(G)
|
| 97 |
+
H = nx.MultiGraph(G)
|
| 98 |
+
self.is_shallow_copy(H, G)
|
| 99 |
+
H = G.to_undirected()
|
| 100 |
+
self.is_deepcopy(H, G)
|
| 101 |
+
|
| 102 |
+
def test_to_directed(self):
|
| 103 |
+
G = self.K3
|
| 104 |
+
self.add_attributes(G)
|
| 105 |
+
H = nx.MultiDiGraph(G)
|
| 106 |
+
self.is_shallow_copy(H, G)
|
| 107 |
+
H = G.to_directed()
|
| 108 |
+
self.is_deepcopy(H, G)
|
| 109 |
+
|
| 110 |
+
def test_number_of_edges_selfloops(self):
|
| 111 |
+
G = self.K3
|
| 112 |
+
G.add_edge(0, 0)
|
| 113 |
+
G.add_edge(0, 0)
|
| 114 |
+
G.add_edge(0, 0, key="parallel edge")
|
| 115 |
+
G.remove_edge(0, 0, key="parallel edge")
|
| 116 |
+
assert G.number_of_edges(0, 0) == 2
|
| 117 |
+
G.remove_edge(0, 0)
|
| 118 |
+
assert G.number_of_edges(0, 0) == 1
|
| 119 |
+
|
| 120 |
+
def test_edge_lookup(self):
|
| 121 |
+
G = self.Graph()
|
| 122 |
+
G.add_edge(1, 2, foo="bar")
|
| 123 |
+
G.add_edge(1, 2, "key", foo="biz")
|
| 124 |
+
assert edges_equal(G.edges[1, 2, 0], {"foo": "bar"})
|
| 125 |
+
assert edges_equal(G.edges[1, 2, "key"], {"foo": "biz"})
|
| 126 |
+
|
| 127 |
+
def test_edge_attr(self):
|
| 128 |
+
G = self.Graph()
|
| 129 |
+
G.add_edge(1, 2, key="k1", foo="bar")
|
| 130 |
+
G.add_edge(1, 2, key="k2", foo="baz")
|
| 131 |
+
assert isinstance(G.get_edge_data(1, 2), G.edge_key_dict_factory)
|
| 132 |
+
assert all(
|
| 133 |
+
isinstance(d, G.edge_attr_dict_factory) for u, v, d in G.edges(data=True)
|
| 134 |
+
)
|
| 135 |
+
assert edges_equal(
|
| 136 |
+
G.edges(keys=True, data=True),
|
| 137 |
+
[(1, 2, "k1", {"foo": "bar"}), (1, 2, "k2", {"foo": "baz"})],
|
| 138 |
+
)
|
| 139 |
+
assert edges_equal(
|
| 140 |
+
G.edges(keys=True, data="foo"), [(1, 2, "k1", "bar"), (1, 2, "k2", "baz")]
|
| 141 |
+
)
|
| 142 |
+
|
| 143 |
+
def test_edge_attr4(self):
|
| 144 |
+
G = self.Graph()
|
| 145 |
+
G.add_edge(1, 2, key=0, data=7, spam="bar", bar="foo")
|
| 146 |
+
assert edges_equal(
|
| 147 |
+
G.edges(data=True), [(1, 2, {"data": 7, "spam": "bar", "bar": "foo"})]
|
| 148 |
+
)
|
| 149 |
+
G[1][2][0]["data"] = 10 # OK to set data like this
|
| 150 |
+
assert edges_equal(
|
| 151 |
+
G.edges(data=True), [(1, 2, {"data": 10, "spam": "bar", "bar": "foo"})]
|
| 152 |
+
)
|
| 153 |
+
|
| 154 |
+
G.adj[1][2][0]["data"] = 20
|
| 155 |
+
assert edges_equal(
|
| 156 |
+
G.edges(data=True), [(1, 2, {"data": 20, "spam": "bar", "bar": "foo"})]
|
| 157 |
+
)
|
| 158 |
+
G.edges[1, 2, 0]["data"] = 21 # another spelling, "edge"
|
| 159 |
+
assert edges_equal(
|
| 160 |
+
G.edges(data=True), [(1, 2, {"data": 21, "spam": "bar", "bar": "foo"})]
|
| 161 |
+
)
|
| 162 |
+
G.adj[1][2][0]["listdata"] = [20, 200]
|
| 163 |
+
G.adj[1][2][0]["weight"] = 20
|
| 164 |
+
assert edges_equal(
|
| 165 |
+
G.edges(data=True),
|
| 166 |
+
[
|
| 167 |
+
(
|
| 168 |
+
1,
|
| 169 |
+
2,
|
| 170 |
+
{
|
| 171 |
+
"data": 21,
|
| 172 |
+
"spam": "bar",
|
| 173 |
+
"bar": "foo",
|
| 174 |
+
"listdata": [20, 200],
|
| 175 |
+
"weight": 20,
|
| 176 |
+
},
|
| 177 |
+
)
|
| 178 |
+
],
|
| 179 |
+
)
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
class TestMultiGraph(BaseMultiGraphTester, _TestGraph):
|
| 183 |
+
def setup_method(self):
|
| 184 |
+
self.Graph = nx.MultiGraph
|
| 185 |
+
# build K3
|
| 186 |
+
ed1, ed2, ed3 = ({0: {}}, {0: {}}, {0: {}})
|
| 187 |
+
self.k3adj = {0: {1: ed1, 2: ed2}, 1: {0: ed1, 2: ed3}, 2: {0: ed2, 1: ed3}}
|
| 188 |
+
self.k3edges = [(0, 1), (0, 2), (1, 2)]
|
| 189 |
+
self.k3nodes = [0, 1, 2]
|
| 190 |
+
self.K3 = self.Graph()
|
| 191 |
+
self.K3._adj = self.k3adj
|
| 192 |
+
self.K3._node = {}
|
| 193 |
+
self.K3._node[0] = {}
|
| 194 |
+
self.K3._node[1] = {}
|
| 195 |
+
self.K3._node[2] = {}
|
| 196 |
+
|
| 197 |
+
def test_data_input(self):
|
| 198 |
+
G = self.Graph({1: [2], 2: [1]}, name="test")
|
| 199 |
+
assert G.name == "test"
|
| 200 |
+
expected = [(1, {2: {0: {}}}), (2, {1: {0: {}}})]
|
| 201 |
+
assert sorted(G.adj.items()) == expected
|
| 202 |
+
|
| 203 |
+
def test_data_multigraph_input(self):
|
| 204 |
+
# standard case with edge keys and edge data
|
| 205 |
+
edata0 = {"w": 200, "s": "foo"}
|
| 206 |
+
edata1 = {"w": 201, "s": "bar"}
|
| 207 |
+
keydict = {0: edata0, 1: edata1}
|
| 208 |
+
dododod = {"a": {"b": keydict}}
|
| 209 |
+
|
| 210 |
+
multiple_edge = [("a", "b", 0, edata0), ("a", "b", 1, edata1)]
|
| 211 |
+
single_edge = [("a", "b", 0, keydict)]
|
| 212 |
+
|
| 213 |
+
G = self.Graph(dododod, multigraph_input=True)
|
| 214 |
+
assert list(G.edges(keys=True, data=True)) == multiple_edge
|
| 215 |
+
G = self.Graph(dododod, multigraph_input=None)
|
| 216 |
+
assert list(G.edges(keys=True, data=True)) == multiple_edge
|
| 217 |
+
G = self.Graph(dododod, multigraph_input=False)
|
| 218 |
+
assert list(G.edges(keys=True, data=True)) == single_edge
|
| 219 |
+
|
| 220 |
+
# test round-trip to_dict_of_dict and MultiGraph constructor
|
| 221 |
+
G = self.Graph(dododod, multigraph_input=True)
|
| 222 |
+
H = self.Graph(nx.to_dict_of_dicts(G))
|
| 223 |
+
assert nx.is_isomorphic(G, H) is True # test that default is True
|
| 224 |
+
for mgi in [True, False]:
|
| 225 |
+
H = self.Graph(nx.to_dict_of_dicts(G), multigraph_input=mgi)
|
| 226 |
+
assert nx.is_isomorphic(G, H) == mgi
|
| 227 |
+
|
| 228 |
+
# Set up cases for when incoming_graph_data is not multigraph_input
|
| 229 |
+
etraits = {"w": 200, "s": "foo"}
|
| 230 |
+
egraphics = {"color": "blue", "shape": "box"}
|
| 231 |
+
edata = {"traits": etraits, "graphics": egraphics}
|
| 232 |
+
dodod1 = {"a": {"b": edata}}
|
| 233 |
+
dodod2 = {"a": {"b": etraits}}
|
| 234 |
+
dodod3 = {"a": {"b": {"traits": etraits, "s": "foo"}}}
|
| 235 |
+
dol = {"a": ["b"]}
|
| 236 |
+
|
| 237 |
+
multiple_edge = [("a", "b", "traits", etraits), ("a", "b", "graphics", egraphics)]
|
| 238 |
+
single_edge = [("a", "b", 0, {})] # type: ignore[var-annotated]
|
| 239 |
+
single_edge1 = [("a", "b", 0, edata)]
|
| 240 |
+
single_edge2 = [("a", "b", 0, etraits)]
|
| 241 |
+
single_edge3 = [("a", "b", 0, {"traits": etraits, "s": "foo"})]
|
| 242 |
+
|
| 243 |
+
cases = [ # (dod, mgi, edges)
|
| 244 |
+
(dodod1, True, multiple_edge),
|
| 245 |
+
(dodod1, False, single_edge1),
|
| 246 |
+
(dodod2, False, single_edge2),
|
| 247 |
+
(dodod3, False, single_edge3),
|
| 248 |
+
(dol, False, single_edge),
|
| 249 |
+
]
|
| 250 |
+
|
| 251 |
+
@pytest.mark.parametrize("dod, mgi, edges", cases)
|
| 252 |
+
def test_non_multigraph_input(self, dod, mgi, edges):
|
| 253 |
+
G = self.Graph(dod, multigraph_input=mgi)
|
| 254 |
+
assert list(G.edges(keys=True, data=True)) == edges
|
| 255 |
+
G = nx.to_networkx_graph(dod, create_using=self.Graph, multigraph_input=mgi)
|
| 256 |
+
assert list(G.edges(keys=True, data=True)) == edges
|
| 257 |
+
|
| 258 |
+
mgi_none_cases = [
|
| 259 |
+
(dodod1, multiple_edge),
|
| 260 |
+
(dodod2, single_edge2),
|
| 261 |
+
(dodod3, single_edge3),
|
| 262 |
+
]
|
| 263 |
+
|
| 264 |
+
@pytest.mark.parametrize("dod, edges", mgi_none_cases)
|
| 265 |
+
def test_non_multigraph_input_mgi_none(self, dod, edges):
|
| 266 |
+
# test constructor without to_networkx_graph for mgi=None
|
| 267 |
+
G = self.Graph(dod)
|
| 268 |
+
assert list(G.edges(keys=True, data=True)) == edges
|
| 269 |
+
|
| 270 |
+
raise_cases = [dodod2, dodod3, dol]
|
| 271 |
+
|
| 272 |
+
@pytest.mark.parametrize("dod", raise_cases)
|
| 273 |
+
def test_non_multigraph_input_raise(self, dod):
|
| 274 |
+
# cases where NetworkXError is raised
|
| 275 |
+
pytest.raises(nx.NetworkXError, self.Graph, dod, multigraph_input=True)
|
| 276 |
+
pytest.raises(
|
| 277 |
+
nx.NetworkXError,
|
| 278 |
+
nx.to_networkx_graph,
|
| 279 |
+
dod,
|
| 280 |
+
create_using=self.Graph,
|
| 281 |
+
multigraph_input=True,
|
| 282 |
+
)
|
| 283 |
+
|
| 284 |
+
def test_getitem(self):
|
| 285 |
+
G = self.K3
|
| 286 |
+
assert G[0] == {1: {0: {}}, 2: {0: {}}}
|
| 287 |
+
with pytest.raises(KeyError):
|
| 288 |
+
G.__getitem__("j")
|
| 289 |
+
with pytest.raises(TypeError):
|
| 290 |
+
G.__getitem__(["A"])
|
| 291 |
+
|
| 292 |
+
def test_remove_node(self):
|
| 293 |
+
G = self.K3
|
| 294 |
+
G.remove_node(0)
|
| 295 |
+
assert G.adj == {1: {2: {0: {}}}, 2: {1: {0: {}}}}
|
| 296 |
+
with pytest.raises(nx.NetworkXError):
|
| 297 |
+
G.remove_node(-1)
|
| 298 |
+
|
| 299 |
+
def test_add_edge(self):
|
| 300 |
+
G = self.Graph()
|
| 301 |
+
G.add_edge(0, 1)
|
| 302 |
+
assert G.adj == {0: {1: {0: {}}}, 1: {0: {0: {}}}}
|
| 303 |
+
G = self.Graph()
|
| 304 |
+
G.add_edge(*(0, 1))
|
| 305 |
+
assert G.adj == {0: {1: {0: {}}}, 1: {0: {0: {}}}}
|
| 306 |
+
G = self.Graph()
|
| 307 |
+
with pytest.raises(ValueError):
|
| 308 |
+
G.add_edge(None, "anything")
|
| 309 |
+
|
| 310 |
+
def test_add_edge_conflicting_key(self):
|
| 311 |
+
G = self.Graph()
|
| 312 |
+
G.add_edge(0, 1, key=1)
|
| 313 |
+
G.add_edge(0, 1)
|
| 314 |
+
assert G.number_of_edges() == 2
|
| 315 |
+
G = self.Graph()
|
| 316 |
+
G.add_edges_from([(0, 1, 1, {})])
|
| 317 |
+
G.add_edges_from([(0, 1)])
|
| 318 |
+
assert G.number_of_edges() == 2
|
| 319 |
+
|
| 320 |
+
def test_add_edges_from(self):
|
| 321 |
+
G = self.Graph()
|
| 322 |
+
G.add_edges_from([(0, 1), (0, 1, {"weight": 3})])
|
| 323 |
+
assert G.adj == {
|
| 324 |
+
0: {1: {0: {}, 1: {"weight": 3}}},
|
| 325 |
+
1: {0: {0: {}, 1: {"weight": 3}}},
|
| 326 |
+
}
|
| 327 |
+
G.add_edges_from([(0, 1), (0, 1, {"weight": 3})], weight=2)
|
| 328 |
+
assert G.adj == {
|
| 329 |
+
0: {1: {0: {}, 1: {"weight": 3}, 2: {"weight": 2}, 3: {"weight": 3}}},
|
| 330 |
+
1: {0: {0: {}, 1: {"weight": 3}, 2: {"weight": 2}, 3: {"weight": 3}}},
|
| 331 |
+
}
|
| 332 |
+
G = self.Graph()
|
| 333 |
+
edges = [
|
| 334 |
+
(0, 1, {"weight": 3}),
|
| 335 |
+
(0, 1, (("weight", 2),)),
|
| 336 |
+
(0, 1, 5),
|
| 337 |
+
(0, 1, "s"),
|
| 338 |
+
]
|
| 339 |
+
G.add_edges_from(edges)
|
| 340 |
+
keydict = {0: {"weight": 3}, 1: {"weight": 2}, 5: {}, "s": {}}
|
| 341 |
+
assert G._adj == {0: {1: keydict}, 1: {0: keydict}}
|
| 342 |
+
|
| 343 |
+
# too few in tuple
|
| 344 |
+
with pytest.raises(nx.NetworkXError):
|
| 345 |
+
G.add_edges_from([(0,)])
|
| 346 |
+
# too many in tuple
|
| 347 |
+
with pytest.raises(nx.NetworkXError):
|
| 348 |
+
G.add_edges_from([(0, 1, 2, 3, 4)])
|
| 349 |
+
# not a tuple
|
| 350 |
+
with pytest.raises(TypeError):
|
| 351 |
+
G.add_edges_from([0])
|
| 352 |
+
|
| 353 |
+
def test_multigraph_add_edges_from_four_tuple_misordered(self):
|
| 354 |
+
"""add_edges_from expects 4-tuples of the format (u, v, key, data_dict).
|
| 355 |
+
|
| 356 |
+
Ensure 4-tuples of form (u, v, data_dict, key) raise exception.
|
| 357 |
+
"""
|
| 358 |
+
G = nx.MultiGraph()
|
| 359 |
+
with pytest.raises(TypeError):
|
| 360 |
+
# key/data values flipped in 4-tuple
|
| 361 |
+
G.add_edges_from([(0, 1, {"color": "red"}, 0)])
|
| 362 |
+
|
| 363 |
+
def test_remove_edge(self):
|
| 364 |
+
G = self.K3
|
| 365 |
+
G.remove_edge(0, 1)
|
| 366 |
+
assert G.adj == {0: {2: {0: {}}}, 1: {2: {0: {}}}, 2: {0: {0: {}}, 1: {0: {}}}}
|
| 367 |
+
|
| 368 |
+
with pytest.raises(nx.NetworkXError):
|
| 369 |
+
G.remove_edge(-1, 0)
|
| 370 |
+
with pytest.raises(nx.NetworkXError):
|
| 371 |
+
G.remove_edge(0, 2, key=1)
|
| 372 |
+
|
| 373 |
+
def test_remove_edges_from(self):
|
| 374 |
+
G = self.K3.copy()
|
| 375 |
+
G.remove_edges_from([(0, 1)])
|
| 376 |
+
kd = {0: {}}
|
| 377 |
+
assert G.adj == {0: {2: kd}, 1: {2: kd}, 2: {0: kd, 1: kd}}
|
| 378 |
+
G.remove_edges_from([(0, 0)]) # silent fail
|
| 379 |
+
self.K3.add_edge(0, 1)
|
| 380 |
+
G = self.K3.copy()
|
| 381 |
+
G.remove_edges_from(list(G.edges(data=True, keys=True)))
|
| 382 |
+
assert G.adj == {0: {}, 1: {}, 2: {}}
|
| 383 |
+
G = self.K3.copy()
|
| 384 |
+
G.remove_edges_from(list(G.edges(data=False, keys=True)))
|
| 385 |
+
assert G.adj == {0: {}, 1: {}, 2: {}}
|
| 386 |
+
G = self.K3.copy()
|
| 387 |
+
G.remove_edges_from(list(G.edges(data=False, keys=False)))
|
| 388 |
+
assert G.adj == {0: {}, 1: {}, 2: {}}
|
| 389 |
+
G = self.K3.copy()
|
| 390 |
+
G.remove_edges_from([(0, 1, 0), (0, 2, 0, {}), (1, 2)])
|
| 391 |
+
assert G.adj == {0: {1: {1: {}}}, 1: {0: {1: {}}}, 2: {}}
|
| 392 |
+
|
| 393 |
+
def test_remove_multiedge(self):
|
| 394 |
+
G = self.K3
|
| 395 |
+
G.add_edge(0, 1, key="parallel edge")
|
| 396 |
+
G.remove_edge(0, 1, key="parallel edge")
|
| 397 |
+
assert G.adj == {
|
| 398 |
+
0: {1: {0: {}}, 2: {0: {}}},
|
| 399 |
+
1: {0: {0: {}}, 2: {0: {}}},
|
| 400 |
+
2: {0: {0: {}}, 1: {0: {}}},
|
| 401 |
+
}
|
| 402 |
+
G.remove_edge(0, 1)
|
| 403 |
+
kd = {0: {}}
|
| 404 |
+
assert G.adj == {0: {2: kd}, 1: {2: kd}, 2: {0: kd, 1: kd}}
|
| 405 |
+
with pytest.raises(nx.NetworkXError):
|
| 406 |
+
G.remove_edge(-1, 0)
|
| 407 |
+
|
| 408 |
+
|
| 409 |
+
class TestEdgeSubgraph:
|
| 410 |
+
"""Unit tests for the :meth:`MultiGraph.edge_subgraph` method."""
|
| 411 |
+
|
| 412 |
+
def setup_method(self):
|
| 413 |
+
# Create a doubly-linked path graph on five nodes.
|
| 414 |
+
G = nx.MultiGraph()
|
| 415 |
+
nx.add_path(G, range(5))
|
| 416 |
+
nx.add_path(G, range(5))
|
| 417 |
+
# Add some node, edge, and graph attributes.
|
| 418 |
+
for i in range(5):
|
| 419 |
+
G.nodes[i]["name"] = f"node{i}"
|
| 420 |
+
G.adj[0][1][0]["name"] = "edge010"
|
| 421 |
+
G.adj[0][1][1]["name"] = "edge011"
|
| 422 |
+
G.adj[3][4][0]["name"] = "edge340"
|
| 423 |
+
G.adj[3][4][1]["name"] = "edge341"
|
| 424 |
+
G.graph["name"] = "graph"
|
| 425 |
+
# Get the subgraph induced by one of the first edges and one of
|
| 426 |
+
# the last edges.
|
| 427 |
+
self.G = G
|
| 428 |
+
self.H = G.edge_subgraph([(0, 1, 0), (3, 4, 1)])
|
| 429 |
+
|
| 430 |
+
def test_correct_nodes(self):
|
| 431 |
+
"""Tests that the subgraph has the correct nodes."""
|
| 432 |
+
assert [0, 1, 3, 4] == sorted(self.H.nodes())
|
| 433 |
+
|
| 434 |
+
def test_correct_edges(self):
|
| 435 |
+
"""Tests that the subgraph has the correct edges."""
|
| 436 |
+
assert [(0, 1, 0, "edge010"), (3, 4, 1, "edge341")] == sorted(
|
| 437 |
+
self.H.edges(keys=True, data="name")
|
| 438 |
+
)
|
| 439 |
+
|
| 440 |
+
def test_add_node(self):
|
| 441 |
+
"""Tests that adding a node to the original graph does not
|
| 442 |
+
affect the nodes of the subgraph.
|
| 443 |
+
|
| 444 |
+
"""
|
| 445 |
+
self.G.add_node(5)
|
| 446 |
+
assert [0, 1, 3, 4] == sorted(self.H.nodes())
|
| 447 |
+
|
| 448 |
+
def test_remove_node(self):
|
| 449 |
+
"""Tests that removing a node in the original graph does
|
| 450 |
+
affect the nodes of the subgraph.
|
| 451 |
+
|
| 452 |
+
"""
|
| 453 |
+
self.G.remove_node(0)
|
| 454 |
+
assert [1, 3, 4] == sorted(self.H.nodes())
|
| 455 |
+
|
| 456 |
+
def test_node_attr_dict(self):
|
| 457 |
+
"""Tests that the node attribute dictionary of the two graphs is
|
| 458 |
+
the same object.
|
| 459 |
+
|
| 460 |
+
"""
|
| 461 |
+
for v in self.H:
|
| 462 |
+
assert self.G.nodes[v] == self.H.nodes[v]
|
| 463 |
+
# Making a change to G should make a change in H and vice versa.
|
| 464 |
+
self.G.nodes[0]["name"] = "foo"
|
| 465 |
+
assert self.G.nodes[0] == self.H.nodes[0]
|
| 466 |
+
self.H.nodes[1]["name"] = "bar"
|
| 467 |
+
assert self.G.nodes[1] == self.H.nodes[1]
|
| 468 |
+
|
| 469 |
+
def test_edge_attr_dict(self):
|
| 470 |
+
"""Tests that the edge attribute dictionary of the two graphs is
|
| 471 |
+
the same object.
|
| 472 |
+
|
| 473 |
+
"""
|
| 474 |
+
for u, v, k in self.H.edges(keys=True):
|
| 475 |
+
assert self.G._adj[u][v][k] == self.H._adj[u][v][k]
|
| 476 |
+
# Making a change to G should make a change in H and vice versa.
|
| 477 |
+
self.G._adj[0][1][0]["name"] = "foo"
|
| 478 |
+
assert self.G._adj[0][1][0]["name"] == self.H._adj[0][1][0]["name"]
|
| 479 |
+
self.H._adj[3][4][1]["name"] = "bar"
|
| 480 |
+
assert self.G._adj[3][4][1]["name"] == self.H._adj[3][4][1]["name"]
|
| 481 |
+
|
| 482 |
+
def test_graph_attr_dict(self):
|
| 483 |
+
"""Tests that the graph attribute dictionary of the two graphs
|
| 484 |
+
is the same object.
|
| 485 |
+
|
| 486 |
+
"""
|
| 487 |
+
assert self.G.graph is self.H.graph
|
| 488 |
+
|
| 489 |
+
|
| 490 |
+
class CustomDictClass(UserDict):
|
| 491 |
+
pass
|
| 492 |
+
|
| 493 |
+
|
| 494 |
+
class MultiGraphSubClass(nx.MultiGraph):
|
| 495 |
+
node_dict_factory = CustomDictClass # type: ignore[assignment]
|
| 496 |
+
node_attr_dict_factory = CustomDictClass # type: ignore[assignment]
|
| 497 |
+
adjlist_outer_dict_factory = CustomDictClass # type: ignore[assignment]
|
| 498 |
+
adjlist_inner_dict_factory = CustomDictClass # type: ignore[assignment]
|
| 499 |
+
edge_key_dict_factory = CustomDictClass # type: ignore[assignment]
|
| 500 |
+
edge_attr_dict_factory = CustomDictClass # type: ignore[assignment]
|
| 501 |
+
graph_attr_dict_factory = CustomDictClass # type: ignore[assignment]
|
| 502 |
+
|
| 503 |
+
|
| 504 |
+
class TestMultiGraphSubclass(TestMultiGraph):
|
| 505 |
+
def setup_method(self):
|
| 506 |
+
self.Graph = MultiGraphSubClass
|
| 507 |
+
# build K3
|
| 508 |
+
self.k3edges = [(0, 1), (0, 2), (1, 2)]
|
| 509 |
+
self.k3nodes = [0, 1, 2]
|
| 510 |
+
self.K3 = self.Graph()
|
| 511 |
+
self.K3._adj = self.K3.adjlist_outer_dict_factory(
|
| 512 |
+
{
|
| 513 |
+
0: self.K3.adjlist_inner_dict_factory(),
|
| 514 |
+
1: self.K3.adjlist_inner_dict_factory(),
|
| 515 |
+
2: self.K3.adjlist_inner_dict_factory(),
|
| 516 |
+
}
|
| 517 |
+
)
|
| 518 |
+
self.K3._pred = {0: {}, 1: {}, 2: {}}
|
| 519 |
+
for u in self.k3nodes:
|
| 520 |
+
for v in self.k3nodes:
|
| 521 |
+
if u != v:
|
| 522 |
+
d = {0: {}}
|
| 523 |
+
self.K3._adj[u][v] = d
|
| 524 |
+
self.K3._adj[v][u] = d
|
| 525 |
+
self.K3._node = self.K3.node_dict_factory()
|
| 526 |
+
self.K3._node[0] = self.K3.node_attr_dict_factory()
|
| 527 |
+
self.K3._node[1] = self.K3.node_attr_dict_factory()
|
| 528 |
+
self.K3._node[2] = self.K3.node_attr_dict_factory()
|
.venv/lib/python3.11/site-packages/networkx/classes/tests/test_reportviews.py
ADDED
|
@@ -0,0 +1,1435 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pickle
|
| 2 |
+
from copy import deepcopy
|
| 3 |
+
|
| 4 |
+
import pytest
|
| 5 |
+
|
| 6 |
+
import networkx as nx
|
| 7 |
+
from networkx.classes import reportviews as rv
|
| 8 |
+
from networkx.classes.reportviews import NodeDataView
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
# Nodes
|
| 12 |
+
class TestNodeView:
|
| 13 |
+
@classmethod
|
| 14 |
+
def setup_class(cls):
|
| 15 |
+
cls.G = nx.path_graph(9)
|
| 16 |
+
cls.nv = cls.G.nodes # NodeView(G)
|
| 17 |
+
|
| 18 |
+
def test_pickle(self):
|
| 19 |
+
import pickle
|
| 20 |
+
|
| 21 |
+
nv = self.nv
|
| 22 |
+
pnv = pickle.loads(pickle.dumps(nv, -1))
|
| 23 |
+
assert nv == pnv
|
| 24 |
+
assert nv.__slots__ == pnv.__slots__
|
| 25 |
+
|
| 26 |
+
def test_str(self):
|
| 27 |
+
assert str(self.nv) == "[0, 1, 2, 3, 4, 5, 6, 7, 8]"
|
| 28 |
+
|
| 29 |
+
def test_repr(self):
|
| 30 |
+
assert repr(self.nv) == "NodeView((0, 1, 2, 3, 4, 5, 6, 7, 8))"
|
| 31 |
+
|
| 32 |
+
def test_contains(self):
|
| 33 |
+
G = self.G.copy()
|
| 34 |
+
nv = G.nodes
|
| 35 |
+
assert 7 in nv
|
| 36 |
+
assert 9 not in nv
|
| 37 |
+
G.remove_node(7)
|
| 38 |
+
G.add_node(9)
|
| 39 |
+
assert 7 not in nv
|
| 40 |
+
assert 9 in nv
|
| 41 |
+
|
| 42 |
+
def test_getitem(self):
|
| 43 |
+
G = self.G.copy()
|
| 44 |
+
nv = G.nodes
|
| 45 |
+
G.nodes[3]["foo"] = "bar"
|
| 46 |
+
assert nv[7] == {}
|
| 47 |
+
assert nv[3] == {"foo": "bar"}
|
| 48 |
+
# slicing
|
| 49 |
+
with pytest.raises(nx.NetworkXError):
|
| 50 |
+
G.nodes[0:5]
|
| 51 |
+
|
| 52 |
+
def test_iter(self):
|
| 53 |
+
nv = self.nv
|
| 54 |
+
for i, n in enumerate(nv):
|
| 55 |
+
assert i == n
|
| 56 |
+
inv = iter(nv)
|
| 57 |
+
assert next(inv) == 0
|
| 58 |
+
assert iter(nv) != nv
|
| 59 |
+
assert iter(inv) == inv
|
| 60 |
+
inv2 = iter(nv)
|
| 61 |
+
next(inv2)
|
| 62 |
+
assert list(inv) == list(inv2)
|
| 63 |
+
# odd case where NodeView calls NodeDataView with data=False
|
| 64 |
+
nnv = nv(data=False)
|
| 65 |
+
for i, n in enumerate(nnv):
|
| 66 |
+
assert i == n
|
| 67 |
+
|
| 68 |
+
def test_call(self):
|
| 69 |
+
nodes = self.nv
|
| 70 |
+
assert nodes is nodes()
|
| 71 |
+
assert nodes is not nodes(data=True)
|
| 72 |
+
assert nodes is not nodes(data="weight")
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
class TestNodeDataView:
|
| 76 |
+
@classmethod
|
| 77 |
+
def setup_class(cls):
|
| 78 |
+
cls.G = nx.path_graph(9)
|
| 79 |
+
cls.nv = NodeDataView(cls.G)
|
| 80 |
+
cls.ndv = cls.G.nodes.data(True)
|
| 81 |
+
cls.nwv = cls.G.nodes.data("foo")
|
| 82 |
+
|
| 83 |
+
def test_viewtype(self):
|
| 84 |
+
nv = self.G.nodes
|
| 85 |
+
ndvfalse = nv.data(False)
|
| 86 |
+
assert nv is ndvfalse
|
| 87 |
+
assert nv is not self.ndv
|
| 88 |
+
|
| 89 |
+
def test_pickle(self):
|
| 90 |
+
import pickle
|
| 91 |
+
|
| 92 |
+
nv = self.nv
|
| 93 |
+
pnv = pickle.loads(pickle.dumps(nv, -1))
|
| 94 |
+
assert nv == pnv
|
| 95 |
+
assert nv.__slots__ == pnv.__slots__
|
| 96 |
+
|
| 97 |
+
def test_str(self):
|
| 98 |
+
msg = str([(n, {}) for n in range(9)])
|
| 99 |
+
assert str(self.ndv) == msg
|
| 100 |
+
|
| 101 |
+
def test_repr(self):
|
| 102 |
+
expected = "NodeDataView((0, 1, 2, 3, 4, 5, 6, 7, 8))"
|
| 103 |
+
assert repr(self.nv) == expected
|
| 104 |
+
expected = (
|
| 105 |
+
"NodeDataView({0: {}, 1: {}, 2: {}, 3: {}, "
|
| 106 |
+
+ "4: {}, 5: {}, 6: {}, 7: {}, 8: {}})"
|
| 107 |
+
)
|
| 108 |
+
assert repr(self.ndv) == expected
|
| 109 |
+
expected = (
|
| 110 |
+
"NodeDataView({0: None, 1: None, 2: None, 3: None, 4: None, "
|
| 111 |
+
+ "5: None, 6: None, 7: None, 8: None}, data='foo')"
|
| 112 |
+
)
|
| 113 |
+
assert repr(self.nwv) == expected
|
| 114 |
+
|
| 115 |
+
def test_contains(self):
|
| 116 |
+
G = self.G.copy()
|
| 117 |
+
nv = G.nodes.data()
|
| 118 |
+
nwv = G.nodes.data("foo")
|
| 119 |
+
G.nodes[3]["foo"] = "bar"
|
| 120 |
+
assert (7, {}) in nv
|
| 121 |
+
assert (3, {"foo": "bar"}) in nv
|
| 122 |
+
assert (3, "bar") in nwv
|
| 123 |
+
assert (7, None) in nwv
|
| 124 |
+
# default
|
| 125 |
+
nwv_def = G.nodes(data="foo", default="biz")
|
| 126 |
+
assert (7, "biz") in nwv_def
|
| 127 |
+
assert (3, "bar") in nwv_def
|
| 128 |
+
|
| 129 |
+
def test_getitem(self):
|
| 130 |
+
G = self.G.copy()
|
| 131 |
+
nv = G.nodes
|
| 132 |
+
G.nodes[3]["foo"] = "bar"
|
| 133 |
+
assert nv[3] == {"foo": "bar"}
|
| 134 |
+
# default
|
| 135 |
+
nwv_def = G.nodes(data="foo", default="biz")
|
| 136 |
+
assert nwv_def[7], "biz"
|
| 137 |
+
assert nwv_def[3] == "bar"
|
| 138 |
+
# slicing
|
| 139 |
+
with pytest.raises(nx.NetworkXError):
|
| 140 |
+
G.nodes.data()[0:5]
|
| 141 |
+
|
| 142 |
+
def test_iter(self):
|
| 143 |
+
G = self.G.copy()
|
| 144 |
+
nv = G.nodes.data()
|
| 145 |
+
ndv = G.nodes.data(True)
|
| 146 |
+
nwv = G.nodes.data("foo")
|
| 147 |
+
for i, (n, d) in enumerate(nv):
|
| 148 |
+
assert i == n
|
| 149 |
+
assert d == {}
|
| 150 |
+
inv = iter(nv)
|
| 151 |
+
assert next(inv) == (0, {})
|
| 152 |
+
G.nodes[3]["foo"] = "bar"
|
| 153 |
+
# default
|
| 154 |
+
for n, d in nv:
|
| 155 |
+
if n == 3:
|
| 156 |
+
assert d == {"foo": "bar"}
|
| 157 |
+
else:
|
| 158 |
+
assert d == {}
|
| 159 |
+
# data=True
|
| 160 |
+
for n, d in ndv:
|
| 161 |
+
if n == 3:
|
| 162 |
+
assert d == {"foo": "bar"}
|
| 163 |
+
else:
|
| 164 |
+
assert d == {}
|
| 165 |
+
# data='foo'
|
| 166 |
+
for n, d in nwv:
|
| 167 |
+
if n == 3:
|
| 168 |
+
assert d == "bar"
|
| 169 |
+
else:
|
| 170 |
+
assert d is None
|
| 171 |
+
# data='foo', default=1
|
| 172 |
+
for n, d in G.nodes.data("foo", default=1):
|
| 173 |
+
if n == 3:
|
| 174 |
+
assert d == "bar"
|
| 175 |
+
else:
|
| 176 |
+
assert d == 1
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
def test_nodedataview_unhashable():
|
| 180 |
+
G = nx.path_graph(9)
|
| 181 |
+
G.nodes[3]["foo"] = "bar"
|
| 182 |
+
nvs = [G.nodes.data()]
|
| 183 |
+
nvs.append(G.nodes.data(True))
|
| 184 |
+
H = G.copy()
|
| 185 |
+
H.nodes[4]["foo"] = {1, 2, 3}
|
| 186 |
+
nvs.append(H.nodes.data(True))
|
| 187 |
+
# raise unhashable
|
| 188 |
+
for nv in nvs:
|
| 189 |
+
pytest.raises(TypeError, set, nv)
|
| 190 |
+
pytest.raises(TypeError, eval, "nv | nv", locals())
|
| 191 |
+
# no raise... hashable
|
| 192 |
+
Gn = G.nodes.data(False)
|
| 193 |
+
set(Gn)
|
| 194 |
+
Gn | Gn
|
| 195 |
+
Gn = G.nodes.data("foo")
|
| 196 |
+
set(Gn)
|
| 197 |
+
Gn | Gn
|
| 198 |
+
|
| 199 |
+
|
| 200 |
+
class TestNodeViewSetOps:
|
| 201 |
+
@classmethod
|
| 202 |
+
def setup_class(cls):
|
| 203 |
+
cls.G = nx.path_graph(9)
|
| 204 |
+
cls.G.nodes[3]["foo"] = "bar"
|
| 205 |
+
cls.nv = cls.G.nodes
|
| 206 |
+
|
| 207 |
+
def n_its(self, nodes):
|
| 208 |
+
return set(nodes)
|
| 209 |
+
|
| 210 |
+
def test_len(self):
|
| 211 |
+
G = self.G.copy()
|
| 212 |
+
nv = G.nodes
|
| 213 |
+
assert len(nv) == 9
|
| 214 |
+
G.remove_node(7)
|
| 215 |
+
assert len(nv) == 8
|
| 216 |
+
G.add_node(9)
|
| 217 |
+
assert len(nv) == 9
|
| 218 |
+
|
| 219 |
+
def test_and(self):
|
| 220 |
+
# print("G & H nodes:", gnv & hnv)
|
| 221 |
+
nv = self.nv
|
| 222 |
+
some_nodes = self.n_its(range(5, 12))
|
| 223 |
+
assert nv & some_nodes == self.n_its(range(5, 9))
|
| 224 |
+
assert some_nodes & nv == self.n_its(range(5, 9))
|
| 225 |
+
|
| 226 |
+
def test_or(self):
|
| 227 |
+
# print("G | H nodes:", gnv | hnv)
|
| 228 |
+
nv = self.nv
|
| 229 |
+
some_nodes = self.n_its(range(5, 12))
|
| 230 |
+
assert nv | some_nodes == self.n_its(range(12))
|
| 231 |
+
assert some_nodes | nv == self.n_its(range(12))
|
| 232 |
+
|
| 233 |
+
def test_xor(self):
|
| 234 |
+
# print("G ^ H nodes:", gnv ^ hnv)
|
| 235 |
+
nv = self.nv
|
| 236 |
+
some_nodes = self.n_its(range(5, 12))
|
| 237 |
+
nodes = {0, 1, 2, 3, 4, 9, 10, 11}
|
| 238 |
+
assert nv ^ some_nodes == self.n_its(nodes)
|
| 239 |
+
assert some_nodes ^ nv == self.n_its(nodes)
|
| 240 |
+
|
| 241 |
+
def test_sub(self):
|
| 242 |
+
# print("G - H nodes:", gnv - hnv)
|
| 243 |
+
nv = self.nv
|
| 244 |
+
some_nodes = self.n_its(range(5, 12))
|
| 245 |
+
assert nv - some_nodes == self.n_its(range(5))
|
| 246 |
+
assert some_nodes - nv == self.n_its(range(9, 12))
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
class TestNodeDataViewSetOps(TestNodeViewSetOps):
|
| 250 |
+
@classmethod
|
| 251 |
+
def setup_class(cls):
|
| 252 |
+
cls.G = nx.path_graph(9)
|
| 253 |
+
cls.G.nodes[3]["foo"] = "bar"
|
| 254 |
+
cls.nv = cls.G.nodes.data("foo")
|
| 255 |
+
|
| 256 |
+
def n_its(self, nodes):
|
| 257 |
+
return {(node, "bar" if node == 3 else None) for node in nodes}
|
| 258 |
+
|
| 259 |
+
|
| 260 |
+
class TestNodeDataViewDefaultSetOps(TestNodeDataViewSetOps):
|
| 261 |
+
@classmethod
|
| 262 |
+
def setup_class(cls):
|
| 263 |
+
cls.G = nx.path_graph(9)
|
| 264 |
+
cls.G.nodes[3]["foo"] = "bar"
|
| 265 |
+
cls.nv = cls.G.nodes.data("foo", default=1)
|
| 266 |
+
|
| 267 |
+
def n_its(self, nodes):
|
| 268 |
+
return {(node, "bar" if node == 3 else 1) for node in nodes}
|
| 269 |
+
|
| 270 |
+
|
| 271 |
+
# Edges Data View
|
| 272 |
+
class TestEdgeDataView:
|
| 273 |
+
@classmethod
|
| 274 |
+
def setup_class(cls):
|
| 275 |
+
cls.G = nx.path_graph(9)
|
| 276 |
+
cls.eview = nx.reportviews.EdgeView
|
| 277 |
+
|
| 278 |
+
def test_pickle(self):
|
| 279 |
+
import pickle
|
| 280 |
+
|
| 281 |
+
ev = self.eview(self.G)(data=True)
|
| 282 |
+
pev = pickle.loads(pickle.dumps(ev, -1))
|
| 283 |
+
assert list(ev) == list(pev)
|
| 284 |
+
assert ev.__slots__ == pev.__slots__
|
| 285 |
+
|
| 286 |
+
def modify_edge(self, G, e, **kwds):
|
| 287 |
+
G._adj[e[0]][e[1]].update(kwds)
|
| 288 |
+
|
| 289 |
+
def test_str(self):
|
| 290 |
+
ev = self.eview(self.G)(data=True)
|
| 291 |
+
rep = str([(n, n + 1, {}) for n in range(8)])
|
| 292 |
+
assert str(ev) == rep
|
| 293 |
+
|
| 294 |
+
def test_repr(self):
|
| 295 |
+
ev = self.eview(self.G)(data=True)
|
| 296 |
+
rep = (
|
| 297 |
+
"EdgeDataView([(0, 1, {}), (1, 2, {}), "
|
| 298 |
+
+ "(2, 3, {}), (3, 4, {}), "
|
| 299 |
+
+ "(4, 5, {}), (5, 6, {}), "
|
| 300 |
+
+ "(6, 7, {}), (7, 8, {})])"
|
| 301 |
+
)
|
| 302 |
+
assert repr(ev) == rep
|
| 303 |
+
|
| 304 |
+
def test_iterdata(self):
|
| 305 |
+
G = self.G.copy()
|
| 306 |
+
evr = self.eview(G)
|
| 307 |
+
ev = evr(data=True)
|
| 308 |
+
ev_def = evr(data="foo", default=1)
|
| 309 |
+
|
| 310 |
+
for u, v, d in ev:
|
| 311 |
+
pass
|
| 312 |
+
assert d == {}
|
| 313 |
+
|
| 314 |
+
for u, v, wt in ev_def:
|
| 315 |
+
pass
|
| 316 |
+
assert wt == 1
|
| 317 |
+
|
| 318 |
+
self.modify_edge(G, (2, 3), foo="bar")
|
| 319 |
+
for e in ev:
|
| 320 |
+
assert len(e) == 3
|
| 321 |
+
if set(e[:2]) == {2, 3}:
|
| 322 |
+
assert e[2] == {"foo": "bar"}
|
| 323 |
+
checked = True
|
| 324 |
+
else:
|
| 325 |
+
assert e[2] == {}
|
| 326 |
+
assert checked
|
| 327 |
+
|
| 328 |
+
for e in ev_def:
|
| 329 |
+
assert len(e) == 3
|
| 330 |
+
if set(e[:2]) == {2, 3}:
|
| 331 |
+
assert e[2] == "bar"
|
| 332 |
+
checked_wt = True
|
| 333 |
+
else:
|
| 334 |
+
assert e[2] == 1
|
| 335 |
+
assert checked_wt
|
| 336 |
+
|
| 337 |
+
def test_iter(self):
|
| 338 |
+
evr = self.eview(self.G)
|
| 339 |
+
ev = evr()
|
| 340 |
+
for u, v in ev:
|
| 341 |
+
pass
|
| 342 |
+
iev = iter(ev)
|
| 343 |
+
assert next(iev) == (0, 1)
|
| 344 |
+
assert iter(ev) != ev
|
| 345 |
+
assert iter(iev) == iev
|
| 346 |
+
|
| 347 |
+
def test_contains(self):
|
| 348 |
+
evr = self.eview(self.G)
|
| 349 |
+
ev = evr()
|
| 350 |
+
if self.G.is_directed():
|
| 351 |
+
assert (1, 2) in ev and (2, 1) not in ev
|
| 352 |
+
else:
|
| 353 |
+
assert (1, 2) in ev and (2, 1) in ev
|
| 354 |
+
assert (1, 4) not in ev
|
| 355 |
+
assert (1, 90) not in ev
|
| 356 |
+
assert (90, 1) not in ev
|
| 357 |
+
|
| 358 |
+
def test_contains_with_nbunch(self):
|
| 359 |
+
evr = self.eview(self.G)
|
| 360 |
+
ev = evr(nbunch=[0, 2])
|
| 361 |
+
if self.G.is_directed():
|
| 362 |
+
assert (0, 1) in ev
|
| 363 |
+
assert (1, 2) not in ev
|
| 364 |
+
assert (2, 3) in ev
|
| 365 |
+
else:
|
| 366 |
+
assert (0, 1) in ev
|
| 367 |
+
assert (1, 2) in ev
|
| 368 |
+
assert (2, 3) in ev
|
| 369 |
+
assert (3, 4) not in ev
|
| 370 |
+
assert (4, 5) not in ev
|
| 371 |
+
assert (5, 6) not in ev
|
| 372 |
+
assert (7, 8) not in ev
|
| 373 |
+
assert (8, 9) not in ev
|
| 374 |
+
|
| 375 |
+
def test_len(self):
|
| 376 |
+
evr = self.eview(self.G)
|
| 377 |
+
ev = evr(data="foo")
|
| 378 |
+
assert len(ev) == 8
|
| 379 |
+
assert len(evr(1)) == 2
|
| 380 |
+
assert len(evr([1, 2, 3])) == 4
|
| 381 |
+
|
| 382 |
+
assert len(self.G.edges(1)) == 2
|
| 383 |
+
assert len(self.G.edges()) == 8
|
| 384 |
+
assert len(self.G.edges) == 8
|
| 385 |
+
|
| 386 |
+
H = self.G.copy()
|
| 387 |
+
H.add_edge(1, 1)
|
| 388 |
+
assert len(H.edges(1)) == 3
|
| 389 |
+
assert len(H.edges()) == 9
|
| 390 |
+
assert len(H.edges) == 9
|
| 391 |
+
|
| 392 |
+
|
| 393 |
+
class TestOutEdgeDataView(TestEdgeDataView):
|
| 394 |
+
@classmethod
|
| 395 |
+
def setup_class(cls):
|
| 396 |
+
cls.G = nx.path_graph(9, create_using=nx.DiGraph())
|
| 397 |
+
cls.eview = nx.reportviews.OutEdgeView
|
| 398 |
+
|
| 399 |
+
def test_repr(self):
|
| 400 |
+
ev = self.eview(self.G)(data=True)
|
| 401 |
+
rep = (
|
| 402 |
+
"OutEdgeDataView([(0, 1, {}), (1, 2, {}), "
|
| 403 |
+
+ "(2, 3, {}), (3, 4, {}), "
|
| 404 |
+
+ "(4, 5, {}), (5, 6, {}), "
|
| 405 |
+
+ "(6, 7, {}), (7, 8, {})])"
|
| 406 |
+
)
|
| 407 |
+
assert repr(ev) == rep
|
| 408 |
+
|
| 409 |
+
def test_len(self):
|
| 410 |
+
evr = self.eview(self.G)
|
| 411 |
+
ev = evr(data="foo")
|
| 412 |
+
assert len(ev) == 8
|
| 413 |
+
assert len(evr(1)) == 1
|
| 414 |
+
assert len(evr([1, 2, 3])) == 3
|
| 415 |
+
|
| 416 |
+
assert len(self.G.edges(1)) == 1
|
| 417 |
+
assert len(self.G.edges()) == 8
|
| 418 |
+
assert len(self.G.edges) == 8
|
| 419 |
+
|
| 420 |
+
H = self.G.copy()
|
| 421 |
+
H.add_edge(1, 1)
|
| 422 |
+
assert len(H.edges(1)) == 2
|
| 423 |
+
assert len(H.edges()) == 9
|
| 424 |
+
assert len(H.edges) == 9
|
| 425 |
+
|
| 426 |
+
def test_contains_with_nbunch(self):
|
| 427 |
+
evr = self.eview(self.G)
|
| 428 |
+
ev = evr(nbunch=[0, 2])
|
| 429 |
+
assert (0, 1) in ev
|
| 430 |
+
assert (1, 2) not in ev
|
| 431 |
+
assert (2, 3) in ev
|
| 432 |
+
assert (3, 4) not in ev
|
| 433 |
+
assert (4, 5) not in ev
|
| 434 |
+
assert (5, 6) not in ev
|
| 435 |
+
assert (7, 8) not in ev
|
| 436 |
+
assert (8, 9) not in ev
|
| 437 |
+
|
| 438 |
+
|
| 439 |
+
class TestInEdgeDataView(TestOutEdgeDataView):
|
| 440 |
+
@classmethod
|
| 441 |
+
def setup_class(cls):
|
| 442 |
+
cls.G = nx.path_graph(9, create_using=nx.DiGraph())
|
| 443 |
+
cls.eview = nx.reportviews.InEdgeView
|
| 444 |
+
|
| 445 |
+
def test_repr(self):
|
| 446 |
+
ev = self.eview(self.G)(data=True)
|
| 447 |
+
rep = (
|
| 448 |
+
"InEdgeDataView([(0, 1, {}), (1, 2, {}), "
|
| 449 |
+
+ "(2, 3, {}), (3, 4, {}), "
|
| 450 |
+
+ "(4, 5, {}), (5, 6, {}), "
|
| 451 |
+
+ "(6, 7, {}), (7, 8, {})])"
|
| 452 |
+
)
|
| 453 |
+
assert repr(ev) == rep
|
| 454 |
+
|
| 455 |
+
def test_contains_with_nbunch(self):
|
| 456 |
+
evr = self.eview(self.G)
|
| 457 |
+
ev = evr(nbunch=[0, 2])
|
| 458 |
+
assert (0, 1) not in ev
|
| 459 |
+
assert (1, 2) in ev
|
| 460 |
+
assert (2, 3) not in ev
|
| 461 |
+
assert (3, 4) not in ev
|
| 462 |
+
assert (4, 5) not in ev
|
| 463 |
+
assert (5, 6) not in ev
|
| 464 |
+
assert (7, 8) not in ev
|
| 465 |
+
assert (8, 9) not in ev
|
| 466 |
+
|
| 467 |
+
|
| 468 |
+
class TestMultiEdgeDataView(TestEdgeDataView):
|
| 469 |
+
@classmethod
|
| 470 |
+
def setup_class(cls):
|
| 471 |
+
cls.G = nx.path_graph(9, create_using=nx.MultiGraph())
|
| 472 |
+
cls.eview = nx.reportviews.MultiEdgeView
|
| 473 |
+
|
| 474 |
+
def modify_edge(self, G, e, **kwds):
|
| 475 |
+
G._adj[e[0]][e[1]][0].update(kwds)
|
| 476 |
+
|
| 477 |
+
def test_repr(self):
|
| 478 |
+
ev = self.eview(self.G)(data=True)
|
| 479 |
+
rep = (
|
| 480 |
+
"MultiEdgeDataView([(0, 1, {}), (1, 2, {}), "
|
| 481 |
+
+ "(2, 3, {}), (3, 4, {}), "
|
| 482 |
+
+ "(4, 5, {}), (5, 6, {}), "
|
| 483 |
+
+ "(6, 7, {}), (7, 8, {})])"
|
| 484 |
+
)
|
| 485 |
+
assert repr(ev) == rep
|
| 486 |
+
|
| 487 |
+
def test_contains_with_nbunch(self):
|
| 488 |
+
evr = self.eview(self.G)
|
| 489 |
+
ev = evr(nbunch=[0, 2])
|
| 490 |
+
assert (0, 1) in ev
|
| 491 |
+
assert (1, 2) in ev
|
| 492 |
+
assert (2, 3) in ev
|
| 493 |
+
assert (3, 4) not in ev
|
| 494 |
+
assert (4, 5) not in ev
|
| 495 |
+
assert (5, 6) not in ev
|
| 496 |
+
assert (7, 8) not in ev
|
| 497 |
+
assert (8, 9) not in ev
|
| 498 |
+
|
| 499 |
+
|
| 500 |
+
class TestOutMultiEdgeDataView(TestOutEdgeDataView):
|
| 501 |
+
@classmethod
|
| 502 |
+
def setup_class(cls):
|
| 503 |
+
cls.G = nx.path_graph(9, create_using=nx.MultiDiGraph())
|
| 504 |
+
cls.eview = nx.reportviews.OutMultiEdgeView
|
| 505 |
+
|
| 506 |
+
def modify_edge(self, G, e, **kwds):
|
| 507 |
+
G._adj[e[0]][e[1]][0].update(kwds)
|
| 508 |
+
|
| 509 |
+
def test_repr(self):
|
| 510 |
+
ev = self.eview(self.G)(data=True)
|
| 511 |
+
rep = (
|
| 512 |
+
"OutMultiEdgeDataView([(0, 1, {}), (1, 2, {}), "
|
| 513 |
+
+ "(2, 3, {}), (3, 4, {}), "
|
| 514 |
+
+ "(4, 5, {}), (5, 6, {}), "
|
| 515 |
+
+ "(6, 7, {}), (7, 8, {})])"
|
| 516 |
+
)
|
| 517 |
+
assert repr(ev) == rep
|
| 518 |
+
|
| 519 |
+
def test_contains_with_nbunch(self):
|
| 520 |
+
evr = self.eview(self.G)
|
| 521 |
+
ev = evr(nbunch=[0, 2])
|
| 522 |
+
assert (0, 1) in ev
|
| 523 |
+
assert (1, 2) not in ev
|
| 524 |
+
assert (2, 3) in ev
|
| 525 |
+
assert (3, 4) not in ev
|
| 526 |
+
assert (4, 5) not in ev
|
| 527 |
+
assert (5, 6) not in ev
|
| 528 |
+
assert (7, 8) not in ev
|
| 529 |
+
assert (8, 9) not in ev
|
| 530 |
+
|
| 531 |
+
|
| 532 |
+
class TestInMultiEdgeDataView(TestOutMultiEdgeDataView):
|
| 533 |
+
@classmethod
|
| 534 |
+
def setup_class(cls):
|
| 535 |
+
cls.G = nx.path_graph(9, create_using=nx.MultiDiGraph())
|
| 536 |
+
cls.eview = nx.reportviews.InMultiEdgeView
|
| 537 |
+
|
| 538 |
+
def test_repr(self):
|
| 539 |
+
ev = self.eview(self.G)(data=True)
|
| 540 |
+
rep = (
|
| 541 |
+
"InMultiEdgeDataView([(0, 1, {}), (1, 2, {}), "
|
| 542 |
+
+ "(2, 3, {}), (3, 4, {}), "
|
| 543 |
+
+ "(4, 5, {}), (5, 6, {}), "
|
| 544 |
+
+ "(6, 7, {}), (7, 8, {})])"
|
| 545 |
+
)
|
| 546 |
+
assert repr(ev) == rep
|
| 547 |
+
|
| 548 |
+
def test_contains_with_nbunch(self):
|
| 549 |
+
evr = self.eview(self.G)
|
| 550 |
+
ev = evr(nbunch=[0, 2])
|
| 551 |
+
assert (0, 1) not in ev
|
| 552 |
+
assert (1, 2) in ev
|
| 553 |
+
assert (2, 3) not in ev
|
| 554 |
+
assert (3, 4) not in ev
|
| 555 |
+
assert (4, 5) not in ev
|
| 556 |
+
assert (5, 6) not in ev
|
| 557 |
+
assert (7, 8) not in ev
|
| 558 |
+
assert (8, 9) not in ev
|
| 559 |
+
|
| 560 |
+
|
| 561 |
+
# Edge Views
|
| 562 |
+
class TestEdgeView:
|
| 563 |
+
@classmethod
|
| 564 |
+
def setup_class(cls):
|
| 565 |
+
cls.G = nx.path_graph(9)
|
| 566 |
+
cls.eview = nx.reportviews.EdgeView
|
| 567 |
+
|
| 568 |
+
def test_pickle(self):
|
| 569 |
+
import pickle
|
| 570 |
+
|
| 571 |
+
ev = self.eview(self.G)
|
| 572 |
+
pev = pickle.loads(pickle.dumps(ev, -1))
|
| 573 |
+
assert ev == pev
|
| 574 |
+
assert ev.__slots__ == pev.__slots__
|
| 575 |
+
|
| 576 |
+
def modify_edge(self, G, e, **kwds):
|
| 577 |
+
G._adj[e[0]][e[1]].update(kwds)
|
| 578 |
+
|
| 579 |
+
def test_str(self):
|
| 580 |
+
ev = self.eview(self.G)
|
| 581 |
+
rep = str([(n, n + 1) for n in range(8)])
|
| 582 |
+
assert str(ev) == rep
|
| 583 |
+
|
| 584 |
+
def test_repr(self):
|
| 585 |
+
ev = self.eview(self.G)
|
| 586 |
+
rep = (
|
| 587 |
+
"EdgeView([(0, 1), (1, 2), (2, 3), (3, 4), "
|
| 588 |
+
+ "(4, 5), (5, 6), (6, 7), (7, 8)])"
|
| 589 |
+
)
|
| 590 |
+
assert repr(ev) == rep
|
| 591 |
+
|
| 592 |
+
def test_getitem(self):
|
| 593 |
+
G = self.G.copy()
|
| 594 |
+
ev = G.edges
|
| 595 |
+
G.edges[0, 1]["foo"] = "bar"
|
| 596 |
+
assert ev[0, 1] == {"foo": "bar"}
|
| 597 |
+
|
| 598 |
+
# slicing
|
| 599 |
+
with pytest.raises(nx.NetworkXError, match=".*does not support slicing"):
|
| 600 |
+
G.edges[0:5]
|
| 601 |
+
|
| 602 |
+
# Invalid edge
|
| 603 |
+
with pytest.raises(KeyError, match=r".*edge.*is not in the graph."):
|
| 604 |
+
G.edges[0, 9]
|
| 605 |
+
|
| 606 |
+
def test_call(self):
|
| 607 |
+
ev = self.eview(self.G)
|
| 608 |
+
assert id(ev) == id(ev())
|
| 609 |
+
assert id(ev) == id(ev(data=False))
|
| 610 |
+
assert id(ev) != id(ev(data=True))
|
| 611 |
+
assert id(ev) != id(ev(nbunch=1))
|
| 612 |
+
|
| 613 |
+
def test_data(self):
|
| 614 |
+
ev = self.eview(self.G)
|
| 615 |
+
assert id(ev) != id(ev.data())
|
| 616 |
+
assert id(ev) == id(ev.data(data=False))
|
| 617 |
+
assert id(ev) != id(ev.data(data=True))
|
| 618 |
+
assert id(ev) != id(ev.data(nbunch=1))
|
| 619 |
+
|
| 620 |
+
def test_iter(self):
|
| 621 |
+
ev = self.eview(self.G)
|
| 622 |
+
for u, v in ev:
|
| 623 |
+
pass
|
| 624 |
+
iev = iter(ev)
|
| 625 |
+
assert next(iev) == (0, 1)
|
| 626 |
+
assert iter(ev) != ev
|
| 627 |
+
assert iter(iev) == iev
|
| 628 |
+
|
| 629 |
+
def test_contains(self):
|
| 630 |
+
ev = self.eview(self.G)
|
| 631 |
+
edv = ev()
|
| 632 |
+
if self.G.is_directed():
|
| 633 |
+
assert (1, 2) in ev and (2, 1) not in ev
|
| 634 |
+
assert (1, 2) in edv and (2, 1) not in edv
|
| 635 |
+
else:
|
| 636 |
+
assert (1, 2) in ev and (2, 1) in ev
|
| 637 |
+
assert (1, 2) in edv and (2, 1) in edv
|
| 638 |
+
assert (1, 4) not in ev
|
| 639 |
+
assert (1, 4) not in edv
|
| 640 |
+
# edge not in graph
|
| 641 |
+
assert (1, 90) not in ev
|
| 642 |
+
assert (90, 1) not in ev
|
| 643 |
+
assert (1, 90) not in edv
|
| 644 |
+
assert (90, 1) not in edv
|
| 645 |
+
|
| 646 |
+
def test_contains_with_nbunch(self):
|
| 647 |
+
ev = self.eview(self.G)
|
| 648 |
+
evn = ev(nbunch=[0, 2])
|
| 649 |
+
assert (0, 1) in evn
|
| 650 |
+
assert (1, 2) in evn
|
| 651 |
+
assert (2, 3) in evn
|
| 652 |
+
assert (3, 4) not in evn
|
| 653 |
+
assert (4, 5) not in evn
|
| 654 |
+
assert (5, 6) not in evn
|
| 655 |
+
assert (7, 8) not in evn
|
| 656 |
+
assert (8, 9) not in evn
|
| 657 |
+
|
| 658 |
+
def test_len(self):
|
| 659 |
+
ev = self.eview(self.G)
|
| 660 |
+
num_ed = 9 if self.G.is_multigraph() else 8
|
| 661 |
+
assert len(ev) == num_ed
|
| 662 |
+
|
| 663 |
+
H = self.G.copy()
|
| 664 |
+
H.add_edge(1, 1)
|
| 665 |
+
assert len(H.edges(1)) == 3 + H.is_multigraph() - H.is_directed()
|
| 666 |
+
assert len(H.edges()) == num_ed + 1
|
| 667 |
+
assert len(H.edges) == num_ed + 1
|
| 668 |
+
|
| 669 |
+
def test_and(self):
|
| 670 |
+
# print("G & H edges:", gnv & hnv)
|
| 671 |
+
ev = self.eview(self.G)
|
| 672 |
+
some_edges = {(0, 1), (1, 0), (0, 2)}
|
| 673 |
+
if self.G.is_directed():
|
| 674 |
+
assert some_edges & ev, {(0, 1)}
|
| 675 |
+
assert ev & some_edges, {(0, 1)}
|
| 676 |
+
else:
|
| 677 |
+
assert ev & some_edges == {(0, 1), (1, 0)}
|
| 678 |
+
assert some_edges & ev == {(0, 1), (1, 0)}
|
| 679 |
+
return
|
| 680 |
+
|
| 681 |
+
def test_or(self):
|
| 682 |
+
# print("G | H edges:", gnv | hnv)
|
| 683 |
+
ev = self.eview(self.G)
|
| 684 |
+
some_edges = {(0, 1), (1, 0), (0, 2)}
|
| 685 |
+
result1 = {(n, n + 1) for n in range(8)}
|
| 686 |
+
result1.update(some_edges)
|
| 687 |
+
result2 = {(n + 1, n) for n in range(8)}
|
| 688 |
+
result2.update(some_edges)
|
| 689 |
+
assert (ev | some_edges) in (result1, result2)
|
| 690 |
+
assert (some_edges | ev) in (result1, result2)
|
| 691 |
+
|
| 692 |
+
def test_xor(self):
|
| 693 |
+
# print("G ^ H edges:", gnv ^ hnv)
|
| 694 |
+
ev = self.eview(self.G)
|
| 695 |
+
some_edges = {(0, 1), (1, 0), (0, 2)}
|
| 696 |
+
if self.G.is_directed():
|
| 697 |
+
result = {(n, n + 1) for n in range(1, 8)}
|
| 698 |
+
result.update({(1, 0), (0, 2)})
|
| 699 |
+
assert ev ^ some_edges == result
|
| 700 |
+
else:
|
| 701 |
+
result = {(n, n + 1) for n in range(1, 8)}
|
| 702 |
+
result.update({(0, 2)})
|
| 703 |
+
assert ev ^ some_edges == result
|
| 704 |
+
return
|
| 705 |
+
|
| 706 |
+
def test_sub(self):
|
| 707 |
+
# print("G - H edges:", gnv - hnv)
|
| 708 |
+
ev = self.eview(self.G)
|
| 709 |
+
some_edges = {(0, 1), (1, 0), (0, 2)}
|
| 710 |
+
result = {(n, n + 1) for n in range(8)}
|
| 711 |
+
result.remove((0, 1))
|
| 712 |
+
assert ev - some_edges, result
|
| 713 |
+
|
| 714 |
+
|
| 715 |
+
class TestOutEdgeView(TestEdgeView):
|
| 716 |
+
@classmethod
|
| 717 |
+
def setup_class(cls):
|
| 718 |
+
cls.G = nx.path_graph(9, nx.DiGraph())
|
| 719 |
+
cls.eview = nx.reportviews.OutEdgeView
|
| 720 |
+
|
| 721 |
+
def test_repr(self):
|
| 722 |
+
ev = self.eview(self.G)
|
| 723 |
+
rep = (
|
| 724 |
+
"OutEdgeView([(0, 1), (1, 2), (2, 3), (3, 4), "
|
| 725 |
+
+ "(4, 5), (5, 6), (6, 7), (7, 8)])"
|
| 726 |
+
)
|
| 727 |
+
assert repr(ev) == rep
|
| 728 |
+
|
| 729 |
+
def test_contains_with_nbunch(self):
|
| 730 |
+
ev = self.eview(self.G)
|
| 731 |
+
evn = ev(nbunch=[0, 2])
|
| 732 |
+
assert (0, 1) in evn
|
| 733 |
+
assert (1, 2) not in evn
|
| 734 |
+
assert (2, 3) in evn
|
| 735 |
+
assert (3, 4) not in evn
|
| 736 |
+
assert (4, 5) not in evn
|
| 737 |
+
assert (5, 6) not in evn
|
| 738 |
+
assert (7, 8) not in evn
|
| 739 |
+
assert (8, 9) not in evn
|
| 740 |
+
|
| 741 |
+
|
| 742 |
+
class TestInEdgeView(TestEdgeView):
|
| 743 |
+
@classmethod
|
| 744 |
+
def setup_class(cls):
|
| 745 |
+
cls.G = nx.path_graph(9, nx.DiGraph())
|
| 746 |
+
cls.eview = nx.reportviews.InEdgeView
|
| 747 |
+
|
| 748 |
+
def test_repr(self):
|
| 749 |
+
ev = self.eview(self.G)
|
| 750 |
+
rep = (
|
| 751 |
+
"InEdgeView([(0, 1), (1, 2), (2, 3), (3, 4), "
|
| 752 |
+
+ "(4, 5), (5, 6), (6, 7), (7, 8)])"
|
| 753 |
+
)
|
| 754 |
+
assert repr(ev) == rep
|
| 755 |
+
|
| 756 |
+
def test_contains_with_nbunch(self):
|
| 757 |
+
ev = self.eview(self.G)
|
| 758 |
+
evn = ev(nbunch=[0, 2])
|
| 759 |
+
assert (0, 1) not in evn
|
| 760 |
+
assert (1, 2) in evn
|
| 761 |
+
assert (2, 3) not in evn
|
| 762 |
+
assert (3, 4) not in evn
|
| 763 |
+
assert (4, 5) not in evn
|
| 764 |
+
assert (5, 6) not in evn
|
| 765 |
+
assert (7, 8) not in evn
|
| 766 |
+
assert (8, 9) not in evn
|
| 767 |
+
|
| 768 |
+
|
| 769 |
+
class TestMultiEdgeView(TestEdgeView):
|
| 770 |
+
@classmethod
|
| 771 |
+
def setup_class(cls):
|
| 772 |
+
cls.G = nx.path_graph(9, nx.MultiGraph())
|
| 773 |
+
cls.G.add_edge(1, 2, key=3, foo="bar")
|
| 774 |
+
cls.eview = nx.reportviews.MultiEdgeView
|
| 775 |
+
|
| 776 |
+
def modify_edge(self, G, e, **kwds):
|
| 777 |
+
if len(e) == 2:
|
| 778 |
+
e = e + (0,)
|
| 779 |
+
G._adj[e[0]][e[1]][e[2]].update(kwds)
|
| 780 |
+
|
| 781 |
+
def test_str(self):
|
| 782 |
+
ev = self.eview(self.G)
|
| 783 |
+
replist = [(n, n + 1, 0) for n in range(8)]
|
| 784 |
+
replist.insert(2, (1, 2, 3))
|
| 785 |
+
rep = str(replist)
|
| 786 |
+
assert str(ev) == rep
|
| 787 |
+
|
| 788 |
+
def test_getitem(self):
|
| 789 |
+
G = self.G.copy()
|
| 790 |
+
ev = G.edges
|
| 791 |
+
G.edges[0, 1, 0]["foo"] = "bar"
|
| 792 |
+
assert ev[0, 1, 0] == {"foo": "bar"}
|
| 793 |
+
|
| 794 |
+
# slicing
|
| 795 |
+
with pytest.raises(nx.NetworkXError):
|
| 796 |
+
G.edges[0:5]
|
| 797 |
+
|
| 798 |
+
def test_repr(self):
|
| 799 |
+
ev = self.eview(self.G)
|
| 800 |
+
rep = (
|
| 801 |
+
"MultiEdgeView([(0, 1, 0), (1, 2, 0), (1, 2, 3), (2, 3, 0), "
|
| 802 |
+
+ "(3, 4, 0), (4, 5, 0), (5, 6, 0), (6, 7, 0), (7, 8, 0)])"
|
| 803 |
+
)
|
| 804 |
+
assert repr(ev) == rep
|
| 805 |
+
|
| 806 |
+
def test_call(self):
|
| 807 |
+
ev = self.eview(self.G)
|
| 808 |
+
assert id(ev) == id(ev(keys=True))
|
| 809 |
+
assert id(ev) == id(ev(data=False, keys=True))
|
| 810 |
+
assert id(ev) != id(ev(keys=False))
|
| 811 |
+
assert id(ev) != id(ev(data=True))
|
| 812 |
+
assert id(ev) != id(ev(nbunch=1))
|
| 813 |
+
|
| 814 |
+
def test_data(self):
|
| 815 |
+
ev = self.eview(self.G)
|
| 816 |
+
assert id(ev) != id(ev.data())
|
| 817 |
+
assert id(ev) == id(ev.data(data=False, keys=True))
|
| 818 |
+
assert id(ev) != id(ev.data(keys=False))
|
| 819 |
+
assert id(ev) != id(ev.data(data=True))
|
| 820 |
+
assert id(ev) != id(ev.data(nbunch=1))
|
| 821 |
+
|
| 822 |
+
def test_iter(self):
|
| 823 |
+
ev = self.eview(self.G)
|
| 824 |
+
for u, v, k in ev:
|
| 825 |
+
pass
|
| 826 |
+
iev = iter(ev)
|
| 827 |
+
assert next(iev) == (0, 1, 0)
|
| 828 |
+
assert iter(ev) != ev
|
| 829 |
+
assert iter(iev) == iev
|
| 830 |
+
|
| 831 |
+
def test_iterkeys(self):
|
| 832 |
+
G = self.G
|
| 833 |
+
evr = self.eview(G)
|
| 834 |
+
ev = evr(keys=True)
|
| 835 |
+
for u, v, k in ev:
|
| 836 |
+
pass
|
| 837 |
+
assert k == 0
|
| 838 |
+
ev = evr(keys=True, data="foo", default=1)
|
| 839 |
+
for u, v, k, wt in ev:
|
| 840 |
+
pass
|
| 841 |
+
assert wt == 1
|
| 842 |
+
|
| 843 |
+
self.modify_edge(G, (2, 3, 0), foo="bar")
|
| 844 |
+
ev = evr(keys=True, data=True)
|
| 845 |
+
for e in ev:
|
| 846 |
+
assert len(e) == 4
|
| 847 |
+
print("edge:", e)
|
| 848 |
+
if set(e[:2]) == {2, 3}:
|
| 849 |
+
print(self.G._adj[2][3])
|
| 850 |
+
assert e[2] == 0
|
| 851 |
+
assert e[3] == {"foo": "bar"}
|
| 852 |
+
checked = True
|
| 853 |
+
elif set(e[:3]) == {1, 2, 3}:
|
| 854 |
+
assert e[2] == 3
|
| 855 |
+
assert e[3] == {"foo": "bar"}
|
| 856 |
+
checked_multi = True
|
| 857 |
+
else:
|
| 858 |
+
assert e[2] == 0
|
| 859 |
+
assert e[3] == {}
|
| 860 |
+
assert checked
|
| 861 |
+
assert checked_multi
|
| 862 |
+
ev = evr(keys=True, data="foo", default=1)
|
| 863 |
+
for e in ev:
|
| 864 |
+
if set(e[:2]) == {1, 2} and e[2] == 3:
|
| 865 |
+
assert e[3] == "bar"
|
| 866 |
+
if set(e[:2]) == {1, 2} and e[2] == 0:
|
| 867 |
+
assert e[3] == 1
|
| 868 |
+
if set(e[:2]) == {2, 3}:
|
| 869 |
+
assert e[2] == 0
|
| 870 |
+
assert e[3] == "bar"
|
| 871 |
+
assert len(e) == 4
|
| 872 |
+
checked_wt = True
|
| 873 |
+
assert checked_wt
|
| 874 |
+
ev = evr(keys=True)
|
| 875 |
+
for e in ev:
|
| 876 |
+
assert len(e) == 3
|
| 877 |
+
elist = sorted([(i, i + 1, 0) for i in range(8)] + [(1, 2, 3)])
|
| 878 |
+
assert sorted(ev) == elist
|
| 879 |
+
# test that the keyword arguments are passed correctly
|
| 880 |
+
ev = evr((1, 2), "foo", keys=True, default=1)
|
| 881 |
+
with pytest.raises(TypeError):
|
| 882 |
+
evr((1, 2), "foo", True, 1)
|
| 883 |
+
with pytest.raises(TypeError):
|
| 884 |
+
evr((1, 2), "foo", True, default=1)
|
| 885 |
+
for e in ev:
|
| 886 |
+
if set(e[:2]) == {1, 2}:
|
| 887 |
+
assert e[2] in {0, 3}
|
| 888 |
+
if e[2] == 3:
|
| 889 |
+
assert e[3] == "bar"
|
| 890 |
+
else: # e[2] == 0
|
| 891 |
+
assert e[3] == 1
|
| 892 |
+
if G.is_directed():
|
| 893 |
+
assert len(list(ev)) == 3
|
| 894 |
+
else:
|
| 895 |
+
assert len(list(ev)) == 4
|
| 896 |
+
|
| 897 |
+
def test_or(self):
|
| 898 |
+
# print("G | H edges:", gnv | hnv)
|
| 899 |
+
ev = self.eview(self.G)
|
| 900 |
+
some_edges = {(0, 1, 0), (1, 0, 0), (0, 2, 0)}
|
| 901 |
+
result = {(n, n + 1, 0) for n in range(8)}
|
| 902 |
+
result.update(some_edges)
|
| 903 |
+
result.update({(1, 2, 3)})
|
| 904 |
+
assert ev | some_edges == result
|
| 905 |
+
assert some_edges | ev == result
|
| 906 |
+
|
| 907 |
+
def test_sub(self):
|
| 908 |
+
# print("G - H edges:", gnv - hnv)
|
| 909 |
+
ev = self.eview(self.G)
|
| 910 |
+
some_edges = {(0, 1, 0), (1, 0, 0), (0, 2, 0)}
|
| 911 |
+
result = {(n, n + 1, 0) for n in range(8)}
|
| 912 |
+
result.remove((0, 1, 0))
|
| 913 |
+
result.update({(1, 2, 3)})
|
| 914 |
+
assert ev - some_edges, result
|
| 915 |
+
assert some_edges - ev, result
|
| 916 |
+
|
| 917 |
+
def test_xor(self):
|
| 918 |
+
# print("G ^ H edges:", gnv ^ hnv)
|
| 919 |
+
ev = self.eview(self.G)
|
| 920 |
+
some_edges = {(0, 1, 0), (1, 0, 0), (0, 2, 0)}
|
| 921 |
+
if self.G.is_directed():
|
| 922 |
+
result = {(n, n + 1, 0) for n in range(1, 8)}
|
| 923 |
+
result.update({(1, 0, 0), (0, 2, 0), (1, 2, 3)})
|
| 924 |
+
assert ev ^ some_edges == result
|
| 925 |
+
assert some_edges ^ ev == result
|
| 926 |
+
else:
|
| 927 |
+
result = {(n, n + 1, 0) for n in range(1, 8)}
|
| 928 |
+
result.update({(0, 2, 0), (1, 2, 3)})
|
| 929 |
+
assert ev ^ some_edges == result
|
| 930 |
+
assert some_edges ^ ev == result
|
| 931 |
+
|
| 932 |
+
def test_and(self):
|
| 933 |
+
# print("G & H edges:", gnv & hnv)
|
| 934 |
+
ev = self.eview(self.G)
|
| 935 |
+
some_edges = {(0, 1, 0), (1, 0, 0), (0, 2, 0)}
|
| 936 |
+
if self.G.is_directed():
|
| 937 |
+
assert ev & some_edges == {(0, 1, 0)}
|
| 938 |
+
assert some_edges & ev == {(0, 1, 0)}
|
| 939 |
+
else:
|
| 940 |
+
assert ev & some_edges == {(0, 1, 0), (1, 0, 0)}
|
| 941 |
+
assert some_edges & ev == {(0, 1, 0), (1, 0, 0)}
|
| 942 |
+
|
| 943 |
+
def test_contains_with_nbunch(self):
|
| 944 |
+
ev = self.eview(self.G)
|
| 945 |
+
evn = ev(nbunch=[0, 2])
|
| 946 |
+
assert (0, 1) in evn
|
| 947 |
+
assert (1, 2) in evn
|
| 948 |
+
assert (2, 3) in evn
|
| 949 |
+
assert (3, 4) not in evn
|
| 950 |
+
assert (4, 5) not in evn
|
| 951 |
+
assert (5, 6) not in evn
|
| 952 |
+
assert (7, 8) not in evn
|
| 953 |
+
assert (8, 9) not in evn
|
| 954 |
+
|
| 955 |
+
|
| 956 |
+
class TestOutMultiEdgeView(TestMultiEdgeView):
|
| 957 |
+
@classmethod
|
| 958 |
+
def setup_class(cls):
|
| 959 |
+
cls.G = nx.path_graph(9, nx.MultiDiGraph())
|
| 960 |
+
cls.G.add_edge(1, 2, key=3, foo="bar")
|
| 961 |
+
cls.eview = nx.reportviews.OutMultiEdgeView
|
| 962 |
+
|
| 963 |
+
def modify_edge(self, G, e, **kwds):
|
| 964 |
+
if len(e) == 2:
|
| 965 |
+
e = e + (0,)
|
| 966 |
+
G._adj[e[0]][e[1]][e[2]].update(kwds)
|
| 967 |
+
|
| 968 |
+
def test_repr(self):
|
| 969 |
+
ev = self.eview(self.G)
|
| 970 |
+
rep = (
|
| 971 |
+
"OutMultiEdgeView([(0, 1, 0), (1, 2, 0), (1, 2, 3), (2, 3, 0),"
|
| 972 |
+
+ " (3, 4, 0), (4, 5, 0), (5, 6, 0), (6, 7, 0), (7, 8, 0)])"
|
| 973 |
+
)
|
| 974 |
+
assert repr(ev) == rep
|
| 975 |
+
|
| 976 |
+
def test_contains_with_nbunch(self):
|
| 977 |
+
ev = self.eview(self.G)
|
| 978 |
+
evn = ev(nbunch=[0, 2])
|
| 979 |
+
assert (0, 1) in evn
|
| 980 |
+
assert (1, 2) not in evn
|
| 981 |
+
assert (2, 3) in evn
|
| 982 |
+
assert (3, 4) not in evn
|
| 983 |
+
assert (4, 5) not in evn
|
| 984 |
+
assert (5, 6) not in evn
|
| 985 |
+
assert (7, 8) not in evn
|
| 986 |
+
assert (8, 9) not in evn
|
| 987 |
+
|
| 988 |
+
|
| 989 |
+
class TestInMultiEdgeView(TestMultiEdgeView):
|
| 990 |
+
@classmethod
|
| 991 |
+
def setup_class(cls):
|
| 992 |
+
cls.G = nx.path_graph(9, nx.MultiDiGraph())
|
| 993 |
+
cls.G.add_edge(1, 2, key=3, foo="bar")
|
| 994 |
+
cls.eview = nx.reportviews.InMultiEdgeView
|
| 995 |
+
|
| 996 |
+
def modify_edge(self, G, e, **kwds):
|
| 997 |
+
if len(e) == 2:
|
| 998 |
+
e = e + (0,)
|
| 999 |
+
G._adj[e[0]][e[1]][e[2]].update(kwds)
|
| 1000 |
+
|
| 1001 |
+
def test_repr(self):
|
| 1002 |
+
ev = self.eview(self.G)
|
| 1003 |
+
rep = (
|
| 1004 |
+
"InMultiEdgeView([(0, 1, 0), (1, 2, 0), (1, 2, 3), (2, 3, 0), "
|
| 1005 |
+
+ "(3, 4, 0), (4, 5, 0), (5, 6, 0), (6, 7, 0), (7, 8, 0)])"
|
| 1006 |
+
)
|
| 1007 |
+
assert repr(ev) == rep
|
| 1008 |
+
|
| 1009 |
+
def test_contains_with_nbunch(self):
|
| 1010 |
+
ev = self.eview(self.G)
|
| 1011 |
+
evn = ev(nbunch=[0, 2])
|
| 1012 |
+
assert (0, 1) not in evn
|
| 1013 |
+
assert (1, 2) in evn
|
| 1014 |
+
assert (2, 3) not in evn
|
| 1015 |
+
assert (3, 4) not in evn
|
| 1016 |
+
assert (4, 5) not in evn
|
| 1017 |
+
assert (5, 6) not in evn
|
| 1018 |
+
assert (7, 8) not in evn
|
| 1019 |
+
assert (8, 9) not in evn
|
| 1020 |
+
|
| 1021 |
+
|
| 1022 |
+
# Degrees
|
| 1023 |
+
class TestDegreeView:
|
| 1024 |
+
GRAPH = nx.Graph
|
| 1025 |
+
dview = nx.reportviews.DegreeView
|
| 1026 |
+
|
| 1027 |
+
@classmethod
|
| 1028 |
+
def setup_class(cls):
|
| 1029 |
+
cls.G = nx.path_graph(6, cls.GRAPH())
|
| 1030 |
+
cls.G.add_edge(1, 3, foo=2)
|
| 1031 |
+
cls.G.add_edge(1, 3, foo=3)
|
| 1032 |
+
|
| 1033 |
+
def test_pickle(self):
|
| 1034 |
+
import pickle
|
| 1035 |
+
|
| 1036 |
+
deg = self.G.degree
|
| 1037 |
+
pdeg = pickle.loads(pickle.dumps(deg, -1))
|
| 1038 |
+
assert dict(deg) == dict(pdeg)
|
| 1039 |
+
|
| 1040 |
+
def test_str(self):
|
| 1041 |
+
dv = self.dview(self.G)
|
| 1042 |
+
rep = str([(0, 1), (1, 3), (2, 2), (3, 3), (4, 2), (5, 1)])
|
| 1043 |
+
assert str(dv) == rep
|
| 1044 |
+
dv = self.G.degree()
|
| 1045 |
+
assert str(dv) == rep
|
| 1046 |
+
|
| 1047 |
+
def test_repr(self):
|
| 1048 |
+
dv = self.dview(self.G)
|
| 1049 |
+
rep = "DegreeView({0: 1, 1: 3, 2: 2, 3: 3, 4: 2, 5: 1})"
|
| 1050 |
+
assert repr(dv) == rep
|
| 1051 |
+
|
| 1052 |
+
def test_iter(self):
|
| 1053 |
+
dv = self.dview(self.G)
|
| 1054 |
+
for n, d in dv:
|
| 1055 |
+
pass
|
| 1056 |
+
idv = iter(dv)
|
| 1057 |
+
assert iter(dv) != dv
|
| 1058 |
+
assert iter(idv) == idv
|
| 1059 |
+
assert next(idv) == (0, dv[0])
|
| 1060 |
+
assert next(idv) == (1, dv[1])
|
| 1061 |
+
# weighted
|
| 1062 |
+
dv = self.dview(self.G, weight="foo")
|
| 1063 |
+
for n, d in dv:
|
| 1064 |
+
pass
|
| 1065 |
+
idv = iter(dv)
|
| 1066 |
+
assert iter(dv) != dv
|
| 1067 |
+
assert iter(idv) == idv
|
| 1068 |
+
assert next(idv) == (0, dv[0])
|
| 1069 |
+
assert next(idv) == (1, dv[1])
|
| 1070 |
+
|
| 1071 |
+
def test_nbunch(self):
|
| 1072 |
+
dv = self.dview(self.G)
|
| 1073 |
+
dvn = dv(0)
|
| 1074 |
+
assert dvn == 1
|
| 1075 |
+
dvn = dv([2, 3])
|
| 1076 |
+
assert sorted(dvn) == [(2, 2), (3, 3)]
|
| 1077 |
+
|
| 1078 |
+
def test_getitem(self):
|
| 1079 |
+
dv = self.dview(self.G)
|
| 1080 |
+
assert dv[0] == 1
|
| 1081 |
+
assert dv[1] == 3
|
| 1082 |
+
assert dv[2] == 2
|
| 1083 |
+
assert dv[3] == 3
|
| 1084 |
+
dv = self.dview(self.G, weight="foo")
|
| 1085 |
+
assert dv[0] == 1
|
| 1086 |
+
assert dv[1] == 5
|
| 1087 |
+
assert dv[2] == 2
|
| 1088 |
+
assert dv[3] == 5
|
| 1089 |
+
|
| 1090 |
+
def test_weight(self):
|
| 1091 |
+
dv = self.dview(self.G)
|
| 1092 |
+
dvw = dv(0, weight="foo")
|
| 1093 |
+
assert dvw == 1
|
| 1094 |
+
dvw = dv(1, weight="foo")
|
| 1095 |
+
assert dvw == 5
|
| 1096 |
+
dvw = dv([2, 3], weight="foo")
|
| 1097 |
+
assert sorted(dvw) == [(2, 2), (3, 5)]
|
| 1098 |
+
dvd = dict(dv(weight="foo"))
|
| 1099 |
+
assert dvd[0] == 1
|
| 1100 |
+
assert dvd[1] == 5
|
| 1101 |
+
assert dvd[2] == 2
|
| 1102 |
+
assert dvd[3] == 5
|
| 1103 |
+
|
| 1104 |
+
def test_len(self):
|
| 1105 |
+
dv = self.dview(self.G)
|
| 1106 |
+
assert len(dv) == 6
|
| 1107 |
+
|
| 1108 |
+
|
| 1109 |
+
class TestDiDegreeView(TestDegreeView):
|
| 1110 |
+
GRAPH = nx.DiGraph
|
| 1111 |
+
dview = nx.reportviews.DiDegreeView
|
| 1112 |
+
|
| 1113 |
+
def test_repr(self):
|
| 1114 |
+
dv = self.G.degree()
|
| 1115 |
+
rep = "DiDegreeView({0: 1, 1: 3, 2: 2, 3: 3, 4: 2, 5: 1})"
|
| 1116 |
+
assert repr(dv) == rep
|
| 1117 |
+
|
| 1118 |
+
|
| 1119 |
+
class TestOutDegreeView(TestDegreeView):
|
| 1120 |
+
GRAPH = nx.DiGraph
|
| 1121 |
+
dview = nx.reportviews.OutDegreeView
|
| 1122 |
+
|
| 1123 |
+
def test_str(self):
|
| 1124 |
+
dv = self.dview(self.G)
|
| 1125 |
+
rep = str([(0, 1), (1, 2), (2, 1), (3, 1), (4, 1), (5, 0)])
|
| 1126 |
+
assert str(dv) == rep
|
| 1127 |
+
dv = self.G.out_degree()
|
| 1128 |
+
assert str(dv) == rep
|
| 1129 |
+
|
| 1130 |
+
def test_repr(self):
|
| 1131 |
+
dv = self.G.out_degree()
|
| 1132 |
+
rep = "OutDegreeView({0: 1, 1: 2, 2: 1, 3: 1, 4: 1, 5: 0})"
|
| 1133 |
+
assert repr(dv) == rep
|
| 1134 |
+
|
| 1135 |
+
def test_nbunch(self):
|
| 1136 |
+
dv = self.dview(self.G)
|
| 1137 |
+
dvn = dv(0)
|
| 1138 |
+
assert dvn == 1
|
| 1139 |
+
dvn = dv([2, 3])
|
| 1140 |
+
assert sorted(dvn) == [(2, 1), (3, 1)]
|
| 1141 |
+
|
| 1142 |
+
def test_getitem(self):
|
| 1143 |
+
dv = self.dview(self.G)
|
| 1144 |
+
assert dv[0] == 1
|
| 1145 |
+
assert dv[1] == 2
|
| 1146 |
+
assert dv[2] == 1
|
| 1147 |
+
assert dv[3] == 1
|
| 1148 |
+
dv = self.dview(self.G, weight="foo")
|
| 1149 |
+
assert dv[0] == 1
|
| 1150 |
+
assert dv[1] == 4
|
| 1151 |
+
assert dv[2] == 1
|
| 1152 |
+
assert dv[3] == 1
|
| 1153 |
+
|
| 1154 |
+
def test_weight(self):
|
| 1155 |
+
dv = self.dview(self.G)
|
| 1156 |
+
dvw = dv(0, weight="foo")
|
| 1157 |
+
assert dvw == 1
|
| 1158 |
+
dvw = dv(1, weight="foo")
|
| 1159 |
+
assert dvw == 4
|
| 1160 |
+
dvw = dv([2, 3], weight="foo")
|
| 1161 |
+
assert sorted(dvw) == [(2, 1), (3, 1)]
|
| 1162 |
+
dvd = dict(dv(weight="foo"))
|
| 1163 |
+
assert dvd[0] == 1
|
| 1164 |
+
assert dvd[1] == 4
|
| 1165 |
+
assert dvd[2] == 1
|
| 1166 |
+
assert dvd[3] == 1
|
| 1167 |
+
|
| 1168 |
+
|
| 1169 |
+
class TestInDegreeView(TestDegreeView):
|
| 1170 |
+
GRAPH = nx.DiGraph
|
| 1171 |
+
dview = nx.reportviews.InDegreeView
|
| 1172 |
+
|
| 1173 |
+
def test_str(self):
|
| 1174 |
+
dv = self.dview(self.G)
|
| 1175 |
+
rep = str([(0, 0), (1, 1), (2, 1), (3, 2), (4, 1), (5, 1)])
|
| 1176 |
+
assert str(dv) == rep
|
| 1177 |
+
dv = self.G.in_degree()
|
| 1178 |
+
assert str(dv) == rep
|
| 1179 |
+
|
| 1180 |
+
def test_repr(self):
|
| 1181 |
+
dv = self.G.in_degree()
|
| 1182 |
+
rep = "InDegreeView({0: 0, 1: 1, 2: 1, 3: 2, 4: 1, 5: 1})"
|
| 1183 |
+
assert repr(dv) == rep
|
| 1184 |
+
|
| 1185 |
+
def test_nbunch(self):
|
| 1186 |
+
dv = self.dview(self.G)
|
| 1187 |
+
dvn = dv(0)
|
| 1188 |
+
assert dvn == 0
|
| 1189 |
+
dvn = dv([2, 3])
|
| 1190 |
+
assert sorted(dvn) == [(2, 1), (3, 2)]
|
| 1191 |
+
|
| 1192 |
+
def test_getitem(self):
|
| 1193 |
+
dv = self.dview(self.G)
|
| 1194 |
+
assert dv[0] == 0
|
| 1195 |
+
assert dv[1] == 1
|
| 1196 |
+
assert dv[2] == 1
|
| 1197 |
+
assert dv[3] == 2
|
| 1198 |
+
dv = self.dview(self.G, weight="foo")
|
| 1199 |
+
assert dv[0] == 0
|
| 1200 |
+
assert dv[1] == 1
|
| 1201 |
+
assert dv[2] == 1
|
| 1202 |
+
assert dv[3] == 4
|
| 1203 |
+
|
| 1204 |
+
def test_weight(self):
|
| 1205 |
+
dv = self.dview(self.G)
|
| 1206 |
+
dvw = dv(0, weight="foo")
|
| 1207 |
+
assert dvw == 0
|
| 1208 |
+
dvw = dv(1, weight="foo")
|
| 1209 |
+
assert dvw == 1
|
| 1210 |
+
dvw = dv([2, 3], weight="foo")
|
| 1211 |
+
assert sorted(dvw) == [(2, 1), (3, 4)]
|
| 1212 |
+
dvd = dict(dv(weight="foo"))
|
| 1213 |
+
assert dvd[0] == 0
|
| 1214 |
+
assert dvd[1] == 1
|
| 1215 |
+
assert dvd[2] == 1
|
| 1216 |
+
assert dvd[3] == 4
|
| 1217 |
+
|
| 1218 |
+
|
| 1219 |
+
class TestMultiDegreeView(TestDegreeView):
|
| 1220 |
+
GRAPH = nx.MultiGraph
|
| 1221 |
+
dview = nx.reportviews.MultiDegreeView
|
| 1222 |
+
|
| 1223 |
+
def test_str(self):
|
| 1224 |
+
dv = self.dview(self.G)
|
| 1225 |
+
rep = str([(0, 1), (1, 4), (2, 2), (3, 4), (4, 2), (5, 1)])
|
| 1226 |
+
assert str(dv) == rep
|
| 1227 |
+
dv = self.G.degree()
|
| 1228 |
+
assert str(dv) == rep
|
| 1229 |
+
|
| 1230 |
+
def test_repr(self):
|
| 1231 |
+
dv = self.G.degree()
|
| 1232 |
+
rep = "MultiDegreeView({0: 1, 1: 4, 2: 2, 3: 4, 4: 2, 5: 1})"
|
| 1233 |
+
assert repr(dv) == rep
|
| 1234 |
+
|
| 1235 |
+
def test_nbunch(self):
|
| 1236 |
+
dv = self.dview(self.G)
|
| 1237 |
+
dvn = dv(0)
|
| 1238 |
+
assert dvn == 1
|
| 1239 |
+
dvn = dv([2, 3])
|
| 1240 |
+
assert sorted(dvn) == [(2, 2), (3, 4)]
|
| 1241 |
+
|
| 1242 |
+
def test_getitem(self):
|
| 1243 |
+
dv = self.dview(self.G)
|
| 1244 |
+
assert dv[0] == 1
|
| 1245 |
+
assert dv[1] == 4
|
| 1246 |
+
assert dv[2] == 2
|
| 1247 |
+
assert dv[3] == 4
|
| 1248 |
+
dv = self.dview(self.G, weight="foo")
|
| 1249 |
+
assert dv[0] == 1
|
| 1250 |
+
assert dv[1] == 7
|
| 1251 |
+
assert dv[2] == 2
|
| 1252 |
+
assert dv[3] == 7
|
| 1253 |
+
|
| 1254 |
+
def test_weight(self):
|
| 1255 |
+
dv = self.dview(self.G)
|
| 1256 |
+
dvw = dv(0, weight="foo")
|
| 1257 |
+
assert dvw == 1
|
| 1258 |
+
dvw = dv(1, weight="foo")
|
| 1259 |
+
assert dvw == 7
|
| 1260 |
+
dvw = dv([2, 3], weight="foo")
|
| 1261 |
+
assert sorted(dvw) == [(2, 2), (3, 7)]
|
| 1262 |
+
dvd = dict(dv(weight="foo"))
|
| 1263 |
+
assert dvd[0] == 1
|
| 1264 |
+
assert dvd[1] == 7
|
| 1265 |
+
assert dvd[2] == 2
|
| 1266 |
+
assert dvd[3] == 7
|
| 1267 |
+
|
| 1268 |
+
|
| 1269 |
+
class TestDiMultiDegreeView(TestMultiDegreeView):
|
| 1270 |
+
GRAPH = nx.MultiDiGraph
|
| 1271 |
+
dview = nx.reportviews.DiMultiDegreeView
|
| 1272 |
+
|
| 1273 |
+
def test_repr(self):
|
| 1274 |
+
dv = self.G.degree()
|
| 1275 |
+
rep = "DiMultiDegreeView({0: 1, 1: 4, 2: 2, 3: 4, 4: 2, 5: 1})"
|
| 1276 |
+
assert repr(dv) == rep
|
| 1277 |
+
|
| 1278 |
+
|
| 1279 |
+
class TestOutMultiDegreeView(TestDegreeView):
|
| 1280 |
+
GRAPH = nx.MultiDiGraph
|
| 1281 |
+
dview = nx.reportviews.OutMultiDegreeView
|
| 1282 |
+
|
| 1283 |
+
def test_str(self):
|
| 1284 |
+
dv = self.dview(self.G)
|
| 1285 |
+
rep = str([(0, 1), (1, 3), (2, 1), (3, 1), (4, 1), (5, 0)])
|
| 1286 |
+
assert str(dv) == rep
|
| 1287 |
+
dv = self.G.out_degree()
|
| 1288 |
+
assert str(dv) == rep
|
| 1289 |
+
|
| 1290 |
+
def test_repr(self):
|
| 1291 |
+
dv = self.G.out_degree()
|
| 1292 |
+
rep = "OutMultiDegreeView({0: 1, 1: 3, 2: 1, 3: 1, 4: 1, 5: 0})"
|
| 1293 |
+
assert repr(dv) == rep
|
| 1294 |
+
|
| 1295 |
+
def test_nbunch(self):
|
| 1296 |
+
dv = self.dview(self.G)
|
| 1297 |
+
dvn = dv(0)
|
| 1298 |
+
assert dvn == 1
|
| 1299 |
+
dvn = dv([2, 3])
|
| 1300 |
+
assert sorted(dvn) == [(2, 1), (3, 1)]
|
| 1301 |
+
|
| 1302 |
+
def test_getitem(self):
|
| 1303 |
+
dv = self.dview(self.G)
|
| 1304 |
+
assert dv[0] == 1
|
| 1305 |
+
assert dv[1] == 3
|
| 1306 |
+
assert dv[2] == 1
|
| 1307 |
+
assert dv[3] == 1
|
| 1308 |
+
dv = self.dview(self.G, weight="foo")
|
| 1309 |
+
assert dv[0] == 1
|
| 1310 |
+
assert dv[1] == 6
|
| 1311 |
+
assert dv[2] == 1
|
| 1312 |
+
assert dv[3] == 1
|
| 1313 |
+
|
| 1314 |
+
def test_weight(self):
|
| 1315 |
+
dv = self.dview(self.G)
|
| 1316 |
+
dvw = dv(0, weight="foo")
|
| 1317 |
+
assert dvw == 1
|
| 1318 |
+
dvw = dv(1, weight="foo")
|
| 1319 |
+
assert dvw == 6
|
| 1320 |
+
dvw = dv([2, 3], weight="foo")
|
| 1321 |
+
assert sorted(dvw) == [(2, 1), (3, 1)]
|
| 1322 |
+
dvd = dict(dv(weight="foo"))
|
| 1323 |
+
assert dvd[0] == 1
|
| 1324 |
+
assert dvd[1] == 6
|
| 1325 |
+
assert dvd[2] == 1
|
| 1326 |
+
assert dvd[3] == 1
|
| 1327 |
+
|
| 1328 |
+
|
| 1329 |
+
class TestInMultiDegreeView(TestDegreeView):
|
| 1330 |
+
GRAPH = nx.MultiDiGraph
|
| 1331 |
+
dview = nx.reportviews.InMultiDegreeView
|
| 1332 |
+
|
| 1333 |
+
def test_str(self):
|
| 1334 |
+
dv = self.dview(self.G)
|
| 1335 |
+
rep = str([(0, 0), (1, 1), (2, 1), (3, 3), (4, 1), (5, 1)])
|
| 1336 |
+
assert str(dv) == rep
|
| 1337 |
+
dv = self.G.in_degree()
|
| 1338 |
+
assert str(dv) == rep
|
| 1339 |
+
|
| 1340 |
+
def test_repr(self):
|
| 1341 |
+
dv = self.G.in_degree()
|
| 1342 |
+
rep = "InMultiDegreeView({0: 0, 1: 1, 2: 1, 3: 3, 4: 1, 5: 1})"
|
| 1343 |
+
assert repr(dv) == rep
|
| 1344 |
+
|
| 1345 |
+
def test_nbunch(self):
|
| 1346 |
+
dv = self.dview(self.G)
|
| 1347 |
+
dvn = dv(0)
|
| 1348 |
+
assert dvn == 0
|
| 1349 |
+
dvn = dv([2, 3])
|
| 1350 |
+
assert sorted(dvn) == [(2, 1), (3, 3)]
|
| 1351 |
+
|
| 1352 |
+
def test_getitem(self):
|
| 1353 |
+
dv = self.dview(self.G)
|
| 1354 |
+
assert dv[0] == 0
|
| 1355 |
+
assert dv[1] == 1
|
| 1356 |
+
assert dv[2] == 1
|
| 1357 |
+
assert dv[3] == 3
|
| 1358 |
+
dv = self.dview(self.G, weight="foo")
|
| 1359 |
+
assert dv[0] == 0
|
| 1360 |
+
assert dv[1] == 1
|
| 1361 |
+
assert dv[2] == 1
|
| 1362 |
+
assert dv[3] == 6
|
| 1363 |
+
|
| 1364 |
+
def test_weight(self):
|
| 1365 |
+
dv = self.dview(self.G)
|
| 1366 |
+
dvw = dv(0, weight="foo")
|
| 1367 |
+
assert dvw == 0
|
| 1368 |
+
dvw = dv(1, weight="foo")
|
| 1369 |
+
assert dvw == 1
|
| 1370 |
+
dvw = dv([2, 3], weight="foo")
|
| 1371 |
+
assert sorted(dvw) == [(2, 1), (3, 6)]
|
| 1372 |
+
dvd = dict(dv(weight="foo"))
|
| 1373 |
+
assert dvd[0] == 0
|
| 1374 |
+
assert dvd[1] == 1
|
| 1375 |
+
assert dvd[2] == 1
|
| 1376 |
+
assert dvd[3] == 6
|
| 1377 |
+
|
| 1378 |
+
|
| 1379 |
+
@pytest.mark.parametrize(
|
| 1380 |
+
("reportview", "err_msg_terms"),
|
| 1381 |
+
(
|
| 1382 |
+
(rv.NodeView, "list(G.nodes"),
|
| 1383 |
+
(rv.NodeDataView, "list(G.nodes.data"),
|
| 1384 |
+
(rv.EdgeView, "list(G.edges"),
|
| 1385 |
+
# Directed EdgeViews
|
| 1386 |
+
(rv.InEdgeView, "list(G.in_edges"),
|
| 1387 |
+
(rv.OutEdgeView, "list(G.edges"),
|
| 1388 |
+
# Multi EdgeViews
|
| 1389 |
+
(rv.MultiEdgeView, "list(G.edges"),
|
| 1390 |
+
(rv.InMultiEdgeView, "list(G.in_edges"),
|
| 1391 |
+
(rv.OutMultiEdgeView, "list(G.edges"),
|
| 1392 |
+
),
|
| 1393 |
+
)
|
| 1394 |
+
def test_slicing_reportviews(reportview, err_msg_terms):
|
| 1395 |
+
G = nx.complete_graph(3)
|
| 1396 |
+
view = reportview(G)
|
| 1397 |
+
with pytest.raises(nx.NetworkXError) as exc:
|
| 1398 |
+
view[0:2]
|
| 1399 |
+
errmsg = str(exc.value)
|
| 1400 |
+
assert type(view).__name__ in errmsg
|
| 1401 |
+
assert err_msg_terms in errmsg
|
| 1402 |
+
|
| 1403 |
+
|
| 1404 |
+
@pytest.mark.parametrize(
|
| 1405 |
+
"graph", [nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph]
|
| 1406 |
+
)
|
| 1407 |
+
def test_cache_dict_get_set_state(graph):
|
| 1408 |
+
G = nx.path_graph(5, graph())
|
| 1409 |
+
G.nodes, G.edges, G.adj, G.degree
|
| 1410 |
+
if G.is_directed():
|
| 1411 |
+
G.pred, G.succ, G.in_edges, G.out_edges, G.in_degree, G.out_degree
|
| 1412 |
+
cached_dict = G.__dict__
|
| 1413 |
+
assert "nodes" in cached_dict
|
| 1414 |
+
assert "edges" in cached_dict
|
| 1415 |
+
assert "adj" in cached_dict
|
| 1416 |
+
assert "degree" in cached_dict
|
| 1417 |
+
if G.is_directed():
|
| 1418 |
+
assert "pred" in cached_dict
|
| 1419 |
+
assert "succ" in cached_dict
|
| 1420 |
+
assert "in_edges" in cached_dict
|
| 1421 |
+
assert "out_edges" in cached_dict
|
| 1422 |
+
assert "in_degree" in cached_dict
|
| 1423 |
+
assert "out_degree" in cached_dict
|
| 1424 |
+
|
| 1425 |
+
# Raises error if the cached properties and views do not work
|
| 1426 |
+
pickle.loads(pickle.dumps(G, -1))
|
| 1427 |
+
deepcopy(G)
|
| 1428 |
+
|
| 1429 |
+
|
| 1430 |
+
def test_edge_views_inherit_from_EdgeViewABC():
|
| 1431 |
+
all_edge_view_classes = (v for v in dir(nx.reportviews) if "Edge" in v)
|
| 1432 |
+
for eview_class in all_edge_view_classes:
|
| 1433 |
+
assert issubclass(
|
| 1434 |
+
getattr(nx.reportviews, eview_class), nx.reportviews.EdgeViewABC
|
| 1435 |
+
)
|
.venv/lib/python3.11/site-packages/networkx/classes/tests/test_special.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import networkx as nx
|
| 2 |
+
|
| 3 |
+
from .test_digraph import BaseDiGraphTester
|
| 4 |
+
from .test_digraph import TestDiGraph as _TestDiGraph
|
| 5 |
+
from .test_graph import BaseGraphTester
|
| 6 |
+
from .test_graph import TestGraph as _TestGraph
|
| 7 |
+
from .test_multidigraph import TestMultiDiGraph as _TestMultiDiGraph
|
| 8 |
+
from .test_multigraph import TestMultiGraph as _TestMultiGraph
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def test_factories():
|
| 12 |
+
class mydict1(dict):
|
| 13 |
+
pass
|
| 14 |
+
|
| 15 |
+
class mydict2(dict):
|
| 16 |
+
pass
|
| 17 |
+
|
| 18 |
+
class mydict3(dict):
|
| 19 |
+
pass
|
| 20 |
+
|
| 21 |
+
class mydict4(dict):
|
| 22 |
+
pass
|
| 23 |
+
|
| 24 |
+
class mydict5(dict):
|
| 25 |
+
pass
|
| 26 |
+
|
| 27 |
+
for Graph in (nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph):
|
| 28 |
+
# print("testing class: ", Graph.__name__)
|
| 29 |
+
class MyGraph(Graph):
|
| 30 |
+
node_dict_factory = mydict1
|
| 31 |
+
adjlist_outer_dict_factory = mydict2
|
| 32 |
+
adjlist_inner_dict_factory = mydict3
|
| 33 |
+
edge_key_dict_factory = mydict4
|
| 34 |
+
edge_attr_dict_factory = mydict5
|
| 35 |
+
|
| 36 |
+
G = MyGraph()
|
| 37 |
+
assert isinstance(G._node, mydict1)
|
| 38 |
+
assert isinstance(G._adj, mydict2)
|
| 39 |
+
G.add_node(1)
|
| 40 |
+
assert isinstance(G._adj[1], mydict3)
|
| 41 |
+
if G.is_directed():
|
| 42 |
+
assert isinstance(G._pred, mydict2)
|
| 43 |
+
assert isinstance(G._succ, mydict2)
|
| 44 |
+
assert isinstance(G._pred[1], mydict3)
|
| 45 |
+
G.add_edge(1, 2)
|
| 46 |
+
if G.is_multigraph():
|
| 47 |
+
assert isinstance(G._adj[1][2], mydict4)
|
| 48 |
+
assert isinstance(G._adj[1][2][0], mydict5)
|
| 49 |
+
else:
|
| 50 |
+
assert isinstance(G._adj[1][2], mydict5)
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
class TestSpecialGraph(_TestGraph):
|
| 54 |
+
def setup_method(self):
|
| 55 |
+
_TestGraph.setup_method(self)
|
| 56 |
+
self.Graph = nx.Graph
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
class TestThinGraph(BaseGraphTester):
|
| 60 |
+
def setup_method(self):
|
| 61 |
+
all_edge_dict = {"weight": 1}
|
| 62 |
+
|
| 63 |
+
class MyGraph(nx.Graph):
|
| 64 |
+
def edge_attr_dict_factory(self):
|
| 65 |
+
return all_edge_dict
|
| 66 |
+
|
| 67 |
+
self.Graph = MyGraph
|
| 68 |
+
# build dict-of-dict-of-dict K3
|
| 69 |
+
ed1, ed2, ed3 = (all_edge_dict, all_edge_dict, all_edge_dict)
|
| 70 |
+
self.k3adj = {0: {1: ed1, 2: ed2}, 1: {0: ed1, 2: ed3}, 2: {0: ed2, 1: ed3}}
|
| 71 |
+
self.k3edges = [(0, 1), (0, 2), (1, 2)]
|
| 72 |
+
self.k3nodes = [0, 1, 2]
|
| 73 |
+
self.K3 = self.Graph()
|
| 74 |
+
self.K3._adj = self.k3adj
|
| 75 |
+
self.K3._node = {}
|
| 76 |
+
self.K3._node[0] = {}
|
| 77 |
+
self.K3._node[1] = {}
|
| 78 |
+
self.K3._node[2] = {}
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
class TestSpecialDiGraph(_TestDiGraph):
|
| 82 |
+
def setup_method(self):
|
| 83 |
+
_TestDiGraph.setup_method(self)
|
| 84 |
+
self.Graph = nx.DiGraph
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
class TestThinDiGraph(BaseDiGraphTester):
|
| 88 |
+
def setup_method(self):
|
| 89 |
+
all_edge_dict = {"weight": 1}
|
| 90 |
+
|
| 91 |
+
class MyGraph(nx.DiGraph):
|
| 92 |
+
def edge_attr_dict_factory(self):
|
| 93 |
+
return all_edge_dict
|
| 94 |
+
|
| 95 |
+
self.Graph = MyGraph
|
| 96 |
+
# build dict-of-dict-of-dict K3
|
| 97 |
+
ed1, ed2, ed3 = (all_edge_dict, all_edge_dict, all_edge_dict)
|
| 98 |
+
ed4, ed5, ed6 = (all_edge_dict, all_edge_dict, all_edge_dict)
|
| 99 |
+
self.k3adj = {0: {1: ed1, 2: ed2}, 1: {0: ed3, 2: ed4}, 2: {0: ed5, 1: ed6}}
|
| 100 |
+
self.k3edges = [(0, 1), (0, 2), (1, 2)]
|
| 101 |
+
self.k3nodes = [0, 1, 2]
|
| 102 |
+
self.K3 = self.Graph()
|
| 103 |
+
self.K3._succ = self.k3adj
|
| 104 |
+
# K3._adj is synced with K3._succ
|
| 105 |
+
self.K3._pred = {0: {1: ed3, 2: ed5}, 1: {0: ed1, 2: ed6}, 2: {0: ed2, 1: ed4}}
|
| 106 |
+
self.K3._node = {}
|
| 107 |
+
self.K3._node[0] = {}
|
| 108 |
+
self.K3._node[1] = {}
|
| 109 |
+
self.K3._node[2] = {}
|
| 110 |
+
|
| 111 |
+
ed1, ed2 = (all_edge_dict, all_edge_dict)
|
| 112 |
+
self.P3 = self.Graph()
|
| 113 |
+
self.P3._succ = {0: {1: ed1}, 1: {2: ed2}, 2: {}}
|
| 114 |
+
# P3._adj is synced with P3._succ
|
| 115 |
+
self.P3._pred = {0: {}, 1: {0: ed1}, 2: {1: ed2}}
|
| 116 |
+
self.P3._node = {}
|
| 117 |
+
self.P3._node[0] = {}
|
| 118 |
+
self.P3._node[1] = {}
|
| 119 |
+
self.P3._node[2] = {}
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
class TestSpecialMultiGraph(_TestMultiGraph):
|
| 123 |
+
def setup_method(self):
|
| 124 |
+
_TestMultiGraph.setup_method(self)
|
| 125 |
+
self.Graph = nx.MultiGraph
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
class TestSpecialMultiDiGraph(_TestMultiDiGraph):
|
| 129 |
+
def setup_method(self):
|
| 130 |
+
_TestMultiDiGraph.setup_method(self)
|
| 131 |
+
self.Graph = nx.MultiDiGraph
|
.venv/lib/python3.11/site-packages/networkx/classes/tests/test_subgraphviews.py
ADDED
|
@@ -0,0 +1,362 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pytest
|
| 2 |
+
|
| 3 |
+
import networkx as nx
|
| 4 |
+
from networkx.utils import edges_equal
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class TestSubGraphView:
|
| 8 |
+
gview = staticmethod(nx.subgraph_view)
|
| 9 |
+
graph = nx.Graph
|
| 10 |
+
hide_edges_filter = staticmethod(nx.filters.hide_edges)
|
| 11 |
+
show_edges_filter = staticmethod(nx.filters.show_edges)
|
| 12 |
+
|
| 13 |
+
@classmethod
|
| 14 |
+
def setup_class(cls):
|
| 15 |
+
cls.G = nx.path_graph(9, create_using=cls.graph())
|
| 16 |
+
cls.hide_edges_w_hide_nodes = {(3, 4), (4, 5), (5, 6)}
|
| 17 |
+
|
| 18 |
+
def test_hidden_nodes(self):
|
| 19 |
+
hide_nodes = [4, 5, 111]
|
| 20 |
+
nodes_gone = nx.filters.hide_nodes(hide_nodes)
|
| 21 |
+
gview = self.gview
|
| 22 |
+
G = gview(self.G, filter_node=nodes_gone)
|
| 23 |
+
assert self.G.nodes - G.nodes == {4, 5}
|
| 24 |
+
assert self.G.edges - G.edges == self.hide_edges_w_hide_nodes
|
| 25 |
+
if G.is_directed():
|
| 26 |
+
assert list(G[3]) == []
|
| 27 |
+
assert list(G[2]) == [3]
|
| 28 |
+
else:
|
| 29 |
+
assert list(G[3]) == [2]
|
| 30 |
+
assert set(G[2]) == {1, 3}
|
| 31 |
+
pytest.raises(KeyError, G.__getitem__, 4)
|
| 32 |
+
pytest.raises(KeyError, G.__getitem__, 112)
|
| 33 |
+
pytest.raises(KeyError, G.__getitem__, 111)
|
| 34 |
+
assert G.degree(3) == (3 if G.is_multigraph() else 1)
|
| 35 |
+
assert G.size() == (7 if G.is_multigraph() else 5)
|
| 36 |
+
|
| 37 |
+
def test_hidden_edges(self):
|
| 38 |
+
hide_edges = [(2, 3), (8, 7), (222, 223)]
|
| 39 |
+
edges_gone = self.hide_edges_filter(hide_edges)
|
| 40 |
+
gview = self.gview
|
| 41 |
+
G = gview(self.G, filter_edge=edges_gone)
|
| 42 |
+
assert self.G.nodes == G.nodes
|
| 43 |
+
if G.is_directed():
|
| 44 |
+
assert self.G.edges - G.edges == {(2, 3)}
|
| 45 |
+
assert list(G[2]) == []
|
| 46 |
+
assert list(G.pred[3]) == []
|
| 47 |
+
assert list(G.pred[2]) == [1]
|
| 48 |
+
assert G.size() == 7
|
| 49 |
+
else:
|
| 50 |
+
assert self.G.edges - G.edges == {(2, 3), (7, 8)}
|
| 51 |
+
assert list(G[2]) == [1]
|
| 52 |
+
assert G.size() == 6
|
| 53 |
+
assert list(G[3]) == [4]
|
| 54 |
+
pytest.raises(KeyError, G.__getitem__, 221)
|
| 55 |
+
pytest.raises(KeyError, G.__getitem__, 222)
|
| 56 |
+
assert G.degree(3) == 1
|
| 57 |
+
|
| 58 |
+
def test_shown_node(self):
|
| 59 |
+
induced_subgraph = nx.filters.show_nodes([2, 3, 111])
|
| 60 |
+
gview = self.gview
|
| 61 |
+
G = gview(self.G, filter_node=induced_subgraph)
|
| 62 |
+
assert set(G.nodes) == {2, 3}
|
| 63 |
+
if G.is_directed():
|
| 64 |
+
assert list(G[3]) == []
|
| 65 |
+
else:
|
| 66 |
+
assert list(G[3]) == [2]
|
| 67 |
+
assert list(G[2]) == [3]
|
| 68 |
+
pytest.raises(KeyError, G.__getitem__, 4)
|
| 69 |
+
pytest.raises(KeyError, G.__getitem__, 112)
|
| 70 |
+
pytest.raises(KeyError, G.__getitem__, 111)
|
| 71 |
+
assert G.degree(3) == (3 if G.is_multigraph() else 1)
|
| 72 |
+
assert G.size() == (3 if G.is_multigraph() else 1)
|
| 73 |
+
|
| 74 |
+
def test_shown_edges(self):
|
| 75 |
+
show_edges = [(2, 3), (8, 7), (222, 223)]
|
| 76 |
+
edge_subgraph = self.show_edges_filter(show_edges)
|
| 77 |
+
G = self.gview(self.G, filter_edge=edge_subgraph)
|
| 78 |
+
assert self.G.nodes == G.nodes
|
| 79 |
+
if G.is_directed():
|
| 80 |
+
assert G.edges == {(2, 3)}
|
| 81 |
+
assert list(G[3]) == []
|
| 82 |
+
assert list(G[2]) == [3]
|
| 83 |
+
assert list(G.pred[3]) == [2]
|
| 84 |
+
assert list(G.pred[2]) == []
|
| 85 |
+
assert G.size() == 1
|
| 86 |
+
else:
|
| 87 |
+
assert G.edges == {(2, 3), (7, 8)}
|
| 88 |
+
assert list(G[3]) == [2]
|
| 89 |
+
assert list(G[2]) == [3]
|
| 90 |
+
assert G.size() == 2
|
| 91 |
+
pytest.raises(KeyError, G.__getitem__, 221)
|
| 92 |
+
pytest.raises(KeyError, G.__getitem__, 222)
|
| 93 |
+
assert G.degree(3) == 1
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
class TestSubDiGraphView(TestSubGraphView):
|
| 97 |
+
gview = staticmethod(nx.subgraph_view)
|
| 98 |
+
graph = nx.DiGraph
|
| 99 |
+
hide_edges_filter = staticmethod(nx.filters.hide_diedges)
|
| 100 |
+
show_edges_filter = staticmethod(nx.filters.show_diedges)
|
| 101 |
+
hide_edges = [(2, 3), (8, 7), (222, 223)]
|
| 102 |
+
excluded = {(2, 3), (3, 4), (4, 5), (5, 6)}
|
| 103 |
+
|
| 104 |
+
def test_inoutedges(self):
|
| 105 |
+
edges_gone = self.hide_edges_filter(self.hide_edges)
|
| 106 |
+
hide_nodes = [4, 5, 111]
|
| 107 |
+
nodes_gone = nx.filters.hide_nodes(hide_nodes)
|
| 108 |
+
G = self.gview(self.G, filter_node=nodes_gone, filter_edge=edges_gone)
|
| 109 |
+
|
| 110 |
+
assert self.G.in_edges - G.in_edges == self.excluded
|
| 111 |
+
assert self.G.out_edges - G.out_edges == self.excluded
|
| 112 |
+
|
| 113 |
+
def test_pred(self):
|
| 114 |
+
edges_gone = self.hide_edges_filter(self.hide_edges)
|
| 115 |
+
hide_nodes = [4, 5, 111]
|
| 116 |
+
nodes_gone = nx.filters.hide_nodes(hide_nodes)
|
| 117 |
+
G = self.gview(self.G, filter_node=nodes_gone, filter_edge=edges_gone)
|
| 118 |
+
|
| 119 |
+
assert list(G.pred[2]) == [1]
|
| 120 |
+
assert list(G.pred[6]) == []
|
| 121 |
+
|
| 122 |
+
def test_inout_degree(self):
|
| 123 |
+
edges_gone = self.hide_edges_filter(self.hide_edges)
|
| 124 |
+
hide_nodes = [4, 5, 111]
|
| 125 |
+
nodes_gone = nx.filters.hide_nodes(hide_nodes)
|
| 126 |
+
G = self.gview(self.G, filter_node=nodes_gone, filter_edge=edges_gone)
|
| 127 |
+
|
| 128 |
+
assert G.degree(2) == 1
|
| 129 |
+
assert G.out_degree(2) == 0
|
| 130 |
+
assert G.in_degree(2) == 1
|
| 131 |
+
assert G.size() == 4
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
# multigraph
|
| 135 |
+
class TestMultiGraphView(TestSubGraphView):
|
| 136 |
+
gview = staticmethod(nx.subgraph_view)
|
| 137 |
+
graph = nx.MultiGraph
|
| 138 |
+
hide_edges_filter = staticmethod(nx.filters.hide_multiedges)
|
| 139 |
+
show_edges_filter = staticmethod(nx.filters.show_multiedges)
|
| 140 |
+
|
| 141 |
+
@classmethod
|
| 142 |
+
def setup_class(cls):
|
| 143 |
+
cls.G = nx.path_graph(9, create_using=cls.graph())
|
| 144 |
+
multiedges = {(2, 3, 4), (2, 3, 5)}
|
| 145 |
+
cls.G.add_edges_from(multiedges)
|
| 146 |
+
cls.hide_edges_w_hide_nodes = {(3, 4, 0), (4, 5, 0), (5, 6, 0)}
|
| 147 |
+
|
| 148 |
+
def test_hidden_edges(self):
|
| 149 |
+
hide_edges = [(2, 3, 4), (2, 3, 3), (8, 7, 0), (222, 223, 0)]
|
| 150 |
+
edges_gone = self.hide_edges_filter(hide_edges)
|
| 151 |
+
G = self.gview(self.G, filter_edge=edges_gone)
|
| 152 |
+
assert self.G.nodes == G.nodes
|
| 153 |
+
if G.is_directed():
|
| 154 |
+
assert self.G.edges - G.edges == {(2, 3, 4)}
|
| 155 |
+
assert list(G[3]) == [4]
|
| 156 |
+
assert list(G[2]) == [3]
|
| 157 |
+
assert list(G.pred[3]) == [2] # only one 2 but two edges
|
| 158 |
+
assert list(G.pred[2]) == [1]
|
| 159 |
+
assert G.size() == 9
|
| 160 |
+
else:
|
| 161 |
+
assert self.G.edges - G.edges == {(2, 3, 4), (7, 8, 0)}
|
| 162 |
+
assert list(G[3]) == [2, 4]
|
| 163 |
+
assert list(G[2]) == [1, 3]
|
| 164 |
+
assert G.size() == 8
|
| 165 |
+
assert G.degree(3) == 3
|
| 166 |
+
pytest.raises(KeyError, G.__getitem__, 221)
|
| 167 |
+
pytest.raises(KeyError, G.__getitem__, 222)
|
| 168 |
+
|
| 169 |
+
def test_shown_edges(self):
|
| 170 |
+
show_edges = [(2, 3, 4), (2, 3, 3), (8, 7, 0), (222, 223, 0)]
|
| 171 |
+
edge_subgraph = self.show_edges_filter(show_edges)
|
| 172 |
+
G = self.gview(self.G, filter_edge=edge_subgraph)
|
| 173 |
+
assert self.G.nodes == G.nodes
|
| 174 |
+
if G.is_directed():
|
| 175 |
+
assert G.edges == {(2, 3, 4)}
|
| 176 |
+
assert list(G[3]) == []
|
| 177 |
+
assert list(G.pred[3]) == [2]
|
| 178 |
+
assert list(G.pred[2]) == []
|
| 179 |
+
assert G.size() == 1
|
| 180 |
+
else:
|
| 181 |
+
assert G.edges == {(2, 3, 4), (7, 8, 0)}
|
| 182 |
+
assert G.size() == 2
|
| 183 |
+
assert list(G[3]) == [2]
|
| 184 |
+
assert G.degree(3) == 1
|
| 185 |
+
assert list(G[2]) == [3]
|
| 186 |
+
pytest.raises(KeyError, G.__getitem__, 221)
|
| 187 |
+
pytest.raises(KeyError, G.__getitem__, 222)
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
# multidigraph
|
| 191 |
+
class TestMultiDiGraphView(TestMultiGraphView, TestSubDiGraphView):
|
| 192 |
+
gview = staticmethod(nx.subgraph_view)
|
| 193 |
+
graph = nx.MultiDiGraph
|
| 194 |
+
hide_edges_filter = staticmethod(nx.filters.hide_multidiedges)
|
| 195 |
+
show_edges_filter = staticmethod(nx.filters.show_multidiedges)
|
| 196 |
+
hide_edges = [(2, 3, 0), (8, 7, 0), (222, 223, 0)]
|
| 197 |
+
excluded = {(2, 3, 0), (3, 4, 0), (4, 5, 0), (5, 6, 0)}
|
| 198 |
+
|
| 199 |
+
def test_inout_degree(self):
|
| 200 |
+
edges_gone = self.hide_edges_filter(self.hide_edges)
|
| 201 |
+
hide_nodes = [4, 5, 111]
|
| 202 |
+
nodes_gone = nx.filters.hide_nodes(hide_nodes)
|
| 203 |
+
G = self.gview(self.G, filter_node=nodes_gone, filter_edge=edges_gone)
|
| 204 |
+
|
| 205 |
+
assert G.degree(2) == 3
|
| 206 |
+
assert G.out_degree(2) == 2
|
| 207 |
+
assert G.in_degree(2) == 1
|
| 208 |
+
assert G.size() == 6
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
# induced_subgraph
|
| 212 |
+
class TestInducedSubGraph:
|
| 213 |
+
@classmethod
|
| 214 |
+
def setup_class(cls):
|
| 215 |
+
cls.K3 = G = nx.complete_graph(3)
|
| 216 |
+
G.graph["foo"] = []
|
| 217 |
+
G.nodes[0]["foo"] = []
|
| 218 |
+
G.remove_edge(1, 2)
|
| 219 |
+
ll = []
|
| 220 |
+
G.add_edge(1, 2, foo=ll)
|
| 221 |
+
G.add_edge(2, 1, foo=ll)
|
| 222 |
+
|
| 223 |
+
def test_full_graph(self):
|
| 224 |
+
G = self.K3
|
| 225 |
+
H = nx.induced_subgraph(G, [0, 1, 2, 5])
|
| 226 |
+
assert H.name == G.name
|
| 227 |
+
self.graphs_equal(H, G)
|
| 228 |
+
self.same_attrdict(H, G)
|
| 229 |
+
|
| 230 |
+
def test_partial_subgraph(self):
|
| 231 |
+
G = self.K3
|
| 232 |
+
H = nx.induced_subgraph(G, 0)
|
| 233 |
+
assert dict(H.adj) == {0: {}}
|
| 234 |
+
assert dict(G.adj) != {0: {}}
|
| 235 |
+
|
| 236 |
+
H = nx.induced_subgraph(G, [0, 1])
|
| 237 |
+
assert dict(H.adj) == {0: {1: {}}, 1: {0: {}}}
|
| 238 |
+
|
| 239 |
+
def same_attrdict(self, H, G):
|
| 240 |
+
old_foo = H[1][2]["foo"]
|
| 241 |
+
H.edges[1, 2]["foo"] = "baz"
|
| 242 |
+
assert G.edges == H.edges
|
| 243 |
+
H.edges[1, 2]["foo"] = old_foo
|
| 244 |
+
assert G.edges == H.edges
|
| 245 |
+
old_foo = H.nodes[0]["foo"]
|
| 246 |
+
H.nodes[0]["foo"] = "baz"
|
| 247 |
+
assert G.nodes == H.nodes
|
| 248 |
+
H.nodes[0]["foo"] = old_foo
|
| 249 |
+
assert G.nodes == H.nodes
|
| 250 |
+
|
| 251 |
+
def graphs_equal(self, H, G):
|
| 252 |
+
assert G._adj == H._adj
|
| 253 |
+
assert G._node == H._node
|
| 254 |
+
assert G.graph == H.graph
|
| 255 |
+
assert G.name == H.name
|
| 256 |
+
if not G.is_directed() and not H.is_directed():
|
| 257 |
+
assert H._adj[1][2] is H._adj[2][1]
|
| 258 |
+
assert G._adj[1][2] is G._adj[2][1]
|
| 259 |
+
else: # at least one is directed
|
| 260 |
+
if not G.is_directed():
|
| 261 |
+
G._pred = G._adj
|
| 262 |
+
G._succ = G._adj
|
| 263 |
+
if not H.is_directed():
|
| 264 |
+
H._pred = H._adj
|
| 265 |
+
H._succ = H._adj
|
| 266 |
+
assert G._pred == H._pred
|
| 267 |
+
assert G._succ == H._succ
|
| 268 |
+
assert H._succ[1][2] is H._pred[2][1]
|
| 269 |
+
assert G._succ[1][2] is G._pred[2][1]
|
| 270 |
+
|
| 271 |
+
|
| 272 |
+
# edge_subgraph
|
| 273 |
+
class TestEdgeSubGraph:
|
| 274 |
+
@classmethod
|
| 275 |
+
def setup_class(cls):
|
| 276 |
+
# Create a path graph on five nodes.
|
| 277 |
+
cls.G = G = nx.path_graph(5)
|
| 278 |
+
# Add some node, edge, and graph attributes.
|
| 279 |
+
for i in range(5):
|
| 280 |
+
G.nodes[i]["name"] = f"node{i}"
|
| 281 |
+
G.edges[0, 1]["name"] = "edge01"
|
| 282 |
+
G.edges[3, 4]["name"] = "edge34"
|
| 283 |
+
G.graph["name"] = "graph"
|
| 284 |
+
# Get the subgraph induced by the first and last edges.
|
| 285 |
+
cls.H = nx.edge_subgraph(G, [(0, 1), (3, 4)])
|
| 286 |
+
|
| 287 |
+
def test_correct_nodes(self):
|
| 288 |
+
"""Tests that the subgraph has the correct nodes."""
|
| 289 |
+
assert [(0, "node0"), (1, "node1"), (3, "node3"), (4, "node4")] == sorted(
|
| 290 |
+
self.H.nodes.data("name")
|
| 291 |
+
)
|
| 292 |
+
|
| 293 |
+
def test_correct_edges(self):
|
| 294 |
+
"""Tests that the subgraph has the correct edges."""
|
| 295 |
+
assert edges_equal(
|
| 296 |
+
[(0, 1, "edge01"), (3, 4, "edge34")], self.H.edges.data("name")
|
| 297 |
+
)
|
| 298 |
+
|
| 299 |
+
def test_add_node(self):
|
| 300 |
+
"""Tests that adding a node to the original graph does not
|
| 301 |
+
affect the nodes of the subgraph.
|
| 302 |
+
|
| 303 |
+
"""
|
| 304 |
+
self.G.add_node(5)
|
| 305 |
+
assert [0, 1, 3, 4] == sorted(self.H.nodes)
|
| 306 |
+
self.G.remove_node(5)
|
| 307 |
+
|
| 308 |
+
def test_remove_node(self):
|
| 309 |
+
"""Tests that removing a node in the original graph
|
| 310 |
+
removes the nodes of the subgraph.
|
| 311 |
+
|
| 312 |
+
"""
|
| 313 |
+
self.G.remove_node(0)
|
| 314 |
+
assert [1, 3, 4] == sorted(self.H.nodes)
|
| 315 |
+
self.G.add_node(0, name="node0")
|
| 316 |
+
self.G.add_edge(0, 1, name="edge01")
|
| 317 |
+
|
| 318 |
+
def test_node_attr_dict(self):
|
| 319 |
+
"""Tests that the node attribute dictionary of the two graphs is
|
| 320 |
+
the same object.
|
| 321 |
+
|
| 322 |
+
"""
|
| 323 |
+
for v in self.H:
|
| 324 |
+
assert self.G.nodes[v] == self.H.nodes[v]
|
| 325 |
+
# Making a change to G should make a change in H and vice versa.
|
| 326 |
+
self.G.nodes[0]["name"] = "foo"
|
| 327 |
+
assert self.G.nodes[0] == self.H.nodes[0]
|
| 328 |
+
self.H.nodes[1]["name"] = "bar"
|
| 329 |
+
assert self.G.nodes[1] == self.H.nodes[1]
|
| 330 |
+
# Revert the change, so tests pass with pytest-randomly
|
| 331 |
+
self.G.nodes[0]["name"] = "node0"
|
| 332 |
+
self.H.nodes[1]["name"] = "node1"
|
| 333 |
+
|
| 334 |
+
def test_edge_attr_dict(self):
|
| 335 |
+
"""Tests that the edge attribute dictionary of the two graphs is
|
| 336 |
+
the same object.
|
| 337 |
+
|
| 338 |
+
"""
|
| 339 |
+
for u, v in self.H.edges():
|
| 340 |
+
assert self.G.edges[u, v] == self.H.edges[u, v]
|
| 341 |
+
# Making a change to G should make a change in H and vice versa.
|
| 342 |
+
self.G.edges[0, 1]["name"] = "foo"
|
| 343 |
+
assert self.G.edges[0, 1]["name"] == self.H.edges[0, 1]["name"]
|
| 344 |
+
self.H.edges[3, 4]["name"] = "bar"
|
| 345 |
+
assert self.G.edges[3, 4]["name"] == self.H.edges[3, 4]["name"]
|
| 346 |
+
# Revert the change, so tests pass with pytest-randomly
|
| 347 |
+
self.G.edges[0, 1]["name"] = "edge01"
|
| 348 |
+
self.H.edges[3, 4]["name"] = "edge34"
|
| 349 |
+
|
| 350 |
+
def test_graph_attr_dict(self):
|
| 351 |
+
"""Tests that the graph attribute dictionary of the two graphs
|
| 352 |
+
is the same object.
|
| 353 |
+
|
| 354 |
+
"""
|
| 355 |
+
assert self.G.graph is self.H.graph
|
| 356 |
+
|
| 357 |
+
def test_readonly(self):
|
| 358 |
+
"""Tests that the subgraph cannot change the graph structure"""
|
| 359 |
+
pytest.raises(nx.NetworkXError, self.H.add_node, 5)
|
| 360 |
+
pytest.raises(nx.NetworkXError, self.H.remove_node, 0)
|
| 361 |
+
pytest.raises(nx.NetworkXError, self.H.add_edge, 5, 6)
|
| 362 |
+
pytest.raises(nx.NetworkXError, self.H.remove_edge, 0, 1)
|
.venv/lib/python3.11/site-packages/networkx/readwrite/__init__.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
A package for reading and writing graphs in various formats.
|
| 3 |
+
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from networkx.readwrite.adjlist import *
|
| 7 |
+
from networkx.readwrite.multiline_adjlist import *
|
| 8 |
+
from networkx.readwrite.edgelist import *
|
| 9 |
+
from networkx.readwrite.pajek import *
|
| 10 |
+
from networkx.readwrite.leda import *
|
| 11 |
+
from networkx.readwrite.sparse6 import *
|
| 12 |
+
from networkx.readwrite.graph6 import *
|
| 13 |
+
from networkx.readwrite.gml import *
|
| 14 |
+
from networkx.readwrite.graphml import *
|
| 15 |
+
from networkx.readwrite.gexf import *
|
| 16 |
+
from networkx.readwrite.json_graph import *
|
| 17 |
+
from networkx.readwrite.text import *
|
.venv/lib/python3.11/site-packages/networkx/readwrite/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (858 Bytes). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/readwrite/__pycache__/gexf.cpython-311.pyc
ADDED
|
Binary file (49.5 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/readwrite/__pycache__/gml.cpython-311.pyc
ADDED
|
Binary file (39.1 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/readwrite/__pycache__/graph6.cpython-311.pyc
ADDED
|
Binary file (15.7 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/readwrite/__pycache__/multiline_adjlist.cpython-311.pyc
ADDED
|
Binary file (14.4 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/readwrite/__pycache__/text.cpython-311.pyc
ADDED
|
Binary file (30 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/readwrite/gexf.py
ADDED
|
@@ -0,0 +1,1066 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Read and write graphs in GEXF format.
|
| 2 |
+
|
| 3 |
+
.. warning::
|
| 4 |
+
This parser uses the standard xml library present in Python, which is
|
| 5 |
+
insecure - see :external+python:mod:`xml` for additional information.
|
| 6 |
+
Only parse GEFX files you trust.
|
| 7 |
+
|
| 8 |
+
GEXF (Graph Exchange XML Format) is a language for describing complex
|
| 9 |
+
network structures, their associated data and dynamics.
|
| 10 |
+
|
| 11 |
+
This implementation does not support mixed graphs (directed and
|
| 12 |
+
undirected edges together).
|
| 13 |
+
|
| 14 |
+
Format
|
| 15 |
+
------
|
| 16 |
+
GEXF is an XML format. See http://gexf.net/schema.html for the
|
| 17 |
+
specification and http://gexf.net/basic.html for examples.
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
import itertools
|
| 21 |
+
import time
|
| 22 |
+
from xml.etree.ElementTree import (
|
| 23 |
+
Element,
|
| 24 |
+
ElementTree,
|
| 25 |
+
SubElement,
|
| 26 |
+
register_namespace,
|
| 27 |
+
tostring,
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
import networkx as nx
|
| 31 |
+
from networkx.utils import open_file
|
| 32 |
+
|
| 33 |
+
__all__ = ["write_gexf", "read_gexf", "relabel_gexf_graph", "generate_gexf"]
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
@open_file(1, mode="wb")
|
| 37 |
+
def write_gexf(G, path, encoding="utf-8", prettyprint=True, version="1.2draft"):
|
| 38 |
+
"""Write G in GEXF format to path.
|
| 39 |
+
|
| 40 |
+
"GEXF (Graph Exchange XML Format) is a language for describing
|
| 41 |
+
complex networks structures, their associated data and dynamics" [1]_.
|
| 42 |
+
|
| 43 |
+
Node attributes are checked according to the version of the GEXF
|
| 44 |
+
schemas used for parameters which are not user defined,
|
| 45 |
+
e.g. visualization 'viz' [2]_. See example for usage.
|
| 46 |
+
|
| 47 |
+
Parameters
|
| 48 |
+
----------
|
| 49 |
+
G : graph
|
| 50 |
+
A NetworkX graph
|
| 51 |
+
path : file or string
|
| 52 |
+
File or file name to write.
|
| 53 |
+
File names ending in .gz or .bz2 will be compressed.
|
| 54 |
+
encoding : string (optional, default: 'utf-8')
|
| 55 |
+
Encoding for text data.
|
| 56 |
+
prettyprint : bool (optional, default: True)
|
| 57 |
+
If True use line breaks and indenting in output XML.
|
| 58 |
+
version: string (optional, default: '1.2draft')
|
| 59 |
+
The version of GEXF to be used for nodes attributes checking
|
| 60 |
+
|
| 61 |
+
Examples
|
| 62 |
+
--------
|
| 63 |
+
>>> G = nx.path_graph(4)
|
| 64 |
+
>>> nx.write_gexf(G, "test.gexf")
|
| 65 |
+
|
| 66 |
+
# visualization data
|
| 67 |
+
>>> G.nodes[0]["viz"] = {"size": 54}
|
| 68 |
+
>>> G.nodes[0]["viz"]["position"] = {"x": 0, "y": 1}
|
| 69 |
+
>>> G.nodes[0]["viz"]["color"] = {"r": 0, "g": 0, "b": 256}
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
Notes
|
| 73 |
+
-----
|
| 74 |
+
This implementation does not support mixed graphs (directed and undirected
|
| 75 |
+
edges together).
|
| 76 |
+
|
| 77 |
+
The node id attribute is set to be the string of the node label.
|
| 78 |
+
If you want to specify an id use set it as node data, e.g.
|
| 79 |
+
node['a']['id']=1 to set the id of node 'a' to 1.
|
| 80 |
+
|
| 81 |
+
References
|
| 82 |
+
----------
|
| 83 |
+
.. [1] GEXF File Format, http://gexf.net/
|
| 84 |
+
.. [2] GEXF schema, http://gexf.net/schema.html
|
| 85 |
+
"""
|
| 86 |
+
writer = GEXFWriter(encoding=encoding, prettyprint=prettyprint, version=version)
|
| 87 |
+
writer.add_graph(G)
|
| 88 |
+
writer.write(path)
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def generate_gexf(G, encoding="utf-8", prettyprint=True, version="1.2draft"):
|
| 92 |
+
"""Generate lines of GEXF format representation of G.
|
| 93 |
+
|
| 94 |
+
"GEXF (Graph Exchange XML Format) is a language for describing
|
| 95 |
+
complex networks structures, their associated data and dynamics" [1]_.
|
| 96 |
+
|
| 97 |
+
Parameters
|
| 98 |
+
----------
|
| 99 |
+
G : graph
|
| 100 |
+
A NetworkX graph
|
| 101 |
+
encoding : string (optional, default: 'utf-8')
|
| 102 |
+
Encoding for text data.
|
| 103 |
+
prettyprint : bool (optional, default: True)
|
| 104 |
+
If True use line breaks and indenting in output XML.
|
| 105 |
+
version : string (default: 1.2draft)
|
| 106 |
+
Version of GEFX File Format (see http://gexf.net/schema.html)
|
| 107 |
+
Supported values: "1.1draft", "1.2draft"
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
Examples
|
| 111 |
+
--------
|
| 112 |
+
>>> G = nx.path_graph(4)
|
| 113 |
+
>>> linefeed = chr(10) # linefeed=\n
|
| 114 |
+
>>> s = linefeed.join(nx.generate_gexf(G))
|
| 115 |
+
>>> for line in nx.generate_gexf(G): # doctest: +SKIP
|
| 116 |
+
... print(line)
|
| 117 |
+
|
| 118 |
+
Notes
|
| 119 |
+
-----
|
| 120 |
+
This implementation does not support mixed graphs (directed and undirected
|
| 121 |
+
edges together).
|
| 122 |
+
|
| 123 |
+
The node id attribute is set to be the string of the node label.
|
| 124 |
+
If you want to specify an id use set it as node data, e.g.
|
| 125 |
+
node['a']['id']=1 to set the id of node 'a' to 1.
|
| 126 |
+
|
| 127 |
+
References
|
| 128 |
+
----------
|
| 129 |
+
.. [1] GEXF File Format, https://gephi.org/gexf/format/
|
| 130 |
+
"""
|
| 131 |
+
writer = GEXFWriter(encoding=encoding, prettyprint=prettyprint, version=version)
|
| 132 |
+
writer.add_graph(G)
|
| 133 |
+
yield from str(writer).splitlines()
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
@open_file(0, mode="rb")
|
| 137 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 138 |
+
def read_gexf(path, node_type=None, relabel=False, version="1.2draft"):
|
| 139 |
+
"""Read graph in GEXF format from path.
|
| 140 |
+
|
| 141 |
+
"GEXF (Graph Exchange XML Format) is a language for describing
|
| 142 |
+
complex networks structures, their associated data and dynamics" [1]_.
|
| 143 |
+
|
| 144 |
+
Parameters
|
| 145 |
+
----------
|
| 146 |
+
path : file or string
|
| 147 |
+
File or file name to read.
|
| 148 |
+
File names ending in .gz or .bz2 will be decompressed.
|
| 149 |
+
node_type: Python type (default: None)
|
| 150 |
+
Convert node ids to this type if not None.
|
| 151 |
+
relabel : bool (default: False)
|
| 152 |
+
If True relabel the nodes to use the GEXF node "label" attribute
|
| 153 |
+
instead of the node "id" attribute as the NetworkX node label.
|
| 154 |
+
version : string (default: 1.2draft)
|
| 155 |
+
Version of GEFX File Format (see http://gexf.net/schema.html)
|
| 156 |
+
Supported values: "1.1draft", "1.2draft"
|
| 157 |
+
|
| 158 |
+
Returns
|
| 159 |
+
-------
|
| 160 |
+
graph: NetworkX graph
|
| 161 |
+
If no parallel edges are found a Graph or DiGraph is returned.
|
| 162 |
+
Otherwise a MultiGraph or MultiDiGraph is returned.
|
| 163 |
+
|
| 164 |
+
Notes
|
| 165 |
+
-----
|
| 166 |
+
This implementation does not support mixed graphs (directed and undirected
|
| 167 |
+
edges together).
|
| 168 |
+
|
| 169 |
+
References
|
| 170 |
+
----------
|
| 171 |
+
.. [1] GEXF File Format, http://gexf.net/
|
| 172 |
+
"""
|
| 173 |
+
reader = GEXFReader(node_type=node_type, version=version)
|
| 174 |
+
if relabel:
|
| 175 |
+
G = relabel_gexf_graph(reader(path))
|
| 176 |
+
else:
|
| 177 |
+
G = reader(path)
|
| 178 |
+
return G
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
class GEXF:
|
| 182 |
+
versions = {
|
| 183 |
+
"1.1draft": {
|
| 184 |
+
"NS_GEXF": "http://www.gexf.net/1.1draft",
|
| 185 |
+
"NS_VIZ": "http://www.gexf.net/1.1draft/viz",
|
| 186 |
+
"NS_XSI": "http://www.w3.org/2001/XMLSchema-instance",
|
| 187 |
+
"SCHEMALOCATION": " ".join(
|
| 188 |
+
[
|
| 189 |
+
"http://www.gexf.net/1.1draft",
|
| 190 |
+
"http://www.gexf.net/1.1draft/gexf.xsd",
|
| 191 |
+
]
|
| 192 |
+
),
|
| 193 |
+
"VERSION": "1.1",
|
| 194 |
+
},
|
| 195 |
+
"1.2draft": {
|
| 196 |
+
"NS_GEXF": "http://www.gexf.net/1.2draft",
|
| 197 |
+
"NS_VIZ": "http://www.gexf.net/1.2draft/viz",
|
| 198 |
+
"NS_XSI": "http://www.w3.org/2001/XMLSchema-instance",
|
| 199 |
+
"SCHEMALOCATION": " ".join(
|
| 200 |
+
[
|
| 201 |
+
"http://www.gexf.net/1.2draft",
|
| 202 |
+
"http://www.gexf.net/1.2draft/gexf.xsd",
|
| 203 |
+
]
|
| 204 |
+
),
|
| 205 |
+
"VERSION": "1.2",
|
| 206 |
+
},
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
def construct_types(self):
|
| 210 |
+
types = [
|
| 211 |
+
(int, "integer"),
|
| 212 |
+
(float, "float"),
|
| 213 |
+
(float, "double"),
|
| 214 |
+
(bool, "boolean"),
|
| 215 |
+
(list, "string"),
|
| 216 |
+
(dict, "string"),
|
| 217 |
+
(int, "long"),
|
| 218 |
+
(str, "liststring"),
|
| 219 |
+
(str, "anyURI"),
|
| 220 |
+
(str, "string"),
|
| 221 |
+
]
|
| 222 |
+
|
| 223 |
+
# These additions to types allow writing numpy types
|
| 224 |
+
try:
|
| 225 |
+
import numpy as np
|
| 226 |
+
except ImportError:
|
| 227 |
+
pass
|
| 228 |
+
else:
|
| 229 |
+
# prepend so that python types are created upon read (last entry wins)
|
| 230 |
+
types = [
|
| 231 |
+
(np.float64, "float"),
|
| 232 |
+
(np.float32, "float"),
|
| 233 |
+
(np.float16, "float"),
|
| 234 |
+
(np.int_, "int"),
|
| 235 |
+
(np.int8, "int"),
|
| 236 |
+
(np.int16, "int"),
|
| 237 |
+
(np.int32, "int"),
|
| 238 |
+
(np.int64, "int"),
|
| 239 |
+
(np.uint8, "int"),
|
| 240 |
+
(np.uint16, "int"),
|
| 241 |
+
(np.uint32, "int"),
|
| 242 |
+
(np.uint64, "int"),
|
| 243 |
+
(np.int_, "int"),
|
| 244 |
+
(np.intc, "int"),
|
| 245 |
+
(np.intp, "int"),
|
| 246 |
+
] + types
|
| 247 |
+
|
| 248 |
+
self.xml_type = dict(types)
|
| 249 |
+
self.python_type = dict(reversed(a) for a in types)
|
| 250 |
+
|
| 251 |
+
# http://www.w3.org/TR/xmlschema-2/#boolean
|
| 252 |
+
convert_bool = {
|
| 253 |
+
"true": True,
|
| 254 |
+
"false": False,
|
| 255 |
+
"True": True,
|
| 256 |
+
"False": False,
|
| 257 |
+
"0": False,
|
| 258 |
+
0: False,
|
| 259 |
+
"1": True,
|
| 260 |
+
1: True,
|
| 261 |
+
}
|
| 262 |
+
|
| 263 |
+
def set_version(self, version):
|
| 264 |
+
d = self.versions.get(version)
|
| 265 |
+
if d is None:
|
| 266 |
+
raise nx.NetworkXError(f"Unknown GEXF version {version}.")
|
| 267 |
+
self.NS_GEXF = d["NS_GEXF"]
|
| 268 |
+
self.NS_VIZ = d["NS_VIZ"]
|
| 269 |
+
self.NS_XSI = d["NS_XSI"]
|
| 270 |
+
self.SCHEMALOCATION = d["SCHEMALOCATION"]
|
| 271 |
+
self.VERSION = d["VERSION"]
|
| 272 |
+
self.version = version
|
| 273 |
+
|
| 274 |
+
|
| 275 |
+
class GEXFWriter(GEXF):
|
| 276 |
+
# class for writing GEXF format files
|
| 277 |
+
# use write_gexf() function
|
| 278 |
+
def __init__(
|
| 279 |
+
self, graph=None, encoding="utf-8", prettyprint=True, version="1.2draft"
|
| 280 |
+
):
|
| 281 |
+
self.construct_types()
|
| 282 |
+
self.prettyprint = prettyprint
|
| 283 |
+
self.encoding = encoding
|
| 284 |
+
self.set_version(version)
|
| 285 |
+
self.xml = Element(
|
| 286 |
+
"gexf",
|
| 287 |
+
{
|
| 288 |
+
"xmlns": self.NS_GEXF,
|
| 289 |
+
"xmlns:xsi": self.NS_XSI,
|
| 290 |
+
"xsi:schemaLocation": self.SCHEMALOCATION,
|
| 291 |
+
"version": self.VERSION,
|
| 292 |
+
},
|
| 293 |
+
)
|
| 294 |
+
|
| 295 |
+
# Make meta element a non-graph element
|
| 296 |
+
# Also add lastmodifieddate as attribute, not tag
|
| 297 |
+
meta_element = Element("meta")
|
| 298 |
+
subelement_text = f"NetworkX {nx.__version__}"
|
| 299 |
+
SubElement(meta_element, "creator").text = subelement_text
|
| 300 |
+
meta_element.set("lastmodifieddate", time.strftime("%Y-%m-%d"))
|
| 301 |
+
self.xml.append(meta_element)
|
| 302 |
+
|
| 303 |
+
register_namespace("viz", self.NS_VIZ)
|
| 304 |
+
|
| 305 |
+
# counters for edge and attribute identifiers
|
| 306 |
+
self.edge_id = itertools.count()
|
| 307 |
+
self.attr_id = itertools.count()
|
| 308 |
+
self.all_edge_ids = set()
|
| 309 |
+
# default attributes are stored in dictionaries
|
| 310 |
+
self.attr = {}
|
| 311 |
+
self.attr["node"] = {}
|
| 312 |
+
self.attr["edge"] = {}
|
| 313 |
+
self.attr["node"]["dynamic"] = {}
|
| 314 |
+
self.attr["node"]["static"] = {}
|
| 315 |
+
self.attr["edge"]["dynamic"] = {}
|
| 316 |
+
self.attr["edge"]["static"] = {}
|
| 317 |
+
|
| 318 |
+
if graph is not None:
|
| 319 |
+
self.add_graph(graph)
|
| 320 |
+
|
| 321 |
+
def __str__(self):
|
| 322 |
+
if self.prettyprint:
|
| 323 |
+
self.indent(self.xml)
|
| 324 |
+
s = tostring(self.xml).decode(self.encoding)
|
| 325 |
+
return s
|
| 326 |
+
|
| 327 |
+
def add_graph(self, G):
|
| 328 |
+
# first pass through G collecting edge ids
|
| 329 |
+
for u, v, dd in G.edges(data=True):
|
| 330 |
+
eid = dd.get("id")
|
| 331 |
+
if eid is not None:
|
| 332 |
+
self.all_edge_ids.add(str(eid))
|
| 333 |
+
# set graph attributes
|
| 334 |
+
if G.graph.get("mode") == "dynamic":
|
| 335 |
+
mode = "dynamic"
|
| 336 |
+
else:
|
| 337 |
+
mode = "static"
|
| 338 |
+
# Add a graph element to the XML
|
| 339 |
+
if G.is_directed():
|
| 340 |
+
default = "directed"
|
| 341 |
+
else:
|
| 342 |
+
default = "undirected"
|
| 343 |
+
name = G.graph.get("name", "")
|
| 344 |
+
graph_element = Element("graph", defaultedgetype=default, mode=mode, name=name)
|
| 345 |
+
self.graph_element = graph_element
|
| 346 |
+
self.add_nodes(G, graph_element)
|
| 347 |
+
self.add_edges(G, graph_element)
|
| 348 |
+
self.xml.append(graph_element)
|
| 349 |
+
|
| 350 |
+
def add_nodes(self, G, graph_element):
|
| 351 |
+
nodes_element = Element("nodes")
|
| 352 |
+
for node, data in G.nodes(data=True):
|
| 353 |
+
node_data = data.copy()
|
| 354 |
+
node_id = str(node_data.pop("id", node))
|
| 355 |
+
kw = {"id": node_id}
|
| 356 |
+
label = str(node_data.pop("label", node))
|
| 357 |
+
kw["label"] = label
|
| 358 |
+
try:
|
| 359 |
+
pid = node_data.pop("pid")
|
| 360 |
+
kw["pid"] = str(pid)
|
| 361 |
+
except KeyError:
|
| 362 |
+
pass
|
| 363 |
+
try:
|
| 364 |
+
start = node_data.pop("start")
|
| 365 |
+
kw["start"] = str(start)
|
| 366 |
+
self.alter_graph_mode_timeformat(start)
|
| 367 |
+
except KeyError:
|
| 368 |
+
pass
|
| 369 |
+
try:
|
| 370 |
+
end = node_data.pop("end")
|
| 371 |
+
kw["end"] = str(end)
|
| 372 |
+
self.alter_graph_mode_timeformat(end)
|
| 373 |
+
except KeyError:
|
| 374 |
+
pass
|
| 375 |
+
# add node element with attributes
|
| 376 |
+
node_element = Element("node", **kw)
|
| 377 |
+
# add node element and attr subelements
|
| 378 |
+
default = G.graph.get("node_default", {})
|
| 379 |
+
node_data = self.add_parents(node_element, node_data)
|
| 380 |
+
if self.VERSION == "1.1":
|
| 381 |
+
node_data = self.add_slices(node_element, node_data)
|
| 382 |
+
else:
|
| 383 |
+
node_data = self.add_spells(node_element, node_data)
|
| 384 |
+
node_data = self.add_viz(node_element, node_data)
|
| 385 |
+
node_data = self.add_attributes("node", node_element, node_data, default)
|
| 386 |
+
nodes_element.append(node_element)
|
| 387 |
+
graph_element.append(nodes_element)
|
| 388 |
+
|
| 389 |
+
def add_edges(self, G, graph_element):
|
| 390 |
+
def edge_key_data(G):
|
| 391 |
+
# helper function to unify multigraph and graph edge iterator
|
| 392 |
+
if G.is_multigraph():
|
| 393 |
+
for u, v, key, data in G.edges(data=True, keys=True):
|
| 394 |
+
edge_data = data.copy()
|
| 395 |
+
edge_data.update(key=key)
|
| 396 |
+
edge_id = edge_data.pop("id", None)
|
| 397 |
+
if edge_id is None:
|
| 398 |
+
edge_id = next(self.edge_id)
|
| 399 |
+
while str(edge_id) in self.all_edge_ids:
|
| 400 |
+
edge_id = next(self.edge_id)
|
| 401 |
+
self.all_edge_ids.add(str(edge_id))
|
| 402 |
+
yield u, v, edge_id, edge_data
|
| 403 |
+
else:
|
| 404 |
+
for u, v, data in G.edges(data=True):
|
| 405 |
+
edge_data = data.copy()
|
| 406 |
+
edge_id = edge_data.pop("id", None)
|
| 407 |
+
if edge_id is None:
|
| 408 |
+
edge_id = next(self.edge_id)
|
| 409 |
+
while str(edge_id) in self.all_edge_ids:
|
| 410 |
+
edge_id = next(self.edge_id)
|
| 411 |
+
self.all_edge_ids.add(str(edge_id))
|
| 412 |
+
yield u, v, edge_id, edge_data
|
| 413 |
+
|
| 414 |
+
edges_element = Element("edges")
|
| 415 |
+
for u, v, key, edge_data in edge_key_data(G):
|
| 416 |
+
kw = {"id": str(key)}
|
| 417 |
+
try:
|
| 418 |
+
edge_label = edge_data.pop("label")
|
| 419 |
+
kw["label"] = str(edge_label)
|
| 420 |
+
except KeyError:
|
| 421 |
+
pass
|
| 422 |
+
try:
|
| 423 |
+
edge_weight = edge_data.pop("weight")
|
| 424 |
+
kw["weight"] = str(edge_weight)
|
| 425 |
+
except KeyError:
|
| 426 |
+
pass
|
| 427 |
+
try:
|
| 428 |
+
edge_type = edge_data.pop("type")
|
| 429 |
+
kw["type"] = str(edge_type)
|
| 430 |
+
except KeyError:
|
| 431 |
+
pass
|
| 432 |
+
try:
|
| 433 |
+
start = edge_data.pop("start")
|
| 434 |
+
kw["start"] = str(start)
|
| 435 |
+
self.alter_graph_mode_timeformat(start)
|
| 436 |
+
except KeyError:
|
| 437 |
+
pass
|
| 438 |
+
try:
|
| 439 |
+
end = edge_data.pop("end")
|
| 440 |
+
kw["end"] = str(end)
|
| 441 |
+
self.alter_graph_mode_timeformat(end)
|
| 442 |
+
except KeyError:
|
| 443 |
+
pass
|
| 444 |
+
source_id = str(G.nodes[u].get("id", u))
|
| 445 |
+
target_id = str(G.nodes[v].get("id", v))
|
| 446 |
+
edge_element = Element("edge", source=source_id, target=target_id, **kw)
|
| 447 |
+
default = G.graph.get("edge_default", {})
|
| 448 |
+
if self.VERSION == "1.1":
|
| 449 |
+
edge_data = self.add_slices(edge_element, edge_data)
|
| 450 |
+
else:
|
| 451 |
+
edge_data = self.add_spells(edge_element, edge_data)
|
| 452 |
+
edge_data = self.add_viz(edge_element, edge_data)
|
| 453 |
+
edge_data = self.add_attributes("edge", edge_element, edge_data, default)
|
| 454 |
+
edges_element.append(edge_element)
|
| 455 |
+
graph_element.append(edges_element)
|
| 456 |
+
|
| 457 |
+
def add_attributes(self, node_or_edge, xml_obj, data, default):
|
| 458 |
+
# Add attrvalues to node or edge
|
| 459 |
+
attvalues = Element("attvalues")
|
| 460 |
+
if len(data) == 0:
|
| 461 |
+
return data
|
| 462 |
+
mode = "static"
|
| 463 |
+
for k, v in data.items():
|
| 464 |
+
# rename generic multigraph key to avoid any name conflict
|
| 465 |
+
if k == "key":
|
| 466 |
+
k = "networkx_key"
|
| 467 |
+
val_type = type(v)
|
| 468 |
+
if val_type not in self.xml_type:
|
| 469 |
+
raise TypeError(f"attribute value type is not allowed: {val_type}")
|
| 470 |
+
if isinstance(v, list):
|
| 471 |
+
# dynamic data
|
| 472 |
+
for val, start, end in v:
|
| 473 |
+
val_type = type(val)
|
| 474 |
+
if start is not None or end is not None:
|
| 475 |
+
mode = "dynamic"
|
| 476 |
+
self.alter_graph_mode_timeformat(start)
|
| 477 |
+
self.alter_graph_mode_timeformat(end)
|
| 478 |
+
break
|
| 479 |
+
attr_id = self.get_attr_id(
|
| 480 |
+
str(k), self.xml_type[val_type], node_or_edge, default, mode
|
| 481 |
+
)
|
| 482 |
+
for val, start, end in v:
|
| 483 |
+
e = Element("attvalue")
|
| 484 |
+
e.attrib["for"] = attr_id
|
| 485 |
+
e.attrib["value"] = str(val)
|
| 486 |
+
# Handle nan, inf, -inf differently
|
| 487 |
+
if val_type == float:
|
| 488 |
+
if e.attrib["value"] == "inf":
|
| 489 |
+
e.attrib["value"] = "INF"
|
| 490 |
+
elif e.attrib["value"] == "nan":
|
| 491 |
+
e.attrib["value"] = "NaN"
|
| 492 |
+
elif e.attrib["value"] == "-inf":
|
| 493 |
+
e.attrib["value"] = "-INF"
|
| 494 |
+
if start is not None:
|
| 495 |
+
e.attrib["start"] = str(start)
|
| 496 |
+
if end is not None:
|
| 497 |
+
e.attrib["end"] = str(end)
|
| 498 |
+
attvalues.append(e)
|
| 499 |
+
else:
|
| 500 |
+
# static data
|
| 501 |
+
mode = "static"
|
| 502 |
+
attr_id = self.get_attr_id(
|
| 503 |
+
str(k), self.xml_type[val_type], node_or_edge, default, mode
|
| 504 |
+
)
|
| 505 |
+
e = Element("attvalue")
|
| 506 |
+
e.attrib["for"] = attr_id
|
| 507 |
+
if isinstance(v, bool):
|
| 508 |
+
e.attrib["value"] = str(v).lower()
|
| 509 |
+
else:
|
| 510 |
+
e.attrib["value"] = str(v)
|
| 511 |
+
# Handle float nan, inf, -inf differently
|
| 512 |
+
if val_type == float:
|
| 513 |
+
if e.attrib["value"] == "inf":
|
| 514 |
+
e.attrib["value"] = "INF"
|
| 515 |
+
elif e.attrib["value"] == "nan":
|
| 516 |
+
e.attrib["value"] = "NaN"
|
| 517 |
+
elif e.attrib["value"] == "-inf":
|
| 518 |
+
e.attrib["value"] = "-INF"
|
| 519 |
+
attvalues.append(e)
|
| 520 |
+
xml_obj.append(attvalues)
|
| 521 |
+
return data
|
| 522 |
+
|
| 523 |
+
def get_attr_id(self, title, attr_type, edge_or_node, default, mode):
|
| 524 |
+
# find the id of the attribute or generate a new id
|
| 525 |
+
try:
|
| 526 |
+
return self.attr[edge_or_node][mode][title]
|
| 527 |
+
except KeyError:
|
| 528 |
+
# generate new id
|
| 529 |
+
new_id = str(next(self.attr_id))
|
| 530 |
+
self.attr[edge_or_node][mode][title] = new_id
|
| 531 |
+
attr_kwargs = {"id": new_id, "title": title, "type": attr_type}
|
| 532 |
+
attribute = Element("attribute", **attr_kwargs)
|
| 533 |
+
# add subelement for data default value if present
|
| 534 |
+
default_title = default.get(title)
|
| 535 |
+
if default_title is not None:
|
| 536 |
+
default_element = Element("default")
|
| 537 |
+
default_element.text = str(default_title)
|
| 538 |
+
attribute.append(default_element)
|
| 539 |
+
# new insert it into the XML
|
| 540 |
+
attributes_element = None
|
| 541 |
+
for a in self.graph_element.findall("attributes"):
|
| 542 |
+
# find existing attributes element by class and mode
|
| 543 |
+
a_class = a.get("class")
|
| 544 |
+
a_mode = a.get("mode", "static")
|
| 545 |
+
if a_class == edge_or_node and a_mode == mode:
|
| 546 |
+
attributes_element = a
|
| 547 |
+
if attributes_element is None:
|
| 548 |
+
# create new attributes element
|
| 549 |
+
attr_kwargs = {"mode": mode, "class": edge_or_node}
|
| 550 |
+
attributes_element = Element("attributes", **attr_kwargs)
|
| 551 |
+
self.graph_element.insert(0, attributes_element)
|
| 552 |
+
attributes_element.append(attribute)
|
| 553 |
+
return new_id
|
| 554 |
+
|
| 555 |
+
def add_viz(self, element, node_data):
|
| 556 |
+
viz = node_data.pop("viz", False)
|
| 557 |
+
if viz:
|
| 558 |
+
color = viz.get("color")
|
| 559 |
+
if color is not None:
|
| 560 |
+
if self.VERSION == "1.1":
|
| 561 |
+
e = Element(
|
| 562 |
+
f"{{{self.NS_VIZ}}}color",
|
| 563 |
+
r=str(color.get("r")),
|
| 564 |
+
g=str(color.get("g")),
|
| 565 |
+
b=str(color.get("b")),
|
| 566 |
+
)
|
| 567 |
+
else:
|
| 568 |
+
e = Element(
|
| 569 |
+
f"{{{self.NS_VIZ}}}color",
|
| 570 |
+
r=str(color.get("r")),
|
| 571 |
+
g=str(color.get("g")),
|
| 572 |
+
b=str(color.get("b")),
|
| 573 |
+
a=str(color.get("a", 1.0)),
|
| 574 |
+
)
|
| 575 |
+
element.append(e)
|
| 576 |
+
|
| 577 |
+
size = viz.get("size")
|
| 578 |
+
if size is not None:
|
| 579 |
+
e = Element(f"{{{self.NS_VIZ}}}size", value=str(size))
|
| 580 |
+
element.append(e)
|
| 581 |
+
|
| 582 |
+
thickness = viz.get("thickness")
|
| 583 |
+
if thickness is not None:
|
| 584 |
+
e = Element(f"{{{self.NS_VIZ}}}thickness", value=str(thickness))
|
| 585 |
+
element.append(e)
|
| 586 |
+
|
| 587 |
+
shape = viz.get("shape")
|
| 588 |
+
if shape is not None:
|
| 589 |
+
if shape.startswith("http"):
|
| 590 |
+
e = Element(
|
| 591 |
+
f"{{{self.NS_VIZ}}}shape", value="image", uri=str(shape)
|
| 592 |
+
)
|
| 593 |
+
else:
|
| 594 |
+
e = Element(f"{{{self.NS_VIZ}}}shape", value=str(shape))
|
| 595 |
+
element.append(e)
|
| 596 |
+
|
| 597 |
+
position = viz.get("position")
|
| 598 |
+
if position is not None:
|
| 599 |
+
e = Element(
|
| 600 |
+
f"{{{self.NS_VIZ}}}position",
|
| 601 |
+
x=str(position.get("x")),
|
| 602 |
+
y=str(position.get("y")),
|
| 603 |
+
z=str(position.get("z")),
|
| 604 |
+
)
|
| 605 |
+
element.append(e)
|
| 606 |
+
return node_data
|
| 607 |
+
|
| 608 |
+
def add_parents(self, node_element, node_data):
|
| 609 |
+
parents = node_data.pop("parents", False)
|
| 610 |
+
if parents:
|
| 611 |
+
parents_element = Element("parents")
|
| 612 |
+
for p in parents:
|
| 613 |
+
e = Element("parent")
|
| 614 |
+
e.attrib["for"] = str(p)
|
| 615 |
+
parents_element.append(e)
|
| 616 |
+
node_element.append(parents_element)
|
| 617 |
+
return node_data
|
| 618 |
+
|
| 619 |
+
def add_slices(self, node_or_edge_element, node_or_edge_data):
|
| 620 |
+
slices = node_or_edge_data.pop("slices", False)
|
| 621 |
+
if slices:
|
| 622 |
+
slices_element = Element("slices")
|
| 623 |
+
for start, end in slices:
|
| 624 |
+
e = Element("slice", start=str(start), end=str(end))
|
| 625 |
+
slices_element.append(e)
|
| 626 |
+
node_or_edge_element.append(slices_element)
|
| 627 |
+
return node_or_edge_data
|
| 628 |
+
|
| 629 |
+
def add_spells(self, node_or_edge_element, node_or_edge_data):
|
| 630 |
+
spells = node_or_edge_data.pop("spells", False)
|
| 631 |
+
if spells:
|
| 632 |
+
spells_element = Element("spells")
|
| 633 |
+
for start, end in spells:
|
| 634 |
+
e = Element("spell")
|
| 635 |
+
if start is not None:
|
| 636 |
+
e.attrib["start"] = str(start)
|
| 637 |
+
self.alter_graph_mode_timeformat(start)
|
| 638 |
+
if end is not None:
|
| 639 |
+
e.attrib["end"] = str(end)
|
| 640 |
+
self.alter_graph_mode_timeformat(end)
|
| 641 |
+
spells_element.append(e)
|
| 642 |
+
node_or_edge_element.append(spells_element)
|
| 643 |
+
return node_or_edge_data
|
| 644 |
+
|
| 645 |
+
def alter_graph_mode_timeformat(self, start_or_end):
|
| 646 |
+
# If 'start' or 'end' appears, alter Graph mode to dynamic and
|
| 647 |
+
# set timeformat
|
| 648 |
+
if self.graph_element.get("mode") == "static":
|
| 649 |
+
if start_or_end is not None:
|
| 650 |
+
if isinstance(start_or_end, str):
|
| 651 |
+
timeformat = "date"
|
| 652 |
+
elif isinstance(start_or_end, float):
|
| 653 |
+
timeformat = "double"
|
| 654 |
+
elif isinstance(start_or_end, int):
|
| 655 |
+
timeformat = "long"
|
| 656 |
+
else:
|
| 657 |
+
raise nx.NetworkXError(
|
| 658 |
+
"timeformat should be of the type int, float or str"
|
| 659 |
+
)
|
| 660 |
+
self.graph_element.set("timeformat", timeformat)
|
| 661 |
+
self.graph_element.set("mode", "dynamic")
|
| 662 |
+
|
| 663 |
+
def write(self, fh):
|
| 664 |
+
# Serialize graph G in GEXF to the open fh
|
| 665 |
+
if self.prettyprint:
|
| 666 |
+
self.indent(self.xml)
|
| 667 |
+
document = ElementTree(self.xml)
|
| 668 |
+
document.write(fh, encoding=self.encoding, xml_declaration=True)
|
| 669 |
+
|
| 670 |
+
def indent(self, elem, level=0):
|
| 671 |
+
# in-place prettyprint formatter
|
| 672 |
+
i = "\n" + " " * level
|
| 673 |
+
if len(elem):
|
| 674 |
+
if not elem.text or not elem.text.strip():
|
| 675 |
+
elem.text = i + " "
|
| 676 |
+
if not elem.tail or not elem.tail.strip():
|
| 677 |
+
elem.tail = i
|
| 678 |
+
for elem in elem:
|
| 679 |
+
self.indent(elem, level + 1)
|
| 680 |
+
if not elem.tail or not elem.tail.strip():
|
| 681 |
+
elem.tail = i
|
| 682 |
+
else:
|
| 683 |
+
if level and (not elem.tail or not elem.tail.strip()):
|
| 684 |
+
elem.tail = i
|
| 685 |
+
|
| 686 |
+
|
| 687 |
+
class GEXFReader(GEXF):
|
| 688 |
+
# Class to read GEXF format files
|
| 689 |
+
# use read_gexf() function
|
| 690 |
+
def __init__(self, node_type=None, version="1.2draft"):
|
| 691 |
+
self.construct_types()
|
| 692 |
+
self.node_type = node_type
|
| 693 |
+
# assume simple graph and test for multigraph on read
|
| 694 |
+
self.simple_graph = True
|
| 695 |
+
self.set_version(version)
|
| 696 |
+
|
| 697 |
+
def __call__(self, stream):
|
| 698 |
+
self.xml = ElementTree(file=stream)
|
| 699 |
+
g = self.xml.find(f"{{{self.NS_GEXF}}}graph")
|
| 700 |
+
if g is not None:
|
| 701 |
+
return self.make_graph(g)
|
| 702 |
+
# try all the versions
|
| 703 |
+
for version in self.versions:
|
| 704 |
+
self.set_version(version)
|
| 705 |
+
g = self.xml.find(f"{{{self.NS_GEXF}}}graph")
|
| 706 |
+
if g is not None:
|
| 707 |
+
return self.make_graph(g)
|
| 708 |
+
raise nx.NetworkXError("No <graph> element in GEXF file.")
|
| 709 |
+
|
| 710 |
+
def make_graph(self, graph_xml):
|
| 711 |
+
# start with empty DiGraph or MultiDiGraph
|
| 712 |
+
edgedefault = graph_xml.get("defaultedgetype", None)
|
| 713 |
+
if edgedefault == "directed":
|
| 714 |
+
G = nx.MultiDiGraph()
|
| 715 |
+
else:
|
| 716 |
+
G = nx.MultiGraph()
|
| 717 |
+
|
| 718 |
+
# graph attributes
|
| 719 |
+
graph_name = graph_xml.get("name", "")
|
| 720 |
+
if graph_name != "":
|
| 721 |
+
G.graph["name"] = graph_name
|
| 722 |
+
graph_start = graph_xml.get("start")
|
| 723 |
+
if graph_start is not None:
|
| 724 |
+
G.graph["start"] = graph_start
|
| 725 |
+
graph_end = graph_xml.get("end")
|
| 726 |
+
if graph_end is not None:
|
| 727 |
+
G.graph["end"] = graph_end
|
| 728 |
+
graph_mode = graph_xml.get("mode", "")
|
| 729 |
+
if graph_mode == "dynamic":
|
| 730 |
+
G.graph["mode"] = "dynamic"
|
| 731 |
+
else:
|
| 732 |
+
G.graph["mode"] = "static"
|
| 733 |
+
|
| 734 |
+
# timeformat
|
| 735 |
+
self.timeformat = graph_xml.get("timeformat")
|
| 736 |
+
if self.timeformat == "date":
|
| 737 |
+
self.timeformat = "string"
|
| 738 |
+
|
| 739 |
+
# node and edge attributes
|
| 740 |
+
attributes_elements = graph_xml.findall(f"{{{self.NS_GEXF}}}attributes")
|
| 741 |
+
# dictionaries to hold attributes and attribute defaults
|
| 742 |
+
node_attr = {}
|
| 743 |
+
node_default = {}
|
| 744 |
+
edge_attr = {}
|
| 745 |
+
edge_default = {}
|
| 746 |
+
for a in attributes_elements:
|
| 747 |
+
attr_class = a.get("class")
|
| 748 |
+
if attr_class == "node":
|
| 749 |
+
na, nd = self.find_gexf_attributes(a)
|
| 750 |
+
node_attr.update(na)
|
| 751 |
+
node_default.update(nd)
|
| 752 |
+
G.graph["node_default"] = node_default
|
| 753 |
+
elif attr_class == "edge":
|
| 754 |
+
ea, ed = self.find_gexf_attributes(a)
|
| 755 |
+
edge_attr.update(ea)
|
| 756 |
+
edge_default.update(ed)
|
| 757 |
+
G.graph["edge_default"] = edge_default
|
| 758 |
+
else:
|
| 759 |
+
raise # unknown attribute class
|
| 760 |
+
|
| 761 |
+
# Hack to handle Gephi0.7beta bug
|
| 762 |
+
# add weight attribute
|
| 763 |
+
ea = {"weight": {"type": "double", "mode": "static", "title": "weight"}}
|
| 764 |
+
ed = {}
|
| 765 |
+
edge_attr.update(ea)
|
| 766 |
+
edge_default.update(ed)
|
| 767 |
+
G.graph["edge_default"] = edge_default
|
| 768 |
+
|
| 769 |
+
# add nodes
|
| 770 |
+
nodes_element = graph_xml.find(f"{{{self.NS_GEXF}}}nodes")
|
| 771 |
+
if nodes_element is not None:
|
| 772 |
+
for node_xml in nodes_element.findall(f"{{{self.NS_GEXF}}}node"):
|
| 773 |
+
self.add_node(G, node_xml, node_attr)
|
| 774 |
+
|
| 775 |
+
# add edges
|
| 776 |
+
edges_element = graph_xml.find(f"{{{self.NS_GEXF}}}edges")
|
| 777 |
+
if edges_element is not None:
|
| 778 |
+
for edge_xml in edges_element.findall(f"{{{self.NS_GEXF}}}edge"):
|
| 779 |
+
self.add_edge(G, edge_xml, edge_attr)
|
| 780 |
+
|
| 781 |
+
# switch to Graph or DiGraph if no parallel edges were found.
|
| 782 |
+
if self.simple_graph:
|
| 783 |
+
if G.is_directed():
|
| 784 |
+
G = nx.DiGraph(G)
|
| 785 |
+
else:
|
| 786 |
+
G = nx.Graph(G)
|
| 787 |
+
return G
|
| 788 |
+
|
| 789 |
+
def add_node(self, G, node_xml, node_attr, node_pid=None):
|
| 790 |
+
# add a single node with attributes to the graph
|
| 791 |
+
|
| 792 |
+
# get attributes and subattributues for node
|
| 793 |
+
data = self.decode_attr_elements(node_attr, node_xml)
|
| 794 |
+
data = self.add_parents(data, node_xml) # add any parents
|
| 795 |
+
if self.VERSION == "1.1":
|
| 796 |
+
data = self.add_slices(data, node_xml) # add slices
|
| 797 |
+
else:
|
| 798 |
+
data = self.add_spells(data, node_xml) # add spells
|
| 799 |
+
data = self.add_viz(data, node_xml) # add viz
|
| 800 |
+
data = self.add_start_end(data, node_xml) # add start/end
|
| 801 |
+
|
| 802 |
+
# find the node id and cast it to the appropriate type
|
| 803 |
+
node_id = node_xml.get("id")
|
| 804 |
+
if self.node_type is not None:
|
| 805 |
+
node_id = self.node_type(node_id)
|
| 806 |
+
|
| 807 |
+
# every node should have a label
|
| 808 |
+
node_label = node_xml.get("label")
|
| 809 |
+
data["label"] = node_label
|
| 810 |
+
|
| 811 |
+
# parent node id
|
| 812 |
+
node_pid = node_xml.get("pid", node_pid)
|
| 813 |
+
if node_pid is not None:
|
| 814 |
+
data["pid"] = node_pid
|
| 815 |
+
|
| 816 |
+
# check for subnodes, recursive
|
| 817 |
+
subnodes = node_xml.find(f"{{{self.NS_GEXF}}}nodes")
|
| 818 |
+
if subnodes is not None:
|
| 819 |
+
for node_xml in subnodes.findall(f"{{{self.NS_GEXF}}}node"):
|
| 820 |
+
self.add_node(G, node_xml, node_attr, node_pid=node_id)
|
| 821 |
+
|
| 822 |
+
G.add_node(node_id, **data)
|
| 823 |
+
|
| 824 |
+
def add_start_end(self, data, xml):
|
| 825 |
+
# start and end times
|
| 826 |
+
ttype = self.timeformat
|
| 827 |
+
node_start = xml.get("start")
|
| 828 |
+
if node_start is not None:
|
| 829 |
+
data["start"] = self.python_type[ttype](node_start)
|
| 830 |
+
node_end = xml.get("end")
|
| 831 |
+
if node_end is not None:
|
| 832 |
+
data["end"] = self.python_type[ttype](node_end)
|
| 833 |
+
return data
|
| 834 |
+
|
| 835 |
+
def add_viz(self, data, node_xml):
|
| 836 |
+
# add viz element for node
|
| 837 |
+
viz = {}
|
| 838 |
+
color = node_xml.find(f"{{{self.NS_VIZ}}}color")
|
| 839 |
+
if color is not None:
|
| 840 |
+
if self.VERSION == "1.1":
|
| 841 |
+
viz["color"] = {
|
| 842 |
+
"r": int(color.get("r")),
|
| 843 |
+
"g": int(color.get("g")),
|
| 844 |
+
"b": int(color.get("b")),
|
| 845 |
+
}
|
| 846 |
+
else:
|
| 847 |
+
viz["color"] = {
|
| 848 |
+
"r": int(color.get("r")),
|
| 849 |
+
"g": int(color.get("g")),
|
| 850 |
+
"b": int(color.get("b")),
|
| 851 |
+
"a": float(color.get("a", 1)),
|
| 852 |
+
}
|
| 853 |
+
|
| 854 |
+
size = node_xml.find(f"{{{self.NS_VIZ}}}size")
|
| 855 |
+
if size is not None:
|
| 856 |
+
viz["size"] = float(size.get("value"))
|
| 857 |
+
|
| 858 |
+
thickness = node_xml.find(f"{{{self.NS_VIZ}}}thickness")
|
| 859 |
+
if thickness is not None:
|
| 860 |
+
viz["thickness"] = float(thickness.get("value"))
|
| 861 |
+
|
| 862 |
+
shape = node_xml.find(f"{{{self.NS_VIZ}}}shape")
|
| 863 |
+
if shape is not None:
|
| 864 |
+
viz["shape"] = shape.get("shape")
|
| 865 |
+
if viz["shape"] == "image":
|
| 866 |
+
viz["shape"] = shape.get("uri")
|
| 867 |
+
|
| 868 |
+
position = node_xml.find(f"{{{self.NS_VIZ}}}position")
|
| 869 |
+
if position is not None:
|
| 870 |
+
viz["position"] = {
|
| 871 |
+
"x": float(position.get("x", 0)),
|
| 872 |
+
"y": float(position.get("y", 0)),
|
| 873 |
+
"z": float(position.get("z", 0)),
|
| 874 |
+
}
|
| 875 |
+
|
| 876 |
+
if len(viz) > 0:
|
| 877 |
+
data["viz"] = viz
|
| 878 |
+
return data
|
| 879 |
+
|
| 880 |
+
def add_parents(self, data, node_xml):
|
| 881 |
+
parents_element = node_xml.find(f"{{{self.NS_GEXF}}}parents")
|
| 882 |
+
if parents_element is not None:
|
| 883 |
+
data["parents"] = []
|
| 884 |
+
for p in parents_element.findall(f"{{{self.NS_GEXF}}}parent"):
|
| 885 |
+
parent = p.get("for")
|
| 886 |
+
data["parents"].append(parent)
|
| 887 |
+
return data
|
| 888 |
+
|
| 889 |
+
def add_slices(self, data, node_or_edge_xml):
|
| 890 |
+
slices_element = node_or_edge_xml.find(f"{{{self.NS_GEXF}}}slices")
|
| 891 |
+
if slices_element is not None:
|
| 892 |
+
data["slices"] = []
|
| 893 |
+
for s in slices_element.findall(f"{{{self.NS_GEXF}}}slice"):
|
| 894 |
+
start = s.get("start")
|
| 895 |
+
end = s.get("end")
|
| 896 |
+
data["slices"].append((start, end))
|
| 897 |
+
return data
|
| 898 |
+
|
| 899 |
+
def add_spells(self, data, node_or_edge_xml):
|
| 900 |
+
spells_element = node_or_edge_xml.find(f"{{{self.NS_GEXF}}}spells")
|
| 901 |
+
if spells_element is not None:
|
| 902 |
+
data["spells"] = []
|
| 903 |
+
ttype = self.timeformat
|
| 904 |
+
for s in spells_element.findall(f"{{{self.NS_GEXF}}}spell"):
|
| 905 |
+
start = self.python_type[ttype](s.get("start"))
|
| 906 |
+
end = self.python_type[ttype](s.get("end"))
|
| 907 |
+
data["spells"].append((start, end))
|
| 908 |
+
return data
|
| 909 |
+
|
| 910 |
+
def add_edge(self, G, edge_element, edge_attr):
|
| 911 |
+
# add an edge to the graph
|
| 912 |
+
|
| 913 |
+
# raise error if we find mixed directed and undirected edges
|
| 914 |
+
edge_direction = edge_element.get("type")
|
| 915 |
+
if G.is_directed() and edge_direction == "undirected":
|
| 916 |
+
raise nx.NetworkXError("Undirected edge found in directed graph.")
|
| 917 |
+
if (not G.is_directed()) and edge_direction == "directed":
|
| 918 |
+
raise nx.NetworkXError("Directed edge found in undirected graph.")
|
| 919 |
+
|
| 920 |
+
# Get source and target and recast type if required
|
| 921 |
+
source = edge_element.get("source")
|
| 922 |
+
target = edge_element.get("target")
|
| 923 |
+
if self.node_type is not None:
|
| 924 |
+
source = self.node_type(source)
|
| 925 |
+
target = self.node_type(target)
|
| 926 |
+
|
| 927 |
+
data = self.decode_attr_elements(edge_attr, edge_element)
|
| 928 |
+
data = self.add_start_end(data, edge_element)
|
| 929 |
+
|
| 930 |
+
if self.VERSION == "1.1":
|
| 931 |
+
data = self.add_slices(data, edge_element) # add slices
|
| 932 |
+
else:
|
| 933 |
+
data = self.add_spells(data, edge_element) # add spells
|
| 934 |
+
|
| 935 |
+
# GEXF stores edge ids as an attribute
|
| 936 |
+
# NetworkX uses them as keys in multigraphs
|
| 937 |
+
# if networkx_key is not specified as an attribute
|
| 938 |
+
edge_id = edge_element.get("id")
|
| 939 |
+
if edge_id is not None:
|
| 940 |
+
data["id"] = edge_id
|
| 941 |
+
|
| 942 |
+
# check if there is a 'multigraph_key' and use that as edge_id
|
| 943 |
+
multigraph_key = data.pop("networkx_key", None)
|
| 944 |
+
if multigraph_key is not None:
|
| 945 |
+
edge_id = multigraph_key
|
| 946 |
+
|
| 947 |
+
weight = edge_element.get("weight")
|
| 948 |
+
if weight is not None:
|
| 949 |
+
data["weight"] = float(weight)
|
| 950 |
+
|
| 951 |
+
edge_label = edge_element.get("label")
|
| 952 |
+
if edge_label is not None:
|
| 953 |
+
data["label"] = edge_label
|
| 954 |
+
|
| 955 |
+
if G.has_edge(source, target):
|
| 956 |
+
# seen this edge before - this is a multigraph
|
| 957 |
+
self.simple_graph = False
|
| 958 |
+
G.add_edge(source, target, key=edge_id, **data)
|
| 959 |
+
if edge_direction == "mutual":
|
| 960 |
+
G.add_edge(target, source, key=edge_id, **data)
|
| 961 |
+
|
| 962 |
+
def decode_attr_elements(self, gexf_keys, obj_xml):
|
| 963 |
+
# Use the key information to decode the attr XML
|
| 964 |
+
attr = {}
|
| 965 |
+
# look for outer '<attvalues>' element
|
| 966 |
+
attr_element = obj_xml.find(f"{{{self.NS_GEXF}}}attvalues")
|
| 967 |
+
if attr_element is not None:
|
| 968 |
+
# loop over <attvalue> elements
|
| 969 |
+
for a in attr_element.findall(f"{{{self.NS_GEXF}}}attvalue"):
|
| 970 |
+
key = a.get("for") # for is required
|
| 971 |
+
try: # should be in our gexf_keys dictionary
|
| 972 |
+
title = gexf_keys[key]["title"]
|
| 973 |
+
except KeyError as err:
|
| 974 |
+
raise nx.NetworkXError(f"No attribute defined for={key}.") from err
|
| 975 |
+
atype = gexf_keys[key]["type"]
|
| 976 |
+
value = a.get("value")
|
| 977 |
+
if atype == "boolean":
|
| 978 |
+
value = self.convert_bool[value]
|
| 979 |
+
else:
|
| 980 |
+
value = self.python_type[atype](value)
|
| 981 |
+
if gexf_keys[key]["mode"] == "dynamic":
|
| 982 |
+
# for dynamic graphs use list of three-tuples
|
| 983 |
+
# [(value1,start1,end1), (value2,start2,end2), etc]
|
| 984 |
+
ttype = self.timeformat
|
| 985 |
+
start = self.python_type[ttype](a.get("start"))
|
| 986 |
+
end = self.python_type[ttype](a.get("end"))
|
| 987 |
+
if title in attr:
|
| 988 |
+
attr[title].append((value, start, end))
|
| 989 |
+
else:
|
| 990 |
+
attr[title] = [(value, start, end)]
|
| 991 |
+
else:
|
| 992 |
+
# for static graphs just assign the value
|
| 993 |
+
attr[title] = value
|
| 994 |
+
return attr
|
| 995 |
+
|
| 996 |
+
def find_gexf_attributes(self, attributes_element):
|
| 997 |
+
# Extract all the attributes and defaults
|
| 998 |
+
attrs = {}
|
| 999 |
+
defaults = {}
|
| 1000 |
+
mode = attributes_element.get("mode")
|
| 1001 |
+
for k in attributes_element.findall(f"{{{self.NS_GEXF}}}attribute"):
|
| 1002 |
+
attr_id = k.get("id")
|
| 1003 |
+
title = k.get("title")
|
| 1004 |
+
atype = k.get("type")
|
| 1005 |
+
attrs[attr_id] = {"title": title, "type": atype, "mode": mode}
|
| 1006 |
+
# check for the 'default' subelement of key element and add
|
| 1007 |
+
default = k.find(f"{{{self.NS_GEXF}}}default")
|
| 1008 |
+
if default is not None:
|
| 1009 |
+
if atype == "boolean":
|
| 1010 |
+
value = self.convert_bool[default.text]
|
| 1011 |
+
else:
|
| 1012 |
+
value = self.python_type[atype](default.text)
|
| 1013 |
+
defaults[title] = value
|
| 1014 |
+
return attrs, defaults
|
| 1015 |
+
|
| 1016 |
+
|
| 1017 |
+
def relabel_gexf_graph(G):
|
| 1018 |
+
"""Relabel graph using "label" node keyword for node label.
|
| 1019 |
+
|
| 1020 |
+
Parameters
|
| 1021 |
+
----------
|
| 1022 |
+
G : graph
|
| 1023 |
+
A NetworkX graph read from GEXF data
|
| 1024 |
+
|
| 1025 |
+
Returns
|
| 1026 |
+
-------
|
| 1027 |
+
H : graph
|
| 1028 |
+
A NetworkX graph with relabeled nodes
|
| 1029 |
+
|
| 1030 |
+
Raises
|
| 1031 |
+
------
|
| 1032 |
+
NetworkXError
|
| 1033 |
+
If node labels are missing or not unique while relabel=True.
|
| 1034 |
+
|
| 1035 |
+
Notes
|
| 1036 |
+
-----
|
| 1037 |
+
This function relabels the nodes in a NetworkX graph with the
|
| 1038 |
+
"label" attribute. It also handles relabeling the specific GEXF
|
| 1039 |
+
node attributes "parents", and "pid".
|
| 1040 |
+
"""
|
| 1041 |
+
# build mapping of node labels, do some error checking
|
| 1042 |
+
try:
|
| 1043 |
+
mapping = [(u, G.nodes[u]["label"]) for u in G]
|
| 1044 |
+
except KeyError as err:
|
| 1045 |
+
raise nx.NetworkXError(
|
| 1046 |
+
"Failed to relabel nodes: missing node labels found. Use relabel=False."
|
| 1047 |
+
) from err
|
| 1048 |
+
x, y = zip(*mapping)
|
| 1049 |
+
if len(set(y)) != len(G):
|
| 1050 |
+
raise nx.NetworkXError(
|
| 1051 |
+
"Failed to relabel nodes: "
|
| 1052 |
+
"duplicate node labels found. "
|
| 1053 |
+
"Use relabel=False."
|
| 1054 |
+
)
|
| 1055 |
+
mapping = dict(mapping)
|
| 1056 |
+
H = nx.relabel_nodes(G, mapping)
|
| 1057 |
+
# relabel attributes
|
| 1058 |
+
for n in G:
|
| 1059 |
+
m = mapping[n]
|
| 1060 |
+
H.nodes[m]["id"] = n
|
| 1061 |
+
H.nodes[m].pop("label")
|
| 1062 |
+
if "pid" in H.nodes[m]:
|
| 1063 |
+
H.nodes[m]["pid"] = mapping[G.nodes[n]["pid"]]
|
| 1064 |
+
if "parents" in H.nodes[m]:
|
| 1065 |
+
H.nodes[m]["parents"] = [mapping[p] for p in G.nodes[n]["parents"]]
|
| 1066 |
+
return H
|
.venv/lib/python3.11/site-packages/networkx/readwrite/graphml.py
ADDED
|
@@ -0,0 +1,1053 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
*******
|
| 3 |
+
GraphML
|
| 4 |
+
*******
|
| 5 |
+
Read and write graphs in GraphML format.
|
| 6 |
+
|
| 7 |
+
.. warning::
|
| 8 |
+
|
| 9 |
+
This parser uses the standard xml library present in Python, which is
|
| 10 |
+
insecure - see :external+python:mod:`xml` for additional information.
|
| 11 |
+
Only parse GraphML files you trust.
|
| 12 |
+
|
| 13 |
+
This implementation does not support mixed graphs (directed and unidirected
|
| 14 |
+
edges together), hyperedges, nested graphs, or ports.
|
| 15 |
+
|
| 16 |
+
"GraphML is a comprehensive and easy-to-use file format for graphs. It
|
| 17 |
+
consists of a language core to describe the structural properties of a
|
| 18 |
+
graph and a flexible extension mechanism to add application-specific
|
| 19 |
+
data. Its main features include support of
|
| 20 |
+
|
| 21 |
+
* directed, undirected, and mixed graphs,
|
| 22 |
+
* hypergraphs,
|
| 23 |
+
* hierarchical graphs,
|
| 24 |
+
* graphical representations,
|
| 25 |
+
* references to external data,
|
| 26 |
+
* application-specific attribute data, and
|
| 27 |
+
* light-weight parsers.
|
| 28 |
+
|
| 29 |
+
Unlike many other file formats for graphs, GraphML does not use a
|
| 30 |
+
custom syntax. Instead, it is based on XML and hence ideally suited as
|
| 31 |
+
a common denominator for all kinds of services generating, archiving,
|
| 32 |
+
or processing graphs."
|
| 33 |
+
|
| 34 |
+
http://graphml.graphdrawing.org/
|
| 35 |
+
|
| 36 |
+
Format
|
| 37 |
+
------
|
| 38 |
+
GraphML is an XML format. See
|
| 39 |
+
http://graphml.graphdrawing.org/specification.html for the specification and
|
| 40 |
+
http://graphml.graphdrawing.org/primer/graphml-primer.html
|
| 41 |
+
for examples.
|
| 42 |
+
"""
|
| 43 |
+
|
| 44 |
+
import warnings
|
| 45 |
+
from collections import defaultdict
|
| 46 |
+
|
| 47 |
+
import networkx as nx
|
| 48 |
+
from networkx.utils import open_file
|
| 49 |
+
|
| 50 |
+
__all__ = [
|
| 51 |
+
"write_graphml",
|
| 52 |
+
"read_graphml",
|
| 53 |
+
"generate_graphml",
|
| 54 |
+
"write_graphml_xml",
|
| 55 |
+
"write_graphml_lxml",
|
| 56 |
+
"parse_graphml",
|
| 57 |
+
"GraphMLWriter",
|
| 58 |
+
"GraphMLReader",
|
| 59 |
+
]
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
@open_file(1, mode="wb")
|
| 63 |
+
def write_graphml_xml(
|
| 64 |
+
G,
|
| 65 |
+
path,
|
| 66 |
+
encoding="utf-8",
|
| 67 |
+
prettyprint=True,
|
| 68 |
+
infer_numeric_types=False,
|
| 69 |
+
named_key_ids=False,
|
| 70 |
+
edge_id_from_attribute=None,
|
| 71 |
+
):
|
| 72 |
+
"""Write G in GraphML XML format to path
|
| 73 |
+
|
| 74 |
+
Parameters
|
| 75 |
+
----------
|
| 76 |
+
G : graph
|
| 77 |
+
A networkx graph
|
| 78 |
+
path : file or string
|
| 79 |
+
File or filename to write.
|
| 80 |
+
Filenames ending in .gz or .bz2 will be compressed.
|
| 81 |
+
encoding : string (optional)
|
| 82 |
+
Encoding for text data.
|
| 83 |
+
prettyprint : bool (optional)
|
| 84 |
+
If True use line breaks and indenting in output XML.
|
| 85 |
+
infer_numeric_types : boolean
|
| 86 |
+
Determine if numeric types should be generalized.
|
| 87 |
+
For example, if edges have both int and float 'weight' attributes,
|
| 88 |
+
we infer in GraphML that both are floats.
|
| 89 |
+
named_key_ids : bool (optional)
|
| 90 |
+
If True use attr.name as value for key elements' id attribute.
|
| 91 |
+
edge_id_from_attribute : dict key (optional)
|
| 92 |
+
If provided, the graphml edge id is set by looking up the corresponding
|
| 93 |
+
edge data attribute keyed by this parameter. If `None` or the key does not exist in edge data,
|
| 94 |
+
the edge id is set by the edge key if `G` is a MultiGraph, else the edge id is left unset.
|
| 95 |
+
|
| 96 |
+
Examples
|
| 97 |
+
--------
|
| 98 |
+
>>> G = nx.path_graph(4)
|
| 99 |
+
>>> nx.write_graphml(G, "test.graphml")
|
| 100 |
+
|
| 101 |
+
Notes
|
| 102 |
+
-----
|
| 103 |
+
This implementation does not support mixed graphs (directed
|
| 104 |
+
and unidirected edges together) hyperedges, nested graphs, or ports.
|
| 105 |
+
"""
|
| 106 |
+
writer = GraphMLWriter(
|
| 107 |
+
encoding=encoding,
|
| 108 |
+
prettyprint=prettyprint,
|
| 109 |
+
infer_numeric_types=infer_numeric_types,
|
| 110 |
+
named_key_ids=named_key_ids,
|
| 111 |
+
edge_id_from_attribute=edge_id_from_attribute,
|
| 112 |
+
)
|
| 113 |
+
writer.add_graph_element(G)
|
| 114 |
+
writer.dump(path)
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
@open_file(1, mode="wb")
|
| 118 |
+
def write_graphml_lxml(
|
| 119 |
+
G,
|
| 120 |
+
path,
|
| 121 |
+
encoding="utf-8",
|
| 122 |
+
prettyprint=True,
|
| 123 |
+
infer_numeric_types=False,
|
| 124 |
+
named_key_ids=False,
|
| 125 |
+
edge_id_from_attribute=None,
|
| 126 |
+
):
|
| 127 |
+
"""Write G in GraphML XML format to path
|
| 128 |
+
|
| 129 |
+
This function uses the LXML framework and should be faster than
|
| 130 |
+
the version using the xml library.
|
| 131 |
+
|
| 132 |
+
Parameters
|
| 133 |
+
----------
|
| 134 |
+
G : graph
|
| 135 |
+
A networkx graph
|
| 136 |
+
path : file or string
|
| 137 |
+
File or filename to write.
|
| 138 |
+
Filenames ending in .gz or .bz2 will be compressed.
|
| 139 |
+
encoding : string (optional)
|
| 140 |
+
Encoding for text data.
|
| 141 |
+
prettyprint : bool (optional)
|
| 142 |
+
If True use line breaks and indenting in output XML.
|
| 143 |
+
infer_numeric_types : boolean
|
| 144 |
+
Determine if numeric types should be generalized.
|
| 145 |
+
For example, if edges have both int and float 'weight' attributes,
|
| 146 |
+
we infer in GraphML that both are floats.
|
| 147 |
+
named_key_ids : bool (optional)
|
| 148 |
+
If True use attr.name as value for key elements' id attribute.
|
| 149 |
+
edge_id_from_attribute : dict key (optional)
|
| 150 |
+
If provided, the graphml edge id is set by looking up the corresponding
|
| 151 |
+
edge data attribute keyed by this parameter. If `None` or the key does not exist in edge data,
|
| 152 |
+
the edge id is set by the edge key if `G` is a MultiGraph, else the edge id is left unset.
|
| 153 |
+
|
| 154 |
+
Examples
|
| 155 |
+
--------
|
| 156 |
+
>>> G = nx.path_graph(4)
|
| 157 |
+
>>> nx.write_graphml_lxml(G, "fourpath.graphml")
|
| 158 |
+
|
| 159 |
+
Notes
|
| 160 |
+
-----
|
| 161 |
+
This implementation does not support mixed graphs (directed
|
| 162 |
+
and unidirected edges together) hyperedges, nested graphs, or ports.
|
| 163 |
+
"""
|
| 164 |
+
try:
|
| 165 |
+
import lxml.etree as lxmletree
|
| 166 |
+
except ImportError:
|
| 167 |
+
return write_graphml_xml(
|
| 168 |
+
G,
|
| 169 |
+
path,
|
| 170 |
+
encoding,
|
| 171 |
+
prettyprint,
|
| 172 |
+
infer_numeric_types,
|
| 173 |
+
named_key_ids,
|
| 174 |
+
edge_id_from_attribute,
|
| 175 |
+
)
|
| 176 |
+
|
| 177 |
+
writer = GraphMLWriterLxml(
|
| 178 |
+
path,
|
| 179 |
+
graph=G,
|
| 180 |
+
encoding=encoding,
|
| 181 |
+
prettyprint=prettyprint,
|
| 182 |
+
infer_numeric_types=infer_numeric_types,
|
| 183 |
+
named_key_ids=named_key_ids,
|
| 184 |
+
edge_id_from_attribute=edge_id_from_attribute,
|
| 185 |
+
)
|
| 186 |
+
writer.dump()
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
def generate_graphml(
|
| 190 |
+
G,
|
| 191 |
+
encoding="utf-8",
|
| 192 |
+
prettyprint=True,
|
| 193 |
+
named_key_ids=False,
|
| 194 |
+
edge_id_from_attribute=None,
|
| 195 |
+
):
|
| 196 |
+
"""Generate GraphML lines for G
|
| 197 |
+
|
| 198 |
+
Parameters
|
| 199 |
+
----------
|
| 200 |
+
G : graph
|
| 201 |
+
A networkx graph
|
| 202 |
+
encoding : string (optional)
|
| 203 |
+
Encoding for text data.
|
| 204 |
+
prettyprint : bool (optional)
|
| 205 |
+
If True use line breaks and indenting in output XML.
|
| 206 |
+
named_key_ids : bool (optional)
|
| 207 |
+
If True use attr.name as value for key elements' id attribute.
|
| 208 |
+
edge_id_from_attribute : dict key (optional)
|
| 209 |
+
If provided, the graphml edge id is set by looking up the corresponding
|
| 210 |
+
edge data attribute keyed by this parameter. If `None` or the key does not exist in edge data,
|
| 211 |
+
the edge id is set by the edge key if `G` is a MultiGraph, else the edge id is left unset.
|
| 212 |
+
|
| 213 |
+
Examples
|
| 214 |
+
--------
|
| 215 |
+
>>> G = nx.path_graph(4)
|
| 216 |
+
>>> linefeed = chr(10) # linefeed = \n
|
| 217 |
+
>>> s = linefeed.join(nx.generate_graphml(G))
|
| 218 |
+
>>> for line in nx.generate_graphml(G): # doctest: +SKIP
|
| 219 |
+
... print(line)
|
| 220 |
+
|
| 221 |
+
Notes
|
| 222 |
+
-----
|
| 223 |
+
This implementation does not support mixed graphs (directed and unidirected
|
| 224 |
+
edges together) hyperedges, nested graphs, or ports.
|
| 225 |
+
"""
|
| 226 |
+
writer = GraphMLWriter(
|
| 227 |
+
encoding=encoding,
|
| 228 |
+
prettyprint=prettyprint,
|
| 229 |
+
named_key_ids=named_key_ids,
|
| 230 |
+
edge_id_from_attribute=edge_id_from_attribute,
|
| 231 |
+
)
|
| 232 |
+
writer.add_graph_element(G)
|
| 233 |
+
yield from str(writer).splitlines()
|
| 234 |
+
|
| 235 |
+
|
| 236 |
+
@open_file(0, mode="rb")
|
| 237 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 238 |
+
def read_graphml(path, node_type=str, edge_key_type=int, force_multigraph=False):
|
| 239 |
+
"""Read graph in GraphML format from path.
|
| 240 |
+
|
| 241 |
+
Parameters
|
| 242 |
+
----------
|
| 243 |
+
path : file or string
|
| 244 |
+
File or filename to write.
|
| 245 |
+
Filenames ending in .gz or .bz2 will be compressed.
|
| 246 |
+
|
| 247 |
+
node_type: Python type (default: str)
|
| 248 |
+
Convert node ids to this type
|
| 249 |
+
|
| 250 |
+
edge_key_type: Python type (default: int)
|
| 251 |
+
Convert graphml edge ids to this type. Multigraphs use id as edge key.
|
| 252 |
+
Non-multigraphs add to edge attribute dict with name "id".
|
| 253 |
+
|
| 254 |
+
force_multigraph : bool (default: False)
|
| 255 |
+
If True, return a multigraph with edge keys. If False (the default)
|
| 256 |
+
return a multigraph when multiedges are in the graph.
|
| 257 |
+
|
| 258 |
+
Returns
|
| 259 |
+
-------
|
| 260 |
+
graph: NetworkX graph
|
| 261 |
+
If parallel edges are present or `force_multigraph=True` then
|
| 262 |
+
a MultiGraph or MultiDiGraph is returned. Otherwise a Graph/DiGraph.
|
| 263 |
+
The returned graph is directed if the file indicates it should be.
|
| 264 |
+
|
| 265 |
+
Notes
|
| 266 |
+
-----
|
| 267 |
+
Default node and edge attributes are not propagated to each node and edge.
|
| 268 |
+
They can be obtained from `G.graph` and applied to node and edge attributes
|
| 269 |
+
if desired using something like this:
|
| 270 |
+
|
| 271 |
+
>>> default_color = G.graph["node_default"]["color"] # doctest: +SKIP
|
| 272 |
+
>>> for node, data in G.nodes(data=True): # doctest: +SKIP
|
| 273 |
+
... if "color" not in data:
|
| 274 |
+
... data["color"] = default_color
|
| 275 |
+
>>> default_color = G.graph["edge_default"]["color"] # doctest: +SKIP
|
| 276 |
+
>>> for u, v, data in G.edges(data=True): # doctest: +SKIP
|
| 277 |
+
... if "color" not in data:
|
| 278 |
+
... data["color"] = default_color
|
| 279 |
+
|
| 280 |
+
This implementation does not support mixed graphs (directed and unidirected
|
| 281 |
+
edges together), hypergraphs, nested graphs, or ports.
|
| 282 |
+
|
| 283 |
+
For multigraphs the GraphML edge "id" will be used as the edge
|
| 284 |
+
key. If not specified then they "key" attribute will be used. If
|
| 285 |
+
there is no "key" attribute a default NetworkX multigraph edge key
|
| 286 |
+
will be provided.
|
| 287 |
+
|
| 288 |
+
Files with the yEd "yfiles" extension can be read. The type of the node's
|
| 289 |
+
shape is preserved in the `shape_type` node attribute.
|
| 290 |
+
|
| 291 |
+
yEd compressed files ("file.graphmlz" extension) can be read by renaming
|
| 292 |
+
the file to "file.graphml.gz".
|
| 293 |
+
|
| 294 |
+
"""
|
| 295 |
+
reader = GraphMLReader(node_type, edge_key_type, force_multigraph)
|
| 296 |
+
# need to check for multiple graphs
|
| 297 |
+
glist = list(reader(path=path))
|
| 298 |
+
if len(glist) == 0:
|
| 299 |
+
# If no graph comes back, try looking for an incomplete header
|
| 300 |
+
header = b'<graphml xmlns="http://graphml.graphdrawing.org/xmlns">'
|
| 301 |
+
path.seek(0)
|
| 302 |
+
old_bytes = path.read()
|
| 303 |
+
new_bytes = old_bytes.replace(b"<graphml>", header)
|
| 304 |
+
glist = list(reader(string=new_bytes))
|
| 305 |
+
if len(glist) == 0:
|
| 306 |
+
raise nx.NetworkXError("file not successfully read as graphml")
|
| 307 |
+
return glist[0]
|
| 308 |
+
|
| 309 |
+
|
| 310 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 311 |
+
def parse_graphml(
|
| 312 |
+
graphml_string, node_type=str, edge_key_type=int, force_multigraph=False
|
| 313 |
+
):
|
| 314 |
+
"""Read graph in GraphML format from string.
|
| 315 |
+
|
| 316 |
+
Parameters
|
| 317 |
+
----------
|
| 318 |
+
graphml_string : string
|
| 319 |
+
String containing graphml information
|
| 320 |
+
(e.g., contents of a graphml file).
|
| 321 |
+
|
| 322 |
+
node_type: Python type (default: str)
|
| 323 |
+
Convert node ids to this type
|
| 324 |
+
|
| 325 |
+
edge_key_type: Python type (default: int)
|
| 326 |
+
Convert graphml edge ids to this type. Multigraphs use id as edge key.
|
| 327 |
+
Non-multigraphs add to edge attribute dict with name "id".
|
| 328 |
+
|
| 329 |
+
force_multigraph : bool (default: False)
|
| 330 |
+
If True, return a multigraph with edge keys. If False (the default)
|
| 331 |
+
return a multigraph when multiedges are in the graph.
|
| 332 |
+
|
| 333 |
+
|
| 334 |
+
Returns
|
| 335 |
+
-------
|
| 336 |
+
graph: NetworkX graph
|
| 337 |
+
If no parallel edges are found a Graph or DiGraph is returned.
|
| 338 |
+
Otherwise a MultiGraph or MultiDiGraph is returned.
|
| 339 |
+
|
| 340 |
+
Examples
|
| 341 |
+
--------
|
| 342 |
+
>>> G = nx.path_graph(4)
|
| 343 |
+
>>> linefeed = chr(10) # linefeed = \n
|
| 344 |
+
>>> s = linefeed.join(nx.generate_graphml(G))
|
| 345 |
+
>>> H = nx.parse_graphml(s)
|
| 346 |
+
|
| 347 |
+
Notes
|
| 348 |
+
-----
|
| 349 |
+
Default node and edge attributes are not propagated to each node and edge.
|
| 350 |
+
They can be obtained from `G.graph` and applied to node and edge attributes
|
| 351 |
+
if desired using something like this:
|
| 352 |
+
|
| 353 |
+
>>> default_color = G.graph["node_default"]["color"] # doctest: +SKIP
|
| 354 |
+
>>> for node, data in G.nodes(data=True): # doctest: +SKIP
|
| 355 |
+
... if "color" not in data:
|
| 356 |
+
... data["color"] = default_color
|
| 357 |
+
>>> default_color = G.graph["edge_default"]["color"] # doctest: +SKIP
|
| 358 |
+
>>> for u, v, data in G.edges(data=True): # doctest: +SKIP
|
| 359 |
+
... if "color" not in data:
|
| 360 |
+
... data["color"] = default_color
|
| 361 |
+
|
| 362 |
+
This implementation does not support mixed graphs (directed and unidirected
|
| 363 |
+
edges together), hypergraphs, nested graphs, or ports.
|
| 364 |
+
|
| 365 |
+
For multigraphs the GraphML edge "id" will be used as the edge
|
| 366 |
+
key. If not specified then they "key" attribute will be used. If
|
| 367 |
+
there is no "key" attribute a default NetworkX multigraph edge key
|
| 368 |
+
will be provided.
|
| 369 |
+
|
| 370 |
+
"""
|
| 371 |
+
reader = GraphMLReader(node_type, edge_key_type, force_multigraph)
|
| 372 |
+
# need to check for multiple graphs
|
| 373 |
+
glist = list(reader(string=graphml_string))
|
| 374 |
+
if len(glist) == 0:
|
| 375 |
+
# If no graph comes back, try looking for an incomplete header
|
| 376 |
+
header = '<graphml xmlns="http://graphml.graphdrawing.org/xmlns">'
|
| 377 |
+
new_string = graphml_string.replace("<graphml>", header)
|
| 378 |
+
glist = list(reader(string=new_string))
|
| 379 |
+
if len(glist) == 0:
|
| 380 |
+
raise nx.NetworkXError("file not successfully read as graphml")
|
| 381 |
+
return glist[0]
|
| 382 |
+
|
| 383 |
+
|
| 384 |
+
class GraphML:
|
| 385 |
+
NS_GRAPHML = "http://graphml.graphdrawing.org/xmlns"
|
| 386 |
+
NS_XSI = "http://www.w3.org/2001/XMLSchema-instance"
|
| 387 |
+
# xmlns:y="http://www.yworks.com/xml/graphml"
|
| 388 |
+
NS_Y = "http://www.yworks.com/xml/graphml"
|
| 389 |
+
SCHEMALOCATION = " ".join(
|
| 390 |
+
[
|
| 391 |
+
"http://graphml.graphdrawing.org/xmlns",
|
| 392 |
+
"http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd",
|
| 393 |
+
]
|
| 394 |
+
)
|
| 395 |
+
|
| 396 |
+
def construct_types(self):
|
| 397 |
+
types = [
|
| 398 |
+
(int, "integer"), # for Gephi GraphML bug
|
| 399 |
+
(str, "yfiles"),
|
| 400 |
+
(str, "string"),
|
| 401 |
+
(int, "int"),
|
| 402 |
+
(int, "long"),
|
| 403 |
+
(float, "float"),
|
| 404 |
+
(float, "double"),
|
| 405 |
+
(bool, "boolean"),
|
| 406 |
+
]
|
| 407 |
+
|
| 408 |
+
# These additions to types allow writing numpy types
|
| 409 |
+
try:
|
| 410 |
+
import numpy as np
|
| 411 |
+
except:
|
| 412 |
+
pass
|
| 413 |
+
else:
|
| 414 |
+
# prepend so that python types are created upon read (last entry wins)
|
| 415 |
+
types = [
|
| 416 |
+
(np.float64, "float"),
|
| 417 |
+
(np.float32, "float"),
|
| 418 |
+
(np.float16, "float"),
|
| 419 |
+
(np.int_, "int"),
|
| 420 |
+
(np.int8, "int"),
|
| 421 |
+
(np.int16, "int"),
|
| 422 |
+
(np.int32, "int"),
|
| 423 |
+
(np.int64, "int"),
|
| 424 |
+
(np.uint8, "int"),
|
| 425 |
+
(np.uint16, "int"),
|
| 426 |
+
(np.uint32, "int"),
|
| 427 |
+
(np.uint64, "int"),
|
| 428 |
+
(np.int_, "int"),
|
| 429 |
+
(np.intc, "int"),
|
| 430 |
+
(np.intp, "int"),
|
| 431 |
+
] + types
|
| 432 |
+
|
| 433 |
+
self.xml_type = dict(types)
|
| 434 |
+
self.python_type = dict(reversed(a) for a in types)
|
| 435 |
+
|
| 436 |
+
# This page says that data types in GraphML follow Java(TM).
|
| 437 |
+
# http://graphml.graphdrawing.org/primer/graphml-primer.html#AttributesDefinition
|
| 438 |
+
# true and false are the only boolean literals:
|
| 439 |
+
# http://en.wikibooks.org/wiki/Java_Programming/Literals#Boolean_Literals
|
| 440 |
+
convert_bool = {
|
| 441 |
+
# We use data.lower() in actual use.
|
| 442 |
+
"true": True,
|
| 443 |
+
"false": False,
|
| 444 |
+
# Include integer strings for convenience.
|
| 445 |
+
"0": False,
|
| 446 |
+
0: False,
|
| 447 |
+
"1": True,
|
| 448 |
+
1: True,
|
| 449 |
+
}
|
| 450 |
+
|
| 451 |
+
def get_xml_type(self, key):
|
| 452 |
+
"""Wrapper around the xml_type dict that raises a more informative
|
| 453 |
+
exception message when a user attempts to use data of a type not
|
| 454 |
+
supported by GraphML."""
|
| 455 |
+
try:
|
| 456 |
+
return self.xml_type[key]
|
| 457 |
+
except KeyError as err:
|
| 458 |
+
raise TypeError(
|
| 459 |
+
f"GraphML does not support type {key} as data values."
|
| 460 |
+
) from err
|
| 461 |
+
|
| 462 |
+
|
| 463 |
+
class GraphMLWriter(GraphML):
|
| 464 |
+
def __init__(
|
| 465 |
+
self,
|
| 466 |
+
graph=None,
|
| 467 |
+
encoding="utf-8",
|
| 468 |
+
prettyprint=True,
|
| 469 |
+
infer_numeric_types=False,
|
| 470 |
+
named_key_ids=False,
|
| 471 |
+
edge_id_from_attribute=None,
|
| 472 |
+
):
|
| 473 |
+
self.construct_types()
|
| 474 |
+
from xml.etree.ElementTree import Element
|
| 475 |
+
|
| 476 |
+
self.myElement = Element
|
| 477 |
+
|
| 478 |
+
self.infer_numeric_types = infer_numeric_types
|
| 479 |
+
self.prettyprint = prettyprint
|
| 480 |
+
self.named_key_ids = named_key_ids
|
| 481 |
+
self.edge_id_from_attribute = edge_id_from_attribute
|
| 482 |
+
self.encoding = encoding
|
| 483 |
+
self.xml = self.myElement(
|
| 484 |
+
"graphml",
|
| 485 |
+
{
|
| 486 |
+
"xmlns": self.NS_GRAPHML,
|
| 487 |
+
"xmlns:xsi": self.NS_XSI,
|
| 488 |
+
"xsi:schemaLocation": self.SCHEMALOCATION,
|
| 489 |
+
},
|
| 490 |
+
)
|
| 491 |
+
self.keys = {}
|
| 492 |
+
self.attributes = defaultdict(list)
|
| 493 |
+
self.attribute_types = defaultdict(set)
|
| 494 |
+
|
| 495 |
+
if graph is not None:
|
| 496 |
+
self.add_graph_element(graph)
|
| 497 |
+
|
| 498 |
+
def __str__(self):
|
| 499 |
+
from xml.etree.ElementTree import tostring
|
| 500 |
+
|
| 501 |
+
if self.prettyprint:
|
| 502 |
+
self.indent(self.xml)
|
| 503 |
+
s = tostring(self.xml).decode(self.encoding)
|
| 504 |
+
return s
|
| 505 |
+
|
| 506 |
+
def attr_type(self, name, scope, value):
|
| 507 |
+
"""Infer the attribute type of data named name. Currently this only
|
| 508 |
+
supports inference of numeric types.
|
| 509 |
+
|
| 510 |
+
If self.infer_numeric_types is false, type is used. Otherwise, pick the
|
| 511 |
+
most general of types found across all values with name and scope. This
|
| 512 |
+
means edges with data named 'weight' are treated separately from nodes
|
| 513 |
+
with data named 'weight'.
|
| 514 |
+
"""
|
| 515 |
+
if self.infer_numeric_types:
|
| 516 |
+
types = self.attribute_types[(name, scope)]
|
| 517 |
+
|
| 518 |
+
if len(types) > 1:
|
| 519 |
+
types = {self.get_xml_type(t) for t in types}
|
| 520 |
+
if "string" in types:
|
| 521 |
+
return str
|
| 522 |
+
elif "float" in types or "double" in types:
|
| 523 |
+
return float
|
| 524 |
+
else:
|
| 525 |
+
return int
|
| 526 |
+
else:
|
| 527 |
+
return list(types)[0]
|
| 528 |
+
else:
|
| 529 |
+
return type(value)
|
| 530 |
+
|
| 531 |
+
def get_key(self, name, attr_type, scope, default):
|
| 532 |
+
keys_key = (name, attr_type, scope)
|
| 533 |
+
try:
|
| 534 |
+
return self.keys[keys_key]
|
| 535 |
+
except KeyError:
|
| 536 |
+
if self.named_key_ids:
|
| 537 |
+
new_id = name
|
| 538 |
+
else:
|
| 539 |
+
new_id = f"d{len(list(self.keys))}"
|
| 540 |
+
|
| 541 |
+
self.keys[keys_key] = new_id
|
| 542 |
+
key_kwargs = {
|
| 543 |
+
"id": new_id,
|
| 544 |
+
"for": scope,
|
| 545 |
+
"attr.name": name,
|
| 546 |
+
"attr.type": attr_type,
|
| 547 |
+
}
|
| 548 |
+
key_element = self.myElement("key", **key_kwargs)
|
| 549 |
+
# add subelement for data default value if present
|
| 550 |
+
if default is not None:
|
| 551 |
+
default_element = self.myElement("default")
|
| 552 |
+
default_element.text = str(default)
|
| 553 |
+
key_element.append(default_element)
|
| 554 |
+
self.xml.insert(0, key_element)
|
| 555 |
+
return new_id
|
| 556 |
+
|
| 557 |
+
def add_data(self, name, element_type, value, scope="all", default=None):
|
| 558 |
+
"""
|
| 559 |
+
Make a data element for an edge or a node. Keep a log of the
|
| 560 |
+
type in the keys table.
|
| 561 |
+
"""
|
| 562 |
+
if element_type not in self.xml_type:
|
| 563 |
+
raise nx.NetworkXError(
|
| 564 |
+
f"GraphML writer does not support {element_type} as data values."
|
| 565 |
+
)
|
| 566 |
+
keyid = self.get_key(name, self.get_xml_type(element_type), scope, default)
|
| 567 |
+
data_element = self.myElement("data", key=keyid)
|
| 568 |
+
data_element.text = str(value)
|
| 569 |
+
return data_element
|
| 570 |
+
|
| 571 |
+
def add_attributes(self, scope, xml_obj, data, default):
|
| 572 |
+
"""Appends attribute data to edges or nodes, and stores type information
|
| 573 |
+
to be added later. See add_graph_element.
|
| 574 |
+
"""
|
| 575 |
+
for k, v in data.items():
|
| 576 |
+
self.attribute_types[(str(k), scope)].add(type(v))
|
| 577 |
+
self.attributes[xml_obj].append([k, v, scope, default.get(k)])
|
| 578 |
+
|
| 579 |
+
def add_nodes(self, G, graph_element):
|
| 580 |
+
default = G.graph.get("node_default", {})
|
| 581 |
+
for node, data in G.nodes(data=True):
|
| 582 |
+
node_element = self.myElement("node", id=str(node))
|
| 583 |
+
self.add_attributes("node", node_element, data, default)
|
| 584 |
+
graph_element.append(node_element)
|
| 585 |
+
|
| 586 |
+
def add_edges(self, G, graph_element):
|
| 587 |
+
if G.is_multigraph():
|
| 588 |
+
for u, v, key, data in G.edges(data=True, keys=True):
|
| 589 |
+
edge_element = self.myElement(
|
| 590 |
+
"edge",
|
| 591 |
+
source=str(u),
|
| 592 |
+
target=str(v),
|
| 593 |
+
id=str(data.get(self.edge_id_from_attribute))
|
| 594 |
+
if self.edge_id_from_attribute
|
| 595 |
+
and self.edge_id_from_attribute in data
|
| 596 |
+
else str(key),
|
| 597 |
+
)
|
| 598 |
+
default = G.graph.get("edge_default", {})
|
| 599 |
+
self.add_attributes("edge", edge_element, data, default)
|
| 600 |
+
graph_element.append(edge_element)
|
| 601 |
+
else:
|
| 602 |
+
for u, v, data in G.edges(data=True):
|
| 603 |
+
if self.edge_id_from_attribute and self.edge_id_from_attribute in data:
|
| 604 |
+
# select attribute to be edge id
|
| 605 |
+
edge_element = self.myElement(
|
| 606 |
+
"edge",
|
| 607 |
+
source=str(u),
|
| 608 |
+
target=str(v),
|
| 609 |
+
id=str(data.get(self.edge_id_from_attribute)),
|
| 610 |
+
)
|
| 611 |
+
else:
|
| 612 |
+
# default: no edge id
|
| 613 |
+
edge_element = self.myElement("edge", source=str(u), target=str(v))
|
| 614 |
+
default = G.graph.get("edge_default", {})
|
| 615 |
+
self.add_attributes("edge", edge_element, data, default)
|
| 616 |
+
graph_element.append(edge_element)
|
| 617 |
+
|
| 618 |
+
def add_graph_element(self, G):
|
| 619 |
+
"""
|
| 620 |
+
Serialize graph G in GraphML to the stream.
|
| 621 |
+
"""
|
| 622 |
+
if G.is_directed():
|
| 623 |
+
default_edge_type = "directed"
|
| 624 |
+
else:
|
| 625 |
+
default_edge_type = "undirected"
|
| 626 |
+
|
| 627 |
+
graphid = G.graph.pop("id", None)
|
| 628 |
+
if graphid is None:
|
| 629 |
+
graph_element = self.myElement("graph", edgedefault=default_edge_type)
|
| 630 |
+
else:
|
| 631 |
+
graph_element = self.myElement(
|
| 632 |
+
"graph", edgedefault=default_edge_type, id=graphid
|
| 633 |
+
)
|
| 634 |
+
default = {}
|
| 635 |
+
data = {
|
| 636 |
+
k: v
|
| 637 |
+
for (k, v) in G.graph.items()
|
| 638 |
+
if k not in ["node_default", "edge_default"]
|
| 639 |
+
}
|
| 640 |
+
self.add_attributes("graph", graph_element, data, default)
|
| 641 |
+
self.add_nodes(G, graph_element)
|
| 642 |
+
self.add_edges(G, graph_element)
|
| 643 |
+
|
| 644 |
+
# self.attributes contains a mapping from XML Objects to a list of
|
| 645 |
+
# data that needs to be added to them.
|
| 646 |
+
# We postpone processing in order to do type inference/generalization.
|
| 647 |
+
# See self.attr_type
|
| 648 |
+
for xml_obj, data in self.attributes.items():
|
| 649 |
+
for k, v, scope, default in data:
|
| 650 |
+
xml_obj.append(
|
| 651 |
+
self.add_data(
|
| 652 |
+
str(k), self.attr_type(k, scope, v), str(v), scope, default
|
| 653 |
+
)
|
| 654 |
+
)
|
| 655 |
+
self.xml.append(graph_element)
|
| 656 |
+
|
| 657 |
+
def add_graphs(self, graph_list):
|
| 658 |
+
"""Add many graphs to this GraphML document."""
|
| 659 |
+
for G in graph_list:
|
| 660 |
+
self.add_graph_element(G)
|
| 661 |
+
|
| 662 |
+
def dump(self, stream):
|
| 663 |
+
from xml.etree.ElementTree import ElementTree
|
| 664 |
+
|
| 665 |
+
if self.prettyprint:
|
| 666 |
+
self.indent(self.xml)
|
| 667 |
+
document = ElementTree(self.xml)
|
| 668 |
+
document.write(stream, encoding=self.encoding, xml_declaration=True)
|
| 669 |
+
|
| 670 |
+
def indent(self, elem, level=0):
|
| 671 |
+
# in-place prettyprint formatter
|
| 672 |
+
i = "\n" + level * " "
|
| 673 |
+
if len(elem):
|
| 674 |
+
if not elem.text or not elem.text.strip():
|
| 675 |
+
elem.text = i + " "
|
| 676 |
+
if not elem.tail or not elem.tail.strip():
|
| 677 |
+
elem.tail = i
|
| 678 |
+
for elem in elem:
|
| 679 |
+
self.indent(elem, level + 1)
|
| 680 |
+
if not elem.tail or not elem.tail.strip():
|
| 681 |
+
elem.tail = i
|
| 682 |
+
else:
|
| 683 |
+
if level and (not elem.tail or not elem.tail.strip()):
|
| 684 |
+
elem.tail = i
|
| 685 |
+
|
| 686 |
+
|
| 687 |
+
class IncrementalElement:
|
| 688 |
+
"""Wrapper for _IncrementalWriter providing an Element like interface.
|
| 689 |
+
|
| 690 |
+
This wrapper does not intend to be a complete implementation but rather to
|
| 691 |
+
deal with those calls used in GraphMLWriter.
|
| 692 |
+
"""
|
| 693 |
+
|
| 694 |
+
def __init__(self, xml, prettyprint):
|
| 695 |
+
self.xml = xml
|
| 696 |
+
self.prettyprint = prettyprint
|
| 697 |
+
|
| 698 |
+
def append(self, element):
|
| 699 |
+
self.xml.write(element, pretty_print=self.prettyprint)
|
| 700 |
+
|
| 701 |
+
|
| 702 |
+
class GraphMLWriterLxml(GraphMLWriter):
|
| 703 |
+
def __init__(
|
| 704 |
+
self,
|
| 705 |
+
path,
|
| 706 |
+
graph=None,
|
| 707 |
+
encoding="utf-8",
|
| 708 |
+
prettyprint=True,
|
| 709 |
+
infer_numeric_types=False,
|
| 710 |
+
named_key_ids=False,
|
| 711 |
+
edge_id_from_attribute=None,
|
| 712 |
+
):
|
| 713 |
+
self.construct_types()
|
| 714 |
+
import lxml.etree as lxmletree
|
| 715 |
+
|
| 716 |
+
self.myElement = lxmletree.Element
|
| 717 |
+
|
| 718 |
+
self._encoding = encoding
|
| 719 |
+
self._prettyprint = prettyprint
|
| 720 |
+
self.named_key_ids = named_key_ids
|
| 721 |
+
self.edge_id_from_attribute = edge_id_from_attribute
|
| 722 |
+
self.infer_numeric_types = infer_numeric_types
|
| 723 |
+
|
| 724 |
+
self._xml_base = lxmletree.xmlfile(path, encoding=encoding)
|
| 725 |
+
self._xml = self._xml_base.__enter__()
|
| 726 |
+
self._xml.write_declaration()
|
| 727 |
+
|
| 728 |
+
# We need to have a xml variable that support insertion. This call is
|
| 729 |
+
# used for adding the keys to the document.
|
| 730 |
+
# We will store those keys in a plain list, and then after the graph
|
| 731 |
+
# element is closed we will add them to the main graphml element.
|
| 732 |
+
self.xml = []
|
| 733 |
+
self._keys = self.xml
|
| 734 |
+
self._graphml = self._xml.element(
|
| 735 |
+
"graphml",
|
| 736 |
+
{
|
| 737 |
+
"xmlns": self.NS_GRAPHML,
|
| 738 |
+
"xmlns:xsi": self.NS_XSI,
|
| 739 |
+
"xsi:schemaLocation": self.SCHEMALOCATION,
|
| 740 |
+
},
|
| 741 |
+
)
|
| 742 |
+
self._graphml.__enter__()
|
| 743 |
+
self.keys = {}
|
| 744 |
+
self.attribute_types = defaultdict(set)
|
| 745 |
+
|
| 746 |
+
if graph is not None:
|
| 747 |
+
self.add_graph_element(graph)
|
| 748 |
+
|
| 749 |
+
def add_graph_element(self, G):
|
| 750 |
+
"""
|
| 751 |
+
Serialize graph G in GraphML to the stream.
|
| 752 |
+
"""
|
| 753 |
+
if G.is_directed():
|
| 754 |
+
default_edge_type = "directed"
|
| 755 |
+
else:
|
| 756 |
+
default_edge_type = "undirected"
|
| 757 |
+
|
| 758 |
+
graphid = G.graph.pop("id", None)
|
| 759 |
+
if graphid is None:
|
| 760 |
+
graph_element = self._xml.element("graph", edgedefault=default_edge_type)
|
| 761 |
+
else:
|
| 762 |
+
graph_element = self._xml.element(
|
| 763 |
+
"graph", edgedefault=default_edge_type, id=graphid
|
| 764 |
+
)
|
| 765 |
+
|
| 766 |
+
# gather attributes types for the whole graph
|
| 767 |
+
# to find the most general numeric format needed.
|
| 768 |
+
# Then pass through attributes to create key_id for each.
|
| 769 |
+
graphdata = {
|
| 770 |
+
k: v
|
| 771 |
+
for k, v in G.graph.items()
|
| 772 |
+
if k not in ("node_default", "edge_default")
|
| 773 |
+
}
|
| 774 |
+
node_default = G.graph.get("node_default", {})
|
| 775 |
+
edge_default = G.graph.get("edge_default", {})
|
| 776 |
+
# Graph attributes
|
| 777 |
+
for k, v in graphdata.items():
|
| 778 |
+
self.attribute_types[(str(k), "graph")].add(type(v))
|
| 779 |
+
for k, v in graphdata.items():
|
| 780 |
+
element_type = self.get_xml_type(self.attr_type(k, "graph", v))
|
| 781 |
+
self.get_key(str(k), element_type, "graph", None)
|
| 782 |
+
# Nodes and data
|
| 783 |
+
for node, d in G.nodes(data=True):
|
| 784 |
+
for k, v in d.items():
|
| 785 |
+
self.attribute_types[(str(k), "node")].add(type(v))
|
| 786 |
+
for node, d in G.nodes(data=True):
|
| 787 |
+
for k, v in d.items():
|
| 788 |
+
T = self.get_xml_type(self.attr_type(k, "node", v))
|
| 789 |
+
self.get_key(str(k), T, "node", node_default.get(k))
|
| 790 |
+
# Edges and data
|
| 791 |
+
if G.is_multigraph():
|
| 792 |
+
for u, v, ekey, d in G.edges(keys=True, data=True):
|
| 793 |
+
for k, v in d.items():
|
| 794 |
+
self.attribute_types[(str(k), "edge")].add(type(v))
|
| 795 |
+
for u, v, ekey, d in G.edges(keys=True, data=True):
|
| 796 |
+
for k, v in d.items():
|
| 797 |
+
T = self.get_xml_type(self.attr_type(k, "edge", v))
|
| 798 |
+
self.get_key(str(k), T, "edge", edge_default.get(k))
|
| 799 |
+
else:
|
| 800 |
+
for u, v, d in G.edges(data=True):
|
| 801 |
+
for k, v in d.items():
|
| 802 |
+
self.attribute_types[(str(k), "edge")].add(type(v))
|
| 803 |
+
for u, v, d in G.edges(data=True):
|
| 804 |
+
for k, v in d.items():
|
| 805 |
+
T = self.get_xml_type(self.attr_type(k, "edge", v))
|
| 806 |
+
self.get_key(str(k), T, "edge", edge_default.get(k))
|
| 807 |
+
|
| 808 |
+
# Now add attribute keys to the xml file
|
| 809 |
+
for key in self.xml:
|
| 810 |
+
self._xml.write(key, pretty_print=self._prettyprint)
|
| 811 |
+
|
| 812 |
+
# The incremental_writer writes each node/edge as it is created
|
| 813 |
+
incremental_writer = IncrementalElement(self._xml, self._prettyprint)
|
| 814 |
+
with graph_element:
|
| 815 |
+
self.add_attributes("graph", incremental_writer, graphdata, {})
|
| 816 |
+
self.add_nodes(G, incremental_writer) # adds attributes too
|
| 817 |
+
self.add_edges(G, incremental_writer) # adds attributes too
|
| 818 |
+
|
| 819 |
+
def add_attributes(self, scope, xml_obj, data, default):
|
| 820 |
+
"""Appends attribute data."""
|
| 821 |
+
for k, v in data.items():
|
| 822 |
+
data_element = self.add_data(
|
| 823 |
+
str(k), self.attr_type(str(k), scope, v), str(v), scope, default.get(k)
|
| 824 |
+
)
|
| 825 |
+
xml_obj.append(data_element)
|
| 826 |
+
|
| 827 |
+
def __str__(self):
|
| 828 |
+
return object.__str__(self)
|
| 829 |
+
|
| 830 |
+
def dump(self, stream=None):
|
| 831 |
+
self._graphml.__exit__(None, None, None)
|
| 832 |
+
self._xml_base.__exit__(None, None, None)
|
| 833 |
+
|
| 834 |
+
|
| 835 |
+
# default is lxml is present.
|
| 836 |
+
write_graphml = write_graphml_lxml
|
| 837 |
+
|
| 838 |
+
|
| 839 |
+
class GraphMLReader(GraphML):
|
| 840 |
+
"""Read a GraphML document. Produces NetworkX graph objects."""
|
| 841 |
+
|
| 842 |
+
def __init__(self, node_type=str, edge_key_type=int, force_multigraph=False):
|
| 843 |
+
self.construct_types()
|
| 844 |
+
self.node_type = node_type
|
| 845 |
+
self.edge_key_type = edge_key_type
|
| 846 |
+
self.multigraph = force_multigraph # If False, test for multiedges
|
| 847 |
+
self.edge_ids = {} # dict mapping (u,v) tuples to edge id attributes
|
| 848 |
+
|
| 849 |
+
def __call__(self, path=None, string=None):
|
| 850 |
+
from xml.etree.ElementTree import ElementTree, fromstring
|
| 851 |
+
|
| 852 |
+
if path is not None:
|
| 853 |
+
self.xml = ElementTree(file=path)
|
| 854 |
+
elif string is not None:
|
| 855 |
+
self.xml = fromstring(string)
|
| 856 |
+
else:
|
| 857 |
+
raise ValueError("Must specify either 'path' or 'string' as kwarg")
|
| 858 |
+
(keys, defaults) = self.find_graphml_keys(self.xml)
|
| 859 |
+
for g in self.xml.findall(f"{{{self.NS_GRAPHML}}}graph"):
|
| 860 |
+
yield self.make_graph(g, keys, defaults)
|
| 861 |
+
|
| 862 |
+
def make_graph(self, graph_xml, graphml_keys, defaults, G=None):
|
| 863 |
+
# set default graph type
|
| 864 |
+
edgedefault = graph_xml.get("edgedefault", None)
|
| 865 |
+
if G is None:
|
| 866 |
+
if edgedefault == "directed":
|
| 867 |
+
G = nx.MultiDiGraph()
|
| 868 |
+
else:
|
| 869 |
+
G = nx.MultiGraph()
|
| 870 |
+
# set defaults for graph attributes
|
| 871 |
+
G.graph["node_default"] = {}
|
| 872 |
+
G.graph["edge_default"] = {}
|
| 873 |
+
for key_id, value in defaults.items():
|
| 874 |
+
key_for = graphml_keys[key_id]["for"]
|
| 875 |
+
name = graphml_keys[key_id]["name"]
|
| 876 |
+
python_type = graphml_keys[key_id]["type"]
|
| 877 |
+
if key_for == "node":
|
| 878 |
+
G.graph["node_default"].update({name: python_type(value)})
|
| 879 |
+
if key_for == "edge":
|
| 880 |
+
G.graph["edge_default"].update({name: python_type(value)})
|
| 881 |
+
# hyperedges are not supported
|
| 882 |
+
hyperedge = graph_xml.find(f"{{{self.NS_GRAPHML}}}hyperedge")
|
| 883 |
+
if hyperedge is not None:
|
| 884 |
+
raise nx.NetworkXError("GraphML reader doesn't support hyperedges")
|
| 885 |
+
# add nodes
|
| 886 |
+
for node_xml in graph_xml.findall(f"{{{self.NS_GRAPHML}}}node"):
|
| 887 |
+
self.add_node(G, node_xml, graphml_keys, defaults)
|
| 888 |
+
# add edges
|
| 889 |
+
for edge_xml in graph_xml.findall(f"{{{self.NS_GRAPHML}}}edge"):
|
| 890 |
+
self.add_edge(G, edge_xml, graphml_keys)
|
| 891 |
+
# add graph data
|
| 892 |
+
data = self.decode_data_elements(graphml_keys, graph_xml)
|
| 893 |
+
G.graph.update(data)
|
| 894 |
+
|
| 895 |
+
# switch to Graph or DiGraph if no parallel edges were found
|
| 896 |
+
if self.multigraph:
|
| 897 |
+
return G
|
| 898 |
+
|
| 899 |
+
G = nx.DiGraph(G) if G.is_directed() else nx.Graph(G)
|
| 900 |
+
# add explicit edge "id" from file as attribute in NX graph.
|
| 901 |
+
nx.set_edge_attributes(G, values=self.edge_ids, name="id")
|
| 902 |
+
return G
|
| 903 |
+
|
| 904 |
+
def add_node(self, G, node_xml, graphml_keys, defaults):
|
| 905 |
+
"""Add a node to the graph."""
|
| 906 |
+
# warn on finding unsupported ports tag
|
| 907 |
+
ports = node_xml.find(f"{{{self.NS_GRAPHML}}}port")
|
| 908 |
+
if ports is not None:
|
| 909 |
+
warnings.warn("GraphML port tag not supported.")
|
| 910 |
+
# find the node by id and cast it to the appropriate type
|
| 911 |
+
node_id = self.node_type(node_xml.get("id"))
|
| 912 |
+
# get data/attributes for node
|
| 913 |
+
data = self.decode_data_elements(graphml_keys, node_xml)
|
| 914 |
+
G.add_node(node_id, **data)
|
| 915 |
+
# get child nodes
|
| 916 |
+
if node_xml.attrib.get("yfiles.foldertype") == "group":
|
| 917 |
+
graph_xml = node_xml.find(f"{{{self.NS_GRAPHML}}}graph")
|
| 918 |
+
self.make_graph(graph_xml, graphml_keys, defaults, G)
|
| 919 |
+
|
| 920 |
+
def add_edge(self, G, edge_element, graphml_keys):
|
| 921 |
+
"""Add an edge to the graph."""
|
| 922 |
+
# warn on finding unsupported ports tag
|
| 923 |
+
ports = edge_element.find(f"{{{self.NS_GRAPHML}}}port")
|
| 924 |
+
if ports is not None:
|
| 925 |
+
warnings.warn("GraphML port tag not supported.")
|
| 926 |
+
|
| 927 |
+
# raise error if we find mixed directed and undirected edges
|
| 928 |
+
directed = edge_element.get("directed")
|
| 929 |
+
if G.is_directed() and directed == "false":
|
| 930 |
+
msg = "directed=false edge found in directed graph."
|
| 931 |
+
raise nx.NetworkXError(msg)
|
| 932 |
+
if (not G.is_directed()) and directed == "true":
|
| 933 |
+
msg = "directed=true edge found in undirected graph."
|
| 934 |
+
raise nx.NetworkXError(msg)
|
| 935 |
+
|
| 936 |
+
source = self.node_type(edge_element.get("source"))
|
| 937 |
+
target = self.node_type(edge_element.get("target"))
|
| 938 |
+
data = self.decode_data_elements(graphml_keys, edge_element)
|
| 939 |
+
# GraphML stores edge ids as an attribute
|
| 940 |
+
# NetworkX uses them as keys in multigraphs too if no key
|
| 941 |
+
# attribute is specified
|
| 942 |
+
edge_id = edge_element.get("id")
|
| 943 |
+
if edge_id:
|
| 944 |
+
# self.edge_ids is used by `make_graph` method for non-multigraphs
|
| 945 |
+
self.edge_ids[source, target] = edge_id
|
| 946 |
+
try:
|
| 947 |
+
edge_id = self.edge_key_type(edge_id)
|
| 948 |
+
except ValueError: # Could not convert.
|
| 949 |
+
pass
|
| 950 |
+
else:
|
| 951 |
+
edge_id = data.get("key")
|
| 952 |
+
|
| 953 |
+
if G.has_edge(source, target):
|
| 954 |
+
# mark this as a multigraph
|
| 955 |
+
self.multigraph = True
|
| 956 |
+
|
| 957 |
+
# Use add_edges_from to avoid error with add_edge when `'key' in data`
|
| 958 |
+
# Note there is only one edge here...
|
| 959 |
+
G.add_edges_from([(source, target, edge_id, data)])
|
| 960 |
+
|
| 961 |
+
def decode_data_elements(self, graphml_keys, obj_xml):
|
| 962 |
+
"""Use the key information to decode the data XML if present."""
|
| 963 |
+
data = {}
|
| 964 |
+
for data_element in obj_xml.findall(f"{{{self.NS_GRAPHML}}}data"):
|
| 965 |
+
key = data_element.get("key")
|
| 966 |
+
try:
|
| 967 |
+
data_name = graphml_keys[key]["name"]
|
| 968 |
+
data_type = graphml_keys[key]["type"]
|
| 969 |
+
except KeyError as err:
|
| 970 |
+
raise nx.NetworkXError(f"Bad GraphML data: no key {key}") from err
|
| 971 |
+
text = data_element.text
|
| 972 |
+
# assume anything with subelements is a yfiles extension
|
| 973 |
+
if text is not None and len(list(data_element)) == 0:
|
| 974 |
+
if data_type == bool:
|
| 975 |
+
# Ignore cases.
|
| 976 |
+
# http://docs.oracle.com/javase/6/docs/api/java/lang/
|
| 977 |
+
# Boolean.html#parseBoolean%28java.lang.String%29
|
| 978 |
+
data[data_name] = self.convert_bool[text.lower()]
|
| 979 |
+
else:
|
| 980 |
+
data[data_name] = data_type(text)
|
| 981 |
+
elif len(list(data_element)) > 0:
|
| 982 |
+
# Assume yfiles as subelements, try to extract node_label
|
| 983 |
+
node_label = None
|
| 984 |
+
# set GenericNode's configuration as shape type
|
| 985 |
+
gn = data_element.find(f"{{{self.NS_Y}}}GenericNode")
|
| 986 |
+
if gn is not None:
|
| 987 |
+
data["shape_type"] = gn.get("configuration")
|
| 988 |
+
for node_type in ["GenericNode", "ShapeNode", "SVGNode", "ImageNode"]:
|
| 989 |
+
pref = f"{{{self.NS_Y}}}{node_type}/{{{self.NS_Y}}}"
|
| 990 |
+
geometry = data_element.find(f"{pref}Geometry")
|
| 991 |
+
if geometry is not None:
|
| 992 |
+
data["x"] = geometry.get("x")
|
| 993 |
+
data["y"] = geometry.get("y")
|
| 994 |
+
if node_label is None:
|
| 995 |
+
node_label = data_element.find(f"{pref}NodeLabel")
|
| 996 |
+
shape = data_element.find(f"{pref}Shape")
|
| 997 |
+
if shape is not None:
|
| 998 |
+
data["shape_type"] = shape.get("type")
|
| 999 |
+
if node_label is not None:
|
| 1000 |
+
data["label"] = node_label.text
|
| 1001 |
+
|
| 1002 |
+
# check all the different types of edges available in yEd.
|
| 1003 |
+
for edge_type in [
|
| 1004 |
+
"PolyLineEdge",
|
| 1005 |
+
"SplineEdge",
|
| 1006 |
+
"QuadCurveEdge",
|
| 1007 |
+
"BezierEdge",
|
| 1008 |
+
"ArcEdge",
|
| 1009 |
+
]:
|
| 1010 |
+
pref = f"{{{self.NS_Y}}}{edge_type}/{{{self.NS_Y}}}"
|
| 1011 |
+
edge_label = data_element.find(f"{pref}EdgeLabel")
|
| 1012 |
+
if edge_label is not None:
|
| 1013 |
+
break
|
| 1014 |
+
if edge_label is not None:
|
| 1015 |
+
data["label"] = edge_label.text
|
| 1016 |
+
elif text is None:
|
| 1017 |
+
data[data_name] = ""
|
| 1018 |
+
return data
|
| 1019 |
+
|
| 1020 |
+
def find_graphml_keys(self, graph_element):
|
| 1021 |
+
"""Extracts all the keys and key defaults from the xml."""
|
| 1022 |
+
graphml_keys = {}
|
| 1023 |
+
graphml_key_defaults = {}
|
| 1024 |
+
for k in graph_element.findall(f"{{{self.NS_GRAPHML}}}key"):
|
| 1025 |
+
attr_id = k.get("id")
|
| 1026 |
+
attr_type = k.get("attr.type")
|
| 1027 |
+
attr_name = k.get("attr.name")
|
| 1028 |
+
yfiles_type = k.get("yfiles.type")
|
| 1029 |
+
if yfiles_type is not None:
|
| 1030 |
+
attr_name = yfiles_type
|
| 1031 |
+
attr_type = "yfiles"
|
| 1032 |
+
if attr_type is None:
|
| 1033 |
+
attr_type = "string"
|
| 1034 |
+
warnings.warn(f"No key type for id {attr_id}. Using string")
|
| 1035 |
+
if attr_name is None:
|
| 1036 |
+
raise nx.NetworkXError(f"Unknown key for id {attr_id}.")
|
| 1037 |
+
graphml_keys[attr_id] = {
|
| 1038 |
+
"name": attr_name,
|
| 1039 |
+
"type": self.python_type[attr_type],
|
| 1040 |
+
"for": k.get("for"),
|
| 1041 |
+
}
|
| 1042 |
+
# check for "default" sub-element of key element
|
| 1043 |
+
default = k.find(f"{{{self.NS_GRAPHML}}}default")
|
| 1044 |
+
if default is not None:
|
| 1045 |
+
# Handle default values identically to data element values
|
| 1046 |
+
python_type = graphml_keys[attr_id]["type"]
|
| 1047 |
+
if python_type == bool:
|
| 1048 |
+
graphml_key_defaults[attr_id] = self.convert_bool[
|
| 1049 |
+
default.text.lower()
|
| 1050 |
+
]
|
| 1051 |
+
else:
|
| 1052 |
+
graphml_key_defaults[attr_id] = python_type(default.text)
|
| 1053 |
+
return graphml_keys, graphml_key_defaults
|
.venv/lib/python3.11/site-packages/networkx/readwrite/json_graph/cytoscape.py
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import networkx as nx
|
| 2 |
+
|
| 3 |
+
__all__ = ["cytoscape_data", "cytoscape_graph"]
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def cytoscape_data(G, name="name", ident="id"):
|
| 7 |
+
"""Returns data in Cytoscape JSON format (cyjs).
|
| 8 |
+
|
| 9 |
+
Parameters
|
| 10 |
+
----------
|
| 11 |
+
G : NetworkX Graph
|
| 12 |
+
The graph to convert to cytoscape format
|
| 13 |
+
name : string
|
| 14 |
+
A string which is mapped to the 'name' node element in cyjs format.
|
| 15 |
+
Must not have the same value as `ident`.
|
| 16 |
+
ident : string
|
| 17 |
+
A string which is mapped to the 'id' node element in cyjs format.
|
| 18 |
+
Must not have the same value as `name`.
|
| 19 |
+
|
| 20 |
+
Returns
|
| 21 |
+
-------
|
| 22 |
+
data: dict
|
| 23 |
+
A dictionary with cyjs formatted data.
|
| 24 |
+
|
| 25 |
+
Raises
|
| 26 |
+
------
|
| 27 |
+
NetworkXError
|
| 28 |
+
If the values for `name` and `ident` are identical.
|
| 29 |
+
|
| 30 |
+
See Also
|
| 31 |
+
--------
|
| 32 |
+
cytoscape_graph: convert a dictionary in cyjs format to a graph
|
| 33 |
+
|
| 34 |
+
References
|
| 35 |
+
----------
|
| 36 |
+
.. [1] Cytoscape user's manual:
|
| 37 |
+
http://manual.cytoscape.org/en/stable/index.html
|
| 38 |
+
|
| 39 |
+
Examples
|
| 40 |
+
--------
|
| 41 |
+
>>> G = nx.path_graph(2)
|
| 42 |
+
>>> nx.cytoscape_data(G) # doctest: +SKIP
|
| 43 |
+
{'data': [],
|
| 44 |
+
'directed': False,
|
| 45 |
+
'multigraph': False,
|
| 46 |
+
'elements': {'nodes': [{'data': {'id': '0', 'value': 0, 'name': '0'}},
|
| 47 |
+
{'data': {'id': '1', 'value': 1, 'name': '1'}}],
|
| 48 |
+
'edges': [{'data': {'source': 0, 'target': 1}}]}}
|
| 49 |
+
"""
|
| 50 |
+
if name == ident:
|
| 51 |
+
raise nx.NetworkXError("name and ident must be different.")
|
| 52 |
+
|
| 53 |
+
jsondata = {"data": list(G.graph.items())}
|
| 54 |
+
jsondata["directed"] = G.is_directed()
|
| 55 |
+
jsondata["multigraph"] = G.is_multigraph()
|
| 56 |
+
jsondata["elements"] = {"nodes": [], "edges": []}
|
| 57 |
+
nodes = jsondata["elements"]["nodes"]
|
| 58 |
+
edges = jsondata["elements"]["edges"]
|
| 59 |
+
|
| 60 |
+
for i, j in G.nodes.items():
|
| 61 |
+
n = {"data": j.copy()}
|
| 62 |
+
n["data"]["id"] = j.get(ident) or str(i)
|
| 63 |
+
n["data"]["value"] = i
|
| 64 |
+
n["data"]["name"] = j.get(name) or str(i)
|
| 65 |
+
nodes.append(n)
|
| 66 |
+
|
| 67 |
+
if G.is_multigraph():
|
| 68 |
+
for e in G.edges(keys=True):
|
| 69 |
+
n = {"data": G.adj[e[0]][e[1]][e[2]].copy()}
|
| 70 |
+
n["data"]["source"] = e[0]
|
| 71 |
+
n["data"]["target"] = e[1]
|
| 72 |
+
n["data"]["key"] = e[2]
|
| 73 |
+
edges.append(n)
|
| 74 |
+
else:
|
| 75 |
+
for e in G.edges():
|
| 76 |
+
n = {"data": G.adj[e[0]][e[1]].copy()}
|
| 77 |
+
n["data"]["source"] = e[0]
|
| 78 |
+
n["data"]["target"] = e[1]
|
| 79 |
+
edges.append(n)
|
| 80 |
+
return jsondata
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 84 |
+
def cytoscape_graph(data, name="name", ident="id"):
|
| 85 |
+
"""
|
| 86 |
+
Create a NetworkX graph from a dictionary in cytoscape JSON format.
|
| 87 |
+
|
| 88 |
+
Parameters
|
| 89 |
+
----------
|
| 90 |
+
data : dict
|
| 91 |
+
A dictionary of data conforming to cytoscape JSON format.
|
| 92 |
+
name : string
|
| 93 |
+
A string which is mapped to the 'name' node element in cyjs format.
|
| 94 |
+
Must not have the same value as `ident`.
|
| 95 |
+
ident : string
|
| 96 |
+
A string which is mapped to the 'id' node element in cyjs format.
|
| 97 |
+
Must not have the same value as `name`.
|
| 98 |
+
|
| 99 |
+
Returns
|
| 100 |
+
-------
|
| 101 |
+
graph : a NetworkX graph instance
|
| 102 |
+
The `graph` can be an instance of `Graph`, `DiGraph`, `MultiGraph`, or
|
| 103 |
+
`MultiDiGraph` depending on the input data.
|
| 104 |
+
|
| 105 |
+
Raises
|
| 106 |
+
------
|
| 107 |
+
NetworkXError
|
| 108 |
+
If the `name` and `ident` attributes are identical.
|
| 109 |
+
|
| 110 |
+
See Also
|
| 111 |
+
--------
|
| 112 |
+
cytoscape_data: convert a NetworkX graph to a dict in cyjs format
|
| 113 |
+
|
| 114 |
+
References
|
| 115 |
+
----------
|
| 116 |
+
.. [1] Cytoscape user's manual:
|
| 117 |
+
http://manual.cytoscape.org/en/stable/index.html
|
| 118 |
+
|
| 119 |
+
Examples
|
| 120 |
+
--------
|
| 121 |
+
>>> data_dict = {
|
| 122 |
+
... "data": [],
|
| 123 |
+
... "directed": False,
|
| 124 |
+
... "multigraph": False,
|
| 125 |
+
... "elements": {
|
| 126 |
+
... "nodes": [
|
| 127 |
+
... {"data": {"id": "0", "value": 0, "name": "0"}},
|
| 128 |
+
... {"data": {"id": "1", "value": 1, "name": "1"}},
|
| 129 |
+
... ],
|
| 130 |
+
... "edges": [{"data": {"source": 0, "target": 1}}],
|
| 131 |
+
... },
|
| 132 |
+
... }
|
| 133 |
+
>>> G = nx.cytoscape_graph(data_dict)
|
| 134 |
+
>>> G.name
|
| 135 |
+
''
|
| 136 |
+
>>> G.nodes()
|
| 137 |
+
NodeView((0, 1))
|
| 138 |
+
>>> G.nodes(data=True)[0]
|
| 139 |
+
{'id': '0', 'value': 0, 'name': '0'}
|
| 140 |
+
>>> G.edges(data=True)
|
| 141 |
+
EdgeDataView([(0, 1, {'source': 0, 'target': 1})])
|
| 142 |
+
"""
|
| 143 |
+
if name == ident:
|
| 144 |
+
raise nx.NetworkXError("name and ident must be different.")
|
| 145 |
+
|
| 146 |
+
multigraph = data.get("multigraph")
|
| 147 |
+
directed = data.get("directed")
|
| 148 |
+
if multigraph:
|
| 149 |
+
graph = nx.MultiGraph()
|
| 150 |
+
else:
|
| 151 |
+
graph = nx.Graph()
|
| 152 |
+
if directed:
|
| 153 |
+
graph = graph.to_directed()
|
| 154 |
+
graph.graph = dict(data.get("data"))
|
| 155 |
+
for d in data["elements"]["nodes"]:
|
| 156 |
+
node_data = d["data"].copy()
|
| 157 |
+
node = d["data"]["value"]
|
| 158 |
+
|
| 159 |
+
if d["data"].get(name):
|
| 160 |
+
node_data[name] = d["data"].get(name)
|
| 161 |
+
if d["data"].get(ident):
|
| 162 |
+
node_data[ident] = d["data"].get(ident)
|
| 163 |
+
|
| 164 |
+
graph.add_node(node)
|
| 165 |
+
graph.nodes[node].update(node_data)
|
| 166 |
+
|
| 167 |
+
for d in data["elements"]["edges"]:
|
| 168 |
+
edge_data = d["data"].copy()
|
| 169 |
+
sour = d["data"]["source"]
|
| 170 |
+
targ = d["data"]["target"]
|
| 171 |
+
if multigraph:
|
| 172 |
+
key = d["data"].get("key", 0)
|
| 173 |
+
graph.add_edge(sour, targ, key=key)
|
| 174 |
+
graph.edges[sour, targ, key].update(edge_data)
|
| 175 |
+
else:
|
| 176 |
+
graph.add_edge(sour, targ)
|
| 177 |
+
graph.edges[sour, targ].update(edge_data)
|
| 178 |
+
return graph
|
.venv/lib/python3.11/site-packages/networkx/readwrite/json_graph/node_link.py
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import warnings
|
| 2 |
+
from itertools import count
|
| 3 |
+
|
| 4 |
+
import networkx as nx
|
| 5 |
+
|
| 6 |
+
__all__ = ["node_link_data", "node_link_graph"]
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def _to_tuple(x):
|
| 10 |
+
"""Converts lists to tuples, including nested lists.
|
| 11 |
+
|
| 12 |
+
All other non-list inputs are passed through unmodified. This function is
|
| 13 |
+
intended to be used to convert potentially nested lists from json files
|
| 14 |
+
into valid nodes.
|
| 15 |
+
|
| 16 |
+
Examples
|
| 17 |
+
--------
|
| 18 |
+
>>> _to_tuple([1, 2, [3, 4]])
|
| 19 |
+
(1, 2, (3, 4))
|
| 20 |
+
"""
|
| 21 |
+
if not isinstance(x, tuple | list):
|
| 22 |
+
return x
|
| 23 |
+
return tuple(map(_to_tuple, x))
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def node_link_data(
|
| 27 |
+
G,
|
| 28 |
+
*,
|
| 29 |
+
source="source",
|
| 30 |
+
target="target",
|
| 31 |
+
name="id",
|
| 32 |
+
key="key",
|
| 33 |
+
edges=None,
|
| 34 |
+
nodes="nodes",
|
| 35 |
+
link=None,
|
| 36 |
+
):
|
| 37 |
+
"""Returns data in node-link format that is suitable for JSON serialization
|
| 38 |
+
and use in JavaScript documents.
|
| 39 |
+
|
| 40 |
+
Parameters
|
| 41 |
+
----------
|
| 42 |
+
G : NetworkX graph
|
| 43 |
+
source : string
|
| 44 |
+
A string that provides the 'source' attribute name for storing NetworkX-internal graph data.
|
| 45 |
+
target : string
|
| 46 |
+
A string that provides the 'target' attribute name for storing NetworkX-internal graph data.
|
| 47 |
+
name : string
|
| 48 |
+
A string that provides the 'name' attribute name for storing NetworkX-internal graph data.
|
| 49 |
+
key : string
|
| 50 |
+
A string that provides the 'key' attribute name for storing NetworkX-internal graph data.
|
| 51 |
+
edges : string
|
| 52 |
+
A string that provides the 'edges' attribute name for storing NetworkX-internal graph data.
|
| 53 |
+
nodes : string
|
| 54 |
+
A string that provides the 'nodes' attribute name for storing NetworkX-internal graph data.
|
| 55 |
+
link : string
|
| 56 |
+
.. deprecated:: 3.4
|
| 57 |
+
|
| 58 |
+
The `link` argument is deprecated and will be removed in version `3.6`.
|
| 59 |
+
Use the `edges` keyword instead.
|
| 60 |
+
|
| 61 |
+
A string that provides the 'edges' attribute name for storing NetworkX-internal graph data.
|
| 62 |
+
|
| 63 |
+
Returns
|
| 64 |
+
-------
|
| 65 |
+
data : dict
|
| 66 |
+
A dictionary with node-link formatted data.
|
| 67 |
+
|
| 68 |
+
Raises
|
| 69 |
+
------
|
| 70 |
+
NetworkXError
|
| 71 |
+
If the values of 'source', 'target' and 'key' are not unique.
|
| 72 |
+
|
| 73 |
+
Examples
|
| 74 |
+
--------
|
| 75 |
+
>>> from pprint import pprint
|
| 76 |
+
>>> G = nx.Graph([("A", "B")])
|
| 77 |
+
>>> data1 = nx.node_link_data(G, edges="edges")
|
| 78 |
+
>>> pprint(data1)
|
| 79 |
+
{'directed': False,
|
| 80 |
+
'edges': [{'source': 'A', 'target': 'B'}],
|
| 81 |
+
'graph': {},
|
| 82 |
+
'multigraph': False,
|
| 83 |
+
'nodes': [{'id': 'A'}, {'id': 'B'}]}
|
| 84 |
+
|
| 85 |
+
To serialize with JSON
|
| 86 |
+
|
| 87 |
+
>>> import json
|
| 88 |
+
>>> s1 = json.dumps(data1)
|
| 89 |
+
>>> s1
|
| 90 |
+
'{"directed": false, "multigraph": false, "graph": {}, "nodes": [{"id": "A"}, {"id": "B"}], "edges": [{"source": "A", "target": "B"}]}'
|
| 91 |
+
|
| 92 |
+
A graph can also be serialized by passing `node_link_data` as an encoder function.
|
| 93 |
+
|
| 94 |
+
>>> s1 = json.dumps(G, default=nx.node_link_data)
|
| 95 |
+
>>> s1
|
| 96 |
+
'{"directed": false, "multigraph": false, "graph": {}, "nodes": [{"id": "A"}, {"id": "B"}], "links": [{"source": "A", "target": "B"}]}'
|
| 97 |
+
|
| 98 |
+
The attribute names for storing NetworkX-internal graph data can
|
| 99 |
+
be specified as keyword options.
|
| 100 |
+
|
| 101 |
+
>>> H = nx.gn_graph(2)
|
| 102 |
+
>>> data2 = nx.node_link_data(
|
| 103 |
+
... H, edges="links", source="from", target="to", nodes="vertices"
|
| 104 |
+
... )
|
| 105 |
+
>>> pprint(data2)
|
| 106 |
+
{'directed': True,
|
| 107 |
+
'graph': {},
|
| 108 |
+
'links': [{'from': 1, 'to': 0}],
|
| 109 |
+
'multigraph': False,
|
| 110 |
+
'vertices': [{'id': 0}, {'id': 1}]}
|
| 111 |
+
|
| 112 |
+
Notes
|
| 113 |
+
-----
|
| 114 |
+
Graph, node, and link attributes are stored in this format. Note that
|
| 115 |
+
attribute keys will be converted to strings in order to comply with JSON.
|
| 116 |
+
|
| 117 |
+
Attribute 'key' is only used for multigraphs.
|
| 118 |
+
|
| 119 |
+
To use `node_link_data` in conjunction with `node_link_graph`,
|
| 120 |
+
the keyword names for the attributes must match.
|
| 121 |
+
|
| 122 |
+
See Also
|
| 123 |
+
--------
|
| 124 |
+
node_link_graph, adjacency_data, tree_data
|
| 125 |
+
"""
|
| 126 |
+
# TODO: Remove between the lines when `link` deprecation expires
|
| 127 |
+
# -------------------------------------------------------------
|
| 128 |
+
if link is not None:
|
| 129 |
+
warnings.warn(
|
| 130 |
+
"Keyword argument 'link' is deprecated; use 'edges' instead",
|
| 131 |
+
DeprecationWarning,
|
| 132 |
+
stacklevel=2,
|
| 133 |
+
)
|
| 134 |
+
if edges is not None:
|
| 135 |
+
raise ValueError(
|
| 136 |
+
"Both 'edges' and 'link' are specified. Use 'edges', 'link' will be remove in a future release"
|
| 137 |
+
)
|
| 138 |
+
else:
|
| 139 |
+
edges = link
|
| 140 |
+
else:
|
| 141 |
+
if edges is None:
|
| 142 |
+
warnings.warn(
|
| 143 |
+
(
|
| 144 |
+
'\nThe default value will be `edges="edges" in NetworkX 3.6.\n\n'
|
| 145 |
+
"To make this warning go away, explicitly set the edges kwarg, e.g.:\n\n"
|
| 146 |
+
' nx.node_link_data(G, edges="links") to preserve current behavior, or\n'
|
| 147 |
+
' nx.node_link_data(G, edges="edges") for forward compatibility.'
|
| 148 |
+
),
|
| 149 |
+
FutureWarning,
|
| 150 |
+
)
|
| 151 |
+
edges = "links"
|
| 152 |
+
# ------------------------------------------------------------
|
| 153 |
+
|
| 154 |
+
multigraph = G.is_multigraph()
|
| 155 |
+
|
| 156 |
+
# Allow 'key' to be omitted from attrs if the graph is not a multigraph.
|
| 157 |
+
key = None if not multigraph else key
|
| 158 |
+
if len({source, target, key}) < 3:
|
| 159 |
+
raise nx.NetworkXError("Attribute names are not unique.")
|
| 160 |
+
data = {
|
| 161 |
+
"directed": G.is_directed(),
|
| 162 |
+
"multigraph": multigraph,
|
| 163 |
+
"graph": G.graph,
|
| 164 |
+
nodes: [{**G.nodes[n], name: n} for n in G],
|
| 165 |
+
}
|
| 166 |
+
if multigraph:
|
| 167 |
+
data[edges] = [
|
| 168 |
+
{**d, source: u, target: v, key: k}
|
| 169 |
+
for u, v, k, d in G.edges(keys=True, data=True)
|
| 170 |
+
]
|
| 171 |
+
else:
|
| 172 |
+
data[edges] = [{**d, source: u, target: v} for u, v, d in G.edges(data=True)]
|
| 173 |
+
return data
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 177 |
+
def node_link_graph(
|
| 178 |
+
data,
|
| 179 |
+
directed=False,
|
| 180 |
+
multigraph=True,
|
| 181 |
+
*,
|
| 182 |
+
source="source",
|
| 183 |
+
target="target",
|
| 184 |
+
name="id",
|
| 185 |
+
key="key",
|
| 186 |
+
edges=None,
|
| 187 |
+
nodes="nodes",
|
| 188 |
+
link=None,
|
| 189 |
+
):
|
| 190 |
+
"""Returns graph from node-link data format.
|
| 191 |
+
|
| 192 |
+
Useful for de-serialization from JSON.
|
| 193 |
+
|
| 194 |
+
Parameters
|
| 195 |
+
----------
|
| 196 |
+
data : dict
|
| 197 |
+
node-link formatted graph data
|
| 198 |
+
|
| 199 |
+
directed : bool
|
| 200 |
+
If True, and direction not specified in data, return a directed graph.
|
| 201 |
+
|
| 202 |
+
multigraph : bool
|
| 203 |
+
If True, and multigraph not specified in data, return a multigraph.
|
| 204 |
+
|
| 205 |
+
source : string
|
| 206 |
+
A string that provides the 'source' attribute name for storing NetworkX-internal graph data.
|
| 207 |
+
target : string
|
| 208 |
+
A string that provides the 'target' attribute name for storing NetworkX-internal graph data.
|
| 209 |
+
name : string
|
| 210 |
+
A string that provides the 'name' attribute name for storing NetworkX-internal graph data.
|
| 211 |
+
key : string
|
| 212 |
+
A string that provides the 'key' attribute name for storing NetworkX-internal graph data.
|
| 213 |
+
edges : string
|
| 214 |
+
A string that provides the 'edges' attribute name for storing NetworkX-internal graph data.
|
| 215 |
+
nodes : string
|
| 216 |
+
A string that provides the 'nodes' attribute name for storing NetworkX-internal graph data.
|
| 217 |
+
link : string
|
| 218 |
+
.. deprecated:: 3.4
|
| 219 |
+
|
| 220 |
+
The `link` argument is deprecated and will be removed in version `3.6`.
|
| 221 |
+
Use the `edges` keyword instead.
|
| 222 |
+
|
| 223 |
+
A string that provides the 'edges' attribute name for storing NetworkX-internal graph data.
|
| 224 |
+
|
| 225 |
+
Returns
|
| 226 |
+
-------
|
| 227 |
+
G : NetworkX graph
|
| 228 |
+
A NetworkX graph object
|
| 229 |
+
|
| 230 |
+
Examples
|
| 231 |
+
--------
|
| 232 |
+
|
| 233 |
+
Create data in node-link format by converting a graph.
|
| 234 |
+
|
| 235 |
+
>>> from pprint import pprint
|
| 236 |
+
>>> G = nx.Graph([("A", "B")])
|
| 237 |
+
>>> data = nx.node_link_data(G, edges="edges")
|
| 238 |
+
>>> pprint(data)
|
| 239 |
+
{'directed': False,
|
| 240 |
+
'edges': [{'source': 'A', 'target': 'B'}],
|
| 241 |
+
'graph': {},
|
| 242 |
+
'multigraph': False,
|
| 243 |
+
'nodes': [{'id': 'A'}, {'id': 'B'}]}
|
| 244 |
+
|
| 245 |
+
Revert data in node-link format to a graph.
|
| 246 |
+
|
| 247 |
+
>>> H = nx.node_link_graph(data, edges="edges")
|
| 248 |
+
>>> print(H.edges)
|
| 249 |
+
[('A', 'B')]
|
| 250 |
+
|
| 251 |
+
To serialize and deserialize a graph with JSON,
|
| 252 |
+
|
| 253 |
+
>>> import json
|
| 254 |
+
>>> d = json.dumps(nx.node_link_data(G, edges="edges"))
|
| 255 |
+
>>> H = nx.node_link_graph(json.loads(d), edges="edges")
|
| 256 |
+
>>> print(G.edges, H.edges)
|
| 257 |
+
[('A', 'B')] [('A', 'B')]
|
| 258 |
+
|
| 259 |
+
|
| 260 |
+
Notes
|
| 261 |
+
-----
|
| 262 |
+
Attribute 'key' is only used for multigraphs.
|
| 263 |
+
|
| 264 |
+
To use `node_link_data` in conjunction with `node_link_graph`,
|
| 265 |
+
the keyword names for the attributes must match.
|
| 266 |
+
|
| 267 |
+
See Also
|
| 268 |
+
--------
|
| 269 |
+
node_link_data, adjacency_data, tree_data
|
| 270 |
+
"""
|
| 271 |
+
# TODO: Remove between the lines when `link` deprecation expires
|
| 272 |
+
# -------------------------------------------------------------
|
| 273 |
+
if link is not None:
|
| 274 |
+
warnings.warn(
|
| 275 |
+
"Keyword argument 'link' is deprecated; use 'edges' instead",
|
| 276 |
+
DeprecationWarning,
|
| 277 |
+
stacklevel=2,
|
| 278 |
+
)
|
| 279 |
+
if edges is not None:
|
| 280 |
+
raise ValueError(
|
| 281 |
+
"Both 'edges' and 'link' are specified. Use 'edges', 'link' will be remove in a future release"
|
| 282 |
+
)
|
| 283 |
+
else:
|
| 284 |
+
edges = link
|
| 285 |
+
else:
|
| 286 |
+
if edges is None:
|
| 287 |
+
warnings.warn(
|
| 288 |
+
(
|
| 289 |
+
'\nThe default value will be changed to `edges="edges" in NetworkX 3.6.\n\n'
|
| 290 |
+
"To make this warning go away, explicitly set the edges kwarg, e.g.:\n\n"
|
| 291 |
+
' nx.node_link_graph(data, edges="links") to preserve current behavior, or\n'
|
| 292 |
+
' nx.node_link_graph(data, edges="edges") for forward compatibility.'
|
| 293 |
+
),
|
| 294 |
+
FutureWarning,
|
| 295 |
+
)
|
| 296 |
+
edges = "links"
|
| 297 |
+
# -------------------------------------------------------------
|
| 298 |
+
|
| 299 |
+
multigraph = data.get("multigraph", multigraph)
|
| 300 |
+
directed = data.get("directed", directed)
|
| 301 |
+
if multigraph:
|
| 302 |
+
graph = nx.MultiGraph()
|
| 303 |
+
else:
|
| 304 |
+
graph = nx.Graph()
|
| 305 |
+
if directed:
|
| 306 |
+
graph = graph.to_directed()
|
| 307 |
+
|
| 308 |
+
# Allow 'key' to be omitted from attrs if the graph is not a multigraph.
|
| 309 |
+
key = None if not multigraph else key
|
| 310 |
+
graph.graph = data.get("graph", {})
|
| 311 |
+
c = count()
|
| 312 |
+
for d in data[nodes]:
|
| 313 |
+
node = _to_tuple(d.get(name, next(c)))
|
| 314 |
+
nodedata = {str(k): v for k, v in d.items() if k != name}
|
| 315 |
+
graph.add_node(node, **nodedata)
|
| 316 |
+
for d in data[edges]:
|
| 317 |
+
src = tuple(d[source]) if isinstance(d[source], list) else d[source]
|
| 318 |
+
tgt = tuple(d[target]) if isinstance(d[target], list) else d[target]
|
| 319 |
+
if not multigraph:
|
| 320 |
+
edgedata = {str(k): v for k, v in d.items() if k != source and k != target}
|
| 321 |
+
graph.add_edge(src, tgt, **edgedata)
|
| 322 |
+
else:
|
| 323 |
+
ky = d.get(key, None)
|
| 324 |
+
edgedata = {
|
| 325 |
+
str(k): v
|
| 326 |
+
for k, v in d.items()
|
| 327 |
+
if k != source and k != target and k != key
|
| 328 |
+
}
|
| 329 |
+
graph.add_edge(src, tgt, ky, **edgedata)
|
| 330 |
+
return graph
|
.venv/lib/python3.11/site-packages/networkx/readwrite/json_graph/tree.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from itertools import chain
|
| 2 |
+
|
| 3 |
+
import networkx as nx
|
| 4 |
+
|
| 5 |
+
__all__ = ["tree_data", "tree_graph"]
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def tree_data(G, root, ident="id", children="children"):
|
| 9 |
+
"""Returns data in tree format that is suitable for JSON serialization
|
| 10 |
+
and use in JavaScript documents.
|
| 11 |
+
|
| 12 |
+
Parameters
|
| 13 |
+
----------
|
| 14 |
+
G : NetworkX graph
|
| 15 |
+
G must be an oriented tree
|
| 16 |
+
|
| 17 |
+
root : node
|
| 18 |
+
The root of the tree
|
| 19 |
+
|
| 20 |
+
ident : string
|
| 21 |
+
Attribute name for storing NetworkX-internal graph data. `ident` must
|
| 22 |
+
have a different value than `children`. The default is 'id'.
|
| 23 |
+
|
| 24 |
+
children : string
|
| 25 |
+
Attribute name for storing NetworkX-internal graph data. `children`
|
| 26 |
+
must have a different value than `ident`. The default is 'children'.
|
| 27 |
+
|
| 28 |
+
Returns
|
| 29 |
+
-------
|
| 30 |
+
data : dict
|
| 31 |
+
A dictionary with node-link formatted data.
|
| 32 |
+
|
| 33 |
+
Raises
|
| 34 |
+
------
|
| 35 |
+
NetworkXError
|
| 36 |
+
If `children` and `ident` attributes are identical.
|
| 37 |
+
|
| 38 |
+
Examples
|
| 39 |
+
--------
|
| 40 |
+
>>> from networkx.readwrite import json_graph
|
| 41 |
+
>>> G = nx.DiGraph([(1, 2)])
|
| 42 |
+
>>> data = json_graph.tree_data(G, root=1)
|
| 43 |
+
|
| 44 |
+
To serialize with json
|
| 45 |
+
|
| 46 |
+
>>> import json
|
| 47 |
+
>>> s = json.dumps(data)
|
| 48 |
+
|
| 49 |
+
Notes
|
| 50 |
+
-----
|
| 51 |
+
Node attributes are stored in this format but keys
|
| 52 |
+
for attributes must be strings if you want to serialize with JSON.
|
| 53 |
+
|
| 54 |
+
Graph and edge attributes are not stored.
|
| 55 |
+
|
| 56 |
+
See Also
|
| 57 |
+
--------
|
| 58 |
+
tree_graph, node_link_data, adjacency_data
|
| 59 |
+
"""
|
| 60 |
+
if G.number_of_nodes() != G.number_of_edges() + 1:
|
| 61 |
+
raise TypeError("G is not a tree.")
|
| 62 |
+
if not G.is_directed():
|
| 63 |
+
raise TypeError("G is not directed.")
|
| 64 |
+
if not nx.is_weakly_connected(G):
|
| 65 |
+
raise TypeError("G is not weakly connected.")
|
| 66 |
+
|
| 67 |
+
if ident == children:
|
| 68 |
+
raise nx.NetworkXError("The values for `id` and `children` must be different.")
|
| 69 |
+
|
| 70 |
+
def add_children(n, G):
|
| 71 |
+
nbrs = G[n]
|
| 72 |
+
if len(nbrs) == 0:
|
| 73 |
+
return []
|
| 74 |
+
children_ = []
|
| 75 |
+
for child in nbrs:
|
| 76 |
+
d = {**G.nodes[child], ident: child}
|
| 77 |
+
c = add_children(child, G)
|
| 78 |
+
if c:
|
| 79 |
+
d[children] = c
|
| 80 |
+
children_.append(d)
|
| 81 |
+
return children_
|
| 82 |
+
|
| 83 |
+
return {**G.nodes[root], ident: root, children: add_children(root, G)}
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 87 |
+
def tree_graph(data, ident="id", children="children"):
|
| 88 |
+
"""Returns graph from tree data format.
|
| 89 |
+
|
| 90 |
+
Parameters
|
| 91 |
+
----------
|
| 92 |
+
data : dict
|
| 93 |
+
Tree formatted graph data
|
| 94 |
+
|
| 95 |
+
ident : string
|
| 96 |
+
Attribute name for storing NetworkX-internal graph data. `ident` must
|
| 97 |
+
have a different value than `children`. The default is 'id'.
|
| 98 |
+
|
| 99 |
+
children : string
|
| 100 |
+
Attribute name for storing NetworkX-internal graph data. `children`
|
| 101 |
+
must have a different value than `ident`. The default is 'children'.
|
| 102 |
+
|
| 103 |
+
Returns
|
| 104 |
+
-------
|
| 105 |
+
G : NetworkX DiGraph
|
| 106 |
+
|
| 107 |
+
Examples
|
| 108 |
+
--------
|
| 109 |
+
>>> from networkx.readwrite import json_graph
|
| 110 |
+
>>> G = nx.DiGraph([(1, 2)])
|
| 111 |
+
>>> data = json_graph.tree_data(G, root=1)
|
| 112 |
+
>>> H = json_graph.tree_graph(data)
|
| 113 |
+
|
| 114 |
+
See Also
|
| 115 |
+
--------
|
| 116 |
+
tree_data, node_link_data, adjacency_data
|
| 117 |
+
"""
|
| 118 |
+
graph = nx.DiGraph()
|
| 119 |
+
|
| 120 |
+
def add_children(parent, children_):
|
| 121 |
+
for data in children_:
|
| 122 |
+
child = data[ident]
|
| 123 |
+
graph.add_edge(parent, child)
|
| 124 |
+
grandchildren = data.get(children, [])
|
| 125 |
+
if grandchildren:
|
| 126 |
+
add_children(child, grandchildren)
|
| 127 |
+
nodedata = {
|
| 128 |
+
str(k): v for k, v in data.items() if k != ident and k != children
|
| 129 |
+
}
|
| 130 |
+
graph.add_node(child, **nodedata)
|
| 131 |
+
|
| 132 |
+
root = data[ident]
|
| 133 |
+
children_ = data.get(children, [])
|
| 134 |
+
nodedata = {str(k): v for k, v in data.items() if k != ident and k != children}
|
| 135 |
+
graph.add_node(root, **nodedata)
|
| 136 |
+
add_children(root, children_)
|
| 137 |
+
return graph
|
.venv/lib/python3.11/site-packages/networkx/readwrite/leda.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Read graphs in LEDA format.
|
| 3 |
+
|
| 4 |
+
LEDA is a C++ class library for efficient data types and algorithms.
|
| 5 |
+
|
| 6 |
+
Format
|
| 7 |
+
------
|
| 8 |
+
See http://www.algorithmic-solutions.info/leda_guide/graphs/leda_native_graph_fileformat.html
|
| 9 |
+
|
| 10 |
+
"""
|
| 11 |
+
# Original author: D. Eppstein, UC Irvine, August 12, 2003.
|
| 12 |
+
# The original code at http://www.ics.uci.edu/~eppstein/PADS/ is public domain.
|
| 13 |
+
|
| 14 |
+
__all__ = ["read_leda", "parse_leda"]
|
| 15 |
+
|
| 16 |
+
import networkx as nx
|
| 17 |
+
from networkx.exception import NetworkXError
|
| 18 |
+
from networkx.utils import open_file
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
@open_file(0, mode="rb")
|
| 22 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 23 |
+
def read_leda(path, encoding="UTF-8"):
|
| 24 |
+
"""Read graph in LEDA format from path.
|
| 25 |
+
|
| 26 |
+
Parameters
|
| 27 |
+
----------
|
| 28 |
+
path : file or string
|
| 29 |
+
File or filename to read. Filenames ending in .gz or .bz2 will be
|
| 30 |
+
uncompressed.
|
| 31 |
+
|
| 32 |
+
Returns
|
| 33 |
+
-------
|
| 34 |
+
G : NetworkX graph
|
| 35 |
+
|
| 36 |
+
Examples
|
| 37 |
+
--------
|
| 38 |
+
G=nx.read_leda('file.leda')
|
| 39 |
+
|
| 40 |
+
References
|
| 41 |
+
----------
|
| 42 |
+
.. [1] http://www.algorithmic-solutions.info/leda_guide/graphs/leda_native_graph_fileformat.html
|
| 43 |
+
"""
|
| 44 |
+
lines = (line.decode(encoding) for line in path)
|
| 45 |
+
G = parse_leda(lines)
|
| 46 |
+
return G
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 50 |
+
def parse_leda(lines):
|
| 51 |
+
"""Read graph in LEDA format from string or iterable.
|
| 52 |
+
|
| 53 |
+
Parameters
|
| 54 |
+
----------
|
| 55 |
+
lines : string or iterable
|
| 56 |
+
Data in LEDA format.
|
| 57 |
+
|
| 58 |
+
Returns
|
| 59 |
+
-------
|
| 60 |
+
G : NetworkX graph
|
| 61 |
+
|
| 62 |
+
Examples
|
| 63 |
+
--------
|
| 64 |
+
G=nx.parse_leda(string)
|
| 65 |
+
|
| 66 |
+
References
|
| 67 |
+
----------
|
| 68 |
+
.. [1] http://www.algorithmic-solutions.info/leda_guide/graphs/leda_native_graph_fileformat.html
|
| 69 |
+
"""
|
| 70 |
+
if isinstance(lines, str):
|
| 71 |
+
lines = iter(lines.split("\n"))
|
| 72 |
+
lines = iter(
|
| 73 |
+
[
|
| 74 |
+
line.rstrip("\n")
|
| 75 |
+
for line in lines
|
| 76 |
+
if not (line.startswith(("#", "\n")) or line == "")
|
| 77 |
+
]
|
| 78 |
+
)
|
| 79 |
+
for i in range(3):
|
| 80 |
+
next(lines)
|
| 81 |
+
# Graph
|
| 82 |
+
du = int(next(lines)) # -1=directed, -2=undirected
|
| 83 |
+
if du == -1:
|
| 84 |
+
G = nx.DiGraph()
|
| 85 |
+
else:
|
| 86 |
+
G = nx.Graph()
|
| 87 |
+
|
| 88 |
+
# Nodes
|
| 89 |
+
n = int(next(lines)) # number of nodes
|
| 90 |
+
node = {}
|
| 91 |
+
for i in range(1, n + 1): # LEDA counts from 1 to n
|
| 92 |
+
symbol = next(lines).rstrip().strip("|{}| ")
|
| 93 |
+
if symbol == "":
|
| 94 |
+
symbol = str(i) # use int if no label - could be trouble
|
| 95 |
+
node[i] = symbol
|
| 96 |
+
|
| 97 |
+
G.add_nodes_from([s for i, s in node.items()])
|
| 98 |
+
|
| 99 |
+
# Edges
|
| 100 |
+
m = int(next(lines)) # number of edges
|
| 101 |
+
for i in range(m):
|
| 102 |
+
try:
|
| 103 |
+
s, t, reversal, label = next(lines).split()
|
| 104 |
+
except BaseException as err:
|
| 105 |
+
raise NetworkXError(f"Too few fields in LEDA.GRAPH edge {i+1}") from err
|
| 106 |
+
# BEWARE: no handling of reversal edges
|
| 107 |
+
G.add_edge(node[int(s)], node[int(t)], label=label[2:-2])
|
| 108 |
+
return G
|
.venv/lib/python3.11/site-packages/networkx/readwrite/pajek.py
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
*****
|
| 3 |
+
Pajek
|
| 4 |
+
*****
|
| 5 |
+
Read graphs in Pajek format.
|
| 6 |
+
|
| 7 |
+
This implementation handles directed and undirected graphs including
|
| 8 |
+
those with self loops and parallel edges.
|
| 9 |
+
|
| 10 |
+
Format
|
| 11 |
+
------
|
| 12 |
+
See http://vlado.fmf.uni-lj.si/pub/networks/pajek/doc/draweps.htm
|
| 13 |
+
for format information.
|
| 14 |
+
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
import warnings
|
| 18 |
+
|
| 19 |
+
import networkx as nx
|
| 20 |
+
from networkx.utils import open_file
|
| 21 |
+
|
| 22 |
+
__all__ = ["read_pajek", "parse_pajek", "generate_pajek", "write_pajek"]
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def generate_pajek(G):
|
| 26 |
+
"""Generate lines in Pajek graph format.
|
| 27 |
+
|
| 28 |
+
Parameters
|
| 29 |
+
----------
|
| 30 |
+
G : graph
|
| 31 |
+
A Networkx graph
|
| 32 |
+
|
| 33 |
+
References
|
| 34 |
+
----------
|
| 35 |
+
See http://vlado.fmf.uni-lj.si/pub/networks/pajek/doc/draweps.htm
|
| 36 |
+
for format information.
|
| 37 |
+
"""
|
| 38 |
+
if G.name == "":
|
| 39 |
+
name = "NetworkX"
|
| 40 |
+
else:
|
| 41 |
+
name = G.name
|
| 42 |
+
# Apparently many Pajek format readers can't process this line
|
| 43 |
+
# So we'll leave it out for now.
|
| 44 |
+
# yield '*network %s'%name
|
| 45 |
+
|
| 46 |
+
# write nodes with attributes
|
| 47 |
+
yield f"*vertices {G.order()}"
|
| 48 |
+
nodes = list(G)
|
| 49 |
+
# make dictionary mapping nodes to integers
|
| 50 |
+
nodenumber = dict(zip(nodes, range(1, len(nodes) + 1)))
|
| 51 |
+
for n in nodes:
|
| 52 |
+
# copy node attributes and pop mandatory attributes
|
| 53 |
+
# to avoid duplication.
|
| 54 |
+
na = G.nodes.get(n, {}).copy()
|
| 55 |
+
x = na.pop("x", 0.0)
|
| 56 |
+
y = na.pop("y", 0.0)
|
| 57 |
+
try:
|
| 58 |
+
id = int(na.pop("id", nodenumber[n]))
|
| 59 |
+
except ValueError as err:
|
| 60 |
+
err.args += (
|
| 61 |
+
(
|
| 62 |
+
"Pajek format requires 'id' to be an int()."
|
| 63 |
+
" Refer to the 'Relabeling nodes' section."
|
| 64 |
+
),
|
| 65 |
+
)
|
| 66 |
+
raise
|
| 67 |
+
nodenumber[n] = id
|
| 68 |
+
shape = na.pop("shape", "ellipse")
|
| 69 |
+
s = " ".join(map(make_qstr, (id, n, x, y, shape)))
|
| 70 |
+
# only optional attributes are left in na.
|
| 71 |
+
for k, v in na.items():
|
| 72 |
+
if isinstance(v, str) and v.strip() != "":
|
| 73 |
+
s += f" {make_qstr(k)} {make_qstr(v)}"
|
| 74 |
+
else:
|
| 75 |
+
warnings.warn(
|
| 76 |
+
f"Node attribute {k} is not processed. {('Empty attribute' if isinstance(v, str) else 'Non-string attribute')}."
|
| 77 |
+
)
|
| 78 |
+
yield s
|
| 79 |
+
|
| 80 |
+
# write edges with attributes
|
| 81 |
+
if G.is_directed():
|
| 82 |
+
yield "*arcs"
|
| 83 |
+
else:
|
| 84 |
+
yield "*edges"
|
| 85 |
+
for u, v, edgedata in G.edges(data=True):
|
| 86 |
+
d = edgedata.copy()
|
| 87 |
+
value = d.pop("weight", 1.0) # use 1 as default edge value
|
| 88 |
+
s = " ".join(map(make_qstr, (nodenumber[u], nodenumber[v], value)))
|
| 89 |
+
for k, v in d.items():
|
| 90 |
+
if isinstance(v, str) and v.strip() != "":
|
| 91 |
+
s += f" {make_qstr(k)} {make_qstr(v)}"
|
| 92 |
+
else:
|
| 93 |
+
warnings.warn(
|
| 94 |
+
f"Edge attribute {k} is not processed. {('Empty attribute' if isinstance(v, str) else 'Non-string attribute')}."
|
| 95 |
+
)
|
| 96 |
+
yield s
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
@open_file(1, mode="wb")
|
| 100 |
+
def write_pajek(G, path, encoding="UTF-8"):
|
| 101 |
+
"""Write graph in Pajek format to path.
|
| 102 |
+
|
| 103 |
+
Parameters
|
| 104 |
+
----------
|
| 105 |
+
G : graph
|
| 106 |
+
A Networkx graph
|
| 107 |
+
path : file or string
|
| 108 |
+
File or filename to write.
|
| 109 |
+
Filenames ending in .gz or .bz2 will be compressed.
|
| 110 |
+
|
| 111 |
+
Examples
|
| 112 |
+
--------
|
| 113 |
+
>>> G = nx.path_graph(4)
|
| 114 |
+
>>> nx.write_pajek(G, "test.net")
|
| 115 |
+
|
| 116 |
+
Warnings
|
| 117 |
+
--------
|
| 118 |
+
Optional node attributes and edge attributes must be non-empty strings.
|
| 119 |
+
Otherwise it will not be written into the file. You will need to
|
| 120 |
+
convert those attributes to strings if you want to keep them.
|
| 121 |
+
|
| 122 |
+
References
|
| 123 |
+
----------
|
| 124 |
+
See http://vlado.fmf.uni-lj.si/pub/networks/pajek/doc/draweps.htm
|
| 125 |
+
for format information.
|
| 126 |
+
"""
|
| 127 |
+
for line in generate_pajek(G):
|
| 128 |
+
line += "\n"
|
| 129 |
+
path.write(line.encode(encoding))
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
@open_file(0, mode="rb")
|
| 133 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 134 |
+
def read_pajek(path, encoding="UTF-8"):
|
| 135 |
+
"""Read graph in Pajek format from path.
|
| 136 |
+
|
| 137 |
+
Parameters
|
| 138 |
+
----------
|
| 139 |
+
path : file or string
|
| 140 |
+
File or filename to write.
|
| 141 |
+
Filenames ending in .gz or .bz2 will be uncompressed.
|
| 142 |
+
|
| 143 |
+
Returns
|
| 144 |
+
-------
|
| 145 |
+
G : NetworkX MultiGraph or MultiDiGraph.
|
| 146 |
+
|
| 147 |
+
Examples
|
| 148 |
+
--------
|
| 149 |
+
>>> G = nx.path_graph(4)
|
| 150 |
+
>>> nx.write_pajek(G, "test.net")
|
| 151 |
+
>>> G = nx.read_pajek("test.net")
|
| 152 |
+
|
| 153 |
+
To create a Graph instead of a MultiGraph use
|
| 154 |
+
|
| 155 |
+
>>> G1 = nx.Graph(G)
|
| 156 |
+
|
| 157 |
+
References
|
| 158 |
+
----------
|
| 159 |
+
See http://vlado.fmf.uni-lj.si/pub/networks/pajek/doc/draweps.htm
|
| 160 |
+
for format information.
|
| 161 |
+
"""
|
| 162 |
+
lines = (line.decode(encoding) for line in path)
|
| 163 |
+
return parse_pajek(lines)
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 167 |
+
def parse_pajek(lines):
|
| 168 |
+
"""Parse Pajek format graph from string or iterable.
|
| 169 |
+
|
| 170 |
+
Parameters
|
| 171 |
+
----------
|
| 172 |
+
lines : string or iterable
|
| 173 |
+
Data in Pajek format.
|
| 174 |
+
|
| 175 |
+
Returns
|
| 176 |
+
-------
|
| 177 |
+
G : NetworkX graph
|
| 178 |
+
|
| 179 |
+
See Also
|
| 180 |
+
--------
|
| 181 |
+
read_pajek
|
| 182 |
+
|
| 183 |
+
"""
|
| 184 |
+
import shlex
|
| 185 |
+
|
| 186 |
+
# multigraph=False
|
| 187 |
+
if isinstance(lines, str):
|
| 188 |
+
lines = iter(lines.split("\n"))
|
| 189 |
+
lines = iter([line.rstrip("\n") for line in lines])
|
| 190 |
+
G = nx.MultiDiGraph() # are multiedges allowed in Pajek? assume yes
|
| 191 |
+
labels = [] # in the order of the file, needed for matrix
|
| 192 |
+
while lines:
|
| 193 |
+
try:
|
| 194 |
+
l = next(lines)
|
| 195 |
+
except: # EOF
|
| 196 |
+
break
|
| 197 |
+
if l.lower().startswith("*network"):
|
| 198 |
+
try:
|
| 199 |
+
label, name = l.split(None, 1)
|
| 200 |
+
except ValueError:
|
| 201 |
+
# Line was not of the form: *network NAME
|
| 202 |
+
pass
|
| 203 |
+
else:
|
| 204 |
+
G.graph["name"] = name
|
| 205 |
+
elif l.lower().startswith("*vertices"):
|
| 206 |
+
nodelabels = {}
|
| 207 |
+
l, nnodes = l.split()
|
| 208 |
+
for i in range(int(nnodes)):
|
| 209 |
+
l = next(lines)
|
| 210 |
+
try:
|
| 211 |
+
splitline = [
|
| 212 |
+
x.decode("utf-8") for x in shlex.split(str(l).encode("utf-8"))
|
| 213 |
+
]
|
| 214 |
+
except AttributeError:
|
| 215 |
+
splitline = shlex.split(str(l))
|
| 216 |
+
id, label = splitline[0:2]
|
| 217 |
+
labels.append(label)
|
| 218 |
+
G.add_node(label)
|
| 219 |
+
nodelabels[id] = label
|
| 220 |
+
G.nodes[label]["id"] = id
|
| 221 |
+
try:
|
| 222 |
+
x, y, shape = splitline[2:5]
|
| 223 |
+
G.nodes[label].update(
|
| 224 |
+
{"x": float(x), "y": float(y), "shape": shape}
|
| 225 |
+
)
|
| 226 |
+
except:
|
| 227 |
+
pass
|
| 228 |
+
extra_attr = zip(splitline[5::2], splitline[6::2])
|
| 229 |
+
G.nodes[label].update(extra_attr)
|
| 230 |
+
elif l.lower().startswith("*edges") or l.lower().startswith("*arcs"):
|
| 231 |
+
if l.lower().startswith("*edge"):
|
| 232 |
+
# switch from multidigraph to multigraph
|
| 233 |
+
G = nx.MultiGraph(G)
|
| 234 |
+
if l.lower().startswith("*arcs"):
|
| 235 |
+
# switch to directed with multiple arcs for each existing edge
|
| 236 |
+
G = G.to_directed()
|
| 237 |
+
for l in lines:
|
| 238 |
+
try:
|
| 239 |
+
splitline = [
|
| 240 |
+
x.decode("utf-8") for x in shlex.split(str(l).encode("utf-8"))
|
| 241 |
+
]
|
| 242 |
+
except AttributeError:
|
| 243 |
+
splitline = shlex.split(str(l))
|
| 244 |
+
|
| 245 |
+
if len(splitline) < 2:
|
| 246 |
+
continue
|
| 247 |
+
ui, vi = splitline[0:2]
|
| 248 |
+
u = nodelabels.get(ui, ui)
|
| 249 |
+
v = nodelabels.get(vi, vi)
|
| 250 |
+
# parse the data attached to this edge and put in a dictionary
|
| 251 |
+
edge_data = {}
|
| 252 |
+
try:
|
| 253 |
+
# there should always be a single value on the edge?
|
| 254 |
+
w = splitline[2:3]
|
| 255 |
+
edge_data.update({"weight": float(w[0])})
|
| 256 |
+
except:
|
| 257 |
+
pass
|
| 258 |
+
# if there isn't, just assign a 1
|
| 259 |
+
# edge_data.update({'value':1})
|
| 260 |
+
extra_attr = zip(splitline[3::2], splitline[4::2])
|
| 261 |
+
edge_data.update(extra_attr)
|
| 262 |
+
# if G.has_edge(u,v):
|
| 263 |
+
# multigraph=True
|
| 264 |
+
G.add_edge(u, v, **edge_data)
|
| 265 |
+
elif l.lower().startswith("*matrix"):
|
| 266 |
+
G = nx.DiGraph(G)
|
| 267 |
+
adj_list = (
|
| 268 |
+
(labels[row], labels[col], {"weight": int(data)})
|
| 269 |
+
for (row, line) in enumerate(lines)
|
| 270 |
+
for (col, data) in enumerate(line.split())
|
| 271 |
+
if int(data) != 0
|
| 272 |
+
)
|
| 273 |
+
G.add_edges_from(adj_list)
|
| 274 |
+
|
| 275 |
+
return G
|
| 276 |
+
|
| 277 |
+
|
| 278 |
+
def make_qstr(t):
|
| 279 |
+
"""Returns the string representation of t.
|
| 280 |
+
Add outer double-quotes if the string has a space.
|
| 281 |
+
"""
|
| 282 |
+
if not isinstance(t, str):
|
| 283 |
+
t = str(t)
|
| 284 |
+
if " " in t:
|
| 285 |
+
t = f'"{t}"'
|
| 286 |
+
return t
|