def lindbladian(l, rho): """ lindblad superoperator: l rho l^\dag - 1/2 * {l^\dag l, rho} l is the operator corresponding to the disired physical process e.g. l = a, for the cavity decay and l = sm for polarization decay """ return l.dot(rho.dot(dag(l))) - 0.5 * anticomm(dag(l).dot(l), rho)
def eigenstates(self, k=None): if self.L is None: # raise ValueError('L is None. Call liouvillian to construct L first.') L = self.liouvillian() else: L = self.L N = L.shape[-1] if k is None: w, vl, vr = scipy.linalg.eig(L.toarray(), left=True, \ right=True) self.eigvals = w self.left_eigvecs = vl self.right_eigvecs = vr # self.norm = [np.vdot(vl[:,n], vr[:,n]) for n in range(self.dim)] self.norm = np.diagonal(cdot(vl, vr)).real elif k < N - 1: # right eigenvectors of L evals1, U1 = eigs(L, k=k, which='LR') evals1, U1 = sort(evals1, U1) # left evals2, U2 = eigs(dag(L), k=k, which='LR') evals2, U2 = sort(evals2, U2) else: raise ValueError('k should be < the size of the matrix.') return w, vr, vl
def rdm(self, psi, which='A'): """ compute the reduced density matrix of A/B from a pure state Parameters ---------- psi: array pure state which: str indicator of which rdm A or B. Default 'A'. Returns ------- """ na = self.A.dim nb = self.B.dim psi_reshaped = psi.reshape((na, nb)) if which == 'A': rdm = psi_reshaped @ dag(psi_reshaped) return rdm elif which == 'B': rdm = psi_reshaped.T @ psi_reshaped.conj() return rdm else: raise ValueError('which option can only be A or B.')
def rdm(f, dx=1, dy=1, which='x'): ''' Compute the reduced density matrix by tracing out the other dof for a 2D wavefunction Parameters ---------- f : 2D array 2D wavefunction dx : float, optional DESCRIPTION. The default is 1. dy : float, optional DESCRIPTION. The default is 1. which: str indicator which rdm is required. Default is 'x'. Returns ------- rho1 : TYPE Reduced density matrix ''' if which == 'x': rho = f.dot(dag(f)) * dy elif which == 'y': rho = f.T.dot(np.conj(f)) * dx else: raise ValueError('The argument which can only be x or y.') return rho
def correlation_3p_1t(self, psi0, oplist, dt, Nt): """ <AB(t)C> Parameters ---------- psi0 oplist dt Nt Returns ------- """ H = self.H a_op, b_op, c_op = oplist corr_vec = np.zeros(Nt, dtype=complex) psi_ket = _quantum_dynamics(H, c_op @ psi0, dt=dt, Nt=Nt).psilist psi_bra = _quantum_dynamics(H, dag(a_op) @ psi0, dt=dt, Nt=Nt).psilist for j in range(Nt): corr_vec[j] = np.vdot(psi_bra[j], b_op @ psi_ket[j]) return corr_vec
def linear_absorption(omegas, ham, dip): """ Note that the eigenvectors of the liouvillian L and L^\dag have to be ordered in parallel! Parameters ---------- omegas : TYPE DESCRIPTION. ham : TYPE DESCRIPTION. dip : TYPE DESCRIPTION. rho0 : 2d array initial density matrix c_ops : TYPE, optional DESCRIPTION. The default is []. ntrans : int, optional Number of transitions to be computed. The default is 1. Returns ------- signal : TYPE DESCRIPTION. """ ntrans = np.size(ham, 0) eigvals1, U1 = eig(ham) eigvals1, U1 = sort(eigvals1, U1) eigvals2, U2 = eig(dag(ham)) eigvals2, U2 = sort(eigvals2, U2) norm = [np.vdot(U2[:, n], U1[:, n]) for n in range(ntrans)] signal = np.zeros(len(omegas), dtype=complex) tmp = [np.vdot(dip, U1[:,n]) * np.vdot(U2[:,n], dip)/norm[n] \ for n in range(ntrans)] for j in range(len(omegas)): omega = omegas[j] signal[j] += sum(tmp / (omega - eigvals1)) return -2. * signal.imag, eigvals1, dag(U1).dot(dip)
def coherent(N, alpha): """Generates a coherent state with eigenvalue alpha. Constructed using displacement operator on vacuum state. Modified from Qutip. Parameters ---------- N : int Number of Fock states in Hilbert space. alpha : float/complex Eigenvalue of coherent state. offset : int (default 0) The lowest number state that is included in the finite number state representation of the state. Using a non-zero offset will make the default method 'analytic'. method : string {'operator', 'analytic'} Method for generating coherent state. Returns ------- state : qobj Qobj quantum object for coherent state Examples -------- >>> coherent(5,0.25j) Quantum object: dims = [[5], [1]], shape = [5, 1], type = ket Qobj data = [[ 9.69233235e-01+0.j ] [ 0.00000000e+00+0.24230831j] [ -4.28344935e-02+0.j ] [ 0.00000000e+00-0.00618204j] [ 7.80904967e-04+0.j ]] Notes ----- Select method 'operator' (default) or 'analytic'. With the 'operator' method, the coherent state is generated by displacing the vacuum state using the displacement operator defined in the truncated Hilbert space of size 'N'. This method guarantees that the resulting state is normalized. With 'analytic' method the coherent state is generated using the analytical formula for the coherent state coefficients in the Fock basis. This method does not guarantee that the state is normalized if truncated to a small number of Fock states, but would in that case give more accurate coefficients. """ x = basis(N, 0) a = destroy(N) D = la.expm(alpha * dag(a) - np.conj(alpha) * a) return D @ x
def func(rho, h0, c_ops, l_ops): """ right-hand side of the master equation """ rhs = -1j * commutator(h0, rho) for i in range(len(c_ops)): c_op = c_ops[i] l_op = l_ops[i] rhs -= commutator(c_op, l_op.dot(rho) - rho.dot(dag(l_op))) return rhs
def linear_absorption(omegas, ham, dip, rho0, c_ops=[], ntrans=1): """ Note that the eigenvectors of the liouvillian L and L^\dag have to be ordered in parallel! Parameters ---------- omegas : TYPE DESCRIPTION. ham : TYPE DESCRIPTION. dip : TYPE DESCRIPTION. rho0 : 2d array initial density matrix c_ops : TYPE, optional DESCRIPTION. The default is []. ntrans : int, optional Number of transitions to be computed. The default is 1. Returns ------- signal : TYPE DESCRIPTION. """ # dissipation dissipator = 0. for c_op in c_ops: dissipator += lindblad_dissipator(c_op) liouvillian = operator_to_superoperator(ham) + 1j * dissipator eigvals1, U1 = eigs(liouvillian, k=ntrans, which='LR') eigvals1, U1 = sort(eigvals1, U1) eigvals2, U2 = eigs(dag(liouvillian), k=ntrans, which='LR') eigvals2, U2 = sort(eigvals2, U2) norm = [np.vdot(U2[:, n], U1[:, n]) for n in range(ntrans)] signal = np.zeros(len(omegas), dtype=complex) tmp = [np.vdot(dip.flatten(), U1[:,n]) * np.vdot(U2[:,n], \ dip.dot(rho0).flatten()) / norm[n] for n in range(ntrans)] for j in range(len(omegas)): omega = omegas[j] signal[j] += sum(tmp / (omega - eigvals1)) return -2. * signal.imag
def redfield_tensor(H, a_ops, spectra, secular=False): for a in a_ops: if issparse(a): if not isherm(a.todense()): raise TypeError("Operators in a_ops must be Hermitian.") elif not isherm(a): raise TypeError("Operators in a_ops must be Hermitian.") if issparse(H): evals, evecs = eigh(H.todense()) else: evals, evecs = eigh(H) print('eigenvalues', evals) # pre-calculate matrix elements and spectral densities # W[m,n] = real(evals[m] - evals[n]) W = np.real(evals[:, np.newaxis] - evals[np.newaxis, :]) # bath spectral density at transition energies K = len(a_ops) N = len(evals) C = [] for k in range(K): c = np.zeros((N, N)) for n in range(N): for m in range(N): c[n, m] = spectra[k](-W[n, m]) C.append(c) # C = [s(-W) for s in spectra] # transfrom a_ops to eigenbasis A = [transform(a, evecs) for a in a_ops] # calc the lambda operators L = [C[k] * A[k] for k in range(K)] R = 0 # superoperator for k in range(K): a = A[k] l = L[k] R += op2sop(a).dot(left(l) - right(dag(l))) return csr_matrix(-1j * op2sop(np.diag(evals)) - R), evecs
def absorption_eseries(omegas, L, edip, rho0, ntrans=1): """ Compute absorption spectrum by diagonalizing the liouvillian L. Note that the eigenvectors of the L and L^\dag have to be ordered in parallel! Parameters ---------- omegas : TYPE frequencies for the signal L : ndarray liouvillian superoperator edip : TYPE electric dipole. rho0 : 2d array initial density matrix c_ops : TYPE, optional DESCRIPTION. The default is []. ntrans : int, optional Number of transitions to be computed. The default is 1. Returns ------- signal : TYPE DESCRIPTION. """ eigvals1, U1 = eigs(L, k=ntrans, which='LR') eigvals1, U1 = sort(eigvals1, U1) eigvals2, U2 = eigs(dag(L), k=ntrans, which='LR') eigvals2, U2 = sort(eigvals2, U2) norm = [np.vdot(U2[:, n], U1[:, n]) for n in range(ntrans)] signal = np.zeros(len(omegas), dtype=complex) tmp = [np.vdot(edip.flatten(), U1[:,n]) * np.vdot(U2[:,n], \ edip.dot(rho0).flatten()) / norm[n] for n in range(ntrans)] for j in range(len(omegas)): omega = omegas[j] signal[j] += sum(tmp / (omega - eigvals1)) return -2. * signal.imag
def cdot(a, b): """ matrix product of a.H.dot(b) Parameters ---------- a : TYPE DESCRIPTION. b : TYPE DESCRIPTION. Returns ------- None. """ return dag(a) @ b
def kraus(a): """ Kraus superoperator a rho a^\dag = a^\dag_R a_L Parameters ---------- a : TYPE DESCRIPTION. Returns ------- TYPE DESCRIPTION. """ al = left(a) ar = right(dag(a)) return ar.dot(al)
def correlation_3p_2t(self, psi0, oplist, dt, Nt, Ntau): """ <A(t)B(t+tau)C(t)> Parameters ---------- oplist: list of arrays [a, b, c] psi0: array initial state dt nt: integer number of time steps for t ntau: integer time steps for tau Returns ------- """ H = self.H psi_t = _quantum_dynamics(H, psi0, dt=dt, Nt=Nt).psilist a_op, b_op, c_op = oplist corr_mat = np.zeros([Nt, Ntau], dtype=complex) for t_idx, psi in enumerate(psi_t): psi_tau_ket = _quantum_dynamics(H, c_op @ psi, dt=dt, Nt=Ntau).psilist psi_tau_bra = _quantum_dynamics(H, dag(a_op) @ psi, dt=dt, Nt=Ntau).psilist corr_mat[t_idx, :] = [ np.vdot(psi_tau_bra[j], b_op @ psi_tau_ket[j]) for j in range(Ntau) ] return corr_mat
def polarizability(w, Er, Ev, d, use_rwa=True): """ Compute the vibrational/electronic polarizability using sum-over-states formula \alpha_{ji}(w) = d_{jv} d_{vi}/(E_v - E_i - w) Parameters ---------- w : TYPE DESCRIPTION. Er : TYPE eigenenergies of resonant states. Ev : TYPE eigenenergies of virtual states. d : TYPE DESCRIPTION. use_rwa : TYPE, optional DESCRIPTION. The default is True. Returns ------- a : TYPE DESCRIPTION. """ ne = len(Er) nv = len(Ev) assert d.shape == (nv, ne) # denominator dE = Ev[:, np.newaxis] - Er - w a = dag(d).dot(d / dE) return a
def schmidt_decompose(f, dp, dq, nmodes=5, method='rdm'): """ kernel method f: 2D array, input function to be decomposed nmodes: int number of modes to be kept method: str rdm or svd """ if method == 'rdm': kernel1 = f.dot(dag(f)) * dq * dp kernel2 = f.T.dot(f.conj()) * dp * dq print('c: Schmidt coefficients') s, phi = np.linalg.eig(kernel1) s1, psi = np.linalg.eig(kernel2) phi /= np.sqrt(dp) psi /= np.sqrt(dq) elif method == 'svd': raise NotImplementedError return np.sqrt(s[:nmodes]), phi[:, :nmodes], psi[:, :nmodes]
dm.setdiag(w) return dm.tocsr() N = 10 cav = Cavity(1.0, N) H = cav.hamiltonian H = csr_matrix(H) kappa = 0.25 n_th = 2.0 # bath temperature in terms of excitation number a = cav.get_annihilate() c_ops = [np.sqrt(kappa * (1 + n_th)) * a, np.sqrt(kappa * n_th) * dag(a)] num_op = cav.get_num() rho0 = thermal_dm(N, 2) print(rho0) tlist = np.linspace(0, 10, 200) corr(H, rho0, [dag(a), num_op, a], c_ops, tlist, lindblad) t, corr = np.genfromtxt('cor.dat', unpack=True, dtype=complex) import matplotlib.pyplot as plt
def absorption(mol, omegas, c_ops): """ superoperator formalism for absorption spectrum Parameters ---------- mol : TYPE DESCRIPTION. omegas: vector detection window of the spectrum c_ops : TYPE list of collapse operators Returns ------- None. """ gamma = 0.02 l = op2sop(H) + 1j * c_op.to_linblad(gamma=gamma) ntrans = 3 * nstates # number of transitions eigvals1, U1 = eigs(l, k=ntrans, which='LR') eigvals1, U1 = sort(eigvals1, U1) # print(eigvals1) omegas = np.linspace(0.1, 10.5, 200) rho0 = Qobj(dims=[10, 10]) rho0.data[0, 0] = 1.0 ops = [sz, sz] # out = correlation_2p_1t(omegas, rho0, ops, L) # print(eigvecs) eigvals2, U2 = eigs(dag(l), k=ntrans, which='LR') eigvals2, U2 = sort(eigvals2, U2) #idx = np.where(eigvals2.real > 0.2)[0] #print(idx) norm = [np.vdot(U2[:, n], U1[:, n]) for n in range(ntrans)] la = np.zeros(len(omegas), dtype=complex) # linear absorption for j, omega in enumerate(omegas): for n in range(ntrans): la[j] += np.vdot(dip.to_vector(), U1[:,n]) * \ np.vdot(U2[:,n], dip.dot(rho0).to_vector()) \ /(omega - eigvals1[n]) / norm[n] fig, ax = plt.subplots() # ax.scatter(eigvals1.real, eigvals1.imag) ax.plot(omegas, -2 * la.imag) return
def lindblad_dissipator(l, gamma=1.): return gamma * (kron(l, l.conj()) - operator_to_superoperator( dag(l).dot(l), type='anticommutator'))
def obs(rho, a): return np.vdot(operator_to_vector(dag(a)), rho)
def _redfield(R, rho0, evecs=None, Nt=1, dt=0.005, t0=0, e_ops=[], return_result=True): """ time propagation of the Redfield quantum master equation with RK4 Input ------- R: 2d array Redfield tensor rho0: 2d array initial density matrix Nt: total number of time steps dt: time step e_ops: list of observable operators Returns ========= rho: 2D array density matrix at time t = Nt * dt """ N = rho0.shape[0] # initialize the density matrix if e_ops is None: e_ops = [] # basis transformation if evecs is not None: rho0 = transform(rho0, evecs) e_ops = [transform(e, evecs) for e in e_ops] rho = rho0.copy() rho = dm2vec(rho).astype(complex) # tf = t0 + dt * Nt # result = solve_ivp(rhs, t_span=(t0, tf), y0=rho0, vectorized=True, args=(R, )) t = t0 if return_result == False: f_obs = open('obs.dat', 'w') fmt = '{} ' * (len(e_ops) + 1) + '\n' for k in range(Nt): # compute observables # observables = np.zeros(len(e_ops), dtype=complex) # for i, obs_op in enumerate(e_ops): observables = [obs_dm(rho, e) for e in e_ops] t += dt rho = rk4(rho, rhs, dt, R) # dipole-dipole auto-corrlation function #cor = np.trace(np.matmul(d, rho)) # take a partial trace to obtain the rho_el f_obs.write(fmt.format(t, *observables)) f_obs.close() # f_dm.close() return rho else: rholist = [] # store density matries result = Result(dt=dt, Nt=Nt, rho0=rho0) observables = np.zeros((Nt, len(e_ops)), dtype=complex) for k in range(Nt): t += dt rho = rk4(rho, rhs, dt, R) tmp = np.reshape(rho, (N, N)) rholist.append(transform(tmp, dag(evecs))) observables[k, :] = [obs_dm(tmp, e) for e in e_ops] result.observables = observables result.rholist = rholist return result
def to_linblad(self, gamma=1.): l = self.data return gamma * (kron(l, l.conj()) - \ operator_to_superoperator(dag(l).dot(l), type='anticommutator'))
def lindblad_dissipator(l): return kron(l, l.conj()) - 0.5 *\ operator_to_superoperator(dag(l).dot(l), kind='anticommutator')
eigvals1, U1 = eigs(l, k=ntrans, which='LR') eigvals1, U1 = sort(eigvals1, U1) print(eigvals1.real) omegas = np.linspace(0.1, 10.5, 200) rho0 = Qobj(dims=[10, 10]) rho0.data[0, 0] = 1.0 ops = [sz, sz] # out = correlation_2p_1t(omegas, rho0, ops, L) # print(eigvecs) eigvals2, U2 = eigs(dag(l), k=ntrans, which='LR') eigvals2, U2 = sort(eigvals2, U2) #idx = np.where(eigvals2.real > 0.2)[0] #print(idx) norm = [np.vdot(U2[:, n], U1[:, n]) for n in range(ntrans)] la = np.zeros(len(omegas), dtype=complex) # linear absorption for j, omega in enumerate(omegas): for n in range(ntrans): la[j] += np.vdot(dip.to_vector(), U1[:,n]) * \ np.vdot(U2[:,n], dip.dot(rho0).to_vector()) \