Applies a function to the eigenvalues of a matrix.

    Given M = sum_k a_k |v_k><v_k|, returns f(M) = sum_k f(a_k) |v_k><v_k|.

    Args:
        matrix: The matrix to modify with the function.
        func: The function to apply to the eigenvalues of the matrix.
        rtol: Relative threshold used when separating eigenspaces.
        atol: Absolute threshold used when separating eigenspaces.

    Returns:
        The transformed matrix.

def map_eigenvalues(
        matrix: np.ndarray,
        func: Callable[[complex], complex],
        *,
        rtol: float = 1e-5,
        atol: float = 1e-8) -> np.ndarray:
    """Applies a function to the eigenvalues of a matrix.

    Given M = sum_k a_k |v_k><v_k|, returns f(M) = sum_k f(a_k) |v_k><v_k|.

    Args:
        matrix: The matrix to modify with the function.
        func: The function to apply to the eigenvalues of the matrix.
        rtol: Relative threshold used when separating eigenspaces.
        atol: Absolute threshold used when separating eigenspaces.

    Returns:
        The transformed matrix.
    """
    vals, vecs = _perp_eigendecompose(matrix,
                                      rtol=rtol,
                                      atol=atol)
    pieces = [np.outer(vec, np.conj(vec.T)) for vec in vecs]
    out_vals = np.vectorize(func)(vals.astype(complex))

    total = np.zeros(shape=matrix.shape)
    for piece, val in zip(pieces, out_vals):
        total = np.add(total, piece * val)
    return total