123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251 |
- ######################################################
- #
- # Fock space Hamiltonian truncation for phi^4 theory in 2 dimensions
- # Authors: Slava Rychkov (slava.rychkov@lpt.ens.fr) and Lorenzo Vitale (lorenzo.vitale@epfl.ch)
- # December 2014
- #
- ######################################################
- import scipy
- import numpy as np
- from numpy import pi, sqrt, product
- from operator import attrgetter
- from statefuncs import omega, State, NotInBasis
- from qedstatefuncs import FermionState
- from dressedstatefuncs import DressedFermionState
- tol = 0.0001
- def uspinor(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)]])
- 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)]])
- class FermionOperator():
- """ abstract class for normal ordered fermionic operator
- A normal-ordered operator is given by two lists of mode indices specifying
- which creation operators are present (clist) and which annihilation operators
- are present (dlist) and an overall multiplicative coefficient.
-
- For fermionic operators, we also have anticlist and antidlist for the
- antiparticles. To keep the sign conventions straight, we apply particle
- operators first and then antiparticle operators, but each of these is
- separately normal-ordered.
-
- Note also that the order matters in clist and dlist due to the signs
- associated with anticommutation. Operators are provided in the order one
- would write them (from left to right) and stored in reverse order
- (the order they act on states).
-
- Attributes:
- clist (list of ints): a list of ns (Fourier indices) corresponding
- to creation ops
- dlist (list of ints): another list of ns corresponding to
- destruction ops
- anticlist (list of ints)
- antidlist (list of ints)
- L (float): circumference of the circle (the spatial dimension)
- m (float): mass of the field
- coeff (float): the overall multiplicative prefactor of this operator
- deltaE (float): the net energy difference produced by acting on a state
- with this operator
- """
- 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])
- #can perform this as a vector op but the speedup is small
- #self.deltaE = sum(omega(array(clist),L,m))-sum(omega(array(dlist),L,m))
-
- def checkValidList(self, opsList):
- """
- Given a list of creation or annihilation operators, checks if
- the modes they act on are all unique.
- """
-
- return len(np.unique(opsList)) == len(opsList)
-
- def __repr__(self):
- return (str(self.clist) + " " + str(self.dlist) + " " + str(self.anticlist)
- + " " + str(self.antidlist) + " " + str(self.coeff))
-
- def _transformState(self, state0, returnCoeff=False, dressed=False):
- """
- Applies the normal ordered operator to a given state.
-
- Args:
- state0 (State): an input FermionState for this operator
- returncoeff (bool): boolean representing whether or not to
- include the factor self.coeff with the returned state
-
- Returns:
- A tuple representing the input state after being acted on by
- the normal-ordered operator and any multiplicative factors
- from performing the commutations.
-
- Example:
- For a state with nmax=1 and state0.occs = [0,0,2]
- (2 excitations in the n=+1 mode, since counting starts at
- -nmax) if the operator is a_{k=1}, corresponding to
- clist = []
- dlist = [1]
- coeff = 1
- then this will return a state with occs [0,0,1] and a prefactor of
- 2 (for the two commutations).
-
- """
- if not self.uniqueOps:
- return (0,None)
- #make a copy of this state up to occupation numbers and nmax
- #use DressedFermionState if the original state is dressed
- #otherwise use FermionState
- if state0.isDressed:
- state = DressedFermionState(particleOccs=state0.occs[:,0],
- antiparticleOccs=state0.occs[:,1],
- zeromode=state0.getAZeroMode(),
- nmax=state0.nmax,
- fast=True)
- else:
- state = FermionState(particleOccs=state0.occs[:,0],
- antiparticleOccs=state0.occs[:,1],
- nmax=state0.nmax,
- fast=True)
-
- # note: there may be an easier way for fermionic states
- # however, these loops are short and fast, so NumPy shortcuts probably
- # will not provide too much speed-up at this level.
-
- norm = 1
-
- for i in self.dlist:
- if state[i][0] == 0:
- return(0,None)
- state[i][0] -= 1
- # we have to anticommute past all the antiparticle creation ops
- # and the particle creation ops up to i
- norm *= (-1)**(np.sum(state.occs[:,1])+
- np.sum(state.occs[:int(i-state.nmin),0]))
-
- for i in self.antidlist:
- if state[i][1] == 0:
- return(0,None)
- state[i][1] -= 1
- # anticommute past the antiparticle creation ops up to i
- norm *= (-1)**(np.sum(state.occs[:int(i-state.nmin),1]))
-
- for i in self.clist:
- # by Pauli exclusion, states can have at most one excitation
- # in a mode
- if state[i][0] == 1:
- return (0,None)
- state[i][0] += 1
- # anticommute past all the antiparticle creation ops and the
- # particle creation ops through i
- norm *= (-1)**(np.sum(state.occs[:,1])+
- np.sum(state.occs[:int(i-state.nmin),0]))
-
-
- for i in self.anticlist:
- if state[i][1] == 1:
- return (0,None)
- state[i][1] += 1
- # anticommute past the antiparticle creation ops
- norm *= (-1)**(np.sum(state.occs[:int(i-state.nmin),1]))
-
- # We never pick up a nontrivial normalization factor for fermionic
- # states since the occupation numbers are either one or zero.
- # The only option is if we wish to return the overall coefficient
- # of this operator or not.
- if returnCoeff:
- return (norm*self.coeff, state)
- return (norm, state)
- def apply(self, basis, i, lookupbasis=None):
- """ Takes a state index in basis, returns another state index (if it
- belongs to the lookupbasis) and a proportionality coefficient. Otherwise raises NotInBasis.
- lookupbasis can be different from basis, but it's assumed that they have the same nmax"""
- if lookupbasis == None:
- lookupbasis = basis
- if self.deltaE+basis[i].energy < 0.-tol or self.deltaE+basis[i].energy > lookupbasis.Emax+tol:
- # The transformed element surely does not belong to the basis if E>Emax or E<0
- raise NotInBasis()
- # apply the normal-order operator to this basis state
- n, newstate = self._transformState(basis[i])
- #if the state was annihilated by the operator, return (0, None)
- if n==0:
- return (0, None)
- # otherwise, look up this state in the lookup basis
- norm, j = lookupbasis.lookup(newstate)
- c = 1.
- #if basis[i].isParityEigenstate():
- # c = 1/sqrt(2.)
- # Required for state normalization
- # return the overall proportionality and the state index
- return (norm*c*sqrt(n)*self.coeff, j)
-
- def apply2(self, basis, state, lookupbasis=None):
- """
- Like apply but with a state as input rather than an index in the
- original basis.
- """
- # TO-DO: add the energy shortcut from the original apply
- # we need the energy of the state-- is it expensive to recompute it?
- if lookupbasis == None:
- lookupbasis = basis
-
- n, newstate = self._transformState(state)
-
- if n==0:
- return (0, None)
-
- norm, j = lookupbasis.lookup(newstate)
-
- c = 1.
- #no sqrt n since n is either 1 or -1
- return (norm*c*n*self.coeff,j)
|