# 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 the symbolic operation that indicates the control of an operator."""# pylint: disable=too-many-positional-argumentsimportfunctoolsimportwarningsfromcollections.abcimportCallable,SequencefromcopyimportcopyfromfunctoolsimportwrapsfrominspectimportsignaturefromtypingimportAny,Optional,overloadimportnumpyasnpfromscipyimportsparseimportpennylaneasqmlfrompennylaneimportmathasqmlmathfrompennylaneimportoperationfrompennylane.compilerimportcompilerfrompennylane.operationimportOperatorfrompennylane.wiresimportWires,WiresLikefrom.controlled_decompositionsimportctrl_decomp_bisect,ctrl_decomp_zyzfrom.symbolicopimportSymbolicOp@overloaddefctrl(op:Operator,control:Any,control_values:Optional[Sequence[bool]]=None,work_wires:Optional[Any]=None,)->Operator:...@overloaddefctrl(op:Callable,control:Any,control_values:Optional[Sequence[bool]]=None,work_wires:Optional[Any]=None,)->Callable:...
[docs]defctrl(op,control:Any,control_values=None,work_wires=None):"""Create a method that applies a controlled version of the provided op. :func:`~.qjit` compatible. .. note:: When used with :func:`~.qjit`, this function only supports the Catalyst compiler. See :func:`catalyst.ctrl` for more details. Please see the Catalyst :doc:`quickstart guide <catalyst:dev/quick_start>`, as well as the :doc:`sharp bits and debugging tips <catalyst:dev/sharp_bits>` page for an overview of the differences between Catalyst and PennyLane. Args: op (function or :class:`~.operation.Operator`): A single operator or a function that applies pennylane operators. control (Wires): The control wire(s). control_values (bool or list[bool]): The value(s) the control wire(s) should take. Integers other than 0 or 1 will be treated as ``int(bool(x))``. work_wires (Any): Any auxiliary wires that can be used in the decomposition Returns: function or :class:`~.operation.Operator`: If an Operator is provided, returns a Controlled version of the Operator. If a function is provided, returns a function with the same call signature that creates a controlled version of the provided function. .. seealso:: :class:`~.Controlled`. **Example** .. code-block:: python3 @qml.qnode(qml.device('default.qubit', wires=range(4))) def circuit(x): qml.X(2) qml.ctrl(qml.RX, (1,2,3), control_values=(0,1,0))(x, wires=0) return qml.expval(qml.Z(0)) >>> print(qml.draw(circuit)("x")) 0: ────╭RX(x)─┤ <Z> 1: ────├○─────┤ 2: ──X─├●─────┤ 3: ────╰○─────┤ >>> x = np.array(1.2) >>> circuit(x) tensor(0.36235775, requires_grad=True) >>> qml.grad(circuit)(x) tensor(-0.93203909, requires_grad=True) :func:`~.ctrl` works on both callables like ``qml.RX`` or a quantum function and individual :class:`~.operation.Operator`'s. >>> qml.ctrl(qml.Hadamard(0), (1,2)) Controlled(H(0), control_wires=[1, 2]) Controlled operations work with all other forms of operator math and simplification: >>> op = qml.ctrl(qml.RX(1.2, wires=0) ** 2 @ qml.RY(0.1, wires=0), control=1) >>> qml.simplify(qml.adjoint(op)) Controlled(RY(12.466370614359173, wires=[0]) @ RX(10.166370614359172, wires=[0]), control_wires=[1]) **Example with compiler** .. code-block:: python dev = qml.device("lightning.qubit", wires=2) @qml.qjit @qml.qnode(dev) def workflow(theta, w, cw): qml.Hadamard(wires=[0]) qml.Hadamard(wires=[1]) def func(arg): qml.RX(theta, wires=arg) def cond_fn(): qml.RY(theta, wires=w) qml.ctrl(func, control=[cw])(w) qml.ctrl(qml.cond(theta > 0.0, cond_fn), control=[cw])() qml.ctrl(qml.RZ, control=[cw])(theta, wires=w) qml.ctrl(qml.RY(theta, wires=w), control=[cw]) return qml.probs() >>> workflow(jnp.pi/4, 1, 0) array([0.25, 0.25, 0.03661165, 0.46338835]) """ifactive_jit:=compiler.active_compiler():available_eps=compiler.AvailableCompilers.names_entrypointsops_loader=available_eps[active_jit]["ops"].load()returnops_loader.ctrl(op,control,control_values=control_values,work_wires=work_wires)ifqml.math.is_abstract(op):returnControlled(op,control,control_values=control_values,work_wires=work_wires)returncreate_controlled_op(op,control,control_values=control_values,work_wires=work_wires)
defcreate_controlled_op(op,control,control_values=None,work_wires=None):"""Default ``qml.ctrl`` implementation, allowing other implementations to call it when needed."""control=qml.wires.Wires(control)ifisinstance(control_values,(int,bool)):control_values=[control_values]elifcontrol_valuesisNone:control_values=[True]*len(control)elifisinstance(control_values,tuple):control_values=list(control_values)ctrl_op=_try_wrap_in_custom_ctrl_op(op,control,control_values=control_values,work_wires=work_wires)ifctrl_opisnotNone:returnctrl_oppauli_x_based_ctrl_ops=_get_pauli_x_based_ops()# Special handling for PauliX-based controlled operationsifisinstance(op,pauli_x_based_ctrl_ops):qml.QueuingManager.remove(op)return_handle_pauli_x_based_controlled_ops(op,control,control_values,work_wires)# Flatten nested controlled operations to a multi-controlled operation for better# decomposition algorithms. This includes special cases like CRX, CRot, etc.ifisinstance(op,Controlled):work_wires=()ifwork_wiresisNoneelsework_wiresreturnctrl(op.base,control=control+op.control_wires,control_values=control_values+op.control_values,work_wires=work_wires+op.work_wires,)ifisinstance(op,Operator):returnControlled(op,control_wires=control,control_values=control_values,work_wires=work_wires)ifnotcallable(op):raiseValueError(f"The object {op} of type {type(op)} is not an Operator or callable. ""This error might occur if you apply ctrl to a list ""of operations instead of a function or Operator.")ifqml.capture.enabled():return_capture_ctrl_transform(op,control,control_values,work_wires)return_ctrl_transform(op,control,control_values,work_wires)def_ctrl_transform(op,control,control_values,work_wires):@wraps(op)defwrapper(*args,**kwargs):qscript=qml.tape.make_qscript(op)(*args,**kwargs)leaves,_=qml.pytrees.flatten((args,kwargs),lambdaobj:isinstance(obj,Operator))_=[qml.QueuingManager.remove(l)forlinleavesifisinstance(l,Operator)]# flip control_values == 0 wires here, so we don't have to do it for each individual op.flip_control_on_zero=(len(qscript)>1)and(control_valuesisnotNone)op_control_values=Noneifflip_control_on_zeroelsecontrol_valuesifflip_control_on_zero:_=[qml.X(w)forw,valinzip(control,control_values)ifnotval]_=[ctrl(op,control=control,control_values=op_control_values,work_wires=work_wires)foropinqscript.operations]ifflip_control_on_zero:_=[qml.X(w)forw,valinzip(control,control_values)ifnotval]ifqml.QueuingManager.recording():_=[qml.apply(m)forminqscript.measurements]returnqscript.measurementsreturnwrapper@functools.lru_cache# only create the first time requesteddef_get_ctrl_qfunc_prim():"""See capture/explanations.md : Higher Order primitives for more information on this code."""# if capture is enabled, jax should be installed# pylint: disable=import-outside-toplevelfrompennylane.capture.custom_primitivesimportNonInterpPrimitivectrl_prim=NonInterpPrimitive("ctrl_transform")ctrl_prim.multiple_results=Truectrl_prim.prim_type="higher_order"@ctrl_prim.def_impldef_(*args,n_control,jaxpr,control_values,work_wires,n_consts):frompennylane.tape.plxpr_conversionimportCollectOpsandMeasconsts=args[:n_consts]control_wires=args[-n_control:]args=args[n_consts:-n_control]collector=CollectOpsandMeas()withqml.QueuingManager.stop_recording():collector.eval(jaxpr,consts,*args)foropincollector.state["ops"]:ctrl(op,control_wires,control_values,work_wires)return[]@ctrl_prim.def_abstract_evaldef_(*_,**__):return[]returnctrl_primdef_capture_ctrl_transform(qfunc:Callable,control,control_values,work_wires)->Callable:"""Capture compatible way of performing an ctrl transform."""# note that this logic is tested in `tests/capture/test_nested_plxpr.py`importjax# pylint: disable=import-outside-toplevelctrl_prim=_get_ctrl_qfunc_prim()@wraps(qfunc)defnew_qfunc(*args,**kwargs):abstracted_axes,abstract_shapes=qml.capture.determine_abstracted_axes(args)jaxpr=jax.make_jaxpr(functools.partial(qfunc,**kwargs),abstracted_axes=abstracted_axes)(*args)flat_args=jax.tree_util.tree_leaves(args)control_wires=qml.wires.Wires(control)# make sure is iterablectrl_prim.bind(*jaxpr.consts,*abstract_shapes,*flat_args,*control_wires,jaxpr=jaxpr.jaxpr,n_control=len(control_wires),control_values=control_values,work_wires=work_wires,n_consts=len(jaxpr.consts),)returnnew_qfunc@functools.lru_cache()def_get_special_ops():"""Gets a list of special operations with custom controlled versions. This is placed inside a function to avoid circular imports. """ops_with_custom_ctrl_ops={(qml.PauliZ,1):qml.CZ,(qml.PauliZ,2):qml.CCZ,(qml.PauliY,1):qml.CY,(qml.CZ,1):qml.CCZ,(qml.SWAP,1):qml.CSWAP,(qml.Hadamard,1):qml.CH,(qml.RX,1):qml.CRX,(qml.RY,1):qml.CRY,(qml.RZ,1):qml.CRZ,(qml.Rot,1):qml.CRot,(qml.PhaseShift,1):qml.ControlledPhaseShift,}returnops_with_custom_ctrl_ops@functools.lru_cache()def_get_pauli_x_based_ops():"""Gets a list of pauli-x based operations This is placed inside a function to avoid circular imports. """returnqml.X,qml.CNOT,qml.Toffoli,qml.MultiControlledXdef_try_wrap_in_custom_ctrl_op(op,control,control_values=None,work_wires=None):"""Wraps a controlled operation in custom ControlledOp, returns None if not applicable."""ops_with_custom_ctrl_ops=_get_special_ops()custom_key=(type(op),len(control))ifcustom_keyinops_with_custom_ctrl_opsandall(control_values):qml.QueuingManager.remove(op)returnops_with_custom_ctrl_ops[custom_key](*op.data,control+op.wires)ifisinstance(op,qml.QubitUnitary):qml.QueuingManager.remove(op)returnqml.ControlledQubitUnitary(op.matrix(),wires=control+op.wires,control_values=control_values,work_wires=work_wires,)returnNonedef_handle_pauli_x_based_controlled_ops(op,control,control_values,work_wires):"""Handles PauliX-based controlled operations."""op_map={(qml.PauliX,1):qml.CNOT,(qml.PauliX,2):qml.Toffoli,(qml.CNOT,1):qml.Toffoli,}custom_key=(type(op),len(control))ifcustom_keyinop_mapandall(control_values):qml.QueuingManager.remove(op)returnop_map[custom_key](wires=control+op.wires)ifisinstance(op,qml.PauliX):returnqml.MultiControlledX(wires=control+op.wires,control_values=control_values,work_wires=work_wires)work_wires=work_wiresor[]returnqml.MultiControlledX(wires=control+op.wires,control_values=control_values+op.control_values,work_wires=work_wires+op.work_wires,)# pylint: disable=too-many-arguments, too-many-public-methods
[docs]classControlled(SymbolicOp):"""Symbolic operator denoting a controlled operator. Args: base (~.operation.Operator): the operator that is controlled control_wires (Any): The wires to control on. Keyword Args: control_values (Iterable[Bool]): The values to control on. Must be the same length as ``control_wires``. Defaults to ``True`` for all control wires. Provided values are converted to `Bool` internally. work_wires (Any): Any auxiliary wires that can be used in the decomposition .. note:: This class, ``Controlled``, denotes a controlled version of any individual operation. :class:`~.ControlledOp` adds :class:`~.Operation` specific methods and properties to the more general ``Controlled`` class. .. seealso:: :class:`~.ControlledOp`, and :func:`~.ctrl` **Example** >>> base = qml.RX(1.234, 1) >>> Controlled(base, (0, 2, 3), control_values=[True, False, True]) Controlled(RX(1.234, wires=[1]), control_wires=[0, 2, 3], control_values=[True, False, True]) >>> op = Controlled(base, 0, control_values=[0]) >>> op Controlled(RX(1.234, wires=[1]), control_wires=[0], control_values=[0]) The operation has both standard :class:`~.operation.Operator` properties and ``Controlled`` specific properties: >>> op.base RX(1.234, wires=[1]) >>> op.data (1.234,) >>> op.wires Wires([0, 1]) >>> op.control_wires Wires([0]) >>> op.target_wires Wires([1]) Control values are lists of booleans, indicating whether or not to control on the ``0==False`` value or the ``1==True`` wire. >>> op.control_values [0] Provided control values are converted to booleans internally, so any "truthy" or "falsy" objects work. >>> Controlled(base, ("a", "b", "c"), control_values=["", None, 5]).control_values [False, False, True] Representations for an operator are available if the base class defines them. Sparse matrices are available if the base class defines either a sparse matrix or only a dense matrix. >>> np.set_printoptions(precision=4) # easier to read the matrix >>> qml.matrix(op) array([[0.8156+0.j , 0. -0.5786j, 0. +0.j , 0. +0.j ], [0. -0.5786j, 0.8156+0.j , 0. +0.j , 0. +0.j ], [0. +0.j , 0. +0.j , 1. +0.j , 0. +0.j ], [0. +0.j , 0. +0.j , 0. +0.j , 1. +0.j ]]) >>> qml.eigvals(op) array([1. +0.j , 1. +0.j , 0.8156+0.5786j, 0.8156-0.5786j]) >>> print(qml.generator(op, format='observable')) (-0.5) [Projector0 X1] >>> op.sparse_matrix() <4x4 sparse matrix of type '<class 'numpy.complex128'>' with 6 stored elements in Compressed Sparse Row format> If the provided base matrix is an :class:`~.operation.Operation`, then the created object will be of type :class:`~.ops.op_math.ControlledOp`. This class adds some additional methods and properties to the basic :class:`~.ops.op_math.Controlled` class. >>> type(op) <class 'pennylane.ops.op_math.controlled_class.ControlledOp'> >>> op.parameter_frequencies [(0.5, 1.0)] """def_flatten(self):return(self.base,),(self.control_wires,tuple(self.control_values),self.work_wires)@classmethoddef_unflatten(cls,data,metadata):returncls(data[0],control_wires=metadata[0],control_values=metadata[1],work_wires=metadata[2])# pylint: disable=no-self-argument@operation.classpropertydef__signature__(cls):# pragma: no cover# this method is defined so inspect.signature returns __init__ signature# instead of __new__ signature# See PEP 362# use __init__ signature instead of __new__ signaturesig=signature(cls.__init__)# get rid of self from signaturenew_parameters=tuple(sig.parameters.values())[1:]new_sig=sig.replace(parameters=new_parameters)returnnew_sig# pylint: disable=unused-argumentdef__new__(cls,base,*_,**__):"""If base is an ``Operation``, then a ``ControlledOp`` should be used instead."""ifisinstance(base,operation.Operation):returnobject.__new__(ControlledOp)returnobject.__new__(Controlled)# pylint: disable=arguments-differ@classmethoddef_primitive_bind_call(cls,base,control_wires,control_values=None,work_wires=None,id=None):control_wires=Wires(control_wires)returncls._primitive.bind(base,*control_wires,control_values=control_values,work_wires=work_wires)# pylint: disable=too-many-function-argsdef__init__(self,base,control_wires:WiresLike,control_values=None,work_wires:WiresLike=None,id=None,):control_wires=Wires(control_wires)work_wires=Wires(()ifwork_wiresisNoneelsework_wires)ifcontrol_valuesisNone:control_values=[True]*len(control_wires)else:control_values=([bool(control_values)]ifisinstance(control_values,int)else[bool(control_value)forcontrol_valueincontrol_values])iflen(control_values)!=len(control_wires):raiseValueError("control_values should be the same length as control_wires")iflen(Wires.shared_wires([base.wires,control_wires]))!=0:raiseValueError("The control wires must be different from the base operation wires.")iflen(Wires.shared_wires([work_wires,base.wires+control_wires]))!=0:raiseValueError("Work wires must be different the control_wires and base operation wires.")self.hyperparameters["control_wires"]=control_wiresself.hyperparameters["control_values"]=control_valuesself.hyperparameters["work_wires"]=work_wiresself._name=f"C({base.name})"super().__init__(base,id)@propertydefhash(self):# these gates do not consider global phases in their hashifself.base.namein("RX","RY","RZ","Rot"):base_params=str([(id(d)ifqml.math.is_abstract(d)elseqml.math.round(qml.math.real(d)%(4*np.pi),10))fordinself.base.data])base_hash=hash((str(self.base.name),tuple(self.base.wires.tolist()),base_params,))else:base_hash=self.base.hashreturnhash(("Controlled",base_hash,tuple(self.control_wires.tolist()),tuple(self.control_values),tuple(self.work_wires.tolist()),))# pylint: disable=arguments-renamed, invalid-overridden-method@propertydefhas_matrix(self):returnself.base.has_matrix@propertydefbatch_size(self):returnself.base.batch_size@propertydefndim_params(self):returnself.base.ndim_params# Properties on the control values ######################@propertydefcontrol_values(self):"""Iterable[Bool]. For each control wire, denotes whether to control on ``True`` or ``False``."""returnself.hyperparameters["control_values"]@propertydef_control_int(self):"""Int. Conversion of ``control_values`` to an integer."""returnsum(2**ifori,valinenumerate(reversed(self.control_values))ifval)# Properties on the wires ##########################@propertydefcontrol_wires(self):"""The control wires."""returnself.hyperparameters["control_wires"]@propertydeftarget_wires(self):"""The wires of the target operator."""returnself.base.wires@propertydefwork_wires(self):"""Additional wires that can be used in the decomposition. Not modified by the operation."""returnself.hyperparameters["work_wires"]@propertydefwires(self):returnself.control_wires+self.target_wires
def_compute_matrix_from_base(self):base_matrix=self.base.matrix()interface=qmlmath.get_interface(base_matrix)num_target_states=2**len(self.target_wires)num_control_states=2**len(self.control_wires)total_matrix_size=num_control_states*num_target_statespadding_left=self._control_int*num_target_statespadding_right=total_matrix_size-padding_left-num_target_statesleft_pad=qmlmath.convert_like(qmlmath.cast_like(qmlmath.eye(padding_left,like=interface),1j),base_matrix)right_pad=qmlmath.convert_like(qmlmath.cast_like(qmlmath.eye(padding_right,like=interface),1j),base_matrix)shape=qml.math.shape(base_matrix)iflen(shape)==3:# stack if batchingreturnqml.math.stack([qml.math.block_diag([left_pad,_U,right_pad])for_Uinbase_matrix])returnqmlmath.block_diag([left_pad,base_matrix,right_pad])
[docs]defdecomposition(self):ifself.compute_decompositionisnotOperator.compute_decomposition:returnself.compute_decomposition(*self.data,self.wires)ifall(self.control_values):decomp=_decompose_no_control_values(self)ifdecompisNone:raiseqml.operation.DecompositionUndefinedErrorreturndecomp# We need to add paulis to flip some control wiresd=[qml.X(w)forw,valinzip(self.control_wires,self.control_values)ifnotval]decomp=_decompose_no_control_values(self)ifdecompisNone:no_control_values=copy(self).queue()no_control_values.hyperparameters["control_values"]=[1]*len(self.control_wires)d.append(no_control_values)else:d+=decompd+=[qml.X(w)forw,valinzip(self.control_wires,self.control_values)ifnotval]returnd
[docs]defgenerator(self):sub_gen=self.base.generator()projectors=(qml.Projector([val],wires=w)forval,winzip(self.control_values,self.control_wires))# needs to return a new_opmath instance regardless of whether new_opmath is enabled, because# it otherwise can't handle ControlledGlobalPhase, see PR #5194returnqml.prod(*projectors,sub_gen)
def_is_single_qubit_special_unitary(op):ifnotop.has_matrixorlen(op.wires)!=1:returnFalsemat=op.matrix()det=mat[0,0]*mat[1,1]-mat[0,1]*mat[1,0]returnqmlmath.allclose(det,1)def_decompose_pauli_x_based_no_control_values(op:Controlled):"""Decomposes a PauliX-based operation"""ifisinstance(op.base,qml.PauliX)andlen(op.control_wires)==1:return[qml.CNOT(wires=op.wires)]ifisinstance(op.base,qml.PauliX)andlen(op.control_wires)==2:returnqml.Toffoli.compute_decomposition(wires=op.wires)ifisinstance(op.base,qml.CNOT)andlen(op.control_wires)==1:returnqml.Toffoli.compute_decomposition(wires=op.wires)returnqml.MultiControlledX.compute_decomposition(wires=op.wires,work_wires=op.work_wires,)def_decompose_custom_ops(op:Controlled)->list["operation.Operator"]:"""Custom handling for decomposing a controlled operation"""pauli_x_based_ctrl_ops=_get_pauli_x_based_ops()ops_with_custom_ctrl_ops=_get_special_ops()custom_key=(type(op.base),len(op.control_wires))ifcustom_keyinops_with_custom_ctrl_ops:custom_op_cls=ops_with_custom_ctrl_ops[custom_key]returncustom_op_cls.compute_decomposition(*op.data,op.wires)ifisinstance(op.base,pauli_x_based_ctrl_ops):# has some special case handling of its own for further decompositionreturn_decompose_pauli_x_based_no_control_values(op)ifisinstance(op.base,qml.GlobalPhase):# A singly-controlled global phase is the same as a phase shift on the control wire# (Lemma 5.2 from https://arxiv.org/pdf/quant-ph/9503016)# Mathematically, this is the equation (with Id_2 being the 2-dim. identity matrix)# |0><0|⊗ Id_2 + |1><1|⊗ e^{i\phi} = [|0><0| + |1><1| e^{i\phi}] ⊗ Id_2phase_shift=qml.PhaseShift(phi=-op.data[0],wires=op.control_wires[-1])iflen(op.control_wires)==1:return[phase_shift]# For N>1 control wires, we simply add N-1 control wires to the phase shift# Mathematically, this is the equation (proven by inserting an identity)# (Id_{2^N} - |1><1|^N)⊗ Id_2 + |1><1|^N ⊗ e^{i\phi}# = (Id_{2^{N-1}} - |1><1|^{N-1}) ⊗ Id_4 + |1><1|^{N-1} ⊗ [|0><0|+|1><1|e^{i\phi}]⊗ Id_2return[ctrl(phase_shift,control=op.control_wires[:-1])]# TODO: will be removed in the second part of the controlled rework [sc-37951]iflen(op.control_wires)==1andhasattr(op.base,"_controlled"):result=op.base._controlled(op.control_wires[0])# pylint: disable=protected-access# disallow decomposing to itself# pylint: disable=unidiomatic-typecheckiftype(result)!=type(op):return[result]qml.QueuingManager.remove(result)returnNonedef_decompose_no_control_values(op:Controlled)->Optional[list["operation.Operator"]]:"""Decompose without considering control values. Returns None if no decomposition."""decomp=_decompose_custom_ops(op)ifdecompisnotNone:returndecompif_is_single_qubit_special_unitary(op.base):iflen(op.control_wires)>=2andqmlmath.get_interface(*op.data)=="numpy":returnctrl_decomp_bisect(op.base,op.control_wires)returnctrl_decomp_zyz(op.base,op.control_wires,work_wires=op.work_wires)ifnotop.base.has_decomposition:returnNonebase_decomp=op.base.decomposition()return[ctrl(newop,op.control_wires,work_wires=op.work_wires)fornewopinbase_decomp]
[docs]classControlledOp(Controlled,operation.Operation):"""Operation-specific methods and properties for the :class:`~.ops.op_math.Controlled` class. When an :class:`~.operation.Operation` is provided to the :class:`~.ops.op_math.Controlled` class, this type is constructed instead. It adds some additional :class:`~.operation.Operation` specific methods and properties. When we no longer rely on certain functionality through ``Operation``, we can get rid of this class. .. seealso:: :class:`~.Controlled` """def__new__(cls,*_,**__):# overrides dispatch behaviour of ``Controlled``returnobject.__new__(cls)# pylint: disable=too-many-function-argsdef__init__(self,base,control_wires,control_values=None,work_wires=None,id=None):super().__init__(base,control_wires,control_values,work_wires,id)# check the grad_recipe validityifself.grad_recipeisNone:# Make sure grad_recipe is an iterable of correct length instead of Noneself.grad_recipe=[None]*self.num_params@propertydefname(self):returnself._name@propertydefgrad_method(self):returnself.base.grad_method@propertydefparameter_frequencies(self):ifself.base.num_params==1:try:base_gen=qml.generator(self.base,format="observable")exceptoperation.GeneratorUndefinedErrorase:raiseoperation.ParameterFrequenciesUndefinedError(f"Operation {self.base.name} does not have parameter frequencies defined.")fromewithwarnings.catch_warnings():warnings.filterwarnings(action="ignore",message=r".+ eigenvalues will be computed numerically\.")base_gen_eigvals=qml.eigvals(base_gen,k=2**self.base.num_wires)# The projectors in the full generator add a eigenvalue of `0` to# the eigenvalues of the base generator.gen_eigvals=np.append(base_gen_eigvals,0)processed_gen_eigvals=tuple(np.round(gen_eigvals,8))return[qml.gradients.eigvals_to_frequencies(processed_gen_eigvals)]raiseoperation.ParameterFrequenciesUndefinedError(f"Operation {self.name} does not have parameter frequencies defined, ""and parameter frequencies can not be computed via generator for more than one ""parameter.")
# Program capture with controlled ops needs to unpack and re-pack the control wires to support dynamic wires# See capture module for more information on primitives# If None, jax isn't installed so the class never got a primitive.ifControlled._primitiveisnotNone:# pylint: disable=protected-access@Controlled._primitive.def_impl# pylint: disable=protected-accessdef_(base,*control_wires,control_values=None,work_wires=None,id=None):returntype.__call__(Controlled,base,control_wires,control_values=control_values,work_wires=work_wires,id=id,)# easier to just keep the same primitive for both versions# dispatch between the two types happens inside instance creation anywayControlledOp._primitive=Controlled._primitive# pylint: disable=protected-access