""" Second quantization operators and states for bosons. This follow the formulation of Fetter and Welecka, "Quantum Theory of Many-Particle Systems." """ from sympy import ( Basic, Function, var, Mul, sympify, Integer, Add, sqrt, Number, Matrix, zeros, Pow, I ) from sympy.utilities.decorator import deprecated __all__ = [ 'Dagger', 'KroneckerDelta', 'BosonicOperator', 'AnnihilateBoson', 'CreateBoson', 'FockState', 'FockStateBra', 'FockStateKet', 'Bra', 'Ket', 'B', 'Bd', 'apply_operators', 'InnerProduct', 'BosonicBasis', 'VarBosonicBasis', 'FixedBosonicBasis', 'commutator', 'matrix_rep' ] class Dagger(Basic): """ Hermitian conjugate of creation/annihilation operators. """ def __new__(cls, arg): arg = sympify(arg) r = cls.eval(arg) if isinstance(r, Basic): return r obj = Basic.__new__(cls, arg) return obj @classmethod @deprecated def canonize(cls, arg): return cls.eval(arg) @classmethod def eval(cls, arg): try: d = arg._dagger_() except: if isinstance(arg, Basic): if arg.is_Add: return Add(*tuple(map(Dagger, arg.args))) if arg.is_Mul: return Mul(*tuple(map(Dagger, reversed(arg.args)))) if arg.is_Number: return arg if arg.is_Pow: return Pow(Dagger(arg.args[0]),arg.args[1]) if arg == I: return -arg else: return None else: return d def _eval_subs(self, old, new): r = Dagger(self.args[0].subs(old, new)) return r def _dagger_(self): return self.args[0] class KroneckerDelta(Basic): """ Discrete delta function. """ def __new__(cls, i, j): i, j = map(sympify, (i, j)) r = cls.eval(i, j) if isinstance(r, Basic): return r obj = Basic.__new__(cls, i, j, commutative=True) return obj @classmethod @deprecated def canonize(cls, i, j): return cls.eval(i, j) @classmethod def eval(cls, i, j): diff = i-j if diff == 0: return Integer(1) elif diff.is_number: return Integer(0) def _eval_subs(self, old, new): r = KroneckerDelta(self.args[0].subs(old, new), self.args[1].subs(old, new)) return r def _dagger_(): return self class BosonicOperator(Basic): """ Base class for bosonic operators. """ op_symbol = 'bo' def __new__(cls, k): obj = Basic.__new__(cls, sympify(k), commutative=False) return obj def _eval_subs(self, old, new): r = self.__class__(self.args[0].subs(old, new)) return r @property def state(self): return self.args[0] @property def is_symbolic(self): if self.state.is_Integer: return False else: return True def __repr__(self): return "%s(%r)" % (self.op_symbol, self.state) def __str__(self): return self.__repr__() def apply_operator(self, state): raise NotImplementedError('implement apply_operator in a subclass') class AnnihilateBoson(BosonicOperator): """ Bosonic annihilation operator """ op_symbol = 'b' def _dagger_(self): return CreateBoson(self.state) def apply_operator(self, state): if not self.is_symbolic and isinstance(state, FockStateKet): element = self.state amp = sqrt(state[element]) return amp*state.down(element) else: return Mul(self,state) class CreateBoson(BosonicOperator): """ Bosonic creation operator """ op_symbol = 'b+' def _dagger_(self): return AnnihilateBoson(self.state) def apply_operator(self, state): if not self.is_symbolic and isinstance(state, FockStateKet): element = self.state amp = sqrt(state[element] + 1) return amp*state.up(element) else: return Mul(self,state) B = AnnihilateBoson Bd = CreateBoson class FockState(Basic): """ Many particle Fock state with a sequence of occupation numbers. Anywhere you can have a FockState, you can also have Integer(0). All code must check for this! """ def __new__(cls, occupations): o = map(sympify, occupations) obj = Basic.__new__(cls, tuple(o), commutative=False) return obj def _eval_subs(self, old, new): r = self.__class__([o.subs(old, new) for o in self.args[0]]) return r def up(self, i): i = int(i) new_occs = list(self.args[0]) new_occs[i] = new_occs[i]+Integer(1) return self.__class__(new_occs) def down(self, i): i = int(i) new_occs = list(self.args[0]) if new_occs[i]==Integer(0): return Integer(0) else: new_occs[i] = new_occs[i]-Integer(1) return self.__class__(new_occs) def __getitem__(self, i): i = int(i) return self.args[0][i] def __repr__(self): return ("FockState(%r)") % (self.args) def __str__(self): return self.__repr__() def __len__(self): return len(self.args[0]) class FockStateKet(FockState): def _dagger_(self): return FockStateBra(*self.args) def __repr__(self): return ("|%r>") % (self.args) class FockStateBra(FockState): def _dagger_(self): return FockStateKet(*self.args) def __repr__(self): return ("<%r|") % (self.args) def __mul__(self, other): if isinstance(other, FockStateKet): return InnerProduct(self, other) else: return Basic.__mul__(self, other) Bra = FockStateBra Ket = FockStateKet def split_commutative_parts(m): c_part = [p for p in m.args if p.is_commutative] nc_part = [p for p in m.args if not p.is_commutative] return c_part, nc_part def apply_Mul(m): """ Take a Mul instance with operators and apply them to states. This method applies all operators with integer state labels to the actual states. For symbolic state labels, nothing is done. When inner products of FockStates are encountered (like ), the are converted to instances of InnerProduct. This does not currently work on double inner products like, . If the argument is not a Mul, it is simply returned as is. """ if not isinstance(m, Mul): return m c_part, nc_part = split_commutative_parts(m) n_nc = len(nc_part) if n_nc == 0 or n_nc == 1: return m else: last = nc_part[-1] next_to_last = nc_part[-2] if isinstance(last, FockStateKet): if isinstance(next_to_last, BosonicOperator): if next_to_last.is_symbolic: return m else: result = next_to_last.apply_operator(last) if result == 0: return 0 else: return apply_Mul(Mul(*(c_part+nc_part[:-2]+[result]))) elif isinstance(next_to_last, Pow): if isinstance(next_to_last.base, BosonicOperator) and \ next_to_last.exp.is_Integer: if next_to_last.base.is_symbolic: return m else: result = last for i in range(next_to_last.exp): result = next_to_last.base.apply_operator(result) if result == 0: break if result == 0: return 0 else: return apply_Mul(Mul(*(c_part+nc_part[:-2]+[result]))) else: return m elif isinstance(next_to_last, FockStateBra): result = InnerProduct(next_to_last, last) if result == 0: return 0 else: return apply_Mul(Mul(*(c_part+nc_part[:-2]+[result]))) else: return m else: return m def apply_operators(e): """ Take a sympy expression with operators and states and apply the operators. """ e = e.expand() muls = e.atoms(Mul) subs_list = [(m,apply_Mul(m)) for m in iter(muls)] return e.subs(subs_list) class InnerProduct(Basic): """ An unevaluated inner product between a bra and ket. Currently this class just reduces things to a prouct of KroneckerDeltas. In the future, we could introduce abstract states like |a> and |b>, and leave the inner product unevaluated as . """ def __new__(cls, bra, ket): assert isinstance(bra, FockStateBra), 'must be a bra' assert isinstance(ket, FockStateKet), 'must be a key' r = cls.eval(bra, ket) if isinstance(r, Basic): return r obj = Basic.__new__(cls, *(bra, ket), **dict(commutative=True)) return obj @classmethod @deprecated def canonize(cls, bra, ket): return cls.eval(bra, ket) @classmethod def eval(cls, bra, ket): result = Integer(1) for i,j in zip(bra.args[0], ket.args[0]): result *= KroneckerDelta(i,j) if result == 0: break return result @property def bra(self): return self.args[0] @property def ket(self): return self.args[1] def _eval_subs(self, old, new): r = self.__class__(self.bra.subs(old,new), self.ket.subs(old,new)) return r def __repr__(self): sbra = repr(self.bra) sket = repr(self.ket) return "%s|%s" % (sbra[:-1], sket[1:]) def __str__(self): return self.__repr__() def matrix_rep(op, basis): """ Find the representation of an operator in a basis. """ a = zeros((len(basis), len(basis))) for i in range(len(basis)): for j in range(len(basis)): a[i,j] = apply_operators(Dagger(basis[i])*op*basis[j]) return a class BosonicBasis(object): """ Base class for a basis set of bosonic Fock states. """ pass class VarBosonicBasis(object): """ A single state, variable particle number basis set. """ def __init__(self, n_max): self.n_max = n_max self._build_states() def _build_states(self): self.basis = [] for i in range(self.n_max): self.basis.append(FockStateKet([i])) self.n_basis = len(self.basis) def index(self, state): return self.basis.index(state) def state(self, i): return self.basis[i] def __getitem__(self, i): return self.state(i) def __len__(self): return len(self.basis) def __repr__(self): return repr(self.basis) class FixedBosonicBasis(BosonicBasis): """ Fixed particle number basis set. """ def __init__(self, n_particles, n_levels): self.n_particles = n_particles self.n_levels = n_levels self._build_particle_locations() self._build_states() def _build_particle_locations(self): tup = ["i"+str(i) for i in range(self.n_particles)] first_loop = "for i0 in range(%i)" % self.n_levels other_loops = '' for i in range(len(tup)-1): temp = "for %s in range(%s + 1) " % (tup[i+1],tup[i]) other_loops = other_loops + temp var = "(" for i in tup[:-1]: var = var + i + "," var = var + tup[-1] + ")" cmd = "result = [%s %s %s]" % (var, first_loop, other_loops) exec cmd if self.n_particles==1: result = [(item,) for item in result] self.particle_locations = result def _build_states(self): self.basis = [] for tuple_of_indices in self.particle_locations: occ_numbers = self.n_levels*[0] for level in tuple_of_indices: occ_numbers[level] += 1 self.basis.append(FockStateKet(occ_numbers)) self.n_basis = len(self.basis) def index(self, state): return self.basis.index(state) def state(self, i): return self.basis[i] def __getitem__(self, i): return self.state(i) def __len__(self): return len(self.basis) def __repr__(self): return repr(self.basis) # def move(e, i, d): # """ # Takes the expression "e" and moves the operator at the position i by "d". # """ # if e.is_Mul: # if d == 1: # # e = a*b*c*d # a = Mul(*e.args[:i]) # b = e.args[i] # c = e.args[i+1] # d = Mul(*e.args[i+2:]) # if isinstance(b, Dagger) and not isinstance(c, Dagger): # i, j = b.args[0].args[0], c.args[0] # return a*c*b*d-a*KroneckerDelta(i, j)*d # elif not isinstance(b, Dagger) and isinstance(c, Dagger): # i, j = b.args[0], c.args[0].args[0] # return a*c*b*d-a*KroneckerDelta(i, j)*d # else: # return a*c*b*d # elif d == -1: # # e = a*b*c*d # a = Mul(*e.args[:i-1]) # b = e.args[i-1] # c = e.args[i] # d = Mul(*e.args[i+1:]) # if isinstance(b, Dagger) and not isinstance(c, Dagger): # i, j = b.args[0].args[0], c.args[0] # return a*c*b*d-a*KroneckerDelta(i, j)*d # elif not isinstance(b, Dagger) and isinstance(c, Dagger): # i, j = b.args[0], c.args[0].args[0] # return a*c*b*d-a*KroneckerDelta(i, j)*d # else: # return a*c*b*d # else: # if d > 1: # while d >= 1: # e = move(e, i, 1) # d -= 1 # i += 1 # return e # elif d < -1: # while d <= -1: # e = move(e, i, -1) # d += 1 # i -= 1 # return e # elif isinstance(e, Add): # a, b = e.as_two_terms() # return move(a, i, d) + move(b, i, d) # raise NotImplementedError() def commutator(a, b): """ Return the commutator: [a, b] = a*b - b*a """ return a*b - b*a