def ira(A: LinearOperator, start_vector=None, eig_n: int = 5, which: str = "LM", max_size: int = 20,
        max_iters: int = 100, tol: float = 1e-7, pbar: bool = False):
    """
    Runs the Implicitly Restarted Arnoldi Method (IRAM), which basically
    finds a factorization A V = V H using constant memory.

    Args:
        A (LinearOperator): A linear operator of size (n, n).
        start_vector (Array, optional): An initial vector to start Arnoldi of size (n, ).
         Defaults to a random probe.
        eig_n (int): The number of eigenvalues to estimate.
        max_size (int): The maximum number of inner Arnoldi iterations to run.
        max_iters (int): The maximum number of outer iterations to run.
        tol (float, optional): Stopping criteria.
        pbar (bool, optional): Show a progress bar.

    Returns:
        tuple:
            - V (Array): Unitary matrix of size (n, max_iters) or (b, n, max_iters)
            - H (Array): The upper Hessenberg matrix of size (max_iters, max_iters) or (b, max_iters, max_iters)
            - idx (Array): The number of iterations ran
            - info (dict): General information about the iterative procedure.
    """
    xnp = A.xnp
    init_val = init_arnoldi(xnp=xnp, rhs=start_vector, max_iters=max_size, dtype=A.dtype)

    def cond_fun(state):
        _, H, idx, norm = state
        is_not_max = idx < max_iters
        is_large = (norm > tol)
        return is_not_max & xnp.any(is_large)

    def body_fun(state):
        V, H, idx, _ = state
        V, H, *_ = arnoldi_fact(A, state, max_iters=max_size, tol=tol, pbar=pbar)
        V, H = V[0], H[0]
        eigvals, _ = xnp.eig(H[:-1])
        eig_slice = get_deflation_eig_slice(eigvals, which=which, eig_n=eig_n, xnp=xnp)
        eigvals = xnp.cast(eigvals[eig_slice], dtype=A.dtype)
        vec = H[-1, -1] * V[:, [-1]]
        H, Q = run_shift(H[:-1], eigvals, xnp)
        beta = H[eig_n, eig_n - 1]
        sigma = Q[-1, eig_n - 1]
        new_vec = beta * V[:, [eig_n]] + sigma * vec
        V0 = V[:, :-1] @ Q[:, :eig_n]
        H0 = H[:eig_n, :eig_n]
        init_val = init_arnoldi_from_vec(H0, V0, xnp, new_vec.T, rest=eig_n, max_iters=max_size)
        V, H, *_ = init_val
        norm = xnp.norm(A @ V0 - V0 @ H0)
        return V, H, idx + 1, norm[None]

    while_fn, info = xnp.while_loop_winfo(lambda s: s[-1][0], tol, max_iters, pbar=pbar)
    state = while_fn(cond_fun, body_fun, init_val)
    V, H, idx, _ = state
    return V, H, idx, info

    Runs the Implicitly Restarted Arnoldi Method (IRAM), which basically
    finds a factorization A V = V H using constant memory.

    Args:
        A (LinearOperator): A linear operator of size (n, n).
        start_vector (Array, optional): An initial vector to start Arnoldi of size (n, ).
         Defaults to a random probe.
        eig_n (int): The number of eigenvalues to estimate.
        max_size (int): The maximum number of inner Arnoldi iterations to run.
        max_iters (int): The maximum number of outer iterations to run.
        tol (float, optional): Stopping criteria.
        pbar (bool, optional): Show a progress bar.

    Returns:
        tuple:
            - V (Array): Unitary matrix of size (n, max_iters) or (b, n, max_iters)
            - H (Array): The upper Hessenberg matrix of size (max_iters, max_iters) or (b, max_iters, max_iters)
            - idx (Array): The number of iterations ran
            - info (dict): General information about the iterative procedure.
    
cola/linalg/decompositions/arnoldi.py