# Copyright 2019-2024 Cambridge Quantum Computing## 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.importcopyfromtypingimportDict,TYPE_CHECKING,Union,List,Optional,Set,AnyimportnumpyimportnumpyasnpfromsympyimportSymbol,sympify,Expr,re,imfrompytket.pauliimportQubitPauliString,pauli_string_multfrompytket.circuitimportQubitfrompytket.utils.serializationimportcomplex_to_list,list_to_complexCoeffTypeAccepted=Union[int,float,complex,Expr]ifTYPE_CHECKING:fromscipy.sparseimportcsc_matrixdef_coeff_convert(coeff:Union[CoeffTypeAccepted,str])->Expr:sympy_val=sympify(coeff)ifnotisinstance(sympy_val,Expr):raiseValueError("Unsupported value for QubitPauliString coefficient")returnsympy_val
[docs]classQubitPauliOperator:""" Generic data structure for generation of circuits and expectation value calculation. Contains a dictionary from QubitPauliString to sympy Expr. Capacity for symbolic expressions allows the operator to be used to generate ansätze for variational algorithms. Represents a mathematical object :math:`\\sum_j \\alpha_j P_j`, where each :math:`\\alpha_j` is a complex symbolic expression and :math:`P_j` is a Pauli string, i.e. :math:`P_j \\in \\{ I, X, Y, Z\\}^{\\otimes n}`. A prototypical example is a molecular Hamiltonian, for which one may wish to calculate the expectation value :math:`\\langle \\Psi | H | \\Psi \\rangle` by decomposing :math:`H` into individual Pauli measurements. Alternatively, one may wish to evolve a state by the operator :math:`e^{-iHt}` for digital quantum simulation. In this case, the whole operator must be decomposed into native operations. In both cases, :math:`H` may be represented by a QubitPauliOperator. """
def__repr__(self)->str:returnself._dict.__repr__()def__getitem__(self,key:QubitPauliString)->Expr:returnself._dict[key]defget(self,key:QubitPauliString,default:CoeffTypeAccepted)->Expr:returnself._dict.get(key,_coeff_convert(default))def__setitem__(self,key:QubitPauliString,value:CoeffTypeAccepted)->None:"""Update value in dictionary ([]). Automatically converts value into sympy Expr. :param key: String to use as key :type key: QubitPauliString :param value: Associated coefficient :type value: Union[int, float, complex, Expr] """self._dict[key]=_coeff_convert(value)self._all_qubits.update(key.map.keys())def__getstate__(self)->Dict[QubitPauliString,Expr]:returnself._dictdef__setstate__(self,_dict:Dict[QubitPauliString,Expr])->None:# values assumed to be already sympifiedself._dict=_dictself._collect_qubits()def__eq__(self,other:object)->bool:ifisinstance(other,QubitPauliOperator):returnself._dict==other._dictreturnFalsedef__iadd__(self,addend:"QubitPauliOperator")->"QubitPauliOperator":"""In-place addition (+=) of QubitPauliOperators. :param addend: The operator to add :type addend: QubitPauliOperator :return: Updated operator (self) :rtype: QubitPauliOperator """ifisinstance(addend,QubitPauliOperator):forkey,valueinaddend._dict.items():self[key]=self.get(key,0.0)+valueself._all_qubits.update(addend._all_qubits)else:raiseTypeError("Cannot add {} to QubitPauliOperator.".format(type(addend)))returnselfdef__add__(self,addend:"QubitPauliOperator")->"QubitPauliOperator":"""Addition (+) of QubitPauliOperators. :param addend: The operator to add :type addend: QubitPauliOperator :return: Sum operator :rtype: QubitPauliOperator """summand=copy.deepcopy(self)summand+=addendreturnsummanddef__imul__(self,multiplier:Union[float,Expr,"QubitPauliOperator"])->"QubitPauliOperator":"""In-place multiplication (*=) with QubitPauliOperator or scalar. Multiply coefficients and terms. :param multiplier: The operator or scalar to multiply :type multiplier: Union[QubitPauliOperator, int, float, complex, Expr] :return: Updated operator (self) :rtype: QubitPauliOperator """# Handle operator of the same typeifisinstance(multiplier,QubitPauliOperator):result_terms:Dict=dict()forleft_key,left_valueinself._dict.items():forright_key,right_valueinmultiplier._dict.items():new_term,bonus_coeff=pauli_string_mult(left_key,right_key)new_coefficient=bonus_coeff*left_value*right_value# Update result dict.ifnew_terminresult_terms:result_terms[new_term]+=new_coefficientelse:result_terms[new_term]=new_coefficientself._dict=result_termsself._all_qubits.update(multiplier._all_qubits)returnself# Handle scalars.elifisinstance(multiplier,(float,Expr)):forkeyinself._dict:self[key]*=multiplierreturnself# Invalid multiplier typeelse:raiseTypeError("Cannot multiply QubitPauliOperator with {}".format(type(multiplier)))def__mul__(self,multiplier:Union[float,Expr,"QubitPauliOperator"])->"QubitPauliOperator":"""Multiplication (*) by QubitPauliOperator or scalar. :param multiplier: The scalar to multiply by :type multiplier: Union[int, float, complex, Expr, QubitPauliOperator] :return: Product operator :rtype: QubitPauliOperator """product=copy.deepcopy(self)product*=multiplierreturnproductdef__rmul__(self,multiplier:CoeffTypeAccepted)->"QubitPauliOperator":"""Multiplication (*) by a scalar. We only define __rmul__ for scalars because left multiply is queried as default behaviour, and is used for QubitPauliOperator*QubitPauliOperator. :param multiplier: The scalar to multiply by :type multiplier: Union[int, float, complex, Expr] :return: Product operator :rtype: QubitPauliOperator """returnself.__mul__(_coeff_convert(multiplier))@propertydefall_qubits(self)->Set[Qubit]:""" :return: The set of all qubits the operator ranges over (including qubits that were provided explicitly as identities) :rtype: Set[Qubit] """returnself._all_qubits
[docs]defsubs(self,symbol_dict:Dict[Symbol,complex])->None:"""Substitutes any matching symbols in the QubitPauliOperator. :param symbol_dict: A dictionary of symbols to fixed values. :type symbol_dict: Dict[Symbol, complex] """forkey,valueinself._dict.items():self._dict[key]=value.subs(symbol_dict)
[docs]defto_list(self)->List[Dict[str,Any]]:"""Generate a list serialized representation of QubitPauliOperator, suitable for writing to JSON. :return: JSON serializable list of dictionaries. :rtype: List[Dict[str, Any]] """ret:List[Dict[str,Any]]=[]fork,vinself._dict.items():try:coeff=complex_to_list(complex(v))exceptTypeError:asserttype(Expr(v))==Exprcoeff=str(v)ret.append({"string":k.to_list(),"coefficient":coeff,})returnret
[docs]@classmethoddeffrom_list(cls,pauli_list:List[Dict[str,Any]])->"QubitPauliOperator":"""Construct a QubitPauliOperator from a serializable JSON list format, as returned by QubitPauliOperator.to_list() :return: New QubitPauliOperator instance. :rtype: QubitPauliOperator """defget_qps(obj:Dict[str,Any])->QubitPauliString:returnQubitPauliString.from_list(obj["string"])defget_coeff(obj:Dict[str,Any])->Expr:coeff=obj["coefficient"]iftype(coeff)isstr:return_coeff_convert(coeff)else:return_coeff_convert(list_to_complex(coeff))returnQubitPauliOperator({get_qps(obj):get_coeff(obj)forobjinpauli_list})
[docs]defto_sparse_matrix(self,qubits:Union[List[Qubit],int,None]=None)->"csc_matrix":"""Represents the sparse operator as a dense operator under the ordering scheme specified by ``qubits``, and generates the corresponding matrix. - When ``qubits`` is an explicit list, the qubits are ordered with ``qubits[0]`` as the most significant qubit for indexing into the matrix. - If ``None``, then no padding qubits are introduced and we use the ILO-BE convention, e.g. ``Qubit("a", 0)`` is more significant than ``Qubit("a", 1)`` or ``Qubit("b")``. - Giving a number specifies the number of qubits to use in the final operator, treated as sequentially indexed from 0 in the default register (padding with identities as necessary) and ordered by ILO-BE so ``Qubit(0)`` is the most significant. :param qubits: Sequencing of qubits in the matrix, either as an explicit list, number of qubits to pad to, or infer from the operator. Defaults to None :type qubits: Union[List[Qubit], int, None], optional :return: A sparse matrix representation of the operator. :rtype: csc_matrix """ifqubitsisNone:qubits_=sorted(list(self._all_qubits))returnsum(complex(coeff)*pauli.to_sparse_matrix(qubits_)forpauli,coeffinself._dict.items())returnsum(complex(coeff)*pauli.to_sparse_matrix(qubits)forpauli,coeffinself._dict.items())
[docs]defdot_state(self,state:np.ndarray,qubits:Optional[List[Qubit]]=None)->np.ndarray:"""Applies the operator to the given state, mapping qubits to indexes according to ``qubits``. - When ``qubits`` is an explicit list, the qubits are ordered with ``qubits[0]`` as the most significant qubit for indexing into ``state``. - If ``None``, qubits sequentially indexed from 0 in the default register and ordered by ILO-BE so ``Qubit(0)`` is the most significant. :param state: The initial statevector :type state: numpy.ndarray :param qubits: Sequencing of qubits in ``state``, if not mapped to the default register. Defaults to None :type qubits: Union[List[Qubit], None], optional :return: The dot product of the operator with the statevector :rtype: numpy.ndarray """ifqubits:product_sum=sum(complex(coeff)*pauli.dot_state(state,qubits)forpauli,coeffinself._dict.items())else:product_sum=sum(complex(coeff)*pauli.dot_state(state)forpauli,coeffinself._dict.items())returnproduct_sumifisinstance(product_sum,numpy.ndarray)elsestate
[docs]defstate_expectation(self,state:np.ndarray,qubits:Optional[List[Qubit]]=None)->complex:"""Calculates the expectation value of the given statevector with respect to the operator, mapping qubits to indexes according to ``qubits``. - When ``qubits`` is an explicit list, the qubits are ordered with ``qubits[0]`` as the most significant qubit for indexing into ``state``. - If ``None``, qubits sequentially indexed from 0 in the default register and ordered by ILO-BE so ``Qubit(0)`` is the most significant. :param state: The initial statevector :type state: numpy.ndarray :param qubits: Sequencing of qubits in ``state``, if not mapped to the default register. Defaults to None :type qubits: Union[List[Qubit], None], optional :return: The expectation value of the statevector and operator :rtype: complex """ifqubits:returnsum(complex(coeff)*pauli.state_expectation(state,qubits)forpauli,coeffinself._dict.items())returnsum(complex(coeff)*pauli.state_expectation(state)forpauli,coeffinself._dict.items())
[docs]defcompress(self,abs_tol:float=1e-10)->None:"""Substitutes all free symbols in the QubitPauliOperator with 1, and then removes imaginary and real components which have magnitudes below the tolerance. If the resulting expression is 0, the term is removed entirely. Warning: This methods assumes significant expression structure is known a priori, and is best suited to operators which have simple product expressions, such as excitation operators for VQE ansätze and digital quantum simulation. Otherwise, it may remove terms relevant to computation. Each expression is of the form :math:`f(a_1,a_2,\\ldots,a_n)` for some symbols :math:`a_i`. :math:`|f(a_1,a_2,\\ldots,a_n)|` is assumed to monotonically increase in both real and imaginary components for all :math:`a_i \\in [0, 1]`. :param abs_tol: The threshold below which to remove values. :type abs_tol: float """to_delete=[]forkey,valueinself._dict.items():placeholder=value.subs({s:1forsinvalue.free_symbols})ifabs(re(placeholder))<=abs_tol:ifabs(im(placeholder))<=abs_tol:to_delete.append(key)else:self._dict[key]=im(value)*1jelifabs(im(placeholder))<=abs_tol:self._dict[key]=re(value)forkeyinto_delete:delself._dict[key]