From 7f98534c3e7d9c499e2a510ecd6407840621cabd Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Mon, 21 Oct 2024 16:58:20 +0200 Subject: [PATCH 01/11] move to plugins & gates --- .../library/arithmetic/adders/adder.py | 134 +++++++++++++- .../adders/cdkm_ripple_carry_adder.py | 57 +----- .../arithmetic/adders/draper_qft_adder.py | 47 +---- .../adders/vbe_ripple_carry_adder.py | 94 +--------- qiskit/synthesis/arithmetic/__init__.py | 17 ++ .../arithmetic/cdkm_ripple_carry_adder.py | 150 +++++++++++++++ .../synthesis/arithmetic/draper_qft_adder.py | 104 +++++++++++ .../arithmetic/vbe_ripple_carry_adder.py | 155 ++++++++++++++++ .../passes/synthesis/hls_plugins.py | 172 ++++++++++++++++++ 9 files changed, 738 insertions(+), 192 deletions(-) create mode 100644 qiskit/synthesis/arithmetic/__init__.py create mode 100644 qiskit/synthesis/arithmetic/cdkm_ripple_carry_adder.py create mode 100644 qiskit/synthesis/arithmetic/draper_qft_adder.py create mode 100644 qiskit/synthesis/arithmetic/vbe_ripple_carry_adder.py diff --git a/qiskit/circuit/library/arithmetic/adders/adder.py b/qiskit/circuit/library/arithmetic/adders/adder.py index 2e1a814d92cc..a0cb70379e4a 100644 --- a/qiskit/circuit/library/arithmetic/adders/adder.py +++ b/qiskit/circuit/library/arithmetic/adders/adder.py @@ -12,7 +12,9 @@ """Compute the sum of two equally sized qubit registers.""" -from qiskit.circuit import QuantumCircuit +from __future__ import annotations + +from qiskit.circuit import QuantumCircuit, Gate class Adder(QuantumCircuit): @@ -56,3 +58,133 @@ def num_state_qubits(self) -> int: The number of state qubits. """ return self._num_state_qubits + + +class AdderGate(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. + """ + super().__init__("Adder", 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 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\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. + """ + 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. + """ + super().__init__("FullAdder", 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 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 a489685bc426..52f7c79a71e3 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_cd04 from .adder import Adder @@ -102,58 +101,6 @@ 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) - - 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]]) - + circuit = adder_cd04(num_state_qubits, kind) 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 80671866bab1..f64a34efc924 100644 --- a/qiskit/circuit/library/arithmetic/adders/draper_qft_adder.py +++ b/qiskit/circuit/library/arithmetic/adders/draper_qft_adder.py @@ -12,12 +12,7 @@ """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 QFT - +from qiskit.synthesis.arithmetic import adder_d00 from .adder import Adder @@ -73,44 +68,6 @@ def __init__( Raises: ValueError: If ``num_state_qubits`` is lower than 1. """ - 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.") - super().__init__(num_state_qubits, name=name) - - 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 - self.add_register(*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_qubits_qft = num_state_qubits if kind == "fixed" else num_state_qubits + 1 - - circuit = QuantumCircuit(*self.qregs, name=name) - - # build QFT adder circuit - circuit.append(QFT(num_qubits_qft, do_swaps=False).to_gate(), qr_sum[:]) - - for j in range(num_state_qubits): - for k in range(num_state_qubits - j): - lam = np.pi / (2**k) - circuit.cp(lam, qr_a[j], qr_b[j + k]) - - if kind == "half": - for j in range(num_state_qubits): - lam = np.pi / (2 ** (j + 1)) - circuit.cp(lam, qr_a[num_state_qubits - j - 1], qr_z[0]) - - circuit.append(QFT(num_qubits_qft, do_swaps=False).inverse().to_gate(), qr_sum[:]) - + circuit = adder_d00(num_state_qubits, kind) self.append(circuit.to_gate(), self.qubits) 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 9c1eeb5b1b33..17547824e133 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_vb95 from .adder import Adder @@ -74,92 +72,6 @@ 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) - - # 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]) - + circuit = adder_vb95(num_state_qubits, kind) self.append(circuit.to_gate(), self.qubits) diff --git a/qiskit/synthesis/arithmetic/__init__.py b/qiskit/synthesis/arithmetic/__init__.py new file mode 100644 index 000000000000..dad2f929009e --- /dev/null +++ b/qiskit/synthesis/arithmetic/__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_cd04 +from .vbe_ripple_carry_adder import adder_vb95 +from .draper_qft_adder import adder_d00 diff --git a/qiskit/synthesis/arithmetic/cdkm_ripple_carry_adder.py b/qiskit/synthesis/arithmetic/cdkm_ripple_carry_adder.py new file mode 100644 index 000000000000..3a0e80318771 --- /dev/null +++ b/qiskit/synthesis/arithmetic/cdkm_ripple_carry_adder.py @@ -0,0 +1,150 @@ +# 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_cd04(num_state_qubits: int, kind: str) -> QuantumCircuit: + r"""A ripple-carry circuit to perform in-place addition on two qubit registers. + + 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/draper_qft_adder.py b/qiskit/synthesis/arithmetic/draper_qft_adder.py new file mode 100644 index 000000000000..01ef8beb7f44 --- /dev/null +++ b/qiskit/synthesis/arithmetic/draper_qft_adder.py @@ -0,0 +1,104 @@ +# 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_d00(num_state_qubits: int, kind: str) -> 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"``). + + 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(π) │ │ │ │ ┌───────┐ + b_0: ┤0 ├─■──────┼────────┼──────┼────────┼───────┤0 ├ + │ │ │P(π/2) │P(π) │ │ │ │ + b_1: ┤1 qft ├────────■────────■──────┼────────┼───────┤1 iqft ├ + │ │ │P(π/2) │P(π/4) │ │ + cout_0: ┤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_qubits_qft = num_state_qubits if kind == "fixed" else num_state_qubits + 1 + + # build QFT adder circuit + qft = QFTGate(num_qubits_qft) + circuit.append(qft, qr_sum[:]) + + for j in range(num_state_qubits): + for k in range(num_state_qubits - j): + lam = np.pi / (2**k) + circuit.cp(lam, qr_a[j], qr_b[j + k]) + + if kind == "half": + for j in range(num_state_qubits): + lam = np.pi / (2 ** (j + 1)) + circuit.cp(lam, qr_a[num_state_qubits - j - 1], qr_z[0]) + + circuit.append(qft.inverse(), qr_sum[:]) + + return circuit diff --git a/qiskit/synthesis/arithmetic/vbe_ripple_carry_adder.py b/qiskit/synthesis/arithmetic/vbe_ripple_carry_adder.py new file mode 100644 index 000000000000..9638858e608b --- /dev/null +++ b/qiskit/synthesis/arithmetic/vbe_ripple_carry_adder.py @@ -0,0 +1,155 @@ +# 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_vb95(num_state_qubits: int, kind: str) -> QuantumCircuit: + r"""The VBE ripple carry adder [1]. + + 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 `_ + + """ + 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/transpiler/passes/synthesis/hls_plugins.py b/qiskit/transpiler/passes/synthesis/hls_plugins.py index fa827a20ed48..9f52baf85c00 100644 --- a/qiskit/transpiler/passes/synthesis/hls_plugins.py +++ b/qiskit/transpiler/passes/synthesis/hls_plugins.py @@ -249,6 +249,95 @@ 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:`.ModularAdderSynthesisCD04` + - 1 + - a ripple-carry adder + * - ``"ripple_vbe"`` + - :class:`.ModularAdderSynthesisVB95` + - 1, for more than 1 state qubit + - a ripple-carry adder + * - ``"qft"`` + - :class:`.ModularAdderSynthesisD00` + - 0 + - a QFT-based adder + +.. autosummary:: + :toctree: ../stubs/ + + ModularAdderSynthesisCD04 + ModularAdderSynthesisD00 + ModularAdderSynthesisVB95 + +Adder Synthesis +''''''''''''''' + +.. list-table:: Plugins for :class:`.AdderGate` (key = ``"Adder"``) + :header-rows: 1 + + * - Plugin name + - Plugin class + - Number of clean ancillas + - Description + * - ``"ripple_cdkm"`` + - :class:`.AdderSynthesisCD04` + - 1 + - a ripple-carry adder + * - ``"ripple_vbe"`` + - :class:`.AdderSynthesisVB95` + - 1, for more than 1 state qubit + - a ripple-carry adder + * - ``"qft"`` + - :class:`.AdderSynthesisD00` + - 0 + - a QFT-based adder + +.. autosummary:: + :toctree: ../stubs/ + + AdderSynthesisCD04 + AdderSynthesisD00 + AdderSynthesisVB95 + +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:`.FullAdderSynthesisCD04` + - 0 + - a ripple-carry adder + * - ``"ripple_vbe"`` + - :class:`.FullAdderSynthesisVB95` + - 1, for more than 1 state qubit + - a ripple-carry adder + * - ``"qft"`` + - :class:`.FullAdderSynthesisD00` + - 0 + - a QFT-based adder + +.. autosummary:: + :toctree: ../stubs/ + + FullAdderSynthesisCD04 + FullAdderSynthesisD00 + FullAdderSynthesisVB95 """ import numpy as np @@ -290,6 +379,7 @@ synth_mcx_noaux_v24, ) from qiskit.synthesis.multi_controlled import synth_mcmt_vchain +from qiskit.synthesis.arithmetic import adder_cd04, adder_d00, adder_vb95 from qiskit.transpiler.passes.routing.algorithms import ApproximateTokenSwapper from .plugin import HighLevelSynthesisPlugin @@ -1029,3 +1119,85 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, ** high_level_object.num_target_qubits, ctrl_state, ) + + +class ModularAdderSynthesisCD04(HighLevelSynthesisPlugin): + r"""A ripple-carry adder, modulo :math:`2^n`.""" + + def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): + # unless we implement the full adder, this implementation needs an ancilla qubit + if options.get("num_clean_ancillas", 0) < 1: + return None + + return adder_cd04(high_level_object.num_state_qubits, kind="fixed") + + +class ModularAdderSynthesisVB95(HighLevelSynthesisPlugin): + r"""A ripple-carry adder, modulo :math:`2^n`.""" + + def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): + num_state_qubits = high_level_object.num_state_qubits + + # for more than 1 state qubit, we need an ancilla + if num_state_qubits > 1 and options.get("num_clean_ancillas", 0) < 1: + return None + + return adder_vb95(num_state_qubits, kind="fixed") + + +class ModularAdderSynthesisD00(HighLevelSynthesisPlugin): + r"""A QFT-based adder, modulo :math:`2^n`.""" + + def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): + return adder_d00(high_level_object.num_state_qubits, kind="fixed") + + +class AdderSynthesisCD04(HighLevelSynthesisPlugin): + """A ripple-carry adder with a carry-out bit.""" + + def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): + # unless we implement the full adder, this implementation needs an ancilla qubit + if options.get("num_clean_ancillas", 0) < 1: + return None + + return adder_cd04(high_level_object.num_state_qubits, kind="half") + + +class AdderSynthesisVB95(HighLevelSynthesisPlugin): + """A ripple-carry adder with a carry-out bit.""" + + def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): + num_state_qubits = high_level_object.num_state_qubits + + # for more than 1 state qubit, we need an ancilla + if num_state_qubits > 1 and options.get("num_clean_ancillas", 0) < 1: + return None + + return adder_vb95(num_state_qubits, kind="half") + + +class AdderSynthesisD00(HighLevelSynthesisPlugin): + """A QFT-based adder with a carry-in and a carry-out bit.""" + + def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): + return adder_d00(high_level_object.num_state_qubits, kind="half") + + +class FullAdderSynthesisCD04(HighLevelSynthesisPlugin): + """A ripple-carry adder with a carry-in and a carry-out bit.""" + + def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): + return adder_cd04(high_level_object.num_state_qubits, kind="full") + + +class FullAdderSynthesisVB95(HighLevelSynthesisPlugin): + """A ripple-carry adder with a carry-in and a carry-out bit.""" + + def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): + num_state_qubits = high_level_object.num_state_qubits + + # for more than 1 state qubit, we need an ancilla + if num_state_qubits > 1 and options.get("num_clean_ancillas", 0) < 1: + return None + + return adder_vb95(num_state_qubits, kind="full") From 1ecd282b2c1b3c0ff88893d23a41ea29a693637c Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Tue, 22 Oct 2024 09:54:37 +0200 Subject: [PATCH 02/11] add plugins, fix QFT --- pyproject.toml | 11 ++ qiskit/circuit/library/__init__.py | 3 + qiskit/circuit/library/arithmetic/__init__.py | 9 +- .../library/arithmetic/adders/__init__.py | 1 + .../library/arithmetic/adders/adder.py | 14 +- .../adders/cdkm_ripple_carry_adder.py | 6 +- .../arithmetic/adders/draper_qft_adder.py | 47 ++++++- .../adders/vbe_ripple_carry_adder.py | 6 +- qiskit/synthesis/arithmetic/__init__.py | 6 +- .../arithmetic/cdkm_ripple_carry_adder.py | 6 +- .../synthesis/arithmetic/draper_qft_adder.py | 41 +++--- .../arithmetic/vbe_ripple_carry_adder.py | 12 +- .../passes/synthesis/hls_plugins.py | 86 +++++++----- test/python/circuit/library/test_adders.py | 123 ++++++++++++++---- 14 files changed, 278 insertions(+), 93 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 70a6e7c9d0cf..23903714b8b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -100,6 +100,17 @@ 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" +"Adder.default" = "qiskit.transpiler.passes.synthesis.hls_plugins:AdderSynthesisDefault" +"Adder.ripple_c04" = "qiskit.transpiler.passes.synthesis.hls_plugins:AdderSynthesisC04" +"Adder.ripple_v95" = "qiskit.transpiler.passes.synthesis.hls_plugins:AdderSynthesisV95" +"Adder.qft_d00" = "qiskit.transpiler.passes.synthesis.hls_plugins:AdderSynthesisD00" +"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" [project.entry-points."qiskit.transpiler.init"] default = "qiskit.transpiler.preset_passmanagers.builtin_plugins:DefaultInitPassManager" diff --git a/qiskit/circuit/library/__init__.py b/qiskit/circuit/library/__init__.py index a4203f63b738..edafede2bfda 100644 --- a/qiskit/circuit/library/__init__.py +++ b/qiskit/circuit/library/__init__.py @@ -529,6 +529,9 @@ ) from .basis_change import QFT, QFTGate from .arithmetic import ( + ModularAdderGate, + AdderGate, + FullAdderGate, FunctionalPauliRotations, LinearPauliRotations, PiecewiseLinearPauliRotations, diff --git a/qiskit/circuit/library/arithmetic/__init__.py b/qiskit/circuit/library/arithmetic/__init__.py index 71c6cb7f5df5..a6e3b1b5aee9 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, + AdderGate, + FullAdderGate, +) from .piecewise_chebyshev import PiecewiseChebyshev from .multipliers import HRSCumulativeMultiplier, RGQFTMultiplier 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..9e4487c87cdb 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, AdderGate, FullAdderGate diff --git a/qiskit/circuit/library/arithmetic/adders/adder.py b/qiskit/circuit/library/arithmetic/adders/adder.py index a0cb70379e4a..fe06a0d93012 100644 --- a/qiskit/circuit/library/arithmetic/adders/adder.py +++ b/qiskit/circuit/library/arithmetic/adders/adder.py @@ -15,6 +15,7 @@ from __future__ import annotations from qiskit.circuit import QuantumCircuit, Gate +from qiskit.utils.deprecation import deprecate_func class Adder(QuantumCircuit): @@ -41,6 +42,15 @@ 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, AdderGate, FullAdderGate, respectively.", + ), + pending=True, + ) def __init__(self, num_state_qubits: int, name: str = "Adder") -> None: """ Args: @@ -104,14 +114,14 @@ def num_state_qubits(self) -> int: class ModularAdderGate(Gate): - r"""Compute the sum of two :math:`n`-sized qubit registers. + 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\rangle_{n + 1}. + |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`) 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 52f7c79a71e3..2d26c0c0ad05 100644 --- a/qiskit/circuit/library/arithmetic/adders/cdkm_ripple_carry_adder.py +++ b/qiskit/circuit/library/arithmetic/adders/cdkm_ripple_carry_adder.py @@ -12,7 +12,7 @@ """Compute the sum of two qubit registers using ripple-carry approach.""" -from qiskit.synthesis.arithmetic import adder_cd04 +from qiskit.synthesis.arithmetic import adder_ripple_c04 from .adder import Adder @@ -102,5 +102,7 @@ def __init__( ValueError: If ``num_state_qubits`` is lower than 1. """ super().__init__(num_state_qubits, name=name) - circuit = adder_cd04(num_state_qubits, kind) + circuit = adder_ripple_c04(num_state_qubits, kind) + + 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 f64a34efc924..80671866bab1 100644 --- a/qiskit/circuit/library/arithmetic/adders/draper_qft_adder.py +++ b/qiskit/circuit/library/arithmetic/adders/draper_qft_adder.py @@ -12,7 +12,12 @@ """Compute the sum of two qubit registers using QFT.""" -from qiskit.synthesis.arithmetic import adder_d00 +import numpy as np + +from qiskit.circuit.quantumcircuit import QuantumCircuit +from qiskit.circuit.quantumregister import QuantumRegister +from qiskit.circuit.library.basis_change import QFT + from .adder import Adder @@ -68,6 +73,44 @@ def __init__( Raises: ValueError: If ``num_state_qubits`` is lower than 1. """ + 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.") + super().__init__(num_state_qubits, name=name) - circuit = adder_d00(num_state_qubits, kind) + + 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 + self.add_register(*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_qubits_qft = num_state_qubits if kind == "fixed" else num_state_qubits + 1 + + circuit = QuantumCircuit(*self.qregs, name=name) + + # build QFT adder circuit + circuit.append(QFT(num_qubits_qft, do_swaps=False).to_gate(), qr_sum[:]) + + for j in range(num_state_qubits): + for k in range(num_state_qubits - j): + lam = np.pi / (2**k) + circuit.cp(lam, qr_a[j], qr_b[j + k]) + + if kind == "half": + for j in range(num_state_qubits): + lam = np.pi / (2 ** (j + 1)) + circuit.cp(lam, qr_a[num_state_qubits - j - 1], qr_z[0]) + + circuit.append(QFT(num_qubits_qft, do_swaps=False).inverse().to_gate(), qr_sum[:]) + self.append(circuit.to_gate(), self.qubits) 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 17547824e133..46c82cc0f245 100644 --- a/qiskit/circuit/library/arithmetic/adders/vbe_ripple_carry_adder.py +++ b/qiskit/circuit/library/arithmetic/adders/vbe_ripple_carry_adder.py @@ -13,7 +13,7 @@ """Compute the sum of two qubit registers using Classical Addition.""" from __future__ import annotations -from qiskit.synthesis.arithmetic import adder_vb95 +from qiskit.synthesis.arithmetic import adder_ripple_v95 from .adder import Adder @@ -73,5 +73,7 @@ def __init__( ValueError: If ``num_state_qubits`` is lower than 1. """ super().__init__(num_state_qubits, name=name) - circuit = adder_vb95(num_state_qubits, kind) + circuit = adder_ripple_v95(num_state_qubits, kind) + + self.add_register(*circuit.qregs) self.append(circuit.to_gate(), self.qubits) diff --git a/qiskit/synthesis/arithmetic/__init__.py b/qiskit/synthesis/arithmetic/__init__.py index dad2f929009e..73be2f449f03 100644 --- a/qiskit/synthesis/arithmetic/__init__.py +++ b/qiskit/synthesis/arithmetic/__init__.py @@ -12,6 +12,6 @@ """Module containing multi-controlled circuits synthesis""" -from .cdkm_ripple_carry_adder import adder_cd04 -from .vbe_ripple_carry_adder import adder_vb95 -from .draper_qft_adder import adder_d00 +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/cdkm_ripple_carry_adder.py b/qiskit/synthesis/arithmetic/cdkm_ripple_carry_adder.py index 3a0e80318771..9e18bfbb9fff 100644 --- a/qiskit/synthesis/arithmetic/cdkm_ripple_carry_adder.py +++ b/qiskit/synthesis/arithmetic/cdkm_ripple_carry_adder.py @@ -16,9 +16,13 @@ from qiskit.circuit.quantumregister import QuantumRegister, AncillaRegister -def adder_cd04(num_state_qubits: int, kind: str) -> QuantumCircuit: +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: diff --git a/qiskit/synthesis/arithmetic/draper_qft_adder.py b/qiskit/synthesis/arithmetic/draper_qft_adder.py index 01ef8beb7f44..0bf9629b4249 100644 --- a/qiskit/synthesis/arithmetic/draper_qft_adder.py +++ b/qiskit/synthesis/arithmetic/draper_qft_adder.py @@ -19,28 +19,29 @@ from qiskit.circuit.library.basis_change import QFTGate -def adder_d00(num_state_qubits: int, kind: str) -> QuantumCircuit: +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"``). + ``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(π) │ │ │ │ ┌───────┐ - b_0: ┤0 ├─■──────┼────────┼──────┼────────┼───────┤0 ├ - │ │ │P(π/2) │P(π) │ │ │ │ - b_1: ┤1 qft ├────────■────────■──────┼────────┼───────┤1 iqft ├ - │ │ │P(π/2) │P(π/4) │ │ - cout_0: ┤2 ├────────────────────────■────────■───────┤2 ├ - └──────┘ └───────┘ + 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 @@ -83,21 +84,19 @@ def adder_d00(num_state_qubits: int, kind: str) -> QuantumCircuit: # 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_qubits_qft = num_state_qubits if kind == "fixed" else num_state_qubits + 1 + num_sum = num_state_qubits if kind == "fixed" else num_state_qubits + 1 # build QFT adder circuit - qft = QFTGate(num_qubits_qft) + qft = QFTGate(num_sum) circuit.append(qft, qr_sum[:]) for j in range(num_state_qubits): - for k in range(num_state_qubits - j): + for k in range(num_sum - j): lam = np.pi / (2**k) - circuit.cp(lam, qr_a[j], qr_b[j + k]) - - if kind == "half": - for j in range(num_state_qubits): - lam = np.pi / (2 ** (j + 1)) - circuit.cp(lam, qr_a[num_state_qubits - j - 1], qr_z[0]) + # 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[:]) diff --git a/qiskit/synthesis/arithmetic/vbe_ripple_carry_adder.py b/qiskit/synthesis/arithmetic/vbe_ripple_carry_adder.py index 9638858e608b..05d8581db942 100644 --- a/qiskit/synthesis/arithmetic/vbe_ripple_carry_adder.py +++ b/qiskit/synthesis/arithmetic/vbe_ripple_carry_adder.py @@ -19,9 +19,12 @@ from qiskit.circuit.quantumregister import QuantumRegister, AncillaRegister -def adder_vb95(num_state_qubits: int, kind: str) -> QuantumCircuit: +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: @@ -54,8 +57,8 @@ def adder_vb95(num_state_qubits: int, kind: str) -> QuantumCircuit: 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 + 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. @@ -67,6 +70,9 @@ def adder_vb95(num_state_qubits: int, kind: str) -> QuantumCircuit: [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.") diff --git a/qiskit/transpiler/passes/synthesis/hls_plugins.py b/qiskit/transpiler/passes/synthesis/hls_plugins.py index 9f52baf85c00..fcc40a474203 100644 --- a/qiskit/transpiler/passes/synthesis/hls_plugins.py +++ b/qiskit/transpiler/passes/synthesis/hls_plugins.py @@ -260,12 +260,12 @@ - Number of clean ancillas - Description * - ``"ripple_cdkm"`` - - :class:`.ModularAdderSynthesisCD04` + - :class:`.ModularAdderSynthesisC04` - 1 - a ripple-carry adder * - ``"ripple_vbe"`` - - :class:`.ModularAdderSynthesisVB95` - - 1, for more than 1 state qubit + - :class:`.ModularAdderSynthesisV95` + - :math:`n-1`, for :math:`n`-bit numbers - a ripple-carry adder * - ``"qft"`` - :class:`.ModularAdderSynthesisD00` @@ -275,9 +275,9 @@ .. autosummary:: :toctree: ../stubs/ - ModularAdderSynthesisCD04 + ModularAdderSynthesisC04 ModularAdderSynthesisD00 - ModularAdderSynthesisVB95 + ModularAdderSynthesisV95 Adder Synthesis ''''''''''''''' @@ -290,12 +290,12 @@ - Number of clean ancillas - Description * - ``"ripple_cdkm"`` - - :class:`.AdderSynthesisCD04` + - :class:`.AdderSynthesisC04` - 1 - a ripple-carry adder * - ``"ripple_vbe"`` - - :class:`.AdderSynthesisVB95` - - 1, for more than 1 state qubit + - :class:`.AdderSynthesisV95` + - :math:`n-1`, for :math:`n`-bit numbers - a ripple-carry adder * - ``"qft"`` - :class:`.AdderSynthesisD00` @@ -305,9 +305,9 @@ .. autosummary:: :toctree: ../stubs/ - AdderSynthesisCD04 + AdderSynthesisC04 AdderSynthesisD00 - AdderSynthesisVB95 + AdderSynthesisV95 Full Adder Synthesis '''''''''''''''''''' @@ -320,12 +320,12 @@ - Number of clean ancillas - Description * - ``"ripple_cdkm"`` - - :class:`.FullAdderSynthesisCD04` + - :class:`.FullAdderSynthesisC04` - 0 - a ripple-carry adder * - ``"ripple_vbe"`` - - :class:`.FullAdderSynthesisVB95` - - 1, for more than 1 state qubit + - :class:`.FullAdderSynthesisV95` + - :math:`n-1`, for :math:`n`-bit numbers - a ripple-carry adder * - ``"qft"`` - :class:`.FullAdderSynthesisD00` @@ -335,9 +335,9 @@ .. autosummary:: :toctree: ../stubs/ - FullAdderSynthesisCD04 + FullAdderSynthesisC04 FullAdderSynthesisD00 - FullAdderSynthesisVB95 + FullAdderSynthesisV95 """ import numpy as np @@ -379,7 +379,7 @@ synth_mcx_noaux_v24, ) from qiskit.synthesis.multi_controlled import synth_mcmt_vchain -from qiskit.synthesis.arithmetic import adder_cd04, adder_d00, adder_vb95 +from qiskit.synthesis.arithmetic import adder_ripple_c04, adder_qft_d00, adder_ripple_v95 from qiskit.transpiler.passes.routing.algorithms import ApproximateTokenSwapper from .plugin import HighLevelSynthesisPlugin @@ -1121,7 +1121,17 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, ** ) -class ModularAdderSynthesisCD04(HighLevelSynthesisPlugin): +class ModularAdderSynthesisDefault(HighLevelSynthesisPlugin): + """The default modular adder (no carry in, no carry out qubit) synthesis.""" + + def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): + 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`.""" def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): @@ -1129,10 +1139,10 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, ** if options.get("num_clean_ancillas", 0) < 1: return None - return adder_cd04(high_level_object.num_state_qubits, kind="fixed") + return adder_ripple_c04(high_level_object.num_state_qubits, kind="fixed") -class ModularAdderSynthesisVB95(HighLevelSynthesisPlugin): +class ModularAdderSynthesisV95(HighLevelSynthesisPlugin): r"""A ripple-carry adder, modulo :math:`2^n`.""" def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): @@ -1142,17 +1152,33 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, ** if num_state_qubits > 1 and options.get("num_clean_ancillas", 0) < 1: return None - return adder_vb95(num_state_qubits, kind="fixed") + return adder_ripple_v95(num_state_qubits, kind="fixed") class ModularAdderSynthesisD00(HighLevelSynthesisPlugin): r"""A QFT-based adder, modulo :math:`2^n`.""" def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): - return adder_d00(high_level_object.num_state_qubits, kind="fixed") + return adder_qft_d00(high_level_object.num_state_qubits, kind="fixed") + + +class AdderSynthesisDefault(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. + """ + + def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): + 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 AdderSynthesisCD04(HighLevelSynthesisPlugin): +class AdderSynthesisC04(HighLevelSynthesisPlugin): """A ripple-carry adder with a carry-out bit.""" def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): @@ -1160,10 +1186,10 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, ** if options.get("num_clean_ancillas", 0) < 1: return None - return adder_cd04(high_level_object.num_state_qubits, kind="half") + return adder_ripple_c04(high_level_object.num_state_qubits, kind="half") -class AdderSynthesisVB95(HighLevelSynthesisPlugin): +class AdderSynthesisV95(HighLevelSynthesisPlugin): """A ripple-carry adder with a carry-out bit.""" def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): @@ -1173,24 +1199,24 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, ** if num_state_qubits > 1 and options.get("num_clean_ancillas", 0) < 1: return None - return adder_vb95(num_state_qubits, kind="half") + return adder_ripple_v95(num_state_qubits, kind="half") class AdderSynthesisD00(HighLevelSynthesisPlugin): """A QFT-based adder with a carry-in and a carry-out bit.""" def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): - return adder_d00(high_level_object.num_state_qubits, kind="half") + return adder_qft_d00(high_level_object.num_state_qubits, kind="half") -class FullAdderSynthesisCD04(HighLevelSynthesisPlugin): +class FullAdderSynthesisC04(HighLevelSynthesisPlugin): """A ripple-carry adder with a carry-in and a carry-out bit.""" def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): - return adder_cd04(high_level_object.num_state_qubits, kind="full") + return adder_ripple_c04(high_level_object.num_state_qubits, kind="full") -class FullAdderSynthesisVB95(HighLevelSynthesisPlugin): +class FullAdderSynthesisV95(HighLevelSynthesisPlugin): """A ripple-carry adder with a carry-in and a carry-out bit.""" def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): @@ -1200,4 +1226,4 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, ** if num_state_qubits > 1 and options.get("num_clean_ancillas", 0) < 1: return None - return adder_vb95(num_state_qubits, kind="full") + return adder_ripple_v95(num_state_qubits, kind="full") diff --git a/test/python/circuit/library/test_adders.py b/test/python/circuit/library/test_adders.py index 0866cb766710..3fbfc15ee55c 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, + AdderGate, + 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"]), + "Adder": (AdderGate, ["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() From 27cc9b23a5b87f9607889deed5e42774fe534da6 Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Tue, 22 Oct 2024 17:01:51 +0200 Subject: [PATCH 03/11] add multipliers --- pyproject.toml | 3 + qiskit/circuit/library/__init__.py | 1 + qiskit/circuit/library/arithmetic/__init__.py | 2 +- .../library/arithmetic/adders/adder.py | 3 +- .../arithmetic/multipliers/__init__.py | 1 + .../arithmetic/multipliers/multiplier.py | 97 ++++++++++++++++- qiskit/synthesis/__init__.py | 23 ++++ qiskit/synthesis/arithmetic/__init__.py | 5 +- .../synthesis/arithmetic/adders/__init__.py | 17 +++ .../{ => adders}/cdkm_ripple_carry_adder.py | 0 .../{ => adders}/draper_qft_adder.py | 0 .../{ => adders}/vbe_ripple_carry_adder.py | 0 .../arithmetic/multipliers/__init__.py | 16 +++ .../multipliers/hrs_cumulative_multiplier.py | 102 ++++++++++++++++++ .../multipliers/rg_qft_multiplier.py | 99 +++++++++++++++++ .../passes/synthesis/hls_plugins.py | 53 ++++++++- ...ary-arithmetic-gates-6cd2b1c8112febe0.yaml | 25 +++++ test/python/circuit/library/test_adders.py | 2 +- .../circuit/library/test_multipliers.py | 40 ++++++- 19 files changed, 478 insertions(+), 11 deletions(-) create mode 100644 qiskit/synthesis/arithmetic/adders/__init__.py rename qiskit/synthesis/arithmetic/{ => adders}/cdkm_ripple_carry_adder.py (100%) rename qiskit/synthesis/arithmetic/{ => adders}/draper_qft_adder.py (100%) rename qiskit/synthesis/arithmetic/{ => adders}/vbe_ripple_carry_adder.py (100%) create mode 100644 qiskit/synthesis/arithmetic/multipliers/__init__.py create mode 100644 qiskit/synthesis/arithmetic/multipliers/hrs_cumulative_multiplier.py create mode 100644 qiskit/synthesis/arithmetic/multipliers/rg_qft_multiplier.py create mode 100644 releasenotes/notes/binary-arithmetic-gates-6cd2b1c8112febe0.yaml diff --git a/pyproject.toml b/pyproject.toml index 23903714b8b7..ba6bbb0da652 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -111,6 +111,9 @@ sk = "qiskit.transpiler.passes.synthesis.solovay_kitaev_synthesis:SolovayKitaevS "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" [project.entry-points."qiskit.transpiler.init"] default = "qiskit.transpiler.preset_passmanagers.builtin_plugins:DefaultInitPassManager" diff --git a/qiskit/circuit/library/__init__.py b/qiskit/circuit/library/__init__.py index edafede2bfda..c92d6a34286f 100644 --- a/qiskit/circuit/library/__init__.py +++ b/qiskit/circuit/library/__init__.py @@ -532,6 +532,7 @@ ModularAdderGate, AdderGate, FullAdderGate, + MultiplierGate, FunctionalPauliRotations, LinearPauliRotations, PiecewiseLinearPauliRotations, diff --git a/qiskit/circuit/library/arithmetic/__init__.py b/qiskit/circuit/library/arithmetic/__init__.py index a6e3b1b5aee9..c7d0b2ed51c9 100644 --- a/qiskit/circuit/library/arithmetic/__init__.py +++ b/qiskit/circuit/library/arithmetic/__init__.py @@ -30,5 +30,5 @@ 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/adder.py b/qiskit/circuit/library/arithmetic/adders/adder.py index fe06a0d93012..a9558c69407c 100644 --- a/qiskit/circuit/library/arithmetic/adders/adder.py +++ b/qiskit/circuit/library/arithmetic/adders/adder.py @@ -47,7 +47,8 @@ class Adder(QuantumCircuit): 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, AdderGate, FullAdderGate, respectively.", + "ModularAdderGate, AdderGate, FullAdderGate, respectively. For different adder " + "implementations, see https://docs.quantum.ibm.com/api/qiskit/synthesis.", ), pending=True, ) 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/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/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 index 73be2f449f03..f413421d6b4b 100644 --- a/qiskit/synthesis/arithmetic/__init__.py +++ b/qiskit/synthesis/arithmetic/__init__.py @@ -12,6 +12,5 @@ """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 +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/cdkm_ripple_carry_adder.py b/qiskit/synthesis/arithmetic/adders/cdkm_ripple_carry_adder.py similarity index 100% rename from qiskit/synthesis/arithmetic/cdkm_ripple_carry_adder.py rename to qiskit/synthesis/arithmetic/adders/cdkm_ripple_carry_adder.py diff --git a/qiskit/synthesis/arithmetic/draper_qft_adder.py b/qiskit/synthesis/arithmetic/adders/draper_qft_adder.py similarity index 100% rename from qiskit/synthesis/arithmetic/draper_qft_adder.py rename to qiskit/synthesis/arithmetic/adders/draper_qft_adder.py diff --git a/qiskit/synthesis/arithmetic/vbe_ripple_carry_adder.py b/qiskit/synthesis/arithmetic/adders/vbe_ripple_carry_adder.py similarity index 100% rename from qiskit/synthesis/arithmetic/vbe_ripple_carry_adder.py rename to qiskit/synthesis/arithmetic/adders/vbe_ripple_carry_adder.py 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..78fb8f3595cc --- /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 AdderGate, ModularAdderGate + + adder = AdderGate(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 fcc40a474203..4c657a58329c 100644 --- a/qiskit/transpiler/passes/synthesis/hls_plugins.py +++ b/qiskit/transpiler/passes/synthesis/hls_plugins.py @@ -338,6 +338,33 @@ FullAdderSynthesisC04 FullAdderSynthesisD00 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 + """ import numpy as np @@ -379,7 +406,13 @@ synth_mcx_noaux_v24, ) from qiskit.synthesis.multi_controlled import synth_mcmt_vchain -from qiskit.synthesis.arithmetic import adder_ripple_c04, adder_qft_d00, adder_ripple_v95 +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 @@ -1227,3 +1260,21 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, ** return None return adder_ripple_v95(num_state_qubits, kind="full") + + +class MultiplierSynthesisH18(HighLevelSynthesisPlugin): + """A cumulative multiplier based on controlled adders.""" + + def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): + return multiplier_cumulative_h18( + high_level_object.num_state_qubits, high_level_object.num_result_qubits + ) + + +class MultiplierSynthesisR17(HighLevelSynthesisPlugin): + """A QFT-based multiplier.""" + + def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): + return multiplier_qft_r17( + high_level_object.num_state_qubits, high_level_object.num_result_qubits + ) 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 3fbfc15ee55c..677c2183746e 100644 --- a/test/python/circuit/library/test_adders.py +++ b/test/python/circuit/library/test_adders.py @@ -181,7 +181,7 @@ def test_plugins(self): expected_ops = { "ripple_c04": "MAJ", "ripple_v95": "Carry", - "qft_d00": "cp", + "qft_d00": "qft", } num_state_qubits = 3 diff --git a/test/python/circuit/library/test_multipliers.py b/test/python/circuit/library/test_multipliers.py index fd083e287153..362f907b5b3e 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", "ccp", "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() From 573ac69d90323a7c2b252a77f6b5b3fac0a225cb Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Tue, 22 Oct 2024 17:11:34 +0200 Subject: [PATCH 04/11] add seealso --- .../arithmetic/adders/cdkm_ripple_carry_adder.py | 15 +++++++++++++++ .../arithmetic/adders/draper_qft_adder.py | 13 +++++++++++++ .../arithmetic/adders/vbe_ripple_carry_adder.py | 16 ++++++++++++++++ .../multipliers/hrs_cumulative_multiplier.py | 7 +++++++ .../arithmetic/multipliers/rg_qft_multiplier.py | 7 +++++++ 5 files changed, 58 insertions(+) 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 2d26c0c0ad05..7f4c0467d78f 100644 --- a/qiskit/circuit/library/arithmetic/adders/cdkm_ripple_carry_adder.py +++ b/qiskit/circuit/library/arithmetic/adders/cdkm_ripple_carry_adder.py @@ -74,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:** diff --git a/qiskit/circuit/library/arithmetic/adders/draper_qft_adder.py b/qiskit/circuit/library/arithmetic/adders/draper_qft_adder.py index 80671866bab1..c8773ed94af2 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 46c82cc0f245..9f475eced1e2 100644 --- a/qiskit/circuit/library/arithmetic/adders/vbe_ripple_carry_adder.py +++ b/qiskit/circuit/library/arithmetic/adders/vbe_ripple_carry_adder.py @@ -50,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. diff --git a/qiskit/circuit/library/arithmetic/multipliers/hrs_cumulative_multiplier.py b/qiskit/circuit/library/arithmetic/multipliers/hrs_cumulative_multiplier.py index 220f1b48f770..a4311480932b 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/rg_qft_multiplier.py b/qiskit/circuit/library/arithmetic/multipliers/rg_qft_multiplier.py index 0a6c08d9d784..b3e799f4a8f5 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. From 458fbe388c4de32e84d64f5eb3c97a462d1c1d23 Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Fri, 25 Oct 2024 16:16:57 +0200 Subject: [PATCH 05/11] fix tests --- test/python/circuit/library/test_adders.py | 2 +- test/python/circuit/test_gate_definitions.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/test/python/circuit/library/test_adders.py b/test/python/circuit/library/test_adders.py index 677c2183746e..3fbfc15ee55c 100644 --- a/test/python/circuit/library/test_adders.py +++ b/test/python/circuit/library/test_adders.py @@ -181,7 +181,7 @@ def test_plugins(self): expected_ops = { "ripple_c04": "MAJ", "ripple_v95": "Carry", - "qft_d00": "qft", + "qft_d00": "cp", } num_state_qubits = 3 diff --git a/test/python/circuit/test_gate_definitions.py b/test/python/circuit/test_gate_definitions.py index 220478159ee3..866efd71654e 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", + "AdderGate", + "FullAdderGate", + "MultiplierGate", } # Amazingly, Python's scoping rules for class bodies means that this is the closest we can get From 4f3146abcaeb06cc567db0b38d108875c6257832 Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Fri, 25 Oct 2024 16:55:48 +0200 Subject: [PATCH 06/11] thou shall obey lint and not imagine new classes that dont exist --- qiskit/transpiler/passes/synthesis/hls_plugins.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/qiskit/transpiler/passes/synthesis/hls_plugins.py b/qiskit/transpiler/passes/synthesis/hls_plugins.py index 4c657a58329c..9126ddfc74db 100644 --- a/qiskit/transpiler/passes/synthesis/hls_plugins.py +++ b/qiskit/transpiler/passes/synthesis/hls_plugins.py @@ -327,16 +327,11 @@ - :class:`.FullAdderSynthesisV95` - :math:`n-1`, for :math:`n`-bit numbers - a ripple-carry adder - * - ``"qft"`` - - :class:`.FullAdderSynthesisD00` - - 0 - - a QFT-based adder .. autosummary:: :toctree: ../stubs/ FullAdderSynthesisC04 - FullAdderSynthesisD00 FullAdderSynthesisV95 @@ -1182,7 +1177,7 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=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 and options.get("num_clean_ancillas", 0) < 1: + if num_state_qubits > 1 > options.get("num_clean_ancillas", 1): return None return adder_ripple_v95(num_state_qubits, kind="fixed") @@ -1229,7 +1224,7 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=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 and options.get("num_clean_ancillas", 0) < 1: + if num_state_qubits > 1 > options.get("num_clean_ancillas", 1): return None return adder_ripple_v95(num_state_qubits, kind="half") @@ -1256,7 +1251,7 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=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 and options.get("num_clean_ancillas", 0) < 1: + if num_state_qubits > 1 > options.get("num_clean_ancillas", 1): return None return adder_ripple_v95(num_state_qubits, kind="full") From a54907eec954f98f61bf723fe0e82d5f4d0fb00e Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Tue, 5 Nov 2024 14:10:13 +0100 Subject: [PATCH 07/11] review comments --- pyproject.toml | 8 +- qiskit/circuit/library/__init__.py | 2 +- qiskit/circuit/library/arithmetic/__init__.py | 2 +- .../library/arithmetic/adders/__init__.py | 2 +- .../library/arithmetic/adders/adder.py | 13 +- .../multipliers/hrs_cumulative_multiplier.py | 4 +- .../passes/synthesis/hls_plugins.py | 202 +++++++++++++++--- test/python/circuit/library/test_adders.py | 4 +- 8 files changed, 199 insertions(+), 38 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ba6bbb0da652..a58819796509 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -104,10 +104,10 @@ sk = "qiskit.transpiler.passes.synthesis.solovay_kitaev_synthesis:SolovayKitaevS "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" -"Adder.default" = "qiskit.transpiler.passes.synthesis.hls_plugins:AdderSynthesisDefault" -"Adder.ripple_c04" = "qiskit.transpiler.passes.synthesis.hls_plugins:AdderSynthesisC04" -"Adder.ripple_v95" = "qiskit.transpiler.passes.synthesis.hls_plugins:AdderSynthesisV95" -"Adder.qft_d00" = "qiskit.transpiler.passes.synthesis.hls_plugins:AdderSynthesisD00" +"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" diff --git a/qiskit/circuit/library/__init__.py b/qiskit/circuit/library/__init__.py index c92d6a34286f..a1342e525de6 100644 --- a/qiskit/circuit/library/__init__.py +++ b/qiskit/circuit/library/__init__.py @@ -530,7 +530,7 @@ from .basis_change import QFT, QFTGate from .arithmetic import ( ModularAdderGate, - AdderGate, + HalfAdderGate, FullAdderGate, MultiplierGate, FunctionalPauliRotations, diff --git a/qiskit/circuit/library/arithmetic/__init__.py b/qiskit/circuit/library/arithmetic/__init__.py index c7d0b2ed51c9..ede35a9451e1 100644 --- a/qiskit/circuit/library/arithmetic/__init__.py +++ b/qiskit/circuit/library/arithmetic/__init__.py @@ -26,7 +26,7 @@ CDKMRippleCarryAdder, DraperQFTAdder, ModularAdderGate, - AdderGate, + HalfAdderGate, FullAdderGate, ) from .piecewise_chebyshev import PiecewiseChebyshev diff --git a/qiskit/circuit/library/arithmetic/adders/__init__.py b/qiskit/circuit/library/arithmetic/adders/__init__.py index 9e4487c87cdb..03c042ab70c5 100644 --- a/qiskit/circuit/library/arithmetic/adders/__init__.py +++ b/qiskit/circuit/library/arithmetic/adders/__init__.py @@ -15,4 +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, AdderGate, FullAdderGate +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 a9558c69407c..e283f9e8c216 100644 --- a/qiskit/circuit/library/arithmetic/adders/adder.py +++ b/qiskit/circuit/library/arithmetic/adders/adder.py @@ -71,7 +71,7 @@ def num_state_qubits(self) -> int: return self._num_state_qubits -class AdderGate(Gate): +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 @@ -101,6 +101,9 @@ def __init__(self, num_state_qubits: int, label: str | None = None) -> None: 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__("Adder", 2 * num_state_qubits + 1, [], label=label) self._num_state_qubits = num_state_qubits @@ -144,6 +147,9 @@ def __init__(self, num_state_qubits: int, label: str | None = None) -> None: 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 @@ -188,7 +194,10 @@ def __init__(self, num_state_qubits: int, label: str | None = None) -> None: num_state_qubits: The number of qubits in each of the registers. name: The name of the circuit. """ - super().__init__("FullAdder", 2 * num_state_qubits, [], label=label) + 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 diff --git a/qiskit/synthesis/arithmetic/multipliers/hrs_cumulative_multiplier.py b/qiskit/synthesis/arithmetic/multipliers/hrs_cumulative_multiplier.py index 78fb8f3595cc..676d57ce4342 100644 --- a/qiskit/synthesis/arithmetic/multipliers/hrs_cumulative_multiplier.py +++ b/qiskit/synthesis/arithmetic/multipliers/hrs_cumulative_multiplier.py @@ -76,9 +76,9 @@ def multiplier_cumulative_h18( # prepare adder as controlled gate # pylint: disable=cyclic-import - from qiskit.circuit.library.arithmetic import AdderGate, ModularAdderGate + from qiskit.circuit.library.arithmetic import HalfAdderGate, ModularAdderGate - adder = AdderGate(num_state_qubits) + adder = HalfAdderGate(num_state_qubits) controlled_adder = adder.control(annotated=True) # build multiplication circuit diff --git a/qiskit/transpiler/passes/synthesis/hls_plugins.py b/qiskit/transpiler/passes/synthesis/hls_plugins.py index 9126ddfc74db..1fcb9bccd690 100644 --- a/qiskit/transpiler/passes/synthesis/hls_plugins.py +++ b/qiskit/transpiler/passes/synthesis/hls_plugins.py @@ -279,10 +279,10 @@ ModularAdderSynthesisD00 ModularAdderSynthesisV95 -Adder Synthesis -''''''''''''''' +Half Adder Synthesis +'''''''''''''''''''' -.. list-table:: Plugins for :class:`.AdderGate` (key = ``"Adder"``) +.. list-table:: Plugins for :class:`.HalfAdderGate` (key = ``"HalfAdder"``) :header-rows: 1 * - Plugin name @@ -290,24 +290,24 @@ - Number of clean ancillas - Description * - ``"ripple_cdkm"`` - - :class:`.AdderSynthesisC04` + - :class:`.HalfAdderSynthesisC04` - 1 - a ripple-carry adder * - ``"ripple_vbe"`` - - :class:`.AdderSynthesisV95` + - :class:`.HalfAdderSynthesisV95` - :math:`n-1`, for :math:`n`-bit numbers - a ripple-carry adder * - ``"qft"`` - - :class:`.AdderSynthesisD00` + - :class:`.HalfAdderSynthesisD00` - 0 - a QFT-based adder .. autosummary:: :toctree: ../stubs/ - AdderSynthesisC04 - AdderSynthesisD00 - AdderSynthesisV95 + HalfAdderSynthesisC04 + HalfAdderSynthesisD00 + HalfAdderSynthesisV95 Full Adder Synthesis '''''''''''''''''''' @@ -366,7 +366,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 @@ -1150,9 +1160,24 @@ 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.""" + """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") @@ -1160,9 +1185,23 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, ** class ModularAdderSynthesisC04(HighLevelSynthesisPlugin): - r"""A ripple-carry adder, modulo :math:`2^n`.""" + 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 @@ -1171,9 +1210,24 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, ** class ModularAdderSynthesisV95(HighLevelSynthesisPlugin): - r"""A ripple-carry adder, modulo :math:`2^n`.""" + 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 @@ -1184,32 +1238,67 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, ** class ModularAdderSynthesisD00(HighLevelSynthesisPlugin): - r"""A QFT-based adder, modulo :math:`2^n`.""" + 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 AdderSynthesisDefault(HighLevelSynthesisPlugin): +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 AdderSynthesisC04(HighLevelSynthesisPlugin): - """A ripple-carry adder with a carry-out bit.""" +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 @@ -1217,10 +1306,24 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, ** return adder_ripple_c04(high_level_object.num_state_qubits, kind="half") -class AdderSynthesisV95(HighLevelSynthesisPlugin): - """A ripple-carry adder with a carry-out bit.""" +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 @@ -1230,24 +1333,59 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, ** return adder_ripple_v95(num_state_qubits, kind="half") -class AdderSynthesisD00(HighLevelSynthesisPlugin): - """A QFT-based adder with a carry-in and a carry-out bit.""" +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.""" + """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.""" + """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 @@ -1258,18 +1396,32 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, ** class MultiplierSynthesisH18(HighLevelSynthesisPlugin): - """A cumulative multiplier based on controlled adders.""" + """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.""" + """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 ) diff --git a/test/python/circuit/library/test_adders.py b/test/python/circuit/library/test_adders.py index 3fbfc15ee55c..eacf8057775a 100644 --- a/test/python/circuit/library/test_adders.py +++ b/test/python/circuit/library/test_adders.py @@ -23,7 +23,7 @@ DraperQFTAdder, VBERippleCarryAdder, ModularAdderGate, - AdderGate, + HalfAdderGate, FullAdderGate, ) from qiskit.synthesis.arithmetic import adder_ripple_c04, adder_ripple_v95, adder_qft_d00 @@ -173,7 +173,7 @@ def test_plugins(self): # all gates with the plugins we check modes = { "ModularAdder": (ModularAdderGate, ["ripple_c04", "ripple_v95", "qft_d00"]), - "Adder": (AdderGate, ["ripple_c04", "ripple_v95", "qft_d00"]), + "HalfAdder": (HalfAdderGate, ["ripple_c04", "ripple_v95", "qft_d00"]), "FullAdder": (FullAdderGate, ["ripple_c04", "ripple_v95"]), } From 91807e8beb2aa8ca1301bb89a53e37a1acc111ac Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Tue, 5 Nov 2024 14:13:57 +0100 Subject: [PATCH 08/11] fix tests --- qiskit/circuit/library/arithmetic/adders/adder.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/circuit/library/arithmetic/adders/adder.py b/qiskit/circuit/library/arithmetic/adders/adder.py index e283f9e8c216..9330f8a8ee8b 100644 --- a/qiskit/circuit/library/arithmetic/adders/adder.py +++ b/qiskit/circuit/library/arithmetic/adders/adder.py @@ -47,7 +47,7 @@ class Adder(QuantumCircuit): 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, AdderGate, FullAdderGate, respectively. For different adder " + "ModularAdderGate, HalfAdderGate, FullAdderGate, respectively. For different adder " "implementations, see https://docs.quantum.ibm.com/api/qiskit/synthesis.", ), pending=True, @@ -104,7 +104,7 @@ def __init__(self, num_state_qubits: int, label: str | None = None) -> None: if num_state_qubits < 1: raise ValueError("Need at least 1 state qubit.") - super().__init__("Adder", 2 * num_state_qubits + 1, [], label=label) + super().__init__("HalfAdder", 2 * num_state_qubits + 1, [], label=label) self._num_state_qubits = num_state_qubits @property From 4dd219dc47d4a4ee6efb1ac01b7c81f8d1577bed Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Wed, 6 Nov 2024 10:49:32 +0100 Subject: [PATCH 09/11] fix remnant from HalfAdder renaming --- test/python/circuit/test_gate_definitions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/circuit/test_gate_definitions.py b/test/python/circuit/test_gate_definitions.py index 7b09e800d83f..3497ef9fe46a 100644 --- a/test/python/circuit/test_gate_definitions.py +++ b/test/python/circuit/test_gate_definitions.py @@ -312,7 +312,7 @@ class TestGateEquivalenceEqual(QiskitTestCase): "_SingletonControlledGateOverrides", "QFTGate", "ModularAdderGate", - "AdderGate", + "HalfAdderGate", "FullAdderGate", "MultiplierGate", "GraphStateGate", From 0f43d12b1a1614be79f8e5ce9636b1f799009cfe Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Wed, 6 Nov 2024 16:40:44 +0100 Subject: [PATCH 10/11] fix lint --- qiskit/transpiler/passes/synthesis/hls_plugins.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qiskit/transpiler/passes/synthesis/hls_plugins.py b/qiskit/transpiler/passes/synthesis/hls_plugins.py index 80b1e2e0b5ca..fe68e58ada80 100644 --- a/qiskit/transpiler/passes/synthesis/hls_plugins.py +++ b/qiskit/transpiler/passes/synthesis/hls_plugins.py @@ -426,7 +426,6 @@ synth_mcx_noaux_v24, synth_mcmt_vchain, ) -from qiskit.synthesis.multi_controlled import synth_mcmt_vchain from qiskit.synthesis.arithmetic import ( adder_ripple_c04, adder_qft_d00, From ee692f90e3c5d5cc2476cc13ac659beed2a0cbdd Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Wed, 6 Nov 2024 17:18:37 +0100 Subject: [PATCH 11/11] remove custom basis gate use --- test/python/circuit/library/test_multipliers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/circuit/library/test_multipliers.py b/test/python/circuit/library/test_multipliers.py index 362f907b5b3e..f5665007079b 100644 --- a/test/python/circuit/library/test_multipliers.py +++ b/test/python/circuit/library/test_multipliers.py @@ -58,7 +58,7 @@ 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 - tqc = transpile(circuit, basis_gates=["h", "p", "cp", "rz", "cx", "ccx", "ccp", "swap"]) + 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