def gradient_CFI(self, M): ''' Input: 1) M: TYPE: list of Qobj (matrix) DESCRIPTION: merasurement. It takes the form [M1,M2,...], where M1, M2 ... are matrices and satisfies M1+M2+... = identity matrix. Output: 1) updated values of self.control_coefficients. Notice: To run this function, the function 'propagation_single()' has to run first. ''' num = len(self.times) dH0 = 1.j * Qobj(liouvillian(self.Hamiltonian_derivative[0]).full()) dim = self.freeHamiltonian.dims[0][0] dt = self.times[1] - self.times[0] Hc_coeff = self.control_coefficients D = self.propagator rhoT_vec = self.rho[num-1] drhoT_vec = self.rho_derivative[num-1] rhoT = Qobj(vec2mat(rhoT_vec.full())) drhoT = Qobj(vec2mat(drhoT_vec.full())) L1 = Qobj(np.array([[0. for i in range(0,dim)] for k in range(0,dim)])) L2 = Qobj(np.array([[0. for i in range(0,dim)] for k in range(0,dim)])) for mi in range(0, len(M)): ptemp = (rhoT * M[mi]).tr() dptemp = (drhoT * M[mi]).tr() if ptemp != 0: L1 += (dptemp / ptemp) * M[mi] L2 += ((dptemp / ptemp)**2) * M[mi] for ki in range(0, len(self.control_Hamiltonian)): Hk = 1.j * Qobj(liouvillian(self.control_Hamiltonian[ki]).full()) Hc_ki = Hc_coeff[ki] for ti in range(0,num): Mj1 = 1.j * D[ti+1][num-1] * Hk * self.rho[ti] Mj2 = Qobj(np.zeros((dim*dim, 1))) for ri in range(0, ti+1): Mj2 += D[ti+1][num-1] * Hk * D[ri+1][ti] * dH0 * self.rho[ri] Mj3 = Qobj(np.zeros((dim*dim, 1))) for ri in range(ti+1, num): Mj3 += D[ri+1][num-1] * dH0 * D[ti+1][ri] * Hk * self.rho[ti] Mj1mat = Qobj(vec2mat(Mj1.full())) Mj2mat = Qobj(vec2mat(Mj2.full())) Mj3mat = Qobj(vec2mat(Mj3.full())) term1 = dt * (L2 * Mj1mat).tr() term2 = -2 * (dt * dt) * (L1 * (Mj2mat+Mj3mat)).tr() delta = np.real(term1 + term2) Hc_ki[ti] += Hc_ki[ti] + self.epsilon * delta Hc_coeff[ki] = Hc_ki self.control_coefficients = Hc_coeff
def _mesolve_const(H, rho0, tlist, c_op_list, expt_ops, args, opt): """! Evolve the density matrix using an ODE solver, for constant hamiltonian and collapse operators. """ if debug: print(inspect.stack()[0][3]) # # check initial state # if isket(rho0): # if initial state is a ket and no collapse operator where given, # fallback on the unitary schrodinger equation solver if len(c_op_list) == 0 and isoper(H): return _sesolve_const(H, rho0, tlist, expt_ops, args, opt) # Got a wave function as initial state: convert to density matrix. rho0 = rho0 * rho0.dag() # # construct liouvillian # if opt.tidy: H = H.tidyup(opt.atol) if issuper(H): L = H + liouvillian(None, c_op_list) else: L = liouvillian(H, c_op_list) # # setup integrator # initial_vector = mat2vec(rho0.full()) r = scipy.integrate.ode(cy_ode_rhs) r.set_f_params(L.data.data, L.data.indices, L.data.indptr) r.set_integrator('zvode', method=opt.method, order=opt.order, atol=opt.atol, rtol=opt.rtol, nsteps=opt.nsteps, first_step=opt.first_step, min_step=opt.min_step, max_step=opt.max_step) r.set_initial_value(initial_vector, tlist[0]) # # call generic ODE code # return _generic_ode_solve(r, rho0, tlist, expt_ops, opt, vec2mat)
def __init__(self, hamiltonian, coupling, coup_strength, ck, vk, ncut, beta=np.inf): self.hamiltonian = hamiltonian self.coupling = coupling self.ck, self.vk = ck, vk self.ncut = ncut self.kcut = len(ck) nhe, he2idx, idx2he = _heom_state_dictionaries([ncut + 1] * (len(ck)), ncut) self.nhe = nhe self.he2idx = he2idx self.idx2he = idx2he self.N = self.hamiltonian.shape[0] total_nhe = int( factorial(self.ncut + self.kcut) / (factorial(self.ncut) * factorial(self.kcut))) self.total_nhe = total_nhe self.hshape = (total_nhe, self.N**2) self.L = liouvillian(self.hamiltonian, []).data self.grad_shape = (self.N**2, self.N**2) self.spreQ = spre(coupling).data self.spostQ = spost(coupling).data self.L_helems = lil_matrix( (total_nhe * self.N**2, total_nhe * self.N**2), dtype=np.complex) self.lam = coup_strength self.full_hierarchy = []
def _spectrum_es(H, wlist, c_ops, a_op, b_op): """ Internal function for calculating the spectrum of the correlation function :math:`\left<A(\\tau)B(0)\\right>`. """ if debug: print(inspect.stack()[0][3]) # construct the Liouvillian L = liouvillian(H, c_ops) # find the steady state density matrix and a_op and b_op expecation values rho0 = steadystate(L) a_op_ss = expect(a_op, rho0) b_op_ss = expect(b_op, rho0) # eseries solution for (b * rho0)(t) es = ode2es(L, b_op * rho0) # correlation corr_es = expect(a_op, es) # covariance cov_es = corr_es - np.real(np.conjugate(a_op_ss) * b_op_ss) # spectrum spectrum = esspec(cov_es, wlist) return spectrum
def _correlation_es_2t(H, state0, tlist, taulist, c_ops, a_op, b_op, c_op): """ Internal function for calculating the three-operator two-time correlation function: <A(t)B(t+tau)C(t)> using an exponential series solver. """ # the solvers only work for positive time differences and the correlators # require positive tau if state0 is None: rho0 = steadystate(H, c_ops) tlist = [0] elif isket(state0): rho0 = ket2dm(state0) else: rho0 = state0 if debug: print(inspect.stack()[0][3]) # contruct the Liouvillian L = liouvillian(H, c_ops) corr_mat = np.zeros([np.size(tlist), np.size(taulist)], dtype=complex) solES_t = ode2es(L, rho0) # evaluate the correlation function for t_idx in range(len(tlist)): rho_t = esval(solES_t, [tlist[t_idx]]) solES_tau = ode2es(L, c_op * rho_t * a_op) corr_mat[t_idx, :] = esval(expect(b_op, solES_tau), taulist) return corr_mat
def _jc_liouvillian(N): from qutip.tensor import tensor from qutip.operators import destroy, qeye from qutip.superoperator import liouvillian wc = 1.0 * 2 * np.pi # cavity frequency wa = 1.0 * 2 * np.pi # atom frequency g = 0.05 * 2 * np.pi # coupling strength kappa = 0.005 # cavity dissipation rate gamma = 0.05 # atom dissipation rate n_th_a = 1 # temperature in frequency units use_rwa = 0 # operators a = tensor(destroy(N), qeye(2)) sm = tensor(qeye(N), destroy(2)) # Hamiltonian if use_rwa: H = wc * a.dag() * a + wa * sm.dag() * sm + g * (a.dag() * sm + a * sm.dag()) else: H = wc * a.dag() * a + wa * sm.dag() * sm + g * (a.dag() + a) * (sm + sm.dag()) c_op_list = [] rate = kappa * (1 + n_th_a) if rate > 0.0: c_op_list.append(np.sqrt(rate) * a) rate = kappa * n_th_a if rate > 0.0: c_op_list.append(np.sqrt(rate) * a.dag()) rate = gamma if rate > 0.0: c_op_list.append(np.sqrt(rate) * sm) return liouvillian(H, c_op_list)
def _mesolve_QobjEvo(H, c_ops, tlist, args, opt): """ Prepare the system for the solver, H can be an QobjEvo. """ H_td = QobjEvo(H, args, tlist=tlist) if not issuper(H_td.cte): L_td = liouvillian(H_td) else: L_td = H_td for op in c_ops: op_td = QobjEvo(op, args, tlist=tlist) if not issuper(op_td.cte): op_td = lindblad_dissipator(op_td) L_td += op_td if opt.rhs_with_state: L_td._check_old_with_state() nthread = opt.openmp_threads if opt.use_openmp else 0 L_td.compile(omp=nthread) ss = SolverSystem() ss.H = L_td ss.makefunc = _qobjevo_set solver_safe["mesolve"] = ss return ss
def _opto_liouvillian(N): from qutip.tensor import tensor from qutip.operators import destroy, qeye from qutip.superoperator import liouvillian Nc = 5 # Number of cavity states Nm = N # Number of mech states kappa = 0.3 # Cavity damping rate E = 0.1 # Driving Amplitude g0 = 2.4 * kappa # Coupling strength Qm = 1e4 # Mech quality factor gamma = 1 / Qm # Mech damping rate n_th = 1 # Mech bath temperature delta = -0.43 # Detuning a = tensor(destroy(Nc), qeye(Nm)) b = tensor(qeye(Nc), destroy(Nm)) num_b = b.dag() * b num_a = a.dag() * a H = -delta * (num_a) + num_b + g0 * (b.dag() + b) * num_a + E * (a.dag() + a) cc = np.sqrt(kappa) * a cm = np.sqrt(gamma * (1.0 + n_th)) * b cp = np.sqrt(gamma * n_th) * b.dag() c_ops = [cc, cm, cp] return liouvillian(H, c_ops)
def _spectrum_es(H, wlist, c_ops, a_op, b_op): """ Internal function for calculating the spectrum of the correlation function :math:`\left<A(\\tau)B(0)\\right>`. """ if debug: print(inspect.stack()[0][3]) # construct the Liouvillian L = liouvillian(H, c_ops) # find the steady state density matrix and a_op and b_op expecation values rho0 = steadystate(L) a_op_ss = expect(a_op, rho0) b_op_ss = expect(b_op, rho0) # eseries solution for (b * rho0)(t) es = ode2es(L, b_op * rho0) # correlation corr_es = expect(a_op, es) # covariance cov_es = corr_es - a_op_ss * b_op_ss # tidy up covariance (to combine, e.g., zero-frequency components that cancel) cov_es.tidyup() # spectrum spectrum = esspec(cov_es, wlist) return spectrum
def _mesolve_const(H, rho0, tlist, c_op_list, e_ops, args, opt, progress_bar): """ Evolve the density matrix using an ODE solver, for constant hamiltonian and collapse operators. """ if debug: print(inspect.stack()[0][3]) # # check initial state # if isket(rho0): # if initial state is a ket and no collapse operator where given, # fall back on the unitary schrodinger equation solver if len(c_op_list) == 0 and isoper(H): return _sesolve_const(H, rho0, tlist, e_ops, args, opt, progress_bar) # Got a wave function as initial state: convert to density matrix. rho0 = ket2dm(rho0) # # construct liouvillian # if opt.tidy: H = H.tidyup(opt.atol) L = liouvillian(H, c_op_list) # # setup integrator # initial_vector = mat2vec(rho0.full()).ravel('F') if issuper(rho0): r = scipy.integrate.ode(_ode_super_func) r.set_f_params(L.data) else: if opt.use_openmp and L.data.nnz >= qset.openmp_thresh: r = scipy.integrate.ode(cy_ode_rhs_openmp) r.set_f_params(L.data.data, L.data.indices, L.data.indptr, opt.openmp_threads) else: r = scipy.integrate.ode(cy_ode_rhs) r.set_f_params(L.data.data, L.data.indices, L.data.indptr) # r = scipy.integrate.ode(_ode_rho_test) # r.set_f_params(L.data) r.set_integrator('zvode', method=opt.method, order=opt.order, atol=opt.atol, rtol=opt.rtol, nsteps=opt.nsteps, first_step=opt.first_step, min_step=opt.min_step, max_step=opt.max_step) r.set_initial_value(initial_vector, tlist[0]) # # call generic ODE code # return _generic_ode_solve(r, rho0, tlist, e_ops, opt, progress_bar)
def __init__( self, H_sys, bath, max_depth, options=None, progress_bar=None, ): self.H_sys = self._convert_h_sys(H_sys) self.options = Options() if options is None else options self._is_timedep = isinstance(self.H_sys, QobjEvo) self._H0 = self.H_sys.to_list()[0] if self._is_timedep else self.H_sys self._is_hamiltonian = self._H0.type == "oper" self._L0 = liouvillian(self._H0) if self._is_hamiltonian else self._H0 self._sys_shape = (self._H0.shape[0] if self._is_hamiltonian else int( np.sqrt(self._H0.shape[0]))) self._sup_shape = self._L0.shape[0] self._sys_dims = (self._H0.dims if self._is_hamiltonian else self._H0.dims[0]) self.ados = HierarchyADOs( self._combine_bath_exponents(bath), max_depth, ) self._n_ados = len(self.ados.labels) self._n_exponents = len(self.ados.exponents) # pre-calculate identity matrix required by _grad_n self._sId = fast_identity(self._sup_shape) # pre-calculate superoperators required by _grad_prev and _grad_next: Qs = [exp.Q for exp in self.ados.exponents] self._spreQ = [spre(op).data for op in Qs] self._spostQ = [spost(op).data for op in Qs] self._s_pre_minus_post_Q = [ self._spreQ[k] - self._spostQ[k] for k in range(self._n_exponents) ] self._s_pre_plus_post_Q = [ self._spreQ[k] + self._spostQ[k] for k in range(self._n_exponents) ] self._spreQdag = [spre(op.dag()).data for op in Qs] self._spostQdag = [spost(op.dag()).data for op in Qs] self._s_pre_minus_post_Qdag = [ self._spreQdag[k] - self._spostQdag[k] for k in range(self._n_exponents) ] self._s_pre_plus_post_Qdag = [ self._spreQdag[k] + self._spostQdag[k] for k in range(self._n_exponents) ] if progress_bar is None: self.progress_bar = BaseProgressBar() if progress_bar is True: self.progress_bar = TextProgressBar() self._configure_solver()
def _smepdpsolve_generic(sso, options, progress_bar): """ For internal use. See smepdpsolve. """ if debug: logger.debug(inspect.stack()[0][3]) N_store = len(sso.times) N_substeps = sso.nsubsteps dt = (sso.times[1] - sso.times[0]) / N_substeps nt = sso.ntraj data = Result() data.solver = "smepdpsolve" data.times = sso.times data.expect = np.zeros((len(sso.e_ops), N_store), dtype=complex) data.jump_times = [] data.jump_op_idx = [] # Liouvillian for the deterministic part. # needs to be modified for TD systems L = liouvillian(sso.H, sso.c_ops) progress_bar.start(sso.ntraj) for n in range(sso.ntraj): progress_bar.update(n) rho_t = mat2vec(sso.rho0.full()).ravel() states_list, jump_times, jump_op_idx = \ _smepdpsolve_single_trajectory(data, L, dt, sso.times, N_store, N_substeps, rho_t, sso.rho0.dims, sso.c_ops, sso.e_ops) data.states.append(states_list) data.jump_times.append(jump_times) data.jump_op_idx.append(jump_op_idx) progress_bar.finished() # average density matrices if options.average_states and np.any(data.states): data.states = [ sum([data.states[m][n] for m in range(nt)]).unit() for n in range(len(data.times)) ] # average data.expect = data.expect / sso.ntraj # standard error if nt > 1: data.se = (data.ss - nt * (data.expect**2)) / (nt * (nt - 1)) else: data.se = None return data
def gradient_QFI(self): ''' Output: 1) updated values of self.control_coefficients Notice: To run this function, the function 'propagation_single()' has to run first. ''' num = len(self.times) dH0 = 1.j * Qobj(liouvillian(self.Hamiltonian_derivative[0]).full()) rhomat_final = Qobj(vec2mat(self.rho[num - 1].full())) drhomat_final = Qobj(vec2mat(self.rho_derivative[num - 1].full())) SLD_final = CR.SLD(rhomat_final, drhomat_final) dim = self.freeHamiltonian.dims[0][0] dt = self.times[1] - self.times[0] Hc_coeff = self.control_coefficients D = self.propagator for ki in range(0, len(self.control_Hamiltonian)): Hk = 1.j * Qobj(liouvillian(self.control_Hamiltonian[ki]).full()) Hc_ki = Hc_coeff[ki] for ti in range(0, num): Mj1 = 1.j * D[ti + 1][num - 1] * Hk * self.rho[ti] Mj2 = Qobj(np.zeros((dim * dim, 1))) for ri in range(0, ti + 1): Mj2 += D[ti + 1][num - 1] * Hk * D[ri + 1][ti] * dH0 * self.rho[ri] Mj3 = Qobj(np.zeros((dim * dim, 1))) for ri in range(ti + 1, num): Mj3 += D[ri + 1][num - 1] * dH0 * D[ti + 1][ri] * Hk * self.rho[ti] Mj1mat = Qobj(vec2mat(Mj1.full())) Mj2mat = Qobj(vec2mat(Mj2.full())) Mj3mat = Qobj(vec2mat(Mj3.full())) term1 = dt * (SLD_final * SLD_final * Mj1mat).tr() term2 = -2 * (dt * dt) * (SLD_final * (Mj2mat + Mj3mat)).tr() delta = np.real(term1 + term2) Hc_ki[ti] += self.epsilon * delta Hc_coeff[ki] = Hc_ki self.control_coefficients = Hc_coeff
def _smepdpsolve_generic(sso, options, progress_bar): """ For internal use. See smepdpsolve. """ if debug: logger.debug(inspect.stack()[0][3]) N_store = len(sso.times) N_substeps = sso.nsubsteps dt = (sso.times[1] - sso.times[0]) / N_substeps nt = sso.ntraj data = Result() data.solver = "smepdpsolve" data.times = sso.times data.expect = np.zeros((len(sso.e_ops), N_store), dtype=complex) data.jump_times = [] data.jump_op_idx = [] # Liouvillian for the deterministic part. # needs to be modified for TD systems L = liouvillian(sso.H, sso.c_ops) progress_bar.start(sso.ntraj) for n in range(sso.ntraj): progress_bar.update(n) rho_t = mat2vec(sso.rho0.full()).ravel() states_list, jump_times, jump_op_idx = \ _smepdpsolve_single_trajectory(data, L, dt, sso.times, N_store, N_substeps, rho_t, sso.rho0.dims, sso.c_ops, sso.e_ops) data.states.append(states_list) data.jump_times.append(jump_times) data.jump_op_idx.append(jump_op_idx) progress_bar.finished() # average density matrices if options.average_states and np.any(data.states): data.states = [sum([data.states[m][n] for m in range(nt)]).unit() for n in range(len(data.times))] # average data.expect = data.expect / sso.ntraj # standard error if nt > 1: data.se = (data.ss - nt * (data.expect ** 2)) / (nt * (nt - 1)) else: data.se = None return data
def __init__( self, H_sys, coup_op, coup_strength, temperature, N_cut, N_exp, cut_freq, bnd_cut_approx=False, options=None, progress_bar=None, combine=True, ): bath = DrudeLorentzBath( Q=coup_op, lam=coup_strength, gamma=cut_freq, Nk=N_exp - 1, T=temperature, combine=combine, ) if bnd_cut_approx: # upgrade H_sys to a Liouvillian if needed and add the # bath terminator H_sys = self._convert_h_sys(H_sys) is_timedep = isinstance(H_sys, QobjEvo) H0 = H_sys.to_list()[0] if is_timedep else H_sys is_hamiltonian = H0.type == "oper" if is_hamiltonian: H_sys = liouvillian(H_sys) _, terminator = bath.terminator() H_sys = H_sys + terminator super().__init__( H_sys, bath=bath, max_depth=N_cut, options=options, progress_bar=progress_bar, ) # store input parameters as attributes for politeness and compatibility # with HSolverDL in QuTiP 4.6 and below. self.coup_strength = coup_strength self.cut_freq = cut_freq self.temperature = temperature self.N_exp = N_exp self.bnd_cut_approx = bnd_cut_approx
def add_photon_noise(rho0, gamma, tlist): """ """ n = rho0.shape[0] a = destroy(n) c_ops = [ gamma * a, ] H = -0 * (a.dag() + a) opts = Options(atol=1e-20, store_states=True, nsteps=1500) L = liouvillian(H, c_ops=c_ops) states = mesolve(H, rho0, tlist, c_ops=c_ops) return states.states
def evolution(self, t): ''' internal function ''' H0 = self.freeHamiltonian tj = (t-self.times[0])/(self.times[1]-self.times[0]) dt = self.times[1]-self.times[0] for hn in range(0, len(self.control_Hamiltonian)): Hc_temp = self.control_coefficients[hn] H0 += self.control_Hamiltonian[hn] * Hc_temp[tj] Ld = dt * Qobj(liouvillian(H0, c_ops=self.Liouville_operator).full()) return Ld.expm()
def test_liouvillian_td(self): "Superoperator: liouvillian, time-dependent" assert_(liouvillian(self.t1)(0.5) == liouvillian(self.t1(0.5))) assert_(liouvillian(None, [self.t2])(0.5) == liouvillian(None, [self.t2(0.5)])) assert_(liouvillian(self.t1, [self.t2, self.q1, self.t3], chi=[1,2,3])(0.5) == liouvillian(self.t1(0.5), [self.t2(0.5), self.q1, self.t3(0.5)], chi=[1,2,3]))
def _steadystate_setup(A, c_op_list): """Build Liouvillian (if necessary) and check input. """ if isoper(A): if len(c_op_list) > 0: return liouvillian(A, c_op_list) raise TypeError('Cannot calculate the steady state for a ' + 'non-dissipative system ' + '(no collapse operators given)') elif issuper(A): return A else: raise TypeError('Solving for steady states requires ' + 'Liouvillian (super) operators')
def mesolve_const_checkpoint(H, rho0, tlist, c_op_list, e_ops, args, opt, progress_bar, save, subdir): """ Evolve the density matrix using an ODE solver, for constant hamiltonian and collapse operators. """ if debug: print(inspect.stack()[0][3]) # # check initial state # if isket(rho0): # Got a wave function as initial state: convert to density matrix. rho0 = ket2dm(rho0) # # construct liouvillian # if opt.tidy: H = H.tidyup(opt.atol) L = liouvillian(H, c_op_list) # # setup integrator # initial_vector = mat2vec(rho0.full()).ravel() r = scipy.integrate.ode(cy_ode_rhs) r.set_f_params(L.data.data, L.data.indices, L.data.indptr) r.set_integrator('zvode', method=opt.method, order=opt.order, atol=opt.atol, rtol=opt.rtol, nsteps=opt.nsteps, first_step=opt.first_step, min_step=opt.min_step, max_step=opt.max_step) r.set_initial_value(initial_vector, tlist[0]) # # call generic ODE code # return generic_ode_solve_checkpoint(r, rho0, tlist, e_ops, opt, progress_bar, save, subdir)
def propagation_multiple(self): ''' This function will save all the propators during the evolution, which may be memory consuming. ''' if len(self.Hamiltonian_derivative) < 2: raise TypeError( 'this is a multiparameter scenario, the length of dH has to be larger than 1!' ) num = len(self.times) dim = self.freeHamiltonian.dims[0][0] dt = self.times[1] - self.times[0] dL = [ Qobj(liouvillian(self.Hamiltonian_derivative[i]).full()) for i in range(0, len(self.Hamiltonian_derivative)) ] D = [[[] for i in range(0, num + 1)] for k in range(0, num + 1)] rhovec = [[] for i in range(0, num)] drhovec = [[[] for k in range(0, len(self.Hamiltonian_derivative))] for i in range(0, num)] rhovec[0] = Qobj(mat2vec(self.rho_initial.full())) for para_i in range(0, len(self.Hamiltonian_derivative)): drhovec[0][para_i] = dt * dL[para_i] * rhovec[0] D[0][0] = self.evolution(0) D[1][0] = qeye(dim**2) for di in range(1, num): tnow = self.times[0] + dt * di D[di + 1][di] = qeye(dim**2) D[di][di] = self.evolution(tnow) D[0][di] = D[di][di] * D[0][di - 1] rhovec[di] = D[di][di] * rhovec[di - 1] for para_i in range(0, len(self.Hamiltonian_derivative)): drho_temp = dt * dL[para_i] * rhovec[di] for dj in range(1, di): D[di - dj][di] = D[di - dj + 1][di] * D[di - dj][di - dj] drho_temp += dt * D[di - dj + 1][di] * dL[para_i] * rhovec[di - dj] drhovec[di][para_i] = drho_temp self.rho = rhovec self.rho_derivative = drhovec self.propagator = D
def testLiouvillianImplem(self): """ Superoperator: Randomized comparison of standard and reference Liouvillian functions. """ N1 = 3 N2 = 4 N3 = 5 a1 = tensor(rand_dm(N1, density=0.75), identity(N2), identity(N3)) a2 = tensor(identity(N1), rand_dm(N2, density=0.75), identity(N3)) a3 = tensor(identity(N1), identity(N2), rand_dm(N3, density=0.75)) H = a1.dag() * a1 + a2.dag() * a2 + a3.dag() * a3 c_ops = [np.sqrt(0.01) * a1, np.sqrt(0.025) * a2, np.sqrt(0.05) * a3] L1 = liouvillian(H, c_ops) L2 = liouvillian_ref(H, c_ops) assert_((L1 - L2).norm('max') < 1e-8)
def _configure_solver(self): """ Set up the solver. """ RHSmat = self._rhs(self._L0.data) assert isinstance(RHSmat, sp.csr_matrix) if self._is_timedep: # In the time dependent case, we construct the parameters # for the ODE gradient function _dsuper_list_td under the # assumption that RHSmat(t) = RHSmat + time dependent terms # that only affect the diagonal blocks of the RHS matrix. # This assumption holds because only _grad_n dependents on # the system Liovillian (and not _grad_prev or _grad_next). h_identity_mat = sp.identity(self._n_ados, format="csr") H_list = self.H_sys.to_list() solver_params = [[RHSmat]] for idx in range(1, len(H_list)): temp_mat = sp.kron(h_identity_mat, liouvillian(H_list[idx][0])) solver_params.append([temp_mat, H_list[idx][1]]) solver = scipy.integrate.ode(self._dsuper_list_td) solver.set_f_params(solver_params) else: solver = scipy.integrate.ode(cy_ode_rhs) solver.set_f_params(RHSmat.data, RHSmat.indices, RHSmat.indptr) solver.set_integrator( "zvode", method=self.options.method, order=self.options.order, atol=self.options.atol, rtol=self.options.rtol, nsteps=self.options.nsteps, first_step=self.options.first_step, min_step=self.options.min_step, max_step=self.options.max_step, ) self._ode = solver self.RHSmat = RHSmat
def _spectrum_pi(H, wlist, c_ops, a_op, b_op, use_pinv=False): """ Internal function for calculating the spectrum of the correlation function :math:`\left<A(\\tau)B(0)\\right>`. """ L = H if issuper(H) else liouvillian(H, c_ops) tr_mat = tensor([qeye(n) for n in L.dims[0][0]]) N = np.prod(L.dims[0][0]) A = L.full() b = spre(b_op).full() a = spre(a_op).full() tr_vec = np.transpose(mat2vec(tr_mat.full())) rho_ss = steadystate(L) rho = np.transpose(mat2vec(rho_ss.full())) I = np.identity(N * N) P = np.kron(np.transpose(rho), tr_vec) Q = I - P spectrum = np.zeros(len(wlist)) for idx, w in enumerate(wlist): if use_pinv: MMR = np.linalg.pinv(-1.0j * w * I + A) else: MMR = np.dot(Q, np.linalg.solve(-1.0j * w * I + A, Q)) s = np.dot(tr_vec, np.dot(a, np.dot(MMR, np.dot(b, np.transpose(rho))))) spectrum[idx] = -2 * np.real(s[0, 0]) return spectrum
def _opto_liouvillian(N): from qutip.tensor import tensor from qutip.operators import destroy, qeye from qutip.superoperator import liouvillian Nc = 5 # Number of cavity states Nm = N # Number of mech states kappa = 0.3 # Cavity damping rate E = 0.1 # Driving Amplitude g0 = 2.4*kappa # Coupling strength Qm = 1e4 # Mech quality factor gamma = 1/Qm # Mech damping rate n_th = 1 # Mech bath temperature delta = -0.43 # Detuning a = tensor(destroy(Nc), qeye(Nm)) b = tensor(qeye(Nc), destroy(Nm)) num_b = b.dag()*b num_a = a.dag()*a H = -delta*(num_a)+num_b+g0*(b.dag()+b)*num_a+E*(a.dag()+a) cc = np.sqrt(kappa)*a cm = np.sqrt(gamma*(1.0 + n_th))*b cp = np.sqrt(gamma*n_th)*b.dag() c_ops = [cc,cm,cp] return liouvillian(H, c_ops)
def _mesolve_QobjEvo(H, c_ops, tlist, args, opt): """ Prepare the system for the solver, H can be an QobjEvo. """ H_td = QobjEvo(H, args, tlist=tlist) if not issuper(H_td.cte): L_td = liouvillian(H_td) else: L_td = H_td for op in c_ops: # We want to avoid passing tlist where it isn't necessary, to allow a # Hamiltonian/Liouvillian which already _has_ time-dependence not equal # to the mesolve evaluation times to be used in conjunction with # time-independent c_ops. If we _always_ pass it, it may appear to # QobjEvo that there is a tlist mismatch, even though it is not used. if isinstance(op, Qobj): op_td = QobjEvo(op) elif isinstance(op, QobjEvo): op_td = QobjEvo(op, args) else: op_td = QobjEvo(op, args, tlist=tlist) if not issuper(op_td.cte): op_td = lindblad_dissipator(op_td) L_td += op_td if opt.rhs_with_state: L_td._check_old_with_state() nthread = opt.openmp_threads if opt.use_openmp else 0 L_td.compile(omp=nthread) ss = SolverSystem() ss.H = L_td ss.makefunc = _qobjevo_set solver_safe["mesolve"] = ss return ss
def liouvillian_c(c_ops, chi): return liouvillian(None, c_ops=[c_ops], chi=chi)
def bloch_redfield_tensor(H, a_ops, spectra_cb=None, c_ops=[], use_secular=True, sec_cutoff=0.1): """ Calculate the Bloch-Redfield tensor for a system given a set of operators and corresponding spectral functions that describes the system's coupling to its environment. .. note:: This tensor generation requires a time-independent Hamiltonian. Parameters ---------- H : :class:`qutip.qobj` System Hamiltonian. a_ops : list of :class:`qutip.qobj` List of system operators that couple to the environment. spectra_cb : list of callback functions List of callback functions that evaluate the noise power spectrum at a given frequency. c_ops : list of :class:`qutip.qobj` List of system collapse operators. use_secular : bool Flag (True of False) that indicates if the secular approximation should be used. sec_cutoff : float {0.1} Threshold for secular approximation. Returns ------- R, kets: :class:`qutip.Qobj`, list of :class:`qutip.Qobj` R is the Bloch-Redfield tensor and kets is a list eigenstates of the Hamiltonian. """ if not (spectra_cb is None): warnings.warn("The use of spectra_cb is depreciated.", DeprecationWarning) _a_ops = [] for kk, a in enumerate(a_ops): _a_ops.append([a,spectra_cb[kk]]) a_ops = _a_ops # Sanity checks for input parameters if not isinstance(H, Qobj): raise TypeError("H must be an instance of Qobj") for a in a_ops: if not isinstance(a[0], Qobj) or not a[0].isherm: raise TypeError("Operators in a_ops must be Hermitian Qobj.") if c_ops is None: c_ops = [] # use the eigenbasis evals, ekets = H.eigenstates() N = len(evals) K = len(a_ops) #only Lindblad collapse terms if K==0: Heb = qdiags(evals,0,dims=H.dims) L = liouvillian(Heb, c_ops=[c_op.transform(ekets) for c_op in c_ops]) return L, ekets A = np.array([a_ops[k][0].transform(ekets).full() for k in range(K)]) Jw = np.zeros((K, N, N), dtype=complex) # pre-calculate matrix elements and spectral densities # W[m,n] = real(evals[m] - evals[n]) W = np.real(evals[:,np.newaxis] - evals[np.newaxis,:]) for k in range(K): # do explicit loops here in case spectra_cb[k] can not deal with array arguments for n in range(N): for m in range(N): Jw[k, n, m] = a_ops[k][1](W[n, m]) dw_min = np.abs(W[W.nonzero()]).min() # pre-calculate mapping between global index I and system indices a,b Iabs = np.empty((N*N,3),dtype=int) for I, Iab in enumerate(Iabs): # important: use [:] to change array values, instead of creating new variable Iab Iab[0] = I Iab[1:] = vec2mat_index(N, I) # unitary part + dissipation from c_ops (if given): Heb = qdiags(evals,0,dims=H.dims) L = liouvillian(Heb, c_ops=[c_op.transform(ekets) for c_op in c_ops]) # dissipative part: rows = [] cols = [] data = [] for I, a, b in Iabs: # only check use_secular once per I if use_secular: # only loop over those indices J which actually contribute Jcds = Iabs[np.where(np.abs(W[a, b] - W[Iabs[:,1], Iabs[:,2]]) < dw_min * sec_cutoff)] else: Jcds = Iabs for J, c, d in Jcds: elem = 0+0j # summed over k, i.e., each operator coupling the system to the environment elem += 0.5 * np.sum(A[:, a, c] * A[:, d, b] * (Jw[:, c, a] + Jw[:, d, b])) if b==d: # sum_{k,n} A[k, a, n] * A[k, n, c] * Jw[k, c, n]) elem -= 0.5 * np.sum(A[:, a, :] * A[:, :, c] * Jw[:, c, :]) if a==c: # sum_{k,n} A[k, d, n] * A[k, n, b] * Jw[k, d, n]) elem -= 0.5 * np.sum(A[:, d, :] * A[:, :, b] * Jw[:, d, :]) if elem != 0: rows.append(I) cols.append(J) data.append(elem) R = arr_coo2fast(np.array(data, dtype=complex), np.array(rows, dtype=np.int32), np.array(cols, dtype=np.int32), N**2, N**2) L.data = L.data + R return L, ekets
def essolve(H, rho0, tlist, c_op_list, e_ops): """ Evolution of a state vector or density matrix (`rho0`) for a given Hamiltonian (`H`) and set of collapse operators (`c_op_list`), by expressing the ODE as an exponential series. The output is either the state vector at arbitrary points in time (`tlist`), or the expectation values of the supplied operators (`e_ops`). Parameters ---------- H : qobj/function_type System Hamiltonian. rho0 : :class:`qutip.qobj` Initial state density matrix. tlist : list/array ``list`` of times for :math:`t`. c_op_list : list of :class:`qutip.qobj` ``list`` of :class:`qutip.qobj` collapse operators. e_ops : list of :class:`qutip.qobj` ``list`` of :class:`qutip.qobj` operators for which to evaluate expectation values. Returns ------- expt_array : array Expectation values of wavefunctions/density matrices for the times specified in ``tlist``. .. note:: This solver does not support time-dependent Hamiltonians. """ n_expt_op = len(e_ops) n_tsteps = len(tlist) # Calculate the Liouvillian if (c_op_list is None or len(c_op_list) == 0) and isket(rho0): L = H else: L = liouvillian(H, c_op_list) es = ode2es(L, rho0) # evaluate the expectation values if n_expt_op == 0: results = [Qobj()] * n_tsteps else: results = np.zeros([n_expt_op, n_tsteps], dtype=complex) for n, e in enumerate(e_ops): results[n, :] = expect(e, esval(es, tlist)) data = Result() data.solver = "essolve" data.times = tlist data.expect = [np.real(results[n, :]) if e.isherm else results[n, :] for n, e in enumerate(e_ops)] return data
def essolve(H, rho0, tlist, c_op_list, e_ops): """ Evolution of a state vector or density matrix (`rho0`) for a given Hamiltonian (`H`) and set of collapse operators (`c_op_list`), by expressing the ODE as an exponential series. The output is either the state vector at arbitrary points in time (`tlist`), or the expectation values of the supplied operators (`e_ops`). .. deprecated:: 4.6.0 :obj:`~essolev` will be removed in QuTiP 5. Please use :obj:`~sesolve` or :obj:`~mesolve` for general-purpose integration of the Schroedinger/Lindblad master equation. This will likely be faster than :obj:`~essolve` for you. Parameters ---------- H : qobj/function_type System Hamiltonian. rho0 : :class:`qutip.qobj` Initial state density matrix. tlist : list/array ``list`` of times for :math:`t`. c_op_list : list of :class:`qutip.qobj` ``list`` of :class:`qutip.qobj` collapse operators. e_ops : list of :class:`qutip.qobj` ``list`` of :class:`qutip.qobj` operators for which to evaluate expectation values. Returns ------- expt_array : array Expectation values of wavefunctions/density matrices for the times specified in ``tlist``. .. note:: This solver does not support time-dependent Hamiltonians. """ n_expt_op = len(e_ops) n_tsteps = len(tlist) # Calculate the Liouvillian if (c_op_list is None or len(c_op_list) == 0) and isket(rho0): L = H else: L = liouvillian(H, c_op_list) es = ode2es(L, rho0) # evaluate the expectation values if n_expt_op == 0: results = [Qobj()] * n_tsteps else: results = np.zeros([n_expt_op, n_tsteps], dtype=complex) for n, e in enumerate(e_ops): results[n, :] = expect(e, esval(es, tlist)) data = Result() data.solver = "essolve" data.times = tlist data.expect = [ np.real(results[n, :]) if e.isherm else results[n, :] for n, e in enumerate(e_ops) ] return data
def steadystate(A, c_op_list=[], **kwargs): """Calculates the steady state for quantum evolution subject to the supplied Hamiltonian or Liouvillian operator and (if given a Hamiltonian) a list of collapse operators. If the user passes a Hamiltonian then it, along with the list of collapse operators, will be converted into a Liouvillian operator in Lindblad form. Parameters ---------- A : qobj A Hamiltonian or Liouvillian operator. c_op_list : list A list of collapse operators. method : str {'direct', 'eigen', 'iterative-bicg', 'iterative-gmres', 'svd', 'power'} Method for solving the underlying linear equation. Direct LU solver 'direct' (default), sparse eigenvalue problem 'eigen', iterative GMRES method 'iterative-gmres', iterative LGMRES method 'iterative-lgmres', SVD 'svd' (dense), or inverse-power method 'power'. sparse : bool, optional, default=True Solve for the steady state using sparse algorithms. If set to False, the underlying Liouvillian operator will be converted into a dense matrix. Use only for 'smaller' systems. use_rcm : bool, optional, default=True Use reverse Cuthill-Mckee reordering to minimize fill-in in the LU factorization of the Liouvillian. use_wbm : bool, optional, default=False Use Weighted Bipartite Matching reordering to make the Liouvillian diagonally dominant. This is useful for iterative preconditioners only, and is set to ``True`` by default when finding a preconditioner. weight : float, optional Sets the size of the elements used for adding the unity trace condition to the linear solvers. This is set to the average abs value of the Liouvillian elements if not specified by the user. use_umfpack : bool {False, True} Use umfpack solver instead of SuperLU. For SciPy 0.14+, this option requires installing scikits.umfpack. maxiter : int, optional, default=10000 Maximum number of iterations to perform if using an iterative method. tol : float, optional, default=1e-9 Tolerance used for terminating solver solution when using iterative solvers. permc_spec : str, optional, default='COLAMD' Column ordering used internally by superLU for the 'direct' LU decomposition method. Options include 'COLAMD' and 'NATURAL'. If using RCM then this is set to 'NATURAL' automatically unless explicitly specified. use_precond : bool optional, default = True ITERATIVE ONLY. Use an incomplete sparse LU decomposition as a preconditioner for the 'iterative' GMRES and BICG solvers. Speeds up convergence time by orders of magnitude in many cases. M : {sparse matrix, dense matrix, LinearOperator}, optional Preconditioner for A. The preconditioner should approximate the inverse of A. Effective preconditioning dramatically improves the rate of convergence, for iterative methods only . If no preconditioner is given and ``use_precond=True``, then one is generated automatically. fill_factor : float, optional, default=10 ITERATIVE ONLY. Specifies the fill ratio upper bound (>=1) of the iLU preconditioner. Lower values save memory at the cost of longer execution times and a possible singular factorization. drop_tol : float, optional, default=1e-3 ITERATIVE ONLY. Sets the threshold for the magnitude of preconditioner elements that should be dropped. Can be reduced for a courser factorization at the cost of an increased number of iterations, and a possible singular factorization. diag_pivot_thresh : float, optional, default=None ITERATIVE ONLY. Sets the threshold between [0,1] for which diagonal elements are considered acceptable pivot points when using a preconditioner. A value of zero forces the pivot to be the diagonal element. ILU_MILU : str, optional, default='smilu_2' Selects the incomplete LU decomposition method algoithm used in creating the preconditoner. Should only be used by advanced users. Returns ------- dm : qobj Steady state density matrix. Notes ----- The SVD method works only for dense operators (i.e. small systems). """ ss_args = _default_steadystate_args() for key in kwargs.keys(): if key in ss_args.keys(): ss_args[key] = kwargs[key] else: raise Exception( "Invalid keyword argument '"+key+"' passed to steadystate.") # Set column perm to NATURAL if using RCM and not specified by user if ss_args['use_rcm'] and ('permc_spec' not in kwargs.keys()): ss_args['permc_spec'] = 'NATURAL' # Set use_wbm=True if using iterative solver with preconditioner and # not explicitly set to False by user if (ss_args['method'] in ['iterative-lgmres', 'iterative-gmres']) \ and ('use_wbm' not in kwargs.keys()): ss_args['use_wbm'] = True n_op = len(c_op_list) if isoper(A): if n_op == 0: raise TypeError('Cannot calculate the steady state for a ' + 'non-dissipative system ' + '(no collapse operators given)') else: A = liouvillian(A, c_op_list) if not issuper(A): raise TypeError('Solving for steady states requires ' + 'Liouvillian (super) operators') # Set weight parameter to avg abs val in L if not set explicitly if 'weight' not in kwargs.keys(): ss_args['weight'] = np.mean(np.abs(A.data.data.max())) if ss_args['method'] == 'direct': if ss_args['sparse']: return _steadystate_direct_sparse(A, ss_args) else: return _steadystate_direct_dense(A) elif ss_args['method'] == 'eigen': return _steadystate_eigen(A, ss_args) elif ss_args['method'] in ['iterative-gmres', 'iterative-lgmres']: return _steadystate_iterative(A, ss_args) elif ss_args['method'] == 'svd': return _steadystate_svd_dense(A, ss_args) elif ss_args['method'] == 'power': return _steadystate_power(A, ss_args) else: raise ValueError('Invalid method argument for steadystate.')
def bloch_redfield_tensor(H, a_ops, spectra_cb, c_ops=None, use_secular=True): """ Calculate the Bloch-Redfield tensor for a system given a set of operators and corresponding spectral functions that describes the system's coupling to its environment. .. note:: This tensor generation requires a time-independent Hamiltonian. Parameters ---------- H : :class:`qutip.qobj` System Hamiltonian. a_ops : list of :class:`qutip.qobj` List of system operators that couple to the environment. spectra_cb : list of callback functions List of callback functions that evaluate the noise power spectrum at a given frequency. c_ops : list of :class:`qutip.qobj` List of system collapse operators. use_secular : bool Flag (True of False) that indicates if the secular approximation should be used. Returns ------- R, kets: :class:`qutip.Qobj`, list of :class:`qutip.Qobj` R is the Bloch-Redfield tensor and kets is a list eigenstates of the Hamiltonian. """ # Sanity checks for input parameters if not isinstance(H, Qobj): raise TypeError("H must be an instance of Qobj") for a in a_ops: if not isinstance(a, Qobj) or not a.isherm: raise TypeError("Operators in a_ops must be Hermitian Qobj.") # default spectrum if not spectra_cb: spectra_cb = [lambda w: 1.0 for _ in a_ops] # use the eigenbasis evals, ekets = H.eigenstates() N = len(evals) K = len(a_ops) A = np.zeros((K, N, N), dtype=complex) # TODO: use sparse here W = np.zeros((N, N)) # pre-calculate matrix elements for n in range(N): for m in range(N): W[m, n] = np.real(evals[m] - evals[n]) for k in range(K): # A[k,n,m] = a_ops[k].matrix_element(ekets[n], ekets[m]) A[k, :, :] = a_ops[k].transform(ekets).full() dw_min = abs(W[W.nonzero()]).min() # unitary part Heb = H.transform(ekets) if c_ops is not None: R = liouvillian(Heb, c_ops=[c_op.transform(ekets) for c_op in c_ops]) else: R = -1.0j * (spre(Heb) - spost(Heb)) R.data = R.data.tolil() for I in range(N * N): a, b = vec2mat_index(N, I) for J in range(N * N): c, d = vec2mat_index(N, J) # unitary part: use spre and spost above, same as this: # R.data[I,J] = -1j * W[a,b] * (a == c) * (b == d) if use_secular is False or abs(W[a, b] - W[c, d]) < dw_min / 10.0: # dissipative part: for k in range(K): # for each operator coupling the system to the environment R.data[I, J] += ((A[k, a, c] * A[k, d, b] / 2) * (spectra_cb[k](W[c, a]) + spectra_cb[k](W[d, b]))) s1 = s2 = 0 for n in range(N): s1 += A[k, a, n] * A[k, n, c] * spectra_cb[k](W[c, n]) s2 += A[k, d, n] * A[k, n, b] * spectra_cb[k](W[d, n]) R.data[I, J] += - (b == d) * s1 / 2 - (a == c) * s2 / 2 R.data = R.data.tocsr() return R, ekets
def configure(self, H_sys, coup_op, coup_strength, temperature, N_cut, N_exp, cut_freq, planck=None, boltzmann=None, renorm=None, bnd_cut_approx=None, options=None, progress_bar=None, stats=None): """ Calls configure from :class:`HEOMSolver` and sets any attributes that are specific to this subclass """ start_config = timeit.default_timer() HEOMSolver.configure(self, H_sys, coup_op, coup_strength, temperature, N_cut, N_exp, planck=planck, boltzmann=boltzmann, options=options, progress_bar=progress_bar, stats=stats) self.cut_freq = cut_freq if renorm is not None: self.renorm = renorm if bnd_cut_approx is not None: self.bnd_cut_approx = bnd_cut_approx # Load local values for optional parameters # Constants and Hamiltonian. hbar = self.planck options = self.options progress_bar = self.progress_bar stats = self.stats if stats: ss_conf = stats.sections.get('config') if ss_conf is None: ss_conf = stats.add_section('config') c, nu = self._calc_matsubara_params() if renorm: norm_plus, norm_minus = self._calc_renorm_factors() if stats: stats.add_message('options', 'renormalisation', ss_conf) # Dimensions et by system sup_dim = H_sys.dims[0][0]**2 unit_sys = qeye(H_sys.dims[0]) # Use shorthands (mainly as in referenced PRL) lam0 = self.coup_strength gam = self.cut_freq N_c = self.N_cut N_m = self.N_exp Q = coup_op # Q as shorthand for coupling operator beta = 1.0/(self.boltzmann*self.temperature) # Ntot is the total number of ancillary elements in the hierarchy # Ntot = factorial(N_c + N_m) / (factorial(N_c)*factorial(N_m)) # Turns out to be the same as nstates from state_number_enumerate N_he, he2idx, idx2he = enr_state_dictionaries([N_c + 1]*N_m , N_c) unit_helems = sp.identity(N_he, format='csr') if self.bnd_cut_approx: # the Tanimura boundary cut off operator if stats: stats.add_message('options', 'boundary cutoff approx', ss_conf) op = -2*spre(Q)*spost(Q.dag()) + spre(Q.dag()*Q) + spost(Q.dag()*Q) approx_factr = ((2*lam0 / (beta*gam*hbar)) - 1j*lam0) / hbar for k in range(N_m): approx_factr -= (c[k] / nu[k]) L_bnd = -approx_factr*op.data L_helems = sp.kron(unit_helems, L_bnd) else: L_helems = sp.csr_matrix((N_he*sup_dim, N_he*sup_dim), dtype=complex) # Build the hierarchy element interaction matrix if stats: start_helem_constr = timeit.default_timer() unit_sup = spre(unit_sys).data spreQ = spre(Q).data spostQ = spost(Q).data commQ = (spre(Q) - spost(Q)).data N_he_interact = 0 for he_idx in range(N_he): he_state = list(idx2he[he_idx]) n_excite = sum(he_state) # The diagonal elements for the hierarchy operator # coeff for diagonal elements sum_n_m_freq = 0.0 for k in range(N_m): sum_n_m_freq += he_state[k]*nu[k] op = -sum_n_m_freq*unit_sup L_he = _pad_csr(op, N_he, N_he, he_idx, he_idx) L_helems += L_he # Add the neighour interations he_state_neigh = copy(he_state) for k in range(N_m): n_k = he_state[k] if n_k >= 1: # find the hierarchy element index of the neighbour before # this element, for this Matsubara term he_state_neigh[k] = n_k - 1 he_idx_neigh = he2idx[tuple(he_state_neigh)] op = c[k]*spreQ - np.conj(c[k])*spostQ if renorm: op = -1j*norm_minus[n_k, k]*op else: op = -1j*n_k*op L_he = _pad_csr(op, N_he, N_he, he_idx, he_idx_neigh) L_helems += L_he N_he_interact += 1 he_state_neigh[k] = n_k if n_excite <= N_c - 1: # find the hierarchy element index of the neighbour after # this element, for this Matsubara term he_state_neigh[k] = n_k + 1 he_idx_neigh = he2idx[tuple(he_state_neigh)] op = commQ if renorm: op = -1j*norm_plus[n_k, k]*op else: op = -1j*op L_he = _pad_csr(op, N_he, N_he, he_idx, he_idx_neigh) L_helems += L_he N_he_interact += 1 he_state_neigh[k] = n_k if stats: stats.add_timing('hierarchy contruct', timeit.default_timer() - start_helem_constr, ss_conf) stats.add_count('Num hierarchy elements', N_he, ss_conf) stats.add_count('Num he interactions', N_he_interact, ss_conf) # Setup Liouvillian if stats: start_louvillian = timeit.default_timer() H_he = sp.kron(unit_helems, liouvillian(H_sys).data) L_helems += H_he if stats: stats.add_timing('Liouvillian contruct', timeit.default_timer() - start_louvillian, ss_conf) if stats: start_integ_conf = timeit.default_timer() r = scipy.integrate.ode(cy_ode_rhs) r.set_f_params(L_helems.data, L_helems.indices, L_helems.indptr) r.set_integrator('zvode', method=options.method, order=options.order, atol=options.atol, rtol=options.rtol, nsteps=options.nsteps, first_step=options.first_step, min_step=options.min_step, max_step=options.max_step) if stats: time_now = timeit.default_timer() stats.add_timing('Liouvillian contruct', time_now - start_integ_conf, ss_conf) if ss_conf.total_time is None: ss_conf.total_time = time_now - start_config else: ss_conf.total_time += time_now - start_config self._ode = r self._N_he = N_he self._sup_dim = sup_dim self._configured = True
def _mesolve_func_td(L_func, rho0, tlist, c_op_list, e_ops, args, opt, progress_bar): """ Evolve the density matrix using an ODE solver with time dependent Hamiltonian. """ if debug: print(inspect.stack()[0][3]) # # check initial state # if isket(rho0): rho0 = ket2dm(rho0) # # construct liouvillian # new_args = None if len(c_op_list) > 0: L_data = liouvillian(None, c_op_list).data else: n, m = rho0.shape L_data = sp.csr_matrix((n ** 2, m ** 2), dtype=complex) if type(args) is dict: new_args = {} for key in args: if isinstance(args[key], Qobj): if isoper(args[key]): new_args[key] = (-1j * (spre(args[key]) - spost(args[key]))).data else: new_args[key] = args[key].data else: new_args[key] = args[key] elif type(args) is list or type(args) is tuple: new_args = [] for arg in args: if isinstance(arg, Qobj): if isoper(arg): new_args.append((-1j * (spre(arg) - spost(arg))).data) else: new_args.append(arg.data) else: new_args.append(arg) if type(args) is tuple: new_args = tuple(new_args) else: if isinstance(args, Qobj): if isoper(args): new_args = (-1j * (spre(args) - spost(args))).data else: new_args = args.data else: new_args = args # # setup integrator # initial_vector = mat2vec(rho0.full()).ravel() if not opt.rhs_with_state: r = scipy.integrate.ode(cy_ode_rho_func_td) else: r = scipy.integrate.ode(_ode_rho_func_td_with_state) r.set_integrator( "zvode", method=opt.method, order=opt.order, atol=opt.atol, rtol=opt.rtol, nsteps=opt.nsteps, first_step=opt.first_step, min_step=opt.min_step, max_step=opt.max_step, ) r.set_initial_value(initial_vector, tlist[0]) r.set_f_params(L_data, L_func, new_args) # # call generic ODE code # return _generic_ode_solve(r, rho0, tlist, e_ops, opt, progress_bar)
def _mesolve_list_td(H_func, rho0, tlist, c_op_list, e_ops, args, opt, progress_bar): """ Evolve the density matrix using an ODE solver with time dependent Hamiltonian. """ if debug: print(inspect.stack()[0][3]) # # check initial state # if isket(rho0): # if initial state is a ket and no collapse operator where given, # fall back on the unitary schrodinger equation solver if len(c_op_list) == 0: return _sesolve_list_td(H_func, rho0, tlist, e_ops, args, opt, progress_bar) # Got a wave function as initial state: convert to density matrix. rho0 = ket2dm(rho0) # # construct liouvillian # if len(H_func) != 2: raise TypeError("Time-dependent Hamiltonian list must have two terms.") if not isinstance(H_func[0], (list, np.ndarray)) or len(H_func[0]) <= 1: raise TypeError("Time-dependent Hamiltonians must be a list " + "with two or more terms") if (not isinstance(H_func[1], (list, np.ndarray))) or (len(H_func[1]) != (len(H_func[0]) - 1)): raise TypeError( "Time-dependent coefficients must be list with " + "length N-1 where N is the number of " + "Hamiltonian terms." ) if opt.rhs_reuse and config.tdfunc is None: rhs_generate(H_func, args) lenh = len(H_func[0]) if opt.tidy: H_func[0] = [(H_func[0][k]).tidyup() for k in range(lenh)] L_func = [[liouvillian(H_func[0][0], c_op_list)], H_func[1]] for m in range(1, lenh): L_func[0].append(liouvillian(H_func[0][m], [])) # create data arrays for time-dependent RHS function Ldata = [L_func[0][k].data.data for k in range(lenh)] Linds = [L_func[0][k].data.indices for k in range(lenh)] Lptrs = [L_func[0][k].data.indptr for k in range(lenh)] # setup ode args string string = "" for k in range(lenh): string += "Ldata[%d], Linds[%d], Lptrs[%d]," % (k, k, k) if args: td_consts = args.items() for elem in td_consts: string += str(elem[1]) if elem != td_consts[-1]: string += "," # run code generator if not opt.rhs_reuse or config.tdfunc is None: if opt.rhs_filename is None: config.tdname = "rhs" + str(os.getpid()) + str(config.cgen_num) else: config.tdname = opt.rhs_filename cgen = Codegen(h_terms=n_L_terms, h_tdterms=Lcoeff, args=args, config=config) cgen.generate(config.tdname + ".pyx") code = compile("from " + config.tdname + " import cy_td_ode_rhs", "<string>", "exec") exec(code, globals()) config.tdfunc = cy_td_ode_rhs # # setup integrator # initial_vector = mat2vec(rho0.full()).ravel() r = scipy.integrate.ode(config.tdfunc) r.set_integrator( "zvode", method=opt.method, order=opt.order, atol=opt.atol, rtol=opt.rtol, nsteps=opt.nsteps, first_step=opt.first_step, min_step=opt.min_step, max_step=opt.max_step, ) r.set_initial_value(initial_vector, tlist[0]) code = compile("r.set_f_params(" + string + ")", "<string>", "exec") exec(code) # # call generic ODE code # return _generic_ode_solve(r, rho0, tlist, e_ops, opt, progress_bar)
def _mesolve_list_func_td(H_list, rho0, tlist, c_list, e_ops, args, opt, progress_bar): """ Internal function for solving the master equation. See mesolve for usage. """ if debug: print(inspect.stack()[0][3]) # # check initial state # if isket(rho0): rho0 = rho0 * rho0.dag() # # construct liouvillian in list-function format # L_list = [] if opt.rhs_with_state: constant_func = lambda x, y, z: 1.0 else: constant_func = lambda x, y: 1.0 # add all hamitonian terms to the lagrangian list for h_spec in H_list: if isinstance(h_spec, Qobj): h = h_spec h_coeff = constant_func elif isinstance(h_spec, list) and isinstance(h_spec[0], Qobj): h = h_spec[0] h_coeff = h_spec[1] else: raise TypeError("Incorrect specification of time-dependent " + "Hamiltonian (expected callback function)") if isoper(h): L_list.append([(-1j * (spre(h) - spost(h))).data, h_coeff, False]) elif issuper(h): L_list.append([h.data, h_coeff, False]) else: raise TypeError( "Incorrect specification of time-dependent " + "Hamiltonian (expected operator or superoperator)" ) # add all collapse operators to the liouvillian list for c_spec in c_list: if isinstance(c_spec, Qobj): c = c_spec c_coeff = constant_func c_square = False elif isinstance(c_spec, list) and isinstance(c_spec[0], Qobj): c = c_spec[0] c_coeff = c_spec[1] c_square = True else: raise TypeError( "Incorrect specification of time-dependent " + "collapse operators (expected callback function)" ) if isoper(c): L_list.append([liouvillian(None, [c], data_only=True), c_coeff, c_square]) elif issuper(c): L_list.append([c.data, c_coeff, c_square]) else: raise TypeError( "Incorrect specification of time-dependent " + "collapse operators (expected operator or " + "superoperator)" ) # # setup integrator # initial_vector = mat2vec(rho0.full()).ravel() if opt.rhs_with_state: r = scipy.integrate.ode(drho_list_td_with_state) else: r = scipy.integrate.ode(drho_list_td) r.set_integrator( "zvode", method=opt.method, order=opt.order, atol=opt.atol, rtol=opt.rtol, nsteps=opt.nsteps, first_step=opt.first_step, min_step=opt.min_step, max_step=opt.max_step, ) r.set_initial_value(initial_vector, tlist[0]) r.set_f_params(L_list, args) # # call generic ODE code # return _generic_ode_solve(r, rho0, tlist, e_ops, opt, progress_bar)
def bloch_redfield_tensor(H, a_ops, spectra_cb, c_ops=[], use_secular=True): """ Calculate the Bloch-Redfield tensor for a system given a set of operators and corresponding spectral functions that describes the system's coupling to its environment. .. note:: This tensor generation requires a time-independent Hamiltonian. Parameters ---------- H : :class:`qutip.qobj` System Hamiltonian. a_ops : list of :class:`qutip.qobj` List of system operators that couple to the environment. spectra_cb : list of callback functions List of callback functions that evaluate the noise power spectrum at a given frequency. c_ops : list of :class:`qutip.qobj` List of system collapse operators. use_secular : bool Flag (True of False) that indicates if the secular approximation should be used. Returns ------- R, kets: :class:`qutip.Qobj`, list of :class:`qutip.Qobj` R is the Bloch-Redfield tensor and kets is a list eigenstates of the Hamiltonian. """ # Sanity checks for input parameters if not isinstance(H, Qobj): raise TypeError("H must be an instance of Qobj") for a in a_ops: if not isinstance(a, Qobj) or not a.isherm: raise TypeError("Operators in a_ops must be Hermitian Qobj.") # default spectrum if not spectra_cb: spectra_cb = [lambda w: 1.0 for _ in a_ops] if c_ops is None: c_ops = [] # use the eigenbasis evals, ekets = H.eigenstates() N = len(evals) K = len(a_ops) A = np.zeros((K, N, N), dtype=complex) # TODO: use sparse here Jw = np.zeros((K, N, N), dtype=complex) # pre-calculate matrix elements and spectral densities # W[m,n] = real(evals[m] - evals[n]) W = np.real(evals[:,np.newaxis] - evals[np.newaxis,:]) for k in range(K): # A[k,n,m] = a_ops[k].matrix_element(ekets[n], ekets[m]) A[k, :, :] = a_ops[k].transform(ekets).full() # do explicit loops here in case spectra_cb[k] can not deal with array arguments for n in range(N): for m in range(N): Jw[k, n, m] = spectra_cb[k](W[n, m]) dw_min = abs(W[W.nonzero()]).min() # pre-calculate mapping between global index I and system indices a,b Iabs = np.empty((N*N,3),dtype=int) for I, Iab in enumerate(Iabs): # important: use [:] to change array values, instead of creating new variable Iab Iab[0] = I Iab[1:] = vec2mat_index(N, I) # unitary part + dissipation from c_ops (if given): Heb = H.transform(ekets) R = liouvillian(Heb, c_ops=[c_op.transform(ekets) for c_op in c_ops]) R.data = R.data.tolil() # dissipative part: for I, a, b in Iabs: # only check use_secular once per I if use_secular: # only loop over those indices J which actually contribute Jcds = Iabs[np.where(abs(W[a, b] - W[Iabs[:,1], Iabs[:,2]]) < dw_min / 10.0)] else: Jcds = Iabs for J, c, d in Jcds: # summed over k, i.e., each operator coupling the system to the environment R.data[I, J] += 0.5 * np.sum(A[:, a, c] * A[:, d, b] * (Jw[:, c, a] + Jw[:, d, b])) if b==d: # sum_{k,n} A[k, a, n] * A[k, n, c] * Jw[k, c, n]) R.data[I, J] -= 0.5 * np.sum(A[:, a, :] * A[:, :, c] * Jw[:, c, :]) if a==c: # sum_{k,n} A[k, d, n] * A[k, n, b] * Jw[k, d, n]) R.data[I, J] -= 0.5 * np.sum(A[:, d, :] * A[:, :, b] * Jw[:, d, :]) R.data = R.data.tocsr() return R, ekets
def _mesolve_func_td(L_func, rho0, tlist, c_op_list, e_ops, args, opt, progress_bar): """ Evolve the density matrix using an ODE solver with time dependent Hamiltonian. """ if debug: print(inspect.stack()[0][3]) # # check initial state # if isket(rho0): rho0 = ket2dm(rho0) # # construct liouvillian # new_args = None if len(c_op_list) > 0: L_data = liouvillian(None, c_op_list).data else: n, m = rho0.shape if issuper(rho0): L_data = sp.csr_matrix((n, m), dtype=complex) else: L_data = sp.csr_matrix((n**2, m**2), dtype=complex) if type(args) is dict: new_args = {} for key in args: if isinstance(args[key], Qobj): if isoper(args[key]): new_args[key] = (-1j * (spre(args[key]) - spost(args[key]))) else: new_args[key] = args[key] else: new_args[key] = args[key] elif type(args) is list or type(args) is tuple: new_args = [] for arg in args: if isinstance(arg, Qobj): if isoper(arg): new_args.append((-1j * (spre(arg) - spost(arg))).data) else: new_args.append(arg.data) else: new_args.append(arg) if type(args) is tuple: new_args = tuple(new_args) else: if isinstance(args, Qobj): if isoper(args): new_args = (-1j * (spre(args) - spost(args))) else: new_args = args else: new_args = args # # setup integrator # initial_vector = mat2vec(rho0.full()).ravel('F') if issuper(rho0): if not opt.rhs_with_state: r = scipy.integrate.ode(_ode_super_func_td) else: r = scipy.integrate.ode(_ode_super_func_td_with_state) else: if not opt.rhs_with_state: r = scipy.integrate.ode(cy_ode_rho_func_td) else: r = scipy.integrate.ode(_ode_rho_func_td_with_state) r.set_integrator('zvode', method=opt.method, order=opt.order, atol=opt.atol, rtol=opt.rtol, nsteps=opt.nsteps, first_step=opt.first_step, min_step=opt.min_step, max_step=opt.max_step) r.set_initial_value(initial_vector, tlist[0]) r.set_f_params(L_data, L_func, new_args) # # call generic ODE code # return _generic_ode_solve(r, rho0, tlist, e_ops, opt, progress_bar)
def _mesolve_list_td(H_func, rho0, tlist, c_op_list, expt_ops, args, opt): """! Evolve the density matrix using an ODE solver with time dependent Hamiltonian. """ if debug: print(inspect.stack()[0][3]) # # check initial state # if isket(rho0): # if initial state is a ket and no collapse operator where given, # fallback on the unitary schrodinger equation solver if len(c_op_list) == 0: return _sesolve_list_td(H_func, rho0, tlist, expt_ops, args, opt) # Got a wave function as initial state: convert to density matrix. rho0 = ket2dm(rho0) # # construct liouvillian # if len(H_func) != 2: raise TypeError('Time-dependent Hamiltonian list must have two terms.') if not isinstance(H_func[0], (list, np.ndarray)) or len(H_func[0]) <= 1: raise TypeError('Time-dependent Hamiltonians must be a list ' + 'with two or more terms') if (not isinstance(H_func[1], (list, np.ndarray))) or \ (len(H_func[1]) != (len(H_func[0]) - 1)): raise TypeError('Time-dependent coefficients must be list with ' + 'length N-1 where N is the number of ' + 'Hamiltonian terms.') if opt.rhs_reuse and odeconfig.tdfunc is None: print("No previous time-dependent RHS found.") print("Generating one for you...") rhs_generate(H_func, args) lenh = len(H_func[0]) if opt.tidy: H_func[0] = [(H_func[0][k]).tidyup() for k in range(lenh)] L_func = [[liouvillian(H_func[0][0], c_op_list)], H_func[1]] for m in range(1, lenh): L_func[0].append(liouvillian(H_func[0][m], [])) # create data arrays for time-dependent RHS function Ldata = [L_func[0][k].data.data for k in range(lenh)] Linds = [L_func[0][k].data.indices for k in range(lenh)] Lptrs = [L_func[0][k].data.indptr for k in range(lenh)] # setup ode args string string = "" for k in range(lenh): string += ("Ldata[" + str(k) + "],Linds[" + str(k) + "],Lptrs[" + str(k) + "],") if args: td_consts = args.items() for elem in td_consts: string += str(elem[1]) if elem != td_consts[-1]: string += (",") # run code generator if not opt.rhs_reuse or odeconfig.tdfunc is None: if opt.rhs_filename is None: odeconfig.tdname = "rhs" + str(odeconfig.cgen_num) else: odeconfig.tdname = opt.rhs_filename cgen = Codegen(h_terms=n_L_terms, h_tdterms=Lcoeff, args=args, odeconfig=odeconfig) cgen.generate(odeconfig.tdname + ".pyx") code = compile('from ' + odeconfig.tdname + ' import cyq_td_ode_rhs', '<string>', 'exec') exec(code, globals()) odeconfig.tdfunc = cyq_td_ode_rhs # # setup integrator # initial_vector = mat2vec(rho0.full()) r = scipy.integrate.ode(odeconfig.tdfunc) r.set_integrator('zvode', method=opt.method, order=opt.order, atol=opt.atol, rtol=opt.rtol, nsteps=opt.nsteps, first_step=opt.first_step, min_step=opt.min_step, max_step=opt.max_step) r.set_initial_value(initial_vector, tlist[0]) code = compile('r.set_f_params(' + string + ')', '<string>', 'exec') exec(code) # # call generic ODE code # return _generic_ode_solve(r, rho0, tlist, expt_ops, opt, vec2mat)
def gradient_CFIM(self, M, obj_fun): ''' Input: 1) M: TYPE: list of Qobj (matrix) DESCRIPTION: measurement. It takes the form [M1,M2,...], where M1, M2 ... are matrices and satisfies M1+M2+... = identity matrix. 3) obj_fun TYPE: string DESCRIPTION: obj_fun = {'f0','f1','exact'}. Different with the single-parameter case, the GRAPE here uses two alternative objective functions: 'f0': $f_{0}=\sum_{\alpha} 1/F_{\alpha\alpha}$. 'f1': the lower bound $f_{1}=d^2/TrF$. Notice both above functions only use the gradient of diagonal entries of CFIM. 'exact': exact gradient for TrF^{-1}, however, it is ONLY valid for two-parameter systems. Output: 1) updated values of self.control_coefficients. Notice: To run this function, the function 'propagation_multiple()' has to be run first. ''' if len(self.control_Hamiltonian) > 2 and obj_fun == 'exact': raise TypeError('the "exact" mode is only valid for two-parameter estimations!') num = len(self.times) dim = self.freeHamiltonian.dims[0][0] dt = self.times[1] - self.times[0] Hc_coeff = self.control_coefficients D = self.propagator rhoTvec = self.rho[num-1] rhoTmat = Qobj(vec2mat(rhoTvec.full())) drhoTvec = self.rho_derivative[num-1] drhoTmat = [Qobj(vec2mat(drhoTvec[i].full())) for i in range(0, len(self.Hamiltonian_derivative))] #Generation of L1 and L2 for diagonal entries of CFIM (i.e., alpha = beta): L1 = [Qobj(np.zeros((dim, dim))) for i in range(0, len(self.Hamiltonian_derivative))] L2 = [Qobj(np.zeros((dim, dim))) for i in range(0, len(self.Hamiltonian_derivative))] for para_i in range(0, len(self.Hamiltonian_derivative)): for mi in range(0, len(M)): ptemp = (rhoTmat * M[mi]).tr() dptemp = (drhoTmat[para_i] * M[mi]).tr() if ptemp != 0: L1[para_i] += (dptemp / ptemp) * M[mi] L2[para_i] += ((dptemp / ptemp) ** 2) * M[mi] # Generation L2 for off-diagonal entries of CFIM in two-parameter estimation: if len(self.Hamiltonian_derivative) == 2: L2_offdiag = Qobj(np.zeros((dim, dim))) for mi in range(0, len(M)): ptp_2para = (rhoTmat * M[mi]).tr() dptp0_2para = (drhoTmat[0] * M[mi]).tr() dptp1_2para = (drhoTmat[1] * M[mi]).tr() L2_offdiag += (dptp0_2para * dptp1_2para / ptp_2para / ptp_2para) * M[mi] # Generation of CFIM at the target time CFIM_temp = CR.CFIM(rhoTmat, drhoTmat, M) norm_f0 = 0. for ci in range(0, len(self.Hamiltonian_derivative)): norm_f0 += 1 / CFIM_temp[ci, ci] norm_f0 = norm_f0**2 M2_2para = [[] for i in range(0, 2)] M3_2para = [[] for i in range(0, 2)] #update for ki in range(0, len(self.control_Hamiltonian)): Hk = 1.j * Qobj(liouvillian(self.control_Hamiltonian[ki]).full()) Hc_ki = Hc_coeff[ki] for ti in range(0, num): Mj1 = 1.j * D[ti+1][num-1] * Hk * self.rho[ti] Mj1mat = Qobj(vec2mat(Mj1.fulll())) delta = 0. for para_i in range(0, len(self.control_Hamiltonian)): dH0_i = 1.j * Qobj(liouvillian(self.Hamiltonian_derivative[para_i]).full) Mj2 = Qobj(np.zeros((dim*dim, 1))) for ri in range(0,ti+1): Mj2 += D[ti+1][num-1] * Hk * D[ri+1][ti] * dH0_i * self.rho[ri] Mj3 = Qobj(np.zeros((dim*dim,1))) for ri in range(ti+1, num): Mj3 += D[ri+1][num-1] * dH0_i * D[ti+1][ri] * Hk * self.rho[ti] Mj2mat = Qobj(vec2mat(Mj2.full())) Mj3mat = Qobj(vec2mat(Mj3.full())) term1 = dt * (L2[para_i] * Mj1mat).tr() term2 = -2 * (dt ** 2) * (L1[para_i] * (Mj2mat + Mj3mat)).tr() if obj_fun == 'f0': delta += np.real(term1+term2)/((CFIM_temp[para_i, para_i])**2)/norm_f0 elif obj_fun == 'f1': delta += np.real(term1+term2)/(float(len(self.control_Hamiltonian))**2) elif obj_fun == 'exact': delta += np.real(term1+term2)*(CFIM_temp[1-para_i, 1-para_i]**2 + CFIM_temp[0, 1]**2) \ /(CFIM_temp.tr()**2) M2_2para[para_i] = Mj2mat M3_2para[para_i] = Mj3mat if obj_fun == 'exact': grad_offdiag = dt * (L2_offdiag * Mj1mat).tr() \ - (dt**2) * (L1[1] * (M2_2para[0] + M3_2para[0])).tr() \ - (dt**2) * (L1[0] * (M2_2para[1] + M3_2para[1])).tr() delta += - np.real(2 * grad_offdiag * CFIM_temp[0, 1]/CFIM_temp.tr()) Hc_ki[ti] += Hc_ki[ti] + self.epsilon * delta Hc_coeff[ki] = Hc_ki self.control_coefficients = Hc_coeff
def bloch_redfield_tensor(H, a_ops, spectra_cb, c_ops=[], use_secular=True): """ Calculate the Bloch-Redfield tensor for a system given a set of operators and corresponding spectral functions that describes the system's coupling to its environment. .. note:: This tensor generation requires a time-independent Hamiltonian. Parameters ---------- H : :class:`qutip.qobj` System Hamiltonian. a_ops : list of :class:`qutip.qobj` List of system operators that couple to the environment. spectra_cb : list of callback functions List of callback functions that evaluate the noise power spectrum at a given frequency. c_ops : list of :class:`qutip.qobj` List of system collapse operators. use_secular : bool Flag (True of False) that indicates if the secular approximation should be used. Returns ------- R, kets: :class:`qutip.Qobj`, list of :class:`qutip.Qobj` R is the Bloch-Redfield tensor and kets is a list eigenstates of the Hamiltonian. """ # Sanity checks for input parameters if not isinstance(H, Qobj): raise TypeError("H must be an instance of Qobj") for a in a_ops: if not isinstance(a, Qobj) or not a.isherm: raise TypeError("Operators in a_ops must be Hermitian Qobj.") # default spectrum if not spectra_cb: spectra_cb = [lambda w: 1.0 for _ in a_ops] if c_ops is None: c_ops = [] # use the eigenbasis evals, ekets = H.eigenstates() N = len(evals) K = len(a_ops) #only Lindblad collapse terms if K == 0: Heb = H.transform(ekets) L = liouvillian(Heb, c_ops=[c_op.transform(ekets) for c_op in c_ops]) return L, ekets A = np.array([a_ops[k].transform(ekets).full() for k in range(K)]) Jw = np.zeros((K, N, N), dtype=complex) # pre-calculate matrix elements and spectral densities # W[m,n] = real(evals[m] - evals[n]) W = np.real(evals[:, np.newaxis] - evals[np.newaxis, :]) for k in range(K): # do explicit loops here in case spectra_cb[k] can not deal with array arguments for n in range(N): for m in range(N): Jw[k, n, m] = spectra_cb[k](W[n, m]) dw_min = np.abs(W[W.nonzero()]).min() # pre-calculate mapping between global index I and system indices a,b Iabs = np.empty((N * N, 3), dtype=int) for I, Iab in enumerate(Iabs): # important: use [:] to change array values, instead of creating new variable Iab Iab[0] = I Iab[1:] = vec2mat_index(N, I) # unitary part + dissipation from c_ops (if given): Heb = H.transform(ekets) L = liouvillian(Heb, c_ops=[c_op.transform(ekets) for c_op in c_ops]) # dissipative part: rows = [] cols = [] data = [] for I, a, b in Iabs: # only check use_secular once per I if use_secular: # only loop over those indices J which actually contribute Jcds = Iabs[np.where( np.abs(W[a, b] - W[Iabs[:, 1], Iabs[:, 2]]) < dw_min / 10.0)] else: Jcds = Iabs for J, c, d in Jcds: elem = 0 + 0j # summed over k, i.e., each operator coupling the system to the environment elem += 0.5 * (A[:, a, c] * A[:, d, b] * (Jw[:, c, a] + Jw[:, d, b]))[0] if b == d: # sum_{k,n} A[k, a, n] * A[k, n, c] * Jw[k, c, n]) elem -= 0.5 * np.sum(A[:, a, :] * A[:, :, c] * Jw[:, c, :]) if a == c: # sum_{k,n} A[k, d, n] * A[k, n, b] * Jw[k, d, n]) elem -= 0.5 * np.sum(A[:, d, :] * A[:, :, b] * Jw[:, d, :]) if elem != 0: rows.append(I) cols.append(J) data.append(elem) R = sp.coo_matrix((np.array(data), (np.array(rows), np.array(cols))), shape=(N**2, N**2), dtype=complex).tocsr() L.data = L.data + R return L, ekets
def steadystate(A, c_op_list=[], **kwargs): """Calculates the steady state for quantum evolution subject to the supplied Hamiltonian or Liouvillian operator and (if given a Hamiltonian) a list of collapse operators. If the user passes a Hamiltonian then it, along with the list of collapse operators, will be converted into a Liouvillian operator in Lindblad form. Parameters ---------- A : qobj A Hamiltonian or Liouvillian operator. c_op_list : list A list of collapse operators. method : str {'direct', 'iterative', 'iterative-bicg', 'svd', 'power'} Method for solving the underlying linear equation. Direct solver 'direct' (default), iterative GMRES method 'iterative', iterative method BICGSTAB 'iterative-bicg', SVD 'svd' (dense), or inverse-power method 'power'. sparse : bool, {True, False} Solve for the steady state using sparse algorithms. If set to False, the underlying Liouvillian operator will be converted into a dense matrix. Use only for 'smaller' systems. use_rcm : bool, {True, False} Use reverse Cuthill-Mckee reordering to minimize fill-in in the LU factorization of the Liouvillian. use_umfpack : bool {False, True} Use umfpack solver instead of SuperLU. For SciPy 0.14+, this option requires installing scikits.umfpack. maxiter : int, optional Maximum number of iterations to perform if using an iterative method such as 'iterative' (default=1000), or 'power' (default=10). tol : float, optional, default=1e-5 Tolerance used for terminating solver solution when using iterative solvers. use_precond : bool optional, default = True ITERATIVE ONLY. Use an incomplete sparse LU decomposition as a preconditioner for the 'iterative' GMRES and BICG solvers. Speeds up convergence time by orders of magnitude in many cases. M : {sparse matrix, dense matrix, LinearOperator}, optional Preconditioner for A. The preconditioner should approximate the inverse of A. Effective preconditioning dramatically improves the rate of convergence, for iterative methods. Does not affect other solvers. fill_factor : float, default=12 ITERATIVE ONLY. Specifies the fill ratio upper bound (>=1) of the iLU preconditioner. Lower values save memory at the cost of longer execution times and a possible singular factorization. drop_tol : float, default=1e-3 ITERATIVE ONLY. Sets the threshold for the magnitude of preconditioner elements that should be dropped. Can be reduced for a courser factorization at the cost of an increased number of iterations, and a possible singular factorization. diag_pivot_thresh : float, default=None ITERATIVE ONLY. Sets the threshold between [0,1] for which diagonal elements are considered acceptable pivot points when using a preconditioner. A value of zero forces the pivot to be the diagonal element. Returns ------- dm : qobj Steady state density matrix. Notes ----- The SVD method works only for dense operators (i.e. small systems). """ ss_args = _default_steadystate_args() for key in kwargs.keys(): if key in ss_args.keys(): ss_args[key]=kwargs[key] else: raise Exception('Invalid keyword argument passed to steadystate.') n_op = len(c_op_list) if isoper(A): if n_op == 0: raise TypeError('Cannot calculate the steady state for a ' + 'non-dissipative system ' + '(no collapse operators given)') else: A = liouvillian(A, c_op_list) if not issuper(A): raise TypeError('Solving for steady states requires ' + 'Liouvillian (super) operators') if ss_args['method'] == 'direct': if ss_args['sparse']: return _steadystate_direct_sparse(A, ss_args) else: return _steadystate_direct_dense(A) elif ss_args['method'] == 'iterative': return _steadystate_iterative(A, ss_args) elif ss_args['method'] == 'iterative-bicg': return _steadystate_iterative_bicg(A, ss_args) elif ss_args['method'] == 'svd': return _steadystate_svd_dense(A, ss_args) elif ss_args['method'] == 'power': return _steadystate_power(A, ss_args) else: raise ValueError('Invalid method argument for steadystate.')