def _mesolve_QobjEvo(H, c_ops, tlist, args, opt): """ Prepare the system for the solver, H can be an QobjEvo. """ H_td = QobjEvo(H, args, tlist=tlist) if not issuper(H_td.cte): L_td = liouvillian(H_td) else: L_td = H_td for op in c_ops: op_td = QobjEvo(op, args, tlist=tlist) if not issuper(op_td.cte): op_td = lindblad_dissipator(op_td) L_td += op_td if opt.rhs_with_state: L_td._check_old_with_state() nthread = opt.openmp_threads if opt.use_openmp else 0 L_td.compile(omp=nthread) ss = SolverSystem() ss.H = L_td ss.makefunc = _qobjevo_set solver_safe["mesolve"] = ss return ss
def _mesolve_const(H, rho0, tlist, c_op_list, e_ops, args, opt, progress_bar): """ Evolve the density matrix using an ODE solver, for constant hamiltonian and collapse operators. """ if debug: print(inspect.stack()[0][3]) # # check initial state # if isket(rho0): # if initial state is a ket and no collapse operator where given, # fall back on the unitary schrodinger equation solver if len(c_op_list) == 0 and isoper(H): return _sesolve_const(H, rho0, tlist, e_ops, args, opt, progress_bar) # Got a wave function as initial state: convert to density matrix. rho0 = ket2dm(rho0) # # construct liouvillian # if opt.tidy: H = H.tidyup(opt.atol) L = liouvillian(H, c_op_list) # # setup integrator # initial_vector = mat2vec(rho0.full()).ravel('F') if issuper(rho0): r = scipy.integrate.ode(_ode_super_func) r.set_f_params(L.data) else: if opt.use_openmp and L.data.nnz >= qset.openmp_thresh: r = scipy.integrate.ode(cy_ode_rhs_openmp) r.set_f_params(L.data.data, L.data.indices, L.data.indptr, opt.openmp_threads) else: r = scipy.integrate.ode(cy_ode_rhs) r.set_f_params(L.data.data, L.data.indices, L.data.indptr) # r = scipy.integrate.ode(_ode_rho_test) # r.set_f_params(L.data) r.set_integrator('zvode', method=opt.method, order=opt.order, atol=opt.atol, rtol=opt.rtol, nsteps=opt.nsteps, first_step=opt.first_step, min_step=opt.min_step, max_step=opt.max_step) r.set_initial_value(initial_vector, tlist[0]) # # call generic ODE code # return _generic_ode_solve(r, rho0, tlist, e_ops, opt, progress_bar)
def _mesolve_func_td(L_func, c_op_list, rho0, tlist, args, opt): """ Evolve the density matrix using an ODE solver with time dependent Hamiltonian. """ if type(c_op_list) is list: c_ops = [] for op in c_op_list: op_td = QobjEvo(op, args, tlist=tlist, copy=False) if not issuper(op_td.cte): c_ops += [lindblad_dissipator(op_td)] else: c_ops += [op_td] if c_op_list: c_ops_ = [sum(c_ops)] else: c_ops_ = [] elif callable(c_op_list): c_ops_ = c_op_list if opt.rhs_with_state: state0 = rho0.full().ravel("F") obj = L_func(0., state0, args) if not issuper(obj): L_func = _LiouvillianFromFunc(L_func, c_ops_).H2L_with_state else: L_func = _LiouvillianFromFunc(L_func, c_ops_).L_with_state else: obj = L_func(0., args) if callable(c_ops_): if not issuper(obj): L_func = _LiouvillianFromFunc(L_func, c_ops_).H2L_c else: L_func = _LiouvillianFromFunc(L_func, c_ops_).L_c else: if not issuper(obj): L_func = _LiouvillianFromFunc(L_func, c_ops_).H2L else: L_func = _LiouvillianFromFunc(L_func, c_ops_).L ss = SolverSystem() ss.L = L_func ss.makefunc = _Lfunc_set solver_safe["mesolve"] = ss return ss
def _Lfunc_set(HS, rho0, args, e_ops, opt): """ From the system, get the ode function and args """ L_func = HS.L if issuper(rho0): func = _ode_super_func_td else: func = _ode_rho_func_td return func, (L_func, args)
def _mesolve_const(H, rho0, tlist, c_op_list, expt_ops, args, opt): """! Evolve the density matrix using an ODE solver, for constant hamiltonian and collapse operators. """ if debug: print(inspect.stack()[0][3]) # # check initial state # if isket(rho0): # if initial state is a ket and no collapse operator where given, # fallback on the unitary schrodinger equation solver if len(c_op_list) == 0 and isoper(H): return _sesolve_const(H, rho0, tlist, expt_ops, args, opt) # Got a wave function as initial state: convert to density matrix. rho0 = rho0 * rho0.dag() # # construct liouvillian # if opt.tidy: H = H.tidyup(opt.atol) if issuper(H): L = H + liouvillian(None, c_op_list) else: L = liouvillian(H, c_op_list) # # setup integrator # initial_vector = mat2vec(rho0.full()) r = scipy.integrate.ode(cy_ode_rhs) r.set_f_params(L.data.data, L.data.indices, L.data.indptr) r.set_integrator('zvode', method=opt.method, order=opt.order, atol=opt.atol, rtol=opt.rtol, nsteps=opt.nsteps, first_step=opt.first_step, min_step=opt.min_step, max_step=opt.max_step) r.set_initial_value(initial_vector, tlist[0]) # # call generic ODE code # return _generic_ode_solve(r, rho0, tlist, expt_ops, opt, vec2mat)
def _mesolve_func_td(L_func, c_op_list, rho0, tlist, args, opt): """ Evolve the density matrix using an ODE solver with time dependent Hamiltonian. """ c_ops = [] for op in c_op_list: td = QobjEvo(op, args, tlist=tlist, copy=False) c_ops.append(td if td.cte.issuper else lindblad_dissipator(td)) c_ops_ = [sum(c_ops)] if c_op_list else [] L_api = _LiouvillianFromFunc(L_func, c_ops_, rho0.dims) if opt.rhs_with_state: obj = L_func(0., rho0.full().ravel("F"), args) L_func = L_api.L_with_state if issuper(obj) else L_api.H2L_with_state else: obj = L_func(0., args) L_func = L_api.L if issuper(obj) else L_api.H2L ss = SolverSystem() ss.L = L_func ss.makefunc = _Lfunc_set solver_safe["mesolve"] = ss return ss
def _steadystate_power(L, maxiter=10, tol=1e-6, itertol=1e-5, use_umfpack=True, verbose=False): """ Inverse power method for steady state solving. """ if verbose: print('Starting iterative power method Solver...') use_solver(assumeSortedIndices=True, useUmfpack=use_umfpack) rhoss = Qobj() sflag = issuper(L) if sflag: rhoss.dims = L.dims[0] rhoss.shape = [prod(rhoss.dims[0]), prod(rhoss.dims[1])] else: rhoss.dims = [L.dims[0], 1] rhoss.shape = [prod(rhoss.dims[0]), 1] n = prod(rhoss.shape) L = L.data.tocsc() - (tol**2) * sp.eye(n, n, format='csc') L.sort_indices() v = mat2vec(rand_dm(rhoss.shape[0], 0.5 / rhoss.shape[0] + 0.5).full()) if verbose: start_time = time.time() it = 0 while (la.norm(L * v, np.inf) > tol) and (it < maxiter): v = spsolve(L, v, use_umfpack=use_umfpack) v = v / la.norm(v, np.inf) it += 1 if it >= maxiter: raise Exception('Failed to find steady state after ' + str(maxiter) + ' iterations') # normalise according to type of problem if sflag: trow = sp.eye(rhoss.shape[0], rhoss.shape[0], format='coo') trow = sp_reshape(trow, (1, n)) data = v / sum(trow.dot(v)) else: data = data / la.norm(v) data = sp.csr_matrix(vec2mat(data)) rhoss.data = 0.5 * (data + data.conj().T) rhoss.isherm = True if verbose: print('Power solver time: ', time.time() - start_time) if qset.auto_tidyup: return rhoss.tidyup() else: return rhoss
def _qobjevo_set(HS, rho0, args, opt): """ From the system, get the ode function and args """ H_td = HS.H H_td.arguments(args) if issuper(rho0): func = H_td.compiled_qobjevo.ode_mul_mat_f_vec elif rho0.isket or rho0.isoper: func = H_td.compiled_qobjevo.mul_vec else: raise TypeError("The unitary solver requires rho0 to be" " a ket or dm as initial state" " or a super operator as initial state.") return func, ()
def _steadystate_setup(A, c_op_list): """Build Liouvillian (if necessary) and check input. """ if isoper(A): if len(c_op_list) > 0: return liouvillian(A, c_op_list) raise TypeError('Cannot calculate the steady state for a ' + 'non-dissipative system ' + '(no collapse operators given)') elif issuper(A): return A else: raise TypeError('Solving for steady states requires ' + 'Liouvillian (super) operators')
def _mesolve_QobjEvo(H, c_ops, tlist, args, opt): """ Prepare the system for the solver, H can be an QobjEvo. """ H_td = QobjEvo(H, args, tlist=tlist) if not issuper(H_td.cte): L_td = liouvillian(H_td) else: L_td = H_td for op in c_ops: # We want to avoid passing tlist where it isn't necessary, to allow a # Hamiltonian/Liouvillian which already _has_ time-dependence not equal # to the mesolve evaluation times to be used in conjunction with # time-independent c_ops. If we _always_ pass it, it may appear to # QobjEvo that there is a tlist mismatch, even though it is not used. if isinstance(op, Qobj): op_td = QobjEvo(op) elif isinstance(op, QobjEvo): op_td = QobjEvo(op, args) else: op_td = QobjEvo(op, args, tlist=tlist) if not issuper(op_td.cte): op_td = lindblad_dissipator(op_td) L_td += op_td if opt.rhs_with_state: L_td._check_old_with_state() nthread = opt.openmp_threads if opt.use_openmp else 0 L_td.compile(omp=nthread) ss = SolverSystem() ss.H = L_td ss.makefunc = _qobjevo_set solver_safe["mesolve"] = ss return ss
def _qobjevo_set(HS, rho0, args, e_ops, opt): """ From the system, get the ode function and args """ H_td = HS.H H_td.solver_set_args(args, rho0, e_ops) if issuper(rho0): func = H_td.compiled_qobjevo.ode_mul_mat_f_vec elif rho0.isket or rho0.isoper: func = H_td.compiled_qobjevo.mul_vec else: # Should be caught earlier in mesolve. raise ValueError("rho0 must be a ket, density matrix or superoperator") _test_liouvillian_dimensions(H_td.cte.dims, rho0.dims) return func, ()
def _steadystate_power(L, maxiter=10, tol=1e-6, itertol=1e-5, verbose=False): """ Inverse power method for steady state solving. """ if verbose: print('Starting iterative power method Solver...') use_solver(assumeSortedIndices=True) rhoss = Qobj() sflag = issuper(L) if sflag: rhoss.dims = L.dims[0] rhoss.shape = [prod(rhoss.dims[0]), prod(rhoss.dims[1])] else: rhoss.dims = [L.dims[0], 1] rhoss.shape = [prod(rhoss.dims[0]), 1] n = prod(rhoss.shape) L = L.data.tocsc() - (tol ** 2) * sp.eye(n, n, format='csc') L.sort_indices() v = mat2vec(rand_dm(rhoss.shape[0], 0.5 / rhoss.shape[0] + 0.5).full()) if verbose: start_time = time.time() it = 0 while (la.norm(L * v, np.inf) > tol) and (it < maxiter): v = spsolve(L, v) v = v / la.norm(v, np.inf) it += 1 if it >= maxiter: raise Exception('Failed to find steady state after ' + str(maxiter) + ' iterations') # normalise according to type of problem if sflag: trow = sp.eye(rhoss.shape[0], rhoss.shape[0], format='coo') trow = sp_reshape(trow, (1, n)) data = v / sum(trow.dot(v)) else: data = data / la.norm(v) data = sp.csr_matrix(vec2mat(data)) rhoss.data = 0.5 * (data + data.conj().T) rhoss.isherm = True if verbose: print('Power solver time: ', time.time() - start_time) if qset.auto_tidyup: return rhoss.tidyup() else: return rhoss
def _steadystate_power(L, ss_args): """ Inverse power method for steady state solving. """ if settings.debug: print('Starting iterative power method Solver...') tol=ss_args['tol'] maxiter=ss_args['maxiter'] use_solver(assumeSortedIndices=True) rhoss = Qobj() sflag = issuper(L) if sflag: rhoss.dims = L.dims[0] else: rhoss.dims = [L.dims[0], 1] n = prod(rhoss.shape) L = L.data.tocsc() - (tol ** 2) * sp.eye(n, n, format='csc') L.sort_indices() v = mat2vec(rand_dm(rhoss.shape[0], 0.5 / rhoss.shape[0] + 0.5).full()) it = 0 while (la.norm(L * v, np.inf) > tol) and (it < maxiter): v = spsolve(L, v) v = v / la.norm(v, np.inf) it += 1 if it >= maxiter: raise Exception('Failed to find steady state after ' + str(maxiter) + ' iterations') # normalise according to type of problem if sflag: trow = sp.eye(rhoss.shape[0], rhoss.shape[0], format='coo') trow = sp_reshape(trow, (1, n)) data = v / sum(trow.dot(v)) else: data = data / la.norm(v) data = sp.csr_matrix(vec2mat(data)) rhoss.data = 0.5 * (data + data.conj().T) rhoss.isherm = True return rhoss
def _spectrum_pi(H, wlist, c_ops, a_op, b_op, use_pinv=False): """ Internal function for calculating the spectrum of the correlation function :math:`\left<A(\\tau)B(0)\\right>`. """ L = H if issuper(H) else liouvillian(H, c_ops) tr_mat = tensor([qeye(n) for n in L.dims[0][0]]) N = np.prod(L.dims[0][0]) A = L.full() b = spre(b_op).full() a = spre(a_op).full() tr_vec = np.transpose(mat2vec(tr_mat.full())) rho_ss = steadystate(L) rho = np.transpose(mat2vec(rho_ss.full())) I = np.identity(N * N) P = np.kron(np.transpose(rho), tr_vec) Q = I - P spectrum = np.zeros(len(wlist)) for idx, w in enumerate(wlist): if use_pinv: MMR = np.linalg.pinv(-1.0j * w * I + A) else: MMR = np.dot(Q, np.linalg.solve(-1.0j * w * I + A, Q)) s = np.dot(tr_vec, np.dot(a, np.dot(MMR, np.dot(b, np.transpose(rho))))) spectrum[idx] = -2 * np.real(s[0, 0]) return spectrum
def mesolve(H, rho0, tlist, c_ops, e_ops, args={}, options=None, progress_bar=BaseProgressBar()): """ Master equation evolution of a density matrix for a given Hamiltonian. Evolve the state vector or density matrix (`rho0`) using a given Hamiltonian (`H`) and an [optional] set of collapse operators (`c_op_list`), by integrating the set of ordinary differential equations that define the system. In the absense of collase 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. **Time-dependent operators** For problems with time-dependent problems `H` and `c_ops` can be callback functions that takes two arguments, time and `args`, and returns the Hamiltonian or Liuovillian 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*) or a callback function (*list callback format*) that evaluates to the time-dependent coefficient for the corresponding operator. *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). 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 .. note:: If an element in the list-specification of the Hamiltonian or the list of collapse operators are in super-operator for 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. odesolve 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 to scipy who will raise an NotImplemented exception. Parameters ---------- H : :class:`qutip.qobj` system Hamiltonian, or a callback function for time-dependent Hamiltonians. 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. 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.Odeoptions` with options for the ODE solver. Returns ------- output: :class:`qutip.odedata` An instance of the class :class:`qutip.odedata`, which contains either an *array* of expectation values for the times specified by `tlist`, or an *array* or 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 inplace 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 # check for type (if any) of time-dependent inputs n_const, n_func, n_str = _ode_checks(H, c_ops) if options is None: options = Odeoptions() if (not options.rhs_reuse) or (not odeconfig.tdfunc): # reset odeconfig collapse and time-dependence flags to default values odeconfig.reset() 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 # # # 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)): # old 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
def steadystate( A, c_op_list=[], method='direct', sparse=True, use_umfpack=True, maxiter=5000, tol=1e-5, use_precond=True, perm_method='AUTO', drop_tol=1e-1, diag_pivot_thresh=0.33, verbose=False): """Calculates the steady state for the evolution subject to the supplied Hamiltonian or Liouvillian operator and (if given a Hamiltonian) a list of collapse operators. If the user passes a Hamiltonian then it, along with the list of collapse operators, will be converted into a Liouvillian operator in Lindblad form. Parameters ---------- A : qobj A Hamiltonian or Liouvillian operator. c_op_list : list A list of collapse operators. method : str {'direct', 'iterative', 'iterative-bicg', 'lu', 'svd', 'power'} Method for solving the underlying linear equation. Direct solver 'direct' (default), iterative LGMRES method 'iterative', iterative method BICG 'iterative-bicg', LU decomposition 'lu', SVD 'svd' (dense), or inverse-power method 'power'. sparse : bool default=True Solve for the steady state using sparse algorithms. If set to False, the underlying Liouvillian operator will be converted into a dense matrix. Use only for 'smaller' systems. use_umfpack : bool optional, default = True Use the UMFpack backend for the direct solver 'direct' or Power method 'power. If 'False', the solver uses the SuperLU backend. This option does not affect the other methods. Used only when sparse=True. maxiter : int optional Maximum number of iterations to perform if using an iterative method such as 'iterative' (default=5000), or 'power' (default=10). tol : float optional, default=1e-5 Tolerance used for terminating solver solution when using iterative solvers. use_precond : bool optional, default = True ITERATIVE ONLY. Use an incomplete sparse LU decomposition as a preconditioner for the 'iterative' LGMRES and BICG solvers. Speeds up convergence time by orders of magnitude in many cases. perm_method : str {'AUTO', 'AUTO-BREAK', 'COLAMD', 'MMD_ATA', 'NATURAL'} ITERATIVE ONLY. Sets the method for column ordering the incomplete LU preconditioner used by the 'iterative' method. When set to 'AUTO' (default), the solver will attempt to precondition the system using 'COLAMD'. If this fails, the solver will use no preconditioner. Using 'AUTO-BREAK' will cause the solver to issue an exception and stop if the 'COLAMD' method fails. drop_tol : float default=1e-1 ITERATIVE ONLY. Sets the threshold for the magnitude of preconditioner elements that should be dropped. diag_pivot_thresh : float default=0.33 ITERATIVE ONLY. Sets the threshold for which diagonal elements are considered acceptable pivot points when using a preconditioner. verbose : bool default=False Flag for printing out detailed information on the steady state solver. Returns ------- dm : qobj Steady state density matrix. Notes ----- The SVD method works only for dense operators (i.e. small systems). Setting use_umfpack=True (default) may result in 'out of memory' errors if your system size becomes to large. """ n_op = len(c_op_list) if isoper(A): if n_op == 0: raise TypeError('Cannot calculate the steady state for a ' + 'non-dissipative system ' + '(no collapse operators given)') else: A = liouvillian_fast(A, c_op_list) if not issuper(A): raise TypeError('Solving for steady states requires ' + 'Liouvillian (super) operators') if method == 'direct': if sparse: return _steadystate_direct_sparse(A, use_umfpack=use_umfpack, verbose=verbose) else: return _steadystate_direct_dense(A, verbose=verbose) elif method == 'iterative': return _steadystate_iterative(A, tol=tol, use_precond=use_precond, maxiter=maxiter, perm_method=perm_method, drop_tol=drop_tol, verbose=verbose, diag_pivot_thresh=diag_pivot_thresh) elif method == 'iterative-bicg': return _steadystate_iterative_bicg(A, tol=tol, use_precond=use_precond, maxiter=maxiter, perm_method=perm_method, drop_tol=drop_tol, verbose=verbose, diag_pivot_thresh=diag_pivot_thresh) elif method == 'lu': return _steadystate_lu(A, verbose=verbose) elif method == 'svd': return _steadystate_svd_dense(A, atol=1e-12, rtol=0, all_steadystates=False, verbose=verbose) elif method == 'power': return _steadystate_power(A, maxiter=10, tol=tol, itertol=tol, use_umfpack=use_umfpack, verbose=verbose) else: raise ValueError('Invalid method argument for steadystate.')
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
def _mesolve_list_str_td(H_list, rho0, tlist, c_list, e_ops, args, opt, progress_bar): """ Internal function for solving the master equation. See mesolve for usage. """ if debug: print(inspect.stack()[0][3]) # # check initial state: must be a density matrix # if isket(rho0): rho0 = rho0 * rho0.dag() # # construct liouvillian # Lconst = 0 Ldata = [] Linds = [] Lptrs = [] Lcoeff = [] Lobj = [] me_cops_coeff = [] me_cops_obj = [] me_cops_obj_flags = [] # loop over all hamiltonian terms, convert to superoperator form and # add the data of sparse matrix representation to n_not_const_terms = 0 for h_spec in H_list: if isinstance(h_spec, Qobj): h = h_spec if isoper(h): Lconst += -1j * (spre(h) - spost(h)) elif issuper(h): Lconst += h else: raise TypeError("Incorrect specification of time-dependent " + "Hamiltonian (expected operator or " + "superoperator)") elif isinstance(h_spec, list): n_not_const_terms +=1 h = h_spec[0] h_coeff = h_spec[1] if isoper(h): L = -1j * (spre(h) - spost(h)) elif issuper(h): L = h else: raise TypeError("Incorrect specification of time-dependent " + "Hamiltonian (expected operator or " + "superoperator)") Ldata.append(L.data.data) Linds.append(L.data.indices) Lptrs.append(L.data.indptr) if isinstance(h_coeff, Cubic_Spline): Lobj.append(h_coeff.coeffs) Lcoeff.append(h_coeff) else: raise TypeError("Incorrect specification of time-dependent " + "Hamiltonian (expected string format)") # loop over all collapse operators for c_spec in c_list: if isinstance(c_spec, Qobj): c = c_spec if isoper(c): cdc = c.dag() * c Lconst += spre(c) * spost(c.dag()) - 0.5 * spre(cdc) \ - 0.5 * spost(cdc) elif issuper(c): Lconst += c else: raise TypeError("Incorrect specification of time-dependent " + "Liouvillian (expected operator or " + "superoperator)") elif isinstance(c_spec, list): n_not_const_terms +=1 c = c_spec[0] c_coeff = c_spec[1] if isoper(c): cdc = c.dag() * c L = spre(c) * spost(c.dag()) - 0.5 * spre(cdc) \ - 0.5 * spost(cdc) if isinstance(c_coeff, Cubic_Spline): me_cops_obj.append(c_coeff.coeffs) me_cops_obj_flags.append(n_not_const_terms) me_cops_coeff.append(c_coeff) else: c_coeff = "(" + c_coeff + ")**2" Lcoeff.append(c_coeff) elif issuper(c): L = c if isinstance(c_coeff, Cubic_Spline): me_cops_obj.append(c_coeff.coeffs) me_cops_obj_flags.append(-n_not_const_terms) me_cops_coeff.append(c_coeff) else: Lcoeff.append(c_coeff) else: raise TypeError("Incorrect specification of time-dependent " + "Liouvillian (expected operator or " + "superoperator)") Ldata.append(L.data.data) Linds.append(L.data.indices) Lptrs.append(L.data.indptr) #Lcoeff.append(c_coeff) else: raise TypeError("Incorrect specification of time-dependent " + "collapse operators (expected string format)") #prepend the constant part of the liouvillian if Lconst != 0: Ldata = [Lconst.data.data]+Ldata Linds = [Lconst.data.indices]+Linds Lptrs = [Lconst.data.indptr]+Lptrs Lcoeff = ["1.0"]+Lcoeff else: me_cops_obj_flags = [kk-1 for kk in me_cops_obj_flags] # the total number of liouvillian terms (hamiltonian terms + # collapse operators) n_L_terms = len(Ldata) n_td_cops = len(me_cops_obj) # Check which components should use OPENMP omp_components = None if qset.has_openmp: if opt.use_openmp: omp_components = openmp_components(Lptrs) # # setup ode args string: we expand the list Ldata, Linds and Lptrs into # and explicit list of parameters # string_list = [] for k in range(n_L_terms): string_list.append("Ldata[%d], Linds[%d], Lptrs[%d]" % (k, k, k)) # Add H object terms to ode args string for k in range(len(Lobj)): string_list.append("Lobj[%d]" % k) # Add cop object terms to end of ode args string for k in range(len(me_cops_obj)): string_list.append("me_cops_obj[%d]" % k) for name, value in args.items(): if isinstance(value, np.ndarray): string_list.append(name) else: string_list.append(str(value)) parameter_string = ",".join(string_list) # # generate and compile new cython code if necessary # if not opt.rhs_reuse or config.tdfunc is None: if opt.rhs_filename is None: config.tdname = "rhs" + str(os.getpid()) + str(config.cgen_num) else: config.tdname = opt.rhs_filename cgen = Codegen(h_terms=len(Lcoeff), h_tdterms=Lcoeff, c_td_splines=me_cops_coeff, c_td_spline_flags=me_cops_obj_flags, args=args, config=config, use_openmp=opt.use_openmp, omp_components=omp_components, omp_threads=opt.openmp_threads) cgen.generate(config.tdname + ".pyx") code = compile('from ' + config.tdname + ' import cy_td_ode_rhs', '<string>', 'exec') exec(code, globals()) config.tdfunc = cy_td_ode_rhs # # setup integrator # initial_vector = mat2vec(rho0.full()).ravel('F') if issuper(rho0): r = scipy.integrate.ode(_td_ode_rhs_super) code = compile('r.set_f_params([' + parameter_string + '])', '<string>', 'exec') else: r = scipy.integrate.ode(config.tdfunc) code = compile('r.set_f_params(' + parameter_string + ')', '<string>', 'exec') r.set_integrator('zvode', method=opt.method, order=opt.order, atol=opt.atol, rtol=opt.rtol, nsteps=opt.nsteps, first_step=opt.first_step, min_step=opt.min_step, max_step=opt.max_step) r.set_initial_value(initial_vector, tlist[0]) exec(code, locals(), args) # # call generic ODE code # return _generic_ode_solve(r, rho0, tlist, e_ops, opt, progress_bar)
def _steadystate_power(L, ss_args): """ Inverse power method for steady state solving. """ ss_args['info'].pop('weight', None) if settings.debug: logger.debug('Starting iterative inverse-power method solver.') tol = ss_args['tol'] maxiter = ss_args['maxiter'] use_solver(assumeSortedIndices=True) rhoss = Qobj() sflag = issuper(L) if sflag: rhoss.dims = L.dims[0] else: rhoss.dims = [L.dims[0], 1] n = L.shape[0] # Build Liouvillian L, perm, perm2, rev_perm, ss_args = _steadystate_power_liouvillian( L, ss_args) orig_nnz = L.nnz # start with all ones as RHS v = np.ones(n, dtype=complex) if ss_args['use_rcm']: v = v[np.ix_(perm2, )] # Do preconditioning if ss_args['M'] is None and ss_args['use_precond'] and \ ss_args['method'] in ['power-gmres', 'power-lgmres', 'power-bicgstab']: ss_args['M'], ss_args = _iterative_precondition( L, int(np.sqrt(n)), ss_args) if ss_args['M'] is None: warnings.warn("Preconditioning failed. Continuing without.", UserWarning) ss_iters = {'iter': 0} def _iter_count(r): ss_iters['iter'] += 1 return _power_start = time.time() # Get LU factors if ss_args['method'] == 'power': lu = splu(L, permc_spec=ss_args['permc_spec'], diag_pivot_thresh=ss_args['diag_pivot_thresh'], options=dict(ILU_MILU=ss_args['ILU_MILU'])) if settings.debug and _scipy_check: L_nnz = lu.L.nnz U_nnz = lu.U.nnz logger.debug('L NNZ: %i ; U NNZ: %i' % (L_nnz, U_nnz)) logger.debug('Fill factor: %f' % ((L_nnz + U_nnz) / orig_nnz)) it = 0 _tol = np.max(ss_args['tol'] / 10, 1e-15) # Should make this user accessible while (la.norm(L * v, np.inf) > tol) and (it < maxiter): if ss_args['method'] == 'power': v = lu.solve(v) elif ss_args['method'] == 'power-gmres': v, check = gmres(L, v, tol=_tol, M=ss_args['M'], x0=ss_args['x0'], restart=ss_args['restart'], maxiter=ss_args['maxiter'], callback=_iter_count) elif ss_args['method'] == 'power-lgmres': v, check = lgmres(L, v, tol=_tol, M=ss_args['M'], x0=ss_args['x0'], maxiter=ss_args['maxiter'], callback=_iter_count) elif ss_args['method'] == 'power-bicgstab': v, check = bicgstab(L, v, tol=_tol, M=ss_args['M'], x0=ss_args['x0'], maxiter=ss_args['maxiter'], callback=_iter_count) else: raise Exception("Invalid iterative solver method.") v = v / la.norm(v, np.inf) it += 1 if it >= maxiter: raise Exception('Failed to find steady state after ' + str(maxiter) + ' iterations') _power_end = time.time() ss_args['info']['solution_time'] = _power_end - _power_start ss_args['info']['iterations'] = it if ss_args['return_info']: ss_args['info']['residual_norm'] = la.norm(L * v) if settings.debug: logger.debug('Number of iterations: %i' % it) if ss_args['use_rcm']: v = v[np.ix_(rev_perm, )] # normalise according to type of problem if sflag: trow = sp.eye(rhoss.shape[0], rhoss.shape[0], format='coo') trow = sp_reshape(trow, (1, n)) data = v / sum(trow.dot(v)) else: data = data / la.norm(v) data = sp.csr_matrix(vec2mat(data)) rhoss.data = 0.5 * (data + data.conj().T) rhoss.isherm = True if ss_args['return_info']: return rhoss, ss_args['info'] else: return rhoss
def _block_apply(block, channel): if isoper(channel): block = _top_apply_U(block, channel) elif issuper(channel): block = _top_apply_S(block, channel) return block
def _mesolve_list_str_td(H_list, rho0, tlist, c_list, e_ops, args, opt, progress_bar): """ Internal function for solving the master equation. See mesolve for usage. """ if debug: print(inspect.stack()[0][3]) # # check initial state: must be a density matrix # if isket(rho0): rho0 = rho0 * rho0.dag() # # construct liouvillian # Lconst = 0 Ldata = [] Linds = [] Lptrs = [] Lcoeff = [] # loop over all hamiltonian terms, convert to superoperator form and # add the data of sparse matrix representation to for h_spec in H_list: if isinstance(h_spec, Qobj): h = h_spec if isoper(h): Lconst += -1j * (spre(h) - spost(h)) elif issuper(h): Lconst += h else: raise TypeError( "Incorrect specification of time-dependent " + "Hamiltonian (expected operator or " + "superoperator)" ) elif isinstance(h_spec, list): h = h_spec[0] h_coeff = h_spec[1] if isoper(h): L = -1j * (spre(h) - spost(h)) elif issuper(h): L = h else: raise TypeError( "Incorrect specification of time-dependent " + "Hamiltonian (expected operator or " + "superoperator)" ) Ldata.append(L.data.data) Linds.append(L.data.indices) Lptrs.append(L.data.indptr) Lcoeff.append(h_coeff) else: raise TypeError("Incorrect specification of time-dependent " + "Hamiltonian (expected string format)") # loop over all collapse operators for c_spec in c_list: if isinstance(c_spec, Qobj): c = c_spec if isoper(c): cdc = c.dag() * c Lconst += spre(c) * spost(c.dag()) - 0.5 * spre(cdc) - 0.5 * spost(cdc) elif issuper(c): Lconst += c else: raise TypeError( "Incorrect specification of time-dependent " + "Liouvillian (expected operator or " + "superoperator)" ) elif isinstance(c_spec, list): c = c_spec[0] c_coeff = c_spec[1] if isoper(c): cdc = c.dag() * c L = spre(c) * spost(c.dag()) - 0.5 * spre(cdc) - 0.5 * spost(cdc) c_coeff = "(" + c_coeff + ")**2" elif issuper(c): L = c else: raise TypeError( "Incorrect specification of time-dependent " + "Liouvillian (expected operator or " + "superoperator)" ) Ldata.append(L.data.data) Linds.append(L.data.indices) Lptrs.append(L.data.indptr) Lcoeff.append(c_coeff) else: raise TypeError( "Incorrect specification of time-dependent " + "collapse operators (expected string format)" ) # add the constant part of the lagrangian if Lconst != 0: Ldata.append(Lconst.data.data) Linds.append(Lconst.data.indices) Lptrs.append(Lconst.data.indptr) Lcoeff.append("1.0") # the total number of liouvillian terms (hamiltonian terms + # collapse operators) n_L_terms = len(Ldata) # # setup ode args string: we expand the list Ldata, Linds and Lptrs into # and explicit list of parameters # string_list = [] for k in range(n_L_terms): string_list.append("Ldata[%d], Linds[%d], Lptrs[%d]" % (k, k, k)) for name, value in args.items(): if isinstance(value, np.ndarray): string_list.append(name) else: string_list.append(str(value)) parameter_string = ",".join(string_list) # # generate and compile new cython code if necessary # if not opt.rhs_reuse or config.tdfunc is None: if opt.rhs_filename is None: config.tdname = "rhs" + str(os.getpid()) + str(config.cgen_num) else: config.tdname = opt.rhs_filename cgen = Codegen(h_terms=n_L_terms, h_tdterms=Lcoeff, args=args, config=config) cgen.generate(config.tdname + ".pyx") code = compile("from " + config.tdname + " import cy_td_ode_rhs", "<string>", "exec") exec(code, globals()) config.tdfunc = cy_td_ode_rhs # # setup integrator # initial_vector = mat2vec(rho0.full()).ravel() r = scipy.integrate.ode(config.tdfunc) r.set_integrator( "zvode", method=opt.method, order=opt.order, atol=opt.atol, rtol=opt.rtol, nsteps=opt.nsteps, first_step=opt.first_step, min_step=opt.min_step, max_step=opt.max_step, ) r.set_initial_value(initial_vector, tlist[0]) code = compile("r.set_f_params(" + parameter_string + ")", "<string>", "exec") exec(code, locals(), args) # # call generic ODE code # return _generic_ode_solve(r, rho0, tlist, e_ops, opt, progress_bar)
def _steadystate_power(L, ss_args): """ Inverse power method for steady state solving. """ ss_args['info'].pop('weight', None) if settings.debug: logger.debug('Starting iterative inverse-power method solver.') tol = ss_args['tol'] mtol = ss_args['mtol'] if mtol is None: mtol = max(0.1*tol, 1e-15) maxiter = ss_args['maxiter'] use_solver(assumeSortedIndices=True) rhoss = Qobj() sflag = issuper(L) if sflag: rhoss.dims = L.dims[0] else: rhoss.dims = [L.dims[0], 1] n = L.shape[0] # Build Liouvillian if ss_args['solver'] == 'mkl' and ss_args['method'] == 'power': has_mkl = 1 else: has_mkl = 0 L, perm, perm2, rev_perm, ss_args = _steadystate_power_liouvillian(L, ss_args, has_mkl) orig_nnz = L.nnz # start with all ones as RHS v = np.ones(n, dtype=complex) if ss_args['use_rcm']: v = v[np.ix_(perm2,)] # Do preconditioning if ss_args['solver'] == 'scipy': if ss_args['M'] is None and ss_args['use_precond'] and \ ss_args['method'] in ['power-gmres', 'power-lgmres', 'power-bicgstab']: ss_args['M'], ss_args = _iterative_precondition(L, int(np.sqrt(n)), ss_args) if ss_args['M'] is None: warnings.warn("Preconditioning failed. Continuing without.", UserWarning) ss_iters = {'iter': 0} def _iter_count(r): ss_iters['iter'] += 1 return _power_start = time.time() # Get LU factors if ss_args['method'] == 'power': if ss_args['solver'] == 'mkl': lu = mkl_splu(L, max_iter_refine=ss_args['max_iter_refine'], scaling_vectors=ss_args['scaling_vectors'], weighted_matching=ss_args['weighted_matching']) else: lu = splu(L, permc_spec=ss_args['permc_spec'], diag_pivot_thresh=ss_args['diag_pivot_thresh'], options=dict(ILU_MILU=ss_args['ILU_MILU'])) if settings.debug and _scipy_check: L_nnz = lu.L.nnz U_nnz = lu.U.nnz logger.debug('L NNZ: %i ; U NNZ: %i' % (L_nnz, U_nnz)) logger.debug('Fill factor: %f' % ((L_nnz+U_nnz)/orig_nnz)) it = 0 # FIXME: These atol keyword except checks can be removed once scipy 1.1 # is a minimum requirement while (la.norm(L * v, np.inf) > tol) and (it < maxiter): check = 0 if ss_args['method'] == 'power': v = lu.solve(v) elif ss_args['method'] == 'power-gmres': try: v, check = gmres(L, v, tol=mtol, atol=ss_args['matol'], M=ss_args['M'], x0=ss_args['x0'], restart=ss_args['restart'], maxiter=ss_args['maxiter'], callback=_iter_count) except TypeError as e: if "unexpected keyword argument 'atol'" in str(e): v, check = gmres(L, v, tol=mtol, M=ss_args['M'], x0=ss_args['x0'], restart=ss_args['restart'], maxiter=ss_args['maxiter'], callback=_iter_count) elif ss_args['method'] == 'power-lgmres': try: v, check = lgmres(L, v, tol=mtol, atol=ss_args['matol'], M=ss_args['M'], x0=ss_args['x0'], maxiter=ss_args['maxiter'], callback=_iter_count) except TypeError as e: if "unexpected keyword argument 'atol'" in str(e): v, check = lgmres(L, v, tol=mtol, M=ss_args['M'], x0=ss_args['x0'], maxiter=ss_args['maxiter'], callback=_iter_count) elif ss_args['method'] == 'power-bicgstab': try: v, check = bicgstab(L, v, tol=mtol, atol=ss_args['matol'], M=ss_args['M'], x0=ss_args['x0'], maxiter=ss_args['maxiter'], callback=_iter_count) except TypeError as e: if "unexpected keyword argument 'atol'" in str(e): v, check = bicgstab(L, v, tol=mtol, M=ss_args['M'], x0=ss_args['x0'], maxiter=ss_args['maxiter'], callback=_iter_count) else: raise Exception("Invalid iterative solver method.") if check > 0: raise Exception("{} failed to find solution in " "{} iterations.".format(ss_args['method'], check)) if check < 0: raise Exception("Breakdown in {}".format(ss_args['method'])) v = v / la.norm(v, np.inf) it += 1 if ss_args['method'] == 'power' and ss_args['solver'] == 'mkl': lu.delete() if ss_args['return_info']: ss_args['info']['max_iter_refine'] = ss_args['max_iter_refine'] ss_args['info']['scaling_vectors'] = ss_args['scaling_vectors'] ss_args['info']['weighted_matching'] = ss_args['weighted_matching'] if it >= maxiter: raise Exception('Failed to find steady state after ' + str(maxiter) + ' iterations') _power_end = time.time() ss_args['info']['solution_time'] = _power_end-_power_start ss_args['info']['iterations'] = it if ss_args['return_info']: ss_args['info']['residual_norm'] = la.norm(L*v, np.inf) if settings.debug: logger.debug('Number of iterations: %i' % it) if ss_args['use_rcm']: v = v[np.ix_(rev_perm,)] # normalise according to type of problem if sflag: trow = v[::rhoss.shape[0]+1] data = v / np.sum(trow) else: data = data / la.norm(v) data = dense2D_to_fastcsr_fmode(vec2mat(data), rhoss.shape[0], rhoss.shape[0]) rhoss.data = 0.5 * (data + data.H) rhoss.isherm = True if ss_args['return_info']: return rhoss, ss_args['info'] else: return rhoss
def _mesolve_list_str_td(H_list, rho0, tlist, c_list, e_ops, args, opt, progress_bar): """ Internal function for solving the master equation. See mesolve for usage. """ if debug: print(inspect.stack()[0][3]) # # check initial state: must be a density matrix # if isket(rho0): rho0 = rho0 * rho0.dag() # # construct liouvillian # Lconst = 0 Ldata = [] Linds = [] Lptrs = [] Lcoeff = [] Lobj = [] me_cops_coeff = [] me_cops_obj = [] me_cops_obj_flags = [] # loop over all hamiltonian terms, convert to superoperator form and # add the data of sparse matrix representation to n_not_const_terms = 0 for h_spec in H_list: if isinstance(h_spec, Qobj): h = h_spec if isoper(h): Lconst += -1j * (spre(h) - spost(h)) elif issuper(h): Lconst += h else: raise TypeError("Incorrect specification of time-dependent " + "Hamiltonian (expected operator or " + "superoperator)") elif isinstance(h_spec, list): n_not_const_terms += 1 h = h_spec[0] h_coeff = h_spec[1] if isoper(h): L = -1j * (spre(h) - spost(h)) elif issuper(h): L = h else: raise TypeError("Incorrect specification of time-dependent " + "Hamiltonian (expected operator or " + "superoperator)") Ldata.append(L.data.data) Linds.append(L.data.indices) Lptrs.append(L.data.indptr) if isinstance(h_coeff, Cubic_Spline): Lobj.append(h_coeff.coeffs) Lcoeff.append(h_coeff) else: raise TypeError("Incorrect specification of time-dependent " + "Hamiltonian (expected string format)") # loop over all collapse operators for c_spec in c_list: if isinstance(c_spec, Qobj): c = c_spec if isoper(c): cdc = c.dag() * c Lconst += spre(c) * spost(c.dag()) - 0.5 * spre(cdc) \ - 0.5 * spost(cdc) elif issuper(c): Lconst += c else: raise TypeError("Incorrect specification of time-dependent " + "Liouvillian (expected operator or " + "superoperator)") elif isinstance(c_spec, list): n_not_const_terms += 1 c = c_spec[0] c_coeff = c_spec[1] if isoper(c): cdc = c.dag() * c L = spre(c) * spost(c.dag()) - 0.5 * spre(cdc) \ - 0.5 * spost(cdc) if isinstance(c_coeff, Cubic_Spline): me_cops_obj.append(c_coeff.coeffs) me_cops_obj_flags.append(n_not_const_terms) me_cops_coeff.append(c_coeff) else: c_coeff = "(" + c_coeff + ")**2" Lcoeff.append(c_coeff) elif issuper(c): L = c if isinstance(c_coeff, Cubic_Spline): me_cops_obj.append(c_coeff.coeffs) me_cops_obj_flags.append(-n_not_const_terms) me_cops_coeff.append(c_coeff) else: Lcoeff.append(c_coeff) else: raise TypeError("Incorrect specification of time-dependent " + "Liouvillian (expected operator or " + "superoperator)") Ldata.append(L.data.data) Linds.append(L.data.indices) Lptrs.append(L.data.indptr) #Lcoeff.append(c_coeff) else: raise TypeError("Incorrect specification of time-dependent " + "collapse operators (expected string format)") #prepend the constant part of the liouvillian if Lconst != 0: Ldata = [Lconst.data.data] + Ldata Linds = [Lconst.data.indices] + Linds Lptrs = [Lconst.data.indptr] + Lptrs Lcoeff = ["1.0"] + Lcoeff else: me_cops_obj_flags = [kk - 1 for kk in me_cops_obj_flags] # the total number of liouvillian terms (hamiltonian terms + # collapse operators) n_L_terms = len(Ldata) n_td_cops = len(me_cops_obj) # Check which components should use OPENMP omp_components = None if qset.has_openmp: if opt.use_openmp: omp_components = openmp_components(Lptrs) # # setup ode args string: we expand the list Ldata, Linds and Lptrs into # and explicit list of parameters # string_list = [] for k in range(n_L_terms): string_list.append("Ldata[%d], Linds[%d], Lptrs[%d]" % (k, k, k)) # Add H object terms to ode args string for k in range(len(Lobj)): string_list.append("Lobj[%d]" % k) # Add cop object terms to end of ode args string for k in range(len(me_cops_obj)): string_list.append("me_cops_obj[%d]" % k) for name, value in args.items(): if isinstance(value, np.ndarray): string_list.append(name) else: string_list.append(str(value)) parameter_string = ",".join(string_list) # # generate and compile new cython code if necessary # if not opt.rhs_reuse or config.tdfunc is None: if opt.rhs_filename is None: config.tdname = "rhs" + str(os.getpid()) + str(config.cgen_num) else: config.tdname = opt.rhs_filename cgen = Codegen(h_terms=len(Lcoeff), h_tdterms=Lcoeff, c_td_splines=me_cops_coeff, c_td_spline_flags=me_cops_obj_flags, args=args, config=config, use_openmp=opt.use_openmp, omp_components=omp_components, omp_threads=opt.openmp_threads) cgen.generate(config.tdname + ".pyx") code = compile('from ' + config.tdname + ' import cy_td_ode_rhs', '<string>', 'exec') exec(code, globals()) config.tdfunc = cy_td_ode_rhs # # setup integrator # initial_vector = mat2vec(rho0.full()).ravel('F') if issuper(rho0): r = scipy.integrate.ode(_td_ode_rhs_super) code = compile('r.set_f_params([' + parameter_string + '])', '<string>', 'exec') else: r = scipy.integrate.ode(config.tdfunc) code = compile('r.set_f_params(' + parameter_string + ')', '<string>', 'exec') r.set_integrator('zvode', method=opt.method, order=opt.order, atol=opt.atol, rtol=opt.rtol, nsteps=opt.nsteps, first_step=opt.first_step, min_step=opt.min_step, max_step=opt.max_step) r.set_initial_value(initial_vector, tlist[0]) exec(code, locals(), args) # # call generic ODE code # return _generic_ode_solve(r, rho0, tlist, e_ops, opt, progress_bar)
def _steadystate_power(L, ss_args): """ Inverse power method for steady state solving. """ ss_args['info'].pop('weight', None) if settings.debug: print('Starting iterative inverse-power method solver...') tol = ss_args['tol'] maxiter = ss_args['maxiter'] use_solver(assumeSortedIndices=True) rhoss = Qobj() sflag = issuper(L) if sflag: rhoss.dims = L.dims[0] else: rhoss.dims = [L.dims[0], 1] n = prod(rhoss.shape) L = L.data.tocsc() - (1e-15) * sp.eye(n, n, format='csc') L.sort_indices() orig_nnz = L.nnz # start with all ones as RHS v = np.ones(n,dtype=complex) if ss_args['use_rcm']: if settings.debug: old_band = sp_bandwidth(L)[0] print('Original bandwidth:', old_band) perm = reverse_cuthill_mckee(L) rev_perm = np.argsort(perm) L = sp_permute(L, perm, perm, 'csc') v = v[np.ix_(perm,)] if settings.debug: new_band = sp_bandwidth(L)[0] print('RCM bandwidth:', new_band) print('Bandwidth reduction factor:', round(old_band/new_band, 2)) # Get LU factors lu = splu(L, permc_spec=ss_args['permc_spec'], diag_pivot_thresh=ss_args['diag_pivot_thresh'], options=dict(ILU_MILU=ss_args['ILU_MILU'])) if settings.debug and _scipy_check: L_nnz = lu.L.nnz U_nnz = lu.U.nnz print('L NNZ:', L_nnz, ';', 'U NNZ:', U_nnz) print('Fill factor:', (L_nnz+U_nnz)/orig_nnz) _power_start = time.time() it = 0 while (la.norm(L * v, np.inf) > tol) and (it < maxiter): v = lu.solve(v) v = v / la.norm(v, np.inf) it += 1 if it >= maxiter: raise Exception('Failed to find steady state after ' + str(maxiter) + ' iterations') _power_end = time.time() ss_args['info']['solution_time'] = _power_end-_power_start ss_args['info']['iterations'] = it if settings.debug: print('Number of iterations:', it) if ss_args['use_rcm']: v = v[np.ix_(rev_perm,)] # normalise according to type of problem if sflag: trow = sp.eye(rhoss.shape[0], rhoss.shape[0], format='coo') trow = sp_reshape(trow, (1, n)) data = v / sum(trow.dot(v)) else: data = data / la.norm(v) data = sp.csr_matrix(vec2mat(data)) rhoss.data = 0.5 * (data + data.conj().T) rhoss.isherm = True if ss_args['return_info']: return rhoss, ss_args['info'] else: return rhoss
def _steadystate_power(L, ss_args): """ Inverse power method for steady state solving. """ ss_args['info'].pop('weight', None) if settings.debug: logger.debug('Starting iterative inverse-power method solver.') tol = ss_args['tol'] maxiter = ss_args['maxiter'] use_solver(assumeSortedIndices=True) rhoss = Qobj() sflag = issuper(L) if sflag: rhoss.dims = L.dims[0] else: rhoss.dims = [L.dims[0], 1] n = L.shape[0] # Build Liouvillian if settings.has_mkl and ss_args['method'] == 'power': has_mkl = 1 else: has_mkl = 0 L, perm, perm2, rev_perm, ss_args = _steadystate_power_liouvillian(L, ss_args, has_mkl) orig_nnz = L.nnz # start with all ones as RHS v = np.ones(n, dtype=complex) if ss_args['use_rcm']: v = v[np.ix_(perm2,)] # Do preconditioning if ss_args['M'] is None and ss_args['use_precond'] and \ ss_args['method'] in ['power-gmres', 'power-lgmres', 'power-bicgstab']: ss_args['M'], ss_args = _iterative_precondition(L, int(np.sqrt(n)), ss_args) if ss_args['M'] is None: warnings.warn("Preconditioning failed. Continuing without.", UserWarning) ss_iters = {'iter': 0} def _iter_count(r): ss_iters['iter'] += 1 return _power_start = time.time() # Get LU factors if ss_args['method'] == 'power': if settings.has_mkl: lu = mkl_splu(L) else: lu = splu(L, permc_spec=ss_args['permc_spec'], diag_pivot_thresh=ss_args['diag_pivot_thresh'], options=dict(ILU_MILU=ss_args['ILU_MILU'])) if settings.debug and _scipy_check: L_nnz = lu.L.nnz U_nnz = lu.U.nnz logger.debug('L NNZ: %i ; U NNZ: %i' % (L_nnz, U_nnz)) logger.debug('Fill factor: %f' % ((L_nnz+U_nnz)/orig_nnz)) it = 0 _tol = max(ss_args['tol']/10, 1e-15) # Should make this user accessible while (la.norm(L * v, np.inf) > tol) and (it < maxiter): if ss_args['method'] == 'power': v = lu.solve(v) elif ss_args['method'] == 'power-gmres': v, check = gmres(L, v, tol=_tol, M=ss_args['M'], x0=ss_args['x0'], restart=ss_args['restart'], maxiter=ss_args['maxiter'], callback=_iter_count) elif ss_args['method'] == 'power-lgmres': v, check = lgmres(L, v, tol=_tol, M=ss_args['M'], x0=ss_args['x0'], maxiter=ss_args['maxiter'], callback=_iter_count) elif ss_args['method'] == 'power-bicgstab': v, check = bicgstab(L, v, tol=_tol, M=ss_args['M'], x0=ss_args['x0'], maxiter=ss_args['maxiter'], callback=_iter_count) else: raise Exception("Invalid iterative solver method.") v = v / la.norm(v, np.inf) it += 1 if ss_args['method'] == 'power' and settings.has_mkl: lu.delete() if it >= maxiter: raise Exception('Failed to find steady state after ' + str(maxiter) + ' iterations') _power_end = time.time() ss_args['info']['solution_time'] = _power_end-_power_start ss_args['info']['iterations'] = it if ss_args['return_info']: ss_args['info']['residual_norm'] = la.norm(L*v) if settings.debug: logger.debug('Number of iterations: %i' % it) if ss_args['use_rcm']: v = v[np.ix_(rev_perm,)] # normalise according to type of problem if sflag: trow = sp.eye(rhoss.shape[0], rhoss.shape[0], format='coo') trow = sp_reshape(trow, (1, n)) data = v / sum(trow.dot(v)) else: data = data / la.norm(v) data = sp.csr_matrix(vec2mat(data)) rhoss.data = 0.5 * (data + data.conj().T) rhoss.isherm = True if ss_args['return_info']: return rhoss, ss_args['info'] else: return rhoss
def _mesolve_func_td(L_func, rho0, tlist, c_op_list, e_ops, args, opt, progress_bar): """ Evolve the density matrix using an ODE solver with time dependent Hamiltonian. """ if debug: print(inspect.stack()[0][3]) # # check initial state # if isket(rho0): rho0 = ket2dm(rho0) # # construct liouvillian # new_args = None if len(c_op_list) > 0: L_data = liouvillian(None, c_op_list).data else: n, m = rho0.shape if issuper(rho0): L_data = sp.csr_matrix((n, m), dtype=complex) else: L_data = sp.csr_matrix((n**2, m**2), dtype=complex) if type(args) is dict: new_args = {} for key in args: if isinstance(args[key], Qobj): if isoper(args[key]): new_args[key] = (-1j * (spre(args[key]) - spost(args[key]))) else: new_args[key] = args[key] else: new_args[key] = args[key] elif type(args) is list or type(args) is tuple: new_args = [] for arg in args: if isinstance(arg, Qobj): if isoper(arg): new_args.append((-1j * (spre(arg) - spost(arg))).data) else: new_args.append(arg.data) else: new_args.append(arg) if type(args) is tuple: new_args = tuple(new_args) else: if isinstance(args, Qobj): if isoper(args): new_args = (-1j * (spre(args) - spost(args))) else: new_args = args else: new_args = args # # setup integrator # initial_vector = mat2vec(rho0.full()).ravel('F') if issuper(rho0): if not opt.rhs_with_state: r = scipy.integrate.ode(_ode_super_func_td) else: r = scipy.integrate.ode(_ode_super_func_td_with_state) else: if not opt.rhs_with_state: r = scipy.integrate.ode(cy_ode_rho_func_td) else: r = scipy.integrate.ode(_ode_rho_func_td_with_state) r.set_integrator('zvode', method=opt.method, order=opt.order, atol=opt.atol, rtol=opt.rtol, nsteps=opt.nsteps, first_step=opt.first_step, min_step=opt.min_step, max_step=opt.max_step) r.set_initial_value(initial_vector, tlist[0]) r.set_f_params(L_data, L_func, new_args) # # call generic ODE code # return _generic_ode_solve(r, rho0, tlist, e_ops, opt, progress_bar)
def _steadystate_power(L, ss_args): """ Inverse power method for steady state solving. """ ss_args['info'].pop('weight', None) if settings.debug: logger.debug('Starting iterative inverse-power method solver.') tol = ss_args['tol'] maxiter = ss_args['maxiter'] use_solver(assumeSortedIndices=True) rhoss = Qobj() sflag = issuper(L) if sflag: rhoss.dims = L.dims[0] else: rhoss.dims = [L.dims[0], 1] n = prod(rhoss.shape) L = L.data.tocsc() - (1e-15) * sp.eye(n, n, format='csc') L.sort_indices() orig_nnz = L.nnz # start with all ones as RHS v = np.ones(n, dtype=complex) if ss_args['use_rcm']: if settings.debug: old_band = sp_bandwidth(L)[0] logger.debug('Original bandwidth: %i' % old_band) perm = reverse_cuthill_mckee(L) rev_perm = np.argsort(perm) L = sp_permute(L, perm, perm, 'csc') v = v[np.ix_(perm, )] if settings.debug: new_band = sp_bandwidth(L)[0] logger.debug('RCM bandwidth: %i' % new_band) logger.debug('Bandwidth reduction factor: %f' % round(old_band / new_band, 2)) _power_start = time.time() # Get LU factors lu = splu(L, permc_spec=ss_args['permc_spec'], diag_pivot_thresh=ss_args['diag_pivot_thresh'], options=dict(ILU_MILU=ss_args['ILU_MILU'])) if settings.debug and _scipy_check: L_nnz = lu.L.nnz U_nnz = lu.U.nnz logger.debug('L NNZ: %i ; U NNZ: %i' % (L_nnz, U_nnz)) logger.debug('Fill factor: %f' % ((L_nnz + U_nnz) / orig_nnz)) it = 0 while (la.norm(L * v, np.inf) > tol) and (it < maxiter): v = lu.solve(v) v = v / la.norm(v, np.inf) it += 1 if it >= maxiter: raise Exception('Failed to find steady state after ' + str(maxiter) + ' iterations') _power_end = time.time() ss_args['info']['solution_time'] = _power_end - _power_start ss_args['info']['iterations'] = it if ss_args['return_info']: ss_args['info']['residual_norm'] = la.norm(L * v, np.inf) if settings.debug: logger.debug('Number of iterations: %i' % it) if ss_args['use_rcm']: v = v[np.ix_(rev_perm, )] # normalise according to type of problem if sflag: trow = sp.eye(rhoss.shape[0], rhoss.shape[0], format='coo') trow = sp_reshape(trow, (1, n)) data = v / sum(trow.dot(v)) else: data = data / la.norm(v) data = sp.csr_matrix(vec2mat(data)) rhoss.data = 0.5 * (data + data.conj().T) rhoss.isherm = True if ss_args['return_info']: return rhoss, ss_args['info'] else: return rhoss
def _mesolve_list_func_td(H_list, rho0, tlist, c_list, e_ops, args, opt, progress_bar): """ Internal function for solving the master equation. See mesolve for usage. """ if debug: print(inspect.stack()[0][3]) # # check initial state # if isket(rho0): rho0 = rho0 * rho0.dag() # # construct liouvillian in list-function format # L_list = [] if opt.rhs_with_state: constant_func = lambda x, y, z: 1.0 else: constant_func = lambda x, y: 1.0 # add all hamitonian terms to the lagrangian list for h_spec in H_list: if isinstance(h_spec, Qobj): h = h_spec h_coeff = constant_func elif isinstance(h_spec, list) and isinstance(h_spec[0], Qobj): h = h_spec[0] h_coeff = h_spec[1] else: raise TypeError("Incorrect specification of time-dependent " + "Hamiltonian (expected callback function)") if isoper(h): L_list.append([(-1j * (spre(h) - spost(h))).data, h_coeff, False]) elif issuper(h): L_list.append([h.data, h_coeff, False]) else: raise TypeError( "Incorrect specification of time-dependent " + "Hamiltonian (expected operator or superoperator)" ) # add all collapse operators to the liouvillian list for c_spec in c_list: if isinstance(c_spec, Qobj): c = c_spec c_coeff = constant_func c_square = False elif isinstance(c_spec, list) and isinstance(c_spec[0], Qobj): c = c_spec[0] c_coeff = c_spec[1] c_square = True else: raise TypeError( "Incorrect specification of time-dependent " + "collapse operators (expected callback function)" ) if isoper(c): L_list.append([liouvillian(None, [c], data_only=True), c_coeff, c_square]) elif issuper(c): L_list.append([c.data, c_coeff, c_square]) else: raise TypeError( "Incorrect specification of time-dependent " + "collapse operators (expected operator or " + "superoperator)" ) # # setup integrator # initial_vector = mat2vec(rho0.full()).ravel() if opt.rhs_with_state: r = scipy.integrate.ode(drho_list_td_with_state) else: r = scipy.integrate.ode(drho_list_td) r.set_integrator( "zvode", method=opt.method, order=opt.order, atol=opt.atol, rtol=opt.rtol, nsteps=opt.nsteps, first_step=opt.first_step, min_step=opt.min_step, max_step=opt.max_step, ) r.set_initial_value(initial_vector, tlist[0]) r.set_f_params(L_list, args) # # call generic ODE code # return _generic_ode_solve(r, rho0, tlist, e_ops, opt, progress_bar)
def steadystate(A, c_op_list=[], method='direct', sparse=True, use_rcm=True, sym=False, use_precond=True, M=None, drop_tol=1e-3, fill_factor=12, diag_pivot_thresh=None, maxiter=1000, tol=1e-5, use_umfpack=False): """Calculates the steady state for quantum evolution subject to the supplied Hamiltonian or Liouvillian operator and (if given a Hamiltonian) a list of collapse operators. If the user passes a Hamiltonian then it, along with the list of collapse operators, will be converted into a Liouvillian operator in Lindblad form. Parameters ---------- A : qobj A Hamiltonian or Liouvillian operator. c_op_list : list A list of collapse operators. method : str {'direct', 'iterative', 'iterative-bicg', 'lu', 'svd', 'power'} Method for solving the underlying linear equation. Direct solver 'direct' (default), iterative GMRES method 'iterative', iterative method BICGSTAB 'iterative-bicg', LU decomposition 'lu', SVD 'svd' (dense), or inverse-power method 'power'. sparse : bool, default = True Solve for the steady state using sparse algorithms. If set to False, the underlying Liouvillian operator will be converted into a dense matrix. Use only for 'smaller' systems. use_rcm : bool, default = True Use reverse Cuthill-Mckee reordering to minimize fill-in in the LU factorization of the Liouvillian. maxiter : int, optional Maximum number of iterations to perform if using an iterative method such as 'iterative' (default=1000), or 'power' (default=10). tol : float, optional, default=1e-5 Tolerance used for terminating solver solution when using iterative solvers. use_precond : bool optional, default = True ITERATIVE ONLY. Use an incomplete sparse LU decomposition as a preconditioner for the 'iterative' LGMRES and BICG solvers. Speeds up convergence time by orders of magnitude in many cases. M : {sparse matrix, dense matrix, LinearOperator} Preconditioner for A. The preconditioner should approximate the inverse of A. Effective preconditioning dramatically improves the rate of convergence, for iterative methods. Does not affect other solvers. fill_factor : float, default=12 ITERATIVE ONLY. Specifies the fill ratio upper bound (>=1) of the iLU preconditioner. Lower values save memory at the cost of longer execution times and a possible singular factorization. drop_tol : float, default=1e-3 ITERATIVE ONLY. Sets the threshold for the magnitude of preconditioner elements that should be dropped. Can be reduced for a courser factorization at the cost of an increased number of iterations, and a possible singular factorization. diag_pivot_thresh : float, default=None ITERATIVE ONLY. Sets the threshold between [0,1] for which diagonal elements are considered acceptable pivot points when using a preconditioner. A value of zero forces the pivot to be the diagonal element. Returns ------- dm : qobj Steady state density matrix. Notes ----- The SVD method works only for dense operators (i.e. small systems). """ n_op = len(c_op_list) if isoper(A): if n_op == 0: raise TypeError('Cannot calculate the steady state for a ' + 'non-dissipative system ' + '(no collapse operators given)') else: A = liouvillian_fast(A, c_op_list) if not issuper(A): raise TypeError('Solving for steady states requires ' + 'Liouvillian (super) operators') if use_umfpack: warnings.warn("The use of use_umfpack is deprecated.") if method == 'direct': if sparse: return _steadystate_direct_sparse(A, use_rcm=use_rcm, use_umfpack=use_umfpack) else: return _steadystate_direct_dense(A) elif method == 'iterative': return _steadystate_iterative(A, tol=tol, use_precond=use_precond, M=M, use_rcm=use_rcm, sym=sym, maxiter=maxiter, fill_factor=fill_factor, drop_tol=drop_tol, diag_pivot_thresh=diag_pivot_thresh) elif method == 'iterative-bicg': return _steadystate_iterative_bicg(A, tol=tol, use_precond=use_precond, M=M, use_rcm=use_rcm, maxiter=maxiter, fill_factor=fill_factor, drop_tol=drop_tol, diag_pivot_thresh=diag_pivot_thresh) elif method == 'lu': return _steadystate_lu(A, use_umfpack=use_umfpack) elif method == 'svd': return _steadystate_svd_dense(A, atol=1e-12, rtol=0, all_steadystates=False) elif method == 'power': return _steadystate_power(A, maxiter=10, tol=tol, itertol=tol) else: raise ValueError('Invalid method argument for steadystate.')
def mesolve(H, rho0, tlist, c_ops, e_ops, args={}, options=None, progress_bar=BaseProgressBar()): """ Master equation evolution of a density matrix for a given Hamiltonian. Evolve the state vector or density matrix (`rho0`) using a given Hamiltonian (`H`) and an [optional] set of collapse operators (`c_op_list`), 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. **Time-dependent operators** For problems with 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, sin(w*tlist)], [H1, sin(2*w*tlist)]] H = [[H0, f0_t], [H1, f1_t]] where f0_t and f1_t are python functions with signature f_t(t, args). 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 .. note:: If an element in the list-specification of the Hamiltonian or the list of collapse operators are in super-operator for 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. 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. 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 ODE solver. progress_bar: TextProgressBar 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 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 # 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() 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 # # # 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
def ode2es(L, rho0): """Creates an exponential series that describes the time evolution for the initial density matrix (or state vector) `rho0`, given the Liouvillian (or Hamiltonian) `L`. Parameters ---------- L : qobj Liouvillian of the system. rho0 : qobj Initial state vector or density matrix. Returns ------- eseries : :class:`qutip.eseries` ``eseries`` represention of the system dynamics. """ if issuper(L): # check initial state if isket(rho0): # Got a wave function as initial state: convert to density matrix. rho0 = rho0 * rho0.dag() w, v = L.eigenstates() v = np.hstack([ket.full() for ket in v]) # w[i] = eigenvalue i # v[:,i] = eigenvector i rlen = np.prod(rho0.shape) r0 = mat2vec(rho0.full()) v0 = la.solve(v, r0) vv = v * sp.spdiags(v0.T, 0, rlen, rlen) out = None for i in range(rlen): qo = Qobj(vec2mat(vv[:, i]), dims=rho0.dims, shape=rho0.shape) if out: out += eseries(qo, w[i]) else: out = eseries(qo, w[i]) elif isoper(L): if not isket(rho0): raise TypeError('Second argument must be a ket if first' + 'is a Hamiltonian.') w, v = L.eigenstates() v = np.hstack([ket.full() for ket in v]) # w[i] = eigenvalue i # v[:,i] = eigenvector i rlen = np.prod(rho0.shape) r0 = rho0.full() v0 = la.solve(v, r0) vv = v * sp.spdiags(v0.T, 0, rlen, rlen) out = None for i in range(rlen): qo = Qobj(np.matrix(vv[:, i]).T, dims=rho0.dims, shape=rho0.shape) if out: out += eseries(qo, -1.0j * w[i]) else: out = eseries(qo, -1.0j * w[i]) else: raise TypeError('First argument must be a Hamiltonian or Liouvillian.') return estidy(out)
def steadystate(A, c_op_list=[], method='direct', sparse=True, use_rcm=True, sym=False, use_precond=True, M=None, drop_tol=1e-3, fill_factor=12, diag_pivot_thresh=None, maxiter=1000, tol=1e-5, verbose=False): """Calculates the steady state for quantum evolution subject to the supplied Hamiltonian or Liouvillian operator and (if given a Hamiltonian) a list of collapse operators. If the user passes a Hamiltonian then it, along with the list of collapse operators, will be converted into a Liouvillian operator in Lindblad form. Parameters ---------- A : qobj A Hamiltonian or Liouvillian operator. c_op_list : list A list of collapse operators. method : str {'direct', 'iterative', 'iterative-bicg', 'lu', 'svd', 'power'} Method for solving the underlying linear equation. Direct solver 'direct' (default), iterative GMRES method 'iterative', iterative method BICGSTAB 'iterative-bicg', LU decomposition 'lu', SVD 'svd' (dense), or inverse-power method 'power'. sparse : bool, default=True Solve for the steady state using sparse algorithms. If set to False, the underlying Liouvillian operator will be converted into a dense matrix. Use only for 'smaller' systems. maxiter : int, optional Maximum number of iterations to perform if using an iterative method such as 'iterative' (default=1000), or 'power' (default=10). tol : float, optional, default=1e-5 Tolerance used for terminating solver solution when using iterative solvers. use_precond : bool optional, default = True ITERATIVE ONLY. Use an incomplete sparse LU decomposition as a preconditioner for the 'iterative' LGMRES and BICG solvers. Speeds up convergence time by orders of magnitude in many cases. M : {sparse matrix, dense matrix, LinearOperator} Preconditioner for A. The preconditioner should approximate the inverse of A. Effective preconditioning dramatically improves the rate of convergence, for iterative methods. Does not affect other solvers. fill_factor : float, default=12 ITERATIVE ONLY. Specifies the fill ratio upper bound (>=1) of the iLU preconditioner. Lower values save memory at the cost of longer execution times and a possible singular factorization. drop_tol : float, default=1e-3 ITERATIVE ONLY. Sets the threshold for the magnitude of preconditioner elements that should be dropped. Can be reduced for a courser factorization at the cost of an increased number of iterations, and a possible singular factorization. diag_pivot_thresh : float, default=None ITERATIVE ONLY. Sets the threshold between [0,1] for which diagonal elements are considered acceptable pivot points when using a preconditioner. A value of zero forces the pivot to be the diagonal element. verbose : bool default=False Flag for printing out detailed information on the steady state solver. Returns ------- dm : qobj Steady state density matrix. Notes ----- The SVD method works only for dense operators (i.e. small systems). """ n_op = len(c_op_list) if isoper(A): if n_op == 0: raise TypeError('Cannot calculate the steady state for a ' + 'non-dissipative system ' + '(no collapse operators given)') else: A = liouvillian_fast(A, c_op_list) if not issuper(A): raise TypeError('Solving for steady states requires ' + 'Liouvillian (super) operators') if method == 'direct': if sparse: return _steadystate_direct_sparse(A, verbose=verbose) else: return _steadystate_direct_dense(A, verbose=verbose) elif method == 'iterative': return _steadystate_iterative(A, tol=tol, use_precond=use_precond, M=M, use_rcm=use_rcm, sym=sym, maxiter=maxiter, fill_factor=fill_factor, drop_tol=drop_tol, diag_pivot_thresh=diag_pivot_thresh, verbose=verbose) elif method == 'iterative-bicg': return _steadystate_iterative_bicg(A, tol=tol, use_precond=use_precond, M=M, use_rcm=use_rcm, maxiter=maxiter, fill_factor=fill_factor, drop_tol=drop_tol, diag_pivot_thresh=diag_pivot_thresh, verbose=verbose) elif method == 'lu': return _steadystate_lu(A, verbose=verbose) elif method == 'svd': return _steadystate_svd_dense(A, atol=1e-12, rtol=0, all_steadystates=False, verbose=verbose) elif method == 'power': return _steadystate_power(A, maxiter=10, tol=tol, itertol=tol, verbose=verbose) else: raise ValueError('Invalid method argument for steadystate.')
def _generic_ode_solve(func, ode_args, rho0, tlist, e_ops, opt, progress_bar, dims=None): """ Internal function for solving ME. Calculate the required expectation values or invoke callback function at each time step. """ # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% # This function is made similar to sesolve's one for futur merging in a # solver class # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% # prepare output array n_tsteps = len(tlist) output = Result() output.solver = "mesolve" output.times = tlist size = rho0.shape[0] initial_vector = rho0.full().ravel('F') r = scipy.integrate.ode(func) r.set_integrator('zvode', method=opt.method, order=opt.order, atol=opt.atol, rtol=opt.rtol, nsteps=opt.nsteps, first_step=opt.first_step, min_step=opt.min_step, max_step=opt.max_step) if ode_args: r.set_f_params(*ode_args) r.set_initial_value(initial_vector, tlist[0]) e_ops_data = [] output.expect = [] if callable(e_ops): n_expt_op = 0 expt_callback = True output.num_expect = 1 elif isinstance(e_ops, list): n_expt_op = len(e_ops) expt_callback = False output.num_expect = n_expt_op if n_expt_op == 0: # fall back on storing states opt.store_states = True else: for op in e_ops: if not isinstance(op, Qobj) and callable(op): output.expect.append(np.zeros(n_tsteps, dtype=complex)) continue if op.dims != rho0.dims: raise TypeError(f"e_ops dims ({op.dims}) are not " f"compatible with the state's " f"({rho0.dims})") e_ops_data.append(spre(op).data) if op.isherm and rho0.isherm: output.expect.append(np.zeros(n_tsteps)) else: output.expect.append(np.zeros(n_tsteps, dtype=complex)) else: raise TypeError("Expectation parameter must be a list or a function") if opt.store_states: output.states = [] def get_curr_state_data(r): return vec2mat(r.y) # # start evolution # dt = np.diff(tlist) cdata = None progress_bar.start(n_tsteps) for t_idx, t in enumerate(tlist): progress_bar.update(t_idx) if not r.successful(): raise Exception("ODE integration error: Try to increase " "the allowed number of substeps by increasing " "the nsteps parameter in the Options class.") if opt.store_states or expt_callback: cdata = get_curr_state_data(r) fdata = dense2D_to_fastcsr_fmode(cdata, size, size) # Try to guess if there is a fast path for rho_t if issuper(rho0) or not rho0.isherm: rho_t = Qobj(fdata, dims=dims) else: rho_t = Qobj(fdata, dims=dims, fast="mc-dm") if opt.store_states: output.states.append(rho_t) if expt_callback: # use callback method output.expect.append(e_ops(t, rho_t)) for m in range(n_expt_op): if not isinstance(e_ops[m], Qobj) and callable(e_ops[m]): output.expect[m][t_idx] = e_ops[m](t, rho_t) continue output.expect[m][t_idx] = expect_rho_vec( e_ops_data[m], r.y, e_ops[m].isherm and rho0.isherm) if t_idx < n_tsteps - 1: r.integrate(r.t + dt[t_idx]) progress_bar.finished() if opt.store_final_state: cdata = get_curr_state_data(r) output.final_state = Qobj(cdata, dims=dims, isherm=rho0.isherm or None) 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 subsystem_apply(state, channel, mask, reference=False): """ Returns the result of applying the propagator `channel` to the subsystems indicated in `mask`, which comprise the density operator `state`. Parameters ---------- state : :class:`qutip.qobj` A density matrix or ket. channel : :class:`qutip.qobj` A propagator, either an `oper` or `super`. mask : *list* / *array* A mask that selects which subsystems should be subjected to the channel. reference : bool Decides whether explicit Kraus map should be used to evaluate action of channel. Returns ------- rho_out: :class:`qutip.qobj` A density matrix with the selected subsystems transformed according to the specified channel. """ # TODO: Include sparse/dense methods a la partial_transpose. # ---Sanity Checks---# # state must be a ket or density matrix, channel must be a propagator. assert state.type == 'ket' or state.type == 'oper', "Input state must be\ a ket or oper, given: " + repr(state.type) assert channel.type == 'super' or channel.type == 'oper', "Input channel \ must be a super or oper, given: " + repr(channel.type) # Since there's only one channel, all affected subsystems must have # the same dimensions: aff_subs_dim_ar = transpose(array(state.dims))[array(mask)] assert all([(aff_subs_dim_ar[j] == aff_subs_dim_ar[0]).all() for j in range(len(aff_subs_dim_ar))]), \ "Affected subsystems must have the same dimension. Given:" +\ repr(aff_subs_dim_ar) # If the map is on the Hilbert space, it must have the same dimension # as the affected subsystem. If it is on the Liouville space, it must # exist on a space as large as the square of the Hilbert dimension. if issuper(channel): required_shape = list(map(lambda x: x**2, aff_subs_dim_ar[0])) else: required_shape = aff_subs_dim_ar[0] assert array([channel.shape == required_shape]).all(), \ "Superoperator dimension must be the " + \ "subsystem dimension squared, given: " \ + repr(channel.shape) # Ensure mask is an array: mask = array(mask) if reference: return _subsystem_apply_reference(state, channel, mask) if state.type == 'oper': return _subsystem_apply_dm(state, channel, mask) else: return _subsystem_apply_ket(state, channel, mask)
def ode2es(L, rho0): """Creates an exponential series that describes the time evolution for the initial density matrix (or state vector) `rho0`, given the Liouvillian (or Hamiltonian) `L`. .. deprecated:: 4.6.0 :obj:`~ode2es` will be removed in QuTiP 5. Please use :obj:`Qobj.eigenstates` to get the eigenstates and -values, and use :obj:`~QobjEvo` for general time-dependence. Parameters ---------- L : qobj Liouvillian of the system. rho0 : qobj Initial state vector or density matrix. Returns ------- eseries : :class:`qutip.eseries` ``eseries`` represention of the system dynamics. """ if issuper(L): # check initial state if isket(rho0): # Got a wave function as initial state: convert to density matrix. rho0 = rho0 * rho0.dag() # check if state is below error threshold if abs(rho0.full()).sum() < 1e-10 + 1e-24: # enforce zero operator return eseries(qzero(rho0.dims[0])) w, v = L.eigenstates() v = np.hstack([ket.full() for ket in v]) # w[i] = eigenvalue i # v[:,i] = eigenvector i rlen = np.prod(rho0.shape) r0 = mat2vec(rho0.full()) v0 = la.solve(v, r0) vv = v * sp.spdiags(v0.T, 0, rlen, rlen) out = None for i in range(rlen): qo = Qobj(vec2mat(vv[:, i]), dims=rho0.dims, shape=rho0.shape) if out: out += eseries(qo, w[i]) else: out = eseries(qo, w[i]) elif isoper(L): if not isket(rho0): raise TypeError('Second argument must be a ket if first' + 'is a Hamiltonian.') # check if state is below error threshold if abs(rho0.full()).sum() < 1e-5 + 1e-20: # enforce zero operator dims = rho0.dims return eseries( Qobj(sp.csr_matrix((dims[0][0], dims[1][0]), dtype=complex))) w, v = L.eigenstates() v = np.hstack([ket.full() for ket in v]) # w[i] = eigenvalue i # v[:,i] = eigenvector i rlen = np.prod(rho0.shape) r0 = rho0.full() v0 = la.solve(v, r0) vv = v * sp.spdiags(v0.T, 0, rlen, rlen) out = None for i in range(rlen): qo = Qobj(np.array(vv[:, i]).T, dims=rho0.dims, shape=rho0.shape) if out: out += eseries(qo, -1.0j * w[i]) else: out = eseries(qo, -1.0j * w[i]) else: raise TypeError('First argument must be a Hamiltonian or Liouvillian.') return estidy(out)
def subsystem_apply(state, channel, mask, reference=False): """ Returns the result of applying the propagator `channel` to the subsystems indicated in `mask`, which comprise the density operator `state`. Parameters ---------- state : :class:`qutip.qobj` A density matrix or ket. channel : :class:`qutip.qobj` A propagator, either an `oper` or `super`. mask : *list* / *array* A mask that selects which subsystems should be subjected to the channel. reference : bool Decides whether explicit Kraus map should be used to evaluate action of channel. Returns ------- rho_out: :class:`qutip.qobj` A density matrix with the selected subsystems transformed according to the specified channel. """ # TODO: Include sparse/dense methods a la partial_transpose. # ---Sanity Checks---# # state must be a ket or density matrix, channel must be a propagator. assert state.type == 'ket' or state.type == 'oper', "Input state must be\ a ket or oper, given: " + repr(state.type) assert channel.type == 'super' or channel.type == 'oper', "Input channel \ must be a super or oper, given: " + repr(channel.type) # Since there's only one channel, all affected subsystems must have # the same dimensions: aff_subs_dim_ar = transpose(array(state.dims))[array(mask)] assert all([(aff_subs_dim_ar[j] == aff_subs_dim_ar[0]).all() for j in range(len(aff_subs_dim_ar))]), \ "Affected subsystems must have the same dimension. Given:" +\ repr(aff_subs_dim_ar) # If the map is on the Hilbert space, it must have the same dimension # as the affected subsystem. If it is on the Liouville space, it must # exist on a space as large as the square of the Hilbert dimension. if issuper(channel): required_shape = list(map(lambda x: x ** 2, aff_subs_dim_ar[0])) else: required_shape = aff_subs_dim_ar[0] assert array([channel.shape == required_shape]).all(), \ "Superoperator dimension must be the " + \ "subsystem dimension squared, given: " \ + repr(channel.shape) # Ensure mask is an array: mask = array(mask) if reference: return _subsystem_apply_reference(state, channel, mask) if state.type == 'oper': return _subsystem_apply_dm(state, channel, mask) else: return _subsystem_apply_ket(state, channel, mask)
def steadystate(A, c_op_list=[], **kwargs): """Calculates the steady state for quantum evolution subject to the supplied Hamiltonian or Liouvillian operator and (if given a Hamiltonian) a list of collapse operators. If the user passes a Hamiltonian then it, along with the list of collapse operators, will be converted into a Liouvillian operator in Lindblad form. Parameters ---------- A : qobj A Hamiltonian or Liouvillian operator. c_op_list : list A list of collapse operators. method : str {'direct', 'iterative', 'iterative-bicg', 'svd', 'power'} Method for solving the underlying linear equation. Direct solver 'direct' (default), iterative GMRES method 'iterative', iterative method BICGSTAB 'iterative-bicg', SVD 'svd' (dense), or inverse-power method 'power'. sparse : bool, {True, False} Solve for the steady state using sparse algorithms. If set to False, the underlying Liouvillian operator will be converted into a dense matrix. Use only for 'smaller' systems. use_rcm : bool, {True, False} Use reverse Cuthill-Mckee reordering to minimize fill-in in the LU factorization of the Liouvillian. use_umfpack : bool {False, True} Use umfpack solver instead of SuperLU. For SciPy 0.14+, this option requires installing scikits.umfpack. maxiter : int, optional Maximum number of iterations to perform if using an iterative method such as 'iterative' (default=1000), or 'power' (default=10). tol : float, optional, default=1e-5 Tolerance used for terminating solver solution when using iterative solvers. use_precond : bool optional, default = True ITERATIVE ONLY. Use an incomplete sparse LU decomposition as a preconditioner for the 'iterative' GMRES and BICG solvers. Speeds up convergence time by orders of magnitude in many cases. M : {sparse matrix, dense matrix, LinearOperator}, optional Preconditioner for A. The preconditioner should approximate the inverse of A. Effective preconditioning dramatically improves the rate of convergence, for iterative methods. Does not affect other solvers. fill_factor : float, default=12 ITERATIVE ONLY. Specifies the fill ratio upper bound (>=1) of the iLU preconditioner. Lower values save memory at the cost of longer execution times and a possible singular factorization. drop_tol : float, default=1e-3 ITERATIVE ONLY. Sets the threshold for the magnitude of preconditioner elements that should be dropped. Can be reduced for a courser factorization at the cost of an increased number of iterations, and a possible singular factorization. diag_pivot_thresh : float, default=None ITERATIVE ONLY. Sets the threshold between [0,1] for which diagonal elements are considered acceptable pivot points when using a preconditioner. A value of zero forces the pivot to be the diagonal element. Returns ------- dm : qobj Steady state density matrix. Notes ----- The SVD method works only for dense operators (i.e. small systems). """ ss_args = _default_steadystate_args() for key in kwargs.keys(): if key in ss_args.keys(): ss_args[key]=kwargs[key] else: raise Exception('Invalid keyword argument passed to steadystate.') n_op = len(c_op_list) if isoper(A): if n_op == 0: raise TypeError('Cannot calculate the steady state for a ' + 'non-dissipative system ' + '(no collapse operators given)') else: A = liouvillian(A, c_op_list) if not issuper(A): raise TypeError('Solving for steady states requires ' + 'Liouvillian (super) operators') if ss_args['method'] == 'direct': if ss_args['sparse']: return _steadystate_direct_sparse(A, ss_args) else: return _steadystate_direct_dense(A) elif ss_args['method'] == 'iterative': return _steadystate_iterative(A, ss_args) elif ss_args['method'] == 'iterative-bicg': return _steadystate_iterative_bicg(A, ss_args) elif ss_args['method'] == 'svd': return _steadystate_svd_dense(A, ss_args) elif ss_args['method'] == 'power': return _steadystate_power(A, ss_args) else: raise ValueError('Invalid method argument for steadystate.')
def steadystate(A, c_op_list=[], **kwargs): """Calculates the steady state for quantum evolution subject to the supplied Hamiltonian or Liouvillian operator and (if given a Hamiltonian) a list of collapse operators. If the user passes a Hamiltonian then it, along with the list of collapse operators, will be converted into a Liouvillian operator in Lindblad form. Parameters ---------- A : qobj A Hamiltonian or Liouvillian operator. c_op_list : list A list of collapse operators. method : str {'direct', 'eigen', 'iterative-bicg', 'iterative-gmres', 'svd', 'power'} Method for solving the underlying linear equation. Direct LU solver 'direct' (default), sparse eigenvalue problem 'eigen', iterative GMRES method 'iterative-gmres', iterative LGMRES method 'iterative-lgmres', SVD 'svd' (dense), or inverse-power method 'power'. sparse : bool, optional, default=True Solve for the steady state using sparse algorithms. If set to False, the underlying Liouvillian operator will be converted into a dense matrix. Use only for 'smaller' systems. use_rcm : bool, optional, default=True Use reverse Cuthill-Mckee reordering to minimize fill-in in the LU factorization of the Liouvillian. use_wbm : bool, optional, default=False Use Weighted Bipartite Matching reordering to make the Liouvillian diagonally dominant. This is useful for iterative preconditioners only, and is set to ``True`` by default when finding a preconditioner. weight : float, optional Sets the size of the elements used for adding the unity trace condition to the linear solvers. This is set to the average abs value of the Liouvillian elements if not specified by the user. use_umfpack : bool {False, True} Use umfpack solver instead of SuperLU. For SciPy 0.14+, this option requires installing scikits.umfpack. maxiter : int, optional, default=10000 Maximum number of iterations to perform if using an iterative method. tol : float, optional, default=1e-9 Tolerance used for terminating solver solution when using iterative solvers. permc_spec : str, optional, default='COLAMD' Column ordering used internally by superLU for the 'direct' LU decomposition method. Options include 'COLAMD' and 'NATURAL'. If using RCM then this is set to 'NATURAL' automatically unless explicitly specified. use_precond : bool optional, default = True ITERATIVE ONLY. Use an incomplete sparse LU decomposition as a preconditioner for the 'iterative' GMRES and BICG solvers. Speeds up convergence time by orders of magnitude in many cases. M : {sparse matrix, dense matrix, LinearOperator}, optional Preconditioner for A. The preconditioner should approximate the inverse of A. Effective preconditioning dramatically improves the rate of convergence, for iterative methods only . If no preconditioner is given and ``use_precond=True``, then one is generated automatically. fill_factor : float, optional, default=10 ITERATIVE ONLY. Specifies the fill ratio upper bound (>=1) of the iLU preconditioner. Lower values save memory at the cost of longer execution times and a possible singular factorization. drop_tol : float, optional, default=1e-3 ITERATIVE ONLY. Sets the threshold for the magnitude of preconditioner elements that should be dropped. Can be reduced for a courser factorization at the cost of an increased number of iterations, and a possible singular factorization. diag_pivot_thresh : float, optional, default=None ITERATIVE ONLY. Sets the threshold between [0,1] for which diagonal elements are considered acceptable pivot points when using a preconditioner. A value of zero forces the pivot to be the diagonal element. ILU_MILU : str, optional, default='smilu_2' Selects the incomplete LU decomposition method algoithm used in creating the preconditoner. Should only be used by advanced users. Returns ------- dm : qobj Steady state density matrix. Notes ----- The SVD method works only for dense operators (i.e. small systems). """ ss_args = _default_steadystate_args() for key in kwargs.keys(): if key in ss_args.keys(): ss_args[key] = kwargs[key] else: raise Exception( "Invalid keyword argument '"+key+"' passed to steadystate.") # Set column perm to NATURAL if using RCM and not specified by user if ss_args['use_rcm'] and ('permc_spec' not in kwargs.keys()): ss_args['permc_spec'] = 'NATURAL' # Set use_wbm=True if using iterative solver with preconditioner and # not explicitly set to False by user if (ss_args['method'] in ['iterative-lgmres', 'iterative-gmres']) \ and ('use_wbm' not in kwargs.keys()): ss_args['use_wbm'] = True n_op = len(c_op_list) if isoper(A): if n_op == 0: raise TypeError('Cannot calculate the steady state for a ' + 'non-dissipative system ' + '(no collapse operators given)') else: A = liouvillian(A, c_op_list) if not issuper(A): raise TypeError('Solving for steady states requires ' + 'Liouvillian (super) operators') # Set weight parameter to avg abs val in L if not set explicitly if 'weight' not in kwargs.keys(): ss_args['weight'] = np.mean(np.abs(A.data.data.max())) if ss_args['method'] == 'direct': if ss_args['sparse']: return _steadystate_direct_sparse(A, ss_args) else: return _steadystate_direct_dense(A) elif ss_args['method'] == 'eigen': return _steadystate_eigen(A, ss_args) elif ss_args['method'] in ['iterative-gmres', 'iterative-lgmres']: return _steadystate_iterative(A, ss_args) elif ss_args['method'] == 'svd': return _steadystate_svd_dense(A, ss_args) elif ss_args['method'] == 'power': return _steadystate_power(A, ss_args) else: raise ValueError('Invalid method argument for steadystate.')
def _mesolve_func_td(L_func, rho0, tlist, c_op_list, e_ops, args, opt, progress_bar): """ Evolve the density matrix using an ODE solver with time dependent Hamiltonian. """ if debug: print(inspect.stack()[0][3]) # # check initial state # if isket(rho0): rho0 = ket2dm(rho0) # # construct liouvillian # new_args = None if len(c_op_list) > 0: L_data = liouvillian(None, c_op_list).data else: n, m = rho0.shape if issuper(rho0): L_data = sp.csr_matrix((n, m), dtype=complex) else: L_data = sp.csr_matrix((n ** 2, m ** 2), dtype=complex) if type(args) is dict: new_args = {} for key in args: if isinstance(args[key], Qobj): if isoper(args[key]): new_args[key] = ( -1j * (spre(args[key]) - spost(args[key]))) else: new_args[key] = args[key] else: new_args[key] = args[key] elif type(args) is list or type(args) is tuple: new_args = [] for arg in args: if isinstance(arg, Qobj): if isoper(arg): new_args.append((-1j * (spre(arg) - spost(arg))).data) else: new_args.append(arg.data) else: new_args.append(arg) if type(args) is tuple: new_args = tuple(new_args) else: if isinstance(args, Qobj): if isoper(args): new_args = (-1j * (spre(args) - spost(args))) else: new_args = args else: new_args = args # # setup integrator # initial_vector = mat2vec(rho0.full()).ravel('F') if issuper(rho0): if not opt.rhs_with_state: r = scipy.integrate.ode(_ode_super_func_td) else: r = scipy.integrate.ode(_ode_super_func_td_with_state) else: if not opt.rhs_with_state: r = scipy.integrate.ode(cy_ode_rho_func_td) else: r = scipy.integrate.ode(_ode_rho_func_td_with_state) r.set_integrator('zvode', method=opt.method, order=opt.order, atol=opt.atol, rtol=opt.rtol, nsteps=opt.nsteps, first_step=opt.first_step, min_step=opt.min_step, max_step=opt.max_step) r.set_initial_value(initial_vector, tlist[0]) r.set_f_params(L_data, L_func, new_args) # # call generic ODE code # return _generic_ode_solve(r, rho0, tlist, e_ops, opt, progress_bar)
def _mesolve_list_func_td(H_list, rho0, tlist, c_list, e_ops, args, opt, progress_bar): """ Internal function for solving the master equation. See mesolve for usage. """ if debug: print(inspect.stack()[0][3]) # # check initial state # if isket(rho0): rho0 = rho0 * rho0.dag() # # construct liouvillian in list-function format # L_list = [] if opt.rhs_with_state: constant_func = lambda x, y, z: 1.0 else: constant_func = lambda x, y: 1.0 # add all hamitonian terms to the lagrangian list for h_spec in H_list: if isinstance(h_spec, Qobj): h = h_spec h_coeff = constant_func elif isinstance(h_spec, list) and isinstance(h_spec[0], Qobj): h = h_spec[0] h_coeff = h_spec[1] else: raise TypeError("Incorrect specification of time-dependent " + "Hamiltonian (expected callback function)") if isoper(h): L_list.append([(-1j * (spre(h) - spost(h))).data, h_coeff, False]) elif issuper(h): L_list.append([h.data, h_coeff, False]) else: raise TypeError("Incorrect specification of time-dependent " + "Hamiltonian (expected operator or superoperator)") # add all collapse operators to the lagrangian list for c_spec in c_list: if isinstance(c_spec, Qobj): c = c_spec c_coeff = constant_func c_square = False elif isinstance(c_spec, list) and isinstance(c_spec[0], Qobj): c = c_spec[0] c_coeff = c_spec[1] c_square = True else: raise TypeError("Incorrect specification of time-dependent " + "collapse operators (expected callback function)") if isoper(c): cdc = c.dag() * c L_list.append([ liouvillian_fast(None, [c], data_only=True), c_coeff, c_square ]) elif issuper(c): L_list.append([c.data, c_coeff, c_square]) else: raise TypeError("Incorrect specification of time-dependent " + "collapse operators (expected operator or " + "superoperator)") # # setup integrator # initial_vector = mat2vec(rho0.full()).ravel() if opt.rhs_with_state: r = scipy.integrate.ode(drho_list_td_with_state) else: r = scipy.integrate.ode(drho_list_td) r.set_integrator('zvode', method=opt.method, order=opt.order, atol=opt.atol, rtol=opt.rtol, nsteps=opt.nsteps, first_step=opt.first_step, min_step=opt.min_step, max_step=opt.max_step) r.set_initial_value(initial_vector, tlist[0]) r.set_f_params(L_list, args) # # call generic ODE code # return _generic_ode_solve(r, rho0, tlist, e_ops, opt, progress_bar)
def _steadystate_power(L, ss_args): """ Inverse power method for steady state solving. """ ss_args['info'].pop('weight', None) if settings.debug: logger.debug('Starting iterative inverse-power method solver.') tol = ss_args['tol'] mtol = ss_args['mtol'] if mtol is None: mtol = max(0.1 * tol, 1e-15) maxiter = ss_args['maxiter'] use_solver(assumeSortedIndices=True) rhoss = Qobj() sflag = issuper(L) if sflag: rhoss.dims = L.dims[0] else: rhoss.dims = [L.dims[0], 1] n = L.shape[0] # Build Liouvillian if ss_args['solver'] == 'mkl' and ss_args['method'] == 'power': has_mkl = 1 else: has_mkl = 0 L, perm, perm2, rev_perm, ss_args = _steadystate_power_liouvillian( L, ss_args, has_mkl) orig_nnz = L.nnz # start with all ones as RHS v = np.ones(n, dtype=complex) if ss_args['use_rcm']: v = v[np.ix_(perm2, )] # Do preconditioning if ss_args['solver'] == 'scipy': if ss_args['M'] is None and ss_args['use_precond'] and \ ss_args['method'] in ['power-gmres', 'power-lgmres', 'power-bicgstab']: ss_args['M'], ss_args = _iterative_precondition( L, int(np.sqrt(n)), ss_args) if ss_args['M'] is None: warnings.warn("Preconditioning failed. Continuing without.", UserWarning) ss_iters = {'iter': 0} def _iter_count(r): ss_iters['iter'] += 1 return _power_start = time.time() # Get LU factors if ss_args['method'] == 'power': if ss_args['solver'] == 'mkl': lu = mkl_splu(L, max_iter_refine=ss_args['max_iter_refine'], scaling_vectors=ss_args['scaling_vectors'], weighted_matching=ss_args['weighted_matching']) else: lu = splu(L, permc_spec=ss_args['permc_spec'], diag_pivot_thresh=ss_args['diag_pivot_thresh'], options=dict(ILU_MILU=ss_args['ILU_MILU'])) if settings.debug and _scipy_check: L_nnz = lu.L.nnz U_nnz = lu.U.nnz logger.debug('L NNZ: %i ; U NNZ: %i' % (L_nnz, U_nnz)) logger.debug('Fill factor: %f' % ((L_nnz + U_nnz) / orig_nnz)) it = 0 # FIXME: These atol keyword except checks can be removed once scipy 1.1 # is a minimum requirement while (la.norm(L * v, np.inf) > tol) and (it < maxiter): check = 0 if ss_args['method'] == 'power': v = lu.solve(v) elif ss_args['method'] == 'power-gmres': try: v, check = gmres(L, v, tol=mtol, atol=ss_args['matol'], M=ss_args['M'], x0=ss_args['x0'], restart=ss_args['restart'], maxiter=ss_args['maxiter'], callback=_iter_count) except TypeError as e: if "unexpected keyword argument 'atol'" in str(e): v, check = gmres(L, v, tol=mtol, M=ss_args['M'], x0=ss_args['x0'], restart=ss_args['restart'], maxiter=ss_args['maxiter'], callback=_iter_count) elif ss_args['method'] == 'power-lgmres': try: v, check = lgmres(L, v, tol=mtol, atol=ss_args['matol'], M=ss_args['M'], x0=ss_args['x0'], maxiter=ss_args['maxiter'], callback=_iter_count) except TypeError as e: if "unexpected keyword argument 'atol'" in str(e): v, check = lgmres(L, v, tol=mtol, M=ss_args['M'], x0=ss_args['x0'], maxiter=ss_args['maxiter'], callback=_iter_count) elif ss_args['method'] == 'power-bicgstab': try: v, check = bicgstab(L, v, tol=mtol, atol=ss_args['matol'], M=ss_args['M'], x0=ss_args['x0'], maxiter=ss_args['maxiter'], callback=_iter_count) except TypeError as e: if "unexpected keyword argument 'atol'" in str(e): v, check = bicgstab(L, v, tol=mtol, M=ss_args['M'], x0=ss_args['x0'], maxiter=ss_args['maxiter'], callback=_iter_count) else: raise Exception("Invalid iterative solver method.") if check > 0: raise Exception("{} failed to find solution in " "{} iterations.".format(ss_args['method'], check)) if check < 0: raise Exception("Breakdown in {}".format(ss_args['method'])) v = v / la.norm(v, np.inf) it += 1 if ss_args['method'] == 'power' and ss_args['solver'] == 'mkl': lu.delete() if ss_args['return_info']: ss_args['info']['max_iter_refine'] = ss_args['max_iter_refine'] ss_args['info']['scaling_vectors'] = ss_args['scaling_vectors'] ss_args['info']['weighted_matching'] = ss_args['weighted_matching'] if it >= maxiter: raise Exception('Failed to find steady state after ' + str(maxiter) + ' iterations') _power_end = time.time() ss_args['info']['solution_time'] = _power_end - _power_start ss_args['info']['iterations'] = it if ss_args['return_info']: ss_args['info']['residual_norm'] = la.norm(L * v, np.inf) if settings.debug: logger.debug('Number of iterations: %i' % it) if ss_args['use_rcm']: v = v[np.ix_(rev_perm, )] # normalise according to type of problem if sflag: trow = v[::rhoss.shape[0] + 1] data = v / np.sum(trow) else: data = data / la.norm(v) data = dense2D_to_fastcsr_fmode(vec2mat(data), rhoss.shape[0], rhoss.shape[0]) rhoss.data = 0.5 * (data + data.H) rhoss.isherm = True if ss_args['return_info']: return rhoss, ss_args['info'] else: return rhoss
def _mesolve_list_str_td(H_list, rho0, tlist, c_list, e_ops, args, opt, progress_bar): """ Internal function for solving the master equation. See mesolve for usage. """ if debug: print(inspect.stack()[0][3]) # # check initial state: must be a density matrix # if isket(rho0): rho0 = rho0 * rho0.dag() # # construct liouvillian # Lconst = 0 Ldata = [] Linds = [] Lptrs = [] Lcoeff = [] # loop over all hamiltonian terms, convert to superoperator form and # add the data of sparse matrix representation to for h_spec in H_list: if isinstance(h_spec, Qobj): h = h_spec if isoper(h): Lconst += -1j * (spre(h) - spost(h)) elif issuper(h): Lconst += h else: raise TypeError("Incorrect specification of time-dependent " + "Hamiltonian (expected operator or " + "superoperator)") elif isinstance(h_spec, list): h = h_spec[0] h_coeff = h_spec[1] if isoper(h): L = -1j * (spre(h) - spost(h)) elif issuper(h): L = h else: raise TypeError("Incorrect specification of time-dependent " + "Hamiltonian (expected operator or " + "superoperator)") Ldata.append(L.data.data) Linds.append(L.data.indices) Lptrs.append(L.data.indptr) Lcoeff.append(h_coeff) else: raise TypeError("Incorrect specification of time-dependent " + "Hamiltonian (expected string format)") # loop over all collapse operators for c_spec in c_list: if isinstance(c_spec, Qobj): c = c_spec if isoper(c): cdc = c.dag() * c Lconst += spre(c) * spost(c.dag()) - 0.5 * spre(cdc) \ - 0.5 * spost(cdc) elif issuper(c): Lconst += c else: raise TypeError("Incorrect specification of time-dependent " + "Liouvillian (expected operator or " + "superoperator)") elif isinstance(c_spec, list): c = c_spec[0] c_coeff = c_spec[1] if isoper(c): cdc = c.dag() * c L = spre(c) * spost(c.dag()) - 0.5 * spre(cdc) \ - 0.5 * spost(cdc) c_coeff = "(" + c_coeff + ")**2" elif issuper(c): L = c else: raise TypeError("Incorrect specification of time-dependent " + "Liouvillian (expected operator or " + "superoperator)") Ldata.append(L.data.data) Linds.append(L.data.indices) Lptrs.append(L.data.indptr) Lcoeff.append(c_coeff) else: raise TypeError("Incorrect specification of time-dependent " + "collapse operators (expected string format)") # add the constant part of the lagrangian if Lconst != 0: Ldata.append(Lconst.data.data) Linds.append(Lconst.data.indices) Lptrs.append(Lconst.data.indptr) Lcoeff.append("1.0") # the total number of liouvillian terms (hamiltonian terms + # collapse operators) n_L_terms = len(Ldata) # # setup ode args string: we expand the list Ldata, Linds and Lptrs into # and explicit list of parameters # string_list = [] for k in range(n_L_terms): string_list.append("Ldata[%d], Linds[%d], Lptrs[%d]" % (k, k, k)) for name, value in args.items(): string_list.append(str(value)) parameter_string = ",".join(string_list) # # generate and compile new cython code if necessary # if not opt.rhs_reuse or odeconfig.tdfunc is None: if opt.rhs_filename is None: odeconfig.tdname = "rhs" + str(odeconfig.cgen_num) else: odeconfig.tdname = opt.rhs_filename cgen = Codegen(h_terms=n_L_terms, h_tdterms=Lcoeff, args=args, odeconfig=odeconfig) cgen.generate(odeconfig.tdname + ".pyx") code = compile('from ' + odeconfig.tdname + ' import cyq_td_ode_rhs', '<string>', 'exec') exec(code, globals()) odeconfig.tdfunc = cyq_td_ode_rhs # # setup integrator # initial_vector = mat2vec(rho0.full()).ravel() r = scipy.integrate.ode(odeconfig.tdfunc) r.set_integrator('zvode', method=opt.method, order=opt.order, atol=opt.atol, rtol=opt.rtol, nsteps=opt.nsteps, first_step=opt.first_step, min_step=opt.min_step, max_step=opt.max_step) r.set_initial_value(initial_vector, tlist[0]) code = compile('r.set_f_params(' + parameter_string + ')', '<string>', 'exec') exec(code) # # call generic ODE code # return _generic_ode_solve(r, rho0, tlist, e_ops, opt, progress_bar)
def ode2es(L, rho0): """Creates an exponential series that describes the time evolution for the initial density matrix (or state vector) `rho0`, given the Liouvillian (or Hamiltonian) `L`. Parameters ---------- L : qobj Liouvillian of the system. rho0 : qobj Initial state vector or density matrix. Returns ------- eseries : :class:`qutip.eseries` ``eseries`` represention of the system dynamics. """ if issuper(L): # check initial state if isket(rho0): # Got a wave function as initial state: convert to density matrix. rho0 = rho0 * rho0.dag() w, v = L.eigenstates() v = np.hstack([ket.full() for ket in v]) # w[i] = eigenvalue i # v[:,i] = eigenvector i rlen = np.prod(rho0.shape) r0 = mat2vec(rho0.full()) v0 = la.solve(v, r0) vv = v * sp.spdiags(v0.T, 0, rlen, rlen) out = None for i in range(rlen): qo = Qobj(vec2mat(vv[:, i]), dims=rho0.dims, shape=rho0.shape) if out: out += eseries(qo, w[i]) else: out = eseries(qo, w[i]) elif isoper(L): if not isket(rho0): raise TypeError("Second argument must be a ket if first" + "is a Hamiltonian.") w, v = L.eigenstates() v = np.hstack([ket.full() for ket in v]) # w[i] = eigenvalue i # v[:,i] = eigenvector i rlen = np.prod(rho0.shape) r0 = rho0.full() v0 = la.solve(v, r0) vv = v * sp.spdiags(v0.T, 0, rlen, rlen) out = None for i in range(rlen): qo = Qobj(np.matrix(vv[:, i]).T, dims=rho0.dims, shape=rho0.shape) if out: out += eseries(qo, -1.0j * w[i]) else: out = eseries(qo, -1.0j * w[i]) else: raise TypeError("First argument must be a Hamiltonian or Liouvillian.") return estidy(out)
def steadystate(A, c_op_list=[], method='direct', sparse=True, use_umfpack=True, maxiter=5000, tol=1e-5, use_precond=True, perm_method='AUTO', drop_tol=1e-1, diag_pivot_thresh=0.33, verbose=False): """Calculates the steady state for the evolution subject to the supplied Hamiltonian or Liouvillian operator and (if given a Hamiltonian) a list of collapse operators. If the user passes a Hamiltonian then it, along with the list of collapse operators, will be converted into a Liouvillian operator in Lindblad form. Parameters ---------- A : qobj A Hamiltonian or Liouvillian operator. c_op_list : list A list of collapse operators. method : str {'direct', 'iterative', 'iterative-bicg', 'lu', 'svd', 'power'} Method for solving the underlying linear equation. Direct solver 'direct' (default), iterative LGMRES method 'iterative', iterative method BICG 'iterative-bicg', LU decomposition 'lu', SVD 'svd' (dense), or inverse-power method 'power'. sparse : bool default=True Solve for the steady state using sparse algorithms. If set to False, the underlying Liouvillian operator will be converted into a dense matrix. Use only for 'smaller' systems. use_umfpack : bool optional, default = True Use the UMFpack backend for the direct solver 'direct' or Power method 'power. If 'False', the solver uses the SuperLU backend. This option does not affect the other methods. Used only when sparse=True. maxiter : int optional Maximum number of iterations to perform if using an iterative method such as 'iterative' (default=5000), or 'power' (default=10). tol : float optional, default=1e-5 Tolerance used for terminating solver solution when using iterative solvers. use_precond : bool optional, default = True ITERATIVE ONLY. Use an incomplete sparse LU decomposition as a preconditioner for the 'iterative' LGMRES and BICG solvers. Speeds up convergence time by orders of magnitude in many cases. perm_method : str {'AUTO', 'AUTO-BREAK', 'COLAMD', 'MMD_ATA', 'NATURAL'} ITERATIVE ONLY. Sets the method for column ordering the incomplete LU preconditioner used by the 'iterative' method. When set to 'AUTO' (default), the solver will attempt to precondition the system using 'COLAMD'. If this fails, the solver will use no preconditioner. Using 'AUTO-BREAK' will cause the solver to issue an exception and stop if the 'COLAMD' method fails. drop_tol : float default=1e-1 ITERATIVE ONLY. Sets the threshold for the magnitude of preconditioner elements that should be dropped. diag_pivot_thresh : float default=0.33 ITERATIVE ONLY. Sets the threshold for which diagonal elements are considered acceptable pivot points when using a preconditioner. verbose : bool default=False Flag for printing out detailed information on the steady state solver. Returns ------- dm : qobj Steady state density matrix. Notes ----- The SVD method works only for dense operators (i.e. small systems). Setting use_umfpack=True (default) may result in 'out of memory' errors if your system size becomes to large. """ n_op = len(c_op_list) if isoper(A): if n_op == 0: raise TypeError('Cannot calculate the steady state for a ' + 'non-dissipative system ' + '(no collapse operators given)') else: A = liouvillian_fast(A, c_op_list) if not issuper(A): raise TypeError('Solving for steady states requires ' + 'Liouvillian (super) operators') if method == 'direct': if sparse: return _steadystate_direct_sparse(A, use_umfpack=use_umfpack, verbose=verbose) else: return _steadystate_direct_dense(A, verbose=verbose) elif method == 'iterative': return _steadystate_iterative(A, tol=tol, use_precond=use_precond, maxiter=maxiter, perm_method=perm_method, drop_tol=drop_tol, verbose=verbose, diag_pivot_thresh=diag_pivot_thresh) elif method == 'iterative-bicg': return _steadystate_iterative_bicg(A, tol=tol, use_precond=use_precond, maxiter=maxiter, perm_method=perm_method, drop_tol=drop_tol, verbose=verbose, diag_pivot_thresh=diag_pivot_thresh) elif method == 'lu': return _steadystate_lu(A, verbose=verbose) elif method == 'svd': return _steadystate_svd_dense(A, atol=1e-12, rtol=0, all_steadystates=False, verbose=verbose) elif method == 'power': return _steadystate_power(A, maxiter=10, tol=tol, itertol=tol, use_umfpack=use_umfpack, verbose=verbose) else: raise ValueError('Invalid method argument for steadystate.')