Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Minor fixes for new adder and multiplier gates classes #13530

Merged
merged 7 commits into from
Dec 12, 2024
Merged
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -109,7 +109,7 @@ sk = "qiskit.transpiler.passes.synthesis.solovay_kitaev_synthesis:SolovayKitaevS
"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.default" = "qiskit.transpiler.passes.synthesis.hls_plugins:FullAdderSynthesisDefault"
"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"
24 changes: 24 additions & 0 deletions qiskit/circuit/library/arithmetic/adders/adder.py
Original file line number Diff line number Diff line change
@@ -116,6 +116,15 @@ def num_state_qubits(self) -> int:
"""
return self._num_state_qubits

def _define(self):
"""Populates self.definition with some decomposition of this gate."""
from qiskit.synthesis.arithmetic import adder_qft_d00

# This particular decomposition does not use any ancilla qubits.
# Note that the transpiler may choose a different decomposition
# based on the number of ancilla qubits available.
self.definition = adder_qft_d00(self.num_state_qubits, kind="half")


class ModularAdderGate(Gate):
r"""Compute the sum modulo :math:`2^n` of two :math:`n`-sized qubit registers.
@@ -162,6 +171,15 @@ def num_state_qubits(self) -> int:
"""
return self._num_state_qubits

def _define(self):
"""Populates self.definition with some decomposition of this gate."""
from qiskit.synthesis.arithmetic import adder_qft_d00

# This particular decomposition does not use any ancilla qubits.
# Note that the transpiler may choose a different decomposition
# based on the number of ancilla qubits available.
self.definition = adder_qft_d00(self.num_state_qubits, kind="fixed")


class FullAdderGate(Gate):
r"""Compute the sum of two :math:`n`-sized qubit registers, including carry-in and -out bits.
@@ -208,3 +226,9 @@ def num_state_qubits(self) -> int:
The number of state qubits.
"""
return self._num_state_qubits

def _define(self):
"""Populates self.definition with a decomposition of this gate."""
from qiskit.synthesis.arithmetic import adder_ripple_c04

self.definition = adder_ripple_c04(self.num_state_qubits, kind="full")
Cryoris marked this conversation as resolved.
Show resolved Hide resolved
9 changes: 9 additions & 0 deletions qiskit/circuit/library/arithmetic/multipliers/multiplier.py
Original file line number Diff line number Diff line change
@@ -190,3 +190,12 @@ def num_result_qubits(self) -> int:
The number of result qubits.
"""
return self._num_result_qubits

def _define(self):
"""Populates self.definition with some decomposition of this gate."""
from qiskit.synthesis.arithmetic import multiplier_qft_r17

# This particular decomposition does not use any ancilla qubits.
# Note that the transpiler may choose a different decomposition
# based on the number of ancilla qubits available.
self.definition = multiplier_qft_r17(self.num_state_qubits)
101 changes: 83 additions & 18 deletions qiskit/transpiler/passes/synthesis/hls_plugins.py
Original file line number Diff line number Diff line change
@@ -300,13 +300,18 @@
- :class:`.ModularAdderSynthesisD00`
- 0
- a QFT-based adder
* - ``"default"``
- :class:`~.ModularAdderSynthesisDefault`
- any
- chooses the best algorithm based on the ancillas available

.. autosummary::
:toctree: ../stubs/

ModularAdderSynthesisC04
ModularAdderSynthesisD00
ModularAdderSynthesisV95
ModularAdderSynthesisDefault

Half Adder Synthesis
''''''''''''''''''''
@@ -330,13 +335,18 @@
- :class:`.HalfAdderSynthesisD00`
- 0
- a QFT-based adder
* - ``"default"``
- :class:`~.HalfAdderSynthesisDefault`
- any
- chooses the best algorithm based on the ancillas available

.. autosummary::
:toctree: ../stubs/

HalfAdderSynthesisC04
HalfAdderSynthesisD00
HalfAdderSynthesisV95
HalfAdderSynthesisDefault

Full Adder Synthesis
''''''''''''''''''''
@@ -356,12 +366,17 @@
- :class:`.FullAdderSynthesisV95`
- :math:`n-1`, for :math:`n`-bit numbers
- a ripple-carry adder
* - ``"default"``
- :class:`~.FullAdderSynthesisDefault`
- any
- chooses the best algorithm based on the ancillas available

.. autosummary::
:toctree: ../stubs/

FullAdderSynthesisC04
FullAdderSynthesisV95
FullAdderSynthesisDefault


Multiplier Synthesis
@@ -1212,10 +1227,26 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **
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")
# For up to 5 qubits, the QFT-based adder is best
Cryoris marked this conversation as resolved.
Show resolved Hide resolved
if high_level_object.num_state_qubits <= 5:
decomposition = ModularAdderSynthesisD00().run(
high_level_object, coupling_map, target, qubits, **options
)
if decomposition is not None:
return decomposition

return adder_qft_d00(high_level_object.num_state_qubits, kind="fixed")
# Otherwise, the following decomposition is best (if there are enough ancillas)
if (
decomposition := ModularAdderSynthesisC04().run(
high_level_object, coupling_map, target, qubits, **options
)
) is not None:
return decomposition

# Otherwise, use the QFT-adder again
return ModularAdderSynthesisD00().run(
high_level_object, coupling_map, target, qubits, **options
)


class ModularAdderSynthesisC04(HighLevelSynthesisPlugin):
@@ -1264,8 +1295,8 @@ 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 > options.get("num_clean_ancillas", 1):
# The synthesis method needs n-1 clean ancilla qubits
if num_state_qubits - 1 > options.get("num_clean_ancillas", 0):
return None

return adder_ripple_v95(num_state_qubits, kind="fixed")
@@ -1309,10 +1340,24 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **
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")
# For up to 3 qubits, ripple_v95 is better (if there are enough ancilla qubits)
if high_level_object.num_state_qubits <= 3:
decomposition = HalfAdderSynthesisV95().run(
high_level_object, coupling_map, target, qubits, **options
)
if decomposition is not None:
return decomposition

return adder_qft_d00(high_level_object.num_state_qubits, kind="half")
# The next best option is to use ripple_c04 (if there are enough ancilla qubits)
if (
decomposition := HalfAdderSynthesisC04().run(
high_level_object, coupling_map, target, qubits, **options
)
) is not None:
return decomposition

# The QFT-based adder does not require ancilla qubits and should always succeed
return HalfAdderSynthesisD00.run(high_level_object, coupling_map, target, qubits, **options)
Cryoris marked this conversation as resolved.
Show resolved Hide resolved


class HalfAdderSynthesisC04(HighLevelSynthesisPlugin):
@@ -1360,8 +1405,8 @@ 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 > options.get("num_clean_ancillas", 1):
# The synthesis method needs n-1 clean ancilla qubits
if num_state_qubits - 1 > options.get("num_clean_ancillas", 0):
return None

return adder_ripple_v95(num_state_qubits, kind="half")
@@ -1381,18 +1426,38 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **
return adder_qft_d00(high_level_object.num_state_qubits, kind="half")


class FullAdderSynthesisC04(HighLevelSynthesisPlugin):
class FullAdderSynthesisDefault(HighLevelSynthesisPlugin):
"""A ripple-carry adder with a carry-in and a carry-out bit.

This plugin name is:``FullAdder.ripple_c04`` which can be used as the key on
This plugin name is:``FullAdder.default`` 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.
def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options):
if not isinstance(high_level_object, FullAdderGate):
return None

The plugin supports the following plugin-specific options:
# FullAdderSynthesisC04 requires no ancilla qubits and returns better results
# than FullAdderSynthesisV95 in all cases except for n=1.
if high_level_object.num_state_qubits == 1:
decomposition = FullAdderSynthesisV95().run(
high_level_object, coupling_map, target, qubits, **options
)
if decomposition is not None:
return decomposition
Cryoris marked this conversation as resolved.
Show resolved Hide resolved

* ``num_clean_ancillas``: The number of clean auxiliary qubits available.
return FullAdderSynthesisC04().run(
high_level_object, coupling_map, target, qubits, **options
)


class FullAdderSynthesisC04(HighLevelSynthesisPlugin):
"""A ripple-carry adder with a carry-in and a carry-out bit.

This plugin name is:``FullAdder.ripple_c04`` which can be used as the key on
an :class:`~.HLSConfig` object to use this method with :class:`~.HighLevelSynthesis`.

This plugin requires no auxiliary qubits.
"""

def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options):
@@ -1409,7 +1474,7 @@ class FullAdderSynthesisV95(HighLevelSynthesisPlugin):
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.
least :math:`n-1` clean auxiliary qubits.

The plugin supports the following plugin-specific options:

@@ -1422,8 +1487,8 @@ 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 > options.get("num_clean_ancillas", 1):
# The synthesis method needs n-1 clean ancilla qubits
if num_state_qubits - 1 > options.get("num_clean_ancillas", 0):
return None

return adder_ripple_v95(num_state_qubits, kind="full")
20 changes: 20 additions & 0 deletions releasenotes/notes/fix-adder-gates-39cf3d5f683e8880.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
features_transpiler:
- |
Added :class:`.FullAdderSynthesisDefault` plugin that chooses the best decomposition
for :class:`.FullAdderGate` based on the number of clean ancilla qubits
available.
- |
Improved :class:`.HalfAdderSynthesisDefault` and :class:`.ModularAdderSynthesisDefault`
plugins for :class:`.HalfAdderGate` and :class:`.ModularAdderGate` respectively,
choosing the best decomposition based on the number of clean ancilla qubits available.
Cryoris marked this conversation as resolved.
Show resolved Hide resolved
fixes:
- |
Adding default definitions for :class:`.FullAdderGate`, :class:`.HalfAdderGate`,
:class:`.ModularAdderGate` and :class:`.MultiplierGate` gates, allowing to
contruct :class:`qiskit.quantum_info.Operator`\s from quantum circuits
Cryoris marked this conversation as resolved.
Show resolved Hide resolved
containing these gates.
- |
Fixed the number of clean ancilla qubits required by
:class:`.FullAdderSynthesisV95`, :class:`.HalfAdderSynthesisV95`, and
:class:`.ModularAdderSynthesisV95` plugins.
31 changes: 30 additions & 1 deletion test/python/circuit/library/test_adders.py
Original file line number Diff line number Diff line change
@@ -168,7 +168,7 @@ def test_raises_on_wrong_num_bits(self, adder):
_ = adder(-1)

def test_plugins(self):
"""Test setting the HLS plugins for the modular adder."""
"""Test calling HLS plugins for various adder types."""

# all gates with the plugins we check
modes = {
@@ -204,6 +204,35 @@ def test_plugins(self):

self.assertTrue(expected_ops[plugin] in ops)

def test_plugins_when_do_not_apply(self):
"""Test that plugins do not do anything when not enough
clean ancilla qubits are available.
"""
with self.subTest(name="FullAdder"):
adder = FullAdderGate(3)
circuit = QuantumCircuit(9)
circuit.append(adder, range(adder.num_qubits))
hls_config = HLSConfig(FullAdder=["ripple_v95"])
hls = HighLevelSynthesis(hls_config=hls_config)
synth = hls(circuit)
self.assertEqual(synth.count_ops(), {"FullAdder": 1})
with self.subTest(name="HalfAdder"):
adder = HalfAdderGate(3)
circuit = QuantumCircuit(8)
circuit.append(adder, range(adder.num_qubits))
hls_config = HLSConfig(HalfAdder=["ripple_v95"])
hls = HighLevelSynthesis(hls_config=hls_config)
synth = hls(circuit)
self.assertEqual(synth.count_ops(), {"HalfAdder": 1})
with self.subTest(name="ModularAdder"):
adder = ModularAdderGate(3)
circuit = QuantumCircuit(7)
circuit.append(adder, range(adder.num_qubits))
hls_config = HLSConfig(ModularAdder=["ripple_v95"])
hls = HighLevelSynthesis(hls_config=hls_config)
synth = hls(circuit)
self.assertEqual(synth.count_ops(), {"ModularAdder": 1})


if __name__ == "__main__":
unittest.main()
40 changes: 40 additions & 0 deletions test/python/circuit/test_gate_definitions.py
Original file line number Diff line number Diff line change
@@ -65,6 +65,11 @@
CSXGate,
RVGate,
XXMinusYYGate,
FullAdderGate,
HalfAdderGate,
ModularAdderGate,
LinearFunction,
MultiplierGate,
)
from qiskit.circuit.library.standard_gates.equivalence_library import (
StandardEquivalenceLibrary as std_eqlib,
@@ -199,6 +204,41 @@ def test_ucpaulirotgate_repeat(self):
operator = Operator(gate)
self.assertTrue(np.allclose(Operator(gate.repeat(2)), operator @ operator))

def test_linear_function_definition(self):
"""Test LinearFunction gate matrix and definition."""
circ = QuantumCircuit(3)
circ.append(LinearFunction([[1, 1], [0, 1]]), [0, 2])
decomposed_circ = circ.decompose()
self.assertTrue(Operator(circ).equiv(Operator(decomposed_circ)))

def test_full_adder_definition(self):
"""Test FullAdder gate matrix and definition."""
circ = QuantumCircuit(4)
circ.append(FullAdderGate(1), [0, 1, 2, 3])
decomposed_circ = circ.decompose()
self.assertTrue(Operator(circ).equiv(Operator(decomposed_circ)))

def test_half_adder_definition(self):
"""Test HalfAdder gate matrix and definition."""
circ = QuantumCircuit(3)
circ.append(HalfAdderGate(1), [0, 1, 2])
decomposed_circ = circ.decompose()
self.assertTrue(Operator(circ).equiv(Operator(decomposed_circ)))

def test_modular_adder_definition(self):
"""Test ModularAdder gate matrix and definition."""
circ = QuantumCircuit(2)
circ.append(ModularAdderGate(1), [0, 1])
decomposed_circ = circ.decompose()
self.assertTrue(Operator(circ).equiv(Operator(decomposed_circ)))

def test_multiplier_gate_definition(self):
"""Test Multiplier gate matrix and definition."""
circ = QuantumCircuit(4)
circ.append(MultiplierGate(1), [0, 1, 2, 3])
decomposed_circ = circ.decompose()
self.assertTrue(Operator(circ).equiv(Operator(decomposed_circ)))


@ddt
class TestStandardGates(QiskitTestCase):
Loading