# Copyright 2018-2022 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 a base class for symbolic operations representing operator math."""fromabcimportabstractmethodfromcopyimportcopyimportnumpyasnpimportpennylaneasqmlfrompennylane.operationimport_UNSET_BATCH_SIZE,Operatorfrompennylane.queuingimportQueuingManagerfrom.compositeimporthandle_recursion_error
[docs]classSymbolicOp(Operator):"""Developer-facing base class for single-operator symbolic operators. Args: base (~.operation.Operator): the base operation that is modified symbolically id (str): custom label given to an operator instance, can be useful for some applications where the instance has to be identified This *developer-facing* class can serve as a parent to single base symbolic operators, such as :class:`~.ops.op_math.Adjoint`. New symbolic operators can inherit from this class to receive some common default behaviour, such as deferring properties to the base class, copying the base class during a shallow copy, and updating the metadata of the base operator during queueing. The child symbolic operator should define the `_name` property during initialization and define any relevant representations, such as :meth:`~.operation.Operator.matrix`, :meth:`~.operation.Operator.diagonalizing_gates`, :meth:`~.operation.Operator.eigvals`, and :meth:`~.operation.Operator.decomposition`. """_name="Symbolic"@classmethoddef_primitive_bind_call(cls,*args,**kwargs):# has no wires, so doesn't need any wires processingreturncls._primitive.bind(*args,**kwargs)# pylint: disable=attribute-defined-outside-init@handle_recursion_errordef__copy__(self):# this method needs to be overwritten because the base must be copied too.copied_op=object.__new__(type(self))# copied_op must maintain inheritance structure of self# Relevant for symbolic ops that mix in operation-specific components.forattr,valueinvars(self).items():ifattrnotin{"_hyperparameters"}:setattr(copied_op,attr,value)copied_op._hyperparameters=copy(self.hyperparameters)copied_op.hyperparameters["base"]=copy(self.base)returncopied_op# pylint: disable=super-init-not-calleddef__init__(self,base,id=None):self.hyperparameters["base"]=baseself._id=idself._pauli_rep=Noneself.queue()@propertydefbatch_size(self):returnself.base.batch_size@propertydefbase(self)->Operator:"""The base operator."""returnself.hyperparameters["base"]@propertydefdata(self):"""The trainable parameters"""returnself.base.data@data.setterdefdata(self,new_data):self.base.data=new_data@propertydefnum_params(self):returnself.base.num_params@property@handle_recursion_errordefwires(self):returnself.base.wires# pylint:disable = missing-function-docstring@property@handle_recursion_errordefbasis(self):returnself.base.basis@propertydefnum_wires(self):"""Number of wires the operator acts on."""returnlen(self.wires)# pylint: disable=arguments-renamed, invalid-overridden-method@propertydefhas_matrix(self):returnself.base.has_matrix@propertydefis_hermitian(self):returnself.base.is_hermitian@propertydef_queue_category(self):returnself.base._queue_category# pylint: disable=protected-access
[docs]classScalarSymbolicOp(SymbolicOp):"""Developer-facing base class for single-operator symbolic operators that contain a scalar coefficient. Args: base (~.operation.Operator): the base operation that is modified symbolically scalar (float): the scalar coefficient id (str): custom label given to an operator instance, can be useful for some applications where the instance has to be identified This *developer-facing* class can serve as a parent to single base symbolic operators, such as :class:`~.ops.op_math.SProd` and :class:`~.ops.op_math.Pow`. """_name="ScalarSymbolicOp"def__init__(self,base,scalar:float,id=None):self.scalar=np.array(scalar)ifisinstance(scalar,list)elsescalarsuper().__init__(base,id=id)self._batch_size=_UNSET_BATCH_SIZE@property@handle_recursion_errordefbatch_size(self):ifself._batch_sizeis_UNSET_BATCH_SIZE:base_batch_size=self.base.batch_sizeifqml.math.ndim(self.scalar)==0:# coeff is not batchedself._batch_size=base_batch_sizeelse:# coeff is batchedscalar_size=qml.math.size(self.scalar)ifbase_batch_sizeisnotNoneandbase_batch_size!=scalar_size:raiseValueError("Broadcasting was attempted but the broadcasted dimensions "f"do not match: {scalar_size}, {base_batch_size}.")self._batch_size=scalar_sizereturnself._batch_size@property@handle_recursion_errordefdata(self):return(self.scalar,*self.base.data)@data.setterdefdata(self,new_data):self.scalar=new_data[0]self.base.data=new_data[1:]@property@handle_recursion_errordefhas_matrix(self):returnself.base.has_matrix@property@handle_recursion_errordefhash(self):returnhash((str(self.name),str(self.scalar),self.base.hash,))@staticmethod@abstractmethoddef_matrix(scalar,mat):"""Scalar-matrix operation that doesn't take into account batching. ``ScalarSymbolicOp.matrix`` will call this method to compute the matrix for a single scalar and base matrix. Args: scalar (Union[int, float]): non-broadcasted scalar mat (ndarray): non-broadcasted matrix """
[docs]@handle_recursion_errordefmatrix(self,wire_order=None):r"""Representation of the operator as a matrix in the computational basis. If ``wire_order`` is provided, the numerical representation considers the position of the operator's wires in the global wire order. Otherwise, the wire order defaults to the operator's wires. If the matrix depends on trainable parameters, the result will be cast in the same autodifferentiation framework as the parameters. A ``MatrixUndefinedError`` is raised if the base matrix representation has not been defined. .. seealso:: :meth:`~.Operator.compute_matrix` Args: wire_order (Iterable): global wire order, must contain all wire labels from the operator's wires Returns: tensor_like: matrix representation """# compute base matrixbase_matrix=self.base.matrix()scalar_interface=qml.math.get_interface(self.scalar)scalar=self.scalarifscalar_interface=="torch":# otherwise get `RuntimeError: Can't call numpy() on Tensor that requires grad.`base_matrix=qml.math.convert_like(base_matrix,self.scalar)elifscalar_interface=="tensorflow":# just cast everything to complex128. Otherwise we may have casting problems# where things get truncated like in SProd(tf.Variable(0.1), qml.X(0))scalar=qml.math.cast(scalar,"complex128")base_matrix=qml.math.cast(base_matrix,"complex128")# compute scalar operation on base matrix taking batching into accountscalar_size=qml.math.size(scalar)ifscalar_size!=1:ifscalar_size==self.base.batch_size:# both base and scalar are broadcastedmat=qml.math.stack([self._matrix(s,m)fors,minzip(scalar,base_matrix)])else:# only scalar is broadcastedmat=qml.math.stack([self._matrix(s,base_matrix)forsinscalar])elifself.base.batch_sizeisnotNone:# only base is broadcastedmat=qml.math.stack([self._matrix(scalar,ar2)forar2inbase_matrix])else:# none are broadcastedmat=self._matrix(scalar,base_matrix)returnqml.math.expand_matrix(mat,wires=self.wires,wire_order=wire_order)