Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding QROM template #5688

Merged
merged 48 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
6c1e795
qrom
KetpuntoG May 13, 2024
7cd3add
adding tests, fixing some issues
KetpuntoG May 14, 2024
5fa0683
pylint
KetpuntoG May 15, 2024
bde0cf4
docs
KetpuntoG May 15, 2024
e047b38
pylint
KetpuntoG May 15, 2024
bd792c1
Update qrom.py
KetpuntoG May 15, 2024
b4305d9
Update qrom.py
KetpuntoG May 15, 2024
4870e39
tests fix
KetpuntoG May 16, 2024
88d1930
docs
KetpuntoG May 16, 2024
28ca927
Update qrom.py
KetpuntoG May 16, 2024
e56be8e
Update qrom.py
KetpuntoG May 16, 2024
134fcad
Update test_qubitization.py
KetpuntoG May 16, 2024
d76e415
Update qrom.py
KetpuntoG May 16, 2024
ce7d6ba
Update test_qubitization.py
KetpuntoG May 16, 2024
657f652
Update test_qrom.py
KetpuntoG May 16, 2024
4e04ec3
comments to clarify code
KetpuntoG May 16, 2024
aeead30
Update qrom.py
KetpuntoG May 16, 2024
e3ff382
Merge branch 'master' into qrom_template
KetpuntoG May 16, 2024
4fae9d5
Update qrom.py
KetpuntoG May 16, 2024
051b986
Merge branch 'qrom_template' of https://github.com/PennyLaneAI/pennyl…
KetpuntoG May 16, 2024
8515c59
Update qrom.py
KetpuntoG May 17, 2024
6cd706c
b -> bitstrings
KetpuntoG May 23, 2024
f711d91
pylint
KetpuntoG May 23, 2024
12d5f92
Merge branch 'master' into qrom_template
KetpuntoG May 23, 2024
ad2fab8
Apply suggestions from code review
KetpuntoG May 24, 2024
f2a3fe6
Some Soran comments
KetpuntoG May 24, 2024
3140731
docs
KetpuntoG May 27, 2024
c754847
provisional thumbnail + updating docs
KetpuntoG May 27, 2024
c278e1c
thumbnail
KetpuntoG May 27, 2024
d5added
typo image
KetpuntoG May 27, 2024
1f7af37
paper change notation
KetpuntoG May 27, 2024
fa917f9
codecov
KetpuntoG May 27, 2024
83180cc
Update qrom.py
KetpuntoG May 27, 2024
b6f4428
[skip-ci]
KetpuntoG May 28, 2024
5afa6e4
Apply suggestions from code review
KetpuntoG Jun 4, 2024
1e2876e
final comments review
KetpuntoG Jun 4, 2024
781515d
Merge branch 'master' into qrom_template
KetpuntoG Jun 4, 2024
11ef6e0
fix jax test
KetpuntoG Jun 5, 2024
5235b9c
Update test_templates.py
KetpuntoG Jun 5, 2024
d9b7e9d
Merge branch 'master' into qrom_template
KetpuntoG Jun 5, 2024
3a5f139
adding _primitive_blind_call
KetpuntoG Jun 5, 2024
f3c9e4f
[skip-ci]
KetpuntoG Jun 5, 2024
c2a359c
final final
KetpuntoG Jun 5, 2024
d9d85f2
Apply suggestions from code review
KetpuntoG Jun 5, 2024
1dd5e9c
Merge branch 'master' into qrom_template
KetpuntoG Jun 5, 2024
b1b2555
Update test_qrom.py
KetpuntoG Jun 5, 2024
8ac2905
reference format
KetpuntoG Jun 6, 2024
aadc12c
Merge branch 'master' into qrom_template
KetpuntoG Jun 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added doc/_static/templates/qrom/qrom_thumbnail.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions doc/introduction/templates.rst
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,11 @@ Other useful templates which do not belong to the previous categories can be fou
:description: :doc:`Qubitization <../code/api/pennylane.Qubitization>`
:figure: _static/templates/qubitization/thumbnail_qubitization.png

.. gallery-item::
:description: :doc:`QROM <../code/api/pennylane.QROM>`
:figure: _static/templates/qrom/qrom_thumbnail.png


.. raw:: html

<div style='clear:both'></div>
Expand Down
29 changes: 29 additions & 0 deletions doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,33 @@

<h3>New features since last release</h3>

* QROM template is added. This template allows you to enter classic data in the form of bitstrings.
[(#5688)](https://github.com/PennyLaneAI/pennylane/pull/5688)

```python
# a list of bitstrings is defined
bitstrings = ["010", "111", "110", "000"]

dev = qml.device("default.qubit", shots = 1)

@qml.qnode(dev)
def circuit():

# the third index is encoded in the control wires [0, 1]
qml.BasisEmbedding(2, wires = [0,1])

qml.QROM(bitstrings = bitstrings,
control_wires = [0,1],
target_wires = [2,3,4],
work_wires = [5,6,7])

return qml.sample(wires = [2,3,4])
```
```pycon
>>> print(circuit())
[1 1 0]
```

* `qml.QNode` and `qml.qnode` now accept two new keyword arguments: `postselect_mode` and `mcm_method`.
These keyword arguments can be used to configure how the device should behave when running circuits with
mid-circuit measurements.
Expand All @@ -20,6 +47,7 @@
* The `default.tensor` device is introduced to perform tensor network simulation of a quantum circuit.
[(#5699)](https://github.com/PennyLaneAI/pennylane/pull/5699)


<h3>Improvements 🛠</h3>

* The wires for the `default.tensor` device are selected at runtime if they are not provided by user.
Expand Down Expand Up @@ -308,6 +336,7 @@

This release contains contributions from (in alphabetical order):

Guillermo Alonso-Linaje,
Lillian M. A. Frederiksen,
Gabriel Bottrill,
Astral Cai,
Expand Down
1 change: 1 addition & 0 deletions pennylane/templates/subroutines/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,4 @@
from .reflection import Reflection
from .amplitude_amplification import AmplitudeAmplification
from .qubitization import Qubitization
from .qrom import QROM
295 changes: 295 additions & 0 deletions pennylane/templates/subroutines/qrom.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,295 @@
# 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 QROM.
"""

KetpuntoG marked this conversation as resolved.
Show resolved Hide resolved
import math

import numpy as np

import pennylane as qml
from pennylane.operation import Operation


def _multi_swap(wires1, wires2):
"""Apply a series of SWAP gates between two sets of wires."""
for wire1, wire2 in zip(wires1, wires2):
qml.SWAP(wires=[wire1, wire2])
KetpuntoG marked this conversation as resolved.
Show resolved Hide resolved
KetpuntoG marked this conversation as resolved.
Show resolved Hide resolved


class QROM(Operation):
r"""Applies the QROM operator.

This operator encodes bitstrings associated with indexes:

.. math::
\text{QROM}|i\rangle|0\rangle = |i\rangle |b_i\rangle,

where :math:`b_i` is the bitstring associated with index :math:`i`.

Args:
bitstrings (list[str]): the bitstrings to be encoded
control_wires (Sequence[int]): the wires where the indexes are specified
target_wires (Sequence[int]): the wires where the bitstring is loaded
work_wires (Sequence[int]): the auxiliary wires used for the computation
clean (bool): if True, the work wires are not altered by operator, default is ``True``

**Example**

In this example, the QROM operator is applied to encode the third bitstring, associated with index 2, in the target wires.

.. code-block::

# a list of bitstrings is defined
bitstrings = ["010", "111", "110", "000"]
KetpuntoG marked this conversation as resolved.
Show resolved Hide resolved

dev = qml.device("default.qubit", shots = 1)

@qml.qnode(dev)
def circuit():

# the third index is encoded in the control wires [0, 1]
qml.BasisEmbedding(2, wires = [0,1])

qml.QROM(bitstrings = bitstrings,
control_wires = [0,1],
target_wires = [2,3,4],
work_wires = [5,6,7])
KetpuntoG marked this conversation as resolved.
Show resolved Hide resolved

return qml.sample(wires = [2,3,4])

.. code-block:: pycon

>>> print(circuit())
[1 1 0]


.. details::
:title: Usage Details

This template takes as input three different sets of wires. The first one is ``control_wires`` which is used
to encode the desired index. Therefore, if we have :math:`m` bitstrings, we need
at least :math:`\lceil \log_2(m)\rceil` control wires.

The second set of wires is ``target_wires`` which stores the bitstrings.
For instance, if the bitstring is "0110", we will need four target wires. Internally, the bitstrings are
encoded using the :class:`~.BasisEmbedding` template.


The ``work_wires`` are the auxiliary qubits used by the template to reduce the number of gates required.
Let :math:`k` be the number of work wires. If :math:`k = 0`, the template is equivalent to executing :class:`~.Select`.
Following the idea in [`arXiv:1812.00954 <https://arxiv.org/abs/1812.00954>`__], auxiliary qubits can be used to
load more than one bitstring in parallel . Let :math:`\lambda` be
the number of bitstrings we want to store in parallel, assumed to be a power of :math:`2`.
Then, :math:`k = l \cdot (\lambda-1)` work wires are needed,
where :math:`l` is the length of the bitstrings.
KetpuntoG marked this conversation as resolved.
Show resolved Hide resolved

The QROM template has two variants. The first one (``clean = False``) is based on [`arXiv:1812.00954 <https://arxiv.org/abs/1812.00954>`__] that alterates the state in the ``work_wires``.
The second one (``clean = True``), based on [`arXiv:1902.02134 <https://arxiv.org/abs/1902.02134>`__], solves that issue by
returning ``work_wires`` to their initial state. This technique can be applied when the ``work_wires`` are not
initialized to zero.

"""

def __init__(
KetpuntoG marked this conversation as resolved.
Show resolved Hide resolved
self, bitstrings, control_wires, target_wires, work_wires, clean=True, id=None
): # pylint: disable=too-many-arguments

KetpuntoG marked this conversation as resolved.
Show resolved Hide resolved
control_wires = qml.wires.Wires(control_wires)
target_wires = qml.wires.Wires(target_wires)

work_wires = qml.wires.Wires(work_wires) if work_wires else qml.wires.Wires([])

self.hyperparameters["bitstrings"] = tuple(bitstrings)
self.hyperparameters["control_wires"] = control_wires
self.hyperparameters["target_wires"] = target_wires
self.hyperparameters["work_wires"] = work_wires
self.hyperparameters["clean"] = clean

if work_wires:
if any(wire in work_wires for wire in control_wires):
KetpuntoG marked this conversation as resolved.
Show resolved Hide resolved
raise ValueError("Control wires should be different from work wires.")

if any(wire in work_wires for wire in target_wires):
raise ValueError("Target wires should be different from work wires.")

if any(wire in control_wires for wire in target_wires):
raise ValueError("Target wires should be different from control wires.")

if 2 ** len(control_wires) < len(bitstrings):
raise ValueError(
f"Not enough control wires ({len(control_wires)}) for the desired number of "
+ f"bitstrings ({len(bitstrings)}). At least {int(math.ceil(math.log2(len(bitstrings))))} control "
+ "wires are required."
)

if len(bitstrings[0]) != len(target_wires):
raise ValueError("Bitstring length must match the number of target wires.")

all_wires = target_wires + control_wires + work_wires
super().__init__(wires=all_wires, id=id)

def _flatten(self):
metadata = tuple((key, value) for key, value in self.hyperparameters.items())
return tuple(), metadata

@classmethod
def _unflatten(cls, data, metadata):
hyperparams_dict = dict(metadata)
return cls(**hyperparams_dict)

def __repr__(self):
return f"QROM(control_wires={self.control_wires}, target_wires={self.target_wires}, work_wires={self.work_wires}, clean={self.clean})"

def map_wires(self, wire_map: dict):
new_dict = {
key: [wire_map.get(w, w) for w in self.hyperparameters[key]]
for key in ["target_wires", "control_wires", "work_wires"]
}

return QROM(
self.bitstrings,
new_dict["control_wires"],
new_dict["target_wires"],
new_dict["work_wires"],
self.clean,
)

def __copy__(self):
"""Copy this op"""
cls = self.__class__
copied_op = cls.__new__(cls)

for attr, value in vars(self).items():
setattr(copied_op, attr, value)

return copied_op

def decomposition(self): # pylint: disable=arguments-differ

return self.compute_decomposition(
self.bitstrings,
control_wires=self.control_wires,
target_wires=self.target_wires,
work_wires=self.work_wires,
clean=self.clean,
)

@staticmethod
def compute_decomposition(
KetpuntoG marked this conversation as resolved.
Show resolved Hide resolved
bitstrings, control_wires, target_wires, work_wires, clean
): # pylint: disable=arguments-differ
with qml.QueuingManager.stop_recording():

swap_wires = target_wires + work_wires

# number of operators we store per column (power of 2)
depth = len(swap_wires) // len(target_wires)
depth = int(2 ** np.floor(np.log2(depth)))

ops = [qml.BasisEmbedding(int(bits, 2), wires=target_wires) for bits in bitstrings]
ops_identity = ops + [qml.I(target_wires)] * int(2 ** len(control_wires) - len(ops))

n_columns = len(ops) // depth if len(ops) % depth == 0 else len(ops) // depth + 1
new_ops = []
for i in range(n_columns):
column_ops = []
for j in range(depth):
dic_map = {
ops_identity[i * depth + j].wires[l]: swap_wires[j * len(target_wires) + l]
for l in range(len(target_wires))
}
column_ops.append(qml.map_wires(ops_identity[i * depth + j], dic_map))
new_ops.append(qml.prod(*column_ops))

# Select block
n_control_select_wires = int(math.ceil(math.log2(2 ** len(control_wires) / depth)))
control_select_wires = control_wires[:n_control_select_wires]

select_ops = []
if control_select_wires:
select_ops += [qml.Select(new_ops, control=control_select_wires)]
else:
select_ops = new_ops

# Swap block
control_swap_wires = control_wires[n_control_select_wires:]
swap_ops = []
for ind in range(len(control_swap_wires)):
for j in range(2**ind):
new_op = qml.prod(_multi_swap)(
swap_wires[(j) * len(target_wires) : (j + 1) * len(target_wires)],
swap_wires[
(j + 2**ind)
* len(target_wires) : (j + 2 ** (ind + 1))
* len(target_wires)
],
)
swap_ops.insert(0, qml.ctrl(new_op, control=control_swap_wires[-ind - 1]))

if not clean:
# Based on this paper (Fig 1.c): https://arxiv.org/abs/1812.00954
decomp_ops = select_ops + swap_ops

else:
# Based on this paper (Fig 4): https://arxiv.org/abs/1902.02134
adjoint_swap_ops = swap_ops[::-1]
hadamard_ops = [qml.Hadamard(wires=w) for w in target_wires]

decomp_ops = 2 * (hadamard_ops + adjoint_swap_ops + select_ops + swap_ops)

if qml.QueuingManager.recording():
for op in decomp_ops:
qml.apply(op)

return decomp_ops

@classmethod
def _primitive_bind_call(cls, *args, **kwargs):
return cls._primitive.bind(*args, **kwargs)
KetpuntoG marked this conversation as resolved.
Show resolved Hide resolved

@property
def bitstrings(self):
"""bitstrings to be added."""
return self.hyperparameters["bitstrings"]

@property
def control_wires(self):
"""The control wires."""
return self.hyperparameters["control_wires"]

@property
def target_wires(self):
"""The wires where the bitstring is loaded."""
return self.hyperparameters["target_wires"]

@property
def work_wires(self):
"""The wires where the index is specified."""
return self.hyperparameters["work_wires"]

@property
def wires(self):
"""All wires involved in the operation."""
return (
self.hyperparameters["control_wires"]
+ self.hyperparameters["target_wires"]
+ self.hyperparameters["work_wires"]
)

@property
def clean(self):
"""Boolean to select the version of QROM."""
return self.hyperparameters["clean"]
Loading
Loading