def test_dynamic_programming_logic(self): # Test for the dynamic programming part # This test is directly taken from Cormen page 376. arrays = [ np.random.random((30, 35)), np.random.random((35, 15)), np.random.random((15, 5)), np.random.random((5, 10)), np.random.random((10, 20)), np.random.random((20, 25)) ] m_expected = np.array([[0., 15750., 7875., 9375., 11875., 15125.], [0., 0., 2625., 4375., 7125., 10500.], [0., 0., 0., 750., 2500., 5375.], [0., 0., 0., 0., 1000., 3500.], [0., 0., 0., 0., 0., 5000.], [0., 0., 0., 0., 0., 0.]]) s_expected = np.array( [[0, 1, 1, 3, 3, 3], [0, 0, 2, 3, 3, 3], [0, 0, 0, 3, 3, 3], [0, 0, 0, 0, 4, 5], [0, 0, 0, 0, 0, 5], [0, 0, 0, 0, 0, 0]], dtype=np.int) s_expected -= 1 # Cormen uses 1-based index, python does not. s, m = _multi_dot_matrix_chain_order(arrays, return_costs=True) # Only the upper triangular part (without the diagonal) is interesting. assert_almost_equal(np.triu(s[:-1, 1:]), np.triu(s_expected[:-1, 1:])) assert_almost_equal(np.triu(m), np.triu(m_expected))
def test_dynamic_programming_logic(self): # Test for the dynamic programming part # This test is directly taken from Cormen page 376. arrays = [np.random.random((30, 35)), np.random.random((35, 15)), np.random.random((15, 5)), np.random.random((5, 10)), np.random.random((10, 20)), np.random.random((20, 25))] m_expected = np.array([[0., 15750., 7875., 9375., 11875., 15125.], [0., 0., 2625., 4375., 7125., 10500.], [0., 0., 0., 750., 2500., 5375.], [0., 0., 0., 0., 1000., 3500.], [0., 0., 0., 0., 0., 5000.], [0., 0., 0., 0., 0., 0.]]) s_expected = np.array([[0, 1, 1, 3, 3, 3], [0, 0, 2, 3, 3, 3], [0, 0, 0, 3, 3, 3], [0, 0, 0, 0, 4, 5], [0, 0, 0, 0, 0, 5], [0, 0, 0, 0, 0, 0]], dtype=np.int) s_expected -= 1 # Cormen uses 1-based index, python does not. s, m = _multi_dot_matrix_chain_order(arrays, return_costs=True) # Only the upper triangular part (without the diagonal) is interesting. assert_almost_equal(np.triu(s[:-1, 1:]), np.triu(s_expected[:-1, 1:])) assert_almost_equal(np.triu(m), np.triu(m_expected))
def multi_dot(*arrays, **kwargs): """compute chain of matrix dot products in most efficient order - like np.linalg.multi_dot, but handles 'out' argument - number of ways to evaluate chain is C(n-1), the (n-1)th Catalan number - C(n-1) for n in range(2, 10): 1, 2, 5, 14, 42, 132, 429, 1430, 4862 - see https://en.wikipedia.org/wiki/Matrix_chain_multiplication - numpy's algorithm is O(n^3) - becomes very expensive for large lists of arrays - much more efficient algorithms exist on the order of O(n log n) - is there an easy workaround for 1d arrays? - maybe broadcast them? are they always representative of square matrices? Parameters ---------- - arrays: iterable of arrays to dot multiply as chain - kwargs: [only here because python 2 can't have args after *args] - out: array in which to store output """ for array in arrays: if array.ndim != 2: raise Exception('all arrays must be length 2') out = kwargs.get('out') if len(arrays) == 1: if out is not None: out[:] = arrays[0] return arrays[0] elif len(arrays) == 2: A, B = arrays return np.dot(A, B, out=out) elif len(arrays) == 3: A, B, C = arrays a0, a1b0 = A.shape b1c0, c1 = C.shape cost1 = a0 * b1c0 * (a1b0 + c1) cost2 = a1b0 * c1 * (a0 + b1c0) if cost1 < cost2: return np.dot(np.dot(A, B), C, out=out) else: return np.dot(A, np.dot(B, C), out=out) else: from numpy.linalg.linalg import _multi_dot_matrix_chain_order order = _multi_dot_matrix_chain_order(arrays) def _multi_dot(arrays, order, i, j, out=None): """based on np.linalg.linalg._multi_dot""" if i == j: return arrays[i] else: return np.dot( _multi_dot(arrays, order, i, order[i, j]), _multi_dot(arrays, order, order[i, j] + 1, j), out=out, ) return _multi_dot(arrays, order, 0, len(arrays) - 1, out=out)