# Copyright 2024 Xanadu Quantum Technologies Inc.# Licensed under the Apache License, Version 2.0 (the "License");# you may not use this file except in compliance with the License.# You may obtain a copy of the License at# http://www.apache.org/licenses/LICENSE-2.0# Unless required by applicable law or agreed to in writing, software# distributed under the License is distributed on an "AS IS" BASIS,# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.# See the License for the specific language governing permissions and# limitations under the License."""The bosonic representation classes and functions."""fromcopyimportcopyimportpennylaneasqmlfrompennylane.typingimportTensorLike# pylint: disable= too-many-nested-blocks, too-many-branches, invalid-name
[docs]classBoseWord(dict):r"""Dictionary used to represent a Bose word, a product of bosonic creation and annihilation operators, that can be constructed from a standard dictionary. The keys of the dictionary are tuples of two integers. The first integer represents the position of the creation/annihilation operator in the Bose word and the second integer represents the mode it acts on. The values of the dictionary are one of ``'+'`` or ``'-'`` symbols that denote creation and annihilation operators, respectively. The operator :math:`b^{\dagger}_0 b_1` can then be constructed as >>> w = qml.BoseWord({(0, 0) : '+', (1, 1) : '-'}) >>> print(w) b⁺(0) b(1) """# override the arithmetic dunder methods for numpy arrays so that the# methods defined on this class are used instead# (i.e. ensure `np.array + BoseWord` uses `BoseWord.__radd__` instead of `np.array.__add__`)__numpy_ufunc__=None__array_ufunc__=Nonedef__init__(self,operator):self.sorted_dic=dict(sorted(operator.items()))indices=[i[0]foriinself.sorted_dic.keys()]ifindices:iflist(range(max(indices)+1))!=indices:raiseValueError("The operator indices must belong to the set {0, ..., len(operator)-1}.")super().__init__(operator)
[docs]defadjoint(self):r"""Return the adjoint of BoseWord."""n=len(self.items())adjoint_dict={}forkey,valueinreversed(self.items()):position=n-key[0]-1orbital=key[1]bose="+"ifvalue=="-"else"-"adjoint_dict[(position,orbital)]=bosereturnBoseWord(adjoint_dict)
[docs]defitems(self):"""Returns the dictionary items in sorted order."""returnself.sorted_dic.items()
@propertydefwires(self):r"""Return wires in a BoseWord."""returnset(i[1]foriinself.sorted_dic.keys())def__missing__(self,key):r"""Return empty string for a missing key in BoseWord."""return""
[docs]defupdate(self,item):r"""Restrict updating BoseWord after instantiation."""raiseTypeError("BoseWord object does not support assignment")
def__setitem__(self,key,item):r"""Restrict setting items after instantiation."""raiseTypeError("BoseWord object does not support assignment")def__reduce__(self):r"""Defines how to pickle and unpickle a BoseWord. Otherwise, un-pickling would cause __setitem__ to be called, which is forbidden on PauliWord. For more information, see: https://docs.python.org/3/library/pickle.html#object.__reduce__ """returnBoseWord,(dict(self),)def__copy__(self):r"""Copy the BoseWord instance."""returnBoseWord(dict(self.items()))def__deepcopy__(self,memo):r"""Deep copy the BoseWord instance."""res=self.__copy__()memo[id(self)]=resreturnresdef__hash__(self):r"""Hash value of a BoseWord."""returnhash(frozenset(self.items()))
[docs]defto_string(self):r"""Return a compact string representation of a BoseWord. Each operator in the word is represented by the number of the wire it operates on, and a `+` or `-` to indicate either a creation or annihilation operator. >>> w = qml.BoseWord({(0, 0) : '+', (1, 1) : '-'}) >>> w.to_string() 'b⁺(0) b(1)' """iflen(self)==0:return"I"symbol_map={"+":"\u207a","-":""}string=" ".join(["b"+symbol_map[j]+"("+i+")"fori,jinzip([str(i[1])foriinself.sorted_dic.keys()],self.sorted_dic.values())])returnstring
def__str__(self):r"""String representation of a BoseWord."""returnf"{self.to_string()}"def__repr__(self):r"""Terminal representation of a BoseWord"""returnf"BoseWord({self.sorted_dic})"def__add__(self,other):"""Add a BoseSentence, BoseWord or constant to a BoseWord. Converts both elements into BoseSentences, and uses the BoseSentence __add__ method"""self_bs=BoseSentence({self:1.0})ifisinstance(other,BoseSentence):returnself_bs+otherifisinstance(other,BoseWord):returnself_bs+BoseSentence({other:1.0})ifnotisinstance(other,TensorLike):raiseTypeError(f"Cannot add {type(other)} to a BoseWord.")ifqml.math.size(other)>1:raiseValueError(f"Arithmetic Bose operations can only accept an array of length 1, "f"but received {other} of length {len(other)}")returnself_bs+BoseSentence({BoseWord({}):other})def__radd__(self,other):"""Add a BoseWord to a constant, i.e. `2 + BoseWord({...})`"""returnself.__add__(other)def__sub__(self,other):"""Subtract a BoseSentence, BoseWord or constant from a BoseWord. Converts both elements into BoseSentences (with negative coefficient for `other`), and uses the BoseSentence __add__ method"""self_bs=BoseSentence({self:1.0})ifisinstance(other,BoseWord):returnself_bs+BoseSentence({other:-1.0})ifisinstance(other,BoseSentence):other_bs=BoseSentence(dict(zip(other.keys(),[-vforvinother.values()])))returnself_bs+other_bsifnotisinstance(other,TensorLike):raiseTypeError(f"Cannot subtract {type(other)} from a BoseWord.")ifqml.math.size(other)>1:raiseValueError(f"Arithmetic Bose operations can only accept an array of length 1, "f"but received {other} of length {len(other)}")returnself_bs+BoseSentence({BoseWord({}):-1*other})# -constant * Idef__rsub__(self,other):"""Subtract a BoseWord to a constant, i.e. `2 - BoseWord({...})`"""ifnotisinstance(other,TensorLike):raiseTypeError(f"Cannot subtract a BoseWord from {type(other)}.")ifqml.math.size(other)>1:raiseValueError(f"Arithmetic Bose operations can only accept an array of length 1, "f"but received {other} of length {len(other)}")self_bs=BoseSentence({self:-1.0})other_bs=BoseSentence({BoseWord({}):other})returnself_bs+other_bsdef__mul__(self,other):r"""Multiply a BoseWord with another BoseWord, a BoseSentence, or a constant. >>> w = qml.BoseWord({(0, 0) : '+', (1, 1) : '-'}) >>> print(w * w) b⁺(0) b(1) b⁺(0) b(1) """ifisinstance(other,BoseWord):iflen(self)==0:returncopy(other)iflen(other)==0:returncopy(self)order_final=[i[0]+len(self)foriinother.sorted_dic.keys()]other_wires=[i[1]foriinother.sorted_dic.keys()]dict_other=dict(zip([(order_idx,other_wires[i])fori,order_idxinenumerate(order_final)],other.values(),))dict_self=dict(zip(self.keys(),self.values()))dict_self.update(dict_other)returnBoseWord(dict_self)ifisinstance(other,BoseSentence):returnBoseSentence({self:1})*otherifnotisinstance(other,TensorLike):raiseTypeError(f"Cannot multiply BoseWord by {type(other)}.")ifqml.math.size(other)>1:raiseValueError(f"Arithmetic Bose operations can only accept an array of length 1, "f"but received {other} of length {len(other)}")returnBoseSentence({self:other})def__rmul__(self,other):r"""Reverse multiply a BoseWord Multiplies a BoseWord "from the left" with an object that can't be modified to support __mul__ for BoseWord. Will be defaulted in for example ``2 * BoseWord({(0, 0): "+"})``, where the ``__mul__`` operator on an integer will fail to multiply with a BoseWord"""returnself.__mul__(other)def__pow__(self,value):r"""Exponentiate a Bose word to an integer power. >>> w = qml.BoseWord({(0, 0) : '+', (1, 1) : '-'}) >>> print(w**3) b⁺(0) b(1) b⁺(0) b(1) b⁺(0) b(1) """ifvalue<0ornotisinstance(value,int):raiseValueError("The exponent must be a positive integer.")operator=BoseWord({})for_inrange(value):operator*=selfreturnoperator
[docs]defnormal_order(self):r"""Convert a BoseWord to its normal-ordered form. >>> bw = qml.BoseWord({(0, 0): "-", (1, 0): "-", (2, 0): "+", (3, 0): "+"}) >>> print(bw.normal_order()) 4.0 * b⁺(0) b(0) + 2.0 * I + 1.0 * b⁺(0) b⁺(0) b(0) b(0) """bw_terms=sorted(self)len_op=len(bw_terms)bw_comm=BoseSentence({BoseWord({}):0.0})iflen_op==0:return1*BoseWord({})bw=selfleft_pointer=0# The right pointer iterates through all operators in the BoseWordforright_pointerinrange(len_op):# The right pointer finds the leftmost creation operatorifself[bw_terms[right_pointer]]=="+":# This ensures that the left pointer starts at the leftmost annihilation termifleft_pointer==right_pointer:left_pointer+=1continue# We shift the leftmost creation operator to the position of the left pointerbs=bw.shift_operator(right_pointer,left_pointer)bs_as_list=sorted(list(bs.items()),key=lambdax:len(x[0].keys()),reverse=True)bw=bs_as_list[0][0]foriinrange(1,len(bs_as_list)):bw_comm+=bs_as_list[i][0]*bs_as_list[i][1]# Left pointer now points to the new leftmost annihilation termleft_pointer+=1# Sort BoseWord by indiceplus_terms=list(bw.items())[:left_pointer]minus_terms=list(bw.items())[left_pointer:]sorted_plus_terms=dict(sorted(plus_terms,key=lambdax:(x[0][1],x[0][0])))sorted_minus_terms=dict(sorted(minus_terms,key=lambdax:(x[0][1],x[0][0])))sorted_dict={**sorted_plus_terms,**sorted_minus_terms}bw_sorted_by_index={}fori,(k,v)inenumerate(sorted_dict.items()):bw_sorted_by_index[(i,k[1])]=vordered_op=BoseWord(bw_sorted_by_index)+bw_comm.normal_order()ordered_op.simplify(tol=1e-8)returnordered_op
[docs]defshift_operator(self,initial_position,final_position):r"""Shifts an operator in the BoseWord from ``initial_position`` to ``final_position`` by applying the bosonic commutation relations. Args: initial_position (int): the position of the operator to be shifted final_position (int): the desired position of the operator Returns: BoseSentence: The ``BoseSentence`` obtained after applying the commutator relations. Raises: TypeError: if ``initial_position`` or ``final_position`` is not an integer ValueError: if ``initial_position`` or ``final_position`` are outside the range ``[0, len(BoseWord) - 1]`` where ``len(BoseWord)`` is the number of operators in the BoseWord. """ifnotisinstance(initial_position,int)ornotisinstance(final_position,int):raiseTypeError("Positions must be integers.")ifinitial_position<0orfinal_position<0:raiseValueError("Positions must be positive integers.")ifinitial_position>len(self.sorted_dic)-1orfinal_position>len(self.sorted_dic)-1:raiseValueError("Positions are out of range.")ifinitial_position==final_position:returnBoseSentence({self:1})bw=selfbs=BoseSentence({bw:1})delta=1ifinitial_position<final_positionelse-1current=initial_positionwhilecurrent!=final_position:indices=list(bw.sorted_dic.keys())next=current+deltacurr_idx,curr_val=indices[current],bw[indices[current]]next_idx,next_val=indices[next],bw[indices[next]]# commuting identical termsifcurr_idx[1]==next_idx[1]andcurr_val==next_val:current+=deltacontinuecoeff=bs.pop(bw)bw=dict(bw)bw[(current,next_idx[1])]=next_valbw[(next,curr_idx[1])]=curr_valifcurr_idx[1]!=next_idx[1]:delbw[curr_idx],bw[next_idx]bw=BoseWord(bw)# commutator is 0ifcurr_val==next_valorcurr_idx[1]!=next_idx[1]:current+=deltabs+=coeff*bwcontinue# commutator is 1_min=min(current,next)_max=max(current,next)items=list(bw.sorted_dic.items())left=BoseWord({(i,key[1]):valuefori,(key,value)inenumerate(items[:_min])})middle=BoseWord({(i,key[1]):valuefori,(key,value)inenumerate(items[_min:_max+1])})right=BoseWord({(i,key[1]):valuefori,(key,value)inenumerate(items[_max+1:])})terms=left*(1+middle)*rightbs+=coeff*termscurrent+=deltareturnbs
# pylint: disable=useless-super-delegation
[docs]classBoseSentence(dict):r"""Dictionary used to represent a Bose sentence, a linear combination of Bose words, with the keys as BoseWord instances and the values correspond to coefficients. >>> w1 = qml.BoseWord({(0, 0) : '+', (1, 1) : '-'}) >>> w2 = qml.BoseWord({(0, 1) : '+', (1, 2) : '-'}) >>> s = qml.BoseSentence({w1 : 1.2, w2: 3.1}) >>> print(s) 1.2 * b⁺(0) b(1) + 3.1 * b⁺(1) b(2) """# override the arithmetic dunder methods for numpy arrays so that the# methods defined on this class are used instead# (i.e. ensure `np.array + BoseSentence` uses `BoseSentence.__radd__`# instead of `np.array.__add__`)__numpy_ufunc__=None__array_ufunc__=Nonedef__init__(self,operator):super().__init__(operator)
[docs]defadjoint(self):r"""Return the adjoint of BoseSentence."""adjoint_dict={}forkey,valueinself.items():word=key.adjoint()scalar=qml.math.conj(value)adjoint_dict[word]=scalarreturnBoseSentence(adjoint_dict)
@propertydefwires(self):r"""Return wires of the BoseSentence."""returnset().union(*(bw.wiresforbwinself.keys()))def__str__(self):r"""String representation of a BoseSentence."""iflen(self)==0:return"0 * I"return"\n+ ".join(f"{coeff} * {bw.to_string()}"forbw,coeffinself.items())def__repr__(self):r"""Terminal representation for BoseSentence."""returnf"BoseSentence({dict(self)})"def__missing__(self,key):r"""If the BoseSentence does not contain a BoseWord then the associated value will be 0."""return0.0def__add__(self,other):r"""Add a BoseSentence, BoseWord or constant to a BoseSentence by iterating over the smaller one and adding its terms to the larger one."""ifnotisinstance(other,(TensorLike,BoseWord,BoseSentence)):raiseTypeError(f"Cannot add {type(other)} to a BoseSentence.")ifqml.math.size(other)>1:raiseValueError(f"Arithmetic Bose operations can only accept an array of length 1, "f"but received {other} of length {len(other)}")ifisinstance(other,BoseWord):other=BoseSentence({other:1})ifisinstance(other,TensorLike):other=BoseSentence({BoseWord({}):other})smaller_bs,larger_bs=((self,copy(other))iflen(self)<len(other)else(other,copy(self)))forkeyinsmaller_bs:larger_bs[key]+=smaller_bs[key]returnlarger_bsdef__radd__(self,other):"""Add a BoseSentence to a constant, i.e. `2 + BoseSentence({...})`"""returnself.__add__(other)def__sub__(self,other):r"""Subtract a BoseSentence, BoseWord or constant from a BoseSentence"""ifisinstance(other,BoseWord):other=BoseSentence({other:-1})returnself.__add__(other)ifisinstance(other,BoseSentence):other=BoseSentence(dict(zip(other.keys(),[-1*vforvinother.values()])))returnself.__add__(other)ifnotisinstance(other,TensorLike):raiseTypeError(f"Cannot subtract {type(other)} from a BoseSentence.")ifqml.math.size(other)>1:raiseValueError(f"Arithmetic Bose operations can only accept an array of length 1, "f"but received {other} of length {len(other)}")other=BoseSentence({BoseWord({}):-1*other})# -constant * Ireturnself.__add__(other)def__rsub__(self,other):"""Subtract a BoseSentence to a constant, i.e. 2 - BoseSentence({...})"""ifnotisinstance(other,TensorLike):raiseTypeError(f"Cannot subtract a BoseSentence from {type(other)}.")ifqml.math.size(other)>1:raiseValueError(f"Arithmetic Bose operations can only accept an array of length 1, "f"but received {other} of length {len(other)}")self_bs=BoseSentence(dict(zip(self.keys(),[-1*vforvinself.values()])))other_bs=BoseSentence({BoseWord({}):other})# constant * Ireturnself_bs+other_bsdef__mul__(self,other):r"""Multiply two Bose sentences by iterating over each sentence and multiplying the Bose words pair-wise"""ifisinstance(other,BoseWord):other=BoseSentence({other:1})ifisinstance(other,BoseSentence):if(len(self)==0)or(len(other)==0):returnBoseSentence({BoseWord({}):0})product=BoseSentence({})forbw1,coeff1inself.items():forbw2,coeff2inother.items():product[bw1*bw2]+=coeff1*coeff2returnproductifnotisinstance(other,TensorLike):raiseTypeError(f"Cannot multiply BoseSentence by {type(other)}.")ifqml.math.size(other)>1:raiseValueError(f"Arithmetic Bose operations can only accept an array of length 1, "f"but received {other} of length {len(other)}")vals=[i*otherforiinself.values()]returnBoseSentence(dict(zip(self.keys(),vals)))def__rmul__(self,other):r"""Reverse multiply a BoseSentence Multiplies a BoseSentence "from the left" with an object that can't be modified to support __mul__ for BoseSentence. Will be defaulted in for example when multiplying ``2 * bose_sentence``, since the ``__mul__`` operator on an integer will fail to multiply with a BoseSentence"""ifnotisinstance(other,TensorLike):raiseTypeError(f"Cannot multiply {type(other)} by BoseSentence.")ifqml.math.size(other)>1:raiseValueError(f"Arithmetic Bose operations can only accept an array of length 1, "f"but received {other} of length {len(other)}")vals=[i*otherforiinself.values()]returnBoseSentence(dict(zip(self.keys(),vals)))def__pow__(self,value):r"""Exponentiate a Bose sentence to an integer power."""ifvalue<0ornotisinstance(value,int):raiseValueError("The exponent must be a positive integer.")operator=BoseSentence({BoseWord({}):1})# 1 times Identityfor_inrange(value):operator*=selfreturnoperator
[docs]defsimplify(self,tol=1e-8):r"""Remove any BoseWords in the BoseSentence with coefficients less than the threshold tolerance."""items=list(self.items())forbw,coeffinitems:ifabs(coeff)<=tol:delself[bw]