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)]])
Exemple #2
0
 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
Exemple #3
0
 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)
Exemple #7
0
    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
Exemple #8
0
 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