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 _gather(sols): # gather list of Odedata objects, sols, into one. sol = Odedata() # sol = sols[0] ntraj = sum([a.ntraj for a in sols]) sol.col_times = np.zeros((ntraj), dtype=np.ndarray) sol.col_which = np.zeros((ntraj), dtype=np.ndarray) sol.col_times[0:sols[0].ntraj] = sols[0].col_times sol.col_which[0:sols[0].ntraj] = sols[0].col_which sol.states = np.array(sols[0].states) sol.expect = np.array(sols[0].expect) if (hasattr(sols[0], 'entropy')): sol.entropy = np.array(sols[0].entropy) sofar = 0 for j in range(1, len(sols)): sofar = sofar + sols[j - 1].ntraj sol.col_times[sofar:sofar + sols[j].ntraj] = ( sols[j].col_times) sol.col_which[sofar:sofar + sols[j].ntraj] = ( sols[j].col_which) if (odeconfig.e_num == 0): if (odeconfig.options.average_states): # collect states, averaged over trajectories sol.states += np.array(sols[j].states) else: # collect states, all trajectories sol.states = np.vstack((sol.states, np.array(sols[j].states))) else: if (odeconfig.options.average_expect): # collect expectation values, averaged for i in range(odeconfig.e_num): sol.expect[i] += np.array(sols[j].expect[i]) else: # collect expectation values, all trajectories sol.expect = np.vstack((sol.expect, np.array(sols[j].expect))) if (hasattr(sols[j], 'entropy')): if (odeconfig.options.average_states or odeconfig.options.average_expect): # collect entropy values, averaged sol.entropy += np.array(sols[j].entropy) else: # collect entropy values, all trajectories sol.entropy = np.vstack((sol.entropy, np.array(sols[j].entropy))) if (odeconfig.options.average_states or odeconfig.options.average_expect): if (odeconfig.e_num == 0): sol.states = sol.states / len(sols) else: sol.expect = list(sol.expect / len(sols)) inds=np.where(odeconfig.e_ops_isherm)[0] for jj in inds: sol.expect[jj]=np.real(sol.expect[jj]) if (hasattr(sols[0], 'entropy')): sol.entropy = sol.entropy / len(sols) #convert sol.expect array to list and fix dtypes of arrays if (not odeconfig.options.average_expect) and odeconfig.e_num!=0: temp=[list(sol.expect[ii]) for ii in range(ntraj)] for ii in range(ntraj): for jj in np.where(odeconfig.e_ops_isherm)[0]: temp[ii][jj]=np.real(temp[ii][jj]) sol.expect=temp # convert to list/array to be consistent with qutip mcsolve sol.states = list(sol.states) 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