def test_depolarize(self, stack): rho = qu.rand_rho(2) I, X, Y, Z = (qu.pauli(s) for s in 'IXYZ') es = [qu.expec(rho, A) for A in (X, Y, Z)] p = 0.1 Ek = [(1 - p)**0.5 * I, (p / 3)**0.5 * X, (p / 3)**0.5 * Y, (p / 3)**0.5 * Z] if stack: Ek = np.stack(Ek, axis=0) sigma = qu.kraus_op(rho, Ek, check=True) es2 = [qu.expec(sigma, A) for A in (X, Y, Z)] assert qu.tr(sigma) == pytest.approx(1.0) assert all(abs(e2) < abs(e) for e, e2 in zip(es, es2)) sig_exp = sum(E @ rho @ qu.dag(E) for E in Ek) assert_allclose(sig_exp, sigma)
def test_subsystem(self): rho = qu.rand_rho(6) dims = [3, 2] I, X, Y, Z = (qu.pauli(s) for s in 'IXYZ') mi_i = qu.mutual_information(rho, dims) p = 0.1 Ek = [(1 - p)**0.5 * I, (p / 3)**0.5 * X, (p / 3)**0.5 * Y, (p / 3)**0.5 * Z] with pytest.raises(ValueError): qu.kraus_op(rho, qu.randn((3, 2, 2)), check=True, dims=dims, where=1) sigma = qu.kraus_op(rho, Ek, check=True, dims=dims, where=1) mi_f = qu.mutual_information(sigma, dims) assert mi_f < mi_i assert qu.tr(sigma) == pytest.approx(1.0) sig_exp = sum( (qu.eye(3) & E) @ rho @ qu.dag(qu.eye(3) & E) for E in Ek) assert_allclose(sig_exp, sigma)
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 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