Skip to content

Commit

Permalink
OutAdder and ModExp Operators Quantum Arithmetic (#6121)
Browse files Browse the repository at this point in the history
**Context:**

Adding OutAdder and ModExp templates for Quantum Arithmetic.

**Description of the Change:**

Adding OutAdder and ModExp templates to perform arithmetic operations
between quantum registers.

**Benefits:**

Users can implement arithmetic operations in an easy and efficient way.

**Related GitHub Issues:** [sc-71590]

---------

Co-authored-by: KetpuntoG <[email protected]>
Co-authored-by: soranjh <[email protected]>
  • Loading branch information
3 people authored Aug 23, 2024
1 parent 1be2ba1 commit 72c8a41
Show file tree
Hide file tree
Showing 8 changed files with 856 additions and 1 deletion.
4 changes: 4 additions & 0 deletions doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@
* The `qml.Multiplier` and `qml.OutMultiplier` templates are added to perform modular multiplication.
[(#6112)](https://github.com/PennyLaneAI/pennylane/pull/6112)

* The `qml.OutAdder` and `qml.ModExp` templates are added to perform out-of-place modular addition and modular exponentiation.
[(#6121)](https://github.com/PennyLaneAI/pennylane/pull/6121)


<h4>Creating spin Hamiltonians 🧑‍🎨</h4>

* The function ``transverse_ising`` is added to generate transverse-field Ising Hamiltonian.
Expand Down
2 changes: 2 additions & 0 deletions pennylane/templates/subroutines/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,5 @@
from .adder import Adder
from .multiplier import Multiplier
from .out_multiplier import OutMultiplier
from .out_adder import OutAdder
from .mod_exp import ModExp
187 changes: 187 additions & 0 deletions pennylane/templates/subroutines/mod_exp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
# 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.
"""
Contains the ModExp template.
"""
import pennylane as qml
from pennylane.operation import Operation


class ModExp(Operation):
r"""Performs the out-place modular exponentiation operation.
This operator performs the modular exponentiation of the integer :math:`base` to the power
:math:`x` modulo :math:`mod` in the computational basis:
.. math::
\text{ModExp}(base,mod) |x \rangle |k \rangle = |x \rangle |k*base^x \, \text{mod} \, mod \rangle,
The implementation is based on the quantum Fourier transform method presented in
`arXiv:2311.08555 <https://arxiv.org/abs/2311.08555>`_.
.. note::
Note that :math:`x` must be smaller than :math:`mod` to get the correct result.
Also, it is required that :math:`base` has inverse, :math:`base^-1` modulo :math:`mod`.
That means :math:`base*base^-1 modulo mod = 1`, which will only be possible if :math:`base`
and :math:`mod` are coprime.
Args:
x_wires (Sequence[int]): the wires that store the integer :math:`x`
output_wires (Sequence[int]): the wires that store the exponentiation result
base (int): integer that needs to be exponentiated
mod (int): the modulus for performing the exponentiation, default value is :math:`2^{len(output\_wires)}`
work_wires (Sequence[int]): the auxiliary wires to be used for the exponentiation. There
must be as many as ``output_wires`` and if :math:`mod \neq 2^{len(x\_wires)}`, two more
wires must be added.
**Example**
This example performs the exponentiation of :math:`base=2` to the power :math:`x=3` modulo :math:`mod=7`.
.. code-block::
x, k = 3, 1
base = 2
mod = 7
x_wires = [0, 1]
output_wires = [2, 3, 4]
work_wires = [5, 6, 7, 8, 9]
dev = qml.device("default.qubit", shots=1)
@qml.qnode(dev)
def circuit():
qml.BasisEmbedding(x, wires = x_wires)
qml.BasisEmbedding(k, wires = output_wires)
qml.ModExp(x_wires, output_wires, base, mod, work_wires)
return qml.sample(wires = output_wires)
.. code-block:: pycon
>>> print(circuit())
[0 0 1]
The result :math:`[0 0 1]`, is the ket representation of
:math:`2^3 \, \text{modulo} \, 7 = 1`.
"""

grad_method = None

def __init__(
self, x_wires, output_wires, base, mod=None, work_wires=None, id=None
): # pylint: disable=too-many-arguments

output_wires = qml.wires.Wires(output_wires)

if mod is None:
mod = 2 ** (len(output_wires))
if len(output_wires) == 0 or (mod > 2 ** (len(output_wires))):
raise ValueError("ModExp must have enough wires to represent mod.")
if mod != 2 ** len(x_wires):
if len(work_wires) < (len(output_wires) + 2):
raise ValueError("ModExp needs as many work_wires as output_wires plus two.")
else:
if len(work_wires) < len(output_wires):
raise ValueError("ModExp needs as many work_wires as output_wires.")
if work_wires is not None:
if any(wire in work_wires for wire in x_wires):
raise ValueError("None of the wires in work_wires should be included in x_wires.")
if any(wire in work_wires for wire in output_wires):
raise ValueError(
"None of the wires in work_wires should be included in output_wires."
)
if any(wire in x_wires for wire in output_wires):
raise ValueError("None of the wires in x_wires should be included in output_wires.")
wire_keys = ["x_wires", "output_wires", "work_wires"]
for key in wire_keys:
self.hyperparameters[key] = qml.wires.Wires(locals()[key])
all_wires = sum(self.hyperparameters[key] for key in wire_keys)
base = base % mod
self.hyperparameters["base"] = base
self.hyperparameters["mod"] = mod
super().__init__(wires=all_wires, id=id)

@property
def num_params(self):
return 0

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

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

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

return ModExp(
new_dict["x_wires"],
new_dict["output_wires"],
self.hyperparameters["base"],
self.hyperparameters["mod"],
new_dict["work_wires"],
)

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

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

return self.compute_decomposition(**self.hyperparameters)

@classmethod
def _primitive_bind_call(cls, *args, **kwargs):
return cls._primitive.bind(*args, **kwargs)

@staticmethod
def compute_decomposition(
x_wires, output_wires, base, mod, work_wires
): # pylint: disable=arguments-differ
r"""Representation of the operator as a product of other operators.
Args:
x_wires (Sequence[int]): the wires that store the integer :math:`x`
output_wires (Sequence[int]): the wires that store the exponentiation result
base (int): integer that needs to be exponentiated
mod (int): the modulus for performing the exponentiation, default value is :math:`2^{len(output\_wires)}`
work_wires (Sequence[int]): the auxiliary wires to be used for the exponentiation. There must be as many as ``output_wires`` and if :math:`mod \neq 2^{len(x\_wires)}`, two more wires must be added.
Returns:
list[.Operator]: Decomposition of the operator
**Example**
>>> qml.ModExp.compute_decomposition(x_wires=[0,1], output_wires=[2,3,4], base=3, mod=8, work_wires=[5,6,7,8,9])
[ControlledSequence(Multiplier(wires=[2, 3, 4, 5, 6, 7, 8, 9]), control=[0, 1])]
"""

# TODO: Cancel the QFTs of consecutive Multipliers
op_list = []
op_list.append(
qml.ControlledSequence(
qml.Multiplier(base, output_wires, mod, work_wires), control=x_wires
)
)
return op_list
190 changes: 190 additions & 0 deletions pennylane/templates/subroutines/out_adder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
# Copyright 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.
"""
Contains the OutAdder template.
"""

import pennylane as qml
from pennylane.operation import Operation


class OutAdder(Operation):
r"""Performs the out-place modular addition operation.
This operator performs the modular addition of two integers :math:`x` and :math:`y` modulo
:math:`mod` in the computational basis:
.. math::
\text{OutAdder}(mod) |x \rangle | y \rangle | b \rangle = |x \rangle | y \rangle | b+x+y \, \text{mod} \, mod \rangle,
The implementation is based on the quantum Fourier transform method presented in
`arXiv:2311.08555 <https://arxiv.org/abs/2311.08555>`_.
.. note::
Note that :math:`x` and :math:`y` must be smaller than :math:`mod` to get the correct result.
Args:
x_wires (Sequence[int]): the wires that store the integer :math:`x`
y_wires (Sequence[int]): the wires that store the integer :math:`y`
output_wires (Sequence[int]): the wires that store the addition result
mod (int): the modulus for performing the addition, default value is :math:`2^{\text{len(output\_wires)}}`
work_wires (Sequence[int]): the auxiliary wires to use for the addition
**Example**
This example computes the sum of two integers :math:`x=5` and :math:`y=6` modulo :math:`mod=7`.
.. code-block::
x=5
y=6
mod=7
x_wires=[0,1,2]
y_wires=[3,4,5]
output_wires=[7,8,9]
work_wires=[6,10]
dev = qml.device("default.qubit", shots=1)
@qml.qnode(dev)
def circuit():
qml.BasisEmbedding(x, wires=x_wires)
qml.BasisEmbedding(y, wires=y_wires)
qml.OutAdder(x_wires, y_wires, output_wires, mod, work_wires)
return qml.sample(wires=output_wires)
.. code-block:: pycon
>>> print(circuit())
[1 0 0]
The result :math:`[1 0 0]`, is the ket representation of
:math:`5 + 6 \, \text{modulo} \, 7 = 4`.
"""

grad_method = None

def __init__(
self, x_wires, y_wires, output_wires, mod=None, work_wires=None, id=None
): # pylint: disable=too-many-arguments

if mod is None:
mod = 2 ** (len(output_wires))
if (not hasattr(output_wires, "__len__")) or (mod > 2 ** len(output_wires)):
raise ValueError("OutAdder must have enough wires to represent mod.")
if work_wires is not None:
if any(wire in work_wires for wire in x_wires):
raise ValueError("None of the wires in work_wires should be included in x_wires.")
if any(wire in work_wires for wire in y_wires):
raise ValueError("None of the wires in work_wires should be included in y_wires.")
if any(wire in y_wires for wire in x_wires):
raise ValueError("None of the wires in y_wires should be included in x_wires.")
if any(wire in x_wires for wire in output_wires):
raise ValueError("None of the wires in x_wires should be included in output_wires.")
if any(wire in y_wires for wire in output_wires):
raise ValueError("None of the wires in y_wires should be included in output_wires.")
for key in ["x_wires", "y_wires", "output_wires", "work_wires"]:
self.hyperparameters[key] = qml.wires.Wires(locals()[key])
all_wires = sum(
self.hyperparameters[key]
for key in ["x_wires", "y_wires", "output_wires", "work_wires"]
)
self.hyperparameters["mod"] = mod
super().__init__(wires=all_wires, id=id)

@property
def num_params(self):
return 0

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

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

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

return OutAdder(
new_dict["x_wires"],
new_dict["y_wires"],
new_dict["output_wires"],
self.hyperparameters["mod"],
new_dict["work_wires"],
)

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

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

return self.compute_decomposition(**self.hyperparameters)

@classmethod
def _primitive_bind_call(cls, *args, **kwargs):
return cls._primitive.bind(*args, **kwargs)

@staticmethod
def compute_decomposition(
x_wires, y_wires, output_wires, mod, work_wires
): # pylint: disable=arguments-differ
r"""Representation of the operator as a product of other operators.
Args:
x_wires (Sequence[int]): the wires that store the integer :math:`x`
y_wires (Sequence[int]): the wires that store the integer :math:`y`
output_wires (Sequence[int]): the wires that store the addition result
mod (int): the modulus for performing the addition, default value is :math:`2^{\text{len(output\_wires)}}`
work_wires (Sequence[int]): the auxiliary wires to use for the addition
Returns:
list[.Operator]: Decomposition of the operator
**Example**
>>> qml.OutAdder.compute_decomposition(x_wires=[0,1], y_wires=[2,3], output_wires=[5,6], mod=4, work_wires=[4,7])
[QFT(wires=[5, 6]),
ControlledSequence(PhaseAdder(wires=[5, 6, None]), control=[0, 1])
ControlledSequence(PhaseAdder(wires=[5, 6, None]), control=[2, 3]),
Adjoint(QFT(wires=[5, 6]))]
"""
op_list = []
if mod != 2 ** len(output_wires) and mod is not None:
qft_new_output_wires = work_wires[:1] + output_wires
work_wire = work_wires[1:]
else:
qft_new_output_wires = output_wires
work_wire = None
op_list.append(qml.QFT(wires=qft_new_output_wires))
op_list.append(
qml.ControlledSequence(
qml.PhaseAdder(1, qft_new_output_wires, mod, work_wire), control=x_wires
)
)
op_list.append(
qml.ControlledSequence(
qml.PhaseAdder(1, qft_new_output_wires, mod, work_wire), control=y_wires
)
)
op_list.append(qml.adjoint(qml.QFT)(wires=qft_new_output_wires))

return op_list
Loading

0 comments on commit 72c8a41

Please sign in to comment.