def _simple_relative_entropy_implementation(self, rho, sigma, log_base=np.log, tol=1e-12): """ A simplified relative entropy implementation for use in double-checking the optimised implementation within QuTiP itself. """ # S(rho || sigma) = sum_i(p_i log p_i) - sum_ij(p_i P_ij log q_i) rvals, rvecs = sp_eigs(rho.data, rho.isherm, vecs=True) svals, svecs = sp_eigs(sigma.data, sigma.isherm, vecs=True) # Calculate S S = 0 for i in range(len(rvals)): if abs(rvals[i]) >= tol: S += rvals[i] * log_base(rvals[i]) for j in range(len(svals)): P_ij = (np.dot(rvecs[i], svecs[j].conjugate()) * np.dot(svecs[j], rvecs[i].conjugate())) if abs(svals[j]) < tol and not (abs(rvals[i]) < tol or abs(P_ij) < tol): # kernel of sigma intersects support of rho return np.inf if abs(svals[j]) >= tol: S -= rvals[i] * P_ij * log_base(svals[j]) return np.real(S)
def test_Transformation8(): "Check Qobj eigs and direct eig solver reverse transformations match" N = 10 H = rand_herm(N) # generate a random basis rand = rand_dm(N, density=1) evals, rand_basis = rand.eigenstates() evals2, rand_basis2 = sp_eigs(rand.data, isherm=1) H1 = H.transform(rand_basis, True) H2 = H.transform(rand_basis2, True) assert_((H1 - H2).norm() < 1e-6) ket = rand_ket(N) K1 = ket.transform(rand_basis,1) K2 = ket.transform(rand_basis2,1) assert_((K1 - K2).norm() < 1e-6) bra = rand_ket(N).dag() B1 = bra.transform(rand_basis,1) B2 = bra.transform(rand_basis2,1) assert_((B1 - B2).norm() < 1e-6)
def testRandhermEigs(self): "Random: Hermitian - Eigs given" H = [rand_herm([1, 2, 3, 4, 5], 0.5) for k in range(5)] for h in H: eigs = sp_eigs(h.data, h.isherm, vecs=False) assert_(np.abs(np.sum(eigs) - 15.0) < 1e-12)
def test_Transformation8(): "Check Qobj eigs and direct eig solver reverse transformations match" N = 10 H = rand_herm(N) # generate a random basis rand = rand_dm(N, density=1) evals, rand_basis = rand.eigenstates() evals2, rand_basis2 = sp_eigs(rand.data, isherm=1) H1 = H.transform(rand_basis, True) H2 = H.transform(rand_basis2, True) assert_((H1 - H2).norm() < 1e-6) ket = rand_ket(N) K1 = ket.transform(rand_basis, 1) K2 = ket.transform(rand_basis2, 1) assert_((K1 - K2).norm() < 1e-6) bra = rand_ket(N).dag() B1 = bra.transform(rand_basis, 1) B2 = bra.transform(rand_basis2, 1) assert_((B1 - B2).norm() < 1e-6)
def eigenenergies(self,sparse=False,sort='low',eigvals=0,tol=0,maxiter=100000): """Finds the Eigenenergies (Eigenvalues) of a quantum object. Eigenenergies are defined for operators or superoperators only. Parameters ---------- sparse : bool Use sparse Eigensolver sort : str Sort eigenvalues 'low' to high, or 'high' to low. eigvals : int Number of requested eigenvalues. Default is all eigenvalues. tol : float Tolerance used by sparse Eigensolver (0=machine precision). The sparse solver may not converge if the tolerance is set too low. maxiter : int Maximum number of iterations performed by sparse solver (if used). Returns ------- eigvals: array Array of eigenvalues for operator. Notes ----- The sparse eigensolver is much slower than the dense version. Use sparse only if memory requirements demand it. """ return sp_eigs(self,vecs=False,sparse=sparse,sort=sort,eigvals=eigvals,tol=tol,maxiter=maxiter)
def testRandhermEigs(self): "Random: Hermitian - Eigs given" H = [rand_herm([1,2,3,4,5],0.5) for k in range(5)] for h in H: eigs = sp_eigs(h.data, h.isherm, vecs=False) assert_(np.abs(np.sum(eigs)-15.0) < 1e-12)
def hellinger_dist(A, B, sparse=False, tol=0): """ Calculates the quantum Hellinger distance between two density matrices. Formula: hellinger_dist(A, B) = sqrt(2-2*Tr(sqrt(A)*sqrt(B))) See: D. Spehner, F. Illuminati, M. Orszag, and W. Roga, "Geometric measures of quantum correlations with Bures and Hellinger distances" arXiv:1611.03449 Parameters ---------- A : :class:`qutip.Qobj` Density matrix or state vector. B : :class:`qutip.Qobj` Density matrix or state vector with same dimensions as A. tol : float Tolerance used by sparse eigensolver, if used. (0=Machine precision) sparse : {False, True} Use sparse eigensolver. Returns ------- hellinger_dist : float Quantum Hellinger distance between A and B. Ranges from 0 to sqrt(2). Examples -------- >>> x=fock_dm(5,3) >>> y=coherent_dm(5,1) >>> hellinger_dist(x,y) 1.3725145002591095 """ if A.dims != B.dims: raise TypeError("A and B do not have same dimensions.") if A.isket or A.isbra: sqrtmA = ket2dm(A) else: sqrtmA = A.sqrtm(sparse=sparse, tol=tol) if B.isket or B.isbra: sqrtmB = ket2dm(B) else: sqrtmB = B.sqrtm(sparse=sparse, tol=tol) product = sqrtmA * sqrtmB eigs = sp_eigs(product.data, isherm=product.isherm, vecs=False, sparse=sparse, tol=tol) #np.maximum() is to avoid nan appearing sometimes due to numerical #instabilities causing np.sum(eigs) slightly (~1e-8) larger than 1 #when hellinger_dist(A, B) is called for A=B return np.sqrt(2.0 * np.maximum(0., (1.0 - np.real(np.sum(eigs)))))
def eigenstates(self, sparse=False, sort='low', eigvals=0, tol=0, maxiter=100000): """Find the eigenstates and eigenenergies. Eigenstates and Eigenvalues are defined for operators and superoperators only. Parameters ---------- sparse : bool Use sparse Eigensolver sort : str Sort eigenvalues (and vectors) 'low' to high, or 'high' to low. eigvals : int Number of requested eigenvalues. Default is all eigenvalues. tol : float Tolerance used by sparse Eigensolver (0 = machine precision).The sparse solver may not converge if the tolerance is set too low. maxiter : int Maximum number of iterations performed by sparse solver (if used). Returns ------- eigvals : array Array of eigenvalues for operator. eigvecs : array Array of quantum operators representing the oprator eigenkets. Order of eigenkets is determined by order of eigenvalues. Notes ----- The sparse eigensolver is much slower than the dense version. Use sparse only if memory requirements demand it. """ evals, evecs = sp_eigs(self, sparse=sparse, sort=sort, eigvals=eigvals, tol=tol, maxiter=maxiter) new_dims = [self.dims[0], [1] * len(self.dims[0])] new_shape = [self.shape[0], 1] ekets = np.array( [Qobj(vec, dims=new_dims, shape=new_shape) for vec in evecs]) norms = np.array([ket.norm() for ket in ekets]) return evals, ekets / norms
def norm(self, oper_norm='tr', sparse=False, tol=0, maxiter=100000): """Returns the norm of a quantum object. Norm is L2-norm for kets and trace-norm (by default) for operators. Other operator norms may be specified using the `oper_norm` argument. Parameters ---------- oper_norm : str Which norm to use for operators: trace 'tr', Frobius 'fro', one 'one', or max 'max'. This parameter does not affect the norm of a state vector. sparse : bool Use sparse eigenvalue solver for trace norm. Other norms are not affected by this parameter. tol : float Tolerance for sparse solver (if used) for trace norm. The sparse solver may not converge if the tolerance is set too low. maxiter : int Maximum number of iterations performed by sparse solver (if used) for trace norm. Returns ------- norm : float The requested norm of the operator or state quantum object. Notes ----- The sparse eigensolver is much slower than the dense version. Use sparse only if memory requirements demand it. """ if self.type == 'oper' or self.type == 'super': if oper_norm == 'tr': vals = sp_eigs(self, vecs=False, sparse=sparse, tol=tol, maxiter=maxiter) return sum(sqrt(abs(vals)**2)) elif oper_norm == 'fro': return _sp_fro_norm(self) elif oper_norm == 'one': return _sp_one_norm(self) elif oper_norm == 'max': return _sp_max_norm(self) else: raise ValueError( "Operator norm must be 'tr', 'fro', 'one', or 'max'.") else: return _sp_L2_norm(self)
def testRanddm(self): "random density matrix" R = [rand_dm(5) for k in range(5)] for r in R: assert_(r.tr() - 1.0 < 1e-15) # verify all eigvals are >=0 assert_(not any(sp_eigs(r.data, r.isherm, vecs=False)) < 0) # verify hermitian assert_(r.isherm)
def sqrtm(self, sparse=False, tol=0, maxiter=100000): """The sqrt of a quantum operator. Operator must be square. Parameters ---------- sparse : bool Use sparse eigenvalue/vector solver. tol : float Tolerance used by sparse solver (0 = machine precision). maxiter : int Maximum number of iterations used by sparse solver. Returns ------- oper: qobj Matrix square root of operator. Raises ------ TypeError Quantum object is not square. Notes ----- The sparse eigensolver is much slower than the dense version. Use sparse only if memory requirements demand it. """ if self.dims[0][0] == self.dims[1][0]: evals, evecs = sp_eigs(self, sparse=sparse, tol=tol, maxiter=maxiter) numevals = len(evals) dV = sp.spdiags(np.sqrt(np.abs(evals)), 0, numevals, numevals, format='csr') evecs = sp.hstack(evecs, format='csr') spDv = dV.dot(evecs.conj().T) out = Qobj(evecs.dot(spDv), dims=self.dims, shape=self.shape) if qset.auto_tidyup: return out.tidyup() else: return out else: raise TypeError('Invalid operand for matrix square root')
def testRanddmEigs(self): "Random: Density matrix - Eigs given" R = [] for k in range(5): eigs = np.random.random(5) eigs /= np.sum(eigs) R += [rand_dm(eigs)] for r in R: assert_(r.tr() - 1.0 < 1e-15) # verify all eigvals are >=0 assert_(not any(sp_eigs(r.data, r.isherm, vecs=False)) < 0) # verify hermitian assert_(r.isherm)
def _entropy_relative(rho, sigma, base=e, sparse=False): """ ****NEEDS TO BE WORKED ON**** Calculates the relative entropy S(rho||sigma) between two density matrices. Parameters ---------- rho : qobj First density matrix. sigma : qobj Second density matrix. base : {e,2} Base of logarithm. Returns ------- rel_ent : float Value of relative entropy. """ if rho.type != 'oper' or sigma.type != 'oper': raise TypeError("Inputs must be density matrices..") # sigma terms svals = sp_eigs(sigma.data, sigma.isherm, vecs=False, sparse=sparse) snzvals = svals[svals != 0] if base == 2: slogvals = log2(snzvals) elif base == e: slogvals = log(snzvals) else: raise ValueError("Base must be 2 or e.") # rho terms rvals = sp_eigs(rho.data, rho.isherm, vecs=False, sparse=sparse) rnzvals = rvals[rvals != 0] # calculate tr(rho*log sigma) rel_trace = float(real(sum(rnzvals * slogvals))) return -entropy_vn(rho, base, sparse) - rel_trace
def _entropy_relative(rho, sigma, base=e, sparse=False): """ ****NEEDS TO BE WORKED ON**** (after 2.0 release) Calculates the relative entropy S(rho||sigma) between two density matrices.. Parameters ---------- rho : qobj First density matrix. sigma : qobj Second density matrix. base : {e,2} Base of logarithm. Returns ------- rel_ent : float Value of relative entropy. """ if rho.type != 'oper' or sigma.type != 'oper': raise TypeError("Inputs must be density matrices..") # sigma terms svals = sp_eigs(sigma, vecs=False, sparse=sparse) snzvals = svals[svals != 0] if base == 2: slogvals = log2(snzvals) elif base == e: slogvals = log(snzvals) else: raise ValueError("Base must be 2 or e.") # rho terms rvals = sp_eigs(rho, vecs=False, sparse=sparse) rnzvals = rvals[rvals != 0] # calculate tr(rho*log sigma) rel_trace = float(real(sum(rnzvals * slogvals))) return -entropy_vn(rho, base, sparse) - rel_trace
def norm(self,oper_norm='tr',sparse=False,tol=0,maxiter=100000): """Returns the norm of a quantum object. Norm is L2-norm for kets and trace-norm (by default) for operators. Other operator norms may be specified using the `oper_norm` argument. Parameters ---------- oper_norm : str Which norm to use for operators: trace 'tr', Frobius 'fro',one 'one', or max 'max'. This parameter does not affect the norm of a state vector. sparse : bool Use sparse eigenvalue solver for trace norm. Other norms are not affected by this parameter. tol : float Tolerance for sparse solver (if used) for trace norm. The sparse solver may not converge if the tolerance is set too low. maxiter : int Maximum number of iterations performed by sparse solver (if used) for trace norm. Returns ------- norm : float The requested norm of the operator or state quantum object. Notes ----- The sparse eigensolver is much slower than the dense version. Use sparse only if memory requirements demand it. """ if self.type=='oper' or self.type=='super': if oper_norm=='tr': vals=sp_eigs(self,vecs=False,sparse=sparse,tol=tol,maxiter=maxiter) return sum(sqrt(abs(vals)**2)) elif oper_norm=='fro': return _sp_fro_norm(self) elif oper_norm=='one': return _sp_one_norm(self) elif oper_norm=='max': return _sp_max_norm(self) else: raise ValueError("Operator norm must be 'tr', 'fro', 'one', or 'max'.") else: return _sp_L2_norm(self)
def power(qstate, power, sparse=False, tol=0, maxiter=100000): """power of a quantum operator. Operator must be square. Parameters ---------- qstate: qutip.Qobj() quantum state power: float power sparse : bool Use sparse eigenvalue/vector solver. tol : float Tolerance used by sparse solver (0 = machine precision). maxiter : int Maximum number of iterations used by sparse solver. Returns ------- oper : qobj Matrix square root of operator. Raises ------ TypeError Quantum object is not square. Notes ----- The sparse eigensolver is much slower than the dense version. Use sparse only if memory requirements demand it. """ if qstate.dims[0][0] == qstate.dims[1][0]: evals, evecs = sp_eigs(qstate.data, qstate.isherm, sparse=sparse, tol=tol, maxiter=maxiter) numevals = len(evals) dV = spdiags(np.power(evals, power, dtype=complex), 0, numevals, numevals, format='csr') if qstate.isherm: spDv = dV.dot(evecs.T.conj().T) else: spDv = dV.dot(np.linalg.inv(evecs.T)) out = qu.Qobj(evecs.T.dot(spDv), dims=qstate.dims) return out.tidyup() if qu.settings.auto_tidyup else out else: raise TypeError('Invalid operand for matrix square root')
def eigenenergies(self, sparse=False, sort='low', eigvals=0, tol=0, maxiter=100000): """Finds the Eigenenergies (Eigenvalues) of a quantum object. Eigenenergies are defined for operators or superoperators only. Parameters ---------- sparse : bool Use sparse Eigensolver sort : str Sort eigenvalues 'low' to high, or 'high' to low. eigvals : int Number of requested eigenvalues. Default is all eigenvalues. tol : float Tolerance used by sparse Eigensolver (0=machine precision). The sparse solver may not converge if the tolerance is set too low. maxiter : int Maximum number of iterations performed by sparse solver (if used). Returns ------- eigvals: array Array of eigenvalues for operator. Notes ----- The sparse eigensolver is much slower than the dense version. Use sparse only if memory requirements demand it. """ return sp_eigs(self, vecs=False, sparse=sparse, sort=sort, eigvals=eigvals, tol=tol, maxiter=maxiter)
def sqrtm(self,sparse=False,tol=0,maxiter=100000): """The sqrt of a quantum operator. Operator must be square. Parameters ---------- sparse : bool Use sparse eigenvalue/vector solver. tol : float Tolerance used by sparse solver (0 = machine precision). maxiter : int Maximum number of iterations used by sparse solver. Returns ------- oper: qobj Matrix square root of operator. Raises ------ TypeError Quantum object is not square. Notes ----- The sparse eigensolver is much slower than the dense version. Use sparse only if memory requirements demand it. """ if self.dims[0][0]==self.dims[1][0]: evals,evecs=sp_eigs(self,sparse=sparse,tol=tol,maxiter=maxiter) numevals=len(evals) dV=sp.spdiags(np.sqrt(np.abs(evals)),0,numevals,numevals,format='csr') evecs=sp.hstack(evecs,format='csr') spDv=dV.dot(evecs.conj().T) out=Qobj(evecs.dot(spDv),dims=self.dims,shape=self.shape) if qset.auto_tidyup: return out.tidyup() else: return out else: raise TypeError('Invalid operand for matrix square root')
def eigenstates(self,sparse=False,sort='low',eigvals=0,tol=0,maxiter=100000): """Find the eigenstates and eigenenergies. Eigenstates and Eigenvalues are defined for operators and superoperators only. Parameters ---------- sparse : bool Use sparse Eigensolver sort : str Sort eigenvalues (and vectors) 'low' to high, or 'high' to low. eigvals : int Number of requested eigenvalues. Default is all eigenvalues. tol : float Tolerance used by sparse Eigensolver (0 = machine precision).The sparse solver may not converge if the tolerance is set too low. maxiter : int Maximum number of iterations performed by sparse solver (if used). Returns ------- eigvals : array Array of eigenvalues for operator. eigvecs : array Array of quantum operators representing the oprator eigenkets. Order of eigenkets is determined by order of eigenvalues. Notes ----- The sparse eigensolver is much slower than the dense version. Use sparse only if memory requirements demand it. """ evals,evecs = sp_eigs(self,sparse=sparse,sort=sort,eigvals=eigvals,tol=tol,maxiter=maxiter) new_dims = [self.dims[0], [1] * len(self.dims[0])] new_shape = [self.shape[0], 1] ekets = np.array([Qobj(vec, dims=new_dims, shape=new_shape) for vec in evecs]) norms=np.array([ket.norm() for ket in ekets]) return evals, ekets/norms
def groundstate(self, sparse=False, tol=0, maxiter=100000): """Finds the ground state Eigenvalue and Eigenvector. Defined for quantum operators or superoperators only. Parameters ---------- sparse : bool Use sparse Eigensolver tol : float Tolerance used by sparse Eigensolver (0 = machine precision). The sparse solver may not converge if the tolerance is set too low. maxiter : int Maximum number of iterations performed by sparse solver (if used). Returns ------- eigval : float Eigenvalue for the ground state of quantum operator. eigvec : qobj Eigenket for the ground state of quantum operator. Notes ----- The sparse eigensolver is much slower than the dense version. Use sparse only if memory requirements demand it. """ grndval, grndvec = sp_eigs(self, sparse=sparse, eigvals=1, tol=tol, maxiter=maxiter) new_dims = [self.dims[0], [1] * len(self.dims[0])] new_shape = [self.shape[0], 1] grndvec = Qobj(grndvec[0], dims=new_dims, shape=new_shape) grndvec = grndvec / grndvec.norm() return grndval[0], grndvec
def tracedist(A, B, sparse=False, tol=0): """ Calculates the trace distance between two density matrices.. See: Nielsen & Chuang, "Quantum Computation and Quantum Information" Parameters ----------!= A : qobj Density matrix or state vector. B : qobj Density matrix or state vector with same dimensions as A. tol : float Tolerance used by sparse eigensolver, if used. (0=Machine precision) sparse : {False, True} Use sparse eigensolver. Returns ------- tracedist : float Trace distance between A and B. Examples -------- >>> x=fock_dm(5,3) >>> y=coherent_dm(5,1) >>> tracedist(x,y) 0.9705143161472971 """ if A.isket or A.isbra: A = ket2dm(A) if B.isket or B.isbra: B = ket2dm(B) if A.dims != B.dims: raise TypeError("A and B do not have same dimensions.") diff = A - B diff = diff.dag() * diff vals = sp_eigs(diff.data, diff.isherm, vecs=False, sparse=sparse, tol=tol) return float(np.real(0.5 * np.sum(np.sqrt(np.abs(vals)))))
def entropy_vn(rho, base=e, sparse=False): """ Von-Neumann entropy of density matrix Parameters ---------- rho : qobj Density matrix. base : {e,2} Base of logarithm. Other Parameters ---------------- sparse : {False,True} Use sparse eigensolver. Returns ------- entropy : float Von-Neumann entropy of `rho`. Examples -------- >>> rho=0.5*fock_dm(2,0)+0.5*fock_dm(2,1) >>> entropy_vn(rho,2) 1.0 """ if rho.type == 'ket' or rho.type == 'bra': rho = ket2dm(rho) vals = sp_eigs(rho, vecs=False, sparse=sparse) nzvals = vals[vals != 0] if base == 2: logvals = log2(nzvals) elif base == e: logvals = log(nzvals) else: raise ValueError("Base must be 2 or e.") return float(real(-sum(nzvals * logvals)))
def tracedist(A,B,sparse=False,tol=0): """ Calculates the trace distance between two density matricies. See: Nielsen & Chuang, "Quantum Computation and Quantum Information" Parameters ---------- A : qobj Density matrix. B : qobj: Density matrix with same dimensions as A. Other Parameters ---------------- tol : float Tolerance used by sparse eigensolver. (0=Machine precision) sparse : {False, True} Use sparse eigensolver. Returns ------- tracedist : float Trace distance between A and B. Examples -------- >>> x=fock_dm(5,3) >>> y=coherent_dm(5,1) >>> tracedist(x,y) 0.9705143161472971 """ if A.dims!=B.dims: raise TypeError('Density matricies do not have same dimensions.') else: diff=A-B diff=diff.dag()*diff vals=sp_eigs(diff,vecs=False,sparse=sparse,tol=tol) return float(real(0.5*sum(sqrt(vals))))
def tracedist(A, B, sparse=False, tol=0): """ Calculates the trace distance between two density matricies. See: Nielsen & Chuang, "Quantum Computation and Quantum Information" Parameters ---------- A : qobj Density matrix. B : qobj: Density matrix with same dimensions as A. Other Parameters ---------------- tol : float Tolerance used by sparse eigensolver. (0=Machine precision) sparse : {False, True} Use sparse eigensolver. Returns ------- tracedist : float Trace distance between A and B. Examples -------- >>> x=fock_dm(5,3) >>> y=coherent_dm(5,1) >>> tracedist(x,y) 0.9705143161472971 """ if A.dims != B.dims: raise TypeError('Density matricies do not have same dimensions.') else: diff = A - B diff = diff.dag() * diff vals = sp_eigs(diff, vecs=False, sparse=sparse, tol=tol) return float(real(0.5 * sum(sqrt(vals))))
def entropy_vn(rho,base=e,sparse=False): """ Von-Neumann entropy of density matrix Parameters ---------- rho : qobj Density matrix. base : {e,2} Base of logarithm. Other Parameters ---------------- sparse : {False,True} Use sparse eigensolver. Returns ------- entropy : float Von-Neumann entropy of `rho`. Examples -------- >>> rho=0.5*fock_dm(2,0)+0.5*fock_dm(2,1) >>> entropy_vn(rho,2) 1.0 """ if rho.type=='ket' or rho.type=='bra': rho=ket2dm(rho) vals=sp_eigs(rho,vecs=False,sparse=sparse) nzvals=vals[vals!=0] if base==2: logvals=log2(nzvals) elif base==e: logvals=log(nzvals) else: raise ValueError("Base must be 2 or e.") return float(real(-sum(nzvals*logvals)))
def groundstate(self,sparse=False,tol=0,maxiter=100000): """Finds the ground state Eigenvalue and Eigenvector. Defined for quantum operators or superoperators only. Parameters ---------- sparse : bool Use sparse Eigensolver tol : float Tolerance used by sparse Eigensolver (0 = machine precision). The sparse solver may not converge if the tolerance is set too low. maxiter : int Maximum number of iterations performed by sparse solver (if used). Returns ------- eigval : float Eigenvalue for the ground state of quantum operator. eigvec : qobj Eigenket for the ground state of quantum operator. Notes ----- The sparse eigensolver is much slower than the dense version. Use sparse only if memory requirements demand it. """ grndval,grndvec=sp_eigs(self,sparse=sparse,eigvals=1,tol=tol,maxiter=maxiter) new_dims = [self.dims[0], [1] * len(self.dims[0])] new_shape = [self.shape[0], 1] grndvec=Qobj(grndvec[0],dims=new_dims,shape=new_shape) grndvec=grndvec/grndvec.norm() return grndval[0],grndvec
def _spectral_decomp(self, k): """ Calculates the diagonalization of the dynamics generator generating lists of eigenvectors, propagators in the diagonalised basis, and the 'factormatrix' used in calculating the propagator gradient """ if self.memory_optimization >= 2: sparse = True else: sparse = False if self.oper_dtype == Qobj: H = self._dyn_gen[k] # Returns eigenvalues as array (row) # and eigenvectors as rows of an array eig_val, eig_vec = sp_eigs(H.data, H.isherm, sparse=sparse) eig_vec = eig_vec.T elif self.oper_dtype == np.ndarray: H = self._dyn_gen[k] # returns row vector of eigenvals, columns with the eigenvecs eig_val, eig_vec = np.linalg.eig(H) else: if sparse: H = self._dyn_gen[k].toarray() else: H = self._dyn_gen[k] # returns row vector of eigenvals, columns with the eigenvecs eig_val, eig_vec = la.eigh(H) # assuming H is an nxn matrix, find n n = self.get_drift_dim() # Calculate the propagator in the diagonalised basis eig_val_tau = -1j*eig_val*self.tau[k] prop_eig = np.exp(eig_val_tau) # Generate the factor matrix through the differences # between each of the eigenvectors and the exponentiations # create nxn matrix where each eigen val is repeated n times # down the columns o = np.ones([n, n]) eig_val_cols = eig_val_tau*o # calculate all the differences by subtracting it from its transpose eig_val_diffs = eig_val_cols - eig_val_cols.T # repeat for the propagator prop_eig_cols = prop_eig*o prop_eig_diffs = prop_eig_cols - prop_eig_cols.T # the factor matrix is the elementwise quotient of the # differeneces between the exponentiated eigen vals and the # differences between the eigen vals # need to avoid division by zero that would arise due to denegerate # eigenvalues and the diagonals degen_mask = np.abs(eig_val_diffs) < self.fact_mat_round_prec eig_val_diffs[degen_mask] = 1 factors = prop_eig_diffs / eig_val_diffs # for degenerate eigenvalues the factor is just the exponent factors[degen_mask] = prop_eig_cols[degen_mask] # Store eigenvectors, propagator and factor matric # for use in propagator computations self._decomp_curr[k] = True if isinstance(factors, np.ndarray): self._dyn_gen_factormatrix[k] = factors else: self._dyn_gen_factormatrix[k] = np.array(factors) if self.oper_dtype == Qobj: self._prop_eigen[k] = Qobj(np.diagflat(prop_eig), dims=self.dyn_dims) self._dyn_gen_eigenvectors[k] = Qobj(eig_vec, dims=self.dyn_dims) if self._dyn_gen_eigenvectors_adj is not None: self._dyn_gen_eigenvectors_adj[k] = \ self._dyn_gen_eigenvectors[k].dag() else: self._prop_eigen[k] = np.diagflat(prop_eig) self._dyn_gen_eigenvectors[k] = eig_vec if self._dyn_gen_eigenvectors_adj is not None: self._dyn_gen_eigenvectors_adj[k] = \ self._dyn_gen_eigenvectors[k].conj().T
def testRandhermPosDef(self): "Random: Hermitian - Positive semi-def" H = [rand_herm(5, pos_def=1) for k in range(5)] for h in H: assert_(not any(sp_eigs(h.data, h.isherm, vecs=False)) < 0)
def _spectral_decomp(self, k): """ Calculates the diagonalization of the dynamics generator generating lists of eigenvectors, propagators in the diagonalised basis, and the 'factormatrix' used in calculating the propagator gradient """ if self.memory_optimization >= 2: sparse = True else: sparse = False if self.oper_dtype == Qobj: H = self._dyn_gen[k] # Returns eigenvalues as array (row) # and eigenvectors as rows of an array eig_val, eig_vec = sp_eigs(H.data, H.isherm, sparse=sparse) eig_vec = eig_vec.T elif self.oper_dtype == np.ndarray: H = self._dyn_gen[k] # returns row vector of eigenvals, columns with the eigenvecs eig_val, eig_vec = np.linalg.eig(H) else: if sparse: H = self._dyn_gen[k].toarray() else: H = self._dyn_gen[k] # returns row vector of eigenvals, columns with the eigenvecs eig_val, eig_vec = la.eigh(H) # assuming H is an nxn matrix, find n n = self.get_drift_dim() # Calculate the propagator in the diagonalised basis eig_val_tau = -1j * eig_val * self.tau[k] prop_eig = np.exp(eig_val_tau) # Generate the factor matrix through the differences # between each of the eigenvectors and the exponentiations # create nxn matrix where each eigen val is repeated n times # down the columns o = np.ones([n, n]) eig_val_cols = eig_val_tau * o # calculate all the differences by subtracting it from its transpose eig_val_diffs = eig_val_cols - eig_val_cols.T # repeat for the propagator prop_eig_cols = prop_eig * o prop_eig_diffs = prop_eig_cols - prop_eig_cols.T # the factor matrix is the elementwise quotient of the # differeneces between the exponentiated eigen vals and the # differences between the eigen vals # need to avoid division by zero that would arise due to denegerate # eigenvalues and the diagonals degen_mask = np.abs(eig_val_diffs) < self.fact_mat_round_prec eig_val_diffs[degen_mask] = 1 factors = prop_eig_diffs / eig_val_diffs # for degenerate eigenvalues the factor is just the exponent factors[degen_mask] = prop_eig_cols[degen_mask] # Store eigenvectors, propagator and factor matric # for use in propagator computations self._decomp_curr[k] = True if isinstance(factors, np.ndarray): self._dyn_gen_factormatrix[k] = factors else: self._dyn_gen_factormatrix[k] = np.array(factors) if self.oper_dtype == Qobj: self._prop_eigen[k] = Qobj(np.diagflat(prop_eig), dims=self.dyn_dims) self._dyn_gen_eigenvectors[k] = Qobj(eig_vec, dims=self.dyn_dims) if self._dyn_gen_eigenvectors_adj is not None: self._dyn_gen_eigenvectors_adj[k] = \ self._dyn_gen_eigenvectors[k].dag() else: self._prop_eigen[k] = np.diagflat(prop_eig) self._dyn_gen_eigenvectors[k] = eig_vec if self._dyn_gen_eigenvectors_adj is not None: self._dyn_gen_eigenvectors_adj[k] = \ self._dyn_gen_eigenvectors[k].conj().T
def testRandhermPosDef(self): "Random: Hermitian - Positive semi-def" H = [rand_herm(5,pos_def=1) for k in range(5)] for h in H: assert_(not any(sp_eigs(h.data, h.isherm, vecs=False)) < 0)
def entropy_relative(rho, sigma, base=e, sparse=False, tol=1e-12): """ Calculates the relative entropy S(rho||sigma) between two density matrices. Parameters ---------- rho : :class:`qutip.Qobj` First density matrix (or ket which will be converted to a density matrix). sigma : :class:`qutip.Qobj` Second density matrix (or ket which will be converted to a density matrix). base : {e,2} Base of logarithm. Defaults to e. sparse : bool Flag to use sparse solver when determining the eigenvectors of the density matrices. Defaults to False. tol : float Tolerance to use to detect 0 eigenvalues or dot producted between eigenvectors. Defaults to 1e-12. Returns ------- rel_ent : float Value of relative entropy. Guaranteed to be greater than zero and should equal zero only when rho and sigma are identical. Examples -------- First we define two density matrices: >>> rho = qutip.ket2dm(qutip.ket("00")) >>> sigma = rho + qutip.ket2dm(qutip.ket("01")) >>> sigma = sigma.unit() Then we calculate their relative entropy using base 2 (i.e. ``log2``) and base e (i.e. ``log``). >>> qutip.entropy_relative(rho, sigma, base=2) 1.0 >>> qutip.entropy_relative(rho, sigma) 0.6931471805599453 References ---------- See Nielsen & Chuang, "Quantum Computation and Quantum Information", Section 11.3.1, pg. 511 for a detailed explanation of quantum relative entropy. """ if rho.isket: rho = ket2dm(rho) if sigma.isket: sigma = ket2dm(sigma) if not rho.isoper or not sigma.isoper: raise TypeError("Inputs must be density matrices.") if rho.dims != sigma.dims: raise ValueError("Inputs must have the same shape and dims.") if base == 2: log_base = log2 elif base == e: log_base = log else: raise ValueError("Base must be 2 or e.") # S(rho || sigma) = sum_i(p_i log p_i) - sum_ij(p_i P_ij log q_i) # # S is +inf if the kernel of sigma (i.e. svecs[svals == 0]) has non-trivial # intersection with the support of rho (i.e. rvecs[rvals != 0]). rvals, rvecs = sp_eigs(rho.data, rho.isherm, vecs=True, sparse=sparse) if any(abs(imag(rvals)) >= tol): raise ValueError("Input rho has non-real eigenvalues.") rvals = real(rvals) svals, svecs = sp_eigs(sigma.data, sigma.isherm, vecs=True, sparse=sparse) if any(abs(imag(svals)) >= tol): raise ValueError("Input sigma has non-real eigenvalues.") svals = real(svals) # Calculate inner products of eigenvectors and return +inf if kernel # of sigma overlaps with support of rho. P = abs(inner(rvecs, conj(svecs)))**2 if (rvals >= tol) @ (P >= tol) @ (svals < tol): return inf # Avoid -inf from log(0) -- these terms will be multiplied by zero later # anyway svals[abs(svals) < tol] = 1 nzrvals = rvals[abs(rvals) >= tol] # Calculate S S = nzrvals @ log_base(nzrvals) - rvals @ P @ log_base(svals) # the relative entropy is guaranteed to be >= 0, so we clamp the # calculated value to 0 to avoid small violations of the lower bound. return max(0, S)