Source code for pennylane.templates.subroutines.qpe

# 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.
"""
Contains the QuantumPhaseEstimation template.
"""
# pylint: disable=too-many-arguments,arguments-differ
import copy

import pennylane as qml
from pennylane.operation import AnyWires, Operator
from pennylane.queuing import QueuingManager
from pennylane.resource.error import ErrorOperation, SpectralNormError
from pennylane.wires import Wires


[docs]class QuantumPhaseEstimation(ErrorOperation): r"""Performs the `quantum phase estimation <https://en.wikipedia.org/wiki/Quantum_phase_estimation_algorithm>`__ circuit. Given a unitary matrix :math:`U`, this template applies the circuit for quantum phase estimation. The unitary is applied to the qubits specified by ``target_wires`` and :math:`n` qubits are used for phase estimation as specified by ``estimation_wires``. .. figure:: ../../_static/templates/subroutines/qpe.svg :align: center :width: 60% :target: javascript:void(0); Args: unitary (array or Operator): the phase estimation unitary, specified as a matrix or an :class:`~.Operator` target_wires (Union[Wires, Sequence[int], or int]): the target wires to apply the unitary. If the unitary is specified as an operator, the target wires should already have been defined as part of the operator. In this case, target_wires should not be specified. estimation_wires (Union[Wires, Sequence[int], or int]): the wires to be used for phase estimation Raises: QuantumFunctionError: if the ``target_wires`` and ``estimation_wires`` share a common element, or if ``target_wires`` are specified for an operator unitary. .. details:: :title: Usage Details This circuit can be used to perform the standard quantum phase estimation algorithm, consisting of the following steps: #. Prepare ``target_wires`` in a given state. If ``target_wires`` are prepared in an eigenstate of :math:`U` that has corresponding eigenvalue :math:`e^{2 \pi i \theta}` with phase :math:`\theta \in [0, 1)`, this algorithm will measure :math:`\theta`. Other input states can be prepared more generally. #. Apply the ``QuantumPhaseEstimation`` circuit. #. Measure ``estimation_wires`` using :func:`~.probs`, giving a probability distribution over measurement outcomes in the computational basis. #. Find the index of the largest value in the probability distribution and divide that number by :math:`2^{n}`. This number will be an estimate of :math:`\theta` with an error that decreases exponentially with the number of qubits :math:`n`. Note that if :math:`\theta \in (-1, 0]`, we can estimate the phase by again finding the index :math:`i` found in step 4 and calculating :math:`\theta \approx \frac{1 - i}{2^{n}}`. An example of this case is below. Consider the matrix corresponding to a rotation from an :class:`~.RX` gate: .. code-block:: python import pennylane as qml from pennylane.templates import QuantumPhaseEstimation from pennylane import numpy as np phase = 5 target_wires = [0] unitary = qml.RX(phase, wires=0).matrix() The ``phase`` parameter can be estimated using ``QuantumPhaseEstimation``. An example is shown below using a register of five phase-estimation qubits: .. code-block:: python n_estimation_wires = 5 estimation_wires = range(1, n_estimation_wires + 1) dev = qml.device("default.qubit", wires=n_estimation_wires + 1) @qml.qnode(dev) def circuit(): # Start in the |+> eigenstate of the unitary qml.Hadamard(wires=target_wires) QuantumPhaseEstimation( unitary, target_wires=target_wires, estimation_wires=estimation_wires, ) return qml.probs(estimation_wires) phase_estimated = np.argmax(circuit()) / 2 ** n_estimation_wires # Need to rescale phase due to convention of RX gate phase_estimated = 4 * np.pi * (1 - phase_estimated) We can also perform phase estimation on an operator. Note that since operators are defined with target wires, the target wires should not be provided for the QPE. .. code-block:: python # use the product to specify compound operators unitary = qml.RX(np.pi / 2, wires=[0]) @ qml.CNOT(wires=[0, 1]) eigenvector = np.array([-1/2, -1/2, 1/2, 1/2]) n_estimation_wires = 5 estimation_wires = range(2, n_estimation_wires + 2) target_wires = [0, 1] dev = qml.device("default.qubit", wires=n_estimation_wires + 2) @qml.qnode(dev) def circuit(): qml.StatePrep(eigenvector, wires=target_wires) QuantumPhaseEstimation( unitary, estimation_wires=estimation_wires, ) return qml.probs(estimation_wires) phase_estimated = np.argmax(circuit()) / 2 ** n_estimation_wires """ num_wires = AnyWires grad_method = None # pylint: disable=no-member def _flatten(self): data = (self.hyperparameters["unitary"],) metadata = (self.hyperparameters["estimation_wires"],) return data, metadata @classmethod def _primitive_bind_call(cls, *args, **kwargs): return cls._primitive.bind(*args, **kwargs) @classmethod def _unflatten(cls, data, metadata) -> "QuantumPhaseEstimation": return cls(data[0], estimation_wires=metadata[0]) def __init__(self, unitary, target_wires=None, estimation_wires=None, id=None): if isinstance(unitary, Operator): # If the unitary is expressed in terms of operators, do not provide target wires if target_wires is not None: raise qml.QuantumFunctionError( "The unitary is expressed as an operator, which already has target wires " "defined, do not additionally specify target wires." ) target_wires = unitary.wires elif target_wires is None: raise qml.QuantumFunctionError( "Target wires must be specified if the unitary is expressed as a matrix." ) else: unitary = qml.QubitUnitary(unitary, wires=target_wires) # Estimation wires are required, but kept as an optional argument so that it can be # placed after target_wires for backwards compatibility. if estimation_wires is None: raise qml.QuantumFunctionError("No estimation wires specified.") target_wires = qml.wires.Wires(target_wires) estimation_wires = qml.wires.Wires(estimation_wires) wires = target_wires + estimation_wires if any(wire in target_wires for wire in estimation_wires): raise qml.QuantumFunctionError( "The target wires and estimation wires must not overlap." ) self._hyperparameters = { "unitary": unitary, "target_wires": target_wires, "estimation_wires": estimation_wires, } super().__init__(wires=wires, id=id) @property def target_wires(self): """The target wires of the QPE""" return self._hyperparameters["target_wires"] @property def estimation_wires(self): """The estimation wires of the QPE""" return self._hyperparameters["estimation_wires"]
[docs] def error(self): """The QPE error computed from the spectral norm error of the input unitary operator. **Example** >>> class CustomOP(qml.resource.ErrorOperation): ... def error(self): ... return qml.resource.SpectralNormError(0.005) >>> Op = CustomOP(wires=[0]) >>> QPE = QuantumPhaseEstimation(Op, estimation_wires = range(1, 5)) >>> QPE.error() SpectralNormError(0.075) """ base_unitary = self._hyperparameters["unitary"] if not isinstance(base_unitary, ErrorOperation): return SpectralNormError(0.0) unitary_error = base_unitary.error().error sequence_error = qml.math.array( [unitary_error * (2**i) for i in range(len(self.estimation_wires) - 1, -1, -1)], like=qml.math.get_interface(unitary_error), ) additive_error = qml.math.sum(sequence_error) return SpectralNormError(additive_error)
# pylint: disable=protected-access
[docs] def map_wires(self, wire_map: dict): new_op = copy.deepcopy(self) new_op._wires = Wires([wire_map.get(wire, wire) for wire in self.wires]) new_op._hyperparameters["unitary"] = qml.map_wires( new_op._hyperparameters["unitary"], wire_map ) for key in ["estimation_wires", "target_wires"]: new_op._hyperparameters[key] = [ wire_map.get(wire, wire) for wire in self.hyperparameters[key] ] return new_op
[docs] def queue(self, context=QueuingManager): context.remove(self._hyperparameters["unitary"]) context.append(self) return self
[docs] @staticmethod def compute_decomposition( wires, unitary, target_wires, estimation_wires ): # pylint: disable=arguments-differ,unused-argument r"""Representation of the QPE circuit as a product of other operators. .. math:: O = O_1 O_2 \dots O_n. .. seealso:: :meth:`~.QuantumPhaseEstimation.decomposition`. Args: wires (Any or Iterable[Any]): wires that the QPE circuit acts on unitary (Operator): the phase estimation unitary, specified as an operator target_wires (Any or Iterable[Any]): the target wires to apply the unitary estimation_wires (Any or Iterable[Any]): the wires to be used for phase estimation Returns: list[.Operator]: decomposition of the operator """ op_list = [qml.Hadamard(w) for w in estimation_wires] pow_ops = (qml.pow(unitary, 2**i) for i in range(len(estimation_wires) - 1, -1, -1)) op_list.extend(qml.ctrl(op, w) for op, w in zip(pow_ops, estimation_wires)) op_list.append(qml.adjoint(qml.templates.QFT(wires=estimation_wires))) return op_list