# Copyright 2018-2024 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."""Defines a LegacyDeviceFacade class for converting legacy devices to thenew interface."""# pylint: disable=not-callable, unused-argumentfromcontextlibimportcontextmanagerfromcopyimportcopy,deepcopyfromdataclassesimportreplaceimportpennylaneasqmlfrompennylane.mathimportget_canonical_interface_namefrompennylane.measurementsimportMidMeasureMP,Shotsfrompennylane.transforms.core.transform_programimportTransformProgramfrom.device_apiimportDevicefrom.execution_configimportDefaultExecutionConfigfrom.modifiersimportsingle_tape_supportfrom.preprocessimport(decompose,no_sampling,validate_adjoint_trainable_params,validate_measurements,)def_requests_adjoint(execution_config):returnexecution_config.gradient_method=="adjoint"or(execution_config.gradient_method=="device"andexecution_config.gradient_keyword_arguments.get("method",None)=="adjoint_jacobian")@contextmanagerdef_set_shots(device,shots):"""Context manager to temporarily change the shots of a device. This context manager can be used in two ways. As a standard context manager: >>> with _set_shots(dev, shots=100): ... print(dev.shots) 100 >>> print(dev.shots) None Or as a decorator that acts on a function that uses the device: >>> _set_shots(dev, shots=100)(lambda: dev.shots)() 100 """shots=qml.measurements.Shots(shots)shots=shots.shot_vectorifshots.has_partitioned_shotselseshots.total_shotsifshots==device.shots:yieldreturnoriginal_shots=device.shotsoriginal_shot_vector=device._shot_vector# pylint: disable=protected-accesstry:device.shots=shotsyieldfinally:device.shots=original_shotsdevice._shot_vector=original_shot_vector# pylint: disable=protected-accessdefnull_postprocessing(results):"""A postprocessing function with null behavior."""returnresults[0]@qml.transformdeflegacy_device_expand_fn(tape,device):"""Turn the ``expand_fn`` from the legacy device interface into a transform."""new_tape=_set_shots(device,tape.shots)(device.expand_fn)(tape)return(new_tape,),null_postprocessing@qml.transformdeflegacy_device_batch_transform(tape,device):"""Turn the ``batch_transform`` from the legacy device interface into a transform."""return_set_shots(device,tape.shots)(device.batch_transform)(tape)defadjoint_ops(op:qml.operation.Operator)->bool:"""Specify whether or not an Operator is supported by adjoint differentiation."""ifisinstance(op,qml.QubitUnitary)andnotqml.operation.is_trainable(op):returnTruereturnnotisinstance(op,MidMeasureMP)and(op.num_params==0or(op.num_params==1andop.has_generator))def_add_adjoint_transforms(program:TransformProgram,name="adjoint"):"""Add the adjoint specific transforms to the transform program."""program.add_transform(no_sampling,name=name)program.add_transform(decompose,stopping_condition=adjoint_ops,name=name,)defaccepted_adjoint_measurements(mp):returnisinstance(mp,qml.measurements.ExpectationMP)program.add_transform(validate_measurements,analytic_measurements=accepted_adjoint_measurements,name=name,)program.add_transform(qml.transforms.broadcast_expand)program.add_transform(validate_adjoint_trainable_params)
[docs]@single_tape_supportclassLegacyDeviceFacade(Device):""" A Facade that converts a device from the old ``qml.Device`` interface into the new interface. Args: device (qml.device.LegacyDevice): a device that follows the legacy device interface. >>> from pennylane.devices import DefaultQutrit, LegacyDeviceFacade >>> legacy_dev = DefaultQutrit(wires=2) >>> new_dev = LegacyDeviceFacade(legacy_dev) >>> new_dev.preprocess() (TransformProgram(legacy_device_batch_transform, legacy_device_expand_fn, defer_measurements), ExecutionConfig(grad_on_execution=None, use_device_gradient=None, use_device_jacobian_product=None, gradient_method=None, gradient_keyword_arguments={}, device_options={}, interface=<Interface.NUMPY: 'numpy'>, derivative_order=1, mcm_config=MCMConfig(mcm_method=None, postselect_mode=None))) >>> new_dev.shots Shots(total_shots=None, shot_vector=()) >>> tape = qml.tape.QuantumScript([], [qml.sample(wires=0)], shots=5) >>> new_dev.execute(tape) array([0, 0, 0, 0, 0]) """# pylint: disable=super-init-not-calleddef__init__(self,device:"qml.devices.LegacyDevice"):ifisinstance(device,type(self)):raiseRuntimeError("An already-facaded device can not be wrapped in a facade again.")ifnotisinstance(device,qml.devices.LegacyDevice):raiseValueError("The LegacyDeviceFacade only accepts a device of type qml.devices.LegacyDevice.")self._device=deviceself.config_filepath=getattr(self._device,"config_filepath",None)@propertydeftracker(self):"""A :class:`~.Tracker` that can store information about device executions, shots, batches, intermediate results, or any additional device dependent information. """returnself._device.tracker@tracker.setterdeftracker(self,new_tracker):self._device.tracker=new_tracker@propertydefname(self)->str:returnself._device.short_namedef__repr__(self):returnf"<LegacyDeviceFacade: {repr(self._device)}>"def__getattr__(self,name):returngetattr(self._device,name)# These custom copy methods are needed for Catalystdef__copy__(self):returntype(self)(copy(self.target_device))def__deepcopy__(self,memo):returntype(self)(deepcopy(self.target_device,memo))@propertydeftarget_device(self)->"qml.devices.LegacyDevice":"""The device wrapped by the facade."""returnself._device@propertydefwires(self)->qml.wires.Wires:returnself._device.wires# pylint: disable=protected-access@propertydefshots(self)->Shots:ifself._device._shot_vector:returnShots(self._device._raw_shot_sequence)returnShots(self._device.shots)@propertydef_debugger(self):returnself._device._debugger@_debugger.setterdef_debugger(self,new_debugger):self._device._debugger=new_debugger
def_setup_backprop_config(self,execution_config):tape=qml.tape.QuantumScript()ifnotself._validate_backprop_method(tape):raiseqml.DeviceError("device does not support backprop.")ifexecution_config.use_device_gradientisNone:returnreplace(execution_config,use_device_gradient=True)returnexecution_configdef_setup_adjoint_config(self,execution_config):tape=qml.tape.QuantumScript([],[])ifnotself._validate_adjoint_method(tape):raiseqml.DeviceError("device does not support device derivatives")updated_values={"gradient_keyword_arguments":{"use_device_state":True,"method":"adjoint_jacobian"}}ifexecution_config.use_device_gradientisNone:updated_values["use_device_gradient"]=Trueifexecution_config.grad_on_executionisNone:updated_values["grad_on_execution"]=Truereturnreplace(execution_config,**updated_values)def_setup_device_config(self,execution_config):tape=qml.tape.QuantumScript([],[])ifnotself._validate_device_method(tape):raiseqml.DeviceError("device does not support device derivatives")updated_values={}ifexecution_config.use_device_gradientisNone:updated_values["use_device_gradient"]=Trueifexecution_config.grad_on_executionisNone:updated_values["grad_on_execution"]=Truereturnreplace(execution_config,**updated_values)# pylint: disable=too-many-return-statementsdef_setup_execution_config(self,execution_config):ifexecution_config.gradient_method=="best":tape=qml.tape.QuantumScript([],[])ifself._validate_device_method(tape):config=replace(execution_config,gradient_method="device")returnself._setup_execution_config(config)ifself._validate_backprop_method(tape):config=replace(execution_config,gradient_method="backprop")returnself._setup_backprop_config(config)ifexecution_config.gradient_method=="backprop":returnself._setup_backprop_config(execution_config)if_requests_adjoint(execution_config):returnself._setup_adjoint_config(execution_config)ifexecution_config.gradient_method=="device":returnself._setup_device_config(execution_config)returnexecution_config
def_validate_backprop_method(self,tape):iftape.shots:returnFalseparams=tape.get_parameters(trainable_only=False)interface=qml.math.get_interface(*params)ifinterface!="numpy":interface=get_canonical_interface_name(interface).valueiftapeandany(isinstance(m.obs,qml.SparseHamiltonian)formintape.measurements):returnFalse# determine if the device supports backpropagationbackprop_interface=self._device.capabilities().get("passthru_interface",None)ifbackprop_interfaceisnotNone:# device supports backpropagation nativelyreturninterfacein[backprop_interface,"numpy"]# determine if the device has any child devices that support backpropagationbackprop_devices=self._device.capabilities().get("passthru_devices",None)ifbackprop_devicesisNone:returnFalsereturninterfaceinbackprop_devicesorinterface=="numpy"def_validate_adjoint_method(self,tape):# The conditions below provide a minimal set of requirements that we can likely improve upon in# future, or alternatively summarize within a single device capability. Moreover, we also# need to inspect the circuit measurements to ensure only expectation values are taken. This# cannot be done here since we don't yet know the composition of the circuit.required_attrs=["_apply_operation","_apply_unitary","adjoint_jacobian"]supported_device=all(hasattr(self._device,attr)forattrinrequired_attrs)supported_device=supported_deviceandself._device.capabilities().get("returns_state")ifnotsupported_deviceorbool(tape.shots):returnFalseprogram=TransformProgram()_add_adjoint_transforms(program,name=f"{self.name} + adjoint")try:program((tape,))except(qml.operation.DecompositionUndefinedError,qml.DeviceError,AttributeError):returnFalsereturnTruedef_validate_device_method(self,_):# determine if the device provides its own jacobian methodreturnself._device.capabilities().get("provides_jacobian",False)