Exemplo n.º 1
0
    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))
Exemplo n.º 2
0
    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))
Exemplo n.º 3
0
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)