From 84f7ab34b9ff334fb47c19d2f4f4705e971504e6 Mon Sep 17 00:00:00 2001 From: Alexander Ivrii Date: Thu, 5 Dec 2024 15:30:00 +0200 Subject: [PATCH 1/7] 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. --- pyproject.toml | 2 +- .../library/arithmetic/adders/adder.py | 24 ++++ .../passes/synthesis/hls_plugins.py | 104 +++++++++++++++--- test/python/circuit/test_gate_definitions.py | 32 ++++++ 4 files changed, 143 insertions(+), 19 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index bdabeda6f146..e08b0e042377 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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" diff --git a/qiskit/circuit/library/arithmetic/adders/adder.py b/qiskit/circuit/library/arithmetic/adders/adder.py index 7fa3411d0436..19b6e35ac720 100644 --- a/qiskit/circuit/library/arithmetic/adders/adder.py +++ b/qiskit/circuit/library/arithmetic/adders/adder.py @@ -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") diff --git a/qiskit/transpiler/passes/synthesis/hls_plugins.py b/qiskit/transpiler/passes/synthesis/hls_plugins.py index a609b11f0fef..c581793e09a8 100644 --- a/qiskit/transpiler/passes/synthesis/hls_plugins.py +++ b/qiskit/transpiler/passes/synthesis/hls_plugins.py @@ -300,6 +300,11 @@ - :class:`.ModularAdderSynthesisD00` - 0 - a QFT-based adder + * - ``"default"`` + - :class:`~.ModularAdderSynthesisDefault` + - any + - any + - chooses the best algorithm based on the ancillas available .. autosummary:: :toctree: ../stubs/ @@ -307,6 +312,7 @@ ModularAdderSynthesisC04 ModularAdderSynthesisD00 ModularAdderSynthesisV95 + ModularAdderSynthesisDefault Half Adder Synthesis '''''''''''''''''''' @@ -330,6 +336,11 @@ - :class:`.HalfAdderSynthesisD00` - 0 - a QFT-based adder + * - ``"default"`` + - :class:`~.HalfAdderSynthesisDefault` + - any + - any + - chooses the best algorithm based on the ancillas available .. autosummary:: :toctree: ../stubs/ @@ -337,6 +348,7 @@ HalfAdderSynthesisC04 HalfAdderSynthesisD00 HalfAdderSynthesisV95 + HalfAdderSynthesisDefault Full Adder Synthesis '''''''''''''''''''' @@ -356,12 +368,18 @@ - :class:`.FullAdderSynthesisV95` - :math:`n-1`, for :math:`n`-bit numbers - a ripple-carry adder + * - ``"default"`` + - :class:`~.FullAdderSynthesisDefault` + - any + - any + - chooses the best algorithm based on the ancillas available .. autosummary:: :toctree: ../stubs/ FullAdderSynthesisC04 FullAdderSynthesisV95 + FullAdderSynthesisDefault Multiplier Synthesis @@ -1212,10 +1230,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): @@ -1264,8 +1298,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 +1343,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) class HalfAdderSynthesisC04(HighLevelSynthesisPlugin): @@ -1360,8 +1408,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 +1429,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): @@ -1409,7 +1477,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 +1490,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") diff --git a/test/python/circuit/test_gate_definitions.py b/test/python/circuit/test_gate_definitions.py index 3497ef9fe46a..c27b8c8b293f 100644 --- a/test/python/circuit/test_gate_definitions.py +++ b/test/python/circuit/test_gate_definitions.py @@ -65,6 +65,10 @@ CSXGate, RVGate, XXMinusYYGate, + FullAdderGate, + HalfAdderGate, + ModularAdderGate, + LinearFunction, ) from qiskit.circuit.library.standard_gates.equivalence_library import ( StandardEquivalenceLibrary as std_eqlib, @@ -199,6 +203,34 @@ 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))) + @ddt class TestStandardGates(QiskitTestCase): From 6aad5e8cf1a1e3009442ef87e842873039cfc891 Mon Sep 17 00:00:00 2001 From: Alexander Ivrii Date: Thu, 5 Dec 2024 16:20:41 +0200 Subject: [PATCH 2/7] Adding tests for the case that adder plugins do not apply --- test/python/circuit/library/test_adders.py | 31 +++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/test/python/circuit/library/test_adders.py b/test/python/circuit/library/test_adders.py index eacf8057775a..64e2bee3aee8 100644 --- a/test/python/circuit/library/test_adders.py +++ b/test/python/circuit/library/test_adders.py @@ -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() From 546545eb5ebc907962a5bc551f816d885dfee7ff Mon Sep 17 00:00:00 2001 From: Alexander Ivrii Date: Thu, 5 Dec 2024 16:31:17 +0200 Subject: [PATCH 3/7] Constructing Operators from circuits with MultiplierGates --- .../circuit/library/arithmetic/multipliers/multiplier.py | 9 +++++++++ test/python/circuit/test_gate_definitions.py | 8 ++++++++ 2 files changed, 17 insertions(+) diff --git a/qiskit/circuit/library/arithmetic/multipliers/multiplier.py b/qiskit/circuit/library/arithmetic/multipliers/multiplier.py index 4089cc35452a..38f362cf34d3 100644 --- a/qiskit/circuit/library/arithmetic/multipliers/multiplier.py +++ b/qiskit/circuit/library/arithmetic/multipliers/multiplier.py @@ -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) diff --git a/test/python/circuit/test_gate_definitions.py b/test/python/circuit/test_gate_definitions.py index c27b8c8b293f..1b78861c6c00 100644 --- a/test/python/circuit/test_gate_definitions.py +++ b/test/python/circuit/test_gate_definitions.py @@ -69,6 +69,7 @@ HalfAdderGate, ModularAdderGate, LinearFunction, + MultiplierGate, ) from qiskit.circuit.library.standard_gates.equivalence_library import ( StandardEquivalenceLibrary as std_eqlib, @@ -231,6 +232,13 @@ def test_modular_adder_definition(self): 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): From 8b38e4fb10a3d6aa66495b3365b1a5c6726ab005 Mon Sep 17 00:00:00 2001 From: Alexander Ivrii Date: Thu, 5 Dec 2024 16:48:45 +0200 Subject: [PATCH 4/7] release notes --- .../fix-adder-gates-39cf3d5f683e8880.yaml | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 releasenotes/notes/fix-adder-gates-39cf3d5f683e8880.yaml diff --git a/releasenotes/notes/fix-adder-gates-39cf3d5f683e8880.yaml b/releasenotes/notes/fix-adder-gates-39cf3d5f683e8880.yaml new file mode 100644 index 000000000000..468be621f9e0 --- /dev/null +++ b/releasenotes/notes/fix-adder-gates-39cf3d5f683e8880.yaml @@ -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. +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 + containing these gates. + - | + Fixed the number of clean ancilla qubits required by + :class:`.FullAdderSynthesisV95`, :class:`.HalfAdderSynthesisV95`, and + :class:`.ModularAdderSynthesisV95` plugins. From 2553f77713e900c2ffd7d3ff089c8a5babf68ad0 Mon Sep 17 00:00:00 2001 From: Alexander Ivrii Date: Fri, 6 Dec 2024 08:56:22 +0200 Subject: [PATCH 5/7] docstring fix --- qiskit/transpiler/passes/synthesis/hls_plugins.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/qiskit/transpiler/passes/synthesis/hls_plugins.py b/qiskit/transpiler/passes/synthesis/hls_plugins.py index c581793e09a8..e68a55236445 100644 --- a/qiskit/transpiler/passes/synthesis/hls_plugins.py +++ b/qiskit/transpiler/passes/synthesis/hls_plugins.py @@ -303,7 +303,6 @@ * - ``"default"`` - :class:`~.ModularAdderSynthesisDefault` - any - - any - chooses the best algorithm based on the ancillas available .. autosummary:: @@ -339,7 +338,6 @@ * - ``"default"`` - :class:`~.HalfAdderSynthesisDefault` - any - - any - chooses the best algorithm based on the ancillas available .. autosummary:: @@ -371,7 +369,6 @@ * - ``"default"`` - :class:`~.FullAdderSynthesisDefault` - any - - any - chooses the best algorithm based on the ancillas available .. autosummary:: From 0398a1f4187009c4c9ea9be96efdc54c4abd5913 Mon Sep 17 00:00:00 2001 From: Alexander Ivrii Date: Wed, 11 Dec 2024 14:30:43 +0200 Subject: [PATCH 6/7] apply suggestions from code review --- .../library/arithmetic/adders/adder.py | 1 + .../passes/synthesis/hls_plugins.py | 4 +- .../fix-adder-gates-39cf3d5f683e8880.yaml | 19 ++++--- test/python/circuit/library/test_adders.py | 51 +++++++++++++++++++ 4 files changed, 64 insertions(+), 11 deletions(-) diff --git a/qiskit/circuit/library/arithmetic/adders/adder.py b/qiskit/circuit/library/arithmetic/adders/adder.py index 19b6e35ac720..435fab109476 100644 --- a/qiskit/circuit/library/arithmetic/adders/adder.py +++ b/qiskit/circuit/library/arithmetic/adders/adder.py @@ -231,4 +231,5 @@ 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") diff --git a/qiskit/transpiler/passes/synthesis/hls_plugins.py b/qiskit/transpiler/passes/synthesis/hls_plugins.py index e68a55236445..eef996e636eb 100644 --- a/qiskit/transpiler/passes/synthesis/hls_plugins.py +++ b/qiskit/transpiler/passes/synthesis/hls_plugins.py @@ -1357,7 +1357,9 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=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) + return HalfAdderSynthesisD00().run( + high_level_object, coupling_map, target, qubits, **options + ) class HalfAdderSynthesisC04(HighLevelSynthesisPlugin): diff --git a/releasenotes/notes/fix-adder-gates-39cf3d5f683e8880.yaml b/releasenotes/notes/fix-adder-gates-39cf3d5f683e8880.yaml index 468be621f9e0..cbcc943b93b9 100644 --- a/releasenotes/notes/fix-adder-gates-39cf3d5f683e8880.yaml +++ b/releasenotes/notes/fix-adder-gates-39cf3d5f683e8880.yaml @@ -1,16 +1,7 @@ --- -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. fixes: - | - Adding default definitions for :class:`.FullAdderGate`, :class:`.HalfAdderGate`, + Added 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 containing these gates. @@ -18,3 +9,11 @@ fixes: 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. diff --git a/test/python/circuit/library/test_adders.py b/test/python/circuit/library/test_adders.py index 64e2bee3aee8..de838410b095 100644 --- a/test/python/circuit/library/test_adders.py +++ b/test/python/circuit/library/test_adders.py @@ -233,6 +233,57 @@ def test_plugins_when_do_not_apply(self): synth = hls(circuit) self.assertEqual(synth.count_ops(), {"ModularAdder": 1}) + def test_default_plugins(self): + """Tests covering different branches in the default synthesis plugins..""" + with self.subTest(name="HalfAdderTest1"): + adder = HalfAdderGate(3) + circuit = QuantumCircuit(9) + circuit.append(adder, range(7)) + hls = HighLevelSynthesis() + _ = hls(circuit) + with self.subTest(name="HalfAdderTest2"): + adder = HalfAdderGate(4) + circuit = QuantumCircuit(12) + circuit.append(adder, range(9)) + hls = HighLevelSynthesis() + _ = hls(circuit) + with self.subTest(name="HalfAdderTest3"): + adder = HalfAdderGate(4) + circuit = QuantumCircuit(9) + circuit.append(adder, range(9)) + hls = HighLevelSynthesis() + _ = hls(circuit) + with self.subTest(name="FullAdderTest1"): + adder = FullAdderGate(4) + circuit = QuantumCircuit(10) + circuit.append(adder, range(10)) + hls = HighLevelSynthesis() + _ = hls(circuit) + with self.subTest(name="FullAdderTest2"): + adder = FullAdderGate(1) + circuit = QuantumCircuit(10) + circuit.append(adder, range(4)) + hls = HighLevelSynthesis() + _ = hls(circuit) + with self.subTest(name="ModularAdderTest1"): + adder = ModularAdderGate(4) + circuit = QuantumCircuit(8) + circuit.append(adder, range(8)) + hls = HighLevelSynthesis() + _ = hls(circuit) + with self.subTest(name="ModularAdderTest2"): + adder = ModularAdderGate(6) + circuit = QuantumCircuit(12) + circuit.append(adder, range(12)) + hls = HighLevelSynthesis() + _ = hls(circuit) + with self.subTest(name="ModularAdderTest3"): + adder = ModularAdderGate(6) + circuit = QuantumCircuit(16) + circuit.append(adder, range(12)) + hls = HighLevelSynthesis() + _ = hls(circuit) + if __name__ == "__main__": unittest.main() From c39d08e0f6f91b1d80ddead3f15078b46219670a Mon Sep 17 00:00:00 2001 From: Alexander Ivrii Date: Thu, 12 Dec 2024 11:18:32 +0200 Subject: [PATCH 7/7] futher improving tests based on additional review comments --- .../fix-adder-gates-39cf3d5f683e8880.yaml | 3 +- test/python/circuit/library/test_adders.py | 54 +++++++++++++------ 2 files changed, 38 insertions(+), 19 deletions(-) diff --git a/releasenotes/notes/fix-adder-gates-39cf3d5f683e8880.yaml b/releasenotes/notes/fix-adder-gates-39cf3d5f683e8880.yaml index cbcc943b93b9..d4b792062352 100644 --- a/releasenotes/notes/fix-adder-gates-39cf3d5f683e8880.yaml +++ b/releasenotes/notes/fix-adder-gates-39cf3d5f683e8880.yaml @@ -3,8 +3,7 @@ fixes: - | Added 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 - containing these gates. + contruct :class:`.Operator`\s from quantum circuits containing these gates. - | Fixed the number of clean ancilla qubits required by :class:`.FullAdderSynthesisV95`, :class:`.HalfAdderSynthesisV95`, and diff --git a/test/python/circuit/library/test_adders.py b/test/python/circuit/library/test_adders.py index de838410b095..5c960cedb171 100644 --- a/test/python/circuit/library/test_adders.py +++ b/test/python/circuit/library/test_adders.py @@ -234,55 +234,75 @@ def test_plugins_when_do_not_apply(self): self.assertEqual(synth.count_ops(), {"ModularAdder": 1}) def test_default_plugins(self): - """Tests covering different branches in the default synthesis plugins..""" - with self.subTest(name="HalfAdderTest1"): + """Tests covering different branches in the default synthesis plugins.""" + + # Test's name indicates which synthesis method should get used. + with self.subTest(name="HalfAdder_use_ripple_v95"): adder = HalfAdderGate(3) circuit = QuantumCircuit(9) circuit.append(adder, range(7)) hls = HighLevelSynthesis() - _ = hls(circuit) - with self.subTest(name="HalfAdderTest2"): + synth = hls(circuit) + ops = set(synth.count_ops().keys()) + self.assertTrue("Carry" in ops) + with self.subTest(name="HalfAdder_use_ripple_c04"): adder = HalfAdderGate(4) circuit = QuantumCircuit(12) circuit.append(adder, range(9)) hls = HighLevelSynthesis() - _ = hls(circuit) - with self.subTest(name="HalfAdderTest3"): + synth = hls(circuit) + ops = set(synth.count_ops().keys()) + self.assertTrue("MAJ" in ops) + with self.subTest(name="HalfAdder_use_qft_d00"): adder = HalfAdderGate(4) circuit = QuantumCircuit(9) circuit.append(adder, range(9)) hls = HighLevelSynthesis() - _ = hls(circuit) - with self.subTest(name="FullAdderTest1"): + synth = hls(circuit) + ops = set(synth.count_ops().keys()) + self.assertTrue("cp" in ops) + + with self.subTest(name="FullAdder_use_ripple_c04"): adder = FullAdderGate(4) circuit = QuantumCircuit(10) circuit.append(adder, range(10)) hls = HighLevelSynthesis() - _ = hls(circuit) - with self.subTest(name="FullAdderTest2"): + synth = hls(circuit) + ops = set(synth.count_ops().keys()) + self.assertTrue("MAJ" in ops) + with self.subTest(name="FullAdder_use_ripple_v95"): adder = FullAdderGate(1) circuit = QuantumCircuit(10) circuit.append(adder, range(4)) hls = HighLevelSynthesis() - _ = hls(circuit) - with self.subTest(name="ModularAdderTest1"): + synth = hls(circuit) + ops = set(synth.count_ops().keys()) + self.assertTrue("Carry" in ops) + + with self.subTest(name="ModularAdder_use_qft_d00"): adder = ModularAdderGate(4) circuit = QuantumCircuit(8) circuit.append(adder, range(8)) hls = HighLevelSynthesis() - _ = hls(circuit) - with self.subTest(name="ModularAdderTest2"): + synth = hls(circuit) + ops = set(synth.count_ops().keys()) + self.assertTrue("cp" in ops) + with self.subTest(name="ModularAdder_also_use_qft_d00"): adder = ModularAdderGate(6) circuit = QuantumCircuit(12) circuit.append(adder, range(12)) hls = HighLevelSynthesis() - _ = hls(circuit) - with self.subTest(name="ModularAdderTest3"): + synth = hls(circuit) + ops = set(synth.count_ops().keys()) + self.assertTrue("cp" in ops) + with self.subTest(name="ModularAdder_use_ripple_c04"): adder = ModularAdderGate(6) circuit = QuantumCircuit(16) circuit.append(adder, range(12)) hls = HighLevelSynthesis() - _ = hls(circuit) + synth = hls(circuit) + ops = set(synth.count_ops().keys()) + self.assertTrue("MAJ" in ops) if __name__ == "__main__":