dressedstatefuncs.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  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. from qedstatefuncs import FermionState, FermionBasis, half_floor
  14. def calculateAxialCharge(state):
  15. total = 0
  16. for wn in np.arange(state.nmin, state.nmax+1):
  17. if wn < 0:
  18. total -= (state[wn][0]-state[wn][1])
  19. elif wn > 0:
  20. total += (state[wn][0]-state[wn][1])
  21. return total
  22. class DressedFermionState(FermionState):
  23. def __init__(self, particleOccs, antiparticleOccs, zeromode, nmax,
  24. L=None, m=None, fast=False, checkAtRest=True,
  25. checkChargeNeutral=True, e_var=1):
  26. """
  27. Args:
  28. antiparticleOccs: occupation number list
  29. particleOccs: occupation number list
  30. zeromode (int): int specifying eigenstate of the A1 zero mode
  31. nmax (int): wave number of the last element in occs
  32. fast (bool): a flag for when occs and nmax are all that are needed
  33. (see transformState in oscillators.py)
  34. checkAtRest (bool): a flag to check if the total momentum is zero
  35. e_var (float): charge (default 1), used in calculating zero mode energy
  36. """
  37. #assert m >= 0, "Negative or zero mass"
  38. #assert L > 0, "Circumference must be positive"
  39. assert zeromode >= 0, "zeromode eigenstate label should be >=0"
  40. self.zeromode = zeromode
  41. self.omega0 = e_var/sqrt(pi)
  42. FermionState.__init__(self, particleOccs, antiparticleOccs, nmax,
  43. L, m, fast, checkAtRest, checkChargeNeutral)
  44. self.isDressed = True
  45. if fast: return
  46. self.energy += self.zeromode * self.omega0
  47. # removed behavior checking equality of states up to parity
  48. def __eq__(self, other):
  49. return np.array_equal(self.occs,other.occs) and self.zeromode == other.zeromode
  50. def __hash__(self):
  51. """Needed for construction of state lookup dictionaries"""
  52. return hash(tuple(np.append(np.reshape(self.occs,2*len(self.occs)),[self.zeromode])))
  53. def getAZeroMode(self):
  54. return self.zeromode
  55. def __repr__(self):
  56. return f"(Particle occs: {self.occs.T[0]}, antiparticle occs: {self.occs.T[1]}, zero mode: {self.zeromode})"
  57. def setAZeroMode(self, n):
  58. if self.fast==False:
  59. self.energy += self.omega0*(n-self.zeromode)
  60. self.zeromode = n
  61. class DressedFermionBasis(FermionBasis):
  62. """ List of fermionic basis elements dressed with a zero-mode wavefunction"""
  63. def __init__(self, L, Emax, m, nmax=None, bcs="periodic", q=1):
  64. self.q = q
  65. self.omega0 = q/sqrt(pi)
  66. """ nmax: if not None, forces the state vectors to have length 2nmax+1
  67. """
  68. self.L = L
  69. self.Emax = Emax
  70. self.m = m
  71. assert bcs == "periodic" or bcs == "antiperiodic"
  72. self.bcs = bcs
  73. if nmax == None:
  74. if bcs == "periodic":
  75. self.nmax = int(math.floor(sqrt((Emax/2.)**2.-m**2.)*self.L/(2.*pi)))
  76. elif bcs == "antiperiodic":
  77. self.nmax = half_floor(sqrt((Emax/2.)**2.-m**2.)*self.L/(2.*pi))
  78. else:
  79. self.nmax = nmax
  80. self.stateList = sorted(self.__buildBasis(), key=attrgetter('energy'))
  81. # Collection of Fock space states, possibly sorted in energy
  82. self.reversedStateList = [state.parityReversed() for state in self.stateList]
  83. # P-parity reversed collection of Fock-space states
  84. #make a dictionary of states for use in lookup()
  85. self.statePos = { state : i for i, state in enumerate(self.stateList) }
  86. self.reversedStatePos = { state : i for i, state in enumerate(self.reversedStateList) }
  87. self.size = len(self.stateList)
  88. def __buildBasis(self):
  89. """ creates basis of states of total momentum zero and energy <=Emax """
  90. self.__buildRMlist()
  91. self.__divideRMlist()
  92. statelist = []
  93. for nRM,RMsublist in enumerate(self.__RMdivided):
  94. for i, RMstate in enumerate(RMsublist):
  95. ERM = RMstate.energy
  96. 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
  97. #we will just have to reverse it
  98. ELM = LMstate.energy
  99. deltaE = self.Emax - ERM - ELM
  100. if deltaE < 0: #if this happens, we can break since subsequent LMstates have even higherenergy (RMsublist is ordered in energy)
  101. break
  102. if self.bcs == "antiperiodic":
  103. #there is no zero mode with half integer n
  104. newOccs = np.array((LMstate.occs[::-1]).tolist()
  105. + RMstate.occs.tolist())
  106. state = DressedFermionState(newOccs[:,0],newOccs[:,1],0,
  107. nmax=self.nmax,L=self.L,m=self.m,
  108. checkAtRest=True,
  109. checkChargeNeutral=False)
  110. if state.isNeutral():
  111. statelist.append(state)
  112. self.addZeroModes(statelist, state.energy, newOccs)
  113. continue
  114. else:
  115. assert self.bcs == "periodic"
  116. # for massless excitations we can put the max of 2
  117. # excitations in the zero mode.
  118. # this is different from the bosonic case where
  119. # massless excitations carry no energy and the number
  120. # of zero mode excitations is unbounded.
  121. # we can manually set this to not add particles to the
  122. # zero mode by taking maxN0 = 0.
  123. if self.m != 0:
  124. maxN0 = min(int(math.floor(deltaE/self.m)),2)
  125. else:
  126. maxN0 = 2
  127. assert maxN0 in range(3)
  128. if maxN0 == 0:
  129. nextOccsList = [[0,0]]
  130. elif maxN0 == 1:
  131. nextOccsList = [[0,0],[0,1],[1,0]]
  132. else:
  133. nextOccsList = [[0,0],[0,1],[1,0],[1,1]]
  134. for nextOccs in nextOccsList:
  135. newOccs = np.array((LMstate.occs[::-1]).tolist()
  136. + [nextOccs] + RMstate.occs.tolist())
  137. #print(newOccs)
  138. state = DressedFermionState(newOccs[:,0],newOccs[:,1],0,
  139. nmax=self.nmax,L=self.L,m=self.m,
  140. checkAtRest=True,
  141. checkChargeNeutral=False)
  142. if state.isNeutral():
  143. statelist.append(state)
  144. self.addZeroModes(statelist, state.energy, newOccs)
  145. return statelist
  146. def __buildRMlist(self):
  147. """ sets list of all right-moving states with particles of individual wave number
  148. <= nmax, total momentum <= Emax/2 and total energy <= Emax
  149. This function works by first filling in n=1 mode in all possible ways, then n=2 mode
  150. in all possible ways assuming the occupation of n=1 mode, etc
  151. This is modified for fermionic states. In the fermionic case,
  152. occupation numbers are zero or one due to Pauli exclusion.
  153. """
  154. if self.nmax == 0:
  155. self.__RMlist = [FermionState([],[],nmax=0,L=self.L,m=self.m,
  156. checkAtRest=False,
  157. checkChargeNeutral=False)]
  158. return
  159. # for zero-momentum states, the maximum value of k is as follows.
  160. kmax = max(0., np.sqrt((self.Emax/2.)**2.-self.m**2.))
  161. # the max occupation number of the n=1 mode is either kmax divided
  162. # by the momentum at n=1 or Emax/omega, whichever is less
  163. # the 2 here accounts for that we can have a single particle and an
  164. # antiparticle in n=1
  165. if self.bcs == "periodic":
  166. seedN = 1
  167. elif self.bcs == "antiperiodic":
  168. seedN = 0.5
  169. maxN1 = min([math.floor(kmax/k(seedN,self.L)),
  170. math.floor(self.Emax/omega(seedN,self.L,self.m)),
  171. 2])
  172. if maxN1 <= 0:
  173. nextOccs = [[0,0]]
  174. elif maxN1 == 1:
  175. nextOccs = [[0,0],[0,1],[1,0]]
  176. else:
  177. nextOccs = [[0,0],[0,1],[1,0],[1,1]]
  178. RMlist0 = [FermionState([occs[0]],[occs[1]],seedN,L=self.L,m=self.m,checkAtRest=False,
  179. checkChargeNeutral=False) for occs in nextOccs]
  180. # seed list of RM states,all possible n=1 mode occupation numbers
  181. for n in np.arange(seedN+1,self.nmax+1): #go over all other modes
  182. RMlist1=[] #we will take states out of RMlist0, augment them and add to RMlist1
  183. for RMstate in RMlist0: # cycle over all RMstates
  184. p0 = RMstate.momentum
  185. e0 = RMstate.energy
  186. # maximal occupation number of mode n given the momentum/energy
  187. # in all previous modes. The sqrt term accounts for the
  188. # ground state energy of the overall state, while e0 gives
  189. # the energy in each of the mode excitations.
  190. maxNn = min([math.floor((kmax-p0)/k(n,self.L)),
  191. math.floor((self.Emax-np.sqrt(self.m**2+p0**2)-e0)/omega(n,self.L,self.m)),
  192. 2])
  193. if maxNn <= 0:
  194. nextOccsList = [[0,0]]
  195. elif maxNn == 1:
  196. nextOccsList = [[0,0],[0,1],[1,0]]
  197. else:
  198. nextOccsList = [[0,0],[0,1],[1,0],[1,1]]
  199. assert maxNn <= 2, f"maxNn was {maxNn}"
  200. # got to here in edits.
  201. # should we maybe just write a numpy function to calculate
  202. # energy and momentum from the occs list?
  203. # update: i did this. But it would take an extra
  204. # function call instead of accessing state properties.
  205. #print(f"RMstate occs are {RMstate.occs}")
  206. for nextOccs in nextOccsList:
  207. longerstate = np.append(RMstate.occs,[nextOccs],axis=0)
  208. RMlist1.append(FermionState(longerstate[:,0],
  209. longerstate[:,1],
  210. nmax=n,L=self.L,m=self.m,
  211. checkAtRest=False,
  212. checkChargeNeutral=False))
  213. #RMlist1 created, copy it back to RMlist0
  214. RMlist0 = RMlist1
  215. self.__RMlist = RMlist0 #save list of RMstates in an internal variable
  216. def __divideRMlist(self):
  217. """ divides the list of RMstates into a list of lists, RMdivided,
  218. so that two states in each list have a fixed total RM wavenumber,
  219. also each sublist is ordered in energy"""
  220. self.__nRMmax=max([RMstate.totalWN for RMstate in self.__RMlist])
  221. if self.bcs == "periodic":
  222. self.__RMdivided = [[] for ntot in range(self.__nRMmax+1)] #initialize list of lists
  223. elif self.bcs == "antiperiodic":
  224. self.__RMdivided = [[] for ntot in np.arange(self.__nRMmax*2+2)] #initialize list of lists
  225. for RMstate in self.__RMlist: #go over RMstates and append them to corresponding sublists
  226. if self.bcs == "periodic":
  227. self.__RMdivided[RMstate.totalWN].append(RMstate)
  228. elif self.bcs == "antiperiodic":
  229. self.__RMdivided[int(RMstate.totalWN*2)].append(RMstate)
  230. #now sort each sublist in energy
  231. for RMsublist in self.__RMdivided:
  232. RMsublist.sort(key=attrgetter('energy'))
  233. def addZeroModes(self, statelist, state_energy, occs):
  234. #print("start addZeromodes")
  235. zeromodetemp = 1
  236. deltaE_zeromode = self.Emax - state_energy
  237. while (deltaE_zeromode > self.omega0):
  238. #print(deltaE_zeromode)
  239. state = DressedFermionState(occs[:,0],occs[:,1],zeromodetemp,
  240. nmax=self.nmax,L=self.L,m=self.m)
  241. statelist.append(state)
  242. deltaE_zeromode -= self.omega0
  243. zeromodetemp += 1
  244. return
  245. class ZeroModeRaisingOperator():
  246. """ Raising operator for the zero mode of A1. Has similar methods to
  247. FermionOperator.
  248. """
  249. def __init__(self,extracoeff=1,q=1):
  250. """
  251. Args:
  252. clist, dlist, L, m: as above
  253. extracoeff (float): an overall multiplicative prefactor for the
  254. operator, *written as a power of the field operator phi*
  255. normed (bool): indicates whether factor of 1/sqrt(2*omega) has
  256. been absorbed into the definition of the spinor wavefunctions
  257. """
  258. self.coeff = extracoeff
  259. # coeff converts the overall prefactor of phi (extracoeff) to a prefactor
  260. # for the string of creation and annihilation operators in the final operator
  261. # see the normalization in Eq. 2.6
  262. # if normed:
  263. # self.coeff = extracoeff
  264. # else:
  265. # self.coeff = extracoeff/product([sqrt(2.*L*omega(n,L,m))
  266. # for n in clist+dlist])
  267. self.deltaE = q/sqrt(pi)
  268. def __repr__(self):
  269. return f"{extracoeff}*a^dagger"
  270. def _transformState(self, state0, returnCoeff=False):
  271. """
  272. Applies the normal ordered operator to a given state.
  273. Args:
  274. state0 (State): an input DressedFermionState for this operator
  275. returncoeff (bool): boolean representing whether or not to
  276. include the factor self.coeff with the returned state
  277. Returns:
  278. A tuple representing the input state after being acted on by
  279. the normal-ordered operator and any multiplicative factors
  280. from performing the commutations.
  281. """
  282. #make a copy of this state up to occupation numbers and nmax
  283. #use DressedFermionState if the original state is dressed
  284. #otherwise use FermionState
  285. assert state0.isDressed, "State has no zero mode"
  286. state = DressedFermionState(particleOccs=state0.occs[:,0],
  287. antiparticleOccs=state0.occs[:,1],
  288. zeromode=state0.getAZeroMode(),
  289. nmax=state0.nmax,
  290. fast=True)
  291. # note: there may be an easier way for fermionic states
  292. # however, these loops are short and fast, so NumPy shortcuts probably
  293. # will not provide too much speed-up at this level.
  294. norm = sqrt(state.getAZeroMode()+1)
  295. state.setAZeroMode(state.getAZeroMode()+1)
  296. # if returnCoeff:
  297. # return (norm*self.coeff, state)
  298. return (norm, state)
  299. def apply(self, basis, i, lookupbasis=None):
  300. """ Takes a state index in basis, returns another state index (if it
  301. belongs to the lookupbasis) and a proportionality coefficient. Otherwise raises NotInBasis.
  302. lookupbasis can be different from basis, but it's assumed that they have the same nmax
  303. This is not updated for dressed states.
  304. """
  305. if lookupbasis == None:
  306. lookupbasis = basis
  307. if self.deltaE+basis[i].energy < 0.-tol or self.deltaE+basis[i].energy > lookupbasis.Emax+tol:
  308. # The transformed element surely does not belong to the basis if E>Emax or E<0
  309. raise NotInBasis()
  310. # apply the normal-order operator to this basis state
  311. n, newstate = self._transformState(basis[i])
  312. #if the state was annihilated by the operator, return (0, None)
  313. if n==0:
  314. return (0, None)
  315. # otherwise, look up this state in the lookup basis
  316. norm, j = lookupbasis.lookup(newstate)
  317. c = 1.
  318. #if basis[i].isParityEigenstate():
  319. # c = 1/sqrt(2.)
  320. # Required for state normalization
  321. # return the overall proportionality and the state index
  322. return (norm*c*sqrt(n)*self.coeff, j)
  323. def apply2(self, basis, state, lookupbasis=None):
  324. """
  325. Like apply but with a state as input rather than an index in the
  326. original basis.
  327. """
  328. # TO-DO: add the energy shortcut from the original apply
  329. # we need the energy of the state-- is it expensive to recompute it?
  330. if lookupbasis == None:
  331. lookupbasis = basis
  332. n, newstate = self._transformState(state)
  333. if n==0:
  334. return (0, None)
  335. norm, j = lookupbasis.lookup(newstate)
  336. c = 1.
  337. #no sqrt n since n is either 1 or -1
  338. return (norm*c*n*self.coeff,j)
  339. class ZeroModeLoweringOperator():
  340. """ Lowering operator for the zero mode of A1. Has similar methods to
  341. FermionOperator.
  342. """
  343. def __init__(self,extracoeff=1,q=1):
  344. """
  345. Args:
  346. clist, dlist, L, m: as above
  347. extracoeff (float): an overall multiplicative prefactor for the
  348. operator, *written as a power of the field operator phi*
  349. normed (bool): indicates whether factor of 1/sqrt(2*omega) has
  350. been absorbed into the definition of the spinor wavefunctions
  351. """
  352. self.coeff = extracoeff
  353. # coeff converts the overall prefactor of phi (extracoeff) to a prefactor
  354. # for the string of creation and annihilation operators in the final operator
  355. # see the normalization in Eq. 2.6
  356. # if normed:
  357. # self.coeff = extracoeff
  358. # else:
  359. # self.coeff = extracoeff/product([sqrt(2.*L*omega(n,L,m))
  360. # for n in clist+dlist])
  361. self.deltaE = q/sqrt(pi)
  362. def __repr__(self):
  363. return f"{extracoeff}*a"
  364. def _transformState(self, state0, returnCoeff=False):
  365. """
  366. Applies the normal ordered operator to a given state.
  367. Args:
  368. state0 (State): an input DressedFermionState for this operator
  369. returncoeff (bool): boolean representing whether or not to
  370. include the factor self.coeff with the returned state
  371. Returns:
  372. A tuple representing the input state after being acted on by
  373. the normal-ordered operator and any multiplicative factors
  374. from performing the commutations.
  375. """
  376. #make a copy of this state up to occupation numbers and nmax
  377. #use DressedFermionState if the original state is dressed
  378. #otherwise use FermionState
  379. assert state0.isDressed, "State has no zero mode"
  380. state = DressedFermionState(particleOccs=state0.occs[:,0],
  381. antiparticleOccs=state0.occs[:,1],
  382. zeromode=state0.getAZeroMode(),
  383. nmax=state0.nmax,
  384. fast=True)
  385. # note: there may be an easier way for fermionic states
  386. # however, these loops are short and fast, so NumPy shortcuts probably
  387. # will not provide too much speed-up at this level.
  388. if state.getAZeroMode() == 0:
  389. return (0, None)
  390. norm = sqrt(state.getAZeroMode())
  391. state.setAZeroMode(state.getAZeroMode()-1)
  392. # if returnCoeff:
  393. # return (norm*self.coeff, state)
  394. return (norm, state)
  395. def apply(self, basis, i, lookupbasis=None):
  396. """ Takes a state index in basis, returns another state index (if it
  397. belongs to the lookupbasis) and a proportionality coefficient. Otherwise raises NotInBasis.
  398. lookupbasis can be different from basis, but it's assumed that they have the same nmax
  399. This is not updated for dressed states.
  400. """
  401. if lookupbasis == None:
  402. lookupbasis = basis
  403. if self.deltaE+basis[i].energy < 0.-tol or self.deltaE+basis[i].energy > lookupbasis.Emax+tol:
  404. # The transformed element surely does not belong to the basis if E>Emax or E<0
  405. raise NotInBasis()
  406. # apply the normal-order operator to this basis state
  407. n, newstate = self._transformState(basis[i])
  408. #if the state was annihilated by the operator, return (0, None)
  409. if n==0:
  410. return (0, None)
  411. # otherwise, look up this state in the lookup basis
  412. norm, j = lookupbasis.lookup(newstate)
  413. c = 1.
  414. #if basis[i].isParityEigenstate():
  415. # c = 1/sqrt(2.)
  416. # Required for state normalization
  417. # return the overall proportionality and the state index
  418. return (norm*c*sqrt(n)*self.coeff, j)
  419. def apply2(self, basis, state, lookupbasis=None):
  420. """
  421. Like apply but with a state as input rather than an index in the
  422. original basis.
  423. """
  424. # TO-DO: add the energy shortcut from the original apply
  425. # we need the energy of the state-- is it expensive to recompute it?
  426. if lookupbasis == None:
  427. lookupbasis = basis
  428. n, newstate = self._transformState(state)
  429. if n==0:
  430. return (0, None)
  431. norm, j = lookupbasis.lookup(newstate)
  432. c = 1.
  433. #no sqrt n since n is either 1 or -1
  434. return (norm*c*n*self.coeff,j)