# Copyright 2018-2023 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."""This submodule defines the symbolic operation that stands for an exponential of an operator."""fromwarningsimportwarnimportnumpyasnpfromscipy.sparse.linalgimportexpmassparse_expmimportpennylaneasqmlfrompennylaneimportmathfrompennylane.mathimportexpand_matrixfrompennylane.operationimport(AnyWires,DecompositionUndefinedError,GeneratorUndefinedError,Operation,Operator,OperatorPropertyUndefined,)frompennylane.wiresimportWiresfrom.linear_combinationimportLinearCombinationfrom.sprodimportSProdfrom.sumimportSumfrom.symbolicopimportScalarSymbolicOp
[docs]defexp(op,coeff=1,num_steps=None,id=None):"""Take the exponential of an Operator times a coefficient. Args: base (~.operation.Operator): The Operator to be exponentiated coeff (float): a scalar coefficient of the operator num_steps (int): The number of steps used in the decomposition of the exponential operator, also known as the Trotter number. If this value is `None` and the Suzuki-Trotter decomposition is needed, an error will be raised. id (str): id for the Exp operator. Default is None. Returns: :class:`Exp`: An :class:`~.operation.Operator` representing an operator exponential. .. note:: This operator supports a batched base, a batched coefficient and a combination of both: >>> op = qml.exp(qml.RX([1, 2, 3], wires=0), coeff=4) >>> qml.matrix(op).shape (3, 2, 2) >>> op = qml.exp(qml.RX(1, wires=0), coeff=[1, 2, 3]) >>> qml.matrix(op).shape (3, 2, 2) >>> op = qml.exp(qml.RX([1, 2, 3], wires=0), coeff=[4, 5, 6]) >>> qml.matrix(op).shape (3, 2, 2) But it doesn't support batching of operators: >>> op = qml.exp([qml.RX(1, wires=0), qml.RX(2, wires=0)], coeff=4) AttributeError: 'list' object has no attribute 'batch_size' **Example** This symbolic operator can be used to make general rotation operators: >>> x = np.array(1.23) >>> op = qml.exp(qml.X(0), -0.5j * x) >>> qml.math.allclose(op.matrix(), qml.RX(x, wires=0).matrix()) True This can even be used for more complicated generators: >>> t = qml.X(0) @ qml.X(1) + qml.Y(0) @ qml.Y(1) >>> isingxy = qml.exp(t, 0.25j * x) >>> qml.math.allclose(isingxy.matrix(), qml.IsingXY(x, wires=(0,1)).matrix()) True If the coefficient is purely imaginary and the base operator is Hermitian, then the gate can be used in a circuit, though it may not be supported by the device and may not be differentiable. >>> @qml.qnode(qml.device('default.qubit', wires=1)) ... def circuit(x): ... qml.exp(qml.X(0), -0.5j * x) ... return qml.expval(qml.Z(0)) >>> print(qml.draw(circuit)(1.23)) 0: ──Exp─┤ <Z> If the base operator is Hermitian and the coefficient is real, then the ``Exp`` operator can be measured as an observable: >>> obs = qml.exp(qml.Z(0), 3) >>> @qml.qnode(qml.device('default.qubit', wires=1)) ... def circuit(): ... return qml.expval(obs) >>> circuit() tensor(20.08553692, requires_grad=True) """returnExp(op,coeff,num_steps=num_steps,id=id)
[docs]classExp(ScalarSymbolicOp,Operation):"""A symbolic operator representing the exponential of a operator. Args: base (~.operation.Operator): The Operator to be exponentiated coeff=1 (Number): A scalar coefficient of the operator. num_steps (int): The number of steps used in the decomposition of the exponential operator, also known as the Trotter number. If this value is `None` and the Suzuki-Trotter decomposition is needed, an error will be raised. id (str): id for the Exp operator. Default is None. **Example** This symbolic operator can be used to make general rotation operators: >>> x = np.array(1.23) >>> op = Exp( qml.X(0), -0.5j * x) >>> qml.math.allclose(op.matrix(), qml.RX(x, wires=0).matrix()) True This can even be used for more complicated generators: >>> t = qml.X(0) @ qml.X(1) + qml.Y(0) @ qml.Y(1) >>> isingxy = Exp(t, 0.25j * x) >>> qml.math.allclose(isingxy.matrix(), qml.IsingXY(x, wires=(0,1)).matrix()) True If the coefficient is purely imaginary and the base operator is Hermitian, then the gate can be used in a circuit, though it may not be supported by the device and may not be differentiable. >>> @qml.qnode(qml.device('default.qubit', wires=1)) ... def circuit(x): ... Exp(qml.X(0), -0.5j * x) ... return qml.expval(qml.Z(0)) >>> print(qml.draw(circuit)(1.23)) 0: ──Exp─┤ <Z> If the base operator is Hermitian and the coefficient is real, then the ``Exp`` operator can be measured as an observable: >>> obs = Exp(qml.Z(0), 3) >>> @qml.qnode(qml.device('default.qubit', wires=1)) ... def circuit(): ... return qml.expval(obs) >>> circuit() tensor(20.08553692, requires_grad=True) """control_wires=Wires([])_name="Exp"def_flatten(self):return(self.base,self.data[0]),(self.num_steps,)@classmethoddef_unflatten(cls,data,metadata):returncls(data[0],data[1],num_steps=metadata[0])# pylint: disable=too-many-argumentsdef__init__(self,base,coeff=1,num_steps=None,id=None):ifnotisinstance(base,Operator):raiseTypeError(f"base is expected to be of type Operator, but received {type(base)}")super().__init__(base,scalar=coeff,id=id)self.grad_recipe=[None]self.num_steps=num_stepsself.hyperparameters["num_steps"]=num_stepsdef__repr__(self):return(f"Exp({self.coeff}{self.base})"ifself.base.arithmetic_depth>0elsef"Exp({self.coeff}{self.base.name})")@propertydefhash(self):returnhash((str(self.name),self.base.hash,str(self.coeff)))@propertydefcoeff(self):"""The numerical coefficient of the operator in the exponent."""returnself.scalar@propertydefnum_params(self):returnself.base.num_params+1@propertydefis_hermitian(self):returnself.base.is_hermitianandmath.allequal(math.imag(self.coeff),0)@propertydef_queue_category(self):return"_ops"# pylint: disable=invalid-overridden-method, arguments-renamed@propertydefhas_decomposition(self):# TODO: Support nested sums in methodbase=self.basecoeff=self.coeffifisinstance(base,SProd):coeff*=base.scalarbase=base.baseis_pauli_rot=qml.pauli.is_pauli_word(self.base)andmath.real(self.coeff)==0is_hamiltonian=isinstance(base,LinearCombination)is_sum_of_pauli_words=isinstance(base,Sum)andall(qml.pauli.is_pauli_word(o)foroinbase)returnis_pauli_rotoris_hamiltonianoris_sum_of_pauli_words
[docs]defdecomposition(self):r"""Representation of the operator as a product of other operators. Decomposes into :class:`~.PauliRot` if the coefficient is imaginary and the base is a Pauli Word. .. math:: O = O_1 O_2 \dots O_n A ``DecompositionUndefinedError`` is raised if the coefficient is not imaginary or the base is not a Pauli Word. Returns: list[PauliRot]: decomposition of the operator """withqml.QueuingManager.stop_recording():d=self._recursive_decomposition(self.base,self.coeff)ifqml.QueuingManager.recording():foropind:qml.apply(op)returnd
def_recursive_decomposition(self,base:Operator,coeff:complex):"""Decompose the exponential of ``base`` multiplied by ``coeff``. Args: base (Operator): exponentiated operator coeff (complex): coefficient multiplying the exponentiated operator Returns: List[Operator]: decomposition """# Change base to `Sum`/`Prod`ifisinstance(base,LinearCombination):base=qml.dot(base.coeffs,base.ops)ifisinstance(base,SProd):returnself._recursive_decomposition(base.base,base.scalar*coeff)ifself.num_stepsisnotNoneandisinstance(base,Sum):# Apply trotter decompositioncoeffs,ops=[1]*len(base),base.operandscoeffs=[c*coeffforcincoeffs]returnself._trotter_decomposition(ops,coeffs)ifnotqml.math.is_abstract(coeff)andqml.math.real(coeff):error_msg=f"The decomposition of the {self} operator is not defined."ifnotself.num_steps:# if num_steps was not seterror_msg+=(" Please set a value to ``num_steps`` when instantiating the ``Exp`` operator ""if a Suzuki-Trotter decomposition is required.")ifself.base.is_hermitian:error_msg+=(" Decomposition is not defined for real coefficients of hermitian operators.")raiseDecompositionUndefinedError(error_msg)returnself._smart_decomposition(coeff,base)def_smart_decomposition(self,coeff,base):"""Decompose to an operator to an operator with a generator or a PauliRot if possible."""# Store operator classes with generatorshas_generator_types=[]has_generator_types_anywires=[]forop_nameinqml.ops.qubit.__all__:# pylint:disable=no-memberop_class=getattr(qml.ops.qubit,op_name)# pylint:disable=no-memberifop_class.has_generator:ifop_class.num_wires==AnyWires:has_generator_types_anywires.append(op_class)elifop_class.num_wires==len(base.wires):has_generator_types.append(op_class)# Ensure op_class.num_wires == base.num_wires before op_class.num_wires == AnyWireshas_generator_types.extend(has_generator_types_anywires)forop_classinhas_generator_types:# PauliRot and PCPhase have different positional argsifop_classnotin{qml.PauliRot,qml.PCPhase}:g,c=qml.generator(op_class)(coeff,base.wires)# Some generators are not wire-ordered (e.g. OrbitalRotation)mapped_wires_g=qml.map_wires(g,dict(zip(g.wires,base.wires)))ifqml.equal(mapped_wires_g,base):# Cancel the coefficients added by the generatorcoeff=math.real(-1j/c*coeff)return[op_class(coeff,g.wires)]# could have absorbed the coefficient.simplified_g=qml.simplify(qml.s_prod(c,mapped_wires_g))ifqml.equal(simplified_g,base):# Cancel the coefficients added by the generatorcoeff=math.real(-1j*coeff)return[op_class(coeff,g.wires)]ifqml.pauli.is_pauli_word(base):# Check if the exponential can be decomposed into a PauliRot gatereturnself._pauli_rot_decomposition(base,coeff)error_msg=f"The decomposition of the {self} operator is not defined."ifnotself.num_steps:# if num_steps was not seterror_msg+=(" Please set a value to ``num_steps`` when instantiating the ``Exp`` operator ""if a Suzuki-Trotter decomposition is required. ")raiseDecompositionUndefinedError(error_msg)@staticmethoddef_pauli_rot_decomposition(base:Operator,coeff:complex):"""Decomposes the exponential of a Pauli word into a PauliRot. Args: base (Operator): exponentiated operator coeff (complex): coefficient multiplying the exponentiated operator Returns: List[Operator]: list containing the PauliRot operator """# Cancel the coefficients added by PauliRot and Ising gatescoeff=math.real(2j*coeff)pauli_word=qml.pauli.pauli_word_to_string(base)ifpauli_word=="I"*base.num_wires:return[]return[qml.PauliRot(theta=coeff,pauli_word=pauli_word,wires=base.wires)]def_trotter_decomposition(self,ops:list[Operator],coeffs:list[complex]):"""Uses the Suzuki-Trotter approximation to decompose the exponential of the linear combination of ``coeffs`` and ``ops``. Args: ops (List[Operator]): list of operators of the linear combination coeffs (List[complex]): list of coefficients of the linear combination Raises: ValueError: if the Trotter number (``num_steps``) is not defined DecompositionUndefinedError: if the linear combination contains operators that are not Pauli words Returns: List[Operator]: a list of operators containing the decomposition """op_list=[]forc,opinzip(coeffs,ops):c/=self.num_steps# divide by trotter numberifisinstance(op,SProd):c*=op.scalarop=op.baseop_list.extend(self._recursive_decomposition(op,c))returnop_list*self.num_steps# apply operators ``num_steps`` times
[docs]defmatrix(self,wire_order=None):coeff_interface=math.get_interface(self.scalar)ifcoeff_interface=="autograd"andmath.requires_grad(self.scalar):# math.expm is not differentiable with autograd# So we try to do a differentiable construction if possible## This won't catch situations when the base matrix is autograd,# but at least this provides as much trainablility as possibletry:eigvals=self.eigvals()eigvals_mat=(math.stack([math.diag(e)foreineigvals])ifqml.math.ndim(self.scalar)>0elsemath.diag(eigvals))iflen(self.diagonalizing_gates())==0:returnexpand_matrix(eigvals_mat,wires=self.wires,wire_order=wire_order)diagonalizing_mat=qml.matrix(self.diagonalizing_gates,wire_order=self.wires)()mat=diagonalizing_mat.conj().T@eigvals_mat@diagonalizing_matreturnexpand_matrix(mat,wires=self.wires,wire_order=wire_order)exceptOperatorPropertyUndefined:warn(f"The autograd matrix for {self} is not differentiable. ""Use a different interface if you need backpropagation.",UserWarning,)returnsuper().matrix(wire_order=wire_order)
[docs]defsparse_matrix(self,wire_order=None,format="csr"):ifwire_orderisnotNone:raiseNotImplementedError("Wire order is not implemented for sparse_matrix")returnsparse_expm(self.coeff*self.base.sparse_matrix().tocsc()).asformat(format)
[docs]defgenerator(self):r"""Generator of an operator that is in single-parameter-form. For example, for operator .. math:: U(\phi) = e^{i\phi (0.5 Y + Z\otimes X)} we get the generator >>> U.generator() 0.5 * Y(0) + Z(0) @ X(1) """ifself.has_generator:returnself.baseraiseGeneratorUndefinedError(f"Exponential with coefficient {self.coeff} and base operator {self.base} does not appear to have a "f"generator. Consider using op.simplify() to simplify before finding the generator, or define the operator "f"in the form exp(-ixG) through the Evolution class.")