Fixed database typo and removed unnecessary class identifier.

This commit is contained in:
Batuhan Berk Başoğlu 2020-10-14 10:10:37 -04:00
parent 00ad49a143
commit 45fb349a7d
5098 changed files with 952558 additions and 85 deletions

View file

@ -0,0 +1,195 @@
r"""
Compressed sparse graph routines (:mod:`scipy.sparse.csgraph`)
==============================================================
.. currentmodule:: scipy.sparse.csgraph
Fast graph algorithms based on sparse matrix representations.
Contents
--------
.. autosummary::
:toctree: generated/
connected_components -- determine connected components of a graph
laplacian -- compute the laplacian of a graph
shortest_path -- compute the shortest path between points on a positive graph
dijkstra -- use Dijkstra's algorithm for shortest path
floyd_warshall -- use the Floyd-Warshall algorithm for shortest path
bellman_ford -- use the Bellman-Ford algorithm for shortest path
johnson -- use Johnson's algorithm for shortest path
breadth_first_order -- compute a breadth-first order of nodes
depth_first_order -- compute a depth-first order of nodes
breadth_first_tree -- construct the breadth-first tree from a given node
depth_first_tree -- construct a depth-first tree from a given node
minimum_spanning_tree -- construct the minimum spanning tree of a graph
reverse_cuthill_mckee -- compute permutation for reverse Cuthill-McKee ordering
maximum_flow -- solve the maximum flow problem for a graph
maximum_bipartite_matching -- compute a maximum matching of a bipartite graph
structural_rank -- compute the structural rank of a graph
NegativeCycleError
.. autosummary::
:toctree: generated/
construct_dist_matrix
csgraph_from_dense
csgraph_from_masked
csgraph_masked_from_dense
csgraph_to_dense
csgraph_to_masked
reconstruct_path
Graph Representations
---------------------
This module uses graphs which are stored in a matrix format. A
graph with N nodes can be represented by an (N x N) adjacency matrix G.
If there is a connection from node i to node j, then G[i, j] = w, where
w is the weight of the connection. For nodes i and j which are
not connected, the value depends on the representation:
- for dense array representations, non-edges are represented by
G[i, j] = 0, infinity, or NaN.
- for dense masked representations (of type np.ma.MaskedArray), non-edges
are represented by masked values. This can be useful when graphs with
zero-weight edges are desired.
- for sparse array representations, non-edges are represented by
non-entries in the matrix. This sort of sparse representation also
allows for edges with zero weights.
As a concrete example, imagine that you would like to represent the following
undirected graph::
G
(0)
/ \
1 2
/ \
(2) (1)
This graph has three nodes, where node 0 and 1 are connected by an edge of
weight 2, and nodes 0 and 2 are connected by an edge of weight 1.
We can construct the dense, masked, and sparse representations as follows,
keeping in mind that an undirected graph is represented by a symmetric matrix::
>>> G_dense = np.array([[0, 2, 1],
... [2, 0, 0],
... [1, 0, 0]])
>>> G_masked = np.ma.masked_values(G_dense, 0)
>>> from scipy.sparse import csr_matrix
>>> G_sparse = csr_matrix(G_dense)
This becomes more difficult when zero edges are significant. For example,
consider the situation when we slightly modify the above graph::
G2
(0)
/ \
0 2
/ \
(2) (1)
This is identical to the previous graph, except nodes 0 and 2 are connected
by an edge of zero weight. In this case, the dense representation above
leads to ambiguities: how can non-edges be represented if zero is a meaningful
value? In this case, either a masked or sparse representation must be used
to eliminate the ambiguity::
>>> G2_data = np.array([[np.inf, 2, 0 ],
... [2, np.inf, np.inf],
... [0, np.inf, np.inf]])
>>> G2_masked = np.ma.masked_invalid(G2_data)
>>> from scipy.sparse.csgraph import csgraph_from_dense
>>> # G2_sparse = csr_matrix(G2_data) would give the wrong result
>>> G2_sparse = csgraph_from_dense(G2_data, null_value=np.inf)
>>> G2_sparse.data
array([ 2., 0., 2., 0.])
Here we have used a utility routine from the csgraph submodule in order to
convert the dense representation to a sparse representation which can be
understood by the algorithms in submodule. By viewing the data array, we
can see that the zero values are explicitly encoded in the graph.
Directed vs. undirected
^^^^^^^^^^^^^^^^^^^^^^^
Matrices may represent either directed or undirected graphs. This is
specified throughout the csgraph module by a boolean keyword. Graphs are
assumed to be directed by default. In a directed graph, traversal from node
i to node j can be accomplished over the edge G[i, j], but not the edge
G[j, i]. Consider the following dense graph::
>>> G_dense = np.array([[0, 1, 0],
... [2, 0, 3],
... [0, 4, 0]])
When ``directed=True`` we get the graph::
---1--> ---3-->
(0) (1) (2)
<--2--- <--4---
In a non-directed graph, traversal from node i to node j can be
accomplished over either G[i, j] or G[j, i]. If both edges are not null,
and the two have unequal weights, then the smaller of the two is used.
So for the same graph, when ``directed=False`` we get the graph::
(0)--1--(1)--2--(2)
Note that a symmetric matrix will represent an undirected graph, regardless
of whether the 'directed' keyword is set to True or False. In this case,
using ``directed=True`` generally leads to more efficient computation.
The routines in this module accept as input either scipy.sparse representations
(csr, csc, or lil format), masked representations, or dense representations
with non-edges indicated by zeros, infinities, and NaN entries.
"""
__docformat__ = "restructuredtext en"
__all__ = ['connected_components',
'laplacian',
'shortest_path',
'floyd_warshall',
'dijkstra',
'bellman_ford',
'johnson',
'breadth_first_order',
'depth_first_order',
'breadth_first_tree',
'depth_first_tree',
'minimum_spanning_tree',
'reverse_cuthill_mckee',
'maximum_flow',
'maximum_bipartite_matching',
'structural_rank',
'construct_dist_matrix',
'reconstruct_path',
'csgraph_masked_from_dense',
'csgraph_from_dense',
'csgraph_from_masked',
'csgraph_to_dense',
'csgraph_to_masked',
'NegativeCycleError']
from ._laplacian import laplacian
from ._shortest_path import shortest_path, floyd_warshall, dijkstra,\
bellman_ford, johnson, NegativeCycleError
from ._traversal import breadth_first_order, depth_first_order, \
breadth_first_tree, depth_first_tree, connected_components
from ._min_spanning_tree import minimum_spanning_tree
from ._flow import maximum_flow
from ._matching import maximum_bipartite_matching
from ._reordering import reverse_cuthill_mckee, structural_rank
from ._tools import construct_dist_matrix, reconstruct_path,\
csgraph_from_dense, csgraph_to_dense, csgraph_masked_from_dense,\
csgraph_from_masked, csgraph_to_masked
from scipy._lib._testutils import PytestTester
test = PytestTester(__name__)
del PytestTester

View file

@ -0,0 +1,126 @@
"""
Laplacian of a compressed-sparse graph
"""
# Authors: Aric Hagberg <hagberg@lanl.gov>
# Gael Varoquaux <gael.varoquaux@normalesup.org>
# Jake Vanderplas <vanderplas@astro.washington.edu>
# License: BSD
import numpy as np
from scipy.sparse import isspmatrix
###############################################################################
# Graph laplacian
def laplacian(csgraph, normed=False, return_diag=False, use_out_degree=False):
"""
Return the Laplacian matrix of a directed graph.
Parameters
----------
csgraph : array_like or sparse matrix, 2 dimensions
compressed-sparse graph, with shape (N, N).
normed : bool, optional
If True, then compute symmetric normalized Laplacian.
return_diag : bool, optional
If True, then also return an array related to vertex degrees.
use_out_degree : bool, optional
If True, then use out-degree instead of in-degree.
This distinction matters only if the graph is asymmetric.
Default: False.
Returns
-------
lap : ndarray or sparse matrix
The N x N laplacian matrix of csgraph. It will be a NumPy array (dense)
if the input was dense, or a sparse matrix otherwise.
diag : ndarray, optional
The length-N diagonal of the Laplacian matrix.
For the normalized Laplacian, this is the array of square roots
of vertex degrees or 1 if the degree is zero.
Notes
-----
The Laplacian matrix of a graph is sometimes referred to as the
"Kirchoff matrix" or the "admittance matrix", and is useful in many
parts of spectral graph theory. In particular, the eigen-decomposition
of the laplacian matrix can give insight into many properties of the graph.
Examples
--------
>>> from scipy.sparse import csgraph
>>> G = np.arange(5) * np.arange(5)[:, np.newaxis]
>>> G
array([[ 0, 0, 0, 0, 0],
[ 0, 1, 2, 3, 4],
[ 0, 2, 4, 6, 8],
[ 0, 3, 6, 9, 12],
[ 0, 4, 8, 12, 16]])
>>> csgraph.laplacian(G, normed=False)
array([[ 0, 0, 0, 0, 0],
[ 0, 9, -2, -3, -4],
[ 0, -2, 16, -6, -8],
[ 0, -3, -6, 21, -12],
[ 0, -4, -8, -12, 24]])
"""
if csgraph.ndim != 2 or csgraph.shape[0] != csgraph.shape[1]:
raise ValueError('csgraph must be a square matrix or array')
if normed and (np.issubdtype(csgraph.dtype, np.signedinteger)
or np.issubdtype(csgraph.dtype, np.uint)):
csgraph = csgraph.astype(float)
create_lap = _laplacian_sparse if isspmatrix(csgraph) else _laplacian_dense
degree_axis = 1 if use_out_degree else 0
lap, d = create_lap(csgraph, normed=normed, axis=degree_axis)
if return_diag:
return lap, d
return lap
def _setdiag_dense(A, d):
A.flat[::len(d)+1] = d
def _laplacian_sparse(graph, normed=False, axis=0):
if graph.format in ('lil', 'dok'):
m = graph.tocoo()
needs_copy = False
else:
m = graph
needs_copy = True
w = m.sum(axis=axis).getA1() - m.diagonal()
if normed:
m = m.tocoo(copy=needs_copy)
isolated_node_mask = (w == 0)
w = np.where(isolated_node_mask, 1, np.sqrt(w))
m.data /= w[m.row]
m.data /= w[m.col]
m.data *= -1
m.setdiag(1 - isolated_node_mask)
else:
if m.format == 'dia':
m = m.copy()
else:
m = m.tocoo(copy=needs_copy)
m.data *= -1
m.setdiag(w)
return m, w
def _laplacian_dense(graph, normed=False, axis=0):
m = np.array(graph)
np.fill_diagonal(m, 0)
w = m.sum(axis=axis)
if normed:
isolated_node_mask = (w == 0)
w = np.where(isolated_node_mask, 1, np.sqrt(w))
m /= w
m /= w[:, np.newaxis]
m *= -1
_setdiag_dense(m, 1 - isolated_node_mask)
else:
m *= -1
_setdiag_dense(m, w)
return m, w

View file

@ -0,0 +1,56 @@
import numpy as np
from scipy.sparse import csr_matrix, isspmatrix, isspmatrix_csc
from ._tools import csgraph_to_dense, csgraph_from_dense,\
csgraph_masked_from_dense, csgraph_from_masked
DTYPE = np.float64
def validate_graph(csgraph, directed, dtype=DTYPE,
csr_output=True, dense_output=True,
copy_if_dense=False, copy_if_sparse=False,
null_value_in=0, null_value_out=np.inf,
infinity_null=True, nan_null=True):
"""Routine for validation and conversion of csgraph inputs"""
if not (csr_output or dense_output):
raise ValueError("Internal: dense or csr output must be true")
# if undirected and csc storage, then transposing in-place
# is quicker than later converting to csr.
if (not directed) and isspmatrix_csc(csgraph):
csgraph = csgraph.T
if isspmatrix(csgraph):
if csr_output:
csgraph = csr_matrix(csgraph, dtype=DTYPE, copy=copy_if_sparse)
else:
csgraph = csgraph_to_dense(csgraph, null_value=null_value_out)
elif np.ma.isMaskedArray(csgraph):
if dense_output:
mask = csgraph.mask
csgraph = np.array(csgraph.data, dtype=DTYPE, copy=copy_if_dense)
csgraph[mask] = null_value_out
else:
csgraph = csgraph_from_masked(csgraph)
else:
if dense_output:
csgraph = csgraph_masked_from_dense(csgraph,
copy=copy_if_dense,
null_value=null_value_in,
nan_null=nan_null,
infinity_null=infinity_null)
mask = csgraph.mask
csgraph = np.asarray(csgraph.data, dtype=DTYPE)
csgraph[mask] = null_value_out
else:
csgraph = csgraph_from_dense(csgraph, null_value=null_value_in,
infinity_null=infinity_null,
nan_null=nan_null)
if csgraph.ndim != 2:
raise ValueError("compressed-sparse graph must be 2-D")
if csgraph.shape[0] != csgraph.shape[1]:
raise ValueError("compressed-sparse graph must be shape (N, N)")
return csgraph

View file

@ -0,0 +1,38 @@
def configuration(parent_package='', top_path=None):
import numpy
from numpy.distutils.misc_util import Configuration
config = Configuration('csgraph', parent_package, top_path)
config.add_data_dir('tests')
config.add_extension('_shortest_path',
sources=['_shortest_path.c'],
include_dirs=[numpy.get_include()])
config.add_extension('_traversal',
sources=['_traversal.c'],
include_dirs=[numpy.get_include()])
config.add_extension('_min_spanning_tree',
sources=['_min_spanning_tree.c'],
include_dirs=[numpy.get_include()])
config.add_extension('_matching',
sources=['_matching.c'],
include_dirs=[numpy.get_include()])
config.add_extension('_flow',
sources=['_flow.c'],
include_dirs=[numpy.get_include()])
config.add_extension('_reordering',
sources=['_reordering.c'],
include_dirs=[numpy.get_include()])
config.add_extension('_tools',
sources=['_tools.c'],
include_dirs=[numpy.get_include()])
return config

View file

@ -0,0 +1,99 @@
import numpy as np
from numpy.testing import assert_equal, assert_array_almost_equal
from scipy.sparse import csgraph
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)

View file

@ -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 sqare 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]))

View file

@ -0,0 +1,124 @@
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
def test_raises_on_dense_input():
with pytest.raises(TypeError):
graph = np.array([[0, 1], [0, 0]])
maximum_flow(graph, 0, 1)
def test_raises_on_csc_input():
with pytest.raises(TypeError):
graph = csc_matrix([[0, 1], [0, 0]])
maximum_flow(graph, 0, 1)
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)
def test_raises_when_source_is_sink():
with pytest.raises(ValueError):
graph = csr_matrix([[0, 1], [0, 0]])
maximum_flow(graph, 0, 0)
@pytest.mark.parametrize('source', [-1, 2, 3])
def test_raises_when_source_is_out_of_bounds(source):
with pytest.raises(ValueError):
graph = csr_matrix([[0, 1], [0, 0]])
maximum_flow(graph, source, 1)
@pytest.mark.parametrize('sink', [-1, 2, 3])
def test_raises_when_sink_is_out_of_bounds(sink):
with pytest.raises(ValueError):
graph = csr_matrix([[0, 1], [0, 0]])
maximum_flow(graph, 0, sink)
def test_simple_graph():
# This graph looks as follows:
# (0) --5--> (1)
graph = csr_matrix([[0, 5], [0, 0]])
res = maximum_flow(graph, 0, 1)
assert res.flow_value == 5
expected_residual = np.array([[0, 5], [-5, 0]])
assert_array_equal(res.residual.toarray(), expected_residual)
def test_bottle_neck_graph():
# 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)
assert res.flow_value == 3
expected_residual = np.array([[0, 3, 0], [-3, 0, 3], [0, -3, 0]])
assert_array_equal(res.residual.toarray(), expected_residual)
def test_backwards_flow():
# 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)
assert res.flow_value == 20
expected_residual = 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.residual.toarray(), expected_residual)
def test_example_from_clrs_chapter_26_1():
# 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)
assert res.flow_value == 23
expected_residual = 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.residual.toarray(), expected_residual)
def test_disconnected_graph():
# 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)
assert res.flow_value == 0
expected_residual = np.zeros((4, 4), dtype=np.int32)
assert_array_equal(res.residual.toarray(), expected_residual)

View file

@ -0,0 +1,134 @@
# Author: Gael Varoquaux <gael.varoquaux@normalesup.org>
# Jake Vanderplas <vanderplas@astro.washington.edu>
# License: BSD
import numpy as np
from numpy.testing import assert_allclose, assert_array_almost_equal
from pytest import raises as assert_raises
from scipy import sparse
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 _check_symmetric_graph_laplacian(mat, normed):
if not hasattr(mat, 'shape'):
mat = eval(mat, dict(np=np, sparse=sparse))
if sparse.issparse(mat):
sp_mat = mat
mat = sp_mat.todense()
else:
sp_mat = sparse.csr_matrix(mat)
laplacian = csgraph.laplacian(mat, normed=normed)
n_nodes = mat.shape[0]
if not normed:
assert_array_almost_equal(laplacian.sum(axis=0), np.zeros(n_nodes))
assert_array_almost_equal(laplacian.T, laplacian)
assert_array_almost_equal(laplacian,
csgraph.laplacian(sp_mat, normed=normed).todense())
assert_array_almost_equal(laplacian,
_explicit_laplacian(mat, normed=normed))
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 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)).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 symmetric_mats:
for normed in True, False:
_check_symmetric_graph_laplacian(mat_str, normed)
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 = a.toarray()
assert_allclose(a, b, **kwargs)
def _check_laplacian(A, desired_L, desired_d, normed, use_out_degree):
for arr_type in np.array, sparse.csr_matrix, sparse.coo_matrix:
for t in int, float, complex:
adj = arr_type(A, dtype=t)
L = csgraph.laplacian(adj, normed=normed, return_diag=False,
use_out_degree=use_out_degree)
_assert_allclose_sparse(L, desired_L, atol=1e-12)
L, d = csgraph.laplacian(adj, normed=normed, return_diag=True,
use_out_degree=use_out_degree)
_assert_allclose_sparse(L, desired_L, atol=1e-12)
_assert_allclose_sparse(d, desired_d, atol=1e-12)
def test_asymmetric_laplacian():
# adjacency matrix
A = [[0, 1, 0],
[4, 2, 0],
[0, 0, 0]]
# Laplacian matrix using out-degree
L = [[1, -1, 0],
[-4, 4, 0],
[0, 0, 0]]
d = [1, 4, 0]
_check_laplacian(A, L, d, normed=False, use_out_degree=True)
# normalized Laplacian matrix using out-degree
L = [[1, -0.5, 0],
[-2, 1, 0],
[0, 0, 0]]
d = [1, 2, 1]
_check_laplacian(A, L, d, normed=True, use_out_degree=True)
# Laplacian matrix using in-degree
L = [[4, -1, 0],
[-4, 1, 0],
[0, 0, 0]]
d = [4, 1, 0]
_check_laplacian(A, L, d, normed=False, use_out_degree=False)
# normalized Laplacian matrix using in-degree
L = [[1, -0.5, 0],
[-2, 1, 0],
[0, 0, 0]]
d = [2, 1, 1]
_check_laplacian(A, L, d, normed=True, use_out_degree=False)
def test_sparse_formats():
for fmt in ('csr', 'csc', 'coo', 'lil', 'dok', 'dia', 'bsr'):
mat = sparse.diags([1, 1], [-1, 1], shape=(4,4), format=fmt)
for normed in True, False:
_check_symmetric_graph_laplacian(mat, normed)

View file

@ -0,0 +1,145 @@
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
def test_raises_on_dense_input():
with pytest.raises(TypeError):
graph = np.array([[0, 1], [0, 0]])
maximum_bipartite_matching(graph)
def test_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_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_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_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_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_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_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_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_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_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)

View file

@ -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)

View file

@ -0,0 +1,334 @@
import numpy as np
from numpy.testing import assert_array_almost_equal, assert_array_equal
from pytest import raises as assert_raises
from scipy.sparse.csgraph import (shortest_path, dijkstra, johnson,
bellman_ford, construct_dist_matrix,
NegativeCycleError)
import scipy.sparse
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_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)
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_shortest_path_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=False,
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_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 method in ['FW', 'J', 'BF']:
for directed in (True, False):
check(method, directed)
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 pytest.warns(None) 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))

View file

@ -0,0 +1,65 @@
"""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)
npt.assert_array_equal(mintree.todense(), expected,
'Incorrect spanning tree found.')
# Ensure that the original graph was not modified.
npt.assert_array_equal(csgraph.todense(), 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.todense(), 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.todense(), expected,
'Incorrect spanning tree found.')

View file

@ -0,0 +1,68 @@
import numpy as np
from numpy.testing import assert_array_almost_equal
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)