diff --git a/qiskit/circuit/library/__init__.py b/qiskit/circuit/library/__init__.py index c732fcf5bb3d..98e3cdacceec 100644 --- a/qiskit/circuit/library/__init__.py +++ b/qiskit/circuit/library/__init__.py @@ -151,6 +151,15 @@ VBERippleCarryAdder WeightedAdder +Multipliers ++++++++++++ + +.. autosummary:: + :toctree: ../stubs/ + + HRSCumulativeMultiplier + RGQFTMultiplier + Comparators +++++++++++ @@ -362,6 +371,8 @@ CDKMRippleCarryAdder, DraperQFTAdder, PiecewiseChebyshev, + HRSCumulativeMultiplier, + RGQFTMultiplier, ) from .n_local import ( diff --git a/qiskit/circuit/library/arithmetic/__init__.py b/qiskit/circuit/library/arithmetic/__init__.py index 511f82c85961..16560b2c1a94 100644 --- a/qiskit/circuit/library/arithmetic/__init__.py +++ b/qiskit/circuit/library/arithmetic/__init__.py @@ -23,3 +23,4 @@ from .linear_amplitude_function import LinearAmplitudeFunction from .adders import VBERippleCarryAdder, CDKMRippleCarryAdder, DraperQFTAdder from .piecewise_chebyshev import PiecewiseChebyshev +from .multipliers import HRSCumulativeMultiplier, RGQFTMultiplier diff --git a/qiskit/circuit/library/arithmetic/multipliers/__init__.py b/qiskit/circuit/library/arithmetic/multipliers/__init__.py new file mode 100644 index 000000000000..64a7edb0e7e0 --- /dev/null +++ b/qiskit/circuit/library/arithmetic/multipliers/__init__.py @@ -0,0 +1,16 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""The multiplier circuit library.""" + +from .hrs_cumulative_multiplier import HRSCumulativeMultiplier +from .rg_qft_multiplier import RGQFTMultiplier diff --git a/qiskit/circuit/library/arithmetic/multipliers/hrs_cumulative_multiplier.py b/qiskit/circuit/library/arithmetic/multipliers/hrs_cumulative_multiplier.py new file mode 100644 index 000000000000..bb254d7cdd14 --- /dev/null +++ b/qiskit/circuit/library/arithmetic/multipliers/hrs_cumulative_multiplier.py @@ -0,0 +1,134 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Compute the product of two qubit registers using classical multiplication approach.""" + +from typing import Optional +from qiskit.circuit import QuantumRegister, AncillaRegister, QuantumCircuit + +from .multiplier import Multiplier + + +class HRSCumulativeMultiplier(Multiplier): + r"""A multiplication circuit to store product of two input registers out-of-place. + + Circuit uses the approach from [1]. As an example, a multiplier circuit that + performs a non-modular multiplication on two 3-qubit sized registers with + the default adder is as follows (where ``Adder`` denotes the + ``CDKMRippleCarryAdder``): + + .. parsed-literal:: + + a_0: ────■───────────────────────── + │ + a_1: ────┼─────────■─────────────── + │ │ + a_2: ────┼─────────┼─────────■───── + ┌───┴────┐┌───┴────┐┌───┴────┐ + b_0: ┤0 ├┤0 ├┤0 ├ + │ ││ ││ │ + b_1: ┤1 ├┤1 ├┤1 ├ + │ ││ ││ │ + b_2: ┤2 ├┤2 ├┤2 ├ + │ ││ ││ │ + out_0: ┤3 ├┤ ├┤ ├ + │ ││ ││ │ + out_1: ┤4 ├┤3 ├┤ ├ + │ Adder ││ Adder ││ Adder │ + out_2: ┤5 ├┤4 ├┤3 ├ + │ ││ ││ │ + out_3: ┤6 ├┤5 ├┤4 ├ + │ ││ ││ │ + out_4: ┤ ├┤6 ├┤5 ├ + │ ││ ││ │ + out_5: ┤ ├┤ ├┤6 ├ + │ ││ ││ │ + aux_0: ┤7 ├┤7 ├┤7 ├ + └────────┘└────────┘└────────┘ + + Multiplication in this circuit is implemented in a classical approach by performing + a series of shifted additions using one of the input registers while the qubits + from the other input register act as control qubits for the adders. + + **References:** + + [1] Häner et al., Optimizing Quantum Circuits for Arithmetic, 2018. + `arXiv:1805.12445 `_ + + """ + + def __init__( + self, + num_state_qubits: int, + num_result_qubits: Optional[int] = None, + adder: Optional[QuantumCircuit] = None, + name: str = "HRSCumulativeMultiplier", + ) -> None: + r""" + Args: + num_state_qubits: The number of qubits in either input register for + state :math:`|a\rangle` or :math:`|b\rangle`. The two input + registers must have the same number of qubits. + num_result_qubits: The number of result qubits to limit the output to. + If number of result qubits is :math:`n`, multiplication modulo :math:`2^n` is performed + to limit the output to the specified number of qubits. Default + value is ``2 * num_state_qubits`` to represent any possible + result from the multiplication of the two inputs. + adder: Half adder circuit to be used for performing multiplication. The + CDKMRippleCarryAdder is used as default if no adder is provided. + name: The name of the circuit object. + Raises: + NotImplementedError: If ``num_result_qubits`` is not default and a custom adder is provided. + """ + super().__init__(num_state_qubits, num_result_qubits, name=name) + + if self.num_result_qubits != 2 * num_state_qubits and adder is not None: + raise NotImplementedError("Only default adder is supported for modular multiplication.") + + # define the registers + qr_a = QuantumRegister(num_state_qubits, name="a") + qr_b = QuantumRegister(num_state_qubits, name="b") + qr_out = QuantumRegister(self.num_result_qubits, name="out") + self.add_register(qr_a, qr_b, qr_out) + + # prepare adder as controlled gate + if adder is None: + from qiskit.circuit.library.arithmetic.adders import CDKMRippleCarryAdder + + adder = CDKMRippleCarryAdder(num_state_qubits, kind="half") + + # get the number of helper qubits needed + num_helper_qubits = adder.num_ancillas + + # add helper qubits if required + if num_helper_qubits > 0: + qr_h = AncillaRegister(num_helper_qubits, name="helper") # helper/ancilla qubits + self.add_register(qr_h) + + # build multiplication circuit + for i in range(num_state_qubits): + excess_qubits = max(0, num_state_qubits + i + 1 - self.num_result_qubits) + if excess_qubits == 0: + num_adder_qubits = num_state_qubits + adder_for_current_step = adder + else: + num_adder_qubits = num_state_qubits - excess_qubits + 1 + adder_for_current_step = CDKMRippleCarryAdder(num_adder_qubits, kind="fixed") + controlled_adder = adder_for_current_step.to_gate().control(1) + qr_list = ( + [qr_a[i]] + + qr_b[:num_adder_qubits] + + qr_out[i : num_state_qubits + i + 1 - excess_qubits] + ) + if num_helper_qubits > 0: + qr_list.extend(qr_h[:]) + self.append(controlled_adder, qr_list) diff --git a/qiskit/circuit/library/arithmetic/multipliers/multiplier.py b/qiskit/circuit/library/arithmetic/multipliers/multiplier.py new file mode 100644 index 000000000000..a56240438829 --- /dev/null +++ b/qiskit/circuit/library/arithmetic/multipliers/multiplier.py @@ -0,0 +1,101 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Compute the product of two equally sized qubit registers.""" + +from typing import Optional + +from qiskit.circuit import QuantumCircuit + + +class Multiplier(QuantumCircuit): + r"""Compute the product of two equally sized qubit registers into a new register. + + For two input registers :math:`|a\rangle_n`, :math:`|b\rangle_n` with :math:`n` qubits each + and an output register with :math:`2n` qubits, a multiplier performs the following operation + + .. math:: + + |a\rangle_n |b\rangle_n |0\rangle_{t} \mapsto |a\rangle_n |b\rangle_n |a \cdot b\rangle_t + + where :math:`t` is the number of bits used to represent the result. To completely store the result + of the multiplication without overflow we need :math:`t = 2n` bits. + + The quantum register :math:`|a\rangle_n` (analogously :math:`|b\rangle_n` and + output register) + + .. math:: + + |a\rangle_n = |a_0\rangle \otimes \cdots \otimes |a_{n - 1}\rangle, + + for :math:`a_i \in \{0, 1\}`, is associated with the integer value + + .. math:: + + a = 2^{0}a_{0} + 2^{1}a_{1} + \cdots + 2^{n - 1}a_{n - 1}. + + """ + + def __init__( + self, + num_state_qubits: int, + num_result_qubits: Optional[int] = None, + name: str = "Multiplier", + ) -> None: + """ + Args: + num_state_qubits: The number of qubits in each of the input registers. + num_result_qubits: The number of result qubits to limit the output to. + Default value is ``2 * num_state_qubits`` to represent any possible + result from the multiplication of the two inputs. + name: The name of the circuit. + Raises: + ValueError: If ``num_state_qubits`` is smaller than 1. + ValueError: If ``num_result_qubits`` is smaller than ``num_state_qubits``. + ValueError: If ``num_result_qubits`` is larger than ``2 * num_state_qubits``. + """ + if num_state_qubits < 1: + raise ValueError("The number of qubits must be at least 1.") + + if num_result_qubits is None: + num_result_qubits = 2 * num_state_qubits + + if num_result_qubits < num_state_qubits: + raise ValueError( + "Number of result qubits is smaller than number of input state qubits." + ) + if num_result_qubits > 2 * num_state_qubits: + raise ValueError( + "Number of result qubits is larger than twice the number of input state qubits." + ) + + super().__init__(name=name) + self._num_state_qubits = num_state_qubits + self._num_result_qubits = num_result_qubits + + @property + def num_state_qubits(self) -> int: + """The number of state qubits, i.e. the number of bits in each input register. + + Returns: + The number of state qubits. + """ + return self._num_state_qubits + + @property + def num_result_qubits(self) -> int: + """The number of result qubits to limit the output to. + + Returns: + The number of result qubits. + """ + return self._num_result_qubits diff --git a/qiskit/circuit/library/arithmetic/multipliers/rg_qft_multiplier.py b/qiskit/circuit/library/arithmetic/multipliers/rg_qft_multiplier.py new file mode 100644 index 000000000000..ec9ec080211c --- /dev/null +++ b/qiskit/circuit/library/arithmetic/multipliers/rg_qft_multiplier.py @@ -0,0 +1,97 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Compute the product of two qubit registers using QFT.""" + +from typing import Optional +import numpy as np + +from qiskit.circuit import QuantumRegister +from qiskit.circuit.library.standard_gates import PhaseGate +from qiskit.circuit.library.basis_change import QFT + +from .multiplier import Multiplier + + +class RGQFTMultiplier(Multiplier): + r"""A QFT multiplication circuit to store product of two input registers out-of-place. + + Multiplication in this circuit is implemented using the procedure of Fig. 3 in [1], where + weighted sum rotations are implemented as given in Fig. 5 in [1]. QFT is used on the output + register and is followed by rotations controlled by input registers. The rotations + transform the state into the product of two input registers in QFT base, which is + reverted from QFT base using inverse QFT. + As an example, a circuit that performs a modular QFT multiplication on two 2-qubit + sized input registers with an output register of 2 qubits, is as follows: + + .. parsed-literal:: + + a_0: ────────────────────────────────────────■───────■──────■──────■──────────────── + │ │ │ │ + a_1: ─────────■───────■───────■───────■──────┼───────┼──────┼──────┼──────────────── + │ │ │ │ │ │ │ │ + b_0: ─────────┼───────┼───────■───────■──────┼───────┼──────■──────■──────────────── + │ │ │ │ │ │ │ │ + b_1: ─────────■───────■───────┼───────┼──────■───────■──────┼──────┼──────────────── + ┌──────┐ │P(4π) │ │P(2π) │ │P(2π) │ │P(π) │ ┌───────┐ + out_0: ┤0 ├─■───────┼───────■───────┼──────■───────┼──────■──────┼───────┤0 ├ + │ qft │ │P(2π) │P(π) │P(π) │P(π/2) │ iqft │ + out_1: ┤1 ├─────────■───────────────■──────────────■─────────────■───────┤1 ├ + └──────┘ └───────┘ + + **References:** + + [1] Ruiz-Perez et al., Quantum arithmetic with the Quantum Fourier Transform, 2017. + `arXiv:1411.5949 `_ + + """ + + def __init__( + self, + num_state_qubits: int, + num_result_qubits: Optional[int] = None, + name: str = "RGQFTMultiplier", + ) -> None: + r""" + Args: + num_state_qubits: The number of qubits in either input register for + state :math:`|a\rangle` or :math:`|b\rangle`. The two input + registers must have the same number of qubits. + num_result_qubits: The number of result qubits to limit the output to. + If number of result qubits is :math:`n`, multiplication modulo :math:`2^n` is performed + to limit the output to the specified number of qubits. Default + value is ``2 * num_state_qubits`` to represent any possible + result from the multiplication of the two inputs. + name: The name of the circuit object. + + """ + super().__init__(num_state_qubits, num_result_qubits, name=name) + + # define the registers + qr_a = QuantumRegister(num_state_qubits, name="a") + qr_b = QuantumRegister(num_state_qubits, name="b") + qr_out = QuantumRegister(self.num_result_qubits, name="out") + self.add_register(qr_a, qr_b, qr_out) + + # build multiplication circuit + self.append(QFT(self.num_result_qubits, do_swaps=False).to_gate(), qr_out[:]) + + for j in range(1, num_state_qubits + 1): + for i in range(1, num_state_qubits + 1): + for k in range(1, self.num_result_qubits + 1): + lam = (2 * np.pi) / (2 ** (i + j + k - 2 * num_state_qubits)) + self.append( + PhaseGate(lam).control(2), + [qr_a[num_state_qubits - j], qr_b[num_state_qubits - i], qr_out[k - 1]], + ) + + self.append(QFT(self.num_result_qubits, do_swaps=False).inverse().to_gate(), qr_out[:]) diff --git a/releasenotes/notes/multipliers-8c7477859508ca58.yaml b/releasenotes/notes/multipliers-8c7477859508ca58.yaml new file mode 100644 index 000000000000..bd3336f29a43 --- /dev/null +++ b/releasenotes/notes/multipliers-8c7477859508ca58.yaml @@ -0,0 +1,46 @@ +--- +features: + - | + Add a new circuit library submodule to perform classical multiplication of two equally-sized + qubit registers. For two registers :math:`|a\rangle_n` and :math:`|b\rangle_n` on :math:`n` + qubits, the two new classes :class:`~qiskit.circuit.library.RGQFTMultiplier` and + :class:`~qiskit.circuit.library.HRSCumulativeMultiplier` perform the operation + + .. math:: + + |a\rangle_n |b\rangle_n |0\rangle_{2n} \mapsto |a\rangle_n |b\rangle_n |a \cdot b\rangle_{2n}. + + Example:: + + from qiskit.circuit import QuantumCircuit + from qiskit.circuit.library import RGQFTMultiplier + from qiskit.quantum_info import Statevector + + num_state_qubits = 2 + + # a encodes |11> = 3 + a = QuantumCircuit(num_state_qubits) + a.x(range(num_state_qubits)) + + # b encodes |11> = 3 + b = QuantumCircuit(num_state_qubits) + b.x(range(num_state_qubits)) + + # multiplier on 2-bit numbers + multiplier = RGQFTMultiplier(num_state_qubits) + + # add the state preparations to the front of the circuit + multiplier.compose(a, [0, 1], inplace=True, front=True) + multiplier.compose(b, [2, 3], inplace=True, front=True) + + # simulate and get the state of all qubits + sv = Statevector(multiplier) + counts = sv.probabilities_dict(decimals=10) + state = list(counts.keys())[0] # we only have a single state + + # skip both input registers + result = state[:-2*num_state_qubits] + print(result) # '1001' = 9 = 3 * 3 + + + diff --git a/test/python/circuit/library/test_multipliers.py b/test/python/circuit/library/test_multipliers.py new file mode 100644 index 000000000000..7dc850802747 --- /dev/null +++ b/test/python/circuit/library/test_multipliers.py @@ -0,0 +1,129 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Test multiplier circuits.""" + +import unittest +import numpy as np +from ddt import ddt, data, unpack + +from qiskit.test.base import QiskitTestCase +from qiskit.circuit import QuantumCircuit +from qiskit.quantum_info import Statevector +from qiskit.circuit.library import ( + RGQFTMultiplier, + HRSCumulativeMultiplier, + CDKMRippleCarryAdder, + DraperQFTAdder, + VBERippleCarryAdder, +) + + +@ddt +class TestMultiplier(QiskitTestCase): + """Test the multiplier circuits.""" + + def assertMultiplicationIsCorrect( + self, num_state_qubits: int, num_result_qubits: int, multiplier: QuantumCircuit + ): + """Assert that multiplier correctly implements the product. + + Args: + num_state_qubits: The number of bits in the numbers that are multiplied. + num_result_qubits: The number of qubits to limit the output to with modulo. + multiplier: The circuit performing the multiplication of two numbers with + ``num_state_qubits`` bits. + """ + circuit = QuantumCircuit(*multiplier.qregs) + + # create equal superposition + circuit.h(range(2 * num_state_qubits)) + + # apply multiplier circuit + circuit.compose(multiplier, inplace=True) + + # obtain the statevector and the probabilities, we don't trace out the ancilla qubits + # as we verify that all ancilla qubits have been uncomputed to state 0 again + statevector = Statevector(circuit) + probabilities = statevector.probabilities() + pad = "0" * circuit.num_ancillas # state of the ancillas + + # compute the expected results + expectations = np.zeros_like(probabilities) + num_bits_product = num_state_qubits * 2 + # iterate over all possible inputs + for x in range(2 ** num_state_qubits): + for y in range(2 ** num_state_qubits): + # compute the product + product = x * y % (2 ** num_result_qubits) + # compute correct index in statevector + bin_x = bin(x)[2:].zfill(num_state_qubits) + bin_y = bin(y)[2:].zfill(num_state_qubits) + bin_res = bin(product)[2:].zfill(num_bits_product) + bin_index = pad + bin_res + bin_y + bin_x + index = int(bin_index, 2) + expectations[index] += 1 / 2 ** (2 * num_state_qubits) + np.testing.assert_array_almost_equal(expectations, probabilities) + + @data( + (3, RGQFTMultiplier), + (3, RGQFTMultiplier, 5), + (3, RGQFTMultiplier, 4), + (3, RGQFTMultiplier, 3), + (3, HRSCumulativeMultiplier), + (3, HRSCumulativeMultiplier, 5), + (3, HRSCumulativeMultiplier, 4), + (3, HRSCumulativeMultiplier, 3), + (3, HRSCumulativeMultiplier, None, CDKMRippleCarryAdder), + (3, HRSCumulativeMultiplier, None, DraperQFTAdder), + (3, HRSCumulativeMultiplier, None, VBERippleCarryAdder), + ) + @unpack + def test_multiplication(self, num_state_qubits, multiplier, num_result_qubits=None, adder=None): + """Test multiplication for all implemented multipliers.""" + if num_result_qubits is None: + num_result_qubits = 2 * num_state_qubits + if adder is not None: + adder = adder(num_state_qubits, kind="half") + multiplier = multiplier(num_state_qubits, num_result_qubits, adder=adder) + else: + multiplier = multiplier(num_state_qubits, num_result_qubits) + self.assertMultiplicationIsCorrect(num_state_qubits, num_result_qubits, multiplier) + + @data( + (RGQFTMultiplier, -1), + (HRSCumulativeMultiplier, -1), + (RGQFTMultiplier, 0, 0), + (HRSCumulativeMultiplier, 0, 0), + (RGQFTMultiplier, 0, 1), + (HRSCumulativeMultiplier, 0, 1), + (RGQFTMultiplier, 1, 0), + (HRSCumulativeMultiplier, 1, 0), + (RGQFTMultiplier, 3, 2), + (HRSCumulativeMultiplier, 3, 2), + (RGQFTMultiplier, 3, 7), + (HRSCumulativeMultiplier, 3, 7), + ) + @unpack + def test_raises_on_wrong_num_bits(self, multiplier, num_state_qubits, num_result_qubits=None): + """Test an error is raised for a bad number of state or result qubits.""" + with self.assertRaises(ValueError): + _ = multiplier(num_state_qubits, num_result_qubits) + + def test_modular_cumulative_multiplier_custom_adder(self): + """Test an error is raised when a custom adder is used with modular cumulative multiplier.""" + with self.assertRaises(NotImplementedError): + _ = HRSCumulativeMultiplier(3, 3, adder=VBERippleCarryAdder(3)) + + +if __name__ == "__main__": + unittest.main()