def create_swap_unitary(psi1, psi2): psi1 = Qobj(psi1).unit() psi2 = Qobj(psi2).unit() op = qeye(psi1.dims[0]) - psi1 * psi1.dag() - psi2 * psi2.dag() op_ = op + psi1 * psi2.dag() + psi2 * psi1.dag() # print("psi1:\n{}\npsi2:\n{}" # "".format(psi1, psi2)) return op_
def test_Transformation4(): "Transform 10-level imag to eigenbasis and back" N = 10 H1 = Qobj(1j * (0.5 - scipy.rand(N, N))) H1 = H1 + H1.dag() evals, ekets = H1.eigenstates() Heb = H1.transform(ekets) # eigenbasis (should be diagonal) H2 = Heb.transform(ekets, True) # back to original basis assert_equal((H1 - H2).norm() < 1e-6, True)
def trace_norm(self): """ Calculates trace norms of density state of matrix - to use this as a metric, take the difference (p_0 - p_1) and return the value of the trace distance Return: dist (float): trace distance of difference between density matrices """ current = Qobj(self.current_state) goal = Qobj(self.goal_state) density_1 = current * current.dag() density_2 = goal * goal.dag() dist = tracedist(density_1, density_2) return dist
def rand_herm(N,density=0.75,dims=None): """Creates a random NxN sparse Hermitian quantum object. Uses :math:`H=X+X^{+}` where :math:`X` is a randomly generated quantum operator with a given `density`. Parameters ---------- N : int Shape of output quantum operator. density : float Density etween [0,1] of output Hermitian operator. Returns ------- oper : qobj NxN Hermitian quantum operator. Other Parameters ---------------- dims : list Dimensions of quantum object. Used for specifying tensor structure. Default is dims=[[N],[N]]. """ if dims: _check_dims(dims,N,N) # to get appropriate density of output # Hermitian operator must convert via: herm_density=2.0*arcsin(density)/pi X = sp.rand(N,N,herm_density,format='csr') X.data=X.data-0.5 Y=X.copy() Y.data=1.0j*np.random.random(len(X.data))-(0.5+0.5j) X=X+Y X=Qobj(X) if dims: return Qobj((X+X.dag())/2.0,dims=dims,shape=[N,N]) else: return Qobj((X+X.dag())/2.0)
def rand_herm(N, density=0.75, dims=None): """Creates a random NxN sparse Hermitian quantum object. Uses :math:`H=X+X^{+}` where :math:`X` is a randomly generated quantum operator with a given `density`. Parameters ---------- N : int Shape of output quantum operator. density : float Density etween [0,1] of output Hermitian operator. Returns ------- oper : qobj NxN Hermitian quantum operator. Other Parameters ---------------- dims : list Dimensions of quantum object. Used for specifying tensor structure. Default is dims=[[N],[N]]. """ if dims: _check_dims(dims, N, N) # to get appropriate density of output # Hermitian operator must convert via: herm_density = 2.0 * arcsin(density) / pi X = sp.rand(N, N, herm_density, format='csr') X.data = X.data - 0.5 Y = X.copy() Y.data = 1.0j * np.random.random(len(X.data)) - (0.5 + 0.5j) X = X + Y X = Qobj(X) if dims: return Qobj((X + X.dag()) / 2.0, dims=dims, shape=[N, N]) else: return Qobj((X + X.dag()) / 2.0)
def test_basis(self): """ lattice: Test the method Lattice1d.basis(). """ lattice_3242 = Lattice1d(num_cell=3, boundary="periodic", cell_num_site=2, cell_site_dof=[4, 2]) psi0 = lattice_3242.basis(1, 0, [2, 1]) psi0dag_a = np.zeros((1, 48), dtype=complex) psi0dag_a[0, 21] = 1 psi0dag = Qobj(psi0dag_a, dims=[[1, 1, 1, 1], [3, 2, 4, 2]]) assert_(psi0 == psi0dag.dag())
def _k_space_calculations(self, knpoints=0): """ Returns bulk Hamiltonian, its eigenvectors and eigenvectors of the space Hamiltonian at all the good quantum numbers of a periodic translationally invariant lattice. Returns ------- knxa : np.array knxA[j][0] is the jth good Quantum number k. qH_ks : np.ndarray of Qobj's qH_ks[j] is the Oobj of type oper that holds a bulk Hamiltonian for a good quantum number k. vec_xs : np.ndarray of Qobj's vec_xs[j] is the Oobj of type ket that holds an eigenvector of the Hamiltonian of the lattice. vec_kns : np.ndarray of Qobj's vec_kns[j] is the Oobj of type ket that holds an eigenvector of the bulk Hamiltonian of the lattice. """ if knpoints == 0: knpoints = self.num_cell a = 1 # The unit cell length is always considered 1 kn_start = 0 kn_end = 2*np.pi/a val_kns = np.zeros((self._length_of_unit_cell, knpoints), dtype=float) knxA = np.zeros((knpoints, 1), dtype=float) G0_H = self._H_intra vec_kns = np.zeros((knpoints, self._length_of_unit_cell, self._length_of_unit_cell), dtype=complex) vec_xs = np.array([None for i in range( knpoints * self._length_of_unit_cell)]) qH_ks = np.array([None for i in range(knpoints)]) for ks in range(knpoints): knx = kn_start + (ks*(kn_end-kn_start)/knpoints) if knx >= np.pi: knxA[ks, 0] = knx - 2 * np.pi else: knxA[ks, 0] = knx knxA = np.roll(knxA, np.floor_divide(knpoints, 2)) dim_hk = [self.cell_tensor_config, self.cell_tensor_config] for ks in range(knpoints): kx = knxA[ks, 0] H_ka = G0_H k_cos = [[kx]] for m in range(len(self._H_inter_list)): r_cos = self._inter_vec_list[m] kr_dotted = np.dot(k_cos, r_cos) H_int = self._H_inter_list[m]*np.exp(kr_dotted*1j)[0] H_ka = H_ka + H_int + H_int.dag() H_k = csr_matrix(H_ka) qH_k = Qobj(H_k, dims=dim_hk) qH_ks[ks] = qH_k (vals, veks) = qH_k.eigenstates() plane_waves = np.kron(np.exp(-1j * (kx * range(self.num_cell))), np.ones(self._length_of_unit_cell)) for eig_no in range(self._length_of_unit_cell): unit_cell_periodic = np.kron( np.ones(self.num_cell), veks[eig_no].dag()) vec_x = np.multiply(plane_waves, unit_cell_periodic) dim_H = [list(np.ones(len(self.lattice_tensor_config), dtype=int)), self.lattice_tensor_config] if self.is_real: if np.count_nonzero(vec_x) > 0: vec_x = np.real(vec_x) length_vec_x = np.sqrt((Qobj(vec_x) * Qobj(vec_x).dag())[0][0]) vec_x = vec_x / length_vec_x vec_x = Qobj(vec_x, dims=dim_H) vec_xs[ks*self._length_of_unit_cell+eig_no] = vec_x.dag() for i in range(self._length_of_unit_cell): v0 = np.squeeze(veks[i].full(), axis=1) vec_kns[ks, i, :] = v0 val_kns[:, ks] = vals[:] return (knxA, qH_ks, val_kns, vec_kns, vec_xs)
class Lattice1d(): """A class for representing a 1d crystal. The Lattice1d class can be defined with any specific unit cells and a specified number of unit cells in the crystal. It can return dispersion relationship, position operators, Hamiltonian in the position represention etc. Parameters ---------- num_cell : int The number of cells in the crystal. boundary : str Specification of the type of boundary the crystal is defined with. cell_num_site : int The number of sites in the unit cell. cell_site_dof : list of int/ int The tensor structure of the degrees of freedom at each site of a unit cell. Hamiltonian_of_cell : qutip.Qobj The Hamiltonian of the unit cell. inter_hop : qutip.Qobj / list of Qobj The coupling between the unit cell at i and at (i+unit vector) Attributes ---------- num_cell : int The number of unit cells in the crystal. cell_num_site : int The nuber of sites in a unit cell. length_for_site : int The length of the dimension per site of a unit cell. cell_tensor_config : list of int The tensor structure of the cell in the form [cell_num_site,cell_site_dof[:][0] ] lattice_tensor_config : list of int The tensor structure of the crystal in the form [num_cell,cell_num_site,cell_site_dof[:][0]] length_of_unit_cell : int The length of the dimension for a unit cell. period_bnd_cond_x : int 1 indicates "periodic" and 0 indicates "hardwall" boundary condition inter_vec_list : list of list The list of list of coefficients of inter unitcell vectors' components along Cartesian uit vectors. lattice_vectors_list : list of list The list of list of coefficients of lattice basis vectors' components along Cartesian unit vectors. H_intra : qutip.Qobj The Qobj storing the Hamiltonian of the unnit cell. H_inter_list : list of Qobj/ qutip.Qobj The list of coupling terms between unit cells of the lattice. is_real : bool Indicates if the Hamiltonian is real or not. """ def __init__(self, num_cell=10, boundary="periodic", cell_num_site=1, cell_site_dof=[1], Hamiltonian_of_cell=None, inter_hop=None): self.num_cell = num_cell self.cell_num_site = cell_num_site if (not isinstance(cell_num_site, int)) or cell_num_site < 0: raise Exception("cell_num_site is required to be a positive \ integer.") if isinstance(cell_site_dof, list): l_v = 1 for i, csd_i in enumerate(cell_site_dof): if (not isinstance(csd_i, int)) or csd_i < 0: raise Exception("Invalid cell_site_dof list element at \ index: ", i, "Elements of cell_site_dof \ required to be positive integers.") l_v = l_v * cell_site_dof[i] self.cell_site_dof = cell_site_dof elif isinstance(cell_site_dof, int): if cell_site_dof < 0: raise Exception("cell_site_dof is required to be a positive \ integer.") else: l_v = cell_site_dof self.cell_site_dof = [cell_site_dof] else: raise Exception("cell_site_dof is required to be a positive \ integer or a list of positive integers.") self._length_for_site = l_v self.cell_tensor_config = [self.cell_num_site] + self.cell_site_dof self.lattice_tensor_config = [self.num_cell] + self.cell_tensor_config # remove any 1 present in self.cell_tensor_config and # self.lattice_tensor_config unless all the eleents are 1 if all(x == 1 for x in self.cell_tensor_config): self.cell_tensor_config = [1] else: while 1 in self.cell_tensor_config: self.cell_tensor_config.remove(1) if all(x == 1 for x in self.lattice_tensor_config): self.lattice_tensor_config = [1] else: while 1 in self.lattice_tensor_config: self.lattice_tensor_config.remove(1) dim_ih = [self.cell_tensor_config, self.cell_tensor_config] self._length_of_unit_cell = self.cell_num_site*self._length_for_site if boundary == "periodic": self.period_bnd_cond_x = 1 elif boundary == "aperiodic" or boundary == "hardwall": self.period_bnd_cond_x = 0 else: raise Exception("Error in boundary: Only recognized bounday \ options are:\"periodic \",\"aperiodic\" and \"hardwall\" ") if Hamiltonian_of_cell is None: # There is no user input for # Hamiltonian_of_cell, so we set it ourselves H_site = np.diag(np.zeros(cell_num_site-1)-1, 1) H_site += np.diag(np.zeros(cell_num_site-1)-1, -1) if cell_site_dof == [1] or cell_site_dof == 1: Hamiltonian_of_cell = Qobj(H_site, type='oper') self._H_intra = Hamiltonian_of_cell else: Hamiltonian_of_cell = tensor(Qobj(H_site), qeye(self.cell_site_dof)) dih = Hamiltonian_of_cell.dims[0] if all(x == 1 for x in dih): dih = [1] else: while 1 in dih: dih.remove(1) self._H_intra = Qobj(Hamiltonian_of_cell, dims=[dih, dih], type='oper') elif not isinstance(Hamiltonian_of_cell, Qobj): # The user # input for Hamiltonian_of_cell is not a Qobj and hence is invalid raise Exception("Hamiltonian_of_cell is required to be a Qobj.") else: # We check if the user input Hamiltonian_of_cell have the # right shape or not. If approved, we give it the proper dims # ourselves. r_shape = (self._length_of_unit_cell, self._length_of_unit_cell) if Hamiltonian_of_cell.shape != r_shape: raise Exception("Hamiltonian_of_cell does not have a shape \ consistent with cell_num_site and cell_site_dof.") self._H_intra = Qobj(Hamiltonian_of_cell, dims=dim_ih, type='oper') is_real = np.isreal(self._H_intra.full()).all() if not isherm(self._H_intra): raise Exception("Hamiltonian_of_cell is required to be Hermitian.") nSb = self._H_intra.shape if isinstance(inter_hop, list): # There is a user input list inter_hop_sum = Qobj(np.zeros(nSb)) for i in range(len(inter_hop)): if not isinstance(inter_hop[i], Qobj): raise Exception("inter_hop[", i, "] is not a Qobj. All \ inter_hop list elements need to be Qobj's. \n") nSi = inter_hop[i].shape # inter_hop[i] is a Qobj, now confirmed if nSb != nSi: raise Exception("inter_hop[", i, "] is dimensionally \ incorrect. All inter_hop list elements need to \ have the same dimensionality as Hamiltonian_of_cell.") else: # inter_hop[i] has the right shape, now confirmed, inter_hop[i] = Qobj(inter_hop[i], dims=dim_ih) inter_hop_sum = inter_hop_sum + inter_hop[i] is_real = is_real and np.isreal(inter_hop[i].full()).all() self._H_inter_list = inter_hop # The user input list was correct # we store it in _H_inter_list self._H_inter = Qobj(inter_hop_sum, dims=dim_ih, type='oper') elif isinstance(inter_hop, Qobj): # There is a user input # Qobj nSi = inter_hop.shape if nSb != nSi: raise Exception("inter_hop is required to have the same \ dimensionality as Hamiltonian_of_cell.") else: inter_hop = Qobj(inter_hop, dims=dim_ih, type='oper') self._H_inter_list = [inter_hop] self._H_inter = inter_hop is_real = is_real and np.isreal(inter_hop.full()).all() elif inter_hop is None: # inter_hop is the default None) # So, we set self._H_inter_list from cell_num_site and # cell_site_dof if self._length_of_unit_cell == 1: inter_hop = Qobj([[-1]], type='oper') else: bNm = basis(cell_num_site, cell_num_site-1) bN0 = basis(cell_num_site, 0) siteT = -bNm * bN0.dag() inter_hop = tensor(Qobj(siteT), qeye(self.cell_site_dof)) dih = inter_hop.dims[0] if all(x == 1 for x in dih): dih = [1] else: while 1 in dih: dih.remove(1) self._H_inter_list = [Qobj(inter_hop, dims=[dih, dih], type='oper')] self._H_inter = Qobj(inter_hop, dims=[dih, dih], type='oper') else: raise Exception("inter_hop is required to be a Qobj or a \ list of Qobjs.") self.positions_of_sites = [(i/self.cell_num_site) for i in range(self.cell_num_site)] self._inter_vec_list = [[1] for i in range(len(self._H_inter_list))] self._Brav_lattice_vectors_list = [[1]] # unit vectors self.is_real = is_real def __repr__(self): s = "" s += ("Lattice1d object: " + "Number of cells = " + str(self.num_cell) + ",\nNumber of sites in the cell = " + str(self.cell_num_site) + ",\nDegrees of freedom per site = " + str( self.lattice_tensor_config[2:len(self.lattice_tensor_config)]) + ",\nLattice tensor configuration = " + str(self.lattice_tensor_config) + ",\nbasis_Hamiltonian = " + str(self._H_intra) + ",\ninter_hop = " + str(self._H_inter_list) + ",\ncell_tensor_config = " + str(self.cell_tensor_config) + "\n") if self.period_bnd_cond_x == 1: s += "Boundary Condition: Periodic" else: s += "Boundary Condition: Hardwall" return s def Hamiltonian(self): """ Returns the lattice Hamiltonian for the instance of Lattice1d. Returns ---------- Qobj(Hamil) : qutip.Qobj oper type Quantum object representing the lattice Hamiltonian. """ D = qeye(self.num_cell) T = np.diag(np.zeros(self.num_cell-1)+1, 1) Tdag = np.diag(np.zeros(self.num_cell-1)+1, -1) if self.period_bnd_cond_x == 1 and self.num_cell > 2: Tdag[0][self.num_cell-1] = 1 T[self.num_cell-1][0] = 1 T = Qobj(T) Tdag = Qobj(Tdag) Hamil = tensor(D, self._H_intra) + tensor( T, self._H_inter) + tensor(Tdag, self._H_inter.dag()) dim_H = [self.lattice_tensor_config, self.lattice_tensor_config] return Qobj(Hamil, dims=dim_H) def basis(self, cell, site, dof_ind): """ Returns a single particle wavefunction ket with the particle localized at a specified dof at a specified site of a specified cell. Parameters ------- cell : int The cell at which the particle is to be localized. site : int The site of the cell at which the particle is to be localized. dof_ind : int/ list of int The index of the degrees of freedom with which the sigle particle is to be localized. Returns ---------- vec_i : qutip.Qobj ket type Quantum object representing the localized particle. """ if not isinstance(cell, int): raise Exception("cell needs to be int in basis().") elif cell >= self.num_cell: raise Exception("cell needs to less than Lattice1d.num_cell") if not isinstance(site, int): raise Exception("site needs to be int in basis().") elif site >= self.cell_num_site: raise Exception("site needs to less than Lattice1d.cell_num_site.") if isinstance(dof_ind, int): dof_ind = [dof_ind] if not isinstance(dof_ind, list): raise Exception("dof_ind in basis() needs to be an int or \ list of int") if np.shape(dof_ind) == np.shape(self.cell_site_dof): for i in range(len(dof_ind)): if dof_ind[i] >= self.cell_site_dof[i]: raise Exception("in basis(), dof_ind[", i, "] is required\ to be smaller than cell_num_site[", i, "]") else: raise Exception("dof_ind in basis() needs to be of the same \ dimensions as cell_site_dof.") doft = basis(self.cell_site_dof[0], dof_ind[0]) for i in range(1, len(dof_ind)): doft = tensor(doft, basis(self.cell_site_dof[i], dof_ind[i])) vec_i = tensor( basis(self.num_cell, cell), basis(self.cell_num_site, site), doft) ltc = self.lattice_tensor_config vec_i = Qobj(vec_i, dims=[ltc, [1 for i, j in enumerate(ltc)]]) return vec_i def distribute_operator(self, op): """ A function that returns an operator matrix that applies op to all the cells in the 1d lattice Parameters ------- op : qutip.Qobj Qobj representing the operator to be applied at all cells. Returns ---------- op_H : qutip.Qobj Quantum object representing the operator with op applied at all cells. """ nSb = self._H_intra.shape if not isinstance(op, Qobj): raise Exception("op in distribute_operator() needs to be Qobj.\n") nSi = op.shape if nSb != nSi: raise Exception("op in distribute_operstor() is required to \ have the same dimensionality as Hamiltonian_of_cell.") cell_All = list(range(self.num_cell)) op_H = self.operator_at_cells(op, cells=cell_All) return op_H def x(self): """ Returns the position operator. All degrees of freedom has the cell number at their correspondig entry in the position operator. Returns ------- Qobj(xs) : qutip.Qobj The position operator. """ nx = self.cell_num_site ne = self._length_for_site # positions = np.kron(range(nx), [1/nx for i in range(ne)]) # not used # in the current definition of x # S = np.kron(np.ones(self.num_cell), positions) # xs = np.diagflat(R+S) # not used in the # current definition of x R = np.kron(range(0, self.num_cell), np.ones(nx*ne)) xs = np.diagflat(R) dim_H = [self.lattice_tensor_config, self.lattice_tensor_config] return Qobj(xs, dims=dim_H) def k(self): """ Returns the crystal momentum operator. All degrees of freedom has the cell number at their correspondig entry in the position operator. Returns ------- Qobj(ks) : qutip.Qobj The crystal momentum operator in units of 1/a. L is the number of unit cells, a is the length of a unit cell which is always taken to be 1. """ L = self.num_cell kop = np.zeros((L, L), dtype=complex) for row in range(L): for col in range(L): if row == col: kop[row, col] = (L-1)/2 # kop[row, col] = ((L+1) % 2)/ 2 # shifting the eigenvalues else: kop[row, col] = 1/(np.exp(2j * np.pi * (row - col)/L) - 1) qkop = Qobj(kop) [kD, kV] = qkop.eigenstates() kop_P = np.zeros((L, L), dtype=complex) for eno in range(L): if kD[eno] > (L // 2 + 0.5): vl = kD[eno] - L else: vl = kD[eno] vk = kV[eno] kop_P = kop_P + vl * vk * vk.dag() kop = 2 * np.pi / L * kop_P nx = self.cell_num_site ne = self._length_for_site k = np.kron(kop, np.eye(nx*ne)) dim_H = [self.lattice_tensor_config, self.lattice_tensor_config] return Qobj(k, dims=dim_H) def operator_at_cells(self, op, cells): """ A function that returns an operator matrix that applies op to specific cells specified in the cells list Parameters ---------- op : qutip.Qobj Qobj representing the operator to be applied at certain cells. cells: list of int The cells at which the operator op is to be applied. Returns ------- Qobj(op_H) : Qobj Quantum object representing the operator with op applied at the specified cells. """ if isinstance(cells, int): cells = [cells] if isinstance(cells, list): for i, cells_i in enumerate(cells): if not isinstance(cells_i, int): raise Exception("cells[", i, "] is not an int!elements of \ cells is required to be ints.") else: raise Exception("cells in operator_at_cells() need to be an int or\ a list of ints.") nSb = self._H_intra.shape if (not isinstance(op, Qobj)): raise Exception("op in operator_at_cells need to be Qobj's. \n") nSi = op.shape if (nSb != nSi): raise Exception("op in operstor_at_cells() is required to \ be dimensionaly the same as Hamiltonian_of_cell.") (xx, yy) = op.shape row_ind = np.array([]) col_ind = np.array([]) data = np.array([]) nS = self._length_of_unit_cell nx_units = self.num_cell ny_units = 1 for i in range(nx_units): lin_RI = i if (i in cells): for k in range(xx): for l in range(yy): row_ind = np.append(row_ind, [lin_RI*nS+k]) col_ind = np.append(col_ind, [lin_RI*nS+l]) data = np.append(data, [op[k, l]]) m = nx_units*ny_units*nS op_H = csr_matrix((data, (row_ind, col_ind)), [m, m], dtype=np.complex128) dim_op = [self.lattice_tensor_config, self.lattice_tensor_config] return Qobj(op_H, dims=dim_op) def operator_between_cells(self, op, row_cell, col_cell): """ A function that returns an operator matrix that applies op to specific cells specified in the cells list Parameters ---------- op : qutip.Qobj Qobj representing the operator to be put between cells row_cell and col_cell. row_cell: int The row index for cell for the operator op to be applied. col_cell: int The column index for cell for the operator op to be applied. Returns ------- oper_bet_cell : Qobj Quantum object representing the operator with op applied between the specified cells. """ if not isinstance(row_cell, int): raise Exception("row_cell is required to be an int between 0 and\ num_cell - 1.") if row_cell < 0 or row_cell > self.num_cell-1: raise Exception("row_cell is required to be an int between 0\ and num_cell - 1.") if not isinstance(col_cell, int): raise Exception("row_cell is required to be an int between 0 and\ num_cell - 1.") if col_cell < 0 or col_cell > self.num_cell-1: raise Exception("row_cell is required to be an int between 0\ and num_cell - 1.") nSb = self._H_intra.shape if (not isinstance(op, Qobj)): raise Exception("op in operator_between_cells need to be Qobj's.") nSi = op.shape if (nSb != nSi): raise Exception("op in operstor_between_cells() is required to \ be dimensionally the same as Hamiltonian_of_cell.") T = np.zeros((self.num_cell, self.num_cell), dtype=complex) T[row_cell, col_cell] = 1 op_H = np.kron(T, op) dim_op = [self.lattice_tensor_config, self.lattice_tensor_config] return Qobj(op_H, dims=dim_op) def plot_dispersion(self): """ Plots the dispersion relationship for the lattice with the specified number of unit cells. The dispersion of the infinte crystal is also plotted if num_cell is smaller than MAXc. """ MAXc = 20 # Cell numbers above which we do not plot the infinite # crystal dispersion if self.period_bnd_cond_x == 0: raise Exception("The lattice is not periodic.") if self.num_cell <= MAXc: (kxA, val_ks) = self.get_dispersion(101) (knxA, val_kns) = self.get_dispersion() fig, ax = plt.subplots() if self.num_cell <= MAXc: for g in range(self._length_of_unit_cell): ax.plot(kxA/np.pi, val_ks[g, :]) for g in range(self._length_of_unit_cell): if self.num_cell % 2 == 0: ax.plot(np.append(knxA, [np.pi])/np.pi, np.append(val_kns[g, :], val_kns[g, 0]), 'ro') else: ax.plot(knxA/np.pi, val_kns[g, :], 'ro') ax.set_ylabel('Energy') ax.set_xlabel(r'$k_x(\pi/a)$') plt.show(fig) fig.savefig('./Dispersion.pdf') def get_dispersion(self, knpoints=0): """ Returns dispersion relationship for the lattice with the specified number of unit cells with a k array and a band energy array. Returns ------- knxa : np.array knxA[j][0] is the jth good Quantum number k. val_kns : np.array val_kns[j][:] is the array of band energies of the jth band good at all the good Quantum numbers of k. """ # The _k_space_calculations() function is not used for get_dispersion # because we calculate the infinite crystal dispersion in # plot_dispersion using this coode and we do not want to calculate # all the eigen-values, eigenvectors of the bulk Hamiltonian for too # many points, as is done in the _k_space_calculations() function. if self.period_bnd_cond_x == 0: raise Exception("The lattice is not periodic.") if knpoints == 0: knpoints = self.num_cell a = 1 # The unit cell length is always considered 1 kn_start = 0 kn_end = 2*np.pi/a val_kns = np.zeros((self._length_of_unit_cell, knpoints), dtype=float) knxA = np.zeros((knpoints, 1), dtype=float) G0_H = self._H_intra # knxA = np.roll(knxA, np.floor_divide(knpoints, 2)) for ks in range(knpoints): knx = kn_start + (ks*(kn_end-kn_start)/knpoints) if knx >= np.pi: knxA[ks, 0] = knx - 2 * np.pi else: knxA[ks, 0] = knx knxA = np.roll(knxA, np.floor_divide(knpoints, 2)) for ks in range(knpoints): kx = knxA[ks, 0] H_ka = G0_H k_cos = [[kx]] for m in range(len(self._H_inter_list)): r_cos = self._inter_vec_list[m] kr_dotted = np.dot(k_cos, r_cos) H_int = self._H_inter_list[m]*np.exp(kr_dotted*1j)[0] H_ka = H_ka + H_int + H_int.dag() H_k = csr_matrix(H_ka) qH_k = Qobj(H_k) (vals, veks) = qH_k.eigenstates() val_kns[:, ks] = vals[:] return (knxA, val_kns) def bloch_wave_functions(self): r""" Returns eigenvectors ($\psi_n(k)$) of the Hamiltonian in a numpy.ndarray for translationally symmetric lattices with periodic boundary condition. .. math:: :nowrap: \begin{eqnarray} |\psi_n(k) \rangle = |k \rangle \otimes | u_{n}(k) \rangle \\ | u_{n}(k) \rangle = a_n(k)|a\rangle + b_n(k)|b\rangle \\ \end{eqnarray} Please see section 1.2 of Asbóth, J. K., Oroszlány, L., & Pályi, A. (2016). A short course on topological insulators. Lecture notes in physics, 919 for a review. Returns ------- eigenstates : ordered np.array eigenstates[j][0] is the jth eigenvalue. eigenstates[j][1] is the corresponding eigenvector. """ if self.period_bnd_cond_x == 0: raise Exception("The lattice is not periodic.") (knxA, qH_ks, val_kns, vec_kns, vec_xs) = self._k_space_calculations() dtype = [('eigen_value', np.longdouble), ('eigen_vector', Qobj)] values = list() for i in range(self.num_cell): for j in range(self._length_of_unit_cell): values.append(( val_kns[j][i], vec_xs[j+i*self._length_of_unit_cell])) eigen_states = np.array(values, dtype=dtype) # eigen_states = np.sort(eigen_states, order='eigen_value') return eigen_states def cell_periodic_parts(self): r""" Returns eigenvectors of the bulk Hamiltonian, i.e. the cell periodic part($u_n(k)$) of the Bloch wavefunctios in a numpy.ndarray for translationally symmetric lattices with periodic boundary condition. .. math:: :nowrap: \begin{eqnarray} |\psi_n(k) \rangle = |k \rangle \otimes | u_{n}(k) \rangle \\ | u_{n}(k) \rangle = a_n(k)|a\rangle + b_n(k)|b\rangle \\ \end{eqnarray} Please see section 1.2 of Asbóth, J. K., Oroszlány, L., & Pályi, A. (2016). A short course on topological insulators. Lecture notes in physics, 919 for a review. Returns ------- knxa : np.array knxA[j][0] is the jth good Quantum number k. vec_kns : np.ndarray of Qobj's vec_kns[j] is the Oobj of type ket that holds an eigenvector of the bulk Hamiltonian of the lattice. """ if self.period_bnd_cond_x == 0: raise Exception("The lattice is not periodic.") (knxA, qH_ks, val_kns, vec_kns, vec_xs) = self._k_space_calculations() return (knxA, vec_kns) def bulk_Hamiltonians(self): """ Returns the bulk momentum space Hamiltonian ($H(k)$) for the lattice at the good quantum numbers of k in a numpy ndarray of Qobj's. Please see section 1.2 of Asbóth, J. K., Oroszlány, L., & Pályi, A. (2016). A short course on topological insulators. Lecture notes in physics, 919 for a review. Returns ------- knxa : np.array knxA[j][0] is the jth good Quantum number k. qH_ks : np.ndarray of Qobj's qH_ks[j] is the Oobj of type oper that holds a bulk Hamiltonian for a good quantum number k. """ if self.period_bnd_cond_x == 0: raise Exception("The lattice is not periodic.") (knxA, qH_ks, val_kns, vec_kns, vec_xs) = self._k_space_calculations() return (knxA, qH_ks) def _k_space_calculations(self, knpoints=0): """ Returns bulk Hamiltonian, its eigenvectors and eigenvectors of the space Hamiltonian at all the good quantum numbers of a periodic translationally invariant lattice. Returns ------- knxa : np.array knxA[j][0] is the jth good Quantum number k. qH_ks : np.ndarray of Qobj's qH_ks[j] is the Oobj of type oper that holds a bulk Hamiltonian for a good quantum number k. vec_xs : np.ndarray of Qobj's vec_xs[j] is the Oobj of type ket that holds an eigenvector of the Hamiltonian of the lattice. vec_kns : np.ndarray of Qobj's vec_kns[j] is the Oobj of type ket that holds an eigenvector of the bulk Hamiltonian of the lattice. """ if knpoints == 0: knpoints = self.num_cell a = 1 # The unit cell length is always considered 1 kn_start = 0 kn_end = 2*np.pi/a val_kns = np.zeros((self._length_of_unit_cell, knpoints), dtype=float) knxA = np.zeros((knpoints, 1), dtype=float) G0_H = self._H_intra vec_kns = np.zeros((knpoints, self._length_of_unit_cell, self._length_of_unit_cell), dtype=complex) vec_xs = np.array([None for i in range( knpoints * self._length_of_unit_cell)]) qH_ks = np.array([None for i in range(knpoints)]) for ks in range(knpoints): knx = kn_start + (ks*(kn_end-kn_start)/knpoints) if knx >= np.pi: knxA[ks, 0] = knx - 2 * np.pi else: knxA[ks, 0] = knx knxA = np.roll(knxA, np.floor_divide(knpoints, 2)) dim_hk = [self.cell_tensor_config, self.cell_tensor_config] for ks in range(knpoints): kx = knxA[ks, 0] H_ka = G0_H k_cos = [[kx]] for m in range(len(self._H_inter_list)): r_cos = self._inter_vec_list[m] kr_dotted = np.dot(k_cos, r_cos) H_int = self._H_inter_list[m]*np.exp(kr_dotted*1j)[0] H_ka = H_ka + H_int + H_int.dag() H_k = csr_matrix(H_ka) qH_k = Qobj(H_k, dims=dim_hk) qH_ks[ks] = qH_k (vals, veks) = qH_k.eigenstates() plane_waves = np.kron(np.exp(-1j * (kx * range(self.num_cell))), np.ones(self._length_of_unit_cell)) for eig_no in range(self._length_of_unit_cell): unit_cell_periodic = np.kron( np.ones(self.num_cell), veks[eig_no].dag()) vec_x = np.multiply(plane_waves, unit_cell_periodic) dim_H = [list(np.ones(len(self.lattice_tensor_config), dtype=int)), self.lattice_tensor_config] if self.is_real: if np.count_nonzero(vec_x) > 0: vec_x = np.real(vec_x) length_vec_x = np.sqrt((Qobj(vec_x) * Qobj(vec_x).dag())[0][0]) vec_x = vec_x / length_vec_x vec_x = Qobj(vec_x, dims=dim_H) vec_xs[ks*self._length_of_unit_cell+eig_no] = vec_x.dag() for i in range(self._length_of_unit_cell): v0 = np.squeeze(veks[i].full(), axis=1) vec_kns[ks, i, :] = v0 val_kns[:, ks] = vals[:] return (knxA, qH_ks, val_kns, vec_kns, vec_xs) def winding_number(self): """ Returns the winding number for a lattice that has chiral symmetry and also plots the trajectory of (dx,dy)(dx,dy are the coefficients of sigmax and sigmay in the Hamiltonian respectively) on a plane. Returns ------- winding_number : int or str knxA[j][0] is the jth good Quantum number k. """ winding_number = 'defined' if (self._length_of_unit_cell != 2): raise Exception('H(k) is not a 2by2 matrix.') if (self._H_intra[0, 0] != 0 or self._H_intra[1, 1] != 0): raise Exception("Hamiltonian_of_cell has nonzero diagonals!") for i in range(len(self._H_inter_list)): H_I_00 = self._H_inter_list[i][0, 0] H_I_11 = self._H_inter_list[i][1, 1] if (H_I_00 != 0 or H_I_11 != 0): raise Exception("inter_hop has nonzero diagonal elements!") chiral_op = self.distribute_operator(sigmaz()) Hamt = self.Hamiltonian() anti_commutator_chi_H = chiral_op * Hamt + Hamt * chiral_op is_null = (np.abs(anti_commutator_chi_H.full()) < 1E-10).all() if not is_null: raise Exception("The Hamiltonian does not have chiral symmetry!") knpoints = 100 # choose even kn_start = 0 kn_end = 2*np.pi knxA = np.zeros((knpoints+1, 1), dtype=float) G0_H = self._H_intra mx_k = np.array([None for i in range(knpoints+1)]) my_k = np.array([None for i in range(knpoints+1)]) Phi_m_k = np.array([None for i in range(knpoints+1)]) for ks in range(knpoints+1): knx = kn_start + (ks*(kn_end-kn_start)/knpoints) knxA[ks, 0] = knx for ks in range(knpoints+1): kx = knxA[ks, 0] H_ka = G0_H k_cos = [[kx]] for m in range(len(self._H_inter_list)): r_cos = self._inter_vec_list[m] kr_dotted = np.dot(k_cos, r_cos) H_int = self._H_inter_list[m]*np.exp(kr_dotted*1j)[0] H_ka = H_ka + H_int + H_int.dag() H_k = csr_matrix(H_ka) qH_k = Qobj(H_k) mx_k[ks] = 0.5*(qH_k*sigmax()).tr() my_k[ks] = 0.5*(qH_k*sigmay()).tr() if np.abs(mx_k[ks]) < 1E-10 and np.abs(my_k[ks]) < 1E-10: winding_number = 'undefined' if np.angle(mx_k[ks]+1j*my_k[ks]) >= 0: Phi_m_k[ks] = np.angle(mx_k[ks]+1j*my_k[ks]) else: Phi_m_k[ks] = 2*np.pi + np.angle(mx_k[ks]+1j*my_k[ks]) if winding_number == 'defined': ddk_Phi_m_k = np.roll(Phi_m_k, -1) - Phi_m_k intg_over_k = -np.sum(ddk_Phi_m_k[0:knpoints//2])+np.sum( ddk_Phi_m_k[knpoints//2:knpoints]) winding_number = intg_over_k/(2*np.pi) X_lim = 1.125 * np.abs(self._H_intra.full()[1, 0]) for i in range(len(self._H_inter_list)): X_lim = X_lim + np.abs(self._H_inter_list[i].full()[1, 0]) plt.figure(figsize=(3*X_lim, 3*X_lim)) plt.plot(mx_k, my_k) plt.plot(0, 0, 'ro') plt.ylabel('$h_y$') plt.xlabel('$h_x$') plt.xlim(-X_lim, X_lim) plt.ylim(-X_lim, X_lim) plt.grid() plt.show() plt.savefig('./Winding.pdf') plt.close() return winding_number def display_unit_cell(self, label_on=False): """ Produces a graphic displaying the unit cell features with labels on if defined by user. Also returns a dict of Qobj's corresponding to the labeled elements on the display. Returns ------- Hcell : dict Hcell[i][j] is the Hamiltonian segment for $H_{i,j}$ labeled on the graphic. """ CNS = self.cell_num_site Hcell = [[{} for i in range(CNS)] for j in range(CNS)] for i0 in range(CNS): for j0 in range(CNS): Qin = np.zeros((self._length_for_site, self._length_for_site), dtype=complex) for i in range(self._length_for_site): for j in range(self._length_for_site): Qin[i, j] = self._H_intra[ i0*self._length_for_site+i, j0*self._length_for_site+j] dims_site = [self.cell_site_dof, self.cell_site_dof] Hcell[i0][j0] = Qobj(Qin, dims=dims_site) fig = plt.figure(figsize=[CNS*2, CNS*2.5]) ax = fig.add_subplot(111, aspect='equal') plt.rc('text', usetex=True) plt.rc('font', family='serif') if (CNS == 1): ax.plot([self.positions_of_sites[0]], [0], "o", c="b", mec="w", mew=0.0, zorder=10, ms=8.0) if label_on is True: plt.text(x=self.positions_of_sites[0]+0.2, y=0.0, s='H'+str(i)+str(i), horizontalalignment='center', verticalalignment='center') x2 = (1+self.positions_of_sites[CNS-1])/2 x1 = x2-1 h = 1-x2 ax.plot([x1, x1], [-h, h], "-", c="k", lw=1.5, zorder=7) ax.plot([x2, x2], [-h, h], "-", c="k", lw=1.5, zorder=7) ax.plot([x1, x2], [h, h], "-", c="k", lw=1.5, zorder=7) ax.plot([x1, x2], [-h, -h], "-", c="k", lw=1.5, zorder=7) plt.axis('off') plt.show() plt.close() else: for i in range(CNS): ax.plot([self.positions_of_sites[i]], [0], "o", c="b", mec="w", mew=0.0, zorder=10, ms=8.0) if label_on is True: x_b = self.positions_of_sites[i]+1/CNS/6 plt.text(x=x_b, y=0.0, s='H'+str(i)+str(i), horizontalalignment='center', verticalalignment='center') if i == CNS-1: continue for j in range(i+1, CNS): if (Hcell[i][j].full() == 0).all(): continue c_cen = (self.positions_of_sites[ i]+self.positions_of_sites[j])/2 c_radius = (self.positions_of_sites[ j]-self.positions_of_sites[i])/2 circle1 = plt.Circle((c_cen, 0), c_radius, color='g', fill=False) ax.add_artist(circle1) if label_on is True: x_b = c_cen y_b = c_radius - 0.025 plt.text(x=x_b, y=y_b, s='H'+str(i)+str(j), horizontalalignment='center', verticalalignment='center') x2 = (1+self.positions_of_sites[CNS-1])/2 x1 = x2-1 h = (self.positions_of_sites[ CNS-1]-self.positions_of_sites[0])*8/15 ax.plot([x1, x1], [-h, h], "-", c="k", lw=1.5, zorder=7) ax.plot([x2, x2], [-h, h], "-", c="k", lw=1.5, zorder=7) ax.plot([x1, x2], [h, h], "-", c="k", lw=1.5, zorder=7) ax.plot([x1, x2], [-h, -h], "-", c="k", lw=1.5, zorder=7) plt.axis('off') plt.show() plt.close() return Hcell def display_lattice(self): r""" Produces a graphic portraying the lattice symbolically with a unit cell marked in it. Returns ------- inter_T : Qobj The coefficient of $\psi_{i,N}^{\dagger}\psi_{0,i+1}$, i.e. the coupling between the two boundary sites of the two unit cells i and i+1. """ dim_I = [self.cell_tensor_config, self.cell_tensor_config] H_inter = Qobj(np.zeros((self._length_of_unit_cell, self._length_of_unit_cell)), dims=dim_I) for no, inter_hop_no in enumerate(self._H_inter_list): H_inter = H_inter + inter_hop_no H_inter = np.array(H_inter) csn = self.cell_num_site Hcell = [[{} for i in range(csn)] for j in range(csn)] for i0 in range(csn): for j0 in range(csn): Qin = np.zeros((self._length_for_site, self._length_for_site), dtype=complex) for i in range(self._length_for_site): for j in range(self._length_for_site): Qin[i, j] = self._H_intra[ i0*self._length_for_site+i, j0*self._length_for_site+j] dims_site = [self.cell_site_dof, self.cell_site_dof] Hcell[i0][j0] = Qobj(Qin, dims=dims_site) j0 = 0 i0 = csn-1 Qin = np.zeros((self._length_for_site, self._length_for_site), dtype=complex) for i in range(self._length_for_site): for j in range(self._length_for_site): Qin[i, j] = H_inter[i0*self._length_for_site+i, j0*self._length_for_site+j] inter_T = Qin fig = plt.figure(figsize=[self.num_cell*3, self.num_cell*3]) plt.rc('text', usetex=True) plt.rc('font', family='serif') ax = fig.add_subplot(111, aspect='equal') for nc in range(self.num_cell): x_cell = nc for i in range(csn): ax.plot([x_cell + self.positions_of_sites[i]], [0], "o", c="b", mec="w", mew=0.0, zorder=10, ms=8.0) if nc > 0: # plot inter_cell_hop ax.plot([x_cell-1+self.positions_of_sites[csn-1], x_cell+self.positions_of_sites[0]], [0.0, 0.0], "-", c="r", lw=1.5, zorder=7) x_b = (x_cell-1+self.positions_of_sites[ csn-1] + x_cell + self.positions_of_sites[0])/2 plt.text(x=x_b, y=0.1, s='T', horizontalalignment='center', verticalalignment='center') if i == csn-1: continue for j in range(i+1, csn): if (Hcell[i][j].full() == 0).all(): continue c_cen = self.positions_of_sites[i] c_cen = (c_cen+self.positions_of_sites[j])/2 c_cen = c_cen + x_cell c_radius = self.positions_of_sites[j] c_radius = (c_radius-self.positions_of_sites[i])/2 circle1 = plt.Circle((c_cen, 0), c_radius, color='g', fill=False) ax.add_artist(circle1) if (self.period_bnd_cond_x == 1): x_cell = 0 x_b = 2*x_cell-1+self.positions_of_sites[csn-1] x_b = (x_b+self.positions_of_sites[0])/2 plt.text(x=x_b, y=0.1, s='T', horizontalalignment='center', verticalalignment='center') ax.plot([x_cell-1+self.positions_of_sites[csn-1], x_cell+self.positions_of_sites[0]], [0.0, 0.0], "-", c="r", lw=1.5, zorder=7) x_cell = self.num_cell x_b = 2*x_cell-1+self.positions_of_sites[csn-1] x_b = (x_b+self.positions_of_sites[0])/2 plt.text(x=x_b, y=0.1, s='T', horizontalalignment='center', verticalalignment='center') ax.plot([x_cell-1+self.positions_of_sites[csn-1], x_cell+self.positions_of_sites[0]], [0.0, 0.0], "-", c="r", lw=1.5, zorder=7) x2 = (1+self.positions_of_sites[csn-1])/2 x1 = x2-1 h = 0.5 if self.num_cell > 2: xu = 1 # The index of cell over which the black box is drawn x1 = x1+xu x2 = x2+xu ax.plot([x1, x1], [-h, h], "-", c="k", lw=1.5, zorder=7, alpha=0.3) ax.plot([x2, x2], [-h, h], "-", c="k", lw=1.5, zorder=7, alpha=0.3) ax.plot([x1, x2], [h, h], "-", c="k", lw=1.5, zorder=7, alpha=0.3) ax.plot([x1, x2], [-h, -h], "-", c="k", lw=1.5, zorder=7, alpha=0.3) plt.axis('off') plt.show() plt.close() dims_site = [self.cell_site_dof, self.cell_site_dof] return Qobj(inter_T, dims=dims_site)
def test_SSH(self): """ lattice: Test the methods of Lattice1d in a SSH model. """ # SSH model with num_cell = 4 and two orbitals, two spins # cell_site_dof = [2,2] t_intra = -0.5 t_inter = -0.6 H_cell = Qobj(np.array([[0, t_intra], [t_intra, 0]])) inter_cell_T = Qobj(np.array([[0, 0], [t_inter, 0]])) SSH_lattice = Lattice1d(num_cell=5, boundary="periodic", cell_num_site=2, cell_site_dof=[1], Hamiltonian_of_cell=H_cell, inter_hop=inter_cell_T) Ham_Sc = SSH_lattice.Hamiltonian() Hin = Qobj([[0, -0.5], [-0.5, 0]]) Ht = Qobj([[0, 0], [-0.6, 0]]) D = qeye(5) T = np.diag(np.zeros(4) + 1, 1) Tdag = np.diag(np.zeros(4) + 1, -1) Tdag[0][4] = 1 T[4][0] = 1 T = Qobj(T) Tdag = Qobj(Tdag) H_Sc = tensor(D, Hin) + tensor(T, Ht) + tensor(Tdag, Ht.dag()) # check for SSH model with num_cell = 5 assert_(Ham_Sc == H_Sc) (kxA, val_ks) = SSH_lattice.get_dispersion() kSSH = np.array([[-2.51327412], [-1.25663706], [0.], [1.25663706], [2.51327412]]) vSSH = np.array( [[-0.35297281, -0.89185772, -1.1, -0.89185772, -0.35297281], [0.35297281, 0.89185772, 1.1, 0.89185772, 0.35297281]]) assert_(np.max(abs(kxA - kSSH)) < 1.0E-6) assert_(np.max(abs(val_ks - vSSH)) < 1.0E-6) # A test on SSH lattice dispersion with a random number of cells and # random values of t_inter and t_intra num_cell = np.random.randint(2, 60) t_intra = -np.random.random() t_inter = -np.random.random() H_cell = Qobj(np.array([[0, t_intra], [t_intra, 0]])) inter_cell_T = Qobj(np.array([[0, 0], [t_inter, 0]])) SSH_Random = Lattice1d(num_cell=num_cell, boundary="periodic", cell_num_site=2, cell_site_dof=[1], Hamiltonian_of_cell=H_cell, inter_hop=inter_cell_T) a = 1 # The unit cell length is always considered 1 kn_start = 0 kn_end = 2 * np.pi / a Ana_val_kns = np.zeros((2, num_cell), dtype=float) knxA = np.zeros((num_cell, 1), dtype=float) for ks in range(num_cell): knx = kn_start + (ks * (kn_end - kn_start) / num_cell) if knx >= np.pi: knxA[ks, 0] = knx - 2 * np.pi else: knxA[ks, 0] = knx knxA = np.roll(knxA, np.floor_divide(num_cell, 2)) for ks in range(num_cell): knx = knxA[ks, 0] # Ana_val_kns are the analytical bands Ana_val_kns[0, ks] = -np.sqrt(t_intra**2 + t_inter**2 + 2 * t_intra * t_inter * np.cos(knx)) Ana_val_kns[1, ks] = np.sqrt(t_intra**2 + t_inter**2 + 2 * t_intra * t_inter * np.cos(knx)) (kxA, val_kns) = SSH_Random.get_dispersion() assert_(np.max(abs(val_kns - Ana_val_kns)) < 1.0E-13)
sz = Qobj([[1, 0], [0, -1]]) print("Sigma Z: ", sz) wait() # Arithmetic with quantum objects H = 1.0 * sz + 0.1 * sy print("Qubit Hamiltonian (H = sz + 0.1 * sy):\n", H) print("Eigenenergies/ Eigenvalues of H: ", H.eigenenergies()) wait() print("sy: ", sy) print("Hermitian Conjugate/Conjugate Transpose of sy: ", sy.dag()) # Same as sy print("Trace of sy: ", sy.tr()) wait() """ --------------- </States> --------------- """ # Fundamental Basis States (Fock states of oscillator modes) N = 2 # number of states in the hilbert space n = 1 # state which will be occupied print("Basis with {N} states, where state {n} is occupied:\n".format(N=N, n=n), basis(N, n)) # Same as fock(N,n) print() # Similarly, print("Basis with {N} states, where state {n} is occupied:\n".format(N=4, n=2),