Source code for pennylane.measurements.measurements
# Copyright 2018-2021 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 module contains the functions for computing different types of measurementoutcomes from quantum observables - expectation values, variances of expectations,and measurement samples using AnnotatedQueues."""importcopyimportwarningsfromabcimportABC,abstractmethodfromcollections.abcimportSequencefromenumimportEnumfromtypingimportOptional,Unionimportpennylaneasqmlfrompennylane.math.utilsimportis_abstractfrompennylane.operationimportDecompositionUndefinedError,EigvalsUndefinedError,Operatorfrompennylane.pytreesimportregister_pytreefrompennylane.typingimportTensorLikefrompennylane.wiresimportWires# =============================================================================# ObservableReturnTypes types# =============================================================================classObservableReturnTypes(Enum):"""Enumeration class to represent the return types of an observable."""Sample="sample"Counts="counts"AllCounts="allcounts"Variance="var"Expectation="expval"Probability="probs"State="state"MidMeasure="measure"VnEntropy="vnentropy"MutualInfo="mutualinfo"Shadow="shadow"ShadowExpval="shadowexpval"Purity="purity"def__repr__(self):"""String representation of the return types."""returnstr(self.value)Sample=ObservableReturnTypes.Sample"""Enum: An enumeration which represents sampling an observable."""Counts=ObservableReturnTypes.Counts"""Enum: An enumeration which represents returning the number of times each of the observed outcomes occurred in sampling."""AllCounts=ObservableReturnTypes.AllCounts"""Enum: An enumeration which represents returning the number of times each of the possible outcomes occurred in sampling, including 0 counts for unobserved outcomes."""Variance=ObservableReturnTypes.Variance"""Enum: An enumeration which represents returning the variance ofan observable on specified wires."""Expectation=ObservableReturnTypes.Expectation"""Enum: An enumeration which represents returning the expectationvalue of an observable on specified wires."""Probability=ObservableReturnTypes.Probability"""Enum: An enumeration which represents returning probabilitiesof all computational basis states."""State=ObservableReturnTypes.State"""Enum: An enumeration which represents returning the state in the computational basis."""MidMeasure=ObservableReturnTypes.MidMeasure"""Enum: An enumeration which represents returning sampling the computationalbasis in the middle of the circuit."""VnEntropy=ObservableReturnTypes.VnEntropy"""Enum: An enumeration which represents returning Von Neumann entropy before measurements."""MutualInfo=ObservableReturnTypes.MutualInfo"""Enum: An enumeration which represents returning the mutual information before measurements."""Shadow=ObservableReturnTypes.Shadow"""Enum: An enumeration which represents returning the bitstrings and recipes fromthe classical shadow protocol"""ShadowExpval=ObservableReturnTypes.ShadowExpval"""Enum: An enumeration which represents returning the estimated expectation valuefrom a classical shadow measurement"""Purity=ObservableReturnTypes.Purity"""Enum: An enumeration which represents returning the purity of the system prior ot measurement."""classMeasurementShapeError(ValueError):"""An error raised when an unsupported operation is attempted with a quantum tape."""
[docs]classMeasurementProcess(ABC,metaclass=qml.capture.ABCCaptureMeta):"""Represents a measurement process occurring at the end of a quantum variational circuit. Args: obs (Union[.Operator, .MeasurementValue, Sequence[.MeasurementValue]]): The observable that is to be measured as part of the measurement process. Not all measurement processes require observables (for example ``Probability``); this argument is optional. wires (.Wires): The wires the measurement process applies to. This can only be specified if an observable was not provided. eigvals (array): A flat array representing the eigenvalues of the measurement. This can only be specified if an observable was not provided. id (str): custom label given to a measurement instance, can be useful for some applications where the instance has to be identified """# pylint:disable=too-many-instance-attributes_shortname=None_obs_primitive:Optional["jax.core.Primitive"]=None_wires_primitive:Optional["jax.core.Primitive"]=None_mcm_primitive:Optional["jax.core.Primitive"]=Nonedef__init_subclass__(cls,**_):register_pytree(cls,cls._flatten,cls._unflatten)name=getattr(cls._shortname,"value",cls.__name__)cls._wires_primitive=qml.capture.create_measurement_wires_primitive(cls,name=name)cls._obs_primitive=qml.capture.create_measurement_obs_primitive(cls,name=name)cls._mcm_primitive=qml.capture.create_measurement_mcm_primitive(cls,name=name)@classmethoddef_primitive_bind_call(cls,obs=None,wires=None,eigvals=None,id=None,**kwargs):"""Called instead of ``type.__call__`` if ``qml.capture.enabled()``. Measurements have three "modes": 1) Wires or wires + eigvals 2) Observable 3) Mid circuit measurements Not all measurements support all three modes. For example, ``VNEntropyMP`` does not allow being specified via an observable. But we handle the generic case here. """ifcls._obs_primitiveisNone:# safety check if primitives aren't set correctly.returntype.__call__(cls,obs=obs,wires=wires,eigvals=eigvals,id=id,**kwargs)ifobsisNone:wires=()ifwiresisNoneelsewiresifeigvalsisNone:returncls._wires_primitive.bind(*wires,**kwargs)# wiresreturncls._wires_primitive.bind(*wires,eigvals,has_eigvals=True,**kwargs)# wires + eigvalsifisinstance(obs,Operator)orisinstance(getattr(obs,"aval",None),qml.capture.AbstractOperator):returncls._obs_primitive.bind(obs,**kwargs)ifisinstance(obs,(list,tuple)):returncls._mcm_primitive.bind(*obs,single_mcm=False,**kwargs)# iterable of mcmsreturncls._mcm_primitive.bind(obs,single_mcm=True,**kwargs)# single mcm# pylint: disable=unused-argument@classmethoddef_abstract_eval(cls,n_wires:Optional[int]=None,has_eigvals=False,shots:Optional[int]=None,num_device_wires:int=0,)->tuple[tuple,type]:"""Calculate the shape and dtype that will be returned when a measurement is performed. This information is similar to ``numeric_type`` and ``shape``, but is provided through a class method and does not require the creation of an instance. Note that ``shots`` should strictly be ``None`` or ``int``. Shot vectors are handled higher in the stack. If ``n_wires is None``, then the measurement process contains an observable. An integer ``n_wires`` can correspond either to the number of wires or to the number of mid circuit measurements. ``n_wires = 0`` indicates a measurement that is broadcasted across all device wires. >>> ProbabilityMP._abstract_eval(n_wires=2) ((4,), float) >>> ProbabilityMP._abstract_eval(n_wires=0, num_device_wires=2) ((4,), float) >>> SampleMP._abstract_eval(n_wires=0, shots=50, num_device_wires=2) ((50, 2), int) >>> SampleMP._abstract_eval(n_wires=4, has_eigvals=True, shots=50) ((50,), float) >>> SampleMP._abstract_eval(n_wires=None, shots=50) ((50,), float) """return(),floatdef_flatten(self):metadata=(("wires",self.raw_wires),)return(self.obsorself.mv,self._eigvals),metadata@classmethoddef_unflatten(cls,data,metadata):ifdata[0]isnotNone:returncls(obs=data[0],**dict(metadata))ifdata[1]isnotNone:returncls(eigvals=data[1],**dict(metadata))returncls(**dict(metadata))# pylint: disable=too-many-argumentsdef__init__(self,obs:Optional[Union[Operator,"qml.measurements.MeasurementValue",Sequence["qml.measurements.MeasurementValue"],]]=None,wires:Optional[Wires]=None,eigvals:Optional[TensorLike]=None,id:Optional[str]=None,):ifgetattr(obs,"name",None)=="MeasurementValue"orisinstance(obs,Sequence):# Cast sequence of measurement values to listself.mv=obsifgetattr(obs,"name",None)=="MeasurementValue"elselist(obs)self.obs=Noneelifis_abstract(obs):# Catalyst program with qml.sample(m, wires=i)self.mv=obsself.obs=Noneelse:self.obs=obsself.mv=Noneself.id=idifwiresisnotNone:ifnotqml.capture.enabled()andlen(wires)==0:raiseValueError("Cannot set an empty list of wires.")ifobsisnotNone:raiseValueError("Cannot set the wires if an observable is provided.")# _wires = None indicates broadcasting across all available wires.# It translates to the public property wires = Wires([])self._wires=wiresself._eigvals=NoneifeigvalsisnotNone:ifobsisnotNone:raiseValueError("Cannot set the eigenvalues if an observable is provided.")self._eigvals=qml.math.asarray(eigvals)# Queue the measurement processself.queue()@propertydefreturn_type(self)->Optional[ObservableReturnTypes]:"""Measurement return type."""warnings.warn("MeasurementProcess property return_type is deprecated and will be removed in version 0.42. ""Instead, please use isinstance for type checking directly.",qml.PennyLaneDeprecationWarning,)returnself._shortname@propertydefnumeric_type(self)->type:"""The Python numeric type of the measurement result. Returns: type: The output numeric type; ``int``, ``float`` or ``complex``. Raises: QuantumFunctionError: the return type of the measurement process is unrecognized and cannot deduce the numeric type """raiseqml.QuantumFunctionError(f"The numeric type of the measurement {self.__class__.__name__} is not defined.")
[docs]defshape(self,shots:Optional[int]=None,num_device_wires:int=0)->tuple[int,...]:"""Calculate the shape of the result object tensor. Args: shots (Optional[int]) = None: the number of shots used execute the circuit. ``None`` indicates an analytic simulation. Shot vectors are handled by calling this method multiple times. num_device_wires (int)=0 : The number of wires that will be used if the measurement is broadcasted across all available wires (``len(mp.wires) == 0``). If the device itself doesn't provide a number of wires, the number of tape wires will be provided here instead: Returns: tuple[int,...]: An arbitrary length tuple of ints. May be an empty tuple. >>> qml.probs(wires=(0,1)).shape() (4,) >>> qml.sample(wires=(0,1)).shape(shots=50) (50, 2) >>> qml.state().shape(num_device_wires=4) (16,) >>> qml.expval(qml.Z(0)).shape() () """raiseqml.QuantumFunctionError(f"The shape of the measurement {self.__class__.__name__} is not defined")
[docs]@qml.QueuingManager.stop_recording()defdiagonalizing_gates(self):"""Returns the gates that diagonalize the measured wires such that they are in the eigenbasis of the circuit observables. Returns: List[.Operation]: the operations that diagonalize the observables """returnself.obs.diagonalizing_gates()ifself.obselse[]
def__eq__(self,other):returnqml.equal(self,other)def__hash__(self):returnself.hashdef__repr__(self):"""Representation of this class."""ifself._shortname:ifisinstance(self._shortname,str):name_str=self._shortnameelse:name_str=self._shortname.valueelse:name_str=type(self).__name__ifself.mvisnotNone:returnf"{name_str}({repr(self.mv)})"ifself.obs:returnf"{name_str}({self.obs})"ifself._eigvalsisnotNone:returnf"{name_str}(eigvals={self._eigvals}, wires={self.wires.tolist()})"# Todo: when tape is core the return type will always be taken from the MeasurementProcessreturnf"{getattr(self._shortname,'value','None')}(wires={self.wires.tolist()})"def__copy__(self):cls=self.__class__copied_m=cls.__new__(cls)forattr,valueinvars(self).items():setattr(copied_m,attr,value)ifself.obsisnotNone:copied_m.obs=copy.copy(self.obs)returncopied_m@propertydefwires(self):r"""The wires the measurement process acts on. This is the union of all the Wires objects of the measurement. """ifself.mvisnotNoneandnotis_abstract(self.mv):ifisinstance(self.mv,list):returnqml.wires.Wires.all_wires([m.wiresforminself.mv])returnself.mv.wiresifself.obsisnotNone:returnself.obs.wiresreturn(Wires.all_wires(self._wires)ifisinstance(self._wires,(tuple,list))elseself._wiresorWires([]))@propertydefraw_wires(self):r"""The wires the measurement process acts on. For measurements involving more than one set of wires (such as mutual information), this is a list of the Wires objects. Otherwise, this is the same as :func:`~.MeasurementProcess.wires` """returnself._wires
[docs]defeigvals(self):r"""Eigenvalues associated with the measurement process. If the measurement process has an associated observable, the eigenvalues will correspond to this observable. Otherwise, they will be the eigenvalues provided when the measurement process was instantiated. Note that the eigenvalues are not guaranteed to be in any particular order. **Example:** >>> m = MeasurementProcess(Expectation, obs=qml.X(1)) >>> m.eigvals() array([1, -1]) Returns: array: eigvals representation """ifself.mvisnotNone:ifgetattr(self.mv,"name",None)=="MeasurementValue":# "Eigvals" should be the processed values for all branches of a MeasurementValue_,processed_values=tuple(zip(*self.mv.items()))interface=qml.math.get_deep_interface(processed_values)returnqml.math.asarray(processed_values,like=interface)returnqml.math.arange(0,2**len(self.wires),1)ifself.obsisnotNone:try:returnqml.eigvals(self.obs)exceptDecompositionUndefinedErrorase:raiseEigvalsUndefinedErrorfromereturnself._eigvals
@propertydefhas_decomposition(self):r"""Bool: Whether or not the MeasurementProcess returns a defined decomposition when calling ``expand``. """# If self.obs is not None, `expand` queues the diagonalizing gates of self.obs,# which we have to check to be defined. The subsequent creation of the new# `MeasurementProcess` within `expand` should never fail with the given parameters.returnself.obs.has_diagonalizing_gatesifself.obsisnotNoneelseFalse@propertydefsamples_computational_basis(self):r"""Bool: Whether or not the MeasurementProcess measures in the computational basis."""returnself.obsisNone
[docs]defexpand(self):"""Expand the measurement of an observable to a unitary rotation and a measurement in the computational basis. Returns: .QuantumTape: a quantum tape containing the operations required to diagonalize the observable **Example:** Consider a measurement process consisting of the expectation value of an Hermitian observable: >>> H = np.array([[1, 2], [2, 4]]) >>> obs = qml.Hermitian(H, wires=['a']) >>> m = MeasurementProcess(Expectation, obs=obs) Expanding this out: >>> tape = m.expand() We can see that the resulting tape has the qubit unitary applied, and a measurement process with no observable, but the eigenvalues specified: >>> print(tape.operations) [QubitUnitary(array([[-0.89442719, 0.4472136 ], [ 0.4472136 , 0.89442719]]), wires=['a'])] >>> print(tape.measurements[0].eigvals()) [0. 5.] >>> print(tape.measurements[0].obs) None """ifself.obsisNone:raiseqml.operation.DecompositionUndefinedErrorwithqml.queuing.AnnotatedQueue()asq:self.obs.diagonalizing_gates()self.__class__(wires=self.obs.wires,eigvals=self.obs.eigvals())returnqml.tape.QuantumScript.from_queue(q)
[docs]defqueue(self,context=qml.QueuingManager):"""Append the measurement process to an annotated queue."""ifself.obsisnotNone:context.remove(self.obs)context.append(self)returnself
@propertydef_queue_category(self):"""Denotes that `MeasurementProcess` objects should be processed into the `_measurements` list in `QuantumTape` objects. This property is a temporary solution that should not exist long-term and should not be used outside of ``QuantumTape._process_queue``. """return"_measurements"@propertydefhash(self):"""int: returns an integer hash uniquely representing the measurement process"""fingerprint=(self.__class__.__name__,getattr(self.obs,"hash","None"),getattr(self.mv,"hash","None"),str(self._eigvals),# eigvals() could be expensive to compute for large observablestuple(self.wires.tolist()),)returnhash(fingerprint)
[docs]defsimplify(self):"""Reduce the depth of the observable to the minimum. Returns: .MeasurementProcess: A measurement process with a simplified observable. """returnselfifself.obsisNoneelseself.__class__(obs=self.obs.simplify())
# pylint: disable=protected-access
[docs]defmap_wires(self,wire_map:dict):"""Returns a copy of the current measurement process with its wires changed according to the given wire map. Args: wire_map (dict): dictionary containing the old wires as keys and the new wires as values Returns: .MeasurementProcess: new measurement process """new_measurement=copy.copy(self)ifself.mvisnotNone:new_measurement.mv=(self.mv.map_wires(wire_map=wire_map)ifgetattr(self.mv,"name",None)=="MeasurementValue"else[m.map_wires(wire_map=wire_map)forminself.mv])elifself.obsisnotNone:new_measurement.obs=self.obs.map_wires(wire_map=wire_map)elifself._wiresisnotNone:new_measurement._wires=Wires([wire_map.get(wire,wire)forwireinself.wires])returnnew_measurement
[docs]classSampleMeasurement(MeasurementProcess):"""Sample-based measurement process. Any class inheriting from ``SampleMeasurement`` should define its own ``process_samples`` method, which should have the following arguments: * samples (Sequence[complex]): computational basis samples generated for all wires * wire_order (Wires): wires determining the subspace that ``samples`` acts on * shot_range (tuple[int]): 2-tuple of integers specifying the range of samples to use. If not specified, all samples are used. * bin_size (int): Divides the shot range into bins of size ``bin_size``, and returns the measurement statistic separately over each bin. If not provided, the entire shot range is treated as a single bin. **Example:** Let's create a measurement that returns the sum of all samples of the given wires. >>> class MyMeasurement(SampleMeasurement): ... def process_samples(self, samples, wire_order, shot_range=None, bin_size=None): ... return qml.math.sum(samples[..., self.wires]) We can now execute it in a QNode: >>> dev = qml.device("default.qubit", wires=2, shots=1000) >>> @qml.qnode(dev) ... def circuit(): ... qml.X(0) ... return MyMeasurement(wires=[0]), MyMeasurement(wires=[1]) >>> circuit() (tensor(1000, requires_grad=True), tensor(0, requires_grad=True)) """_shortname=Sample#! Note: deprecated. Change the value to "sample" in v0.42
[docs]@abstractmethoddefprocess_samples(self,samples:Sequence[complex],wire_order:Wires,shot_range:tuple[int]=None,bin_size:int=None,):"""Process the given samples. Args: samples (Sequence[complex]): computational basis samples generated for all wires wire_order (Wires): wires determining the subspace that ``samples`` acts on shot_range (tuple[int]): 2-tuple of integers specifying the range of samples to use. If not specified, all samples are used. bin_size (int): Divides the shot range into bins of size ``bin_size``, and returns the measurement statistic separately over each bin. If not provided, the entire shot range is treated as a single bin. """
[docs]@abstractmethoddefprocess_counts(self,counts:dict,wire_order:Wires):"""Calculate the measurement given a counts histogram dictionary. Args: counts (dict): a dictionary matching the format returned by :class:`~.CountsMP` wire_order (Wires): the wire order used in producing the counts Note that the input dictionary may only contain states with non-zero entries (``all_outcomes=False``). """
[docs]classStateMeasurement(MeasurementProcess):"""State-based measurement process. Any class inheriting from ``StateMeasurement`` should define its own ``process_state`` method, which should have the following arguments: * state (Sequence[complex]): quantum state with a flat shape. It may also have an optional batch dimension * wire_order (Wires): wires determining the subspace that ``state`` acts on; a matrix of dimension :math:`2^n` acts on a subspace of :math:`n` wires **Example:** Let's create a measurement that returns the diagonal of the reduced density matrix. >>> class MyMeasurement(StateMeasurement): ... def process_state(self, state, wire_order): ... # use the already defined `qml.density_matrix` measurement to compute the ... # reduced density matrix from the given state ... density_matrix = qml.density_matrix(wires=self.wires).process_state(state, wire_order) ... return qml.math.diagonal(qml.math.real(density_matrix)) We can now execute it in a QNode: >>> dev = qml.device("default.qubit", wires=2) >>> @qml.qnode(dev) ... def circuit(): ... qml.Hadamard(0) ... qml.CNOT([0, 1]) ... return MyMeasurement(wires=[0]) >>> circuit() tensor([0.5, 0.5], requires_grad=True) """
[docs]@abstractmethoddefprocess_state(self,state:Sequence[complex],wire_order:Wires):"""Process the given quantum state. Args: state (Sequence[complex]): quantum state with a flat shape. It may also have an optional batch dimension wire_order (Wires): wires determining the subspace that ``state`` acts on; a matrix of dimension :math:`2^n` acts on a subspace of :math:`n` wires """
[docs]defprocess_density_matrix(self,density_matrix:TensorLike,wire_order:Wires):""" Process the given density matrix. Args: density_matrix (TensorLike): The density matrix representing the (mixed) quantum state, which may be single or batched. For a single matrix, the shape should be ``(2^n, 2^n)`` where `n` is the number of wires the matrix acts upon. For batched matrices, the shape should be ``(batch_size, 2^n, 2^n)``. wire_order (Wires): The wires determining the subspace that the ``density_matrix`` acts on. A matrix of dimension :math:`2^n` acts on a subspace of :math:`n` wires. This parameter specifies the mapping of matrix dimensions to physical qubits, allowing the function to correctly trace out the subsystems not involved in the measurement or operation. """raiseNotImplementedError
[docs]classMeasurementTransform(MeasurementProcess):"""Measurement process that applies a transform into the given quantum tape. This transform is carried out inside the gradient black box, thus is not tracked by the gradient transform. Any class inheriting from ``MeasurementTransform`` should define its own ``process`` method, which should have the following arguments: * tape (QuantumTape): quantum tape to transform * device (pennylane.devices.LegacyDevice): device used to transform the quantum tape """
[docs]@abstractmethoddefprocess(self,tape,device):"""Process the given quantum tape. Args: tape (QuantumTape): quantum tape to transform device (pennylane.devices.LegacyDevice): device used to transform the quantum tape """