# 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 the power of an operator."""importcopyfromtypingimportUnionfromscipy.linalgimportfractional_matrix_powerimportpennylaneasqmlfrompennylaneimportmathasqmlmathfrompennylane.operationimport(AdjointUndefinedError,DecompositionUndefinedError,Observable,Operation,PowUndefinedError,SparseMatrixUndefinedError,)frompennylane.ops.identityimportIdentityfrompennylane.queuingimportQueuingManager,applyfrom.symbolicopimportScalarSymbolicOp_superscript=str.maketrans("0123456789.+-","⁰¹²³⁴⁵⁶⁷⁸⁹⋅⁺⁻")
[docs]defpow(base,z=1,lazy=True,id=None):"""Raise an Operator to a power. Args: base (~.operation.Operator): the operator to be raised to a power z (float): the exponent (default value is 1) Keyword Args: lazy=True (bool): In lazy mode, all operations are wrapped in a ``Pow`` class and handled later. If ``lazy=False``, operation-specific simplifications are first attempted. id (str): custom label given to an operator instance, can be useful for some applications where the instance has to be identified Returns: Operator .. note:: This operator supports a batched base, a batched coefficient and a combination of both: >>> op = qml.pow(qml.RX([1, 2, 3], wires=0), z=4) >>> qml.matrix(op).shape (3, 2, 2) >>> op = qml.pow(qml.RX(1, wires=0), z=[1, 2, 3]) >>> qml.matrix(op).shape (3, 2, 2) >>> op = qml.pow(qml.RX([1, 2, 3], wires=0), z=[4, 5, 6]) >>> qml.matrix(op).shape (3, 2, 2) But it doesn't support batching of operators: >>> op = qml.pow([qml.RX(1, wires=0), qml.RX(2, wires=0)], z=4) AttributeError: 'list' object has no attribute 'name' .. seealso:: :class:`~.Pow`, :meth:`~.Operator.pow`. **Example** >>> qml.pow(qml.X(0), 0.5) X(0)**0.5 >>> qml.pow(qml.X(0), 0.5, lazy=False) SX(0) >>> qml.pow(qml.X(0), 0.1, lazy=False) X(0)**0.1 >>> qml.pow(qml.X(0), 2, lazy=False) I(0) Lazy behaviour can also be accessed via ``op ** z``. """iflazy:returnPow(base,z,id=id)try:pow_ops=base.pow(z)exceptPowUndefinedError:returnPow(base,z,id=id)num_ops=len(pow_ops)ifnum_ops==0:pow_op=qml.Identity(base.wires,id=id)elifnum_ops==1:pow_op=pow_ops[0]else:pow_op=qml.prod(*pow_ops)QueuingManager.remove(base)returnpow_op
[docs]classPow(ScalarSymbolicOp):"""Symbolic operator denoting an operator raised to a power. Args: base (~.operation.Operator): the operator to be raised to a power z=1 (float): the exponent **Example** >>> sqrt_x = Pow(qml.X(0), 0.5) >>> sqrt_x.decomposition() [SX(0)] >>> qml.matrix(sqrt_x) array([[0.5+0.5j, 0.5-0.5j], [0.5-0.5j, 0.5+0.5j]]) >>> qml.matrix(qml.SX(0)) array([[0.5+0.5j, 0.5-0.5j], [0.5-0.5j, 0.5+0.5j]]) >>> qml.matrix(Pow(qml.T(0), 1.234)) array([[1. +0.j , 0. +0.j ], [0. +0.j , 0.56597465+0.82442265j]]) """def_flatten(self):return(self.base,self.z),tuple()@classmethoddef_unflatten(cls,data,_):returnpow(data[0],z=data[1])# pylint: disable=unused-argumentdef__new__(cls,base=None,z=1,id=None):"""Mixes in parents based on inheritance structure of base. Though all the types will be named "Pow", their *identity* and location in memory will be different based on ``base``'s inheritance. We cache the different types in private class variables so that: >>> Pow(op, z).__class__ is Pow(op, z).__class__ True >>> type(Pow(op, z)) == type(Pow(op, z)) True >>> isinstance(Pow(op, z), type(Pow(op, z))) True >>> Pow(qml.RX(1.2, wires=0), 0.5).__class__ is Pow._operation_type True >>> Pow(qml.X(0), 1.2).__class__ is Pow._operation_observable_type True """ifisinstance(base,Operation):ifisinstance(base,Observable):returnobject.__new__(PowOpObs)# not an observablereturnobject.__new__(PowOperation)ifisinstance(base,Observable):returnobject.__new__(PowObs)returnobject.__new__(Pow)def__init__(self,base=None,z=1,id=None):self.hyperparameters["z"]=zself._name=f"{base.name}**{z}"super().__init__(base,scalar=z,id=id)ifisinstance(self.z,int)andself.z>0:if(base_pauli_rep:=getattr(self.base,"pauli_rep",None))and(self.batch_sizeisNone):pr=base_pauli_repfor_inrange(self.z-1):pr=pr@base_pauli_repself._pauli_rep=prelse:self._pauli_rep=Noneelse:self._pauli_rep=Nonedef__repr__(self):return(f"({self.base})**{self.z}"ifself.base.arithmetic_depth>0elsef"{self.base}**{self.z}")@propertydefz(self):"""The exponent."""returnself.hyperparameters["z"]@propertydefndim_params(self):returnself.base.ndim_params@propertydefdata(self):"""The trainable parameters"""returnself.base.data@data.setterdefdata(self,new_data):self.base.data=new_data
@staticmethoddef_matrix(scalar,mat):ifisinstance(scalar,int):ifqml.math.get_deep_interface(mat)!="tensorflow":returnqmlmath.linalg.matrix_power(mat,scalar)# TensorFlow doesn't have a matrix_power func, and scipy.linalg.fractional_matrix_power# is not differentiable. So we use a custom implementation of matrix power for integer# exponents below.ifscalar==0:# Used instead of qml.math.eye for tracing derivativesreturnmat@qmlmath.linalg.inv(mat)ifscalar>0:out=matelse:out=mat=qmlmath.linalg.inv(mat)scalar*=-1for_inrange(scalar-1):out@=matreturnoutreturnfractional_matrix_power(mat,scalar)# pylint: disable=arguments-renamed, invalid-overridden-method@propertydefhas_sparse_matrix(self)->bool:returnself.base.has_sparse_matrixandisinstance(self.z,int)# pylint: disable=arguments-differ
# pylint: disable=arguments-renamed, invalid-overridden-method@propertydefhas_decomposition(self):ifisinstance(self.z,int)andself.z>0:returnTruetry:self.base.pow(self.z)exceptPowUndefinedError:returnFalseexceptExceptionase:# pylint: disable=broad-except# some pow methods cant handle a batched zifqml.math.ndim(self.z)!=0:returnFalseraiseereturnTrue
[docs]defdecomposition(self):try:returnself.base.pow(self.z)exceptPowUndefinedErrorase:ifisinstance(self.z,int)andself.z>0:ifQueuingManager.recording():return[apply(self.base)for_inrange(self.z)]return[copy.copy(self.base)for_inrange(self.z)]# TODO: consider: what if z is an int and less than 0?# do we want Pow(base, -1) to be a "more fundamental" opraiseDecompositionUndefinedErrorfromeexceptExceptionase:# pylint: disable=broad-exceptraiseDecompositionUndefinedErrorfrome
[docs]defdiagonalizing_gates(self):r"""Sequence of gates that diagonalize the operator in the computational basis. Given the eigendecomposition :math:`O = U \Sigma U^{\dagger}` where :math:`\Sigma` is a diagonal matrix containing the eigenvalues, the sequence of diagonalizing gates implements the unitary :math:`U^{\dagger}`. The diagonalizing gates of an operator to a power is the same as the diagonalizing gates as the original operator. As we can see, .. math:: O^2 = U \Sigma U^{\dagger} U \Sigma U^{\dagger} = U \Sigma^2 U^{\dagger} This formula can be extended to inversion and any rational number. The diagonalizing gates rotate the state into the eigenbasis of the operator. A ``DiagGatesUndefinedError`` is raised if no representation by decomposition is defined. .. seealso:: :meth:`~.Operator.compute_diagonalizing_gates`. Returns: list[.Operator] or None: a list of operators """returnself.base.diagonalizing_gates()
[docs]defgenerator(self):r"""Generator of an operator that is in single-parameter-form. The generator of a power operator is ``z`` times the generator of the base matrix. .. math:: U(\phi)^z = e^{i\phi (z G)} See also :func:`~.generator` """returnself.z*self.base.generator()
[docs]defadjoint(self):"""Create an operation that is the adjoint of this one. Adjointed operations are the conjugated and transposed version of the original operation. Adjointed ops are equivalent to the inverted operation for unitary gates. .. warning:: The adjoint of a fractional power of an operator is not well-defined due to branch cuts in the power function. Therefore, an ``AdjointUndefinedError`` is raised when the power ``z`` is not an integer. The integer power check is a type check, so that floats like ``2.0`` are not considered to be integers. Returns: The adjointed operation. Raises: AdjointUndefinedError: If the exponent ``z`` is not of type ``int``. """ifisinstance(self.z,int):returnPow(base=qml.adjoint(self.base),z=self.z)raiseAdjointUndefinedError("The adjoint of Pow operators only is well-defined for integer powers.")
[docs]defsimplify(self)->Union["Pow",Identity]:# try using pauli_rep:ifpr:=self.pauli_rep:pr.simplify()returnpr.operation(wire_order=self.wires)base=self.baseifqml.capture.enabled()elseself.base.simplify()try:ops=base.pow(z=self.z)ifnotops:returnqml.Identity(self.wires)op=qml.prod(*ops)iflen(ops)>1elseops[0]returnopifqml.capture.enabled()elseop.simplify()exceptPowUndefinedError:returnPow(base=base,z=self.z)
# pylint: disable=no-memberclassPowOperation(Pow,Operation):"""Operation-specific methods and properties for the ``Pow`` class. Dynamically mixed in based on the provided base operator. If the base operator is an Operation, this class will be mixed in. When we no longer rely on certain functionality through `Operation`, we can get rid of this class. """def__new__(cls,*_,**__):returnobject.__new__(cls)# until we add gradient supportgrad_method=None@propertydefname(self):returnself._name@propertydefcontrol_wires(self):returnself.base.control_wiresclassPowObs(Pow,Observable):"""A child class of ``Pow`` that also inherits from ``Observable``."""def__new__(cls,*_,**__):returnobject.__new__(cls)# pylint: disable=too-many-ancestorsclassPowOpObs(PowOperation,Observable):"""A child class of ``Pow`` that inherits from both ``Observable`` and ``Operation``. """def__new__(cls,*_,**__):returnobject.__new__(cls)