//  [python]
def _solve_subproblem_krylov(
            self,
            loss: float,
            flat_grad: Tensor,
            trust_radius: float) -> Tuple[Tensor, bool]:
        """
            Solves the quadratic subproblem in the trust region using Generalized Lanczos Method,
            see Algorithm 7.5.2 Conn et al.
        """
        INTERIOR_FLAG = True
        Q, diagonals, off_diagonals = [], [], []

        flat_grads_detached = flat_grad.detach()
        n_features = len(flat_grads_detached)
        h = torch.zeros_like(flat_grads_detached, requires_grad=False)
        q, p = flat_grads_detached, -flat_grads_detached

        gamma0 = torch.linalg.norm(q)

        krylov_dim, sigma = 0, 1

        device = flat_grad.device
        targs = {'device': device, 'dtype': flat_grad.dtype}

        while True:
            Hp = self._compute_hessian_vector_product(flat_grad, p)
            ptHp = torch.sum(Hp * p)
            alpha = torch.linalg.norm(q) ** 2 / ptHp
            # if alpha == 0:
            #     print('hard case')
            if krylov_dim == 0:
                diagonals.append(1. / alpha.clamp_(min=self.epsilon).item())
                off_diagonals.append(float('inf'))  # dummy value
                Q.append(sigma * q / torch.linalg.norm(q))
                T_x = torch.tensor([diagonals], **targs)
                alpha_prev = alpha
            else:
                diagonals.append(1. / alpha.item() +
                                 beta.item() / alpha_prev.item())
                sigma = - torch.sign(alpha_prev) * sigma
                Q.append(sigma * q / torch.linalg.norm(q))
                T_x = (torch.diag(torch.tensor(diagonals, **targs), 0)
                       + torch.diag(torch.tensor(off_diagonals[1:], **targs), -1)
                       + torch.diag(torch.tensor(off_diagonals[1:], **targs), 1))
                alpha_prev = alpha

            if INTERIOR_FLAG and alpha < 0 or torch.linalg.norm(h + alpha * p) >= trust_radius:
                INTERIOR_FLAG = False

            if INTERIOR_FLAG:
                h = h + alpha * p
            else:
                # Lanczos Step 2: solve problem in subspace
                e_1 = torch.eye(1, krylov_dim + 1,
                                device=flat_grad.device).flatten()
                lanczos_g = gamma0 * e_1
                s = self._root_finder(trust_radius=trust_radius,
                                      T_x=T_x, lanczos_g=lanczos_g,
                                      loss=loss, device=flat_grad.device)
                s = s.to(flat_grad.device)

            q_next = q + alpha * Hp

            # test for convergence
            if INTERIOR_FLAG and torch.linalg.norm(q_next) ** 2 < self.lanczos_tol:
                break
            if not INTERIOR_FLAG and torch.linalg.norm(q_next) * abs(s[-1]) < self.lanczos_tol:
                break

            if krylov_dim == n_features:
                # print(RuntimeWarning(
                #     'Krylov dimensionality reach full space! Breaking out..'))
                break
                # return h

            if krylov_dim > self.max_krylov_dim:
                # print(RuntimeWarning('Max Krylov dimension reached! Breaking out..'))
                break

            beta = torch.dot(q_next, q_next) / torch.dot(q, q)
            off_diagonals.append(torch.sqrt(beta) / torch.abs(alpha_prev))
            p = -q_next + beta * p
            q = q_next
            krylov_dim = krylov_dim + 1

        if not INTERIOR_FLAG:
            # Return to the original space
            Q = torch.vstack(Q).T
            h = torch.sum(Q * torch.squeeze(s), dim=1)

        return h, not INTERIOR_FLAG