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 serial_map(task, values, task_args=tuple(), task_kwargs={}, **kwargs): """ Serial mapping function with the same call signature as parallel_map, for easy switching between serial and parallel execution. This is functionally equivalent to: result = [task(value, *task_args, **task_kwargs) for value in values] This function work as a drop-in replacement of :func:`qutip.parallel_map`. Parameters ---------- task: a Python function The function that is to be called for each value in ``task_vec``. values: array / list The list or array of values for which the ``task`` function is to be evaluated. task_args: list / dictionary The optional additional argument to the ``task`` function. task_kwargs: list / dictionary The optional additional keyword argument to the ``task`` function. progress_bar: ProgressBar Progress bar class instance for showing progress. Returns -------- result : list The result list contains the value of ``task(value, *task_args, **task_kwargs)`` for each value in ``values``. """ try: progress_bar = kwargs['progress_bar'] if progress_bar is True: progress_bar = TextProgressBar() except: progress_bar = BaseProgressBar() progress_bar.start(len(values)) results = [] for n, value in enumerate(values): progress_bar.update(n) result = task(value, *task_args, **task_kwargs) results.append(result) progress_bar.finished() return results
def configure(self, H_sys, coup_op, coup_strength, temperature, N_cut, N_exp, planck=None, boltzmann=None, renorm=None, bnd_cut_approx=None, options=None, progress_bar=None, stats=None): """ Configure the solver using the passed parameters The parameters are described in the class attributes, unless there is some specific behaviour Parameters ---------- options : :class:`qutip.solver.Options` Generic solver options. If set to None the default options will be used progress_bar: BaseProgressBar Optional instance of BaseProgressBar, or a subclass thereof, for showing the progress of the simulation. If set to None, then the default progress bar will be used Set to False for no progress bar stats: :class:`qutip.solver.Stats` Optional instance of solver.Stats, or a subclass thereof, for storing performance statistics for the solver If set to True, then the default Stats for this class will be used Set to False for no stats """ self.H_sys = H_sys self.coup_op = coup_op self.coup_strength = coup_strength self.temperature = temperature self.N_cut = N_cut self.N_exp = N_exp if planck: self.planck = planck if boltzmann: self.boltzmann = boltzmann if isinstance(options, Options): self.options = options if isinstance(progress_bar, BaseProgressBar): self.progress_bar = progress_bar elif progress_bar == True: self.progress_bar = TextProgressBar() elif progress_bar == False: self.progress_bar = None if isinstance(stats, Stats): self.stats = stats elif stats == True: self.stats = self.create_new_stats() elif stats == False: self.stats = None
def freqCalc(rho0, frelist, tlist, target): wmw1 = 0 wmw2 = 0 wmw3 = 0 rmat = np.zeros((len(frelist), len(tlist))) pbar = ProgressBar(len(frelist)) c_ops = [] if g1 > 0.0: c_ops.append(np.sqrt(g1) * sigmam()) if g2 > 0.0: c_ops.append(np.sqrt(g2) * sigmaz()) sx = sigmax() sz = sigmaz() idm = qeye(2) for i in range(len(frelist)): pbar.update(i) if target == 1: e_ops = [tensor(sz, idm, idm)] wmw1 = frelist[i] elif target == 2: e_ops = [tensor(idm, sz, idm)] wmw2 = frelist[i] else: e_ops = [tensor(idm, idm, sz)] wmw3 = frelist[i] H0 = (w01 - wmw1) / 2 * tensor(sz, idm, idm) + ( w02 - wmw2) / 2 * tensor(idm, sz, idm) + (w03 - wmw3) / 2 * tensor( idm, idm, sz) + J12 * tensor(sz, sz, idm) + J23 * tensor( idm, sz, sz) Hint = (Bac1 / 4) * tensor(sx, idm, idm) + (Bac2 / 4) * tensor( idm, sx, idm) + (Bac3 / 4) * tensor(idm, idm, sx) H = H0 + Hint output = mesolve(H, rho0, tlist, c_ops, e_ops, options=options) rmat[i] = output.expect[0] return rmat
def _check_progress_bar(progress_bar): """ Check instance of progress_bar and return the object. """ if progress_bar is None: pbar = BaseProgressBar() if progress_bar is True: pbar = TextProgressBar() return pbar
def calculate(): rmat = np.zeros((len(frelist), len(tlist))) pbar = ProgressBar(len(frelist)) c_ops = [] if g1 > 0.0: c_ops.append(np.sqrt(g1) * sigmam()) if g2 > 0.0: c_ops.append(np.sqrt(g2) * sigmaz()) e_ops = [sigmax(), sigmay(), sigmaz()] for i in range(len(frelist)): pbar.update(i) wmw = frelist[i] args = {'wmw': wmw} # H0=(w01/2.0)*tensor(sigmaz(),qeye(2))+(w02/2.0)*tensor(qeye(2),sigmaz())+(J12/2.0)*tensor(sigmaz(),sigmaz()) # H1=Bac*tensor(sigmax(),qeye(2)) # H=[H0,[H1,'np.cos(wmw*t)']] H0 = (w01 - wmw) / 2 * tensor( sigmaz(), qeye(2)) + (w02 - wmw) / 2 * tensor( qeye(2), sigmaz()) + J12 / 2 * tensor(sigmaz(), sigmaz()) Hint = (Bac1 / 2) * tensor(sigmax(), qeye(2)) + (Bac2 / 2) * tensor( qeye(2), sigmax()) H = H0 + Hint output = mesolve(H, rho0, tlist, c_ops, [ tensor(sigmax(), qeye(2)), tensor(sigmay(), qeye(2)), tensor(sigmaz(), qeye(2)) ], args, options=options) rmat[i] = output.expect[2] return rmat
def freqFind(rho0): rmat=np.zeros((len(frelist),len(tlist))) pbar=ProgressBar(len(frelist)) c_ops=[] if g1>0.0: c_ops.append(np.sqrt(g1)*sigmam()) if g2>0.0: c_ops.append(np.sqrt(g2)*sigmaz()) # H0=(w01/2.0)*tensor(sigmaz(),qeye(2))+(w02/2.0)*tensor(qeye(2),sigmaz())+(J12/2.0)*tensor(sigmaz(),sigmaz()) # H1=Bac*tensor(sigmax(),qeye(2)) # H=[H0,[H1,'np.cos(wmw*t)']] for i in range(len(frelist)): pbar.update(i) wmw2=frelist[i] H0=(w01-wmw1)/2*tensor(sigmaz(),qeye(2))+(w02-wmw2)/2*tensor(qeye(2),sigmaz())+J12*tensor(sigmaz(),sigmaz()) Hint=(Bac1/4)*tensor(sigmax(),qeye(2))+(Bac2/4)*tensor(qeye(2),sigmax()) H=H0+Hint output=mesolve(H,rho0,tlist,c_ops,[tensor(qeye(2),sigmaz())],options=options) rmat[i]=output.expect[0] return rmat
def test_create_progress_bar(self): Q = sigmaz() H = sigmax() bath = Bath([ BathExponent("R", None, Q=Q, ck=1.1, vk=2.1), ]) hsolver = HEOMSolver(H, bath, 2) assert isinstance(hsolver.progress_bar, BaseProgressBar) hsolver = HEOMSolver(H, bath, 2, progress_bar=True) assert isinstance(hsolver.progress_bar, TextProgressBar) hsolver = HEOMSolver(H, bath, 2, progress_bar=TextProgressBar()) assert isinstance(hsolver.progress_bar, BaseProgressBar) with pytest.raises(TypeError): HEOMSolver(H, bath, 2, progress_bar=False)
def __init__(self, H_sys, coup_op, coup_strength, temperature, N_cut, N_exp, cut_freq, planck=1.0, boltzmann=1.0, renorm=True, bnd_cut_approx=True, options=None, progress_bar=None, stats=None): self.reset() if options is None: self.options = Options() else: self.options = options self.progress_bar = False if progress_bar is None: self.progress_bar = BaseProgressBar() elif progress_bar == True: self.progress_bar = TextProgressBar() # the other attributes will be set in the configure method self.configure(H_sys, coup_op, coup_strength, temperature, N_cut, N_exp, cut_freq, planck=planck, boltzmann=boltzmann, renorm=renorm, bnd_cut_approx=bnd_cut_approx, stats=stats)
def transmission_calc_array(queue): args = [] for index, value in enumerate(queue.wd_points): args.append([value, queue.params[index]]) steady_states = parallel_map(transmission_calc, args, num_cpus=10, progress_bar=TextProgressBar()) transmissions = np.array( [steady_state[0] for steady_state in steady_states]) edge_occupations_c = np.array( [steady_state[1] for steady_state in steady_states]) edge_occupations_c = np.absolute(edge_occupations_c) edge_occupations_t = np.array( [steady_state[2] for steady_state in steady_states]) edge_occupations_t = np.absolute(edge_occupations_t) results = Results(queue.params, queue.wd_points, transmissions, edge_occupations_c, edge_occupations_t) abs_transmissions = np.absolute(transmissions) return results
if False : "Calculates the coefficient of the hamiltonian time-dependant terms" def coeff_rot(t): return(np.exp(1j*2*delta*(t-T1)*(t>=T1 and t<=T2))) H_NOT=0*a #H=-1j*(a**2*eps_2-a.dag()**2*np.conjugate(eps_2)) cops1=[k1**0.5*a] cops2_bis=convert_time_dependant_cops_2(k2**0.5*a**2,coeff_rot,-k2**0.5*alpha_inf_abs**2*Ia) cops_bis=cops1+cops2_bis cops_noNOT=[k1**0.5*a,k2**0.5*(a**2-alpha_inf_abs**2*Ia)] "Resolution of the equation over time with mesolve" init_state=C_alpha_plus#initial state tlist = np.linspace(0, T_final, n_t) res_NOT = qt.mesolve(H_NOT, init_state, tlist, cops, progress_bar=TextProgressBar()) "Resolution of the equation without NOT" #to have the theoretical fidelity (no analytical formula) res_free= qt.mesolve(H_NOT, init_state, tlist, cops_noNOT, progress_bar=TextProgressBar()) #Wigner res_NOT_Wigner = compute_Wigner([-6, 6, 51], nbWignerPlot,nbCols, n_t,-1) res_NOT_Wigner.draw_Wigner(res_NOT.states, title='Simulations NOT') res_free_Wigner= compute_Wigner([-6,6, 51], nbWignerPlot,nbCols, n_t,-1) res_free_Wigner.draw_Wigner(res_free.states, title='Simulations without NOT') # #
def coef_eps_conj(t, args): return (np.conjugate(coef_eps(t, args))) H_NOT = [[a**2, coef_eps], [a.dag()**2, coef_eps_conj]] #H=-1j*(a**2*eps_2-a.dag()**2*np.conjugate(eps_2)) cops = [k1**0.5 * a, k2**0.5 * a**2] "Resolution of the equation over time with mesolve" init_state = qt.coherent(Na, alpha) #initial state tlist = np.linspace(0, T_final, n_t) res_NOT = qt.mesolve(H_NOT, init_state, tlist, cops, progress_bar=TextProgressBar()) ##Wigner res_NOT_Wigner = compute_Wigner([-6, 6, 51], nbWignerPlot, nbCols, n_t, -1) res_NOT_Wigner.draw_Wigner(res_NOT.states, title='Simulations with NOT') # res_free_Wigner= compute_Wigner([-6,6, 51], nbWignerPlot,nbCols, n_t,-1) # res_free_Wigner.draw_Wigner(res_free.states, title='Simulations without NOT') "Plot the evolution of fidelity over time" target_res = [] #to check the Wigner fidelity_NOT_list = [] for ii, t in enumerate(tlist): if t <= T1: current_theta = 0 elif (t > T1) and (t < T2): current_theta = delta * (t - T1)
def run(self, num_traj=0, psi0=None, tlist=None, args={}, e_ops=None, options=None, progress_bar=True, map_func=parallel_map, map_kwargs={}): # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% # 4 situation for run: # - first run # - change parameters # - add trajectories # (self.add_traj) Not Implemented # - continue from the last time and states # (self.continue_runs) Not Implemented # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% options = options if options is not None else self.options if self.ran and tlist[0] == self.t: # psi0 is ignored since we restart from a # different states for each trajectories self.continue_runs(num_traj, tlist, args, e_ops, options, progress_bar, map_func, map_kwargs) return if args and args != self.ss.args: self.ss.set_args(self.ss, args) self.reset() if e_ops and e_ops != self.e_ops: self.set_e_ops(e_ops) self.reset() if psi0 is not None and psi0 != self.psi0: self.psi0 = psi0 self.reset() tlist = np.array(tlist) if tlist is not None and np.all(tlist != self.tlist): self.tlist = tlist self.reset() if self.ran: if options.store_states and self._psi_out[0].shape[0] == 1: self.reset() else: # if not reset here, add trajectories self.add_traj(num_traj, progress_bar, map_func, map_kwargs) return if not num_traj: num_traj = options.ntraj if options.num_cpus == 1 or num_traj == 1: map_func = serial_map if len(self.seeds) != num_traj: self.seed(num_traj, self.seeds) if not progress_bar: progress_bar = BaseProgressBar() elif progress_bar is True: progress_bar = TextProgressBar() # set arguments for input to monte carlo map_kwargs_ = {'progress_bar': progress_bar, 'num_cpus': options.num_cpus} map_kwargs_.update(map_kwargs) map_kwargs = map_kwargs_ if self.e_ops is None: self.set_e_ops() if self.ss.type == "Diagonal": results = map_func(self._single_traj_diag, list(range(num_traj)), **map_kwargs) else: results = map_func(self._single_traj, list(range(num_traj)), **map_kwargs) self.t = self.tlist[-1] self.num_traj = num_traj self.ran = True for result in results: state_out, ss_out, expect, collapse = result self._psi_out.append(state_out) self._ss_out.append(ss_out) self._expect_out.append(expect) self._collapse.append(collapse) self._psi_out = np.stack(self._psi_out) self._ss_out = np.stack(self._ss_out)
def time_dependant_c_ops(t,args): return B**2-1/2*alpha*(A+alpha)+1/2*alpha*np.exp(2j*np.pi/T*t)*(A-alpha) H=0*A #H=-1j*(a**2*eps_2-a.dag()**2*np.conjugate(eps_2)) cops=[k2**0.5*(A**2-alpha**2), time_dependant_c_ops] "Resolution of the equation over time with mesolve" init_state= C_alpha_plus #initial state n_t = 1001 T=np.pi /delta #total time of simulation tlist = np.linspace(0, 2*T, n_t) res = qt.mesolve(H, init_state, tlist, cops, progress_bar=TextProgressBar()) #Wigner test_Wigner = compute_Wigner([-4, 4, 51], nbWignerPlot,nbCols, n_t,-1) test_Wigner.draw_Wigner(res) "Plot the evolution of fidelity over time" fidelity_list=[] for ii,t in enumerate(tlist): if t<T/2: fidelity_list.append(qt.fidelity(res.states[ii],C_alpha_plus)) elif t>3*T/2: fidelity_list.append(qt.fidelity(res.states[ii],C_alpha_plus)) else: theta=np.exp(-1j*delta*(t-T/2)) C_alpha_plus_rot=qt.coherent(Na, alpha*theta)+qt.coherent(Na, -alpha*theta)
def sesolve(H, psi0, tlist, e_ops=[], args={}, options=Options(), progress_bar=BaseProgressBar(), _safe_mode=True): """ Schrodinger equation evolution of a state vector or unitary matrix for a given Hamiltonian. Evolve the state vector (`psi0`) using a given Hamiltonian (`H`), by integrating the set of ordinary differential equations that define the system. Alternatively evolve a unitary matrix in solving the Schrodinger operator equation. The output is either the state vector or unitary matrix at arbitrary points in time (`tlist`), or the expectation values of the supplied operators (`e_ops`). If e_ops is a callback function, it is invoked for each time in `tlist` with time and the state as arguments, and the function does not use any return values. e_ops cannot be used in conjunction with solving the Schrodinger operator equation Parameters ---------- H : :class:`qutip.qobj`, :class:`qutip.qobjevo`, *list*, *callable* system Hamiltonian as a Qobj, list of Qobj and coefficient, QobjEvo, or a callback function for time-dependent Hamiltonians. list format and options can be found in QobjEvo's description. psi0 : :class:`qutip.qobj` initial state vector (ket) or initial unitary operator `psi0 = U` tlist : *list* / *array* list of times for :math:`t`. e_ops : list of :class:`qutip.qobj` / callback function single operator or list of operators for which to evaluate expectation values. For list operator evolution, the overlapse is computed: tr(e_ops[i].dag()*op(t)) args : *dictionary* dictionary of parameters for time-dependent Hamiltonians options : :class:`qutip.Qdeoptions` with options for the ODE solver. progress_bar : BaseProgressBar Optional instance of BaseProgressBar, or a subclass thereof, for showing the progress of the simulation. Returns ------- output: :class:`qutip.solver` An instance of the class :class:`qutip.solver`, which contains either an *array* of expectation values for the times specified by `tlist`, or an *array* or state vectors corresponding to the times in `tlist` [if `e_ops` is an empty list], or nothing if a callback function was given inplace of operators for which to calculate the expectation values. """ if isinstance(e_ops, Qobj): e_ops = [e_ops] elif isinstance(e_ops, dict): e_ops_dict = e_ops e_ops = [e for e in e_ops.values()] else: e_ops_dict = None if progress_bar is True: progress_bar = TextProgressBar() if not (psi0.isket or psi0.isunitary): raise TypeError("The unitary solver requires psi0 to be" " a ket as initial state" " or a unitary as initial operator.") if options.rhs_reuse and not isinstance(H, SolverSystem): # TODO: deprecate when going to class based solver. if "sesolve" in solver_safe: # print(" ") H = solver_safe["sesolve"] else: pass # raise Exception("Could not find the Hamiltonian to reuse.") #check if should use OPENMP check_use_openmp(options) if isinstance(H, SolverSystem): ss = H elif isinstance(H, (list, Qobj, QobjEvo)): ss = _sesolve_QobjEvo(H, tlist, args, options) elif callable(H): ss = _sesolve_func_td(H, args, options) else: raise Exception("Invalid H type") func, ode_args = ss.makefunc(ss, psi0, args, options) if _safe_mode: v = psi0.full().ravel('F') func(0., v, *ode_args) + v res = _generic_ode_solve(func, ode_args, psi0, tlist, e_ops, options, progress_bar, dims=psi0.dims) if e_ops_dict: res.expect = { e: res.expect[n] for n, e in enumerate(e_ops_dict.keys()) } res.SolverSystem = ss return res
def sesolve(H, psi0, tlist, e_ops=[], args={}, options=None, progress_bar=None, _safe_mode=True): """ Schrodinger equation evolution of a state vector or unitary matrix for a given Hamiltonian. Evolve the state vector (`psi0`) using a given Hamiltonian (`H`), by integrating the set of ordinary differential equations that define the system. Alternatively evolve a unitary matrix in solving the Schrodinger operator equation. The output is either the state vector or unitary matrix at arbitrary points in time (`tlist`), or the expectation values of the supplied operators (`e_ops`). If e_ops is a callback function, it is invoked for each time in `tlist` with time and the state as arguments, and the function does not use any return values. e_ops cannot be used in conjunction with solving the Schrodinger operator equation Parameters ---------- H : :class:`qutip.qobj` system Hamiltonian, or a callback function for time-dependent Hamiltonians. psi0 : :class:`qutip.qobj` initial state vector (ket) or initial unitary operator `psi0 = U` tlist : *list* / *array* list of times for :math:`t`. e_ops : list of :class:`qutip.qobj` / callback function single single operator or list of operators for which to evaluate expectation values. Must be empty list operator evolution args : *dictionary* dictionary of parameters for time-dependent Hamiltonians options : :class:`qutip.Qdeoptions` with options for the ODE solver. progress_bar : BaseProgressBar Optional instance of BaseProgressBar, or a subclass thereof, for showing the progress of the simulation. Returns ------- output: :class:`qutip.solver` An instance of the class :class:`qutip.solver`, which contains either an *array* of expectation values for the times specified by `tlist`, or an *array* or state vectors corresponding to the times in `tlist` [if `e_ops` is an empty list], or nothing if a callback function was given inplace of operators for which to calculate the expectation values. """ # check initial state: must be a state vector if _safe_mode: if not isinstance(psi0, Qobj): raise TypeError("psi0 must be Qobj") if psi0.isket: pass elif psi0.isunitary: if not e_ops == []: raise TypeError("Must have e_ops = [] when initial condition" " psi0 is a unitary operator.") else: raise TypeError("The unitary solver requires psi0 to be" " a ket as initial state" " or a unitary as initial operator.") _solver_safety_check(H, psi0, c_ops=[], e_ops=e_ops, args=args) if isinstance(e_ops, Qobj): e_ops = [e_ops] if isinstance(e_ops, dict): e_ops_dict = e_ops e_ops = [e for e in e_ops.values()] else: e_ops_dict = None if progress_bar is None: progress_bar = BaseProgressBar() elif progress_bar is True: progress_bar = TextProgressBar() # convert array based time-dependence to string format H, _, args = _td_wrap_array_str(H, [], args, tlist) # check for type (if any) of time-dependent inputs n_const, n_func, n_str = _td_format_check(H, []) if options is None: options = Options() if (not options.rhs_reuse) or (not config.tdfunc): # reset config time-dependence flags to default values config.reset() #check if should use OPENMP check_use_openmp(options) if n_func > 0: res = _sesolve_list_func_td(H, psi0, tlist, e_ops, args, options, progress_bar) elif n_str > 0: res = _sesolve_list_str_td(H, psi0, tlist, e_ops, args, options, progress_bar) elif isinstance(H, (types.FunctionType, types.BuiltinFunctionType, partial)): res = _sesolve_func_td(H, psi0, tlist, e_ops, args, options, progress_bar) elif isinstance(H, Qobj): res = _sesolve_const(H, psi0, tlist, e_ops, args, options, progress_bar) else: raise TypeError("Invalid Hamiltonian specification") if e_ops_dict: res.expect = {e: res.expect[n] for n, e in enumerate(e_ops_dict.keys())} return res
def brmesolve(H, psi0, tlist, a_ops=[], e_ops=[], c_ops=[], args={}, use_secular=True, tol=qset.atol, spectra_cb=None, options=None, progress_bar=None, _safe_mode=True): """ Solves for the dynamics of a system using the Bloch-Redfield master equation, given an input Hamiltonian, Hermitian bath-coupling terms and their associated spectrum functions, as well as possible Lindblad collapse operators. For time-independent systems, the Hamiltonian must be given as a Qobj, whereas the bath-coupling terms (a_ops), must be written as a nested list of operator - spectrum function pairs, where the frequency is specified by the `w` variable. *Example* a_ops = [[a+a.dag(),lambda w: 0.2*(w>=0)]] For time-dependent systems, the Hamiltonian, a_ops, and Lindblad collapse operators (c_ops), can be specified in the QuTiP string-based time-dependent format. For the a_op spectra, the frequency variable must be `w`, and the string cannot contain any other variables other than the possibility of having a time-dependence through the time variable `t`: *Example* a_ops = [[a+a.dag(), '0.2*exp(-t)*(w>=0)']] Parameters ---------- H : Qobj / list System Hamiltonian given as a Qobj or nested list in string-based format. psi0: Qobj Initial density matrix or state vector (ket). tlist : array_like List of times for evaluating evolution a_ops : list Nested list of Hermitian system operators that couple to the bath degrees of freedom, along with their associated spectra. e_ops : list List of operators for which to evaluate expectation values. c_ops : list List of system collapse operators, or nested list in string-based format. args : dict (not implimented) Placeholder for future implementation, kept for API consistency. use_secular : bool {True} Use secular approximation when evaluating bath-coupling terms. tol : float {qutip.setttings.atol} Tolerance used for removing small values after basis transformation. spectra_cb : list DEPRECIATED. Do not use. options : :class:`qutip.solver.Options` Options for the solver. progress_bar : BaseProgressBar Optional instance of BaseProgressBar, or a subclass thereof, for showing the progress of the simulation. Returns ------- result: :class:`qutip.solver.Result` An instance of the class :class:`qutip.solver.Result`, which contains either an array of expectation values, for operators given in e_ops, or a list of states for the times specified by `tlist`. """ if isinstance(c_ops, Qobj): c_ops = [c_ops] if isinstance(e_ops, Qobj): e_ops = [e_ops] if isinstance(e_ops, dict): e_ops_dict = e_ops e_ops = [e for e in e_ops.values()] else: e_ops_dict = None 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 if _safe_mode: _solver_safety_check(H, psi0, a_ops + c_ops, e_ops, args) # check for type (if any) of time-dependent inputs _, n_func, n_str = _td_format_check(H, a_ops + c_ops) if progress_bar is None: progress_bar = BaseProgressBar() elif progress_bar is True: progress_bar = TextProgressBar() if options is None: options = Options() if (not options.rhs_reuse) or (not config.tdfunc): # reset config collapse and time-dependence flags to default values config.reset() #check if should use OPENMP check_use_openmp(options) if n_str == 0: R, ekets = bloch_redfield_tensor(H, a_ops, spectra_cb=None, c_ops=c_ops) output = Result() output.solver = "brmesolve" output.times = tlist results = bloch_redfield_solve(R, ekets, psi0, tlist, e_ops, options, progress_bar=progress_bar) if e_ops: output.expect = results else: output.states = results return output elif n_str != 0 and n_func == 0: output = _td_brmesolve(H, psi0, tlist, a_ops=a_ops, e_ops=e_ops, c_ops=c_ops, use_secular=use_secular, tol=tol, options=options, progress_bar=progress_bar, _safe_mode=_safe_mode) return output else: raise Exception('Cannot mix func and str formats.')
def propagator(H, t, c_op_list=[], args={}, options=None, unitary_mode='batch', parallel=False, progress_bar=None, _safe_mode=True, **kwargs): r""" Calculate the propagator U(t) for the density matrix or wave function such that :math:`\psi(t) = U(t)\psi(0)` or :math:`\rho_{\mathrm vec}(t) = U(t) \rho_{\mathrm vec}(0)` where :math:`\rho_{\mathrm vec}` is the vector representation of the density matrix. Parameters ---------- H : qobj or list Hamiltonian as a Qobj instance of a nested list of Qobjs and coefficients in the list-string or list-function format for time-dependent Hamiltonians (see description in :func:`qutip.mesolve`). t : float or array-like Time or list of times for which to evaluate the propagator. c_op_list : list List of qobj collapse operators. args : list/array/dictionary Parameters to callback functions for time-dependent Hamiltonians and collapse operators. options : :class:`qutip.Options` with options for the ODE solver. unitary_mode = str ('batch', 'single') Solve all basis vectors simulaneously ('batch') or individually ('single'). parallel : bool {False, True} Run the propagator in parallel mode. This will override the unitary_mode settings if set to True. progress_bar: BaseProgressBar Optional instance of BaseProgressBar, or a subclass thereof, for showing the progress of the simulation. By default no progress bar is used, and if set to True a TextProgressBar will be used. Returns ------- a : qobj Instance representing the propagator :math:`U(t)`. """ kw = _default_kwargs() if 'num_cpus' in kwargs: num_cpus = kwargs['num_cpus'] else: num_cpus = kw['num_cpus'] if progress_bar is None: progress_bar = BaseProgressBar() elif progress_bar is True: progress_bar = TextProgressBar() if options is None: options = Options() options.rhs_reuse = True rhs_clear() if isinstance(t, (int, float, np.integer, np.floating)): tlist = [0, t] else: tlist = t if _safe_mode: _solver_safety_check(H, None, c_ops=c_op_list, e_ops=[], args=args) td_type = _td_format_check(H, c_op_list, solver='me') if isinstance( H, (types.FunctionType, types.BuiltinFunctionType, functools.partial)): H0 = H(0.0, args) if unitary_mode == 'batch': # batch don't work with function Hamiltonian unitary_mode = 'single' elif isinstance(H, list): H0 = H[0][0] if isinstance(H[0], list) else H[0] else: H0 = H if len(c_op_list) == 0 and H0.isoper: # calculate propagator for the wave function N = H0.shape[0] dims = H0.dims if parallel: unitary_mode = 'single' u = np.zeros([N, N, len(tlist)], dtype=complex) output = parallel_map(_parallel_sesolve, range(N), task_args=(N, H, tlist, args, options), progress_bar=progress_bar, num_cpus=num_cpus) for n in range(N): for k, t in enumerate(tlist): u[:, n, k] = output[n].states[k].full().T else: if unitary_mode == 'single': output = sesolve(H, qeye(dims[0]), tlist, [], args, options, _safe_mode=False) if len(tlist) == 2: return output.states[-1] else: return output.states elif unitary_mode == 'batch': u = np.zeros(len(tlist), dtype=object) _rows = np.array([(N + 1) * m for m in range(N)]) _cols = np.zeros_like(_rows) _data = np.ones_like(_rows, dtype=complex) psi0 = Qobj(sp.coo_matrix((_data, (_rows, _cols))).tocsr()) if td_type[1] > 0 or td_type[2] > 0: H2 = [] for k in range(len(H)): if isinstance(H[k], list): H2.append([tensor(qeye(N), H[k][0]), H[k][1]]) else: H2.append(tensor(qeye(N), H[k])) else: H2 = tensor(qeye(N), H) options.normalize_output = False output = sesolve(H2, psi0, tlist, [], args=args, options=options, _safe_mode=False) for k, t in enumerate(tlist): u[k] = sp_reshape(output.states[k].data, (N, N)) unit_row_norm(u[k].data, u[k].indptr, u[k].shape[0]) u[k] = u[k].T.tocsr() else: raise Exception('Invalid unitary mode.') elif len(c_op_list) == 0 and H0.issuper: # calculate the propagator for the vector representation of the # density matrix (a superoperator propagator) unitary_mode = 'single' N = H0.shape[0] sqrt_N = int(np.sqrt(N)) dims = H0.dims u = np.zeros([N, N, len(tlist)], dtype=complex) if parallel: output = parallel_map(_parallel_mesolve, range(N * N), task_args=(sqrt_N, H, tlist, c_op_list, args, options), progress_bar=progress_bar, num_cpus=num_cpus) for n in range(N * N): for k, t in enumerate(tlist): u[:, n, k] = mat2vec(output[n].states[k].full()).T else: rho0 = qeye(N, N) rho0.dims = [[sqrt_N, sqrt_N], [sqrt_N, sqrt_N]] output = mesolve(H, psi0, tlist, [], args, options, _safe_mode=False) if len(tlist) == 2: return output.states[-1] else: return output.states else: # calculate the propagator for the vector representation of the # density matrix (a superoperator propagator) unitary_mode = 'single' N = H0.shape[0] dims = [H0.dims, H0.dims] u = np.zeros([N * N, N * N, len(tlist)], dtype=complex) if parallel: output = parallel_map(_parallel_mesolve, range(N * N), task_args=(N, H, tlist, c_op_list, args, options), progress_bar=progress_bar, num_cpus=num_cpus) for n in range(N * N): for k, t in enumerate(tlist): u[:, n, k] = mat2vec(output[n].states[k].full()).T else: progress_bar.start(N * N) for n in range(N * N): progress_bar.update(n) col_idx, row_idx = np.unravel_index(n, (N, N)) rho0 = Qobj( sp.csr_matrix(([1], ([row_idx], [col_idx])), shape=(N, N), dtype=complex)) output = mesolve(H, rho0, tlist, c_op_list, [], args, options, _safe_mode=False) for k, t in enumerate(tlist): u[:, n, k] = mat2vec(output.states[k].full()).T progress_bar.finished() if len(tlist) == 2: if unitary_mode == 'batch': return Qobj(u[-1], dims=dims) else: return Qobj(u[:, :, 1], dims=dims) else: if unitary_mode == 'batch': return np.array([Qobj(u[k], dims=dims) for k in range(len(tlist))], dtype=object) else: return np.array( [Qobj(u[:, :, k], dims=dims) for k in range(len(tlist))], dtype=object)
def parallel_map( task, values, task_args=None, task_kwargs=None, num_cpus=None, progress_bar=None, ): """Map function `task` onto `values`, in parallel. This function's interface is identical to :func:`qutip.parallel.parallel_map` as of QuTiP 4.5.0, but has the option of using :mod:`loky` as a backend (see :func:`set_parallelization`). It also eliminates internal threads, according to :obj:`USE_THREADPOOL_LIMITS`. """ # TODO: if QuTiP's parallel_map catches up, we can remove this function, # and put QuTiP's parallel_map into __all__ to maintain krotov's interface. if task_args is None: task_args = () if task_kwargs is None: task_kwargs = {} if num_cpus is None: num_cpus = multiprocessing.cpu_count() if progress_bar is None: progress_bar = BaseProgressBar() if progress_bar is True: progress_bar = TextProgressBar() progress_bar.start(len(values)) nfinished = [0] def _update_progress_bar(x): nfinished[0] += 1 progress_bar.update(nfinished[0]) if USE_LOKY: Executor = LokyReusableExecutor if USE_THREADPOOL_LIMITS: Executor = partial( LokyReusableExecutor, initializer=_process_threadpool_limits_initializier, ) else: Executor = ProcessPoolExecutor _threadpool_limits = _no_threadpool_limits if USE_THREADPOOL_LIMITS: _threadpool_limits = threadpool_limits with _threadpool_limits(limits=1): with Executor(max_workers=num_cpus) as executor: jobs = [] try: for value in values: args = (value, ) + tuple(task_args) job = executor.submit(task, *args, **task_kwargs) job.add_done_callback(_update_progress_bar) jobs.append(job) res = [job.result() for job in jobs] except KeyboardInterrupt as e: raise e progress_bar.finished() return res
def sesolve(H, psi0, tlist, e_ops=None, args=None, options=None, progress_bar=None, _safe_mode=True): """ Schrödinger equation evolution of a state vector or unitary matrix for a given Hamiltonian. Evolve the state vector (``psi0``) using a given Hamiltonian (``H``), by integrating the set of ordinary differential equations that define the system. Alternatively evolve a unitary matrix in solving the Schrodinger operator equation. The output is either the state vector or unitary matrix at arbitrary points in time (``tlist``), or the expectation values of the supplied operators (``e_ops``). If ``e_ops`` is a callback function, it is invoked for each time in ``tlist`` with time and the state as arguments, and the function does not use any return values. ``e_ops`` cannot be used in conjunction with solving the Schrodinger operator equation Parameters ---------- H : :class:`~Qobj`, :class:`~QobjEvo`, list, or callable System Hamiltonian as a :obj:`~Qobj , list of :obj:`Qobj` and coefficient, :obj:`~QObjEvo`, or a callback function for time-dependent Hamiltonians. List format and options can be found in QobjEvo's description. psi0 : :class:`~Qobj` Initial state vector (ket) or initial unitary operator ``psi0 = U``. tlist : array_like of float List of times for :math:`t`. e_ops : None / list / callback function, optional A list of operators as `Qobj` and/or callable functions (can be mixed) or a single callable function. For callable functions, they are called as ``f(t, state)`` and return the expectation value. A single callback's expectation value can be any type, but a callback part of a list must return a number as the expectation value. For operators, the result's expect will be computed by :func:`qutip.expect` when the state is a ``ket``. For operator evolution, the overlap is computed by: :: (e_ops[i].dag() * op(t)).tr() args : dict, optional Dictionary of scope parameters for time-dependent Hamiltonians. options : :obj:`~solver.Options`, optional Options for the ODE solver. progress_bar : :obj:`~BaseProgressBar`, optional Optional instance of :obj:`~BaseProgressBar`, or a subclass thereof, for showing the progress of the simulation. Returns ------- output: :class:`~solver.Result` An instance of the class :class:`~solver.Result`, which contains either an array of expectation values for the times specified by ``tlist``, or an array or state vectors corresponding to the times in ``tlist`` (if ``e_ops`` is an empty list), or nothing if a callback function was given inplace of operators for which to calculate the expectation values. """ if e_ops is None: e_ops = [] if isinstance(e_ops, Qobj): e_ops = [e_ops] elif isinstance(e_ops, dict): e_ops_dict = e_ops e_ops = [e for e in e_ops.values()] else: e_ops_dict = None if progress_bar is None: progress_bar = BaseProgressBar() if progress_bar is True: progress_bar = TextProgressBar() if not (psi0.isket or psi0.isunitary): raise TypeError("The unitary solver requires psi0 to be" " a ket as initial state" " or a unitary as initial operator.") if options is None: options = Options() if options.rhs_reuse and not isinstance(H, SolverSystem): # TODO: deprecate when going to class based solver. if "sesolve" in solver_safe: H = solver_safe["sesolve"] if args is None: args = {} check_use_openmp(options) if isinstance(H, SolverSystem): ss = H elif isinstance(H, (list, Qobj, QobjEvo)): ss = _sesolve_QobjEvo(H, tlist, args, options) elif callable(H): ss = _sesolve_func_td(H, args, options) else: raise Exception("Invalid H type") func, ode_args = ss.makefunc(ss, psi0, args, e_ops, options) if _safe_mode: v = psi0.full().ravel('F') func(0., v, *ode_args) + v res = _generic_ode_solve(func, ode_args, psi0, tlist, e_ops, options, progress_bar, dims=psi0.dims) if e_ops_dict: res.expect = {e: res.expect[n] for n, e in enumerate(e_ops_dict.keys())} return res
def propagator(H, t, c_op_list, args=None, options=None, sparse=False, progress_bar=None): """ Calculate the propagator U(t) for the density matrix or wave function such that :math:`\psi(t) = U(t)\psi(0)` or :math:`\\rho_{\mathrm vec}(t) = U(t) \\rho_{\mathrm vec}(0)` where :math:`\\rho_{\mathrm vec}` is the vector representation of the density matrix. Parameters ---------- H : qobj or list Hamiltonian as a Qobj instance of a nested list of Qobjs and coefficients in the list-string or list-function format for time-dependent Hamiltonians (see description in :func:`qutip.mesolve`). t : float or array-like Time or list of times for which to evaluate the propagator. c_op_list : list List of qobj collapse operators. args : list/array/dictionary Parameters to callback functions for time-dependent Hamiltonians and collapse operators. options : :class:`qutip.Options` with options for the ODE solver. progress_bar: BaseProgressBar Optional instance of BaseProgressBar, or a subclass thereof, for showing the progress of the simulation. By default no progress bar is used, and if set to True a TextProgressBar will be used. Returns ------- a : qobj Instance representing the propagator :math:`U(t)`. """ if progress_bar is None: progress_bar = BaseProgressBar() elif progress_bar is True: progress_bar = TextProgressBar() if options is None: options = Options() options.rhs_reuse = True rhs_clear() if isinstance(t, (int, float, np.integer, np.floating)): tlist = [0, t] else: tlist = t if isinstance( H, (types.FunctionType, types.BuiltinFunctionType, functools.partial)): H0 = H(0.0, args) elif isinstance(H, list): H0 = H[0][0] if isinstance(H[0], list) else H[0] else: H0 = H if len(c_op_list) == 0 and H0.isoper: # calculate propagator for the wave function N = H0.shape[0] dims = H0.dims u = np.zeros([N, N, len(tlist)], dtype=complex) progress_bar.start(N) for n in range(0, N): progress_bar.update(n) psi0 = basis(N, n) output = sesolve(H, psi0, tlist, [], args, options) for k, t in enumerate(tlist): u[:, n, k] = output.states[k].full().T progress_bar.finished() # todo: evolving a batch of wave functions: # psi_0_list = [basis(N, n) for n in range(N)] # psi_t_list = mesolve(H, psi_0_list, [0, t], [], [], args, options) # for n in range(0, N): # u[:,n] = psi_t_list[n][1].full().T elif len(c_op_list) == 0 and H0.issuper: # calculate the propagator for the vector representation of the # density matrix (a superoperator propagator) N = H0.shape[0] dims = H0.dims u = np.zeros([N, N, len(tlist)], dtype=complex) progress_bar.start(N) for n in range(0, N): progress_bar.update(n) psi0 = basis(N, n) rho0 = Qobj(vec2mat(psi0.full())) output = mesolve(H, rho0, tlist, [], [], args, options) for k, t in enumerate(tlist): u[:, n, k] = mat2vec(output.states[k].full()).T progress_bar.finished() else: # calculate the propagator for the vector representation of the # density matrix (a superoperator propagator) N = H0.shape[0] dims = [H0.dims, H0.dims] u = np.zeros([N * N, N * N, len(tlist)], dtype=complex) if sparse: progress_bar.start(N * N) for n in range(N * N): progress_bar.update(n) psi0 = basis(N * N, n) psi0.dims = [dims[0], 1] rho0 = vector_to_operator(psi0) output = mesolve(H, rho0, tlist, c_op_list, [], args, options) for k, t in enumerate(tlist): u[:, n, k] = operator_to_vector( output.states[k]).full(squeeze=True) progress_bar.finished() else: progress_bar.start(N * N) for n in range(N * N): progress_bar.update(n) psi0 = basis(N * N, n) rho0 = Qobj(vec2mat(psi0.full())) output = mesolve(H, rho0, tlist, c_op_list, [], args, options) for k, t in enumerate(tlist): u[:, n, k] = mat2vec(output.states[k].full()).T progress_bar.finished() if len(tlist) == 2: return Qobj(u[:, :, 1], dims=dims) else: return [Qobj(u[:, :, k], dims=dims) for k in range(len(tlist))]
return val**0.5 def funcApiB(t, args=0): val = complex(i(t)) return val**0.5 A =k2**0.5*(b_tensor**2-1./2*alpha_inf_abs*(a_tensor+alpha_inf_abs*I_tensor)) B =k2**0.5*(1./2*alpha_inf_abs*(a_tensor-alpha_inf_abs*I_tensor)) cops = [k1**0.5*a_tensor,k1**0.5*b_tensor] cops.append(k2**0.5*(a_tensor**2-alpha_inf_abs**2*I_tensor)) #stabilization control cat cops.append([A, funcA]) cops.append([B, funcB]) cops.append([A+B, funcApB]) cops.append([A+1j*B, funcApiB]) init_state_C=C_alpha_plus control_on=True init_state_T=C_alpha_plus init_state_tensor=qt.tensor(init_state_C,init_state_T)#initial state tlist = np.linspace(0,T_final, n_t) res_CNOT = qt.mesolve(0*a_tensor, init_state_tensor, tlist, cops,progress_bar=TextProgressBar())#,progress_bar=TextProgressBar()) #np.save(path+'tab_values',res_CNOT.states) # cannot save the data because it is an array of Quantum objects name='density_with_loss_k2_over_%d'%(ratio) qt.qsave(res_CNOT,path+name)
def __init__(self, H=None, state0=None, times=None, c_ops=[], sc_ops=[], e_ops=[], m_ops=None, args=None, ntraj=1, nsubsteps=1, d1=None, d2=None, d2_len=1, dW_factors=None, rhs=None, generate_A_ops=None, generate_noise=None, homogeneous=True, solver=None, method=None, distribution='normal', store_measurement=False, noise=None, normalize=True, options=None, progress_bar=None, map_func=None, map_kwargs=None): if options is None: options = Options() if progress_bar is None: progress_bar = TextProgressBar() self.H = H self.d1 = d1 self.d2 = d2 self.d2_len = d2_len self.dW_factors = dW_factors # if dW_factors else np.ones(d2_len) self.state0 = state0 self.times = times self.c_ops = c_ops self.sc_ops = sc_ops self.e_ops = e_ops #if m_ops is None: # self.m_ops = [[c for _ in range(d2_len)] for c in sc_ops] #else: # self.m_ops = m_ops self.m_ops = m_ops self.ntraj = ntraj self.nsubsteps = nsubsteps self.solver = solver self.method = method self.distribution = distribution self.homogeneous = homogeneous self.rhs = rhs self.options = options self.progress_bar = progress_bar self.store_measurement = store_measurement self.store_states = options.store_states self.noise = noise self.args = args self.normalize = normalize self.generate_noise = generate_noise self.generate_A_ops = generate_A_ops if self.ntraj > 1 and map_func: self.map_func = map_func else: self.map_func = serial_map self.map_kwargs = map_kwargs if map_kwargs is not None else {} #Does any operator depend on time? self.td = False if not isinstance(H, Qobj): self.td = True for ops in c_ops: if not isinstance(ops, Qobj): self.td = True for ops in sc_ops: if not isinstance(ops, Qobj): self.td = True
def parallel_map(task, values, task_args=tuple(), task_kwargs={}, **kwargs): """ Parallel execution of a mapping of `values` to the function `task`. This is functionally equivalent to:: result = [task(value, *task_args, **task_kwargs) for value in values] Parameters ---------- task : a Python function The function that is to be called for each value in ``task_vec``. values : array / list The list or array of values for which the ``task`` function is to be evaluated. task_args : list / dictionary The optional additional argument to the ``task`` function. task_kwargs : list / dictionary The optional additional keyword argument to the ``task`` function. progress_bar : ProgressBar Progress bar class instance for showing progress. Returns -------- result : list The result list contains the value of ``task(value, *task_args, **task_kwargs)`` for each value in ``values``. """ kw = _default_kwargs() if 'num_cpus' in kwargs: kw['num_cpus'] = kwargs['num_cpus'] try: progress_bar = kwargs['progress_bar'] if progress_bar is True: progress_bar = TextProgressBar() except: progress_bar = BaseProgressBar() progress_bar.start(len(values)) nfinished = [0] def _update_progress_bar(x): nfinished[0] += 1 progress_bar.update(nfinished[0]) try: pool = Pool(processes=kw['num_cpus']) async_res = [pool.apply_async(task, (value,) + task_args, task_kwargs, _update_progress_bar) for value in values] while not all([ar.ready() for ar in async_res]): for ar in async_res: ar.wait(timeout=0.1) pool.terminate() pool.join() except KeyboardInterrupt as e: pool.terminate() pool.join() raise e progress_bar.finished() return [ar.get() for ar in async_res]
psi_plus = psi_plus/psi_plus.norm() # even cat /!\ normalisation def dissipative_operator(t, args): return k2**0.5*(a**2-(alpha*np.exp(1j*np.pi*t*delta))**2) H = 0*a # see Wigner : it seems that the pumping is not rotating # if one does not want to display wigners of the time evolution and # only evolution of average quantities, one can put a list of operator # in the following list eops = [parity_a, a, n_a, ] c_ops = [] tlist = np.linspace(0, T_gate, n_t) res = qt.mesolve(dissipative_operator, psi_plus, tlist, c_ops,progress_bar=TextProgressBar()) test_Wigner = compute_Wigner([-4, 4, 51], 10, n_t,-1) test_Wigner.draw_Wigner(res) number_t = [] for ii, t in enumerate(tlist): number_t.append(qt.expect(n_a, res.states[ii])) #fig, ax = plt.subplots(3) #plt.plot(tlist, number_t) #ax[1].plot(tlist, na_t) #ax[2].plot(tlist, np.real(a_t)) #ax[2].plot(tlist, np.imag(a_t), linestyle='dashed')
def bloch_redfield_solve(R, ekets, rho0, tlist, e_ops=[], options=None, progress_bar=None): """ Evolve the ODEs defined by Bloch-Redfield master equation. The Bloch-Redfield tensor can be calculated by the function :func:`bloch_redfield_tensor`. Parameters ---------- R : :class:`qutip.qobj` Bloch-Redfield tensor. ekets : array of :class:`qutip.qobj` Array of kets that make up a basis tranformation for the eigenbasis. rho0 : :class:`qutip.qobj` Initial density matrix. tlist : *list* / *array* List of times for :math:`t`. e_ops : list of :class:`qutip.qobj` / callback function List of operators for which to evaluate expectation values. options : :class:`qutip.Qdeoptions` Options for the ODE solver. Returns ------- output: :class:`qutip.solver` An instance of the class :class:`qutip.solver`, which contains either an *array* of expectation values for the times specified by `tlist`. """ if options is None: options = Options() if options.tidy: R.tidyup() if progress_bar is None: progress_bar = BaseProgressBar() elif progress_bar is True: progress_bar = TextProgressBar() # # check initial state # if isket(rho0): # Got a wave function as initial state: convert to density matrix. rho0 = rho0 * rho0.dag() # # prepare output array # n_tsteps = len(tlist) dt = tlist[1] - tlist[0] result_list = [] # # transform the initial density matrix and the e_ops opterators to the # eigenbasis # rho_eb = rho0.transform(ekets) e_eb_ops = [e.transform(ekets) for e in e_ops] for e_eb in e_eb_ops: if e_eb.isherm: result_list.append(np.zeros(n_tsteps, dtype=float)) else: result_list.append(np.zeros(n_tsteps, dtype=complex)) # # setup integrator # initial_vector = mat2vec(rho_eb.full()) r = scipy.integrate.ode(cy_ode_rhs) r.set_f_params(R.data.data, R.data.indices, R.data.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) r.set_initial_value(initial_vector, tlist[0]) # # start evolution # dt = np.diff(tlist) progress_bar.start(n_tsteps) for t_idx, _ in enumerate(tlist): progress_bar.update(t_idx) if not r.successful(): break rho_eb.data = dense2D_to_fastcsr_fmode(vec2mat(r.y), rho0.shape[0], rho0.shape[1]) # calculate all the expectation values, or output rho_eb if no # expectation value operators are given if e_ops: rho_eb_tmp = Qobj(rho_eb) for m, e in enumerate(e_eb_ops): result_list[m][t_idx] = expect(e, rho_eb_tmp) else: result_list.append(rho_eb.transform(ekets, True)) if t_idx < n_tsteps - 1: r.integrate(r.t + dt[t_idx]) progress_bar.finished() return result_list
class HEOMSolver: """ HEOM solver that supports multiple baths. The baths must be all either bosonic or fermionic baths. Parameters ---------- H_sys : QObj, QobjEvo or a list The system Hamiltonian or Liouvillian specified as either a :obj:`Qobj`, a :obj:`QobjEvo`, or a list of elements that may be converted to a :obj:`ObjEvo`. bath : Bath or list of Bath A :obj:`Bath` containing the exponents of the expansion of the bath correlation funcion and their associated coefficients and coupling operators, or a list of baths. If multiple baths are given, they must all be either fermionic or bosonic baths. max_depth : int The maximum depth of the heirarchy (i.e. the maximum number of bath exponent "excitations" to retain). options : :class:`qutip.solver.Options` Generic solver options. If set to None the default options will be used. progress_bar : None, True or :class:`BaseProgressBar` Optional instance of BaseProgressBar, or a subclass thereof, for showing the progress of the solver. If True, an instance of :class:`TextProgressBar` is used instead. Attributes ---------- ados : :obj:`HierarchyADOs` The description of the hierarchy constructed from the given bath and maximum depth. """ 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 _convert_h_sys(self, H_sys): """ Process input system Hamiltonian, converting and raising as needed. """ if isinstance(H_sys, (Qobj, QobjEvo)): pass elif isinstance(H_sys, list): try: H_sys = QobjEvo(H_sys) except Exception as err: raise ValueError( "Hamiltonian (H_sys) of type list cannot be converted to" " QObjEvo") from err else: raise TypeError( f"Hamiltonian (H_sys) has unsupported type: {type(H_sys)!r}") return H_sys def _combine_bath_exponents(self, bath): """ Combine the exponents for the specified baths. """ if not isinstance(bath, (list, tuple)): exponents = bath.exponents else: exponents = [] for b in bath: exponents.extend(b.exponents) all_bosonic = all(exp.type in (exp.types.R, exp.types.I, exp.types.RI) for exp in exponents) all_fermionic = all(exp.type in (exp.types["+"], exp.types["-"]) for exp in exponents) if not (all_bosonic or all_fermionic): raise ValueError( "Bath exponents are currently restricted to being either" " all bosonic or all fermionic, but a mixture of bath" " exponents was given.") if not all(exp.Q.dims == exponents[0].Q.dims for exp in exponents): raise ValueError( "All bath exponents must have system coupling operators" " with the same dimensions but a mixture of dimensions" " was given.") return exponents def _dsuper_list_td(self, t, y, L_list): """ Auxiliary function for the time-dependent integration. Called every time step. """ L = L_list[0][0] for n in range(1, len(L_list)): L = L + L_list[n][0] * L_list[n][1](t) return L * y def _grad_n(self, L, he_n): """ Get the gradient for the hierarchy ADO at level n. """ vk = self.ados.vk vk_sum = sum(he_n[i] * vk[i] for i in range(len(vk))) op = L - vk_sum * self._sId return op def _grad_prev(self, he_n, k): """ Get the previous gradient. """ if self.ados.exponents[k].type in (BathExponent.types.R, BathExponent.types.I, BathExponent.types.RI): return self._grad_prev_bosonic(he_n, k) elif self.ados.exponents[k].type in (BathExponent.types["+"], BathExponent.types["-"]): return self._grad_prev_fermionic(he_n, k) else: raise ValueError( f"Mode {k} has unsupported type {self.ados.exponents[k].type}") def _grad_prev_bosonic(self, he_n, k): if self.ados.exponents[k].type == BathExponent.types.R: op = (-1j * he_n[k] * self.ados.ck[k]) * (self._s_pre_minus_post_Q[k]) elif self.ados.exponents[k].type == BathExponent.types.I: op = (-1j * he_n[k] * 1j * self.ados.ck[k]) * (self._s_pre_plus_post_Q[k]) elif self.ados.exponents[k].type == BathExponent.types.RI: term1 = (he_n[k] * -1j * self.ados.ck[k]) * (self._s_pre_minus_post_Q[k]) term2 = (he_n[k] * self.ados.ck2[k]) * self._s_pre_plus_post_Q[k] op = term1 + term2 else: raise ValueError(f"Unsupported type {self.ados.exponents[k].type}" f" for exponent {k}") return op def _grad_prev_fermionic(self, he_n, k): ck = self.ados.ck n_excite = sum(he_n) sign1 = (-1)**(n_excite + 1) n_excite_before_m = sum(he_n[:k]) sign2 = (-1)**(n_excite_before_m) sigma_bar_k = k + self.ados.sigma_bar_k_offset[k] if self.ados.exponents[k].type == BathExponent.types["+"]: op = -1j * sign2 * ( (ck[k] * self._spreQdag[k]) - (sign1 * np.conj(ck[sigma_bar_k]) * self._spostQdag[k])) elif self.ados.exponents[k].type == BathExponent.types["-"]: op = -1j * sign2 * ( (ck[k] * self._spreQ[k]) - (sign1 * np.conj(ck[sigma_bar_k]) * self._spostQ[k])) else: raise ValueError(f"Unsupported type {self.ados.exponents[k].type}" f" for exponent {k}") return op def _grad_next(self, he_n, k): """ Get the previous gradient. """ if self.ados.exponents[k].type in (BathExponent.types.R, BathExponent.types.I, BathExponent.types.RI): return self._grad_next_bosonic(he_n, k) elif self.ados.exponents[k].type in (BathExponent.types["+"], BathExponent.types["-"]): return self._grad_next_fermionic(he_n, k) else: raise ValueError( f"Mode {k} has unsupported type {self.ados.exponents[k].type}") def _grad_next_bosonic(self, he_n, k): op = -1j * self._s_pre_minus_post_Q[k] return op def _grad_next_fermionic(self, he_n, k): n_excite = sum(he_n) sign1 = (-1)**(n_excite + 1) n_excite_before_m = sum(he_n[:k]) sign2 = (-1)**(n_excite_before_m) if self.ados.exponents[k].type == BathExponent.types["+"]: if sign1 == -1: op = (-1j * sign2) * self._s_pre_minus_post_Q[k] else: op = (-1j * sign2) * self._s_pre_plus_post_Q[k] elif self.ados.exponents[k].type == BathExponent.types["-"]: if sign1 == -1: op = (-1j * sign2) * self._s_pre_minus_post_Qdag[k] else: op = (-1j * sign2) * self._s_pre_plus_post_Qdag[k] else: raise ValueError(f"Unsupported type {self.ados.exponents[k].type}" f" for exponent {k}") return op def _rhs(self, L): """ Make the RHS for the HEOM. """ ops = _GatherHEOMRHS(self.ados.idx, block=L.shape[0], nhe=self._n_ados) for he_n in self.ados.labels: op = self._grad_n(L, he_n) ops.add_op(he_n, he_n, op) for k in range(len(self.ados.dims)): next_he = self.ados.next(he_n, k) if next_he is not None: op = self._grad_next(he_n, k) ops.add_op(he_n, next_he, op) prev_he = self.ados.prev(he_n, k) if prev_he is not None: op = self._grad_prev(he_n, k) ops.add_op(he_n, prev_he, op) return ops.gather() 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 steady_state(self, use_mkl=True, mkl_max_iter_refine=100, mkl_weighted_matching=False): """ Compute the steady state of the system. Parameters ---------- use_mkl : bool, default=False Whether to use mkl or not. If mkl is not installed or if this is false, use the scipy splu solver instead. mkl_max_iter_refine : int Specifies the the maximum number of iterative refinement steps that the MKL PARDISO solver performs. For a complete description, see iparm(8) in http://cali2.unilim.fr/intel-xe/mkl/mklman/GUID-264E311E-ACED-4D56-AC31-E9D3B11D1CBF.htm. mkl_weighted_matching : bool MKL PARDISO can use a maximum weighted matching algorithm to permute large elements close the diagonal. This strategy adds an additional level of reliability to the factorization methods. For a complete description, see iparm(13) in http://cali2.unilim.fr/intel-xe/mkl/mklman/GUID-264E311E-ACED-4D56-AC31-E9D3B11D1CBF.htm. Returns ------- steady_state : Qobj The steady state density matrix of the system. steady_ados : :class:`HierarchyADOsState` The steady state of the full ADO hierarchy. A particular ADO may be extracted from the full state by calling :meth:`.extract`. """ n = self._sys_shape b_mat = np.zeros(n**2 * self._n_ados, dtype=complex) b_mat[0] = 1.0 L = deepcopy(self.RHSmat) L = L.tolil() L[0, 0:n**2 * self._n_ados] = 0.0 L = L.tocsr() L += sp.csr_matrix( (np.ones(n), (np.zeros(n), [num * (n + 1) for num in range(n)])), shape=(n**2 * self._n_ados, n**2 * self._n_ados)) if mkl_spsolve is not None and use_mkl: L.sort_indices() solution = mkl_spsolve( L, b_mat, perm=None, verbose=False, max_iter_refine=mkl_max_iter_refine, scaling_vectors=True, weighted_matching=mkl_weighted_matching, ) else: L = L.tocsc() solution = spsolve(L, b_mat) data = dense2D_to_fastcsr_fmode(vec2mat(solution[:n**2]), n, n) data = 0.5 * (data + data.H) steady_state = Qobj(data, dims=self._sys_dims) solution = solution.reshape((self._n_ados, n, n)) steady_ados = HierarchyADOsState(steady_state, self.ados, solution) return steady_state, steady_ados def _convert_e_ops(self, e_ops): """ Parse and convert a dictionary or list of e_ops. Returns ------- e_ops, expected : tuple If the input ``e_ops`` was a list or scalar, ``expected`` is a list with one item for each element of the original e_ops. If the input ``e_ops`` was a dictionary, ``expected`` is a dictionary with the same keys. The output ``e_ops`` is always a dictionary. Its keys are the keys or indexes for ``expected`` and its elements are the e_ops functions or callables. """ if isinstance(e_ops, (list, dict)): pass elif e_ops is None: e_ops = [] elif isinstance(e_ops, Qobj): e_ops = [e_ops] elif callable(e_ops): e_ops = [e_ops] else: try: e_ops = list(e_ops) except Exception as err: raise TypeError( "e_ops must be an iterable, Qobj or function") from err if isinstance(e_ops, dict): expected = {k: [] for k in e_ops} else: expected = [[] for _ in e_ops] e_ops = {i: op for i, op in enumerate(e_ops)} if not all( callable(op) or isinstance(op, Qobj) for op in e_ops.values()): raise TypeError("e_ops must only contain Qobj or functions") return e_ops, expected def run(self, rho0, tlist, e_ops=None, ado_init=False, ado_return=False): """ Solve for the time evolution of the system. Parameters ---------- rho0 : Qobj or HierarchyADOsState or numpy.array Initial state (:obj:`~Qobj` density matrix) of the system if ``ado_init`` is ``False``. If ``ado_init`` is ``True``, then ``rho0`` should be an instance of :obj:`~HierarchyADOsState` or a numpy array giving the initial state of all ADOs. Usually the state of the ADOs would be determine from a previous call to ``.run(..., ado_return=True)``. For example, ``result = solver.run(..., ado_return=True)`` could be followed by ``solver.run(result.ado_states[-1], tlist, ado_init=True)``. If a numpy array is passed its shape must be ``(number_of_ados, n, n)`` where ``(n, n)`` is the system shape (i.e. shape of the system density matrix) and the ADOs must be in the same order as in ``.ados.labels``. tlist : list An ordered list of times at which to return the value of the state. e_ops : Qobj / callable / list / dict / None, optional A list or dictionary of operators as `Qobj` and/or callable functions (they can be mixed) or a single operator or callable function. For an operator ``op``, the result will be computed using ``(state * op).tr()`` and the state at each time ``t``. For callable functions, ``f``, the result is computed using ``f(t, ado_state)``. The values are stored in ``expect`` on (see the return section below). ado_init: bool, default False Indicates if initial condition is just the system state, or a numpy array including all ADOs. ado_return: bool, default True Whether to also return as output the full state of all ADOs. Returns ------- :class:`qutip.solver.Result` The results of the simulation run, with the following attributes: * ``times``: the times ``t`` (i.e. the ``tlist``). * ``states``: the system state at each time ``t`` (only available if ``e_ops`` was ``None`` or if the solver option ``store_states`` was set to ``True``). * ``ado_states``: the full ADO state at each time (only available if ``ado_return`` was set to ``True``). Each element is an instance of :class:`HierarchyADOsState`. . The state of a particular ADO may be extracted from ``result.ado_states[i]`` by calling :meth:`.extract`. * ``expect``: the value of each ``e_ops`` at time ``t`` (only available if ``e_ops`` were given). If ``e_ops`` was passed as a dictionary, then ``expect`` will be a dictionary with the same keys as ``e_ops`` and values giving the list of outcomes for the corresponding key. """ e_ops, expected = self._convert_e_ops(e_ops) e_ops_callables = any(not isinstance(op, Qobj) for op in e_ops.values()) n = self._sys_shape rho_shape = (n, n) rho_dims = self._sys_dims hierarchy_shape = (self._n_ados, n, n) output = Result() output.solver = "HEOMSolver" output.times = tlist if e_ops: output.expect = expected if not e_ops or self.options.store_states: output.states = [] if ado_init: if isinstance(rho0, HierarchyADOsState): rho0_he = rho0._ado_state else: rho0_he = rho0 if rho0_he.shape != hierarchy_shape: raise ValueError( f"ADOs passed with ado_init have shape {rho0_he.shape}" f"but the solver hierarchy shape is {hierarchy_shape}") rho0_he = rho0_he.reshape(n**2 * self._n_ados) else: rho0_he = np.zeros([n**2 * self._n_ados], dtype=complex) rho0_he[:n**2] = rho0.full().ravel('F') if ado_return: output.ado_states = [] solver = self._ode solver.set_initial_value(rho0_he, tlist[0]) self.progress_bar.start(len(tlist)) for t_idx, t in enumerate(tlist): self.progress_bar.update(t_idx) if t_idx != 0: solver.integrate(t) if not solver.successful(): raise RuntimeError( "HEOMSolver ODE integration error. Try increasing" " the nsteps given in the HEOMSolver options" " (which increases the allowed substeps in each" " step between times given in tlist).") rho = Qobj( solver.y[:n**2].reshape(rho_shape, order='F'), dims=rho_dims, ) if self.options.store_states: output.states.append(rho) if ado_return or e_ops_callables: ado_state = HierarchyADOsState( rho, self.ados, solver.y.reshape(hierarchy_shape)) if ado_return: output.ado_states.append(ado_state) for e_key, e_op in e_ops.items(): if isinstance(e_op, Qobj): e_result = (rho * e_op).tr() else: e_result = e_op(t, ado_state) output.expect[e_key].append(e_result) self.progress_bar.finished() return output
def mesolve(H, rho0, tlist, c_ops=None, e_ops=None, args=None, options=None, progress_bar=None, _safe_mode=True): """ Master equation evolution of a density matrix for a given Hamiltonian and set of collapse operators, or a Liouvillian. Evolve the state vector or density matrix (`rho0`) using a given Hamiltonian or Liouvillian (`H`) and an optional set of collapse operators (`c_ops`), by integrating the set of ordinary differential equations that define the system. In the absence of collapse operators the system is evolved according to the unitary evolution of the Hamiltonian. The output is either the state vector at arbitrary points in time (`tlist`), or the expectation values of the supplied operators (`e_ops`). If e_ops is a callback function, it is invoked for each time in `tlist` with time and the state as arguments, and the function does not use any return values. If either `H` or the Qobj elements in `c_ops` are superoperators, they will be treated as direct contributions to the total system Liouvillian. This allows the solution of master equations that are not in standard Lindblad form. **Time-dependent operators** For time-dependent problems, `H` and `c_ops` can be a specified in a nested-list format where each element in the list is a list of length 2, containing an operator (:class:`qutip.qobj`) at the first element and where the second element is either a string (*list string format*), a callback function (*list callback format*) that evaluates to the time-dependent coefficient for the corresponding operator, or a NumPy array (*list array format*) which specifies the value of the coefficient to the corresponding operator for each value of t in `tlist`. Alternatively, `H` (but not `c_ops`) can be a callback function with the signature `f(t, args) -> Qobj` (*callback format*), which can return the Hamiltonian or Liouvillian superoperator at any point in time. If the equation cannot be put in standard Lindblad form, then this time-dependence format must be used. *Examples* H = [[H0, 'sin(w*t)'], [H1, 'sin(2*w*t)']] H = [[H0, f0_t], [H1, f1_t]] where f0_t and f1_t are python functions with signature f_t(t, args). H = [[H0, np.sin(w*tlist)], [H1, np.sin(2*w*tlist)]] In the *list string format* and *list callback format*, the string expression and the callback function must evaluate to a real or complex number (coefficient for the corresponding operator). In all cases of time-dependent operators, `args` is a dictionary of parameters that is used when evaluating operators. It is passed to the callback functions as their second argument. **Additional options** Additional options to mesolve can be set via the `options` argument, which should be an instance of :class:`qutip.solver.Options`. Many ODE integration options can be set this way, and the `store_states` and `store_final_state` options can be used to store states even though expectation values are requested via the `e_ops` argument. .. note:: If an element in the list-specification of the Hamiltonian or the list of collapse operators are in superoperator form it will be added to the total Liouvillian of the problem without further transformation. This allows for using mesolve for solving master equations that are not in standard Lindblad form. .. note:: On using callback functions: mesolve transforms all :class:`qutip.Qobj` objects to sparse matrices before handing the problem to the integrator function. In order for your callback function to work correctly, pass all :class:`qutip.Qobj` objects that are used in constructing the Hamiltonian via `args`. mesolve will check for :class:`qutip.Qobj` in `args` and handle the conversion to sparse matrices. All other :class:`qutip.Qobj` objects that are not passed via `args` will be passed on to the integrator in scipy which will raise a NotImplemented exception. Parameters ---------- H : :class:`qutip.Qobj` System Hamiltonian, or a callback function for time-dependent Hamiltonians, or alternatively a system Liouvillian. rho0 : :class:`qutip.Qobj` initial density matrix or state vector (ket). tlist : *list* / *array* list of times for :math:`t`. c_ops : None / list of :class:`qutip.Qobj` single collapse operator, or list of collapse operators, or a list of Liouvillian superoperators. e_ops : None / list / callback function, optional A list of operators as `Qobj` and/or callable functions (can be mixed) or a single callable function. For operators, the result's expect will be computed by :func:`qutip.expect`. For callable functions, they are called as ``f(t, state)`` and return the expectation value. A single callback's expectation value can be any type, but a callback part of a list must return a number as the expectation value. args : None / *dictionary* dictionary of parameters for time-dependent Hamiltonians and collapse operators. options : None / :class:`qutip.Options` with options for the solver. progress_bar : None / BaseProgressBar Optional instance of BaseProgressBar, or a subclass thereof, for showing the progress of the simulation. Returns ------- result: :class:`qutip.Result` An instance of the class :class:`qutip.Result`, which contains either an *array* `result.expect` of expectation values for the times specified by `tlist`, or an *array* `result.states` of state vectors or density matrices corresponding to the times in `tlist` [if `e_ops` is an empty list], or nothing if a callback function was given in place of operators for which to calculate the expectation values. """ if c_ops is None: c_ops = [] if isinstance(c_ops, (Qobj, QobjEvo)): c_ops = [c_ops] if e_ops is None: e_ops = [] if isinstance(e_ops, Qobj): e_ops = [e_ops] if isinstance(e_ops, dict): e_ops_dict = e_ops e_ops = [e for e in e_ops.values()] else: e_ops_dict = None if progress_bar is None: progress_bar = BaseProgressBar() if progress_bar is True: progress_bar = TextProgressBar() # check if rho0 is a superoperator, in which case e_ops argument should # be empty, i.e., e_ops = [] # TODO: e_ops for superoperator if issuper(rho0) and not e_ops == []: raise TypeError("Must have e_ops = [] when initial condition rho0 is" + " a superoperator.") if options is None: options = Options() if options.rhs_reuse and not isinstance(H, SolverSystem): # TODO: deprecate when going to class based solver. if "mesolve" in solver_safe: # print(" ") H = solver_safe["mesolve"] else: pass # raise Exception("Could not find the Hamiltonian to reuse.") if args is None: args = {} check_use_openmp(options) use_mesolve = ((c_ops and len(c_ops) > 0) or (not isket(rho0)) or (isinstance(H, Qobj) and issuper(H)) or (isinstance(H, QobjEvo) and issuper(H.cte)) or (isinstance(H, list) and isinstance(H[0], Qobj) and issuper(H[0])) or (not isinstance(H, (Qobj, QobjEvo)) and callable(H) and not options.rhs_with_state and issuper(H(0., args))) or (not isinstance(H, (Qobj, QobjEvo)) and callable(H) and options.rhs_with_state)) if not use_mesolve: return sesolve(H, rho0, tlist, e_ops=e_ops, args=args, options=options, progress_bar=progress_bar, _safe_mode=_safe_mode) if isket(rho0): rho0 = ket2dm(rho0) if (not (rho0.isoper or rho0.issuper)) or (rho0.dims[0] != rho0.dims[1]): raise ValueError( "input state must be a pure state vector, square density matrix, " "or superoperator") if isinstance(H, SolverSystem): ss = H elif isinstance(H, (list, Qobj, QobjEvo)): ss = _mesolve_QobjEvo(H, c_ops, tlist, args, options) elif callable(H): ss = _mesolve_func_td(H, c_ops, rho0, tlist, args, options) else: raise Exception("Invalid H type") func, ode_args = ss.makefunc(ss, rho0, args, e_ops, options) if _safe_mode: # This is to test safety of the function before starting the loop. v = rho0.full().ravel('F') func(0., v, *ode_args) + v res = _generic_ode_solve(func, ode_args, rho0, tlist, e_ops, options, progress_bar, dims=rho0.dims) res.num_collapse = len(c_ops) if e_ops_dict: res.expect = { e: res.expect[n] for n, e in enumerate(e_ops_dict.keys()) } return res
def parallel_map(task, values, task_args=tuple(), task_kwargs={}, **kwargs): """ Parallel execution of a mapping of `values` to the function `task`. This is functionally equivalent to:: result = [task(value, *task_args, **task_kwargs) for value in values] Parameters ---------- task : a Python function The function that is to be called for each value in ``task_vec``. values : array / list The list or array of values for which the ``task`` function is to be evaluated. task_args : list / dictionary The optional additional argument to the ``task`` function. task_kwargs : list / dictionary The optional additional keyword argument to the ``task`` function. progress_bar : ProgressBar Progress bar class instance for showing progress. Returns -------- result : list The result list contains the value of ``task(value, *task_args, **task_kwargs)`` for each value in ``values``. """ os.environ['QUTIP_IN_PARALLEL'] = 'TRUE' kw = _default_kwargs() if 'num_cpus' in kwargs: kw['num_cpus'] = kwargs['num_cpus'] try: progress_bar = kwargs['progress_bar'] if progress_bar is True: progress_bar = TextProgressBar() except: progress_bar = BaseProgressBar() progress_bar.start(len(values)) nfinished = [0] def _update_progress_bar(x): nfinished[0] += 1 progress_bar.update(nfinished[0]) try: pool = Pool(processes=kw['num_cpus']) async_res = [pool.apply_async(task, (value,) + task_args, task_kwargs, _update_progress_bar) for value in values] while not all([ar.ready() for ar in async_res]): for ar in async_res: ar.wait(timeout=0.1) pool.terminate() pool.join() except KeyboardInterrupt as e: os.environ['QUTIP_IN_PARALLEL'] = 'FALSE' pool.terminate() pool.join() raise e progress_bar.finished() os.environ['QUTIP_IN_PARALLEL'] = 'FALSE' return [ar.get() for ar in async_res]
# in the following list eops = [n_a_tensor, n_b_tensor, ] n_t = 101 tlist = np.linspace(0, Tp, n_t) # return the average value evolution of given operators ##print(H_pcc.shape) #print(H_inter.shape) #print(storage_0.shape) #print(PCC_0.shape) #print(qt.tensor(storage_0,PCC_0).shape) res = qt.mesolve([chi0*H_inter+H_storage+H_pcc,], qt.tensor(storage_0,PCC_0), tlist,cops,progress_bar=TextProgressBar()) print("solved") print("Wigner storage") test_Wigner1 = compute_Wigner([-4, 4, 51], 10, n_t, 0) test_Wigner1.draw_Wigner(res) print("Wigner PCC") test_Wigner2 = compute_Wigner([-4, 4, 51], 10, n_t, 1) test_Wigner2.draw_Wigner(res) proba_PCC = [] proba_storage = [] fidelity_PCC = [] fidelity_storage = [] for ii, t in enumerate(tlist): rho_PCC = res.states[ii].ptrace(1)
def mesolve(H, rho0, tlist, c_ops=[], e_ops=[], args={}, options=None, progress_bar=None, _safe_mode=True): """ Master equation evolution of a density matrix for a given Hamiltonian and set of collapse operators, or a Liouvillian. Evolve the state vector or density matrix (`rho0`) using a given Hamiltonian (`H`) and an [optional] set of collapse operators (`c_ops`), by integrating the set of ordinary differential equations that define the system. In the absence of collapse operators the system is evolved according to the unitary evolution of the Hamiltonian. The output is either the state vector at arbitrary points in time (`tlist`), or the expectation values of the supplied operators (`e_ops`). If e_ops is a callback function, it is invoked for each time in `tlist` with time and the state as arguments, and the function does not use any return values. If either `H` or the Qobj elements in `c_ops` are superoperators, they will be treated as direct contributions to the total system Liouvillian. This allows to solve master equations that are not on standard Lindblad form by passing a custom Liouvillian in place of either the `H` or `c_ops` elements. **Time-dependent operators** For time-dependent problems, `H` and `c_ops` can be callback functions that takes two arguments, time and `args`, and returns the Hamiltonian or Liouvillian for the system at that point in time (*callback format*). Alternatively, `H` and `c_ops` can be a specified in a nested-list format where each element in the list is a list of length 2, containing an operator (:class:`qutip.qobj`) at the first element and where the second element is either a string (*list string format*), a callback function (*list callback format*) that evaluates to the time-dependent coefficient for the corresponding operator, or a NumPy array (*list array format*) which specifies the value of the coefficient to the corresponding operator for each value of t in tlist. *Examples* H = [[H0, 'sin(w*t)'], [H1, 'sin(2*w*t)']] H = [[H0, f0_t], [H1, f1_t]] where f0_t and f1_t are python functions with signature f_t(t, args). H = [[H0, np.sin(w*tlist)], [H1, np.sin(2*w*tlist)]] In the *list string format* and *list callback format*, the string expression and the callback function must evaluate to a real or complex number (coefficient for the corresponding operator). In all cases of time-dependent operators, `args` is a dictionary of parameters that is used when evaluating operators. It is passed to the callback functions as second argument. **Additional options** Additional options to mesolve can be set via the `options` argument, which should be an instance of :class:`qutip.solver.Options`. Many ODE integration options can be set this way, and the `store_states` and `store_final_state` options can be used to store states even though expectation values are requested via the `e_ops` argument. .. note:: If an element in the list-specification of the Hamiltonian or the list of collapse operators are in superoperator form it will be added to the total Liouvillian of the problem with out further transformation. This allows for using mesolve for solving master equations that are not on standard Lindblad form. .. note:: On using callback function: mesolve transforms all :class:`qutip.qobj` objects to sparse matrices before handing the problem to the integrator function. In order for your callback function to work correctly, pass all :class:`qutip.qobj` objects that are used in constructing the Hamiltonian via args. mesolve will check for :class:`qutip.qobj` in `args` and handle the conversion to sparse matrices. All other :class:`qutip.qobj` objects that are not passed via `args` will be passed on to the integrator in scipy which will raise an NotImplemented exception. Parameters ---------- H : :class:`qutip.Qobj` System Hamiltonian, or a callback function for time-dependent Hamiltonians, or alternatively a system Liouvillian. rho0 : :class:`qutip.Qobj` initial density matrix or state vector (ket). tlist : *list* / *array* list of times for :math:`t`. c_ops : list of :class:`qutip.Qobj` single collapse operator, or list of collapse operators, or a list of Liouvillian superoperators. e_ops : list of :class:`qutip.Qobj` / callback function single single operator or list of operators for which to evaluate expectation values. args : *dictionary* dictionary of parameters for time-dependent Hamiltonians and collapse operators. options : :class:`qutip.Options` with options for the solver. progress_bar : BaseProgressBar Optional instance of BaseProgressBar, or a subclass thereof, for showing the progress of the simulation. Returns ------- result: :class:`qutip.Result` An instance of the class :class:`qutip.Result`, which contains either an *array* `result.expect` of expectation values for the times specified by `tlist`, or an *array* `result.states` of state vectors or density matrices corresponding to the times in `tlist` [if `e_ops` is an empty list], or nothing if a callback function was given in place of operators for which to calculate the expectation values. """ # check whether c_ops or e_ops is is a single operator # if so convert it to a list containing only that operator if isinstance(c_ops, Qobj): c_ops = [c_ops] if isinstance(e_ops, Qobj): e_ops = [e_ops] if isinstance(e_ops, dict): e_ops_dict = e_ops e_ops = [e for e in e_ops.values()] else: e_ops_dict = None if _safe_mode: _solver_safety_check(H, rho0, c_ops, e_ops, args) if progress_bar is None: progress_bar = BaseProgressBar() elif progress_bar is True: progress_bar = TextProgressBar() # check if rho0 is a superoperator, in which case e_ops argument should # be empty, i.e., e_ops = [] if issuper(rho0) and not e_ops == []: raise TypeError("Must have e_ops = [] when initial condition rho0 is" + " a superoperator.") # convert array based time-dependence to string format H, c_ops, args = _td_wrap_array_str(H, c_ops, args, tlist) # check for type (if any) of time-dependent inputs _, n_func, n_str = _td_format_check(H, c_ops) if options is None: options = Options() if (not options.rhs_reuse) or (not config.tdfunc): # reset config collapse and time-dependence flags to default values config.reset() #check if should use OPENMP check_use_openmp(options) res = None # # dispatch the appropriate solver # if ((c_ops and len(c_ops) > 0) or (not isket(rho0)) or (isinstance(H, Qobj) and issuper(H)) or (isinstance(H, list) and isinstance(H[0], Qobj) and issuper(H[0]))): # # we have collapse operators, or rho0 is not a ket, # or H is a Liouvillian # # # find out if we are dealing with all-constant hamiltonian and # collapse operators or if we have at least one time-dependent # operator. Then delegate to appropriate solver... # if isinstance(H, Qobj): # constant hamiltonian if n_func == 0 and n_str == 0: # constant collapse operators res = _mesolve_const(H, rho0, tlist, c_ops, e_ops, args, options, progress_bar) elif n_str > 0: # constant hamiltonian but time-dependent collapse # operators in list string format res = _mesolve_list_str_td([H], rho0, tlist, c_ops, e_ops, args, options, progress_bar) elif n_func > 0: # constant hamiltonian but time-dependent collapse # operators in list function format res = _mesolve_list_func_td([H], rho0, tlist, c_ops, e_ops, args, options, progress_bar) elif isinstance( H, (types.FunctionType, types.BuiltinFunctionType, partial)): # function-callback style time-dependence: must have constant # collapse operators if n_str > 0: # or n_func > 0: raise TypeError("Incorrect format: function-format " + "Hamiltonian cannot be mixed with " + "time-dependent collapse operators.") else: res = _mesolve_func_td(H, rho0, tlist, c_ops, e_ops, args, options, progress_bar) elif isinstance(H, list): # determine if we are dealing with list of [Qobj, string] or # [Qobj, function] style time-dependencies (for pure python and # cython, respectively) if n_func > 0: res = _mesolve_list_func_td(H, rho0, tlist, c_ops, e_ops, args, options, progress_bar) else: res = _mesolve_list_str_td(H, rho0, tlist, c_ops, e_ops, args, options, progress_bar) else: raise TypeError("Incorrect specification of Hamiltonian " + "or collapse operators.") else: # # no collapse operators: unitary dynamics # if n_func > 0: res = _sesolve_list_func_td(H, rho0, tlist, e_ops, args, options, progress_bar) elif n_str > 0: res = _sesolve_list_str_td(H, rho0, tlist, e_ops, args, options, progress_bar) elif isinstance( H, (types.FunctionType, types.BuiltinFunctionType, partial)): res = _sesolve_func_td(H, rho0, tlist, e_ops, args, options, progress_bar) else: res = _sesolve_const(H, rho0, tlist, e_ops, args, options, progress_bar) if e_ops_dict: res.expect = { e: res.expect[n] for n, e in enumerate(e_ops_dict.keys()) } return res