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 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 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
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