Source code for pennylane.templates.subroutines.reflection
# 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.
"""
This submodule contains the template for the Reflection operation.
"""
import copy
import numpy as np
from pennylane import ops
from pennylane.decomposition import (
add_decomps,
adjoint_resource_rep,
controlled_resource_rep,
register_resources,
resource_rep,
)
from pennylane.operation import Operation
from pennylane.queuing import QueuingManager
from pennylane.wires import Wires
[docs]
class Reflection(Operation):
r"""Apply a reflection about a state :math:`|\Psi\rangle`.
This operator works by providing an operation, :math:`U`, that prepares the desired state, :math:`\vert \Psi \rangle`,
that we want to reflect about. We can also provide a reflection angle :math:`\alpha`
to define the operation in a more generic form:
.. math::
R(U, \alpha) = -I + (1 - e^{i\alpha}) |\Psi\rangle \langle \Psi|
This operator is an important component of quantum algorithms such as amplitude amplification [`arXiv:quant-ph/0005055 <https://arxiv.org/abs/quant-ph/0005055>`__]
and oblivious amplitude amplification [`arXiv:1312.1414 <https://arxiv.org/abs/1312.1414>`__].
Args:
U (Operator): the operator that prepares the state :math:`|\Psi\rangle`
alpha (float): the angle of the operator, default is :math:`\pi`
reflection_wires (Any or Iterable[Any]): subsystem of wires on which to reflect, the
default is ``None`` and the reflection will be applied on the ``U`` wires.
**Example**
This example shows how to apply the reflection :math:`-I + 2|+\rangle \langle +|` to the state :math:`|1\rangle`.
.. code-block::
U = qml.Hadamard(wires=0)
dev = qml.device(‘default.qubit’)
@qml.qnode(dev)
def circuit():
qml.PauliX(wires=0)
qml.Reflection(U)
return qml.state()
>>> circuit()
tensor([1.+6.123234e-17j, 0.-6.123234e-17j], requires_grad=True)
For cases when :math:`U` comprises many operations, you can create a quantum
function containing each operation, one per line, then decorate the quantum
function with ``@qml.prod``:
.. code-block::
@qml.prod
def U(wires):
qml.Hadamard(wires=wires[0])
qml.RY(0.1, wires=wires[1])
@qml.qnode(dev)
def circuit():
qml.Reflection(U([0, 1]))
return qml.state()
>>> circuit()
tensor([-0.00249792-6.13852933e-17j, 0.04991671+3.05651685e-18j,
0.99750208+6.10793866e-17j, 0.04991671+3.05651685e-18j], requires_grad=True)
.. details::
:title: Theory
The operator is built as follows:
.. math::
\text{R}(U, \alpha) = -I + (1 - e^{i\alpha}) |\Psi\rangle \langle \Psi| = U(-I + (1 - e^{i\alpha}) |0\rangle \langle 0|)U^{\dagger}.
The central block is obtained through a :class:`~.PhaseShift` controlled operator.
In the case of specifying the reflection wires, the operator would have the following expression.
.. math::
U(-I + (1 - e^{i\alpha}) |0\rangle^{\otimes m} \langle 0|^{\otimes m}\otimes I^{n-m})U^{\dagger},
where :math:`m` is the number of reflection wires and :math:`n` is the total number of wires.
"""
grad_method = None
resource_keys = {"base_class", "base_params", "num_wires", "num_reflection_wires"}
@classmethod
def _primitive_bind_call(cls, *args, **kwargs):
return cls._primitive.bind(*args, **kwargs)
def _flatten(self):
data = (self.hyperparameters["base"], self.parameters[0])
return data, (self.hyperparameters["reflection_wires"],)
@classmethod
def _primitive_bind_call(cls, *args, **kwargs):
return cls._primitive.bind(*args, **kwargs)
@classmethod
def _unflatten(cls, data, metadata):
U, alpha = data
return cls(U, alpha=alpha, reflection_wires=metadata[0])
def __init__(self, U, alpha=np.pi, reflection_wires=None, id=None):
self._name = "Reflection"
wires = U.wires
if reflection_wires is None:
reflection_wires = U.wires
if not set(reflection_wires).issubset(set(U.wires)):
raise ValueError("The reflection wires must be a subset of the operation wires.")
self._hyperparameters = {
"base": U,
"reflection_wires": tuple(reflection_wires),
}
super().__init__(alpha, *U.data, wires=wires, id=id)
@property
def resource_params(self) -> dict:
return {
"base_class": self.hyperparameters["base"].__class__,
"base_params": self.hyperparameters["base"].resource_params,
"num_wires": None,
"num_reflection_wires": len(self.hyperparameters["reflection_wires"]),
}
[docs]
def map_wires(self, wire_map: dict):
# pylint: disable=protected-access
new_op = copy.deepcopy(self)
new_op._wires = Wires([wire_map.get(wire, wire) for wire in self.wires])
new_op._hyperparameters["base"] = new_op._hyperparameters["base"].map_wires(wire_map)
new_op._hyperparameters["reflection_wires"] = tuple(
wire_map.get(w, w) for w in new_op._hyperparameters["reflection_wires"]
)
return new_op
@property
def alpha(self):
"""The alpha angle for the operation."""
return self.parameters[0]
@property
def reflection_wires(self):
"""The reflection wires for the operation."""
return self.hyperparameters["reflection_wires"]
[docs]
def queue(self, context=QueuingManager):
context.remove(self.hyperparameters["base"])
context.append(self)
return self
[docs]
@staticmethod
def compute_decomposition(*parameters, wires=None, **hyperparameters):
alpha = parameters[0]
U = hyperparameters["base"]
reflection_wires = hyperparameters["reflection_wires"]
wires = Wires(reflection_wires) if reflection_wires is not None else wires
decomp_ops = []
decomp_ops.append(ops.GlobalPhase(np.pi))
decomp_ops.append(ops.adjoint(U))
if len(wires) > 1:
decomp_ops.append(ops.X(wires=wires[-1]))
decomp_ops.append(
ops.ctrl(
ops.PhaseShift(alpha, wires=wires[-1]),
control=wires[:-1],
control_values=[0] * (len(wires) - 1),
)
)
decomp_ops.append(ops.X(wires=wires[-1]))
else:
decomp_ops.append(ops.X(wires=wires))
decomp_ops.append(ops.PhaseShift(alpha, wires=wires))
decomp_ops.append(ops.X(wires=wires))
decomp_ops.append(U)
return decomp_ops
def _reflection_decomposition_resources(
base_class, base_params, num_wires, num_reflection_wires=None
) -> dict:
num_wires = num_reflection_wires if num_reflection_wires is not None else num_wires
resources = {
ops.GlobalPhase: 1,
adjoint_resource_rep(base_class, base_params): 1,
ops.PauliX: 2,
}
if num_wires > 1:
resources[
controlled_resource_rep(
ops.PhaseShift,
{},
num_control_wires=num_wires - 1,
num_zero_control_values=num_wires - 1,
)
] = 1
else:
resources[resource_rep(ops.PhaseShift)] = 1
resources[resource_rep(base_class, **base_params)] = 1
return resources
@register_resources(_reflection_decomposition_resources)
def _reflection_decomposition(*parameters, wires=None, **hyperparameters):
alpha = parameters[0]
U = hyperparameters["base"]
reflection_wires = hyperparameters["reflection_wires"]
wires = Wires(reflection_wires) if reflection_wires is not None else wires
ops.GlobalPhase(np.pi)
ops.adjoint(U)
if len(wires) > 1:
ops.PauliX(wires=wires[-1])
ops.ctrl(
ops.PhaseShift(alpha, wires=wires[-1]),
control=wires[:-1],
control_values=[0] * (len(wires) - 1),
)
ops.PauliX(wires=wires[-1])
else:
ops.PauliX(wires=wires)
ops.PhaseShift(alpha, wires=wires)
ops.PauliX(wires=wires)
U._unflatten(*U._flatten()) # pylint: disable=protected-access
add_decomps(Reflection, _reflection_decomposition)
_modules/pennylane/templates/subroutines/reflection
Download Python script
Download Notebook
View on GitHub