1"""Base class for MultiGraph."""
2
3from copy import deepcopy
4from functools import cached_property
5
6import networkx as nx
7from networkx import NetworkXError, convert
8from networkx.classes.coreviews import MultiAdjacencyView
9from networkx.classes.graph import Graph
10from networkx.classes.reportviews import MultiDegreeView, MultiEdgeView
11
12__all__ = ["MultiGraph"]
13
14
15class 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 # This __new__ method just does what Python itself does automatically.
311 # We include it here as part of the dispatchable/backend interface.
312 # If your goal is to understand how the graph classes work, you can ignore
313 # this method, even when subclassing the base classes. If you are subclassing
314 # in order to provide a backend that allows class instantiation, this method
315 # can be overridden to return your own backend graph class.
316 @nx._dispatchable(name="multigraph__new__", graphs=None, returns_graph=True)
317 def __new__(cls, incoming_graph_data=None, multigraph_input=None, **attr):
318 return object.__new__(cls)
319
320 def __init__(self, incoming_graph_data=None, multigraph_input=None, **attr):
321 """Initialize a graph with edges, name, or graph attributes.
322
323 Parameters
324 ----------
325 incoming_graph_data : input graph
326 Data to initialize graph. If incoming_graph_data=None (default)
327 an empty graph is created. The data can be an edge list, or any
328 NetworkX graph object. If the corresponding optional Python
329 packages are installed the data can also be a 2D NumPy array, a
330 SciPy sparse array, or a PyGraphviz graph.
331
332 multigraph_input : bool or None (default None)
333 Note: Only used when `incoming_graph_data` is a dict.
334 If True, `incoming_graph_data` is assumed to be a
335 dict-of-dict-of-dict-of-dict structure keyed by
336 node to neighbor to edge keys to edge data for multi-edges.
337 A NetworkXError is raised if this is not the case.
338 If False, :func:`to_networkx_graph` is used to try to determine
339 the dict's graph data structure as either a dict-of-dict-of-dict
340 keyed by node to neighbor to edge data, or a dict-of-iterable
341 keyed by node to neighbors.
342 If None, the treatment for True is tried, but if it fails,
343 the treatment for False is tried.
344
345 attr : keyword arguments, optional (default= no attributes)
346 Attributes to add to graph as key=value pairs.
347
348 See Also
349 --------
350 convert
351
352 Examples
353 --------
354 >>> G = nx.MultiGraph()
355 >>> G = nx.MultiGraph(name="my graph")
356 >>> e = [(1, 2), (1, 2), (2, 3), (3, 4)] # list of edges
357 >>> G = nx.MultiGraph(e)
358
359 Arbitrary graph attribute pairs (key=value) may be assigned
360
361 >>> G = nx.MultiGraph(e, day="Friday")
362 >>> G.graph
363 {'day': 'Friday'}
364
365 """
366 attr.pop("backend", None) # Ignore explicit `backend="networkx"`
367 # multigraph_input can be None/True/False. So check "is not False"
368 if isinstance(incoming_graph_data, dict) and multigraph_input is not False:
369 Graph.__init__(self)
370 try:
371 convert.from_dict_of_dicts(
372 incoming_graph_data, create_using=self, multigraph_input=True
373 )
374 self.graph.update(attr)
375 except Exception as err:
376 if multigraph_input is True:
377 raise nx.NetworkXError(
378 f"converting multigraph_input raised:\n{type(err)}: {err}"
379 )
380 Graph.__init__(self, incoming_graph_data, **attr)
381 else:
382 Graph.__init__(self, incoming_graph_data, **attr)
383
384 @cached_property
385 def adj(self):
386 """Graph adjacency object holding the neighbors 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-data-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, edgesdict in G.adj[n].items():`.
395
396 The neighbor information is also provided by subscripting the graph.
397
398 Examples
399 --------
400 >>> e = [(1, 2), (1, 2), (1, 3), (3, 4)] # list of edges
401 >>> G = nx.MultiGraph(e)
402 >>> G.edges[1, 2, 0]["weight"] = 3
403 >>> result = set()
404 >>> for edgekey, data in G[1][2].items():
405 ... result.add(data.get("weight", 1))
406 >>> result
407 {1, 3}
408
409 For directed graphs, `G.adj` holds outgoing (successor) info.
410 """
411 return MultiAdjacencyView(self._adj)
412
413 def new_edge_key(self, u, v):
414 """Returns an unused key for edges between nodes `u` and `v`.
415
416 The nodes `u` and `v` do not need to be already in the graph.
417
418 Notes
419 -----
420 In the standard MultiGraph class the new key is the number of existing
421 edges between `u` and `v` (increased if necessary to ensure unused).
422 The first edge will have key 0, then 1, etc. If an edge is removed
423 further new_edge_keys may not be in this order.
424
425 Parameters
426 ----------
427 u, v : nodes
428
429 Returns
430 -------
431 key : int
432 """
433 try:
434 keydict = self._adj[u][v]
435 except KeyError:
436 return 0
437 key = len(keydict)
438 while key in keydict:
439 key += 1
440 return key
441
442 def add_edge(self, u_for_edge, v_for_edge, key=None, **attr):
443 """Add an edge between u and v.
444
445 The nodes u and v will be automatically added if they are
446 not already in the graph.
447
448 Edge attributes can be specified with keywords or by directly
449 accessing the edge's attribute dictionary. See examples below.
450
451 Parameters
452 ----------
453 u_for_edge, v_for_edge : nodes
454 Nodes can be, for example, strings or numbers.
455 Nodes must be hashable (and not None) Python objects.
456 key : hashable identifier, optional (default=lowest unused integer)
457 Used to distinguish multiedges between a pair of nodes.
458 attr : keyword arguments, optional
459 Edge data (or labels or objects) can be assigned using
460 keyword arguments.
461
462 Returns
463 -------
464 The edge key assigned to the edge.
465
466 See Also
467 --------
468 add_edges_from : add a collection of edges
469
470 Notes
471 -----
472 To replace/update edge data, use the optional key argument
473 to identify a unique edge. Otherwise a new edge will be created.
474
475 NetworkX algorithms designed for weighted graphs cannot use
476 multigraphs directly because it is not clear how to handle
477 multiedge weights. Convert to Graph using edge attribute
478 'weight' to enable weighted graph algorithms.
479
480 Default keys are generated using the method `new_edge_key()`.
481 This method can be overridden by subclassing the base class and
482 providing a custom `new_edge_key()` method.
483
484 Examples
485 --------
486 The following each add an additional edge e=(1, 2) to graph G:
487
488 >>> G = nx.MultiGraph()
489 >>> e = (1, 2)
490 >>> ekey = G.add_edge(1, 2) # explicit two-node form
491 >>> G.add_edge(*e) # single edge as tuple of two nodes
492 1
493 >>> G.add_edges_from([(1, 2)]) # add edges from iterable container
494 [2]
495
496 Associate data to edges using keywords:
497
498 >>> ekey = G.add_edge(1, 2, weight=3)
499 >>> ekey = G.add_edge(1, 2, key=0, weight=4) # update data for key=0
500 >>> ekey = G.add_edge(1, 3, weight=7, capacity=15, length=342.7)
501
502 For non-string attribute keys, use subscript notation.
503
504 >>> ekey = G.add_edge(1, 2)
505 >>> G[1][2][0].update({0: 5})
506 >>> G.edges[1, 2, 0].update({0: 5})
507 """
508 u, v = u_for_edge, v_for_edge
509 # add nodes
510 if u not in self._adj:
511 if u is None:
512 raise ValueError("None cannot be a node")
513 self._adj[u] = self.adjlist_inner_dict_factory()
514 self._node[u] = self.node_attr_dict_factory()
515 if v not in self._adj:
516 if v is None:
517 raise ValueError("None cannot be a node")
518 self._adj[v] = self.adjlist_inner_dict_factory()
519 self._node[v] = self.node_attr_dict_factory()
520 if key is None:
521 key = self.new_edge_key(u, v)
522 if v in self._adj[u]:
523 keydict = self._adj[u][v]
524 datadict = keydict.get(key, self.edge_attr_dict_factory())
525 datadict.update(attr)
526 keydict[key] = datadict
527 else:
528 # selfloops work this way without special treatment
529 datadict = self.edge_attr_dict_factory()
530 datadict.update(attr)
531 keydict = self.edge_key_dict_factory()
532 keydict[key] = datadict
533 self._adj[u][v] = keydict
534 self._adj[v][u] = keydict
535 nx._clear_cache(self)
536 return key
537
538 def add_edges_from(self, ebunch_to_add, **attr):
539 """Add all the edges in ebunch_to_add.
540
541 Parameters
542 ----------
543 ebunch_to_add : container of edges
544 Each edge given in the container will be added to the
545 graph. The edges can be:
546
547 - 2-tuples (u, v) or
548 - 3-tuples (u, v, d) for an edge data dict d, or
549 - 3-tuples (u, v, k) for not iterable key k, or
550 - 4-tuples (u, v, k, d) for an edge with data and key k
551
552 attr : keyword arguments, optional
553 Edge data (or labels or objects) can be assigned using
554 keyword arguments.
555
556 Returns
557 -------
558 A list of edge keys assigned to the edges in `ebunch`.
559
560 See Also
561 --------
562 add_edge : add a single edge
563 add_weighted_edges_from : convenient way to add weighted edges
564
565 Notes
566 -----
567 Adding the same edge twice has no effect but any edge data
568 will be updated when each duplicate edge is added.
569
570 Edge attributes specified in an ebunch take precedence over
571 attributes specified via keyword arguments.
572
573 Default keys are generated using the method ``new_edge_key()``.
574 This method can be overridden by subclassing the base class and
575 providing a custom ``new_edge_key()`` method.
576
577 When adding edges from an iterator over the graph you are changing,
578 a `RuntimeError` can be raised with message:
579 `RuntimeError: dictionary changed size during iteration`. This
580 happens when the graph's underlying dictionary is modified during
581 iteration. To avoid this error, evaluate the iterator into a separate
582 object, e.g. by using `list(iterator_of_edges)`, and pass this
583 object to `G.add_edges_from`.
584
585 Examples
586 --------
587 >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
588 >>> G.add_edges_from([(0, 1), (1, 2)]) # using a list of edge tuples
589 >>> e = zip(range(0, 3), range(1, 4))
590 >>> G.add_edges_from(e) # Add the path graph 0-1-2-3
591
592 Associate data to edges
593
594 >>> G.add_edges_from([(1, 2), (2, 3)], weight=3)
595 >>> G.add_edges_from([(3, 4), (1, 4)], label="WN2898")
596
597 Evaluate an iterator over a graph if using it to modify the same graph
598
599 >>> G = nx.MultiGraph([(1, 2), (2, 3), (3, 4)])
600 >>> # Grow graph by one new node, adding edges to all existing nodes.
601 >>> # wrong way - will raise RuntimeError
602 >>> # G.add_edges_from(((5, n) for n in G.nodes))
603 >>> # right way - note that there will be no self-edge for node 5
604 >>> assigned_keys = G.add_edges_from(list((5, n) for n in G.nodes))
605 """
606 keylist = []
607 for e in ebunch_to_add:
608 ne = len(e)
609 if ne == 4:
610 u, v, key, dd = e
611 elif ne == 3:
612 u, v, dd = e
613 key = None
614 elif ne == 2:
615 u, v = e
616 dd = {}
617 key = None
618 else:
619 msg = f"Edge tuple {e} must be a 2-tuple, 3-tuple or 4-tuple."
620 raise NetworkXError(msg)
621 ddd = {}
622 ddd.update(attr)
623 try:
624 ddd.update(dd)
625 except (TypeError, ValueError):
626 if ne != 3:
627 raise
628 key = dd # ne == 3 with 3rd value not dict, must be a key
629 key = self.add_edge(u, v, key)
630 self[u][v][key].update(ddd)
631 keylist.append(key)
632 nx._clear_cache(self)
633 return keylist
634
635 def remove_edge(self, u, v, key=None):
636 """Remove an edge between u and v.
637
638 Parameters
639 ----------
640 u, v : nodes
641 Remove an edge between nodes u and v.
642 key : hashable identifier, optional (default=None)
643 Used to distinguish multiple edges between a pair of nodes.
644 If None, remove a single edge between u and v. If there are
645 multiple edges, removes the last edge added in terms of
646 insertion order.
647
648 Raises
649 ------
650 NetworkXError
651 If there is not an edge between u and v, or
652 if there is no edge with the specified key.
653
654 See Also
655 --------
656 remove_edges_from : remove a collection of edges
657
658 Examples
659 --------
660 >>> G = nx.MultiGraph()
661 >>> nx.add_path(G, [0, 1, 2, 3])
662 >>> G.remove_edge(0, 1)
663 >>> e = (1, 2)
664 >>> G.remove_edge(*e) # unpacks e from an edge tuple
665
666 For multiple edges
667
668 >>> G = nx.MultiGraph() # or MultiDiGraph, etc
669 >>> G.add_edges_from([(1, 2), (1, 2), (1, 2)]) # key_list returned
670 [0, 1, 2]
671
672 When ``key=None`` (the default), edges are removed in the opposite
673 order that they were added:
674
675 >>> G.remove_edge(1, 2)
676 >>> G.edges(keys=True)
677 MultiEdgeView([(1, 2, 0), (1, 2, 1)])
678 >>> G.remove_edge(2, 1) # edges are not directed
679 >>> G.edges(keys=True)
680 MultiEdgeView([(1, 2, 0)])
681
682 For edges with keys
683
684 >>> G = nx.MultiGraph()
685 >>> G.add_edge(1, 2, key="first")
686 'first'
687 >>> G.add_edge(1, 2, key="second")
688 'second'
689 >>> G.remove_edge(1, 2, key="first")
690 >>> G.edges(keys=True)
691 MultiEdgeView([(1, 2, 'second')])
692
693 """
694 try:
695 d = self._adj[u][v]
696 except KeyError as err:
697 raise NetworkXError(f"The edge {u}-{v} is not in the graph.") from err
698 # remove the edge with specified data
699 if key is None:
700 d.popitem()
701 else:
702 try:
703 del d[key]
704 except KeyError as err:
705 msg = f"The edge {u}-{v} with key {key} is not in the graph."
706 raise NetworkXError(msg) from err
707 if len(d) == 0:
708 # remove the key entries if last edge
709 del self._adj[u][v]
710 if u != v: # check for selfloop
711 del self._adj[v][u]
712 nx._clear_cache(self)
713
714 def remove_edges_from(self, ebunch):
715 """Remove all edges specified in ebunch.
716
717 Parameters
718 ----------
719 ebunch: list or container of edge tuples
720 Each edge given in the list or container will be removed
721 from the graph. The edges can be:
722
723 - 2-tuples (u, v) A single edge between u and v is removed.
724 - 3-tuples (u, v, key) The edge identified by key is removed.
725 - 4-tuples (u, v, key, data) where data is ignored.
726
727 See Also
728 --------
729 remove_edge : remove a single edge
730
731 Notes
732 -----
733 Will fail silently if an edge in ebunch is not in the graph.
734
735 Examples
736 --------
737 >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
738 >>> ebunch = [(1, 2), (2, 3)]
739 >>> G.remove_edges_from(ebunch)
740
741 Removing multiple copies of edges
742
743 >>> G = nx.MultiGraph()
744 >>> keys = G.add_edges_from([(1, 2), (1, 2), (1, 2)])
745 >>> G.remove_edges_from([(1, 2), (2, 1)]) # edges aren't directed
746 >>> list(G.edges())
747 [(1, 2)]
748 >>> G.remove_edges_from([(1, 2), (1, 2)]) # silently ignore extra copy
749 >>> list(G.edges) # now empty graph
750 []
751
752 When the edge is a 2-tuple ``(u, v)`` but there are multiple edges between
753 u and v in the graph, the most recent edge (in terms of insertion
754 order) is removed.
755
756 >>> G = nx.MultiGraph()
757 >>> for key in ("x", "y", "a"):
758 ... k = G.add_edge(0, 1, key=key)
759 >>> G.edges(keys=True)
760 MultiEdgeView([(0, 1, 'x'), (0, 1, 'y'), (0, 1, 'a')])
761 >>> G.remove_edges_from([(0, 1)])
762 >>> G.edges(keys=True)
763 MultiEdgeView([(0, 1, 'x'), (0, 1, 'y')])
764
765 """
766 for e in ebunch:
767 try:
768 self.remove_edge(*e[:3])
769 except NetworkXError:
770 pass
771 nx._clear_cache(self)
772
773 def has_edge(self, u, v, key=None):
774 """Returns True if the graph has an edge between nodes u and v.
775
776 This is the same as `v in G[u] or key in G[u][v]`
777 without KeyError exceptions.
778
779 Parameters
780 ----------
781 u, v : nodes
782 Nodes can be, for example, strings or numbers.
783
784 key : hashable identifier, optional (default=None)
785 If specified return True only if the edge with
786 key is found.
787
788 Returns
789 -------
790 edge_ind : bool
791 True if edge is in the graph, False otherwise.
792
793 Examples
794 --------
795 Can be called either using two nodes u, v, an edge tuple (u, v),
796 or an edge tuple (u, v, key).
797
798 >>> G = nx.MultiGraph() # or MultiDiGraph
799 >>> nx.add_path(G, [0, 1, 2, 3])
800 >>> G.has_edge(0, 1) # using two nodes
801 True
802 >>> e = (0, 1)
803 >>> G.has_edge(*e) # e is a 2-tuple (u, v)
804 True
805 >>> G.add_edge(0, 1, key="a")
806 'a'
807 >>> G.has_edge(0, 1, key="a") # specify key
808 True
809 >>> G.has_edge(1, 0, key="a") # edges aren't directed
810 True
811 >>> e = (0, 1, "a")
812 >>> G.has_edge(*e) # e is a 3-tuple (u, v, 'a')
813 True
814
815 The following syntax are equivalent:
816
817 >>> G.has_edge(0, 1)
818 True
819 >>> 1 in G[0] # though this gives :exc:`KeyError` if 0 not in G
820 True
821 >>> 0 in G[1] # other order; also gives :exc:`KeyError` if 0 not in G
822 True
823
824 """
825 try:
826 if key is None:
827 return v in self._adj[u]
828 else:
829 return key in self._adj[u][v]
830 except KeyError:
831 return False
832
833 @cached_property
834 def edges(self):
835 """Returns an iterator over the edges.
836
837 edges(self, nbunch=None, data=False, keys=False, default=None)
838
839 The MultiEdgeView provides set-like operations on the edge-tuples
840 as well as edge attribute lookup. When called, it also provides
841 an EdgeDataView object which allows control of access to edge
842 attributes (but does not provide set-like operations).
843 Hence, ``G.edges[u, v, k]['color']`` provides the value of the color
844 attribute for the edge from ``u`` to ``v`` with key ``k`` while
845 ``for (u, v, k, c) in G.edges(data='color', keys=True, default="red"):``
846 iterates through all the edges yielding the color attribute with
847 default `'red'` if no color attribute exists.
848
849 Edges are returned as tuples with optional data and keys
850 in the order (node, neighbor, key, data). If ``keys=True`` is not
851 provided, the tuples will just be (node, neighbor, data), but
852 multiple tuples with the same node and neighbor will be generated
853 when multiple edges exist between two nodes.
854
855 Parameters
856 ----------
857 nbunch : single node, container, or all nodes (default= all nodes)
858 The view will only report edges from these nodes.
859 data : string or bool, optional (default=False)
860 The edge attribute returned in 3-tuple (u, v, ddict[data]).
861 If True, return edge attribute dict in 3-tuple (u, v, ddict).
862 If False, return 2-tuple (u, v).
863 keys : bool, optional (default=False)
864 If True, return edge keys with each edge, creating (u, v, k)
865 tuples or (u, v, k, d) tuples if data is also requested.
866 default : value, optional (default=None)
867 Value used for edges that don't have the requested attribute.
868 Only relevant if data is not True or False.
869
870 Returns
871 -------
872 edges : MultiEdgeView
873 A view of edge attributes, usually it iterates over (u, v)
874 (u, v, k) or (u, v, k, d) tuples of edges, but can also be
875 used for attribute lookup as ``edges[u, v, k]['foo']``.
876
877 Notes
878 -----
879 Nodes in nbunch that are not in the graph will be (quietly) ignored.
880 For directed graphs this returns the out-edges.
881
882 Examples
883 --------
884 >>> G = nx.MultiGraph()
885 >>> nx.add_path(G, [0, 1, 2])
886 >>> key = G.add_edge(2, 3, weight=5)
887 >>> key2 = G.add_edge(2, 1, weight=2) # multi-edge
888 >>> [e for e in G.edges()]
889 [(0, 1), (1, 2), (1, 2), (2, 3)]
890 >>> G.edges.data() # default data is {} (empty dict)
891 MultiEdgeDataView([(0, 1, {}), (1, 2, {}), (1, 2, {'weight': 2}), (2, 3, {'weight': 5})])
892 >>> G.edges.data("weight", default=1)
893 MultiEdgeDataView([(0, 1, 1), (1, 2, 1), (1, 2, 2), (2, 3, 5)])
894 >>> G.edges(keys=True) # default keys are integers
895 MultiEdgeView([(0, 1, 0), (1, 2, 0), (1, 2, 1), (2, 3, 0)])
896 >>> G.edges.data(keys=True)
897 MultiEdgeDataView([(0, 1, 0, {}), (1, 2, 0, {}), (1, 2, 1, {'weight': 2}), (2, 3, 0, {'weight': 5})])
898 >>> G.edges.data("weight", default=1, keys=True)
899 MultiEdgeDataView([(0, 1, 0, 1), (1, 2, 0, 1), (1, 2, 1, 2), (2, 3, 0, 5)])
900 >>> G.edges([0, 3]) # Note ordering of tuples from listed sources
901 MultiEdgeDataView([(0, 1), (3, 2)])
902 >>> G.edges([0, 3, 2, 1]) # Note ordering of tuples
903 MultiEdgeDataView([(0, 1), (3, 2), (2, 1), (2, 1)])
904 >>> G.edges(0)
905 MultiEdgeDataView([(0, 1)])
906 """
907 return MultiEdgeView(self)
908
909 def get_edge_data(self, u, v, key=None, default=None):
910 """Returns the attribute dictionary associated with edge (u, v,
911 key).
912
913 If a key is not provided, returns a dictionary mapping edge keys
914 to attribute dictionaries for each edge between u and v.
915
916 This is identical to `G[u][v][key]` except the default is returned
917 instead of an exception is the edge doesn't exist.
918
919 Parameters
920 ----------
921 u, v : nodes
922
923 default : any Python object (default=None)
924 Value to return if the specific edge (u, v, key) is not
925 found, OR if there are no edges between u and v and no key
926 is specified.
927
928 key : hashable identifier, optional (default=None)
929 Return data only for the edge with specified key, as an
930 attribute dictionary (rather than a dictionary mapping keys
931 to attribute dictionaries).
932
933 Returns
934 -------
935 edge_dict : dictionary
936 The edge attribute dictionary, OR a dictionary mapping edge
937 keys to attribute dictionaries for each of those edges if no
938 specific key is provided (even if there's only one edge
939 between u and v).
940
941 Examples
942 --------
943 >>> G = nx.MultiGraph() # or MultiDiGraph
944 >>> key = G.add_edge(0, 1, key="a", weight=7)
945 >>> G[0][1]["a"] # key='a'
946 {'weight': 7}
947 >>> G.edges[0, 1, "a"] # key='a'
948 {'weight': 7}
949
950 Warning: we protect the graph data structure by making
951 `G.edges` and `G[1][2]` read-only dict-like structures.
952 However, you can assign values to attributes in e.g.
953 `G.edges[1, 2, 'a']` or `G[1][2]['a']` using an additional
954 bracket as shown next. You need to specify all edge info
955 to assign to the edge data associated with an edge.
956
957 >>> G[0][1]["a"]["weight"] = 10
958 >>> G.edges[0, 1, "a"]["weight"] = 10
959 >>> G[0][1]["a"]["weight"]
960 10
961 >>> G.edges[1, 0, "a"]["weight"]
962 10
963
964 >>> G = nx.MultiGraph() # or MultiDiGraph
965 >>> nx.add_path(G, [0, 1, 2, 3])
966 >>> G.edges[0, 1, 0]["weight"] = 5
967 >>> G.get_edge_data(0, 1)
968 {0: {'weight': 5}}
969 >>> e = (0, 1)
970 >>> G.get_edge_data(*e) # tuple form
971 {0: {'weight': 5}}
972 >>> G.get_edge_data(3, 0) # edge not in graph, returns None
973 >>> G.get_edge_data(3, 0, default=0) # edge not in graph, return default
974 0
975 >>> G.get_edge_data(1, 0, 0) # specific key gives back
976 {'weight': 5}
977 """
978 try:
979 if key is None:
980 return self._adj[u][v]
981 else:
982 return self._adj[u][v][key]
983 except KeyError:
984 return default
985
986 @cached_property
987 def degree(self):
988 """A DegreeView for the Graph as G.degree or G.degree().
989
990 The node degree is the number of edges adjacent to the node.
991 The weighted node degree is the sum of the edge weights for
992 edges incident to that node.
993
994 This object provides an iterator for (node, degree) as well as
995 lookup for the degree for a single node.
996
997 Parameters
998 ----------
999 nbunch : single node, container, or all nodes (default= all nodes)
1000 The view will only report edges incident to these nodes.
1001
1002 weight : string or None, optional (default=None)
1003 The name of an edge attribute that holds the numerical value used
1004 as a weight. If None, then each edge has weight 1.
1005 The degree is the sum of the edge weights adjacent to the node.
1006
1007 Returns
1008 -------
1009 MultiDegreeView or int
1010 If multiple nodes are requested (the default), returns a `MultiDegreeView`
1011 mapping nodes to their degree.
1012 If a single node is requested, returns the degree of the node as an integer.
1013
1014 Examples
1015 --------
1016 >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
1017 >>> nx.add_path(G, [0, 1, 2, 3])
1018 >>> G.degree(0) # node 0 with degree 1
1019 1
1020 >>> list(G.degree([0, 1]))
1021 [(0, 1), (1, 2)]
1022
1023 """
1024 return MultiDegreeView(self)
1025
1026 def is_multigraph(self):
1027 """Returns True if graph is a multigraph, False otherwise."""
1028 return True
1029
1030 def is_directed(self):
1031 """Returns True if graph is directed, False otherwise."""
1032 return False
1033
1034 def copy(self, as_view=False):
1035 """Returns a copy of the graph.
1036
1037 The copy method by default returns an independent shallow copy
1038 of the graph and attributes. That is, if an attribute is a
1039 container, that container is shared by the original an the copy.
1040 Use Python's `copy.deepcopy` for new containers.
1041
1042 If `as_view` is True then a view is returned instead of a copy.
1043
1044 Notes
1045 -----
1046 All copies reproduce the graph structure, but data attributes
1047 may be handled in different ways. There are four types of copies
1048 of a graph that people might want.
1049
1050 Deepcopy -- A "deepcopy" copies the graph structure as well as
1051 all data attributes and any objects they might contain.
1052 The entire graph object is new so that changes in the copy
1053 do not affect the original object. (see Python's copy.deepcopy)
1054
1055 Data Reference (Shallow) -- For a shallow copy the graph structure
1056 is copied but the edge, node and graph attribute dicts are
1057 references to those in the original graph. This saves
1058 time and memory but could cause confusion if you change an attribute
1059 in one graph and it changes the attribute in the other.
1060 NetworkX does not provide this level of shallow copy.
1061
1062 Independent Shallow -- This copy creates new independent attribute
1063 dicts and then does a shallow copy of the attributes. That is, any
1064 attributes that are containers are shared between the new graph
1065 and the original. This is exactly what `dict.copy()` provides.
1066 You can obtain this style copy using:
1067
1068 >>> G = nx.path_graph(5)
1069 >>> H = G.copy()
1070 >>> H = G.copy(as_view=False)
1071 >>> H = nx.Graph(G)
1072 >>> H = G.__class__(G)
1073
1074 Fresh Data -- For fresh data, the graph structure is copied while
1075 new empty data attribute dicts are created. The resulting graph
1076 is independent of the original and it has no edge, node or graph
1077 attributes. Fresh copies are not enabled. Instead use:
1078
1079 >>> H = G.__class__()
1080 >>> H.add_nodes_from(G)
1081 >>> H.add_edges_from(G.edges)
1082
1083 View -- Inspired by dict-views, graph-views act like read-only
1084 versions of the original graph, providing a copy of the original
1085 structure without requiring any memory for copying the information.
1086
1087 See the Python copy module for more information on shallow
1088 and deep copies, https://docs.python.org/3/library/copy.html.
1089
1090 Parameters
1091 ----------
1092 as_view : bool, optional (default=False)
1093 If True, the returned graph-view provides a read-only view
1094 of the original graph without actually copying any data.
1095
1096 Returns
1097 -------
1098 G : Graph
1099 A copy of the graph.
1100
1101 See Also
1102 --------
1103 to_directed: return a directed copy of the graph.
1104
1105 Examples
1106 --------
1107 >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1108 >>> H = G.copy()
1109
1110 """
1111 if as_view is True:
1112 return nx.graphviews.generic_graph_view(self)
1113 G = self.__class__()
1114 G.graph.update(self.graph)
1115 G.add_nodes_from((n, d.copy()) for n, d in self._node.items())
1116 G.add_edges_from(
1117 (u, v, key, datadict.copy())
1118 for u, nbrs in self._adj.items()
1119 for v, keydict in nbrs.items()
1120 for key, datadict in keydict.items()
1121 )
1122 return G
1123
1124 def to_directed(self, as_view=False):
1125 """Returns a directed representation of the graph.
1126
1127 Returns
1128 -------
1129 G : MultiDiGraph
1130 A directed graph with the same name, same nodes, and with
1131 each edge (u, v, k, data) replaced by two directed edges
1132 (u, v, k, data) and (v, u, k, data).
1133
1134 Notes
1135 -----
1136 This returns a "deepcopy" of the edge, node, and
1137 graph attributes which attempts to completely copy
1138 all of the data and references.
1139
1140 This is in contrast to the similar D=MultiDiGraph(G) which
1141 returns a shallow copy of the data.
1142
1143 See the Python copy module for more information on shallow
1144 and deep copies, https://docs.python.org/3/library/copy.html.
1145
1146 Warning: If you have subclassed MultiGraph to use dict-like objects
1147 in the data structure, those changes do not transfer to the
1148 MultiDiGraph created by this method.
1149
1150 Examples
1151 --------
1152 >>> G = nx.MultiGraph()
1153 >>> G.add_edge(0, 1)
1154 0
1155 >>> G.add_edge(0, 1)
1156 1
1157 >>> H = G.to_directed()
1158 >>> list(H.edges)
1159 [(0, 1, 0), (0, 1, 1), (1, 0, 0), (1, 0, 1)]
1160
1161 If already directed, return a (deep) copy
1162
1163 >>> G = nx.MultiDiGraph()
1164 >>> G.add_edge(0, 1)
1165 0
1166 >>> H = G.to_directed()
1167 >>> list(H.edges)
1168 [(0, 1, 0)]
1169 """
1170 graph_class = self.to_directed_class()
1171 if as_view is True:
1172 return nx.graphviews.generic_graph_view(self, graph_class)
1173 # deepcopy when not a view
1174 G = graph_class()
1175 G.graph.update(deepcopy(self.graph))
1176 G.add_nodes_from((n, deepcopy(d)) for n, d in self._node.items())
1177 G.add_edges_from(
1178 (u, v, key, deepcopy(datadict))
1179 for u, nbrs in self.adj.items()
1180 for v, keydict in nbrs.items()
1181 for key, datadict in keydict.items()
1182 )
1183 return G
1184
1185 def to_undirected(self, as_view=False):
1186 """Returns an undirected copy of the graph.
1187
1188 Returns
1189 -------
1190 G : Graph/MultiGraph
1191 A deepcopy of the graph.
1192
1193 See Also
1194 --------
1195 copy, add_edge, add_edges_from
1196
1197 Notes
1198 -----
1199 This returns a "deepcopy" of the edge, node, and
1200 graph attributes which attempts to completely copy
1201 all of the data and references.
1202
1203 This is in contrast to the similar `G = nx.MultiGraph(D)`
1204 which returns a shallow copy of the data.
1205
1206 See the Python copy module for more information on shallow
1207 and deep copies, https://docs.python.org/3/library/copy.html.
1208
1209 Warning: If you have subclassed MultiGraph to use dict-like
1210 objects in the data structure, those changes do not transfer
1211 to the MultiGraph created by this method.
1212
1213 Examples
1214 --------
1215 >>> G = nx.MultiGraph([(0, 1), (0, 1), (1, 2)])
1216 >>> H = G.to_directed()
1217 >>> list(H.edges)
1218 [(0, 1, 0), (0, 1, 1), (1, 0, 0), (1, 0, 1), (1, 2, 0), (2, 1, 0)]
1219 >>> G2 = H.to_undirected()
1220 >>> list(G2.edges)
1221 [(0, 1, 0), (0, 1, 1), (1, 2, 0)]
1222 """
1223 graph_class = self.to_undirected_class()
1224 if as_view is True:
1225 return nx.graphviews.generic_graph_view(self, graph_class)
1226 # deepcopy when not a view
1227 G = graph_class()
1228 G.graph.update(deepcopy(self.graph))
1229 G.add_nodes_from((n, deepcopy(d)) for n, d in self._node.items())
1230 G.add_edges_from(
1231 (u, v, key, deepcopy(datadict))
1232 for u, nbrs in self._adj.items()
1233 for v, keydict in nbrs.items()
1234 for key, datadict in keydict.items()
1235 )
1236 return G
1237
1238 def number_of_edges(self, u=None, v=None):
1239 """Returns the number of edges between two nodes.
1240
1241 Parameters
1242 ----------
1243 u, v : nodes, optional (Default=all edges)
1244 If u and v are specified, return the number of edges between
1245 u and v. Otherwise return the total number of all edges.
1246
1247 Returns
1248 -------
1249 nedges : int
1250 The number of edges in the graph. If nodes `u` and `v` are
1251 specified return the number of edges between those nodes. If
1252 the graph is directed, this only returns the number of edges
1253 from `u` to `v`.
1254
1255 See Also
1256 --------
1257 size
1258
1259 Examples
1260 --------
1261 For undirected multigraphs, this method counts the total number
1262 of edges in the graph::
1263
1264 >>> G = nx.MultiGraph()
1265 >>> G.add_edges_from([(0, 1), (0, 1), (1, 2)])
1266 [0, 1, 0]
1267 >>> G.number_of_edges()
1268 3
1269
1270 If you specify two nodes, this counts the total number of edges
1271 joining the two nodes::
1272
1273 >>> G.number_of_edges(0, 1)
1274 2
1275
1276 For directed multigraphs, this method can count the total number
1277 of directed edges from `u` to `v`::
1278
1279 >>> G = nx.MultiDiGraph()
1280 >>> G.add_edges_from([(0, 1), (0, 1), (1, 0)])
1281 [0, 1, 0]
1282 >>> G.number_of_edges(0, 1)
1283 2
1284 >>> G.number_of_edges(1, 0)
1285 1
1286
1287 """
1288 if u is None:
1289 return self.size()
1290 try:
1291 edgedata = self._adj[u][v]
1292 except KeyError:
1293 return 0 # no such edge
1294 return len(edgedata)