def test_addition(self): addition = self.undirected + self.undirected expected = SparseLR(2 * house(), [(np.ones(5), 2 * np.ones(5))]) err = (addition.sparse_mat - expected.sparse_mat).count_nonzero() self.assertEqual(err, 0) x = np.random.rand(5) self.assertAlmostEqual( np.linalg.norm(addition.dot(x) - expected.dot(x)), 0)
class TestSparseLowRank(unittest.TestCase): def setUp(self): self.undirected = SparseLR(house(), [(np.ones(5), np.ones(5))]) self.bipartite = SparseLR(star_wars_villains(), [(np.ones(4), np.ones(3))]) def test_addition(self): addition = self.undirected + self.undirected expected = SparseLR(2 * house(), [(np.ones(5), 2 * np.ones(5))]) err = (addition.sparse_mat - expected.sparse_mat).count_nonzero() self.assertEqual(err, 0) random_vector = np.random.rand(5) self.assertAlmostEqual( np.linalg.norm( addition.dot(random_vector) - expected.dot(random_vector)), 0) def test_product(self): prod = self.undirected.dot(np.ones(5)) self.assertEqual(prod.shape, (5, )) prod = self.bipartite.dot(np.ones(3)) self.assertEqual(np.linalg.norm(prod - np.array([5., 4., 6., 5.])), 0.) prod = self.bipartite.dot(0.5 * np.ones(3)) self.assertEqual(np.linalg.norm(prod - np.array([2.5, 2., 3., 2.5])), 0.) def test_transposition(self): transposed = self.undirected.T error = (self.undirected.sparse_mat - transposed.sparse_mat).data self.assertEqual(abs(error).sum(), 0.) transposed = self.bipartite.T x, y = transposed.low_rank_tuples[0] self.assertTrue((x == np.ones(3)).all()) self.assertTrue((y == np.ones(4)).all()) def test_decomposition(self): eigenvalues, eigenvectors = randomized_eig(self.undirected, n_components=2, which='LM') self.assertEqual(eigenvalues.shape, (2, )) self.assertEqual(eigenvectors.shape, (5, 2)) eigenvalues, eigenvectors = randomized_eig(self.undirected, n_components=2, which='SM') self.assertEqual(eigenvalues.shape, (2, )) self.assertEqual(eigenvectors.shape, (5, 2)) left_sv, sv, right_sv = randomized_svd(self.bipartite, n_components=2) self.assertEqual(left_sv.shape, (4, 2)) self.assertEqual(sv.shape, (2, )) self.assertEqual(right_sv.shape, (2, 3))
class TestSparseLowRank(unittest.TestCase): def setUp(self): """Simple regularized adjacency and biadjacency for tests.""" self.undirected = SparseLR(house(), [(np.ones(5), np.ones(5))]) self.bipartite = SparseLR(star_wars(), [(np.ones(4), np.ones(3))]) def test_init(self): with self.assertRaises(ValueError): SparseLR(house(), [(np.ones(5), np.ones(4))]) with self.assertRaises(ValueError): SparseLR(house(), [(np.ones(4), np.ones(5))]) def test_addition(self): addition = self.undirected + self.undirected expected = SparseLR(2 * house(), [(np.ones(5), 2 * np.ones(5))]) err = (addition.sparse_mat - expected.sparse_mat).count_nonzero() self.assertEqual(err, 0) x = np.random.rand(5) self.assertAlmostEqual( np.linalg.norm(addition.dot(x) - expected.dot(x)), 0) def test_operations(self): adjacency = self.undirected.sparse_mat slr = -self.undirected slr += adjacency slr -= adjacency slr.left_sparse_dot(adjacency) slr.right_sparse_dot(adjacency) slr.astype(float) def test_product(self): prod = self.undirected.dot(np.ones(5)) self.assertEqual(prod.shape, (5, )) prod = self.bipartite.dot(np.ones(3)) self.assertEqual(np.linalg.norm(prod - np.array([5., 4., 6., 5.])), 0.) prod = self.bipartite.dot(0.5 * np.ones(3)) self.assertEqual(np.linalg.norm(prod - np.array([2.5, 2., 3., 2.5])), 0.) prod = (2 * self.bipartite).dot(0.5 * np.ones(3)) self.assertEqual( np.linalg.norm(prod - 2 * np.array([2.5, 2., 3., 2.5])), 0.) def test_transposition(self): transposed = self.undirected.T error = (self.undirected.sparse_mat - transposed.sparse_mat).data self.assertEqual(abs(error).sum(), 0.) transposed = self.bipartite.T x, y = transposed.low_rank_tuples[0] self.assertTrue((x == np.ones(3)).all()) self.assertTrue((y == np.ones(4)).all())
def bipartite2directed( biadjacency: Union[sparse.csr_matrix, SparseLR] ) -> Union[sparse.csr_matrix, SparseLR]: """Adjacency matrix of the directed graph associated with a bipartite graph (with edges from one part to the other). The returned adjacency matrix is: :math:`A = \\begin{bmatrix} 0 & B \\\\ 0 & 0 \\end{bmatrix}` where :math:`B` is the biadjacency matrix. Parameters ---------- biadjacency : Biadjacency matrix of the graph. Returns ------- adjacency : Adjacency matrix (same format as input). """ check_csr_or_slr(biadjacency) n_row, n_col = biadjacency.shape if type(biadjacency) == sparse.csr_matrix: adjacency = sparse.bmat( [[None, biadjacency], [sparse.csr_matrix((n_col, n_row)), None]], format='csr') adjacency.sort_indices() return adjacency else: new_tuples = [(np.hstack( (x, np.zeros(n_col))), np.hstack((np.zeros(n_row), y))) for (x, y) in biadjacency.low_rank_tuples] return SparseLR(bipartite2directed(biadjacency.sparse_mat), new_tuples)
def bipartite2directed( biadjacency: Union[sparse.csr_matrix, SparseLR] ) -> Union[sparse.csr_matrix, SparseLR]: """ Returns the adjacency matrix of the directed graph associated with a bipartite graph (with edges from one part to the other). The returned adjacency matrix is: :math:`A = \\begin{bmatrix} 0 & B \\\\ 0 & 0 \\end{bmatrix}` where :math:`B` is the biadjacency matrix. Parameters ---------- biadjacency: Biadjacency matrix of the graph. Returns ------- Adjacency matrix. """ n1, n2 = biadjacency.shape if type(biadjacency) == sparse.csr_matrix: return sparse.bmat( [[None, biadjacency], [sparse.csr_matrix((n2, n1)), None]], format='csr') elif type(biadjacency) == SparseLR: new_tuples = [(np.hstack( (x, np.zeros(n2))), np.hstack((np.zeros(n1), y))) for (x, y) in biadjacency.low_rank_tuples] return SparseLR(bipartite2directed(biadjacency.sparse_mat), new_tuples) else: raise TypeError( 'Input must be a scipy CSR matrix or a SparseLR object.')
def directed2undirected( adjacency: Union[sparse.csr_matrix, SparseLR], weighted: bool = True) -> Union[sparse.csr_matrix, SparseLR]: """Adjacency matrix of the undirected graph associated with some directed graph. The new adjacency matrix becomes either: :math:`A+A^T` (default) or :math:`\\max(A,A^T)` If the initial adjacency matrix :math:`A` is binary, bidirectional edges have weight 2 (first method, default) or 1 (second method). Parameters ---------- adjacency : Adjacency matrix. weighted : If ``True``, return the sum of the weights in both directions of each edge. Returns ------- new_adjacency : New adjacency matrix (same format as input). """ check_csr_or_slr(adjacency) if type(adjacency) == sparse.csr_matrix: if weighted: data_type = int if len(adjacency.data) and type(adjacency.data[0].item()) == float: data_type = float new_adjacency = adjacency.astype(data_type) new_adjacency += adjacency.T else: new_adjacency = (adjacency + adjacency.T).astype(bool) new_adjacency.tocsr().sort_indices() return new_adjacency else: if weighted: new_tuples = [(y, x) for (x, y) in adjacency.low_rank_tuples] return SparseLR(directed2undirected(adjacency.sparse_mat), adjacency.low_rank_tuples + new_tuples) else: raise ValueError( 'This function only works with ``weighted=True`` for SparseLR objects.' )
def test_dot(self): n = 5 x = np.random.randn(n) mat = sparse.eye(n, format='csr') slr = SparseLR(mat, [(np.zeros(n), np.zeros(n))]) y1 = safe_sparse_dot(x, slr) y2 = safe_sparse_dot(slr, x) self.assertAlmostEqual(np.linalg.norm(y1 - y2), 0) with self.assertRaises(NotImplementedError): safe_sparse_dot(slr, slr) y1 = safe_sparse_dot(slr, mat).dot(x) y2 = safe_sparse_dot(mat, slr).dot(x) self.assertAlmostEqual(np.linalg.norm(y1 - y2), 0)
def directed2undirected( adjacency: Union[sparse.csr_matrix, SparseLR], weight_sum: bool = True) -> Union[sparse.csr_matrix, SparseLR]: """ Returns the adjacency matrix of the undirected graph associated with some directed graph. The new adjacency matrix becomes either: :math:`A+A^T` (default) or :math:`\\max(A,A^T)` If the initial adjacency matrix :math:`A` is binary, bidirectional edges have weight 2 (first method, default) or 1 (second method). Parameters ---------- adjacency: Adjacency matrix. weight_sum: If True, return the sum of the weights in both directions of each edge. Returns ------- New adjacency matrix (symmetric). """ if type(adjacency) == sparse.csr_matrix: if weight_sum: return sparse.csr_matrix(adjacency + adjacency.T) else: return adjacency.maximum(adjacency.T) elif type(adjacency) == SparseLR: if weight_sum: new_tuples = [(y, x) for (x, y) in adjacency.low_rank_tuples] return SparseLR(directed2undirected(adjacency.sparse_mat), adjacency.low_rank_tuples + new_tuples) else: raise ValueError( 'This function only works with ``weight_sum=True`` for SparseLR objects.' ) else: raise TypeError( 'Input must be a scipy CSR matrix or a SparseLR object.')
def bipartite2undirected( biadjacency: Union[sparse.csr_matrix, SparseLR] ) -> Union[sparse.csr_matrix, SparseLR]: """Adjacency matrix of a bigraph defined by its biadjacency matrix. The returned adjacency matrix is: :math:`A = \\begin{bmatrix} 0 & B \\\\ B^T & 0 \\end{bmatrix}` where :math:`B` is the biadjacency matrix of the bipartite graph. Parameters ---------- biadjacency: Biadjacency matrix of the graph. Returns ------- adjacency : Adjacency matrix (same format as input). """ check_csr_or_slr(biadjacency) if type(biadjacency) == sparse.csr_matrix: adjacency = sparse.bmat([[None, biadjacency], [biadjacency.T, None]], format='csr') adjacency.sort_indices() return adjacency else: n_row, n_col = biadjacency.shape new_tuples = [] for (x, y) in biadjacency.low_rank_tuples: new_tuples.append((np.hstack( (x, np.zeros(n_col))), np.hstack((np.zeros(n_row), y)))) new_tuples.append((np.hstack( (np.zeros(n_row), y)), np.hstack((x, np.zeros(n_col))))) return SparseLR(bipartite2undirected(biadjacency.sparse_mat), new_tuples)
def bipartite2undirected( biadjacency: Union[sparse.csr_matrix, SparseLR] ) -> Union[sparse.csr_matrix, SparseLR]: """ Returns the adjacency matrix of a biadjacency adjacency defined by its biadjacency matrix. The returned adjacency matrix is: :math:`A = \\begin{bmatrix} 0 & B \\\\ B^T & 0 \\end{bmatrix}` where :math:`B` is the biadjacency matrix of the bipartite graph. Parameters ---------- biadjacency: Biadjacency matrix of the graph. Returns ------- Adjacency matrix (symmetric). """ if type(biadjacency) == sparse.csr_matrix: return sparse.bmat([[None, biadjacency], [biadjacency.T, None]], format='csr') elif type(biadjacency) == SparseLR: n1, n2 = biadjacency.shape new_tuples = [] for (x, y) in biadjacency.low_rank_tuples: new_tuples.append((np.hstack( (x, np.zeros(n2))), np.hstack((np.zeros(n1), y)))) new_tuples.append((np.hstack( (np.zeros(n1), y)), np.hstack((x, np.zeros(n2))))) return SparseLR(bipartite2undirected(biadjacency.sparse_mat), new_tuples) else: raise TypeError( 'Input must be a scipy CSR matrix or a SparseLR object.')
def test_init(self): with self.assertRaises(ValueError): SparseLR(house(), [(np.ones(5), np.ones(4))]) with self.assertRaises(ValueError): SparseLR(house(), [(np.ones(4), np.ones(5))])
def setUp(self): """Simple regularized adjacency and biadjacency for tests.""" self.undirected = SparseLR(house(), [(np.ones(5), np.ones(5))]) self.bipartite = SparseLR(star_wars(), [(np.ones(4), np.ones(3))])
def randomized_eig(matrix, n_components: int, which='LM', n_oversamples: int = 10, n_iter='auto', power_iteration_normalizer: Union[str, None] = 'auto', random_state=None, one_pass: bool = False): """Randomized eigenvalue decomposition. Parameters ---------- matrix: ndarray or sparse matrix Matrix to decompose n_components: int Number of singular values and vectors to extract. which: str which eigenvalues to compute. ``'LM'`` for Largest Magnitude and ``'SM'`` for Smallest Magnitude. Any other entry will result in Largest Magnitude. n_oversamples : int (default=10) Additional number of random vectors to sample the range of ``matrix`` so as to ensure proper conditioning. The total number of random vectors used to find the range of ``matrix`` is ``n_components + n_oversamples``. Smaller number can improve speed but can negatively impact the quality of approximation of singular vectors and singular values. n_iter: int or 'auto' (default is 'auto') See :meth:`randomized_range_finder` power_iteration_normalizer: ``'auto'`` (default), ``'QR'``, ``'LU'``, ``None`` See :meth:`randomized_range_finder` random_state: int, RandomState instance or None, optional (default=None) See :meth:`randomized_range_finder` one_pass: bool (default=False) whether to use algorithm 5.6 instead of 5.3. 5.6 requires less access to the original matrix, while 5.3 is more accurate. Returns ------- eigenvalues: np.ndarray eigenvectors: np.ndarray References ---------- Finding structure with randomness: Stochastic algorithms for constructing approximate matrix decompositions Halko, et al., 2009 http://arxiv.org/abs/arXiv:0909.4061 """ random_state = check_random_state(random_state) n_random = n_components + n_oversamples n_samples, n_features = matrix.shape lambda_max = 0. if n_samples != n_features: raise ValueError('The input matrix is not square.') if which == 'SM': lambda_max: float = 1.1 * randomized_eig(matrix, n_components=1)[0][0] matrix *= -1 if isinstance(matrix, SparseLR): matrix += SparseLR(lambda_max * sparse.identity(matrix.shape[0]), []) else: matrix += lambda_max * sparse.identity(matrix.shape[0]) if n_iter == 'auto': # Checks if the number of iterations is explicitly specified # Adjust n_iter. 7 was found a good compromise for PCA. See #5299 n_iter = 7 if n_components < .1 * min(matrix.shape) else 4 range_matrix, random_matrix, random_proj = randomized_range_finder(matrix, n_random, n_iter, power_iteration_normalizer, random_state, True) if one_pass: approx_matrix = np.linalg.lstsq(random_matrix.T.dot(range_matrix), random_proj.T.dot(range_matrix), None)[0].T else: approx_matrix = (matrix.dot(range_matrix)).T.dot(range_matrix) eigenvalues, eigenvectors = np.linalg.eig(approx_matrix) del approx_matrix # eigenvalues indices in decreasing order values_order = np.argsort(eigenvalues)[::-1] eigenvalues = eigenvalues[values_order] eigenvectors = np.dot(range_matrix, eigenvectors)[:, values_order] if which == 'SM': eigenvalues = lambda_max - eigenvalues return eigenvalues[:n_components], eigenvectors[:, :n_components]
def setUp(self): self.undirected = SparseLR(house(), [(np.ones(5), np.ones(5))]) self.bipartite = SparseLR(star_wars_villains(), [(np.ones(4), np.ones(3))])