Source code for pennylane.labs.estimator_beta.templates.comparators
# Copyright 2026 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.
r"""Resource operators for PennyLane comparison templates."""
import pennylane.labs.estimator_beta as qre
from pennylane.estimator.resource_operator import (
CompressedResourceOp,
GateCount,
ResourceOperator,
resource_rep,
)
from pennylane.wires import WiresLike
# pylint: disable= signature-differs, arguments-differ
[docs]
class OutOfPlaceIntegerComparator(ResourceOperator):
r"""Resource class for an out-of-place integer comparator.
Compares an n-bit quantum register :math:`|x\rangle` against a classical
integer :math:`L`, storing the result :math:`x < L` (or :math:`x \geq L`)
in a dedicated output qubit.
The circuit computes the borrow chain of the subtraction :math:`x - L`.
The n - 1 intermediate borrow qubits are kept dirty after the
forward pass, enabling the inverse to be performed with Clifford gates
only (0 Toffoli cost).
Args:
value (int): The value :math:`L` that the state’s decimal representation is compared against.
register_size (int): size of the register for basis state
geq (bool): If set to ``True``, the comparison made will be :math:`n \geq L`. If
``False``, the comparison made will be :math:`n \lt L`.
wires (WiresLike | None): The wires the operation acts on.
Resources:
The resources are computed based on Figure 6 of Appendix E in
`Su et al. (2021) <https://arxiv.org/abs/2105.12767>`_. This decomposition
is useful when extra auxiliary wires are available and an inverse of the operation is required in the same circuit.
The resources are as follows: `register_size - 1` ``TemporaryAND`` gates, `register_size - 1` ``CNOT`` gates, and the number of ``X``
gates depend on the number of ones in binary representation of the value we are comparing to. There are also `register_size - 1`
auxiliary qubits that are allocated in the circuit and are deallocated by its adjoint.
**Example**
The resources for this operation are computed using:
>>> import pennylane.labs.estimator_beta as qre
>>> comparator = qre.OutOfPlaceIntegerComparator(value=11, register_size=4, geq=False)
>>> print(qre.estimate(comparator))
--- Resources: ---
Total wires: 8
algorithmic wires: 5
allocated wires: 3
zero state: 0
any state: 3
Total gates : 17
'Toffoli': 3,
'CNOT': 4,
'X': 10
"""
resource_keys = {"value", "register_size", "geq"}
def __init__(
self,
value: int,
register_size: int | None = None,
geq: bool = False,
wires: WiresLike | None = None,
):
if register_size is None:
if wires is None:
raise ValueError("Must provide at least one of `register_size` and `wires`.")
register_size = len(wires) - 1
self.value = value
self.register_size = register_size
self.geq = geq
# n input qubits + 1 output qubit
self.num_wires = register_size + 1
if wires and len(wires) != self.num_wires:
raise ValueError(f"Expected {self.num_wires} wires, got {len(wires)}")
super().__init__(wires=wires)
@property
def resource_params(self) -> dict:
r"""Returns a dictionary containing the minimal information needed to compute the resources.
Returns:
dict: A dictionary containing the resource parameters:
* value (int): The value :math:`L` that the state’s decimal representation is compared against.
* register_size (int): size of the register for basis state
* geq (bool): If set to ``True``, the comparison made will be :math:`n \geq L`. If
``False``, the comparison made will be :math:`n \lt L`.
"""
return {
"value": self.value,
"register_size": self.register_size,
"geq": self.geq,
}
[docs]
@classmethod
def resource_rep(
cls, value: int, register_size: int, geq: bool = False
) -> CompressedResourceOp:
r"""Returns a compressed representation containing only the parameters of
the Operator that are needed to compute the resources.
Args:
value (int): The value :math:`L` that the state’s decimal representation is compared against.
register_size (int): size of the register for basis state
geq (bool): If set to ``True``, the comparison made will be :math:`n \geq L`. If
``False``, the comparison made will be :math:`n \lt L`.
Returns:
:class:`~.pennylane.estimator.resource_operator.CompressedResourceOp`: the operator in a compressed representation
"""
params = {"value": value, "register_size": register_size, "geq": geq}
num_wires = register_size + 1
return CompressedResourceOp(cls, num_wires, params)
[docs]
@classmethod
def resource_decomp(cls, value: int, register_size: int, geq: bool = False) -> list[GateCount]:
r"""Returns a list representing the resources of the operator. Each object in the list represents a gate and the
number of times it occurs in the circuit.
Args:
value (int): The value :math:`L` that the state’s decimal representation is compared against.
register_size (int): size of the register for basis state
geq (bool): If set to ``True``, the comparison made will be :math:`n \geq L`. If
``False``, the comparison made will be :math:`n \lt L`.
Resources:
The resources are computed based on Figure 6 of Appendix E in
`Su et al. (2021) <https://arxiv.org/abs/2105.12767>`_. This decomposition
is useful when extra auxiliary wires are available and an inverse of the operation is required in the same circuit.
The resources are as follows: `register_size - 1` ``TemporaryAND`` gates, `register_size - 1` ``CNOT`` gates, and the number of ``X``
gates depend on the number of ones in binary representation of the value we are comparing to. There are also `register_size - 1`
auxiliary qubits that are allocated in the circuit and are deallocated by its adjoint.
Returns:
list[GateCount]: A list of gate counts representing the resources of the operator.
"""
num_ones = value.bit_count()
gate_lst = []
if value == 0:
if geq:
gate_lst.append(GateCount(resource_rep(qre.X), 1))
return gate_lst
if value > 2 ** (register_size) - 1:
if not geq:
gate_lst.append(GateCount(resource_rep(qre.X), 1))
return gate_lst
gate_lst.append(qre.Allocate(register_size - 1))
gate_lst.append(GateCount(resource_rep(qre.TemporaryAND), register_size - 1))
gate_lst.append(GateCount(resource_rep(qre.CNOT), register_size))
gate_lst.append(GateCount(resource_rep(qre.X), 4 * (num_ones - 1) + 2))
if geq:
gate_lst.append(GateCount(resource_rep(qre.X), 1))
return gate_lst
[docs]
@classmethod
def adjoint_resource_decomp(cls, target_resource_params: dict) -> list[GateCount]:
r"""Returns a list representing the resources of the adjoint of the operator. Each object in the list represents a gate and the
number of times it occurs in the circuit.
Args:
target_resource_params (dict): Dictionary containing the resource parameters of the target operator.
Resources:
The resources are computed based on Figure 6 of Appendix E in
`Su et al. (2021) <https://arxiv.org/abs/2105.12767>`_. This decomposition
is useful when extra auxiliary wires are available and an inverse of the operation is required in the same circuit.
The resources are as follows: `register_size - 1` ``TemporaryAND`` gates, `register_size - 1` ``CNOT`` gates, and the number of ``X``
gates depend on the number of ones in binary representation of the value we are comparing to. There are also `register_size - 1`
auxiliary qubits that are allocated in the circuit and are deallocated by its adjoint.
Returns:
list[GateCount]: A list of gate counts representing the resources of the adjoint of the operator.
"""
value = target_resource_params["value"]
register_size = target_resource_params["register_size"]
geq = target_resource_params["geq"]
num_ones = value.bit_count()
gate_lst = []
if value == 0:
if not geq:
gate_lst.append(GateCount(resource_rep(qre.X), 1))
return gate_lst
if value > 2 ** (register_size) - 1:
if geq:
gate_lst.append(GateCount(resource_rep(qre.X), 1))
return gate_lst
gate_lst.append(
GateCount(
resource_rep(qre.Adjoint, {"base_cmpr_op": resource_rep(qre.TemporaryAND)}),
register_size - 1,
)
)
gate_lst.append(GateCount(resource_rep(qre.CNOT), register_size))
gate_lst.append(GateCount(resource_rep(qre.X), 4 * (num_ones - 1) + 2))
if geq:
gate_lst.append(GateCount(resource_rep(qre.X), 1))
gate_lst.append(qre.Deallocate(register_size - 1))
return gate_lst
[docs]
class RegisterEquality(ResourceOperator):
r"""Resource class for testing the equality of two quantum registers.
Compares two n-bit quantum registers :math:`|i\rangle` and :math:`|j\rangle`,
storing the result (:math:`i == j`) in a dedicated output qubit.
The circuit computes the bitwise XOR of the two registers
using CNOTs, then uses a ``TemporaryAND`` cascade to flag whether
all XOR results are zero (i.e., the registers are equal).
Args:
register_size (int): Number of qubits n in each register.
wires (WiresLike | None): The wires the operation acts on.
Resources:
The circuit computes the bitwise XOR of the two registers using
CNOTs, then checks whether all results are zero via a Toffoli
cascade. The circuit is represented as:
.. code-block:: bash
0: ─╭●──────────┤
1: ─│──╭●───────┤
2: ─│──│──╭●────┤
3: ─╰X─│──│──╭○─┤
4: ────╰X─│──├○─┤
5: ───────╰X─├○─┤
6: ──────────╰X─┤ <Z>
Note that the state of the second register is not preserved after this operation and it needs to be uncomputed if it is needed later in the circuit.
**Example**
The resources for this operation are computed using:
>>> import pennylane.labs.estimator_beta as qre
>>> comparator = qre.RegisterEquality(register_size=3)
>>> print(qre.estimate(comparator))
--- Resources: ---
Total wires: 8
algorithmic wires: 7
allocated wires: 1
zero state: 1
any state: 0
Total gates : 12
'Toffoli': 2,
'CNOT': 4,
'X': 3,
'Hadamard': 3
"""
resource_keys = {"register_size"}
def __init__(
self,
register_size: int | None = None,
wires: WiresLike | None = None,
):
if register_size is None:
if wires is None:
raise ValueError("Must provide at least one of `register_size` and `wires`.")
register_size = (len(wires) - 1) // 2
self.register_size = register_size
self.num_wires = 2 * self.register_size + 1
if wires and len(wires) != self.num_wires:
raise ValueError(f"Expected {self.num_wires} wires, got {len(wires)}")
super().__init__(wires=wires)
@property
def resource_params(self) -> dict:
r"""Returns a dictionary containing the minimal information needed to compute the resources.
Returns:
dict: A dictionary containing the resource parameters:
* register_size (int): size of the registers for basis state
"""
return {"register_size": self.register_size}
[docs]
@classmethod
def resource_rep(cls, register_size: int) -> CompressedResourceOp:
r"""Returns a compressed representation containing only the parameters of
the Operator that are needed to compute the resources.
Args:
register_size (int): Number of qubits n in each register.
Returns:
:class:`~.pennylane.estimator.resource_operator.CompressedResourceOp`: the operator in a compressed representation
"""
num_wires = 2 * register_size + 1
return CompressedResourceOp(cls, num_wires, {"register_size": register_size})
[docs]
@classmethod
def resource_decomp(cls, register_size: int) -> list[GateCount]:
r"""Returns a list representing the resources of the operator. Each object in the list represents a gate and the
number of times it occurs in the circuit.
Args:
register_size (int): Number of qubits n in each register.
Resources:
The circuit computes the bitwise XOR of the two registers using
CNOTs, then checks whether all results are zero via a Toffoli
cascade. The circuit is represented as:
.. code-block:: bash
0: ─╭●──────────┤
1: ─│──╭●───────┤
2: ─│──│──╭●────┤
3: ─╰X─│──│──╭○─┤
4: ────╰X─│──├○─┤
5: ───────╰X─├○─┤
6: ──────────╰X─┤ <Z>
Note that the state of the second register is not preserved after this operation and it needs to be uncomputed if it is needed later in the circuit.
Returns:
list[GateCount]: A list of gate counts representing the resources of the operator.
"""
gate_lst = []
if register_size == 0:
return gate_lst
if register_size == 1:
gate_lst.append(GateCount(resource_rep(qre.X), 1))
gate_lst.append(GateCount(resource_rep(qre.CNOT), 2))
return gate_lst
mcx = resource_rep(
qre.MultiControlledX, {"num_ctrl_wires": register_size, "num_zero_ctrl": 0}
)
gate_lst.append(GateCount(mcx, 1))
gate_lst.append(GateCount(resource_rep(qre.X), register_size))
gate_lst.append(GateCount(resource_rep(qre.CNOT), register_size))
return gate_lst
_modules/pennylane/labs/estimator_beta/templates/comparators
Download Python script
Download Notebook
View on GitHub