Source code for wholecell.tests.utils.test_fast_nnls

"""
Test the fast nonnegative least squares (NNLS) utils function

        cd wcEcoli
        pytest wholecell/tests/utils/test_fast_nnls.py
"""

from wholecell.utils.fast_nonnegative_least_squares import fast_nnls

import numpy as np
import numpy.testing as npt
from scipy import sparse
from scipy.optimize import nnls
import unittest
import time

# Silence Sphinx autodoc warning
unittest.TestCase.__module__ = "unittest"


[docs] def time_this(code_to_measure): """ Time the execution of code_to_measure() and return elapsed time in fractional seconds. """ elapsed_start = time.monotonic() code_to_measure() elapsed_end = time.monotonic() elapsed_time = elapsed_end - elapsed_start return elapsed_time
[docs] class Test_fast_nnls(unittest.TestCase):
[docs] def setUp(self): self.default_array_size = 10 np.random.seed(0)
def test_return_value_dimensions(self): """ Test that return values have the correct dimensions. """ m = 5 n = 3 A = np.random.rand(m, n) b = np.random.rand(m) x, r = fast_nnls(A, b) assert x.shape == (n,) assert b.shape == (m,) def test_type_error(self): """ Test that arguments with wrong array types or dimensions raise TypeError exceptions. """ m = 5 n = 3 A = np.random.rand(m, n) sA = sparse.csr_matrix(A) A_wrongdim = np.random.rand(m) b = np.random.rand(m) sb = sparse.csr_matrix(b) b_wrongsize = np.random.rand(n) with self.assertRaisesRegex(TypeError, r"two-dimensional"): fast_nnls(A_wrongdim, b) with self.assertRaisesRegex(TypeError, r"one-dimensional"): fast_nnls(A, sb) with self.assertRaisesRegex(TypeError, r"one-dimensional"): fast_nnls(sA, sb) with self.assertRaisesRegex(TypeError, r"Dimensions of"): fast_nnls(sA, b_wrongsize) def test_identity_matrix(self): """ Test fast_nnls with an identity matrix A returns an array x that is equivalent to b. """ A = np.eye(self.default_array_size) b = np.random.rand(self.default_array_size) x, r = fast_nnls(A, b) npt.assert_array_equal(x, b) npt.assert_array_equal(r, np.zeros(self.default_array_size)) def test_full_sparse_equivalence(self): """ Test if function returns same values for full and sparse matrix A's. """ A = np.random.rand(self.default_array_size, self.default_array_size) sA = sparse.csr_matrix(A) b = np.random.rand(self.default_array_size) x1, r1 = fast_nnls(A, b) x2, r2 = fast_nnls(sA, b) npt.assert_array_equal(x1, x2) npt.assert_array_almost_equal(r1, r2) def test_reproducibility(self): """ Test reproducibility of fast_nnls outputs. """ A = np.random.rand(self.default_array_size, self.default_array_size) b = np.random.rand(self.default_array_size) x1, r1 = fast_nnls(A, b) x2, r2 = fast_nnls(A, b) npt.assert_array_equal(x1, x2) npt.assert_array_equal(r1, r2) def test_equilvalence_to_nnls(self): """ Test fast_nnls returns the same norm of the residual as scipy nnls for a random matrix A. """ A = np.random.rand(self.default_array_size, self.default_array_size) b = np.random.rand(self.default_array_size) _, rnorm_slow = nnls(A, b) _, r = fast_nnls(A, b) rnorm_fast = np.linalg.norm(r) self.assertAlmostEqual(rnorm_slow, rnorm_fast) def test_zero_column(self): """ Test fast_nnls returns a solution with a value of zero in the index corresponding to a column of zeros in matrix A. """ A = np.random.rand(self.default_array_size, self.default_array_size) b = np.random.rand(self.default_array_size) for i in range(A.shape[1]): A_copy = A.copy() A_copy[:, i] = 0 x, _ = fast_nnls(A_copy, b) assert x[i] == 0 def test_improved_performance(self): """ Test fast_nnls is faster than nnls for sparse arrays that can be decomposed into multiple nnls problems. """ # Decomposable array A = np.array( [ [1, 1, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 1, 0, 0, 0, 0, 0, 0, 0], [0, 1, 1, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 0, 0, 1, 1, 1, 0, 0], [0, 0, 0, 0, 0, 0, 1, 1, 0, 0], [0, 0, 0, 0, 0, 0, 0, 1, 1, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 1], ] ) b = np.random.rand(10) time_slow = time_this(lambda: nnls(A, b)) time_fast = time_this(lambda: fast_nnls(A, b)) self.assertLess(time_slow, time_fast)