koichi12 commited on
Commit
9354bbb
·
verified ·
1 Parent(s): b4a4a78

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +3 -0
  2. .venv/lib/python3.11/site-packages/networkx/classes/__init__.py +13 -0
  3. .venv/lib/python3.11/site-packages/networkx/classes/__pycache__/__init__.cpython-311.pyc +0 -0
  4. .venv/lib/python3.11/site-packages/networkx/classes/__pycache__/coreviews.cpython-311.pyc +0 -0
  5. .venv/lib/python3.11/site-packages/networkx/classes/__pycache__/digraph.cpython-311.pyc +0 -0
  6. .venv/lib/python3.11/site-packages/networkx/classes/__pycache__/filters.cpython-311.pyc +0 -0
  7. .venv/lib/python3.11/site-packages/networkx/classes/__pycache__/function.cpython-311.pyc +0 -0
  8. .venv/lib/python3.11/site-packages/networkx/classes/__pycache__/graph.cpython-311.pyc +0 -0
  9. .venv/lib/python3.11/site-packages/networkx/classes/__pycache__/graphviews.cpython-311.pyc +0 -0
  10. .venv/lib/python3.11/site-packages/networkx/classes/__pycache__/multidigraph.cpython-311.pyc +0 -0
  11. .venv/lib/python3.11/site-packages/networkx/classes/__pycache__/multigraph.cpython-311.pyc +0 -0
  12. .venv/lib/python3.11/site-packages/networkx/classes/__pycache__/reportviews.cpython-311.pyc +0 -0
  13. .venv/lib/python3.11/site-packages/networkx/classes/coreviews.py +431 -0
  14. .venv/lib/python3.11/site-packages/networkx/classes/digraph.py +1352 -0
  15. .venv/lib/python3.11/site-packages/networkx/classes/filters.py +95 -0
  16. .venv/lib/python3.11/site-packages/networkx/classes/function.py +1407 -0
  17. .venv/lib/python3.11/site-packages/networkx/classes/graph.py +2058 -0
  18. .venv/lib/python3.11/site-packages/networkx/classes/graphviews.py +269 -0
  19. .venv/lib/python3.11/site-packages/networkx/classes/multidigraph.py +966 -0
  20. .venv/lib/python3.11/site-packages/networkx/classes/multigraph.py +1283 -0
  21. .venv/lib/python3.11/site-packages/networkx/classes/reportviews.py +1447 -0
  22. .venv/lib/python3.11/site-packages/networkx/classes/tests/dispatch_interface.py +185 -0
  23. .venv/lib/python3.11/site-packages/networkx/classes/tests/historical_tests.py +475 -0
  24. .venv/lib/python3.11/site-packages/networkx/classes/tests/test_coreviews.py +362 -0
  25. .venv/lib/python3.11/site-packages/networkx/classes/tests/test_digraph.py +331 -0
  26. .venv/lib/python3.11/site-packages/networkx/classes/tests/test_digraph_historical.py +111 -0
  27. .venv/lib/python3.11/site-packages/networkx/classes/tests/test_filters.py +177 -0
  28. .venv/lib/python3.11/site-packages/networkx/classes/tests/test_function.py +1035 -0
  29. .venv/lib/python3.11/site-packages/networkx/classes/tests/test_graph.py +920 -0
  30. .venv/lib/python3.11/site-packages/networkx/classes/tests/test_graph_historical.py +13 -0
  31. .venv/lib/python3.11/site-packages/networkx/classes/tests/test_graphviews.py +350 -0
  32. .venv/lib/python3.11/site-packages/networkx/classes/tests/test_multidigraph.py +459 -0
  33. .venv/lib/python3.11/site-packages/networkx/classes/tests/test_multigraph.py +528 -0
  34. .venv/lib/python3.11/site-packages/networkx/classes/tests/test_reportviews.py +1435 -0
  35. .venv/lib/python3.11/site-packages/networkx/classes/tests/test_special.py +131 -0
  36. .venv/lib/python3.11/site-packages/networkx/classes/tests/test_subgraphviews.py +362 -0
  37. .venv/lib/python3.11/site-packages/networkx/readwrite/__init__.py +17 -0
  38. .venv/lib/python3.11/site-packages/networkx/readwrite/__pycache__/__init__.cpython-311.pyc +0 -0
  39. .venv/lib/python3.11/site-packages/networkx/readwrite/__pycache__/gexf.cpython-311.pyc +0 -0
  40. .venv/lib/python3.11/site-packages/networkx/readwrite/__pycache__/gml.cpython-311.pyc +0 -0
  41. .venv/lib/python3.11/site-packages/networkx/readwrite/__pycache__/graph6.cpython-311.pyc +0 -0
  42. .venv/lib/python3.11/site-packages/networkx/readwrite/__pycache__/multiline_adjlist.cpython-311.pyc +0 -0
  43. .venv/lib/python3.11/site-packages/networkx/readwrite/__pycache__/text.cpython-311.pyc +0 -0
  44. .venv/lib/python3.11/site-packages/networkx/readwrite/gexf.py +1066 -0
  45. .venv/lib/python3.11/site-packages/networkx/readwrite/graphml.py +1053 -0
  46. .venv/lib/python3.11/site-packages/networkx/readwrite/json_graph/cytoscape.py +178 -0
  47. .venv/lib/python3.11/site-packages/networkx/readwrite/json_graph/node_link.py +330 -0
  48. .venv/lib/python3.11/site-packages/networkx/readwrite/json_graph/tree.py +137 -0
  49. .venv/lib/python3.11/site-packages/networkx/readwrite/leda.py +108 -0
  50. .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