def mi_mcsolve(H, psi0, tlist, c_ops, e_ops, ntraj=500, args={}, options=Odeoptions()): if psi0.type != 'ket': raise ValueError("psi0 must be a state vector") if type(ntraj) == int: ntraj = [ntraj] elif type(ntraj[0]) != int: raise ValueError( "ntraj must either be an integer or a list of integers") num_eops = len(e_ops) num_cops = len(c_ops) # Just use mcsolve if there aren't any collapse or expect. operators if num_eops == num_cops == 0: raise ValueError( "Must supply at least one expectation value operator.") # should not ever meet this condition #return qutip.mcsolve(H, psi0, tlist, c_ops, e_ops, ntraj, args, options) elif num_cops == 0: ntraj = 1 # Let's be sure we're not changing anything: H = copy.deepcopy(H) H = np.matrix(H.full()) psi0 = copy.deepcopy(psi0) psi0 = psi0.full() tlist = copy.deepcopy(tlist) c_ops = copy.deepcopy(c_ops) for i in range(num_cops): c_ops[i] = np.matrix(c_ops[i].full()) e_ops = copy.deepcopy(e_ops) eops_herm = [False for _ in range(num_eops)] for i in range(num_eops): e_ops[i] = np.matrix(e_ops[i].full()) eops_herm[i] = not any(abs(e_ops[i].getH() - e_ops[i]) > 1e-15) # check if each e_op is Hermetian # Construct the effective Hamiltonian Heff = H for cop in c_ops: Heff += -0.5j * np.dot(cop.getH(), cop) Heff = (-1j) * Heff # Find the eigenstates of the effective Hamiltonian la, v = np.linalg.eig(Heff) # Construct the similarity transformation matricies S = np.matrix(v) Sinv = np.linalg.inv(S) Heff_diag = np.dot(Sinv, np.dot(Heff, S)).round(10) for i in range(num_cops): c_ops[i] = np.dot( c_ops[i], S) # Multiply each Collapse Operator to the left by S psi0 = psi0 / np.linalg.norm(psi0) psi0_nb = np.dot(Sinv, psi0) # change basis for initial state vector for i in range(num_eops): e_ops[i] = np.dot( S.getH(), np.dot(e_ops[i], S) ) # Change basis for the operator for which expectation values are requested if len(ntraj) > 1: exp_vals = [ list( np.zeros(len(tlist), dtype=(float if eops_herm[i] else complex)) for i in range(num_eops)) for _ in range(len(ntraj)) ] collapse_times_out = [list() for _ in range(len(ntraj))] which_op_out = [list() for _ in range(len(ntraj))] else: exp_vals = list( np.zeros(len(tlist), dtype=(float if eops_herm[i] else complex)) for i in range(num_eops)) collapse_times_out, which_op_out = list(), list() for _n in range(len(ntraj)): # ntraj can be passed in as a list print "Calculation Starting on", multiprocessing.cpu_count(), "CPUs" p = Pool() def callback(r): # method to display progress callback.counter += 1 if (round(100.0 * float(callback.counter) / callback.ntraj) >= 10 + round(100.0 * float(callback.last) / callback.ntraj)): print "Progress: %.0f%% (approx. %.2fs remaining)" % ( (100.0 * float(callback.counter) / callback.ntraj), ((time.time() - callback.start) / callback.counter * (callback.ntraj - callback.counter))) callback.last = callback.counter callback.last = 0 callback.counter = 0 callback.ntraj = ntraj[_n] callback.start = time.time() results = [ r.get() for r in [ p.apply_async(one_traj, (Heff_diag, S, Sinv, psi0_nb, tlist, e_ops, c_ops, num_eops, num_cops), {}, callback) for _ in range(ntraj[_n]) ] ] p.close() p.join() # The following is a manipulation of the data resulting from the calculation # The goal is to output the results in an identical format as those from qutip.mcsolve() if len(ntraj) > 1: for i in range(ntraj[_n]): collapse_times_out[_n].append(results[i][1]) which_op_out[_n].append(results[i][2]) for j in range(num_eops): if eops_herm[j]: exp_vals[_n][j] += results[i][0][j].real else: exp_vals[_n][j] += results[i][0][j] for i in range(num_eops): exp_vals[_n][i] = exp_vals[_n][i] / ntraj[_n] else: for i in range(ntraj[_n]): collapse_times_out.append(results[i][1]) which_op_out.append(results[i][2]) for j in range(num_eops): if eops_herm[j]: exp_vals[j] += results[i][0][j].real else: exp_vals[j] += results[i][0][j] for i in range(num_eops): exp_vals[i] = exp_vals[i] / ntraj[_n] output = Odedata() output.solver = 'mi_mcsolve' output.expect = exp_vals output.times = tlist output.num_expect = num_eops output.num_collapse = num_cops output.ntraj = ntraj output.col_times = collapse_times_out output.col_which = which_op_out return output
def evolve_serial(self, args): if debug: print(inspect.stack()[0][3] + ":" + str(os.getpid())) # run ntraj trajectories for one process via fortran # get args queue, ntraj, instanceno, rngseed = args # initialize the problem in fortran _init_tlist() _init_psi0() if (self.ptrace_sel != []): _init_ptrace_stuff(self.ptrace_sel) _init_hamilt() if (odeconfig.c_num != 0): _init_c_ops() if (odeconfig.e_num != 0): _init_e_ops() # set options qtf90.qutraj_run.n_c_ops = odeconfig.c_num qtf90.qutraj_run.n_e_ops = odeconfig.e_num qtf90.qutraj_run.ntraj = ntraj qtf90.qutraj_run.unravel_type = self.unravel_type qtf90.qutraj_run.average_states = odeconfig.options.average_states qtf90.qutraj_run.average_expect = odeconfig.options.average_expect qtf90.qutraj_run.init_odedata(odeconfig.psi0_shape[0], odeconfig.options.atol, odeconfig.options.rtol, mf=self.mf, norm_steps=odeconfig.norm_steps, norm_tol=odeconfig.norm_tol) # set optional arguments qtf90.qutraj_run.order = odeconfig.options.order qtf90.qutraj_run.nsteps = odeconfig.options.nsteps qtf90.qutraj_run.first_step = odeconfig.options.first_step qtf90.qutraj_run.min_step = odeconfig.options.min_step qtf90.qutraj_run.max_step = odeconfig.options.max_step qtf90.qutraj_run.norm_steps = odeconfig.options.norm_steps qtf90.qutraj_run.norm_tol = odeconfig.options.norm_tol # use sparse density matrices during computation? qtf90.qutraj_run.rho_return_sparse = self.sparse_dms # calculate entropy of reduced density matrice? qtf90.qutraj_run.calc_entropy = self.calc_entropy # run show_progress = 1 if debug else 0 qtf90.qutraj_run.evolve(instanceno, rngseed, show_progress) # construct Odedata instance sol = Odedata() sol.ntraj = ntraj # sol.col_times = qtf90.qutraj_run.col_times # sol.col_which = qtf90.qutraj_run.col_which-1 sol.col_times, sol.col_which = self.get_collapses(ntraj) if (odeconfig.e_num == 0): sol.states = self.get_states(len(odeconfig.tlist), ntraj) else: sol.expect = self.get_expect(len(odeconfig.tlist), ntraj) if (self.calc_entropy): sol.entropy = self.get_entropy(len(odeconfig.tlist)) if (not self.serial_run): # put to queue queue.put(sol) queue.join() # deallocate stuff # finalize() return sol
def mcsolve_f90(H, psi0, tlist, c_ops, e_ops, ntraj=None, options=Odeoptions(), 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 : Odeoptions Instance of ODE 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 : Odedata 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.") odeconfig.options = options # set num_cpus to the value given in qutip.settings # if none in Odeoptions if not odeconfig.options.num_cpus: odeconfig.options.num_cpus = qutip.settings.num_cpus # set initial value data if options.tidy: odeconfig.psi0 = psi0.tidyup(options.atol).full() else: odeconfig.psi0 = psi0.full() odeconfig.psi0_dims = psi0.dims odeconfig.psi0_shape = psi0.shape # set general items odeconfig.tlist = tlist if isinstance(ntraj, (list, np.ndarray)): raise Exception("ntraj as list argument is not supported.") else: odeconfig.ntraj = ntraj # ntraj_list = [ntraj] # set norm finding constants odeconfig.norm_tol = options.norm_tol odeconfig.norm_steps = options.norm_steps if not options.rhs_reuse: odeconfig.soft_reset() # no time dependence odeconfig.tflag = 0 # check for collapse operators if len(c_ops) > 0: odeconfig.cflag = 1 else: odeconfig.cflag = 0 # Configure data _mc_data_config(H, psi0, [], c_ops, [], [], e_ops, options, odeconfig) # 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 Odedata object output = Odedata() # 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 = odeconfig.tlist output.num_expect = odeconfig.e_num output.num_collapse = odeconfig.c_num output.ntraj = odeconfig.ntraj return output
def mcsolve(H,psi0,tlist,c_ops,e_ops,ntraj=500,args={},options=Odeoptions()): """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 Odeoptions 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 : Odeoptions Instance of ODE solver options. Returns ------- results : Odedata Object storing all results from simulation. """ # if single operator is passed for c_ops or e_ops, convert it to # list containing only that operator if isinstance(c_ops, Qobj): c_ops = [c_ops] if isinstance(e_ops, Qobj): e_ops = [e_ops] if psi0.type!='ket': raise Exception("Initial state must be a state vector.") odeconfig.options=options #set num_cpus to the value given in qutip.settings if none in Odeoptions if not odeconfig.options.num_cpus: odeconfig.options.num_cpus=qutip.settings.num_cpus #set initial value data if options.tidy: odeconfig.psi0=psi0.tidyup(options.atol).full() else: odeconfig.psi0=psi0.full() odeconfig.psi0_dims=psi0.dims odeconfig.psi0_shape=psi0.shape #set general items odeconfig.tlist=tlist if isinstance(ntraj,(list,ndarray)): odeconfig.ntraj=sort(ntraj)[-1] else: odeconfig.ntraj=ntraj #set norm finding constants odeconfig.norm_tol=options.norm_tol odeconfig.norm_steps=options.norm_steps #---- #---------------------------------------------- # SETUP ODE DATA IF NONE EXISTS OR NOT REUSING #---------------------------------------------- if (not options.rhs_reuse) or (not odeconfig.tdfunc): #reset odeconfig collapse and time-dependence flags to default values _reset_odeconfig() #check for type of time-dependence (if any) time_type,h_stuff,c_stuff=_ode_checks(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 odeconfig.tflag=time_type #-Check for PyObjC on Mac platforms if sys.platform=='darwin' and odeconfig.options.gui: try: import Foundation except: odeconfig.options.gui=False #check if running in iPython and using Cython compiling (then no GUI to work around error) if odeconfig.options.gui and odeconfig.tflag in array([1,10,11]): try: __IPYTHON__ except: pass else: odeconfig.options.gui=False if qutip.settings.qutip_gui=="NONE": odeconfig.options.gui=False #check for collapse operators if c_terms>0: odeconfig.cflag=1 else: odeconfig.cflag=0 #Configure data _mc_data_config(H,psi0,h_stuff,c_ops,c_stuff,args,e_ops,options) if odeconfig.tflag in array([1,10,11]): #compile time-depdendent RHS code os.environ['CFLAGS'] = '-O3 -w' import pyximport pyximport.install(setup_args={'include_dirs':[numpy.get_include()]}) if odeconfig.tflag in array([1,11]): code = compile('from '+odeconfig.tdname+' import cyq_td_ode_rhs,col_spmv,col_expect', '<string>', 'exec') exec(code, globals()) odeconfig.tdfunc=cyq_td_ode_rhs odeconfig.colspmv=col_spmv odeconfig.colexpect=col_expect else: code = compile('from '+odeconfig.tdname+' import cyq_td_ode_rhs', '<string>', 'exec') exec(code, globals()) odeconfig.tdfunc=cyq_td_ode_rhs try: os.remove(odeconfig.tdname+".pyx") except: print("Error removing pyx file. File not found.") elif odeconfig.tflag==0: odeconfig.tdfunc=cyq_ode_rhs else:#setup args for new parameters when rhs_reuse=True and tdfunc is given #string based if odeconfig.tflag in array([1,10,11]): if any(args): odeconfig.c_args=[] arg_items=args.items() for k in range(len(args)): odeconfig.c_args.append(arg_items[k][1]) #function based elif odeconfig.tflag in array([2,3,20,22]): odeconfig.h_func_args=args #load monte-carlo class mc=_MC_class() #RUN THE SIMULATION mc.run() #AFTER MCSOLVER IS DONE -------------------------------------- #-------COLLECT AND RETURN OUTPUT DATA IN ODEDATA OBJECT --------------# output=Odedata() output.solver='mcsolve' #state vectors if mc.psi_out is not None and odeconfig.options.mc_avg and odeconfig.cflag: 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 odeconfig.cflag and odeconfig.options.mc_avg:#averaging if multiple trajectories if isinstance(ntraj,int): output.expect=mean(mc.expect_out,axis=0) elif isinstance(ntraj,(list,ndarray)): output.expect=[] for num in ntraj: expt_data=mean(mc.expect_out[:num],axis=0) data_list=[] if any([op.isherm==False for op in e_ops]): for k in range(len(e_ops)): if e_ops[k].isherm: data_list.append(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 mc_avg flag (Odeoptions) is off if mc.expect_out is not None: output.expect=mc.expect_out #simulation parameters output.times=odeconfig.tlist output.num_expect=odeconfig.e_num output.num_collapse=odeconfig.c_num output.ntraj=odeconfig.ntraj output.col_times=mc.collapse_times_out output.col_which=mc.which_op_out return output