Ejemplo n.º 1
0
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
Ejemplo n.º 2
0
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
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
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
Ejemplo n.º 5
0
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
Ejemplo n.º 6
0
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
Ejemplo n.º 7
0
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