statefuncs.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. ######################################################
  2. #
  3. # Fock space Hamiltonian truncation for phi^4 theory in 2 dimensions
  4. # Authors: Slava Rychkov (slava.rychkov@lpt.ens.fr) and Lorenzo Vitale (lorenzo.vitale@epfl.ch)
  5. # December 2014
  6. #
  7. ######################################################
  8. import numpy
  9. import scipy
  10. from numpy import sqrt, pi
  11. from operator import attrgetter
  12. import math
  13. from numpy.testing import assert_allclose
  14. tol = 0.00001
  15. def omega(n,L,m):
  16. """ computes one particle energy from wavenumber
  17. Note: since sqrt comes from scipy, this method also works with Numpy arrays.
  18. """
  19. return sqrt(m**2.+((2.*pi/L)*n)**2.)
  20. def k(n,L):
  21. """ computes momentum from wavenumber n, given circumference L"""
  22. return (2.*pi/L)*n
  23. def occsEnergy(occs,nmax,L,m):
  24. """
  25. Computes energy given a list of occupation numbers.
  26. occs can be a NumPy array or a list.
  27. """
  28. nmin = nmax - len(occs) + 1
  29. energies = omega(numpy.arange(len(occs))+nmin, L, m)
  30. return energies.dot(occs)
  31. def occsMomentum(occs,nmax,L):
  32. """
  33. Computes momentum given a list of occupation numbers.
  34. occs can be a NumPy array or a list.
  35. """
  36. nmin = nmax - len(occs) + 1
  37. # dot seems to be okay here because these are ints;
  38. # in the energy calc there is some rounding error
  39. totalWN = (numpy.arange(len(occs))+nmin).dot(occs)
  40. return (2*pi/L)*totalWN
  41. class State():
  42. def __init__(self, occs, nmax, L=None, m=None, fast=False, checkAtRest=True):
  43. """
  44. Args:
  45. occs (list of ints): occupation number list
  46. nmax (int): wave number of the last element in occs
  47. fast (bool): a flag for when occs and nmax are all that are needed
  48. (see transformState in oscillators.py)
  49. checkAtRest (bool): a flag to check if the total momentum is zero
  50. For instance,
  51. State([1,0,1],nmax=1) is a state with one excitation in the n=-1 mode
  52. and one in the n=+1 mode.
  53. State([1,0,1],nmax=2), however, is a state with one excitation in the
  54. n=0 mode and one in the n=+2 mode.
  55. """
  56. #assert m >= 0, "Negative or zero mass"
  57. #assert L > 0, "Circumference must be positive"
  58. self.occs = occs
  59. self.size = len(self.occs)
  60. self.nmax = nmax
  61. self.fast = fast
  62. if fast == True:
  63. return
  64. #make a list of ns of size running from nmax-occs.size+1 to nmax
  65. wavenum = scipy.vectorize(lambda i: i-self.size+self.nmax+1)(range(self.size))
  66. #compute the corresponding energies
  67. energies = scipy.vectorize(lambda k : omega(k,L,m))(wavenum)
  68. #and compute the sum of the ns, multiplied by the occupation numbers
  69. self.totalWN = (wavenum*self.occs).sum()
  70. if checkAtRest:
  71. if self.totalWN != 0:
  72. raise ValueError("State not at rest")
  73. if self.size == 2*self.nmax+1 and self.occs[::-1] == self.occs:
  74. self.__parityEigenstate = True
  75. else:
  76. self.__parityEigenstate = False
  77. self.L = L
  78. self.m = m
  79. self.energy = sum(energies*self.occs)
  80. #couldn't we just use the k function for this? IL
  81. self.momentum = (2.*pi/self.L)*self.totalWN
  82. def isParityEigenstate(self):
  83. """ Returns True if the Fock space state is a P-parity eigenstate """
  84. return self.__parityEigenstate
  85. def Kparity(self):
  86. """ Returns the K-parity quantum number """
  87. return (-1)**sum(self.occs)
  88. def __repr__(self):
  89. return str(self.occs)
  90. def __eq__(self, other):
  91. return (self.occs == other.occs) or (self.occs == other.occs[::-1])
  92. # check also if the P-reversed is the same!
  93. def __hash__(self):
  94. return hash(tuple(self.occs))
  95. def __setitem__(self, wn, n):
  96. """ Sets the occupation number corresponding to a wave number """
  97. if self.fast==False:
  98. self.energy += (n-self[wn])*omega(wn,self.L,self.m)
  99. self.totalWN += (n-self[wn])*wn
  100. self.momentum = (2.*pi/self.L)*self.totalWN
  101. self.occs[wn+self.size-self.nmax-1] = n
  102. def __getitem__(self, wn):
  103. """ Returns the occupation number corresponding to a wave number"""
  104. return self.occs[wn+self.size-self.nmax-1]
  105. def parityReversed(self):
  106. """ Reverse under P parity """
  107. if not self.size == 2*self.nmax+1:
  108. raise ValueError("attempt to reverse asymmetric occupation list")
  109. return State(self.occs[::-1],self.nmax,L=self.L,m=self.m)
  110. class npState(State):
  111. def __init__(self, occs, nmax, L=None, m=None, fast=False, checkAtRest=True):
  112. """
  113. A Numpy version of the State class.
  114. Args:
  115. occs: occupation number list (list or ndarray)
  116. nmax: wave number of the last element in occs (int)
  117. fast (bool): a flag for when occs and nmax are all that are needed
  118. (see transformState in oscillators.py)
  119. checkAtRest (bool): a flag to check if the total momentum is zero
  120. """
  121. #assert m >= 0, "Negative or zero mass"
  122. #assert L > 0, "Circumference must be positive"
  123. self.occs = numpy.array(occs)
  124. self.size = len(self.occs)
  125. self.nmax = nmax
  126. self.fast = fast
  127. if fast == True:
  128. return
  129. #make a list of ns of length self.size running from nmax-self.size+1 to nmax
  130. wavenum = numpy.arange(self.size)-self.size+self.nmax+1
  131. #compute the corresponding energies
  132. energies = omega(wavenum,L,m)
  133. #and compute the sum of the ns, multiplied by the occupation numbers
  134. self.totalWN = (wavenum*self.occs).sum()
  135. if checkAtRest:
  136. if self.totalWN != 0:
  137. raise ValueError("State not at rest")
  138. if self.size == 2*self.nmax+1 and numpy.array_equal(self.occs[::-1],self.occs):
  139. self.__parityEigenstate = True
  140. else:
  141. self.__parityEigenstate = False
  142. self.L = L
  143. self.m = m
  144. self.energy = sum(energies*self.occs)
  145. #couldn't we just use the k function for this? IL
  146. self.momentum = (2.*pi/self.L)*self.totalWN
  147. def parityReversed(self):
  148. """ Reverse under P parity """
  149. if not self.size == 2*self.nmax+1:
  150. raise ValueError("attempt to reverse asymmetric occupation list")
  151. return npState(self.occs[::-1],self.nmax,L=self.L,m=self.m)
  152. def __add__(self, other):
  153. """
  154. Performs (elementwise) addition with respect to the occupation
  155. number array, occs.
  156. Note: should not be used with the += assignment, since the
  157. resulting object will be a ndarray rather than an npState object.
  158. """
  159. return self.occs + other
  160. def __eq__(self, other):
  161. return numpy.array_equal(self.occs,other.occs) or numpy.array_equal(self.occs,other.occs[::-1])
  162. def __hash__(self):
  163. """Needed for construction of state lookup dictionaries"""
  164. return hash(tuple(self.occs))
  165. class NotInBasis(LookupError):
  166. """ Exception class """
  167. pass
  168. class Basis():
  169. """ Generic list of basis elements sorted in energy. """
  170. def __init__(self, L, Emax, m, K, nmax=None):
  171. """ nmax: if not None, forces the state vectors to have length 2nmax+1
  172. K: field parity (+1 or -1)
  173. """
  174. self.L = L
  175. self.Emax = Emax
  176. self.m = m
  177. self.K = K
  178. if nmax == None:
  179. self.nmax = int(math.floor(sqrt((Emax/2.)**2.-m**2.)*self.L/(2.*pi)))
  180. else:
  181. self.nmax=nmax
  182. self.stateList = sorted(self.__buildBasis(), key=attrgetter('energy'))
  183. # Collection of Fock space states, possibly sorted in energy
  184. self.reversedStateList = [state.parityReversed() for state in self.stateList]
  185. # P-parity reversed collection of Fock-space states
  186. #make a dictionary of states for use in lookup()
  187. self.statePos = { state : i for i, state in enumerate(self.stateList) }
  188. self.reversedStatePos = { state : i for i, state in enumerate(self.reversedStateList) }
  189. self.size = len(self.stateList)
  190. def __repr__(self):
  191. return str(self.stateList)
  192. def __len__(self):
  193. return len(self.stateList)
  194. def __getitem__(self,index):
  195. return self.stateList[index]
  196. def lookup(self, state):
  197. # Now this is implemented only for P-even states. Generalization for P-odd states is straightforward
  198. """looks up the index of a state. If this is not present, tries to look up for its parity-reversed
  199. Returns:
  200. A tuple with the normalization factor c and the state index i
  201. """
  202. #rewritten to use if statements, which is syntactically somewhat cleaner
  203. if (state in self.statePos):
  204. i = self.statePos[state]
  205. c=1.
  206. if (self.stateList[i].isParityEigenstate()):
  207. c=sqrt(2.)
  208. # Required for state normalization
  209. return (c,i)
  210. if (state in self.reversedStatePos):
  211. return (1,self.reversedStatePos[state])
  212. raise NotInBasis
  213. def __buildRMlist(self):
  214. """ sets list of all right-moving states with particles of individual wave number
  215. <= nmax, total momentum <= Emax/2 and total energy <= Emax
  216. This function works by first filling in n=1 mode in all possible ways, then n=2 mode
  217. in all possible ways assuming the occupation of n=1 mode, etc"""
  218. if self.nmax == 0:
  219. self.__RMlist = [State([],0,L=self.L,m=self.m,checkAtRest=False)]
  220. return
  221. # for zero-momentum states, the maximum value of k is as follows.
  222. kmax = max(0., sqrt((self.Emax/2.)**2.-self.m**2.))
  223. # the max occupation number of the n=1 mode is either kmax divided
  224. # by the momentum at n=1 or Emax/omega, whichever is less
  225. maxN1 = int(math.floor(
  226. min(kmax/k(1,self.L), self.Emax/omega(1,self.L,self.m))
  227. ))
  228. RMlist0 = [State([N],1,L=self.L,m=self.m,checkAtRest=False) for N in range(maxN1+1)]
  229. # seed list of RM states,all possible n=1 mode occupation numbers
  230. for n in range(2,self.nmax+1): #go over all other modes
  231. RMlist1=[] #we will take states out of RMlist0, augment them and add to RMlist1
  232. for RMstate in RMlist0: # cycle over all RMstates
  233. p0 = RMstate.momentum
  234. e0 = RMstate.energy
  235. maxNn = int(math.floor(
  236. min((kmax-p0)/k(n,self.L), (self.Emax-sqrt(self.m**2+p0**2)-e0)/omega(n,self.L,self.m))
  237. ))#maximal occupation number of mode n given the occupation numbers of all previous modes
  238. #either based on the k limit or the energy limit -IL
  239. for N in range(maxNn+1):
  240. longerstate=RMstate.occs[:]
  241. #longerstate = numpy.append(longerstate,N)
  242. longerstate.append(N) #add all possible occupation numbers for mode n
  243. RMlist1.append(State(longerstate,len(longerstate),L=self.L,m=self.m, checkAtRest=False))
  244. #RMlist1 created, copy it back to RMlist0
  245. RMlist0 = RMlist1
  246. self.__RMlist = RMlist0 #save list of RMstates in an internal variable
  247. def __buildRMlist2(self):
  248. """ sets list of all right-moving states with particles of individual wave number
  249. <= nmax, total momentum <= Emax/2 and total energy <= Emax
  250. This function works by first filling in n=1 mode in all possible ways, then n=2 mode
  251. in all possible ways assuming the occupation of n=1 mode, etc
  252. We use NumPy arrays to avoid having to create states that will
  253. eventually be thrown away.
  254. """
  255. if self.nmax == 0:
  256. self.__RMlist = [State([],0,L=self.L,m=self.m,checkAtRest=False)]
  257. return
  258. # for zero-momentum states, the maximum value of k is as follows.
  259. kmax = max(0., sqrt((self.Emax/2.)**2.-self.m**2.))
  260. # the max occupation number of the n=1 mode is either kmax divided
  261. # by the momentum at n=1 or Emax/omega, whichever is less
  262. maxN1 = int(math.floor(
  263. min(kmax/k(1,self.L), self.Emax/omega(1,self.L,self.m))
  264. ))
  265. RMlist0 = [State([N],1,L=self.L,m=self.m,checkAtRest=False) for N in range(maxN1+1)]
  266. # seed list of RM states,all possible n=1 mode occupation numbers
  267. for n in range(2,self.nmax+1): #go over all other modes
  268. RMlist1=[] #we will take states out of RMlist0, augment them and add to RMlist1
  269. for RMstate in RMlist0: # cycle over all RMstates
  270. #p0 = RMstate.momentum
  271. #e0 = RMstate.energy
  272. assert len(RMstate.occs) == RMstate.nmax
  273. p0 = occsMomentum(RMstate.occs, nmax=len(RMstate.occs),
  274. L=self.L)
  275. e0 = occsEnergy(RMstate.occs, nmax=len(RMstate.occs),
  276. L=self.L, m=self.m)
  277. '''
  278. # for testing occsMomentum and occsEnergy
  279. assert p0 == occsMomentum(RMstate.occs,
  280. len(RMstate.occs), self.L)
  281. assert_allclose(e0,occsEnergy(RMstate.occs,
  282. len(RMstate.occs), self.L, self.m))
  283. '''
  284. maxNn = int(math.floor(
  285. min((kmax-p0)/k(n,self.L), (self.Emax-sqrt(self.m**2+p0**2)-e0)/omega(n,self.L,self.m))
  286. ))#maximal occupation number of mode n given the occupation numbers of all previous modes
  287. #either based on the k limit or the energy limit -IL
  288. for N in range(maxNn+1):
  289. longerstate=RMstate.occs[:]
  290. #longerstate = numpy.append(longerstate,N)
  291. longerstate.append(N) #add all possible occupation numbers for mode n
  292. RMlist1.append(State(longerstate,len(longerstate),L=self.L,m=self.m, checkAtRest=False))
  293. #RMlist1 created, copy it back to RMlist0
  294. RMlist0 = RMlist1
  295. self.__RMlist = RMlist0 #save list of RMstates in an internal variable
  296. def __divideRMlist(self):
  297. """ divides the list of RMstates into a list of lists, RMdivided,
  298. so that two states in each list have a fixed total RM wavenumber,
  299. also each sublist is ordered in energy"""
  300. self.__nRMmax=max([RMstate.totalWN for RMstate in self.__RMlist])
  301. self.__RMdivided = [[] for ntot in range(self.__nRMmax+1)] #initialize list of lists
  302. for RMstate in self.__RMlist: #go over RMstates and append them to corresponding sublists
  303. self.__RMdivided[RMstate.totalWN].append(RMstate)
  304. #now sort each sublist in energy
  305. for RMsublist in self.__RMdivided:
  306. RMsublist.sort(key=attrgetter('energy'))
  307. # finally function which builds the basis
  308. def __buildBasis(self):
  309. """ creates basis of states of total momentum zero and energy <=Emax """
  310. self.__buildRMlist()
  311. self.__divideRMlist()
  312. statelist = []
  313. for nRM,RMsublist in enumerate(self.__RMdivided):
  314. for i, RMstate in enumerate(RMsublist):
  315. ERM = RMstate.energy
  316. for LMstate in RMsublist[i:]: # 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
  317. #we will just have to reverse it
  318. ELM = LMstate.energy
  319. deltaE = self.Emax - ERM - ELM
  320. if deltaE < 0: #if this happens, we can break since subsequent LMstates have even higherenergy (RMsublist is ordered in energy)
  321. break
  322. maxN0 = int(math.floor(deltaE/self.m))
  323. for N0 in range(maxN0+1):
  324. #possible values for the occupation value at rest
  325. state = State(LMstate.occs[::-1]+[N0]+RMstate.occs, self.nmax, L=self.L,m=self.m,checkAtRest=True)
  326. #state = npState(numpy.concatenate((LMstate.occs[::-1],numpy.array([N0]),RMstate.occs)), self.nmax, L=self.L,m=self.m,checkAtRest=True)
  327. if self.K == state.Kparity():
  328. statelist.append(state)
  329. return statelist