def access_state(self, wires=None): """Check that the device has access to an internal state and return it if available. Args: wires (Wires): wires of the reduced system Raises: QuantumFunctionError: if the device is not capable of returning the state Returns: array or tensor: the state or the density matrix of the device """ if not self.capabilities().get("returns_state"): raise QuantumFunctionError( "The current device is not capable of returning the state") state = getattr(self, "state", None) if state is None: raise QuantumFunctionError( "The state is not available in the current device") if wires: density_matrix = self.density_matrix(wires) return density_matrix return state
def statistics(self, observables): """Process measurement results from circuit execution and return statistics. This includes returning expectation values, variance, samples, probabilities, states, and density matrices. Args: observables (List[.Observable]): the observables to be measured Raises: QuantumFunctionError: if the value of :attr:`~.Observable.return_type` is not supported Returns: Union[float, List[float]]: the corresponding statistics """ results = [] for obs in observables: # Pass instances directly if obs.return_type is Expectation: results.append(self.expval(obs)) elif obs.return_type is Variance: results.append(self.var(obs)) elif obs.return_type is Sample: results.append(self.sample(obs)) elif obs.return_type is Probability: results.append(self.probability(wires=obs.wires)) elif obs.return_type is State: if len(observables) > 1: raise QuantumFunctionError( "The state or density matrix cannot be returned in combination" " with other return types") if self.wires.labels != tuple(range(self.num_wires)): raise QuantumFunctionError( "Returning the state is not supported when using custom wire labels" ) # Check if the state is accessible and decide to return the state or the density # matrix. results.append(self.access_state(wires=obs.wires)) elif obs.return_type is not None: raise QuantumFunctionError( "Unsupported return type specified for observable {}". format(obs.name)) return results
def var(op): r"""Variance of the supplied observable. **Example:** .. code-block:: python3 dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) def circuit(x): qml.RX(x, wires=0) qml.Hadamard(wires=1) qml.CNOT(wires=[0, 1]) return qml.var(qml.PauliY(0)) Executing this QNode: >>> circuit(0.5) 0.7701511529340698 Args: op (Observable): a quantum observable object Raises: QuantumFunctionError: `op` is not an instance of :class:`~.Observable` """ if not isinstance(op, Observable): raise QuantumFunctionError( "{} is not an observable: cannot be used with var".format(op.name)) return MeasurementProcess(Variance, obs=op)
def expval(op): r"""Expectation value of the supplied observable. **Example:** .. code-block:: python3 dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) def circuit(x): qml.RX(x, wires=0) qml.Hadamard(wires=1) qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliY(0)) Executing this QNode: >>> circuit(0.5) -0.4794255386042029 Args: op (Observable): a quantum observable object Raises: QuantumFunctionError: `op` is not an instance of :class:`~.Observable` """ if not isinstance(op, Observable): raise QuantumFunctionError( "{} is not an observable: cannot be used with expval".format( op.name)) return MeasurementProcess(Expectation, obs=op)
def sample(op): r"""Sample from the supplied observable, with the number of shots determined from the ``dev.shots`` attribute of the corresponding device. **Example:** .. code-block:: python3 dev = qml.device("default.qubit", wires=2, shots=4) @qml.qnode(dev) def circuit(x): qml.RX(x, wires=0) qml.Hadamard(wires=1) qml.CNOT(wires=[0, 1]) return qml.sample(qml.PauliY(0)) Executing this QNode: >>> circuit(0.5) array([ 1., 1., 1., -1.]) Args: op (Observable): a quantum observable object Raises: QuantumFunctionError: `op` is not an instance of :class:`~.Observable` """ if not isinstance(op, Observable): raise QuantumFunctionError( "{} is not an observable: cannot be used with sample".format( op.name)) return MeasurementProcess(Sample, obs=op)
def access_state(self): """Check that the device has access to an internal state and return it if available. Raises: QuantumFunctionError: if the device is not capable of returning the state Returns: array or tensor: the state of the device """ if not self.capabilities().get("returns_state"): raise QuantumFunctionError( "The current device is not capable of returning the state") state = getattr(self, "state", None) if state is None: raise QuantumFunctionError( "The state is not available in the current device") return state
def sample(op): r"""Sample from the supplied observable, with the number of shots determined from the ``dev.shots`` attribute of the corresponding device. The samples are drawn from the eigenvalues :math:`\{\lambda_i\}` of the observable. The probability of drawing eigenvalue :math:`\lambda_i` is given by :math:`p(\lambda_i) = |\langle \xi_i | \psi \rangle|^2`, where :math:`| \xi_i \rangle` is the corresponding basis state from the observable's eigenbasis. **Example:** .. code-block:: python3 dev = qml.device("default.qubit", wires=2, shots=4) @qml.qnode(dev) def circuit(x): qml.RX(x, wires=0) qml.Hadamard(wires=1) qml.CNOT(wires=[0, 1]) return qml.sample(qml.PauliY(0)) Executing this QNode: >>> circuit(0.5) array([ 1., 1., 1., -1.]) Args: op (Observable): a quantum observable object Raises: QuantumFunctionError: `op` is not an instance of :class:`~.Observable` """ if not isinstance(op, Observable): raise QuantumFunctionError( "{} is not an observable: cannot be used with sample".format( op.name)) if isinstance(op, Tensor): for o in op.obs: qml.QueuingContext.remove(o) else: qml.QueuingContext.remove(op) op.return_type = Sample qml.QueuingContext.append(op) return op
def expval(op): r"""Expectation value of the supplied observable. **Example:** .. code-block:: python3 dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) def circuit(x): qml.RX(x, wires=0) qml.Hadamard(wires=1) qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliY(0)) Executing this QNode: >>> circuit(0.5) -0.4794255386042029 Args: op (Observable): a quantum observable object Raises: QuantumFunctionError: `op` is not an instance of :class:`~.Observable` """ if not isinstance(op, Observable): raise QuantumFunctionError( "{} is not an observable: cannot be used with expval".format( op.name)) if isinstance(op, Tensor): for o in op.obs: qml.QueuingContext.remove_operator(o) else: qml.QueuingContext.remove_operator(op) op.return_type = Expectation qml.QueuingContext.append_operator(op) return op
def var(op): r"""Variance of the supplied observable. **Example:** .. code-block:: python3 dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) def circuit(x): qml.RX(x, wires=0) qml.Hadamard(wires=1) qml.CNOT(wires=[0, 1]) return qml.var(qml.PauliY(0)) Executing this QNode: >>> circuit(0.5) 0.7701511529340698 Args: op (Observable): a quantum observable object Raises: QuantumFunctionError: `op` is not an instance of :class:`~.Observable` """ if not isinstance(op, Observable): raise QuantumFunctionError( "{} is not an observable: cannot be used with var".format(op.name)) if isinstance(op, Tensor): for o in op.obs: qml.QueuingContext.remove(o) else: qml.QueuingContext.remove(op) op.return_type = Variance qml.QueuingContext.append(op) return op
def statistics(self, observables): """Process measurement results from circuit execution and return statistics. This includes returning expectation values, variance, samples and probabilities. Args: observables (List[:class:`Observable`]): the observables to be measured Raises: QuantumFunctionError: if the value of :attr:`~.Observable.return_type` is not supported Returns: Union[float, List[float]]: the corresponding statistics """ results = [] for obs in observables: # Pass instances directly if obs.return_type is Expectation: results.append(self.expval(obs)) elif obs.return_type is Variance: results.append(self.var(obs)) elif obs.return_type is Sample: results.append(np.array(self.sample(obs))) elif obs.return_type is Probability: results.append(self.probability(wires=obs.wires)) elif obs.return_type is not None: raise QuantumFunctionError( "Unsupported return type specified for observable {}". format(obs.name)) return results
def statistics( self, braket_result: GateModelQuantumTaskResult, observables: Sequence[Observable]) -> Union[float, List[float]]: """Processes measurement results from a Braket task result and returns statistics. Args: braket_result (GateModelQuantumTaskResult): the Braket task result observables (List[Observable]): the observables to be measured Raises: QuantumFunctionError: if the value of :attr:`~.Observable.return_type` is not supported Returns: Union[float, List[float]]: the corresponding statistics """ results = [] for obs in observables: if obs.return_type not in RETURN_TYPES: raise QuantumFunctionError( "Unsupported return type specified for observable {}". format(obs.name)) results.append(self._get_statistic(braket_result, obs)) return results
def adjoint_jacobian(self, tape): """Implements the adjoint method outlined in `Jones and Gacon <https://arxiv.org/abs/2009.02823>`__ to differentiate an input tape. After a forward pass, the circuit is reversed by iteratively applying inverse (adjoint) gates to scan backwards through the circuit. This method is similar to the reversible method, but has a lower time overhead and a similar memory overhead. .. note:: The adjoint differentation method has the following restrictions: * As it requires knowledge of the statevector, only statevector simulator devices can be used. * Only expectation values are supported as measurements. Args: tape (.QuantumTape): circuit that the function takes the gradient of Returns: array: the derivative of the tape with respect to trainable parameters. Dimensions are ``(len(observables), len(trainable_params))``. Raises: QuantumFunctionError: if the input tape has measurements that are not expectation values or contains a multi-parameter operation aside from :class:`~.Rot` """ for m in tape.measurements: if m.return_type is not qml.operation.Expectation: raise qml.QuantumFunctionError( "Adjoint differentiation method does not support" f" measurement {m.return_type.value}") if not hasattr(m.obs, "base_name"): m.obs.base_name = None # This is needed for when the observable is a tensor product # Perform the forward pass. # Consider using caching and calling lower-level functionality. We just need the device to # be in the post-forward pass state. # https://github.com/PennyLaneAI/pennylane/pull/1032/files#r563441040 self.reset() self.execute(tape) phi = self._reshape(self.state, [2] * self.num_wires) lambdas = [self._apply_operation(phi, obs) for obs in tape.observables] expanded_ops = [] for op in reversed(tape.operations): if op.num_params > 1: if isinstance(op, qml.Rot) and not op.inverse: ops = op.decomposition(*op.parameters, wires=op.wires) expanded_ops.extend(reversed(ops)) else: raise QuantumFunctionError( f"The {op.name} operation is not supported using " 'the "adjoint" differentiation method') else: if op.name not in ("QubitStateVector", "BasisState"): expanded_ops.append(op) jac = np.zeros((len(tape.observables), len(tape.trainable_params))) dot_product_real = lambda a, b: self._real(qmlsum(self._conj(a) * b)) param_number = len(tape._par_info) - 1 # pylint: disable=protected-access trainable_param_number = len(tape.trainable_params) - 1 for op in expanded_ops: if (op.grad_method is not None) and (param_number in tape.trainable_params): d_op_matrix = operation_derivative(op) op.inv() phi = self._apply_operation(phi, op) if op.grad_method is not None: if param_number in tape.trainable_params: mu = self._apply_unitary(phi, d_op_matrix, op.wires) jac_column = np.array([ 2 * dot_product_real(lambda_, mu) for lambda_ in lambdas ]) jac[:, trainable_param_number] = jac_column trainable_param_number -= 1 param_number -= 1 lambdas = [ self._apply_operation(lambda_, op) for lambda_ in lambdas ] op.inv() return jac
def statistics(self, observables, shot_range=None, bin_size=None): """Process measurement results from circuit execution and return statistics. This includes returning expectation values, variance, samples, probabilities, states, and density matrices. Args: observables (List[.Observable]): the observables to be measured 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. Raises: QuantumFunctionError: if the value of :attr:`~.Observable.return_type` is not supported Returns: Union[float, List[float]]: the corresponding statistics .. UsageDetails:: The ``shot_range`` and ``bin_size`` arguments allow for the statistics to be performed on only a subset of device samples. This finer level of control is accessible from the main UI by instantiating a device with a batch of shots. For example, consider the following device: >>> dev = qml.device("my_device", shots=[5, (10, 3), 100]) This device will execute QNodes using 135 shots, however measurement statistics will be **course grained** across these 135 shots: * All measurement statistics will first be computed using the first 5 shots --- that is, ``shots_range=[0, 5]``, ``bin_size=5``. * Next, the tuple ``(10, 3)`` indicates 10 shots, repeated 3 times. We will want to use ``shot_range=[5, 35]``, performing the expectation value in bins of size 10 (``bin_size=10``). * Finally, we repeat the measurement statistics for the final 100 shots, ``shot_range=[35, 135]``, ``bin_size=100``. """ results = [] for obs in observables: # Pass instances directly if obs.return_type is Expectation: results.append( self.expval(obs, shot_range=shot_range, bin_size=bin_size)) elif obs.return_type is Variance: results.append( self.var(obs, shot_range=shot_range, bin_size=bin_size)) elif obs.return_type is Sample: results.append( self.sample(obs, shot_range=shot_range, bin_size=bin_size)) elif obs.return_type is Probability: results.append( self.probability(wires=obs.wires, shot_range=shot_range, bin_size=bin_size)) elif obs.return_type is State: if len(observables) > 1: raise QuantumFunctionError( "The state or density matrix cannot be returned in combination" " with other return types") if self.wires.labels != tuple(range(self.num_wires)): raise QuantumFunctionError( "Returning the state is not supported when using custom wire labels" ) # Check if the state is accessible and decide to return the state or the density # matrix. results.append(self.access_state(wires=obs.wires)) elif obs.return_type is not None: raise QuantumFunctionError( "Unsupported return type specified for observable {}". format(obs.name)) return results
def execute(self, queue, observables, parameters={}, **kwargs): """Execute a queue of quantum operations on the device and then measure the given observables. For plugin developers: Instead of overwriting this, consider implementing a suitable subset of :meth:`pre_apply`, :meth:`apply`, :meth:`post_apply`, :meth:`pre_measure`, :meth:`expval`, :meth:`var`, :meth:`sample`, :meth:`post_measure`, and :meth:`execution_context`. Args: queue (Iterable[~.operation.Operation]): operations to execute on the device observables (Iterable[~.operation.Observable]): observables to measure and return parameters (dict[int, list[ParameterDependency]]): Mapping from free parameter index to the list of :class:`Operations <pennylane.operation.Operation>` (in the queue) that depend on it. Keyword Args: return_native_type (bool): If True, return the result in whatever type the device uses internally, otherwise convert it into array[float]. Default: False. Raises: QuantumFunctionError: if the value of :attr:`~.Observable.return_type` is not supported Returns: array[float]: measured value(s) """ self.check_validity(queue, observables) self._op_queue = queue self._obs_queue = observables self._parameters = {} self._parameters.update(parameters) results = [] with self.execution_context(): self.pre_apply() for operation in queue: self.apply(operation.name, operation.wires, operation.parameters) self.post_apply() self.pre_measure() for obs in observables: if isinstance(obs, Tensor): wires = [ob.wires for ob in obs.obs] else: wires = obs.wires if obs.return_type is Expectation: results.append(self.expval(obs.name, wires, obs.parameters)) elif obs.return_type is Variance: results.append(self.var(obs.name, wires, obs.parameters)) elif obs.return_type is Sample: results.append( np.array(self.sample(obs.name, wires, obs.parameters))) elif obs.return_type is Probability: results.append(list( self.probability(wires=wires).values())) elif obs.return_type is State: raise QuantumFunctionError( "Returning the state is not supported") elif obs.return_type is not None: raise QuantumFunctionError( "Unsupported return type specified for observable {}". format(obs.name)) self.post_measure() self._op_queue = None self._obs_queue = None self._parameters = None # Ensures that a combination with sample does not put # expvals and vars in superfluous arrays if all(obs.return_type is Sample for obs in observables): return self._asarray(results) if any(obs.return_type is Sample for obs in observables): return self._asarray(results, dtype="object") return self._asarray(results)