Repository: networkx/networkx

Issue:
Add {to, from}_numpy_array functions
Should we add a `to_numpy_array` function to generate an `np.array` from a graph and a `from_numpy_array` function for the reverse? These would be similar to the existing `{to, from}_numpy_matrix` functions.

**Motivation:**
* Times have changed since #895.
* `np.matrix` is as good as deprecated, especially after the introduction of the `@` operator for matrix multiplication of arrays in Python 3.5.
* `np.matrix` doesn't always produce the same results as `np.array` and can lead to confusion.
Add {to, from}_numpy_array functions
Should we add a `to_numpy_array` function to generate an `np.array` from a graph and a `from_numpy_array` function for the reverse? These would be similar to the existing `{to, from}_numpy_matrix` functions.

**Motivation:**
* Times have changed since #895.
* `np.matrix` is as good as deprecated, especially after the introduction of the `@` operator for matrix multiplication of arrays in Python 3.5.
* `np.matrix` doesn't always produce the same results as `np.array` and can lead to confusion.


Discussion:
NetworkX uses matrix and sparse_matrix for consistency and because the SciPy sparse matrix collection has  many good tools associated with it. The sparse matrix mimics the matrix structure though I have read that there are a few differences. Also, to change from a matrix object to an array is fast and easy with ```numpy.asarray(M)```, ```M.A```, ```M.getA()``` and perhaps other methods.

I would be Ok with adding a conversion function ```to_numpy_array``` so long as it is easy to maintain.
The `to_numpy_array` could be implemented using the existing `to_numpy_matrix` function, so that it remains easy to maintain:
```
def to_numpy_array(G, nodelist=None, dtype=None, order=None, multigraph_weight=sum, weight='weight', nonedge=0.0):
    M = to_numpy_matrix(G, nodelist=nodelist, dtype=dtype, order=order, multigraph_weight=multigraph_weight, weight=weight, nonedge=nonedge)
    return M.A
```
`from_numpy_array` would require a bit more work, but could be based on `from_numpy_matrix` implementation.

I'm very willing to do this and create a PR if you're happy to give me the go ahead.
Yes - if you could create PR I'll work to get it in. Thanks
Should this be part of the Networkx 2 milestone?
As @harryscholes mentions, Python 3-5+ have the @-operator, and the Numpy Matrix class is all but deprecated... Networkx 2 is a release for the future, so it should be convenient to use the more modern array-class instead of the Matrix.

@AllanLRH sounds like a good idea to me. I'll get working on it soon
NetworkX uses matrix and sparse_matrix for consistency and because the SciPy sparse matrix collection has  many good tools associated with it. The sparse matrix mimics the matrix structure though I have read that there are a few differences. Also, to change from a matrix object to an array is fast and easy with ```numpy.asarray(M)```, ```M.A```, ```M.getA()``` and perhaps other methods.

I would be Ok with adding a conversion function ```to_numpy_array``` so long as it is easy to maintain.
The `to_numpy_array` could be implemented using the existing `to_numpy_matrix` function, so that it remains easy to maintain:
```
def to_numpy_array(G, nodelist=None, dtype=None, order=None, multigraph_weight=sum, weight='weight', nonedge=0.0):
    M = to_numpy_matrix(G, nodelist=nodelist, dtype=dtype, order=order, multigraph_weight=multigraph_weight, weight=weight, nonedge=nonedge)
    return M.A
```
`from_numpy_array` would require a bit more work, but could be based on `from_numpy_matrix` implementation.

I'm very willing to do this and create a PR if you're happy to give me the go ahead.
Yes - if you could create PR I'll work to get it in. Thanks
Should this be part of the Networkx 2 milestone?
As @harryscholes mentions, Python 3-5+ have the @-operator, and the Numpy Matrix class is all but deprecated... Networkx 2 is a release for the future, so it should be convenient to use the more modern array-class instead of the Matrix.

@AllanLRH sounds like a good idea to me. I'll get working on it soon

Resolving patch:
diff --git a/networkx/convert_matrix.py b/networkx/convert_matrix.py
--- a/networkx/convert_matrix.py
+++ b/networkx/convert_matrix.py
@@ -37,7 +37,8 @@
 __all__ = ['from_numpy_matrix', 'to_numpy_matrix',
            'from_pandas_dataframe', 'to_pandas_dataframe',
            'to_numpy_recarray',
-           'from_scipy_sparse_matrix', 'to_scipy_sparse_matrix']
+           'from_scipy_sparse_matrix', 'to_scipy_sparse_matrix',
+           'from_numpy_array', 'to_numpy_array']
 
 
 def to_pandas_dataframe(G, nodelist=None, dtype=None, order=None,
@@ -330,80 +331,11 @@ def to_numpy_matrix(G, nodelist=None, dtype=None, order=None,
             [ 0.,  0.,  4.]])
     """
     import numpy as np
-    if nodelist is None:
-        nodelist = list(G)
-    nodeset = set(nodelist)
-    if len(nodelist) != len(nodeset):
-        msg = "Ambiguous ordering: `nodelist` contained duplicates."
-        raise nx.NetworkXError(msg)
-
-    nlen = len(nodelist)
-    undirected = not G.is_directed()
-    index = dict(zip(nodelist, range(nlen)))
-
-    # Initially, we start with an array of nans.  Then we populate the matrix
-    # using data from the graph.  Afterwards, any leftover nans will be
-    # converted to the value of `nonedge`.  Note, we use nans initially,
-    # instead of zero, for two reasons:
-    #
-    #   1) It can be important to distinguish a real edge with the value 0
-    #      from a nonedge with the value 0.
-    #
-    #   2) When working with multi(di)graphs, we must combine the values of all
-    #      edges between any two nodes in some manner.  This often takes the
-    #      form of a sum, min, or max.  Using the value 0 for a nonedge would
-    #      have undesirable effects with min and max, but using nanmin and
-    #      nanmax with initially nan values is not problematic at all.
-    #
-    # That said, there are still some drawbacks to this approach. Namely, if
-    # a real edge is nan, then that value is a) not distinguishable from
-    # nonedges and b) is ignored by the default combinator (nansum, nanmin,
-    # nanmax) functions used for multi(di)graphs. If this becomes an issue,
-    # an alternative approach is to use masked arrays.  Initially, every
-    # element is masked and set to some `initial` value. As we populate the
-    # graph, elements are unmasked (automatically) when we combine the initial
-    # value with the values given by real edges.  At the end, we convert all
-    # masked values to `nonedge`. Using masked arrays fully addresses reason 1,
-    # but for reason 2, we would still have the issue with min and max if the
-    # initial values were 0.0.  Note: an initial value of +inf is appropriate
-    # for min, while an initial value of -inf is appropriate for max. When
-    # working with sum, an initial value of zero is appropriate. Ideally then,
-    # we'd want to allow users to specify both a value for nonedges and also
-    # an initial value.  For multi(di)graphs, the choice of the initial value
-    # will, in general, depend on the combinator function---sensible defaults
-    # can be provided.
-
-    if G.is_multigraph():
-        # Handle MultiGraphs and MultiDiGraphs
-        M = np.full((nlen, nlen), np.nan, order=order)
-        # use numpy nan-aware operations
-        operator = {sum: np.nansum, min: np.nanmin, max: np.nanmax}
-        try:
-            op = operator[multigraph_weight]
-        except:
-            raise ValueError('multigraph_weight must be sum, min, or max')
 
-        for u, v, attrs in G.edges(data=True):
-            if (u in nodeset) and (v in nodeset):
-                i, j = index[u], index[v]
-                e_weight = attrs.get(weight, 1)
-                M[i, j] = op([e_weight, M[i, j]])
-                if undirected:
-                    M[j, i] = M[i, j]
-    else:
-        # Graph or DiGraph, this is much faster than above
-        M = np.full((nlen, nlen), np.nan, order=order)
-        for u, nbrdict in G.adjacency():
-            for v, d in nbrdict.items():
-                try:
-                    M[index[u], index[v]] = d.get(weight, 1)
-                except KeyError:
-                    # This occurs when there are fewer desired nodes than
-                    # there are nodes in the graph: len(nodelist) < len(G)
-                    pass
-
-    M[np.isnan(M)] = nonedge
-    M = np.asmatrix(M, dtype=dtype)
+    A = to_numpy_array(G, nodelist=nodelist, dtype=dtype, order=order,
+                       multigraph_weight=multigraph_weight, weight=weight,
+                       nonedge=nonedge)
+    M = np.asmatrix(A, dtype=dtype)
     return M
 
 
@@ -923,6 +855,266 @@ def from_scipy_sparse_matrix(A, parallel_edges=False, create_using=None,
     return G
 
 
+def to_numpy_array(G, nodelist=None, dtype=None, order=None,
+                   multigraph_weight=sum, weight='weight', nonedge=0.0):
+    """Return the graph adjacency matrix as a NumPy array.
+
+    Parameters
+    ----------
+    G : graph
+        The NetworkX graph used to construct the NumPy array.
+
+    nodelist : list, optional
+        The rows and columns are ordered according to the nodes in `nodelist`.
+        If `nodelist` is None, then the ordering is produced by G.nodes().
+
+    dtype : NumPy data type, optional
+        A valid single NumPy data type used to initialize the array.
+        This must be a simple type such as int or numpy.float64 and
+        not a compound data type (see to_numpy_recarray)
+        If None, then the NumPy default is used.
+
+    order : {'C', 'F'}, optional
+        Whether to store multidimensional data in C- or Fortran-contiguous
+        (row- or column-wise) order in memory. If None, then the NumPy default
+        is used.
+
+    multigraph_weight : {sum, min, max}, optional
+        An operator that determines how weights in multigraphs are handled.
+        The default is to sum the weights of the multiple edges.
+
+    weight : string or None optional (default = 'weight')
+        The edge attribute that holds the numerical value used for
+        the edge weight. If an edge does not have that attribute, then the
+        value 1 is used instead.
+
+    nonedge : float (default = 0.0)
+        The array values corresponding to nonedges are typically set to zero.
+        However, this could be undesirable if there are array values
+        corresponding to actual edges that also have the value zero. If so,
+        one might prefer nonedges to have some other value, such as nan.
+
+    Returns
+    -------
+    A : NumPy ndarray
+        Graph adjacency matrix
+
+    See Also
+    --------
+    from_numpy_array
+
+    Notes
+    -----
+    Entries in the adjacency matrix are assigned to the weight edge attribute.
+    When an edge does not have a weight attribute, the value of the entry is
+    set to the number 1.  For multiple (parallel) edges, the values of the
+    entries are determined by the `multigraph_weight` parameter. The default is
+    to sum the weight attributes for each of the parallel edges.
+
+    When `nodelist` does not contain every node in `G`, the adjacency matrix is
+    built from the subgraph of `G` that is induced by the nodes in `nodelist`.
+
+    The convention used for self-loop edges in graphs is to assign the
+    diagonal array entry value to the weight attribute of the edge
+    (or the number 1 if the edge has no weight attribute). If the
+    alternate convention of doubling the edge weight is desired the
+    resulting NumPy array can be modified as follows:
+
+    >>> import numpy as np
+    >>> G = nx.Graph([(1, 1)])
+    >>> A = nx.to_numpy_array(G)
+    >>> A
+    array([[ 1.]])
+    >>> A[np.diag_indices_from(A)] *= 2
+    >>> A
+    array([[ 2.]])
+
+    Examples
+    --------
+    >>> G = nx.MultiDiGraph()
+    >>> G.add_edge(0,1,weight=2)
+    0
+    >>> G.add_edge(1,0)
+    0
+    >>> G.add_edge(2,2,weight=3)
+    0
+    >>> G.add_edge(2,2)
+    1
+    >>> nx.to_numpy_array(G, nodelist=[0,1,2])
+    array([[ 0.,  2.,  0.],
+           [ 1.,  0.,  0.],
+           [ 0.,  0.,  4.]])
+    """
+    import numpy as np
+    if nodelist is None:
+        nodelist = list(G)
+    nodeset = set(nodelist)
+    if len(nodelist) != len(nodeset):
+        msg = "Ambiguous ordering: `nodelist` contained duplicates."
+        raise nx.NetworkXError(msg)
+
+    nlen = len(nodelist)
+    undirected = not G.is_directed()
+    index = dict(zip(nodelist, range(nlen)))
+
+    # Initially, we start with an array of nans.  Then we populate the array
+    # using data from the graph.  Afterwards, any leftover nans will be
+    # converted to the value of `nonedge`.  Note, we use nans initially,
+    # instead of zero, for two reasons:
+    #
+    #   1) It can be important to distinguish a real edge with the value 0
+    #      from a nonedge with the value 0.
+    #
+    #   2) When working with multi(di)graphs, we must combine the values of all
+    #      edges between any two nodes in some manner.  This often takes the
+    #      form of a sum, min, or max.  Using the value 0 for a nonedge would
+    #      have undesirable effects with min and max, but using nanmin and
+    #      nanmax with initially nan values is not problematic at all.
+    #
+    # That said, there are still some drawbacks to this approach. Namely, if
+    # a real edge is nan, then that value is a) not distinguishable from
+    # nonedges and b) is ignored by the default combinator (nansum, nanmin,
+    # nanmax) functions used for multi(di)graphs. If this becomes an issue,
+    # an alternative approach is to use masked arrays.  Initially, every
+    # element is masked and set to some `initial` value. As we populate the
+    # graph, elements are unmasked (automatically) when we combine the initial
+    # value with the values given by real edges.  At the end, we convert all
+    # masked values to `nonedge`. Using masked arrays fully addresses reason 1,
+    # but for reason 2, we would still have the issue with min and max if the
+    # initial values were 0.0.  Note: an initial value of +inf is appropriate
+    # for min, while an initial value of -inf is appropriate for max. When
+    # working with sum, an initial value of zero is appropriate. Ideally then,
+    # we'd want to allow users to specify both a value for nonedges and also
+    # an initial value.  For multi(di)graphs, the choice of the initial value
+    # will, in general, depend on the combinator function---sensible defaults
+    # can be provided.
+
+    if G.is_multigraph():
+        # Handle MultiGraphs and MultiDiGraphs
+        A = np.full((nlen, nlen), np.nan, order=order)
+        # use numpy nan-aware operations
+        operator = {sum: np.nansum, min: np.nanmin, max: np.nanmax}
+        try:
+            op = operator[multigraph_weight]
+        except:
+            raise ValueError('multigraph_weight must be sum, min, or max')
+
+        for u, v, attrs in G.edges(data=True):
+            if (u in nodeset) and (v in nodeset):
+                i, j = index[u], index[v]
+                e_weight = attrs.get(weight, 1)
+                A[i, j] = op([e_weight, A[i, j]])
+                if undirected:
+                    A[j, i] = A[i, j]
+    else:
+        # Graph or DiGraph, this is much faster than above
+        A = np.full((nlen, nlen), np.nan, order=order)
+        for u, nbrdict in G.adjacency():
+            for v, d in nbrdict.items():
+                try:
+                    A[index[u], index[v]] = d.get(weight, 1)
+                except KeyError:
+                    # This occurs when there are fewer desired nodes than
+                    # there are nodes in the graph: len(nodelist) < len(G)
+                    pass
+
+    A[np.isnan(A)] = nonedge
+    A = np.asarray(A, dtype=dtype)
+    return A
+
+
+def from_numpy_array(A, parallel_edges=False, create_using=None):
+    """Return a graph from NumPy array.
+
+    The NumPy array is interpreted as an adjacency matrix for the graph.
+
+    Parameters
+    ----------
+    A : NumPy ndarray
+        An adjacency matrix representation of a graph
+
+    parallel_edges : Boolean
+        If this is True, `create_using` is a multigraph, and `A` is an
+        integer array, then entry *(i, j)* in the adjacency matrix is
+        interpreted as the number of parallel edges joining vertices *i*
+        and *j* in the graph. If it is False, then the entries in the
+        adjacency matrix are interpreted as the weight of a single edge
+        joining the vertices.
+
+    create_using : NetworkX graph
+        Use specified graph for result. The default is Graph()
+
+    Notes
+    -----
+    If `create_using` is an instance of :class:`networkx.MultiGraph` or
+    :class:`networkx.MultiDiGraph`, `parallel_edges` is True, and the
+    entries of `A` are of type :class:`int`, then this function returns a
+    multigraph (of the same type as `create_using`) with parallel edges.
+
+    If `create_using` is an undirected multigraph, then only the edges
+    indicated by the upper triangle of the array `A` will be added to the
+    graph.
+
+    If the NumPy array has a single data type for each array entry it
+    will be converted to an appropriate Python data type.
+
+    If the NumPy array has a user-specified compound data type the names
+    of the data fields will be used as attribute keys in the resulting
+    NetworkX graph.
+
+    See Also
+    --------
+    to_numpy_array
+
+    Examples
+    --------
+    Simple integer weights on edges:
+
+    >>> import numpy as np
+    >>> A = np.array([[1, 1], [2, 1]])
+    >>> G = nx.from_numpy_array(A)
+    >>> G.edges(data=True)
+    EdgeDataView([(0, 0, {'weight': 1}), (0, 1, {'weight': 2}), (1, 1, {'weight': 1})])
+
+    If `create_using` is a multigraph and the array has only integer entries,
+    the entries will be interpreted as weighted edges joining the vertices
+    (without creating parallel edges):
+
+    >>> import numpy as np
+    >>> A = np.array([[1, 1], [1, 2]])
+    >>> G = nx.from_numpy_array(A, create_using=nx.MultiGraph())
+    >>> G[1][1]
+    AtlasView({0: {'weight': 2}})
+
+    If `create_using` is a multigraph and the array has only integer entries
+    but `parallel_edges` is True, then the entries will be interpreted as
+    the number of parallel edges joining those two vertices:
+
+    >>> import numpy as np
+    >>> A = np.array([[1, 1], [1, 2]])
+    >>> temp = nx.MultiGraph()
+    >>> G = nx.from_numpy_array(A, parallel_edges=True, create_using=temp)
+    >>> G[1][1]
+    AtlasView({0: {'weight': 1}, 1: {'weight': 1}})
+
+    User defined compound data type on edges:
+
+    >>> import numpy
+    >>> dt = [('weight', float), ('cost', int)]
+    >>> A = np.array([[(1.0, 2)]], dtype=dt)
+    >>> G = nx.from_numpy_array(A)
+    >>> G.edges()
+    EdgeView([(0, 0)])
+    >>> G[0][0]['cost']
+    2
+    >>> G[0][0]['weight']
+    1.0
+
+    """
+    return from_numpy_matrix(A, parallel_edges=parallel_edges,
+                             create_using=create_using)
+
+
 # fixture for nose tests
 def setup_module(module):
     from nose import SkipTest


Test patch:
diff --git a/networkx/tests/test_convert_numpy.py b/networkx/tests/test_convert_numpy.py
--- a/networkx/tests/test_convert_numpy.py
+++ b/networkx/tests/test_convert_numpy.py
@@ -235,3 +235,210 @@ def test_dtype_int_multigraph(self):
         G = nx.MultiGraph(nx.complete_graph(3))
         A = nx.to_numpy_matrix(G, dtype=int)
         assert_equal(A.dtype, int)
+
+
+class TestConvertNumpyArray(object):
+    numpy=1 # nosetests attribute, use nosetests -a 'not numpy' to skip test
+    @classmethod
+    def setupClass(cls):
+        global np
+        global np_assert_equal
+        try:
+            import numpy as np
+            np_assert_equal=np.testing.assert_equal
+        except ImportError:
+            raise SkipTest('NumPy not available.')
+
+    def __init__(self):
+        self.G1 = barbell_graph(10, 3)
+        self.G2 = cycle_graph(10, create_using=nx.DiGraph())
+
+        self.G3 = self.create_weighted(nx.Graph())
+        self.G4 = self.create_weighted(nx.DiGraph())
+
+    def create_weighted(self, G):
+        g = cycle_graph(4)
+        G.add_nodes_from(g)
+        G.add_weighted_edges_from( (u,v,10+u) for u,v in g.edges())
+        return G
+
+    def assert_equal(self, G1, G2):
+        assert_true( sorted(G1.nodes())==sorted(G2.nodes()) )
+        assert_true( sorted(G1.edges())==sorted(G2.edges()) )
+
+    def identity_conversion(self, G, A, create_using):
+        assert(A.sum() > 0)
+        GG = nx.from_numpy_array(A, create_using=create_using)
+        self.assert_equal(G, GG)
+        GW = nx.to_networkx_graph(A, create_using=create_using)
+        self.assert_equal(G, GW)
+        GI = create_using.__class__(A)
+        self.assert_equal(G, GI)
+
+    def test_shape(self):
+        "Conversion from non-square array."
+        A=np.array([[1,2,3],[4,5,6]])
+        assert_raises(nx.NetworkXError, nx.from_numpy_array, A)
+
+    def test_identity_graph_array(self):
+        "Conversion from graph to array to graph."
+        A = nx.to_numpy_array(self.G1)
+        self.identity_conversion(self.G1, A, nx.Graph())
+
+    def test_identity_digraph_array(self):
+        """Conversion from digraph to array to digraph."""
+        A = nx.to_numpy_array(self.G2)
+        self.identity_conversion(self.G2, A, nx.DiGraph())
+
+    def test_identity_weighted_graph_array(self):
+        """Conversion from weighted graph to array to weighted graph."""
+        A = nx.to_numpy_array(self.G3)
+        self.identity_conversion(self.G3, A, nx.Graph())
+
+    def test_identity_weighted_digraph_array(self):
+        """Conversion from weighted digraph to array to weighted digraph."""
+        A = nx.to_numpy_array(self.G4)
+        self.identity_conversion(self.G4, A, nx.DiGraph())
+
+    def test_nodelist(self):
+        """Conversion from graph to array to graph with nodelist."""
+        P4 = path_graph(4)
+        P3 = path_graph(3)
+        nodelist = list(P3)
+        A = nx.to_numpy_array(P4, nodelist=nodelist)
+        GA = nx.Graph(A)
+        self.assert_equal(GA, P3)
+
+        # Make nodelist ambiguous by containing duplicates.
+        nodelist += [nodelist[0]]
+        assert_raises(nx.NetworkXError, nx.to_numpy_array, P3, nodelist=nodelist)
+
+    def test_weight_keyword(self):
+        WP4 = nx.Graph()
+        WP4.add_edges_from( (n,n+1,dict(weight=0.5,other=0.3)) for n in range(3) )
+        P4 = path_graph(4)
+        A = nx.to_numpy_array(P4)
+        np_assert_equal(A, nx.to_numpy_array(WP4,weight=None))
+        np_assert_equal(0.5*A, nx.to_numpy_array(WP4))
+        np_assert_equal(0.3*A, nx.to_numpy_array(WP4,weight='other'))
+
+    def test_from_numpy_array_type(self):
+        A=np.array([[1]])
+        G=nx.from_numpy_array(A)
+        assert_equal(type(G[0][0]['weight']),int)
+
+        A=np.array([[1]]).astype(np.float)
+        G=nx.from_numpy_array(A)
+        assert_equal(type(G[0][0]['weight']),float)
+
+        A=np.array([[1]]).astype(np.str)
+        G=nx.from_numpy_array(A)
+        assert_equal(type(G[0][0]['weight']),str)
+
+        A=np.array([[1]]).astype(np.bool)
+        G=nx.from_numpy_array(A)
+        assert_equal(type(G[0][0]['weight']),bool)
+
+        A=np.array([[1]]).astype(np.complex)
+        G=nx.from_numpy_array(A)
+        assert_equal(type(G[0][0]['weight']),complex)
+
+        A=np.array([[1]]).astype(np.object)
+        assert_raises(TypeError,nx.from_numpy_array,A)
+
+    def test_from_numpy_array_dtype(self):
+        dt=[('weight',float),('cost',int)]
+        A=np.array([[(1.0,2)]],dtype=dt)
+        G=nx.from_numpy_array(A)
+        assert_equal(type(G[0][0]['weight']),float)
+        assert_equal(type(G[0][0]['cost']),int)
+        assert_equal(G[0][0]['cost'],2)
+        assert_equal(G[0][0]['weight'],1.0)
+
+    def test_to_numpy_recarray(self):
+        G=nx.Graph()
+        G.add_edge(1,2,weight=7.0,cost=5)
+        A=nx.to_numpy_recarray(G,dtype=[('weight',float),('cost',int)])
+        assert_equal(sorted(A.dtype.names),['cost','weight'])
+        assert_equal(A.weight[0,1],7.0)
+        assert_equal(A.weight[0,0],0.0)
+        assert_equal(A.cost[0,1],5)
+        assert_equal(A.cost[0,0],0)
+
+    def test_numpy_multigraph(self):
+        G=nx.MultiGraph()
+        G.add_edge(1,2,weight=7)
+        G.add_edge(1,2,weight=70)
+        A=nx.to_numpy_array(G)
+        assert_equal(A[1,0],77)
+        A=nx.to_numpy_array(G,multigraph_weight=min)
+        assert_equal(A[1,0],7)
+        A=nx.to_numpy_array(G,multigraph_weight=max)
+        assert_equal(A[1,0],70)
+
+    def test_from_numpy_array_parallel_edges(self):
+        """Tests that the :func:`networkx.from_numpy_array` function
+        interprets integer weights as the number of parallel edges when
+        creating a multigraph.
+
+        """
+        A = np.array([[1, 1], [1, 2]])
+        # First, with a simple graph, each integer entry in the adjacency
+        # matrix is interpreted as the weight of a single edge in the graph.
+        expected = nx.DiGraph()
+        edges = [(0, 0), (0, 1), (1, 0)]
+        expected.add_weighted_edges_from([(u, v, 1) for (u, v) in edges])
+        expected.add_edge(1, 1, weight=2)
+        actual = nx.from_numpy_array(A, parallel_edges=True,
+                                      create_using=nx.DiGraph())
+        assert_graphs_equal(actual, expected)
+        actual = nx.from_numpy_array(A, parallel_edges=False,
+                                      create_using=nx.DiGraph())
+        assert_graphs_equal(actual, expected)
+        # Now each integer entry in the adjacency matrix is interpreted as the
+        # number of parallel edges in the graph if the appropriate keyword
+        # argument is specified.
+        edges = [(0, 0), (0, 1), (1, 0), (1, 1), (1, 1)]
+        expected = nx.MultiDiGraph()
+        expected.add_weighted_edges_from([(u, v, 1) for (u, v) in edges])
+        actual = nx.from_numpy_array(A, parallel_edges=True,
+                                      create_using=nx.MultiDiGraph())
+        assert_graphs_equal(actual, expected)
+        expected = nx.MultiDiGraph()
+        expected.add_edges_from(set(edges), weight=1)
+        # The sole self-loop (edge 0) on vertex 1 should have weight 2.
+        expected[1][1][0]['weight'] = 2
+        actual = nx.from_numpy_array(A, parallel_edges=False,
+                                      create_using=nx.MultiDiGraph())
+        assert_graphs_equal(actual, expected)
+
+    def test_symmetric(self):
+        """Tests that a symmetric array has edges added only once to an
+        undirected multigraph when using :func:`networkx.from_numpy_array`.
+
+        """
+        A = np.array([[0, 1], [1, 0]])
+        G = nx.from_numpy_array(A, create_using=nx.MultiGraph())
+        expected = nx.MultiGraph()
+        expected.add_edge(0, 1, weight=1)
+        assert_graphs_equal(G, expected)
+
+    def test_dtype_int_graph(self):
+        """Test that setting dtype int actually gives an integer array.
+
+        For more information, see GitHub pull request #1363.
+
+        """
+        G = nx.complete_graph(3)
+        A = nx.to_numpy_array(G, dtype=int)
+        assert_equal(A.dtype, int)
+
+    def test_dtype_int_multigraph(self):
+        """Test that setting dtype int actually gives an integer array.
+
+        For more information, see GitHub pull request #1363.
+
+        """
+        G = nx.MultiGraph(nx.complete_graph(3))
+        A = nx.to_numpy_array(G, dtype=int)
+        assert_equal(A.dtype, int)