def _heom_state_dictionaries(dims, excitations): """ Return the number of states, and lookup-dictionaries for translating a state tuple to a state index, and vice versa, for a system with a given number of components and maximum number of excitations. Parameters ---------- dims: list A list with the number of states in each sub-system. excitations : integer The maximum numbers of dimension Returns ------- nstates, state2idx, idx2state: integer, dict, dict The number of states `nstates`, a dictionary for looking up state indices from a state tuple, and a dictionary for looking up state state tuples from state indices. """ nstates = 0 state2idx = {} idx2state = {} for state in state_number_enumerate(dims, excitations): state2idx[state] = nstates idx2state[nstates] = state nstates += 1 return nstates, state2idx, idx2state
def __init__(self, exponents, max_depth): self.exponents = exponents self.max_depth = max_depth self.dims = [exp.dim or (max_depth + 1) for exp in self.exponents] self.vk = [exp.vk for exp in self.exponents] self.ck = [exp.ck for exp in self.exponents] self.ck2 = [exp.ck2 for exp in self.exponents] self.sigma_bar_k_offset = [ exp.sigma_bar_k_offset for exp in self.exponents ] self.labels = list(state_number_enumerate(self.dims, max_depth)) self._label_idx = {s: i for i, s in enumerate(self.labels)}
def test_fock_state(dimensions, n_excitations): """ Test Fock state creation agrees with the number operators implied by the existence of the ENR annihiliation operators. """ number = [ a.dag() * a for a in qutip.enr_destroy(dimensions, n_excitations) ] bases = list(qutip.state_number_enumerate(dimensions, n_excitations)) n_samples = min((len(bases), 5)) for basis in random.sample(bases, n_samples): state = qutip.enr_fock(dimensions, n_excitations, basis) for n, x in zip(number, basis): assert abs(n.matrix_element(state.dag(), state)) - x < 1e-10
def test_enr_thermal_dm2(): "Excitation-number-restricted state space: thermal density operator (II)" dims, excitations = [3, 4, 5], 2 n_vec = 0.1 rho = enr_thermal_dm(dims, excitations, n_vec) rho_ref = tensor([thermal_dm(d, n_vec) for idx, d in enumerate(dims)]) gonners = [idx for idx, state in enumerate(state_number_enumerate(dims)) if sum(state) > excitations] rho_ref = rho_ref.eliminate_states(gonners) rho_ref = rho_ref / rho_ref.tr() assert_(abs((rho.data - rho_ref.data).data).max() < 1e-12)
def _reference_dm(dimensions, n_excitations, nbars): """ Get the reference density matrix using `Qobj.eliminate_states` explicitly, to compare to the direct ENR construction. """ if np.isscalar(nbars): nbars = [nbars] * len(dimensions) out = qutip.tensor([ qutip.thermal_dm(dimension, nbar) for dimension, nbar in zip(dimensions, nbars) ]) eliminate = [ i for i, state in enumerate(qutip.state_number_enumerate(dimensions)) if sum(state) > n_excitations ] out = out.eliminate_states(eliminate) return out / out.tr()
def _heom_number_enumerate(dims, excitations=None, state=None, idx=0): """ An iterator that enumerate all the state number arrays (quantum numbers on the form [n1, n2, n3, ...]) for a system with dimensions given by dims. Example: >>> for state in state_number_enumerate([2,2]): >>> print(state) [ 0. 0.] [ 0. 1.] [ 1. 0.] [ 1. 1.] Parameters ---------- dims : list or array The quantum state dimensions array, as it would appear in a Qobj. state : list Current state in the iteration. Used internally. excitations : integer (None) Restrict state space to states with excitation numbers below or equal to this value. idx : integer Current index in the iteration. Used internally. Returns ------- state_number : list Successive state number arrays that can be used in loops and other iterations, using standard state enumeration *by definition*. """ if state is None: state = np.zeros(len(dims)) if excitations and sum(state[0:idx]) > excitations: pass elif idx == len(dims): if excitations is None: yield np.array(state) else: yield tuple(state) else: for n in range(dims[idx]): state[idx] = n for s in state_number_enumerate(dims, excitations, state, idx + 1): yield s
def __init__(self, ops, states = [], state_labels = []): # -- Assertions -- if isinstance(ops, Qobj): # Make ops list if given as Qobj self.ops = [ops] else: self.ops = ops assert isinstance(self.ops,list) if len(states) == 0: states = [ket(s,self.ops[0].dims[0]) for s in state_number_enumerate(self.ops[0].dims[0]) ] if state_labels: assert len(state_labels) == len(states) else: state_labels = [str(n) for n in range(len(states))] for state in states: assert state.isket # -- Initializing figure -- self.fig = figure() self.ax = axes() self.fig.canvas.callbacks.connect('pick_event', self.on_pick) # -- Building level instances -- self.levels = [] self.deg_subspaces = [] self.transitions = [] for psi,label in zip(states,state_labels): E = expect(self.ops[0],psi) self.levels.append(level(E,psi,label)) degeneracy_tol = (max([lvl.E for lvl in self.levels])-min([lvl.E for lvl in self.levels]))*0.5e-1 lvls = self.levels.copy() while lvls: new_subspace = [lvl for lvl in lvls if abs(lvl.E-lvls[0].E)<degeneracy_tol] self.deg_subspaces.append(_subspace(new_subspace)) for lvl in new_subspace: lvls.remove(lvl) # -- Building transition instances -- min_strength = 1e-11*sum([lvl.E**2 for lvl in self.levels]) for i in range(len(states)-1): for j in range(i+1,len(states)): strength = sum(oper.matrix_element(states[i],states[j]) for oper in self.ops) if abs(strength)>min_strength: self.transitions.append(transition(strength, self.levels[i], self.levels[j]))
def quantize_SHO(K, U, p, x, dims=[], taylor_order=4, x0=None, t_symbol=None): """A function that can quantize a sympy hamiltonian. Currently only in SHO/fock basis Parameters ---------- K : sympy expression Sympy expression for the kinetic term U : sympy expression sympy expression for the potential p : iterable of sympy symbols Symbols representing the canonical momenta of the system x : iterable of sympy symbols Symbols representing the canonical position variables of the system dims : list of integers Specifies the desired dimension of each mode. Must be same length as x and p taylor_order : integer Specifies the order to which the taylor expansion will be carried out if the quantization method utilises, such an expansion. x0 : list of floats The point around which the taylor expansion of the potential wil be carried out. Returns ------- operator_generator An operator generator that when called as instance(*params) return the hamiltonian for the system with given params. A parameter is defined as a symbol that appears in K or U but not in x or p. """ # Preliminaries if not dims: dims = len(x) * [4] if x0 == None: x0 = np.zeros(len(x)) # Taylor expansion T_U = au.taylor.taylor_sympy(f=U, x=x, x0=x0, N=taylor_order) T_K = au.taylor.taylor_sympy(f=K, x=p, x0=np.zeros(len(p)), N=2) # Effective mass and "spring constant" m = len(x) * [0] k = len(x) * [0] for coeff, powers in T_K: inds = np.nonzero(np.array(powers) == 2)[0] if len(inds) == 1: m[inds[0]] = 1 / (2 * coeff) for coeff, powers in T_U: inds = np.nonzero(np.array(powers) == 2)[0] if len(inds) == 1: k[inds[0]] = 2 * coeff # Constructing position and momentum operator x_ops = [] p_ops = [] padded_dims = [d + int(np.floor(taylor_order / 2)) for d in dims] for j, d in zip(range(len(k)), padded_dims): op = [qt.qeye(d) for d in padded_dims] op[j] = 1j * (qt.create(d) - qt.destroy(d)) / np.sqrt(2) p_ops.append(qt.tensor(op)) op[j] = (qt.create(d) + qt.destroy(d)) / np.sqrt(2) x_ops.append(qt.tensor(op)) terms = [] # Calculating qutip operator and symbolic coefficient for each term P = 0 # This will be the projection operator that projects onto the subspace defined by dims for state in qt.state_number_enumerate(dims): P += qt.ket(state, dims) * qt.bra(state, padded_dims) for coeff, powers in T_K: op = qt.qeye(padded_dims) for kj, mj, p_op, pow in zip(k, m, p_ops, powers): if pow > 0: coeff *= (kj * mj)**(pow / 4) op *= p_op**pow terms.append((coeff, P * op * P.dag())) for coeff, powers in T_U: op = qt.qeye(padded_dims) for kj, mj, x_op, pow in zip(k, m, x_ops, powers): if pow > 0: coeff *= (kj * mj)**(-pow / 4) op *= x_op**pow terms.append((coeff, P * op * P.dag())) # Constructing opgen instance return au.td_symopgen(sym_terms=terms, t_symbol=t_symbol)
def filter(self, level=None, tags=None, dims=None, types=None): """ Return a list of ADO labels for ADOs whose "excitations" match the given patterns. Each of the filter parameters (tags, dims, types) may be either unspecified (None) or a list. Unspecified parameters are excluded from the filtering. All specified filter parameters must be lists of the same length. Each position in the lists describes a particular excitation and any exponent that matches the filters may supply that excitation. The level of all labels returned is thus equal to the length of the filter parameter lists. Within a filter parameter list, items that are None represent wildcards and match any value of that exponent attribute Parameters ---------- level : int The hierarchy depth to return ADOs from. tags : list of object or None Filter parameter that matches the ``.tag`` attribute of exponents. dims : list of int Filter parameter that matches the ``.dim`` attribute of exponents. types : list of BathExponent types or list of str Filter parameter that matches the ``.type`` attribute of exponents. Types may be supplied by name (e.g. "R", "I", "+") instead of by the actual type (e.g. ``BathExponent.types.R``). Returns ------- list of tuple The ADO label for each ADO whose exponent excitations (i.e. label) match the given filters or level. """ if types is not None: types = [ t if t is None or isinstance(t, BathExponent.types) else BathExponent.types[t] for t in types ] filters = [("tag", tags), ("type", types), ("dim", dims)] filters = [(attr, f) for attr, f in filters if f is not None] n = max((len(f) for _, f in filters), default=0) if any(len(f) != n for _, f in filters): raise ValueError( "The tags, dims and types filters must all be the same length." ) if n > self.max_depth: raise ValueError( f"The maximum depth for the hierarchy is {self.max_depth} but" f" {n} levels of excitation filters were given.") if level is None: if not filters: # fast path for when there are no excitation filters return self.labels[:] else: if not filters: # fast path for when there are no excitation filters return [label for label in self.labels if sum(label) == level] if level != n: raise ValueError( f"The level parameter is {level} but {n} levels of" " excitation filters were given.") filtered_dims = [1] * len(self.exponents) for lvl in range(n): level_filters = [(attr, f[lvl]) for attr, f in filters if f[lvl] is not None] for j, exp in enumerate(self.exponents): if any(getattr(exp, attr) != f for attr, f in level_filters): continue filtered_dims[j] += 1 filtered_dims[j] = min(self.dims[j], filtered_dims[j]) return [ label for label in state_number_enumerate(filtered_dims, n) if sum(label) == n ]
[3], [4]) + 20 * qt.ket([0], [4]) * qt.bra([0], [4]) def process(rho, tlist): res = qt.mesolve(H, rho, tlist) return [ transform_state_unitary(rho, U_target, inverse=True) for rho in res.states ] tlist = np.linspace(0, np.pi / (2 * np.sqrt(2)), 10) F_av = average_fidelity(process, subspace_basis, tlist) print(F_av, '\n') # One qubit doing nothing in interaction pic but simulated in schrodinger subspace_basis = [qt.ket(seq, [2]) for seq in qt.state_number_enumerate([2])] H0 = qt.sigmaz() def U_target(t): return qt.qeye(2) * (-1j * H0 * t).expm() def process(rho, tlist): res = qt.mesolve(H0, rho, tlist) return [ transform_state_unitary(rho, U_target(t), inverse=True) for rho, t in zip(res.states, tlist) ]
def hsolve(H, psi0, tlist, Q, gam, lam0, Nc, N, w_th, options=None): """ Function to solve for an open quantum system using the hierarchy model. Parameters ---------- H: Qobj The system hamiltonian. psi0: Qobj Initial state of the system. tlist: List. Time over which system evolves. Q: Qobj The coupling between system and bath. gam: Float Bath cutoff frequency. lam0: Float Coupling strength. Nc: Integer Cutoff parameter. N: Integer Number of matsubara terms. w_th: Float Temperature. options : :class:`qutip.Options` With options for the solver. Returns ------- output: Result System evolution. """ if options is None: options = Options() # Set up terms of the matsubara and tanimura boundaries # Parameters and hamiltonian hbar = 1. kb = 1. # Set by system dimensions = dims(H) Nsup = dimensions[0][0] * dimensions[0][0] unit = qeye(dimensions[0]) # Ntot is the total number of ancillary elements in the hierarchy Ntot = int(round(factorial(Nc+N) / (factorial(Nc) * factorial(N)))) c0 = (lam0 * gam * (_cot(gam * hbar / (2. * kb * w_th)) - (1j))) / hbar LD1 = (-2. * spre(Q) * spost(Q.dag()) + spre(Q.dag()*Q) + spost(Q.dag()*Q)) pref = ((2. * lam0 * kb * w_th / (gam * hbar)) - 1j * lam0) / hbar gj = 2 * np.pi * kb * w_th / hbar L12 = -pref * LD1 + (c0 / gam) * LD1 for i1 in range(1, N): num = (4 * lam0 * gam * kb * w_th * i1 * gj/((i1 * gj)**2 - gam**2)) ci = num / (hbar**2) L12 = L12 + (ci / gj) * LD1 # Setup liouvillian L = liouvillian(H, [L12]) Ltot = L.data unit = sp.eye(Ntot,format='csr') Lbig = sp.kron(unit, Ltot) rho0big1 = np.zeros((Nsup * Ntot), dtype=complex) # Prepare initial state: rhotemp = mat2vec(np.array(psi0.full(), dtype=complex)) for idx, element in enumerate(rhotemp): rho0big1[idx] = element[0] nstates, state2idx, idx2state = enr_state_dictionaries([Nc+1]*(N), Nc) for nlabelt in state_number_enumerate([Nc+1]*(N), Nc): nlabel = list(nlabelt) ntotalcheck = 0 for ncheck in range(N): ntotalcheck = ntotalcheck + nlabel[ncheck] current_pos = int(round(state2idx[tuple(nlabel)])) Ltemp = sp.lil_matrix((Ntot, Ntot)) Ltemp[current_pos, current_pos] = 1 Ltemp.tocsr() Lbig = Lbig + sp.kron(Ltemp, (-nlabel[0] * gam * spre(unit).data)) for kcount in range(1, N): counts = -nlabel[kcount] * kcount * gj * spre(unit).data Lbig = Lbig + sp.kron(Ltemp, counts) for kcount in range(N): if nlabel[kcount] >= 1: # find the position of the neighbour nlabeltemp = copy(nlabel) nlabel[kcount] = nlabel[kcount] - 1 current_pos2 = int(round(state2idx[tuple(nlabel)])) Ltemp = sp.lil_matrix((Ntot, Ntot)) Ltemp[current_pos, current_pos2] = 1 Ltemp.tocsr() # renormalized version: ci = (4 * lam0 * gam * kb * w_th * kcount * gj/((kcount * gj)**2 - gam**2)) / (hbar**2) if kcount == 0: Lbig = Lbig + sp.kron(Ltemp, (-1j * (np.sqrt(nlabeltemp[kcount] / abs(c0))) * ((c0) * spre(Q).data - (np.conj(c0)) * spost(Q).data))) if kcount > 0: ci = (4 * lam0 * gam * kb * w_th * kcount * gj/((kcount * gj)**2 - gam**2)) / (hbar**2) Lbig = Lbig + sp.kron(Ltemp, (-1j * (np.sqrt(nlabeltemp[kcount] / abs(ci))) * ((ci) * spre(Q).data - (np.conj(ci)) * spost(Q).data))) nlabel = copy(nlabeltemp) for kcount in range(N): if ntotalcheck <= (Nc-1): nlabeltemp = copy(nlabel) nlabel[kcount] = nlabel[kcount] + 1 current_pos3 = int(round(state2idx[tuple(nlabel)])) if current_pos3 <= (Ntot): Ltemp = sp.lil_matrix((Ntot, Ntot)) Ltemp[current_pos, current_pos3] = 1 Ltemp.tocsr() # renormalized if kcount == 0: Lbig = Lbig + sp.kron(Ltemp, -1j * (np.sqrt((nlabeltemp[kcount]+1) * abs(c0))) * (spre(Q) - spost(Q)).data) if kcount > 0: ci = (4 * lam0 * gam * kb * w_th * kcount * gj/((kcount * gj)**2 - gam**2)) / (hbar**2) Lbig = Lbig + sp.kron(Ltemp, -1j * (np.sqrt((nlabeltemp[kcount]+1) * abs(ci))) * (spre(Q) - spost(Q)).data) nlabel = copy(nlabeltemp) output = [] for element in rhotemp: output.append([]) r = scipy.integrate.ode(cy_ode_rhs) Lbig2 = Lbig.tocsr() r.set_f_params(Lbig2.data, Lbig2.indices, Lbig2.indptr) r.set_integrator('zvode', method=options.method, order=options.order, atol=options.atol, rtol=options.rtol, nsteps=options.nsteps, first_step=options.first_step, min_step=options.min_step, max_step=options.max_step) r.set_initial_value(rho0big1, tlist[0]) dt = tlist[1] - tlist[0] for t_idx, t in enumerate(tlist): r.integrate(r.t + dt) for idx, element in enumerate(rhotemp): output[idx].append(r.y[idx]) return output