Skip to content

qttools.kernels.linalg.eigvalsh#

[docs] module qttools.kernels.linalg.eigvalsh

import numpy as np

from qttools import NDArray, xp
from qttools.kernels import linalg
from qttools.profiling import Profiler
from qttools.utils.gpu_utils import get_any_location, get_array_module_name

profiler = Profiler()


@profiler.profile(level="debug")
def _eigvalsh(
    A: NDArray,
    B: NDArray,
    xp_eigvalsh: xp,
) -> NDArray:
    """Compute eigenvalues of a hermitain generalized eigenvalue problem.

    Parameters
    ----------
    A : NDArray
        A complex or real symmetric or hermitian matrix.
    B : NDArray
        A complex or real symmetric or hermitian positive definite matrix.
    xp_eigvalsh : module
        The location where to compute the eigenvalues.

    Returns
    -------
    NDArray
        The eigenvalues of the generalized eigenvalue problem.

    """

    R = xp_eigvalsh.linalg.cholesky(B)

    # NOTE: would be more efficient to use cholesky_solve
    # if it would be supported by cupy
    if xp_eigvalsh.__name__ == "cupy":
        R_inv = linalg.inv(R)
    else:
        R_inv = xp_eigvalsh.linalg.inv(R)
    A_hat = R_inv @ A @ R_inv.swapaxes(-2, -1).conj()
    w = xp_eigvalsh.linalg.eigvalsh(A_hat)

    return w


@profiler.profile(level="api")
def eigvalsh(
    A: NDArray,
    B: NDArray | None = None,
    compute_module: str = "cupy",
    output_module: str | None = None,
    use_pinned_memory: bool = True,
) -> NDArray:
    """Compute eigenvalues of a hermitain generalized eigenvalue problem.

    TODO: only type 1 generalized problems are supported,
    Only a subset of keywords of scipy.linalg.eigvalsh are supported.

    Parameters
    ----------
    A : NDArray
        A complex or real symmetric or hermitian matrix.
    B : NDArray, optional
        A complex or real symmetric or hermitian positive definite matrix.
        If omitted, identity matrix is assumed.
    compute_module : str, optional
        The location where to compute the eigenvalues.
    output_module : str, optional
        The location where to store the resulting eigenvalues.
    use_pinned_memory : bool, optional
        Whether to use pinnend memory if cupy is used.
        Default is `True`.

    Returns
    -------
    NDArray
        The eigenvalues of the generalized eigenvalue problem.

    """
    input_module = get_array_module_name(A)

    if B is not None and input_module != get_array_module_name(B):
        raise ValueError("Input arrays must be at the same location.")

    if output_module is None:
        output_module = input_module

    if xp.__name__ == "numpy" and (
        compute_module == "cupy" or output_module == "cupy" or input_module == "cupy"
    ):
        raise ValueError("Cannot do gpu computation with numpy as xp.")

    # memcopy to correct location
    A = get_any_location(A, compute_module, use_pinned_memory=use_pinned_memory)

    if B is None:
        if compute_module == "numpy":
            w = np.linalg.eigvalsh(A)
        elif compute_module == "cupy":
            w = xp.linalg.eigvalsh(A)
    else:

        B = get_any_location(B, compute_module, use_pinned_memory=use_pinned_memory)

        if compute_module == "numpy":
            w = _eigvalsh(A, B, np)
        elif compute_module == "cupy":
            w = _eigvalsh(A, B, xp)

    return get_any_location(w, output_module, use_pinned_memory=use_pinned_memory)