def mcsolve(H, psi0, tlist, c_ops, e_ops, ntraj=None, args={}, options=None, progress_bar=True, map_func=None, map_kwargs=None): """Monte Carlo evolution of a state vector :math:`|\psi \\rangle` for a given Hamiltonian and sets of collapse operators, and possibly, operators for calculating expectation values. Options for the underlying ODE solver are given by the Options class. mcsolve supports time-dependent Hamiltonians and collapse operators using either Python functions of strings to represent time-dependent coefficients. Note that, the system Hamiltonian MUST have at least one constant term. As an example of a time-dependent problem, consider a Hamiltonian with two terms ``H0`` and ``H1``, where ``H1`` is time-dependent with coefficient ``sin(w*t)``, and collapse operators ``C0`` and ``C1``, where ``C1`` is time-dependent with coeffcient ``exp(-a*t)``. Here, w and a are constant arguments with values ``W`` and ``A``. Using the Python function time-dependent format requires two Python functions, one for each collapse coefficient. Therefore, this problem could be expressed as:: def H1_coeff(t,args): return sin(args['w']*t) def C1_coeff(t,args): return exp(-args['a']*t) H = [H0, [H1, H1_coeff]] c_ops = [C0, [C1, C1_coeff]] args={'a': A, 'w': W} or in String (Cython) format we could write:: H = [H0, [H1, 'sin(w*t)']] c_ops = [C0, [C1, 'exp(-a*t)']] args={'a': A, 'w': W} Constant terms are preferably placed first in the Hamiltonian and collapse operator lists. Parameters ---------- H : :class:`qutip.Qobj` System Hamiltonian. psi0 : :class:`qutip.Qobj` Initial state vector tlist : array_like Times at which results are recorded. ntraj : int Number of trajectories to run. c_ops : array_like single collapse operator or ``list`` or ``array`` of collapse operators. e_ops : array_like single operator or ``list`` or ``array`` of operators for calculating expectation values. args : dict Arguments for time-dependent Hamiltonian and collapse operator terms. options : Options Instance of ODE solver options. progress_bar: BaseProgressBar Optional instance of BaseProgressBar, or a subclass thereof, for showing the progress of the simulation. Set to None to disable the progress bar. map_func: function A map function for managing the calls to the single-trajactory solver. map_kwargs: dictionary Optional keyword arguments to the map_func function. Returns ------- results : :class:`qutip.solver.Result` Object storing all results from the simulation. .. note:: It is possible to reuse the random number seeds from a previous run of the mcsolver by passing the output Result object seeds via the Options class, i.e. Options(seeds=prev_result.seeds). """ if debug: print(inspect.stack()[0][3]) if options is None: options = Options() if ntraj is None: ntraj = options.ntraj config.map_func = map_func if map_func is not None else parallel_map config.map_kwargs = map_kwargs if map_kwargs is not None else {} if not psi0.isket: raise Exception("Initial state must be a state vector.") 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 config.options = options if progress_bar: if progress_bar is True: config.progress_bar = TextProgressBar() else: config.progress_bar = progress_bar else: config.progress_bar = BaseProgressBar() # set num_cpus to the value given in qutip.settings if none in Options if not config.options.num_cpus: config.options.num_cpus = qutip.settings.num_cpus if config.options.num_cpus == 1: # fallback on serial_map if num_cpu == 1, since there is no # benefit of starting multiprocessing in this case config.map_func = serial_map # set initial value data if options.tidy: config.psi0 = psi0.tidyup(options.atol).full().ravel() else: config.psi0 = psi0.full().ravel() config.psi0_dims = psi0.dims config.psi0_shape = psi0.shape # set options on ouput states if config.options.steady_state_average: config.options.average_states = True # set general items config.tlist = tlist if isinstance(ntraj, (list, np.ndarray)): config.ntraj = np.sort(ntraj)[-1] else: config.ntraj = ntraj # set norm finding constants config.norm_tol = options.norm_tol config.norm_steps = options.norm_steps # convert array based time-dependence to string format H, c_ops, args = _td_wrap_array_str(H, c_ops, args, tlist) # SETUP ODE DATA IF NONE EXISTS OR NOT REUSING # -------------------------------------------- if not options.rhs_reuse or not config.tdfunc: # reset config collapse and time-dependence flags to default values config.soft_reset() # check for type of time-dependence (if any) time_type, h_stuff, c_stuff = _td_format_check(H, c_ops, 'mc') c_terms = len(c_stuff[0]) + len(c_stuff[1]) + len(c_stuff[2]) # set time_type for use in multiprocessing config.tflag = time_type # check for collapse operators if c_terms > 0: config.cflag = 1 else: config.cflag = 0 # Configure data _mc_data_config(H, psi0, h_stuff, c_ops, c_stuff, args, e_ops, options, config) # compile and load cython functions if necessary _mc_func_load(config) else: # setup args for new parameters when rhs_reuse=True and tdfunc is given # string based if config.tflag in [1, 10, 11]: if any(args): config.c_args = [] arg_items = list(args.items()) for k in range(len(arg_items)): config.c_args.append(arg_items[k][1]) # function based elif config.tflag in [2, 3, 20, 22]: config.h_func_args = args # load monte carlo class mc = _MC(config) # Run the simulation mc.run() # Remove RHS cython file if necessary if not options.rhs_reuse and config.tdname: _cython_build_cleanup(config.tdname) # AFTER MCSOLVER IS DONE # ---------------------- # Store results in the Result object output = Result() output.solver = 'mcsolve' output.seeds = config.options.seeds # state vectors if (mc.psi_out is not None and config.options.average_states and config.cflag and ntraj != 1): output.states = parfor(_mc_dm_avg, mc.psi_out.T) elif mc.psi_out is not None: output.states = mc.psi_out # expectation values if (mc.expect_out is not None and config.cflag and config.options.average_expect): # averaging if multiple trajectories if isinstance(ntraj, int): output.expect = [np.mean(np.array([mc.expect_out[nt][op] for nt in range(ntraj)], dtype=object), axis=0) for op in range(config.e_num)] elif isinstance(ntraj, (list, np.ndarray)): output.expect = [] for num in ntraj: expt_data = np.mean(mc.expect_out[:num], axis=0) data_list = [] if any([not op.isherm for op in e_ops]): for k in range(len(e_ops)): if e_ops[k].isherm: data_list.append(np.real(expt_data[k])) else: data_list.append(expt_data[k]) else: data_list = [data for data in expt_data] output.expect.append(data_list) else: # no averaging for single trajectory or if average_expect flag # (Options) is off if mc.expect_out is not None: output.expect = mc.expect_out # simulation parameters output.times = config.tlist output.num_expect = config.e_num output.num_collapse = config.c_num output.ntraj = config.ntraj output.col_times = mc.collapse_times_out output.col_which = mc.which_op_out if e_ops_dict: output.expect = {e: output.expect[n] for n, e in enumerate(e_ops_dict.keys())} return output
def mcsolve_f90(H, psi0, tlist, c_ops, e_ops, ntraj=None, options=Options(), sparse_dms=True, serial=False, ptrace_sel=[], calc_entropy=False): """ Monte-Carlo wave function solver with fortran 90 backend. Usage is identical to qutip.mcsolve, for problems without explicit time-dependence, and with some optional input: Parameters ---------- H : qobj System Hamiltonian. psi0 : qobj Initial state vector tlist : array_like Times at which results are recorded. ntraj : int Number of trajectories to run. c_ops : array_like ``list`` or ``array`` of collapse operators. e_ops : array_like ``list`` or ``array`` of operators for calculating expectation values. options : Options Instance of solver options. sparse_dms : boolean If averaged density matrices are returned, they will be stored as sparse (Compressed Row Format) matrices during computation if sparse_dms = True (default), and dense matrices otherwise. Dense matrices might be preferable for smaller systems. serial : boolean If True (default is False) the solver will not make use of the multiprocessing module, and simply run in serial. ptrace_sel: list This optional argument specifies a list of components to keep when returning a partially traced density matrix. This can be convenient for large systems where memory becomes a problem, but you are only interested in parts of the density matrix. calc_entropy : boolean If ptrace_sel is specified, calc_entropy=True will have the solver return the averaged entropy over trajectories in results.entropy. This can be interpreted as a measure of entanglement. See Phys. Rev. Lett. 93, 120408 (2004), Phys. Rev. A 86, 022310 (2012). Returns ------- results : Result Object storing all results from simulation. """ if ntraj is None: ntraj = options.ntraj if psi0.type != 'ket': raise Exception("Initial state must be a state vector.") config.options = options # set num_cpus to the value given in qutip.settings # if none in Options if not config.options.num_cpus: config.options.num_cpus = qutip.settings.num_cpus # set initial value data if options.tidy: config.psi0 = psi0.tidyup(options.atol).full() else: config.psi0 = psi0.full() config.psi0_dims = psi0.dims config.psi0_shape = psi0.shape # set general items config.tlist = tlist if isinstance(ntraj, (list, np.ndarray)): raise Exception("ntraj as list argument is not supported.") else: config.ntraj = ntraj # ntraj_list = [ntraj] # set norm finding constants config.norm_tol = options.norm_tol config.norm_steps = options.norm_steps if not options.rhs_reuse: config.soft_reset() # no time dependence config.tflag = 0 # check for collapse operators if len(c_ops) > 0: config.cflag = 1 else: config.cflag = 0 # Configure data _mc_data_config(H, psi0, [], c_ops, [], [], e_ops, options, config) # Load Monte Carlo class mc = _MC_class() # Set solver type if (options.method == 'adams'): mc.mf = 10 elif (options.method == 'bdf'): mc.mf = 22 else: if debug: print('Unrecognized method for ode solver, using "adams".') mc.mf = 10 # store ket and density matrix dims and shape for convenience mc.psi0_dims = psi0.dims mc.psi0_shape = psi0.shape mc.dm_dims = (psi0 * psi0.dag()).dims mc.dm_shape = (psi0 * psi0.dag()).shape # use sparse density matrices during computation? mc.sparse_dms = sparse_dms # run in serial? mc.serial_run = serial or (ntraj == 1) # are we doing a partial trace for returned states? mc.ptrace_sel = ptrace_sel if (ptrace_sel != []): if debug: print("ptrace_sel set to " + str(ptrace_sel)) print("We are using dense density matrices during computation " + "when performing partial trace. Setting sparse_dms = False") print("This feature is experimental.") mc.sparse_dms = False mc.dm_dims = psi0.ptrace(ptrace_sel).dims mc.dm_shape = psi0.ptrace(ptrace_sel).shape if (calc_entropy): if (ptrace_sel == []): if debug: print("calc_entropy = True, but ptrace_sel = []. Please set " + "a list of components to keep when calculating average" + " entropy of reduced density matrix in ptrace_sel. " + "Setting calc_entropy = False.") calc_entropy = False mc.calc_entropy = calc_entropy # construct output Result object output = Result() # Run mc.run() output.states = mc.sol.states output.expect = mc.sol.expect output.col_times = mc.sol.col_times output.col_which = mc.sol.col_which if (hasattr(mc.sol, 'entropy')): output.entropy = mc.sol.entropy output.solver = 'Fortran 90 Monte Carlo solver' # simulation parameters output.times = config.tlist output.num_expect = config.e_num output.num_collapse = config.c_num output.ntraj = config.ntraj return output
def _mc_data_config(H, psi0, h_stuff, c_ops, c_stuff, args, e_ops, options, config): """Creates the appropriate data structures for the monte carlo solver based on the given time-dependent, or indepdendent, format. """ if debug: print(inspect.stack()[0][3]) config.soft_reset() # take care of expectation values, if any if any(e_ops): config.e_num = len(e_ops) for op in e_ops: if isinstance(op, list): op = op[0] config.e_ops_data.append(op.data.data) config.e_ops_ind.append(op.data.indices) config.e_ops_ptr.append(op.data.indptr) config.e_ops_isherm.append(op.isherm) config.e_ops_data = np.array(config.e_ops_data) config.e_ops_ind = np.array(config.e_ops_ind) config.e_ops_ptr = np.array(config.e_ops_ptr) config.e_ops_isherm = np.array(config.e_ops_isherm) # take care of collapse operators, if any if any(c_ops): config.c_num = len(c_ops) for c_op in c_ops: if isinstance(c_op, list): c_op = c_op[0] n_op = c_op.dag() * c_op config.c_ops_data.append(c_op.data.data) config.c_ops_ind.append(c_op.data.indices) config.c_ops_ptr.append(c_op.data.indptr) # norm ops config.n_ops_data.append(n_op.data.data) config.n_ops_ind.append(n_op.data.indices) config.n_ops_ptr.append(n_op.data.indptr) # to array config.c_ops_data = np.array(config.c_ops_data) config.c_ops_ind = np.array(config.c_ops_ind) config.c_ops_ptr = np.array(config.c_ops_ptr) config.n_ops_data = np.array(config.n_ops_data) config.n_ops_ind = np.array(config.n_ops_ind) config.n_ops_ptr = np.array(config.n_ops_ptr) if config.tflag == 0: # CONSTANT H & C_OPS CODE # ----------------------- if config.cflag: config.c_const_inds = np.arange(len(c_ops)) for c_op in c_ops: n_op = c_op.dag() * c_op H -= 0.5j * \ n_op # combine Hamiltonian and collapse terms into one # construct Hamiltonian data structures if options.tidy: H = H.tidyup(options.atol) config.h_data = -1.0j * H.data.data config.h_ind = H.data.indices config.h_ptr = H.data.indptr elif config.tflag in [1, 10, 11]: # STRING BASED TIME-DEPENDENCE # ---------------------------- # take care of arguments for collapse operators, if any if any(args): for item in args.items(): config.c_args.append(item[1]) # constant Hamiltonian / string-type collapse operators if config.tflag == 1: H_inds = np.arange(1) H_tdterms = 0 len_h = 1 C_inds = np.arange(config.c_num) # find inds of time-dependent terms C_td_inds = np.array(c_stuff[2]) # find inds of constant terms C_const_inds = np.setdiff1d(C_inds, C_td_inds) # extract time-dependent coefficients (strings) C_tdterms = [c_ops[k][1] for k in C_td_inds] # store indicies of constant collapse terms config.c_const_inds = C_const_inds # store indicies of time-dependent collapse terms config.c_td_inds = C_td_inds for k in config.c_const_inds: H -= 0.5j * (c_ops[k].dag() * c_ops[k]) if options.tidy: H = H.tidyup(options.atol) config.h_data = [H.data.data] config.h_ind = [H.data.indices] config.h_ptr = [H.data.indptr] for k in config.c_td_inds: op = c_ops[k][0].dag() * c_ops[k][0] config.h_data.append(-0.5j * op.data.data) config.h_ind.append(op.data.indices) config.h_ptr.append(op.data.indptr) config.h_data = -1.0j * np.array(config.h_data) config.h_ind = np.array(config.h_ind) config.h_ptr = np.array(config.h_ptr) else: # string-type Hamiltonian & at least one string-type # collapse operator # ----------------- H_inds = np.arange(len(H)) # find inds of time-dependent terms H_td_inds = np.array(h_stuff[2]) # find inds of constant terms H_const_inds = np.setdiff1d(H_inds, H_td_inds) # extract time-dependent coefficients (strings or functions) H_tdterms = [H[k][1] for k in H_td_inds] # combine time-INDEPENDENT terms into one. H = np.array([np.sum(H[k] for k in H_const_inds)] + [H[k][0] for k in H_td_inds], dtype=object) len_h = len(H) H_inds = np.arange(len_h) # store indicies of time-dependent Hamiltonian terms config.h_td_inds = np.arange(1, len_h) # if there are any collapse operators if config.c_num > 0: if config.tflag == 10: # constant collapse operators config.c_const_inds = np.arange(config.c_num) for k in config.c_const_inds: H[0] -= 0.5j * (c_ops[k].dag() * c_ops[k]) C_inds = np.arange(config.c_num) C_tdterms = np.array([]) else: # some time-dependent collapse terms C_inds = np.arange(config.c_num) # find inds of time-dependent terms C_td_inds = np.array(c_stuff[2]) # find inds of constant terms C_const_inds = np.setdiff1d(C_inds, C_td_inds) C_tdterms = [c_ops[k][1] for k in C_td_inds] # extract time-dependent coefficients (strings) # store indicies of constant collapse terms config.c_const_inds = C_const_inds # store indicies of time-dependent collapse terms config.c_td_inds = C_td_inds for k in config.c_const_inds: H[0] -= 0.5j * (c_ops[k].dag() * c_ops[k]) else: # set empty objects if no collapse operators C_const_inds = np.arange(config.c_num) config.c_const_inds = np.arange(config.c_num) config.c_td_inds = np.array([]) C_tdterms = np.array([]) C_inds = np.array([]) # tidyup if options.tidy: H = np.array([H[k].tidyup(options.atol) for k in range(len_h)], dtype=object) # construct data sets config.h_data = [H[k].data.data for k in range(len_h)] config.h_ind = [H[k].data.indices for k in range(len_h)] config.h_ptr = [H[k].data.indptr for k in range(len_h)] for k in config.c_td_inds: config.h_data.append(-0.5j * config.n_ops_data[k]) config.h_ind.append(config.n_ops_ind[k]) config.h_ptr.append(config.n_ops_ptr[k]) config.h_data = -1.0j * np.array(config.h_data) config.h_ind = np.array(config.h_ind) config.h_ptr = np.array(config.h_ptr) # set execuatble code for collapse expectation values and spmv col_spmv_code = ("state = _cy_col_spmv_func(j, ODE.t, " + "config.c_ops_data[j], config.c_ops_ind[j], " + "config.c_ops_ptr[j], ODE.y") col_expect_code = ("for i in config.c_td_inds: " + "n_dp.append(_cy_col_expect_func(i, ODE.t, " + "config.n_ops_data[i], " + "config.n_ops_ind[i], " + "config.n_ops_ptr[i], ODE.y") for kk in range(len(config.c_args)): col_spmv_code += ",config.c_args[" + str(kk) + "]" col_expect_code += ",config.c_args[" + str(kk) + "]" col_spmv_code += ")" col_expect_code += "))" config.col_spmv_code = col_spmv_code config.col_expect_code = col_expect_code # setup ode args string config.string = "" data_range = range(len(config.h_data)) for k in data_range: config.string += ("config.h_data[" + str(k) + "], config.h_ind[" + str(k) + "], config.h_ptr[" + str(k) + "]") if k != data_range[-1]: config.string += "," # attach args to ode args string if len(config.c_args) > 0: for kk in range(len(config.c_args)): config.string += "," + "config.c_args[" + str(kk) + "]" name = "rhs" + str(os.getpid()) + str(config.cgen_num) config.tdname = name cgen = Codegen(H_inds, H_tdterms, config.h_td_inds, args, C_inds, C_tdterms, config.c_td_inds, type='mc', config=config) cgen.generate(name + ".pyx") elif config.tflag in [2, 20, 22]: # PYTHON LIST-FUNCTION BASED TIME-DEPENDENCE # ------------------------------------------ # take care of Hamiltonian if config.tflag == 2: # constant Hamiltonian, at least one function based collapse # operators H_inds = np.array([0]) H_tdterms = 0 len_h = 1 else: # function based Hamiltonian H_inds = np.arange(len(H)) H_td_inds = np.array(h_stuff[1]) H_const_inds = np.setdiff1d(H_inds, H_td_inds) config.h_funcs = np.array([H[k][1] for k in H_td_inds]) config.h_func_args = args Htd = np.array([H[k][0] for k in H_td_inds], dtype=object) config.h_td_inds = np.arange(len(Htd)) H = np.sum(H[k] for k in H_const_inds) # take care of collapse operators C_inds = np.arange(config.c_num) # find inds of time-dependent terms C_td_inds = np.array(c_stuff[1]) # find inds of constant terms C_const_inds = np.setdiff1d(C_inds, C_td_inds) # store indicies of constant collapse terms config.c_const_inds = C_const_inds # store indicies of time-dependent collapse terms config.c_td_inds = C_td_inds config.c_funcs = np.zeros(config.c_num, dtype=FunctionType) for k in config.c_td_inds: config.c_funcs[k] = c_ops[k][1] config.c_func_args = args # combine constant collapse terms with constant H and construct data for k in config.c_const_inds: H -= 0.5j * (c_ops[k].dag() * c_ops[k]) if options.tidy: H = H.tidyup(options.atol) Htd = np.array([Htd[j].tidyup(options.atol) for j in config.h_td_inds], dtype=object) # setup constant H terms data config.h_data = -1.0j * H.data.data config.h_ind = H.data.indices config.h_ptr = H.data.indptr # setup td H terms data config.h_td_data = np.array( [-1.0j * Htd[k].data.data for k in config.h_td_inds]) config.h_td_ind = np.array( [Htd[k].data.indices for k in config.h_td_inds]) config.h_td_ptr = np.array( [Htd[k].data.indptr for k in config.h_td_inds]) elif config.tflag == 3: # PYTHON FUNCTION BASED HAMILTONIAN # --------------------------------- # take care of Hamiltonian config.h_funcs = H config.h_func_args = args # take care of collapse operators config.c_const_inds = np.arange(config.c_num) config.c_td_inds = np.array([]) if len(config.c_const_inds) > 0: H = 0 for k in config.c_const_inds: H -= 0.5j * (c_ops[k].dag() * c_ops[k]) if options.tidy: H = H.tidyup(options.atol) config.h_data = -1.0j * H.data.data config.h_ind = H.data.indices config.h_ptr = H.data.indptr
def mcsolve(H, psi0, tlist, c_ops, e_ops, ntraj=None, args={}, options=Options()): """Monte-Carlo evolution of a state vector :math:`|\psi \\rangle` for a given Hamiltonian and sets of collapse operators, and possibly, operators for calculating expectation values. Options for the underlying ODE solver are given by the Options class. mcsolve supports time-dependent Hamiltonians and collapse operators using either Python functions of strings to represent time-dependent coefficients. Note that, the system Hamiltonian MUST have at least one constant term. As an example of a time-dependent problem, consider a Hamiltonian with two terms ``H0`` and ``H1``, where ``H1`` is time-dependent with coefficient ``sin(w*t)``, and collapse operators ``C0`` and ``C1``, where ``C1`` is time-dependent with coeffcient ``exp(-a*t)``. Here, w and a are constant arguments with values ``W`` and ``A``. Using the Python function time-dependent format requires two Python functions, one for each collapse coefficient. Therefore, this problem could be expressed as:: def H1_coeff(t,args): return sin(args['w']*t) def C1_coeff(t,args): return exp(-args['a']*t) H=[H0,[H1,H1_coeff]] c_op_list=[C0,[C1,C1_coeff]] args={'a':A,'w':W} or in String (Cython) format we could write:: H=[H0,[H1,'sin(w*t)']] c_op_list=[C0,[C1,'exp(-a*t)']] args={'a':A,'w':W} Constant terms are preferably placed first in the Hamiltonian and collapse operator lists. Parameters ---------- H : qobj System Hamiltonian. psi0 : qobj Initial state vector tlist : array_like Times at which results are recorded. ntraj : int Number of trajectories to run. c_ops : array_like single collapse operator or ``list`` or ``array`` of collapse operators. e_ops : array_like single operator or ``list`` or ``array`` of operators for calculating expectation values. args : dict Arguments for time-dependent Hamiltonian and collapse operator terms. options : Options Instance of ODE solver options. Returns ------- results : Result Object storing all results from simulation. """ if debug: print(inspect.stack()[0][3]) if ntraj is None: ntraj = options.ntraj if not psi0.isket: raise Exception("Initial state must be a state vector.") 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 config.options = options if isinstance(ntraj, list): config.progress_bar = TextProgressBar(max(ntraj)) else: config.progress_bar = TextProgressBar(ntraj) # set num_cpus to the value given in qutip.settings if none in Options if not config.options.num_cpus: config.options.num_cpus = qutip.settings.num_cpus # set initial value data if options.tidy: config.psi0 = psi0.tidyup(options.atol).full().ravel() else: config.psi0 = psi0.full().ravel() config.psi0_dims = psi0.dims config.psi0_shape = psi0.shape # set options on ouput states if config.options.steady_state_average: config.options.average_states = True # set general items config.tlist = tlist if isinstance(ntraj, (list, ndarray)): config.ntraj = sort(ntraj)[-1] else: config.ntraj = ntraj # set norm finding constants config.norm_tol = options.norm_tol config.norm_steps = options.norm_steps # convert array based time-dependence to string format H, c_ops, args = _td_wrap_array_str(H, c_ops, args, tlist) # ---------------------------------------------- # SETUP ODE DATA IF NONE EXISTS OR NOT REUSING # ---------------------------------------------- if (not options.rhs_reuse) or (not config.tdfunc): # reset config collapse and time-dependence flags to default values config.soft_reset() # check for type of time-dependence (if any) time_type, h_stuff, c_stuff = _td_format_check(H, c_ops, "mc") h_terms = len(h_stuff[0]) + len(h_stuff[1]) + len(h_stuff[2]) c_terms = len(c_stuff[0]) + len(c_stuff[1]) + len(c_stuff[2]) # set time_type for use in multiprocessing config.tflag = time_type # check for collapse operators if c_terms > 0: config.cflag = 1 else: config.cflag = 0 # Configure data _mc_data_config(H, psi0, h_stuff, c_ops, c_stuff, args, e_ops, options, config) # compile and load cython functions if necessary _mc_func_load(config) else: # setup args for new parameters when rhs_reuse=True and tdfunc is given # string based if config.tflag in array([1, 10, 11]): if any(args): config.c_args = [] arg_items = args.items() for k in range(len(args)): config.c_args.append(arg_items[k][1]) # function based elif config.tflag in array([2, 3, 20, 22]): config.h_func_args = args # load monte-carlo class mc = _MC_class(config) # RUN THE SIMULATION mc.run() # remove RHS cython file if necessary if not options.rhs_reuse and config.tdname: try: os.remove(config.tdname + ".pyx") except: pass # AFTER MCSOLVER IS DONE -------------------------------------- # ------- COLLECT AND RETURN OUTPUT DATA IN ODEDATA OBJECT -------------- output = Result() output.solver = "mcsolve" # state vectors if mc.psi_out is not None and config.options.average_states and config.cflag and ntraj != 1: output.states = parfor(_mc_dm_avg, mc.psi_out.T) elif mc.psi_out is not None: output.states = mc.psi_out # expectation values elif mc.expect_out is not None and config.cflag and config.options.average_expect: # averaging if multiple trajectories if isinstance(ntraj, int): output.expect = [mean([mc.expect_out[nt][op] for nt in range(ntraj)], axis=0) for op in range(config.e_num)] elif isinstance(ntraj, (list, ndarray)): output.expect = [] for num in ntraj: expt_data = mean(mc.expect_out[:num], axis=0) data_list = [] if any([not op.isherm for op in e_ops]): for k in range(len(e_ops)): if e_ops[k].isherm: data_list.append(np.real(expt_data[k])) else: data_list.append(expt_data[k]) else: data_list = [data for data in expt_data] output.expect.append(data_list) else: # no averaging for single trajectory or if average_states flag # (Options) is off if mc.expect_out is not None: output.expect = mc.expect_out # simulation parameters output.times = config.tlist output.num_expect = config.e_num output.num_collapse = config.c_num output.ntraj = config.ntraj output.col_times = mc.collapse_times_out output.col_which = mc.which_op_out if e_ops_dict: output.expect = {e: output.expect[n] for n, e in enumerate(e_ops_dict.keys())} return output
def mcsolve_f90(H, psi0, tlist, c_ops, e_ops, ntraj=None, options=Options(), sparse_dms=True, serial=False, ptrace_sel=[], calc_entropy=False): """ Monte-Carlo wave function solver with fortran 90 backend. Usage is identical to qutip.mcsolve, for problems without explicit time-dependence, and with some optional input: Parameters ---------- H : qobj System Hamiltonian. psi0 : qobj Initial state vector tlist : array_like Times at which results are recorded. ntraj : int Number of trajectories to run. c_ops : array_like ``list`` or ``array`` of collapse operators. e_ops : array_like ``list`` or ``array`` of operators for calculating expectation values. options : Options Instance of solver options. sparse_dms : boolean If averaged density matrices are returned, they will be stored as sparse (Compressed Row Format) matrices during computation if sparse_dms = True (default), and dense matrices otherwise. Dense matrices might be preferable for smaller systems. serial : boolean If True (default is False) the solver will not make use of the multiprocessing module, and simply run in serial. ptrace_sel: list This optional argument specifies a list of components to keep when returning a partially traced density matrix. This can be convenient for large systems where memory becomes a problem, but you are only interested in parts of the density matrix. calc_entropy : boolean If ptrace_sel is specified, calc_entropy=True will have the solver return the averaged entropy over trajectories in results.entropy. This can be interpreted as a measure of entanglement. See Phys. Rev. Lett. 93, 120408 (2004), Phys. Rev. A 86, 022310 (2012). Returns ------- results : Result Object storing all results from simulation. """ if ntraj is None: ntraj = options.ntraj if psi0.type != 'ket': raise Exception("Initial state must be a state vector.") config.options = options # set num_cpus to the value given in qutip.settings # if none in Options if not config.options.num_cpus: config.options.num_cpus = qutip.settings.num_cpus # set initial value data if options.tidy: config.psi0 = psi0.tidyup(options.atol).full() else: config.psi0 = psi0.full() config.psi0_dims = psi0.dims config.psi0_shape = psi0.shape # set general items config.tlist = tlist if isinstance(ntraj, (list, np.ndarray)): raise Exception("ntraj as list argument is not supported.") else: config.ntraj = ntraj # ntraj_list = [ntraj] # set norm finding constants config.norm_tol = options.norm_tol config.norm_steps = options.norm_steps if not options.rhs_reuse: config.soft_reset() # no time dependence config.tflag = 0 # check for collapse operators if len(c_ops) > 0: config.cflag = 1 else: config.cflag = 0 # Configure data _mc_data_config(H, psi0, [], c_ops, [], [], e_ops, options, config) # Load Monte Carlo class mc = _MC_class() # Set solver type if (options.method == 'adams'): mc.mf = 10 elif (options.method == 'bdf'): mc.mf = 22 else: if debug: print('Unrecognized method for ode solver, using "adams".') mc.mf = 10 # store ket and density matrix dims and shape for convenience mc.psi0_dims = psi0.dims mc.psi0_shape = psi0.shape mc.dm_dims = (psi0 * psi0.dag()).dims mc.dm_shape = (psi0 * psi0.dag()).shape # use sparse density matrices during computation? mc.sparse_dms = sparse_dms # run in serial? mc.serial_run = serial or (ntraj == 1) # are we doing a partial trace for returned states? mc.ptrace_sel = ptrace_sel if (ptrace_sel != []): if debug: print("ptrace_sel set to " + str(ptrace_sel)) print("We are using dense density matrices during computation " + "when performing partial trace. Setting sparse_dms = False") print("This feature is experimental.") mc.sparse_dms = False mc.dm_dims = psi0.ptrace(ptrace_sel).dims mc.dm_shape = psi0.ptrace(ptrace_sel).shape if (calc_entropy): if (ptrace_sel == []): if debug: print("calc_entropy = True, but ptrace_sel = []. Please set " + "a list of components to keep when calculating average" + " entropy of reduced density matrix in ptrace_sel. " + "Setting calc_entropy = False.") calc_entropy = False mc.calc_entropy = calc_entropy # construct output Result object output = Result() # Run mc.run() output.states = mc.sol.states output.expect = mc.sol.expect output.col_times = mc.sol.col_times output.col_which = mc.sol.col_which if (hasattr(mc.sol, 'entropy')): output.entropy = mc.sol.entropy output.solver = 'Fortran 90 Monte Carlo solver' # simulation parameters output.times = config.tlist output.num_expect = config.e_num output.num_collapse = config.c_num output.ntraj = config.ntraj return output
def mcsolve(H, psi0, tlist, c_ops, e_ops, ntraj=None, args={}, options=None, progress_bar=True, map_func=None, map_kwargs=None): """Monte Carlo evolution of a state vector :math:`|\psi \\rangle` for a given Hamiltonian and sets of collapse operators, and possibly, operators for calculating expectation values. Options for the underlying ODE solver are given by the Options class. mcsolve supports time-dependent Hamiltonians and collapse operators using either Python functions of strings to represent time-dependent coefficients. Note that, the system Hamiltonian MUST have at least one constant term. As an example of a time-dependent problem, consider a Hamiltonian with two terms ``H0`` and ``H1``, where ``H1`` is time-dependent with coefficient ``sin(w*t)``, and collapse operators ``C0`` and ``C1``, where ``C1`` is time-dependent with coeffcient ``exp(-a*t)``. Here, w and a are constant arguments with values ``W`` and ``A``. Using the Python function time-dependent format requires two Python functions, one for each collapse coefficient. Therefore, this problem could be expressed as:: def H1_coeff(t,args): return sin(args['w']*t) def C1_coeff(t,args): return exp(-args['a']*t) H = [H0, [H1, H1_coeff]] c_ops = [C0, [C1, C1_coeff]] args={'a': A, 'w': W} or in String (Cython) format we could write:: H = [H0, [H1, 'sin(w*t)']] c_ops = [C0, [C1, 'exp(-a*t)']] args={'a': A, 'w': W} Constant terms are preferably placed first in the Hamiltonian and collapse operator lists. Parameters ---------- H : :class:`qutip.Qobj` System Hamiltonian. psi0 : :class:`qutip.Qobj` Initial state vector tlist : array_like Times at which results are recorded. ntraj : int Number of trajectories to run. c_ops : array_like single collapse operator or ``list`` or ``array`` of collapse operators. e_ops : array_like single operator or ``list`` or ``array`` of operators for calculating expectation values. args : dict Arguments for time-dependent Hamiltonian and collapse operator terms. options : Options Instance of ODE solver options. progress_bar: BaseProgressBar Optional instance of BaseProgressBar, or a subclass thereof, for showing the progress of the simulation. Set to None to disable the progress bar. map_func: function A map function for managing the calls to the single-trajactory solver. map_kwargs: dictionary Optional keyword arguments to the map_func function. Returns ------- results : :class:`qutip.solver.Result` Object storing all results from the simulation. .. note:: It is possible to reuse the random number seeds from a previous run of the mcsolver by passing the output Result object seeds via the Options class, i.e. Options(seeds=prev_result.seeds). """ if debug: print(inspect.stack()[0][3]) if options is None: options = Options() if ntraj is None: ntraj = options.ntraj config.map_func = map_func if map_func is not None else parallel_map config.map_kwargs = map_kwargs if map_kwargs is not None else {} if not psi0.isket: raise Exception("Initial state must be a state vector.") 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 config.options = options if progress_bar: if progress_bar is True: config.progress_bar = TextProgressBar() else: config.progress_bar = progress_bar else: config.progress_bar = BaseProgressBar() # set num_cpus to the value given in qutip.settings if none in Options if not config.options.num_cpus: config.options.num_cpus = qutip.settings.num_cpus if config.options.num_cpus == 1: # fallback on serial_map if num_cpu == 1, since there is no # benefit of starting multiprocessing in this case config.map_func = serial_map # set initial value data if options.tidy: config.psi0 = psi0.tidyup(options.atol).full().ravel() else: config.psi0 = psi0.full().ravel() config.psi0_dims = psi0.dims config.psi0_shape = psi0.shape # set options on ouput states if config.options.steady_state_average: config.options.average_states = True # set general items config.tlist = tlist if isinstance(ntraj, (list, np.ndarray)): config.ntraj = np.sort(ntraj)[-1] else: config.ntraj = ntraj # set norm finding constants config.norm_tol = options.norm_tol config.norm_steps = options.norm_steps # convert array based time-dependence to string format H, c_ops, args = _td_wrap_array_str(H, c_ops, args, tlist) # SETUP ODE DATA IF NONE EXISTS OR NOT REUSING # -------------------------------------------- if not options.rhs_reuse or not config.tdfunc: # reset config collapse and time-dependence flags to default values config.soft_reset() # check for type of time-dependence (if any) time_type, h_stuff, c_stuff = _td_format_check(H, c_ops, 'mc') c_terms = len(c_stuff[0]) + len(c_stuff[1]) + len(c_stuff[2]) # set time_type for use in multiprocessing config.tflag = time_type # check for collapse operators if c_terms > 0: config.cflag = 1 else: config.cflag = 0 # Configure data _mc_data_config(H, psi0, h_stuff, c_ops, c_stuff, args, e_ops, options, config) # compile and load cython functions if necessary _mc_func_load(config) else: # setup args for new parameters when rhs_reuse=True and tdfunc is given # string based if config.tflag in [1, 10, 11]: if any(args): config.c_args = [] arg_items = list(args.items()) for k in range(len(arg_items)): config.c_args.append(arg_items[k][1]) # function based elif config.tflag in [2, 3, 20, 22]: config.h_func_args = args # load monte carlo class mc = _MC(config) # Run the simulation mc.run() # Remove RHS cython file if necessary if not options.rhs_reuse and config.tdname: _cython_build_cleanup(config.tdname) # AFTER MCSOLVER IS DONE # ---------------------- # Store results in the Result object output = Result() output.solver = 'mcsolve' output.seeds = config.options.seeds # state vectors if (mc.psi_out is not None and config.options.average_states and config.cflag and ntraj != 1): output.states = parfor(_mc_dm_avg, mc.psi_out.T) elif mc.psi_out is not None: output.states = mc.psi_out # expectation values if (mc.expect_out is not None and config.cflag and config.options.average_expect): # averaging if multiple trajectories if isinstance(ntraj, int): output.expect = [np.mean(np.array([mc.expect_out[nt][op] for nt in range(ntraj)], dtype=object), axis=0) for op in range(config.e_num)] elif isinstance(ntraj, (list, np.ndarray)): output.expect = [] for num in ntraj: expt_data = np.mean(mc.expect_out[:num], axis=0) data_list = [] if any([not op.isherm for op in e_ops]): for k in range(len(e_ops)): if e_ops[k].isherm: data_list.append(np.real(expt_data[k])) else: data_list.append(expt_data[k]) else: data_list = [data for data in expt_data] output.expect.append(data_list) else: # no averaging for single trajectory or if average_expect flag # (Options) is off if mc.expect_out is not None: output.expect = mc.expect_out # simulation parameters output.times = config.tlist output.num_expect = config.e_num output.num_collapse = config.c_num output.ntraj = config.ntraj output.col_times = mc.collapse_times_out output.col_which = mc.which_op_out if e_ops_dict: output.expect = {e: output.expect[n] for n, e in enumerate(e_ops_dict.keys())} return output
def _mc_data_config(H, psi0, h_stuff, c_ops, c_stuff, args, e_ops, options, config): """Creates the appropriate data structures for the monte carlo solver based on the given time-dependent, or indepdendent, format. """ if debug: print(inspect.stack()[0][3]) config.soft_reset() # take care of expectation values, if any if any(e_ops): config.e_num = len(e_ops) for op in e_ops: if isinstance(op, list): op = op[0] config.e_ops_data.append(op.data.data) config.e_ops_ind.append(op.data.indices) config.e_ops_ptr.append(op.data.indptr) config.e_ops_isherm.append(op.isherm) config.e_ops_data = np.array(config.e_ops_data) config.e_ops_ind = np.array(config.e_ops_ind) config.e_ops_ptr = np.array(config.e_ops_ptr) config.e_ops_isherm = np.array(config.e_ops_isherm) # take care of collapse operators, if any if any(c_ops): config.c_num = len(c_ops) for c_op in c_ops: if isinstance(c_op, list): c_op = c_op[0] n_op = c_op.dag() * c_op config.c_ops_data.append(c_op.data.data) config.c_ops_ind.append(c_op.data.indices) config.c_ops_ptr.append(c_op.data.indptr) # norm ops config.n_ops_data.append(n_op.data.data) config.n_ops_ind.append(n_op.data.indices) config.n_ops_ptr.append(n_op.data.indptr) # to array config.c_ops_data = np.array(config.c_ops_data) config.c_ops_ind = np.array(config.c_ops_ind) config.c_ops_ptr = np.array(config.c_ops_ptr) config.n_ops_data = np.array(config.n_ops_data) config.n_ops_ind = np.array(config.n_ops_ind) config.n_ops_ptr = np.array(config.n_ops_ptr) if config.tflag == 0: # CONSTANT H & C_OPS CODE # ----------------------- if config.cflag: config.c_const_inds = np.arange(len(c_ops)) for c_op in c_ops: n_op = c_op.dag() * c_op H -= 0.5j * \ n_op # combine Hamiltonian and collapse terms into one # construct Hamiltonian data structures if options.tidy: H = H.tidyup(options.atol) config.h_data = -1.0j * H.data.data config.h_ind = H.data.indices config.h_ptr = H.data.indptr elif config.tflag in [1, 10, 11]: # STRING BASED TIME-DEPENDENCE # ---------------------------- # take care of arguments for collapse operators, if any if any(args): for item in args.items(): config.c_args.append(item[1]) # constant Hamiltonian / string-type collapse operators if config.tflag == 1: H_inds = np.arange(1) H_tdterms = 0 len_h = 1 C_inds = np.arange(config.c_num) # find inds of time-dependent terms C_td_inds = np.array(c_stuff[2]) # find inds of constant terms C_const_inds = np.setdiff1d(C_inds, C_td_inds) # extract time-dependent coefficients (strings) C_tdterms = [c_ops[k][1] for k in C_td_inds] # store indicies of constant collapse terms config.c_const_inds = C_const_inds # store indicies of time-dependent collapse terms config.c_td_inds = C_td_inds for k in config.c_const_inds: H -= 0.5j * (c_ops[k].dag() * c_ops[k]) if options.tidy: H = H.tidyup(options.atol) config.h_data = [H.data.data] config.h_ind = [H.data.indices] config.h_ptr = [H.data.indptr] for k in config.c_td_inds: op = c_ops[k][0].dag() * c_ops[k][0] config.h_data.append(-0.5j * op.data.data) config.h_ind.append(op.data.indices) config.h_ptr.append(op.data.indptr) config.h_data = -1.0j * np.array(config.h_data) config.h_ind = np.array(config.h_ind) config.h_ptr = np.array(config.h_ptr) else: # string-type Hamiltonian & at least one string-type # collapse operator # ----------------- H_inds = np.arange(len(H)) # find inds of time-dependent terms H_td_inds = np.array(h_stuff[2]) # find inds of constant terms H_const_inds = np.setdiff1d(H_inds, H_td_inds) # extract time-dependent coefficients (strings or functions) H_tdterms = [H[k][1] for k in H_td_inds] # combine time-INDEPENDENT terms into one. H = np.array([np.sum(H[k] for k in H_const_inds)] + [H[k][0] for k in H_td_inds], dtype=object) len_h = len(H) H_inds = np.arange(len_h) # store indicies of time-dependent Hamiltonian terms config.h_td_inds = np.arange(1, len_h) # if there are any collapse operators if config.c_num > 0: if config.tflag == 10: # constant collapse operators config.c_const_inds = np.arange(config.c_num) for k in config.c_const_inds: H[0] -= 0.5j * (c_ops[k].dag() * c_ops[k]) C_inds = np.arange(config.c_num) C_tdterms = np.array([]) else: # some time-dependent collapse terms C_inds = np.arange(config.c_num) # find inds of time-dependent terms C_td_inds = np.array(c_stuff[2]) # find inds of constant terms C_const_inds = np.setdiff1d(C_inds, C_td_inds) C_tdterms = [c_ops[k][1] for k in C_td_inds] # extract time-dependent coefficients (strings) # store indicies of constant collapse terms config.c_const_inds = C_const_inds # store indicies of time-dependent collapse terms config.c_td_inds = C_td_inds for k in config.c_const_inds: H[0] -= 0.5j * (c_ops[k].dag() * c_ops[k]) else: # set empty objects if no collapse operators C_const_inds = np.arange(config.c_num) config.c_const_inds = np.arange(config.c_num) config.c_td_inds = np.array([]) C_tdterms = np.array([]) C_inds = np.array([]) # tidyup if options.tidy: H = np.array([H[k].tidyup(options.atol) for k in range(len_h)], dtype=object) # construct data sets config.h_data = [H[k].data.data for k in range(len_h)] config.h_ind = [H[k].data.indices for k in range(len_h)] config.h_ptr = [H[k].data.indptr for k in range(len_h)] for k in config.c_td_inds: config.h_data.append(-0.5j * config.n_ops_data[k]) config.h_ind.append(config.n_ops_ind[k]) config.h_ptr.append(config.n_ops_ptr[k]) config.h_data = -1.0j * np.array(config.h_data) config.h_ind = np.array(config.h_ind) config.h_ptr = np.array(config.h_ptr) # set execuatble code for collapse expectation values and spmv col_spmv_code = ("state = _cy_col_spmv_func(j, ODE.t, " + "config.c_ops_data[j], config.c_ops_ind[j], " + "config.c_ops_ptr[j], ODE.y") col_expect_code = ("for i in config.c_td_inds: " + "n_dp.append(_cy_col_expect_func(i, ODE.t, " + "config.n_ops_data[i], " + "config.n_ops_ind[i], " + "config.n_ops_ptr[i], ODE.y") for kk in range(len(config.c_args)): col_spmv_code += ",config.c_args[" + str(kk) + "]" col_expect_code += ",config.c_args[" + str(kk) + "]" col_spmv_code += ")" col_expect_code += "))" config.col_spmv_code = col_spmv_code config.col_expect_code = col_expect_code # setup ode args string config.string = "" data_range = range(len(config.h_data)) for k in data_range: config.string += ("config.h_data[" + str(k) + "], config.h_ind[" + str(k) + "], config.h_ptr[" + str(k) + "]") if k != data_range[-1]: config.string += "," # attach args to ode args string if len(config.c_args) > 0: for kk in range(len(config.c_args)): config.string += "," + "config.c_args[" + str(kk) + "]" name = "rhs" + str(os.getpid()) + str(config.cgen_num) config.tdname = name cgen = Codegen(H_inds, H_tdterms, config.h_td_inds, args, C_inds, C_tdterms, config.c_td_inds, type='mc', config=config) cgen.generate(name + ".pyx") elif config.tflag in [2, 20, 22]: # PYTHON LIST-FUNCTION BASED TIME-DEPENDENCE # ------------------------------------------ # take care of Hamiltonian if config.tflag == 2: # constant Hamiltonian, at least one function based collapse # operators H_inds = np.array([0]) H_tdterms = 0 len_h = 1 else: # function based Hamiltonian H_inds = np.arange(len(H)) H_td_inds = np.array(h_stuff[1]) H_const_inds = np.setdiff1d(H_inds, H_td_inds) config.h_funcs = np.array([H[k][1] for k in H_td_inds]) config.h_func_args = args Htd = np.array([H[k][0] for k in H_td_inds], dtype=object) config.h_td_inds = np.arange(len(Htd)) H = np.sum(H[k] for k in H_const_inds) # take care of collapse operators C_inds = np.arange(config.c_num) # find inds of time-dependent terms C_td_inds = np.array(c_stuff[1]) # find inds of constant terms C_const_inds = np.setdiff1d(C_inds, C_td_inds) # store indicies of constant collapse terms config.c_const_inds = C_const_inds # store indicies of time-dependent collapse terms config.c_td_inds = C_td_inds config.c_funcs = np.zeros(config.c_num, dtype=FunctionType) for k in config.c_td_inds: config.c_funcs[k] = c_ops[k][1] config.c_func_args = args # combine constant collapse terms with constant H and construct data for k in config.c_const_inds: H -= 0.5j * (c_ops[k].dag() * c_ops[k]) if options.tidy: H = H.tidyup(options.atol) Htd = np.array([Htd[j].tidyup(options.atol) for j in config.h_td_inds], dtype=object) # setup constant H terms data config.h_data = -1.0j * H.data.data config.h_ind = H.data.indices config.h_ptr = H.data.indptr # setup td H terms data config.h_td_data = np.array( [-1.0j * Htd[k].data.data for k in config.h_td_inds]) config.h_td_ind = np.array( [Htd[k].data.indices for k in config.h_td_inds]) config.h_td_ptr = np.array( [Htd[k].data.indptr for k in config.h_td_inds]) elif config.tflag == 3: # PYTHON FUNCTION BASED HAMILTONIAN # --------------------------------- # take care of Hamiltonian config.h_funcs = H config.h_func_args = args # take care of collapse operators config.c_const_inds = np.arange(config.c_num) config.c_td_inds = np.array([]) if len(config.c_const_inds) > 0: H = 0 for k in config.c_const_inds: H -= 0.5j * (c_ops[k].dag() * c_ops[k]) if options.tidy: H = H.tidyup(options.atol) config.h_data = -1.0j * H.data.data config.h_ind = H.data.indices config.h_ptr = H.data.indptr