Source code for quimb.linalg.numpy_linalg

"""Numpy base linear algebra.
"""

import numpy as np
import numpy.linalg as nla
import scipy.linalg as scla

import quimb as qu
from .autoblock import eigensystem_autoblocked


_NUMPY_EIG_FUNCS = {
    (True, True): nla.eigh,
    (True, False): nla.eig,
    (False, True): nla.eigvalsh,
    (False, False): nla.eigvals,
}


[docs]def eig_numpy(A, sort=True, isherm=True, return_vecs=True, autoblock=False): """Numpy based dense eigensolve. Parameters ---------- A : array_like The operator to decompose. sort : bool, optional Whether to sort into ascending order. isherm : bool, optional Whether ``A`` is hermitian. return_vecs : bool, optional Whether to return the eigenvectors. autoblock : bool, optional If true, automatically identify and exploit symmetries appearing in the current basis as block diagonals formed via permutation of rows and columns. Returns ------- evals : 1D-array The eigenvalues. evecs : qarray If ``return_vecs=True``, the eigenvectors. """ if autoblock: return eigensystem_autoblocked(A, sort=sort, isherm=isherm, return_vecs=return_vecs) evals = _NUMPY_EIG_FUNCS[return_vecs, isherm](A) if return_vecs: evals, evecs = evals if sort: sortinds = np.argsort(evals) evals, evecs = evals[sortinds], evecs[:, sortinds] return evals, qu.qarray(evecs) if sort: evals.sort() return evals
[docs]def sort_inds(a, method, sigma=None): """Return the sorting inds of a list Parameters ---------- a : array_like List to base sort on. method : str Method of sorting list, one of * "LM" - Largest magnitude first * "SM" - Smallest magnitude first * "SA" - Smallest algebraic first * "SR" - Smallest real part first * "SI" - Smallest imaginary part first * "LA" - Largest algebraic first * "LR" - Largest real part first * "LI" - Largest imaginary part first * "TM" - Magnitude closest to target sigma first * "TR" - Real part closest to target sigma first * "TI" - Imaginary part closest to target sigma first sigma : float, optional The target if method={"TM", "TR", or "TI"}. Returns ------- inds : array of int Indices that would sort `a` based on `method` """ _SORT_FUNCS = { "LM": lambda a: -abs(a), "SM": lambda a: -abs(1 / a), "SA": lambda a: a, "SR": lambda a: a.real, "SI": lambda a: a.imag, "LA": lambda a: -a, "LR": lambda a: -a.real, "LI": lambda a: -a.imag, "TM": lambda a: -1 / abs(abs(a) - sigma), "TR": lambda a: -1 / abs(a.real - sigma), "TI": lambda a: -1 / abs(a.imag - sigma), } return np.argsort(_SORT_FUNCS[method.upper()](a))
_DENSE_EIG_METHODS = { (True, True, False): nla.eigh, (True, False, False): nla.eigvalsh, (False, True, False): nla.eig, (False, False, False): nla.eigvals, (True, True, True): scla.eigh, (True, False, True): scla.eigvalsh, (False, True, True): scla.eig, (False, False, True): scla.eigvals, }
[docs]def eigs_numpy(A, k, B=None, which=None, return_vecs=True, sigma=None, isherm=True, P=None, sort=True, **eig_opts): """Partial eigen-decomposition using numpy's dense linear algebra. Parameters ---------- A : array_like or quimb.Lazy Operator to partially eigen-decompose. k : int Number of eigenpairs to return. B : array_like or quimb.Lazy If given, the RHS operator defining a generalized eigen problem. which : str, optional Which part of the spectrum to target. return_vecs : bool, optional Whether to return eigenvectors. sigma : None or float, optional Target eigenvalue. isherm : bool, optional Whether `a` is hermitian. P : array_like or quimb.Lazy Perform the eigensolve in the subspace defined by this projector. sort : bool, optional Whether to sort reduced list of eigenpairs into ascending order. eig_opts Settings to pass to numpy.eig... functions. Returns ------- lk, (vk): k eigenvalues (and eigenvectors) sorted according to which """ if isinstance(A, qu.Lazy): A = A() if isinstance(B, qu.Lazy): B = B() if isinstance(P, qu.Lazy): P = P() # project into subspace if P is not None: A = qu.dag(P) @ (A @ P) generalized = B is not None eig_fn = _DENSE_EIG_METHODS[(isherm, return_vecs, generalized)] if generalized: eig_opts['b'] = B # these might be given for partial eigsys but not relevant for numpy eig_opts.pop('ncv', None) eig_opts.pop('v0', None) eig_opts.pop('tol', None) eig_opts.pop('maxiter', None) eig_opts.pop('EPSType', None) if return_vecs: # get all eigenpairs lk, vk = eig_fn(A.A if qu.issparse(A) else A, **eig_opts) # sort and trim according to which k we want sk = sort_inds(lk, method=which, sigma=sigma)[:k] lk, vk = lk[sk], vk[:, sk] # also potentially sort into ascending order if sort: so = np.argsort(lk) lk, vk = lk[so], vk[:, so] # map eigenvectors out of subspace if P is not None: vk = P @ vk return lk, qu.qarray(vk) else: # get all eigenvalues lk = eig_fn(A.A if qu.issparse(A) else A, **eig_opts) # sort and trim according to which k we want sk = sort_inds(lk, method=which, sigma=sigma)[:k] lk = lk[sk] # also potentially sort into ascending order return np.sort(lk) if sort else lk
[docs]def svds_numpy(a, k, return_vecs=True, **_): """Partial singular value decomposition using numpys (full) singular value decomposition. Parameters ---------- a : array_like Operator to decompose. k : int, optional Number of singular value triplets to retrieve. return_vecs : bool, optional whether to return the computed vecs or values only Returns ------- (uk,) sk (, vkt) : Singlar value triplets. """ if return_vecs: uk, sk, vkt = nla.svd(a.A if qu.issparse(a) else a, compute_uv=True) return qu.qarray(uk[:, :k]), sk[:k], qu.qarray(vkt[:k, :]) else: sk = nla.svd(a.A if qu.issparse(a) else a, compute_uv=False) return sk[:k]