def qr_householder(matrix):
    rows, cols = g.shape(matrix)
    Q = identity_matrix(rows)
    for i in range(cols):
        u, v = _get_householder_vectors(matrix, i)
        if not u or u == v:  # 'u' and 'v' may be empty when called for the last column. hint: check for square matrix
            continue
        w = g.divide_by_scalar(g.subtract(u, v), vector.norm(g.subtract(u, v)))
        temp = g.multiply_by_scalar(g.multiply(w, g.transpose(w)), 2)
        H = g.subtract(identity_matrix(len(w)), temp)
        H = _add_identity_matrix_to_householder(H, i)
        Q = g.multiply(H, Q)
        matrix = g.multiply(H, matrix)

    return g.transpose(Q), matrix
def eigenvalues(matrix, algo='householder'):
    # returns a row vector of eigenvalues
    if algo == 'householder':
        qr_algo = qr_householder
    else:
        qr_algo = qr_gram_schmidt

    size = check_for_square(matrix)
    if size == -1:
        print("can not find eigenvalues of a non-square matrix!")
        return

    change = 1
    E = diagonal(matrix)
    steps = 0
    while change > 0.00000001:
        if steps > 50000:    # eigenvalues are not converging
            return None
        Eold = E
        Q, R = qr_algo(matrix)
        matrix = g.multiply(R, Q)
        E = diagonal(matrix)
        change = vector.norm(g.subtract(E, Eold))
        steps += 1

    return vector.transform_to_row_vector(diagonal(matrix))
def tridiagonal(matrix):
    size = check_for_square(matrix)
    if size == -1:
        print("can not tridiagonalize a non-square matrix")
        return

    # H = I - w*transpose(w)
    # w = (u - v)/norm(u - v)
    # since the matrix is symmetric, we can extract 'u' vector from matrix rows and then transform it to column vector
    for i in range(size - 2):
        u = vector.transform_to_column_vector([matrix[i][u_i] for u_i in range(i + 1, size)])
        v = vector.transform_to_column_vector([vector.norm(u) if v_i == 0 else 0 for v_i in range(len(u))])
        w = g.divide_by_scalar(g.subtract(u, v), vector.norm(g.subtract(u, v)))
        temp = g.multiply_by_scalar(g.multiply(w, g.transpose(w)), 2)
        H = g.subtract(identity_matrix(len(w)), temp)
        H = _add_identity_matrix_to_householder(H, size - len(H))
        matrix = g.multiply(H, g.multiply(matrix, H))

    return matrix
def dot(v1, v2):
    return g.multiply(g.transpose(v1), v2)[0][0]
def qr_gram_schmidt(matrix):
    Q = gram_schmidt_orthonormalize(matrix)
    R = g.multiply(g.transpose(Q), matrix)  # as Q is orthonormal matrix, so, transpose(Q) = inverse(Q)
    return Q, R