def svds_scipy(A, k=6, *, return_vecs=True, **svds_opts): """Compute a number of singular value pairs Parameters ---------- A : (m, n) dense, sparse or linear operator. The operator to solve. k : int Number of requested singular values. return_vecs : bool, optional Whether to return the singular vectors. Returns ------- U : (m, k) array Left singular vectors (if ``return_vecs=True``) as columns. s : (k,) array Singular values. VH : (k, n) array Right singular vectors (if ``return_vecs=True``) as rows. """ settings = {'k': k, 'return_singular_vectors': return_vecs, **svds_opts} # avoid matrix like behaviour if isinstance(A, qu.qarray): A = A.A if return_vecs: uk, sk, vtk = spla.svds(A, **settings) so = np.argsort(-sk) return qu.qarray(uk[:, so]), sk[so], qu.qarray(vtk[so, :]) else: sk = spla.svds(A, **settings) return sk[np.argsort(-sk)]
def to_dense(self): """Convert this TN to a dense array. """ t = self.TN.contract_tags(...) t.fuse_([('k', list(map(self.lower_ind_id.format, self.TN.sites))), ('b', list(map(self.upper_ind_id.format, self.TN.sites)))]) return qu.qarray(t.data)
def test_convert_vector_to_dop(self): x = [1, 2, 3j] p = qu.qu(x, qtype='r') assert_allclose( p, qu.qarray([[1. + 0.j, 2. + 0.j, 0. - 3.j], [2. + 0.j, 4. + 0.j, 0. - 6.j], [0. + 3.j, 0. + 6.j, 9. + 0.j]]))
def maybe_sort_and_project(lk, vk, P, sort=True): if sort: sortinds = np.argsort(lk) lk, vk = lk[sortinds], vk[:, sortinds] # map eigenvectors out of subspace if P is not None: vk = P @ vk return lk, qu.qarray(vk)
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]
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
def eig_numpy(A, sort=True, isherm=True, return_vecs=True): """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. Returns ------- evals : 1D-array The eigenvalues. evecs : qarray If ``return_vecs=True``, the eigenvectors. """ 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: return np.sort(evals) return evals
def eigs_scipy(A, k, *, B=None, which=None, return_vecs=True, sigma=None, isherm=True, sort=True, P=None, tol=None, **eigs_opts): """Returns a few eigenpairs from a possibly sparse hermitian operator Parameters ---------- A : array_like, sparse_matrix, LinearOperator or quimb.Lazy The operator to solve for. k : int Number of eigenpairs to return B : array_like, sparse_matrix, LinearOperator or quimb.Lazy, optional If given, the RHS operator (which should be positive) defining a generalized eigen problem. which : str, optional where in spectrum to take eigenvalues from (see :func:`scipy.sparse.linalg.eigsh`). return_vecs : bool, optional Whether to return the eigenvectors as well. sigma : float, optional Shift, if targeting interior eigenpairs. isherm : bool, optional Whether ``A`` is hermitian. P : array_like, sparse_matrix, LinearOperator or quimb.Lazy, optional Perform the eigensolve in the subspace defined by this projector. sort : bool, optional Whether to ensure the eigenvalues are sorted in ascending value. eigs_opts Supplied to :func:`scipy.sparse.linalg.eigsh` or :func:`scipy.sparse.linalg.eigs`. Returns ------- lk : (k,) array The eigenvalues. vk : (m, k) array Corresponding eigenvectors (if ``return_vecs=True``). """ if isinstance(A, qu.Lazy): A = A() if isinstance(B, qu.Lazy): B = B() if isinstance(P, qu.Lazy): P = P() # avoid matrix like behaviour if isinstance(A, qu.qarray): A = A.A # project into subspace if P is not None: A = qu.dag(P) @ (A @ P) # Options that might get passed that scipy doesn't support eigs_opts.pop('EPSType', None) # convert certain options for scipy settings = { 'k': k, 'M': B, 'which': ( 'SA' if (which is None) and (sigma is None) else 'LM' if (which is None) and (sigma is not None) else # For target using shift-invert scipy requires 'LM' -> 'LM' if ('T' in which.upper()) and (sigma is not None) else which), 'sigma': sigma, 'return_eigenvectors': return_vecs, 'tol': 0 if tol is None else tol } eig_fn = spla.eigsh if isherm else spla.eigs if return_vecs: lk, vk = eig_fn(A, **settings, **eigs_opts) vk = qu.qarray(vk) return maybe_sort_and_project(lk, vk, P, sort) else: lk = eig_fn(A, **settings, **eigs_opts) return np.sort(lk) if sort else lk
def eigs_lobpcg(A, k, *, B=None, v0=None, which=None, return_vecs=True, sigma=None, isherm=True, P=None, sort=True, **lobpcg_opts): """Interface to scipy's lobpcg eigensolver, which can be good for generalized eigenproblems with matrix-free operators. Seems to a be a bit innacurate though (e.g. on the order of ~ 1e-6 for eigenvalues). Also only takes real, symmetric problems, targeting smallest eigenvalues (though scipy will soon have complex support, and its easy to add oneself). Note that the slepc eigensolver also has a lobpcg backend (``EPSType='lobpcg'``) which accepts complex input and is more accurate - though seems slower. Parameters ---------- A : array_like, sparse_matrix, LinearOperator or callable The operator to solve for. k : int Number of eigenpairs to return B : array_like, sparse_matrix, LinearOperator or callable, optional If given, the RHS operator (which should be positive) defining a generalized eigen problem. v0 : array_like (d, k), optional The initial subspace to iterate with. which : {'SA', 'LA'}, optional Find the smallest or largest eigenvalues. return_vecs : bool, optional Whether to return the eigenvectors found. P : array_like, sparse_matrix, LinearOperator or callable, optional Perform the eigensolve in the subspace defined by this projector. sort : bool, optional Whether to ensure the eigenvalues are sorted in ascending value. lobpcg_opts Supplied to :func:`scipy.sparse.linagl.lobpcg`. Returns ------- lk : array_like (k,) The eigenvalues. vk : array_like (d, k) The eigenvectors, if `return_vecs=True`. See Also -------- eigs_scipy, eigs_numpy, eigs_slepc """ if not isherm: raise ValueError("lobpcg can only solve symmetric problems.") if sigma is not None: raise ValueError("lobpcg can only solve extremal eigenvalues.") # remove invalid options for lobpcg lobpcg_opts.pop('ncv', None) lobpcg_opts.pop('EPSType', None) # convert some arguments and defaults lobpcg_opts.setdefault('maxiter', 30) if lobpcg_opts['maxiter'] is None: lobpcg_opts['maxiter'] = 30 largest = {'SA': False, 'LA': True}[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) # avoid matrix like behaviour if isinstance(A, qu.qarray): A = A.A d = A.shape[0] # set up the initial subsspace to iterate with if v0 is None: v0 = qu.randn((d, k), dtype=A.dtype) else: # check if intial space should be projected too if P is not None and v0.shape[0] != d: v0 = qu.dag(P) @ v0 v0 = v0.reshape(d, -1) # if not enough initial states given, flesh out with random if v0.shape[1] != k: v0 = np.hstack(v0, qu.randn((d, k - v0.shape[1]), dtype=A.dtype)) lk, vk = spla.lobpcg(A=A, X=v0, B=B, largest=largest, **lobpcg_opts) if return_vecs: vk = qu.qarray(vk) return maybe_sort_and_project(lk, vk, P, sort) else: return np.sort(lk) if sort else lk
def test_norm_trace_dense(self): a = qu.qarray(np.diag([-3, 1, 7])) assert qu.norm(a, "trace") == 11 a = qu.rand_product_state(1, qtype="dop") assert_allclose(qu.norm(a, "nuc"), 1)
def to_dense(self): t = self.contract_tags(...) t.fuse_([('k', list(map(self.site_ind_id.format, self.sites)))]) return qu.qarray(t.data.reshape(-1, 1))
def svds_slepc(A, k=6, ncv=None, return_vecs=True, SVDType='cross', return_all_conv=False, tol=None, maxiter=None, comm=None): """Find the singular values for sparse matrix `a`. Parameters ---------- A : sparse matrix in csr format The matrix to solve. k : int Number of requested singular values. method : {"cross", "cyclic", "lanczos", "trlanczos"} Solver method to use. Returns ------- U : (m, k) array Left singular vectors (if ``return_vecs=True``) as columns. s : (k,) array Singular values. VH : (k, n) array Right singular vectors (if ``return_vecs=True``) as rows. """ if comm is None: comm = get_default_comm() pA = convert_mat_to_petsc(A, comm=comm) svd_solver = _init_svd_solver(nsv=k, SVDType=SVDType, tol=tol, maxiter=maxiter, ncv=ncv, comm=comm) svd_solver.setOperator(pA) svd_solver.solve() nconv = svd_solver.getConverged() k = nconv if return_all_conv else k if nconv < k: raise RuntimeError("SLEPC svds did not find enough singular triplets, " f"wanted: {k}, found: {nconv}.") rank = comm.Get_rank() if return_vecs: def usv_getter(): v, u = pA.createVecs() for i in range(k): s = svd_solver.getSingularTriplet(i, u, v) lu = gather_petsc_array(u, comm=comm, out_shape=(-1, 1)) lv = gather_petsc_array(v, comm=comm, out_shape=(1, -1)) yield lu, s, lv lus, sk, lvs = zip(*usv_getter()) sk = np.asarray(sk) if rank == 0: uk = qu.qarray(np.concatenate(lus, axis=1)) vtk = qu.qarray(np.concatenate(lvs, axis=0).conjugate()) # # check if input matrix was real -> use real output when # # petsc compiled with complex scalars and thus outputs complex # convert = (np.issubdtype(A.dtype, np.floating) and # np.issubdtype(uk.dtype, np.complexfloating)) # if convert: # uk = normalize_real_part(uk) # vtk = normalize_real_part(uk, transposed=True) res = uk, sk, vtk else: res = np.asarray([svd_solver.getValue(i) for i in range(k)]) comm.Barrier() svd_solver.destroy() return res if rank == 0 else None
def eigs_slepc(A, k, *, B=None, which=None, sigma=None, isherm=True, P=None, v0=None, ncv=None, return_vecs=True, sort=True, EPSType=None, return_all_conv=False, st_opts=None, tol=None, maxiter=None, l_win=None, comm=None): """Solve a matrix using the advanced eigensystem solver Parameters ---------- A : dense-matrix, sparse-matrix, LinearOperator or callable Operator to solve. k : int, optional Number of requested eigenpairs. B : dense-matrix, sparse-matrix, LinearOperator or callable The RHS operator defining a generalized eigenproblem. which : {"LM": "SM", "LR", "LA", "SR", "SA", "LI", "SI", "TM", "TR", "TI"} Which eigenpairs to target. See :func:`scipy.sparse.linalg.eigs`. sigma : float, optional Target eigenvalue, implies ``which='TR'`` if this is not set but ``sigma`` is. isherm : bool, optional Whether problem is hermitian or not. P : dense-matrix, sparse-matrix, LinearOperator or callable Perform the eigensolve in the subspace defined by this projector. v0 : 1D-array like, optional Initial iteration vector, e.g., informed guess at eigenvector. ncv : int, optional Subspace size, defaults to ``min(20, 2 * k)``. return_vecs : bool, optional Whether to return the eigenvectors. sort : bool, optional Whether to sort the eigenpairs in ascending real value. EPSType : {"krylovschur", 'gd', 'lobpcg', 'jd', 'ciss'}, optional SLEPc eigensolver type to use, see slepc4py.EPSType. return_all_conv : bool, optional Whether to return converged eigenpairs beyond requested subspace size st_opts : dict, optional options to send to the eigensolver internal inverter. tol : float, optional Tolerance. maxiter : int, optional Maximum number of iterations. comm : mpi4py communicator, optional MPI communicator, defaults to ``COMM_SELF`` for a single process solve. Returns ------- lk : (k,) array The eigenvalues. vk : (m, k) array Corresponding eigenvectors (if ``return_vecs=True``). """ if comm is None: comm = get_default_comm() A_is_linop = isinstance(A, sp.linalg.LinearOperator) B_is_linop = isinstance(B, sp.linalg.LinearOperator) isgen = B is not None eigensolver = _init_eigensolver( which=("SA" if (which is None) and (sigma is None) else "TR" if (which is None) and (sigma is not None) else which), EPSType=EPSType, k=k, sigma=sigma, isherm=isherm, tol=tol, ncv=ncv, maxiter=maxiter, st_opts=st_opts, comm=comm, l_win=l_win, isgen=isgen, A_is_linop=A_is_linop, B_is_linop=B_is_linop) # set up the initial operators and solver pA = convert_mat_to_petsc(A, comm=comm) pB = convert_mat_to_petsc(B, comm=comm) if isgen else None if P is not None: pP = convert_mat_to_petsc(P, comm=comm) pA = pP.transposeMatMult(pA.matMult(pP)) eigensolver.setOperators(pA, pB) if v0 is not None: eigensolver.setInitialSpace(convert_vec_to_petsc(v0, comm=comm)) eigensolver.solve() # work out how many eigenpairs to retrieve nconv = eigensolver.getConverged() k = nconv if (return_all_conv or l_win is not None) else k if nconv < k: raise RuntimeError("SLEPC eigs did not find enough eigenpairs, " f"wanted: {k}, found: {nconv}.") # get eigenvalues rank = comm.Get_rank() if rank == 0: lk = np.asarray([eigensolver.getEigenvalue(i) for i in range(k)]) lk = lk.real if isherm else lk else: res = None # gather eigenvectors if return_vecs: pvec = pA.getVecLeft() def get_vecs_local(): for i in range(k): eigensolver.getEigenvector(i, pvec) if P is not None: pvecP = pP.getVecLeft() pP.mult(pvec, pvecP) yield gather_petsc_array( pvecP if P is not None else pvec, comm=comm, out_shape=(-1, 1)) lvecs = tuple(get_vecs_local()) if rank == 0: vk = np.concatenate(lvecs, axis=1) if sort: sortinds = np.argsort(lk) lk, vk = lk[sortinds], vk[:, sortinds] # check if input matrix was real -> use real output when # petsc compiled with complex scalars and thus outputs complex convert = (isherm and np.issubdtype(A.dtype, np.floating) and np.issubdtype(vk.dtype, np.complexfloating)) if convert: vk = normalize_real_part(vk) res = lk, qu.qarray(vk) elif rank == 0: res = np.sort(lk) if sort else lk eigensolver.destroy() return res
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