def vspinor(n, L, m, normed=False): if m == 0 and n == 0: return np.array([[1 / sqrt(2)], [-1 / sqrt(2)]]) k = (2. * pi / L) * n energy = omega(n, L, m) if normed: return np.array([[sqrt(energy - k) / sqrt(2 * energy)], [-sqrt(energy + k) / sqrt(2 * energy)]]) return np.array([[sqrt(energy - k)], [-sqrt(energy + k)]])
def __setitem__(self, wn, n): """ Sets the occupation number corresponding to a wave number """ if self.fast==False: self.energy += ((n-self[wn])*omega(wn,self.L,self.m)).sum() self.totalWN += ((n-self[wn])*wn).sum() self.momentum = (2.*pi/self.L)*self.totalWN #should we update parity eigenstate too? probably - IL self.occs[int(wn-self.nmin)] = n
def __init__(self, clist, dlist, L, m, extracoeff=1): """ Args: clist, dlist, L, m: as above extracoeff (float): an overall multiplicative prefactor for the operator, *written as a power of the field operator phi* """ self.clist = clist self.dlist = dlist self.L = L self.m = m # coeff converts the overall prefactor of phi (extracoeff) to a prefactor # for the string of creation and annihilation operators in the final operator # see the normalization in Eq. 2.6 self.coeff = extracoeff / product( [sqrt(2. * L * omega(n, L, m)) for n in clist + dlist]) #can this be sped up by vectorizing the omega function? IL self.deltaE = sum([omega(n, L, m) for n in clist]) - sum( [omega(n, L, m) for n in dlist])
def __init__(self, clist, dlist, anticlist, antidlist, L, m, extracoeff=1, normed=False): """ Args: clist, dlist, L, m: as above extracoeff (float): an overall multiplicative prefactor for the operator, *written as a power of the field operator phi* normed (bool): indicates whether factor of 1/sqrt(2*omega) has been absorbed into the definition of the spinor wavefunctions """ # Check if there are multiple operators acting on the same mode. # Since fermionic operators anticommute, operators which have e.g. # 2 annihilation operators acting on the same mode are just 0. self.uniqueOps = (self.checkValidList(clist) and self.checkValidList(dlist) and self.checkValidList(anticlist) and self.checkValidList(antidlist)) self.clist = clist[::-1] self.dlist = dlist[::-1] self.anticlist = anticlist[::-1] self.antidlist = antidlist[::-1] self.L = L self.m = m # coeff converts the overall prefactor of phi (extracoeff) to a prefactor # for the string of creation and annihilation operators in the final operator # see the normalization in Eq. 2.6 if normed: self.coeff = extracoeff else: #note: have to be careful with this for massless zero modes self.coeff = extracoeff / product( [sqrt(2. * L * omega(n, L, m)) for n in clist + dlist]) self.deltaE = sum([omega(n, L, m) for n in clist]) - sum( [omega(n, L, m) for n in dlist])
def setUp(self): self.L = 2 * pi self.m = 1. self.n = 5 self.k = (2. * pi / self.L) * self.n self.E = omega(self.n, self.L, self.m) self.GAMMA0 = np.array([[0, 1], [1, 0]]) self.GAMMA1 = np.array([[0, -1], [1, 0]]) self.myUSpinor = uspinor(self.n, self.L, self.m) self.myVSpinor = vspinor(self.n, self.L, self.m)
def setUp(self): self.L = 2 * pi self.m = 0. self.n = 5 self.k = (2. * pi / self.L) * self.n self.E = omega(self.n, self.L, self.m) self.myUSpinor = uspinor(self.n, self.L, self.m) self.myVSpinor = vspinor(self.n, self.L, self.m) # make spinors with -p self.myUSpinor2 = uspinor(-self.n, self.L, self.m) self.myVSpinor2 = vspinor(-self.n, self.L, self.m) self.myUSpinorNormed = uspinor(self.n, self.L, self.m, normed=True) self.myVSpinorNormed = vspinor(self.n, self.L, self.m, normed=True) # make spinors with -p self.myUSpinor2Normed = uspinor(-self.n, self.L, self.m, normed=True) self.myVSpinor2Normed = vspinor(-self.n, self.L, self.m, normed=True)
def __init__(self, particleOccs, antiparticleOccs, nmax, L=None, m=None, fast=False, checkAtRest=True, checkChargeNeutral=True): """ Args: antiparticleOccs: occupation number list particleOccs: occupation number list nmax (int): wave number of the last element in occs fast (bool): a flag for when occs and nmax are all that are needed (see transformState in oscillators.py) checkAtRest (bool): a flag to check if the total momentum is zero """ #assert m >= 0, "Negative or zero mass" #assert L > 0, "Circumference must be positive" assert (len(particleOccs) == len(antiparticleOccs)),\ "Occupation number lists should match in length" assert (np.all(np.less_equal(particleOccs, 1)) and np.all(np.less_equal(antiparticleOccs, 1))),\ "Pauli exclusion violated" self.particleOccs = np.array(particleOccs) self.antiparticleOccs = np.array(antiparticleOccs) self.occs = np.transpose(np.vstack((self.particleOccs, self.antiparticleOccs))) self.size = len(self.occs) self.nmax = nmax self.nmin = self.nmax - self.size + 1 self.isDressed = False self.fast = fast if fast: return wavenum = np.arange(self.nmin, self.nmax+1) self.totalWN = (wavenum*np.transpose(self.occs)).sum() self.__parityEigenstate = (self.size == 2*self.nmax + 1 and np.array_equal(self.occs[::-1],self.occs)) self.netCharge = self.particleOccs.sum() - self.antiparticleOccs.sum() if checkAtRest: if self.totalWN != 0: raise ValueError("State not at rest") if checkChargeNeutral: if self.netCharge != 0: raise ValueError("State not charge-neutral") self.__chargeNeutral = (self.netCharge == 0) self.L = L self.m = m energies = omega(wavenum,L,m) self.energy = (energies*np.transpose(self.occs)).sum() self.momentum = (2.*pi/self.L)*self.totalWN
def __buildRMlist(self): """ sets list of all right-moving states with particles of individual wave number <= nmax, total momentum <= Emax/2 and total energy <= Emax This function works by first filling in n=1 mode in all possible ways, then n=2 mode in all possible ways assuming the occupation of n=1 mode, etc This is modified for fermionic states. In the fermionic case, occupation numbers are zero or one due to Pauli exclusion. """ if self.nmax == 0: self.__RMlist = [FermionState([],[],nmax=0,L=self.L,m=self.m, checkAtRest=False, checkChargeNeutral=False)] return # for zero-momentum states, the maximum value of k is as follows. kmax = max(0., np.sqrt((self.Emax/2.)**2.-self.m**2.)) # the max occupation number of the n=1 mode is either kmax divided # by the momentum at n=1 or Emax/omega, whichever is less # the 2 here accounts for that we can have a single particle and an # antiparticle in n=1 if self.bcs == "periodic": seedN = 1 elif self.bcs == "antiperiodic": seedN = 0.5 maxN1 = min([math.floor(kmax/k(seedN,self.L)), math.floor(self.Emax/omega(seedN,self.L,self.m)), 2]) if maxN1 <= 0: nextOccs = [[0,0]] elif maxN1 == 1: nextOccs = [[0,0],[0,1],[1,0]] else: nextOccs = [[0,0],[0,1],[1,0],[1,1]] RMlist0 = [FermionState([occs[0]],[occs[1]],seedN,L=self.L,m=self.m,checkAtRest=False, checkChargeNeutral=False) for occs in nextOccs] # seed list of RM states,all possible n=1 mode occupation numbers for n in np.arange(seedN+1,self.nmax+1): #go over all other modes RMlist1=[] #we will take states out of RMlist0, augment them and add to RMlist1 for RMstate in RMlist0: # cycle over all RMstates p0 = RMstate.momentum e0 = RMstate.energy # maximal occupation number of mode n given the momentum/energy # in all previous modes. The sqrt term accounts for the # ground state energy of the overall state, while e0 gives # the energy in each of the mode excitations. maxNn = min([math.floor((kmax-p0)/k(n,self.L)), math.floor((self.Emax-np.sqrt(self.m**2+p0**2)-e0)/omega(n,self.L,self.m)), 2]) if maxNn <= 0: nextOccsList = [[0,0]] elif maxNn == 1: nextOccsList = [[0,0],[0,1],[1,0]] else: nextOccsList = [[0,0],[0,1],[1,0],[1,1]] assert maxNn <= 2, f"maxNn was {maxNn}" # got to here in edits. # should we maybe just write a numpy function to calculate # energy and momentum from the occs list? # update: i did this. But it would take an extra # function call instead of accessing state properties. #print(f"RMstate occs are {RMstate.occs}") for nextOccs in nextOccsList: longerstate = np.append(RMstate.occs,[nextOccs],axis=0) RMlist1.append(FermionState(longerstate[:,0], longerstate[:,1], nmax=n,L=self.L,m=self.m, checkAtRest=False, checkChargeNeutral=False)) #RMlist1 created, copy it back to RMlist0 RMlist0 = RMlist1 self.__RMlist = RMlist0 #save list of RMstates in an internal variable
def buildMatrix(self): """ Builds the full Hamiltonian in the basis of the free Hamiltonian eigenvectors. This is computationally intensive. It can be skipped by loading the matrix from file """ """ Possible speedups: Simplify the diagonal operator loops? Can we apply a numpy mask to simplify the loops without the conditionals? Basically this loops over the operators at each order in phi and stores all the normal order operators explicitly in lists. """ L = self.L m = self.m for k in (1, -1): basis = self.fullBasis[k] lookupBasis = self.fullBasis[k] Emax = basis.Emax nmax = basis.nmax diagOps = {0: None, 2: None, 4: None} offdiagOps = {0: None, 2: None, 4: None} diagOps[0] = [NOO([], [], L, m)] offdiagOps[0] = [] #the 2 is a combinatorial factor since both aa^dagger and a^dagger a contribute diagOps[2] = [ NOO([a], [a], L, m, extracoeff=2.) for a in range(-nmax, nmax + 1) ] #the symmetry factor is 1 if a=-a and 2 otherwise offdiagOps[2] = [ NOO([a, -a], [], L, m, extracoeff=comb(a, -a)) for a in range(-nmax, nmax + 1) if a <= -a <= nmax and omega(a, L, m) + omega(-a, L, m) <= Emax + tol ] # the default symmetry factor is 6 (4 choose 2) if a and b are distinct # and c, a+b-c are distinct # notice the index for b runs from a to nmax so we only get unique # pairs a and b, i.e. (1,1), (1,2), (1,3), (2,2), (2,3), (3,3). diagOps[4] = [ NOO([a, b], [c, a + b - c], L, m, extracoeff=6. * comb(a, b) * comb(c, a + b - c)) for a in range(-nmax, nmax + 1) for b in range(a, nmax + 1) for c in range(-nmax, nmax + 1) if (c <= a + b - c <= nmax and (a, b) == (c, a + b - c) and -Emax - tol <= omega(a, L, m) + omega(b, L, m) - omega(c, L, m) - omega(a + b - c, L, m) <= Emax + tol) ] offdiagOps[4] = [ NOO([a,b,c,-a-b-c],[],L,m,extracoeff=comb(a,b,c,-a-b-c)) for a in range(-nmax,nmax+1) for b in range (a,nmax+1) for c in range(b,nmax+1) if c<=-a-b-c<=nmax and omega(a,L,m)+omega(b,L,m) + omega(c,L,m)+omega(-a-b-c,L,m)<= Emax+tol] \ + [ NOO([a,b,c],[a+b+c],L,m, extracoeff = 4. * comb(a,b,c)) for a in range(-nmax, nmax+1) for b in range (a,nmax+1) for c in range(b,nmax+1) if (-nmax<=a+b+c<=nmax and -Emax-tol <= omega(a,L,m)+omega(b,L,m)+ omega(c,L,m)-omega(a+b+c,L,m) <=Emax+tol)] \ + [ NOO([a,b],[c,a+b-c],L,m, extracoeff = 6. * comb(a,b)*comb(c,a+b-c)) for a in range(-nmax,nmax+1) for b in range (a,nmax+1) for c in range(-nmax,nmax+1) if ( c<=a+b-c<=nmax and (a,b) != (c,a+b-c) and sorted([abs(a),abs(b)]) < sorted([abs(c),abs(a+b-c)]) and -Emax-tol <= omega(a,L,m)+omega(b,L,m)- omega(c,L,m)-omega(a+b-c,L,m) <=Emax+tol)] #save as h0 a lookupBasis.size by 1 sparse matrix initialized to zeros #really just a single column #and store the bases as h0[k].basisI and h0[k].basisJ #self.h0[k] = Matrix(lookupBasis, basis) tempEnergies = np.empty(basis.size) #initialTime = time.time() #this was xrange in the original; range in 3.x was xrange in 2.x -IL for j in range(basis.size): #make a new column of the appropriate length #newcolumn = scipy.zeros(lookupBasis.size) #set the jth entry in this column to be the energy of #the jth state in the basis #newcolumn[j] = basis[j].energy tempEnergies[j] = basis[j].energy #self.h0[k].addColumn(newcolumn) #self.h0[k].finalize() """ basically this is just a diagonal matrix of the eigenvalues since the Hamiltonian h0 is diagonal in this basis this is faster. the loop adding columns takes 0.45711565017700195 s just creating the sparse matrix takes 0.000997304916381836 s. """ temph0 = scipy.sparse.diags(tempEnergies, format="coo") self.h0[k] = Matrix(lookupBasis, basis, temph0) #ran some tests, doing scipy.sparse.diags does seem more straightforward #print(time.time()-initialTime) """ We build the potential in this basis. self.potential is a dictionary with two keys corresponding to k=1 and k=-1. Each of the entries is a list of sparse matrices. Each sparse matrix consists of a sum of off-diagonal components and diagonal components in COO format. Possible speedup: could we apply each operator to all states in one shot? Then we would just loop over operators. Right now, for each state, we apply each operator one at a time and mark down its image in the corresponding column (the origin state) and row (the image state). If we apply an operator to a vector of states, we will get a vector of their images which can be collectively checked for validity by checking the dictionary keys/energy ranges, and then this is just the matrix form of the operator. """ # for each order (0,2,4) in phi for n in offdiagOps.keys(): offdiag_V = Matrix(lookupBasis, basis) diagonal = np.zeros(basis.size) # for each state in the basis for j in range(basis.size): newcolumn = np.zeros(lookupBasis.size) # for each off-diagonal operator at a given order for op in offdiagOps[n]: try: # apply this operator to find whether the # new state is still in the basis (x, i) = op.apply(basis, j, lookupBasis) # if so, add the corresponding value to the matrix # this is basically writing the effects of the # operator in the basis of the free states if (i != None): newcolumn[i] += x except NotInBasis: pass offdiag_V.addColumn(newcolumn) # for each diagonal operator at the same order n for op in diagOps[n]: (x, i) = op.apply(basis, j, lookupBasis) # It should be j=i if i != None: if i != j: raise RuntimeError('Non-diagonal operator') diagonal[i] += x offdiag_V.finalize() diag_V = scipy.sparse.spdiags(diagonal, 0, basis.size, basis.size) self.potential[k][n] = ( offdiag_V + offdiag_V.transpose() + Matrix(lookupBasis, basis, diag_V)).to('coo') * self.L