chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,729 @@
|
||||
"""Base class for MultiGraph."""
|
||||
from copy import deepcopy
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
|
||||
import easygraph as eg
|
||||
import easygraph.convert as convert
|
||||
|
||||
from easygraph.classes.graph import Graph
|
||||
from easygraph.utils.exception import EasyGraphError
|
||||
|
||||
|
||||
__all__ = ["MultiGraph"]
|
||||
|
||||
|
||||
class MultiGraph(Graph):
|
||||
edge_key_dict_factory = dict
|
||||
|
||||
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
|
||||
EasyGraph graph object. If the corresponding optional Python
|
||||
packages are installed the data can also be a NumPy matrix
|
||||
or 2d ndarray, a SciPy sparse matrix, 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 EasyGraphError is raised if this is not the case.
|
||||
If False, :func:`to_easygraph_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 = eg.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
|
||||
>>> G = eg.Graph(name="my graph")
|
||||
>>> e = [(1, 2), (2, 3), (3, 4)] # list of edges
|
||||
>>> G = eg.Graph(e)
|
||||
|
||||
Arbitrary graph attribute pairs (key=value) may be assigned
|
||||
|
||||
>>> G = eg.Graph(e, day="Friday")
|
||||
>>> G.graph
|
||||
{'day': 'Friday'}
|
||||
|
||||
"""
|
||||
self.edge_key_dict_factory = self.edge_key_dict_factory
|
||||
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 eg.EasyGraphError(
|
||||
f"converting multigraph_input raised:\n{type(err)}: {err}"
|
||||
)
|
||||
Graph.__init__(self, incoming_graph_data, **attr)
|
||||
else:
|
||||
Graph.__init__(self, incoming_graph_data, **attr)
|
||||
|
||||
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.
|
||||
|
||||
EasyGraph 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 all add the edge e=(1, 2) to graph G:
|
||||
|
||||
>>> G = eg.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
|
||||
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.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = eg.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")
|
||||
"""
|
||||
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 EasyGraphError(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)
|
||||
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 (arbitrary) edge between u and v.
|
||||
|
||||
Raises
|
||||
------
|
||||
EasyGraphError
|
||||
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
|
||||
--------
|
||||
For multiple edges
|
||||
|
||||
>>> G = eg.MultiGraph() # or MultiDiGraph, etc
|
||||
>>> G.add_edges_from([(1, 2), (1, 2), (1, 2)]) # key_list returned
|
||||
[0, 1, 2]
|
||||
>>> G.remove_edge(1, 2) # remove a single (arbitrary) edge
|
||||
|
||||
For edges with keys
|
||||
|
||||
>>> G = eg.MultiGraph() # or MultiDiGraph, etc
|
||||
>>> G.add_edge(1, 2, key="first")
|
||||
'first'
|
||||
>>> G.add_edge(1, 2, key="second")
|
||||
'second'
|
||||
>>> G.remove_edge(1, 2, key="second")
|
||||
|
||||
"""
|
||||
try:
|
||||
d = self._adj[u][v]
|
||||
except KeyError as err:
|
||||
raise EasyGraphError(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 EasyGraphError(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]
|
||||
|
||||
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) All edges between u and v are 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
|
||||
--------
|
||||
Removing multiple copies of edges
|
||||
|
||||
>>> G = eg.MultiGraph()
|
||||
>>> keys = G.add_edges_from([(1, 2), (1, 2), (1, 2)])
|
||||
>>> G.remove_edges_from([(1, 2), (1, 2)])
|
||||
>>> list(G.edges())
|
||||
[(1, 2)]
|
||||
>>> G.remove_edges_from([(1, 2), (1, 2)]) # silently ignore extra copy
|
||||
>>> list(G.edges) # now empty graph
|
||||
[]
|
||||
"""
|
||||
for e in ebunch:
|
||||
try:
|
||||
self.remove_edge(*e[:3])
|
||||
except EasyGraphError:
|
||||
pass
|
||||
|
||||
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 = eg.MultiGraph() # or MultiDiGraph
|
||||
>>> G = eg.complete_graph(4, create_using=eg.MultiDiGraph)
|
||||
>>> 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
|
||||
>>> 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
|
||||
|
||||
"""
|
||||
try:
|
||||
if key is None:
|
||||
return v in self._adj[u]
|
||||
else:
|
||||
return key in self._adj[u][v]
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
@property
|
||||
def edges(self):
|
||||
edges = list()
|
||||
seen = {}
|
||||
for n, nbrs in self._adj.items():
|
||||
for nbr, kd in nbrs.items():
|
||||
if nbr not in seen:
|
||||
for k, dd in kd.items():
|
||||
edges.append((n, nbr, k, dd))
|
||||
seen[n] = 1
|
||||
del seen
|
||||
return edges
|
||||
|
||||
def get_edge_data(self, u, v, key=None, default=None):
|
||||
"""Returns the attribute dictionary associated with edge (u, 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 edge (u, v) is not found.
|
||||
|
||||
key : hashable identifier, optional (default=None)
|
||||
Return data only for the edge with specified key.
|
||||
|
||||
Returns
|
||||
-------
|
||||
edge_dict : dictionary
|
||||
The edge attribute dictionary.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = eg.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 = eg.MultiGraph() # or MultiDiGraph
|
||||
>>> G = eg.complete_graph(4, create_using=eg.MultiDiGraph)
|
||||
>>> G.get_edge_data(0, 1)
|
||||
{0: {}}
|
||||
>>> e = (0, 1)
|
||||
>>> G.get_edge_data(*e) # tuple form
|
||||
{0: {}}
|
||||
>>> G.get_edge_data("a", "b", default=0) # edge not in graph, return 0
|
||||
0
|
||||
"""
|
||||
try:
|
||||
if key is None:
|
||||
return self._adj[u][v]
|
||||
else:
|
||||
return self._adj[u][v][key]
|
||||
except KeyError:
|
||||
return default
|
||||
|
||||
@property
|
||||
def degree(self, weight="weight"):
|
||||
degree = dict()
|
||||
if weight is None:
|
||||
for n in self._nodes:
|
||||
nbrs = self._succ[n]
|
||||
deg = sum(len(keys) for keys in nbrs.values()) + (
|
||||
n in nbrs and len(nbrs[n])
|
||||
)
|
||||
degree[n] = deg
|
||||
else:
|
||||
for n in self._nodes:
|
||||
nbrs = self._succ[n]
|
||||
deg = sum(
|
||||
d.get(weight, 1)
|
||||
for key_dict in nbrs.values()
|
||||
for d in key_dict.values()
|
||||
)
|
||||
if n in nbrs:
|
||||
deg += sum(d.get(weight, 1) for d in nbrs[n].values())
|
||||
degree[n] = deg
|
||||
|
||||
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):
|
||||
"""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.
|
||||
|
||||
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.
|
||||
EasyGraph 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 = eg.path_graph(5)
|
||||
>>> H = G.copy()
|
||||
>>> H = eg.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)
|
||||
|
||||
See the Python copy module for more information on shallow
|
||||
and deep copies, https://docs.python.org/3/library/copy.html.
|
||||
|
||||
Returns
|
||||
-------
|
||||
G : Graph
|
||||
A copy of the graph.
|
||||
|
||||
See Also
|
||||
--------
|
||||
to_directed: return a directed copy of the graph.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = eg.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
|
||||
>>> H = G.copy()
|
||||
|
||||
"""
|
||||
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):
|
||||
"""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, data) replaced by two directed edges
|
||||
(u, v, data) and (v, u, 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=DiGraph(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 = eg.Graph() # or MultiGraph, etc
|
||||
>>> G.add_edge(0, 1)
|
||||
>>> H = G.to_directed()
|
||||
>>> list(H.edges)
|
||||
[(0, 1), (1, 0)]
|
||||
|
||||
If already directed, return a (deep) copy
|
||||
|
||||
>>> G = eg.DiGraph() # or MultiDiGraph, etc
|
||||
>>> G.add_edge(0, 1)
|
||||
>>> H = G.to_directed()
|
||||
>>> list(H.edges)
|
||||
[(0, 1)]
|
||||
"""
|
||||
G = eg.MultiDiGraph()
|
||||
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 (Gefault=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 = eg.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 = eg.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)
|
||||
Reference in New Issue
Block a user