| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294 |
- """Base class for MultiGraph."""
- from copy import deepcopy
- from functools import cached_property
- import networkx as nx
- from networkx import NetworkXError, convert
- from networkx.classes.coreviews import MultiAdjacencyView
- from networkx.classes.graph import Graph
- from networkx.classes.reportviews import MultiDegreeView, MultiEdgeView
- __all__ = ["MultiGraph"]
- class MultiGraph(Graph):
- """
- An undirected graph class that can store multiedges.
- Multiedges are multiple edges between two nodes. Each edge
- can hold optional data or attributes.
- A MultiGraph holds undirected edges. Self loops are allowed.
- Nodes can be arbitrary (hashable) Python objects with optional
- key/value attributes. By convention `None` is not used as a node.
- Edges are represented as links between nodes with optional
- key/value attributes, in a MultiGraph each edge has a key to
- distinguish between multiple edges that have the same source and
- destination nodes.
- Parameters
- ----------
- incoming_graph_data : input graph (optional, default: None)
- Data to initialize graph. If None (default) an empty
- graph is created. The data can be any format that is supported
- by the to_networkx_graph() function, currently including edge list,
- dict of dicts, dict of lists, NetworkX graph, 2D NumPy array,
- SciPy sparse array, or PyGraphviz graph.
- multigraph_input : bool or None (default None)
- Note: Only used when `incoming_graph_data` is a dict.
- If True, `incoming_graph_data` is assumed to be a
- dict-of-dict-of-dict-of-dict structure keyed by
- node to neighbor to edge keys to edge data for multi-edges.
- A NetworkXError is raised if this is not the case.
- If False, :func:`to_networkx_graph` is used to try to determine
- the dict's graph data structure as either a dict-of-dict-of-dict
- keyed by node to neighbor to edge data, or a dict-of-iterable
- keyed by node to neighbors.
- If None, the treatment for True is tried, but if it fails,
- the treatment for False is tried.
- attr : keyword arguments, optional (default= no attributes)
- Attributes to add to graph as key=value pairs.
- See Also
- --------
- Graph
- DiGraph
- MultiDiGraph
- Examples
- --------
- Create an empty graph structure (a "null graph") with no nodes and
- no edges.
- >>> G = nx.MultiGraph()
- G can be grown in several ways.
- **Nodes:**
- Add one node at a time:
- >>> G.add_node(1)
- Add the nodes from any container (a list, dict, set or
- even the lines from a file or the nodes from another graph).
- >>> G.add_nodes_from([2, 3])
- >>> G.add_nodes_from(range(100, 110))
- >>> H = nx.path_graph(10)
- >>> G.add_nodes_from(H)
- In addition to strings and integers any hashable Python object
- (except None) can represent a node, e.g. a customized node object,
- or even another Graph.
- >>> G.add_node(H)
- **Edges:**
- G can also be grown by adding edges.
- Add one edge,
- >>> key = G.add_edge(1, 2)
- a list of edges,
- >>> keys = G.add_edges_from([(1, 2), (1, 3)])
- or a collection of edges,
- >>> keys = G.add_edges_from(H.edges)
- If some edges connect nodes not yet in the graph, the nodes
- are added automatically. If an edge already exists, an additional
- edge is created and stored using a key to identify the edge.
- By default the key is the lowest unused integer.
- >>> keys = G.add_edges_from([(4, 5, {"route": 28}), (4, 5, {"route": 37})])
- >>> G[4]
- AdjacencyView({3: {0: {}}, 5: {0: {}, 1: {'route': 28}, 2: {'route': 37}}})
- **Attributes:**
- Each graph, node, and edge can hold key/value attribute pairs
- in an associated attribute dictionary (the keys must be hashable).
- By default these are empty, but can be added or changed using
- add_edge, add_node or direct manipulation of the attribute
- dictionaries named graph, node and edge respectively.
- >>> G = nx.MultiGraph(day="Friday")
- >>> G.graph
- {'day': 'Friday'}
- Add node attributes using add_node(), add_nodes_from() or G.nodes
- >>> G.add_node(1, time="5pm")
- >>> G.add_nodes_from([3], time="2pm")
- >>> G.nodes[1]
- {'time': '5pm'}
- >>> G.nodes[1]["room"] = 714
- >>> del G.nodes[1]["room"] # remove attribute
- >>> list(G.nodes(data=True))
- [(1, {'time': '5pm'}), (3, {'time': '2pm'})]
- Add edge attributes using add_edge(), add_edges_from(), subscript
- notation, or G.edges.
- >>> key = G.add_edge(1, 2, weight=4.7)
- >>> keys = G.add_edges_from([(3, 4), (4, 5)], color="red")
- >>> keys = G.add_edges_from([(1, 2, {"color": "blue"}), (2, 3, {"weight": 8})])
- >>> G[1][2][0]["weight"] = 4.7
- >>> G.edges[1, 2, 0]["weight"] = 4
- Warning: we protect the graph data structure by making `G.edges[1,
- 2, 0]` a read-only dict-like structure. However, you can assign to
- attributes in e.g. `G.edges[1, 2, 0]`. Thus, use 2 sets of brackets
- to add/change data attributes: `G.edges[1, 2, 0]['weight'] = 4`.
- **Shortcuts:**
- Many common graph features allow python syntax to speed reporting.
- >>> 1 in G # check if node in graph
- True
- >>> [n for n in G if n < 3] # iterate through nodes
- [1, 2]
- >>> len(G) # number of nodes in graph
- 5
- >>> G[1] # adjacency dict-like view mapping neighbor -> edge key -> edge attributes
- AdjacencyView({2: {0: {'weight': 4}, 1: {'color': 'blue'}}})
- Often the best way to traverse all edges of a graph is via the neighbors.
- The neighbors are reported as an adjacency-dict `G.adj` or `G.adjacency()`.
- >>> for n, nbrsdict in G.adjacency():
- ... for nbr, keydict in nbrsdict.items():
- ... for key, eattr in keydict.items():
- ... if "weight" in eattr:
- ... # Do something useful with the edges
- ... pass
- But the edges() method is often more convenient:
- >>> for u, v, keys, weight in G.edges(data="weight", keys=True):
- ... if weight is not None:
- ... # Do something useful with the edges
- ... pass
- **Reporting:**
- Simple graph information is obtained using methods and object-attributes.
- Reporting usually provides views instead of containers to reduce memory
- usage. The views update as the graph is updated similarly to dict-views.
- The objects `nodes`, `edges` and `adj` provide access to data attributes
- via lookup (e.g. `nodes[n]`, `edges[u, v, k]`, `adj[u][v]`) and iteration
- (e.g. `nodes.items()`, `nodes.data('color')`,
- `nodes.data('color', default='blue')` and similarly for `edges`)
- Views exist for `nodes`, `edges`, `neighbors()`/`adj` and `degree`.
- For details on these and other miscellaneous methods, see below.
- **Subclasses (Advanced):**
- The MultiGraph class uses a dict-of-dict-of-dict-of-dict data structure.
- The outer dict (node_dict) holds adjacency information keyed by node.
- The next dict (adjlist_dict) represents the adjacency information
- and holds edge_key dicts keyed by neighbor. The edge_key dict holds
- each edge_attr dict keyed by edge key. The inner dict
- (edge_attr_dict) represents the edge data and holds edge attribute
- values keyed by attribute names.
- Each of these four dicts in the dict-of-dict-of-dict-of-dict
- structure can be replaced by a user defined dict-like object.
- In general, the dict-like features should be maintained but
- extra features can be added. To replace one of the dicts create
- a new graph class by changing the class(!) variable holding the
- factory for that dict-like structure. The variable names are
- node_dict_factory, node_attr_dict_factory, adjlist_inner_dict_factory,
- adjlist_outer_dict_factory, edge_key_dict_factory, edge_attr_dict_factory
- and graph_attr_dict_factory.
- node_dict_factory : function, (default: dict)
- Factory function to be used to create the dict containing node
- attributes, keyed by node id.
- It should require no arguments and return a dict-like object
- node_attr_dict_factory: function, (default: dict)
- Factory function to be used to create the node attribute
- dict which holds attribute values keyed by attribute name.
- It should require no arguments and return a dict-like object
- adjlist_outer_dict_factory : function, (default: dict)
- Factory function to be used to create the outer-most dict
- in the data structure that holds adjacency info keyed by node.
- It should require no arguments and return a dict-like object.
- adjlist_inner_dict_factory : function, (default: dict)
- Factory function to be used to create the adjacency list
- dict which holds multiedge key dicts keyed by neighbor.
- It should require no arguments and return a dict-like object.
- edge_key_dict_factory : function, (default: dict)
- Factory function to be used to create the edge key dict
- which holds edge data keyed by edge key.
- It should require no arguments and return a dict-like object.
- edge_attr_dict_factory : function, (default: dict)
- Factory function to be used to create the edge attribute
- dict which holds attribute values keyed by attribute name.
- It should require no arguments and return a dict-like object.
- graph_attr_dict_factory : function, (default: dict)
- Factory function to be used to create the graph attribute
- dict which holds attribute values keyed by attribute name.
- It should require no arguments and return a dict-like object.
- Typically, if your extension doesn't impact the data structure all
- methods will inherited without issue except: `to_directed/to_undirected`.
- By default these methods create a DiGraph/Graph class and you probably
- want them to create your extension of a DiGraph/Graph. To facilitate
- this we define two class variables that you can set in your subclass.
- to_directed_class : callable, (default: DiGraph or MultiDiGraph)
- Class to create a new graph structure in the `to_directed` method.
- If `None`, a NetworkX class (DiGraph or MultiDiGraph) is used.
- to_undirected_class : callable, (default: Graph or MultiGraph)
- Class to create a new graph structure in the `to_undirected` method.
- If `None`, a NetworkX class (Graph or MultiGraph) is used.
- **Subclassing Example**
- Create a low memory graph class that effectively disallows edge
- attributes by using a single attribute dict for all edges.
- This reduces the memory used, but you lose edge attributes.
- >>> class ThinGraph(nx.Graph):
- ... all_edge_dict = {"weight": 1}
- ...
- ... def single_edge_dict(self):
- ... return self.all_edge_dict
- ...
- ... edge_attr_dict_factory = single_edge_dict
- >>> G = ThinGraph()
- >>> G.add_edge(2, 1)
- >>> G[2][1]
- {'weight': 1}
- >>> G.add_edge(2, 2)
- >>> G[2][1] is G[2][2]
- True
- """
- # node_dict_factory = dict # already assigned in Graph
- # adjlist_outer_dict_factory = dict
- # adjlist_inner_dict_factory = dict
- edge_key_dict_factory = dict
- # edge_attr_dict_factory = dict
- def to_directed_class(self):
- """Returns the class to use for empty directed copies.
- If you subclass the base classes, use this to designate
- what directed class to use for `to_directed()` copies.
- """
- return nx.MultiDiGraph
- def to_undirected_class(self):
- """Returns the class to use for empty undirected copies.
- If you subclass the base classes, use this to designate
- what directed class to use for `to_directed()` copies.
- """
- return MultiGraph
- # This __new__ method just does what Python itself does automatically.
- # We include it here as part of the dispatchable/backend interface.
- # If your goal is to understand how the graph classes work, you can ignore
- # this method, even when subclassing the base classes. If you are subclassing
- # in order to provide a backend that allows class instantiation, this method
- # can be overridden to return your own backend graph class.
- @nx._dispatchable(name="multigraph__new__", graphs=None, returns_graph=True)
- def __new__(cls, *args, **kwargs):
- return object.__new__(cls)
- def __init__(self, incoming_graph_data=None, multigraph_input=None, **attr):
- """Initialize a graph with edges, name, or graph attributes.
- Parameters
- ----------
- incoming_graph_data : input graph
- Data to initialize graph. If incoming_graph_data=None (default)
- an empty graph is created. The data can be an edge list, or any
- NetworkX graph object. If the corresponding optional Python
- packages are installed the data can also be a 2D NumPy array, a
- SciPy sparse array, or a PyGraphviz graph.
- multigraph_input : bool or None (default None)
- Note: Only used when `incoming_graph_data` is a dict.
- If True, `incoming_graph_data` is assumed to be a
- dict-of-dict-of-dict-of-dict structure keyed by
- node to neighbor to edge keys to edge data for multi-edges.
- A NetworkXError is raised if this is not the case.
- If False, :func:`to_networkx_graph` is used to try to determine
- the dict's graph data structure as either a dict-of-dict-of-dict
- keyed by node to neighbor to edge data, or a dict-of-iterable
- keyed by node to neighbors.
- If None, the treatment for True is tried, but if it fails,
- the treatment for False is tried.
- attr : keyword arguments, optional (default= no attributes)
- Attributes to add to graph as key=value pairs.
- See Also
- --------
- convert
- Examples
- --------
- >>> G = nx.MultiGraph()
- >>> G = nx.MultiGraph(name="my graph")
- >>> e = [(1, 2), (1, 2), (2, 3), (3, 4)] # list of edges
- >>> G = nx.MultiGraph(e)
- Arbitrary graph attribute pairs (key=value) may be assigned
- >>> G = nx.MultiGraph(e, day="Friday")
- >>> G.graph
- {'day': 'Friday'}
- """
- attr.pop("backend", None) # Ignore explicit `backend="networkx"`
- # multigraph_input can be None/True/False. So check "is not False"
- if isinstance(incoming_graph_data, dict) and multigraph_input is not False:
- Graph.__init__(self)
- try:
- convert.from_dict_of_dicts(
- incoming_graph_data, create_using=self, multigraph_input=True
- )
- self.graph.update(attr)
- except Exception as err:
- if multigraph_input is True:
- raise nx.NetworkXError(
- f"converting multigraph_input raised:\n{type(err)}: {err}"
- )
- Graph.__init__(self, incoming_graph_data, **attr)
- else:
- Graph.__init__(self, incoming_graph_data, **attr)
- @cached_property
- def adj(self):
- """Graph adjacency object holding the neighbors of each node.
- This object is a read-only dict-like structure with node keys
- and neighbor-dict values. The neighbor-dict is keyed by neighbor
- to the edgekey-data-dict. So `G.adj[3][2][0]['color'] = 'blue'` sets
- the color of the edge `(3, 2, 0)` to `"blue"`.
- Iterating over G.adj behaves like a dict. Useful idioms include
- `for nbr, edgesdict in G.adj[n].items():`.
- The neighbor information is also provided by subscripting the graph.
- Examples
- --------
- >>> e = [(1, 2), (1, 2), (1, 3), (3, 4)] # list of edges
- >>> G = nx.MultiGraph(e)
- >>> G.edges[1, 2, 0]["weight"] = 3
- >>> result = set()
- >>> for edgekey, data in G[1][2].items():
- ... result.add(data.get("weight", 1))
- >>> result
- {1, 3}
- For directed graphs, `G.adj` holds outgoing (successor) info.
- """
- return MultiAdjacencyView(self._adj)
- def new_edge_key(self, u, v):
- """Returns an unused key for edges between nodes `u` and `v`.
- The nodes `u` and `v` do not need to be already in the graph.
- Notes
- -----
- In the standard MultiGraph class the new key is the number of existing
- edges between `u` and `v` (increased if necessary to ensure unused).
- The first edge will have key 0, then 1, etc. If an edge is removed
- further new_edge_keys may not be in this order.
- Parameters
- ----------
- u, v : nodes
- Returns
- -------
- key : int
- """
- try:
- keydict = self._adj[u][v]
- except KeyError:
- return 0
- key = len(keydict)
- while key in keydict:
- key += 1
- return key
- def add_edge(self, u_for_edge, v_for_edge, key=None, **attr):
- """Add an edge between u and v.
- The nodes u and v will be automatically added if they are
- not already in the graph.
- Edge attributes can be specified with keywords or by directly
- accessing the edge's attribute dictionary. See examples below.
- Parameters
- ----------
- u_for_edge, v_for_edge : nodes
- Nodes can be, for example, strings or numbers.
- Nodes must be hashable (and not None) Python objects.
- key : hashable identifier, optional (default=lowest unused integer)
- Used to distinguish multiedges between a pair of nodes.
- attr : keyword arguments, optional
- Edge data (or labels or objects) can be assigned using
- keyword arguments.
- Returns
- -------
- The edge key assigned to the edge.
- See Also
- --------
- add_edges_from : add a collection of edges
- Notes
- -----
- To replace/update edge data, use the optional key argument
- to identify a unique edge. Otherwise a new edge will be created.
- NetworkX algorithms designed for weighted graphs cannot use
- multigraphs directly because it is not clear how to handle
- multiedge weights. Convert to Graph using edge attribute
- 'weight' to enable weighted graph algorithms.
- Default keys are generated using the method `new_edge_key()`.
- This method can be overridden by subclassing the base class and
- providing a custom `new_edge_key()` method.
- Examples
- --------
- The following each add an additional edge e=(1, 2) to graph G:
- >>> G = nx.MultiGraph()
- >>> e = (1, 2)
- >>> ekey = G.add_edge(1, 2) # explicit two-node form
- >>> G.add_edge(*e) # single edge as tuple of two nodes
- 1
- >>> G.add_edges_from([(1, 2)]) # add edges from iterable container
- [2]
- Associate data to edges using keywords:
- >>> ekey = G.add_edge(1, 2, weight=3)
- >>> ekey = G.add_edge(1, 2, key=0, weight=4) # update data for key=0
- >>> ekey = G.add_edge(1, 3, weight=7, capacity=15, length=342.7)
- For non-string attribute keys, use subscript notation.
- >>> ekey = G.add_edge(1, 2)
- >>> G[1][2][0].update({0: 5})
- >>> G.edges[1, 2, 0].update({0: 5})
- """
- u, v = u_for_edge, v_for_edge
- # add nodes
- if u not in self._adj:
- if u is None:
- raise ValueError("None cannot be a node")
- self._adj[u] = self.adjlist_inner_dict_factory()
- self._node[u] = self.node_attr_dict_factory()
- if v not in self._adj:
- if v is None:
- raise ValueError("None cannot be a node")
- self._adj[v] = self.adjlist_inner_dict_factory()
- self._node[v] = self.node_attr_dict_factory()
- if key is None:
- key = self.new_edge_key(u, v)
- if v in self._adj[u]:
- keydict = self._adj[u][v]
- datadict = keydict.get(key, self.edge_attr_dict_factory())
- datadict.update(attr)
- keydict[key] = datadict
- else:
- # selfloops work this way without special treatment
- datadict = self.edge_attr_dict_factory()
- datadict.update(attr)
- keydict = self.edge_key_dict_factory()
- keydict[key] = datadict
- self._adj[u][v] = keydict
- self._adj[v][u] = keydict
- nx._clear_cache(self)
- return key
- def add_edges_from(self, ebunch_to_add, **attr):
- """Add all the edges in ebunch_to_add.
- Parameters
- ----------
- ebunch_to_add : container of edges
- Each edge given in the container will be added to the
- graph. The edges can be:
- - 2-tuples (u, v) or
- - 3-tuples (u, v, d) for an edge data dict d, or
- - 3-tuples (u, v, k) for not iterable key k, or
- - 4-tuples (u, v, k, d) for an edge with data and key k
- attr : keyword arguments, optional
- Edge data (or labels or objects) can be assigned using
- keyword arguments.
- Returns
- -------
- A list of edge keys assigned to the edges in `ebunch`.
- See Also
- --------
- add_edge : add a single edge
- add_weighted_edges_from : convenient way to add weighted edges
- Notes
- -----
- Adding the same edge twice has no effect but any edge data
- will be updated when each duplicate edge is added.
- Edge attributes specified in an ebunch take precedence over
- attributes specified via keyword arguments.
- Default keys are generated using the method ``new_edge_key()``.
- This method can be overridden by subclassing the base class and
- providing a custom ``new_edge_key()`` method.
- When adding edges from an iterator over the graph you are changing,
- a `RuntimeError` can be raised with message:
- `RuntimeError: dictionary changed size during iteration`. This
- happens when the graph's underlying dictionary is modified during
- iteration. To avoid this error, evaluate the iterator into a separate
- object, e.g. by using `list(iterator_of_edges)`, and pass this
- object to `G.add_edges_from`.
- Examples
- --------
- >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
- >>> G.add_edges_from([(0, 1), (1, 2)]) # using a list of edge tuples
- >>> e = zip(range(0, 3), range(1, 4))
- >>> G.add_edges_from(e) # Add the path graph 0-1-2-3
- Associate data to edges
- >>> G.add_edges_from([(1, 2), (2, 3)], weight=3)
- >>> G.add_edges_from([(3, 4), (1, 4)], label="WN2898")
- Evaluate an iterator over a graph if using it to modify the same graph
- >>> G = nx.MultiGraph([(1, 2), (2, 3), (3, 4)])
- >>> # Grow graph by one new node, adding edges to all existing nodes.
- >>> # wrong way - will raise RuntimeError
- >>> # G.add_edges_from(((5, n) for n in G.nodes))
- >>> # right way - note that there will be no self-edge for node 5
- >>> assigned_keys = G.add_edges_from(list((5, n) for n in G.nodes))
- """
- keylist = []
- for e in ebunch_to_add:
- ne = len(e)
- if ne == 4:
- u, v, key, dd = e
- elif ne == 3:
- u, v, dd = e
- key = None
- elif ne == 2:
- u, v = e
- dd = {}
- key = None
- else:
- msg = f"Edge tuple {e} must be a 2-tuple, 3-tuple or 4-tuple."
- raise NetworkXError(msg)
- ddd = {}
- ddd.update(attr)
- try:
- ddd.update(dd)
- except (TypeError, ValueError):
- if ne != 3:
- raise
- key = dd # ne == 3 with 3rd value not dict, must be a key
- key = self.add_edge(u, v, key)
- self[u][v][key].update(ddd)
- keylist.append(key)
- nx._clear_cache(self)
- return keylist
- def remove_edge(self, u, v, key=None):
- """Remove an edge between u and v.
- Parameters
- ----------
- u, v : nodes
- Remove an edge between nodes u and v.
- key : hashable identifier, optional (default=None)
- Used to distinguish multiple edges between a pair of nodes.
- If None, remove a single edge between u and v. If there are
- multiple edges, removes the last edge added in terms of
- insertion order.
- Raises
- ------
- NetworkXError
- If there is not an edge between u and v, or
- if there is no edge with the specified key.
- See Also
- --------
- remove_edges_from : remove a collection of edges
- Examples
- --------
- >>> G = nx.MultiGraph()
- >>> nx.add_path(G, [0, 1, 2, 3])
- >>> G.remove_edge(0, 1)
- >>> e = (1, 2)
- >>> G.remove_edge(*e) # unpacks e from an edge tuple
- For multiple edges
- >>> G = nx.MultiGraph() # or MultiDiGraph, etc
- >>> G.add_edges_from([(1, 2), (1, 2), (1, 2)]) # key_list returned
- [0, 1, 2]
- When ``key=None`` (the default), edges are removed in the opposite
- order that they were added:
- >>> G.remove_edge(1, 2)
- >>> G.edges(keys=True)
- MultiEdgeView([(1, 2, 0), (1, 2, 1)])
- >>> G.remove_edge(2, 1) # edges are not directed
- >>> G.edges(keys=True)
- MultiEdgeView([(1, 2, 0)])
- For edges with keys
- >>> G = nx.MultiGraph()
- >>> G.add_edge(1, 2, key="first")
- 'first'
- >>> G.add_edge(1, 2, key="second")
- 'second'
- >>> G.remove_edge(1, 2, key="first")
- >>> G.edges(keys=True)
- MultiEdgeView([(1, 2, 'second')])
- """
- try:
- d = self._adj[u][v]
- except KeyError as err:
- raise NetworkXError(f"The edge {u}-{v} is not in the graph.") from err
- # remove the edge with specified data
- if key is None:
- d.popitem()
- else:
- try:
- del d[key]
- except KeyError as err:
- msg = f"The edge {u}-{v} with key {key} is not in the graph."
- raise NetworkXError(msg) from err
- if len(d) == 0:
- # remove the key entries if last edge
- del self._adj[u][v]
- if u != v: # check for selfloop
- del self._adj[v][u]
- nx._clear_cache(self)
- def remove_edges_from(self, ebunch):
- """Remove all edges specified in ebunch.
- Parameters
- ----------
- ebunch: list or container of edge tuples
- Each edge given in the list or container will be removed
- from the graph. The edges can be:
- - 2-tuples (u, v) A single edge between u and v is removed.
- - 3-tuples (u, v, key) The edge identified by key is removed.
- - 4-tuples (u, v, key, data) where data is ignored.
- See Also
- --------
- remove_edge : remove a single edge
- Notes
- -----
- Will fail silently if an edge in ebunch is not in the graph.
- Examples
- --------
- >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
- >>> ebunch = [(1, 2), (2, 3)]
- >>> G.remove_edges_from(ebunch)
- Removing multiple copies of edges
- >>> G = nx.MultiGraph()
- >>> keys = G.add_edges_from([(1, 2), (1, 2), (1, 2)])
- >>> G.remove_edges_from([(1, 2), (2, 1)]) # edges aren't directed
- >>> list(G.edges())
- [(1, 2)]
- >>> G.remove_edges_from([(1, 2), (1, 2)]) # silently ignore extra copy
- >>> list(G.edges) # now empty graph
- []
- When the edge is a 2-tuple ``(u, v)`` but there are multiple edges between
- u and v in the graph, the most recent edge (in terms of insertion
- order) is removed.
- >>> G = nx.MultiGraph()
- >>> for key in ("x", "y", "a"):
- ... k = G.add_edge(0, 1, key=key)
- >>> G.edges(keys=True)
- MultiEdgeView([(0, 1, 'x'), (0, 1, 'y'), (0, 1, 'a')])
- >>> G.remove_edges_from([(0, 1)])
- >>> G.edges(keys=True)
- MultiEdgeView([(0, 1, 'x'), (0, 1, 'y')])
- """
- for e in ebunch:
- try:
- self.remove_edge(*e[:3])
- except NetworkXError:
- pass
- nx._clear_cache(self)
- def has_edge(self, u, v, key=None):
- """Returns True if the graph has an edge between nodes u and v.
- This is the same as `v in G[u] or key in G[u][v]`
- without KeyError exceptions.
- Parameters
- ----------
- u, v : nodes
- Nodes can be, for example, strings or numbers.
- key : hashable identifier, optional (default=None)
- If specified return True only if the edge with
- key is found.
- Returns
- -------
- edge_ind : bool
- True if edge is in the graph, False otherwise.
- Examples
- --------
- Can be called either using two nodes u, v, an edge tuple (u, v),
- or an edge tuple (u, v, key).
- >>> G = nx.MultiGraph() # or MultiDiGraph
- >>> nx.add_path(G, [0, 1, 2, 3])
- >>> G.has_edge(0, 1) # using two nodes
- True
- >>> e = (0, 1)
- >>> G.has_edge(*e) # e is a 2-tuple (u, v)
- True
- >>> G.add_edge(0, 1, key="a")
- 'a'
- >>> G.has_edge(0, 1, key="a") # specify key
- True
- >>> G.has_edge(1, 0, key="a") # edges aren't directed
- True
- >>> e = (0, 1, "a")
- >>> G.has_edge(*e) # e is a 3-tuple (u, v, 'a')
- True
- The following syntax are equivalent:
- >>> G.has_edge(0, 1)
- True
- >>> 1 in G[0] # though this gives :exc:`KeyError` if 0 not in G
- True
- >>> 0 in G[1] # other order; also gives :exc:`KeyError` if 0 not in G
- True
- """
- try:
- if key is None:
- return v in self._adj[u]
- else:
- return key in self._adj[u][v]
- except KeyError:
- return False
- @cached_property
- def edges(self):
- """Returns an iterator over the edges.
- edges(self, nbunch=None, data=False, keys=False, default=None)
- The MultiEdgeView provides set-like operations on the edge-tuples
- as well as edge attribute lookup. When called, it also provides
- an EdgeDataView object which allows control of access to edge
- attributes (but does not provide set-like operations).
- Hence, ``G.edges[u, v, k]['color']`` provides the value of the color
- attribute for the edge from ``u`` to ``v`` with key ``k`` while
- ``for (u, v, k, c) in G.edges(data='color', keys=True, default="red"):``
- iterates through all the edges yielding the color attribute with
- default `'red'` if no color attribute exists.
- Edges are returned as tuples with optional data and keys
- in the order (node, neighbor, key, data). If ``keys=True`` is not
- provided, the tuples will just be (node, neighbor, data), but
- multiple tuples with the same node and neighbor will be generated
- when multiple edges exist between two nodes.
- Parameters
- ----------
- nbunch : single node, container, or all nodes (default= all nodes)
- The view will only report edges from these nodes.
- data : string or bool, optional (default=False)
- The edge attribute returned in 3-tuple (u, v, ddict[data]).
- If True, return edge attribute dict in 3-tuple (u, v, ddict).
- If False, return 2-tuple (u, v).
- keys : bool, optional (default=False)
- If True, return edge keys with each edge, creating (u, v, k)
- tuples or (u, v, k, d) tuples if data is also requested.
- default : value, optional (default=None)
- Value used for edges that don't have the requested attribute.
- Only relevant if data is not True or False.
- Returns
- -------
- edges : MultiEdgeView
- A view of edge attributes, usually it iterates over (u, v)
- (u, v, k) or (u, v, k, d) tuples of edges, but can also be
- used for attribute lookup as ``edges[u, v, k]['foo']``.
- Notes
- -----
- Nodes in nbunch that are not in the graph will be (quietly) ignored.
- For directed graphs this returns the out-edges.
- Examples
- --------
- >>> G = nx.MultiGraph()
- >>> nx.add_path(G, [0, 1, 2])
- >>> key = G.add_edge(2, 3, weight=5)
- >>> key2 = G.add_edge(2, 1, weight=2) # multi-edge
- >>> [e for e in G.edges()]
- [(0, 1), (1, 2), (1, 2), (2, 3)]
- >>> G.edges.data() # default data is {} (empty dict)
- MultiEdgeDataView([(0, 1, {}), (1, 2, {}), (1, 2, {'weight': 2}), (2, 3, {'weight': 5})])
- >>> G.edges.data("weight", default=1)
- MultiEdgeDataView([(0, 1, 1), (1, 2, 1), (1, 2, 2), (2, 3, 5)])
- >>> G.edges(keys=True) # default keys are integers
- MultiEdgeView([(0, 1, 0), (1, 2, 0), (1, 2, 1), (2, 3, 0)])
- >>> G.edges.data(keys=True)
- MultiEdgeDataView([(0, 1, 0, {}), (1, 2, 0, {}), (1, 2, 1, {'weight': 2}), (2, 3, 0, {'weight': 5})])
- >>> G.edges.data("weight", default=1, keys=True)
- MultiEdgeDataView([(0, 1, 0, 1), (1, 2, 0, 1), (1, 2, 1, 2), (2, 3, 0, 5)])
- >>> G.edges([0, 3]) # Note ordering of tuples from listed sources
- MultiEdgeDataView([(0, 1), (3, 2)])
- >>> G.edges([0, 3, 2, 1]) # Note ordering of tuples
- MultiEdgeDataView([(0, 1), (3, 2), (2, 1), (2, 1)])
- >>> G.edges(0)
- MultiEdgeDataView([(0, 1)])
- """
- return MultiEdgeView(self)
- def get_edge_data(self, u, v, key=None, default=None):
- """Returns the attribute dictionary associated with edge (u, v,
- key).
- If a key is not provided, returns a dictionary mapping edge keys
- to attribute dictionaries for each edge between u and v.
- This is identical to `G[u][v][key]` except the default is returned
- instead of an exception is the edge doesn't exist.
- Parameters
- ----------
- u, v : nodes
- default : any Python object (default=None)
- Value to return if the specific edge (u, v, key) is not
- found, OR if there are no edges between u and v and no key
- is specified.
- key : hashable identifier, optional (default=None)
- Return data only for the edge with specified key, as an
- attribute dictionary (rather than a dictionary mapping keys
- to attribute dictionaries).
- Returns
- -------
- edge_dict : dictionary
- The edge attribute dictionary, OR a dictionary mapping edge
- keys to attribute dictionaries for each of those edges if no
- specific key is provided (even if there's only one edge
- between u and v).
- Examples
- --------
- >>> G = nx.MultiGraph() # or MultiDiGraph
- >>> key = G.add_edge(0, 1, key="a", weight=7)
- >>> G[0][1]["a"] # key='a'
- {'weight': 7}
- >>> G.edges[0, 1, "a"] # key='a'
- {'weight': 7}
- Warning: we protect the graph data structure by making
- `G.edges` and `G[1][2]` read-only dict-like structures.
- However, you can assign values to attributes in e.g.
- `G.edges[1, 2, 'a']` or `G[1][2]['a']` using an additional
- bracket as shown next. You need to specify all edge info
- to assign to the edge data associated with an edge.
- >>> G[0][1]["a"]["weight"] = 10
- >>> G.edges[0, 1, "a"]["weight"] = 10
- >>> G[0][1]["a"]["weight"]
- 10
- >>> G.edges[1, 0, "a"]["weight"]
- 10
- >>> G = nx.MultiGraph() # or MultiDiGraph
- >>> nx.add_path(G, [0, 1, 2, 3])
- >>> G.edges[0, 1, 0]["weight"] = 5
- >>> G.get_edge_data(0, 1)
- {0: {'weight': 5}}
- >>> e = (0, 1)
- >>> G.get_edge_data(*e) # tuple form
- {0: {'weight': 5}}
- >>> G.get_edge_data(3, 0) # edge not in graph, returns None
- >>> G.get_edge_data(3, 0, default=0) # edge not in graph, return default
- 0
- >>> G.get_edge_data(1, 0, 0) # specific key gives back
- {'weight': 5}
- """
- try:
- if key is None:
- return self._adj[u][v]
- else:
- return self._adj[u][v][key]
- except KeyError:
- return default
- @cached_property
- def degree(self):
- """A DegreeView for the Graph as G.degree or G.degree().
- The node degree is the number of edges adjacent to the node.
- The weighted node degree is the sum of the edge weights for
- edges incident to that node.
- This object provides an iterator for (node, degree) as well as
- lookup for the degree for a single node.
- Parameters
- ----------
- nbunch : single node, container, or all nodes (default= all nodes)
- The view will only report edges incident to these nodes.
- weight : string or None, optional (default=None)
- The name of an edge attribute that holds the numerical value used
- as a weight. If None, then each edge has weight 1.
- The degree is the sum of the edge weights adjacent to the node.
- Returns
- -------
- MultiDegreeView or int
- If multiple nodes are requested (the default), returns a `MultiDegreeView`
- mapping nodes to their degree.
- If a single node is requested, returns the degree of the node as an integer.
- Examples
- --------
- >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
- >>> nx.add_path(G, [0, 1, 2, 3])
- >>> G.degree(0) # node 0 with degree 1
- 1
- >>> list(G.degree([0, 1]))
- [(0, 1), (1, 2)]
- """
- return MultiDegreeView(self)
- def is_multigraph(self):
- """Returns True if graph is a multigraph, False otherwise."""
- return True
- def is_directed(self):
- """Returns True if graph is directed, False otherwise."""
- return False
- def copy(self, as_view=False):
- """Returns a copy of the graph.
- The copy method by default returns an independent shallow copy
- of the graph and attributes. That is, if an attribute is a
- container, that container is shared by the original an the copy.
- Use Python's `copy.deepcopy` for new containers.
- If `as_view` is True then a view is returned instead of a copy.
- Notes
- -----
- All copies reproduce the graph structure, but data attributes
- may be handled in different ways. There are four types of copies
- of a graph that people might want.
- Deepcopy -- A "deepcopy" copies the graph structure as well as
- all data attributes and any objects they might contain.
- The entire graph object is new so that changes in the copy
- do not affect the original object. (see Python's copy.deepcopy)
- Data Reference (Shallow) -- For a shallow copy the graph structure
- is copied but the edge, node and graph attribute dicts are
- references to those in the original graph. This saves
- time and memory but could cause confusion if you change an attribute
- in one graph and it changes the attribute in the other.
- NetworkX does not provide this level of shallow copy.
- Independent Shallow -- This copy creates new independent attribute
- dicts and then does a shallow copy of the attributes. That is, any
- attributes that are containers are shared between the new graph
- and the original. This is exactly what `dict.copy()` provides.
- You can obtain this style copy using:
- >>> G = nx.path_graph(5)
- >>> H = G.copy()
- >>> H = G.copy(as_view=False)
- >>> H = nx.Graph(G)
- >>> H = G.__class__(G)
- Fresh Data -- For fresh data, the graph structure is copied while
- new empty data attribute dicts are created. The resulting graph
- is independent of the original and it has no edge, node or graph
- attributes. Fresh copies are not enabled. Instead use:
- >>> H = G.__class__()
- >>> H.add_nodes_from(G)
- >>> H.add_edges_from(G.edges)
- View -- Inspired by dict-views, graph-views act like read-only
- versions of the original graph, providing a copy of the original
- structure without requiring any memory for copying the information.
- See the Python copy module for more information on shallow
- and deep copies, https://docs.python.org/3/library/copy.html.
- Parameters
- ----------
- as_view : bool, optional (default=False)
- If True, the returned graph-view provides a read-only view
- of the original graph without actually copying any data.
- Returns
- -------
- G : Graph
- A copy of the graph.
- See Also
- --------
- to_directed: return a directed copy of the graph.
- Examples
- --------
- >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
- >>> H = G.copy()
- """
- if as_view is True:
- return nx.graphviews.generic_graph_view(self)
- G = self.__class__()
- G.graph.update(self.graph)
- G.add_nodes_from((n, d.copy()) for n, d in self._node.items())
- G.add_edges_from(
- (u, v, key, datadict.copy())
- for u, nbrs in self._adj.items()
- for v, keydict in nbrs.items()
- for key, datadict in keydict.items()
- )
- return G
- def to_directed(self, as_view=False):
- """Returns a directed representation of the graph.
- Returns
- -------
- G : MultiDiGraph
- A directed graph with the same name, same nodes, and with
- each edge (u, v, k, data) replaced by two directed edges
- (u, v, k, data) and (v, u, k, data).
- Notes
- -----
- This returns a "deepcopy" of the edge, node, and
- graph attributes which attempts to completely copy
- all of the data and references.
- This is in contrast to the similar D=MultiDiGraph(G) which
- returns a shallow copy of the data.
- See the Python copy module for more information on shallow
- and deep copies, https://docs.python.org/3/library/copy.html.
- Warning: If you have subclassed MultiGraph to use dict-like objects
- in the data structure, those changes do not transfer to the
- MultiDiGraph created by this method.
- Examples
- --------
- >>> G = nx.MultiGraph()
- >>> G.add_edge(0, 1)
- 0
- >>> G.add_edge(0, 1)
- 1
- >>> H = G.to_directed()
- >>> list(H.edges)
- [(0, 1, 0), (0, 1, 1), (1, 0, 0), (1, 0, 1)]
- If already directed, return a (deep) copy
- >>> G = nx.MultiDiGraph()
- >>> G.add_edge(0, 1)
- 0
- >>> H = G.to_directed()
- >>> list(H.edges)
- [(0, 1, 0)]
- """
- graph_class = self.to_directed_class()
- if as_view is True:
- return nx.graphviews.generic_graph_view(self, graph_class)
- # deepcopy when not a view
- G = graph_class()
- G.graph.update(deepcopy(self.graph))
- G.add_nodes_from((n, deepcopy(d)) for n, d in self._node.items())
- G.add_edges_from(
- (u, v, key, deepcopy(datadict))
- for u, nbrs in self.adj.items()
- for v, keydict in nbrs.items()
- for key, datadict in keydict.items()
- )
- return G
- def to_undirected(self, as_view=False):
- """Returns an undirected copy of the graph.
- Returns
- -------
- G : Graph/MultiGraph
- A deepcopy of the graph.
- See Also
- --------
- copy, add_edge, add_edges_from
- Notes
- -----
- This returns a "deepcopy" of the edge, node, and
- graph attributes which attempts to completely copy
- all of the data and references.
- This is in contrast to the similar `G = nx.MultiGraph(D)`
- which returns a shallow copy of the data.
- See the Python copy module for more information on shallow
- and deep copies, https://docs.python.org/3/library/copy.html.
- Warning: If you have subclassed MultiGraph to use dict-like
- objects in the data structure, those changes do not transfer
- to the MultiGraph created by this method.
- Examples
- --------
- >>> G = nx.MultiGraph([(0, 1), (0, 1), (1, 2)])
- >>> H = G.to_directed()
- >>> list(H.edges)
- [(0, 1, 0), (0, 1, 1), (1, 0, 0), (1, 0, 1), (1, 2, 0), (2, 1, 0)]
- >>> G2 = H.to_undirected()
- >>> list(G2.edges)
- [(0, 1, 0), (0, 1, 1), (1, 2, 0)]
- """
- graph_class = self.to_undirected_class()
- if as_view is True:
- return nx.graphviews.generic_graph_view(self, graph_class)
- # deepcopy when not a view
- G = graph_class()
- G.graph.update(deepcopy(self.graph))
- G.add_nodes_from((n, deepcopy(d)) for n, d in self._node.items())
- G.add_edges_from(
- (u, v, key, deepcopy(datadict))
- for u, nbrs in self._adj.items()
- for v, keydict in nbrs.items()
- for key, datadict in keydict.items()
- )
- return G
- def number_of_edges(self, u=None, v=None):
- """Returns the number of edges between two nodes.
- Parameters
- ----------
- u, v : nodes, optional (Default=all edges)
- If u and v are specified, return the number of edges between
- u and v. Otherwise return the total number of all edges.
- Returns
- -------
- nedges : int
- The number of edges in the graph. If nodes `u` and `v` are
- specified return the number of edges between those nodes. If
- the graph is directed, this only returns the number of edges
- from `u` to `v`.
- See Also
- --------
- size
- Examples
- --------
- For undirected multigraphs, this method counts the total number
- of edges in the graph::
- >>> G = nx.MultiGraph()
- >>> G.add_edges_from([(0, 1), (0, 1), (1, 2)])
- [0, 1, 0]
- >>> G.number_of_edges()
- 3
- If you specify two nodes, this counts the total number of edges
- joining the two nodes::
- >>> G.number_of_edges(0, 1)
- 2
- For directed multigraphs, this method can count the total number
- of directed edges from `u` to `v`::
- >>> G = nx.MultiDiGraph()
- >>> G.add_edges_from([(0, 1), (0, 1), (1, 0)])
- [0, 1, 0]
- >>> G.number_of_edges(0, 1)
- 2
- >>> G.number_of_edges(1, 0)
- 1
- """
- if u is None:
- return self.size()
- try:
- edgedata = self._adj[u][v]
- except KeyError:
- return 0 # no such edge
- return len(edgedata)
|