qedstatefuncs.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. # -*- coding: utf-8 -*-
  2. """
  3. Created on Sat Jun 27 15:19:10 2020
  4. @author: Ian Lim
  5. """
  6. import numpy as np
  7. import scipy
  8. from numpy import sqrt, pi
  9. from operator import attrgetter
  10. import math
  11. from statefuncs import omega, k
  12. from statefuncs import State, Basis, NotInBasis
  13. def half_floor(x):
  14. if x >= math.floor(x) + 0.5:
  15. return math.floor(x) + 0.5
  16. return max(0,math.floor(x)-0.5)
  17. class FermionState():
  18. def __init__(self, particleOccs, antiparticleOccs, nmax,
  19. L=None, m=None, fast=False, checkAtRest=True,
  20. checkChargeNeutral=True):
  21. """
  22. Args:
  23. antiparticleOccs: occupation number list
  24. particleOccs: occupation number list
  25. nmax (int): wave number of the last element in occs
  26. fast (bool): a flag for when occs and nmax are all that are needed
  27. (see transformState in oscillators.py)
  28. checkAtRest (bool): a flag to check if the total momentum is zero
  29. """
  30. #assert m >= 0, "Negative or zero mass"
  31. #assert L > 0, "Circumference must be positive"
  32. assert (len(particleOccs) == len(antiparticleOccs)),\
  33. "Occupation number lists should match in length"
  34. assert (np.all(np.less_equal(particleOccs, 1))
  35. and np.all(np.less_equal(antiparticleOccs, 1))),\
  36. "Pauli exclusion violated"
  37. self.particleOccs = np.array(particleOccs)
  38. self.antiparticleOccs = np.array(antiparticleOccs)
  39. self.occs = np.transpose(np.vstack((self.particleOccs,
  40. self.antiparticleOccs)))
  41. self.size = len(self.occs)
  42. self.nmax = nmax
  43. self.nmin = self.nmax - self.size + 1
  44. self.isDressed = False
  45. self.fast = fast
  46. if fast:
  47. return
  48. wavenum = np.arange(self.nmin, self.nmax+1)
  49. self.totalWN = (wavenum*np.transpose(self.occs)).sum()
  50. self.__parityEigenstate = (self.size == 2*self.nmax + 1
  51. and np.array_equal(self.occs[::-1],self.occs))
  52. self.netCharge = self.particleOccs.sum() - self.antiparticleOccs.sum()
  53. if checkAtRest:
  54. if self.totalWN != 0:
  55. raise ValueError("State not at rest")
  56. if checkChargeNeutral:
  57. if self.netCharge != 0:
  58. raise ValueError("State not charge-neutral")
  59. self.__chargeNeutral = (self.netCharge == 0)
  60. self.L = L
  61. self.m = m
  62. energies = omega(wavenum,L,m)
  63. self.energy = (energies*np.transpose(self.occs)).sum()
  64. self.momentum = (2.*pi/self.L)*self.totalWN
  65. # the second behavior here is for checking states up to parity
  66. def __eq__(self, other):
  67. return np.array_equal(self.occs,other.occs) or np.array_equal(self.occs,other.occs[::-1])
  68. def __getitem__(self, wn):
  69. """ Returns the occupation numbers corresponding to a wave number"""
  70. return self.occs[int(wn - self.nmin)]
  71. def __hash__(self):
  72. """Needed for construction of state lookup dictionaries"""
  73. return hash(tuple(np.reshape(self.occs,2*len(self.occs))))
  74. def isParityEigenstate(self):
  75. """ Returns True if the Fock space state is a P-parity eigenstate """
  76. return self.__parityEigenstate
  77. def isNeutral(self):
  78. return self.__chargeNeutral
  79. def __repr__(self):
  80. return f"(Particle occs: {self.occs.T[0]}, antiparticle occs: {self.occs.T[1]})"#str(self.occs)
  81. def __setitem__(self, wn, n):
  82. """ Sets the occupation number corresponding to a wave number """
  83. if self.fast==False:
  84. self.energy += ((n-self[wn])*omega(wn,self.L,self.m)).sum()
  85. self.totalWN += ((n-self[wn])*wn).sum()
  86. self.momentum = (2.*pi/self.L)*self.totalWN
  87. #should we update parity eigenstate too? probably - IL
  88. self.occs[int(wn-self.nmin)] = n
  89. def parityReversed(self):
  90. """ Reverse under P parity """
  91. if not self.size == 2*self.nmax+1:
  92. raise ValueError("attempt to reverse asymmetric occupation list")
  93. return FermionState(self.occs[::-1,0],self.occs[::-1,1],self.nmax,
  94. L=self.L,m=self.m)
  95. class FermionBasis(Basis):
  96. """ Generic list of fermionic basis elements sorted in energy. """
  97. def __init__(self, L, Emax, m, nmax=None, bcs="periodic"):
  98. """ nmax: if not None, forces the state vectors to have length 2nmax+1
  99. """
  100. self.L = L
  101. self.Emax = Emax
  102. self.m = m
  103. assert bcs == "periodic" or bcs == "antiperiodic"
  104. self.bcs = bcs
  105. if nmax == None:
  106. if bcs == "periodic":
  107. self.nmax = int(math.floor(sqrt((Emax/2.)**2.-m**2.)*self.L/(2.*pi)))
  108. elif bcs == "antiperiodic":
  109. self.nmax = half_floor(sqrt((Emax/2.)**2.-m**2.)*self.L/(2.*pi))
  110. else:
  111. self.nmax = nmax
  112. self.stateList = sorted(self.__buildBasis(), key=attrgetter('energy'))
  113. # Collection of Fock space states, possibly sorted in energy
  114. self.reversedStateList = [state.parityReversed() for state in self.stateList]
  115. # P-parity reversed collection of Fock-space states
  116. #make a dictionary of states for use in lookup()
  117. self.statePos = { state : i for i, state in enumerate(self.stateList) }
  118. self.reversedStatePos = { state : i for i, state in enumerate(self.reversedStateList) }
  119. self.size = len(self.stateList)
  120. def lookup(self, state):
  121. """looks up the index of a state. If this is not present, tries to look up for its parity-reversed
  122. Returns:
  123. A tuple with the normalization factor c and the state index i
  124. """
  125. #rewritten to use if statements, which is syntactically somewhat cleaner
  126. if (state in self.statePos):
  127. i = self.statePos[state]
  128. return (1,i)
  129. if (state in self.reversedStatePos):
  130. return (1,self.reversedStatePos[state])
  131. raise NotInBasis
  132. def __buildRMlist(self):
  133. """ sets list of all right-moving states with particles of individual wave number
  134. <= nmax, total momentum <= Emax/2 and total energy <= Emax
  135. This function works by first filling in n=1 mode in all possible ways, then n=2 mode
  136. in all possible ways assuming the occupation of n=1 mode, etc
  137. This is modified for fermionic states. In the fermionic case,
  138. occupation numbers are zero or one due to Pauli exclusion.
  139. """
  140. if self.nmax == 0:
  141. self.__RMlist = [FermionState([],[],nmax=0,L=self.L,m=self.m,
  142. checkAtRest=False,
  143. checkChargeNeutral=False)]
  144. return
  145. # for zero-momentum states, the maximum value of k is as follows.
  146. kmax = max(0., np.sqrt((self.Emax/2.)**2.-self.m**2.))
  147. # the max occupation number of the n=1 mode is either kmax divided
  148. # by the momentum at n=1 or Emax/omega, whichever is less
  149. # the 2 here accounts for that we can have a single particle and an
  150. # antiparticle in n=1
  151. if self.bcs == "periodic":
  152. seedN = 1
  153. elif self.bcs == "antiperiodic":
  154. seedN = 0.5
  155. maxN1 = min([math.floor(kmax/k(seedN,self.L)),
  156. math.floor(self.Emax/omega(seedN,self.L,self.m)),
  157. 2])
  158. if maxN1 <= 0:
  159. nextOccs = [[0,0]]
  160. elif maxN1 == 1:
  161. nextOccs = [[0,0],[0,1],[1,0]]
  162. else:
  163. nextOccs = [[0,0],[0,1],[1,0],[1,1]]
  164. RMlist0 = [FermionState([occs[0]],[occs[1]],seedN,L=self.L,m=self.m,checkAtRest=False,
  165. checkChargeNeutral=False) for occs in nextOccs]
  166. # seed list of RM states,all possible n=1 mode occupation numbers
  167. for n in np.arange(seedN+1,self.nmax+1): #go over all other modes
  168. RMlist1=[] #we will take states out of RMlist0, augment them and add to RMlist1
  169. for RMstate in RMlist0: # cycle over all RMstates
  170. p0 = RMstate.momentum
  171. e0 = RMstate.energy
  172. # maximal occupation number of mode n given the momentum/energy
  173. # in all previous modes. The sqrt term accounts for the
  174. # ground state energy of the overall state, while e0 gives
  175. # the energy in each of the mode excitations.
  176. maxNn = min([math.floor((kmax-p0)/k(n,self.L)),
  177. math.floor((self.Emax-np.sqrt(self.m**2+p0**2)-e0)/omega(n,self.L,self.m)),
  178. 2])
  179. if maxNn <= 0:
  180. nextOccsList = [[0,0]]
  181. elif maxNn == 1:
  182. nextOccsList = [[0,0],[0,1],[1,0]]
  183. else:
  184. nextOccsList = [[0,0],[0,1],[1,0],[1,1]]
  185. assert maxNn <= 2, f"maxNn was {maxNn}"
  186. # got to here in edits.
  187. # should we maybe just write a numpy function to calculate
  188. # energy and momentum from the occs list?
  189. # update: i did this. But it would take an extra
  190. # function call instead of accessing state properties.
  191. #print(f"RMstate occs are {RMstate.occs}")
  192. for nextOccs in nextOccsList:
  193. longerstate = np.append(RMstate.occs,[nextOccs],axis=0)
  194. RMlist1.append(FermionState(longerstate[:,0],
  195. longerstate[:,1],
  196. nmax=n,L=self.L,m=self.m,
  197. checkAtRest=False,
  198. checkChargeNeutral=False))
  199. #RMlist1 created, copy it back to RMlist0
  200. RMlist0 = RMlist1
  201. self.__RMlist = RMlist0 #save list of RMstates in an internal variable
  202. def __divideRMlist(self):
  203. """ divides the list of RMstates into a list of lists, RMdivided,
  204. so that two states in each list have a fixed total RM wavenumber,
  205. also each sublist is ordered in energy"""
  206. self.__nRMmax=max([RMstate.totalWN for RMstate in self.__RMlist])
  207. if self.bcs == "periodic":
  208. self.__RMdivided = [[] for ntot in range(self.__nRMmax+1)] #initialize list of lists
  209. elif self.bcs == "antiperiodic":
  210. self.__RMdivided = [[] for ntot in np.arange(self.__nRMmax*2+2)] #initialize list of lists
  211. for RMstate in self.__RMlist: #go over RMstates and append them to corresponding sublists
  212. if self.bcs == "periodic":
  213. self.__RMdivided[RMstate.totalWN].append(RMstate)
  214. elif self.bcs == "antiperiodic":
  215. self.__RMdivided[int(RMstate.totalWN*2)].append(RMstate)
  216. #now sort each sublist in energy
  217. for RMsublist in self.__RMdivided:
  218. RMsublist.sort(key=attrgetter('energy'))
  219. # should we revise this to be more efficient about generating
  220. # only charge zero states?
  221. # finally function which builds the basis
  222. def __buildBasis(self):
  223. """ creates basis of states of total momentum zero and energy <=Emax """
  224. self.__buildRMlist()
  225. self.__divideRMlist()
  226. statelist = []
  227. for nRM,RMsublist in enumerate(self.__RMdivided):
  228. for i, RMstate in enumerate(RMsublist):
  229. ERM = RMstate.energy
  230. for LMstate in RMsublist: # LM part of the state will come from the same sublist. We take the position of LMState to be greater or equal to the position of RMstate
  231. #we will just have to reverse it
  232. ELM = LMstate.energy
  233. deltaE = self.Emax - ERM - ELM
  234. if deltaE < 0: #if this happens, we can break since subsequent LMstates have even higherenergy (RMsublist is ordered in energy)
  235. break
  236. if self.bcs == "antiperiodic":
  237. #there is no zero mode with half integer n
  238. newOccs = np.array((LMstate.occs[::-1]).tolist()
  239. + RMstate.occs.tolist())
  240. state = FermionState(newOccs[:,0],newOccs[:,1],
  241. nmax=self.nmax,L=self.L,m=self.m,
  242. checkAtRest=True,
  243. checkChargeNeutral=False)
  244. if state.isNeutral():
  245. statelist.append(state)
  246. continue
  247. else:
  248. assert self.bcs == "periodic"
  249. # for massless excitations we can put the max of 2
  250. # excitations in the zero mode.
  251. # this is different from the bosonic case where
  252. # massless excitations carry no energy and the number
  253. # of zero mode excitations is unbounded.
  254. # we can manually set this to not add particles to the
  255. # zero mode by taking maxN0 = 0.
  256. if self.m != 0:
  257. maxN0 = min(int(math.floor(deltaE/self.m)),2)
  258. else:
  259. maxN0 = 2
  260. assert maxN0 in range(3)
  261. if maxN0 == 0:
  262. nextOccsList = [[0,0]]
  263. elif maxN0 == 1:
  264. nextOccsList = [[0,0],[0,1],[1,0]]
  265. else:
  266. nextOccsList = [[0,0],[0,1],[1,0],[1,1]]
  267. for nextOccs in nextOccsList:
  268. newOccs = np.array((LMstate.occs[::-1]).tolist()
  269. + [nextOccs] + RMstate.occs.tolist())
  270. #print(newOccs)
  271. state = FermionState(newOccs[:,0],newOccs[:,1],
  272. nmax=self.nmax,L=self.L,m=self.m,
  273. checkAtRest=True,
  274. checkChargeNeutral=False)
  275. if state.isNeutral():
  276. statelist.append(state)
  277. return statelist