def basis(N, n=0, offset=0): """Generates the vector representation of a Fock state. Parameters ---------- N : int Number of Fock states in Hilbert space. n : int Integer corresponding to desired number state, defaults to 0 if omitted. offset : int (default 0) The lowest number state that is included in the finite number state representation of the state. Returns ------- state : :class:`qutip.Qobj` Qobj representing the requested number state ``|n>``. Examples -------- >>> basis(5,2) Quantum object: dims = [[5], [1]], shape = [5, 1], type = ket Qobj data = [[ 0.+0.j] [ 0.+0.j] [ 1.+0.j] [ 0.+0.j] [ 0.+0.j]] Notes ----- A subtle incompatibility with the quantum optics toolbox: In QuTiP:: basis(N, 0) = ground state but in the qotoolbox:: basis(N, 1) = ground state """ if (not isinstance(N, (int, np.integer))) or N < 0: raise ValueError("N must be integer N >= 0") if (not isinstance(n, (int, np.integer))) or n < offset: raise ValueError("n must be integer n >= 0") if n - offset > (N - 1): # check if n is within bounds raise ValueError("basis vector index need to be in n <= N-1") data = np.array([1], dtype=complex) ind = np.array([0], dtype=np.int32) ptr = np.array([0]*((n - offset)+1)+[1]*(N-(n-offset)),dtype=np.int32) return Qobj(fast_csr_matrix((data,ind,ptr), shape=(N,1)), isherm=False)
def _unsorted_csr(N, density=0.5): N = np.arange(N) M = sp.diags(N,0, dtype=complex, format='csr') N = N.shape[0] nvals = N**2*density while M.nnz < 0.95*nvals: M = rand_jacobi_rotation(M) M = M.tocsr() return fast_csr_matrix((M.data,M.indices,M.indptr), shape = M.shape)
def sp_reverse_permute(A, rperm=(), cperm=(), safe=True): """ Performs a reverse permutations of the rows and columns of a sparse CSR/CSC matrix according to the permutation arrays rperm and cperm, respectively. Here, the permutation arrays specify the order of the rows and columns used to permute the original array. Parameters ---------- A : csr_matrix, csc_matrix Input matrix. rperm : array_like of integers Array of row permutations. cperm : array_like of integers Array of column permutations. safe : bool Check structure of permutation arrays. Returns ------- perm_csr : csr_matrix, csc_matrix CSR or CSC matrix with permuted rows/columns. """ rperm = np.asarray(rperm, dtype=np.int32) cperm = np.asarray(cperm, dtype=np.int32) nrows = A.shape[0] ncols = A.shape[1] if len(rperm) == 0: rperm = np.arange(nrows, dtype=np.int32) if len(cperm) == 0: cperm = np.arange(ncols, dtype=np.int32) if safe: if len(np.setdiff1d(rperm, np.arange(nrows))) != 0: raise Exception('Invalid row permutation array.') if len(np.setdiff1d(cperm, np.arange(ncols))) != 0: raise Exception('Invalid column permutation array.') shp = A.shape kind = A.getformat() if kind == 'csr': flag = 0 elif kind == 'csc': flag = 1 else: raise Exception('Input must be Qobj, CSR, or CSC matrix.') data, ind, ptr = _sparse_reverse_permute(A.data, A.indices, A.indptr, nrows, ncols, rperm, cperm, flag) if kind == 'csr': return fast_csr_matrix((data, ind, ptr), shape=shp) elif kind == 'csc': return sp.csc_matrix((data, ind, ptr), shape=shp, dtype=data.dtype)
def _jplus(j): """ Internal functions for generating the data representing the J-plus operator. """ m = np.arange(j, -j - 1, -1, dtype=complex) data = (np.sqrt(j * (j + 1.0) - (m + 1.0) * m))[1:] N = m.shape[0] ind = np.arange(1, N, dtype=np.int32) ptr = np.array(list(range(N-1))+[N-1]*2, dtype=np.int32) ptr[-1] = N-1 return fast_csr_matrix((data,ind,ptr), shape=(N,N))
def _jz(j): """ Internal functions for generating the data representing the J-z operator. """ N = int(2*j+1) data = np.array([j-k for k in range(N) if (j-k)!=0], dtype=complex) # Even shaped matrix if (N % 2 == 0): ind = np.arange(N, dtype=np.int32) ptr = np.arange(N+1,dtype=np.int32) ptr[-1] = N # Odd shaped matrix else: j = int(j) ind = np.array(list(range(j))+list(range(j+1,N)), dtype=np.int32) ptr = np.array(list(range(j+1))+list(range(j,N)), dtype=np.int32) ptr[-1] = N-1 return fast_csr_matrix((data,ind,ptr), shape=(N,N))
def num(N, offset=0): """Quantum object for number operator. Parameters ---------- N : int The dimension of the Hilbert space. offset : int (default 0) The lowest number state that is included in the finite number state representation of the operator. Returns ------- oper: qobj Qobj for number operator. Examples -------- >>> num(4) Quantum object: dims = [[4], [4]], \ shape = [4, 4], type = oper, isHerm = True Qobj data = [[0 0 0 0] [0 1 0 0] [0 0 2 0] [0 0 0 3]] """ if offset == 0: data = np.arange(1,N, dtype=complex) ind = np.arange(1,N, dtype=np.int32) ptr = np.array([0]+list(range(0,N)), dtype=np.int32) ptr[-1] = N-1 else: data = np.arange(offset, offset + N, dtype=complex) ind = np.arange(N, dtype=np.int32) ptr = np.arange(N+1,dtype=np.int32) ptr[-1] = N return Qobj(fast_csr_matrix((data,ind,ptr), shape=(N,N)), isherm=True)
def qzero(dimensions): """ Zero operator. Parameters ---------- dimensions : (int) or (list of int) or (list of list of int) Dimension of Hilbert space. If provided as a list of ints, then the dimension is the product over this list, but the ``dims`` property of the new Qobj are set to this list. This can produce either `oper` or `super` depending on the passed `dimensions`. Returns ------- qzero : qobj Zero operator Qobj. """ size, dimensions = _implicit_tensor_dimensions(dimensions) # A sparse matrix with no data is equal to a zero matrix. return Qobj(fast_csr_matrix(shape=(size, size), dtype=complex), dims=dimensions, isherm=True)
def destroy(N, offset=0): '''Destruction (lowering) operator. Parameters ---------- N : int Dimension of Hilbert space. offset : int (default 0) The lowest number state that is included in the finite number state representation of the operator. Returns ------- oper : qobj Qobj for lowering operator. Examples -------- >>> destroy(4) Quantum object: dims = [[4], [4]], \ shape = [4, 4], type = oper, isHerm = False Qobj data = [[ 0.00000000+0.j 1.00000000+0.j 0.00000000+0.j 0.00000000+0.j] [ 0.00000000+0.j 0.00000000+0.j 1.41421356+0.j 0.00000000+0.j] [ 0.00000000+0.j 0.00000000+0.j 0.00000000+0.j 1.73205081+0.j] [ 0.00000000+0.j 0.00000000+0.j 0.00000000+0.j 0.00000000+0.j]] ''' if not isinstance(N, (int, np.integer)): # raise error if N not integer raise ValueError("Hilbert space dimension must be integer value") data = np.sqrt(np.arange(offset+1, N+offset, dtype=complex)) ind = np.arange(1,N, dtype=np.int32) ptr = np.arange(N+1, dtype=np.int32) ptr[-1] = N-1 return Qobj(fast_csr_matrix((data,ind,ptr),shape=(N,N)), isherm=False)
def destroy(N, offset=0): '''Destruction (lowering) operator. Parameters ---------- N : int Dimension of Hilbert space. offset : int (default 0) The lowest number state that is included in the finite number state representation of the operator. Returns ------- oper : qobj Qobj for lowering operator. Examples -------- >>> destroy(4) # doctest: +SKIP Quantum object: dims = [[4], [4]], \ shape = [4, 4], type = oper, isHerm = False Qobj data = [[ 0.00000000+0.j 1.00000000+0.j 0.00000000+0.j 0.00000000+0.j] [ 0.00000000+0.j 0.00000000+0.j 1.41421356+0.j 0.00000000+0.j] [ 0.00000000+0.j 0.00000000+0.j 0.00000000+0.j 1.73205081+0.j] [ 0.00000000+0.j 0.00000000+0.j 0.00000000+0.j 0.00000000+0.j]] ''' if not isinstance(N, (int, np.integer)): # raise error if N not integer raise ValueError("Hilbert space dimension must be integer value") data = np.sqrt(np.arange(offset + 1, N + offset, dtype=complex)) ind = np.arange(1, N, dtype=np.int32) ptr = np.arange(N + 1, dtype=np.int32) ptr[-1] = N - 1 return Qobj(fast_csr_matrix((data, ind, ptr), shape=(N, N)), isherm=False)
def liouvillian(H, c_ops=[], data_only=False, chi=None): """Assembles the Liouvillian superoperator from a Hamiltonian and a ``list`` of collapse operators. Like liouvillian, but with an experimental implementation which avoids creating extra Qobj instances, which can be advantageous for large systems. Parameters ---------- H : Qobj or QobjEvo System Hamiltonian. c_ops : array_like of Qobj or QobjEvo A ``list`` or ``array`` of collapse operators. Returns ------- L : Qobj or QobjEvo Liouvillian superoperator. """ if isinstance(c_ops, (Qobj, QobjEvo)): c_ops = [c_ops] if chi and len(chi) != len(c_ops): raise ValueError('chi must be a list with same length as c_ops') h = None if H is not None: if isinstance(H, QobjEvo): h = H.cte else: h = H if h.isoper: op_dims = h.dims op_shape = h.shape elif h.issuper: op_dims = h.dims[0] op_shape = [np.prod(op_dims[0]), np.prod(op_dims[0])] else: raise TypeError("Invalid type for Hamiltonian.") else: # no hamiltonian given, pick system size from a collapse operator if isinstance(c_ops, list) and len(c_ops) > 0: if isinstance(c_ops[0], QobjEvo): c = c_ops[0].cte else: c = c_ops[0] if c.isoper: op_dims = c.dims op_shape = c.shape elif c.issuper: op_dims = c.dims[0] op_shape = [np.prod(op_dims[0]), np.prod(op_dims[0])] else: raise TypeError("Invalid type for collapse operator.") else: raise TypeError("Either H or c_ops must be given.") sop_dims = [[op_dims[0], op_dims[0]], [op_dims[1], op_dims[1]]] sop_shape = [np.prod(op_dims), np.prod(op_dims)] spI = fast_identity(op_shape[0]) td = False L = None if isinstance(H, QobjEvo): td = True def H2L(H): if H.isoper: return -1.0j * (spre(H) - spost(H)) else: return H L = H.apply(H2L) data = L.cte.data elif isinstance(H, Qobj): if H.isoper: Ht = H.data.T data = -1j * zcsr_kron(spI, H.data) data += 1j * zcsr_kron(Ht, spI) else: data = H.data else: data = fast_csr_matrix(shape=(sop_shape[0], sop_shape[1])) td_c_ops = [] for idx, c_op in enumerate(c_ops): if isinstance(c_op, QobjEvo): td = True if c_op.const: c_ = c_op.cte elif chi: td_c_ops.append(lindblad_dissipator(c_op, chi=chi[idx])) continue else: td_c_ops.append(lindblad_dissipator(c_op)) continue else: c_ = c_op if c_.issuper: data = data + c_.data else: cd = c_.data.H c = c_.data if chi: data = data + np.exp(1j * chi[idx]) * \ zcsr_kron(c.conj(), c) else: data = data + zcsr_kron(c.conj(), c) cdc = cd * c cdct = cdc.T data = data - 0.5 * zcsr_kron(spI, cdc) data = data - 0.5 * zcsr_kron(cdct, spI) if not td: if data_only: return data else: L = Qobj() L.dims = sop_dims L.data = data L.superrep = 'super' return L else: if not L: l = Qobj() l.dims = sop_dims l.data = data l.superrep = 'super' L = QobjEvo(l) else: L.cte.data = data for c_op in td_c_ops: L += c_op return L
def test_zcsr_isherm_compare_implicit_zero(): """ Regression test for gh-1350, comparing explicitly stored values in the matrix (but below the tolerance for allowable Hermicity) to implicit zeros. """ tol = 1e-12 n = 10 base = sp.csr_matrix(np.array([[1, tol * 1e-3j], [0, 1]])) base = fast_csr_matrix((base.data, base.indices, base.indptr), base.shape) # If this first line fails, the zero has been stored explicitly and so the # test is invalid. assert base.data.size == 3 assert zcsr_isherm(base, tol=tol) assert zcsr_isherm(base.T, tol=tol) # A similar test if the structures are different, but it's not # Hermitian. base = sp.csr_matrix(np.array([[1, 1j], [0, 1]])) base = fast_csr_matrix((base.data, base.indices, base.indptr), base.shape) assert base.data.size == 3 assert not zcsr_isherm(base, tol=tol) assert not zcsr_isherm(base.T, tol=tol) # Catch possible edge case where it shouldn't be Hermitian, but faulty loop # logic doesn't fully compare all rows. base = sp.csr_matrix( np.array([ [0, 0, 0, 0], [0, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 0], ], dtype=np.complex128)) base = fast_csr_matrix((base.data, base.indices, base.indptr), base.shape) assert base.data.size == 1 assert not zcsr_isherm(base, tol=tol) assert not zcsr_isherm(base.T, tol=tol) # Pure diagonal matrix. base = fast_identity(n) base.data *= np.random.rand(n) assert zcsr_isherm(base, tol=tol) assert not zcsr_isherm(base * 1j, tol=tol) # Larger matrices where all off-diagonal elements are below the absolute # tolerance, so everything should always appear Hermitian, but with random # patterns of non-zero elements. It doesn't matter that it isn't Hermitian # if scaled up; everything is below absolute tolerance, so it should appear # so. We also set the diagonal to be larger to the tolerance to ensure # isherm can't just compare everything to zero. for density in np.linspace(0.2, 1, 21): base = tol * 1e-2 * (np.random.rand(n, n) + 1j * np.random.rand(n, n)) # Mask some values out to zero. base[np.random.rand(n, n) > density] = 0 np.fill_diagonal(base, tol * 1000) nnz = np.count_nonzero(base) base = sp.csr_matrix(base) base = fast_csr_matrix((base.data, base.indices, base.indptr), (n, n)) assert base.data.size == nnz assert zcsr_isherm(base, tol=tol) assert zcsr_isherm(base.T, tol=tol) # Similar test when it must be non-Hermitian. We set the diagonal to # be real because we want to test off-diagonal implicit zeros, and # having an imaginary first element would automatically fail. nnz = 0 while nnz <= n: # Ensure that we don't just have the real diagonal. base = tol * 1000j * np.random.rand(n, n) # Mask some values out to zero. base[np.random.rand(n, n) > density] = 0 np.fill_diagonal(base, tol * 1000) nnz = np.count_nonzero(base) base = sp.csr_matrix(base) base = fast_csr_matrix((base.data, base.indices, base.indptr), (n, n)) assert base.data.size == nnz assert not zcsr_isherm(base, tol=tol) assert not zcsr_isherm(base.T, tol=tol)
def configure(self, H_sys, coup_op, coup_strength, temperature, N_cut, N_exp, cut_freq, planck=None, boltzmann=None, renorm=None, bnd_cut_approx=None, options=None, progress_bar=None, stats=None): """ Calls configure from :class:`HEOMSolver` and sets any attributes that are specific to this subclass """ start_config = timeit.default_timer() HEOMSolver.configure(self, H_sys, coup_op, coup_strength, temperature, N_cut, N_exp, planck=planck, boltzmann=boltzmann, options=options, progress_bar=progress_bar, stats=stats) self.cut_freq = cut_freq if renorm is not None: self.renorm = renorm if bnd_cut_approx is not None: self.bnd_cut_approx = bnd_cut_approx # Load local values for optional parameters # Constants and Hamiltonian. hbar = self.planck options = self.options progress_bar = self.progress_bar stats = self.stats if stats: ss_conf = stats.sections.get('config') if ss_conf is None: ss_conf = stats.add_section('config') c, nu = self._calc_matsubara_params() if renorm: norm_plus, norm_minus = self._calc_renorm_factors() if stats: stats.add_message('options', 'renormalisation', ss_conf) # Dimensions et by system N_temp = 1 for i in H_sys.dims[0]: N_temp *= i sup_dim = N_temp**2 unit_sys = qeye(N_temp) # Use shorthands (mainly as in referenced PRL) lam0 = self.coup_strength gam = self.cut_freq N_c = self.N_cut N_m = self.N_exp Q = coup_op # Q as shorthand for coupling operator beta = 1.0/(self.boltzmann*self.temperature) # Ntot is the total number of ancillary elements in the hierarchy # Ntot = factorial(N_c + N_m) / (factorial(N_c)*factorial(N_m)) # Turns out to be the same as nstates from state_number_enumerate N_he, he2idx, idx2he = enr_state_dictionaries([N_c + 1]*N_m , N_c) unit_helems = fast_identity(N_he) if self.bnd_cut_approx: # the Tanimura boundary cut off operator if stats: stats.add_message('options', 'boundary cutoff approx', ss_conf) op = -2*spre(Q)*spost(Q.dag()) + spre(Q.dag()*Q) + spost(Q.dag()*Q) approx_factr = ((2*lam0 / (beta*gam*hbar)) - 1j*lam0) / hbar for k in range(N_m): approx_factr -= (c[k] / nu[k]) L_bnd = -approx_factr*op.data L_helems = zcsr_kron(unit_helems, L_bnd) else: L_helems = fast_csr_matrix(shape=(N_he*sup_dim, N_he*sup_dim)) # Build the hierarchy element interaction matrix if stats: start_helem_constr = timeit.default_timer() unit_sup = spre(unit_sys).data spreQ = spre(Q).data spostQ = spost(Q).data commQ = (spre(Q) - spost(Q)).data N_he_interact = 0 for he_idx in range(N_he): he_state = list(idx2he[he_idx]) n_excite = sum(he_state) # The diagonal elements for the hierarchy operator # coeff for diagonal elements sum_n_m_freq = 0.0 for k in range(N_m): sum_n_m_freq += he_state[k]*nu[k] op = -sum_n_m_freq*unit_sup L_he = cy_pad_csr(op, N_he, N_he, he_idx, he_idx) L_helems += L_he # Add the neighour interations he_state_neigh = copy(he_state) for k in range(N_m): n_k = he_state[k] if n_k >= 1: # find the hierarchy element index of the neighbour before # this element, for this Matsubara term he_state_neigh[k] = n_k - 1 he_idx_neigh = he2idx[tuple(he_state_neigh)] op = c[k]*spreQ - np.conj(c[k])*spostQ if renorm: op = -1j*norm_minus[n_k, k]*op else: op = -1j*n_k*op L_he = cy_pad_csr(op, N_he, N_he, he_idx, he_idx_neigh) L_helems += L_he N_he_interact += 1 he_state_neigh[k] = n_k if n_excite <= N_c - 1: # find the hierarchy element index of the neighbour after # this element, for this Matsubara term he_state_neigh[k] = n_k + 1 he_idx_neigh = he2idx[tuple(he_state_neigh)] op = commQ if renorm: op = -1j*norm_plus[n_k, k]*op else: op = -1j*op L_he = cy_pad_csr(op, N_he, N_he, he_idx, he_idx_neigh) L_helems += L_he N_he_interact += 1 he_state_neigh[k] = n_k if stats: stats.add_timing('hierarchy contruct', timeit.default_timer() - start_helem_constr, ss_conf) stats.add_count('Num hierarchy elements', N_he, ss_conf) stats.add_count('Num he interactions', N_he_interact, ss_conf) # Setup Liouvillian if stats: start_louvillian = timeit.default_timer() H_he = zcsr_kron(unit_helems, liouvillian(H_sys).data) L_helems += H_he if stats: stats.add_timing('Liouvillian contruct', timeit.default_timer() - start_louvillian, ss_conf) if stats: start_integ_conf = timeit.default_timer() r = scipy.integrate.ode(cy_ode_rhs) r.set_f_params(L_helems.data, L_helems.indices, L_helems.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) if stats: time_now = timeit.default_timer() stats.add_timing('Liouvillian contruct', time_now - start_integ_conf, ss_conf) if ss_conf.total_time is None: ss_conf.total_time = time_now - start_config else: ss_conf.total_time += time_now - start_config self._ode = r self._N_he = N_he self._sup_dim = sup_dim self._configured = True
def gather(self): """ Create the HEOM liouvillian from a sorted list of smaller (fast) CSR matrices. .. note:: The list of operators contains tuples of the form ``(row_idx, col_idx, op)``. The row_idx and col_idx give the *block* row and column for each op. An operator with block indices ``(N, M)`` is placed at position ``[N * block: (N + 1) * block, M * block: (M + 1) * block]`` in the output matrix. Returns ------- rhs : :obj:`qutip.fastsparse.fast_csr_matrix` A combined matrix of shape ``(block * nhe, block * ne)``. """ block = self._block nhe = self._nhe ops = self._ops shape = (block * nhe, block * nhe) if not ops: return sp.csr_matrix(shape, dtype=np.complex128) ops.sort() nnz = sum(op.nnz for _, _, op in ops) indptr = np.zeros(shape[0] + 1, dtype=np.int32) indices = np.zeros(nnz, dtype=np.int32) data = np.zeros(nnz, dtype=np.complex128) end = 0 op_idx = 0 op_len = len(ops) for row_idx in range(nhe): prev_op_idx = op_idx while op_idx < op_len: if ops[op_idx][0] != row_idx: break op_idx += 1 row_ops = ops[prev_op_idx:op_idx] rowpos = row_idx * block for op_row in range(block): for _, col_idx, op in row_ops: colpos = col_idx * block op_row_start = op.indptr[op_row] op_row_end = op.indptr[op_row + 1] op_row_len = op_row_end - op_row_start if op_row_len == 0: continue indices[end:end + op_row_len] = ( op.indices[op_row_start:op_row_end] + colpos) data[end:end + op_row_len] = (op.data[op_row_start:op_row_end]) end += op_row_len indptr[rowpos + op_row + 1] = end return fast_csr_matrix( (data, indices, indptr), shape=shape, dtype=np.complex128, )
def basis(dimensions, n=None, offset=None): """Generates the vector representation of a Fock state. Parameters ---------- dimensions : int or list of ints Number of Fock states in Hilbert space. If a list, then the resultant object will be a tensor product over spaces with those dimensions. n : int or list of ints, optional (default 0 for all dimensions) Integer corresponding to desired number state, defaults to 0 for all dimensions if omitted. The shape must match ``dimensions``, e.g. if ``dimensions`` is a list, then ``n`` must either be omitted or a list of equal length. offset : int or list of ints, optional (default 0 for all dimensions) The lowest number state that is included in the finite number state representation of the state in the relevant dimension. Returns ------- state : :class:`qutip.Qobj` Qobj representing the requested number state ``|n>``. Examples -------- >>> basis(5,2) # doctest: +SKIP Quantum object: dims = [[5], [1]], shape = (5, 1), type = ket Qobj data = [[ 0.+0.j] [ 0.+0.j] [ 1.+0.j] [ 0.+0.j] [ 0.+0.j]] >>> basis([2,2,2], [0,1,0]) # doctest: +SKIP Quantum object: dims = [[2, 2, 2], [1, 1, 1]], shape = (8, 1), type = ket Qobj data = [[0.] [0.] [1.] [0.] [0.] [0.] [0.] [0.]] Notes ----- A subtle incompatibility with the quantum optics toolbox: In QuTiP:: basis(N, 0) = ground state but in the qotoolbox:: basis(N, 1) = ground state """ # Promote all parameters to lists to simplify later logic. if not isinstance(dimensions, list): dimensions = [dimensions] n_dimensions = len(dimensions) ns = [ m - off for m, off in zip(_promote_to_zero_list(n, n_dimensions), _promote_to_zero_list(offset, n_dimensions)) ] if any((not isinstance(x, numbers.Integral)) or x < 0 for x in dimensions): raise ValueError("All dimensions must be >= 0.") if not all(0 <= n < dimension for n, dimension in zip(ns, dimensions)): raise ValueError("All basis indices must be " "`offset <= n < dimension+offset`.") location, size = 0, 1 for m, dimension in zip(reversed(ns), reversed(dimensions)): location += m * size size *= dimension data = np.array([1], dtype=complex) ind = np.array([0], dtype=np.int32) ptr = np.array([0] * (location + 1) + [1] * (size - location), dtype=np.int32) return Qobj(fast_csr_matrix((data, ind, ptr), shape=(size, 1)), dims=[dimensions, [1] * n_dimensions], isherm=False)
def liouvillian(H, c_ops=[], data_only=False, chi=None): """Assembles the Liouvillian superoperator from a Hamiltonian and a ``list`` of collapse operators. Like liouvillian, but with an experimental implementation which avoids creating extra Qobj instances, which can be advantageous for large systems. Parameters ---------- H : qobj System Hamiltonian. c_ops : array_like A ``list`` or ``array`` of collapse operators. Returns ------- L : qobj Liouvillian superoperator. """ if chi and len(chi) != len(c_ops): raise ValueError('chi must be a list with same length as c_ops') if H is not None: if H.isoper: op_dims = H.dims op_shape = H.shape elif H.issuper: op_dims = H.dims[0] op_shape = [np.prod(op_dims[0]), np.prod(op_dims[0])] else: raise TypeError("Invalid type for Hamiltonian.") else: # no hamiltonian given, pick system size from a collapse operator if isinstance(c_ops, list) and len(c_ops) > 0: c = c_ops[0] if c.isoper: op_dims = c.dims op_shape = c.shape elif c.issuper: op_dims = c.dims[0] op_shape = [np.prod(op_dims[0]), np.prod(op_dims[0])] else: raise TypeError("Invalid type for collapse operator.") else: raise TypeError("Either H or c_ops must be given.") sop_dims = [[op_dims[0], op_dims[0]], [op_dims[1], op_dims[1]]] sop_shape = [np.prod(op_dims), np.prod(op_dims)] spI = fast_identity(op_shape[0]) if H: if H.isoper: Ht = H.data.T data = -1j * zcsr_kron(spI, H.data) data += 1j * zcsr_kron(Ht, spI) else: data = H.data else: data = fast_csr_matrix(shape=(sop_shape[0], sop_shape[1])) for idx, c_op in enumerate(c_ops): if c_op.issuper: data = data + c_op.data else: cd = c_op.data.H c = c_op.data if chi: data = data + np.exp(1j * chi[idx]) * \ zcsr_kron(c.conj(), c) else: data = data + zcsr_kron(c.conj(), c) cdc = cd * c cdct = cdc.T data = data - 0.5 * zcsr_kron(spI, cdc) data = data - 0.5 * zcsr_kron(cdct, spI) if data_only: return data else: L = Qobj() L.dims = sop_dims L.data = data L.isherm = False L.superrep = 'super' return L
def configure(self, H_sys, coup_op, coup_strength, temperature, N_cut, N_exp, cut_freq, planck=None, boltzmann=None, renorm=None, bnd_cut_approx=None, options=None, progress_bar=None, stats=None): """ Calls configure from :class:`HEOMSolver` and sets any attributes that are specific to this subclass """ start_config = timeit.default_timer() HEOMSolver.configure(self, H_sys, coup_op, coup_strength, temperature, N_cut, N_exp, planck=planck, boltzmann=boltzmann, options=options, progress_bar=progress_bar, stats=stats) self.cut_freq = cut_freq if renorm is not None: self.renorm = renorm if bnd_cut_approx is not None: self.bnd_cut_approx = bnd_cut_approx # Load local values for optional parameters # Constants and Hamiltonian. hbar = self.planck options = self.options progress_bar = self.progress_bar stats = self.stats if stats: ss_conf = stats.sections.get('config') if ss_conf is None: ss_conf = stats.add_section('config') c, nu = self._calc_matsubara_params() if renorm: norm_plus, norm_minus = self._calc_renorm_factors() if stats: stats.add_message('options', 'renormalisation', ss_conf) # Dimensions et by system sup_dim = H_sys.dims[0][0]**2 unit_sys = qeye(H_sys.dims[0]) # Use shorthands (mainly as in referenced PRL) lam0 = self.coup_strength gam = self.cut_freq N_c = self.N_cut N_m = self.N_exp Q = coup_op # Q as shorthand for coupling operator beta = 1.0 / (self.boltzmann * self.temperature) # Ntot is the total number of ancillary elements in the hierarchy # Ntot = factorial(N_c + N_m) / (factorial(N_c)*factorial(N_m)) # Turns out to be the same as nstates from state_number_enumerate N_he, he2idx, idx2he = enr_state_dictionaries([N_c + 1] * N_m, N_c) unit_helems = fast_identity(N_he) if self.bnd_cut_approx: # the Tanimura boundary cut off operator if stats: stats.add_message('options', 'boundary cutoff approx', ss_conf) op = -2 * spre(Q) * spost(Q.dag()) + spre(Q.dag() * Q) + spost( Q.dag() * Q) approx_factr = ((2 * lam0 / (beta * gam * hbar)) - 1j * lam0) / hbar for k in range(N_m): approx_factr -= (c[k] / nu[k]) L_bnd = -approx_factr * op.data L_helems = zcsr_kron(unit_helems, L_bnd) else: L_helems = fast_csr_matrix(shape=(N_he * sup_dim, N_he * sup_dim)) # Build the hierarchy element interaction matrix if stats: start_helem_constr = timeit.default_timer() unit_sup = spre(unit_sys).data spreQ = spre(Q).data spostQ = spost(Q).data commQ = (spre(Q) - spost(Q)).data N_he_interact = 0 for he_idx in range(N_he): he_state = list(idx2he[he_idx]) n_excite = sum(he_state) # The diagonal elements for the hierarchy operator # coeff for diagonal elements sum_n_m_freq = 0.0 for k in range(N_m): sum_n_m_freq += he_state[k] * nu[k] op = -sum_n_m_freq * unit_sup L_he = cy_pad_csr(op, N_he, N_he, he_idx, he_idx) L_helems += L_he # Add the neighour interations he_state_neigh = copy(he_state) for k in range(N_m): n_k = he_state[k] if n_k >= 1: # find the hierarchy element index of the neighbour before # this element, for this Matsubara term he_state_neigh[k] = n_k - 1 he_idx_neigh = he2idx[tuple(he_state_neigh)] op = c[k] * spreQ - np.conj(c[k]) * spostQ if renorm: op = -1j * norm_minus[n_k, k] * op else: op = -1j * n_k * op L_he = cy_pad_csr(op, N_he, N_he, he_idx, he_idx_neigh) L_helems += L_he N_he_interact += 1 he_state_neigh[k] = n_k if n_excite <= N_c - 1: # find the hierarchy element index of the neighbour after # this element, for this Matsubara term he_state_neigh[k] = n_k + 1 he_idx_neigh = he2idx[tuple(he_state_neigh)] op = commQ if renorm: op = -1j * norm_plus[n_k, k] * op else: op = -1j * op L_he = cy_pad_csr(op, N_he, N_he, he_idx, he_idx_neigh) L_helems += L_he N_he_interact += 1 he_state_neigh[k] = n_k if stats: stats.add_timing('hierarchy contruct', timeit.default_timer() - start_helem_constr, ss_conf) stats.add_count('Num hierarchy elements', N_he, ss_conf) stats.add_count('Num he interactions', N_he_interact, ss_conf) # Setup Liouvillian if stats: start_louvillian = timeit.default_timer() H_he = zcsr_kron(unit_helems, liouvillian(H_sys).data) L_helems += H_he if stats: stats.add_timing('Liouvillian contruct', timeit.default_timer() - start_louvillian, ss_conf) if stats: start_integ_conf = timeit.default_timer() r = scipy.integrate.ode(cy_ode_rhs) r.set_f_params(L_helems.data, L_helems.indices, L_helems.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) if stats: time_now = timeit.default_timer() stats.add_timing('Liouvillian contruct', time_now - start_integ_conf, ss_conf) if ss_conf.total_time is None: ss_conf.total_time = time_now - start_config else: ss_conf.total_time += time_now - start_config self._ode = r self._N_he = N_he self._sup_dim = sup_dim self._configured = True