diff env/lib/python3.7/site-packages/networkx/classes/multigraph.py @ 0:26e78fe6e8c4 draft

"planemo upload commit c699937486c35866861690329de38ec1a5d9f783"
author shellac
date Sat, 02 May 2020 07:14:21 -0400
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/env/lib/python3.7/site-packages/networkx/classes/multigraph.py	Sat May 02 07:14:21 2020 -0400
@@ -0,0 +1,1139 @@
+#    Copyright (C) 2004-2019 by
+#    Aric Hagberg <hagberg@lanl.gov>
+#    Dan Schult <dschult@colgate.edu>
+#    Pieter Swart <swart@lanl.gov>
+#    All rights reserved.
+#    BSD license.
+#
+# Authors:   Aric Hagberg <hagberg@lanl.gov>
+#            Dan Schult <dschult@colgate.edu>
+#            Pieter Swart <swart@lanl.gov>
+"""Base class for MultiGraph."""
+from copy import deepcopy
+
+import networkx as nx
+from networkx.classes.graph import Graph
+from networkx.classes.coreviews import MultiAdjacencyView
+from networkx.classes.reportviews import MultiEdgeView, MultiDegreeView
+from networkx import NetworkXError
+from networkx.utils import iterable
+
+
+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.
+
+    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, NumPy matrix
+        or 2d ndarray, SciPy sparse matrix, or PyGraphviz graph.
+
+    attr : keyword arguments, optional (default= no attributes)
+        Attributes to add to graph as key=value pairs.
+
+    See Also
+    --------
+    Graph
+    DiGraph
+    MultiDiGraph
+    OrderedMultiGraph
+
+    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]` a
+    read-only dict-like structure. However, you can assign to attributes
+    in e.g. `G.edges[1, 2]`. Thus, use 2 sets of brackets to add/change
+    data attributes: `G.edges[1, 2]['weight'] = 4`
+    (For multigraphs: `MG.edges[u, v, key][name] = value`).
+
+    **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 keyed by neighbor to 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]`, `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.
+
+    Examples
+    --------
+
+    Please see :mod:`~networkx.classes.ordered` for examples of
+    creating graph subclasses by overwriting the base class `dict` with
+    a dictionary-like object.
+    """
+    # 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
+
+    def __init__(self, incoming_graph_data=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 NumPy matrix
+            or 2d ndarray, a SciPy sparse matrix, or a PyGraphviz graph.
+
+        attr : keyword arguments, optional (default= no attributes)
+            Attributes to add to graph as key=value pairs.
+
+        See Also
+        --------
+        convert
+
+        Examples
+        --------
+        >>> G = nx.Graph()   # or DiGraph, MultiGraph, MultiDiGraph, etc
+        >>> G = nx.Graph(name='my graph')
+        >>> e = [(1, 2), (2, 3), (3, 4)] # list of edges
+        >>> G = nx.Graph(e)
+
+        Arbitrary graph attribute pairs (key=value) may be assigned
+
+        >>> G = nx.Graph(e, day="Friday")
+        >>> G.graph
+        {'day': 'Friday'}
+
+        """
+        self.edge_key_dict_factory = self.edge_key_dict_factory
+        Graph.__init__(self, incoming_graph_data, **attr)
+
+    @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, nbrdict in G.adj[n].items():`.
+
+        The neighbor information is also provided by subscripting the graph.
+        So `for nbr, foovalue in G[node].data('foo', default=1):` works.
+
+        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 all add the 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:
+            self._adj[u] = self.adjlist_inner_dict_factory()
+            self._node[u] = self.node_attr_dict_factory()
+        if v not in self._adj:
+            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 = 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')
+        """
+        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 = "Edge tuple {} must be a 2-tuple, 3-tuple or 4-tuple."
+                raise NetworkXError(msg.format(e))
+            ddd = {}
+            ddd.update(attr)
+            try:
+                ddd.update(dd)
+            except:
+                if ne != 3:
+                    raise
+                key = dd
+            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
+        ------
+        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]
+        >>> G.remove_edge(1, 2) # remove a single (arbitrary) edge
+
+        For edges with keys
+
+        >>> G = nx.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:
+            raise NetworkXError(
+                "The edge %s-%s is not in the graph." % (u, v))
+        # remove the edge with specified data
+        if key is None:
+            d.popitem()
+        else:
+            try:
+                del d[key]
+            except KeyError:
+                msg = "The edge %s-%s with key %s is not in the graph."
+                raise NetworkXError(msg % (u, v, key))
+        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
+        --------
+        >>> 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), (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 NetworkXError:
+                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 = 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
+        >>> 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):
+        """Returns an iterator over the edges.
+
+        edges(self, nbunch=None, data=False, keys=False, default=None)
+
+        The EdgeView 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]['color']` provides the value of the color
+        attribute for edge `(u, v)` while
+        `for (u, v, c) in G.edges(data='color', default='red'):`
+        iterates through all the edges yielding the color attribute.
+
+        Edges are returned as tuples with optional data and keys
+        in the order (node, neighbor, key, data).
+
+        Parameters
+        ----------
+        nbunch : single node, container, or all nodes (default= all nodes)
+            The view will only report edges incident to 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.
+        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()   # or MultiDiGraph
+        >>> nx.add_path(G, [0, 1, 2])
+        >>> key = G.add_edge(2, 3, weight=5)
+        >>> [e for e in G.edges()]
+        [(0, 1), (1, 2), (2, 3)]
+        >>> G.edges.data() # default data is {} (empty dict)
+        MultiEdgeDataView([(0, 1, {}), (1, 2, {}), (2, 3, {'weight': 5})])
+        >>> G.edges.data('weight', default=1)
+        MultiEdgeDataView([(0, 1, 1), (1, 2, 1), (2, 3, 5)])
+        >>> G.edges(keys=True) # default keys are integers
+        MultiEdgeView([(0, 1, 0), (1, 2, 0), (2, 3, 0)])
+        >>> G.edges.data(keys=True)
+        MultiEdgeDataView([(0, 1, 0, {}), (1, 2, 0, {}), (2, 3, 0, {'weight': 5})])
+        >>> G.edges.data('weight', default=1, keys=True)
+        MultiEdgeDataView([(0, 1, 0, 1), (1, 2, 0, 1), (2, 3, 0, 5)])
+        >>> G.edges([0, 3])
+        MultiEdgeDataView([(0, 1), (3, 2)])
+        >>> 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).
+
+        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 = 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.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):
+        """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
+        -------
+        If a single node is requested
+        deg : int
+            Degree of the node, if a single node is passed as argument.
+
+        OR if multiple nodes are requested
+        nd_iter : iterator
+            The iterator returns two-tuples of (node, degree).
+
+        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/2/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, 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/2/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.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 = nx.DiGraph()   # or MultiDiGraph, etc
+        >>> G.add_edge(0, 1)
+        >>> H = G.to_directed()
+        >>> list(H.edges)
+        [(0, 1)]
+        """
+        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/2/library/copy.html.
+
+        Warning: If you have subclassed MultiiGraph to use dict-like
+        objects in the data structure, those changes do not transfer
+        to the MultiGraph created by this method.
+
+        Examples
+        --------
+        >>> G = nx.path_graph(2)   # or MultiGraph, etc
+        >>> H = G.to_directed()
+        >>> list(H.edges)
+        [(0, 1), (1, 0)]
+        >>> G2 = H.to_undirected()
+        >>> list(G2.edges)
+        [(0, 1)]
+        """
+        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 (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 = 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)