Skip to content

Commit

Permalink
Merge branch 'pull-266-laplacian' into master.
Browse files Browse the repository at this point in the history
Reviewed on PR-266. Closes ticket 1681.
  • Loading branch information
rgommers committed Jul 17, 2012
2 parents 65e8ad4 + 9095c6d commit 9689399
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 27 deletions.
24 changes: 13 additions & 11 deletions scipy/sparse/csgraph/_laplacian.py
Expand Up @@ -8,7 +8,8 @@
# License: BSD # License: BSD


import numpy as np import numpy as np
from scipy.sparse import isspmatrix from scipy.sparse import isspmatrix, coo_matrix



############################################################################### ###############################################################################
# Graph laplacian # Graph laplacian
Expand All @@ -31,7 +32,7 @@ def laplacian(csgraph, normed=False, return_diag=False):
------- -------
lap: ndarray lap: ndarray
The N x N laplacian matrix of graph. The N x N laplacian matrix of graph.
diag: ndarray diag: ndarray
The length-N diagonal of the laplacian matrix. The length-N diagonal of the laplacian matrix.
diag is returned only if return_diag is True. diag is returned only if return_diag is True.
Expand Down Expand Up @@ -70,7 +71,7 @@ def laplacian(csgraph, normed=False, return_diag=False):
or np.issubdtype(csgraph.dtype, np.uint)): or np.issubdtype(csgraph.dtype, np.uint)):
csgraph = csgraph.astype(np.float) csgraph = csgraph.astype(np.float)


if isspmatrix(csgraph): if isspmatrix(csgraph):
return _laplacian_sparse(csgraph, normed=normed, return _laplacian_sparse(csgraph, normed=normed,
return_diag=return_diag) return_diag=return_diag)
else: else:
Expand All @@ -89,14 +90,14 @@ def _laplacian_sparse(graph, normed=False, return_diag=False):
# The sparsity pattern of the matrix has holes on the diagonal, # The sparsity pattern of the matrix has holes on the diagonal,
# we need to fix that # we need to fix that
diag_idx = lap.row[diag_mask] diag_idx = lap.row[diag_mask]

lap = lap.tolil()

diagonal_holes = list(set(range(n_nodes)).difference( diagonal_holes = list(set(range(n_nodes)).difference(
diag_idx)) diag_idx))
lap[diagonal_holes, diagonal_holes] = 1 new_data = np.concatenate([lap.data, np.ones(len(diagonal_holes))])
lap = lap.tocoo() new_row = np.concatenate([lap.row, diagonal_holes])
new_col = np.concatenate([lap.col, diagonal_holes])
lap = coo_matrix((new_data, (new_row, new_col)), shape=lap.shape)
diag_mask = (lap.row == lap.col) diag_mask = (lap.row == lap.col)

lap.data[diag_mask] = 0 lap.data[diag_mask] = 0
w = -np.asarray(lap.sum(axis=1)).squeeze() w = -np.asarray(lap.sum(axis=1)).squeeze()
if normed: if normed:
Expand All @@ -105,17 +106,18 @@ def _laplacian_sparse(graph, normed=False, return_diag=False):
w[w_zeros] = 1 w[w_zeros] = 1
lap.data /= w[lap.row] lap.data /= w[lap.row]
lap.data /= w[lap.col] lap.data /= w[lap.col]
lap.data[diag_mask] = (1 - w_zeros).astype(lap.data.dtype) lap.data[diag_mask] = (1 - w_zeros[lap.row[diag_mask]]).astype(lap.data.dtype)
else: else:
lap.data[diag_mask] = w[lap.row[diag_mask]] lap.data[diag_mask] = w[lap.row[diag_mask]]

if return_diag: if return_diag:
return lap, w return lap, w
return lap return lap




def _laplacian_dense(graph, normed=False, return_diag=False): def _laplacian_dense(graph, normed=False, return_diag=False):
n_nodes = graph.shape[0] n_nodes = graph.shape[0]
lap = -graph.copy() lap = -np.asarray(graph) # minus sign leads to a copy


# set diagonal to zero # set diagonal to zero
lap.flat[::n_nodes + 1] = 0 lap.flat[::n_nodes + 1] = 0
Expand All @@ -129,7 +131,7 @@ def _laplacian_dense(graph, normed=False, return_diag=False):
lap.flat[::n_nodes + 1] = 1 - w_zeros lap.flat[::n_nodes + 1] = 1 - w_zeros
else: else:
lap.flat[::n_nodes + 1] = w lap.flat[::n_nodes + 1] = w

if return_diag: if return_diag:
return lap, w return lap, w
return lap return lap

71 changes: 55 additions & 16 deletions scipy/sparse/csgraph/tests/test_graph_laplacian.py
@@ -1,27 +1,66 @@
# Author: Gael Varoquaux <gael.varoquaux@normalesup.org> # Author: Gael Varoquaux <gael.varoquaux@normalesup.org>
# Jake Vanderplas <vanderplas@astro.washington.edu>
# License: BSD # License: BSD


import numpy as np import numpy as np
from scipy import sparse from scipy import sparse


from scipy.sparse import csgraph from scipy.sparse import csgraph


def _explicit_laplacian(x, normed=False):
if sparse.issparse(x):
x = x.todense()
x = np.asarray(x)
y = -1.0 * x
for j in range(y.shape[0]):
y[j,j] = x[j,j+1:].sum() + x[j,:j].sum()
if normed:
d = np.diag(y).copy()
d[d == 0] = 1.0
y /= d[:,None]**.5
y /= d[None,:]**.5
return y


def test_graph_laplacian(): def _check_graph_laplacian(mat, normed):
for mat in (np.arange(10) * np.arange(10)[:, np.newaxis], if not hasattr(mat, 'shape'):
np.ones((7, 7)), mat = eval(mat, dict(np=np, sparse=sparse))
np.eye(19),
np.vander(np.arange(4)) + np.vander(np.arange(4)).T, if sparse.issparse(mat):
): sp_mat = mat
mat = sp_mat.todense()
else:
sp_mat = sparse.csr_matrix(mat) sp_mat = sparse.csr_matrix(mat)

laplacian = csgraph.laplacian(mat, normed=normed)
n_nodes = mat.shape[0]
if not normed:
np.testing.assert_array_almost_equal(laplacian.sum(axis=0),
np.zeros(n_nodes))
np.testing.assert_array_almost_equal(laplacian.T,
laplacian)
np.testing.assert_array_almost_equal(\
laplacian,
csgraph.laplacian(sp_mat, normed=normed).todense())

np.testing.assert_array_almost_equal(
laplacian,
_explicit_laplacian(mat, normed=normed))

def test_graph_laplacian():
mats = ('np.arange(10) * np.arange(10)[:, np.newaxis]',
'np.ones((7, 7))',
'np.eye(19)',
'sparse.diags([1, 1], [-1, 1], shape=(4,4))',
'sparse.diags([1, 1], [-1, 1], shape=(4,4)).todense()',
'np.asarray(sparse.diags([1, 1], [-1, 1], shape=(4,4)).todense())',
'np.vander(np.arange(4)) + np.vander(np.arange(4)).T',
)

for mat_str in mats:
for normed in (True, False): for normed in (True, False):
laplacian = csgraph.laplacian(mat, normed=normed) yield _check_graph_laplacian, mat_str, normed
n_nodes = mat.shape[0]
if not normed:
np.testing.assert_array_almost_equal(laplacian.sum(axis=0), if __name__ == '__main__':
np.zeros(n_nodes)) import nose
np.testing.assert_array_almost_equal(laplacian.T, nose.runmodule()
laplacian)
np.testing.assert_array_almost_equal(\
laplacian,
csgraph.laplacian(sp_mat, normed=normed).todense())

0 comments on commit 9689399

Please sign in to comment.