Skip to content

Commit

Permalink
Arithmetic circuit library: multipliers (Qiskit#6470)
Browse files Browse the repository at this point in the history
* Implement initial draft of ripple-carry adder circuit

* Switch to ancilla register for ancilla qubits

* Remove padding for simpler implementation

* Add small fixes from review

* Add initial unit tests

Co-authored-by: Julien Gacon <jules.gacon@googlemail.com>

* Switch carry out from ancilla to usual register

* Update __init__ docstring with raises

* Add suggestions from review; Clean-up

Co-authored-by: Julien Gacon <jules.gacon@googlemail.com>

* Add additional test for ripple-carry adder

* Implement initial QFT adder circuit

Co-authored-by: Julien Gacon <jules.gacon@googlemail.com>

* Fix QFT adder carry out; Switch to non-modular as default

* Expand adder tests with QFT adder

* Rename test_adder.py -> test_adders.py

* Include additional QFT adder documentation

* Fix typos

* Implement ripple-carry adder circuit (#11)

* Implement initial draft of ripple-carry adder circuit

* Switch to ancilla register for ancilla qubits

* Remove padding for simpler implementation

* Add small fixes from review

* Add initial unit tests

Co-authored-by: Julien Gacon <jules.gacon@googlemail.com>

* Switch carry out from ancilla to usual register

* Update __init__ docstring with raises

* Add suggestions from review; Clean-up

Co-authored-by: Julien Gacon <jules.gacon@googlemail.com>

* Add additional test for ripple-carry adder

Co-authored-by: Julien Gacon <jules.gacon@googlemail.com>

* Fix documentation math

Co-authored-by: Julien Gacon <jules.gacon@googlemail.com>

* Add non-modular test as default

Co-authored-by: Julien Gacon <jules.gacon@googlemail.com>

* Remove duplicate file after merge

* Implement QFT adder (#22)

* Implement initial draft of ripple-carry adder circuit

* Switch to ancilla register for ancilla qubits

* Remove padding for simpler implementation

* Add small fixes from review

* Add initial unit tests

Co-authored-by: Julien Gacon <jules.gacon@googlemail.com>

* Switch carry out from ancilla to usual register

* Update __init__ docstring with raises

* Add suggestions from review; Clean-up

Co-authored-by: Julien Gacon <jules.gacon@googlemail.com>

* Add additional test for ripple-carry adder

* Implement initial QFT adder circuit

Co-authored-by: Julien Gacon <jules.gacon@googlemail.com>

* Fix QFT adder carry out; Switch to non-modular as default

* Expand adder tests with QFT adder

* Rename test_adder.py -> test_adders.py

* Include additional QFT adder documentation

* Fix typos

* Fix documentation math

Co-authored-by: Julien Gacon <jules.gacon@googlemail.com>

* Add non-modular test as default

Co-authored-by: Julien Gacon <jules.gacon@googlemail.com>

* Remove duplicate file after merge

Co-authored-by: Julien Gacon <jules.gacon@googlemail.com>

* Implemented classical adder in QFT paper (#13)

* empty

* empty

* Implemented classical adder in QFT paper

* Implemented classical adder in QFT paper

* fix classicaladd imports

* fix missing imports

* Added the tests part for ClassicalAdd

* move cin to bottom

* Updated the documentation and test parts for ClassicalAdder

* Updated init files

* Deleted adder folder from the git repository

* Deleted test_classicaladd.py

* Deleted old classicaladder file

* Deleted __init__.py file from repository

* Updated classical_adder file

Co-authored-by: Cryoris <jules.gacon@googlemail.com>
Co-authored-by: Julien Gacon <gaconju@gmail.com>

* unify tests

* inject base class ``Adder``

* add reno

* fix lint

* Implement initial classical multiplier

* Implement test for multipliers

* Update docstring

* fix trailing whitespace in line beginning

* rework docstrings for less duplication

* Change instructions to gates in adder circuits

* Generalize multiplier for any adder

* Expand multiplier test with all adders

* Fix pylint errors

* Implement review suggestions

Co-authored-by: Julien Gacon <jules.gacon@googlemail.com>

* several updates

* modular RippleCarryAdder
* test that ancillas are uncomputed
* fix ClassicalAdder ancilla uncompute
* replace all to_instruction by to_gate

* lint

* rename classical to plain, make plain modular

* Implement initial QFT multiplier draft

* Update QFT multiplier docstring

* Create common multiplier class

* Renamed PlainAdder to VBERippleCarryAdder (#29)

Co-authored-by: Cryoris <jules.gacon@googlemail.com>

* rename modular->fixed point

and fix lint

* add author names to adders

* add comment on how the test works

* black

* Fix common multiplier errors

* Update classical multiplier default adder name

* Fix QFT multiplier registers

* Implement tests for multipliers

* Fix lint

* Add black formatter changes

* Switch to adder object instead of type

* Update classical multiplier drawing

* Switch to PhaseGate in QFT multiplier

* Rename multipliers

* Implement modulo in QFT multiplier

* Update to half adder in cumulative multiplier

* Make number of output qubits optional

* Implement modular cumulative multiplier

* Add test for modular multiplication

* Fix wrong parameter name

* Expand common multiplier class with checks; Add more tests for multipliers

* Add black and lint changes

* Add releasenote

* Attempt cyclic import fix

* Attempt to fix cyclic import once more

* Update qiskit/circuit/library/arithmetic/multipliers/hrs_cumulative_multiplier.py

Co-authored-by: Julien Gacon <gaconju@gmail.com>

* Update qiskit/circuit/library/arithmetic/multipliers/hrs_cumulative_multiplier.py

Co-authored-by: Julien Gacon <gaconju@gmail.com>

* Update qiskit/circuit/library/arithmetic/multipliers/hrs_cumulative_multiplier.py

Co-authored-by: Julien Gacon <gaconju@gmail.com>

* Update qiskit/circuit/library/arithmetic/multipliers/multiplier.py

Co-authored-by: Julien Gacon <gaconju@gmail.com>

* Update qiskit/circuit/library/arithmetic/multipliers/hrs_cumulative_multiplier.py

Co-authored-by: Julien Gacon <gaconju@gmail.com>

* Add additional test cases

Co-authored-by: Julien Gacon <gaconju@gmail.com>

* Include circuit diagram for QFT multiplier

Co-authored-by: Julien Gacon <gaconju@gmail.com>

* Update docstring as suggested

Co-authored-by: Julien Gacon <gaconju@gmail.com>

* Move num_result_qubits logic to parent class

Co-authored-by: Julien Gacon <gaconju@gmail.com>

* Switch to NotImplementedError for cumulative multiplier

Co-authored-by: Julien Gacon <gaconju@gmail.com>

* Add black changes

* Update Multiplier class docstring

Co-authored-by: Julien Gacon <gaconju@gmail.com>

* Add lint changes

* Update qiskit/circuit/library/arithmetic/multipliers/multiplier.py

Co-authored-by: Julien Gacon <gaconju@gmail.com>

* Update qiskit/circuit/library/arithmetic/multipliers/multiplier.py

Co-authored-by: Julien Gacon <gaconju@gmail.com>

* Add black and pylint fixes

Co-authored-by: Julien Gacon <jules.gacon@googlemail.com>
Co-authored-by: Julien Gacon <gaconju@gmail.com>
Co-authored-by: Manjula <64902594+ManjulaGandhi@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
5 people authored Jun 1, 2021
1 parent a2193a5 commit 57d6f9d
Show file tree
Hide file tree
Showing 8 changed files with 535 additions and 0 deletions.
11 changes: 11 additions & 0 deletions qiskit/circuit/library/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,15 @@
VBERippleCarryAdder
WeightedAdder
Multipliers
+++++++++++
.. autosummary::
:toctree: ../stubs/
HRSCumulativeMultiplier
RGQFTMultiplier
Comparators
+++++++++++
Expand Down Expand Up @@ -362,6 +371,8 @@
CDKMRippleCarryAdder,
DraperQFTAdder,
PiecewiseChebyshev,
HRSCumulativeMultiplier,
RGQFTMultiplier,
)

from .n_local import (
Expand Down
1 change: 1 addition & 0 deletions qiskit/circuit/library/arithmetic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
16 changes: 16 additions & 0 deletions qiskit/circuit/library/arithmetic/multipliers/__init__.py
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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 <https://arxiv.org/pdf/1805.12445.pdf>`_
"""

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)
101 changes: 101 additions & 0 deletions qiskit/circuit/library/arithmetic/multipliers/multiplier.py
Original file line number Diff line number Diff line change
@@ -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
97 changes: 97 additions & 0 deletions qiskit/circuit/library/arithmetic/multipliers/rg_qft_multiplier.py
Original file line number Diff line number Diff line change
@@ -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 <https://arxiv.org/pdf/1411.5949.pdf>`_
"""

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[:])
Loading

0 comments on commit 57d6f9d

Please sign in to comment.