1"""Base class for directed graphs."""
2
3from copy import deepcopy
4from functools import cached_property
5
6import networkx as nx
7from networkx import convert
8from networkx.classes.coreviews import AdjacencyView
9from networkx.classes.graph import Graph
10from networkx.classes.reportviews import (
11 DiDegreeView,
12 InDegreeView,
13 InEdgeView,
14 OutDegreeView,
15 OutEdgeView,
16)
17from networkx.exception import NetworkXError
18
19__all__ = ["DiGraph"]
20
21
22class _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
60class _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
88class 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)