From 6c1e7958b8754bac252471cf9a30b2137036b27d Mon Sep 17 00:00:00 2001 From: KetpuntoG <65235481+KetpuntoG@users.noreply.github.com> Date: Mon, 13 May 2024 19:14:19 -0400 Subject: [PATCH 01/41] qrom --- pennylane/templates/subroutines/qrom.py | 210 ++++++++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 pennylane/templates/subroutines/qrom.py diff --git a/pennylane/templates/subroutines/qrom.py b/pennylane/templates/subroutines/qrom.py new file mode 100644 index 00000000000..b904651248f --- /dev/null +++ b/pennylane/templates/subroutines/qrom.py @@ -0,0 +1,210 @@ +# 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. +""" + +import pennylane as qml +from pennylane.operation import Operation +import numpy as np +import itertools +import math +import copy + + +class QROM(Operation): + + def _flatten(self): + return (self.ops), (self.control) + + @classmethod + def _unflatten(cls, data, metadata): + return cls(data, metadata) + + def __repr__(self): + return f"QROM(ops={self.ops}, control={self.control}, work_wires={self.work_wires})" + + def __init__(self, bitstrings, target_wires, control_wires, work_wires, clean=True, id=None): + + control_wires = qml.wires.Wires(control_wires) + target_wires = qml.wires.Wires(target_wires) + work_wires = qml.wires.Wires(work_wires) + + self.hyperparameters["bitstrings"] = bitstrings + self.hyperparameters["target_wires"] = target_wires + self.hyperparameters["control_wires"] = control_wires + self.hyperparameters["work_wires"] = work_wires + self.hyperparameters["clean"] = clean + + if 2 ** len(control_wires) < len(bitstrings): + raise ValueError( + f"Not enough control wires ({len(control_wires)}) for the desired number of " + + f"operations ({len(bitstrings)}). At least {int(math.ceil(math.log2(len(bitstrings))))} control " + + "wires required." + ) + + if any(wire in work_wires for wire in control_wires): + 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.") + + all_wires = target_wires + control_wires + work_wires + super().__init__(wires=all_wires, id=id) + + def map_wires(self, wire_map: dict): + new_target_wires = [ + wire_map.get(wire, wire) for wire in self.hyperparameters["target_wires"] + ] + new_control_wires = [ + wire_map.get(wire, wire) for wire in self.hyperparameters["control_wires"] + ] + new_work_wires = [wire_map.get(wire, wire) for wire in self.hyperparameters["work_wires"]] + return QROM( + self.bitstrings, new_target_wires, new_control_wires, new_work_wires, self.clean + ) + + @staticmethod + def multi_swap(wires1, wires2): + for wire1, wire2 in zip(wires1, wires2): + qml.SWAP(wires=[wire1, wire2]) + + def __copy__(self): + """Copy this op""" + cls = self.__class__ + copied_op = cls.__new__(cls) + + new_data = copy.copy(self.data) + + for attr, value in vars(self).items(): + if attr != "data": + setattr(copied_op, attr, value) + + copied_op.data = new_data + + return copied_op + + def decomposition(self): + + return self.compute_decomposition( + self.bitstrings, + control_wires=self.control_wires, + work_wires=self.work_wires, + target_wires=self.target_wires, + clean=self.clean, + ) + + @staticmethod + def compute_decomposition(bitstrings, target_wires, control_wires, work_wires, clean): + # BasisEmbedding applied to embed the bitstrings + ops = [qml.BasisEmbedding(int(bits, 2), wires=target_wires) for bits in bitstrings] + + swap_wires = target_wires + work_wires # wires available to apply the operators + depth = len(swap_wires) // len(target_wires) # number of operators we store per column (power of 2) + depth = int(2 ** np.floor(np.log2(depth))) + + c_sel_wires = control_wires[ + : int(math.ceil(math.log2(2 ** len(control_wires) / depth))) + ] # control wires used in the Select block + c_swap_wires = control_wires[len(c_sel_wires) :] # control wires used in the Swap block + + # with qml.QueuingManager.stop_recording(): + ops_I = ops + [qml.I(target_wires)] * int(2 ** len(control_wires) - len(ops)) + + decomp_ops = [] + + # Select block + sel_ops = [] + bin_indx = list(itertools.product([0, 1], repeat=len(c_sel_wires))) + length = len(ops) // depth if len(ops) % depth == 0 else len(ops) // depth + 1 + for i, bin in enumerate(bin_indx[:length]): + new_op = qml.prod( + *[ + qml.map_wires( + ops_I[i * depth + j], + { + ops_I[i * depth + j].wires[l]: swap_wires[j * len(target_wires) + l] + for l in range(len(target_wires)) + }, + ) + for j in range(depth) + ] + ) + sel_ops.append( + qml.ctrl(new_op, control=control_wires[: len(c_sel_wires)], control_values=bin) + ) + + # Swap block + # Todo: This should be optimized + swap_ops = [] + bin_indx = list(itertools.product([0, 1], repeat=len(c_swap_wires))) + for i, bin in enumerate(bin_indx[1:depth]): + new_op = qml.prod(QROM.multi_swap)( + target_wires, swap_wires[(i + 1) * len(target_wires) : (i + 2) * len(target_wires)] + ) + swap_ops.append(qml.ctrl(new_op, control=c_swap_wires, control_values=bin)) + + if not clean: + # Based on this paper: https://arxiv.org/pdf/1812.00954 + decomp_ops += sel_ops + swap_ops + + else: + # Based on this paper: https://arxiv.org/abs/1902.02134 + + for _ in range(2): + + for w in target_wires: + decomp_ops.append(qml.Hadamard(wires=w)) + + # TODO: It should be adjoint the last one + decomp_ops += swap_ops + sel_ops + swap_ops + + return decomp_ops + + @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 that choose the version ussed.""" + return self.hyperparameters["clean"] From 7cd3add5f9b8ea55f3ac72bb315c805517153d11 Mon Sep 17 00:00:00 2001 From: KetpuntoG <65235481+KetpuntoG@users.noreply.github.com> Date: Tue, 14 May 2024 16:57:17 -0400 Subject: [PATCH 02/41] adding tests, fixing some issues --- pennylane/templates/subroutines/__init__.py | 1 + pennylane/templates/subroutines/qrom.py | 174 ++++++++------- tests/templates/test_subroutines/test_qrom.py | 202 ++++++++++++++++++ 3 files changed, 303 insertions(+), 74 deletions(-) create mode 100644 tests/templates/test_subroutines/test_qrom.py diff --git a/pennylane/templates/subroutines/__init__.py b/pennylane/templates/subroutines/__init__.py index 0e5a8b2caac..b0acee37f5f 100644 --- a/pennylane/templates/subroutines/__init__.py +++ b/pennylane/templates/subroutines/__init__.py @@ -43,3 +43,4 @@ from .reflection import Reflection from .amplitude_amplification import AmplitudeAmplification from .qubitization import Qubitization +from .qrom import QROM diff --git a/pennylane/templates/subroutines/qrom.py b/pennylane/templates/subroutines/qrom.py index b904651248f..4901a7b9912 100644 --- a/pennylane/templates/subroutines/qrom.py +++ b/pennylane/templates/subroutines/qrom.py @@ -26,39 +26,46 @@ class QROM(Operation): def _flatten(self): - return (self.ops), (self.control) + data = (self.hyperparameters["b"],) + metadata = tuple((key, value) for key, value in self.hyperparameters.items() if key != "b") + return data, metadata @classmethod def _unflatten(cls, data, metadata): - return cls(data, metadata) + b = data[0] + hyperparams_dict = dict(metadata) + return cls(b, **hyperparams_dict) def __repr__(self): - return f"QROM(ops={self.ops}, control={self.control}, work_wires={self.work_wires})" + return f"QROM(control_wires={self.control_wires}, target_wires={self.target_wires} work_wires={self.work_wires})" - def __init__(self, bitstrings, target_wires, control_wires, work_wires, clean=True, id=None): + def __init__(self, b, target_wires, control_wires, work_wires, clean=True, id=None): control_wires = qml.wires.Wires(control_wires) target_wires = qml.wires.Wires(target_wires) - work_wires = qml.wires.Wires(work_wires) - self.hyperparameters["bitstrings"] = bitstrings + if work_wires: + work_wires = qml.wires.Wires(work_wires) + + self.hyperparameters["b"] = b self.hyperparameters["target_wires"] = target_wires self.hyperparameters["control_wires"] = control_wires self.hyperparameters["work_wires"] = work_wires self.hyperparameters["clean"] = clean - if 2 ** len(control_wires) < len(bitstrings): + if 2 ** len(control_wires) < len(b): raise ValueError( f"Not enough control wires ({len(control_wires)}) for the desired number of " - + f"operations ({len(bitstrings)}). At least {int(math.ceil(math.log2(len(bitstrings))))} control " + + f"operations ({len(b)}). At least {int(math.ceil(math.log2(len(b))))} control " + "wires required." ) - if any(wire in work_wires for wire in control_wires): - raise ValueError("Control wires should be different from work wires.") + if work_wires: + if any(wire in work_wires for wire in control_wires): + 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 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.") @@ -73,10 +80,13 @@ def map_wires(self, wire_map: dict): new_control_wires = [ wire_map.get(wire, wire) for wire in self.hyperparameters["control_wires"] ] - new_work_wires = [wire_map.get(wire, wire) for wire in self.hyperparameters["work_wires"]] - return QROM( - self.bitstrings, new_target_wires, new_control_wires, new_work_wires, self.clean - ) + if self.hyperparameters["work_wires"]: + new_work_wires = [ + wire_map.get(wire, wire) for wire in self.hyperparameters["work_wires"] + ] + else: + new_work_wires = [] + return QROM(self.b, new_target_wires, new_control_wires, new_work_wires, self.clean) @staticmethod def multi_swap(wires1, wires2): @@ -101,7 +111,7 @@ def __copy__(self): def decomposition(self): return self.compute_decomposition( - self.bitstrings, + self.b, control_wires=self.control_wires, work_wires=self.work_wires, target_wires=self.target_wires, @@ -109,76 +119,92 @@ def decomposition(self): ) @staticmethod - def compute_decomposition(bitstrings, target_wires, control_wires, work_wires, clean): + def compute_decomposition(b, target_wires, control_wires, work_wires, clean): # BasisEmbedding applied to embed the bitstrings - ops = [qml.BasisEmbedding(int(bits, 2), wires=target_wires) for bits in bitstrings] - - swap_wires = target_wires + work_wires # wires available to apply the operators - depth = len(swap_wires) // len(target_wires) # number of operators we store per column (power of 2) - depth = int(2 ** np.floor(np.log2(depth))) - - c_sel_wires = control_wires[ - : int(math.ceil(math.log2(2 ** len(control_wires) / depth))) - ] # control wires used in the Select block - c_swap_wires = control_wires[len(c_sel_wires) :] # control wires used in the Swap block - - # with qml.QueuingManager.stop_recording(): - ops_I = ops + [qml.I(target_wires)] * int(2 ** len(control_wires) - len(ops)) - - decomp_ops = [] - - # Select block - sel_ops = [] - bin_indx = list(itertools.product([0, 1], repeat=len(c_sel_wires))) - length = len(ops) // depth if len(ops) % depth == 0 else len(ops) // depth + 1 - for i, bin in enumerate(bin_indx[:length]): - new_op = qml.prod( - *[ - qml.map_wires( - ops_I[i * depth + j], - { - ops_I[i * depth + j].wires[l]: swap_wires[j * len(target_wires) + l] - for l in range(len(target_wires)) - }, + with qml.QueuingManager.stop_recording(): + ops = [qml.BasisEmbedding(int(bits, 2), wires=target_wires) for bits in b] + + if work_wires: + swap_wires = target_wires + work_wires # wires available to apply the operators + else: + swap_wires = target_wires + depth = len(swap_wires) // len( + target_wires + ) # number of operators we store per column (power of 2) + depth = int(2 ** np.floor(np.log2(depth))) + + c_sel_wires = control_wires[ + : int(math.ceil(math.log2(2 ** len(control_wires) / depth))) + ] # control wires used in the Select block + c_swap_wires = control_wires[len(c_sel_wires) :] # control wires used in the Swap block + + ops_I = ops + [qml.I(target_wires)] * int(2 ** len(control_wires) - len(ops)) + + decomp_ops = [] + + length = len(ops) // depth if len(ops) % depth == 0 else len(ops) // depth + 1 + + s_ops = [] + for i in range(length): + s_ops.append( + qml.prod( + *[ + qml.map_wires( + ops_I[i * depth + j], + { + ops_I[i * depth + j].wires[l]: swap_wires[ + j * len(target_wires) + l + ] + for l in range(len(target_wires)) + }, + ) + for j in range(depth) + ] ) - for j in range(depth) - ] - ) - sel_ops.append( - qml.ctrl(new_op, control=control_wires[: len(c_sel_wires)], control_values=bin) - ) + ) + + sel_ops = [qml.Select(s_ops, control=control_wires[: len(c_sel_wires)])] + + # Swap block + swap_ops = [] + for ind, wire in enumerate(c_swap_wires): + for j in range(2**ind): + new_op = qml.prod(QROM.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.append(qml.ctrl(new_op, control=wire)) - # Swap block - # Todo: This should be optimized - swap_ops = [] - bin_indx = list(itertools.product([0, 1], repeat=len(c_swap_wires))) - for i, bin in enumerate(bin_indx[1:depth]): - new_op = qml.prod(QROM.multi_swap)( - target_wires, swap_wires[(i + 1) * len(target_wires) : (i + 2) * len(target_wires)] - ) - swap_ops.append(qml.ctrl(new_op, control=c_swap_wires, control_values=bin)) + adjoint_swap_ops = swap_ops[::-1] - if not clean: - # Based on this paper: https://arxiv.org/pdf/1812.00954 - decomp_ops += sel_ops + swap_ops + if not clean: + # Based on this paper: https://arxiv.org/pdf/1812.00954 + decomp_ops += sel_ops + swap_ops - else: - # Based on this paper: https://arxiv.org/abs/1902.02134 + else: + # Based on this paper: https://arxiv.org/abs/1902.02134 + + for _ in range(2): - for _ in range(2): + for w in target_wires: + decomp_ops.append(qml.Hadamard(wires=w)) - for w in target_wires: - decomp_ops.append(qml.Hadamard(wires=w)) + decomp_ops += swap_ops + sel_ops + adjoint_swap_ops - # TODO: It should be adjoint the last one - decomp_ops += swap_ops + sel_ops + swap_ops + if qml.QueuingManager.recording(): + for op in decomp_ops: + qml.apply(op) return decomp_ops @property - def bitstrings(self): + def b(self): """bitstrings to be added.""" - return self.hyperparameters["bitstrings"] + return self.hyperparameters["b"] @property def control_wires(self): diff --git a/tests/templates/test_subroutines/test_qrom.py b/tests/templates/test_subroutines/test_qrom.py new file mode 100644 index 00000000000..65203fd8073 --- /dev/null +++ b/tests/templates/test_subroutines/test_qrom.py @@ -0,0 +1,202 @@ +# 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. +""" +Tests for the QROM template. +""" + +import copy + +import pytest + +import pennylane as qml +from pennylane import numpy as np +from pennylane.templates.subroutines.qubitization import _positive_coeffs_hamiltonian + + +def test_standard_checks(): + """Run standard validity tests.""" + bitstrings = ["000", "001", "111", "011", "000", "101", "110", "111"] + + op = qml.QROM(bitstrings, control_wires=[0, 1, 2], target_wires=[3, 4, 5], work_wires=[6, 7, 8]) + qml.ops.functions.assert_valid(op) + + +class TestQROM: + """Tests that the template defines the correct decomposition.""" + + @pytest.mark.parametrize( + ("b", "target_wires", "control_wires", "work_wires", "clean"), + [ + ( + ["11", "01", "00", "10"], + [0, 1], + [2, 3], + [4, 5], + True, + ), + ( + ["01", "01", "00", "00"], + ["a", "b"], + [2, 3], + [4, 5, 6], + False, + ), + ( + ["111", "001", "000", "100"], + [0, 1, "b"], + [2, 3], + ["a", 5, 6], + False, + ), + ( + ["1111", "0101", "0100", "1010"], + [0, 1, "b", "d"], + [2, 3], + ["a", 5, 6, 7], + True, + ), + ], + ) + def test_operation_result(self, b, target_wires, control_wires, work_wires, clean): + """Test the correctness of the Select template output.""" + dev = qml.device("default.qubit", shots=1) + + @qml.qnode(dev) + def circuit(j): + qml.BasisEmbedding(j, wires=control_wires) + + qml.QROM(b, target_wires, control_wires, work_wires, clean) + return qml.sample(wires=target_wires) + + @qml.qnode(dev) + def circuit_test(j): + for ind, bit in enumerate(b[j]): + if bit == "1": + qml.PauliX(wires=target_wires[ind]) + return qml.sample(wires=target_wires) + + for j in range(2 ** len(control_wires)): + assert np.allclose(circuit(j), circuit_test(j)) + + @pytest.mark.parametrize( + ("b", "target_wires", "control_wires", "work_wires"), + [ + ( + ["11", "01", "00", "10"], + [0, 1], + [2, 3], + [4, 5], + ), + ( + ["01", "01", "00", "00"], + ["a", "b"], + [2, 3], + [4, 5, 6], + ), + ( + ["111", "001", "000", "100"], + [0, 1, "b"], + [2, 3], + ["a", 5, 6], + ), + ( + ["1111", "0101", "0100", "1010"], + [0, 1, "b", "d"], + [2, 3], + ["a", 5, 6, 7], + ), + ], + ) + def test_work_wires_output(self, b, target_wires, control_wires, work_wires): + """Tests that the ``clean = True`` version don't modify the initial state in work_wires.""" + dev = qml.device("default.qubit", shots=1) + + @qml.qnode(dev) + def circuit(): + + # Initialize the work wires to a non-zero state + for ind, wire in enumerate(work_wires): + qml.RX(ind, wires=wire) + + for wire in control_wires: + qml.Hadamard(wires=wire) + + qml.QROM(b, target_wires, control_wires, work_wires) + + for ind, wire in enumerate(work_wires): + qml.RX(-ind, wires=wire) + + return qml.probs(wires=work_wires) + + assert np.isclose(circuit()[0], 1.0) + + def test_decomposition(self): + """Unit test checking that compute_decomposition and decomposition work as expected.""" + qrom_decomposition = qml.QROM( + ["1", "0", "0", "1"], control_wires=[0, 1], target_wires=[2], work_wires=[3], clean=True + ).decomposition() + + expected_gates = [ + qml.Hadamard(wires=[2]), + qml.CSWAP(wires=[1, 2, 3]), + qml.Select( + ops=( + qml.BasisEmbedding(1, wires=[2]) @ qml.BasisEmbedding(0, wires=[3]), + qml.BasisEmbedding(0, wires=[2]) @ qml.BasisEmbedding(1, wires=[3]), + ), + control=[0], + ), + qml.CSWAP(wires=[1, 2, 3]), + qml.Hadamard(wires=[2]), + qml.CSWAP(wires=[1, 2, 3]), + qml.Select( + ops=( + qml.BasisEmbedding(1, wires=[2]) @ qml.BasisEmbedding(0, wires=[3]), + qml.BasisEmbedding(0, wires=[2]) @ qml.BasisEmbedding(1, wires=[3]), + ), + control=0, + ), + qml.CSWAP(wires=[1, 2, 3]), + ] + + assert all(qml.equal(op1, op2) for op1, op2 in zip(qrom_decomposition, expected_gates)) + + +@pytest.mark.parametrize( + ("control_wires", "target_wires", "work_wires", "msg_match"), + [ + ( + [0, 1, 2], + [0, 3], + [4, 5], + "Target wires should be different from control wires.", + ), + ( + [0, 1, 2], + [4], + [2, 5], + "Control wires should be different from work wires.", + ), + ( + [0, 1, 2], + [4], + [4], + "Target wires should be different from work wires.", + ), + ], +) +def test_wires_error(control_wires, target_wires, work_wires, msg_match): + """Test an error is raised when a control wire is in one of the ops""" + with pytest.raises(ValueError, match=msg_match): + qml.QROM(["1"] * 8, target_wires, control_wires, work_wires) From 5fa06839c3f1d5f5b011b5b57af27afed8ffd009 Mon Sep 17 00:00:00 2001 From: KetpuntoG <65235481+KetpuntoG@users.noreply.github.com> Date: Wed, 15 May 2024 09:35:07 -0400 Subject: [PATCH 03/41] pylint --- pennylane/templates/subroutines/qrom.py | 9 ++++----- tests/templates/test_subroutines/test_qrom.py | 17 +++++++++++++---- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/pennylane/templates/subroutines/qrom.py b/pennylane/templates/subroutines/qrom.py index 4901a7b9912..75be3543378 100644 --- a/pennylane/templates/subroutines/qrom.py +++ b/pennylane/templates/subroutines/qrom.py @@ -15,12 +15,11 @@ This submodule contains the template for QROM. """ -import pennylane as qml -from pennylane.operation import Operation import numpy as np -import itertools import math import copy +import pennylane as qml +from pennylane.operation import Operation class QROM(Operation): @@ -37,7 +36,7 @@ def _unflatten(cls, data, metadata): return cls(b, **hyperparams_dict) def __repr__(self): - return f"QROM(control_wires={self.control_wires}, target_wires={self.target_wires} work_wires={self.work_wires})" + return f"QROM(target_wires={self.target_wires}, control_wires={self.control_wires}, work_wires={self.work_wires})" def __init__(self, b, target_wires, control_wires, work_wires, clean=True, id=None): @@ -108,7 +107,7 @@ def __copy__(self): return copied_op - def decomposition(self): + def decomposition(self): # pylint: disable=arguments-differ return self.compute_decomposition( self.b, diff --git a/tests/templates/test_subroutines/test_qrom.py b/tests/templates/test_subroutines/test_qrom.py index 65203fd8073..80adfce62ad 100644 --- a/tests/templates/test_subroutines/test_qrom.py +++ b/tests/templates/test_subroutines/test_qrom.py @@ -15,13 +15,10 @@ Tests for the QROM template. """ -import copy - import pytest import pennylane as qml from pennylane import numpy as np -from pennylane.templates.subroutines.qubitization import _positive_coeffs_hamiltonian def test_standard_checks(): @@ -114,7 +111,7 @@ def circuit_test(j): ["1111", "0101", "0100", "1010"], [0, 1, "b", "d"], [2, 3], - ["a", 5, 6, 7], + None, ), ], ) @@ -200,3 +197,15 @@ def test_wires_error(control_wires, target_wires, work_wires, msg_match): """Test an error is raised when a control wire is in one of the ops""" with pytest.raises(ValueError, match=msg_match): qml.QROM(["1"] * 8, target_wires, control_wires, work_wires) + + +def test_repr(): + """Test that the __repr__ method works as expected.""" + op = str( + qml.QROM( + ["1", "0", "0", "1"], control_wires=[0, 1], target_wires=[2], work_wires=[3], clean=True + ) + ) + res = op.__repr__() + expected = "QROM(target_wires=, control_wires=, work_wires=)" + assert res == expected From bde0cf4b04efc7bcec435a7be00807d8e9da900b Mon Sep 17 00:00:00 2001 From: KetpuntoG <65235481+KetpuntoG@users.noreply.github.com> Date: Wed, 15 May 2024 11:11:32 -0400 Subject: [PATCH 04/41] docs --- doc/releases/changelog-dev.md | 25 ++++++++ pennylane/templates/subroutines/qrom.py | 59 +++++++++++++++++++ tests/templates/test_subroutines/test_qrom.py | 4 +- 3 files changed, 87 insertions(+), 1 deletion(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index ed88f98fcb4..ecfcb2fbb0d 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -4,6 +4,31 @@

New features since last release

+* 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 + bitstrings = ["010", "111", "110", "000"] + + dev = qml.device("default.qubit", shots = 1) + @qml.qnode(dev) + def circuit(): + + # third index + qml.BasisEmbedding(2, wires = [0,1]) + + qml.QROM(b = 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] + ``` +

Improvements 🛠

* The sorting order of parameter-shift terms is now guaranteed to resolve ties in the absolute value with the sign of the shifts. diff --git a/pennylane/templates/subroutines/qrom.py b/pennylane/templates/subroutines/qrom.py index 75be3543378..d9479ff8417 100644 --- a/pennylane/templates/subroutines/qrom.py +++ b/pennylane/templates/subroutines/qrom.py @@ -23,6 +23,65 @@ 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: + b (list[str]): the bitstrings to be encoded + target_wires (Sequence[int]): the wires where the bitstring is loaded + control_wires (Sequence[int]): the wires where the indexes are specified + work_wires (Sequence[int]): the auxiliar wires used for the computation + clean (bool): if True, the work wires are not altered by operator, default is ``True`` + + .. note:: + The position of the bitstrings in the list determines which index store that bitstring. + + **Example** + + In this example the QROM is applied and the target wires are measured to get the third bitstring. + + .. code-block:: + + bitstrings = ["010", "111", "110", "000"] + + dev = qml.device("default.qubit", shots = 1) + + @qml.qnode(dev) + def circuit(): + + # third index + qml.BasisEmbedding(2, wires = [0,1]) + + qml.QROM(b = bitstrings, + control_wires = [0,1], + target_wires = [2,3,4], + work_wires = [5,6,7]) + + return qml.sample(wires = [2,3,4]) + + .. code-block:: pycon + + >>> print(circuit()) + [1 1 0] + + + .. details:: + :title: Usage Details + + The ``work_wires`` are the auxiliary qubits used by the template. If :math:`m` is the number of target wires, + :math:`m \cdot 2^{\lambda-1}` work wires can be used, where :math:`\lambda` is the number of bitstrings + we want to store per column. In case it is not a power of two, this template uses the closest approximation. + See `arXiv:1812.00954 `__ for more information. + + The version applied by setting ``clean = True`` is able to work with ``work_wires`` that are not initialized to zero. + In `arXiv:1902.02134 `__ you could find more details. + """ def _flatten(self): data = (self.hyperparameters["b"],) diff --git a/tests/templates/test_subroutines/test_qrom.py b/tests/templates/test_subroutines/test_qrom.py index 80adfce62ad..73f2a6d9521 100644 --- a/tests/templates/test_subroutines/test_qrom.py +++ b/tests/templates/test_subroutines/test_qrom.py @@ -65,7 +65,9 @@ class TestQROM: ), ], ) - def test_operation_result(self, b, target_wires, control_wires, work_wires, clean): + def test_operation_result( + self, b, target_wires, control_wires, work_wires, clean + ): # pylint: disable=arguments-differ """Test the correctness of the Select template output.""" dev = qml.device("default.qubit", shots=1) From e047b38e8a9af07900c6390f56e91d4d22426a0d Mon Sep 17 00:00:00 2001 From: KetpuntoG <65235481+KetpuntoG@users.noreply.github.com> Date: Wed, 15 May 2024 14:17:04 -0400 Subject: [PATCH 05/41] pylint --- pennylane/templates/subroutines/qrom.py | 3 ++- tests/templates/test_subroutines/test_qrom.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pennylane/templates/subroutines/qrom.py b/pennylane/templates/subroutines/qrom.py index d9479ff8417..52a0d4abffa 100644 --- a/pennylane/templates/subroutines/qrom.py +++ b/pennylane/templates/subroutines/qrom.py @@ -40,7 +40,8 @@ class QROM(Operation): clean (bool): if True, the work wires are not altered by operator, default is ``True`` .. note:: - The position of the bitstrings in the list determines which index store that bitstring. + + The position of the bitstrings in the list determines which index store that bitstring. **Example** diff --git a/tests/templates/test_subroutines/test_qrom.py b/tests/templates/test_subroutines/test_qrom.py index 73f2a6d9521..7afc7cd9141 100644 --- a/tests/templates/test_subroutines/test_qrom.py +++ b/tests/templates/test_subroutines/test_qrom.py @@ -67,7 +67,7 @@ class TestQROM: ) def test_operation_result( self, b, target_wires, control_wires, work_wires, clean - ): # pylint: disable=arguments-differ + ): # pylint: disable=too-many-arguments """Test the correctness of the Select template output.""" dev = qml.device("default.qubit", shots=1) From bd792c1aa056721f8324c6371802f6e794add3e2 Mon Sep 17 00:00:00 2001 From: KetpuntoG <65235481+KetpuntoG@users.noreply.github.com> Date: Wed, 15 May 2024 14:29:39 -0400 Subject: [PATCH 06/41] Update qrom.py --- pennylane/templates/subroutines/qrom.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pennylane/templates/subroutines/qrom.py b/pennylane/templates/subroutines/qrom.py index 52a0d4abffa..bfcb0efcf5d 100644 --- a/pennylane/templates/subroutines/qrom.py +++ b/pennylane/templates/subroutines/qrom.py @@ -15,9 +15,10 @@ This submodule contains the template for QROM. """ -import numpy as np + import math import copy +import numpy as np import pennylane as qml from pennylane.operation import Operation From b4305d9bf66890d8d130c2455e5f8f12f767e42b Mon Sep 17 00:00:00 2001 From: KetpuntoG <65235481+KetpuntoG@users.noreply.github.com> Date: Wed, 15 May 2024 14:37:43 -0400 Subject: [PATCH 07/41] Update qrom.py --- pennylane/templates/subroutines/qrom.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pennylane/templates/subroutines/qrom.py b/pennylane/templates/subroutines/qrom.py index bfcb0efcf5d..81e38499d65 100644 --- a/pennylane/templates/subroutines/qrom.py +++ b/pennylane/templates/subroutines/qrom.py @@ -179,7 +179,9 @@ def decomposition(self): # pylint: disable=arguments-differ ) @staticmethod - def compute_decomposition(b, target_wires, control_wires, work_wires, clean): + def compute_decomposition( + b, target_wires, control_wires, work_wires, clean + ): # pylint: disable=arguments-differ # BasisEmbedding applied to embed the bitstrings with qml.QueuingManager.stop_recording(): ops = [qml.BasisEmbedding(int(bits, 2), wires=target_wires) for bits in b] From 4870e39a7c5c59cdc0cda2a88f1aaf2c23cb52c8 Mon Sep 17 00:00:00 2001 From: KetpuntoG <65235481+KetpuntoG@users.noreply.github.com> Date: Thu, 16 May 2024 11:17:01 -0400 Subject: [PATCH 08/41] tests fix --- pennylane/templates/subroutines/qrom.py | 4 +++- tests/templates/test_subroutines/test_qrom.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pennylane/templates/subroutines/qrom.py b/pennylane/templates/subroutines/qrom.py index 81e38499d65..925b8b6cc9c 100644 --- a/pennylane/templates/subroutines/qrom.py +++ b/pennylane/templates/subroutines/qrom.py @@ -99,7 +99,9 @@ def _unflatten(cls, data, metadata): def __repr__(self): return f"QROM(target_wires={self.target_wires}, control_wires={self.control_wires}, work_wires={self.work_wires})" - def __init__(self, b, target_wires, control_wires, work_wires, clean=True, id=None): + def __init__( + self, b, target_wires, control_wires, work_wires, clean=True, id=None + ): # pylint: disable=too-many-arguments control_wires = qml.wires.Wires(control_wires) target_wires = qml.wires.Wires(target_wires) diff --git a/tests/templates/test_subroutines/test_qrom.py b/tests/templates/test_subroutines/test_qrom.py index 7afc7cd9141..a880f1f4809 100644 --- a/tests/templates/test_subroutines/test_qrom.py +++ b/tests/templates/test_subroutines/test_qrom.py @@ -208,6 +208,6 @@ def test_repr(): ["1", "0", "0", "1"], control_wires=[0, 1], target_wires=[2], work_wires=[3], clean=True ) ) - res = op.__repr__() + res = op.__repr__()[1:-1] expected = "QROM(target_wires=, control_wires=, work_wires=)" assert res == expected From 88d1930fb953757f60232116c74aecdb556fb5b2 Mon Sep 17 00:00:00 2001 From: KetpuntoG <65235481+KetpuntoG@users.noreply.github.com> Date: Thu, 16 May 2024 11:25:17 -0400 Subject: [PATCH 09/41] docs --- pennylane/templates/subroutines/qrom.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pennylane/templates/subroutines/qrom.py b/pennylane/templates/subroutines/qrom.py index 925b8b6cc9c..ac8f85c653b 100644 --- a/pennylane/templates/subroutines/qrom.py +++ b/pennylane/templates/subroutines/qrom.py @@ -152,6 +152,7 @@ def map_wires(self, wire_map: dict): @staticmethod 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]) From 28ca92723894de1ac81de14069b51e41eddc1753 Mon Sep 17 00:00:00 2001 From: KetpuntoG <65235481+KetpuntoG@users.noreply.github.com> Date: Thu, 16 May 2024 11:37:21 -0400 Subject: [PATCH 10/41] Update qrom.py --- pennylane/templates/subroutines/qrom.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pennylane/templates/subroutines/qrom.py b/pennylane/templates/subroutines/qrom.py index ac8f85c653b..cb7fed090ac 100644 --- a/pennylane/templates/subroutines/qrom.py +++ b/pennylane/templates/subroutines/qrom.py @@ -19,6 +19,7 @@ import math import copy import numpy as np + import pennylane as qml from pennylane.operation import Operation From e56be8e8c93e3befd44d015245271fd7694949b6 Mon Sep 17 00:00:00 2001 From: KetpuntoG <65235481+KetpuntoG@users.noreply.github.com> Date: Thu, 16 May 2024 13:16:29 -0400 Subject: [PATCH 11/41] Update qrom.py --- pennylane/templates/subroutines/qrom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane/templates/subroutines/qrom.py b/pennylane/templates/subroutines/qrom.py index cb7fed090ac..34df5dfc317 100644 --- a/pennylane/templates/subroutines/qrom.py +++ b/pennylane/templates/subroutines/qrom.py @@ -16,8 +16,8 @@ """ -import math import copy +import math import numpy as np import pennylane as qml From 134fcad0f14db7147f991de199bb753cedcd3e49 Mon Sep 17 00:00:00 2001 From: KetpuntoG <65235481+KetpuntoG@users.noreply.github.com> Date: Thu, 16 May 2024 13:16:38 -0400 Subject: [PATCH 12/41] Update test_qubitization.py --- tests/templates/test_subroutines/test_qubitization.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/templates/test_subroutines/test_qubitization.py b/tests/templates/test_subroutines/test_qubitization.py index 5230c4ed401..327b245b6ed 100644 --- a/tests/templates/test_subroutines/test_qubitization.py +++ b/tests/templates/test_subroutines/test_qubitization.py @@ -16,7 +16,6 @@ """ import copy - import pytest import pennylane as qml From d76e41525f2ea6167644863cf173a2a5482fd962 Mon Sep 17 00:00:00 2001 From: KetpuntoG <65235481+KetpuntoG@users.noreply.github.com> Date: Thu, 16 May 2024 13:28:05 -0400 Subject: [PATCH 13/41] Update qrom.py --- pennylane/templates/subroutines/qrom.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pennylane/templates/subroutines/qrom.py b/pennylane/templates/subroutines/qrom.py index 34df5dfc317..427e03490d4 100644 --- a/pennylane/templates/subroutines/qrom.py +++ b/pennylane/templates/subroutines/qrom.py @@ -18,6 +18,7 @@ import copy import math + import numpy as np import pennylane as qml From ce7d6ba20f8a556589dd1289d99dd2ea74bb563e Mon Sep 17 00:00:00 2001 From: KetpuntoG <65235481+KetpuntoG@users.noreply.github.com> Date: Thu, 16 May 2024 13:38:02 -0400 Subject: [PATCH 14/41] Update test_qubitization.py --- tests/templates/test_subroutines/test_qubitization.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/templates/test_subroutines/test_qubitization.py b/tests/templates/test_subroutines/test_qubitization.py index 327b245b6ed..5230c4ed401 100644 --- a/tests/templates/test_subroutines/test_qubitization.py +++ b/tests/templates/test_subroutines/test_qubitization.py @@ -16,6 +16,7 @@ """ import copy + import pytest import pennylane as qml From 657f6523c5a98b9ca39e3f321ad32683d2e60139 Mon Sep 17 00:00:00 2001 From: KetpuntoG <65235481+KetpuntoG@users.noreply.github.com> Date: Thu, 16 May 2024 14:22:42 -0400 Subject: [PATCH 15/41] Update test_qrom.py --- tests/templates/test_subroutines/test_qrom.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/templates/test_subroutines/test_qrom.py b/tests/templates/test_subroutines/test_qrom.py index a880f1f4809..7d1e594ceb0 100644 --- a/tests/templates/test_subroutines/test_qrom.py +++ b/tests/templates/test_subroutines/test_qrom.py @@ -53,7 +53,7 @@ class TestQROM: ["111", "001", "000", "100"], [0, 1, "b"], [2, 3], - ["a", 5, 6], + None, False, ), ( @@ -113,7 +113,7 @@ def circuit_test(j): ["1111", "0101", "0100", "1010"], [0, 1, "b", "d"], [2, 3], - None, + ["a", 5, 6, 7], ), ], ) From 4e04ec3d1c97acf66d652b6f1667ae46fd53f278 Mon Sep 17 00:00:00 2001 From: KetpuntoG <65235481+KetpuntoG@users.noreply.github.com> Date: Thu, 16 May 2024 15:24:12 -0400 Subject: [PATCH 16/41] comments to clarify code --- pennylane/templates/subroutines/qrom.py | 42 +++++++++++++------------ 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/pennylane/templates/subroutines/qrom.py b/pennylane/templates/subroutines/qrom.py index 427e03490d4..d8f5b30dff3 100644 --- a/pennylane/templates/subroutines/qrom.py +++ b/pennylane/templates/subroutines/qrom.py @@ -117,13 +117,6 @@ def __init__( self.hyperparameters["work_wires"] = work_wires self.hyperparameters["clean"] = clean - if 2 ** len(control_wires) < len(b): - raise ValueError( - f"Not enough control wires ({len(control_wires)}) for the desired number of " - + f"operations ({len(b)}). At least {int(math.ceil(math.log2(len(b))))} control " - + "wires required." - ) - if work_wires: if any(wire in work_wires for wire in control_wires): raise ValueError("Control wires should be different from work wires.") @@ -187,32 +180,37 @@ def decomposition(self): # pylint: disable=arguments-differ def compute_decomposition( b, target_wires, control_wires, work_wires, clean ): # pylint: disable=arguments-differ - # BasisEmbedding applied to embed the bitstrings with qml.QueuingManager.stop_recording(): - ops = [qml.BasisEmbedding(int(bits, 2), wires=target_wires) for bits in b] if work_wires: swap_wires = target_wires + work_wires # wires available to apply the operators else: swap_wires = target_wires - depth = len(swap_wires) // len( - target_wires - ) # number of operators we store per column (power of 2) + + # 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))) + # control wires used in the Select block c_sel_wires = control_wires[ : int(math.ceil(math.log2(2 ** len(control_wires) / depth))) - ] # control wires used in the Select block - c_swap_wires = control_wires[len(c_sel_wires) :] # control wires used in the Swap block + ] - ops_I = ops + [qml.I(target_wires)] * int(2 ** len(control_wires) - len(ops)) + # control wires used in the Swap block + c_swap_wires = control_wires[len(c_sel_wires) :] - decomp_ops = [] + ops = [qml.BasisEmbedding(int(bits, 2), wires=target_wires) for bits in b] + ops_I = ops + [qml.I(target_wires)] * int(2 ** len(control_wires) - len(ops)) + # number of new operators after grouping length = len(ops) // depth if len(ops) % depth == 0 else len(ops) // depth + 1 + # operators to be included in the Select block s_ops = [] + for i in range(length): + + # map the wires to put more than one bitstring per column. s_ops.append( qml.prod( *[ @@ -230,6 +228,7 @@ def compute_decomposition( ) ) + # Select block sel_ops = [qml.Select(s_ops, control=control_wires[: len(c_sel_wires)])] # Swap block @@ -246,15 +245,18 @@ def compute_decomposition( ) swap_ops.append(qml.ctrl(new_op, control=wire)) - adjoint_swap_ops = swap_ops[::-1] - if not clean: - # Based on this paper: https://arxiv.org/pdf/1812.00954 - decomp_ops += sel_ops + swap_ops + # Based on this paper: https://arxiv.org/abs/1812.00954 + decomp_ops = sel_ops + swap_ops else: # Based on this paper: https://arxiv.org/abs/1902.02134 + # Adjoint Swap block + adjoint_swap_ops = swap_ops[::-1] + + decomp_ops = [] + for _ in range(2): for w in target_wires: From aeead30416e271b2b3c4f9bedaac8f26666ace73 Mon Sep 17 00:00:00 2001 From: KetpuntoG <65235481+KetpuntoG@users.noreply.github.com> Date: Thu, 16 May 2024 15:35:33 -0400 Subject: [PATCH 17/41] Update qrom.py --- pennylane/templates/subroutines/qrom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane/templates/subroutines/qrom.py b/pennylane/templates/subroutines/qrom.py index d8f5b30dff3..bfe7ed024f5 100644 --- a/pennylane/templates/subroutines/qrom.py +++ b/pennylane/templates/subroutines/qrom.py @@ -229,7 +229,7 @@ def compute_decomposition( ) # Select block - sel_ops = [qml.Select(s_ops, control=control_wires[: len(c_sel_wires)])] + sel_ops = [qml.Select(s_ops, control=c_sel_wires)] # Swap block swap_ops = [] From 4fae9d515ccc4987c1acf9689c0b19c42b8aaf63 Mon Sep 17 00:00:00 2001 From: KetpuntoG <65235481+KetpuntoG@users.noreply.github.com> Date: Thu, 16 May 2024 16:12:26 -0400 Subject: [PATCH 18/41] Update qrom.py --- pennylane/templates/subroutines/qrom.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pennylane/templates/subroutines/qrom.py b/pennylane/templates/subroutines/qrom.py index bfe7ed024f5..ebc34e74093 100644 --- a/pennylane/templates/subroutines/qrom.py +++ b/pennylane/templates/subroutines/qrom.py @@ -141,8 +141,7 @@ def map_wires(self, wire_map: dict): new_work_wires = [ wire_map.get(wire, wire) for wire in self.hyperparameters["work_wires"] ] - else: - new_work_wires = [] + return QROM(self.b, new_target_wires, new_control_wires, new_work_wires, self.clean) @staticmethod From 8515c59b8f1e4694a186c3b8ea94b20bed14af36 Mon Sep 17 00:00:00 2001 From: KetpuntoG <65235481+KetpuntoG@users.noreply.github.com> Date: Fri, 17 May 2024 09:43:46 -0400 Subject: [PATCH 19/41] Update qrom.py --- pennylane/templates/subroutines/qrom.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pennylane/templates/subroutines/qrom.py b/pennylane/templates/subroutines/qrom.py index ebc34e74093..a31ff6caa47 100644 --- a/pennylane/templates/subroutines/qrom.py +++ b/pennylane/templates/subroutines/qrom.py @@ -137,6 +137,8 @@ def map_wires(self, wire_map: dict): new_control_wires = [ wire_map.get(wire, wire) for wire in self.hyperparameters["control_wires"] ] + + new_work_wires = [] if self.hyperparameters["work_wires"]: new_work_wires = [ wire_map.get(wire, wire) for wire in self.hyperparameters["work_wires"] @@ -228,7 +230,9 @@ def compute_decomposition( ) # Select block - sel_ops = [qml.Select(s_ops, control=c_sel_wires)] + sel_ops = [] + if c_sel_wires: + sel_ops += [qml.Select(s_ops, control=c_sel_wires)] # Swap block swap_ops = [] From 6cd706cea069497fb9685b16220845fc1e669af1 Mon Sep 17 00:00:00 2001 From: KetpuntoG <65235481+KetpuntoG@users.noreply.github.com> Date: Thu, 23 May 2024 11:51:45 -0400 Subject: [PATCH 20/41] b -> bitstrings --- pennylane/templates/subroutines/qrom.py | 28 +++++++++---------- tests/templates/test_subroutines/test_qrom.py | 14 +++++----- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/pennylane/templates/subroutines/qrom.py b/pennylane/templates/subroutines/qrom.py index a31ff6caa47..b2337824a22 100644 --- a/pennylane/templates/subroutines/qrom.py +++ b/pennylane/templates/subroutines/qrom.py @@ -36,7 +36,7 @@ class QROM(Operation): where :math:`b_i` is the bitstring associated with index :math:`i`. Args: - b (list[str]): the bitstrings to be encoded + bitstrings (list[str]): the bitstrings to be encoded target_wires (Sequence[int]): the wires where the bitstring is loaded control_wires (Sequence[int]): the wires where the indexes are specified work_wires (Sequence[int]): the auxiliar wires used for the computation @@ -62,7 +62,7 @@ def circuit(): # third index qml.BasisEmbedding(2, wires = [0,1]) - qml.QROM(b = bitstrings, + qml.QROM(bitstrings = bitstrings, control_wires = [0,1], target_wires = [2,3,4], work_wires = [5,6,7]) @@ -88,21 +88,21 @@ def circuit(): """ def _flatten(self): - data = (self.hyperparameters["b"],) - metadata = tuple((key, value) for key, value in self.hyperparameters.items() if key != "b") + data = (self.hyperparameters["bitstrings"],) + metadata = tuple((key, value) for key, value in self.hyperparameters.items() if key != "bitstrings") return data, metadata @classmethod def _unflatten(cls, data, metadata): - b = data[0] + bitstrings = data[0] hyperparams_dict = dict(metadata) - return cls(b, **hyperparams_dict) + return cls(bitstrings, **hyperparams_dict) def __repr__(self): return f"QROM(target_wires={self.target_wires}, control_wires={self.control_wires}, work_wires={self.work_wires})" def __init__( - self, b, target_wires, control_wires, work_wires, clean=True, id=None + self, bitstrings, target_wires, control_wires, work_wires, clean=True, id=None ): # pylint: disable=too-many-arguments control_wires = qml.wires.Wires(control_wires) @@ -111,7 +111,7 @@ def __init__( if work_wires: work_wires = qml.wires.Wires(work_wires) - self.hyperparameters["b"] = b + self.hyperparameters["bitstrings"] = bitstrings self.hyperparameters["target_wires"] = target_wires self.hyperparameters["control_wires"] = control_wires self.hyperparameters["work_wires"] = work_wires @@ -144,7 +144,7 @@ def map_wires(self, wire_map: dict): wire_map.get(wire, wire) for wire in self.hyperparameters["work_wires"] ] - return QROM(self.b, new_target_wires, new_control_wires, new_work_wires, self.clean) + return QROM(self.bitstrings, new_target_wires, new_control_wires, new_work_wires, self.clean) @staticmethod def multi_swap(wires1, wires2): @@ -170,7 +170,7 @@ def __copy__(self): def decomposition(self): # pylint: disable=arguments-differ return self.compute_decomposition( - self.b, + self.bitstrings, control_wires=self.control_wires, work_wires=self.work_wires, target_wires=self.target_wires, @@ -179,7 +179,7 @@ def decomposition(self): # pylint: disable=arguments-differ @staticmethod def compute_decomposition( - b, target_wires, control_wires, work_wires, clean + bitstrings, target_wires, control_wires, work_wires, clean ): # pylint: disable=arguments-differ with qml.QueuingManager.stop_recording(): @@ -200,7 +200,7 @@ def compute_decomposition( # control wires used in the Swap block c_swap_wires = control_wires[len(c_sel_wires) :] - ops = [qml.BasisEmbedding(int(bits, 2), wires=target_wires) for bits in b] + ops = [qml.BasisEmbedding(int(bits, 2), wires=target_wires) for bits in bitstrings] ops_I = ops + [qml.I(target_wires)] * int(2 ** len(control_wires) - len(ops)) # number of new operators after grouping @@ -274,9 +274,9 @@ def compute_decomposition( return decomp_ops @property - def b(self): + def bitstrings(self): """bitstrings to be added.""" - return self.hyperparameters["b"] + return self.hyperparameters["bitstrings"] @property def control_wires(self): diff --git a/tests/templates/test_subroutines/test_qrom.py b/tests/templates/test_subroutines/test_qrom.py index 7d1e594ceb0..cd1f065b218 100644 --- a/tests/templates/test_subroutines/test_qrom.py +++ b/tests/templates/test_subroutines/test_qrom.py @@ -33,7 +33,7 @@ class TestQROM: """Tests that the template defines the correct decomposition.""" @pytest.mark.parametrize( - ("b", "target_wires", "control_wires", "work_wires", "clean"), + ("bitstrings", "target_wires", "control_wires", "work_wires", "clean"), [ ( ["11", "01", "00", "10"], @@ -66,7 +66,7 @@ class TestQROM: ], ) def test_operation_result( - self, b, target_wires, control_wires, work_wires, clean + self, bitstrings, target_wires, control_wires, work_wires, clean ): # pylint: disable=too-many-arguments """Test the correctness of the Select template output.""" dev = qml.device("default.qubit", shots=1) @@ -75,12 +75,12 @@ def test_operation_result( def circuit(j): qml.BasisEmbedding(j, wires=control_wires) - qml.QROM(b, target_wires, control_wires, work_wires, clean) + qml.QROM(bitstrings, target_wires, control_wires, work_wires, clean) return qml.sample(wires=target_wires) @qml.qnode(dev) def circuit_test(j): - for ind, bit in enumerate(b[j]): + for ind, bit in enumerate(bitstrings[j]): if bit == "1": qml.PauliX(wires=target_wires[ind]) return qml.sample(wires=target_wires) @@ -89,7 +89,7 @@ def circuit_test(j): assert np.allclose(circuit(j), circuit_test(j)) @pytest.mark.parametrize( - ("b", "target_wires", "control_wires", "work_wires"), + ("bitstrings", "target_wires", "control_wires", "work_wires"), [ ( ["11", "01", "00", "10"], @@ -117,7 +117,7 @@ def circuit_test(j): ), ], ) - def test_work_wires_output(self, b, target_wires, control_wires, work_wires): + def test_work_wires_output(self, bitstrings, target_wires, control_wires, work_wires): """Tests that the ``clean = True`` version don't modify the initial state in work_wires.""" dev = qml.device("default.qubit", shots=1) @@ -131,7 +131,7 @@ def circuit(): for wire in control_wires: qml.Hadamard(wires=wire) - qml.QROM(b, target_wires, control_wires, work_wires) + qml.QROM(bitstrings, target_wires, control_wires, work_wires) for ind, wire in enumerate(work_wires): qml.RX(-ind, wires=wire) From f711d91b23c92c979fda0ffd12be61e7639eb348 Mon Sep 17 00:00:00 2001 From: KetpuntoG <65235481+KetpuntoG@users.noreply.github.com> Date: Thu, 23 May 2024 14:15:35 -0400 Subject: [PATCH 21/41] pylint --- doc/releases/changelog-dev.md | 2 +- pennylane/templates/subroutines/qrom.py | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 6354278fb36..9345ad1f483 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -17,7 +17,7 @@ # third index qml.BasisEmbedding(2, wires = [0,1]) - qml.QROM(b = bitstrings, + qml.QROM(bitstrings = bitstrings, control_wires = [0,1], target_wires = [2,3,4], work_wires = [5,6,7]) diff --git a/pennylane/templates/subroutines/qrom.py b/pennylane/templates/subroutines/qrom.py index b2337824a22..4b4194607cd 100644 --- a/pennylane/templates/subroutines/qrom.py +++ b/pennylane/templates/subroutines/qrom.py @@ -89,7 +89,9 @@ def circuit(): def _flatten(self): data = (self.hyperparameters["bitstrings"],) - metadata = tuple((key, value) for key, value in self.hyperparameters.items() if key != "bitstrings") + metadata = tuple( + (key, value) for key, value in self.hyperparameters.items() if key != "bitstrings" + ) return data, metadata @classmethod @@ -144,7 +146,9 @@ def map_wires(self, wire_map: dict): wire_map.get(wire, wire) for wire in self.hyperparameters["work_wires"] ] - return QROM(self.bitstrings, new_target_wires, new_control_wires, new_work_wires, self.clean) + return QROM( + self.bitstrings, new_target_wires, new_control_wires, new_work_wires, self.clean + ) @staticmethod def multi_swap(wires1, wires2): From ad2fab8ab36be956bf7b8926ce56439a5f1d67d2 Mon Sep 17 00:00:00 2001 From: Guillermo Alonso-Linaje <65235481+KetpuntoG@users.noreply.github.com> Date: Fri, 24 May 2024 14:46:36 -0400 Subject: [PATCH 22/41] Apply suggestions from code review Co-authored-by: soranjh <40344468+soranjh@users.noreply.github.com> --- pennylane/templates/subroutines/qrom.py | 2 +- tests/templates/test_subroutines/test_qrom.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pennylane/templates/subroutines/qrom.py b/pennylane/templates/subroutines/qrom.py index 4b4194607cd..e3fc00eef3b 100644 --- a/pennylane/templates/subroutines/qrom.py +++ b/pennylane/templates/subroutines/qrom.py @@ -39,7 +39,7 @@ class QROM(Operation): bitstrings (list[str]): the bitstrings to be encoded target_wires (Sequence[int]): the wires where the bitstring is loaded control_wires (Sequence[int]): the wires where the indexes are specified - work_wires (Sequence[int]): the auxiliar wires used for the computation + 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`` .. note:: diff --git a/tests/templates/test_subroutines/test_qrom.py b/tests/templates/test_subroutines/test_qrom.py index cd1f065b218..948eeaebaa7 100644 --- a/tests/templates/test_subroutines/test_qrom.py +++ b/tests/templates/test_subroutines/test_qrom.py @@ -141,7 +141,7 @@ def circuit(): assert np.isclose(circuit()[0], 1.0) def test_decomposition(self): - """Unit test checking that compute_decomposition and decomposition work as expected.""" + """Test that compute_decomposition and decomposition work as expected.""" qrom_decomposition = qml.QROM( ["1", "0", "0", "1"], control_wires=[0, 1], target_wires=[2], work_wires=[3], clean=True ).decomposition() From f2a3fe6155f1cebb2954f33c3361b1bf66f1efa0 Mon Sep 17 00:00:00 2001 From: KetpuntoG <65235481+KetpuntoG@users.noreply.github.com> Date: Fri, 24 May 2024 16:03:08 -0400 Subject: [PATCH 23/41] Some Soran comments [skip-ci] --- pennylane/templates/subroutines/qrom.py | 131 +++++++----------- tests/templates/test_subroutines/test_qrom.py | 19 ++- 2 files changed, 62 insertions(+), 88 deletions(-) diff --git a/pennylane/templates/subroutines/qrom.py b/pennylane/templates/subroutines/qrom.py index e3fc00eef3b..9b216a6cb12 100644 --- a/pennylane/templates/subroutines/qrom.py +++ b/pennylane/templates/subroutines/qrom.py @@ -25,6 +25,12 @@ 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]) + + class QROM(Operation): r"""Applies the QROM operator. @@ -87,24 +93,8 @@ def circuit(): In `arXiv:1902.02134 `__ you could find more details. """ - def _flatten(self): - data = (self.hyperparameters["bitstrings"],) - metadata = tuple( - (key, value) for key, value in self.hyperparameters.items() if key != "bitstrings" - ) - return data, metadata - - @classmethod - def _unflatten(cls, data, metadata): - bitstrings = data[0] - hyperparams_dict = dict(metadata) - return cls(bitstrings, **hyperparams_dict) - - def __repr__(self): - return f"QROM(target_wires={self.target_wires}, control_wires={self.control_wires}, work_wires={self.work_wires})" - def __init__( - self, bitstrings, target_wires, control_wires, work_wires, clean=True, id=None + self, bitstrings, control_wires, target_wires, work_wires, clean=True, id=None ): # pylint: disable=too-many-arguments control_wires = qml.wires.Wires(control_wires) @@ -114,8 +104,8 @@ def __init__( work_wires = qml.wires.Wires(work_wires) self.hyperparameters["bitstrings"] = bitstrings - self.hyperparameters["target_wires"] = target_wires self.hyperparameters["control_wires"] = control_wires + self.hyperparameters["target_wires"] = target_wires self.hyperparameters["work_wires"] = work_wires self.hyperparameters["clean"] = clean @@ -132,6 +122,22 @@ def __init__( all_wires = target_wires + control_wires + work_wires super().__init__(wires=all_wires, id=id) + def _flatten(self): + data = (self.hyperparameters["bitstrings"],) + metadata = tuple( + (key, value) for key, value in self.hyperparameters.items() if key != "bitstrings" + ) + return data, metadata + + @classmethod + def _unflatten(cls, data, metadata): + bitstrings = data[0] + hyperparams_dict = dict(metadata) + return cls(bitstrings, **hyperparams_dict) + + def __repr__(self): + return f"QROM(control_wires={self.control_wires}, target_wires={self.target_wires}, work_wires={self.work_wires})" + def map_wires(self, wire_map: dict): new_target_wires = [ wire_map.get(wire, wire) for wire in self.hyperparameters["target_wires"] @@ -147,15 +153,9 @@ def map_wires(self, wire_map: dict): ] return QROM( - self.bitstrings, new_target_wires, new_control_wires, new_work_wires, self.clean + self.bitstrings, new_control_wires, new_target_wires, new_work_wires, self.clean ) - @staticmethod - 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]) - def __copy__(self): """Copy this op""" cls = self.__class__ @@ -176,19 +176,19 @@ def decomposition(self): # pylint: disable=arguments-differ return self.compute_decomposition( self.bitstrings, control_wires=self.control_wires, - work_wires=self.work_wires, target_wires=self.target_wires, + work_wires=self.work_wires, clean=self.clean, ) @staticmethod def compute_decomposition( - bitstrings, target_wires, control_wires, work_wires, clean + bitstrings, control_wires, target_wires, work_wires, clean ): # pylint: disable=arguments-differ with qml.QueuingManager.stop_recording(): if work_wires: - swap_wires = target_wires + work_wires # wires available to apply the operators + swap_wires = target_wires + work_wires else: swap_wires = target_wires @@ -196,53 +196,35 @@ def compute_decomposition( depth = len(swap_wires) // len(target_wires) depth = int(2 ** np.floor(np.log2(depth))) - # control wires used in the Select block - c_sel_wires = control_wires[ - : int(math.ceil(math.log2(2 ** len(control_wires) / depth))) - ] - - # control wires used in the Swap block - c_swap_wires = control_wires[len(c_sel_wires) :] - ops = [qml.BasisEmbedding(int(bits, 2), wires=target_wires) for bits in bitstrings] - ops_I = ops + [qml.I(target_wires)] * int(2 ** len(control_wires) - len(ops)) - - # number of new operators after grouping - length = len(ops) // depth if len(ops) % depth == 0 else len(ops) // depth + 1 - - # operators to be included in the Select block - s_ops = [] - - for i in range(length): - - # map the wires to put more than one bitstring per column. - s_ops.append( - qml.prod( - *[ - qml.map_wires( - ops_I[i * depth + j], - { - ops_I[i * depth + j].wires[l]: swap_wires[ - j * len(target_wires) + l - ] - for l in range(len(target_wires)) - }, - ) - for j in range(depth) - ] - ) - ) + 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 - sel_ops = [] - if c_sel_wires: - sel_ops += [qml.Select(s_ops, control=c_sel_wires)] + 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)] # Swap block + control_swap_wires = control_wires[n_control_select_wires:] swap_ops = [] - for ind, wire in enumerate(c_swap_wires): + for ind, wire in enumerate(control_swap_wires): for j in range(2**ind): - new_op = qml.prod(QROM.multi_swap)( + new_op = qml.prod(_multi_swap)( swap_wires[(j) * len(target_wires) : (j + 1) * len(target_wires)], swap_wires[ (j + 2**ind) @@ -254,22 +236,15 @@ def compute_decomposition( if not clean: # Based on this paper: https://arxiv.org/abs/1812.00954 - decomp_ops = sel_ops + swap_ops + decomp_ops = select_ops + swap_ops else: # Based on this paper: https://arxiv.org/abs/1902.02134 - # Adjoint Swap block adjoint_swap_ops = swap_ops[::-1] + hadamard_ops = [qml.Hadamard(wires=w) for w in target_wires] - decomp_ops = [] - - for _ in range(2): - - for w in target_wires: - decomp_ops.append(qml.Hadamard(wires=w)) - - decomp_ops += swap_ops + sel_ops + adjoint_swap_ops + decomp_ops = 2 * (hadamard_ops + swap_ops + select_ops + adjoint_swap_ops) if qml.QueuingManager.recording(): for op in decomp_ops: diff --git a/tests/templates/test_subroutines/test_qrom.py b/tests/templates/test_subroutines/test_qrom.py index 948eeaebaa7..52a9a745105 100644 --- a/tests/templates/test_subroutines/test_qrom.py +++ b/tests/templates/test_subroutines/test_qrom.py @@ -68,14 +68,14 @@ class TestQROM: def test_operation_result( self, bitstrings, target_wires, control_wires, work_wires, clean ): # pylint: disable=too-many-arguments - """Test the correctness of the Select template output.""" + """Test the correctness of the QROM template output.""" dev = qml.device("default.qubit", shots=1) @qml.qnode(dev) def circuit(j): qml.BasisEmbedding(j, wires=control_wires) - qml.QROM(bitstrings, target_wires, control_wires, work_wires, clean) + qml.QROM(bitstrings, control_wires, target_wires, work_wires, clean) return qml.sample(wires=target_wires) @qml.qnode(dev) @@ -131,7 +131,7 @@ def circuit(): for wire in control_wires: qml.Hadamard(wires=wire) - qml.QROM(bitstrings, target_wires, control_wires, work_wires) + qml.QROM(bitstrings, control_wires, target_wires, work_wires) for ind, wire in enumerate(work_wires): qml.RX(-ind, wires=wire) @@ -198,16 +198,15 @@ def test_decomposition(self): def test_wires_error(control_wires, target_wires, work_wires, msg_match): """Test an error is raised when a control wire is in one of the ops""" with pytest.raises(ValueError, match=msg_match): - qml.QROM(["1"] * 8, target_wires, control_wires, work_wires) + qml.QROM(["1"] * 8, control_wires, target_wires, work_wires) def test_repr(): """Test that the __repr__ method works as expected.""" - op = str( - qml.QROM( - ["1", "0", "0", "1"], control_wires=[0, 1], target_wires=[2], work_wires=[3], clean=True - ) + + op = qml.QROM( + ["1", "0", "0", "1"], control_wires=[0, 1], target_wires=[2], work_wires=[3], clean=True ) - res = op.__repr__()[1:-1] - expected = "QROM(target_wires=, control_wires=, work_wires=)" + res = op.__repr__() + expected = "QROM(control_wires=, target_wires=, work_wires=)" assert res == expected From 314073199a7041d43e9b51de5cbb469e971306af Mon Sep 17 00:00:00 2001 From: KetpuntoG <65235481+KetpuntoG@users.noreply.github.com> Date: Mon, 27 May 2024 09:19:54 -0400 Subject: [PATCH 24/41] docs --- pennylane/templates/subroutines/qrom.py | 26 ++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/pennylane/templates/subroutines/qrom.py b/pennylane/templates/subroutines/qrom.py index 9b216a6cb12..314178526c2 100644 --- a/pennylane/templates/subroutines/qrom.py +++ b/pennylane/templates/subroutines/qrom.py @@ -43,15 +43,11 @@ class QROM(Operation): Args: bitstrings (list[str]): the bitstrings to be encoded - target_wires (Sequence[int]): the wires where the bitstring is loaded 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`` - .. note:: - - The position of the bitstrings in the list determines which index store that bitstring. - **Example** In this example the QROM is applied and the target wires are measured to get the third bitstring. @@ -84,10 +80,22 @@ def circuit(): .. details:: :title: Usage Details - The ``work_wires`` are the auxiliary qubits used by the template. If :math:`m` is the number of target wires, - :math:`m \cdot 2^{\lambda-1}` work wires can be used, where :math:`\lambda` is the number of bitstrings - we want to store per column. In case it is not a power of two, this template uses the closest approximation. - See `arXiv:1812.00954 `__ for more information. + This template takes as input three different sets of wires. The first one in ``control_wires``. This register + makes reference to the wires where we will introduce the index. Therefore, if we have :math:`m` bitstrings, we need + at least :math:`\lceil \log_2(m)\rceil` wires. + + The second set of wires is ``target_wires``. These are the wires where the bitstrings will be stored. + 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 `__, auxiliary qubits can be used to + load in parallel more than one bit string. Let :math:`\lambda` be + the number of bitstrings we want to store in parallel, which it is assumed to be a power of :math:`2`. + Then, :math:`k = m \cdot (\lambda-1)` work wires are needed, + where :math:`m` is the length of the bitstrings. The version applied by setting ``clean = True`` is able to work with ``work_wires`` that are not initialized to zero. In `arXiv:1902.02134 `__ you could find more details. From c75484742b8dad742c38ce05d89f8bdec23ccfa8 Mon Sep 17 00:00:00 2001 From: KetpuntoG <65235481+KetpuntoG@users.noreply.github.com> Date: Mon, 27 May 2024 10:24:08 -0400 Subject: [PATCH 25/41] provisional thumbnail + updating docs --- doc/_static/templates/qrom/thumbnail_qrom.png | Bin 0 -> 49886 bytes doc/introduction/templates.rst | 5 +++++ pennylane/templates/subroutines/qrom.py | 11 +++++++---- 3 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 doc/_static/templates/qrom/thumbnail_qrom.png diff --git a/doc/_static/templates/qrom/thumbnail_qrom.png b/doc/_static/templates/qrom/thumbnail_qrom.png new file mode 100644 index 0000000000000000000000000000000000000000..68414578eb4e439b827578a7e529c005e9dbd1a1 GIT binary patch literal 49886 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

(jd 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 literal 0 HcmV?d00001 diff --git a/doc/introduction/templates.rst b/doc/introduction/templates.rst index 1cda874fcbc..ab80bea745a 100644 --- a/doc/introduction/templates.rst +++ b/doc/introduction/templates.rst @@ -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/thumbnail_qrom.png + + .. raw:: html

diff --git a/pennylane/templates/subroutines/qrom.py b/pennylane/templates/subroutines/qrom.py index 314178526c2..b1d7d574a5d 100644 --- a/pennylane/templates/subroutines/qrom.py +++ b/pennylane/templates/subroutines/qrom.py @@ -94,11 +94,14 @@ def circuit(): Following the idea in `arXiv:1812.00954 `__, auxiliary qubits can be used to load in parallel more than one bit string. Let :math:`\lambda` be the number of bitstrings we want to store in parallel, which it is assumed to be a power of :math:`2`. - Then, :math:`k = m \cdot (\lambda-1)` work wires are needed, - where :math:`m` is the length of the bitstrings. + Then, :math:`k = l \cdot (\lambda-1)` work wires are needed, + where :math:`l` is the length of the bitstrings. + + The QROM template has two variants. The first one (``clean = False``) is based on the previous paper that creates garbage in the ``work_wires``. + The second one (``clean = True``), based on `arXiv:1902.02134 `__, solves that issue by + returning ``work_wires`` to their initial state. This technique is able to work with ``work_wires`` that are not + initialized to zero. - The version applied by setting ``clean = True`` is able to work with ``work_wires`` that are not initialized to zero. - In `arXiv:1902.02134 `__ you could find more details. """ def __init__( From c278e1c43c20789f6640a2744ea7c1a5da5ffd82 Mon Sep 17 00:00:00 2001 From: KetpuntoG <65235481+KetpuntoG@users.noreply.github.com> Date: Mon, 27 May 2024 10:26:43 -0400 Subject: [PATCH 26/41] thumbnail --- doc/_static/templates/qrom/qrom_thumbnail.png | Bin 0 -> 56407 bytes doc/_static/templates/qrom/thumbnail_qrom.png | Bin 49886 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 doc/_static/templates/qrom/qrom_thumbnail.png delete mode 100644 doc/_static/templates/qrom/thumbnail_qrom.png diff --git a/doc/_static/templates/qrom/qrom_thumbnail.png b/doc/_static/templates/qrom/qrom_thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..977465fc9c9530509370d345e3eba523ae336525 GIT binary patch literal 56407 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 F{2v@5K4<^{ literal 0 HcmV?d00001 diff --git a/doc/_static/templates/qrom/thumbnail_qrom.png b/doc/_static/templates/qrom/thumbnail_qrom.png deleted file mode 100644 index 68414578eb4e439b827578a7e529c005e9dbd1a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 49886 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

(jd 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 From d5added2b453b150858cce924a0618cc176bb34c Mon Sep 17 00:00:00 2001 From: KetpuntoG <65235481+KetpuntoG@users.noreply.github.com> Date: Mon, 27 May 2024 10:38:56 -0400 Subject: [PATCH 27/41] typo image --- doc/introduction/templates.rst | 2 +- pennylane/templates/subroutines/qrom.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/introduction/templates.rst b/doc/introduction/templates.rst index ab80bea745a..2969f5168b2 100644 --- a/doc/introduction/templates.rst +++ b/doc/introduction/templates.rst @@ -305,7 +305,7 @@ Other useful templates which do not belong to the previous categories can be fou .. gallery-item:: :description: :doc:`QROM <../code/api/pennylane.QROM>` - :figure: _static/templates/qrom/thumbnail_qrom.png + :figure: _static/templates/qrom/qrom_thumbnail.png .. raw:: html diff --git a/pennylane/templates/subroutines/qrom.py b/pennylane/templates/subroutines/qrom.py index b1d7d574a5d..c5d9a6c1e50 100644 --- a/pennylane/templates/subroutines/qrom.py +++ b/pennylane/templates/subroutines/qrom.py @@ -97,7 +97,7 @@ def circuit(): Then, :math:`k = l \cdot (\lambda-1)` work wires are needed, where :math:`l` is the length of the bitstrings. - The QROM template has two variants. The first one (``clean = False``) is based on the previous paper that creates garbage in the ``work_wires``. + The QROM template has two variants. The first one (``clean = False``) is based on the previous paper that alterates the state in the ``work_wires``. The second one (``clean = True``), based on `arXiv:1902.02134 `__, solves that issue by returning ``work_wires`` to their initial state. This technique is able to work with ``work_wires`` that are not initialized to zero. From 1f7af3745ed6dc26546dcb9c6646276dcec85052 Mon Sep 17 00:00:00 2001 From: KetpuntoG <65235481+KetpuntoG@users.noreply.github.com> Date: Mon, 27 May 2024 15:57:53 -0400 Subject: [PATCH 28/41] paper change notation --- pennylane/templates/subroutines/qrom.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pennylane/templates/subroutines/qrom.py b/pennylane/templates/subroutines/qrom.py index c5d9a6c1e50..ae57e240aed 100644 --- a/pennylane/templates/subroutines/qrom.py +++ b/pennylane/templates/subroutines/qrom.py @@ -229,6 +229,8 @@ def compute_decomposition( 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:] @@ -243,7 +245,7 @@ def compute_decomposition( * len(target_wires) ], ) - swap_ops.append(qml.ctrl(new_op, control=wire)) + swap_ops.insert(0, qml.ctrl(new_op, control=control_swap_wires[-ind - 1])) if not clean: # Based on this paper: https://arxiv.org/abs/1812.00954 @@ -255,7 +257,7 @@ def compute_decomposition( adjoint_swap_ops = swap_ops[::-1] hadamard_ops = [qml.Hadamard(wires=w) for w in target_wires] - decomp_ops = 2 * (hadamard_ops + swap_ops + select_ops + adjoint_swap_ops) + decomp_ops = 2 * (hadamard_ops + adjoint_swap_ops + select_ops + swap_ops) if qml.QueuingManager.recording(): for op in decomp_ops: From fa917f98c6ff542c34e8fb51e6c24b1337f4d0d0 Mon Sep 17 00:00:00 2001 From: KetpuntoG <65235481+KetpuntoG@users.noreply.github.com> Date: Mon, 27 May 2024 16:00:50 -0400 Subject: [PATCH 29/41] codecov --- tests/templates/test_subroutines/test_qrom.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/templates/test_subroutines/test_qrom.py b/tests/templates/test_subroutines/test_qrom.py index 52a9a745105..b908b2c2579 100644 --- a/tests/templates/test_subroutines/test_qrom.py +++ b/tests/templates/test_subroutines/test_qrom.py @@ -42,6 +42,13 @@ class TestQROM: [4, 5], True, ), + ( + ["11", "01", "00", "10"], + [0, 1], + [2, 3], + [4, 5, 6, 7, 8, 9], + True, + ), ( ["01", "01", "00", "00"], ["a", "b"], From 83180cc9c07b5ab6ebc8894f6b689fad55f40e46 Mon Sep 17 00:00:00 2001 From: KetpuntoG <65235481+KetpuntoG@users.noreply.github.com> Date: Mon, 27 May 2024 16:02:44 -0400 Subject: [PATCH 30/41] Update qrom.py code factor --- pennylane/templates/subroutines/qrom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane/templates/subroutines/qrom.py b/pennylane/templates/subroutines/qrom.py index ae57e240aed..5128ed1d333 100644 --- a/pennylane/templates/subroutines/qrom.py +++ b/pennylane/templates/subroutines/qrom.py @@ -235,7 +235,7 @@ def compute_decomposition( # Swap block control_swap_wires = control_wires[n_control_select_wires:] swap_ops = [] - for ind, wire in enumerate(control_swap_wires): + 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)], From b6f442886d4e220a6d43adb896f1a182d120d0e3 Mon Sep 17 00:00:00 2001 From: KetpuntoG <65235481+KetpuntoG@users.noreply.github.com> Date: Tue, 28 May 2024 11:23:11 -0400 Subject: [PATCH 31/41] [skip-ci] --- pennylane/templates/subroutines/qrom.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pennylane/templates/subroutines/qrom.py b/pennylane/templates/subroutines/qrom.py index 5128ed1d333..4598babb192 100644 --- a/pennylane/templates/subroutines/qrom.py +++ b/pennylane/templates/subroutines/qrom.py @@ -248,11 +248,11 @@ def compute_decomposition( swap_ops.insert(0, qml.ctrl(new_op, control=control_swap_wires[-ind - 1])) if not clean: - # Based on this paper: https://arxiv.org/abs/1812.00954 + # Based on this paper (Fig 1.c): https://arxiv.org/abs/1812.00954 decomp_ops = select_ops + swap_ops else: - # Based on this paper: https://arxiv.org/abs/1902.02134 + # 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] From 5afa6e4d612042d254748059c254c0b79fb3baed Mon Sep 17 00:00:00 2001 From: Guillermo Alonso-Linaje <65235481+KetpuntoG@users.noreply.github.com> Date: Tue, 4 Jun 2024 15:51:18 -0400 Subject: [PATCH 32/41] Apply suggestions from code review suggestions from code review Co-authored-by: Jay Soni Co-authored-by: soranjh <40344468+soranjh@users.noreply.github.com> --- pennylane/templates/subroutines/qrom.py | 40 ++++++++++--------------- 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/pennylane/templates/subroutines/qrom.py b/pennylane/templates/subroutines/qrom.py index 4598babb192..4253dedc3c0 100644 --- a/pennylane/templates/subroutines/qrom.py +++ b/pennylane/templates/subroutines/qrom.py @@ -15,7 +15,6 @@ This submodule contains the template for QROM. """ - import copy import math @@ -50,10 +49,11 @@ class QROM(Operation): **Example** - In this example the QROM is applied and the target wires are measured to get the third bitstring. + 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"] dev = qml.device("default.qubit", shots = 1) @@ -61,7 +61,7 @@ class QROM(Operation): @qml.qnode(dev) def circuit(): - # third index + # the third index is encoded in the control wires [0, 1] qml.BasisEmbedding(2, wires = [0,1]) qml.QROM(bitstrings = bitstrings, @@ -80,26 +80,26 @@ def circuit(): .. details:: :title: Usage Details - This template takes as input three different sets of wires. The first one in ``control_wires``. This register - makes reference to the wires where we will introduce the index. Therefore, if we have :math:`m` bitstrings, we need - at least :math:`\lceil \log_2(m)\rceil` wires. + 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``. These are the wires where the bitstrings will be stored. - For instance, if the bitstring is "0110", we will need four target wires. Internally the bitstrings are + The second set of wires is ``target_wires`` which store 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 `__, auxiliary qubits can be used to - load in parallel more than one bit string. Let :math:`\lambda` be - the number of bitstrings we want to store in parallel, which it is assumed to be a power of :math:`2`. + 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. The QROM template has two variants. The first one (``clean = False``) is based on the previous paper that alterates the state in the ``work_wires``. The second one (``clean = True``), based on `arXiv:1902.02134 `__, solves that issue by - returning ``work_wires`` to their initial state. This technique is able to work with ``work_wires`` that are not + returning ``work_wires`` to their initial state. This technique can be applied when the ``work_wires`` are not initialized to zero. """ @@ -111,8 +111,7 @@ def __init__( control_wires = qml.wires.Wires(control_wires) target_wires = qml.wires.Wires(target_wires) - if work_wires: - work_wires = qml.wires.Wires(work_wires) + work_wires = qml.wires.Wires(work_wires) if work_wires else qml.wires.Wires([]) self.hyperparameters["bitstrings"] = bitstrings self.hyperparameters["control_wires"] = control_wires @@ -121,7 +120,7 @@ def __init__( self.hyperparameters["clean"] = clean if work_wires: - if any(wire in work_wires for wire in control_wires): + if Wires.shared_wires([work_wires, control_wires, target_wires]): raise ValueError("Control wires should be different from work wires.") if any(wire in work_wires for wire in target_wires): @@ -172,14 +171,9 @@ def __copy__(self): cls = self.__class__ copied_op = cls.__new__(cls) - new_data = copy.copy(self.data) - for attr, value in vars(self).items(): - if attr != "data": setattr(copied_op, attr, value) - copied_op.data = new_data - return copied_op def decomposition(self): # pylint: disable=arguments-differ @@ -198,10 +192,7 @@ def compute_decomposition( ): # pylint: disable=arguments-differ with qml.QueuingManager.stop_recording(): - if work_wires: - swap_wires = target_wires + work_wires - else: - swap_wires = target_wires + swap_wires = target_wires + work_wires # number of operators we store per column (power of 2) depth = len(swap_wires) // len(target_wires) @@ -253,7 +244,6 @@ def compute_decomposition( 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] @@ -296,5 +286,5 @@ def wires(self): @property def clean(self): - """Boolean that choose the version ussed.""" + """Boolean to select the version of QROM.""" return self.hyperparameters["clean"] From 1e2876e5d8912a5c8fc072188b6dbe8e9dcbed74 Mon Sep 17 00:00:00 2001 From: KetpuntoG <65235481+KetpuntoG@users.noreply.github.com> Date: Tue, 4 Jun 2024 17:04:56 -0400 Subject: [PATCH 33/41] final comments review --- doc/releases/changelog-dev.md | 16 +++--- pennylane/templates/subroutines/qrom.py | 18 +++++-- tests/templates/test_subroutines/test_qrom.py | 51 +++++++++++++++---- 3 files changed, 63 insertions(+), 22 deletions(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 87898a35b28..79764a6a4a5 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -8,21 +8,23 @@ [(#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(): - # third index - qml.BasisEmbedding(2, wires = [0,1]) + # 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]) + 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]) + return qml.sample(wires = [2,3,4]) ``` ```pycon >>> print(circuit()) diff --git a/pennylane/templates/subroutines/qrom.py b/pennylane/templates/subroutines/qrom.py index 4253dedc3c0..642494a0131 100644 --- a/pennylane/templates/subroutines/qrom.py +++ b/pennylane/templates/subroutines/qrom.py @@ -97,7 +97,7 @@ def circuit(): Then, :math:`k = l \cdot (\lambda-1)` work wires are needed, where :math:`l` is the length of the bitstrings. - The QROM template has two variants. The first one (``clean = False``) is based on the previous paper that alterates the state in the ``work_wires``. + The QROM template has two variants. The first one (``clean = False``) is based on `arXiv:1812.00954 `__ that alterates the state in the ``work_wires``. The second one (``clean = True``), based on `arXiv: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. @@ -120,7 +120,7 @@ def __init__( self.hyperparameters["clean"] = clean if work_wires: - if Wires.shared_wires([work_wires, control_wires, target_wires]): + if any(wire in work_wires for wire in control_wires): raise ValueError("Control wires should be different from work wires.") if any(wire in work_wires for wire in target_wires): @@ -129,6 +129,16 @@ def __init__( 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 required." + ) + + if len(bitstrings[0]) != len(target_wires): + raise ValueError(f"Bitstring length must match the number of target wires.") + all_wires = target_wires + control_wires + work_wires super().__init__(wires=all_wires, id=id) @@ -146,7 +156,7 @@ def _unflatten(cls, data, metadata): return cls(bitstrings, **hyperparams_dict) def __repr__(self): - return f"QROM(control_wires={self.control_wires}, target_wires={self.target_wires}, work_wires={self.work_wires})" + 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_target_wires = [ @@ -172,7 +182,7 @@ def __copy__(self): copied_op = cls.__new__(cls) for attr, value in vars(self).items(): - setattr(copied_op, attr, value) + setattr(copied_op, attr, value) return copied_op diff --git a/tests/templates/test_subroutines/test_qrom.py b/tests/templates/test_subroutines/test_qrom.py index b908b2c2579..2f1cef16e7c 100644 --- a/tests/templates/test_subroutines/test_qrom.py +++ b/tests/templates/test_subroutines/test_qrom.py @@ -21,7 +21,7 @@ from pennylane import numpy as np -def test_standard_checks(): +def test_assert_valid_qrom(): """Run standard validity tests.""" bitstrings = ["000", "001", "111", "011", "000", "101", "110", "111"] @@ -30,7 +30,7 @@ def test_standard_checks(): class TestQROM: - """Tests that the template defines the correct decomposition.""" + """Test the qml.QROM template.""" @pytest.mark.parametrize( ("bitstrings", "target_wires", "control_wires", "work_wires", "clean"), @@ -85,15 +85,8 @@ def circuit(j): qml.QROM(bitstrings, control_wires, target_wires, work_wires, clean) return qml.sample(wires=target_wires) - @qml.qnode(dev) - def circuit_test(j): - for ind, bit in enumerate(bitstrings[j]): - if bit == "1": - qml.PauliX(wires=target_wires[ind]) - return qml.sample(wires=target_wires) - for j in range(2 ** len(control_wires)): - assert np.allclose(circuit(j), circuit_test(j)) + assert np.allclose(circuit(j), [int(bit) for bit in bitstrings[j]]) @pytest.mark.parametrize( ("bitstrings", "target_wires", "control_wires", "work_wires"), @@ -178,6 +171,19 @@ def test_decomposition(self): assert all(qml.equal(op1, op2) for op1, op2 in zip(qrom_decomposition, expected_gates)) + def test_jit_compatible(self): + """Test that the template is compatible with the JIT compiler.""" + + dev = qml.device("default.qubit", wires=4) + + @qml.qjit + @qml.qnode(dev) + def circuit(): + qml.QROM(["1", "0", "0", "1"], control_wires=[0, 1], target_wires=[2], work_wires=[3]) + return qml.probs(wires=3) + + assert np.allclose(circuit(), np.array([1.0, 0.0])) + @pytest.mark.parametrize( ("control_wires", "target_wires", "work_wires", "msg_match"), @@ -215,5 +221,28 @@ def test_repr(): ["1", "0", "0", "1"], control_wires=[0, 1], target_wires=[2], work_wires=[3], clean=True ) res = op.__repr__() - expected = "QROM(control_wires=, target_wires=, work_wires=)" + expected = "QROM(control_wires=, target_wires=, work_wires=, clean=True)" assert res == expected + + +@pytest.mark.parametrize( + ("bitstrings", "control_wires", "target_wires", "msg_match"), + [ + ( + ["1", "0", "0", "1"], + [0], + [2], + r"Not enough control wires \(1\) for the desired number of bitstrings \(4\). At least 2 control wires required.", + ), + ( + ["1", "0", "0", "1"], + [0, 1], + [2, 3], + r"Bitstring length must match the number of target wires.", + ), + ], +) +def test_wrong_wires_error(bitstrings, control_wires, target_wires, msg_match): + """Test that error is raised if more ops are requested than can fit in control wires""" + with pytest.raises(ValueError, match=msg_match): + qml.QROM(bitstrings, control_wires, target_wires, work_wires=None) From 11ef6e0f070e60ed9a062bf671e5f0dd6858741d Mon Sep 17 00:00:00 2001 From: KetpuntoG <65235481+KetpuntoG@users.noreply.github.com> Date: Wed, 5 Jun 2024 08:46:07 -0400 Subject: [PATCH 34/41] fix jax test --- pennylane/templates/subroutines/qrom.py | 3 +-- tests/templates/test_subroutines/test_qrom.py | 9 +++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/pennylane/templates/subroutines/qrom.py b/pennylane/templates/subroutines/qrom.py index 642494a0131..a58e2debfd8 100644 --- a/pennylane/templates/subroutines/qrom.py +++ b/pennylane/templates/subroutines/qrom.py @@ -15,7 +15,6 @@ This submodule contains the template for QROM. """ -import copy import math import numpy as np @@ -137,7 +136,7 @@ def __init__( ) if len(bitstrings[0]) != len(target_wires): - raise ValueError(f"Bitstring length must match the number of 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) diff --git a/tests/templates/test_subroutines/test_qrom.py b/tests/templates/test_subroutines/test_qrom.py index 2f1cef16e7c..620afaf3de1 100644 --- a/tests/templates/test_subroutines/test_qrom.py +++ b/tests/templates/test_subroutines/test_qrom.py @@ -171,18 +171,23 @@ def test_decomposition(self): assert all(qml.equal(op1, op2) for op1, op2 in zip(qrom_decomposition, expected_gates)) + @pytest.mark.jax def test_jit_compatible(self): """Test that the template is compatible with the JIT compiler.""" + import jax + + jax.config.update("jax_enable_x64", True) + dev = qml.device("default.qubit", wires=4) - @qml.qjit + @jax.jit @qml.qnode(dev) def circuit(): qml.QROM(["1", "0", "0", "1"], control_wires=[0, 1], target_wires=[2], work_wires=[3]) return qml.probs(wires=3) - assert np.allclose(circuit(), np.array([1.0, 0.0])) + assert jax.numpy.allclose(circuit(), jax.numpy.array([1.0, 0.0])) @pytest.mark.parametrize( From 5235b9c9b86a5ffc62029f69849c411e3b9d8597 Mon Sep 17 00:00:00 2001 From: KetpuntoG <65235481+KetpuntoG@users.noreply.github.com> Date: Wed, 5 Jun 2024 09:46:12 -0400 Subject: [PATCH 35/41] Update test_templates.py --- tests/capture/test_templates.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/capture/test_templates.py b/tests/capture/test_templates.py index 06a8f3139ed..cef7c921dde 100644 --- a/tests/capture/test_templates.py +++ b/tests/capture/test_templates.py @@ -258,6 +258,7 @@ def fn(*args): qml.MERA, qml.MPS, qml.TTN, + qml.QROM, ] From 3a5f139f9e54e274cf04b109c243558d56ccfd8b Mon Sep 17 00:00:00 2001 From: KetpuntoG <65235481+KetpuntoG@users.noreply.github.com> Date: Wed, 5 Jun 2024 11:34:27 -0400 Subject: [PATCH 36/41] adding _primitive_blind_call --- pennylane/templates/subroutines/qrom.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pennylane/templates/subroutines/qrom.py b/pennylane/templates/subroutines/qrom.py index a58e2debfd8..4f8f0b9dd73 100644 --- a/pennylane/templates/subroutines/qrom.py +++ b/pennylane/templates/subroutines/qrom.py @@ -112,7 +112,7 @@ def __init__( work_wires = qml.wires.Wires(work_wires) if work_wires else qml.wires.Wires([]) - self.hyperparameters["bitstrings"] = bitstrings + self.hyperparameters["bitstrings"] = tuple(bitstrings) self.hyperparameters["control_wires"] = control_wires self.hyperparameters["target_wires"] = target_wires self.hyperparameters["work_wires"] = work_wires @@ -142,17 +142,13 @@ def __init__( super().__init__(wires=all_wires, id=id) def _flatten(self): - data = (self.hyperparameters["bitstrings"],) - metadata = tuple( - (key, value) for key, value in self.hyperparameters.items() if key != "bitstrings" - ) - return data, metadata + metadata = tuple((key, value) for key, value in self.hyperparameters.items()) + return tuple(), metadata @classmethod def _unflatten(cls, data, metadata): - bitstrings = data[0] hyperparams_dict = dict(metadata) - return cls(bitstrings, **hyperparams_dict) + 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})" @@ -264,6 +260,10 @@ def compute_decomposition( return decomp_ops + @classmethod + def _primitive_bind_call(cls, *args, **kwargs): + return cls._primitive.bind(*args, **kwargs) + @property def bitstrings(self): """bitstrings to be added.""" From f3c9e4f5bfbab09bd399e031e4e7c74ad6c6fccb Mon Sep 17 00:00:00 2001 From: Guillermo Alonso-Linaje <65235481+KetpuntoG@users.noreply.github.com> Date: Wed, 5 Jun 2024 12:25:17 -0400 Subject: [PATCH 37/41] [skip-ci] [skip-ci] Co-authored-by: David Wierichs --- pennylane/templates/subroutines/qrom.py | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/pennylane/templates/subroutines/qrom.py b/pennylane/templates/subroutines/qrom.py index 4f8f0b9dd73..30afc76c647 100644 --- a/pennylane/templates/subroutines/qrom.py +++ b/pennylane/templates/subroutines/qrom.py @@ -154,18 +154,7 @@ 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_target_wires = [ - wire_map.get(wire, wire) for wire in self.hyperparameters["target_wires"] - ] - new_control_wires = [ - wire_map.get(wire, wire) for wire in self.hyperparameters["control_wires"] - ] - - new_work_wires = [] - if self.hyperparameters["work_wires"]: - new_work_wires = [ - wire_map.get(wire, wire) for wire in self.hyperparameters["work_wires"] - ] + 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_control_wires, new_target_wires, new_work_wires, self.clean From c2a359c0faebe81d9572b05e560a8cf9482c91d8 Mon Sep 17 00:00:00 2001 From: KetpuntoG <65235481+KetpuntoG@users.noreply.github.com> Date: Wed, 5 Jun 2024 12:28:18 -0400 Subject: [PATCH 38/41] final final --- pennylane/templates/subroutines/qrom.py | 11 ++++++-- tests/capture/test_templates.py | 35 +++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/pennylane/templates/subroutines/qrom.py b/pennylane/templates/subroutines/qrom.py index 30afc76c647..c33a9101822 100644 --- a/pennylane/templates/subroutines/qrom.py +++ b/pennylane/templates/subroutines/qrom.py @@ -154,10 +154,17 @@ 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"]} + 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_control_wires, new_target_wires, new_work_wires, self.clean + self.bitstrings, + new_dict["control_wires"], + new_dict["target_wires"], + new_dict["work_wires"], + self.clean, ) def __copy__(self): diff --git a/tests/capture/test_templates.py b/tests/capture/test_templates.py index cef7c921dde..b7fb8c60e00 100644 --- a/tests/capture/test_templates.py +++ b/tests/capture/test_templates.py @@ -653,6 +653,41 @@ def qfunc(): assert len(q) == 1 assert qml.equal(q.queue[0], qml.Qubitization(**kwargs)) + @pytest.mark.usefixtures("new_opmath_only") + def test_qrom(self): + """Test the primitive bind call of QROM.""" + + kwargs = { + "bitstrings": ["0", "1"], + "control_wires": [0], + "target_wires": [1], + "work_wires": None, + } + + def qfunc(): + qml.QROM(**kwargs) + + # Validate inputs + qfunc() + + # Actually test primitive bind + jaxpr = jax.make_jaxpr(qfunc)() + + assert len(jaxpr.eqns) == 1 + + eqn = jaxpr.eqns[0] + assert eqn.primitive == qml.QROM._primitive + assert eqn.invars == jaxpr.jaxpr.invars + assert eqn.params == kwargs + assert len(eqn.outvars) == 1 + assert isinstance(eqn.outvars[0], jax.core.DropVar) + + with qml.queuing.AnnotatedQueue() as q: + jax.core.eval_jaxpr(jaxpr.jaxpr, jaxpr.consts) + + assert len(q) == 1 + assert qml.equal(q.queue[0], qml.QROM(**kwargs)) + @pytest.mark.parametrize( "template, kwargs", [ From d9d85f25dfd972b110ba1d6506669dc990dc9759 Mon Sep 17 00:00:00 2001 From: Guillermo Alonso-Linaje <65235481+KetpuntoG@users.noreply.github.com> Date: Wed, 5 Jun 2024 18:49:28 -0400 Subject: [PATCH 39/41] Apply suggestions from code review Co-authored-by: soranjh <40344468+soranjh@users.noreply.github.com> --- pennylane/templates/subroutines/qrom.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pennylane/templates/subroutines/qrom.py b/pennylane/templates/subroutines/qrom.py index c33a9101822..a88c5240193 100644 --- a/pennylane/templates/subroutines/qrom.py +++ b/pennylane/templates/subroutines/qrom.py @@ -83,7 +83,7 @@ def circuit(): 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 store the bitstrings. + 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. @@ -132,7 +132,7 @@ def __init__( 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 required." + + "wires are required." ) if len(bitstrings[0]) != len(target_wires): From b1b25555019498ee2f1e8668c1afe0cb7f485cd1 Mon Sep 17 00:00:00 2001 From: KetpuntoG <65235481+KetpuntoG@users.noreply.github.com> Date: Wed, 5 Jun 2024 19:18:17 -0400 Subject: [PATCH 40/41] Update test_qrom.py --- tests/templates/test_subroutines/test_qrom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/templates/test_subroutines/test_qrom.py b/tests/templates/test_subroutines/test_qrom.py index 620afaf3de1..21d60f28aaa 100644 --- a/tests/templates/test_subroutines/test_qrom.py +++ b/tests/templates/test_subroutines/test_qrom.py @@ -237,7 +237,7 @@ def test_repr(): ["1", "0", "0", "1"], [0], [2], - r"Not enough control wires \(1\) for the desired number of bitstrings \(4\). At least 2 control wires required.", + r"Not enough control wires \(1\) for the desired number of bitstrings \(4\). At least 2 control wires are required.", ), ( ["1", "0", "0", "1"], From 8ac29058794245f0ba21529d42558ddc9e2c45ce Mon Sep 17 00:00:00 2001 From: KetpuntoG <65235481+KetpuntoG@users.noreply.github.com> Date: Thu, 6 Jun 2024 12:35:35 -0400 Subject: [PATCH 41/41] reference format --- pennylane/templates/subroutines/qrom.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pennylane/templates/subroutines/qrom.py b/pennylane/templates/subroutines/qrom.py index a88c5240193..38c0f99ddc0 100644 --- a/pennylane/templates/subroutines/qrom.py +++ b/pennylane/templates/subroutines/qrom.py @@ -90,14 +90,14 @@ def circuit(): 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 `__, auxiliary qubits can be used to + Following the idea in [`arXiv: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. - The QROM template has two variants. The first one (``clean = False``) is based on `arXiv:1812.00954 `__ that alterates the state in the ``work_wires``. - The second one (``clean = True``), based on `arXiv:1902.02134 `__, solves that issue by + The QROM template has two variants. The first one (``clean = False``) is based on [`arXiv:1812.00954 `__] that alterates the state in the ``work_wires``. + The second one (``clean = True``), based on [`arXiv: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.