예제 #1
0
def compute_svd(mat: Matrix) -> (Matrix, Matrix, Matrix):
    if mat.num_cols() > mat.num_rows():
        u, s, v = compute_svd(mat.transpose())
        return v, s.transpose(), u
    elif mat.num_rows() > mat.num_cols():
        # mat is m x n, m > n
        # q should be m x m, r should be m x n
        q, r = compute_qr_factorization(mat)
        # truncate r to be n x n, truncate q to be m x n
        r_truncated = MatrixView.with_size(
            r, (0, 0), (mat.num_cols(), mat.num_cols())).to_matrix()
        u, s, v = compute_svd(r_truncated)
        u_padded = Matrix.identity(mat.num_rows())
        MatrixView.with_size(u_padded, (0, 0),
                             (mat.num_cols(), mat.num_cols())).set(
                                 MatrixView.whole(u))
        u = q.multiply(u_padded)
        s_padded = Matrix.zeroes(mat.num_rows(), mat.num_cols())
        MatrixView.with_size(s_padded, (0, 0),
                             (mat.num_cols(), mat.num_cols())).set(
                                 MatrixView.whole(s))
        s = s_padded
        return u, s, v
    else:
        # matrix is square
        b, left, right = reduce_to_bidiagonal(mat)
        u, s, v = compute_svd_bidiagonal(b)
        for index, hh in list(enumerate(left))[::-1]:
            u = hh.multiply_left(u, index)
        v_transpose = v.transpose()
        for index, hh in list(enumerate(right))[::-1]:
            v_transpose = hh.multiply_right(v_transpose, index + 1)
        return u, s, v_transpose.transpose()
예제 #2
0
def compute_qr_factorization(mat: Matrix) -> (Matrix, Matrix):
    # Do not overwrite original matrix
    mat = mat.copy()
    householders = []  # store householder transformations
    iterations = min(mat.num_rows(), mat.num_cols())
    for iteration in range(iterations):
        col = mat.get_col(iteration)
        # Zero out the entries below the diagonal
        hh = Householder(col[iteration:])
        householders.append((iteration, hh))
        mat = hh.multiply_left(mat, pad_top=iteration)
    # Accumulate the householder transformations
    q_mat = Matrix.identity(mat.num_rows())
    for iteration, hh in householders[::-1]:
        q_mat = hh.multiply_left(q_mat, pad_top=iteration)
    return (q_mat, mat)
예제 #3
0
def reduce_to_bidiagonal(
        mat: Matrix) -> (Matrix, List[Householder], List[Householder]):
    mat = mat.copy()
    if mat.num_rows() != mat.num_cols():
        raise ValueError("Matrix should be square")
    iterations = mat.num_rows() - 1
    acc_left = []
    acc_right = []
    for iteration in range(iterations):
        # clear zeroes below diagonal
        col = mat.get_col(iteration)[iteration:]
        householder_left = Householder(col)
        mat = householder_left.multiply_left(mat, pad_top=iteration)
        acc_left.append(householder_left)
        if iteration != iterations - 1:
            # clear zeroes above superdiagonal
            row = mat.get_row(iteration)[iteration + 1:]
            householder_right = Householder(row)
            mat = householder_right.multiply_right(mat, pad_top=iteration + 1)
            acc_right.append(householder_right)
    return mat, acc_left, acc_right
예제 #4
0
def compute_svd_bidiagonal(mat: Matrix) -> (Matrix, Matrix, Matrix):
    dims = mat.num_cols()
    u = Matrix.identity(dims)
    v = Matrix.identity(dims)
    while True:
        # while not converged
        # find max off-diagonal
        max_off_diag = 0
        diag_sum = 0
        for i in range(dims - 1):
            max_off_diag = max(max_off_diag, abs(mat.get(i, i + 1)))
        for i in range(dims):
            diag_sum += abs(mat.get(i, i))
        diag_sum /= dims
        if max_off_diag < diag_sum * 1e-6 and max_off_diag < 1e-8:
            break

        # introduce the bulge
        givens = Givens(
            mat.get(0, 0)**2 - mat.get(dims - 1, dims - 1)**2 -
            mat.get(dims - 2, dims - 1)**2,
            mat.get(0, 1) * mat.get(0, 0)).transpose()
        mat = givens.multiply_right(mat)
        v = givens.transpose().multiply_left(v)

        # chase the bulge
        for iteration in range(0, dims - 1):
            # zero subdiagonal
            givens_lower = Givens(mat.get(iteration, iteration),
                                  mat.get(iteration + 1, iteration))
            mat = givens_lower.multiply_left(mat, pad_top=iteration)
            u = givens_lower.transpose().multiply_right(u, pad_top=iteration)
            # zero above superdiagonal
            if iteration != dims - 2:
                givens_upper = Givens(mat.get(iteration, iteration + 1),
                                      mat.get(iteration,
                                              iteration + 2)).transpose()
                mat = givens_upper.multiply_right(mat, pad_top=iteration + 1)
                v = givens_upper.transpose().multiply_left(v,
                                                           pad_top=iteration +
                                                           1)

    v = v.transpose()
    # Ensure singular values are non-negative
    for i in range(dims):
        if mat.get(i, i) < 0:
            MatrixView.with_size(mat, (i, i), (1, 1)).scale(-1)
            MatrixView(u, (0, i), (u.num_rows() - 1, i)).scale(-1)

    # reorder columns
    sv = [(mat.get(i, i), i) for i in range(dims)]
    sv.sort()
    sv = sv[::-1]
    sorted_v_cols = [v.get_col(index) for value, index in sv]
    sorted_u_cols = [u.get_col(index) for value, index in sv]
    v = Matrix.from_cols(sorted_v_cols)
    u = Matrix.from_cols(sorted_u_cols)
    mat = Matrix.zeroes(dims, dims)
    for index, (value, _) in enumerate(sv):
        MatrixView.with_size(mat, (index, index),
                             (1, 1)).set_element(0, 0, value)

    return u, mat, v