Skip to content

Commit

Permalink
Minor fixes for new adder and multiplier gates classes (#13530)
Browse files Browse the repository at this point in the history
* Fixes to AdderGate classes

Added define method to HalfAdder, FullAdder, and ModularAdder
classes to allow constructing Operators from quantum circuits
containing such adder gates.

Fixing plugins related to adder_ripple_v95, the plugins can be used
only if n-1 clean ancillas are available.

Improved the default adder plugins to choose the best decomposition
based on the number of state qubits and the number of ancilla qubits.

* Adding tests for the case that adder plugins do not apply

* Constructing Operators from circuits with MultiplierGates

* release notes

* docstring fix

* apply suggestions from code review

* futher improving tests based on additional review comments
  • Loading branch information
alexanderivrii authored Dec 12, 2024
1 parent 17a2ccc commit b9ea266
Show file tree
Hide file tree
Showing 7 changed files with 279 additions and 20 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
25 changes: 25 additions & 0 deletions qiskit/circuit/library/arithmetic/adders/adder.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -208,3 +226,10 @@ 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

# In the case of a full adder, this method does not use any ancilla qubits
self.definition = adder_ripple_c04(self.num_state_qubits, kind="full")
9 changes: 9 additions & 0 deletions qiskit/circuit/library/arithmetic/multipliers/multiplier.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
103 changes: 85 additions & 18 deletions qiskit/transpiler/passes/synthesis/hls_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
''''''''''''''''''''
Expand All @@ -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
''''''''''''''''''''
Expand All @@ -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
Expand Down Expand Up @@ -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
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):
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -1309,10 +1340,26 @@ 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
)


class HalfAdderSynthesisC04(HighLevelSynthesisPlugin):
Expand Down Expand Up @@ -1360,8 +1407,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")
Expand All @@ -1381,18 +1428,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

return FullAdderSynthesisC04().run(
high_level_object, coupling_map, target, qubits, **options
)

* ``num_clean_ancillas``: The number of clean auxiliary qubits available.

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):
Expand All @@ -1409,7 +1476,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:
Expand All @@ -1422,8 +1489,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")
Expand Down
18 changes: 18 additions & 0 deletions releasenotes/notes/fix-adder-gates-39cf3d5f683e8880.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
fixes:
- |
Added default definitions for :class:`.FullAdderGate`, :class:`.HalfAdderGate`,
:class:`.ModularAdderGate` and :class:`.MultiplierGate` gates, allowing to
contruct :class:`.Operator`\s from quantum circuits containing these gates.
- |
Fixed the number of clean ancilla qubits required by
:class:`.FullAdderSynthesisV95`, :class:`.HalfAdderSynthesisV95`, and
:class:`.ModularAdderSynthesisV95` plugins.
- |
Added missing :class:`.FullAdderSynthesisDefault` plugin that chooses the best
decomposition for :class:`.FullAdderGate` based on the number of clean ancilla qubits
available.
- |
Fixed :class:`.HalfAdderSynthesisDefault` and :class:`.ModularAdderSynthesisDefault`
plugins, for :class:`.HalfAdderGate` and :class:`.ModularAdderGate` respectively,
to choose the best decomposition based on the number of clean ancilla qubits available.
Loading

0 comments on commit b9ea266

Please sign in to comment.