# Copyright 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."""The null.qubit device is a no-op device, useful for resource estimation, and forbenchmarking PennyLane's auxiliary functionality outside direct circuit evaluations."""# pylint:disable=unused-argumentimportinspectimportloggingfromdataclassesimportreplacefromfunctoolsimportsingledispatchfromnumbersimportNumberfromtypingimportOptional,Unionimportnumpyasnpfrompennylaneimportmathfrompennylane.devices.execution_configimportExecutionConfigfrompennylane.devices.modifiersimportsimulator_tracking,single_tape_supportfrompennylane.measurementsimport(ClassicalShadowMP,CountsMP,DensityMatrixMP,MeasurementProcess,MeasurementValue,ProbabilityMP,StateMP,)frompennylane.tapeimportQuantumScriptOrBatchfrompennylane.transforms.coreimportTransformProgramfrompennylane.typingimportResult,ResultBatchfrom.importDefaultQubit,Devicefrom.execution_configimportDefaultExecutionConfig,ExecutionConfigfrom.preprocessimportdecomposelogger=logging.getLogger(__name__)logger.addHandler(logging.NullHandler())
[docs]@singledispatchdefzero_measurement(mp:MeasurementProcess,num_device_wires,shots:Optional[int],batch_size:int,interface:str):"""Create all-zero results for various measurement processes."""return_zero_measurement(mp,num_device_wires,shots,batch_size,interface)
def_zero_measurement(mp:MeasurementProcess,num_device_wires:int,shots:Optional[int],batch_size,interface):shape=mp.shape(shots,num_device_wires)ifbatch_sizeisnotNone:shape=(batch_size,)+shapereturnmath.zeros(shape,like=interface,dtype=mp.numeric_type)@zero_measurement.registerdef_(mp:ClassicalShadowMP,num_device_wires,shots:Optional[int],batch_size,interface):ifbatch_sizeisnotNone:# shapes = [(batch_size,) + shape for shape in shapes]raiseValueError("Parameter broadcasting is not supported with null.qubit and qml.classical_shadow")shape=mp.shape(shots,num_device_wires)returnmath.zeros(shape,like=interface,dtype=np.int8)@zero_measurement.registerdef_(mp:CountsMP,num_device_wires,shots,batch_size,interface):outcomes=[]ifmp.obsisNoneandnotisinstance(mp.mv,MeasurementValue):state="0"*num_device_wiresresults={state:math.asarray(shots,like=interface)}ifmp.all_outcomes:outcomes=[f"{x:0{num_device_wires}b}"forxinrange(1,2**num_device_wires)]else:outcomes=sorted(mp.eigvals())# always assign shots to the smallestresults={outcomes[0]:math.asarray(shots,like=interface)}outcomes=outcomes[1:]ifmp.all_outcomeselse[]ifoutcomes:zero=math.asarray(0,like=interface)forvalinoutcomes:results[val]=zeroifbatch_sizeisnotNone:results=tuple(resultsfor_inrange(batch_size))returnresultszero_measurement.register(DensityMatrixMP)(_zero_measurement)@zero_measurement.register(StateMP)@zero_measurement.register(ProbabilityMP)def_(mp:Union[StateMP,ProbabilityMP],num_device_wires:int,shots:Optional[int],batch_size,interface,):num_wires=len(mp.wires)ornum_device_wiresstate=[1.0]+[0.0]*(2**num_wires-1)ifbatch_sizeisnotNone:state=[state]*batch_sizereturnmath.asarray(state,like=interface)def_interface(config:ExecutionConfig):returnconfig.interface.get_like()ifconfig.gradient_method=="backprop"else"numpy"
[docs]@simulator_tracking@single_tape_supportclassNullQubit(Device):"""Null qubit device for PennyLane. This device performs no operations involved in numerical calculations. Instead the time spent in execution is dominated by support (or setting up) operations, like tape creation etc. Args: wires (int, Iterable[Number, str]): Number of wires present on the device, or iterable that contains unique labels for the wires as numbers (i.e., ``[-1, 0, 2]``) or strings (``['aux_wire', 'q1', 'q2']``). Default ``None`` if not specified. shots (int, Sequence[int], Sequence[Union[int, Sequence[int]]]): The default number of shots to use in executions involving this device. **Example:** .. code-block:: python qs = qml.tape.QuantumScript( [qml.Hadamard(0), qml.CNOT([0, 1])], [qml.expval(qml.PauliZ(0)), qml.probs()], ) qscripts = [qs, qs, qs] >>> dev = NullQubit() >>> program, execution_config = dev.preprocess() >>> new_batch, post_processing_fn = program(qscripts) >>> results = dev.execute(new_batch, execution_config=execution_config) >>> post_processing_fn(results) ((array(0.), array([1., 0., 0., 0.])), (array(0.), array([1., 0., 0., 0.])), (array(0.), array([1., 0., 0., 0.]))) This device currently supports (trivial) derivatives: >>> from pennylane.devices import ExecutionConfig >>> dev.supports_derivatives(ExecutionConfig(gradient_method="device")) True This device can be used to track resource usage: .. code-block:: python n_layers = 50 n_wires = 100 shape = qml.StronglyEntanglingLayers.shape(n_layers=n_layers, n_wires=n_wires) @qml.qnode(dev) def circuit(params): qml.StronglyEntanglingLayers(params, wires=range(n_wires)) return [qml.expval(qml.Z(i)) for i in range(n_wires)] params = np.random.random(shape) with qml.Tracker(dev) as tracker: circuit(params) >>> tracker.history["resources"][0] num_wires: 100 num_gates: 10000 depth: 502 shots: Shots(total=None) gate_types: {'Rot': 5000, 'CNOT': 5000} gate_sizes: {1: 5000, 2: 5000} .. details:: :title: Tracking ``NullQubit`` tracks: * ``executions``: the number of unique circuits that would be required on quantum hardware * ``shots``: the number of shots * ``resources``: the :class:`~.resource.Resources` for the executed circuit. * ``simulations``: the number of simulations performed. One simulation can cover multiple QPU executions, such as for non-commuting measurements and batched parameters. * ``batches``: The number of times :meth:`~.execute` is called. * ``results``: The results of each call of :meth:`~.execute` * ``derivative_batches``: How many times :meth:`~.compute_derivatives` is called. * ``execute_and_derivative_batches``: How many times :meth:`~.execute_and_compute_derivatives` is called * ``vjp_batches``: How many times :meth:`~.compute_vjp` is called * ``execute_and_vjp_batches``: How many times :meth:`~.execute_and_compute_vjp` is called * ``jvp_batches``: How many times :meth:`~.compute_jvp` is called * ``execute_and_jvp_batches``: How many times :meth:`~.execute_and_compute_jvp` is called * ``derivatives``: How many circuits are submitted to :meth:`~.compute_derivatives` or :meth:`~.execute_and_compute_derivatives`. * ``vjps``: How many circuits are submitted to :meth:`~.compute_vjp` or :meth:`~.execute_and_compute_vjp` * ``jvps``: How many circuits are submitted to :meth:`~.compute_jvp` or :meth:`~.execute_and_compute_jvp` """@propertydefname(self):"""The name of the device."""return"null.qubit"def__init__(self,wires=None,shots=None)->None:super().__init__(wires=wires,shots=shots)self._debugger=Nonedef_simulate(self,circuit,interface):num_device_wires=len(self.wires)ifself.wireselselen(circuit.wires)results=[]forsincircuit.shotsor[None]:r=tuple(zero_measurement(mp,num_device_wires,s,circuit.batch_size,interface)formpincircuit.measurements)results.append(r[0]iflen(circuit.measurements)==1elser)ifcircuit.shots.has_partitioned_shots:returntuple(results)returnresults[0]def_derivatives(self,circuit,interface):shots=circuit.shotsnum_device_wires=len(self.wires)ifself.wireselselen(circuit.wires)n=len(circuit.trainable_params)derivatives=tuple((math.zeros_like(zero_measurement(mp,num_device_wires,shots,circuit.batch_size,interface)),)*nformpincircuit.measurements)ifn==1:derivatives=tuple(d[0]fordinderivatives)returnderivatives[0]iflen(derivatives)==1elsederivatives@staticmethoddef_vjp(circuit,interface):batch_size=circuit.batch_sizen=len(circuit.trainable_params)res_shape=(n,)ifbatch_sizeisNoneelse(n,batch_size)returnmath.zeros(res_shape,like=interface)@staticmethoddef_jvp(circuit,interface):jvps=(math.asarray(0.0,like=interface),)*len(circuit.measurements)returnjvps[0]iflen(jvps)==1elsejvps@staticmethoddef_setup_execution_config(execution_config:ExecutionConfig)->ExecutionConfig:"""No-op function to allow for borrowing DefaultQubit.preprocess without AttributeErrors"""returnexecution_config@propertydef_max_workers(self):"""No-op property to allow for borrowing DefaultQubit.preprocess without AttributeErrors"""returnNone# pylint: disable=cell-var-from-loop
[docs]defexecute(self,circuits:QuantumScriptOrBatch,execution_config:ExecutionConfig=DefaultExecutionConfig,)->Union[Result,ResultBatch]:iflogger.isEnabledFor(logging.DEBUG):# pragma: no coverlogger.debug("""Entry with args=(circuits=%s) called by=%s""",circuits,"::L".join(str(i)foriininspect.getouterframes(inspect.currentframe(),2)[1][1:3]),)returntuple(self._simulate(c,_interface(execution_config))forcincircuits)