diff --git a/pyproject.toml b/pyproject.toml index 91ef14f2e84d..6c54b1c55cc7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -101,6 +101,20 @@ sk = "qiskit.transpiler.passes.synthesis.solovay_kitaev_synthesis:SolovayKitaevS "qft.line" = "qiskit.transpiler.passes.synthesis.hls_plugins:QFTSynthesisLine" "qft.default" = "qiskit.transpiler.passes.synthesis.hls_plugins:QFTSynthesisFull" "permutation.token_swapper" = "qiskit.transpiler.passes.synthesis.hls_plugins:TokenSwapperSynthesisPermutation" +"ModularAdder.default" = "qiskit.transpiler.passes.synthesis.hls_plugins:ModularAdderSynthesisDefault" +"ModularAdder.ripple_c04" = "qiskit.transpiler.passes.synthesis.hls_plugins:ModularAdderSynthesisC04" +"ModularAdder.ripple_v95" = "qiskit.transpiler.passes.synthesis.hls_plugins:ModularAdderSynthesisV95" +"ModularAdder.qft_d00" = "qiskit.transpiler.passes.synthesis.hls_plugins:ModularAdderSynthesisD00" +"HalfAdder.default" = "qiskit.transpiler.passes.synthesis.hls_plugins:HalfAdderSynthesisDefault" +"HalfAdder.ripple_c04" = "qiskit.transpiler.passes.synthesis.hls_plugins:HalfAdderSynthesisC04" +"HalfAdder.ripple_v95" = "qiskit.transpiler.passes.synthesis.hls_plugins:HalfAdderSynthesisV95" +"HalfAdder.qft_d00" = "qiskit.transpiler.passes.synthesis.hls_plugins:HalfAdderSynthesisD00" +"FullAdder.default" = "qiskit.transpiler.passes.synthesis.hls_plugins:FullAdderSynthesisC04" +"FullAdder.ripple_c04" = "qiskit.transpiler.passes.synthesis.hls_plugins:FullAdderSynthesisC04" +"FullAdder.ripple_v95" = "qiskit.transpiler.passes.synthesis.hls_plugins:FullAdderSynthesisV95" +"Multiplier.default" = "qiskit.transpiler.passes.synthesis.hls_plugins:MultiplierSynthesisR17" +"Multiplier.qft_r17" = "qiskit.transpiler.passes.synthesis.hls_plugins:MultiplierSynthesisR17" +"Multiplier.cumulative_h18" = "qiskit.transpiler.passes.synthesis.hls_plugins:MultiplierSynthesisH18" "PauliEvolution.default" = "qiskit.transpiler.passes.synthesis.hls_plugins:PauliEvolutionSynthesisDefault" [project.entry-points."qiskit.transpiler.init"] diff --git a/qiskit/circuit/library/__init__.py b/qiskit/circuit/library/__init__.py index e17a4c0ae05e..4125c76b1e65 100644 --- a/qiskit/circuit/library/__init__.py +++ b/qiskit/circuit/library/__init__.py @@ -554,6 +554,10 @@ ) from .basis_change import QFT, QFTGate from .arithmetic import ( + ModularAdderGate, + HalfAdderGate, + FullAdderGate, + MultiplierGate, FunctionalPauliRotations, LinearPauliRotations, PiecewiseLinearPauliRotations, diff --git a/qiskit/circuit/library/arithmetic/__init__.py b/qiskit/circuit/library/arithmetic/__init__.py index 71c6cb7f5df5..ede35a9451e1 100644 --- a/qiskit/circuit/library/arithmetic/__init__.py +++ b/qiskit/circuit/library/arithmetic/__init__.py @@ -21,7 +21,14 @@ from .weighted_adder import WeightedAdder from .quadratic_form import QuadraticForm from .linear_amplitude_function import LinearAmplitudeFunction -from .adders import VBERippleCarryAdder, CDKMRippleCarryAdder, DraperQFTAdder +from .adders import ( + VBERippleCarryAdder, + CDKMRippleCarryAdder, + DraperQFTAdder, + ModularAdderGate, + HalfAdderGate, + FullAdderGate, +) from .piecewise_chebyshev import PiecewiseChebyshev -from .multipliers import HRSCumulativeMultiplier, RGQFTMultiplier +from .multipliers import HRSCumulativeMultiplier, RGQFTMultiplier, MultiplierGate from .exact_reciprocal import ExactReciprocal diff --git a/qiskit/circuit/library/arithmetic/adders/__init__.py b/qiskit/circuit/library/arithmetic/adders/__init__.py index 1b53c707318d..03c042ab70c5 100644 --- a/qiskit/circuit/library/arithmetic/adders/__init__.py +++ b/qiskit/circuit/library/arithmetic/adders/__init__.py @@ -15,3 +15,4 @@ from .cdkm_ripple_carry_adder import CDKMRippleCarryAdder from .draper_qft_adder import DraperQFTAdder from .vbe_ripple_carry_adder import VBERippleCarryAdder +from .adder import ModularAdderGate, HalfAdderGate, FullAdderGate diff --git a/qiskit/circuit/library/arithmetic/adders/adder.py b/qiskit/circuit/library/arithmetic/adders/adder.py index 2e1a814d92cc..9330f8a8ee8b 100644 --- a/qiskit/circuit/library/arithmetic/adders/adder.py +++ b/qiskit/circuit/library/arithmetic/adders/adder.py @@ -12,7 +12,10 @@ """Compute the sum of two equally sized qubit registers.""" -from qiskit.circuit import QuantumCircuit +from __future__ import annotations + +from qiskit.circuit import QuantumCircuit, Gate +from qiskit.utils.deprecation import deprecate_func class Adder(QuantumCircuit): @@ -39,6 +42,16 @@ class Adder(QuantumCircuit): """ + @deprecate_func( + since="1.3", + additional_msg=( + "Use the adder gates provided in qiskit.circuit.library.arithmetic instead. " + "The gate type depends on the adder kind: fixed, half, full are represented by " + "ModularAdderGate, HalfAdderGate, FullAdderGate, respectively. For different adder " + "implementations, see https://docs.quantum.ibm.com/api/qiskit/synthesis.", + ), + pending=True, + ) def __init__(self, num_state_qubits: int, name: str = "Adder") -> None: """ Args: @@ -56,3 +69,142 @@ def num_state_qubits(self) -> int: The number of state qubits. """ return self._num_state_qubits + + +class HalfAdderGate(Gate): + r"""Compute the sum of two equally-sized qubit registers, including a carry-out bit. + + For two registers :math:`|a\rangle_n` and :math:|b\rangle_n` with :math:`n` qubits each, an + adder performs the following operation + + .. math:: + + |a\rangle_n |b\rangle_n \mapsto |a\rangle_n |a + b\rangle_{n + 1}. + + The quantum register :math:`|a\rangle_n` (and analogously :math:`|b\rangle_n`) + + .. 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, label: str | None = None) -> None: + """ + Args: + num_state_qubits: The number of qubits in each of the registers. + name: The name of the circuit. + """ + if num_state_qubits < 1: + raise ValueError("Need at least 1 state qubit.") + + super().__init__("HalfAdder", 2 * num_state_qubits + 1, [], label=label) + self._num_state_qubits = num_state_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 + + +class ModularAdderGate(Gate): + r"""Compute the sum modulo :math:`2^n` of two :math:`n`-sized qubit registers. + + For two registers :math:`|a\rangle_n` and :math:|b\rangle_n` with :math:`n` qubits each, an + adder performs the following operation + + .. math:: + + |a\rangle_n |b\rangle_n \mapsto |a\rangle_n |a + b \text{ mod } 2^n\rangle_n. + + The quantum register :math:`|a\rangle_n` (and analogously :math:`|b\rangle_n`) + + .. 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, label: str | None = None) -> None: + """ + Args: + num_state_qubits: The number of qubits in each of the registers. + name: The name of the circuit. + """ + if num_state_qubits < 1: + raise ValueError("Need at least 1 state qubit.") + + super().__init__("ModularAdder", 2 * num_state_qubits, [], label=label) + self._num_state_qubits = num_state_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 + + +class FullAdderGate(Gate): + r"""Compute the sum of two :math:`n`-sized qubit registers, including carry-in and -out bits. + + For two registers :math:`|a\rangle_n` and :math:|b\rangle_n` with :math:`n` qubits each, an + adder performs the following operation + + .. math:: + + |c_{\text{in}\rangle_1 |a\rangle_n |b\rangle_n + \mapsto |a\rangle_n |c_{\text{in}} + a + b \rangle_{n + 1}. + + The quantum register :math:`|a\rangle_n` (and analogously :math:`|b\rangle_n`) + + .. 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, label: str | None = None) -> None: + """ + Args: + num_state_qubits: The number of qubits in each of the registers. + name: The name of the circuit. + """ + if num_state_qubits < 1: + raise ValueError("Need at least 1 state qubit.") + + super().__init__("FullAdder", 2 * num_state_qubits + 2, [], label=label) + self._num_state_qubits = num_state_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 diff --git a/qiskit/circuit/library/arithmetic/adders/cdkm_ripple_carry_adder.py b/qiskit/circuit/library/arithmetic/adders/cdkm_ripple_carry_adder.py index 3e18791e1cb6..7f610302adb8 100644 --- a/qiskit/circuit/library/arithmetic/adders/cdkm_ripple_carry_adder.py +++ b/qiskit/circuit/library/arithmetic/adders/cdkm_ripple_carry_adder.py @@ -12,8 +12,7 @@ """Compute the sum of two qubit registers using ripple-carry approach.""" -from qiskit.circuit import QuantumCircuit, QuantumRegister, AncillaRegister - +from qiskit.synthesis.arithmetic import adder_ripple_c04 from .adder import Adder @@ -75,6 +74,21 @@ class CDKMRippleCarryAdder(Adder): It has one less qubit than the full-adder since it doesn't have the carry-out, but uses a helper qubit instead of the carry-in, so it only has one less qubit, not two. + .. seealso:: + + The following generic gate objects perform additions, like this circuit class, + but allow the compiler to select the optimal decomposition based on the context. + Specific implementations can be set via the :class:`.HLSConfig`, e.g. this circuit + can be chosen via ``Adder=["ripple_c04"]``. + + :class:`.ModularAdderGate`: A generic inplace adder, modulo :math:`2^n`. This + is functionally equivalent to ``kind="fixed"``. + + :class:`.AdderGate`: A generic inplace adder. This + is functionally equivalent to ``kind="half"``. + + :class:`.FullAdderGate`: A generic inplace adder, with a carry-in bit. This + is functionally equivalent to ``kind="full"``. **References:** @@ -102,58 +116,8 @@ def __init__( Raises: ValueError: If ``num_state_qubits`` is lower than 1. """ - if num_state_qubits < 1: - raise ValueError("The number of qubits must be at least 1.") - super().__init__(num_state_qubits, name=name) + circuit = adder_ripple_c04(num_state_qubits, kind) - if kind == "full": - qr_c = QuantumRegister(1, name="cin") - self.add_register(qr_c) - else: - qr_c = AncillaRegister(1, name="help") - - qr_a = QuantumRegister(num_state_qubits, name="a") - qr_b = QuantumRegister(num_state_qubits, name="b") - self.add_register(qr_a, qr_b) - - if kind in ["full", "half"]: - qr_z = QuantumRegister(1, name="cout") - self.add_register(qr_z) - - if kind != "full": - self.add_register(qr_c) - - # build carry circuit for majority of 3 bits in-place - # corresponds to MAJ gate in [1] - qc_maj = QuantumCircuit(3, name="MAJ") - qc_maj.cx(0, 1) - qc_maj.cx(0, 2) - qc_maj.ccx(2, 1, 0) - maj_gate = qc_maj.to_gate() - - # build circuit for reversing carry operation - # corresponds to UMA gate in [1] - qc_uma = QuantumCircuit(3, name="UMA") - qc_uma.ccx(2, 1, 0) - qc_uma.cx(0, 2) - qc_uma.cx(2, 1) - uma_gate = qc_uma.to_gate() - - circuit = QuantumCircuit(*self.qregs, name=name) - - # build ripple-carry adder circuit - circuit.append(maj_gate, [qr_a[0], qr_b[0], qr_c[0]]) - - for i in range(num_state_qubits - 1): - circuit.append(maj_gate, [qr_a[i + 1], qr_b[i + 1], qr_a[i]]) - - if kind in ["full", "half"]: - circuit.cx(qr_a[-1], qr_z[0]) - - for i in reversed(range(num_state_qubits - 1)): - circuit.append(uma_gate, [qr_a[i + 1], qr_b[i + 1], qr_a[i]]) - - circuit.append(uma_gate, [qr_a[0], qr_b[0], qr_c[0]]) - + self.add_register(*circuit.qregs) self.append(circuit.to_gate(), self.qubits) diff --git a/qiskit/circuit/library/arithmetic/adders/draper_qft_adder.py b/qiskit/circuit/library/arithmetic/adders/draper_qft_adder.py index c213b85c422f..62f8c88ffcdc 100644 --- a/qiskit/circuit/library/arithmetic/adders/draper_qft_adder.py +++ b/qiskit/circuit/library/arithmetic/adders/draper_qft_adder.py @@ -44,6 +44,19 @@ class DraperQFTAdder(Adder): cout_0: ┤2 ├────────────────────────■────────■───────┤2 ├ └──────┘ └───────┘ + .. seealso:: + + The following generic gate objects perform additions, like this circuit class, + but allow the compiler to select the optimal decomposition based on the context. + Specific implementations can be set via the :class:`.HLSConfig`, e.g. this + circuit can be chosen via ``Adder=["qft_d00"]``. + + :class:`.ModularAdderGate`: A generic inplace adder, modulo :math:`2^n`. This + is functionally equivalent to ``kind="fixed"``. + + :class:`.AdderGate`: A generic inplace adder. This + is functionally equivalent to ``kind="half"``. + **References:** [1] T. G. Draper, Addition on a Quantum Computer, 2000. diff --git a/qiskit/circuit/library/arithmetic/adders/vbe_ripple_carry_adder.py b/qiskit/circuit/library/arithmetic/adders/vbe_ripple_carry_adder.py index 0279738cb94f..2f8d9ed31e2c 100644 --- a/qiskit/circuit/library/arithmetic/adders/vbe_ripple_carry_adder.py +++ b/qiskit/circuit/library/arithmetic/adders/vbe_ripple_carry_adder.py @@ -11,11 +11,9 @@ # that they have been altered from the originals. """Compute the sum of two qubit registers using Classical Addition.""" -from __future__ import annotations -from qiskit.circuit.bit import Bit - -from qiskit.circuit import QuantumCircuit, QuantumRegister, AncillaRegister +from __future__ import annotations +from qiskit.synthesis.arithmetic import adder_ripple_v95 from .adder import Adder @@ -52,6 +50,22 @@ class VBERippleCarryAdder(Adder): This is different ordering as compared to Figure 2 in [1], which leads to a different drawing of the circuit. + .. seealso:: + + The following generic gate objects perform additions, like this circuit class, + but allow the compiler to select the optimal decomposition based on the context. + Specific implementations can be set via the :class:`.HLSConfig`, e.g. this circuit + can be chosen via ``Adder=["ripple_v95"]``. + + :class:`.ModularAdderGate`: A generic inplace adder, modulo :math:`2^n`. This + is functionally equivalent to ``kind="fixed"``. + + :class:`.AdderGate`: A generic inplace adder. This + is functionally equivalent to ``kind="half"``. + + :class:`.FullAdderGate`: A generic inplace adder, with a carry-in bit. This + is functionally equivalent to ``kind="full"``. + **References:** [1] Vedral et al., Quantum Networks for Elementary Arithmetic Operations, 1995. @@ -74,92 +88,8 @@ def __init__( Raises: ValueError: If ``num_state_qubits`` is lower than 1. """ - if num_state_qubits < 1: - raise ValueError("The number of qubits must be at least 1.") - super().__init__(num_state_qubits, name=name) + circuit = adder_ripple_v95(num_state_qubits, kind) - # define the input registers - registers: list[QuantumRegister | list[Bit]] = [] - if kind == "full": - qr_cin = QuantumRegister(1, name="cin") - registers.append(qr_cin) - else: - qr_cin = QuantumRegister(0) - - qr_a = QuantumRegister(num_state_qubits, name="a") - qr_b = QuantumRegister(num_state_qubits, name="b") - - registers += [qr_a, qr_b] - - if kind in ["half", "full"]: - qr_cout = QuantumRegister(1, name="cout") - registers.append(qr_cout) - else: - qr_cout = QuantumRegister(0) - - self.add_register(*registers) - - if num_state_qubits > 1: - qr_help = AncillaRegister(num_state_qubits - 1, name="helper") - self.add_register(qr_help) - else: - qr_help = AncillaRegister(0) - - # the code is simplified a lot if we create a list of all carries and helpers - carries = qr_cin[:] + qr_help[:] + qr_cout[:] - - # corresponds to Carry gate in [1] - qc_carry = QuantumCircuit(4, name="Carry") - qc_carry.ccx(1, 2, 3) - qc_carry.cx(1, 2) - qc_carry.ccx(0, 2, 3) - carry_gate = qc_carry.to_gate() - carry_gate_dg = carry_gate.inverse() - - # corresponds to Sum gate in [1] - qc_sum = QuantumCircuit(3, name="Sum") - qc_sum.cx(1, 2) - qc_sum.cx(0, 2) - sum_gate = qc_sum.to_gate() - - circuit = QuantumCircuit(*self.qregs, name=name) - - # handle all cases for the first qubits, depending on whether cin is available - i = 0 - if kind == "half": - i += 1 - circuit.ccx(qr_a[0], qr_b[0], carries[0]) - elif kind == "fixed": - i += 1 - if num_state_qubits == 1: - circuit.cx(qr_a[0], qr_b[0]) - else: - circuit.ccx(qr_a[0], qr_b[0], carries[0]) - - for inp, out in zip(carries[:-1], carries[1:]): - circuit.append(carry_gate, [inp, qr_a[i], qr_b[i], out]) - i += 1 - - if kind in ["full", "half"]: # final CX (cancels for the 'fixed' case) - circuit.cx(qr_a[-1], qr_b[-1]) - - if len(carries) > 1: - circuit.append(sum_gate, [carries[-2], qr_a[-1], qr_b[-1]]) - - i -= 2 - for j, (inp, out) in enumerate(zip(reversed(carries[:-1]), reversed(carries[1:]))): - if j == 0: - if kind == "fixed": - i += 1 - else: - continue - circuit.append(carry_gate_dg, [inp, qr_a[i], qr_b[i], out]) - circuit.append(sum_gate, [inp, qr_a[i], qr_b[i]]) - i -= 1 - - if kind in ["half", "fixed"] and num_state_qubits > 1: - circuit.ccx(qr_a[0], qr_b[0], carries[0]) - circuit.cx(qr_a[0], qr_b[0]) - + self.add_register(*circuit.qregs) self.append(circuit.to_gate(), self.qubits) diff --git a/qiskit/circuit/library/arithmetic/multipliers/__init__.py b/qiskit/circuit/library/arithmetic/multipliers/__init__.py index 64a7edb0e7e0..c708d6d051fe 100644 --- a/qiskit/circuit/library/arithmetic/multipliers/__init__.py +++ b/qiskit/circuit/library/arithmetic/multipliers/__init__.py @@ -14,3 +14,4 @@ from .hrs_cumulative_multiplier import HRSCumulativeMultiplier from .rg_qft_multiplier import RGQFTMultiplier +from .multiplier import MultiplierGate diff --git a/qiskit/circuit/library/arithmetic/multipliers/hrs_cumulative_multiplier.py b/qiskit/circuit/library/arithmetic/multipliers/hrs_cumulative_multiplier.py index ba9ed8c89cdc..3a0029d7e896 100644 --- a/qiskit/circuit/library/arithmetic/multipliers/hrs_cumulative_multiplier.py +++ b/qiskit/circuit/library/arithmetic/multipliers/hrs_cumulative_multiplier.py @@ -59,6 +59,13 @@ class HRSCumulativeMultiplier(Multiplier): 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. + .. seealso:: + + The :class:`.MultiplierGate` objects represents a multiplication, like this circuit class, + but allows the compiler to select the optimal decomposition based on the context. + Specific implementations can be set via the :class:`.HLSConfig`, e.g. this circuit + can be chosen via ``Multiplier=["cumulative_h18"]``. + **References:** [1] Häner et al., Optimizing Quantum Circuits for Arithmetic, 2018. diff --git a/qiskit/circuit/library/arithmetic/multipliers/multiplier.py b/qiskit/circuit/library/arithmetic/multipliers/multiplier.py index a56240438829..4089cc35452a 100644 --- a/qiskit/circuit/library/arithmetic/multipliers/multiplier.py +++ b/qiskit/circuit/library/arithmetic/multipliers/multiplier.py @@ -12,9 +12,10 @@ """Compute the product of two equally sized qubit registers.""" -from typing import Optional +from __future__ import annotations -from qiskit.circuit import QuantumCircuit +from qiskit.circuit import QuantumCircuit, Gate +from qiskit.utils.deprecation import deprecate_func class Multiplier(QuantumCircuit): @@ -45,10 +46,19 @@ class Multiplier(QuantumCircuit): """ + @deprecate_func( + since="1.3", + additional_msg=( + "Use the MultiplierGate provided in qiskit.circuit.library.arithmetic instead. " + "For different multiplier implementations, see " + "https://docs.quantum.ibm.com/api/qiskit/synthesis.", + ), + pending=True, + ) def __init__( self, num_state_qubits: int, - num_result_qubits: Optional[int] = None, + num_result_qubits: int | None = None, name: str = "Multiplier", ) -> None: """ @@ -99,3 +109,84 @@ def num_result_qubits(self) -> int: The number of result qubits. """ return self._num_result_qubits + + +class MultiplierGate(Gate): + 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: int | None = None, + label: str | None = None, + ) -> 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 state qubits must be at least 1.") + + if num_result_qubits is None: + num_result_qubits = 2 * num_state_qubits + elif num_result_qubits < num_state_qubits or num_result_qubits > 2 * num_state_qubits: + raise ValueError( + f"num_result_qubits ({num_result_qubits}) must be in between num_state_qubits " + f"({num_state_qubits}) and 2 * num_state_qubits ({2 * num_state_qubits})" + ) + + super().__init__("Multiplier", 2 * num_state_qubits + num_result_qubits, [], label=label) + + 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 index 4bd2799733f2..ad213cd4dce5 100644 --- a/qiskit/circuit/library/arithmetic/multipliers/rg_qft_multiplier.py +++ b/qiskit/circuit/library/arithmetic/multipliers/rg_qft_multiplier.py @@ -48,6 +48,13 @@ class RGQFTMultiplier(Multiplier): out_1: ┤1 ├─────────■───────────────■──────────────■─────────────■───────┤1 ├ └──────┘ └───────┘ + .. seealso:: + + The :class:`.MultiplierGate` objects represents a multiplication, like this circuit class, + but allows the compiler to select the optimal decomposition based on the context. + Specific implementations can be set via the :class:`.HLSConfig`, e.g. this circuit + can be chosen via ``Multiplier=["qft_r17"]``. + **References:** [1] Ruiz-Perez et al., Quantum arithmetic with the Quantum Fourier Transform, 2017. diff --git a/qiskit/synthesis/__init__.py b/qiskit/synthesis/__init__.py index 3d91734277b2..adea95d4260c 100644 --- a/qiskit/synthesis/__init__.py +++ b/qiskit/synthesis/__init__.py @@ -134,6 +134,22 @@ .. autofunction:: synth_c3x .. autofunction:: synth_c4x +Binary Arithmetic Synthesis +=========================== + +Adders +------ + +.. autofunction:: adder_qft_d00 +.. autofunction:: adder_ripple_c04 +.. autofunction:: adder_ripple_v95 + +Multipliers +----------- + +.. autofunction:: multiplier_cumulative_h18 +.. autofunction:: multiplier_qft_r17 + """ from .evolution import ( @@ -195,3 +211,10 @@ synth_c3x, synth_c4x, ) +from .arithmetic import ( + adder_qft_d00, + adder_ripple_c04, + adder_ripple_v95, + multiplier_cumulative_h18, + multiplier_qft_r17, +) diff --git a/qiskit/synthesis/arithmetic/__init__.py b/qiskit/synthesis/arithmetic/__init__.py new file mode 100644 index 000000000000..f413421d6b4b --- /dev/null +++ b/qiskit/synthesis/arithmetic/__init__.py @@ -0,0 +1,16 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2024. +# +# 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. + +"""Module containing multi-controlled circuits synthesis""" + +from .adders import adder_qft_d00, adder_ripple_c04, adder_ripple_v95 +from .multipliers import multiplier_cumulative_h18, multiplier_qft_r17 diff --git a/qiskit/synthesis/arithmetic/adders/__init__.py b/qiskit/synthesis/arithmetic/adders/__init__.py new file mode 100644 index 000000000000..73be2f449f03 --- /dev/null +++ b/qiskit/synthesis/arithmetic/adders/__init__.py @@ -0,0 +1,17 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2024. +# +# 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. + +"""Module containing multi-controlled circuits synthesis""" + +from .cdkm_ripple_carry_adder import adder_ripple_c04 +from .vbe_ripple_carry_adder import adder_ripple_v95 +from .draper_qft_adder import adder_qft_d00 diff --git a/qiskit/synthesis/arithmetic/adders/cdkm_ripple_carry_adder.py b/qiskit/synthesis/arithmetic/adders/cdkm_ripple_carry_adder.py new file mode 100644 index 000000000000..9e18bfbb9fff --- /dev/null +++ b/qiskit/synthesis/arithmetic/adders/cdkm_ripple_carry_adder.py @@ -0,0 +1,154 @@ +# 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 sum of two qubit registers using ripple-carry approach.""" + +from qiskit.circuit.quantumcircuit import QuantumCircuit +from qiskit.circuit.quantumregister import QuantumRegister, AncillaRegister + + +def adder_ripple_c04(num_state_qubits: int, kind: str = "half") -> QuantumCircuit: + r"""A ripple-carry circuit to perform in-place addition on two qubit registers. + + This circuit uses :math:`2n + O(1)` CCX gates and :math:`5n + O(1)` CX gates, + at a depth of :math:`2n + O(1)` [1]. The constant depends on the kind + of adder implemented. + + As an example, a ripple-carry adder circuit that performs addition on two 3-qubit sized + registers with a carry-in bit (``kind="full"``) is as follows: + + .. parsed-literal:: + + ┌──────┐ ┌──────┐ + cin_0: ┤2 ├─────────────────────────────────────┤2 ├ + │ │┌──────┐ ┌──────┐│ │ + a_0: ┤0 ├┤2 ├─────────────────────┤2 ├┤0 ├ + │ ││ │┌──────┐ ┌──────┐│ ││ │ + a_1: ┤ MAJ ├┤0 ├┤2 ├─────┤2 ├┤0 ├┤ UMA ├ + │ ││ ││ │ │ ││ ││ │ + a_2: ┤ ├┤ MAJ ├┤0 ├──■──┤0 ├┤ UMA ├┤ ├ + │ ││ ││ │ │ │ ││ ││ │ + b_0: ┤1 ├┤ ├┤ MAJ ├──┼──┤ UMA ├┤ ├┤1 ├ + └──────┘│ ││ │ │ │ ││ │└──────┘ + b_1: ────────┤1 ├┤ ├──┼──┤ ├┤1 ├──────── + └──────┘│ │ │ │ │└──────┘ + b_2: ────────────────┤1 ├──┼──┤1 ├──────────────── + └──────┘┌─┴─┐└──────┘ + cout_0: ────────────────────────┤ X ├──────────────────────── + └───┘ + + Here *MAJ* and *UMA* gates correspond to the gates introduced in [1]. Note that + in this implementation the input register qubits are ordered as all qubits from + the first input register, followed by all qubits from the second input register. + + Two different kinds of adders are supported. By setting the ``kind`` argument, you can also + choose a half-adder, which doesn't have a carry-in, and a fixed-sized-adder, which has neither + carry-in nor carry-out, and thus acts on fixed register sizes. Unlike the full-adder, + these circuits need one additional helper qubit. + + The circuit diagram for the fixed-point adder (``kind="fixed"``) on 3-qubit sized inputs is + + .. parsed-literal:: + + ┌──────┐┌──────┐ ┌──────┐┌──────┐ + a_0: ┤0 ├┤2 ├────────────────┤2 ├┤0 ├ + │ ││ │┌──────┐┌──────┐│ ││ │ + a_1: ┤ ├┤0 ├┤2 ├┤2 ├┤0 ├┤ ├ + │ ││ ││ ││ ││ ││ │ + a_2: ┤ ├┤ MAJ ├┤0 ├┤0 ├┤ UMA ├┤ ├ + │ ││ ││ ││ ││ ││ │ + b_0: ┤1 MAJ ├┤ ├┤ MAJ ├┤ UMA ├┤ ├┤1 UMA ├ + │ ││ ││ ││ ││ ││ │ + b_1: ┤ ├┤1 ├┤ ├┤ ├┤1 ├┤ ├ + │ │└──────┘│ ││ │└──────┘│ │ + b_2: ┤ ├────────┤1 ├┤1 ├────────┤ ├ + │ │ └──────┘└──────┘ │ │ + help_0: ┤2 ├────────────────────────────────┤2 ├ + └──────┘ └──────┘ + + It has one less qubit than the full-adder since it doesn't have the carry-out, but uses + a helper qubit instead of the carry-in, so it only has one less qubit, not two. + + 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. + kind: The kind of adder, can be ``"full"`` for a full adder, ``"half"`` for a half + adder, or ``"fixed"`` for a fixed-sized adder. A full adder includes both carry-in + and carry-out, a half only carry-out, and a fixed-sized adder neither carry-in + nor carry-out. + + Raises: + ValueError: If ``num_state_qubits`` is lower than 1. + + **References:** + + [1] Cuccaro et al., A new quantum ripple-carry addition circuit, 2004. + `arXiv:quant-ph/0410184 `_ + + [2] Vedral et al., Quantum Networks for Elementary Arithmetic Operations, 1995. + `arXiv:quant-ph/9511018 `_ + + """ + if num_state_qubits < 1: + raise ValueError("The number of qubits must be at least 1.") + + circuit = QuantumCircuit() + + if kind == "full": + qr_c = QuantumRegister(1, name="cin") + circuit.add_register(qr_c) + else: + qr_c = AncillaRegister(1, name="help") + + qr_a = QuantumRegister(num_state_qubits, name="a") + qr_b = QuantumRegister(num_state_qubits, name="b") + circuit.add_register(qr_a, qr_b) + + if kind in ["full", "half"]: + qr_z = QuantumRegister(1, name="cout") + circuit.add_register(qr_z) + + if kind != "full": + circuit.add_register(qr_c) + + # build carry circuit for majority of 3 bits in-place + # corresponds to MAJ gate in [1] + qc_maj = QuantumCircuit(3, name="MAJ") + qc_maj.cx(0, 1) + qc_maj.cx(0, 2) + qc_maj.ccx(2, 1, 0) + maj_gate = qc_maj.to_gate() + + # build circuit for reversing carry operation + # corresponds to UMA gate in [1] + qc_uma = QuantumCircuit(3, name="UMA") + qc_uma.ccx(2, 1, 0) + qc_uma.cx(0, 2) + qc_uma.cx(2, 1) + uma_gate = qc_uma.to_gate() + + # build ripple-carry adder circuit + circuit.append(maj_gate, [qr_a[0], qr_b[0], qr_c[0]]) + + for i in range(num_state_qubits - 1): + circuit.append(maj_gate, [qr_a[i + 1], qr_b[i + 1], qr_a[i]]) + + if kind in ["full", "half"]: + circuit.cx(qr_a[-1], qr_z[0]) + + for i in reversed(range(num_state_qubits - 1)): + circuit.append(uma_gate, [qr_a[i + 1], qr_b[i + 1], qr_a[i]]) + + circuit.append(uma_gate, [qr_a[0], qr_b[0], qr_c[0]]) + + return circuit diff --git a/qiskit/synthesis/arithmetic/adders/draper_qft_adder.py b/qiskit/synthesis/arithmetic/adders/draper_qft_adder.py new file mode 100644 index 000000000000..0bf9629b4249 --- /dev/null +++ b/qiskit/synthesis/arithmetic/adders/draper_qft_adder.py @@ -0,0 +1,103 @@ +# 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 sum of two qubit registers using QFT.""" + +import numpy as np + +from qiskit.circuit.quantumcircuit import QuantumCircuit +from qiskit.circuit.quantumregister import QuantumRegister +from qiskit.circuit.library.basis_change import QFTGate + + +def adder_qft_d00(num_state_qubits: int, kind: str = "half") -> QuantumCircuit: + r"""A circuit that uses QFT to perform in-place addition on two qubit registers. + + For registers with :math:`n` qubits, the QFT adder can perform addition modulo + :math:`2^n` (with ``kind="fixed"``) or ordinary addition by adding a carry qubits (with + ``kind="half"``). The fixed adder uses :math:`(3n^2 - n)/2` :class:`.CPhaseGate` operators, + with an additional :math:`n` for the half adder. + + As an example, a non-fixed_point QFT adder circuit that performs addition on two 2-qubit sized + registers is as follows: + + .. parsed-literal:: + + a_0: ─────────■──────■────────■────────────────────────────────── + │ │ │ + a_1: ─────────┼──────┼────────┼────────■──────■────────────────── + ┌──────┐ │ │ │P(π/4) │ │P(π/2) ┌─────────┐ + b_0: ┤0 ├─┼──────┼────────■────────┼──────■───────┤0 ├ + │ │ │ │P(π/2) │P(π) │ │ + b_1: ┤1 Qft ├─┼──────■─────────────────■──────────────┤1 qft_dg ├ + │ │ │P(π) │ │ + cout: ┤2 ├─■───────────────────────────────────────┤2 ├ + └──────┘ └─────────┘ + + 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. + kind: The kind of adder, can be ``"half"`` for a half adder or + ``"fixed"`` for a fixed-sized adder. A half adder contains a carry-out to represent + the most-significant bit, but the fixed-sized adder doesn't and hence performs + addition modulo ``2 ** num_state_qubits``. + + **References:** + + [1] T. G. Draper, Addition on a Quantum Computer, 2000. + `arXiv:quant-ph/0008033 `_ + + [2] Ruiz-Perez et al., Quantum arithmetic with the Quantum Fourier Transform, 2017. + `arXiv:1411.5949 `_ + + [3] Vedral et al., Quantum Networks for Elementary Arithmetic Operations, 1995. + `arXiv:quant-ph/9511018 `_ + + """ + + if kind == "full": + raise ValueError("The DraperQFTAdder only supports 'half' and 'fixed' as ``kind``.") + + if num_state_qubits < 1: + raise ValueError("The number of qubits must be at least 1.") + + qr_a = QuantumRegister(num_state_qubits, name="a") + qr_b = QuantumRegister(num_state_qubits, name="b") + qr_list = [qr_a, qr_b] + + if kind == "half": + qr_z = QuantumRegister(1, name="cout") + qr_list.append(qr_z) + + # add registers + circuit = QuantumCircuit(*qr_list) + + # define register containing the sum and number of qubits for QFT circuit + qr_sum = qr_b[:] if kind == "fixed" else qr_b[:] + qr_z[:] + num_sum = num_state_qubits if kind == "fixed" else num_state_qubits + 1 + + # build QFT adder circuit + qft = QFTGate(num_sum) + circuit.append(qft, qr_sum[:]) + + for j in range(num_state_qubits): + for k in range(num_sum - j): + lam = np.pi / (2**k) + # note: if we were able to remove the final swaps from the QFTGate, we could + # simply use qr_sum[(j + k)] here and avoid synthesizing two swap networks, which + # can be elided and cancelled by the compiler + circuit.cp(lam, qr_a[j], qr_sum[~(j + k)]) + + circuit.append(qft.inverse(), qr_sum[:]) + + return circuit diff --git a/qiskit/synthesis/arithmetic/adders/vbe_ripple_carry_adder.py b/qiskit/synthesis/arithmetic/adders/vbe_ripple_carry_adder.py new file mode 100644 index 000000000000..05d8581db942 --- /dev/null +++ b/qiskit/synthesis/arithmetic/adders/vbe_ripple_carry_adder.py @@ -0,0 +1,161 @@ +# 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 sum of two qubit registers using Classical Addition.""" + +from __future__ import annotations +from qiskit.circuit.bit import Bit + +from qiskit.circuit.quantumcircuit import QuantumCircuit +from qiskit.circuit.quantumregister import QuantumRegister, AncillaRegister + + +def adder_ripple_v95(num_state_qubits: int, kind: str = "half") -> QuantumCircuit: + r"""The VBE ripple carry adder [1]. + + This method uses :math:`4n + O(1)` CCX gates and :math:`4n + 1` CX gates at a depth + of :math:`6n - 2` [2]. + + This circuit performs inplace addition of two equally-sized quantum registers. + As an example, a classical adder circuit that performs full addition (i.e. including + a carry-in bit) on two 2-qubit sized registers is as follows: + + .. parsed-literal:: + + ┌────────┐ ┌───────────┐┌──────┐ + cin_0: ┤0 ├───────────────────────┤0 ├┤0 ├ + │ │ │ ││ │ + a_0: ┤1 ├───────────────────────┤1 ├┤1 ├ + │ │┌────────┐ ┌──────┐│ ││ Sum │ + a_1: ┤ ├┤1 ├──■──┤1 ├┤ ├┤ ├ + │ ││ │ │ │ ││ ││ │ + b_0: ┤2 Carry ├┤ ├──┼──┤ ├┤2 Carry_dg ├┤2 ├ + │ ││ │┌─┴─┐│ ││ │└──────┘ + b_1: ┤ ├┤2 Carry ├┤ X ├┤2 Sum ├┤ ├──────── + │ ││ │└───┘│ ││ │ + cout_0: ┤ ├┤3 ├─────┤ ├┤ ├──────── + │ ││ │ │ ││ │ + helper_0: ┤3 ├┤0 ├─────┤0 ├┤3 ├──────── + └────────┘└────────┘ └──────┘└───────────┘ + + + Here *Carry* and *Sum* gates correspond to the gates introduced in [1]. + *Carry_dg* correspond to the inverse of the *Carry* gate. Note that + in this implementation the input register qubits are ordered as all qubits from + the first input register, followed by all qubits from the second input register. + This is different ordering as compared to Figure 2 in [1], which leads to a different + drawing of the circuit. + + Args: + num_state_qubits: The size of the register. + kind: The kind of adder, can be ``"full"`` for a full adder, ``"half"`` for a half + adder, or ``"fixed"`` for a fixed-sized adder. A full adder includes both carry-in + and carry-out, a half only carry-out, and a fixed-sized adder neither carry-in + nor carry-out. + + Raises: + ValueError: If ``num_state_qubits`` is lower than 1. + + **References:** + + [1] Vedral et al., Quantum Networks for Elementary Arithmetic Operations, 1995. + `arXiv:quant-ph/9511018 `_ + + [2] Cuccaro et al., A new quantum ripple-carry addition circuit, 2004. + `arXiv:quant-ph/0410184 `_ + + """ + if num_state_qubits < 1: + raise ValueError("The number of qubits must be at least 1.") + + # define the input registers + registers: list[QuantumRegister | list[Bit]] = [] + if kind == "full": + qr_cin = QuantumRegister(1, name="cin") + registers.append(qr_cin) + else: + qr_cin = QuantumRegister(0) + + qr_a = QuantumRegister(num_state_qubits, name="a") + qr_b = QuantumRegister(num_state_qubits, name="b") + + registers += [qr_a, qr_b] + + if kind in ["half", "full"]: + qr_cout = QuantumRegister(1, name="cout") + registers.append(qr_cout) + else: + qr_cout = QuantumRegister(0) + + if num_state_qubits > 1: + qr_help = AncillaRegister(num_state_qubits - 1, name="helper") + registers.append(qr_help) + else: + qr_help = AncillaRegister(0) + + circuit = QuantumCircuit(*registers) + + # the code is simplified a lot if we create a list of all carries and helpers + carries = qr_cin[:] + qr_help[:] + qr_cout[:] + + # corresponds to Carry gate in [1] + qc_carry = QuantumCircuit(4, name="Carry") + qc_carry.ccx(1, 2, 3) + qc_carry.cx(1, 2) + qc_carry.ccx(0, 2, 3) + carry_gate = qc_carry.to_gate() + carry_gate_dg = carry_gate.inverse() + + # corresponds to Sum gate in [1] + qc_sum = QuantumCircuit(3, name="Sum") + qc_sum.cx(1, 2) + qc_sum.cx(0, 2) + sum_gate = qc_sum.to_gate() + + # handle all cases for the first qubits, depending on whether cin is available + i = 0 + if kind == "half": + i += 1 + circuit.ccx(qr_a[0], qr_b[0], carries[0]) + elif kind == "fixed": + i += 1 + if num_state_qubits == 1: + circuit.cx(qr_a[0], qr_b[0]) + else: + circuit.ccx(qr_a[0], qr_b[0], carries[0]) + + for inp, out in zip(carries[:-1], carries[1:]): + circuit.append(carry_gate, [inp, qr_a[i], qr_b[i], out]) + i += 1 + + if kind in ["full", "half"]: # final CX (cancels for the 'fixed' case) + circuit.cx(qr_a[-1], qr_b[-1]) + + if len(carries) > 1: + circuit.append(sum_gate, [carries[-2], qr_a[-1], qr_b[-1]]) + + i -= 2 + for j, (inp, out) in enumerate(zip(reversed(carries[:-1]), reversed(carries[1:]))): + if j == 0: + if kind == "fixed": + i += 1 + else: + continue + circuit.append(carry_gate_dg, [inp, qr_a[i], qr_b[i], out]) + circuit.append(sum_gate, [inp, qr_a[i], qr_b[i]]) + i -= 1 + + if kind in ["half", "fixed"] and num_state_qubits > 1: + circuit.ccx(qr_a[0], qr_b[0], carries[0]) + circuit.cx(qr_a[0], qr_b[0]) + + return circuit diff --git a/qiskit/synthesis/arithmetic/multipliers/__init__.py b/qiskit/synthesis/arithmetic/multipliers/__init__.py new file mode 100644 index 000000000000..5adcfa65091b --- /dev/null +++ b/qiskit/synthesis/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 multiplier_cumulative_h18 +from .rg_qft_multiplier import multiplier_qft_r17 diff --git a/qiskit/synthesis/arithmetic/multipliers/hrs_cumulative_multiplier.py b/qiskit/synthesis/arithmetic/multipliers/hrs_cumulative_multiplier.py new file mode 100644 index 000000000000..676d57ce4342 --- /dev/null +++ b/qiskit/synthesis/arithmetic/multipliers/hrs_cumulative_multiplier.py @@ -0,0 +1,102 @@ +# 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 __future__ import annotations + +from qiskit.circuit.quantumcircuit import QuantumCircuit +from qiskit.circuit.quantumregister import QuantumRegister + + +def multiplier_cumulative_h18( + num_state_qubits: int, num_result_qubits: int | None = None +) -> QuantumCircuit: + r"""A multiplication circuit to store product of two input registers out-of-place. + + The circuit uses the approach from Ref. [1]. As an example, a multiplier circuit that + performs a non-modular multiplication on two 3-qubit sized registers is: + + .. plot:: + :include-source: + + from qiskit.synthesis.arithmetic import multiplier_cumulative_h18 + + num_state_qubits = 3 + circuit = multiplier_cumulative_h18(num_state_qubits) + circuit.draw("mpl") + + 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. + + 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. + + Raises: + ValueError: If ``num_result_qubits`` is given and not valid, meaning not + in ``[num_state_qubits, 2 * num_state_qubits]``. + + **References:** + + [1] Häner et al., Optimizing Quantum Circuits for Arithmetic, 2018. + `arXiv:1805.12445 `_ + + """ + if num_result_qubits is None: + num_result_qubits = 2 * num_state_qubits + elif num_result_qubits < num_state_qubits or num_result_qubits > 2 * num_state_qubits: + raise ValueError( + f"num_result_qubits ({num_result_qubits}) must be in between num_state_qubits " + f"({num_state_qubits}) and 2 * num_state_qubits ({2 * num_state_qubits})" + ) + + # define the registers + qr_a = QuantumRegister(num_state_qubits, name="a") + qr_b = QuantumRegister(num_state_qubits, name="b") + qr_out = QuantumRegister(num_result_qubits, name="out") + + circuit = QuantumCircuit(qr_a, qr_b, qr_out) + + # prepare adder as controlled gate + # pylint: disable=cyclic-import + from qiskit.circuit.library.arithmetic import HalfAdderGate, ModularAdderGate + + adder = HalfAdderGate(num_state_qubits) + controlled_adder = adder.control(annotated=True) + + # build multiplication circuit + for i in range(num_state_qubits): + excess_qubits = max(0, num_state_qubits + i + 1 - num_result_qubits) + if excess_qubits == 0: + num_adder_qubits = num_state_qubits + this_controlled = controlled_adder + else: + num_adder_qubits = num_state_qubits - excess_qubits + 1 + modular = ModularAdderGate(num_adder_qubits) + this_controlled = modular.control(annotated=True) + + qr_list = ( + [qr_a[i]] + + qr_b[:num_adder_qubits] + + qr_out[i : num_state_qubits + i + 1 - excess_qubits] + ) + circuit.append(this_controlled, qr_list) + + return circuit diff --git a/qiskit/synthesis/arithmetic/multipliers/rg_qft_multiplier.py b/qiskit/synthesis/arithmetic/multipliers/rg_qft_multiplier.py new file mode 100644 index 000000000000..550fc44694d0 --- /dev/null +++ b/qiskit/synthesis/arithmetic/multipliers/rg_qft_multiplier.py @@ -0,0 +1,99 @@ +# 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 __future__ import annotations + +import numpy as np + +from qiskit.circuit.quantumcircuit import QuantumCircuit +from qiskit.circuit.quantumregister import QuantumRegister +from qiskit.circuit.library.standard_gates import PhaseGate +from qiskit.circuit.library.basis_change import QFTGate + + +def multiplier_qft_r17( + num_state_qubits: int, num_result_qubits: int | None = None +) -> QuantumCircuit: + 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. + For example, on 3 state qubits, a full multiplier is given by: + + .. plot:: + :include-source: + + from qiskit.synthesis.arithmetic import multiplier_qft_r17 + + num_state_qubits = 3 + circuit = multiplier_qft_r17(num_state_qubits) + circuit.draw("mpl") + + 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. + + Raises: + ValueError: If ``num_result_qubits`` is given and not valid, meaning not + in ``[num_state_qubits, 2 * num_state_qubits]``. + + **References:** + + [1] Ruiz-Perez et al., Quantum arithmetic with the Quantum Fourier Transform, 2017. + `arXiv:1411.5949 `_ + + """ + # define the registers + if num_result_qubits is None: + num_result_qubits = 2 * num_state_qubits + elif num_result_qubits < num_state_qubits or num_result_qubits > 2 * num_state_qubits: + raise ValueError( + f"num_result_qubits ({num_result_qubits}) must be in between num_state_qubits " + f"({num_state_qubits}) and 2 * num_state_qubits ({2 * num_state_qubits})" + ) + + qr_a = QuantumRegister(num_state_qubits, name="a") + qr_b = QuantumRegister(num_state_qubits, name="b") + qr_out = QuantumRegister(num_result_qubits, name="out") + + # build multiplication circuit + circuit = QuantumCircuit(qr_a, qr_b, qr_out) + qft = QFTGate(num_result_qubits) + + circuit.append(qft, 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, num_result_qubits + 1): + lam = (2 * np.pi) / (2 ** (i + j + k - 2 * num_state_qubits)) + + # note: if we can synthesize the QFT without swaps, we can implement this circuit + # more efficiently and just apply phase gate on qr_out[(k - 1)] instead + circuit.append( + PhaseGate(lam).control(2), + [qr_a[num_state_qubits - j], qr_b[num_state_qubits - i], qr_out[~(k - 1)]], + ) + + circuit.append(qft.inverse(), qr_out[:]) + + return circuit diff --git a/qiskit/transpiler/passes/synthesis/hls_plugins.py b/qiskit/transpiler/passes/synthesis/hls_plugins.py index 3e26809a55f7..fe68e58ada80 100644 --- a/qiskit/transpiler/passes/synthesis/hls_plugins.py +++ b/qiskit/transpiler/passes/synthesis/hls_plugins.py @@ -262,6 +262,117 @@ MCMTSynthesisNoAux MCMTSynthesisDefault +Modular Adder Synthesis +''''''''''''''''''''''' + +.. list-table:: Plugins for :class:`.ModularAdderGate` (key = ``"ModularAdder"``) + :header-rows: 1 + + * - Plugin name + - Plugin class + - Number of clean ancillas + - Description + * - ``"ripple_cdkm"`` + - :class:`.ModularAdderSynthesisC04` + - 1 + - a ripple-carry adder + * - ``"ripple_vbe"`` + - :class:`.ModularAdderSynthesisV95` + - :math:`n-1`, for :math:`n`-bit numbers + - a ripple-carry adder + * - ``"qft"`` + - :class:`.ModularAdderSynthesisD00` + - 0 + - a QFT-based adder + +.. autosummary:: + :toctree: ../stubs/ + + ModularAdderSynthesisC04 + ModularAdderSynthesisD00 + ModularAdderSynthesisV95 + +Half Adder Synthesis +'''''''''''''''''''' + +.. list-table:: Plugins for :class:`.HalfAdderGate` (key = ``"HalfAdder"``) + :header-rows: 1 + + * - Plugin name + - Plugin class + - Number of clean ancillas + - Description + * - ``"ripple_cdkm"`` + - :class:`.HalfAdderSynthesisC04` + - 1 + - a ripple-carry adder + * - ``"ripple_vbe"`` + - :class:`.HalfAdderSynthesisV95` + - :math:`n-1`, for :math:`n`-bit numbers + - a ripple-carry adder + * - ``"qft"`` + - :class:`.HalfAdderSynthesisD00` + - 0 + - a QFT-based adder + +.. autosummary:: + :toctree: ../stubs/ + + HalfAdderSynthesisC04 + HalfAdderSynthesisD00 + HalfAdderSynthesisV95 + +Full Adder Synthesis +'''''''''''''''''''' + +.. list-table:: Plugins for :class:`.FullAdderGate` (key = ``"FullAdder"``) + :header-rows: 1 + + * - Plugin name + - Plugin class + - Number of clean ancillas + - Description + * - ``"ripple_cdkm"`` + - :class:`.FullAdderSynthesisC04` + - 0 + - a ripple-carry adder + * - ``"ripple_vbe"`` + - :class:`.FullAdderSynthesisV95` + - :math:`n-1`, for :math:`n`-bit numbers + - a ripple-carry adder + +.. autosummary:: + :toctree: ../stubs/ + + FullAdderSynthesisC04 + FullAdderSynthesisV95 + + +Multiplier Synthesis +'''''''''''''''''''' + +.. list-table:: Plugins for :class:`.MultiplierGate` (key = ``"Multiplier"``) + :header-rows: 1 + + * - Plugin name + - Plugin class + - Number of clean ancillas + - Description + * - ``"cumulative"`` + - :class:`.MultiplierSynthesisH18` + - depending on the :class:`.AdderGate` used + - a cumulative adder based on controlled adders + * - ``"qft"`` + - :class:`.MultiplierSynthesisR17` + - 0 + - a QFT-based multiplier + +.. autosummary:: + :toctree: ../stubs/ + + MultiplierSynthesisH18 + MultiplierSynthesisR17 + """ from __future__ import annotations @@ -270,7 +381,17 @@ import rustworkx as rx from qiskit.circuit.quantumcircuit import QuantumCircuit -from qiskit.circuit.library import LinearFunction, QFTGate, MCXGate, C3XGate, C4XGate +from qiskit.circuit.library import ( + LinearFunction, + QFTGate, + MCXGate, + C3XGate, + C4XGate, + ModularAdderGate, + HalfAdderGate, + FullAdderGate, + MultiplierGate, +) from qiskit.transpiler.exceptions import TranspilerError from qiskit.transpiler.coupling import CouplingMap @@ -305,6 +426,13 @@ synth_mcx_noaux_v24, synth_mcmt_vchain, ) +from qiskit.synthesis.arithmetic import ( + adder_ripple_c04, + adder_qft_d00, + adder_ripple_v95, + multiplier_qft_r17, + multiplier_cumulative_h18, +) from qiskit.transpiler.passes.routing.algorithms import ApproximateTokenSwapper from .plugin import HighLevelSynthesisPlugin @@ -1046,6 +1174,274 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, ** ) +class ModularAdderSynthesisDefault(HighLevelSynthesisPlugin): + """The default modular adder (no carry in, no carry out qubit) synthesis. + + This plugin name is:``ModularAdder.default`` which can be used as the key on + an :class:`~.HLSConfig` object to use this method with :class:`~.HighLevelSynthesis`. + + If at least one clean auxiliary qubit is available, the :class:`ModularAdderSynthesisC04` + is used, otherwise :class:`ModularAdderSynthesisD00`. + + The plugin supports the following plugin-specific options: + + * ``num_clean_ancillas``: The number of clean auxiliary qubits available. + + """ + + def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): + if not isinstance(high_level_object, ModularAdderGate): + return None + + if options.get("num_clean_ancillas", 0) >= 1: + return adder_ripple_c04(high_level_object.num_state_qubits, kind="fixed") + + return adder_qft_d00(high_level_object.num_state_qubits, kind="fixed") + + +class ModularAdderSynthesisC04(HighLevelSynthesisPlugin): + r"""A ripple-carry adder, modulo :math:`2^n`. + + This plugin name is:``ModularAdder.ripple_c04`` which can be used as the key on + an :class:`~.HLSConfig` object to use this method with :class:`~.HighLevelSynthesis`. + + This plugin requires at least one clean auxiliary qubit. + + The plugin supports the following plugin-specific options: + + * ``num_clean_ancillas``: The number of clean auxiliary qubits available. + + """ + + def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): + if not isinstance(high_level_object, ModularAdderGate): + return None + + # unless we implement the full adder, this implementation needs an ancilla qubit + if options.get("num_clean_ancillas", 0) < 1: + return None + + return adder_ripple_c04(high_level_object.num_state_qubits, kind="fixed") + + +class ModularAdderSynthesisV95(HighLevelSynthesisPlugin): + r"""A ripple-carry adder, modulo :math:`2^n`. + + This plugin name is:``ModularAdder.ripple_v95`` which can be used as the key on + an :class:`~.HLSConfig` object to use this method with :class:`~.HighLevelSynthesis`. + + For an adder on 2 registers with :math:`n` qubits each, this plugin requires at + least :math:`n-1` clean auxiliary qubit. + + The plugin supports the following plugin-specific options: + + * ``num_clean_ancillas``: The number of clean auxiliary qubits available. + + """ + + def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): + if not isinstance(high_level_object, ModularAdderGate): + return None + + num_state_qubits = high_level_object.num_state_qubits + + # for more than 1 state qubit, we need an ancilla + if num_state_qubits > 1 > options.get("num_clean_ancillas", 1): + return None + + return adder_ripple_v95(num_state_qubits, kind="fixed") + + +class ModularAdderSynthesisD00(HighLevelSynthesisPlugin): + r"""A QFT-based adder, modulo :math:`2^n`. + + This plugin name is:``ModularAdder.qft_d00`` which can be used as the key on + an :class:`~.HLSConfig` object to use this method with :class:`~.HighLevelSynthesis`. + """ + + def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): + if not isinstance(high_level_object, ModularAdderGate): + return None + + return adder_qft_d00(high_level_object.num_state_qubits, kind="fixed") + + +class HalfAdderSynthesisDefault(HighLevelSynthesisPlugin): + r"""The default half-adder (no carry in, but a carry out qubit) synthesis. + + If we have an auxiliary qubit available, the Cuccaro ripple-carry adder uses + :math:`O(n)` CX gates and 1 auxiliary qubit, whereas the Vedral ripple-carry uses more CX + and :math:`n-1` auxiliary qubits. The QFT-based adder uses no auxiliary qubits, but + :math:`O(n^2)`, hence it is only used if no auxiliary qubits are available. + + This plugin name is:``HalfAdder.default`` which can be used as the key on + an :class:`~.HLSConfig` object to use this method with :class:`~.HighLevelSynthesis`. + + If at least one clean auxiliary qubit is available, the :class:`HalfAdderSynthesisC04` + is used, otherwise :class:`HalfAdderSynthesisD00`. + + The plugin supports the following plugin-specific options: + + * ``num_clean_ancillas``: The number of clean auxiliary qubits available. + + """ + + def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): + if not isinstance(high_level_object, HalfAdderGate): + return None + + if options.get("num_clean_ancillas", 0) >= 1: + return adder_ripple_c04(high_level_object.num_state_qubits, kind="half") + + return adder_qft_d00(high_level_object.num_state_qubits, kind="half") + + +class HalfAdderSynthesisC04(HighLevelSynthesisPlugin): + """A ripple-carry adder with a carry-out bit. + + This plugin name is:``HalfAdder.ripple_c04`` which can be used as the key on + an :class:`~.HLSConfig` object to use this method with :class:`~.HighLevelSynthesis`. + + This plugin requires at least one clean auxiliary qubit. + + The plugin supports the following plugin-specific options: + + * ``num_clean_ancillas``: The number of clean auxiliary qubits available. + + """ + + def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): + if not isinstance(high_level_object, HalfAdderGate): + return None + + # unless we implement the full adder, this implementation needs an ancilla qubit + if options.get("num_clean_ancillas", 0) < 1: + return None + + return adder_ripple_c04(high_level_object.num_state_qubits, kind="half") + + +class HalfAdderSynthesisV95(HighLevelSynthesisPlugin): + """A ripple-carry adder with a carry-out bit. + + This plugin name is:``HalfAdder.ripple_v95`` which can be used as the key on + an :class:`~.HLSConfig` object to use this method with :class:`~.HighLevelSynthesis`. + + For an adder on 2 registers with :math:`n` qubits each, this plugin requires at + least :math:`n-1` clean auxiliary qubit. + + The plugin supports the following plugin-specific options: + + * ``num_clean_ancillas``: The number of clean auxiliary qubits available. + """ + + def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): + if not isinstance(high_level_object, HalfAdderGate): + return None + + num_state_qubits = high_level_object.num_state_qubits + + # for more than 1 state qubit, we need an ancilla + if num_state_qubits > 1 > options.get("num_clean_ancillas", 1): + return None + + return adder_ripple_v95(num_state_qubits, kind="half") + + +class HalfAdderSynthesisD00(HighLevelSynthesisPlugin): + """A QFT-based adder with a carry-in and a carry-out bit. + + This plugin name is:``HalfAdder.qft_d00`` which can be used as the key on + an :class:`~.HLSConfig` object to use this method with :class:`~.HighLevelSynthesis`. + """ + + def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): + if not isinstance(high_level_object, HalfAdderGate): + return None + + return adder_qft_d00(high_level_object.num_state_qubits, kind="half") + + +class FullAdderSynthesisC04(HighLevelSynthesisPlugin): + """A ripple-carry adder with a carry-in and a carry-out bit. + + This plugin name is:``FullAdder.ripple_c04`` which can be used as the key on + an :class:`~.HLSConfig` object to use this method with :class:`~.HighLevelSynthesis`. + + This plugin requires at least one clean auxiliary qubit. + + The plugin supports the following plugin-specific options: + + * ``num_clean_ancillas``: The number of clean auxiliary qubits available. + + """ + + def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): + if not isinstance(high_level_object, FullAdderGate): + return None + + return adder_ripple_c04(high_level_object.num_state_qubits, kind="full") + + +class FullAdderSynthesisV95(HighLevelSynthesisPlugin): + """A ripple-carry adder with a carry-in and a carry-out bit. + + This plugin name is:``FullAdder.ripple_v95`` which can be used as the key on + an :class:`~.HLSConfig` object to use this method with :class:`~.HighLevelSynthesis`. + + For an adder on 2 registers with :math:`n` qubits each, this plugin requires at + least :math:`n-1` clean auxiliary qubit. + + The plugin supports the following plugin-specific options: + + * ``num_clean_ancillas``: The number of clean auxiliary qubits available. + """ + + def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): + if not isinstance(high_level_object, FullAdderGate): + return None + + num_state_qubits = high_level_object.num_state_qubits + + # for more than 1 state qubit, we need an ancilla + if num_state_qubits > 1 > options.get("num_clean_ancillas", 1): + return None + + return adder_ripple_v95(num_state_qubits, kind="full") + + +class MultiplierSynthesisH18(HighLevelSynthesisPlugin): + """A cumulative multiplier based on controlled adders. + + This plugin name is:``Multiplier.cumulative_h18`` which can be used as the key on + an :class:`~.HLSConfig` object to use this method with :class:`~.HighLevelSynthesis`. + """ + + def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): + if not isinstance(high_level_object, MultiplierGate): + return None + + return multiplier_cumulative_h18( + high_level_object.num_state_qubits, high_level_object.num_result_qubits + ) + + +class MultiplierSynthesisR17(HighLevelSynthesisPlugin): + """A QFT-based multiplier. + + This plugin name is:``Multiplier.qft_r17`` which can be used as the key on + an :class:`~.HLSConfig` object to use this method with :class:`~.HighLevelSynthesis`. + """ + + def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): + if not isinstance(high_level_object, MultiplierGate): + return None + + return multiplier_qft_r17( + high_level_object.num_state_qubits, high_level_object.num_result_qubits + ) + + class PauliEvolutionSynthesisDefault(HighLevelSynthesisPlugin): """The default implementation calling the attached synthesis algorithm.""" diff --git a/releasenotes/notes/binary-arithmetic-gates-6cd2b1c8112febe0.yaml b/releasenotes/notes/binary-arithmetic-gates-6cd2b1c8112febe0.yaml new file mode 100644 index 000000000000..a082a98389ad --- /dev/null +++ b/releasenotes/notes/binary-arithmetic-gates-6cd2b1c8112febe0.yaml @@ -0,0 +1,25 @@ +--- +features_circuits: + - | + Added binary arithmetic gates for inplace addition two :math:`n`-qubit registers, that is + :math:`|a\rangle |b\rangle \mapsto |a\rangle |a+b\rangle`. + The :class:`.ModularAdderGate` implements addition modulo :math:`2^n`, the + :class:`.AdderGate` implements standard addition including a carry-out, and the + :class:`.FullAdderGate` includes a carry-in qubit. See the respective documentations for + details and examples. + + In contrast to the existing library circuits, such as :class:`.CDKMRippleCarryAdder`, + handling the abstract gate allows the compiler (or user) to select the optimal gate + synthesis, depending on the circuit's context. + - | + Added the :class:`.MultiplierGate` for multiplication of two :math:`n`-qubit registers, that is + :math:`|a\rangle |b\rangle \mapsto |a\rangle |b\rangle |a \cdot b\rangle`. + See the class documentations for details and examples. +features_synthesis: + - | + Added :func:`.adder_qft_d00`, :func:`.adder_ripple_c04`, and :func:`.adder_ripple_v95` + to synthesize the adder gates, :class:`.ModularAdderGate`, :class:`.AdderGate`, and + :class:`.FullAdderGate`. + - | + Added :func:`.multiplier_cumulative_h18` and :func:`.multiplier_qft_r17` + to synthesize the :class:`.MultiplierGate`. \ No newline at end of file diff --git a/test/python/circuit/library/test_adders.py b/test/python/circuit/library/test_adders.py index 0866cb766710..eacf8057775a 100644 --- a/test/python/circuit/library/test_adders.py +++ b/test/python/circuit/library/test_adders.py @@ -18,9 +18,30 @@ from qiskit.circuit import QuantumCircuit from qiskit.quantum_info import Statevector -from qiskit.circuit.library import CDKMRippleCarryAdder, DraperQFTAdder, VBERippleCarryAdder +from qiskit.circuit.library import ( + CDKMRippleCarryAdder, + DraperQFTAdder, + VBERippleCarryAdder, + ModularAdderGate, + HalfAdderGate, + FullAdderGate, +) +from qiskit.synthesis.arithmetic import adder_ripple_c04, adder_ripple_v95, adder_qft_d00 +from qiskit.transpiler.passes import HLSConfig, HighLevelSynthesis from test import QiskitTestCase # pylint: disable=wrong-import-order +ADDERS = { + "vbe": adder_ripple_v95, + "cdkm": adder_ripple_c04, + "draper": adder_qft_d00, +} + +ADDER_CIRCUITS = { + "vbe": VBERippleCarryAdder, + "cdkm": CDKMRippleCarryAdder, + "draper": DraperQFTAdder, +} + @ddt class TestAdder(QiskitTestCase): @@ -42,7 +63,7 @@ def assertAdditionIsCorrect( inplace: If True, compare against an inplace addition where the result is written into the second register plus carry qubit. If False, assume that the result is written into a third register of appropriate size. - kind: TODO + kind: The kind of adder; "fixed", "half", or "full". """ circuit = QuantumCircuit(*adder.qregs) @@ -100,39 +121,89 @@ def assertAdditionIsCorrect( np.testing.assert_array_almost_equal(expectations, probabilities) @data( - (3, CDKMRippleCarryAdder, True), - (5, CDKMRippleCarryAdder, True), - (3, CDKMRippleCarryAdder, True, "fixed"), - (5, CDKMRippleCarryAdder, True, "fixed"), - (1, CDKMRippleCarryAdder, True, "full"), - (3, CDKMRippleCarryAdder, True, "full"), - (5, CDKMRippleCarryAdder, True, "full"), - (3, DraperQFTAdder, True), - (5, DraperQFTAdder, True), - (3, DraperQFTAdder, True, "fixed"), - (5, DraperQFTAdder, True, "fixed"), - (1, VBERippleCarryAdder, True, "full"), - (3, VBERippleCarryAdder, True, "full"), - (5, VBERippleCarryAdder, True, "full"), - (1, VBERippleCarryAdder, True), - (2, VBERippleCarryAdder, True), - (5, VBERippleCarryAdder, True), - (1, VBERippleCarryAdder, True, "fixed"), - (2, VBERippleCarryAdder, True, "fixed"), - (4, VBERippleCarryAdder, True, "fixed"), + (3, "cdkm", "half"), + (5, "cdkm", "half"), + (3, "cdkm", "fixed"), + (5, "cdkm", "fixed"), + (1, "cdkm", "full"), + (3, "cdkm", "full"), + (5, "cdkm", "full"), + (3, "draper", "half"), + (5, "draper", "half"), + (3, "draper", "fixed"), + (5, "draper", "fixed"), + (1, "vbe", "full"), + (3, "vbe", "full"), + (5, "vbe", "full"), + (1, "vbe", "half"), + (2, "vbe", "half"), + (5, "vbe", "half"), + (1, "vbe", "fixed"), + (2, "vbe", "fixed"), + (4, "vbe", "fixed"), ) @unpack - def test_summation(self, num_state_qubits, adder, inplace, kind="half"): + def test_summation(self, num_state_qubits, adder, kind): """Test summation for all implemented adders.""" - adder = adder(num_state_qubits, kind=kind) - self.assertAdditionIsCorrect(num_state_qubits, adder, inplace, kind) + for use_function in [True, False]: + with self.subTest(use_function=use_function): + if use_function: + circuit = ADDERS[adder](num_state_qubits, kind) + else: + circuit = ADDER_CIRCUITS[adder](num_state_qubits, kind) + + self.assertAdditionIsCorrect(num_state_qubits, circuit, True, kind) - @data(CDKMRippleCarryAdder, DraperQFTAdder, VBERippleCarryAdder) + @data( + CDKMRippleCarryAdder, + DraperQFTAdder, + VBERippleCarryAdder, + adder_ripple_c04, + adder_ripple_v95, + adder_qft_d00, + ) def test_raises_on_wrong_num_bits(self, adder): """Test an error is raised for a bad number of qubits.""" with self.assertRaises(ValueError): _ = adder(-1) + def test_plugins(self): + """Test setting the HLS plugins for the modular adder.""" + + # all gates with the plugins we check + modes = { + "ModularAdder": (ModularAdderGate, ["ripple_c04", "ripple_v95", "qft_d00"]), + "HalfAdder": (HalfAdderGate, ["ripple_c04", "ripple_v95", "qft_d00"]), + "FullAdder": (FullAdderGate, ["ripple_c04", "ripple_v95"]), + } + + # an operation we expect to be in the circuit with given plugin name + expected_ops = { + "ripple_c04": "MAJ", + "ripple_v95": "Carry", + "qft_d00": "cp", + } + + num_state_qubits = 3 + max_auxiliaries = num_state_qubits - 1 # V95 needs these + max_num_qubits = 2 * num_state_qubits + max_auxiliaries + 2 + + for name, (adder_cls, plugins) in modes.items(): + for plugin in plugins: + with self.subTest(name=name, plugin=plugin): + adder = adder_cls(num_state_qubits) + + circuit = QuantumCircuit(max_num_qubits) + circuit.append(adder, range(adder.num_qubits)) + + hls_config = HLSConfig(**{name: [plugin]}) + hls = HighLevelSynthesis(hls_config=hls_config) + + synth = hls(circuit) + ops = set(synth.count_ops().keys()) + + self.assertTrue(expected_ops[plugin] in ops) + if __name__ == "__main__": unittest.main() diff --git a/test/python/circuit/library/test_multipliers.py b/test/python/circuit/library/test_multipliers.py index fd083e287153..f5665007079b 100644 --- a/test/python/circuit/library/test_multipliers.py +++ b/test/python/circuit/library/test_multipliers.py @@ -13,9 +13,11 @@ """Test multiplier circuits.""" import unittest +import re import numpy as np from ddt import ddt, data, unpack +from qiskit import transpile from qiskit.circuit import QuantumCircuit from qiskit.quantum_info import Statevector from qiskit.circuit.library import ( @@ -24,7 +26,10 @@ CDKMRippleCarryAdder, DraperQFTAdder, VBERippleCarryAdder, + MultiplierGate, ) +from qiskit.transpiler.passes import HighLevelSynthesis, HLSConfig +from qiskit.synthesis.arithmetic import multiplier_qft_r17, multiplier_cumulative_h18 from test import QiskitTestCase # pylint: disable=wrong-import-order @@ -53,7 +58,8 @@ def assertMultiplicationIsCorrect( # 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) + tqc = transpile(circuit, basis_gates=["h", "p", "cp", "rz", "cx", "ccx", "swap"]) + statevector = Statevector(tqc) probabilities = statevector.probabilities() pad = "0" * circuit.num_ancillas # state of the ancillas @@ -79,10 +85,18 @@ def assertMultiplicationIsCorrect( (3, RGQFTMultiplier, 5), (3, RGQFTMultiplier, 4), (3, RGQFTMultiplier, 3), + (3, multiplier_qft_r17), + (3, multiplier_qft_r17, 5), + (3, multiplier_qft_r17, 4), + (3, multiplier_qft_r17, 3), (3, HRSCumulativeMultiplier), (3, HRSCumulativeMultiplier, 5), (3, HRSCumulativeMultiplier, 4), (3, HRSCumulativeMultiplier, 3), + (3, multiplier_cumulative_h18), + (3, multiplier_cumulative_h18, 5), + (3, multiplier_cumulative_h18, 4), + (3, multiplier_cumulative_h18, 3), (3, HRSCumulativeMultiplier, None, CDKMRippleCarryAdder), (3, HRSCumulativeMultiplier, None, DraperQFTAdder), (3, HRSCumulativeMultiplier, None, VBERippleCarryAdder), @@ -97,6 +111,7 @@ def test_multiplication(self, num_state_qubits, multiplier, num_result_qubits=No 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( @@ -124,6 +139,29 @@ def test_modular_cumulative_multiplier_custom_adder(self): with self.assertRaises(NotImplementedError): _ = HRSCumulativeMultiplier(3, 3, adder=VBERippleCarryAdder(3)) + def test_plugins(self): + """Test setting the HLS plugins for the modular adder.""" + + # all gates with the plugins we check, including an expected operation + plugins = [("cumulative_h18", "ccircuit-.*"), ("qft_r17", "qft")] + + num_state_qubits = 2 + + for plugin, expected_op in plugins: + with self.subTest(plugin=plugin): + multiplier = MultiplierGate(num_state_qubits) + + circuit = QuantumCircuit(multiplier.num_qubits) + circuit.append(multiplier, range(multiplier.num_qubits)) + + hls_config = HLSConfig(Multiplier=[plugin]) + hls = HighLevelSynthesis(hls_config=hls_config) + + synth = hls(circuit) + ops = set(synth.count_ops().keys()) + + self.assertTrue(any(re.match(expected_op, op) for op in ops)) + if __name__ == "__main__": unittest.main() diff --git a/test/python/circuit/test_gate_definitions.py b/test/python/circuit/test_gate_definitions.py index 976c66d266ad..3497ef9fe46a 100644 --- a/test/python/circuit/test_gate_definitions.py +++ b/test/python/circuit/test_gate_definitions.py @@ -311,6 +311,10 @@ class TestGateEquivalenceEqual(QiskitTestCase): "_SingletonGateOverrides", "_SingletonControlledGateOverrides", "QFTGate", + "ModularAdderGate", + "HalfAdderGate", + "FullAdderGate", + "MultiplierGate", "GraphStateGate", "AndGate", "OrGate",