173 lines
5.4 KiB
Python
173 lines
5.4 KiB
Python
|
"""
|
||
|
Adjacency matrix and incidence matrix of graphs.
|
||
|
"""
|
||
|
# 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.
|
||
|
import networkx as nx
|
||
|
__author__ = "\n".join(['Aric Hagberg (hagberg@lanl.gov)',
|
||
|
'Pieter Swart (swart@lanl.gov)',
|
||
|
'Dan Schult(dschult@colgate.edu)'])
|
||
|
|
||
|
__all__ = ['incidence_matrix',
|
||
|
'adj_matrix', 'adjacency_matrix',
|
||
|
]
|
||
|
|
||
|
|
||
|
def incidence_matrix(G, nodelist=None, edgelist=None,
|
||
|
oriented=False, weight=None):
|
||
|
"""Returns incidence matrix of G.
|
||
|
|
||
|
The incidence matrix assigns each row to a node and each column to an edge.
|
||
|
For a standard incidence matrix a 1 appears wherever a row's node is
|
||
|
incident on the column's edge. For an oriented incidence matrix each
|
||
|
edge is assigned an orientation (arbitrarily for undirected and aligning to
|
||
|
direction for directed). A -1 appears for the tail of an edge and 1
|
||
|
for the head of the edge. The elements are zero otherwise.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
G : graph
|
||
|
A NetworkX graph
|
||
|
|
||
|
nodelist : list, optional (default= all nodes in G)
|
||
|
The rows are ordered according to the nodes in nodelist.
|
||
|
If nodelist is None, then the ordering is produced by G.nodes().
|
||
|
|
||
|
edgelist : list, optional (default= all edges in G)
|
||
|
The columns are ordered according to the edges in edgelist.
|
||
|
If edgelist is None, then the ordering is produced by G.edges().
|
||
|
|
||
|
oriented: bool, optional (default=False)
|
||
|
If True, matrix elements are +1 or -1 for the head or tail node
|
||
|
respectively of each edge. If False, +1 occurs at both nodes.
|
||
|
|
||
|
weight : string or None, optional (default=None)
|
||
|
The edge data key used to provide each value in the matrix.
|
||
|
If None, then each edge has weight 1. Edge weights, if used,
|
||
|
should be positive so that the orientation can provide the sign.
|
||
|
|
||
|
Returns
|
||
|
-------
|
||
|
A : SciPy sparse matrix
|
||
|
The incidence matrix of G.
|
||
|
|
||
|
Notes
|
||
|
-----
|
||
|
For MultiGraph/MultiDiGraph, the edges in edgelist should be
|
||
|
(u,v,key) 3-tuples.
|
||
|
|
||
|
"Networks are the best discrete model for so many problems in
|
||
|
applied mathematics" [1]_.
|
||
|
|
||
|
References
|
||
|
----------
|
||
|
.. [1] Gil Strang, Network applications: A = incidence matrix,
|
||
|
http://academicearth.org/lectures/network-applications-incidence-matrix
|
||
|
"""
|
||
|
import scipy.sparse
|
||
|
if nodelist is None:
|
||
|
nodelist = list(G)
|
||
|
if edgelist is None:
|
||
|
if G.is_multigraph():
|
||
|
edgelist = list(G.edges(keys=True))
|
||
|
else:
|
||
|
edgelist = list(G.edges())
|
||
|
A = scipy.sparse.lil_matrix((len(nodelist), len(edgelist)))
|
||
|
node_index = dict((node, i) for i, node in enumerate(nodelist))
|
||
|
for ei, e in enumerate(edgelist):
|
||
|
(u, v) = e[:2]
|
||
|
if u == v:
|
||
|
continue # self loops give zero column
|
||
|
try:
|
||
|
ui = node_index[u]
|
||
|
vi = node_index[v]
|
||
|
except KeyError:
|
||
|
raise nx.NetworkXError('node %s or %s in edgelist '
|
||
|
'but not in nodelist' % (u, v))
|
||
|
if weight is None:
|
||
|
wt = 1
|
||
|
else:
|
||
|
if G.is_multigraph():
|
||
|
ekey = e[2]
|
||
|
wt = G[u][v][ekey].get(weight, 1)
|
||
|
else:
|
||
|
wt = G[u][v].get(weight, 1)
|
||
|
if oriented:
|
||
|
A[ui, ei] = -wt
|
||
|
A[vi, ei] = wt
|
||
|
else:
|
||
|
A[ui, ei] = wt
|
||
|
A[vi, ei] = wt
|
||
|
return A.asformat('csc')
|
||
|
|
||
|
|
||
|
def adjacency_matrix(G, nodelist=None, weight='weight'):
|
||
|
"""Returns adjacency matrix of G.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
G : graph
|
||
|
A NetworkX graph
|
||
|
|
||
|
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().
|
||
|
|
||
|
weight : string or None, optional (default='weight')
|
||
|
The edge data key used to provide each value in the matrix.
|
||
|
If None, then each edge has weight 1.
|
||
|
|
||
|
Returns
|
||
|
-------
|
||
|
A : SciPy sparse matrix
|
||
|
Adjacency matrix representation of G.
|
||
|
|
||
|
Notes
|
||
|
-----
|
||
|
For directed graphs, entry i,j corresponds to an edge from i to j.
|
||
|
|
||
|
If you want a pure Python adjacency matrix representation try
|
||
|
networkx.convert.to_dict_of_dicts which will return a
|
||
|
dictionary-of-dictionaries format that can be addressed as a
|
||
|
sparse matrix.
|
||
|
|
||
|
For MultiGraph/MultiDiGraph with parallel edges the weights are summed.
|
||
|
See to_numpy_matrix for other options.
|
||
|
|
||
|
The convention used for self-loop edges in graphs is to assign the
|
||
|
diagonal matrix entry value to the edge weight attribute
|
||
|
(or the number 1 if the edge has no weight attribute). If the
|
||
|
alternate convention of doubling the edge weight is desired the
|
||
|
resulting Scipy sparse matrix can be modified as follows:
|
||
|
|
||
|
>>> import scipy as sp
|
||
|
>>> G = nx.Graph([(1,1)])
|
||
|
>>> A = nx.adjacency_matrix(G)
|
||
|
>>> print(A.todense())
|
||
|
[[1]]
|
||
|
>>> A.setdiag(A.diagonal()*2)
|
||
|
>>> print(A.todense())
|
||
|
[[2]]
|
||
|
|
||
|
See Also
|
||
|
--------
|
||
|
to_numpy_matrix
|
||
|
to_scipy_sparse_matrix
|
||
|
to_dict_of_dicts
|
||
|
adjacency_spectrum
|
||
|
"""
|
||
|
return nx.to_scipy_sparse_matrix(G, nodelist=nodelist, weight=weight)
|
||
|
|
||
|
|
||
|
adj_matrix = adjacency_matrix
|
||
|
|
||
|
|
||
|
# fixture for pytest
|
||
|
def setup_module(module):
|
||
|
import pytest
|
||
|
scipy = pytest.importorskip('scipy')
|