123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358 |
- import pytest
- import numpy as np
- from numpy.testing import assert_allclose
- from pytest import raises as assert_raises
- from scipy import sparse
- from scipy.sparse import csgraph
- def check_int_type(mat):
- return np.issubdtype(mat.dtype, np.signedinteger) or np.issubdtype(
- mat.dtype, np.uint
- )
- def test_laplacian_value_error():
- for t in int, float, complex:
- for m in ([1, 1],
- [[[1]]],
- [[1, 2, 3], [4, 5, 6]],
- [[1, 2], [3, 4], [5, 5]]):
- A = np.array(m, dtype=t)
- assert_raises(ValueError, csgraph.laplacian, A)
- def _explicit_laplacian(x, normed=False):
- if sparse.issparse(x):
- x = x.toarray()
- 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 _check_symmetric_graph_laplacian(mat, normed, copy=True):
- if not hasattr(mat, 'shape'):
- mat = eval(mat, dict(np=np, sparse=sparse))
- if sparse.issparse(mat):
- sp_mat = mat
- mat = sp_mat.toarray()
- else:
- sp_mat = sparse.csr_matrix(mat)
- mat_copy = np.copy(mat)
- sp_mat_copy = sparse.csr_matrix(sp_mat, copy=True)
- n_nodes = mat.shape[0]
- explicit_laplacian = _explicit_laplacian(mat, normed=normed)
- laplacian = csgraph.laplacian(mat, normed=normed, copy=copy)
- sp_laplacian = csgraph.laplacian(sp_mat, normed=normed,
- copy=copy)
- if copy:
- assert_allclose(mat, mat_copy)
- _assert_allclose_sparse(sp_mat, sp_mat_copy)
- else:
- if not (normed and check_int_type(mat)):
- assert_allclose(laplacian, mat)
- if sp_mat.format == 'coo':
- _assert_allclose_sparse(sp_laplacian, sp_mat)
- assert_allclose(laplacian, sp_laplacian.toarray())
- for tested in [laplacian, sp_laplacian.toarray()]:
- if not normed:
- assert_allclose(tested.sum(axis=0), np.zeros(n_nodes))
- assert_allclose(tested.T, tested)
- assert_allclose(tested, explicit_laplacian)
- def test_symmetric_graph_laplacian():
- symmetric_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)).toarray()',
- 'sparse.diags([1, 1], [-1, 1], shape=(4, 4)).todense()',
- 'np.vander(np.arange(4)) + np.vander(np.arange(4)).T'
- )
- for mat in symmetric_mats:
- for normed in True, False:
- for copy in True, False:
- _check_symmetric_graph_laplacian(mat, normed, copy)
- def _assert_allclose_sparse(a, b, **kwargs):
- # helper function that can deal with sparse matrices
- if sparse.issparse(a):
- a = a.toarray()
- if sparse.issparse(b):
- b = b.toarray()
- assert_allclose(a, b, **kwargs)
- def _check_laplacian_dtype_none(
- A, desired_L, desired_d, normed, use_out_degree, copy, dtype, arr_type
- ):
- mat = arr_type(A, dtype=dtype)
- L, d = csgraph.laplacian(
- mat,
- normed=normed,
- return_diag=True,
- use_out_degree=use_out_degree,
- copy=copy,
- dtype=None,
- )
- if normed and check_int_type(mat):
- assert L.dtype == np.float64
- assert d.dtype == np.float64
- _assert_allclose_sparse(L, desired_L, atol=1e-12)
- _assert_allclose_sparse(d, desired_d, atol=1e-12)
- else:
- assert L.dtype == dtype
- assert d.dtype == dtype
- desired_L = np.asarray(desired_L).astype(dtype)
- desired_d = np.asarray(desired_d).astype(dtype)
- _assert_allclose_sparse(L, desired_L, atol=1e-12)
- _assert_allclose_sparse(d, desired_d, atol=1e-12)
- if not copy:
- if not (normed and check_int_type(mat)):
- if type(mat) is np.ndarray:
- assert_allclose(L, mat)
- elif mat.format == "coo":
- _assert_allclose_sparse(L, mat)
- def _check_laplacian_dtype(
- A, desired_L, desired_d, normed, use_out_degree, copy, dtype, arr_type
- ):
- mat = arr_type(A, dtype=dtype)
- L, d = csgraph.laplacian(
- mat,
- normed=normed,
- return_diag=True,
- use_out_degree=use_out_degree,
- copy=copy,
- dtype=dtype,
- )
- assert L.dtype == dtype
- assert d.dtype == dtype
- desired_L = np.asarray(desired_L).astype(dtype)
- desired_d = np.asarray(desired_d).astype(dtype)
- _assert_allclose_sparse(L, desired_L, atol=1e-12)
- _assert_allclose_sparse(d, desired_d, atol=1e-12)
- if not copy:
- if not (normed and check_int_type(mat)):
- if type(mat) is np.ndarray:
- assert_allclose(L, mat)
- elif mat.format == 'coo':
- _assert_allclose_sparse(L, mat)
- INT_DTYPES = {np.intc, np.int_, np.longlong}
- REAL_DTYPES = {np.single, np.double, np.longdouble}
- COMPLEX_DTYPES = {np.csingle, np.cdouble, np.clongdouble}
- # use sorted tuple to ensure fixed order of tests
- DTYPES = tuple(sorted(INT_DTYPES ^ REAL_DTYPES ^ COMPLEX_DTYPES, key=str))
- @pytest.mark.parametrize("dtype", DTYPES)
- @pytest.mark.parametrize("arr_type", [np.array,
- sparse.csr_matrix,
- sparse.coo_matrix])
- @pytest.mark.parametrize("copy", [True, False])
- @pytest.mark.parametrize("normed", [True, False])
- @pytest.mark.parametrize("use_out_degree", [True, False])
- def test_asymmetric_laplacian(use_out_degree, normed,
- copy, dtype, arr_type):
- # adjacency matrix
- A = [[0, 1, 0],
- [4, 2, 0],
- [0, 0, 0]]
- A = arr_type(np.array(A), dtype=dtype)
- A_copy = A.copy()
- if not normed and use_out_degree:
- # Laplacian matrix using out-degree
- L = [[1, -1, 0],
- [-4, 4, 0],
- [0, 0, 0]]
- d = [1, 4, 0]
- if normed and use_out_degree:
- # normalized Laplacian matrix using out-degree
- L = [[1, -0.5, 0],
- [-2, 1, 0],
- [0, 0, 0]]
- d = [1, 2, 1]
- if not normed and not use_out_degree:
- # Laplacian matrix using in-degree
- L = [[4, -1, 0],
- [-4, 1, 0],
- [0, 0, 0]]
- d = [4, 1, 0]
- if normed and not use_out_degree:
- # normalized Laplacian matrix using in-degree
- L = [[1, -0.5, 0],
- [-2, 1, 0],
- [0, 0, 0]]
- d = [2, 1, 1]
- _check_laplacian_dtype_none(
- A,
- L,
- d,
- normed=normed,
- use_out_degree=use_out_degree,
- copy=copy,
- dtype=dtype,
- arr_type=arr_type,
- )
- _check_laplacian_dtype(
- A_copy,
- L,
- d,
- normed=normed,
- use_out_degree=use_out_degree,
- copy=copy,
- dtype=dtype,
- arr_type=arr_type,
- )
- @pytest.mark.parametrize("fmt", ['csr', 'csc', 'coo', 'lil',
- 'dok', 'dia', 'bsr'])
- @pytest.mark.parametrize("normed", [True, False])
- @pytest.mark.parametrize("copy", [True, False])
- def test_sparse_formats(fmt, normed, copy):
- mat = sparse.diags([1, 1], [-1, 1], shape=(4, 4), format=fmt)
- _check_symmetric_graph_laplacian(mat, normed, copy)
- @pytest.mark.parametrize(
- "arr_type", [np.asarray, sparse.csr_matrix, sparse.coo_matrix]
- )
- @pytest.mark.parametrize("form", ["array", "function", "lo"])
- def test_laplacian_symmetrized(arr_type, form):
- # adjacency matrix
- n = 3
- mat = arr_type(np.arange(n * n).reshape(n, n))
- L_in, d_in = csgraph.laplacian(
- mat,
- return_diag=True,
- form=form,
- )
- L_out, d_out = csgraph.laplacian(
- mat,
- return_diag=True,
- use_out_degree=True,
- form=form,
- )
- Ls, ds = csgraph.laplacian(
- mat,
- return_diag=True,
- symmetrized=True,
- form=form,
- )
- Ls_normed, ds_normed = csgraph.laplacian(
- mat,
- return_diag=True,
- symmetrized=True,
- normed=True,
- form=form,
- )
- mat += mat.T
- Lss, dss = csgraph.laplacian(mat, return_diag=True, form=form)
- Lss_normed, dss_normed = csgraph.laplacian(
- mat,
- return_diag=True,
- normed=True,
- form=form,
- )
- assert_allclose(ds, d_in + d_out)
- assert_allclose(ds, dss)
- assert_allclose(ds_normed, dss_normed)
- d = {}
- for L in ["L_in", "L_out", "Ls", "Ls_normed", "Lss", "Lss_normed"]:
- if form == "array":
- d[L] = eval(L)
- else:
- d[L] = eval(L)(np.eye(n, dtype=mat.dtype))
- _assert_allclose_sparse(d["Ls"], d["L_in"] + d["L_out"].T)
- _assert_allclose_sparse(d["Ls"], d["Lss"])
- _assert_allclose_sparse(d["Ls_normed"], d["Lss_normed"])
- @pytest.mark.parametrize(
- "arr_type", [np.asarray, sparse.csr_matrix, sparse.coo_matrix]
- )
- @pytest.mark.parametrize("dtype", DTYPES)
- @pytest.mark.parametrize("normed", [True, False])
- @pytest.mark.parametrize("symmetrized", [True, False])
- @pytest.mark.parametrize("use_out_degree", [True, False])
- @pytest.mark.parametrize("form", ["function", "lo"])
- def test_format(dtype, arr_type, normed, symmetrized, use_out_degree, form):
- n = 3
- mat = [[0, 1, 0], [4, 2, 0], [0, 0, 0]]
- mat = arr_type(np.array(mat), dtype=dtype)
- Lo, do = csgraph.laplacian(
- mat,
- return_diag=True,
- normed=normed,
- symmetrized=symmetrized,
- use_out_degree=use_out_degree,
- dtype=dtype,
- )
- La, da = csgraph.laplacian(
- mat,
- return_diag=True,
- normed=normed,
- symmetrized=symmetrized,
- use_out_degree=use_out_degree,
- dtype=dtype,
- form="array",
- )
- assert_allclose(do, da)
- _assert_allclose_sparse(Lo, La)
- L, d = csgraph.laplacian(
- mat,
- return_diag=True,
- normed=normed,
- symmetrized=symmetrized,
- use_out_degree=use_out_degree,
- dtype=dtype,
- form=form,
- )
- assert_allclose(d, do)
- assert d.dtype == dtype
- Lm = L(np.eye(n, dtype=mat.dtype)).astype(dtype)
- _assert_allclose_sparse(Lm, Lo, rtol=2e-7, atol=2e-7)
- x = np.arange(6).reshape(3, 2)
- if not (normed and dtype in INT_DTYPES):
- assert_allclose(L(x), Lo @ x)
- else:
- # Normalized Lo is casted to integer, but L() is not
- pass
- def test_format_error_message():
- with pytest.raises(ValueError, match="Invalid form: 'toto'"):
- _ = csgraph.laplacian(np.eye(1), form='toto')
|