schwinger.py 48 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104
  1. ######################################################
  2. #
  3. # Fock space Hamiltonian truncation for QED in 1+1 dimensions
  4. # Author: Ian Lim (itlim@ucdavis.edu), adapted from work by Ryzhkov and Vitale
  5. # July 2020
  6. #
  7. ######################################################
  8. import scipy
  9. from scipy import pi
  10. import numpy as np
  11. import scipy.sparse.linalg
  12. import scipy.sparse
  13. import scipy.interpolate
  14. from operator import attrgetter
  15. from math import factorial
  16. from phi1234 import Matrix
  17. from statefuncs import Basis, NotInBasis, omega, State
  18. from oscillators import NormalOrderedOperator as NOO
  19. from qedops import uspinor, vspinor, FermionOperator
  20. from qedstatefuncs import FermionBasis, FermionState
  21. from dressedstatefuncs import DressedFermionBasis, DressedFermionState
  22. from dressedstatefuncs import ZeroModeRaisingOperator, ZeroModeLoweringOperator
  23. from dressedstatefuncs import calculateAxialCharge
  24. import collections
  25. import renorm
  26. import itertools
  27. from progress.bar import ChargingBar
  28. import time
  29. tol = 0.0001
  30. """ P denotes spatial parity, while K field parity.
  31. For now only the P-even sector is implemented """
  32. class Schwinger():
  33. """ main class
  34. Attributes
  35. ----------
  36. L : float
  37. Circumference of the circle on which to quantize
  38. m : float
  39. Mass of the scalar field
  40. Emax: float
  41. Maximum energy cutoff
  42. All basic dictionaries in this class have two keys, 1 and -1,
  43. corresponding to values of k-parity.
  44. h0: dict of Matrix objects
  45. Values are Matrix objects corresponding to the free Hamiltonian
  46. potential: dict of dict of Matrix
  47. Values are dictionaries where the keys are orders in
  48. the field (0,2,4 correspond to phi^0,phi^2,phi^4) and the values
  49. are the potential matrices at each order stored as Matrix objects
  50. When actually diagonalizing the Hamiltonian, we further restrict
  51. to a subspace with some different nmax, possibly?
  52. H: dict of Matrix objects
  53. Like h0, but on a restricted subspace defined by basis rather than
  54. fullBasis
  55. V: dict of dict of Matrix
  56. Like potential but restricted to the basis subspace
  57. """
  58. def __init__(self):
  59. self.L = None
  60. self.m = None
  61. self.Emax = None
  62. self.h0 = None
  63. self.potential = None
  64. self.h0OffDiag = None
  65. self.h0Sub = None
  66. self.H = None
  67. self.V = None
  68. self.h0OffDiagSub = None
  69. self.eigenvalues = None
  70. self.eigsrenlocal = None
  71. self.eigsrensubl = None
  72. self.eigenvectors = None
  73. self.basis = None
  74. self.fullBasis = None
  75. def buildFullBasis(self,L,m,Emax,bcs="periodic"):
  76. """ Builds the full Hilbert space basis """
  77. self.L=float(L)
  78. self.m=float(m)
  79. #self.fullBasis = FermionBasis(self.L, Emax, self.m, bcs=bcs)
  80. self.fullBasis = DressedFermionBasis(self.L, Emax, self.m, bcs=bcs)
  81. # TO-DO: test this method. We only have one interaction term so it's just V
  82. def buildBasis(self,Emax):
  83. """
  84. Builds the Hilbert space basis for which the Hamiltonian to actually diagonalize
  85. is calculated (in general it's a subspace of fullBasis)
  86. Note that this is called in phi4eigs, but not in generating
  87. the original potential matrix and free Hamiltonian.
  88. """
  89. self.basis = DressedFermionBasis(self.L, Emax, self.m, nmax=self.fullBasis.nmax,
  90. bcs=self.fullBasis.bcs)
  91. # We use the vector length (nmax) of the full basis.
  92. # In this way we can compare elements between the two bases
  93. self.Emax = float(Emax)
  94. self.V = self.potential.sub(self.basis, self.basis).M.tocoo()
  95. self.h0Sub = self.h0.sub(self.basis,self.basis).M.tocoo()
  96. self.h0OffDiagSub = self.h0OffDiag.sub(self.basis,self.basis).M.tocoo()
  97. def buildMatrix(self):
  98. """ Builds the full Hamiltonian in the basis of the free Hamiltonian eigenvectors.
  99. This is computationally intensive. It can be skipped by loading the matrix from file """
  100. """
  101. Possible speedups:
  102. Simplify the diagonal operator loops?
  103. Can we apply a numpy mask to simplify the loops without the
  104. conditionals?
  105. Basically this loops over the operators at each order in phi
  106. and stores all the normal order operators explicitly in lists.
  107. """
  108. L=self.L
  109. m=self.m
  110. basis = self.fullBasis
  111. lookupBasis = self.fullBasis
  112. Emax = basis.Emax
  113. nmax = basis.nmax
  114. """
  115. We split operators into diagonal and off-diagonal operators.
  116. The idea is that for off-diagonal operators, we can just compute
  117. the image of half the operators and generate the conjugates by
  118. taking the transpose. That is, if an operator X takes state A to B,
  119. then X^\dagger should take B to A.
  120. """
  121. diagOps = {0: None, 2:None, 4:None}
  122. offdiagOps = {0: None, 2:None, 4:None}
  123. """
  124. diagOps[0] = [ NOO([],[],L,m) ]
  125. offdiagOps[0] = []
  126. diagOps[2] = [ NOO([a],[a],L,m, extracoeff=2.) for a in range(-nmax,nmax+1) ]
  127. offdiagOps[2] = [ NOO([a,-a],[],L,m,extracoeff=comb(a,-a))
  128. for a in range(-nmax,nmax+1) if a<=-a<=nmax and
  129. omega(a,L,m)+omega(-a,L,m) <= Emax+tol]
  130. diagOps[4] = [ NOO([a,b],[c,a+b-c],L,m, extracoeff=6.*comb(a,b)*comb(c,a+b-c))
  131. for a in range(-nmax,nmax+1) for b in range (a,nmax+1)
  132. for c in range(-nmax,nmax+1) if
  133. ( c<=a+b-c<=nmax
  134. and (a,b) == (c,a+b-c)
  135. and -Emax-tol <= omega(a,L,m)+omega(b,L,m) - omega(c,L,m)-omega(a+b-c,L,m) <=Emax+tol)]
  136. offdiagOps[4] = [ NOO([a,b,c,-a-b-c],[],L,m,extracoeff=comb(a,b,c,-a-b-c))
  137. for a in range(-nmax,nmax+1) for b in range (a,nmax+1)
  138. for c in range(b,nmax+1) if c<=-a-b-c<=nmax and
  139. omega(a,L,m)+omega(b,L,m) + omega(c,L,m)+omega(-a-b-c,L,m)<= Emax+tol] \
  140. + [ NOO([a,b,c],[a+b+c],L,m, extracoeff = 4. * comb(a,b,c))
  141. for a in range(-nmax, nmax+1) for b in range (a,nmax+1)
  142. for c in range(b,nmax+1) if
  143. (-nmax<=a+b+c<=nmax
  144. and -Emax-tol <= omega(a,L,m)+omega(b,L,m)+ omega(c,L,m)-omega(a+b+c,L,m) <=Emax+tol)] \
  145. + [ NOO([a,b],[c,a+b-c],L,m, extracoeff = 6. * comb(a,b)*comb(c,a+b-c))
  146. for a in range(-nmax,nmax+1) for b in range (a,nmax+1)
  147. for c in range(-nmax,nmax+1) if
  148. ( c<=a+b-c<=nmax
  149. and (a,b) != (c,a+b-c)
  150. and sorted([abs(a),abs(b)]) < sorted([abs(c),abs(a+b-c)])
  151. and -Emax-tol <= omega(a,L,m)+omega(b,L,m)- omega(c,L,m)-omega(a+b-c,L,m) <=Emax+tol)]
  152. """
  153. #save as h0 a lookupBasis.size by 1 sparse matrix initialized to zeros
  154. #really just a single column
  155. #and store the bases as h0[k].basisI and h0[k].basisJ
  156. #self.h0[k] = Matrix(lookupBasis, basis)
  157. tempEnergies = np.empty(basis.size)
  158. #tempEnergies = np.array([basis[j].energy
  159. # for j in range(basis.size)])
  160. for j in range(basis.size):
  161. tempEnergies[j] = basis[j].energy
  162. #self.h0[k].finalize()
  163. temph0 = scipy.sparse.diags(tempEnergies,format="coo")
  164. self.h0 = Matrix(lookupBasis,basis,temph0)
  165. """
  166. We build the potential in this basis. self.potential is a
  167. dictionary with two keys corresponding to k=1 and k=-1.
  168. Each of the entries is a list of sparse matrices.
  169. Each sparse matrix consists of a sum of off-diagonal components
  170. and diagonal components in COO format.
  171. Possible speedup: could we apply each operator to all states
  172. in one shot? Then we would just loop over operators.
  173. Right now, for each state, we apply each operator one at a time
  174. and mark down its image in the corresponding column (the origin
  175. state) and row (the image state).
  176. If we apply an operator to a vector of states, we will get
  177. a vector of their images which can be collectively checked for
  178. validity by checking the dictionary keys/energy ranges, and
  179. then this is just the matrix form of the operator.
  180. """
  181. #psidaggerpsi = self.generateOperators()
  182. # can change this to enumerate if we want to specialize to diagonal
  183. # operators
  184. # the use of only the upper triangular matrix and then just transposing
  185. # is a great way to save time
  186. # maybe later should just enumerate all 16 ops by hand? Seems like
  187. # a pain though.
  188. allops = self.generateOperators3(nmax)
  189. potential = Matrix(lookupBasis,basis)
  190. bar = ChargingBar("States", max=basis.size)
  191. for state in basis:
  192. newcolumn = np.zeros(lookupBasis.size)
  193. for op in allops:
  194. try:
  195. normalization, index = op.apply2(basis,state)
  196. if index != None:
  197. newcolumn[index] += normalization
  198. except NotInBasis:
  199. pass
  200. '''
  201. for k in np.arange(-nmax,nmax+1):
  202. for op2 in psidaggerpsi[-k]:
  203. n, newstate= op2._transformState(state,returnCoeff=True)
  204. if n == 0:
  205. continue
  206. for op1 in psidaggerpsi[k]:
  207. try:
  208. normalization, index = op1.apply2(basis,newstate)
  209. if (index != None):
  210. #for ease of comparison we can put the 1/2L later
  211. newcolumn[index] += (normalization * n
  212. / (2*self.L*k**2))
  213. except NotInBasis:
  214. pass
  215. '''
  216. potential.addColumn(newcolumn)
  217. bar.next()
  218. bar.finish()
  219. potential.finalize()
  220. #print(potential.M.toarray())
  221. isSymmetric = (np.array_equal(potential.M.toarray(),
  222. potential.M.toarray().T))
  223. assert isSymmetric, "Matrix not symmetric (hermitian)"
  224. # if isSymmetric:
  225. # print("Matrix is symmetric")
  226. #
  227. # if np.any(np.diag(potential.M.toarray())):
  228. # print("nonzero diagonal entries")
  229. self.potential = potential
  230. #off diagonal h0 elements
  231. h0offdiag = Matrix(lookupBasis,basis)
  232. raising_op = ZeroModeRaisingOperator()
  233. lowering_op = ZeroModeLoweringOperator()
  234. for state in basis:
  235. newcolumn = np.zeros(lookupBasis.size)
  236. axialcharge = calculateAxialCharge(state)
  237. if axialcharge == 0:
  238. potential.addColumn(newcolumn)
  239. pass
  240. for op in [raising_op,lowering_op]:
  241. try:
  242. normalization, index = op.apply2(basis,state)
  243. if index != None:
  244. #this second factor is because h0 'counts' the number
  245. #of fermion insertions and adds or subtracts factors
  246. #of A1. possibly there's a minus sign here?
  247. newcolumn[index] += normalization * (-1*axialcharge)
  248. except NotInBasis:
  249. pass
  250. h0offdiag.addColumn(newcolumn)
  251. h0offdiag.finalize()
  252. #print(potential.M.toarray())
  253. isSymmetric = (np.array_equal(h0offdiag.M.toarray(),
  254. h0offdiag.M.toarray().T))
  255. assert isSymmetric, "Matrix not symmetric (hermitian)"
  256. # if isSymmetric:
  257. # print("Matrix is symmetric")
  258. #
  259. # if np.any(np.diag(potential.M.toarray())):
  260. # print("nonzero diagonal entries")
  261. #include normalization factors of 1/sqrt(2)*1/pi^1/4
  262. self.h0OffDiag = h0offdiag *(2**-0.5)*(pi**-0.25)
  263. # for each order (0,2,4) in phi
  264. """
  265. for n in offdiagOps.keys():
  266. offdiag_V = Matrix(lookupBasis, basis)
  267. diagonal = np.zeros(basis.size)
  268. # for each state in the basis
  269. for j in range(basis.size):
  270. newcolumn = np.zeros(lookupBasis.size)
  271. # for each off-diagonal operator at a given order
  272. for op in offdiagOps[n]:
  273. try:
  274. # apply this operator to find whether the
  275. # new state is still in the basis
  276. (x,i) = op.apply(basis,j,lookupBasis)
  277. # if so, add the corresponding value to the matrix
  278. # this is basically writing the effects of the
  279. # operator in the basis of the free states
  280. if(i != None):
  281. newcolumn[i]+=x
  282. except NotInBasis:
  283. pass
  284. offdiag_V.addColumn(newcolumn)
  285. # for each diagonal operator at the same order n
  286. for op in diagOps[n]:
  287. (x,i) = op.apply(basis,j,lookupBasis)
  288. # It should be j=i
  289. if i!= None:
  290. if i != j:
  291. raise RuntimeError('Non-diagonal operator')
  292. diagonal[i]+=x
  293. offdiag_V.finalize()
  294. diag_V = scipy.sparse.spdiags(diagonal,0,basis.size,basis.size)
  295. self.potential[n] = (offdiag_V+offdiag_V.transpose()+Matrix(lookupBasis, basis, diag_V)).to('coo')*self.L
  296. """
  297. # TO-DO: update for QED
  298. def saveMatrix(self, fname):
  299. """ Saves the free Hamiltonian and potential matrices to file """
  300. t = (fname, self.L, self.m, \
  301. self.fullBasis.Emax, self.fullBasis.nmax, self.fullBasis.bcs, \
  302. self.h0.M.data,self.h0.M.row,self.h0.M.col, \
  303. self.potential.M.data,self.potential.M.row,self.potential.M.col, \
  304. )
  305. scipy.savez(*t)
  306. def loadMatrix(self, fname):
  307. """ Loads the free Hamiltonian and potential matrices from file """
  308. f = scipy.load(fname)
  309. self.L = f['arr_0'].item()
  310. self.m = f['arr_1'].item()
  311. Emax = f['arr_2'].item()
  312. nmax = f['arr_3'].item()
  313. bcs = f['arr_4'].item()
  314. self.buildFullBasis(L=self.L, m=self.m, Emax=Emax, bcs=bcs)
  315. basisI = self.fullBasis
  316. basisJ = self.fullBasis
  317. print(basisI.size)
  318. self.h0 = Matrix(basisI, basisJ,
  319. scipy.sparse.coo_matrix((f['arr_5'], (f['arr_6'], f['arr_7'])),
  320. shape=(basisI.size, basisJ.size)))
  321. self.potential = Matrix(basisI, basisJ,
  322. scipy.sparse.coo_matrix((f['arr_8'], (f['arr_9'], f['arr_10'])),
  323. shape=(basisI.size, basisJ.size)))
  324. def generateOperators(self):
  325. """
  326. Generates a dictionary of normal-ordered operators corresponding
  327. to the operator (\psi^\dagger \psi)_k for all values of k within range.
  328. Returns
  329. -------
  330. opsList : dict of FermionOperators
  331. A dictionary whose keys are values of k, i.e. which Fourier mode
  332. of psi^\dagger \psi is under consideration, and whose entries are
  333. the corresponding set of operators.
  334. """
  335. opsList = {}
  336. # we will generate (psi^\dagger psi)_k
  337. for k in np.arange(-self.fullBasis.nmax,self.fullBasis.nmax+1):
  338. opsList[k] = []
  339. if k == 0:
  340. continue
  341. # note: what is the range on n? revisit. All valid ns such that
  342. # -nmax <= k+n <= nmax and -nmax <= n <= nmax
  343. for n in np.arange(-self.fullBasis.nmax,self.fullBasis.nmax+1):
  344. if not (-self.fullBasis.nmax <= k-n <= self.fullBasis.nmax):
  345. continue
  346. # zero mode spinor wavefunctions fixed, can remove this?
  347. # if k-n == 0 or n == 0:
  348. # continue
  349. coeff = np.vdot(uspinor(n-k,self.L,self.m,normed=True),
  350. uspinor(n,self.L,self.m,normed=True))
  351. adaggera = FermionOperator([n-k],[n],[],[],self.L,self.m,
  352. extracoeff=coeff/np.sqrt(self.L),
  353. normed=True)
  354. #n.b. -1 from anticommutation of bdagger and adagger
  355. coeff = -1 * np.vdot(uspinor(n-k,self.L,self.m,normed=True),
  356. vspinor(-n,self.L,self.m,normed=True))
  357. bdaggeradagger = FermionOperator([n-k],[],[-n],[],self.L,self.m,
  358. extracoeff=coeff/np.sqrt(self.L),
  359. normed=True)
  360. coeff = np.vdot(vspinor(k-n,self.L,self.m,normed=True),
  361. uspinor(n,self.L,self.m,normed=True))
  362. ba = FermionOperator([],[n],[],[k-n],self.L,self.m,
  363. extracoeff=coeff/np.sqrt(self.L),normed=True)
  364. # -1 from anticommuting b and b dagger
  365. coeff = -1 * np.vdot(vspinor(k-n,self.L,self.m,normed=True),
  366. vspinor(-n,self.L,self.m,normed=True))
  367. bdaggerb = FermionOperator([],[],[-n],[k-n],self.L,self.m,
  368. extracoeff=coeff/np.sqrt(self.L),normed=True)
  369. #the anticommutator is always trivial because k-n = -n -> k=0
  370. #and we've precluded this possiblity since k != 0
  371. opsList[k] += [adaggera, bdaggeradagger, ba, bdaggerb]
  372. return opsList
  373. def udotu(self,k1,k2):
  374. return np.vdot(uspinor(k1,self.L,self.m,normed=True),
  375. uspinor(k2,self.L,self.m,normed=True))
  376. def udotv(self,k1,k2):
  377. return np.vdot(uspinor(k1,self.L,self.m,normed=True),
  378. vspinor(k2,self.L,self.m,normed=True))
  379. def vdotu(self,k1,k2):
  380. return np.vdot(vspinor(k1,self.L,self.m,normed=True),
  381. uspinor(k2,self.L,self.m,normed=True))
  382. def vdotv(self,k1,k2):
  383. return np.vdot(vspinor(k1,self.L,self.m,normed=True),
  384. vspinor(k2,self.L,self.m,normed=True))
  385. def makeInteractionOps(self,clist,dlist,anticlist,antidlist,nmax,
  386. spinors,coeff=1.,deltacondition=None):
  387. """
  388. Takes a set of field labels for (anti)particle creation/annihilation
  389. operators and generates all corresponding strings of operators with
  390. momenta up to nmax, subject to (optional) delta function constraints.
  391. Parameters
  392. ----------
  393. clist : list of int
  394. labels for which fields the particle creation operators come from,
  395. e.g. [1,3] indicates that the first and third fields contributed
  396. a daggers.
  397. dlist : list of int
  398. labels for which fields the particle annihilation operators come from
  399. anticlist : list of int
  400. labels for which fields the antiparticle creation operators come from
  401. antidlist : list of int
  402. labels for which fields the antiparticle annihilation operators come from
  403. nmax : int
  404. maximum value of wavenumber in the truncation
  405. spinors : string
  406. A four-character string representing the contracted spinor
  407. wavefunctions to be computed. For example, udagger u vdagger v
  408. is just given as "uuvv".
  409. coeff : float, optional
  410. Extra coefficients for this operator. The default is 1.
  411. deltacondition : tuple, optional
  412. Any pairs of momenta that are to be set equal by Kronecker deltas,
  413. e.g. (1,2) is \delta_{k1,k2}. The default is None.
  414. Returns
  415. -------
  416. ops : list of FermionOperators
  417. All fermion operators matching the input parameters. The normalization
  418. is for 1/2k^2 * |\psi^\dagger \psi|^2, i.e. it contains the spinor
  419. inner products and also the 1/L factor. The g^2 then multiplies
  420. the matrix entries of V.
  421. """
  422. momenta = np.array([[k1,k2,k3,-k1-k2-k3]
  423. for k1 in np.arange(-nmax,nmax+1)
  424. for k2 in np.arange(-nmax,nmax+1)
  425. for k3 in np.arange(-nmax,nmax+1)
  426. if k1 + k2 != 0 and abs(k1+k2+k3) <= nmax
  427. ])
  428. # note: easily modified for multiple delta functions
  429. # just make it a list of tuples and iterate over the masking conditions
  430. # a minus sign is needed for the delta conditions with this convention
  431. # since we have a_k1 and a^\dagger_{-k2}
  432. if deltacondition:
  433. mask = momenta[:,deltacondition[0]-1] == -momenta[:,deltacondition[1]-1]
  434. momenta = momenta[mask]
  435. # for kvals in momenta:
  436. # assert(kvals[2]+kvals[3] != 0)
  437. # assert(kvals[0]+kvals[1] + kvals[2] + kvals[3] == 0)
  438. # return the right functions for the spinor inner products
  439. # with this convention, vs and udaggers get minus signs
  440. spinor_lookup = {"uu" : lambda k1,k2: self.udotu(-k1,k2),
  441. "uv" : lambda k1,k2: self.udotv(-k1,-k2),
  442. "vu" : lambda k1,k2: self.vdotu(k1,k2),
  443. "vv" : lambda k1,k2: self.vdotv(k1,-k2)
  444. }
  445. assert len(spinors) == 4
  446. firstspinor = spinor_lookup[spinors[0:2]]
  447. secondspinor = spinor_lookup[spinors[2:4]]
  448. ops = []
  449. for kvals in momenta:
  450. spinorfactor = (firstspinor(kvals[0],kvals[1])
  451. * secondspinor(kvals[2],kvals[3]))
  452. if spinorfactor == 0: continue
  453. # print(firstspinor(kvals[0],kvals[1]))
  454. # print(secondspinor(kvals[2],kvals[3]))
  455. # print(spinorfactor)
  456. # print(kvals)
  457. ksquared = 1/(kvals[0]+kvals[1])**2
  458. #creation operators get the minus sign on k
  459. ops += [FermionOperator(-kvals[np.array(clist,dtype=int)-1],
  460. kvals[np.array(dlist,dtype=int)-1],
  461. -kvals[np.array(anticlist,dtype=int)-1],
  462. kvals[np.array(antidlist,dtype=int)-1],
  463. self.L,self.m,
  464. extracoeff=coeff*spinorfactor*ksquared/(2*self.L),
  465. normed=True)]
  466. return ops
  467. def generateOperators2(self,nmax):
  468. """
  469. Parameters
  470. ----------
  471. nmax : TYPE
  472. DESCRIPTION.
  473. Returns
  474. -------
  475. None.
  476. """
  477. """
  478. operators are named by their non-normal ordered versions
  479. and how many basic operators are in the final version,
  480. e.g. this first operator is the normal ordered version of
  481. adagger a adagger a, which looks like adagger adagger a a
  482. and there is also a 2-operator term adagger a from the one anticommutation
  483. """
  484. ops = []
  485. adagger_a_adagger_a_4 = self.makeInteractionOps([1,3],[2,4],[],[],
  486. nmax=nmax,
  487. coeff=-1.,
  488. spinors="uuuu")
  489. ops += adagger_a_adagger_a_4
  490. adagger_a_adagger_a_2 = self.makeInteractionOps([1],[4],[],[],
  491. nmax=nmax,
  492. deltacondition=(2,3),
  493. spinors="uuuu")
  494. ops += adagger_a_adagger_a_2
  495. adagger_a_adagger_bdagger_4 = self.makeInteractionOps([1,3],[2],[4],[],
  496. nmax=nmax,
  497. spinors="uuuv")
  498. ops += adagger_a_adagger_bdagger_4
  499. adagger_a_adagger_bdagger_2 = self.makeInteractionOps([1],[],[4],[],
  500. nmax=nmax,
  501. coeff=-1.,
  502. deltacondition=(2,3),
  503. spinors="uuuv")
  504. ops += adagger_a_adagger_bdagger_2
  505. adagger_bdagger_adaggger_a_4 = self.makeInteractionOps([1,3],[4],[2],[],
  506. nmax=nmax,
  507. coeff=-1.,
  508. spinors="uvuu")
  509. ops += adagger_bdagger_adaggger_a_4
  510. adagger_a_b_bdagger_4 = self.makeInteractionOps([1],[2],[4],[3],
  511. nmax=nmax,
  512. spinors="uuvv")
  513. ops += adagger_a_b_bdagger_4
  514. '''
  515. adagger_a_b_bdagger_2 = self.makeInteractionOps([1],[2],[],[],
  516. nmax=nmax,
  517. deltacondition=(3,4),
  518. spinors="uuvv")
  519. ops += adagger_a_b_bdagger_2
  520. '''
  521. adagger_bdagger_b_bdagger_4 = self.makeInteractionOps([1],[],[2,4],[3],
  522. nmax=nmax,
  523. coeff=-1.,
  524. spinors="uvvv")
  525. ops += adagger_bdagger_b_bdagger_4
  526. '''
  527. adagger_bdagger_b_bdagger_2 = self.makeInteractionOps([1],[],[2],[],
  528. nmax=nmax,
  529. coeff=-1.,
  530. deltacondition=(3,4),
  531. spinors="uvvv")
  532. ops += adagger_bdagger_b_bdagger_2
  533. '''
  534. adagger_bdagger_adagger_bdagger_4 = self.makeInteractionOps([1,3],[],[2,4],[],
  535. nmax=nmax,
  536. coeff=-1.,
  537. spinors="uvuv")
  538. ops += adagger_bdagger_adagger_bdagger_4
  539. b_bdagger_adagger_bdagger_4 = self.makeInteractionOps([3],[],[2,4],[1],
  540. nmax=nmax,
  541. spinors="vvuv")
  542. ops += b_bdagger_adagger_bdagger_4
  543. b_bdagger_adagger_bdagger_2_1 = self.makeInteractionOps([3],[],[2],[],
  544. nmax=nmax,
  545. deltacondition=(1,4),
  546. spinors="vvuv")
  547. ops += b_bdagger_adagger_bdagger_2_1
  548. '''
  549. b_bdagger_adagger_bdagger_2_2 = self.makeInteractionOps([3],[],[4],[],
  550. nmax=nmax,
  551. coeff=-1.,
  552. deltacondition=(1,2),
  553. spinors="vvuv")
  554. ops += b_bdagger_adagger_bdagger_2_2
  555. '''
  556. adagger_bdagger_b_a_4 = self.makeInteractionOps([1],[4],[2],[3],
  557. nmax=nmax,
  558. coeff=-1.,
  559. spinors="uvvu")
  560. ops += adagger_bdagger_b_a_4
  561. b_a_adagger_bdagger_4 = self.makeInteractionOps([3],[2],[4],[1],
  562. nmax=nmax,
  563. coeff=-1.,
  564. spinors="vuuv")
  565. ops += b_a_adagger_bdagger_4
  566. b_a_adagger_bdagger_2_1 = self.makeInteractionOps([3],[2],[],[],
  567. nmax=nmax,
  568. coeff=-1.,
  569. deltacondition=(1,4),
  570. spinors="vuuv")
  571. ops += b_a_adagger_bdagger_2_1
  572. b_a_adagger_bdagger_2_2 = self.makeInteractionOps([],[],[4],[1],
  573. nmax=nmax,
  574. coeff=-1.,
  575. deltacondition=(2,3),
  576. spinors="vuuv")
  577. ops += b_a_adagger_bdagger_2_2
  578. # note: there is a total delta function term here but it just shifts
  579. # the vacuum energy so we omit it. It would be delta_14 delta_23 vuuv.
  580. b_bdagger_b_bdagger_4 = self.makeInteractionOps([],[],[2,4],[1,3],
  581. nmax=nmax,
  582. coeff=-1.,
  583. spinors="vvvv")
  584. ops += b_bdagger_b_bdagger_4
  585. b_bdagger_b_bdagger_2_1 = self.makeInteractionOps([],[],[2],[3],
  586. nmax=nmax,
  587. deltacondition=(1,4),
  588. spinors="vvvv")
  589. ops += b_bdagger_b_bdagger_2_1
  590. '''
  591. b_bdagger_b_bdagger_2_2 = self.makeInteractionOps([],[],[2],[1],
  592. nmax=nmax,
  593. coeff=-1.,
  594. deltacondition=(3,4),
  595. spinors="vvvv")
  596. ops += b_bdagger_b_bdagger_2_2
  597. b_bdagger_b_bdagger_2_3 = self.makeInteractionOps([],[],[4],[3],
  598. nmax=nmax,
  599. coeff=-1.,
  600. deltacondition=(1,2),
  601. spinors="vvvv")
  602. ops += b_bdagger_b_bdagger_2_3
  603. '''
  604. #next the hermitian conjugate terms
  605. #we can probably do this with the off diagonal trick later
  606. b_a_adagger_a_4 = self.makeInteractionOps([3],[2,4],[],[1],
  607. nmax=nmax,
  608. spinors="vuuu")
  609. ops += b_a_adagger_a_4
  610. b_a_adagger_a_2 = self.makeInteractionOps([],[4],[],[1],
  611. nmax=nmax,
  612. deltacondition=(2,3),
  613. spinors="vuuu")
  614. ops += b_a_adagger_a_2
  615. adagger_a_b_a_4 = self.makeInteractionOps([1],[2,4],[],[3],
  616. nmax=nmax,
  617. coeff=-1.,
  618. spinors="uuvu")
  619. ops += adagger_a_b_a_4
  620. b_bdagger_adagger_a_4 = self.makeInteractionOps([3],[4],[2],[1],
  621. nmax=nmax,
  622. spinors="vvuu")
  623. ops += b_bdagger_adagger_a_4
  624. '''
  625. b_bdagger_adagger_a_2 = self.makeInteractionOps([3],[4],[],[],
  626. nmax=nmax,
  627. deltacondition=(1,2),
  628. spinors="vvuu")
  629. ops += b_bdagger_adagger_a_2
  630. '''
  631. b_bdagger_b_a_4 = self.makeInteractionOps([],[4],[2],[1,3],
  632. nmax=nmax,
  633. coeff=-1.,
  634. spinors="vvvu")
  635. ops += b_bdagger_b_a_4
  636. '''
  637. b_bdagger_b_a_2 = self.makeInteractionOps([],[4],[],[3],
  638. nmax=nmax,
  639. deltacondition=(1,2),
  640. spinors="vvvu")
  641. ops += b_bdagger_b_a_2
  642. '''
  643. b_a_b_a_4 = self.makeInteractionOps([],[2,4],[],[1,3],
  644. nmax=nmax,
  645. coeff=-1.,
  646. spinors="vuvu")
  647. ops += b_a_b_a_4
  648. b_a_b_bdagger_4 = self.makeInteractionOps([],[2],[4],[1,3],
  649. nmax=nmax,
  650. spinors="vuvv")
  651. ops += b_a_b_bdagger_4
  652. '''
  653. b_a_b_bdagger_2_1 = self.makeInteractionOps([],[2],[],[1],
  654. nmax=nmax,
  655. deltacondition=(3,4),
  656. spinors="vuvv")
  657. ops += b_a_b_bdagger_2_1
  658. '''
  659. b_a_b_bdagger_2_2 = self.makeInteractionOps([],[2],[],[3],
  660. nmax=nmax,
  661. coeff=-1.,
  662. deltacondition=(1,4),
  663. spinors="vuvv")
  664. ops += b_a_b_bdagger_2_2
  665. return ops
  666. def generateOperators3(self,nmax):
  667. """
  668. Parameters
  669. ----------
  670. nmax : TYPE
  671. DESCRIPTION.
  672. Returns
  673. -------
  674. None.
  675. """
  676. """
  677. operators are named by their non-normal ordered versions
  678. and how many basic operators are in the final version,
  679. e.g. this first operator is the normal ordered version of
  680. adagger a adagger a, which looks like adagger adagger a a
  681. and there is also a 2-operator term adagger a from the one anticommutation
  682. """
  683. ops = []
  684. adagger_a_adagger_a_4 = self.makeInteractionOps([1,3],[2,4],[],[],
  685. nmax=nmax,
  686. coeff=-1.,
  687. spinors="uuuu")
  688. ops += adagger_a_adagger_a_4
  689. adagger_a_adagger_a_2 = self.makeInteractionOps([1],[4],[],[],
  690. nmax=nmax,
  691. deltacondition=(2,3),
  692. spinors="uuuu")
  693. ops += adagger_a_adagger_a_2
  694. adagger_a_adagger_bdagger_4 = self.makeInteractionOps([1,3],[2],[4],[],
  695. nmax=nmax,
  696. coeff=2.,
  697. spinors="uuuv")
  698. ops += adagger_a_adagger_bdagger_4
  699. adagger_a_adagger_bdagger_2 = self.makeInteractionOps([1],[],[4],[],
  700. nmax=nmax,
  701. coeff=-1.,
  702. deltacondition=(2,3),
  703. spinors="uuuv")
  704. ops += adagger_a_adagger_bdagger_2
  705. # adagger_bdagger_adaggger_a_4 = self.makeInteractionOps([1,3],[4],[2],[],
  706. # nmax=nmax,
  707. # coeff=-1.,
  708. # spinors="uvuu")
  709. # ops += adagger_bdagger_adaggger_a_4
  710. adagger_a_b_bdagger_4 = self.makeInteractionOps([1],[2],[4],[3],
  711. nmax=nmax,
  712. coeff=2.,
  713. spinors="uuvv")
  714. ops += adagger_a_b_bdagger_4
  715. adagger_bdagger_b_bdagger_4 = self.makeInteractionOps([1],[],[2,4],[3],
  716. nmax=nmax,
  717. coeff=-2.,
  718. spinors="uvvv")
  719. ops += adagger_bdagger_b_bdagger_4
  720. adagger_bdagger_adagger_bdagger_4 = self.makeInteractionOps([1,3],[],[2,4],[],
  721. nmax=nmax,
  722. coeff=-1.,
  723. spinors="uvuv")
  724. ops += adagger_bdagger_adagger_bdagger_4
  725. # b_bdagger_adagger_bdagger_4 = self.makeInteractionOps([3],[],[2,4],[1],
  726. # nmax=nmax,
  727. # spinors="vvuv")
  728. # ops += b_bdagger_adagger_bdagger_4
  729. b_bdagger_adagger_bdagger_2_1 = self.makeInteractionOps([3],[],[2],[],
  730. nmax=nmax,
  731. deltacondition=(1,4),
  732. spinors="vvuv")
  733. ops += b_bdagger_adagger_bdagger_2_1
  734. adagger_bdagger_b_a_4 = self.makeInteractionOps([1],[4],[2],[3],
  735. nmax=nmax,
  736. coeff=-2.,
  737. spinors="uvvu")
  738. ops += adagger_bdagger_b_a_4
  739. # b_a_adagger_bdagger_4 = self.makeInteractionOps([3],[2],[4],[1],
  740. # nmax=nmax,
  741. # coeff=-1.,
  742. # spinors="vuuv")
  743. # ops += b_a_adagger_bdagger_4
  744. b_a_adagger_bdagger_2_1 = self.makeInteractionOps([3],[2],[],[],
  745. nmax=nmax,
  746. coeff=-1.,
  747. deltacondition=(1,4),
  748. spinors="vuuv")
  749. ops += b_a_adagger_bdagger_2_1
  750. b_a_adagger_bdagger_2_2 = self.makeInteractionOps([],[],[4],[1],
  751. nmax=nmax,
  752. coeff=-1.,
  753. deltacondition=(2,3),
  754. spinors="vuuv")
  755. ops += b_a_adagger_bdagger_2_2
  756. # note: there is a total delta function term here but it just shifts
  757. # the vacuum energy so we omit it. It would be delta_14 delta_23 vuuv.
  758. b_bdagger_b_bdagger_4 = self.makeInteractionOps([],[],[2,4],[1,3],
  759. nmax=nmax,
  760. coeff=-1.,
  761. spinors="vvvv")
  762. ops += b_bdagger_b_bdagger_4
  763. b_bdagger_b_bdagger_2_1 = self.makeInteractionOps([],[],[2],[3],
  764. nmax=nmax,
  765. deltacondition=(1,4),
  766. spinors="vvvv")
  767. ops += b_bdagger_b_bdagger_2_1
  768. #next the hermitian conjugate terms
  769. #we can probably do this with the off diagonal trick later
  770. b_a_adagger_a_4 = self.makeInteractionOps([3],[2,4],[],[1],
  771. nmax=nmax,
  772. coeff=2.,
  773. spinors="vuuu")
  774. ops += b_a_adagger_a_4
  775. b_a_adagger_a_2 = self.makeInteractionOps([],[4],[],[1],
  776. nmax=nmax,
  777. deltacondition=(2,3),
  778. spinors="vuuu")
  779. ops += b_a_adagger_a_2
  780. # adagger_a_b_a_4 = self.makeInteractionOps([1],[2,4],[],[3],
  781. # nmax=nmax,
  782. # coeff=-1.,
  783. # spinors="uuvu")
  784. # ops += adagger_a_b_a_4
  785. # b_bdagger_adagger_a_4 = self.makeInteractionOps([3],[4],[2],[1],
  786. # nmax=nmax,
  787. # spinors="vvuu")
  788. # ops += b_bdagger_adagger_a_4
  789. b_bdagger_b_a_4 = self.makeInteractionOps([],[4],[2],[1,3],
  790. nmax=nmax,
  791. coeff=-2.,
  792. spinors="vvvu")
  793. ops += b_bdagger_b_a_4
  794. b_a_b_a_4 = self.makeInteractionOps([],[2,4],[],[1,3],
  795. nmax=nmax,
  796. coeff=-1.,
  797. spinors="vuvu")
  798. ops += b_a_b_a_4
  799. # b_a_b_bdagger_4 = self.makeInteractionOps([],[2],[4],[1,3],
  800. # nmax=nmax,
  801. # spinors="vuvv")
  802. # ops += b_a_b_bdagger_4
  803. b_a_b_bdagger_2_2 = self.makeInteractionOps([],[2],[],[3],
  804. nmax=nmax,
  805. coeff=-1.,
  806. deltacondition=(1,4),
  807. spinors="vuvv")
  808. ops += b_a_b_bdagger_2_2
  809. return ops
  810. def setcouplings(self, g):
  811. self.g = float(g)
  812. def renlocal(self,Er):
  813. self.g0r, self.g2r, self.g4r = renorm.renlocal(self.g2,self.g4,self.Emax,Er)
  814. self.Er = Er
  815. def computeHamiltonian(self, ren=False):
  816. """ Computes the (renormalized) Hamiltonian to diagonalize
  817. ren : if True, computes the eigenvalue with the "local" renormalization procedure, otherwise the "raw" eigenvalues
  818. """
  819. # note: V (if using generateOperators2) is |psi^\dagger psi|^2/2k^2.
  820. # The 1/L factor is accounted for.
  821. self.H = self.h0Sub - self.V*self.g**2 + self.h0OffDiagSub*self.g**0.5
  822. """
  823. if not(ren):
  824. self.H[k] = self.h0Sub[k] + self.V[k][2]*self.g2 + self.V[k][4]*self.g4
  825. else:
  826. self.H[k] = self.h0Sub[k] + self.V[k][0]*self.g0r + self.V[k][2]*self.g2r + self.V[k][4]*self.g4r
  827. """
  828. def computeEigval(self, ren=False, corr=False, sigma=0, n=10):
  829. """ Diagonalizes the Hamiltonian and possibly computes the subleading renormalization corrections
  830. ren (bool): it should have the same value as the one passed to computeHamiltonian()
  831. corr (bool): if True, computes the subleading renormalization corrections, otherwise not.
  832. n (int): number of lowest eigenvalues to compute
  833. sigma : value around which the Lanczos method looks for the lowest eigenvalue.
  834. """
  835. (self.eigenvalues, eigenvectorstranspose) = scipy.sparse.linalg.eigsh(
  836. self.H, k=n, sigma=sigma,
  837. which='LM', return_eigenvectors=True)
  838. """
  839. if not ren:
  840. (self.eigenvalues[k], eigenvectorstranspose) = scipy.sparse.linalg.eigsh(self.H[k], k=n, sigma=sigma,
  841. which='LM', return_eigenvectors=True)
  842. else:
  843. (self.eigsrenlocal[k], eigenvectorstranspose) = scipy.sparse.linalg.eigsh(self.H[k], k=n, sigma=sigma,
  844. which='LM', return_eigenvectors=True)
  845. """
  846. eigenvectors = eigenvectorstranspose.T
  847. # TO-DO: update this for QED
  848. if corr:
  849. print("Adding subleading corrections to k="+str(k), " eigenvalues")
  850. self.eigsrensubl[k] = np.zeros(n)
  851. cutoff = 5.
  852. for i in range(n):
  853. cbar = eigenvectors[i]
  854. if abs(sum([x*x for x in cbar])-1.0) > 10**(-13):
  855. raise RuntimeError('Eigenvector not normalized')
  856. Ebar = self.eigsrenlocal[k][i]
  857. self.eigsrensubl[k][i] += Ebar
  858. ktab, rentab = renorm.rensubl(self.g2, self.g4, Ebar, self.Emax, self.Er, cutoff=cutoff)
  859. tckren = { }
  860. tckren[0] = scipy.interpolate.interp1d(ktab,rentab.T[0],kind='linear')
  861. tckren[2] = scipy.interpolate.interp1d(ktab,rentab.T[1],kind='linear')
  862. tckren[4] = scipy.interpolate.interp1d(ktab,rentab.T[2],kind='linear')
  863. for nn in (0,2,4):
  864. #for a,b,Vab in itertools.izip(self.V[k][nn].row,self.V[k][nn].col,self.V[k][nn].data):
  865. for a,b,Vab in zip(self.V[k][nn].row,self.V[k][nn].col,self.V[k][nn].data):
  866. if a > b:
  867. continue
  868. elif a == b:
  869. c = 1
  870. else:
  871. c = 2
  872. Eab2= (self.basis[k][a].energy + self.basis[k][b].energy)/2.
  873. coeff = tckren[nn](Eab2)
  874. self.eigsrensubl[k][i] += c * coeff * cbar[a] * cbar[b] * Vab
  875. def vacuumE(self, ren="raw"):
  876. return self.eigenvalues[0]
  877. # implement others with corrections later
  878. """
  879. if ren=="raw":
  880. return self.eigenvalues[1][0]
  881. elif ren=="renlocal":
  882. return self.eigsrenlocal[1][0]
  883. elif ren=="rensubl":
  884. return self.eigsrensubl[1][0]
  885. else:
  886. raise ValueError("Wrong argument")
  887. # The vacuum is K-even
  888. """
  889. def spectrum(self, ren="raw"):
  890. if ren=="raw":
  891. eigs = self.eigenvalues
  892. elif ren=="renlocal":
  893. eigs = self.eigsrenlocal
  894. elif ren=="rensubl":
  895. eigs = self.eigsrensubl
  896. else:
  897. raise ValueError("Wrong argument")
  898. return scipy.array([x-self.vacuumE(ren=ren) for x in eigs[1:]])
  899. """
  900. # Now subtract vacuum energies
  901. if k==1:
  902. return scipy.array([x-self.vacuumE(ren=ren) for x in eigs[k][1:]])
  903. elif k==-1:
  904. return scipy.array([x-self.vacuumE(ren=ren) for x in eigs[k]])
  905. else:
  906. raise ValueError("Wrong argument")
  907. """