asd
This commit is contained in:
@ -0,0 +1,119 @@
|
||||
import numpy as np
|
||||
from numpy.testing import assert_equal, assert_array_almost_equal
|
||||
from scipy.sparse import csgraph, csr_array
|
||||
|
||||
|
||||
def test_weak_connections():
|
||||
Xde = np.array([[0, 1, 0],
|
||||
[0, 0, 0],
|
||||
[0, 0, 0]])
|
||||
|
||||
Xsp = csgraph.csgraph_from_dense(Xde, null_value=0)
|
||||
|
||||
for X in Xsp, Xde:
|
||||
n_components, labels =\
|
||||
csgraph.connected_components(X, directed=True,
|
||||
connection='weak')
|
||||
|
||||
assert_equal(n_components, 2)
|
||||
assert_array_almost_equal(labels, [0, 0, 1])
|
||||
|
||||
|
||||
def test_strong_connections():
|
||||
X1de = np.array([[0, 1, 0],
|
||||
[0, 0, 0],
|
||||
[0, 0, 0]])
|
||||
X2de = X1de + X1de.T
|
||||
|
||||
X1sp = csgraph.csgraph_from_dense(X1de, null_value=0)
|
||||
X2sp = csgraph.csgraph_from_dense(X2de, null_value=0)
|
||||
|
||||
for X in X1sp, X1de:
|
||||
n_components, labels =\
|
||||
csgraph.connected_components(X, directed=True,
|
||||
connection='strong')
|
||||
|
||||
assert_equal(n_components, 3)
|
||||
labels.sort()
|
||||
assert_array_almost_equal(labels, [0, 1, 2])
|
||||
|
||||
for X in X2sp, X2de:
|
||||
n_components, labels =\
|
||||
csgraph.connected_components(X, directed=True,
|
||||
connection='strong')
|
||||
|
||||
assert_equal(n_components, 2)
|
||||
labels.sort()
|
||||
assert_array_almost_equal(labels, [0, 0, 1])
|
||||
|
||||
|
||||
def test_strong_connections2():
|
||||
X = np.array([[0, 0, 0, 0, 0, 0],
|
||||
[1, 0, 1, 0, 0, 0],
|
||||
[0, 0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 1, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 1, 0]])
|
||||
n_components, labels =\
|
||||
csgraph.connected_components(X, directed=True,
|
||||
connection='strong')
|
||||
assert_equal(n_components, 5)
|
||||
labels.sort()
|
||||
assert_array_almost_equal(labels, [0, 1, 2, 2, 3, 4])
|
||||
|
||||
|
||||
def test_weak_connections2():
|
||||
X = np.array([[0, 0, 0, 0, 0, 0],
|
||||
[1, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 1, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 1, 0]])
|
||||
n_components, labels =\
|
||||
csgraph.connected_components(X, directed=True,
|
||||
connection='weak')
|
||||
assert_equal(n_components, 2)
|
||||
labels.sort()
|
||||
assert_array_almost_equal(labels, [0, 0, 1, 1, 1, 1])
|
||||
|
||||
|
||||
def test_ticket1876():
|
||||
# Regression test: this failed in the original implementation
|
||||
# There should be two strongly-connected components; previously gave one
|
||||
g = np.array([[0, 1, 1, 0],
|
||||
[1, 0, 0, 1],
|
||||
[0, 0, 0, 1],
|
||||
[0, 0, 1, 0]])
|
||||
n_components, labels = csgraph.connected_components(g, connection='strong')
|
||||
|
||||
assert_equal(n_components, 2)
|
||||
assert_equal(labels[0], labels[1])
|
||||
assert_equal(labels[2], labels[3])
|
||||
|
||||
|
||||
def test_fully_connected_graph():
|
||||
# Fully connected dense matrices raised an exception.
|
||||
# https://github.com/scipy/scipy/issues/3818
|
||||
g = np.ones((4, 4))
|
||||
n_components, labels = csgraph.connected_components(g)
|
||||
assert_equal(n_components, 1)
|
||||
|
||||
|
||||
def test_int64_indices_undirected():
|
||||
# See https://github.com/scipy/scipy/issues/18716
|
||||
g = csr_array(([1], np.array([[0], [1]], dtype=np.int64)), shape=(2, 2))
|
||||
assert g.indices.dtype == np.int64
|
||||
n, labels = csgraph.connected_components(g, directed=False)
|
||||
assert n == 1
|
||||
assert_array_almost_equal(labels, [0, 0])
|
||||
|
||||
|
||||
def test_int64_indices_directed():
|
||||
# See https://github.com/scipy/scipy/issues/18716
|
||||
g = csr_array(([1], np.array([[0], [1]], dtype=np.int64)), shape=(2, 2))
|
||||
assert g.indices.dtype == np.int64
|
||||
n, labels = csgraph.connected_components(g, directed=True,
|
||||
connection='strong')
|
||||
assert n == 2
|
||||
assert_array_almost_equal(labels, [1, 0])
|
||||
|
||||
@ -0,0 +1,61 @@
|
||||
import numpy as np
|
||||
from numpy.testing import assert_array_almost_equal
|
||||
from scipy.sparse import csr_matrix
|
||||
from scipy.sparse.csgraph import csgraph_from_dense, csgraph_to_dense
|
||||
|
||||
|
||||
def test_csgraph_from_dense():
|
||||
np.random.seed(1234)
|
||||
G = np.random.random((10, 10))
|
||||
some_nulls = (G < 0.4)
|
||||
all_nulls = (G < 0.8)
|
||||
|
||||
for null_value in [0, np.nan, np.inf]:
|
||||
G[all_nulls] = null_value
|
||||
with np.errstate(invalid="ignore"):
|
||||
G_csr = csgraph_from_dense(G, null_value=0)
|
||||
|
||||
G[all_nulls] = 0
|
||||
assert_array_almost_equal(G, G_csr.toarray())
|
||||
|
||||
for null_value in [np.nan, np.inf]:
|
||||
G[all_nulls] = 0
|
||||
G[some_nulls] = null_value
|
||||
with np.errstate(invalid="ignore"):
|
||||
G_csr = csgraph_from_dense(G, null_value=0)
|
||||
|
||||
G[all_nulls] = 0
|
||||
assert_array_almost_equal(G, G_csr.toarray())
|
||||
|
||||
|
||||
def test_csgraph_to_dense():
|
||||
np.random.seed(1234)
|
||||
G = np.random.random((10, 10))
|
||||
nulls = (G < 0.8)
|
||||
G[nulls] = np.inf
|
||||
|
||||
G_csr = csgraph_from_dense(G)
|
||||
|
||||
for null_value in [0, 10, -np.inf, np.inf]:
|
||||
G[nulls] = null_value
|
||||
assert_array_almost_equal(G, csgraph_to_dense(G_csr, null_value))
|
||||
|
||||
|
||||
def test_multiple_edges():
|
||||
# create a random square matrix with an even number of elements
|
||||
np.random.seed(1234)
|
||||
X = np.random.random((10, 10))
|
||||
Xcsr = csr_matrix(X)
|
||||
|
||||
# now double-up every other column
|
||||
Xcsr.indices[::2] = Xcsr.indices[1::2]
|
||||
|
||||
# normal sparse toarray() will sum the duplicated edges
|
||||
Xdense = Xcsr.toarray()
|
||||
assert_array_almost_equal(Xdense[:, 1::2],
|
||||
X[:, ::2] + X[:, 1::2])
|
||||
|
||||
# csgraph_to_dense chooses the minimum of each duplicated edge
|
||||
Xdense = csgraph_to_dense(Xcsr)
|
||||
assert_array_almost_equal(Xdense[:, 1::2],
|
||||
np.minimum(X[:, ::2], X[:, 1::2]))
|
||||
@ -0,0 +1,201 @@
|
||||
import numpy as np
|
||||
from numpy.testing import assert_array_equal
|
||||
import pytest
|
||||
|
||||
from scipy.sparse import csr_matrix, csc_matrix
|
||||
from scipy.sparse.csgraph import maximum_flow
|
||||
from scipy.sparse.csgraph._flow import (
|
||||
_add_reverse_edges, _make_edge_pointers, _make_tails
|
||||
)
|
||||
|
||||
methods = ['edmonds_karp', 'dinic']
|
||||
|
||||
def test_raises_on_dense_input():
|
||||
with pytest.raises(TypeError):
|
||||
graph = np.array([[0, 1], [0, 0]])
|
||||
maximum_flow(graph, 0, 1)
|
||||
maximum_flow(graph, 0, 1, method='edmonds_karp')
|
||||
|
||||
|
||||
def test_raises_on_csc_input():
|
||||
with pytest.raises(TypeError):
|
||||
graph = csc_matrix([[0, 1], [0, 0]])
|
||||
maximum_flow(graph, 0, 1)
|
||||
maximum_flow(graph, 0, 1, method='edmonds_karp')
|
||||
|
||||
|
||||
def test_raises_on_floating_point_input():
|
||||
with pytest.raises(ValueError):
|
||||
graph = csr_matrix([[0, 1.5], [0, 0]], dtype=np.float64)
|
||||
maximum_flow(graph, 0, 1)
|
||||
maximum_flow(graph, 0, 1, method='edmonds_karp')
|
||||
|
||||
|
||||
def test_raises_on_non_square_input():
|
||||
with pytest.raises(ValueError):
|
||||
graph = csr_matrix([[0, 1, 2], [2, 1, 0]])
|
||||
maximum_flow(graph, 0, 1)
|
||||
|
||||
|
||||
def test_raises_when_source_is_sink():
|
||||
with pytest.raises(ValueError):
|
||||
graph = csr_matrix([[0, 1], [0, 0]])
|
||||
maximum_flow(graph, 0, 0)
|
||||
maximum_flow(graph, 0, 0, method='edmonds_karp')
|
||||
|
||||
|
||||
@pytest.mark.parametrize('method', methods)
|
||||
@pytest.mark.parametrize('source', [-1, 2, 3])
|
||||
def test_raises_when_source_is_out_of_bounds(source, method):
|
||||
with pytest.raises(ValueError):
|
||||
graph = csr_matrix([[0, 1], [0, 0]])
|
||||
maximum_flow(graph, source, 1, method=method)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('method', methods)
|
||||
@pytest.mark.parametrize('sink', [-1, 2, 3])
|
||||
def test_raises_when_sink_is_out_of_bounds(sink, method):
|
||||
with pytest.raises(ValueError):
|
||||
graph = csr_matrix([[0, 1], [0, 0]])
|
||||
maximum_flow(graph, 0, sink, method=method)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('method', methods)
|
||||
def test_simple_graph(method):
|
||||
# This graph looks as follows:
|
||||
# (0) --5--> (1)
|
||||
graph = csr_matrix([[0, 5], [0, 0]])
|
||||
res = maximum_flow(graph, 0, 1, method=method)
|
||||
assert res.flow_value == 5
|
||||
expected_flow = np.array([[0, 5], [-5, 0]])
|
||||
assert_array_equal(res.flow.toarray(), expected_flow)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('method', methods)
|
||||
def test_bottle_neck_graph(method):
|
||||
# This graph cannot use the full capacity between 0 and 1:
|
||||
# (0) --5--> (1) --3--> (2)
|
||||
graph = csr_matrix([[0, 5, 0], [0, 0, 3], [0, 0, 0]])
|
||||
res = maximum_flow(graph, 0, 2, method=method)
|
||||
assert res.flow_value == 3
|
||||
expected_flow = np.array([[0, 3, 0], [-3, 0, 3], [0, -3, 0]])
|
||||
assert_array_equal(res.flow.toarray(), expected_flow)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('method', methods)
|
||||
def test_backwards_flow(method):
|
||||
# This example causes backwards flow between vertices 3 and 4,
|
||||
# and so this test ensures that we handle that accordingly. See
|
||||
# https://stackoverflow.com/q/38843963/5085211
|
||||
# for more information.
|
||||
graph = csr_matrix([[0, 10, 0, 0, 10, 0, 0, 0],
|
||||
[0, 0, 10, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 10, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 10],
|
||||
[0, 0, 0, 10, 0, 10, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 10, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 10],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0]])
|
||||
res = maximum_flow(graph, 0, 7, method=method)
|
||||
assert res.flow_value == 20
|
||||
expected_flow = np.array([[0, 10, 0, 0, 10, 0, 0, 0],
|
||||
[-10, 0, 10, 0, 0, 0, 0, 0],
|
||||
[0, -10, 0, 10, 0, 0, 0, 0],
|
||||
[0, 0, -10, 0, 0, 0, 0, 10],
|
||||
[-10, 0, 0, 0, 0, 10, 0, 0],
|
||||
[0, 0, 0, 0, -10, 0, 10, 0],
|
||||
[0, 0, 0, 0, 0, -10, 0, 10],
|
||||
[0, 0, 0, -10, 0, 0, -10, 0]])
|
||||
assert_array_equal(res.flow.toarray(), expected_flow)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('method', methods)
|
||||
def test_example_from_clrs_chapter_26_1(method):
|
||||
# See page 659 in CLRS second edition, but note that the maximum flow
|
||||
# we find is slightly different than the one in CLRS; we push a flow of
|
||||
# 12 to v_1 instead of v_2.
|
||||
graph = csr_matrix([[0, 16, 13, 0, 0, 0],
|
||||
[0, 0, 10, 12, 0, 0],
|
||||
[0, 4, 0, 0, 14, 0],
|
||||
[0, 0, 9, 0, 0, 20],
|
||||
[0, 0, 0, 7, 0, 4],
|
||||
[0, 0, 0, 0, 0, 0]])
|
||||
res = maximum_flow(graph, 0, 5, method=method)
|
||||
assert res.flow_value == 23
|
||||
expected_flow = np.array([[0, 12, 11, 0, 0, 0],
|
||||
[-12, 0, 0, 12, 0, 0],
|
||||
[-11, 0, 0, 0, 11, 0],
|
||||
[0, -12, 0, 0, -7, 19],
|
||||
[0, 0, -11, 7, 0, 4],
|
||||
[0, 0, 0, -19, -4, 0]])
|
||||
assert_array_equal(res.flow.toarray(), expected_flow)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('method', methods)
|
||||
def test_disconnected_graph(method):
|
||||
# This tests the following disconnected graph:
|
||||
# (0) --5--> (1) (2) --3--> (3)
|
||||
graph = csr_matrix([[0, 5, 0, 0],
|
||||
[0, 0, 0, 0],
|
||||
[0, 0, 9, 3],
|
||||
[0, 0, 0, 0]])
|
||||
res = maximum_flow(graph, 0, 3, method=method)
|
||||
assert res.flow_value == 0
|
||||
expected_flow = np.zeros((4, 4), dtype=np.int32)
|
||||
assert_array_equal(res.flow.toarray(), expected_flow)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('method', methods)
|
||||
def test_add_reverse_edges_large_graph(method):
|
||||
# Regression test for https://github.com/scipy/scipy/issues/14385
|
||||
n = 100_000
|
||||
indices = np.arange(1, n)
|
||||
indptr = np.array(list(range(n)) + [n - 1])
|
||||
data = np.ones(n - 1, dtype=np.int32)
|
||||
graph = csr_matrix((data, indices, indptr), shape=(n, n))
|
||||
res = maximum_flow(graph, 0, n - 1, method=method)
|
||||
assert res.flow_value == 1
|
||||
expected_flow = graph - graph.transpose()
|
||||
assert_array_equal(res.flow.data, expected_flow.data)
|
||||
assert_array_equal(res.flow.indices, expected_flow.indices)
|
||||
assert_array_equal(res.flow.indptr, expected_flow.indptr)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("a,b_data_expected", [
|
||||
([[]], []),
|
||||
([[0], [0]], []),
|
||||
([[1, 0, 2], [0, 0, 0], [0, 3, 0]], [1, 2, 0, 0, 3]),
|
||||
([[9, 8, 7], [4, 5, 6], [0, 0, 0]], [9, 8, 7, 4, 5, 6, 0, 0])])
|
||||
def test_add_reverse_edges(a, b_data_expected):
|
||||
"""Test that the reversal of the edges of the input graph works
|
||||
as expected.
|
||||
"""
|
||||
a = csr_matrix(a, dtype=np.int32, shape=(len(a), len(a)))
|
||||
b = _add_reverse_edges(a)
|
||||
assert_array_equal(b.data, b_data_expected)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("a,expected", [
|
||||
([[]], []),
|
||||
([[0]], []),
|
||||
([[1]], [0]),
|
||||
([[0, 1], [10, 0]], [1, 0]),
|
||||
([[1, 0, 2], [0, 0, 3], [4, 5, 0]], [0, 3, 4, 1, 2])
|
||||
])
|
||||
def test_make_edge_pointers(a, expected):
|
||||
a = csr_matrix(a, dtype=np.int32)
|
||||
rev_edge_ptr = _make_edge_pointers(a)
|
||||
assert_array_equal(rev_edge_ptr, expected)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("a,expected", [
|
||||
([[]], []),
|
||||
([[0]], []),
|
||||
([[1]], [0]),
|
||||
([[0, 1], [10, 0]], [0, 1]),
|
||||
([[1, 0, 2], [0, 0, 3], [4, 5, 0]], [0, 0, 1, 2, 2])
|
||||
])
|
||||
def test_make_tails(a, expected):
|
||||
a = csr_matrix(a, dtype=np.int32)
|
||||
tails = _make_tails(a)
|
||||
assert_array_equal(tails, expected)
|
||||
@ -0,0 +1,369 @@
|
||||
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
|
||||
from scipy._lib._util import np_long, np_ulong
|
||||
|
||||
|
||||
def check_int_type(mat):
|
||||
return np.issubdtype(mat.dtype, np.signedinteger) or np.issubdtype(
|
||||
mat.dtype, np_ulong
|
||||
)
|
||||
|
||||
|
||||
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_long, np.longlong}
|
||||
REAL_DTYPES = {np.float32, np.float64, np.longdouble}
|
||||
COMPLEX_DTYPES = {np.complex64, np.complex128, np.clongdouble}
|
||||
# use sorted list to ensure fixed order of tests
|
||||
DTYPES = 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,
|
||||
sparse.csr_array,
|
||||
sparse.coo_array])
|
||||
@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,
|
||||
sparse.csr_array,
|
||||
sparse.coo_array]
|
||||
)
|
||||
@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,
|
||||
sparse.csr_array,
|
||||
sparse.coo_array]
|
||||
)
|
||||
@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')
|
||||
@ -0,0 +1,294 @@
|
||||
from itertools import product
|
||||
|
||||
import numpy as np
|
||||
from numpy.testing import assert_array_equal, assert_equal
|
||||
import pytest
|
||||
|
||||
from scipy.sparse import csr_matrix, coo_matrix, diags
|
||||
from scipy.sparse.csgraph import (
|
||||
maximum_bipartite_matching, min_weight_full_bipartite_matching
|
||||
)
|
||||
|
||||
|
||||
def test_maximum_bipartite_matching_raises_on_dense_input():
|
||||
with pytest.raises(TypeError):
|
||||
graph = np.array([[0, 1], [0, 0]])
|
||||
maximum_bipartite_matching(graph)
|
||||
|
||||
|
||||
def test_maximum_bipartite_matching_empty_graph():
|
||||
graph = csr_matrix((0, 0))
|
||||
x = maximum_bipartite_matching(graph, perm_type='row')
|
||||
y = maximum_bipartite_matching(graph, perm_type='column')
|
||||
expected_matching = np.array([])
|
||||
assert_array_equal(expected_matching, x)
|
||||
assert_array_equal(expected_matching, y)
|
||||
|
||||
|
||||
def test_maximum_bipartite_matching_empty_left_partition():
|
||||
graph = csr_matrix((2, 0))
|
||||
x = maximum_bipartite_matching(graph, perm_type='row')
|
||||
y = maximum_bipartite_matching(graph, perm_type='column')
|
||||
assert_array_equal(np.array([]), x)
|
||||
assert_array_equal(np.array([-1, -1]), y)
|
||||
|
||||
|
||||
def test_maximum_bipartite_matching_empty_right_partition():
|
||||
graph = csr_matrix((0, 3))
|
||||
x = maximum_bipartite_matching(graph, perm_type='row')
|
||||
y = maximum_bipartite_matching(graph, perm_type='column')
|
||||
assert_array_equal(np.array([-1, -1, -1]), x)
|
||||
assert_array_equal(np.array([]), y)
|
||||
|
||||
|
||||
def test_maximum_bipartite_matching_graph_with_no_edges():
|
||||
graph = csr_matrix((2, 2))
|
||||
x = maximum_bipartite_matching(graph, perm_type='row')
|
||||
y = maximum_bipartite_matching(graph, perm_type='column')
|
||||
assert_array_equal(np.array([-1, -1]), x)
|
||||
assert_array_equal(np.array([-1, -1]), y)
|
||||
|
||||
|
||||
def test_maximum_bipartite_matching_graph_that_causes_augmentation():
|
||||
# In this graph, column 1 is initially assigned to row 1, but it should be
|
||||
# reassigned to make room for row 2.
|
||||
graph = csr_matrix([[1, 1], [1, 0]])
|
||||
x = maximum_bipartite_matching(graph, perm_type='column')
|
||||
y = maximum_bipartite_matching(graph, perm_type='row')
|
||||
expected_matching = np.array([1, 0])
|
||||
assert_array_equal(expected_matching, x)
|
||||
assert_array_equal(expected_matching, y)
|
||||
|
||||
|
||||
def test_maximum_bipartite_matching_graph_with_more_rows_than_columns():
|
||||
graph = csr_matrix([[1, 1], [1, 0], [0, 1]])
|
||||
x = maximum_bipartite_matching(graph, perm_type='column')
|
||||
y = maximum_bipartite_matching(graph, perm_type='row')
|
||||
assert_array_equal(np.array([0, -1, 1]), x)
|
||||
assert_array_equal(np.array([0, 2]), y)
|
||||
|
||||
|
||||
def test_maximum_bipartite_matching_graph_with_more_columns_than_rows():
|
||||
graph = csr_matrix([[1, 1, 0], [0, 0, 1]])
|
||||
x = maximum_bipartite_matching(graph, perm_type='column')
|
||||
y = maximum_bipartite_matching(graph, perm_type='row')
|
||||
assert_array_equal(np.array([0, 2]), x)
|
||||
assert_array_equal(np.array([0, -1, 1]), y)
|
||||
|
||||
|
||||
def test_maximum_bipartite_matching_explicit_zeros_count_as_edges():
|
||||
data = [0, 0]
|
||||
indices = [1, 0]
|
||||
indptr = [0, 1, 2]
|
||||
graph = csr_matrix((data, indices, indptr), shape=(2, 2))
|
||||
x = maximum_bipartite_matching(graph, perm_type='row')
|
||||
y = maximum_bipartite_matching(graph, perm_type='column')
|
||||
expected_matching = np.array([1, 0])
|
||||
assert_array_equal(expected_matching, x)
|
||||
assert_array_equal(expected_matching, y)
|
||||
|
||||
|
||||
def test_maximum_bipartite_matching_feasibility_of_result():
|
||||
# This is a regression test for GitHub issue #11458
|
||||
data = np.ones(50, dtype=int)
|
||||
indices = [11, 12, 19, 22, 23, 5, 22, 3, 8, 10, 5, 6, 11, 12, 13, 5, 13,
|
||||
14, 20, 22, 3, 15, 3, 13, 14, 11, 12, 19, 22, 23, 5, 22, 3, 8,
|
||||
10, 5, 6, 11, 12, 13, 5, 13, 14, 20, 22, 3, 15, 3, 13, 14]
|
||||
indptr = [0, 5, 7, 10, 10, 15, 20, 22, 22, 23, 25, 30, 32, 35, 35, 40, 45,
|
||||
47, 47, 48, 50]
|
||||
graph = csr_matrix((data, indices, indptr), shape=(20, 25))
|
||||
x = maximum_bipartite_matching(graph, perm_type='row')
|
||||
y = maximum_bipartite_matching(graph, perm_type='column')
|
||||
assert (x != -1).sum() == 13
|
||||
assert (y != -1).sum() == 13
|
||||
# Ensure that each element of the matching is in fact an edge in the graph.
|
||||
for u, v in zip(range(graph.shape[0]), y):
|
||||
if v != -1:
|
||||
assert graph[u, v]
|
||||
for u, v in zip(x, range(graph.shape[1])):
|
||||
if u != -1:
|
||||
assert graph[u, v]
|
||||
|
||||
|
||||
def test_matching_large_random_graph_with_one_edge_incident_to_each_vertex():
|
||||
np.random.seed(42)
|
||||
A = diags(np.ones(25), offsets=0, format='csr')
|
||||
rand_perm = np.random.permutation(25)
|
||||
rand_perm2 = np.random.permutation(25)
|
||||
|
||||
Rrow = np.arange(25)
|
||||
Rcol = rand_perm
|
||||
Rdata = np.ones(25, dtype=int)
|
||||
Rmat = coo_matrix((Rdata, (Rrow, Rcol))).tocsr()
|
||||
|
||||
Crow = rand_perm2
|
||||
Ccol = np.arange(25)
|
||||
Cdata = np.ones(25, dtype=int)
|
||||
Cmat = coo_matrix((Cdata, (Crow, Ccol))).tocsr()
|
||||
# Randomly permute identity matrix
|
||||
B = Rmat * A * Cmat
|
||||
|
||||
# Row permute
|
||||
perm = maximum_bipartite_matching(B, perm_type='row')
|
||||
Rrow = np.arange(25)
|
||||
Rcol = perm
|
||||
Rdata = np.ones(25, dtype=int)
|
||||
Rmat = coo_matrix((Rdata, (Rrow, Rcol))).tocsr()
|
||||
C1 = Rmat * B
|
||||
|
||||
# Column permute
|
||||
perm2 = maximum_bipartite_matching(B, perm_type='column')
|
||||
Crow = perm2
|
||||
Ccol = np.arange(25)
|
||||
Cdata = np.ones(25, dtype=int)
|
||||
Cmat = coo_matrix((Cdata, (Crow, Ccol))).tocsr()
|
||||
C2 = B * Cmat
|
||||
|
||||
# Should get identity matrix back
|
||||
assert_equal(any(C1.diagonal() == 0), False)
|
||||
assert_equal(any(C2.diagonal() == 0), False)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('num_rows,num_cols', [(0, 0), (2, 0), (0, 3)])
|
||||
def test_min_weight_full_matching_trivial_graph(num_rows, num_cols):
|
||||
biadjacency_matrix = csr_matrix((num_cols, num_rows))
|
||||
row_ind, col_ind = min_weight_full_bipartite_matching(biadjacency_matrix)
|
||||
assert len(row_ind) == 0
|
||||
assert len(col_ind) == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize('biadjacency_matrix',
|
||||
[
|
||||
[[1, 1, 1], [1, 0, 0], [1, 0, 0]],
|
||||
[[1, 1, 1], [0, 0, 1], [0, 0, 1]],
|
||||
[[1, 0, 0, 1], [1, 1, 0, 1], [0, 0, 0, 0]],
|
||||
[[1, 0, 0], [2, 0, 0]],
|
||||
[[0, 1, 0], [0, 2, 0]],
|
||||
[[1, 0], [2, 0], [5, 0]]
|
||||
])
|
||||
def test_min_weight_full_matching_infeasible_problems(biadjacency_matrix):
|
||||
with pytest.raises(ValueError):
|
||||
min_weight_full_bipartite_matching(csr_matrix(biadjacency_matrix))
|
||||
|
||||
|
||||
def test_min_weight_full_matching_large_infeasible():
|
||||
# Regression test for GitHub issue #17269
|
||||
a = np.asarray([
|
||||
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
|
||||
0.0, 0.0, 0.001, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
|
||||
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
|
||||
0.0, 0.0, 0.0, 0.001, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
|
||||
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
|
||||
0.0, 0.0, 0.0, 0.0, 0.001, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
|
||||
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
|
||||
0.0, 0.0, 0.0, 0.0, 0.0, 0.001, 0.0, 0.0, 0.0, 0.0, 0.0],
|
||||
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
|
||||
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.001, 0.0, 0.0, 0.0, 0.0],
|
||||
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
|
||||
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.001, 0.0, 0.0, 0.0],
|
||||
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
|
||||
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.001, 0.0, 0.0],
|
||||
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
|
||||
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.001, 0.0],
|
||||
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
|
||||
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.001],
|
||||
[0.0, 0.11687445, 0.0, 0.0, 0.01319788, 0.07509257, 0.0,
|
||||
0.0, 0.0, 0.74228317, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
|
||||
0.0, 0.0, 0.0, 0.0, 0.0],
|
||||
[0.0, 0.0, 0.0, 0.81087935, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
|
||||
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
|
||||
[0.0, 0.0, 0.0, 0.0, 0.8408466, 0.0, 0.0, 0.0, 0.0, 0.01194389,
|
||||
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
|
||||
[0.0, 0.82994211, 0.0, 0.0, 0.0, 0.11468516, 0.0, 0.0, 0.0,
|
||||
0.11173505, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
|
||||
0.0, 0.0],
|
||||
[0.18796507, 0.0, 0.04002318, 0.0, 0.0, 0.0, 0.0, 0.0, 0.75883335,
|
||||
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
|
||||
[0.0, 0.0, 0.71545464, 0.0, 0.0, 0.0, 0.0, 0.0, 0.02748488,
|
||||
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
|
||||
[0.78470564, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.14829198,
|
||||
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
|
||||
[0.0, 0.10870609, 0.0, 0.0, 0.0, 0.8918677, 0.0, 0.0, 0.0, 0.06306644,
|
||||
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
|
||||
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
|
||||
0.63844085, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
|
||||
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.7442354, 0.0, 0.0, 0.0,
|
||||
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
|
||||
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.09850549, 0.0, 0.0, 0.18638258,
|
||||
0.2769244, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
|
||||
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.73182464, 0.0, 0.0, 0.46443561,
|
||||
0.38589284, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
|
||||
[0.29510278, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.09666032, 0.0,
|
||||
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
|
||||
])
|
||||
with pytest.raises(ValueError, match='no full matching exists'):
|
||||
min_weight_full_bipartite_matching(csr_matrix(a))
|
||||
|
||||
|
||||
def test_explicit_zero_causes_warning():
|
||||
with pytest.warns(UserWarning):
|
||||
biadjacency_matrix = csr_matrix(((2, 0, 3), (0, 1, 1), (0, 2, 3)))
|
||||
min_weight_full_bipartite_matching(biadjacency_matrix)
|
||||
|
||||
|
||||
# General test for linear sum assignment solvers to make it possible to rely
|
||||
# on the same tests for scipy.optimize.linear_sum_assignment.
|
||||
def linear_sum_assignment_assertions(
|
||||
solver, array_type, sign, test_case
|
||||
):
|
||||
cost_matrix, expected_cost = test_case
|
||||
maximize = sign == -1
|
||||
cost_matrix = sign * array_type(cost_matrix)
|
||||
expected_cost = sign * np.array(expected_cost)
|
||||
|
||||
row_ind, col_ind = solver(cost_matrix, maximize=maximize)
|
||||
assert_array_equal(row_ind, np.sort(row_ind))
|
||||
assert_array_equal(expected_cost,
|
||||
np.array(cost_matrix[row_ind, col_ind]).flatten())
|
||||
|
||||
cost_matrix = cost_matrix.T
|
||||
row_ind, col_ind = solver(cost_matrix, maximize=maximize)
|
||||
assert_array_equal(row_ind, np.sort(row_ind))
|
||||
assert_array_equal(np.sort(expected_cost),
|
||||
np.sort(np.array(
|
||||
cost_matrix[row_ind, col_ind])).flatten())
|
||||
|
||||
|
||||
linear_sum_assignment_test_cases = product(
|
||||
[-1, 1],
|
||||
[
|
||||
# Square
|
||||
([[400, 150, 400],
|
||||
[400, 450, 600],
|
||||
[300, 225, 300]],
|
||||
[150, 400, 300]),
|
||||
|
||||
# Rectangular variant
|
||||
([[400, 150, 400, 1],
|
||||
[400, 450, 600, 2],
|
||||
[300, 225, 300, 3]],
|
||||
[150, 2, 300]),
|
||||
|
||||
([[10, 10, 8],
|
||||
[9, 8, 1],
|
||||
[9, 7, 4]],
|
||||
[10, 1, 7]),
|
||||
|
||||
# Square
|
||||
([[10, 10, 8, 11],
|
||||
[9, 8, 1, 1],
|
||||
[9, 7, 4, 10]],
|
||||
[10, 1, 4]),
|
||||
|
||||
# Rectangular variant
|
||||
([[10, float("inf"), float("inf")],
|
||||
[float("inf"), float("inf"), 1],
|
||||
[float("inf"), 7, float("inf")]],
|
||||
[10, 1, 7])
|
||||
])
|
||||
|
||||
|
||||
@pytest.mark.parametrize('sign,test_case', linear_sum_assignment_test_cases)
|
||||
def test_min_weight_full_matching_small_inputs(sign, test_case):
|
||||
linear_sum_assignment_assertions(
|
||||
min_weight_full_bipartite_matching, csr_matrix, sign, test_case)
|
||||
@ -0,0 +1,149 @@
|
||||
import pytest
|
||||
|
||||
import numpy as np
|
||||
import scipy.sparse as sp
|
||||
import scipy.sparse.csgraph as spgraph
|
||||
|
||||
from numpy.testing import assert_equal
|
||||
|
||||
try:
|
||||
import sparse
|
||||
except Exception:
|
||||
sparse = None
|
||||
|
||||
pytestmark = pytest.mark.skipif(sparse is None,
|
||||
reason="pydata/sparse not installed")
|
||||
|
||||
|
||||
msg = "pydata/sparse (0.15.1) does not implement necessary operations"
|
||||
|
||||
|
||||
sparse_params = (pytest.param("COO"),
|
||||
pytest.param("DOK", marks=[pytest.mark.xfail(reason=msg)]))
|
||||
|
||||
|
||||
@pytest.fixture(params=sparse_params)
|
||||
def sparse_cls(request):
|
||||
return getattr(sparse, request.param)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def graphs(sparse_cls):
|
||||
graph = [
|
||||
[0, 1, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 1],
|
||||
[0, 0, 0, 0, 0],
|
||||
]
|
||||
A_dense = np.array(graph)
|
||||
A_sparse = sparse_cls(A_dense)
|
||||
return A_dense, A_sparse
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"func",
|
||||
[
|
||||
spgraph.shortest_path,
|
||||
spgraph.dijkstra,
|
||||
spgraph.floyd_warshall,
|
||||
spgraph.bellman_ford,
|
||||
spgraph.johnson,
|
||||
spgraph.reverse_cuthill_mckee,
|
||||
spgraph.maximum_bipartite_matching,
|
||||
spgraph.structural_rank,
|
||||
]
|
||||
)
|
||||
def test_csgraph_equiv(func, graphs):
|
||||
A_dense, A_sparse = graphs
|
||||
actual = func(A_sparse)
|
||||
desired = func(sp.csc_matrix(A_dense))
|
||||
assert_equal(actual, desired)
|
||||
|
||||
|
||||
def test_connected_components(graphs):
|
||||
A_dense, A_sparse = graphs
|
||||
func = spgraph.connected_components
|
||||
|
||||
actual_comp, actual_labels = func(A_sparse)
|
||||
desired_comp, desired_labels, = func(sp.csc_matrix(A_dense))
|
||||
|
||||
assert actual_comp == desired_comp
|
||||
assert_equal(actual_labels, desired_labels)
|
||||
|
||||
|
||||
def test_laplacian(graphs):
|
||||
A_dense, A_sparse = graphs
|
||||
sparse_cls = type(A_sparse)
|
||||
func = spgraph.laplacian
|
||||
|
||||
actual = func(A_sparse)
|
||||
desired = func(sp.csc_matrix(A_dense))
|
||||
|
||||
assert isinstance(actual, sparse_cls)
|
||||
|
||||
assert_equal(actual.todense(), desired.todense())
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"func", [spgraph.breadth_first_order, spgraph.depth_first_order]
|
||||
)
|
||||
def test_order_search(graphs, func):
|
||||
A_dense, A_sparse = graphs
|
||||
|
||||
actual = func(A_sparse, 0)
|
||||
desired = func(sp.csc_matrix(A_dense), 0)
|
||||
|
||||
assert_equal(actual, desired)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"func", [spgraph.breadth_first_tree, spgraph.depth_first_tree]
|
||||
)
|
||||
def test_tree_search(graphs, func):
|
||||
A_dense, A_sparse = graphs
|
||||
sparse_cls = type(A_sparse)
|
||||
|
||||
actual = func(A_sparse, 0)
|
||||
desired = func(sp.csc_matrix(A_dense), 0)
|
||||
|
||||
assert isinstance(actual, sparse_cls)
|
||||
|
||||
assert_equal(actual.todense(), desired.todense())
|
||||
|
||||
|
||||
def test_minimum_spanning_tree(graphs):
|
||||
A_dense, A_sparse = graphs
|
||||
sparse_cls = type(A_sparse)
|
||||
func = spgraph.minimum_spanning_tree
|
||||
|
||||
actual = func(A_sparse)
|
||||
desired = func(sp.csc_matrix(A_dense))
|
||||
|
||||
assert isinstance(actual, sparse_cls)
|
||||
|
||||
assert_equal(actual.todense(), desired.todense())
|
||||
|
||||
|
||||
def test_maximum_flow(graphs):
|
||||
A_dense, A_sparse = graphs
|
||||
sparse_cls = type(A_sparse)
|
||||
func = spgraph.maximum_flow
|
||||
|
||||
actual = func(A_sparse, 0, 2)
|
||||
desired = func(sp.csr_matrix(A_dense), 0, 2)
|
||||
|
||||
assert actual.flow_value == desired.flow_value
|
||||
assert isinstance(actual.flow, sparse_cls)
|
||||
|
||||
assert_equal(actual.flow.todense(), desired.flow.todense())
|
||||
|
||||
|
||||
def test_min_weight_full_bipartite_matching(graphs):
|
||||
A_dense, A_sparse = graphs
|
||||
func = spgraph.min_weight_full_bipartite_matching
|
||||
|
||||
actual = func(A_sparse[0:2, 1:3])
|
||||
desired = func(sp.csc_matrix(A_dense)[0:2, 1:3])
|
||||
|
||||
assert_equal(actual, desired)
|
||||
@ -0,0 +1,70 @@
|
||||
import numpy as np
|
||||
from numpy.testing import assert_equal
|
||||
from scipy.sparse.csgraph import reverse_cuthill_mckee, structural_rank
|
||||
from scipy.sparse import csc_matrix, csr_matrix, coo_matrix
|
||||
|
||||
|
||||
def test_graph_reverse_cuthill_mckee():
|
||||
A = np.array([[1, 0, 0, 0, 1, 0, 0, 0],
|
||||
[0, 1, 1, 0, 0, 1, 0, 1],
|
||||
[0, 1, 1, 0, 1, 0, 0, 0],
|
||||
[0, 0, 0, 1, 0, 0, 1, 0],
|
||||
[1, 0, 1, 0, 1, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0, 1, 0, 1],
|
||||
[0, 0, 0, 1, 0, 0, 1, 0],
|
||||
[0, 1, 0, 0, 0, 1, 0, 1]], dtype=int)
|
||||
|
||||
graph = csr_matrix(A)
|
||||
perm = reverse_cuthill_mckee(graph)
|
||||
correct_perm = np.array([6, 3, 7, 5, 1, 2, 4, 0])
|
||||
assert_equal(perm, correct_perm)
|
||||
|
||||
# Test int64 indices input
|
||||
graph.indices = graph.indices.astype('int64')
|
||||
graph.indptr = graph.indptr.astype('int64')
|
||||
perm = reverse_cuthill_mckee(graph, True)
|
||||
assert_equal(perm, correct_perm)
|
||||
|
||||
|
||||
def test_graph_reverse_cuthill_mckee_ordering():
|
||||
data = np.ones(63,dtype=int)
|
||||
rows = np.array([0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2,
|
||||
2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5,
|
||||
6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 9, 9,
|
||||
9, 10, 10, 10, 10, 10, 11, 11, 11, 11,
|
||||
12, 12, 12, 13, 13, 13, 13, 14, 14, 14,
|
||||
14, 15, 15, 15, 15, 15])
|
||||
cols = np.array([0, 2, 5, 8, 10, 1, 3, 9, 11, 0, 2,
|
||||
7, 10, 1, 3, 11, 4, 6, 12, 14, 0, 7, 13,
|
||||
15, 4, 6, 14, 2, 5, 7, 15, 0, 8, 10, 13,
|
||||
1, 9, 11, 0, 2, 8, 10, 15, 1, 3, 9, 11,
|
||||
4, 12, 14, 5, 8, 13, 15, 4, 6, 12, 14,
|
||||
5, 7, 10, 13, 15])
|
||||
graph = coo_matrix((data, (rows,cols))).tocsr()
|
||||
perm = reverse_cuthill_mckee(graph)
|
||||
correct_perm = np.array([12, 14, 4, 6, 10, 8, 2, 15,
|
||||
0, 13, 7, 5, 9, 11, 1, 3])
|
||||
assert_equal(perm, correct_perm)
|
||||
|
||||
|
||||
def test_graph_structural_rank():
|
||||
# Test square matrix #1
|
||||
A = csc_matrix([[1, 1, 0],
|
||||
[1, 0, 1],
|
||||
[0, 1, 0]])
|
||||
assert_equal(structural_rank(A), 3)
|
||||
|
||||
# Test square matrix #2
|
||||
rows = np.array([0,0,0,0,0,1,1,2,2,3,3,3,3,3,3,4,4,5,5,6,6,7,7])
|
||||
cols = np.array([0,1,2,3,4,2,5,2,6,0,1,3,5,6,7,4,5,5,6,2,6,2,4])
|
||||
data = np.ones_like(rows)
|
||||
B = coo_matrix((data,(rows,cols)), shape=(8,8))
|
||||
assert_equal(structural_rank(B), 6)
|
||||
|
||||
#Test non-square matrix
|
||||
C = csc_matrix([[1, 0, 2, 0],
|
||||
[2, 0, 4, 0]])
|
||||
assert_equal(structural_rank(C), 2)
|
||||
|
||||
#Test tall matrix
|
||||
assert_equal(structural_rank(C.T), 2)
|
||||
@ -0,0 +1,454 @@
|
||||
from io import StringIO
|
||||
import warnings
|
||||
import numpy as np
|
||||
from numpy.testing import assert_array_almost_equal, assert_array_equal, assert_allclose
|
||||
from pytest import raises as assert_raises
|
||||
from scipy.sparse.csgraph import (shortest_path, dijkstra, johnson,
|
||||
bellman_ford, construct_dist_matrix, yen,
|
||||
NegativeCycleError)
|
||||
import scipy.sparse
|
||||
from scipy.io import mmread
|
||||
import pytest
|
||||
|
||||
directed_G = np.array([[0, 3, 3, 0, 0],
|
||||
[0, 0, 0, 2, 4],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, 0, 0, 0],
|
||||
[2, 0, 0, 2, 0]], dtype=float)
|
||||
|
||||
undirected_G = np.array([[0, 3, 3, 1, 2],
|
||||
[3, 0, 0, 2, 4],
|
||||
[3, 0, 0, 0, 0],
|
||||
[1, 2, 0, 0, 2],
|
||||
[2, 4, 0, 2, 0]], dtype=float)
|
||||
|
||||
unweighted_G = (directed_G > 0).astype(float)
|
||||
|
||||
directed_SP = [[0, 3, 3, 5, 7],
|
||||
[3, 0, 6, 2, 4],
|
||||
[np.inf, np.inf, 0, np.inf, np.inf],
|
||||
[1, 4, 4, 0, 8],
|
||||
[2, 5, 5, 2, 0]]
|
||||
|
||||
directed_2SP_0_to_3 = [[-9999, 0, -9999, 1, -9999],
|
||||
[-9999, 0, -9999, 4, 1]]
|
||||
|
||||
directed_sparse_zero_G = scipy.sparse.csr_matrix(([0, 1, 2, 3, 1],
|
||||
([0, 1, 2, 3, 4],
|
||||
[1, 2, 0, 4, 3])),
|
||||
shape = (5, 5))
|
||||
|
||||
directed_sparse_zero_SP = [[0, 0, 1, np.inf, np.inf],
|
||||
[3, 0, 1, np.inf, np.inf],
|
||||
[2, 2, 0, np.inf, np.inf],
|
||||
[np.inf, np.inf, np.inf, 0, 3],
|
||||
[np.inf, np.inf, np.inf, 1, 0]]
|
||||
|
||||
undirected_sparse_zero_G = scipy.sparse.csr_matrix(([0, 0, 1, 1, 2, 2, 1, 1],
|
||||
([0, 1, 1, 2, 2, 0, 3, 4],
|
||||
[1, 0, 2, 1, 0, 2, 4, 3])),
|
||||
shape = (5, 5))
|
||||
|
||||
undirected_sparse_zero_SP = [[0, 0, 1, np.inf, np.inf],
|
||||
[0, 0, 1, np.inf, np.inf],
|
||||
[1, 1, 0, np.inf, np.inf],
|
||||
[np.inf, np.inf, np.inf, 0, 1],
|
||||
[np.inf, np.inf, np.inf, 1, 0]]
|
||||
|
||||
directed_pred = np.array([[-9999, 0, 0, 1, 1],
|
||||
[3, -9999, 0, 1, 1],
|
||||
[-9999, -9999, -9999, -9999, -9999],
|
||||
[3, 0, 0, -9999, 1],
|
||||
[4, 0, 0, 4, -9999]], dtype=float)
|
||||
|
||||
undirected_SP = np.array([[0, 3, 3, 1, 2],
|
||||
[3, 0, 6, 2, 4],
|
||||
[3, 6, 0, 4, 5],
|
||||
[1, 2, 4, 0, 2],
|
||||
[2, 4, 5, 2, 0]], dtype=float)
|
||||
|
||||
undirected_SP_limit_2 = np.array([[0, np.inf, np.inf, 1, 2],
|
||||
[np.inf, 0, np.inf, 2, np.inf],
|
||||
[np.inf, np.inf, 0, np.inf, np.inf],
|
||||
[1, 2, np.inf, 0, 2],
|
||||
[2, np.inf, np.inf, 2, 0]], dtype=float)
|
||||
|
||||
undirected_SP_limit_0 = np.ones((5, 5), dtype=float) - np.eye(5)
|
||||
undirected_SP_limit_0[undirected_SP_limit_0 > 0] = np.inf
|
||||
|
||||
undirected_pred = np.array([[-9999, 0, 0, 0, 0],
|
||||
[1, -9999, 0, 1, 1],
|
||||
[2, 0, -9999, 0, 0],
|
||||
[3, 3, 0, -9999, 3],
|
||||
[4, 4, 0, 4, -9999]], dtype=float)
|
||||
|
||||
directed_negative_weighted_G = np.array([[0, 0, 0],
|
||||
[-1, 0, 0],
|
||||
[0, -1, 0]], dtype=float)
|
||||
|
||||
directed_negative_weighted_SP = np.array([[0, np.inf, np.inf],
|
||||
[-1, 0, np.inf],
|
||||
[-2, -1, 0]], dtype=float)
|
||||
|
||||
methods = ['auto', 'FW', 'D', 'BF', 'J']
|
||||
|
||||
|
||||
def test_dijkstra_limit():
|
||||
limits = [0, 2, np.inf]
|
||||
results = [undirected_SP_limit_0,
|
||||
undirected_SP_limit_2,
|
||||
undirected_SP]
|
||||
|
||||
def check(limit, result):
|
||||
SP = dijkstra(undirected_G, directed=False, limit=limit)
|
||||
assert_array_almost_equal(SP, result)
|
||||
|
||||
for limit, result in zip(limits, results):
|
||||
check(limit, result)
|
||||
|
||||
|
||||
def test_directed():
|
||||
def check(method):
|
||||
SP = shortest_path(directed_G, method=method, directed=True,
|
||||
overwrite=False)
|
||||
assert_array_almost_equal(SP, directed_SP)
|
||||
|
||||
for method in methods:
|
||||
check(method)
|
||||
|
||||
|
||||
def test_undirected():
|
||||
def check(method, directed_in):
|
||||
if directed_in:
|
||||
SP1 = shortest_path(directed_G, method=method, directed=False,
|
||||
overwrite=False)
|
||||
assert_array_almost_equal(SP1, undirected_SP)
|
||||
else:
|
||||
SP2 = shortest_path(undirected_G, method=method, directed=True,
|
||||
overwrite=False)
|
||||
assert_array_almost_equal(SP2, undirected_SP)
|
||||
|
||||
for method in methods:
|
||||
for directed_in in (True, False):
|
||||
check(method, directed_in)
|
||||
|
||||
|
||||
def test_directed_sparse_zero():
|
||||
# test directed sparse graph with zero-weight edge and two connected components
|
||||
def check(method):
|
||||
SP = shortest_path(directed_sparse_zero_G, method=method, directed=True,
|
||||
overwrite=False)
|
||||
assert_array_almost_equal(SP, directed_sparse_zero_SP)
|
||||
|
||||
for method in methods:
|
||||
check(method)
|
||||
|
||||
|
||||
def test_undirected_sparse_zero():
|
||||
def check(method, directed_in):
|
||||
if directed_in:
|
||||
SP1 = shortest_path(directed_sparse_zero_G, method=method, directed=False,
|
||||
overwrite=False)
|
||||
assert_array_almost_equal(SP1, undirected_sparse_zero_SP)
|
||||
else:
|
||||
SP2 = shortest_path(undirected_sparse_zero_G, method=method, directed=True,
|
||||
overwrite=False)
|
||||
assert_array_almost_equal(SP2, undirected_sparse_zero_SP)
|
||||
|
||||
for method in methods:
|
||||
for directed_in in (True, False):
|
||||
check(method, directed_in)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('directed, SP_ans',
|
||||
((True, directed_SP),
|
||||
(False, undirected_SP)))
|
||||
@pytest.mark.parametrize('indices', ([0, 2, 4], [0, 4], [3, 4], [0, 0]))
|
||||
def test_dijkstra_indices_min_only(directed, SP_ans, indices):
|
||||
SP_ans = np.array(SP_ans)
|
||||
indices = np.array(indices, dtype=np.int64)
|
||||
min_ind_ans = indices[np.argmin(SP_ans[indices, :], axis=0)]
|
||||
min_d_ans = np.zeros(SP_ans.shape[0], SP_ans.dtype)
|
||||
for k in range(SP_ans.shape[0]):
|
||||
min_d_ans[k] = SP_ans[min_ind_ans[k], k]
|
||||
min_ind_ans[np.isinf(min_d_ans)] = -9999
|
||||
|
||||
SP, pred, sources = dijkstra(directed_G,
|
||||
directed=directed,
|
||||
indices=indices,
|
||||
min_only=True,
|
||||
return_predecessors=True)
|
||||
assert_array_almost_equal(SP, min_d_ans)
|
||||
assert_array_equal(min_ind_ans, sources)
|
||||
SP = dijkstra(directed_G,
|
||||
directed=directed,
|
||||
indices=indices,
|
||||
min_only=True,
|
||||
return_predecessors=False)
|
||||
assert_array_almost_equal(SP, min_d_ans)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('n', (10, 100, 1000))
|
||||
def test_dijkstra_min_only_random(n):
|
||||
np.random.seed(1234)
|
||||
data = scipy.sparse.rand(n, n, density=0.5, format='lil',
|
||||
random_state=42, dtype=np.float64)
|
||||
data.setdiag(np.zeros(n, dtype=np.bool_))
|
||||
# choose some random vertices
|
||||
v = np.arange(n)
|
||||
np.random.shuffle(v)
|
||||
indices = v[:int(n*.1)]
|
||||
ds, pred, sources = dijkstra(data,
|
||||
directed=True,
|
||||
indices=indices,
|
||||
min_only=True,
|
||||
return_predecessors=True)
|
||||
for k in range(n):
|
||||
p = pred[k]
|
||||
s = sources[k]
|
||||
while p != -9999:
|
||||
assert sources[p] == s
|
||||
p = pred[p]
|
||||
|
||||
|
||||
def test_dijkstra_random():
|
||||
# reproduces the hang observed in gh-17782
|
||||
n = 10
|
||||
indices = [0, 4, 4, 5, 7, 9, 0, 6, 2, 3, 7, 9, 1, 2, 9, 2, 5, 6]
|
||||
indptr = [0, 0, 2, 5, 6, 7, 8, 12, 15, 18, 18]
|
||||
data = [0.33629, 0.40458, 0.47493, 0.42757, 0.11497, 0.91653, 0.69084,
|
||||
0.64979, 0.62555, 0.743, 0.01724, 0.99945, 0.31095, 0.15557,
|
||||
0.02439, 0.65814, 0.23478, 0.24072]
|
||||
graph = scipy.sparse.csr_matrix((data, indices, indptr), shape=(n, n))
|
||||
dijkstra(graph, directed=True, return_predecessors=True)
|
||||
|
||||
|
||||
def test_gh_17782_segfault():
|
||||
text = """%%MatrixMarket matrix coordinate real general
|
||||
84 84 22
|
||||
2 1 4.699999809265137e+00
|
||||
6 14 1.199999973177910e-01
|
||||
9 6 1.199999973177910e-01
|
||||
10 16 2.012000083923340e+01
|
||||
11 10 1.422000026702881e+01
|
||||
12 1 9.645999908447266e+01
|
||||
13 18 2.012000083923340e+01
|
||||
14 13 4.679999828338623e+00
|
||||
15 11 1.199999973177910e-01
|
||||
16 12 1.199999973177910e-01
|
||||
18 15 1.199999973177910e-01
|
||||
32 2 2.299999952316284e+00
|
||||
33 20 6.000000000000000e+00
|
||||
33 32 5.000000000000000e+00
|
||||
36 9 3.720000028610229e+00
|
||||
36 37 3.720000028610229e+00
|
||||
36 38 3.720000028610229e+00
|
||||
37 44 8.159999847412109e+00
|
||||
38 32 7.903999328613281e+01
|
||||
43 20 2.400000000000000e+01
|
||||
43 33 4.000000000000000e+00
|
||||
44 43 6.028000259399414e+01
|
||||
"""
|
||||
data = mmread(StringIO(text))
|
||||
dijkstra(data, directed=True, return_predecessors=True)
|
||||
|
||||
|
||||
def test_shortest_path_indices():
|
||||
indices = np.arange(4)
|
||||
|
||||
def check(func, indshape):
|
||||
outshape = indshape + (5,)
|
||||
SP = func(directed_G, directed=False,
|
||||
indices=indices.reshape(indshape))
|
||||
assert_array_almost_equal(SP, undirected_SP[indices].reshape(outshape))
|
||||
|
||||
for indshape in [(4,), (4, 1), (2, 2)]:
|
||||
for func in (dijkstra, bellman_ford, johnson, shortest_path):
|
||||
check(func, indshape)
|
||||
|
||||
assert_raises(ValueError, shortest_path, directed_G, method='FW',
|
||||
indices=indices)
|
||||
|
||||
|
||||
def test_predecessors():
|
||||
SP_res = {True: directed_SP,
|
||||
False: undirected_SP}
|
||||
pred_res = {True: directed_pred,
|
||||
False: undirected_pred}
|
||||
|
||||
def check(method, directed):
|
||||
SP, pred = shortest_path(directed_G, method, directed=directed,
|
||||
overwrite=False,
|
||||
return_predecessors=True)
|
||||
assert_array_almost_equal(SP, SP_res[directed])
|
||||
assert_array_almost_equal(pred, pred_res[directed])
|
||||
|
||||
for method in methods:
|
||||
for directed in (True, False):
|
||||
check(method, directed)
|
||||
|
||||
|
||||
def test_construct_shortest_path():
|
||||
def check(method, directed):
|
||||
SP1, pred = shortest_path(directed_G,
|
||||
directed=directed,
|
||||
overwrite=False,
|
||||
return_predecessors=True)
|
||||
SP2 = construct_dist_matrix(directed_G, pred, directed=directed)
|
||||
assert_array_almost_equal(SP1, SP2)
|
||||
|
||||
for method in methods:
|
||||
for directed in (True, False):
|
||||
check(method, directed)
|
||||
|
||||
|
||||
def test_unweighted_path():
|
||||
def check(method, directed):
|
||||
SP1 = shortest_path(directed_G,
|
||||
directed=directed,
|
||||
overwrite=False,
|
||||
unweighted=True)
|
||||
SP2 = shortest_path(unweighted_G,
|
||||
directed=directed,
|
||||
overwrite=False,
|
||||
unweighted=False)
|
||||
assert_array_almost_equal(SP1, SP2)
|
||||
|
||||
for method in methods:
|
||||
for directed in (True, False):
|
||||
check(method, directed)
|
||||
|
||||
|
||||
def test_negative_cycles():
|
||||
# create a small graph with a negative cycle
|
||||
graph = np.ones([5, 5])
|
||||
graph.flat[::6] = 0
|
||||
graph[1, 2] = -2
|
||||
|
||||
def check(method, directed):
|
||||
assert_raises(NegativeCycleError, shortest_path, graph, method,
|
||||
directed)
|
||||
|
||||
for directed in (True, False):
|
||||
for method in ['FW', 'J', 'BF']:
|
||||
check(method, directed)
|
||||
|
||||
assert_raises(NegativeCycleError, yen, graph, 0, 1, 1,
|
||||
directed=directed)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("method", ['FW', 'J', 'BF'])
|
||||
def test_negative_weights(method):
|
||||
SP = shortest_path(directed_negative_weighted_G, method, directed=True)
|
||||
assert_allclose(SP, directed_negative_weighted_SP, atol=1e-10)
|
||||
|
||||
|
||||
def test_masked_input():
|
||||
np.ma.masked_equal(directed_G, 0)
|
||||
|
||||
def check(method):
|
||||
SP = shortest_path(directed_G, method=method, directed=True,
|
||||
overwrite=False)
|
||||
assert_array_almost_equal(SP, directed_SP)
|
||||
|
||||
for method in methods:
|
||||
check(method)
|
||||
|
||||
|
||||
def test_overwrite():
|
||||
G = np.array([[0, 3, 3, 1, 2],
|
||||
[3, 0, 0, 2, 4],
|
||||
[3, 0, 0, 0, 0],
|
||||
[1, 2, 0, 0, 2],
|
||||
[2, 4, 0, 2, 0]], dtype=float)
|
||||
foo = G.copy()
|
||||
shortest_path(foo, overwrite=False)
|
||||
assert_array_equal(foo, G)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('method', methods)
|
||||
def test_buffer(method):
|
||||
# Smoke test that sparse matrices with read-only buffers (e.g., those from
|
||||
# joblib workers) do not cause::
|
||||
#
|
||||
# ValueError: buffer source array is read-only
|
||||
#
|
||||
G = scipy.sparse.csr_matrix([[1.]])
|
||||
G.data.flags['WRITEABLE'] = False
|
||||
shortest_path(G, method=method)
|
||||
|
||||
|
||||
def test_NaN_warnings():
|
||||
with warnings.catch_warnings(record=True) as record:
|
||||
shortest_path(np.array([[0, 1], [np.nan, 0]]))
|
||||
for r in record:
|
||||
assert r.category is not RuntimeWarning
|
||||
|
||||
|
||||
def test_sparse_matrices():
|
||||
# Test that using lil,csr and csc sparse matrix do not cause error
|
||||
G_dense = np.array([[0, 3, 0, 0, 0],
|
||||
[0, 0, -1, 0, 0],
|
||||
[0, 0, 0, 2, 0],
|
||||
[0, 0, 0, 0, 4],
|
||||
[0, 0, 0, 0, 0]], dtype=float)
|
||||
SP = shortest_path(G_dense)
|
||||
G_csr = scipy.sparse.csr_matrix(G_dense)
|
||||
G_csc = scipy.sparse.csc_matrix(G_dense)
|
||||
G_lil = scipy.sparse.lil_matrix(G_dense)
|
||||
assert_array_almost_equal(SP, shortest_path(G_csr))
|
||||
assert_array_almost_equal(SP, shortest_path(G_csc))
|
||||
assert_array_almost_equal(SP, shortest_path(G_lil))
|
||||
|
||||
|
||||
def test_yen_directed():
|
||||
distances, predecessors = yen(
|
||||
directed_G,
|
||||
source=0,
|
||||
sink=3,
|
||||
K=2,
|
||||
return_predecessors=True
|
||||
)
|
||||
assert_allclose(distances, [5., 9.])
|
||||
assert_allclose(predecessors, directed_2SP_0_to_3)
|
||||
|
||||
|
||||
def test_yen_undirected():
|
||||
distances = yen(
|
||||
undirected_G,
|
||||
source=0,
|
||||
sink=3,
|
||||
K=4,
|
||||
)
|
||||
assert_allclose(distances, [1., 4., 5., 8.])
|
||||
|
||||
def test_yen_unweighted():
|
||||
# Ask for more paths than there are, verify only the available paths are returned
|
||||
distances, predecessors = yen(
|
||||
directed_G,
|
||||
source=0,
|
||||
sink=3,
|
||||
K=4,
|
||||
unweighted=True,
|
||||
return_predecessors=True,
|
||||
)
|
||||
assert_allclose(distances, [2., 3.])
|
||||
assert_allclose(predecessors, directed_2SP_0_to_3)
|
||||
|
||||
def test_yen_no_paths():
|
||||
distances = yen(
|
||||
directed_G,
|
||||
source=2,
|
||||
sink=3,
|
||||
K=1,
|
||||
)
|
||||
assert distances.size == 0
|
||||
|
||||
def test_yen_negative_weights():
|
||||
distances = yen(
|
||||
directed_negative_weighted_G,
|
||||
source=2,
|
||||
sink=0,
|
||||
K=1,
|
||||
)
|
||||
assert_allclose(distances, [-2.])
|
||||
@ -0,0 +1,66 @@
|
||||
"""Test the minimum spanning tree function"""
|
||||
import numpy as np
|
||||
from numpy.testing import assert_
|
||||
import numpy.testing as npt
|
||||
from scipy.sparse import csr_matrix
|
||||
from scipy.sparse.csgraph import minimum_spanning_tree
|
||||
|
||||
|
||||
def test_minimum_spanning_tree():
|
||||
|
||||
# Create a graph with two connected components.
|
||||
graph = [[0,1,0,0,0],
|
||||
[1,0,0,0,0],
|
||||
[0,0,0,8,5],
|
||||
[0,0,8,0,1],
|
||||
[0,0,5,1,0]]
|
||||
graph = np.asarray(graph)
|
||||
|
||||
# Create the expected spanning tree.
|
||||
expected = [[0,1,0,0,0],
|
||||
[0,0,0,0,0],
|
||||
[0,0,0,0,5],
|
||||
[0,0,0,0,1],
|
||||
[0,0,0,0,0]]
|
||||
expected = np.asarray(expected)
|
||||
|
||||
# Ensure minimum spanning tree code gives this expected output.
|
||||
csgraph = csr_matrix(graph)
|
||||
mintree = minimum_spanning_tree(csgraph)
|
||||
mintree_array = mintree.toarray()
|
||||
npt.assert_array_equal(mintree_array, expected,
|
||||
'Incorrect spanning tree found.')
|
||||
|
||||
# Ensure that the original graph was not modified.
|
||||
npt.assert_array_equal(csgraph.toarray(), graph,
|
||||
'Original graph was modified.')
|
||||
|
||||
# Now let the algorithm modify the csgraph in place.
|
||||
mintree = minimum_spanning_tree(csgraph, overwrite=True)
|
||||
npt.assert_array_equal(mintree.toarray(), expected,
|
||||
'Graph was not properly modified to contain MST.')
|
||||
|
||||
np.random.seed(1234)
|
||||
for N in (5, 10, 15, 20):
|
||||
|
||||
# Create a random graph.
|
||||
graph = 3 + np.random.random((N, N))
|
||||
csgraph = csr_matrix(graph)
|
||||
|
||||
# The spanning tree has at most N - 1 edges.
|
||||
mintree = minimum_spanning_tree(csgraph)
|
||||
assert_(mintree.nnz < N)
|
||||
|
||||
# Set the sub diagonal to 1 to create a known spanning tree.
|
||||
idx = np.arange(N-1)
|
||||
graph[idx,idx+1] = 1
|
||||
csgraph = csr_matrix(graph)
|
||||
mintree = minimum_spanning_tree(csgraph)
|
||||
|
||||
# We expect to see this pattern in the spanning tree and otherwise
|
||||
# have this zero.
|
||||
expected = np.zeros((N, N))
|
||||
expected[idx, idx+1] = 1
|
||||
|
||||
npt.assert_array_equal(mintree.toarray(), expected,
|
||||
'Incorrect spanning tree found.')
|
||||
@ -0,0 +1,81 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
from numpy.testing import assert_array_almost_equal
|
||||
from scipy.sparse import csr_array
|
||||
from scipy.sparse.csgraph import (breadth_first_tree, depth_first_tree,
|
||||
csgraph_to_dense, csgraph_from_dense)
|
||||
|
||||
|
||||
def test_graph_breadth_first():
|
||||
csgraph = np.array([[0, 1, 2, 0, 0],
|
||||
[1, 0, 0, 0, 3],
|
||||
[2, 0, 0, 7, 0],
|
||||
[0, 0, 7, 0, 1],
|
||||
[0, 3, 0, 1, 0]])
|
||||
csgraph = csgraph_from_dense(csgraph, null_value=0)
|
||||
|
||||
bfirst = np.array([[0, 1, 2, 0, 0],
|
||||
[0, 0, 0, 0, 3],
|
||||
[0, 0, 0, 7, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0]])
|
||||
|
||||
for directed in [True, False]:
|
||||
bfirst_test = breadth_first_tree(csgraph, 0, directed)
|
||||
assert_array_almost_equal(csgraph_to_dense(bfirst_test),
|
||||
bfirst)
|
||||
|
||||
|
||||
def test_graph_depth_first():
|
||||
csgraph = np.array([[0, 1, 2, 0, 0],
|
||||
[1, 0, 0, 0, 3],
|
||||
[2, 0, 0, 7, 0],
|
||||
[0, 0, 7, 0, 1],
|
||||
[0, 3, 0, 1, 0]])
|
||||
csgraph = csgraph_from_dense(csgraph, null_value=0)
|
||||
|
||||
dfirst = np.array([[0, 1, 0, 0, 0],
|
||||
[0, 0, 0, 0, 3],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 7, 0, 0],
|
||||
[0, 0, 0, 1, 0]])
|
||||
|
||||
for directed in [True, False]:
|
||||
dfirst_test = depth_first_tree(csgraph, 0, directed)
|
||||
assert_array_almost_equal(csgraph_to_dense(dfirst_test),
|
||||
dfirst)
|
||||
|
||||
|
||||
def test_graph_breadth_first_trivial_graph():
|
||||
csgraph = np.array([[0]])
|
||||
csgraph = csgraph_from_dense(csgraph, null_value=0)
|
||||
|
||||
bfirst = np.array([[0]])
|
||||
|
||||
for directed in [True, False]:
|
||||
bfirst_test = breadth_first_tree(csgraph, 0, directed)
|
||||
assert_array_almost_equal(csgraph_to_dense(bfirst_test),
|
||||
bfirst)
|
||||
|
||||
|
||||
def test_graph_depth_first_trivial_graph():
|
||||
csgraph = np.array([[0]])
|
||||
csgraph = csgraph_from_dense(csgraph, null_value=0)
|
||||
|
||||
bfirst = np.array([[0]])
|
||||
|
||||
for directed in [True, False]:
|
||||
bfirst_test = depth_first_tree(csgraph, 0, directed)
|
||||
assert_array_almost_equal(csgraph_to_dense(bfirst_test),
|
||||
bfirst)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('directed', [True, False])
|
||||
@pytest.mark.parametrize('tree_func', [breadth_first_tree, depth_first_tree])
|
||||
def test_int64_indices(tree_func, directed):
|
||||
# See https://github.com/scipy/scipy/issues/18716
|
||||
g = csr_array(([1], np.array([[0], [1]], dtype=np.int64)), shape=(2, 2))
|
||||
assert g.indices.dtype == np.int64
|
||||
tree = tree_func(g, 0, directed=directed)
|
||||
assert_array_almost_equal(csgraph_to_dense(tree), [[0, 1], [0, 0]])
|
||||
|
||||
Reference in New Issue
Block a user